@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
@@ -1,8 +1,10 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { formatPhysicalLocation } from "../../../utils/physical-location";
3
4
  import { normalizeError } from "../../../errors/normalize";
4
5
  import { toUserMessage } from "../../../errors/error-display-map";
5
6
  import { SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit/client";
7
+ import { LOW_STOCK_THRESHOLD } from "../../store-extensions/constants/alert-metrics";
6
8
  import { sortBy } from "@mohasinac/appkit/client";
7
9
  import { useState, useCallback } from "react";
8
10
  import { useEntityDelete } from "../../../react/hooks/useEntityDelete";
@@ -39,7 +41,55 @@ const FILTER_KEYS = [
39
41
  "tags",
40
42
  "badges",
41
43
  "listingType",
44
+ /*
45
+ * The stock quick-filter. `FILTER_KEYS` had no stock key at all — stock
46
+ * appeared only inside a row's subtitle string, so there was no way to ask
47
+ * "what needs restocking" short of reading every row.
48
+ *
49
+ * Registered here (not in `buildExtraParams`) because it IS a drawer facet:
50
+ * it narrows the list the way `condition` does, so it should inflate the
51
+ * filter badge and be cleared by the drawer's Clear button. That is the
52
+ * opposite call from the availability SCOPE, which is a tab and deliberately
53
+ * stays out of `filterKeys` so a tab selection cannot be silently reset by
54
+ * Clear.
55
+ */
56
+ "stockState",
42
57
  ];
58
+ /**
59
+ * 🛑 Applied IN MEMORY, never as a Firestore clause.
60
+ *
61
+ * `availableQuantity <= N` is an inequality, and Firestore appends an
62
+ * inequality's field to the `orderBy` implicitly — so pairing one with the
63
+ * default `-createdAt` sort demands a composite index nobody declares, and the
64
+ * `FAILED_PRECONDITION` gets swallowed into an empty page (Root Cause #59).
65
+ * Same reasoning as the alert evaluator's `low_stock` branch.
66
+ *
67
+ * The honest cost is that it filters the PAGE rather than the collection: a
68
+ * seller on page 1 of 200 listings sees the low-stock rows from those 50. That
69
+ * is stated in the facet's own label rather than left for them to infer.
70
+ */
71
+ const STOCK_STATE_OPTIONS = [
72
+ { value: "", label: "Any stock level" },
73
+ { value: "low", label: `Low — ${LOW_STOCK_THRESHOLD} or fewer left` },
74
+ { value: "out", label: "Out of stock" },
75
+ { value: "in", label: "In stock" },
76
+ ];
77
+ function matchesStockState(stockValue, state) {
78
+ if (!state)
79
+ return true;
80
+ // 🛑 An unknown quantity matches NOTHING rather than everything. A listing
81
+ // whose stock the API did not report is not evidence of being low, and
82
+ // including it would put rows a seller cannot act on into a worklist.
83
+ if (stockValue == null)
84
+ return false;
85
+ if (state === "out")
86
+ return stockValue <= 0;
87
+ if (state === "low")
88
+ return stockValue > 0 && stockValue <= LOW_STOCK_THRESHOLD;
89
+ if (state === "in")
90
+ return stockValue > LOW_STOCK_THRESHOLD;
91
+ return true;
92
+ }
43
93
  const DEFAULT_SORT = "-createdAt";
44
94
  const SORT_OPTIONS = [
45
95
  { value: sortBy("createdAt", "DESC"), label: "Newest" },
@@ -86,6 +136,43 @@ const PRODUCT_COLUMNS = [
86
136
  className: "w-28 text-right",
87
137
  render: (row) => (_jsx(Span, { size: "sm", weight: "medium", className: "text-[var(--appkit-color-text)]", children: row.price })),
88
138
  },
139
+ /*
140
+ * 🛑 `row.stock` was COMPUTED in `mapRows` and no column rendered it.
141
+ *
142
+ * Data fetched and discarded — Root Cause #52's shape — and the reason it
143
+ * stayed invisible is that the figure also appears inside the row's
144
+ * `secondary` string, so the information was technically on screen while
145
+ * being unsortable, unfilterable and impossible to scan down a column.
146
+ *
147
+ * Both stock fields have been `canFilter`/`canSort` at the repository layer
148
+ * the whole time; only the view omitted them.
149
+ */
150
+ {
151
+ key: "stock",
152
+ header: "Stock",
153
+ className: "w-24 text-right",
154
+ render: (row) => {
155
+ if (row.stockValue == null)
156
+ return _jsx(Span, { size: "xs", color: "faint", children: "\u2014" });
157
+ /*
158
+ * Three states, not two, and the middle one is the point of the column.
159
+ * `LOW_STOCK_THRESHOLD` is imported rather than re-picked here: the
160
+ * alert, this column and the dashboard tile must agree on what "low"
161
+ * means, or a seller gets a warning from one and a clean bill from
162
+ * another.
163
+ */
164
+ /*
165
+ * Three states, and `Span` has no `warning` variant — so "low" is
166
+ * carried by WEIGHT plus a suffix rather than by colour alone. That is
167
+ * better anyway: `audit-a11y` records that white-on-warning fails
168
+ * contrast in this theme set, and a status conveyed only by colour is
169
+ * invisible to a colour-blind seller.
170
+ */
171
+ const isOut = row.stockValue <= 0;
172
+ const isLow = !isOut && row.stockValue <= LOW_STOCK_THRESHOLD;
173
+ return (_jsxs(Span, { size: "sm", weight: isOut || isLow ? "semibold" : "normal", color: isOut ? "error" : isLow ? "primary" : "muted", children: [row.stockValue, isOut ? " · out" : isLow ? " · low" : ""] }));
174
+ },
175
+ },
89
176
  {
90
177
  key: "status",
91
178
  header: "Status",
@@ -109,7 +196,13 @@ const PRODUCT_COLUMNS = [
109
196
  key: "physicalLocation",
110
197
  header: "Location",
111
198
  className: "w-28",
112
- render: (row) => row.physicalLocation ? (_jsxs(Span, { size: "xs", className: "font-mono text-[var(--appkit-color-text-muted)]", children: [row.physicalLocation.zone, "/", row.physicalLocation.shelf, "/", row.physicalLocation.bin] })) : (_jsx(Span, { size: "xs", color: "faint", children: "\u2014" })),
199
+ render: (row) => {
200
+ // One formatter, shared with SellerOrdersView and PrintCenterView. It
201
+ // also returns undefined for an all-blank location, which the bulk
202
+ // routes genuinely allow — this used to print a bare "//".
203
+ const loc = formatPhysicalLocation(row.physicalLocation);
204
+ return loc ? (_jsx(Span, { size: "xs", className: "font-mono text-[var(--appkit-color-text-muted)]", children: loc })) : (_jsx(Span, { size: "xs", color: "faint", children: "\u2014" }));
205
+ },
113
206
  },
114
207
  ];
115
208
  export function SellerProductsView({ onDeleteProduct, onCreateClick, }) {
@@ -288,7 +381,9 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, }) {
288
381
  * reserve and the current bid, both already in `auctionSecondary`, and
289
382
  * a "price" beside them would read as a third, competing figure.
290
383
  */
384
+ const availableRaw = typeof item.availableQuantity === "number" ? item.availableQuantity : null;
291
385
  const stockRaw = typeof item.stockQuantity === "number" ? item.stockQuantity : null;
386
+ const stockValue = availableRaw ?? stockRaw;
292
387
  const priceLabel = kind !== "auction" && priceRaw ? `₹${priceRaw.toLocaleString("en-IN")}` : "";
293
388
  const stockLabel = stockRaw === null ? "" : `${stockRaw} in stock`;
294
389
  const secondaryBase = auctionSecondary || toStringValue(item.condition, "");
@@ -303,13 +398,23 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, }) {
303
398
  listingKind: kind,
304
399
  price: priceRaw ? `₹${priceRaw.toLocaleString("en-IN")}` : "—",
305
400
  stock: stockLabel || "—",
401
+ stockValue,
306
402
  physicalLocation: item.physicalLocation &&
307
403
  typeof item.physicalLocation.zone === "string"
308
404
  ? item.physicalLocation
309
405
  : undefined,
310
406
  };
311
407
  });
312
- return rows.filter((r) => !deletedIds.has(r.id)).map((r) => statusOverrides.has(r.id) ? { ...r, status: statusOverrides.get(r.id) } : r);
408
+ /*
409
+ * The stock predicate runs HERE, in the same pass that already drops
410
+ * optimistically-deleted rows and applies status overrides — so there is
411
+ * one place a row can be removed from the rendered list rather than two
412
+ * that could disagree about which rows exist.
413
+ */
414
+ const stockState = sideTable.get("stockState") ?? "";
415
+ return rows
416
+ .filter((r) => !deletedIds.has(r.id) && matchesStockState(r.stockValue, stockState))
417
+ .map((r) => statusOverrides.has(r.id) ? { ...r, status: statusOverrides.get(r.id) } : r);
313
418
  },
314
419
  getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
315
420
  buildFilters: (state) => {
@@ -321,7 +426,7 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, }) {
321
426
  // same rows. The scope param carries the per-type meaning instead.
322
427
  return [statusFilter, kindFilter].filter(Boolean).join(",") || undefined;
323
428
  },
324
- renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(SellerProductsFilterFields, { pendingFilters: pendingFilters, statusOptions: STATUS_OPTIONS, onChange: setPendingFilters })),
429
+ renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(SellerProductsFilterFields, { pendingFilters: pendingFilters, statusOptions: STATUS_OPTIONS, stockStateOptions: STOCK_STATE_OPTIONS, onChange: setPendingFilters })),
325
430
  buildExtraParams: () => scope.extraParams,
326
431
  renderAboveContent: () => (_jsxs(Stack, { gap: "sm", children: [_jsx(TypeDropdown, { active: listingKind, onChange: handleKindChange }), scope.renderAboveContent()] })),
327
432
  buildBulkActions: (selection) => {
@@ -76,6 +76,23 @@ export interface SellerDashboardStats {
76
76
  totalRevenue: number;
77
77
  pendingPayouts: number;
78
78
  activeListings: number;
79
+ /**
80
+ * Published listings at or below `LOW_STOCK_THRESHOLD`, and at zero.
81
+ *
82
+ * 🛑 Two numbers rather than one "needs attention" count, because the two
83
+ * call for different actions: low means reorder, out means the listing
84
+ * cannot be bought right now. A combined figure hides which.
85
+ *
86
+ * Both from `availableQuantity`, not `stockQuantity` — purchasability gates
87
+ * on the first in three places, and reporting the second would tell a seller
88
+ * they have stock while buyers cannot buy.
89
+ *
90
+ * Optional because a store that does not exist yet returns the zeroed
91
+ * payload, and because an older client reading a newer field is a nicer
92
+ * failure than a required field a stale route omits.
93
+ */
94
+ lowStockListings?: number;
95
+ outOfStockListings?: number;
79
96
  totalOrders: number;
80
97
  pendingOrders: number;
81
98
  averageRating?: number;
@@ -49,6 +49,19 @@ import type { SellerProductDraft } from "../components/SellerProductShell";
49
49
  export interface StoredProductBlocks {
50
50
  finalSale?: boolean;
51
51
  returnPolicy?: string;
52
+ authenticity?: "original" | "reproduction" | "unverified";
53
+ specifications?: {
54
+ name: string;
55
+ value: string;
56
+ unit?: string;
57
+ }[];
58
+ features?: string[];
59
+ weightG?: number;
60
+ dimensionsCm?: {
61
+ l: number;
62
+ w: number;
63
+ h: number;
64
+ };
52
65
  classified?: {
53
66
  meetupArea?: {
54
67
  city?: string;
@@ -120,6 +120,23 @@ export function productToDraft(product) {
120
120
  */
121
121
  finalSale: product.finalSale,
122
122
  returnPolicy: product.returnPolicy,
123
+ /*
124
+ * The inherited-but-editable fields (C2c). Same category as `finalSale`:
125
+ * flat, stored, and absent from the ten hand-written `initialValues`
126
+ * literals — so the Details section would open EMPTY on a listing that
127
+ * already carries them, and a seller saving anything else would appear to
128
+ * have cleared their own specifications.
129
+ *
130
+ * (The write is partial, so an untouched save does not actually clear
131
+ * them. But a form that shows a listing's authenticity as unanswered when
132
+ * it is answered is telling the seller something untrue, which is exactly
133
+ * the argument `finalSale` above is making.)
134
+ */
135
+ authenticity: product.authenticity,
136
+ specifications: product.specifications,
137
+ features: product.features,
138
+ weightG: product.weightG,
139
+ dimensionsCm: product.dimensionsCm,
123
140
  classifiedCity: classified?.meetupArea?.city,
124
141
  classifiedLocality: classified?.meetupArea?.locality,
125
142
  classifiedPincode: classified?.meetupArea?.pincode,
@@ -0,0 +1,86 @@
1
+ /**
2
+ * ONE alert-metric vocabulary — C3a.
3
+ *
4
+ * ## The defect this closes
5
+ *
6
+ * `AnalyticsAlertDocument.metric` was `z.string()`, and the two places that
7
+ * produce one shared **ZERO tokens**:
8
+ *
9
+ * the seller UI offered daily_revenue · daily_orders · low_stock ·
10
+ * cancelled_orders · pending_payouts · avg_rating ·
11
+ * reviews_count (7, snake_case)
12
+ * the seed wrote min-stock · orders-window · api-error-rate ·
13
+ * reports-1h (4, kebab-case)
14
+ *
15
+ * Not a near-miss — no value in one list appears in the other. And because
16
+ * nothing validated the field, every seeded alert rendered its raw string in
17
+ * the seller's list (`METRIC_OPTIONS.find(...)?.label ?? alert.metric`) while
18
+ * every alert a seller created was equally unrecognised by anything else.
19
+ *
20
+ * It stayed invisible for one reason: **nothing read either value**. There was
21
+ * no evaluator anywhere — zero references across all 59 job files and
22
+ * `functions/` — so `lastTriggeredAt` was declared and written nowhere, and
23
+ * sellers configured alerts that could never fire.
24
+ *
25
+ * ## 🛑 The rule that keeps it closed
26
+ *
27
+ * **A metric is in this map only if the evaluator computes it.** Offering one
28
+ * it cannot is how the original dead promise was built — a dropdown is a
29
+ * commitment, and `low_stock` sitting in a picker with no evaluator behind it
30
+ * is worse than its absence, because the seller stops watching their stock.
31
+ *
32
+ * So `ALERT_METRICS` is a `Record<AlertMetric, AlertMetricDef>`: adding a
33
+ * union member without a definition is a compile error, and the evaluator's
34
+ * own switch is exhaustive over the same union, so adding a member without an
35
+ * implementation is a compile error too. Root Cause #61 — a `Record<Union, T>`
36
+ * is compile-safe where an array literal is not, and this vocabulary existed
37
+ * as two array literals.
38
+ */
39
+ /** Who may set an alert on this metric. */
40
+ export type AlertMetricScope = "seller" | "admin";
41
+ /** How to render the threshold the seller types. */
42
+ export type AlertMetricUnit = "count" | "currency" | "rating";
43
+ export interface AlertMetricDef {
44
+ label: string;
45
+ /** What the number means, so the form can suffix it honestly. */
46
+ unit: AlertMetricUnit;
47
+ /** Which portals may offer it. Seller metrics are evaluated per store. */
48
+ scopes: readonly AlertMetricScope[];
49
+ /**
50
+ * Plain-English statement of what is counted, shown as helper text.
51
+ *
52
+ * Carried because "Daily Orders" does not say whether a cancelled order
53
+ * counts, and an alert whose definition the seller has to guess at is an
54
+ * alert they will not trust when it fires.
55
+ */
56
+ describes: string;
57
+ }
58
+ export declare const ALERT_METRIC_VALUES: readonly ["low_stock", "out_of_stock", "orders_count", "revenue_total", "cancelled_orders", "pending_payout_total", "avg_rating", "reviews_count", "open_reports", "pending_moderation"];
59
+ export type AlertMetric = (typeof ALERT_METRIC_VALUES)[number];
60
+ /**
61
+ * 🛑 `Record<AlertMetric, …>`, not a partial and not an array. A union member
62
+ * with no definition does not compile, and the evaluator switches over the
63
+ * same union exhaustively — so the picker, the evaluator and the stored value
64
+ * cannot drift the way the two array literals did.
65
+ */
66
+ export declare const ALERT_METRICS: Record<AlertMetric, AlertMetricDef>;
67
+ /** Narrow an arbitrary stored string. */
68
+ export declare function isAlertMetric(value: unknown): value is AlertMetric;
69
+ /**
70
+ * The metrics one portal may offer, derived — never a second hand-written list.
71
+ *
72
+ * That is the whole defect this file closes: the seller picker WAS a
73
+ * hand-written list, and it agreed with nothing.
74
+ */
75
+ export declare function alertMetricsForScope(scope: AlertMetricScope): {
76
+ value: AlertMetric;
77
+ def: AlertMetricDef;
78
+ }[];
79
+ /**
80
+ * The stock level `low_stock` counts at or below.
81
+ *
82
+ * Exported so the alert, the products-list filter and the dashboard tile agree
83
+ * on what "low" means. Three surfaces each picking their own number is how a
84
+ * seller gets a warning from one and a clean bill from another.
85
+ */
86
+ export declare const LOW_STOCK_THRESHOLD = 5;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * ONE alert-metric vocabulary — C3a.
3
+ *
4
+ * ## The defect this closes
5
+ *
6
+ * `AnalyticsAlertDocument.metric` was `z.string()`, and the two places that
7
+ * produce one shared **ZERO tokens**:
8
+ *
9
+ * the seller UI offered daily_revenue · daily_orders · low_stock ·
10
+ * cancelled_orders · pending_payouts · avg_rating ·
11
+ * reviews_count (7, snake_case)
12
+ * the seed wrote min-stock · orders-window · api-error-rate ·
13
+ * reports-1h (4, kebab-case)
14
+ *
15
+ * Not a near-miss — no value in one list appears in the other. And because
16
+ * nothing validated the field, every seeded alert rendered its raw string in
17
+ * the seller's list (`METRIC_OPTIONS.find(...)?.label ?? alert.metric`) while
18
+ * every alert a seller created was equally unrecognised by anything else.
19
+ *
20
+ * It stayed invisible for one reason: **nothing read either value**. There was
21
+ * no evaluator anywhere — zero references across all 59 job files and
22
+ * `functions/` — so `lastTriggeredAt` was declared and written nowhere, and
23
+ * sellers configured alerts that could never fire.
24
+ *
25
+ * ## 🛑 The rule that keeps it closed
26
+ *
27
+ * **A metric is in this map only if the evaluator computes it.** Offering one
28
+ * it cannot is how the original dead promise was built — a dropdown is a
29
+ * commitment, and `low_stock` sitting in a picker with no evaluator behind it
30
+ * is worse than its absence, because the seller stops watching their stock.
31
+ *
32
+ * So `ALERT_METRICS` is a `Record<AlertMetric, AlertMetricDef>`: adding a
33
+ * union member without a definition is a compile error, and the evaluator's
34
+ * own switch is exhaustive over the same union, so adding a member without an
35
+ * implementation is a compile error too. Root Cause #61 — a `Record<Union, T>`
36
+ * is compile-safe where an array literal is not, and this vocabulary existed
37
+ * as two array literals.
38
+ */
39
+ export const ALERT_METRIC_VALUES = [
40
+ "low_stock",
41
+ "out_of_stock",
42
+ "orders_count",
43
+ "revenue_total",
44
+ "cancelled_orders",
45
+ "pending_payout_total",
46
+ "avg_rating",
47
+ "reviews_count",
48
+ "open_reports",
49
+ "pending_moderation",
50
+ ];
51
+ /**
52
+ * 🛑 `Record<AlertMetric, …>`, not a partial and not an array. A union member
53
+ * with no definition does not compile, and the evaluator switches over the
54
+ * same union exhaustively — so the picker, the evaluator and the stored value
55
+ * cannot drift the way the two array literals did.
56
+ */
57
+ export const ALERT_METRICS = {
58
+ low_stock: {
59
+ label: "Products low on stock",
60
+ unit: "count",
61
+ scopes: ["seller", "admin"],
62
+ describes: "How many of your published listings have available stock at or below 5. With 76–93% of competitor catalogues sold out, restock is the main lifecycle event.",
63
+ },
64
+ out_of_stock: {
65
+ label: "Products out of stock",
66
+ unit: "count",
67
+ scopes: ["seller", "admin"],
68
+ describes: "How many published listings have zero available stock and cannot be bought.",
69
+ },
70
+ orders_count: {
71
+ label: "Orders in the window",
72
+ unit: "count",
73
+ scopes: ["seller", "admin"],
74
+ describes: "Orders placed in the window, whatever their status. Set this to `== 0` to be told when nothing is selling.",
75
+ },
76
+ revenue_total: {
77
+ label: "Revenue in the window",
78
+ unit: "currency",
79
+ scopes: ["seller", "admin"],
80
+ describes: "Goods value of delivered, paid, unrefunded orders in the window — the same base a payout is computed from, not the grand total.",
81
+ },
82
+ cancelled_orders: {
83
+ label: "Cancelled orders in the window",
84
+ unit: "count",
85
+ scopes: ["seller", "admin"],
86
+ describes: "Orders cancelled in the window.",
87
+ },
88
+ pending_payout_total: {
89
+ label: "Pending payout amount",
90
+ unit: "currency",
91
+ scopes: ["seller", "admin"],
92
+ describes: "Total of payouts not yet marked paid. Not window-scoped — a backlog is a backlog.",
93
+ },
94
+ avg_rating: {
95
+ label: "Average rating",
96
+ unit: "rating",
97
+ scopes: ["seller"],
98
+ describes: "Mean of approved reviews across the whole store. Set this to `< 4` to hear about a slide early.",
99
+ },
100
+ reviews_count: {
101
+ label: "New reviews in the window",
102
+ unit: "count",
103
+ scopes: ["seller"],
104
+ describes: "Approved reviews published in the window.",
105
+ },
106
+ open_reports: {
107
+ label: "Open reports",
108
+ unit: "count",
109
+ scopes: ["admin"],
110
+ describes: "Reports awaiting moderation. Replaces the seeded `reports-1h`, which nothing read.",
111
+ },
112
+ pending_moderation: {
113
+ label: "Listings pending review",
114
+ unit: "count",
115
+ scopes: ["admin"],
116
+ describes: "Listings in `in_review`, waiting on a moderator.",
117
+ },
118
+ };
119
+ /** Narrow an arbitrary stored string. */
120
+ export function isAlertMetric(value) {
121
+ return typeof value === "string" && ALERT_METRIC_VALUES.includes(value);
122
+ }
123
+ /**
124
+ * The metrics one portal may offer, derived — never a second hand-written list.
125
+ *
126
+ * That is the whole defect this file closes: the seller picker WAS a
127
+ * hand-written list, and it agreed with nothing.
128
+ */
129
+ export function alertMetricsForScope(scope) {
130
+ return ALERT_METRIC_VALUES.filter((m) => ALERT_METRICS[m].scopes.includes(scope)).map((m) => ({
131
+ value: m,
132
+ def: ALERT_METRICS[m],
133
+ }));
134
+ }
135
+ /**
136
+ * The stock level `low_stock` counts at or below.
137
+ *
138
+ * Exported so the alert, the products-list filter and the dashboard tile agree
139
+ * on what "low" means. Three surfaces each picking their own number is how a
140
+ * seller gets a warning from one and a clean bill from another.
141
+ */
142
+ export const LOW_STOCK_THRESHOLD = 5;
@@ -1,3 +1,4 @@
1
+ import type { AlertMetric } from "../constants/alert-metrics";
1
2
  import type { FirestoreDocument } from "@mohasinac/appkit";
2
3
  import type { BaseDocument } from "../../../_internal/shared/types/base-document";
3
4
  /**
@@ -76,13 +77,30 @@ export interface AnalyticsAlertDocument extends BaseDocument {
76
77
  scope: AnalyticsScope;
77
78
  ownerId: string;
78
79
  label: string;
79
- metric: string;
80
+ /**
81
+ * 🛑 Was `string`, and that is what let the two producers share ZERO tokens:
82
+ * the seller picker offered seven snake_case values and the seed wrote four
83
+ * kebab-case ones, with no overlap and nothing to notice. Narrowed to the
84
+ * one vocabulary in `constants/alert-metrics.ts`, which the picker derives
85
+ * from and the evaluator switches over exhaustively.
86
+ */
87
+ metric: AlertMetric;
80
88
  operator: AlertOperator;
81
89
  threshold: number;
82
90
  windowHours: number;
83
91
  isActive: boolean;
84
92
  notifyChannels: Array<"in-app" | "email" | "whatsapp">;
93
+ /**
94
+ * When this alert last fired.
95
+ *
96
+ * Declared from the start and written NOWHERE until C3b, because there was
97
+ * no evaluator — which is also why nobody noticed the metric vocabulary was
98
+ * two disjoint lists. It is now the re-notify guard: an alert that is still
99
+ * true must not mail every run.
100
+ */
85
101
  lastTriggeredAt?: Date;
102
+ /** The value observed at `lastTriggeredAt`, so the message can quote it. */
103
+ lastObservedValue?: number;
86
104
  }
87
105
  export declare const ANALYTICS_ALERTS_COLLECTION: "analyticsAlerts";
88
106
  export declare const ANALYTICS_ALERT_INDEXED_FIELDS: readonly ["scope", "ownerId", "isActive", "metric", "createdAt"];
@@ -53,9 +53,9 @@ export interface StoreDocument extends BaseDocument {
53
53
  searchTxt?: string[];
54
54
  storeSlug: string;
55
55
  ownerId: string;
56
- /** Tester sandbox flag — set on the shared admin-seeded test store; swept by testerSandboxCleanup. */
56
+ /** Test-data flag — set on the shared admin-seeded test store; swept by testDataCleanup. */
57
57
  isTestData?: boolean;
58
- /** When isTestData, the cutoff after which testerSandboxCleanup deletes this doc. */
58
+ /** When isTestData, the cutoff after which testDataCleanup deletes this doc. */
59
59
  testDataExpiresAt?: Date;
60
60
  storeName: string;
61
61
  storeDescription?: string;
@@ -120,6 +120,35 @@ export interface StoreDocument extends BaseDocument {
120
120
  * true. Default false — sellers must explicitly opt in.
121
121
  */
122
122
  emiEnabled?: boolean;
123
+ /**
124
+ * How this seller THINKS about the prices they type. An authoring
125
+ * convenience, and nothing more.
126
+ *
127
+ * 🛑 **Every stored `price` in this system is tax-INCLUSIVE** — it is what
128
+ * the buyer pays, with GST inside it, remitted from it. No money path
129
+ * branches on this flag, and that is deliberate: a mode read in five places
130
+ * is five places to drift, which is the defect family C0/C0c spent its
131
+ * whole length removing.
132
+ *
133
+ * `true` (the default) — the seller types the buyer-facing price. ₹999 at 5%
134
+ * means the buyer pays ₹999 and the seller earns from a ₹951.43 taxable
135
+ * value.
136
+ *
137
+ * `false` — the seller types an EX-TAX price, and `ProductRepository`
138
+ * grosses it up **once, at write time**: ₹999 at 5% is stored as ₹1,048.95,
139
+ * so the buyer pays ₹1,048.95 and the seller earns from ₹999.00.
140
+ *
141
+ * Default `true`, which matters twice. It is the Indian B2C convention
142
+ * (and what worldhobbyshop asserts in its own JSON-LD via
143
+ * `valueAddedTaxIncluded: true`); and it means enabling GST changes no
144
+ * displayed price, where defaulting to `false` would raise every listing in
145
+ * the catalogue the moment the switch was flipped.
146
+ *
147
+ * 🛑 Flipping this does NOT re-price existing listings. The gross-up happens
148
+ * at write, so a seller who switches must re-save (or have re-derived) the
149
+ * products they want converted — see `ProductRepository`.
150
+ */
151
+ pricesIncludeGst?: boolean;
123
152
  whatsappConfig?: {
124
153
  /** Seller's WhatsApp Business phone number, digits-only with country code e.g. "919876543210" */
125
154
  phoneNumber?: string;
@@ -0,0 +1,55 @@
1
+ import { BaseRepository, type FirebaseSieveResult, type SieveModel } from "../../../providers/db-firebase";
2
+ import { type TaxCodeCreateInput, type TaxCodeDocument, type TaxCodeUpdateInput } from "../schemas/firestore";
3
+ /**
4
+ * The `taxCodes` collection.
5
+ *
6
+ * Small, read-mostly and read on a hot path — `deriveTaxonomy` resolves a
7
+ * category's `taxCodeId` on every product create and on any update that names
8
+ * a taxonomy field. So the two reads below are single-document and
9
+ * single-field-equality shaped on purpose; neither needs a composite index.
10
+ */
11
+ export declare class TaxCodesRepository extends BaseRepository<TaxCodeDocument> {
12
+ constructor();
13
+ list(model: SieveModel): Promise<FirebaseSieveResult<TaxCodeDocument>>;
14
+ /**
15
+ * Every active code, chapter-then-rate ordered, for the picker.
16
+ *
17
+ * Unpaginated deliberately — this is ~10 rows and will be a few dozen at
18
+ * most. If it ever needs paging, the picker needs `searchTxt` first, and
19
+ * that is a different change.
20
+ */
21
+ listActive(): Promise<TaxCodeDocument[]>;
22
+ /**
23
+ * Every code including inactive ones, for the admin list.
24
+ *
25
+ * 🛑 EXPLICITLY bounded, even though the collection is five rows. Two
26
+ * reasons: `BaseRepository.findAll()` relies on a cap several layers away
27
+ * (`base.ts` clamps perPage at 500), and a route that reads "all of a
28
+ * collection" with no visible bound is the shape that has to be re-audited
29
+ * every time somebody wonders whether it is safe. 200 is far above any
30
+ * plausible number of HSN codes and far below anything that costs.
31
+ */
32
+ listAllBounded(): Promise<TaxCodeDocument[]>;
33
+ /**
34
+ * Resolve a code for derivation.
35
+ *
36
+ * 🛑 Returns `null` for an INACTIVE code as well as a missing one, and the
37
+ * caller must treat both the same way: leave the product's `gstRate` and
38
+ * `hsnCode` alone rather than writing a zero. Deactivating a code must not
39
+ * silently re-rate the next listing to 0% — that is indistinguishable from a
40
+ * deliberate exemption, which is the whole reason `gstRate` distinguishes
41
+ * `undefined` from `0`.
42
+ */
43
+ findResolvable(id: string): Promise<TaxCodeDocument | null>;
44
+ /**
45
+ * Create with a DERIVED id, so the same HSN cannot exist twice.
46
+ *
47
+ * Two rows for one HSN is the failure worth preventing structurally: an
48
+ * admin edits one, categories point at the other, and the rate that reaches
49
+ * an invoice is whichever the data happens to reference. A 409 here is the
50
+ * right answer — the request is well-formed and the world already has it.
51
+ */
52
+ create(input: TaxCodeCreateInput): Promise<TaxCodeDocument>;
53
+ update(id: string, data: TaxCodeUpdateInput): Promise<TaxCodeDocument>;
54
+ }
55
+ export declare const taxCodesRepository: TaxCodesRepository;