@mohasinac/appkit 4.62.1 → 4.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-stamp.json +3 -3
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +8 -29
- package/dist/_internal/client/features/layout/filterNavItems.d.ts +28 -0
- package/dist/_internal/client/features/layout/filterNavItems.js +35 -0
- package/dist/_internal/server/features/ads/booking.d.ts +118 -0
- package/dist/_internal/server/features/ads/booking.js +326 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +51 -11
- package/dist/_internal/server/features/checkout/actions.js +335 -80
- package/dist/_internal/server/features/checkout/expand-order-items.d.ts +1 -2
- package/dist/_internal/server/features/checkout/expand-order-items.js +32 -0
- package/dist/_internal/server/features/checkout/freeze-parts.d.ts +50 -0
- package/dist/_internal/server/features/checkout/freeze-parts.js +94 -0
- package/dist/_internal/server/features/checkout/value-otp-gate.d.ts +55 -0
- package/dist/_internal/server/features/checkout/value-otp-gate.js +60 -0
- package/dist/_internal/server/features/dashboard/attention.d.ts +128 -0
- package/dist/_internal/server/features/dashboard/attention.js +183 -0
- package/dist/_internal/server/features/dashboard/data.d.ts +47 -0
- package/dist/_internal/server/features/dashboard/data.js +82 -0
- package/dist/_internal/server/features/inventory/reservations.d.ts +99 -0
- package/dist/_internal/server/features/inventory/reservations.js +273 -0
- package/dist/_internal/server/features/offers/destinations.d.ts +40 -0
- package/dist/_internal/server/features/offers/destinations.js +65 -0
- package/dist/_internal/server/features/orders/adapters.js +21 -0
- package/dist/_internal/server/features/orders/amend-delivery.d.ts +56 -0
- package/dist/_internal/server/features/orders/amend-delivery.js +124 -0
- package/dist/_internal/server/features/orders/balance.d.ts +89 -0
- package/dist/_internal/server/features/orders/balance.js +256 -0
- package/dist/_internal/server/features/pricing/actions.d.ts +58 -0
- package/dist/_internal/server/features/pricing/actions.js +144 -0
- package/dist/_internal/server/features/shipping/book-split.d.ts +86 -0
- package/dist/_internal/server/features/shipping/book-split.js +130 -0
- package/dist/_internal/server/features/shipping/book.d.ts +61 -0
- package/dist/_internal/server/features/shipping/book.js +176 -0
- package/dist/_internal/server/features/shipping/courier-block.js +26 -1
- package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
- package/dist/_internal/server/features/site-settings/adapters.js +14 -0
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +14 -0
- package/dist/_internal/server/functions/scheduled.d.ts +2 -1
- package/dist/_internal/server/functions/scheduled.js +57 -1
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +2 -0
- package/dist/_internal/server/jobs/core/jobRunners.js +45 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +4 -1
- package/dist/_internal/server/jobs/core/reservationExpiry.d.ts +17 -0
- package/dist/_internal/server/jobs/core/reservationExpiry.js +40 -0
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +15 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
- package/dist/_internal/server/jobs/handlers/index.js +1 -0
- package/dist/_internal/server/jobs/handlers/reservationExpiry.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/reservationExpiry.js +2 -0
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +268 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +31 -2
- package/dist/_internal/shared/checkout/order-math.js +22 -36
- package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
- package/dist/_internal/shared/checkout/rules/index.js +1 -1
- package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +18 -0
- package/dist/_internal/shared/checkout/rules/payment-gate.js +25 -0
- package/dist/_internal/shared/checkout/split.d.ts +0 -12
- package/dist/_internal/shared/checkout/split.js +22 -2
- package/dist/_internal/shared/content/disclaimers.js +7 -7
- package/dist/_internal/shared/content/published-values.d.ts +6 -1
- package/dist/_internal/shared/content/published-values.js +8 -0
- package/dist/_internal/shared/features/admin/integrations.d.ts +32 -2
- package/dist/_internal/shared/features/admin/integrations.js +19 -9
- package/dist/_internal/shared/features/dashboard/hub-sections.d.ts +72 -0
- package/dist/_internal/shared/features/dashboard/hub-sections.js +75 -0
- package/dist/_internal/shared/features/emi/choice.d.ts +98 -0
- package/dist/_internal/shared/features/emi/choice.js +69 -0
- package/dist/_internal/shared/features/emi/group.d.ts +84 -0
- package/dist/_internal/shared/features/emi/group.js +126 -0
- package/dist/_internal/shared/features/ledger/posting.d.ts +107 -0
- package/dist/_internal/shared/features/ledger/posting.js +165 -0
- package/dist/_internal/shared/features/ledger/reports.d.ts +76 -0
- package/dist/_internal/shared/features/ledger/reports.js +127 -0
- package/dist/_internal/shared/features/ledger/rules.d.ts +170 -0
- package/dist/_internal/shared/features/ledger/rules.js +221 -0
- package/dist/_internal/shared/features/offers/coordination.d.ts +122 -0
- package/dist/_internal/shared/features/offers/coordination.js +80 -0
- package/dist/_internal/shared/features/orders/delivery-snapshot.d.ts +114 -0
- package/dist/_internal/shared/features/orders/delivery-snapshot.js +116 -0
- package/dist/_internal/shared/features/payouts/pay-period.d.ts +91 -0
- package/dist/_internal/shared/features/payouts/pay-period.js +104 -0
- package/dist/_internal/shared/features/points/accepts.d.ts +1 -8
- package/dist/_internal/shared/features/points/accepts.js +12 -6
- package/dist/_internal/shared/features/pricing/rate-registry.d.ts +150 -0
- package/dist/_internal/shared/features/pricing/rate-registry.js +246 -0
- package/dist/_internal/shared/features/shipping/parcel.d.ts +123 -0
- package/dist/_internal/shared/features/shipping/parcel.js +139 -0
- package/dist/_internal/shared/features/site-settings/fees.d.ts +16 -8
- package/dist/_internal/shared/features/site-settings/fees.js +12 -8
- package/dist/_internal/shared/fees/explain.d.ts +81 -0
- package/dist/_internal/shared/fees/explain.js +101 -0
- package/dist/_internal/shared/history/index.d.ts +1 -1
- package/dist/_internal/shared/history/index.js +1 -1
- package/dist/_internal/shared/history/types.d.ts +7 -1
- package/dist/_internal/shared/history/types.js +25 -0
- package/dist/client.d.ts +11 -1
- package/dist/client.js +13 -0
- package/dist/constants/api-endpoints.d.ts +72 -0
- package/dist/constants/api-endpoints.js +41 -0
- package/dist/constants/field-names.d.ts +41 -0
- package/dist/constants/field-names.js +47 -0
- package/dist/features/account/components/UserOrdersView.js +28 -2
- package/dist/features/admin/components/AdminIntegrationsView.d.ts +55 -0
- package/dist/features/admin/components/AdminIntegrationsView.js +74 -0
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +16 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +72 -3
- package/dist/features/admin/components/AdminOrdersView.js +20 -1
- package/dist/features/admin/schemas/firestore.d.ts +32 -0
- package/dist/features/ads/schemas/booking-write.d.ts +120 -0
- package/dist/features/ads/schemas/booking-write.js +75 -0
- package/dist/features/ads/schemas/creative-form.d.ts +18 -0
- package/dist/features/ads/schemas/creative-form.js +51 -0
- package/dist/features/ads/schemas/firestore.d.ts +140 -0
- package/dist/features/ads/schemas/firestore.js +109 -0
- package/dist/features/auctions/repository/bid.repository.js +3 -1
- package/dist/features/audit-log/schemas/firestore.d.ts +110 -0
- package/dist/features/audit-log/schemas/firestore.js +74 -0
- package/dist/features/auth/actions/profile-actions.d.ts +69 -1
- package/dist/features/auth/actions/profile-actions.js +62 -1
- package/dist/features/auth/schemas/firestore.d.ts +16 -2
- package/dist/features/auth/schemas/firestore.js +2 -1
- package/dist/features/blog/actions/blog-actions.d.ts +6 -0
- package/dist/features/blog/actions/blog-actions.js +17 -2
- package/dist/features/blog/api/route.js +4 -1
- package/dist/features/blog/columns/index.js +5 -0
- package/dist/features/blog/components/BlogIndexPageView.js +2 -1
- package/dist/features/blog/repository/blog.repository.d.ts +4 -0
- package/dist/features/blog/repository/blog.repository.js +5 -0
- package/dist/features/blog/schemas/blog-form.d.ts +3 -0
- package/dist/features/blog/schemas/blog-form.js +1 -0
- package/dist/features/blog/schemas/firestore.d.ts +38 -1
- package/dist/features/blog/schemas/firestore.js +23 -0
- package/dist/features/blog/schemas/index.d.ts +6 -0
- package/dist/features/blog/schemas/index.js +2 -0
- package/dist/features/blog/types/index.d.ts +2 -0
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +12 -1
- package/dist/features/cart/components/CartPriceBreakdown.js +24 -5
- package/dist/features/cart/repository/cart.repository.d.ts +14 -0
- package/dist/features/cart/repository/cart.repository.js +20 -0
- package/dist/features/cart/schemas/firestore.d.ts +24 -0
- package/dist/features/dashboard/components/DashboardHubView.d.ts +83 -0
- package/dist/features/dashboard/components/DashboardHubView.js +56 -0
- package/dist/features/dashboard/components/DashboardSectionsClient.d.ts +23 -0
- package/dist/features/dashboard/components/DashboardSectionsClient.js +66 -0
- package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.d.ts +55 -0
- package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.js +175 -0
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +24 -0
- package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +28 -0
- package/dist/features/dispute-reasons/schemas/firestore.d.ts +3 -5
- package/dist/features/events/components/EventSaleBanner.js +1 -1
- package/dist/features/faq/components/FAQPageContent.js +2 -2
- package/dist/features/gateway-events/schemas/index.d.ts +2 -2
- package/dist/features/homepage/schemas/index.d.ts +8 -8
- package/dist/features/inventory/schemas/stock-reservation.d.ts +114 -0
- package/dist/features/inventory/schemas/stock-reservation.js +103 -0
- package/dist/features/layout/AppLayoutShell.d.ts +9 -1
- package/dist/features/layout/AppLayoutShell.js +12 -4
- package/dist/features/layout/BottomActions.js +2 -1
- package/dist/features/layout/BottomNavbar.d.ts +10 -2
- package/dist/features/layout/SideActionDock.js +52 -2
- package/dist/features/layout/TitleBarLayout.js +13 -4
- package/dist/features/layout/TitleBarLayout.style.css +59 -0
- package/dist/features/layout/useCtaReveal.d.ts +16 -1
- package/dist/features/layout/useCtaReveal.js +17 -26
- package/dist/features/ledger/repository/journal.repository.d.ts +110 -0
- package/dist/features/ledger/repository/journal.repository.js +312 -0
- package/dist/features/ledger/schemas/expenses.d.ts +87 -0
- package/dist/features/ledger/schemas/expenses.js +27 -0
- package/dist/features/ledger/schemas/firestore.d.ts +183 -0
- package/dist/features/ledger/schemas/firestore.js +69 -0
- package/dist/features/orders/components/BalancePaymentPanel.d.ts +12 -0
- package/dist/features/orders/components/BalancePaymentPanel.js +94 -0
- package/dist/features/orders/components/RaiseDisputeForm.js +19 -11
- package/dist/features/orders/repository/orders.repository.d.ts +28 -1
- package/dist/features/orders/repository/orders.repository.js +60 -6
- package/dist/features/orders/schemas/balance.d.ts +40 -0
- package/dist/features/orders/schemas/balance.js +47 -0
- package/dist/features/orders/schemas/firestore.d.ts +202 -0
- package/dist/features/orders/schemas/firestore.js +43 -0
- package/dist/features/orders/schemas/index.d.ts +2 -2
- package/dist/features/orders/types/index.d.ts +21 -0
- package/dist/features/payments/repository/payout.repository.js +3 -2
- package/dist/features/payments/schemas/firestore.d.ts +92 -1
- package/dist/features/payments/schemas/firestore.js +23 -0
- package/dist/features/pricing/components/AdminPricingView.d.ts +13 -0
- package/dist/features/pricing/components/AdminPricingView.js +157 -0
- package/dist/features/products/schemas/firestore.d.ts +26 -1
- package/dist/features/products/schemas/firestore.js +2 -0
- package/dist/features/products/schemas/index.d.ts +2 -2
- package/dist/features/reviews/repository/reviews.repository.d.ts +14 -0
- package/dist/features/reviews/repository/reviews.repository.js +16 -0
- package/dist/features/scams/repository/scammer.repository.js +2 -1
- package/dist/features/scams/schemas/firestore.d.ts +2 -1
- package/dist/features/scams/schemas/firestore.js +2 -1
- package/dist/features/seller/actions/offer-actions.js +56 -0
- package/dist/features/seller/actions/seller-actions.d.ts +1 -1
- package/dist/features/seller/actions/seller-actions.js +4 -0
- package/dist/features/seller/components/DispatchForm.d.ts +11 -0
- package/dist/features/seller/components/DispatchForm.js +190 -0
- package/dist/features/seller/components/SellerOrdersView.js +94 -2
- package/dist/features/seller/components/index.d.ts +1 -0
- package/dist/features/seller/components/index.js +1 -0
- package/dist/features/seller/repository/offer.repository.js +4 -3
- package/dist/features/seller/schemas/firestore.d.ts +23 -2
- package/dist/features/seller/schemas/firestore.js +3 -2
- package/dist/features/seller/schemas/index.d.ts +4 -4
- package/dist/features/seller/schemas/order-forms.d.ts +61 -4
- package/dist/features/seller/schemas/order-forms.js +41 -8
- package/dist/features/shipments/schemas/firestore.d.ts +25 -0
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +39 -0
- package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +55 -0
- package/dist/features/shipping-boxes/schemas/firestore.d.ts +2 -4
- package/dist/features/stores/repository/store.repository.d.ts +18 -0
- package/dist/features/stores/repository/store.repository.js +27 -1
- package/dist/features/stores/schemas/firestore.d.ts +41 -0
- package/dist/features/stores/schemas/firestore.js +20 -0
- package/dist/features/stores/schemas/index.d.ts +6 -6
- package/dist/features/support/repository/support.repository.js +72 -6
- package/dist/features/support/schemas/firestore.d.ts +17 -0
- package/dist/features/support/schemas/index.d.ts +2 -2
- package/dist/index.d.ts +5 -0
- package/dist/index.js +19 -0
- package/dist/next/routing/route-map.d.ts +92 -0
- package/dist/next/routing/route-map.js +53 -0
- package/dist/react/contexts/SessionContext.d.ts +17 -1
- package/dist/schemas/registry.d.ts +22 -16
- package/dist/security/pii-schemas.d.ts +1 -1
- package/dist/security/pii-schemas.js +45 -0
- package/dist/server.d.ts +27 -1
- package/dist/server.js +144 -0
- package/dist/styles.css +349 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Button.d.ts +10 -1
- package/dist/ui/components/Button.js +25 -3
- package/dist/ui/components/Chart.d.ts +63 -0
- package/dist/ui/components/Chart.js +169 -0
- package/dist/ui/components/Chart.style.css +214 -0
- package/dist/ui/components/Disclaimer.js +10 -1
- package/dist/ui/components/OverflowRow.js +54 -2
- package/dist/ui/components/OverflowRow.style.css +38 -0
- package/dist/ui/components/RichTextEditor.js +99 -8
- package/dist/ui/components/chart-spec.d.ts +68 -0
- package/dist/ui/components/chart-spec.js +140 -0
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/components/surface-tokens.d.ts +1 -1
- package/dist/ui/components/surface-tokens.js +9 -0
- package/dist/ui/components/toolbar-collapse.d.ts +13 -0
- package/dist/ui/components/toolbar-collapse.js +10 -1
- package/dist/ui/index.d.ts +6 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/rich-text/RichText.d.ts +10 -1
- package/dist/ui/rich-text/RichText.js +33 -2
- package/dist/ui/rich-text/RichText.style.css +35 -0
- package/dist/ui/rich-text/RichTextRenderer.d.ts +30 -1
- package/dist/ui/rich-text/RichTextRenderer.js +34 -2
- package/dist/ui/rich-text/chart-blocks.d.ts +62 -0
- package/dist/ui/rich-text/chart-blocks.js +87 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +3 -0
|
@@ -32,10 +32,30 @@ export declare const sellerOrderUpdateSchema: z.ZodEffects<z.ZodObject<{
|
|
|
32
32
|
}>;
|
|
33
33
|
export type SellerOrderUpdateValues = z.infer<typeof sellerOrderUpdateSchema>;
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
* Shipment entry — manual, or booked through the platform.
|
|
36
|
+
*
|
|
37
|
+
* 🛑 **`method` was `z.literal("custom")`, and that literal WAS bug 0e.**
|
|
38
|
+
*
|
|
39
|
+
* Its comment said *"a literal rather than an enum because
|
|
40
|
+
* `ManualShippingProvider` is the only provider"* — no longer true:
|
|
41
|
+
* `providers/shipping-shiprocket/` is 570 lines with all six methods, and
|
|
42
|
+
* `providers.config.ts` already resolves it from
|
|
43
|
+
* `siteSettings.shipping.provider`. The seller simply could not EXPRESS
|
|
44
|
+
* "book this through us", so there was no option to offer, and
|
|
45
|
+
* `platformBookedShipment` — which `order-math.ts` gates reimbursement on —
|
|
46
|
+
* had zero writers.
|
|
47
|
+
*
|
|
48
|
+
* A DISCRIMINATED UNION rather than an enum plus optional fields, because
|
|
49
|
+
* the two branches need different things and an optional-everything shape
|
|
50
|
+
* cannot say so:
|
|
51
|
+
*
|
|
52
|
+
* · `custom` — the seller posted it and types the carrier and tracking id.
|
|
53
|
+
* Those are REQUIRED, which is the rule enforced in three places today.
|
|
54
|
+
* · `platform` — we book it, so there is no tracking id to type; it comes
|
|
55
|
+
* back from the carrier. Requiring one here would be asking the seller to
|
|
56
|
+
* invent the thing the booking produces.
|
|
37
57
|
*/
|
|
38
|
-
export declare const shipOrderSchema: z.ZodObject<{
|
|
58
|
+
export declare const shipOrderSchema: z.ZodDiscriminatedUnion<"method", [z.ZodObject<{
|
|
39
59
|
method: z.ZodLiteral<"custom">;
|
|
40
60
|
shippingCarrier: z.ZodString;
|
|
41
61
|
trackingNumber: z.ZodString;
|
|
@@ -50,7 +70,44 @@ export declare const shipOrderSchema: z.ZodObject<{
|
|
|
50
70
|
shippingCarrier: string;
|
|
51
71
|
trackingUrl: string;
|
|
52
72
|
method: "custom";
|
|
53
|
-
}
|
|
73
|
+
}>, z.ZodObject<{
|
|
74
|
+
method: z.ZodLiteral<"platform">;
|
|
75
|
+
pickupAddressId: z.ZodString;
|
|
76
|
+
dimensions: z.ZodObject<{
|
|
77
|
+
weight: z.ZodNumber;
|
|
78
|
+
length: z.ZodNumber;
|
|
79
|
+
width: z.ZodNumber;
|
|
80
|
+
height: z.ZodNumber;
|
|
81
|
+
}, "strip", z.ZodTypeAny, {
|
|
82
|
+
length: number;
|
|
83
|
+
weight: number;
|
|
84
|
+
height: number;
|
|
85
|
+
width: number;
|
|
86
|
+
}, {
|
|
87
|
+
length: number;
|
|
88
|
+
weight: number;
|
|
89
|
+
height: number;
|
|
90
|
+
width: number;
|
|
91
|
+
}>;
|
|
92
|
+
}, "strip", z.ZodTypeAny, {
|
|
93
|
+
pickupAddressId: string;
|
|
94
|
+
method: "platform";
|
|
95
|
+
dimensions: {
|
|
96
|
+
length: number;
|
|
97
|
+
weight: number;
|
|
98
|
+
height: number;
|
|
99
|
+
width: number;
|
|
100
|
+
};
|
|
101
|
+
}, {
|
|
102
|
+
pickupAddressId: string;
|
|
103
|
+
method: "platform";
|
|
104
|
+
dimensions: {
|
|
105
|
+
length: number;
|
|
106
|
+
weight: number;
|
|
107
|
+
height: number;
|
|
108
|
+
width: number;
|
|
109
|
+
};
|
|
110
|
+
}>]>;
|
|
54
111
|
export type ShipOrderValues = z.infer<typeof shipOrderSchema>;
|
|
55
112
|
/**
|
|
56
113
|
* Marking one EMI installment paid. `installmentIndex` is 1-based, matching
|
|
@@ -92,15 +92,48 @@ export const sellerOrderUpdateSchema = z
|
|
|
92
92
|
* it is also called directly from a server action, where no route ran first.
|
|
93
93
|
*/
|
|
94
94
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
95
|
+
* Shipment entry — manual, or booked through the platform.
|
|
96
|
+
*
|
|
97
|
+
* 🛑 **`method` was `z.literal("custom")`, and that literal WAS bug 0e.**
|
|
98
|
+
*
|
|
99
|
+
* Its comment said *"a literal rather than an enum because
|
|
100
|
+
* `ManualShippingProvider` is the only provider"* — no longer true:
|
|
101
|
+
* `providers/shipping-shiprocket/` is 570 lines with all six methods, and
|
|
102
|
+
* `providers.config.ts` already resolves it from
|
|
103
|
+
* `siteSettings.shipping.provider`. The seller simply could not EXPRESS
|
|
104
|
+
* "book this through us", so there was no option to offer, and
|
|
105
|
+
* `platformBookedShipment` — which `order-math.ts` gates reimbursement on —
|
|
106
|
+
* had zero writers.
|
|
107
|
+
*
|
|
108
|
+
* A DISCRIMINATED UNION rather than an enum plus optional fields, because
|
|
109
|
+
* the two branches need different things and an optional-everything shape
|
|
110
|
+
* cannot say so:
|
|
111
|
+
*
|
|
112
|
+
* · `custom` — the seller posted it and types the carrier and tracking id.
|
|
113
|
+
* Those are REQUIRED, which is the rule enforced in three places today.
|
|
114
|
+
* · `platform` — we book it, so there is no tracking id to type; it comes
|
|
115
|
+
* back from the carrier. Requiring one here would be asking the seller to
|
|
116
|
+
* invent the thing the booking produces.
|
|
97
117
|
*/
|
|
98
|
-
export const shipOrderSchema = z.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
118
|
+
export const shipOrderSchema = z.discriminatedUnion("method", [
|
|
119
|
+
z.object({
|
|
120
|
+
method: z.literal("custom"),
|
|
121
|
+
shippingCarrier: z.string().min(1),
|
|
122
|
+
trackingNumber: z.string().min(1),
|
|
123
|
+
trackingUrl: z.string().url(),
|
|
124
|
+
}),
|
|
125
|
+
z.object({
|
|
126
|
+
method: z.literal("platform"),
|
|
127
|
+
/* The store address the courier collects from. */
|
|
128
|
+
pickupAddressId: z.string().min(1),
|
|
129
|
+
dimensions: z.object({
|
|
130
|
+
weight: z.number().positive().max(50),
|
|
131
|
+
length: z.number().positive().max(200),
|
|
132
|
+
width: z.number().positive().max(200),
|
|
133
|
+
height: z.number().positive().max(200),
|
|
134
|
+
}),
|
|
135
|
+
}),
|
|
136
|
+
]);
|
|
104
137
|
/**
|
|
105
138
|
* Marking one EMI installment paid. `installmentIndex` is 1-based, matching
|
|
106
139
|
* how the schedule is presented to the buyer.
|
|
@@ -43,7 +43,32 @@ export interface ShipmentTotals {
|
|
|
43
43
|
export declare const DEFAULT_SHIPMENT_TOTALS: ShipmentTotals;
|
|
44
44
|
export interface ShipmentDocument extends BaseDocument {
|
|
45
45
|
shipmentNumber: string;
|
|
46
|
+
/**
|
|
47
|
+
* The supplier as TYPED. Kept, and kept required, for display.
|
|
48
|
+
*
|
|
49
|
+
* 🛑 It is free text, so four shipments from one supplier are four
|
|
50
|
+
* unjoined strings — "Acme Toys", "acme toys", "Acme Toys Ltd", "ACME".
|
|
51
|
+
* Nothing can total what we have spent with anyone, which is the first
|
|
52
|
+
* question anybody asks of procurement data.
|
|
53
|
+
*/
|
|
46
54
|
supplierName: string;
|
|
55
|
+
/**
|
|
56
|
+
* The joinable identity — a `vendors` row.
|
|
57
|
+
*
|
|
58
|
+
* 🛑 OPTIONAL, and that is deliberate rather than a migration left half
|
|
59
|
+
* done. Every existing shipment has only the string, and backfilling by
|
|
60
|
+
* fuzzy-matching names is precisely how two real suppliers get merged into
|
|
61
|
+
* one spend figure that nobody can unpick afterwards. A shipment gains an
|
|
62
|
+
* id when a human links it; until then it is honestly unattributed, and
|
|
63
|
+
* the vendor report can say how many are.
|
|
64
|
+
*
|
|
65
|
+
* 🛑 The NAME is not derived from the vendor row on read. Two reasons: a
|
|
66
|
+
* vendor rename would retroactively rewrite what every past shipment says
|
|
67
|
+
* it was bought from, and the typed string is evidence of what the invoice
|
|
68
|
+
* actually said. Same stance the order item's frozen listing snapshot
|
|
69
|
+
* takes (Part 14).
|
|
70
|
+
*/
|
|
71
|
+
supplierId?: string;
|
|
47
72
|
originCountry?: string;
|
|
48
73
|
status: ShipmentStatus;
|
|
49
74
|
trackingNumber?: string;
|
|
@@ -39,5 +39,44 @@ export declare class ShippingBoxesRepository extends BaseRepository<ShippingBoxD
|
|
|
39
39
|
box: ShippingBoxDocument;
|
|
40
40
|
created: boolean;
|
|
41
41
|
}>;
|
|
42
|
+
/**
|
|
43
|
+
* Every preset, retired included — the staff management list.
|
|
44
|
+
*
|
|
45
|
+
* Separate from `listActive` for the same reason the dispute reasons are: the
|
|
46
|
+
* picker must never offer a retired row, and a management view must be able
|
|
47
|
+
* to find one in order to put it back.
|
|
48
|
+
*/
|
|
49
|
+
listAll(model: SieveModel): Promise<FirebaseSieveResult<ShippingBoxDocument>>;
|
|
50
|
+
/**
|
|
51
|
+
* Correct a preset's dimensions. **Staff only** — enforced at the route.
|
|
52
|
+
*
|
|
53
|
+
* ## 🛑 This does NOT change any dispatched parcel, by construction
|
|
54
|
+
*
|
|
55
|
+
* The dimensions are COPIED onto the shipment at use time (the schema's own
|
|
56
|
+
* header: *"a preset is a DEFAULT, never a lock"*), so there is no stored
|
|
57
|
+
* reference for this to reach through. That is precisely why staff editing is
|
|
58
|
+
* safe to offer at all: a seller's past declaration is a snapshot, and the
|
|
59
|
+
* courier billed against the figures at dispatch.
|
|
60
|
+
*
|
|
61
|
+
* It is staff-only for the mirror reason — these numbers are what the NEXT
|
|
62
|
+
* seller's dispatch form defaults to, so a wrong edit misdeclares other
|
|
63
|
+
* people's parcels. A seller who finds a preset wrong adds a new one; staff
|
|
64
|
+
* decide whether the shared row changes.
|
|
65
|
+
*
|
|
66
|
+
* 🛑 The NAME is deliberately not editable. The id is `box-<slug of name>`,
|
|
67
|
+
* so renaming would either leave the id disagreeing with the name (the
|
|
68
|
+
* mirror-drift trap) or require a new document, which is just a create. Add
|
|
69
|
+
* the correctly-named preset and retire this one.
|
|
70
|
+
*/
|
|
71
|
+
updatePreset(id: string, input: {
|
|
72
|
+
length?: number;
|
|
73
|
+
width?: number;
|
|
74
|
+
height?: number;
|
|
75
|
+
tareWeight?: number;
|
|
76
|
+
}): Promise<void>;
|
|
77
|
+
/** Soft-retire. See the schema for why this is never a delete. */
|
|
78
|
+
retire(id: string): Promise<void>;
|
|
79
|
+
/** Put a retired preset back in the picker. */
|
|
80
|
+
reactivate(id: string): Promise<void>;
|
|
42
81
|
}
|
|
43
82
|
export declare const shippingBoxesRepository: ShippingBoxesRepository;
|
|
@@ -58,6 +58,61 @@ export class ShippingBoxesRepository extends BaseRepository {
|
|
|
58
58
|
await this.createWithId(id, box);
|
|
59
59
|
return { box, created: true };
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Every preset, retired included — the staff management list.
|
|
63
|
+
*
|
|
64
|
+
* Separate from `listActive` for the same reason the dispute reasons are: the
|
|
65
|
+
* picker must never offer a retired row, and a management view must be able
|
|
66
|
+
* to find one in order to put it back.
|
|
67
|
+
*/
|
|
68
|
+
async listAll(model) {
|
|
69
|
+
return this.sieveQuery(model, ShippingBoxesRepository.SIEVE_FIELDS, {
|
|
70
|
+
baseQuery: this.getCollection(),
|
|
71
|
+
defaultPageSize: 20,
|
|
72
|
+
maxPageSize: 50,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Correct a preset's dimensions. **Staff only** — enforced at the route.
|
|
77
|
+
*
|
|
78
|
+
* ## 🛑 This does NOT change any dispatched parcel, by construction
|
|
79
|
+
*
|
|
80
|
+
* The dimensions are COPIED onto the shipment at use time (the schema's own
|
|
81
|
+
* header: *"a preset is a DEFAULT, never a lock"*), so there is no stored
|
|
82
|
+
* reference for this to reach through. That is precisely why staff editing is
|
|
83
|
+
* safe to offer at all: a seller's past declaration is a snapshot, and the
|
|
84
|
+
* courier billed against the figures at dispatch.
|
|
85
|
+
*
|
|
86
|
+
* It is staff-only for the mirror reason — these numbers are what the NEXT
|
|
87
|
+
* seller's dispatch form defaults to, so a wrong edit misdeclares other
|
|
88
|
+
* people's parcels. A seller who finds a preset wrong adds a new one; staff
|
|
89
|
+
* decide whether the shared row changes.
|
|
90
|
+
*
|
|
91
|
+
* 🛑 The NAME is deliberately not editable. The id is `box-<slug of name>`,
|
|
92
|
+
* so renaming would either leave the id disagreeing with the name (the
|
|
93
|
+
* mirror-drift trap) or require a new document, which is just a create. Add
|
|
94
|
+
* the correctly-named preset and retire this one.
|
|
95
|
+
*/
|
|
96
|
+
async updatePreset(id, input) {
|
|
97
|
+
const patch = { updatedAt: new Date() };
|
|
98
|
+
if (input.length !== undefined)
|
|
99
|
+
patch.length = clampCm(input.length);
|
|
100
|
+
if (input.width !== undefined)
|
|
101
|
+
patch.width = clampCm(input.width);
|
|
102
|
+
if (input.height !== undefined)
|
|
103
|
+
patch.height = clampCm(input.height);
|
|
104
|
+
if (input.tareWeight !== undefined)
|
|
105
|
+
patch.tareWeight = clampKg(input.tareWeight);
|
|
106
|
+
await this.update(id, patch);
|
|
107
|
+
}
|
|
108
|
+
/** Soft-retire. See the schema for why this is never a delete. */
|
|
109
|
+
async retire(id) {
|
|
110
|
+
await this.update(id, { isActive: false, updatedAt: new Date() });
|
|
111
|
+
}
|
|
112
|
+
/** Put a retired preset back in the picker. */
|
|
113
|
+
async reactivate(id) {
|
|
114
|
+
await this.update(id, { isActive: true, updatedAt: new Date() });
|
|
115
|
+
}
|
|
61
116
|
}
|
|
62
117
|
ShippingBoxesRepository.SIEVE_FIELDS = {
|
|
63
118
|
/*
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
* cannot dispute without the parcel. The form says so rather than treating it
|
|
28
28
|
* as a required field to get past.
|
|
29
29
|
*/
|
|
30
|
+
import type { BaseDocument } from "../../../_internal/shared/types/base-document";
|
|
30
31
|
/** `box-` prefixed slug, so the id reads as what it is. */
|
|
31
32
|
export declare const SHIPPING_BOX_COLLECTION = "shippingBoxes";
|
|
32
|
-
export interface ShippingBoxDocument {
|
|
33
|
-
id: string;
|
|
33
|
+
export interface ShippingBoxDocument extends BaseDocument {
|
|
34
34
|
/** What a seller picks it by: "Standard Beyblade carton", "Poly mailer S". */
|
|
35
35
|
name: string;
|
|
36
36
|
/** cm. */
|
|
@@ -55,8 +55,6 @@ export interface ShippingBoxDocument {
|
|
|
55
55
|
* not vanish mid-dispatch, and a deleted row takes its author with it.
|
|
56
56
|
*/
|
|
57
57
|
isActive: boolean;
|
|
58
|
-
createdAt: Date;
|
|
59
|
-
updatedAt: Date;
|
|
60
58
|
}
|
|
61
59
|
export declare const SHIPPING_BOX_FIELDS: {
|
|
62
60
|
readonly ID: "id";
|
|
@@ -29,6 +29,24 @@ export declare class StoreRepository extends BaseRepository<StoreDocument> {
|
|
|
29
29
|
* Find a store by its URL slug (also the document ID).
|
|
30
30
|
*/
|
|
31
31
|
findBySlug(storeSlug: string): Promise<StoreDocument | null>;
|
|
32
|
+
/**
|
|
33
|
+
* The slugs of every store paid at shipping rather than at delivery.
|
|
34
|
+
*
|
|
35
|
+
* 🛑 A `Set` of IDS, not the documents. The payout sweep needs only
|
|
36
|
+
* membership, and loading full store documents for a question answered by a
|
|
37
|
+
* string would spend the ~3-round-trip budget (Rule #6) on data it discards.
|
|
38
|
+
*
|
|
39
|
+
* 🛑 An EQUALITY on `trustedSeller == true`, never `!= false`. A Firestore
|
|
40
|
+
* inequality excludes every document that LACKS the field — which is every
|
|
41
|
+
* store, since the flag is new and optional — so `!=` would return the
|
|
42
|
+
* trusted set and nothing else, or everything, depending on which way you
|
|
43
|
+
* wrote it. Both are wrong and neither raises.
|
|
44
|
+
*
|
|
45
|
+
* Bounded at 500: trust is granted by hand, one store at a time, on risk
|
|
46
|
+
* grounds. If this ever saturates, the grant has stopped being deliberate
|
|
47
|
+
* and that is the thing to fix rather than the limit.
|
|
48
|
+
*/
|
|
49
|
+
findTrustedStoreIds(): Promise<Set<string>>;
|
|
32
50
|
/**
|
|
33
51
|
* Find the store owned by a specific user UID.
|
|
34
52
|
*/
|
|
@@ -59,6 +59,32 @@ export class StoreRepository extends BaseRepository {
|
|
|
59
59
|
async findBySlug(storeSlug) {
|
|
60
60
|
return this.findById(storeSlug);
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* The slugs of every store paid at shipping rather than at delivery.
|
|
64
|
+
*
|
|
65
|
+
* 🛑 A `Set` of IDS, not the documents. The payout sweep needs only
|
|
66
|
+
* membership, and loading full store documents for a question answered by a
|
|
67
|
+
* string would spend the ~3-round-trip budget (Rule #6) on data it discards.
|
|
68
|
+
*
|
|
69
|
+
* 🛑 An EQUALITY on `trustedSeller == true`, never `!= false`. A Firestore
|
|
70
|
+
* inequality excludes every document that LACKS the field — which is every
|
|
71
|
+
* store, since the flag is new and optional — so `!=` would return the
|
|
72
|
+
* trusted set and nothing else, or everything, depending on which way you
|
|
73
|
+
* wrote it. Both are wrong and neither raises.
|
|
74
|
+
*
|
|
75
|
+
* Bounded at 500: trust is granted by hand, one store at a time, on risk
|
|
76
|
+
* grounds. If this ever saturates, the grant has stopped being deliberate
|
|
77
|
+
* and that is the thing to fix rather than the limit.
|
|
78
|
+
*/
|
|
79
|
+
async findTrustedStoreIds() {
|
|
80
|
+
const snap = await this.db
|
|
81
|
+
.collection(this.collection)
|
|
82
|
+
.where(STORE_FIELDS.TRUSTED_SELLER, "==", true)
|
|
83
|
+
.select()
|
|
84
|
+
.limit(500)
|
|
85
|
+
.get();
|
|
86
|
+
return new Set(snap.docs.map((d) => d.id));
|
|
87
|
+
}
|
|
62
88
|
/**
|
|
63
89
|
* Find the store owned by a specific user UID.
|
|
64
90
|
*/
|
|
@@ -117,7 +143,7 @@ export class StoreRepository extends BaseRepository {
|
|
|
117
143
|
trigger: ctx?.trigger ?? defaultTrigger,
|
|
118
144
|
reason: ctx?.reason,
|
|
119
145
|
note: ctx?.note,
|
|
120
|
-
// `encryptPiiFields`
|
|
146
|
+
// `encryptPiiFields` covers only NAMED paths, so an undeclared nested secret
|
|
121
147
|
// would be stored in plaintext — the list below is the scrub.
|
|
122
148
|
piiFields: STORE_HISTORY_PII_FIELDS,
|
|
123
149
|
});
|
|
@@ -107,6 +107,27 @@ export interface StoreDocument extends BaseDocument {
|
|
|
107
107
|
adminNotes?: string;
|
|
108
108
|
/** Admin "verified store" badge — distinct from `status`/`isPublic`. */
|
|
109
109
|
isVerified?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Paid at SHIPPING rather than at delivery.
|
|
112
|
+
*
|
|
113
|
+
* 🛑 **A risk transfer, not a courtesy or a badge.** Paying before delivery
|
|
114
|
+
* means the platform carries a lost parcel and a non-delivery dispute with
|
|
115
|
+
* the money already gone. Admin-granted only, `recordAdminAction`-logged,
|
|
116
|
+
* revocable — and **PRIVATE in the public projection**, because it describes
|
|
117
|
+
* our risk posture rather than anything a buyer should weigh. A store does
|
|
118
|
+
* not get to display that we trust it.
|
|
119
|
+
*
|
|
120
|
+
* 🛑 A faster path through the SAME machinery, never a second one. The
|
|
121
|
+
* eligibility sweep moves the delivered-status test, not the payout, the
|
|
122
|
+
* hold, the deductions or the dispatch.
|
|
123
|
+
*
|
|
124
|
+
* 🛑 **A refund after a trusted payout has no money left to net against.**
|
|
125
|
+
* The ordinary path deducts from a still-`pending` payout; once paid there
|
|
126
|
+
* is nothing to deduct from, so it becomes a `manual_refund_due` case —
|
|
127
|
+
* which already exists and already means exactly this. Never a negative
|
|
128
|
+
* payout.
|
|
129
|
+
*/
|
|
130
|
+
trustedSeller?: boolean;
|
|
110
131
|
/** Reason recorded when `status` is set to "suspended". */
|
|
111
132
|
suspensionReason?: string;
|
|
112
133
|
stats?: {
|
|
@@ -386,6 +407,7 @@ export declare const STORE_FIELDS: {
|
|
|
386
407
|
readonly ADMIN_NOTES: "adminNotes";
|
|
387
408
|
readonly IS_FEATURED: "isFeatured";
|
|
388
409
|
readonly IS_VERIFIED: "isVerified";
|
|
410
|
+
readonly TRUSTED_SELLER: "trustedSeller";
|
|
389
411
|
readonly SUSPENSION_REASON: "suspensionReason";
|
|
390
412
|
readonly CAPABILITIES: "capabilities";
|
|
391
413
|
readonly STATUS_VALUES: {
|
|
@@ -404,3 +426,22 @@ export type StoreAddressUpdateInput = _AddrUpdate;
|
|
|
404
426
|
export { ADDRESS_FIELDS as STORE_ADDRESS_FIELDS, ADDRESS_INDEXED_FIELDS as STORE_ADDRESS_INDEXED_FIELDS, ADDRESS_PUBLIC_FIELDS as STORE_ADDRESS_PUBLIC_FIELDS, ADDRESS_UPDATABLE_FIELDS as STORE_ADDRESS_UPDATABLE_FIELDS, DEFAULT_ADDRESS_DATA as DEFAULT_STORE_ADDRESS_DATA, } from "../../addresses/schemas";
|
|
405
427
|
/** @deprecated SB-UNI-A — literal alias only. Use ADDRESSES_COLLECTION. */
|
|
406
428
|
export declare const STORE_ADDRESS_SUBCOLLECTION: "addresses";
|
|
429
|
+
/**
|
|
430
|
+
* The platform's own storefront.
|
|
431
|
+
*
|
|
432
|
+
* An IDENTITY, not a setting — it is the same id the seed's `REAL_STORE_IDS`
|
|
433
|
+
* and `realListingsOnly` already treat as the platform's own, and resolving it
|
|
434
|
+
* in code is what stops it being switched off by an edit, a reseed, or a
|
|
435
|
+
* mis-clicked admin toggle. `storeAcceptsPoints` is the motivating reader: the
|
|
436
|
+
* one store the points programme is advertised on cannot be allowed to decline
|
|
437
|
+
* to fund it.
|
|
438
|
+
*
|
|
439
|
+
* 🛑 It lives HERE, in `features/`, and not beside its reader in
|
|
440
|
+
* `_internal/shared/features/points/` — `audit-ssr-in-appkit` Rule 3 forbids a
|
|
441
|
+
* brand-bearing literal inside `_internal/`, because a consumer of appkit would
|
|
442
|
+
* inherit OUR store id with no way to override it. `features/` is where the
|
|
443
|
+
* same literal already lives legitimately (`CONSIGNMENT_STORE_ID` in
|
|
444
|
+
* `features/catalogue/utils/product-from-item.ts`), and this file is the one
|
|
445
|
+
* that already documents the rule on `acceptsPoints` above.
|
|
446
|
+
*/
|
|
447
|
+
export declare const PLATFORM_STORE_ID = "store-letitrip-official";
|
|
@@ -74,6 +74,7 @@ export const STORE_FIELDS = {
|
|
|
74
74
|
ADMIN_NOTES: "adminNotes",
|
|
75
75
|
IS_FEATURED: "isFeatured",
|
|
76
76
|
IS_VERIFIED: "isVerified",
|
|
77
|
+
TRUSTED_SELLER: "trustedSeller",
|
|
77
78
|
SUSPENSION_REASON: "suspensionReason",
|
|
78
79
|
CAPABILITIES: "capabilities",
|
|
79
80
|
STATUS_VALUES: {
|
|
@@ -101,3 +102,22 @@ export function generateStoreSlug(storeName, ownerDisplayName) {
|
|
|
101
102
|
export { ADDRESS_FIELDS as STORE_ADDRESS_FIELDS, ADDRESS_INDEXED_FIELDS as STORE_ADDRESS_INDEXED_FIELDS, ADDRESS_PUBLIC_FIELDS as STORE_ADDRESS_PUBLIC_FIELDS, ADDRESS_UPDATABLE_FIELDS as STORE_ADDRESS_UPDATABLE_FIELDS, DEFAULT_ADDRESS_DATA as DEFAULT_STORE_ADDRESS_DATA, } from "../../addresses/schemas/index.js";
|
|
102
103
|
/** @deprecated SB-UNI-A — literal alias only. Use ADDRESSES_COLLECTION. */
|
|
103
104
|
export const STORE_ADDRESS_SUBCOLLECTION = "addresses";
|
|
105
|
+
/**
|
|
106
|
+
* The platform's own storefront.
|
|
107
|
+
*
|
|
108
|
+
* An IDENTITY, not a setting — it is the same id the seed's `REAL_STORE_IDS`
|
|
109
|
+
* and `realListingsOnly` already treat as the platform's own, and resolving it
|
|
110
|
+
* in code is what stops it being switched off by an edit, a reseed, or a
|
|
111
|
+
* mis-clicked admin toggle. `storeAcceptsPoints` is the motivating reader: the
|
|
112
|
+
* one store the points programme is advertised on cannot be allowed to decline
|
|
113
|
+
* to fund it.
|
|
114
|
+
*
|
|
115
|
+
* 🛑 It lives HERE, in `features/`, and not beside its reader in
|
|
116
|
+
* `_internal/shared/features/points/` — `audit-ssr-in-appkit` Rule 3 forbids a
|
|
117
|
+
* brand-bearing literal inside `_internal/`, because a consumer of appkit would
|
|
118
|
+
* inherit OUR store id with no way to override it. `features/` is where the
|
|
119
|
+
* same literal already lives legitimately (`CONSIGNMENT_STORE_ID` in
|
|
120
|
+
* `features/catalogue/utils/product-from-item.ts`), and this file is the one
|
|
121
|
+
* that already documents the rule on `acceptsPoints` above.
|
|
122
|
+
*/
|
|
123
|
+
export const PLATFORM_STORE_ID = "store-letitrip-official";
|
|
@@ -218,13 +218,13 @@ export declare const storeFirestoreSchema: z.ZodObject<{
|
|
|
218
218
|
totalReviews: z.ZodNumber;
|
|
219
219
|
averageRating: z.ZodOptional<z.ZodNumber>;
|
|
220
220
|
}, "strip", z.ZodTypeAny, {
|
|
221
|
-
itemsSold: number;
|
|
222
221
|
totalProducts: number;
|
|
222
|
+
itemsSold: number;
|
|
223
223
|
totalReviews: number;
|
|
224
224
|
averageRating?: number | undefined;
|
|
225
225
|
}, {
|
|
226
|
-
itemsSold: number;
|
|
227
226
|
totalProducts: number;
|
|
227
|
+
itemsSold: number;
|
|
228
228
|
totalReviews: number;
|
|
229
229
|
averageRating?: number | undefined;
|
|
230
230
|
}>>;
|
|
@@ -363,8 +363,8 @@ export declare const storeFirestoreSchema: z.ZodObject<{
|
|
|
363
363
|
location?: string | undefined;
|
|
364
364
|
returnPolicy?: string | undefined;
|
|
365
365
|
stats?: {
|
|
366
|
-
itemsSold: number;
|
|
367
366
|
totalProducts: number;
|
|
367
|
+
itemsSold: number;
|
|
368
368
|
totalReviews: number;
|
|
369
369
|
averageRating?: number | undefined;
|
|
370
370
|
} | undefined;
|
|
@@ -432,8 +432,8 @@ export declare const storeFirestoreSchema: z.ZodObject<{
|
|
|
432
432
|
location?: string | undefined;
|
|
433
433
|
returnPolicy?: string | undefined;
|
|
434
434
|
stats?: {
|
|
435
|
-
itemsSold: number;
|
|
436
435
|
totalProducts: number;
|
|
436
|
+
itemsSold: number;
|
|
437
437
|
totalReviews: number;
|
|
438
438
|
averageRating?: number | undefined;
|
|
439
439
|
} | undefined;
|
|
@@ -530,8 +530,8 @@ export declare const storeListItemSchema: z.ZodObject<{
|
|
|
530
530
|
storeDescription?: string | undefined;
|
|
531
531
|
storeLogoURL?: string | undefined;
|
|
532
532
|
storeBannerURL?: string | undefined;
|
|
533
|
-
itemsSold?: number | undefined;
|
|
534
533
|
totalProducts?: number | undefined;
|
|
534
|
+
itemsSold?: number | undefined;
|
|
535
535
|
totalReviews?: number | undefined;
|
|
536
536
|
averageRating?: number | undefined;
|
|
537
537
|
}, {
|
|
@@ -546,8 +546,8 @@ export declare const storeListItemSchema: z.ZodObject<{
|
|
|
546
546
|
storeDescription?: string | undefined;
|
|
547
547
|
storeLogoURL?: string | undefined;
|
|
548
548
|
storeBannerURL?: string | undefined;
|
|
549
|
-
itemsSold?: number | undefined;
|
|
550
549
|
totalProducts?: number | undefined;
|
|
550
|
+
itemsSold?: number | undefined;
|
|
551
551
|
totalReviews?: number | undefined;
|
|
552
552
|
averageRating?: number | undefined;
|
|
553
553
|
}>;
|
|
@@ -9,6 +9,41 @@ import { sieveAnd, sieveFilter, SIEVE_OP } from "../../../utils/sieve-builder.js
|
|
|
9
9
|
import { maskPii, revealPii } from "../../../security/pii-accessors.js";
|
|
10
10
|
import { SUPPORT_TICKET_COLLECTION, SUPPORT_TICKET_PII_FIELDS, SUPPORT_TICKET_TRACKED_FIELDS, TicketStatusValues, ACTIVE_TICKET_STATUSES, } from "../schemas/firestore.js";
|
|
11
11
|
import { withHistory } from "../../../_internal/shared/history/index.js";
|
|
12
|
+
/**
|
|
13
|
+
* Who, if anyone, gets credited with resolving this ticket.
|
|
14
|
+
*
|
|
15
|
+
* 🛑 **A BUYER is never the resolver**, even though a buyer can drive the
|
|
16
|
+
* transition — replying "that's sorted, thanks" is a legitimate way for a
|
|
17
|
+
* ticket to reach `resolved`, and crediting it would put a customer's uid in
|
|
18
|
+
* a field a payslip is computed from. `system` has no uid at all, which is
|
|
19
|
+
* the other case that must not be paid.
|
|
20
|
+
*
|
|
21
|
+
* Returning `undefined` is correct and expected: the ticket is still resolved,
|
|
22
|
+
* it simply earns nobody wages. A fabricated fallback — the assignee, the
|
|
23
|
+
* ticket owner — would be a wage attributed to someone who did not do the
|
|
24
|
+
* work, and it would look exactly like a real one.
|
|
25
|
+
*/
|
|
26
|
+
const WAGE_EARNING_ACTOR_ROLES = new Set(["seller", "admin"]);
|
|
27
|
+
function resolverUid(ctx) {
|
|
28
|
+
const actor = ctx?.actor;
|
|
29
|
+
if (!actor?.uid)
|
|
30
|
+
return undefined;
|
|
31
|
+
/*
|
|
32
|
+
* An ALLOW-LIST, not `role !== "buyer"`. A tenth actor role added later
|
|
33
|
+
* would silently start earning wages under a deny-list, which is the
|
|
34
|
+
* `Record<Union, T>`-over-`Set<string>` argument this codebase makes
|
|
35
|
+
* everywhere else — and the roles that can be credited is the shorter,
|
|
36
|
+
* more stable list.
|
|
37
|
+
*
|
|
38
|
+
* 🛑 `HistoryActorRole` has no `employee` member, and that is FINE: this
|
|
39
|
+
* field records WHO resolved it, and the payroll decides whether that
|
|
40
|
+
* person is on it. A seller resolving their own store's ticket is recorded
|
|
41
|
+
* here and appears on no payslip, because they are not an employee.
|
|
42
|
+
*/
|
|
43
|
+
if (!WAGE_EARNING_ACTOR_ROLES.has(actor.role))
|
|
44
|
+
return undefined;
|
|
45
|
+
return actor.uid;
|
|
46
|
+
}
|
|
12
47
|
export class SupportRepository extends BaseRepository {
|
|
13
48
|
constructor() {
|
|
14
49
|
super(SUPPORT_TICKET_COLLECTION);
|
|
@@ -210,7 +245,7 @@ export class SupportRepository extends BaseRepository {
|
|
|
210
245
|
// `piiFields`, which is what encrypts `assignedToName` on the way down.
|
|
211
246
|
const patch = {
|
|
212
247
|
...update,
|
|
213
|
-
...this.resolutionStamps(current, update.status),
|
|
248
|
+
...this.resolutionStamps(current, update.status, resolverUid(ctx)),
|
|
214
249
|
updatedAt: new Date(),
|
|
215
250
|
};
|
|
216
251
|
return this.update(ticketId, this.withTicketHistory(current, patch, ctx, "updateTicketStatus"));
|
|
@@ -233,14 +268,45 @@ export class SupportRepository extends BaseRepository {
|
|
|
233
268
|
* against the document would poison that patch's inferred type with
|
|
234
269
|
* `Encrypted<string>` members it never actually sets.
|
|
235
270
|
*/
|
|
236
|
-
resolutionStamps(current, next) {
|
|
271
|
+
resolutionStamps(current, next, actorUid) {
|
|
237
272
|
if (!next || next === current?.status)
|
|
238
273
|
return {};
|
|
239
274
|
const now = new Date();
|
|
240
|
-
if (next === TicketStatusValues.RESOLVED && !current?.resolvedAt)
|
|
241
|
-
|
|
275
|
+
if (next === TicketStatusValues.RESOLVED && !current?.resolvedAt) {
|
|
276
|
+
/*
|
|
277
|
+
* 🛑 `resolvedBy` is stamped HERE and is not the same thing as
|
|
278
|
+
* `assignedTo`.
|
|
279
|
+
*
|
|
280
|
+
* `assignedTo` is the CURRENT assignee: reassign a resolved ticket for
|
|
281
|
+
* follow-up and it moves, so paying from it credits whoever happens to
|
|
282
|
+
* hold the ticket at month end rather than whoever did the work. That is
|
|
283
|
+
* the whole reason this field exists.
|
|
284
|
+
*
|
|
285
|
+
* Absent when the caller supplied no actor — a system transition has no
|
|
286
|
+
* person to pay, and a fabricated uid would be a wage attributed to
|
|
287
|
+
* somebody who did nothing.
|
|
288
|
+
*/
|
|
289
|
+
return actorUid ? { resolvedAt: now, resolvedBy: actorUid } : { resolvedAt: now };
|
|
290
|
+
}
|
|
242
291
|
if (next === TicketStatusValues.CLOSED && !current?.closedAt)
|
|
243
292
|
return { closedAt: now };
|
|
293
|
+
/*
|
|
294
|
+
* 🛑 A REOPEN clears both, and clearing `resolvedBy` is load-bearing.
|
|
295
|
+
*
|
|
296
|
+
* Pay is computed from tickets STILL RESOLVED at period end, so a ticket
|
|
297
|
+
* that goes back to open must stop counting — otherwise close-it,
|
|
298
|
+
* let-it-reopen, close-it-again pays twice for one problem.
|
|
299
|
+
*
|
|
300
|
+
* Cleared rather than left in place with the status as the only signal,
|
|
301
|
+
* because `resolvedTicketsForPeriod` filters on all three and a stale
|
|
302
|
+
* `resolvedAt` surviving a reopen is a row that looks payable to anything
|
|
303
|
+
* that checks fewer of them.
|
|
304
|
+
*/
|
|
305
|
+
const reopened = current?.resolvedAt != null &&
|
|
306
|
+
next !== TicketStatusValues.RESOLVED &&
|
|
307
|
+
next !== TicketStatusValues.CLOSED;
|
|
308
|
+
if (reopened)
|
|
309
|
+
return { resolvedAt: undefined, resolvedBy: undefined };
|
|
244
310
|
return {};
|
|
245
311
|
}
|
|
246
312
|
/** Append a timeline entry when the patch touches a tracked field. */
|
|
@@ -259,7 +325,7 @@ export class SupportRepository extends BaseRepository {
|
|
|
259
325
|
trigger: ctx?.trigger ?? defaultTrigger,
|
|
260
326
|
reason: ctx?.reason,
|
|
261
327
|
note: ctx?.note,
|
|
262
|
-
// `encryptPiiFields`
|
|
328
|
+
// `encryptPiiFields` covers only NAMED paths, so an undeclared PII-named value
|
|
263
329
|
// reaching `changes` would persist in PLAINTEXT inside statusHistory.
|
|
264
330
|
piiFields: SUPPORT_TICKET_PII_FIELDS,
|
|
265
331
|
});
|
|
@@ -288,7 +354,7 @@ export class SupportRepository extends BaseRepository {
|
|
|
288
354
|
};
|
|
289
355
|
if (newStatus) {
|
|
290
356
|
const current = prior !== undefined ? prior : await this.getTicketById(ticketId);
|
|
291
|
-
Object.assign(updateData, this.withTicketHistory(current, { status: newStatus, ...this.resolutionStamps(current, newStatus) }, ctx, "addMessage"));
|
|
357
|
+
Object.assign(updateData, this.withTicketHistory(current, { status: newStatus, ...this.resolutionStamps(current, newStatus, resolverUid(ctx)) }, ctx, "addMessage"));
|
|
292
358
|
}
|
|
293
359
|
await ref.update(updateData);
|
|
294
360
|
}
|
|
@@ -196,6 +196,23 @@ export interface SupportTicketDocument extends BaseDocument {
|
|
|
196
196
|
* `Date` field and split the collection into two shapes.
|
|
197
197
|
*/
|
|
198
198
|
resolvedAt?: Date;
|
|
199
|
+
/**
|
|
200
|
+
* The uid of whoever moved the ticket to `resolved`.
|
|
201
|
+
*
|
|
202
|
+
* 🛑 **Not `assignedTo`.** That is the CURRENT assignee and moves on
|
|
203
|
+
* reassignment, so paying from it credits whoever holds the ticket at period
|
|
204
|
+
* end rather than whoever did the work. Employee pay is computed from this
|
|
205
|
+
* field; see `resolvedTicketsForPeriod`.
|
|
206
|
+
*
|
|
207
|
+
* Server-stamped, never client-sent, and **cleared on reopen** — pay counts
|
|
208
|
+
* tickets still resolved at period end, so a reopened ticket must stop
|
|
209
|
+
* counting or closing it twice pays twice.
|
|
210
|
+
*
|
|
211
|
+
* Absent on a system transition and on a buyer-driven one: both are real
|
|
212
|
+
* resolutions that earn nobody wages, and a fallback to the assignee would
|
|
213
|
+
* be a wage attributed to someone who did nothing.
|
|
214
|
+
*/
|
|
215
|
+
resolvedBy?: string;
|
|
199
216
|
closedAt?: Date;
|
|
200
217
|
/** When we acknowledged receipt. Server-stamped at intake. */
|
|
201
218
|
acknowledgedAt?: Date;
|