@mohasinac/appkit 4.61.0 → 4.62.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-stamp.json +3 -3
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +19 -3
- package/dist/_internal/client/features/maintenance/views/AnalysisRunnerView.js +1 -1
- package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +1 -1
- package/dist/_internal/server/features/checkout/actions.d.ts +80 -0
- package/dist/_internal/server/features/checkout/actions.js +542 -26
- package/dist/_internal/server/features/invoicing/allocate.d.ts +63 -0
- package/dist/_internal/server/features/invoicing/allocate.js +162 -0
- package/dist/_internal/server/features/invoicing/credit-note.d.ts +57 -0
- package/dist/_internal/server/features/invoicing/credit-note.js +126 -0
- package/dist/_internal/server/features/invoicing/gstr1.d.ts +74 -0
- package/dist/_internal/server/features/invoicing/gstr1.js +251 -0
- package/dist/_internal/server/features/invoicing/issue.d.ts +96 -0
- package/dist/_internal/server/features/invoicing/issue.js +202 -0
- package/dist/_internal/server/features/orders/actions.d.ts +8 -1
- package/dist/_internal/server/features/orders/actions.js +81 -8
- package/dist/_internal/server/features/orders/adapters.js +33 -0
- package/dist/_internal/server/features/orders/cancellation-refund.d.ts +15 -9
- package/dist/_internal/server/features/orders/cancellation-refund.js +15 -9
- package/dist/_internal/server/features/points/award.js +46 -4
- package/dist/_internal/server/features/refunds/actions.js +26 -0
- package/dist/_internal/server/features/shipping/courier-block.d.ts +69 -0
- package/dist/_internal/server/features/shipping/courier-block.js +239 -0
- package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
- package/dist/_internal/server/features/site-settings/adapters.js +15 -0
- package/dist/_internal/server/features/stores/adapters.d.ts +2 -2
- package/dist/_internal/server/features/stores/adapters.js +51 -0
- package/dist/_internal/server/functions/scheduled.js +38 -1
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +9 -2
- package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +4 -0
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +19 -2
- package/dist/_internal/server/jobs/core/index.d.ts +2 -1
- package/dist/_internal/server/jobs/core/index.js +2 -1
- package/dist/_internal/server/jobs/core/onJobCreated.d.ts +30 -7
- package/dist/_internal/server/jobs/core/onJobCreated.js +78 -45
- package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +7 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +62 -0
- package/dist/_internal/server/jobs/core/paymentReconcile.js +87 -97
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +33 -0
- package/dist/_internal/server/jobs/core/runJobOnce.d.ts +48 -0
- package/dist/_internal/server/jobs/core/runJobOnce.js +195 -0
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +10 -2
- package/dist/_internal/server/jobs/handlers/onOrderStatusChange.js +17 -0
- package/dist/_internal/server/notifications/channel-health.d.ts +26 -3
- package/dist/_internal/shared/actions/action-registry.js +177 -9
- package/dist/_internal/shared/checkout/coupon-discount.d.ts +43 -1
- package/dist/_internal/shared/checkout/coupon-discount.js +26 -1
- package/dist/_internal/shared/checkout/order-math.d.ts +145 -0
- package/dist/_internal/shared/checkout/order-math.js +173 -0
- package/dist/_internal/shared/checkout/refundable-base.d.ts +25 -0
- package/dist/_internal/shared/checkout/refundable-base.js +13 -1
- package/dist/_internal/shared/checkout/split.d.ts +87 -0
- package/dist/_internal/shared/checkout/split.js +100 -0
- package/dist/_internal/shared/content/disclaimers.d.ts +88 -0
- package/dist/_internal/shared/content/disclaimers.js +217 -0
- package/dist/_internal/shared/content/published-values.d.ts +88 -0
- package/dist/_internal/shared/content/published-values.js +198 -0
- package/dist/_internal/shared/features/admin/integrations.d.ts +109 -0
- package/dist/_internal/shared/features/admin/integrations.js +253 -0
- package/dist/_internal/shared/features/orders/dispute-reasons.d.ts +113 -0
- package/dist/_internal/shared/features/orders/dispute-reasons.js +181 -0
- package/dist/_internal/shared/features/orders/refund-copy.d.ts +1 -1
- package/dist/_internal/shared/features/orders/refund-copy.js +18 -1
- package/dist/_internal/shared/features/points/accepts.d.ts +36 -0
- package/dist/_internal/shared/features/points/accepts.js +43 -0
- package/dist/_internal/shared/features/shipping/shared-purchase.d.ts +91 -0
- package/dist/_internal/shared/features/shipping/shared-purchase.js +108 -0
- package/dist/_internal/shared/features/site-settings/types.d.ts +11 -0
- package/dist/_internal/shared/fees/calculator.d.ts +33 -1
- package/dist/_internal/shared/fees/calculator.js +32 -2
- package/dist/_internal/shared/media/limits.d.ts +10 -0
- package/dist/_internal/shared/media/limits.js +10 -0
- package/dist/_internal/shared/types/action-result.js +15 -1
- package/dist/client/api/surface-error.js +16 -2
- package/dist/client/api/useApiMutation.js +17 -1
- package/dist/client.d.ts +12 -1
- package/dist/client.js +31 -1
- package/dist/constants/api-endpoints.d.ts +45 -0
- package/dist/constants/api-endpoints.js +15 -0
- package/dist/constants/field-names.d.ts +18 -0
- package/dist/constants/field-names.js +28 -0
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/shipping.d.ts +45 -0
- package/dist/contracts/shipping.js +15 -0
- package/dist/core/server-action.js +19 -3
- package/dist/errors/error-mapping.js +26 -1
- package/dist/features/about/components/AboutView.js +1 -1
- package/dist/features/account/components/BuyerAuctionsGuideView.js +4 -1
- package/dist/features/account/components/UserOffersPanel.js +5 -3
- package/dist/features/account/components/UserOrdersView.js +19 -33
- package/dist/features/admin/components/AdminActionIndexView.js +100 -5
- package/dist/features/admin/components/AdminAddressClustersView.js +21 -2
- package/dist/features/admin/components/AdminBlogEditorView.js +8 -8
- package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
- package/dist/features/admin/components/AdminCarouselView.js +96 -4
- package/dist/features/admin/components/AdminCouponEditorView.js +17 -0
- package/dist/features/admin/components/AdminFulfillmentView.js +3 -2
- package/dist/features/admin/components/AdminInvoicingView.d.ts +2 -0
- package/dist/features/admin/components/AdminInvoicingView.js +65 -0
- package/dist/features/admin/components/AdminMediaView.js +36 -3
- package/dist/features/admin/components/AdminNavigationView.js +4 -1
- package/dist/features/admin/components/AdminOffersView.js +2 -1
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +18 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +11 -2
- package/dist/features/admin/components/AdminOrdersGuideView.js +23 -2
- package/dist/features/admin/components/AdminOrdersView.js +67 -5
- package/dist/features/admin/components/AdminPaymentClustersView.js +19 -2
- package/dist/features/admin/components/AdminProductEditorView.js +2 -2
- package/dist/features/admin/components/AdminSectionsView.d.ts +10 -0
- package/dist/features/admin/components/AdminSectionsView.js +486 -224
- package/dist/features/admin/components/OwnerQuickActions.js +24 -2
- package/dist/features/admin/components/ReferenceRecordEditor.js +1 -1
- package/dist/features/admin/constants/filter-tabs.d.ts +6 -0
- package/dist/features/admin/constants/filter-tabs.js +6 -0
- package/dist/features/admin/constants/site-settings-tabs.d.ts +1 -1
- package/dist/features/admin/hooks/useAdminListingData.js +16 -0
- package/dist/features/admin/hooks/useOptimisticRowFlag.d.ts +33 -0
- package/dist/features/admin/hooks/useOptimisticRowFlag.js +87 -0
- package/dist/features/admin/repository/notification.repository.d.ts +15 -0
- package/dist/features/admin/repository/notification.repository.js +32 -0
- package/dist/features/admin/schemas/action-index-form.d.ts +34 -0
- package/dist/features/admin/schemas/action-index-form.js +115 -0
- package/dist/features/admin/schemas/admin-user-form.d.ts +2 -2
- package/dist/features/admin/schemas/firestore.d.ts +30 -0
- package/dist/features/admin/schemas/firestore.js +5 -0
- package/dist/features/audit-log/schemas/firestore.d.ts +30 -0
- package/dist/features/audit-log/schemas/firestore.js +15 -0
- package/dist/features/auth/components/RegisterForm.js +2 -1
- package/dist/features/auth/permissions/constants.js +13 -0
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +13 -0
- package/dist/features/cart/components/CartPriceBreakdown.js +6 -2
- package/dist/features/cart/components/PointsTenderPicker.d.ts +61 -0
- package/dist/features/cart/components/PointsTenderPicker.js +15 -0
- package/dist/features/cart/components/StoreAddonsPicker.js +2 -1
- package/dist/features/cart/components/index.d.ts +2 -0
- package/dist/features/cart/components/index.js +1 -0
- package/dist/features/cart/repository/cart.repository.d.ts +19 -0
- package/dist/features/cart/repository/cart.repository.js +27 -0
- package/dist/features/cart/schemas/firestore.d.ts +92 -0
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +3 -3
- package/dist/features/contact/email.d.ts +27 -10
- package/dist/features/contact/email.js +20 -3
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +38 -0
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +80 -0
- package/dist/features/dispute-reasons/schemas/firestore.d.ts +64 -0
- package/dist/features/dispute-reasons/schemas/firestore.js +40 -0
- package/dist/features/events/components/AdminEventEditorView.js +8 -6
- package/dist/features/faq/components/FAQAccordion.d.ts +13 -1
- package/dist/features/faq/components/FAQAccordion.js +29 -4
- package/dist/features/invoicing/schemas/firestore.d.ts +112 -0
- package/dist/features/invoicing/schemas/firestore.js +45 -0
- package/dist/features/invoicing/utils/financial-year.d.ts +43 -0
- package/dist/features/invoicing/utils/financial-year.js +53 -0
- package/dist/features/jobs/actions/enqueue-job.js +18 -1
- package/dist/features/jobs/repository/jobs.repository.d.ts +62 -1
- package/dist/features/jobs/repository/jobs.repository.js +84 -1
- package/dist/features/jobs/schemas/firestore.d.ts +80 -0
- package/dist/features/jobs/schemas/firestore.js +37 -0
- package/dist/features/layout/hooks/useFormBottomActions.js +20 -1
- package/dist/features/lottery/schemas/zod.d.ts +6 -6
- package/dist/features/media/MediaImage.js +22 -1
- package/dist/features/media/upload/ImageUpload.js +1 -1
- package/dist/features/obligations/constants/policies.js +24 -1
- package/dist/features/orders/components/EmiInstallmentsPanel.js +25 -7
- package/dist/features/orders/components/OrderPaymentSummary.js +10 -1
- package/dist/features/orders/components/OrderProvenanceCard.d.ts +8 -0
- package/dist/features/orders/components/OrderProvenanceCard.js +185 -0
- package/dist/features/orders/components/RaiseDisputeForm.d.ts +14 -0
- package/dist/features/orders/components/RaiseDisputeForm.js +111 -0
- package/dist/features/orders/components/RefundRequestView.js +22 -4
- package/dist/features/orders/components/index.d.ts +4 -0
- package/dist/features/orders/components/index.js +2 -0
- package/dist/features/orders/constants/order-lane-tabs.d.ts +69 -0
- package/dist/features/orders/constants/order-lane-tabs.js +88 -0
- package/dist/features/orders/repository/orders.repository.d.ts +57 -0
- package/dist/features/orders/repository/orders.repository.js +145 -0
- package/dist/features/orders/schemas/firestore.d.ts +196 -1
- package/dist/features/orders/schemas/index.d.ts +60 -12
- package/dist/features/orders/schemas/index.js +12 -0
- package/dist/features/orders/types/index.d.ts +26 -0
- package/dist/features/orders/utils/courier-block.d.ts +25 -0
- package/dist/features/orders/utils/courier-block.js +23 -0
- package/dist/features/payment-cases/schemas/firestore.d.ts +26 -1
- package/dist/features/payment-cases/schemas/firestore.js +1 -0
- package/dist/features/products/components/GroupMemberPicker.js +4 -1
- package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -2
- package/dist/features/products/components/PrizeDrawEntryActions.js +2 -1
- package/dist/features/products/components/ProductDetailActions.js +2 -1
- package/dist/features/products/components/ProductDetailPageView.d.ts +17 -1
- package/dist/features/products/components/ProductDetailPageView.js +2 -2
- package/dist/features/products/schemas/product-features.validators.d.ts +10 -10
- package/dist/features/promotions/actions/coupon-stacking.d.ts +30 -3
- package/dist/features/promotions/actions/coupon-stacking.js +21 -2
- package/dist/features/promotions/components/CouponCard.js +13 -0
- package/dist/features/promotions/constants/coupon-help.d.ts +2 -2
- package/dist/features/promotions/constants/coupon-help.js +9 -1
- package/dist/features/promotions/schemas/firestore.d.ts +24 -0
- package/dist/features/promotions/schemas/firestore.js +4 -0
- package/dist/features/promotions/types/index.d.ts +19 -1
- package/dist/features/scams/components/ScamProfileView.js +2 -2
- package/dist/features/scams/components/ScamRegistryView.js +2 -2
- package/dist/features/search/action-index/tab-entries.js +1 -1
- package/dist/features/seller/actions/seller-actions.js +33 -3
- package/dist/features/seller/components/FulfillmentView.js +3 -2
- package/dist/features/seller/components/SellerAddressesView.js +32 -4
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +62 -3
- package/dist/features/seller/components/SellerBundlesView.js +53 -14
- package/dist/features/seller/components/SellerGroupedListingsView.js +23 -3
- package/dist/features/seller/components/SellerListingTypeView.js +56 -10
- package/dist/features/seller/components/SellerOrdersView.js +129 -27
- package/dist/features/seller/components/SellerPayoutMethodsView.js +33 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +48 -9
- package/dist/features/seller/components/SellerProductsView.js +5 -1
- package/dist/features/seller/components/SellerShippingConfigsView.js +33 -1
- package/dist/features/seller/components/SellerShippingView.js +2 -1
- package/dist/features/seller/components/SellerStoreCategoriesView.js +33 -1
- package/dist/features/seller/components/SellerSupportView.js +69 -2
- package/dist/features/seller/hooks/useSellerListingData.js +25 -0
- package/dist/features/shell/SectionForm.js +20 -16
- package/dist/features/shell/build-sections.js +14 -1
- package/dist/features/shipments/components/AdminShipmentLotItemsView.js +54 -1
- package/dist/features/shipments/components/AdminShipmentProjectionsView.js +3 -2
- package/dist/features/shipments/schemas/validation.d.ts +4 -4
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +43 -0
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +90 -0
- package/dist/features/shipping-boxes/schemas/firestore.d.ts +82 -0
- package/dist/features/shipping-boxes/schemas/firestore.js +53 -0
- package/dist/features/sticker-trace/components/StickerCommitPanel.js +2 -1
- package/dist/features/sticker-trace/components/StickerPrintPanel.js +2 -1
- package/dist/features/sticker-trace/components/StickerRetryPanel.js +24 -4
- package/dist/features/sticker-trace/components/StickerTraceStudio.js +2 -1
- package/dist/features/store-extensions/schemas/report-create-form.d.ts +2 -2
- package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +2 -2
- package/dist/features/stores/components/StoreOrdersGuideView.js +6 -1
- package/dist/features/stores/schemas/firestore.d.ts +71 -0
- package/dist/features/stores/types/index.d.ts +19 -0
- package/dist/features/support/schemas/index.d.ts +6 -6
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +3 -2
- package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.js +17 -2
- package/dist/next/routing/route-map.d.ts +24 -0
- package/dist/next/routing/route-map.js +19 -0
- package/dist/providers/shipping-shiprocket/index.d.ts +27 -1
- package/dist/providers/shipping-shiprocket/index.js +84 -1
- package/dist/schemas/registry.d.ts +51 -22
- package/dist/schemas/sieve.d.ts +5 -5
- package/dist/server-entry.d.ts +2 -2
- package/dist/server-entry.js +8 -1
- package/dist/server.d.ts +11 -2
- package/dist/server.js +40 -2
- package/dist/styles.css +64 -80
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/default-dark.js +1 -0
- package/dist/tokens/themes/default-light.js +1 -0
- package/dist/tokens/themes/required.d.ts +1 -1
- package/dist/tokens/themes/required.js +1 -0
- package/dist/tokens/tokens.css +15 -0
- package/dist/ui/components/Checkbox.js +10 -2
- package/dist/ui/components/DateInput.js +8 -1
- package/dist/ui/components/Disclaimer.d.ts +19 -0
- package/dist/ui/components/Disclaimer.js +74 -0
- package/dist/ui/components/Input.js +22 -2
- package/dist/ui/components/PaginatedSelect.d.ts +8 -19
- package/dist/ui/components/PaginatedSelect.js +43 -9
- package/dist/ui/components/Radio.js +8 -1
- package/dist/ui/components/Select.js +10 -2
- package/dist/ui/components/TagInput.d.ts +8 -1
- package/dist/ui/components/TagInput.js +4 -3
- package/dist/ui/components/Textarea.js +10 -2
- package/dist/ui/components/tabs-overflow.js +64 -11
- package/dist/ui/forms/FieldCheckbox.js +1 -1
- package/dist/ui/forms/FormErrorList.d.ts +0 -16
- package/dist/ui/forms/FormErrorList.js +34 -1
- package/dist/ui/forms/apply-zod-issues.d.ts +19 -1
- package/dist/ui/forms/apply-zod-issues.js +23 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +6 -0
- package/dist/ui/rich-text/RichText.style.css +48 -79
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +1831 -1822
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* OrderProvenanceCard — renders `OrderDocument.sourceContext`, the record of
|
|
5
|
+
* HOW the buyer earned the right to buy at this price.
|
|
6
|
+
*
|
|
7
|
+
* ## Why this component had to be written
|
|
8
|
+
*
|
|
9
|
+
* `sourceContext` is a six-variant discriminated union, assembled at a real
|
|
10
|
+
* cost — an auction pays one `findByProductSorted`, an offer one `findById` —
|
|
11
|
+
* written on every order by both checkout paths, mapped through to the client
|
|
12
|
+
* type by `orderDocumentToOrder` (adapters.ts:359) … and, until this file,
|
|
13
|
+
* rendered in **zero** `.tsx` files anywhere in the repo.
|
|
14
|
+
*
|
|
15
|
+
* That is Root Cause #52/#57: the data was already present and complete, and
|
|
16
|
+
* no surface was ever wired to present it. The buyer could not see what they
|
|
17
|
+
* had agreed, the seller could not see what they had accepted, and support
|
|
18
|
+
* could not see either.
|
|
19
|
+
*
|
|
20
|
+
* ## What it deliberately does NOT do
|
|
21
|
+
*
|
|
22
|
+
* **`standard` renders nothing.** Its only field is the list price, which the
|
|
23
|
+
* order total already shows — a card reading "Standard purchase" on every
|
|
24
|
+
* ordinary order is noise that would teach people to ignore the card on the
|
|
25
|
+
* orders where it matters. It is still an explicit `case`, so the switch stays
|
|
26
|
+
* exhaustive.
|
|
27
|
+
*
|
|
28
|
+
* **A saving is shown only where it is real.** Not every variant records a
|
|
29
|
+
* displaced price: `auction-buy-now` has `listPrice`, `offer-accepted` has
|
|
30
|
+
* `listPriceAtOffer`, and `auction-won` has only `startingBid` — which is a
|
|
31
|
+
* floor, not an asking price, so presenting the gap as a discount would be a
|
|
32
|
+
* fabrication. Where a displaced price exists it is shown only when it is
|
|
33
|
+
* strictly greater than what was paid.
|
|
34
|
+
*
|
|
35
|
+
* **Nothing is fetched.** It is handed the context the page already loaded; a
|
|
36
|
+
* component that fetched would fire one request per opened drawer per portal.
|
|
37
|
+
*/
|
|
38
|
+
import { Badge, Card, DescriptionField, Heading, Row, Stack, Text } from "../../../ui/index.js";
|
|
39
|
+
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
40
|
+
import { formatDate } from "../../../utils/date.formatter.js";
|
|
41
|
+
/**
|
|
42
|
+
* A date on a `sourceContext` survives a Firestore round trip as a Timestamp,
|
|
43
|
+
* an ISO string or a Date depending on which path loaded it, so normalise
|
|
44
|
+
* rather than assuming. An unparseable value renders an em-dash — never
|
|
45
|
+
* `new Date()`, which would invent a timestamp for a record that has none
|
|
46
|
+
* (the rule `OrderStatusTimeline` already follows).
|
|
47
|
+
*/
|
|
48
|
+
function dateText(value) {
|
|
49
|
+
if (value === null || value === undefined)
|
|
50
|
+
return "—";
|
|
51
|
+
const raw = typeof value === "object" && value !== null && "toDate" in value
|
|
52
|
+
? value.toDate()
|
|
53
|
+
: value;
|
|
54
|
+
const d = raw instanceof Date ? raw : new Date(raw);
|
|
55
|
+
return Number.isFinite(d.getTime()) ? formatDate(d) : "—";
|
|
56
|
+
}
|
|
57
|
+
function plural(n, one, many) {
|
|
58
|
+
return `${n} ${n === 1 ? one : many}`;
|
|
59
|
+
}
|
|
60
|
+
function contentFor(ctx, currency) {
|
|
61
|
+
const money = (n) => formatCurrency(n, currency);
|
|
62
|
+
switch (ctx.path) {
|
|
63
|
+
case "standard":
|
|
64
|
+
// Deliberately silent — see the header. Explicit so the switch is exhaustive.
|
|
65
|
+
return null;
|
|
66
|
+
case "auction-won": {
|
|
67
|
+
const rows = [
|
|
68
|
+
{ label: "Winning bid", value: money(ctx.winningBidAmount) },
|
|
69
|
+
{ label: "Bids in the auction", value: String(ctx.bidCount) },
|
|
70
|
+
{ label: "Starting bid", value: money(ctx.startingBid) },
|
|
71
|
+
];
|
|
72
|
+
if (ctx.runnerUpAmount !== undefined) {
|
|
73
|
+
rows.push({ label: "Runner-up bid", value: money(ctx.runnerUpAmount) });
|
|
74
|
+
}
|
|
75
|
+
if (ctx.reservePrice !== undefined) {
|
|
76
|
+
rows.push({
|
|
77
|
+
label: "Reserve",
|
|
78
|
+
value: `${money(ctx.reservePrice)} — ${ctx.reserveMet ? "met" : "not met"}`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
rows.push({ label: "Auction ended", value: dateText(ctx.auctionEndedAt) });
|
|
82
|
+
return {
|
|
83
|
+
title: "Won at auction",
|
|
84
|
+
tone: "success",
|
|
85
|
+
summary: ctx.bidCount > 1
|
|
86
|
+
? `Highest of ${plural(ctx.bidCount, "bid", "bids")}.`
|
|
87
|
+
: "The only bid placed.",
|
|
88
|
+
rows,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
case "auction-buy-now": {
|
|
92
|
+
const rows = [
|
|
93
|
+
{ label: "Buy Now price", value: money(ctx.buyNowPrice) },
|
|
94
|
+
{ label: "List price", value: money(ctx.listPrice) },
|
|
95
|
+
{
|
|
96
|
+
label: "Standing bid beaten",
|
|
97
|
+
value: ctx.standingBidAtBuyout > 0 ? money(ctx.standingBidAtBuyout) : "none",
|
|
98
|
+
},
|
|
99
|
+
{ label: "Bids at buyout", value: String(ctx.bidCount) },
|
|
100
|
+
{ label: "Bought", value: dateText(ctx.boughtAt) },
|
|
101
|
+
];
|
|
102
|
+
return {
|
|
103
|
+
title: "Bought now, ending the auction",
|
|
104
|
+
tone: "info",
|
|
105
|
+
/*
|
|
106
|
+
* A buyout IS a real bid (the schema's own note: recording bidCount 0
|
|
107
|
+
* would bake a falsehood into an immutable record), so the summary
|
|
108
|
+
* must not claim nobody had bid when somebody had.
|
|
109
|
+
*/
|
|
110
|
+
summary: ctx.bidCount > 0
|
|
111
|
+
? `Taken at the Buy Now price while ${plural(ctx.bidCount, "bid was", "bids were")} live.`
|
|
112
|
+
: "Taken at the Buy Now price before any bids were placed.",
|
|
113
|
+
rows,
|
|
114
|
+
saved: ctx.listPrice > ctx.buyNowPrice ? ctx.listPrice - ctx.buyNowPrice : undefined,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
case "offer-accepted": {
|
|
118
|
+
const rows = [
|
|
119
|
+
{ label: "Agreed price", value: money(ctx.offeredAmount) },
|
|
120
|
+
{ label: "Asking price when offered", value: money(ctx.listPriceAtOffer) },
|
|
121
|
+
];
|
|
122
|
+
if (ctx.counterAmount !== undefined) {
|
|
123
|
+
rows.push({ label: "Final counter", value: money(ctx.counterAmount) });
|
|
124
|
+
}
|
|
125
|
+
rows.push({ label: "Negotiation", value: plural(ctx.counterRounds, "round", "rounds") }, { label: "Accepted", value: dateText(ctx.acceptedAt) });
|
|
126
|
+
return {
|
|
127
|
+
title: "Price agreed by offer",
|
|
128
|
+
tone: "primary",
|
|
129
|
+
summary: ctx.counterRounds > 1
|
|
130
|
+
? `Agreed after ${plural(ctx.counterRounds, "round", "rounds")} of counter-offers.`
|
|
131
|
+
: "Accepted outright, with no counter-offer.",
|
|
132
|
+
rows,
|
|
133
|
+
saved: ctx.listPriceAtOffer > ctx.offeredAmount
|
|
134
|
+
? ctx.listPriceAtOffer - ctx.offeredAmount
|
|
135
|
+
: undefined,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
case "pre-order":
|
|
139
|
+
return {
|
|
140
|
+
title: "Pre-order",
|
|
141
|
+
tone: "warning",
|
|
142
|
+
summary: ctx.balanceDue > 0
|
|
143
|
+
? "A deposit was taken at checkout; the balance is due before dispatch."
|
|
144
|
+
: "Paid in full at checkout.",
|
|
145
|
+
rows: [
|
|
146
|
+
{ label: "Deposit paid", value: money(ctx.depositAmount) },
|
|
147
|
+
{ label: "Balance due", value: money(ctx.balanceDue) },
|
|
148
|
+
{ label: "Expected delivery", value: dateText(ctx.expectedDeliveryDate) },
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
case "prize-draw":
|
|
152
|
+
return {
|
|
153
|
+
title: "Prize draw entry",
|
|
154
|
+
tone: "info",
|
|
155
|
+
summary: ctx.revealMode === "instant"
|
|
156
|
+
? "The result was revealed immediately."
|
|
157
|
+
: "The result is revealed at the scheduled draw.",
|
|
158
|
+
rows: [
|
|
159
|
+
{ label: "Entries bought", value: plural(ctx.entryCount, "entry", "entries") },
|
|
160
|
+
{ label: "Price per entry", value: money(ctx.pricePerEntry) },
|
|
161
|
+
{ label: "Reveal", value: ctx.revealMode === "instant" ? "Instant" : "Scheduled" },
|
|
162
|
+
],
|
|
163
|
+
};
|
|
164
|
+
default: {
|
|
165
|
+
/*
|
|
166
|
+
* Exhaustive. A seventh variant — the shared-purchase path S5a still
|
|
167
|
+
* needs — will not compile rather than silently rendering nothing, which
|
|
168
|
+
* is the failure mode of `default: return null` and is precisely how this
|
|
169
|
+
* union came to have six populated variants and no reader at all.
|
|
170
|
+
*/
|
|
171
|
+
const unreachable = ctx;
|
|
172
|
+
return unreachable;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
export function OrderProvenanceCard({ sourceContext, currency, className, }) {
|
|
177
|
+
// Every order written before W2 has no context at all — absence is normal
|
|
178
|
+
// and is not an error state to announce.
|
|
179
|
+
if (!sourceContext)
|
|
180
|
+
return null;
|
|
181
|
+
const content = contentFor(sourceContext, currency);
|
|
182
|
+
if (!content)
|
|
183
|
+
return null;
|
|
184
|
+
return (_jsx(Card, { variant: "outlined", padding: "md", className: className, children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Heading, { level: 3, size: "sm", children: content.title }), content.saved !== undefined ? (_jsx(Badge, { variant: "success", children: `Saved ${formatCurrency(content.saved, currency)}` })) : null] }), content.summary ? (_jsx(Text, { size: "sm", color: "muted", children: content.summary })) : null, _jsx(Row, { as: "dl", wrap: true, gap: "md", children: content.rows.map((row) => (_jsx(DescriptionField, { label: row.label, value: row.value }, row.label))) })] }) }));
|
|
185
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type DisputeGateOrder, type DisputeKind } from "../../../_internal/shared/features/orders/dispute-reasons";
|
|
2
|
+
export interface RaiseDisputeFormProps {
|
|
3
|
+
/** The gate reads four dates off this; an `OrderDocument` satisfies it. */
|
|
4
|
+
order: DisputeGateOrder;
|
|
5
|
+
/** `GET ?kind=` / `POST` — supplied by the consumer so appkit holds no route. */
|
|
6
|
+
reasonsEndpoint: string;
|
|
7
|
+
onSubmit: (input: {
|
|
8
|
+
kind: DisputeKind;
|
|
9
|
+
reasonId: string;
|
|
10
|
+
detail?: string;
|
|
11
|
+
}) => Promise<void>;
|
|
12
|
+
onDone?: () => void;
|
|
13
|
+
}
|
|
14
|
+
export declare function RaiseDisputeForm({ order, reasonsEndpoint, onSubmit, onDone, }: RaiseDisputeFormProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* The buyer's dispute form: pick a kind, pick a reason, say what happened.
|
|
5
|
+
*
|
|
6
|
+
* ## Why the kind comes first
|
|
7
|
+
*
|
|
8
|
+
* The two kinds have MUTUALLY EXCLUSIVE gates — an item problem needs the
|
|
9
|
+
* order to have arrived, a non-delivery needs it not to have. So the kind is
|
|
10
|
+
* the first choice, it scopes the reason list, and it decides which gate the
|
|
11
|
+
* server will apply. Offering one merged list of reasons would mean a buyer
|
|
12
|
+
* could pick "it never arrived" on a delivered order and have the server
|
|
13
|
+
* refuse them, which reads as the form being broken rather than as the wrong
|
|
14
|
+
* choice.
|
|
15
|
+
*
|
|
16
|
+
* ## The gate is checked HERE for the affordance and AGAIN on the server
|
|
17
|
+
*
|
|
18
|
+
* 🛑 This copy decides what to OFFER. The server decides what to ACCEPT, and
|
|
19
|
+
* it is the authority — `raiseOrderDisputeAction` re-runs `checkDisputeGate`
|
|
20
|
+
* on the real order. Showing a kind the server would refuse is the dead-end
|
|
21
|
+
* shape (CLAUDE.md's rule for `OrderCompletionPanel`: never offer a button the
|
|
22
|
+
* server will refuse), and trusting this copy alone would be a client-side
|
|
23
|
+
* security gate.
|
|
24
|
+
*/
|
|
25
|
+
import { useCallback, useMemo, useState } from "react";
|
|
26
|
+
import { DISPUTE_KIND, DISPUTE_KIND_LABEL, checkDisputeGate, disputeNotReachedFilableFrom, } from "../../../_internal/shared/features/orders/dispute-reasons.js";
|
|
27
|
+
import { Alert, Button, Div, FieldTextarea, Form, Stack, Text, useToast } from "../../../ui/index.js";
|
|
28
|
+
import { PaginatedSelect } from "../../../ui/components/PaginatedSelect.js";
|
|
29
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
|
|
30
|
+
import { normalizeError } from "../../../errors/normalize.js";
|
|
31
|
+
export function RaiseDisputeForm({ order, reasonsEndpoint, onSubmit, onDone, }) {
|
|
32
|
+
const { showToast } = useToast();
|
|
33
|
+
const [kind, setKind] = useState(null);
|
|
34
|
+
const [reasonId, setReasonId] = useState(null);
|
|
35
|
+
const [detail, setDetail] = useState("");
|
|
36
|
+
const [isPending, setIsPending] = useState(false);
|
|
37
|
+
/*
|
|
38
|
+
* Which kinds this order can actually support, with the server's own gate.
|
|
39
|
+
* A kind that would be refused is shown DISABLED with the reason rather than
|
|
40
|
+
* hidden: "why can't I report this?" is a question the form should answer,
|
|
41
|
+
* and a silently missing option looks like a bug.
|
|
42
|
+
*/
|
|
43
|
+
const kindStates = useMemo(() => DISPUTE_KIND.map((k) => ({
|
|
44
|
+
kind: k,
|
|
45
|
+
label: DISPUTE_KIND_LABEL[k],
|
|
46
|
+
gate: checkDisputeGate(k, order),
|
|
47
|
+
})), [order]);
|
|
48
|
+
const filableFrom = useMemo(() => disputeNotReachedFilableFrom(order), [order]);
|
|
49
|
+
/*
|
|
50
|
+
* 🛑 Keyed on `kind` so switching kind refetches. `PaginatedSelect` caches
|
|
51
|
+
* per `loadOptions` identity, so a stable callback would keep showing the
|
|
52
|
+
* previous kind's reasons — which is the one thing that must never happen
|
|
53
|
+
* here, because a mismatched reason is refused by the server.
|
|
54
|
+
*/
|
|
55
|
+
const loadReasons = useCallback(async (query, page) => {
|
|
56
|
+
if (!kind)
|
|
57
|
+
return { items: [], hasMore: false };
|
|
58
|
+
const res = await fetch(`${reasonsEndpoint}?kind=${encodeURIComponent(kind)}`);
|
|
59
|
+
if (!res.ok) {
|
|
60
|
+
/*
|
|
61
|
+
* Surfaced, not swallowed into an empty list. An empty picker is
|
|
62
|
+
* indistinguishable from "there are no reasons", and the buyer would
|
|
63
|
+
* sit on a required field with nothing in it (Root Cause #59).
|
|
64
|
+
*/
|
|
65
|
+
throw new Error("Could not load the list of reasons. Please try again.");
|
|
66
|
+
}
|
|
67
|
+
const json = (await res.json());
|
|
68
|
+
const rows = json.data?.items ?? [];
|
|
69
|
+
const q = query.trim().toLowerCase();
|
|
70
|
+
const filtered = q ? rows.filter((r) => r.label.toLowerCase().includes(q)) : rows;
|
|
71
|
+
return {
|
|
72
|
+
// `items`, not `options` — checked against `AsyncPage` rather than
|
|
73
|
+
// guessed; the wrong key yields an empty picker with no error.
|
|
74
|
+
items: filtered.map((r) => ({ value: r.id, label: r.label })),
|
|
75
|
+
// The route caps at 50 and the list is short by design; there is no
|
|
76
|
+
// second page to promise. `page` is unused for the same reason.
|
|
77
|
+
hasMore: false,
|
|
78
|
+
};
|
|
79
|
+
}, [kind, reasonsEndpoint]);
|
|
80
|
+
const chooseKind = (next) => {
|
|
81
|
+
setKind(next);
|
|
82
|
+
// Clearing is required: a reason from the previous kind would be refused.
|
|
83
|
+
setReasonId(null);
|
|
84
|
+
};
|
|
85
|
+
const submit = async () => {
|
|
86
|
+
if (!kind || !reasonId)
|
|
87
|
+
return;
|
|
88
|
+
setIsPending(true);
|
|
89
|
+
try {
|
|
90
|
+
await onSubmit({ kind, reasonId, detail: detail.trim() || undefined });
|
|
91
|
+
showToast("Thanks — we've recorded it and will look into it.", "success");
|
|
92
|
+
onDone?.();
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
// The server's message names the next action; show it verbatim rather
|
|
96
|
+
// than replacing it with a generic failure.
|
|
97
|
+
showToast(normalizeError(err).message, "error");
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
setIsPending(false);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
return (_jsx(Form, { onSubmit: (e) => e.preventDefault(), children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "What's the problem?" }), kindStates.map((k) => (_jsxs(Div, { children: [_jsx(Button, { variant: kind === k.kind ? "primary" : "secondary", disabled: !k.gate.allowed, onClick: () => chooseKind(k.kind), children: k.label }), !k.gate.allowed ? (_jsx(Text, { size: "xs", color: "muted", children: k.gate.message })) : null] }, k.kind)))] }), kind ? (_jsxs(_Fragment, { children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Reason" }), _jsx(PaginatedSelect, { ariaLabel: "Reason for the dispute",
|
|
104
|
+
/*
|
|
105
|
+
* 🛑 `?? null`, not `?? ""`. PaginatedSelect renders its
|
|
106
|
+
* placeholder only on `value == null`; an empty string paints a
|
|
107
|
+
* BLANK trigger with no prompt, which reads as a broken control
|
|
108
|
+
* rather than an unmade choice.
|
|
109
|
+
*/
|
|
110
|
+
value: reasonId ?? null, onChange: (v) => setReasonId(v), loadOptions: loadReasons, placeholder: "Pick the closest match\u2026" }), _jsx(FieldTextarea, { name: "detail", label: "What happened? (optional)", value: detail, onChange: setDetail, maxLength: 2000 }), kind === "not_reached" && filableFrom ? (_jsx(Alert, { variant: "info", children: `We'll chase the courier. If it turns up before we finish, let us know.` })) : null, _jsx(Button, { type: "submit", action: ACTIONS.SUPPORT["raise-dispute"], isLoading: isPending, disabled: !reasonId, onClick: submit, children: "Report this" })] })) : null] }) }));
|
|
111
|
+
}
|
|
@@ -28,11 +28,19 @@ import { FormShellContext, useFormShellState, FormErrorSummary, FieldTextarea, }
|
|
|
28
28
|
import { SectionForm, useSectionFormNav } from "../../shell/index.js";
|
|
29
29
|
import { z } from "zod";
|
|
30
30
|
import { REFUND_COPY } from "../../../_internal/shared/features/orders/refund-copy.js";
|
|
31
|
+
import { resolvePublishedCopy, resolvePublishedValues, } from "../../../_internal/shared/content/published-values.js";
|
|
31
32
|
import { RETURN_REASON_LABEL, selectableReturnReasons, } from "../../../_internal/shared/features/orders/return-reasons.js";
|
|
32
33
|
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
33
34
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
34
35
|
import { toUserMessage } from "../../../errors/error-display-map.js";
|
|
35
36
|
const ACK_COUNT = REFUND_COPY.request.acknowledgments.length;
|
|
37
|
+
/*
|
|
38
|
+
* Phase 9 — code-derived values, resolved once. No settings argument: the only
|
|
39
|
+
* token in this copy is the refund timeline, which has no setting (see the 🛑
|
|
40
|
+
* on `refundBusinessDaysMin` in the registry), so passing settings would imply
|
|
41
|
+
* an editable number that does not exist.
|
|
42
|
+
*/
|
|
43
|
+
const PUBLISHED_VALUES = resolvePublishedValues();
|
|
36
44
|
export function RefundRequestView({ order, onSubmitRequest, isLoading = false, className = "", }) {
|
|
37
45
|
/*
|
|
38
46
|
* Does anything in this order carry the final-sale term? Read from the
|
|
@@ -71,7 +79,7 @@ export function RefundRequestView({ order, onSubmitRequest, isLoading = false, c
|
|
|
71
79
|
label: "Why are you returning this?",
|
|
72
80
|
required: true,
|
|
73
81
|
fields: ["reasonCode", "reasonNote"],
|
|
74
|
-
render: ({ values, onChange }) => (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { size: "sm", color: "muted", children: [REFUND_COPY.request.orderTotalLabel, " ", _jsx(Span, { weight: "bold", children: formatCurrency(order.total, order.currency ?? "INR") })] }), hasFinalSaleLine && (_jsx(Alert, { variant: "warning", title: REFUND_COPY.request.finalSaleNoticeHeading, children: "You can\u2019t return it for a change of mind, but you can still claim if it never arrived, arrived damaged, was the wrong item, wasn\u2019t as described, or is counterfeit. Only those reasons are listed below." })), _jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "sm", weight: "semibold", children: [REFUND_COPY.request.reasonSelectLabel, " ", _jsx(Span, { "aria-hidden": true, children: "*" })] }), _jsx(PaginatedSelect, { value: values.reasonCode === "" ? null : values.reasonCode, onChange: (v) => onChange({ reasonCode: v ?? "" }), options: allowedReasons.map((r) => ({
|
|
82
|
+
render: ({ values, onChange, errors }) => (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { size: "sm", color: "muted", children: [REFUND_COPY.request.orderTotalLabel, " ", _jsx(Span, { weight: "bold", children: formatCurrency(order.total, order.currency ?? "INR") })] }), hasFinalSaleLine && (_jsx(Alert, { variant: "warning", title: REFUND_COPY.request.finalSaleNoticeHeading, children: "You can\u2019t return it for a change of mind, but you can still claim if it never arrived, arrived damaged, was the wrong item, wasn\u2019t as described, or is counterfeit. Only those reasons are listed below." })), _jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "sm", weight: "semibold", children: [REFUND_COPY.request.reasonSelectLabel, " ", _jsx(Span, { "aria-hidden": true, children: "*" })] }), _jsx(PaginatedSelect, { name: "reasonCode", error: errors.reasonCode, value: values.reasonCode === "" ? null : values.reasonCode, onChange: (v) => onChange({ reasonCode: v ?? "" }), options: allowedReasons.map((r) => ({
|
|
75
83
|
value: r,
|
|
76
84
|
label: RETURN_REASON_LABEL[r],
|
|
77
85
|
})), placeholder: REFUND_COPY.request.reasonSelectPlaceholder, ariaLabel: REFUND_COPY.request.reasonSelectLabel })] }), _jsx(FieldTextarea, { name: "reasonNote", label: REFUND_COPY.request.reasonNoteLabel, value: values.reasonNote, onChange: (v) => onChange({ reasonNote: v }), rows: 3, maxLength: 500, showCharCount: true })] })),
|
|
@@ -81,9 +89,19 @@ export function RefundRequestView({ order, onSubmitRequest, isLoading = false, c
|
|
|
81
89
|
label: REFUND_COPY.request.acknowledgeHeading,
|
|
82
90
|
required: true,
|
|
83
91
|
fields: ["acknowledged"],
|
|
84
|
-
render: ({ values, onChange }) => (
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
render: ({ values, onChange, errors }) => (_jsxs(Stack, { gap: "sm", children: [REFUND_COPY.request.acknowledgments.map((text, i) => (_jsx(Checkbox, { checked: values.acknowledged[i] ?? false, onChange: () => onChange({
|
|
93
|
+
acknowledged: values.acknowledged.map((v, idx) => idx === i ? !v : v),
|
|
94
|
+
}),
|
|
95
|
+
/*
|
|
96
|
+
* 🛑 Phase 9 — resolved, because this is an ACKNOWLEDGEMENT
|
|
97
|
+
* the buyer ticks. `REFUND_COPY`'s refund-timeline line now
|
|
98
|
+
* carries `{{refundBusinessDaysMin}}`/`Max` so it has one
|
|
99
|
+
* owner instead of being the third of three contradicting
|
|
100
|
+
* statements — and an unresolved token inside something a
|
|
101
|
+
* buyer is agreeing to is the strongest reason the unknown-key
|
|
102
|
+
* rule leaves `{{…}}` VISIBLE rather than blanking it.
|
|
103
|
+
*/
|
|
104
|
+
label: _jsx(Text, { size: "sm", className: "leading-snug", children: resolvePublishedCopy(text, PUBLISHED_VALUES) }), "aria-label": `Acknowledgment ${i + 1}`, "aria-invalid": errors.acknowledged ? "true" : undefined }, i))), errors.acknowledged && (_jsx(Text, { size: "sm", variant: "error", role: "alert", children: errors.acknowledged }))] })),
|
|
87
105
|
},
|
|
88
106
|
], [order.total, order.currency, hasFinalSaleLine, allowedReasons]);
|
|
89
107
|
const nav = useSectionFormNav(sections, draft);
|
|
@@ -6,6 +6,10 @@ export { OrderSiblingPayments } from "./OrderSiblingPayments";
|
|
|
6
6
|
export type { OrderSiblingPaymentsProps } from "./OrderSiblingPayments";
|
|
7
7
|
export { OrderPaymentSummary } from "./OrderPaymentSummary";
|
|
8
8
|
export type { OrderPaymentSummaryProps } from "./OrderPaymentSummary";
|
|
9
|
+
export { OrderProvenanceCard } from "./OrderProvenanceCard";
|
|
10
|
+
export type { OrderProvenanceCardProps } from "./OrderProvenanceCard";
|
|
11
|
+
export { RaiseDisputeForm } from "./RaiseDisputeForm";
|
|
12
|
+
export type { RaiseDisputeFormProps } from "./RaiseDisputeForm";
|
|
9
13
|
export { OrderStatusTimeline } from "./OrderStatusTimeline";
|
|
10
14
|
export type { OrderStatusTimelineProps } from "./OrderStatusTimeline";
|
|
11
15
|
export { RefundHistoryTable } from "./RefundHistoryTable";
|
|
@@ -3,6 +3,8 @@ export { OrderAddonBadges, } from "./OrderAddonBadges.js";
|
|
|
3
3
|
export { OrderFilters, ORDER_FILTER_KEYS, ORDER_ADMIN_SORT_OPTIONS, ORDER_SELLER_SORT_OPTIONS, ORDER_USER_SORT_OPTIONS, getOrderFilterKeys, getOrderSortOptions, } from "./OrderFilters.js";
|
|
4
4
|
export { OrderSiblingPayments } from "./OrderSiblingPayments.js";
|
|
5
5
|
export { OrderPaymentSummary } from "./OrderPaymentSummary.js";
|
|
6
|
+
export { OrderProvenanceCard } from "./OrderProvenanceCard.js";
|
|
7
|
+
export { RaiseDisputeForm } from "./RaiseDisputeForm.js";
|
|
6
8
|
export { OrderStatusTimeline } from "./OrderStatusTimeline.js";
|
|
7
9
|
export { RefundHistoryTable } from "./RefundHistoryTable.js";
|
|
8
10
|
export { RefundRequestView } from "./RefundRequestView.js";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Order PROVENANCE tabs — the one definition, shared by the buyer's and the
|
|
3
|
+
* seller's order lists.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this file exists
|
|
6
|
+
*
|
|
7
|
+
* The buyer's list had these tabs and the seller's did not. A seller could see
|
|
8
|
+
* every order and could not ask "show me the ones I accepted an offer on" or
|
|
9
|
+
* "show me the auction wins", which is exactly the split that changes how they
|
|
10
|
+
* are handled: an auction win has a forfeit deadline, an accepted offer has an
|
|
11
|
+
* agreed price that is not the listing price.
|
|
12
|
+
*
|
|
13
|
+
* Copying the array into the second view would have been the ninth
|
|
14
|
+
* hand-written enumeration of one union in this codebase (Root Cause #61), so
|
|
15
|
+
* the labels are a `Record<OrderType, string>` instead: a sixth lane is a
|
|
16
|
+
* COMPILE error here rather than a tab that silently never appears.
|
|
17
|
+
*
|
|
18
|
+
* 🛑 Three things about these ids, each of which has already cost a bug:
|
|
19
|
+
*
|
|
20
|
+
* 1. **Every id is EXACTLY a stored `OrderDocument.orderType` value.** Firestore
|
|
21
|
+
* `==` is byte-exact, so a tab id that is merely plausible returns zero rows
|
|
22
|
+
* forever with no error anywhere (Root Cause #33).
|
|
23
|
+
*
|
|
24
|
+
* 2. **`preorder` has NO HYPHEN and that is not a typo to tidy up.** `OrderType`
|
|
25
|
+
* spells it `preorder`; `ListingType` spells the same concept `pre-order`.
|
|
26
|
+
* Two unions, two spellings, and the tab must match the one actually stored.
|
|
27
|
+
*
|
|
28
|
+
* 3. **`standard` must be filtered IN MEMORY, never as a Firestore `==`.**
|
|
29
|
+
* Orders written before `orderType` existed carry no such field, and an
|
|
30
|
+
* equality excludes every document that lacks it — so the "Normal" tab would
|
|
31
|
+
* hide every legacy order while looking like it worked. `mergeOrderScopeFilter`
|
|
32
|
+
* and the server-side list both already honour this; see `ORDER_LANE_ALL` for
|
|
33
|
+
* the "no filter" sentinel.
|
|
34
|
+
*/
|
|
35
|
+
import type { OrderType } from "../utils/order-splitter";
|
|
36
|
+
/**
|
|
37
|
+
* The "All" tab's id. An empty string rather than a word, so it can never
|
|
38
|
+
* collide with a real `orderType` value and so `state.orderType ? … : null`
|
|
39
|
+
* reads as "no filter" at every call site.
|
|
40
|
+
*/
|
|
41
|
+
export declare const ORDER_LANE_ALL: "";
|
|
42
|
+
/**
|
|
43
|
+
* 🛑 A `Record<OrderType, string>`, deliberately. Omitting a lane does not
|
|
44
|
+
* compile, which is the whole point — the buyer's list shipped with four tabs
|
|
45
|
+
* for five lanes, so pre-order and prize-draw orders were reachable only by
|
|
46
|
+
* landing on "All" and scrolling.
|
|
47
|
+
*/
|
|
48
|
+
export declare const ORDER_LANE_LABELS: Record<OrderType, string>;
|
|
49
|
+
/**
|
|
50
|
+
* The stored `orderType` values a lane filter may name, for route-side
|
|
51
|
+
* validation.
|
|
52
|
+
*
|
|
53
|
+
* Derived from `ORDER_LANE_LABELS`, so it cannot drift from the tabs. Both
|
|
54
|
+
* `/api/user/orders` and `/api/store/orders` had — or would have needed — their
|
|
55
|
+
* own hand-written copy of these five strings; this is the eleventh
|
|
56
|
+
* enumeration of one union in this codebase collapsed into the union itself
|
|
57
|
+
* (Root Cause #61).
|
|
58
|
+
*
|
|
59
|
+
* 🛑 Validating against it is load-bearing, not hygiene: an unrecognised value
|
|
60
|
+
* interpolated into a Sieve filter string is a caller-controlled field name
|
|
61
|
+
* reaching the query builder.
|
|
62
|
+
*/
|
|
63
|
+
export declare const VALID_ORDER_TYPES: ReadonlySet<string>;
|
|
64
|
+
export interface OrderLaneTab {
|
|
65
|
+
id: string;
|
|
66
|
+
label: string;
|
|
67
|
+
}
|
|
68
|
+
/** `All` first, then every lane in `LANE_ORDER`. */
|
|
69
|
+
export declare const ORDER_LANE_TABS: readonly OrderLaneTab[];
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Order PROVENANCE tabs — the one definition, shared by the buyer's and the
|
|
3
|
+
* seller's order lists.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this file exists
|
|
6
|
+
*
|
|
7
|
+
* The buyer's list had these tabs and the seller's did not. A seller could see
|
|
8
|
+
* every order and could not ask "show me the ones I accepted an offer on" or
|
|
9
|
+
* "show me the auction wins", which is exactly the split that changes how they
|
|
10
|
+
* are handled: an auction win has a forfeit deadline, an accepted offer has an
|
|
11
|
+
* agreed price that is not the listing price.
|
|
12
|
+
*
|
|
13
|
+
* Copying the array into the second view would have been the ninth
|
|
14
|
+
* hand-written enumeration of one union in this codebase (Root Cause #61), so
|
|
15
|
+
* the labels are a `Record<OrderType, string>` instead: a sixth lane is a
|
|
16
|
+
* COMPILE error here rather than a tab that silently never appears.
|
|
17
|
+
*
|
|
18
|
+
* 🛑 Three things about these ids, each of which has already cost a bug:
|
|
19
|
+
*
|
|
20
|
+
* 1. **Every id is EXACTLY a stored `OrderDocument.orderType` value.** Firestore
|
|
21
|
+
* `==` is byte-exact, so a tab id that is merely plausible returns zero rows
|
|
22
|
+
* forever with no error anywhere (Root Cause #33).
|
|
23
|
+
*
|
|
24
|
+
* 2. **`preorder` has NO HYPHEN and that is not a typo to tidy up.** `OrderType`
|
|
25
|
+
* spells it `preorder`; `ListingType` spells the same concept `pre-order`.
|
|
26
|
+
* Two unions, two spellings, and the tab must match the one actually stored.
|
|
27
|
+
*
|
|
28
|
+
* 3. **`standard` must be filtered IN MEMORY, never as a Firestore `==`.**
|
|
29
|
+
* Orders written before `orderType` existed carry no such field, and an
|
|
30
|
+
* equality excludes every document that lacks it — so the "Normal" tab would
|
|
31
|
+
* hide every legacy order while looking like it worked. `mergeOrderScopeFilter`
|
|
32
|
+
* and the server-side list both already honour this; see `ORDER_LANE_ALL` for
|
|
33
|
+
* the "no filter" sentinel.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* The "All" tab's id. An empty string rather than a word, so it can never
|
|
37
|
+
* collide with a real `orderType` value and so `state.orderType ? … : null`
|
|
38
|
+
* reads as "no filter" at every call site.
|
|
39
|
+
*/
|
|
40
|
+
export const ORDER_LANE_ALL = "";
|
|
41
|
+
/**
|
|
42
|
+
* 🛑 A `Record<OrderType, string>`, deliberately. Omitting a lane does not
|
|
43
|
+
* compile, which is the whole point — the buyer's list shipped with four tabs
|
|
44
|
+
* for five lanes, so pre-order and prize-draw orders were reachable only by
|
|
45
|
+
* landing on "All" and scrolling.
|
|
46
|
+
*/
|
|
47
|
+
export const ORDER_LANE_LABELS = {
|
|
48
|
+
standard: "Normal",
|
|
49
|
+
auction: "Auction wins",
|
|
50
|
+
offer: "Offer wins",
|
|
51
|
+
preorder: "Pre-orders",
|
|
52
|
+
"prize-draw": "Prize draws",
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Display order, listed explicitly rather than taken from `Object.keys`.
|
|
56
|
+
*
|
|
57
|
+
* Key order on an object literal is not a contract anyone should rely on for
|
|
58
|
+
* UI ordering, and the intended order is not alphabetical: it mirrors the
|
|
59
|
+
* cart's lane PRIORITY (auction > offer > standard — highest obligation
|
|
60
|
+
* first), with the two schedule-driven lanes after them.
|
|
61
|
+
*/
|
|
62
|
+
const LANE_ORDER = [
|
|
63
|
+
"standard",
|
|
64
|
+
"auction",
|
|
65
|
+
"offer",
|
|
66
|
+
"preorder",
|
|
67
|
+
"prize-draw",
|
|
68
|
+
];
|
|
69
|
+
/**
|
|
70
|
+
* The stored `orderType` values a lane filter may name, for route-side
|
|
71
|
+
* validation.
|
|
72
|
+
*
|
|
73
|
+
* Derived from `ORDER_LANE_LABELS`, so it cannot drift from the tabs. Both
|
|
74
|
+
* `/api/user/orders` and `/api/store/orders` had — or would have needed — their
|
|
75
|
+
* own hand-written copy of these five strings; this is the eleventh
|
|
76
|
+
* enumeration of one union in this codebase collapsed into the union itself
|
|
77
|
+
* (Root Cause #61).
|
|
78
|
+
*
|
|
79
|
+
* 🛑 Validating against it is load-bearing, not hygiene: an unrecognised value
|
|
80
|
+
* interpolated into a Sieve filter string is a caller-controlled field name
|
|
81
|
+
* reaching the query builder.
|
|
82
|
+
*/
|
|
83
|
+
export const VALID_ORDER_TYPES = new Set(Object.keys(ORDER_LANE_LABELS));
|
|
84
|
+
/** `All` first, then every lane in `LANE_ORDER`. */
|
|
85
|
+
export const ORDER_LANE_TABS = [
|
|
86
|
+
{ id: ORDER_LANE_ALL, label: "All" },
|
|
87
|
+
...LANE_ORDER.map((type) => ({ id: type, label: ORDER_LANE_LABELS[type] })),
|
|
88
|
+
];
|
|
@@ -196,6 +196,10 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
|
|
|
196
196
|
canFilter: boolean;
|
|
197
197
|
canSort: boolean;
|
|
198
198
|
};
|
|
199
|
+
orderType: {
|
|
200
|
+
canFilter: boolean;
|
|
201
|
+
canSort: boolean;
|
|
202
|
+
};
|
|
199
203
|
giftWrapAddon: {
|
|
200
204
|
canFilter: boolean;
|
|
201
205
|
canSort: boolean;
|
|
@@ -404,6 +408,32 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
|
|
|
404
408
|
status: "failed";
|
|
405
409
|
failureReason?: string;
|
|
406
410
|
}, becomeRefunded?: boolean, ctx?: OrderWriteContext): Promise<OrderDocument>;
|
|
411
|
+
/**
|
|
412
|
+
* Stamp a credit-note number onto one settled refund event (Phase 7, 7c).
|
|
413
|
+
*
|
|
414
|
+
* ## 🛑 Why this is a repository method and not an inline update
|
|
415
|
+
*
|
|
416
|
+
* `refunds[]` is written by re-mapping the whole array, exactly as
|
|
417
|
+
* `settleRefund` directly above does — because a read-modify-write of
|
|
418
|
+
* `[...existing, event]` LOSES a concurrent event (D6), and that defect is
|
|
419
|
+
* just as reachable from a credit-note stamp as from a refund append.
|
|
420
|
+
* Letting the invoicing feature touch the array directly would reintroduce
|
|
421
|
+
* it one caller away from the fix.
|
|
422
|
+
*
|
|
423
|
+
* It goes through `updateWithHistory` for the same reason every other
|
|
424
|
+
* mutator here does: the transition guard and the status-history entry live
|
|
425
|
+
* there, and this is a real recorded event on the order.
|
|
426
|
+
*
|
|
427
|
+
* 🛑 A missing refund event is a NO-OP, not a throw. The caller is the
|
|
428
|
+
* refund settle path, where money has already moved; raising here would turn
|
|
429
|
+
* a successful refund into a reported failure. See
|
|
430
|
+
* `issueCreditNoteForRefund`'s own 🛑.
|
|
431
|
+
*/
|
|
432
|
+
setRefundCreditNote(orderId: string, refundId: string, note: {
|
|
433
|
+
creditNoteNumber: string;
|
|
434
|
+
creditNoteIssuedAt: Date;
|
|
435
|
+
creditNoteStoragePath?: string;
|
|
436
|
+
}, ctx?: OrderWriteContext): Promise<OrderDocument | null>;
|
|
407
437
|
/**
|
|
408
438
|
* 🛑 LEGACY — prefer `reserveRefund` + `settleRefund`.
|
|
409
439
|
*
|
|
@@ -442,6 +472,33 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
|
|
|
442
472
|
* `paymentProofUrl != null` filter would silently exclude every doc where
|
|
443
473
|
* the field isn't set at all — the opposite of what's needed here.
|
|
444
474
|
*/
|
|
475
|
+
/**
|
|
476
|
+
* Orders invoiced within a window — the GSTR-1 export's only query
|
|
477
|
+
* (Phase 7d).
|
|
478
|
+
*
|
|
479
|
+
* ## 🛑 Windowed on `invoiceIssuedAt`, not on the order date
|
|
480
|
+
*
|
|
481
|
+
* A COD order placed on 30 March and paid on 2 April is invoiced in the NEW
|
|
482
|
+
* financial year, and that is the period its tax belongs to. Numbering
|
|
483
|
+
* happens at the payment transition for exactly this reason, so the invoice
|
|
484
|
+
* date is the only correct axis — windowing on `orderDate` would file that
|
|
485
|
+
* order's tax in the year before it was invoiced.
|
|
486
|
+
*
|
|
487
|
+
* ## 🛑 It also returns PAID orders with no invoice number, deliberately
|
|
488
|
+
*
|
|
489
|
+
* Those are the ones the export must warn about rather than omit — a paid
|
|
490
|
+
* order silently absent from a tax return is the single error this whole
|
|
491
|
+
* feature exists to prevent. Firestore cannot OR across two fields, so the
|
|
492
|
+
* second query is a separate one and the caller merges; the alternative
|
|
493
|
+
* (one query on `paymentStatus`, filtered in memory) would scan every paid
|
|
494
|
+
* order ever placed.
|
|
495
|
+
*
|
|
496
|
+
* Unbounded `.limit(2000)`: a quarter's orders at this volume is a few
|
|
497
|
+
* hundred, and an export that silently truncated would understate a tax
|
|
498
|
+
* return. If this ever saturates, the fix is pagination with an explicit
|
|
499
|
+
* "more rows exist" signal in the CSV — never a higher cap.
|
|
500
|
+
*/
|
|
501
|
+
listInvoicedBetween(from: Date, to: Date): Promise<OrderDocument[]>;
|
|
445
502
|
getExpiredPaymentDeadlines(): Promise<Array<{
|
|
446
503
|
id: string;
|
|
447
504
|
ref: DocumentReference;
|