Merged PR 1989: fix(checkout): resolve currency constraint violations in price handling

fix(checkout): resolve currency constraint violations in price handling

- Add ensureCurrencyDefaults() helper to normalize price objects with EUR defaults
- Fix currency constraint violation in shopping cart item additions (bug #5405)
- Apply price normalization across availability, checkout, and shopping cart services
- Update 8 locations: availability.adapter, checkout.service, shopping-cart.service,
  get-availability-params.adapter, availability-transformers, reward quantity control
- Refactor OrderType to @isa/common/data-access for cross-domain reusability
- Remove duplicate availability service from catalogue library
- Enhance PriceValue and VatValue schemas with proper currency defaults
- Add availability-transformers.spec.ts test coverage
- Fix QuantityControl fallback from 0 to 1 to prevent invalid state warnings

Resolves issue where POST requests to /checkout/v6/store/shoppingcart/{id}/item
were sending price objects without required currency/currencySymbol fields,
causing 400 Bad Request with 'Currency: Constraint violation: NotNull' error.

Related work items: #5405
This commit is contained in:
Lorenz Hilpert
2025-10-28 10:34:39 +00:00
committed by Nino Righi
parent 2d654aa63a
commit bfd151dd84
24 changed files with 1185 additions and 1170 deletions

View File

@@ -1,3 +1,4 @@
export * from './create-esc-abort-controller.helper';
export * from './is-response-args.helper';
export * from './price-helpers';
export * from './zod-error.helper';

View File

@@ -0,0 +1,37 @@
import { Price } from '../models';
/**
* Ensures that a price object has currency and currencySymbol set.
* If the price has a value but currency or currencySymbol are missing,
* defaults to 'EUR' and '€' respectively.
*
* This is necessary because the backend API requires these fields to be non-null
* when a price value is present, but various adapters and API responses may
* omit these fields.
*
* @param price - The price object to normalize
* @returns The price object with currency defaults applied, or undefined if input was undefined
*
* @example
* const price = {
* value: { value: 10.99 } // Missing currency and currencySymbol
* };
* const normalized = ensureCurrencyDefaults(price);
* // Result: { value: { value: 10.99, currency: 'EUR', currencySymbol: '€' } }
*/
export function ensureCurrencyDefaults(
price: Price | undefined,
): Price | undefined {
if (!price?.value) {
return price;
}
return {
...price,
value: {
...price.value,
currency: price.value.currency ?? 'EUR',
currencySymbol: price.value.currencySymbol ?? '€',
},
};
}

View File

@@ -5,6 +5,7 @@ export * from './entity-cotnainer';
export * from './entity-status';
export * from './gender';
export * from './list-response-args';
export * from './order-type';
export * from './payer-type';
export * from './price-value';
export * from './price';

View File

@@ -0,0 +1,10 @@
export const OrderType = {
InStore: 'Rücklage',
Pickup: 'Abholung',
Delivery: 'Versand',
DigitalShipping: 'DIG-Versand',
B2BShipping: 'B2B-Versand',
Download: 'Download',
} as const;
export type OrderType = (typeof OrderType)[keyof typeof OrderType];

View File

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

View File

@@ -4,6 +4,6 @@ import { VatTypeSchema } from './vat-type.schema';
export const VatValueSchema = z.object({
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(),
inPercent: z.number().describe('In percent').default(0).optional(),
vatType: VatTypeSchema.describe('VAT type').default(0).optional(),
});