@mohasinac/appkit 4.61.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 (281) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +19 -3
  3. package/dist/_internal/client/features/maintenance/views/AnalysisRunnerView.js +1 -1
  4. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +1 -1
  5. package/dist/_internal/server/features/checkout/actions.d.ts +80 -0
  6. package/dist/_internal/server/features/checkout/actions.js +542 -26
  7. package/dist/_internal/server/features/invoicing/allocate.d.ts +63 -0
  8. package/dist/_internal/server/features/invoicing/allocate.js +162 -0
  9. package/dist/_internal/server/features/invoicing/credit-note.d.ts +57 -0
  10. package/dist/_internal/server/features/invoicing/credit-note.js +126 -0
  11. package/dist/_internal/server/features/invoicing/gstr1.d.ts +74 -0
  12. package/dist/_internal/server/features/invoicing/gstr1.js +251 -0
  13. package/dist/_internal/server/features/invoicing/issue.d.ts +96 -0
  14. package/dist/_internal/server/features/invoicing/issue.js +202 -0
  15. package/dist/_internal/server/features/orders/actions.d.ts +8 -1
  16. package/dist/_internal/server/features/orders/actions.js +81 -8
  17. package/dist/_internal/server/features/orders/adapters.js +33 -0
  18. package/dist/_internal/server/features/orders/cancellation-refund.d.ts +15 -9
  19. package/dist/_internal/server/features/orders/cancellation-refund.js +15 -9
  20. package/dist/_internal/server/features/points/award.js +46 -4
  21. package/dist/_internal/server/features/refunds/actions.js +26 -0
  22. package/dist/_internal/server/features/shipping/courier-block.d.ts +69 -0
  23. package/dist/_internal/server/features/shipping/courier-block.js +239 -0
  24. package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
  25. package/dist/_internal/server/features/site-settings/adapters.js +15 -0
  26. package/dist/_internal/server/features/stores/adapters.d.ts +2 -2
  27. package/dist/_internal/server/features/stores/adapters.js +51 -0
  28. package/dist/_internal/server/functions/scheduled.js +38 -1
  29. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +9 -2
  30. package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +4 -0
  31. package/dist/_internal/server/jobs/core/dailyStatusDigest.js +19 -2
  32. package/dist/_internal/server/jobs/core/index.d.ts +2 -1
  33. package/dist/_internal/server/jobs/core/index.js +2 -1
  34. package/dist/_internal/server/jobs/core/onJobCreated.d.ts +30 -7
  35. package/dist/_internal/server/jobs/core/onJobCreated.js +78 -45
  36. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +7 -0
  37. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +62 -0
  38. package/dist/_internal/server/jobs/core/paymentReconcile.js +87 -97
  39. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +33 -0
  40. package/dist/_internal/server/jobs/core/runJobOnce.d.ts +48 -0
  41. package/dist/_internal/server/jobs/core/runJobOnce.js +195 -0
  42. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +10 -2
  43. package/dist/_internal/server/jobs/handlers/onOrderStatusChange.js +17 -0
  44. package/dist/_internal/server/notifications/channel-health.d.ts +26 -3
  45. package/dist/_internal/shared/actions/action-registry.js +177 -9
  46. package/dist/_internal/shared/checkout/coupon-discount.d.ts +43 -1
  47. package/dist/_internal/shared/checkout/coupon-discount.js +26 -1
  48. package/dist/_internal/shared/checkout/order-math.d.ts +145 -0
  49. package/dist/_internal/shared/checkout/order-math.js +173 -0
  50. package/dist/_internal/shared/checkout/refundable-base.d.ts +25 -0
  51. package/dist/_internal/shared/checkout/refundable-base.js +13 -1
  52. package/dist/_internal/shared/checkout/split.d.ts +87 -0
  53. package/dist/_internal/shared/checkout/split.js +100 -0
  54. package/dist/_internal/shared/content/disclaimers.d.ts +88 -0
  55. package/dist/_internal/shared/content/disclaimers.js +217 -0
  56. package/dist/_internal/shared/content/published-values.d.ts +88 -0
  57. package/dist/_internal/shared/content/published-values.js +198 -0
  58. package/dist/_internal/shared/features/admin/integrations.d.ts +109 -0
  59. package/dist/_internal/shared/features/admin/integrations.js +253 -0
  60. package/dist/_internal/shared/features/orders/dispute-reasons.d.ts +113 -0
  61. package/dist/_internal/shared/features/orders/dispute-reasons.js +181 -0
  62. package/dist/_internal/shared/features/orders/refund-copy.d.ts +1 -1
  63. package/dist/_internal/shared/features/orders/refund-copy.js +18 -1
  64. package/dist/_internal/shared/features/points/accepts.d.ts +36 -0
  65. package/dist/_internal/shared/features/points/accepts.js +43 -0
  66. package/dist/_internal/shared/features/shipping/shared-purchase.d.ts +91 -0
  67. package/dist/_internal/shared/features/shipping/shared-purchase.js +108 -0
  68. package/dist/_internal/shared/features/site-settings/types.d.ts +11 -0
  69. package/dist/_internal/shared/fees/calculator.d.ts +33 -1
  70. package/dist/_internal/shared/fees/calculator.js +32 -2
  71. package/dist/_internal/shared/media/limits.d.ts +10 -0
  72. package/dist/_internal/shared/media/limits.js +10 -0
  73. package/dist/_internal/shared/types/action-result.js +15 -1
  74. package/dist/client/api/surface-error.js +16 -2
  75. package/dist/client/api/useApiMutation.js +17 -1
  76. package/dist/client.d.ts +12 -1
  77. package/dist/client.js +31 -1
  78. package/dist/constants/api-endpoints.d.ts +45 -0
  79. package/dist/constants/api-endpoints.js +15 -0
  80. package/dist/constants/field-names.d.ts +18 -0
  81. package/dist/constants/field-names.js +28 -0
  82. package/dist/contracts/index.d.ts +1 -1
  83. package/dist/contracts/shipping.d.ts +45 -0
  84. package/dist/contracts/shipping.js +15 -0
  85. package/dist/core/server-action.js +19 -3
  86. package/dist/errors/error-mapping.js +26 -1
  87. package/dist/features/about/components/AboutView.js +1 -1
  88. package/dist/features/account/components/BuyerAuctionsGuideView.js +4 -1
  89. package/dist/features/account/components/UserOffersPanel.js +5 -3
  90. package/dist/features/account/components/UserOrdersView.js +19 -33
  91. package/dist/features/admin/components/AdminActionIndexView.js +100 -5
  92. package/dist/features/admin/components/AdminAddressClustersView.js +21 -2
  93. package/dist/features/admin/components/AdminBlogEditorView.js +8 -8
  94. package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
  95. package/dist/features/admin/components/AdminCarouselView.js +96 -4
  96. package/dist/features/admin/components/AdminCouponEditorView.js +17 -0
  97. package/dist/features/admin/components/AdminFulfillmentView.js +3 -2
  98. package/dist/features/admin/components/AdminInvoicingView.d.ts +2 -0
  99. package/dist/features/admin/components/AdminInvoicingView.js +65 -0
  100. package/dist/features/admin/components/AdminMediaView.js +36 -3
  101. package/dist/features/admin/components/AdminNavigationView.js +4 -1
  102. package/dist/features/admin/components/AdminOffersView.js +2 -1
  103. package/dist/features/admin/components/AdminOrderEditorView.d.ts +18 -1
  104. package/dist/features/admin/components/AdminOrderEditorView.js +11 -2
  105. package/dist/features/admin/components/AdminOrdersGuideView.js +23 -2
  106. package/dist/features/admin/components/AdminOrdersView.js +67 -5
  107. package/dist/features/admin/components/AdminPaymentClustersView.js +19 -2
  108. package/dist/features/admin/components/AdminProductEditorView.js +2 -2
  109. package/dist/features/admin/components/AdminSectionsView.d.ts +10 -0
  110. package/dist/features/admin/components/AdminSectionsView.js +486 -224
  111. package/dist/features/admin/components/OwnerQuickActions.js +24 -2
  112. package/dist/features/admin/components/ReferenceRecordEditor.js +1 -1
  113. package/dist/features/admin/constants/filter-tabs.d.ts +6 -0
  114. package/dist/features/admin/constants/filter-tabs.js +6 -0
  115. package/dist/features/admin/constants/site-settings-tabs.d.ts +1 -1
  116. package/dist/features/admin/hooks/useAdminListingData.js +16 -0
  117. package/dist/features/admin/hooks/useOptimisticRowFlag.d.ts +33 -0
  118. package/dist/features/admin/hooks/useOptimisticRowFlag.js +87 -0
  119. package/dist/features/admin/repository/notification.repository.d.ts +15 -0
  120. package/dist/features/admin/repository/notification.repository.js +32 -0
  121. package/dist/features/admin/schemas/action-index-form.d.ts +34 -0
  122. package/dist/features/admin/schemas/action-index-form.js +115 -0
  123. package/dist/features/admin/schemas/admin-user-form.d.ts +2 -2
  124. package/dist/features/admin/schemas/firestore.d.ts +30 -0
  125. package/dist/features/admin/schemas/firestore.js +5 -0
  126. package/dist/features/audit-log/schemas/firestore.d.ts +30 -0
  127. package/dist/features/audit-log/schemas/firestore.js +15 -0
  128. package/dist/features/auth/components/RegisterForm.js +2 -1
  129. package/dist/features/auth/permissions/constants.js +13 -0
  130. package/dist/features/cart/components/CartPriceBreakdown.d.ts +13 -0
  131. package/dist/features/cart/components/CartPriceBreakdown.js +6 -2
  132. package/dist/features/cart/components/PointsTenderPicker.d.ts +61 -0
  133. package/dist/features/cart/components/PointsTenderPicker.js +15 -0
  134. package/dist/features/cart/components/StoreAddonsPicker.js +2 -1
  135. package/dist/features/cart/components/index.d.ts +2 -0
  136. package/dist/features/cart/components/index.js +1 -0
  137. package/dist/features/cart/repository/cart.repository.d.ts +19 -0
  138. package/dist/features/cart/repository/cart.repository.js +27 -0
  139. package/dist/features/cart/schemas/firestore.d.ts +92 -0
  140. package/dist/features/catalogue/components/CatalogueItemEditorView.js +3 -3
  141. package/dist/features/contact/email.d.ts +27 -10
  142. package/dist/features/contact/email.js +20 -3
  143. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +38 -0
  144. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +80 -0
  145. package/dist/features/dispute-reasons/schemas/firestore.d.ts +64 -0
  146. package/dist/features/dispute-reasons/schemas/firestore.js +40 -0
  147. package/dist/features/events/components/AdminEventEditorView.js +8 -6
  148. package/dist/features/faq/components/FAQAccordion.d.ts +13 -1
  149. package/dist/features/faq/components/FAQAccordion.js +29 -4
  150. package/dist/features/invoicing/schemas/firestore.d.ts +112 -0
  151. package/dist/features/invoicing/schemas/firestore.js +45 -0
  152. package/dist/features/invoicing/utils/financial-year.d.ts +43 -0
  153. package/dist/features/invoicing/utils/financial-year.js +53 -0
  154. package/dist/features/jobs/actions/enqueue-job.js +18 -1
  155. package/dist/features/jobs/repository/jobs.repository.d.ts +62 -1
  156. package/dist/features/jobs/repository/jobs.repository.js +84 -1
  157. package/dist/features/jobs/schemas/firestore.d.ts +80 -0
  158. package/dist/features/jobs/schemas/firestore.js +37 -0
  159. package/dist/features/layout/hooks/useFormBottomActions.js +20 -1
  160. package/dist/features/lottery/schemas/zod.d.ts +6 -6
  161. package/dist/features/media/MediaImage.js +22 -1
  162. package/dist/features/media/upload/ImageUpload.js +1 -1
  163. package/dist/features/obligations/constants/policies.js +24 -1
  164. package/dist/features/orders/components/EmiInstallmentsPanel.js +25 -7
  165. package/dist/features/orders/components/OrderPaymentSummary.js +10 -1
  166. package/dist/features/orders/components/OrderProvenanceCard.d.ts +8 -0
  167. package/dist/features/orders/components/OrderProvenanceCard.js +185 -0
  168. package/dist/features/orders/components/RaiseDisputeForm.d.ts +14 -0
  169. package/dist/features/orders/components/RaiseDisputeForm.js +111 -0
  170. package/dist/features/orders/components/RefundRequestView.js +22 -4
  171. package/dist/features/orders/components/index.d.ts +4 -0
  172. package/dist/features/orders/components/index.js +2 -0
  173. package/dist/features/orders/constants/order-lane-tabs.d.ts +69 -0
  174. package/dist/features/orders/constants/order-lane-tabs.js +88 -0
  175. package/dist/features/orders/repository/orders.repository.d.ts +57 -0
  176. package/dist/features/orders/repository/orders.repository.js +145 -0
  177. package/dist/features/orders/schemas/firestore.d.ts +196 -1
  178. package/dist/features/orders/schemas/index.d.ts +60 -12
  179. package/dist/features/orders/schemas/index.js +12 -0
  180. package/dist/features/orders/types/index.d.ts +26 -0
  181. package/dist/features/orders/utils/courier-block.d.ts +25 -0
  182. package/dist/features/orders/utils/courier-block.js +23 -0
  183. package/dist/features/payment-cases/schemas/firestore.d.ts +26 -1
  184. package/dist/features/payment-cases/schemas/firestore.js +1 -0
  185. package/dist/features/products/components/GroupMemberPicker.js +4 -1
  186. package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -2
  187. package/dist/features/products/components/PrizeDrawEntryActions.js +2 -1
  188. package/dist/features/products/components/ProductDetailActions.js +2 -1
  189. package/dist/features/products/components/ProductDetailPageView.d.ts +17 -1
  190. package/dist/features/products/components/ProductDetailPageView.js +2 -2
  191. package/dist/features/products/schemas/product-features.validators.d.ts +10 -10
  192. package/dist/features/promotions/actions/coupon-stacking.d.ts +30 -3
  193. package/dist/features/promotions/actions/coupon-stacking.js +21 -2
  194. package/dist/features/promotions/components/CouponCard.js +13 -0
  195. package/dist/features/promotions/constants/coupon-help.d.ts +2 -2
  196. package/dist/features/promotions/constants/coupon-help.js +9 -1
  197. package/dist/features/promotions/schemas/firestore.d.ts +24 -0
  198. package/dist/features/promotions/schemas/firestore.js +4 -0
  199. package/dist/features/promotions/types/index.d.ts +19 -1
  200. package/dist/features/scams/components/ScamProfileView.js +2 -2
  201. package/dist/features/scams/components/ScamRegistryView.js +2 -2
  202. package/dist/features/search/action-index/tab-entries.js +1 -1
  203. package/dist/features/seller/actions/seller-actions.js +33 -3
  204. package/dist/features/seller/components/FulfillmentView.js +3 -2
  205. package/dist/features/seller/components/SellerAddressesView.js +32 -4
  206. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +62 -3
  207. package/dist/features/seller/components/SellerBundlesView.js +53 -14
  208. package/dist/features/seller/components/SellerGroupedListingsView.js +23 -3
  209. package/dist/features/seller/components/SellerListingTypeView.js +56 -10
  210. package/dist/features/seller/components/SellerOrdersView.js +129 -27
  211. package/dist/features/seller/components/SellerPayoutMethodsView.js +33 -1
  212. package/dist/features/seller/components/SellerPayoutSettingsView.js +48 -9
  213. package/dist/features/seller/components/SellerProductsView.js +5 -1
  214. package/dist/features/seller/components/SellerShippingConfigsView.js +33 -1
  215. package/dist/features/seller/components/SellerShippingView.js +2 -1
  216. package/dist/features/seller/components/SellerStoreCategoriesView.js +33 -1
  217. package/dist/features/seller/components/SellerSupportView.js +69 -2
  218. package/dist/features/seller/hooks/useSellerListingData.js +25 -0
  219. package/dist/features/shell/SectionForm.js +20 -16
  220. package/dist/features/shell/build-sections.js +14 -1
  221. package/dist/features/shipments/components/AdminShipmentLotItemsView.js +54 -1
  222. package/dist/features/shipments/components/AdminShipmentProjectionsView.js +3 -2
  223. package/dist/features/shipments/schemas/validation.d.ts +4 -4
  224. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +43 -0
  225. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +90 -0
  226. package/dist/features/shipping-boxes/schemas/firestore.d.ts +82 -0
  227. package/dist/features/shipping-boxes/schemas/firestore.js +53 -0
  228. package/dist/features/sticker-trace/components/StickerCommitPanel.js +2 -1
  229. package/dist/features/sticker-trace/components/StickerPrintPanel.js +2 -1
  230. package/dist/features/sticker-trace/components/StickerRetryPanel.js +24 -4
  231. package/dist/features/sticker-trace/components/StickerTraceStudio.js +2 -1
  232. package/dist/features/store-extensions/schemas/report-create-form.d.ts +2 -2
  233. package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +2 -2
  234. package/dist/features/stores/components/StoreOrdersGuideView.js +6 -1
  235. package/dist/features/stores/schemas/firestore.d.ts +71 -0
  236. package/dist/features/stores/types/index.d.ts +19 -0
  237. package/dist/features/support/schemas/index.d.ts +6 -6
  238. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +3 -2
  239. package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
  240. package/dist/index.d.ts +11 -2
  241. package/dist/index.js +17 -2
  242. package/dist/next/routing/route-map.d.ts +24 -0
  243. package/dist/next/routing/route-map.js +19 -0
  244. package/dist/providers/shipping-shiprocket/index.d.ts +27 -1
  245. package/dist/providers/shipping-shiprocket/index.js +84 -1
  246. package/dist/schemas/registry.d.ts +51 -22
  247. package/dist/schemas/sieve.d.ts +5 -5
  248. package/dist/server-entry.d.ts +2 -2
  249. package/dist/server-entry.js +8 -1
  250. package/dist/server.d.ts +11 -2
  251. package/dist/server.js +40 -2
  252. package/dist/styles.css +64 -80
  253. package/dist/tailwind-utilities.css +1 -1
  254. package/dist/tokens/themes/default-dark.js +1 -0
  255. package/dist/tokens/themes/default-light.js +1 -0
  256. package/dist/tokens/themes/required.d.ts +1 -1
  257. package/dist/tokens/themes/required.js +1 -0
  258. package/dist/tokens/tokens.css +15 -0
  259. package/dist/ui/components/Checkbox.js +10 -2
  260. package/dist/ui/components/DateInput.js +8 -1
  261. package/dist/ui/components/Disclaimer.d.ts +19 -0
  262. package/dist/ui/components/Disclaimer.js +74 -0
  263. package/dist/ui/components/Input.js +22 -2
  264. package/dist/ui/components/PaginatedSelect.d.ts +8 -19
  265. package/dist/ui/components/PaginatedSelect.js +43 -9
  266. package/dist/ui/components/Radio.js +8 -1
  267. package/dist/ui/components/Select.js +10 -2
  268. package/dist/ui/components/TagInput.d.ts +8 -1
  269. package/dist/ui/components/TagInput.js +4 -3
  270. package/dist/ui/components/Textarea.js +10 -2
  271. package/dist/ui/components/tabs-overflow.js +64 -11
  272. package/dist/ui/forms/FieldCheckbox.js +1 -1
  273. package/dist/ui/forms/FormErrorList.d.ts +0 -16
  274. package/dist/ui/forms/FormErrorList.js +34 -1
  275. package/dist/ui/forms/apply-zod-issues.d.ts +19 -1
  276. package/dist/ui/forms/apply-zod-issues.js +23 -1
  277. package/dist/ui/index.d.ts +2 -0
  278. package/dist/ui/index.js +6 -0
  279. package/dist/ui/rich-text/RichText.style.css +48 -79
  280. package/package.json +1 -1
  281. package/scripts/seed-cli.mjs +1831 -1822
@@ -0,0 +1,185 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * OrderProvenanceCard — renders `OrderDocument.sourceContext`, the record of
5
+ * HOW the buyer earned the right to buy at this price.
6
+ *
7
+ * ## Why this component had to be written
8
+ *
9
+ * `sourceContext` is a six-variant discriminated union, assembled at a real
10
+ * cost — an auction pays one `findByProductSorted`, an offer one `findById` —
11
+ * written on every order by both checkout paths, mapped through to the client
12
+ * type by `orderDocumentToOrder` (adapters.ts:359) … and, until this file,
13
+ * rendered in **zero** `.tsx` files anywhere in the repo.
14
+ *
15
+ * That is Root Cause #52/#57: the data was already present and complete, and
16
+ * no surface was ever wired to present it. The buyer could not see what they
17
+ * had agreed, the seller could not see what they had accepted, and support
18
+ * could not see either.
19
+ *
20
+ * ## What it deliberately does NOT do
21
+ *
22
+ * **`standard` renders nothing.** Its only field is the list price, which the
23
+ * order total already shows — a card reading "Standard purchase" on every
24
+ * ordinary order is noise that would teach people to ignore the card on the
25
+ * orders where it matters. It is still an explicit `case`, so the switch stays
26
+ * exhaustive.
27
+ *
28
+ * **A saving is shown only where it is real.** Not every variant records a
29
+ * displaced price: `auction-buy-now` has `listPrice`, `offer-accepted` has
30
+ * `listPriceAtOffer`, and `auction-won` has only `startingBid` — which is a
31
+ * floor, not an asking price, so presenting the gap as a discount would be a
32
+ * fabrication. Where a displaced price exists it is shown only when it is
33
+ * strictly greater than what was paid.
34
+ *
35
+ * **Nothing is fetched.** It is handed the context the page already loaded; a
36
+ * component that fetched would fire one request per opened drawer per portal.
37
+ */
38
+ import { Badge, Card, DescriptionField, Heading, Row, Stack, Text } from "../../../ui/index.js";
39
+ import { formatCurrency } from "../../../utils/number.formatter.js";
40
+ import { formatDate } from "../../../utils/date.formatter.js";
41
+ /**
42
+ * A date on a `sourceContext` survives a Firestore round trip as a Timestamp,
43
+ * an ISO string or a Date depending on which path loaded it, so normalise
44
+ * rather than assuming. An unparseable value renders an em-dash — never
45
+ * `new Date()`, which would invent a timestamp for a record that has none
46
+ * (the rule `OrderStatusTimeline` already follows).
47
+ */
48
+ function dateText(value) {
49
+ if (value === null || value === undefined)
50
+ return "—";
51
+ const raw = typeof value === "object" && value !== null && "toDate" in value
52
+ ? value.toDate()
53
+ : value;
54
+ const d = raw instanceof Date ? raw : new Date(raw);
55
+ return Number.isFinite(d.getTime()) ? formatDate(d) : "—";
56
+ }
57
+ function plural(n, one, many) {
58
+ return `${n} ${n === 1 ? one : many}`;
59
+ }
60
+ function contentFor(ctx, currency) {
61
+ const money = (n) => formatCurrency(n, currency);
62
+ switch (ctx.path) {
63
+ case "standard":
64
+ // Deliberately silent — see the header. Explicit so the switch is exhaustive.
65
+ return null;
66
+ case "auction-won": {
67
+ const rows = [
68
+ { label: "Winning bid", value: money(ctx.winningBidAmount) },
69
+ { label: "Bids in the auction", value: String(ctx.bidCount) },
70
+ { label: "Starting bid", value: money(ctx.startingBid) },
71
+ ];
72
+ if (ctx.runnerUpAmount !== undefined) {
73
+ rows.push({ label: "Runner-up bid", value: money(ctx.runnerUpAmount) });
74
+ }
75
+ if (ctx.reservePrice !== undefined) {
76
+ rows.push({
77
+ label: "Reserve",
78
+ value: `${money(ctx.reservePrice)} — ${ctx.reserveMet ? "met" : "not met"}`,
79
+ });
80
+ }
81
+ rows.push({ label: "Auction ended", value: dateText(ctx.auctionEndedAt) });
82
+ return {
83
+ title: "Won at auction",
84
+ tone: "success",
85
+ summary: ctx.bidCount > 1
86
+ ? `Highest of ${plural(ctx.bidCount, "bid", "bids")}.`
87
+ : "The only bid placed.",
88
+ rows,
89
+ };
90
+ }
91
+ case "auction-buy-now": {
92
+ const rows = [
93
+ { label: "Buy Now price", value: money(ctx.buyNowPrice) },
94
+ { label: "List price", value: money(ctx.listPrice) },
95
+ {
96
+ label: "Standing bid beaten",
97
+ value: ctx.standingBidAtBuyout > 0 ? money(ctx.standingBidAtBuyout) : "none",
98
+ },
99
+ { label: "Bids at buyout", value: String(ctx.bidCount) },
100
+ { label: "Bought", value: dateText(ctx.boughtAt) },
101
+ ];
102
+ return {
103
+ title: "Bought now, ending the auction",
104
+ tone: "info",
105
+ /*
106
+ * A buyout IS a real bid (the schema's own note: recording bidCount 0
107
+ * would bake a falsehood into an immutable record), so the summary
108
+ * must not claim nobody had bid when somebody had.
109
+ */
110
+ summary: ctx.bidCount > 0
111
+ ? `Taken at the Buy Now price while ${plural(ctx.bidCount, "bid was", "bids were")} live.`
112
+ : "Taken at the Buy Now price before any bids were placed.",
113
+ rows,
114
+ saved: ctx.listPrice > ctx.buyNowPrice ? ctx.listPrice - ctx.buyNowPrice : undefined,
115
+ };
116
+ }
117
+ case "offer-accepted": {
118
+ const rows = [
119
+ { label: "Agreed price", value: money(ctx.offeredAmount) },
120
+ { label: "Asking price when offered", value: money(ctx.listPriceAtOffer) },
121
+ ];
122
+ if (ctx.counterAmount !== undefined) {
123
+ rows.push({ label: "Final counter", value: money(ctx.counterAmount) });
124
+ }
125
+ rows.push({ label: "Negotiation", value: plural(ctx.counterRounds, "round", "rounds") }, { label: "Accepted", value: dateText(ctx.acceptedAt) });
126
+ return {
127
+ title: "Price agreed by offer",
128
+ tone: "primary",
129
+ summary: ctx.counterRounds > 1
130
+ ? `Agreed after ${plural(ctx.counterRounds, "round", "rounds")} of counter-offers.`
131
+ : "Accepted outright, with no counter-offer.",
132
+ rows,
133
+ saved: ctx.listPriceAtOffer > ctx.offeredAmount
134
+ ? ctx.listPriceAtOffer - ctx.offeredAmount
135
+ : undefined,
136
+ };
137
+ }
138
+ case "pre-order":
139
+ return {
140
+ title: "Pre-order",
141
+ tone: "warning",
142
+ summary: ctx.balanceDue > 0
143
+ ? "A deposit was taken at checkout; the balance is due before dispatch."
144
+ : "Paid in full at checkout.",
145
+ rows: [
146
+ { label: "Deposit paid", value: money(ctx.depositAmount) },
147
+ { label: "Balance due", value: money(ctx.balanceDue) },
148
+ { label: "Expected delivery", value: dateText(ctx.expectedDeliveryDate) },
149
+ ],
150
+ };
151
+ case "prize-draw":
152
+ return {
153
+ title: "Prize draw entry",
154
+ tone: "info",
155
+ summary: ctx.revealMode === "instant"
156
+ ? "The result was revealed immediately."
157
+ : "The result is revealed at the scheduled draw.",
158
+ rows: [
159
+ { label: "Entries bought", value: plural(ctx.entryCount, "entry", "entries") },
160
+ { label: "Price per entry", value: money(ctx.pricePerEntry) },
161
+ { label: "Reveal", value: ctx.revealMode === "instant" ? "Instant" : "Scheduled" },
162
+ ],
163
+ };
164
+ default: {
165
+ /*
166
+ * Exhaustive. A seventh variant — the shared-purchase path S5a still
167
+ * needs — will not compile rather than silently rendering nothing, which
168
+ * is the failure mode of `default: return null` and is precisely how this
169
+ * union came to have six populated variants and no reader at all.
170
+ */
171
+ const unreachable = ctx;
172
+ return unreachable;
173
+ }
174
+ }
175
+ }
176
+ export function OrderProvenanceCard({ sourceContext, currency, className, }) {
177
+ // Every order written before W2 has no context at all — absence is normal
178
+ // and is not an error state to announce.
179
+ if (!sourceContext)
180
+ return null;
181
+ const content = contentFor(sourceContext, currency);
182
+ if (!content)
183
+ return null;
184
+ return (_jsx(Card, { variant: "outlined", padding: "md", className: className, children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Heading, { level: 3, size: "sm", children: content.title }), content.saved !== undefined ? (_jsx(Badge, { variant: "success", children: `Saved ${formatCurrency(content.saved, currency)}` })) : null] }), content.summary ? (_jsx(Text, { size: "sm", color: "muted", children: content.summary })) : null, _jsx(Row, { as: "dl", wrap: true, gap: "md", children: content.rows.map((row) => (_jsx(DescriptionField, { label: row.label, value: row.value }, row.label))) })] }) }));
185
+ }
@@ -0,0 +1,14 @@
1
+ import { type DisputeGateOrder, type DisputeKind } from "../../../_internal/shared/features/orders/dispute-reasons";
2
+ export interface RaiseDisputeFormProps {
3
+ /** The gate reads four dates off this; an `OrderDocument` satisfies it. */
4
+ order: DisputeGateOrder;
5
+ /** `GET ?kind=` / `POST` — supplied by the consumer so appkit holds no route. */
6
+ reasonsEndpoint: string;
7
+ onSubmit: (input: {
8
+ kind: DisputeKind;
9
+ reasonId: string;
10
+ detail?: string;
11
+ }) => Promise<void>;
12
+ onDone?: () => void;
13
+ }
14
+ export declare function RaiseDisputeForm({ order, reasonsEndpoint, onSubmit, onDone, }: RaiseDisputeFormProps): import("react").JSX.Element;
@@ -0,0 +1,111 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ /**
4
+ * The buyer's dispute form: pick a kind, pick a reason, say what happened.
5
+ *
6
+ * ## Why the kind comes first
7
+ *
8
+ * The two kinds have MUTUALLY EXCLUSIVE gates — an item problem needs the
9
+ * order to have arrived, a non-delivery needs it not to have. So the kind is
10
+ * the first choice, it scopes the reason list, and it decides which gate the
11
+ * server will apply. Offering one merged list of reasons would mean a buyer
12
+ * could pick "it never arrived" on a delivered order and have the server
13
+ * refuse them, which reads as the form being broken rather than as the wrong
14
+ * choice.
15
+ *
16
+ * ## The gate is checked HERE for the affordance and AGAIN on the server
17
+ *
18
+ * 🛑 This copy decides what to OFFER. The server decides what to ACCEPT, and
19
+ * it is the authority — `raiseOrderDisputeAction` re-runs `checkDisputeGate`
20
+ * on the real order. Showing a kind the server would refuse is the dead-end
21
+ * shape (CLAUDE.md's rule for `OrderCompletionPanel`: never offer a button the
22
+ * server will refuse), and trusting this copy alone would be a client-side
23
+ * security gate.
24
+ */
25
+ import { useCallback, useMemo, useState } from "react";
26
+ import { DISPUTE_KIND, DISPUTE_KIND_LABEL, checkDisputeGate, disputeNotReachedFilableFrom, } from "../../../_internal/shared/features/orders/dispute-reasons.js";
27
+ import { Alert, Button, Div, FieldTextarea, Form, Stack, Text, useToast } from "../../../ui/index.js";
28
+ import { PaginatedSelect } from "../../../ui/components/PaginatedSelect.js";
29
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
30
+ import { normalizeError } from "../../../errors/normalize.js";
31
+ export function RaiseDisputeForm({ order, reasonsEndpoint, onSubmit, onDone, }) {
32
+ const { showToast } = useToast();
33
+ const [kind, setKind] = useState(null);
34
+ const [reasonId, setReasonId] = useState(null);
35
+ const [detail, setDetail] = useState("");
36
+ const [isPending, setIsPending] = useState(false);
37
+ /*
38
+ * Which kinds this order can actually support, with the server's own gate.
39
+ * A kind that would be refused is shown DISABLED with the reason rather than
40
+ * hidden: "why can't I report this?" is a question the form should answer,
41
+ * and a silently missing option looks like a bug.
42
+ */
43
+ const kindStates = useMemo(() => DISPUTE_KIND.map((k) => ({
44
+ kind: k,
45
+ label: DISPUTE_KIND_LABEL[k],
46
+ gate: checkDisputeGate(k, order),
47
+ })), [order]);
48
+ const filableFrom = useMemo(() => disputeNotReachedFilableFrom(order), [order]);
49
+ /*
50
+ * 🛑 Keyed on `kind` so switching kind refetches. `PaginatedSelect` caches
51
+ * per `loadOptions` identity, so a stable callback would keep showing the
52
+ * previous kind's reasons — which is the one thing that must never happen
53
+ * here, because a mismatched reason is refused by the server.
54
+ */
55
+ const loadReasons = useCallback(async (query, page) => {
56
+ if (!kind)
57
+ return { items: [], hasMore: false };
58
+ const res = await fetch(`${reasonsEndpoint}?kind=${encodeURIComponent(kind)}`);
59
+ if (!res.ok) {
60
+ /*
61
+ * Surfaced, not swallowed into an empty list. An empty picker is
62
+ * indistinguishable from "there are no reasons", and the buyer would
63
+ * sit on a required field with nothing in it (Root Cause #59).
64
+ */
65
+ throw new Error("Could not load the list of reasons. Please try again.");
66
+ }
67
+ const json = (await res.json());
68
+ const rows = json.data?.items ?? [];
69
+ const q = query.trim().toLowerCase();
70
+ const filtered = q ? rows.filter((r) => r.label.toLowerCase().includes(q)) : rows;
71
+ return {
72
+ // `items`, not `options` — checked against `AsyncPage` rather than
73
+ // guessed; the wrong key yields an empty picker with no error.
74
+ items: filtered.map((r) => ({ value: r.id, label: r.label })),
75
+ // The route caps at 50 and the list is short by design; there is no
76
+ // second page to promise. `page` is unused for the same reason.
77
+ hasMore: false,
78
+ };
79
+ }, [kind, reasonsEndpoint]);
80
+ const chooseKind = (next) => {
81
+ setKind(next);
82
+ // Clearing is required: a reason from the previous kind would be refused.
83
+ setReasonId(null);
84
+ };
85
+ const submit = async () => {
86
+ if (!kind || !reasonId)
87
+ return;
88
+ setIsPending(true);
89
+ try {
90
+ await onSubmit({ kind, reasonId, detail: detail.trim() || undefined });
91
+ showToast("Thanks — we've recorded it and will look into it.", "success");
92
+ onDone?.();
93
+ }
94
+ catch (err) {
95
+ // The server's message names the next action; show it verbatim rather
96
+ // than replacing it with a generic failure.
97
+ showToast(normalizeError(err).message, "error");
98
+ }
99
+ finally {
100
+ setIsPending(false);
101
+ }
102
+ };
103
+ return (_jsx(Form, { onSubmit: (e) => e.preventDefault(), children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "What's the problem?" }), kindStates.map((k) => (_jsxs(Div, { children: [_jsx(Button, { variant: kind === k.kind ? "primary" : "secondary", disabled: !k.gate.allowed, onClick: () => chooseKind(k.kind), children: k.label }), !k.gate.allowed ? (_jsx(Text, { size: "xs", color: "muted", children: k.gate.message })) : null] }, k.kind)))] }), kind ? (_jsxs(_Fragment, { children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Reason" }), _jsx(PaginatedSelect, { ariaLabel: "Reason for the dispute",
104
+ /*
105
+ * 🛑 `?? null`, not `?? ""`. PaginatedSelect renders its
106
+ * placeholder only on `value == null`; an empty string paints a
107
+ * BLANK trigger with no prompt, which reads as a broken control
108
+ * rather than an unmade choice.
109
+ */
110
+ value: reasonId ?? null, onChange: (v) => setReasonId(v), loadOptions: loadReasons, placeholder: "Pick the closest match\u2026" }), _jsx(FieldTextarea, { name: "detail", label: "What happened? (optional)", value: detail, onChange: setDetail, maxLength: 2000 }), kind === "not_reached" && filableFrom ? (_jsx(Alert, { variant: "info", children: `We'll chase the courier. If it turns up before we finish, let us know.` })) : null, _jsx(Button, { type: "submit", action: ACTIONS.SUPPORT["raise-dispute"], isLoading: isPending, disabled: !reasonId, onClick: submit, children: "Report this" })] })) : null] }) }));
111
+ }
@@ -28,11 +28,19 @@ import { FormShellContext, useFormShellState, FormErrorSummary, FieldTextarea, }
28
28
  import { SectionForm, useSectionFormNav } from "../../shell/index.js";
29
29
  import { z } from "zod";
30
30
  import { REFUND_COPY } from "../../../_internal/shared/features/orders/refund-copy.js";
31
+ import { resolvePublishedCopy, resolvePublishedValues, } from "../../../_internal/shared/content/published-values.js";
31
32
  import { RETURN_REASON_LABEL, selectableReturnReasons, } from "../../../_internal/shared/features/orders/return-reasons.js";
32
33
  import { formatCurrency } from "../../../utils/number.formatter.js";
33
34
  import { normalizeError } from "../../../errors/normalize.js";
34
35
  import { toUserMessage } from "../../../errors/error-display-map.js";
35
36
  const ACK_COUNT = REFUND_COPY.request.acknowledgments.length;
37
+ /*
38
+ * Phase 9 — code-derived values, resolved once. No settings argument: the only
39
+ * token in this copy is the refund timeline, which has no setting (see the 🛑
40
+ * on `refundBusinessDaysMin` in the registry), so passing settings would imply
41
+ * an editable number that does not exist.
42
+ */
43
+ const PUBLISHED_VALUES = resolvePublishedValues();
36
44
  export function RefundRequestView({ order, onSubmitRequest, isLoading = false, className = "", }) {
37
45
  /*
38
46
  * Does anything in this order carry the final-sale term? Read from the
@@ -71,7 +79,7 @@ export function RefundRequestView({ order, onSubmitRequest, isLoading = false, c
71
79
  label: "Why are you returning this?",
72
80
  required: true,
73
81
  fields: ["reasonCode", "reasonNote"],
74
- render: ({ values, onChange }) => (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { size: "sm", color: "muted", children: [REFUND_COPY.request.orderTotalLabel, " ", _jsx(Span, { weight: "bold", children: formatCurrency(order.total, order.currency ?? "INR") })] }), hasFinalSaleLine && (_jsx(Alert, { variant: "warning", title: REFUND_COPY.request.finalSaleNoticeHeading, children: "You can\u2019t return it for a change of mind, but you can still claim if it never arrived, arrived damaged, was the wrong item, wasn\u2019t as described, or is counterfeit. Only those reasons are listed below." })), _jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "sm", weight: "semibold", children: [REFUND_COPY.request.reasonSelectLabel, " ", _jsx(Span, { "aria-hidden": true, children: "*" })] }), _jsx(PaginatedSelect, { value: values.reasonCode === "" ? null : values.reasonCode, onChange: (v) => onChange({ reasonCode: v ?? "" }), options: allowedReasons.map((r) => ({
82
+ render: ({ values, onChange, errors }) => (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { size: "sm", color: "muted", children: [REFUND_COPY.request.orderTotalLabel, " ", _jsx(Span, { weight: "bold", children: formatCurrency(order.total, order.currency ?? "INR") })] }), hasFinalSaleLine && (_jsx(Alert, { variant: "warning", title: REFUND_COPY.request.finalSaleNoticeHeading, children: "You can\u2019t return it for a change of mind, but you can still claim if it never arrived, arrived damaged, was the wrong item, wasn\u2019t as described, or is counterfeit. Only those reasons are listed below." })), _jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "sm", weight: "semibold", children: [REFUND_COPY.request.reasonSelectLabel, " ", _jsx(Span, { "aria-hidden": true, children: "*" })] }), _jsx(PaginatedSelect, { name: "reasonCode", error: errors.reasonCode, value: values.reasonCode === "" ? null : values.reasonCode, onChange: (v) => onChange({ reasonCode: v ?? "" }), options: allowedReasons.map((r) => ({
75
83
  value: r,
76
84
  label: RETURN_REASON_LABEL[r],
77
85
  })), placeholder: REFUND_COPY.request.reasonSelectPlaceholder, ariaLabel: REFUND_COPY.request.reasonSelectLabel })] }), _jsx(FieldTextarea, { name: "reasonNote", label: REFUND_COPY.request.reasonNoteLabel, value: values.reasonNote, onChange: (v) => onChange({ reasonNote: v }), rows: 3, maxLength: 500, showCharCount: true })] })),
@@ -81,9 +89,19 @@ export function RefundRequestView({ order, onSubmitRequest, isLoading = false, c
81
89
  label: REFUND_COPY.request.acknowledgeHeading,
82
90
  required: true,
83
91
  fields: ["acknowledged"],
84
- render: ({ values, onChange }) => (_jsx(Stack, { gap: "sm", children: REFUND_COPY.request.acknowledgments.map((text, i) => (_jsx(Checkbox, { checked: values.acknowledged[i] ?? false, onChange: () => onChange({
85
- acknowledged: values.acknowledged.map((v, idx) => idx === i ? !v : v),
86
- }), label: _jsx(Text, { size: "sm", className: "leading-snug", children: text }), "aria-label": `Acknowledgment ${i + 1}` }, i))) })),
92
+ render: ({ values, onChange, errors }) => (_jsxs(Stack, { gap: "sm", children: [REFUND_COPY.request.acknowledgments.map((text, i) => (_jsx(Checkbox, { checked: values.acknowledged[i] ?? false, onChange: () => onChange({
93
+ acknowledged: values.acknowledged.map((v, idx) => idx === i ? !v : v),
94
+ }),
95
+ /*
96
+ * 🛑 Phase 9 — resolved, because this is an ACKNOWLEDGEMENT
97
+ * the buyer ticks. `REFUND_COPY`'s refund-timeline line now
98
+ * carries `{{refundBusinessDaysMin}}`/`Max` so it has one
99
+ * owner instead of being the third of three contradicting
100
+ * statements — and an unresolved token inside something a
101
+ * buyer is agreeing to is the strongest reason the unknown-key
102
+ * rule leaves `{{…}}` VISIBLE rather than blanking it.
103
+ */
104
+ label: _jsx(Text, { size: "sm", className: "leading-snug", children: resolvePublishedCopy(text, PUBLISHED_VALUES) }), "aria-label": `Acknowledgment ${i + 1}`, "aria-invalid": errors.acknowledged ? "true" : undefined }, i))), errors.acknowledged && (_jsx(Text, { size: "sm", variant: "error", role: "alert", children: errors.acknowledged }))] })),
87
105
  },
88
106
  ], [order.total, order.currency, hasFinalSaleLine, allowedReasons]);
89
107
  const nav = useSectionFormNav(sections, draft);
@@ -6,6 +6,10 @@ export { OrderSiblingPayments } from "./OrderSiblingPayments";
6
6
  export type { OrderSiblingPaymentsProps } from "./OrderSiblingPayments";
7
7
  export { OrderPaymentSummary } from "./OrderPaymentSummary";
8
8
  export type { OrderPaymentSummaryProps } from "./OrderPaymentSummary";
9
+ export { OrderProvenanceCard } from "./OrderProvenanceCard";
10
+ export type { OrderProvenanceCardProps } from "./OrderProvenanceCard";
11
+ export { RaiseDisputeForm } from "./RaiseDisputeForm";
12
+ export type { RaiseDisputeFormProps } from "./RaiseDisputeForm";
9
13
  export { OrderStatusTimeline } from "./OrderStatusTimeline";
10
14
  export type { OrderStatusTimelineProps } from "./OrderStatusTimeline";
11
15
  export { RefundHistoryTable } from "./RefundHistoryTable";
@@ -3,6 +3,8 @@ export { OrderAddonBadges, } from "./OrderAddonBadges.js";
3
3
  export { OrderFilters, ORDER_FILTER_KEYS, ORDER_ADMIN_SORT_OPTIONS, ORDER_SELLER_SORT_OPTIONS, ORDER_USER_SORT_OPTIONS, getOrderFilterKeys, getOrderSortOptions, } from "./OrderFilters.js";
4
4
  export { OrderSiblingPayments } from "./OrderSiblingPayments.js";
5
5
  export { OrderPaymentSummary } from "./OrderPaymentSummary.js";
6
+ export { OrderProvenanceCard } from "./OrderProvenanceCard.js";
7
+ export { RaiseDisputeForm } from "./RaiseDisputeForm.js";
6
8
  export { OrderStatusTimeline } from "./OrderStatusTimeline.js";
7
9
  export { RefundHistoryTable } from "./RefundHistoryTable.js";
8
10
  export { RefundRequestView } from "./RefundRequestView.js";
@@ -0,0 +1,69 @@
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
+ import type { OrderType } from "../utils/order-splitter";
36
+ /**
37
+ * The "All" tab's id. An empty string rather than a word, so it can never
38
+ * collide with a real `orderType` value and so `state.orderType ? … : null`
39
+ * reads as "no filter" at every call site.
40
+ */
41
+ export declare const ORDER_LANE_ALL: "";
42
+ /**
43
+ * 🛑 A `Record<OrderType, string>`, deliberately. Omitting a lane does not
44
+ * compile, which is the whole point — the buyer's list shipped with four tabs
45
+ * for five lanes, so pre-order and prize-draw orders were reachable only by
46
+ * landing on "All" and scrolling.
47
+ */
48
+ export declare const ORDER_LANE_LABELS: Record<OrderType, string>;
49
+ /**
50
+ * The stored `orderType` values a lane filter may name, for route-side
51
+ * validation.
52
+ *
53
+ * Derived from `ORDER_LANE_LABELS`, so it cannot drift from the tabs. Both
54
+ * `/api/user/orders` and `/api/store/orders` had — or would have needed — their
55
+ * own hand-written copy of these five strings; this is the eleventh
56
+ * enumeration of one union in this codebase collapsed into the union itself
57
+ * (Root Cause #61).
58
+ *
59
+ * 🛑 Validating against it is load-bearing, not hygiene: an unrecognised value
60
+ * interpolated into a Sieve filter string is a caller-controlled field name
61
+ * reaching the query builder.
62
+ */
63
+ export declare const VALID_ORDER_TYPES: ReadonlySet<string>;
64
+ export interface OrderLaneTab {
65
+ id: string;
66
+ label: string;
67
+ }
68
+ /** `All` first, then every lane in `LANE_ORDER`. */
69
+ export declare const ORDER_LANE_TABS: readonly OrderLaneTab[];
@@ -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;