@mohasinac/appkit 4.42.18 → 4.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/dist/_internal/client/features/lottery/LotteryDetailView.js +22 -1
  2. package/dist/_internal/server/features/categories/adapters.d.ts +47 -0
  3. package/dist/_internal/server/features/categories/adapters.js +235 -0
  4. package/dist/_internal/server/features/checkout/actions.js +161 -22
  5. package/dist/_internal/server/features/checkout/expand-order-items.js +34 -0
  6. package/dist/_internal/server/features/orders/adapters.js +21 -0
  7. package/dist/_internal/server/features/products/list-public.d.ts +24 -0
  8. package/dist/_internal/server/features/products/list-public.js +10 -2
  9. package/dist/_internal/server/features/refunds/actions.js +1 -1
  10. package/dist/_internal/server/features/seo/sitemap.js +54 -15
  11. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  12. package/dist/_internal/server/features/stores/adapters.js +6 -0
  13. package/dist/_internal/server/functions/scheduled.d.ts +4 -2
  14. package/dist/_internal/server/functions/scheduled.js +64 -6
  15. package/dist/_internal/server/jobs/core/adminAnalytics.d.ts +0 -1
  16. package/dist/_internal/server/jobs/core/adminAnalytics.js +9 -1
  17. package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.d.ts +47 -0
  18. package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +468 -0
  19. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +14 -1
  20. package/dist/_internal/server/jobs/core/countersReconcile.js +24 -8
  21. package/dist/_internal/server/jobs/core/onCategoryWrite.js +59 -0
  22. package/dist/_internal/server/jobs/core/onProductWrite.js +31 -0
  23. package/dist/_internal/server/jobs/core/onReviewWrite.js +8 -0
  24. package/dist/_internal/server/jobs/core/onStoreWrite.d.ts +1 -1
  25. package/dist/_internal/server/jobs/core/onStoreWrite.js +8 -2
  26. package/dist/_internal/server/jobs/core/priceIndexRollup.d.ts +47 -0
  27. package/dist/_internal/server/jobs/core/priceIndexRollup.js +234 -0
  28. package/dist/_internal/server/jobs/core/revalidateNotify.d.ts +28 -0
  29. package/dist/_internal/server/jobs/core/revalidateNotify.js +125 -0
  30. package/dist/_internal/server/jobs/core/{testerSandboxCleanup.d.ts → testDataCleanup.d.ts} +3 -3
  31. package/dist/_internal/server/jobs/core/{testerSandboxCleanup.js → testDataCleanup.js} +3 -3
  32. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +5 -1
  33. package/dist/_internal/server/jobs/handlers/analyticsAlertEvaluate.d.ts +2 -0
  34. package/dist/_internal/server/jobs/handlers/analyticsAlertEvaluate.js +2 -0
  35. package/dist/_internal/server/jobs/handlers/index.d.ts +3 -1
  36. package/dist/_internal/server/jobs/handlers/index.js +3 -1
  37. package/dist/_internal/server/jobs/handlers/messages.d.ts +5 -2
  38. package/dist/_internal/server/jobs/handlers/messages.js +5 -2
  39. package/dist/_internal/server/jobs/handlers/priceIndexRollup.d.ts +2 -0
  40. package/dist/_internal/server/jobs/handlers/priceIndexRollup.js +2 -0
  41. package/dist/_internal/server/jobs/handlers/testDataCleanup.d.ts +2 -0
  42. package/dist/_internal/server/jobs/handlers/testDataCleanup.js +2 -0
  43. package/dist/_internal/shared/actions/action-registry.js +34 -0
  44. package/dist/_internal/shared/cache/revalidate-targets.d.ts +49 -0
  45. package/dist/_internal/shared/cache/revalidate-targets.js +157 -0
  46. package/dist/_internal/shared/checkout/order-math.d.ts +113 -3
  47. package/dist/_internal/shared/checkout/order-math.js +194 -7
  48. package/dist/_internal/shared/features/categories/price-guidance.d.ts +110 -0
  49. package/dist/_internal/shared/features/categories/price-guidance.js +136 -0
  50. package/dist/_internal/shared/features/site-settings/gst-registration.d.ts +90 -0
  51. package/dist/_internal/shared/features/site-settings/gst-registration.js +126 -0
  52. package/dist/_internal/shared/fees/calculator.d.ts +105 -0
  53. package/dist/_internal/shared/fees/calculator.js +143 -0
  54. package/dist/_internal/shared/listing-types/art/config.d.ts +0 -6
  55. package/dist/_internal/shared/listing-types/classified/config.d.ts +0 -6
  56. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +0 -6
  57. package/dist/_internal/shared/listing-types/live/config.d.ts +0 -6
  58. package/dist/_internal/shared/listing-types/standard/config.d.ts +0 -6
  59. package/dist/_internal/shared/listing-types/stickers/config.d.ts +0 -6
  60. package/dist/_internal/shared/templating/placeholders.d.ts +70 -0
  61. package/dist/_internal/shared/templating/placeholders.js +73 -0
  62. package/dist/client.d.ts +2 -0
  63. package/dist/client.js +1 -0
  64. package/dist/constants/api-endpoints.d.ts +42 -0
  65. package/dist/constants/api-endpoints.js +14 -0
  66. package/dist/features/admin/actions/admin-actions.d.ts +1 -1
  67. package/dist/features/admin/actions/admin-actions.js +1 -1
  68. package/dist/features/admin/components/AdminCategoryEditorView.js +269 -3
  69. package/dist/features/admin/components/AdminProductEditorView.js +8 -1
  70. package/dist/features/admin/components/AdminProductsView.js +78 -7
  71. package/dist/features/admin/components/AdminSiteSettingsView.js +20 -1
  72. package/dist/features/admin/components/AdminTaxCodeEditorView.d.ts +10 -0
  73. package/dist/features/admin/components/AdminTaxCodeEditorView.js +160 -0
  74. package/dist/features/admin/components/AdminTaxCodesView.d.ts +13 -0
  75. package/dist/features/admin/components/AdminTaxCodesView.js +86 -0
  76. package/dist/features/admin/components/AdminTesterChecklistView.js +1 -1
  77. package/dist/features/admin/components/analytics/AdminTopProductsTable.js +1 -1
  78. package/dist/features/auctions/components/AuctionBottomActions.js +10 -1
  79. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -1
  80. package/dist/features/auctions/components/AuctionLiveStatusBadge.d.ts +20 -0
  81. package/dist/features/auctions/components/AuctionLiveStatusBadge.js +21 -0
  82. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
  83. package/dist/features/auctions/components/PlaceBidFormClient.js +20 -2
  84. package/dist/features/auctions/hooks/useIsAuctionEnded.d.ts +41 -0
  85. package/dist/features/auctions/hooks/useIsAuctionEnded.js +76 -0
  86. package/dist/features/auth/schemas/firestore.d.ts +0 -1
  87. package/dist/features/blog/api/route.js +16 -1
  88. package/dist/features/blog/schemas/firestore.d.ts +2 -2
  89. package/dist/features/catalogue/components/CatalogueItemEditorView.js +14 -3
  90. package/dist/features/categories/actions/category-actions.d.ts +2 -2
  91. package/dist/features/categories/actions/category-actions.js +22 -10
  92. package/dist/features/categories/api/[id]/description-template/route.d.ts +45 -0
  93. package/dist/features/categories/api/[id]/description-template/route.js +252 -0
  94. package/dist/features/categories/api/route.js +156 -11
  95. package/dist/features/categories/components/CategoriesIndexListing.d.ts +1 -3
  96. package/dist/features/categories/components/CategoriesIndexListing.js +1 -1
  97. package/dist/features/categories/components/CategoriesIndexPageView.d.ts +27 -1
  98. package/dist/features/categories/components/CategoriesIndexPageView.js +24 -33
  99. package/dist/features/categories/components/CategoryDetailPageView.js +43 -10
  100. package/dist/features/categories/components/CategoryTemplatePicker.d.ts +65 -0
  101. package/dist/features/categories/components/CategoryTemplatePicker.js +170 -0
  102. package/dist/features/categories/constants/listing-categories.d.ts +17 -0
  103. package/dist/features/categories/constants/listing-categories.js +53 -0
  104. package/dist/features/categories/hooks/useCategoryTree.js +28 -2
  105. package/dist/features/categories/repository/categories.repository.d.ts +73 -12
  106. package/dist/features/categories/repository/categories.repository.js +203 -42
  107. package/dist/features/categories/repository/price-index.repository.d.ts +115 -0
  108. package/dist/features/categories/repository/price-index.repository.js +85 -0
  109. package/dist/features/categories/schemas/category-content.d.ts +206 -0
  110. package/dist/features/categories/schemas/category-content.js +28 -0
  111. package/dist/features/categories/schemas/firestore.d.ts +74 -5
  112. package/dist/features/categories/schemas/firestore.js +74 -3
  113. package/dist/features/categories/schemas/index.d.ts +1047 -0
  114. package/dist/features/categories/schemas/index.js +96 -0
  115. package/dist/features/categories/server.d.ts +1 -0
  116. package/dist/features/categories/server.js +7 -0
  117. package/dist/features/categories/types/index.d.ts +9 -0
  118. package/dist/features/events/schemas/firestore.d.ts +2 -2
  119. package/dist/features/faq/schemas/firestore.d.ts +2 -1
  120. package/dist/features/faq/schemas/firestore.js +20 -8
  121. package/dist/features/faq/utils/getHomepageFaqsCached.d.ts +20 -0
  122. package/dist/features/faq/utils/getHomepageFaqsCached.js +22 -0
  123. package/dist/features/homepage/components/MarketplaceHomepageView.js +13 -3
  124. package/dist/features/homepage/lib/live-stats.js +18 -8
  125. package/dist/features/media/MediaImage.js +19 -2
  126. package/dist/features/orders/schemas/firestore.d.ts +70 -0
  127. package/dist/features/orders/types/index.d.ts +55 -0
  128. package/dist/features/products/actions/product-actions.d.ts +0 -9
  129. package/dist/features/products/actions/product-actions.js +25 -0
  130. package/dist/features/products/columns/index.d.ts +0 -1
  131. package/dist/features/products/columns/index.js +5 -1
  132. package/dist/features/products/components/ArtStickersListView.js +7 -1
  133. package/dist/features/products/components/PrizeDrawsSection.js +5 -0
  134. package/dist/features/products/components/ProductFilters.d.ts +0 -9
  135. package/dist/features/products/components/ProductForm.d.ts +27 -1
  136. package/dist/features/products/components/ProductForm.js +26 -12
  137. package/dist/features/products/components/ProductsIndexPageView.js +1 -1
  138. package/dist/features/products/constants/sieve.d.ts +0 -6
  139. package/dist/features/products/constants/sieve.js +35 -2
  140. package/dist/features/products/repository/products.repository.d.ts +115 -1
  141. package/dist/features/products/repository/products.repository.js +491 -39
  142. package/dist/features/products/schemas/firestore.d.ts +65 -5
  143. package/dist/features/products/schemas/firestore.js +9 -0
  144. package/dist/features/products/schemas/product-features.d.ts +140 -2
  145. package/dist/features/products/schemas/product-features.js +62 -1
  146. package/dist/features/promotions/repository/coupons.repository.js +6 -1
  147. package/dist/features/promotions/schemas/firestore.d.ts +44 -1
  148. package/dist/features/promotions/schemas/firestore.js +76 -2
  149. package/dist/features/reviews/repository/reviews.repository.d.ts +22 -0
  150. package/dist/features/reviews/repository/reviews.repository.js +35 -0
  151. package/dist/features/seller/actions/seller-actions.js +17 -2
  152. package/dist/features/seller/components/PrintCenterView.d.ts +3 -2
  153. package/dist/features/seller/components/PrintCenterView.js +3 -2
  154. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +21 -11
  155. package/dist/features/seller/components/SellerOrdersView.js +6 -1
  156. package/dist/features/seller/components/SellerPayoutSettingsView.js +19 -3
  157. package/dist/features/seller/components/SellerProductShell.d.ts +50 -2
  158. package/dist/features/seller/components/SellerProductShell.js +101 -9
  159. package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +13 -1
  160. package/dist/features/seller/components/SellerProductsFilterDrawer.js +2 -2
  161. package/dist/features/seller/components/SellerProductsView.js +108 -3
  162. package/dist/features/seller/types/index.d.ts +17 -0
  163. package/dist/features/seller/utils/product-draft-mapping.d.ts +13 -0
  164. package/dist/features/seller/utils/product-draft-mapping.js +17 -0
  165. package/dist/features/store-extensions/constants/alert-metrics.d.ts +86 -0
  166. package/dist/features/store-extensions/constants/alert-metrics.js +142 -0
  167. package/dist/features/store-extensions/schemas/firestore.d.ts +19 -1
  168. package/dist/features/stores/schemas/firestore.d.ts +31 -2
  169. package/dist/features/tax-codes/repository/tax-codes.repository.d.ts +55 -0
  170. package/dist/features/tax-codes/repository/tax-codes.repository.js +131 -0
  171. package/dist/features/tax-codes/schemas/firestore.d.ts +125 -0
  172. package/dist/features/tax-codes/schemas/firestore.js +82 -0
  173. package/dist/features/tax-codes/schemas/tax-code-form.d.ts +63 -0
  174. package/dist/features/tax-codes/schemas/tax-code-form.js +152 -0
  175. package/dist/features/tax-codes/schemas/tax-codes.validators.d.ts +107 -0
  176. package/dist/features/tax-codes/schemas/tax-codes.validators.js +85 -0
  177. package/dist/features/tester/actions/index.d.ts +0 -1
  178. package/dist/features/tester/actions/index.js +0 -1
  179. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +1 -1
  180. package/dist/features/tester/components/AdminTesterFeedbackListView.js +1 -1
  181. package/dist/features/tester/components/index.d.ts +0 -4
  182. package/dist/features/tester/components/index.js +0 -2
  183. package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +1 -20
  184. package/dist/features/tester/repository/tester-checklist-item.repository.js +0 -52
  185. package/dist/features/tester/schemas/firestore.d.ts +0 -6
  186. package/dist/features/tester/seed-data/authored/admin__admin-detail-round-trips.js +3 -3
  187. package/dist/features/tester/seed-data/authored/admin__site-system.js +4 -5
  188. package/dist/features/tester/seed-data/authored/content-discovery__category-counts-and-rollup.js +2 -2
  189. package/dist/features/tester/seed-data/authored/index.js +0 -2
  190. package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +0 -19
  191. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +10 -29
  192. package/dist/index.d.ts +21 -7
  193. package/dist/index.js +53 -7
  194. package/dist/next/routing/route-map.d.ts +2 -4
  195. package/dist/next/routing/route-map.js +1 -2
  196. package/dist/repositories/index.d.ts +1 -0
  197. package/dist/repositories/index.js +1 -0
  198. package/dist/schemas/registry.d.ts +506 -0
  199. package/dist/seed/_helpers/brand-rows.d.ts +35 -0
  200. package/dist/seed/_helpers/brand-rows.js +576 -0
  201. package/dist/seed/_helpers/category-content.d.ts +39 -0
  202. package/dist/seed/_helpers/category-content.js +634 -0
  203. package/dist/seed/_helpers/category-forest.d.ts +2 -0
  204. package/dist/seed/_helpers/category-forest.js +585 -243
  205. package/dist/seed/_helpers/category-models.d.ts +5 -0
  206. package/dist/seed/_helpers/category-models.js +1364 -0
  207. package/dist/seed/_helpers/category-tree.js +17 -1
  208. package/dist/seed/_helpers/character-features.d.ts +10 -0
  209. package/dist/seed/_helpers/character-features.js +118 -0
  210. package/dist/seed/_helpers/media.d.ts +1 -1
  211. package/dist/seed/_helpers/media.js +44 -8
  212. package/dist/seed/_helpers/search-txt-wrappers.d.ts +10 -0
  213. package/dist/seed/_helpers/search-txt-wrappers.js +12 -1
  214. package/dist/seed/categories-seed-data.js +147 -169
  215. package/dist/seed/index.d.ts +1 -0
  216. package/dist/seed/index.js +1 -0
  217. package/dist/seed/manifest.d.ts +1 -1
  218. package/dist/seed/manifest.js +5 -0
  219. package/dist/seed/orders-seed-data.js +5 -3
  220. package/dist/seed/product-features-seed-data.d.ts +1 -8
  221. package/dist/seed/product-features-seed-data.js +358 -154
  222. package/dist/seed/products-art-seed-data.js +16 -4
  223. package/dist/seed/products-auctions-seed-data.js +6 -6
  224. package/dist/seed/products-classifieds-seed-data.js +17 -7
  225. package/dist/seed/products-digital-codes-seed-data.js +12 -12
  226. package/dist/seed/products-preorders-seed-data.js +8 -8
  227. package/dist/seed/products-prize-draws-seed-data.js +25 -8
  228. package/dist/seed/products-standard-seed-data.js +10 -60
  229. package/dist/seed/products-stickers-seed-data.js +6 -6
  230. package/dist/seed/site-settings-seed-data.js +24 -0
  231. package/dist/seed/store-extensions-seed-data.js +10 -4
  232. package/dist/seed/tax-codes-seed-data.d.ts +34 -0
  233. package/dist/seed/tax-codes-seed-data.js +100 -0
  234. package/dist/seed/test-data-ttl.d.ts +29 -0
  235. package/dist/seed/test-data-ttl.js +31 -0
  236. package/dist/server-entry.d.ts +1 -1
  237. package/dist/server-entry.js +6 -1
  238. package/dist/server.d.ts +4 -1
  239. package/dist/server.js +11 -2
  240. package/dist/styles.css +40 -0
  241. package/dist/ui/components/PaginatedSelect.d.ts +14 -0
  242. package/dist/ui/components/PaginatedSelect.js +0 -0
  243. package/dist/ui/components/PaginatedSelect.style.css +40 -0
  244. package/dist/utils/media-url.d.ts +26 -0
  245. package/dist/utils/media-url.js +144 -0
  246. package/dist/utils/physical-location.d.ts +23 -0
  247. package/dist/utils/physical-location.js +52 -0
  248. package/dist/utils/search-txt-builders.d.ts +20 -0
  249. package/dist/utils/search-txt-builders.js +36 -0
  250. package/package.json +1 -1
  251. package/scripts/seed-cli.mjs +4 -1
  252. package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.d.ts +0 -2
  253. package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.js +0 -2
  254. package/dist/features/tester/actions/leaderboard-actions.d.ts +0 -3
  255. package/dist/features/tester/actions/leaderboard-actions.js +0 -5
  256. package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +0 -23
  257. package/dist/features/tester/components/BugHunterLeaderboardView.js +0 -18
  258. package/dist/features/tester/components/TesterHubView.d.ts +0 -5
  259. package/dist/features/tester/components/TesterHubView.js +0 -110
  260. package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.d.ts +0 -2
  261. package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +0 -78
  262. package/dist/features/tester/seed-data/tester-ttl.d.ts +0 -4
  263. package/dist/features/tester/seed-data/tester-ttl.js +0 -6
@@ -0,0 +1,131 @@
1
+ import { normalizeError } from "../../../errors/normalize";
2
+ import { DatabaseError, ConflictError, ValidationError } from "../../../errors";
3
+ import { BaseRepository, } from "../../../providers/db-firebase";
4
+ import { TAX_CODES_COLLECTION, TAX_CODE_FIELDS, TAX_CODE_SIEVE_FIELDS, createTaxCodeId, isValidHsnCode, } from "../schemas/firestore";
5
+ function failureMessage(prefix, error) {
6
+ return `${prefix}: ${error instanceof Error ? error.message : "Unknown error"}`;
7
+ }
8
+ /**
9
+ * The `taxCodes` collection.
10
+ *
11
+ * Small, read-mostly and read on a hot path — `deriveTaxonomy` resolves a
12
+ * category's `taxCodeId` on every product create and on any update that names
13
+ * a taxonomy field. So the two reads below are single-document and
14
+ * single-field-equality shaped on purpose; neither needs a composite index.
15
+ */
16
+ export class TaxCodesRepository extends BaseRepository {
17
+ constructor() {
18
+ super(TAX_CODES_COLLECTION);
19
+ }
20
+ async list(model) {
21
+ return this.sieveQuery(model, TAX_CODE_SIEVE_FIELDS);
22
+ }
23
+ /**
24
+ * Every active code, chapter-then-rate ordered, for the picker.
25
+ *
26
+ * Unpaginated deliberately — this is ~10 rows and will be a few dozen at
27
+ * most. If it ever needs paging, the picker needs `searchTxt` first, and
28
+ * that is a different change.
29
+ */
30
+ async listActive() {
31
+ try {
32
+ const snap = await this.getCollection()
33
+ .where(TAX_CODE_FIELDS.IS_ACTIVE, "==", true)
34
+ .get();
35
+ return snap.docs
36
+ .map((d) => this.mapDoc(d))
37
+ .sort((a, b) => (a.chapter ?? "").localeCompare(b.chapter ?? "") ||
38
+ a.gstRate - b.gstRate ||
39
+ a.label.localeCompare(b.label));
40
+ }
41
+ catch (error) {
42
+ void normalizeError(error);
43
+ throw new DatabaseError(failureMessage("Failed to list tax codes", error));
44
+ }
45
+ }
46
+ /**
47
+ * Every code including inactive ones, for the admin list.
48
+ *
49
+ * 🛑 EXPLICITLY bounded, even though the collection is five rows. Two
50
+ * reasons: `BaseRepository.findAll()` relies on a cap several layers away
51
+ * (`base.ts` clamps perPage at 500), and a route that reads "all of a
52
+ * collection" with no visible bound is the shape that has to be re-audited
53
+ * every time somebody wonders whether it is safe. 200 is far above any
54
+ * plausible number of HSN codes and far below anything that costs.
55
+ */
56
+ async listAllBounded() {
57
+ try {
58
+ const snap = await this.getCollection().limit(200).get();
59
+ return snap.docs
60
+ .map((d) => this.mapDoc(d))
61
+ .sort((a, b) => a.label.localeCompare(b.label));
62
+ }
63
+ catch (error) {
64
+ void normalizeError(error);
65
+ throw new DatabaseError(failureMessage("Failed to list tax codes", error));
66
+ }
67
+ }
68
+ /**
69
+ * Resolve a code for derivation.
70
+ *
71
+ * 🛑 Returns `null` for an INACTIVE code as well as a missing one, and the
72
+ * caller must treat both the same way: leave the product's `gstRate` and
73
+ * `hsnCode` alone rather than writing a zero. Deactivating a code must not
74
+ * silently re-rate the next listing to 0% — that is indistinguishable from a
75
+ * deliberate exemption, which is the whole reason `gstRate` distinguishes
76
+ * `undefined` from `0`.
77
+ */
78
+ async findResolvable(id) {
79
+ try {
80
+ const snap = await this.getCollection().doc(id).get();
81
+ if (!snap.exists)
82
+ return null;
83
+ const doc = this.mapDoc(snap);
84
+ return doc.isActive ? doc : null;
85
+ }
86
+ catch (error) {
87
+ void normalizeError(error);
88
+ throw new DatabaseError(failureMessage("Failed to read tax code", error));
89
+ }
90
+ }
91
+ /**
92
+ * Create with a DERIVED id, so the same HSN cannot exist twice.
93
+ *
94
+ * Two rows for one HSN is the failure worth preventing structurally: an
95
+ * admin edits one, categories point at the other, and the rate that reaches
96
+ * an invoice is whichever the data happens to reference. A 409 here is the
97
+ * right answer — the request is well-formed and the world already has it.
98
+ */
99
+ async create(input) {
100
+ const hsn = input.hsnCode?.trim() ?? "";
101
+ if (hsn !== "" && !isValidHsnCode(hsn)) {
102
+ throw new ValidationError("An HSN code must be exactly 4, 6 or 8 digits.", { hsnCode: "Must be 4, 6 or 8 digits." });
103
+ }
104
+ if (hsn === "" && input.gstRate !== 0) {
105
+ throw new ValidationError("An HSN code is required for any rate above 0%.", { hsnCode: "Required unless the rate is 0%." });
106
+ }
107
+ const id = createTaxCodeId(hsn);
108
+ const existing = await this.getCollection().doc(id).get();
109
+ if (existing.exists) {
110
+ throw new ConflictError(`A tax code for HSN ${hsn || "(exempt)"} already exists. Edit it instead of creating a second one.`);
111
+ }
112
+ return this.createWithId(id, { ...input, hsnCode: hsn });
113
+ }
114
+ async update(id, data) {
115
+ /*
116
+ * 🛑 `hsnCode` is editable but the id is NOT re-derived from it. Changing
117
+ * the id would orphan every `CategoryProductDefaults.taxCodeId` pointing
118
+ * here — the mirror-drift shape of Root Cause #42, with an invoice at the
119
+ * end of it. To correct a wrong HSN: deactivate this row, create the right
120
+ * one, and repoint the categories.
121
+ */
122
+ if (data.hsnCode !== undefined) {
123
+ const hsn = data.hsnCode.trim();
124
+ if (hsn !== "" && !isValidHsnCode(hsn)) {
125
+ throw new ValidationError("An HSN code must be exactly 4, 6 or 8 digits.", { hsnCode: "Must be 4, 6 or 8 digits." });
126
+ }
127
+ }
128
+ return super.update(id, data);
129
+ }
130
+ }
131
+ export const taxCodesRepository = new TaxCodesRepository();
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Tax codes — Firestore schema
3
+ *
4
+ * An HSN code and its GST rate, as an ENTITY rather than two fields repeated
5
+ * across every category. Changing a rate becomes one edit instead of ~330.
6
+ *
7
+ * ## 🛑 The reference is for AUTHORING. The scalars stay for CORRECTNESS.
8
+ *
9
+ * `CategoryProductDefaults.taxCodeId` points here, and `deriveTaxonomy`
10
+ * resolves it to `ProductDocument.gstRate` / `.hsnCode` **on the product**;
11
+ * the order item then snapshots from the product, exactly as it already does.
12
+ *
13
+ * That layering is not redundancy. Without it, editing a tax code would
14
+ * retroactively change the GST on orders already invoiced — a compliance
15
+ * problem, not a modelling preference. It is the same reason
16
+ * `OrderDocumentItem` already snapshots `hsnCode`/`gstRate`.
17
+ *
18
+ * a category edit -> changes future listings
19
+ * a tax-code edit -> changes future derivations
20
+ * nothing -> rewrites a past sale
21
+ *
22
+ * ## Why this exists at all
23
+ *
24
+ * GST has never been charged on goods here. Recorded in our own source
25
+ * (`_internal/server/features/checkout/actions.ts`): *"0 of 40 live orders had
26
+ * a `gstAmount`, and the order page's Tax row had never rendered."* And
27
+ * `gstRate` is set on zero of the current products.
28
+ *
29
+ * id === slug, prefix "tax-".
30
+ */
31
+ import type { BaseDocument } from "../../../_internal/shared/types/base-document";
32
+ export declare const TAX_CODES_COLLECTION: "taxCodes";
33
+ export declare const TAX_CODE_PREFIX: "tax-";
34
+ /**
35
+ * The GST slabs. A closed union because a rate outside it is a data error, not
36
+ * a new option — and because `ProductDocument.gstRate` already declares this
37
+ * exact set, so a wider type here would not survive the derivation.
38
+ */
39
+ export type GstRate = 0 | 5 | 12 | 18 | 28;
40
+ export declare const GST_RATE_VALUES: readonly [0, 5, 12, 18, 28];
41
+ /**
42
+ * HSN chapters this catalogue touches.
43
+ *
44
+ * 🛑 Only **9503** is verified. 4911 (printed cards/stickers/posters) and 9504
45
+ * (playing cards, board games) are the correct chapters for that merchandise
46
+ * and their CURRENT RATES ARE NOT CONFIRMED — the toys rates themselves moved
47
+ * in the 2025 revision. Verify before seeding a rate against either, because a
48
+ * wrong HSN on a real invoice is worse than an absent one.
49
+ */
50
+ export type HsnChapter = "9503" | "4911" | "9504" | "exempt";
51
+ export interface TaxCodeDocument extends BaseDocument {
52
+ /** 4, 6 or 8 digits. Empty for the exemption row, which has no HSN. */
53
+ hsnCode: string;
54
+ /** Picker label, e.g. "Toys, non-electronic (HSN 9503 0020) — 5%". */
55
+ label: string;
56
+ description?: string;
57
+ gstRate: GstRate;
58
+ /** Groups the picker. ~10 rows today, more once 4911/9504 are verified. */
59
+ chapter?: HsnChapter;
60
+ /**
61
+ * When the CURRENT rate took effect. Informational — it does NOT rewrite
62
+ * history, because history lives in the per-product and per-order-item
63
+ * snapshots. Recorded so an admin can tell a rate that has always been 5%
64
+ * from one that changed.
65
+ */
66
+ effectiveFrom?: Date;
67
+ isActive: boolean;
68
+ /** Why this rate, and what it covers — the thing a future admin will want. */
69
+ notes?: string;
70
+ }
71
+ export type TaxCodeCreateInput = Omit<TaxCodeDocument, "id" | "createdAt" | "updatedAt">;
72
+ export declare const TAX_CODE_UPDATABLE_FIELDS: readonly ["hsnCode", "label", "description", "gstRate", "chapter", "effectiveFrom", "isActive", "notes"];
73
+ export type TaxCodeUpdateInput = Partial<Pick<TaxCodeDocument, (typeof TAX_CODE_UPDATABLE_FIELDS)[number]>>;
74
+ export declare const TAX_CODE_FIELDS: {
75
+ readonly ID: "id";
76
+ readonly HSN_CODE: "hsnCode";
77
+ readonly LABEL: "label";
78
+ readonly GST_RATE: "gstRate";
79
+ readonly CHAPTER: "chapter";
80
+ readonly IS_ACTIVE: "isActive";
81
+ readonly EFFECTIVE_FROM: "effectiveFrom";
82
+ readonly CREATED_AT: "createdAt";
83
+ readonly UPDATED_AT: "updatedAt";
84
+ };
85
+ export declare const TAX_CODE_SIEVE_FIELDS: {
86
+ readonly label: {
87
+ readonly canFilter: true;
88
+ readonly canSort: true;
89
+ };
90
+ readonly hsnCode: {
91
+ readonly canFilter: true;
92
+ readonly canSort: true;
93
+ };
94
+ readonly gstRate: {
95
+ readonly canFilter: true;
96
+ readonly canSort: true;
97
+ };
98
+ readonly chapter: {
99
+ readonly canFilter: true;
100
+ readonly canSort: false;
101
+ };
102
+ readonly isActive: {
103
+ readonly canFilter: true;
104
+ readonly canSort: false;
105
+ };
106
+ readonly createdAt: {
107
+ readonly canFilter: false;
108
+ readonly canSort: true;
109
+ };
110
+ };
111
+ /**
112
+ * Derive the document id from the HSN code.
113
+ *
114
+ * Deterministic, so a seed run is idempotent (Root Cause #25) and so the same
115
+ * HSN cannot be entered twice under two ids — which would give two rows one
116
+ * admin edits and the other serves.
117
+ */
118
+ export declare function createTaxCodeId(hsnCode: string): string;
119
+ /**
120
+ * India's HSN codes are 4, 6 or 8 digits. Checked rather than trusted because
121
+ * the inline-create path in the seller form reaches the same route as the
122
+ * admin editor, and a malformed code propagates onto every product derived
123
+ * from the category that points at it.
124
+ */
125
+ export declare function isValidHsnCode(hsnCode: string): boolean;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Tax codes — Firestore schema
3
+ *
4
+ * An HSN code and its GST rate, as an ENTITY rather than two fields repeated
5
+ * across every category. Changing a rate becomes one edit instead of ~330.
6
+ *
7
+ * ## 🛑 The reference is for AUTHORING. The scalars stay for CORRECTNESS.
8
+ *
9
+ * `CategoryProductDefaults.taxCodeId` points here, and `deriveTaxonomy`
10
+ * resolves it to `ProductDocument.gstRate` / `.hsnCode` **on the product**;
11
+ * the order item then snapshots from the product, exactly as it already does.
12
+ *
13
+ * That layering is not redundancy. Without it, editing a tax code would
14
+ * retroactively change the GST on orders already invoiced — a compliance
15
+ * problem, not a modelling preference. It is the same reason
16
+ * `OrderDocumentItem` already snapshots `hsnCode`/`gstRate`.
17
+ *
18
+ * a category edit -> changes future listings
19
+ * a tax-code edit -> changes future derivations
20
+ * nothing -> rewrites a past sale
21
+ *
22
+ * ## Why this exists at all
23
+ *
24
+ * GST has never been charged on goods here. Recorded in our own source
25
+ * (`_internal/server/features/checkout/actions.ts`): *"0 of 40 live orders had
26
+ * a `gstAmount`, and the order page's Tax row had never rendered."* And
27
+ * `gstRate` is set on zero of the current products.
28
+ *
29
+ * id === slug, prefix "tax-".
30
+ */
31
+ export const TAX_CODES_COLLECTION = "taxCodes";
32
+ export const TAX_CODE_PREFIX = "tax-";
33
+ export const GST_RATE_VALUES = [0, 5, 12, 18, 28];
34
+ export const TAX_CODE_UPDATABLE_FIELDS = [
35
+ "hsnCode",
36
+ "label",
37
+ "description",
38
+ "gstRate",
39
+ "chapter",
40
+ "effectiveFrom",
41
+ "isActive",
42
+ "notes",
43
+ ];
44
+ export const TAX_CODE_FIELDS = {
45
+ ID: "id",
46
+ HSN_CODE: "hsnCode",
47
+ LABEL: "label",
48
+ GST_RATE: "gstRate",
49
+ CHAPTER: "chapter",
50
+ IS_ACTIVE: "isActive",
51
+ EFFECTIVE_FROM: "effectiveFrom",
52
+ CREATED_AT: "createdAt",
53
+ UPDATED_AT: "updatedAt",
54
+ };
55
+ export const TAX_CODE_SIEVE_FIELDS = {
56
+ label: { canFilter: true, canSort: true },
57
+ hsnCode: { canFilter: true, canSort: true },
58
+ gstRate: { canFilter: true, canSort: true },
59
+ chapter: { canFilter: true, canSort: false },
60
+ isActive: { canFilter: true, canSort: false },
61
+ createdAt: { canFilter: false, canSort: true },
62
+ };
63
+ /**
64
+ * Derive the document id from the HSN code.
65
+ *
66
+ * Deterministic, so a seed run is idempotent (Root Cause #25) and so the same
67
+ * HSN cannot be entered twice under two ids — which would give two rows one
68
+ * admin edits and the other serves.
69
+ */
70
+ export function createTaxCodeId(hsnCode) {
71
+ const digits = hsnCode.replace(/\D/g, "");
72
+ return digits ? `${TAX_CODE_PREFIX}hsn-${digits}` : `${TAX_CODE_PREFIX}exempt-0`;
73
+ }
74
+ /**
75
+ * India's HSN codes are 4, 6 or 8 digits. Checked rather than trusted because
76
+ * the inline-create path in the seller form reaches the same route as the
77
+ * admin editor, and a malformed code propagates onto every product derived
78
+ * from the category that points at it.
79
+ */
80
+ export function isValidHsnCode(hsnCode) {
81
+ return /^(\d{4}|\d{6}|\d{8})$/.test(hsnCode.trim());
82
+ }
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ /** The GST slabs, as the rate select offers them. */
3
+ export declare const GST_RATE_OPTIONS: {
4
+ value: string;
5
+ label: string;
6
+ }[];
7
+ /**
8
+ * Chapter options.
9
+ *
10
+ * 🛑 The two non-9503 chapters are offered because the merchandise exists
11
+ * (printed cards and stickers; playing cards), and their labels say the rate is
12
+ * unverified rather than implying one. Picking a chapter does not set a rate.
13
+ */
14
+ export declare const HSN_CHAPTER_OPTIONS: {
15
+ value: string;
16
+ label: string;
17
+ }[];
18
+ /**
19
+ * 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
20
+ * by schema instance and every zod wrapper returns a new one.
21
+ */
22
+ export declare const taxCodeFormSchema: z.ZodEffects<z.ZodObject<{
23
+ label: z.ZodString;
24
+ hsnCode: z.ZodOptional<z.ZodString>;
25
+ gstRate: z.ZodEffects<z.ZodNumber, number, number>;
26
+ chapter: z.ZodOptional<z.ZodEnum<["9503", "4911", "9504", "exempt"]>>;
27
+ description: z.ZodOptional<z.ZodString>;
28
+ notes: z.ZodOptional<z.ZodString>;
29
+ isActive: z.ZodBoolean;
30
+ }, "strip", z.ZodTypeAny, {
31
+ label: string;
32
+ isActive: boolean;
33
+ gstRate: number;
34
+ description?: string | undefined;
35
+ notes?: string | undefined;
36
+ hsnCode?: string | undefined;
37
+ chapter?: "9503" | "4911" | "9504" | "exempt" | undefined;
38
+ }, {
39
+ label: string;
40
+ isActive: boolean;
41
+ gstRate: number;
42
+ description?: string | undefined;
43
+ notes?: string | undefined;
44
+ hsnCode?: string | undefined;
45
+ chapter?: "9503" | "4911" | "9504" | "exempt" | undefined;
46
+ }>, {
47
+ label: string;
48
+ isActive: boolean;
49
+ gstRate: number;
50
+ description?: string | undefined;
51
+ notes?: string | undefined;
52
+ hsnCode?: string | undefined;
53
+ chapter?: "9503" | "4911" | "9504" | "exempt" | undefined;
54
+ }, {
55
+ label: string;
56
+ isActive: boolean;
57
+ gstRate: number;
58
+ description?: string | undefined;
59
+ notes?: string | undefined;
60
+ hsnCode?: string | undefined;
61
+ chapter?: "9503" | "4911" | "9504" | "exempt" | undefined;
62
+ }>;
63
+ export type TaxCodeFormValues = z.infer<typeof taxCodeFormSchema>;
@@ -0,0 +1,152 @@
1
+ /*
2
+ * WHAT: The tax-code editor's schema — the field set, annotated for
3
+ * `buildSectionsFromSchema`, with the HSN-format rule as a refinement
4
+ * rather than a hint under the input.
5
+ *
6
+ * ## 🛑 Why the HSN rule is a refinement and not placeholder text
7
+ *
8
+ * A malformed HSN does not fail loudly. It propagates: a category points at
9
+ * the tax code, `deriveTaxonomy` writes `hsnCode` onto every product derived
10
+ * from that category, and the order item snapshots it onto a GST invoice.
11
+ * Nothing in that chain validates the string again, and the invoice is the
12
+ * first place a human might notice — by which point the sale is recorded.
13
+ *
14
+ * So the format is checked at the one place it enters the system. The same
15
+ * route serves the inline-create path in the seller form, which is the busiest
16
+ * surface and the one most likely to be used in a hurry.
17
+ *
18
+ * ## The exemption row is the reason `hsnCode` is not simply required
19
+ *
20
+ * A 0% row (live plants, live animals) has no HSN. Requiring one would force
21
+ * an author to invent a code to express an exemption, and an invented HSN on
22
+ * an invoice is exactly the failure above. So the refinement is conditional:
23
+ * blank is legal only at rate 0.
24
+ *
25
+ * 🛑 The two option arrays are exported for the VIEW to pass as
26
+ * `options: { gstRate: GST_RATE_OPTIONS, chapter: HSN_CHAPTER_OPTIONS }` —
27
+ * `FieldUiMeta` has no `options` key, and `annotate` would silently accept
28
+ * neither. That split follows `AdminBundleEditorView`, which supplies
29
+ * `BUNDLE_RULE_TYPE_OPTIONS` the same way: a select's VALUES are schema, its
30
+ * option list is often runtime data (brands, categories) and so belongs to
31
+ * whoever can fetch it.
32
+ *
33
+ * EXPORTS:
34
+ * taxCodeFormSchema, type TaxCodeFormValues, GST_RATE_OPTIONS,
35
+ * HSN_CHAPTER_OPTIONS
36
+ *
37
+ * @tag domain:tax-codes
38
+ * @tag layer:schema
39
+ * @tag pattern:none
40
+ * @tag access:isomorphic
41
+ * @tag consumers:AdminTaxCodeEditorView
42
+ * @tag sideEffects:none
43
+ */
44
+ import { z } from "zod";
45
+ import { annotate } from "../../shell/field-ui-meta";
46
+ import { GST_RATE_VALUES, isValidHsnCode } from "./firestore";
47
+ /** The GST slabs, as the rate select offers them. */
48
+ export const GST_RATE_OPTIONS = GST_RATE_VALUES.map((r) => ({
49
+ value: String(r),
50
+ label: r === 0 ? "0% — exempt" : `${r}%`,
51
+ }));
52
+ /**
53
+ * Chapter options.
54
+ *
55
+ * 🛑 The two non-9503 chapters are offered because the merchandise exists
56
+ * (printed cards and stickers; playing cards), and their labels say the rate is
57
+ * unverified rather than implying one. Picking a chapter does not set a rate.
58
+ */
59
+ export const HSN_CHAPTER_OPTIONS = [
60
+ { value: "9503", label: "9503 — Toys (verified)" },
61
+ { value: "4911", label: "4911 — Printed matter: cards, stickers, posters (rate unverified)" },
62
+ { value: "9504", label: "9504 — Playing cards, board games (rate unverified)" },
63
+ { value: "exempt", label: "Exempt — no HSN" },
64
+ ];
65
+ /**
66
+ * 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
67
+ * by schema instance and every zod wrapper returns a new one.
68
+ */
69
+ export const taxCodeFormSchema = z
70
+ .object({
71
+ label: annotate(z
72
+ .string()
73
+ .min(1, "Give the code a label an admin will recognise in a picker.")
74
+ .max(150, "Keep the label under 150 characters."), {
75
+ section: "basics",
76
+ sectionLabel: "Tax code",
77
+ sectionRequired: true,
78
+ quick: true,
79
+ order: 1,
80
+ label: "Label",
81
+ help: 'What the picker shows, e.g. "Toys, non-electronic (HSN 9503 0020) — 5%".',
82
+ }),
83
+ /*
84
+ * A STRING, not a number: leading zeros are significant in an HSN code
85
+ * (`04` is not `4`) and a numeric input would eat them. Digits-only is
86
+ * enforced by the refinement below, against the real 4/6/8-digit rule.
87
+ */
88
+ hsnCode: annotate(z.string().trim().max(8, "An HSN code is at most 8 digits.").optional(), {
89
+ section: "basics",
90
+ quick: true,
91
+ order: 2,
92
+ row: "pair",
93
+ label: "HSN code",
94
+ help: "4, 6 or 8 digits. Leave blank only for a 0% exemption.",
95
+ }),
96
+ gstRate: annotate(z.coerce
97
+ .number()
98
+ .refine((v) => GST_RATE_VALUES.includes(v), {
99
+ message: "Pick one of the GST slabs: 0, 5, 12, 18 or 28.",
100
+ }), {
101
+ section: "basics",
102
+ sectionRequired: true,
103
+ quick: true,
104
+ order: 3,
105
+ row: "pair",
106
+ kind: "select",
107
+ label: "GST rate",
108
+ }),
109
+ chapter: annotate(z.enum(["9503", "4911", "9504", "exempt"]).optional(), {
110
+ section: "basics",
111
+ order: 4,
112
+ kind: "select",
113
+ label: "Chapter",
114
+ help: "Groups the picker. Only chapter 9503's rates are verified.",
115
+ }),
116
+ description: annotate(z.string().max(500).optional(), { section: "detail", sectionLabel: "Detail", order: 1, kind: "textarea", label: "Description" }),
117
+ notes: annotate(z.string().max(1000).optional(), {
118
+ section: "detail",
119
+ order: 2,
120
+ kind: "textarea",
121
+ label: "Notes",
122
+ help: "Why this rate, and what it covers. The thing a future admin will want.",
123
+ }),
124
+ isActive: annotate(z.boolean(), {
125
+ section: "detail",
126
+ order: 3,
127
+ label: "Active",
128
+ help: "An inactive code is never resolved onto a product, and disappears from the picker.",
129
+ }),
130
+ })
131
+ .superRefine((values, ctx) => {
132
+ const hsn = values.hsnCode?.trim() ?? "";
133
+ // Blank is legal ONLY at 0%. See the header: forcing an HSN on an exemption
134
+ // is what gets a code invented, and an invented code reaches an invoice.
135
+ if (hsn === "") {
136
+ if (values.gstRate !== 0) {
137
+ ctx.addIssue({
138
+ code: z.ZodIssueCode.custom,
139
+ path: ["hsnCode"],
140
+ message: "An HSN code is required for any rate above 0%. Leave it blank only to record an exemption.",
141
+ });
142
+ }
143
+ return;
144
+ }
145
+ if (!isValidHsnCode(hsn)) {
146
+ ctx.addIssue({
147
+ code: z.ZodIssueCode.custom,
148
+ path: ["hsnCode"],
149
+ message: "An HSN code must be exactly 4, 6 or 8 digits — no spaces or dots.",
150
+ });
151
+ }
152
+ });
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Zod validators for the `/api/admin/tax-codes` route handlers.
3
+ *
4
+ * Separate from `tax-code-form.ts`, which is the EDITOR's schema: that one is
5
+ * annotated for `buildSectionsFromSchema`, takes strings from inputs, and
6
+ * coerces. These are the wire contracts, and they are `.strict()`.
7
+ *
8
+ * ## 🛑 `.strict()`, not `z.object()` and not `.passthrough()`
9
+ *
10
+ * `z.object()` STRIPS unknown keys, which is how a PATCH returns 200 having
11
+ * written nothing (Root Cause #40's shape, and the admin grouped-listings
12
+ * route's documented bug). `.passthrough()` writes whatever arrives, which is
13
+ * how a form overwrote booked lottery slots. `.strict()` 400s on a field the
14
+ * contract does not name — the caller is told, and nothing silently vanishes.
15
+ *
16
+ * ## The id is NOT accepted on update
17
+ *
18
+ * `createTaxCodeId` derives the id from the HSN code, so accepting an id (or
19
+ * re-deriving one on update) would let an edit orphan every
20
+ * `CategoryProductDefaults.taxCodeId` pointing at the old one — with a GST
21
+ * invoice at the end of that chain. To correct a wrong HSN: deactivate, create
22
+ * the right row, repoint the categories.
23
+ */
24
+ import { z } from "zod";
25
+ /**
26
+ * 🛑 `z.custom<GstRate>`, not `z.number().refine(...)`.
27
+ *
28
+ * A `.refine()` validates at runtime and does NOT narrow the inferred type —
29
+ * the payload still types as `number`, so `repository.create(body)` fails to
30
+ * compile against `gstRate: GstRate`, and the obvious unblock is a cast. A
31
+ * cast at a request boundary is where this codebase's worst bugs live (an
32
+ * `ActionResult` envelope spread as a product, a raw `StoreDocument` published
33
+ * as HTML), so the type is narrowed here instead.
34
+ *
35
+ * And `z.custom` rather than a hand-written `z.union([z.literal(0), …])`
36
+ * because the slab list then has ONE definition — `GST_RATE_VALUES`, which
37
+ * `ProductDocument.gstRate` and the form's option list also derive from. A
38
+ * literal union here would be a second enumeration of the same union, which is
39
+ * Root Cause #61's shape.
40
+ */
41
+ export declare const GST_RATE_ENUM: z.ZodType<0 | 5 | 12 | 18 | 28, z.ZodTypeDef, 0 | 5 | 12 | 18 | 28>;
42
+ export declare const HSN_CHAPTER_ENUM: z.ZodEnum<["9503", "4911", "9504", "exempt"]>;
43
+ /**
44
+ * 🛑 `hsnCode` is OPTIONAL here and the blank-only-at-0% rule lives in the
45
+ * repository, not this schema. Deliberate: the same rule has to hold for the
46
+ * seed path and any future importer, neither of which goes through a Zod
47
+ * request schema. A rule enforced only at the HTTP edge is a rule with a side
48
+ * door.
49
+ */
50
+ export declare const taxCodeCreateSchema: z.ZodObject<{
51
+ hsnCode: z.ZodDefault<z.ZodString>;
52
+ label: z.ZodString;
53
+ description: z.ZodOptional<z.ZodString>;
54
+ gstRate: z.ZodType<0 | 5 | 12 | 18 | 28, z.ZodTypeDef, 0 | 5 | 12 | 18 | 28>;
55
+ chapter: z.ZodOptional<z.ZodEnum<["9503", "4911", "9504", "exempt"]>>;
56
+ effectiveFrom: z.ZodOptional<z.ZodDate>;
57
+ isActive: z.ZodDefault<z.ZodBoolean>;
58
+ notes: z.ZodOptional<z.ZodString>;
59
+ }, "strict", z.ZodTypeAny, {
60
+ label: string;
61
+ isActive: boolean;
62
+ gstRate: 0 | 5 | 12 | 18 | 28;
63
+ hsnCode: string;
64
+ description?: string | undefined;
65
+ notes?: string | undefined;
66
+ chapter?: "9503" | "4911" | "9504" | "exempt" | undefined;
67
+ effectiveFrom?: Date | undefined;
68
+ }, {
69
+ label: string;
70
+ gstRate: 0 | 5 | 12 | 18 | 28;
71
+ description?: string | undefined;
72
+ isActive?: boolean | undefined;
73
+ notes?: string | undefined;
74
+ hsnCode?: string | undefined;
75
+ chapter?: "9503" | "4911" | "9504" | "exempt" | undefined;
76
+ effectiveFrom?: Date | undefined;
77
+ }>;
78
+ export type TaxCodeCreatePayload = z.infer<typeof taxCodeCreateSchema>;
79
+ export declare const taxCodeUpdateSchema: z.ZodObject<{
80
+ hsnCode: z.ZodOptional<z.ZodString>;
81
+ label: z.ZodOptional<z.ZodString>;
82
+ description: z.ZodOptional<z.ZodString>;
83
+ gstRate: z.ZodOptional<z.ZodType<0 | 5 | 12 | 18 | 28, z.ZodTypeDef, 0 | 5 | 12 | 18 | 28>>;
84
+ chapter: z.ZodOptional<z.ZodEnum<["9503", "4911", "9504", "exempt"]>>;
85
+ effectiveFrom: z.ZodOptional<z.ZodDate>;
86
+ isActive: z.ZodOptional<z.ZodBoolean>;
87
+ notes: z.ZodOptional<z.ZodString>;
88
+ }, "strict", z.ZodTypeAny, {
89
+ label?: string | undefined;
90
+ description?: string | undefined;
91
+ isActive?: boolean | undefined;
92
+ notes?: string | undefined;
93
+ gstRate?: 0 | 5 | 12 | 18 | 28 | undefined;
94
+ hsnCode?: string | undefined;
95
+ chapter?: "9503" | "4911" | "9504" | "exempt" | undefined;
96
+ effectiveFrom?: Date | undefined;
97
+ }, {
98
+ label?: string | undefined;
99
+ description?: string | undefined;
100
+ isActive?: boolean | undefined;
101
+ notes?: string | undefined;
102
+ gstRate?: 0 | 5 | 12 | 18 | 28 | undefined;
103
+ hsnCode?: string | undefined;
104
+ chapter?: "9503" | "4911" | "9504" | "exempt" | undefined;
105
+ effectiveFrom?: Date | undefined;
106
+ }>;
107
+ export type TaxCodeUpdatePayload = z.infer<typeof taxCodeUpdateSchema>;