@mohasinac/appkit 4.59.0 → 4.60.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 +62 -5
- package/dist/_internal/client/features/maintenance/views/MaintenanceDashboardView.js +2 -2
- package/dist/_internal/server/features/brands/actions.js +78 -4
- package/dist/_internal/server/features/checkout/actions.d.ts +36 -13
- package/dist/_internal/server/features/checkout/actions.js +668 -183
- package/dist/_internal/server/features/checkout/supplier-identity.js +18 -1
- package/dist/_internal/server/features/gateway-events/index.d.ts +10 -0
- package/dist/_internal/server/features/gateway-events/index.js +10 -0
- package/dist/_internal/server/features/gateway-events/ingest.d.ts +127 -0
- package/dist/_internal/server/features/gateway-events/ingest.js +188 -0
- package/dist/_internal/server/features/gateway-events/redact.d.ts +66 -0
- package/dist/_internal/server/features/gateway-events/redact.js +151 -0
- package/dist/_internal/server/features/grievance/actions.d.ts +63 -0
- package/dist/_internal/server/features/grievance/actions.js +173 -0
- package/dist/_internal/server/features/grievance/index.d.ts +8 -0
- package/dist/_internal/server/features/grievance/index.js +8 -0
- package/dist/_internal/server/features/grouped/data.js +28 -3
- package/dist/_internal/server/features/lottery/actions.js +99 -4
- package/dist/_internal/server/features/order-completion/actions.d.ts +92 -0
- package/dist/_internal/server/features/order-completion/actions.js +336 -0
- package/dist/_internal/server/features/order-completion/index.d.ts +7 -0
- package/dist/_internal/server/features/order-completion/index.js +7 -0
- package/dist/_internal/server/features/orders/actions.d.ts +13 -0
- package/dist/_internal/server/features/orders/actions.js +56 -2
- package/dist/_internal/server/features/orders/adapters.js +26 -0
- package/dist/_internal/server/features/orders/cancellation-refund.d.ts +116 -0
- package/dist/_internal/server/features/orders/cancellation-refund.js +466 -0
- package/dist/_internal/server/features/orders/index.d.ts +2 -1
- package/dist/_internal/server/features/orders/index.js +1 -1
- package/dist/_internal/server/features/orders/service.d.ts +60 -0
- package/dist/_internal/server/features/orders/service.js +74 -0
- package/dist/_internal/server/features/payment-cases/disputes.d.ts +76 -0
- package/dist/_internal/server/features/payment-cases/disputes.js +241 -0
- package/dist/_internal/server/features/payment-cases/index.d.ts +9 -0
- package/dist/_internal/server/features/payment-cases/index.js +9 -0
- package/dist/_internal/server/features/payouts/actions.d.ts +28 -3
- package/dist/_internal/server/features/payouts/actions.js +84 -10
- package/dist/_internal/server/features/points/adjust.d.ts +61 -0
- package/dist/_internal/server/features/points/adjust.js +121 -0
- package/dist/_internal/server/features/points/award.d.ts +65 -0
- package/dist/_internal/server/features/points/award.js +145 -0
- package/dist/_internal/server/features/price-index/market.d.ts +33 -0
- package/dist/_internal/server/features/price-index/market.js +54 -1
- package/dist/_internal/server/features/products/data.js +40 -22
- package/dist/_internal/server/features/protection-claims/actions.d.ts +72 -0
- package/dist/_internal/server/features/protection-claims/actions.js +228 -0
- package/dist/_internal/server/features/protection-claims/index.d.ts +8 -0
- package/dist/_internal/server/features/protection-claims/index.js +8 -0
- package/dist/_internal/server/features/refunds/actions.d.ts +20 -0
- package/dist/_internal/server/features/refunds/actions.js +259 -17
- package/dist/_internal/server/features/refunds/capacity.d.ts +29 -0
- package/dist/_internal/server/features/refunds/capacity.js +37 -0
- package/dist/_internal/server/features/reviews/actions.js +3 -2
- package/dist/_internal/server/features/search/index-build/sources.js +20 -1
- package/dist/_internal/server/features/shipping/reconcile.d.ts +10 -1
- package/dist/_internal/server/features/shipping/reconcile.js +34 -3
- package/dist/_internal/server/features/shipping/reimbursement.d.ts +117 -0
- package/dist/_internal/server/features/shipping/reimbursement.js +223 -0
- package/dist/_internal/server/features/stores/tabs.d.ts +19 -4
- package/dist/_internal/server/features/stores/tabs.js +24 -4
- package/dist/_internal/server/functions/https.d.ts +31 -1
- package/dist/_internal/server/functions/https.js +52 -1
- package/dist/_internal/server/functions/scheduled.d.ts +72 -1
- package/dist/_internal/server/functions/scheduled.js +124 -1
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +62 -16
- package/dist/_internal/server/jobs/core/boxRollupReconcile.d.ts +46 -0
- package/dist/_internal/server/jobs/core/boxRollupReconcile.js +142 -0
- package/dist/_internal/server/jobs/core/dailyDataCleanup.js +149 -1
- package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +14 -0
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +79 -1
- package/dist/_internal/server/jobs/core/index.d.ts +2 -0
- package/dist/_internal/server/jobs/core/index.js +2 -0
- package/dist/_internal/server/jobs/core/marketPriceRollup.d.ts +64 -0
- package/dist/_internal/server/jobs/core/marketPriceRollup.js +153 -0
- package/dist/_internal/server/jobs/core/obligationSweep.d.ts +36 -0
- package/dist/_internal/server/jobs/core/obligationSweep.js +503 -0
- package/dist/_internal/server/jobs/core/paymentReconcile.d.ts +79 -0
- package/dist/_internal/server/jobs/core/paymentReconcile.js +614 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +34 -0
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +35 -4
- package/dist/_internal/server/jobs/handlers/boxRollupReconcile.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/boxRollupReconcile.js +15 -0
- package/dist/_internal/server/jobs/handlers/countersReconcileNow.d.ts +65 -0
- package/dist/_internal/server/jobs/handlers/countersReconcileNow.js +89 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +5 -0
- package/dist/_internal/server/jobs/handlers/index.js +5 -0
- package/dist/_internal/server/jobs/handlers/marketPriceRollup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/marketPriceRollup.js +2 -0
- package/dist/_internal/server/jobs/handlers/obligationSweep.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/obligationSweep.js +15 -0
- package/dist/_internal/server/jobs/handlers/onShipmentAllocationSync.js +30 -1
- package/dist/_internal/server/jobs/handlers/paymentReconcile.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/paymentReconcile.js +6 -0
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +71 -0
- package/dist/_internal/shared/checkout/coupon-discount.d.ts +73 -0
- package/dist/_internal/shared/checkout/coupon-discount.js +103 -0
- package/dist/_internal/shared/checkout/lanes.d.ts +42 -0
- package/dist/_internal/shared/checkout/lanes.js +46 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +149 -1
- package/dist/_internal/shared/checkout/order-math.js +251 -3
- package/dist/_internal/shared/checkout/refundable-base.d.ts +116 -0
- package/dist/_internal/shared/checkout/refundable-base.js +122 -0
- package/dist/_internal/shared/features/auctions/config.d.ts +14 -1
- package/dist/_internal/shared/features/auctions/config.js +30 -1
- package/dist/_internal/shared/features/brands/schema.d.ts +42 -0
- package/dist/_internal/shared/features/brands/schema.js +18 -0
- package/dist/_internal/shared/features/orders/refund-copy.d.ts +22 -0
- package/dist/_internal/shared/features/orders/refund-copy.js +34 -0
- package/dist/_internal/shared/features/print/label-profiles.d.ts +198 -0
- package/dist/_internal/shared/features/print/label-profiles.js +350 -0
- package/dist/_internal/shared/features/products/to-product-item.js +33 -5
- package/dist/_internal/shared/fees/calculator.d.ts +31 -2
- package/dist/_internal/shared/fees/calculator.js +103 -5
- package/dist/_internal/shared/fees/india-states.d.ts +195 -0
- package/dist/_internal/shared/fees/india-states.js +190 -0
- package/dist/_internal/shared/fees/payout-hold.d.ts +80 -0
- package/dist/_internal/shared/fees/payout-hold.js +74 -0
- package/dist/_internal/shared/obligations/deadlines.d.ts +94 -0
- package/dist/_internal/shared/obligations/deadlines.js +155 -0
- package/dist/_internal/shared/shipping/quote.d.ts +96 -0
- package/dist/_internal/shared/shipping/quote.js +67 -0
- package/dist/_internal/shared/time/business-days.d.ts +91 -0
- package/dist/_internal/shared/time/business-days.js +185 -0
- package/dist/cli/index.js +0 -2
- package/dist/client.d.ts +9 -4
- package/dist/client.js +26 -3
- package/dist/configs/tailwind.d.ts +0 -12
- package/dist/configs/tailwind.js +28 -15
- package/dist/constants/api-endpoints.d.ts +51 -9
- package/dist/constants/api-endpoints.js +23 -7
- package/dist/constants/index.d.ts +1 -1
- package/dist/constants/index.js +1 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/payment.d.ts +131 -0
- package/dist/features/about/components/AboutView.js +1 -1
- package/dist/features/about/components/DeveloperView.js +5 -2
- package/dist/features/about/components/FAQPageView.js +2 -1
- package/dist/features/about/components/FeesView.js +2 -1
- package/dist/features/about/components/HelpPageView.js +2 -1
- package/dist/features/about/components/HowAuctionsWorkView.js +2 -1
- package/dist/features/about/components/HowCheckoutWorksView.js +2 -1
- package/dist/features/about/components/HowEmiWorksView.js +2 -1
- package/dist/features/about/components/HowOffersWorkView.js +2 -1
- package/dist/features/about/components/HowOrdersWorkView.js +2 -1
- package/dist/features/about/components/HowPayoutsWorkView.js +2 -1
- package/dist/features/about/components/HowPreOrdersWorkView.js +2 -1
- package/dist/features/about/components/HowReviewsWorkView.js +2 -1
- package/dist/features/about/components/PolicyPageView.js +2 -1
- package/dist/features/about/components/ProseHero.d.ts +73 -0
- package/dist/features/about/components/ProseHero.js +18 -0
- package/dist/features/about/components/SecurityPrivacyView.js +2 -1
- package/dist/features/about/components/ShippingPolicyView.js +2 -1
- package/dist/features/about/components/TrackOrderView.js +2 -1
- package/dist/features/about/components/UnauthorizedView.js +1 -1
- package/dist/features/about/index.d.ts +2 -0
- package/dist/features/about/index.js +5 -0
- package/dist/features/account/actions/address-actions.d.ts +26 -2
- package/dist/features/account/actions/address-actions.js +25 -1
- package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
- package/dist/features/account/components/NotificationPreferencesPanel.js +1 -1
- package/dist/features/account/components/UserOrdersView.js +17 -0
- package/dist/features/account/components/UserSidebar.js +1 -1
- package/dist/features/account/hooks/useNotifications.js +29 -0
- package/dist/features/admin/actions/admin-coupon-actions.js +36 -0
- package/dist/features/admin/components/AdminBeysView.js +2 -2
- package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
- package/dist/features/admin/components/AdminBrandEditorView.js +32 -2
- package/dist/features/admin/components/AdminBundleEditorView.js +1 -1
- package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
- package/dist/features/admin/components/AdminCategoriesView.js +1 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +73 -17
- package/dist/features/admin/components/AdminContactEditorView.js +1 -1
- package/dist/features/admin/components/AdminCouponEditorView.js +26 -1
- package/dist/features/admin/components/AdminFaqEditorView.js +1 -1
- package/dist/features/admin/components/AdminFaqsView.js +2 -2
- package/dist/features/admin/components/AdminGatewayEventsView.d.ts +54 -0
- package/dist/features/admin/components/AdminGatewayEventsView.js +209 -0
- package/dist/features/admin/components/AdminGuideHubView.js +1 -1
- package/dist/features/admin/components/AdminMoneyCasesView.d.ts +60 -0
- package/dist/features/admin/components/AdminMoneyCasesView.js +287 -0
- package/dist/features/admin/components/AdminPartsView.js +2 -2
- package/dist/features/admin/components/AdminProductsView.js +75 -4
- package/dist/features/admin/components/AdminSidebar.js +2 -2
- package/dist/features/admin/components/AdminSiteSettingsView.js +65 -7
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +2 -0
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +1 -1
- package/dist/features/admin/components/AdminSupportTicketsView.js +2 -2
- package/dist/features/admin/components/AdminTesterChecklistView.js +2 -2
- package/dist/features/admin/components/AdminUsersView.js +72 -1
- package/dist/features/admin/components/AdminViewCards.d.ts +3 -1
- package/dist/features/admin/components/AdminViewCards.js +4 -4
- package/dist/features/admin/components/DataListingView.d.ts +28 -0
- package/dist/features/admin/components/DataListingView.js +175 -14
- package/dist/features/admin/components/DataTable.d.ts +12 -1
- package/dist/features/admin/components/DataTable.js +3 -3
- package/dist/features/admin/components/OwnerQuickActions.d.ts +1 -1
- package/dist/features/admin/components/OwnerQuickActions.js +60 -2
- package/dist/features/admin/components/RecordOwnerQuickActions.d.ts +1 -1
- package/dist/features/admin/components/RecordOwnerQuickActions.js +61 -14
- package/dist/features/admin/components/analytics/AdminStatCard.d.ts +35 -1
- package/dist/features/admin/components/analytics/AdminStatCard.js +77 -2
- package/dist/features/admin/components/index.d.ts +4 -0
- package/dist/features/admin/components/index.js +6 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +156 -0
- package/dist/features/admin/constants/filter-tabs.js +106 -0
- package/dist/features/admin/repository/site-settings.repository.d.ts +22 -0
- package/dist/features/admin/repository/site-settings.repository.js +82 -1
- package/dist/features/admin/schemas/admin-editor-forms.d.ts +21 -0
- package/dist/features/admin/schemas/admin-editor-forms.js +44 -0
- package/dist/features/admin/schemas/firestore.js +5 -1
- package/dist/features/admin/schemas/site-settings-form.js +30 -1
- package/dist/features/auctions/actions/bid-actions.js +13 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +2 -2
- package/dist/features/auctions/components/AuctionDetailPageView.js +3 -1
- package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -0
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +5 -3
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +8 -1
- package/dist/features/auctions/components/PlaceBidFormClient.js +2 -2
- package/dist/features/audit-log/schemas/firestore.d.ts +28 -0
- package/dist/features/audit-log/schemas/firestore.js +14 -0
- package/dist/features/auth/permissions/constants.d.ts +17 -1
- package/dist/features/auth/permissions/constants.js +18 -0
- package/dist/features/blog/components/BlogPostView.js +1 -1
- package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +1 -1
- package/dist/features/categories/api/[id]/description-template/route.js +107 -1
- package/dist/features/categories/components/BrandDetailPageView.js +2 -2
- package/dist/features/categories/components/BreadcrumbTrail.js +7 -1
- package/dist/features/categories/components/BundleDetailView.js +1 -1
- package/dist/features/categories/components/CategoryDetailPageView.js +2 -2
- package/dist/features/categories/components/CategoryForm.js +12 -1
- package/dist/features/categories/components/CategoryTemplatePicker.js +38 -5
- package/dist/features/categories/repository/market-price-index.repository.d.ts +123 -0
- package/dist/features/categories/repository/market-price-index.repository.js +105 -0
- package/dist/features/categories/repository/price-observation.repository.d.ts +166 -0
- package/dist/features/categories/repository/price-observation.repository.js +132 -0
- package/dist/features/events/components/EventSaleBanner.js +1 -1
- package/dist/features/events/components/EventStatusBadge.js +1 -1
- package/dist/features/faq/components/FAQAccordion.js +2 -1
- package/dist/features/faq/components/FAQCategorySidebar.js +2 -2
- package/dist/features/faq/components/FAQHelpfulButtons.js +4 -4
- package/dist/features/faq/components/RelatedFAQs.js +1 -1
- package/dist/features/filters/FilterFacetSection.js +1 -1
- package/dist/features/filters/SwitchFilter.js +1 -1
- package/dist/features/gateway-events/repository/gateway-events.repository.d.ts +117 -0
- package/dist/features/gateway-events/repository/gateway-events.repository.js +274 -0
- package/dist/features/gateway-events/schemas/firestore.d.ts +217 -0
- package/dist/features/gateway-events/schemas/firestore.js +125 -0
- package/dist/features/gateway-events/schemas/index.d.ts +81 -0
- package/dist/features/gateway-events/schemas/index.js +79 -0
- package/dist/features/grouped/components/GroupedListingDetailView.js +1 -1
- package/dist/features/homepage/components/AnnouncementBar.js +37 -1
- package/dist/features/homepage/components/AnnouncementBar.style.css +30 -0
- package/dist/features/homepage/components/BrandsSection.js +1 -1
- package/dist/features/homepage/components/CTABannerSection.js +1 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +14 -24
- package/dist/features/homepage/components/HeroCarousel.js +2 -2
- package/dist/features/homepage/components/SocialPostCard.js +1 -1
- package/dist/features/inventory/components/AdminMoveLedgerView.d.ts +7 -0
- package/dist/features/inventory/components/AdminMoveLedgerView.js +157 -0
- package/dist/features/inventory/components/AdminStorageBoxesView.d.ts +11 -0
- package/dist/features/inventory/components/AdminStorageBoxesView.js +168 -0
- package/dist/features/inventory/components/ReceiveStationView.d.ts +39 -0
- package/dist/features/inventory/components/ReceiveStationView.js +413 -0
- package/dist/features/inventory/components/operator-message.d.ts +29 -0
- package/dist/features/inventory/components/operator-message.js +41 -0
- package/dist/features/inventory/index.d.ts +13 -0
- package/dist/features/inventory/index.js +28 -0
- package/dist/features/inventory/repository/inventory-movements.repository.d.ts +97 -0
- package/dist/features/inventory/repository/inventory-movements.repository.js +186 -0
- package/dist/features/inventory/repository/inventory-placements.repository.d.ts +179 -0
- package/dist/features/inventory/repository/inventory-placements.repository.js +345 -0
- package/dist/features/inventory/repository/storage-boxes.repository.d.ts +83 -0
- package/dist/features/inventory/repository/storage-boxes.repository.js +127 -0
- package/dist/features/inventory/schemas/firestore.d.ts +197 -0
- package/dist/features/inventory/schemas/firestore.js +159 -0
- package/dist/features/layout/AppLayoutShell.d.ts +12 -1
- package/dist/features/layout/AppLayoutShell.js +25 -12
- package/dist/features/layout/AutoBreadcrumbs.js +162 -3
- package/dist/features/layout/AutoBreadcrumbs.style.css +45 -0
- package/dist/features/layout/BottomNavbar.d.ts +1 -1
- package/dist/features/layout/BottomNavbar.js +2 -2
- package/dist/features/media/MediaAvatar.js +1 -1
- package/dist/features/media/MediaImage.d.ts +24 -4
- package/dist/features/media/MediaImage.js +44 -4
- package/dist/features/media/MediaLightbox.js +1 -1
- package/dist/features/media/modals/ImageCropModal.js +1 -1
- package/dist/features/media/modals/ImageEditor.js +1 -1
- package/dist/features/media/upload/MediaUploadField.js +91 -3
- package/dist/features/obligations/constants/policies.d.ts +92 -0
- package/dist/features/obligations/constants/policies.js +378 -0
- package/dist/features/obligations/repository/obligations.repository.d.ts +68 -0
- package/dist/features/obligations/repository/obligations.repository.js +158 -0
- package/dist/features/obligations/schemas/firestore.d.ts +160 -0
- package/dist/features/obligations/schemas/firestore.js +87 -0
- package/dist/features/orders/actions/order-actions.d.ts +43 -4
- package/dist/features/orders/actions/order-actions.js +60 -3
- package/dist/features/orders/actions/refund-actions.js +70 -10
- package/dist/features/orders/components/OrderCompletionPanel.d.ts +27 -0
- package/dist/features/orders/components/OrderCompletionPanel.js +192 -0
- package/dist/features/orders/components/OrdersList.js +2 -2
- package/dist/features/orders/components/RefundHistoryTable.js +3 -1
- package/dist/features/orders/components/index.d.ts +2 -0
- package/dist/features/orders/components/index.js +6 -0
- package/dist/features/orders/constants/transitions.d.ts +59 -0
- package/dist/features/orders/constants/transitions.js +104 -0
- package/dist/features/orders/repository/orders.repository.d.ts +109 -6
- package/dist/features/orders/repository/orders.repository.js +464 -21
- package/dist/features/orders/schemas/firestore.d.ts +270 -0
- package/dist/features/orders/schemas/firestore.js +24 -0
- package/dist/features/orders/schemas/index.d.ts +61 -0
- package/dist/features/orders/schemas/index.js +22 -0
- package/dist/features/orders/types/index.d.ts +79 -0
- package/dist/features/parts/components/PartsIndexPageView.js +8 -1
- package/dist/features/payment-cases/repository/payment-cases.repository.d.ts +170 -0
- package/dist/features/payment-cases/repository/payment-cases.repository.js +342 -0
- package/dist/features/payment-cases/schemas/firestore.d.ts +229 -0
- package/dist/features/payment-cases/schemas/firestore.js +105 -0
- package/dist/features/payment-cases/schemas/resolve-form.d.ts +97 -0
- package/dist/features/payment-cases/schemas/resolve-form.js +114 -0
- package/dist/features/payments/repository/payout.repository.d.ts +0 -1
- package/dist/features/payments/repository/payout.repository.js +0 -1
- package/dist/features/points/repository/points.repository.d.ts +122 -0
- package/dist/features/points/repository/points.repository.js +304 -1
- package/dist/features/points/schemas/adjust-form.d.ts +58 -0
- package/dist/features/points/schemas/adjust-form.js +63 -0
- package/dist/features/points/schemas/firestore.d.ts +108 -1
- package/dist/features/points/schemas/firestore.js +84 -0
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
- package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
- package/dist/features/price-index/components/PriceIndexToolView.d.ts +23 -4
- package/dist/features/price-index/components/PriceIndexToolView.js +71 -6
- package/dist/features/price-index/market-stats.d.ts +82 -0
- package/dist/features/price-index/market-stats.js +230 -0
- package/dist/features/print/components/BoxLabelPrintView.d.ts +25 -0
- package/dist/features/print/components/BoxLabelPrintView.js +66 -0
- package/dist/features/print/components/LabelProfilePicker.d.ts +13 -0
- package/dist/features/print/components/LabelProfilePicker.js +38 -0
- package/dist/features/print/components/labels.d.ts +117 -0
- package/dist/features/print/components/labels.js +258 -0
- package/dist/features/print/components/labels.style.css +46 -0
- package/dist/features/print/index.d.ts +6 -0
- package/dist/features/print/index.js +16 -0
- package/dist/features/products/columns/productTableColumns.js +5 -4
- package/dist/features/products/components/MarketplaceBundleCard.js +2 -2
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +6 -6
- package/dist/features/products/components/PrizeDrawCollage.js +1 -1
- package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
- package/dist/features/products/components/PrizeRevealModal.js +2 -2
- package/dist/features/products/components/ProductDetailPageView.js +18 -4
- package/dist/features/products/components/ProductForm.js +34 -23
- package/dist/features/products/components/ProductGrid.js +86 -62
- package/dist/features/products/constants/card-badges.d.ts +97 -0
- package/dist/features/products/constants/card-badges.js +100 -0
- package/dist/features/products/repository/products.repository.js +34 -1
- package/dist/features/products/schemas/firestore.d.ts +46 -3
- package/dist/features/products/schemas/firestore.js +21 -0
- package/dist/features/promotions/components/PromotionsView.js +1 -1
- package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +32 -0
- package/dist/features/promotions/repository/claimed-coupons.repository.js +58 -0
- package/dist/features/promotions/schemas/firestore.js +30 -3
- package/dist/features/promotions/schemas/index.d.ts +6 -6
- package/dist/features/protection-claims/repository/protection-claims.repository.d.ts +54 -0
- package/dist/features/protection-claims/repository/protection-claims.repository.js +174 -0
- package/dist/features/protection-claims/schemas/firestore.d.ts +168 -0
- package/dist/features/protection-claims/schemas/firestore.js +130 -0
- package/dist/features/reviews/actions/review-actions.js +3 -2
- package/dist/features/reviews/components/ReviewModal.js +1 -1
- package/dist/features/reviews/components/ReviewsList.js +1 -1
- package/dist/features/reviews/schemas/firestore.d.ts +20 -1
- package/dist/features/scams/constants/scam-types.d.ts +2 -0
- package/dist/features/scams/constants/scam-types.js +18 -0
- package/dist/features/scams/schemas/report-form.d.ts +3 -3
- package/dist/features/scams/schemas/report-form.js +26 -1
- package/dist/features/search/components/Search.js +9 -9
- package/dist/features/search/components/SearchResultsClient.js +18 -3
- package/dist/features/search/components/SearchResultsPageView.d.ts +1 -6
- package/dist/features/search/components/SearchResultsPageView.js +2 -2
- package/dist/features/seller/actions/offer-actions.js +16 -1
- package/dist/features/seller/actions/seller-actions.js +14 -3
- package/dist/features/seller/components/CategoryInlineSelect.js +75 -11
- package/dist/features/seller/components/PrintCenterView.d.ts +5 -0
- package/dist/features/seller/components/PrintCenterView.js +41 -30
- package/dist/features/seller/components/SellerOrdersView.js +1 -1
- package/dist/features/seller/components/SellerPayoutRequestView.js +16 -5
- package/dist/features/seller/components/SellerPayoutSettingsView.js +1 -1
- package/dist/features/seller/components/SellerProductShell.d.ts +16 -0
- package/dist/features/seller/components/SellerProductShell.js +123 -13
- package/dist/features/seller/components/SellerReviewsView.js +4 -2
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/seller/schemas/coupon-form.d.ts +26 -0
- package/dist/features/seller/schemas/coupon-form.js +27 -0
- package/dist/features/seller/schemas/product-form.d.ts +3 -0
- package/dist/features/seller/schemas/product-form.js +5 -0
- package/dist/features/shell/FormShell.js +2 -2
- package/dist/features/shell/QuickFormDrawer.js +14 -0
- package/dist/features/shell/SectionForm.d.ts +19 -0
- package/dist/features/shell/SectionForm.js +133 -9
- package/dist/features/shell/SectionForm.style.css +54 -0
- package/dist/features/shipments/components/AdminShipmentsView.js +16 -1
- package/dist/features/shipments/repository/shipment-items.repository.d.ts +9 -0
- package/dist/features/shipments/repository/shipment-items.repository.js +14 -0
- package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
- package/dist/features/sticker-trace/components/StickerCommitPanel.js +1 -1
- package/dist/features/store-extensions/schemas/report-review-form.d.ts +8 -8
- package/dist/features/stores/components/StoreGuideHubView.js +1 -1
- package/dist/features/stores/components/StoreHeader.js +1 -1
- package/dist/features/support/schemas/firestore.d.ts +73 -2
- package/dist/features/support/schemas/firestore.js +59 -0
- package/dist/index.d.ts +16 -24
- package/dist/index.js +38 -47
- package/dist/next/ErrorBoundary.js +1 -1
- package/dist/next/routing/route-map.d.ts +24 -0
- package/dist/next/routing/route-map.js +40 -0
- package/dist/providers/payment-manual/index.d.ts +15 -1
- package/dist/providers/payment-manual/index.js +37 -0
- package/dist/providers/payment-phonepe/index.d.ts +14 -1
- package/dist/providers/payment-phonepe/index.js +36 -0
- package/dist/providers/payment-razorpay/index.d.ts +44 -1
- package/dist/providers/payment-razorpay/index.js +99 -0
- package/dist/providers/shipping-shiprocket/index.d.ts +18 -0
- package/dist/providers/shipping-shiprocket/index.js +18 -0
- package/dist/schemas/registry.d.ts +44 -11
- package/dist/server-entry.d.ts +2 -0
- package/dist/server-entry.js +27 -0
- package/dist/server.d.ts +42 -0
- package/dist/server.js +93 -0
- package/dist/styles.css +418 -149
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/default-dark.js +15 -12
- package/dist/tokens/themes/default-light.js +14 -11
- package/dist/tokens/tokens.css +106 -23
- package/dist/ui/DataTable.style.css +4 -4
- package/dist/ui/columns/cell-renderers.d.ts +20 -0
- package/dist/ui/columns/cell-renderers.js +26 -1
- package/dist/ui/columns/index.d.ts +1 -1
- package/dist/ui/columns/index.js +4 -1
- package/dist/ui/components/Badge.style.css +1 -1
- package/dist/ui/components/Breadcrumb.js +19 -1
- package/dist/ui/components/BulkActionBar.style.css +4 -4
- package/dist/ui/components/Button.style.css +1 -1
- package/dist/ui/components/Checkbox.style.css +1 -1
- package/dist/ui/components/FallbackShell.js +1 -1
- package/dist/ui/components/Form.d.ts +14 -1
- package/dist/ui/components/Form.js +2 -2
- package/dist/ui/components/FormField.js +18 -4
- package/dist/ui/components/FormField.style.css +43 -3
- package/dist/ui/components/GatedPrice.style.css +1 -1
- package/dist/ui/components/IconButton.style.css +1 -1
- package/dist/ui/components/Input.js +22 -1
- package/dist/ui/components/Input.style.css +38 -22
- package/dist/ui/components/ListingFilterDrawer.js +1 -1
- package/dist/ui/components/ListingToolbar.js +1 -1
- package/dist/ui/components/PaginatedSelect.js +0 -0
- package/dist/ui/components/Radio.style.css +1 -1
- package/dist/ui/components/Repeater.d.ts +53 -0
- package/dist/ui/components/Repeater.js +7 -0
- package/dist/ui/components/RichTextEditor.d.ts +20 -1
- package/dist/ui/components/RichTextEditor.js +23 -2
- package/dist/ui/components/Select.style.css +7 -21
- package/dist/ui/components/Semantic.js +27 -3
- package/dist/ui/components/TagInput.style.css +4 -9
- package/dist/ui/components/Textarea.style.css +4 -18
- package/dist/ui/components/Toggle.style.css +1 -1
- package/dist/ui/components/Typography.d.ts +3 -3
- package/dist/ui/components/Typography.js +15 -15
- package/dist/ui/components/ViewToggle.style.css +4 -4
- package/dist/ui/forms/AddAnotherToggle.d.ts +16 -0
- package/dist/ui/forms/AddAnotherToggle.js +56 -0
- package/dist/ui/forms/ColorPickerField.js +15 -2
- package/dist/ui/forms/FieldCheckbox.js +15 -2
- package/dist/ui/forms/FieldInput.d.ts +22 -1
- package/dist/ui/forms/FieldInput.js +19 -5
- package/dist/ui/forms/FieldLabel.d.ts +35 -0
- package/dist/ui/forms/FieldLabel.js +70 -0
- package/dist/ui/forms/FieldSelect.d.ts +4 -1
- package/dist/ui/forms/FieldSelect.js +27 -5
- package/dist/ui/forms/FieldTextarea.d.ts +4 -1
- package/dist/ui/forms/FieldTextarea.js +22 -5
- package/dist/ui/forms/FormShell.d.ts +17 -1
- package/dist/ui/forms/FormShell.js +36 -2
- package/dist/ui/forms/FormShell.style.css +41 -33
- package/dist/ui/forms/index.d.ts +5 -1
- package/dist/ui/forms/index.js +2 -0
- package/dist/ui/icons/icon-registry.d.ts +1 -0
- package/dist/ui/icons/icon-registry.js +17 -1
- package/dist/utils/id-generators.d.ts +80 -0
- package/dist/utils/id-generators.js +93 -0
- package/dist/utils/search-txt-builders.d.ts +12 -0
- package/dist/utils/search-txt-builders.js +17 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +106 -5
- package/dist/features/loyalty/api/admin/loyalty/grant/route.d.ts +0 -12
- package/dist/features/loyalty/api/admin/loyalty/grant/route.js +0 -38
- package/dist/features/loyalty/api/balance/route.d.ts +0 -12
- package/dist/features/loyalty/api/balance/route.js +0 -41
- package/dist/features/loyalty/api/earn/route.d.ts +0 -12
- package/dist/features/loyalty/api/earn/route.js +0 -38
- package/dist/features/loyalty/api/history/route.d.ts +0 -12
- package/dist/features/loyalty/api/history/route.js +0 -58
- package/dist/features/loyalty/api/redeem/route.d.ts +0 -12
- package/dist/features/loyalty/api/redeem/route.js +0 -38
- package/dist/features/loyalty/columns/index.d.ts +0 -17
- package/dist/features/loyalty/columns/index.js +0 -40
- package/dist/features/loyalty/components/CoinsDisplay.d.ts +0 -12
- package/dist/features/loyalty/components/CoinsDisplay.js +0 -10
- package/dist/features/loyalty/components/index.d.ts +0 -1
- package/dist/features/loyalty/components/index.js +0 -1
- package/dist/features/loyalty/hooks/useLoyaltyBalance.d.ts +0 -4
- package/dist/features/loyalty/hooks/useLoyaltyBalance.js +0 -25
- package/dist/features/loyalty/index.d.ts +0 -6
- package/dist/features/loyalty/index.js +0 -6
- package/dist/features/loyalty/manifest.d.ts +0 -2
- package/dist/features/loyalty/manifest.js +0 -16
- package/dist/features/loyalty/messages/en.json +0 -19
- package/dist/features/loyalty/repository/loyalty.repository.d.ts +0 -8
- package/dist/features/loyalty/repository/loyalty.repository.js +0 -22
- package/dist/features/loyalty/schemas/index.d.ts +0 -85
- package/dist/features/loyalty/schemas/index.js +0 -40
- package/dist/features/loyalty/server.d.ts +0 -6
- package/dist/features/loyalty/server.js +0 -6
- package/dist/features/loyalty/types/index.d.ts +0 -31
- package/dist/features/loyalty/types/index.js +0 -1
- package/dist/features/loyalty/types/loyalty-config.d.ts +0 -7
- package/dist/features/loyalty/types/loyalty-config.js +0 -1
- package/dist/features/loyalty/types/loyalty-math.d.ts +0 -9
- package/dist/features/loyalty/types/loyalty-math.js +0 -27
- package/dist/features/products/components/ProductOwnerQuickActions.d.ts +0 -11
- package/dist/features/products/components/ProductOwnerQuickActions.js +0 -120
|
@@ -52,6 +52,21 @@ export declare class PointsRepository {
|
|
|
52
52
|
orderId?: string;
|
|
53
53
|
adminReason?: string;
|
|
54
54
|
actorUid?: string;
|
|
55
|
+
/**
|
|
56
|
+
* When this batch stops being spendable. Decision 4: three months.
|
|
57
|
+
*
|
|
58
|
+
* 🛑 Optional, and ABSENT MEANS NEVER EXPIRES — which is the right default
|
|
59
|
+
* for every entry written before the field existed. Treating absence as
|
|
60
|
+
* "expired" would retroactively void live balances.
|
|
61
|
+
*
|
|
62
|
+
* A caller crediting points SHOULD pass it; `pointsExpiryFrom(now)` is the
|
|
63
|
+
* one helper that computes it, so the three-month figure has one owner.
|
|
64
|
+
* Spends and reversals deliberately pass nothing: a spend has nothing to
|
|
65
|
+
* expire, and a reversal restores points whose original clock was already
|
|
66
|
+
* consumed — a fresh three months there would be a quiet extension nobody
|
|
67
|
+
* granted.
|
|
68
|
+
*/
|
|
69
|
+
expiresAt?: Date;
|
|
55
70
|
}): Promise<{
|
|
56
71
|
entryId: string;
|
|
57
72
|
balance: number;
|
|
@@ -75,7 +90,114 @@ export declare class PointsRepository {
|
|
|
75
90
|
entryId: string;
|
|
76
91
|
balance: number;
|
|
77
92
|
}>;
|
|
93
|
+
/**
|
|
94
|
+
* Expire every lapsed batch, debiting whatever was never spent.
|
|
95
|
+
*
|
|
96
|
+
* ## 🛑 Expiry is a LEDGER ENTRY, never a silent balance edit
|
|
97
|
+
*
|
|
98
|
+
* The balance is DEFINED as the sum of the ledger. Quietly lowering the
|
|
99
|
+
* mirror would make the two disagree by exactly the expired amount, and the
|
|
100
|
+
* reconciliation job would then report drift forever with no way to tell it
|
|
101
|
+
* from a real fault. An adjustment row with a stated reason is the honest
|
|
102
|
+
* representation: the points left, and the row says why.
|
|
103
|
+
*
|
|
104
|
+
* 🛑 `actorUid: "system:points-expiry"` is a sentinel and this is the ONE
|
|
105
|
+
* place a system actor may write an adjustment — because expiry is a rule
|
|
106
|
+
* with a date attached, not a discretionary decision. Every other adjustment
|
|
107
|
+
* is a human's, which is why `adminReason` is mandatory on the kind.
|
|
108
|
+
*
|
|
109
|
+
* Marks the batch fully `consumed` in the same pass, and THAT is the
|
|
110
|
+
* idempotency key: without it an expiry job that ran twice would debit the
|
|
111
|
+
* buyer twice for one lapse.
|
|
112
|
+
*/
|
|
113
|
+
expireLapsedBatches(now: Date, limit?: number): Promise<{
|
|
114
|
+
examined: number;
|
|
115
|
+
expired: number;
|
|
116
|
+
points: number;
|
|
117
|
+
}>;
|
|
118
|
+
/**
|
|
119
|
+
* Return points a cancelled order had consumed — `redemption_reversal`.
|
|
120
|
+
*
|
|
121
|
+
* ## 🛑 Points come back as POINTS, never as money
|
|
122
|
+
*
|
|
123
|
+
* The schema header states it as a legal boundary rather than a preference:
|
|
124
|
+
* *"never refunded as cash when an order is cancelled; points SPENT on a
|
|
125
|
+
* cancelled order are returned as points."* Paying them out in rupees is what
|
|
126
|
+
* would make this stored value, and stored value in India is PPI territory
|
|
127
|
+
* with RBI authorisation attached. This is the writer that makes that stated
|
|
128
|
+
* rule true — the kind has existed with no writer since it was declared (D48).
|
|
129
|
+
*
|
|
130
|
+
* 🛑 NO fresh `expiresAt`. A reversal restores points whose original clock has
|
|
131
|
+
* already been consumed; granting three new months would be a quiet extension
|
|
132
|
+
* nobody authorised, and it would reward cancelling an order with a longer
|
|
133
|
+
* window than keeping it.
|
|
134
|
+
*
|
|
135
|
+
* Idempotent on the ORDER: one reversal per order, found by querying for an
|
|
136
|
+
* existing `redemption_reversal` carrying the same `orderId`. An append-only
|
|
137
|
+
* ledger cannot net off a double reversal, so the check has to happen before
|
|
138
|
+
* the write.
|
|
139
|
+
*/
|
|
140
|
+
reverseRedemptionForOrder(orderId: string, reason: string): Promise<{
|
|
141
|
+
reversed: number;
|
|
142
|
+
}>;
|
|
78
143
|
/** Most recent entries first, for the buyer's own points page. */
|
|
79
144
|
listForUser(userId: string, limit?: number): Promise<PointsLedgerEntry[]>;
|
|
145
|
+
/**
|
|
146
|
+
* Prove the mirror agrees with the ledger, and repair it where it does not.
|
|
147
|
+
*
|
|
148
|
+
* ## This is the job `sumLedger`'s own docstring has promised since day one
|
|
149
|
+
*
|
|
150
|
+
* That method says it exists "to repair the mirror and to let a
|
|
151
|
+
* reconciliation job prove the two agree" — and no such job was ever written
|
|
152
|
+
* (D49). So `users/{uid}.pointsBalance` could diverge from the entries
|
|
153
|
+
* indefinitely with nothing reporting it, which is Root Cause #102 exactly:
|
|
154
|
+
* a counter that cannot be checked drifts silently, because a wrong number
|
|
155
|
+
* raises nothing.
|
|
156
|
+
*
|
|
157
|
+
* ## 🛑 It enumerates USERS, not ledger entries
|
|
158
|
+
*
|
|
159
|
+
* Root Cause #102's reconciler could only ever ADD, because it built its
|
|
160
|
+
* tally from the events that existed rather than the entities that do — so a
|
|
161
|
+
* row whose last event was deleted was never visited and never reset. The
|
|
162
|
+
* same trap here: a user whose entries were all removed keeps a stale
|
|
163
|
+
* non-zero mirror forever if you only walk the ledger.
|
|
164
|
+
*
|
|
165
|
+
* So the candidate set is the UNION of two reads:
|
|
166
|
+
*
|
|
167
|
+
* - every user document carrying a `pointsBalance` — catches a mirror with
|
|
168
|
+
* no entries behind it, which is the case a ledger walk cannot see
|
|
169
|
+
* - the distinct `userId`s in a bounded page of the ledger — catches
|
|
170
|
+
* entries whose user has no mirror yet
|
|
171
|
+
*
|
|
172
|
+
* ## Repair direction, and why it still opens a case
|
|
173
|
+
*
|
|
174
|
+
* The ledger is append-only and is the DEFINITION of the balance, so repair
|
|
175
|
+
* is always mirror ← `sumLedger`. There is no judgement in it.
|
|
176
|
+
*
|
|
177
|
+
* 🛑 But a repaired mirror hides the writer that broke it. Every drift is
|
|
178
|
+
* therefore returned for the caller to raise a `ledger_drift` case: silent
|
|
179
|
+
* self-healing would make a persistently broken write path look like a
|
|
180
|
+
* healthy system, which is the failure this whole phase exists to remove.
|
|
181
|
+
*
|
|
182
|
+
* Absence of a mirror is NOT drift. `getBalance` already falls back to
|
|
183
|
+
* summing the ledger, so a user with entries and no mirror reads correctly —
|
|
184
|
+
* it is merely uncomputed. It is still written, because paying the scan on
|
|
185
|
+
* every balance read is what the mirror exists to avoid, but it is counted
|
|
186
|
+
* separately and never reported as a fault.
|
|
187
|
+
*
|
|
188
|
+
* Saturation is reported rather than hidden: at the page limit there are more
|
|
189
|
+
* users to check than were checked, which is a fact about the programme's
|
|
190
|
+
* size, not an error — but it must not read as "all clear".
|
|
191
|
+
*/
|
|
192
|
+
reconcileLedgerMirror(limit?: number): Promise<{
|
|
193
|
+
examined: number;
|
|
194
|
+
drifted: {
|
|
195
|
+
userId: string;
|
|
196
|
+
mirror: number;
|
|
197
|
+
ledger: number;
|
|
198
|
+
}[];
|
|
199
|
+
initialised: number;
|
|
200
|
+
saturated: boolean;
|
|
201
|
+
}>;
|
|
80
202
|
}
|
|
81
203
|
export declare const pointsRepository: PointsRepository;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getAdminDb } from "../../../providers/db-firebase/index.js";
|
|
2
|
-
import { POINTS_FIELDS, POINTS_LEDGER_COLLECTION, isPointsEntryWellFormed, } from "../schemas/firestore.js";
|
|
2
|
+
import { POINTS_FIELDS, POINTS_LEDGER_COLLECTION, POINTS_EXPIRY_MONTHS, isPointsEntryWellFormed, } from "../schemas/firestore.js";
|
|
3
3
|
import { USER_COLLECTION } from "../../auth/schemas/firestore.js";
|
|
4
4
|
/**
|
|
5
5
|
* The points ledger: append-only entries, and a balance that is their sum.
|
|
@@ -90,6 +90,7 @@ export class PointsRepository {
|
|
|
90
90
|
...(input.orderId ? { orderId: input.orderId } : {}),
|
|
91
91
|
...(input.adminReason ? { adminReason: input.adminReason } : {}),
|
|
92
92
|
...(input.actorUid ? { actorUid: input.actorUid } : {}),
|
|
93
|
+
...(input.expiresAt ? { expiresAt: input.expiresAt } : {}),
|
|
93
94
|
createdAt: new Date(),
|
|
94
95
|
updatedAt: new Date(),
|
|
95
96
|
};
|
|
@@ -116,6 +117,74 @@ export class PointsRepository {
|
|
|
116
117
|
const db = getAdminDb();
|
|
117
118
|
const entryRef = db.collection(POINTS_LEDGER_COLLECTION).doc();
|
|
118
119
|
const userRef = db.collection(USER_COLLECTION).doc(input.userId);
|
|
120
|
+
const now = new Date();
|
|
121
|
+
/*
|
|
122
|
+
* ── FIFO: which BATCHES does this spend draw from? ────────────────────
|
|
123
|
+
*
|
|
124
|
+
* 🛑 Queried OUTSIDE the transaction, and that is a Firestore constraint
|
|
125
|
+
* rather than a preference: a transaction must complete all reads before
|
|
126
|
+
* any write, and a collection QUERY cannot be mixed with the per-document
|
|
127
|
+
* updates below it. The part that must not race — the balance check — stays
|
|
128
|
+
* inside.
|
|
129
|
+
*
|
|
130
|
+
* The residual risk is two concurrent redemptions picking the same batches
|
|
131
|
+
* and over-consuming one, leaving a `consumed` figure above that entry's
|
|
132
|
+
* `amount`. That is an artefact on an EXPIRY calculation, not on a balance:
|
|
133
|
+
* the balance is guarded transactionally and still cannot go negative. The
|
|
134
|
+
* reconciliation job reports it, and a lock here would buy nothing a buyer
|
|
135
|
+
* can see.
|
|
136
|
+
*/
|
|
137
|
+
const credits = await db
|
|
138
|
+
.collection(POINTS_LEDGER_COLLECTION)
|
|
139
|
+
.where(POINTS_FIELDS.USER_ID, "==", input.userId)
|
|
140
|
+
.where(POINTS_FIELDS.AMOUNT, ">", 0)
|
|
141
|
+
.orderBy(POINTS_FIELDS.AMOUNT)
|
|
142
|
+
.limit(200)
|
|
143
|
+
.get();
|
|
144
|
+
/*
|
|
145
|
+
* 🛑 Re-sorted in memory, because the QUERY CANNOT RETURN FIFO ORDER.
|
|
146
|
+
* Firestore forces an inequality's field to lead the `orderBy` (Root Cause
|
|
147
|
+
* #59), so `amount > 0` means the results arrive smallest-credit-first.
|
|
148
|
+
* Skipping this sort would consume the SMALLEST batches first — which looks
|
|
149
|
+
* exactly like FIFO on a ledger where every credit happens to be the same
|
|
150
|
+
* size, and silently is not.
|
|
151
|
+
*
|
|
152
|
+
* Oldest first, so a buyer never loses points they could have spent.
|
|
153
|
+
* Newest-first silently maximises expiry: the house winning by a mechanism
|
|
154
|
+
* nobody announced.
|
|
155
|
+
*/
|
|
156
|
+
const batches = credits.docs
|
|
157
|
+
.map((d) => d.data())
|
|
158
|
+
.filter((e) => {
|
|
159
|
+
/*
|
|
160
|
+
* An expired batch is not spendable. Absent `expiresAt` means never
|
|
161
|
+
* expires — correct for every entry written before the field existed.
|
|
162
|
+
*/
|
|
163
|
+
if (!e.expiresAt)
|
|
164
|
+
return true;
|
|
165
|
+
const exp = e.expiresAt instanceof Date ? e.expiresAt : new Date(e.expiresAt);
|
|
166
|
+
return exp.getTime() > now.getTime();
|
|
167
|
+
})
|
|
168
|
+
.sort((a, b) => {
|
|
169
|
+
const at = a.createdAt instanceof Date ? a.createdAt.getTime() : 0;
|
|
170
|
+
const bt = b.createdAt instanceof Date ? b.createdAt.getTime() : 0;
|
|
171
|
+
return at - bt;
|
|
172
|
+
});
|
|
173
|
+
let toDraw = input.points;
|
|
174
|
+
const draws = [];
|
|
175
|
+
for (const batch of batches) {
|
|
176
|
+
if (toDraw <= 0)
|
|
177
|
+
break;
|
|
178
|
+
const remaining = Math.round((batch.amount - (batch.consumed ?? 0)) * 100) / 100;
|
|
179
|
+
if (remaining <= 0)
|
|
180
|
+
continue;
|
|
181
|
+
const take = Math.min(remaining, toDraw);
|
|
182
|
+
draws.push({
|
|
183
|
+
id: batch.id,
|
|
184
|
+
consumed: Math.round(((batch.consumed ?? 0) + take) * 100) / 100,
|
|
185
|
+
});
|
|
186
|
+
toDraw = Math.round((toDraw - take) * 100) / 100;
|
|
187
|
+
}
|
|
119
188
|
const balance = await db.runTransaction(async (tx) => {
|
|
120
189
|
const userSnap = await tx.get(userRef);
|
|
121
190
|
const current = typeof userSnap.data()?.pointsBalance === "number"
|
|
@@ -134,12 +203,138 @@ export class PointsRepository {
|
|
|
134
203
|
description: input.description,
|
|
135
204
|
createdAt: new Date(),
|
|
136
205
|
updatedAt: new Date(),
|
|
206
|
+
/*
|
|
207
|
+
* Which batches paid for this spend, recorded ON the spend rather than
|
|
208
|
+
* derived later — a reversal needs to know what to give back, and by
|
|
209
|
+
* then those batches may have expired. "Return it where it came from"
|
|
210
|
+
* is only answerable if the spend wrote it down.
|
|
211
|
+
*/
|
|
212
|
+
...(draws.length ? { drawnFrom: draws.map((d) => d.id) } : {}),
|
|
137
213
|
});
|
|
214
|
+
for (const d of draws) {
|
|
215
|
+
tx.update(db.collection(POINTS_LEDGER_COLLECTION).doc(d.id), {
|
|
216
|
+
consumed: d.consumed,
|
|
217
|
+
updatedAt: new Date(),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
138
220
|
tx.set(userRef, { pointsBalance: next }, { merge: true });
|
|
139
221
|
return next;
|
|
140
222
|
});
|
|
141
223
|
return { entryId: entryRef.id, balance };
|
|
142
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Expire every lapsed batch, debiting whatever was never spent.
|
|
227
|
+
*
|
|
228
|
+
* ## 🛑 Expiry is a LEDGER ENTRY, never a silent balance edit
|
|
229
|
+
*
|
|
230
|
+
* The balance is DEFINED as the sum of the ledger. Quietly lowering the
|
|
231
|
+
* mirror would make the two disagree by exactly the expired amount, and the
|
|
232
|
+
* reconciliation job would then report drift forever with no way to tell it
|
|
233
|
+
* from a real fault. An adjustment row with a stated reason is the honest
|
|
234
|
+
* representation: the points left, and the row says why.
|
|
235
|
+
*
|
|
236
|
+
* 🛑 `actorUid: "system:points-expiry"` is a sentinel and this is the ONE
|
|
237
|
+
* place a system actor may write an adjustment — because expiry is a rule
|
|
238
|
+
* with a date attached, not a discretionary decision. Every other adjustment
|
|
239
|
+
* is a human's, which is why `adminReason` is mandatory on the kind.
|
|
240
|
+
*
|
|
241
|
+
* Marks the batch fully `consumed` in the same pass, and THAT is the
|
|
242
|
+
* idempotency key: without it an expiry job that ran twice would debit the
|
|
243
|
+
* buyer twice for one lapse.
|
|
244
|
+
*/
|
|
245
|
+
async expireLapsedBatches(now, limit = 200) {
|
|
246
|
+
const db = getAdminDb();
|
|
247
|
+
const snap = await db
|
|
248
|
+
.collection(POINTS_LEDGER_COLLECTION)
|
|
249
|
+
.where(POINTS_FIELDS.EXPIRES_AT, "<=", now)
|
|
250
|
+
.orderBy(POINTS_FIELDS.EXPIRES_AT, "asc")
|
|
251
|
+
.limit(limit)
|
|
252
|
+
.get();
|
|
253
|
+
let expired = 0;
|
|
254
|
+
let points = 0;
|
|
255
|
+
for (const doc of snap.docs) {
|
|
256
|
+
const e = doc.data();
|
|
257
|
+
/*
|
|
258
|
+
* Credits only. A redemption carries no `expiresAt` so should never
|
|
259
|
+
* appear here — but the guard is free, and the cost of being wrong is
|
|
260
|
+
* debiting a buyer for a spend they already made.
|
|
261
|
+
*/
|
|
262
|
+
if (e.amount <= 0)
|
|
263
|
+
continue;
|
|
264
|
+
const remaining = Math.round((e.amount - (e.consumed ?? 0)) * 100) / 100;
|
|
265
|
+
if (remaining <= 0)
|
|
266
|
+
continue;
|
|
267
|
+
await this.append({
|
|
268
|
+
userId: e.userId,
|
|
269
|
+
kind: "admin_adjustment",
|
|
270
|
+
amount: -remaining,
|
|
271
|
+
description: `${remaining} points expired.`,
|
|
272
|
+
adminReason: `Automatic expiry after ${POINTS_EXPIRY_MONTHS} months — batch ${e.id}.`,
|
|
273
|
+
actorUid: "system:points-expiry",
|
|
274
|
+
});
|
|
275
|
+
await doc.ref.update({ consumed: e.amount, updatedAt: now });
|
|
276
|
+
expired += 1;
|
|
277
|
+
points = Math.round((points + remaining) * 100) / 100;
|
|
278
|
+
}
|
|
279
|
+
return { examined: snap.size, expired, points };
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Return points a cancelled order had consumed — `redemption_reversal`.
|
|
283
|
+
*
|
|
284
|
+
* ## 🛑 Points come back as POINTS, never as money
|
|
285
|
+
*
|
|
286
|
+
* The schema header states it as a legal boundary rather than a preference:
|
|
287
|
+
* *"never refunded as cash when an order is cancelled; points SPENT on a
|
|
288
|
+
* cancelled order are returned as points."* Paying them out in rupees is what
|
|
289
|
+
* would make this stored value, and stored value in India is PPI territory
|
|
290
|
+
* with RBI authorisation attached. This is the writer that makes that stated
|
|
291
|
+
* rule true — the kind has existed with no writer since it was declared (D48).
|
|
292
|
+
*
|
|
293
|
+
* 🛑 NO fresh `expiresAt`. A reversal restores points whose original clock has
|
|
294
|
+
* already been consumed; granting three new months would be a quiet extension
|
|
295
|
+
* nobody authorised, and it would reward cancelling an order with a longer
|
|
296
|
+
* window than keeping it.
|
|
297
|
+
*
|
|
298
|
+
* Idempotent on the ORDER: one reversal per order, found by querying for an
|
|
299
|
+
* existing `redemption_reversal` carrying the same `orderId`. An append-only
|
|
300
|
+
* ledger cannot net off a double reversal, so the check has to happen before
|
|
301
|
+
* the write.
|
|
302
|
+
*/
|
|
303
|
+
async reverseRedemptionForOrder(orderId, reason) {
|
|
304
|
+
const db = getAdminDb();
|
|
305
|
+
const already = await db
|
|
306
|
+
.collection(POINTS_LEDGER_COLLECTION)
|
|
307
|
+
.where(POINTS_FIELDS.ORDER_ID, "==", orderId)
|
|
308
|
+
.where(POINTS_FIELDS.KIND, "==", "redemption_reversal")
|
|
309
|
+
.limit(1)
|
|
310
|
+
.get();
|
|
311
|
+
if (!already.empty)
|
|
312
|
+
return { reversed: 0 };
|
|
313
|
+
const spends = await db
|
|
314
|
+
.collection(POINTS_LEDGER_COLLECTION)
|
|
315
|
+
.where(POINTS_FIELDS.ORDER_ID, "==", orderId)
|
|
316
|
+
.where(POINTS_FIELDS.KIND, "==", "redemption")
|
|
317
|
+
.get();
|
|
318
|
+
if (spends.empty)
|
|
319
|
+
return { reversed: 0 };
|
|
320
|
+
const rows = spends.docs.map((d) => d.data());
|
|
321
|
+
/*
|
|
322
|
+
* `Math.abs`, because a redemption is stored NEGATIVE and a reversal must
|
|
323
|
+
* be positive — `isPointsEntryWellFormed` refuses the wrong sign, so a
|
|
324
|
+
* missing `abs` here would throw rather than silently credit a negative.
|
|
325
|
+
*/
|
|
326
|
+
const total = Math.round(rows.reduce((sum, r) => sum + Math.abs(r.amount), 0) * 100) / 100;
|
|
327
|
+
if (total <= 0)
|
|
328
|
+
return { reversed: 0 };
|
|
329
|
+
await this.append({
|
|
330
|
+
userId: rows[0].userId,
|
|
331
|
+
kind: "redemption_reversal",
|
|
332
|
+
amount: total,
|
|
333
|
+
orderId,
|
|
334
|
+
description: `${total} points returned — ${reason}`,
|
|
335
|
+
});
|
|
336
|
+
return { reversed: total };
|
|
337
|
+
}
|
|
143
338
|
/** Most recent entries first, for the buyer's own points page. */
|
|
144
339
|
async listForUser(userId, limit = 50) {
|
|
145
340
|
const db = getAdminDb();
|
|
@@ -151,5 +346,113 @@ export class PointsRepository {
|
|
|
151
346
|
.get();
|
|
152
347
|
return rows.docs.map((d) => d.data());
|
|
153
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* Prove the mirror agrees with the ledger, and repair it where it does not.
|
|
351
|
+
*
|
|
352
|
+
* ## This is the job `sumLedger`'s own docstring has promised since day one
|
|
353
|
+
*
|
|
354
|
+
* That method says it exists "to repair the mirror and to let a
|
|
355
|
+
* reconciliation job prove the two agree" — and no such job was ever written
|
|
356
|
+
* (D49). So `users/{uid}.pointsBalance` could diverge from the entries
|
|
357
|
+
* indefinitely with nothing reporting it, which is Root Cause #102 exactly:
|
|
358
|
+
* a counter that cannot be checked drifts silently, because a wrong number
|
|
359
|
+
* raises nothing.
|
|
360
|
+
*
|
|
361
|
+
* ## 🛑 It enumerates USERS, not ledger entries
|
|
362
|
+
*
|
|
363
|
+
* Root Cause #102's reconciler could only ever ADD, because it built its
|
|
364
|
+
* tally from the events that existed rather than the entities that do — so a
|
|
365
|
+
* row whose last event was deleted was never visited and never reset. The
|
|
366
|
+
* same trap here: a user whose entries were all removed keeps a stale
|
|
367
|
+
* non-zero mirror forever if you only walk the ledger.
|
|
368
|
+
*
|
|
369
|
+
* So the candidate set is the UNION of two reads:
|
|
370
|
+
*
|
|
371
|
+
* - every user document carrying a `pointsBalance` — catches a mirror with
|
|
372
|
+
* no entries behind it, which is the case a ledger walk cannot see
|
|
373
|
+
* - the distinct `userId`s in a bounded page of the ledger — catches
|
|
374
|
+
* entries whose user has no mirror yet
|
|
375
|
+
*
|
|
376
|
+
* ## Repair direction, and why it still opens a case
|
|
377
|
+
*
|
|
378
|
+
* The ledger is append-only and is the DEFINITION of the balance, so repair
|
|
379
|
+
* is always mirror ← `sumLedger`. There is no judgement in it.
|
|
380
|
+
*
|
|
381
|
+
* 🛑 But a repaired mirror hides the writer that broke it. Every drift is
|
|
382
|
+
* therefore returned for the caller to raise a `ledger_drift` case: silent
|
|
383
|
+
* self-healing would make a persistently broken write path look like a
|
|
384
|
+
* healthy system, which is the failure this whole phase exists to remove.
|
|
385
|
+
*
|
|
386
|
+
* Absence of a mirror is NOT drift. `getBalance` already falls back to
|
|
387
|
+
* summing the ledger, so a user with entries and no mirror reads correctly —
|
|
388
|
+
* it is merely uncomputed. It is still written, because paying the scan on
|
|
389
|
+
* every balance read is what the mirror exists to avoid, but it is counted
|
|
390
|
+
* separately and never reported as a fault.
|
|
391
|
+
*
|
|
392
|
+
* Saturation is reported rather than hidden: at the page limit there are more
|
|
393
|
+
* users to check than were checked, which is a fact about the programme's
|
|
394
|
+
* size, not an error — but it must not read as "all clear".
|
|
395
|
+
*/
|
|
396
|
+
async reconcileLedgerMirror(limit = 500) {
|
|
397
|
+
const db = getAdminDb();
|
|
398
|
+
const mirrorSnap = await db
|
|
399
|
+
.collection(USER_COLLECTION)
|
|
400
|
+
.select("pointsBalance")
|
|
401
|
+
.limit(limit)
|
|
402
|
+
.get();
|
|
403
|
+
const ledgerSnap = await db
|
|
404
|
+
.collection(POINTS_LEDGER_COLLECTION)
|
|
405
|
+
.select(POINTS_FIELDS.USER_ID)
|
|
406
|
+
.limit(limit)
|
|
407
|
+
.get();
|
|
408
|
+
const mirrors = new Map();
|
|
409
|
+
for (const d of mirrorSnap.docs) {
|
|
410
|
+
const v = d.data()?.pointsBalance;
|
|
411
|
+
if (typeof v === "number")
|
|
412
|
+
mirrors.set(d.id, v);
|
|
413
|
+
}
|
|
414
|
+
const candidates = new Set(mirrors.keys());
|
|
415
|
+
for (const d of ledgerSnap.docs) {
|
|
416
|
+
const uid = d.data()?.[POINTS_FIELDS.USER_ID];
|
|
417
|
+
if (typeof uid === "string" && uid)
|
|
418
|
+
candidates.add(uid);
|
|
419
|
+
}
|
|
420
|
+
const drifted = [];
|
|
421
|
+
let initialised = 0;
|
|
422
|
+
for (const userId of candidates) {
|
|
423
|
+
const ledger = await this.sumLedger(userId);
|
|
424
|
+
const mirror = mirrors.get(userId);
|
|
425
|
+
if (mirror === undefined) {
|
|
426
|
+
// Uncomputed, not drifted — see the docstring.
|
|
427
|
+
if (ledger !== 0) {
|
|
428
|
+
await db
|
|
429
|
+
.collection(USER_COLLECTION)
|
|
430
|
+
.doc(userId)
|
|
431
|
+
.set({ pointsBalance: ledger }, { merge: true });
|
|
432
|
+
initialised += 1;
|
|
433
|
+
}
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
/*
|
|
437
|
+
* Half-paisa epsilon, matching every other money comparison here:
|
|
438
|
+
* `0.1 + 0.2 > 0.3` is true in IEEE-754, so an exact `!==` would report
|
|
439
|
+
* drift on a balance that is correct to the last paisa — and a reconciler
|
|
440
|
+
* that cries wolf nightly is one people stop reading.
|
|
441
|
+
*/
|
|
442
|
+
if (Math.abs(mirror - ledger) > 0.005) {
|
|
443
|
+
drifted.push({ userId, mirror, ledger });
|
|
444
|
+
await db
|
|
445
|
+
.collection(USER_COLLECTION)
|
|
446
|
+
.doc(userId)
|
|
447
|
+
.set({ pointsBalance: ledger }, { merge: true });
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
examined: candidates.size,
|
|
452
|
+
drifted,
|
|
453
|
+
initialised,
|
|
454
|
+
saturated: mirrorSnap.size === limit || ledgerSnap.size === limit,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
154
457
|
}
|
|
155
458
|
export const pointsRepository = new PointsRepository();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* The write contract for a manual points adjustment.
|
|
4
|
+
*
|
|
5
|
+
* ## 🛑 This creates or destroys SPENDABLE MONEY
|
|
6
|
+
*
|
|
7
|
+
* 1 point = ₹1 against a future order, so a credit here is a liability the
|
|
8
|
+
* platform has to honour and a debit takes value a buyer already holds. That
|
|
9
|
+
* is why this is the one ledger kind whose invariant
|
|
10
|
+
* (`isPointsEntryWellFormed`) demands a reason, and why the schema adds two
|
|
11
|
+
* things the invariant cannot express.
|
|
12
|
+
*
|
|
13
|
+
* ## 🛑 A CAP, because the realistic failure is a typo
|
|
14
|
+
*
|
|
15
|
+
* The invariant checks sign and reason, not magnitude — so `50000` where `500`
|
|
16
|
+
* was meant passes every existing check and mints ₹50,000 of liability in one
|
|
17
|
+
* click. `POINTS_ADJUSTMENT_MAX` bounds a single adjustment; a genuinely larger
|
|
18
|
+
* correction is several deliberate ones, each with its own reason and its own
|
|
19
|
+
* audit row, which is the right amount of friction for a number that size.
|
|
20
|
+
*
|
|
21
|
+
* The bound is on the ABSOLUTE value, so it guards the debit direction too: a
|
|
22
|
+
* mistyped debit wipes a balance the buyer earned and there is no undo beyond
|
|
23
|
+
* another adjustment.
|
|
24
|
+
*
|
|
25
|
+
* ## The reason is for the BUYER'S case file, not for us
|
|
26
|
+
*
|
|
27
|
+
* `adminReason` is never shown to the buyer (the schema says so), so its reader
|
|
28
|
+
* is the next admin asking "why does this account have 53 points". A reason
|
|
29
|
+
* like "fix" answers nothing, which is why the floor is a length rather than
|
|
30
|
+
* mere presence — the same reasoning as `paymentCaseResolveFormSchema`'s
|
|
31
|
+
* 10-character floor, and deliberately the same number so an operator meets one
|
|
32
|
+
* rule rather than two.
|
|
33
|
+
*
|
|
34
|
+
* 🛑 `amount` is SIGNED here, unlike `redeem()` which takes a positive and
|
|
35
|
+
* stores a negative. An adjustment genuinely goes both ways and the sign IS the
|
|
36
|
+
* intent, so hiding it behind a separate direction field would mean two inputs
|
|
37
|
+
* that can disagree. `.refine` rejects zero rather than letting `append()`
|
|
38
|
+
* throw on it, so the operator gets a field error instead of a 500.
|
|
39
|
+
*/
|
|
40
|
+
export declare const pointsAdjustFormSchema: z.ZodObject<{
|
|
41
|
+
userId: z.ZodString;
|
|
42
|
+
amount: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodNumber, number, number>, number, number>, number, number>, number, number>;
|
|
43
|
+
/** Shown to the BUYER on their points page. */
|
|
44
|
+
description: z.ZodString;
|
|
45
|
+
/** Never shown to the buyer. The next admin's answer to "why?". */
|
|
46
|
+
adminReason: z.ZodString;
|
|
47
|
+
}, "strict", z.ZodTypeAny, {
|
|
48
|
+
amount: number;
|
|
49
|
+
description: string;
|
|
50
|
+
userId: string;
|
|
51
|
+
adminReason: string;
|
|
52
|
+
}, {
|
|
53
|
+
amount: number;
|
|
54
|
+
description: string;
|
|
55
|
+
userId: string;
|
|
56
|
+
adminReason: string;
|
|
57
|
+
}>;
|
|
58
|
+
export type PointsAdjustFormValues = z.infer<typeof pointsAdjustFormSchema>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { POINTS_ADJUSTMENT_MAX, POINTS_ADJUSTMENT_REASON_MIN } from "./firestore.js";
|
|
3
|
+
/**
|
|
4
|
+
* The write contract for a manual points adjustment.
|
|
5
|
+
*
|
|
6
|
+
* ## 🛑 This creates or destroys SPENDABLE MONEY
|
|
7
|
+
*
|
|
8
|
+
* 1 point = ₹1 against a future order, so a credit here is a liability the
|
|
9
|
+
* platform has to honour and a debit takes value a buyer already holds. That
|
|
10
|
+
* is why this is the one ledger kind whose invariant
|
|
11
|
+
* (`isPointsEntryWellFormed`) demands a reason, and why the schema adds two
|
|
12
|
+
* things the invariant cannot express.
|
|
13
|
+
*
|
|
14
|
+
* ## 🛑 A CAP, because the realistic failure is a typo
|
|
15
|
+
*
|
|
16
|
+
* The invariant checks sign and reason, not magnitude — so `50000` where `500`
|
|
17
|
+
* was meant passes every existing check and mints ₹50,000 of liability in one
|
|
18
|
+
* click. `POINTS_ADJUSTMENT_MAX` bounds a single adjustment; a genuinely larger
|
|
19
|
+
* correction is several deliberate ones, each with its own reason and its own
|
|
20
|
+
* audit row, which is the right amount of friction for a number that size.
|
|
21
|
+
*
|
|
22
|
+
* The bound is on the ABSOLUTE value, so it guards the debit direction too: a
|
|
23
|
+
* mistyped debit wipes a balance the buyer earned and there is no undo beyond
|
|
24
|
+
* another adjustment.
|
|
25
|
+
*
|
|
26
|
+
* ## The reason is for the BUYER'S case file, not for us
|
|
27
|
+
*
|
|
28
|
+
* `adminReason` is never shown to the buyer (the schema says so), so its reader
|
|
29
|
+
* is the next admin asking "why does this account have 53 points". A reason
|
|
30
|
+
* like "fix" answers nothing, which is why the floor is a length rather than
|
|
31
|
+
* mere presence — the same reasoning as `paymentCaseResolveFormSchema`'s
|
|
32
|
+
* 10-character floor, and deliberately the same number so an operator meets one
|
|
33
|
+
* rule rather than two.
|
|
34
|
+
*
|
|
35
|
+
* 🛑 `amount` is SIGNED here, unlike `redeem()` which takes a positive and
|
|
36
|
+
* stores a negative. An adjustment genuinely goes both ways and the sign IS the
|
|
37
|
+
* intent, so hiding it behind a separate direction field would mean two inputs
|
|
38
|
+
* that can disagree. `.refine` rejects zero rather than letting `append()`
|
|
39
|
+
* throw on it, so the operator gets a field error instead of a 500.
|
|
40
|
+
*/
|
|
41
|
+
export const pointsAdjustFormSchema = z
|
|
42
|
+
.object({
|
|
43
|
+
userId: z.string().trim().min(1, "Pick a user."),
|
|
44
|
+
amount: z
|
|
45
|
+
.number({ invalid_type_error: "Enter a number of points." })
|
|
46
|
+
.refine((v) => Number.isFinite(v), "Enter a number of points.")
|
|
47
|
+
.refine((v) => v !== 0, "An adjustment of zero does nothing.")
|
|
48
|
+
.refine((v) => Math.abs(v) <= POINTS_ADJUSTMENT_MAX, `A single adjustment is capped at ${POINTS_ADJUSTMENT_MAX} points. Split a larger correction.`)
|
|
49
|
+
.refine((v) => Number.isInteger(v), "Points are whole numbers — a fraction of a point cannot be shown to a buyer or spent."),
|
|
50
|
+
/** Shown to the BUYER on their points page. */
|
|
51
|
+
description: z
|
|
52
|
+
.string()
|
|
53
|
+
.trim()
|
|
54
|
+
.min(1, "Say what the buyer will see for this entry.")
|
|
55
|
+
.max(140, "Keep the buyer-facing line under 140 characters."),
|
|
56
|
+
/** Never shown to the buyer. The next admin's answer to "why?". */
|
|
57
|
+
adminReason: z
|
|
58
|
+
.string()
|
|
59
|
+
.trim()
|
|
60
|
+
.min(POINTS_ADJUSTMENT_REASON_MIN, `Give a reason of at least ${POINTS_ADJUSTMENT_REASON_MIN} characters — the reader is the next admin, not the buyer.`)
|
|
61
|
+
.max(500, "Keep the reason under 500 characters."),
|
|
62
|
+
})
|
|
63
|
+
.strict();
|