@pisell/pisellos 0.0.568 → 0.0.570
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/effects/index.d.ts +2 -2
- package/dist/model/strategy/adapter/dataVariant/type.d.ts +3 -3
- package/dist/model/strategy/adapter/itemRule/type.d.ts +6 -6
- package/dist/model/strategy/adapter/promotion/evaluator.js +3 -2
- package/dist/model/strategy/adapter/promotion/type.d.ts +5 -5
- package/dist/model/strategy/adapter/walletPass/utils.js +7 -4
- package/dist/model/strategy/type.d.ts +1 -1
- package/dist/modules/BaseModule.d.ts +2 -2
- package/dist/modules/BaseModule.js +4 -4
- package/dist/modules/BookingContext/index.d.ts +48 -0
- package/dist/modules/BookingContext/index.js +566 -0
- package/dist/modules/BookingContext/types.d.ts +102 -0
- package/dist/modules/BookingContext/types.js +51 -0
- package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.d.ts +16 -0
- package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +302 -0
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +8 -0
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +149 -0
- package/dist/modules/BookingContext/utils/cacheItemToBookingInput.d.ts +76 -0
- package/dist/modules/BookingContext/utils/cacheItemToBookingInput.js +249 -0
- package/dist/modules/BookingContext/utils/flexible.d.ts +28 -0
- package/dist/modules/BookingContext/utils/flexible.js +56 -0
- package/dist/modules/BookingContext/utils/formatResourceList.d.ts +8 -0
- package/dist/modules/BookingContext/utils/formatResourceList.js +38 -0
- package/dist/modules/BookingContext/utils/index.d.ts +11 -0
- package/dist/modules/BookingContext/utils/index.js +11 -0
- package/dist/modules/BookingContext/utils/noResource.d.ts +13 -0
- package/dist/modules/BookingContext/utils/noResource.js +77 -0
- package/dist/modules/BookingContext/utils/orderLineDisplay.d.ts +1 -0
- package/dist/modules/BookingContext/utils/orderLineDisplay.js +36 -0
- package/dist/modules/BookingContext/utils/productExtend.d.ts +72 -0
- package/dist/modules/BookingContext/utils/productExtend.js +339 -0
- package/dist/modules/BookingContext/utils/resourceErrors.d.ts +41 -0
- package/dist/modules/BookingContext/utils/resourceErrors.js +74 -0
- package/dist/modules/BookingContext/utils/resourceSelection.d.ts +7 -0
- package/dist/modules/BookingContext/utils/resourceSelection.js +24 -0
- package/dist/modules/BookingContext/utils/resources.d.ts +80 -0
- package/dist/modules/BookingContext/utils/resources.js +486 -0
- package/dist/modules/BookingContext/utils/serviceTimes.d.ts +55 -0
- package/dist/modules/BookingContext/utils/serviceTimes.js +151 -0
- package/dist/modules/BookingContext/utils/timeSlices.d.ts +42 -0
- package/dist/modules/BookingContext/utils/timeSlices.js +100 -0
- package/dist/modules/Cart/utils/cartProduct.js +26 -5
- package/dist/modules/Cart/utils/changePrice.js +27 -20
- package/dist/modules/Customer/index.d.ts +20 -0
- package/dist/modules/Customer/index.js +172 -83
- package/dist/modules/Customer/types.d.ts +3 -1
- package/dist/modules/Discount/index.d.ts +2 -1
- package/dist/modules/Discount/index.js +37 -9
- package/dist/modules/Discount/types.d.ts +2 -1
- package/dist/modules/Holder/types.d.ts +1 -1
- package/dist/modules/OpenData/index.d.ts +8 -0
- package/dist/modules/OpenData/index.js +36 -16
- package/dist/modules/OpenData/types.d.ts +1 -1
- package/dist/modules/Order/index.d.ts +448 -23
- package/dist/modules/Order/index.js +5417 -561
- package/dist/modules/Order/types.d.ts +693 -88
- package/dist/modules/Order/types.js +68 -1
- package/dist/modules/Order/utils/bundleDiscountHydration.d.ts +5 -0
- package/dist/modules/Order/utils/bundleDiscountHydration.js +144 -0
- package/dist/modules/Order/utils/manualProductDiscount.d.ts +106 -0
- package/dist/modules/Order/utils/manualProductDiscount.js +210 -0
- package/dist/modules/Order/utils/orderCollectionIdentity.d.ts +53 -0
- package/dist/modules/Order/utils/orderCollectionIdentity.js +405 -0
- package/dist/modules/Order/utils.d.ts +167 -18
- package/dist/modules/Order/utils.js +960 -165
- package/dist/modules/Payment/index.d.ts +10 -9
- package/dist/modules/Payment/index.js +465 -703
- package/dist/modules/Payment/types.d.ts +47 -23
- package/dist/modules/Payment/utils.d.ts +1 -1
- package/dist/modules/Payment/utils.js +4 -3
- package/dist/modules/Payment/walletpass.js +44 -29
- package/dist/modules/Product/index.d.ts +1 -1
- package/dist/modules/Product/types.d.ts +29 -7
- package/dist/modules/ProductList/index.d.ts +17 -13
- package/dist/modules/ProductList/index.js +184 -74
- package/dist/modules/ProductList/types.d.ts +17 -0
- package/dist/modules/Quotation/index.d.ts +22 -2
- package/dist/modules/Quotation/index.js +108 -42
- package/dist/modules/Quotation/types.d.ts +19 -0
- package/dist/modules/ResourcePlanner/index.d.ts +24 -0
- package/dist/modules/ResourcePlanner/index.js +181 -0
- package/dist/modules/ResourcePlanner/planner.d.ts +14 -0
- package/dist/modules/ResourcePlanner/planner.js +1069 -0
- package/dist/modules/ResourcePlanner/types.d.ts +227 -0
- package/dist/modules/ResourcePlanner/types.js +1 -0
- package/dist/modules/Rules/index.d.ts +8 -10
- package/dist/modules/Rules/index.js +222 -109
- package/dist/modules/Rules/types.d.ts +15 -4
- package/dist/modules/SalesSummary/index.d.ts +23 -26
- package/dist/modules/SalesSummary/index.js +152 -46
- package/dist/modules/SalesSummary/types.d.ts +9 -1
- package/dist/modules/SalesSummary/utils.d.ts +3 -22
- package/dist/modules/SalesSummary/utils.js +803 -163
- package/dist/modules/ScanOrderLogger/types.d.ts +2 -2
- package/dist/modules/Schedule/getDateIsInSchedule.d.ts +1 -1
- package/dist/modules/Schedule/getDateIsInSchedule.js +14 -7
- package/dist/modules/Schedule/index.d.ts +18 -5
- package/dist/modules/Schedule/index.js +78 -24
- package/dist/modules/Schedule/type.d.ts +7 -7
- package/dist/modules/Schedule/types.d.ts +11 -9
- package/dist/modules/Summary/index.js +4 -3
- package/dist/modules/SurchargeList/index.d.ts +18 -0
- package/dist/modules/SurchargeList/index.js +185 -0
- package/dist/modules/SurchargeList/types.d.ts +15 -0
- package/dist/modules/SurchargeList/types.js +1 -0
- package/dist/modules/index.d.ts +3 -0
- package/dist/modules/index.js +4 -1
- package/dist/plugins/app.d.ts +2 -0
- package/dist/plugins/request.d.ts +3 -2
- package/dist/server/index.d.ts +462 -8
- package/dist/server/index.js +6112 -656
- package/dist/server/modules/floor-plan/index.d.ts +39 -0
- package/dist/server/modules/floor-plan/index.js +595 -0
- package/dist/server/modules/floor-plan/types.d.ts +43 -0
- package/dist/server/modules/floor-plan/types.js +13 -0
- package/dist/server/modules/index.d.ts +7 -2
- package/dist/server/modules/index.js +6 -0
- package/dist/server/modules/menu/types.d.ts +1 -1
- package/dist/server/modules/order/index.d.ts +207 -14
- package/dist/server/modules/order/index.js +2326 -379
- package/dist/server/modules/order/types.d.ts +91 -20
- package/dist/server/modules/order/types.js +14 -1
- package/dist/server/modules/order/utils/filterBookings.d.ts +7 -1
- package/dist/server/modules/order/utils/filterBookings.js +118 -11
- package/dist/server/modules/order/utils/filterOrders.js +242 -14
- package/dist/server/modules/payment/index.d.ts +28 -0
- package/dist/server/modules/payment/index.js +305 -0
- package/dist/server/modules/payment/types.d.ts +9 -0
- package/dist/server/modules/payment/types.js +1 -0
- package/dist/server/modules/products/index.d.ts +105 -14
- package/dist/server/modules/products/index.js +1393 -516
- package/dist/server/modules/products/types.d.ts +35 -2
- package/dist/server/modules/products/types.js +4 -0
- package/dist/server/modules/quotation/index.d.ts +39 -0
- package/dist/server/modules/quotation/index.js +278 -66
- package/dist/server/modules/resource/index.d.ts +2 -3
- package/dist/server/modules/resource/index.js +108 -86
- package/dist/server/modules/resource/types.d.ts +1 -1
- package/dist/server/modules/schedule/index.d.ts +6 -4
- package/dist/server/modules/schedule/index.js +116 -85
- package/dist/server/modules/schedule/utils.d.ts +1 -1
- package/dist/server/test.json +713 -0
- package/dist/server/types.d.ts +6 -4
- package/dist/server/utils/product.js +2 -2
- package/dist/server/utils/small-ticket.d.ts +71 -0
- package/dist/server/utils/small-ticket.js +963 -0
- package/dist/solution/BaseSales/index.d.ts +378 -0
- package/dist/solution/BaseSales/index.js +4006 -0
- package/dist/solution/BaseSales/types.d.ts +394 -0
- package/dist/solution/BaseSales/types.js +40 -0
- package/dist/solution/BaseSales/utils/cartPromotion.d.ts +287 -0
- package/dist/solution/BaseSales/utils/cartPromotion.js +1450 -0
- package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +144 -0
- package/dist/solution/BaseSales/utils/parseSalesResponse.js +157 -0
- package/dist/solution/BaseSales/utils/quotationPrice.d.ts +18 -0
- package/dist/solution/BaseSales/utils/quotationPrice.js +250 -0
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +63 -0
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +218 -0
- package/dist/solution/BaseSales/utils.d.ts +40 -0
- package/dist/solution/BaseSales/utils.js +263 -0
- package/dist/solution/BookingByStep/index.d.ts +2 -2
- package/dist/solution/BookingByStep/utils/resources.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +382 -25
- package/dist/solution/BookingTicket/index.js +2506 -443
- package/dist/solution/BookingTicket/types.d.ts +191 -6
- package/dist/solution/BookingTicket/types.js +83 -10
- package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +117 -0
- package/dist/solution/BookingTicket/utils/addProductDecision.js +346 -0
- package/dist/solution/BookingTicket/utils/addTimeAvailability.d.ts +35 -0
- package/dist/solution/BookingTicket/utils/addTimeAvailability.js +95 -0
- package/dist/solution/BookingTicket/utils/bookingStatus.d.ts +77 -0
- package/dist/solution/BookingTicket/utils/bookingStatus.js +136 -0
- package/dist/solution/BookingTicket/utils/cartView.d.ts +29 -0
- package/dist/solution/BookingTicket/utils/cartView.js +209 -0
- package/dist/solution/BookingTicket/utils/exampleData.d.ts +123 -0
- package/dist/solution/BookingTicket/utils/exampleData.js +183 -0
- package/dist/solution/BookingTicket/utils/orderCustomer.d.ts +4 -0
- package/dist/solution/BookingTicket/utils/orderCustomer.js +31 -0
- package/dist/solution/BookingTicket/utils/resolveBestAddTimePlan.d.ts +185 -0
- package/dist/solution/BookingTicket/utils/resolveBestAddTimePlan.js +438 -0
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +36 -0
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +400 -0
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.d.ts +4 -0
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +41 -1
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +32 -0
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +56 -0
- package/dist/solution/BookingTicket/utils/scan/handleScan.d.ts +3 -1
- package/dist/solution/BookingTicket/utils/scan/handleScan.js +11 -3
- package/dist/solution/BookingTicket/utils/scan/index.d.ts +5 -0
- package/dist/solution/BookingTicket/utils/scan/index.js +20 -8
- package/dist/solution/BookingTicket/utils/scan/scanProductValues.d.ts +21 -0
- package/dist/solution/BookingTicket/utils/scan/scanProductValues.js +150 -0
- package/dist/solution/BookingTicket/utils/sessionCatalogStale.d.ts +23 -0
- package/dist/solution/BookingTicket/utils/sessionCatalogStale.js +78 -0
- package/dist/solution/Checkout/index.d.ts +1 -0
- package/dist/solution/Checkout/index.js +25 -34
- package/dist/solution/Checkout/utils/index.js +4 -4
- package/dist/solution/RegisterAndLogin/config.js +2 -10
- package/dist/solution/Sales/index.d.ts +50 -5
- package/dist/solution/Sales/index.js +484 -92
- package/dist/solution/Sales/types.d.ts +29 -5
- package/dist/solution/ScanOrder/index.d.ts +27 -21
- package/dist/solution/ScanOrder/index.js +499 -434
- package/dist/solution/ScanOrder/types.d.ts +38 -16
- package/dist/solution/ScanOrder/types.js +4 -5
- package/dist/solution/ScanOrder/utils.d.ts +4 -1
- package/dist/solution/ScanOrder/utils.js +109 -34
- package/dist/solution/ShopDiscount/index.js +4 -4
- package/dist/solution/ShopDiscount/types.d.ts +1 -1
- package/dist/solution/ShopDiscount/utils.d.ts +2 -2
- package/dist/solution/UnifiedBookingSales/index.d.ts +183 -0
- package/dist/solution/UnifiedBookingSales/index.js +1871 -0
- package/dist/solution/UnifiedBookingSales/types.d.ts +143 -0
- package/dist/solution/UnifiedBookingSales/types.js +11 -0
- package/dist/solution/VenueBooking/index.d.ts +29 -20
- package/dist/solution/VenueBooking/index.js +301 -213
- package/dist/solution/VenueBooking/sales-section-4-annotated.json +0 -2
- package/dist/solution/VenueBooking/types.d.ts +1 -1
- package/dist/solution/VenueBooking/utils/slotMerge.d.ts +6 -0
- package/dist/solution/VenueBooking/utils/slotMerge.js +9 -3
- package/dist/solution/index.d.ts +2 -0
- package/dist/solution/index.js +3 -1
- package/dist/types/index.d.ts +12 -2
- package/dist/utils/task.d.ts +2 -2
- package/dist/utils/watch.d.ts +2 -2
- package/lib/effects/index.d.ts +2 -2
- package/lib/model/strategy/adapter/dataVariant/type.d.ts +3 -3
- package/lib/model/strategy/adapter/itemRule/type.d.ts +6 -6
- package/lib/model/strategy/adapter/promotion/evaluator.js +3 -2
- package/lib/model/strategy/adapter/promotion/type.d.ts +5 -5
- package/lib/model/strategy/adapter/walletPass/utils.js +6 -4
- package/lib/model/strategy/type.d.ts +1 -1
- package/lib/modules/BaseModule.d.ts +2 -2
- package/lib/modules/BaseModule.js +4 -4
- package/lib/modules/BookingContext/index.d.ts +48 -0
- package/lib/modules/BookingContext/index.js +390 -0
- package/lib/modules/BookingContext/types.d.ts +102 -0
- package/lib/modules/BookingContext/types.js +48 -0
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.d.ts +16 -0
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +286 -0
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +8 -0
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +148 -0
- package/lib/modules/BookingContext/utils/cacheItemToBookingInput.d.ts +76 -0
- package/lib/modules/BookingContext/utils/cacheItemToBookingInput.js +242 -0
- package/lib/modules/BookingContext/utils/flexible.d.ts +28 -0
- package/lib/modules/BookingContext/utils/flexible.js +63 -0
- package/lib/modules/BookingContext/utils/formatResourceList.d.ts +8 -0
- package/lib/modules/BookingContext/utils/formatResourceList.js +35 -0
- package/lib/modules/BookingContext/utils/index.d.ts +11 -0
- package/lib/modules/BookingContext/utils/index.js +126 -0
- package/lib/modules/BookingContext/utils/noResource.d.ts +13 -0
- package/lib/modules/BookingContext/utils/noResource.js +78 -0
- package/lib/modules/BookingContext/utils/orderLineDisplay.d.ts +1 -0
- package/lib/modules/BookingContext/utils/orderLineDisplay.js +29 -0
- package/lib/modules/BookingContext/utils/productExtend.d.ts +72 -0
- package/lib/modules/BookingContext/utils/productExtend.js +330 -0
- package/lib/modules/BookingContext/utils/resourceErrors.d.ts +41 -0
- package/lib/modules/BookingContext/utils/resourceErrors.js +80 -0
- package/lib/modules/BookingContext/utils/resourceSelection.d.ts +7 -0
- package/lib/modules/BookingContext/utils/resourceSelection.js +24 -0
- package/lib/modules/BookingContext/utils/resources.d.ts +80 -0
- package/lib/modules/BookingContext/utils/resources.js +419 -0
- package/lib/modules/BookingContext/utils/serviceTimes.d.ts +55 -0
- package/lib/modules/BookingContext/utils/serviceTimes.js +161 -0
- package/lib/modules/BookingContext/utils/timeSlices.d.ts +42 -0
- package/lib/modules/BookingContext/utils/timeSlices.js +114 -0
- package/lib/modules/Cart/utils/cartProduct.js +27 -5
- package/lib/modules/Cart/utils/changePrice.js +20 -14
- package/lib/modules/Customer/index.d.ts +20 -0
- package/lib/modules/Customer/index.js +63 -10
- package/lib/modules/Customer/types.d.ts +3 -1
- package/lib/modules/Discount/index.d.ts +2 -1
- package/lib/modules/Discount/index.js +18 -2
- package/lib/modules/Discount/types.d.ts +2 -1
- package/lib/modules/Holder/types.d.ts +1 -1
- package/lib/modules/OpenData/index.d.ts +8 -0
- package/lib/modules/OpenData/index.js +21 -3
- package/lib/modules/OpenData/types.d.ts +1 -1
- package/lib/modules/Order/index.d.ts +448 -23
- package/lib/modules/Order/index.js +3641 -308
- package/lib/modules/Order/types.d.ts +693 -88
- package/lib/modules/Order/types.js +55 -1
- package/lib/modules/Order/utils/bundleDiscountHydration.d.ts +5 -0
- package/lib/modules/Order/utils/bundleDiscountHydration.js +106 -0
- package/lib/modules/Order/utils/manualProductDiscount.d.ts +106 -0
- package/lib/modules/Order/utils/manualProductDiscount.js +207 -0
- package/lib/modules/Order/utils/orderCollectionIdentity.d.ts +53 -0
- package/lib/modules/Order/utils/orderCollectionIdentity.js +353 -0
- package/lib/modules/Order/utils.d.ts +167 -18
- package/lib/modules/Order/utils.js +974 -138
- package/lib/modules/Payment/index.d.ts +10 -9
- package/lib/modules/Payment/index.js +30 -124
- package/lib/modules/Payment/types.d.ts +47 -23
- package/lib/modules/Payment/utils.d.ts +1 -1
- package/lib/modules/Payment/utils.js +4 -3
- package/lib/modules/Payment/walletpass.js +16 -6
- package/lib/modules/Product/index.d.ts +1 -1
- package/lib/modules/Product/types.d.ts +29 -7
- package/lib/modules/ProductList/index.d.ts +17 -13
- package/lib/modules/ProductList/index.js +92 -51
- package/lib/modules/ProductList/types.d.ts +17 -0
- package/lib/modules/Quotation/index.d.ts +22 -2
- package/lib/modules/Quotation/index.js +76 -20
- package/lib/modules/Quotation/types.d.ts +19 -0
- package/lib/modules/ResourcePlanner/index.d.ts +24 -0
- package/lib/modules/ResourcePlanner/index.js +147 -0
- package/lib/modules/ResourcePlanner/planner.d.ts +14 -0
- package/lib/modules/ResourcePlanner/planner.js +895 -0
- package/lib/modules/ResourcePlanner/types.d.ts +227 -0
- package/lib/modules/ResourcePlanner/types.js +5 -0
- package/lib/modules/Rules/index.d.ts +8 -10
- package/lib/modules/Rules/index.js +153 -61
- package/lib/modules/Rules/types.d.ts +15 -4
- package/lib/modules/SalesSummary/index.d.ts +23 -26
- package/lib/modules/SalesSummary/index.js +84 -16
- package/lib/modules/SalesSummary/types.d.ts +9 -1
- package/lib/modules/SalesSummary/utils.d.ts +3 -22
- package/lib/modules/SalesSummary/utils.js +616 -111
- package/lib/modules/ScanOrderLogger/types.d.ts +2 -2
- package/lib/modules/Schedule/getDateIsInSchedule.d.ts +1 -1
- package/lib/modules/Schedule/getDateIsInSchedule.js +14 -7
- package/lib/modules/Schedule/index.d.ts +18 -5
- package/lib/modules/Schedule/index.js +49 -14
- package/lib/modules/Schedule/type.d.ts +7 -7
- package/lib/modules/Schedule/types.d.ts +11 -9
- package/lib/modules/Summary/index.js +2 -2
- package/lib/modules/SurchargeList/index.d.ts +18 -0
- package/lib/modules/SurchargeList/index.js +95 -0
- package/lib/modules/SurchargeList/types.d.ts +15 -0
- package/lib/modules/SurchargeList/types.js +5 -0
- package/lib/modules/index.d.ts +3 -0
- package/lib/modules/index.js +33 -0
- package/lib/plugins/app.d.ts +2 -0
- package/lib/plugins/request.d.ts +3 -2
- package/lib/server/index.d.ts +462 -8
- package/lib/server/index.js +3901 -205
- package/lib/server/modules/floor-plan/index.d.ts +39 -0
- package/lib/server/modules/floor-plan/index.js +306 -0
- package/lib/server/modules/floor-plan/types.d.ts +43 -0
- package/lib/server/modules/floor-plan/types.js +19 -0
- package/lib/server/modules/index.d.ts +7 -2
- package/lib/server/modules/index.js +21 -0
- package/lib/server/modules/menu/types.d.ts +1 -1
- package/lib/server/modules/order/index.d.ts +207 -14
- package/lib/server/modules/order/index.js +1324 -141
- package/lib/server/modules/order/types.d.ts +91 -20
- package/lib/server/modules/order/types.js +11 -1
- package/lib/server/modules/order/utils/filterBookings.d.ts +7 -1
- package/lib/server/modules/order/utils/filterBookings.js +119 -11
- package/lib/server/modules/order/utils/filterOrders.js +238 -13
- package/lib/server/modules/payment/index.d.ts +28 -0
- package/lib/server/modules/payment/index.js +168 -0
- package/lib/server/modules/payment/types.d.ts +9 -0
- package/lib/server/modules/payment/types.js +5 -0
- package/lib/server/modules/products/index.d.ts +105 -14
- package/lib/server/modules/products/index.js +588 -97
- package/lib/server/modules/products/types.d.ts +35 -2
- package/lib/server/modules/products/types.js +3 -0
- package/lib/server/modules/quotation/index.d.ts +39 -0
- package/lib/server/modules/quotation/index.js +145 -1
- package/lib/server/modules/resource/index.d.ts +2 -3
- package/lib/server/modules/resource/index.js +71 -45
- package/lib/server/modules/resource/types.d.ts +1 -1
- package/lib/server/modules/schedule/index.d.ts +6 -4
- package/lib/server/modules/schedule/index.js +55 -44
- package/lib/server/modules/schedule/utils.d.ts +1 -1
- package/lib/server/test.json +713 -0
- package/lib/server/types.d.ts +6 -4
- package/lib/server/utils/product.js +2 -2
- package/lib/server/utils/small-ticket.d.ts +71 -0
- package/lib/server/utils/small-ticket.js +926 -0
- package/lib/solution/BaseSales/index.d.ts +378 -0
- package/lib/solution/BaseSales/index.js +2367 -0
- package/lib/solution/BaseSales/types.d.ts +394 -0
- package/lib/solution/BaseSales/types.js +47 -0
- package/lib/solution/BaseSales/utils/cartPromotion.d.ts +287 -0
- package/lib/solution/BaseSales/utils/cartPromotion.js +1155 -0
- package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +144 -0
- package/lib/solution/BaseSales/utils/parseSalesResponse.js +140 -0
- package/lib/solution/BaseSales/utils/quotationPrice.d.ts +18 -0
- package/lib/solution/BaseSales/utils/quotationPrice.js +231 -0
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +63 -0
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +215 -0
- package/lib/solution/BaseSales/utils.d.ts +40 -0
- package/lib/solution/BaseSales/utils.js +259 -0
- package/lib/solution/BookingByStep/index.d.ts +2 -2
- package/lib/solution/BookingByStep/utils/resources.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +382 -25
- package/lib/solution/BookingTicket/index.js +1350 -76
- package/lib/solution/BookingTicket/types.d.ts +191 -6
- package/lib/solution/BookingTicket/types.js +71 -10
- package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +117 -0
- package/lib/solution/BookingTicket/utils/addProductDecision.js +353 -0
- package/lib/solution/BookingTicket/utils/addTimeAvailability.d.ts +35 -0
- package/lib/solution/BookingTicket/utils/addTimeAvailability.js +94 -0
- package/lib/solution/BookingTicket/utils/bookingStatus.d.ts +77 -0
- package/lib/solution/BookingTicket/utils/bookingStatus.js +136 -0
- package/lib/solution/BookingTicket/utils/cartView.d.ts +29 -0
- package/lib/solution/BookingTicket/utils/cartView.js +200 -0
- package/lib/solution/BookingTicket/utils/exampleData.d.ts +123 -0
- package/lib/solution/BookingTicket/utils/exampleData.js +185 -0
- package/lib/solution/BookingTicket/utils/orderCustomer.d.ts +4 -0
- package/lib/solution/BookingTicket/utils/orderCustomer.js +35 -0
- package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.d.ts +185 -0
- package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.js +387 -0
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +36 -0
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +220 -0
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.d.ts +4 -0
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +24 -3
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +32 -0
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +64 -0
- package/lib/solution/BookingTicket/utils/scan/handleScan.d.ts +3 -1
- package/lib/solution/BookingTicket/utils/scan/handleScan.js +4 -2
- package/lib/solution/BookingTicket/utils/scan/index.d.ts +5 -0
- package/lib/solution/BookingTicket/utils/scan/index.js +14 -2
- package/lib/solution/BookingTicket/utils/scan/scanProductValues.d.ts +21 -0
- package/lib/solution/BookingTicket/utils/scan/scanProductValues.js +115 -0
- package/lib/solution/BookingTicket/utils/sessionCatalogStale.d.ts +23 -0
- package/lib/solution/BookingTicket/utils/sessionCatalogStale.js +83 -0
- package/lib/solution/Checkout/index.d.ts +1 -0
- package/lib/solution/Checkout/index.js +18 -19
- package/lib/solution/Checkout/utils/index.js +4 -4
- package/lib/solution/RegisterAndLogin/config.js +2 -10
- package/lib/solution/Sales/index.d.ts +50 -5
- package/lib/solution/Sales/index.js +296 -39
- package/lib/solution/Sales/types.d.ts +29 -5
- package/lib/solution/ScanOrder/index.d.ts +27 -21
- package/lib/solution/ScanOrder/index.js +144 -103
- package/lib/solution/ScanOrder/types.d.ts +38 -16
- package/lib/solution/ScanOrder/types.js +4 -5
- package/lib/solution/ScanOrder/utils.d.ts +4 -1
- package/lib/solution/ScanOrder/utils.js +98 -26
- package/lib/solution/ShopDiscount/index.js +3 -3
- package/lib/solution/ShopDiscount/types.d.ts +1 -1
- package/lib/solution/ShopDiscount/utils.d.ts +2 -2
- package/lib/solution/UnifiedBookingSales/index.d.ts +183 -0
- package/lib/solution/UnifiedBookingSales/index.js +1050 -0
- package/lib/solution/UnifiedBookingSales/types.d.ts +143 -0
- package/lib/solution/UnifiedBookingSales/types.js +14 -0
- package/lib/solution/VenueBooking/index.d.ts +29 -20
- package/lib/solution/VenueBooking/index.js +169 -94
- package/lib/solution/VenueBooking/sales-section-4-annotated.json +0 -2
- package/lib/solution/VenueBooking/types.d.ts +1 -1
- package/lib/solution/VenueBooking/utils/slotMerge.d.ts +6 -0
- package/lib/solution/VenueBooking/utils/slotMerge.js +9 -3
- package/lib/solution/index.d.ts +2 -0
- package/lib/solution/index.js +22 -0
- package/lib/types/index.d.ts +12 -2
- package/lib/utils/task.d.ts +2 -2
- package/lib/utils/watch.d.ts +2 -2
- package/package.json +18 -19
- package/dist/model/strategy/adapter/promotion/index.d.ts +0 -4
- package/dist/solution/Checkout/appointmentDemo.json +0 -1
- package/lib/model/strategy/adapter/promotion/index.d.ts +0 -4
- package/lib/solution/Checkout/appointmentDemo.json +0 -1
|
@@ -7,6 +7,7 @@ exports.OrderModule = void 0;
|
|
|
7
7
|
var _lodashEs = require("lodash-es");
|
|
8
8
|
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
9
9
|
var _BaseModule = require("../../../modules/BaseModule");
|
|
10
|
+
var _orderCollectionIdentity = require("../../../modules/Order/utils/orderCollectionIdentity");
|
|
10
11
|
var _types = require("./types");
|
|
11
12
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
12
13
|
/**
|
|
@@ -14,6 +15,27 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
14
15
|
*/
|
|
15
16
|
const INDEXDB_STORE_NAME = 'orders';
|
|
16
17
|
const ORDER_LAST_FULL_FETCH_AT_STORAGE_KEY = 'server_order_last_full_fetch_at';
|
|
18
|
+
const ORDER_SYNC_THROTTLE_MS_STORAGE_KEY = 'order_sync_throttle_ms';
|
|
19
|
+
const DEFAULT_ORDER_SYNC_THROTTLE_MS = 2_000;
|
|
20
|
+
/** pubsub 回声写入去重窗口:业务 API 刚写入后,跳过同单 SQLite patch */
|
|
21
|
+
const ORDER_SQLITE_DEDUPE_MS = 15_000;
|
|
22
|
+
/** silentRefresh 最小间隔,避免 preload 后立即全量重拉 */
|
|
23
|
+
const ORDER_SILENT_REFRESH_MIN_INTERVAL_MS = 30_000;
|
|
24
|
+
/** 视为「业务侧刚写入」的来源,pubsub 回声可跳过 SQLite */
|
|
25
|
+
const ORDER_BUSINESS_WRITE_SOURCES = new Set(['upsertOrdersFromRemote', 'upsertPendingSyncOrders', 'overwriteExistingOrder', 'markOrderSyncedByExternalSaleNumber']);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 合并热路径全表去重自检开关:默认关闭。
|
|
29
|
+
* 开启后每次合并会额外对整张 store 做一次 O(n^2) 重复检测并告警,
|
|
30
|
+
* 仅用于排查增量身份索引是否遗漏了重复订单,不应在生产常开。
|
|
31
|
+
*/
|
|
32
|
+
const ORDER_MERGE_SELF_CHECK = false;
|
|
33
|
+
const ORDER_AUTHORITATIVE_COLLECTION_FIELDS = ['products', 'bookings', 'payments'];
|
|
34
|
+
const ORDER_COLLECTION_KIND_BY_FIELD = {
|
|
35
|
+
products: 'product',
|
|
36
|
+
bookings: 'booking',
|
|
37
|
+
payments: 'payment'
|
|
38
|
+
};
|
|
17
39
|
|
|
18
40
|
/**
|
|
19
41
|
* Order 模块 - 基础框架
|
|
@@ -26,14 +48,32 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
26
48
|
orderDataSource;
|
|
27
49
|
pendingSyncMessages = [];
|
|
28
50
|
syncTimer;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
resourceIdIndex = new Map();
|
|
51
|
+
isProcessingSyncBatch = false;
|
|
52
|
+
isIdlePhase = true;
|
|
32
53
|
logger; // LoggerManager 实例
|
|
33
54
|
storage;
|
|
55
|
+
orderSQLiteSaveQueue = Promise.resolve(); /** 按 storageKey 记录最近一次 SQLite 写入来源与时间,用于去重 */
|
|
56
|
+
recentSqliteWriteByStorageKey = new Map();
|
|
57
|
+
/** 最近一次 SSE 全量拉取完成时间 */
|
|
58
|
+
lastServerFullFetchAtMs = 0;
|
|
34
59
|
constructor(name, version) {
|
|
35
60
|
super(name, version);
|
|
36
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 广播订单变更事件,兼容全量列表订阅并额外携带本次变更订单。
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* await this.emitOrdersChanged([freshOrder], 'pubsub.bodyUpsert')
|
|
68
|
+
*/
|
|
69
|
+
async emitOrdersChanged(changedOrders = [], source) {
|
|
70
|
+
const payload = {
|
|
71
|
+
list: this.store.list,
|
|
72
|
+
changedOrders: changedOrders.map(order => this.normalizeOrderForMemoryList(order)),
|
|
73
|
+
source
|
|
74
|
+
};
|
|
75
|
+
await this.core.effects.emit(_types.OrderHooks.onOrdersChanged, payload);
|
|
76
|
+
}
|
|
37
77
|
async initialize(core, options) {
|
|
38
78
|
this.core = core;
|
|
39
79
|
this.store = options?.store;
|
|
@@ -44,42 +84,12 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
44
84
|
this.dbManager = app.sqlite;
|
|
45
85
|
this.logger = app.logger;
|
|
46
86
|
this.storage = app.storage;
|
|
47
|
-
|
|
48
|
-
if (!this.store.createdAtQuery?.sales_time_between && coreData?.core) {
|
|
49
|
-
const today = (0, _dayjs.default)();
|
|
50
|
-
if (coreData?.core.operating_day_boundary) {
|
|
51
|
-
const operatingDayBoundary = coreData.core.operating_day_boundary;
|
|
52
|
-
if (operatingDayBoundary.type === 'start_time') {
|
|
53
|
-
const boundaryTime = String(operatingDayBoundary.time || '').trim();
|
|
54
|
-
const boundaryStart = (0, _dayjs.default)(`${today.format('YYYY-MM-DD')} ${boundaryTime}`);
|
|
55
|
-
const boundaryEnd = boundaryStart.add(1, 'day');
|
|
56
|
-
if (boundaryStart.isValid()) {
|
|
57
|
-
this.store.createdAtQuery = {
|
|
58
|
-
sales_time_between: [boundaryStart.format('YYYY-MM-DD HH:mm:ss'), boundaryEnd.format('YYYY-MM-DD HH:mm:ss')]
|
|
59
|
-
};
|
|
60
|
-
} else {
|
|
61
|
-
// 边界时间异常时回退到自然日,避免 createdAtQuery 为空导致后续查询异常
|
|
62
|
-
this.store.createdAtQuery = {
|
|
63
|
-
sales_time_between: [today.startOf('day').format('YYYY-MM-DD HH:mm:ss'), today.endOf('day').format('YYYY-MM-DD HH:mm:ss')]
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
this.store.createdAtQuery = {
|
|
68
|
-
sales_time_between: [today.startOf('day').format('YYYY-MM-DD HH:mm:ss'), today.endOf('day').format('YYYY-MM-DD HH:mm:ss')]
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
// TODO: 这里后续可能需要使用店铺营业时间查询
|
|
73
|
-
this.store.createdAtQuery = {
|
|
74
|
-
sales_time_between: [today.startOf('day').format('YYYY-MM-DD HH:mm:ss'), today.endOf('day').format('YYYY-MM-DD HH:mm:ss')]
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
}
|
|
87
|
+
this.updateCreateStore();
|
|
78
88
|
}
|
|
79
89
|
if (Array.isArray(options?.initialState?.list)) {
|
|
80
|
-
this.store.list = options.initialState.list;
|
|
90
|
+
this.store.list = options.initialState.list.map(order => this.normalizeOrderForMemoryList(order));
|
|
81
91
|
this.syncOrdersMap();
|
|
82
|
-
this.
|
|
92
|
+
this.emitOrdersChanged([], 'initialState');
|
|
83
93
|
} else {
|
|
84
94
|
this.store.list = [];
|
|
85
95
|
this.store.map = new Map();
|
|
@@ -94,10 +104,10 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
94
104
|
}
|
|
95
105
|
|
|
96
106
|
/**
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
107
|
+
* 记录信息日志
|
|
108
|
+
* @param title 日志标题
|
|
109
|
+
* @param metadata 日志元数据
|
|
110
|
+
*/
|
|
101
111
|
logInfo(title, metadata) {
|
|
102
112
|
try {
|
|
103
113
|
if (this.logger) {
|
|
@@ -130,8 +140,312 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
130
140
|
// 日志记录失败不影响主流程
|
|
131
141
|
}
|
|
132
142
|
}
|
|
143
|
+
logWarning(title, metadata) {
|
|
144
|
+
try {
|
|
145
|
+
if (this.logger) {
|
|
146
|
+
this.logger.addLog({
|
|
147
|
+
type: 'warning',
|
|
148
|
+
title: `[OrderServerModule] ${title}`,
|
|
149
|
+
metadata: metadata || {}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
// 日志记录失败不影响主流程
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 在订单进入内存或 SQLite 前统一压缩子项强身份重复并补齐协议 UID。
|
|
159
|
+
* 仅记录计数与订单标识,不输出商品、预约、支付或客户明细。
|
|
160
|
+
*/
|
|
161
|
+
normalizeOrderCollectionsForIngress(order, source) {
|
|
162
|
+
const result = (0, _orderCollectionIdentity.normalizeOrderCollections)(order);
|
|
163
|
+
const kinds = ['product', 'booking', 'payment'];
|
|
164
|
+
const hasChanges = kinds.some(kind => {
|
|
165
|
+
const stats = result.stats[kind];
|
|
166
|
+
return stats.backfilledUidCount > 0 || stats.collapsedDuplicateCount > 0 || stats.uidConflictCount > 0 || stats.anonymousRowCount > 0;
|
|
167
|
+
});
|
|
168
|
+
if (hasChanges) {
|
|
169
|
+
const summarize = stats => ({
|
|
170
|
+
backfilled_uid_count: stats.backfilledUidCount,
|
|
171
|
+
collapsed_duplicate_count: stats.collapsedDuplicateCount,
|
|
172
|
+
uid_conflict_count: stats.uidConflictCount,
|
|
173
|
+
anonymous_row_count: stats.anonymousRowCount
|
|
174
|
+
});
|
|
175
|
+
const context = {
|
|
176
|
+
source,
|
|
177
|
+
order_id: order?.order_id ?? null,
|
|
178
|
+
external_sale_number: order?.external_sale_number ?? null,
|
|
179
|
+
products: summarize(result.stats.product),
|
|
180
|
+
bookings: summarize(result.stats.booking),
|
|
181
|
+
payments: summarize(result.stats.payment)
|
|
182
|
+
};
|
|
183
|
+
const hasUnsafeIdentity = kinds.some(kind => {
|
|
184
|
+
const stats = result.stats[kind];
|
|
185
|
+
return stats.uidConflictCount > 0 || stats.anonymousRowCount > 0;
|
|
186
|
+
});
|
|
187
|
+
if (hasUnsafeIdentity) {
|
|
188
|
+
this.logWarning('订单子项身份存在冲突或匿名行', context);
|
|
189
|
+
} else {
|
|
190
|
+
this.logInfo('订单子项身份已规范化', context);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return result.order;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* incoming 决定权威成员集合;同一持久化行仍从 existing 补齐 incoming 省略的本地展示字段。
|
|
198
|
+
*/
|
|
199
|
+
mergeAuthoritativeOrderCollection(field, existing, incoming, uidRemaps = []) {
|
|
200
|
+
const kind = ORDER_COLLECTION_KIND_BY_FIELD[field];
|
|
201
|
+
const existingItems = Array.isArray(existing) ? existing : [];
|
|
202
|
+
return incoming.map(incomingItem => {
|
|
203
|
+
const existingItem = existingItems.find(item => (0, _orderCollectionIdentity.isSameOrderCollectionItem)(kind, item, incomingItem));
|
|
204
|
+
if (!existingItem) return (0, _lodashEs.cloneDeep)(incomingItem);
|
|
205
|
+
const mergedItem = (0, _orderCollectionIdentity.mergeOrderCollectionItems)(kind, existingItem, incomingItem);
|
|
206
|
+
const mergedUid = (0, _orderCollectionIdentity.getOrderCollectionUid)(kind, mergedItem);
|
|
207
|
+
if (mergedUid) {
|
|
208
|
+
for (const from of new Set([(0, _orderCollectionIdentity.getOrderCollectionReferenceUid)(kind, existingItem), (0, _orderCollectionIdentity.getOrderCollectionReferenceUid)(kind, incomingItem)])) {
|
|
209
|
+
if (!from || from === mergedUid) continue;
|
|
210
|
+
uidRemaps.push({
|
|
211
|
+
kind,
|
|
212
|
+
from,
|
|
213
|
+
to: mergedUid,
|
|
214
|
+
persistentId: (0, _orderCollectionIdentity.getOrderCollectionPersistentId)(kind, incomingItem) || (0, _orderCollectionIdentity.getOrderCollectionPersistentId)(kind, existingItem)
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return mergedItem;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
reconcileOverwriteOrderCollections(existing, incoming) {
|
|
222
|
+
const reconciled = (0, _lodashEs.cloneDeep)(incoming);
|
|
223
|
+
const existingRecord = existing;
|
|
224
|
+
const incomingRecord = incoming;
|
|
225
|
+
const uidRemaps = [];
|
|
226
|
+
for (const field of ORDER_AUTHORITATIVE_COLLECTION_FIELDS) {
|
|
227
|
+
if (!Array.isArray(incomingRecord[field])) continue;
|
|
228
|
+
reconciled[field] = this.mergeAuthoritativeOrderCollection(field, existingRecord[field], incomingRecord[field], uidRemaps);
|
|
229
|
+
}
|
|
230
|
+
return this.normalizeOrderCollectionsForIngress((0, _orderCollectionIdentity.applyOrderCollectionUidRemaps)(reconciled, uidRemaps), 'overwriteExistingOrder.reconciled');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 记录订单最近一次 SQLite 写入,供 pubsub 回声去重使用。
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* this.recordRecentSqliteWrite('upsertOrdersFromRemote', orders)
|
|
238
|
+
*/
|
|
239
|
+
recordRecentSqliteWrite(source, orders) {
|
|
240
|
+
const now = Date.now();
|
|
241
|
+
for (const order of orders) {
|
|
242
|
+
const storageKey = this.getOrderStorageKey(order);
|
|
243
|
+
if (!storageKey) continue;
|
|
244
|
+
this.recentSqliteWriteByStorageKey.set(storageKey, {
|
|
245
|
+
source,
|
|
246
|
+
ts: now
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 过滤 pubsub 触发的冗余 SQLite patch(业务 API 刚写入过的 update 回声)。
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* const { toWrite, skipped } = this.filterRedundantPatchOrders('pubsub.bodyUpsert', orders, mergeActions)
|
|
256
|
+
*/
|
|
257
|
+
filterRedundantPatchOrders(source, orders, mergeActions) {
|
|
258
|
+
const eligibleSkipSources = new Set(['pubsub.bodyUpsert', 'pubsub.httpRefresh']);
|
|
259
|
+
if (!eligibleSkipSources.has(source)) {
|
|
260
|
+
return {
|
|
261
|
+
toWrite: orders,
|
|
262
|
+
skipped: []
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const now = Date.now();
|
|
266
|
+
const toWrite = [];
|
|
267
|
+
const skipped = [];
|
|
268
|
+
for (const order of orders) {
|
|
269
|
+
const storageKey = this.getOrderStorageKey(order);
|
|
270
|
+
if (!storageKey) {
|
|
271
|
+
toWrite.push(order);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const orderKey = order.order_id !== undefined && order.order_id !== null ? this.getIdKey(order.order_id) : '';
|
|
275
|
+
const mergeAction = orderKey ? mergeActions[orderKey] : undefined;
|
|
276
|
+
|
|
277
|
+
// 新增单必须落库,不参与去重
|
|
278
|
+
if (mergeAction === 'insert') {
|
|
279
|
+
toWrite.push(order);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const recent = this.recentSqliteWriteByStorageKey.get(storageKey);
|
|
283
|
+
if (recent && ORDER_BUSINESS_WRITE_SOURCES.has(recent.source) && now - recent.ts < ORDER_SQLITE_DEDUPE_MS) {
|
|
284
|
+
skipped.push({
|
|
285
|
+
orderId: order.order_id ?? null,
|
|
286
|
+
storageKey,
|
|
287
|
+
reason: 'skip_pubsub_echo_after_business_write',
|
|
288
|
+
lastSource: recent.source,
|
|
289
|
+
elapsedMs: now - recent.ts
|
|
290
|
+
});
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
toWrite.push(order);
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
toWrite,
|
|
297
|
+
skipped
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 过滤 pubsub HTTP 增量拉取 ids:本地刚由业务 API 写入的单可跳过 HTTP。
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* const ids = this.filterRedundantHttpRefreshIds([99331, 99332])
|
|
306
|
+
*/
|
|
307
|
+
filterRedundantHttpRefreshIds(ids) {
|
|
308
|
+
const now = Date.now();
|
|
309
|
+
const toFetch = [];
|
|
310
|
+
const skipped = [];
|
|
311
|
+
for (const id of ids) {
|
|
312
|
+
const existing = this.getOrderByOrderId(id);
|
|
313
|
+
if (!existing) {
|
|
314
|
+
toFetch.push(id);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const storageKey = this.getOrderStorageKey(existing);
|
|
318
|
+
const recent = storageKey ? this.recentSqliteWriteByStorageKey.get(storageKey) : undefined;
|
|
319
|
+
if (recent && recent.source === 'upsertOrdersFromRemote' && now - recent.ts < ORDER_SQLITE_DEDUPE_MS) {
|
|
320
|
+
skipped.push({
|
|
321
|
+
orderId: id,
|
|
322
|
+
lastSource: recent.source,
|
|
323
|
+
elapsedMs: now - recent.ts
|
|
324
|
+
});
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
toFetch.push(id);
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
toFetch,
|
|
331
|
+
skipped
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* 解析 pubsub 消息会走哪条同步分支,便于对照为何触发 HTTP 重拉或 body 直写。
|
|
337
|
+
*
|
|
338
|
+
* @example
|
|
339
|
+
* this.resolveSyncMessageRoute(msg)
|
|
340
|
+
* // => { route: 'httpRefresh.batchIds', ... }
|
|
341
|
+
*/
|
|
342
|
+
resolveSyncMessageRoute(msg) {
|
|
343
|
+
const hasBatchIds = Array.isArray(msg.ids) && msg.ids.length > 0;
|
|
344
|
+
const singleIdRaw = msg.id ?? msg.order_id;
|
|
345
|
+
const hasSingleId = !hasBatchIds && singleIdRaw !== undefined && singleIdRaw !== null;
|
|
346
|
+
const normalizedBody = this.normalizeOrderSyncPayload(msg.body || msg.data);
|
|
347
|
+
if (hasBatchIds) {
|
|
348
|
+
return {
|
|
349
|
+
route: 'httpRefresh.batchIds',
|
|
350
|
+
hasBatchIds: true,
|
|
351
|
+
hasSingleId: false,
|
|
352
|
+
hasNormalizedBody: !!normalizedBody,
|
|
353
|
+
singleId: null,
|
|
354
|
+
batchIds: msg.ids || [],
|
|
355
|
+
bodyOrderId: normalizedBody?.order_id ?? null
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
if (hasSingleId && normalizedBody) {
|
|
359
|
+
return {
|
|
360
|
+
route: 'bodyUpsert',
|
|
361
|
+
hasBatchIds: false,
|
|
362
|
+
hasSingleId: true,
|
|
363
|
+
hasNormalizedBody: true,
|
|
364
|
+
singleId: singleIdRaw,
|
|
365
|
+
batchIds: [],
|
|
366
|
+
bodyOrderId: normalizedBody.order_id ?? null
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
if (hasSingleId) {
|
|
370
|
+
return {
|
|
371
|
+
route: 'httpRefresh.singleId',
|
|
372
|
+
hasBatchIds: false,
|
|
373
|
+
hasSingleId: true,
|
|
374
|
+
hasNormalizedBody: !!normalizedBody,
|
|
375
|
+
singleId: singleIdRaw,
|
|
376
|
+
batchIds: [],
|
|
377
|
+
bodyOrderId: normalizedBody?.order_id ?? null
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
if (msg.relation_order_ids?.length) {
|
|
381
|
+
return {
|
|
382
|
+
route: 'httpRefresh.relationIds',
|
|
383
|
+
hasBatchIds: false,
|
|
384
|
+
hasSingleId: false,
|
|
385
|
+
hasNormalizedBody: !!normalizedBody,
|
|
386
|
+
singleId: null,
|
|
387
|
+
batchIds: msg.relation_order_ids,
|
|
388
|
+
bodyOrderId: normalizedBody?.order_id ?? null
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
route: 'noop',
|
|
393
|
+
hasBatchIds: false,
|
|
394
|
+
hasSingleId: false,
|
|
395
|
+
hasNormalizedBody: !!normalizedBody,
|
|
396
|
+
singleId: null,
|
|
397
|
+
batchIds: [],
|
|
398
|
+
bodyOrderId: normalizedBody?.order_id ?? null
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
updateCreateStore() {
|
|
402
|
+
const appPlugin = this.core.getPlugin('app');
|
|
403
|
+
const app = appPlugin?.getApp();
|
|
404
|
+
if (app) {
|
|
405
|
+
const coreData = app.data.store.getStore().getDataByModel('core');
|
|
406
|
+
if (coreData?.core) {
|
|
407
|
+
const today = (0, _dayjs.default)();
|
|
408
|
+
if (coreData?.core.operating_day_boundary) {
|
|
409
|
+
const operatingDayBoundary = coreData.core.operating_day_boundary;
|
|
410
|
+
if (operatingDayBoundary.type === 'start_time') {
|
|
411
|
+
const boundaryTime = String(operatingDayBoundary.time || '').trim();
|
|
412
|
+
const boundaryStart = (0, _dayjs.default)(`${today.format('YYYY-MM-DD')} ${boundaryTime}`);
|
|
413
|
+
if (boundaryStart.isValid()) {
|
|
414
|
+
const isAfterBoundary = today.isAfter(boundaryStart);
|
|
415
|
+
const queryStart = isAfterBoundary ? today.startOf('day') : today.subtract(1, 'day').startOf('day');
|
|
416
|
+
const queryEnd = isAfterBoundary ? today.add(1, 'day').endOf('day') : today.endOf('day');
|
|
417
|
+
this.store.createdAtQuery = {
|
|
418
|
+
sales_time_between: [queryStart.format('YYYY-MM-DD HH:mm:ss'), queryEnd.format('YYYY-MM-DD HH:mm:ss')]
|
|
419
|
+
};
|
|
420
|
+
} else {
|
|
421
|
+
// 边界时间异常时回退到自然日,避免 createdAtQuery 为空导致后续查询异常
|
|
422
|
+
this.store.createdAtQuery = {
|
|
423
|
+
sales_time_between: [today.startOf('day').format('YYYY-MM-DD HH:mm:ss'), today.endOf('day').format('YYYY-MM-DD HH:mm:ss')]
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
this.store.createdAtQuery = {
|
|
428
|
+
sales_time_between: [today.startOf('day').format('YYYY-MM-DD HH:mm:ss'), today.endOf('day').format('YYYY-MM-DD HH:mm:ss')]
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
// TODO: 这里后续可能需要使用店铺营业时间查询
|
|
433
|
+
this.store.createdAtQuery = {
|
|
434
|
+
sales_time_between: [today.startOf('day').format('YYYY-MM-DD HH:mm:ss'), today.endOf('day').format('YYYY-MM-DD HH:mm:ss')]
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
getOrderSyncThrottleMs() {
|
|
441
|
+
const raw = localStorage.getItem(ORDER_SYNC_THROTTLE_MS_STORAGE_KEY);
|
|
442
|
+
if (raw !== null) {
|
|
443
|
+
const parsed = Number(raw);
|
|
444
|
+
if (Number.isFinite(parsed) && parsed >= 0) return parsed;
|
|
445
|
+
}
|
|
446
|
+
return DEFAULT_ORDER_SYNC_THROTTLE_MS;
|
|
447
|
+
}
|
|
133
448
|
async preload() {
|
|
134
|
-
// const cachedOrders = await this.loadOrdersFromSQLite()
|
|
135
449
|
// const hasPulledInCurrentWindow = this.hasPulledOrdersToday()
|
|
136
450
|
// // 仅当缓存属于当前统计窗口时才直接复用,避免跨天/跨营业日窗口继续使用旧数据
|
|
137
451
|
// if (cachedOrders.length > 0 && hasPulledInCurrentWindow) {
|
|
@@ -148,11 +462,17 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
148
462
|
// return
|
|
149
463
|
// }
|
|
150
464
|
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
465
|
+
const getData = async () => {
|
|
466
|
+
const orders = await this.loadOrdersByServer();
|
|
467
|
+
if (orders.length === 0) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
this.store.list = (0, _lodashEs.cloneDeep)(orders);
|
|
471
|
+
this.syncOrdersMap();
|
|
472
|
+
this.emitOrdersChanged([], 'preload');
|
|
473
|
+
this.core.effects.emit(_types.OrderHooks.onOrdersSyncCompleted, null);
|
|
474
|
+
};
|
|
475
|
+
getData();
|
|
156
476
|
}
|
|
157
477
|
getOrders() {
|
|
158
478
|
return this.store.list;
|
|
@@ -161,32 +481,315 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
161
481
|
return this.store.map.get(this.getIdKey(id));
|
|
162
482
|
}
|
|
163
483
|
|
|
164
|
-
/** 按 order_id
|
|
484
|
+
/** 按 order_id 查询内存订单。 */
|
|
165
485
|
getOrderByOrderId(orderId) {
|
|
166
486
|
return this.store.map.get(this.getIdKey(orderId));
|
|
167
487
|
}
|
|
488
|
+
getLocalOrderFromMemoryByLookup(lookup) {
|
|
489
|
+
const key = this.getIdKey(lookup);
|
|
490
|
+
const memoryOrderById = this.store.map.get(key);
|
|
491
|
+
if (memoryOrderById) return memoryOrderById;
|
|
492
|
+
const memoryOrder = this.store.list.find(order => this.isOrderLookupMatch(order, key));
|
|
493
|
+
return memoryOrder || null;
|
|
494
|
+
}
|
|
495
|
+
async getLocalOrderByLookup(lookup) {
|
|
496
|
+
const key = this.getIdKey(lookup);
|
|
497
|
+
const memoryOrderById = this.store.map.get(key);
|
|
498
|
+
if (memoryOrderById) return memoryOrderById;
|
|
499
|
+
const memoryOrder = this.store.list.find(order => this.isOrderLookupMatch(order, key));
|
|
500
|
+
if (memoryOrder) return memoryOrder;
|
|
501
|
+
const orders = await this.loadOrdersFromSQLite();
|
|
502
|
+
const localOrder = orders.find(order => this.isOrderLookupMatch(order, key));
|
|
503
|
+
if (!localOrder) return null;
|
|
504
|
+
this.store.list = orders.map(order => this.normalizeOrderForMemoryList(order));
|
|
505
|
+
this.syncOrdersMap();
|
|
506
|
+
return this.normalizeOrderForMemoryList(localOrder);
|
|
507
|
+
}
|
|
168
508
|
async loadOrdersByServer() {
|
|
169
509
|
let orderList = [];
|
|
510
|
+
this.logInfo('loadOrdersByServer-开始', {
|
|
511
|
+
hasOrderDataSource: !!this.orderDataSource,
|
|
512
|
+
query: this.store?.createdAtQuery || null
|
|
513
|
+
});
|
|
170
514
|
if (this.orderDataSource) {
|
|
171
515
|
try {
|
|
172
516
|
const data = await this.orderDataSource.run({
|
|
173
|
-
|
|
517
|
+
sse: {
|
|
174
518
|
query: this.store.createdAtQuery
|
|
175
519
|
}
|
|
176
520
|
});
|
|
177
521
|
orderList = data || [];
|
|
522
|
+
this.logInfo('loadOrdersByServer-SSE拉取成功', {
|
|
523
|
+
count: orderList.length
|
|
524
|
+
});
|
|
178
525
|
this.markOrderPulledAtNow();
|
|
526
|
+
this.lastServerFullFetchAtMs = Date.now();
|
|
179
527
|
} catch {
|
|
180
|
-
|
|
528
|
+
this.logInfo('loadOrdersByServer-SSE拉取失败,保持当前内存订单');
|
|
529
|
+
return this.store.list;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
const mergedOrders = await this.replaceOrdersSnapshotInSQLite(orderList, 'loadOrdersByServer');
|
|
533
|
+
const memoryOrders = mergedOrders.map(order => this.normalizeOrderForMemoryList(order));
|
|
534
|
+
this.logInfo('loadOrdersByServer-结束', {
|
|
535
|
+
count: memoryOrders.length
|
|
536
|
+
});
|
|
537
|
+
await this.core.effects.emit(_types.OrderHooks.onOrdersLoaded, memoryOrders);
|
|
538
|
+
return memoryOrders;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* 静默全量刷新:后台重新拉取全量 SSE 订单数据并更新本地
|
|
543
|
+
* 拿到完整数据后一次性替换 store,触发订单变更与同步完成事件
|
|
544
|
+
* @returns 刷新后的订单列表
|
|
545
|
+
*/
|
|
546
|
+
async silentRefresh() {
|
|
547
|
+
const startTime = Date.now();
|
|
548
|
+
const elapsedSinceFullFetch = Date.now() - this.lastServerFullFetchAtMs;
|
|
549
|
+
if (this.lastServerFullFetchAtMs > 0 && elapsedSinceFullFetch < ORDER_SILENT_REFRESH_MIN_INTERVAL_MS) {
|
|
550
|
+
return this.store.list;
|
|
551
|
+
}
|
|
552
|
+
this.logInfo('silentRefresh 开始');
|
|
553
|
+
try {
|
|
554
|
+
// 刷新前,需要更新当前营业日边界(store.createdAtQuery)
|
|
555
|
+
this.updateCreateStore();
|
|
556
|
+
const orders = await this.loadOrdersByServer();
|
|
557
|
+
if (orders.length > 0) {
|
|
558
|
+
this.store.list = (0, _lodashEs.cloneDeep)(orders);
|
|
559
|
+
this.syncOrdersMap();
|
|
560
|
+
this.emitOrdersChanged([], 'silentRefresh');
|
|
561
|
+
await this.core.effects.emit(_types.OrderHooks.onOrdersSyncCompleted, null);
|
|
562
|
+
this.logInfo('silentRefresh 完成', {
|
|
563
|
+
orderCount: orders.length,
|
|
564
|
+
duration: `${Date.now() - startTime}ms`
|
|
565
|
+
});
|
|
566
|
+
} else {
|
|
567
|
+
this.logInfo('silentRefresh: 服务器未返回数据');
|
|
568
|
+
}
|
|
569
|
+
return this.store.list;
|
|
570
|
+
} catch (error) {
|
|
571
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
572
|
+
this.logError('silentRefresh 失败', {
|
|
573
|
+
duration: `${Date.now() - startTime}ms`,
|
|
574
|
+
error: errorMessage
|
|
575
|
+
});
|
|
576
|
+
return this.store.list;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
normalizeOrderForMemoryList(order, source = 'normalizeOrderForMemoryList') {
|
|
580
|
+
const normalizedOrder = this.normalizeOrderCollectionsForIngress(order, source);
|
|
581
|
+
const externalSaleNumber = normalizedOrder?.external_sale_number;
|
|
582
|
+
if (normalizedOrder?.order_id !== undefined && normalizedOrder?.order_id !== null && normalizedOrder?.order_id !== '') {
|
|
583
|
+
return normalizedOrder;
|
|
584
|
+
}
|
|
585
|
+
if (externalSaleNumber === undefined || externalSaleNumber === null || externalSaleNumber === '') {
|
|
586
|
+
return normalizedOrder;
|
|
587
|
+
}
|
|
588
|
+
return {
|
|
589
|
+
...normalizedOrder,
|
|
590
|
+
order_id: externalSaleNumber
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* 仅覆盖本地已存在的订单;不存在则直接跳过,不落库、不 emit。
|
|
596
|
+
* 适用于"按 order_id 主动刷新本地订单详情"的代理场景。
|
|
597
|
+
*/
|
|
598
|
+
async overwriteExistingOrder(fresh) {
|
|
599
|
+
const orderId = fresh?.order_id;
|
|
600
|
+
if (orderId === undefined || orderId === null) {
|
|
601
|
+
this.logError('overwriteExistingOrder-入参缺少 order_id');
|
|
602
|
+
return {
|
|
603
|
+
overwritten: false
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
const key = this.getIdKey(orderId);
|
|
607
|
+
if (!this.store.map.has(key)) {
|
|
608
|
+
this.logInfo('overwriteExistingOrder-本地不存在该订单,跳过', {
|
|
609
|
+
orderId
|
|
610
|
+
});
|
|
611
|
+
return {
|
|
612
|
+
overwritten: false
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
this.logInfo('overwriteExistingOrder-开始覆盖', {
|
|
616
|
+
orderId,
|
|
617
|
+
storeOrderCountBefore: this.store.list.length
|
|
618
|
+
});
|
|
619
|
+
const normalizedFresh = this.normalizeOrderForMemoryList(fresh, 'overwriteExistingOrder.incoming');
|
|
620
|
+
const existingOrder = this.store.map.get(key);
|
|
621
|
+
const reconciledFresh = this.reconcileOverwriteOrderCollections(existingOrder, normalizedFresh);
|
|
622
|
+
this.store.list = this.store.list.map(order => {
|
|
623
|
+
const id = order?.order_id;
|
|
624
|
+
if (id === undefined || id === null) {
|
|
625
|
+
return this.normalizeOrderForMemoryList(order, 'overwriteExistingOrder.existingMemory');
|
|
626
|
+
}
|
|
627
|
+
return this.getIdKey(id) === key ? reconciledFresh : this.normalizeOrderForMemoryList(order, 'overwriteExistingOrder.existingMemory');
|
|
628
|
+
});
|
|
629
|
+
this.syncOrdersMap();
|
|
630
|
+
await this.updateOrderInSQLite(reconciledFresh, 'overwriteExistingOrder');
|
|
631
|
+
this.logInfo('overwriteExistingOrder-结束', {
|
|
632
|
+
orderId,
|
|
633
|
+
storeOrderCountAfter: this.store.list.length
|
|
634
|
+
});
|
|
635
|
+
await this.emitOrdersChanged([reconciledFresh], 'overwriteExistingOrder');
|
|
636
|
+
return {
|
|
637
|
+
overwritten: true
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* 将远端拉取的订单合并进本地 store(已存在则更新,不存在则追加),落库并 emit onOrdersChanged。
|
|
643
|
+
* 供 /update/localOrder 等在「本地尚无该单」时写入新建单。
|
|
644
|
+
*/
|
|
645
|
+
async upsertOrdersFromRemote(freshOrders) {
|
|
646
|
+
if (!freshOrders?.length) {
|
|
647
|
+
this.logInfo('upsertOrdersFromRemote-订单列表为空', {});
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
this.logInfo('upsertOrdersFromRemote-开始', {
|
|
651
|
+
count: freshOrders.length
|
|
652
|
+
});
|
|
653
|
+
await this.mergeOrdersToStore(freshOrders, 'upsertOrdersFromRemote');
|
|
654
|
+
}
|
|
655
|
+
async markOrderSyncedByExternalSaleNumber(params) {
|
|
656
|
+
const externalSaleNumber = params.externalSaleNumber;
|
|
657
|
+
if (externalSaleNumber === undefined || externalSaleNumber === null || externalSaleNumber === '') {
|
|
658
|
+
this.logError('markOrderSyncedByExternalSaleNumber-缺少 external_sale_number');
|
|
659
|
+
return {
|
|
660
|
+
updated: false
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
const key = this.getIdKey(externalSaleNumber);
|
|
664
|
+
const targetIndex = this.store.list.findIndex(order => {
|
|
665
|
+
const value = order?.external_sale_number;
|
|
666
|
+
if (value === undefined || value === null || value === '') return false;
|
|
667
|
+
return this.getIdKey(value) === key;
|
|
668
|
+
});
|
|
669
|
+
if (targetIndex < 0) {
|
|
670
|
+
this.logInfo('markOrderSyncedByExternalSaleNumber-本地订单不存在,跳过', {
|
|
671
|
+
external_sale_number: externalSaleNumber
|
|
672
|
+
});
|
|
673
|
+
return {
|
|
674
|
+
updated: false
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
const existing = this.store.list[targetIndex];
|
|
678
|
+
const nextOrder = this.normalizeRemoteSyncedOrder({
|
|
679
|
+
...existing,
|
|
680
|
+
...(params.patch || {}),
|
|
681
|
+
external_sale_number: existing.external_sale_number ?? externalSaleNumber,
|
|
682
|
+
need_sync: 0
|
|
683
|
+
});
|
|
684
|
+
const storageKey = this.getOrderStorageKey(nextOrder) || key;
|
|
685
|
+
this.store.list = this.store.list.map((order, index) => {
|
|
686
|
+
if (index !== targetIndex) return order;
|
|
687
|
+
return nextOrder;
|
|
688
|
+
});
|
|
689
|
+
this.syncOrdersMap();
|
|
690
|
+
await this.patchOrdersInSQLite([nextOrder], 'markOrderSyncedByExternalSaleNumber', {
|
|
691
|
+
[storageKey]: 'update'
|
|
692
|
+
});
|
|
693
|
+
this.logInfo('markOrderSyncedByExternalSaleNumber-完成', {
|
|
694
|
+
external_sale_number: externalSaleNumber,
|
|
695
|
+
order_id: nextOrder.order_id ?? null
|
|
696
|
+
});
|
|
697
|
+
await this.emitOrdersChanged([nextOrder], 'markOrderSyncedByExternalSaleNumber');
|
|
698
|
+
return {
|
|
699
|
+
updated: true,
|
|
700
|
+
order: nextOrder
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* 将本地待同步订单按 SQLite storage key 合并进 store 并落库。
|
|
706
|
+
* checkout 离线兜底可能没有 order_id,但通常会有 external_sale_number。
|
|
707
|
+
*/
|
|
708
|
+
async upsertPendingSyncOrders(pendingOrders) {
|
|
709
|
+
if (!pendingOrders?.length) {
|
|
710
|
+
this.logInfo('upsertPendingSyncOrders-订单列表为空', {});
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
const pendingMap = new Map();
|
|
714
|
+
const memoryPendingMap = new Map();
|
|
715
|
+
for (const order of pendingOrders) {
|
|
716
|
+
const normalizedOrder = this.normalizeOrderCollectionsForIngress(order, 'upsertPendingSyncOrders.incoming');
|
|
717
|
+
const storageKey = this.getOrderStorageKey(normalizedOrder);
|
|
718
|
+
if (!storageKey) continue;
|
|
719
|
+
pendingMap.set(storageKey, normalizedOrder);
|
|
720
|
+
memoryPendingMap.set(storageKey, this.normalizeOrderForMemoryList(normalizedOrder, 'upsertPendingSyncOrders.memory'));
|
|
721
|
+
}
|
|
722
|
+
if (pendingMap.size === 0) {
|
|
723
|
+
this.logError('upsertPendingSyncOrders-订单缺少可落库标识', {
|
|
724
|
+
count: pendingOrders.length
|
|
725
|
+
});
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
this.logInfo('upsertPendingSyncOrders-开始', {
|
|
729
|
+
count: pendingMap.size
|
|
730
|
+
});
|
|
731
|
+
const mergeActions = {};
|
|
732
|
+
const updatedList = this.store.list.map(order => this.normalizeOrderForMemoryList(order, 'upsertPendingSyncOrders.existingMemory'));
|
|
733
|
+
|
|
734
|
+
// 与 mergeOrdersToStore 一致:基于身份键(field:value)建立索引,把「查找已存在订单」
|
|
735
|
+
// 从 O(n) 线性扫描降为 O(1),整体复杂度由 O(n^2) 降为 O(n + m)。
|
|
736
|
+
const identityKeyToIndex = new Map();
|
|
737
|
+
for (let index = 0; index < updatedList.length; index += 1) {
|
|
738
|
+
for (const key of this.getOrderIdentityMatchKeys(updatedList[index])) {
|
|
739
|
+
// 同一 key 命中多张时保留最靠前的下标,与原 findIndex「取首个匹配」一致
|
|
740
|
+
if (!identityKeyToIndex.has(key)) identityKeyToIndex.set(key, index);
|
|
181
741
|
}
|
|
182
742
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
743
|
+
for (const [storageKey, pendingOrder] of memoryPendingMap.entries()) {
|
|
744
|
+
const matchIndex = this.lookupOrderIndexByIdentityKeys(identityKeyToIndex, this.getOrderIdentityMatchKeys(pendingOrder));
|
|
745
|
+
if (matchIndex >= 0) {
|
|
746
|
+
const existingOrder = updatedList[matchIndex];
|
|
747
|
+
const mergedOrder = this.mergeOrderRecords(existingOrder, pendingOrder);
|
|
748
|
+
updatedList[matchIndex] = mergedOrder;
|
|
749
|
+
const pendingSnapshot = pendingMap.get(storageKey);
|
|
750
|
+
if (pendingSnapshot) {
|
|
751
|
+
pendingMap.set(storageKey, this.reconcileOverwriteOrderCollections(existingOrder, pendingSnapshot));
|
|
752
|
+
}
|
|
753
|
+
// 合并后身份字段可能新增(如挂单拿到 order_id),同步登记,供同批后续 pending 命中同一槽位
|
|
754
|
+
this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex);
|
|
755
|
+
mergeActions[storageKey] = 'update';
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
const insertIndex = updatedList.length;
|
|
759
|
+
updatedList.push(pendingOrder);
|
|
760
|
+
this.registerOrderIdentityKeys(identityKeyToIndex, pendingOrder, insertIndex);
|
|
761
|
+
mergeActions[storageKey] = 'insert';
|
|
762
|
+
}
|
|
763
|
+
const patchedOrders = [...pendingMap.values()];
|
|
764
|
+
// 增量索引已保证 store 去重不变量,无需再对全表做 O(n^2) 压缩;仅自检开关开启时兜底。
|
|
765
|
+
this.store.list = this.runOrderStoreSelfCheck(updatedList, 'upsertPendingSyncOrders.store');
|
|
766
|
+
this.syncOrdersMap();
|
|
767
|
+
await this.patchOrdersInSQLite(patchedOrders, 'upsertPendingSyncOrders', mergeActions);
|
|
768
|
+
this.logInfo('upsertPendingSyncOrders-结束', {
|
|
769
|
+
count: patchedOrders.length,
|
|
770
|
+
storeOrderCountAfter: this.store.list.length
|
|
771
|
+
});
|
|
772
|
+
await this.emitOrdersChanged(patchedOrders, 'upsertPendingSyncOrders');
|
|
186
773
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* 通过 SSE 按自定义 query 拉取订单(支持 select/with 精简字段)
|
|
777
|
+
*/
|
|
778
|
+
async fetchOrdersBySSE(query = {}) {
|
|
779
|
+
if (!this.orderDataSource) return [];
|
|
780
|
+
try {
|
|
781
|
+
const data = await this.orderDataSource.run({
|
|
782
|
+
sse: {
|
|
783
|
+
query
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
return data || [];
|
|
787
|
+
} catch (error) {
|
|
788
|
+
this.logError('fetchOrdersBySSE-失败', {
|
|
789
|
+
error: error instanceof Error ? error.message : String(error)
|
|
790
|
+
});
|
|
791
|
+
return [];
|
|
792
|
+
}
|
|
190
793
|
}
|
|
191
794
|
getRoutes() {
|
|
192
795
|
return [];
|
|
@@ -196,41 +799,67 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
196
799
|
clearTimeout(this.syncTimer);
|
|
197
800
|
this.syncTimer = undefined;
|
|
198
801
|
}
|
|
802
|
+
this.isProcessingSyncBatch = false;
|
|
199
803
|
this.pendingSyncMessages = [];
|
|
200
804
|
if (this.orderDataSource?.destroy) this.orderDataSource.destroy();
|
|
201
805
|
super.destroy();
|
|
202
806
|
}
|
|
203
807
|
syncOrdersMap() {
|
|
204
808
|
this.store.map.clear();
|
|
205
|
-
this.resourceIdIndex.clear();
|
|
206
809
|
for (const order of this.store.list) {
|
|
207
|
-
const
|
|
208
|
-
if (
|
|
209
|
-
this.store.map.set(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
810
|
+
const primaryIdentity = this.getOrderMapKey(order);
|
|
811
|
+
if (!primaryIdentity) continue;
|
|
812
|
+
this.store.map.set(primaryIdentity, order);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
getOrderMapKey(order) {
|
|
816
|
+
if (!order) return '';
|
|
817
|
+
if (!this.isBlankIdentityValue(order.order_id)) {
|
|
818
|
+
return this.getIdKey(order.order_id);
|
|
819
|
+
}
|
|
820
|
+
const externalSaleNumber = order.external_sale_number;
|
|
821
|
+
if (!this.isBlankIdentityValue(externalSaleNumber)) {
|
|
822
|
+
return this.getIdKey(externalSaleNumber);
|
|
823
|
+
}
|
|
824
|
+
return '';
|
|
825
|
+
}
|
|
826
|
+
isBlankIdentityValue(value) {
|
|
827
|
+
return value === undefined || value === null || value === '';
|
|
828
|
+
}
|
|
829
|
+
getOrderIdentityEntries(order) {
|
|
830
|
+
if (!order) return [];
|
|
831
|
+
const record = order;
|
|
832
|
+
const candidates = [{
|
|
833
|
+
field: 'order_id',
|
|
834
|
+
value: order.order_id
|
|
835
|
+
}, {
|
|
836
|
+
field: 'external_sale_number',
|
|
837
|
+
value: record.external_sale_number
|
|
838
|
+
}, {
|
|
839
|
+
field: 'order_number',
|
|
840
|
+
value: record.order_number
|
|
841
|
+
}, {
|
|
842
|
+
field: 'shop_order_number',
|
|
843
|
+
value: record.shop_order_number
|
|
844
|
+
}];
|
|
845
|
+
const entries = [];
|
|
846
|
+
for (const candidate of candidates) {
|
|
847
|
+
if (this.isBlankIdentityValue(candidate.value)) continue;
|
|
848
|
+
const key = this.getIdKey(candidate.value);
|
|
849
|
+
if (entries.some(entry => entry.field === candidate.field && entry.key === key)) continue;
|
|
850
|
+
entries.push({
|
|
851
|
+
field: candidate.field,
|
|
852
|
+
key
|
|
228
853
|
});
|
|
229
|
-
};
|
|
230
|
-
if (Array.isArray(order?.bookings)) {
|
|
231
|
-
order.bookings.forEach(booking => visitResources(booking?.resources || []));
|
|
232
854
|
}
|
|
233
|
-
return
|
|
855
|
+
return entries;
|
|
856
|
+
}
|
|
857
|
+
getOrderIdentityKeys(order) {
|
|
858
|
+
const keys = [];
|
|
859
|
+
for (const entry of this.getOrderIdentityEntries(order)) {
|
|
860
|
+
if (!keys.includes(entry.key)) keys.push(entry.key);
|
|
861
|
+
}
|
|
862
|
+
return keys;
|
|
234
863
|
}
|
|
235
864
|
getOrderDateKey(order) {
|
|
236
865
|
const candidates = [order?.schedule_date, order?.create_date, order?.created_at, order?.bookings?.[0]?.start_date, order?.bookings?.[0]?.select_date];
|
|
@@ -253,73 +882,163 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
253
882
|
}
|
|
254
883
|
|
|
255
884
|
/**
|
|
256
|
-
* 初始化 pubsub
|
|
885
|
+
* 初始化 pubsub 订阅,监听订单变更(纯订阅,不拉数据;全量 SSE 由 loadOrdersByServer 负责)
|
|
257
886
|
*/
|
|
258
887
|
setupOrderSync() {
|
|
259
888
|
if (!this.orderDataSource) return;
|
|
260
|
-
|
|
261
|
-
const syncPayload = {
|
|
889
|
+
this.orderDataSource.run({
|
|
262
890
|
pubsub: {
|
|
263
891
|
callback: res => {
|
|
892
|
+
const pubsubReceivedAt = Date.now();
|
|
264
893
|
const message = this.normalizeOrderSyncMessage(res);
|
|
265
894
|
if (!message) return;
|
|
895
|
+
message._pubsubReceivedAt = pubsubReceivedAt;
|
|
266
896
|
this.pendingSyncMessages.push(message);
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
897
|
+
const throttleMs = this.getOrderSyncThrottleMs();
|
|
898
|
+
const routeInfo = this.resolveSyncMessageRoute(message);
|
|
899
|
+
this.logInfo('orderSync-收到消息并入队', {
|
|
900
|
+
id: message.id ?? null,
|
|
901
|
+
order_id: message.order_id ?? null,
|
|
902
|
+
type: message.type ?? null,
|
|
903
|
+
pendingCount: this.pendingSyncMessages.length,
|
|
904
|
+
throttleMs,
|
|
905
|
+
pubsubReceivedAt: new Date(pubsubReceivedAt).toISOString(),
|
|
906
|
+
fullMessage: JSON.stringify(message)
|
|
907
|
+
});
|
|
908
|
+
this.scheduleOrderSyncThrottle();
|
|
271
909
|
}
|
|
272
910
|
}
|
|
273
|
-
};
|
|
274
|
-
syncPayload.http = {
|
|
275
|
-
query: this.store.createdAtQuery,
|
|
276
|
-
skipHttp: shouldSkipHttpPull
|
|
277
|
-
};
|
|
278
|
-
this.orderDataSource.run(syncPayload).then(() => {
|
|
279
|
-
if (!shouldSkipHttpPull) this.markOrderPulledAtNow();
|
|
280
911
|
}).catch(() => {
|
|
281
912
|
// 忽略同步初始化异常,避免影响主流程
|
|
282
913
|
});
|
|
283
914
|
}
|
|
284
915
|
|
|
285
916
|
/**
|
|
286
|
-
*
|
|
917
|
+
* 调度订单同步节流窗口(leading-edge throttle):
|
|
918
|
+
* - 空闲状态下收到的第一条消息立即触发批处理,无需等待;
|
|
919
|
+
* - 首条处理后进入冷却窗口,窗口期间的后续消息聚合,窗口结束后批量处理;
|
|
920
|
+
* - 若当前批处理执行中,则由批处理结束后决定是否开启下一轮窗口。
|
|
921
|
+
*/
|
|
922
|
+
scheduleOrderSyncThrottle() {
|
|
923
|
+
if (this.isProcessingSyncBatch) return;
|
|
924
|
+
if (this.isIdlePhase) {
|
|
925
|
+
this.isIdlePhase = false;
|
|
926
|
+
this.logInfo('orderSync-首条消息立即触发处理');
|
|
927
|
+
void this.flushOrderSyncMessagesByThrottle();
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
if (this.syncTimer) return;
|
|
931
|
+
const throttleMs = this.getOrderSyncThrottleMs();
|
|
932
|
+
this.syncTimer = setTimeout(() => {
|
|
933
|
+
this.syncTimer = undefined;
|
|
934
|
+
void this.flushOrderSyncMessagesByThrottle();
|
|
935
|
+
}, throttleMs);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* 执行一次节流批处理并在需要时续约下一轮窗口。
|
|
940
|
+
* 批处理完成且无后续消息时恢复空闲状态,下一条消息将立即触发。
|
|
941
|
+
*/
|
|
942
|
+
async flushOrderSyncMessagesByThrottle() {
|
|
943
|
+
if (this.isProcessingSyncBatch) return;
|
|
944
|
+
if (this.pendingSyncMessages.length === 0) {
|
|
945
|
+
this.isIdlePhase = true;
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
this.isProcessingSyncBatch = true;
|
|
949
|
+
try {
|
|
950
|
+
await this.processOrderSyncMessages();
|
|
951
|
+
} finally {
|
|
952
|
+
this.isProcessingSyncBatch = false;
|
|
953
|
+
if (this.pendingSyncMessages.length > 0) {
|
|
954
|
+
this.scheduleOrderSyncThrottle();
|
|
955
|
+
} else {
|
|
956
|
+
this.isIdlePhase = true;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* 处理节流窗口内聚合后的同步消息批次
|
|
287
963
|
*
|
|
288
964
|
* 后端统一发送 change 消息,不再区分新增/编辑/删除。
|
|
289
965
|
* - 单条(id/order_id):若携带 body/data 则直接 upsert 到本地
|
|
290
966
|
* - 批量(ids):通过 HTTP 按 ids 增量拉取,再 merge 到本地
|
|
967
|
+
*
|
|
968
|
+
* @example
|
|
969
|
+
* // pending 队列中存在 8 条消息时,仅处理一次批次并统一 merge
|
|
970
|
+
* await this.processOrderSyncMessages()
|
|
291
971
|
*/
|
|
292
972
|
async processOrderSyncMessages() {
|
|
293
973
|
const messages = [...this.pendingSyncMessages];
|
|
294
974
|
this.pendingSyncMessages = [];
|
|
295
975
|
if (messages.length === 0) return;
|
|
976
|
+
const batchProcessStartAt = Date.now();
|
|
977
|
+
const earliestReceivedAt = Math.min(...messages.map(m => m._pubsubReceivedAt ?? batchProcessStartAt));
|
|
978
|
+
this.logInfo('processOrderSyncMessages-开始', {
|
|
979
|
+
messageCount: messages.length,
|
|
980
|
+
earliestReceivedAt: new Date(earliestReceivedAt).toISOString(),
|
|
981
|
+
batchProcessStartAt: new Date(batchProcessStartAt).toISOString(),
|
|
982
|
+
waitDurationMs: batchProcessStartAt - earliestReceivedAt
|
|
983
|
+
});
|
|
296
984
|
const upsertOrders = new Map();
|
|
297
985
|
const refreshIds = [];
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
const
|
|
301
|
-
const
|
|
302
|
-
const
|
|
303
|
-
const normalizedBody = this.normalizeOrderSyncPayload(
|
|
304
|
-
if (
|
|
986
|
+
messages.forEach((msg, msgIndex) => {
|
|
987
|
+
const routeInfo = this.resolveSyncMessageRoute(msg);
|
|
988
|
+
const hasBatchIds = routeInfo.hasBatchIds;
|
|
989
|
+
const singleId = routeInfo.singleId;
|
|
990
|
+
const hasSingleId = routeInfo.hasSingleId;
|
|
991
|
+
const normalizedBody = this.normalizeOrderSyncPayload(msg.body || msg.data);
|
|
992
|
+
if (routeInfo.route === 'bodyUpsert' && normalizedBody) {
|
|
305
993
|
upsertOrders.set(this.getIdKey(normalizedBody.order_id), normalizedBody);
|
|
306
|
-
|
|
994
|
+
return;
|
|
307
995
|
}
|
|
308
|
-
if (hasBatchIds) refreshIds.push(...
|
|
309
|
-
}
|
|
996
|
+
if (hasBatchIds) refreshIds.push(...routeInfo.batchIds);else if (routeInfo.route === 'httpRefresh.singleId' && singleId !== null) refreshIds.push(singleId);else if (routeInfo.route === 'httpRefresh.relationIds') refreshIds.push(...routeInfo.batchIds);
|
|
997
|
+
});
|
|
310
998
|
const uniqueRefreshIds = this.uniqueOrderIds(refreshIds);
|
|
311
999
|
const upsertList = [...upsertOrders.values()];
|
|
312
|
-
|
|
1000
|
+
this.logInfo('processOrderSyncMessages-去重完成', {
|
|
1001
|
+
upsertCount: upsertList.length,
|
|
1002
|
+
refreshIdCount: uniqueRefreshIds.length
|
|
1003
|
+
});
|
|
1004
|
+
if (upsertList.length > 0) {
|
|
1005
|
+
await this.mergeOrdersToStore(upsertList, 'pubsub.bodyUpsert');
|
|
1006
|
+
}
|
|
313
1007
|
if (uniqueRefreshIds.length > 0) {
|
|
314
|
-
const
|
|
315
|
-
|
|
1008
|
+
const {
|
|
1009
|
+
toFetch,
|
|
1010
|
+
skipped
|
|
1011
|
+
} = this.filterRedundantHttpRefreshIds(uniqueRefreshIds);
|
|
1012
|
+
if (skipped.length > 0) {}
|
|
1013
|
+
if (toFetch.length > 0) {
|
|
1014
|
+
const freshOrders = await this.fetchOrdersByHttp(toFetch);
|
|
1015
|
+
if (freshOrders.length > 0) {
|
|
1016
|
+
await this.mergeOrdersToStore(freshOrders, 'pubsub.httpRefresh');
|
|
1017
|
+
} else {}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (upsertList.length === 0 && uniqueRefreshIds.length === 0) {
|
|
1021
|
+
return;
|
|
316
1022
|
}
|
|
317
|
-
|
|
1023
|
+
const batchProcessEndAt = Date.now();
|
|
1024
|
+
this.logInfo('processOrderSyncMessages-结束', {
|
|
1025
|
+
upsertCount: upsertList.length,
|
|
1026
|
+
refreshIdCount: uniqueRefreshIds.length,
|
|
1027
|
+
batchProcessDurationMs: batchProcessEndAt - batchProcessStartAt,
|
|
1028
|
+
totalFromPubsubToProcessedMs: batchProcessEndAt - earliestReceivedAt
|
|
1029
|
+
});
|
|
318
1030
|
await this.core.effects.emit(_types.OrderHooks.onOrdersSyncCompleted, null);
|
|
319
1031
|
}
|
|
320
1032
|
|
|
321
1033
|
/**
|
|
322
|
-
* 通过 HTTP 按 ids
|
|
1034
|
+
* 通过 HTTP 按 ids 批量拉取订单详情(走 orderDataSource)。
|
|
1035
|
+
*
|
|
1036
|
+
* - 优先使用 `orderDataSource.fetchOrdersByIds`(如适配器实现了该方法)
|
|
1037
|
+
* - 否则回退到 `orderDataSource.run({ http: { query: { ids } } })`
|
|
1038
|
+
* - 失败统一返回空数组(由调用方按"返回缺失的 id" 判断单笔失败)
|
|
1039
|
+
*
|
|
1040
|
+
* @example
|
|
1041
|
+
* const fresh = await orderModule.fetchOrdersByHttp(['96971', '96972'])
|
|
323
1042
|
*/
|
|
324
1043
|
async fetchOrdersByHttp(ids) {
|
|
325
1044
|
if (!this.orderDataSource || ids.length === 0) return [];
|
|
@@ -344,29 +1063,145 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
344
1063
|
/**
|
|
345
1064
|
* 将增量订单合并到 store
|
|
346
1065
|
*/
|
|
347
|
-
async mergeOrdersToStore(freshOrders) {
|
|
348
|
-
const
|
|
1066
|
+
async mergeOrdersToStore(freshOrders, source) {
|
|
1067
|
+
const storeOrderCountBefore = this.store.list.length;
|
|
1068
|
+
this.logInfo('mergeOrdersToStore-开始', {
|
|
1069
|
+
freshOrderCount: freshOrders.length,
|
|
1070
|
+
storeOrderCountBefore,
|
|
1071
|
+
source
|
|
1072
|
+
});
|
|
1073
|
+
this.logDuplicateOrders(`${source}.beforeMerge`, this.store.list);
|
|
1074
|
+
const normalizedFreshOrders = [];
|
|
349
1075
|
for (const order of freshOrders) {
|
|
350
1076
|
const id = order?.order_id;
|
|
351
1077
|
if (id === undefined || id === null) continue;
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
1078
|
+
normalizedFreshOrders.push(this.normalizeRemoteSyncedOrder(order));
|
|
1079
|
+
}
|
|
1080
|
+
// 先对本批新单做去重(批内规模 m 很小),保证每条 fresh 身份唯一
|
|
1081
|
+
const uniqueFreshOrders = this.compactOrderListByIdentity(normalizedFreshOrders, `${source}.incoming`).list;
|
|
1082
|
+
const uniqueFreshCount = uniqueFreshOrders.length;
|
|
1083
|
+
const patchedOrders = [];
|
|
1084
|
+
const mergeActions = {};
|
|
1085
|
+
const updatedList = this.store.list.map(order => this.normalizeOrderForMemoryList(order, `${source}.existingMemory`));
|
|
1086
|
+
|
|
1087
|
+
// 基于身份键(field:value)建立索引,把「查找已存在订单」从 O(n) 线性扫描降为 O(1),
|
|
1088
|
+
// 整体合并复杂度由 O(n^2) 降为 O(n + m)。索引语义与 doOrdersShareIdentity 完全一致:
|
|
1089
|
+
// 任一身份字段(order_id/external_sale_number/order_number/shop_order_number)同值即同一单。
|
|
1090
|
+
const identityKeyToIndex = new Map();
|
|
1091
|
+
for (let index = 0; index < updatedList.length; index += 1) {
|
|
1092
|
+
const identityKeys = this.getOrderIdentityMatchKeys(updatedList[index]);
|
|
1093
|
+
for (const key of identityKeys) {
|
|
1094
|
+
// 同一 key 命中多张时保留最靠前的下标,与原 findIndex「取首个匹配」一致
|
|
1095
|
+
if (!identityKeyToIndex.has(key)) identityKeyToIndex.set(key, index);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
for (const fresh of uniqueFreshOrders) {
|
|
1099
|
+
const freshIdentityKeys = this.getOrderIdentityMatchKeys(fresh);
|
|
1100
|
+
const matchIndex = this.lookupOrderIndexByIdentityKeys(identityKeyToIndex, freshIdentityKeys);
|
|
1101
|
+
const storageKey = this.getOrderStorageKey(fresh);
|
|
1102
|
+
const mergeKey = storageKey || (fresh.order_id !== undefined && fresh.order_id !== null ? this.getIdKey(fresh.order_id) : '');
|
|
1103
|
+
if (matchIndex >= 0) {
|
|
1104
|
+
const existingOrder = updatedList[matchIndex];
|
|
1105
|
+
if (this.isPendingSyncOrder(existingOrder) && this.isRemoteEchoMergeSource(source)) {
|
|
1106
|
+
this.logInfo('mergeOrdersToStore-跳过 pending 订单远端覆盖', {
|
|
1107
|
+
source,
|
|
1108
|
+
order_id: existingOrder.order_id ?? fresh.order_id ?? null,
|
|
1109
|
+
external_sale_number: existingOrder.external_sale_number ?? fresh.external_sale_number ?? null,
|
|
1110
|
+
local_payment_status: existingOrder.payment_status ?? null,
|
|
1111
|
+
remote_payment_status: fresh.payment_status ?? null,
|
|
1112
|
+
local_need_sync: existingOrder.need_sync ?? null,
|
|
1113
|
+
remote_need_sync: fresh.need_sync ?? null,
|
|
1114
|
+
reason: 'protect_pending_sync_order_from_remote_echo'
|
|
1115
|
+
});
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
const mergedOrder = this.mergeOrderRecords(existingOrder, fresh);
|
|
1119
|
+
updatedList[matchIndex] = mergedOrder;
|
|
1120
|
+
// 合并后身份字段可能新增(如挂单拿到 order_id),同步登记到索引,
|
|
1121
|
+
// 供同批后续 fresh 命中同一槽位,避免重复插入
|
|
1122
|
+
this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex);
|
|
1123
|
+
patchedOrders.push(mergedOrder);
|
|
1124
|
+
if (mergeKey) mergeActions[mergeKey] = 'update';
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
const insertIndex = updatedList.length;
|
|
1128
|
+
updatedList.push(fresh);
|
|
1129
|
+
this.registerOrderIdentityKeys(identityKeyToIndex, fresh, insertIndex);
|
|
1130
|
+
patchedOrders.push(fresh);
|
|
1131
|
+
if (mergeKey) mergeActions[mergeKey] = 'insert';
|
|
365
1132
|
}
|
|
366
|
-
|
|
1133
|
+
const insertCount = Object.values(mergeActions).filter(v => v === 'insert').length;
|
|
1134
|
+
const updateCount = Object.values(mergeActions).filter(v => v === 'update').length;
|
|
1135
|
+
this.logInfo('mergeOrdersToStore-合并完成', {
|
|
1136
|
+
insertCount,
|
|
1137
|
+
updateCount,
|
|
1138
|
+
storeOrderCountAfter: this.store.list.length
|
|
1139
|
+
});
|
|
1140
|
+
// 增量索引已保证 store 去重不变量,无需再对全表做 O(n^2) 压缩;
|
|
1141
|
+
// 仅在自检开关开启时做一次兜底校验。
|
|
1142
|
+
this.store.list = this.runOrderStoreSelfCheck(updatedList, `${source}.store`);
|
|
367
1143
|
this.syncOrdersMap();
|
|
368
|
-
await this.
|
|
369
|
-
this.
|
|
1144
|
+
await this.patchOrdersInSQLite(patchedOrders, source, mergeActions);
|
|
1145
|
+
this.logInfo('mergeOrdersToStore-结束', {
|
|
1146
|
+
uniqueFreshCount,
|
|
1147
|
+
storeOrderCountAfter: this.store.list.length,
|
|
1148
|
+
insertCount,
|
|
1149
|
+
updateCount
|
|
1150
|
+
});
|
|
1151
|
+
await this.emitOrdersChanged(patchedOrders, source);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
/**
|
|
1155
|
+
* 在身份索引中查找与目标订单共享任一身份键的已存在订单下标。
|
|
1156
|
+
* 返回所有命中键里最小的下标,等价于原 findOrderIndexByIdentity 的「取首个匹配」语义。
|
|
1157
|
+
*
|
|
1158
|
+
* @example
|
|
1159
|
+
* const idx = this.lookupOrderIndexByIdentityKeys(identityKeyToIndex, freshIdentityKeys)
|
|
1160
|
+
*/
|
|
1161
|
+
lookupOrderIndexByIdentityKeys(identityKeyToIndex, identityKeys) {
|
|
1162
|
+
let matchIndex = -1;
|
|
1163
|
+
for (const key of identityKeys) {
|
|
1164
|
+
const index = identityKeyToIndex.get(key);
|
|
1165
|
+
if (index === undefined) continue;
|
|
1166
|
+
if (matchIndex < 0 || index < matchIndex) matchIndex = index;
|
|
1167
|
+
}
|
|
1168
|
+
return matchIndex;
|
|
1169
|
+
}
|
|
1170
|
+
isRemoteEchoMergeSource(source) {
|
|
1171
|
+
return source === 'pubsub.bodyUpsert' || source === 'pubsub.httpRefresh';
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* 将订单的全部身份键登记到索引并指向其在列表中的下标。
|
|
1176
|
+
* 合并后订单新增的身份字段(如挂单同步后拿到 order_id)也会被登记。
|
|
1177
|
+
*
|
|
1178
|
+
* @example
|
|
1179
|
+
* this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex)
|
|
1180
|
+
*/
|
|
1181
|
+
registerOrderIdentityKeys(identityKeyToIndex, order, index) {
|
|
1182
|
+
const identityKeys = this.getOrderIdentityMatchKeys(order);
|
|
1183
|
+
for (const key of identityKeys) {
|
|
1184
|
+
identityKeyToIndex.set(key, index);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* 合并热路径去重兜底:默认直接返回原列表(增量索引已保证无重复)。
|
|
1190
|
+
* 仅当 ORDER_MERGE_SELF_CHECK 开启时,额外做一次全表压缩并在发现残留重复时告警。
|
|
1191
|
+
*/
|
|
1192
|
+
runOrderStoreSelfCheck(orders, source) {
|
|
1193
|
+
if (!ORDER_MERGE_SELF_CHECK) return orders;
|
|
1194
|
+
const {
|
|
1195
|
+
list,
|
|
1196
|
+
removedCount
|
|
1197
|
+
} = this.compactOrderListByIdentity(orders, source);
|
|
1198
|
+
if (removedCount > 0) {
|
|
1199
|
+
this.logWarning('合并自检发现残留重复订单,已压缩', {
|
|
1200
|
+
source,
|
|
1201
|
+
removedCount
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
return list;
|
|
370
1205
|
}
|
|
371
1206
|
normalizeOrderSyncMessage(res) {
|
|
372
1207
|
const data = res?.data || res;
|
|
@@ -404,6 +1239,169 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
404
1239
|
getIdKey(id) {
|
|
405
1240
|
return String(id);
|
|
406
1241
|
}
|
|
1242
|
+
isOrderLookupMatch(order, lookupKey) {
|
|
1243
|
+
if (!order || !lookupKey) return false;
|
|
1244
|
+
const candidates = [order.order_id, order.external_sale_number, order.order_number, order.shop_order_number, order.shop_full_order_number];
|
|
1245
|
+
return candidates.some(value => {
|
|
1246
|
+
if (value === undefined || value === null || value === '') return false;
|
|
1247
|
+
return this.getIdKey(value) === lookupKey;
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* 解析订单 SQLite 主键,规则与宿主 OrderDataSource.getOrderStorageKey 一致。
|
|
1253
|
+
*
|
|
1254
|
+
* @example
|
|
1255
|
+
* const key = this.getOrderStorageKey({ order_id: 1001, external_sale_number: 'S-1001' })
|
|
1256
|
+
* // => 'S-1001'
|
|
1257
|
+
*/
|
|
1258
|
+
getOrderStorageKey(order) {
|
|
1259
|
+
const externalSaleNumber = order?.external_sale_number;
|
|
1260
|
+
if (externalSaleNumber !== undefined && externalSaleNumber !== null && externalSaleNumber !== '') {
|
|
1261
|
+
return String(externalSaleNumber);
|
|
1262
|
+
}
|
|
1263
|
+
const orderId = order?.order_id;
|
|
1264
|
+
if (orderId !== undefined && orderId !== null && orderId !== '') {
|
|
1265
|
+
return String(orderId);
|
|
1266
|
+
}
|
|
1267
|
+
return '';
|
|
1268
|
+
}
|
|
1269
|
+
getOrderIdentityMatchKeys(order) {
|
|
1270
|
+
return this.getOrderIdentityEntries(order).map(entry => `${entry.field}:${entry.key}`);
|
|
1271
|
+
}
|
|
1272
|
+
doOrdersShareIdentity(left, right) {
|
|
1273
|
+
const leftKeys = new Set(this.getOrderIdentityMatchKeys(left));
|
|
1274
|
+
if (leftKeys.size === 0) return false;
|
|
1275
|
+
return this.getOrderIdentityMatchKeys(right).some(key => leftKeys.has(key));
|
|
1276
|
+
}
|
|
1277
|
+
findOrderIndexByIdentity(orders, target) {
|
|
1278
|
+
if (this.getOrderIdentityMatchKeys(target).length === 0) return -1;
|
|
1279
|
+
return orders.findIndex(order => this.doOrdersShareIdentity(order, target));
|
|
1280
|
+
}
|
|
1281
|
+
getOrderCompletenessScore(order) {
|
|
1282
|
+
const record = order;
|
|
1283
|
+
let score = this.isPendingSyncOrder(order) ? 0 : 100;
|
|
1284
|
+
if (!this.isBlankIdentityValue(order.order_id) && order.order_id !== record.external_sale_number) score += 30;
|
|
1285
|
+
score += this.getOrderIdentityMatchKeys(order).length * 5;
|
|
1286
|
+
if (Array.isArray(order.products)) score += Math.min(order.products.length, 10);
|
|
1287
|
+
if (Array.isArray(order.bookings)) score += Math.min(order.bookings.length, 10);
|
|
1288
|
+
if (Array.isArray(order.payments)) score += Math.min(order.payments.length, 10);
|
|
1289
|
+
if (record.updated_at) score += 1;
|
|
1290
|
+
return score;
|
|
1291
|
+
}
|
|
1292
|
+
pickPreferredOrder(left, right) {
|
|
1293
|
+
const leftScore = this.getOrderCompletenessScore(left);
|
|
1294
|
+
const rightScore = this.getOrderCompletenessScore(right);
|
|
1295
|
+
if (rightScore >= leftScore) return right;
|
|
1296
|
+
return left;
|
|
1297
|
+
}
|
|
1298
|
+
mergeOrderRecords(existing, incoming) {
|
|
1299
|
+
const preferred = this.pickPreferredOrder(existing, incoming);
|
|
1300
|
+
const secondary = preferred === existing ? incoming : existing;
|
|
1301
|
+
const merged = {
|
|
1302
|
+
...secondary,
|
|
1303
|
+
...preferred
|
|
1304
|
+
};
|
|
1305
|
+
const fieldsToPreserve = ['order_id', 'external_sale_number', 'order_number', 'shop_order_number', 'shop_full_order_number'];
|
|
1306
|
+
const incomingPatchFields = ['payment_status', 'status', 'shipping_status', 'updated_at', 'shop_discount', 'summary', 'note', 'customer_id', 'customer_name', 'customer', 'holder', 'contacts_info', 'phone', 'email', 'relation_forms'];
|
|
1307
|
+
const mergedRecord = merged;
|
|
1308
|
+
const preferredRecord = preferred;
|
|
1309
|
+
const secondaryRecord = secondary;
|
|
1310
|
+
const existingRecord = existing;
|
|
1311
|
+
const incomingRecord = incoming;
|
|
1312
|
+
const childUidRemaps = [];
|
|
1313
|
+
for (const field of fieldsToPreserve) {
|
|
1314
|
+
if (!this.isBlankIdentityValue(preferredRecord[field])) {
|
|
1315
|
+
mergedRecord[field] = preferredRecord[field];
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1318
|
+
if (!this.isBlankIdentityValue(secondaryRecord[field])) {
|
|
1319
|
+
mergedRecord[field] = secondaryRecord[field];
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
for (const field of incomingPatchFields) {
|
|
1323
|
+
if (!Object.prototype.hasOwnProperty.call(incomingRecord, field)) continue;
|
|
1324
|
+
if (incomingRecord[field] === undefined || incomingRecord[field] === null) continue;
|
|
1325
|
+
if (typeof incomingRecord[field] === 'string' && incomingRecord[field] === '') continue;
|
|
1326
|
+
mergedRecord[field] = (0, _lodashEs.cloneDeep)(incomingRecord[field]);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// 子项数组遵循完整快照语义:incoming 自有且为数组(包括 [])时权威替换;
|
|
1330
|
+
// 字段缺席或不是数组时视为稀疏 patch,继续保留 existing。
|
|
1331
|
+
for (const field of ORDER_AUTHORITATIVE_COLLECTION_FIELDS) {
|
|
1332
|
+
if (Array.isArray(incomingRecord[field])) {
|
|
1333
|
+
mergedRecord[field] = this.mergeAuthoritativeOrderCollection(field, existingRecord[field], incomingRecord[field], childUidRemaps);
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
if (Object.prototype.hasOwnProperty.call(existingRecord, field)) {
|
|
1337
|
+
mergedRecord[field] = (0, _lodashEs.cloneDeep)(existingRecord[field]);
|
|
1338
|
+
} else {
|
|
1339
|
+
delete mergedRecord[field];
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
if (!this.isPendingSyncOrder(preferred) || !this.isPendingSyncOrder(secondary)) {
|
|
1343
|
+
merged.need_sync = 0;
|
|
1344
|
+
}
|
|
1345
|
+
return this.normalizeOrderCollectionsForIngress((0, _orderCollectionIdentity.applyOrderCollectionUidRemaps)(merged, childUidRemaps), 'mergeOrderRecords');
|
|
1346
|
+
}
|
|
1347
|
+
summarizeDuplicateOrders(orders) {
|
|
1348
|
+
const groups = new Map();
|
|
1349
|
+
for (const order of orders) {
|
|
1350
|
+
for (const key of this.getOrderIdentityMatchKeys(order)) {
|
|
1351
|
+
const group = groups.get(key) || [];
|
|
1352
|
+
group.push(order);
|
|
1353
|
+
groups.set(key, group);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return [...groups.entries()].filter(([, group]) => group.length > 1).slice(0, 20).map(([key, group]) => ({
|
|
1357
|
+
key,
|
|
1358
|
+
count: group.length,
|
|
1359
|
+
orders: group.map(order => ({
|
|
1360
|
+
order_id: order.order_id ?? null,
|
|
1361
|
+
external_sale_number: order.external_sale_number ?? null,
|
|
1362
|
+
shop_order_number: order.shop_order_number ?? null,
|
|
1363
|
+
shop_full_order_number: order.shop_full_order_number ?? null,
|
|
1364
|
+
need_sync: order.need_sync ?? null
|
|
1365
|
+
}))
|
|
1366
|
+
}));
|
|
1367
|
+
}
|
|
1368
|
+
logDuplicateOrders(source, orders) {
|
|
1369
|
+
const duplicateGroups = this.summarizeDuplicateOrders(orders);
|
|
1370
|
+
if (duplicateGroups.length === 0) return;
|
|
1371
|
+
this.logWarning('检测到重复订单身份键', {
|
|
1372
|
+
source,
|
|
1373
|
+
duplicateGroupCount: duplicateGroups.length,
|
|
1374
|
+
duplicateGroups
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
compactOrderListByIdentity(orders, source) {
|
|
1378
|
+
const list = [];
|
|
1379
|
+
let removedCount = 0;
|
|
1380
|
+
for (const order of orders) {
|
|
1381
|
+
const normalizedOrder = this.normalizeOrderCollectionsForIngress(order, `${source}.order`);
|
|
1382
|
+
const matchIndex = this.findOrderIndexByIdentity(list, normalizedOrder);
|
|
1383
|
+
if (matchIndex < 0) {
|
|
1384
|
+
list.push(normalizedOrder);
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
list[matchIndex] = this.mergeOrderRecords(list[matchIndex], normalizedOrder);
|
|
1388
|
+
removedCount += 1;
|
|
1389
|
+
}
|
|
1390
|
+
if (removedCount > 0) {
|
|
1391
|
+
this.logWarning('订单列表重复项已压缩', {
|
|
1392
|
+
source,
|
|
1393
|
+
beforeCount: orders.length,
|
|
1394
|
+
afterCount: list.length,
|
|
1395
|
+
removedCount
|
|
1396
|
+
});
|
|
1397
|
+
} else {
|
|
1398
|
+
this.logDuplicateOrders(source, orders);
|
|
1399
|
+
}
|
|
1400
|
+
return {
|
|
1401
|
+
list,
|
|
1402
|
+
removedCount
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
407
1405
|
|
|
408
1406
|
/**
|
|
409
1407
|
* 当前订单拉取窗口:
|
|
@@ -474,34 +1472,219 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
474
1472
|
if (!this.dbManager) return [];
|
|
475
1473
|
try {
|
|
476
1474
|
const orders = await this.dbManager.getAll(INDEXDB_STORE_NAME);
|
|
477
|
-
return orders || [];
|
|
1475
|
+
return (orders || []).map(order => this.normalizeOrderCollectionsForIngress(order, 'loadOrdersFromSQLite'));
|
|
478
1476
|
} catch {
|
|
479
1477
|
return [];
|
|
480
1478
|
}
|
|
481
1479
|
}
|
|
482
|
-
|
|
483
|
-
|
|
1480
|
+
isPendingSyncOrder(order) {
|
|
1481
|
+
return Number(order?.need_sync) === 1;
|
|
1482
|
+
}
|
|
1483
|
+
normalizeRemoteSyncedOrder(order) {
|
|
1484
|
+
const normalizedOrder = this.normalizeOrderCollectionsForIngress(order, 'normalizeRemoteSyncedOrder');
|
|
1485
|
+
if (this.isPendingSyncOrder(normalizedOrder)) return normalizedOrder;
|
|
1486
|
+
return {
|
|
1487
|
+
...normalizedOrder,
|
|
1488
|
+
need_sync: 0
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
mergeRemoteSnapshotWithPendingOrders(remoteOrders, existingOrders) {
|
|
1492
|
+
const merged = remoteOrders.map(order => this.normalizeRemoteSyncedOrder(order));
|
|
1493
|
+
for (const rawOrder of existingOrders) {
|
|
1494
|
+
const order = this.normalizeOrderCollectionsForIngress(rawOrder, 'mergeRemoteSnapshotWithPendingOrders.existingSQLite');
|
|
1495
|
+
if (!this.isPendingSyncOrder(order)) continue;
|
|
1496
|
+
const matchIndex = this.findOrderIndexByIdentity(merged, order);
|
|
1497
|
+
if (matchIndex >= 0) {
|
|
1498
|
+
merged[matchIndex] = this.mergeOrderRecords(order, merged[matchIndex]);
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
if (!this.getOrderStorageKey(order)) continue;
|
|
1502
|
+
merged.push(order);
|
|
1503
|
+
}
|
|
1504
|
+
return this.compactOrderListByIdentity(merged, 'mergeRemoteSnapshotWithPendingOrders').list;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/**
|
|
1508
|
+
* 串行执行订单 SQLite 写入任务,避免快照替换与增量 upsert 并发交错。
|
|
1509
|
+
*
|
|
1510
|
+
* @example
|
|
1511
|
+
* await this.runInOrderSQLiteSaveQueue(async () => {
|
|
1512
|
+
* await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, orders)
|
|
1513
|
+
* })
|
|
1514
|
+
*/
|
|
1515
|
+
async runInOrderSQLiteSaveQueue(task) {
|
|
1516
|
+
const queuedTask = this.orderSQLiteSaveQueue.then(task, task);
|
|
1517
|
+
this.orderSQLiteSaveQueue = queuedTask.then(() => undefined, () => undefined);
|
|
1518
|
+
return queuedTask;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
/**
|
|
1522
|
+
* 批量增量 upsert 订单到 SQLite,不清空表;用于 pubsub / HTTP 增量合并。
|
|
1523
|
+
*
|
|
1524
|
+
* @example
|
|
1525
|
+
* await this.patchOrdersInSQLite(freshOrders)
|
|
1526
|
+
*/
|
|
1527
|
+
async patchOrdersInSQLite(orders, source, mergeActions = {}) {
|
|
1528
|
+
if (!this.dbManager || orders.length === 0) {
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
const ordersSnapshot = (0, _lodashEs.cloneDeep)(orders).map(order => this.normalizeOrderCollectionsForIngress(order, `${source}.sqliteIngress`)).filter(order => this.getOrderStorageKey(order));
|
|
1532
|
+
if (ordersSnapshot.length === 0) {
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
const {
|
|
1536
|
+
toWrite,
|
|
1537
|
+
skipped
|
|
1538
|
+
} = this.filterRedundantPatchOrders(source, ordersSnapshot, mergeActions);
|
|
1539
|
+
if (toWrite.length === 0) {
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
const compactedToWrite = this.compactOrderListByIdentity(toWrite, `${source}.sqlitePatch`).list;
|
|
1543
|
+
if (compactedToWrite.length === 0) {
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
484
1546
|
try {
|
|
485
|
-
await this.
|
|
486
|
-
|
|
487
|
-
|
|
1547
|
+
await this.runInOrderSQLiteSaveQueue(async () => {
|
|
1548
|
+
let writeMethod = 'none';
|
|
1549
|
+
this.logInfo('patchOrdersInSQLite-开始', {
|
|
1550
|
+
source,
|
|
1551
|
+
count: compactedToWrite.length,
|
|
1552
|
+
dedupedCount: skipped.length
|
|
1553
|
+
});
|
|
1554
|
+
if (typeof this.dbManager.bulkUpdate === 'function') {
|
|
1555
|
+
writeMethod = 'bulkUpdate';
|
|
1556
|
+
await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, compactedToWrite);
|
|
1557
|
+
} else if (typeof this.dbManager.bulkAdd === 'function') {
|
|
1558
|
+
writeMethod = 'bulkAdd';
|
|
1559
|
+
await this.dbManager.bulkAdd(INDEXDB_STORE_NAME, compactedToWrite);
|
|
1560
|
+
} else if (typeof this.dbManager.update === 'function') {
|
|
1561
|
+
writeMethod = 'update';
|
|
1562
|
+
for (const order of compactedToWrite) {
|
|
1563
|
+
await this.dbManager.update(INDEXDB_STORE_NAME, order);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
this.recordRecentSqliteWrite(source, compactedToWrite);
|
|
1567
|
+
this.logInfo('patchOrdersInSQLite-完成', {
|
|
1568
|
+
source,
|
|
1569
|
+
count: compactedToWrite.length,
|
|
1570
|
+
writeMethod,
|
|
1571
|
+
dedupedCount: skipped.length
|
|
1572
|
+
});
|
|
1573
|
+
});
|
|
488
1574
|
} catch (error) {
|
|
489
|
-
|
|
490
|
-
this.logError('保存订单到 SQLite 失败', {
|
|
1575
|
+
this.logError('增量保存订单到 SQLite 失败', {
|
|
491
1576
|
error: error instanceof Error ? error.message : String(error),
|
|
492
|
-
|
|
1577
|
+
orderCount: ordersSnapshot.length
|
|
493
1578
|
});
|
|
494
1579
|
}
|
|
495
1580
|
}
|
|
1581
|
+
|
|
1582
|
+
/**
|
|
1583
|
+
* 单条 upsert 订单到 SQLite;用于 overwriteExistingOrder 等单条覆盖场景。
|
|
1584
|
+
*
|
|
1585
|
+
* @example
|
|
1586
|
+
* await this.updateOrderInSQLite(freshOrder)
|
|
1587
|
+
*/
|
|
1588
|
+
async updateOrderInSQLite(order, source) {
|
|
1589
|
+
if (!this.dbManager || !this.getOrderStorageKey(order)) {
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
const orderSnapshot = this.normalizeOrderCollectionsForIngress((0, _lodashEs.cloneDeep)(order), `${source}.sqliteIngress`);
|
|
1593
|
+
try {
|
|
1594
|
+
await this.runInOrderSQLiteSaveQueue(async () => {
|
|
1595
|
+
let writeMethod = 'none';
|
|
1596
|
+
this.logInfo('updateOrderInSQLite-开始', {
|
|
1597
|
+
source,
|
|
1598
|
+
orderId: orderSnapshot.order_id ?? null,
|
|
1599
|
+
storageKey: this.getOrderStorageKey(orderSnapshot)
|
|
1600
|
+
});
|
|
1601
|
+
if (typeof this.dbManager.update === 'function') {
|
|
1602
|
+
writeMethod = 'update';
|
|
1603
|
+
await this.dbManager.update(INDEXDB_STORE_NAME, orderSnapshot);
|
|
1604
|
+
} else if (typeof this.dbManager.bulkUpdate === 'function') {
|
|
1605
|
+
writeMethod = 'bulkUpdate';
|
|
1606
|
+
await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, [orderSnapshot]);
|
|
1607
|
+
} else if (typeof this.dbManager.bulkAdd === 'function') {
|
|
1608
|
+
writeMethod = 'bulkAdd';
|
|
1609
|
+
await this.dbManager.bulkAdd(INDEXDB_STORE_NAME, [orderSnapshot]);
|
|
1610
|
+
}
|
|
1611
|
+
this.recordRecentSqliteWrite(source, [orderSnapshot]);
|
|
1612
|
+
this.logInfo('updateOrderInSQLite-完成', {
|
|
1613
|
+
source,
|
|
1614
|
+
orderId: orderSnapshot.order_id ?? null,
|
|
1615
|
+
writeMethod
|
|
1616
|
+
});
|
|
1617
|
+
});
|
|
1618
|
+
} catch (error) {
|
|
1619
|
+
this.logError('单条保存订单到 SQLite 失败', {
|
|
1620
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1621
|
+
orderId: orderSnapshot.order_id ?? null
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
/**
|
|
1627
|
+
* 全量快照替换 orders 表(clear + bulkAdd),用于 SSE 全量拉取与营业日窗口裁剪。
|
|
1628
|
+
*
|
|
1629
|
+
* @example
|
|
1630
|
+
* await this.replaceOrdersSnapshotInSQLite(orderList)
|
|
1631
|
+
*/
|
|
1632
|
+
async replaceOrdersSnapshotInSQLite(orderList, source) {
|
|
1633
|
+
if (!this.dbManager) {
|
|
1634
|
+
return this.compactOrderListByIdentity(orderList.map(order => this.normalizeRemoteSyncedOrder(order)), `${source}.snapshotNoDb`).list;
|
|
1635
|
+
}
|
|
1636
|
+
const remoteSnapshot = (0, _lodashEs.cloneDeep)(orderList).map(order => this.normalizeOrderCollectionsForIngress(order, `${source}.remoteIngress`));
|
|
1637
|
+
let mergedSnapshot = this.compactOrderListByIdentity(remoteSnapshot.map(order => this.normalizeRemoteSyncedOrder(order)), `${source}.remoteSnapshot`).list;
|
|
1638
|
+
try {
|
|
1639
|
+
await this.runInOrderSQLiteSaveQueue(async () => {
|
|
1640
|
+
const existingOrders = typeof this.dbManager.getAll === 'function' ? await this.dbManager.getAll(INDEXDB_STORE_NAME) : [];
|
|
1641
|
+
const orderListSnapshot = this.mergeRemoteSnapshotWithPendingOrders(remoteSnapshot, existingOrders || []);
|
|
1642
|
+
mergedSnapshot = this.compactOrderListByIdentity(orderListSnapshot, `${source}.sqliteSnapshot`).list;
|
|
1643
|
+
this.logInfo('replaceOrdersSnapshotInSQLite-开始', {
|
|
1644
|
+
source,
|
|
1645
|
+
count: mergedSnapshot.length,
|
|
1646
|
+
remoteCount: remoteSnapshot.length,
|
|
1647
|
+
preservedPendingCount: mergedSnapshot.length - remoteSnapshot.length
|
|
1648
|
+
});
|
|
1649
|
+
await this.dbManager.clear(INDEXDB_STORE_NAME);
|
|
1650
|
+
this.logInfo('replaceOrdersSnapshotInSQLite-clear完成', {
|
|
1651
|
+
count: mergedSnapshot.length
|
|
1652
|
+
});
|
|
1653
|
+
if (mergedSnapshot.length === 0) {
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
await this.dbManager.bulkAdd(INDEXDB_STORE_NAME, mergedSnapshot);
|
|
1657
|
+
this.recordRecentSqliteWrite(source, mergedSnapshot);
|
|
1658
|
+
this.logInfo('replaceOrdersSnapshotInSQLite-bulkAdd完成', {
|
|
1659
|
+
source,
|
|
1660
|
+
count: mergedSnapshot.length
|
|
1661
|
+
});
|
|
1662
|
+
});
|
|
1663
|
+
} catch (error) {
|
|
1664
|
+
this.logError('全量保存订单到 SQLite 失败', {
|
|
1665
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1666
|
+
orderList: remoteSnapshot.length
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
return mergedSnapshot;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
/**
|
|
1673
|
+
* 清空订单模块的内存数据和本地 SQLite 订单表。
|
|
1674
|
+
*
|
|
1675
|
+
* @example
|
|
1676
|
+
* await orderModule.clear()
|
|
1677
|
+
*/
|
|
496
1678
|
async clear() {
|
|
497
1679
|
this.store.list = [];
|
|
498
1680
|
this.store.map = new Map();
|
|
499
|
-
this.resourceIdIndex.clear();
|
|
500
1681
|
if (this.dbManager) {
|
|
501
|
-
await this.
|
|
1682
|
+
await this.runInOrderSQLiteSaveQueue(async () => {
|
|
1683
|
+
await this.dbManager.clear(INDEXDB_STORE_NAME);
|
|
1684
|
+
});
|
|
502
1685
|
}
|
|
503
1686
|
this.setStorageItem(ORDER_LAST_FULL_FETCH_AT_STORAGE_KEY, '');
|
|
504
|
-
this.
|
|
1687
|
+
this.emitOrdersChanged([], 'clear');
|
|
505
1688
|
}
|
|
506
1689
|
}
|
|
507
1690
|
exports.OrderModule = OrderModule;
|