mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merged PR 1985: 🐛 fix(reward-order-confirmation): group items by item-level delivery type
🐛 fix(reward-order-confirmation): group items by item-level delivery type Fix incorrect delivery type headers by grouping order items based on item.features.orderType instead of order.features.orderType, since items within a single order can have different delivery types. Changes: - Add groupDisplayOrderItemsByDeliveryType helper to group items by delivery type - Add groupDisplayOrderItemsByBranch helper to group items by branch - Refactor OrderConfirmationItemListComponent to use item-level grouping - Move list rendering and grouping logic from parent to child component - Update template to use branchGroup.items instead of branchGroup.allItems Fixes #5403 Related work items: #5403
This commit is contained in:
committed by
Nino Righi
parent
6e614683c5
commit
9239f8960d
@@ -0,0 +1,110 @@
|
||||
import { DisplayOrderItem } from '@isa/oms/data-access';
|
||||
import { OrderType } from '../models';
|
||||
|
||||
/**
|
||||
* Represents a group of order items sharing the same branch (for pickup/in-store)
|
||||
* or all items for delivery types that don't have branch-specific grouping.
|
||||
*/
|
||||
export type OrderItemBranchGroup = {
|
||||
/** Branch ID (undefined for delivery types without branch grouping) */
|
||||
branchId?: number;
|
||||
/** Branch name (undefined for delivery types without branch grouping) */
|
||||
branchName?: string;
|
||||
/** Array of items in this branch group */
|
||||
items: DisplayOrderItem[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Order types that require grouping by branch/filiale.
|
||||
* Other order types (Versand, DIG-Versand, etc.) are grouped together without branch subdivision.
|
||||
*/
|
||||
const ORDER_TYPES_WITH_BRANCH_GROUPING = [
|
||||
OrderType.Pickup,
|
||||
OrderType.InStore,
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Sorts branch groups by branch ID (ascending).
|
||||
*/
|
||||
const sortByBranchId = (
|
||||
entries: [number, DisplayOrderItem[]][],
|
||||
): [number, DisplayOrderItem[]][] => {
|
||||
return [...entries].sort(([a], [b]) => a - b);
|
||||
};
|
||||
|
||||
/**
|
||||
* Groups display order items by their target branch.
|
||||
* Only applies branch-level grouping for Abholung (Pickup) and Rücklage (InStore) order types.
|
||||
* For other delivery types (Versand, DIG-Versand, etc.), returns a single group with all items.
|
||||
*
|
||||
* Uses item.order.targetBranch for grouping since items inherit their parent order's branch information.
|
||||
*
|
||||
* @param orderType - The order type to determine if branch grouping is needed
|
||||
* @param items - Array of DisplayOrderItem objects to group
|
||||
* @returns Array of OrderItemBranchGroup objects, each containing items for a specific branch
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // For Abholung (pickup) items from different branches
|
||||
* const pickupItems = [
|
||||
* { id: 1, order: { targetBranch: { id: 1, name: 'München' } } },
|
||||
* { id: 2, order: { targetBranch: { id: 2, name: 'Berlin' } } }
|
||||
* ];
|
||||
* const groups = groupDisplayOrderItemsByBranch('Abholung', pickupItems);
|
||||
* // [
|
||||
* // { branchId: 1, branchName: 'München', items: [item1] },
|
||||
* // { branchId: 2, branchName: 'Berlin', items: [item2] }
|
||||
* // ]
|
||||
*
|
||||
* // For Versand (delivery) items
|
||||
* const deliveryItems = [
|
||||
* { id: 1, ... },
|
||||
* { id: 2, ... }
|
||||
* ];
|
||||
* const groups = groupDisplayOrderItemsByBranch('Versand', deliveryItems);
|
||||
* // [
|
||||
* // { branchId: undefined, branchName: undefined, items: [item1, item2] }
|
||||
* // ]
|
||||
* ```
|
||||
*/
|
||||
export function groupDisplayOrderItemsByBranch(
|
||||
orderType: OrderType | string,
|
||||
items: DisplayOrderItem[],
|
||||
): OrderItemBranchGroup[] {
|
||||
const needsBranchGrouping =
|
||||
orderType === OrderType.Pickup || orderType === OrderType.InStore;
|
||||
|
||||
if (!needsBranchGrouping) {
|
||||
// For delivery types without branch grouping, return single group with all items
|
||||
return [
|
||||
{
|
||||
branchId: undefined,
|
||||
branchName: undefined,
|
||||
items,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// For Abholung/Rücklage, group by item.order.targetBranch.id
|
||||
const branchGroups = items.reduce((map, item) => {
|
||||
const branchId = item.order?.targetBranch?.id ?? 0;
|
||||
if (!map.has(branchId)) {
|
||||
map.set(branchId, []);
|
||||
}
|
||||
map.get(branchId)!.push(item);
|
||||
return map;
|
||||
}, new Map<number, DisplayOrderItem[]>());
|
||||
|
||||
// Convert Map to array of OrderItemBranchGroup, sorted by branch ID
|
||||
return sortByBranchId(Array.from(branchGroups.entries())).map(
|
||||
([branchId, branchItems]) => {
|
||||
const branch = branchItems[0]?.order?.targetBranch;
|
||||
|
||||
return {
|
||||
branchId: branchId || undefined,
|
||||
branchName: branch?.name,
|
||||
items: branchItems,
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { DisplayOrder, DisplayOrderItem } from '@isa/oms/data-access';
|
||||
import { getOrderTypeFeature } from './get-order-type-feature.helper';
|
||||
import { OrderType } from '../models';
|
||||
|
||||
/**
|
||||
* Groups display order items by their delivery type (item.features.orderType).
|
||||
*
|
||||
* Unlike groupDisplayOrdersByDeliveryType which groups entire orders,
|
||||
* this groups individual items since items within one order can have different delivery types.
|
||||
*
|
||||
* @param orders - Array of DisplayOrder objects containing items to group
|
||||
* @returns Map where keys are order types (Abholung, Rücklage, Versand, etc.)
|
||||
* and values are arrays of items with that type
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const orders = [
|
||||
* {
|
||||
* id: 1,
|
||||
* features: { orderType: 'Abholung' },
|
||||
* items: [
|
||||
* { id: 1, features: { orderType: 'Abholung' }, ... },
|
||||
* { id: 2, features: { orderType: 'Rücklage' }, ... }
|
||||
* ]
|
||||
* }
|
||||
* ];
|
||||
*
|
||||
* const grouped = groupDisplayOrderItemsByDeliveryType(orders);
|
||||
* // Map {
|
||||
* // 'Abholung' => [item1],
|
||||
* // 'Rücklage' => [item2]
|
||||
* // }
|
||||
* ```
|
||||
*/
|
||||
export function groupDisplayOrderItemsByDeliveryType(
|
||||
orders: DisplayOrder[],
|
||||
): Map<OrderType | string, DisplayOrderItem[]> {
|
||||
const allItems = orders.flatMap((order) => order.items ?? []);
|
||||
|
||||
return allItems.reduce((map, item) => {
|
||||
const orderType = getOrderTypeFeature(item.features) ?? 'Unbekannt';
|
||||
if (!map.has(orderType)) {
|
||||
map.set(orderType, []);
|
||||
}
|
||||
map.get(orderType)!.push(item);
|
||||
return map;
|
||||
}, new Map<OrderType | string, DisplayOrderItem[]>());
|
||||
}
|
||||
@@ -12,6 +12,8 @@ export * from './group-by-branch.helper';
|
||||
export * from './group-by-order-type.helper';
|
||||
export * from './group-display-orders-by-branch.helper';
|
||||
export * from './group-display-orders-by-delivery-type.helper';
|
||||
export * from './group-display-order-items-by-branch.helper';
|
||||
export * from './group-display-order-items-by-delivery-type.helper';
|
||||
export * from './item-selection-changed.helper';
|
||||
export * from './merge-reward-selection-items.helper';
|
||||
export * from './should-show-grouping.helper';
|
||||
|
||||
@@ -1,14 +1,39 @@
|
||||
<div
|
||||
class="flex p-2 items-center gap-[0.625rem] self-stretch rounded-2xl bg-isa-neutral-200 w-full isa-text-body-2-bold"
|
||||
>
|
||||
<ng-icon [name]="orderTypeIcon()" size="1.5rem"></ng-icon>
|
||||
<div>
|
||||
{{ orderType() }}
|
||||
</div>
|
||||
</div>
|
||||
@for (group of groupedOrders(); track group.orderType) {
|
||||
<!-- Delivery type section -->
|
||||
<div
|
||||
class="flex flex-col gap-2 self-stretch"
|
||||
data-what="delivery-type-group"
|
||||
[attr.data-which]="group.orderType"
|
||||
>
|
||||
@for (branchGroup of group.branchGroups; track branchGroup.branchId ?? 0) {
|
||||
<!-- Branch header (only shown when multiple branches exist within same delivery type) -->
|
||||
@if (branchGroup.branchName && group.branchGroups.length > 1) {
|
||||
<div
|
||||
class="flex p-2 items-center gap-[0.625rem] self-stretch rounded-2xl bg-isa-neutral-200 w-full isa-text-body-2-bold"
|
||||
data-what="branch-header"
|
||||
[attr.data-which]="branchGroup.branchName"
|
||||
>
|
||||
<ng-icon [name]="group.icon" size="1.5rem" />
|
||||
<div>{{ group.orderType }} - {{ branchGroup.branchName }}</div>
|
||||
</div>
|
||||
} @else {
|
||||
<!-- Order type header (for single branch or Versand types) -->
|
||||
<div
|
||||
class="flex p-2 items-center gap-[0.625rem] self-stretch rounded-2xl bg-isa-neutral-200 w-full isa-text-body-2-bold"
|
||||
data-what="order-type-header"
|
||||
[attr.data-which]="group.orderType"
|
||||
>
|
||||
<ng-icon [name]="group.icon" size="1.5rem" />
|
||||
<div>
|
||||
{{ group.orderType }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@for (item of items(); track item.id) {
|
||||
<checkout-order-confirmation-item-list-item
|
||||
[item]="item"
|
||||
></checkout-order-confirmation-item-list-item>
|
||||
<!-- Items from all orders in this (orderType + branch) group -->
|
||||
@for (item of branchGroup.items; track item.id) {
|
||||
<checkout-order-confirmation-item-list-item [item]="item" />
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -2,19 +2,24 @@ import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
input,
|
||||
inject,
|
||||
} from '@angular/core';
|
||||
|
||||
import { OrderConfirmationItemListItemComponent } from './order-confirmation-item-list-item/order-confirmation-item-list-item.component';
|
||||
|
||||
import { getOrderTypeFeature, OrderType } from '@isa/checkout/data-access';
|
||||
import {
|
||||
groupDisplayOrderItemsByDeliveryType,
|
||||
groupDisplayOrderItemsByBranch,
|
||||
getOrderTypeIcon,
|
||||
} from '@isa/checkout/data-access';
|
||||
import { NgIcon, provideIcons } from '@ng-icons/core';
|
||||
import {
|
||||
isaDeliveryVersand,
|
||||
isaDeliveryRuecklage2,
|
||||
isaDeliveryRuecklage1,
|
||||
isaDeliveryB2BVersand1,
|
||||
} from '@isa/icons';
|
||||
import { DisplayOrder } from '@isa/oms/data-access';
|
||||
import { OrderConfiramtionStore } from '../reward-order-confirmation.store';
|
||||
|
||||
@Component({
|
||||
selector: 'checkout-order-confirmation-item-list',
|
||||
@@ -27,33 +32,40 @@ import { DisplayOrder } from '@isa/oms/data-access';
|
||||
isaDeliveryVersand,
|
||||
isaDeliveryRuecklage2,
|
||||
isaDeliveryRuecklage1,
|
||||
isaDeliveryB2BVersand1,
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class OrderConfirmationItemListComponent {
|
||||
order = input.required<DisplayOrder>();
|
||||
#store = inject(OrderConfiramtionStore);
|
||||
|
||||
orderType = computed(() => {
|
||||
return getOrderTypeFeature(this.order().features);
|
||||
orders = this.#store.orders;
|
||||
|
||||
/**
|
||||
* Groups order items hierarchically by delivery type and branch.
|
||||
* - Primary grouping: By delivery type from item.features.orderType (Abholung, Rücklage, Versand, etc.)
|
||||
* - Secondary grouping: By branch (only for Abholung and Rücklage)
|
||||
*
|
||||
* Note: Groups by item-level orderType, not order-level, since items within
|
||||
* a single order can have different delivery types.
|
||||
*/
|
||||
groupedOrders = computed(() => {
|
||||
const orders = this.orders();
|
||||
if (!orders || orders.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const byDeliveryType = groupDisplayOrderItemsByDeliveryType(orders);
|
||||
|
||||
const result = Array.from(byDeliveryType.entries()).map(
|
||||
([orderType, items]) => ({
|
||||
orderType,
|
||||
icon: getOrderTypeIcon(orderType),
|
||||
branchGroups: groupDisplayOrderItemsByBranch(orderType, items),
|
||||
}),
|
||||
);
|
||||
|
||||
console.log('Grouped orders:', result);
|
||||
return result;
|
||||
});
|
||||
|
||||
orderTypeIcon = computed(() => {
|
||||
const orderType = this.orderType();
|
||||
|
||||
if (OrderType.Delivery === orderType) {
|
||||
return 'isaDeliveryVersand';
|
||||
}
|
||||
|
||||
if (OrderType.Pickup === orderType) {
|
||||
return 'isaDeliveryRuecklage2';
|
||||
}
|
||||
|
||||
if (OrderType.InStore === orderType) {
|
||||
return 'isaDeliveryRuecklage1';
|
||||
}
|
||||
|
||||
return 'isaDeliveryVersand';
|
||||
});
|
||||
|
||||
items = computed(() => this.order().items ?? []);
|
||||
}
|
||||
|
||||
@@ -1,47 +1,8 @@
|
||||
<div
|
||||
class="bg-isa-white p-6 rounded-2xl flex flex-col gap-6 items-start self-stretch"
|
||||
>
|
||||
<checkout-order-confirmation-header></checkout-order-confirmation-header>
|
||||
<checkout-order-confirmation-header />
|
||||
<div class="w-full h-[0.0625rem] bg-[#DEE2E6]"></div>
|
||||
<checkout-order-confirmation-addresses></checkout-order-confirmation-addresses>
|
||||
|
||||
@for (group of groupedOrders(); track group.orderType) {
|
||||
<!-- Delivery type section -->
|
||||
<div class="flex flex-col gap-2 self-stretch" data-what="delivery-type-group" [attr.data-which]="group.orderType">
|
||||
@for (branchGroup of group.branchGroups; track branchGroup.branchId ?? 0) {
|
||||
<!-- Branch header (only shown when multiple branches exist within same delivery type) -->
|
||||
@if (branchGroup.branchName && group.branchGroups.length > 1) {
|
||||
<div
|
||||
class="flex p-2 items-center gap-[0.625rem] self-stretch rounded-2xl bg-isa-neutral-200 w-full isa-text-body-2-bold"
|
||||
data-what="branch-header"
|
||||
[attr.data-which]="branchGroup.branchName"
|
||||
>
|
||||
<ng-icon [name]="group.icon" size="1.5rem"></ng-icon>
|
||||
<div>
|
||||
{{ group.orderType }} - {{ branchGroup.branchName }}
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<!-- Order type header (for single branch or Versand types) -->
|
||||
<div
|
||||
class="flex p-2 items-center gap-[0.625rem] self-stretch rounded-2xl bg-isa-neutral-200 w-full isa-text-body-2-bold"
|
||||
data-what="order-type-header"
|
||||
[attr.data-which]="group.orderType"
|
||||
>
|
||||
<ng-icon [name]="group.icon" size="1.5rem"></ng-icon>
|
||||
<div>
|
||||
{{ group.orderType }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Items from all orders in this (orderType + branch) group -->
|
||||
@for (item of branchGroup.allItems; track item.id) {
|
||||
<checkout-order-confirmation-item-list-item
|
||||
[item]="item"
|
||||
></checkout-order-confirmation-item-list-item>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<checkout-order-confirmation-addresses />
|
||||
<checkout-order-confirmation-item-list />
|
||||
</div>
|
||||
|
||||
@@ -11,22 +11,9 @@ import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { OrderConfirmationAddressesComponent } from './order-confirmation-addresses/order-confirmation-addresses.component';
|
||||
import { OrderConfirmationHeaderComponent } from './order-confirmation-header/order-confirmation-header.component';
|
||||
import { OrderConfirmationItemListComponent } from './order-confirmation-item-list/order-confirmation-item-list.component';
|
||||
import { OrderConfirmationItemListItemComponent } from './order-confirmation-item-list/order-confirmation-item-list-item/order-confirmation-item-list-item.component';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { TabService } from '@isa/core/tabs';
|
||||
import { OrderConfiramtionStore } from './reward-order-confirmation.store';
|
||||
import {
|
||||
groupDisplayOrdersByDeliveryType,
|
||||
groupDisplayOrdersByBranch,
|
||||
getOrderTypeIcon,
|
||||
} from '@isa/checkout/data-access';
|
||||
import { NgIcon, provideIcons } from '@ng-icons/core';
|
||||
import {
|
||||
isaDeliveryVersand,
|
||||
isaDeliveryRuecklage2,
|
||||
isaDeliveryRuecklage1,
|
||||
isaDeliveryB2BVersand1,
|
||||
} from '@isa/icons';
|
||||
|
||||
@Component({
|
||||
selector: 'checkout-reward-order-confirmation',
|
||||
@@ -37,18 +24,8 @@ import {
|
||||
OrderConfirmationHeaderComponent,
|
||||
OrderConfirmationAddressesComponent,
|
||||
OrderConfirmationItemListComponent,
|
||||
OrderConfirmationItemListItemComponent,
|
||||
NgIcon,
|
||||
],
|
||||
providers: [
|
||||
OrderConfiramtionStore,
|
||||
provideIcons({
|
||||
isaDeliveryVersand,
|
||||
isaDeliveryRuecklage2,
|
||||
isaDeliveryRuecklage1,
|
||||
isaDeliveryB2BVersand1,
|
||||
}),
|
||||
],
|
||||
providers: [OrderConfiramtionStore],
|
||||
})
|
||||
export class RewardOrderConfirmationComponent {
|
||||
#store = inject(OrderConfiramtionStore);
|
||||
@@ -66,28 +43,6 @@ export class RewardOrderConfirmationComponent {
|
||||
return param ? param.split('+').map((strId) => parseInt(strId, 10)) : [];
|
||||
});
|
||||
|
||||
orders = this.#store.orders;
|
||||
|
||||
/**
|
||||
* Groups orders hierarchically by delivery type and branch.
|
||||
* - Primary grouping: By delivery type (Abholung, Rücklage, Versand, etc.)
|
||||
* - Secondary grouping: By branch (only for Abholung and Rücklage)
|
||||
*/
|
||||
groupedOrders = computed(() => {
|
||||
const orders = this.orders();
|
||||
if (!orders || orders.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const byDeliveryType = groupDisplayOrdersByDeliveryType(orders);
|
||||
|
||||
return Array.from(byDeliveryType.entries()).map(([orderType, typeOrders]) => ({
|
||||
orderType,
|
||||
icon: getOrderTypeIcon(orderType),
|
||||
branchGroups: groupDisplayOrdersByBranch(orderType, typeOrders),
|
||||
}));
|
||||
});
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const tabId = this.#tabId() || undefined;
|
||||
@@ -97,9 +52,5 @@ export class RewardOrderConfirmationComponent {
|
||||
this.#store.patch({ tabId, orderIds });
|
||||
});
|
||||
});
|
||||
|
||||
effect(() => {
|
||||
console.log('Orders to display:', this.orders());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user