@mohasinac/appkit 4.62.0 → 4.62.1

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 (204) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/server/features/checkout/actions.d.ts +80 -0
  3. package/dist/_internal/server/features/checkout/actions.js +542 -26
  4. package/dist/_internal/server/features/invoicing/allocate.d.ts +63 -0
  5. package/dist/_internal/server/features/invoicing/allocate.js +162 -0
  6. package/dist/_internal/server/features/invoicing/credit-note.d.ts +57 -0
  7. package/dist/_internal/server/features/invoicing/credit-note.js +126 -0
  8. package/dist/_internal/server/features/invoicing/gstr1.d.ts +74 -0
  9. package/dist/_internal/server/features/invoicing/gstr1.js +251 -0
  10. package/dist/_internal/server/features/invoicing/issue.d.ts +96 -0
  11. package/dist/_internal/server/features/invoicing/issue.js +202 -0
  12. package/dist/_internal/server/features/orders/actions.d.ts +8 -1
  13. package/dist/_internal/server/features/orders/actions.js +81 -8
  14. package/dist/_internal/server/features/orders/adapters.js +33 -0
  15. package/dist/_internal/server/features/orders/cancellation-refund.d.ts +15 -9
  16. package/dist/_internal/server/features/orders/cancellation-refund.js +15 -9
  17. package/dist/_internal/server/features/points/award.js +46 -4
  18. package/dist/_internal/server/features/refunds/actions.js +26 -0
  19. package/dist/_internal/server/features/shipping/courier-block.d.ts +69 -0
  20. package/dist/_internal/server/features/shipping/courier-block.js +239 -0
  21. package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
  22. package/dist/_internal/server/features/site-settings/adapters.js +15 -0
  23. package/dist/_internal/server/features/stores/adapters.d.ts +2 -2
  24. package/dist/_internal/server/features/stores/adapters.js +51 -0
  25. package/dist/_internal/server/functions/scheduled.js +38 -1
  26. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +9 -2
  27. package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +4 -0
  28. package/dist/_internal/server/jobs/core/dailyStatusDigest.js +19 -2
  29. package/dist/_internal/server/jobs/core/index.d.ts +2 -1
  30. package/dist/_internal/server/jobs/core/index.js +2 -1
  31. package/dist/_internal/server/jobs/core/onJobCreated.d.ts +30 -7
  32. package/dist/_internal/server/jobs/core/onJobCreated.js +78 -45
  33. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +7 -0
  34. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +62 -0
  35. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +33 -0
  36. package/dist/_internal/server/jobs/core/runJobOnce.d.ts +48 -0
  37. package/dist/_internal/server/jobs/core/runJobOnce.js +195 -0
  38. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +10 -2
  39. package/dist/_internal/server/jobs/handlers/onOrderStatusChange.js +17 -0
  40. package/dist/_internal/server/notifications/channel-health.d.ts +26 -3
  41. package/dist/_internal/shared/actions/action-registry.js +28 -0
  42. package/dist/_internal/shared/checkout/coupon-discount.d.ts +43 -1
  43. package/dist/_internal/shared/checkout/coupon-discount.js +26 -1
  44. package/dist/_internal/shared/checkout/order-math.d.ts +145 -0
  45. package/dist/_internal/shared/checkout/order-math.js +173 -0
  46. package/dist/_internal/shared/checkout/refundable-base.d.ts +25 -0
  47. package/dist/_internal/shared/checkout/refundable-base.js +13 -1
  48. package/dist/_internal/shared/checkout/split.d.ts +87 -0
  49. package/dist/_internal/shared/checkout/split.js +100 -0
  50. package/dist/_internal/shared/content/disclaimers.d.ts +88 -0
  51. package/dist/_internal/shared/content/disclaimers.js +217 -0
  52. package/dist/_internal/shared/content/published-values.d.ts +88 -0
  53. package/dist/_internal/shared/content/published-values.js +198 -0
  54. package/dist/_internal/shared/features/admin/integrations.d.ts +109 -0
  55. package/dist/_internal/shared/features/admin/integrations.js +253 -0
  56. package/dist/_internal/shared/features/orders/dispute-reasons.d.ts +113 -0
  57. package/dist/_internal/shared/features/orders/dispute-reasons.js +181 -0
  58. package/dist/_internal/shared/features/orders/refund-copy.d.ts +1 -1
  59. package/dist/_internal/shared/features/orders/refund-copy.js +18 -1
  60. package/dist/_internal/shared/features/points/accepts.d.ts +36 -0
  61. package/dist/_internal/shared/features/points/accepts.js +43 -0
  62. package/dist/_internal/shared/features/shipping/shared-purchase.d.ts +91 -0
  63. package/dist/_internal/shared/features/shipping/shared-purchase.js +108 -0
  64. package/dist/_internal/shared/features/site-settings/types.d.ts +11 -0
  65. package/dist/_internal/shared/fees/calculator.d.ts +33 -1
  66. package/dist/_internal/shared/fees/calculator.js +32 -2
  67. package/dist/client.d.ts +11 -0
  68. package/dist/client.js +30 -0
  69. package/dist/constants/api-endpoints.d.ts +42 -0
  70. package/dist/constants/api-endpoints.js +14 -0
  71. package/dist/constants/field-names.d.ts +1 -0
  72. package/dist/constants/field-names.js +5 -0
  73. package/dist/contracts/index.d.ts +1 -1
  74. package/dist/contracts/shipping.d.ts +45 -0
  75. package/dist/contracts/shipping.js +15 -0
  76. package/dist/features/about/components/AboutView.js +1 -1
  77. package/dist/features/account/components/BuyerAuctionsGuideView.js +4 -1
  78. package/dist/features/account/components/UserOrdersView.js +19 -33
  79. package/dist/features/admin/components/AdminCouponEditorView.js +17 -0
  80. package/dist/features/admin/components/AdminInvoicingView.d.ts +2 -0
  81. package/dist/features/admin/components/AdminInvoicingView.js +65 -0
  82. package/dist/features/admin/components/AdminOrderEditorView.d.ts +18 -1
  83. package/dist/features/admin/components/AdminOrderEditorView.js +11 -2
  84. package/dist/features/admin/components/AdminOrdersGuideView.js +23 -2
  85. package/dist/features/admin/components/AdminOrdersView.js +67 -5
  86. package/dist/features/admin/components/OwnerQuickActions.js +24 -2
  87. package/dist/features/admin/constants/filter-tabs.d.ts +6 -0
  88. package/dist/features/admin/constants/filter-tabs.js +6 -0
  89. package/dist/features/admin/constants/site-settings-tabs.d.ts +1 -1
  90. package/dist/features/admin/hooks/useAdminListingData.js +16 -0
  91. package/dist/features/admin/schemas/admin-user-form.d.ts +2 -2
  92. package/dist/features/admin/schemas/firestore.d.ts +30 -0
  93. package/dist/features/admin/schemas/firestore.js +5 -0
  94. package/dist/features/audit-log/schemas/firestore.d.ts +30 -0
  95. package/dist/features/audit-log/schemas/firestore.js +15 -0
  96. package/dist/features/auth/permissions/constants.js +13 -0
  97. package/dist/features/cart/components/CartPriceBreakdown.d.ts +13 -0
  98. package/dist/features/cart/components/CartPriceBreakdown.js +6 -2
  99. package/dist/features/cart/components/PointsTenderPicker.d.ts +61 -0
  100. package/dist/features/cart/components/PointsTenderPicker.js +15 -0
  101. package/dist/features/cart/components/StoreAddonsPicker.js +2 -1
  102. package/dist/features/cart/components/index.d.ts +2 -0
  103. package/dist/features/cart/components/index.js +1 -0
  104. package/dist/features/cart/repository/cart.repository.d.ts +19 -0
  105. package/dist/features/cart/repository/cart.repository.js +27 -0
  106. package/dist/features/cart/schemas/firestore.d.ts +92 -0
  107. package/dist/features/contact/email.d.ts +27 -10
  108. package/dist/features/contact/email.js +20 -3
  109. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +38 -0
  110. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +80 -0
  111. package/dist/features/dispute-reasons/schemas/firestore.d.ts +64 -0
  112. package/dist/features/dispute-reasons/schemas/firestore.js +40 -0
  113. package/dist/features/faq/components/FAQAccordion.d.ts +13 -1
  114. package/dist/features/faq/components/FAQAccordion.js +29 -4
  115. package/dist/features/invoicing/schemas/firestore.d.ts +112 -0
  116. package/dist/features/invoicing/schemas/firestore.js +45 -0
  117. package/dist/features/invoicing/utils/financial-year.d.ts +43 -0
  118. package/dist/features/invoicing/utils/financial-year.js +53 -0
  119. package/dist/features/jobs/actions/enqueue-job.js +18 -1
  120. package/dist/features/jobs/repository/jobs.repository.d.ts +62 -1
  121. package/dist/features/jobs/repository/jobs.repository.js +84 -1
  122. package/dist/features/jobs/schemas/firestore.d.ts +80 -0
  123. package/dist/features/jobs/schemas/firestore.js +37 -0
  124. package/dist/features/lottery/schemas/zod.d.ts +6 -6
  125. package/dist/features/media/MediaImage.js +22 -1
  126. package/dist/features/media/upload/ImageUpload.js +1 -1
  127. package/dist/features/obligations/constants/policies.js +24 -1
  128. package/dist/features/orders/components/OrderPaymentSummary.js +10 -1
  129. package/dist/features/orders/components/OrderProvenanceCard.d.ts +8 -0
  130. package/dist/features/orders/components/OrderProvenanceCard.js +185 -0
  131. package/dist/features/orders/components/RaiseDisputeForm.d.ts +14 -0
  132. package/dist/features/orders/components/RaiseDisputeForm.js +111 -0
  133. package/dist/features/orders/components/RefundRequestView.js +19 -1
  134. package/dist/features/orders/components/index.d.ts +4 -0
  135. package/dist/features/orders/components/index.js +2 -0
  136. package/dist/features/orders/constants/order-lane-tabs.d.ts +69 -0
  137. package/dist/features/orders/constants/order-lane-tabs.js +88 -0
  138. package/dist/features/orders/repository/orders.repository.d.ts +57 -0
  139. package/dist/features/orders/repository/orders.repository.js +145 -0
  140. package/dist/features/orders/schemas/firestore.d.ts +196 -1
  141. package/dist/features/orders/schemas/index.d.ts +54 -6
  142. package/dist/features/orders/schemas/index.js +12 -0
  143. package/dist/features/orders/types/index.d.ts +26 -0
  144. package/dist/features/orders/utils/courier-block.d.ts +25 -0
  145. package/dist/features/orders/utils/courier-block.js +23 -0
  146. package/dist/features/payment-cases/schemas/firestore.d.ts +26 -1
  147. package/dist/features/payment-cases/schemas/firestore.js +1 -0
  148. package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -2
  149. package/dist/features/products/components/ProductDetailPageView.d.ts +17 -1
  150. package/dist/features/products/components/ProductDetailPageView.js +2 -2
  151. package/dist/features/products/schemas/product-features.validators.d.ts +10 -10
  152. package/dist/features/promotions/actions/coupon-stacking.d.ts +30 -3
  153. package/dist/features/promotions/actions/coupon-stacking.js +21 -2
  154. package/dist/features/promotions/components/CouponCard.js +13 -0
  155. package/dist/features/promotions/constants/coupon-help.d.ts +2 -2
  156. package/dist/features/promotions/constants/coupon-help.js +9 -1
  157. package/dist/features/promotions/schemas/firestore.d.ts +24 -0
  158. package/dist/features/promotions/schemas/firestore.js +4 -0
  159. package/dist/features/promotions/types/index.d.ts +19 -1
  160. package/dist/features/scams/components/ScamProfileView.js +2 -2
  161. package/dist/features/scams/components/ScamRegistryView.js +2 -2
  162. package/dist/features/search/action-index/tab-entries.js +1 -1
  163. package/dist/features/seller/actions/seller-actions.js +33 -3
  164. package/dist/features/seller/components/SellerOrdersView.js +129 -27
  165. package/dist/features/seller/hooks/useSellerListingData.js +25 -0
  166. package/dist/features/shipments/schemas/validation.d.ts +4 -4
  167. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +43 -0
  168. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +90 -0
  169. package/dist/features/shipping-boxes/schemas/firestore.d.ts +82 -0
  170. package/dist/features/shipping-boxes/schemas/firestore.js +53 -0
  171. package/dist/features/store-extensions/schemas/report-create-form.d.ts +2 -2
  172. package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +2 -2
  173. package/dist/features/stores/components/StoreOrdersGuideView.js +6 -1
  174. package/dist/features/stores/schemas/firestore.d.ts +71 -0
  175. package/dist/features/stores/types/index.d.ts +19 -0
  176. package/dist/features/support/schemas/index.d.ts +6 -6
  177. package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
  178. package/dist/index.d.ts +6 -1
  179. package/dist/index.js +14 -1
  180. package/dist/next/routing/route-map.d.ts +20 -0
  181. package/dist/next/routing/route-map.js +10 -0
  182. package/dist/providers/shipping-shiprocket/index.d.ts +27 -1
  183. package/dist/providers/shipping-shiprocket/index.js +84 -1
  184. package/dist/schemas/registry.d.ts +47 -18
  185. package/dist/schemas/sieve.d.ts +2 -2
  186. package/dist/server-entry.d.ts +2 -2
  187. package/dist/server-entry.js +8 -1
  188. package/dist/server.d.ts +10 -1
  189. package/dist/server.js +39 -1
  190. package/dist/styles.css +64 -80
  191. package/dist/tailwind-utilities.css +1 -1
  192. package/dist/tokens/themes/default-dark.js +1 -0
  193. package/dist/tokens/themes/default-light.js +1 -0
  194. package/dist/tokens/themes/required.d.ts +1 -1
  195. package/dist/tokens/themes/required.js +1 -0
  196. package/dist/tokens/tokens.css +15 -0
  197. package/dist/ui/components/Disclaimer.d.ts +19 -0
  198. package/dist/ui/components/Disclaimer.js +74 -0
  199. package/dist/ui/components/tabs-overflow.js +64 -11
  200. package/dist/ui/index.d.ts +2 -0
  201. package/dist/ui/index.js +6 -0
  202. package/dist/ui/rich-text/RichText.style.css +48 -79
  203. package/package.json +1 -1
  204. package/scripts/seed-cli.mjs +1831 -1822
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Order PROVENANCE tabs — the one definition, shared by the buyer's and the
3
+ * seller's order lists.
4
+ *
5
+ * ## Why this file exists
6
+ *
7
+ * The buyer's list had these tabs and the seller's did not. A seller could see
8
+ * every order and could not ask "show me the ones I accepted an offer on" or
9
+ * "show me the auction wins", which is exactly the split that changes how they
10
+ * are handled: an auction win has a forfeit deadline, an accepted offer has an
11
+ * agreed price that is not the listing price.
12
+ *
13
+ * Copying the array into the second view would have been the ninth
14
+ * hand-written enumeration of one union in this codebase (Root Cause #61), so
15
+ * the labels are a `Record<OrderType, string>` instead: a sixth lane is a
16
+ * COMPILE error here rather than a tab that silently never appears.
17
+ *
18
+ * 🛑 Three things about these ids, each of which has already cost a bug:
19
+ *
20
+ * 1. **Every id is EXACTLY a stored `OrderDocument.orderType` value.** Firestore
21
+ * `==` is byte-exact, so a tab id that is merely plausible returns zero rows
22
+ * forever with no error anywhere (Root Cause #33).
23
+ *
24
+ * 2. **`preorder` has NO HYPHEN and that is not a typo to tidy up.** `OrderType`
25
+ * spells it `preorder`; `ListingType` spells the same concept `pre-order`.
26
+ * Two unions, two spellings, and the tab must match the one actually stored.
27
+ *
28
+ * 3. **`standard` must be filtered IN MEMORY, never as a Firestore `==`.**
29
+ * Orders written before `orderType` existed carry no such field, and an
30
+ * equality excludes every document that lacks it — so the "Normal" tab would
31
+ * hide every legacy order while looking like it worked. `mergeOrderScopeFilter`
32
+ * and the server-side list both already honour this; see `ORDER_LANE_ALL` for
33
+ * the "no filter" sentinel.
34
+ */
35
+ /**
36
+ * The "All" tab's id. An empty string rather than a word, so it can never
37
+ * collide with a real `orderType` value and so `state.orderType ? … : null`
38
+ * reads as "no filter" at every call site.
39
+ */
40
+ export const ORDER_LANE_ALL = "";
41
+ /**
42
+ * 🛑 A `Record<OrderType, string>`, deliberately. Omitting a lane does not
43
+ * compile, which is the whole point — the buyer's list shipped with four tabs
44
+ * for five lanes, so pre-order and prize-draw orders were reachable only by
45
+ * landing on "All" and scrolling.
46
+ */
47
+ export const ORDER_LANE_LABELS = {
48
+ standard: "Normal",
49
+ auction: "Auction wins",
50
+ offer: "Offer wins",
51
+ preorder: "Pre-orders",
52
+ "prize-draw": "Prize draws",
53
+ };
54
+ /**
55
+ * Display order, listed explicitly rather than taken from `Object.keys`.
56
+ *
57
+ * Key order on an object literal is not a contract anyone should rely on for
58
+ * UI ordering, and the intended order is not alphabetical: it mirrors the
59
+ * cart's lane PRIORITY (auction > offer > standard — highest obligation
60
+ * first), with the two schedule-driven lanes after them.
61
+ */
62
+ const LANE_ORDER = [
63
+ "standard",
64
+ "auction",
65
+ "offer",
66
+ "preorder",
67
+ "prize-draw",
68
+ ];
69
+ /**
70
+ * The stored `orderType` values a lane filter may name, for route-side
71
+ * validation.
72
+ *
73
+ * Derived from `ORDER_LANE_LABELS`, so it cannot drift from the tabs. Both
74
+ * `/api/user/orders` and `/api/store/orders` had — or would have needed — their
75
+ * own hand-written copy of these five strings; this is the eleventh
76
+ * enumeration of one union in this codebase collapsed into the union itself
77
+ * (Root Cause #61).
78
+ *
79
+ * 🛑 Validating against it is load-bearing, not hygiene: an unrecognised value
80
+ * interpolated into a Sieve filter string is a caller-controlled field name
81
+ * reaching the query builder.
82
+ */
83
+ export const VALID_ORDER_TYPES = new Set(Object.keys(ORDER_LANE_LABELS));
84
+ /** `All` first, then every lane in `LANE_ORDER`. */
85
+ export const ORDER_LANE_TABS = [
86
+ { id: ORDER_LANE_ALL, label: "All" },
87
+ ...LANE_ORDER.map((type) => ({ id: type, label: ORDER_LANE_LABELS[type] })),
88
+ ];
@@ -196,6 +196,10 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
196
196
  canFilter: boolean;
197
197
  canSort: boolean;
198
198
  };
199
+ orderType: {
200
+ canFilter: boolean;
201
+ canSort: boolean;
202
+ };
199
203
  giftWrapAddon: {
200
204
  canFilter: boolean;
201
205
  canSort: boolean;
@@ -404,6 +408,32 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
404
408
  status: "failed";
405
409
  failureReason?: string;
406
410
  }, becomeRefunded?: boolean, ctx?: OrderWriteContext): Promise<OrderDocument>;
411
+ /**
412
+ * Stamp a credit-note number onto one settled refund event (Phase 7, 7c).
413
+ *
414
+ * ## 🛑 Why this is a repository method and not an inline update
415
+ *
416
+ * `refunds[]` is written by re-mapping the whole array, exactly as
417
+ * `settleRefund` directly above does — because a read-modify-write of
418
+ * `[...existing, event]` LOSES a concurrent event (D6), and that defect is
419
+ * just as reachable from a credit-note stamp as from a refund append.
420
+ * Letting the invoicing feature touch the array directly would reintroduce
421
+ * it one caller away from the fix.
422
+ *
423
+ * It goes through `updateWithHistory` for the same reason every other
424
+ * mutator here does: the transition guard and the status-history entry live
425
+ * there, and this is a real recorded event on the order.
426
+ *
427
+ * 🛑 A missing refund event is a NO-OP, not a throw. The caller is the
428
+ * refund settle path, where money has already moved; raising here would turn
429
+ * a successful refund into a reported failure. See
430
+ * `issueCreditNoteForRefund`'s own 🛑.
431
+ */
432
+ setRefundCreditNote(orderId: string, refundId: string, note: {
433
+ creditNoteNumber: string;
434
+ creditNoteIssuedAt: Date;
435
+ creditNoteStoragePath?: string;
436
+ }, ctx?: OrderWriteContext): Promise<OrderDocument | null>;
407
437
  /**
408
438
  * 🛑 LEGACY — prefer `reserveRefund` + `settleRefund`.
409
439
  *
@@ -442,6 +472,33 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
442
472
  * `paymentProofUrl != null` filter would silently exclude every doc where
443
473
  * the field isn't set at all — the opposite of what's needed here.
444
474
  */
475
+ /**
476
+ * Orders invoiced within a window — the GSTR-1 export's only query
477
+ * (Phase 7d).
478
+ *
479
+ * ## 🛑 Windowed on `invoiceIssuedAt`, not on the order date
480
+ *
481
+ * A COD order placed on 30 March and paid on 2 April is invoiced in the NEW
482
+ * financial year, and that is the period its tax belongs to. Numbering
483
+ * happens at the payment transition for exactly this reason, so the invoice
484
+ * date is the only correct axis — windowing on `orderDate` would file that
485
+ * order's tax in the year before it was invoiced.
486
+ *
487
+ * ## 🛑 It also returns PAID orders with no invoice number, deliberately
488
+ *
489
+ * Those are the ones the export must warn about rather than omit — a paid
490
+ * order silently absent from a tax return is the single error this whole
491
+ * feature exists to prevent. Firestore cannot OR across two fields, so the
492
+ * second query is a separate one and the caller merges; the alternative
493
+ * (one query on `paymentStatus`, filtered in memory) would scan every paid
494
+ * order ever placed.
495
+ *
496
+ * Unbounded `.limit(2000)`: a quarter's orders at this volume is a few
497
+ * hundred, and an export that silently truncated would understate a tax
498
+ * return. If this ever saturates, the fix is pagination with an explicit
499
+ * "more rows exist" signal in the CSV — never a higher cap.
500
+ */
501
+ listInvoicedBetween(from: Date, to: Date): Promise<OrderDocument[]>;
445
502
  getExpiredPaymentDeadlines(): Promise<Array<{
446
503
  id: string;
447
504
  ref: DocumentReference;
@@ -783,6 +783,52 @@ class OrderRepository extends BaseRepository {
783
783
  reason: ctx?.reason ?? target.reason,
784
784
  }, "orderRepository.settleRefund", order);
785
785
  }
786
+ /**
787
+ * Stamp a credit-note number onto one settled refund event (Phase 7, 7c).
788
+ *
789
+ * ## 🛑 Why this is a repository method and not an inline update
790
+ *
791
+ * `refunds[]` is written by re-mapping the whole array, exactly as
792
+ * `settleRefund` directly above does — because a read-modify-write of
793
+ * `[...existing, event]` LOSES a concurrent event (D6), and that defect is
794
+ * just as reachable from a credit-note stamp as from a refund append.
795
+ * Letting the invoicing feature touch the array directly would reintroduce
796
+ * it one caller away from the fix.
797
+ *
798
+ * It goes through `updateWithHistory` for the same reason every other
799
+ * mutator here does: the transition guard and the status-history entry live
800
+ * there, and this is a real recorded event on the order.
801
+ *
802
+ * 🛑 A missing refund event is a NO-OP, not a throw. The caller is the
803
+ * refund settle path, where money has already moved; raising here would turn
804
+ * a successful refund into a reported failure. See
805
+ * `issueCreditNoteForRefund`'s own 🛑.
806
+ */
807
+ async setRefundCreditNote(orderId, refundId, note, ctx) {
808
+ const order = await this.findById(orderId);
809
+ if (!order)
810
+ return null;
811
+ const existing = order.refunds ?? [];
812
+ const target = existing.find((r) => r.refundId === refundId);
813
+ if (!target)
814
+ return null;
815
+ // Already numbered — never overwrite. A credit-note number is issued once,
816
+ // and a second number for one reversal is a permanent gap in the series.
817
+ if (target.creditNoteNumber)
818
+ return order;
819
+ const stamped = existing.map((r) => r.refundId === refundId ? { ...r, ...note } : r);
820
+ return this.updateWithHistory(orderId, { refunds: stamped, updatedAt: new Date() }, {
821
+ ...ctx,
822
+ extraChanges: {
823
+ creditNote: {
824
+ from: null,
825
+ to: { refundId, creditNoteNumber: note.creditNoteNumber },
826
+ },
827
+ },
828
+ actor: ctx?.actor ?? { uid: "system:invoicing", role: "system" },
829
+ reason: ctx?.reason ?? `Credit note ${note.creditNoteNumber} issued`,
830
+ }, "orderRepository.setRefundCreditNote", order);
831
+ }
786
832
  /**
787
833
  * 🛑 LEGACY — prefer `reserveRefund` + `settleRefund`.
788
834
  *
@@ -878,6 +924,68 @@ class OrderRepository extends BaseRepository {
878
924
  * `paymentProofUrl != null` filter would silently exclude every doc where
879
925
  * the field isn't set at all — the opposite of what's needed here.
880
926
  */
927
+ /**
928
+ * Orders invoiced within a window — the GSTR-1 export's only query
929
+ * (Phase 7d).
930
+ *
931
+ * ## 🛑 Windowed on `invoiceIssuedAt`, not on the order date
932
+ *
933
+ * A COD order placed on 30 March and paid on 2 April is invoiced in the NEW
934
+ * financial year, and that is the period its tax belongs to. Numbering
935
+ * happens at the payment transition for exactly this reason, so the invoice
936
+ * date is the only correct axis — windowing on `orderDate` would file that
937
+ * order's tax in the year before it was invoiced.
938
+ *
939
+ * ## 🛑 It also returns PAID orders with no invoice number, deliberately
940
+ *
941
+ * Those are the ones the export must warn about rather than omit — a paid
942
+ * order silently absent from a tax return is the single error this whole
943
+ * feature exists to prevent. Firestore cannot OR across two fields, so the
944
+ * second query is a separate one and the caller merges; the alternative
945
+ * (one query on `paymentStatus`, filtered in memory) would scan every paid
946
+ * order ever placed.
947
+ *
948
+ * Unbounded `.limit(2000)`: a quarter's orders at this volume is a few
949
+ * hundred, and an export that silently truncated would understate a tax
950
+ * return. If this ever saturates, the fix is pagination with an explicit
951
+ * "more rows exist" signal in the CSV — never a higher cap.
952
+ */
953
+ async listInvoicedBetween(from, to) {
954
+ const [invoiced, paidUnnumbered] = await Promise.all([
955
+ this.db
956
+ .collection(this.collection)
957
+ .where("invoiceIssuedAt", ">=", from)
958
+ .where("invoiceIssuedAt", "<=", to)
959
+ .limit(2000)
960
+ .get(),
961
+ /*
962
+ * The exceptions. Bounded on `orderDate` because an un-invoiced order
963
+ * has no invoice date to window on — which is the whole problem with it
964
+ * — and because `paymentStatus == paid` alone would match every paid
965
+ * order in the collection's history.
966
+ */
967
+ this.db
968
+ .collection(this.collection)
969
+ .where(ORDER_FIELDS.PAYMENT_STATUS, "==", "paid")
970
+ .where(ORDER_FIELDS.ORDER_DATE, ">=", from)
971
+ .where(ORDER_FIELDS.ORDER_DATE, "<=", to)
972
+ .limit(2000)
973
+ .get(),
974
+ ]);
975
+ const byId = new Map();
976
+ for (const d of invoiced.docs) {
977
+ byId.set(d.id, { id: d.id, ...d.data() });
978
+ }
979
+ for (const d of paidUnnumbered.docs) {
980
+ const data = { id: d.id, ...d.data() };
981
+ // Only the genuinely un-numbered ones — a paid, numbered order is
982
+ // already in the first set, and adding it again would double every one
983
+ // of its rows in the return.
984
+ if (!data.invoiceNumber && !byId.has(d.id))
985
+ byId.set(d.id, data);
986
+ }
987
+ return [...byId.values()];
988
+ }
881
989
  async getExpiredPaymentDeadlines() {
882
990
  const snap = await this.db
883
991
  .collection(this.collection)
@@ -1056,6 +1164,27 @@ class OrderRepository extends BaseRepository {
1056
1164
  * early. Withholding is recoverable — the next sweep picks it up;
1057
1165
  * releasing early is not, because the money has gone.
1058
1166
  */
1167
+ /*
1168
+ * 🛑 A BUYER'S OWN CONFIRMATION RELEASES THE PAYOUT IMMEDIATELY.
1169
+ *
1170
+ * The owner's rule: an order is CONFIRMED when it is delivered and
1171
+ * either the buyer says they received it, or 7 days pass. This filter
1172
+ * only ever implemented the second half — `deliveryDate <= cutoff` —
1173
+ * so a buyer who pressed "Mark complete" on day one still made their
1174
+ * seller wait out the full hold. The fast path existed and bought the
1175
+ * seller nothing.
1176
+ *
1177
+ * Safe in the direction that matters, and not by luck:
1178
+ * `markOrderCompleteAction` is the only writer of `completedAt`, and it
1179
+ * REFUSES while a return in any state, a posted refund or a buyer
1180
+ * dispute is live — the same precondition the `order-complete`
1181
+ * obligation carries. So honouring it here cannot release money while
1182
+ * an issue is open; the hold is skipped only because the one party the
1183
+ * hold protects has said they are satisfied.
1184
+ */
1185
+ const completedAt = toDateOrNull(o.data.completedAt);
1186
+ if (completedAt)
1187
+ return true;
1059
1188
  const anchor = toDateOrNull(o.data.deliveryDate) ?? toDateOrNull(o.data.updatedAt);
1060
1189
  if (!o.data.deliveryDate)
1061
1190
  missingAnchor++;
@@ -1162,6 +1291,22 @@ OrderRepository.SELLER_SIEVE_FIELDS = {
1162
1291
  orderDate: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
1163
1292
  createdAt: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
1164
1293
  contestable: { canFilter: true, canSort: false },
1294
+ /*
1295
+ * Lane segregation on the SELLER's order list — the same provenance axis
1296
+ * `/user/orders` has had.
1297
+ *
1298
+ * 🛑 It was absent, and absence here is SILENT: `sieve.ts` sets
1299
+ * `throwExceptions: false`, so an unallowlisted field does not raise —
1300
+ * `findField` returns undefined and the clause is simply dropped. A lane
1301
+ * chip emitting `orderType==auction` would therefore have returned the
1302
+ * seller's ENTIRE order list while looking exactly like a working filter
1303
+ * (Root Cause #62). Checked before wiring the chip, not after.
1304
+ *
1305
+ * 🛑 Never pushed as `orderType==standard`, for the same reason the admin
1306
+ * set above carries that note: orders written before the field existed have
1307
+ * no value to match. `/api/store/orders` refines that one case in memory.
1308
+ */
1309
+ orderType: { canFilter: true, canSort: false },
1165
1310
  // Fulfilment flags. Filterable so "which of my orders need gift wrap?" is a
1166
1311
  // query against the orders themselves — the whole point of recording the
1167
1312
  // add-on on the order rather than keeping a side list of who asked for what.
@@ -7,6 +7,7 @@ import type { BaseDocument } from "../../../_internal/shared/types/base-document
7
7
  import type { ListingType } from "../../products/types";
8
8
  import type { StatusChangeEntry } from "../../../_internal/shared/history/index";
9
9
  import type { ReturnReason } from "../../../_internal/shared/features/orders/return-reasons";
10
+ import type { DisputeKind } from "../../../_internal/shared/features/orders/dispute-reasons";
10
11
  import type { ReturnCustodyState } from "../../../_internal/shared/features/returns/custody";
11
12
  import type { ActualShipmentCost, SelectedShippingQuote, ShippingReconciliation } from "../../../_internal/shared/shipping/quote";
12
13
  import type { Encrypted, Plaintext } from "../../../security/encrypted";
@@ -352,6 +353,25 @@ export interface OrderRefundEvent {
352
353
  proofDocumentMimeType?: string;
353
354
  /** Required for Razorpay-override path ("failed auto-refund, settled manually"). */
354
355
  overrideReason?: string;
356
+ /**
357
+ * The credit note that documents this refund, e.g. `LIRCN/26-27/0001`
358
+ * (Phase 7, D12).
359
+ *
360
+ * 🛑 Per REFUND EVENT, not per order — an order can legitimately be refunded
361
+ * several times, and each reversal of tax needs its own numbered document
362
+ * referencing the original invoice. That is why the allocation claim for a
363
+ * credit note is keyed `${orderId}_${refundId}` while an invoice's is keyed
364
+ * on the order alone: keying a credit note on the order would refuse the
365
+ * second one.
366
+ *
367
+ * Only ever set on a refund that actually SETTLED. A `reserved` entry that
368
+ * failed is not a reversal of anything, and numbering it would leave a
369
+ * credit note in the series for money that never moved.
370
+ */
371
+ creditNoteNumber?: string;
372
+ creditNoteIssuedAt?: Date;
373
+ /** Private storage path — same no-public-URL rule as `invoiceStoragePath`. */
374
+ creditNoteStoragePath?: string;
355
375
  }
356
376
  /** One applied discount/coupon saved on the order for accounting and display */
357
377
  export interface AppliedOrderDiscount {
@@ -426,6 +446,21 @@ export interface OrderDocument extends BaseDocument {
426
446
  */
427
447
  acceptedAt?: Date;
428
448
  shippingDate?: Date;
449
+ /**
450
+ * The COURIER's estimated delivery date, captured at dispatch.
451
+ *
452
+ * 🛑 Not to be confused with `OrderSourcePreOrder.expectedDeliveryDate`,
453
+ * which is when a pre-ordered product RELEASES — a different concept that
454
+ * this field was mistaken for while planning the dispute gates.
455
+ *
456
+ * Optional, and will stay mostly empty for now: nothing platform-books yet
457
+ * (`createShipment` has zero callers), and a self-shipping seller types a
458
+ * tracking number with no estimate attached. `resolveDisputeEtd` therefore
459
+ * falls back to `shippingDate + DISPUTE_TRANSIT_WINDOW_DAYS` — without that
460
+ * fallback the `not_reached` dispute gate would be unreachable for every
461
+ * parcel currently shipped.
462
+ */
463
+ estimatedDeliveryDate?: Date;
429
464
  deliveryDate?: Date;
430
465
  /**
431
466
  * When the order was marked COMPLETE — the fact that awards points and
@@ -481,6 +516,37 @@ export interface OrderDocument extends BaseDocument {
481
516
  * figure the order may since have changed.
482
517
  */
483
518
  pointsAwarded?: number;
519
+ /**
520
+ * Points SPENT on this order as part-payment, in points (1 point = ₹1).
521
+ *
522
+ * 🛑 Distinct from `pointsAwarded` directly above, which is what the buyer
523
+ * EARNED. Both can be non-zero on one order — a buyer can pay partly with
524
+ * points and still earn on the goods value — and conflating them would make
525
+ * the ledger unexplainable in exactly the place it matters most.
526
+ *
527
+ * Written at order creation from `resolvePointsTender`, never from a request
528
+ * body. The ledger's `redemption` entry is keyed on this order, so this field
529
+ * and that entry must agree; the order carries it so a payout deduction and a
530
+ * refund can both answer "how much of this was points" without re-deriving a
531
+ * percentage of a total that may since have changed.
532
+ */
533
+ pointsApplied?: number;
534
+ /**
535
+ * What the buyer actually pays: `totalPrice − pointsApplied`.
536
+ *
537
+ * ## 🛑 THE FIGURE THE GATEWAY CAPTURES, and the one the guard compares
538
+ *
539
+ * `totalPrice` stays the full value of the order — that is what the invoice
540
+ * declares and what GST is computed against, because points are a DISCOUNT
541
+ * and not a reduction in the value of the supply (decision 5). `amountDue` is
542
+ * the cash leg.
543
+ *
544
+ * Absent means "nothing was tendered", so every order written before this
545
+ * existed reads correctly as `amountDue === totalPrice`. 🛑 Readers must use
546
+ * `amountDue ?? totalPrice` rather than defaulting to 0 — treating absence as
547
+ * zero would say every historical order was fully paid in points.
548
+ */
549
+ amountDue?: number;
484
550
  cancellationDate?: Date;
485
551
  cancellationReason?: string;
486
552
  /**
@@ -668,6 +734,25 @@ export interface OrderDocument extends BaseDocument {
668
734
  igst: number;
669
735
  gstAmount: number;
670
736
  }>;
737
+ /**
738
+ * The buyer's state — the PLACE OF SUPPLY (Phase 7).
739
+ *
740
+ * 🛑 This is the input the CGST/SGST-vs-IGST decision was made FROM, and it
741
+ * was not recorded anywhere. `isIntraStateSupply(buyerState, storeState)`
742
+ * runs at checkout and only its *result* survived, in the shape of which tax
743
+ * fields got populated — so an order could not state why it split the way it
744
+ * did, and GSTR-1 has a mandatory place-of-supply column that nothing could
745
+ * fill.
746
+ *
747
+ * `shippingAddress` is a flat STRING on this document and contains the state
748
+ * somewhere inside it; parsing a state back out of free text to file a tax
749
+ * return is not a thing to do. The value is in scope at order creation, so
750
+ * it is written there.
751
+ *
752
+ * Absent on orders placed before this, which the export reports rather than
753
+ * silently blanks.
754
+ */
755
+ placeOfSupply?: string;
671
756
  /**
672
757
  * OUR GST registration as it stood at the moment of supply — LetItRip is the
673
758
  * seller of record and invoices under its own GSTIN.
@@ -689,6 +774,58 @@ export interface OrderDocument extends BaseDocument {
689
774
  supplierGstin?: string;
690
775
  supplierLegalName?: string;
691
776
  supplierAddress?: string;
777
+ /**
778
+ * The invoice number from the gapless FY series, e.g. `LIR/26-27/0001`.
779
+ *
780
+ * 🛑 Allocated on `paymentStatus → paid`, never at order creation — a number
781
+ * burned on an abandoned COD order is a gap nobody can explain. Written once
782
+ * and never rewritten: a GST invoice is issued once, and an adjustment is a
783
+ * CREDIT NOTE, not a re-issue.
784
+ *
785
+ * Absence means "not yet invoiced", which is the correct state for a pending
786
+ * order and for every order placed before this phase.
787
+ */
788
+ invoiceNumber?: string;
789
+ /** When the number was allocated — the invoice's own date. */
790
+ invoiceIssuedAt?: Date;
791
+ /**
792
+ * Where the rendered PDF lives.
793
+ *
794
+ * 🛑 **`private/invoices/{orderId}/{uuid}.pdf`, with NO `mediaAssets` row and
795
+ * a RANDOM object name.** An invoice carries the buyer's full name, address
796
+ * and phone. `/api/media/[...slug]` applies **no authentication** over a
797
+ * bucket whose storage rule is `allow read: if true`, and the slug resolves
798
+ * through `mediaAssets` — so no row means no public URL exists at all.
799
+ *
800
+ * The random name is deliberately AGAINST the content-derived SEO-slug
801
+ * convention used everywhere else in this codebase, because guessable is the
802
+ * one property this must not have. `generateInvoiceFilename` is
803
+ * content-derived and is therefore the wrong tool here. Same reasoning as the
804
+ * digital-content decision (D103).
805
+ */
806
+ invoiceStoragePath?: string;
807
+ /**
808
+ * The arrangement's fees, SNAPSHOTTED at checkout.
809
+ *
810
+ * 🛑 Snapshotted, not re-read. A coordinator commits against a quoted total;
811
+ * if the rates were read again at settle a seller could raise them
812
+ * afterwards and the coordinator would absorb it. The precedent is exact —
813
+ * the refund path reads `finalSale` from this document rather than the live
814
+ * product, "because re-reading the live product would let a seller flip the
815
+ * switch afterwards" — and D61 generalises it to every agreement with a gap
816
+ * between terms-agreed and money-taken.
817
+ *
818
+ * Absent on every ordinary single-destination order, which is almost all of
819
+ * them.
820
+ */
821
+ sharedPurchase?: {
822
+ /** Distinct destinations, including the first. Always ≥ 2 when present. */
823
+ destinationCount: number;
824
+ /** Flat per-arrangement fee, as charged. */
825
+ handlingFee: number;
826
+ /** Extra shipping for destinations beyond the first, as charged. */
827
+ extraShippingFee: number;
828
+ };
692
829
  emiEnabled?: boolean;
693
830
  /** Number of monthly installments the buyer chose (2–6). */
694
831
  emiTenureMonths?: number;
@@ -776,6 +913,38 @@ export interface OrderDocument extends BaseDocument {
776
913
  * requires evidence and approval before anything is paid.
777
914
  */
778
915
  platformBookedShipment?: boolean;
916
+ /**
917
+ * We tried to book a courier for this order and could not.
918
+ *
919
+ * ## 🛑 ITS WHOLE PURPOSE IS TO STOP AN AUTO-CANCELLATION
920
+ *
921
+ * `seller-dispatch` is an `onDue: "act"` obligation on a 2-business-day
922
+ * clock. Without this field, an order sitting on OUR empty courier wallet is
923
+ * auto-cancelled and the buyer refunded — with the seller recorded as having
924
+ * failed to dispatch. The buyer paid, the seller did nothing wrong, and the
925
+ * platform's own funding state caused the cancellation.
926
+ *
927
+ * So every `onDue: "act"` policy anchored at or after dispatch gates its
928
+ * `precondition` on `!hasOpenCourierBlock(order)`. The obligation engine
929
+ * re-evaluates `precondition` at fire time and closes `superseded` when it
930
+ * returns false, so the clock pauses and resumes with no new mechanism.
931
+ *
932
+ * Absent means no block — which is every order ever written, so this needs
933
+ * no migration and changes nothing until something sets it.
934
+ *
935
+ * `reason` is the operator's word, not the seller's: the seller is told only
936
+ * that we could not book a courier right now, because our balance is our
937
+ * problem and not theirs.
938
+ */
939
+ courierBlock?: {
940
+ reason: "insufficient_funds" | "provider_error" | "unserviceable";
941
+ at: Date;
942
+ lastAttemptAt?: Date;
943
+ /** Attempts so far, so the digest can distinguish stuck from transient. */
944
+ attempts: number;
945
+ /** Upstream detail, for the admin surface only. Never shown to a seller. */
946
+ detail?: string;
947
+ };
779
948
  /**
780
949
  * The self-shipped reimbursement workflow: evidence in, approved amount out.
781
950
  *
@@ -970,11 +1139,37 @@ export interface OrderDocument extends BaseDocument {
970
1139
  /** True when `paymentReviewAutoApprove` approved this order after 2 hours of no manual admin review. */
971
1140
  autoApproved?: boolean;
972
1141
  autoApprovedAt?: Date;
973
- /** Buyer/seller/admin dispute raised against an auto-approved order — surfaces to the admin queue, does not itself reverse payment/order status. */
1142
+ /**
1143
+ * A buyer/seller/admin dispute. Surfaces to the admin queue; does not itself
1144
+ * reverse payment or order status.
1145
+ *
1146
+ * 🛑 This used to say "against an auto-approved order", and that was the
1147
+ * whole scope: `raiseOrderDisputeAction` refused anything else, telling the
1148
+ * caller to use the return/refund flow. Disputes are now GENERAL — any
1149
+ * delivered-or-late order can be disputed, gated by `disputeKind`.
1150
+ */
974
1151
  disputeRaised?: boolean;
975
1152
  disputeRaisedBy?: string;
976
1153
  disputeRaisedAt?: Date;
1154
+ /**
1155
+ * The reason's LABEL, snapshotted at the moment the dispute was raised.
1156
+ *
1157
+ * 🛑 A snapshot, not a lookup, and that is the point: `disputeReasons` rows
1158
+ * can be retired or reworded by staff, and neither may rewrite what a buyer
1159
+ * said months ago. `disputeReasonId` below is the join key for analytics;
1160
+ * THIS is the record.
1161
+ *
1162
+ * Still a plain `string` and still the field every existing surface renders,
1163
+ * so pre-2026-10-22 disputes (free text typed by the buyer) keep displaying
1164
+ * unchanged rather than being orphaned beside a new field.
1165
+ */
977
1166
  disputeReason?: string;
1167
+ /** Which gate this dispute passed. Absent on pre-2026-10-22 disputes. */
1168
+ disputeKind?: DisputeKind;
1169
+ /** `disputeReasons/{id}`. The join key for "what are buyers reporting". */
1170
+ disputeReasonId?: string;
1171
+ /** The buyer's own words. Free text, never parsed, never gated on. */
1172
+ disputeDetail?: string;
978
1173
  disputeStatus?: "open" | "resolved";
979
1174
  /**
980
1175
  * Same-shape payment record regardless of how the buyer paid — manual