@mohasinac/appkit 4.59.0 → 4.60.0

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 (531) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +62 -5
  3. package/dist/_internal/client/features/maintenance/views/MaintenanceDashboardView.js +2 -2
  4. package/dist/_internal/server/features/brands/actions.js +78 -4
  5. package/dist/_internal/server/features/checkout/actions.d.ts +36 -13
  6. package/dist/_internal/server/features/checkout/actions.js +668 -183
  7. package/dist/_internal/server/features/checkout/supplier-identity.js +18 -1
  8. package/dist/_internal/server/features/gateway-events/index.d.ts +10 -0
  9. package/dist/_internal/server/features/gateway-events/index.js +10 -0
  10. package/dist/_internal/server/features/gateway-events/ingest.d.ts +127 -0
  11. package/dist/_internal/server/features/gateway-events/ingest.js +188 -0
  12. package/dist/_internal/server/features/gateway-events/redact.d.ts +66 -0
  13. package/dist/_internal/server/features/gateway-events/redact.js +151 -0
  14. package/dist/_internal/server/features/grievance/actions.d.ts +63 -0
  15. package/dist/_internal/server/features/grievance/actions.js +173 -0
  16. package/dist/_internal/server/features/grievance/index.d.ts +8 -0
  17. package/dist/_internal/server/features/grievance/index.js +8 -0
  18. package/dist/_internal/server/features/grouped/data.js +28 -3
  19. package/dist/_internal/server/features/lottery/actions.js +99 -4
  20. package/dist/_internal/server/features/order-completion/actions.d.ts +92 -0
  21. package/dist/_internal/server/features/order-completion/actions.js +336 -0
  22. package/dist/_internal/server/features/order-completion/index.d.ts +7 -0
  23. package/dist/_internal/server/features/order-completion/index.js +7 -0
  24. package/dist/_internal/server/features/orders/actions.d.ts +13 -0
  25. package/dist/_internal/server/features/orders/actions.js +56 -2
  26. package/dist/_internal/server/features/orders/adapters.js +26 -0
  27. package/dist/_internal/server/features/orders/cancellation-refund.d.ts +116 -0
  28. package/dist/_internal/server/features/orders/cancellation-refund.js +466 -0
  29. package/dist/_internal/server/features/orders/index.d.ts +2 -1
  30. package/dist/_internal/server/features/orders/index.js +1 -1
  31. package/dist/_internal/server/features/orders/service.d.ts +60 -0
  32. package/dist/_internal/server/features/orders/service.js +74 -0
  33. package/dist/_internal/server/features/payment-cases/disputes.d.ts +76 -0
  34. package/dist/_internal/server/features/payment-cases/disputes.js +241 -0
  35. package/dist/_internal/server/features/payment-cases/index.d.ts +9 -0
  36. package/dist/_internal/server/features/payment-cases/index.js +9 -0
  37. package/dist/_internal/server/features/payouts/actions.d.ts +28 -3
  38. package/dist/_internal/server/features/payouts/actions.js +84 -10
  39. package/dist/_internal/server/features/points/adjust.d.ts +61 -0
  40. package/dist/_internal/server/features/points/adjust.js +121 -0
  41. package/dist/_internal/server/features/points/award.d.ts +65 -0
  42. package/dist/_internal/server/features/points/award.js +145 -0
  43. package/dist/_internal/server/features/price-index/market.d.ts +33 -0
  44. package/dist/_internal/server/features/price-index/market.js +54 -1
  45. package/dist/_internal/server/features/products/data.js +40 -22
  46. package/dist/_internal/server/features/protection-claims/actions.d.ts +72 -0
  47. package/dist/_internal/server/features/protection-claims/actions.js +228 -0
  48. package/dist/_internal/server/features/protection-claims/index.d.ts +8 -0
  49. package/dist/_internal/server/features/protection-claims/index.js +8 -0
  50. package/dist/_internal/server/features/refunds/actions.d.ts +20 -0
  51. package/dist/_internal/server/features/refunds/actions.js +259 -17
  52. package/dist/_internal/server/features/refunds/capacity.d.ts +29 -0
  53. package/dist/_internal/server/features/refunds/capacity.js +37 -0
  54. package/dist/_internal/server/features/reviews/actions.js +3 -2
  55. package/dist/_internal/server/features/search/index-build/sources.js +20 -1
  56. package/dist/_internal/server/features/shipping/reconcile.d.ts +10 -1
  57. package/dist/_internal/server/features/shipping/reconcile.js +34 -3
  58. package/dist/_internal/server/features/shipping/reimbursement.d.ts +117 -0
  59. package/dist/_internal/server/features/shipping/reimbursement.js +223 -0
  60. package/dist/_internal/server/features/stores/tabs.d.ts +19 -4
  61. package/dist/_internal/server/features/stores/tabs.js +24 -4
  62. package/dist/_internal/server/functions/https.d.ts +31 -1
  63. package/dist/_internal/server/functions/https.js +52 -1
  64. package/dist/_internal/server/functions/scheduled.d.ts +72 -1
  65. package/dist/_internal/server/functions/scheduled.js +124 -1
  66. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +62 -16
  67. package/dist/_internal/server/jobs/core/boxRollupReconcile.d.ts +46 -0
  68. package/dist/_internal/server/jobs/core/boxRollupReconcile.js +142 -0
  69. package/dist/_internal/server/jobs/core/dailyDataCleanup.js +149 -1
  70. package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +14 -0
  71. package/dist/_internal/server/jobs/core/dailyStatusDigest.js +79 -1
  72. package/dist/_internal/server/jobs/core/index.d.ts +2 -0
  73. package/dist/_internal/server/jobs/core/index.js +2 -0
  74. package/dist/_internal/server/jobs/core/marketPriceRollup.d.ts +64 -0
  75. package/dist/_internal/server/jobs/core/marketPriceRollup.js +153 -0
  76. package/dist/_internal/server/jobs/core/obligationSweep.d.ts +36 -0
  77. package/dist/_internal/server/jobs/core/obligationSweep.js +503 -0
  78. package/dist/_internal/server/jobs/core/paymentReconcile.d.ts +79 -0
  79. package/dist/_internal/server/jobs/core/paymentReconcile.js +614 -0
  80. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +34 -0
  81. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +35 -4
  82. package/dist/_internal/server/jobs/handlers/boxRollupReconcile.d.ts +2 -0
  83. package/dist/_internal/server/jobs/handlers/boxRollupReconcile.js +15 -0
  84. package/dist/_internal/server/jobs/handlers/countersReconcileNow.d.ts +65 -0
  85. package/dist/_internal/server/jobs/handlers/countersReconcileNow.js +89 -0
  86. package/dist/_internal/server/jobs/handlers/index.d.ts +5 -0
  87. package/dist/_internal/server/jobs/handlers/index.js +5 -0
  88. package/dist/_internal/server/jobs/handlers/marketPriceRollup.d.ts +2 -0
  89. package/dist/_internal/server/jobs/handlers/marketPriceRollup.js +2 -0
  90. package/dist/_internal/server/jobs/handlers/obligationSweep.d.ts +2 -0
  91. package/dist/_internal/server/jobs/handlers/obligationSweep.js +15 -0
  92. package/dist/_internal/server/jobs/handlers/onShipmentAllocationSync.js +30 -1
  93. package/dist/_internal/server/jobs/handlers/paymentReconcile.d.ts +2 -0
  94. package/dist/_internal/server/jobs/handlers/paymentReconcile.js +6 -0
  95. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  96. package/dist/_internal/shared/actions/action-registry.js +71 -0
  97. package/dist/_internal/shared/checkout/coupon-discount.d.ts +73 -0
  98. package/dist/_internal/shared/checkout/coupon-discount.js +103 -0
  99. package/dist/_internal/shared/checkout/lanes.d.ts +42 -0
  100. package/dist/_internal/shared/checkout/lanes.js +46 -0
  101. package/dist/_internal/shared/checkout/order-math.d.ts +149 -1
  102. package/dist/_internal/shared/checkout/order-math.js +251 -3
  103. package/dist/_internal/shared/checkout/refundable-base.d.ts +116 -0
  104. package/dist/_internal/shared/checkout/refundable-base.js +122 -0
  105. package/dist/_internal/shared/features/auctions/config.d.ts +14 -1
  106. package/dist/_internal/shared/features/auctions/config.js +30 -1
  107. package/dist/_internal/shared/features/brands/schema.d.ts +42 -0
  108. package/dist/_internal/shared/features/brands/schema.js +18 -0
  109. package/dist/_internal/shared/features/orders/refund-copy.d.ts +22 -0
  110. package/dist/_internal/shared/features/orders/refund-copy.js +34 -0
  111. package/dist/_internal/shared/features/print/label-profiles.d.ts +198 -0
  112. package/dist/_internal/shared/features/print/label-profiles.js +350 -0
  113. package/dist/_internal/shared/features/products/to-product-item.js +33 -5
  114. package/dist/_internal/shared/fees/calculator.d.ts +31 -2
  115. package/dist/_internal/shared/fees/calculator.js +103 -5
  116. package/dist/_internal/shared/fees/india-states.d.ts +195 -0
  117. package/dist/_internal/shared/fees/india-states.js +190 -0
  118. package/dist/_internal/shared/fees/payout-hold.d.ts +80 -0
  119. package/dist/_internal/shared/fees/payout-hold.js +74 -0
  120. package/dist/_internal/shared/obligations/deadlines.d.ts +94 -0
  121. package/dist/_internal/shared/obligations/deadlines.js +155 -0
  122. package/dist/_internal/shared/shipping/quote.d.ts +96 -0
  123. package/dist/_internal/shared/shipping/quote.js +67 -0
  124. package/dist/_internal/shared/time/business-days.d.ts +91 -0
  125. package/dist/_internal/shared/time/business-days.js +185 -0
  126. package/dist/cli/index.js +0 -2
  127. package/dist/client.d.ts +9 -4
  128. package/dist/client.js +26 -3
  129. package/dist/configs/tailwind.d.ts +0 -12
  130. package/dist/configs/tailwind.js +28 -15
  131. package/dist/constants/api-endpoints.d.ts +51 -9
  132. package/dist/constants/api-endpoints.js +23 -7
  133. package/dist/constants/index.d.ts +1 -1
  134. package/dist/constants/index.js +1 -1
  135. package/dist/contracts/index.d.ts +1 -1
  136. package/dist/contracts/payment.d.ts +131 -0
  137. package/dist/features/about/components/AboutView.js +1 -1
  138. package/dist/features/about/components/DeveloperView.js +5 -2
  139. package/dist/features/about/components/FAQPageView.js +2 -1
  140. package/dist/features/about/components/FeesView.js +2 -1
  141. package/dist/features/about/components/HelpPageView.js +2 -1
  142. package/dist/features/about/components/HowAuctionsWorkView.js +2 -1
  143. package/dist/features/about/components/HowCheckoutWorksView.js +2 -1
  144. package/dist/features/about/components/HowEmiWorksView.js +2 -1
  145. package/dist/features/about/components/HowOffersWorkView.js +2 -1
  146. package/dist/features/about/components/HowOrdersWorkView.js +2 -1
  147. package/dist/features/about/components/HowPayoutsWorkView.js +2 -1
  148. package/dist/features/about/components/HowPreOrdersWorkView.js +2 -1
  149. package/dist/features/about/components/HowReviewsWorkView.js +2 -1
  150. package/dist/features/about/components/PolicyPageView.js +2 -1
  151. package/dist/features/about/components/ProseHero.d.ts +73 -0
  152. package/dist/features/about/components/ProseHero.js +18 -0
  153. package/dist/features/about/components/SecurityPrivacyView.js +2 -1
  154. package/dist/features/about/components/ShippingPolicyView.js +2 -1
  155. package/dist/features/about/components/TrackOrderView.js +2 -1
  156. package/dist/features/about/components/UnauthorizedView.js +1 -1
  157. package/dist/features/about/index.d.ts +2 -0
  158. package/dist/features/about/index.js +5 -0
  159. package/dist/features/account/actions/address-actions.d.ts +26 -2
  160. package/dist/features/account/actions/address-actions.js +25 -1
  161. package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
  162. package/dist/features/account/components/NotificationPreferencesPanel.js +1 -1
  163. package/dist/features/account/components/UserOrdersView.js +17 -0
  164. package/dist/features/account/components/UserSidebar.js +1 -1
  165. package/dist/features/account/hooks/useNotifications.js +29 -0
  166. package/dist/features/admin/actions/admin-coupon-actions.js +36 -0
  167. package/dist/features/admin/components/AdminBeysView.js +2 -2
  168. package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
  169. package/dist/features/admin/components/AdminBrandEditorView.js +32 -2
  170. package/dist/features/admin/components/AdminBundleEditorView.js +1 -1
  171. package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
  172. package/dist/features/admin/components/AdminCategoriesView.js +1 -1
  173. package/dist/features/admin/components/AdminCategoryEditorView.js +73 -17
  174. package/dist/features/admin/components/AdminContactEditorView.js +1 -1
  175. package/dist/features/admin/components/AdminCouponEditorView.js +26 -1
  176. package/dist/features/admin/components/AdminFaqEditorView.js +1 -1
  177. package/dist/features/admin/components/AdminFaqsView.js +2 -2
  178. package/dist/features/admin/components/AdminGatewayEventsView.d.ts +54 -0
  179. package/dist/features/admin/components/AdminGatewayEventsView.js +209 -0
  180. package/dist/features/admin/components/AdminGuideHubView.js +1 -1
  181. package/dist/features/admin/components/AdminMoneyCasesView.d.ts +60 -0
  182. package/dist/features/admin/components/AdminMoneyCasesView.js +287 -0
  183. package/dist/features/admin/components/AdminPartsView.js +2 -2
  184. package/dist/features/admin/components/AdminProductsView.js +75 -4
  185. package/dist/features/admin/components/AdminSidebar.js +2 -2
  186. package/dist/features/admin/components/AdminSiteSettingsView.js +65 -7
  187. package/dist/features/admin/components/AdminSublistingCategoriesView.js +2 -0
  188. package/dist/features/admin/components/AdminSupportTicketDetailView.js +1 -1
  189. package/dist/features/admin/components/AdminSupportTicketsView.js +2 -2
  190. package/dist/features/admin/components/AdminTesterChecklistView.js +2 -2
  191. package/dist/features/admin/components/AdminUsersView.js +72 -1
  192. package/dist/features/admin/components/AdminViewCards.d.ts +3 -1
  193. package/dist/features/admin/components/AdminViewCards.js +4 -4
  194. package/dist/features/admin/components/DataListingView.d.ts +28 -0
  195. package/dist/features/admin/components/DataListingView.js +175 -14
  196. package/dist/features/admin/components/DataTable.d.ts +12 -1
  197. package/dist/features/admin/components/DataTable.js +3 -3
  198. package/dist/features/admin/components/OwnerQuickActions.d.ts +1 -1
  199. package/dist/features/admin/components/OwnerQuickActions.js +60 -2
  200. package/dist/features/admin/components/RecordOwnerQuickActions.d.ts +1 -1
  201. package/dist/features/admin/components/RecordOwnerQuickActions.js +61 -14
  202. package/dist/features/admin/components/analytics/AdminStatCard.d.ts +35 -1
  203. package/dist/features/admin/components/analytics/AdminStatCard.js +77 -2
  204. package/dist/features/admin/components/index.d.ts +4 -0
  205. package/dist/features/admin/components/index.js +6 -0
  206. package/dist/features/admin/constants/filter-tabs.d.ts +156 -0
  207. package/dist/features/admin/constants/filter-tabs.js +106 -0
  208. package/dist/features/admin/repository/site-settings.repository.d.ts +22 -0
  209. package/dist/features/admin/repository/site-settings.repository.js +82 -1
  210. package/dist/features/admin/schemas/admin-editor-forms.d.ts +21 -0
  211. package/dist/features/admin/schemas/admin-editor-forms.js +44 -0
  212. package/dist/features/admin/schemas/firestore.js +5 -1
  213. package/dist/features/admin/schemas/site-settings-form.js +30 -1
  214. package/dist/features/auctions/actions/bid-actions.js +13 -1
  215. package/dist/features/auctions/components/AuctionBottomActions.js +2 -2
  216. package/dist/features/auctions/components/AuctionDetailPageView.js +3 -1
  217. package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -0
  218. package/dist/features/auctions/components/MarketplaceAuctionCard.js +5 -3
  219. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +8 -1
  220. package/dist/features/auctions/components/PlaceBidFormClient.js +2 -2
  221. package/dist/features/audit-log/schemas/firestore.d.ts +28 -0
  222. package/dist/features/audit-log/schemas/firestore.js +14 -0
  223. package/dist/features/auth/permissions/constants.d.ts +17 -1
  224. package/dist/features/auth/permissions/constants.js +18 -0
  225. package/dist/features/blog/components/BlogPostView.js +1 -1
  226. package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +1 -1
  227. package/dist/features/categories/api/[id]/description-template/route.js +107 -1
  228. package/dist/features/categories/components/BrandDetailPageView.js +2 -2
  229. package/dist/features/categories/components/BreadcrumbTrail.js +7 -1
  230. package/dist/features/categories/components/BundleDetailView.js +1 -1
  231. package/dist/features/categories/components/CategoryDetailPageView.js +2 -2
  232. package/dist/features/categories/components/CategoryForm.js +12 -1
  233. package/dist/features/categories/components/CategoryTemplatePicker.js +38 -5
  234. package/dist/features/categories/repository/market-price-index.repository.d.ts +123 -0
  235. package/dist/features/categories/repository/market-price-index.repository.js +105 -0
  236. package/dist/features/categories/repository/price-observation.repository.d.ts +166 -0
  237. package/dist/features/categories/repository/price-observation.repository.js +132 -0
  238. package/dist/features/events/components/EventSaleBanner.js +1 -1
  239. package/dist/features/events/components/EventStatusBadge.js +1 -1
  240. package/dist/features/faq/components/FAQAccordion.js +2 -1
  241. package/dist/features/faq/components/FAQCategorySidebar.js +2 -2
  242. package/dist/features/faq/components/FAQHelpfulButtons.js +4 -4
  243. package/dist/features/faq/components/RelatedFAQs.js +1 -1
  244. package/dist/features/filters/FilterFacetSection.js +1 -1
  245. package/dist/features/filters/SwitchFilter.js +1 -1
  246. package/dist/features/gateway-events/repository/gateway-events.repository.d.ts +117 -0
  247. package/dist/features/gateway-events/repository/gateway-events.repository.js +274 -0
  248. package/dist/features/gateway-events/schemas/firestore.d.ts +217 -0
  249. package/dist/features/gateway-events/schemas/firestore.js +125 -0
  250. package/dist/features/gateway-events/schemas/index.d.ts +81 -0
  251. package/dist/features/gateway-events/schemas/index.js +79 -0
  252. package/dist/features/grouped/components/GroupedListingDetailView.js +1 -1
  253. package/dist/features/homepage/components/AnnouncementBar.js +37 -1
  254. package/dist/features/homepage/components/AnnouncementBar.style.css +30 -0
  255. package/dist/features/homepage/components/BrandsSection.js +1 -1
  256. package/dist/features/homepage/components/CTABannerSection.js +1 -1
  257. package/dist/features/homepage/components/FeaturedBundlesSection.js +14 -24
  258. package/dist/features/homepage/components/HeroCarousel.js +2 -2
  259. package/dist/features/homepage/components/SocialPostCard.js +1 -1
  260. package/dist/features/inventory/components/AdminMoveLedgerView.d.ts +7 -0
  261. package/dist/features/inventory/components/AdminMoveLedgerView.js +157 -0
  262. package/dist/features/inventory/components/AdminStorageBoxesView.d.ts +11 -0
  263. package/dist/features/inventory/components/AdminStorageBoxesView.js +168 -0
  264. package/dist/features/inventory/components/ReceiveStationView.d.ts +39 -0
  265. package/dist/features/inventory/components/ReceiveStationView.js +413 -0
  266. package/dist/features/inventory/components/operator-message.d.ts +29 -0
  267. package/dist/features/inventory/components/operator-message.js +41 -0
  268. package/dist/features/inventory/index.d.ts +13 -0
  269. package/dist/features/inventory/index.js +28 -0
  270. package/dist/features/inventory/repository/inventory-movements.repository.d.ts +97 -0
  271. package/dist/features/inventory/repository/inventory-movements.repository.js +186 -0
  272. package/dist/features/inventory/repository/inventory-placements.repository.d.ts +179 -0
  273. package/dist/features/inventory/repository/inventory-placements.repository.js +345 -0
  274. package/dist/features/inventory/repository/storage-boxes.repository.d.ts +83 -0
  275. package/dist/features/inventory/repository/storage-boxes.repository.js +127 -0
  276. package/dist/features/inventory/schemas/firestore.d.ts +197 -0
  277. package/dist/features/inventory/schemas/firestore.js +159 -0
  278. package/dist/features/layout/AppLayoutShell.d.ts +12 -1
  279. package/dist/features/layout/AppLayoutShell.js +25 -12
  280. package/dist/features/layout/AutoBreadcrumbs.js +162 -3
  281. package/dist/features/layout/AutoBreadcrumbs.style.css +45 -0
  282. package/dist/features/layout/BottomNavbar.d.ts +1 -1
  283. package/dist/features/layout/BottomNavbar.js +2 -2
  284. package/dist/features/media/MediaAvatar.js +1 -1
  285. package/dist/features/media/MediaImage.d.ts +24 -4
  286. package/dist/features/media/MediaImage.js +44 -4
  287. package/dist/features/media/MediaLightbox.js +1 -1
  288. package/dist/features/media/modals/ImageCropModal.js +1 -1
  289. package/dist/features/media/modals/ImageEditor.js +1 -1
  290. package/dist/features/media/upload/MediaUploadField.js +91 -3
  291. package/dist/features/obligations/constants/policies.d.ts +92 -0
  292. package/dist/features/obligations/constants/policies.js +378 -0
  293. package/dist/features/obligations/repository/obligations.repository.d.ts +68 -0
  294. package/dist/features/obligations/repository/obligations.repository.js +158 -0
  295. package/dist/features/obligations/schemas/firestore.d.ts +160 -0
  296. package/dist/features/obligations/schemas/firestore.js +87 -0
  297. package/dist/features/orders/actions/order-actions.d.ts +43 -4
  298. package/dist/features/orders/actions/order-actions.js +60 -3
  299. package/dist/features/orders/actions/refund-actions.js +70 -10
  300. package/dist/features/orders/components/OrderCompletionPanel.d.ts +27 -0
  301. package/dist/features/orders/components/OrderCompletionPanel.js +192 -0
  302. package/dist/features/orders/components/OrdersList.js +2 -2
  303. package/dist/features/orders/components/RefundHistoryTable.js +3 -1
  304. package/dist/features/orders/components/index.d.ts +2 -0
  305. package/dist/features/orders/components/index.js +6 -0
  306. package/dist/features/orders/constants/transitions.d.ts +59 -0
  307. package/dist/features/orders/constants/transitions.js +104 -0
  308. package/dist/features/orders/repository/orders.repository.d.ts +109 -6
  309. package/dist/features/orders/repository/orders.repository.js +464 -21
  310. package/dist/features/orders/schemas/firestore.d.ts +270 -0
  311. package/dist/features/orders/schemas/firestore.js +24 -0
  312. package/dist/features/orders/schemas/index.d.ts +61 -0
  313. package/dist/features/orders/schemas/index.js +22 -0
  314. package/dist/features/orders/types/index.d.ts +79 -0
  315. package/dist/features/parts/components/PartsIndexPageView.js +8 -1
  316. package/dist/features/payment-cases/repository/payment-cases.repository.d.ts +170 -0
  317. package/dist/features/payment-cases/repository/payment-cases.repository.js +342 -0
  318. package/dist/features/payment-cases/schemas/firestore.d.ts +229 -0
  319. package/dist/features/payment-cases/schemas/firestore.js +105 -0
  320. package/dist/features/payment-cases/schemas/resolve-form.d.ts +97 -0
  321. package/dist/features/payment-cases/schemas/resolve-form.js +114 -0
  322. package/dist/features/payments/repository/payout.repository.d.ts +0 -1
  323. package/dist/features/payments/repository/payout.repository.js +0 -1
  324. package/dist/features/points/repository/points.repository.d.ts +122 -0
  325. package/dist/features/points/repository/points.repository.js +304 -1
  326. package/dist/features/points/schemas/adjust-form.d.ts +58 -0
  327. package/dist/features/points/schemas/adjust-form.js +63 -0
  328. package/dist/features/points/schemas/firestore.d.ts +108 -1
  329. package/dist/features/points/schemas/firestore.js +84 -0
  330. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
  331. package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
  332. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
  333. package/dist/features/price-index/components/PriceIndexToolView.d.ts +23 -4
  334. package/dist/features/price-index/components/PriceIndexToolView.js +71 -6
  335. package/dist/features/price-index/market-stats.d.ts +82 -0
  336. package/dist/features/price-index/market-stats.js +230 -0
  337. package/dist/features/print/components/BoxLabelPrintView.d.ts +25 -0
  338. package/dist/features/print/components/BoxLabelPrintView.js +66 -0
  339. package/dist/features/print/components/LabelProfilePicker.d.ts +13 -0
  340. package/dist/features/print/components/LabelProfilePicker.js +38 -0
  341. package/dist/features/print/components/labels.d.ts +117 -0
  342. package/dist/features/print/components/labels.js +258 -0
  343. package/dist/features/print/components/labels.style.css +46 -0
  344. package/dist/features/print/index.d.ts +6 -0
  345. package/dist/features/print/index.js +16 -0
  346. package/dist/features/products/columns/productTableColumns.js +5 -4
  347. package/dist/features/products/components/MarketplaceBundleCard.js +2 -2
  348. package/dist/features/products/components/MarketplacePrizeDrawCard.js +6 -6
  349. package/dist/features/products/components/PrizeDrawCollage.js +1 -1
  350. package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
  351. package/dist/features/products/components/PrizeRevealModal.js +2 -2
  352. package/dist/features/products/components/ProductDetailPageView.js +18 -4
  353. package/dist/features/products/components/ProductForm.js +34 -23
  354. package/dist/features/products/components/ProductGrid.js +86 -62
  355. package/dist/features/products/constants/card-badges.d.ts +97 -0
  356. package/dist/features/products/constants/card-badges.js +100 -0
  357. package/dist/features/products/repository/products.repository.js +34 -1
  358. package/dist/features/products/schemas/firestore.d.ts +46 -3
  359. package/dist/features/products/schemas/firestore.js +21 -0
  360. package/dist/features/promotions/components/PromotionsView.js +1 -1
  361. package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +32 -0
  362. package/dist/features/promotions/repository/claimed-coupons.repository.js +58 -0
  363. package/dist/features/promotions/schemas/firestore.js +30 -3
  364. package/dist/features/promotions/schemas/index.d.ts +6 -6
  365. package/dist/features/protection-claims/repository/protection-claims.repository.d.ts +54 -0
  366. package/dist/features/protection-claims/repository/protection-claims.repository.js +174 -0
  367. package/dist/features/protection-claims/schemas/firestore.d.ts +168 -0
  368. package/dist/features/protection-claims/schemas/firestore.js +130 -0
  369. package/dist/features/reviews/actions/review-actions.js +3 -2
  370. package/dist/features/reviews/components/ReviewModal.js +1 -1
  371. package/dist/features/reviews/components/ReviewsList.js +1 -1
  372. package/dist/features/reviews/schemas/firestore.d.ts +20 -1
  373. package/dist/features/scams/constants/scam-types.d.ts +2 -0
  374. package/dist/features/scams/constants/scam-types.js +18 -0
  375. package/dist/features/scams/schemas/report-form.d.ts +3 -3
  376. package/dist/features/scams/schemas/report-form.js +26 -1
  377. package/dist/features/search/components/Search.js +9 -9
  378. package/dist/features/search/components/SearchResultsClient.js +18 -3
  379. package/dist/features/search/components/SearchResultsPageView.d.ts +1 -6
  380. package/dist/features/search/components/SearchResultsPageView.js +2 -2
  381. package/dist/features/seller/actions/offer-actions.js +16 -1
  382. package/dist/features/seller/actions/seller-actions.js +14 -3
  383. package/dist/features/seller/components/CategoryInlineSelect.js +75 -11
  384. package/dist/features/seller/components/PrintCenterView.d.ts +5 -0
  385. package/dist/features/seller/components/PrintCenterView.js +41 -30
  386. package/dist/features/seller/components/SellerOrdersView.js +1 -1
  387. package/dist/features/seller/components/SellerPayoutRequestView.js +16 -5
  388. package/dist/features/seller/components/SellerPayoutSettingsView.js +1 -1
  389. package/dist/features/seller/components/SellerProductShell.d.ts +16 -0
  390. package/dist/features/seller/components/SellerProductShell.js +123 -13
  391. package/dist/features/seller/components/SellerReviewsView.js +4 -2
  392. package/dist/features/seller/components/SellerSidebar.js +1 -1
  393. package/dist/features/seller/schemas/coupon-form.d.ts +26 -0
  394. package/dist/features/seller/schemas/coupon-form.js +27 -0
  395. package/dist/features/seller/schemas/product-form.d.ts +3 -0
  396. package/dist/features/seller/schemas/product-form.js +5 -0
  397. package/dist/features/shell/FormShell.js +2 -2
  398. package/dist/features/shell/QuickFormDrawer.js +14 -0
  399. package/dist/features/shell/SectionForm.d.ts +19 -0
  400. package/dist/features/shell/SectionForm.js +133 -9
  401. package/dist/features/shell/SectionForm.style.css +54 -0
  402. package/dist/features/shipments/components/AdminShipmentsView.js +16 -1
  403. package/dist/features/shipments/repository/shipment-items.repository.d.ts +9 -0
  404. package/dist/features/shipments/repository/shipment-items.repository.js +14 -0
  405. package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
  406. package/dist/features/sticker-trace/components/StickerCommitPanel.js +1 -1
  407. package/dist/features/store-extensions/schemas/report-review-form.d.ts +8 -8
  408. package/dist/features/stores/components/StoreGuideHubView.js +1 -1
  409. package/dist/features/stores/components/StoreHeader.js +1 -1
  410. package/dist/features/support/schemas/firestore.d.ts +73 -2
  411. package/dist/features/support/schemas/firestore.js +59 -0
  412. package/dist/index.d.ts +16 -24
  413. package/dist/index.js +38 -47
  414. package/dist/next/ErrorBoundary.js +1 -1
  415. package/dist/next/routing/route-map.d.ts +24 -0
  416. package/dist/next/routing/route-map.js +40 -0
  417. package/dist/providers/payment-manual/index.d.ts +15 -1
  418. package/dist/providers/payment-manual/index.js +37 -0
  419. package/dist/providers/payment-phonepe/index.d.ts +14 -1
  420. package/dist/providers/payment-phonepe/index.js +36 -0
  421. package/dist/providers/payment-razorpay/index.d.ts +44 -1
  422. package/dist/providers/payment-razorpay/index.js +99 -0
  423. package/dist/providers/shipping-shiprocket/index.d.ts +18 -0
  424. package/dist/providers/shipping-shiprocket/index.js +18 -0
  425. package/dist/schemas/registry.d.ts +44 -11
  426. package/dist/server-entry.d.ts +2 -0
  427. package/dist/server-entry.js +27 -0
  428. package/dist/server.d.ts +42 -0
  429. package/dist/server.js +93 -0
  430. package/dist/styles.css +418 -149
  431. package/dist/tailwind-utilities.css +1 -1
  432. package/dist/tokens/themes/default-dark.js +15 -12
  433. package/dist/tokens/themes/default-light.js +14 -11
  434. package/dist/tokens/tokens.css +106 -23
  435. package/dist/ui/DataTable.style.css +4 -4
  436. package/dist/ui/columns/cell-renderers.d.ts +20 -0
  437. package/dist/ui/columns/cell-renderers.js +26 -1
  438. package/dist/ui/columns/index.d.ts +1 -1
  439. package/dist/ui/columns/index.js +4 -1
  440. package/dist/ui/components/Badge.style.css +1 -1
  441. package/dist/ui/components/Breadcrumb.js +19 -1
  442. package/dist/ui/components/BulkActionBar.style.css +4 -4
  443. package/dist/ui/components/Button.style.css +1 -1
  444. package/dist/ui/components/Checkbox.style.css +1 -1
  445. package/dist/ui/components/FallbackShell.js +1 -1
  446. package/dist/ui/components/Form.d.ts +14 -1
  447. package/dist/ui/components/Form.js +2 -2
  448. package/dist/ui/components/FormField.js +18 -4
  449. package/dist/ui/components/FormField.style.css +43 -3
  450. package/dist/ui/components/GatedPrice.style.css +1 -1
  451. package/dist/ui/components/IconButton.style.css +1 -1
  452. package/dist/ui/components/Input.js +22 -1
  453. package/dist/ui/components/Input.style.css +38 -22
  454. package/dist/ui/components/ListingFilterDrawer.js +1 -1
  455. package/dist/ui/components/ListingToolbar.js +1 -1
  456. package/dist/ui/components/PaginatedSelect.js +0 -0
  457. package/dist/ui/components/Radio.style.css +1 -1
  458. package/dist/ui/components/Repeater.d.ts +53 -0
  459. package/dist/ui/components/Repeater.js +7 -0
  460. package/dist/ui/components/RichTextEditor.d.ts +20 -1
  461. package/dist/ui/components/RichTextEditor.js +23 -2
  462. package/dist/ui/components/Select.style.css +7 -21
  463. package/dist/ui/components/Semantic.js +27 -3
  464. package/dist/ui/components/TagInput.style.css +4 -9
  465. package/dist/ui/components/Textarea.style.css +4 -18
  466. package/dist/ui/components/Toggle.style.css +1 -1
  467. package/dist/ui/components/Typography.d.ts +3 -3
  468. package/dist/ui/components/Typography.js +15 -15
  469. package/dist/ui/components/ViewToggle.style.css +4 -4
  470. package/dist/ui/forms/AddAnotherToggle.d.ts +16 -0
  471. package/dist/ui/forms/AddAnotherToggle.js +56 -0
  472. package/dist/ui/forms/ColorPickerField.js +15 -2
  473. package/dist/ui/forms/FieldCheckbox.js +15 -2
  474. package/dist/ui/forms/FieldInput.d.ts +22 -1
  475. package/dist/ui/forms/FieldInput.js +19 -5
  476. package/dist/ui/forms/FieldLabel.d.ts +35 -0
  477. package/dist/ui/forms/FieldLabel.js +70 -0
  478. package/dist/ui/forms/FieldSelect.d.ts +4 -1
  479. package/dist/ui/forms/FieldSelect.js +27 -5
  480. package/dist/ui/forms/FieldTextarea.d.ts +4 -1
  481. package/dist/ui/forms/FieldTextarea.js +22 -5
  482. package/dist/ui/forms/FormShell.d.ts +17 -1
  483. package/dist/ui/forms/FormShell.js +36 -2
  484. package/dist/ui/forms/FormShell.style.css +41 -33
  485. package/dist/ui/forms/index.d.ts +5 -1
  486. package/dist/ui/forms/index.js +2 -0
  487. package/dist/ui/icons/icon-registry.d.ts +1 -0
  488. package/dist/ui/icons/icon-registry.js +17 -1
  489. package/dist/utils/id-generators.d.ts +80 -0
  490. package/dist/utils/id-generators.js +93 -0
  491. package/dist/utils/search-txt-builders.d.ts +12 -0
  492. package/dist/utils/search-txt-builders.js +17 -0
  493. package/package.json +1 -1
  494. package/scripts/seed-cli.mjs +106 -5
  495. package/dist/features/loyalty/api/admin/loyalty/grant/route.d.ts +0 -12
  496. package/dist/features/loyalty/api/admin/loyalty/grant/route.js +0 -38
  497. package/dist/features/loyalty/api/balance/route.d.ts +0 -12
  498. package/dist/features/loyalty/api/balance/route.js +0 -41
  499. package/dist/features/loyalty/api/earn/route.d.ts +0 -12
  500. package/dist/features/loyalty/api/earn/route.js +0 -38
  501. package/dist/features/loyalty/api/history/route.d.ts +0 -12
  502. package/dist/features/loyalty/api/history/route.js +0 -58
  503. package/dist/features/loyalty/api/redeem/route.d.ts +0 -12
  504. package/dist/features/loyalty/api/redeem/route.js +0 -38
  505. package/dist/features/loyalty/columns/index.d.ts +0 -17
  506. package/dist/features/loyalty/columns/index.js +0 -40
  507. package/dist/features/loyalty/components/CoinsDisplay.d.ts +0 -12
  508. package/dist/features/loyalty/components/CoinsDisplay.js +0 -10
  509. package/dist/features/loyalty/components/index.d.ts +0 -1
  510. package/dist/features/loyalty/components/index.js +0 -1
  511. package/dist/features/loyalty/hooks/useLoyaltyBalance.d.ts +0 -4
  512. package/dist/features/loyalty/hooks/useLoyaltyBalance.js +0 -25
  513. package/dist/features/loyalty/index.d.ts +0 -6
  514. package/dist/features/loyalty/index.js +0 -6
  515. package/dist/features/loyalty/manifest.d.ts +0 -2
  516. package/dist/features/loyalty/manifest.js +0 -16
  517. package/dist/features/loyalty/messages/en.json +0 -19
  518. package/dist/features/loyalty/repository/loyalty.repository.d.ts +0 -8
  519. package/dist/features/loyalty/repository/loyalty.repository.js +0 -22
  520. package/dist/features/loyalty/schemas/index.d.ts +0 -85
  521. package/dist/features/loyalty/schemas/index.js +0 -40
  522. package/dist/features/loyalty/server.d.ts +0 -6
  523. package/dist/features/loyalty/server.js +0 -6
  524. package/dist/features/loyalty/types/index.d.ts +0 -31
  525. package/dist/features/loyalty/types/index.js +0 -1
  526. package/dist/features/loyalty/types/loyalty-config.d.ts +0 -7
  527. package/dist/features/loyalty/types/loyalty-config.js +0 -1
  528. package/dist/features/loyalty/types/loyalty-math.d.ts +0 -9
  529. package/dist/features/loyalty/types/loyalty-math.js +0 -27
  530. package/dist/features/products/components/ProductOwnerQuickActions.d.ts +0 -11
  531. package/dist/features/products/components/ProductOwnerQuickActions.js +0 -120
@@ -9,12 +9,83 @@ export declare const productStatsSync: import("./types").ScheduledFunctionDefini
9
9
  export declare const revenueRollup: import("./types").ScheduledFunctionDefinition;
10
10
  export declare const searchIndexBuild: import("./types").ScheduledFunctionDefinition;
11
11
  export declare const priceIndexRollup: import("./types").ScheduledFunctionDefinition;
12
+ export declare const marketPriceRollup: import("./types").ScheduledFunctionDefinition;
12
13
  export declare const analyticsAlertEvaluate: import("./types").ScheduledFunctionDefinition;
13
14
  export declare const pageViewRollup: import("./types").ScheduledFunctionDefinition;
14
15
  export declare const pageViewPrune: import("./types").ScheduledFunctionDefinition;
15
16
  export declare const dailyStatusDigest: import("./types").ScheduledFunctionDefinition;
16
17
  export declare const dailyDataCleanup: import("./types").ScheduledFunctionDefinition;
17
18
  export declare const countersReconcile: import("./types").ScheduledFunctionDefinition;
19
+ /**
20
+ * Payment reconciliation — R2, R3 and R4. Daily 05:30 UTC.
21
+ *
22
+ * 🛑 ITS OWN FUNCTION, and the two obvious places to fold it are both wrong.
23
+ *
24
+ * `countersReconcile` above already runs at its 540 s ceiling, so adding work
25
+ * there is how a job starts timing out and taking category metrics with it.
26
+ * `autoPayoutEligibility` is tempting because both touch orders, but
27
+ * `priceIndexRollup`'s own comment sets the precedent: a job should be able to
28
+ * fail without taking an unrelated one down, and these two share nothing beyond
29
+ * the collection.
30
+ *
31
+ * Cloud Scheduler bills **per registered job** (3 free, 28 already registered),
32
+ * so this is ~$0.10/month — a cost the Firebase budget table explicitly accepts
33
+ * for a distinct cadence rather than contorting the schedule around it.
34
+ *
35
+ * 🛑 R1 is NOT here. It is folded into `paymentWindowTimeout` (every 5 minutes),
36
+ * because a webhook we received and lost should not wait up to 24 hours to be
37
+ * noticed — and its query is one indexed read, so the tight loop is free.
38
+ *
39
+ * 05:30 UTC = 11:00 IST, deliberately after `countersReconcile` (03:00) and
40
+ * `autoPayoutEligibility` so a payout decision is never taken on the same tick
41
+ * as the reconciliation that might open a case blocking it.
42
+ */
43
+ /**
44
+ * Every deadline in the order lifecycle — one job, every 5 minutes.
45
+ *
46
+ * 🛑 ONE JOB FOR ~20 SCENARIOS, and the Scheduler count goes DOWN.
47
+ *
48
+ * The alternative was a cron per scenario, which is ~$2/month *and* `O(n²)`
49
+ * coverage reasoning — D25, D27 and the duplicate buyout notification are all
50
+ * that pattern failing. Here it is one indexed query per tick, empty when idle,
51
+ * and `obligations`' deterministic document id gives "at most one open
52
+ * obligation per (subject, kind)" so a double reconcile is a no-op.
53
+ *
54
+ * Phase 8 retires `paymentWindowTimeout`, `pendingOrderTimeout` and
55
+ * `paymentReviewAutoApprove` once their policy rows are proven, taking the
56
+ * registered count **28 → 29 → 27**.
57
+ *
58
+ * 🛑 NOT a Firestore trigger on `orders`. A trigger writing `obligations`
59
+ * whose sweep writes `orders` is Root Cause #92's self-write loop — the one
60
+ * that cost 1,017,548 invocations in 24 hours. Obligations are reconciled
61
+ * inside the order's own write, where the document is already in hand.
62
+ */
63
+ export declare const obligationSweep: import("./types").ScheduledFunctionDefinition;
64
+ export declare const paymentReconcile: import("./types").ScheduledFunctionDefinition;
65
+ /**
66
+ * 🛑 ITS OWN FUNCTION, AND DELIBERATELY NOT A FIRESTORE TRIGGER.
67
+ *
68
+ * A trigger on `inventoryPlacements` writing back to `storageBoxes` would fire
69
+ * on every pick, move and receive — a 50-row receive is 50 placement writes and
70
+ * would wake it 50 times to recompute the same two boxes. It is also one
71
+ * refactor away from Root Cause #92's shape, which cost 1,017,548 invocations
72
+ * in 24 hours against a 2M/month free quota.
73
+ *
74
+ * Nightly is right because the rollup is ADVISORY: every surface that must be
75
+ * correct reads placements directly (the box detail page does, and says so),
76
+ * so a day-stale rollup costs a slightly wrong number in a list and never a
77
+ * wrong decision.
78
+ *
79
+ * 🛑 06:15 UTC, AND THE SLOT WAS CHOSEN BY COUNTING, NOT BY EYE. My first
80
+ * pick was 04:30 with a comment claiming it kept three collection-walking jobs
81
+ * out of the same minute — while `30 4 * * *` ALREADY held two
82
+ * (`priceIndexRollup` at 540s/512MiB among them), so it would have made three
83
+ * and the comment would have asserted the opposite of what it did.
84
+ *
85
+ * `grep -o 'cron: "…"' | sort | uniq -c` over this file is the check: 06:15 is
86
+ * empty, clear of the dense 00:00–05:30 band and of the 10:00 IST digest.
87
+ */
88
+ export declare const boxRollupReconcile: import("./types").ScheduledFunctionDefinition;
18
89
  export declare const positionsReconcile: import("./types").ScheduledFunctionDefinition;
19
90
  export declare const cartPrune: import("./types").ScheduledFunctionDefinition;
20
91
  export declare const notificationPrune: import("./types").ScheduledFunctionDefinition;
@@ -28,4 +99,4 @@ export declare const prizeDrawExpiryReveal: import("./types").ScheduledFunctionD
28
99
  export declare const bundleStockSync: import("./types").ScheduledFunctionDefinition;
29
100
  export declare const emiInstallmentReminder: import("./types").ScheduledFunctionDefinition;
30
101
  export declare const catalogueImageStalenessReminder: import("./types").ScheduledFunctionDefinition;
31
- export declare const SCHEDULED_FUNCTIONS: readonly [import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition];
102
+ export declare const SCHEDULED_FUNCTIONS: readonly [import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition];
@@ -1,6 +1,6 @@
1
1
  // Scheduled function definitions (Cloud Scheduler triggers).
2
2
  // Cron syntax accepts both Firebase shorthand and standard cron expressions.
3
- import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, paymentWindowTimeoutHandler, hardBanReinstatementHandler, paymentReviewAutoApproveHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeDrawExpiryRevealHandler, productStatsSyncHandler, revenueRollupHandler, priceIndexRollupHandler, searchIndexBuildHandler, analyticsAlertEvaluateHandler, pageViewRollupHandler, pageViewPruneHandler, dailyStatusDigestHandler, weeklyPayoutEligibilityHandler, testDataCleanupHandler, } from "../jobs/handlers/index.js";
3
+ import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, boxRollupReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, obligationSweepHandler, paymentReconcileHandler, paymentWindowTimeoutHandler, hardBanReinstatementHandler, paymentReviewAutoApproveHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeDrawExpiryRevealHandler, productStatsSyncHandler, revenueRollupHandler, priceIndexRollupHandler, marketPriceRollupHandler, searchIndexBuildHandler, analyticsAlertEvaluateHandler, pageViewRollupHandler, pageViewPruneHandler, dailyStatusDigestHandler, weeklyPayoutEligibilityHandler, testDataCleanupHandler, } from "../jobs/handlers/index.js";
4
4
  import { defineFunction } from "./define.js";
5
5
  const REGION = "asia-south1";
6
6
  const EVERY_5_MIN = "every 5 minutes";
@@ -113,6 +113,28 @@ export const priceIndexRollup = defineFunction({
113
113
  handler: priceIndexRollupHandler,
114
114
  options: { region: REGION, timeoutSeconds: 540, memory: "512MiB", maxInstances: 1 },
115
115
  });
116
+ /*
117
+ * The ASKING half of the same page, from dated `priceObservations` rows.
118
+ *
119
+ * 🛑 A separate function from `priceIndexRollup`, not a second pass inside it,
120
+ * and the reason is the one that file already states for itself: these two
121
+ * bands are *"rendered side by side and never averaged"*. Sharing a function
122
+ * would also share a failure — a bad observation import taking the realised-
123
+ * sale index down with it, when the two read entirely different collections and
124
+ * have no reason to fail together.
125
+ *
126
+ * 05:00 UTC, half an hour after the sold half: both feed one page, and running
127
+ * them adjacently means a reader never sees one half a day fresher than the
128
+ * other. Not simultaneously — `maxInstances: 1` on each, but they would still
129
+ * contend for the same Firestore read budget in the same minute for no gain.
130
+ */
131
+ export const marketPriceRollup = defineFunction({
132
+ name: "marketPriceRollup",
133
+ description: "Roll up dated price observations into marketPriceIndex/{leafId} — mode, 6-month low/high (daily 05:00 UTC = 10:30 IST).",
134
+ trigger: { kind: "schedule", cron: "0 5 * * *", timeZone: "UTC" },
135
+ handler: marketPriceRollupHandler,
136
+ options: { region: REGION, timeoutSeconds: 540, memory: "512MiB", maxInstances: 1 },
137
+ });
116
138
  /*
117
139
  * Evaluate the alert rules sellers and admins configured — hourly, 20 past.
118
140
  *
@@ -178,6 +200,94 @@ export const countersReconcile = defineFunction({
178
200
  handler: countersReconcileHandler,
179
201
  options: { region: REGION, timeoutSeconds: 540, memory: "256MiB", maxInstances: 1 },
180
202
  });
203
+ /**
204
+ * Payment reconciliation — R2, R3 and R4. Daily 05:30 UTC.
205
+ *
206
+ * 🛑 ITS OWN FUNCTION, and the two obvious places to fold it are both wrong.
207
+ *
208
+ * `countersReconcile` above already runs at its 540 s ceiling, so adding work
209
+ * there is how a job starts timing out and taking category metrics with it.
210
+ * `autoPayoutEligibility` is tempting because both touch orders, but
211
+ * `priceIndexRollup`'s own comment sets the precedent: a job should be able to
212
+ * fail without taking an unrelated one down, and these two share nothing beyond
213
+ * the collection.
214
+ *
215
+ * Cloud Scheduler bills **per registered job** (3 free, 28 already registered),
216
+ * so this is ~$0.10/month — a cost the Firebase budget table explicitly accepts
217
+ * for a distinct cadence rather than contorting the schedule around it.
218
+ *
219
+ * 🛑 R1 is NOT here. It is folded into `paymentWindowTimeout` (every 5 minutes),
220
+ * because a webhook we received and lost should not wait up to 24 hours to be
221
+ * noticed — and its query is one indexed read, so the tight loop is free.
222
+ *
223
+ * 05:30 UTC = 11:00 IST, deliberately after `countersReconcile` (03:00) and
224
+ * `autoPayoutEligibility` so a payout decision is never taken on the same tick
225
+ * as the reconciliation that might open a case blocking it.
226
+ */
227
+ /**
228
+ * Every deadline in the order lifecycle — one job, every 5 minutes.
229
+ *
230
+ * 🛑 ONE JOB FOR ~20 SCENARIOS, and the Scheduler count goes DOWN.
231
+ *
232
+ * The alternative was a cron per scenario, which is ~$2/month *and* `O(n²)`
233
+ * coverage reasoning — D25, D27 and the duplicate buyout notification are all
234
+ * that pattern failing. Here it is one indexed query per tick, empty when idle,
235
+ * and `obligations`' deterministic document id gives "at most one open
236
+ * obligation per (subject, kind)" so a double reconcile is a no-op.
237
+ *
238
+ * Phase 8 retires `paymentWindowTimeout`, `pendingOrderTimeout` and
239
+ * `paymentReviewAutoApprove` once their policy rows are proven, taking the
240
+ * registered count **28 → 29 → 27**.
241
+ *
242
+ * 🛑 NOT a Firestore trigger on `orders`. A trigger writing `obligations`
243
+ * whose sweep writes `orders` is Root Cause #92's self-write loop — the one
244
+ * that cost 1,017,548 invocations in 24 hours. Obligations are reconciled
245
+ * inside the order's own write, where the document is already in hand.
246
+ */
247
+ export const obligationSweep = defineFunction({
248
+ name: "obligationSweep",
249
+ description: "Fire, warn on, or escalate every order-lifecycle deadline that has come due (every 5 minutes).",
250
+ trigger: { kind: "schedule", cron: EVERY_5_MIN },
251
+ handler: obligationSweepHandler,
252
+ options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
253
+ });
254
+ export const paymentReconcile = defineFunction({
255
+ name: "paymentReconcile",
256
+ description: "Reconcile the gateway's payments, refunds and disputes against our orders; open paymentCases (daily 05:30 UTC).",
257
+ trigger: { kind: "schedule", cron: "30 5 * * *", timeZone: "UTC" },
258
+ handler: paymentReconcileHandler,
259
+ options: { region: REGION, timeoutSeconds: 540, memory: "256MiB", maxInstances: 1 },
260
+ });
261
+ /**
262
+ * 🛑 ITS OWN FUNCTION, AND DELIBERATELY NOT A FIRESTORE TRIGGER.
263
+ *
264
+ * A trigger on `inventoryPlacements` writing back to `storageBoxes` would fire
265
+ * on every pick, move and receive — a 50-row receive is 50 placement writes and
266
+ * would wake it 50 times to recompute the same two boxes. It is also one
267
+ * refactor away from Root Cause #92's shape, which cost 1,017,548 invocations
268
+ * in 24 hours against a 2M/month free quota.
269
+ *
270
+ * Nightly is right because the rollup is ADVISORY: every surface that must be
271
+ * correct reads placements directly (the box detail page does, and says so),
272
+ * so a day-stale rollup costs a slightly wrong number in a list and never a
273
+ * wrong decision.
274
+ *
275
+ * 🛑 06:15 UTC, AND THE SLOT WAS CHOSEN BY COUNTING, NOT BY EYE. My first
276
+ * pick was 04:30 with a comment claiming it kept three collection-walking jobs
277
+ * out of the same minute — while `30 4 * * *` ALREADY held two
278
+ * (`priceIndexRollup` at 540s/512MiB among them), so it would have made three
279
+ * and the comment would have asserted the opposite of what it did.
280
+ *
281
+ * `grep -o 'cron: "…"' | sort | uniq -c` over this file is the check: 06:15 is
282
+ * empty, clear of the dense 00:00–05:30 band and of the 10:00 IST digest.
283
+ */
284
+ export const boxRollupReconcile = defineFunction({
285
+ name: "boxRollupReconcile",
286
+ description: "Recompute each storage box's product/unit rollup from its real placements (daily 06:15 UTC).",
287
+ trigger: { kind: "schedule", cron: "15 6 * * *", timeZone: "UTC" },
288
+ handler: boxRollupReconcileHandler,
289
+ options: { region: REGION, timeoutSeconds: 540, memory: "256MiB", maxInstances: 1 },
290
+ });
181
291
  export const positionsReconcile = defineFunction({
182
292
  name: "positionsReconcile",
183
293
  description: "Reconcile bid/auction positions (daily 03:30 UTC).",
@@ -300,6 +410,7 @@ export const SCHEDULED_FUNCTIONS = [
300
410
  productStatsSync,
301
411
  revenueRollup,
302
412
  priceIndexRollup,
413
+ marketPriceRollup,
303
414
  searchIndexBuild,
304
415
  analyticsAlertEvaluate,
305
416
  pageViewRollup,
@@ -307,6 +418,18 @@ export const SCHEDULED_FUNCTIONS = [
307
418
  dailyStatusDigest,
308
419
  dailyDataCleanup,
309
420
  countersReconcile,
421
+ /*
422
+ * 🛑 Registering here is NOT optional and is easy to miss: `defineFunction`
423
+ * only creates the definition — `SCHEDULED_FUNCTIONS` is what
424
+ * `APPKIT_FUNCTIONS` spreads and `bindAllFromRegistry` binds. A function
425
+ * defined and not listed deploys nowhere and NOTHING ERRORS: the export
426
+ * exists, the file compiles, and the job simply never runs. That is the same
427
+ * silent-omission shape as `audit-barrel-no-seed`'s (an export whose absence
428
+ * makes a loader quietly skip a collection).
429
+ */
430
+ paymentReconcile,
431
+ obligationSweep,
432
+ boxRollupReconcile,
310
433
  positionsReconcile,
311
434
  cartPrune,
312
435
  notificationPrune,
@@ -1,28 +1,58 @@
1
1
  import { orderRepository, payoutRepository, storeRepository, userRepository, siteSettingsRepository, } from "../../../../repositories/index.js";
2
2
  import { getDefaultCurrency } from "../../../../core/index.js";
3
3
  import { computePayoutDeduction } from "../../../shared/fees/calculator.js";
4
- import { ordersTaxableValue } from "../../../shared/checkout/order-math.js";
4
+ import { payoutCutoff } from "../../../shared/fees/payout-hold.js";
5
+ import { PROTECTION_CLAIM_WINDOW_HOURS } from "../../../shared/features/shipping/protections.js";
6
+ import { ordersTaxableValue, ordersShippingReimbursement, } from "../../../shared/checkout/order-math.js";
5
7
  import { BATCH_LIMIT } from "../handlers/messages.js";
6
- const AUTO_PAYOUT_WINDOW_DAYS = 7;
7
- function getBusinessDayCutoff(daysAgo) {
8
- const now = new Date();
9
- const cutoff = new Date(now);
10
- cutoff.setUTCDate(cutoff.getUTCDate() - daysAgo);
11
- cutoff.setUTCHours(4, 30, 0, 0);
12
- return cutoff;
13
- }
8
+ /*
9
+ * 🛑 `AUTO_PAYOUT_WINDOW_DAYS = 7` and `getBusinessDayCutoff` were REMOVED on
10
+ * 2026-10-21. Do not reinstate either.
11
+ *
12
+ * The constant was a hardcoded duplicate of `commissions.payoutHoldDays`,
13
+ * which no code read (D28) — so the admin-facing setting was inert while this
14
+ * number decided every payout.
15
+ *
16
+ * The helper was worse: it did `cutoff.setUTCDate(cutoff.getUTCDate() -
17
+ * daysAgo)`, i.e. it subtracted CALENDAR days under a name that said business
18
+ * days. Measured from Monday 2026-10-19, 7 business days back is 2026-10-08
19
+ * and that helper returned 2026-10-12 — **four calendar days short**, so the
20
+ * stated 7-business-day hold was really about five, and every hold spanning a
21
+ * weekend released early in the seller's favour with nothing recording that
22
+ * the stated hold had not been honoured.
23
+ *
24
+ * Both now come from `payoutCutoff`, which is shared with
25
+ * `weeklyPayoutEligibility` so the two sweeps cannot disagree about the hold —
26
+ * they did, and one of them had no cutoff at all.
27
+ */
14
28
  export async function runAutoPayoutEligibility(ctx) {
15
29
  // `FEATURE_PAYOUTS` gate deleted 2026-10-19 — see `auctionSettlement.ts`.
16
30
  // This was the one flag actually set (`functions/.env.letitrip-in-app`), so
17
31
  // unlike its four siblings this job was running; removing the gate is a
18
32
  // no-op in behaviour and removes the lever that could silently stop payouts.
19
- ctx.logger.info("Starting daily auto-payout eligibility sweep", {
20
- windowDays: AUTO_PAYOUT_WINDOW_DAYS,
21
- });
22
33
  const siteSettings = await siteSettingsRepository.getSingleton();
23
34
  const commissions = siteSettings.commissions;
24
- const cutoff = getBusinessDayCutoff(AUTO_PAYOUT_WINDOW_DAYS);
25
- const eligible = await orderRepository.getEligibleAutomatic(cutoff);
35
+ const { cutoff, hold } = payoutCutoff({
36
+ payoutHoldDays: commissions?.payoutHoldDays,
37
+ returnWindowDays: siteSettings.returns?.windowDays,
38
+ maxClaimWindowHours: Math.max(PROTECTION_CLAIM_WINDOW_HOURS.shipping, PROTECTION_CLAIM_WINDOW_HOURS.sale),
39
+ }, { onWarn: (m, meta) => ctx.logger.warn(m, meta) });
40
+ /*
41
+ * Log the hold AND which input won. The admin setting is seeded at 2 days
42
+ * against a 7-day floor, so the honoured value will usually not be the one
43
+ * they configured — saying `source: "floor"` is what stops that reading as
44
+ * a bug to whoever set it.
45
+ */
46
+ ctx.logger.info("Starting daily auto-payout eligibility sweep", {
47
+ holdDays: hold.days,
48
+ holdSource: hold.source,
49
+ floorDays: hold.floor,
50
+ configured: commissions?.payoutHoldDays ?? null,
51
+ cutoff: cutoff.toISOString(),
52
+ });
53
+ const eligible = await orderRepository.getEligibleAutomatic(cutoff, {
54
+ onWarn: (m, meta) => ctx.logger.warn(m, meta),
55
+ });
26
56
  if (eligible.length === 0) {
27
57
  ctx.logger.info("No auto-payout eligible orders found");
28
58
  return;
@@ -82,7 +112,15 @@ export async function runAutoPayoutEligibility(ctx) {
82
112
  * so the goods value still has our GST inside it.
83
113
  */
84
114
  const grossAmount = ordersTaxableValue(orders.map((o) => o.data));
85
- const { platformFee, gatewayFee, gstOnFee: gstAmount, netAmount } = computePayoutDeduction(grossAmount, commissions);
115
+ const { platformFee, gatewayFee, gstOnFee: gstAmount, netAmount } = computePayoutDeduction(grossAmount, commissions,
116
+ /*
117
+ * D45 — postage refunded to the seller, added AFTER the fee bases so no
118
+ * commission is charged on a cost being refunded. `.data` for the same
119
+ * reason `ordersTaxableValue` above needs it: `orders` holds
120
+ * `{ id, data }` wrappers, and passing the wrapper would read every field
121
+ * as undefined and silently reimburse zero.
122
+ */
123
+ ordersShippingReimbursement(orders.map((o) => o.data)));
86
124
  const payoutInput = {
87
125
  storeId,
88
126
  // `displayName` is plaintext by D1; the email fallback must be revealed.
@@ -121,7 +159,15 @@ export async function runAutoPayoutEligibility(ctx) {
121
159
  bankName: sellerPlain.payoutDetails.bankAccount.bankName,
122
160
  }
123
161
  : undefined,
124
- notes: `Auto-payout — ${orders.length} delivered order(s) past ${AUTO_PAYOUT_WINDOW_DAYS}-day window`,
162
+ /*
163
+ * 🛑 `hold.days`, NOT the deleted `AUTO_PAYOUT_WINDOW_DAYS`. This note
164
+ * still named the constant that was removed on 2026-10-21 for being a
165
+ * hardcoded duplicate of `commissions.payoutHoldDays` — see the header.
166
+ * Reinstating it is explicitly forbidden there, and it would also make
167
+ * the note LIE: `hold.days` is the value actually honoured, which is
168
+ * the floor or the admin setting, whichever won.
169
+ */
170
+ notes: `Auto-payout — ${orders.length} delivered order(s) past the ${hold.days}-day hold`,
125
171
  requestedAt: new Date(),
126
172
  };
127
173
  const { id: payoutId } = await payoutRepository.createPayout(payoutInput);
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Recompute every storage box's `rollup` from its real placements.
3
+ *
4
+ * ── WHY A JOB AND NOT A TRIGGER ─────────────────────────────────────────────
5
+ *
6
+ * `rollup` exists so the boxes LIST can show "8 products · 23 units" without
7
+ * one placements query per row. It is a cache, and this is its only writer.
8
+ *
9
+ * 🛑 DELIBERATELY NOT A FIRESTORE TRIGGER ON `inventoryPlacements`. A trigger
10
+ * that watched placements and wrote back to `storageBoxes` would fire on every
11
+ * single pick, move and receive — a 50-row receive is 50 placement writes and
12
+ * would wake it 50 times to recompute the same two boxes. Worse, it is one
13
+ * refactor away from Root Cause #92's shape: a trigger whose write-back lands
14
+ * in a collection it also watches, which cost this project 1,017,548
15
+ * invocations in 24 hours against a 2M/month free quota.
16
+ *
17
+ * A nightly pass is the right cadence BECAUSE THE CACHE IS ADVISORY. Every
18
+ * surface that must be correct reads placements directly — the box detail page
19
+ * does, and its own note says why — so a rollup up to a day stale costs a
20
+ * slightly wrong number in a list, never a wrong decision.
21
+ *
22
+ * ── WHAT IT FIXES, AS OPPOSED TO MAINTAINS ──────────────────────────────────
23
+ *
24
+ * It is a REPAIR job, which is the lesson from Root Cause #102: a reconciler
25
+ * must enumerate the ENTITIES, not the events, or it can only ever add. A box
26
+ * whose last product was picked yesterday has no placements at all — so a pass
27
+ * built from placements would never visit it, and it would keep advertising
28
+ * the count it had when it was last full. Walking `storageBoxes` and treating
29
+ * "no placements" as a real zero is the whole point.
30
+ *
31
+ * It also logs `drifted`. On a quiet day that number should be 0, and a
32
+ * non-zero one is a real signal that something wrote placements without the
33
+ * repository — which `audit-inventory-ledger-append-only` is supposed to make
34
+ * impossible.
35
+ *
36
+ * @tag domain:inventory
37
+ * @tag layer:job
38
+ * @tag access:server
39
+ */
40
+ import type { JobContext } from "../runtime/types";
41
+ export interface BoxRollupReconcileResult {
42
+ boxesVisited: number;
43
+ drifted: number;
44
+ written: number;
45
+ }
46
+ export declare function runBoxRollupReconcile(ctx: JobContext): Promise<BoxRollupReconcileResult>;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Recompute every storage box's `rollup` from its real placements.
3
+ *
4
+ * ── WHY A JOB AND NOT A TRIGGER ─────────────────────────────────────────────
5
+ *
6
+ * `rollup` exists so the boxes LIST can show "8 products · 23 units" without
7
+ * one placements query per row. It is a cache, and this is its only writer.
8
+ *
9
+ * 🛑 DELIBERATELY NOT A FIRESTORE TRIGGER ON `inventoryPlacements`. A trigger
10
+ * that watched placements and wrote back to `storageBoxes` would fire on every
11
+ * single pick, move and receive — a 50-row receive is 50 placement writes and
12
+ * would wake it 50 times to recompute the same two boxes. Worse, it is one
13
+ * refactor away from Root Cause #92's shape: a trigger whose write-back lands
14
+ * in a collection it also watches, which cost this project 1,017,548
15
+ * invocations in 24 hours against a 2M/month free quota.
16
+ *
17
+ * A nightly pass is the right cadence BECAUSE THE CACHE IS ADVISORY. Every
18
+ * surface that must be correct reads placements directly — the box detail page
19
+ * does, and its own note says why — so a rollup up to a day stale costs a
20
+ * slightly wrong number in a list, never a wrong decision.
21
+ *
22
+ * ── WHAT IT FIXES, AS OPPOSED TO MAINTAINS ──────────────────────────────────
23
+ *
24
+ * It is a REPAIR job, which is the lesson from Root Cause #102: a reconciler
25
+ * must enumerate the ENTITIES, not the events, or it can only ever add. A box
26
+ * whose last product was picked yesterday has no placements at all — so a pass
27
+ * built from placements would never visit it, and it would keep advertising
28
+ * the count it had when it was last full. Walking `storageBoxes` and treating
29
+ * "no placements" as a real zero is the whole point.
30
+ *
31
+ * It also logs `drifted`. On a quiet day that number should be 0, and a
32
+ * non-zero one is a real signal that something wrote placements without the
33
+ * repository — which `audit-inventory-ledger-append-only` is supposed to make
34
+ * impossible.
35
+ *
36
+ * @tag domain:inventory
37
+ * @tag layer:job
38
+ * @tag access:server
39
+ */
40
+ import { INVENTORY_PLACEMENTS_COLLECTION, STORAGE_BOXES_COLLECTION, } from "../../../../features/inventory/schemas/firestore.js";
41
+ /** Pages rather than `.limit(1000)` — see the note at the call. */
42
+ const BOX_PAGE = 200;
43
+ export async function runBoxRollupReconcile(ctx) {
44
+ const db = ctx.db;
45
+ let boxesVisited = 0;
46
+ let drifted = 0;
47
+ let written = 0;
48
+ /*
49
+ * 🛑 PAGINATED, NOT `.limit(N)`. A truncated reconcile is indistinguishable
50
+ * from a complete one — it logs "reconciliation complete" having silently
51
+ * ignored every box past the cap, which is exactly how the counters job
52
+ * under-reported for weeks (Root Cause #102). Ordering by document id gives
53
+ * a stable cursor with no index.
54
+ */
55
+ let cursor = null;
56
+ for (;;) {
57
+ let q = db
58
+ .collection(STORAGE_BOXES_COLLECTION)
59
+ .orderBy("__name__")
60
+ .limit(BOX_PAGE);
61
+ if (cursor)
62
+ q = q.startAfter(cursor);
63
+ const page = await q.get();
64
+ if (page.empty)
65
+ break;
66
+ for (const doc of page.docs) {
67
+ boxesVisited += 1;
68
+ const box = doc.data();
69
+ /*
70
+ * One query per box. Bounded and cheap — a box holds tens of products,
71
+ * not thousands — and `.select()` fetches only the two fields the tally
72
+ * needs rather than every placement's denormalised title and SKU.
73
+ */
74
+ const placements = await db
75
+ .collection(INVENTORY_PLACEMENTS_COLLECTION)
76
+ .where("boxId", "==", doc.id)
77
+ .select("quantity")
78
+ .get();
79
+ let distinctProducts = 0;
80
+ let totalUnits = 0;
81
+ for (const p of placements.docs) {
82
+ const qty = p.data()
83
+ .quantity;
84
+ /*
85
+ * A zero- or negative-quantity placement should not exist — `pick`
86
+ * deletes at zero — so it is counted as neither a product nor a unit
87
+ * and reported, rather than quietly inflating `distinctProducts` with
88
+ * a row representing nothing.
89
+ */
90
+ if (!(typeof qty === "number" && qty > 0))
91
+ continue;
92
+ distinctProducts += 1;
93
+ totalUnits += qty;
94
+ }
95
+ const prior = box.rollup;
96
+ const changed = !prior ||
97
+ prior.distinctProducts !== distinctProducts ||
98
+ prior.totalUnits !== totalUnits;
99
+ if (!changed)
100
+ continue;
101
+ drifted += 1;
102
+ /*
103
+ * A direct `update`, not `storageBoxesRepository.setRollup`. The job
104
+ * owns thousands of these and a repository call per box would re-read
105
+ * the document it already holds — and the repository's own note says
106
+ * `setRollup` is for a caller that has a box id and nothing else.
107
+ *
108
+ * Only the three rollup fields plus `updatedAt` are written: a merge of
109
+ * the whole document would resurrect any field a concurrent edit had
110
+ * just cleared.
111
+ */
112
+ /*
113
+ * `ctx.now`, not `new Date()`. Every box in one pass then carries the
114
+ * SAME `computedAt`, so "as of" on a printed label means the pass rather
115
+ * than the millisecond that box happened to be reached — and two boxes
116
+ * reconciled by one run cannot appear to have been counted minutes
117
+ * apart.
118
+ */
119
+ await doc.ref.update({
120
+ rollup: { distinctProducts, totalUnits, computedAt: ctx.now },
121
+ updatedAt: ctx.now,
122
+ });
123
+ written += 1;
124
+ }
125
+ if (page.size < BOX_PAGE)
126
+ break;
127
+ cursor = page.docs[page.docs.length - 1].id;
128
+ }
129
+ /*
130
+ * `drifted` is the signal, not `boxesVisited`. Every placement write goes
131
+ * through the repository, which keeps nothing cached — so on a quiet day
132
+ * this should be the number of boxes whose contents actually changed since
133
+ * the last pass. A number far larger than that, or one that never falls,
134
+ * means something is writing placements outside the repository.
135
+ */
136
+ ctx.logger.info("boxRollupReconcile complete", {
137
+ boxesVisited,
138
+ drifted,
139
+ written,
140
+ });
141
+ return { boxesVisited, drifted, written };
142
+ }