diff --git a/apps/isa-app/src/page/customer-order/customer-order-details/customer-order-details-header/customer-order-details-header.component.html b/apps/isa-app/src/page/customer-order/customer-order-details/customer-order-details-header/customer-order-details-header.component.html index 8f714d4e8..8966d146b 100644 --- a/apps/isa-app/src/page/customer-order/customer-order-details/customer-order-details-header/customer-order-details-header.component.html +++ b/apps/isa-app/src/page/customer-order/customer-order-details/customer-order-details-header/customer-order-details-header.component.html @@ -1,9 +1,14 @@
-
-
- -
+
+
+ +
{{ feature?.description }}
@@ -18,29 +23,54 @@
-
+

{{ orderItem?.organisation }} - - + - {{ orderItem?.lastName }} {{ orderItem?.firstName }}
-
- {{ orderItem?.compartmentCode }}{{ orderItem?.compartmentInfo && '_' + orderItem?.compartmentInfo }} +
+ {{ orderItem?.compartmentCode + }}{{ orderItem?.compartmentInfo && '_' + orderItem?.compartmentInfo }}

-
-
+
+
{{ orderItem?.features?.paid }}
-
- +
+ @@ -49,25 +79,49 @@
-
+
Kundennummer
-
{{ orderItem?.buyerNumber }}
+
+ {{ orderItem?.buyerNumber }} +
-
+
Vorgang-ID
-
{{ orderItem?.orderNumber }}
+
+ {{ orderItem?.orderNumber }} +
-
+
Bestelldatum
-
{{ orderItem?.orderDate | date: 'dd.MM.yy | HH:mm' }} Uhr
+
+ {{ orderItem?.orderDate | date: 'dd.MM.yy | HH:mm' }} Uhr +
-
+
Status
-
+
@@ -91,18 +145,36 @@ icon="arrow-drop-down" > - -
- +
-
+
Bestellkanal
-
{{ order?.features?.orderSource }}
+
+ {{ order?.features?.orderSource }} +
Geändert
-
{{ orderItem?.processingStatusDate | date: 'dd.MM.yy | HH:mm' }} Uhr
+
+ {{ + orderItem?.processingStatusDate | date: 'dd.MM.yy | HH:mm' + }} + Uhr +
-
+
Benachrichtigung
-
{{ (notificationsChannel | notificationsChannel) || '-' }}
+
+ {{ (notificationsChannel | notificationsChannel) || '-' }} +
-
+

Versand

-
+

Versand

-
+

B2B-Versand

-
+

Abholung

{{ orderItem.targetBranch }} -
+

Rücklage

-
+

Download

@@ -201,50 +298,93 @@
-
+
-
+
-
-
+
+

Lieferadresse

-
+

{{ order.shipping?.data?.organisation?.name }}

{{ order.shipping?.data?.organisation?.department }}

-

{{ order.shipping?.data?.firstName }} {{ order.shipping?.data?.lastName }}

+

+ {{ order.shipping?.data?.firstName }} + {{ order.shipping?.data?.lastName }} +

{{ order.shipping?.data?.address?.info }}

-

{{ order.shipping?.data?.address?.street }} {{ order.shipping?.data?.address?.streetNumber }}

-

{{ order.shipping?.data?.address?.zipCode }} {{ order.shipping?.data?.address?.city }}

+

+ {{ order.shipping?.data?.address?.street }} + {{ order.shipping?.data?.address?.streetNumber }} +

+

+ {{ order.shipping?.data?.address?.zipCode }} + {{ order.shipping?.data?.address?.city }} +

-
+

Rechnungsadresse

-
+

{{ order.billing?.data?.organisation?.name }}

{{ order.billing?.data?.organisation?.department }}

-

{{ order.billing?.data?.firstName }} {{ order.billing?.data?.lastName }}

+

+ {{ order.billing?.data?.firstName }} + {{ order.billing?.data?.lastName }} +

{{ order.billing?.data?.address?.info }}

-

{{ order.billing?.data?.address?.street }} {{ order.billing?.data?.address?.streetNumber }}

-

{{ order.billing?.data?.address?.zipCode }} {{ order.billing?.data?.address?.city }}

+

+ {{ order.billing?.data?.address?.street }} + {{ order.billing?.data?.address?.streetNumber }} +

+

+ {{ order.billing?.data?.address?.zipCode }} + {{ order.billing?.data?.address?.city }} +

-
- - {{ selectedOrderItemCount$ | async }} von {{ orderItemCount$ | async }} Titeln +
+ + {{ selectedOrderItemCount$ | async }} von + {{ orderItemCount$ | async }} Titeln
@@ -263,13 +403,20 @@
- +
Zurücklegen bis
-
+
- + @@ -328,7 +500,11 @@ {{ orderItem?.estimatedShippingDate | date: 'dd.MM.yy' }} - +
- +
diff --git a/apps/isa-app/src/page/customer-order/customer-order-details/customer-order-details-header/customer-order-details-header.component.ts b/apps/isa-app/src/page/customer-order/customer-order-details/customer-order-details-header/customer-order-details-header.component.ts index d3e5cba64..85533214f 100644 --- a/apps/isa-app/src/page/customer-order/customer-order-details/customer-order-details-header/customer-order-details-header.component.ts +++ b/apps/isa-app/src/page/customer-order/customer-order-details/customer-order-details-header/customer-order-details-header.component.ts @@ -11,12 +11,17 @@ import { import { CrmCustomerService } from '@domain/crm'; import { DomainOmsService } from '@domain/oms'; import { NotificationChannel } from '@generated/swagger/checkout-api'; -import { KeyValueDTOOfStringAndString, OrderDTO, OrderItemListItemDTO } from '@generated/swagger/oms-api'; +import { + KeyValueDTOOfStringAndString, + OrderDTO, + OrderItemListItemDTO, +} from '@generated/swagger/oms-api'; import { DateAdapter } from '@ui/common'; import { cloneDeep } from 'lodash'; import { BehaviorSubject, combineLatest } from 'rxjs'; import { filter, first, map, shareReplay, switchMap } from 'rxjs/operators'; import { CustomerOrderDetailsStore } from '../customer-order-details.store'; +import { getEnabledCustomerFeature } from '@isa/crm/data-access'; @Component({ selector: 'page-customer-order-details-header', @@ -39,14 +44,21 @@ export class CustomerOrderDetailsHeaderComponent implements OnChanges { return this.order?.features?.orderSource === 'KulturPass'; } - minDateDatepicker = this.dateAdapter.addCalendarDays(this.dateAdapter.today(), -1); + minDateDatepicker = this.dateAdapter.addCalendarDays( + this.dateAdapter.today(), + -1, + ); today = this.dateAdapter.today(); - selectedOrderItemCount$ = this._store.selectedeOrderItemSubsetIds$.pipe(map((ids) => ids?.length ?? 0)); + selectedOrderItemCount$ = this._store.selectedeOrderItemSubsetIds$.pipe( + map((ids) => ids?.length ?? 0), + ); orderItemCount$ = this._store.items$.pipe(map((items) => items?.length ?? 0)); - orderItem$ = this._store.items$.pipe(map((orderItems) => orderItems?.find((_) => true))); + orderItem$ = this._store.items$.pipe( + map((orderItems) => orderItems?.find((_) => true)), + ); preferredPickUpDate$ = new BehaviorSubject(undefined); @@ -58,34 +70,54 @@ export class CustomerOrderDetailsHeaderComponent implements OnChanges { changeStatusDisabled$ = this._store.changeActionDisabled$; changeDateDisabled$ = this.changeStatusDisabled$; - features$ = this.orderItem$.pipe( + customerFeature$ = this.orderItem$.pipe( filter((orderItem) => !!orderItem), switchMap((orderItem) => this.customerService.getCustomers(orderItem.buyerNumber).pipe( - map((res) => res.result.find((c) => c.customerNumber === orderItem.buyerNumber)), - map((customer) => customer?.features || []), - map((features) => features.filter((f) => f.enabled && !!f.description)), + map((res) => + res.result.find((c) => c.customerNumber === orderItem.buyerNumber), + ), + map((customer) => getEnabledCustomerFeature(customer?.features)), ), ), shareReplay(), ); statusActions$ = this.orderItem$.pipe( - map((orderItem) => orderItem?.actions?.filter((action) => action.enabled === false)), + map((orderItem) => + orderItem?.actions?.filter((action) => action.enabled === false), + ), ); - showMultiselect$ = combineLatest([this._store.items$, this._store.fetchPartial$, this._store.itemsSelectable$]).pipe( - map(([orderItems, fetchPartial, multiSelect]) => multiSelect && fetchPartial && orderItems?.length > 1), + showMultiselect$ = combineLatest([ + this._store.items$, + this._store.fetchPartial$, + this._store.itemsSelectable$, + ]).pipe( + map( + ([orderItems, fetchPartial, multiSelect]) => + multiSelect && fetchPartial && orderItems?.length > 1, + ), ); - crudaUpdate$ = this.orderItem$.pipe(map((orederItem) => !!(orederItem?.cruda & 4))); + crudaUpdate$ = this.orderItem$.pipe( + map((orederItem) => !!(orederItem?.cruda & 4)), + ); - editButtonDisabled$ = combineLatest([this.changeStatusLoader$, this.crudaUpdate$]).pipe( - map(([changeStatusLoader, crudaUpdate]) => changeStatusLoader || !crudaUpdate), + editButtonDisabled$ = combineLatest([ + this.changeStatusLoader$, + this.crudaUpdate$, + ]).pipe( + map( + ([changeStatusLoader, crudaUpdate]) => changeStatusLoader || !crudaUpdate, + ), ); canEditStatus$ = combineLatest([this.statusActions$, this.crudaUpdate$]).pipe( - map(([statusActions, crudaUpdate]) => statusActions?.length > 0 && crudaUpdate), + map( + ([statusActions, crudaUpdate]) => + statusActions?.length > 0 && crudaUpdate, + ), ); openAddresses: boolean = false; @@ -96,7 +128,8 @@ export class CustomerOrderDetailsHeaderComponent implements OnChanges { get showAddresses(): boolean { return ( - (this.order?.orderType === 2 || this.order?.orderType === 4) && (!!this.order?.shipping || !!this.order?.billing) + (this.order?.orderType === 2 || this.order?.orderType === 4) && + (!!this.order?.shipping || !!this.order?.billing) ); } @@ -130,10 +163,20 @@ export class CustomerOrderDetailsHeaderComponent implements OnChanges { this.changeStatusDisabled$.next(true); const orderItems = cloneDeep(this._store.items); for (const item of orderItems) { - if (this.dateAdapter.compareDate(deadline, new Date(item.pickUpDeadline)) !== 0) { + if ( + this.dateAdapter.compareDate( + deadline, + new Date(item.pickUpDeadline), + ) !== 0 + ) { try { const res = await this.omsService - .setPickUpDeadline(item.orderId, item.orderItemId, item.orderItemSubsetId, deadline?.toISOString()) + .setPickUpDeadline( + item.orderId, + item.orderItemId, + item.orderItemSubsetId, + deadline?.toISOString(), + ) .pipe(first()) .toPromise(); item.pickUpDeadline = deadline.toISOString(); @@ -152,7 +195,12 @@ export class CustomerOrderDetailsHeaderComponent implements OnChanges { this.changeStatusDisabled$.next(true); const orderItems = cloneDeep(this._store.items); for (const item of orderItems) { - if (this.dateAdapter.compareDate(estimatedShippingDate, new Date(item.pickUpDeadline)) !== 0) { + if ( + this.dateAdapter.compareDate( + estimatedShippingDate, + new Date(item.pickUpDeadline), + ) !== 0 + ) { try { const res = await this.omsService .setEstimatedShippingDate( @@ -198,7 +246,10 @@ export class CustomerOrderDetailsHeaderComponent implements OnChanges { try { await this.omsService.setPreferredPickUpDate({ data }).toPromise(); this.order.items.forEach((item) => { - item.data.subsetItems.forEach((subsetItem) => (subsetItem.data.preferredPickUpDate = date.toISOString())); + item.data.subsetItems.forEach( + (subsetItem) => + (subsetItem.data.preferredPickUpDate = date.toISOString()), + ); }); this.findLatestPreferredPickUpDate(); } catch (error) { @@ -218,7 +269,10 @@ export class CustomerOrderDetailsHeaderComponent implements OnChanges { if (subsetItems?.length > 0) { latestDate = new Date( subsetItems?.reduce((a, b) => { - return new Date(a.data.preferredPickUpDate) > new Date(b.data.preferredPickUpDate) ? a : b; + return new Date(a.data.preferredPickUpDate) > + new Date(b.data.preferredPickUpDate) + ? a + : b; })?.data?.preferredPickUpDate, ); } diff --git a/apps/isa-app/src/page/customer/components/customer-result-list/customer-result-list-item-full/customer-result-list-item-full.component.ts b/apps/isa-app/src/page/customer/components/customer-result-list/customer-result-list-item-full/customer-result-list-item-full.component.ts index 2069c9266..883e9dee3 100644 --- a/apps/isa-app/src/page/customer/components/customer-result-list/customer-result-list-item-full/customer-result-list-item-full.component.ts +++ b/apps/isa-app/src/page/customer/components/customer-result-list/customer-result-list-item-full/customer-result-list-item-full.component.ts @@ -1,6 +1,7 @@ import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; import { CustomerInfoDTO } from '@generated/swagger/crm-api'; import { CustomerLabelColor, CustomerLabelTextColor } from '../../../constants'; +import { getEnabledCustomerFeature } from '@isa/crm/data-access'; @Component({ selector: 'page-customer-result-list-item-full', @@ -15,7 +16,7 @@ export class CustomerResultListItemFullComponent { customerLabelTextColor = CustomerLabelTextColor; get label() { - return this.customer?.features?.find((f) => f.enabled); + return getEnabledCustomerFeature(this.customer?.features); } @Input() diff --git a/apps/isa-app/src/page/customer/components/customer-result-list/customer-result-list-item/customer-result-list-item.component.ts b/apps/isa-app/src/page/customer/components/customer-result-list/customer-result-list-item/customer-result-list-item.component.ts index 523e25910..03a42221f 100644 --- a/apps/isa-app/src/page/customer/components/customer-result-list/customer-result-list-item/customer-result-list-item.component.ts +++ b/apps/isa-app/src/page/customer/components/customer-result-list/customer-result-list-item/customer-result-list-item.component.ts @@ -1,6 +1,7 @@ import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; import { CustomerInfoDTO } from '@generated/swagger/crm-api'; import { CustomerLabelColor, CustomerLabelTextColor } from '../../../constants'; +import { getEnabledCustomerFeature } from '@isa/crm/data-access'; @Component({ selector: 'page-customer-result-list-item', @@ -15,7 +16,7 @@ export class CustomerResultListItemComponent { customerLabelTextColor = CustomerLabelTextColor; get label() { - return this.customer?.features?.find((f) => f.enabled); + return getEnabledCustomerFeature(this.customer?.features); } @Input() diff --git a/apps/isa-app/src/page/customer/customer-search/order-details-main-view/order-details-main-view.component.ts b/apps/isa-app/src/page/customer/customer-search/order-details-main-view/order-details-main-view.component.ts index 49d5b11fe..e9fb5f6a2 100644 --- a/apps/isa-app/src/page/customer/customer-search/order-details-main-view/order-details-main-view.component.ts +++ b/apps/isa-app/src/page/customer/customer-search/order-details-main-view/order-details-main-view.component.ts @@ -1,4 +1,10 @@ -import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, inject } from '@angular/core'; +import { + Component, + ChangeDetectionStrategy, + OnInit, + OnDestroy, + inject, +} from '@angular/core'; import { CustomerSearchStore } from '../store'; import { ActivatedRoute, RouterLink } from '@angular/router'; import { map, takeUntil, tap } from 'rxjs/operators'; @@ -21,6 +27,7 @@ import { CustomerSearchNavigation } from '@shared/services/navigation'; import { CustomerOrderItemListItemComponent } from './order-item-list-item/order-item-list-item.component'; import { groupBy } from '@ui/common'; import { EnvironmentService } from '@core/environment'; +import { getEnabledCustomerFeature } from '@isa/crm/data-access'; @Component({ selector: 'page-customer-order-details-main-view', @@ -52,38 +59,58 @@ export class OrderDetailsMainViewComponent implements OnInit, OnDestroy { private _navigation = inject(CustomerSearchNavigation); private _env = inject(EnvironmentService); - orderId$ = this._activateRoute.params.pipe(map((params) => Number(params.orderId))); + orderId$ = this._activateRoute.params.pipe( + map((params) => Number(params.orderId)), + ); order$ = this._store.order$; - orderTargetBranch$ = this.order$.pipe(map((order) => order?.targetBranch?.id)); + orderTargetBranch$ = this.order$.pipe( + map((order) => order?.targetBranch?.id), + ); - orderShippingTarget$ = this.order$.pipe(map((order) => order?.shipping?.data)); + orderShippingTarget$ = this.order$.pipe( + map((order) => order?.shipping?.data), + ); - customerId$ = this._activateRoute.params.pipe(map((params) => Number(params.customerId))); + customerId$ = this._activateRoute.params.pipe( + map((params) => Number(params.customerId)), + ); customer$ = this._store.customer$; accountType$ = this.customer$.pipe( - map((customer) => customer?.features?.find((feature) => feature.group === 'd-customertype')), + map((customer) => getEnabledCustomerFeature(customer?.features)), ); - accountTypeKey$ = this.accountType$.pipe(map((accountType) => accountType?.key)); + accountTypeKey$ = this.accountType$.pipe( + map((accountType) => accountType?.key), + ); - accountTypeDescription$ = this.accountType$.pipe(map((accountType) => accountType?.description)); + accountTypeDescription$ = this.accountType$.pipe( + map((accountType) => accountType?.description), + ); - orderItemId$ = this._activateRoute.params.pipe(map((params) => Number(params.orderItemId))); + orderItemId$ = this._activateRoute.params.pipe( + map((params) => Number(params.orderItemId)), + ); - orderItems$ = this.order$.pipe(map((order) => order?.items?.map((i) => i?.data))); + orderItems$ = this.order$.pipe( + map((order) => order?.items?.map((i) => i?.data)), + ); selectedOrderItem$ = this._store.selectedOrderItem$; - selectedOrderItemOrderType$ = this.selectedOrderItem$.pipe(map((orderItem) => orderItem?.features?.orderType)); + selectedOrderItemOrderType$ = this.selectedOrderItem$.pipe( + map((orderItem) => orderItem?.features?.orderType), + ); private _onDestroy = new Subject(); ordersRoute$ = combineLatest([this.customerId$, this._store.processId$]).pipe( - map(([customerId, processId]) => this._navigation.ordersRoute({ processId, customerId })), + map(([customerId, processId]) => + this._navigation.ordersRoute({ processId, customerId }), + ), ); orderDetailsHistoryRoute$ = combineLatest([ @@ -93,27 +120,38 @@ export class OrderDetailsMainViewComponent implements OnInit, OnDestroy { this.orderItemId$, ]).pipe( map(([customerId, processId, orderId, orderItemId]) => - this._navigation.orderDetailsHistoryRoute({ processId, customerId, orderId, orderItemId }), + this._navigation.orderDetailsHistoryRoute({ + processId, + customerId, + orderId, + orderItemId, + }), ), ); groupedOrderItemsByOrderType$ = this.orderItems$.pipe( - map((orderItems) => groupBy(orderItems, (orderItem) => orderItem?.features?.orderType)), + map((orderItems) => + groupBy(orderItems, (orderItem) => orderItem?.features?.orderType), + ), tap((groupedOrderItems) => console.log(groupedOrderItems)), ); showSelectedItem$ = this._env.matchDesktopXLarge$; - showItemList$ = this.showSelectedItem$.pipe(map((showSelectedItem) => !showSelectedItem)); + showItemList$ = this.showSelectedItem$.pipe( + map((showSelectedItem) => !showSelectedItem), + ); ngOnInit(): void { this.orderId$.pipe(takeUntil(this._onDestroy)).subscribe((orderId) => { this._store.selectOrder(orderId); }); - this.customerId$.pipe(takeUntil(this._onDestroy)).subscribe((customerId) => { - this._store.selectCustomer({ customerId }); - }); + this.customerId$ + .pipe(takeUntil(this._onDestroy)) + .subscribe((customerId) => { + this._store.selectCustomer({ customerId }); + }); } ngOnDestroy(): void { diff --git a/apps/isa-app/src/page/pickup-shelf/shared/pickup-shelf-details-header/pickup-shelf-details-header.component.html b/apps/isa-app/src/page/pickup-shelf/shared/pickup-shelf-details-header/pickup-shelf-details-header.component.html index 426d2dd22..485cdd596 100644 --- a/apps/isa-app/src/page/pickup-shelf/shared/pickup-shelf-details-header/pickup-shelf-details-header.component.html +++ b/apps/isa-app/src/page/pickup-shelf/shared/pickup-shelf-details-header/pickup-shelf-details-header.component.html @@ -1,10 +1,19 @@
-
-
- - -
+
+
+ + +
{{ feature?.description }}
@@ -27,30 +36,69 @@ -
-
- -

+
+
+ +

{{ orderItem?.organisation }} - - + - {{ orderItem?.lastName }} {{ orderItem?.firstName }}
-
- {{ orderItem?.compartmentCode }}{{ orderItem?.compartmentInfo && '_' + orderItem?.compartmentInfo }} +
+ {{ orderItem?.compartmentCode + }}{{ + orderItem?.compartmentInfo && '_' + orderItem?.compartmentInfo + }}

-
-
- +
+
+ {{ orderItem?.features?.paid }}
-
- +
+ @@ -59,21 +107,42 @@
-
+
Kundennummer
-
{{ orderItem?.buyerNumber }}
+
+ {{ orderItem?.buyerNumber }} +
-
+
Vorgang-ID
-
{{ orderItem?.orderNumber }}
+
+ {{ orderItem?.orderNumber }} +
-
+
Bestelldatum
-
{{ orderItem?.orderDate | date: 'dd.MM.yy | HH:mm' }} Uhr
+
+ {{ orderItem?.orderDate | date: 'dd.MM.yy | HH:mm' }} Uhr +
-
+
Status
-
+
{{ orderItem?.processingStatus | processingStatus }} @@ -97,7 +166,12 @@ icon="arrow-drop-down" > - +
- +
-
+
Bestellkanal
- + {{ order()?.features?.orderSource }} @@ -142,19 +226,30 @@
Geändert
-
{{ orderItem?.processingStatusDate | date: 'dd.MM.yy | HH:mm' }} Uhr
+
+ {{ + orderItem?.processingStatusDate | date: 'dd.MM.yy | HH:mm' + }} + Uhr +
-
+
Benachrichtigung
- + - {{ (notificationsChannel$ | async | notificationsChannel) || '-' }} + {{ + (notificationsChannel$ | async | notificationsChannel) || '-' + }}
@@ -175,11 +275,17 @@
Abholfrist
-
+
Zurücklegen bis
-
+
vsl. Lieferdatum
-
+
(); @Output() - updateDate = new EventEmitter<{ date: Date; type?: 'delivery' | 'pickup' | 'preferred' }>(); + updateDate = new EventEmitter<{ + date: Date; + type?: 'delivery' | 'pickup' | 'preferred'; + }>(); orderItemSubsetLoading$ = this._store.orderItemSubsetLoading$; @@ -83,12 +95,19 @@ export class PickUpShelfDetailsHeaderComponent { ?.reduce((acc, item) => { return [...acc, ...item.data.subsetItems]; }, []) - ?.filter((a) => !!a.data.preferredPickUpDate && selectedOrderItemIds.find((id) => id === a.data.id)); + ?.filter( + (a) => + !!a.data.preferredPickUpDate && + selectedOrderItemIds.find((id) => id === a.data.id), + ); if (subsetItems?.length > 0) { latestDate = new Date( subsetItems?.reduce((a, b) => { - return new Date(a.data.preferredPickUpDate) > new Date(b.data.preferredPickUpDate) ? a : b; + return new Date(a.data.preferredPickUpDate) > + new Date(b.data.preferredPickUpDate) + ? a + : b; })?.data?.preferredPickUpDate, ); } @@ -108,14 +127,24 @@ export class PickUpShelfDetailsHeaderComponent { return this.order()?.features?.orderSource === 'KulturPass'; } - minDateDatepicker = this.dateAdapter.addCalendarDays(this.dateAdapter.today(), -1); + minDateDatepicker = this.dateAdapter.addCalendarDays( + this.dateAdapter.today(), + -1, + ); today = this.dateAdapter.today(); // Daten die im Header Angezeigt werden sollen orderItem$ = combineLatest([ this._store.selectedOrderItem$, // Wenn man im Abholfach ist muss das ausgewählte OrderItem genommen werden - this._store.selectedOrderItems$.pipe(map((orderItems) => orderItems?.find((_) => true))), // Wenn man in der Warenausgabe ist muss man das erste OrderItem nehmen - ]).pipe(map(([selectedOrderItem, selectedOrderItems]) => selectedOrderItem || selectedOrderItems)); + this._store.selectedOrderItems$.pipe( + map((orderItems) => orderItems?.find((_) => true)), + ), // Wenn man in der Warenausgabe ist muss man das erste OrderItem nehmen + ]).pipe( + map( + ([selectedOrderItem, selectedOrderItems]) => + selectedOrderItem || selectedOrderItems, + ), + ); changeDateLoader$ = new BehaviorSubject(false); changePreferredDateLoader$ = new BehaviorSubject(false); @@ -124,28 +153,41 @@ export class PickUpShelfDetailsHeaderComponent { changeDateDisabled$ = this.changeStatusDisabled$; - fetchingCustomerDone$ = this._store.fetchingCustomer$.pipe(map((fetchingCustomer) => !fetchingCustomer)); + fetchingCustomerDone$ = this._store.fetchingCustomer$.pipe( + map((fetchingCustomer) => !fetchingCustomer), + ); customer$ = this._store.customer$; - features$ = this.customer$.pipe( - map((customer) => customer?.features || []), - map((features) => features.filter((f) => f.enabled && !!f.description)), + customerFeature$ = this.customer$.pipe( + map((customer) => getEnabledCustomerFeature(customer?.features)), shareReplay(), ); statusActions$ = this.orderItem$.pipe( - map((orderItem) => orderItem?.actions?.filter((action) => action.enabled === false)), + map((orderItem) => + orderItem?.actions?.filter((action) => action.enabled === false), + ), ); - crudaUpdate$ = this.orderItem$.pipe(map((orederItem) => !!(orederItem?.cruda & 4))); + crudaUpdate$ = this.orderItem$.pipe( + map((orederItem) => !!(orederItem?.cruda & 4)), + ); - editButtonDisabled$ = combineLatest([this.changeStatusLoader$, this.crudaUpdate$]).pipe( - map(([changeStatusLoader, crudaUpdate]) => changeStatusLoader || !crudaUpdate), + editButtonDisabled$ = combineLatest([ + this.changeStatusLoader$, + this.crudaUpdate$, + ]).pipe( + map( + ([changeStatusLoader, crudaUpdate]) => changeStatusLoader || !crudaUpdate, + ), ); canEditStatus$ = combineLatest([this.statusActions$, this.crudaUpdate$]).pipe( - map(([statusActions, crudaUpdate]) => statusActions?.length > 0 && crudaUpdate), + map( + ([statusActions, crudaUpdate]) => + statusActions?.length > 0 && crudaUpdate, + ), ); constructor( diff --git a/libs/common/data-access/src/lib/schemas/key-value.schema.ts b/libs/common/data-access/src/lib/schemas/key-value.schema.ts index 6ca7b18a8..dfd3891cf 100644 --- a/libs/common/data-access/src/lib/schemas/key-value.schema.ts +++ b/libs/common/data-access/src/lib/schemas/key-value.schema.ts @@ -14,7 +14,10 @@ export const KeyValueSchema = < group: z.string().describe('Group').optional(), key: keySchema.describe('Key').optional(), label: z.string().describe('Label').optional(), - selected: z.boolean().describe('Whether this option is currently selected').optional(), + selected: z + .boolean() + .describe('Whether this option is currently selected') + .optional(), sort: z.number().describe('Sort criteria').optional(), value: valueSchema.describe('Value').optional(), }); @@ -23,3 +26,7 @@ export const KeyValueOfStringAndStringSchema = KeyValueSchema( z.string(), z.string(), ); + +export type KeyValueOfStringAndString = z.infer< + typeof KeyValueOfStringAndStringSchema +>; diff --git a/libs/crm/data-access/src/lib/helpers/get-enabled-customer-feature.helper.spec.ts b/libs/crm/data-access/src/lib/helpers/get-enabled-customer-feature.helper.spec.ts new file mode 100644 index 000000000..47871e0fb --- /dev/null +++ b/libs/crm/data-access/src/lib/helpers/get-enabled-customer-feature.helper.spec.ts @@ -0,0 +1,297 @@ +import { KeyValueOfStringAndString } from '@isa/common/data-access'; +import { getEnabledCustomerFeature } from './get-enabled-customer-feature.helper'; +import { CustomerFeatureKey, CustomerFeatureGroup } from '../schemas'; + +describe('getEnabledCustomerFeature', () => { + it('should return undefined when no features are provided', () => { + // Arrange + const customerFeatures: KeyValueOfStringAndString[] = []; + + // Act + const result = getEnabledCustomerFeature(customerFeatures); + + // Assert + expect(result).toBeUndefined(); + }); + + it('should return undefined when no features are enabled', () => { + // Arrange + const customerFeatures: KeyValueOfStringAndString[] = [ + { + key: CustomerFeatureKey.B2B, + value: 'test', + enabled: false, + description: 'test description', + group: CustomerFeatureGroup.DCustomerType, + }, + { + key: CustomerFeatureKey.Staff, + value: 'test', + enabled: false, + description: 'test description', + group: CustomerFeatureGroup.DCustomerType, + }, + ]; + + // Act + const result = getEnabledCustomerFeature(customerFeatures); + + // Assert + expect(result).toBeUndefined(); + }); + + it('should return the only enabled feature', () => { + // Arrange + const enabledFeature: KeyValueOfStringAndString = { + key: CustomerFeatureKey.B2B, + value: 'test-value', + enabled: true, + description: 'B2B feature', + group: CustomerFeatureGroup.DCustomerType, + }; + const customerFeatures: KeyValueOfStringAndString[] = [ + enabledFeature, + { + key: CustomerFeatureKey.Staff, + value: 'test', + enabled: false, + description: 'Staff feature', + group: CustomerFeatureGroup.DCustomerType, + }, + ]; + + // Act + const result = getEnabledCustomerFeature(customerFeatures); + + // Assert + expect(result).toBe(enabledFeature); + }); + + it('should prefer d-account when multiple features are enabled', () => { + // Arrange + const dAccountFeature: KeyValueOfStringAndString = { + key: CustomerFeatureKey.DAccount, + value: 'account-value', + enabled: true, + description: 'D-Account feature', + group: CustomerFeatureGroup.DCustomerType, + }; + const customerFeatures: KeyValueOfStringAndString[] = [ + { + key: CustomerFeatureKey.B2B, + value: 'b2b-value', + enabled: true, + description: 'B2B feature', + group: CustomerFeatureGroup.DCustomerType, + }, + dAccountFeature, + { + key: CustomerFeatureKey.Staff, + value: 'staff-value', + enabled: true, + description: 'Staff feature', + group: CustomerFeatureGroup.DCustomerType, + }, + ]; + + // Act + const result = getEnabledCustomerFeature(customerFeatures); + + // Assert + expect(result).toBe(dAccountFeature); + }); + + it('should prefer d-no-account when multiple features are enabled and d-account is not present', () => { + // Arrange + const dNoAccountFeature: KeyValueOfStringAndString = { + key: CustomerFeatureKey.DNoAccount, + value: 'no-account-value', + enabled: true, + description: 'D-No-Account feature', + group: CustomerFeatureGroup.DCustomerType, + }; + const customerFeatures: KeyValueOfStringAndString[] = [ + { + key: CustomerFeatureKey.B2B, + value: 'b2b-value', + enabled: true, + description: 'B2B feature', + group: CustomerFeatureGroup.DCustomerType, + }, + dNoAccountFeature, + { + key: CustomerFeatureKey.Webshop, + value: 'webshop-value', + enabled: true, + description: 'Webshop feature', + group: CustomerFeatureGroup.DCustomerType, + }, + ]; + + // Act + const result = getEnabledCustomerFeature(customerFeatures); + + // Assert + expect(result).toBe(dNoAccountFeature); + }); + + it('should return first enabled feature when multiple are enabled but neither d-account nor d-no-account are present', () => { + // Arrange + const firstEnabledFeature: KeyValueOfStringAndString = { + key: CustomerFeatureKey.B2B, + value: 'b2b-value', + enabled: true, + description: 'B2B feature', + group: CustomerFeatureGroup.DCustomerType, + }; + const customerFeatures: KeyValueOfStringAndString[] = [ + firstEnabledFeature, + { + key: CustomerFeatureKey.Staff, + value: 'staff-value', + enabled: true, + description: 'Staff feature', + group: CustomerFeatureGroup.DCustomerType, + }, + { + key: CustomerFeatureKey.Webshop, + value: 'webshop-value', + enabled: true, + description: 'Webshop feature', + group: CustomerFeatureGroup.DCustomerType, + }, + ]; + + // Act + const result = getEnabledCustomerFeature(customerFeatures); + + // Assert + expect(result).toBe(firstEnabledFeature); + }); + + it('should handle null or undefined customerFeatures gracefully', () => { + // Act & Assert + expect(getEnabledCustomerFeature(null as any)).toBeUndefined(); + expect(getEnabledCustomerFeature(undefined as any)).toBeUndefined(); + }); + + it('should return undefined when enabled features have no description', () => { + // Arrange + const customerFeatures: KeyValueOfStringAndString[] = [ + { + key: CustomerFeatureKey.B2B, + value: 'test-value', + enabled: true, + description: undefined, + group: CustomerFeatureGroup.DCustomerType, + }, + { + key: CustomerFeatureKey.Staff, + value: 'test-value', + enabled: true, + description: '', + group: CustomerFeatureGroup.DCustomerType, + }, + ]; + + // Act + const result = getEnabledCustomerFeature(customerFeatures); + + // Assert + expect(result).toBeUndefined(); + }); + + it('should only consider features with both enabled flag and description', () => { + // Arrange + const validFeature: KeyValueOfStringAndString = { + key: CustomerFeatureKey.B2B, + value: 'test-value', + enabled: true, + description: 'Valid B2B feature', + group: CustomerFeatureGroup.DCustomerType, + }; + const customerFeatures: KeyValueOfStringAndString[] = [ + { + key: CustomerFeatureKey.Staff, + value: 'test-value', + enabled: true, + description: undefined, + group: CustomerFeatureGroup.DCustomerType, + }, + validFeature, + { + key: CustomerFeatureKey.Webshop, + value: 'test-value', + enabled: true, + description: '', + group: CustomerFeatureGroup.DCustomerType, + }, + ]; + + // Act + const result = getEnabledCustomerFeature(customerFeatures); + + // Assert + expect(result).toBe(validFeature); + }); + + it('should return undefined when enabled features have wrong group', () => { + // Arrange + const customerFeatures: KeyValueOfStringAndString[] = [ + { + key: CustomerFeatureKey.B2B, + value: 'test-value', + enabled: true, + description: 'B2B feature', + group: CustomerFeatureGroup.CustomerType, + }, + { + key: CustomerFeatureKey.Staff, + value: 'test-value', + enabled: true, + description: 'Staff feature', + group: 'wrong-group', + }, + ]; + + // Act + const result = getEnabledCustomerFeature(customerFeatures); + + // Assert + expect(result).toBeUndefined(); + }); + + it('should only consider features with d-customertype group', () => { + // Arrange + const validFeature: KeyValueOfStringAndString = { + key: CustomerFeatureKey.DAccount, + value: 'test-value', + enabled: true, + description: 'Valid D-Account feature', + group: CustomerFeatureGroup.DCustomerType, + }; + const customerFeatures: KeyValueOfStringAndString[] = [ + { + key: CustomerFeatureKey.Staff, + value: 'test-value', + enabled: true, + description: 'Staff feature', + group: CustomerFeatureGroup.CustomerType, + }, + validFeature, + { + key: CustomerFeatureKey.Webshop, + value: 'test-value', + enabled: true, + description: 'Webshop feature', + group: 'other-group', + }, + ]; + + // Act + const result = getEnabledCustomerFeature(customerFeatures); + + // Assert + expect(result).toBe(validFeature); + }); +}); diff --git a/libs/crm/data-access/src/lib/helpers/get-enabled-customer-feature.helper.ts b/libs/crm/data-access/src/lib/helpers/get-enabled-customer-feature.helper.ts new file mode 100644 index 000000000..b064a7134 --- /dev/null +++ b/libs/crm/data-access/src/lib/helpers/get-enabled-customer-feature.helper.ts @@ -0,0 +1,53 @@ +import { KeyValueOfStringAndString } from '@isa/common/data-access'; +import { CustomerFeatureKey, CustomerFeatureGroup } from '../schemas'; + +/** + * Retrieves the first enabled customer feature that meets all validation criteria. + * + * A feature is considered valid if it satisfies ALL of the following conditions: + * - `enabled` property must be `true` + * - `description` property must exist and be non-empty + * - `group` property must be 'd-customertype' + * + * When multiple valid features are found, the function prioritizes features in the following order: + * 1. Features with key 'd-account' (highest priority) + * 2. Features with key 'd-no-account' (second priority) + * 3. The first valid feature in the array (fallback) + * + * @param customerFeatures - Array of customer feature objects to filter and evaluate + * @returns The first enabled feature matching all criteria, or `undefined` if none are found + * + * @example + * ```typescript + * const features = [ + * { key: 'b2b', enabled: true, description: 'B2B', group: 'd-customertype' }, + * { key: 'd-account', enabled: true, description: 'Account', group: 'd-customertype' } + * ]; + * const result = getEnabledCustomerFeature(features); + * // Returns the 'd-account' feature (prioritized over 'b2b') + * ``` + */ +export const getEnabledCustomerFeature = ( + customerFeatures: KeyValueOfStringAndString[], +): KeyValueOfStringAndString | undefined => { + const enabledFeatures = customerFeatures?.filter( + (f) => + f?.enabled && + !!f?.description && + f?.group === CustomerFeatureGroup.DCustomerType, + ); + + // If multiple features are enabled, prefer 'd-account' or 'd-no-account' + if (enabledFeatures?.length > 1) { + const preferredFeature = enabledFeatures.find( + (f) => + f.key === CustomerFeatureKey.DAccount || + f.key === CustomerFeatureKey.DNoAccount, + ); + // If a preferred feature is found, return it; otherwise fall back to the first enabled feature + return preferredFeature ?? enabledFeatures[0]; + } + + // Return the first enabled feature if only one is enabled + return enabledFeatures?.[0]; +}; diff --git a/libs/crm/data-access/src/lib/helpers/index.ts b/libs/crm/data-access/src/lib/helpers/index.ts index 500743682..5cf7df489 100644 --- a/libs/crm/data-access/src/lib/helpers/index.ts +++ b/libs/crm/data-access/src/lib/helpers/index.ts @@ -6,3 +6,4 @@ export { } from './deduplicate-addressees.helper'; export * from './get-customer-name.component'; export * from './get-primary-bonus-card.helper'; +export * from './get-enabled-customer-feature.helper'; diff --git a/libs/crm/data-access/src/lib/schemas/customer-feature-groups.schema.ts b/libs/crm/data-access/src/lib/schemas/customer-feature-groups.schema.ts new file mode 100644 index 000000000..9bbf4d4f7 --- /dev/null +++ b/libs/crm/data-access/src/lib/schemas/customer-feature-groups.schema.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; + +// Customer feature group literals +export const CustomerFeatureGroup = { + DCustomerType: 'd-customertype', + CustomerType: 'customertype', +} as const; + +export const CustomerFeatureGroupSchema = z + .enum([CustomerFeatureGroup.DCustomerType, CustomerFeatureGroup.CustomerType]) + .describe('Customer feature group'); + +export type CustomerFeatureGroupType = z.infer< + typeof CustomerFeatureGroupSchema +>; diff --git a/libs/crm/data-access/src/lib/schemas/customer-feature-keys.schema.ts b/libs/crm/data-access/src/lib/schemas/customer-feature-keys.schema.ts new file mode 100644 index 000000000..ec5eceed8 --- /dev/null +++ b/libs/crm/data-access/src/lib/schemas/customer-feature-keys.schema.ts @@ -0,0 +1,30 @@ +import { z } from 'zod'; + +// Customer feature key literals +export const CustomerFeatureKey = { + DAccount: 'd-account', + DNoAccount: 'd-no-account', + B2B: 'b2b', + Staff: 'staff', + BookshelfId: 'bookshelfId', + P4MAccountId: 'p4mAccountId', + P4MUser: 'p4mUser', + Webshop: 'webshop', + Guest: 'guest', +} as const; + +export const CustomerFeatureKeySchema = z + .enum([ + CustomerFeatureKey.DAccount, + CustomerFeatureKey.DNoAccount, + CustomerFeatureKey.B2B, + CustomerFeatureKey.Staff, + CustomerFeatureKey.BookshelfId, + CustomerFeatureKey.P4MAccountId, + CustomerFeatureKey.P4MUser, + CustomerFeatureKey.Webshop, + CustomerFeatureKey.Guest, + ]) + .describe('Customer feature key'); + +export type CustomerFeatureKeyType = z.infer; diff --git a/libs/crm/data-access/src/lib/schemas/index.ts b/libs/crm/data-access/src/lib/schemas/index.ts index 122a5e226..1e53837e8 100644 --- a/libs/crm/data-access/src/lib/schemas/index.ts +++ b/libs/crm/data-access/src/lib/schemas/index.ts @@ -3,6 +3,8 @@ export * from './attribute.schema'; export * from './bonus-card.schema'; export * from './branch.schema'; export * from './customer.schema'; +export * from './customer-feature-keys.schema'; +export * from './customer-feature-groups.schema'; export * from './fetch-customer-cards.schema'; export * from './fetch-customer-shipping-addresses.schema'; export * from './fetch-customer.schema';