Merged PR 1946: fix(remission-list, remission-return-receipt-details, libs-dialog): improve error handling with dedicated error dialog

fix(remission-list, remission-return-receipt-details, libs-dialog): improve error handling with dedicated error dialog

- Add RemissionResponseArgsErrorMessage constants for standardized error messages
- Create FeedbackErrorDialogComponent for consistent error display across the app
- Implement enhanced error handling in RemissionListComponent.handleRemitItemsError()
- Update RemissionReturnReceiptDetailsItemComponent to use new error dialog pattern
- Add injectFeedbackErrorDialog convenience function for easy error dialog injection
- Include comprehensive unit tests for new dialog component
- Replace generic error handling with specific ResponseArgsError handling
- Clear remission state when "AlreadyCompleted" error occurs

The new error dialog provides a standardized way to display backend error
messages to users with consistent styling and behavior. Error handling now
properly differentiates between different error types and takes appropriate
actions like clearing state for completed remissions.

Ref: #5331
This commit is contained in:
Nino Righi
2025-09-11 14:06:14 +00:00
committed by Lorenz Hilpert
parent 0ca58fe1bf
commit 59f0cc7d43
10 changed files with 229 additions and 15 deletions

View File

@@ -18,3 +18,4 @@ export * from './value-tuple-sting-and-integer';
export * from './create-remission';
export * from './remission-item-source';
export * from './receipt-complete-status';
export * from './remission-response-args-error-message';

View File

@@ -0,0 +1,11 @@
// #5331 - Messages kommen bis auf AlreadyRemoved aus dem Backend
export const RemissionResponseArgsErrorMessage = {
AlreadyCompleted: 'Remission wurde bereits abgeschlossen',
AlreadyRemitted: 'Artikel wurde bereits remittiert',
AlreadyRemoved: 'Artikel konnte nicht entfernt werden',
} as const;
export type RemissionResponseArgsErrorMessageKey =
keyof typeof RemissionResponseArgsErrorMessage;
export type RemissionResponseArgsErrorMessageValue =
(typeof RemissionResponseArgsErrorMessage)[RemissionResponseArgsErrorMessageKey];

View File

@@ -40,16 +40,20 @@ import {
calculateAvailableStock,
RemissionReturnReceiptService,
getStockToRemit,
RemissionListType,
RemissionResponseArgsErrorMessage,
RemissionResponseArgsErrorMessageValue,
} from '@isa/remission/data-access';
import { injectDialog } from '@isa/ui/dialog';
import { injectDialog, injectFeedbackErrorDialog } from '@isa/ui/dialog';
import { SearchItemToRemitDialogComponent } from '@isa/remission/shared/search-item-to-remit-dialog';
import { RemissionListType } from '@isa/remission/data-access';
import { RemissionReturnCardComponent } from './remission-return-card/remission-return-card.component';
import { logger } from '@isa/core/logging';
import { RemissionProcessedHintComponent } from './remission-processed-hint/remission-processed-hint.component';
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 { ResponseArgsError } from '@isa/common/data-access';
import { firstValueFrom } from 'rxjs';
function querySettingsFactory() {
return inject(ActivatedRoute).snapshot.data['querySettings'];
@@ -118,6 +122,7 @@ export class RemissionListComponent {
activatedTabId = injectTabId();
searchItemToRemitDialog = injectDialog(SearchItemToRemitDialogComponent);
errorDialog = injectFeedbackErrorDialog();
/**
* FilterService instance for managing filter state and queries.
@@ -492,17 +497,10 @@ export class RemissionListComponent {
});
}
}
this.remitItemsState.set('success');
this.reloadListAndReturnData();
} catch (error) {
this.#logger.error('Failed to remit items', error);
this.remitItemsError.set(
error instanceof Error
? error.message
: 'Artikel konnten nicht remittiert werden',
);
this.remitItemsState.set('error');
} catch (error: ResponseArgsError<unknown> | Error | unknown) {
await this.handleRemitItemsError(error);
}
this.#store.clearSelectedItems();
@@ -519,6 +517,47 @@ export class RemissionListComponent {
this.#store.reloadReturn();
}
/**
* Handles errors that occur during the remission of items.
* Logs the error, sets the error message signal, and displays an error dialog if applicable.
* If the error indicates that the remission is already completed, it clears the remission state.
* Finally, it reloads the remission list and return data.
* @param error - The error that occurred during the remission process.
* Can be a ResponseArgsError, a generic Error, or any other unknown type.
* @returns A promise that resolves when error handling is complete.
*/
async handleRemitItemsError(
error: ResponseArgsError<unknown> | Error | unknown,
) {
this.#logger.error('Failed to remit items', error);
this.remitItemsError.set(
error instanceof Error || error instanceof ResponseArgsError
? error.message
: 'Artikel konnten nicht remittiert werden',
);
if (error instanceof ResponseArgsError) {
const errorMessage = (error?.responseArgs?.message ??
error?.message) as RemissionResponseArgsErrorMessageValue;
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.

View File

@@ -9,6 +9,7 @@ import {
} from '@angular/core';
import {
ReceiptItem,
RemissionResponseArgsErrorMessage,
RemissionReturnReceiptService,
} from '@isa/remission/data-access';
import { ProductFormatComponent } from '@isa/shared/product-foramt';
@@ -20,6 +21,9 @@ import { IconButtonComponent } from '@isa/ui/buttons';
import { provideIcons } from '@ng-icons/core';
import { isaActionClose } from '@isa/icons';
import { logger } from '@isa/core/logging';
import { injectFeedbackErrorDialog } from '@isa/ui/dialog';
import { ResponseArgsError } from '@isa/common/data-access';
import { firstValueFrom } from 'rxjs';
/**
* Component for displaying a single receipt item within the remission return receipt details.
@@ -55,6 +59,8 @@ export class RemissionReturnReceiptDetailsItemComponent {
}));
#returnReceiptService = inject(RemissionReturnReceiptService);
errorDialog = injectFeedbackErrorDialog();
/**
* Required input for the receipt item to display.
* Contains product information and quantity details.
@@ -85,7 +91,7 @@ export class RemissionReturnReceiptDetailsItemComponent {
removing = signal(false);
removed = output<ReceiptItem>();
reloadReturn = output<void>();
async remove() {
if (this.removing()) {
@@ -98,10 +104,25 @@ export class RemissionReturnReceiptDetailsItemComponent {
returnId: this.returnId(),
receiptItemId: this.item().id,
});
this.removed.emit(this.item());
} catch (error) {
} catch (error: ResponseArgsError<unknown> | Error | unknown) {
this.#logger.error('Failed to remove item', error);
if (error instanceof ResponseArgsError) {
const errorMessage =
error?.responseArgs?.message ??
error?.message ??
RemissionResponseArgsErrorMessage.AlreadyRemoved;
await firstValueFrom(
this.errorDialog({
data: {
errorMessage,
},
}).closed,
);
}
}
this.reloadReturn.emit();
this.removing.set(false);
}
}

View File

@@ -55,7 +55,7 @@
[removeable]="canRemoveItems()"
[receiptId]="receiptId()"
[returnId]="returnId()"
(removed)="returnResource.reload()"
(reloadReturn)="returnResource.reload()"
></remi-remission-return-receipt-details-item>
@if (!last) {
<hr class="border-isa-neutral-300" />