- Added `businessPricing.service.js` to handle business customer pricing logic. - Introduced `money.js` for money parsing, formatting, and calculations. - Created `pricing.service.js` to manage variant quoting and promotion application. - Updated validation schemas in `catalogue.schemas.js` and `inventoryMerchandising.schemas.js` for new pricing and inventory features. - Implemented cron job for inventory reservation expiry in `inventoryReservationExpiry.cron.js`. - Created database migrations for catalogue and inventory structures. - Added unit tests for catalogue localization, validation, and inventory merchandising functionalities.
2.3 KiB
Inventory and Merchandising API
All routes use the /api/v1 prefix. Admin routes require authentication and the named Phase 5 permission.
Inventory and warehouses
GET /admin/inventory(inventory.read)GET /admin/inventory/:variantId(inventory.read)GET /admin/inventory/ledger(inventory.read)GET /admin/inventory/low-stock(inventory.read)POST /admin/inventory/adjustments(inventory.adjust); sendIdempotency-KeyPOST /admin/inventory/transfers(inventory.transfer); sendIdempotency-KeyGET /admin/warehouses(inventory.read)POST /admin/warehouses,PATCH /admin/warehouses/:id(inventory.warehouses.manage)GET /availability/:variantIdreturns onlyIN_STOCK,LOW_STOCK, orOUT_OF_STOCKandavailableForSale; it never exposes warehouse quantities.
Inventory mutations are internal service operations: reserveStock, releaseReservation, and consumeReservation. Reservations default to INVENTORY_RESERVATION_TTL_MINUTES=15. The minute reconciliation job expires bounded batches of 100; the database remains authoritative.
Business pricing
GET /admin/business-pricing(pricing.business.read)POST /admin/business-pricing(pricing.business.manage)
Rules support exactly one tier or customer audience, effective dates, MOQ, and non-overlapping volume ranges. Precedence is customer override, business tier, then retail. Money is stored as DECIMAL and calculated using integer-scaled helpers.
Promotions and coupons
GET|POST /admin/promotions(promotions.read/promotions.manage)GET|POST /admin/coupons(promotions.read/promotions.manage)
The quote boundary resolves retail/business base price, then the highest-priority eligible automatic promotion, then a coupon only when stacking permits. Discounts floor at zero. Coupon codes are canonical uppercase. Usage redemption is intentionally deferred until orders exist.
Banners
GET /banners?placement=&locale=returns active, scheduled, audience-eligible localized banners.GET|POST /admin/bannersrequiresmerchandising.banners.manage.
Banner media reuses Upload records and only returns safe upload identifiers, never bucket/object keys.
Not implemented
Cart, checkout, orders, coupon redemption, shipping, tax, payment, and delivery remain outside Phase 5.