From a9a80a192e3297311027ef1b4883d4a05944375f Mon Sep 17 00:00:00 2001 From: Nino Date: Tue, 16 Dec 2025 17:26:28 +0100 Subject: [PATCH] feature(libs-remission): Improvements and Refactoring of Remission List Component Ref: #5340 --- .../inject-empty-search-result-handler.ts | 185 +++++++++++++ .../src/lib/remission-action/index.ts | 6 + .../remission-action.component.html | 21 ++ .../remission-action.component.scss | 0 .../remission-action.component.ts | 124 +++++++++ .../remission-action.service.ts | 217 +++++++++++++++ .../src/lib/remission-list.component.html | 27 +- .../src/lib/remission-list.component.ts | 250 +++--------------- 8 files changed, 595 insertions(+), 235 deletions(-) create mode 100644 libs/remission/feature/remission-list/src/lib/injects/inject-empty-search-result-handler.ts create mode 100644 libs/remission/feature/remission-list/src/lib/remission-action/index.ts create mode 100644 libs/remission/feature/remission-list/src/lib/remission-action/remission-action.component.html create mode 100644 libs/remission/feature/remission-list/src/lib/remission-action/remission-action.component.scss create mode 100644 libs/remission/feature/remission-list/src/lib/remission-action/remission-action.component.ts create mode 100644 libs/remission/feature/remission-list/src/lib/remission-action/remission-action.service.ts diff --git a/libs/remission/feature/remission-list/src/lib/injects/inject-empty-search-result-handler.ts b/libs/remission/feature/remission-list/src/lib/injects/inject-empty-search-result-handler.ts new file mode 100644 index 000000000..973d267cd --- /dev/null +++ b/libs/remission/feature/remission-list/src/lib/injects/inject-empty-search-result-handler.ts @@ -0,0 +1,185 @@ +import { + effect, + Signal, + untracked, + ResourceStatus, + inject, +} from '@angular/core'; +import { injectDialog } from '@isa/ui/dialog'; +import { SearchItemToRemitDialogComponent } from '@isa/remission/shared/search-item-to-remit-dialog'; +import { RemissionStore, RemissionItem } from '@isa/remission/data-access'; + +/** + * Configuration for the empty search result handler. + * Provides all necessary signals and callbacks for handling empty search scenarios. + */ +export interface EmptySearchResultHandlerConfig { + /** + * Signal returning the status of the remission resource. + */ + remissionResourceStatus: Signal; + + /** + * Signal returning the status of the stock resource. + */ + stockResourceStatus: Signal; + + /** + * Signal returning the current search term. + */ + searchTerm: Signal; + + /** + * Signal returning the number of search hits. + */ + hits: Signal; + + /** + * Signal indicating whether there is a valid search term. + */ + hasValidSearchTerm: Signal; + + /** + * Signal indicating whether the search was triggered by user interaction. + */ + searchTriggeredByUser: Signal; + + /** + * Signal indicating whether a remission has been started. + */ + remissionStarted: Signal; + + /** + * Signal indicating whether the current list type is "Abteilung". + */ + isDepartment: Signal; + + /** + * Signal returning the first item in the list (for auto-preselection). + */ + firstItem: Signal; + + /** + * Callback to preselect a remission item. + */ + preselectItem: (item: RemissionItem) => void; + + /** + * Callback to remit items after dialog selection. + * @param options - Options for the remit operation + */ + remitItems: (options: { addItemFlow: boolean }) => Promise; + + /** + * Callback to navigate to the default remission list. + */ + navigateToDefaultList: () => Promise; + + /** + * Callback to reload the list and return data. + */ + reloadData: () => void; +} + +/** + * Creates an effect that handles scenarios where a search yields no or few results. + * + * This handler implements two behaviors: + * 1. **Auto-Preselection**: When exactly one item is found and remission is started, + * automatically preselects that item for convenience. + * 2. **Empty Search Dialog**: When no items are found after a user-initiated search, + * opens a dialog allowing the user to add items to remit. + * + * @param config - Configuration object containing all required signals and callbacks + * @returns The created effect (for potential cleanup if needed) + * + * @example + * ```typescript + * // In a component + * emptySearchEffect = injectEmptySearchResultHandler({ + * remissionResourceStatus: () => this.remissionResource.status(), + * stockResourceStatus: () => this.inStockResource.status(), + * searchTerm: this.searchTerm, + * hits: this.hits, + * // ... other config + * }); + * ``` + * + * @remarks + * - The effect tracks `remissionResourceStatus`, `stockResourceStatus`, and `searchTerm` + * - Other signals are accessed via `untracked()` to avoid unnecessary re-evaluations + * - The dialog subscription handles async flows for adding items to remission + */ +export const injectEmptySearchResultHandler = ( + config: EmptySearchResultHandlerConfig, +) => { + const store = inject(RemissionStore); + const searchItemToRemitDialog = injectDialog( + SearchItemToRemitDialogComponent, + ); + + return effect(() => { + const status = config.remissionResourceStatus(); + const stockStatus = config.stockResourceStatus(); + const searchTerm = config.searchTerm(); + + // Wait until both resources are resolved + if (status !== 'resolved' || stockStatus !== 'resolved') { + return; + } + + untracked(() => { + const hits = config.hits(); + + // Early return conditions - only proceed if: + // - No hits (hits === 0, so !!hits is false) + // - Valid search term exists + // - Search was triggered by user + if ( + !!hits || + !searchTerm || + !config.hasValidSearchTerm() || + !config.searchTriggeredByUser() + ) { + // #5338 - Auto-select item if exactly one hit after search + if (hits === 1 && config.remissionStarted()) { + store.clearSelectedItems(); + const firstItem = config.firstItem(); + if (firstItem) { + config.preselectItem(firstItem); + } + } + return; + } + + // Open dialog to allow user to add items when search returns no results + searchItemToRemitDialog({ + data: { + searchTerm, + }, + }).closed.subscribe(async (result) => { + store.clearSelectedItems(); + + if (result) { + if (config.remissionStarted()) { + // Select all items from dialog result + for (const item of result) { + if (item?.id) { + store.selectRemissionItem(item.id, item); + } + } + // Remit the selected items + await config.remitItems({ addItemFlow: true }); + } else if (config.isDepartment()) { + // Navigate to default list if in department mode without active remission + await config.navigateToDefaultList(); + return; + } + } + + // Always reload data after dialog closes + config.reloadData(); + }); + }); + }); +}; diff --git a/libs/remission/feature/remission-list/src/lib/remission-action/index.ts b/libs/remission/feature/remission-list/src/lib/remission-action/index.ts new file mode 100644 index 000000000..38a15c562 --- /dev/null +++ b/libs/remission/feature/remission-list/src/lib/remission-action/index.ts @@ -0,0 +1,6 @@ +export { RemissionActionComponent } from './remission-action.component'; +export { + RemissionActionService, + RemitItemsContext, + RemitItemsOptions, +} from './remission-action.service'; diff --git a/libs/remission/feature/remission-list/src/lib/remission-action/remission-action.component.html b/libs/remission/feature/remission-list/src/lib/remission-action/remission-action.component.html new file mode 100644 index 000000000..c52b7cb97 --- /dev/null +++ b/libs/remission/feature/remission-list/src/lib/remission-action/remission-action.component.html @@ -0,0 +1,21 @@ +@if (remissionStarted()) { + + +} diff --git a/libs/remission/feature/remission-list/src/lib/remission-action/remission-action.component.scss b/libs/remission/feature/remission-list/src/lib/remission-action/remission-action.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/libs/remission/feature/remission-list/src/lib/remission-action/remission-action.component.ts b/libs/remission/feature/remission-list/src/lib/remission-action/remission-action.component.ts new file mode 100644 index 000000000..f0c7469cb --- /dev/null +++ b/libs/remission/feature/remission-list/src/lib/remission-action/remission-action.component.ts @@ -0,0 +1,124 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + inject, + input, + output, +} from '@angular/core'; +import { StatefulButtonComponent } from '@isa/ui/buttons'; +import { + RemissionStore, + RemissionItem, + RemissionListType, +} from '@isa/remission/data-access'; +import { + RemissionActionService, + RemitItemsContext, + RemitItemsOptions, +} from './remission-action.service'; + +/** + * RemissionActionComponent + * + * Standalone component that encapsulates the "Remittieren" (remit) button + * and its associated logic. Manages the remit workflow including: + * - Displaying the stateful button with appropriate states + * - Triggering the remit action + * - Handling loading, success, and error states + * + * @remarks + * This component requires the RemissionActionService to be provided, + * either by itself or by a parent component. + * + * @example + * ```html + * + * ``` + */ +@Component({ + selector: 'remi-feature-remission-action', + templateUrl: './remission-action.component.html', + styleUrl: './remission-action.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [StatefulButtonComponent], + providers: [RemissionActionService], +}) +export class RemissionActionComponent { + readonly #store = inject(RemissionStore); + readonly actionService = inject(RemissionActionService); + + /** + * Function to get available stock for a remission item. + * Required for calculating quantities during remit. + */ + getAvailableStockForItem = input.required<(item: RemissionItem) => number>(); + + /** + * The currently selected remission list type. + * Required for determining item types during remit. + */ + selectedRemissionListType = input.required(); + + /** + * Additional disabled state from parent component. + * Combined with internal disabled logic. + */ + disabled = input(false); + + /** + * Emitted when the remit action is completed (success or error). + * Parent component should use this to reload list data. + */ + actionCompleted = output(); + + /** + * Computed signal indicating whether a remission has been started. + * The button is only visible when remission is started. + */ + remissionStarted = computed(() => this.#store.remissionStarted()); + + /** + * Computed signal indicating whether there are selected items. + */ + hasSelectedItems = computed( + () => Object.keys(this.#store.selectedItems()).length > 0, + ); + + /** + * Computed signal for the combined disabled state. + * Button is disabled when: + * - No items are selected + * - An external disabled condition is true + * - A remit operation is in progress + */ + isDisabled = computed( + () => + !this.hasSelectedItems() || + this.disabled() || + this.actionService.inProgress(), + ); + + /** + * Handles the remit button click. + * Delegates to the action service and emits completion event. + * + * @param options - Options for the remit operation + */ + async remitItems( + options: RemitItemsOptions = { addItemFlow: false }, + ): Promise { + const context: RemitItemsContext = { + getAvailableStockForItem: this.getAvailableStockForItem(), + selectedRemissionListType: this.selectedRemissionListType(), + }; + + await this.actionService.remitItems(context, options); + this.actionCompleted.emit(); + } +} diff --git a/libs/remission/feature/remission-list/src/lib/remission-action/remission-action.service.ts b/libs/remission/feature/remission-list/src/lib/remission-action/remission-action.service.ts new file mode 100644 index 000000000..ef131a4a1 --- /dev/null +++ b/libs/remission/feature/remission-list/src/lib/remission-action/remission-action.service.ts @@ -0,0 +1,217 @@ +import { inject, Injectable, signal } from '@angular/core'; +import { logger } from '@isa/core/logging'; +import { + RemissionStore, + RemissionReturnReceiptService, + RemissionListType, + RemissionResponseArgsErrorMessage, + RemissionItem, + getStockToRemit, + getItemType, +} from '@isa/remission/data-access'; +import { StatefulButtonState } from '@isa/ui/buttons'; +import { injectFeedbackErrorDialog } from '@isa/ui/dialog'; +import { firstValueFrom } from 'rxjs'; + +/** + * Configuration options for the remit items operation. + */ +export interface RemitItemsOptions { + /** Whether this operation is part of an add-item flow (e.g., from search dialog) */ + addItemFlow: boolean; +} + +/** + * Context required for remitting items. + * Provides stock information lookup for calculating quantities. + */ +export interface RemitItemsContext { + /** Function to get available stock for a remission item */ + getAvailableStockForItem: (item: RemissionItem) => number; + /** The currently selected remission list type */ + selectedRemissionListType: RemissionListType; +} + +/** + * Service responsible for handling the remission action workflow. + * Manages the state and logic for remitting selected items. + * + * This service encapsulates: + * - State management for the remit button (progress, error, success states) + * - The remitItems business logic + * - Error handling and user feedback + * - Navigation after successful remission + * + * @remarks + * This service should be provided at the component level, not root level, + * as it maintains UI state specific to a single remission action context. + * + * @example + * ```typescript + * // In component providers + * providers: [RemissionActionService] + * + * // Usage + * readonly actionService = inject(RemissionActionService); + * await this.actionService.remitItems(context); + * ``` + */ +@Injectable() +export class RemissionActionService { + readonly #store = inject(RemissionStore); + readonly #remissionReturnReceiptService = inject( + RemissionReturnReceiptService, + ); + readonly #errorDialog = injectFeedbackErrorDialog(); + + readonly #logger = logger(() => ({ + service: 'RemissionActionService', + })); + + /** + * Signal representing the current state of the remit button. + */ + readonly state = signal('default'); + + /** + * Signal containing the current error message, if any. + */ + readonly error = signal(null); + + /** + * Signal indicating whether a remit operation is currently in progress. + */ + readonly inProgress = signal(false); + + /** + * Computed signal indicating whether there are selected items in the store. + */ + get hasSelectedItems(): boolean { + return Object.keys(this.#store.selectedItems()).length > 0; + } + + /** + * Computed signal indicating whether a remission has been started. + */ + get remissionStarted(): boolean { + return this.#store.remissionStarted(); + } + + /** + * Initiates the process to remit selected items. + * + * If remission is already started, items are added directly to the remission. + * Handles the full workflow including: + * - Preventing duplicate operations + * - Processing each selected item + * - Error handling with user feedback + * - State management for UI feedback + * + * @param context - Context providing stock information and list type + * @param options - Options for the remit operation + * @returns A promise that resolves when the operation is complete + */ + async remitItems( + context: RemitItemsContext, + options: RemitItemsOptions = { addItemFlow: false }, + ): Promise { + if (this.inProgress()) { + return; + } + this.inProgress.set(true); + + try { + await this.#processSelectedItems(context, options); + this.state.set('success'); + } catch (error) { + await this.#handleRemitItemsError(error); + } + + this.#store.clearSelectedItems(); + this.inProgress.set(false); + } + + /** + * Processes all selected items for remission. + * @param context - Context providing stock information and list type + * @param options - Options for the remit operation + */ + async #processSelectedItems( + context: RemitItemsContext, + options: RemitItemsOptions, + ): Promise { + // #5273, #5280 Fix - Bei gestarteter Remission dürfen Items die über den AddItemDialog + // hinzugefügt und direkt remittiert werden, nur als ReturnItem (statt ReturnSuggestion) + // zum WBS hinzugefügt werden + const remissionListType = options.addItemFlow + ? RemissionListType.Pflicht + : context.selectedRemissionListType; + + const selected = this.#store.selectedItems(); + const quantities = this.#store.selectedQuantity(); + + for (const [remissionItemId, item] of Object.entries(selected)) { + const returnId = this.#store.returnId(); + const receiptId = this.#store.receiptId(); + const remissionItemIdNumber = Number(remissionItemId); + const quantity = quantities[remissionItemIdNumber]; + const inStock = context.getAvailableStockForItem(item); + const stockToRemit = getStockToRemit({ + remissionItem: item, + remissionListType, + availableStock: inStock, + }); + const quantityToRemit = quantity ?? stockToRemit; + + if (returnId && receiptId) { + await this.#remissionReturnReceiptService.remitItem({ + itemId: remissionItemIdNumber, + addItem: { + returnId, + receiptId, + quantity: quantityToRemit, + inStock, + impedimentComment: stockToRemit > quantity ? 'Restmenge' : '', + remainingQuantity: + isNaN(quantity) || inStock - quantity <= 0 + ? undefined + : inStock - quantity, + }, + type: getItemType(item, remissionListType), + }); + } + } + } + + /** + * Handles errors that occur during the remission of items. + * Logs the error, displays an error dialog, and updates state. + * + * @param error - The error object caught during the remission process + */ + async #handleRemitItemsError(error: unknown): Promise { + this.#logger.error('Failed to remit items', error as Error); + + const errorMessage = + (error as { error?: { message?: string }; message?: string })?.error + ?.message ?? + (error as { message?: string })?.message ?? + 'Artikel konnten nicht remittiert werden'; + + this.error.set(errorMessage); + + await firstValueFrom( + this.#errorDialog({ + data: { + errorMessage, + }, + }).closed, + ); + + if (errorMessage === RemissionResponseArgsErrorMessage.AlreadyCompleted) { + this.#store.clearState(); + } + + this.state.set('error'); + } +} diff --git a/libs/remission/feature/remission-list/src/lib/remission-list.component.html b/libs/remission/feature/remission-list/src/lib/remission-list.component.html index c31098221..7b62740a4 100644 --- a/libs/remission/feature/remission-list/src/lib/remission-list.component.html +++ b/libs/remission/feature/remission-list/src/lib/remission-list.component.html @@ -59,23 +59,10 @@ [class.scroll-top-button-spacing-bottom]="remissionStarted()" > -@if (remissionStarted()) { - - -} + diff --git a/libs/remission/feature/remission-list/src/lib/remission-list.component.ts b/libs/remission/feature/remission-list/src/lib/remission-list.component.ts index 5fb3e1667..8a2bda2ae 100644 --- a/libs/remission/feature/remission-list/src/lib/remission-list.component.ts +++ b/libs/remission/feature/remission-list/src/lib/remission-list.component.ts @@ -3,10 +3,9 @@ import { Component, inject, computed, - effect, - untracked, signal, linkedSignal, + viewChild, } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { @@ -29,12 +28,9 @@ import { createRemissionProductGroupResource, } from './resources'; import { injectRemissionListType } from './injects/inject-remission-list-type'; +import { injectEmptySearchResultHandler } from './injects/inject-empty-search-result-handler'; import { RemissionListItemComponent } from './remission-list-item/remission-list-item.component'; -import { - IconButtonComponent, - StatefulButtonComponent, - StatefulButtonState, -} from '@isa/ui/buttons'; +import { IconButtonComponent } from '@isa/ui/buttons'; import { ReturnItem, StockInfo, @@ -42,22 +38,16 @@ import { RemissionStore, RemissionItem, calculateAvailableStock, - RemissionReturnReceiptService, - getStockToRemit, RemissionListType, - RemissionResponseArgsErrorMessage, UpdateItem, orderByListItems, - getItemType, + getStockToRemit, } from '@isa/remission/data-access'; -import { injectDialog, injectFeedbackErrorDialog } from '@isa/ui/dialog'; -import { SearchItemToRemitDialogComponent } from '@isa/remission/shared/search-item-to-remit-dialog'; import { RemissionReturnCardComponent } from './remission-return-card/remission-return-card.component'; -import { logger } from '@isa/core/logging'; import { RemissionListDepartmentElementsComponent } from './remission-list-department-elements/remission-list-department-elements.component'; import { injectTabId } from '@isa/core/tabs'; import { RemissionListEmptyStateComponent } from './remission-list-empty-state/remission-list-empty-state.component'; -import { firstValueFrom } from 'rxjs'; +import { RemissionActionComponent } from './remission-action'; function querySettingsFactory() { return inject(ActivatedRoute).snapshot.data['querySettings']; @@ -96,7 +86,7 @@ function querySettingsFactory() { RemissionListSelectComponent, RemissionListItemComponent, IconButtonComponent, - StatefulButtonComponent, + RemissionActionComponent, RemissionListDepartmentElementsComponent, RemissionListEmptyStateComponent, ScrollTopButtonComponent, @@ -125,8 +115,10 @@ export class RemissionListComponent { */ activatedTabId = injectTabId(); - searchItemToRemitDialog = injectDialog(SearchItemToRemitDialogComponent); - errorDialog = injectFeedbackErrorDialog(); + /** + * Reference to the RemissionActionComponent for triggering remit actions. + */ + remissionAction = viewChild(RemissionActionComponent); /** * FilterService instance for managing filter state and queries. @@ -140,20 +132,6 @@ export class RemissionListComponent { */ #store = inject(RemissionStore); - /** - * RemissionReturnReceiptService instance for handling return receipt operations. - * @private - */ - #remissionReturnReceiptService = inject(RemissionReturnReceiptService); - - /** - * Logger instance for logging component events and errors. - * @private - */ - #logger = logger(() => ({ - component: 'RemissionListComponent', - })); - /** * Restores scroll position when navigating back to this component. */ @@ -294,32 +272,6 @@ export class RemissionListComponent { */ remissionStarted = computed(() => this.#store.remissionStarted()); - /** - * Computed signal indicating whether there are selected items in the remission store. - * @returns True if there are selected items, false otherwise. - */ - hasSelectedItems = computed(() => { - return Object.keys(this.#store.selectedItems()).length > 0; - }); - - /** - * Signal for the current remission list type. - * @returns The current RemissionListType. - */ - remitItemsState = signal('default'); - - /** - * Signal for any error messages related to remission items. - * @returns Error message string or null if no error. - */ - remitItemsError = signal(null); - - /** - * Signal indicating whether remission items are currently being processed. - * @returns True if in progress, false otherwise. - */ - remitItemsInProgress = signal(false); - /** * Commits the current filter state and triggers a new search. * @@ -414,132 +366,35 @@ export class RemissionListComponent { }); /** - * Effect that handles scenarios where a search yields no results. - * If the search was user-initiated and returned no hits, it opens a dialog - * to allow the user to add a new item to remit. - * If only one hit is found and a remission is started, it selects that item automatically. - * This effect runs whenever the remission or stock resource status changes, - * or when the search term changes. - * It ensures that the user is prompted appropriately based on their actions and the current state of the remission process. - * It also checks if the remission is started or if the list type is 'Abteilung' to determine navigation behavior. - * @see {@link - * https://angular.dev/guide/effects} for more information on Angular effects. - * @remarks This effect uses `untracked` to avoid unnecessary re-evaluations - * when accessing certain signals. + * Computed signal returning the first item in the list. + * Used for auto-preselection when exactly one item is found. */ - emptySearchResultEffect = effect(() => { - const status = this.remissionResource.status(); - const stockStatus = this.inStockResource.status(); - const searchTerm: string | undefined = this.searchTerm(); + #firstItem = computed(() => this.items()[0]); - if (status !== 'resolved' || stockStatus !== 'resolved') { - return; - } - - untracked(() => { - const hits = this.hits(); - - // #5338 - Select item automatically if only one hit after search - if ( - !!hits || - !searchTerm || - !this.hasValidSearchTerm() || - !this.searchTriggeredByUser() - ) { - if (hits === 1 && this.remissionStarted()) { - this.#store.clearSelectedItems(); - this.preselectRemissionItem(this.items()[0]); - } - - return; - } - - this.searchItemToRemitDialog({ - data: { - searchTerm, - }, - }).closed.subscribe(async (result) => { - this.#store.clearSelectedItems(); - if (result) { - if (this.remissionStarted()) { - for (const item of result) { - if (item?.id) { - this.#store.selectRemissionItem(item.id, item); - } - } - await this.remitItems({ addItemFlow: true }); - } else if (this.isDepartment()) { - return await this.navigateToDefaultRemissionList(); - } - } - this.reloadListAndReturnData(); - }); - }); - }); - - // TODO: Improvement - In Separate Komponente zusammen mit Remi-Button Auslagern /** - * Initiates the process to remit selected items. - * If remission is already started, items are added directly to the remission. - * If not, navigates to the default remission list. - * @param options - Options for remitting items, including whether it's part of an add-item flow. - * @returns A promise that resolves when the operation is complete. + * Effect that handles scenarios where a search yields no or few results. + * - Auto-preselects item when exactly one hit is found + * - Opens dialog to add items when no results are found + * + * @see injectEmptySearchResultHandler for implementation details */ - async remitItems(options: { addItemFlow: boolean } = { addItemFlow: false }) { - if (this.remitItemsInProgress()) { - return; - } - this.remitItemsInProgress.set(true); - - try { - // #5273, #5280 Fix - Bei gestarteter Remission dürfen Items die über den AddItemDialog hinzugefügt und direkt remittiert werden, nur als ReturnItem (statt ReturnSuggestion) zum WBS hinzugefügt werden - const remissionListType = options.addItemFlow - ? RemissionListType.Pflicht - : this.selectedRemissionListType(); - - const selected = this.#store.selectedItems(); - const quantities = this.#store.selectedQuantity(); - - for (const [remissionItemId, item] of Object.entries(selected)) { - const returnId = this.#store.returnId(); - const receiptId = this.#store.receiptId(); - const remissionItemIdNumber = Number(remissionItemId); - const quantity = quantities[remissionItemIdNumber]; - const inStock = this.getAvailableStockForItem(item); - const stockToRemit = getStockToRemit({ - remissionItem: item, - remissionListType, - availableStock: inStock, - }); - const quantityToRemit = quantity ?? stockToRemit; - - if (returnId && receiptId) { - await this.#remissionReturnReceiptService.remitItem({ - itemId: remissionItemIdNumber, - addItem: { - returnId, - receiptId, - quantity: quantityToRemit, - inStock, - impedimentComment: stockToRemit > quantity ? 'Restmenge' : '', - remainingQuantity: - isNaN(quantity) || inStock - quantity <= 0 - ? undefined - : inStock - quantity, - }, - type: getItemType(item, remissionListType), - }); - } - } - this.remitItemsState.set('success'); - this.reloadListAndReturnData(); - } catch (error) { - await this.handleRemitItemsError(error); - } - - this.#store.clearSelectedItems(); - this.remitItemsInProgress.set(false); - } + emptySearchResultEffect = injectEmptySearchResultHandler({ + remissionResourceStatus: computed(() => this.remissionResource.status()), + stockResourceStatus: computed(() => this.inStockResource.status()), + searchTerm: this.searchTerm, + hits: this.hits, + hasValidSearchTerm: this.hasValidSearchTerm, + searchTriggeredByUser: this.searchTriggeredByUser, + remissionStarted: this.remissionStarted, + isDepartment: this.isDepartment, + firstItem: this.#firstItem, + preselectItem: (item) => this.preselectRemissionItem(item), + remitItems: async (options) => { + await this.remissionAction()?.remitItems(options); + }, + navigateToDefaultList: () => this.navigateToDefaultRemissionList(), + reloadData: () => this.reloadListAndReturnData(), + }); /** * Reloads the remission list and return data. @@ -572,41 +427,6 @@ export class RemissionListComponent { } } - /** - * Handles errors that occur during the remission of items. - * Logs the error, displays an error dialog, and reloads the list and return data. - * If the error indicates that the remission is already completed, it clears the remission state. - * Sets the stateful button to 'error' to indicate the failure. - * @param error - The error object caught during the remission process. - * @returns A promise that resolves when the error handling is complete. - */ - async handleRemitItemsError(error: any) { - this.#logger.error('Failed to remit items', error); - - const errorMessage = - error?.error?.message ?? - error?.message ?? - 'Artikel konnten nicht remittiert werden'; - - this.remitItemsError.set(errorMessage); - - await firstValueFrom( - this.errorDialog({ - data: { - errorMessage, - }, - }).closed, - ); - - if (errorMessage === RemissionResponseArgsErrorMessage.AlreadyCompleted) { - this.#store.clearState(); - } - - this.reloadListAndReturnData(); - - this.remitItemsState.set('error'); // Stateful-Button auf Error setzen - } - /** * Navigates to the default remission list based on the current activated tab ID. * This method is used to redirect the user to the remission list after completing or starting a remission.