@mohasinac/appkit 4.62.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/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/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 +28 -0
- 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/client.d.ts +11 -0
- package/dist/client.js +30 -0
- package/dist/constants/api-endpoints.d.ts +42 -0
- package/dist/constants/api-endpoints.js +14 -0
- package/dist/constants/field-names.d.ts +1 -0
- package/dist/constants/field-names.js +5 -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/features/about/components/AboutView.js +1 -1
- package/dist/features/account/components/BuyerAuctionsGuideView.js +4 -1
- package/dist/features/account/components/UserOrdersView.js +19 -33
- package/dist/features/admin/components/AdminCouponEditorView.js +17 -0
- package/dist/features/admin/components/AdminInvoicingView.d.ts +2 -0
- package/dist/features/admin/components/AdminInvoicingView.js +65 -0
- 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/OwnerQuickActions.js +24 -2
- 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/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/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/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/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/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/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 +19 -1
- 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 +54 -6
- 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/PrizeDrawDetailPageView.js +2 -2
- 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/SellerOrdersView.js +129 -27
- package/dist/features/seller/hooks/useSellerListingData.js +25 -0
- 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/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/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +14 -1
- package/dist/next/routing/route-map.d.ts +20 -0
- package/dist/next/routing/route-map.js +10 -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 +47 -18
- package/dist/schemas/sieve.d.ts +2 -2
- package/dist/server-entry.d.ts +2 -2
- package/dist/server-entry.js +8 -1
- package/dist/server.d.ts +10 -1
- package/dist/server.js +39 -1
- 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/Disclaimer.d.ts +19 -0
- package/dist/ui/components/Disclaimer.js +74 -0
- package/dist/ui/components/tabs-overflow.js +64 -11
- 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,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;
|
|
@@ -783,6 +783,52 @@ class OrderRepository extends BaseRepository {
|
|
|
783
783
|
reason: ctx?.reason ?? target.reason,
|
|
784
784
|
}, "orderRepository.settleRefund", order);
|
|
785
785
|
}
|
|
786
|
+
/**
|
|
787
|
+
* Stamp a credit-note number onto one settled refund event (Phase 7, 7c).
|
|
788
|
+
*
|
|
789
|
+
* ## 🛑 Why this is a repository method and not an inline update
|
|
790
|
+
*
|
|
791
|
+
* `refunds[]` is written by re-mapping the whole array, exactly as
|
|
792
|
+
* `settleRefund` directly above does — because a read-modify-write of
|
|
793
|
+
* `[...existing, event]` LOSES a concurrent event (D6), and that defect is
|
|
794
|
+
* just as reachable from a credit-note stamp as from a refund append.
|
|
795
|
+
* Letting the invoicing feature touch the array directly would reintroduce
|
|
796
|
+
* it one caller away from the fix.
|
|
797
|
+
*
|
|
798
|
+
* It goes through `updateWithHistory` for the same reason every other
|
|
799
|
+
* mutator here does: the transition guard and the status-history entry live
|
|
800
|
+
* there, and this is a real recorded event on the order.
|
|
801
|
+
*
|
|
802
|
+
* 🛑 A missing refund event is a NO-OP, not a throw. The caller is the
|
|
803
|
+
* refund settle path, where money has already moved; raising here would turn
|
|
804
|
+
* a successful refund into a reported failure. See
|
|
805
|
+
* `issueCreditNoteForRefund`'s own 🛑.
|
|
806
|
+
*/
|
|
807
|
+
async setRefundCreditNote(orderId, refundId, note, ctx) {
|
|
808
|
+
const order = await this.findById(orderId);
|
|
809
|
+
if (!order)
|
|
810
|
+
return null;
|
|
811
|
+
const existing = order.refunds ?? [];
|
|
812
|
+
const target = existing.find((r) => r.refundId === refundId);
|
|
813
|
+
if (!target)
|
|
814
|
+
return null;
|
|
815
|
+
// Already numbered — never overwrite. A credit-note number is issued once,
|
|
816
|
+
// and a second number for one reversal is a permanent gap in the series.
|
|
817
|
+
if (target.creditNoteNumber)
|
|
818
|
+
return order;
|
|
819
|
+
const stamped = existing.map((r) => r.refundId === refundId ? { ...r, ...note } : r);
|
|
820
|
+
return this.updateWithHistory(orderId, { refunds: stamped, updatedAt: new Date() }, {
|
|
821
|
+
...ctx,
|
|
822
|
+
extraChanges: {
|
|
823
|
+
creditNote: {
|
|
824
|
+
from: null,
|
|
825
|
+
to: { refundId, creditNoteNumber: note.creditNoteNumber },
|
|
826
|
+
},
|
|
827
|
+
},
|
|
828
|
+
actor: ctx?.actor ?? { uid: "system:invoicing", role: "system" },
|
|
829
|
+
reason: ctx?.reason ?? `Credit note ${note.creditNoteNumber} issued`,
|
|
830
|
+
}, "orderRepository.setRefundCreditNote", order);
|
|
831
|
+
}
|
|
786
832
|
/**
|
|
787
833
|
* 🛑 LEGACY — prefer `reserveRefund` + `settleRefund`.
|
|
788
834
|
*
|
|
@@ -878,6 +924,68 @@ class OrderRepository extends BaseRepository {
|
|
|
878
924
|
* `paymentProofUrl != null` filter would silently exclude every doc where
|
|
879
925
|
* the field isn't set at all — the opposite of what's needed here.
|
|
880
926
|
*/
|
|
927
|
+
/**
|
|
928
|
+
* Orders invoiced within a window — the GSTR-1 export's only query
|
|
929
|
+
* (Phase 7d).
|
|
930
|
+
*
|
|
931
|
+
* ## 🛑 Windowed on `invoiceIssuedAt`, not on the order date
|
|
932
|
+
*
|
|
933
|
+
* A COD order placed on 30 March and paid on 2 April is invoiced in the NEW
|
|
934
|
+
* financial year, and that is the period its tax belongs to. Numbering
|
|
935
|
+
* happens at the payment transition for exactly this reason, so the invoice
|
|
936
|
+
* date is the only correct axis — windowing on `orderDate` would file that
|
|
937
|
+
* order's tax in the year before it was invoiced.
|
|
938
|
+
*
|
|
939
|
+
* ## 🛑 It also returns PAID orders with no invoice number, deliberately
|
|
940
|
+
*
|
|
941
|
+
* Those are the ones the export must warn about rather than omit — a paid
|
|
942
|
+
* order silently absent from a tax return is the single error this whole
|
|
943
|
+
* feature exists to prevent. Firestore cannot OR across two fields, so the
|
|
944
|
+
* second query is a separate one and the caller merges; the alternative
|
|
945
|
+
* (one query on `paymentStatus`, filtered in memory) would scan every paid
|
|
946
|
+
* order ever placed.
|
|
947
|
+
*
|
|
948
|
+
* Unbounded `.limit(2000)`: a quarter's orders at this volume is a few
|
|
949
|
+
* hundred, and an export that silently truncated would understate a tax
|
|
950
|
+
* return. If this ever saturates, the fix is pagination with an explicit
|
|
951
|
+
* "more rows exist" signal in the CSV — never a higher cap.
|
|
952
|
+
*/
|
|
953
|
+
async listInvoicedBetween(from, to) {
|
|
954
|
+
const [invoiced, paidUnnumbered] = await Promise.all([
|
|
955
|
+
this.db
|
|
956
|
+
.collection(this.collection)
|
|
957
|
+
.where("invoiceIssuedAt", ">=", from)
|
|
958
|
+
.where("invoiceIssuedAt", "<=", to)
|
|
959
|
+
.limit(2000)
|
|
960
|
+
.get(),
|
|
961
|
+
/*
|
|
962
|
+
* The exceptions. Bounded on `orderDate` because an un-invoiced order
|
|
963
|
+
* has no invoice date to window on — which is the whole problem with it
|
|
964
|
+
* — and because `paymentStatus == paid` alone would match every paid
|
|
965
|
+
* order in the collection's history.
|
|
966
|
+
*/
|
|
967
|
+
this.db
|
|
968
|
+
.collection(this.collection)
|
|
969
|
+
.where(ORDER_FIELDS.PAYMENT_STATUS, "==", "paid")
|
|
970
|
+
.where(ORDER_FIELDS.ORDER_DATE, ">=", from)
|
|
971
|
+
.where(ORDER_FIELDS.ORDER_DATE, "<=", to)
|
|
972
|
+
.limit(2000)
|
|
973
|
+
.get(),
|
|
974
|
+
]);
|
|
975
|
+
const byId = new Map();
|
|
976
|
+
for (const d of invoiced.docs) {
|
|
977
|
+
byId.set(d.id, { id: d.id, ...d.data() });
|
|
978
|
+
}
|
|
979
|
+
for (const d of paidUnnumbered.docs) {
|
|
980
|
+
const data = { id: d.id, ...d.data() };
|
|
981
|
+
// Only the genuinely un-numbered ones — a paid, numbered order is
|
|
982
|
+
// already in the first set, and adding it again would double every one
|
|
983
|
+
// of its rows in the return.
|
|
984
|
+
if (!data.invoiceNumber && !byId.has(d.id))
|
|
985
|
+
byId.set(d.id, data);
|
|
986
|
+
}
|
|
987
|
+
return [...byId.values()];
|
|
988
|
+
}
|
|
881
989
|
async getExpiredPaymentDeadlines() {
|
|
882
990
|
const snap = await this.db
|
|
883
991
|
.collection(this.collection)
|
|
@@ -1056,6 +1164,27 @@ class OrderRepository extends BaseRepository {
|
|
|
1056
1164
|
* early. Withholding is recoverable — the next sweep picks it up;
|
|
1057
1165
|
* releasing early is not, because the money has gone.
|
|
1058
1166
|
*/
|
|
1167
|
+
/*
|
|
1168
|
+
* 🛑 A BUYER'S OWN CONFIRMATION RELEASES THE PAYOUT IMMEDIATELY.
|
|
1169
|
+
*
|
|
1170
|
+
* The owner's rule: an order is CONFIRMED when it is delivered and
|
|
1171
|
+
* either the buyer says they received it, or 7 days pass. This filter
|
|
1172
|
+
* only ever implemented the second half — `deliveryDate <= cutoff` —
|
|
1173
|
+
* so a buyer who pressed "Mark complete" on day one still made their
|
|
1174
|
+
* seller wait out the full hold. The fast path existed and bought the
|
|
1175
|
+
* seller nothing.
|
|
1176
|
+
*
|
|
1177
|
+
* Safe in the direction that matters, and not by luck:
|
|
1178
|
+
* `markOrderCompleteAction` is the only writer of `completedAt`, and it
|
|
1179
|
+
* REFUSES while a return in any state, a posted refund or a buyer
|
|
1180
|
+
* dispute is live — the same precondition the `order-complete`
|
|
1181
|
+
* obligation carries. So honouring it here cannot release money while
|
|
1182
|
+
* an issue is open; the hold is skipped only because the one party the
|
|
1183
|
+
* hold protects has said they are satisfied.
|
|
1184
|
+
*/
|
|
1185
|
+
const completedAt = toDateOrNull(o.data.completedAt);
|
|
1186
|
+
if (completedAt)
|
|
1187
|
+
return true;
|
|
1059
1188
|
const anchor = toDateOrNull(o.data.deliveryDate) ?? toDateOrNull(o.data.updatedAt);
|
|
1060
1189
|
if (!o.data.deliveryDate)
|
|
1061
1190
|
missingAnchor++;
|
|
@@ -1162,6 +1291,22 @@ OrderRepository.SELLER_SIEVE_FIELDS = {
|
|
|
1162
1291
|
orderDate: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
|
|
1163
1292
|
createdAt: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
|
|
1164
1293
|
contestable: { canFilter: true, canSort: false },
|
|
1294
|
+
/*
|
|
1295
|
+
* Lane segregation on the SELLER's order list — the same provenance axis
|
|
1296
|
+
* `/user/orders` has had.
|
|
1297
|
+
*
|
|
1298
|
+
* 🛑 It was absent, and absence here is SILENT: `sieve.ts` sets
|
|
1299
|
+
* `throwExceptions: false`, so an unallowlisted field does not raise —
|
|
1300
|
+
* `findField` returns undefined and the clause is simply dropped. A lane
|
|
1301
|
+
* chip emitting `orderType==auction` would therefore have returned the
|
|
1302
|
+
* seller's ENTIRE order list while looking exactly like a working filter
|
|
1303
|
+
* (Root Cause #62). Checked before wiring the chip, not after.
|
|
1304
|
+
*
|
|
1305
|
+
* 🛑 Never pushed as `orderType==standard`, for the same reason the admin
|
|
1306
|
+
* set above carries that note: orders written before the field existed have
|
|
1307
|
+
* no value to match. `/api/store/orders` refines that one case in memory.
|
|
1308
|
+
*/
|
|
1309
|
+
orderType: { canFilter: true, canSort: false },
|
|
1165
1310
|
// Fulfilment flags. Filterable so "which of my orders need gift wrap?" is a
|
|
1166
1311
|
// query against the orders themselves — the whole point of recording the
|
|
1167
1312
|
// add-on on the order rather than keeping a side list of who asked for what.
|
|
@@ -7,6 +7,7 @@ import type { BaseDocument } from "../../../_internal/shared/types/base-document
|
|
|
7
7
|
import type { ListingType } from "../../products/types";
|
|
8
8
|
import type { StatusChangeEntry } from "../../../_internal/shared/history/index";
|
|
9
9
|
import type { ReturnReason } from "../../../_internal/shared/features/orders/return-reasons";
|
|
10
|
+
import type { DisputeKind } from "../../../_internal/shared/features/orders/dispute-reasons";
|
|
10
11
|
import type { ReturnCustodyState } from "../../../_internal/shared/features/returns/custody";
|
|
11
12
|
import type { ActualShipmentCost, SelectedShippingQuote, ShippingReconciliation } from "../../../_internal/shared/shipping/quote";
|
|
12
13
|
import type { Encrypted, Plaintext } from "../../../security/encrypted";
|
|
@@ -352,6 +353,25 @@ export interface OrderRefundEvent {
|
|
|
352
353
|
proofDocumentMimeType?: string;
|
|
353
354
|
/** Required for Razorpay-override path ("failed auto-refund, settled manually"). */
|
|
354
355
|
overrideReason?: string;
|
|
356
|
+
/**
|
|
357
|
+
* The credit note that documents this refund, e.g. `LIRCN/26-27/0001`
|
|
358
|
+
* (Phase 7, D12).
|
|
359
|
+
*
|
|
360
|
+
* 🛑 Per REFUND EVENT, not per order — an order can legitimately be refunded
|
|
361
|
+
* several times, and each reversal of tax needs its own numbered document
|
|
362
|
+
* referencing the original invoice. That is why the allocation claim for a
|
|
363
|
+
* credit note is keyed `${orderId}_${refundId}` while an invoice's is keyed
|
|
364
|
+
* on the order alone: keying a credit note on the order would refuse the
|
|
365
|
+
* second one.
|
|
366
|
+
*
|
|
367
|
+
* Only ever set on a refund that actually SETTLED. A `reserved` entry that
|
|
368
|
+
* failed is not a reversal of anything, and numbering it would leave a
|
|
369
|
+
* credit note in the series for money that never moved.
|
|
370
|
+
*/
|
|
371
|
+
creditNoteNumber?: string;
|
|
372
|
+
creditNoteIssuedAt?: Date;
|
|
373
|
+
/** Private storage path — same no-public-URL rule as `invoiceStoragePath`. */
|
|
374
|
+
creditNoteStoragePath?: string;
|
|
355
375
|
}
|
|
356
376
|
/** One applied discount/coupon saved on the order for accounting and display */
|
|
357
377
|
export interface AppliedOrderDiscount {
|
|
@@ -426,6 +446,21 @@ export interface OrderDocument extends BaseDocument {
|
|
|
426
446
|
*/
|
|
427
447
|
acceptedAt?: Date;
|
|
428
448
|
shippingDate?: Date;
|
|
449
|
+
/**
|
|
450
|
+
* The COURIER's estimated delivery date, captured at dispatch.
|
|
451
|
+
*
|
|
452
|
+
* 🛑 Not to be confused with `OrderSourcePreOrder.expectedDeliveryDate`,
|
|
453
|
+
* which is when a pre-ordered product RELEASES — a different concept that
|
|
454
|
+
* this field was mistaken for while planning the dispute gates.
|
|
455
|
+
*
|
|
456
|
+
* Optional, and will stay mostly empty for now: nothing platform-books yet
|
|
457
|
+
* (`createShipment` has zero callers), and a self-shipping seller types a
|
|
458
|
+
* tracking number with no estimate attached. `resolveDisputeEtd` therefore
|
|
459
|
+
* falls back to `shippingDate + DISPUTE_TRANSIT_WINDOW_DAYS` — without that
|
|
460
|
+
* fallback the `not_reached` dispute gate would be unreachable for every
|
|
461
|
+
* parcel currently shipped.
|
|
462
|
+
*/
|
|
463
|
+
estimatedDeliveryDate?: Date;
|
|
429
464
|
deliveryDate?: Date;
|
|
430
465
|
/**
|
|
431
466
|
* When the order was marked COMPLETE — the fact that awards points and
|
|
@@ -481,6 +516,37 @@ export interface OrderDocument extends BaseDocument {
|
|
|
481
516
|
* figure the order may since have changed.
|
|
482
517
|
*/
|
|
483
518
|
pointsAwarded?: number;
|
|
519
|
+
/**
|
|
520
|
+
* Points SPENT on this order as part-payment, in points (1 point = ₹1).
|
|
521
|
+
*
|
|
522
|
+
* 🛑 Distinct from `pointsAwarded` directly above, which is what the buyer
|
|
523
|
+
* EARNED. Both can be non-zero on one order — a buyer can pay partly with
|
|
524
|
+
* points and still earn on the goods value — and conflating them would make
|
|
525
|
+
* the ledger unexplainable in exactly the place it matters most.
|
|
526
|
+
*
|
|
527
|
+
* Written at order creation from `resolvePointsTender`, never from a request
|
|
528
|
+
* body. The ledger's `redemption` entry is keyed on this order, so this field
|
|
529
|
+
* and that entry must agree; the order carries it so a payout deduction and a
|
|
530
|
+
* refund can both answer "how much of this was points" without re-deriving a
|
|
531
|
+
* percentage of a total that may since have changed.
|
|
532
|
+
*/
|
|
533
|
+
pointsApplied?: number;
|
|
534
|
+
/**
|
|
535
|
+
* What the buyer actually pays: `totalPrice − pointsApplied`.
|
|
536
|
+
*
|
|
537
|
+
* ## 🛑 THE FIGURE THE GATEWAY CAPTURES, and the one the guard compares
|
|
538
|
+
*
|
|
539
|
+
* `totalPrice` stays the full value of the order — that is what the invoice
|
|
540
|
+
* declares and what GST is computed against, because points are a DISCOUNT
|
|
541
|
+
* and not a reduction in the value of the supply (decision 5). `amountDue` is
|
|
542
|
+
* the cash leg.
|
|
543
|
+
*
|
|
544
|
+
* Absent means "nothing was tendered", so every order written before this
|
|
545
|
+
* existed reads correctly as `amountDue === totalPrice`. 🛑 Readers must use
|
|
546
|
+
* `amountDue ?? totalPrice` rather than defaulting to 0 — treating absence as
|
|
547
|
+
* zero would say every historical order was fully paid in points.
|
|
548
|
+
*/
|
|
549
|
+
amountDue?: number;
|
|
484
550
|
cancellationDate?: Date;
|
|
485
551
|
cancellationReason?: string;
|
|
486
552
|
/**
|
|
@@ -668,6 +734,25 @@ export interface OrderDocument extends BaseDocument {
|
|
|
668
734
|
igst: number;
|
|
669
735
|
gstAmount: number;
|
|
670
736
|
}>;
|
|
737
|
+
/**
|
|
738
|
+
* The buyer's state — the PLACE OF SUPPLY (Phase 7).
|
|
739
|
+
*
|
|
740
|
+
* 🛑 This is the input the CGST/SGST-vs-IGST decision was made FROM, and it
|
|
741
|
+
* was not recorded anywhere. `isIntraStateSupply(buyerState, storeState)`
|
|
742
|
+
* runs at checkout and only its *result* survived, in the shape of which tax
|
|
743
|
+
* fields got populated — so an order could not state why it split the way it
|
|
744
|
+
* did, and GSTR-1 has a mandatory place-of-supply column that nothing could
|
|
745
|
+
* fill.
|
|
746
|
+
*
|
|
747
|
+
* `shippingAddress` is a flat STRING on this document and contains the state
|
|
748
|
+
* somewhere inside it; parsing a state back out of free text to file a tax
|
|
749
|
+
* return is not a thing to do. The value is in scope at order creation, so
|
|
750
|
+
* it is written there.
|
|
751
|
+
*
|
|
752
|
+
* Absent on orders placed before this, which the export reports rather than
|
|
753
|
+
* silently blanks.
|
|
754
|
+
*/
|
|
755
|
+
placeOfSupply?: string;
|
|
671
756
|
/**
|
|
672
757
|
* OUR GST registration as it stood at the moment of supply — LetItRip is the
|
|
673
758
|
* seller of record and invoices under its own GSTIN.
|
|
@@ -689,6 +774,58 @@ export interface OrderDocument extends BaseDocument {
|
|
|
689
774
|
supplierGstin?: string;
|
|
690
775
|
supplierLegalName?: string;
|
|
691
776
|
supplierAddress?: string;
|
|
777
|
+
/**
|
|
778
|
+
* The invoice number from the gapless FY series, e.g. `LIR/26-27/0001`.
|
|
779
|
+
*
|
|
780
|
+
* 🛑 Allocated on `paymentStatus → paid`, never at order creation — a number
|
|
781
|
+
* burned on an abandoned COD order is a gap nobody can explain. Written once
|
|
782
|
+
* and never rewritten: a GST invoice is issued once, and an adjustment is a
|
|
783
|
+
* CREDIT NOTE, not a re-issue.
|
|
784
|
+
*
|
|
785
|
+
* Absence means "not yet invoiced", which is the correct state for a pending
|
|
786
|
+
* order and for every order placed before this phase.
|
|
787
|
+
*/
|
|
788
|
+
invoiceNumber?: string;
|
|
789
|
+
/** When the number was allocated — the invoice's own date. */
|
|
790
|
+
invoiceIssuedAt?: Date;
|
|
791
|
+
/**
|
|
792
|
+
* Where the rendered PDF lives.
|
|
793
|
+
*
|
|
794
|
+
* 🛑 **`private/invoices/{orderId}/{uuid}.pdf`, with NO `mediaAssets` row and
|
|
795
|
+
* a RANDOM object name.** An invoice carries the buyer's full name, address
|
|
796
|
+
* and phone. `/api/media/[...slug]` applies **no authentication** over a
|
|
797
|
+
* bucket whose storage rule is `allow read: if true`, and the slug resolves
|
|
798
|
+
* through `mediaAssets` — so no row means no public URL exists at all.
|
|
799
|
+
*
|
|
800
|
+
* The random name is deliberately AGAINST the content-derived SEO-slug
|
|
801
|
+
* convention used everywhere else in this codebase, because guessable is the
|
|
802
|
+
* one property this must not have. `generateInvoiceFilename` is
|
|
803
|
+
* content-derived and is therefore the wrong tool here. Same reasoning as the
|
|
804
|
+
* digital-content decision (D103).
|
|
805
|
+
*/
|
|
806
|
+
invoiceStoragePath?: string;
|
|
807
|
+
/**
|
|
808
|
+
* The arrangement's fees, SNAPSHOTTED at checkout.
|
|
809
|
+
*
|
|
810
|
+
* 🛑 Snapshotted, not re-read. A coordinator commits against a quoted total;
|
|
811
|
+
* if the rates were read again at settle a seller could raise them
|
|
812
|
+
* afterwards and the coordinator would absorb it. The precedent is exact —
|
|
813
|
+
* the refund path reads `finalSale` from this document rather than the live
|
|
814
|
+
* product, "because re-reading the live product would let a seller flip the
|
|
815
|
+
* switch afterwards" — and D61 generalises it to every agreement with a gap
|
|
816
|
+
* between terms-agreed and money-taken.
|
|
817
|
+
*
|
|
818
|
+
* Absent on every ordinary single-destination order, which is almost all of
|
|
819
|
+
* them.
|
|
820
|
+
*/
|
|
821
|
+
sharedPurchase?: {
|
|
822
|
+
/** Distinct destinations, including the first. Always ≥ 2 when present. */
|
|
823
|
+
destinationCount: number;
|
|
824
|
+
/** Flat per-arrangement fee, as charged. */
|
|
825
|
+
handlingFee: number;
|
|
826
|
+
/** Extra shipping for destinations beyond the first, as charged. */
|
|
827
|
+
extraShippingFee: number;
|
|
828
|
+
};
|
|
692
829
|
emiEnabled?: boolean;
|
|
693
830
|
/** Number of monthly installments the buyer chose (2–6). */
|
|
694
831
|
emiTenureMonths?: number;
|
|
@@ -776,6 +913,38 @@ export interface OrderDocument extends BaseDocument {
|
|
|
776
913
|
* requires evidence and approval before anything is paid.
|
|
777
914
|
*/
|
|
778
915
|
platformBookedShipment?: boolean;
|
|
916
|
+
/**
|
|
917
|
+
* We tried to book a courier for this order and could not.
|
|
918
|
+
*
|
|
919
|
+
* ## 🛑 ITS WHOLE PURPOSE IS TO STOP AN AUTO-CANCELLATION
|
|
920
|
+
*
|
|
921
|
+
* `seller-dispatch` is an `onDue: "act"` obligation on a 2-business-day
|
|
922
|
+
* clock. Without this field, an order sitting on OUR empty courier wallet is
|
|
923
|
+
* auto-cancelled and the buyer refunded — with the seller recorded as having
|
|
924
|
+
* failed to dispatch. The buyer paid, the seller did nothing wrong, and the
|
|
925
|
+
* platform's own funding state caused the cancellation.
|
|
926
|
+
*
|
|
927
|
+
* So every `onDue: "act"` policy anchored at or after dispatch gates its
|
|
928
|
+
* `precondition` on `!hasOpenCourierBlock(order)`. The obligation engine
|
|
929
|
+
* re-evaluates `precondition` at fire time and closes `superseded` when it
|
|
930
|
+
* returns false, so the clock pauses and resumes with no new mechanism.
|
|
931
|
+
*
|
|
932
|
+
* Absent means no block — which is every order ever written, so this needs
|
|
933
|
+
* no migration and changes nothing until something sets it.
|
|
934
|
+
*
|
|
935
|
+
* `reason` is the operator's word, not the seller's: the seller is told only
|
|
936
|
+
* that we could not book a courier right now, because our balance is our
|
|
937
|
+
* problem and not theirs.
|
|
938
|
+
*/
|
|
939
|
+
courierBlock?: {
|
|
940
|
+
reason: "insufficient_funds" | "provider_error" | "unserviceable";
|
|
941
|
+
at: Date;
|
|
942
|
+
lastAttemptAt?: Date;
|
|
943
|
+
/** Attempts so far, so the digest can distinguish stuck from transient. */
|
|
944
|
+
attempts: number;
|
|
945
|
+
/** Upstream detail, for the admin surface only. Never shown to a seller. */
|
|
946
|
+
detail?: string;
|
|
947
|
+
};
|
|
779
948
|
/**
|
|
780
949
|
* The self-shipped reimbursement workflow: evidence in, approved amount out.
|
|
781
950
|
*
|
|
@@ -970,11 +1139,37 @@ export interface OrderDocument extends BaseDocument {
|
|
|
970
1139
|
/** True when `paymentReviewAutoApprove` approved this order after 2 hours of no manual admin review. */
|
|
971
1140
|
autoApproved?: boolean;
|
|
972
1141
|
autoApprovedAt?: Date;
|
|
973
|
-
/**
|
|
1142
|
+
/**
|
|
1143
|
+
* A buyer/seller/admin dispute. Surfaces to the admin queue; does not itself
|
|
1144
|
+
* reverse payment or order status.
|
|
1145
|
+
*
|
|
1146
|
+
* 🛑 This used to say "against an auto-approved order", and that was the
|
|
1147
|
+
* whole scope: `raiseOrderDisputeAction` refused anything else, telling the
|
|
1148
|
+
* caller to use the return/refund flow. Disputes are now GENERAL — any
|
|
1149
|
+
* delivered-or-late order can be disputed, gated by `disputeKind`.
|
|
1150
|
+
*/
|
|
974
1151
|
disputeRaised?: boolean;
|
|
975
1152
|
disputeRaisedBy?: string;
|
|
976
1153
|
disputeRaisedAt?: Date;
|
|
1154
|
+
/**
|
|
1155
|
+
* The reason's LABEL, snapshotted at the moment the dispute was raised.
|
|
1156
|
+
*
|
|
1157
|
+
* 🛑 A snapshot, not a lookup, and that is the point: `disputeReasons` rows
|
|
1158
|
+
* can be retired or reworded by staff, and neither may rewrite what a buyer
|
|
1159
|
+
* said months ago. `disputeReasonId` below is the join key for analytics;
|
|
1160
|
+
* THIS is the record.
|
|
1161
|
+
*
|
|
1162
|
+
* Still a plain `string` and still the field every existing surface renders,
|
|
1163
|
+
* so pre-2026-10-22 disputes (free text typed by the buyer) keep displaying
|
|
1164
|
+
* unchanged rather than being orphaned beside a new field.
|
|
1165
|
+
*/
|
|
977
1166
|
disputeReason?: string;
|
|
1167
|
+
/** Which gate this dispute passed. Absent on pre-2026-10-22 disputes. */
|
|
1168
|
+
disputeKind?: DisputeKind;
|
|
1169
|
+
/** `disputeReasons/{id}`. The join key for "what are buyers reporting". */
|
|
1170
|
+
disputeReasonId?: string;
|
|
1171
|
+
/** The buyer's own words. Free text, never parsed, never gated on. */
|
|
1172
|
+
disputeDetail?: string;
|
|
978
1173
|
disputeStatus?: "open" | "resolved";
|
|
979
1174
|
/**
|
|
980
1175
|
* Same-shape payment record regardless of how the buyer paid — manual
|