@open-mercato/core 0.10.1-develop.7833.1.d6cbf0e3ab → 0.10.1-develop.7836.1.0ef0f453c6

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 (36) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/cart/api/current/lines/bulk/route.js +1 -1
  3. package/dist/modules/cart/api/current/lines/bulk/route.js.map +2 -2
  4. package/dist/modules/cart/api/current/lines/route.js +1 -1
  5. package/dist/modules/cart/api/current/lines/route.js.map +2 -2
  6. package/dist/modules/cart/commands/lines.js +5 -10
  7. package/dist/modules/cart/commands/lines.js.map +2 -2
  8. package/dist/modules/cart/lib/currentCartRoute.js +8 -0
  9. package/dist/modules/cart/lib/currentCartRoute.js.map +3 -3
  10. package/dist/modules/cart/lib/lineMutationLock.js +52 -0
  11. package/dist/modules/cart/lib/lineMutationLock.js.map +7 -0
  12. package/dist/modules/sales/commands/documents.js +3 -3
  13. package/dist/modules/sales/commands/documents.js.map +2 -2
  14. package/dist/modules/sales/components/documents/ItemsSection.js +75 -21
  15. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  16. package/dist/modules/sales/components/documents/lineItemUtils.js +2 -2
  17. package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
  18. package/package.json +7 -7
  19. package/src/modules/cart/api/current/lines/bulk/route.ts +1 -1
  20. package/src/modules/cart/api/current/lines/route.ts +1 -1
  21. package/src/modules/cart/commands/lines.ts +5 -10
  22. package/src/modules/cart/i18n/de.json +1 -0
  23. package/src/modules/cart/i18n/en.json +1 -0
  24. package/src/modules/cart/i18n/es.json +1 -0
  25. package/src/modules/cart/i18n/ko.json +1 -0
  26. package/src/modules/cart/i18n/pl.json +1 -0
  27. package/src/modules/cart/lib/currentCartRoute.ts +8 -0
  28. package/src/modules/cart/lib/lineMutationLock.ts +59 -0
  29. package/src/modules/sales/commands/documents.ts +3 -3
  30. package/src/modules/sales/components/documents/ItemsSection.tsx +82 -23
  31. package/src/modules/sales/components/documents/lineItemUtils.ts +2 -2
  32. package/src/modules/sales/i18n/de.json +1 -0
  33. package/src/modules/sales/i18n/en.json +1 -0
  34. package/src/modules/sales/i18n/es.json +1 -0
  35. package/src/modules/sales/i18n/ko.json +1 -0
  36. package/src/modules/sales/i18n/pl.json +1 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/cart/commands/lines.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { asValue } from 'awilix'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandBus, CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { ensureSameScope } from '@open-mercato/shared/lib/commands/scope'\nimport { badRequest, conflict, CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { E } from '#generated/entities.ids.generated'\n\nimport {\n CatalogProduct,\n CatalogProductPrice,\n CatalogProductVariant,\n} from '@open-mercato/core/modules/catalog/data/entities'\nimport {\n buildPriceRowFilter,\n filterRowsForVariant,\n resolvePriceKindId,\n resolvePriceVariantId,\n} from '@open-mercato/core/modules/catalog/lib/pricing'\nimport type { PriceRow, PricingContext } from '@open-mercato/core/modules/catalog/lib/pricing'\nimport type {\n BuyerPricingContext,\n CatalogBuyerPricingService,\n ResolvedLinePrice,\n} from '@open-mercato/core/modules/catalog/services/catalogBuyerPricingService'\n\n// `availability` is NOT in this module's `requires` (index.ts) \u2014 it is a soft/optional\n// peer, same category as `ecommerce` in `lib/routeContext.ts`. `deriveQuantityRules` is a\n// pure, dependency-free leaf function (no DI, no entities, no container) \u2014 see its own\n// doc comment: \"so other modules (e.g. a future `cart` module) can derive the same rules\n// without depending on `ecommerce`\". Importing it directly carries no runtime coupling to\n// `availability`'s module registration. The one genuinely runtime-coupled piece \u2014\n// `policyResolutionService`, a DI-registered service \u2014 is resolved through a local\n// `tryResolvePolicyResolutionService` below, mirroring `routeContext.ts`'s\n// `tryResolveStoreContextService` pattern: absent module, absent service, graceful\n// fallback to the catalog product's own min/max/increment columns.\nimport { deriveQuantityRules } from '@open-mercato/core/modules/availability/lib/quantityRules'\nimport type { QuantityRulePolicy, QuantityRules } from '@open-mercato/core/modules/availability/lib/quantityRules'\nimport type { PolicyResolutionService } from '@open-mercato/core/modules/availability/lib/policyResolution'\n\nimport type { SalesCalculationService } from '@open-mercato/core/modules/sales/services/salesCalculationService'\nimport type { SalesAdjustmentDraft } from '@open-mercato/core/modules/sales/lib/types'\n\nimport { Cart, CartLine } from '../data/entities'\nimport type { CartChannel, CartLineActorType, CartLineOrigin } from '../data/entities'\nimport {\n cartVisibilityKey,\n evaluateCartVisibility,\n isVisible,\n} from '../lib/visibility'\nimport type { CartVisibilityLineRef, ResolvedAssortmentScope } from '../lib/visibility'\nimport { shouldEnforceAssortmentScope } from '../lib/routeContext'\nimport type { ResolvedCartRouteContext } from '../lib/routeContext'\nimport { buildRecomputeCartTotalsPhase } from '../lib/reprice'\nimport type { CartPriceChange } from '../lib/reprice'\nimport { buildPromotionsEffectsPhase } from '../lib/promotionContext'\nimport type { CartPromotionWarning } from '../lib/promotionContext'\nimport { assertCartNotLocked } from '../lib/lockGuard'\nimport { recordBasketMutationActivity } from '../lib/cartActivity'\nimport { emitCartEvent } from '../events'\n// Type-only \u2014 erased at compile time, so this carries none of the runtime circularity risk\n// a VALUE import would: `commands/cart.ts` already imports `emitVisibilityRejectedEvent`\n// from THIS file (Step 7.4), so a value-level import back from here would create a\n// module-load cycle. See `dispatchCartRepriceForUndo` below for how the undo handlers call\n// `cart.reprice` without importing its handler object.\nimport type { CartRepriceInput, CartRepriceResult } from './cart'\n\n// Step 4.6 \u2014 `cart.lines.add`, `.update`, `.remove`, `.bulkAdd`. Builds directly on Step\n// 4.4's `evaluateCartVisibility` (\u00A76a) and Step 4.5's `cart.create`/`cart.get` + the\n// storefront/trusted route-context split. See the module-level comments on each command\n// below for the ordering and rejection-vs-throw decisions this step had to make.\n\nconst CART_LINE_ENTITY_ID = E.cart.cart_line\nconst cartLineCrudIndexer: CrudIndexerConfig<CartLine> = { entityType: CART_LINE_ENTITY_ID }\n\n// Step 8.2 (QA F12) \u2014 this file's own `Cart`-entity indexer/events config, deliberately\n// duplicated from `commands/cart.ts`'s identical `cartCrudIndexer`/`cartCrudEvents` rather\n// than imported from there: `cart.ts` already imports `emitVisibilityRejectedEvent` from\n// THIS file (Step 7.4), so importing a VALUE back from `cart.ts` here would create a\n// module-load cycle. `lib/reprice.ts`'s own header comment sets the precedent for this exact\n// trade-off (\"Deliberately duplicates `commands/lines.ts`'s small per-line pricing helpers\n// ... rather than exporting and importing lines.ts's private functions\").\nconst CART_ENTITY_ID = E.cart.cart\nconst cartCrudIndexer: CrudIndexerConfig<Cart> = { entityType: CART_ENTITY_ID }\nconst cartCrudEvents: CrudEventsConfig<Cart> = {\n module: 'cart',\n entity: 'cart',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nconst MAX_BULK_ADD_ENTRIES = 100\n// Multiples of an increment are compared as floating-point numbers; this tolerance\n// absorbs IEEE-754 noise (e.g. 0.1 + 0.2) without masking a genuinely non-aligned quantity.\nconst QUANTITY_EPSILON = 1e-6\n\ntype CartLineScope = { tenantId: string; organizationId: string }\n\nfunction getCartLineIdentifiers(line: CartLine) {\n return { id: line.id, organizationId: line.organizationId, tenantId: line.tenantId }\n}\n\n// ---------------------------------------------------------------------------\n// Shared: `cart.line.visibility_rejected` (\u00A711, D-R4-23, TC-CART-R1-10)\n// ---------------------------------------------------------------------------\n\n/**\n * \u00A76a.1c's per-line enforcement points (`'lines.add'`/`'lines.update'`/`'lines.bulkAdd'`,\n * this file's own) plus the two whole-cart/merge passes added at Step 7.4:\n * `'reprice'` \u2014 \u00A76a.2's whole-cart re-visibility pass, trigger 2 (`commands/cart.ts`'s\n * `cart.reprice`); `'merge'` \u2014 \u00A76a.5's merge-time pass (`commands/merge.ts`'s `cart.merge`).\n * Trigger 5 (checkout lock, `commands/lock.ts`'s `cart.lock`) gets NO dedicated value of its\n * own (Step 7.5): `cart.lock` dispatches `cart.reprice` with `trigger: 'lock'`, which runs\n * the SAME \u00A76a.2 whole-cart re-visibility pass as trigger 2/merge and reports through this\n * SAME `'reprice'` value \u2014 a lock-triggered rejection is indistinguishable, at this event's\n * granularity, from any other re-price-triggered one.\n */\nexport type CartVisibilityRejectionTrigger = 'lines.add' | 'lines.update' | 'lines.bulkAdd' | 'reprice' | 'merge'\n\n/**\n * Emitted once per REQUEST with the count of lines this call rejected for a VISIBILITY\n * reason specifically \u2014 never once per rejected line, and never for a rejection the request\n * incurred for an unrelated reason (quantity rule, unpriceable product, a missing\n * product/variant row). \u00A76a.3's no-enumeration-oracle collapse applies to the BUYER-facing\n * `product_unavailable` warning only; this event is the operator-facing signal D-R4-23 names,\n * so it is free to be precise about which rejections were actually a scope mismatch.\n *\n * Exported since Step 7.4 \u2014 `commands/cart.ts`'s \u00A76a.2 pass and `commands/merge.ts`'s \u00A76a.5\n * pass both emit through this SAME helper rather than each re-deriving the event shape.\n */\nexport async function emitVisibilityRejectedEvent(\n cart: Pick<Cart, 'id' | 'tenantId' | 'organizationId'>,\n rejectedCount: number,\n triggeredBy: CartVisibilityRejectionTrigger,\n): Promise<void> {\n if (rejectedCount <= 0) return\n await emitCartEvent('cart.line.visibility_rejected', {\n cartId: cart.id,\n organizationId: cart.organizationId,\n tenantId: cart.tenantId,\n rejectedCount,\n triggeredBy,\n })\n}\n\n// ---------------------------------------------------------------------------\n// Shared: quantity-rule validation (\u00A75.5)\n// ---------------------------------------------------------------------------\n\nexport type CartLineRejectionReason =\n | 'quantity_below_minimum'\n | 'quantity_invalid_increment'\n | 'product_unavailable'\n | 'product_not_priceable'\n\nexport type CartLineWarning = 'quantity_capped'\n\nexport type QuantityRuleCheck =\n | { status: 'accepted'; quantity: number }\n | { status: 'accepted_clamped'; quantity: number; requestedQuantity: number }\n | { status: 'rejected'; reason: 'quantity_below_minimum' | 'quantity_invalid_increment'; nearestValid: number }\n\n/**\n * \u00A75.5's three rules, in the order that keeps `nearestValid` meaningful on its own:\n *\n * 1. Increment alignment is checked against base 0 (a plain \"multiple of N\"), never\n * against `minOrderQuantity` as the base. Anchoring to `minOrderQuantity` instead would\n * make the two constraints interact badly whenever `minOrderQuantity` is itself not a\n * multiple of the increment (e.g. min=7, increment=5): summing two independently valid\n * quantities (merge-by-quantity, \u00A74.2) would then routinely fail re-validation, since\n * sums of base-`min` aligned values are not themselves base-`min` aligned in general.\n * Sums of base-0 aligned values always stay base-0 aligned (basic algebra), which is\n * exactly the property the merge-sum re-check below depends on.\n * 2. Below `minOrderQuantity` is rejected outright, with `nearestValid` bumped up to the\n * smallest increment-aligned value at or above the minimum.\n * 3. Above `maxOrderQuantity` is capped with a warning, never rejected.\n *\n * This ordering means a quantity that is both below-minimum and non-aligned reports\n * `quantity_invalid_increment` (its `nearestValid` already accounts for the minimum too,\n * via the same bump), not `quantity_below_minimum`. Documented here since \u00A75.5's prose\n * states the three rules independently and does not resolve this overlap itself.\n */\nexport function checkQuantityRule(rules: QuantityRules, requestedQuantity: number): QuantityRuleCheck {\n const { minOrderQuantity, maxOrderQuantity, quantityIncrement } = rules\n const hasIncrement = quantityIncrement != null && quantityIncrement > 0\n\n const bumpToMinimum = (value: number): number => {\n if (minOrderQuantity == null || value >= minOrderQuantity) return value\n return hasIncrement ? Math.ceil(minOrderQuantity / quantityIncrement) * quantityIncrement : minOrderQuantity\n }\n\n if (hasIncrement) {\n const roundedSteps = Math.round(requestedQuantity / quantityIncrement)\n let aligned = roundedSteps * quantityIncrement\n if (aligned <= 0) aligned = quantityIncrement\n if (Math.abs(aligned - requestedQuantity) > QUANTITY_EPSILON) {\n return { status: 'rejected', reason: 'quantity_invalid_increment', nearestValid: bumpToMinimum(aligned) }\n }\n }\n\n if (minOrderQuantity != null && requestedQuantity < minOrderQuantity) {\n return { status: 'rejected', reason: 'quantity_below_minimum', nearestValid: bumpToMinimum(minOrderQuantity) }\n }\n\n if (maxOrderQuantity != null && requestedQuantity > maxOrderQuantity) {\n return { status: 'accepted_clamped', quantity: maxOrderQuantity, requestedQuantity }\n }\n\n return { status: 'accepted', quantity: requestedQuantity }\n}\n\nfunction tryResolvePolicyResolutionService(container: { resolve: <T = unknown>(name: string) => T }): PolicyResolutionService | null {\n try {\n return container.resolve<PolicyResolutionService>('policyResolutionService') ?? null\n } catch {\n return null\n }\n}\n\nasync function resolveQuantityRulesForProduct(\n em: EntityManager,\n container: { resolve: <T = unknown>(name: string) => T },\n scope: CartLineScope,\n storeId: string | null,\n product: CatalogProduct,\n variantId: string | null,\n): Promise<QuantityRules> {\n const resolver = tryResolvePolicyResolutionService(container)\n let policy: QuantityRulePolicy = null\n if (resolver) {\n policy = await resolver.resolve(em, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n storeId,\n productId: product.id,\n variantId,\n })\n }\n return deriveQuantityRules(policy, product)\n}\n\n// ---------------------------------------------------------------------------\n// Shared: pricing a line\n// ---------------------------------------------------------------------------\n\nfunction buildCartPricingRowFilterContext(cart: Cart): PricingContext {\n return {\n channelId: cart.salesChannelId ?? null,\n customerId: cart.customerId ?? null,\n customerIds: cart.customerIds ?? [],\n customerGroupIds: cart.customerGroupIds,\n currencyCode: cart.currencyCode,\n priceKindId: cart.priceKindId ?? null,\n quantity: 1,\n date: new Date(),\n }\n}\n\nfunction buildBuyerPricingContext(cart: Cart, quantity: number, date: Date): BuyerPricingContext {\n return {\n channelId: cart.salesChannelId ?? null,\n customerId: cart.customerId ?? null,\n customerIds: cart.customerIds ?? [],\n customerGroupIds: cart.customerGroupIds,\n currencyCode: cart.currencyCode,\n priceKindId: cart.priceKindId ?? null,\n quantity,\n date,\n }\n}\n\n/**\n * Matches `ecommerce/lib/storefrontPricing.ts`'s `fetchPageRows` row-fetch shape (the\n * precedent the task points at): one `CatalogProductPrice` query narrowed by\n * `buildPriceRowFilter`, scoped to the requested product ids (plus any variant ids as an\n * `$or`), never one query per product.\n */\nasync function fetchPriceRowsBatch(\n em: EntityManager,\n scope: CartLineScope,\n pricingContext: PricingContext,\n productIds: string[],\n variantIds: string[],\n): Promise<PriceRow[]> {\n if (!productIds.length) return []\n const targetClause = variantIds.length\n ? { $or: [{ product: { $in: productIds } }, { variant: { $in: variantIds } }] }\n : { product: { $in: productIds } }\n const where = {\n $and: [buildPriceRowFilter(pricingContext), targetClause],\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }\n const rows = await findWithDecryption(em, CatalogProductPrice, where as never, { populate: ['priceKind', 'offer'] }, scope)\n return rows as unknown as PriceRow[]\n}\n\nfunction groupRowsByProduct(rows: PriceRow[], variantToProduct: Map<string, string>): Map<string, PriceRow[]> {\n const grouped = new Map<string, PriceRow[]>()\n for (const row of rows) {\n const variantId = resolvePriceVariantId(row)\n const productId = variantId ? variantToProduct.get(variantId) ?? null : (typeof row.product === 'string' ? row.product : row.product?.id ?? null)\n if (!productId) continue\n const bucket = grouped.get(productId) ?? []\n bucket.push(row)\n grouped.set(productId, bucket)\n }\n return grouped\n}\n\nasync function resolveLinePricing(\n em: EntityManager,\n container: { resolve: <T = unknown>(name: string) => T },\n cart: Cart,\n rows: PriceRow[],\n quantity: number,\n date: Date,\n): Promise<ResolvedLinePrice> {\n const buyerPricingService = container.resolve<CatalogBuyerPricingService>('catalogBuyerPricingService')\n const context = buildBuyerPricingContext(cart, quantity, date)\n return buyerPricingService.resolveLinePrice(em, rows, context, {\n tenantId: cart.tenantId,\n organizationId: cart.organizationId,\n })\n}\n\nfunction applyPriceSnapshot(line: CartLine, resolved: ResolvedLinePrice, pricedAt: Date): void {\n line.unitPriceNet = resolved.amountNet !== null ? String(resolved.amountNet) : null\n line.unitPriceGross = resolved.amountGross !== null ? String(resolved.amountGross) : null\n line.taxRate = resolved.taxRatePercent !== null ? String(resolved.taxRatePercent) : null\n line.priceKindId = resolved.priceRow ? resolvePriceKindId(resolved.priceRow) : null\n line.priceRowId = resolved.priceRow?.id ?? null\n line.priceTierMinQuantity = resolved.currentTier?.minQuantity ?? null\n line.pricedAt = pricedAt\n}\n\n/**\n * \u00A75.2 trigger 1 is scoped to \"crosses a `min_quantity`/`max_quantity` tier boundary\" \u2014\n * i.e. the bounds of the single `CatalogProductPrice` row that is currently winning\n * (`line.priceRowId`), not a re-derivation of the whole tier ladder. A single row-by-id\n * lookup (cheap, no row-set fetch, no `catalogBuyerPricingService` call) is enough to\n * answer \"did we leave that row's own window\" \u2014 this is the check `.update` uses to decide\n * whether a re-price is even necessary, so a same-tier quantity change never calls the\n * pricing service at all.\n */\nasync function isWithinStoredPriceTier(\n em: EntityManager,\n scope: CartLineScope,\n line: CartLine,\n quantity: number,\n): Promise<boolean> {\n if (!line.priceRowId) return false\n const row = await findOneWithDecryption(\n em,\n CatalogProductPrice,\n { id: line.priceRowId, tenantId: scope.tenantId, organizationId: scope.organizationId },\n {},\n scope,\n )\n if (!row) return false\n const min = row.minQuantity ?? 1\n if (quantity < min) return false\n if (row.maxQuantity != null && quantity > row.maxQuantity) return false\n return true\n}\n\n/**\n * `CartLine.lineTotals` is \"Verbatim `SalesLineCalculationResult`\" (\u00A74.2) \u2014 `sales/AGENTS.md`\n * is explicit that document math MUST go through `salesCalculationService`, never inlined.\n * `calculateLine` is the single-line entry point (no whole-document context required), so a\n * line's own totals snapshot is computed the same way `sales` would compute it for a real\n * document, with the tax/discount arithmetic owned entirely by `sales`.\n */\nasync function computeLineTotals(\n container: { resolve: <T = unknown>(name: string) => T },\n cart: Cart,\n params: {\n productId: string\n variantId: string | null\n name: string\n quantity: number\n unitPriceNet: number | null\n unitPriceGross: number | null\n taxRate: number | null\n configuration: Record<string, unknown> | null\n },\n): Promise<Record<string, unknown>> {\n const salesCalculationService = container.resolve<SalesCalculationService>('salesCalculationService')\n const result = await salesCalculationService.calculateLine({\n documentKind: 'quote',\n line: {\n kind: 'product',\n productId: params.productId,\n productVariantId: params.variantId,\n name: params.name,\n quantity: params.quantity,\n currencyCode: cart.currencyCode,\n unitPriceNet: params.unitPriceNet,\n unitPriceGross: params.unitPriceGross,\n taxRate: params.taxRate,\n configuration: params.configuration,\n },\n context: { tenantId: cart.tenantId, organizationId: cart.organizationId, currencyCode: cart.currencyCode },\n })\n return result as unknown as Record<string, unknown>\n}\n\n// ---------------------------------------------------------------------------\n// Shared: visibility\n// ---------------------------------------------------------------------------\n\ntype CartLineAuthInput =\n | { routeContext: ResolvedCartRouteContext; tenantId?: undefined; organizationId?: undefined; assortmentScope?: undefined }\n | { routeContext?: undefined; tenantId: string; organizationId: string; assortmentScope?: ResolvedAssortmentScope }\n\nfunction resolveLineAuthScopeIds(input: CartLineAuthInput): {\n tenantId: string\n organizationId: string\n rawScope: ResolvedAssortmentScope | undefined\n} {\n if (input.routeContext) {\n return {\n tenantId: input.routeContext.tenantId,\n organizationId: input.routeContext.organizationId,\n rawScope: input.routeContext.assortmentScope,\n }\n }\n return { tenantId: input.tenantId, organizationId: input.organizationId, rawScope: input.assortmentScope }\n}\n\n/** \u00A76a.1b \u2014 enforce iff the cart channel is `storefront` or a scope was resolved for this call. */\nfunction resolveEnforcedScope(cart: Cart, rawScope: ResolvedAssortmentScope | undefined): ResolvedAssortmentScope | undefined {\n return shouldEnforceAssortmentScope(cart.channel, rawScope) ? rawScope : undefined\n}\n\n/**\n * Defense in depth for \u00A76a.1a: every storefront-channel cart must have been created\n * through the route edge, which always resolves and supplies a `routeContext`. A trusted\n * in-process caller that forgets to resolve one for a `storefront` cart is a programming\n * error, not a buyer-facing condition \u2014 it throws rather than silently exempting the call.\n */\nfunction assertStorefrontHasRouteContext(cart: Cart, input: CartLineAuthInput): void {\n if (cart.channel === 'storefront' && !input.routeContext) {\n throw badRequest('[internal] A storefront-channel cart requires a resolved routeContext for its visibility check')\n }\n}\n\nasync function checkSingleLineVisibility(\n em: EntityManager,\n container: { resolve: <T = unknown>(name: string) => T },\n tenantScope: CartLineScope,\n line: CartVisibilityLineRef,\n assortmentScope: ResolvedAssortmentScope | undefined,\n): Promise<boolean> {\n const result = await evaluateCartVisibility({ lines: [line], scope: assortmentScope, tenantScope, em, container })\n return isVisible(result.get(cartVisibilityKey(line)) ?? 'unavailable')\n}\n\n// ---------------------------------------------------------------------------\n// Shared: actor attribution (\u00A74.2)\n// ---------------------------------------------------------------------------\n\ntype ResolvedLineActor = { addedByActorType: CartLineActorType; addedByActorId: string | null }\n\n/**\n * Storefront calls derive the actor from the resolved `routeContext` \u2014 a buyer, identified\n * (`customerUserId`) or guest (`null`), never anything a request body could spoof. Trusted\n * (non-storefront) calls carry no resolved buyer identity at all (\u00A76a.4), so the caller must\n * supply `addedByActorType`/`addedByActorId` explicitly, the same way `cart.create`'s trusted\n * path supplies its own `customerId` rather than inheriting one from a resolved context.\n */\nfunction resolveLineActor(\n input: CartLineAuthInput,\n explicit: { addedByActorType?: CartLineActorType; addedByActorId?: string | null } | undefined,\n): ResolvedLineActor {\n if (input.routeContext) {\n return { addedByActorType: 'buyer', addedByActorId: input.routeContext.customerUserId ?? null }\n }\n if (!explicit?.addedByActorType) {\n throw badRequest('[internal] addedByActorType is required for a trusted (non-storefront) cart.lines call')\n }\n return { addedByActorType: explicit.addedByActorType, addedByActorId: explicit.addedByActorId ?? null }\n}\n\n// ---------------------------------------------------------------------------\n// Shared: cart/line loading, snapshotting, merge-by-quantity lookup\n// ---------------------------------------------------------------------------\n\n/**\n * Uniform not-found, mirroring `cart.get`'s own no-oracle convention (Step 4.5): a wrong\n * tenant/org and a nonexistent id both collapse to the identical message, so a mutation\n * attempt never discloses which of those is actually true \u2014 that part of the no-oracle\n * collapse protects something real (whether a cart with this id/token even exists for this\n * tenant) and stays a 404.\n *\n * Step 7.5 \u2014 ONE exception to that collapse, applied unconditionally regardless of\n * `requireActive`: a `locked` cart gets the distinguishable `423 cart_locked`\n * (`assertCartNotLocked`, `lib/lockGuard.ts`) BEFORE falling through to the generic\n * `requireActive` check below. \u00A79's own text states the mutation-while-locked rejection as a\n * blanket rule with no stated carve-out for any particular caller, which is why this runs\n * for EVERY caller of this function.\n *\n * Step 8.5 (QA F15) \u2014 `requireActive`'s own branch is no longer part of the no-oracle\n * collapse: it now throws a distinguishable `409 cart_not_active` (carrying the cart's real\n * `status`) instead of folding into the generic 404. `GET /current` already discloses a\n * non-active cart's `status` with a `200` (`cartService.resolveBasketByToken` / \u00A78.3 do not\n * gate on `status` at all), so collapsing a WRITE's \"exists, right tenant, wrong status\" into\n * the SAME 404 as \"does not exist\"/\"wrong tenant\" no longer hides anything a client could not\n * already learn from the read side \u2014 it only contradicted `cart.mdx`'s documented `409`\n * contract for a non-active-cart write. The owner ruled explicitly: fix the code to match the\n * docs, not the other way around. \"Exists, wrong tenant\" vs \"exists, right tenant, not\n * active\" remain genuinely different concerns (identity-hiding vs. state-disclosure), so only\n * the latter is relaxed here.\n */\nasync function loadMutableCart(\n em: EntityManager,\n scope: CartLineScope,\n cartId: string,\n options: { requireActive: boolean },\n): Promise<Cart> {\n const cart = await findOneWithDecryption(\n em,\n Cart,\n { id: cartId, tenantId: scope.tenantId, organizationId: scope.organizationId, kind: 'basket' as const },\n {},\n scope,\n )\n if (!cart) throw notFound('[internal] cart not found')\n assertCartNotLocked(cart)\n await recordBasketMutationActivity(em, cart)\n if (options.requireActive && cart.status !== 'active') {\n throw new CrudHttpError(409, { error: '[internal] cart_not_active', code: 'cart_not_active', status: cart.status })\n }\n return cart\n}\n\nexport type CartLineSnapshot = {\n id: string\n cartId: string\n tenantId: string\n organizationId: string\n lineNumber: number\n productId: string\n variantId: string | null\n sku: string | null\n name: string\n optionValues: Record<string, string> | null\n quantity: string\n quantityUnit: string | null\n unitPriceNet: string | null\n unitPriceGross: string | null\n taxRate: string | null\n priceKindId: string | null\n priceRowId: string | null\n priceTierMinQuantity: number | null\n pricedAt: Date\n lineTotals: Record<string, unknown>\n discountTotalNet: string | null\n promotionId: string | null\n origin: CartLineOrigin\n configuration: Record<string, unknown> | null\n addedByActorType: CartLineActorType\n addedByActorId: string | null\n metadata: Record<string, unknown> | null\n createdAt: Date\n updatedAt: Date\n deletedAt: Date | null\n}\n\n// Exported (Step 5.2) so `lib/cartView.ts`'s \u00A710.1 `CartView` envelope builder can map a\n// freshly re-fetched `CartLine` the same way every command in this file already does,\n// instead of duplicating this 20-field mapping a third time. `lib/currentCartRoute.ts`\n// already imports command handlers/types directly from this module (see its own header\n// comment) \u2014 a plain pure-function export carries no additional coupling risk.\nexport function toCartLineSnapshot(line: CartLine): CartLineSnapshot {\n return {\n id: line.id,\n cartId: line.cartId,\n tenantId: line.tenantId,\n organizationId: line.organizationId,\n lineNumber: line.lineNumber,\n productId: line.productId,\n variantId: line.variantId ?? null,\n sku: line.sku ?? null,\n name: line.name,\n optionValues: line.optionValues ?? null,\n quantity: line.quantity,\n quantityUnit: line.quantityUnit ?? null,\n unitPriceNet: line.unitPriceNet ?? null,\n unitPriceGross: line.unitPriceGross ?? null,\n taxRate: line.taxRate ?? null,\n priceKindId: line.priceKindId ?? null,\n priceRowId: line.priceRowId ?? null,\n priceTierMinQuantity: line.priceTierMinQuantity ?? null,\n pricedAt: line.pricedAt,\n lineTotals: line.lineTotals,\n discountTotalNet: line.discountTotalNet ?? null,\n promotionId: line.promotionId ?? null,\n origin: line.origin,\n configuration: line.configuration ?? null,\n addedByActorType: line.addedByActorType,\n addedByActorId: line.addedByActorId ?? null,\n metadata: line.metadata ?? null,\n createdAt: line.createdAt,\n updatedAt: line.updatedAt,\n deletedAt: line.deletedAt ?? null,\n }\n}\n\nfunction toQuantityNumber(value: number | string): number {\n const numeric = typeof value === 'number' ? value : Number(value)\n if (!Number.isFinite(numeric)) throw badRequest('[internal] quantity must be a finite number')\n return numeric\n}\n\n/**\n * Deep-equality on `configuration`, canonicalizing key order first. Avoids re-deriving\n * Postgres's `md5(jsonb::text)` stored-generated-column algorithm in JS (jsonb normalizes\n * key order on storage, so two differently-ordered-but-equal objects hash identically there\n * \u2014 replicating that exactly is fragile); matching on the plain columns plus a canonical\n * deep-equality check is semantically equivalent for the purpose of finding a merge target,\n * and the DB's own `UNIQUE NULLS NOT DISTINCT` index (\u00A74.2) is the authoritative backstop.\n */\nfunction canonicalJson(value: unknown): string {\n if (value === null || value === undefined) return 'null'\n if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>\n const keys = Object.keys(obj).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(obj[key])}`).join(',')}}`\n }\n return JSON.stringify(value)\n}\n\nfunction configurationsEqual(a: Record<string, unknown> | null | undefined, b: Record<string, unknown> | null | undefined): boolean {\n const left = a ?? null\n const right = b ?? null\n if (left === null && right === null) return true\n if (left === null || right === null) return false\n return canonicalJson(left) === canonicalJson(right)\n}\n\n/** `(cart_id, product_id, variant_id, configuration_hash, promotion_id)` merge-by-quantity lookup, scoped to `origin: 'customer'` lines (`promotion_id IS NULL`) \u2014 the only origin this step ever writes. */\nasync function findExistingCustomerLine(\n em: EntityManager,\n scope: CartLineScope,\n cartId: string,\n productId: string,\n variantId: string | null,\n configuration: Record<string, unknown> | null,\n): Promise<CartLine | null> {\n const candidates = await findWithDecryption(\n em,\n CartLine,\n { cartId, productId, variantId: variantId ?? null, promotionId: null, deletedAt: null, tenantId: scope.tenantId, organizationId: scope.organizationId },\n {},\n scope,\n )\n return (candidates as CartLine[]).find((line) => configurationsEqual(line.configuration ?? null, configuration)) ?? null\n}\n\nasync function computeStartingLineNumber(em: EntityManager, scope: CartLineScope, cartId: string): Promise<number> {\n const existing = await em.find(CartLine, { cartId, tenantId: scope.tenantId, organizationId: scope.organizationId })\n return existing.reduce((max, line) => Math.max(max, line.lineNumber), 0) + 1\n}\n\ntype NewLineFields = {\n productId: string\n variantId: string | null\n sku: string | null\n name: string\n optionValues: Record<string, string> | null\n configuration: Record<string, unknown> | null\n metadata: Record<string, unknown> | null\n}\n\nfunction resolveSnapshotFields(product: CatalogProduct, variant: CatalogProductVariant | null, configuration: Record<string, unknown> | null, metadata: Record<string, unknown> | null): NewLineFields {\n return {\n productId: product.id,\n variantId: variant?.id ?? null,\n sku: variant?.sku ?? product.sku ?? null,\n name: product.title,\n optionValues: variant?.optionValues ?? null,\n configuration: configuration ?? null,\n metadata: metadata ?? null,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Shared: whole-cart promotions + totals recompute (Step 8.2, QA F12)\n// ---------------------------------------------------------------------------\n\n/**\n * Spec `cart-module.md:119`'s write flow for every command \u2014 load, re-price, run through\n * `promotionsService`, `calculateDocumentTotals`, persist totals, all inside the same\n * transaction \u2014 applied to this file's own line-mutation commands, which until this step\n * only ever touched the one line they were mutating and left `Cart.totals`/every OTHER\n * line's `discountTotalNet` stale (QA finding F12: a 2-unit \"5% off 2+ items\" cart showed\n * zeroed totals after add, a stale discount after a quantity cut below the threshold, and a\n * non-zero grand total after deleting the last line).\n *\n * Reuses `cart.reprice`'s OWN phase builders (`lib/promotionContext.ts`'s\n * `buildPromotionsEffectsPhase`, `lib/reprice.ts`'s `buildRecomputeCartTotalsPhase`) rather\n * than a parallel re-implementation \u2014 this function's only job is wiring them into THIS\n * caller's own `withAtomicFlush` phase array, mirroring `commands/cart.ts`'s\n * `repriceCartCommand.execute` almost verbatim for just these two phases (catalog\n * unit-price re-resolution \u2014 `buildCartLineRepricePhases` \u2014 and the \u00A76a.2 whole-cart\n * re-visibility pass are deliberately NOT included: out of this step's scope per the task\n * brief, and already covered by this file's own per-line checks/pricing).\n *\n * Registers `auth` on the container SYNCHRONOUSLY, before returning any phase \u2014 same\n * ordering `cart.reprice` uses, since `promotionsService.evaluate()` resolves its tenant/org\n * scope from that DI token and fails closed (400) without it.\n *\n * The returned phases close over a freshly-populated `lines` array via a leading fetch\n * phase rather than a value the caller fetches up front: every caller here mutates a\n * scalar (create/update/soft-delete a line) BEFORE these phases run, so re-reading the\n * cart's full line set must itself be a phase in the SAME `withAtomicFlush` sequence\n * (`packages/core/AGENTS.md`'s \"Entity Update Safety\" rule) \u2014 never a plain query the caller\n * runs ahead of `withAtomicFlush` entirely. The array is mutated in place by the leading\n * fetch phase (`lines.push(...)`) rather than reassigned, so the SAME reference threads\n * through every later phase exactly like `buildPromotionsEffectsPhase`'s own in-place gift\n * line push/splice already depends on.\n */\nfunction buildCartPromotionsAndTotalsPhases(params: {\n em: EntityManager\n container: CommandRuntimeContext['container']\n cart: Cart\n scope: CartLineScope\n assortmentScope: ResolvedAssortmentScope | undefined\n now: Date\n createdGiftLines: CartLine[]\n removedGiftLines: CartLine[]\n promotionWarnings: CartPromotionWarning[]\n}): Array<() => Promise<void>> {\n const { em, container, cart, scope, assortmentScope, now, createdGiftLines, removedGiftLines, promotionWarnings } = params\n container.register({ auth: asValue({ tenantId: cart.tenantId, orgId: cart.organizationId }) })\n\n const lines: CartLine[] = []\n const adjustments: SalesAdjustmentDraft[] = []\n const changes: CartPriceChange[] = []\n\n const fetchLinesPhase = async () => {\n const fetched = (await findWithDecryption(\n em,\n CartLine,\n { cartId: cart.id, deletedAt: null, tenantId: scope.tenantId, organizationId: scope.organizationId },\n {},\n scope,\n )) as CartLine[]\n lines.push(...fetched)\n }\n\n const promotionsPhase = buildPromotionsEffectsPhase({\n em,\n container,\n cart,\n lines,\n assortmentScope,\n tenantScope: scope,\n now,\n adjustments,\n warnings: promotionWarnings,\n createdGiftLines,\n removedGiftLines,\n })\n\n const totalsPhase = buildRecomputeCartTotalsPhase(container, cart, lines, now, changes, adjustments)\n\n return [fetchLinesPhase, promotionsPhase, totalsPhase]\n}\n\n/**\n * The cart-level side effects every line-mutation command now owes once it also mutates\n * `Cart.totals`/`totalsComputedAt` \u2014 mirrors `cart.reprice`'s own post-commit emission for\n * the SAME three things (the cart's own `updated` CRUD side effect, a created gift line's\n * `created` side effect + `cart.line.added`, a removed gift line's `deleted` side effect +\n * `cart.line.removed`), scoped to just what `buildCartPromotionsAndTotalsPhases` can\n * possibly have produced. Called AFTER the caller's own `withAtomicFlush` commits, same\n * \"side effects only after the DB write lands\" rule as every other emission in this file.\n */\nasync function emitCartPromotionsAndTotalsSideEffects(\n dataEngine: DataEngine,\n cart: Cart,\n outcome: { createdGiftLines: CartLine[]; removedGiftLines: CartLine[]; promotionWarnings: CartPromotionWarning[] },\n): Promise<void> {\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: cart,\n identifiers: { id: cart.id, organizationId: cart.organizationId, tenantId: cart.tenantId },\n events: cartCrudEvents,\n indexer: cartCrudIndexer,\n })\n for (const line of outcome.createdGiftLines) {\n await emitCrudSideEffects({ dataEngine, action: 'created', entity: line, identifiers: getCartLineIdentifiers(line), indexer: cartLineCrudIndexer })\n await emitCartEvent('cart.line.added', { id: line.id, cartId: line.cartId, organizationId: line.organizationId, tenantId: line.tenantId })\n }\n for (const line of outcome.removedGiftLines) {\n await emitCrudSideEffects({ dataEngine, action: 'deleted', entity: line, identifiers: getCartLineIdentifiers(line), indexer: cartLineCrudIndexer })\n await emitCartEvent('cart.line.removed', { id: line.id, cartId: line.cartId, organizationId: line.organizationId, tenantId: line.tenantId })\n }\n for (const warning of outcome.promotionWarnings) {\n if (warning.code !== 'promotion_no_longer_valid') continue\n await emitCartEvent('cart.promotion.no_longer_valid', {\n cartId: cart.id,\n tenantId: cart.tenantId,\n organizationId: cart.organizationId,\n promotionId: warning.promotionId,\n })\n }\n}\n\n/**\n * The undo-side half of QA F12's fix (task brief point 7): `cart.lines.add`/`.remove`/\n * `.bulkAdd` restore their reversed line(s) in their own `undo()` handler, but reversing a\n * line is not the inverse of `Cart.totals` \u2014 only a full `cart.reprice` recomputes the\n * whole-cart aggregate, so this dispatches that command as a follow-up, the same pattern\n * `commands/promotions.ts`'s own `dispatchReprice` helper already establishes for\n * `cart.promotions.apply`/`.remove`.\n *\n * Calls the command bus with the LITERAL id string `'cart.reprice'` rather than importing\n * `repriceCartCommand` from `./cart` \u2014 a value-level import back from here would create a\n * module-load cycle (`cart.ts` already imports from this file). `CartRepriceInput`/\n * `CartRepriceResult` stay `import type`-only above for the identical reason.\n *\n * Trigger reason `'undo'` (`lib/reprice.ts`'s `CartPriceChangeReason`): none of the existing\n * values describe \"a command's own undo handler is restoring a reversed line mutation\" \u2014\n * `'merge'`/`'lock'`/`'promotion_applied'`/`'promotion_removed'` are each already owned by a\n * different caller for a different reason, so reusing one of them would mislabel the\n * `priceChanges`/`cart.price.changed` disclosure this reprice may still produce. An undo has\n * no `ResolvedCartRouteContext` of its own to forward (unlike `cart.get`'s trigger dispatch\n * or `cart.promotions.apply`/`.remove`'s), so this never enforces assortment scope on a gift\n * line a reprice happens to create here \u2014 the same `undefined`-scope behavior every other\n * `routeContext`-less caller in this module already has.\n */\nasync function dispatchCartRepriceForUndo(\n ctx: CommandRuntimeContext,\n cart: { id: string; tenantId: string; organizationId: string },\n): Promise<void> {\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n await commandBus.execute<CartRepriceInput, CartRepriceResult>('cart.reprice', {\n input: { cartId: cart.id, tenantId: cart.tenantId, organizationId: cart.organizationId, trigger: 'undo' },\n ctx,\n })\n}\n\n// ---------------------------------------------------------------------------\n// cart.lines.add\n// ---------------------------------------------------------------------------\n\nexport type CartLinesAddInput = CartLineAuthInput & {\n cartId: string\n productId: string\n variantId?: string | null\n quantity: number | string\n configuration?: Record<string, unknown> | null\n metadata?: Record<string, unknown> | null\n addedByActorType?: CartLineActorType\n addedByActorId?: string | null\n}\n\nexport type CartLinesAddOutcome =\n | { status: 'added'; line: CartLineSnapshot; before: null; warnings: CartLineWarning[] }\n | { status: 'merged'; line: CartLineSnapshot; before: CartLineSnapshot; warnings: CartLineWarning[] }\n | { status: 'rejected'; reason: CartLineRejectionReason; nearestValid?: number }\n\ntype CartLineAddUndoPayload = { before: CartLineSnapshot | null; after: CartLineSnapshot }\n\nconst addLineCommand: CommandHandler<CartLinesAddInput, CartLinesAddOutcome> = {\n id: 'cart.lines.add',\n // \u00A73.1a's undo table: reverses a buyer-visible content change.\n isUndoable: true,\n async execute(input, ctx): Promise<CartLinesAddOutcome> {\n const { tenantId, organizationId, rawScope } = resolveLineAuthScopeIds(input)\n const scope: CartLineScope = { tenantId, organizationId }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n const cart = await loadMutableCart(em, scope, input.cartId, { requireActive: true })\n assertStorefrontHasRouteContext(cart, input)\n const assortmentScope = resolveEnforcedScope(cart, rawScope)\n\n const requestedQuantity = toQuantityNumber(input.quantity)\n if (!(requestedQuantity > 0)) throw badRequest('[internal] quantity must be greater than zero')\n\n const variantId = input.variantId ?? null\n\n const product = await findOneWithDecryption(em, CatalogProduct, { id: input.productId, tenantId, organizationId, deletedAt: null, isActive: true }, {}, scope)\n if (!product) return { status: 'rejected', reason: 'product_unavailable' }\n\n let variant: CatalogProductVariant | null = null\n if (variantId) {\n variant = await findOneWithDecryption(em, CatalogProductVariant, { id: variantId, product: input.productId, tenantId, organizationId, deletedAt: null, isActive: true }, {}, scope)\n if (!variant) return { status: 'rejected', reason: 'product_unavailable' }\n }\n\n const rules = await resolveQuantityRulesForProduct(em, ctx.container, scope, cart.storeId ?? null, product, variantId)\n const check = checkQuantityRule(rules, requestedQuantity)\n if (check.status === 'rejected') return { status: 'rejected', reason: check.reason, nearestValid: check.nearestValid }\n const warnings: CartLineWarning[] = check.status === 'accepted_clamped' ? ['quantity_capped'] : []\n const acceptedQuantity = check.quantity\n\n const visible = await checkSingleLineVisibility(em, ctx.container, scope, { productId: input.productId, variantId }, assortmentScope)\n if (!visible) {\n await emitVisibilityRejectedEvent(cart, 1, 'lines.add')\n return { status: 'rejected', reason: 'product_unavailable' }\n }\n\n const actor = resolveLineActor(input, { addedByActorType: input.addedByActorType, addedByActorId: input.addedByActorId })\n const now = new Date()\n\n let outcome: CartLinesAddOutcome | null = null\n let changedLine: CartLine | null = null\n let changedAction: 'created' | 'updated' | null = null\n\n // Step 8.2 (QA F12) \u2014 populated only when the mutation phase below actually accepted\n // the line (`shouldRecompute` gates every phase that follows on this).\n const createdGiftLines: CartLine[] = []\n const removedGiftLines: CartLine[] = []\n const promotionWarnings: CartPromotionWarning[] = []\n const shouldRecompute = () => !!outcome && outcome.status !== 'rejected'\n\n // The merge-by-quantity decision below (`findExistingCustomerLine`) and its write are a\n // read-decide-write sequence on a row that may not exist yet, so a row-level lock can't\n // protect it. A per-cart Postgres advisory lock \u2014 held for the whole transaction, the\n // same primitive `attachments`/`notifications`/`sso` already use \u2014 serializes every\n // `cart.lines.add`/`.bulkAdd` call against this cart instead, closing the race\n // `TC-CART-CONC-05` reproduced (a silently lost update, and a raw 500 off\n // `cart_lines_unique_line_key`).\n await withAtomicFlush(em, [\n async () => {\n await em.execute('select pg_advisory_xact_lock(hashtextextended(?::text, 0))', [`cart:lines:${cart.id}`])\n },\n async () => {\n const existing = await findExistingCustomerLine(em, scope, cart.id, input.productId, variantId, input.configuration ?? null)\n const pricingContext = buildCartPricingRowFilterContext(cart)\n const variantIds = variantId ? [variantId] : []\n const rows = filterRowsForVariant(await fetchPriceRowsBatch(em, scope, pricingContext, [input.productId], variantIds), variantId)\n\n if (existing) {\n const before = toCartLineSnapshot(existing)\n const summedQuantity = Number(existing.quantity) + acceptedQuantity\n const sumCheck = checkQuantityRule(rules, summedQuantity)\n if (sumCheck.status === 'rejected') {\n outcome = { status: 'rejected', reason: sumCheck.reason, nearestValid: sumCheck.nearestValid }\n return\n }\n if (sumCheck.status === 'accepted_clamped') warnings.push('quantity_capped')\n\n const resolved = await resolveLinePricing(em, ctx.container, cart, rows, sumCheck.quantity, now)\n if (!resolved.priceRow) {\n outcome = { status: 'rejected', reason: 'product_not_priceable' }\n return\n }\n\n existing.quantity = String(sumCheck.quantity)\n applyPriceSnapshot(existing, resolved, now)\n existing.lineTotals = await computeLineTotals(ctx.container, cart, {\n productId: existing.productId,\n variantId: existing.variantId ?? null,\n name: existing.name,\n quantity: sumCheck.quantity,\n unitPriceNet: resolved.amountNet,\n unitPriceGross: resolved.amountGross,\n taxRate: resolved.taxRatePercent,\n configuration: existing.configuration ?? null,\n })\n\n changedLine = existing\n changedAction = 'updated'\n outcome = { status: 'merged', line: toCartLineSnapshot(existing), before, warnings }\n return\n }\n\n const resolved = await resolveLinePricing(em, ctx.container, cart, rows, acceptedQuantity, now)\n if (!resolved.priceRow) {\n outcome = { status: 'rejected', reason: 'product_not_priceable' }\n return\n }\n\n const lineNumber = await computeStartingLineNumber(em, scope, cart.id)\n const fields = resolveSnapshotFields(product, variant, input.configuration ?? null, input.metadata ?? null)\n\n const newLine = em.create(CartLine, {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n cartId: cart.id,\n lineNumber,\n productId: fields.productId,\n variantId: fields.variantId,\n sku: fields.sku,\n name: fields.name,\n optionValues: fields.optionValues,\n quantity: String(acceptedQuantity),\n origin: 'customer' as const,\n promotionId: null,\n discountTotalNet: null,\n flagState: null,\n configuration: fields.configuration,\n addedByActorType: actor.addedByActorType,\n addedByActorId: actor.addedByActorId,\n metadata: fields.metadata,\n // Placeholders \u2014 both overwritten immediately below (`pricedAt`/`lineTotals` carry\n // no MikroORM default, so `em.create`'s required-data type needs a concrete value).\n pricedAt: now,\n lineTotals: {},\n })\n applyPriceSnapshot(newLine, resolved, now)\n newLine.lineTotals = await computeLineTotals(ctx.container, cart, {\n productId: fields.productId,\n variantId: fields.variantId,\n name: fields.name,\n quantity: acceptedQuantity,\n unitPriceNet: resolved.amountNet,\n unitPriceGross: resolved.amountGross,\n taxRate: resolved.taxRatePercent,\n configuration: fields.configuration,\n })\n em.persist(newLine)\n\n changedLine = newLine\n changedAction = 'created'\n outcome = { status: 'added', line: toCartLineSnapshot(newLine), before: null, warnings }\n },\n // Step 8.2 (QA F12) \u2014 whole-cart promotions re-evaluation + totals recompute, gated\n // on the mutation phase above actually accepting the line (never on a rejection).\n ...buildCartPromotionsAndTotalsPhases({\n em,\n container: ctx.container,\n cart,\n scope,\n assortmentScope,\n now,\n createdGiftLines,\n removedGiftLines,\n promotionWarnings,\n }).map((phase) => async () => {\n if (!shouldRecompute()) return\n await phase()\n }),\n ], { transaction: true, label: 'cart.lines.add' })\n\n if (!outcome) throw new Error('[internal] cart.lines.add produced no outcome')\n // Captured into plain `const`s (never reassigned themselves) rather than read directly\n // off `outcome`/`changedLine`/`changedAction` from here on: those are `let`s reassigned\n // inside the `withAtomicFlush` phase closures above, which (correctly, for cases this\n // step does not control) makes TS stop narrowing them past the `await` \u2014 `never`\n // target-types below absorb that at zero runtime cost.\n const finalOutcome = outcome as CartLinesAddOutcome\n\n if (changedLine && changedAction) {\n const mutatedLine = changedLine as CartLine\n const action = changedAction as 'created' | 'updated'\n // Re-snapshot AFTER the promotions phase ran \u2014 it may have further updated\n // `discountTotalNet` on this exact line if a promotion targets it.\n const resultOutcome: CartLinesAddOutcome =\n finalOutcome.status === 'rejected' ? finalOutcome : { ...finalOutcome, line: toCartLineSnapshot(mutatedLine) }\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({ dataEngine, action, entity: mutatedLine, identifiers: getCartLineIdentifiers(mutatedLine), indexer: cartLineCrudIndexer })\n await emitCartEvent(action === 'created' ? 'cart.line.added' : 'cart.line.updated', {\n id: mutatedLine.id,\n cartId: mutatedLine.cartId,\n organizationId: mutatedLine.organizationId,\n tenantId: mutatedLine.tenantId,\n })\n await emitCartPromotionsAndTotalsSideEffects(dataEngine, cart, { createdGiftLines, removedGiftLines, promotionWarnings })\n return resultOutcome\n }\n\n return finalOutcome\n },\n buildLog: async ({ result }) => {\n if (result.status === 'rejected') return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('cart.audit.lines.add', 'Add cart line'),\n resourceKind: 'cart.cart_line',\n resourceId: result.line.id,\n tenantId: result.line.tenantId,\n organizationId: result.line.organizationId,\n snapshotAfter: result.line,\n payload: {\n undo: {\n before: result.before,\n after: result.line,\n } satisfies CartLineAddUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CartLineAddUndoPayload>(logEntry, { dateFields: ['pricedAt', 'createdAt', 'updatedAt', 'deletedAt'] })\n const lineId = logEntry?.resourceId ?? payload?.after?.id ?? null\n if (!lineId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const line = await em.findOne(CartLine, { id: lineId })\n if (!line) return\n if (logEntry?.tenantId && logEntry?.organizationId) {\n ensureSameScope(line, logEntry.organizationId, logEntry.tenantId)\n }\n\n if (payload?.before) {\n restoreLineFromSnapshot(line, payload.before)\n } else {\n line.deletedAt = new Date()\n }\n await em.flush()\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({ dataEngine, action: payload?.before ? 'updated' : 'deleted', entity: line, identifiers: getCartLineIdentifiers(line), indexer: cartLineCrudIndexer })\n\n // Step 8.2 (QA F12) \u2014 reversing this line is not the inverse of `Cart.totals`; only a\n // full `cart.reprice` (trigger `'undo'`) recomputes the whole-cart aggregate correctly.\n await dispatchCartRepriceForUndo(ctx, { id: line.cartId, tenantId: line.tenantId, organizationId: line.organizationId })\n },\n}\n\nfunction restoreLineFromSnapshot(line: CartLine, snapshot: CartLineSnapshot): void {\n line.quantity = snapshot.quantity\n line.unitPriceNet = snapshot.unitPriceNet\n line.unitPriceGross = snapshot.unitPriceGross\n line.taxRate = snapshot.taxRate\n line.priceKindId = snapshot.priceKindId\n line.priceRowId = snapshot.priceRowId\n line.priceTierMinQuantity = snapshot.priceTierMinQuantity\n line.pricedAt = snapshot.pricedAt\n line.lineTotals = snapshot.lineTotals\n line.discountTotalNet = snapshot.discountTotalNet\n line.deletedAt = snapshot.deletedAt\n}\n\n// ---------------------------------------------------------------------------\n// cart.lines.update\n// ---------------------------------------------------------------------------\n\nexport type CartLinesUpdateInput = CartLineAuthInput & {\n cartId: string\n lineId: string\n quantity: number | string\n}\n\nexport type CartLinesUpdateOutcome =\n | { status: 'updated'; line: CartLineSnapshot; warnings: CartLineWarning[]; priceChanges: CartPriceChange[] }\n | { status: 'rejected'; reason: CartLineRejectionReason; nearestValid?: number }\n\nconst updateLineCommand: CommandHandler<CartLinesUpdateInput, CartLinesUpdateOutcome> = {\n id: 'cart.lines.update',\n // \u00A73.1a's undo table: quantity changes re-price; the \"undo\" a buyer wants is setting the\n // quantity back themselves, not a system rollback to a now-stale price snapshot. No\n // `undo` handler, matching `cart.reprice`'s documented non-undoable treatment.\n isUndoable: false,\n async execute(input, ctx) {\n const { tenantId, organizationId, rawScope } = resolveLineAuthScopeIds(input)\n const scope: CartLineScope = { tenantId, organizationId }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n const cart = await loadMutableCart(em, scope, input.cartId, { requireActive: true })\n assertStorefrontHasRouteContext(cart, input)\n const assortmentScope = resolveEnforcedScope(cart, rawScope)\n\n const line = await findOneWithDecryption(em, CartLine, { id: input.lineId, cartId: cart.id, tenantId, organizationId, deletedAt: null }, {}, scope)\n if (!line) throw notFound('[internal] cart line not found')\n\n // \u00A74.2: \"A promotion line is buyer-immutable (`cart.lines.update`/`.remove` on it\n // return `409 promotion_line_immutable`)\".\n if (line.origin === 'promotion') throw conflict('[internal] promotion_line_immutable')\n\n const requestedQuantity = toQuantityNumber(input.quantity)\n if (!(requestedQuantity > 0)) throw badRequest('[internal] quantity must be greater than zero')\n\n const product = await findOneWithDecryption(em, CatalogProduct, { id: line.productId, tenantId, organizationId, deletedAt: null }, {}, scope)\n if (!product) return { status: 'rejected', reason: 'product_unavailable' }\n\n const rules = await resolveQuantityRulesForProduct(em, ctx.container, scope, cart.storeId ?? null, product, line.variantId ?? null)\n const check = checkQuantityRule(rules, requestedQuantity)\n if (check.status === 'rejected') return { status: 'rejected', reason: check.reason, nearestValid: check.nearestValid }\n const warnings: CartLineWarning[] = check.status === 'accepted_clamped' ? ['quantity_capped'] : []\n const finalQuantity = check.quantity\n\n const visible = await checkSingleLineVisibility(em, ctx.container, scope, { productId: line.productId, variantId: line.variantId ?? null }, assortmentScope)\n if (!visible) {\n await emitVisibilityRejectedEvent(cart, 1, 'lines.update')\n return { status: 'rejected', reason: 'product_unavailable' }\n }\n\n const now = new Date()\n const sameTier = await isWithinStoredPriceTier(em, scope, line, finalQuantity)\n // \u00A75.2 trigger 1 + \u00A75.1's `priceChanges` disclosure (Step 5.1): populated only when the\n // tier-crossing re-price below actually moved the unit price, never for a same-tier\n // quantity change. `Cart.lastPriceChangeAt` (\u00A75.3: \"the cart records last_price_change_at\")\n // is set on `cart` \u2014 already loaded on this same `em` by `loadMutableCart` \u2014 so the single\n // `em.flush()` below persists both the line's new snapshot and the cart's own timestamp.\n const priceChanges: CartPriceChange[] = []\n if (!sameTier) {\n const pricingContext = buildCartPricingRowFilterContext(cart)\n const variantIds = line.variantId ? [line.variantId] : []\n const rows = filterRowsForVariant(await fetchPriceRowsBatch(em, scope, pricingContext, [line.productId], variantIds), line.variantId ?? null)\n const resolved = await resolveLinePricing(em, ctx.container, cart, rows, finalQuantity, now)\n if (!resolved.priceRow) return { status: 'rejected', reason: 'product_not_priceable' }\n const oldUnitPriceNet = line.unitPriceNet ?? null\n const oldUnitPriceGross = line.unitPriceGross ?? null\n applyPriceSnapshot(line, resolved, now)\n line.lineTotals = await computeLineTotals(ctx.container, cart, {\n productId: line.productId,\n variantId: line.variantId ?? null,\n name: line.name,\n quantity: finalQuantity,\n unitPriceNet: resolved.amountNet,\n unitPriceGross: resolved.amountGross,\n taxRate: resolved.taxRatePercent,\n configuration: line.configuration ?? null,\n })\n const newUnitPriceNet = line.unitPriceNet ?? null\n const newUnitPriceGross = line.unitPriceGross ?? null\n if (oldUnitPriceNet !== newUnitPriceNet || oldUnitPriceGross !== newUnitPriceGross) {\n priceChanges.push({\n lineId: line.id,\n productId: line.productId,\n oldUnitPriceNet,\n newUnitPriceNet,\n oldUnitPriceGross,\n newUnitPriceGross,\n reason: 'tier_change',\n })\n cart.lastPriceChangeAt = now\n }\n }\n // Step 8.2 (QA F12) \u2014 this command previously closed with a single bare `em.flush()`\n // here. Adding the whole-cart promotions/totals recompute means a QUERY (the fresh\n // full-line fetch `buildCartPromotionsAndTotalsPhases` needs) now runs after this\n // scalar mutation on the SAME `em` \u2014 `packages/core/AGENTS.md`'s \"Entity Update Safety\"\n // rule requires `withAtomicFlush` for that, so this is no longer a plain flush. Every\n // validation/rejection check above (quantity rule, visibility, not-priceable) still\n // runs BEFORE this point and returns directly, matching `cart.lines.add`'s own\n // validate-first-then-atomic-mutate structure.\n const createdGiftLines: CartLine[] = []\n const removedGiftLines: CartLine[] = []\n const promotionWarnings: CartPromotionWarning[] = []\n\n await withAtomicFlush(em, [\n async () => {\n line.quantity = String(finalQuantity)\n },\n ...buildCartPromotionsAndTotalsPhases({\n em,\n container: ctx.container,\n cart,\n scope,\n assortmentScope,\n now,\n createdGiftLines,\n removedGiftLines,\n promotionWarnings,\n }),\n ], { transaction: true, label: 'cart.lines.update' })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({ dataEngine, action: 'updated', entity: line, identifiers: getCartLineIdentifiers(line), indexer: cartLineCrudIndexer })\n await emitCartEvent('cart.line.updated', { id: line.id, cartId: line.cartId, organizationId: line.organizationId, tenantId: line.tenantId })\n if (priceChanges.length > 0) {\n await emitCartEvent('cart.price.changed', {\n cartId: cart.id,\n tenantId: cart.tenantId,\n organizationId: cart.organizationId,\n changes: priceChanges,\n })\n }\n await emitCartPromotionsAndTotalsSideEffects(dataEngine, cart, { createdGiftLines, removedGiftLines, promotionWarnings })\n\n // Re-snapshot AFTER the promotions phase ran \u2014 it may have set/cleared this line's own\n // `discountTotalNet` this same cycle.\n return { status: 'updated', line: toCartLineSnapshot(line), warnings, priceChanges }\n },\n}\n\n// ---------------------------------------------------------------------------\n// cart.lines.remove\n// ---------------------------------------------------------------------------\n\nexport type CartLinesRemoveInput = CartLineAuthInput & {\n cartId: string\n lineId: string\n}\n\nexport type CartLinesRemoveResult = { removed: true; line: CartLineSnapshot }\n\ntype CartLineRemoveUndoPayload = { before: CartLineSnapshot; after: CartLineSnapshot }\n\nconst removeLineCommand: CommandHandler<CartLinesRemoveInput, CartLinesRemoveResult> = {\n id: 'cart.lines.remove',\n // \u00A73.1a's undo table: reverses a buyer-visible content change.\n isUndoable: true,\n async execute(input, ctx) {\n const { tenantId, organizationId, rawScope } = resolveLineAuthScopeIds(input)\n const scope: CartLineScope = { tenantId, organizationId }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n // `.remove` requires an active cart, same as `.add`/`.update`/`.bulkAdd`: `cart.get`\n // resolves a token regardless of status, so without this a stale token could remove a\n // line from a `merged`/`expired`/`converted` cart and the promotions/totals recompute\n // below would persist new `Cart.totals` on a cart whose order has already been finalized.\n const cart = await loadMutableCart(em, scope, input.cartId, { requireActive: true })\n const assortmentScope = resolveEnforcedScope(cart, rawScope)\n\n const line = await findOneWithDecryption(em, CartLine, { id: input.lineId, cartId: cart.id, tenantId, organizationId, deletedAt: null }, {}, scope)\n if (!line) throw notFound('[internal] cart line not found')\n\n if (line.origin === 'promotion') throw conflict('[internal] promotion_line_immutable')\n\n const before = toCartLineSnapshot(line)\n const now = new Date()\n\n // Step 8.2 (QA F12) \u2014 same reasoning as `cart.lines.update`'s own comment: a QUERY (the\n // fresh full-line fetch the whole-cart recompute needs) now runs after this scalar\n // mutation on the SAME `em`, so the former bare `em.flush()` becomes `withAtomicFlush`.\n const createdGiftLines: CartLine[] = []\n const removedGiftLines: CartLine[] = []\n const promotionWarnings: CartPromotionWarning[] = []\n\n await withAtomicFlush(em, [\n async () => {\n line.deletedAt = now\n },\n ...buildCartPromotionsAndTotalsPhases({\n em,\n container: ctx.container,\n cart,\n scope,\n assortmentScope,\n now,\n createdGiftLines,\n removedGiftLines,\n promotionWarnings,\n }),\n ], { transaction: true, label: 'cart.lines.remove' })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({ dataEngine, action: 'deleted', entity: line, identifiers: getCartLineIdentifiers(line), indexer: cartLineCrudIndexer })\n await emitCartEvent('cart.line.removed', { id: line.id, cartId: line.cartId, organizationId: line.organizationId, tenantId: line.tenantId })\n await emitCartPromotionsAndTotalsSideEffects(dataEngine, cart, { createdGiftLines, removedGiftLines, promotionWarnings })\n\n return { removed: true, line: toCartLineSnapshot(line) }\n },\n buildLog: async ({ result }) => {\n const { translate } = await resolveTranslations()\n const before = { ...result.line, deletedAt: null }\n return {\n actionLabel: translate('cart.audit.lines.remove', 'Remove cart line'),\n resourceKind: 'cart.cart_line',\n resourceId: result.line.id,\n tenantId: result.line.tenantId,\n organizationId: result.line.organizationId,\n snapshotBefore: before,\n snapshotAfter: result.line,\n payload: {\n undo: { before, after: result.line } satisfies CartLineRemoveUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CartLineRemoveUndoPayload>(logEntry, { dateFields: ['pricedAt', 'createdAt', 'updatedAt', 'deletedAt'] })\n const lineId = logEntry?.resourceId ?? payload?.before?.id ?? null\n if (!lineId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const line = await em.findOne(CartLine, { id: lineId })\n if (!line) return\n if (logEntry?.tenantId && logEntry?.organizationId) {\n ensureSameScope(line, logEntry.organizationId, logEntry.tenantId)\n }\n line.deletedAt = null\n await em.flush()\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({ dataEngine, action: 'updated', entity: line, identifiers: getCartLineIdentifiers(line), indexer: cartLineCrudIndexer })\n await emitCartEvent('cart.line.added', { id: line.id, cartId: line.cartId, organizationId: line.organizationId, tenantId: line.tenantId })\n\n // Step 8.2 (QA F12) \u2014 restoring this line is not the inverse of `Cart.totals`; only a\n // full `cart.reprice` (trigger `'undo'`) recomputes the whole-cart aggregate correctly.\n await dispatchCartRepriceForUndo(ctx, { id: line.cartId, tenantId: line.tenantId, organizationId: line.organizationId })\n },\n}\n\n// ---------------------------------------------------------------------------\n// cart.lines.bulkAdd\n// ---------------------------------------------------------------------------\n\nexport type CartLinesBulkAddEntry = {\n productId: string\n variantId?: string | null\n quantity: number | string\n configuration?: Record<string, unknown> | null\n metadata?: Record<string, unknown> | null\n}\n\nexport type CartLinesBulkAddInput = CartLineAuthInput & {\n cartId: string\n lines: CartLinesBulkAddEntry[]\n addedByActorType?: CartLineActorType\n addedByActorId?: string | null\n}\n\nexport type CartLinesBulkAddEntryOutcome =\n | { index: number; status: 'added'; line: CartLineSnapshot; before: null; warnings: CartLineWarning[] }\n | { index: number; status: 'merged'; line: CartLineSnapshot; before: CartLineSnapshot; warnings: CartLineWarning[] }\n | { index: number; status: 'rejected'; reason: CartLineRejectionReason; nearestValid?: number }\n\nexport type CartLinesBulkAddResult = {\n results: CartLinesBulkAddEntryOutcome[]\n addedCount: number\n rejectedCount: number\n}\n\ntype CartLineBulkAddUndoPayload = { entries: Array<{ before: CartLineSnapshot | null; after: CartLineSnapshot }> }\n\nconst bulkAddLinesCommand: CommandHandler<CartLinesBulkAddInput, CartLinesBulkAddResult> = {\n id: 'cart.lines.bulkAdd',\n // \u00A73.1a's undo table: reverses a buyer-visible content change.\n isUndoable: true,\n async execute(input, ctx) {\n if (input.lines.length > MAX_BULK_ADD_ENTRIES) {\n throw badRequest(`[internal] cart.lines.bulkAdd accepts at most ${MAX_BULK_ADD_ENTRIES} entries`)\n }\n\n const { tenantId, organizationId, rawScope } = resolveLineAuthScopeIds(input)\n const scope: CartLineScope = { tenantId, organizationId }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n const cart = await loadMutableCart(em, scope, input.cartId, { requireActive: true })\n assertStorefrontHasRouteContext(cart, input)\n const assortmentScope = resolveEnforcedScope(cart, rawScope)\n const actor = resolveLineActor(input, { addedByActorType: input.addedByActorType, addedByActorId: input.addedByActorId })\n\n if (input.lines.length === 0) return { results: [], addedCount: 0, rejectedCount: 0 }\n\n // ---- batched reads (\u00A76a.1c: \"never one-at-a-time\") \u2014 nothing mutated yet ----\n const visibilityRefs: CartVisibilityLineRef[] = input.lines.map((entry) => ({ productId: entry.productId, variantId: entry.variantId ?? null }))\n const visibility = await evaluateCartVisibility({ lines: visibilityRefs, scope: assortmentScope, tenantScope: scope, em, container: ctx.container })\n\n const productIds = Array.from(new Set(input.lines.map((entry) => entry.productId)))\n const products = await findWithDecryption(em, CatalogProduct, { id: { $in: productIds }, tenantId, organizationId, deletedAt: null, isActive: true }, {}, scope)\n const productsById = new Map((products as CatalogProduct[]).map((product) => [product.id, product]))\n\n const variantIds = Array.from(new Set(input.lines.map((entry) => entry.variantId).filter((id): id is string => !!id)))\n const variants = variantIds.length\n ? await findWithDecryption(em, CatalogProductVariant, { id: { $in: variantIds }, tenantId, organizationId, deletedAt: null, isActive: true }, {}, scope)\n : []\n const variantsById = new Map((variants as CatalogProductVariant[]).map((variant) => [variant.id, variant]))\n\n const existingLines = await findWithDecryption(\n em,\n CartLine,\n { cartId: cart.id, productId: { $in: productIds }, promotionId: null, deletedAt: null, tenantId, organizationId },\n {},\n scope,\n )\n const lineByKey = new Map<string, CartLine>()\n for (const existingLine of existingLines as CartLine[]) {\n lineByKey.set(buildMergeKey(existingLine.productId, existingLine.variantId ?? null, existingLine.configuration ?? null), existingLine)\n }\n\n const pricingContext = buildCartPricingRowFilterContext(cart)\n const priceRows = await fetchPriceRowsBatch(em, scope, pricingContext, productIds, variantIds)\n // `CatalogProductVariant.product` resolves to an unpopulated Reference proxy here (no\n // `populate` was requested), so the product id a variant belongs to is read off the\n // REQUEST entries instead of that lazy relation.\n const variantToProduct = new Map<string, string>()\n for (const entry of input.lines) {\n if (entry.variantId) variantToProduct.set(entry.variantId, entry.productId)\n }\n const rowsByProduct = groupRowsByProduct(priceRows, variantToProduct)\n\n const policyResolver = tryResolvePolicyResolutionService(ctx.container)\n const policies = policyResolver\n ? await policyResolver.resolveMany(\n em,\n input.lines.map((entry) => ({ tenantId, organizationId, storeId: cart.storeId ?? null, productId: entry.productId, variantId: entry.variantId ?? null })),\n )\n : input.lines.map(() => null)\n\n let nextLineNumber = await computeStartingLineNumber(em, scope, cart.id)\n\n const outcomes: CartLinesBulkAddEntryOutcome[] = new Array(input.lines.length)\n const changedLines: CartLine[] = []\n const changedActions: Array<'created' | 'updated'> = []\n // TC-CART-R1-10: counts ONLY entries this call rejected for a visibility reason, never a\n // quantity-rule/not-priceable/missing-product rejection \u2014 see `emitVisibilityRejectedEvent`.\n // Phases run sequentially (SPEC-018, the comment above), so a shared closure counter is safe.\n let visibilityRejectedCount = 0\n\n // ---- per-entry decide + mutate, one `withAtomicFlush` phase per entry so an\n // earlier entry's mutation is flushed before a later entry's (in-memory-only) reads\n // run \u2014 SPEC-018's rule, even though every read here is served from the maps above\n // rather than a fresh DB query.\n const phases = input.lines.map((entry, index) => async () => {\n const variantId = entry.variantId ?? null\n let requestedQuantity: number\n try {\n requestedQuantity = toQuantityNumber(entry.quantity)\n } catch {\n outcomes[index] = { index, status: 'rejected', reason: 'product_unavailable' }\n return\n }\n if (!(requestedQuantity > 0)) {\n outcomes[index] = { index, status: 'rejected', reason: 'product_unavailable' }\n return\n }\n\n const product = productsById.get(entry.productId) ?? null\n if (!product) {\n outcomes[index] = { index, status: 'rejected', reason: 'product_unavailable' }\n return\n }\n const variant = variantId ? variantsById.get(variantId) ?? null : null\n if (variantId && !variant) {\n outcomes[index] = { index, status: 'rejected', reason: 'product_unavailable' }\n return\n }\n\n const rules = deriveQuantityRules(policies[index] ?? null, product)\n const check = checkQuantityRule(rules, requestedQuantity)\n if (check.status === 'rejected') {\n outcomes[index] = { index, status: 'rejected', reason: check.reason, nearestValid: check.nearestValid }\n return\n }\n const warnings: CartLineWarning[] = check.status === 'accepted_clamped' ? ['quantity_capped'] : []\n const acceptedQuantity = check.quantity\n\n const visible = isVisible(visibility.get(cartVisibilityKey({ productId: entry.productId, variantId })) ?? 'unavailable')\n if (!visible) {\n outcomes[index] = { index, status: 'rejected', reason: 'product_unavailable' }\n visibilityRejectedCount += 1\n return\n }\n\n const now = new Date()\n const key = buildMergeKey(entry.productId, variantId, entry.configuration ?? null)\n const existingLine = lineByKey.get(key) ?? null\n const rows = filterRowsForVariant(rowsByProduct.get(entry.productId) ?? [], variantId)\n\n if (existingLine) {\n const before = toCartLineSnapshot(existingLine)\n const summedQuantity = Number(existingLine.quantity) + acceptedQuantity\n const sumCheck = checkQuantityRule(rules, summedQuantity)\n if (sumCheck.status === 'rejected') {\n outcomes[index] = { index, status: 'rejected', reason: sumCheck.reason, nearestValid: sumCheck.nearestValid }\n return\n }\n if (sumCheck.status === 'accepted_clamped') warnings.push('quantity_capped')\n\n const resolved = await resolveLinePricing(em, ctx.container, cart, rows, sumCheck.quantity, now)\n if (!resolved.priceRow) {\n outcomes[index] = { index, status: 'rejected', reason: 'product_not_priceable' }\n return\n }\n existingLine.quantity = String(sumCheck.quantity)\n applyPriceSnapshot(existingLine, resolved, now)\n existingLine.lineTotals = await computeLineTotals(ctx.container, cart, {\n productId: existingLine.productId,\n variantId: existingLine.variantId ?? null,\n name: existingLine.name,\n quantity: sumCheck.quantity,\n unitPriceNet: resolved.amountNet,\n unitPriceGross: resolved.amountGross,\n taxRate: resolved.taxRatePercent,\n configuration: existingLine.configuration ?? null,\n })\n lineByKey.set(key, existingLine)\n const after = toCartLineSnapshot(existingLine)\n outcomes[index] = { index, status: 'merged', line: after, before, warnings }\n changedLines.push(existingLine)\n changedActions.push('updated')\n return\n }\n\n const resolved = await resolveLinePricing(em, ctx.container, cart, rows, acceptedQuantity, now)\n if (!resolved.priceRow) {\n outcomes[index] = { index, status: 'rejected', reason: 'product_not_priceable' }\n return\n }\n const fields = resolveSnapshotFields(product, variant, entry.configuration ?? null, entry.metadata ?? null)\n const newLine = em.create(CartLine, {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n cartId: cart.id,\n lineNumber: nextLineNumber++,\n productId: fields.productId,\n variantId: fields.variantId,\n sku: fields.sku,\n name: fields.name,\n optionValues: fields.optionValues,\n quantity: String(acceptedQuantity),\n origin: 'customer' as const,\n promotionId: null,\n discountTotalNet: null,\n flagState: null,\n configuration: fields.configuration,\n addedByActorType: actor.addedByActorType,\n addedByActorId: actor.addedByActorId,\n metadata: fields.metadata,\n pricedAt: now,\n lineTotals: {},\n })\n applyPriceSnapshot(newLine, resolved, now)\n newLine.lineTotals = await computeLineTotals(ctx.container, cart, {\n productId: fields.productId,\n variantId: fields.variantId,\n name: fields.name,\n quantity: acceptedQuantity,\n unitPriceNet: resolved.amountNet,\n unitPriceGross: resolved.amountGross,\n taxRate: resolved.taxRatePercent,\n configuration: fields.configuration,\n })\n em.persist(newLine)\n lineByKey.set(key, newLine)\n const after = toCartLineSnapshot(newLine)\n outcomes[index] = { index, status: 'added', line: after, before: null, warnings }\n changedLines.push(newLine)\n changedActions.push('created')\n })\n\n // Step 8.2 (QA F12) \u2014 whole-cart promotions re-evaluation + totals recompute, appended\n // after every entry's own mutation phase, mirroring `cart.reprice`'s own ordering (line\n // mutations first, promotions phase, totals phase last).\n const now = new Date()\n const createdGiftLines: CartLine[] = []\n const removedGiftLines: CartLine[] = []\n const promotionWarnings: CartPromotionWarning[] = []\n\n // Same per-cart advisory lock as `cart.lines.add` (above) \u2014 `lineByKey`'s merge-by-quantity\n // decision is read-decide-write against rows that may not exist yet, so two concurrent\n // `bulkAdd`/`add` calls on this cart must serialize, not just this call's own entries\n // against each other.\n await withAtomicFlush(em, [\n async () => {\n await em.execute('select pg_advisory_xact_lock(hashtextextended(?::text, 0))', [`cart:lines:${cart.id}`])\n },\n ...phases,\n ...buildCartPromotionsAndTotalsPhases({\n em,\n container: ctx.container,\n cart,\n scope,\n assortmentScope,\n now,\n createdGiftLines,\n removedGiftLines,\n promotionWarnings,\n }),\n ], { transaction: true, label: 'cart.lines.bulkAdd' })\n await emitVisibilityRejectedEvent(cart, visibilityRejectedCount, 'lines.bulkAdd')\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n for (let i = 0; i < changedLines.length; i += 1) {\n const changedLine = changedLines[i]\n const action = changedActions[i]\n await emitCrudSideEffects({ dataEngine, action, entity: changedLine, identifiers: getCartLineIdentifiers(changedLine), indexer: cartLineCrudIndexer })\n await emitCartEvent(action === 'created' ? 'cart.line.added' : 'cart.line.updated', {\n id: changedLine.id,\n cartId: changedLine.cartId,\n organizationId: changedLine.organizationId,\n tenantId: changedLine.tenantId,\n })\n }\n await emitCartPromotionsAndTotalsSideEffects(dataEngine, cart, { createdGiftLines, removedGiftLines, promotionWarnings })\n\n // Re-snapshot every added/merged outcome AFTER the promotions phase ran \u2014 it may have\n // further updated `discountTotalNet` on any of these lines.\n const changedLineById = new Map(changedLines.map((line) => [line.id, line]))\n for (let i = 0; i < outcomes.length; i += 1) {\n const outcome = outcomes[i]\n if (outcome.status !== 'added' && outcome.status !== 'merged') continue\n const changedLine = changedLineById.get(outcome.line.id)\n if (changedLine) outcomes[i] = { ...outcome, line: toCartLineSnapshot(changedLine) }\n }\n\n const addedCount = outcomes.filter((outcome) => outcome.status === 'added' || outcome.status === 'merged').length\n return { results: outcomes, addedCount, rejectedCount: outcomes.length - addedCount }\n },\n buildLog: async ({ result }) => {\n const entries = result.results\n .filter((outcome): outcome is Extract<CartLinesBulkAddEntryOutcome, { status: 'added' | 'merged' }> => outcome.status === 'added' || outcome.status === 'merged')\n .map((outcome) => ({ before: outcome.before, after: outcome.line }))\n if (!entries.length) return null\n const { translate } = await resolveTranslations()\n const first = entries[0].after\n return {\n actionLabel: translate('cart.audit.lines.bulkAdd', 'Add cart lines (bulk)'),\n resourceKind: 'cart.cart_line',\n resourceId: first.cartId,\n tenantId: first.tenantId,\n organizationId: first.organizationId,\n payload: {\n undo: {\n entries,\n } satisfies CartLineBulkAddUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CartLineBulkAddUndoPayload>(logEntry, { dateFields: ['pricedAt', 'createdAt', 'updatedAt', 'deletedAt'] })\n if (!payload?.entries?.length) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n let cartRef: { id: string; tenantId: string; organizationId: string } | null = null\n for (const entry of payload.entries) {\n const line = await em.findOne(CartLine, { id: entry.after.id })\n if (!line) continue\n ensureSameScope(line, entry.after.organizationId, entry.after.tenantId)\n if (entry.before) {\n restoreLineFromSnapshot(line, entry.before)\n } else {\n line.deletedAt = new Date()\n }\n await em.flush()\n await emitCrudSideEffects({ dataEngine, action: entry.before ? 'updated' : 'deleted', entity: line, identifiers: getCartLineIdentifiers(line), indexer: cartLineCrudIndexer })\n cartRef = { id: line.cartId, tenantId: line.tenantId, organizationId: line.organizationId }\n }\n\n // Step 8.2 (QA F12) \u2014 same reasoning as `cart.lines.add`/`.remove`'s own undo: restoring\n // these lines is not the inverse of `Cart.totals`; one `cart.reprice` dispatch after the\n // whole batch is restored recomputes the whole-cart aggregate correctly.\n if (cartRef) await dispatchCartRepriceForUndo(ctx, cartRef)\n },\n}\n\nfunction buildMergeKey(productId: string, variantId: string | null, configuration: Record<string, unknown> | null): string {\n return `${productId}:${variantId ?? ''}:${canonicalJson(configuration ?? null)}`\n}\n\nregisterCommand(addLineCommand)\nregisterCommand(updateLineCommand)\nregisterCommand(removeLineCommand)\nregisterCommand(bulkAddLinesCommand)\n\nexport { addLineCommand, updateLineCommand, removeLineCommand, bulkAddLinesCommand }\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { asValue } from 'awilix'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandBus, CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { ensureSameScope } from '@open-mercato/shared/lib/commands/scope'\nimport { badRequest, conflict, CrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { E } from '#generated/entities.ids.generated'\n\nimport {\n CatalogProduct,\n CatalogProductPrice,\n CatalogProductVariant,\n} from '@open-mercato/core/modules/catalog/data/entities'\nimport {\n buildPriceRowFilter,\n filterRowsForVariant,\n resolvePriceKindId,\n resolvePriceVariantId,\n} from '@open-mercato/core/modules/catalog/lib/pricing'\nimport type { PriceRow, PricingContext } from '@open-mercato/core/modules/catalog/lib/pricing'\nimport type {\n BuyerPricingContext,\n CatalogBuyerPricingService,\n ResolvedLinePrice,\n} from '@open-mercato/core/modules/catalog/services/catalogBuyerPricingService'\n\n// `availability` is NOT in this module's `requires` (index.ts) \u2014 it is a soft/optional\n// peer, same category as `ecommerce` in `lib/routeContext.ts`. `deriveQuantityRules` is a\n// pure, dependency-free leaf function (no DI, no entities, no container) \u2014 see its own\n// doc comment: \"so other modules (e.g. a future `cart` module) can derive the same rules\n// without depending on `ecommerce`\". Importing it directly carries no runtime coupling to\n// `availability`'s module registration. The one genuinely runtime-coupled piece \u2014\n// `policyResolutionService`, a DI-registered service \u2014 is resolved through a local\n// `tryResolvePolicyResolutionService` below, mirroring `routeContext.ts`'s\n// `tryResolveStoreContextService` pattern: absent module, absent service, graceful\n// fallback to the catalog product's own min/max/increment columns.\nimport { deriveQuantityRules } from '@open-mercato/core/modules/availability/lib/quantityRules'\nimport type { QuantityRulePolicy, QuantityRules } from '@open-mercato/core/modules/availability/lib/quantityRules'\nimport type { PolicyResolutionService } from '@open-mercato/core/modules/availability/lib/policyResolution'\n\nimport type { SalesCalculationService } from '@open-mercato/core/modules/sales/services/salesCalculationService'\nimport type { SalesAdjustmentDraft } from '@open-mercato/core/modules/sales/lib/types'\n\nimport { Cart, CartLine } from '../data/entities'\nimport type { CartChannel, CartLineActorType, CartLineOrigin } from '../data/entities'\nimport {\n cartVisibilityKey,\n evaluateCartVisibility,\n isVisible,\n} from '../lib/visibility'\nimport type { CartVisibilityLineRef, ResolvedAssortmentScope } from '../lib/visibility'\nimport { shouldEnforceAssortmentScope } from '../lib/routeContext'\nimport { withCartLineMutationLock } from '../lib/lineMutationLock'\nimport type { ResolvedCartRouteContext } from '../lib/routeContext'\nimport { buildRecomputeCartTotalsPhase } from '../lib/reprice'\nimport type { CartPriceChange } from '../lib/reprice'\nimport { buildPromotionsEffectsPhase } from '../lib/promotionContext'\nimport type { CartPromotionWarning } from '../lib/promotionContext'\nimport { assertCartNotLocked } from '../lib/lockGuard'\nimport { recordBasketMutationActivity } from '../lib/cartActivity'\nimport { emitCartEvent } from '../events'\n// Type-only \u2014 erased at compile time, so this carries none of the runtime circularity risk\n// a VALUE import would: `commands/cart.ts` already imports `emitVisibilityRejectedEvent`\n// from THIS file (Step 7.4), so a value-level import back from here would create a\n// module-load cycle. See `dispatchCartRepriceForUndo` below for how the undo handlers call\n// `cart.reprice` without importing its handler object.\nimport type { CartRepriceInput, CartRepriceResult } from './cart'\n\n// Step 4.6 \u2014 `cart.lines.add`, `.update`, `.remove`, `.bulkAdd`. Builds directly on Step\n// 4.4's `evaluateCartVisibility` (\u00A76a) and Step 4.5's `cart.create`/`cart.get` + the\n// storefront/trusted route-context split. See the module-level comments on each command\n// below for the ordering and rejection-vs-throw decisions this step had to make.\n\nconst CART_LINE_ENTITY_ID = E.cart.cart_line\nconst cartLineCrudIndexer: CrudIndexerConfig<CartLine> = { entityType: CART_LINE_ENTITY_ID }\n\n// Step 8.2 (QA F12) \u2014 this file's own `Cart`-entity indexer/events config, deliberately\n// duplicated from `commands/cart.ts`'s identical `cartCrudIndexer`/`cartCrudEvents` rather\n// than imported from there: `cart.ts` already imports `emitVisibilityRejectedEvent` from\n// THIS file (Step 7.4), so importing a VALUE back from `cart.ts` here would create a\n// module-load cycle. `lib/reprice.ts`'s own header comment sets the precedent for this exact\n// trade-off (\"Deliberately duplicates `commands/lines.ts`'s small per-line pricing helpers\n// ... rather than exporting and importing lines.ts's private functions\").\nconst CART_ENTITY_ID = E.cart.cart\nconst cartCrudIndexer: CrudIndexerConfig<Cart> = { entityType: CART_ENTITY_ID }\nconst cartCrudEvents: CrudEventsConfig<Cart> = {\n module: 'cart',\n entity: 'cart',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nconst MAX_BULK_ADD_ENTRIES = 100\n// Multiples of an increment are compared as floating-point numbers; this tolerance\n// absorbs IEEE-754 noise (e.g. 0.1 + 0.2) without masking a genuinely non-aligned quantity.\nconst QUANTITY_EPSILON = 1e-6\n\ntype CartLineScope = { tenantId: string; organizationId: string }\n\nfunction getCartLineIdentifiers(line: CartLine) {\n return { id: line.id, organizationId: line.organizationId, tenantId: line.tenantId }\n}\n\n// ---------------------------------------------------------------------------\n// Shared: `cart.line.visibility_rejected` (\u00A711, D-R4-23, TC-CART-R1-10)\n// ---------------------------------------------------------------------------\n\n/**\n * \u00A76a.1c's per-line enforcement points (`'lines.add'`/`'lines.update'`/`'lines.bulkAdd'`,\n * this file's own) plus the two whole-cart/merge passes added at Step 7.4:\n * `'reprice'` \u2014 \u00A76a.2's whole-cart re-visibility pass, trigger 2 (`commands/cart.ts`'s\n * `cart.reprice`); `'merge'` \u2014 \u00A76a.5's merge-time pass (`commands/merge.ts`'s `cart.merge`).\n * Trigger 5 (checkout lock, `commands/lock.ts`'s `cart.lock`) gets NO dedicated value of its\n * own (Step 7.5): `cart.lock` dispatches `cart.reprice` with `trigger: 'lock'`, which runs\n * the SAME \u00A76a.2 whole-cart re-visibility pass as trigger 2/merge and reports through this\n * SAME `'reprice'` value \u2014 a lock-triggered rejection is indistinguishable, at this event's\n * granularity, from any other re-price-triggered one.\n */\nexport type CartVisibilityRejectionTrigger = 'lines.add' | 'lines.update' | 'lines.bulkAdd' | 'reprice' | 'merge'\n\n/**\n * Emitted once per REQUEST with the count of lines this call rejected for a VISIBILITY\n * reason specifically \u2014 never once per rejected line, and never for a rejection the request\n * incurred for an unrelated reason (quantity rule, unpriceable product, a missing\n * product/variant row). \u00A76a.3's no-enumeration-oracle collapse applies to the BUYER-facing\n * `product_unavailable` warning only; this event is the operator-facing signal D-R4-23 names,\n * so it is free to be precise about which rejections were actually a scope mismatch.\n *\n * Exported since Step 7.4 \u2014 `commands/cart.ts`'s \u00A76a.2 pass and `commands/merge.ts`'s \u00A76a.5\n * pass both emit through this SAME helper rather than each re-deriving the event shape.\n */\nexport async function emitVisibilityRejectedEvent(\n cart: Pick<Cart, 'id' | 'tenantId' | 'organizationId'>,\n rejectedCount: number,\n triggeredBy: CartVisibilityRejectionTrigger,\n): Promise<void> {\n if (rejectedCount <= 0) return\n await emitCartEvent('cart.line.visibility_rejected', {\n cartId: cart.id,\n organizationId: cart.organizationId,\n tenantId: cart.tenantId,\n rejectedCount,\n triggeredBy,\n })\n}\n\n// ---------------------------------------------------------------------------\n// Shared: quantity-rule validation (\u00A75.5)\n// ---------------------------------------------------------------------------\n\nexport type CartLineRejectionReason =\n | 'quantity_below_minimum'\n | 'quantity_invalid_increment'\n | 'product_unavailable'\n | 'product_not_priceable'\n\nexport type CartLineWarning = 'quantity_capped'\n\nexport type QuantityRuleCheck =\n | { status: 'accepted'; quantity: number }\n | { status: 'accepted_clamped'; quantity: number; requestedQuantity: number }\n | { status: 'rejected'; reason: 'quantity_below_minimum' | 'quantity_invalid_increment'; nearestValid: number }\n\n/**\n * \u00A75.5's three rules, in the order that keeps `nearestValid` meaningful on its own:\n *\n * 1. Increment alignment is checked against base 0 (a plain \"multiple of N\"), never\n * against `minOrderQuantity` as the base. Anchoring to `minOrderQuantity` instead would\n * make the two constraints interact badly whenever `minOrderQuantity` is itself not a\n * multiple of the increment (e.g. min=7, increment=5): summing two independently valid\n * quantities (merge-by-quantity, \u00A74.2) would then routinely fail re-validation, since\n * sums of base-`min` aligned values are not themselves base-`min` aligned in general.\n * Sums of base-0 aligned values always stay base-0 aligned (basic algebra), which is\n * exactly the property the merge-sum re-check below depends on.\n * 2. Below `minOrderQuantity` is rejected outright, with `nearestValid` bumped up to the\n * smallest increment-aligned value at or above the minimum.\n * 3. Above `maxOrderQuantity` is capped with a warning, never rejected.\n *\n * This ordering means a quantity that is both below-minimum and non-aligned reports\n * `quantity_invalid_increment` (its `nearestValid` already accounts for the minimum too,\n * via the same bump), not `quantity_below_minimum`. Documented here since \u00A75.5's prose\n * states the three rules independently and does not resolve this overlap itself.\n */\nexport function checkQuantityRule(rules: QuantityRules, requestedQuantity: number): QuantityRuleCheck {\n const { minOrderQuantity, maxOrderQuantity, quantityIncrement } = rules\n const hasIncrement = quantityIncrement != null && quantityIncrement > 0\n\n const bumpToMinimum = (value: number): number => {\n if (minOrderQuantity == null || value >= minOrderQuantity) return value\n return hasIncrement ? Math.ceil(minOrderQuantity / quantityIncrement) * quantityIncrement : minOrderQuantity\n }\n\n if (hasIncrement) {\n const roundedSteps = Math.round(requestedQuantity / quantityIncrement)\n let aligned = roundedSteps * quantityIncrement\n if (aligned <= 0) aligned = quantityIncrement\n if (Math.abs(aligned - requestedQuantity) > QUANTITY_EPSILON) {\n return { status: 'rejected', reason: 'quantity_invalid_increment', nearestValid: bumpToMinimum(aligned) }\n }\n }\n\n if (minOrderQuantity != null && requestedQuantity < minOrderQuantity) {\n return { status: 'rejected', reason: 'quantity_below_minimum', nearestValid: bumpToMinimum(minOrderQuantity) }\n }\n\n if (maxOrderQuantity != null && requestedQuantity > maxOrderQuantity) {\n return { status: 'accepted_clamped', quantity: maxOrderQuantity, requestedQuantity }\n }\n\n return { status: 'accepted', quantity: requestedQuantity }\n}\n\nfunction tryResolvePolicyResolutionService(container: { resolve: <T = unknown>(name: string) => T }): PolicyResolutionService | null {\n try {\n return container.resolve<PolicyResolutionService>('policyResolutionService') ?? null\n } catch {\n return null\n }\n}\n\nasync function resolveQuantityRulesForProduct(\n em: EntityManager,\n container: { resolve: <T = unknown>(name: string) => T },\n scope: CartLineScope,\n storeId: string | null,\n product: CatalogProduct,\n variantId: string | null,\n): Promise<QuantityRules> {\n const resolver = tryResolvePolicyResolutionService(container)\n let policy: QuantityRulePolicy = null\n if (resolver) {\n policy = await resolver.resolve(em, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n storeId,\n productId: product.id,\n variantId,\n })\n }\n return deriveQuantityRules(policy, product)\n}\n\n// ---------------------------------------------------------------------------\n// Shared: pricing a line\n// ---------------------------------------------------------------------------\n\nfunction buildCartPricingRowFilterContext(cart: Cart): PricingContext {\n return {\n channelId: cart.salesChannelId ?? null,\n customerId: cart.customerId ?? null,\n customerIds: cart.customerIds ?? [],\n customerGroupIds: cart.customerGroupIds,\n currencyCode: cart.currencyCode,\n priceKindId: cart.priceKindId ?? null,\n quantity: 1,\n date: new Date(),\n }\n}\n\nfunction buildBuyerPricingContext(cart: Cart, quantity: number, date: Date): BuyerPricingContext {\n return {\n channelId: cart.salesChannelId ?? null,\n customerId: cart.customerId ?? null,\n customerIds: cart.customerIds ?? [],\n customerGroupIds: cart.customerGroupIds,\n currencyCode: cart.currencyCode,\n priceKindId: cart.priceKindId ?? null,\n quantity,\n date,\n }\n}\n\n/**\n * Matches `ecommerce/lib/storefrontPricing.ts`'s `fetchPageRows` row-fetch shape (the\n * precedent the task points at): one `CatalogProductPrice` query narrowed by\n * `buildPriceRowFilter`, scoped to the requested product ids (plus any variant ids as an\n * `$or`), never one query per product.\n */\nasync function fetchPriceRowsBatch(\n em: EntityManager,\n scope: CartLineScope,\n pricingContext: PricingContext,\n productIds: string[],\n variantIds: string[],\n): Promise<PriceRow[]> {\n if (!productIds.length) return []\n const targetClause = variantIds.length\n ? { $or: [{ product: { $in: productIds } }, { variant: { $in: variantIds } }] }\n : { product: { $in: productIds } }\n const where = {\n $and: [buildPriceRowFilter(pricingContext), targetClause],\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n }\n const rows = await findWithDecryption(em, CatalogProductPrice, where as never, { populate: ['priceKind', 'offer'] }, scope)\n return rows as unknown as PriceRow[]\n}\n\nfunction groupRowsByProduct(rows: PriceRow[], variantToProduct: Map<string, string>): Map<string, PriceRow[]> {\n const grouped = new Map<string, PriceRow[]>()\n for (const row of rows) {\n const variantId = resolvePriceVariantId(row)\n const productId = variantId ? variantToProduct.get(variantId) ?? null : (typeof row.product === 'string' ? row.product : row.product?.id ?? null)\n if (!productId) continue\n const bucket = grouped.get(productId) ?? []\n bucket.push(row)\n grouped.set(productId, bucket)\n }\n return grouped\n}\n\nasync function resolveLinePricing(\n em: EntityManager,\n container: { resolve: <T = unknown>(name: string) => T },\n cart: Cart,\n rows: PriceRow[],\n quantity: number,\n date: Date,\n): Promise<ResolvedLinePrice> {\n const buyerPricingService = container.resolve<CatalogBuyerPricingService>('catalogBuyerPricingService')\n const context = buildBuyerPricingContext(cart, quantity, date)\n return buyerPricingService.resolveLinePrice(em, rows, context, {\n tenantId: cart.tenantId,\n organizationId: cart.organizationId,\n })\n}\n\nfunction applyPriceSnapshot(line: CartLine, resolved: ResolvedLinePrice, pricedAt: Date): void {\n line.unitPriceNet = resolved.amountNet !== null ? String(resolved.amountNet) : null\n line.unitPriceGross = resolved.amountGross !== null ? String(resolved.amountGross) : null\n line.taxRate = resolved.taxRatePercent !== null ? String(resolved.taxRatePercent) : null\n line.priceKindId = resolved.priceRow ? resolvePriceKindId(resolved.priceRow) : null\n line.priceRowId = resolved.priceRow?.id ?? null\n line.priceTierMinQuantity = resolved.currentTier?.minQuantity ?? null\n line.pricedAt = pricedAt\n}\n\n/**\n * \u00A75.2 trigger 1 is scoped to \"crosses a `min_quantity`/`max_quantity` tier boundary\" \u2014\n * i.e. the bounds of the single `CatalogProductPrice` row that is currently winning\n * (`line.priceRowId`), not a re-derivation of the whole tier ladder. A single row-by-id\n * lookup (cheap, no row-set fetch, no `catalogBuyerPricingService` call) is enough to\n * answer \"did we leave that row's own window\" \u2014 this is the check `.update` uses to decide\n * whether a re-price is even necessary, so a same-tier quantity change never calls the\n * pricing service at all.\n */\nasync function isWithinStoredPriceTier(\n em: EntityManager,\n scope: CartLineScope,\n line: CartLine,\n quantity: number,\n): Promise<boolean> {\n if (!line.priceRowId) return false\n const row = await findOneWithDecryption(\n em,\n CatalogProductPrice,\n { id: line.priceRowId, tenantId: scope.tenantId, organizationId: scope.organizationId },\n {},\n scope,\n )\n if (!row) return false\n const min = row.minQuantity ?? 1\n if (quantity < min) return false\n if (row.maxQuantity != null && quantity > row.maxQuantity) return false\n return true\n}\n\n/**\n * `CartLine.lineTotals` is \"Verbatim `SalesLineCalculationResult`\" (\u00A74.2) \u2014 `sales/AGENTS.md`\n * is explicit that document math MUST go through `salesCalculationService`, never inlined.\n * `calculateLine` is the single-line entry point (no whole-document context required), so a\n * line's own totals snapshot is computed the same way `sales` would compute it for a real\n * document, with the tax/discount arithmetic owned entirely by `sales`.\n */\nasync function computeLineTotals(\n container: { resolve: <T = unknown>(name: string) => T },\n cart: Cart,\n params: {\n productId: string\n variantId: string | null\n name: string\n quantity: number\n unitPriceNet: number | null\n unitPriceGross: number | null\n taxRate: number | null\n configuration: Record<string, unknown> | null\n },\n): Promise<Record<string, unknown>> {\n const salesCalculationService = container.resolve<SalesCalculationService>('salesCalculationService')\n const result = await salesCalculationService.calculateLine({\n documentKind: 'quote',\n line: {\n kind: 'product',\n productId: params.productId,\n productVariantId: params.variantId,\n name: params.name,\n quantity: params.quantity,\n currencyCode: cart.currencyCode,\n unitPriceNet: params.unitPriceNet,\n unitPriceGross: params.unitPriceGross,\n taxRate: params.taxRate,\n configuration: params.configuration,\n },\n context: { tenantId: cart.tenantId, organizationId: cart.organizationId, currencyCode: cart.currencyCode },\n })\n return result as unknown as Record<string, unknown>\n}\n\n// ---------------------------------------------------------------------------\n// Shared: visibility\n// ---------------------------------------------------------------------------\n\ntype CartLineAuthInput =\n | { routeContext: ResolvedCartRouteContext; tenantId?: undefined; organizationId?: undefined; assortmentScope?: undefined }\n | { routeContext?: undefined; tenantId: string; organizationId: string; assortmentScope?: ResolvedAssortmentScope }\n\nfunction resolveLineAuthScopeIds(input: CartLineAuthInput): {\n tenantId: string\n organizationId: string\n rawScope: ResolvedAssortmentScope | undefined\n} {\n if (input.routeContext) {\n return {\n tenantId: input.routeContext.tenantId,\n organizationId: input.routeContext.organizationId,\n rawScope: input.routeContext.assortmentScope,\n }\n }\n return { tenantId: input.tenantId, organizationId: input.organizationId, rawScope: input.assortmentScope }\n}\n\n/** \u00A76a.1b \u2014 enforce iff the cart channel is `storefront` or a scope was resolved for this call. */\nfunction resolveEnforcedScope(cart: Cart, rawScope: ResolvedAssortmentScope | undefined): ResolvedAssortmentScope | undefined {\n return shouldEnforceAssortmentScope(cart.channel, rawScope) ? rawScope : undefined\n}\n\n/**\n * Defense in depth for \u00A76a.1a: every storefront-channel cart must have been created\n * through the route edge, which always resolves and supplies a `routeContext`. A trusted\n * in-process caller that forgets to resolve one for a `storefront` cart is a programming\n * error, not a buyer-facing condition \u2014 it throws rather than silently exempting the call.\n */\nfunction assertStorefrontHasRouteContext(cart: Cart, input: CartLineAuthInput): void {\n if (cart.channel === 'storefront' && !input.routeContext) {\n throw badRequest('[internal] A storefront-channel cart requires a resolved routeContext for its visibility check')\n }\n}\n\nasync function checkSingleLineVisibility(\n em: EntityManager,\n container: { resolve: <T = unknown>(name: string) => T },\n tenantScope: CartLineScope,\n line: CartVisibilityLineRef,\n assortmentScope: ResolvedAssortmentScope | undefined,\n): Promise<boolean> {\n const result = await evaluateCartVisibility({ lines: [line], scope: assortmentScope, tenantScope, em, container })\n return isVisible(result.get(cartVisibilityKey(line)) ?? 'unavailable')\n}\n\n// ---------------------------------------------------------------------------\n// Shared: actor attribution (\u00A74.2)\n// ---------------------------------------------------------------------------\n\ntype ResolvedLineActor = { addedByActorType: CartLineActorType; addedByActorId: string | null }\n\n/**\n * Storefront calls derive the actor from the resolved `routeContext` \u2014 a buyer, identified\n * (`customerUserId`) or guest (`null`), never anything a request body could spoof. Trusted\n * (non-storefront) calls carry no resolved buyer identity at all (\u00A76a.4), so the caller must\n * supply `addedByActorType`/`addedByActorId` explicitly, the same way `cart.create`'s trusted\n * path supplies its own `customerId` rather than inheriting one from a resolved context.\n */\nfunction resolveLineActor(\n input: CartLineAuthInput,\n explicit: { addedByActorType?: CartLineActorType; addedByActorId?: string | null } | undefined,\n): ResolvedLineActor {\n if (input.routeContext) {\n return { addedByActorType: 'buyer', addedByActorId: input.routeContext.customerUserId ?? null }\n }\n if (!explicit?.addedByActorType) {\n throw badRequest('[internal] addedByActorType is required for a trusted (non-storefront) cart.lines call')\n }\n return { addedByActorType: explicit.addedByActorType, addedByActorId: explicit.addedByActorId ?? null }\n}\n\n// ---------------------------------------------------------------------------\n// Shared: cart/line loading, snapshotting, merge-by-quantity lookup\n// ---------------------------------------------------------------------------\n\n/**\n * Uniform not-found, mirroring `cart.get`'s own no-oracle convention (Step 4.5): a wrong\n * tenant/org and a nonexistent id both collapse to the identical message, so a mutation\n * attempt never discloses which of those is actually true \u2014 that part of the no-oracle\n * collapse protects something real (whether a cart with this id/token even exists for this\n * tenant) and stays a 404.\n *\n * Step 7.5 \u2014 ONE exception to that collapse, applied unconditionally regardless of\n * `requireActive`: a `locked` cart gets the distinguishable `423 cart_locked`\n * (`assertCartNotLocked`, `lib/lockGuard.ts`) BEFORE falling through to the generic\n * `requireActive` check below. \u00A79's own text states the mutation-while-locked rejection as a\n * blanket rule with no stated carve-out for any particular caller, which is why this runs\n * for EVERY caller of this function.\n *\n * Step 8.5 (QA F15) \u2014 `requireActive`'s own branch is no longer part of the no-oracle\n * collapse: it now throws a distinguishable `409 cart_not_active` (carrying the cart's real\n * `status`) instead of folding into the generic 404. `GET /current` already discloses a\n * non-active cart's `status` with a `200` (`cartService.resolveBasketByToken` / \u00A78.3 do not\n * gate on `status` at all), so collapsing a WRITE's \"exists, right tenant, wrong status\" into\n * the SAME 404 as \"does not exist\"/\"wrong tenant\" no longer hides anything a client could not\n * already learn from the read side \u2014 it only contradicted `cart.mdx`'s documented `409`\n * contract for a non-active-cart write. The owner ruled explicitly: fix the code to match the\n * docs, not the other way around. \"Exists, wrong tenant\" vs \"exists, right tenant, not\n * active\" remain genuinely different concerns (identity-hiding vs. state-disclosure), so only\n * the latter is relaxed here.\n */\nasync function loadMutableCart(\n em: EntityManager,\n scope: CartLineScope,\n cartId: string,\n options: { requireActive: boolean },\n): Promise<Cart> {\n const cart = await findOneWithDecryption(\n em,\n Cart,\n { id: cartId, tenantId: scope.tenantId, organizationId: scope.organizationId, kind: 'basket' as const },\n {},\n scope,\n )\n if (!cart) throw notFound('[internal] cart not found')\n assertCartNotLocked(cart)\n await recordBasketMutationActivity(em, cart)\n if (options.requireActive && cart.status !== 'active') {\n throw new CrudHttpError(409, { error: '[internal] cart_not_active', code: 'cart_not_active', status: cart.status })\n }\n return cart\n}\n\nexport type CartLineSnapshot = {\n id: string\n cartId: string\n tenantId: string\n organizationId: string\n lineNumber: number\n productId: string\n variantId: string | null\n sku: string | null\n name: string\n optionValues: Record<string, string> | null\n quantity: string\n quantityUnit: string | null\n unitPriceNet: string | null\n unitPriceGross: string | null\n taxRate: string | null\n priceKindId: string | null\n priceRowId: string | null\n priceTierMinQuantity: number | null\n pricedAt: Date\n lineTotals: Record<string, unknown>\n discountTotalNet: string | null\n promotionId: string | null\n origin: CartLineOrigin\n configuration: Record<string, unknown> | null\n addedByActorType: CartLineActorType\n addedByActorId: string | null\n metadata: Record<string, unknown> | null\n createdAt: Date\n updatedAt: Date\n deletedAt: Date | null\n}\n\n// Exported (Step 5.2) so `lib/cartView.ts`'s \u00A710.1 `CartView` envelope builder can map a\n// freshly re-fetched `CartLine` the same way every command in this file already does,\n// instead of duplicating this 20-field mapping a third time. `lib/currentCartRoute.ts`\n// already imports command handlers/types directly from this module (see its own header\n// comment) \u2014 a plain pure-function export carries no additional coupling risk.\nexport function toCartLineSnapshot(line: CartLine): CartLineSnapshot {\n return {\n id: line.id,\n cartId: line.cartId,\n tenantId: line.tenantId,\n organizationId: line.organizationId,\n lineNumber: line.lineNumber,\n productId: line.productId,\n variantId: line.variantId ?? null,\n sku: line.sku ?? null,\n name: line.name,\n optionValues: line.optionValues ?? null,\n quantity: line.quantity,\n quantityUnit: line.quantityUnit ?? null,\n unitPriceNet: line.unitPriceNet ?? null,\n unitPriceGross: line.unitPriceGross ?? null,\n taxRate: line.taxRate ?? null,\n priceKindId: line.priceKindId ?? null,\n priceRowId: line.priceRowId ?? null,\n priceTierMinQuantity: line.priceTierMinQuantity ?? null,\n pricedAt: line.pricedAt,\n lineTotals: line.lineTotals,\n discountTotalNet: line.discountTotalNet ?? null,\n promotionId: line.promotionId ?? null,\n origin: line.origin,\n configuration: line.configuration ?? null,\n addedByActorType: line.addedByActorType,\n addedByActorId: line.addedByActorId ?? null,\n metadata: line.metadata ?? null,\n createdAt: line.createdAt,\n updatedAt: line.updatedAt,\n deletedAt: line.deletedAt ?? null,\n }\n}\n\nfunction toQuantityNumber(value: number | string): number {\n const numeric = typeof value === 'number' ? value : Number(value)\n if (!Number.isFinite(numeric)) throw badRequest('[internal] quantity must be a finite number')\n return numeric\n}\n\n/**\n * Deep-equality on `configuration`, canonicalizing key order first. Avoids re-deriving\n * Postgres's `md5(jsonb::text)` stored-generated-column algorithm in JS (jsonb normalizes\n * key order on storage, so two differently-ordered-but-equal objects hash identically there\n * \u2014 replicating that exactly is fragile); matching on the plain columns plus a canonical\n * deep-equality check is semantically equivalent for the purpose of finding a merge target,\n * and the DB's own `UNIQUE NULLS NOT DISTINCT` index (\u00A74.2) is the authoritative backstop.\n */\nfunction canonicalJson(value: unknown): string {\n if (value === null || value === undefined) return 'null'\n if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`\n if (typeof value === 'object') {\n const obj = value as Record<string, unknown>\n const keys = Object.keys(obj).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(obj[key])}`).join(',')}}`\n }\n return JSON.stringify(value)\n}\n\nfunction configurationsEqual(a: Record<string, unknown> | null | undefined, b: Record<string, unknown> | null | undefined): boolean {\n const left = a ?? null\n const right = b ?? null\n if (left === null && right === null) return true\n if (left === null || right === null) return false\n return canonicalJson(left) === canonicalJson(right)\n}\n\n/** `(cart_id, product_id, variant_id, configuration_hash, promotion_id)` merge-by-quantity lookup, scoped to `origin: 'customer'` lines (`promotion_id IS NULL`) \u2014 the only origin this step ever writes. */\nasync function findExistingCustomerLine(\n em: EntityManager,\n scope: CartLineScope,\n cartId: string,\n productId: string,\n variantId: string | null,\n configuration: Record<string, unknown> | null,\n): Promise<CartLine | null> {\n const candidates = await findWithDecryption(\n em,\n CartLine,\n { cartId, productId, variantId: variantId ?? null, promotionId: null, deletedAt: null, tenantId: scope.tenantId, organizationId: scope.organizationId },\n {},\n scope,\n )\n return (candidates as CartLine[]).find((line) => configurationsEqual(line.configuration ?? null, configuration)) ?? null\n}\n\nasync function computeStartingLineNumber(em: EntityManager, scope: CartLineScope, cartId: string): Promise<number> {\n const existing = await em.find(CartLine, { cartId, tenantId: scope.tenantId, organizationId: scope.organizationId })\n return existing.reduce((max, line) => Math.max(max, line.lineNumber), 0) + 1\n}\n\ntype NewLineFields = {\n productId: string\n variantId: string | null\n sku: string | null\n name: string\n optionValues: Record<string, string> | null\n configuration: Record<string, unknown> | null\n metadata: Record<string, unknown> | null\n}\n\nfunction resolveSnapshotFields(product: CatalogProduct, variant: CatalogProductVariant | null, configuration: Record<string, unknown> | null, metadata: Record<string, unknown> | null): NewLineFields {\n return {\n productId: product.id,\n variantId: variant?.id ?? null,\n sku: variant?.sku ?? product.sku ?? null,\n name: product.title,\n optionValues: variant?.optionValues ?? null,\n configuration: configuration ?? null,\n metadata: metadata ?? null,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Shared: whole-cart promotions + totals recompute (Step 8.2, QA F12)\n// ---------------------------------------------------------------------------\n\n/**\n * Spec `cart-module.md:119`'s write flow for every command \u2014 load, re-price, run through\n * `promotionsService`, `calculateDocumentTotals`, persist totals, all inside the same\n * transaction \u2014 applied to this file's own line-mutation commands, which until this step\n * only ever touched the one line they were mutating and left `Cart.totals`/every OTHER\n * line's `discountTotalNet` stale (QA finding F12: a 2-unit \"5% off 2+ items\" cart showed\n * zeroed totals after add, a stale discount after a quantity cut below the threshold, and a\n * non-zero grand total after deleting the last line).\n *\n * Reuses `cart.reprice`'s OWN phase builders (`lib/promotionContext.ts`'s\n * `buildPromotionsEffectsPhase`, `lib/reprice.ts`'s `buildRecomputeCartTotalsPhase`) rather\n * than a parallel re-implementation \u2014 this function's only job is wiring them into THIS\n * caller's own `withAtomicFlush` phase array, mirroring `commands/cart.ts`'s\n * `repriceCartCommand.execute` almost verbatim for just these two phases (catalog\n * unit-price re-resolution \u2014 `buildCartLineRepricePhases` \u2014 and the \u00A76a.2 whole-cart\n * re-visibility pass are deliberately NOT included: out of this step's scope per the task\n * brief, and already covered by this file's own per-line checks/pricing).\n *\n * Registers `auth` on the container SYNCHRONOUSLY, before returning any phase \u2014 same\n * ordering `cart.reprice` uses, since `promotionsService.evaluate()` resolves its tenant/org\n * scope from that DI token and fails closed (400) without it.\n *\n * The returned phases close over a freshly-populated `lines` array via a leading fetch\n * phase rather than a value the caller fetches up front: every caller here mutates a\n * scalar (create/update/soft-delete a line) BEFORE these phases run, so re-reading the\n * cart's full line set must itself be a phase in the SAME `withAtomicFlush` sequence\n * (`packages/core/AGENTS.md`'s \"Entity Update Safety\" rule) \u2014 never a plain query the caller\n * runs ahead of `withAtomicFlush` entirely. The array is mutated in place by the leading\n * fetch phase (`lines.push(...)`) rather than reassigned, so the SAME reference threads\n * through every later phase exactly like `buildPromotionsEffectsPhase`'s own in-place gift\n * line push/splice already depends on.\n */\nfunction buildCartPromotionsAndTotalsPhases(params: {\n em: EntityManager\n container: CommandRuntimeContext['container']\n cart: Cart\n scope: CartLineScope\n assortmentScope: ResolvedAssortmentScope | undefined\n now: Date\n createdGiftLines: CartLine[]\n removedGiftLines: CartLine[]\n promotionWarnings: CartPromotionWarning[]\n}): Array<() => Promise<void>> {\n const { em, container, cart, scope, assortmentScope, now, createdGiftLines, removedGiftLines, promotionWarnings } = params\n container.register({ auth: asValue({ tenantId: cart.tenantId, orgId: cart.organizationId }) })\n\n const lines: CartLine[] = []\n const adjustments: SalesAdjustmentDraft[] = []\n const changes: CartPriceChange[] = []\n\n const fetchLinesPhase = async () => {\n const fetched = (await findWithDecryption(\n em,\n CartLine,\n { cartId: cart.id, deletedAt: null, tenantId: scope.tenantId, organizationId: scope.organizationId },\n {},\n scope,\n )) as CartLine[]\n lines.push(...fetched)\n }\n\n const promotionsPhase = buildPromotionsEffectsPhase({\n em,\n container,\n cart,\n lines,\n assortmentScope,\n tenantScope: scope,\n now,\n adjustments,\n warnings: promotionWarnings,\n createdGiftLines,\n removedGiftLines,\n })\n\n const totalsPhase = buildRecomputeCartTotalsPhase(container, cart, lines, now, changes, adjustments)\n\n return [fetchLinesPhase, promotionsPhase, totalsPhase]\n}\n\n/**\n * The cart-level side effects every line-mutation command now owes once it also mutates\n * `Cart.totals`/`totalsComputedAt` \u2014 mirrors `cart.reprice`'s own post-commit emission for\n * the SAME three things (the cart's own `updated` CRUD side effect, a created gift line's\n * `created` side effect + `cart.line.added`, a removed gift line's `deleted` side effect +\n * `cart.line.removed`), scoped to just what `buildCartPromotionsAndTotalsPhases` can\n * possibly have produced. Called AFTER the caller's own `withAtomicFlush` commits, same\n * \"side effects only after the DB write lands\" rule as every other emission in this file.\n */\nasync function emitCartPromotionsAndTotalsSideEffects(\n dataEngine: DataEngine,\n cart: Cart,\n outcome: { createdGiftLines: CartLine[]; removedGiftLines: CartLine[]; promotionWarnings: CartPromotionWarning[] },\n): Promise<void> {\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: cart,\n identifiers: { id: cart.id, organizationId: cart.organizationId, tenantId: cart.tenantId },\n events: cartCrudEvents,\n indexer: cartCrudIndexer,\n })\n for (const line of outcome.createdGiftLines) {\n await emitCrudSideEffects({ dataEngine, action: 'created', entity: line, identifiers: getCartLineIdentifiers(line), indexer: cartLineCrudIndexer })\n await emitCartEvent('cart.line.added', { id: line.id, cartId: line.cartId, organizationId: line.organizationId, tenantId: line.tenantId })\n }\n for (const line of outcome.removedGiftLines) {\n await emitCrudSideEffects({ dataEngine, action: 'deleted', entity: line, identifiers: getCartLineIdentifiers(line), indexer: cartLineCrudIndexer })\n await emitCartEvent('cart.line.removed', { id: line.id, cartId: line.cartId, organizationId: line.organizationId, tenantId: line.tenantId })\n }\n for (const warning of outcome.promotionWarnings) {\n if (warning.code !== 'promotion_no_longer_valid') continue\n await emitCartEvent('cart.promotion.no_longer_valid', {\n cartId: cart.id,\n tenantId: cart.tenantId,\n organizationId: cart.organizationId,\n promotionId: warning.promotionId,\n })\n }\n}\n\n/**\n * The undo-side half of QA F12's fix (task brief point 7): `cart.lines.add`/`.remove`/\n * `.bulkAdd` restore their reversed line(s) in their own `undo()` handler, but reversing a\n * line is not the inverse of `Cart.totals` \u2014 only a full `cart.reprice` recomputes the\n * whole-cart aggregate, so this dispatches that command as a follow-up, the same pattern\n * `commands/promotions.ts`'s own `dispatchReprice` helper already establishes for\n * `cart.promotions.apply`/`.remove`.\n *\n * Calls the command bus with the LITERAL id string `'cart.reprice'` rather than importing\n * `repriceCartCommand` from `./cart` \u2014 a value-level import back from here would create a\n * module-load cycle (`cart.ts` already imports from this file). `CartRepriceInput`/\n * `CartRepriceResult` stay `import type`-only above for the identical reason.\n *\n * Trigger reason `'undo'` (`lib/reprice.ts`'s `CartPriceChangeReason`): none of the existing\n * values describe \"a command's own undo handler is restoring a reversed line mutation\" \u2014\n * `'merge'`/`'lock'`/`'promotion_applied'`/`'promotion_removed'` are each already owned by a\n * different caller for a different reason, so reusing one of them would mislabel the\n * `priceChanges`/`cart.price.changed` disclosure this reprice may still produce. An undo has\n * no `ResolvedCartRouteContext` of its own to forward (unlike `cart.get`'s trigger dispatch\n * or `cart.promotions.apply`/`.remove`'s), so this never enforces assortment scope on a gift\n * line a reprice happens to create here \u2014 the same `undefined`-scope behavior every other\n * `routeContext`-less caller in this module already has.\n */\nasync function dispatchCartRepriceForUndo(\n ctx: CommandRuntimeContext,\n cart: { id: string; tenantId: string; organizationId: string },\n): Promise<void> {\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n await commandBus.execute<CartRepriceInput, CartRepriceResult>('cart.reprice', {\n input: { cartId: cart.id, tenantId: cart.tenantId, organizationId: cart.organizationId, trigger: 'undo' },\n ctx,\n })\n}\n\n// ---------------------------------------------------------------------------\n// cart.lines.add\n// ---------------------------------------------------------------------------\n\nexport type CartLinesAddInput = CartLineAuthInput & {\n cartId: string\n productId: string\n variantId?: string | null\n quantity: number | string\n configuration?: Record<string, unknown> | null\n metadata?: Record<string, unknown> | null\n addedByActorType?: CartLineActorType\n addedByActorId?: string | null\n}\n\nexport type CartLinesAddOutcome =\n | { status: 'added'; line: CartLineSnapshot; before: null; warnings: CartLineWarning[] }\n | { status: 'merged'; line: CartLineSnapshot; before: CartLineSnapshot; warnings: CartLineWarning[] }\n | { status: 'rejected'; reason: CartLineRejectionReason; nearestValid?: number }\n\ntype CartLineAddUndoPayload = { before: CartLineSnapshot | null; after: CartLineSnapshot }\n\nconst addLineCommand: CommandHandler<CartLinesAddInput, CartLinesAddOutcome> = {\n id: 'cart.lines.add',\n // \u00A73.1a's undo table: reverses a buyer-visible content change.\n isUndoable: true,\n async execute(input, ctx): Promise<CartLinesAddOutcome> {\n const { tenantId, organizationId, rawScope } = resolveLineAuthScopeIds(input)\n const scope: CartLineScope = { tenantId, organizationId }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n const cart = await loadMutableCart(em, scope, input.cartId, { requireActive: true })\n assertStorefrontHasRouteContext(cart, input)\n const assortmentScope = resolveEnforcedScope(cart, rawScope)\n\n const requestedQuantity = toQuantityNumber(input.quantity)\n if (!(requestedQuantity > 0)) throw badRequest('[internal] quantity must be greater than zero')\n\n const variantId = input.variantId ?? null\n\n const product = await findOneWithDecryption(em, CatalogProduct, { id: input.productId, tenantId, organizationId, deletedAt: null, isActive: true }, {}, scope)\n if (!product) return { status: 'rejected', reason: 'product_unavailable' }\n\n let variant: CatalogProductVariant | null = null\n if (variantId) {\n variant = await findOneWithDecryption(em, CatalogProductVariant, { id: variantId, product: input.productId, tenantId, organizationId, deletedAt: null, isActive: true }, {}, scope)\n if (!variant) return { status: 'rejected', reason: 'product_unavailable' }\n }\n\n const rules = await resolveQuantityRulesForProduct(em, ctx.container, scope, cart.storeId ?? null, product, variantId)\n const check = checkQuantityRule(rules, requestedQuantity)\n if (check.status === 'rejected') return { status: 'rejected', reason: check.reason, nearestValid: check.nearestValid }\n const warnings: CartLineWarning[] = check.status === 'accepted_clamped' ? ['quantity_capped'] : []\n const acceptedQuantity = check.quantity\n\n const visible = await checkSingleLineVisibility(em, ctx.container, scope, { productId: input.productId, variantId }, assortmentScope)\n if (!visible) {\n await emitVisibilityRejectedEvent(cart, 1, 'lines.add')\n return { status: 'rejected', reason: 'product_unavailable' }\n }\n\n const actor = resolveLineActor(input, { addedByActorType: input.addedByActorType, addedByActorId: input.addedByActorId })\n const now = new Date()\n\n let outcome: CartLinesAddOutcome | null = null\n let changedLine: CartLine | null = null\n let changedAction: 'created' | 'updated' | null = null\n\n // Step 8.2 (QA F12) \u2014 populated only when the mutation phase below actually accepted\n // the line (`shouldRecompute` gates every phase that follows on this).\n const createdGiftLines: CartLine[] = []\n const removedGiftLines: CartLine[] = []\n const promotionWarnings: CartPromotionWarning[] = []\n const shouldRecompute = () => !!outcome && outcome.status !== 'rejected'\n\n // The merge-by-quantity decision below (`findExistingCustomerLine`) and its write are a\n // read-decide-write sequence on a row that may not exist yet, so a row-level lock can't\n // protect it. A per-cart Postgres advisory lock \u2014 held for the whole transaction, the\n // same primitive `attachments`/`notifications`/`sso` already use \u2014 serializes every\n // `cart.lines.add`/`.bulkAdd` call against this cart instead, closing the race\n // `TC-CART-CONC-05` reproduced (a silently lost update, and a raw 500 off\n // `cart_lines_unique_line_key`).\n await withCartLineMutationLock(em, cart.id, [\n async () => {\n const existing = await findExistingCustomerLine(em, scope, cart.id, input.productId, variantId, input.configuration ?? null)\n const pricingContext = buildCartPricingRowFilterContext(cart)\n const variantIds = variantId ? [variantId] : []\n const rows = filterRowsForVariant(await fetchPriceRowsBatch(em, scope, pricingContext, [input.productId], variantIds), variantId)\n\n if (existing) {\n const before = toCartLineSnapshot(existing)\n const summedQuantity = Number(existing.quantity) + acceptedQuantity\n const sumCheck = checkQuantityRule(rules, summedQuantity)\n if (sumCheck.status === 'rejected') {\n outcome = { status: 'rejected', reason: sumCheck.reason, nearestValid: sumCheck.nearestValid }\n return\n }\n if (sumCheck.status === 'accepted_clamped') warnings.push('quantity_capped')\n\n const resolved = await resolveLinePricing(em, ctx.container, cart, rows, sumCheck.quantity, now)\n if (!resolved.priceRow) {\n outcome = { status: 'rejected', reason: 'product_not_priceable' }\n return\n }\n\n existing.quantity = String(sumCheck.quantity)\n applyPriceSnapshot(existing, resolved, now)\n existing.lineTotals = await computeLineTotals(ctx.container, cart, {\n productId: existing.productId,\n variantId: existing.variantId ?? null,\n name: existing.name,\n quantity: sumCheck.quantity,\n unitPriceNet: resolved.amountNet,\n unitPriceGross: resolved.amountGross,\n taxRate: resolved.taxRatePercent,\n configuration: existing.configuration ?? null,\n })\n\n changedLine = existing\n changedAction = 'updated'\n outcome = { status: 'merged', line: toCartLineSnapshot(existing), before, warnings }\n return\n }\n\n const resolved = await resolveLinePricing(em, ctx.container, cart, rows, acceptedQuantity, now)\n if (!resolved.priceRow) {\n outcome = { status: 'rejected', reason: 'product_not_priceable' }\n return\n }\n\n const lineNumber = await computeStartingLineNumber(em, scope, cart.id)\n const fields = resolveSnapshotFields(product, variant, input.configuration ?? null, input.metadata ?? null)\n\n const newLine = em.create(CartLine, {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n cartId: cart.id,\n lineNumber,\n productId: fields.productId,\n variantId: fields.variantId,\n sku: fields.sku,\n name: fields.name,\n optionValues: fields.optionValues,\n quantity: String(acceptedQuantity),\n origin: 'customer' as const,\n promotionId: null,\n discountTotalNet: null,\n flagState: null,\n configuration: fields.configuration,\n addedByActorType: actor.addedByActorType,\n addedByActorId: actor.addedByActorId,\n metadata: fields.metadata,\n // Placeholders \u2014 both overwritten immediately below (`pricedAt`/`lineTotals` carry\n // no MikroORM default, so `em.create`'s required-data type needs a concrete value).\n pricedAt: now,\n lineTotals: {},\n })\n applyPriceSnapshot(newLine, resolved, now)\n newLine.lineTotals = await computeLineTotals(ctx.container, cart, {\n productId: fields.productId,\n variantId: fields.variantId,\n name: fields.name,\n quantity: acceptedQuantity,\n unitPriceNet: resolved.amountNet,\n unitPriceGross: resolved.amountGross,\n taxRate: resolved.taxRatePercent,\n configuration: fields.configuration,\n })\n em.persist(newLine)\n\n changedLine = newLine\n changedAction = 'created'\n outcome = { status: 'added', line: toCartLineSnapshot(newLine), before: null, warnings }\n },\n // Step 8.2 (QA F12) \u2014 whole-cart promotions re-evaluation + totals recompute, gated\n // on the mutation phase above actually accepting the line (never on a rejection).\n ...buildCartPromotionsAndTotalsPhases({\n em,\n container: ctx.container,\n cart,\n scope,\n assortmentScope,\n now,\n createdGiftLines,\n removedGiftLines,\n promotionWarnings,\n }).map((phase) => async () => {\n if (!shouldRecompute()) return\n await phase()\n }),\n ], 'cart.lines.add')\n\n if (!outcome) throw new Error('[internal] cart.lines.add produced no outcome')\n // Captured into plain `const`s (never reassigned themselves) rather than read directly\n // off `outcome`/`changedLine`/`changedAction` from here on: those are `let`s reassigned\n // inside the `withAtomicFlush` phase closures above, which (correctly, for cases this\n // step does not control) makes TS stop narrowing them past the `await` \u2014 `never`\n // target-types below absorb that at zero runtime cost.\n const finalOutcome = outcome as CartLinesAddOutcome\n\n if (changedLine && changedAction) {\n const mutatedLine = changedLine as CartLine\n const action = changedAction as 'created' | 'updated'\n // Re-snapshot AFTER the promotions phase ran \u2014 it may have further updated\n // `discountTotalNet` on this exact line if a promotion targets it.\n const resultOutcome: CartLinesAddOutcome =\n finalOutcome.status === 'rejected' ? finalOutcome : { ...finalOutcome, line: toCartLineSnapshot(mutatedLine) }\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({ dataEngine, action, entity: mutatedLine, identifiers: getCartLineIdentifiers(mutatedLine), indexer: cartLineCrudIndexer })\n await emitCartEvent(action === 'created' ? 'cart.line.added' : 'cart.line.updated', {\n id: mutatedLine.id,\n cartId: mutatedLine.cartId,\n organizationId: mutatedLine.organizationId,\n tenantId: mutatedLine.tenantId,\n })\n await emitCartPromotionsAndTotalsSideEffects(dataEngine, cart, { createdGiftLines, removedGiftLines, promotionWarnings })\n return resultOutcome\n }\n\n return finalOutcome\n },\n buildLog: async ({ result }) => {\n if (result.status === 'rejected') return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('cart.audit.lines.add', 'Add cart line'),\n resourceKind: 'cart.cart_line',\n resourceId: result.line.id,\n tenantId: result.line.tenantId,\n organizationId: result.line.organizationId,\n snapshotAfter: result.line,\n payload: {\n undo: {\n before: result.before,\n after: result.line,\n } satisfies CartLineAddUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CartLineAddUndoPayload>(logEntry, { dateFields: ['pricedAt', 'createdAt', 'updatedAt', 'deletedAt'] })\n const lineId = logEntry?.resourceId ?? payload?.after?.id ?? null\n if (!lineId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const line = await em.findOne(CartLine, { id: lineId })\n if (!line) return\n if (logEntry?.tenantId && logEntry?.organizationId) {\n ensureSameScope(line, logEntry.organizationId, logEntry.tenantId)\n }\n\n if (payload?.before) {\n restoreLineFromSnapshot(line, payload.before)\n } else {\n line.deletedAt = new Date()\n }\n await em.flush()\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({ dataEngine, action: payload?.before ? 'updated' : 'deleted', entity: line, identifiers: getCartLineIdentifiers(line), indexer: cartLineCrudIndexer })\n\n // Step 8.2 (QA F12) \u2014 reversing this line is not the inverse of `Cart.totals`; only a\n // full `cart.reprice` (trigger `'undo'`) recomputes the whole-cart aggregate correctly.\n await dispatchCartRepriceForUndo(ctx, { id: line.cartId, tenantId: line.tenantId, organizationId: line.organizationId })\n },\n}\n\nfunction restoreLineFromSnapshot(line: CartLine, snapshot: CartLineSnapshot): void {\n line.quantity = snapshot.quantity\n line.unitPriceNet = snapshot.unitPriceNet\n line.unitPriceGross = snapshot.unitPriceGross\n line.taxRate = snapshot.taxRate\n line.priceKindId = snapshot.priceKindId\n line.priceRowId = snapshot.priceRowId\n line.priceTierMinQuantity = snapshot.priceTierMinQuantity\n line.pricedAt = snapshot.pricedAt\n line.lineTotals = snapshot.lineTotals\n line.discountTotalNet = snapshot.discountTotalNet\n line.deletedAt = snapshot.deletedAt\n}\n\n// ---------------------------------------------------------------------------\n// cart.lines.update\n// ---------------------------------------------------------------------------\n\nexport type CartLinesUpdateInput = CartLineAuthInput & {\n cartId: string\n lineId: string\n quantity: number | string\n}\n\nexport type CartLinesUpdateOutcome =\n | { status: 'updated'; line: CartLineSnapshot; warnings: CartLineWarning[]; priceChanges: CartPriceChange[] }\n | { status: 'rejected'; reason: CartLineRejectionReason; nearestValid?: number }\n\nconst updateLineCommand: CommandHandler<CartLinesUpdateInput, CartLinesUpdateOutcome> = {\n id: 'cart.lines.update',\n // \u00A73.1a's undo table: quantity changes re-price; the \"undo\" a buyer wants is setting the\n // quantity back themselves, not a system rollback to a now-stale price snapshot. No\n // `undo` handler, matching `cart.reprice`'s documented non-undoable treatment.\n isUndoable: false,\n async execute(input, ctx) {\n const { tenantId, organizationId, rawScope } = resolveLineAuthScopeIds(input)\n const scope: CartLineScope = { tenantId, organizationId }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n const cart = await loadMutableCart(em, scope, input.cartId, { requireActive: true })\n assertStorefrontHasRouteContext(cart, input)\n const assortmentScope = resolveEnforcedScope(cart, rawScope)\n\n const line = await findOneWithDecryption(em, CartLine, { id: input.lineId, cartId: cart.id, tenantId, organizationId, deletedAt: null }, {}, scope)\n if (!line) throw notFound('[internal] cart line not found')\n\n // \u00A74.2: \"A promotion line is buyer-immutable (`cart.lines.update`/`.remove` on it\n // return `409 promotion_line_immutable`)\".\n if (line.origin === 'promotion') throw conflict('[internal] promotion_line_immutable')\n\n const requestedQuantity = toQuantityNumber(input.quantity)\n if (!(requestedQuantity > 0)) throw badRequest('[internal] quantity must be greater than zero')\n\n const product = await findOneWithDecryption(em, CatalogProduct, { id: line.productId, tenantId, organizationId, deletedAt: null }, {}, scope)\n if (!product) return { status: 'rejected', reason: 'product_unavailable' }\n\n const rules = await resolveQuantityRulesForProduct(em, ctx.container, scope, cart.storeId ?? null, product, line.variantId ?? null)\n const check = checkQuantityRule(rules, requestedQuantity)\n if (check.status === 'rejected') return { status: 'rejected', reason: check.reason, nearestValid: check.nearestValid }\n const warnings: CartLineWarning[] = check.status === 'accepted_clamped' ? ['quantity_capped'] : []\n const finalQuantity = check.quantity\n\n const visible = await checkSingleLineVisibility(em, ctx.container, scope, { productId: line.productId, variantId: line.variantId ?? null }, assortmentScope)\n if (!visible) {\n await emitVisibilityRejectedEvent(cart, 1, 'lines.update')\n return { status: 'rejected', reason: 'product_unavailable' }\n }\n\n const now = new Date()\n const sameTier = await isWithinStoredPriceTier(em, scope, line, finalQuantity)\n // \u00A75.2 trigger 1 + \u00A75.1's `priceChanges` disclosure (Step 5.1): populated only when the\n // tier-crossing re-price below actually moved the unit price, never for a same-tier\n // quantity change. `Cart.lastPriceChangeAt` (\u00A75.3: \"the cart records last_price_change_at\")\n // is set on `cart` \u2014 already loaded on this same `em` by `loadMutableCart` \u2014 so the single\n // `em.flush()` below persists both the line's new snapshot and the cart's own timestamp.\n const priceChanges: CartPriceChange[] = []\n if (!sameTier) {\n const pricingContext = buildCartPricingRowFilterContext(cart)\n const variantIds = line.variantId ? [line.variantId] : []\n const rows = filterRowsForVariant(await fetchPriceRowsBatch(em, scope, pricingContext, [line.productId], variantIds), line.variantId ?? null)\n const resolved = await resolveLinePricing(em, ctx.container, cart, rows, finalQuantity, now)\n if (!resolved.priceRow) return { status: 'rejected', reason: 'product_not_priceable' }\n const oldUnitPriceNet = line.unitPriceNet ?? null\n const oldUnitPriceGross = line.unitPriceGross ?? null\n applyPriceSnapshot(line, resolved, now)\n line.lineTotals = await computeLineTotals(ctx.container, cart, {\n productId: line.productId,\n variantId: line.variantId ?? null,\n name: line.name,\n quantity: finalQuantity,\n unitPriceNet: resolved.amountNet,\n unitPriceGross: resolved.amountGross,\n taxRate: resolved.taxRatePercent,\n configuration: line.configuration ?? null,\n })\n const newUnitPriceNet = line.unitPriceNet ?? null\n const newUnitPriceGross = line.unitPriceGross ?? null\n if (oldUnitPriceNet !== newUnitPriceNet || oldUnitPriceGross !== newUnitPriceGross) {\n priceChanges.push({\n lineId: line.id,\n productId: line.productId,\n oldUnitPriceNet,\n newUnitPriceNet,\n oldUnitPriceGross,\n newUnitPriceGross,\n reason: 'tier_change',\n })\n cart.lastPriceChangeAt = now\n }\n }\n // Step 8.2 (QA F12) \u2014 this command previously closed with a single bare `em.flush()`\n // here. Adding the whole-cart promotions/totals recompute means a QUERY (the fresh\n // full-line fetch `buildCartPromotionsAndTotalsPhases` needs) now runs after this\n // scalar mutation on the SAME `em` \u2014 `packages/core/AGENTS.md`'s \"Entity Update Safety\"\n // rule requires `withAtomicFlush` for that, so this is no longer a plain flush. Every\n // validation/rejection check above (quantity rule, visibility, not-priceable) still\n // runs BEFORE this point and returns directly, matching `cart.lines.add`'s own\n // validate-first-then-atomic-mutate structure.\n const createdGiftLines: CartLine[] = []\n const removedGiftLines: CartLine[] = []\n const promotionWarnings: CartPromotionWarning[] = []\n\n await withAtomicFlush(em, [\n async () => {\n line.quantity = String(finalQuantity)\n },\n ...buildCartPromotionsAndTotalsPhases({\n em,\n container: ctx.container,\n cart,\n scope,\n assortmentScope,\n now,\n createdGiftLines,\n removedGiftLines,\n promotionWarnings,\n }),\n ], { transaction: true, label: 'cart.lines.update' })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({ dataEngine, action: 'updated', entity: line, identifiers: getCartLineIdentifiers(line), indexer: cartLineCrudIndexer })\n await emitCartEvent('cart.line.updated', { id: line.id, cartId: line.cartId, organizationId: line.organizationId, tenantId: line.tenantId })\n if (priceChanges.length > 0) {\n await emitCartEvent('cart.price.changed', {\n cartId: cart.id,\n tenantId: cart.tenantId,\n organizationId: cart.organizationId,\n changes: priceChanges,\n })\n }\n await emitCartPromotionsAndTotalsSideEffects(dataEngine, cart, { createdGiftLines, removedGiftLines, promotionWarnings })\n\n // Re-snapshot AFTER the promotions phase ran \u2014 it may have set/cleared this line's own\n // `discountTotalNet` this same cycle.\n return { status: 'updated', line: toCartLineSnapshot(line), warnings, priceChanges }\n },\n}\n\n// ---------------------------------------------------------------------------\n// cart.lines.remove\n// ---------------------------------------------------------------------------\n\nexport type CartLinesRemoveInput = CartLineAuthInput & {\n cartId: string\n lineId: string\n}\n\nexport type CartLinesRemoveResult = { removed: true; line: CartLineSnapshot }\n\ntype CartLineRemoveUndoPayload = { before: CartLineSnapshot; after: CartLineSnapshot }\n\nconst removeLineCommand: CommandHandler<CartLinesRemoveInput, CartLinesRemoveResult> = {\n id: 'cart.lines.remove',\n // \u00A73.1a's undo table: reverses a buyer-visible content change.\n isUndoable: true,\n async execute(input, ctx) {\n const { tenantId, organizationId, rawScope } = resolveLineAuthScopeIds(input)\n const scope: CartLineScope = { tenantId, organizationId }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n // `.remove` requires an active cart, same as `.add`/`.update`/`.bulkAdd`: `cart.get`\n // resolves a token regardless of status, so without this a stale token could remove a\n // line from a `merged`/`expired`/`converted` cart and the promotions/totals recompute\n // below would persist new `Cart.totals` on a cart whose order has already been finalized.\n const cart = await loadMutableCart(em, scope, input.cartId, { requireActive: true })\n const assortmentScope = resolveEnforcedScope(cart, rawScope)\n\n const line = await findOneWithDecryption(em, CartLine, { id: input.lineId, cartId: cart.id, tenantId, organizationId, deletedAt: null }, {}, scope)\n if (!line) throw notFound('[internal] cart line not found')\n\n if (line.origin === 'promotion') throw conflict('[internal] promotion_line_immutable')\n\n const before = toCartLineSnapshot(line)\n const now = new Date()\n\n // Step 8.2 (QA F12) \u2014 same reasoning as `cart.lines.update`'s own comment: a QUERY (the\n // fresh full-line fetch the whole-cart recompute needs) now runs after this scalar\n // mutation on the SAME `em`, so the former bare `em.flush()` becomes `withAtomicFlush`.\n const createdGiftLines: CartLine[] = []\n const removedGiftLines: CartLine[] = []\n const promotionWarnings: CartPromotionWarning[] = []\n\n await withAtomicFlush(em, [\n async () => {\n line.deletedAt = now\n },\n ...buildCartPromotionsAndTotalsPhases({\n em,\n container: ctx.container,\n cart,\n scope,\n assortmentScope,\n now,\n createdGiftLines,\n removedGiftLines,\n promotionWarnings,\n }),\n ], { transaction: true, label: 'cart.lines.remove' })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({ dataEngine, action: 'deleted', entity: line, identifiers: getCartLineIdentifiers(line), indexer: cartLineCrudIndexer })\n await emitCartEvent('cart.line.removed', { id: line.id, cartId: line.cartId, organizationId: line.organizationId, tenantId: line.tenantId })\n await emitCartPromotionsAndTotalsSideEffects(dataEngine, cart, { createdGiftLines, removedGiftLines, promotionWarnings })\n\n return { removed: true, line: toCartLineSnapshot(line) }\n },\n buildLog: async ({ result }) => {\n const { translate } = await resolveTranslations()\n const before = { ...result.line, deletedAt: null }\n return {\n actionLabel: translate('cart.audit.lines.remove', 'Remove cart line'),\n resourceKind: 'cart.cart_line',\n resourceId: result.line.id,\n tenantId: result.line.tenantId,\n organizationId: result.line.organizationId,\n snapshotBefore: before,\n snapshotAfter: result.line,\n payload: {\n undo: { before, after: result.line } satisfies CartLineRemoveUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CartLineRemoveUndoPayload>(logEntry, { dateFields: ['pricedAt', 'createdAt', 'updatedAt', 'deletedAt'] })\n const lineId = logEntry?.resourceId ?? payload?.before?.id ?? null\n if (!lineId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const line = await em.findOne(CartLine, { id: lineId })\n if (!line) return\n if (logEntry?.tenantId && logEntry?.organizationId) {\n ensureSameScope(line, logEntry.organizationId, logEntry.tenantId)\n }\n line.deletedAt = null\n await em.flush()\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n await emitCrudSideEffects({ dataEngine, action: 'updated', entity: line, identifiers: getCartLineIdentifiers(line), indexer: cartLineCrudIndexer })\n await emitCartEvent('cart.line.added', { id: line.id, cartId: line.cartId, organizationId: line.organizationId, tenantId: line.tenantId })\n\n // Step 8.2 (QA F12) \u2014 restoring this line is not the inverse of `Cart.totals`; only a\n // full `cart.reprice` (trigger `'undo'`) recomputes the whole-cart aggregate correctly.\n await dispatchCartRepriceForUndo(ctx, { id: line.cartId, tenantId: line.tenantId, organizationId: line.organizationId })\n },\n}\n\n// ---------------------------------------------------------------------------\n// cart.lines.bulkAdd\n// ---------------------------------------------------------------------------\n\nexport type CartLinesBulkAddEntry = {\n productId: string\n variantId?: string | null\n quantity: number | string\n configuration?: Record<string, unknown> | null\n metadata?: Record<string, unknown> | null\n}\n\nexport type CartLinesBulkAddInput = CartLineAuthInput & {\n cartId: string\n lines: CartLinesBulkAddEntry[]\n addedByActorType?: CartLineActorType\n addedByActorId?: string | null\n}\n\nexport type CartLinesBulkAddEntryOutcome =\n | { index: number; status: 'added'; line: CartLineSnapshot; before: null; warnings: CartLineWarning[] }\n | { index: number; status: 'merged'; line: CartLineSnapshot; before: CartLineSnapshot; warnings: CartLineWarning[] }\n | { index: number; status: 'rejected'; reason: CartLineRejectionReason; nearestValid?: number }\n\nexport type CartLinesBulkAddResult = {\n results: CartLinesBulkAddEntryOutcome[]\n addedCount: number\n rejectedCount: number\n}\n\ntype CartLineBulkAddUndoPayload = { entries: Array<{ before: CartLineSnapshot | null; after: CartLineSnapshot }> }\n\nconst bulkAddLinesCommand: CommandHandler<CartLinesBulkAddInput, CartLinesBulkAddResult> = {\n id: 'cart.lines.bulkAdd',\n // \u00A73.1a's undo table: reverses a buyer-visible content change.\n isUndoable: true,\n async execute(input, ctx) {\n if (input.lines.length > MAX_BULK_ADD_ENTRIES) {\n throw badRequest(`[internal] cart.lines.bulkAdd accepts at most ${MAX_BULK_ADD_ENTRIES} entries`)\n }\n\n const { tenantId, organizationId, rawScope } = resolveLineAuthScopeIds(input)\n const scope: CartLineScope = { tenantId, organizationId }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n\n const cart = await loadMutableCart(em, scope, input.cartId, { requireActive: true })\n assertStorefrontHasRouteContext(cart, input)\n const assortmentScope = resolveEnforcedScope(cart, rawScope)\n const actor = resolveLineActor(input, { addedByActorType: input.addedByActorType, addedByActorId: input.addedByActorId })\n\n if (input.lines.length === 0) return { results: [], addedCount: 0, rejectedCount: 0 }\n\n // ---- batched reads (\u00A76a.1c: \"never one-at-a-time\") \u2014 nothing mutated yet ----\n const visibilityRefs: CartVisibilityLineRef[] = input.lines.map((entry) => ({ productId: entry.productId, variantId: entry.variantId ?? null }))\n const visibility = await evaluateCartVisibility({ lines: visibilityRefs, scope: assortmentScope, tenantScope: scope, em, container: ctx.container })\n\n const productIds = Array.from(new Set(input.lines.map((entry) => entry.productId)))\n const products = await findWithDecryption(em, CatalogProduct, { id: { $in: productIds }, tenantId, organizationId, deletedAt: null, isActive: true }, {}, scope)\n const productsById = new Map((products as CatalogProduct[]).map((product) => [product.id, product]))\n\n const variantIds = Array.from(new Set(input.lines.map((entry) => entry.variantId).filter((id): id is string => !!id)))\n const variants = variantIds.length\n ? await findWithDecryption(em, CatalogProductVariant, { id: { $in: variantIds }, tenantId, organizationId, deletedAt: null, isActive: true }, {}, scope)\n : []\n const variantsById = new Map((variants as CatalogProductVariant[]).map((variant) => [variant.id, variant]))\n\n const existingLines = await findWithDecryption(\n em,\n CartLine,\n { cartId: cart.id, productId: { $in: productIds }, promotionId: null, deletedAt: null, tenantId, organizationId },\n {},\n scope,\n )\n const lineByKey = new Map<string, CartLine>()\n for (const existingLine of existingLines as CartLine[]) {\n lineByKey.set(buildMergeKey(existingLine.productId, existingLine.variantId ?? null, existingLine.configuration ?? null), existingLine)\n }\n\n const pricingContext = buildCartPricingRowFilterContext(cart)\n const priceRows = await fetchPriceRowsBatch(em, scope, pricingContext, productIds, variantIds)\n // `CatalogProductVariant.product` resolves to an unpopulated Reference proxy here (no\n // `populate` was requested), so the product id a variant belongs to is read off the\n // REQUEST entries instead of that lazy relation.\n const variantToProduct = new Map<string, string>()\n for (const entry of input.lines) {\n if (entry.variantId) variantToProduct.set(entry.variantId, entry.productId)\n }\n const rowsByProduct = groupRowsByProduct(priceRows, variantToProduct)\n\n const policyResolver = tryResolvePolicyResolutionService(ctx.container)\n const policies = policyResolver\n ? await policyResolver.resolveMany(\n em,\n input.lines.map((entry) => ({ tenantId, organizationId, storeId: cart.storeId ?? null, productId: entry.productId, variantId: entry.variantId ?? null })),\n )\n : input.lines.map(() => null)\n\n let nextLineNumber = await computeStartingLineNumber(em, scope, cart.id)\n\n const outcomes: CartLinesBulkAddEntryOutcome[] = new Array(input.lines.length)\n const changedLines: CartLine[] = []\n const changedActions: Array<'created' | 'updated'> = []\n // TC-CART-R1-10: counts ONLY entries this call rejected for a visibility reason, never a\n // quantity-rule/not-priceable/missing-product rejection \u2014 see `emitVisibilityRejectedEvent`.\n // Phases run sequentially (SPEC-018, the comment above), so a shared closure counter is safe.\n let visibilityRejectedCount = 0\n\n // ---- per-entry decide + mutate, one `withAtomicFlush` phase per entry so an\n // earlier entry's mutation is flushed before a later entry's (in-memory-only) reads\n // run \u2014 SPEC-018's rule, even though every read here is served from the maps above\n // rather than a fresh DB query.\n const phases = input.lines.map((entry, index) => async () => {\n const variantId = entry.variantId ?? null\n let requestedQuantity: number\n try {\n requestedQuantity = toQuantityNumber(entry.quantity)\n } catch {\n outcomes[index] = { index, status: 'rejected', reason: 'product_unavailable' }\n return\n }\n if (!(requestedQuantity > 0)) {\n outcomes[index] = { index, status: 'rejected', reason: 'product_unavailable' }\n return\n }\n\n const product = productsById.get(entry.productId) ?? null\n if (!product) {\n outcomes[index] = { index, status: 'rejected', reason: 'product_unavailable' }\n return\n }\n const variant = variantId ? variantsById.get(variantId) ?? null : null\n if (variantId && !variant) {\n outcomes[index] = { index, status: 'rejected', reason: 'product_unavailable' }\n return\n }\n\n const rules = deriveQuantityRules(policies[index] ?? null, product)\n const check = checkQuantityRule(rules, requestedQuantity)\n if (check.status === 'rejected') {\n outcomes[index] = { index, status: 'rejected', reason: check.reason, nearestValid: check.nearestValid }\n return\n }\n const warnings: CartLineWarning[] = check.status === 'accepted_clamped' ? ['quantity_capped'] : []\n const acceptedQuantity = check.quantity\n\n const visible = isVisible(visibility.get(cartVisibilityKey({ productId: entry.productId, variantId })) ?? 'unavailable')\n if (!visible) {\n outcomes[index] = { index, status: 'rejected', reason: 'product_unavailable' }\n visibilityRejectedCount += 1\n return\n }\n\n const now = new Date()\n const key = buildMergeKey(entry.productId, variantId, entry.configuration ?? null)\n const existingLine = lineByKey.get(key) ?? null\n const rows = filterRowsForVariant(rowsByProduct.get(entry.productId) ?? [], variantId)\n\n if (existingLine) {\n const before = toCartLineSnapshot(existingLine)\n const summedQuantity = Number(existingLine.quantity) + acceptedQuantity\n const sumCheck = checkQuantityRule(rules, summedQuantity)\n if (sumCheck.status === 'rejected') {\n outcomes[index] = { index, status: 'rejected', reason: sumCheck.reason, nearestValid: sumCheck.nearestValid }\n return\n }\n if (sumCheck.status === 'accepted_clamped') warnings.push('quantity_capped')\n\n const resolved = await resolveLinePricing(em, ctx.container, cart, rows, sumCheck.quantity, now)\n if (!resolved.priceRow) {\n outcomes[index] = { index, status: 'rejected', reason: 'product_not_priceable' }\n return\n }\n existingLine.quantity = String(sumCheck.quantity)\n applyPriceSnapshot(existingLine, resolved, now)\n existingLine.lineTotals = await computeLineTotals(ctx.container, cart, {\n productId: existingLine.productId,\n variantId: existingLine.variantId ?? null,\n name: existingLine.name,\n quantity: sumCheck.quantity,\n unitPriceNet: resolved.amountNet,\n unitPriceGross: resolved.amountGross,\n taxRate: resolved.taxRatePercent,\n configuration: existingLine.configuration ?? null,\n })\n lineByKey.set(key, existingLine)\n const after = toCartLineSnapshot(existingLine)\n outcomes[index] = { index, status: 'merged', line: after, before, warnings }\n changedLines.push(existingLine)\n changedActions.push('updated')\n return\n }\n\n const resolved = await resolveLinePricing(em, ctx.container, cart, rows, acceptedQuantity, now)\n if (!resolved.priceRow) {\n outcomes[index] = { index, status: 'rejected', reason: 'product_not_priceable' }\n return\n }\n const fields = resolveSnapshotFields(product, variant, entry.configuration ?? null, entry.metadata ?? null)\n const newLine = em.create(CartLine, {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n cartId: cart.id,\n lineNumber: nextLineNumber++,\n productId: fields.productId,\n variantId: fields.variantId,\n sku: fields.sku,\n name: fields.name,\n optionValues: fields.optionValues,\n quantity: String(acceptedQuantity),\n origin: 'customer' as const,\n promotionId: null,\n discountTotalNet: null,\n flagState: null,\n configuration: fields.configuration,\n addedByActorType: actor.addedByActorType,\n addedByActorId: actor.addedByActorId,\n metadata: fields.metadata,\n pricedAt: now,\n lineTotals: {},\n })\n applyPriceSnapshot(newLine, resolved, now)\n newLine.lineTotals = await computeLineTotals(ctx.container, cart, {\n productId: fields.productId,\n variantId: fields.variantId,\n name: fields.name,\n quantity: acceptedQuantity,\n unitPriceNet: resolved.amountNet,\n unitPriceGross: resolved.amountGross,\n taxRate: resolved.taxRatePercent,\n configuration: fields.configuration,\n })\n em.persist(newLine)\n lineByKey.set(key, newLine)\n const after = toCartLineSnapshot(newLine)\n outcomes[index] = { index, status: 'added', line: after, before: null, warnings }\n changedLines.push(newLine)\n changedActions.push('created')\n })\n\n // Step 8.2 (QA F12) \u2014 whole-cart promotions re-evaluation + totals recompute, appended\n // after every entry's own mutation phase, mirroring `cart.reprice`'s own ordering (line\n // mutations first, promotions phase, totals phase last).\n const now = new Date()\n const createdGiftLines: CartLine[] = []\n const removedGiftLines: CartLine[] = []\n const promotionWarnings: CartPromotionWarning[] = []\n\n // Same per-cart advisory lock as `cart.lines.add` (above) \u2014 `lineByKey`'s merge-by-quantity\n // decision is read-decide-write against rows that may not exist yet, so two concurrent\n // `bulkAdd`/`add` calls on this cart must serialize, not just this call's own entries\n // against each other.\n await withCartLineMutationLock(em, cart.id, [\n ...phases,\n ...buildCartPromotionsAndTotalsPhases({\n em,\n container: ctx.container,\n cart,\n scope,\n assortmentScope,\n now,\n createdGiftLines,\n removedGiftLines,\n promotionWarnings,\n }),\n ], 'cart.lines.bulkAdd')\n await emitVisibilityRejectedEvent(cart, visibilityRejectedCount, 'lines.bulkAdd')\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n for (let i = 0; i < changedLines.length; i += 1) {\n const changedLine = changedLines[i]\n const action = changedActions[i]\n await emitCrudSideEffects({ dataEngine, action, entity: changedLine, identifiers: getCartLineIdentifiers(changedLine), indexer: cartLineCrudIndexer })\n await emitCartEvent(action === 'created' ? 'cart.line.added' : 'cart.line.updated', {\n id: changedLine.id,\n cartId: changedLine.cartId,\n organizationId: changedLine.organizationId,\n tenantId: changedLine.tenantId,\n })\n }\n await emitCartPromotionsAndTotalsSideEffects(dataEngine, cart, { createdGiftLines, removedGiftLines, promotionWarnings })\n\n // Re-snapshot every added/merged outcome AFTER the promotions phase ran \u2014 it may have\n // further updated `discountTotalNet` on any of these lines.\n const changedLineById = new Map(changedLines.map((line) => [line.id, line]))\n for (let i = 0; i < outcomes.length; i += 1) {\n const outcome = outcomes[i]\n if (outcome.status !== 'added' && outcome.status !== 'merged') continue\n const changedLine = changedLineById.get(outcome.line.id)\n if (changedLine) outcomes[i] = { ...outcome, line: toCartLineSnapshot(changedLine) }\n }\n\n const addedCount = outcomes.filter((outcome) => outcome.status === 'added' || outcome.status === 'merged').length\n return { results: outcomes, addedCount, rejectedCount: outcomes.length - addedCount }\n },\n buildLog: async ({ result }) => {\n const entries = result.results\n .filter((outcome): outcome is Extract<CartLinesBulkAddEntryOutcome, { status: 'added' | 'merged' }> => outcome.status === 'added' || outcome.status === 'merged')\n .map((outcome) => ({ before: outcome.before, after: outcome.line }))\n if (!entries.length) return null\n const { translate } = await resolveTranslations()\n const first = entries[0].after\n return {\n actionLabel: translate('cart.audit.lines.bulkAdd', 'Add cart lines (bulk)'),\n resourceKind: 'cart.cart_line',\n resourceId: first.cartId,\n tenantId: first.tenantId,\n organizationId: first.organizationId,\n payload: {\n undo: {\n entries,\n } satisfies CartLineBulkAddUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CartLineBulkAddUndoPayload>(logEntry, { dateFields: ['pricedAt', 'createdAt', 'updatedAt', 'deletedAt'] })\n if (!payload?.entries?.length) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n let cartRef: { id: string; tenantId: string; organizationId: string } | null = null\n for (const entry of payload.entries) {\n const line = await em.findOne(CartLine, { id: entry.after.id })\n if (!line) continue\n ensureSameScope(line, entry.after.organizationId, entry.after.tenantId)\n if (entry.before) {\n restoreLineFromSnapshot(line, entry.before)\n } else {\n line.deletedAt = new Date()\n }\n await em.flush()\n await emitCrudSideEffects({ dataEngine, action: entry.before ? 'updated' : 'deleted', entity: line, identifiers: getCartLineIdentifiers(line), indexer: cartLineCrudIndexer })\n cartRef = { id: line.cartId, tenantId: line.tenantId, organizationId: line.organizationId }\n }\n\n // Step 8.2 (QA F12) \u2014 same reasoning as `cart.lines.add`/`.remove`'s own undo: restoring\n // these lines is not the inverse of `Cart.totals`; one `cart.reprice` dispatch after the\n // whole batch is restored recomputes the whole-cart aggregate correctly.\n if (cartRef) await dispatchCartRepriceForUndo(ctx, cartRef)\n },\n}\n\nfunction buildMergeKey(productId: string, variantId: string | null, configuration: Record<string, unknown> | null): string {\n return `${productId}:${variantId ?? ''}:${canonicalJson(configuration ?? null)}`\n}\n\nregisterCommand(addLineCommand)\nregisterCommand(updateLineCommand)\nregisterCommand(removeLineCommand)\nregisterCommand(bulkAddLinesCommand)\n\nexport { addLineCommand, updateLineCommand, removeLineCommand, bulkAddLinesCommand }\n"],
5
+ "mappings": "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",
6
6
  "names": ["resolved", "now", "after"]
7
7
  }
@@ -5,6 +5,7 @@ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
5
5
  import { createLogger } from "@open-mercato/shared/lib/logger";
6
6
  import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
7
7
  import { getCartCommand } from "../commands/cart.js";
8
+ import { isCartLineMutationLockTimeoutError } from "./lineMutationLock.js";
8
9
  import { isCartStoreContextUnavailableError, resolveCartRouteContext } from "./routeContext.js";
9
10
  const CART_RESOURCE_KIND = "cart.cart";
10
11
  const CART_TOKEN_HEADER = "X-Cart-Token";
@@ -70,6 +71,13 @@ function notFoundCartResponse() {
70
71
  }
71
72
  const logger = createLogger("cart").child({ component: "api" });
72
73
  async function mapRouteErrorToResponse(error) {
74
+ if (isCartLineMutationLockTimeoutError(error)) {
75
+ const { translate: translate2 } = await resolveTranslations();
76
+ return NextResponse.json(
77
+ { error: translate2("cart.errors.busy", "Cart is busy. Please try again."), code: "cart_busy" },
78
+ { status: 503, headers: { "Retry-After": "1", "Cache-Control": "no-store" } }
79
+ );
80
+ }
73
81
  if (isCrudHttpError(error)) return NextResponse.json(error.body, { status: error.status });
74
82
  const interceptorRejection = getCommandInterceptorHttpRejection(error);
75
83
  if (interceptorRejection) return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status });