mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merged PR 1984: fix(reward-confirmation): improve action card visibility and status messages
fix(reward-confirmation): improve action card visibility and status messages Refactor confirmation action card to only display for items with 'Rücklage' feature. Replace boolean completion check with state-based system using ProcessingStatusState enum (Cancelled, NotFound, Collected). Add specific completion messages for each state to provide clearer user feedback. Changes: - Add displayActionCard computed signal to check for 'Rücklage' feature - Replace getProcessingStatusCompleted with getProcessingStatusState helper - Add ProcessingStatusState enum with three states (Cancelled, NotFound, Collected) - Update completion messages in template to use @switch based on processingStatus - Wrap entire action card in @if block checking displayActionCard - Add proper test coverage for new helper function - Update component spec to provide required dependencies Ref: #5391, #5404, #5406
This commit is contained in:
committed by
Lorenz Hilpert
parent
27541ab94a
commit
6e614683c5
@@ -1,64 +1,78 @@
|
||||
<div
|
||||
class="w-[24.5rem] h-full p-4 flex flex-col gap-4 rounded-lg bg-isa-secondary-100"
|
||||
[class.confirmation-list-item-done]="item().status !== 1"
|
||||
>
|
||||
@if (!isComplete()) {
|
||||
<div
|
||||
data-what="confirmation-message"
|
||||
data-which="confirmation-comment"
|
||||
class="isa-text-body-2-bold"
|
||||
>
|
||||
Bitte buchen Sie die Prämie aus dem Abholfach aus oder wählen Sie eine
|
||||
andere Aktion.
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-between items-center">
|
||||
<ui-dropdown
|
||||
class="h-8 border-none pl-0 hover:bg-transparent"
|
||||
[value]="selectedAction()"
|
||||
(valueChange)="setDropdownAction($event)"
|
||||
@if (displayActionCard()) {
|
||||
<div
|
||||
class="w-[24.5rem] h-full p-4 flex flex-col gap-4 rounded-lg bg-isa-secondary-100"
|
||||
[class.confirmation-list-item-done]="item().status !== 1"
|
||||
>
|
||||
@if (!isComplete()) {
|
||||
<div
|
||||
data-what="confirmation-message"
|
||||
data-which="confirmation-comment"
|
||||
class="isa-text-body-2-bold"
|
||||
>
|
||||
<ui-dropdown-option [value]="LoyaltyCollectType.Collect"
|
||||
>Prämie ausbuchen</ui-dropdown-option
|
||||
>
|
||||
<ui-dropdown-option [value]="LoyaltyCollectType.OutOfStock"
|
||||
>Nicht gefunden</ui-dropdown-option
|
||||
>
|
||||
<ui-dropdown-option [value]="LoyaltyCollectType.Cancel"
|
||||
>Stornieren</ui-dropdown-option
|
||||
>
|
||||
</ui-dropdown>
|
||||
Bitte buchen Sie die Prämie aus dem Abholfach aus oder wählen Sie eine
|
||||
andere Aktion.
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="flex items-center gap-2 self-end"
|
||||
type="button"
|
||||
uiButton
|
||||
color="primary"
|
||||
size="small"
|
||||
(click)="onCollect()"
|
||||
[pending]="isLoading()"
|
||||
[disabled]="isLoading()"
|
||||
data-what="button"
|
||||
data-which="complete"
|
||||
<div class="flex flex-row justify-between items-center">
|
||||
<ui-dropdown
|
||||
class="h-8 border-none pl-0 hover:bg-transparent"
|
||||
[value]="selectedAction()"
|
||||
(valueChange)="setDropdownAction($event)"
|
||||
>
|
||||
<ui-dropdown-option [value]="LoyaltyCollectType.Collect"
|
||||
>Prämie ausbuchen</ui-dropdown-option
|
||||
>
|
||||
<ui-dropdown-option [value]="LoyaltyCollectType.OutOfStock"
|
||||
>Nicht gefunden</ui-dropdown-option
|
||||
>
|
||||
<ui-dropdown-option [value]="LoyaltyCollectType.Cancel"
|
||||
>Stornieren</ui-dropdown-option
|
||||
>
|
||||
</ui-dropdown>
|
||||
|
||||
<button
|
||||
class="flex items-center gap-2 self-end"
|
||||
type="button"
|
||||
uiButton
|
||||
color="primary"
|
||||
size="small"
|
||||
(click)="onCollect()"
|
||||
[pending]="isLoading()"
|
||||
[disabled]="isLoading()"
|
||||
data-what="button"
|
||||
data-which="complete"
|
||||
>
|
||||
<ng-icon name="isaActionCheck" uiButtonIcon></ng-icon>
|
||||
Abschließen
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<div
|
||||
data-what="done-message"
|
||||
data-which="done-comment"
|
||||
class="isa-text-body-2-bold"
|
||||
>
|
||||
<ng-icon name="isaActionCheck" uiButtonIcon></ng-icon>
|
||||
Abschließen
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<div
|
||||
data-what="done-message"
|
||||
data-which="done-comment"
|
||||
class="isa-text-body-2-bold"
|
||||
>
|
||||
Artikel wurde Storniert und Lesepunkte gut geschrieben.
|
||||
</div>
|
||||
@switch (processingStatus()) {
|
||||
@case (ProcessingStatusState.Cancelled) {
|
||||
Artikel wurde storniert und die Lesepunkte wieder gutgeschrieben.
|
||||
}
|
||||
@case (ProcessingStatusState.NotFound) {
|
||||
Die Prämienbestellung wurde storniert und die Lesepunkte wieder
|
||||
gutgeschrieben. Bitte korrigieren Sie bei Bedarf den Filialbestand.
|
||||
}
|
||||
@case (ProcessingStatusState.Collected) {
|
||||
Der Artikel wurde aus dem Bestand ausgebucht und kann dem Kunden
|
||||
mitgegeben werden.
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="flex items-center gap-2 self-end text-isa-accent-green isa-text-body-2-bold"
|
||||
>
|
||||
<ng-icon name="isaActionCheck"></ng-icon>
|
||||
Abgeschlossen
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<span
|
||||
class="flex items-center gap-2 self-end text-isa-accent-green isa-text-body-2-bold"
|
||||
>
|
||||
<ng-icon name="isaActionCheck"></ng-icon>
|
||||
Abgeschlossen
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
OrderRewardCollectFacade,
|
||||
LoyaltyCollectType,
|
||||
OrderItemSubsetResource,
|
||||
getProcessingStatusCompleted,
|
||||
getProcessingStatusState,
|
||||
ProcessingStatusState,
|
||||
} from '@isa/oms/data-access';
|
||||
import { ButtonComponent } from '@isa/ui/buttons';
|
||||
import { NgIcon } from '@ng-icons/core';
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
DropdownButtonComponent,
|
||||
DropdownOptionComponent,
|
||||
} from '@isa/ui/input-controls';
|
||||
import { hasOrderTypeFeature } from '@isa/checkout/data-access';
|
||||
|
||||
@Component({
|
||||
selector: 'checkout-confirmation-list-item-action-card',
|
||||
@@ -39,6 +41,7 @@ import {
|
||||
})
|
||||
export class ConfirmationListItemActionCardComponent {
|
||||
LoyaltyCollectType = LoyaltyCollectType;
|
||||
ProcessingStatusState = ProcessingStatusState;
|
||||
#orderRewardCollectFacade = inject(OrderRewardCollectFacade);
|
||||
#store = inject(OrderConfiramtionStore);
|
||||
#orderItemSubsetResource = inject(OrderItemSubsetResource);
|
||||
@@ -61,13 +64,22 @@ export class ConfirmationListItemActionCardComponent {
|
||||
|
||||
orderItemSubsets = this.#orderItemSubsetResource.orderItemSubsets;
|
||||
selectedAction = signal<LoyaltyCollectType>(LoyaltyCollectType.Collect);
|
||||
isComplete = computed(() => {
|
||||
|
||||
processingStatus = computed(() => {
|
||||
const subsets = this.orderItemSubsets();
|
||||
const statuses = subsets?.map((subset) => subset.processingStatus);
|
||||
return getProcessingStatusCompleted(statuses);
|
||||
return getProcessingStatusState(statuses);
|
||||
});
|
||||
isLoading = signal(false);
|
||||
|
||||
isComplete = computed(() => {
|
||||
return this.processingStatus() !== undefined;
|
||||
});
|
||||
|
||||
displayActionCard = computed(() =>
|
||||
hasOrderTypeFeature(this.item().features, ['Rücklage']),
|
||||
);
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const item = this.item();
|
||||
|
||||
@@ -77,10 +77,12 @@ export class OrderConfirmationItemListItemComponent {
|
||||
)?.data;
|
||||
|
||||
// Fallback: use DisplayOrderItem features if not found in cart
|
||||
return foundItem ?? {
|
||||
features: item.features,
|
||||
availability: undefined,
|
||||
destination: undefined,
|
||||
};
|
||||
return (
|
||||
foundItem ?? {
|
||||
features: item.features,
|
||||
availability: undefined,
|
||||
destination: undefined,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import { OrderItemProcessingStatusValue } from '../../schemas';
|
||||
import { getProcessingStatusCompleted } from './get-processing-status-completed.helper';
|
||||
|
||||
describe('getProcessingStatusCompleted', () => {
|
||||
it('should return true when all statuses are different from Bestellt (16)', () => {
|
||||
// Arrange
|
||||
const statuses = [
|
||||
OrderItemProcessingStatusValue.Versendet, // 64
|
||||
OrderItemProcessingStatusValue.Eingetroffen, // 128
|
||||
OrderItemProcessingStatusValue.Abgeholt, // 256
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusCompleted(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when statuses include various completed states', () => {
|
||||
// Arrange
|
||||
const statuses = [
|
||||
OrderItemProcessingStatusValue.Zugestellt, // 4194304
|
||||
OrderItemProcessingStatusValue.Abgeholt, // 256
|
||||
OrderItemProcessingStatusValue.Versendet, // 64
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusCompleted(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when at least one status is Bestellt (16)', () => {
|
||||
// Arrange
|
||||
const statuses = [
|
||||
OrderItemProcessingStatusValue.Versendet, // 64
|
||||
OrderItemProcessingStatusValue.Bestellt, // 16
|
||||
OrderItemProcessingStatusValue.Abgeholt, // 256
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusCompleted(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when all statuses are Bestellt (16)', () => {
|
||||
// Arrange
|
||||
const statuses = [
|
||||
OrderItemProcessingStatusValue.Bestellt, // 16
|
||||
OrderItemProcessingStatusValue.Bestellt, // 16
|
||||
OrderItemProcessingStatusValue.Bestellt, // 16
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusCompleted(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when array is empty', () => {
|
||||
// Arrange
|
||||
const statuses: number[] = [];
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusCompleted(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when statuses is undefined', () => {
|
||||
// Arrange
|
||||
const statuses = undefined;
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusCompleted(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true with single status different from Bestellt', () => {
|
||||
// Arrange
|
||||
const statuses = [OrderItemProcessingStatusValue.Abgeholt]; // 256
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusCompleted(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import { OrderItemProcessingStatusValue } from '../../schemas';
|
||||
|
||||
/**
|
||||
* Checks if all processing statuses are completed (not in "Bestellt" state).
|
||||
* Returns true if all statuses are different from "Bestellt" (16).
|
||||
* Returns false if any status is still "Bestellt" (16) or if the array is empty/undefined.
|
||||
*/
|
||||
export const getProcessingStatusCompleted = (
|
||||
statuses: number[] | undefined,
|
||||
): boolean => {
|
||||
if (!statuses || statuses.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return statuses.every(
|
||||
(status) => status !== OrderItemProcessingStatusValue.Bestellt,
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
import { OrderItemProcessingStatusValue } from '../../schemas';
|
||||
import { ProcessingStatusState } from '../../models';
|
||||
import { getProcessingStatusState } from './get-processing-status-state.helper';
|
||||
|
||||
describe('getProcessingStatusState', () => {
|
||||
describe('Cancelled status', () => {
|
||||
it('should return Cancelled when all items are cancelled', () => {
|
||||
// Arrange
|
||||
const statuses = [
|
||||
OrderItemProcessingStatusValue.StorniertKunde, // 512
|
||||
OrderItemProcessingStatusValue.Storniert, // 1024
|
||||
OrderItemProcessingStatusValue.StorniertLieferant, // 2048
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusState(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(ProcessingStatusState.Cancelled);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NotFound status', () => {
|
||||
it('should return NotFound when all items are NichtLieferbar', () => {
|
||||
// Arrange
|
||||
const statuses = [
|
||||
OrderItemProcessingStatusValue.NichtLieferbar, // 4096
|
||||
OrderItemProcessingStatusValue.NichtLieferbar, // 4096
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusState(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(ProcessingStatusState.NotFound);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Collected status', () => {
|
||||
it('should return Collected when all items are Abgeholt', () => {
|
||||
// Arrange
|
||||
const statuses = [
|
||||
OrderItemProcessingStatusValue.Abgeholt, // 256
|
||||
OrderItemProcessingStatusValue.Abgeholt, // 256
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusState(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(ProcessingStatusState.Collected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Undefined cases', () => {
|
||||
it('should return undefined when array is empty', () => {
|
||||
// Arrange
|
||||
const statuses: number[] = [];
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusState(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when statuses is undefined', () => {
|
||||
// Arrange
|
||||
const statuses = undefined;
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusState(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when items have mixed statuses', () => {
|
||||
// Arrange
|
||||
const statuses = [
|
||||
OrderItemProcessingStatusValue.StorniertKunde, // 512
|
||||
OrderItemProcessingStatusValue.Abgeholt, // 256
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = getProcessingStatusState(statuses);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { OrderItemProcessingStatusValue } from '../../schemas';
|
||||
import { ProcessingStatusState } from '../../models';
|
||||
|
||||
/**
|
||||
* Determines the completion status of order items based on their processing statuses.
|
||||
*
|
||||
* @param statuses - Array of processing status values to evaluate
|
||||
* @returns The processing status state:
|
||||
* - `ProcessingStatusState.Cancelled` if all items are cancelled
|
||||
* - `ProcessingStatusState.NotFound` if all items are marked as not available
|
||||
* - `ProcessingStatusState.Collected` if all items are collected
|
||||
* - `undefined` if statuses don't match any completion state
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const statuses = [512, 1024]; // StorniertKunde, Storniert
|
||||
* getProcessingStatusState(statuses); // ProcessingStatusState.Cancelled
|
||||
* ```
|
||||
*/
|
||||
export const getProcessingStatusState = (
|
||||
statuses: number[] | undefined,
|
||||
): ProcessingStatusState | undefined => {
|
||||
if (!statuses || statuses.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Check if all statuses are cancelled
|
||||
const allCancelled = statuses.every(
|
||||
(status) =>
|
||||
status === OrderItemProcessingStatusValue.StorniertKunde ||
|
||||
status === OrderItemProcessingStatusValue.Storniert ||
|
||||
status === OrderItemProcessingStatusValue.StorniertLieferant,
|
||||
);
|
||||
if (allCancelled) {
|
||||
return ProcessingStatusState.Cancelled;
|
||||
}
|
||||
|
||||
// Check if all statuses are not available
|
||||
const allNotFound = statuses.every(
|
||||
(status) => status === OrderItemProcessingStatusValue.NichtLieferbar,
|
||||
);
|
||||
if (allNotFound) {
|
||||
return ProcessingStatusState.NotFound;
|
||||
}
|
||||
|
||||
// Check if all statuses are collected
|
||||
const allCollected = statuses.every(
|
||||
(status) => status === OrderItemProcessingStatusValue.Abgeholt,
|
||||
);
|
||||
if (allCollected) {
|
||||
return ProcessingStatusState.Collected;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
@@ -1 +1 @@
|
||||
export * from './get-processing-status-completed.helper';
|
||||
export * from './get-processing-status-state.helper';
|
||||
|
||||
@@ -5,6 +5,7 @@ export * from './eligible-for-return';
|
||||
export * from './gender';
|
||||
export * from './logistician';
|
||||
export * from './order';
|
||||
export * from './processing-status-state';
|
||||
export * from './quantity';
|
||||
export * from './receipt-item-list-item';
|
||||
export * from './receipt-item-task-list-item';
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Processing status state types for order items
|
||||
*/
|
||||
export const ProcessingStatusState = {
|
||||
/** Item was cancelled by customer, merchant, or supplier */
|
||||
Cancelled: 'cancelled',
|
||||
/** Item was not found / not available */
|
||||
NotFound: 'not-found',
|
||||
/** Item was successfully collected */
|
||||
Collected: 'collected',
|
||||
} as const;
|
||||
|
||||
export type ProcessingStatusState =
|
||||
(typeof ProcessingStatusState)[keyof typeof ProcessingStatusState];
|
||||
Reference in New Issue
Block a user