@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.
- package/dist/_internal/client/features/lottery/LotteryDetailView.js +22 -1
- package/dist/_internal/server/features/categories/adapters.d.ts +47 -0
- package/dist/_internal/server/features/categories/adapters.js +235 -0
- package/dist/_internal/server/features/checkout/actions.js +161 -22
- package/dist/_internal/server/features/checkout/expand-order-items.js +34 -0
- package/dist/_internal/server/features/orders/adapters.js +21 -0
- package/dist/_internal/server/features/products/list-public.d.ts +24 -0
- package/dist/_internal/server/features/products/list-public.js +10 -2
- package/dist/_internal/server/features/refunds/actions.js +1 -1
- package/dist/_internal/server/features/seo/sitemap.js +54 -15
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +6 -0
- package/dist/_internal/server/functions/scheduled.d.ts +4 -2
- package/dist/_internal/server/functions/scheduled.js +64 -6
- package/dist/_internal/server/jobs/core/adminAnalytics.d.ts +0 -1
- package/dist/_internal/server/jobs/core/adminAnalytics.js +9 -1
- package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.d.ts +47 -0
- package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +468 -0
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +14 -1
- package/dist/_internal/server/jobs/core/countersReconcile.js +24 -8
- package/dist/_internal/server/jobs/core/onCategoryWrite.js +59 -0
- package/dist/_internal/server/jobs/core/onProductWrite.js +31 -0
- package/dist/_internal/server/jobs/core/onReviewWrite.js +8 -0
- package/dist/_internal/server/jobs/core/onStoreWrite.d.ts +1 -1
- package/dist/_internal/server/jobs/core/onStoreWrite.js +8 -2
- package/dist/_internal/server/jobs/core/priceIndexRollup.d.ts +47 -0
- package/dist/_internal/server/jobs/core/priceIndexRollup.js +234 -0
- package/dist/_internal/server/jobs/core/revalidateNotify.d.ts +28 -0
- package/dist/_internal/server/jobs/core/revalidateNotify.js +125 -0
- package/dist/_internal/server/jobs/core/{testerSandboxCleanup.d.ts → testDataCleanup.d.ts} +3 -3
- package/dist/_internal/server/jobs/core/{testerSandboxCleanup.js → testDataCleanup.js} +3 -3
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +5 -1
- package/dist/_internal/server/jobs/handlers/analyticsAlertEvaluate.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/analyticsAlertEvaluate.js +2 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +3 -1
- package/dist/_internal/server/jobs/handlers/index.js +3 -1
- package/dist/_internal/server/jobs/handlers/messages.d.ts +5 -2
- package/dist/_internal/server/jobs/handlers/messages.js +5 -2
- package/dist/_internal/server/jobs/handlers/priceIndexRollup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/priceIndexRollup.js +2 -0
- package/dist/_internal/server/jobs/handlers/testDataCleanup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/testDataCleanup.js +2 -0
- package/dist/_internal/shared/actions/action-registry.js +34 -0
- package/dist/_internal/shared/cache/revalidate-targets.d.ts +49 -0
- package/dist/_internal/shared/cache/revalidate-targets.js +157 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +113 -3
- package/dist/_internal/shared/checkout/order-math.js +194 -7
- package/dist/_internal/shared/features/categories/price-guidance.d.ts +110 -0
- package/dist/_internal/shared/features/categories/price-guidance.js +136 -0
- package/dist/_internal/shared/features/site-settings/gst-registration.d.ts +90 -0
- package/dist/_internal/shared/features/site-settings/gst-registration.js +126 -0
- package/dist/_internal/shared/fees/calculator.d.ts +105 -0
- package/dist/_internal/shared/fees/calculator.js +143 -0
- package/dist/_internal/shared/listing-types/art/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/classified/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/live/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/standard/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +0 -6
- package/dist/_internal/shared/templating/placeholders.d.ts +70 -0
- package/dist/_internal/shared/templating/placeholders.js +73 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +1 -0
- package/dist/constants/api-endpoints.d.ts +42 -0
- package/dist/constants/api-endpoints.js +14 -0
- package/dist/features/admin/actions/admin-actions.d.ts +1 -1
- package/dist/features/admin/actions/admin-actions.js +1 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +269 -3
- package/dist/features/admin/components/AdminProductEditorView.js +8 -1
- package/dist/features/admin/components/AdminProductsView.js +78 -7
- package/dist/features/admin/components/AdminSiteSettingsView.js +20 -1
- package/dist/features/admin/components/AdminTaxCodeEditorView.d.ts +10 -0
- package/dist/features/admin/components/AdminTaxCodeEditorView.js +160 -0
- package/dist/features/admin/components/AdminTaxCodesView.d.ts +13 -0
- package/dist/features/admin/components/AdminTaxCodesView.js +86 -0
- package/dist/features/admin/components/AdminTesterChecklistView.js +1 -1
- package/dist/features/admin/components/analytics/AdminTopProductsTable.js +1 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +10 -1
- package/dist/features/auctions/components/AuctionDetailPageView.js +2 -1
- package/dist/features/auctions/components/AuctionLiveStatusBadge.d.ts +20 -0
- package/dist/features/auctions/components/AuctionLiveStatusBadge.js +21 -0
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
- package/dist/features/auctions/components/PlaceBidFormClient.js +20 -2
- package/dist/features/auctions/hooks/useIsAuctionEnded.d.ts +41 -0
- package/dist/features/auctions/hooks/useIsAuctionEnded.js +76 -0
- package/dist/features/auth/schemas/firestore.d.ts +0 -1
- package/dist/features/blog/api/route.js +16 -1
- package/dist/features/blog/schemas/firestore.d.ts +2 -2
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +14 -3
- package/dist/features/categories/actions/category-actions.d.ts +2 -2
- package/dist/features/categories/actions/category-actions.js +22 -10
- package/dist/features/categories/api/[id]/description-template/route.d.ts +45 -0
- package/dist/features/categories/api/[id]/description-template/route.js +252 -0
- package/dist/features/categories/api/route.js +156 -11
- package/dist/features/categories/components/CategoriesIndexListing.d.ts +1 -3
- package/dist/features/categories/components/CategoriesIndexListing.js +1 -1
- package/dist/features/categories/components/CategoriesIndexPageView.d.ts +27 -1
- package/dist/features/categories/components/CategoriesIndexPageView.js +24 -33
- package/dist/features/categories/components/CategoryDetailPageView.js +43 -10
- package/dist/features/categories/components/CategoryTemplatePicker.d.ts +65 -0
- package/dist/features/categories/components/CategoryTemplatePicker.js +170 -0
- package/dist/features/categories/constants/listing-categories.d.ts +17 -0
- package/dist/features/categories/constants/listing-categories.js +53 -0
- package/dist/features/categories/hooks/useCategoryTree.js +28 -2
- package/dist/features/categories/repository/categories.repository.d.ts +73 -12
- package/dist/features/categories/repository/categories.repository.js +203 -42
- package/dist/features/categories/repository/price-index.repository.d.ts +115 -0
- package/dist/features/categories/repository/price-index.repository.js +85 -0
- package/dist/features/categories/schemas/category-content.d.ts +206 -0
- package/dist/features/categories/schemas/category-content.js +28 -0
- package/dist/features/categories/schemas/firestore.d.ts +74 -5
- package/dist/features/categories/schemas/firestore.js +74 -3
- package/dist/features/categories/schemas/index.d.ts +1047 -0
- package/dist/features/categories/schemas/index.js +96 -0
- package/dist/features/categories/server.d.ts +1 -0
- package/dist/features/categories/server.js +7 -0
- package/dist/features/categories/types/index.d.ts +9 -0
- package/dist/features/events/schemas/firestore.d.ts +2 -2
- package/dist/features/faq/schemas/firestore.d.ts +2 -1
- package/dist/features/faq/schemas/firestore.js +20 -8
- package/dist/features/faq/utils/getHomepageFaqsCached.d.ts +20 -0
- package/dist/features/faq/utils/getHomepageFaqsCached.js +22 -0
- package/dist/features/homepage/components/MarketplaceHomepageView.js +13 -3
- package/dist/features/homepage/lib/live-stats.js +18 -8
- package/dist/features/media/MediaImage.js +19 -2
- package/dist/features/orders/schemas/firestore.d.ts +70 -0
- package/dist/features/orders/types/index.d.ts +55 -0
- package/dist/features/products/actions/product-actions.d.ts +0 -9
- package/dist/features/products/actions/product-actions.js +25 -0
- package/dist/features/products/columns/index.d.ts +0 -1
- package/dist/features/products/columns/index.js +5 -1
- package/dist/features/products/components/ArtStickersListView.js +7 -1
- package/dist/features/products/components/PrizeDrawsSection.js +5 -0
- package/dist/features/products/components/ProductFilters.d.ts +0 -9
- package/dist/features/products/components/ProductForm.d.ts +27 -1
- package/dist/features/products/components/ProductForm.js +26 -12
- package/dist/features/products/components/ProductsIndexPageView.js +1 -1
- package/dist/features/products/constants/sieve.d.ts +0 -6
- package/dist/features/products/constants/sieve.js +35 -2
- package/dist/features/products/repository/products.repository.d.ts +115 -1
- package/dist/features/products/repository/products.repository.js +491 -39
- package/dist/features/products/schemas/firestore.d.ts +65 -5
- package/dist/features/products/schemas/firestore.js +9 -0
- package/dist/features/products/schemas/product-features.d.ts +140 -2
- package/dist/features/products/schemas/product-features.js +62 -1
- package/dist/features/promotions/repository/coupons.repository.js +6 -1
- package/dist/features/promotions/schemas/firestore.d.ts +44 -1
- package/dist/features/promotions/schemas/firestore.js +76 -2
- package/dist/features/reviews/repository/reviews.repository.d.ts +22 -0
- package/dist/features/reviews/repository/reviews.repository.js +35 -0
- package/dist/features/seller/actions/seller-actions.js +17 -2
- package/dist/features/seller/components/PrintCenterView.d.ts +3 -2
- package/dist/features/seller/components/PrintCenterView.js +3 -2
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +21 -11
- package/dist/features/seller/components/SellerOrdersView.js +6 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +19 -3
- package/dist/features/seller/components/SellerProductShell.d.ts +50 -2
- package/dist/features/seller/components/SellerProductShell.js +101 -9
- package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +13 -1
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +2 -2
- package/dist/features/seller/components/SellerProductsView.js +108 -3
- package/dist/features/seller/types/index.d.ts +17 -0
- package/dist/features/seller/utils/product-draft-mapping.d.ts +13 -0
- package/dist/features/seller/utils/product-draft-mapping.js +17 -0
- package/dist/features/store-extensions/constants/alert-metrics.d.ts +86 -0
- package/dist/features/store-extensions/constants/alert-metrics.js +142 -0
- package/dist/features/store-extensions/schemas/firestore.d.ts +19 -1
- package/dist/features/stores/schemas/firestore.d.ts +31 -2
- package/dist/features/tax-codes/repository/tax-codes.repository.d.ts +55 -0
- package/dist/features/tax-codes/repository/tax-codes.repository.js +131 -0
- package/dist/features/tax-codes/schemas/firestore.d.ts +125 -0
- package/dist/features/tax-codes/schemas/firestore.js +82 -0
- package/dist/features/tax-codes/schemas/tax-code-form.d.ts +63 -0
- package/dist/features/tax-codes/schemas/tax-code-form.js +152 -0
- package/dist/features/tax-codes/schemas/tax-codes.validators.d.ts +107 -0
- package/dist/features/tax-codes/schemas/tax-codes.validators.js +85 -0
- package/dist/features/tester/actions/index.d.ts +0 -1
- package/dist/features/tester/actions/index.js +0 -1
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +1 -1
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +1 -1
- package/dist/features/tester/components/index.d.ts +0 -4
- package/dist/features/tester/components/index.js +0 -2
- package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +1 -20
- package/dist/features/tester/repository/tester-checklist-item.repository.js +0 -52
- package/dist/features/tester/schemas/firestore.d.ts +0 -6
- package/dist/features/tester/seed-data/authored/admin__admin-detail-round-trips.js +3 -3
- package/dist/features/tester/seed-data/authored/admin__site-system.js +4 -5
- package/dist/features/tester/seed-data/authored/content-discovery__category-counts-and-rollup.js +2 -2
- package/dist/features/tester/seed-data/authored/index.js +0 -2
- package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +0 -19
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +10 -29
- package/dist/index.d.ts +21 -7
- package/dist/index.js +53 -7
- package/dist/next/routing/route-map.d.ts +2 -4
- package/dist/next/routing/route-map.js +1 -2
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +1 -0
- package/dist/schemas/registry.d.ts +506 -0
- package/dist/seed/_helpers/brand-rows.d.ts +35 -0
- package/dist/seed/_helpers/brand-rows.js +576 -0
- package/dist/seed/_helpers/category-content.d.ts +39 -0
- package/dist/seed/_helpers/category-content.js +634 -0
- package/dist/seed/_helpers/category-forest.d.ts +2 -0
- package/dist/seed/_helpers/category-forest.js +585 -243
- package/dist/seed/_helpers/category-models.d.ts +5 -0
- package/dist/seed/_helpers/category-models.js +1364 -0
- package/dist/seed/_helpers/category-tree.js +17 -1
- package/dist/seed/_helpers/character-features.d.ts +10 -0
- package/dist/seed/_helpers/character-features.js +118 -0
- package/dist/seed/_helpers/media.d.ts +1 -1
- package/dist/seed/_helpers/media.js +44 -8
- package/dist/seed/_helpers/search-txt-wrappers.d.ts +10 -0
- package/dist/seed/_helpers/search-txt-wrappers.js +12 -1
- package/dist/seed/categories-seed-data.js +147 -169
- package/dist/seed/index.d.ts +1 -0
- package/dist/seed/index.js +1 -0
- package/dist/seed/manifest.d.ts +1 -1
- package/dist/seed/manifest.js +5 -0
- package/dist/seed/orders-seed-data.js +5 -3
- package/dist/seed/product-features-seed-data.d.ts +1 -8
- package/dist/seed/product-features-seed-data.js +358 -154
- package/dist/seed/products-art-seed-data.js +16 -4
- package/dist/seed/products-auctions-seed-data.js +6 -6
- package/dist/seed/products-classifieds-seed-data.js +17 -7
- package/dist/seed/products-digital-codes-seed-data.js +12 -12
- package/dist/seed/products-preorders-seed-data.js +8 -8
- package/dist/seed/products-prize-draws-seed-data.js +25 -8
- package/dist/seed/products-standard-seed-data.js +10 -60
- package/dist/seed/products-stickers-seed-data.js +6 -6
- package/dist/seed/site-settings-seed-data.js +24 -0
- package/dist/seed/store-extensions-seed-data.js +10 -4
- package/dist/seed/tax-codes-seed-data.d.ts +34 -0
- package/dist/seed/tax-codes-seed-data.js +100 -0
- package/dist/seed/test-data-ttl.d.ts +29 -0
- package/dist/seed/test-data-ttl.js +31 -0
- package/dist/server-entry.d.ts +1 -1
- package/dist/server-entry.js +6 -1
- package/dist/server.d.ts +4 -1
- package/dist/server.js +11 -2
- package/dist/styles.css +40 -0
- package/dist/ui/components/PaginatedSelect.d.ts +14 -0
- package/dist/ui/components/PaginatedSelect.js +0 -0
- package/dist/ui/components/PaginatedSelect.style.css +40 -0
- package/dist/utils/media-url.d.ts +26 -0
- package/dist/utils/media-url.js +144 -0
- package/dist/utils/physical-location.d.ts +23 -0
- package/dist/utils/physical-location.js +52 -0
- package/dist/utils/search-txt-builders.d.ts +20 -0
- package/dist/utils/search-txt-builders.js +36 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +4 -1
- package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.js +0 -2
- package/dist/features/tester/actions/leaderboard-actions.d.ts +0 -3
- package/dist/features/tester/actions/leaderboard-actions.js +0 -5
- package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +0 -23
- package/dist/features/tester/components/BugHunterLeaderboardView.js +0 -18
- package/dist/features/tester/components/TesterHubView.d.ts +0 -5
- package/dist/features/tester/components/TesterHubView.js +0 -110
- package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.d.ts +0 -2
- package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +0 -78
- package/dist/features/tester/seed-data/tester-ttl.d.ts +0 -4
- package/dist/features/tester/seed-data/tester-ttl.js +0 -6
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Price guidance, stated on the basis the SELLER is typing in.
|
|
3
|
+
*
|
|
4
|
+
* `CategoryPriceGuidance` is advisory — it renders as a hint beside the price
|
|
5
|
+
* input and is never written to `price`. This module answers the one question
|
|
6
|
+
* that hint cannot be rendered without: **inclusive of what?**
|
|
7
|
+
*
|
|
8
|
+
* ## The invariant
|
|
9
|
+
*
|
|
10
|
+
* 🛑 **Every figure in a guidance band is TAX-INCLUSIVE.** Both halves, for
|
|
11
|
+
* different reasons, and neither is a choice we get to make:
|
|
12
|
+
*
|
|
13
|
+
* `market` — competitor ASKS. An Indian storefront's displayed price is
|
|
14
|
+
* inclusive by market convention, and worldhobbyshop asserts it
|
|
15
|
+
* outright in its own JSON-LD (`valueAddedTaxIncluded: true`).
|
|
16
|
+
* `sold` — our own completed sales, derived from `items[].unitPrice`, and
|
|
17
|
+
* every stored price in this system is tax-inclusive.
|
|
18
|
+
*
|
|
19
|
+
* So the basis is a documented property of the data rather than a per-row
|
|
20
|
+
* field. A `basis: "inclusive"` column whose every value is the same string is
|
|
21
|
+
* a field that cannot answer a question, and the first row to carry the other
|
|
22
|
+
* value would be a bug rather than a variant.
|
|
23
|
+
*
|
|
24
|
+
* ## Why the hint still has to say so
|
|
25
|
+
*
|
|
26
|
+
* A seller with `pricesIncludeGst: false` types EX-TAX numbers. Their ₹999 and
|
|
27
|
+
* an inclusive ₹999 band are **different quantities** — comparing them is off
|
|
28
|
+
* by the rate, silently, in the direction that makes their listing look
|
|
29
|
+
* cheaper than the market when it is dearer. The hint therefore converts the
|
|
30
|
+
* band into the seller's own basis and labels which one it is on.
|
|
31
|
+
*
|
|
32
|
+
* Pure arithmetic and strings. No React, no Firestore.
|
|
33
|
+
*/
|
|
34
|
+
import type { CategoryPriceGuidance } from "../../../../features/categories/schemas/category-content";
|
|
35
|
+
/**
|
|
36
|
+
* The basis every stored and crawled guidance figure is on.
|
|
37
|
+
*
|
|
38
|
+
* Exported as a named constant so the claim is greppable from the places that
|
|
39
|
+
* depend on it, and so a future change is one edit rather than an archaeology
|
|
40
|
+
* exercise across the renderer, the seed helper and the rollup job.
|
|
41
|
+
*/
|
|
42
|
+
export declare const PRICE_GUIDANCE_TAX_BASIS: "inclusive";
|
|
43
|
+
export interface PriceGuidanceBasis {
|
|
44
|
+
/**
|
|
45
|
+
* How the seller types prices — `StoreDocument.pricesIncludeGst`.
|
|
46
|
+
*
|
|
47
|
+
* 🛑 ABSENT means `true`, matching the schema default and the store route's
|
|
48
|
+
* `?? true`. Three places now agree on that reading; a fourth that treated
|
|
49
|
+
* absence as `false` would convert a band for a seller who never asked.
|
|
50
|
+
*/
|
|
51
|
+
sellerIncludesGst?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* The rate that will be written onto this listing, resolved the same way
|
|
54
|
+
* `deriveTaxonomy` resolves it (product → category tax code → default).
|
|
55
|
+
*
|
|
56
|
+
* `0` is a deliberate exemption and `undefined` is "we could not resolve
|
|
57
|
+
* one" — and the two must not be collapsed here any more than anywhere else
|
|
58
|
+
* in this codebase. See `describePriceGuidanceBasis`.
|
|
59
|
+
*/
|
|
60
|
+
gstRate?: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* One guidance amount, restated on the seller's basis.
|
|
64
|
+
*
|
|
65
|
+
* Inclusive seller, exempt item, or an unresolved rate → unchanged. The last
|
|
66
|
+
* of those is deliberate: converting at `DEFAULT_GST_RATE` would be a guess
|
|
67
|
+
* presented as arithmetic, and the label says so instead.
|
|
68
|
+
*/
|
|
69
|
+
export declare function priceGuidanceAmountOnSellerBasis(amount: number, basis: PriceGuidanceBasis): number;
|
|
70
|
+
/** The inverse — a seller's typed figure, restated as the band's basis. */
|
|
71
|
+
export declare function sellerAmountOnGuidanceBasis(amount: number, basis: PriceGuidanceBasis): number;
|
|
72
|
+
/**
|
|
73
|
+
* The whole guidance object, restated on the seller's basis.
|
|
74
|
+
*
|
|
75
|
+
* 🛑 `sampleSize`, `windowDays`, `soldOutShare` and `asOf` are NOT money and
|
|
76
|
+
* pass through untouched. Mapping every numeric field would silently divide a
|
|
77
|
+
* sample size by 1.05 — the kind of defect that produces a plausible number
|
|
78
|
+
* nobody can trace, which is the failure mode this plan keeps finding.
|
|
79
|
+
*/
|
|
80
|
+
export declare function priceGuidanceOnSellerBasis(guidance: CategoryPriceGuidance, basis: PriceGuidanceBasis): CategoryPriceGuidance;
|
|
81
|
+
/** What the hint must say about which basis its figures are on. */
|
|
82
|
+
export interface PriceGuidanceBasisNote {
|
|
83
|
+
/** A short suffix for the figures themselves — "incl. GST", "before GST". */
|
|
84
|
+
suffix: string;
|
|
85
|
+
/**
|
|
86
|
+
* The longer explanation, or `null` when there is nothing worth saying.
|
|
87
|
+
*
|
|
88
|
+
* `null` for the common case — an inclusive seller on a taxed item is
|
|
89
|
+
* already comparing like with like, so a sentence telling them so is noise
|
|
90
|
+
* that trains people to stop reading hints.
|
|
91
|
+
*/
|
|
92
|
+
note: string | null;
|
|
93
|
+
/** True when the figures were restated rather than shown as stored. */
|
|
94
|
+
converted: boolean;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Four cases, and the fourth is the one worth having this function for.
|
|
98
|
+
*
|
|
99
|
+
* | seller basis | rate | figures | says |
|
|
100
|
+
* |---|---|---|---|
|
|
101
|
+
* | inclusive | > 0 | unchanged | "incl. GST" |
|
|
102
|
+
* | exclusive | > 0 | CONVERTED | "before GST", and that it was converted |
|
|
103
|
+
* | either | 0 | unchanged | nothing about tax — an exempt item has none |
|
|
104
|
+
* | exclusive | unresolved | unchanged | 🛑 that they are NOT comparable |
|
|
105
|
+
*
|
|
106
|
+
* 🛑 The exempt case must not say "incl. GST". The figure is a bare amount and
|
|
107
|
+
* claiming a tax basis on it is false — which is precisely the lie the seller
|
|
108
|
+
* form's old `?? 0` default told on every listing before C0c defect 6.
|
|
109
|
+
*/
|
|
110
|
+
export declare function describePriceGuidanceBasis(basis: PriceGuidanceBasis): PriceGuidanceBasisNote;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Price guidance, stated on the basis the SELLER is typing in.
|
|
3
|
+
*
|
|
4
|
+
* `CategoryPriceGuidance` is advisory — it renders as a hint beside the price
|
|
5
|
+
* input and is never written to `price`. This module answers the one question
|
|
6
|
+
* that hint cannot be rendered without: **inclusive of what?**
|
|
7
|
+
*
|
|
8
|
+
* ## The invariant
|
|
9
|
+
*
|
|
10
|
+
* 🛑 **Every figure in a guidance band is TAX-INCLUSIVE.** Both halves, for
|
|
11
|
+
* different reasons, and neither is a choice we get to make:
|
|
12
|
+
*
|
|
13
|
+
* `market` — competitor ASKS. An Indian storefront's displayed price is
|
|
14
|
+
* inclusive by market convention, and worldhobbyshop asserts it
|
|
15
|
+
* outright in its own JSON-LD (`valueAddedTaxIncluded: true`).
|
|
16
|
+
* `sold` — our own completed sales, derived from `items[].unitPrice`, and
|
|
17
|
+
* every stored price in this system is tax-inclusive.
|
|
18
|
+
*
|
|
19
|
+
* So the basis is a documented property of the data rather than a per-row
|
|
20
|
+
* field. A `basis: "inclusive"` column whose every value is the same string is
|
|
21
|
+
* a field that cannot answer a question, and the first row to carry the other
|
|
22
|
+
* value would be a bug rather than a variant.
|
|
23
|
+
*
|
|
24
|
+
* ## Why the hint still has to say so
|
|
25
|
+
*
|
|
26
|
+
* A seller with `pricesIncludeGst: false` types EX-TAX numbers. Their ₹999 and
|
|
27
|
+
* an inclusive ₹999 band are **different quantities** — comparing them is off
|
|
28
|
+
* by the rate, silently, in the direction that makes their listing look
|
|
29
|
+
* cheaper than the market when it is dearer. The hint therefore converts the
|
|
30
|
+
* band into the seller's own basis and labels which one it is on.
|
|
31
|
+
*
|
|
32
|
+
* Pure arithmetic and strings. No React, no Firestore.
|
|
33
|
+
*/
|
|
34
|
+
import { taxableFromInclusive, grossFromTaxable, } from "../../fees/calculator";
|
|
35
|
+
/**
|
|
36
|
+
* The basis every stored and crawled guidance figure is on.
|
|
37
|
+
*
|
|
38
|
+
* Exported as a named constant so the claim is greppable from the places that
|
|
39
|
+
* depend on it, and so a future change is one edit rather than an archaeology
|
|
40
|
+
* exercise across the renderer, the seed helper and the rollup job.
|
|
41
|
+
*/
|
|
42
|
+
export const PRICE_GUIDANCE_TAX_BASIS = "inclusive";
|
|
43
|
+
/** Whether a conversion is needed at all, and possible. */
|
|
44
|
+
function shouldConvert(basis) {
|
|
45
|
+
const includes = basis.sellerIncludesGst ?? true;
|
|
46
|
+
return !includes && typeof basis.gstRate === "number" && basis.gstRate > 0;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* One guidance amount, restated on the seller's basis.
|
|
50
|
+
*
|
|
51
|
+
* Inclusive seller, exempt item, or an unresolved rate → unchanged. The last
|
|
52
|
+
* of those is deliberate: converting at `DEFAULT_GST_RATE` would be a guess
|
|
53
|
+
* presented as arithmetic, and the label says so instead.
|
|
54
|
+
*/
|
|
55
|
+
export function priceGuidanceAmountOnSellerBasis(amount, basis) {
|
|
56
|
+
if (!shouldConvert(basis))
|
|
57
|
+
return amount;
|
|
58
|
+
return taxableFromInclusive(amount, basis.gstRate);
|
|
59
|
+
}
|
|
60
|
+
/** The inverse — a seller's typed figure, restated as the band's basis. */
|
|
61
|
+
export function sellerAmountOnGuidanceBasis(amount, basis) {
|
|
62
|
+
if (!shouldConvert(basis))
|
|
63
|
+
return amount;
|
|
64
|
+
return grossFromTaxable(amount, basis.gstRate);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The whole guidance object, restated on the seller's basis.
|
|
68
|
+
*
|
|
69
|
+
* 🛑 `sampleSize`, `windowDays`, `soldOutShare` and `asOf` are NOT money and
|
|
70
|
+
* pass through untouched. Mapping every numeric field would silently divide a
|
|
71
|
+
* sample size by 1.05 — the kind of defect that produces a plausible number
|
|
72
|
+
* nobody can trace, which is the failure mode this plan keeps finding.
|
|
73
|
+
*/
|
|
74
|
+
export function priceGuidanceOnSellerBasis(guidance, basis) {
|
|
75
|
+
if (!shouldConvert(basis))
|
|
76
|
+
return guidance;
|
|
77
|
+
const on = (n) => priceGuidanceAmountOnSellerBasis(n, basis);
|
|
78
|
+
return {
|
|
79
|
+
...guidance,
|
|
80
|
+
market: guidance.market
|
|
81
|
+
? {
|
|
82
|
+
...guidance.market,
|
|
83
|
+
median: on(guidance.market.median),
|
|
84
|
+
...(guidance.market.p25 === undefined ? {} : { p25: on(guidance.market.p25) }),
|
|
85
|
+
...(guidance.market.p75 === undefined ? {} : { p75: on(guidance.market.p75) }),
|
|
86
|
+
}
|
|
87
|
+
: undefined,
|
|
88
|
+
sold: guidance.sold
|
|
89
|
+
? {
|
|
90
|
+
...guidance.sold,
|
|
91
|
+
min: on(guidance.sold.min),
|
|
92
|
+
median: on(guidance.sold.median),
|
|
93
|
+
max: on(guidance.sold.max),
|
|
94
|
+
}
|
|
95
|
+
: undefined,
|
|
96
|
+
lastSold: guidance.lastSold
|
|
97
|
+
? { ...guidance.lastSold, amount: on(guidance.lastSold.amount) }
|
|
98
|
+
: undefined,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Four cases, and the fourth is the one worth having this function for.
|
|
103
|
+
*
|
|
104
|
+
* | seller basis | rate | figures | says |
|
|
105
|
+
* |---|---|---|---|
|
|
106
|
+
* | inclusive | > 0 | unchanged | "incl. GST" |
|
|
107
|
+
* | exclusive | > 0 | CONVERTED | "before GST", and that it was converted |
|
|
108
|
+
* | either | 0 | unchanged | nothing about tax — an exempt item has none |
|
|
109
|
+
* | exclusive | unresolved | unchanged | 🛑 that they are NOT comparable |
|
|
110
|
+
*
|
|
111
|
+
* 🛑 The exempt case must not say "incl. GST". The figure is a bare amount and
|
|
112
|
+
* claiming a tax basis on it is false — which is precisely the lie the seller
|
|
113
|
+
* form's old `?? 0` default told on every listing before C0c defect 6.
|
|
114
|
+
*/
|
|
115
|
+
export function describePriceGuidanceBasis(basis) {
|
|
116
|
+
const includes = basis.sellerIncludesGst ?? true;
|
|
117
|
+
const rate = basis.gstRate;
|
|
118
|
+
if (rate === 0) {
|
|
119
|
+
return { suffix: "", note: null, converted: false };
|
|
120
|
+
}
|
|
121
|
+
if (includes) {
|
|
122
|
+
return { suffix: "incl. GST", note: null, converted: false };
|
|
123
|
+
}
|
|
124
|
+
if (typeof rate !== "number" || rate <= 0) {
|
|
125
|
+
return {
|
|
126
|
+
suffix: "incl. GST",
|
|
127
|
+
note: "Your store prices exclude GST, so these figures are not directly comparable to what you type — the tax rate for this category could not be resolved.",
|
|
128
|
+
converted: false,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
suffix: "before GST",
|
|
133
|
+
note: `Market figures are tax-inclusive; shown here with ${rate}% GST removed to match how your store prices.`,
|
|
134
|
+
converted: true,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GST registration validity — the gate on turning product GST ON.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* `siteSettings.gst.enabled` decides whether product GST is charged at all
|
|
7
|
+
* (`createOrderForGroup` computes a breakdown only when `gstSettings?.enabled`
|
|
8
|
+
* and a buyer state are both present). Until now the admin form sent
|
|
9
|
+
* `{ enabled, gstin: gstin.trim().toUpperCase(), legalName, address }` with
|
|
10
|
+
* **no validation of any kind**, and `/api/admin/site` types every group as
|
|
11
|
+
* `z.unknown()` — so GST collection could be switched on with an **empty or
|
|
12
|
+
* malformed GSTIN**, after which every invoice charges tax under no valid
|
|
13
|
+
* registration and prints whatever string was typed.
|
|
14
|
+
*
|
|
15
|
+
* That is the same class as C0c defect 4 (`SETTINGS_ENCRYPTION_KEY` unset must
|
|
16
|
+
* fail LOUDLY rather than storing plaintext): a compliance precondition has to
|
|
17
|
+
* be refused at the write, not discovered on an invoice.
|
|
18
|
+
*
|
|
19
|
+
* ## Shape
|
|
20
|
+
*
|
|
21
|
+
* 15 characters: `99AAAAA9999A9Z9`
|
|
22
|
+
*
|
|
23
|
+
* `[0-9]{2}` state code
|
|
24
|
+
* `[A-Z]{5}` first five of the holder's PAN
|
|
25
|
+
* `[0-9]{4}` PAN digits
|
|
26
|
+
* `[A-Z]` PAN entity letter
|
|
27
|
+
* `[0-9A-Z]` registration number within the state
|
|
28
|
+
* `Z` fixed
|
|
29
|
+
* `[0-9A-Z]` checksum
|
|
30
|
+
*
|
|
31
|
+
* ## Why the checksum is BLOCKING and not advisory
|
|
32
|
+
*
|
|
33
|
+
* It was verified against the GSTIN that India's own GST material uses as its
|
|
34
|
+
* worked example (`27AAPFU0939F1ZV`) plus five more real-format registrations,
|
|
35
|
+
* and against every single-character mutation of them. A regex alone accepts a
|
|
36
|
+
* transposed digit, which is the realistic typo on a 15-character code copied
|
|
37
|
+
* off a certificate.
|
|
38
|
+
*
|
|
39
|
+
* 🛑 The risk runs the other way too, and it is why this was measured rather
|
|
40
|
+
* than assumed: a wrong checksum implementation would refuse a LEGITIMATE
|
|
41
|
+
* registration and leave a real business unable to invoice at all — strictly
|
|
42
|
+
* worse than accepting a typo. Do not change the algorithm without re-running
|
|
43
|
+
* the assertions.
|
|
44
|
+
*
|
|
45
|
+
* Pure. No React, no Firestore, no env.
|
|
46
|
+
*/
|
|
47
|
+
/** `99AAAAA9999A9Z9` — shape only; see `isValidGstin` for the checksum. */
|
|
48
|
+
export declare const GSTIN_PATTERN: RegExp;
|
|
49
|
+
/**
|
|
50
|
+
* The published GSTIN check character for the first 14 characters.
|
|
51
|
+
*
|
|
52
|
+
* Base-36 positional weights alternate 1, 2 from the left. For each character:
|
|
53
|
+
* multiply its value by its weight, then add the quotient and the remainder of
|
|
54
|
+
* that product divided by 36. The check value is `(36 - sum % 36) % 36`.
|
|
55
|
+
*
|
|
56
|
+
* Returns `null` when the prefix is not 14 characters of the alphabet — a
|
|
57
|
+
* caller must not treat that as "checksum failed", because it is a different
|
|
58
|
+
* fault with a different message.
|
|
59
|
+
*/
|
|
60
|
+
export declare function gstinCheckCharacter(first14: string): string | null;
|
|
61
|
+
/** Shape AND checksum. Whitespace and case are the caller's to normalise. */
|
|
62
|
+
export declare function isValidGstin(raw: string | undefined | null): boolean;
|
|
63
|
+
export interface GstEnablementInput {
|
|
64
|
+
enabled?: boolean;
|
|
65
|
+
gstin?: string;
|
|
66
|
+
legalName?: string;
|
|
67
|
+
address?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* One problem per field, keyed by the field that owns it, so a caller can pipe
|
|
71
|
+
* each straight into `setFieldError(key, message)` per Rule #9 — rather than a
|
|
72
|
+
* single banner the admin has to map back onto four inputs themselves.
|
|
73
|
+
*/
|
|
74
|
+
export type GstEnablementProblems = Partial<Record<"gstin" | "legalName" | "address", string>>;
|
|
75
|
+
/**
|
|
76
|
+
* What must be true before product GST may be charged.
|
|
77
|
+
*
|
|
78
|
+
* 🛑 Only checked when `enabled` is being set to `true`. Turning GST **off**
|
|
79
|
+
* must never be blocked by the state of the fields it is turning off — that
|
|
80
|
+
* would trap an admin who enabled it with bad data in exactly the state they
|
|
81
|
+
* are trying to leave.
|
|
82
|
+
*
|
|
83
|
+
* `legalName` and `address` are required alongside the number because a tax
|
|
84
|
+
* invoice carries all three, and the invoice renderer snapshots all three onto
|
|
85
|
+
* the order. Enabling with two of them produces an invoice with a blank
|
|
86
|
+
* supplier block, which is not a lesser version of compliance.
|
|
87
|
+
*/
|
|
88
|
+
export declare function validateGstEnablement(input: GstEnablementInput): GstEnablementProblems;
|
|
89
|
+
/** `true` when GST may be enabled with this input. */
|
|
90
|
+
export declare function canEnableGst(input: GstEnablementInput): boolean;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GST registration validity — the gate on turning product GST ON.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* `siteSettings.gst.enabled` decides whether product GST is charged at all
|
|
7
|
+
* (`createOrderForGroup` computes a breakdown only when `gstSettings?.enabled`
|
|
8
|
+
* and a buyer state are both present). Until now the admin form sent
|
|
9
|
+
* `{ enabled, gstin: gstin.trim().toUpperCase(), legalName, address }` with
|
|
10
|
+
* **no validation of any kind**, and `/api/admin/site` types every group as
|
|
11
|
+
* `z.unknown()` — so GST collection could be switched on with an **empty or
|
|
12
|
+
* malformed GSTIN**, after which every invoice charges tax under no valid
|
|
13
|
+
* registration and prints whatever string was typed.
|
|
14
|
+
*
|
|
15
|
+
* That is the same class as C0c defect 4 (`SETTINGS_ENCRYPTION_KEY` unset must
|
|
16
|
+
* fail LOUDLY rather than storing plaintext): a compliance precondition has to
|
|
17
|
+
* be refused at the write, not discovered on an invoice.
|
|
18
|
+
*
|
|
19
|
+
* ## Shape
|
|
20
|
+
*
|
|
21
|
+
* 15 characters: `99AAAAA9999A9Z9`
|
|
22
|
+
*
|
|
23
|
+
* `[0-9]{2}` state code
|
|
24
|
+
* `[A-Z]{5}` first five of the holder's PAN
|
|
25
|
+
* `[0-9]{4}` PAN digits
|
|
26
|
+
* `[A-Z]` PAN entity letter
|
|
27
|
+
* `[0-9A-Z]` registration number within the state
|
|
28
|
+
* `Z` fixed
|
|
29
|
+
* `[0-9A-Z]` checksum
|
|
30
|
+
*
|
|
31
|
+
* ## Why the checksum is BLOCKING and not advisory
|
|
32
|
+
*
|
|
33
|
+
* It was verified against the GSTIN that India's own GST material uses as its
|
|
34
|
+
* worked example (`27AAPFU0939F1ZV`) plus five more real-format registrations,
|
|
35
|
+
* and against every single-character mutation of them. A regex alone accepts a
|
|
36
|
+
* transposed digit, which is the realistic typo on a 15-character code copied
|
|
37
|
+
* off a certificate.
|
|
38
|
+
*
|
|
39
|
+
* 🛑 The risk runs the other way too, and it is why this was measured rather
|
|
40
|
+
* than assumed: a wrong checksum implementation would refuse a LEGITIMATE
|
|
41
|
+
* registration and leave a real business unable to invoice at all — strictly
|
|
42
|
+
* worse than accepting a typo. Do not change the algorithm without re-running
|
|
43
|
+
* the assertions.
|
|
44
|
+
*
|
|
45
|
+
* Pure. No React, no Firestore, no env.
|
|
46
|
+
*/
|
|
47
|
+
/** `99AAAAA9999A9Z9` — shape only; see `isValidGstin` for the checksum. */
|
|
48
|
+
export const GSTIN_PATTERN = /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][0-9A-Z]Z[0-9A-Z]$/;
|
|
49
|
+
/** The 36 symbols GSTIN checksum arithmetic runs in, in value order. */
|
|
50
|
+
const GSTIN_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
51
|
+
/**
|
|
52
|
+
* The published GSTIN check character for the first 14 characters.
|
|
53
|
+
*
|
|
54
|
+
* Base-36 positional weights alternate 1, 2 from the left. For each character:
|
|
55
|
+
* multiply its value by its weight, then add the quotient and the remainder of
|
|
56
|
+
* that product divided by 36. The check value is `(36 - sum % 36) % 36`.
|
|
57
|
+
*
|
|
58
|
+
* Returns `null` when the prefix is not 14 characters of the alphabet — a
|
|
59
|
+
* caller must not treat that as "checksum failed", because it is a different
|
|
60
|
+
* fault with a different message.
|
|
61
|
+
*/
|
|
62
|
+
export function gstinCheckCharacter(first14) {
|
|
63
|
+
if (first14.length !== 14)
|
|
64
|
+
return null;
|
|
65
|
+
let sum = 0;
|
|
66
|
+
for (let i = 0; i < 14; i += 1) {
|
|
67
|
+
const value = GSTIN_ALPHABET.indexOf(first14[i]);
|
|
68
|
+
if (value < 0)
|
|
69
|
+
return null;
|
|
70
|
+
const product = value * (i % 2 === 0 ? 1 : 2);
|
|
71
|
+
sum += Math.floor(product / 36) + (product % 36);
|
|
72
|
+
}
|
|
73
|
+
return GSTIN_ALPHABET[(36 - (sum % 36)) % 36];
|
|
74
|
+
}
|
|
75
|
+
/** Shape AND checksum. Whitespace and case are the caller's to normalise. */
|
|
76
|
+
export function isValidGstin(raw) {
|
|
77
|
+
if (!raw)
|
|
78
|
+
return false;
|
|
79
|
+
const value = raw.trim().toUpperCase();
|
|
80
|
+
if (!GSTIN_PATTERN.test(value))
|
|
81
|
+
return false;
|
|
82
|
+
return gstinCheckCharacter(value.slice(0, 14)) === value[14];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* What must be true before product GST may be charged.
|
|
86
|
+
*
|
|
87
|
+
* 🛑 Only checked when `enabled` is being set to `true`. Turning GST **off**
|
|
88
|
+
* must never be blocked by the state of the fields it is turning off — that
|
|
89
|
+
* would trap an admin who enabled it with bad data in exactly the state they
|
|
90
|
+
* are trying to leave.
|
|
91
|
+
*
|
|
92
|
+
* `legalName` and `address` are required alongside the number because a tax
|
|
93
|
+
* invoice carries all three, and the invoice renderer snapshots all three onto
|
|
94
|
+
* the order. Enabling with two of them produces an invoice with a blank
|
|
95
|
+
* supplier block, which is not a lesser version of compliance.
|
|
96
|
+
*/
|
|
97
|
+
export function validateGstEnablement(input) {
|
|
98
|
+
if (input.enabled !== true)
|
|
99
|
+
return {};
|
|
100
|
+
const problems = {};
|
|
101
|
+
const gstin = (input.gstin ?? "").trim().toUpperCase();
|
|
102
|
+
if (gstin.length === 0) {
|
|
103
|
+
problems.gstin = "A GSTIN is required before GST can be charged.";
|
|
104
|
+
}
|
|
105
|
+
else if (!GSTIN_PATTERN.test(gstin)) {
|
|
106
|
+
problems.gstin =
|
|
107
|
+
"A GSTIN is 15 characters in the form 99AAAAA9999A9Z9 — check it against your registration certificate.";
|
|
108
|
+
}
|
|
109
|
+
else if (gstinCheckCharacter(gstin.slice(0, 14)) !== gstin[14]) {
|
|
110
|
+
problems.gstin =
|
|
111
|
+
"That GSTIN fails its own check digit, so it contains a typo. Compare it character by character with your certificate.";
|
|
112
|
+
}
|
|
113
|
+
if ((input.legalName ?? "").trim().length === 0) {
|
|
114
|
+
problems.legalName =
|
|
115
|
+
"The registered legal name is required — it is printed on every tax invoice.";
|
|
116
|
+
}
|
|
117
|
+
if ((input.address ?? "").trim().length === 0) {
|
|
118
|
+
problems.address =
|
|
119
|
+
"The registered address is required — it is printed on every tax invoice.";
|
|
120
|
+
}
|
|
121
|
+
return problems;
|
|
122
|
+
}
|
|
123
|
+
/** `true` when GST may be enabled with this input. */
|
|
124
|
+
export function canEnableGst(input) {
|
|
125
|
+
return Object.keys(validateGstEnablement(input)).length === 0;
|
|
126
|
+
}
|
|
@@ -120,4 +120,109 @@ export interface GstBreakdown {
|
|
|
120
120
|
igst: number;
|
|
121
121
|
gstAmount: number;
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* The rate used when nobody has determined one — HSN 95030020, non-electronic
|
|
125
|
+
* toys, which is nearly this whole catalogue.
|
|
126
|
+
*
|
|
127
|
+
* 🛑 A LAST RESORT, not a mechanism. The real resolution order is the
|
|
128
|
+
* product's own `gstRate` → the category's `taxCodeId` → this. A listing that
|
|
129
|
+
* reaches this constant means its category has no tax code, and THAT is the
|
|
130
|
+
* defect to fix; every use must warn and flag the product for triage. The
|
|
131
|
+
* measure of success is that it fires for nothing.
|
|
132
|
+
*
|
|
133
|
+
* It exists because the alternative is worse in one direction specifically:
|
|
134
|
+
* under GST-inclusive pricing the tax is already inside the price, so backing
|
|
135
|
+
* out 0% would treat the whole amount as taxable value, pay the seller all of
|
|
136
|
+
* it, and leave us owing tax on money already handed over. Exclusive pricing
|
|
137
|
+
* merely fails to collect, which does not lose money we hold.
|
|
138
|
+
*
|
|
139
|
+
* Belongs in `siteSettings.gst` the day a rate revision has to land without a
|
|
140
|
+
* deploy — `TaxCodeDocument.effectiveFrom` records that toys have already
|
|
141
|
+
* moved once — but a constant is the honest starting point rather than a
|
|
142
|
+
* settings field nobody has a reason to change.
|
|
143
|
+
*/
|
|
144
|
+
export declare const DEFAULT_GST_RATE = 5;
|
|
145
|
+
/**
|
|
146
|
+
* Whether this is an intra-state supply (CGST+SGST) or inter-state (IGST).
|
|
147
|
+
*
|
|
148
|
+
* 🛑 NOT a raw `===` on two strings, which is what `createOrderForGroup` and
|
|
149
|
+
* `previewCheckoutPricing` both did. `constants/geo/subdivisions.ts` opens by
|
|
150
|
+
* recording why that fails: the same `state` field held *"Karnataka",
|
|
151
|
+
* "karnataka", "KA" and "Karnatka" depending on which of the three surfaces
|
|
152
|
+
* created the row*. A picker now fixes new addresses, but the store's state
|
|
153
|
+
* and every pre-picker row still arrive unnormalised.
|
|
154
|
+
*
|
|
155
|
+
* 🛑 Returns `null` for "cannot tell", and the distinction matters. The old
|
|
156
|
+
* expression was `!!storeState && storeState === buyerState`, so a store with
|
|
157
|
+
* no address resolved to `false` — i.e. **silently inter-state**, reported as
|
|
158
|
+
* full IGST. Note what this defect is and is not: IGST total equals
|
|
159
|
+
* CGST+SGST total, so **the buyer pays exactly the same amount either way**.
|
|
160
|
+
* It is a compliance and reporting defect on the invoice, not an overcharge —
|
|
161
|
+
* which is why it is safe to surface as `null` and let the caller log rather
|
|
162
|
+
* than guess.
|
|
163
|
+
*/
|
|
164
|
+
export declare function isIntraStateSupply(storeState: string | undefined | null, buyerState: string | undefined | null): boolean | null;
|
|
165
|
+
/**
|
|
166
|
+
* Back the tax OUT of a tax-inclusive amount: `gross × 100 / (100 + rate)`.
|
|
167
|
+
*
|
|
168
|
+
* 🛑 **Every price in this system is tax-inclusive.** A listing's `price` is
|
|
169
|
+
* what the buyer pays; the GST is inside it and is remitted from it. That is
|
|
170
|
+
* the Indian B2C convention, it is what worldhobbyshop asserts in its own
|
|
171
|
+
* JSON-LD (`valueAddedTaxIncluded: true`), and it is what makes two listings
|
|
172
|
+
* at ₹999 cost the same regardless of which seller posted them.
|
|
173
|
+
*
|
|
174
|
+
* A seller who would rather think in ex-tax terms sets
|
|
175
|
+
* `StoreDocument.pricesIncludeGst: false`, and their typed price is grossed up
|
|
176
|
+
* **once, at write time**, by `ProductRepository`. So the flag is an
|
|
177
|
+
* AUTHORING convenience and never a display or checkout mode — the money
|
|
178
|
+
* paths have no branch on it at all, which is the whole point: a mode flag
|
|
179
|
+
* read in five places is five places to drift.
|
|
180
|
+
*
|
|
181
|
+
* `rate <= 0` returns the amount unchanged — an exempt supply has no tax to
|
|
182
|
+
* back out, and dividing by 100 would be a silent no-op that merely looked
|
|
183
|
+
* like arithmetic.
|
|
184
|
+
*/
|
|
185
|
+
export declare function taxableFromInclusive(gross: number, rate: number): number;
|
|
186
|
+
/**
|
|
187
|
+
* The inverse: gross an EX-TAX amount up to its tax-inclusive equivalent,
|
|
188
|
+
* `taxable × (100 + rate) / 100`.
|
|
189
|
+
*
|
|
190
|
+
* Used in exactly one place — `ProductRepository`, when a store with
|
|
191
|
+
* `pricesIncludeGst: false` saves a price. That is the whole extent of the
|
|
192
|
+
* flag: one conversion, at write time, after which the stored price is
|
|
193
|
+
* tax-inclusive like every other and no money path can tell the difference.
|
|
194
|
+
*
|
|
195
|
+
* ₹999 ex-tax at 5% → ₹1,048.95 inclusive, which backs out to ₹999.00 again.
|
|
196
|
+
* The round trip is exact at 2dp for every rate in the union, asserted rather
|
|
197
|
+
* than assumed.
|
|
198
|
+
*/
|
|
199
|
+
export declare function grossFromTaxable(taxable: number, rate: number): number;
|
|
200
|
+
/**
|
|
201
|
+
* The breakdown of a tax-INCLUSIVE amount, with two invariants guaranteed by
|
|
202
|
+
* construction rather than by arithmetic luck:
|
|
203
|
+
*
|
|
204
|
+
* **taxable + gstAmount === gross** (to the paisa)
|
|
205
|
+
* **cgst + sgst === gstAmount** (to the paisa)
|
|
206
|
+
*
|
|
207
|
+
* 🛑 Why this exists instead of `calculateGst(rate, intra, taxableFromInclusive(...))`.
|
|
208
|
+
*
|
|
209
|
+
* That composition rounds TWICE and overshoots. Measured on the ₹999 @ 5% case
|
|
210
|
+
* the plan itself specifies: the taxable rounds UP to 951.43, then
|
|
211
|
+
* `calculateGst`'s intra branch takes `half = round(47.5715 / 2) = 23.79` and
|
|
212
|
+
* returns `round(half × 2) = 47.58` — so taxable + tax came to **₹999.01** and
|
|
213
|
+
* the invoice no longer reconciled with the ₹999 the buyer was charged.
|
|
214
|
+
*
|
|
215
|
+
* Under the old add-on-top model that could not happen: the tax was whatever
|
|
216
|
+
* it was and the total was defined as price + tax, so the invariant was free.
|
|
217
|
+
* Making prices inclusive is what turns it into something that must be
|
|
218
|
+
* enforced — the tax is now a PARTITION of a fixed amount, not an addition to
|
|
219
|
+
* it.
|
|
220
|
+
*
|
|
221
|
+
* So the tax is the **exact complement** of the taxable value, and the split
|
|
222
|
+
* assigns the remainder to the second half rather than rounding both. This is
|
|
223
|
+
* the same discipline `allocateAcrossMembers` already applies, and the same
|
|
224
|
+
* class of defect as the `Math.round(gstAmount / 2)` split that C0c's defect 2
|
|
225
|
+
* removed — one level further out.
|
|
226
|
+
*/
|
|
227
|
+
export declare function calculateGstInclusive(rate: number, intraState: boolean, gross: number): GstBreakdown;
|
|
123
228
|
export declare function calculateGst(rate: number, intraState: boolean, taxableAmount: number): GstBreakdown;
|