@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,251 @@
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
+ import { orderRepository } from "../../../../features/orders/repository/orders.repository.js";
30
+ import { roundRupees } from "../../../../utils/number.formatter.js";
31
+ import { serverLogger } from "../../../../monitoring/index.js";
32
+ /** One row per (document, rate) slice — which is what a GSTR-1 line is. */
33
+ function rowsForOrder(order) {
34
+ /*
35
+ * 🛑 `placeOfSupply` ONLY — never parsed out of `shippingAddress`, which is
36
+ * a flat string on this document. Deriving a state from free text to file a
37
+ * tax return is not a thing to do, and an order placed before the field
38
+ * existed is reported as blank rather than guessed at.
39
+ */
40
+ const placeOfSupply = order.placeOfSupply ?? "";
41
+ const date = order.invoiceIssuedAt ?? order.orderDate ?? order.createdAt;
42
+ const dateStr = date instanceof Date ? date.toISOString().slice(0, 10) : "";
43
+ const byRate = order.gstByRate ?? [];
44
+ const invoiceRows = order.invoiceNumber
45
+ ? byRate.map((slice) => ({
46
+ documentNumber: order.invoiceNumber,
47
+ documentType: "invoice",
48
+ documentDate: dateStr,
49
+ orderId: order.id,
50
+ placeOfSupply,
51
+ gstRate: slice.gstRate,
52
+ taxableValue: roundRupees(slice.taxableAmount),
53
+ cgst: roundRupees(slice.cgst),
54
+ sgst: roundRupees(slice.sgst),
55
+ igst: roundRupees(slice.igst),
56
+ total: roundRupees(slice.taxableAmount + slice.cgst + slice.sgst + slice.igst),
57
+ }))
58
+ : [];
59
+ /*
60
+ * ── Credit notes, as NEGATIVE rows ────────────────────────────────────────
61
+ *
62
+ * 🛑 The tax reversed is apportioned across the SAME rate slices the invoice
63
+ * carried, pro-rata on the refunded amount. A refund is a rupee figure with
64
+ * no rate attached, and a multi-rate order's reversal has to be split
65
+ * somehow — pro-rata on the invoice's own slices is the only split that
66
+ * reconciles back to the invoice it adjusts.
67
+ *
68
+ * 🛑 Only SETTLED refunds appear, and only ones that were numbered. A
69
+ * `reserved` entry is money that has not moved; a numbered reversal for it
70
+ * would overstate the period's credit notes.
71
+ */
72
+ const invoiceTotal = byRate.reduce((s, r) => s + r.taxableAmount, 0);
73
+ const creditRows = [];
74
+ for (const refund of order.refunds ?? []) {
75
+ if (!refund.creditNoteNumber)
76
+ continue;
77
+ if (refund.status === "reserved" || refund.status === "failed")
78
+ continue;
79
+ const refundDate = refund.creditNoteIssuedAt ?? refund.refundedAt;
80
+ const refundDateStr = refundDate instanceof Date ? refundDate.toISOString().slice(0, 10) : dateStr;
81
+ // No rate information at all — reported as one untaxed row rather than
82
+ // dropped, so the CSV's total still matches the money that moved.
83
+ if (invoiceTotal <= 0 || byRate.length === 0) {
84
+ creditRows.push({
85
+ documentNumber: refund.creditNoteNumber,
86
+ documentType: "credit_note",
87
+ documentDate: refundDateStr,
88
+ orderId: order.id,
89
+ placeOfSupply,
90
+ gstRate: 0,
91
+ taxableValue: -roundRupees(refund.amount),
92
+ cgst: 0,
93
+ sgst: 0,
94
+ igst: 0,
95
+ total: -roundRupees(refund.amount),
96
+ });
97
+ continue;
98
+ }
99
+ for (const slice of byRate) {
100
+ /*
101
+ * The slice's share of the refund, and then that amount's share of the
102
+ * slice — two ratios, because a refund is a gross rupee figure while a
103
+ * slice is split into taxable value and tax.
104
+ *
105
+ * 🛑 `sliceTotal > 0` guards a 0%-rated (exempt) slice, where the tax is
106
+ * legitimately zero and a naive `portion / sliceTotal` would be a
107
+ * division by zero producing `Infinity` — and `roundRupees(Infinity)` is
108
+ * `NaN`, which propagates into the file's TOTAL row and makes every
109
+ * figure in it unusable. One bad cell would be visible; a NaN total is
110
+ * the whole return.
111
+ */
112
+ const share = slice.taxableAmount / invoiceTotal;
113
+ const portion = refund.amount * share;
114
+ const sliceTotal = slice.taxableAmount + slice.cgst + slice.sgst + slice.igst;
115
+ const taxFraction = sliceTotal > 0 ? portion / sliceTotal : 0;
116
+ creditRows.push({
117
+ documentNumber: refund.creditNoteNumber,
118
+ documentType: "credit_note",
119
+ documentDate: refundDateStr,
120
+ orderId: order.id,
121
+ placeOfSupply,
122
+ gstRate: slice.gstRate,
123
+ taxableValue: -roundRupees(slice.taxableAmount * taxFraction),
124
+ cgst: -roundRupees(slice.cgst * taxFraction),
125
+ sgst: -roundRupees(slice.sgst * taxFraction),
126
+ igst: -roundRupees(slice.igst * taxFraction),
127
+ total: -roundRupees(portion),
128
+ });
129
+ }
130
+ }
131
+ return [...invoiceRows, ...creditRows];
132
+ }
133
+ /**
134
+ * Build the export for a date window.
135
+ *
136
+ * 🛑 Windowed on the INVOICE date, not the order date. A COD order placed on
137
+ * 30 March and paid on 2 April is invoiced in the new financial year, and that
138
+ * is the period its tax belongs to — numbering happens at payment for exactly
139
+ * this reason.
140
+ */
141
+ export async function buildGstr1Export(from, to) {
142
+ const orders = await orderRepository.listInvoicedBetween(from, to);
143
+ const rows = [];
144
+ const unnumberedPaidOrders = [];
145
+ for (const order of orders) {
146
+ if (order.paymentStatus === "paid" && !order.invoiceNumber) {
147
+ unnumberedPaidOrders.push(order.id);
148
+ continue;
149
+ }
150
+ rows.push(...rowsForOrder(order));
151
+ }
152
+ const totals = rows.reduce((acc, r) => ({
153
+ taxableValue: acc.taxableValue + r.taxableValue,
154
+ cgst: acc.cgst + r.cgst,
155
+ sgst: acc.sgst + r.sgst,
156
+ igst: acc.igst + r.igst,
157
+ total: acc.total + r.total,
158
+ }), { taxableValue: 0, cgst: 0, sgst: 0, igst: 0, total: 0 });
159
+ if (unnumberedPaidOrders.length > 0) {
160
+ serverLogger.warn("[gstr1] paid orders with no invoice number in window", {
161
+ count: unnumberedPaidOrders.length,
162
+ from: from.toISOString(),
163
+ to: to.toISOString(),
164
+ });
165
+ }
166
+ return {
167
+ rows,
168
+ totals: {
169
+ taxableValue: roundRupees(totals.taxableValue),
170
+ cgst: roundRupees(totals.cgst),
171
+ sgst: roundRupees(totals.sgst),
172
+ igst: roundRupees(totals.igst),
173
+ total: roundRupees(totals.total),
174
+ },
175
+ unnumberedPaidOrders,
176
+ };
177
+ }
178
+ const CSV_HEADER = [
179
+ "Document Number",
180
+ "Document Type",
181
+ "Document Date",
182
+ "Order ID",
183
+ "Place of Supply",
184
+ "GST Rate",
185
+ "Taxable Value",
186
+ "CGST",
187
+ "SGST",
188
+ "IGST",
189
+ "Total",
190
+ ].join(",");
191
+ /**
192
+ * 🛑 Every field is quoted and internal quotes are doubled.
193
+ *
194
+ * An invoice number contains `/`, a place of supply can contain a comma, and a
195
+ * CSV whose columns shift by one on a single row is worse than no CSV — it
196
+ * reconciles to a plausible wrong number rather than failing to open.
197
+ */
198
+ function csvCell(value) {
199
+ const s = String(value);
200
+ return `"${s.replace(/"/g, '""')}"`;
201
+ }
202
+ export function gstr1ToCsv(data) {
203
+ const lines = [CSV_HEADER];
204
+ for (const r of data.rows) {
205
+ lines.push([
206
+ r.documentNumber,
207
+ r.documentType === "credit_note" ? "Credit Note" : "Invoice",
208
+ r.documentDate,
209
+ r.orderId,
210
+ r.placeOfSupply,
211
+ r.gstRate,
212
+ r.taxableValue.toFixed(2),
213
+ r.cgst.toFixed(2),
214
+ r.sgst.toFixed(2),
215
+ r.igst.toFixed(2),
216
+ r.total.toFixed(2),
217
+ ]
218
+ .map(csvCell)
219
+ .join(","));
220
+ }
221
+ /*
222
+ * A TOTAL row, and it is net of credit notes. An accountant's first act is to
223
+ * check the file's own total against their own figure; making them sum 400
224
+ * rows to do it is how a transcription error enters a tax return.
225
+ */
226
+ lines.push([
227
+ "TOTAL",
228
+ "",
229
+ "",
230
+ "",
231
+ "",
232
+ "",
233
+ data.totals.taxableValue.toFixed(2),
234
+ data.totals.cgst.toFixed(2),
235
+ data.totals.sgst.toFixed(2),
236
+ data.totals.igst.toFixed(2),
237
+ data.totals.total.toFixed(2),
238
+ ]
239
+ .map(csvCell)
240
+ .join(","));
241
+ /*
242
+ * 🛑 The exceptions go IN the file, not only in a log. The person filing is
243
+ * not the person reading our logs, and a paid order absent from a tax return
244
+ * is the one error this export must not hide.
245
+ */
246
+ if (data.unnumberedPaidOrders.length > 0) {
247
+ lines.push("");
248
+ lines.push(csvCell(`WARNING: ${data.unnumberedPaidOrders.length} paid order(s) in this window carry no invoice number and are NOT included above: ${data.unnumberedPaidOrders.join(" ")}`));
249
+ }
250
+ return lines.join("\n");
251
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Issuing an invoice: allocate the number, render, store the bytes (Phase 7).
3
+ *
4
+ * ## 🛑 WHERE the PDF goes, and why it is not the media path
5
+ *
6
+ * `private/invoices/{orderId}/{uuid}.pdf`, with **no `mediaAssets` row** and a
7
+ * **random** object name. An invoice carries the buyer's full name, address and
8
+ * phone.
9
+ *
10
+ * Three facts, each of which alone would be enough:
11
+ *
12
+ * 1. `/api/media/[...slug]` applies **no authentication at all** — it is a
13
+ * deliberately bare handler whose job is watermarking public product
14
+ * photography.
15
+ * 2. The bucket's storage rule is `allow read: if true`.
16
+ * 3. Media filenames in this codebase are content-derived and therefore
17
+ * GUESSABLE — `generateInvoiceFilename` is `invoice-{orderId}-{YYYYMMDD}`.
18
+ *
19
+ * So the random name is against the SEO-slug convention everywhere else *on
20
+ * purpose*: guessable is the one property this must not have. And the `private/`
21
+ * prefix is refused by the proxy's raw-path fallback — a check, not a naming
22
+ * convention, which is precisely the distinction the digital-content comment
23
+ * got wrong for weeks.
24
+ *
25
+ * ## 🛑 Store the bytes; never re-render to serve or to email
26
+ *
27
+ * Two renders can disagree — a changed template, a different library version,
28
+ * a settings read that moved — and the buyer's copy must be byte-identical to
29
+ * ours. So the bytes are written once, at issue time, and every later consumer
30
+ * reads that object. On-demand rendering survives only as a fallback for orders
31
+ * that predate this phase and have no stored path.
32
+ */
33
+ import type { OrderDocument } from "../../../../features/orders/schemas/firestore";
34
+ /** Storage prefix. Mirrors `private/digital-content/` — refused by the proxy. */
35
+ export declare const INVOICE_PREFIX_PATH = "private/invoices";
36
+ /**
37
+ * Renders the PDF for an order.
38
+ *
39
+ * Injected rather than imported, because the renderer lives in
40
+ * `functions/src/invoice-pdf.ts` — it pulls `pdfkit`, which is a Functions
41
+ * dependency and has no business in appkit's module graph (Rule #6's build
42
+ * weight, and Root Cause #18's import-chain trap).
43
+ */
44
+ export type InvoiceRenderer = (order: OrderDocument) => Promise<Buffer>;
45
+ export interface IssueInvoiceResult {
46
+ invoiceNumber: string;
47
+ invoiceStoragePath: string;
48
+ /** False when the order already had an invoice — the normal re-entry case. */
49
+ issued: boolean;
50
+ }
51
+ /**
52
+ * Give a paid order its invoice number and its stored PDF.
53
+ *
54
+ * 🛑 Idempotent through `allocateSeriesNumber`'s transactional claim.
55
+ * `onOrderStatusChange` can fire more than once, and two numbers for one order
56
+ * is the single unrecoverable failure in this phase — a number cannot be
57
+ * withdrawn without leaving a gap.
58
+ *
59
+ * 🛑 Called on `paymentStatus → paid`, never at creation. COD and manual orders
60
+ * are created `pending`.
61
+ */
62
+ export declare function issueInvoiceForOrder(order: OrderDocument, render: InvoiceRenderer): Promise<IssueInvoiceResult>;
63
+ /**
64
+ * Persist bytes rendered on demand, for an order that is already NUMBERED.
65
+ *
66
+ * ## Why the lazy path exists at all
67
+ *
68
+ * Numbering happens in `onOrderStatusChange`, which runs in appkit and cannot
69
+ * render — `pdfkit` is a Functions dependency and bringing it into appkit's
70
+ * module graph would add it to every consumer build (Rule #6's build weight,
71
+ * Root Cause #18's import chain). So the two halves are split: the number is
72
+ * allocated immediately and transactionally, the bytes are produced the first
73
+ * time anyone asks for them and then stored forever.
74
+ *
75
+ * That split is a deliberate trade and worth naming: the first download of a
76
+ * given invoice pays a render, and every later one does not. The alternative —
77
+ * a second Firestore trigger on `orders` living in `functions/` — is the shape
78
+ * that burned a million invocations a day in Root Cause #92.
79
+ *
80
+ * 🛑 Refuses to overwrite an existing path. A second numbered copy of one
81
+ * invoice is two documents claiming one number, and the one a buyer already
82
+ * downloaded is the one that counts.
83
+ *
84
+ * 🛑 Refuses an un-numbered order. Storing a PDF before the number exists
85
+ * would freeze a pre-payment snapshot as "the invoice", and the real issuance
86
+ * would then find a path already set.
87
+ */
88
+ export declare function storeRenderedInvoice(orderId: string, bytes: Buffer): Promise<string | null>;
89
+ /**
90
+ * The stored bytes for an already-issued invoice, or null.
91
+ *
92
+ * Returns null rather than throwing on a missing object, because the caller's
93
+ * correct response is to fall back to an on-demand render for a pre-Phase-7
94
+ * order — and a throw would turn a legacy order's invoice page into an error.
95
+ */
96
+ export declare function readStoredInvoice(storagePath: string | undefined): Promise<Buffer | null>;
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Issuing an invoice: allocate the number, render, store the bytes (Phase 7).
3
+ *
4
+ * ## 🛑 WHERE the PDF goes, and why it is not the media path
5
+ *
6
+ * `private/invoices/{orderId}/{uuid}.pdf`, with **no `mediaAssets` row** and a
7
+ * **random** object name. An invoice carries the buyer's full name, address and
8
+ * phone.
9
+ *
10
+ * Three facts, each of which alone would be enough:
11
+ *
12
+ * 1. `/api/media/[...slug]` applies **no authentication at all** — it is a
13
+ * deliberately bare handler whose job is watermarking public product
14
+ * photography.
15
+ * 2. The bucket's storage rule is `allow read: if true`.
16
+ * 3. Media filenames in this codebase are content-derived and therefore
17
+ * GUESSABLE — `generateInvoiceFilename` is `invoice-{orderId}-{YYYYMMDD}`.
18
+ *
19
+ * So the random name is against the SEO-slug convention everywhere else *on
20
+ * purpose*: guessable is the one property this must not have. And the `private/`
21
+ * prefix is refused by the proxy's raw-path fallback — a check, not a naming
22
+ * convention, which is precisely the distinction the digital-content comment
23
+ * got wrong for weeks.
24
+ *
25
+ * ## 🛑 Store the bytes; never re-render to serve or to email
26
+ *
27
+ * Two renders can disagree — a changed template, a different library version,
28
+ * a settings read that moved — and the buyer's copy must be byte-identical to
29
+ * ours. So the bytes are written once, at issue time, and every later consumer
30
+ * reads that object. On-demand rendering survives only as a fallback for orders
31
+ * that predate this phase and have no stored path.
32
+ */
33
+ import { randomUUID } from "node:crypto";
34
+ import { getAdminStorage } from "../../../../providers/db-firebase/index.js";
35
+ import { orderRepository } from "../../../../features/orders/repository/orders.repository.js";
36
+ import { serverLogger } from "../../../../monitoring/index.js";
37
+ import { normalizeError } from "../../../../errors/normalize.js";
38
+ import { allocateSeriesNumber } from "./allocate.js";
39
+ /** Storage prefix. Mirrors `private/digital-content/` — refused by the proxy. */
40
+ export const INVOICE_PREFIX_PATH = "private/invoices";
41
+ /**
42
+ * Give a paid order its invoice number and its stored PDF.
43
+ *
44
+ * 🛑 Idempotent through `allocateSeriesNumber`'s transactional claim.
45
+ * `onOrderStatusChange` can fire more than once, and two numbers for one order
46
+ * is the single unrecoverable failure in this phase — a number cannot be
47
+ * withdrawn without leaving a gap.
48
+ *
49
+ * 🛑 Called on `paymentStatus → paid`, never at creation. COD and manual orders
50
+ * are created `pending`.
51
+ */
52
+ export async function issueInvoiceForOrder(order, render) {
53
+ /*
54
+ * The fast path, and the common one: a second trigger on an order that is
55
+ * already invoiced does no work and touches no counter. Checked before the
56
+ * transaction purely to save the round trip — the transaction would reach
57
+ * the same answer, which is what makes this safe rather than a race.
58
+ */
59
+ if (order.invoiceNumber && order.invoiceStoragePath) {
60
+ return {
61
+ invoiceNumber: order.invoiceNumber,
62
+ invoiceStoragePath: order.invoiceStoragePath,
63
+ issued: false,
64
+ };
65
+ }
66
+ const issuedAt = new Date();
67
+ const allocation = await allocateSeriesNumber({
68
+ kind: "invoice",
69
+ subjectId: order.id,
70
+ orderId: order.id,
71
+ at: issuedAt,
72
+ });
73
+ /*
74
+ * 🛑 `randomUUID()`, and the ORDER ID is in the directory rather than the
75
+ * filename. The directory makes an operator's life possible (every document
76
+ * for one order is in one place); the random leaf is what makes the object
77
+ * unguessable. Putting the order id in the FILENAME would undo the second
78
+ * without improving the first.
79
+ */
80
+ const storagePath = `${INVOICE_PREFIX_PATH}/${order.id}/${randomUUID()}.pdf`;
81
+ /*
82
+ * Rendered with the number already on the order object, so the PDF carries
83
+ * the number it was allocated — the renderer reads `order.invoiceNumber`,
84
+ * and handing it the un-numbered document would produce an invoice with no
85
+ * number printed on it while the database said otherwise.
86
+ */
87
+ const buffer = await render({
88
+ ...order,
89
+ invoiceNumber: allocation.number,
90
+ invoiceIssuedAt: issuedAt,
91
+ });
92
+ await getAdminStorage()
93
+ .bucket()
94
+ .file(storagePath)
95
+ .save(buffer, {
96
+ contentType: "application/pdf",
97
+ /*
98
+ * 🛑 `private, no-store` on the OBJECT as well as on every response that
99
+ * serves it. The object is not reachable without a signed URL or the
100
+ * Admin SDK, but an intermediate cache that ever saw one must not keep
101
+ * it: an invoice is one buyer's address, not a product photo.
102
+ */
103
+ metadata: { cacheControl: "private, no-store" },
104
+ resumable: false,
105
+ });
106
+ /*
107
+ * 🛑 The order is updated AFTER the bytes exist. The reverse ordering would
108
+ * leave an order claiming an `invoiceStoragePath` that resolves to nothing,
109
+ * and the serve route would then 404 on a document the buyer is entitled to
110
+ * — while the number is already consumed, so retrying cannot re-issue it.
111
+ *
112
+ * A crash between the save and this update leaves an unreferenced object
113
+ * (a few KB) and an order with a number but no path, which the next trigger
114
+ * re-renders into a NEW object. Wasteful and harmless, which is the right
115
+ * side of this trade.
116
+ */
117
+ await orderRepository.update(order.id, {
118
+ invoiceNumber: allocation.number,
119
+ invoiceIssuedAt: issuedAt,
120
+ invoiceStoragePath: storagePath,
121
+ });
122
+ serverLogger.info("[invoicing] invoice issued", {
123
+ orderId: order.id,
124
+ invoiceNumber: allocation.number,
125
+ });
126
+ return {
127
+ invoiceNumber: allocation.number,
128
+ invoiceStoragePath: storagePath,
129
+ issued: allocation.allocated,
130
+ };
131
+ }
132
+ /**
133
+ * Persist bytes rendered on demand, for an order that is already NUMBERED.
134
+ *
135
+ * ## Why the lazy path exists at all
136
+ *
137
+ * Numbering happens in `onOrderStatusChange`, which runs in appkit and cannot
138
+ * render — `pdfkit` is a Functions dependency and bringing it into appkit's
139
+ * module graph would add it to every consumer build (Rule #6's build weight,
140
+ * Root Cause #18's import chain). So the two halves are split: the number is
141
+ * allocated immediately and transactionally, the bytes are produced the first
142
+ * time anyone asks for them and then stored forever.
143
+ *
144
+ * That split is a deliberate trade and worth naming: the first download of a
145
+ * given invoice pays a render, and every later one does not. The alternative —
146
+ * a second Firestore trigger on `orders` living in `functions/` — is the shape
147
+ * that burned a million invocations a day in Root Cause #92.
148
+ *
149
+ * 🛑 Refuses to overwrite an existing path. A second numbered copy of one
150
+ * invoice is two documents claiming one number, and the one a buyer already
151
+ * downloaded is the one that counts.
152
+ *
153
+ * 🛑 Refuses an un-numbered order. Storing a PDF before the number exists
154
+ * would freeze a pre-payment snapshot as "the invoice", and the real issuance
155
+ * would then find a path already set.
156
+ */
157
+ export async function storeRenderedInvoice(orderId, bytes) {
158
+ const order = await orderRepository.findById(orderId);
159
+ if (!order?.invoiceNumber)
160
+ return null;
161
+ if (order.invoiceStoragePath)
162
+ return order.invoiceStoragePath;
163
+ const storagePath = `${INVOICE_PREFIX_PATH}/${orderId}/${randomUUID()}.pdf`;
164
+ await getAdminStorage()
165
+ .bucket()
166
+ .file(storagePath)
167
+ .save(bytes, {
168
+ contentType: "application/pdf",
169
+ metadata: { cacheControl: "private, no-store" },
170
+ resumable: false,
171
+ });
172
+ // Path written only after the bytes exist — the reverse would point at
173
+ // nothing and 404 a document the buyer is entitled to.
174
+ await orderRepository.update(orderId, { invoiceStoragePath: storagePath });
175
+ serverLogger.info("[invoicing] invoice bytes stored on first download", {
176
+ orderId,
177
+ invoiceNumber: order.invoiceNumber,
178
+ });
179
+ return storagePath;
180
+ }
181
+ /**
182
+ * The stored bytes for an already-issued invoice, or null.
183
+ *
184
+ * Returns null rather than throwing on a missing object, because the caller's
185
+ * correct response is to fall back to an on-demand render for a pre-Phase-7
186
+ * order — and a throw would turn a legacy order's invoice page into an error.
187
+ */
188
+ export async function readStoredInvoice(storagePath) {
189
+ if (!storagePath)
190
+ return null;
191
+ try {
192
+ const [buffer] = await getAdminStorage().bucket().file(storagePath).download();
193
+ return buffer;
194
+ }
195
+ catch (err) {
196
+ serverLogger.warn("[invoicing] stored invoice unreadable — falling back to render", {
197
+ storagePath,
198
+ error: normalizeError(err).message,
199
+ });
200
+ return null;
201
+ }
202
+ }
@@ -1,4 +1,5 @@
1
1
  import { type ActionResult } from "../../../shared/types/action-result";
2
+ import { type DisputeKind } from "../../../shared/features/orders/dispute-reasons";
2
3
  export declare function createOrderAction(input: unknown): Promise<ActionResult<unknown>>;
3
4
  /**
4
5
  * Cancel an order, and give the money back if any was taken (D14).
@@ -58,7 +59,13 @@ export declare function adminRejectPaymentAsFraudAction(orderId: string, note: s
58
59
  * the seller (store owner), or admin/moderator. Does not itself reverse
59
60
  * payment/order status — surfaces to the admin queue for investigation.
60
61
  */
61
- export declare function raiseOrderDisputeAction(orderId: string, reason: string): Promise<ActionResult<void>>;
62
+ export declare function raiseOrderDisputeAction(orderId: string, input: {
63
+ kind: DisputeKind;
64
+ /** `disputeReasons/{id}` — must exist and must match `kind`. */
65
+ reasonId: string;
66
+ /** The buyer's own words. Optional; the reason is the required part. */
67
+ detail?: string;
68
+ }): Promise<ActionResult<void>>;
62
69
  /**
63
70
  * A buyer submits payment for ONE of their own EMI instalments.
64
71
  *