mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
feat(checkout): implement reward order confirmation UI
Implement the complete UI for the reward order confirmation page including address displays, order item lists, and supporting helper functions. Features: - Add order confirmation addresses component displaying billing, delivery, and pickup branch addresses - Implement order confirmation item list with order type icons and item details - Add helper functions for order type feature checking and address/branch deduplication - Integrate store computed properties for payers, shipping addresses, and target branches - Apply responsive layout with Tailwind CSS styling
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { hasOrderTypeFeature } from './has-order-type-feature.helper';
|
||||
import { OrderType } from '../models';
|
||||
|
||||
describe('hasOrderTypeFeature', () => {
|
||||
describe('when features is undefined', () => {
|
||||
it('should return false', () => {
|
||||
// Act
|
||||
const result = hasOrderTypeFeature(undefined, [OrderType.Delivery]);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when features is empty', () => {
|
||||
it('should return false', () => {
|
||||
// Arrange
|
||||
const features = {};
|
||||
|
||||
// Act
|
||||
const result = hasOrderTypeFeature(features, [OrderType.Delivery]);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when orderType feature is not present', () => {
|
||||
it('should return false', () => {
|
||||
// Arrange
|
||||
const features = { someOtherFeature: 'value' };
|
||||
|
||||
// Act
|
||||
const result = hasOrderTypeFeature(features, [OrderType.Delivery]);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when orderType feature is invalid', () => {
|
||||
it('should return false', () => {
|
||||
// Arrange
|
||||
const features = { orderType: 'InvalidOrderType' };
|
||||
|
||||
// Act
|
||||
const result = hasOrderTypeFeature(features, [OrderType.Delivery]);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when orderType feature matches one of the provided types', () => {
|
||||
it('should return true for Delivery in delivery types', () => {
|
||||
// Arrange
|
||||
const features = { orderType: OrderType.Delivery };
|
||||
|
||||
// Act
|
||||
const result = hasOrderTypeFeature(features, [
|
||||
OrderType.Delivery,
|
||||
OrderType.DigitalShipping,
|
||||
OrderType.B2BShipping,
|
||||
]);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for DigitalShipping in delivery types', () => {
|
||||
// Arrange
|
||||
const features = { orderType: OrderType.DigitalShipping };
|
||||
|
||||
// Act
|
||||
const result = hasOrderTypeFeature(features, [
|
||||
OrderType.Delivery,
|
||||
OrderType.DigitalShipping,
|
||||
OrderType.B2BShipping,
|
||||
]);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for B2BShipping in delivery types', () => {
|
||||
// Arrange
|
||||
const features = { orderType: OrderType.B2BShipping };
|
||||
|
||||
// Act
|
||||
const result = hasOrderTypeFeature(features, [
|
||||
OrderType.Delivery,
|
||||
OrderType.DigitalShipping,
|
||||
OrderType.B2BShipping,
|
||||
]);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for InStore in branch types', () => {
|
||||
// Arrange
|
||||
const features = { orderType: OrderType.InStore };
|
||||
|
||||
// Act
|
||||
const result = hasOrderTypeFeature(features, [
|
||||
OrderType.InStore,
|
||||
OrderType.Pickup,
|
||||
]);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for Pickup in branch types', () => {
|
||||
// Arrange
|
||||
const features = { orderType: OrderType.Pickup };
|
||||
|
||||
// Act
|
||||
const result = hasOrderTypeFeature(features, [
|
||||
OrderType.InStore,
|
||||
OrderType.Pickup,
|
||||
]);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for Download when checked alone', () => {
|
||||
// Arrange
|
||||
const features = { orderType: OrderType.Download };
|
||||
|
||||
// Act
|
||||
const result = hasOrderTypeFeature(features, [OrderType.Download]);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when orderType feature does not match any provided types', () => {
|
||||
it('should return false for Delivery when checking branch types', () => {
|
||||
// Arrange
|
||||
const features = { orderType: OrderType.Delivery };
|
||||
|
||||
// Act
|
||||
const result = hasOrderTypeFeature(features, [
|
||||
OrderType.InStore,
|
||||
OrderType.Pickup,
|
||||
]);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for InStore when checking delivery types', () => {
|
||||
// Arrange
|
||||
const features = { orderType: OrderType.InStore };
|
||||
|
||||
// Act
|
||||
const result = hasOrderTypeFeature(features, [
|
||||
OrderType.Delivery,
|
||||
OrderType.DigitalShipping,
|
||||
OrderType.B2BShipping,
|
||||
]);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for Download when checking delivery types', () => {
|
||||
// Arrange
|
||||
const features = { orderType: OrderType.Download };
|
||||
|
||||
// Act
|
||||
const result = hasOrderTypeFeature(features, [
|
||||
OrderType.Delivery,
|
||||
OrderType.DigitalShipping,
|
||||
OrderType.B2BShipping,
|
||||
]);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when checking against empty order types array', () => {
|
||||
it('should return false', () => {
|
||||
// Arrange
|
||||
const features = { orderType: OrderType.Delivery };
|
||||
|
||||
// Act
|
||||
const result = hasOrderTypeFeature(features, []);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when checking against all order types', () => {
|
||||
it('should return true for any valid order type', () => {
|
||||
// Arrange
|
||||
const features = { orderType: OrderType.Delivery };
|
||||
|
||||
// Act
|
||||
const result = hasOrderTypeFeature(features, [
|
||||
OrderType.InStore,
|
||||
OrderType.Pickup,
|
||||
OrderType.Delivery,
|
||||
OrderType.DigitalShipping,
|
||||
OrderType.B2BShipping,
|
||||
OrderType.Download,
|
||||
]);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { OrderType } from '../models';
|
||||
import { getOrderTypeFeature } from './get-order-type-feature.helper';
|
||||
|
||||
/**
|
||||
* Checks if the order type feature in the provided features record matches any of the specified order types.
|
||||
*
|
||||
* @param features - Record containing feature flags with an 'orderType' key
|
||||
* @param orderTypes - Array of order types to check against
|
||||
* @returns true if the feature's order type is one of the provided types, false otherwise
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Check if order type is a delivery type
|
||||
* const isDelivery = hasOrderTypeFeature(features, [
|
||||
* OrderType.Delivery,
|
||||
* OrderType.DigitalShipping,
|
||||
* OrderType.B2BShipping
|
||||
* ]);
|
||||
*
|
||||
* // Check if order type requires a target branch
|
||||
* const hasTargetBranch = hasOrderTypeFeature(features, [
|
||||
* OrderType.InStore,
|
||||
* OrderType.Pickup
|
||||
* ]);
|
||||
* ```
|
||||
*/
|
||||
export function hasOrderTypeFeature(
|
||||
features: Record<string, string> | undefined,
|
||||
orderTypes: readonly OrderType[],
|
||||
): boolean {
|
||||
const orderType = getOrderTypeFeature(features);
|
||||
|
||||
if (!orderType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return orderTypes.includes(orderType);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './get-order-type-feature.helper';
|
||||
export * from './has-order-type-feature.helper';
|
||||
export * from './checkout-analysis.helpers';
|
||||
export * from './checkout-business-logic.helpers';
|
||||
export * from './checkout-data.helpers';
|
||||
|
||||
@@ -1 +1,46 @@
|
||||
<h1>Order Confirmation Addresses</h1>
|
||||
<div class="flex items-start gap-[2.5rem_5rem] self-stretch flex-wrap">
|
||||
@for (payer of payers(); track payer) {
|
||||
@if (payer.address) {
|
||||
<div>
|
||||
<h3 class="isa-text-body-1-regular">Rechnugsadresse</h3>
|
||||
<div class="isa-text-body-1-bold mt-1">
|
||||
{{ getCustomerName(payer) }}
|
||||
</div>
|
||||
<shared-address
|
||||
class="isa-text-body-1-bold"
|
||||
[address]="payer.address"
|
||||
></shared-address>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if (hasDeliveryOrderTypeFeature()) {
|
||||
@for (shippingAddress of shippingAddresses(); track shippingAddress) {
|
||||
@if (shippingAddress.address) {
|
||||
<div>
|
||||
<h3 class="isa-text-body-1-regular">Lieferadresse</h3>
|
||||
<div class="isa-text-body-1-bold mt-1">
|
||||
{{ getCustomerName(shippingAddress) }}
|
||||
</div>
|
||||
<shared-address
|
||||
class="isa-text-body-1-bold"
|
||||
[address]="shippingAddress.address"
|
||||
></shared-address>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
@if (hasTargetBranchFeature()) {
|
||||
@for (branch of targetBranches(); track branch) {
|
||||
<div>
|
||||
<h3 class="isa-text-body-1-regular">Abholfiliale</h3>
|
||||
<div class="isa-text-body-1-bold mt-1">
|
||||
{{ branch.name }}
|
||||
</div>
|
||||
<shared-address
|
||||
class="isa-text-body-1-bold"
|
||||
[address]="branch.address"
|
||||
></shared-address>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||
import { AddressComponent } from '@isa/shared/address';
|
||||
import { OrderConfiramtionStore } from '../reward-order-confirmation.store';
|
||||
import { getCustomerName } from '@isa/crm/data-access';
|
||||
|
||||
@Component({
|
||||
selector: 'checkout-order-confirmation-addresses',
|
||||
templateUrl: './order-confirmation-addresses.component.html',
|
||||
styleUrls: ['./order-confirmation-addresses.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [],
|
||||
imports: [AddressComponent],
|
||||
})
|
||||
export class OrderConfirmationAddressesComponent {}
|
||||
export class OrderConfirmationAddressesComponent {
|
||||
getCustomerName = getCustomerName;
|
||||
|
||||
#store = inject(OrderConfiramtionStore);
|
||||
|
||||
payers = this.#store.payers;
|
||||
|
||||
shippingAddresses = this.#store.shippingAddresses;
|
||||
|
||||
hasDeliveryOrderTypeFeature = this.#store.hasDeliveryOrderTypeFeature;
|
||||
|
||||
targetBranches = this.#store.targetBranches;
|
||||
|
||||
hasTargetBranchFeature = this.#store.hasTargetBranchFeature;
|
||||
}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
<h1>Order Confirmation Header</h1>
|
||||
<h1 class="text-isa-neutral-900 isa-text-subtitle-1-regular">
|
||||
Prämienausgabe abgeschlossen
|
||||
</h1>
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
<h1>Order Confirmation Item List Item</h1>
|
||||
<div>Product Info</div>
|
||||
<checkout-confirmation-list-item-action-card
|
||||
[item]="item()"
|
||||
></checkout-confirmation-list-item-action-card>
|
||||
<div>Destination Info</div>
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
||||
import { DisplayOrderItem } from '@isa/oms/data-access';
|
||||
import { ConfirmationListItemActionCardComponent } from './confirmation-list-item-action-card/confirmation-list-item-action-card.component';
|
||||
|
||||
@Component({
|
||||
selector: 'checkout-order-confirmation-item-list-item',
|
||||
templateUrl: './order-confirmation-item-list-item.component.html',
|
||||
styleUrls: ['./order-confirmation-item-list-item.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [],
|
||||
imports: [ConfirmationListItemActionCardComponent],
|
||||
})
|
||||
export class OrderConfirmationItemListItemComponent {}
|
||||
export class OrderConfirmationItemListItemComponent {
|
||||
item = input.required<DisplayOrderItem>();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply block w-full;
|
||||
}
|
||||
|
||||
@@ -1 +1,13 @@
|
||||
<h1>Order Confirmation Item List</h1>
|
||||
<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 (item of items(); track item.id) {
|
||||
<checkout-order-confirmation-item-list-item
|
||||
[item]="item"
|
||||
></checkout-order-confirmation-item-list-item>
|
||||
}
|
||||
|
||||
@@ -1,12 +1,58 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
input,
|
||||
} from '@angular/core';
|
||||
|
||||
import { OrderConfirmationItemListItemComponent } from './order-confirmation-item-list-item/order-confirmation-item-list-item.component';
|
||||
import { DisplayOrder, DisplayOrderItem } from '@isa/oms/data-access';
|
||||
import { getOrderTypeFeature, OrderType } from '@isa/checkout/data-access';
|
||||
import { NgIcon, provideIcons } from '@ng-icons/core';
|
||||
import {
|
||||
isaDeliveryVersand,
|
||||
isaDeliveryRuecklage2,
|
||||
isaDeliveryRuecklage1,
|
||||
} from '@isa/icons';
|
||||
|
||||
@Component({
|
||||
selector: 'checkout-order-confirmation-item-list',
|
||||
templateUrl: './order-confirmation-item-list.component.html',
|
||||
styleUrls: ['./order-confirmation-item-list.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [OrderConfirmationItemListItemComponent],
|
||||
imports: [OrderConfirmationItemListItemComponent, NgIcon],
|
||||
providers: [
|
||||
provideIcons({
|
||||
isaDeliveryVersand,
|
||||
isaDeliveryRuecklage2,
|
||||
isaDeliveryRuecklage1,
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class OrderConfirmationItemListComponent {}
|
||||
export class OrderConfirmationItemListComponent {
|
||||
order = input.required<DisplayOrder>();
|
||||
|
||||
orderType = computed(() => {
|
||||
return getOrderTypeFeature(this.order().features);
|
||||
});
|
||||
|
||||
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 ?? []);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply block w-full text-isa-neutral-900 mt-[1.42rem];
|
||||
}
|
||||
|
||||
@@ -1 +1,12 @@
|
||||
<checkout-order-confirmation-header></checkout-order-confirmation-header>
|
||||
<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>
|
||||
<div class="w-full h-[0.0625rem] bg-[#DEE2E6]"></div>
|
||||
<checkout-order-confirmation-addresses></checkout-order-confirmation-addresses>
|
||||
@for (order of orders(); track order.id) {
|
||||
<checkout-order-confirmation-item-list
|
||||
[order]="order"
|
||||
></checkout-order-confirmation-item-list>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -45,6 +45,8 @@ export class RewardOrderConfirmationComponent {
|
||||
return param ? param.split('+').map((strId) => parseInt(strId, 10)) : [];
|
||||
});
|
||||
|
||||
orders = this.#store.orders;
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const tabId = this.#tabId() || undefined;
|
||||
@@ -54,5 +56,9 @@ export class RewardOrderConfirmationComponent {
|
||||
this.#store.patch({ tabId, orderIds });
|
||||
});
|
||||
});
|
||||
|
||||
effect(() => {
|
||||
console.log('Orders to display:', this.orders());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { computed, inject, resource } from '@angular/core';
|
||||
import { computed, inject } from '@angular/core';
|
||||
import {
|
||||
deduplicateAddressees,
|
||||
deduplicateBranches,
|
||||
} from '@isa/crm/data-access';
|
||||
import { OmsMetadataService } from '@isa/oms/data-access';
|
||||
import { hasOrderTypeFeature, OrderType } from '@isa/checkout/data-access';
|
||||
import {
|
||||
patchState,
|
||||
signalStore,
|
||||
@@ -42,6 +47,57 @@ export const OrderConfiramtionStore = signalStore(
|
||||
return orders?.filter((order) => orderIds.includes(order.id!));
|
||||
}),
|
||||
})),
|
||||
withComputed((state) => ({
|
||||
payers: computed(() => {
|
||||
const orders = state.orders();
|
||||
if (!orders) {
|
||||
return undefined;
|
||||
}
|
||||
const buyers = orders.map((order) => order.buyer);
|
||||
return deduplicateAddressees(buyers);
|
||||
}),
|
||||
shippingAddresses: computed(() => {
|
||||
const orders = state.orders();
|
||||
if (!orders) {
|
||||
return undefined;
|
||||
}
|
||||
const addresses = orders.map((order) => order.shippingAddress);
|
||||
return deduplicateAddressees(addresses);
|
||||
}),
|
||||
targetBranches: computed(() => {
|
||||
const orders = state.orders();
|
||||
if (!orders) {
|
||||
return undefined;
|
||||
}
|
||||
const branches = orders.map((order) => order.targetBranch);
|
||||
return deduplicateBranches(branches);
|
||||
}),
|
||||
hasDeliveryOrderTypeFeature: computed(() => {
|
||||
const orders = state.orders();
|
||||
if (!orders || orders.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return orders.some((order) => {
|
||||
return hasOrderTypeFeature(order.features, [
|
||||
OrderType.Delivery,
|
||||
OrderType.DigitalShipping,
|
||||
OrderType.B2BShipping,
|
||||
]);
|
||||
});
|
||||
}),
|
||||
hasTargetBranchFeature: computed(() => {
|
||||
const orders = state.orders();
|
||||
if (!orders || orders.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return orders.some((order) => {
|
||||
return hasOrderTypeFeature(order.features, [
|
||||
OrderType.InStore,
|
||||
OrderType.Pickup,
|
||||
]);
|
||||
});
|
||||
}),
|
||||
})),
|
||||
withMethods((store) => ({
|
||||
patch(partial: Partial<OrderConfiramtionState>) {
|
||||
patchState(store, partial);
|
||||
|
||||
@@ -0,0 +1,498 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
areAddresseesEqual,
|
||||
areBranchesEqual,
|
||||
deduplicateAddressees,
|
||||
deduplicateBranches,
|
||||
} from './deduplicate-addressees.helper';
|
||||
import {
|
||||
DisplayAddresseeDTO,
|
||||
DisplayBranchDTO,
|
||||
AddressDTO,
|
||||
} from '@generated/swagger/oms-api';
|
||||
|
||||
describe('areAddresseesEqual', () => {
|
||||
const sampleAddress: AddressDTO = {
|
||||
street: 'Teststraße',
|
||||
streetNumber: '123',
|
||||
city: 'Berlin',
|
||||
zipCode: '10115',
|
||||
country: 'DEU',
|
||||
};
|
||||
|
||||
const createAddressee = (
|
||||
firstName: string,
|
||||
lastName: string,
|
||||
address?: AddressDTO,
|
||||
): DisplayAddresseeDTO => ({
|
||||
firstName,
|
||||
lastName,
|
||||
address,
|
||||
gender: 0, // NotSet
|
||||
});
|
||||
|
||||
it('should return true when both addressees are undefined', () => {
|
||||
// Act
|
||||
const result = areAddresseesEqual(undefined, undefined);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when only first addressee is undefined', () => {
|
||||
// Arrange
|
||||
const addressee = createAddressee('John', 'Doe', sampleAddress);
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(undefined, addressee);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when only second addressee is undefined', () => {
|
||||
// Arrange
|
||||
const addressee = createAddressee('John', 'Doe', sampleAddress);
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee, undefined);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when addressees have same name and address', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('John', 'Doe', { ...sampleAddress });
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when addressees have different first names', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('Jane', 'Doe', sampleAddress);
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when addressees have different last names', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('John', 'Smith', sampleAddress);
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when addressees have different addresses', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('John', 'Doe', {
|
||||
...sampleAddress,
|
||||
street: 'Other Street',
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when both addressees have no address', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe');
|
||||
const addressee2 = createAddressee('John', 'Doe');
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when one has address and other does not', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('John', 'Doe');
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle empty string names', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('', '', sampleAddress);
|
||||
const addressee2 = createAddressee('', '', sampleAddress);
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should treat undefined names as empty strings', () => {
|
||||
// Arrange
|
||||
const addressee1: DisplayAddresseeDTO = {
|
||||
gender: 0, // NotSet
|
||||
address: sampleAddress,
|
||||
};
|
||||
const addressee2: DisplayAddresseeDTO = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
gender: 0, // NotSet
|
||||
address: sampleAddress,
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = areAddresseesEqual(addressee1, addressee2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deduplicateAddressees', () => {
|
||||
const sampleAddress: AddressDTO = {
|
||||
street: 'Teststraße',
|
||||
streetNumber: '123',
|
||||
city: 'Berlin',
|
||||
zipCode: '10115',
|
||||
country: 'DEU',
|
||||
};
|
||||
|
||||
const createAddressee = (
|
||||
firstName: string,
|
||||
lastName: string,
|
||||
address?: AddressDTO,
|
||||
): DisplayAddresseeDTO => ({
|
||||
firstName,
|
||||
lastName,
|
||||
address,
|
||||
gender: 0, // NotSet
|
||||
});
|
||||
|
||||
it('should return empty array when input is empty', () => {
|
||||
// Arrange
|
||||
const addressees: DisplayAddresseeDTO[] = [];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should filter out undefined values', () => {
|
||||
// Arrange
|
||||
const addressee = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressees = [undefined, addressee, undefined];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(addressee);
|
||||
});
|
||||
|
||||
it('should return single item when array has one addressee', () => {
|
||||
// Arrange
|
||||
const addressee = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressees = [addressee];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(addressee);
|
||||
});
|
||||
|
||||
it('should return all items when no duplicates exist', () => {
|
||||
// Arrange
|
||||
const addressees = [
|
||||
createAddressee('John', 'Doe', sampleAddress),
|
||||
createAddressee('Jane', 'Smith', sampleAddress),
|
||||
createAddressee('Bob', 'Johnson', {
|
||||
...sampleAddress,
|
||||
street: 'Other Street',
|
||||
}),
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should remove duplicate addressees keeping first occurrence', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('John', 'Doe', { ...sampleAddress });
|
||||
const addressees = [addressee1, addressee2];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toBe(addressee1); // First occurrence kept
|
||||
});
|
||||
|
||||
it('should handle multiple duplicates and keep only first', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('John', 'Doe', { ...sampleAddress });
|
||||
const addressee3 = createAddressee('John', 'Doe', { ...sampleAddress });
|
||||
const addressees = [addressee1, addressee2, addressee3];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toBe(addressee1);
|
||||
});
|
||||
|
||||
it('should not consider different names but same address as duplicates', () => {
|
||||
// Arrange
|
||||
const addressees = [
|
||||
createAddressee('John', 'Doe', sampleAddress),
|
||||
createAddressee('Jane', 'Doe', sampleAddress),
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should not consider same names but different addresses as duplicates', () => {
|
||||
// Arrange
|
||||
const addressees = [
|
||||
createAddressee('John', 'Doe', sampleAddress),
|
||||
createAddressee('John', 'Doe', {
|
||||
...sampleAddress,
|
||||
street: 'Other Street',
|
||||
}),
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should handle complex scenario with mixed duplicates and unique items', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('John', 'Doe', sampleAddress);
|
||||
const addressee2 = createAddressee('John', 'Doe', { ...sampleAddress }); // Duplicate of 1
|
||||
const addressee3 = createAddressee('Jane', 'Smith', sampleAddress);
|
||||
const addressee4 = createAddressee('Jane', 'Smith', { ...sampleAddress }); // Duplicate of 3
|
||||
const addressee5 = createAddressee('Bob', 'Johnson', sampleAddress);
|
||||
const addressees = [
|
||||
addressee1,
|
||||
addressee2,
|
||||
addressee3,
|
||||
addressee4,
|
||||
addressee5,
|
||||
undefined,
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result[0]).toBe(addressee1);
|
||||
expect(result[1]).toBe(addressee3);
|
||||
expect(result[2]).toBe(addressee5);
|
||||
});
|
||||
|
||||
it('should preserve order of first occurrences', () => {
|
||||
// Arrange
|
||||
const addressee1 = createAddressee('Alice', 'Wonder', sampleAddress);
|
||||
const addressee2 = createAddressee('Bob', 'Builder', sampleAddress);
|
||||
const addressee3 = createAddressee('Alice', 'Wonder', { ...sampleAddress }); // Duplicate
|
||||
const addressees = [addressee1, addressee2, addressee3];
|
||||
|
||||
// Act
|
||||
const result = deduplicateAddressees(addressees);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toBe(addressee1);
|
||||
expect(result[1]).toBe(addressee2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('areBranchesEqual', () => {
|
||||
const sampleAddress: AddressDTO = {
|
||||
street: 'Teststraße',
|
||||
streetNumber: '123',
|
||||
city: 'Berlin',
|
||||
zipCode: '10115',
|
||||
country: 'DEU',
|
||||
};
|
||||
|
||||
const createBranch = (
|
||||
name: string,
|
||||
address?: AddressDTO,
|
||||
): DisplayBranchDTO => ({
|
||||
name,
|
||||
address,
|
||||
});
|
||||
|
||||
it('should return true when both branches are undefined', () => {
|
||||
// Act
|
||||
const result = areBranchesEqual(undefined, undefined);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when only first branch is undefined', () => {
|
||||
// Arrange
|
||||
const branch = createBranch('Branch 1', sampleAddress);
|
||||
|
||||
// Act
|
||||
const result = areBranchesEqual(undefined, branch);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when branches have same name and address', () => {
|
||||
// Arrange
|
||||
const branch1 = createBranch('Branch 1', sampleAddress);
|
||||
const branch2 = createBranch('Branch 1', { ...sampleAddress });
|
||||
|
||||
// Act
|
||||
const result = areBranchesEqual(branch1, branch2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when branches have different names', () => {
|
||||
// Arrange
|
||||
const branch1 = createBranch('Branch 1', sampleAddress);
|
||||
const branch2 = createBranch('Branch 2', sampleAddress);
|
||||
|
||||
// Act
|
||||
const result = areBranchesEqual(branch1, branch2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when branches have different addresses', () => {
|
||||
// Arrange
|
||||
const branch1 = createBranch('Branch 1', sampleAddress);
|
||||
const branch2 = createBranch('Branch 1', {
|
||||
...sampleAddress,
|
||||
street: 'Other Street',
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = areBranchesEqual(branch1, branch2);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deduplicateBranches', () => {
|
||||
const sampleAddress: AddressDTO = {
|
||||
street: 'Teststraße',
|
||||
streetNumber: '123',
|
||||
city: 'Berlin',
|
||||
zipCode: '10115',
|
||||
country: 'DEU',
|
||||
};
|
||||
|
||||
const createBranch = (
|
||||
name: string,
|
||||
address?: AddressDTO,
|
||||
): DisplayBranchDTO => ({
|
||||
name,
|
||||
address,
|
||||
});
|
||||
|
||||
it('should return empty array when input is empty', () => {
|
||||
// Arrange
|
||||
const branches: DisplayBranchDTO[] = [];
|
||||
|
||||
// Act
|
||||
const result = deduplicateBranches(branches);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should filter out undefined values', () => {
|
||||
// Arrange
|
||||
const branch = createBranch('Branch 1', sampleAddress);
|
||||
const branches = [undefined, branch, undefined];
|
||||
|
||||
// Act
|
||||
const result = deduplicateBranches(branches);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual(branch);
|
||||
});
|
||||
|
||||
it('should remove duplicate branches keeping first occurrence', () => {
|
||||
// Arrange
|
||||
const branch1 = createBranch('Branch 1', sampleAddress);
|
||||
const branch2 = createBranch('Branch 1', { ...sampleAddress });
|
||||
const branches = [branch1, branch2];
|
||||
|
||||
// Act
|
||||
const result = deduplicateBranches(branches);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toBe(branch1);
|
||||
});
|
||||
|
||||
it('should return all items when no duplicates exist', () => {
|
||||
// Arrange
|
||||
const branches = [
|
||||
createBranch('Branch 1', sampleAddress),
|
||||
createBranch('Branch 2', sampleAddress),
|
||||
createBranch('Branch 3', {
|
||||
...sampleAddress,
|
||||
street: 'Other Street',
|
||||
}),
|
||||
];
|
||||
|
||||
// Act
|
||||
const result = deduplicateBranches(branches);
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,207 @@
|
||||
import {
|
||||
DisplayAddresseeDTO,
|
||||
DisplayBranchDTO,
|
||||
} from '@generated/swagger/oms-api';
|
||||
|
||||
/**
|
||||
* Internal model representing an entity with name and address for comparison.
|
||||
* This allows for type-safe comparison without casting.
|
||||
*/
|
||||
interface EntityWithNameAndAddress {
|
||||
readonly name: string;
|
||||
readonly address: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an addressee-like object to a comparable entity.
|
||||
*/
|
||||
function toComparableAddressee(
|
||||
addressee: Pick<DisplayAddresseeDTO, 'firstName' | 'lastName' | 'address'>,
|
||||
): EntityWithNameAndAddress {
|
||||
const firstName = addressee.firstName ?? '';
|
||||
const lastName = addressee.lastName ?? '';
|
||||
const name = `${firstName}|${lastName}`.trim();
|
||||
return {
|
||||
name,
|
||||
address: addressee.address ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a branch-like object to a comparable entity.
|
||||
*/
|
||||
function toComparableBranch(
|
||||
branch: Pick<DisplayBranchDTO, 'name' | 'address'>,
|
||||
): EntityWithNameAndAddress {
|
||||
return {
|
||||
name: branch.name ?? '',
|
||||
address: branch.address ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two entities for equality based on name and address.
|
||||
*/
|
||||
function areEntitiesEqual(
|
||||
entity1: EntityWithNameAndAddress,
|
||||
entity2: EntityWithNameAndAddress,
|
||||
): boolean {
|
||||
if (entity1.name !== entity2.name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const address1Str = JSON.stringify(entity1.address);
|
||||
const address2Str = JSON.stringify(entity2.address);
|
||||
|
||||
return address1Str === address2Str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two DisplayAddresseeDTO objects for equality based on name and address.
|
||||
* Two addressees are considered equal if they have the same firstName, lastName, and address.
|
||||
*
|
||||
* @param addressee1 - First addressee to compare
|
||||
* @param addressee2 - Second addressee to compare
|
||||
* @returns true if addressees are equal, false otherwise
|
||||
*/
|
||||
export function areAddresseesEqual(
|
||||
addressee1: DisplayAddresseeDTO | undefined,
|
||||
addressee2: DisplayAddresseeDTO | undefined,
|
||||
): boolean {
|
||||
if (!addressee1 && !addressee2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!addressee1 || !addressee2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return areEntitiesEqual(
|
||||
toComparableAddressee(addressee1),
|
||||
toComparableAddressee(addressee2),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two DisplayBranchDTO objects for equality based on name and address.
|
||||
* Two branches are considered equal if they have the same name and address.
|
||||
*
|
||||
* @param branch1 - First branch to compare
|
||||
* @param branch2 - Second branch to compare
|
||||
* @returns true if branches are equal, false otherwise
|
||||
*/
|
||||
export function areBranchesEqual(
|
||||
branch1: DisplayBranchDTO | undefined,
|
||||
branch2: DisplayBranchDTO | undefined,
|
||||
): boolean {
|
||||
if (!branch1 && !branch2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!branch1 || !branch2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return areEntitiesEqual(
|
||||
toComparableBranch(branch1),
|
||||
toComparableBranch(branch2),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if an object can be compared as an addressee.
|
||||
*/
|
||||
function hasAddresseeShape(
|
||||
obj: unknown,
|
||||
): obj is Pick<DisplayAddresseeDTO, 'firstName' | 'lastName' | 'address'> {
|
||||
return (
|
||||
obj !== null &&
|
||||
typeof obj === 'object' &&
|
||||
('firstName' in obj || 'lastName' in obj || 'address' in obj)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if an object can be compared as a branch.
|
||||
*/
|
||||
function hasBranchShape(
|
||||
obj: unknown,
|
||||
): obj is Pick<DisplayBranchDTO, 'name' | 'address'> {
|
||||
return (
|
||||
obj !== null &&
|
||||
typeof obj === 'object' &&
|
||||
('name' in obj || 'address' in obj)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes duplicate addressees from an array, keeping only the first occurrence.
|
||||
* Two addressees are considered duplicates if they have the same firstName, lastName, and address.
|
||||
*
|
||||
* @param addressees - Array of addressees to deduplicate
|
||||
* @returns Deduplicated array with only first occurrence of each unique addressee
|
||||
*/
|
||||
export function deduplicateAddressees<
|
||||
T extends Pick<DisplayAddresseeDTO, 'firstName' | 'lastName' | 'address'>,
|
||||
>(addressees: readonly (T | undefined)[]): T[] {
|
||||
const result: T[] = [];
|
||||
const seen: T[] = [];
|
||||
|
||||
for (const addressee of addressees) {
|
||||
if (!addressee || !hasAddresseeShape(addressee)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isDuplicate = seen.some((seenAddressee) =>
|
||||
hasAddresseeShape(seenAddressee)
|
||||
? areEntitiesEqual(
|
||||
toComparableAddressee(seenAddressee),
|
||||
toComparableAddressee(addressee),
|
||||
)
|
||||
: false,
|
||||
);
|
||||
|
||||
if (!isDuplicate) {
|
||||
result.push(addressee);
|
||||
seen.push(addressee);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes duplicate branches from an array, keeping only the first occurrence.
|
||||
* Two branches are considered duplicates if they have the same name and address.
|
||||
*
|
||||
* @param branches - Array of branches to deduplicate
|
||||
* @returns Deduplicated array with only first occurrence of each unique branch
|
||||
*/
|
||||
export function deduplicateBranches<
|
||||
T extends Pick<DisplayBranchDTO, 'name' | 'address'>,
|
||||
>(branches: readonly (T | undefined)[]): T[] {
|
||||
const result: T[] = [];
|
||||
const seen: T[] = [];
|
||||
|
||||
for (const branch of branches) {
|
||||
if (!branch || !hasBranchShape(branch)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isDuplicate = seen.some((seenBranch) =>
|
||||
hasBranchShape(seenBranch)
|
||||
? areEntitiesEqual(
|
||||
toComparableBranch(seenBranch),
|
||||
toComparableBranch(branch),
|
||||
)
|
||||
: false,
|
||||
);
|
||||
|
||||
if (!isDuplicate) {
|
||||
result.push(branch);
|
||||
seen.push(branch);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1,2 +1,8 @@
|
||||
export {
|
||||
areAddresseesEqual,
|
||||
areBranchesEqual,
|
||||
deduplicateAddressees,
|
||||
deduplicateBranches,
|
||||
} from './deduplicate-addressees.helper';
|
||||
export * from './get-customer-name.component';
|
||||
export * from './get-primary-bonus-card.helper';
|
||||
|
||||
@@ -1,26 +1,32 @@
|
||||
import { EntitySchema, QuantityUnitTypeSchema } from '@isa/common/data-access';
|
||||
import { z } from 'zod';
|
||||
import { PriceSchema } from './price.schema';
|
||||
import { ProductSchema } from './product.schema';
|
||||
import { PromotionSchema } from './promotion.schema';
|
||||
|
||||
// Forward declaration for circular reference
|
||||
export const DisplayOrderItemSchema: z.ZodType<any> = z
|
||||
.object({
|
||||
buyerComment: z.string().describe('Buyer comment').optional(),
|
||||
description: z.string().describe('Description text').optional(),
|
||||
features: z.record(z.string().describe('Features'), z.string()).optional(),
|
||||
order: z.lazy(() => z.any()).describe('Order').optional(), // Circular reference to DisplayOrder
|
||||
orderDate: z.string().describe('Order date').optional(),
|
||||
orderItemNumber: z.string().describe('OrderItem number').optional(),
|
||||
price: PriceSchema.describe('Price information').optional(),
|
||||
product: ProductSchema.describe('Product').optional(),
|
||||
promotion: PromotionSchema.describe('Promotion information').optional(),
|
||||
quantity: z.number().describe('Quantity').optional(),
|
||||
quantityUnit: z.string().describe('Quantity unit').optional(),
|
||||
quantityUnitType: QuantityUnitTypeSchema.describe('QuantityUnit type'),
|
||||
subsetItems: z.array(z.lazy(() => z.any())).describe('Subset items').optional(), // Circular reference to DisplayOrderItemSubset
|
||||
})
|
||||
.extend(EntitySchema.shape);
|
||||
|
||||
export type DisplayOrderItem = z.infer<typeof DisplayOrderItemSchema>;
|
||||
import { EntitySchema, QuantityUnitTypeSchema } from '@isa/common/data-access';
|
||||
import { z } from 'zod';
|
||||
import { PriceSchema } from './price.schema';
|
||||
import { ProductSchema } from './product.schema';
|
||||
import { PromotionSchema } from './promotion.schema';
|
||||
|
||||
// Forward declaration for circular reference
|
||||
export const DisplayOrderItemSchema = z
|
||||
.object({
|
||||
buyerComment: z.string().describe('Buyer comment').optional(),
|
||||
description: z.string().describe('Description text').optional(),
|
||||
features: z.record(z.string().describe('Features'), z.string()).optional(),
|
||||
order: z
|
||||
.lazy(() => z.any())
|
||||
.describe('Order')
|
||||
.optional(), // Circular reference to DisplayOrder
|
||||
orderDate: z.string().describe('Order date').optional(),
|
||||
orderItemNumber: z.string().describe('OrderItem number').optional(),
|
||||
price: PriceSchema.describe('Price information').optional(),
|
||||
product: ProductSchema.describe('Product').optional(),
|
||||
promotion: PromotionSchema.describe('Promotion information').optional(),
|
||||
quantity: z.number().describe('Quantity').optional(),
|
||||
quantityUnit: z.string().describe('Quantity unit').optional(),
|
||||
quantityUnitType: QuantityUnitTypeSchema.describe('QuantityUnit type'),
|
||||
subsetItems: z
|
||||
.array(z.lazy(() => z.any()))
|
||||
.describe('Subset items')
|
||||
.optional(), // Circular reference to DisplayOrderItemSubset
|
||||
})
|
||||
.extend(EntitySchema.shape);
|
||||
|
||||
export type DisplayOrderItem = z.infer<typeof DisplayOrderItemSchema>;
|
||||
|
||||
Reference in New Issue
Block a user