@pisell/pisellos 0.0.567 → 0.0.569
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/core/index.js +6 -9
- package/dist/model/strategy/adapter/promotion/evaluator.js +3 -2
- package/dist/model/strategy/adapter/walletPass/evaluator.js +0 -2
- package/dist/model/strategy/adapter/walletPass/example.js +3 -26
- package/dist/model/strategy/adapter/walletPass/utils.js +7 -7
- package/dist/model/strategy/strategy-example.js +5 -19
- 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/index.js +0 -3
- package/dist/modules/Cart/utils/cartProduct.js +26 -6
- 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 +2 -0
- package/dist/modules/Date/index.js +1 -5
- package/dist/modules/Discount/index.d.ts +2 -1
- package/dist/modules/Discount/index.js +37 -12
- package/dist/modules/Discount/types.d.ts +1 -0
- package/dist/modules/OpenData/index.d.ts +8 -0
- package/dist/modules/OpenData/index.js +35 -17
- package/dist/modules/Order/index.d.ts +449 -24
- package/dist/modules/Order/index.js +5437 -608
- 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 +508 -874
- package/dist/modules/Payment/types.d.ts +40 -16
- package/dist/modules/Payment/utils.d.ts +1 -1
- package/dist/modules/Payment/utils.js +4 -3
- package/dist/modules/Payment/walletpass.js +48 -34
- 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 +219 -114
- package/dist/modules/Rules/types.d.ts +14 -3
- 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/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/types.d.ts +2 -0
- package/dist/modules/Schedule/utils.js +0 -1
- package/dist/modules/Step/index.js +0 -1
- 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 +1 -0
- package/dist/plugins/request.js +1 -4
- package/dist/plugins/window.js +2 -6
- package/dist/server/index.d.ts +462 -8
- package/dist/server/index.js +6137 -714
- 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/index.js +32 -41
- 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 +76 -5
- 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 +1397 -540
- package/dist/server/modules/products/types.d.ts +34 -1
- 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 +289 -86
- package/dist/server/modules/resource/index.d.ts +2 -3
- package/dist/server/modules/resource/index.js +105 -84
- package/dist/server/modules/schedule/index.d.ts +6 -4
- package/dist/server/modules/schedule/index.js +137 -114
- package/dist/server/test.json +713 -0
- package/dist/server/types.d.ts +2 -0
- package/dist/server/utils/product.js +2 -3
- 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.js +7 -59
- package/dist/solution/BookingByStep/utils/capacity.js +1 -11
- package/dist/solution/BookingByStep/utils/resources.js +1 -3
- package/dist/solution/BookingByStep/utils/timeslots.js +2 -43
- package/dist/solution/BookingTicket/index.d.ts +393 -24
- package/dist/solution/BookingTicket/index.js +2506 -445
- 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 +36 -34
- package/dist/solution/BookingTicket/utils/scan/index.d.ts +5 -0
- package/dist/solution/BookingTicket/utils/scan/index.js +26 -18
- 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/BuyTickets/index.js +9 -12
- package/dist/solution/Checkout/index.d.ts +1 -0
- package/dist/solution/Checkout/index.js +199 -277
- package/dist/solution/Checkout/utils/index.js +8 -20
- package/dist/solution/RegisterAndLogin/config.js +2 -10
- package/dist/solution/RegisterAndLogin/index.js +30 -76
- package/dist/solution/Sales/index.d.ts +50 -5
- package/dist/solution/Sales/index.js +484 -92
- package/dist/solution/Sales/types.d.ts +26 -2
- package/dist/solution/ScanOrder/index.d.ts +27 -21
- package/dist/solution/ScanOrder/index.js +509 -454
- package/dist/solution/ScanOrder/types.d.ts +33 -11
- 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 +6 -11
- 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 +34 -20
- package/dist/solution/VenueBooking/index.js +630 -505
- package/dist/solution/VenueBooking/sales-section-4-annotated.json +0 -2
- 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 +11 -1
- package/dist/utils/task.js +15 -18
- package/dist/utils/watch.js +0 -1
- package/lib/apis/picoding.js +2 -0
- package/lib/core/index.js +130 -131
- package/lib/effects/index.js +46 -57
- package/lib/index.js +92 -53
- package/lib/model/index.js +14 -21
- package/lib/model/strategy/adapter/dataVariant/adapter.js +38 -50
- package/lib/model/strategy/adapter/dataVariant/evaluator.js +162 -236
- package/lib/model/strategy/adapter/dataVariant/examples.js +229 -237
- package/lib/model/strategy/adapter/dataVariant/index.js +39 -34
- package/lib/model/strategy/adapter/dataVariant/type.js +57 -28
- package/lib/model/strategy/adapter/index.js +100 -64
- package/lib/model/strategy/adapter/itemRule/adapter.js +156 -144
- package/lib/model/strategy/adapter/itemRule/evaluator.js +36 -48
- package/lib/model/strategy/adapter/itemRule/examples.js +295 -225
- package/lib/model/strategy/adapter/itemRule/index.js +83 -57
- package/lib/model/strategy/adapter/itemRule/type.js +97 -36
- package/lib/model/strategy/adapter/promotion/adapter.js +84 -63
- package/lib/model/strategy/adapter/promotion/evaluator.js +383 -232
- package/lib/model/strategy/adapter/promotion/examples.js +139 -159
- package/lib/model/strategy/adapter/promotion/index.js +1 -49
- package/lib/model/strategy/adapter/promotion/type.js +199 -35
- package/lib/model/strategy/adapter/type.js +4 -16
- package/lib/model/strategy/adapter/walletPass/evaluator.js +180 -152
- package/lib/model/strategy/adapter/walletPass/example.js +217 -190
- package/lib/model/strategy/adapter/walletPass/index.js +75 -51
- package/lib/model/strategy/adapter/walletPass/locales.js +36 -58
- package/lib/model/strategy/adapter/walletPass/type.js +4 -16
- package/lib/model/strategy/adapter/walletPass/utils.js +654 -402
- package/lib/model/strategy/engine.js +270 -168
- package/lib/model/strategy/index.js +49 -34
- package/lib/model/strategy/strategy-example.js +243 -239
- package/lib/model/strategy/type.js +100 -40
- package/lib/modules/Account/index.js +39 -53
- package/lib/modules/Account/types.js +20 -33
- package/lib/modules/AccountList/index.js +116 -154
- package/lib/modules/AccountList/types.js +30 -31
- package/lib/modules/AccountList/utils.js +18 -30
- package/lib/modules/BaseModule.d.ts +2 -2
- package/lib/modules/BaseModule.js +26 -49
- 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/index.js +170 -124
- package/lib/modules/Cart/types.js +46 -51
- package/lib/modules/Cart/utils/cartAccount.js +40 -39
- package/lib/modules/Cart/utils/cartDate.js +56 -61
- package/lib/modules/Cart/utils/cartDiscount.js +28 -33
- package/lib/modules/Cart/utils/cartNote.js +27 -32
- package/lib/modules/Cart/utils/cartProduct.js +331 -244
- package/lib/modules/Cart/utils/cartRelationForms.js +29 -35
- package/lib/modules/Cart/utils/cartResource.js +72 -78
- package/lib/modules/Cart/utils/changePrice.js +37 -67
- package/lib/modules/Cart/utils/index.js +106 -48
- package/lib/modules/Customer/constants.js +13 -34
- package/lib/modules/Customer/index.d.ts +20 -0
- package/lib/modules/Customer/index.js +270 -154
- package/lib/modules/Customer/types.d.ts +2 -0
- package/lib/modules/Customer/types.js +38 -35
- package/lib/modules/Date/index.js +116 -115
- package/lib/modules/Date/types.js +16 -28
- package/lib/modules/Date/utils.js +174 -123
- package/lib/modules/Discount/index.d.ts +2 -1
- package/lib/modules/Discount/index.js +133 -113
- package/lib/modules/Discount/types.d.ts +1 -0
- package/lib/modules/Discount/types.js +9 -31
- package/lib/modules/Guests/index.js +30 -56
- package/lib/modules/Guests/types.js +23 -33
- package/lib/modules/Holder/index.js +209 -189
- package/lib/modules/Holder/types.js +4 -16
- package/lib/modules/Holder/utils.js +20 -49
- package/lib/modules/OpenData/index.d.ts +8 -0
- package/lib/modules/OpenData/index.js +81 -74
- package/lib/modules/OpenData/types.js +4 -16
- package/lib/modules/OpenData/utils.js +44 -78
- package/lib/modules/Order/index.d.ts +449 -24
- package/lib/modules/Order/index.js +3834 -489
- package/lib/modules/Order/types.d.ts +693 -88
- package/lib/modules/Order/types.js +78 -33
- 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 +1247 -345
- package/lib/modules/Payment/cash.js +20 -33
- package/lib/modules/Payment/cashRecommendationAlgorithm.js +145 -76
- package/lib/modules/Payment/eftpos.js +16 -30
- package/lib/modules/Payment/index.d.ts +10 -9
- package/lib/modules/Payment/index.js +542 -502
- package/lib/modules/Payment/mx51.js +1 -0
- package/lib/modules/Payment/types.d.ts +40 -16
- package/lib/modules/Payment/types.js +216 -107
- package/lib/modules/Payment/utils.d.ts +1 -1
- package/lib/modules/Payment/utils.js +53 -49
- package/lib/modules/Payment/walletpass.js +231 -203
- package/lib/modules/Product/index.js +51 -46
- package/lib/modules/Product/types.d.ts +29 -7
- package/lib/modules/Product/types.js +4 -16
- package/lib/modules/Product/utils.js +32 -33
- package/lib/modules/ProductList/index.d.ts +17 -13
- package/lib/modules/ProductList/index.js +160 -118
- package/lib/modules/ProductList/types.d.ts +17 -0
- package/lib/modules/ProductList/types.js +9 -31
- package/lib/modules/Quotation/index.d.ts +22 -2
- package/lib/modules/Quotation/index.js +123 -89
- package/lib/modules/Quotation/types.d.ts +19 -0
- package/lib/modules/Quotation/types.js +4 -16
- package/lib/modules/Resource/index.js +27 -59
- package/lib/modules/Resource/types.js +22 -32
- package/lib/modules/Resource/utils.js +30 -38
- 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 +1286 -894
- package/lib/modules/Rules/types.d.ts +14 -3
- package/lib/modules/Rules/types.js +17 -40
- package/lib/modules/SalesSummary/index.d.ts +23 -26
- package/lib/modules/SalesSummary/index.js +130 -71
- package/lib/modules/SalesSummary/types.d.ts +9 -1
- package/lib/modules/SalesSummary/types.js +4 -16
- package/lib/modules/SalesSummary/utils.d.ts +3 -22
- package/lib/modules/SalesSummary/utils.js +738 -250
- package/lib/modules/ScanOrderLogger/index.js +59 -79
- package/lib/modules/ScanOrderLogger/providers/feishu.js +54 -78
- package/lib/modules/ScanOrderLogger/providers/grafana.js +13 -36
- package/lib/modules/ScanOrderLogger/types.js +4 -16
- package/lib/modules/Schedule/getDateIsInSchedule.js +345 -171
- package/lib/modules/Schedule/index.d.ts +18 -5
- package/lib/modules/Schedule/index.js +139 -114
- package/lib/modules/Schedule/type.js +4 -16
- package/lib/modules/Schedule/types.d.ts +2 -0
- package/lib/modules/Schedule/types.js +4 -16
- package/lib/modules/Schedule/utils.js +433 -291
- package/lib/modules/Step/index.js +40 -52
- package/lib/modules/Step/tyeps.js +4 -16
- package/lib/modules/Summary/index.js +66 -70
- package/lib/modules/Summary/types.js +4 -16
- package/lib/modules/Summary/utils.js +782 -429
- 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 +245 -57
- package/lib/plugins/app.d.ts +2 -0
- package/lib/plugins/app.js +4 -16
- package/lib/plugins/index.js +36 -25
- package/lib/plugins/request.d.ts +1 -0
- package/lib/plugins/request.js +137 -126
- package/lib/plugins/shopStore.js +4 -16
- package/lib/plugins/user.js +4 -16
- package/lib/plugins/window.js +171 -179
- package/lib/server/index.d.ts +462 -8
- package/lib/server/index.js +4668 -713
- 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 +106 -59
- package/lib/server/modules/menu/index.js +146 -122
- package/lib/server/modules/menu/types.js +18 -31
- package/lib/server/modules/order/index.d.ts +207 -14
- package/lib/server/modules/order/index.js +1463 -316
- package/lib/server/modules/order/types.d.ts +76 -5
- package/lib/server/modules/order/types.js +117 -32
- package/lib/server/modules/order/utils/filterBookings.d.ts +7 -1
- package/lib/server/modules/order/utils/filterBookings.js +288 -146
- package/lib/server/modules/order/utils/filterOrders.js +318 -64
- 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 +969 -383
- package/lib/server/modules/products/types.d.ts +34 -1
- package/lib/server/modules/products/types.js +44 -34
- package/lib/server/modules/quotation/index.d.ts +39 -0
- package/lib/server/modules/quotation/index.js +220 -77
- package/lib/server/modules/quotation/types.js +21 -31
- package/lib/server/modules/resource/index.d.ts +2 -3
- package/lib/server/modules/resource/index.js +269 -214
- package/lib/server/modules/resource/types.js +42 -33
- package/lib/server/modules/schedule/index.d.ts +6 -4
- package/lib/server/modules/schedule/index.js +158 -132
- package/lib/server/modules/schedule/types.js +14 -21
- package/lib/server/modules/schedule/utils.js +334 -163
- package/lib/server/test.json +713 -0
- package/lib/server/types.d.ts +2 -0
- package/lib/server/types.js +4 -16
- package/lib/server/utils/index.js +25 -23
- package/lib/server/utils/product.js +197 -140
- package/lib/server/utils/schedule.js +34 -41
- package/lib/server/utils/small-ticket.d.ts +71 -0
- package/lib/server/utils/small-ticket.js +926 -0
- package/lib/server/utils/time.js +40 -49
- 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.js +1530 -1343
- package/lib/solution/BookingByStep/types.js +40 -99
- package/lib/solution/BookingByStep/utils/capacity.js +192 -160
- package/lib/solution/BookingByStep/utils/products.js +42 -52
- package/lib/solution/BookingByStep/utils/resources.js +527 -330
- package/lib/solution/BookingByStep/utils/stock.js +65 -44
- package/lib/solution/BookingByStep/utils/timeslots.js +129 -131
- package/lib/solution/BookingTicket/index.d.ts +393 -24
- package/lib/solution/BookingTicket/index.js +1459 -160
- package/lib/solution/BookingTicket/types.d.ts +191 -6
- package/lib/solution/BookingTicket/types.js +101 -66
- 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 +85 -80
- 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 +84 -80
- package/lib/solution/BookingTicket/utils/scan/index.d.ts +5 -0
- package/lib/solution/BookingTicket/utils/scan/index.js +103 -95
- package/lib/solution/BookingTicket/utils/scan/scanCache.js +90 -43
- 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/BuyTickets/index.js +67 -70
- package/lib/solution/BuyTickets/types.js +22 -38
- package/lib/solution/Checkout/index.d.ts +1 -0
- package/lib/solution/Checkout/index.js +1461 -1169
- package/lib/solution/Checkout/types.js +91 -61
- package/lib/solution/Checkout/utils/index.js +228 -159
- package/lib/solution/PlaceOrder/index.js +55 -0
- package/lib/solution/RegisterAndLogin/config.js +495 -470
- package/lib/solution/RegisterAndLogin/index.js +633 -630
- package/lib/solution/RegisterAndLogin/types.js +189 -76
- package/lib/solution/RegisterAndLogin/utils.js +183 -125
- package/lib/solution/Sales/index.d.ts +50 -5
- package/lib/solution/Sales/index.js +495 -220
- package/lib/solution/Sales/types.d.ts +26 -2
- package/lib/solution/Sales/types.js +27 -33
- package/lib/solution/ScanOrder/index.d.ts +27 -21
- package/lib/solution/ScanOrder/index.js +908 -904
- package/lib/solution/ScanOrder/types.d.ts +33 -11
- package/lib/solution/ScanOrder/types.js +33 -34
- package/lib/solution/ScanOrder/utils.d.ts +4 -1
- package/lib/solution/ScanOrder/utils.js +457 -344
- package/lib/solution/ShopDiscount/index.js +230 -236
- package/lib/solution/ShopDiscount/types.js +15 -37
- package/lib/solution/ShopDiscount/utils.js +309 -172
- 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 +34 -20
- package/lib/solution/VenueBooking/index.js +982 -950
- package/lib/solution/VenueBooking/sales-section-4-annotated.json +0 -2
- package/lib/solution/VenueBooking/types.js +20 -40
- package/lib/solution/VenueBooking/utils/dateSummary.js +22 -54
- package/lib/solution/VenueBooking/utils/resource.js +31 -54
- package/lib/solution/VenueBooking/utils/slotMerge.d.ts +6 -0
- package/lib/solution/VenueBooking/utils/slotMerge.js +96 -108
- package/lib/solution/VenueBooking/utils/smartPricing.js +112 -100
- package/lib/solution/VenueBooking/utils/timeSlot.js +133 -132
- package/lib/solution/VenueBooking/utils.js +130 -67
- package/lib/solution/index.d.ts +2 -0
- package/lib/solution/index.js +124 -37
- package/lib/store/createStore.js +32 -29
- package/lib/types/index.d.ts +11 -1
- package/lib/types/index.js +4 -16
- package/lib/utils/task.js +36 -36
- package/lib/utils/watch.js +81 -47
- package/package.json +2 -1
- package/dist/solution/Checkout/appointmentDemo.json +0 -1
- package/lib/solution/Checkout/appointmentDemo.json +0 -1
|
@@ -0,0 +1,2367 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
var _exportNames = {
|
|
7
|
+
BaseSalesImpl: true
|
|
8
|
+
};
|
|
9
|
+
exports.BaseSalesImpl = void 0;
|
|
10
|
+
var _BaseModule = require("../../modules/BaseModule");
|
|
11
|
+
var _types = require("./types");
|
|
12
|
+
Object.keys(_types).forEach(function (key) {
|
|
13
|
+
if (key === "default" || key === "__esModule") return;
|
|
14
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
15
|
+
if (key in exports && exports[key] === _types[key]) return;
|
|
16
|
+
Object.defineProperty(exports, key, {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () {
|
|
19
|
+
return _types[key];
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
var _Order = require("../../modules/Order");
|
|
24
|
+
var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
25
|
+
var _lodashEs = require("lodash-es");
|
|
26
|
+
var _types2 = require("../BookingByStep/types");
|
|
27
|
+
var _utils = require("../../modules/Order/utils");
|
|
28
|
+
var _orderCollectionIdentity = require("../../modules/Order/utils/orderCollectionIdentity");
|
|
29
|
+
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
30
|
+
var _Quotation = require("../../modules/Quotation");
|
|
31
|
+
var _transformBaseProductToOrderProduct = require("./utils/transformBaseProductToOrderProduct");
|
|
32
|
+
var _quotationPrice = require("./utils/quotationPrice");
|
|
33
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
34
|
+
const SERVER_ORDER_CHANGED_EVENT = 'order:onOrdersChanged';
|
|
35
|
+
function isBaseSalesManualProductDiscountProduct(product) {
|
|
36
|
+
return product?.metadata?.is_manual_discount === true || product?.metadata?.is_manual_discount === 1 || (product?.discount_list || []).some(item => item?.type === 'product');
|
|
37
|
+
}
|
|
38
|
+
function getBaseSalesProductUid(product) {
|
|
39
|
+
const uid = product?.metadata?.unique_identification_number || product?.unique_identification_number;
|
|
40
|
+
if (uid === undefined || uid === null || uid === '') return null;
|
|
41
|
+
return String(uid);
|
|
42
|
+
}
|
|
43
|
+
function preserveManualProductDiscountProducts(previousProducts, nextProducts) {
|
|
44
|
+
const previousByUid = new Map();
|
|
45
|
+
(previousProducts || []).forEach((product, index) => {
|
|
46
|
+
const uid = getBaseSalesProductUid(product);
|
|
47
|
+
if (uid) previousByUid.set(uid, product);
|
|
48
|
+
previousByUid.set(`__index__:${index}`, product);
|
|
49
|
+
});
|
|
50
|
+
return (nextProducts || []).map((product, index) => {
|
|
51
|
+
const uid = getBaseSalesProductUid(product);
|
|
52
|
+
const previous = (uid ? previousByUid.get(uid) : undefined) || previousByUid.get(`__index__:${index}`);
|
|
53
|
+
return previous && isBaseSalesManualProductDiscountProduct(previous) ? previous : product;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function isBaseSalesHistoricalDiscountEntry(discount) {
|
|
57
|
+
return Boolean(discount?.isEditMode || discount?.isDisabled || discount?.order_discount_id != null);
|
|
58
|
+
}
|
|
59
|
+
const BASE_SALES_PERSISTENT_ID_FIELD_BY_KIND = {
|
|
60
|
+
product: 'order_detail_id',
|
|
61
|
+
booking: 'schedule_event_id',
|
|
62
|
+
payment: 'order_payment_id'
|
|
63
|
+
};
|
|
64
|
+
function getBaseSalesOrderCollectionKind(key) {
|
|
65
|
+
if (key === 'products') return 'product';
|
|
66
|
+
if (key === 'bookings') return 'booking';
|
|
67
|
+
if (key === 'payments' || key === 'payment') return 'payment';
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
function isSameBaseSalesOrderRecord(left, right) {
|
|
71
|
+
if (!left || !right || typeof left !== 'object' || typeof right !== 'object') return false;
|
|
72
|
+
const leftIdentities = new Set([left.order_id, left.id, left.external_sale_number, left.order_number].filter(value => value !== undefined && value !== null && value !== '').map(String));
|
|
73
|
+
if (leftIdentities.size === 0) return false;
|
|
74
|
+
return [right.order_id, right.id, right.external_sale_number, right.order_number].some(value => value !== undefined && value !== null && value !== '' && leftIdentities.has(String(value)));
|
|
75
|
+
}
|
|
76
|
+
function isBaseSalesBlankValue(value) {
|
|
77
|
+
return value === undefined || value === null || value === '';
|
|
78
|
+
}
|
|
79
|
+
function mergeBaseSalesProductSnapshotFields(currentItem, loadedItem) {
|
|
80
|
+
const mergedItem = (0, _lodashEs.cloneDeep)(loadedItem);
|
|
81
|
+
if (isBaseSalesBlankValue(mergedItem.product_cover) && !isBaseSalesBlankValue(currentItem?.product_cover)) {
|
|
82
|
+
mergedItem.product_cover = currentItem.product_cover;
|
|
83
|
+
}
|
|
84
|
+
const currentHolderConfig = currentItem?.metadata?.holder_config;
|
|
85
|
+
if ((mergedItem.metadata?.holder_config === undefined || mergedItem.metadata?.holder_config === null) && currentHolderConfig !== undefined && currentHolderConfig !== null) {
|
|
86
|
+
mergedItem.metadata = {
|
|
87
|
+
...(mergedItem.metadata || {}),
|
|
88
|
+
holder_config: currentHolderConfig
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return mergedItem;
|
|
92
|
+
}
|
|
93
|
+
function mergeBaseSalesCollectionItem(kind, currentItem, incomingItem, uidRemaps) {
|
|
94
|
+
const currentReferenceUid = (0, _orderCollectionIdentity.getOrderCollectionReferenceUid)(kind, currentItem);
|
|
95
|
+
const incomingReferenceUid = (0, _orderCollectionIdentity.getOrderCollectionReferenceUid)(kind, incomingItem);
|
|
96
|
+
const identityMergedItem = (0, _orderCollectionIdentity.mergeOrderCollectionItems)(kind, currentItem, incomingItem);
|
|
97
|
+
const mergedUid = (0, _orderCollectionIdentity.getOrderCollectionUid)(kind, identityMergedItem);
|
|
98
|
+
if (mergedUid) {
|
|
99
|
+
for (const from of new Set([currentReferenceUid, incomingReferenceUid])) {
|
|
100
|
+
if (!from || from === mergedUid) continue;
|
|
101
|
+
uidRemaps.push({
|
|
102
|
+
kind,
|
|
103
|
+
from,
|
|
104
|
+
to: mergedUid,
|
|
105
|
+
persistentId: (0, _orderCollectionIdentity.getOrderCollectionPersistentId)(kind, incomingItem) || (0, _orderCollectionIdentity.getOrderCollectionPersistentId)(kind, currentItem)
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const mergedItem = mergedUid ? (0, _orderCollectionIdentity.setOrderCollectionUid)(kind, incomingItem, mergedUid) : (0, _lodashEs.cloneDeep)(incomingItem);
|
|
110
|
+
const persistentIdField = BASE_SALES_PERSISTENT_ID_FIELD_BY_KIND[kind];
|
|
111
|
+
if (isBaseSalesBlankValue(mergedItem[persistentIdField]) && !isBaseSalesBlankValue(identityMergedItem[persistentIdField])) {
|
|
112
|
+
mergedItem[persistentIdField] = identityMergedItem[persistentIdField];
|
|
113
|
+
}
|
|
114
|
+
return kind === 'product' ? mergeBaseSalesProductSnapshotFields(currentItem, mergedItem) : mergedItem;
|
|
115
|
+
}
|
|
116
|
+
function mergeBaseSalesOrderCollection(kind, currentValue, incomingValue, strategy, uidRemaps) {
|
|
117
|
+
const currentItems = (0, _orderCollectionIdentity.normalizeOrderCollection)(kind, currentValue, {
|
|
118
|
+
ensureUid: false
|
|
119
|
+
}).items;
|
|
120
|
+
const incomingItems = (0, _orderCollectionIdentity.normalizeOrderCollection)(kind, incomingValue, {
|
|
121
|
+
ensureUid: false
|
|
122
|
+
}).items;
|
|
123
|
+
if (strategy === 'authoritative-incoming') {
|
|
124
|
+
const result = (0, _lodashEs.cloneDeep)(incomingItems);
|
|
125
|
+
currentItems.forEach(currentItem => {
|
|
126
|
+
const incomingIndex = result.findIndex(incomingItem => (0, _orderCollectionIdentity.isSameOrderCollectionItem)(kind, currentItem, incomingItem));
|
|
127
|
+
if (incomingIndex !== -1) {
|
|
128
|
+
result[incomingIndex] = mergeBaseSalesCollectionItem(kind, currentItem, result[incomingIndex], uidRemaps);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 完整服务端快照负责已落库行;仅保留尚未提交的本地新行。
|
|
133
|
+
if (!(0, _orderCollectionIdentity.getOrderCollectionPersistentId)(kind, currentItem)) {
|
|
134
|
+
result.push((0, _lodashEs.cloneDeep)(currentItem));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
const result = (0, _lodashEs.cloneDeep)(currentItems);
|
|
140
|
+
incomingItems.forEach(incomingItem => {
|
|
141
|
+
const currentIndex = result.findIndex(currentItem => (0, _orderCollectionIdentity.isSameOrderCollectionItem)(kind, currentItem, incomingItem));
|
|
142
|
+
if (currentIndex !== -1) {
|
|
143
|
+
result[currentIndex] = mergeBaseSalesCollectionItem(kind, result[currentIndex], incomingItem, uidRemaps);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
result.push((0, _lodashEs.cloneDeep)(incomingItem));
|
|
147
|
+
});
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
function normalizeBaseSalesRecordCollections(sourceRecord, options) {
|
|
151
|
+
const normalizedRecord = (0, _orderCollectionIdentity.normalizeOrderCollections)((0, _lodashEs.cloneDeep)(sourceRecord || {}), options).order;
|
|
152
|
+
|
|
153
|
+
// 兼容旧调用方偶尔使用的单数 payment 字段。
|
|
154
|
+
if (Array.isArray(normalizedRecord.payment)) {
|
|
155
|
+
normalizedRecord.payment = (0, _orderCollectionIdentity.normalizeOrderCollection)('payment', normalizedRecord.payment, options).items;
|
|
156
|
+
}
|
|
157
|
+
return normalizedRecord;
|
|
158
|
+
}
|
|
159
|
+
function mergeSalesDetailRecordWithTempOrder(currentTempOrder, loadedRecord, strategy) {
|
|
160
|
+
const normalizedLoadedRecord = normalizeBaseSalesRecordCollections(loadedRecord || {}, {
|
|
161
|
+
ensureUid: false
|
|
162
|
+
});
|
|
163
|
+
if (!currentTempOrder) {
|
|
164
|
+
return normalizeBaseSalesRecordCollections(normalizedLoadedRecord);
|
|
165
|
+
}
|
|
166
|
+
const normalizedCurrentTempOrder = normalizeBaseSalesRecordCollections(currentTempOrder, {
|
|
167
|
+
ensureUid: false
|
|
168
|
+
});
|
|
169
|
+
const uidRemaps = [];
|
|
170
|
+
const mergedRecord = (0, _lodashEs.mergeWith)(normalizedCurrentTempOrder, normalizedLoadedRecord, (currentValue, loadedValue, key) => {
|
|
171
|
+
const collectionKind = getBaseSalesOrderCollectionKind(key);
|
|
172
|
+
if (collectionKind && Array.isArray(loadedValue)) {
|
|
173
|
+
return mergeBaseSalesOrderCollection(collectionKind, Array.isArray(currentValue) ? currentValue : [], loadedValue, strategy, uidRemaps);
|
|
174
|
+
}
|
|
175
|
+
if (Array.isArray(currentValue) && Array.isArray(loadedValue)) {
|
|
176
|
+
return [...(0, _lodashEs.cloneDeep)(currentValue), ...(0, _lodashEs.cloneDeep)(loadedValue)];
|
|
177
|
+
}
|
|
178
|
+
if (Array.isArray(loadedValue)) return (0, _lodashEs.cloneDeep)(loadedValue);
|
|
179
|
+
if (Array.isArray(currentValue) && loadedValue === undefined) {
|
|
180
|
+
return (0, _lodashEs.cloneDeep)(currentValue);
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
});
|
|
184
|
+
delete mergedRecord.request_unique_idempotency_token;
|
|
185
|
+
return normalizeBaseSalesRecordCollections((0, _orderCollectionIdentity.applyOrderCollectionUidRemaps)(mergedRecord, uidRemaps));
|
|
186
|
+
}
|
|
187
|
+
class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
188
|
+
defaultName = 'baseSales';
|
|
189
|
+
defaultVersion = '1.0.0';
|
|
190
|
+
isSolution = true;
|
|
191
|
+
initializeOptions = {};
|
|
192
|
+
logger; // LoggerManager 实例
|
|
193
|
+
appPlugin;
|
|
194
|
+
store = {
|
|
195
|
+
order: undefined
|
|
196
|
+
};
|
|
197
|
+
otherParams = {};
|
|
198
|
+
cacheId;
|
|
199
|
+
/**
|
|
200
|
+
* window / request 暴露为 public:现存外部工具(如 BookingTicket Scan)通过
|
|
201
|
+
* `solution.window` / `solution.request` 直接访问插件,保持原有契约不破坏。
|
|
202
|
+
*/
|
|
203
|
+
window;
|
|
204
|
+
request;
|
|
205
|
+
|
|
206
|
+
// 当前缓存中已加载的最新 SalesDetail;只读 getter 走它对外暴露详情数据。
|
|
207
|
+
currentSalesDetail = null;
|
|
208
|
+
discountConfigCacheKey = null;
|
|
209
|
+
hasManualDiscountSelection = false;
|
|
210
|
+
reusedSharedSubModuleNames = new Set();
|
|
211
|
+
paymentMethodsCache = [];
|
|
212
|
+
queuedAutoPaymentSync = false;
|
|
213
|
+
autoPaymentSyncFlushing = false;
|
|
214
|
+
autoPaymentSyncTimer = null;
|
|
215
|
+
salesDetailLoadInFlightCount = 0;
|
|
216
|
+
salesDetailLoadSequence = 0;
|
|
217
|
+
salesDetailHydrateQueue = Promise.resolve();
|
|
218
|
+
serverOrderChangeUnsubscribe = null;
|
|
219
|
+
serverOrderChangeHydrateInFlight = null;
|
|
220
|
+
queuedServerOrderChanges = [];
|
|
221
|
+
serverOrderChangeSyncEnabled = false;
|
|
222
|
+
constructor(name, version) {
|
|
223
|
+
super(name, version);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 子类可覆盖此方法以追加/收敛要注册的子模块。
|
|
228
|
+
* 默认包含通用销售模块:products / order / salesSummary / schedule / payment / quotation。
|
|
229
|
+
*/
|
|
230
|
+
getRegisteredModuleNames() {
|
|
231
|
+
return ['products', 'order', 'salesSummary', 'schedule', 'payment', 'quotation'];
|
|
232
|
+
}
|
|
233
|
+
getReusableSharedSubModule(moduleName) {
|
|
234
|
+
if (moduleName !== 'schedule' && moduleName !== 'quotation') return null;
|
|
235
|
+
return this.core?.getModule?.(moduleName) || null;
|
|
236
|
+
}
|
|
237
|
+
isScheduleListLoaded(scheduleModule) {
|
|
238
|
+
if (!scheduleModule) return false;
|
|
239
|
+
if (typeof scheduleModule.isScheduleListLoaded === 'function') {
|
|
240
|
+
return scheduleModule.isScheduleListLoaded();
|
|
241
|
+
}
|
|
242
|
+
if (typeof scheduleModule.getScheduleList === 'function') {
|
|
243
|
+
return Array.isArray(scheduleModule.getScheduleList());
|
|
244
|
+
}
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* 记录信息日志
|
|
250
|
+
*/
|
|
251
|
+
logInfo(title, metadata) {
|
|
252
|
+
if (this.logger) {
|
|
253
|
+
const tempOrder = this.getTempOrder();
|
|
254
|
+
this.logger.addLog({
|
|
255
|
+
type: 'info',
|
|
256
|
+
title: `[BaseSales] ${tempOrder?.order_id} ${tempOrder?.external_sale_number} ${title}`,
|
|
257
|
+
metadata: metadata || {}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 记录警告日志
|
|
264
|
+
*/
|
|
265
|
+
logWarning(title, metadata) {
|
|
266
|
+
if (this.logger) {
|
|
267
|
+
const tempOrder = this.getTempOrder();
|
|
268
|
+
this.logger.addLog({
|
|
269
|
+
type: 'warning',
|
|
270
|
+
title: `[BaseSales] ${tempOrder?.order_id} ${tempOrder?.external_sale_number} ${title}`,
|
|
271
|
+
metadata: metadata || {}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* 记录错误日志
|
|
278
|
+
*/
|
|
279
|
+
logError(title, metadata) {
|
|
280
|
+
if (this.logger) {
|
|
281
|
+
const tempOrder = this.getTempOrder();
|
|
282
|
+
this.logger.addLog({
|
|
283
|
+
type: 'error',
|
|
284
|
+
title: `[BaseSales] ${tempOrder?.order_id} ${tempOrder?.external_sale_number} ${title}`,
|
|
285
|
+
metadata: metadata || {}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
isCurrentSalesOrderRecord(order) {
|
|
290
|
+
const currentTempOrder = this.store.order?.getTempOrder?.();
|
|
291
|
+
const currentRecords = [currentTempOrder, this.currentSalesDetail].filter(Boolean);
|
|
292
|
+
return currentRecords.some(record => isSameBaseSalesOrderRecord(record, order));
|
|
293
|
+
}
|
|
294
|
+
setServerOrderChangeSyncEnabled(enabled) {
|
|
295
|
+
this.serverOrderChangeSyncEnabled = enabled;
|
|
296
|
+
if (!enabled) this.queuedServerOrderChanges = [];
|
|
297
|
+
}
|
|
298
|
+
queueLatestServerOrderChange(order) {
|
|
299
|
+
const snapshot = (0, _lodashEs.cloneDeep)(order);
|
|
300
|
+
const existingIndex = this.queuedServerOrderChanges.findIndex(queued => isSameBaseSalesOrderRecord(queued, snapshot));
|
|
301
|
+
if (existingIndex >= 0) {
|
|
302
|
+
this.queuedServerOrderChanges[existingIndex] = snapshot;
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
this.queuedServerOrderChanges.push(snapshot);
|
|
306
|
+
}
|
|
307
|
+
async drainQueuedServerOrderChanges() {
|
|
308
|
+
if (!this.serverOrderChangeSyncEnabled || this.salesDetailLoadInFlightCount > 0 || this.serverOrderChangeHydrateInFlight || this.queuedServerOrderChanges.length === 0) return;
|
|
309
|
+
const changedOrders = this.queuedServerOrderChanges;
|
|
310
|
+
this.queuedServerOrderChanges = [];
|
|
311
|
+
await this.syncCurrentSalesDetailFromServerOrderChange({
|
|
312
|
+
changedOrders
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
async syncCurrentSalesDetailFromServerOrderChange(payload) {
|
|
316
|
+
const changedOrders = Array.isArray(payload) ? payload : Array.isArray(payload?.changedOrders) ? payload.changedOrders : payload?.data && typeof payload.data === 'object' ? [payload.data] : [];
|
|
317
|
+
if (!this.serverOrderChangeSyncEnabled) return;
|
|
318
|
+
|
|
319
|
+
// 显式详情加载期间 current 可能仍是上一单,先按订单身份 latest-only 缓存;
|
|
320
|
+
// load 完成后再用新的 current 过滤,避免恰好到达的最新支付快照丢失。
|
|
321
|
+
if (this.salesDetailLoadInFlightCount > 0) {
|
|
322
|
+
changedOrders.forEach(order => this.queueLatestServerOrderChange(order));
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const changedOrder = changedOrders.find(order => this.isCurrentSalesOrderRecord(order));
|
|
326
|
+
if (!changedOrder) return;
|
|
327
|
+
if (this.serverOrderChangeHydrateInFlight) {
|
|
328
|
+
this.queueLatestServerOrderChange(changedOrder);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (!this.store.order) return;
|
|
332
|
+
this.serverOrderChangeHydrateInFlight = (async () => {
|
|
333
|
+
const currentTempOrder = this.store.order.getTempOrder?.();
|
|
334
|
+
const record = mergeSalesDetailRecordWithTempOrder(currentTempOrder, changedOrder, 'authoritative-incoming');
|
|
335
|
+
const sales = await this.store.order.hydrateTempOrderFromRecord(record, {
|
|
336
|
+
recalcOnHydrate: false
|
|
337
|
+
});
|
|
338
|
+
this.currentSalesDetail = sales;
|
|
339
|
+
await this.core.effects.emit(`${this.name}:onSalesOrderLoaded`, {
|
|
340
|
+
sales
|
|
341
|
+
});
|
|
342
|
+
})();
|
|
343
|
+
try {
|
|
344
|
+
await this.serverOrderChangeHydrateInFlight;
|
|
345
|
+
} finally {
|
|
346
|
+
this.serverOrderChangeHydrateInFlight = null;
|
|
347
|
+
await this.drainQueuedServerOrderChanges();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
registerServerOrderChangeSync() {
|
|
351
|
+
this.serverOrderChangeUnsubscribe?.();
|
|
352
|
+
const effects = this.core?.effects;
|
|
353
|
+
if (!effects || typeof effects.on !== 'function') return;
|
|
354
|
+
this.serverOrderChangeUnsubscribe = effects.on(SERVER_ORDER_CHANGED_EVENT, payload => {
|
|
355
|
+
return this.syncCurrentSalesDetailFromServerOrderChange(payload).catch(error => {
|
|
356
|
+
this.logError('sync sales detail from server order change failed', {
|
|
357
|
+
error: error instanceof Error ? error.message : String(error)
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
async hydrateLatestLoadedSalesDetail(record, loadSequence) {
|
|
363
|
+
let hydratedSales = null;
|
|
364
|
+
let skippedBeforeHydrate = false;
|
|
365
|
+
const hydrateTask = async () => {
|
|
366
|
+
// 网络返回顺序可能倒置;只有当前最新显式加载可以进入共享 tempOrder。
|
|
367
|
+
if (loadSequence !== this.salesDetailLoadSequence) {
|
|
368
|
+
skippedBeforeHydrate = true;
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
372
|
+
const sales = await this.store.order.hydrateTempOrderFromRecord(record, {
|
|
373
|
+
recalcOnHydrate: false
|
|
374
|
+
});
|
|
375
|
+
hydratedSales = sales;
|
|
376
|
+
|
|
377
|
+
// hydrate 期间可能又发起了更新请求;后续任务已串行排队,将负责最终状态。
|
|
378
|
+
if (loadSequence !== this.salesDetailLoadSequence) return;
|
|
379
|
+
this.currentSalesDetail = sales;
|
|
380
|
+
await this.core.effects.emit(`${this.name}:onSalesOrderLoaded`, {
|
|
381
|
+
sales
|
|
382
|
+
});
|
|
383
|
+
};
|
|
384
|
+
const queuedTask = this.salesDetailHydrateQueue.then(hydrateTask, hydrateTask);
|
|
385
|
+
this.salesDetailHydrateQueue = queuedTask.then(() => undefined, () => undefined);
|
|
386
|
+
await queuedTask;
|
|
387
|
+
if (skippedBeforeHydrate) return record;
|
|
388
|
+
return hydratedSales || this.currentSalesDetail || record;
|
|
389
|
+
}
|
|
390
|
+
hydrateOrderPaymentNames(payments) {
|
|
391
|
+
if (!payments.length) return [];
|
|
392
|
+
if (!this.paymentMethodsCache.length) {
|
|
393
|
+
return payments.map(payment => ({
|
|
394
|
+
...payment
|
|
395
|
+
}));
|
|
396
|
+
}
|
|
397
|
+
const paymentMethodById = new Map();
|
|
398
|
+
const paymentMethodByCode = new Map();
|
|
399
|
+
this.paymentMethodsCache.forEach(method => {
|
|
400
|
+
if (method?.id !== undefined && method?.id !== null) {
|
|
401
|
+
paymentMethodById.set(String(method.id), method);
|
|
402
|
+
}
|
|
403
|
+
if (method?.code) {
|
|
404
|
+
paymentMethodByCode.set(String(method.code), method);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
return payments.map(payment => {
|
|
408
|
+
const nextPayment = {
|
|
409
|
+
...payment
|
|
410
|
+
};
|
|
411
|
+
if (nextPayment.name) return nextPayment;
|
|
412
|
+
const paymentMethod = paymentMethodById.get(String(nextPayment.custom_payment_id)) || paymentMethodByCode.get(String(nextPayment.code));
|
|
413
|
+
if (!paymentMethod?.name) return nextPayment;
|
|
414
|
+
return {
|
|
415
|
+
...nextPayment,
|
|
416
|
+
name: paymentMethod.name
|
|
417
|
+
};
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
get payment() {
|
|
421
|
+
return this.store.payment;
|
|
422
|
+
}
|
|
423
|
+
getCurrentOrderCustomerId() {
|
|
424
|
+
const tempOrder = this.store.order?.getTempOrder?.();
|
|
425
|
+
const customerId = tempOrder?.customer_id;
|
|
426
|
+
if (customerId === null || customerId === undefined) return undefined;
|
|
427
|
+
return customerId;
|
|
428
|
+
}
|
|
429
|
+
normalizePriceContextCustomerId(customerId) {
|
|
430
|
+
if (customerId === undefined || customerId === null || customerId === '') return null;
|
|
431
|
+
return String(customerId);
|
|
432
|
+
}
|
|
433
|
+
applyQuotationScheduleResolver(quotation) {
|
|
434
|
+
const scheduleModule = this.store.schedule;
|
|
435
|
+
if (!scheduleModule) return;
|
|
436
|
+
const scheduleById = new Map();
|
|
437
|
+
const scheduleList = typeof scheduleModule.getScheduleList === 'function' ? scheduleModule.getScheduleList() : [];
|
|
438
|
+
if (Array.isArray(scheduleList)) {
|
|
439
|
+
scheduleList.forEach(schedule => {
|
|
440
|
+
if (schedule?.id !== undefined && schedule?.id !== null) {
|
|
441
|
+
scheduleById.set(String(schedule.id), schedule);
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
quotation.setScheduleResolver(id => {
|
|
446
|
+
const scheduleId = String(id);
|
|
447
|
+
if (scheduleById.has(scheduleId)) return scheduleById.get(scheduleId);
|
|
448
|
+
const schedules = scheduleModule.getScheduleListByIds?.([id]) || scheduleModule.getScheduleByIds?.([id]) || [];
|
|
449
|
+
if (schedules[0]) scheduleById.set(scheduleId, schedules[0]);
|
|
450
|
+
return schedules[0];
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
async loadQuotationForPriceQuery(params) {
|
|
454
|
+
if (!params.quotation) return;
|
|
455
|
+
this.applyQuotationScheduleResolver(params.quotation);
|
|
456
|
+
await params.quotation.loadQuotations({
|
|
457
|
+
channel: params.channel || this.otherParams?.channel,
|
|
458
|
+
customer_id: params.customer_id
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
getPriceQueryProductId(product) {
|
|
462
|
+
const productId = Number(product?.product_id ?? product?.id);
|
|
463
|
+
if (!Number.isFinite(productId)) return null;
|
|
464
|
+
return productId;
|
|
465
|
+
}
|
|
466
|
+
getPriceQuerySchedule(params) {
|
|
467
|
+
const booking = params.booking || {};
|
|
468
|
+
if (typeof booking.start_date === 'string' && booking.start_date.trim()) {
|
|
469
|
+
const scheduleDate = booking.start_date.trim();
|
|
470
|
+
const startTime = typeof booking.start_time === 'string' && booking.start_time.trim() ? booking.start_time.trim() : '00:00:00';
|
|
471
|
+
const datetime = (0, _dayjs.default)(`${scheduleDate} ${startTime}`);
|
|
472
|
+
return {
|
|
473
|
+
schedule_date: scheduleDate,
|
|
474
|
+
schedule_datetime: datetime.isValid() ? datetime.format('YYYY-MM-DD HH:mm:ss') : `${scheduleDate} ${startTime}`
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
if (typeof params.datetime === 'string' && params.datetime.trim()) {
|
|
478
|
+
const datetime = (0, _dayjs.default)(params.datetime.trim());
|
|
479
|
+
if (datetime.isValid()) {
|
|
480
|
+
return {
|
|
481
|
+
schedule_date: datetime.format('YYYY-MM-DD'),
|
|
482
|
+
schedule_datetime: datetime.format('YYYY-MM-DD HH:mm:ss')
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const now = (0, _dayjs.default)();
|
|
487
|
+
return {
|
|
488
|
+
schedule_date: now.format('YYYY-MM-DD'),
|
|
489
|
+
schedule_datetime: now.format('YYYY-MM-DD HH:mm:ss')
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
async loadProductsForPriceQuery(params) {
|
|
493
|
+
const ids = Array.from(new Set(params.ids.filter(id => Number.isFinite(id))));
|
|
494
|
+
const productsById = new Map();
|
|
495
|
+
if (!ids.length || !this.request) return productsById;
|
|
496
|
+
try {
|
|
497
|
+
const response = await this.request.post('/product/query', {
|
|
498
|
+
ids,
|
|
499
|
+
open_quotation: 1,
|
|
500
|
+
open_bundle: 1,
|
|
501
|
+
status: 'published',
|
|
502
|
+
num: 1,
|
|
503
|
+
skip: 1,
|
|
504
|
+
customer_id: params.customerId,
|
|
505
|
+
schedule_date: params.schedule.schedule_date,
|
|
506
|
+
schedule_datetime: params.schedule.schedule_datetime,
|
|
507
|
+
application_code: params.channel || this.otherParams?.channel
|
|
508
|
+
}, {
|
|
509
|
+
osServer: true,
|
|
510
|
+
customToast: () => {}
|
|
511
|
+
});
|
|
512
|
+
const list = response?.data?.list || response?.list || [];
|
|
513
|
+
if (!Array.isArray(list)) return productsById;
|
|
514
|
+
list.forEach(item => {
|
|
515
|
+
const productId = Number(item?.id ?? item?.product_id);
|
|
516
|
+
if (Number.isFinite(productId)) productsById.set(productId, item);
|
|
517
|
+
});
|
|
518
|
+
return productsById;
|
|
519
|
+
} catch (error) {
|
|
520
|
+
this.logWarning('loadProductsForPriceQuery: 商品批量重查失败,使用入参 fallback', {
|
|
521
|
+
ids,
|
|
522
|
+
error: error instanceof Error ? error.message : String(error)
|
|
523
|
+
});
|
|
524
|
+
return productsById;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
async loadProductForPriceQuery(params, customerId) {
|
|
528
|
+
const product = params.product || {};
|
|
529
|
+
const productId = this.getPriceQueryProductId(product);
|
|
530
|
+
if (productId === null || !this.request) return null;
|
|
531
|
+
const schedule = this.getPriceQuerySchedule(params);
|
|
532
|
+
const productsById = await this.loadProductsForPriceQuery({
|
|
533
|
+
ids: [productId],
|
|
534
|
+
schedule,
|
|
535
|
+
customerId,
|
|
536
|
+
channel: params.channel
|
|
537
|
+
});
|
|
538
|
+
return productsById.get(productId) || null;
|
|
539
|
+
}
|
|
540
|
+
getAuthoritativeBundleItems(product) {
|
|
541
|
+
if (Array.isArray(product?.product_bundle)) return product.product_bundle;
|
|
542
|
+
const groups = Array.isArray(product?.bundle_group) ? product.bundle_group : Array.isArray(product?.bundleGroup) ? product.bundleGroup : [];
|
|
543
|
+
return groups.flatMap(group => {
|
|
544
|
+
const items = Array.isArray(group?.bundle_item) ? group.bundle_item : Array.isArray(group?.bundleItem) ? group.bundleItem : [];
|
|
545
|
+
return items.map(item => ({
|
|
546
|
+
...item,
|
|
547
|
+
group_id: item?.group_id ?? group?.id,
|
|
548
|
+
bundle_group_id: item?.bundle_group_id ?? item?.group_id ?? group?.id
|
|
549
|
+
}));
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
findAuthoritativeBundleItem(bundle, candidates) {
|
|
553
|
+
const bundleId = bundle.bundle_id ?? bundle.id;
|
|
554
|
+
if (bundleId != null) {
|
|
555
|
+
const matchedById = candidates.find(item => String(item?.bundle_id ?? item?.id) === String(bundleId));
|
|
556
|
+
if (matchedById) return matchedById;
|
|
557
|
+
}
|
|
558
|
+
const productId = bundle.bundle_product_id ?? bundle._bundle_product_id;
|
|
559
|
+
const groupId = bundle.bundle_group_id ?? bundle.group_id;
|
|
560
|
+
return candidates.find(item => {
|
|
561
|
+
const itemProductId = item?.bundle_product_id ?? item?._bundle_product_id;
|
|
562
|
+
const itemGroupId = item?.bundle_group_id ?? item?.group_id;
|
|
563
|
+
if (productId == null || String(itemProductId) !== String(productId)) return false;
|
|
564
|
+
if (groupId == null) return true;
|
|
565
|
+
return String(itemGroupId) === String(groupId);
|
|
566
|
+
}) || null;
|
|
567
|
+
}
|
|
568
|
+
getAuthoritativeBundleUnitPrice(bundle) {
|
|
569
|
+
return bundle.price ?? bundle.bundle_selling_price ?? bundle.custom_price ?? bundle.product_price ?? bundle.base_price;
|
|
570
|
+
}
|
|
571
|
+
mergeProductForPriceQuery(product, authoritativeProduct) {
|
|
572
|
+
if (!authoritativeProduct) return product;
|
|
573
|
+
const selectedBundles = Array.isArray(product.product_bundle) ? product.product_bundle : [];
|
|
574
|
+
const authoritativeBundles = this.getAuthoritativeBundleItems(authoritativeProduct);
|
|
575
|
+
const productBundle = selectedBundles.map(bundle => {
|
|
576
|
+
const authoritativeBundle = this.findAuthoritativeBundleItem(bundle, authoritativeBundles);
|
|
577
|
+
const unitPrice = authoritativeBundle ? this.getAuthoritativeBundleUnitPrice(authoritativeBundle) : undefined;
|
|
578
|
+
if (unitPrice === undefined || unitPrice === null || unitPrice === '') return bundle;
|
|
579
|
+
return {
|
|
580
|
+
...bundle,
|
|
581
|
+
price: unitPrice,
|
|
582
|
+
bundle_selling_price: unitPrice,
|
|
583
|
+
base_price: authoritativeBundle?.base_price ?? bundle.base_price,
|
|
584
|
+
product_price: authoritativeBundle?.product_price ?? bundle.product_price
|
|
585
|
+
};
|
|
586
|
+
});
|
|
587
|
+
return {
|
|
588
|
+
...product,
|
|
589
|
+
...authoritativeProduct,
|
|
590
|
+
id: product.id ?? authoritativeProduct.id,
|
|
591
|
+
product_id: product.product_id ?? authoritativeProduct.product_id ?? authoritativeProduct.id,
|
|
592
|
+
product_variant_id: product.product_variant_id ?? authoritativeProduct.product_variant_id ?? 0,
|
|
593
|
+
num: product.num ?? product.quantity ?? authoritativeProduct.num,
|
|
594
|
+
quantity: product.quantity ?? product.num ?? authoritativeProduct.quantity,
|
|
595
|
+
product_sku: (() => {
|
|
596
|
+
const authoritativeSku = (0, _utils.ensureProductSku)(authoritativeProduct);
|
|
597
|
+
const productSku = (0, _utils.ensureProductSku)(product);
|
|
598
|
+
const hasProductSku = product.product_sku && typeof product.product_sku === 'object';
|
|
599
|
+
return {
|
|
600
|
+
...authoritativeSku,
|
|
601
|
+
...productSku,
|
|
602
|
+
option: hasProductSku ? productSku.option : authoritativeSku.option
|
|
603
|
+
};
|
|
604
|
+
})(),
|
|
605
|
+
product_bundle: productBundle,
|
|
606
|
+
metadata: product.metadata ?? authoritativeProduct.metadata
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
getSubmitOrderSalesChannel() {
|
|
610
|
+
return this.otherParams?.channel;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* 工厂入口:根据子模块名实例化对应模块。
|
|
615
|
+
* 默认使用 BookingByStep 公共 `createModule`。子类若需引入额外类型(如 customer),
|
|
616
|
+
* 可覆盖此方法并把未识别的 name 委托给 `super.createSubModule(name)`。
|
|
617
|
+
*/
|
|
618
|
+
createSubModule(moduleName) {
|
|
619
|
+
if (moduleName === 'quotation') {
|
|
620
|
+
return new _Quotation.QuotationModule(`${this.name}_quotation`);
|
|
621
|
+
}
|
|
622
|
+
return (0, _types2.createModule)(moduleName, this.name);
|
|
623
|
+
}
|
|
624
|
+
getSubModuleHooks(moduleName) {
|
|
625
|
+
const optionHooks = this.initializeOptions.hooks || {};
|
|
626
|
+
const otherHooks = this.otherParams.hooks || {};
|
|
627
|
+
if (moduleName === 'order') {
|
|
628
|
+
const directOrderHooks = optionHooks;
|
|
629
|
+
return optionHooks.order || this.otherParams.order?.hooks || otherHooks.order || (directOrderHooks.onBeforeMergeProductToOrder ? directOrderHooks : undefined);
|
|
630
|
+
}
|
|
631
|
+
return optionHooks[moduleName] || this.otherParams[moduleName]?.hooks || otherHooks[moduleName];
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* 构造传给 BaseSales 子模块的完整运行态参数,保证初始化和后续同步语义一致。
|
|
636
|
+
*
|
|
637
|
+
* @example
|
|
638
|
+
* const params = this.buildSubModuleOtherParams();
|
|
639
|
+
* this.core.registerModule(orderModule, { otherParams: params });
|
|
640
|
+
*/
|
|
641
|
+
buildSubModuleOtherParams() {
|
|
642
|
+
return {
|
|
643
|
+
...this.otherParams,
|
|
644
|
+
salesSummaryModuleName: `${this.name}_salesSummary`,
|
|
645
|
+
fatherModule: this.name,
|
|
646
|
+
openCache: this.cacheId ? true : false,
|
|
647
|
+
cacheId: this.cacheId
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* 将父级 otherParams 的变更显式同步给已注册的子模块。
|
|
653
|
+
*
|
|
654
|
+
* @example
|
|
655
|
+
* await this.syncOtherParamsToSubModules({ channel: 'pos' });
|
|
656
|
+
*/
|
|
657
|
+
async syncOtherParamsToSubModules(changedParams, {
|
|
658
|
+
cover = false
|
|
659
|
+
} = {}) {
|
|
660
|
+
const nextSubModuleOtherParams = this.buildSubModuleOtherParams();
|
|
661
|
+
await Promise.all(Object.entries(this.store).map(async ([moduleName, subModule]) => {
|
|
662
|
+
if (this.reusedSharedSubModuleNames.has(moduleName)) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const targetModule = subModule;
|
|
666
|
+
if (!targetModule || typeof targetModule.updateOtherParams !== 'function') {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
await targetModule.updateOtherParams(nextSubModuleOtherParams, {
|
|
670
|
+
changedParams,
|
|
671
|
+
cover
|
|
672
|
+
});
|
|
673
|
+
}));
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* 读取 sessionStorage[this.name][cacheId],作为子模块 initialState 的来源。
|
|
678
|
+
* sessionStorage 损坏时静默忽略,按空状态注册。
|
|
679
|
+
*/
|
|
680
|
+
readSessionCacheData() {
|
|
681
|
+
if (!this.cacheId || !this.window) return {};
|
|
682
|
+
try {
|
|
683
|
+
const sessionData = this.window.sessionStorage.getItem(this.name);
|
|
684
|
+
if (!sessionData) return {};
|
|
685
|
+
const data = JSON.parse(sessionData);
|
|
686
|
+
return data && data[this.cacheId] || {};
|
|
687
|
+
} catch {
|
|
688
|
+
return {};
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
getAppData(key) {
|
|
692
|
+
const getData = this.appPlugin.getData?.();
|
|
693
|
+
if (typeof getData === 'function') return getData(key);
|
|
694
|
+
const app = this.appPlugin.getApp();
|
|
695
|
+
const coreData = app?.models?.getStore?.().getDataByModel?.('core', 'core');
|
|
696
|
+
return coreData?.[key];
|
|
697
|
+
}
|
|
698
|
+
syncAppDataToTempOrder(tempOrder) {
|
|
699
|
+
const isPriceIncludeTax = this.getAppData('is_price_include_tax');
|
|
700
|
+
const taxCountryCode = this.getAppData('tax_country_code');
|
|
701
|
+
const currencyCode = this.getAppData('shop_currency_code');
|
|
702
|
+
const currencySymbol = this.getAppData('shop_symbol');
|
|
703
|
+
let isChanged = false;
|
|
704
|
+
if (isPriceIncludeTax !== undefined) {
|
|
705
|
+
const nextIsPriceIncludeTax = Number(isPriceIncludeTax);
|
|
706
|
+
if ((nextIsPriceIncludeTax === 0 || nextIsPriceIncludeTax === 1) && tempOrder.is_price_include_tax !== nextIsPriceIncludeTax) {
|
|
707
|
+
tempOrder.is_price_include_tax = nextIsPriceIncludeTax;
|
|
708
|
+
isChanged = true;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (taxCountryCode !== undefined) {
|
|
712
|
+
const nextTaxCountryCode = String(taxCountryCode || '');
|
|
713
|
+
if (tempOrder.tax_country_code !== nextTaxCountryCode) {
|
|
714
|
+
tempOrder.tax_country_code = nextTaxCountryCode;
|
|
715
|
+
isChanged = true;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (currencyCode !== undefined) {
|
|
719
|
+
const nextCurrencyCode = String(currencyCode || '');
|
|
720
|
+
if (tempOrder.currency_code !== nextCurrencyCode) {
|
|
721
|
+
tempOrder.currency_code = nextCurrencyCode;
|
|
722
|
+
isChanged = true;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (currencySymbol !== undefined) {
|
|
726
|
+
const nextCurrencySymbol = String(currencySymbol || '');
|
|
727
|
+
if (tempOrder.currency_symbol !== nextCurrencySymbol) {
|
|
728
|
+
tempOrder.currency_symbol = nextCurrencySymbol;
|
|
729
|
+
isChanged = true;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return isChanged;
|
|
733
|
+
}
|
|
734
|
+
async initialize(core, options = {}) {
|
|
735
|
+
this.core = core;
|
|
736
|
+
this.initializeOptions = options || {};
|
|
737
|
+
// this.store = { ...this.store, ...(options.store as Partial<BaseSalesState>) };
|
|
738
|
+
this.otherParams = options.otherParams || {};
|
|
739
|
+
this.cacheId = this.otherParams?.cacheId;
|
|
740
|
+
this.reusedSharedSubModuleNames.clear();
|
|
741
|
+
this.window = core.getPlugin('window');
|
|
742
|
+
this.request = core.getPlugin('request');
|
|
743
|
+
|
|
744
|
+
// 获取 logger 实例
|
|
745
|
+
this.appPlugin = core.getPlugin('app');
|
|
746
|
+
if (!this.appPlugin) {
|
|
747
|
+
// throw new Error('Checkout 解决方案需要 app 插件支持');
|
|
748
|
+
}
|
|
749
|
+
const app = this.appPlugin?.getApp?.();
|
|
750
|
+
this.logger = app?.logger;
|
|
751
|
+
const targetCacheData = this.readSessionCacheData();
|
|
752
|
+
const moduleNames = this.getRegisteredModuleNames();
|
|
753
|
+
moduleNames.forEach(step => {
|
|
754
|
+
const reusedModule = this.getReusableSharedSubModule(step);
|
|
755
|
+
if (reusedModule) {
|
|
756
|
+
this.store[step] = reusedModule;
|
|
757
|
+
this.reusedSharedSubModuleNames.add(step);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const targetModule = this.createSubModule(step);
|
|
761
|
+
if (!targetModule) {
|
|
762
|
+
throw new Error(`模块 ${step} 不存在`);
|
|
763
|
+
}
|
|
764
|
+
const hooks = this.getSubModuleHooks(step);
|
|
765
|
+
this.store[step] = targetModule;
|
|
766
|
+
this.core.registerModule(targetModule, {
|
|
767
|
+
initialState: targetCacheData?.[targetModule.name] || {},
|
|
768
|
+
...(hooks ? {
|
|
769
|
+
hooks
|
|
770
|
+
} : {}),
|
|
771
|
+
otherParams: this.buildSubModuleOtherParams()
|
|
772
|
+
});
|
|
773
|
+
});
|
|
774
|
+
if (this.store.schedule && !this.isScheduleListLoaded(this.store.schedule)) {
|
|
775
|
+
await this.store.schedule.loadAllSchedule();
|
|
776
|
+
}
|
|
777
|
+
if (this.store.order && typeof this.otherParams?.enableTempOrderPersist === 'boolean') {
|
|
778
|
+
this.store.order.setEnableTempOrderPersist(this.otherParams.enableTempOrderPersist);
|
|
779
|
+
}
|
|
780
|
+
await this.getPaymentMethodsAsync();
|
|
781
|
+
this.registerServerOrderChangeSync();
|
|
782
|
+
this.core.effects.emit(`${this.name}:onInited`, {});
|
|
783
|
+
}
|
|
784
|
+
async destroy() {
|
|
785
|
+
this.serverOrderChangeUnsubscribe?.();
|
|
786
|
+
this.serverOrderChangeUnsubscribe = null;
|
|
787
|
+
Object.keys(this.store).forEach(key => {
|
|
788
|
+
if (this.reusedSharedSubModuleNames.has(key)) return;
|
|
789
|
+
const sub = this.store[key];
|
|
790
|
+
if (sub && typeof sub.destroy === 'function') {
|
|
791
|
+
try {
|
|
792
|
+
sub.destroy();
|
|
793
|
+
} catch (error) {
|
|
794
|
+
console.warn(`[BaseSales] 子模块 ${key} 销毁失败`, error);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
this.currentSalesDetail = null;
|
|
799
|
+
this.queuedServerOrderChanges = [];
|
|
800
|
+
this.salesDetailLoadSequence += 1;
|
|
801
|
+
await this.core.effects.emit(`${this.name}:onDestroy`, {});
|
|
802
|
+
super.destroy();
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* 加载销售订单到统一 tempOrder 工作区。
|
|
807
|
+
* 统一通过 OrderModule 的 sales detail lookup 加载订单详情。
|
|
808
|
+
*
|
|
809
|
+
* 子类可在 `super.loadSalesDetail` 之后基于返回的 `sales` 做业务侧装配
|
|
810
|
+
* (比如把 `sales.customer` 写入 CustomerModule、emit 业务 hook 等)。
|
|
811
|
+
*/
|
|
812
|
+
async loadSalesDetail(orderIdOrParams) {
|
|
813
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
814
|
+
const loadSequence = ++this.salesDetailLoadSequence;
|
|
815
|
+
this.salesDetailLoadInFlightCount += 1;
|
|
816
|
+
try {
|
|
817
|
+
const params = typeof orderIdOrParams === 'object' ? orderIdOrParams : {
|
|
818
|
+
orderId: orderIdOrParams
|
|
819
|
+
};
|
|
820
|
+
const externalSaleNumber = params.externalSaleNumber || params.external_sale_number;
|
|
821
|
+
const preloadedSalesDetail = params.preloadedSalesDetail;
|
|
822
|
+
const lookup = params.orderId ?? externalSaleNumber ?? params.orderNumber ?? preloadedSalesDetail?.order_id ?? preloadedSalesDetail?.external_sale_number ?? preloadedSalesDetail?.order_number;
|
|
823
|
+
if (!preloadedSalesDetail && (lookup === undefined || lookup === null || lookup === '')) {
|
|
824
|
+
throw new Error('加载销售详情需要 orderId、externalSaleNumber 或 orderNumber');
|
|
825
|
+
}
|
|
826
|
+
// GlobalSearch 等调用方已拿到完整详情时,直接进入查询成功后的 hydrate 流程,避免按 id 二次请求。
|
|
827
|
+
const loadedRecord = preloadedSalesDetail ?? (await this.store.order.getSalesOrderByLookup({
|
|
828
|
+
lookup,
|
|
829
|
+
forceRemote: params.forceRemote
|
|
830
|
+
}));
|
|
831
|
+
if (!preloadedSalesDetail && (!loadedRecord || loadedRecord.code !== 200)) {
|
|
832
|
+
const message = loadedRecord && (loadedRecord.message || loadedRecord.msg) || `加载销售详情失败: ${lookup}`;
|
|
833
|
+
throw new Error(message);
|
|
834
|
+
}
|
|
835
|
+
const remoteRecord = preloadedSalesDetail ?? loadedRecord.data;
|
|
836
|
+
const currentTempOrder = params.merge ? this.store.order.getTempOrder() : null;
|
|
837
|
+
const record = params.merge ? mergeSalesDetailRecordWithTempOrder(currentTempOrder, remoteRecord, 'preserve-local') : remoteRecord;
|
|
838
|
+
return await this.hydrateLatestLoadedSalesDetail(record, loadSequence);
|
|
839
|
+
} finally {
|
|
840
|
+
this.salesDetailLoadInFlightCount = Math.max(0, this.salesDetailLoadInFlightCount - 1);
|
|
841
|
+
if (this.salesDetailLoadInFlightCount === 0) {
|
|
842
|
+
try {
|
|
843
|
+
await this.drainQueuedServerOrderChanges();
|
|
844
|
+
} catch (error) {
|
|
845
|
+
this.logError('drain queued server order changes failed', {
|
|
846
|
+
error: error instanceof Error ? error.message : String(error)
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/** 获取当前已加载的销售详情(只读快照)。未加载过则返回 null。 */
|
|
854
|
+
getSalesOrder() {
|
|
855
|
+
return this.currentSalesDetail;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/** 获取销售详情顶部业务字段(订单号/状态/金额等通用字段集合)。 */
|
|
859
|
+
getOrderHeader() {
|
|
860
|
+
if (!this.currentSalesDetail) return null;
|
|
861
|
+
return this.currentSalesDetail.header || this.currentSalesDetail;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/** 获取所有 booking(含 root + children),按服务端原始顺序输出。 */
|
|
865
|
+
getBookings() {
|
|
866
|
+
return this.currentSalesDetail?.bookings || [];
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/** 获取根 booking(is_next === 0 / is_root === 1),多个时取第一个。 */
|
|
870
|
+
getRootBooking() {
|
|
871
|
+
if (!this.currentSalesDetail) return null;
|
|
872
|
+
if (this.currentSalesDetail.rootBooking) return this.currentSalesDetail.rootBooking;
|
|
873
|
+
const bookings = this.currentSalesDetail.bookings || [];
|
|
874
|
+
return bookings.find(booking => {
|
|
875
|
+
const parentId = booking?.parent_id;
|
|
876
|
+
return parentId === 0 || parentId === null || parentId === undefined;
|
|
877
|
+
}) || null;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/** 按 product_uid 反查 booking 列表(用于商品行 ↔ booking 映射)。 */
|
|
881
|
+
getChildBookingsByProductUid(productUid) {
|
|
882
|
+
if (!this.currentSalesDetail || !productUid) return [];
|
|
883
|
+
const indexed = this.currentSalesDetail.bookingsByProductUid?.[productUid];
|
|
884
|
+
if (indexed) return indexed;
|
|
885
|
+
return (this.currentSalesDetail.bookings || []).filter(booking => {
|
|
886
|
+
const uid = booking?.product_uid || booking?.metadata?.product_uid;
|
|
887
|
+
return String(uid || '') === String(productUid);
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/** 获取支付列表(已支付/退款等明细)。 */
|
|
892
|
+
getPayments() {
|
|
893
|
+
return this.currentSalesDetail?.payments || [];
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/** 获取附加费用(运费/服务费/税费等)。 */
|
|
897
|
+
getSurcharges() {
|
|
898
|
+
return this.currentSalesDetail?.surcharges || [];
|
|
899
|
+
}
|
|
900
|
+
getTempOrder() {
|
|
901
|
+
const result = this.store.order?.getTempOrder() || null;
|
|
902
|
+
return result;
|
|
903
|
+
}
|
|
904
|
+
async replaceTempOrder(tempOrder) {
|
|
905
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
906
|
+
const nextTempOrder = await this.store.order.replaceTempOrder(tempOrder);
|
|
907
|
+
await this.effectsEmit('onTempOrderReplaced', {
|
|
908
|
+
tempOrder: nextTempOrder
|
|
909
|
+
});
|
|
910
|
+
return nextTempOrder;
|
|
911
|
+
}
|
|
912
|
+
getOrderIdentity() {
|
|
913
|
+
if (!this.store.order) return null;
|
|
914
|
+
return this.store.order.getOrderIdentity();
|
|
915
|
+
}
|
|
916
|
+
getOrderSnapshot() {
|
|
917
|
+
if (!this.store.order) return null;
|
|
918
|
+
return this.store.order.getOrderSnapshot();
|
|
919
|
+
}
|
|
920
|
+
getOrderSyncState() {
|
|
921
|
+
if (!this.store.order) return 'local';
|
|
922
|
+
return this.store.order.getOrderSyncState();
|
|
923
|
+
}
|
|
924
|
+
getOrderAmountSnapshot() {
|
|
925
|
+
if (!this.store.order) return null;
|
|
926
|
+
return this.store.order.getOrderAmountSnapshot();
|
|
927
|
+
}
|
|
928
|
+
getTempOrderNote() {
|
|
929
|
+
if (!this.store.order) return '';
|
|
930
|
+
return this.store.order.getTempOrderNote();
|
|
931
|
+
}
|
|
932
|
+
updateTempOrderNote(note, sync = false) {
|
|
933
|
+
try {
|
|
934
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
935
|
+
const result = this.store.order.updateTempOrderNote(note, sync);
|
|
936
|
+
return result;
|
|
937
|
+
} catch (error) {
|
|
938
|
+
throw error;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
updateRemoteOrderNote(orderId, note) {
|
|
942
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
943
|
+
this.store.order.syncTempOrderNoteToRemote(orderId, note);
|
|
944
|
+
}
|
|
945
|
+
updateTempOrderShopDiscount(amount) {
|
|
946
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
947
|
+
return this.store.order.updateTempOrderShopDiscount(amount);
|
|
948
|
+
}
|
|
949
|
+
updateTempOrderDepositAmount(amount) {
|
|
950
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
951
|
+
return this.store.order.updateTempOrderDepositAmount(amount);
|
|
952
|
+
}
|
|
953
|
+
updateTempOrderContactsInfo(contactsInfo) {
|
|
954
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
955
|
+
return this.store.order.updateTempOrderContactsInfo(contactsInfo);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/** @deprecated OrderModule 不再负责 tempOrder localStorage 持久化。 */
|
|
959
|
+
setEnableTempOrderPersist(enabled) {
|
|
960
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
961
|
+
this.store.order.setEnableTempOrderPersist(enabled);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/** 是否允许 saveDraft 写入 IndexedDB 草稿。 */
|
|
965
|
+
isTempOrderPersistEnabled() {
|
|
966
|
+
if (!this.store.order) return false;
|
|
967
|
+
return this.store.order.isTempOrderPersistEnabled();
|
|
968
|
+
}
|
|
969
|
+
ensureTempOrder() {
|
|
970
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
971
|
+
return this.store.order.ensureTempOrder();
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// 主动创建临时订单;若已存在则直接返回已有订单
|
|
975
|
+
async addNewOrder() {
|
|
976
|
+
try {
|
|
977
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
978
|
+
const tempOrder = await this.store.order.addNewOrder();
|
|
979
|
+
if (this.syncAppDataToTempOrder(tempOrder) && this.isTempOrderPersistEnabled()) {
|
|
980
|
+
this.store.order.persistTempOrder();
|
|
981
|
+
await this.store.order.saveDraft();
|
|
982
|
+
}
|
|
983
|
+
return tempOrder;
|
|
984
|
+
} catch (error) {
|
|
985
|
+
throw error;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
async saveDraft() {
|
|
989
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
990
|
+
return this.store.order.saveDraft();
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// 重置 tempOrder 与与上一单强相关的运行态(resource/entryPaxNumber/enabledReservationRuleProducts),
|
|
994
|
+
// 适用于从支付页通过 react-router 跳回继续下单时清理残留数据
|
|
995
|
+
async restoreOrder() {
|
|
996
|
+
try {
|
|
997
|
+
if (!this.store.order) {
|
|
998
|
+
throw new Error('scanOrder 解决方案需要 order 模块支持');
|
|
999
|
+
}
|
|
1000
|
+
// this.store.resource = null;
|
|
1001
|
+
const tempOrder = this.store.order.restoreOrder();
|
|
1002
|
+
this.currentSalesDetail = null;
|
|
1003
|
+
this.discountConfigCacheKey = null;
|
|
1004
|
+
this.hasManualDiscountSelection = false;
|
|
1005
|
+
await this.effectsEmit('onTempOrderReplaced', {
|
|
1006
|
+
tempOrder
|
|
1007
|
+
});
|
|
1008
|
+
if (this.syncAppDataToTempOrder(tempOrder) && this.isTempOrderPersistEnabled()) {
|
|
1009
|
+
this.store.order.persistTempOrder();
|
|
1010
|
+
await this.store.order.saveDraft();
|
|
1011
|
+
}
|
|
1012
|
+
return tempOrder;
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
throw error;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* 仅清空 tempOrder 购物车行(products / bookings / discount_list)。
|
|
1020
|
+
* 同步清空内存中的 salesDetail 商品/预约视图,避免编辑态仍读到旧 bookings。
|
|
1021
|
+
*/
|
|
1022
|
+
async clearOrderCartLines() {
|
|
1023
|
+
try {
|
|
1024
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1025
|
+
const tempOrder = await this.store.order.clearOrderCartLines();
|
|
1026
|
+
if (this.currentSalesDetail) {
|
|
1027
|
+
this.currentSalesDetail = {
|
|
1028
|
+
...this.currentSalesDetail,
|
|
1029
|
+
products: [],
|
|
1030
|
+
bookings: [],
|
|
1031
|
+
rootBooking: null,
|
|
1032
|
+
bookingsByProductUid: {},
|
|
1033
|
+
discount_list: []
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
await this.effectsEmit('onTempOrderReplaced', {
|
|
1037
|
+
tempOrder
|
|
1038
|
+
});
|
|
1039
|
+
if (this.syncAppDataToTempOrder(tempOrder) && this.isTempOrderPersistEnabled()) {
|
|
1040
|
+
this.store.order.persistTempOrder();
|
|
1041
|
+
await this.store.order.saveDraft();
|
|
1042
|
+
}
|
|
1043
|
+
return tempOrder;
|
|
1044
|
+
} catch (error) {
|
|
1045
|
+
throw error;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
getOrderProducts() {
|
|
1049
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1050
|
+
const products = this.store.order.getOrderProducts();
|
|
1051
|
+
return products;
|
|
1052
|
+
}
|
|
1053
|
+
async getSummary() {
|
|
1054
|
+
try {
|
|
1055
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1056
|
+
const summary = await this.store.order.getOrderSummary();
|
|
1057
|
+
return summary;
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
throw error;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
getHistoricalProductDiscountList(products) {
|
|
1063
|
+
const historyDiscountList = [];
|
|
1064
|
+
products.forEach(item => {
|
|
1065
|
+
if (!this.isPersistedOrderProduct(item)) return;
|
|
1066
|
+
(item.discount_list || []).forEach(discount => {
|
|
1067
|
+
const discountId = discount.discount?.resource_id || discount.id;
|
|
1068
|
+
if (!discountId || !['good_pass', 'discount_card'].includes(discount.type)) return;
|
|
1069
|
+
const quantity = item.quantity || item.num || item.product_quantity || 1;
|
|
1070
|
+
const savedAmount = new _decimal.default(discount.amount || 0).times(quantity).plus(discount?.metadata?.product_discount_difference || 0);
|
|
1071
|
+
const index = historyDiscountList.findIndex(n => n.id === discountId);
|
|
1072
|
+
if (index !== -1) {
|
|
1073
|
+
historyDiscountList[index] = {
|
|
1074
|
+
...historyDiscountList[index],
|
|
1075
|
+
amount: new _decimal.default(historyDiscountList[index].amount || 0).plus(discount.amount || 0).toNumber(),
|
|
1076
|
+
savedAmount: savedAmount.plus(historyDiscountList[index].savedAmount || 0).toNumber()
|
|
1077
|
+
};
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
historyDiscountList.push({
|
|
1081
|
+
...discount,
|
|
1082
|
+
id: discountId,
|
|
1083
|
+
tag: discount.tag || discount.type,
|
|
1084
|
+
name: discount.name || discount.discount?.title?.auto,
|
|
1085
|
+
format_title: discount.discount?.title || discount.format_title,
|
|
1086
|
+
isEditMode: true,
|
|
1087
|
+
limited_relation_product_data: {},
|
|
1088
|
+
savedAmount: savedAmount.toNumber(),
|
|
1089
|
+
isAvailable: true,
|
|
1090
|
+
isDisabled: true,
|
|
1091
|
+
isSelected: true,
|
|
1092
|
+
product_id: discount.discount?.product_id || discount.product_id
|
|
1093
|
+
});
|
|
1094
|
+
});
|
|
1095
|
+
});
|
|
1096
|
+
return historyDiscountList;
|
|
1097
|
+
}
|
|
1098
|
+
isPersistedOrderProduct(product) {
|
|
1099
|
+
const orderDetailId = product.order_detail_id ?? product.orderDetailId;
|
|
1100
|
+
if (orderDetailId !== undefined && orderDetailId !== null && orderDetailId !== '') {
|
|
1101
|
+
return true;
|
|
1102
|
+
}
|
|
1103
|
+
const bookingId = product.booking_id ?? product.bookingId;
|
|
1104
|
+
return bookingId !== undefined && bookingId !== null && bookingId !== '';
|
|
1105
|
+
}
|
|
1106
|
+
mergeHistoricalDiscountList(discountList, products) {
|
|
1107
|
+
const historyDiscountList = this.getHistoricalProductDiscountList(products);
|
|
1108
|
+
const historyIds = new Set(historyDiscountList.map(discount => discount.id));
|
|
1109
|
+
return [...historyDiscountList, ...(discountList || []).filter(discount => {
|
|
1110
|
+
if (!historyIds.has(discount.id)) return true;
|
|
1111
|
+
return !isBaseSalesHistoricalDiscountEntry(discount);
|
|
1112
|
+
})];
|
|
1113
|
+
}
|
|
1114
|
+
shouldSkipAutoApplyFetchedDiscountsInEditMode(params) {
|
|
1115
|
+
if (params.discountAction !== 'edit') return false;
|
|
1116
|
+
if (!params.products.length) return false;
|
|
1117
|
+
const hasDraftProduct = params.products.some(product => !this.isPersistedOrderProduct(product));
|
|
1118
|
+
if (hasDraftProduct) return false;
|
|
1119
|
+
const hasSelectedDiscount = params.currentDiscountList.some(discount => discount.isSelected || discount.isEditMode || discount.isManualSelect);
|
|
1120
|
+
if (hasSelectedDiscount) return false;
|
|
1121
|
+
return !params.nextDiscountList.some(discount => discount.isSelected || discount.isEditMode || discount.isManualSelect);
|
|
1122
|
+
}
|
|
1123
|
+
mergeCurrentDiscountSelectionState(discountList, currentDiscountList) {
|
|
1124
|
+
if (!currentDiscountList.length) return discountList;
|
|
1125
|
+
const currentDiscountMap = new Map();
|
|
1126
|
+
currentDiscountList.forEach(discount => {
|
|
1127
|
+
currentDiscountMap.set(`${discount.id}:${isBaseSalesHistoricalDiscountEntry(discount) ? 'historical' : 'runtime'}`, discount);
|
|
1128
|
+
});
|
|
1129
|
+
return discountList.map(discount => {
|
|
1130
|
+
const currentDiscount = currentDiscountMap.get(`${discount.id}:${isBaseSalesHistoricalDiscountEntry(discount) ? 'historical' : 'runtime'}`);
|
|
1131
|
+
if (!currentDiscount) return discount;
|
|
1132
|
+
if (currentDiscount.isManualSelect) {
|
|
1133
|
+
return {
|
|
1134
|
+
...discount,
|
|
1135
|
+
isSelected: false,
|
|
1136
|
+
isManualSelect: true
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
if (currentDiscount.isSelected) {
|
|
1140
|
+
return {
|
|
1141
|
+
...discount,
|
|
1142
|
+
isSelected: true,
|
|
1143
|
+
isManualSelect: false
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
return discount;
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
async loadDiscountConfig(params) {
|
|
1150
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1151
|
+
const tempOrder = this.store.order.ensureTempOrder();
|
|
1152
|
+
const orderStore = this.store.order.store || {};
|
|
1153
|
+
const discountModule = orderStore.discount;
|
|
1154
|
+
const rulesModule = orderStore.rules;
|
|
1155
|
+
const orderId = tempOrder.order_id || this.getOrderIdentity?.()?.orderId;
|
|
1156
|
+
const customerId = Number(params?.customerId || tempOrder.customer_id || tempOrder.customer?.id || tempOrder._extend?.customerSnapshot?.id || tempOrder._extend?.customerSnapshot?.customer_id || 0);
|
|
1157
|
+
const currentDiscountList = discountModule?.getDiscountList?.() || [];
|
|
1158
|
+
const originalDiscountList = discountModule?.getOriginalDiscountList?.() || [];
|
|
1159
|
+
const hasManualDiscountSelection = this.hasManualDiscountSelection || currentDiscountList.some(discount => discount.isManualSelect);
|
|
1160
|
+
const discountAction = params?.action || (orderId ? 'edit' : 'create');
|
|
1161
|
+
const discountConfigCacheKey = [customerId, discountAction, Number(orderId) || 0].join(':');
|
|
1162
|
+
let preparedDiscountList = [];
|
|
1163
|
+
if (customerId && customerId !== 1) {
|
|
1164
|
+
if (this.discountConfigCacheKey === discountConfigCacheKey) {
|
|
1165
|
+
preparedDiscountList = originalDiscountList;
|
|
1166
|
+
} else {
|
|
1167
|
+
preparedDiscountList = await this.store.order.loadDiscountConfig({
|
|
1168
|
+
customerId,
|
|
1169
|
+
action: discountAction,
|
|
1170
|
+
orderId: Number(orderId) || undefined,
|
|
1171
|
+
apply: false
|
|
1172
|
+
});
|
|
1173
|
+
this.discountConfigCacheKey = discountConfigCacheKey;
|
|
1174
|
+
await discountModule?.setOriginalDiscountList?.(preparedDiscountList);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
if (hasManualDiscountSelection && currentDiscountList.length) {
|
|
1178
|
+
return {
|
|
1179
|
+
productList: tempOrder.products || [],
|
|
1180
|
+
discountList: currentDiscountList,
|
|
1181
|
+
availableWalletIds: this.getAvailableWalletIds()
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
let nextDiscountList = this.mergeHistoricalDiscountList(preparedDiscountList || discountModule?.getDiscountList?.() || [], tempOrder.products || []);
|
|
1185
|
+
nextDiscountList = this.mergeCurrentDiscountSelectionState(nextDiscountList, currentDiscountList);
|
|
1186
|
+
await discountModule?.setDiscountList(nextDiscountList);
|
|
1187
|
+
const shouldSkipAutoApplyFetchedDiscounts = this.shouldSkipAutoApplyFetchedDiscountsInEditMode({
|
|
1188
|
+
discountAction,
|
|
1189
|
+
products: tempOrder.products || [],
|
|
1190
|
+
currentDiscountList,
|
|
1191
|
+
nextDiscountList
|
|
1192
|
+
});
|
|
1193
|
+
if (shouldSkipAutoApplyFetchedDiscounts) {
|
|
1194
|
+
const summary = await this.store.order.recalculateSummary({
|
|
1195
|
+
createIfMissing: true
|
|
1196
|
+
});
|
|
1197
|
+
this.store.order.persistTempOrder();
|
|
1198
|
+
await this.effectsEmit('onDiscountApplied', {
|
|
1199
|
+
productList: tempOrder.products || [],
|
|
1200
|
+
discountList: nextDiscountList,
|
|
1201
|
+
selectedDiscountList: tempOrder.discount_list || [],
|
|
1202
|
+
tempOrder
|
|
1203
|
+
});
|
|
1204
|
+
return {
|
|
1205
|
+
productList: tempOrder.products || [],
|
|
1206
|
+
discountList: nextDiscountList,
|
|
1207
|
+
availableWalletIds: this.getAvailableWalletIds()
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
if (rulesModule) {
|
|
1211
|
+
const orderTotalAmount = Number(orderStore.summary?.total_amount || 0);
|
|
1212
|
+
const result = rulesModule.calcDiscount({
|
|
1213
|
+
productList: tempOrder.products,
|
|
1214
|
+
discountList: nextDiscountList,
|
|
1215
|
+
holders: [],
|
|
1216
|
+
isFormSubject: product => (0, _utils.resolveIsFormSubject)(tempOrder, product),
|
|
1217
|
+
orderTotalAmount
|
|
1218
|
+
}) || {
|
|
1219
|
+
productList: tempOrder.products,
|
|
1220
|
+
discountList: nextDiscountList
|
|
1221
|
+
};
|
|
1222
|
+
if (result.productList) {
|
|
1223
|
+
tempOrder.products = preserveManualProductDiscountProducts(tempOrder.products, result.productList);
|
|
1224
|
+
}
|
|
1225
|
+
for (const product of tempOrder.products || []) {
|
|
1226
|
+
const productUid = product.metadata?.unique_identification_number;
|
|
1227
|
+
const runtimeOrigin = productUid ? tempOrder._extend?.productsByUid?.[productUid]?.origin : undefined;
|
|
1228
|
+
if (runtimeOrigin?.isManualDiscount || isBaseSalesManualProductDiscountProduct(product)) continue;
|
|
1229
|
+
const totalPerUnitDiscount = (product.discount_list || []).reduce((sum, pd) => sum + Number(pd.amount || 0), 0);
|
|
1230
|
+
const optionSum = (0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(product));
|
|
1231
|
+
const sourcePrice = product.metadata?.source_product_price ?? (product.metadata?.main_product_original_price != null ? new _decimal.default(Number(product.metadata.main_product_original_price) || 0).minus(optionSum).toFixed(2) : product.original_price ?? '0');
|
|
1232
|
+
const newSourceSellingPrice = new _decimal.default(Number(sourcePrice) || 0).minus(totalPerUnitDiscount).toDecimalPlaces(2).toString();
|
|
1233
|
+
const newMainSellingPrice = new _decimal.default(Number(newSourceSellingPrice) || 0).plus(optionSum).toDecimalPlaces(2).toFixed(2);
|
|
1234
|
+
if (product.metadata) {
|
|
1235
|
+
product.metadata.main_product_selling_price = newMainSellingPrice;
|
|
1236
|
+
product.metadata.price_schema_version = 2;
|
|
1237
|
+
}
|
|
1238
|
+
product.selling_price = (0, _utils.composeLinePrice)({
|
|
1239
|
+
mainPrice: newMainSellingPrice,
|
|
1240
|
+
bundle: product.product_bundle
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
if (result.discountList) {
|
|
1244
|
+
nextDiscountList = this.mergeHistoricalDiscountList(result.discountList, tempOrder.products || []);
|
|
1245
|
+
_Order.OrderModule.populateSavedAmounts(tempOrder.products, nextDiscountList);
|
|
1246
|
+
await discountModule?.setDiscountList(nextDiscountList);
|
|
1247
|
+
tempOrder.discount_list = nextDiscountList.filter(discount => discount.isSelected);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
const summary = await this.store.order.recalculateSummary({
|
|
1251
|
+
createIfMissing: true
|
|
1252
|
+
});
|
|
1253
|
+
this.store.order.persistTempOrder();
|
|
1254
|
+
await this.effectsEmit('onDiscountApplied', {
|
|
1255
|
+
productList: tempOrder.products || [],
|
|
1256
|
+
discountList: nextDiscountList,
|
|
1257
|
+
selectedDiscountList: tempOrder.discount_list || [],
|
|
1258
|
+
tempOrder
|
|
1259
|
+
});
|
|
1260
|
+
return {
|
|
1261
|
+
productList: tempOrder.products || [],
|
|
1262
|
+
discountList: nextDiscountList,
|
|
1263
|
+
availableWalletIds: this.getAvailableWalletIds()
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
getAvailableWalletIds() {
|
|
1267
|
+
if (!this.store.order?.getAvailableWalletIds) return [];
|
|
1268
|
+
return this.store.order.getAvailableWalletIds();
|
|
1269
|
+
}
|
|
1270
|
+
async bestDiscount(cb) {
|
|
1271
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1272
|
+
const tempOrder = this.store.order.ensureTempOrder();
|
|
1273
|
+
const orderStore = this.store.order.store || {};
|
|
1274
|
+
const discountModule = orderStore.discount;
|
|
1275
|
+
const rulesModule = orderStore.rules;
|
|
1276
|
+
const originalDiscountList = discountModule?.getOriginalDiscountList?.() || this.getDiscountList();
|
|
1277
|
+
const currentDiscountList = discountModule?.getDiscountList?.() || [];
|
|
1278
|
+
let nextDiscountList = this.mergeHistoricalDiscountList(originalDiscountList || [], tempOrder.products || []);
|
|
1279
|
+
let result = {
|
|
1280
|
+
productList: tempOrder.products || [],
|
|
1281
|
+
discountList: nextDiscountList
|
|
1282
|
+
};
|
|
1283
|
+
await discountModule?.setDiscountList(nextDiscountList);
|
|
1284
|
+
if (rulesModule) {
|
|
1285
|
+
const orderTotalAmount = Number(orderStore.summary?.total_amount || 0);
|
|
1286
|
+
result = rulesModule.calcDiscount({
|
|
1287
|
+
productList: tempOrder.products,
|
|
1288
|
+
discountList: nextDiscountList,
|
|
1289
|
+
holders: [],
|
|
1290
|
+
isFormSubject: product => (0, _utils.resolveIsFormSubject)(tempOrder, product),
|
|
1291
|
+
orderTotalAmount
|
|
1292
|
+
}) || result;
|
|
1293
|
+
if (result.productList) {
|
|
1294
|
+
tempOrder.products = preserveManualProductDiscountProducts(tempOrder.products, result.productList);
|
|
1295
|
+
}
|
|
1296
|
+
if (result.discountList) {
|
|
1297
|
+
nextDiscountList = this.mergeHistoricalDiscountList(result.discountList, tempOrder.products || []);
|
|
1298
|
+
_Order.OrderModule.populateSavedAmounts(tempOrder.products, nextDiscountList);
|
|
1299
|
+
await discountModule?.setDiscountList(nextDiscountList);
|
|
1300
|
+
tempOrder.discount_list = nextDiscountList.filter(discount => discount.isSelected);
|
|
1301
|
+
result = {
|
|
1302
|
+
productList: tempOrder.products,
|
|
1303
|
+
discountList: nextDiscountList
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
await this.store.order.recalculateSummary({
|
|
1308
|
+
createIfMissing: true
|
|
1309
|
+
});
|
|
1310
|
+
this.store.order.persistTempOrder();
|
|
1311
|
+
await this.effectsEmit('onDiscountApplied', {
|
|
1312
|
+
productList: tempOrder.products || [],
|
|
1313
|
+
discountList: nextDiscountList,
|
|
1314
|
+
selectedDiscountList: tempOrder.discount_list || [],
|
|
1315
|
+
tempOrder
|
|
1316
|
+
});
|
|
1317
|
+
cb?.(result);
|
|
1318
|
+
return result;
|
|
1319
|
+
}
|
|
1320
|
+
getDiscountList() {
|
|
1321
|
+
if (!this.store.order) return [];
|
|
1322
|
+
return this.store.order.getDiscountList();
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/** 读取 Discount 模块原始券列表(未经过 bookingTime 等过滤)。 */
|
|
1326
|
+
getOriginalDiscountList() {
|
|
1327
|
+
if (!this.store.order) return [];
|
|
1328
|
+
const discountModule = this.store.order.store?.discount;
|
|
1329
|
+
return discountModule?.getOriginalDiscountList?.() || [];
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
/**
|
|
1333
|
+
* 同步写入 Discount 模块的 discountList / originalDiscountList。
|
|
1334
|
+
* 供 Holder 补全等场景在 OS store 层更新 machinecode holder 字段。
|
|
1335
|
+
*/
|
|
1336
|
+
async replaceDiscountStoreLists(params) {
|
|
1337
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1338
|
+
const discountModule = this.store.order.store?.discount;
|
|
1339
|
+
if (!discountModule) return;
|
|
1340
|
+
if (params.originalDiscountList) {
|
|
1341
|
+
await discountModule.setOriginalDiscountList(params.originalDiscountList);
|
|
1342
|
+
}
|
|
1343
|
+
await discountModule.setDiscountList(params.discountList);
|
|
1344
|
+
}
|
|
1345
|
+
async scanPromotionCode(code, customerId) {
|
|
1346
|
+
try {
|
|
1347
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1348
|
+
const raw = await this.store.order.scanCode(code, customerId);
|
|
1349
|
+
if (raw.isAvailable) {
|
|
1350
|
+
await this.store.order.recalculateSummary({
|
|
1351
|
+
createIfMissing: true
|
|
1352
|
+
});
|
|
1353
|
+
this.store.order.persistTempOrder();
|
|
1354
|
+
const tempOrder = this.store.order.ensureTempOrder();
|
|
1355
|
+
await this.effectsEmit('onDiscountApplied', {
|
|
1356
|
+
productList: tempOrder.products || [],
|
|
1357
|
+
discountList: this.store.order.getDiscountList(),
|
|
1358
|
+
selectedDiscountList: tempOrder.discount_list || [],
|
|
1359
|
+
tempOrder
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
return {
|
|
1363
|
+
isAvailable: raw.isAvailable,
|
|
1364
|
+
type: raw.type,
|
|
1365
|
+
unavailableReason: raw.unavailableReason,
|
|
1366
|
+
scannedDiscountList: raw.scannedDiscountList
|
|
1367
|
+
};
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
throw error;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
// TODO 需要优化,这段逻辑太长了,里面应该有些历史遗留问题
|
|
1374
|
+
async setDiscountSelected(params) {
|
|
1375
|
+
try {
|
|
1376
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1377
|
+
const list = this.store.order.getDiscountList();
|
|
1378
|
+
const updated = list.map(d => d.id === params.discountId && !isBaseSalesHistoricalDiscountEntry(d) ? {
|
|
1379
|
+
...d,
|
|
1380
|
+
isSelected: params.isSelected,
|
|
1381
|
+
isManualSelect: !params.isSelected
|
|
1382
|
+
} : d);
|
|
1383
|
+
this.hasManualDiscountSelection = true;
|
|
1384
|
+
const tempOrder = this.store.order.ensureTempOrder();
|
|
1385
|
+
const orderStore = this.store.order.store || {};
|
|
1386
|
+
const discountModule = orderStore.discount;
|
|
1387
|
+
const rulesModule = orderStore.rules;
|
|
1388
|
+
let nextDiscountList = updated;
|
|
1389
|
+
await discountModule?.setDiscountList(updated);
|
|
1390
|
+
if (rulesModule) {
|
|
1391
|
+
const orderTotalAmount = Number(orderStore.summary?.total_amount || 0);
|
|
1392
|
+
const result = rulesModule.calcDiscount({
|
|
1393
|
+
productList: tempOrder.products,
|
|
1394
|
+
discountList: updated,
|
|
1395
|
+
holders: [],
|
|
1396
|
+
isFormSubject: product => (0, _utils.resolveIsFormSubject)(tempOrder, product),
|
|
1397
|
+
orderTotalAmount
|
|
1398
|
+
}, {
|
|
1399
|
+
discountId: params.discountId,
|
|
1400
|
+
isSelected: params.isSelected
|
|
1401
|
+
}) || {
|
|
1402
|
+
productList: tempOrder.products,
|
|
1403
|
+
discountList: updated
|
|
1404
|
+
};
|
|
1405
|
+
if (result?.productList) {
|
|
1406
|
+
tempOrder.products = preserveManualProductDiscountProducts(tempOrder.products, result.productList);
|
|
1407
|
+
}
|
|
1408
|
+
if (result?.discountList) {
|
|
1409
|
+
nextDiscountList = result.discountList;
|
|
1410
|
+
if (!params.isSelected) {
|
|
1411
|
+
const beforeSelectedIds = new Set(updated.filter(d => d.isSelected).map(d => d.id));
|
|
1412
|
+
for (const d of nextDiscountList) {
|
|
1413
|
+
if (d.isSelected && !beforeSelectedIds.has(d.id)) {
|
|
1414
|
+
d.isSelected = false;
|
|
1415
|
+
d.isManualSelect = true;
|
|
1416
|
+
d.appliedProductDetails = [];
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
const selectedResourceIds = new Set(nextDiscountList.filter(d => d.isSelected).map(d => d.id));
|
|
1423
|
+
const filterSelectedDiscountDetails = discountList => (discountList || []).filter(pd => {
|
|
1424
|
+
const rid = pd.discount?.resource_id ?? pd.id;
|
|
1425
|
+
return rid != null && selectedResourceIds.has(rid);
|
|
1426
|
+
});
|
|
1427
|
+
for (const product of tempOrder.products) {
|
|
1428
|
+
const productUid = product.metadata?.unique_identification_number;
|
|
1429
|
+
const runtimeOrigin = productUid ? tempOrder._extend?.productsByUid?.[productUid]?.origin : undefined;
|
|
1430
|
+
if (runtimeOrigin?.isManualDiscount || isBaseSalesManualProductDiscountProduct(product)) continue;
|
|
1431
|
+
product.discount_list = filterSelectedDiscountDetails(product.discount_list);
|
|
1432
|
+
if (Array.isArray(product.product_bundle)) {
|
|
1433
|
+
product.product_bundle = product.product_bundle.map(bundle => {
|
|
1434
|
+
const nextBundleDiscountList = filterSelectedDiscountDetails(bundle?.discount_list);
|
|
1435
|
+
const shouldRestoreBundlePrice = nextBundleDiscountList.length === 0;
|
|
1436
|
+
const restoredBundlePrice = bundle?.original_price ?? bundle?.product_price ?? bundle?.price;
|
|
1437
|
+
return {
|
|
1438
|
+
...bundle,
|
|
1439
|
+
...(shouldRestoreBundlePrice ? {
|
|
1440
|
+
price: restoredBundlePrice,
|
|
1441
|
+
bundle_selling_price: restoredBundlePrice
|
|
1442
|
+
} : {}),
|
|
1443
|
+
discount_list: nextBundleDiscountList
|
|
1444
|
+
};
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// 券作用于 source(不含 option、不含 bundle);算出新 source 折后价后补回 options
|
|
1449
|
+
// 写入含 option 的 main_product_selling_price,再合成 composite 回写 selling_price。
|
|
1450
|
+
const totalPerUnitDiscount = (product.discount_list || []).reduce((sum, pd) => sum + (pd.amount || 0), 0);
|
|
1451
|
+
// TODO 这块逻辑需要拿掉
|
|
1452
|
+
const optionSum = (0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(product));
|
|
1453
|
+
const sourcePrice = product.metadata?.source_product_price ?? (product.metadata?.main_product_original_price != null ? new _decimal.default(Number(product.metadata.main_product_original_price) || 0).minus(optionSum).toFixed(2) : product.original_price ?? '0');
|
|
1454
|
+
const newSourceSellingPrice = new _decimal.default(Number(sourcePrice) || 0).minus(totalPerUnitDiscount).toDecimalPlaces(2).toString();
|
|
1455
|
+
const newMainSellingPrice = new _decimal.default(Number(newSourceSellingPrice) || 0).plus(optionSum).toDecimalPlaces(2).toFixed(2);
|
|
1456
|
+
if (product.metadata) {
|
|
1457
|
+
product.metadata.main_product_selling_price = newMainSellingPrice;
|
|
1458
|
+
product.metadata.price_schema_version = 2;
|
|
1459
|
+
}
|
|
1460
|
+
product.selling_price = (0, _utils.composeLinePrice)({
|
|
1461
|
+
mainPrice: newMainSellingPrice,
|
|
1462
|
+
bundle: product.product_bundle
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
_Order.OrderModule.populateSavedAmounts(tempOrder.products, nextDiscountList);
|
|
1466
|
+
await discountModule?.setDiscountList(nextDiscountList);
|
|
1467
|
+
tempOrder.discount_list = (nextDiscountList || []).filter(d => d.isSelected);
|
|
1468
|
+
await this.store.order.recalculateSummary({
|
|
1469
|
+
createIfMissing: true
|
|
1470
|
+
});
|
|
1471
|
+
this.store.order.persistTempOrder();
|
|
1472
|
+
this.effectsEmit('onDiscountApplied', {
|
|
1473
|
+
productList: tempOrder.products,
|
|
1474
|
+
discountList: nextDiscountList,
|
|
1475
|
+
selectedDiscountList: tempOrder.discount_list,
|
|
1476
|
+
tempOrder
|
|
1477
|
+
});
|
|
1478
|
+
} catch (error) {
|
|
1479
|
+
throw error;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
async applyDiscountCalculationResult(result) {
|
|
1483
|
+
try {
|
|
1484
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1485
|
+
if (!result) return;
|
|
1486
|
+
const tempOrder = this.store.order.ensureTempOrder();
|
|
1487
|
+
const orderStore = this.store.order.store || {};
|
|
1488
|
+
const discountModule = orderStore.discount;
|
|
1489
|
+
if (result.productList) {
|
|
1490
|
+
tempOrder.products = preserveManualProductDiscountProducts(tempOrder.products, result.productList);
|
|
1491
|
+
}
|
|
1492
|
+
if (result.discountList) {
|
|
1493
|
+
_Order.OrderModule.populateSavedAmounts(tempOrder.products, result.discountList);
|
|
1494
|
+
await discountModule?.setDiscountList(result.discountList);
|
|
1495
|
+
tempOrder.discount_list = result.discountList.filter(discount => discount.isSelected);
|
|
1496
|
+
}
|
|
1497
|
+
await this.store.order.recalculateSummary({
|
|
1498
|
+
createIfMissing: true
|
|
1499
|
+
});
|
|
1500
|
+
this.store.order.persistTempOrder();
|
|
1501
|
+
await this.effectsEmit('onDiscountApplied', {
|
|
1502
|
+
productList: tempOrder.products || [],
|
|
1503
|
+
discountList: result.discountList || [],
|
|
1504
|
+
selectedDiscountList: tempOrder.discount_list || [],
|
|
1505
|
+
tempOrder
|
|
1506
|
+
});
|
|
1507
|
+
} catch (error) {
|
|
1508
|
+
throw error;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
async submitScanOrder(params) {
|
|
1512
|
+
// Deprecated compatibility alias. New BaseSales callers should use submitSalesOrder.
|
|
1513
|
+
return this.submitSalesOrder(params);
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/**
|
|
1517
|
+
* 提交当前 Sales 订单。
|
|
1518
|
+
*
|
|
1519
|
+
* PaymentModal 场景通过 smallTicketDataFlag=1 请求 B 端小票数据;
|
|
1520
|
+
* payments 会交给 OrderModule 统一映射为 Sales 协议支付项。
|
|
1521
|
+
*/
|
|
1522
|
+
async submitSalesOrder(params) {
|
|
1523
|
+
if (!this.store.order) {
|
|
1524
|
+
throw new Error('BaseSales 解决方案需要 order 模块支持');
|
|
1525
|
+
}
|
|
1526
|
+
// this.store.order.persistTempOrder();
|
|
1527
|
+
const inferredPaymentStatus = this.getSubmitPaymentStatus(params?.paymentStatus);
|
|
1528
|
+
const smallTicketDataFlag = params?.smallTicketDataFlag ?? this.getDefaultCheckoutSmallTicketDataFlag();
|
|
1529
|
+
const submitParams = {
|
|
1530
|
+
cacheId: this.cacheId,
|
|
1531
|
+
platform: this.otherParams?.platform,
|
|
1532
|
+
businessCode: this.otherParams?.businessCode || this.otherParams?.business_code,
|
|
1533
|
+
channel: this.getSubmitOrderSalesChannel(),
|
|
1534
|
+
type: this.otherParams?.type,
|
|
1535
|
+
request_unique_idempotency_token: this.store.order.generateIdempotencyToken(),
|
|
1536
|
+
...(params?.payments !== undefined ? {
|
|
1537
|
+
payments: params.payments
|
|
1538
|
+
} : {}),
|
|
1539
|
+
...(inferredPaymentStatus !== undefined ? {
|
|
1540
|
+
paymentStatus: inferredPaymentStatus
|
|
1541
|
+
} : {}),
|
|
1542
|
+
...(smallTicketDataFlag !== undefined ? {
|
|
1543
|
+
smallTicketDataFlag
|
|
1544
|
+
} : {}),
|
|
1545
|
+
...(params?.confirmPendingVoucherPayments !== undefined ? {
|
|
1546
|
+
confirmPendingVoucherPayments: params.confirmPendingVoucherPayments
|
|
1547
|
+
} : {}),
|
|
1548
|
+
...(params?.enhancePayload !== undefined ? {
|
|
1549
|
+
enhancePayload: params.enhancePayload
|
|
1550
|
+
} : {})
|
|
1551
|
+
};
|
|
1552
|
+
return this.shouldUseCheckoutSyncTask() ? this.store.order.submitTempOrder(submitParams) : this.store.order.submitTempOrderAsync(submitParams);
|
|
1553
|
+
}
|
|
1554
|
+
async submitTempOrderAsync(params) {
|
|
1555
|
+
if (!this.store.order) {
|
|
1556
|
+
throw new Error('BaseSales 解决方案需要 order 模块支持');
|
|
1557
|
+
}
|
|
1558
|
+
const inferredPaymentStatus = this.getSubmitPaymentStatus(params?.paymentStatus);
|
|
1559
|
+
const smallTicketDataFlag = params?.smallTicketDataFlag ?? this.getDefaultCheckoutSmallTicketDataFlag();
|
|
1560
|
+
const submitParams = {
|
|
1561
|
+
cacheId: this.cacheId,
|
|
1562
|
+
platform: this.otherParams?.platform,
|
|
1563
|
+
businessCode: this.otherParams?.businessCode || this.otherParams?.business_code,
|
|
1564
|
+
channel: this.getSubmitOrderSalesChannel(),
|
|
1565
|
+
type: this.otherParams?.type,
|
|
1566
|
+
request_unique_idempotency_token: this.store.order.generateIdempotencyToken(),
|
|
1567
|
+
...(params?.payments !== undefined ? {
|
|
1568
|
+
payments: params.payments
|
|
1569
|
+
} : {}),
|
|
1570
|
+
...(inferredPaymentStatus !== undefined ? {
|
|
1571
|
+
paymentStatus: inferredPaymentStatus
|
|
1572
|
+
} : {}),
|
|
1573
|
+
...(smallTicketDataFlag !== undefined ? {
|
|
1574
|
+
smallTicketDataFlag
|
|
1575
|
+
} : {}),
|
|
1576
|
+
...(params?.confirmPendingVoucherPayments !== undefined ? {
|
|
1577
|
+
confirmPendingVoucherPayments: params.confirmPendingVoucherPayments
|
|
1578
|
+
} : {}),
|
|
1579
|
+
...(params?.enhancePayload !== undefined ? {
|
|
1580
|
+
enhancePayload: params.enhancePayload
|
|
1581
|
+
} : {})
|
|
1582
|
+
};
|
|
1583
|
+
return this.store.order.submitTempOrderAsync(submitParams);
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
/**
|
|
1587
|
+
* 默认保持 WebPOS 的本地待同步订单模式;需要云端确认后才算成功的渠道可覆盖此策略。
|
|
1588
|
+
* otherParams.checkoutSyncTaskEnabled 优先,便于同一 solution 按入口恢复旧行为。
|
|
1589
|
+
*/
|
|
1590
|
+
shouldUseCheckoutSyncTask() {
|
|
1591
|
+
const configured = this.otherParams?.checkoutSyncTaskEnabled;
|
|
1592
|
+
return typeof configured === 'boolean' ? configured : true;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
/** 可由特定 solution 为真实 checkout 补充默认收据数据请求。 */
|
|
1596
|
+
getDefaultCheckoutSmallTicketDataFlag() {
|
|
1597
|
+
return undefined;
|
|
1598
|
+
}
|
|
1599
|
+
async printLocalOrderReceipt(lookup) {
|
|
1600
|
+
if (!this.store.order) {
|
|
1601
|
+
throw new Error('BaseSales 解决方案需要 order 模块支持');
|
|
1602
|
+
}
|
|
1603
|
+
const hasLookup = lookup !== undefined && lookup !== null && lookup !== '';
|
|
1604
|
+
const lookupPayload = typeof lookup === 'number' ? {
|
|
1605
|
+
order_id: lookup
|
|
1606
|
+
} : {
|
|
1607
|
+
external_sale_number: lookup
|
|
1608
|
+
};
|
|
1609
|
+
const context = {
|
|
1610
|
+
platform: this.otherParams?.platform,
|
|
1611
|
+
businessCode: this.otherParams?.businessCode || this.otherParams?.business_code,
|
|
1612
|
+
channel: this.getSubmitOrderSalesChannel(),
|
|
1613
|
+
type: this.otherParams?.type
|
|
1614
|
+
};
|
|
1615
|
+
const payload = hasLookup ? {
|
|
1616
|
+
...lookupPayload,
|
|
1617
|
+
lookup_order_from_db: 1,
|
|
1618
|
+
small_ticket_data_flag: 1,
|
|
1619
|
+
platform: context.platform,
|
|
1620
|
+
business_code: context.businessCode,
|
|
1621
|
+
order_sales_channel: context.channel,
|
|
1622
|
+
type: context.type
|
|
1623
|
+
} : this.store.order.buildCurrentSubmitPayloadForLocalPrint({
|
|
1624
|
+
cacheId: this.cacheId,
|
|
1625
|
+
platform: context.platform,
|
|
1626
|
+
businessCode: context.businessCode,
|
|
1627
|
+
channel: context.channel,
|
|
1628
|
+
type: context.type
|
|
1629
|
+
});
|
|
1630
|
+
const response = await this.request.post('/local/print-order', payload, {
|
|
1631
|
+
osServer: true,
|
|
1632
|
+
customToast: () => {}
|
|
1633
|
+
});
|
|
1634
|
+
if (!hasLookup) {
|
|
1635
|
+
await this.store.order.applyLocalPrintOrderNumbers?.(response?.data?.order);
|
|
1636
|
+
}
|
|
1637
|
+
return response;
|
|
1638
|
+
}
|
|
1639
|
+
getSubmitPaymentStatus(paymentStatus) {
|
|
1640
|
+
if (paymentStatus !== undefined) return paymentStatus;
|
|
1641
|
+
const amountSnapshot = this.store.order?.getOrderAmountSnapshot?.();
|
|
1642
|
+
if (!amountSnapshot) return undefined;
|
|
1643
|
+
try {
|
|
1644
|
+
return new _decimal.default(amountSnapshot.amountGap || 0).lte(0) ? 'paid' : undefined;
|
|
1645
|
+
} catch {
|
|
1646
|
+
return undefined;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
async syncPaymentsToOrder(params) {
|
|
1650
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1651
|
+
const businessCode = params.businessCode ?? this.otherParams?.businessCode ?? this.otherParams?.business_code;
|
|
1652
|
+
const channel = params.channel ?? this.getSubmitOrderSalesChannel();
|
|
1653
|
+
const syncParams = {
|
|
1654
|
+
...params,
|
|
1655
|
+
checkoutSyncTaskEnabled: this.shouldUseCheckoutSyncTask(),
|
|
1656
|
+
...(businessCode !== undefined ? {
|
|
1657
|
+
businessCode
|
|
1658
|
+
} : {}),
|
|
1659
|
+
...(channel !== undefined ? {
|
|
1660
|
+
channel
|
|
1661
|
+
} : {})
|
|
1662
|
+
};
|
|
1663
|
+
const syncState = this.store.order.getOrderSyncState();
|
|
1664
|
+
if (params.submitWhenPaid && syncState === 'submitting') {
|
|
1665
|
+
this.queueLatestAutoPaymentSync();
|
|
1666
|
+
return this.store.order.syncPaymentsToOrder(syncParams);
|
|
1667
|
+
}
|
|
1668
|
+
const payments = params.payments || [];
|
|
1669
|
+
await this.effectsEmit(_types.BaseSalesHookNames.onPaymentSyncStart, {
|
|
1670
|
+
payments,
|
|
1671
|
+
params: syncParams,
|
|
1672
|
+
amountSnapshot: this.store.order.getOrderAmountSnapshot(),
|
|
1673
|
+
orderSnapshot: this.store.order.getOrderSnapshot()
|
|
1674
|
+
});
|
|
1675
|
+
try {
|
|
1676
|
+
const syncResult = await this.store.order.syncPaymentsToOrder(syncParams);
|
|
1677
|
+
const latestPayments = this.store.order.getOrderPayments();
|
|
1678
|
+
const amountSnapshot = this.store.order.getOrderAmountSnapshot();
|
|
1679
|
+
const orderSnapshot = this.store.order.getOrderSnapshot();
|
|
1680
|
+
const syncPayload = {
|
|
1681
|
+
ok: true,
|
|
1682
|
+
syncResult,
|
|
1683
|
+
payments: latestPayments,
|
|
1684
|
+
params: syncParams,
|
|
1685
|
+
amountSnapshot,
|
|
1686
|
+
orderSnapshot,
|
|
1687
|
+
isFullyPaid: syncResult.isFullyPaid,
|
|
1688
|
+
isOrderFullyPaid: syncResult.isOrderFullyPaid,
|
|
1689
|
+
isDepositFullyPaid: syncResult.isDepositFullyPaid,
|
|
1690
|
+
paymentStage: syncResult.paymentStage,
|
|
1691
|
+
depositAmount: syncResult.depositAmount,
|
|
1692
|
+
orderExpectedAmount: syncResult.orderExpectedAmount
|
|
1693
|
+
};
|
|
1694
|
+
await this.effectsEmit(_types.BaseSalesHookNames.onPaymentSyncEnd, syncPayload);
|
|
1695
|
+
if (syncResult.isDepositFullyPaid && !syncResult.isOrderFullyPaid) {
|
|
1696
|
+
await this.effectsEmit(_types.BaseSalesHookNames.onDepositPaymentSyncEnd, syncPayload);
|
|
1697
|
+
}
|
|
1698
|
+
this.scheduleQueuedAutoPaymentSyncFlush(0);
|
|
1699
|
+
return syncResult;
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
await this.effectsEmit(_types.BaseSalesHookNames.onPaymentSyncEnd, {
|
|
1702
|
+
ok: false,
|
|
1703
|
+
error,
|
|
1704
|
+
payments: this.store.order.getOrderPayments(),
|
|
1705
|
+
params: syncParams,
|
|
1706
|
+
amountSnapshot: this.store.order.getOrderAmountSnapshot(),
|
|
1707
|
+
orderSnapshot: this.store.order.getOrderSnapshot()
|
|
1708
|
+
});
|
|
1709
|
+
this.scheduleQueuedAutoPaymentSyncFlush(0);
|
|
1710
|
+
throw error;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
/** 读取当前 BaseSales 订单上下文中的 Sales 协议支付项。 */
|
|
1715
|
+
getOrderPayments() {
|
|
1716
|
+
if (!this.store.order) return [];
|
|
1717
|
+
const payments = this.store.order.getOrderPayments();
|
|
1718
|
+
return this.hydrateOrderPaymentNames(payments);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
/** 覆盖当前订单支付项,内部由 OrderModule 清洗为 Sales 协议字段。 */
|
|
1722
|
+
setOrderPayments(payments) {
|
|
1723
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1724
|
+
return this.store.order.setOrderPayments(payments);
|
|
1725
|
+
}
|
|
1726
|
+
getPaymentStatusForSync(payments) {
|
|
1727
|
+
const order = this.store.order;
|
|
1728
|
+
if (!order) throw new Error('order 模块未初始化');
|
|
1729
|
+
const completion = order.getPaymentCompletion(payments);
|
|
1730
|
+
return completion.isOrderFullyPaid ? 'paid' : 'partially_paid';
|
|
1731
|
+
}
|
|
1732
|
+
queueLatestAutoPaymentSync() {
|
|
1733
|
+
this.queuedAutoPaymentSync = true;
|
|
1734
|
+
this.scheduleQueuedAutoPaymentSyncFlush();
|
|
1735
|
+
}
|
|
1736
|
+
scheduleQueuedAutoPaymentSyncFlush(delay = 100) {
|
|
1737
|
+
if (!this.queuedAutoPaymentSync) return;
|
|
1738
|
+
if (this.autoPaymentSyncTimer) return;
|
|
1739
|
+
this.autoPaymentSyncTimer = setTimeout(() => {
|
|
1740
|
+
this.autoPaymentSyncTimer = null;
|
|
1741
|
+
void this.flushQueuedAutoPaymentSync();
|
|
1742
|
+
}, delay);
|
|
1743
|
+
}
|
|
1744
|
+
async flushQueuedAutoPaymentSync() {
|
|
1745
|
+
if (!this.store.order) return;
|
|
1746
|
+
if (!this.queuedAutoPaymentSync || this.autoPaymentSyncFlushing) return;
|
|
1747
|
+
if (this.store.order.getOrderSyncState() === 'submitting') {
|
|
1748
|
+
this.scheduleQueuedAutoPaymentSyncFlush();
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
this.autoPaymentSyncFlushing = true;
|
|
1752
|
+
try {
|
|
1753
|
+
while (this.queuedAutoPaymentSync) {
|
|
1754
|
+
this.queuedAutoPaymentSync = false;
|
|
1755
|
+
const payments = this.store.order.getOrderPayments();
|
|
1756
|
+
if (!payments.length) continue;
|
|
1757
|
+
await this.syncPaymentsToOrder({
|
|
1758
|
+
payments,
|
|
1759
|
+
paymentStatus: this.getPaymentStatusForSync(payments),
|
|
1760
|
+
submitWhenPaid: true,
|
|
1761
|
+
smallTicketDataFlag: 1
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
} catch (error) {
|
|
1765
|
+
this.logError('queued auto sync payments failed', {
|
|
1766
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1767
|
+
});
|
|
1768
|
+
} finally {
|
|
1769
|
+
this.autoPaymentSyncFlushing = false;
|
|
1770
|
+
if (this.queuedAutoPaymentSync) {
|
|
1771
|
+
this.scheduleQueuedAutoPaymentSyncFlush(0);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
/** 追加单个支付项到当前订单。 */
|
|
1777
|
+
addOrderPayment(payment) {
|
|
1778
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1779
|
+
const paymentRecord = payment;
|
|
1780
|
+
const paymentAmount = new _decimal.default(paymentRecord.amount || 0);
|
|
1781
|
+
if (paymentAmount.isZero()) {
|
|
1782
|
+
this.logWarning('addOrderPayment: 支付金额为 0,跳过添加支付项', {
|
|
1783
|
+
payment: paymentRecord
|
|
1784
|
+
});
|
|
1785
|
+
return this.store.order.getOrderPayments();
|
|
1786
|
+
}
|
|
1787
|
+
const metadata = paymentRecord.metadata && typeof paymentRecord.metadata === 'object' ? {
|
|
1788
|
+
...paymentRecord.metadata
|
|
1789
|
+
} : {};
|
|
1790
|
+
const shopWalletPassId = this.otherParams?.shop_wallet_pass_id;
|
|
1791
|
+
if (shopWalletPassId !== undefined && shopWalletPassId !== null) {
|
|
1792
|
+
metadata.shop_wallet_pass_id = shopWalletPassId;
|
|
1793
|
+
}
|
|
1794
|
+
if (!metadata.unique_payment_number) {
|
|
1795
|
+
metadata.unique_payment_number = (0, _utils.createUuidV4)();
|
|
1796
|
+
}
|
|
1797
|
+
const paymentTimestamp = (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss');
|
|
1798
|
+
const paymentTime = paymentRecord.payment_time ?? paymentTimestamp;
|
|
1799
|
+
const createdAt = paymentRecord.created_at ?? paymentTimestamp;
|
|
1800
|
+
const serviceCharge = paymentRecord.service_charge && typeof paymentRecord.service_charge === 'object' ? paymentRecord.service_charge : null;
|
|
1801
|
+
const calculatedServiceFee = serviceCharge ? new _decimal.default(paymentRecord.origin_amount ?? paymentRecord.amount ?? 0).times(serviceCharge.percentage || 0).plus(serviceCharge.amount || 0).toDecimalPlaces(2).toFixed(2) : undefined;
|
|
1802
|
+
const payments = this.store.order.addOrderPayment({
|
|
1803
|
+
...paymentRecord,
|
|
1804
|
+
...(calculatedServiceFee !== undefined ? {
|
|
1805
|
+
service_fee: calculatedServiceFee
|
|
1806
|
+
} : {}),
|
|
1807
|
+
metadata,
|
|
1808
|
+
payment_time: paymentTime,
|
|
1809
|
+
created_at: createdAt
|
|
1810
|
+
});
|
|
1811
|
+
const amountSnapshot = this.store.order.getOrderAmountSnapshot();
|
|
1812
|
+
const syncState = this.store.order.getOrderSyncState();
|
|
1813
|
+
if (amountSnapshot) {
|
|
1814
|
+
if (syncState === 'submitting') {
|
|
1815
|
+
this.queueLatestAutoPaymentSync();
|
|
1816
|
+
return payments;
|
|
1817
|
+
}
|
|
1818
|
+
void this.syncPaymentsToOrder({
|
|
1819
|
+
payments,
|
|
1820
|
+
paymentStatus: this.getPaymentStatusForSync(payments),
|
|
1821
|
+
submitWhenPaid: true,
|
|
1822
|
+
smallTicketDataFlag: 1
|
|
1823
|
+
}).catch(error => {
|
|
1824
|
+
this.logError('auto sync payments failed', {
|
|
1825
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1826
|
+
});
|
|
1827
|
+
});
|
|
1828
|
+
}
|
|
1829
|
+
return payments;
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
/** 更新当前订单中的指定支付项。 */
|
|
1833
|
+
updateOrderPayment(paymentIdentity, updates) {
|
|
1834
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1835
|
+
return this.store.order.updateOrderPayment(paymentIdentity, updates);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
/** 删除当前订单中的指定支付项。 */
|
|
1839
|
+
deleteOrderPayment(paymentIdentity) {
|
|
1840
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1841
|
+
return this.store.order.deleteOrderPayment(paymentIdentity);
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
/** 覆盖 wallet pass 支付项,同时保留普通支付项。 */
|
|
1845
|
+
updateVoucherOrderPayments(payments, options) {
|
|
1846
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1847
|
+
return this.store.order.updateVoucherOrderPayments(payments, options);
|
|
1848
|
+
}
|
|
1849
|
+
confirmPendingVoucherPayments() {
|
|
1850
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1851
|
+
return this.store.order.confirmPendingVoucherPayments();
|
|
1852
|
+
}
|
|
1853
|
+
discardPendingVoucherPayments() {
|
|
1854
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1855
|
+
return this.store.order.discardPendingVoucherPayments();
|
|
1856
|
+
}
|
|
1857
|
+
getWalletProductList() {
|
|
1858
|
+
const tempOrder = this.store.order?.getTempOrder();
|
|
1859
|
+
if (!tempOrder?.products?.length) return [];
|
|
1860
|
+
// 过滤出有 product_id的商品来,如果没有代表他是自定义商品,不需要参与 wallet
|
|
1861
|
+
// debugger
|
|
1862
|
+
// const products = tempOrder?.products?.filter(n => n.product_id)
|
|
1863
|
+
|
|
1864
|
+
const walletProducts = tempOrder?.products.map(product => {
|
|
1865
|
+
const metadata = product.metadata || {};
|
|
1866
|
+
const rawHolderId = product.holder_id ?? metadata.holder_id;
|
|
1867
|
+
const holderIdValue = Array.isArray(rawHolderId) ? rawHolderId[0] : rawHolderId;
|
|
1868
|
+
const holder_id = Number.isInteger(Number(holderIdValue)) ? Number(holderIdValue) : undefined;
|
|
1869
|
+
return {
|
|
1870
|
+
product_id: product.product_id || 0,
|
|
1871
|
+
product_variant_id: String(product.product_variant_id ?? ''),
|
|
1872
|
+
quantity: product.num || 1,
|
|
1873
|
+
is_price_include_tax: tempOrder.is_price_include_tax,
|
|
1874
|
+
is_charge_tax: product.is_charge_tax ?? 0,
|
|
1875
|
+
tax_fee: product.tax_fee,
|
|
1876
|
+
selling_price: Number(product.selling_price || 0),
|
|
1877
|
+
discount_list: product.discount_list || [],
|
|
1878
|
+
holder_id,
|
|
1879
|
+
original_price: product.original_price,
|
|
1880
|
+
main_product_original_price: metadata.main_product_original_price ?? product.original_price,
|
|
1881
|
+
main_product_selling_price: metadata.main_product_selling_price ?? product.selling_price,
|
|
1882
|
+
metadata: {
|
|
1883
|
+
main_product_attached_bundle_tax_fee: metadata.main_product_attached_bundle_tax_fee,
|
|
1884
|
+
main_product_attached_bundle_surcharge_fee: metadata.main_product_attached_bundle_surcharge_fee,
|
|
1885
|
+
surcharge_rounding_remainder: metadata.surcharge_rounding_remainder
|
|
1886
|
+
},
|
|
1887
|
+
product_bundle: (product.product_bundle || []).map(bundle => {
|
|
1888
|
+
const bundleMetadata = bundle.metadata || {};
|
|
1889
|
+
return {
|
|
1890
|
+
is_price_include_tax: tempOrder.is_price_include_tax,
|
|
1891
|
+
// 套餐子项在 tempOrder 上常为 id(如 1718),prepare/deduction 协议要求 bundle_id
|
|
1892
|
+
bundle_id: bundle.bundle_id ?? bundle.id,
|
|
1893
|
+
bundle_product_id: bundle.bundle_product_id ?? bundle._bundle_product_id,
|
|
1894
|
+
bundle_group_id: bundle.bundle_group_id ?? bundle.group_id,
|
|
1895
|
+
bundle_variant_id: bundle.bundle_variant_id,
|
|
1896
|
+
price_type: bundle.price_type,
|
|
1897
|
+
price_type_ext: bundle.price_type_ext,
|
|
1898
|
+
bundle_sum_price: bundle.bundle_sum_price,
|
|
1899
|
+
bundle_selling_price: bundle.bundle_selling_price,
|
|
1900
|
+
num: bundle.num,
|
|
1901
|
+
is_charge_tax: bundle.is_charge_tax,
|
|
1902
|
+
tax_fee: bundle.tax_fee,
|
|
1903
|
+
metadata: {
|
|
1904
|
+
surcharge_fee: bundleMetadata.surcharge_fee,
|
|
1905
|
+
surcharge_rounding_remainder: bundleMetadata.surcharge_rounding_remainder
|
|
1906
|
+
}
|
|
1907
|
+
};
|
|
1908
|
+
})
|
|
1909
|
+
};
|
|
1910
|
+
});
|
|
1911
|
+
return walletProducts;
|
|
1912
|
+
}
|
|
1913
|
+
async initWalletData(params) {
|
|
1914
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1915
|
+
if (!this.store.payment) throw new Error('payment 模块未初始化');
|
|
1916
|
+
const snapshot = this.store.order.getOrderSnapshot();
|
|
1917
|
+
const tempOrder = snapshot?.tempOrder;
|
|
1918
|
+
const amount = snapshot?.amount;
|
|
1919
|
+
if (!tempOrder || !amount) {
|
|
1920
|
+
return {
|
|
1921
|
+
walletRecommendList: [],
|
|
1922
|
+
userIdentificationCodes: [],
|
|
1923
|
+
transformList: [],
|
|
1924
|
+
noApplicableVoucher: [],
|
|
1925
|
+
products: []
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
const walletBusinessData = {
|
|
1929
|
+
customer_id: tempOrder.customer_id ? Number(tempOrder.customer_id) : undefined,
|
|
1930
|
+
holder: tempOrder.holder,
|
|
1931
|
+
amountInfo: {
|
|
1932
|
+
totalAmount: amount.totalAmount,
|
|
1933
|
+
subTotal: amount.summary.product_amount || amount.summary.product_expect_amount || amount.totalAmount,
|
|
1934
|
+
depositAmount: amount.depositAmount,
|
|
1935
|
+
isDeposit: tempOrder.is_deposit
|
|
1936
|
+
},
|
|
1937
|
+
products: this.getWalletProductList(),
|
|
1938
|
+
order_wait_pay_amount: params?.order_wait_pay_amount ?? Number(amount.amountGap || 0),
|
|
1939
|
+
is_price_include_tax: tempOrder.is_price_include_tax ?? this.otherParams.is_price_include_tax ?? 1,
|
|
1940
|
+
...(snapshot.identity.orderId ? {
|
|
1941
|
+
payment_order_id: String(snapshot.identity.orderId)
|
|
1942
|
+
} : {})
|
|
1943
|
+
};
|
|
1944
|
+
const result = await this.store.payment.wallet.initializeWalletDataFromBusinessAsync(walletBusinessData);
|
|
1945
|
+
await this.core.effects.emit(`${this.name}:onWalletDataInitialized`, {
|
|
1946
|
+
orderId: snapshot.identity.orderId,
|
|
1947
|
+
customerId: walletBusinessData.customer_id,
|
|
1948
|
+
walletBusinessData: {
|
|
1949
|
+
customer_id: walletBusinessData.customer_id,
|
|
1950
|
+
amountInfo: walletBusinessData.amountInfo,
|
|
1951
|
+
order_wait_pay_amount: walletBusinessData.order_wait_pay_amount
|
|
1952
|
+
},
|
|
1953
|
+
timestamp: Date.now()
|
|
1954
|
+
});
|
|
1955
|
+
return result;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
/**
|
|
1959
|
+
* 更新当前订单客户字段。
|
|
1960
|
+
*
|
|
1961
|
+
* 这是 PaymentModal 客户切换的订单事实入口;外部客户模块同步应由
|
|
1962
|
+
* 调用方通过 onCustomerChange 回调完成。
|
|
1963
|
+
*/
|
|
1964
|
+
updateOrderCustomer(customer) {
|
|
1965
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1966
|
+
const result = this.store.order.updateTempOrderCustomer(customer);
|
|
1967
|
+
this.store.order.recalculateSummary({
|
|
1968
|
+
createIfMissing: true
|
|
1969
|
+
});
|
|
1970
|
+
this.store.order.persistTempOrder();
|
|
1971
|
+
return result;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
/** 获取可用支付方式列表,供 PaymentModal 渲染支付选项。 */
|
|
1975
|
+
async getPaymentMethodsAsync() {
|
|
1976
|
+
// if (this.store.payment) {
|
|
1977
|
+
// return this.store.payment.getPayMethodListAsync();
|
|
1978
|
+
// }
|
|
1979
|
+
try {
|
|
1980
|
+
const response = await this.request.get('/pay/custom-payment/all', {
|
|
1981
|
+
filterPaymentMethods: true
|
|
1982
|
+
}, {
|
|
1983
|
+
osServer: true
|
|
1984
|
+
});
|
|
1985
|
+
const paymentMethods = response?.data || [];
|
|
1986
|
+
this.paymentMethodsCache = Array.isArray(paymentMethods) ? [...paymentMethods] : [];
|
|
1987
|
+
return this.getPaymentMethods();
|
|
1988
|
+
} catch (error) {
|
|
1989
|
+
this.logWarning('getPaymentMethodsAsync: 获取支付方式列表失败,使用缓存列表', {
|
|
1990
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1991
|
+
});
|
|
1992
|
+
return this.getPaymentMethods();
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
/** 同步读取初始化时缓存的支付方式列表。 */
|
|
1997
|
+
getPaymentMethods() {
|
|
1998
|
+
return [...this.paymentMethodsCache];
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
/** 轻量舍入能力,供现金支付 UI 计算 rounding_amount。 */
|
|
2002
|
+
roundAmount(params) {
|
|
2003
|
+
const amount = new _decimal.default(params.amount || 0);
|
|
2004
|
+
const interval = new _decimal.default(params.interval || 0.05);
|
|
2005
|
+
const rule = params.rule || 'standard_rounding';
|
|
2006
|
+
const quotient = amount.div(interval);
|
|
2007
|
+
let roundedQuotient;
|
|
2008
|
+
if (rule === 'always_up') {
|
|
2009
|
+
roundedQuotient = quotient.ceil();
|
|
2010
|
+
} else if (rule === 'always_down') {
|
|
2011
|
+
roundedQuotient = quotient.floor();
|
|
2012
|
+
} else if (rule === 'standard_down') {
|
|
2013
|
+
roundedQuotient = quotient.toDecimalPlaces(0, _decimal.default.ROUND_HALF_DOWN);
|
|
2014
|
+
} else {
|
|
2015
|
+
roundedQuotient = quotient.toDecimalPlaces(0, _decimal.default.ROUND_HALF_UP);
|
|
2016
|
+
}
|
|
2017
|
+
const roundedAmount = roundedQuotient.times(interval).toDecimalPlaces(2);
|
|
2018
|
+
return {
|
|
2019
|
+
originalAmount: amount.toFixed(2),
|
|
2020
|
+
roundedAmount: roundedAmount.toFixed(2),
|
|
2021
|
+
roundingDifference: roundedAmount.minus(amount).toFixed(2)
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
/**
|
|
2026
|
+
* 发送支付链接。
|
|
2027
|
+
*
|
|
2028
|
+
* 如果没有 orderIds 或显式 submitBeforeSend,会先提交当前本地订单到云端;
|
|
2029
|
+
* 只有成功拿到远端 order_id 后才调用 OrderModule 发送邮件。
|
|
2030
|
+
*/
|
|
2031
|
+
async sendCustomerPayLink(params) {
|
|
2032
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
2033
|
+
let orderIds = params.order_ids || params.orderIds || [];
|
|
2034
|
+
if (!orderIds.length || params.submitBeforeSend) {
|
|
2035
|
+
const submitResult = await this.submitSalesOrder({
|
|
2036
|
+
smallTicketDataFlag: 1
|
|
2037
|
+
});
|
|
2038
|
+
const orderId = submitResult?.data?.order_id ?? submitResult?.order_id ?? submitResult?.data?.id ?? submitResult?.id;
|
|
2039
|
+
if (!orderId) {
|
|
2040
|
+
throw new Error('本地订单提交云端失败,无法发送支付链接');
|
|
2041
|
+
}
|
|
2042
|
+
orderIds = [orderId];
|
|
2043
|
+
}
|
|
2044
|
+
return this.store.order.sendCustomerPayLink({
|
|
2045
|
+
emails: params.emails,
|
|
2046
|
+
notify_action: params.notify_action,
|
|
2047
|
+
order_ids: orderIds
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
transformBaseProductToOrderProduct(params) {
|
|
2051
|
+
return (0, _transformBaseProductToOrderProduct.transformBaseProductToOrderProduct)(params);
|
|
2052
|
+
}
|
|
2053
|
+
async calculateProductBookingPrice(params) {
|
|
2054
|
+
const quotation = this.store.quotation;
|
|
2055
|
+
const customerId = params.customer_id ?? this.getCurrentOrderCustomerId();
|
|
2056
|
+
const authoritativeProduct = await this.loadProductForPriceQuery(params, customerId);
|
|
2057
|
+
const product = this.mergeProductForPriceQuery(params.product, authoritativeProduct);
|
|
2058
|
+
await this.loadQuotationForPriceQuery({
|
|
2059
|
+
quotation,
|
|
2060
|
+
customer_id: customerId,
|
|
2061
|
+
channel: params.channel
|
|
2062
|
+
});
|
|
2063
|
+
return (0, _quotationPrice.calculateBaseSalesProductBookingPrice)({
|
|
2064
|
+
...params,
|
|
2065
|
+
product,
|
|
2066
|
+
fallback_price: authoritativeProduct ? undefined : params.fallback_price,
|
|
2067
|
+
customer_id: customerId,
|
|
2068
|
+
quotation
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
getQuotationCustomerScopeInfo() {
|
|
2072
|
+
const quotation = this.store.quotation;
|
|
2073
|
+
if (quotation && typeof quotation.getQuotationCustomerScopeInfo === 'function') {
|
|
2074
|
+
return quotation.getQuotationCustomerScopeInfo();
|
|
2075
|
+
}
|
|
2076
|
+
const list = typeof quotation?.getQuotationList === 'function' ? quotation.getQuotationList() : [];
|
|
2077
|
+
const customerById = new Map();
|
|
2078
|
+
const quotationScopes = (Array.isArray(list) ? list : []).filter(item => Array.isArray(item?.customer) && item.customer.length > 0).map(item => {
|
|
2079
|
+
const localSeen = new Set();
|
|
2080
|
+
const customers = item.customer.filter(customer => {
|
|
2081
|
+
const customerId = this.normalizePriceContextCustomerId(customer?.id);
|
|
2082
|
+
if (!customerId || localSeen.has(customerId)) return false;
|
|
2083
|
+
localSeen.add(customerId);
|
|
2084
|
+
if (!customerById.has(customerId)) customerById.set(customerId, customer);
|
|
2085
|
+
return true;
|
|
2086
|
+
});
|
|
2087
|
+
return {
|
|
2088
|
+
quotationId: item.id,
|
|
2089
|
+
quotationName: item.name,
|
|
2090
|
+
customerIds: customers.map(customer => String(customer.id)),
|
|
2091
|
+
customers
|
|
2092
|
+
};
|
|
2093
|
+
});
|
|
2094
|
+
return {
|
|
2095
|
+
quotationCount: Array.isArray(list) ? list.length : 0,
|
|
2096
|
+
hasCustomerScopedQuotations: quotationScopes.length > 0,
|
|
2097
|
+
allQuotationsCustomerScoped: Array.isArray(list) && list.length > 0 && quotationScopes.length === list.length,
|
|
2098
|
+
customerIds: Array.from(customerById.keys()),
|
|
2099
|
+
customers: Array.from(customerById.values()),
|
|
2100
|
+
quotationScopes
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
shouldRecalculateProductBookingPricesForContextChange(params) {
|
|
2104
|
+
const scopeInfo = this.getQuotationCustomerScopeInfo();
|
|
2105
|
+
const scopedCustomerIds = new Set(scopeInfo.customerIds.map(id => String(id)));
|
|
2106
|
+
const previousCustomerId = this.normalizePriceContextCustomerId(params.previousCustomerId);
|
|
2107
|
+
const nextCustomerId = this.normalizePriceContextCustomerId(params.nextCustomerId);
|
|
2108
|
+
const previousInScope = previousCustomerId ? scopedCustomerIds.has(previousCustomerId) : false;
|
|
2109
|
+
const nextInScope = nextCustomerId ? scopedCustomerIds.has(nextCustomerId) : false;
|
|
2110
|
+
let shouldRecalculate = false;
|
|
2111
|
+
if (scopeInfo.hasCustomerScopedQuotations) {
|
|
2112
|
+
if (previousInScope) {
|
|
2113
|
+
shouldRecalculate = true;
|
|
2114
|
+
} else if (!previousCustomerId && !nextInScope) {
|
|
2115
|
+
shouldRecalculate = false;
|
|
2116
|
+
} else {
|
|
2117
|
+
shouldRecalculate = nextInScope;
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
return shouldRecalculate;
|
|
2121
|
+
}
|
|
2122
|
+
async calculateProductBookingPrices(paramsList) {
|
|
2123
|
+
if (!paramsList.length) return [];
|
|
2124
|
+
const quotation = this.store.quotation;
|
|
2125
|
+
const customerId = paramsList[0]?.customer_id ?? this.getCurrentOrderCustomerId();
|
|
2126
|
+
const productMapsByGroup = new Map();
|
|
2127
|
+
const groups = new Map();
|
|
2128
|
+
paramsList.forEach(params => {
|
|
2129
|
+
const product = params.product || {};
|
|
2130
|
+
const productId = this.getPriceQueryProductId(product);
|
|
2131
|
+
if (productId === null) return;
|
|
2132
|
+
const schedule = this.getPriceQuerySchedule(params);
|
|
2133
|
+
const channel = params.channel || this.otherParams?.channel;
|
|
2134
|
+
const groupKey = [schedule.schedule_date, schedule.schedule_datetime, channel || ''].join('|');
|
|
2135
|
+
const group = groups.get(groupKey) || {
|
|
2136
|
+
ids: new Set(),
|
|
2137
|
+
schedule,
|
|
2138
|
+
channel
|
|
2139
|
+
};
|
|
2140
|
+
group.ids.add(productId);
|
|
2141
|
+
groups.set(groupKey, group);
|
|
2142
|
+
});
|
|
2143
|
+
await Promise.all(Array.from(groups.entries()).map(async ([groupKey, group]) => {
|
|
2144
|
+
const productsById = await this.loadProductsForPriceQuery({
|
|
2145
|
+
ids: Array.from(group.ids),
|
|
2146
|
+
schedule: group.schedule,
|
|
2147
|
+
customerId,
|
|
2148
|
+
channel: group.channel
|
|
2149
|
+
});
|
|
2150
|
+
productMapsByGroup.set(groupKey, productsById);
|
|
2151
|
+
}));
|
|
2152
|
+
await this.loadQuotationForPriceQuery({
|
|
2153
|
+
quotation,
|
|
2154
|
+
customer_id: customerId,
|
|
2155
|
+
channel: paramsList[0]?.channel
|
|
2156
|
+
});
|
|
2157
|
+
return paramsList.map(params => {
|
|
2158
|
+
const productInput = params.product || {};
|
|
2159
|
+
const productId = this.getPriceQueryProductId(productInput);
|
|
2160
|
+
const schedule = this.getPriceQuerySchedule(params);
|
|
2161
|
+
const channel = params.channel || this.otherParams?.channel;
|
|
2162
|
+
const groupKey = [schedule.schedule_date, schedule.schedule_datetime, channel || ''].join('|');
|
|
2163
|
+
const authoritativeProduct = productId === null ? null : productMapsByGroup.get(groupKey)?.get(productId) || null;
|
|
2164
|
+
const product = this.mergeProductForPriceQuery(productInput, authoritativeProduct);
|
|
2165
|
+
return (0, _quotationPrice.calculateBaseSalesProductBookingPrice)({
|
|
2166
|
+
...params,
|
|
2167
|
+
product,
|
|
2168
|
+
fallback_price: authoritativeProduct ? undefined : params.fallback_price,
|
|
2169
|
+
customer_id: customerId,
|
|
2170
|
+
quotation
|
|
2171
|
+
});
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
async addProductToOrder(product, booking) {
|
|
2175
|
+
try {
|
|
2176
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
2177
|
+
const products = await this.store.order.addProductToOrder(product, booking);
|
|
2178
|
+
// // 需要叠加
|
|
2179
|
+
// if (this.hooks.onAddProductOverlay) {
|
|
2180
|
+
// const res = this.hooks.onAddProductOverlay(是否叠加, product)
|
|
2181
|
+
// if (!res) {
|
|
2182
|
+
// 单独添加商品
|
|
2183
|
+
// } else {
|
|
2184
|
+
// 叠加
|
|
2185
|
+
// }
|
|
2186
|
+
// } else {
|
|
2187
|
+
// 叠加
|
|
2188
|
+
// }
|
|
2189
|
+
return products;
|
|
2190
|
+
} catch (error) {
|
|
2191
|
+
throw error;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
async updateOrderProduct(params) {
|
|
2195
|
+
try {
|
|
2196
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
2197
|
+
const products = await this.store.order.updateOrderProduct(params);
|
|
2198
|
+
return products;
|
|
2199
|
+
} catch (error) {
|
|
2200
|
+
throw error;
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
async updateOrderProducts(paramsList) {
|
|
2204
|
+
try {
|
|
2205
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
2206
|
+
const products = await this.store.order.updateOrderProducts(paramsList);
|
|
2207
|
+
return products;
|
|
2208
|
+
} catch (error) {
|
|
2209
|
+
throw error;
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
async updateOrderProductQuantity(params) {
|
|
2213
|
+
try {
|
|
2214
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
2215
|
+
const products = await this.store.order.updateOrderProductQuantity(params);
|
|
2216
|
+
return products;
|
|
2217
|
+
} catch (error) {
|
|
2218
|
+
throw error;
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
updateOrderBooking(params) {
|
|
2222
|
+
try {
|
|
2223
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
2224
|
+
return this.store.order.updateOrderBooking(params);
|
|
2225
|
+
} catch (error) {
|
|
2226
|
+
throw error;
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
/**
|
|
2231
|
+
* 设置单行商品备注(与整单 `updateTempOrderNote` 区分)。
|
|
2232
|
+
* 多行同 SKU 时必须传入与删除/更新一致的 unique_identification_number。
|
|
2233
|
+
*/
|
|
2234
|
+
async setOrderProductLineNote(identity, note) {
|
|
2235
|
+
try {
|
|
2236
|
+
const params = {
|
|
2237
|
+
product_id: identity.product_id,
|
|
2238
|
+
product_variant_id: identity.product_variant_id,
|
|
2239
|
+
updates: {
|
|
2240
|
+
note: String(note || '')
|
|
2241
|
+
}
|
|
2242
|
+
};
|
|
2243
|
+
if (identity.unique_identification_number !== undefined) {
|
|
2244
|
+
params.unique_identification_number = identity.unique_identification_number;
|
|
2245
|
+
}
|
|
2246
|
+
if (identity.product_sku !== undefined) {
|
|
2247
|
+
params.product_sku = identity.product_sku;
|
|
2248
|
+
}
|
|
2249
|
+
if (identity.product_bundle !== undefined) params.product_bundle = identity.product_bundle;
|
|
2250
|
+
const products = await this.updateOrderProduct(params);
|
|
2251
|
+
return products;
|
|
2252
|
+
} catch (error) {
|
|
2253
|
+
throw error;
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
/**
|
|
2258
|
+
* 批量删除购物车行,末尾仅一次促销重算。
|
|
2259
|
+
*
|
|
2260
|
+
* @example
|
|
2261
|
+
* await baseSales.removeProductsFromOrder([identityA, identityB]);
|
|
2262
|
+
*/
|
|
2263
|
+
async removeProductsFromOrder(identities) {
|
|
2264
|
+
try {
|
|
2265
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
2266
|
+
return await this.store.order.removeProductsFromOrder(identities);
|
|
2267
|
+
} catch (error) {
|
|
2268
|
+
throw error;
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
async removeProductFromOrder(identity) {
|
|
2272
|
+
return this.removeProductsFromOrder([identity]);
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
// ─── 促销/赠品 透传(迁移自 booking usePromotion) ────────────────
|
|
2276
|
+
|
|
2277
|
+
/**
|
|
2278
|
+
* 注入促销评估器到底层 OrderModule。
|
|
2279
|
+
*
|
|
2280
|
+
* @example
|
|
2281
|
+
* solution.setPromotionEvaluator(appHelper.utils.promotionEvaluator);
|
|
2282
|
+
*/
|
|
2283
|
+
setPromotionEvaluator(evaluator) {
|
|
2284
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
2285
|
+
this.store.order.setPromotionEvaluator(evaluator);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
/**
|
|
2289
|
+
* 注入赠品选择 resolver。UI 层(SalesSdk)通过 bridge 提供。
|
|
2290
|
+
*
|
|
2291
|
+
* @example
|
|
2292
|
+
* solution.setGiftSelectResolver(async (ctx) => bridge.request(ctx));
|
|
2293
|
+
*/
|
|
2294
|
+
setGiftSelectResolver(resolver) {
|
|
2295
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
2296
|
+
this.store.order.setGiftSelectResolver(resolver);
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
/**
|
|
2300
|
+
* 为商品目录追加促销标签,供 SalesSdkProductProvider 等商品列表入口复用。
|
|
2301
|
+
*
|
|
2302
|
+
* @example
|
|
2303
|
+
* const products = solution.appendPromotionTags(catalogProducts);
|
|
2304
|
+
*/
|
|
2305
|
+
appendPromotionTags(products) {
|
|
2306
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
2307
|
+
return this.store.order.appendPromotionTags(products);
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
/**
|
|
2311
|
+
* 主动触发一次促销应用(一般写路径会自动触发,少数场景如客户切换后可手动调)。
|
|
2312
|
+
*/
|
|
2313
|
+
async applyPromotion() {
|
|
2314
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
2315
|
+
await this.store.order.applyPromotion();
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
/** 最近一次促销计算输出的未满足提示。 */
|
|
2319
|
+
getUnfulfilledPromotions() {
|
|
2320
|
+
return this.store.order?.getUnfulfilledPromotions() || [];
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
/** 最近一次促销计算输出的赠品操作 diff。 */
|
|
2324
|
+
getLastGiftActions() {
|
|
2325
|
+
return this.store.order?.getLastGiftActions() || null;
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
// 获取商品列表
|
|
2329
|
+
// TODO 需要跟 webpos 内的对齐一下
|
|
2330
|
+
async getProductList() {
|
|
2331
|
+
// 可以直接通过配置里的 menu 读取
|
|
2332
|
+
const menu_list_ids = this.otherParams?.dineInConfig?.['menu.associated_menus'].map(n => Number(n.value)) || [];
|
|
2333
|
+
try {
|
|
2334
|
+
const res = await this.store.products?.loadProducts({
|
|
2335
|
+
menu_list_ids,
|
|
2336
|
+
cacheId: this.cacheId,
|
|
2337
|
+
schedule_date: (0, _dayjs.default)().format('YYYY-MM-DD'),
|
|
2338
|
+
schedule_datetime: (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss')
|
|
2339
|
+
});
|
|
2340
|
+
return res;
|
|
2341
|
+
} catch (error) {
|
|
2342
|
+
throw error;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
getOtherParams() {
|
|
2346
|
+
return this.otherParams;
|
|
2347
|
+
}
|
|
2348
|
+
async setOtherParams(params, {
|
|
2349
|
+
cover = false
|
|
2350
|
+
} = {}) {
|
|
2351
|
+
if (cover) {
|
|
2352
|
+
this.otherParams = {
|
|
2353
|
+
...params
|
|
2354
|
+
};
|
|
2355
|
+
} else {
|
|
2356
|
+
this.otherParams = {
|
|
2357
|
+
...this.otherParams,
|
|
2358
|
+
...params
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
this.cacheId = this.otherParams?.cacheId;
|
|
2362
|
+
await this.syncOtherParamsToSubModules(params, {
|
|
2363
|
+
cover
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
exports.BaseSalesImpl = BaseSalesImpl;
|