feat(checkout): add reward order confirmation feature with schema migrations

- Add new reward-order-confirmation feature library with components and store
- Implement checkout completion orchestrator service for order finalization
- Migrate checkout/oms/crm models to Zod schemas for better type safety
- Add order creation facade and display order schemas
- Update shopping cart facade with order completion flow
- Add comprehensive tests for shopping cart facade
- Update routing to include order confirmation page
This commit is contained in:
Lorenz Hilpert
2025-10-21 14:28:52 +02:00
parent b96d889da5
commit 2b5da00249
259 changed files with 61347 additions and 2652 deletions

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +0,0 @@
export const BuyerType = {
NotSet: 0,
ContactCustomer: 1,
BranchCustomer: 2,
Staff: 4,
B2C: 8,
B2B: 16,
} as const;
export type BuyerType = (typeof BuyerType)[keyof typeof BuyerType];

View File

@@ -1,6 +1,5 @@
export * from './async-result';
export * from './batch-response-args';
export * from './buyer-type';
export * from './callback-result';
export * from './entity-cotnainer';
export * from './entity-status';

View File

@@ -2,11 +2,11 @@ import z from 'zod';
export const AddressSchema = z
.object({
street: z.string().optional(),
streetNumber: z.string().optional(),
postalCode: z.string().optional(),
city: z.string().optional(),
country: z.string().optional(),
additionalInfo: z.string().optional(),
street: z.string().describe('Street name').optional(),
streetNumber: z.string().describe('Street number').optional(),
postalCode: z.string().describe('Postal code').optional(),
city: z.string().describe('City name').optional(),
country: z.string().describe('Country').optional(),
additionalInfo: z.string().describe('Additional information').optional(),
})
.optional();

View File

@@ -6,12 +6,12 @@ import { GenderSchema } from './gender.schema';
import { OrganisationSchema } from './organisation.schema';
export const AddresseeWithReferenceSchema = EntityReferenceSchema.extend({
address: AddressSchema.optional(),
communicationDetails: CommunicationDetailsSchema.optional(),
firstName: z.string().optional(),
lastName: z.string().optional(),
gender: GenderSchema.optional(),
locale: z.string().optional(),
organisation: OrganisationSchema.optional(),
title: z.string().optional(),
address: AddressSchema.describe('Address').optional(),
communicationDetails: CommunicationDetailsSchema.describe('Communication details').optional(),
firstName: z.string().describe('First name').optional(),
lastName: z.string().describe('Last name').optional(),
gender: GenderSchema.describe('Gender').optional(),
locale: z.string().describe('Locale').optional(),
organisation: OrganisationSchema.describe('Organisation information').optional(),
title: z.string().describe('Title').optional(),
});

View File

@@ -0,0 +1,14 @@
import { z } from 'zod';
export const BuyerType = {
NotSet: 0,
ContactCustomer: 1,
BranchCustomer: 2,
Staff: 4,
B2C: 8,
B2B: 16,
} as const;
export const BuyerTypeSchema = z.nativeEnum(BuyerType).describe('Buyer type');
export type BuyerType = z.infer<typeof BuyerTypeSchema>;

View File

@@ -1,8 +1,8 @@
import z from 'zod';
export const CommunicationDetailsSchema = z.object({
email: z.string().optional(),
phone: z.string().optional(),
mobile: z.string().optional(),
fax: z.string().optional(),
email: z.string().describe('Email address').optional(),
phone: z.string().describe('Phone number').optional(),
mobile: z.string().describe('Mobile phone number').optional(),
fax: z.string().describe('Fax number').optional(),
});

View File

@@ -14,5 +14,5 @@ import { EntityReferenceContainerSchema } from './entity-reference-container.sch
*/
export const EntityContainerSchema = <T extends z.ZodTypeAny>(dataSchema: T) =>
EntityReferenceContainerSchema.extend({
data: dataSchema.optional(),
data: dataSchema.describe('Data').optional(),
});

View File

@@ -1,18 +1,20 @@
import { z } from 'zod';
import { ExternalReferenceSchema } from './external-reference.schema';
/**
* Schema for EntityDTOReferenceContainer
* Base container type for entity references with metadata
*
* Note: externalReference uses z.any() to avoid type conflicts between
* our optional ExternalReferenceSchema and the generated DTOs which require externalStatus
*/
export const EntityReferenceContainerSchema = z.object({
displayLabel: z.string().optional(),
enabled: z.boolean().optional(),
externalReference: ExternalReferenceSchema.optional(),
id: z.number().optional(),
pId: z.string().optional(),
selected: z.boolean().optional(),
uId: z.string().optional(),
displayLabel: z.string().describe('Display label').optional(),
enabled: z.boolean().describe('Whether this is enabled').optional(),
externalReference: z.any().describe('External system reference').optional(),
id: z.number().describe('Unique identifier').optional(),
pId: z.string().describe('P identifier').optional(),
selected: z.boolean().describe('Whether this option is currently selected').optional(),
uId: z.string().describe('U identifier').optional(),
});
export type EntityReferenceContainer = z.infer<

View File

@@ -2,7 +2,7 @@ import z from 'zod';
import { EntityReferenceContainerSchema } from './entity-reference-container.schema';
export const EntityReferenceSchema = z.object({
pId: z.string().optional(),
reference: EntityReferenceContainerSchema.optional(),
source: z.number().optional(),
pId: z.string().describe('P identifier').optional(),
reference: EntityReferenceContainerSchema.describe('Reference').optional(),
source: z.number().describe('Source').optional(),
});

View File

@@ -4,4 +4,4 @@ import { EntityStatus } from '../models';
/**
* EntityStatus is a bitwise enum with values: 0 | 1 | 2 | 4 | 8
*/
export const EntityStatusSchema = z.nativeEnum(EntityStatus);
export const EntityStatusSchema = z.nativeEnum(EntityStatus).describe('Entity status');

View File

@@ -0,0 +1,12 @@
import { z } from 'zod';
import { EntityStatusSchema } from './entity-status.schema';
export const EntitySchema = z.object({
changed: z.string().describe('Changed').optional(),
created: z.string().describe('Created').optional(),
id: z.number().int().positive().describe('Unique identifier').optional(),
pId: z.string().describe('P identifier').optional(),
status: EntityStatusSchema.describe('Current status').optional(),
uId: z.string().describe('U identifier').optional(),
version: z.number().int().nonnegative().describe('Version').optional(),
});

View File

@@ -5,17 +5,24 @@ import { EntityStatusSchema } from './entity-status.schema';
* Schema for ExternalReferenceDTO
* Represents external system reference information
*
* Note: externalStatus is REQUIRED in all generated ExternalReferenceDTO types
* Note: In the generated DTOs, externalStatus is REQUIRED when ExternalReferenceDTO is present.
* However, we make all fields optional in this schema because:
* 1. This schema is used within EntityReferenceContainerSchema where externalReference itself is optional
* 2. When constructing objects, we often omit externalReference entirely
* 3. Type compatibility: our inferred types must be assignable to the generated DTOs
*
* When using this schema to create objects that will be passed to generated API clients,
* either omit externalReference entirely OR ensure externalStatus is provided.
*/
export const ExternalReferenceSchema = z.object({
externalChanged: z.string().optional(),
externalCreated: z.string().optional(),
externalNumber: z.string().optional(),
externalPK: z.string().optional(),
externalRepository: z.string().optional(),
externalStatus: EntityStatusSchema,
externalVersion: z.number().optional(),
publishToken: z.string().optional(),
externalChanged: z.string().describe('External changed').optional(),
externalCreated: z.string().describe('External created').optional(),
externalNumber: z.string().describe('External number').optional(),
externalPK: z.string().describe('External p k').optional(),
externalRepository: z.string().describe('External repository').optional(),
externalStatus: EntityStatusSchema.describe('External status').optional(),
externalVersion: z.number().describe('External version').optional(),
publishToken: z.string().describe('Publish token').optional(),
});
export type ExternalReference = z.infer<typeof ExternalReferenceSchema>;

View File

@@ -1,4 +1,4 @@
import z from 'zod';
import { Gender } from '../models';
export const GenderSchema = z.nativeEnum(Gender);
export const GenderSchema = z.nativeEnum(Gender).describe('Gender');

View File

@@ -1,15 +1,23 @@
export * from './address.schema';
export * from './addressee-with-reference.schema';
export * from './buyer-type.schema';
export * from './communication-details.schema';
export * from './entity-container.schema';
export * from './entity-reference-container.schema';
export * from './entity-reference.schema';
export * from './entity-status.schema';
export * from './entity.schema';
export * from './external-reference.schema';
export * from './gender.schema';
export * from './key-value.schema';
export * from './label.schema';
export * from './notification-channel.schema';
export * from './organisation-names.schema';
export * from './organisation.schema';
export * from './payment-type.schema';
export * from './price-value.schema';
export * from './price.schema';
export * from './quantity-unit-type.schema';
export * from './touch-base.schema';
export * from './vat-type.schema';
export * from './vat-value.schema';

View File

@@ -0,0 +1,25 @@
import z from 'zod';
export const KeyValueSchema = <
TK extends z.ZodTypeAny,
TV extends z.ZodTypeAny,
>(
keySchema: TK,
valueSchema: TV,
) =>
z.object({
command: z.string().describe('Command').optional(),
description: z.string().describe('Description text').optional(),
enabled: z.boolean().describe('Whether this is enabled').optional(),
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(),
sort: z.number().describe('Sort criteria').optional(),
value: valueSchema.describe('Value').optional(),
});
export const KeyValueOfStringAndStringSchema = KeyValueSchema(
z.string(),
z.string(),
);

View File

@@ -0,0 +1,9 @@
import { z } from 'zod';
import { EntitySchema } from './entity.schema';
export const LabelSchema = z
.object({
key: z.string().describe('Key').optional(),
name: z.string().describe('Name').optional(),
})
.extend(EntitySchema.shape);

View File

@@ -0,0 +1,14 @@
import z from 'zod';
export const NotificationChannel = {
NotSet: 0,
Email: 1,
SMS: 2,
Phone: 4,
Fax: 8,
Postal: 16,
} as const;
export const NotificationChannelSchema = z.nativeEnum(NotificationChannel).describe('Notification channel');
export type NotificationChannel = z.infer<typeof NotificationChannelSchema>;

View File

@@ -1,8 +1,8 @@
import { z } from 'zod';
export const OrganisationNamesSchema = z.object({
department: z.string().optional(),
legalForm: z.string().optional(),
name: z.string().optional(),
nameSuffix: z.string().optional(),
department: z.string().describe('Department').optional(),
legalForm: z.string().describe('Legal form').optional(),
name: z.string().describe('Name').optional(),
nameSuffix: z.string().describe('Name suffix').optional(),
});

View File

@@ -2,8 +2,8 @@ import z from 'zod';
import { OrganisationNamesSchema } from './organisation-names.schema';
export const OrganisationSchema = OrganisationNamesSchema.extend({
costUnit: z.string().optional(),
gln: z.string().optional(),
sector: z.string().optional(),
vatId: z.string().optional(),
costUnit: z.string().describe('Cost unit').optional(),
gln: z.string().describe('Gln').optional(),
sector: z.string().describe('Sector').optional(),
vatId: z.string().describe('Vat identifier').optional(),
});

View File

@@ -0,0 +1,25 @@
import z from 'zod';
export const PaymentType = {
NotSet: 0,
WhenCollecting: 1,
Free: 2,
Cash: 4,
DirectDebit: 8,
DebitAdviceMandate: 16,
DebitCard: 32,
CreditCard: 64,
Invoice: 128,
PrePayment: 256,
Voucher: 512,
CollectiveInvoice: 1024,
PayPal: 2048,
InstantTransfer: 4096,
PayOnDelivery: 8192,
BonusCard: 16384,
AmazonPay: 32768,
} as const;
export const PaymentTypeSchema = z.nativeEnum(PaymentType).describe('Payment type');
export type PaymentType = z.infer<typeof PaymentTypeSchema>;

View File

@@ -1,7 +1,7 @@
import z from 'zod';
import { z } from 'zod';
export const PriceValueSchema = z.object({
value: z.number().optional(),
currency: z.string().optional(),
currencySymbol: z.string().optional(),
value: z.number().describe('Value').optional(),
currency: z.string().describe('Currency code').optional(),
currencySymbol: z.string().describe('Currency symbol').optional(),
});

View File

@@ -1,4 +1,4 @@
import z from 'zod';
import { z } from 'zod';
import { PriceValueSchema } from './price-value.schema';
import { VatValueSchema } from './vat-value.schema';
@@ -55,6 +55,6 @@ import { VatValueSchema } from './vat-value.schema';
// Effort: ~4 hours | Impact: High | Risk: Low
// See: complexity-analysis.md (TypeScript Section, Issue 5)
export const PriceSchema = z.object({
value: PriceValueSchema.optional(),
vat: VatValueSchema.optional(),
value: PriceValueSchema.describe('Value').optional(),
vat: VatValueSchema.describe('Value Added Tax').optional(),
});

View File

@@ -0,0 +1,18 @@
import { z } from 'zod';
export const QuantityUnitType = {
NotSet: 0,
Pieces: 1,
Weight: 2,
Length: 4,
Area: 8,
Volume: 16,
DigitalSize: 32,
Time: 64,
Resolution: 128,
Money: 256,
} as const;
export const QuantityUnitTypeSchema = z.nativeEnum(QuantityUnitType).describe('QuantityUnit type');
export type QuantityUnitType = z.infer<typeof QuantityUnitTypeSchema>;

View File

@@ -0,0 +1,3 @@
import { z } from 'zod';
export const TouchBaseSchema = z.object({});

View File

@@ -1,4 +1,4 @@
import z from 'zod';
import { VatType } from '../models';
export const VatTypeSchema = z.nativeEnum(VatType).optional();
export const VatTypeSchema = z.nativeEnum(VatType).optional().describe('Vat type');

View File

@@ -2,8 +2,8 @@ import z from 'zod';
import { VatTypeSchema } from './vat-type.schema';
export const VatValueSchema = z.object({
value: z.number().optional(),
label: z.string().optional(),
inPercent: z.number().optional(),
vatType: VatTypeSchema.optional(),
value: z.number().describe('Value').optional(),
label: z.string().describe('Label').optional(),
inPercent: z.number().describe('In percent').optional(),
vatType: VatTypeSchema.describe('VAT type').optional(),
});