@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
|
@@ -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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
|
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;
|