@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,63 @@
1
+ /**
2
+ * Allocating a number from a gapless series (Phase 7, D12).
3
+ *
4
+ * ## The whole design in one paragraph
5
+ *
6
+ * One transaction does three things: read the (series, FY) counter, `.create()`
7
+ * an allocation claim keyed on the subject, and write `lastNumber + 1`. Any one
8
+ * of the three failing rolls back all of them, so there is no state where a
9
+ * number was consumed without being recorded, and no state where a subject
10
+ * holds two numbers.
11
+ *
12
+ * ## 🛑 Why the claim is INSIDE the transaction
13
+ *
14
+ * The obvious shape is "check whether this order already has a number, then
15
+ * allocate". That is a read-then-write race: two concurrent triggers both read
16
+ * "no number", both allocate, and the order ends up with two — one of which is
17
+ * a permanent gap in a series an auditor reads.
18
+ *
19
+ * `.create()` inside the transaction makes it impossible rather than unlikely.
20
+ * A duplicate attempt fails the transaction, and the caller treats that as
21
+ * "already allocated" and reads the existing number back. This is the fourth
22
+ * use of that primitive in this codebase (refund claims, the gateway-event
23
+ * inbox, the gateway-order claim), and the one with the least recoverable
24
+ * failure mode.
25
+ *
26
+ * ## 🛑 Allocate on `paymentStatus → paid`, never at order creation
27
+ *
28
+ * COD and manual orders are created `pending`. Numbering at creation burns a
29
+ * number on every abandoned order, and *gapless* means those gaps are
30
+ * unexplainable — "that one was abandoned" is not an answer an auditor accepts
31
+ * for a missing invoice number.
32
+ */
33
+ import { type InvoiceSeriesKind } from "../../../../features/invoicing/schemas/firestore";
34
+ export interface AllocateSeriesNumberInput {
35
+ kind: InvoiceSeriesKind;
36
+ /**
37
+ * What the number is FOR, and the idempotency key.
38
+ *
39
+ * An invoice passes the order id. A credit note passes
40
+ * `${orderId}_${refundId}`, because an order may have several.
41
+ */
42
+ subjectId: string;
43
+ orderId: string;
44
+ /** Defaults to now. Injectable so a backfill can issue into a past FY. */
45
+ at?: Date;
46
+ }
47
+ export interface AllocateSeriesNumberResult {
48
+ number: string;
49
+ fy: string;
50
+ sequence: number;
51
+ /** True when this call did the allocating; false when it found an existing one. */
52
+ allocated: boolean;
53
+ }
54
+ /**
55
+ * Allocate (or return the already-allocated) number for one subject.
56
+ *
57
+ * 🛑 Idempotent and NOT best-effort. It throws rather than returning a
58
+ * sentinel, because every caller's correct response to "we could not number
59
+ * this" is to leave the order un-invoiced and retry — a caller that treated
60
+ * failure as "no number needed" would produce a paid order with no invoice,
61
+ * silently.
62
+ */
63
+ export declare function allocateSeriesNumber(input: AllocateSeriesNumberInput): Promise<AllocateSeriesNumberResult>;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Allocating a number from a gapless series (Phase 7, D12).
3
+ *
4
+ * ## The whole design in one paragraph
5
+ *
6
+ * One transaction does three things: read the (series, FY) counter, `.create()`
7
+ * an allocation claim keyed on the subject, and write `lastNumber + 1`. Any one
8
+ * of the three failing rolls back all of them, so there is no state where a
9
+ * number was consumed without being recorded, and no state where a subject
10
+ * holds two numbers.
11
+ *
12
+ * ## 🛑 Why the claim is INSIDE the transaction
13
+ *
14
+ * The obvious shape is "check whether this order already has a number, then
15
+ * allocate". That is a read-then-write race: two concurrent triggers both read
16
+ * "no number", both allocate, and the order ends up with two — one of which is
17
+ * a permanent gap in a series an auditor reads.
18
+ *
19
+ * `.create()` inside the transaction makes it impossible rather than unlikely.
20
+ * A duplicate attempt fails the transaction, and the caller treats that as
21
+ * "already allocated" and reads the existing number back. This is the fourth
22
+ * use of that primitive in this codebase (refund claims, the gateway-event
23
+ * inbox, the gateway-order claim), and the one with the least recoverable
24
+ * failure mode.
25
+ *
26
+ * ## 🛑 Allocate on `paymentStatus → paid`, never at order creation
27
+ *
28
+ * COD and manual orders are created `pending`. Numbering at creation burns a
29
+ * number on every abandoned order, and *gapless* means those gaps are
30
+ * unexplainable — "that one was abandoned" is not an answer an auditor accepts
31
+ * for a missing invoice number.
32
+ */
33
+ import { getAdminDb } from "../../../../providers/db-firebase/index.js";
34
+ import { serverLogger } from "../../../../monitoring/index.js";
35
+ import { normalizeError } from "../../../../errors/normalize.js";
36
+ import { INVOICE_ALLOCATION_COLLECTION, INVOICE_COUNTER_COLLECTION, } from "../../../../features/invoicing/schemas/firestore.js";
37
+ import { CREDIT_NOTE_PREFIX, INVOICE_PREFIX, financialYearLabel, formatSeriesNumber, } from "../../../../features/invoicing/utils/financial-year.js";
38
+ function prefixFor(kind) {
39
+ return kind === "credit_note" ? CREDIT_NOTE_PREFIX : INVOICE_PREFIX;
40
+ }
41
+ /**
42
+ * Allocate (or return the already-allocated) number for one subject.
43
+ *
44
+ * 🛑 Idempotent and NOT best-effort. It throws rather than returning a
45
+ * sentinel, because every caller's correct response to "we could not number
46
+ * this" is to leave the order un-invoiced and retry — a caller that treated
47
+ * failure as "no number needed" would produce a paid order with no invoice,
48
+ * silently.
49
+ */
50
+ export async function allocateSeriesNumber(input) {
51
+ const db = getAdminDb();
52
+ const at = input.at ?? new Date();
53
+ const fy = financialYearLabel(at);
54
+ const counterRef = db
55
+ .collection(INVOICE_COUNTER_COLLECTION)
56
+ .doc(`${input.kind}_${fy}`);
57
+ const claimRef = db.collection(INVOICE_ALLOCATION_COLLECTION).doc(input.subjectId);
58
+ try {
59
+ return await db.runTransaction(async (tx) => {
60
+ /*
61
+ * 🛑 Both reads BEFORE any write — a Firestore transaction requires it,
62
+ * and the claim read is what lets an already-allocated subject return
63
+ * its existing number instead of failing.
64
+ */
65
+ const [claimSnap, counterSnap] = await Promise.all([
66
+ tx.get(claimRef),
67
+ tx.get(counterRef),
68
+ ]);
69
+ if (claimSnap.exists) {
70
+ const existing = claimSnap.data();
71
+ return {
72
+ number: existing.number,
73
+ fy: existing.fy,
74
+ sequence: existing.sequence,
75
+ allocated: false,
76
+ };
77
+ }
78
+ const last = counterSnap.exists
79
+ ? (counterSnap.data().lastNumber ?? 0)
80
+ : 0;
81
+ const sequence = last + 1;
82
+ const number = formatSeriesNumber(prefixFor(input.kind), fy, sequence);
83
+ /*
84
+ * `set(..., { merge: true })` on the counter rather than `update`,
85
+ * because the first allocation of a financial year creates it — and
86
+ * `update` on a missing document throws, which would make 1 April the
87
+ * one day of the year invoicing breaks.
88
+ */
89
+ tx.set(counterRef, {
90
+ kind: input.kind,
91
+ fy,
92
+ lastNumber: sequence,
93
+ lastIssuedAt: at,
94
+ updatedAt: at,
95
+ ...(counterSnap.exists ? {} : { createdAt: at }),
96
+ }, { merge: true });
97
+ /*
98
+ * 🛑 `create`, not `set`. Within a transaction both are atomic against
99
+ * the counter, but `create` additionally refuses if the claim appeared
100
+ * between our read and our write — which is exactly the window a
101
+ * concurrent trigger occupies.
102
+ */
103
+ tx.create(claimRef, {
104
+ kind: input.kind,
105
+ subjectId: input.subjectId,
106
+ orderId: input.orderId,
107
+ number,
108
+ fy,
109
+ sequence,
110
+ issuedAt: at,
111
+ createdAt: at,
112
+ updatedAt: at,
113
+ });
114
+ return { number, fy, sequence, allocated: true };
115
+ });
116
+ }
117
+ catch (err) {
118
+ const normalized = normalizeError(err);
119
+ /*
120
+ * A lost race: the other side created the claim first. Read it back rather
121
+ * than failing — both callers want the same answer, and whoever wrote it
122
+ * wrote the number this subject now permanently has.
123
+ */
124
+ /*
125
+ * Logged, not swallowed. `.catch(() => null)` alone made a Firestore
126
+ * failure indistinguishable from "the claim genuinely is not there" — and
127
+ * the two take opposite branches below: one must fall through to a fresh
128
+ * allocation, the other must NOT, because re-allocating over a claim that
129
+ * exists is how an invoice number gets issued twice. Root Cause #59's
130
+ * shape, in the one place in this codebase where a silent degrade would
131
+ * duplicate a filed document number.
132
+ */
133
+ const after = await claimRef.get().catch((readErr) => {
134
+ const e = normalizeError(readErr);
135
+ serverLogger.error("[invoicing] could not re-read the allocation claim after a race", {
136
+ collection: INVOICE_ALLOCATION_COLLECTION,
137
+ subjectId: input.subjectId,
138
+ error: e.message,
139
+ });
140
+ return null;
141
+ });
142
+ if (after?.exists) {
143
+ const existing = after.data();
144
+ serverLogger.info("[invoicing] allocation race resolved to the existing number", {
145
+ subjectId: input.subjectId,
146
+ number: existing.number,
147
+ });
148
+ return {
149
+ number: existing.number,
150
+ fy: existing.fy,
151
+ sequence: existing.sequence,
152
+ allocated: false,
153
+ };
154
+ }
155
+ serverLogger.error("[invoicing] allocation failed", {
156
+ subjectId: input.subjectId,
157
+ kind: input.kind,
158
+ error: normalized.message,
159
+ });
160
+ throw err;
161
+ }
162
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Credit notes — the document that reverses tax on a refund (Phase 7, 7c).
3
+ *
4
+ * ## 🛑 A refund needs a credit note; it never needs a re-issued invoice
5
+ *
6
+ * A GST invoice is issued once. When money goes back, the reversal is its own
7
+ * numbered document referencing the original invoice — which is why there are
8
+ * two series rather than a revision suffix on one.
9
+ *
10
+ * ## 🛑 Numbered on SETTLEMENT, not on reservation
11
+ *
12
+ * `processRefundAction` is two-phase: it reserves an entry, calls the provider,
13
+ * then settles to `processed` or `failed`. A credit note numbered at
14
+ * reservation would leave a numbered reversal in the series for money that
15
+ * never moved, on every provider failure — and the number could not be
16
+ * withdrawn.
17
+ *
18
+ * So this is called from the SETTLE step, for `processed` only.
19
+ *
20
+ * ## One per refund event, keyed accordingly
21
+ *
22
+ * An order can be refunded several times. The allocation claim is therefore
23
+ * `${orderId}_${refundId}` — keying on the order alone would refuse the second
24
+ * credit note, which is the inverse of the invoice's rule and is why
25
+ * `allocateSeriesNumber` takes `subjectId` separately from `orderId`.
26
+ */
27
+ import type { OrderDocument, OrderRefundEvent } from "../../../../features/orders/schemas/firestore";
28
+ /** Same private prefix family as invoices — refused by the media proxy. */
29
+ export declare const CREDIT_NOTE_PREFIX_PATH = "private/credit-notes";
30
+ /**
31
+ * Renders a credit note. Injected for the same reason as `InvoiceRenderer`:
32
+ * `pdfkit` is a Functions dependency.
33
+ */
34
+ export type CreditNoteRenderer = (order: OrderDocument, refund: OrderRefundEvent, creditNoteNumber: string) => Promise<Buffer>;
35
+ export interface IssueCreditNoteResult {
36
+ creditNoteNumber: string;
37
+ creditNoteStoragePath?: string;
38
+ issued: boolean;
39
+ }
40
+ /**
41
+ * Number and (optionally) render the credit note for one settled refund.
42
+ *
43
+ * 🛑 **Best-effort by design, and that is the opposite of the invoice rule.**
44
+ * It is called from the refund settle path, where the money has ALREADY left
45
+ * the gateway. Throwing here would turn a successful refund into a failed
46
+ * action — the buyer has their money, our code reports an error, and an
47
+ * operator retries a refund that already happened. A missing credit note is a
48
+ * reporting gap recoverable by a backfill; a double refund is not.
49
+ *
50
+ * The invoice path throws for the mirror-image reason: nothing irreversible has
51
+ * happened yet there, so refusing to proceed is the safe direction.
52
+ *
53
+ * `render` is optional — passing none numbers the reversal without producing a
54
+ * document, which is what the GSTR-1 export actually needs (it reconciles
55
+ * numbers and amounts, not PDFs). The PDF is for the buyer.
56
+ */
57
+ export declare function issueCreditNoteForRefund(order: OrderDocument, refund: OrderRefundEvent, render?: CreditNoteRenderer): Promise<IssueCreditNoteResult | null>;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Credit notes — the document that reverses tax on a refund (Phase 7, 7c).
3
+ *
4
+ * ## 🛑 A refund needs a credit note; it never needs a re-issued invoice
5
+ *
6
+ * A GST invoice is issued once. When money goes back, the reversal is its own
7
+ * numbered document referencing the original invoice — which is why there are
8
+ * two series rather than a revision suffix on one.
9
+ *
10
+ * ## 🛑 Numbered on SETTLEMENT, not on reservation
11
+ *
12
+ * `processRefundAction` is two-phase: it reserves an entry, calls the provider,
13
+ * then settles to `processed` or `failed`. A credit note numbered at
14
+ * reservation would leave a numbered reversal in the series for money that
15
+ * never moved, on every provider failure — and the number could not be
16
+ * withdrawn.
17
+ *
18
+ * So this is called from the SETTLE step, for `processed` only.
19
+ *
20
+ * ## One per refund event, keyed accordingly
21
+ *
22
+ * An order can be refunded several times. The allocation claim is therefore
23
+ * `${orderId}_${refundId}` — keying on the order alone would refuse the second
24
+ * credit note, which is the inverse of the invoice's rule and is why
25
+ * `allocateSeriesNumber` takes `subjectId` separately from `orderId`.
26
+ */
27
+ import { randomUUID } from "node:crypto";
28
+ import { getAdminStorage } from "../../../../providers/db-firebase/index.js";
29
+ import { orderRepository } from "../../../../features/orders/repository/orders.repository.js";
30
+ import { serverLogger } from "../../../../monitoring/index.js";
31
+ import { normalizeError } from "../../../../errors/normalize.js";
32
+ import { allocateSeriesNumber } from "./allocate.js";
33
+ /** Same private prefix family as invoices — refused by the media proxy. */
34
+ export const CREDIT_NOTE_PREFIX_PATH = "private/credit-notes";
35
+ /**
36
+ * Number and (optionally) render the credit note for one settled refund.
37
+ *
38
+ * 🛑 **Best-effort by design, and that is the opposite of the invoice rule.**
39
+ * It is called from the refund settle path, where the money has ALREADY left
40
+ * the gateway. Throwing here would turn a successful refund into a failed
41
+ * action — the buyer has their money, our code reports an error, and an
42
+ * operator retries a refund that already happened. A missing credit note is a
43
+ * reporting gap recoverable by a backfill; a double refund is not.
44
+ *
45
+ * The invoice path throws for the mirror-image reason: nothing irreversible has
46
+ * happened yet there, so refusing to proceed is the safe direction.
47
+ *
48
+ * `render` is optional — passing none numbers the reversal without producing a
49
+ * document, which is what the GSTR-1 export actually needs (it reconciles
50
+ * numbers and amounts, not PDFs). The PDF is for the buyer.
51
+ */
52
+ export async function issueCreditNoteForRefund(order, refund, render) {
53
+ if (refund.creditNoteNumber) {
54
+ return {
55
+ creditNoteNumber: refund.creditNoteNumber,
56
+ creditNoteStoragePath: refund.creditNoteStoragePath,
57
+ issued: false,
58
+ };
59
+ }
60
+ /*
61
+ * 🛑 Only a refund that actually moved money gets a number. `status`
62
+ * undefined means "committed" to every reader (rows written before the
63
+ * two-phase lifecycle existed carry none), so the test admits those and
64
+ * refuses only an explicit `reserved` or `failed`.
65
+ */
66
+ if (refund.status === "reserved" || refund.status === "failed") {
67
+ return null;
68
+ }
69
+ try {
70
+ const issuedAt = new Date();
71
+ const allocation = await allocateSeriesNumber({
72
+ kind: "credit_note",
73
+ subjectId: `${order.id}_${refund.refundId}`,
74
+ orderId: order.id,
75
+ at: issuedAt,
76
+ });
77
+ let storagePath;
78
+ if (render) {
79
+ storagePath = `${CREDIT_NOTE_PREFIX_PATH}/${order.id}/${randomUUID()}.pdf`;
80
+ const buffer = await render(order, refund, allocation.number);
81
+ await getAdminStorage()
82
+ .bucket()
83
+ .file(storagePath)
84
+ .save(buffer, {
85
+ contentType: "application/pdf",
86
+ metadata: { cacheControl: "private, no-store" },
87
+ resumable: false,
88
+ });
89
+ }
90
+ /*
91
+ * 🛑 Written through the repository's own refund-event mutator, NOT by
92
+ * read-modify-writing `refunds[]`. That array is appended by a transaction
93
+ * precisely because a read-then-`[...existing, event]` write LOSES an
94
+ * event under concurrency (D6), and stamping a credit-note number the
95
+ * naive way would reintroduce exactly that defect on the same array.
96
+ */
97
+ await orderRepository.setRefundCreditNote(order.id, refund.refundId, {
98
+ creditNoteNumber: allocation.number,
99
+ creditNoteIssuedAt: issuedAt,
100
+ ...(storagePath ? { creditNoteStoragePath: storagePath } : {}),
101
+ });
102
+ serverLogger.info("[invoicing] credit note issued", {
103
+ orderId: order.id,
104
+ refundId: refund.refundId,
105
+ creditNoteNumber: allocation.number,
106
+ });
107
+ return {
108
+ creditNoteNumber: allocation.number,
109
+ creditNoteStoragePath: storagePath,
110
+ issued: allocation.allocated,
111
+ };
112
+ }
113
+ catch (err) {
114
+ /*
115
+ * Reported loudly and swallowed — see the 🛑 on this function. A refund
116
+ * that succeeded must not be reported as failed because its paperwork
117
+ * could not be numbered.
118
+ */
119
+ serverLogger.error("[invoicing] credit note FAILED — refund stands, reporting gap", {
120
+ orderId: order.id,
121
+ refundId: refund.refundId,
122
+ error: normalizeError(err).message,
123
+ });
124
+ return null;
125
+ }
126
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * GSTR-1 export (Phase 7d) — the INPUT to a human filing, never a filing.
3
+ *
4
+ * ## 🛑 What this is and is not
5
+ *
6
+ * It is a CSV an accountant opens, checks and uses. It is not a return, it is
7
+ * not filed, and nothing here talks to the GST portal — there is no webhook
8
+ * facility in existence and no GSP contract. Labelling it otherwise would be
9
+ * the most consequential lie in this codebase.
10
+ *
11
+ * ## 🛑 Credit notes are not optional
12
+ *
13
+ * A return listing invoices and no credit notes **does not reconcile**: the
14
+ * output tax declared would be the tax on every sale including the ones that
15
+ * were reversed. So both series are exported, the credit notes as negative
16
+ * amounts, and the file's own total is the net.
17
+ *
18
+ * That is also why the two series exist in the first place — a reversal is its
19
+ * own numbered document, so it has its own row here.
20
+ *
21
+ * ## The rate split comes from `gstByRate`
22
+ *
23
+ * Already on the order (`firestore.ts`), already per rate, already recorded at
24
+ * the moment of supply. Nothing is recomputed from today's tax codes: an order
25
+ * supplied under a rate that has since changed must appear at the rate it was
26
+ * charged, and re-deriving it would silently restate history — the same defect
27
+ * the PDF renderer's own 🛑 records about reading live site settings.
28
+ */
29
+ export interface Gstr1Row {
30
+ /** `LIR/26-27/0001` or `LIRCN/26-27/0001`. */
31
+ documentNumber: string;
32
+ documentType: "invoice" | "credit_note";
33
+ documentDate: string;
34
+ orderId: string;
35
+ /** The buyer's state, which decides CGST+SGST vs IGST. */
36
+ placeOfSupply: string;
37
+ gstRate: number;
38
+ taxableValue: number;
39
+ cgst: number;
40
+ sgst: number;
41
+ igst: number;
42
+ total: number;
43
+ }
44
+ export interface Gstr1Export {
45
+ rows: Gstr1Row[];
46
+ /** Net of credit notes — what the period actually owes. */
47
+ totals: {
48
+ taxableValue: number;
49
+ cgst: number;
50
+ sgst: number;
51
+ igst: number;
52
+ total: number;
53
+ };
54
+ /**
55
+ * Orders in the window that are PAID but carry no invoice number.
56
+ *
57
+ * 🛑 Reported, never silently excluded. A paid order missing from a tax
58
+ * return is the one error this export must not hide, and "the row simply
59
+ * wasn't there" is indistinguishable from "there was nothing to report" in a
60
+ * CSV. Root Cause #84's lesson applied to an accounting artefact: a count
61
+ * that goes quiet is as likely to have stopped looking as to be satisfied.
62
+ */
63
+ unnumberedPaidOrders: string[];
64
+ }
65
+ /**
66
+ * Build the export for a date window.
67
+ *
68
+ * 🛑 Windowed on the INVOICE date, not the order date. A COD order placed on
69
+ * 30 March and paid on 2 April is invoiced in the new financial year, and that
70
+ * is the period its tax belongs to — numbering happens at payment for exactly
71
+ * this reason.
72
+ */
73
+ export declare function buildGstr1Export(from: Date, to: Date): Promise<Gstr1Export>;
74
+ export declare function gstr1ToCsv(data: Gstr1Export): string;