@open-mercato/core 0.10.1-develop.7982.1.df20d84e85 → 0.10.1-develop.7990.1.82d0a183c5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/commands/users.js +27 -23
  3. package/dist/modules/auth/commands/users.js.map +2 -2
  4. package/dist/modules/customers/commands/companies.js +10 -1
  5. package/dist/modules/customers/commands/companies.js.map +2 -2
  6. package/dist/modules/customers/docs/issue-8675-ownership-assessment.md +30 -0
  7. package/dist/modules/promotions/backend/promotions/[id]/page.js +3 -2
  8. package/dist/modules/promotions/backend/promotions/[id]/page.js.map +2 -2
  9. package/dist/modules/promotions/backend/promotions/page.js +3 -7
  10. package/dist/modules/promotions/backend/promotions/page.js.map +2 -2
  11. package/dist/modules/promotions/components/promotionDates.js +19 -0
  12. package/dist/modules/promotions/components/promotionDates.js.map +7 -0
  13. package/dist/modules/promotions/data/validators.js +8 -1
  14. package/dist/modules/promotions/data/validators.js.map +2 -2
  15. package/dist/modules/wms/components/backend/CompletePutawayTaskDialog.js +2 -2
  16. package/dist/modules/wms/components/backend/CompletePutawayTaskDialog.js.map +2 -2
  17. package/dist/modules/wms/components/backend/CreateAsnDialog.js +5 -5
  18. package/dist/modules/wms/components/backend/CreateAsnDialog.js.map +2 -2
  19. package/dist/modules/wms/components/backend/ReceiveAsnLineDialog.js +1 -1
  20. package/dist/modules/wms/components/backend/ReceiveAsnLineDialog.js.map +2 -2
  21. package/package.json +7 -7
  22. package/src/modules/auth/commands/users.ts +28 -25
  23. package/src/modules/customers/commands/companies.ts +11 -1
  24. package/src/modules/customers/docs/issue-8675-ownership-assessment.md +30 -0
  25. package/src/modules/promotions/backend/promotions/[id]/page.tsx +3 -2
  26. package/src/modules/promotions/backend/promotions/page.tsx +3 -8
  27. package/src/modules/promotions/components/promotionDates.ts +19 -0
  28. package/src/modules/promotions/data/validators.ts +9 -1
  29. package/src/modules/promotions/i18n/de.json +6 -6
  30. package/src/modules/promotions/i18n/es.json +6 -6
  31. package/src/modules/promotions/i18n/ko.json +6 -6
  32. package/src/modules/promotions/i18n/pl.json +6 -6
  33. package/src/modules/wms/components/backend/CompletePutawayTaskDialog.tsx +2 -2
  34. package/src/modules/wms/components/backend/CreateAsnDialog.tsx +5 -5
  35. package/src/modules/wms/components/backend/ReceiveAsnLineDialog.tsx +1 -1
  36. package/src/modules/wms/i18n/de.json +173 -165
  37. package/src/modules/wms/i18n/en.json +8 -0
  38. package/src/modules/wms/i18n/es.json +175 -167
  39. package/src/modules/wms/i18n/ko.json +175 -167
  40. package/src/modules/wms/i18n/pl.json +172 -164
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/promotions/data/validators.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\n\nconst uuid = () => z.string().uuid()\n\nconst emptyStringToNull = (value: unknown): unknown => {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length ? trimmed : null\n}\n\n// Mirrors `clearableStringSchema` in `customer_groups/data/validators.ts`: blanking a\n// previously-set value on edit must transmit null to clear it, not be silently dropped.\nconst clearableStringSchema = (max: number) =>\n z.preprocess(emptyStringToNull, z.string().trim().max(max).nullable().optional())\n\n// QA F9 (Low/UX, confirmed live): a static promotion code was stored and looked up with\n// exact, case-sensitive matching \u2014 `SUMMER10` created by an admin answered 409 to a buyer\n// typing `summer10`. Fixed at the write boundary (here) and the lookup boundary\n// (`lib/code-service.ts#resolveCodeByString`): every `code`/`prefix`/`suffix` is\n// upper-cased on write, so the two sides of the comparison always share one casing.\n// Charset is restricted to `A-Z0-9_-` \u2014 alphanumeric plus the two separator characters\n// every real promo-code convention already uses (`SUMMER-2026`, `SAVE_20`) \u2014 no spaces, no\n// other symbols \u2014 since these three fields exist for a human to type or transcribe. This\n// is deliberately WIDER than the code-generation worker's own `CODE_CHARSET` (`workers/\n// code-generation.worker.ts`), which additionally excludes `0`/`O`/`1`/`I`/`L` (visual\n// confusion in a MACHINE-GENERATED body, a different, narrower concern) and never\n// generates `-`/`_` itself \u2014 an admin-supplied `prefix`/`suffix` is where a separator\n// would actually appear.\nconst CODE_CHARSET_PATTERN = /^[A-Z0-9_-]+$/\n\nconst codeCharsetStringSchema = (max: number) =>\n z\n .string()\n .trim()\n .max(max)\n .transform((value) => value.toUpperCase())\n .refine((value) => CODE_CHARSET_PATTERN.test(value), {\n message: 'Must contain only letters A-Z, digits 0-9, hyphens or underscores (no spaces or other symbols)',\n })\n\n// Same `preprocess(emptyStringToNull, ...)` shape as `clearableStringSchema` \u2014 null/empty\n// stays valid and skips the charset check entirely; only a non-empty value is upper-cased\n// and charset-checked. Mirrors `clearableCurrencyCodeSchema`'s own\n// `currencyCodeSchema.nullable().optional()` composition below.\nconst clearableUppercaseStringSchema = (max: number) =>\n z.preprocess(emptyStringToNull, codeCharsetStringSchema(max).nullable().optional())\n\nconst clearableDateSchema = z.preprocess(emptyStringToNull, z.coerce.date().nullable().optional())\n\n// QA F7 (Medium, confirmed live): the `endsAt` datepicker sends a bare date (no time\n// component), which `z.coerce.date()` parses as 00:00:00.000 of that day \u2014 so a\n// promotion whose admin set \"ends Oct 16\" (meaning \"valid through end of day Oct 16\")\n// was excluded for the entire day of Oct 16 by `evaluation-engine.ts`'s\n// `isPromotionEligible` (`evaluatedAtMs > endsAtMs`), which is itself correct for a\n// timestamp that already represents the end of the valid window. The fix belongs on\n// write, not in that comparison: a date-only `endsAt` is bumped to 23:59:59.999 of that\n// same calendar day before coercion, so the stored value means \"through the end of that\n// day\". A value that already carries a time component (a full ISO timestamp from any\n// API caller) passes through unchanged \u2014 exact pre-existing semantics are preserved for\n// anyone already sending a real timestamp.\n//\n// Timezone anchor: UTC. No tenant/store timezone setting exists anywhere in this\n// codebase (checked `configs`, `catalog`, and the module registry) to anchor this to\n// instead, so \"end of day\" is computed in UTC, matching every other date field in this\n// module (`clearableDateSchema` itself coerces a bare date string to UTC midnight via\n// `z.coerce.date()` \u2014 this schema keeps the same anchor for its bump).\nconst DATE_ONLY_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/\n\nconst bumpDateOnlyToEndOfDay = (value: unknown): unknown => {\n if (typeof value !== 'string' || !DATE_ONLY_PATTERN.test(value)) return value\n return `${value}T23:59:59.999Z`\n}\n\nexport const clearableEndOfDayDateSchema = z.preprocess(\n (value) => bumpDateOnlyToEndOfDay(emptyStringToNull(value)),\n z.coerce.date().nullable().optional(),\n)\n\nconst clearableUuidSchema = z.preprocess(emptyStringToNull, uuid().nullable().optional())\n\n// Decimal amounts travel as strings end-to-end (\u00A7Data Models: \"value decimal string\") so\n// a caller never loses precision to IEEE-754 float rounding before this module's own\n// half-up integer-minor-units rounding (Phase 2, D-R4-10) ever runs. Every field using\n// this schema \u2014 rule thresholds, benefit discount values, cart/line amounts \u2014 is a\n// non-negative magnitude; a resolved effect's own sign (a discount subtracts) is applied\n// downstream in `lib/effect-resolvers.ts`, never carried in the input itself.\nconst decimalStringSchema = z.string().regex(/^\\d+(\\.\\d+)?$/, 'Must be a non-negative decimal string')\n\nconst clearableDecimalStringSchema = z.preprocess(emptyStringToNull, decimalStringSchema.nullable().optional())\n\n// A percentage discount value is additionally capped at 100 \u2014 this module's shared\n// `decimalStringSchema` only rules out negative values (a `-10` turning a discount into a\n// surcharge); a `150`-percent discount would otherwise still discount more than a line is\n// worth. Fixed-amount benefit values are deliberately NOT capped here \u2014 they are clamped\n// to their base at resolution time in `lib/effect-resolvers.ts`, since that cap depends on\n// the line/order total the discount applies to, not a fixed ceiling.\nfunction rejectPercentageOver100(\n config: { discountType: 'percentage' | 'fixed'; value: string },\n ctx: z.RefinementCtx,\n) {\n if (config.discountType === 'percentage' && Number(config.value) > 100) {\n ctx.addIssue({\n code: 'custom',\n message: 'A percentage discount value cannot exceed 100',\n path: ['value'],\n })\n }\n}\n\nconst currencyCodeSchema = z\n .string()\n .trim()\n .length(3, 'Currency code must be an ISO 4217 3-letter code')\n .transform((value) => value.toUpperCase())\n\nconst clearableCurrencyCodeSchema = z.preprocess(emptyStringToNull, currencyCodeSchema.nullable().optional())\n\n// No operator enum is defined anywhere in SPEC-055 for `order_value`/`order_date`/\n// `product_count`/`cart_weight`/`row_total`/`product`/`category`/`customer_order_history`\n// rule configs or the hidden `producer`/`shipping_address` configs. This six-value\n// comparison set is this module's own resolved judgment call (flagged in the Step 1.2\n// report) \u2014 Phase 2's `lib/rule-evaluators.ts` is the first consumer and should confirm\n// or correct it before it ships to the admin UI.\nexport const comparisonOperatorValues = ['eq', 'neq', 'gt', 'gte', 'lt', 'lte'] as const\nexport const comparisonOperatorSchema = z.enum(comparisonOperatorValues)\n\nconst nonEmptyLabelsSchema = z\n .record(z.string(), z.string().trim().min(1))\n .refine((value) => Object.keys(value).length > 0, 'At least one label is required')\n\nconst isoDateStringSchema = z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'Must be an ISO date (YYYY-MM-DD)')\n\n// ---------------------------------------------------------------------------\n// Rule configs \u2014 Release 4 rule set (\u00A7Data Models, rev P10/D-R4-14)\n// ---------------------------------------------------------------------------\n\nconst orderValueConfigSchema = z\n .object({\n value: decimalStringSchema,\n operator: comparisonOperatorSchema,\n taxInclusive: z.boolean(),\n limitToCategory: clearableStringSchema(200),\n })\n .strict()\n\nconst orderDateConfigSchema = z\n .object({\n date: isoDateStringSchema,\n operator: comparisonOperatorSchema,\n })\n .strict()\n\nconst productCountConfigSchema = z\n .object({\n value: z.number().int(),\n operator: comparisonOperatorSchema,\n })\n .strict()\n\nconst cartWeightConfigSchema = z\n .object({\n value: decimalStringSchema,\n operator: comparisonOperatorSchema,\n })\n .strict()\n\n// `sku`/`category_slug` replaced by id fields (rev \u2014 items are never addressed by SKU\n// in Release 4, \u00A7API Contracts).\nconst rowTotalConfigSchema = z\n .object({\n value: decimalStringSchema,\n operator: comparisonOperatorSchema,\n productId: clearableUuidSchema,\n categoryId: clearableUuidSchema,\n })\n .strict()\n\nconst productConfigSchema = z\n .object({\n productId: uuid(),\n variantId: clearableUuidSchema,\n quantity: z.number().int(),\n operator: comparisonOperatorSchema,\n })\n .strict()\n\n// Includes descendants \u2014 ancestor-expanded match (D-R4-15); the expansion itself is a\n// Phase 2 evaluator concern, not a validator concern.\nconst categoryConfigSchema = z\n .object({\n categoryId: uuid(),\n quantity: z.number().int(),\n operator: comparisonOperatorSchema,\n })\n .strict()\n\nconst productAttributeConfigSchema = z\n .object({\n attributeCode: z.string().trim().min(1).max(100),\n operator: comparisonOperatorSchema,\n value: z.string().trim().min(1).max(500),\n })\n .strict()\n\n// Replaces `user_group` outright (rev \u2014 not additive; nothing has ever shipped against\n// `user_group`, \u00A7Data Models).\nconst customerGroupConfigSchema = z\n .object({\n customerGroupId: uuid(),\n })\n .strict()\n\nconst customerOrderHistoryConfigSchema = z\n .object({\n value: z.number().int(),\n operator: comparisonOperatorSchema,\n })\n .strict()\n\n// `codeId` is validated against `promotion_codes` in commands before persisting\n// (\u00A7Data Models) \u2014 this schema only checks shape.\nconst codeRuleConfigSchema = z\n .object({\n codeId: uuid(),\n })\n .strict()\n\n// --- Hidden rule types (\u00A7Data Models) \u2014 modelled so the discriminated union never needs\n// a second migration later, but excluded from `ruleInputSchema` (the Release 4 write-path\n// schema) below. No admin UI, no Phase 1-3 evaluator registration.\n\nconst producerConfigSchema = z\n .object({\n producerCode: z.string().trim().min(1).max(100),\n quantity: z.number().int(),\n operator: comparisonOperatorSchema,\n })\n .strict()\n\nconst excludePharmaceuticalConfigSchema = z.object({}).strict()\n\nconst consentFlagConfigSchema = z\n .object({\n flagKey: z.string().trim().min(1).max(100),\n })\n .strict()\n\nconst deliveryMethodRuleConfigSchema = z\n .object({\n deliveryMethodCode: z.string().trim().min(1).max(100),\n })\n .strict()\n\nconst paymentMethodConfigSchema = z\n .object({\n paymentMethodCode: z.string().trim().min(1).max(100),\n })\n .strict()\n\nconst shippingAddressConfigSchema = z\n .object({\n field: z.string().trim().min(1).max(100),\n operator: comparisonOperatorSchema,\n value: z.string().trim().min(1).max(500),\n })\n .strict()\n\nconst ruleEnvelope = <Type extends string, Config extends z.ZodTypeAny>(\n ruleType: Type,\n config: Config,\n) =>\n z.object({\n id: uuid().optional(),\n sortOrder: z.number().int().min(0).optional().default(0),\n ruleType: z.literal(ruleType),\n config,\n })\n\nconst shippedRuleVariants = [\n ruleEnvelope('order_value', orderValueConfigSchema),\n ruleEnvelope('order_date', orderDateConfigSchema),\n ruleEnvelope('product_count', productCountConfigSchema),\n ruleEnvelope('row_total', rowTotalConfigSchema),\n ruleEnvelope('product', productConfigSchema),\n ruleEnvelope('category', categoryConfigSchema),\n ruleEnvelope('customer_group', customerGroupConfigSchema),\n ruleEnvelope('code', codeRuleConfigSchema),\n] as const\n\nconst hiddenRuleVariants = [\n ruleEnvelope('customer_order_history', customerOrderHistoryConfigSchema),\n ruleEnvelope('product_attribute', productAttributeConfigSchema),\n ruleEnvelope('cart_weight', cartWeightConfigSchema),\n ruleEnvelope('producer', producerConfigSchema),\n ruleEnvelope('exclude_pharmaceutical', excludePharmaceuticalConfigSchema),\n ruleEnvelope('consent_flag', consentFlagConfigSchema),\n ruleEnvelope('delivery_method', deliveryMethodRuleConfigSchema),\n ruleEnvelope('payment_method', paymentMethodConfigSchema),\n ruleEnvelope('shipping_address', shippingAddressConfigSchema),\n] as const\n\nexport const RELEASE_4_SHIPPED_RULE_TYPES = [\n 'order_value',\n 'order_date',\n 'product_count',\n 'row_total',\n 'product',\n 'category',\n 'customer_group',\n 'code',\n] as const\n\nexport const PROMOTIONS_HIDDEN_RULE_TYPES = [\n 'cart_weight',\n 'product_attribute',\n 'customer_order_history',\n 'producer',\n 'exclude_pharmaceutical',\n 'consent_flag',\n 'delivery_method',\n 'payment_method',\n 'shipping_address',\n] as const\n\n// Write-path schema (\u00A7Implementation Plan Phase 1, step 3): only the eight Release 4\n// rule types are accepted here. A request naming a hidden type fails to match any\n// variant's `ruleType` literal and the discriminated union rejects it \u2014 the rejection\n// a hidden-rule-type write test (`TC-PROM-0xx`) exercises, not merely hiding the type\n// from the admin UI combobox.\nexport const ruleInputSchema = z.discriminatedUnion('ruleType', [...shippedRuleVariants])\n\n// Full model (all seventeen types, shipped + hidden) \u2014 kept so the discriminated union\n// does not need a second migration later when a data source appears for a hidden type\n// (\u00A7Data Models rev P10). Used by extension/internal tooling only, never the write path.\nexport const ruleInputSchemaFull = z.discriminatedUnion('ruleType', [\n ...shippedRuleVariants,\n ...hiddenRuleVariants,\n])\n\nexport type RuleInput = z.infer<typeof ruleInputSchema>\n\n// ---------------------------------------------------------------------------\n// Benefit configs \u2014 Application target (`applies_to`) and shared quantity-cap block\n// (\u00A7Data Models)\n// ---------------------------------------------------------------------------\n\nexport const promotionBenefitAppliesToValues = ['order', 'line', 'unit'] as const\nexport const promotionBenefitAppliesToSchema = z.enum(promotionBenefitAppliesToValues)\n\nexport const quantityCapSelectorValues = ['all', 'cart_order', 'cheapest', 'most_expensive', 'nth'] as const\nexport const quantityCapSelectorSchema = z.enum(quantityCapSelectorValues)\n\n// Shared block intersected into every benefit variant (\u00A7Data Models \"Quantity caps\").\nconst quantityCapShape = {\n maxQuantity: z.number().int().positive().nullable().optional(),\n maxLines: z.number().int().positive().nullable().optional(),\n quantityStep: z.number().int().positive().nullable().optional(),\n selector: quantityCapSelectorSchema.nullable().optional(),\n nthPosition: z.number().int().positive().nullable().optional(),\n}\n\ntype QuantityCapConfig = {\n maxQuantity?: number | null\n maxLines?: number | null\n quantityStep?: number | null\n selector?: (typeof quantityCapSelectorValues)[number] | null\n nthPosition?: number | null\n}\n\n// \u00A7Data Models \"Write-path rejections (422), normative\" \u2014 implemented as real\n// rejections (not documentation): a capped benefit on `applies_to: 'order'`, a cap with\n// no `selector`, `selector: 'all'` plus a cap, `selector: 'nth'` plus a cap,\n// `selector`/`nthPosition` split apart, and `quantityStep` under `applies_to: 'line'`.\nfunction checkQuantityCapRules(\n config: QuantityCapConfig,\n appliesTo: (typeof promotionBenefitAppliesToValues)[number],\n ctx: z.RefinementCtx,\n) {\n const hasCap = config.maxQuantity != null || config.maxLines != null\n\n if (appliesTo === 'order') {\n if (config.maxQuantity != null || config.maxLines != null || config.quantityStep != null) {\n ctx.addIssue({\n code: 'custom',\n message: 'Quantity caps are not meaningful when appliesTo is \"order\" \u2014 nothing to count',\n path: ['config', 'maxQuantity'],\n })\n }\n // Nothing else to check: selector/nthPosition rules below only matter when a cap\n // can exist, which is already rejected above for `order`.\n return\n }\n\n if (hasCap && !config.selector) {\n ctx.addIssue({\n code: 'custom',\n message: 'selector is required when maxQuantity or maxLines is set',\n path: ['config', 'selector'],\n })\n }\n\n if (config.selector === 'all' && hasCap) {\n ctx.addIssue({\n code: 'custom',\n message: '\"all\" selector cannot be combined with a quantity cap \u2014 \"all\" means every qualifying unit',\n path: ['config', 'selector'],\n })\n }\n\n if (config.selector === 'nth' && hasCap) {\n ctx.addIssue({\n code: 'custom',\n message: '\"nth\" selector is its own cap and may not be combined with maxQuantity or maxLines',\n path: ['config', 'selector'],\n })\n }\n\n if (config.selector === 'nth' && config.nthPosition == null) {\n ctx.addIssue({\n code: 'custom',\n message: 'nthPosition is required when selector is \"nth\"',\n path: ['config', 'nthPosition'],\n })\n }\n\n if (config.selector !== 'nth' && config.nthPosition != null) {\n ctx.addIssue({\n code: 'custom',\n message: 'nthPosition is only meaningful when selector is \"nth\"',\n path: ['config', 'nthPosition'],\n })\n }\n\n // Blocks are counted in units only; `applies_to: 'line'` spends whole lines, where \"a\n // block of units\" has no meaning (\u00A7Data Models).\n if (config.quantityStep != null && appliesTo === 'line') {\n ctx.addIssue({\n code: 'custom',\n message: 'quantityStep is not meaningful when appliesTo is \"line\" \u2014 lines are spent whole',\n path: ['config', 'quantityStep'],\n })\n }\n}\n\nconst discountTypeValues = ['percentage', 'fixed'] as const\nconst discountTypeSchema = z.enum(discountTypeValues)\n\nconst tierElementSchema = z\n .object({\n threshold: decimalStringSchema,\n discountType: discountTypeSchema,\n value: decimalStringSchema,\n })\n .strict()\n .superRefine(rejectPercentageOver100)\n\n// \u00A7Data Models: \"Tiers must be sorted ascending by threshold.\"\nconst tiersSchema = z\n .array(tierElementSchema)\n .min(1)\n .superRefine((tiers, ctx) => {\n for (let i = 1; i < tiers.length; i += 1) {\n if (Number(tiers[i - 1].threshold) > Number(tiers[i].threshold)) {\n ctx.addIssue({\n code: 'custom',\n message: 'tiers must be sorted ascending by threshold',\n path: [i, 'threshold'],\n })\n }\n }\n })\n\nconst productDiscountConfigSchema = z\n .object({\n productId: clearableUuidSchema,\n discountType: discountTypeSchema,\n value: decimalStringSchema,\n limitToCategoryId: clearableUuidSchema,\n maxDiscount: clearableDecimalStringSchema,\n labels: nonEmptyLabelsSchema,\n ...quantityCapShape,\n })\n .strict()\n .superRefine(rejectPercentageOver100)\n\nconst cartDiscountConfigSchema = z\n .object({\n discountType: discountTypeSchema,\n value: decimalStringSchema,\n maxDiscount: clearableDecimalStringSchema,\n labels: nonEmptyLabelsSchema,\n ...quantityCapShape,\n })\n .strict()\n .superRefine(rejectPercentageOver100)\n\n// Hidden from the admin tree builder in Release 4 (D-R4-14); the type and its config\n// schema stay in the model per \u00A7Data Models.\nconst deliveryDiscountConfigSchema = z\n .object({\n deliveryMethodCode: z.string().trim().min(1).max(100),\n discountType: discountTypeSchema,\n value: decimalStringSchema,\n labels: nonEmptyLabelsSchema,\n ...quantityCapShape,\n })\n .strict()\n .superRefine(rejectPercentageOver100)\n\nconst freeProductConfigSchema = z\n .object({\n productId: clearableUuidSchema,\n categoryId: clearableUuidSchema,\n quantity: z.number().int().positive(),\n labels: nonEmptyLabelsSchema,\n ...quantityCapShape,\n })\n .strict()\n\nconst buyXGetYConfigSchema = z\n .object({\n triggerProductId: clearableUuidSchema,\n triggerCategoryId: clearableUuidSchema,\n triggerQuantity: z.number().int().positive(),\n rewardProductId: uuid(),\n rewardVariantId: clearableUuidSchema,\n rewardQuantity: z.number().int().positive(),\n discountType: discountTypeSchema,\n value: decimalStringSchema,\n maxApplications: z.number().int().positive().nullable().optional(),\n maxDiscount: clearableDecimalStringSchema,\n labels: nonEmptyLabelsSchema,\n ...quantityCapShape,\n })\n .strict()\n .superRefine(rejectPercentageOver100)\n\nconst tieredDiscountConfigSchema = z\n .object({\n limitToCategoryId: clearableUuidSchema,\n tiers: tiersSchema,\n maxDiscount: clearableDecimalStringSchema,\n labels: nonEmptyLabelsSchema,\n ...quantityCapShape,\n })\n .strict()\n\n// \u00A7Data Models \"Allowed values per applies_to per benefit_type\" table \u2014 enforced by\n// restricting each variant's `appliesTo` literal set (not merely documented) and\n// defaulting to the type's documented default when the operator omits it.\nconst benefitEnvelope = <Type extends string, Config extends z.ZodTypeAny, Allowed extends string>(\n benefitType: Type,\n config: Config,\n allowedAppliesTo: readonly [Allowed, ...Allowed[]],\n defaultAppliesTo: Allowed,\n) =>\n z.object({\n id: uuid().optional(),\n sortOrder: z.number().int().min(0).optional().default(0),\n benefitType: z.literal(benefitType),\n appliesTo: z.enum(allowedAppliesTo).optional().default(defaultAppliesTo),\n config,\n })\n\nconst shippedBenefitVariants = [\n benefitEnvelope('product_discount', productDiscountConfigSchema, ['line', 'unit'] as const, 'line'),\n benefitEnvelope('cart_discount', cartDiscountConfigSchema, ['order'] as const, 'order'),\n benefitEnvelope('free_product', freeProductConfigSchema, ['order'] as const, 'order'),\n benefitEnvelope('buy_x_get_y', buyXGetYConfigSchema, ['line', 'unit'] as const, 'unit'),\n benefitEnvelope('tiered_discount', tieredDiscountConfigSchema, ['order', 'line', 'unit'] as const, 'order'),\n] as const\n\n// Hidden from the admin tree builder in Release 4 (D-R4-14) \u2014 same rejection mechanism\n// as the hidden rule types: excluded from `benefitInputSchema`, kept in\n// `benefitInputSchemaFull` for the model.\nconst hiddenBenefitVariants = [\n benefitEnvelope('delivery_discount', deliveryDiscountConfigSchema, ['order'] as const, 'order'),\n] as const\n\nexport const RELEASE_4_SHIPPED_BENEFIT_TYPES = [\n 'product_discount',\n 'cart_discount',\n 'free_product',\n 'buy_x_get_y',\n 'tiered_discount',\n] as const\n\nexport const PROMOTIONS_HIDDEN_BENEFIT_TYPES = ['delivery_discount'] as const\n\nconst withQuantityCapGuard = <T extends z.ZodTypeAny>(schema: T) =>\n schema.superRefine((value: any, ctx) => {\n checkQuantityCapRules(value.config, value.appliesTo, ctx)\n })\n\nexport const benefitInputSchema = withQuantityCapGuard(\n z.discriminatedUnion('benefitType', [...shippedBenefitVariants]),\n)\n\nexport const benefitInputSchemaFull = withQuantityCapGuard(\n z.discriminatedUnion('benefitType', [...shippedBenefitVariants, ...hiddenBenefitVariants]),\n)\n\nexport type BenefitInput = z.infer<typeof benefitInputSchema>\n\n// ---------------------------------------------------------------------------\n// D-R4-27: single `eligible_currency` requirement\n// ---------------------------------------------------------------------------\n//\n// \"Benefits with fixed amounts or tiers, and order_value/row_total thresholds, require a\n// single eligible_currency (write-path validation).\" This is a cross-entity rule \u2014 it\n// reads `Promotion.eligibleCurrencies`, which is not visible from an individual Rule or\n// Benefit config \u2014 so it cannot live inside `ruleInputSchema`/`benefitInputSchema`\n// themselves. It is exposed here as a composite schema + assertion helper for the\n// commands that own the full tree + promotion context (`commands/promotions.ts`,\n// `commands/rule-tree.ts`, Step 1.3+) to call before persisting. Flagged in the Step 1.2\n// report as the resolved design choice for this cross-entity check.\n\nexport const CURRENCY_SENSITIVE_RULE_TYPES = ['order_value', 'row_total'] as const\n\nexport function benefitRequiresSingleEligibleCurrency(\n benefitType: string,\n config: { discountType?: string },\n): boolean {\n if (benefitType === 'tiered_discount') return true\n if (\n (benefitType === 'product_discount' || benefitType === 'cart_discount' || benefitType === 'buy_x_get_y') &&\n config.discountType === 'fixed'\n ) {\n return true\n }\n return false\n}\n\nexport const promotionCurrencyGuardSchema = z\n .object({\n eligibleCurrencies: z.array(z.string()),\n rules: z.array(z.object({ ruleType: z.string() })),\n benefits: z.array(\n z.object({ benefitType: z.string(), config: z.object({ discountType: z.string().optional() }).passthrough() }),\n ),\n })\n .superRefine((value, ctx) => {\n const requiresSingleCurrency =\n value.rules.some((rule) => (CURRENCY_SENSITIVE_RULE_TYPES as readonly string[]).includes(rule.ruleType)) ||\n value.benefits.some((benefit) => benefitRequiresSingleEligibleCurrency(benefit.benefitType, benefit.config))\n\n if (requiresSingleCurrency && value.eligibleCurrencies.length !== 1) {\n ctx.addIssue({\n code: 'custom',\n message:\n 'A promotion containing a fixed-amount benefit, a tiered_discount benefit, or an order_value/row_total rule must set exactly one eligible currency (D-R4-27)',\n path: ['eligibleCurrencies'],\n })\n }\n })\n\nexport type PromotionCurrencyGuardInput = z.infer<typeof promotionCurrencyGuardSchema>\n\nexport function assertPromotionCurrencyRequirements(input: PromotionCurrencyGuardInput): void {\n promotionCurrencyGuardSchema.parse(input)\n}\n\n// ---------------------------------------------------------------------------\n// RuleGroup tree node (used by `commands/rule-tree.ts`, Step 1.3+)\n// ---------------------------------------------------------------------------\n\nexport const ruleGroupOperatorValues = ['and', 'or'] as const\nexport const ruleGroupOperatorSchema = z.enum(ruleGroupOperatorValues)\n\nexport type RuleGroupTreeNodeInput = {\n id?: string\n operator: (typeof ruleGroupOperatorValues)[number]\n sortOrder: number\n rules: RuleInput[]\n benefits: BenefitInput[]\n children: RuleGroupTreeNodeInput[]\n}\n\nexport const ruleGroupTreeNodeSchema: z.ZodType<RuleGroupTreeNodeInput> = z.lazy(() =>\n z.object({\n id: uuid().optional(),\n operator: ruleGroupOperatorSchema,\n sortOrder: z.number().int().min(0).optional().default(0),\n rules: z.array(ruleInputSchema).optional().default([]),\n benefits: z.array(benefitInputSchema).optional().default([]),\n children: z.array(ruleGroupTreeNodeSchema).optional().default([]),\n }),\n)\n\n// `PUT /api/promotions/[id]/tree` body (\u00A7API Contracts): the full tree plus the\n// promotion's `updatedAt` for the optimistic-lock check on this separate tree-replace\n// surface (distinct from the row-level header `CrudForm` sends for plain metadata PUTs).\nexport const promotionTreeUpdateSchema = z.object({\n updatedAt: z.coerce.date(),\n root: ruleGroupTreeNodeSchema,\n})\n\nexport type PromotionTreeUpdateInput = z.infer<typeof promotionTreeUpdateSchema>\n\n// `PATCH /api/promotions/order` body: each item carries its own `updatedAt`; a stale\n// value 409s that item only, not the whole batch (\u00A7API Contracts).\nexport const promotionOrderBatchSchema = z.object({\n items: z\n .array(\n z.object({\n id: uuid(),\n order: z.number().int(),\n updatedAt: z.coerce.date(),\n }),\n )\n .min(1),\n})\n\nexport type PromotionOrderBatchInput = z.infer<typeof promotionOrderBatchSchema>\n\n// ---------------------------------------------------------------------------\n// Promotion\n// ---------------------------------------------------------------------------\n\n// Default-free field set shared by the create and update schemas. Zod v4's `.partial()`\n// keeps an inner `.default()`, so deriving the update schema from a defaulted shape\n// injected `active: false` / `tags: []` / etc. into every partial update and silently\n// overwrote stored values. Defaults live on the create schema only.\nconst promotionBaseShape = {\n name: z.string().trim().min(1).max(200),\n description: clearableStringSchema(4000),\n order: z.number().int(),\n active: z.boolean(),\n startsAt: clearableDateSchema,\n // A date-only `endsAt` means \"through the end of that day\" (QA F7) \u2014 see\n // `clearableEndOfDayDateSchema` above. `startsAt` stays on `clearableDateSchema`: a\n // date-only start correctly means \"from the beginning of that day\".\n endsAt: clearableEndOfDayDateSchema,\n cumulative: z.boolean(),\n tags: z.array(z.string().trim().min(1).max(60)),\n excludedTags: z.array(z.string().trim().min(1).max(60)),\n excludeFlags: z.record(z.string(), z.boolean()),\n // Empty array = all currencies (\u00A7Data Models).\n eligibleCurrencies: z.array(currencyCodeSchema),\n maxBudget: clearableDecimalStringSchema,\n budgetCurrency: clearableCurrencyCodeSchema,\n}\n\nconst promotionBudgetAndWindowRefine = (\n value: { maxBudget?: string | null; budgetCurrency?: string | null; startsAt?: Date | null; endsAt?: Date | null },\n ctx: z.RefinementCtx,\n) => {\n if (value.maxBudget != null && !value.budgetCurrency) {\n ctx.addIssue({\n code: 'custom',\n message: 'budgetCurrency is required when maxBudget is set',\n path: ['budgetCurrency'],\n })\n }\n if (value.startsAt && value.endsAt && value.startsAt.getTime() > value.endsAt.getTime()) {\n ctx.addIssue({\n code: 'custom',\n message: 'startsAt must be before or equal to endsAt',\n path: ['endsAt'],\n })\n }\n}\n\nexport const promotionCreateSchema = z\n .object({\n ...promotionBaseShape,\n active: promotionBaseShape.active.optional().default(false),\n cumulative: promotionBaseShape.cumulative.optional().default(true),\n tags: promotionBaseShape.tags.optional().default([]),\n excludedTags: promotionBaseShape.excludedTags.optional().default([]),\n excludeFlags: promotionBaseShape.excludeFlags.optional().default({}),\n eligibleCurrencies: promotionBaseShape.eligibleCurrencies.optional().default([]),\n })\n .superRefine(promotionBudgetAndWindowRefine)\n\nexport type PromotionCreateInput = z.infer<typeof promotionCreateSchema>\n\nexport const promotionUpdateSchema = z\n .object({ id: uuid() })\n .merge(z.object(promotionBaseShape).partial())\n .superRefine(promotionBudgetAndWindowRefine)\n\nexport type PromotionUpdateInput = z.infer<typeof promotionUpdateSchema>\n\nexport const promotionDeleteSchema = z.object({\n id: uuid(),\n})\n\nexport type PromotionDeleteInput = z.infer<typeof promotionDeleteSchema>\n\n// ---------------------------------------------------------------------------\n// Code / GeneratedCode\n// ---------------------------------------------------------------------------\n\nexport const promotionCodeTypeValues = ['static', 'dynamic'] as const\nexport const promotionCodeTypeSchema = z.enum(promotionCodeTypeValues)\n\nexport const promotionCodeUsageModeValues = ['single', 'multiple', 'unlimited'] as const\nexport const promotionCodeUsageModeSchema = z.enum(promotionCodeUsageModeValues)\n\n// Default-free field set shared by the create and update schemas; see the identical\n// note above `promotionBaseShape` for why `active` must not carry a default here.\nconst codeBaseShape = {\n name: z.string().trim().min(1).max(200),\n type: promotionCodeTypeSchema,\n code: clearableUppercaseStringSchema(100),\n prefix: clearableUppercaseStringSchema(20),\n suffix: clearableUppercaseStringSchema(20),\n length: z.number().int().positive().max(64).nullable().optional(),\n amount: z.number().int().positive().nullable().optional(),\n usage: promotionCodeUsageModeSchema,\n usageAmount: z.number().int().positive().nullable().optional(),\n usagePerCustomer: z.number().int().positive().nullable().optional(),\n active: z.boolean(),\n}\n\nconst codeShapeRefine = (\n value: {\n type: (typeof promotionCodeTypeValues)[number]\n code?: string | null\n amount?: number | null\n usage: (typeof promotionCodeUsageModeValues)[number]\n usageAmount?: number | null\n },\n ctx: z.RefinementCtx,\n) => {\n if (value.type === 'static' && !value.code) {\n ctx.addIssue({ code: 'custom', message: 'code is required for a static code', path: ['code'] })\n }\n if (value.type === 'dynamic' && !value.amount) {\n ctx.addIssue({ code: 'custom', message: 'amount is required for a dynamic code pool', path: ['amount'] })\n }\n if (value.usage === 'multiple' && value.usageAmount == null) {\n ctx.addIssue({\n code: 'custom',\n message: 'usageAmount is required when usage is \"multiple\"',\n path: ['usageAmount'],\n })\n }\n}\n\nexport const promotionCodeCreateSchema = z\n .object({\n ...codeBaseShape,\n active: codeBaseShape.active.optional().default(true),\n })\n .superRefine(codeShapeRefine)\n\nexport type PromotionCodeCreateInput = z.infer<typeof promotionCodeCreateSchema>\n\nexport const promotionCodeUpdateSchema = z\n .object({ id: uuid() })\n .merge(z.object(codeBaseShape).partial())\n .superRefine((value, ctx) => {\n if (value.type && value.usage) codeShapeRefine(value as any, ctx)\n })\n\nexport type PromotionCodeUpdateInput = z.infer<typeof promotionCodeUpdateSchema>\n\nexport const promotionCodeDeleteSchema = z.object({\n id: uuid(),\n})\n\nexport type PromotionCodeDeleteInput = z.infer<typeof promotionCodeDeleteSchema>\n\nexport const generatedCodeCreateSchema = z.object({\n codeId: uuid(),\n code: z.string().trim().min(1).max(100),\n active: z.boolean().optional().default(true),\n})\n\nexport type GeneratedCodeCreateInput = z.infer<typeof generatedCodeCreateSchema>\n\n// ---------------------------------------------------------------------------\n// CodeReservation / CodeUsage\n// ---------------------------------------------------------------------------\n\n// \u00A7Data Models rev C4/C7/D-R4-8: a reservation is keyed by `(codeId, cartId)`, never\n// `(codeId, customerId)` \u2014 `customerId` stays a separate, nullable identity field.\nexport const codeReservationCreateSchema = z.object({\n codeId: uuid(),\n codeString: z.string().trim().min(1).max(100),\n cartId: uuid(),\n customerId: clearableStringSchema(200),\n})\n\nexport type CodeReservationCreateInput = z.infer<typeof codeReservationCreateSchema>\n\nexport const codeReservationReleaseSchema = z.object({\n codeId: uuid(),\n cartId: uuid(),\n})\n\nexport type CodeReservationReleaseInput = z.infer<typeof codeReservationReleaseSchema>\n\nexport const codeUsageCreateSchema = z.object({\n codeId: uuid(),\n customerId: clearableStringSchema(200),\n})\n\nexport type CodeUsageCreateInput = z.infer<typeof codeUsageCreateSchema>\n\n// ---------------------------------------------------------------------------\n// Code service inputs (\u00A7Public TypeScript Contract `ReserveCodeInput`/`UseCodeInput`,\n// Release 4 Phase 3 Step 6.2) \u2014 the top-level `PromotionsService` method payloads, as\n// opposed to `codeReservationCreateSchema`/`codeUsageCreateSchema` above, which validate\n// the already-resolved row shape persisted after a `codeString` lookup.\n// ---------------------------------------------------------------------------\n\n// `customerId` is required-but-nullable (`string | null`, never omitted) \u2014 distinct from\n// `clearableStringSchema`'s optional-and-nullable shape used by entity CRUD payloads.\nconst requiredNullableCustomerIdSchema = z.string().trim().min(1).max(200).nullable()\n\nexport const codeReserveInputSchema = z.object({\n codeString: z.string().trim().min(1).max(100),\n cartId: uuid(),\n customerId: requiredNullableCustomerIdSchema,\n})\n\nexport type CodeReserveInput = z.infer<typeof codeReserveInputSchema>\n\n// `codes/validate` (\u00A7Code Lifecycle) carries the identical body shape as `codes/reserve`.\nexport const codeValidateInputSchema = codeReserveInputSchema\n\nexport type CodeValidateInput = z.infer<typeof codeValidateInputSchema>\n\nexport const codeUseInputSchema = z.object({\n codeId: uuid(),\n codeString: z.string().trim().min(1).max(100),\n cartId: uuid(),\n customerId: requiredNullableCustomerIdSchema,\n type: promotionCodeTypeSchema,\n})\n\nexport type CodeUseInput = z.infer<typeof codeUseInputSchema>\n\n// ---------------------------------------------------------------------------\n// PromotionUsage\n// ---------------------------------------------------------------------------\n\nexport const promotionUsageOrderTypeValues = ['order', 'quote', 'pos_cart'] as const\nexport const promotionUsageOrderTypeSchema = z.enum(promotionUsageOrderTypeValues)\n\nexport const promotionUsageRegisterSchema = z.object({\n promotionId: uuid(),\n orderId: z.string().trim().min(1).max(200),\n orderType: promotionUsageOrderTypeSchema,\n customerId: clearableStringSchema(200),\n currency: currencyCodeSchema,\n totalDiscountAmount: decimalStringSchema,\n effectsSnapshot: z.unknown(),\n})\n\nexport type PromotionUsageRegisterInput = z.infer<typeof promotionUsageRegisterSchema>\n\nexport const promotionUsageRevertSchema = z.object({\n orderId: z.string().trim().min(1).max(200),\n})\n\nexport type PromotionUsageRevertInput = z.infer<typeof promotionUsageRevertSchema>\n\n// ---------------------------------------------------------------------------\n// Usage service inputs (\u00A7Public TypeScript Contract `RegisterUsageInput`/\n// `RevertUsageInput`, \u00A7A.2a, Release 4 Phase 3 Step 6.3) \u2014 the top-level\n// `promotions.usage.register`/`.usage.revert` command payloads, mirroring how\n// `codeReserveInputSchema`/`codeUseInputSchema` above validate `ReserveCodeInput`/\n// `UseCodeInput`. Distinct from `promotionUsageRegisterSchema` above, which validates a\n// single already-resolved `PromotionUsage` row (unused scaffolding \u2014 no command/route\n// writes one row at a time); `registerUsageInputSchema` validates the whole batch call\n// shape, one promotion per `appliedPromotions[]` entry, per `lib/types.ts`'s\n// `RegisterUsageInput`. `effects` is accepted as `z.record(z.unknown())` rather than a\n// full discriminated-union schema \u2014 the same \"jsonb snapshot, not zod-validated\"\n// convention `promotionUsageRegisterSchema.effectsSnapshot` above already uses, since\n// `ResolvedEffect[]` is a pure-evaluation-engine output already fully typed by the time it\n// reaches this command boundary, not raw wire input from an untrusted HTTP caller.\n// ---------------------------------------------------------------------------\n\nexport const appliedPromotionInputSchema = z.object({\n promotionId: uuid(),\n promotionName: z.string(),\n effects: z.array(z.record(z.string(), z.unknown())),\n})\n\nexport type AppliedPromotionInput = z.infer<typeof appliedPromotionInputSchema>\n\nexport const registerUsageInputSchema = z.object({\n orderId: z.string().trim().min(1).max(200),\n orderType: promotionUsageOrderTypeSchema,\n customerId: requiredNullableCustomerIdSchema,\n currency: currencyCodeSchema,\n appliedPromotions: z.array(appliedPromotionInputSchema),\n})\n\nexport type RegisterUsageCommandInput = z.infer<typeof registerUsageInputSchema>\n\nexport const revertUsageInputSchema = z.object({\n orderId: z.string().trim().min(1).max(200),\n})\n\nexport type RevertUsageCommandInput = z.infer<typeof revertUsageInputSchema>\n\n// ---------------------------------------------------------------------------\n// Cart-facing endpoints (\u00A7Cart-Facing Endpoints, Phase 2 Step 2.7) \u2014 the frozen public\n// `EvaluateInput`/`BuyerContext` contract from `lib/types.ts`, re-expressed as zod so\n// `POST /api/promotions/evaluate` (and `POST /api/promotions/free-products`, which\n// reuses the identical body shape per Step 2.7's own instructions \u2014 \"use the same\n// EvaluateItem-shaped items array for consistency... rather than inventing a new one\")\n// can validate the wire body before handing it to `promotionsService`. Mirrors\n// `lib/types.ts` field-for-field; keep both in sync by hand (the public contract is\n// frozen per SPEC-055 \u00A7Release 4 Contract Freeze, so this schema should rarely need to\n// change independently of it).\n// ---------------------------------------------------------------------------\n\nexport const buyerContextSchema = z\n .object({\n customerId: z.string().nullable(),\n customerIds: z.array(z.string()),\n customerGroupIds: z.array(z.string()),\n })\n .strict()\n\nexport const codeRefSchema = z\n .object({\n id: uuid(),\n type: promotionCodeTypeSchema,\n })\n .strict()\n\nexport const evaluateItemSchema = z\n .object({\n lineId: z.string().trim().min(1),\n productId: uuid(),\n variantId: uuid().nullable(),\n quantity: decimalStringSchema,\n unitPriceNet: decimalStringSchema,\n unitPriceGross: decimalStringSchema,\n priceIsPromotional: z.boolean(),\n })\n .strict()\n\nexport const evaluateInputSchema = z\n .object({\n items: z.array(evaluateItemSchema),\n currency: currencyCodeSchema,\n evaluatedAt: z.string().datetime({ offset: true }),\n buyer: buyerContextSchema,\n customerOrderCount: z.number().int().nullable(),\n cartWeight: decimalStringSchema.nullable(),\n codes: z.array(codeRefSchema),\n })\n .strict()\n\nexport type EvaluateInputPayload = z.infer<typeof evaluateInputSchema>\n\n// `POST /api/promotions/product-page` (\u00A7Cart-Facing Endpoints, rev V2): the one route in\n// this module that still addresses by SKU \u2014 \"a listing/PDP context has no cart line\n// identity to carry\".\nexport const productPageRequestSchema = z\n .object({\n skus: z.array(z.string().trim().min(1)).min(1),\n })\n .strict()\n\nexport type ProductPageRequestInput = z.infer<typeof productPageRequestSchema>\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z } from 'zod'\n\nconst uuid = () => z.string().uuid()\n\nconst emptyStringToNull = (value: unknown): unknown => {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length ? trimmed : null\n}\n\n// Mirrors `clearableStringSchema` in `customer_groups/data/validators.ts`: blanking a\n// previously-set value on edit must transmit null to clear it, not be silently dropped.\nconst clearableStringSchema = (max: number) =>\n z.preprocess(emptyStringToNull, z.string().trim().max(max).nullable().optional())\n\n// QA F9 (Low/UX, confirmed live): a static promotion code was stored and looked up with\n// exact, case-sensitive matching \u2014 `SUMMER10` created by an admin answered 409 to a buyer\n// typing `summer10`. Fixed at the write boundary (here) and the lookup boundary\n// (`lib/code-service.ts#resolveCodeByString`): every `code`/`prefix`/`suffix` is\n// upper-cased on write, so the two sides of the comparison always share one casing.\n// Charset is restricted to `A-Z0-9_-` \u2014 alphanumeric plus the two separator characters\n// every real promo-code convention already uses (`SUMMER-2026`, `SAVE_20`) \u2014 no spaces, no\n// other symbols \u2014 since these three fields exist for a human to type or transcribe. This\n// is deliberately WIDER than the code-generation worker's own `CODE_CHARSET` (`workers/\n// code-generation.worker.ts`), which additionally excludes `0`/`O`/`1`/`I`/`L` (visual\n// confusion in a MACHINE-GENERATED body, a different, narrower concern) and never\n// generates `-`/`_` itself \u2014 an admin-supplied `prefix`/`suffix` is where a separator\n// would actually appear.\nconst CODE_CHARSET_PATTERN = /^[A-Z0-9_-]+$/\n\nconst codeCharsetStringSchema = (max: number) =>\n z\n .string()\n .trim()\n .max(max)\n .transform((value) => value.toUpperCase())\n .refine((value) => CODE_CHARSET_PATTERN.test(value), {\n message: 'Must contain only letters A-Z, digits 0-9, hyphens or underscores (no spaces or other symbols)',\n })\n\n// Same `preprocess(emptyStringToNull, ...)` shape as `clearableStringSchema` \u2014 null/empty\n// stays valid and skips the charset check entirely; only a non-empty value is upper-cased\n// and charset-checked. Mirrors `clearableCurrencyCodeSchema`'s own\n// `currencyCodeSchema.nullable().optional()` composition below.\nconst clearableUppercaseStringSchema = (max: number) =>\n z.preprocess(emptyStringToNull, codeCharsetStringSchema(max).nullable().optional())\n\nconst clearableDateSchema = z.preprocess(emptyStringToNull, z.coerce.date().nullable().optional())\n\n// QA F7 (Medium, confirmed live): the `endsAt` datepicker sends a bare date (no time\n// component), which `z.coerce.date()` parses as 00:00:00.000 of that day \u2014 so a\n// promotion whose admin set \"ends Oct 16\" (meaning \"valid through end of day Oct 16\")\n// was excluded for the entire day of Oct 16 by `evaluation-engine.ts`'s\n// `isPromotionEligible` (`evaluatedAtMs > endsAtMs`), which is itself correct for a\n// timestamp that already represents the end of the valid window. The fix belongs on\n// write, not in that comparison: a date-only `endsAt` is bumped to 23:59:59.999 of that\n// same calendar day before coercion, so the stored value means \"through the end of that\n// day\". A value that already carries a time component (a full ISO timestamp from any\n// API caller) passes through unchanged \u2014 exact pre-existing semantics are preserved for\n// anyone already sending a real timestamp.\n//\n// Timezone anchor: UTC. No tenant/store timezone setting exists anywhere in this\n// codebase (checked `configs`, `catalog`, and the module registry) to anchor this to\n// instead, so \"end of day\" is computed in UTC, matching every other date field in this\n// module (`clearableDateSchema` itself coerces a bare date string to UTC midnight via\n// `z.coerce.date()` \u2014 this schema keeps the same anchor for its bump).\nconst DATE_ONLY_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/\n\nconst bumpDateOnlyToEndOfDay = (value: unknown): unknown => {\n if (typeof value !== 'string' || !DATE_ONLY_PATTERN.test(value)) return value\n return `${value}T23:59:59.999Z`\n}\n\nexport const clearableEndOfDayDateSchema = z.preprocess(\n (value) => bumpDateOnlyToEndOfDay(emptyStringToNull(value)),\n z.coerce.date().nullable().optional(),\n)\n\nconst clearableUuidSchema = z.preprocess(emptyStringToNull, uuid().nullable().optional())\n\n// Decimal amounts travel as strings end-to-end (\u00A7Data Models: \"value decimal string\") so\n// a caller never loses precision to IEEE-754 float rounding before this module's own\n// half-up integer-minor-units rounding (Phase 2, D-R4-10) ever runs. Every field using\n// this schema \u2014 rule thresholds, benefit discount values, cart/line amounts \u2014 is a\n// non-negative magnitude; a resolved effect's own sign (a discount subtracts) is applied\n// downstream in `lib/effect-resolvers.ts`, never carried in the input itself.\nconst normalizeDecimalSeparator = (value: unknown): unknown => {\n if (typeof value !== 'string' || !/^\\d+,\\d+$/.test(value)) return value\n return value.replace(',', '.')\n}\n\nconst decimalStringSchema = z.preprocess(\n normalizeDecimalSeparator,\n z.string().regex(/^\\d+(\\.\\d+)?$/, 'Must be a non-negative decimal string'),\n)\n\nconst clearableDecimalStringSchema = z.preprocess(emptyStringToNull, decimalStringSchema.nullable().optional())\n\n// A percentage discount value is additionally capped at 100 \u2014 this module's shared\n// `decimalStringSchema` only rules out negative values (a `-10` turning a discount into a\n// surcharge); a `150`-percent discount would otherwise still discount more than a line is\n// worth. Fixed-amount benefit values are deliberately NOT capped here \u2014 they are clamped\n// to their base at resolution time in `lib/effect-resolvers.ts`, since that cap depends on\n// the line/order total the discount applies to, not a fixed ceiling.\nfunction rejectPercentageOver100(\n config: { discountType: 'percentage' | 'fixed'; value: string },\n ctx: z.RefinementCtx,\n) {\n if (config.discountType === 'percentage' && Number(config.value) > 100) {\n ctx.addIssue({\n code: 'custom',\n message: 'A percentage discount value cannot exceed 100',\n path: ['value'],\n })\n }\n}\n\nconst currencyCodeSchema = z\n .string()\n .trim()\n .length(3, 'Currency code must be an ISO 4217 3-letter code')\n .transform((value) => value.toUpperCase())\n\nconst clearableCurrencyCodeSchema = z.preprocess(emptyStringToNull, currencyCodeSchema.nullable().optional())\n\n// No operator enum is defined anywhere in SPEC-055 for `order_value`/`order_date`/\n// `product_count`/`cart_weight`/`row_total`/`product`/`category`/`customer_order_history`\n// rule configs or the hidden `producer`/`shipping_address` configs. This six-value\n// comparison set is this module's own resolved judgment call (flagged in the Step 1.2\n// report) \u2014 Phase 2's `lib/rule-evaluators.ts` is the first consumer and should confirm\n// or correct it before it ships to the admin UI.\nexport const comparisonOperatorValues = ['eq', 'neq', 'gt', 'gte', 'lt', 'lte'] as const\nexport const comparisonOperatorSchema = z.enum(comparisonOperatorValues)\n\nconst nonEmptyLabelsSchema = z\n .record(z.string(), z.string().trim().min(1))\n .refine((value) => Object.keys(value).length > 0, 'At least one label is required')\n\nconst isoDateStringSchema = z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'Must be an ISO date (YYYY-MM-DD)')\n\n// ---------------------------------------------------------------------------\n// Rule configs \u2014 Release 4 rule set (\u00A7Data Models, rev P10/D-R4-14)\n// ---------------------------------------------------------------------------\n\nconst orderValueConfigSchema = z\n .object({\n value: decimalStringSchema,\n operator: comparisonOperatorSchema,\n taxInclusive: z.boolean(),\n limitToCategory: clearableStringSchema(200),\n })\n .strict()\n\nconst orderDateConfigSchema = z\n .object({\n date: isoDateStringSchema,\n operator: comparisonOperatorSchema,\n })\n .strict()\n\nconst productCountConfigSchema = z\n .object({\n value: z.number().int(),\n operator: comparisonOperatorSchema,\n })\n .strict()\n\nconst cartWeightConfigSchema = z\n .object({\n value: decimalStringSchema,\n operator: comparisonOperatorSchema,\n })\n .strict()\n\n// `sku`/`category_slug` replaced by id fields (rev \u2014 items are never addressed by SKU\n// in Release 4, \u00A7API Contracts).\nconst rowTotalConfigSchema = z\n .object({\n value: decimalStringSchema,\n operator: comparisonOperatorSchema,\n productId: clearableUuidSchema,\n categoryId: clearableUuidSchema,\n })\n .strict()\n\nconst productConfigSchema = z\n .object({\n productId: uuid(),\n variantId: clearableUuidSchema,\n quantity: z.number().int(),\n operator: comparisonOperatorSchema,\n })\n .strict()\n\n// Includes descendants \u2014 ancestor-expanded match (D-R4-15); the expansion itself is a\n// Phase 2 evaluator concern, not a validator concern.\nconst categoryConfigSchema = z\n .object({\n categoryId: uuid(),\n quantity: z.number().int(),\n operator: comparisonOperatorSchema,\n })\n .strict()\n\nconst productAttributeConfigSchema = z\n .object({\n attributeCode: z.string().trim().min(1).max(100),\n operator: comparisonOperatorSchema,\n value: z.string().trim().min(1).max(500),\n })\n .strict()\n\n// Replaces `user_group` outright (rev \u2014 not additive; nothing has ever shipped against\n// `user_group`, \u00A7Data Models).\nconst customerGroupConfigSchema = z\n .object({\n customerGroupId: uuid(),\n })\n .strict()\n\nconst customerOrderHistoryConfigSchema = z\n .object({\n value: z.number().int(),\n operator: comparisonOperatorSchema,\n })\n .strict()\n\n// `codeId` is validated against `promotion_codes` in commands before persisting\n// (\u00A7Data Models) \u2014 this schema only checks shape.\nconst codeRuleConfigSchema = z\n .object({\n codeId: uuid(),\n })\n .strict()\n\n// --- Hidden rule types (\u00A7Data Models) \u2014 modelled so the discriminated union never needs\n// a second migration later, but excluded from `ruleInputSchema` (the Release 4 write-path\n// schema) below. No admin UI, no Phase 1-3 evaluator registration.\n\nconst producerConfigSchema = z\n .object({\n producerCode: z.string().trim().min(1).max(100),\n quantity: z.number().int(),\n operator: comparisonOperatorSchema,\n })\n .strict()\n\nconst excludePharmaceuticalConfigSchema = z.object({}).strict()\n\nconst consentFlagConfigSchema = z\n .object({\n flagKey: z.string().trim().min(1).max(100),\n })\n .strict()\n\nconst deliveryMethodRuleConfigSchema = z\n .object({\n deliveryMethodCode: z.string().trim().min(1).max(100),\n })\n .strict()\n\nconst paymentMethodConfigSchema = z\n .object({\n paymentMethodCode: z.string().trim().min(1).max(100),\n })\n .strict()\n\nconst shippingAddressConfigSchema = z\n .object({\n field: z.string().trim().min(1).max(100),\n operator: comparisonOperatorSchema,\n value: z.string().trim().min(1).max(500),\n })\n .strict()\n\nconst ruleEnvelope = <Type extends string, Config extends z.ZodTypeAny>(\n ruleType: Type,\n config: Config,\n) =>\n z.object({\n id: uuid().optional(),\n sortOrder: z.number().int().min(0).optional().default(0),\n ruleType: z.literal(ruleType),\n config,\n })\n\nconst shippedRuleVariants = [\n ruleEnvelope('order_value', orderValueConfigSchema),\n ruleEnvelope('order_date', orderDateConfigSchema),\n ruleEnvelope('product_count', productCountConfigSchema),\n ruleEnvelope('row_total', rowTotalConfigSchema),\n ruleEnvelope('product', productConfigSchema),\n ruleEnvelope('category', categoryConfigSchema),\n ruleEnvelope('customer_group', customerGroupConfigSchema),\n ruleEnvelope('code', codeRuleConfigSchema),\n] as const\n\nconst hiddenRuleVariants = [\n ruleEnvelope('customer_order_history', customerOrderHistoryConfigSchema),\n ruleEnvelope('product_attribute', productAttributeConfigSchema),\n ruleEnvelope('cart_weight', cartWeightConfigSchema),\n ruleEnvelope('producer', producerConfigSchema),\n ruleEnvelope('exclude_pharmaceutical', excludePharmaceuticalConfigSchema),\n ruleEnvelope('consent_flag', consentFlagConfigSchema),\n ruleEnvelope('delivery_method', deliveryMethodRuleConfigSchema),\n ruleEnvelope('payment_method', paymentMethodConfigSchema),\n ruleEnvelope('shipping_address', shippingAddressConfigSchema),\n] as const\n\nexport const RELEASE_4_SHIPPED_RULE_TYPES = [\n 'order_value',\n 'order_date',\n 'product_count',\n 'row_total',\n 'product',\n 'category',\n 'customer_group',\n 'code',\n] as const\n\nexport const PROMOTIONS_HIDDEN_RULE_TYPES = [\n 'cart_weight',\n 'product_attribute',\n 'customer_order_history',\n 'producer',\n 'exclude_pharmaceutical',\n 'consent_flag',\n 'delivery_method',\n 'payment_method',\n 'shipping_address',\n] as const\n\n// Write-path schema (\u00A7Implementation Plan Phase 1, step 3): only the eight Release 4\n// rule types are accepted here. A request naming a hidden type fails to match any\n// variant's `ruleType` literal and the discriminated union rejects it \u2014 the rejection\n// a hidden-rule-type write test (`TC-PROM-0xx`) exercises, not merely hiding the type\n// from the admin UI combobox.\nexport const ruleInputSchema = z.discriminatedUnion('ruleType', [...shippedRuleVariants])\n\n// Full model (all seventeen types, shipped + hidden) \u2014 kept so the discriminated union\n// does not need a second migration later when a data source appears for a hidden type\n// (\u00A7Data Models rev P10). Used by extension/internal tooling only, never the write path.\nexport const ruleInputSchemaFull = z.discriminatedUnion('ruleType', [\n ...shippedRuleVariants,\n ...hiddenRuleVariants,\n])\n\nexport type RuleInput = z.infer<typeof ruleInputSchema>\n\n// ---------------------------------------------------------------------------\n// Benefit configs \u2014 Application target (`applies_to`) and shared quantity-cap block\n// (\u00A7Data Models)\n// ---------------------------------------------------------------------------\n\nexport const promotionBenefitAppliesToValues = ['order', 'line', 'unit'] as const\nexport const promotionBenefitAppliesToSchema = z.enum(promotionBenefitAppliesToValues)\n\nexport const quantityCapSelectorValues = ['all', 'cart_order', 'cheapest', 'most_expensive', 'nth'] as const\nexport const quantityCapSelectorSchema = z.enum(quantityCapSelectorValues)\n\n// Shared block intersected into every benefit variant (\u00A7Data Models \"Quantity caps\").\nconst quantityCapShape = {\n maxQuantity: z.number().int().positive().nullable().optional(),\n maxLines: z.number().int().positive().nullable().optional(),\n quantityStep: z.number().int().positive().nullable().optional(),\n selector: quantityCapSelectorSchema.nullable().optional(),\n nthPosition: z.number().int().positive().nullable().optional(),\n}\n\ntype QuantityCapConfig = {\n maxQuantity?: number | null\n maxLines?: number | null\n quantityStep?: number | null\n selector?: (typeof quantityCapSelectorValues)[number] | null\n nthPosition?: number | null\n}\n\n// \u00A7Data Models \"Write-path rejections (422), normative\" \u2014 implemented as real\n// rejections (not documentation): a capped benefit on `applies_to: 'order'`, a cap with\n// no `selector`, `selector: 'all'` plus a cap, `selector: 'nth'` plus a cap,\n// `selector`/`nthPosition` split apart, and `quantityStep` under `applies_to: 'line'`.\nfunction checkQuantityCapRules(\n config: QuantityCapConfig,\n appliesTo: (typeof promotionBenefitAppliesToValues)[number],\n ctx: z.RefinementCtx,\n) {\n const hasCap = config.maxQuantity != null || config.maxLines != null\n\n if (appliesTo === 'order') {\n if (config.maxQuantity != null || config.maxLines != null || config.quantityStep != null) {\n ctx.addIssue({\n code: 'custom',\n message: 'Quantity caps are not meaningful when appliesTo is \"order\" \u2014 nothing to count',\n path: ['config', 'maxQuantity'],\n })\n }\n // Nothing else to check: selector/nthPosition rules below only matter when a cap\n // can exist, which is already rejected above for `order`.\n return\n }\n\n if (hasCap && !config.selector) {\n ctx.addIssue({\n code: 'custom',\n message: 'selector is required when maxQuantity or maxLines is set',\n path: ['config', 'selector'],\n })\n }\n\n if (config.selector === 'all' && hasCap) {\n ctx.addIssue({\n code: 'custom',\n message: '\"all\" selector cannot be combined with a quantity cap \u2014 \"all\" means every qualifying unit',\n path: ['config', 'selector'],\n })\n }\n\n if (config.selector === 'nth' && hasCap) {\n ctx.addIssue({\n code: 'custom',\n message: '\"nth\" selector is its own cap and may not be combined with maxQuantity or maxLines',\n path: ['config', 'selector'],\n })\n }\n\n if (config.selector === 'nth' && config.nthPosition == null) {\n ctx.addIssue({\n code: 'custom',\n message: 'nthPosition is required when selector is \"nth\"',\n path: ['config', 'nthPosition'],\n })\n }\n\n if (config.selector !== 'nth' && config.nthPosition != null) {\n ctx.addIssue({\n code: 'custom',\n message: 'nthPosition is only meaningful when selector is \"nth\"',\n path: ['config', 'nthPosition'],\n })\n }\n\n // Blocks are counted in units only; `applies_to: 'line'` spends whole lines, where \"a\n // block of units\" has no meaning (\u00A7Data Models).\n if (config.quantityStep != null && appliesTo === 'line') {\n ctx.addIssue({\n code: 'custom',\n message: 'quantityStep is not meaningful when appliesTo is \"line\" \u2014 lines are spent whole',\n path: ['config', 'quantityStep'],\n })\n }\n}\n\nconst discountTypeValues = ['percentage', 'fixed'] as const\nconst discountTypeSchema = z.enum(discountTypeValues)\n\nconst tierElementSchema = z\n .object({\n threshold: decimalStringSchema,\n discountType: discountTypeSchema,\n value: decimalStringSchema,\n })\n .strict()\n .superRefine(rejectPercentageOver100)\n\n// \u00A7Data Models: \"Tiers must be sorted ascending by threshold.\"\nconst tiersSchema = z\n .array(tierElementSchema)\n .min(1)\n .superRefine((tiers, ctx) => {\n for (let i = 1; i < tiers.length; i += 1) {\n if (Number(tiers[i - 1].threshold) > Number(tiers[i].threshold)) {\n ctx.addIssue({\n code: 'custom',\n message: 'tiers must be sorted ascending by threshold',\n path: [i, 'threshold'],\n })\n }\n }\n })\n\nconst productDiscountConfigSchema = z\n .object({\n productId: clearableUuidSchema,\n discountType: discountTypeSchema,\n value: decimalStringSchema,\n limitToCategoryId: clearableUuidSchema,\n maxDiscount: clearableDecimalStringSchema,\n labels: nonEmptyLabelsSchema,\n ...quantityCapShape,\n })\n .strict()\n .superRefine(rejectPercentageOver100)\n\nconst cartDiscountConfigSchema = z\n .object({\n discountType: discountTypeSchema,\n value: decimalStringSchema,\n maxDiscount: clearableDecimalStringSchema,\n labels: nonEmptyLabelsSchema,\n ...quantityCapShape,\n })\n .strict()\n .superRefine(rejectPercentageOver100)\n\n// Hidden from the admin tree builder in Release 4 (D-R4-14); the type and its config\n// schema stay in the model per \u00A7Data Models.\nconst deliveryDiscountConfigSchema = z\n .object({\n deliveryMethodCode: z.string().trim().min(1).max(100),\n discountType: discountTypeSchema,\n value: decimalStringSchema,\n labels: nonEmptyLabelsSchema,\n ...quantityCapShape,\n })\n .strict()\n .superRefine(rejectPercentageOver100)\n\nconst freeProductConfigSchema = z\n .object({\n productId: clearableUuidSchema,\n categoryId: clearableUuidSchema,\n quantity: z.number().int().positive(),\n labels: nonEmptyLabelsSchema,\n ...quantityCapShape,\n })\n .strict()\n\nconst buyXGetYConfigSchema = z\n .object({\n triggerProductId: clearableUuidSchema,\n triggerCategoryId: clearableUuidSchema,\n triggerQuantity: z.number().int().positive(),\n rewardProductId: uuid(),\n rewardVariantId: clearableUuidSchema,\n rewardQuantity: z.number().int().positive(),\n discountType: discountTypeSchema,\n value: decimalStringSchema,\n maxApplications: z.number().int().positive().nullable().optional(),\n maxDiscount: clearableDecimalStringSchema,\n labels: nonEmptyLabelsSchema,\n ...quantityCapShape,\n })\n .strict()\n .superRefine(rejectPercentageOver100)\n\nconst tieredDiscountConfigSchema = z\n .object({\n limitToCategoryId: clearableUuidSchema,\n tiers: tiersSchema,\n maxDiscount: clearableDecimalStringSchema,\n labels: nonEmptyLabelsSchema,\n ...quantityCapShape,\n })\n .strict()\n\n// \u00A7Data Models \"Allowed values per applies_to per benefit_type\" table \u2014 enforced by\n// restricting each variant's `appliesTo` literal set (not merely documented) and\n// defaulting to the type's documented default when the operator omits it.\nconst benefitEnvelope = <Type extends string, Config extends z.ZodTypeAny, Allowed extends string>(\n benefitType: Type,\n config: Config,\n allowedAppliesTo: readonly [Allowed, ...Allowed[]],\n defaultAppliesTo: Allowed,\n) =>\n z.object({\n id: uuid().optional(),\n sortOrder: z.number().int().min(0).optional().default(0),\n benefitType: z.literal(benefitType),\n appliesTo: z.enum(allowedAppliesTo).optional().default(defaultAppliesTo),\n config,\n })\n\nconst shippedBenefitVariants = [\n benefitEnvelope('product_discount', productDiscountConfigSchema, ['line', 'unit'] as const, 'line'),\n benefitEnvelope('cart_discount', cartDiscountConfigSchema, ['order'] as const, 'order'),\n benefitEnvelope('free_product', freeProductConfigSchema, ['order'] as const, 'order'),\n benefitEnvelope('buy_x_get_y', buyXGetYConfigSchema, ['line', 'unit'] as const, 'unit'),\n benefitEnvelope('tiered_discount', tieredDiscountConfigSchema, ['order', 'line', 'unit'] as const, 'order'),\n] as const\n\n// Hidden from the admin tree builder in Release 4 (D-R4-14) \u2014 same rejection mechanism\n// as the hidden rule types: excluded from `benefitInputSchema`, kept in\n// `benefitInputSchemaFull` for the model.\nconst hiddenBenefitVariants = [\n benefitEnvelope('delivery_discount', deliveryDiscountConfigSchema, ['order'] as const, 'order'),\n] as const\n\nexport const RELEASE_4_SHIPPED_BENEFIT_TYPES = [\n 'product_discount',\n 'cart_discount',\n 'free_product',\n 'buy_x_get_y',\n 'tiered_discount',\n] as const\n\nexport const PROMOTIONS_HIDDEN_BENEFIT_TYPES = ['delivery_discount'] as const\n\nconst withQuantityCapGuard = <T extends z.ZodTypeAny>(schema: T) =>\n schema.superRefine((value: any, ctx) => {\n checkQuantityCapRules(value.config, value.appliesTo, ctx)\n })\n\nexport const benefitInputSchema = withQuantityCapGuard(\n z.discriminatedUnion('benefitType', [...shippedBenefitVariants]),\n)\n\nexport const benefitInputSchemaFull = withQuantityCapGuard(\n z.discriminatedUnion('benefitType', [...shippedBenefitVariants, ...hiddenBenefitVariants]),\n)\n\nexport type BenefitInput = z.infer<typeof benefitInputSchema>\n\n// ---------------------------------------------------------------------------\n// D-R4-27: single `eligible_currency` requirement\n// ---------------------------------------------------------------------------\n//\n// \"Benefits with fixed amounts or tiers, and order_value/row_total thresholds, require a\n// single eligible_currency (write-path validation).\" This is a cross-entity rule \u2014 it\n// reads `Promotion.eligibleCurrencies`, which is not visible from an individual Rule or\n// Benefit config \u2014 so it cannot live inside `ruleInputSchema`/`benefitInputSchema`\n// themselves. It is exposed here as a composite schema + assertion helper for the\n// commands that own the full tree + promotion context (`commands/promotions.ts`,\n// `commands/rule-tree.ts`, Step 1.3+) to call before persisting. Flagged in the Step 1.2\n// report as the resolved design choice for this cross-entity check.\n\nexport const CURRENCY_SENSITIVE_RULE_TYPES = ['order_value', 'row_total'] as const\n\nexport function benefitRequiresSingleEligibleCurrency(\n benefitType: string,\n config: { discountType?: string },\n): boolean {\n if (benefitType === 'tiered_discount') return true\n if (\n (benefitType === 'product_discount' || benefitType === 'cart_discount' || benefitType === 'buy_x_get_y') &&\n config.discountType === 'fixed'\n ) {\n return true\n }\n return false\n}\n\nexport const promotionCurrencyGuardSchema = z\n .object({\n eligibleCurrencies: z.array(z.string()),\n rules: z.array(z.object({ ruleType: z.string() })),\n benefits: z.array(\n z.object({ benefitType: z.string(), config: z.object({ discountType: z.string().optional() }).passthrough() }),\n ),\n })\n .superRefine((value, ctx) => {\n const requiresSingleCurrency =\n value.rules.some((rule) => (CURRENCY_SENSITIVE_RULE_TYPES as readonly string[]).includes(rule.ruleType)) ||\n value.benefits.some((benefit) => benefitRequiresSingleEligibleCurrency(benefit.benefitType, benefit.config))\n\n if (requiresSingleCurrency && value.eligibleCurrencies.length !== 1) {\n ctx.addIssue({\n code: 'custom',\n message:\n 'A promotion containing a fixed-amount benefit, a tiered_discount benefit, or an order_value/row_total rule must set exactly one eligible currency (D-R4-27)',\n path: ['eligibleCurrencies'],\n })\n }\n })\n\nexport type PromotionCurrencyGuardInput = z.infer<typeof promotionCurrencyGuardSchema>\n\nexport function assertPromotionCurrencyRequirements(input: PromotionCurrencyGuardInput): void {\n promotionCurrencyGuardSchema.parse(input)\n}\n\n// ---------------------------------------------------------------------------\n// RuleGroup tree node (used by `commands/rule-tree.ts`, Step 1.3+)\n// ---------------------------------------------------------------------------\n\nexport const ruleGroupOperatorValues = ['and', 'or'] as const\nexport const ruleGroupOperatorSchema = z.enum(ruleGroupOperatorValues)\n\nexport type RuleGroupTreeNodeInput = {\n id?: string\n operator: (typeof ruleGroupOperatorValues)[number]\n sortOrder: number\n rules: RuleInput[]\n benefits: BenefitInput[]\n children: RuleGroupTreeNodeInput[]\n}\n\nexport const ruleGroupTreeNodeSchema: z.ZodType<RuleGroupTreeNodeInput> = z.lazy(() =>\n z.object({\n id: uuid().optional(),\n operator: ruleGroupOperatorSchema,\n sortOrder: z.number().int().min(0).optional().default(0),\n rules: z.array(ruleInputSchema).optional().default([]),\n benefits: z.array(benefitInputSchema).optional().default([]),\n children: z.array(ruleGroupTreeNodeSchema).optional().default([]),\n }),\n)\n\n// `PUT /api/promotions/[id]/tree` body (\u00A7API Contracts): the full tree plus the\n// promotion's `updatedAt` for the optimistic-lock check on this separate tree-replace\n// surface (distinct from the row-level header `CrudForm` sends for plain metadata PUTs).\nexport const promotionTreeUpdateSchema = z.object({\n updatedAt: z.coerce.date(),\n root: ruleGroupTreeNodeSchema,\n})\n\nexport type PromotionTreeUpdateInput = z.infer<typeof promotionTreeUpdateSchema>\n\n// `PATCH /api/promotions/order` body: each item carries its own `updatedAt`; a stale\n// value 409s that item only, not the whole batch (\u00A7API Contracts).\nexport const promotionOrderBatchSchema = z.object({\n items: z\n .array(\n z.object({\n id: uuid(),\n order: z.number().int(),\n updatedAt: z.coerce.date(),\n }),\n )\n .min(1),\n})\n\nexport type PromotionOrderBatchInput = z.infer<typeof promotionOrderBatchSchema>\n\n// ---------------------------------------------------------------------------\n// Promotion\n// ---------------------------------------------------------------------------\n\n// Default-free field set shared by the create and update schemas. Zod v4's `.partial()`\n// keeps an inner `.default()`, so deriving the update schema from a defaulted shape\n// injected `active: false` / `tags: []` / etc. into every partial update and silently\n// overwrote stored values. Defaults live on the create schema only.\nconst promotionBaseShape = {\n name: z.string().trim().min(1).max(200),\n description: clearableStringSchema(4000),\n order: z.number().int(),\n active: z.boolean(),\n startsAt: clearableDateSchema,\n // A date-only `endsAt` means \"through the end of that day\" (QA F7) \u2014 see\n // `clearableEndOfDayDateSchema` above. `startsAt` stays on `clearableDateSchema`: a\n // date-only start correctly means \"from the beginning of that day\".\n endsAt: clearableEndOfDayDateSchema,\n cumulative: z.boolean(),\n tags: z.array(z.string().trim().min(1).max(60)),\n excludedTags: z.array(z.string().trim().min(1).max(60)),\n excludeFlags: z.record(z.string(), z.boolean()),\n // Empty array = all currencies (\u00A7Data Models).\n eligibleCurrencies: z.array(currencyCodeSchema),\n maxBudget: clearableDecimalStringSchema,\n budgetCurrency: clearableCurrencyCodeSchema,\n}\n\nconst promotionBudgetAndWindowRefine = (\n value: { maxBudget?: string | null; budgetCurrency?: string | null; startsAt?: Date | null; endsAt?: Date | null },\n ctx: z.RefinementCtx,\n) => {\n if (value.maxBudget != null && !value.budgetCurrency) {\n ctx.addIssue({\n code: 'custom',\n message: 'budgetCurrency is required when maxBudget is set',\n path: ['budgetCurrency'],\n })\n }\n if (value.startsAt && value.endsAt && value.startsAt.getTime() > value.endsAt.getTime()) {\n ctx.addIssue({\n code: 'custom',\n message: 'startsAt must be before or equal to endsAt',\n path: ['endsAt'],\n })\n }\n}\n\nexport const promotionCreateSchema = z\n .object({\n ...promotionBaseShape,\n active: promotionBaseShape.active.optional().default(false),\n cumulative: promotionBaseShape.cumulative.optional().default(true),\n tags: promotionBaseShape.tags.optional().default([]),\n excludedTags: promotionBaseShape.excludedTags.optional().default([]),\n excludeFlags: promotionBaseShape.excludeFlags.optional().default({}),\n eligibleCurrencies: promotionBaseShape.eligibleCurrencies.optional().default([]),\n })\n .superRefine(promotionBudgetAndWindowRefine)\n\nexport type PromotionCreateInput = z.infer<typeof promotionCreateSchema>\n\nexport const promotionUpdateSchema = z\n .object({ id: uuid() })\n .merge(z.object(promotionBaseShape).partial())\n .superRefine(promotionBudgetAndWindowRefine)\n\nexport type PromotionUpdateInput = z.infer<typeof promotionUpdateSchema>\n\nexport const promotionDeleteSchema = z.object({\n id: uuid(),\n})\n\nexport type PromotionDeleteInput = z.infer<typeof promotionDeleteSchema>\n\n// ---------------------------------------------------------------------------\n// Code / GeneratedCode\n// ---------------------------------------------------------------------------\n\nexport const promotionCodeTypeValues = ['static', 'dynamic'] as const\nexport const promotionCodeTypeSchema = z.enum(promotionCodeTypeValues)\n\nexport const promotionCodeUsageModeValues = ['single', 'multiple', 'unlimited'] as const\nexport const promotionCodeUsageModeSchema = z.enum(promotionCodeUsageModeValues)\n\n// Default-free field set shared by the create and update schemas; see the identical\n// note above `promotionBaseShape` for why `active` must not carry a default here.\nconst codeBaseShape = {\n name: z.string().trim().min(1).max(200),\n type: promotionCodeTypeSchema,\n code: clearableUppercaseStringSchema(100),\n prefix: clearableUppercaseStringSchema(20),\n suffix: clearableUppercaseStringSchema(20),\n length: z.number().int().positive().max(64).nullable().optional(),\n amount: z.number().int().positive().nullable().optional(),\n usage: promotionCodeUsageModeSchema,\n usageAmount: z.number().int().positive().nullable().optional(),\n usagePerCustomer: z.number().int().positive().nullable().optional(),\n active: z.boolean(),\n}\n\nconst codeShapeRefine = (\n value: {\n type: (typeof promotionCodeTypeValues)[number]\n code?: string | null\n amount?: number | null\n usage: (typeof promotionCodeUsageModeValues)[number]\n usageAmount?: number | null\n },\n ctx: z.RefinementCtx,\n) => {\n if (value.type === 'static' && !value.code) {\n ctx.addIssue({ code: 'custom', message: 'code is required for a static code', path: ['code'] })\n }\n if (value.type === 'dynamic' && !value.amount) {\n ctx.addIssue({ code: 'custom', message: 'amount is required for a dynamic code pool', path: ['amount'] })\n }\n if (value.usage === 'multiple' && value.usageAmount == null) {\n ctx.addIssue({\n code: 'custom',\n message: 'usageAmount is required when usage is \"multiple\"',\n path: ['usageAmount'],\n })\n }\n}\n\nexport const promotionCodeCreateSchema = z\n .object({\n ...codeBaseShape,\n active: codeBaseShape.active.optional().default(true),\n })\n .superRefine(codeShapeRefine)\n\nexport type PromotionCodeCreateInput = z.infer<typeof promotionCodeCreateSchema>\n\nexport const promotionCodeUpdateSchema = z\n .object({ id: uuid() })\n .merge(z.object(codeBaseShape).partial())\n .superRefine((value, ctx) => {\n if (value.type && value.usage) codeShapeRefine(value as any, ctx)\n })\n\nexport type PromotionCodeUpdateInput = z.infer<typeof promotionCodeUpdateSchema>\n\nexport const promotionCodeDeleteSchema = z.object({\n id: uuid(),\n})\n\nexport type PromotionCodeDeleteInput = z.infer<typeof promotionCodeDeleteSchema>\n\nexport const generatedCodeCreateSchema = z.object({\n codeId: uuid(),\n code: z.string().trim().min(1).max(100),\n active: z.boolean().optional().default(true),\n})\n\nexport type GeneratedCodeCreateInput = z.infer<typeof generatedCodeCreateSchema>\n\n// ---------------------------------------------------------------------------\n// CodeReservation / CodeUsage\n// ---------------------------------------------------------------------------\n\n// \u00A7Data Models rev C4/C7/D-R4-8: a reservation is keyed by `(codeId, cartId)`, never\n// `(codeId, customerId)` \u2014 `customerId` stays a separate, nullable identity field.\nexport const codeReservationCreateSchema = z.object({\n codeId: uuid(),\n codeString: z.string().trim().min(1).max(100),\n cartId: uuid(),\n customerId: clearableStringSchema(200),\n})\n\nexport type CodeReservationCreateInput = z.infer<typeof codeReservationCreateSchema>\n\nexport const codeReservationReleaseSchema = z.object({\n codeId: uuid(),\n cartId: uuid(),\n})\n\nexport type CodeReservationReleaseInput = z.infer<typeof codeReservationReleaseSchema>\n\nexport const codeUsageCreateSchema = z.object({\n codeId: uuid(),\n customerId: clearableStringSchema(200),\n})\n\nexport type CodeUsageCreateInput = z.infer<typeof codeUsageCreateSchema>\n\n// ---------------------------------------------------------------------------\n// Code service inputs (\u00A7Public TypeScript Contract `ReserveCodeInput`/`UseCodeInput`,\n// Release 4 Phase 3 Step 6.2) \u2014 the top-level `PromotionsService` method payloads, as\n// opposed to `codeReservationCreateSchema`/`codeUsageCreateSchema` above, which validate\n// the already-resolved row shape persisted after a `codeString` lookup.\n// ---------------------------------------------------------------------------\n\n// `customerId` is required-but-nullable (`string | null`, never omitted) \u2014 distinct from\n// `clearableStringSchema`'s optional-and-nullable shape used by entity CRUD payloads.\nconst requiredNullableCustomerIdSchema = z.string().trim().min(1).max(200).nullable()\n\nexport const codeReserveInputSchema = z.object({\n codeString: z.string().trim().min(1).max(100),\n cartId: uuid(),\n customerId: requiredNullableCustomerIdSchema,\n})\n\nexport type CodeReserveInput = z.infer<typeof codeReserveInputSchema>\n\n// `codes/validate` (\u00A7Code Lifecycle) carries the identical body shape as `codes/reserve`.\nexport const codeValidateInputSchema = codeReserveInputSchema\n\nexport type CodeValidateInput = z.infer<typeof codeValidateInputSchema>\n\nexport const codeUseInputSchema = z.object({\n codeId: uuid(),\n codeString: z.string().trim().min(1).max(100),\n cartId: uuid(),\n customerId: requiredNullableCustomerIdSchema,\n type: promotionCodeTypeSchema,\n})\n\nexport type CodeUseInput = z.infer<typeof codeUseInputSchema>\n\n// ---------------------------------------------------------------------------\n// PromotionUsage\n// ---------------------------------------------------------------------------\n\nexport const promotionUsageOrderTypeValues = ['order', 'quote', 'pos_cart'] as const\nexport const promotionUsageOrderTypeSchema = z.enum(promotionUsageOrderTypeValues)\n\nexport const promotionUsageRegisterSchema = z.object({\n promotionId: uuid(),\n orderId: z.string().trim().min(1).max(200),\n orderType: promotionUsageOrderTypeSchema,\n customerId: clearableStringSchema(200),\n currency: currencyCodeSchema,\n totalDiscountAmount: decimalStringSchema,\n effectsSnapshot: z.unknown(),\n})\n\nexport type PromotionUsageRegisterInput = z.infer<typeof promotionUsageRegisterSchema>\n\nexport const promotionUsageRevertSchema = z.object({\n orderId: z.string().trim().min(1).max(200),\n})\n\nexport type PromotionUsageRevertInput = z.infer<typeof promotionUsageRevertSchema>\n\n// ---------------------------------------------------------------------------\n// Usage service inputs (\u00A7Public TypeScript Contract `RegisterUsageInput`/\n// `RevertUsageInput`, \u00A7A.2a, Release 4 Phase 3 Step 6.3) \u2014 the top-level\n// `promotions.usage.register`/`.usage.revert` command payloads, mirroring how\n// `codeReserveInputSchema`/`codeUseInputSchema` above validate `ReserveCodeInput`/\n// `UseCodeInput`. Distinct from `promotionUsageRegisterSchema` above, which validates a\n// single already-resolved `PromotionUsage` row (unused scaffolding \u2014 no command/route\n// writes one row at a time); `registerUsageInputSchema` validates the whole batch call\n// shape, one promotion per `appliedPromotions[]` entry, per `lib/types.ts`'s\n// `RegisterUsageInput`. `effects` is accepted as `z.record(z.unknown())` rather than a\n// full discriminated-union schema \u2014 the same \"jsonb snapshot, not zod-validated\"\n// convention `promotionUsageRegisterSchema.effectsSnapshot` above already uses, since\n// `ResolvedEffect[]` is a pure-evaluation-engine output already fully typed by the time it\n// reaches this command boundary, not raw wire input from an untrusted HTTP caller.\n// ---------------------------------------------------------------------------\n\nexport const appliedPromotionInputSchema = z.object({\n promotionId: uuid(),\n promotionName: z.string(),\n effects: z.array(z.record(z.string(), z.unknown())),\n})\n\nexport type AppliedPromotionInput = z.infer<typeof appliedPromotionInputSchema>\n\nexport const registerUsageInputSchema = z.object({\n orderId: z.string().trim().min(1).max(200),\n orderType: promotionUsageOrderTypeSchema,\n customerId: requiredNullableCustomerIdSchema,\n currency: currencyCodeSchema,\n appliedPromotions: z.array(appliedPromotionInputSchema),\n})\n\nexport type RegisterUsageCommandInput = z.infer<typeof registerUsageInputSchema>\n\nexport const revertUsageInputSchema = z.object({\n orderId: z.string().trim().min(1).max(200),\n})\n\nexport type RevertUsageCommandInput = z.infer<typeof revertUsageInputSchema>\n\n// ---------------------------------------------------------------------------\n// Cart-facing endpoints (\u00A7Cart-Facing Endpoints, Phase 2 Step 2.7) \u2014 the frozen public\n// `EvaluateInput`/`BuyerContext` contract from `lib/types.ts`, re-expressed as zod so\n// `POST /api/promotions/evaluate` (and `POST /api/promotions/free-products`, which\n// reuses the identical body shape per Step 2.7's own instructions \u2014 \"use the same\n// EvaluateItem-shaped items array for consistency... rather than inventing a new one\")\n// can validate the wire body before handing it to `promotionsService`. Mirrors\n// `lib/types.ts` field-for-field; keep both in sync by hand (the public contract is\n// frozen per SPEC-055 \u00A7Release 4 Contract Freeze, so this schema should rarely need to\n// change independently of it).\n// ---------------------------------------------------------------------------\n\nexport const buyerContextSchema = z\n .object({\n customerId: z.string().nullable(),\n customerIds: z.array(z.string()),\n customerGroupIds: z.array(z.string()),\n })\n .strict()\n\nexport const codeRefSchema = z\n .object({\n id: uuid(),\n type: promotionCodeTypeSchema,\n })\n .strict()\n\nexport const evaluateItemSchema = z\n .object({\n lineId: z.string().trim().min(1),\n productId: uuid(),\n variantId: uuid().nullable(),\n quantity: decimalStringSchema,\n unitPriceNet: decimalStringSchema,\n unitPriceGross: decimalStringSchema,\n priceIsPromotional: z.boolean(),\n })\n .strict()\n\nexport const evaluateInputSchema = z\n .object({\n items: z.array(evaluateItemSchema),\n currency: currencyCodeSchema,\n evaluatedAt: z.string().datetime({ offset: true }),\n buyer: buyerContextSchema,\n customerOrderCount: z.number().int().nullable(),\n cartWeight: decimalStringSchema.nullable(),\n codes: z.array(codeRefSchema),\n })\n .strict()\n\nexport type EvaluateInputPayload = z.infer<typeof evaluateInputSchema>\n\n// `POST /api/promotions/product-page` (\u00A7Cart-Facing Endpoints, rev V2): the one route in\n// this module that still addresses by SKU \u2014 \"a listing/PDP context has no cart line\n// identity to carry\".\nexport const productPageRequestSchema = z\n .object({\n skus: z.array(z.string().trim().min(1)).min(1),\n })\n .strict()\n\nexport type ProductPageRequestInput = z.infer<typeof productPageRequestSchema>\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -32,8 +32,8 @@ import {
32
32
  resolveLocationLabel
33
33
  } from "./inventoryMutationLoaders.js";
34
34
  const formSchema = z.object({
35
- confirmedQuantity: z.number().positive(),
36
- targetLocationId: z.string().uuid()
35
+ confirmedQuantity: z.number().positive("wms.backend.putaway.complete.errors.quantityPositive"),
36
+ targetLocationId: z.string().uuid("wms.backend.putaway.complete.errors.targetRequired")
37
37
  });
38
38
  function CompletePutawayTaskDialog({
39
39
  open,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/wms/components/backend/CompletePutawayTaskDialog.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { flashMutationError } from '../../lib/flashMutationError'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { ComboboxInput } from '@open-mercato/ui/backend/inputs/ComboboxInput'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport {\n buildOptimisticLockHeader,\n extractOptimisticLockConflict,\n} from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { FormField } from '@open-mercato/ui/primitives/form-field'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { KbdShortcut } from '@open-mercato/ui/primitives/kbd'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n loadBinLocationOptions,\n resolveLocationLabel,\n} from './inventoryMutationLoaders'\nimport type { WmsInventoryMutationAccess } from './useWmsInventoryMutationAccess'\n\nexport type PutawayTaskTarget = {\n id: string\n warehouseId: string\n quantity: number\n targetLocationId?: string | null\n catalogVariantId?: string | null\n sourceLocationId?: string | null\n serialNumber?: string | null\n updatedAt?: string | null\n}\n\ntype CompletePutawayTaskDialogProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n access: WmsInventoryMutationAccess\n task: PutawayTaskTarget | null\n onSuccess?: () => void\n}\n\nconst formSchema = z.object({\n confirmedQuantity: z.number().positive(),\n targetLocationId: z.string().uuid(),\n})\n\nexport function CompletePutawayTaskDialog({\n open,\n onOpenChange,\n access,\n task,\n onSuccess,\n}: CompletePutawayTaskDialogProps) {\n const t = useT()\n const queryClient = useQueryClient()\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'wms-putaway-complete',\n })\n const mutationContext = React.useMemo(\n () => ({ retryLastMutation }),\n [retryLastMutation],\n )\n\n const [confirmedQuantity, setConfirmedQuantity] = React.useState(1)\n const [targetLocationId, setTargetLocationId] = React.useState('')\n const [serialConfirmation, setSerialConfirmation] = React.useState('')\n const [fieldErrors, setFieldErrors] = React.useState<Record<string, string>>({})\n const [submitting, setSubmitting] = React.useState(false)\n const [lockUpdatedAt, setLockUpdatedAt] = React.useState<string | null>(null)\n /** Fresh If-Match after 409; ref so guarded retry reads latest without re-seed. */\n const lockUpdatedAtRef = React.useRef<string | null>(null)\n\n const closeDialog = React.useCallback(() => {\n if (submitting) return\n onOpenChange(false)\n }, [onOpenChange, submitting])\n\n const warehouseId = task?.warehouseId?.trim() || ''\n const expectedSerial = task?.serialNumber?.trim() ?? ''\n\n // Stable identity so ComboboxInput's debounced suggestion effect is not torn\n // down on every parent re-render (queue refetch / access churn) \u2014 an inline\n // arrow would recreate each render and can starve the fetch, leaving the\n // portaled list stuck on \"Loading suggestions\u2026\" with no selectable options.\n const loadTargetLocationSuggestions = React.useCallback(\n async (query?: string) => (warehouseId ? loadBinLocationOptions(warehouseId, query) : []),\n [warehouseId],\n )\n\n const resolveTargetLocationLabel = React.useCallback(\n async (value: string) => (await resolveLocationLabel(value)) ?? value,\n [],\n )\n\n React.useEffect(() => {\n if (!open || !task) return\n setConfirmedQuantity(task.quantity > 0 ? task.quantity : 1)\n setTargetLocationId(task.targetLocationId?.trim() || '')\n setSerialConfirmation('')\n setFieldErrors({})\n setSubmitting(false)\n const seed = task.updatedAt ?? null\n lockUpdatedAtRef.current = seed\n setLockUpdatedAt(seed)\n }, [open, task])\n\n const handleSubmit = React.useCallback(\n async (event?: React.FormEvent) => {\n event?.preventDefault()\n if (!task) return\n if (!access.scopeReady || !access.organizationId || !access.tenantId || !access.userId) {\n flash(\n t(\n 'wms.backend.inventory.mutations.errors.scope',\n 'Select an organization and sign in before posting inventory changes.',\n ),\n 'error',\n )\n return\n }\n\n const parsed = formSchema.safeParse({\n confirmedQuantity,\n targetLocationId: targetLocationId.trim(),\n })\n const nextErrors: Record<string, string> = {}\n if (!parsed.success) {\n for (const issue of parsed.error.issues) {\n const key = String(issue.path[0] ?? 'form')\n if (!nextErrors[key]) nextErrors[key] = t(issue.message, issue.message)\n }\n }\n if (expectedSerial && serialConfirmation.trim() !== expectedSerial) {\n nextErrors.serialConfirmation = t(\n 'wms.backend.putaway.complete.errors.serialMismatch',\n 'The scanned serial does not match this task.',\n )\n }\n if (!parsed.success || nextErrors.serialConfirmation) {\n setFieldErrors(nextErrors)\n return\n }\n\n setSubmitting(true)\n setFieldErrors({})\n const payload = {\n organizationId: access.organizationId,\n tenantId: access.tenantId,\n confirmedQuantity: parsed.data.confirmedQuantity,\n targetLocationId: parsed.data.targetLocationId,\n performedBy: access.userId,\n }\n\n try {\n let conflictHandled = false\n await runMutation({\n operation: async () => {\n const call = await apiCall<{ ok?: boolean }>(\n `/api/wms/putaway-tasks/${encodeURIComponent(task.id)}/complete`,\n {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n ...buildOptimisticLockHeader(lockUpdatedAtRef.current ?? undefined),\n },\n body: JSON.stringify(payload),\n },\n )\n if (!call.ok) {\n const refreshLockFromConflict = () => {\n const conflict = extractOptimisticLockConflict({\n status: call.status,\n body: call.result,\n })\n if (conflict?.currentUpdatedAt) {\n lockUpdatedAtRef.current = conflict.currentUpdatedAt\n setLockUpdatedAt(conflict.currentUpdatedAt)\n }\n void queryClient.invalidateQueries({ queryKey: ['wms-putaway-queue'] })\n }\n if (\n surfaceRecordConflict({ status: call.status, body: call.result }, t, {\n onRefresh: refreshLockFromConflict,\n })\n ) {\n refreshLockFromConflict()\n conflictHandled = true\n return {}\n }\n await raiseCrudError(\n call.response,\n t('wms.backend.putaway.complete.errors.submit', 'Failed to complete putaway task.'),\n )\n }\n return call.result ?? {}\n },\n context: mutationContext,\n mutationPayload: payload,\n })\n\n if (conflictHandled) return\n\n flash(t('wms.backend.putaway.complete.flash.success', 'Putaway completed'), 'success')\n await queryClient.invalidateQueries({ queryKey: ['wms-putaway-queue'] })\n closeDialog()\n onSuccess?.()\n } catch (error) {\n flashMutationError(\n error,\n t('wms.backend.putaway.complete.errors.submit', 'Failed to complete putaway task.'),\n t,\n )\n } finally {\n setSubmitting(false)\n }\n },\n [\n access,\n closeDialog,\n confirmedQuantity,\n expectedSerial,\n lockUpdatedAt,\n serialConfirmation,\n mutationContext,\n onSuccess,\n queryClient,\n runMutation,\n t,\n targetLocationId,\n task,\n ],\n )\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n closeDialog()\n return\n }\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n if (!submitting) void handleSubmit()\n }\n },\n [closeDialog, handleSubmit, submitting],\n )\n\n if (!task) return null\n\n return (\n <Dialog open={open} onOpenChange={(next) => (next ? onOpenChange(true) : closeDialog())}>\n <DialogContent className=\"max-w-lg gap-0 overflow-hidden p-0\" onKeyDown={handleKeyDown}>\n <div className=\"border-b px-6 py-4 pr-12\">\n <DialogHeader className=\"space-y-1 text-left\">\n <DialogTitle>\n {t('wms.backend.putaway.complete.dialog.title', 'Complete putaway')}\n </DialogTitle>\n <DialogDescription>\n {t(\n 'wms.backend.putaway.complete.dialog.description',\n 'Confirm quantity and destination bin to move stock from staging.',\n )}\n </DialogDescription>\n </DialogHeader>\n </div>\n\n <form onSubmit={handleSubmit} className=\"flex min-h-0 flex-1 flex-col\">\n <div className=\"flex flex-col gap-5 overflow-y-auto px-6 py-6\">\n {expectedSerial ? (\n <>\n <p className=\"text-sm text-muted-foreground\" data-testid=\"complete-putaway-expected-serial\">\n {t('wms.backend.putaway.complete.form.serial', 'Serial')}: {expectedSerial}\n </p>\n <FormField\n label={t('wms.backend.putaway.complete.form.serialConfirm', 'Confirm serial')}\n required\n disabled={submitting}\n error={fieldErrors.serialConfirmation}\n description={t(\n 'wms.backend.putaway.complete.form.serialConfirmHint',\n 'Scan or type the serial on the unit. It must match this task.',\n )}\n >\n <Input\n value={serialConfirmation}\n onChange={(event) => setSerialConfirmation(event.target.value)}\n data-testid=\"complete-putaway-serial\"\n />\n </FormField>\n </>\n ) : null}\n <FormField\n label={t('wms.backend.putaway.complete.form.quantity', 'Confirmed quantity')}\n required\n error={fieldErrors.confirmedQuantity}\n >\n <Input\n type=\"number\"\n min={0.0001}\n step=\"any\"\n value={confirmedQuantity}\n onChange={(event) => setConfirmedQuantity(Number(event.target.value))}\n disabled={submitting}\n data-testid=\"complete-putaway-qty\"\n />\n </FormField>\n\n <FormField\n label={t('wms.backend.putaway.complete.form.target', 'Target location')}\n required\n error={fieldErrors.targetLocationId}\n >\n <ComboboxInput\n value={targetLocationId}\n onChange={setTargetLocationId}\n loadSuggestions={loadTargetLocationSuggestions}\n resolveLabel={resolveTargetLocationLabel}\n placeholder={t(\n 'wms.backend.putaway.complete.form.targetPlaceholder',\n 'Select destination bin',\n )}\n allowCustomValues={false}\n disabled={submitting}\n />\n </FormField>\n </div>\n\n <DialogFooter bordered={false} className=\"border-t px-6 py-4 sm:justify-between\">\n <span className=\"hidden text-xs text-muted-foreground sm:inline\">\n <KbdShortcut keys={['Ctrl/\u2318', 'Enter']} />{' '}\n {t('wms.backend.putaway.complete.dialog.shortcutHint', 'to complete')}\n </span>\n <div className=\"flex gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={closeDialog} disabled={submitting}>\n {t('wms.backend.putaway.complete.dialog.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"submit\"\n disabled={submitting || access.loading || !access.scopeReady}\n data-testid=\"complete-putaway-submit\"\n >\n {t('wms.backend.putaway.complete.dialog.submit', 'Complete putaway')}\n </Button>\n </div>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { flashMutationError } from '../../lib/flashMutationError'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { ComboboxInput } from '@open-mercato/ui/backend/inputs/ComboboxInput'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport {\n buildOptimisticLockHeader,\n extractOptimisticLockConflict,\n} from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { FormField } from '@open-mercato/ui/primitives/form-field'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { KbdShortcut } from '@open-mercato/ui/primitives/kbd'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n loadBinLocationOptions,\n resolveLocationLabel,\n} from './inventoryMutationLoaders'\nimport type { WmsInventoryMutationAccess } from './useWmsInventoryMutationAccess'\n\nexport type PutawayTaskTarget = {\n id: string\n warehouseId: string\n quantity: number\n targetLocationId?: string | null\n catalogVariantId?: string | null\n sourceLocationId?: string | null\n serialNumber?: string | null\n updatedAt?: string | null\n}\n\ntype CompletePutawayTaskDialogProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n access: WmsInventoryMutationAccess\n task: PutawayTaskTarget | null\n onSuccess?: () => void\n}\n\nconst formSchema = z.object({\n confirmedQuantity: z.number().positive('wms.backend.putaway.complete.errors.quantityPositive'),\n targetLocationId: z.string().uuid('wms.backend.putaway.complete.errors.targetRequired'),\n})\n\nexport function CompletePutawayTaskDialog({\n open,\n onOpenChange,\n access,\n task,\n onSuccess,\n}: CompletePutawayTaskDialogProps) {\n const t = useT()\n const queryClient = useQueryClient()\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'wms-putaway-complete',\n })\n const mutationContext = React.useMemo(\n () => ({ retryLastMutation }),\n [retryLastMutation],\n )\n\n const [confirmedQuantity, setConfirmedQuantity] = React.useState(1)\n const [targetLocationId, setTargetLocationId] = React.useState('')\n const [serialConfirmation, setSerialConfirmation] = React.useState('')\n const [fieldErrors, setFieldErrors] = React.useState<Record<string, string>>({})\n const [submitting, setSubmitting] = React.useState(false)\n const [lockUpdatedAt, setLockUpdatedAt] = React.useState<string | null>(null)\n /** Fresh If-Match after 409; ref so guarded retry reads latest without re-seed. */\n const lockUpdatedAtRef = React.useRef<string | null>(null)\n\n const closeDialog = React.useCallback(() => {\n if (submitting) return\n onOpenChange(false)\n }, [onOpenChange, submitting])\n\n const warehouseId = task?.warehouseId?.trim() || ''\n const expectedSerial = task?.serialNumber?.trim() ?? ''\n\n // Stable identity so ComboboxInput's debounced suggestion effect is not torn\n // down on every parent re-render (queue refetch / access churn) \u2014 an inline\n // arrow would recreate each render and can starve the fetch, leaving the\n // portaled list stuck on \"Loading suggestions\u2026\" with no selectable options.\n const loadTargetLocationSuggestions = React.useCallback(\n async (query?: string) => (warehouseId ? loadBinLocationOptions(warehouseId, query) : []),\n [warehouseId],\n )\n\n const resolveTargetLocationLabel = React.useCallback(\n async (value: string) => (await resolveLocationLabel(value)) ?? value,\n [],\n )\n\n React.useEffect(() => {\n if (!open || !task) return\n setConfirmedQuantity(task.quantity > 0 ? task.quantity : 1)\n setTargetLocationId(task.targetLocationId?.trim() || '')\n setSerialConfirmation('')\n setFieldErrors({})\n setSubmitting(false)\n const seed = task.updatedAt ?? null\n lockUpdatedAtRef.current = seed\n setLockUpdatedAt(seed)\n }, [open, task])\n\n const handleSubmit = React.useCallback(\n async (event?: React.FormEvent) => {\n event?.preventDefault()\n if (!task) return\n if (!access.scopeReady || !access.organizationId || !access.tenantId || !access.userId) {\n flash(\n t(\n 'wms.backend.inventory.mutations.errors.scope',\n 'Select an organization and sign in before posting inventory changes.',\n ),\n 'error',\n )\n return\n }\n\n const parsed = formSchema.safeParse({\n confirmedQuantity,\n targetLocationId: targetLocationId.trim(),\n })\n const nextErrors: Record<string, string> = {}\n if (!parsed.success) {\n for (const issue of parsed.error.issues) {\n const key = String(issue.path[0] ?? 'form')\n if (!nextErrors[key]) nextErrors[key] = t(issue.message, issue.message)\n }\n }\n if (expectedSerial && serialConfirmation.trim() !== expectedSerial) {\n nextErrors.serialConfirmation = t(\n 'wms.backend.putaway.complete.errors.serialMismatch',\n 'The scanned serial does not match this task.',\n )\n }\n if (!parsed.success || nextErrors.serialConfirmation) {\n setFieldErrors(nextErrors)\n return\n }\n\n setSubmitting(true)\n setFieldErrors({})\n const payload = {\n organizationId: access.organizationId,\n tenantId: access.tenantId,\n confirmedQuantity: parsed.data.confirmedQuantity,\n targetLocationId: parsed.data.targetLocationId,\n performedBy: access.userId,\n }\n\n try {\n let conflictHandled = false\n await runMutation({\n operation: async () => {\n const call = await apiCall<{ ok?: boolean }>(\n `/api/wms/putaway-tasks/${encodeURIComponent(task.id)}/complete`,\n {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n ...buildOptimisticLockHeader(lockUpdatedAtRef.current ?? undefined),\n },\n body: JSON.stringify(payload),\n },\n )\n if (!call.ok) {\n const refreshLockFromConflict = () => {\n const conflict = extractOptimisticLockConflict({\n status: call.status,\n body: call.result,\n })\n if (conflict?.currentUpdatedAt) {\n lockUpdatedAtRef.current = conflict.currentUpdatedAt\n setLockUpdatedAt(conflict.currentUpdatedAt)\n }\n void queryClient.invalidateQueries({ queryKey: ['wms-putaway-queue'] })\n }\n if (\n surfaceRecordConflict({ status: call.status, body: call.result }, t, {\n onRefresh: refreshLockFromConflict,\n })\n ) {\n refreshLockFromConflict()\n conflictHandled = true\n return {}\n }\n await raiseCrudError(\n call.response,\n t('wms.backend.putaway.complete.errors.submit', 'Failed to complete putaway task.'),\n )\n }\n return call.result ?? {}\n },\n context: mutationContext,\n mutationPayload: payload,\n })\n\n if (conflictHandled) return\n\n flash(t('wms.backend.putaway.complete.flash.success', 'Putaway completed'), 'success')\n await queryClient.invalidateQueries({ queryKey: ['wms-putaway-queue'] })\n closeDialog()\n onSuccess?.()\n } catch (error) {\n flashMutationError(\n error,\n t('wms.backend.putaway.complete.errors.submit', 'Failed to complete putaway task.'),\n t,\n )\n } finally {\n setSubmitting(false)\n }\n },\n [\n access,\n closeDialog,\n confirmedQuantity,\n expectedSerial,\n lockUpdatedAt,\n serialConfirmation,\n mutationContext,\n onSuccess,\n queryClient,\n runMutation,\n t,\n targetLocationId,\n task,\n ],\n )\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n closeDialog()\n return\n }\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n if (!submitting) void handleSubmit()\n }\n },\n [closeDialog, handleSubmit, submitting],\n )\n\n if (!task) return null\n\n return (\n <Dialog open={open} onOpenChange={(next) => (next ? onOpenChange(true) : closeDialog())}>\n <DialogContent className=\"max-w-lg gap-0 overflow-hidden p-0\" onKeyDown={handleKeyDown}>\n <div className=\"border-b px-6 py-4 pr-12\">\n <DialogHeader className=\"space-y-1 text-left\">\n <DialogTitle>\n {t('wms.backend.putaway.complete.dialog.title', 'Complete putaway')}\n </DialogTitle>\n <DialogDescription>\n {t(\n 'wms.backend.putaway.complete.dialog.description',\n 'Confirm quantity and destination bin to move stock from staging.',\n )}\n </DialogDescription>\n </DialogHeader>\n </div>\n\n <form onSubmit={handleSubmit} className=\"flex min-h-0 flex-1 flex-col\">\n <div className=\"flex flex-col gap-5 overflow-y-auto px-6 py-6\">\n {expectedSerial ? (\n <>\n <p className=\"text-sm text-muted-foreground\" data-testid=\"complete-putaway-expected-serial\">\n {t('wms.backend.putaway.complete.form.serial', 'Serial')}: {expectedSerial}\n </p>\n <FormField\n label={t('wms.backend.putaway.complete.form.serialConfirm', 'Confirm serial')}\n required\n disabled={submitting}\n error={fieldErrors.serialConfirmation}\n description={t(\n 'wms.backend.putaway.complete.form.serialConfirmHint',\n 'Scan or type the serial on the unit. It must match this task.',\n )}\n >\n <Input\n value={serialConfirmation}\n onChange={(event) => setSerialConfirmation(event.target.value)}\n data-testid=\"complete-putaway-serial\"\n />\n </FormField>\n </>\n ) : null}\n <FormField\n label={t('wms.backend.putaway.complete.form.quantity', 'Confirmed quantity')}\n required\n error={fieldErrors.confirmedQuantity}\n >\n <Input\n type=\"number\"\n min={0.0001}\n step=\"any\"\n value={confirmedQuantity}\n onChange={(event) => setConfirmedQuantity(Number(event.target.value))}\n disabled={submitting}\n data-testid=\"complete-putaway-qty\"\n />\n </FormField>\n\n <FormField\n label={t('wms.backend.putaway.complete.form.target', 'Target location')}\n required\n error={fieldErrors.targetLocationId}\n >\n <ComboboxInput\n value={targetLocationId}\n onChange={setTargetLocationId}\n loadSuggestions={loadTargetLocationSuggestions}\n resolveLabel={resolveTargetLocationLabel}\n placeholder={t(\n 'wms.backend.putaway.complete.form.targetPlaceholder',\n 'Select destination bin',\n )}\n allowCustomValues={false}\n disabled={submitting}\n />\n </FormField>\n </div>\n\n <DialogFooter bordered={false} className=\"border-t px-6 py-4 sm:justify-between\">\n <span className=\"hidden text-xs text-muted-foreground sm:inline\">\n <KbdShortcut keys={['Ctrl/\u2318', 'Enter']} />{' '}\n {t('wms.backend.putaway.complete.dialog.shortcutHint', 'to complete')}\n </span>\n <div className=\"flex gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={closeDialog} disabled={submitting}>\n {t('wms.backend.putaway.complete.dialog.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"submit\"\n disabled={submitting || access.loading || !access.scopeReady}\n data-testid=\"complete-putaway-submit\"\n >\n {t('wms.backend.putaway.complete.dialog.submit', 'Complete putaway')}\n </Button>\n </div>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -32,17 +32,17 @@ import {
32
32
  resolveWarehouseLabel
33
33
  } from "./inventoryMutationLoaders.js";
34
34
  const formSchema = z.object({
35
- warehouseId: z.string().uuid(),
36
- expectedAt: z.string().min(1),
35
+ warehouseId: z.string().uuid("wms.backend.asns.create.errors.warehouseRequired"),
36
+ expectedAt: z.string().min(1, "wms.backend.asns.create.errors.expectedAtRequired"),
37
37
  referenceNumber: z.string().trim().max(120).optional(),
38
38
  notes: z.string().trim().max(2e3).optional(),
39
39
  lines: z.array(
40
40
  z.object({
41
- catalogVariantId: z.string().uuid(),
42
- expectedQty: z.number().positive(),
41
+ catalogVariantId: z.string().uuid("wms.backend.asns.create.errors.variantRequired"),
42
+ expectedQty: z.number().positive("wms.backend.asns.create.errors.quantityPositive"),
43
43
  targetStagingLocationId: z.string().uuid().optional()
44
44
  })
45
- ).min(1)
45
+ ).min(1, "wms.backend.asns.create.errors.linesRequired")
46
46
  });
47
47
  const EMPTY_LINE = {
48
48
  catalogVariantId: "",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/wms/components/backend/CreateAsnDialog.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { flashMutationError } from '../../lib/flashMutationError'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { ComboboxInput } from '@open-mercato/ui/backend/inputs/ComboboxInput'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { FormField } from '@open-mercato/ui/primitives/form-field'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { KbdShortcut } from '@open-mercato/ui/primitives/kbd'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n loadCatalogVariantOptions,\n loadStagingLocationOptions,\n loadWarehouseOptions,\n resolveCatalogVariantLabel,\n resolveLocationLabel,\n resolveWarehouseLabel,\n} from './inventoryMutationLoaders'\nimport type { WmsInventoryMutationAccess } from './useWmsInventoryMutationAccess'\n\ntype CreateAsnDialogProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n access: WmsInventoryMutationAccess\n onCreated?: (asnId: string) => void\n}\n\ntype LineDraft = {\n catalogVariantId: string\n expectedQty: number\n targetStagingLocationId: string\n}\n\nconst formSchema = z.object({\n warehouseId: z.string().uuid(),\n expectedAt: z.string().min(1),\n referenceNumber: z.string().trim().max(120).optional(),\n notes: z.string().trim().max(2000).optional(),\n lines: z\n .array(\n z.object({\n catalogVariantId: z.string().uuid(),\n expectedQty: z.number().positive(),\n targetStagingLocationId: z.string().uuid().optional(),\n }),\n )\n .min(1),\n})\n\nconst EMPTY_LINE: LineDraft = {\n catalogVariantId: '',\n expectedQty: 1,\n targetStagingLocationId: '',\n}\n\nexport function CreateAsnDialog({ open, onOpenChange, access, onCreated }: CreateAsnDialogProps) {\n const t = useT()\n const queryClient = useQueryClient()\n const formRef = React.useRef<HTMLFormElement>(null)\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'wms-asn-create',\n })\n const mutationContext = React.useMemo(\n () => ({ retryLastMutation }),\n [retryLastMutation],\n )\n\n const [warehouseId, setWarehouseId] = React.useState('')\n const [expectedAt, setExpectedAt] = React.useState('')\n const [referenceNumber, setReferenceNumber] = React.useState('')\n const [notes, setNotes] = React.useState('')\n const [lines, setLines] = React.useState<LineDraft[]>([{ ...EMPTY_LINE }])\n const [fieldErrors, setFieldErrors] = React.useState<Record<string, string>>({})\n const [submitting, setSubmitting] = React.useState(false)\n const [labelCache, setLabelCache] = React.useState<Record<string, string>>({})\n\n const closeDialog = React.useCallback(() => {\n if (submitting) return\n onOpenChange(false)\n }, [onOpenChange, submitting])\n\n React.useEffect(() => {\n if (!open) return\n setWarehouseId('')\n setExpectedAt(new Date().toISOString().slice(0, 10))\n setReferenceNumber('')\n setNotes('')\n setLines([{ ...EMPTY_LINE }])\n setFieldErrors({})\n setSubmitting(false)\n }, [open])\n\n const resolveOptionLabel = React.useCallback(\n async (value: string) => {\n if (labelCache[value]) return labelCache[value]\n const [warehouse, variant, location] = await Promise.all([\n resolveWarehouseLabel(value),\n resolveCatalogVariantLabel(value),\n resolveLocationLabel(value),\n ])\n const label = warehouse || variant || location\n if (label) setLabelCache((prev) => ({ ...prev, [value]: label }))\n return label ?? value\n },\n [labelCache],\n )\n\n const handleSubmit = React.useCallback(\n async (event?: React.FormEvent) => {\n event?.preventDefault()\n if (!access.scopeReady || !access.organizationId || !access.tenantId) {\n flash(\n t(\n 'wms.backend.inventory.mutations.errors.scope',\n 'Select an organization and sign in before posting inventory changes.',\n ),\n 'error',\n )\n return\n }\n\n const parsed = formSchema.safeParse({\n warehouseId,\n expectedAt,\n referenceNumber: referenceNumber.trim() || undefined,\n notes: notes.trim() || undefined,\n lines: lines.map((line) => ({\n catalogVariantId: line.catalogVariantId,\n expectedQty: line.expectedQty,\n targetStagingLocationId: line.targetStagingLocationId.trim() || undefined,\n })),\n })\n if (!parsed.success) {\n const nextErrors: Record<string, string> = {}\n for (const issue of parsed.error.issues) {\n const key = issue.path.join('.') || 'form'\n if (!nextErrors[key]) {\n nextErrors[key] = t(issue.message, issue.message)\n }\n }\n setFieldErrors(nextErrors)\n return\n }\n\n setSubmitting(true)\n setFieldErrors({})\n const payload = {\n organizationId: access.organizationId,\n tenantId: access.tenantId,\n warehouseId: parsed.data.warehouseId,\n expectedAt: parsed.data.expectedAt,\n referenceNumber: parsed.data.referenceNumber ?? null,\n notes: parsed.data.notes ?? null,\n status: 'in_transit' as const,\n lines: parsed.data.lines,\n }\n\n try {\n const result = await runMutation({\n operation: async () => {\n const call = await apiCall<{ id?: string }>(\n '/api/wms/asns',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n )\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('wms.backend.asns.create.errors.submit', 'Failed to create ASN.'),\n )\n }\n return call.result ?? {}\n },\n context: mutationContext,\n mutationPayload: payload,\n })\n\n const asnId = typeof result?.id === 'string' ? result.id : null\n flash(t('wms.backend.asns.create.flash.success', 'ASN created'), 'success')\n await queryClient.invalidateQueries({ queryKey: ['wms-asns-list'] })\n closeDialog()\n if (asnId) onCreated?.(asnId)\n } catch (error) {\n flashMutationError(error, t('wms.backend.asns.create.errors.submit', 'Failed to create ASN.'), t)\n } finally {\n setSubmitting(false)\n }\n },\n [\n access,\n closeDialog,\n expectedAt,\n lines,\n mutationContext,\n notes,\n onCreated,\n queryClient,\n referenceNumber,\n runMutation,\n t,\n warehouseId,\n ],\n )\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n closeDialog()\n return\n }\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n if (!submitting) void handleSubmit()\n }\n },\n [closeDialog, handleSubmit, submitting],\n )\n\n return (\n <Dialog open={open} onOpenChange={(next) => (next ? onOpenChange(true) : closeDialog())}>\n <DialogContent className=\"max-w-xl gap-0 overflow-hidden p-0\" onKeyDown={handleKeyDown}>\n <div className=\"border-b px-6 py-4 pr-12\">\n <DialogHeader className=\"space-y-1 text-left\">\n <DialogTitle>{t('wms.backend.asns.create.dialog.title', 'Create ASN')}</DialogTitle>\n <DialogDescription>\n {t(\n 'wms.backend.asns.create.dialog.description',\n 'Create an expected inbound shipment with receiving lines.',\n )}\n </DialogDescription>\n </DialogHeader>\n </div>\n\n <form ref={formRef} onSubmit={handleSubmit} className=\"flex min-h-0 flex-1 flex-col\">\n <div className=\"flex flex-col gap-5 overflow-y-auto px-6 py-6\">\n <FormField\n label={t('wms.backend.asns.create.form.warehouse', 'Warehouse')}\n required\n error={fieldErrors.warehouseId}\n >\n <ComboboxInput\n value={warehouseId}\n onChange={setWarehouseId}\n loadSuggestions={async (query) => loadWarehouseOptions(query)}\n resolveLabel={resolveOptionLabel}\n placeholder={t('wms.backend.asns.create.form.warehousePlaceholder', 'Select warehouse')}\n allowCustomValues={false}\n disabled={submitting}\n />\n </FormField>\n\n <div className=\"grid gap-5 sm:grid-cols-2\">\n <FormField\n label={t('wms.backend.asns.create.form.expectedAt', 'Expected date')}\n required\n error={fieldErrors.expectedAt}\n >\n <Input\n type=\"date\"\n value={expectedAt}\n onChange={(event) => setExpectedAt(event.target.value)}\n disabled={submitting}\n />\n </FormField>\n <FormField\n label={t('wms.backend.asns.create.form.referenceNumber', 'Reference')}\n error={fieldErrors.referenceNumber}\n >\n <Input\n value={referenceNumber}\n onChange={(event) => setReferenceNumber(event.target.value)}\n disabled={submitting}\n placeholder={t('wms.backend.asns.create.form.referencePlaceholder', 'PO / ASN ref')}\n />\n </FormField>\n </div>\n\n <FormField label={t('wms.backend.asns.create.form.notes', 'Notes')} error={fieldErrors.notes}>\n <Textarea\n value={notes}\n onChange={(event) => setNotes(event.target.value)}\n disabled={submitting}\n rows={2}\n />\n </FormField>\n\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-2\">\n <h3 className=\"text-sm font-medium\">\n {t('wms.backend.asns.create.form.lines', 'Expected lines')}\n </h3>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={submitting}\n onClick={() => setLines((current) => [...current, { ...EMPTY_LINE }])}\n >\n {t('wms.backend.asns.create.form.addLine', 'Add line')}\n </Button>\n </div>\n {lines.map((line, index) => (\n <div key={`line-${index}`} className=\"grid gap-3 rounded-md border p-3 sm:grid-cols-3\">\n <FormField\n label={t('wms.backend.asns.create.form.variant', 'Variant')}\n required\n error={fieldErrors[`lines.${index}.catalogVariantId`]}\n className=\"sm:col-span-2\"\n >\n <ComboboxInput\n value={line.catalogVariantId}\n onChange={(next) => {\n setLines((current) =>\n current.map((item, itemIndex) =>\n itemIndex === index ? { ...item, catalogVariantId: next.trim() } : item,\n ),\n )\n }}\n loadSuggestions={async (query) => loadCatalogVariantOptions(query)}\n resolveLabel={resolveOptionLabel}\n placeholder={t(\n 'wms.backend.asns.create.form.variantPlaceholder',\n 'Search variant or SKU',\n )}\n allowCustomValues={false}\n disabled={submitting}\n />\n </FormField>\n <FormField\n label={t('wms.backend.asns.create.form.expectedQty', 'Expected qty')}\n required\n error={fieldErrors[`lines.${index}.expectedQty`]}\n >\n <Input\n type=\"number\"\n min={0.0001}\n step=\"any\"\n value={line.expectedQty}\n onChange={(event) => {\n const next = Number(event.target.value)\n setLines((current) =>\n current.map((item, itemIndex) =>\n itemIndex === index\n ? { ...item, expectedQty: Number.isFinite(next) ? next : 0 }\n : item,\n ),\n )\n }}\n disabled={submitting}\n />\n </FormField>\n <FormField\n label={t('wms.backend.asns.create.form.staging', 'Staging location')}\n error={fieldErrors[`lines.${index}.targetStagingLocationId`]}\n className=\"sm:col-span-3\"\n >\n <ComboboxInput\n value={line.targetStagingLocationId}\n onChange={(next) => {\n setLines((current) =>\n current.map((item, itemIndex) =>\n itemIndex === index\n ? { ...item, targetStagingLocationId: next.trim() }\n : item,\n ),\n )\n }}\n loadSuggestions={async (query) =>\n warehouseId ? loadStagingLocationOptions(warehouseId, query) : []\n }\n resolveLabel={resolveOptionLabel}\n placeholder={t(\n 'wms.backend.asns.create.form.stagingPlaceholder',\n 'Optional staging/dock',\n )}\n allowCustomValues={false}\n disabled={submitting || !warehouseId}\n />\n </FormField>\n </div>\n ))}\n {fieldErrors.lines ? (\n <p className=\"text-sm text-status-error-text\">{fieldErrors.lines}</p>\n ) : null}\n </div>\n </div>\n\n <DialogFooter bordered={false} className=\"border-t px-6 py-4 sm:justify-between\">\n <span className=\"hidden text-xs text-muted-foreground sm:inline\">\n <KbdShortcut keys={['Ctrl/\u2318', 'Enter']} />{' '}\n {t('wms.backend.asns.create.dialog.shortcutHint', 'to save')}\n </span>\n <div className=\"flex gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={closeDialog} disabled={submitting}>\n {t('wms.backend.asns.create.dialog.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={submitting} data-testid=\"create-asn-submit\">\n {t('wms.backend.asns.create.dialog.submit', 'Create ASN')}\n </Button>\n </div>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
- "mappings": ";AAmPU,SACE,KADF;AAjPV,YAAY,WAAW;AACvB,SAAS,SAAS;AAClB,SAAS,sBAAsB;AAC/B,SAAS,aAAa;AACtB,SAAS,0BAA0B;AACnC,SAAS,0BAA0B;AACnC,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AACxB,SAAS,sBAAsB;AAC/B,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,iBAAiB;AAC1B,SAAS,aAAa;AACtB,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AACzB,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAgBP,MAAM,aAAa,EAAE,OAAO;AAAA,EAC1B,aAAa,EAAE,OAAO,EAAE,KAAK;AAAA,EAC7B,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC5B,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACrD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,GAAI,EAAE,SAAS;AAAA,EAC5C,OAAO,EACJ;AAAA,IACC,EAAE,OAAO;AAAA,MACP,kBAAkB,EAAE,OAAO,EAAE,KAAK;AAAA,MAClC,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,MACjC,yBAAyB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,IACtD,CAAC;AAAA,EACH,EACC,IAAI,CAAC;AACV,CAAC;AAED,MAAM,aAAwB;AAAA,EAC5B,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,yBAAyB;AAC3B;AAEO,SAAS,gBAAgB,EAAE,MAAM,cAAc,QAAQ,UAAU,GAAyB;AAC/F,QAAM,IAAI,KAAK;AACf,QAAM,cAAc,eAAe;AACnC,QAAM,UAAU,MAAM,OAAwB,IAAI;AAClD,QAAM,EAAE,aAAa,kBAAkB,IAAI,mBAExC;AAAA,IACD,WAAW;AAAA,EACb,CAAC;AACD,QAAM,kBAAkB,MAAM;AAAA,IAC5B,OAAO,EAAE,kBAAkB;AAAA,IAC3B,CAAC,iBAAiB;AAAA,EACpB;AAEA,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,EAAE;AACvD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,EAAE;AACrD,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,EAAE;AAC/D,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,EAAE;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAsB,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC;AACzE,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAiC,CAAC,CAAC;AAC/E,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AACxD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAiC,CAAC,CAAC;AAE7E,QAAM,cAAc,MAAM,YAAY,MAAM;AAC1C,QAAI,WAAY;AAChB,iBAAa,KAAK;AAAA,EACpB,GAAG,CAAC,cAAc,UAAU,CAAC;AAE7B,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,KAAM;AACX,mBAAe,EAAE;AACjB,mBAAc,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AACnD,uBAAmB,EAAE;AACrB,aAAS,EAAE;AACX,aAAS,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC;AAC5B,mBAAe,CAAC,CAAC;AACjB,kBAAc,KAAK;AAAA,EACrB,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,qBAAqB,MAAM;AAAA,IAC/B,OAAO,UAAkB;AACvB,UAAI,WAAW,KAAK,EAAG,QAAO,WAAW,KAAK;AAC9C,YAAM,CAAC,WAAW,SAAS,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,QACvD,sBAAsB,KAAK;AAAA,QAC3B,2BAA2B,KAAK;AAAA,QAChC,qBAAqB,KAAK;AAAA,MAC5B,CAAC;AACD,YAAM,QAAQ,aAAa,WAAW;AACtC,UAAI,MAAO,eAAc,CAAC,UAAU,EAAE,GAAG,MAAM,CAAC,KAAK,GAAG,MAAM,EAAE;AAChE,aAAO,SAAS;AAAA,IAClB;AAAA,IACA,CAAC,UAAU;AAAA,EACb;AAEA,QAAM,eAAe,MAAM;AAAA,IACzB,OAAO,UAA4B;AACjC,aAAO,eAAe;AACtB,UAAI,CAAC,OAAO,cAAc,CAAC,OAAO,kBAAkB,CAAC,OAAO,UAAU;AACpE;AAAA,UACE;AAAA,YACE;AAAA,YACA;AAAA,UACF;AAAA,UACA;AAAA,QACF;AACA;AAAA,MACF;AAEA,YAAM,SAAS,WAAW,UAAU;AAAA,QAClC;AAAA,QACA;AAAA,QACA,iBAAiB,gBAAgB,KAAK,KAAK;AAAA,QAC3C,OAAO,MAAM,KAAK,KAAK;AAAA,QACvB,OAAO,MAAM,IAAI,CAAC,UAAU;AAAA,UAC1B,kBAAkB,KAAK;AAAA,UACvB,aAAa,KAAK;AAAA,UAClB,yBAAyB,KAAK,wBAAwB,KAAK,KAAK;AAAA,QAClE,EAAE;AAAA,MACJ,CAAC;AACD,UAAI,CAAC,OAAO,SAAS;AACnB,cAAM,aAAqC,CAAC;AAC5C,mBAAW,SAAS,OAAO,MAAM,QAAQ;AACvC,gBAAM,MAAM,MAAM,KAAK,KAAK,GAAG,KAAK;AACpC,cAAI,CAAC,WAAW,GAAG,GAAG;AACpB,uBAAW,GAAG,IAAI,EAAE,MAAM,SAAS,MAAM,OAAO;AAAA,UAClD;AAAA,QACF;AACA,uBAAe,UAAU;AACzB;AAAA,MACF;AAEA,oBAAc,IAAI;AAClB,qBAAe,CAAC,CAAC;AACjB,YAAM,UAAU;AAAA,QACd,gBAAgB,OAAO;AAAA,QACvB,UAAU,OAAO;AAAA,QACjB,aAAa,OAAO,KAAK;AAAA,QACzB,YAAY,OAAO,KAAK;AAAA,QACxB,iBAAiB,OAAO,KAAK,mBAAmB;AAAA,QAChD,OAAO,OAAO,KAAK,SAAS;AAAA,QAC5B,QAAQ;AAAA,QACR,OAAO,OAAO,KAAK;AAAA,MACrB;AAEA,UAAI;AACF,cAAM,SAAS,MAAM,YAAY;AAAA,UAC/B,WAAW,YAAY;AACrB,kBAAM,OAAO,MAAM;AAAA,cACjB;AAAA,cACA;AAAA,gBACE,QAAQ;AAAA,gBACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,gBAC9C,MAAM,KAAK,UAAU,OAAO;AAAA,cAC9B;AAAA,YACF;AACA,gBAAI,CAAC,KAAK,IAAI;AACZ,oBAAM;AAAA,gBACJ,KAAK;AAAA,gBACL,EAAE,yCAAyC,uBAAuB;AAAA,cACpE;AAAA,YACF;AACA,mBAAO,KAAK,UAAU,CAAC;AAAA,UACzB;AAAA,UACA,SAAS;AAAA,UACT,iBAAiB;AAAA,QACnB,CAAC;AAED,cAAM,QAAQ,OAAO,QAAQ,OAAO,WAAW,OAAO,KAAK;AAC3D,cAAM,EAAE,yCAAyC,aAAa,GAAG,SAAS;AAC1E,cAAM,YAAY,kBAAkB,EAAE,UAAU,CAAC,eAAe,EAAE,CAAC;AACnE,oBAAY;AACZ,YAAI,MAAO,aAAY,KAAK;AAAA,MAC9B,SAAS,OAAO;AACd,2BAAmB,OAAO,EAAE,yCAAyC,uBAAuB,GAAG,CAAC;AAAA,MAClG,UAAE;AACA,sBAAc,KAAK;AAAA,MACrB;AAAA,IACF;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,gBAAgB,MAAM;AAAA,IAC1B,CAAC,UAA+C;AAC9C,UAAI,MAAM,QAAQ,UAAU;AAC1B,cAAM,eAAe;AACrB,oBAAY;AACZ;AAAA,MACF;AACA,WAAK,MAAM,WAAW,MAAM,YAAY,MAAM,QAAQ,SAAS;AAC7D,cAAM,eAAe;AACrB,YAAI,CAAC,WAAY,MAAK,aAAa;AAAA,MACrC;AAAA,IACF;AAAA,IACA,CAAC,aAAa,cAAc,UAAU;AAAA,EACxC;AAEA,SACE,oBAAC,UAAO,MAAY,cAAc,CAAC,SAAU,OAAO,aAAa,IAAI,IAAI,YAAY,GACnF,+BAAC,iBAAc,WAAU,sCAAqC,WAAW,eACvE;AAAA,wBAAC,SAAI,WAAU,4BACb,+BAAC,gBAAa,WAAU,uBACtB;AAAA,0BAAC,eAAa,YAAE,wCAAwC,YAAY,GAAE;AAAA,MACtE,oBAAC,qBACE;AAAA,QACC;AAAA,QACA;AAAA,MACF,GACF;AAAA,OACF,GACF;AAAA,IAEA,qBAAC,UAAK,KAAK,SAAS,UAAU,cAAc,WAAU,gCACpD;AAAA,2BAAC,SAAI,WAAU,iDACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,EAAE,0CAA0C,WAAW;AAAA,YAC9D,UAAQ;AAAA,YACR,OAAO,YAAY;AAAA,YAEnB;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO;AAAA,gBACP,UAAU;AAAA,gBACV,iBAAiB,OAAO,UAAU,qBAAqB,KAAK;AAAA,gBAC5D,cAAc;AAAA,gBACd,aAAa,EAAE,qDAAqD,kBAAkB;AAAA,gBACtF,mBAAmB;AAAA,gBACnB,UAAU;AAAA;AAAA,YACZ;AAAA;AAAA,QACF;AAAA,QAEA,qBAAC,SAAI,WAAU,6BACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,EAAE,2CAA2C,eAAe;AAAA,cACnE,UAAQ;AAAA,cACR,OAAO,YAAY;AAAA,cAEnB;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,OAAO;AAAA,kBACP,UAAU,CAAC,UAAU,cAAc,MAAM,OAAO,KAAK;AAAA,kBACrD,UAAU;AAAA;AAAA,cACZ;AAAA;AAAA,UACF;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,EAAE,gDAAgD,WAAW;AAAA,cACpE,OAAO,YAAY;AAAA,cAEnB;AAAA,gBAAC;AAAA;AAAA,kBACC,OAAO;AAAA,kBACP,UAAU,CAAC,UAAU,mBAAmB,MAAM,OAAO,KAAK;AAAA,kBAC1D,UAAU;AAAA,kBACV,aAAa,EAAE,qDAAqD,cAAc;AAAA;AAAA,cACpF;AAAA;AAAA,UACF;AAAA,WACF;AAAA,QAEA,oBAAC,aAAU,OAAO,EAAE,sCAAsC,OAAO,GAAG,OAAO,YAAY,OACrF;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,YACP,UAAU,CAAC,UAAU,SAAS,MAAM,OAAO,KAAK;AAAA,YAChD,UAAU;AAAA,YACV,MAAM;AAAA;AAAA,QACR,GACF;AAAA,QAEA,qBAAC,SAAI,WAAU,aACb;AAAA,+BAAC,SAAI,WAAU,2CACb;AAAA,gCAAC,QAAG,WAAU,uBACX,YAAE,sCAAsC,gBAAgB,GAC3D;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,UAAU;AAAA,gBACV,SAAS,MAAM,SAAS,CAAC,YAAY,CAAC,GAAG,SAAS,EAAE,GAAG,WAAW,CAAC,CAAC;AAAA,gBAEnE,YAAE,wCAAwC,UAAU;AAAA;AAAA,YACvD;AAAA,aACF;AAAA,UACC,MAAM,IAAI,CAAC,MAAM,UAChB,qBAAC,SAA0B,WAAU,mDACnC;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO,EAAE,wCAAwC,SAAS;AAAA,gBAC1D,UAAQ;AAAA,gBACR,OAAO,YAAY,SAAS,KAAK,mBAAmB;AAAA,gBACpD,WAAU;AAAA,gBAEV;AAAA,kBAAC;AAAA;AAAA,oBACC,OAAO,KAAK;AAAA,oBACZ,UAAU,CAAC,SAAS;AAClB;AAAA,wBAAS,CAAC,YACR,QAAQ;AAAA,0BAAI,CAAC,MAAM,cACjB,cAAc,QAAQ,EAAE,GAAG,MAAM,kBAAkB,KAAK,KAAK,EAAE,IAAI;AAAA,wBACrE;AAAA,sBACF;AAAA,oBACF;AAAA,oBACA,iBAAiB,OAAO,UAAU,0BAA0B,KAAK;AAAA,oBACjE,cAAc;AAAA,oBACd,aAAa;AAAA,sBACX;AAAA,sBACA;AAAA,oBACF;AAAA,oBACA,mBAAmB;AAAA,oBACnB,UAAU;AAAA;AAAA,gBACZ;AAAA;AAAA,YACF;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO,EAAE,4CAA4C,cAAc;AAAA,gBACnE,UAAQ;AAAA,gBACR,OAAO,YAAY,SAAS,KAAK,cAAc;AAAA,gBAE/C;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,KAAK;AAAA,oBACL,MAAK;AAAA,oBACL,OAAO,KAAK;AAAA,oBACZ,UAAU,CAAC,UAAU;AACnB,4BAAM,OAAO,OAAO,MAAM,OAAO,KAAK;AACtC;AAAA,wBAAS,CAAC,YACR,QAAQ;AAAA,0BAAI,CAAC,MAAM,cACjB,cAAc,QACV,EAAE,GAAG,MAAM,aAAa,OAAO,SAAS,IAAI,IAAI,OAAO,EAAE,IACzD;AAAA,wBACN;AAAA,sBACF;AAAA,oBACF;AAAA,oBACA,UAAU;AAAA;AAAA,gBACZ;AAAA;AAAA,YACF;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO,EAAE,wCAAwC,kBAAkB;AAAA,gBACnE,OAAO,YAAY,SAAS,KAAK,0BAA0B;AAAA,gBAC3D,WAAU;AAAA,gBAEV;AAAA,kBAAC;AAAA;AAAA,oBACC,OAAO,KAAK;AAAA,oBACZ,UAAU,CAAC,SAAS;AAClB;AAAA,wBAAS,CAAC,YACR,QAAQ;AAAA,0BAAI,CAAC,MAAM,cACjB,cAAc,QACV,EAAE,GAAG,MAAM,yBAAyB,KAAK,KAAK,EAAE,IAChD;AAAA,wBACN;AAAA,sBACF;AAAA,oBACF;AAAA,oBACA,iBAAiB,OAAO,UACtB,cAAc,2BAA2B,aAAa,KAAK,IAAI,CAAC;AAAA,oBAElE,cAAc;AAAA,oBACd,aAAa;AAAA,sBACX;AAAA,sBACA;AAAA,oBACF;AAAA,oBACA,mBAAmB;AAAA,oBACnB,UAAU,cAAc,CAAC;AAAA;AAAA,gBAC3B;AAAA;AAAA,YACF;AAAA,eA5EQ,QAAQ,KAAK,EA6EvB,CACD;AAAA,UACA,YAAY,QACX,oBAAC,OAAE,WAAU,kCAAkC,sBAAY,OAAM,IAC/D;AAAA,WACN;AAAA,SACF;AAAA,MAEA,qBAAC,gBAAa,UAAU,OAAO,WAAU,yCACvC;AAAA,6BAAC,UAAK,WAAU,kDACd;AAAA,8BAAC,eAAY,MAAM,CAAC,eAAU,OAAO,GAAG;AAAA,UAAG;AAAA,UAC1C,EAAE,+CAA+C,SAAS;AAAA,WAC7D;AAAA,QACA,qBAAC,SAAI,WAAU,cACb;AAAA,8BAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,aAAa,UAAU,YACrE,YAAE,yCAAyC,QAAQ,GACtD;AAAA,UACA,oBAAC,UAAO,MAAK,UAAS,UAAU,YAAY,eAAY,qBACrD,YAAE,yCAAyC,YAAY,GAC1D;AAAA,WACF;AAAA,SACF;AAAA,OACF;AAAA,KACF,GACF;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { flashMutationError } from '../../lib/flashMutationError'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { ComboboxInput } from '@open-mercato/ui/backend/inputs/ComboboxInput'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { FormField } from '@open-mercato/ui/primitives/form-field'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { KbdShortcut } from '@open-mercato/ui/primitives/kbd'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n loadCatalogVariantOptions,\n loadStagingLocationOptions,\n loadWarehouseOptions,\n resolveCatalogVariantLabel,\n resolveLocationLabel,\n resolveWarehouseLabel,\n} from './inventoryMutationLoaders'\nimport type { WmsInventoryMutationAccess } from './useWmsInventoryMutationAccess'\n\ntype CreateAsnDialogProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n access: WmsInventoryMutationAccess\n onCreated?: (asnId: string) => void\n}\n\ntype LineDraft = {\n catalogVariantId: string\n expectedQty: number\n targetStagingLocationId: string\n}\n\nconst formSchema = z.object({\n warehouseId: z.string().uuid('wms.backend.asns.create.errors.warehouseRequired'),\n expectedAt: z.string().min(1, 'wms.backend.asns.create.errors.expectedAtRequired'),\n referenceNumber: z.string().trim().max(120).optional(),\n notes: z.string().trim().max(2000).optional(),\n lines: z\n .array(\n z.object({\n catalogVariantId: z.string().uuid('wms.backend.asns.create.errors.variantRequired'),\n expectedQty: z.number().positive('wms.backend.asns.create.errors.quantityPositive'),\n targetStagingLocationId: z.string().uuid().optional(),\n }),\n )\n .min(1, 'wms.backend.asns.create.errors.linesRequired'),\n})\n\nconst EMPTY_LINE: LineDraft = {\n catalogVariantId: '',\n expectedQty: 1,\n targetStagingLocationId: '',\n}\n\nexport function CreateAsnDialog({ open, onOpenChange, access, onCreated }: CreateAsnDialogProps) {\n const t = useT()\n const queryClient = useQueryClient()\n const formRef = React.useRef<HTMLFormElement>(null)\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'wms-asn-create',\n })\n const mutationContext = React.useMemo(\n () => ({ retryLastMutation }),\n [retryLastMutation],\n )\n\n const [warehouseId, setWarehouseId] = React.useState('')\n const [expectedAt, setExpectedAt] = React.useState('')\n const [referenceNumber, setReferenceNumber] = React.useState('')\n const [notes, setNotes] = React.useState('')\n const [lines, setLines] = React.useState<LineDraft[]>([{ ...EMPTY_LINE }])\n const [fieldErrors, setFieldErrors] = React.useState<Record<string, string>>({})\n const [submitting, setSubmitting] = React.useState(false)\n const [labelCache, setLabelCache] = React.useState<Record<string, string>>({})\n\n const closeDialog = React.useCallback(() => {\n if (submitting) return\n onOpenChange(false)\n }, [onOpenChange, submitting])\n\n React.useEffect(() => {\n if (!open) return\n setWarehouseId('')\n setExpectedAt(new Date().toISOString().slice(0, 10))\n setReferenceNumber('')\n setNotes('')\n setLines([{ ...EMPTY_LINE }])\n setFieldErrors({})\n setSubmitting(false)\n }, [open])\n\n const resolveOptionLabel = React.useCallback(\n async (value: string) => {\n if (labelCache[value]) return labelCache[value]\n const [warehouse, variant, location] = await Promise.all([\n resolveWarehouseLabel(value),\n resolveCatalogVariantLabel(value),\n resolveLocationLabel(value),\n ])\n const label = warehouse || variant || location\n if (label) setLabelCache((prev) => ({ ...prev, [value]: label }))\n return label ?? value\n },\n [labelCache],\n )\n\n const handleSubmit = React.useCallback(\n async (event?: React.FormEvent) => {\n event?.preventDefault()\n if (!access.scopeReady || !access.organizationId || !access.tenantId) {\n flash(\n t(\n 'wms.backend.inventory.mutations.errors.scope',\n 'Select an organization and sign in before posting inventory changes.',\n ),\n 'error',\n )\n return\n }\n\n const parsed = formSchema.safeParse({\n warehouseId,\n expectedAt,\n referenceNumber: referenceNumber.trim() || undefined,\n notes: notes.trim() || undefined,\n lines: lines.map((line) => ({\n catalogVariantId: line.catalogVariantId,\n expectedQty: line.expectedQty,\n targetStagingLocationId: line.targetStagingLocationId.trim() || undefined,\n })),\n })\n if (!parsed.success) {\n const nextErrors: Record<string, string> = {}\n for (const issue of parsed.error.issues) {\n const key = issue.path.join('.') || 'form'\n if (!nextErrors[key]) {\n nextErrors[key] = t(issue.message, issue.message)\n }\n }\n setFieldErrors(nextErrors)\n return\n }\n\n setSubmitting(true)\n setFieldErrors({})\n const payload = {\n organizationId: access.organizationId,\n tenantId: access.tenantId,\n warehouseId: parsed.data.warehouseId,\n expectedAt: parsed.data.expectedAt,\n referenceNumber: parsed.data.referenceNumber ?? null,\n notes: parsed.data.notes ?? null,\n status: 'in_transit' as const,\n lines: parsed.data.lines,\n }\n\n try {\n const result = await runMutation({\n operation: async () => {\n const call = await apiCall<{ id?: string }>(\n '/api/wms/asns',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n )\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('wms.backend.asns.create.errors.submit', 'Failed to create ASN.'),\n )\n }\n return call.result ?? {}\n },\n context: mutationContext,\n mutationPayload: payload,\n })\n\n const asnId = typeof result?.id === 'string' ? result.id : null\n flash(t('wms.backend.asns.create.flash.success', 'ASN created'), 'success')\n await queryClient.invalidateQueries({ queryKey: ['wms-asns-list'] })\n closeDialog()\n if (asnId) onCreated?.(asnId)\n } catch (error) {\n flashMutationError(error, t('wms.backend.asns.create.errors.submit', 'Failed to create ASN.'), t)\n } finally {\n setSubmitting(false)\n }\n },\n [\n access,\n closeDialog,\n expectedAt,\n lines,\n mutationContext,\n notes,\n onCreated,\n queryClient,\n referenceNumber,\n runMutation,\n t,\n warehouseId,\n ],\n )\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n closeDialog()\n return\n }\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n if (!submitting) void handleSubmit()\n }\n },\n [closeDialog, handleSubmit, submitting],\n )\n\n return (\n <Dialog open={open} onOpenChange={(next) => (next ? onOpenChange(true) : closeDialog())}>\n <DialogContent className=\"max-w-xl gap-0 overflow-hidden p-0\" onKeyDown={handleKeyDown}>\n <div className=\"border-b px-6 py-4 pr-12\">\n <DialogHeader className=\"space-y-1 text-left\">\n <DialogTitle>{t('wms.backend.asns.create.dialog.title', 'Create ASN')}</DialogTitle>\n <DialogDescription>\n {t(\n 'wms.backend.asns.create.dialog.description',\n 'Create an expected inbound shipment with receiving lines.',\n )}\n </DialogDescription>\n </DialogHeader>\n </div>\n\n <form ref={formRef} onSubmit={handleSubmit} className=\"flex min-h-0 flex-1 flex-col\">\n <div className=\"flex flex-col gap-5 overflow-y-auto px-6 py-6\">\n <FormField\n label={t('wms.backend.asns.create.form.warehouse', 'Warehouse')}\n required\n error={fieldErrors.warehouseId}\n >\n <ComboboxInput\n value={warehouseId}\n onChange={setWarehouseId}\n loadSuggestions={async (query) => loadWarehouseOptions(query)}\n resolveLabel={resolveOptionLabel}\n placeholder={t('wms.backend.asns.create.form.warehousePlaceholder', 'Select warehouse')}\n allowCustomValues={false}\n disabled={submitting}\n />\n </FormField>\n\n <div className=\"grid gap-5 sm:grid-cols-2\">\n <FormField\n label={t('wms.backend.asns.create.form.expectedAt', 'Expected date')}\n required\n error={fieldErrors.expectedAt}\n >\n <Input\n type=\"date\"\n value={expectedAt}\n onChange={(event) => setExpectedAt(event.target.value)}\n disabled={submitting}\n />\n </FormField>\n <FormField\n label={t('wms.backend.asns.create.form.referenceNumber', 'Reference')}\n error={fieldErrors.referenceNumber}\n >\n <Input\n value={referenceNumber}\n onChange={(event) => setReferenceNumber(event.target.value)}\n disabled={submitting}\n placeholder={t('wms.backend.asns.create.form.referencePlaceholder', 'PO / ASN ref')}\n />\n </FormField>\n </div>\n\n <FormField label={t('wms.backend.asns.create.form.notes', 'Notes')} error={fieldErrors.notes}>\n <Textarea\n value={notes}\n onChange={(event) => setNotes(event.target.value)}\n disabled={submitting}\n rows={2}\n />\n </FormField>\n\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between gap-2\">\n <h3 className=\"text-sm font-medium\">\n {t('wms.backend.asns.create.form.lines', 'Expected lines')}\n </h3>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={submitting}\n onClick={() => setLines((current) => [...current, { ...EMPTY_LINE }])}\n >\n {t('wms.backend.asns.create.form.addLine', 'Add line')}\n </Button>\n </div>\n {lines.map((line, index) => (\n <div key={`line-${index}`} className=\"grid gap-3 rounded-md border p-3 sm:grid-cols-3\">\n <FormField\n label={t('wms.backend.asns.create.form.variant', 'Variant')}\n required\n error={fieldErrors[`lines.${index}.catalogVariantId`]}\n className=\"sm:col-span-2\"\n >\n <ComboboxInput\n value={line.catalogVariantId}\n onChange={(next) => {\n setLines((current) =>\n current.map((item, itemIndex) =>\n itemIndex === index ? { ...item, catalogVariantId: next.trim() } : item,\n ),\n )\n }}\n loadSuggestions={async (query) => loadCatalogVariantOptions(query)}\n resolveLabel={resolveOptionLabel}\n placeholder={t(\n 'wms.backend.asns.create.form.variantPlaceholder',\n 'Search variant or SKU',\n )}\n allowCustomValues={false}\n disabled={submitting}\n />\n </FormField>\n <FormField\n label={t('wms.backend.asns.create.form.expectedQty', 'Expected qty')}\n required\n error={fieldErrors[`lines.${index}.expectedQty`]}\n >\n <Input\n type=\"number\"\n min={0.0001}\n step=\"any\"\n value={line.expectedQty}\n onChange={(event) => {\n const next = Number(event.target.value)\n setLines((current) =>\n current.map((item, itemIndex) =>\n itemIndex === index\n ? { ...item, expectedQty: Number.isFinite(next) ? next : 0 }\n : item,\n ),\n )\n }}\n disabled={submitting}\n />\n </FormField>\n <FormField\n label={t('wms.backend.asns.create.form.staging', 'Staging location')}\n error={fieldErrors[`lines.${index}.targetStagingLocationId`]}\n className=\"sm:col-span-3\"\n >\n <ComboboxInput\n value={line.targetStagingLocationId}\n onChange={(next) => {\n setLines((current) =>\n current.map((item, itemIndex) =>\n itemIndex === index\n ? { ...item, targetStagingLocationId: next.trim() }\n : item,\n ),\n )\n }}\n loadSuggestions={async (query) =>\n warehouseId ? loadStagingLocationOptions(warehouseId, query) : []\n }\n resolveLabel={resolveOptionLabel}\n placeholder={t(\n 'wms.backend.asns.create.form.stagingPlaceholder',\n 'Optional staging/dock',\n )}\n allowCustomValues={false}\n disabled={submitting || !warehouseId}\n />\n </FormField>\n </div>\n ))}\n {fieldErrors.lines ? (\n <p className=\"text-sm text-status-error-text\">{fieldErrors.lines}</p>\n ) : null}\n </div>\n </div>\n\n <DialogFooter bordered={false} className=\"border-t px-6 py-4 sm:justify-between\">\n <span className=\"hidden text-xs text-muted-foreground sm:inline\">\n <KbdShortcut keys={['Ctrl/\u2318', 'Enter']} />{' '}\n {t('wms.backend.asns.create.dialog.shortcutHint', 'to save')}\n </span>\n <div className=\"flex gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={closeDialog} disabled={submitting}>\n {t('wms.backend.asns.create.dialog.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={submitting} data-testid=\"create-asn-submit\">\n {t('wms.backend.asns.create.dialog.submit', 'Create ASN')}\n </Button>\n </div>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -43,7 +43,7 @@ function parseReceiveSerialNumbers(value) {
43
43
  return value.split(/[\n,]/).map((part) => part.trim()).filter((part) => part.length > 0);
44
44
  }
45
45
  const formSchema = z.object({
46
- receivedQty: z.number().positive(),
46
+ receivedQty: z.number().positive("wms.backend.asns.receive.errors.quantityPositive"),
47
47
  qcStatus: z.enum(["passed", "failed"]),
48
48
  targetStagingLocationId: z.string().uuid().optional(),
49
49
  lotNumber: z.string().trim().max(120).optional(),