@pisell/pisellos 0.0.568 → 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/model/strategy/adapter/promotion/evaluator.js +3 -2
- package/dist/model/strategy/adapter/walletPass/utils.js +7 -4
- package/dist/modules/BaseModule.d.ts +2 -2
- package/dist/modules/BaseModule.js +4 -4
- package/dist/modules/BookingContext/index.d.ts +48 -0
- package/dist/modules/BookingContext/index.js +566 -0
- package/dist/modules/BookingContext/types.d.ts +102 -0
- package/dist/modules/BookingContext/types.js +51 -0
- package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.d.ts +16 -0
- package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +302 -0
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +8 -0
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +149 -0
- package/dist/modules/BookingContext/utils/cacheItemToBookingInput.d.ts +76 -0
- package/dist/modules/BookingContext/utils/cacheItemToBookingInput.js +249 -0
- package/dist/modules/BookingContext/utils/flexible.d.ts +28 -0
- package/dist/modules/BookingContext/utils/flexible.js +56 -0
- package/dist/modules/BookingContext/utils/formatResourceList.d.ts +8 -0
- package/dist/modules/BookingContext/utils/formatResourceList.js +38 -0
- package/dist/modules/BookingContext/utils/index.d.ts +11 -0
- package/dist/modules/BookingContext/utils/index.js +11 -0
- package/dist/modules/BookingContext/utils/noResource.d.ts +13 -0
- package/dist/modules/BookingContext/utils/noResource.js +77 -0
- package/dist/modules/BookingContext/utils/orderLineDisplay.d.ts +1 -0
- package/dist/modules/BookingContext/utils/orderLineDisplay.js +36 -0
- package/dist/modules/BookingContext/utils/productExtend.d.ts +72 -0
- package/dist/modules/BookingContext/utils/productExtend.js +339 -0
- package/dist/modules/BookingContext/utils/resourceErrors.d.ts +41 -0
- package/dist/modules/BookingContext/utils/resourceErrors.js +74 -0
- package/dist/modules/BookingContext/utils/resourceSelection.d.ts +7 -0
- package/dist/modules/BookingContext/utils/resourceSelection.js +24 -0
- package/dist/modules/BookingContext/utils/resources.d.ts +80 -0
- package/dist/modules/BookingContext/utils/resources.js +486 -0
- package/dist/modules/BookingContext/utils/serviceTimes.d.ts +55 -0
- package/dist/modules/BookingContext/utils/serviceTimes.js +151 -0
- package/dist/modules/BookingContext/utils/timeSlices.d.ts +42 -0
- package/dist/modules/BookingContext/utils/timeSlices.js +100 -0
- package/dist/modules/Cart/utils/cartProduct.js +26 -5
- package/dist/modules/Cart/utils/changePrice.js +27 -20
- package/dist/modules/Customer/index.d.ts +20 -0
- package/dist/modules/Customer/index.js +172 -83
- package/dist/modules/Customer/types.d.ts +2 -0
- package/dist/modules/Discount/index.d.ts +2 -1
- package/dist/modules/Discount/index.js +37 -9
- package/dist/modules/Discount/types.d.ts +1 -0
- package/dist/modules/OpenData/index.d.ts +8 -0
- package/dist/modules/OpenData/index.js +36 -16
- package/dist/modules/Order/index.d.ts +448 -23
- package/dist/modules/Order/index.js +5417 -561
- package/dist/modules/Order/types.d.ts +693 -88
- package/dist/modules/Order/types.js +68 -1
- package/dist/modules/Order/utils/bundleDiscountHydration.d.ts +5 -0
- package/dist/modules/Order/utils/bundleDiscountHydration.js +144 -0
- package/dist/modules/Order/utils/manualProductDiscount.d.ts +106 -0
- package/dist/modules/Order/utils/manualProductDiscount.js +210 -0
- package/dist/modules/Order/utils/orderCollectionIdentity.d.ts +53 -0
- package/dist/modules/Order/utils/orderCollectionIdentity.js +405 -0
- package/dist/modules/Order/utils.d.ts +167 -18
- package/dist/modules/Order/utils.js +960 -165
- package/dist/modules/Payment/index.d.ts +10 -9
- package/dist/modules/Payment/index.js +465 -703
- package/dist/modules/Payment/types.d.ts +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 +44 -29
- 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 +221 -109
- 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/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/server/index.d.ts +462 -8
- package/dist/server/index.js +6112 -656
- package/dist/server/modules/floor-plan/index.d.ts +39 -0
- package/dist/server/modules/floor-plan/index.js +595 -0
- package/dist/server/modules/floor-plan/types.d.ts +43 -0
- package/dist/server/modules/floor-plan/types.js +13 -0
- package/dist/server/modules/index.d.ts +7 -2
- package/dist/server/modules/index.js +6 -0
- package/dist/server/modules/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 +1393 -516
- 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 +278 -66
- package/dist/server/modules/resource/index.d.ts +2 -3
- package/dist/server/modules/resource/index.js +108 -86
- package/dist/server/modules/schedule/index.d.ts +6 -4
- package/dist/server/modules/schedule/index.js +116 -85
- package/dist/server/test.json +713 -0
- package/dist/server/types.d.ts +2 -0
- package/dist/server/utils/product.js +2 -2
- package/dist/server/utils/small-ticket.d.ts +71 -0
- package/dist/server/utils/small-ticket.js +963 -0
- package/dist/solution/BaseSales/index.d.ts +378 -0
- package/dist/solution/BaseSales/index.js +4006 -0
- package/dist/solution/BaseSales/types.d.ts +394 -0
- package/dist/solution/BaseSales/types.js +40 -0
- package/dist/solution/BaseSales/utils/cartPromotion.d.ts +287 -0
- package/dist/solution/BaseSales/utils/cartPromotion.js +1450 -0
- package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +144 -0
- package/dist/solution/BaseSales/utils/parseSalesResponse.js +157 -0
- package/dist/solution/BaseSales/utils/quotationPrice.d.ts +18 -0
- package/dist/solution/BaseSales/utils/quotationPrice.js +250 -0
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +63 -0
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +218 -0
- package/dist/solution/BaseSales/utils.d.ts +40 -0
- package/dist/solution/BaseSales/utils.js +263 -0
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +394 -25
- package/dist/solution/BookingTicket/index.js +2506 -443
- package/dist/solution/BookingTicket/types.d.ts +191 -6
- package/dist/solution/BookingTicket/types.js +83 -10
- package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +117 -0
- package/dist/solution/BookingTicket/utils/addProductDecision.js +346 -0
- package/dist/solution/BookingTicket/utils/addTimeAvailability.d.ts +35 -0
- package/dist/solution/BookingTicket/utils/addTimeAvailability.js +95 -0
- package/dist/solution/BookingTicket/utils/bookingStatus.d.ts +77 -0
- package/dist/solution/BookingTicket/utils/bookingStatus.js +136 -0
- package/dist/solution/BookingTicket/utils/cartView.d.ts +29 -0
- package/dist/solution/BookingTicket/utils/cartView.js +209 -0
- package/dist/solution/BookingTicket/utils/exampleData.d.ts +123 -0
- package/dist/solution/BookingTicket/utils/exampleData.js +183 -0
- package/dist/solution/BookingTicket/utils/orderCustomer.d.ts +4 -0
- package/dist/solution/BookingTicket/utils/orderCustomer.js +31 -0
- package/dist/solution/BookingTicket/utils/resolveBestAddTimePlan.d.ts +185 -0
- package/dist/solution/BookingTicket/utils/resolveBestAddTimePlan.js +438 -0
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +36 -0
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +400 -0
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.d.ts +4 -0
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +41 -1
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +32 -0
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +56 -0
- package/dist/solution/BookingTicket/utils/scan/handleScan.d.ts +3 -1
- package/dist/solution/BookingTicket/utils/scan/handleScan.js +11 -3
- package/dist/solution/BookingTicket/utils/scan/index.d.ts +5 -0
- package/dist/solution/BookingTicket/utils/scan/index.js +20 -8
- package/dist/solution/BookingTicket/utils/scan/scanProductValues.d.ts +21 -0
- package/dist/solution/BookingTicket/utils/scan/scanProductValues.js +150 -0
- package/dist/solution/BookingTicket/utils/sessionCatalogStale.d.ts +23 -0
- package/dist/solution/BookingTicket/utils/sessionCatalogStale.js +78 -0
- package/dist/solution/Checkout/index.d.ts +1 -0
- package/dist/solution/Checkout/index.js +25 -34
- package/dist/solution/Checkout/utils/index.js +4 -4
- package/dist/solution/RegisterAndLogin/config.js +2 -10
- package/dist/solution/Sales/index.d.ts +50 -5
- package/dist/solution/Sales/index.js +484 -92
- package/dist/solution/Sales/types.d.ts +26 -2
- package/dist/solution/ScanOrder/index.d.ts +27 -21
- package/dist/solution/ScanOrder/index.js +499 -434
- 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 +4 -4
- package/dist/solution/UnifiedBookingSales/index.d.ts +183 -0
- package/dist/solution/UnifiedBookingSales/index.js +1871 -0
- package/dist/solution/UnifiedBookingSales/types.d.ts +143 -0
- package/dist/solution/UnifiedBookingSales/types.js +11 -0
- package/dist/solution/VenueBooking/index.d.ts +29 -20
- package/dist/solution/VenueBooking/index.js +301 -213
- package/dist/solution/VenueBooking/sales-section-4-annotated.json +0 -2
- package/dist/solution/VenueBooking/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/lib/model/strategy/adapter/promotion/evaluator.js +3 -2
- package/lib/model/strategy/adapter/walletPass/utils.js +6 -4
- package/lib/modules/BaseModule.d.ts +2 -2
- package/lib/modules/BaseModule.js +4 -4
- package/lib/modules/BookingContext/index.d.ts +48 -0
- package/lib/modules/BookingContext/index.js +390 -0
- package/lib/modules/BookingContext/types.d.ts +102 -0
- package/lib/modules/BookingContext/types.js +48 -0
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.d.ts +16 -0
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +286 -0
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +8 -0
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +148 -0
- package/lib/modules/BookingContext/utils/cacheItemToBookingInput.d.ts +76 -0
- package/lib/modules/BookingContext/utils/cacheItemToBookingInput.js +242 -0
- package/lib/modules/BookingContext/utils/flexible.d.ts +28 -0
- package/lib/modules/BookingContext/utils/flexible.js +63 -0
- package/lib/modules/BookingContext/utils/formatResourceList.d.ts +8 -0
- package/lib/modules/BookingContext/utils/formatResourceList.js +35 -0
- package/lib/modules/BookingContext/utils/index.d.ts +11 -0
- package/lib/modules/BookingContext/utils/index.js +126 -0
- package/lib/modules/BookingContext/utils/noResource.d.ts +13 -0
- package/lib/modules/BookingContext/utils/noResource.js +78 -0
- package/lib/modules/BookingContext/utils/orderLineDisplay.d.ts +1 -0
- package/lib/modules/BookingContext/utils/orderLineDisplay.js +29 -0
- package/lib/modules/BookingContext/utils/productExtend.d.ts +72 -0
- package/lib/modules/BookingContext/utils/productExtend.js +330 -0
- package/lib/modules/BookingContext/utils/resourceErrors.d.ts +41 -0
- package/lib/modules/BookingContext/utils/resourceErrors.js +80 -0
- package/lib/modules/BookingContext/utils/resourceSelection.d.ts +7 -0
- package/lib/modules/BookingContext/utils/resourceSelection.js +24 -0
- package/lib/modules/BookingContext/utils/resources.d.ts +80 -0
- package/lib/modules/BookingContext/utils/resources.js +419 -0
- package/lib/modules/BookingContext/utils/serviceTimes.d.ts +55 -0
- package/lib/modules/BookingContext/utils/serviceTimes.js +161 -0
- package/lib/modules/BookingContext/utils/timeSlices.d.ts +42 -0
- package/lib/modules/BookingContext/utils/timeSlices.js +114 -0
- package/lib/modules/Cart/utils/cartProduct.js +27 -5
- package/lib/modules/Cart/utils/changePrice.js +20 -14
- package/lib/modules/Customer/index.d.ts +20 -0
- package/lib/modules/Customer/index.js +63 -10
- package/lib/modules/Customer/types.d.ts +2 -0
- package/lib/modules/Discount/index.d.ts +2 -1
- package/lib/modules/Discount/index.js +18 -2
- package/lib/modules/Discount/types.d.ts +1 -0
- package/lib/modules/OpenData/index.d.ts +8 -0
- package/lib/modules/OpenData/index.js +21 -3
- package/lib/modules/Order/index.d.ts +448 -23
- package/lib/modules/Order/index.js +3641 -308
- package/lib/modules/Order/types.d.ts +693 -88
- package/lib/modules/Order/types.js +55 -1
- package/lib/modules/Order/utils/bundleDiscountHydration.d.ts +5 -0
- package/lib/modules/Order/utils/bundleDiscountHydration.js +106 -0
- package/lib/modules/Order/utils/manualProductDiscount.d.ts +106 -0
- package/lib/modules/Order/utils/manualProductDiscount.js +207 -0
- package/lib/modules/Order/utils/orderCollectionIdentity.d.ts +53 -0
- package/lib/modules/Order/utils/orderCollectionIdentity.js +353 -0
- package/lib/modules/Order/utils.d.ts +167 -18
- package/lib/modules/Order/utils.js +974 -138
- package/lib/modules/Payment/index.d.ts +10 -9
- package/lib/modules/Payment/index.js +30 -124
- package/lib/modules/Payment/types.d.ts +40 -16
- package/lib/modules/Payment/utils.d.ts +1 -1
- package/lib/modules/Payment/utils.js +4 -3
- package/lib/modules/Payment/walletpass.js +16 -6
- package/lib/modules/Product/types.d.ts +29 -7
- package/lib/modules/ProductList/index.d.ts +17 -13
- package/lib/modules/ProductList/index.js +92 -51
- package/lib/modules/ProductList/types.d.ts +17 -0
- package/lib/modules/Quotation/index.d.ts +22 -2
- package/lib/modules/Quotation/index.js +76 -20
- package/lib/modules/Quotation/types.d.ts +19 -0
- package/lib/modules/ResourcePlanner/index.d.ts +24 -0
- package/lib/modules/ResourcePlanner/index.js +147 -0
- package/lib/modules/ResourcePlanner/planner.d.ts +14 -0
- package/lib/modules/ResourcePlanner/planner.js +895 -0
- package/lib/modules/ResourcePlanner/types.d.ts +227 -0
- package/lib/modules/ResourcePlanner/types.js +5 -0
- package/lib/modules/Rules/index.d.ts +8 -10
- package/lib/modules/Rules/index.js +152 -60
- package/lib/modules/Rules/types.d.ts +14 -3
- package/lib/modules/SalesSummary/index.d.ts +23 -26
- package/lib/modules/SalesSummary/index.js +84 -16
- package/lib/modules/SalesSummary/types.d.ts +9 -1
- package/lib/modules/SalesSummary/utils.d.ts +3 -22
- package/lib/modules/SalesSummary/utils.js +616 -111
- package/lib/modules/Schedule/getDateIsInSchedule.js +14 -7
- package/lib/modules/Schedule/index.d.ts +18 -5
- package/lib/modules/Schedule/index.js +49 -14
- package/lib/modules/Schedule/types.d.ts +2 -0
- package/lib/modules/Summary/index.js +2 -2
- package/lib/modules/SurchargeList/index.d.ts +18 -0
- package/lib/modules/SurchargeList/index.js +95 -0
- package/lib/modules/SurchargeList/types.d.ts +15 -0
- package/lib/modules/SurchargeList/types.js +5 -0
- package/lib/modules/index.d.ts +3 -0
- package/lib/modules/index.js +33 -0
- package/lib/plugins/app.d.ts +2 -0
- package/lib/plugins/request.d.ts +1 -0
- package/lib/server/index.d.ts +462 -8
- package/lib/server/index.js +3901 -205
- package/lib/server/modules/floor-plan/index.d.ts +39 -0
- package/lib/server/modules/floor-plan/index.js +306 -0
- package/lib/server/modules/floor-plan/types.d.ts +43 -0
- package/lib/server/modules/floor-plan/types.js +19 -0
- package/lib/server/modules/index.d.ts +7 -2
- package/lib/server/modules/index.js +21 -0
- package/lib/server/modules/order/index.d.ts +207 -14
- package/lib/server/modules/order/index.js +1324 -141
- package/lib/server/modules/order/types.d.ts +76 -5
- package/lib/server/modules/order/types.js +11 -1
- package/lib/server/modules/order/utils/filterBookings.d.ts +7 -1
- package/lib/server/modules/order/utils/filterBookings.js +119 -11
- package/lib/server/modules/order/utils/filterOrders.js +238 -13
- package/lib/server/modules/payment/index.d.ts +28 -0
- package/lib/server/modules/payment/index.js +168 -0
- package/lib/server/modules/payment/types.d.ts +9 -0
- package/lib/server/modules/payment/types.js +5 -0
- package/lib/server/modules/products/index.d.ts +105 -14
- package/lib/server/modules/products/index.js +588 -97
- package/lib/server/modules/products/types.d.ts +34 -1
- package/lib/server/modules/products/types.js +3 -0
- package/lib/server/modules/quotation/index.d.ts +39 -0
- package/lib/server/modules/quotation/index.js +145 -1
- package/lib/server/modules/resource/index.d.ts +2 -3
- package/lib/server/modules/resource/index.js +71 -45
- package/lib/server/modules/schedule/index.d.ts +6 -4
- package/lib/server/modules/schedule/index.js +55 -44
- package/lib/server/test.json +713 -0
- package/lib/server/types.d.ts +2 -0
- package/lib/server/utils/product.js +2 -2
- package/lib/server/utils/small-ticket.d.ts +71 -0
- package/lib/server/utils/small-ticket.js +926 -0
- package/lib/solution/BaseSales/index.d.ts +378 -0
- package/lib/solution/BaseSales/index.js +2367 -0
- package/lib/solution/BaseSales/types.d.ts +394 -0
- package/lib/solution/BaseSales/types.js +47 -0
- package/lib/solution/BaseSales/utils/cartPromotion.d.ts +287 -0
- package/lib/solution/BaseSales/utils/cartPromotion.js +1155 -0
- package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +144 -0
- package/lib/solution/BaseSales/utils/parseSalesResponse.js +140 -0
- package/lib/solution/BaseSales/utils/quotationPrice.d.ts +18 -0
- package/lib/solution/BaseSales/utils/quotationPrice.js +231 -0
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +63 -0
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +215 -0
- package/lib/solution/BaseSales/utils.d.ts +40 -0
- package/lib/solution/BaseSales/utils.js +259 -0
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +394 -25
- package/lib/solution/BookingTicket/index.js +1350 -76
- package/lib/solution/BookingTicket/types.d.ts +191 -6
- package/lib/solution/BookingTicket/types.js +71 -10
- package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +117 -0
- package/lib/solution/BookingTicket/utils/addProductDecision.js +353 -0
- package/lib/solution/BookingTicket/utils/addTimeAvailability.d.ts +35 -0
- package/lib/solution/BookingTicket/utils/addTimeAvailability.js +94 -0
- package/lib/solution/BookingTicket/utils/bookingStatus.d.ts +77 -0
- package/lib/solution/BookingTicket/utils/bookingStatus.js +136 -0
- package/lib/solution/BookingTicket/utils/cartView.d.ts +29 -0
- package/lib/solution/BookingTicket/utils/cartView.js +200 -0
- package/lib/solution/BookingTicket/utils/exampleData.d.ts +123 -0
- package/lib/solution/BookingTicket/utils/exampleData.js +185 -0
- package/lib/solution/BookingTicket/utils/orderCustomer.d.ts +4 -0
- package/lib/solution/BookingTicket/utils/orderCustomer.js +35 -0
- package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.d.ts +185 -0
- package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.js +387 -0
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +36 -0
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +220 -0
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.d.ts +4 -0
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +24 -3
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +32 -0
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +64 -0
- package/lib/solution/BookingTicket/utils/scan/handleScan.d.ts +3 -1
- package/lib/solution/BookingTicket/utils/scan/handleScan.js +4 -2
- package/lib/solution/BookingTicket/utils/scan/index.d.ts +5 -0
- package/lib/solution/BookingTicket/utils/scan/index.js +14 -2
- package/lib/solution/BookingTicket/utils/scan/scanProductValues.d.ts +21 -0
- package/lib/solution/BookingTicket/utils/scan/scanProductValues.js +115 -0
- package/lib/solution/BookingTicket/utils/sessionCatalogStale.d.ts +23 -0
- package/lib/solution/BookingTicket/utils/sessionCatalogStale.js +83 -0
- package/lib/solution/Checkout/index.d.ts +1 -0
- package/lib/solution/Checkout/index.js +18 -19
- package/lib/solution/Checkout/utils/index.js +4 -4
- package/lib/solution/RegisterAndLogin/config.js +2 -10
- package/lib/solution/Sales/index.d.ts +50 -5
- package/lib/solution/Sales/index.js +296 -39
- package/lib/solution/Sales/types.d.ts +26 -2
- package/lib/solution/ScanOrder/index.d.ts +27 -21
- package/lib/solution/ScanOrder/index.js +144 -103
- package/lib/solution/ScanOrder/types.d.ts +33 -11
- package/lib/solution/ScanOrder/types.js +4 -5
- package/lib/solution/ScanOrder/utils.d.ts +4 -1
- package/lib/solution/ScanOrder/utils.js +98 -26
- package/lib/solution/ShopDiscount/index.js +3 -3
- package/lib/solution/UnifiedBookingSales/index.d.ts +183 -0
- package/lib/solution/UnifiedBookingSales/index.js +1050 -0
- package/lib/solution/UnifiedBookingSales/types.d.ts +143 -0
- package/lib/solution/UnifiedBookingSales/types.js +14 -0
- package/lib/solution/VenueBooking/index.d.ts +29 -20
- package/lib/solution/VenueBooking/index.js +169 -94
- package/lib/solution/VenueBooking/sales-section-4-annotated.json +0 -2
- package/lib/solution/VenueBooking/utils/slotMerge.d.ts +6 -0
- package/lib/solution/VenueBooking/utils/slotMerge.js +9 -3
- package/lib/solution/index.d.ts +2 -0
- package/lib/solution/index.js +22 -0
- package/lib/types/index.d.ts +11 -1
- package/package.json +1 -1
- package/dist/solution/Checkout/appointmentDemo.json +0 -1
- package/lib/solution/Checkout/appointmentDemo.json +0 -1
|
@@ -5,15 +5,53 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.OrderModule = void 0;
|
|
7
7
|
var _BaseModule = require("../BaseModule");
|
|
8
|
+
var _types = require("./types");
|
|
8
9
|
var _utils = require("./utils");
|
|
9
10
|
var _utils2 = require("../Product/utils");
|
|
10
11
|
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
12
|
+
var _cartPromotion = require("../../solution/BaseSales/utils/cartPromotion");
|
|
11
13
|
var _utils3 = require("../../solution/ScanOrder/utils");
|
|
14
|
+
var _manualProductDiscount = require("./utils/manualProductDiscount");
|
|
15
|
+
var _bundleDiscountHydration = require("./utils/bundleDiscountHydration");
|
|
16
|
+
var _orderCollectionIdentity = require("./utils/orderCollectionIdentity");
|
|
12
17
|
var _Discount = require("../Discount");
|
|
13
18
|
var _Rules = require("../Rules");
|
|
14
|
-
var
|
|
19
|
+
var _types2 = require("../Rules/types");
|
|
15
20
|
var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
21
|
+
var _lodashEs = require("lodash-es");
|
|
16
22
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
23
|
+
function normalizeDiscountListSignature(discountList) {
|
|
24
|
+
return (discountList || []).map(item => ({
|
|
25
|
+
type: item?.type ?? '',
|
|
26
|
+
discount_id: item?.discount_id ?? null,
|
|
27
|
+
order_discount_id: item?.order_discount_id ?? null,
|
|
28
|
+
amount: new _decimal.default(Number(item?.amount) || 0).toFixed(2)
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
function isSameDiscountList(left, right) {
|
|
32
|
+
if (!Array.isArray(left) || !Array.isArray(right)) return false;
|
|
33
|
+
return JSON.stringify(normalizeDiscountListSignature(left)) === JSON.stringify(normalizeDiscountListSignature(right));
|
|
34
|
+
}
|
|
35
|
+
function isManualProductDiscountProduct(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
|
+
const ORDER_PAYMENT_STATUS_PAID = 'paid';
|
|
39
|
+
const ORDER_PAYMENT_STATUS_PENDING = 'payment_pending';
|
|
40
|
+
function isVoucherPaymentRecord(payment) {
|
|
41
|
+
return payment?.voucher_id !== undefined && Number(payment.voucher_id) !== 0;
|
|
42
|
+
}
|
|
43
|
+
function hasSyncedOrderPaymentRecord(payment) {
|
|
44
|
+
return payment?.order_payment_id !== undefined && payment.order_payment_id !== null;
|
|
45
|
+
}
|
|
46
|
+
function isPendingVoucherPaymentRecord(payment) {
|
|
47
|
+
return isVoucherPaymentRecord(payment) && payment?.status === ORDER_PAYMENT_STATUS_PENDING;
|
|
48
|
+
}
|
|
49
|
+
function isPaidPaymentRecord(payment) {
|
|
50
|
+
return payment?.status === ORDER_PAYMENT_STATUS_PAID;
|
|
51
|
+
}
|
|
52
|
+
function isCommittedVoucherPaymentRecord(payment) {
|
|
53
|
+
return isVoucherPaymentRecord(payment) && payment?.status !== 'voided' && (hasSyncedOrderPaymentRecord(payment) || isPaidPaymentRecord(payment));
|
|
54
|
+
}
|
|
17
55
|
class OrderModule extends _BaseModule.BaseModule {
|
|
18
56
|
defaultName = 'order';
|
|
19
57
|
defaultVersion = '1.0.0';
|
|
@@ -24,6 +62,114 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
24
62
|
cacheId;
|
|
25
63
|
salesSummaryModuleName;
|
|
26
64
|
rulesHooksOverride;
|
|
65
|
+
moduleHooksOverride;
|
|
66
|
+
orderHooks;
|
|
67
|
+
otherParams;
|
|
68
|
+
// ─── 促销/赠品 ────────────────────────────────────
|
|
69
|
+
/** 评估器引用;由 SalesSdkProvider 注入,未注入时 applyPromotion 静默跳过 */
|
|
70
|
+
promotionEvaluator = null;
|
|
71
|
+
/** 赠品选择 resolver;由 SDK host bridge 注入 */
|
|
72
|
+
giftSelectResolver = null;
|
|
73
|
+
/** applyPromotion 递归保护 flag(写路径同步调 applyPromotion,禁止重入) */
|
|
74
|
+
isApplyingPromotion = false;
|
|
75
|
+
/** 商品曾应用过客户券卡,客户变更剥离后需强制跑一次 calcDiscount 复位价格 */
|
|
76
|
+
hasActiveCustomerBoundDiscount = false;
|
|
77
|
+
/** 最近一次 applyPromotion 的未满足促销提示 */
|
|
78
|
+
lastUnfulfilledPromotions = [];
|
|
79
|
+
/** 最近一次 applyPromotion 的赠品操作 diff(debug / SDK 读取使用) */
|
|
80
|
+
lastGiftActions = null;
|
|
81
|
+
/**
|
|
82
|
+
* 是否将 tempOrder 写入 IndexedDB 草稿(orders store)。
|
|
83
|
+
* BigSale 弹窗等短生命周期场景通过 setEnableTempOrderPersist(false) 关闭。
|
|
84
|
+
*/
|
|
85
|
+
draftPersistEnabled = true;
|
|
86
|
+
collectHydratedEditDiscounts(products) {
|
|
87
|
+
const discountMap = new Map();
|
|
88
|
+
for (const product of (0, _bundleDiscountHydration.expandHydratedProductsForDiscountCollection)(products)) {
|
|
89
|
+
for (const item of product?.discount_list || []) {
|
|
90
|
+
const type = item?.type ?? item?.tag;
|
|
91
|
+
if (!type || type === 'product') continue;
|
|
92
|
+
const resourceId = item?.discount?.resource_id ?? item?.resource_id ?? item?.discount_id ?? item?.order_discount_id;
|
|
93
|
+
if (resourceId === undefined || resourceId === null || resourceId === '') continue;
|
|
94
|
+
const id = Number(resourceId);
|
|
95
|
+
const key = Number.isFinite(id) ? id : String(resourceId);
|
|
96
|
+
const title = item?.discount?.title ?? {};
|
|
97
|
+
const amount = Number(item?.amount || 0);
|
|
98
|
+
const detail = {
|
|
99
|
+
amount: String(item?.amount ?? '0'),
|
|
100
|
+
type,
|
|
101
|
+
resource_id: key,
|
|
102
|
+
title,
|
|
103
|
+
original_amount: String(item?.discount?.original_amount ?? item?.amount ?? '0'),
|
|
104
|
+
num: Number(item?.metadata?.num ?? product?.num ?? product?.product_quantity ?? 1) || 1,
|
|
105
|
+
metadata: item?.metadata,
|
|
106
|
+
discount: item?.discount,
|
|
107
|
+
_num: Number(item?.metadata?.num ?? product?.num ?? product?.product_quantity ?? 1) || 1
|
|
108
|
+
};
|
|
109
|
+
const existed = discountMap.get(key);
|
|
110
|
+
if (existed) {
|
|
111
|
+
existed.amount = Number(existed.amount || 0) + amount;
|
|
112
|
+
existed.used_par_value = String(Number(existed.used_par_value || 0) + amount);
|
|
113
|
+
existed.balance = String(Number(existed.balance || 0) + amount);
|
|
114
|
+
const productIds = Array.isArray(existed.limited_relation_product_data?.product_ids) ? existed.limited_relation_product_data.product_ids : [];
|
|
115
|
+
if (product?.product_id !== undefined && product?.product_id !== null && !productIds.includes(product.product_id)) {
|
|
116
|
+
existed.limited_relation_product_data = {
|
|
117
|
+
...(existed.limited_relation_product_data || {}),
|
|
118
|
+
product_ids: [...productIds, product.product_id]
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
existed.applicableProductDetails = [...(existed.applicableProductDetails || []), detail];
|
|
122
|
+
existed.appliedProductDetails = [...(existed.appliedProductDetails || []), detail];
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
discountMap.set(key, {
|
|
126
|
+
id: key,
|
|
127
|
+
product_name: '',
|
|
128
|
+
encoded: '',
|
|
129
|
+
code: '',
|
|
130
|
+
tag: type,
|
|
131
|
+
product_id: item?.discount?.discount_product_id ?? product?.product_id ?? 0,
|
|
132
|
+
relation_type: '',
|
|
133
|
+
par_value: String(item?.discount?.original_amount ?? item?.amount ?? '0'),
|
|
134
|
+
used_par_value: String(item?.amount ?? '0'),
|
|
135
|
+
limit_status: 'enable',
|
|
136
|
+
limited_relation_product_data: {
|
|
137
|
+
id: 0,
|
|
138
|
+
type: 'products',
|
|
139
|
+
product_ids: [product?.product_id].filter(value => value !== undefined && value !== null),
|
|
140
|
+
product_collection_id: [],
|
|
141
|
+
filter: 0,
|
|
142
|
+
exclude_product_ids: []
|
|
143
|
+
},
|
|
144
|
+
balance: String(item?.amount ?? '0'),
|
|
145
|
+
format_title: title,
|
|
146
|
+
product: {
|
|
147
|
+
id: product?.product_id ?? 0,
|
|
148
|
+
title: '',
|
|
149
|
+
is_charge_tax: product?.is_charge_tax ?? 0,
|
|
150
|
+
base_price: String(product?.original_price ?? product?.selling_price ?? '0')
|
|
151
|
+
},
|
|
152
|
+
type,
|
|
153
|
+
resource_id: key,
|
|
154
|
+
isEditMode: true,
|
|
155
|
+
isSelected: true,
|
|
156
|
+
isAvailable: true,
|
|
157
|
+
isUsed: true,
|
|
158
|
+
isDisabled: true,
|
|
159
|
+
amount,
|
|
160
|
+
discount: {
|
|
161
|
+
...(item?.discount || {}),
|
|
162
|
+
resource_id: key,
|
|
163
|
+
discount_product_id: item?.discount?.discount_product_id ?? product?.product_id ?? 0,
|
|
164
|
+
discount_calculation_mode: item?.discount?.discount_calculation_mode ?? 'item_level'
|
|
165
|
+
},
|
|
166
|
+
applicableProductDetails: [detail],
|
|
167
|
+
appliedProductDetails: [detail]
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return Array.from(discountMap.values());
|
|
172
|
+
}
|
|
27
173
|
|
|
28
174
|
/**
|
|
29
175
|
* Populate `savedAmount` on each discount based on product-level discount details.
|
|
@@ -31,22 +177,79 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
31
177
|
*/
|
|
32
178
|
static populateSavedAmounts(productList, discountList) {
|
|
33
179
|
const savedMap = new Map();
|
|
180
|
+
const editModeSavedMap = new Map();
|
|
181
|
+
const runtimeSavedMap = new Map();
|
|
182
|
+
const editModeDiscountKeys = new Set((discountList || []).filter(discount => discount?.isEditMode).map(discount => discount?.id).filter(id => id !== undefined && id !== null));
|
|
183
|
+
const addDiscountAmount = (targetMap, discountKey, amount) => {
|
|
184
|
+
targetMap.set(discountKey, (targetMap.get(discountKey) || new _decimal.default(0)).plus(amount));
|
|
185
|
+
};
|
|
186
|
+
const addProductDiscountAmount = (discount, quantity, isPersistedProduct) => {
|
|
187
|
+
const discountKey = discount.discount?.resource_id || discount.id;
|
|
188
|
+
if (discountKey == null) return;
|
|
189
|
+
const qty = Number(quantity) || 1;
|
|
190
|
+
const amount = new _decimal.default(discount.amount || 0).times(qty).plus(discount.metadata?.product_discount_difference || 0);
|
|
191
|
+
addDiscountAmount(savedMap, discountKey, amount);
|
|
192
|
+
addDiscountAmount(isPersistedProduct || discount.order_discount_id != null ? editModeSavedMap : runtimeSavedMap, discountKey, amount);
|
|
193
|
+
};
|
|
34
194
|
for (const product of productList) {
|
|
35
195
|
const qty = product.num || 1;
|
|
196
|
+
const isPersistedProduct = Boolean(product.order_detail_id || product.orderDetailId || product.booking_id);
|
|
36
197
|
for (const pd of product.discount_list || []) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
198
|
+
addProductDiscountAmount(pd, qty, isPersistedProduct);
|
|
199
|
+
}
|
|
200
|
+
for (const bundle of product.product_bundle || []) {
|
|
201
|
+
const isPersistedBundleProduct = isPersistedProduct || Boolean(bundle?.order_detail_id || bundle?.orderDetailId || bundle?.booking_id);
|
|
202
|
+
const bundleQty = new _decimal.default(Number(qty) || 1).times(Number(bundle?.num ?? bundle?.quantity ?? 1) || 1).toNumber();
|
|
203
|
+
for (const pd of bundle?.discount_list || []) {
|
|
204
|
+
addProductDiscountAmount(pd, bundleQty, isPersistedBundleProduct);
|
|
205
|
+
}
|
|
41
206
|
}
|
|
42
207
|
}
|
|
43
208
|
for (const d of discountList) {
|
|
44
209
|
const key = d.id;
|
|
45
|
-
|
|
210
|
+
const sourceMap = d.isEditMode ? editModeSavedMap : editModeDiscountKeys.has(key) ? runtimeSavedMap : savedMap;
|
|
211
|
+
d.savedAmount = d.isSelected && key != null && sourceMap.has(key) ? sourceMap.get(key).toNumber() : 0;
|
|
46
212
|
}
|
|
47
213
|
}
|
|
214
|
+
applyProductDiscountPrices(products) {
|
|
215
|
+
return (products || []).map(product => {
|
|
216
|
+
if (isManualProductDiscountProduct(product)) return product;
|
|
217
|
+
const discountItems = Array.isArray(product.discount_list) ? product.discount_list : [];
|
|
218
|
+
const hasOnlyPersistedDiscountSnapshots = product.order_detail_id !== undefined && product.order_detail_id !== null && discountItems.length > 0 && discountItems.every(discount => discount?.order_discount_id !== undefined && discount?.order_discount_id !== null);
|
|
219
|
+
if (hasOnlyPersistedDiscountSnapshots) {
|
|
220
|
+
return product;
|
|
221
|
+
}
|
|
222
|
+
const totalPerUnitDiscount = (0, _utils.resolveEffectivePerUnitDiscount)(product);
|
|
223
|
+
const optionSum = (0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(product));
|
|
224
|
+
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');
|
|
225
|
+
const newSourceSellingPrice = new _decimal.default(Number(sourcePrice) || 0).minus(totalPerUnitDiscount).toDecimalPlaces(2).toString();
|
|
226
|
+
const newMainSellingPrice = new _decimal.default(Number(newSourceSellingPrice) || 0).plus(optionSum).toDecimalPlaces(2).toFixed(2);
|
|
227
|
+
if (product.metadata) {
|
|
228
|
+
product.metadata.main_product_selling_price = newMainSellingPrice;
|
|
229
|
+
product.metadata.price_schema_version = 2;
|
|
230
|
+
}
|
|
231
|
+
if (Array.isArray(product.product_bundle)) {
|
|
232
|
+
product.product_bundle = product.product_bundle.map(bundle => {
|
|
233
|
+
const hasBundleDiscount = Array.isArray(bundle?.discount_list) && bundle.discount_list.length > 0;
|
|
234
|
+
if (hasBundleDiscount) return bundle;
|
|
235
|
+
const restoredPrice = bundle?.original_price ?? bundle?.product_price ?? bundle?.price;
|
|
236
|
+
if (restoredPrice === undefined || restoredPrice === null || restoredPrice === '') return bundle;
|
|
237
|
+
return {
|
|
238
|
+
...bundle,
|
|
239
|
+
price: restoredPrice,
|
|
240
|
+
bundle_selling_price: restoredPrice
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
product.selling_price = (0, _utils.composeLinePrice)({
|
|
245
|
+
mainPrice: newMainSellingPrice,
|
|
246
|
+
bundle: product.product_bundle
|
|
247
|
+
});
|
|
248
|
+
return product;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
48
251
|
constructor(name, version) {
|
|
49
|
-
super(name ||
|
|
252
|
+
super(name || 'order', version);
|
|
50
253
|
}
|
|
51
254
|
async initialize(core, options) {
|
|
52
255
|
this.core = core;
|
|
@@ -54,25 +257,66 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
54
257
|
if (!this.store.tempOrder) {
|
|
55
258
|
this.store.tempOrder = null;
|
|
56
259
|
}
|
|
260
|
+
if (!this.store.summary) {
|
|
261
|
+
this.store.summary = (0, _utils.createEmptySummary)();
|
|
262
|
+
}
|
|
57
263
|
if (!Array.isArray(this.store.availableWalletIds)) {
|
|
58
264
|
this.store.availableWalletIds = [];
|
|
59
265
|
}
|
|
60
266
|
this.request = this.core.getPlugin('request');
|
|
267
|
+
const appPlugin = this.core.getPlugin('app');
|
|
268
|
+
if (!appPlugin) {
|
|
269
|
+
console.error('Order 模块需要 app 插件支持');
|
|
270
|
+
}
|
|
271
|
+
const app = appPlugin?.getApp?.();
|
|
272
|
+
this.logger = app?.logger;
|
|
61
273
|
this.window = this.core.getPlugin('window');
|
|
62
274
|
const otherParams = options.otherParams || {};
|
|
63
275
|
this.cacheId = otherParams.cacheId;
|
|
64
276
|
this.salesSummaryModuleName = otherParams.salesSummaryModuleName;
|
|
65
277
|
this.rulesHooksOverride = otherParams.rules?.hooks;
|
|
278
|
+
this.moduleHooksOverride = options.hooks;
|
|
279
|
+
this.orderHooks = this.moduleHooksOverride || otherParams.order?.hooks || otherParams.hooks?.order;
|
|
280
|
+
this.otherParams = otherParams;
|
|
66
281
|
this.registerDiscountModules(options);
|
|
67
|
-
this.restoreTempOrderFromStorage();
|
|
68
|
-
const appPlugin = this.core.getPlugin('app');
|
|
69
|
-
if (!appPlugin) {
|
|
70
|
-
console.warn('Order 模块需要 app 插件支持');
|
|
71
|
-
}
|
|
72
|
-
const app = appPlugin?.getApp();
|
|
73
|
-
if (app) this.logger = app.logger;
|
|
74
282
|
this.logInfo('OrderModule initialized successfully');
|
|
75
283
|
}
|
|
284
|
+
async destroy() {
|
|
285
|
+
const childModules = [this.store?.discount, this.store?.rules];
|
|
286
|
+
for (const childModule of childModules) {
|
|
287
|
+
if (!childModule || typeof childModule.destroy !== 'function') continue;
|
|
288
|
+
try {
|
|
289
|
+
// Order 内部注册的子模块需要跟随父模块释放,避免 core 中残留独立模块。
|
|
290
|
+
await childModule.destroy();
|
|
291
|
+
} catch (error) {
|
|
292
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
293
|
+
this.logWarning('Destroy child module failed', {
|
|
294
|
+
module: childModule.name,
|
|
295
|
+
error: errorMessage
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (this.store) {
|
|
300
|
+
this.store.discount = null;
|
|
301
|
+
this.store.rules = null;
|
|
302
|
+
}
|
|
303
|
+
super.destroy();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* 更新父级解决方案透传的运行态参数,并同步 Order 内部依赖的派生字段。
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* orderModule.updateOtherParams({ cacheId: 'cache-2', channel: 'pos' });
|
|
311
|
+
*/
|
|
312
|
+
updateOtherParams(params) {
|
|
313
|
+
const otherParams = params || {};
|
|
314
|
+
this.cacheId = otherParams.cacheId;
|
|
315
|
+
this.salesSummaryModuleName = otherParams.salesSummaryModuleName;
|
|
316
|
+
this.rulesHooksOverride = otherParams.rules?.hooks;
|
|
317
|
+
this.orderHooks = this.moduleHooksOverride || otherParams.order?.hooks || otherParams.hooks?.order;
|
|
318
|
+
this.otherParams = otherParams;
|
|
319
|
+
}
|
|
76
320
|
|
|
77
321
|
/**
|
|
78
322
|
* 记录信息日志
|
|
@@ -151,30 +395,70 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
151
395
|
// ─── Discount: 公共 API ───
|
|
152
396
|
|
|
153
397
|
async loadDiscountConfig(params) {
|
|
154
|
-
const
|
|
398
|
+
const orderId = params.orderId || this.store.tempOrder?.order_id || undefined;
|
|
399
|
+
const prepareConfigResult = await this.store.discount?.loadPrepareConfig({
|
|
155
400
|
customer_id: params.customerId,
|
|
156
|
-
action: params.action || 'create',
|
|
401
|
+
action: params.action || (orderId ? 'edit' : 'create'),
|
|
157
402
|
with_good_pass: 1,
|
|
158
403
|
with_discount_card: 1,
|
|
159
404
|
with_wallet_pass_holder: 1,
|
|
160
405
|
with_available_wallet_flag: 1,
|
|
161
406
|
with_available_wallet_pass_flag: 1,
|
|
162
|
-
request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
407
|
+
request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
408
|
+
...(orderId ? {
|
|
409
|
+
order_id: orderId
|
|
410
|
+
} : {})
|
|
163
411
|
});
|
|
164
|
-
const discountList =
|
|
165
|
-
this.store.availableWalletIds =
|
|
412
|
+
const discountList = prepareConfigResult?.discountList;
|
|
413
|
+
this.store.availableWalletIds = prepareConfigResult?.availableWalletIds || [];
|
|
166
414
|
if (discountList) {
|
|
167
|
-
this.store.discount?.
|
|
415
|
+
const currentDiscountList = this.store.discount?.getDiscountList() || [];
|
|
416
|
+
const currentEditDiscounts = currentDiscountList.filter(discount => discount?.isEditMode);
|
|
417
|
+
const currentScanDiscounts = currentDiscountList.filter(discount => discount?.isScan);
|
|
418
|
+
const currentScanDiscountIds = new Set(currentScanDiscounts.map(discount => discount?.id));
|
|
419
|
+
const runtimeDiscountIds = new Set();
|
|
420
|
+
const runtimeDiscounts = discountList.filter(discount => {
|
|
421
|
+
if (currentScanDiscountIds.has(discount?.id)) return false;
|
|
422
|
+
if (runtimeDiscountIds.has(discount?.id)) return false;
|
|
423
|
+
runtimeDiscountIds.add(discount?.id);
|
|
424
|
+
return true;
|
|
425
|
+
});
|
|
426
|
+
const mergedDiscountList = [...currentEditDiscounts, ...currentScanDiscounts, ...runtimeDiscounts];
|
|
427
|
+
await this.store.discount?.setOriginalDiscountList(mergedDiscountList);
|
|
428
|
+
await this.store.discount?.setDiscountList(mergedDiscountList);
|
|
168
429
|
}
|
|
169
|
-
if (this.store.tempOrder?.products?.length) {
|
|
430
|
+
if (params.apply !== false && this.store.tempOrder?.products?.length) {
|
|
170
431
|
this.applyDiscount();
|
|
171
432
|
}
|
|
433
|
+
return this.getDiscountList();
|
|
172
434
|
}
|
|
173
435
|
getDiscountList() {
|
|
174
436
|
return this.store.discount?.getDiscountList() || [];
|
|
175
437
|
}
|
|
176
438
|
getAvailableWalletIds() {
|
|
177
|
-
return this.store.availableWalletIds
|
|
439
|
+
return Array.isArray(this.store.availableWalletIds) ? [...this.store.availableWalletIds] : [];
|
|
440
|
+
}
|
|
441
|
+
async syncScannedDiscountsToOriginalDiscountList(discountList) {
|
|
442
|
+
const scanDiscounts = (discountList || []).filter(discount => discount?.isScan);
|
|
443
|
+
if (!scanDiscounts.length) return;
|
|
444
|
+
const scanDiscountIds = new Set(scanDiscounts.map(discount => discount?.id));
|
|
445
|
+
const originalDiscounts = this.store.discount?.getOriginalDiscountList?.() || [];
|
|
446
|
+
const nextOriginalDiscounts = [...scanDiscounts, ...originalDiscounts.filter(discount => !scanDiscountIds.has(discount?.id))];
|
|
447
|
+
await this.store.discount?.setOriginalDiscountList?.(nextOriginalDiscounts);
|
|
448
|
+
}
|
|
449
|
+
async ensureEditDiscountConfigForProductChange(tempOrder) {
|
|
450
|
+
const orderId = tempOrder.order_id;
|
|
451
|
+
const customerId = Number(tempOrder.customer_id || tempOrder.customer?.id || tempOrder._extend?.customerSnapshot?.id || tempOrder._extend?.customerSnapshot?.customer_id || 0);
|
|
452
|
+
if (!orderId || !customerId || customerId === 1) return;
|
|
453
|
+
const currentDiscountList = this.store.discount?.getDiscountList() || [];
|
|
454
|
+
const hasRuntimeDiscounts = currentDiscountList.some(discount => !discount?.isEditMode);
|
|
455
|
+
if (hasRuntimeDiscounts) return;
|
|
456
|
+
await this.loadDiscountConfig({
|
|
457
|
+
customerId,
|
|
458
|
+
action: 'edit',
|
|
459
|
+
orderId: Number(orderId),
|
|
460
|
+
apply: false
|
|
461
|
+
});
|
|
178
462
|
}
|
|
179
463
|
async scanCode(code, customerId) {
|
|
180
464
|
const resultDiscountWithReason = (await this.store.discount?.batchSearch(code, {
|
|
@@ -193,7 +477,8 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
193
477
|
type: 'clientCalc',
|
|
194
478
|
isAvailable: false,
|
|
195
479
|
discountList: this.getDiscountList(),
|
|
196
|
-
|
|
480
|
+
scannedDiscountList: resultDiscountList,
|
|
481
|
+
unavailableReason: _types2.UnavailableReason.Unknown
|
|
197
482
|
};
|
|
198
483
|
}
|
|
199
484
|
if (!resultDiscountList.length) {
|
|
@@ -201,6 +486,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
201
486
|
type: 'server',
|
|
202
487
|
isAvailable: false,
|
|
203
488
|
discountList: this.getDiscountList(),
|
|
489
|
+
scannedDiscountList: resultDiscountList,
|
|
204
490
|
unavailableReasonKey
|
|
205
491
|
};
|
|
206
492
|
}
|
|
@@ -210,16 +496,16 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
210
496
|
}));
|
|
211
497
|
const currentSelected = this.getDiscountList().filter(n => n.isSelected);
|
|
212
498
|
if (currentSelected.length && currentSelected.some(n => withScanList.some(m => m.id === n.id))) {
|
|
499
|
+
await this.syncScannedDiscountsToOriginalDiscountList(this.getDiscountList());
|
|
213
500
|
return {
|
|
214
501
|
type: 'clientCalc',
|
|
215
502
|
isAvailable: true,
|
|
216
|
-
discountList: this.getDiscountList()
|
|
503
|
+
discountList: this.getDiscountList(),
|
|
504
|
+
scannedDiscountList: resultDiscountList
|
|
217
505
|
};
|
|
218
506
|
}
|
|
219
507
|
const tempOrder = this.store.tempOrder;
|
|
220
|
-
const holders = tempOrder?.holder?.
|
|
221
|
-
form_record_id: tempOrder.holder.form_record_id
|
|
222
|
-
}] : [];
|
|
508
|
+
const holders = tempOrder?.holder?.form_record || [];
|
|
223
509
|
const {
|
|
224
510
|
isAvailable,
|
|
225
511
|
discountList: newDiscountList,
|
|
@@ -228,9 +514,9 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
228
514
|
productList: tempOrder?.products || [],
|
|
229
515
|
oldDiscountList: this.getDiscountList(),
|
|
230
516
|
newDiscountList: withScanList,
|
|
517
|
+
orderTotalAmount: Number(this.store.summary?.total_amount || 0),
|
|
231
518
|
holders,
|
|
232
|
-
isFormSubject: !!tempOrder?.holder?.type && tempOrder.holder.type === 'form'
|
|
233
|
-
orderTotalAmount: Number(tempOrder?.summary?.total_amount || 0)
|
|
519
|
+
isFormSubject: !!tempOrder?.holder?.type && tempOrder.holder.type === 'form'
|
|
234
520
|
}) || {
|
|
235
521
|
isAvailable: false,
|
|
236
522
|
discountList: this.getDiscountList()
|
|
@@ -238,111 +524,255 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
238
524
|
if (isAvailable && newDiscountList) {
|
|
239
525
|
this.store.discount?.setDiscountList(newDiscountList);
|
|
240
526
|
this.applyDiscount();
|
|
527
|
+
await this.syncScannedDiscountsToOriginalDiscountList(this.getDiscountList());
|
|
241
528
|
}
|
|
242
529
|
return {
|
|
243
530
|
type: 'clientCalc',
|
|
244
531
|
isAvailable: isAvailable || false,
|
|
245
532
|
discountList: newDiscountList || this.getDiscountList(),
|
|
533
|
+
scannedDiscountList: resultDiscountList,
|
|
246
534
|
unavailableReason
|
|
247
535
|
};
|
|
248
536
|
}
|
|
249
|
-
applyDiscount() {
|
|
537
|
+
applyDiscount(options) {
|
|
250
538
|
const tempOrder = this.store.tempOrder;
|
|
251
539
|
// 任意 products CRUD 后都应当重算 discount 状态;即使 products 为空,
|
|
252
540
|
// 也需要让 Rules.calcDiscount 把 DiscountModule 的 isSelected / isAvailable /
|
|
253
|
-
// appliedProductDetails / savedAmount
|
|
541
|
+
// appliedProductDetails / savedAmount 复位。
|
|
254
542
|
if (!tempOrder) return;
|
|
543
|
+
this.syncLinkedBookingHoldersToProducts(tempOrder);
|
|
544
|
+
delete tempOrder.discount_list;
|
|
545
|
+
const discountList = this.store.discount?.getDiscountList() || [];
|
|
546
|
+
const shouldSkipDiscountCalculation = this.shouldSkipDiscountCalculation(tempOrder);
|
|
547
|
+
if (shouldSkipDiscountCalculation) return;
|
|
255
548
|
const rulesModule = this.store.rules;
|
|
256
549
|
if (!rulesModule) return;
|
|
257
|
-
const discountList = this.store.discount?.getDiscountList() || [];
|
|
258
|
-
const holders = tempOrder.holder?.form_record_id ? [{
|
|
259
|
-
form_record_id: tempOrder.holder.form_record_id
|
|
260
|
-
}] : [];
|
|
261
550
|
const result = rulesModule.calcDiscount({
|
|
262
551
|
productList: tempOrder.products,
|
|
263
552
|
discountList,
|
|
264
|
-
holders,
|
|
265
|
-
isFormSubject:
|
|
266
|
-
orderTotalAmount: Number(
|
|
553
|
+
holders: [],
|
|
554
|
+
isFormSubject: product => (0, _utils.resolveIsFormSubject)(tempOrder, product),
|
|
555
|
+
orderTotalAmount: Number(this.store.summary?.total_amount || 0)
|
|
556
|
+
}, {
|
|
557
|
+
reapplyBookingDiscountProductUids: options?.reapplyBookingDiscountProductUids
|
|
267
558
|
});
|
|
268
559
|
if (result?.productList) {
|
|
269
|
-
|
|
560
|
+
const previousProductsByUid = (0, _utils.indexOrderProductsByUid)(tempOrder.products);
|
|
561
|
+
tempOrder.products = this.applyProductDiscountPrices((result.productList || []).map((product, index) => {
|
|
562
|
+
const uid = (0, _utils.getOrderProductLineUid)(product);
|
|
563
|
+
const previous = (uid ? previousProductsByUid.get(uid) : undefined) ?? previousProductsByUid.get(`__index__:${index}`);
|
|
564
|
+
if (isManualProductDiscountProduct(previous)) {
|
|
565
|
+
return previous;
|
|
566
|
+
}
|
|
567
|
+
const mergedProduct = previous ? (0, _utils.mergeOrderProductDisplayFields)(previous, product) : product;
|
|
568
|
+
return {
|
|
569
|
+
...mergedProduct,
|
|
570
|
+
discount_list: (0, _utils.normalizeOrderProductDiscountList)((0, _manualProductDiscount.syncManualProductDiscountProductNum)(mergedProduct.discount_list, mergedProduct.num))
|
|
571
|
+
};
|
|
572
|
+
}));
|
|
270
573
|
}
|
|
271
574
|
if (result?.discountList) {
|
|
272
575
|
OrderModule.populateSavedAmounts(result.productList || tempOrder.products, result.discountList);
|
|
273
576
|
this.store.discount?.setDiscountList(result.discountList);
|
|
274
|
-
tempOrder.discount_list = result.discountList.filter(d => d.isSelected);
|
|
275
577
|
}
|
|
578
|
+
this.hasActiveCustomerBoundDiscount = (tempOrder.products || []).some(product => this.productHasCustomerBoundDiscount(product));
|
|
276
579
|
}
|
|
277
580
|
|
|
278
|
-
// ───
|
|
581
|
+
// ─── 促销/赠品 ──────────────────────────────────────
|
|
279
582
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
583
|
+
/**
|
|
584
|
+
* 注入促销评估器。
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* order.setPromotionEvaluator(appHelper.utils.promotionEvaluator);
|
|
588
|
+
*/
|
|
589
|
+
setPromotionEvaluator(evaluator) {
|
|
590
|
+
this.promotionEvaluator = evaluator || null;
|
|
284
591
|
}
|
|
285
592
|
|
|
286
|
-
|
|
593
|
+
/**
|
|
594
|
+
* 注入赠品选择 resolver。OS 需要补赠品时会 await 调用 resolver;缺省时跳过新增并 console.warn。
|
|
595
|
+
*
|
|
596
|
+
* @example
|
|
597
|
+
* order.setGiftSelectResolver(async (ctx) => {
|
|
598
|
+
* // SDK 内部决定弹窗 or 自动选第一项,返回完整 OrderProduct[]
|
|
599
|
+
* return giftSelectBridge.request(ctx);
|
|
600
|
+
* });
|
|
601
|
+
*/
|
|
602
|
+
setGiftSelectResolver(resolver) {
|
|
603
|
+
this.giftSelectResolver = resolver || null;
|
|
604
|
+
}
|
|
287
605
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
606
|
+
/**
|
|
607
|
+
* 为商品目录追加促销标签,供上层 Sales 门面复用。
|
|
608
|
+
*
|
|
609
|
+
* @example
|
|
610
|
+
* const products = order.appendPromotionTags(catalogProducts);
|
|
611
|
+
*/
|
|
612
|
+
appendPromotionTags(products) {
|
|
613
|
+
return (0, _cartPromotion.appendPromotionTags)(products, this.promotionEvaluator);
|
|
291
614
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* 应用购物车促销:计算 → 差异化赠品 → 写回 tempOrder.products → emit onPromotionApplied。
|
|
618
|
+
*
|
|
619
|
+
* 调用时机:写路径(add/update/remove/clear/setCustomer)末尾自动触发;外部一般无需手动调。
|
|
620
|
+
*
|
|
621
|
+
* 关键约束:
|
|
622
|
+
* - 不调用任何公开 CRUD API(如 removeProductFromOrder),全部内部 mutate tempOrder.products,避免事件风暴;
|
|
623
|
+
* - 用 `isApplyingPromotion` 防递归;
|
|
624
|
+
* - 多选项赠品依赖 giftSelectResolver,未注入则跳过且 console.warn;
|
|
625
|
+
* - 不主动调用 applyDiscount / recalculateSummary,调用方负责跟进。
|
|
626
|
+
*/
|
|
627
|
+
async applyPromotion() {
|
|
628
|
+
if (this.isApplyingPromotion) {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
if (!this.promotionEvaluator) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
const tempOrder = this.store.tempOrder;
|
|
635
|
+
if (!tempOrder) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
const strategyConfigs = this.promotionEvaluator.getStrategyConfigs?.();
|
|
639
|
+
if (Array.isArray(strategyConfigs) && strategyConfigs.length === 0) {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
this.isApplyingPromotion = true;
|
|
298
643
|
try {
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
644
|
+
const result = (0, _cartPromotion.processCartPromotion)(tempOrder.products, this.promotionEvaluator);
|
|
645
|
+
|
|
646
|
+
// step 1: toRemove —— 直接过滤
|
|
647
|
+
if (result.giftActions.toRemove.length > 0) {
|
|
648
|
+
const removeSet = new Set(result.giftActions.toRemove.map(String));
|
|
649
|
+
result.products = result.products.filter(p => {
|
|
650
|
+
const uid = (0, _cartPromotion.getOrderProductUid)(p);
|
|
651
|
+
return uid ? !removeSet.has(String(uid)) : true;
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// step 2: toReduce —— 调整数量
|
|
656
|
+
for (const reduce of result.giftActions.toReduce) {
|
|
657
|
+
for (const item of reduce.items) {
|
|
658
|
+
const product = result.products.find(p => (0, _cartPromotion.getOrderProductUid)(p) === item.uid);
|
|
659
|
+
if (product) {
|
|
660
|
+
product.num = item.newQuantity;
|
|
310
661
|
}
|
|
311
|
-
|
|
312
|
-
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// step 3: toAdd —— 补赠品
|
|
666
|
+
if (result.giftActions.toAdd.length > 0) {
|
|
667
|
+
if (!this.giftSelectResolver) {
|
|
668
|
+
if (result.giftActions.toAdd.some(g => g.giftOptions.length > 1)) {
|
|
669
|
+
// 多选项必须走 resolver;单选项也建议走(避免 OS 自造 stub),但允许跳过
|
|
670
|
+
console.warn('[OrderModule] giftSelectResolver not set, skip applying gifts', result.giftActions.toAdd);
|
|
671
|
+
} else {
|
|
672
|
+
console.warn('[OrderModule] giftSelectResolver not set, skip applying single-option gifts', result.giftActions.toAdd);
|
|
313
673
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
674
|
+
} else {
|
|
675
|
+
for (const addAction of result.giftActions.toAdd) {
|
|
676
|
+
// 单选普通赠品可累加;预约赠品必须新增独立 booking,不能只改 num。
|
|
677
|
+
if (addAction.giftOptions.length === 1) {
|
|
678
|
+
const sameStrategy = result.products.find(p => p.metadata?._giftInfo?.strategyId === addAction.strategyId);
|
|
679
|
+
if (sameStrategy && !sameStrategy.booking_uid && !sameStrategy.metadata?.booking_uid) {
|
|
680
|
+
sameStrategy.num = (Number(sameStrategy.num) || 0) + (addAction.giftCount || 1);
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
323
683
|
}
|
|
324
|
-
|
|
325
|
-
|
|
684
|
+
try {
|
|
685
|
+
const giftResult = await this.giftSelectResolver({
|
|
686
|
+
strategyId: addAction.strategyId,
|
|
687
|
+
strategyName: addAction.strategyName,
|
|
688
|
+
giftCount: addAction.giftCount,
|
|
689
|
+
giftOptions: addAction.giftOptions,
|
|
690
|
+
sourceProductIds: addAction.sourceProductIds
|
|
691
|
+
});
|
|
692
|
+
const giftProducts = Array.isArray(giftResult) ? giftResult : giftResult?.products;
|
|
693
|
+
const giftBookings = Array.isArray(giftResult) ? [] : giftResult?.bookings || [];
|
|
694
|
+
if (Array.isArray(giftProducts) && giftProducts.length > 0) {
|
|
695
|
+
for (const [index, gp] of giftProducts.entries()) {
|
|
696
|
+
if (!gp) continue;
|
|
697
|
+
const booking = giftBookings[index];
|
|
698
|
+
if (booking) {
|
|
699
|
+
const linked = this.createLinkedProductAndBooking({
|
|
700
|
+
product: gp,
|
|
701
|
+
booking
|
|
702
|
+
});
|
|
703
|
+
result.products.push(linked.product);
|
|
704
|
+
tempOrder.bookings = [...(tempOrder.bookings || []), linked.booking];
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
// normalize 略过:调用方应保证返回完整 OrderProduct。
|
|
708
|
+
result.products.push(gp);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
} catch (error) {
|
|
712
|
+
// 用户取消 / 选择失败 → 视为放弃本次新增
|
|
713
|
+
console.warn('[OrderModule] giftSelectResolver rejected, skip add gift', addAction.strategyId, error);
|
|
326
714
|
}
|
|
327
715
|
}
|
|
328
|
-
// 价格 schema 迁移:重跑 normalizeOrderProduct。
|
|
329
|
-
// `metadata.price_schema_version === 2` → 已是新语义,保留现有 main_product_* 折扣;
|
|
330
|
-
// 否则(v1 或无 metadata)→ 走 legacyDiscount 反推分支,基于新的 source + options
|
|
331
|
-
// 重算出含 option 的 main_product_*,并打上 price_schema_version: 2。
|
|
332
|
-
// 幂等:多次 restore 不会重复叠加 option/bundle。
|
|
333
|
-
parsedData.products[i] = (0, _utils3.normalizeOrderProduct)(row);
|
|
334
716
|
}
|
|
335
717
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
this.
|
|
718
|
+
tempOrder.products = result.products;
|
|
719
|
+
this.lastUnfulfilledPromotions = result.unfulfilledPromotions;
|
|
720
|
+
this.lastGiftActions = result.giftActions;
|
|
721
|
+
const payload = {
|
|
722
|
+
unfulfilledPromotions: result.unfulfilledPromotions,
|
|
723
|
+
giftActions: result.giftActions,
|
|
724
|
+
gifts: result.gifts.map(g => ({
|
|
725
|
+
strategyId: g.strategyId,
|
|
726
|
+
strategyName: g.strategyName,
|
|
727
|
+
giftCount: g.giftCount,
|
|
728
|
+
giftOptions: g.giftOptions
|
|
729
|
+
})),
|
|
730
|
+
products: tempOrder.products
|
|
731
|
+
};
|
|
732
|
+
this.effectsEmit('onPromotionApplied', payload);
|
|
733
|
+
} catch (error) {
|
|
734
|
+
console.error('[OrderModule] applyPromotion failed', error);
|
|
735
|
+
} finally {
|
|
736
|
+
this.isApplyingPromotion = false;
|
|
339
737
|
}
|
|
340
738
|
}
|
|
739
|
+
|
|
740
|
+
/** 最近一次 applyPromotion 输出的未满足促销列表 */
|
|
741
|
+
getUnfulfilledPromotions() {
|
|
742
|
+
return this.lastUnfulfilledPromotions;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/** 最近一次 applyPromotion 输出的赠品操作 diff */
|
|
746
|
+
getLastGiftActions() {
|
|
747
|
+
return this.lastGiftActions;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// ─── TempOrder: 初始化入口 ───
|
|
751
|
+
|
|
752
|
+
initTempOrder(params) {
|
|
753
|
+
if (params.cacheId !== undefined) this.cacheId = params.cacheId;
|
|
754
|
+
if (params.salesSummaryModuleName !== undefined) this.salesSummaryModuleName = params.salesSummaryModuleName;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* 控制 IndexedDB 草稿(saveDraft)是否写入。
|
|
759
|
+
* localStorage 版 tempOrder 持久化已废弃,此方法仅影响 saveDraft。
|
|
760
|
+
*
|
|
761
|
+
* @example
|
|
762
|
+
* order.setEnableTempOrderPersist(false); // BigSale 弹窗:跳过 ~1s 的 dbUpdate
|
|
763
|
+
*/
|
|
764
|
+
setEnableTempOrderPersist(enabled) {
|
|
765
|
+
this.draftPersistEnabled = enabled;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/** 是否允许 saveDraft 写入 IndexedDB 草稿。 */
|
|
769
|
+
isTempOrderPersistEnabled() {
|
|
770
|
+
return this.draftPersistEnabled;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/** @deprecated OrderModule 不再负责 tempOrder localStorage 持久化;草稿保存请使用 IndexedDB saveDraft。 */
|
|
341
774
|
persistTempOrder() {
|
|
342
|
-
|
|
343
|
-
if (!key || !this.store.tempOrder) return;
|
|
344
|
-
if (!this.window) return;
|
|
345
|
-
this.window.localStorage.setItem(key, JSON.stringify(this.store.tempOrder));
|
|
775
|
+
// no-op: OrderModule 的刷新恢复不再依赖 localStorage。
|
|
346
776
|
}
|
|
347
777
|
notifyTempOrderChanged() {
|
|
348
778
|
if (!this.store.tempOrder) return;
|
|
@@ -358,263 +788,2722 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
358
788
|
|
|
359
789
|
createDefaultTempOrderInstance() {
|
|
360
790
|
return (0, _utils.createDefaultTempOrder)({
|
|
361
|
-
now: (0, _utils.formatDateTime)(new Date())
|
|
362
|
-
summary: (0, _utils.createEmptySummary)()
|
|
791
|
+
now: (0, _utils.formatDateTime)(new Date())
|
|
363
792
|
});
|
|
364
793
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
this.
|
|
370
|
-
|
|
794
|
+
createExternalSaleNumber() {
|
|
795
|
+
const timestamp = (0, _dayjs.default)().format('YYYYMMDDHHmmssSSS');
|
|
796
|
+
const random6 = String(Math.floor(Math.random() * 1000000)).padStart(6, '0');
|
|
797
|
+
const parts = ['LOCAL'];
|
|
798
|
+
const context = this.core?.context;
|
|
799
|
+
const getData = context?.getData;
|
|
800
|
+
if (typeof getData === 'function') {
|
|
801
|
+
const shopId = getData.call(context, 'shop_id');
|
|
802
|
+
const deviceId = getData.call(context, 'device_id');
|
|
803
|
+
if (shopId !== undefined && shopId !== null && shopId !== '') {
|
|
804
|
+
parts.push(String(shopId));
|
|
805
|
+
}
|
|
806
|
+
if (deviceId !== undefined && deviceId !== null && deviceId !== '') {
|
|
807
|
+
parts.push(String(deviceId).slice(-8));
|
|
808
|
+
}
|
|
809
|
+
} else {
|
|
810
|
+
this.logWarning('external_sale_number context getData unavailable', {
|
|
811
|
+
key: 'shop_id'
|
|
812
|
+
});
|
|
813
|
+
this.logWarning('external_sale_number context getData unavailable', {
|
|
814
|
+
key: 'device_id'
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
parts.push(timestamp, random6);
|
|
818
|
+
return parts.join('_');
|
|
371
819
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
820
|
+
ensureExternalSaleNumber(tempOrder) {
|
|
821
|
+
if (!tempOrder.external_sale_number) {
|
|
822
|
+
tempOrder.external_sale_number = this.createExternalSaleNumber();
|
|
823
|
+
}
|
|
824
|
+
this.ensureRequestUniqueIdempotencyToken(tempOrder);
|
|
825
|
+
return tempOrder.external_sale_number;
|
|
377
826
|
}
|
|
378
|
-
|
|
379
|
-
|
|
827
|
+
ensureRequestUniqueIdempotencyToken(tempOrder) {
|
|
828
|
+
if (!tempOrder.request_unique_idempotency_token) {
|
|
829
|
+
tempOrder.request_unique_idempotency_token = `${tempOrder.external_sale_number}_${Date.now()}`;
|
|
830
|
+
}
|
|
831
|
+
return tempOrder.request_unique_idempotency_token;
|
|
380
832
|
}
|
|
381
|
-
|
|
833
|
+
buildPaymentSyncIdempotencyToken(tempOrder, payments) {
|
|
834
|
+
const baseToken = this.ensureRequestUniqueIdempotencyToken(tempOrder);
|
|
835
|
+
const paidPaymentsCount = (payments || []).filter(payment => {
|
|
836
|
+
return String(payment?.status || '').toLowerCase() === 'paid';
|
|
837
|
+
}).length;
|
|
838
|
+
const paidCount = paidPaymentsCount > 0 ? paidPaymentsCount : payments.length;
|
|
839
|
+
return `${baseToken}_payment_${paidCount}`;
|
|
840
|
+
}
|
|
841
|
+
async saveDraft() {
|
|
842
|
+
// no-op: 普通 OrderModule 不再负责订单草稿或本地数据库持久化。
|
|
843
|
+
}
|
|
844
|
+
saveDraftInBackground() {
|
|
845
|
+
void this.saveDraft();
|
|
846
|
+
}
|
|
847
|
+
async hydrateTempOrderFromLocalRecord(record) {
|
|
848
|
+
return this.hydrateTempOrderFromRecord(record);
|
|
849
|
+
}
|
|
850
|
+
extractOrderIdFromSubmitResult(result) {
|
|
851
|
+
return result?.data?.order_id ?? result?.order_id ?? result?.data?.id ?? result?.id ?? null;
|
|
852
|
+
}
|
|
853
|
+
calculatePaymentEffectiveAmount(payment) {
|
|
854
|
+
const amount = new _decimal.default(payment.amount || 0);
|
|
855
|
+
const originAmount = new _decimal.default(payment.origin_amount ?? payment.amount ?? 0);
|
|
856
|
+
const serviceFee = new _decimal.default(payment.service_fee || 0);
|
|
857
|
+
// TODO: 后端允许 amount 表示顾客实际支付总额后,移除这个兼容分支。
|
|
858
|
+
const effectiveAmount = serviceFee.gt(0) && amount.eq(originAmount) ? amount.plus(serviceFee) : amount;
|
|
859
|
+
const rounding = new _decimal.default(payment.rounding_amount || 0);
|
|
860
|
+
return effectiveAmount.minus(rounding);
|
|
861
|
+
}
|
|
862
|
+
calculatePaymentTotal(payments) {
|
|
863
|
+
return (0, _utils.mapPaymentItemsToOrderPayments)(payments, {
|
|
864
|
+
includeVoided: false
|
|
865
|
+
}).reduce((sum, payment) => {
|
|
866
|
+
if (!isPaidPaymentRecord(payment)) return sum;
|
|
867
|
+
return sum.plus(this.calculatePaymentEffectiveAmount(payment));
|
|
868
|
+
}, new _decimal.default(0));
|
|
869
|
+
}
|
|
870
|
+
calculateOrderPaidPaymentTotal(payments) {
|
|
871
|
+
return (0, _utils.mapPaymentItemsToOrderPayments)(payments, {
|
|
872
|
+
includeVoided: false
|
|
873
|
+
}).reduce((sum, payment) => {
|
|
874
|
+
if (!isPaidPaymentRecord(payment)) return sum;
|
|
875
|
+
return sum.plus(this.calculatePaymentGapAmount(payment));
|
|
876
|
+
}, new _decimal.default(0));
|
|
877
|
+
}
|
|
878
|
+
getDepositAmount(tempOrder, summary) {
|
|
879
|
+
return new _decimal.default(summary.deposit_amount || tempOrder.deposit_amount || '0.00');
|
|
880
|
+
}
|
|
881
|
+
normalizeDepositAmount(amount) {
|
|
882
|
+
return _decimal.default.max(new _decimal.default(Number(amount) || 0), 0).toDecimalPlaces(2).toFixed(2);
|
|
883
|
+
}
|
|
884
|
+
getLastOrderDepositSnapshot() {
|
|
885
|
+
const lastOrderInfo = this.store.lastOrderInfo;
|
|
886
|
+
if (!lastOrderInfo || typeof lastOrderInfo !== 'object') return null;
|
|
887
|
+
const rawDepositAmount = lastOrderInfo.deposit_amount ?? lastOrderInfo.summary?.deposit_amount;
|
|
888
|
+
const depositAmount = this.normalizeDepositAmount(rawDepositAmount ?? 0);
|
|
889
|
+
const hasDepositAmount = new _decimal.default(depositAmount).gt(0);
|
|
890
|
+
const isDeposit = Number(lastOrderInfo.is_deposit) === 1 || hasDepositAmount;
|
|
891
|
+
if (!isDeposit || !hasDepositAmount) return null;
|
|
892
|
+
return {
|
|
893
|
+
isDeposit: 1,
|
|
894
|
+
depositAmount
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
getLastOrderSubmitSnapshot() {
|
|
898
|
+
const lastOrderInfo = this.store.lastOrderInfo;
|
|
899
|
+
if (!lastOrderInfo || typeof lastOrderInfo !== 'object') return {};
|
|
900
|
+
const businessCode = lastOrderInfo.business_code;
|
|
901
|
+
const type = lastOrderInfo.type;
|
|
902
|
+
return {
|
|
903
|
+
...(typeof businessCode === 'string' && businessCode ? {
|
|
904
|
+
businessCode
|
|
905
|
+
} : {}),
|
|
906
|
+
...(typeof type === 'string' && type ? {
|
|
907
|
+
type
|
|
908
|
+
} : {})
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
applyDepositAmountToSummary(tempOrder, summary, amount) {
|
|
912
|
+
const depositAmount = this.normalizeDepositAmount(amount);
|
|
913
|
+
const hasDepositAmount = new _decimal.default(depositAmount).gt(0);
|
|
914
|
+
const existedDeposit = summary.deposit && typeof summary.deposit === 'object' ? summary.deposit : undefined;
|
|
915
|
+
const nextSummary = {
|
|
916
|
+
...summary,
|
|
917
|
+
deposit_amount: depositAmount,
|
|
918
|
+
deposit: hasDepositAmount ? {
|
|
919
|
+
...(existedDeposit || {}),
|
|
920
|
+
total: depositAmount,
|
|
921
|
+
deposit_policy_ids: existedDeposit?.deposit_policy_ids || [],
|
|
922
|
+
hasDeposit: true
|
|
923
|
+
} : undefined
|
|
924
|
+
};
|
|
925
|
+
tempOrder.deposit_amount = depositAmount;
|
|
926
|
+
tempOrder.is_deposit = hasDepositAmount ? 1 : 0;
|
|
927
|
+
return nextSummary;
|
|
928
|
+
}
|
|
929
|
+
applyLastOrderDepositSnapshot(tempOrder, summary) {
|
|
930
|
+
const snapshot = this.getLastOrderDepositSnapshot();
|
|
931
|
+
if (!snapshot) return summary;
|
|
932
|
+
const existedDeposit = summary.deposit && typeof summary.deposit === 'object' ? summary.deposit : undefined;
|
|
933
|
+
const nextSummary = {
|
|
934
|
+
...summary,
|
|
935
|
+
deposit_amount: snapshot.depositAmount,
|
|
936
|
+
deposit: {
|
|
937
|
+
...(existedDeposit || {}),
|
|
938
|
+
total: snapshot.depositAmount,
|
|
939
|
+
deposit_policy_ids: existedDeposit?.deposit_policy_ids || [],
|
|
940
|
+
hasDeposit: true
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
tempOrder.deposit_amount = snapshot.depositAmount;
|
|
944
|
+
tempOrder.is_deposit = tempOrder.is_deposit === 0 && new _decimal.default(tempOrder.deposit_amount || 0).gt(0) ? 0 : snapshot.isDeposit;
|
|
945
|
+
return nextSummary;
|
|
946
|
+
}
|
|
947
|
+
isDepositOrder(tempOrder, depositAmount) {
|
|
948
|
+
return tempOrder.is_deposit === 1 && depositAmount.gt(0);
|
|
949
|
+
}
|
|
950
|
+
getOrderExpectedAmount(summary) {
|
|
951
|
+
return new _decimal.default(summary.expect_amount || summary.total_amount || 0);
|
|
952
|
+
}
|
|
953
|
+
isDepositCovered(params) {
|
|
954
|
+
const depositAmount = this.getDepositAmount(params.tempOrder, params.summary);
|
|
955
|
+
if (!this.isDepositOrder(params.tempOrder, depositAmount)) return false;
|
|
956
|
+
if (depositAmount.lte(0)) return false;
|
|
957
|
+
return params.paidAmount.gte(depositAmount);
|
|
958
|
+
}
|
|
959
|
+
getPaymentCompletion(payments) {
|
|
382
960
|
const tempOrder = this.ensureTempOrder();
|
|
383
|
-
|
|
384
|
-
|
|
961
|
+
const summary = this.store.summary || (0, _utils.createEmptySummary)();
|
|
962
|
+
const paymentList = payments || tempOrder.payments || [];
|
|
963
|
+
const refundAmount = this.getSummaryRefundAmount(summary);
|
|
964
|
+
const netPaidAmount = _decimal.default.max(this.calculateOrderPaidPaymentTotal(paymentList).minus(refundAmount), 0);
|
|
965
|
+
const depositAmount = this.getDepositAmount(tempOrder, summary);
|
|
966
|
+
const orderExpectedAmount = this.getOrderExpectedAmount(summary);
|
|
967
|
+
const isDepositFullyPaid = this.isDepositCovered({
|
|
968
|
+
tempOrder,
|
|
969
|
+
summary,
|
|
970
|
+
paidAmount: netPaidAmount
|
|
385
971
|
});
|
|
386
|
-
|
|
387
|
-
|
|
972
|
+
const isOrderFullyPaid = orderExpectedAmount.lte(0) ? true : netPaidAmount.gte(orderExpectedAmount);
|
|
973
|
+
const paymentStage = this.isDepositOrder(tempOrder, depositAmount) && !isDepositFullyPaid ? 'deposit' : 'normal';
|
|
974
|
+
return {
|
|
975
|
+
isFullyPaid: isOrderFullyPaid,
|
|
976
|
+
isOrderFullyPaid,
|
|
977
|
+
isDepositFullyPaid,
|
|
978
|
+
paymentStage,
|
|
979
|
+
depositAmount: depositAmount.toFixed(2),
|
|
980
|
+
orderExpectedAmount: orderExpectedAmount.toFixed(2)
|
|
981
|
+
};
|
|
388
982
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
983
|
+
calculateDepositPaidAmount(payments) {
|
|
984
|
+
return (payments || []).reduce((sum, payment) => {
|
|
985
|
+
const paymentRecord = payment;
|
|
986
|
+
if (!isPaidPaymentRecord(paymentRecord)) return sum;
|
|
987
|
+
const isDepositPayment = paymentRecord.type === 'deposit' || paymentRecord.order_payment_type === 'deposit';
|
|
988
|
+
if (!isDepositPayment) return sum;
|
|
989
|
+
return sum.plus(this.calculatePaymentGapAmount(paymentRecord));
|
|
990
|
+
}, new _decimal.default(0));
|
|
392
991
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
return
|
|
992
|
+
resolveNextOrderPaymentType(tempOrder) {
|
|
993
|
+
const depositAmount = new _decimal.default(tempOrder.deposit_amount || '0.00');
|
|
994
|
+
if (!this.isDepositOrder(tempOrder, depositAmount)) return 'normal';
|
|
995
|
+
if (depositAmount.lte(0)) return 'normal';
|
|
996
|
+
const depositPaidAmount = this.calculateDepositPaidAmount(tempOrder.payments || []);
|
|
997
|
+
return depositPaidAmount.lt(depositAmount) ? 'deposit' : 'normal';
|
|
399
998
|
}
|
|
400
|
-
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
return tempOrder.summary;
|
|
999
|
+
normalizePaymentSource(payment, options) {
|
|
1000
|
+
const paymentRecord = payment;
|
|
1001
|
+
const metadata = paymentRecord.metadata && typeof paymentRecord.metadata === 'object' ? {
|
|
1002
|
+
...paymentRecord.metadata
|
|
1003
|
+
} : {};
|
|
1004
|
+
if (!metadata.unique_payment_number) {
|
|
1005
|
+
metadata.unique_payment_number = (0, _utils.createUuidV4)();
|
|
408
1006
|
}
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
1007
|
+
const normalized = {
|
|
1008
|
+
...paymentRecord,
|
|
1009
|
+
metadata,
|
|
1010
|
+
origin_amount: paymentRecord.origin_amount ?? paymentRecord.amount ?? '0.00'
|
|
1011
|
+
};
|
|
1012
|
+
if (paymentRecord.status !== undefined) {
|
|
1013
|
+
normalized.status = paymentRecord.status;
|
|
1014
|
+
} else if (options?.defaultPaid !== false) {
|
|
1015
|
+
normalized.status = 'paid';
|
|
415
1016
|
}
|
|
416
|
-
|
|
417
|
-
|
|
1017
|
+
return normalized;
|
|
1018
|
+
}
|
|
1019
|
+
updateTempOrderPaymentStatus(tempOrder, options) {
|
|
1020
|
+
const summary = options?.summary || this.store.summary || (0, _utils.createEmptySummary)();
|
|
1021
|
+
const paymentTotal = this.calculatePaymentTotal(tempOrder.payments || []);
|
|
1022
|
+
const orderPaymentTotal = this.calculateOrderPaidPaymentTotal(tempOrder.payments || []);
|
|
1023
|
+
const refundAmount = this.getSummaryRefundAmount(summary);
|
|
1024
|
+
const netPaymentTotal = _decimal.default.max(orderPaymentTotal.minus(refundAmount), 0);
|
|
1025
|
+
const targetAmount = this.calculatePaymentTargetAmount(tempOrder, summary, netPaymentTotal);
|
|
1026
|
+
if (this.shouldKeepPaymentStatusAfterAmountRecalc(tempOrder.payment_status, {
|
|
1027
|
+
netPaymentTotal,
|
|
1028
|
+
paymentTotal,
|
|
1029
|
+
refundAmount,
|
|
1030
|
+
targetAmount,
|
|
1031
|
+
previousSummary: options?.previousSummary,
|
|
1032
|
+
tempOrder
|
|
1033
|
+
})) {
|
|
1034
|
+
return;
|
|
418
1035
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
1036
|
+
if (targetAmount.lte(0)) {
|
|
1037
|
+
tempOrder.payment_status = 'paid';
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
if (netPaymentTotal.lte(0)) {
|
|
1041
|
+
tempOrder.payment_status = 'payment_processing';
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
tempOrder.payment_status = netPaymentTotal.gte(targetAmount) ? 'paid' : 'partially_paid';
|
|
425
1045
|
}
|
|
426
|
-
|
|
427
|
-
const
|
|
428
|
-
|
|
1046
|
+
shouldKeepPaymentStatusAfterAmountRecalc(paymentStatus, params) {
|
|
1047
|
+
const normalizedStatus = String(paymentStatus || '').trim().toLowerCase();
|
|
1048
|
+
if (normalizedStatus !== 'refunded' && normalizedStatus !== 'partially_refunded') {
|
|
1049
|
+
return false;
|
|
1050
|
+
}
|
|
1051
|
+
if (normalizedStatus === 'refunded' && params.paymentTotal.gt(0) && params.refundAmount.gte(params.paymentTotal)) {
|
|
1052
|
+
return true;
|
|
1053
|
+
}
|
|
1054
|
+
if (params.previousSummary) {
|
|
1055
|
+
const previousTargetAmount = this.calculatePaymentTargetAmount(params.tempOrder, params.previousSummary, params.netPaymentTotal);
|
|
1056
|
+
if (!previousTargetAmount.eq(params.targetAmount)) return false;
|
|
1057
|
+
}
|
|
1058
|
+
return true;
|
|
1059
|
+
}
|
|
1060
|
+
resolveWalletPassUseValue(payment, amount) {
|
|
1061
|
+
const usageUnit = payment.wallet_pass_usage_unit && typeof payment.wallet_pass_usage_unit === 'object' ? payment.wallet_pass_usage_unit : null;
|
|
1062
|
+
if (!usageUnit) return payment.wallet_pass_use_value;
|
|
1063
|
+
const price = new _decimal.default(usageUnit.price || 0);
|
|
1064
|
+
const points = new _decimal.default(usageUnit.points || 0);
|
|
1065
|
+
if (price.lte(0) || points.lte(0)) return payment.wallet_pass_use_value;
|
|
1066
|
+
return amount.div(price).times(points).toDecimalPlaces(2).toFixed(2);
|
|
1067
|
+
}
|
|
1068
|
+
adjustVoucherFundRecord(params) {
|
|
1069
|
+
const fundRecord = params.voucher.fund_record && typeof params.voucher.fund_record === 'object' && !Array.isArray(params.voucher.fund_record) ? {
|
|
1070
|
+
...params.voucher.fund_record
|
|
1071
|
+
} : null;
|
|
1072
|
+
if (!fundRecord || fundRecord.using_before_value === undefined || fundRecord.using_before_value === null) {
|
|
1073
|
+
return fundRecord || undefined;
|
|
1074
|
+
}
|
|
1075
|
+
const code = String(params.voucher.code || '').toUpperCase();
|
|
1076
|
+
const isPointCard = code === 'POINTCARD' || String(params.voucher.fund_record?.tag || '').toLowerCase() === 'point_card';
|
|
1077
|
+
const usingValue = isPointCard ? new _decimal.default(params.walletPassUseValue || 0) : params.amount;
|
|
1078
|
+
fundRecord.using_after_value = new _decimal.default(fundRecord.using_before_value || 0).minus(usingValue).toDecimalPlaces(2).toFixed(2);
|
|
1079
|
+
return fundRecord;
|
|
1080
|
+
}
|
|
1081
|
+
capVoucherPaymentsByRemainingAmount(params) {
|
|
1082
|
+
const targetAmount = this.getPaymentTargetAmount();
|
|
1083
|
+
const nonVoucherTotal = this.calculateOrderPaidPaymentTotal(params.nonVoucherPayments);
|
|
1084
|
+
if (targetAmount.lte(0)) return [];
|
|
1085
|
+
let remainingAmount = _decimal.default.max(targetAmount.minus(nonVoucherTotal), 0);
|
|
1086
|
+
const cappedVouchers = [];
|
|
1087
|
+
for (const voucher of params.vouchers) {
|
|
1088
|
+
if (remainingAmount.lte(0)) break;
|
|
1089
|
+
const voucherAmount = _decimal.default.max(new _decimal.default(voucher.amount || 0), 0);
|
|
1090
|
+
if (voucherAmount.lte(0)) continue;
|
|
1091
|
+
const cappedAmount = _decimal.default.min(voucherAmount, remainingAmount);
|
|
1092
|
+
const amountText = cappedAmount.toDecimalPlaces(2).toFixed(2);
|
|
1093
|
+
const walletPassUseValue = this.resolveWalletPassUseValue(voucher, cappedAmount);
|
|
1094
|
+
cappedVouchers.push({
|
|
1095
|
+
...voucher,
|
|
1096
|
+
amount: amountText,
|
|
1097
|
+
origin_amount: amountText,
|
|
1098
|
+
wallet_pass_use_value: walletPassUseValue,
|
|
1099
|
+
fund_record: this.adjustVoucherFundRecord({
|
|
1100
|
+
voucher: voucher,
|
|
1101
|
+
amount: cappedAmount,
|
|
1102
|
+
walletPassUseValue
|
|
1103
|
+
})
|
|
1104
|
+
});
|
|
1105
|
+
remainingAmount = remainingAmount.minus(cappedAmount);
|
|
1106
|
+
}
|
|
1107
|
+
return cappedVouchers;
|
|
1108
|
+
}
|
|
1109
|
+
calculatePaidPaymentSummary(payments) {
|
|
1110
|
+
return (payments || []).reduce((summary, payment) => {
|
|
1111
|
+
const paymentRecord = payment;
|
|
1112
|
+
if (!isPaidPaymentRecord(paymentRecord)) return summary;
|
|
1113
|
+
return {
|
|
1114
|
+
customerPaidAmount: summary.customerPaidAmount.plus(paymentRecord.amount || 0),
|
|
1115
|
+
orderPaidAmount: summary.orderPaidAmount.plus(this.calculatePaymentOrderAmount(paymentRecord)),
|
|
1116
|
+
gapPaidAmount: summary.gapPaidAmount.plus(this.calculatePaymentGapAmount(paymentRecord)),
|
|
1117
|
+
payServiceChargeAmount: summary.payServiceChargeAmount.plus(this.calculatePaymentServiceFee(paymentRecord)),
|
|
1118
|
+
roundingAmount: summary.roundingAmount.plus(this.calculatePaymentRoundingAmount(paymentRecord))
|
|
1119
|
+
};
|
|
1120
|
+
}, {
|
|
1121
|
+
customerPaidAmount: new _decimal.default(0),
|
|
1122
|
+
orderPaidAmount: new _decimal.default(0),
|
|
1123
|
+
gapPaidAmount: new _decimal.default(0),
|
|
1124
|
+
payServiceChargeAmount: new _decimal.default(0),
|
|
1125
|
+
roundingAmount: new _decimal.default(0)
|
|
429
1126
|
});
|
|
430
|
-
if (!summary) return (0, _utils.createEmptySummary)();
|
|
431
|
-
this.persistTempOrder();
|
|
432
|
-
return summary;
|
|
433
1127
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
return
|
|
1128
|
+
calculatePaymentServiceFee(payment) {
|
|
1129
|
+
if (payment.service_fee !== undefined && payment.service_fee !== null && payment.service_fee !== '') {
|
|
1130
|
+
return new _decimal.default(payment.service_fee || 0);
|
|
1131
|
+
}
|
|
1132
|
+
const serviceCharge = payment.service_charge && typeof payment.service_charge === 'object' ? payment.service_charge : {};
|
|
1133
|
+
const percentage = new _decimal.default(serviceCharge.percentage || 0);
|
|
1134
|
+
const fixedAmount = new _decimal.default(serviceCharge.amount || 0);
|
|
1135
|
+
return new _decimal.default(payment.amount || 0).times(percentage).plus(fixedAmount);
|
|
442
1136
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
1137
|
+
calculatePaymentGapAmount(payment) {
|
|
1138
|
+
if (payment.origin_amount !== undefined && payment.origin_amount !== null && payment.origin_amount !== '') {
|
|
1139
|
+
const rounding = new _decimal.default(payment.rounding_amount || 0);
|
|
1140
|
+
return new _decimal.default(payment.origin_amount || 0).minus(rounding);
|
|
1141
|
+
}
|
|
1142
|
+
const rounding = new _decimal.default(payment.rounding_amount || 0);
|
|
1143
|
+
const amount = new _decimal.default(payment.amount || 0);
|
|
1144
|
+
const serviceFee = this.calculatePaymentServiceFee(payment);
|
|
1145
|
+
if (serviceFee.lte(0)) return amount.minus(rounding);
|
|
1146
|
+
if (!hasSyncedOrderPaymentRecord(payment)) return new _decimal.default(0);
|
|
1147
|
+
return _decimal.default.max(amount.minus(serviceFee), 0).minus(rounding);
|
|
448
1148
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
1149
|
+
calculatePaymentOrderAmount(payment) {
|
|
1150
|
+
if (payment.origin_amount !== undefined && payment.origin_amount !== null && payment.origin_amount !== '') {
|
|
1151
|
+
return new _decimal.default(payment.origin_amount || 0);
|
|
1152
|
+
}
|
|
1153
|
+
const amount = new _decimal.default(payment.amount || 0);
|
|
1154
|
+
const serviceFee = this.calculatePaymentServiceFee(payment);
|
|
1155
|
+
if (serviceFee.lte(0)) return amount;
|
|
1156
|
+
if (!hasSyncedOrderPaymentRecord(payment)) return new _decimal.default(0);
|
|
1157
|
+
return _decimal.default.max(amount.minus(serviceFee), 0);
|
|
454
1158
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
1159
|
+
calculatePaymentRoundingAmount(payment) {
|
|
1160
|
+
if (payment.rounding_amount === undefined || payment.rounding_amount === null || payment.rounding_amount === '') {
|
|
1161
|
+
return new _decimal.default(0);
|
|
1162
|
+
}
|
|
1163
|
+
return new _decimal.default(payment.rounding_amount || 0);
|
|
1164
|
+
}
|
|
1165
|
+
formatSummaryMetadataMoney(value) {
|
|
1166
|
+
if (value === undefined || value === null || value === '') return undefined;
|
|
1167
|
+
if (value instanceof _decimal.default) return value.toFixed(2);
|
|
1168
|
+
return new _decimal.default(String(value || 0)).toFixed(2);
|
|
1169
|
+
}
|
|
1170
|
+
syncSummaryProductMetadata(products) {
|
|
1171
|
+
const moneyKeys = ['main_product_attached_bundle_surcharge_fee', 'main_product_attached_bundle_tax_fee', 'surcharge_rounding_remainder', 'tax_fee_rounding_remainder'];
|
|
1172
|
+
// 整单折扣均摊层产出的 payment 维度字段,直接由计算层写入 metadata,
|
|
1173
|
+
// 这里需把它们一并回写到商品 metadata 以便持久化/出站。
|
|
1174
|
+
const metadataMoneyKeys = ['main_product_attached_bundle_selling_price', 'main_product_attached_bundle_payment_price'];
|
|
1175
|
+
for (const product of products || []) {
|
|
1176
|
+
const productRecord = product;
|
|
1177
|
+
const metadata = productRecord.metadata && typeof productRecord.metadata === 'object' ? {
|
|
1178
|
+
...productRecord.metadata
|
|
1179
|
+
} : {};
|
|
1180
|
+
let hasMetadataPatch = false;
|
|
1181
|
+
for (const key of moneyKeys) {
|
|
1182
|
+
const value = this.formatSummaryMetadataMoney(productRecord[key]);
|
|
1183
|
+
if (value === undefined) continue;
|
|
1184
|
+
metadata[key] = value;
|
|
1185
|
+
hasMetadataPatch = true;
|
|
475
1186
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
1187
|
+
for (const key of metadataMoneyKeys) {
|
|
1188
|
+
const value = this.formatSummaryMetadataMoney(metadata[key]);
|
|
1189
|
+
if (value === undefined) continue;
|
|
1190
|
+
metadata[key] = value;
|
|
1191
|
+
hasMetadataPatch = true;
|
|
1192
|
+
}
|
|
1193
|
+
if (metadata.average_discount_amount_rate !== undefined) {
|
|
1194
|
+
hasMetadataPatch = true;
|
|
1195
|
+
}
|
|
1196
|
+
if (Array.isArray(productRecord.relation_surcharge_ids)) {
|
|
1197
|
+
metadata.relation_surcharge_ids = productRecord.relation_surcharge_ids;
|
|
1198
|
+
hasMetadataPatch = true;
|
|
1199
|
+
}
|
|
1200
|
+
const productPaymentPrice = this.formatSummaryMetadataMoney(productRecord.payment_price);
|
|
1201
|
+
if (productPaymentPrice !== undefined) {
|
|
1202
|
+
productRecord.payment_price = productPaymentPrice;
|
|
1203
|
+
}
|
|
1204
|
+
if (hasMetadataPatch) productRecord.metadata = metadata;
|
|
1205
|
+
if (!Array.isArray(productRecord.product_bundle)) continue;
|
|
1206
|
+
for (const bundle of productRecord.product_bundle) {
|
|
1207
|
+
const bundleRecord = bundle;
|
|
1208
|
+
const bundleMetadata = bundleRecord.metadata && typeof bundleRecord.metadata === 'object' ? {
|
|
1209
|
+
...bundleRecord.metadata
|
|
1210
|
+
} : {};
|
|
1211
|
+
let hasBundleMetadataPatch = false;
|
|
1212
|
+
const bundleSurchargeFee = this.formatSummaryMetadataMoney(bundleRecord.surcharge_fee);
|
|
1213
|
+
if (bundleSurchargeFee !== undefined) {
|
|
1214
|
+
bundleMetadata.surcharge_fee = bundleSurchargeFee;
|
|
1215
|
+
hasBundleMetadataPatch = true;
|
|
1216
|
+
}
|
|
1217
|
+
const bundlePaymentPrice = this.formatSummaryMetadataMoney(bundleRecord.bundle_payment_price);
|
|
1218
|
+
if (bundlePaymentPrice !== undefined) {
|
|
1219
|
+
bundleRecord.bundle_payment_price = bundlePaymentPrice;
|
|
1220
|
+
}
|
|
1221
|
+
if (Array.isArray(bundleRecord.relation_surcharge_ids)) {
|
|
1222
|
+
bundleMetadata.relation_surcharge_ids = bundleRecord.relation_surcharge_ids;
|
|
1223
|
+
hasBundleMetadataPatch = true;
|
|
1224
|
+
}
|
|
1225
|
+
if (hasBundleMetadataPatch) bundleRecord.metadata = bundleMetadata;
|
|
502
1226
|
}
|
|
503
1227
|
}
|
|
504
|
-
this.applyDiscount();
|
|
505
|
-
await this.recalculateSummary({
|
|
506
|
-
createIfMissing: true
|
|
507
|
-
});
|
|
508
|
-
this.persistTempOrder();
|
|
509
|
-
return tempOrder.products;
|
|
510
1228
|
}
|
|
511
|
-
|
|
1229
|
+
calculateSummaryAmountGap(params) {
|
|
512
1230
|
const {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
identity_key,
|
|
517
|
-
product_option_item,
|
|
518
|
-
product_bundle
|
|
1231
|
+
tempOrder,
|
|
1232
|
+
summary,
|
|
1233
|
+
orderPaidAmount
|
|
519
1234
|
} = params;
|
|
520
|
-
const
|
|
1235
|
+
const refundAmount = this.getSummaryRefundAmount(summary);
|
|
1236
|
+
const netOrderPaidAmount = _decimal.default.max(orderPaidAmount.minus(refundAmount), 0);
|
|
1237
|
+
const targetAmount = this.calculatePaymentTargetAmount(tempOrder, summary, netOrderPaidAmount);
|
|
1238
|
+
return targetAmount.minus(netOrderPaidAmount).toFixed(2);
|
|
1239
|
+
}
|
|
1240
|
+
getSummaryRefundAmount(summary) {
|
|
1241
|
+
return _decimal.default.max(new _decimal.default(summary?.total_refund_amount || 0), 0);
|
|
1242
|
+
}
|
|
1243
|
+
calculatePaymentTargetAmount(tempOrder, summary, paidAmount = new _decimal.default(0)) {
|
|
1244
|
+
const depositAmount = this.getDepositAmount(tempOrder, summary);
|
|
1245
|
+
if (!this.isDepositOrder(tempOrder, depositAmount)) {
|
|
1246
|
+
return this.getOrderExpectedAmount(summary);
|
|
1247
|
+
}
|
|
1248
|
+
if (this.isDepositCovered({
|
|
1249
|
+
tempOrder,
|
|
1250
|
+
summary,
|
|
1251
|
+
paidAmount
|
|
1252
|
+
})) {
|
|
1253
|
+
return this.getOrderExpectedAmount(summary);
|
|
1254
|
+
}
|
|
1255
|
+
return depositAmount;
|
|
1256
|
+
}
|
|
1257
|
+
normalizeFingerprintMoney(value) {
|
|
1258
|
+
if (value === undefined || value === null || value === '') return '';
|
|
1259
|
+
try {
|
|
1260
|
+
const parsed = new _decimal.default(String(value || 0));
|
|
1261
|
+
return parsed.toFixed(4);
|
|
1262
|
+
} catch {
|
|
1263
|
+
return String(value);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
normalizeFingerprintValue(value) {
|
|
1267
|
+
if (Array.isArray(value)) {
|
|
1268
|
+
return value.map(item => this.normalizeFingerprintValue(item));
|
|
1269
|
+
}
|
|
1270
|
+
if (!value || typeof value !== 'object') return value ?? '';
|
|
1271
|
+
return Object.keys(value).sort().reduce((result, key) => {
|
|
1272
|
+
result[key] = this.normalizeFingerprintValue(value[key]);
|
|
1273
|
+
return result;
|
|
1274
|
+
}, {});
|
|
1275
|
+
}
|
|
1276
|
+
buildProductFingerprintItem(product) {
|
|
1277
|
+
const sku = product?.product_sku || {};
|
|
1278
|
+
const metadata = product?.metadata || {};
|
|
1279
|
+
const discountTypes = (product?.discount_list || []).map(discount => discount?.type ?? discount?.tag);
|
|
1280
|
+
const extensionType = product?.extension_type || (discountTypes.includes('good_pass') ? 'good_pass' : 'normal');
|
|
1281
|
+
return {
|
|
1282
|
+
product_id: product?.product_id ?? null,
|
|
1283
|
+
product_variant_id: product?.product_variant_id ?? null,
|
|
1284
|
+
extension_type: extensionType,
|
|
1285
|
+
num: (0, _utils3.getSafeProductNum)(product?.num ?? product?.product_quantity),
|
|
1286
|
+
selling_price: this.normalizeFingerprintMoney(product?.selling_price),
|
|
1287
|
+
original_price: this.normalizeFingerprintMoney(product?.original_price),
|
|
1288
|
+
payment_price: this.normalizeFingerprintMoney(product?.payment_price ?? product?.selling_price),
|
|
1289
|
+
metadata: {
|
|
1290
|
+
source_product_price: this.normalizeFingerprintMoney(metadata.source_product_price),
|
|
1291
|
+
main_product_selling_price: this.normalizeFingerprintMoney(metadata.main_product_selling_price),
|
|
1292
|
+
main_product_original_price: this.normalizeFingerprintMoney(metadata.main_product_original_price),
|
|
1293
|
+
product_discount_difference: this.normalizeFingerprintMoney(metadata.product_discount_difference)
|
|
1294
|
+
},
|
|
1295
|
+
product_sku: this.normalizeFingerprintValue({
|
|
1296
|
+
code: sku.code ?? '',
|
|
1297
|
+
barcode: sku.barcode ?? '',
|
|
1298
|
+
variant_id: sku.variant_id ?? product?.product_variant_id ?? 0,
|
|
1299
|
+
variant: sku.variant || [],
|
|
1300
|
+
option: (0, _utils.getProductSkuOptions)(product || {}).map(option => ({
|
|
1301
|
+
id: option?.id ?? option?.option_group_item_id ?? null,
|
|
1302
|
+
option_group_id: option?.option_group_id ?? null,
|
|
1303
|
+
option_group_item_id: option?.option_group_item_id ?? option?.id ?? null,
|
|
1304
|
+
num: Number(option?.num ?? 1) || 1,
|
|
1305
|
+
add_price: this.normalizeFingerprintMoney(option?.add_price ?? option?.price)
|
|
1306
|
+
}))
|
|
1307
|
+
}),
|
|
1308
|
+
product_bundle: this.normalizeFingerprintValue((product?.product_bundle || []).map(bundle => ({
|
|
1309
|
+
bundle_product_id: bundle?.bundle_product_id ?? bundle?._bundle_product_id ?? bundle?.product_id ?? null,
|
|
1310
|
+
bundle_variant_id: bundle?.bundle_variant_id ?? bundle?.product_variant_id ?? 0,
|
|
1311
|
+
num: (0, _utils3.getSafeProductNum)(bundle?.num ?? bundle?.quantity),
|
|
1312
|
+
price: this.normalizeFingerprintMoney(bundle?.price),
|
|
1313
|
+
bundle_selling_price: this.normalizeFingerprintMoney(bundle?.bundle_selling_price),
|
|
1314
|
+
price_type: bundle?.price_type ?? '',
|
|
1315
|
+
price_type_ext: bundle?.price_type_ext ?? ''
|
|
1316
|
+
}))),
|
|
1317
|
+
discount_list: this.normalizeFingerprintValue((product?.discount_list || []).map(discount => ({
|
|
1318
|
+
type: discount?.type ?? discount?.tag ?? '',
|
|
1319
|
+
discount_id: discount?.discount_id ?? discount?.order_discount_id ?? discount?.discount?.resource_id ?? null,
|
|
1320
|
+
amount: this.normalizeFingerprintMoney(discount?.amount)
|
|
1321
|
+
})))
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
buildOrderProductsFingerprint(products) {
|
|
1325
|
+
const items = (products || []).map(product => this.buildProductFingerprintItem(product));
|
|
1326
|
+
items.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
|
1327
|
+
return JSON.stringify(items);
|
|
1328
|
+
}
|
|
1329
|
+
buildProductStructuralFingerprintItem(product) {
|
|
1330
|
+
const sku = product?.product_sku || {};
|
|
1331
|
+
const metadata = product?.metadata || {};
|
|
1332
|
+
const discountTypes = (product?.discount_list || []).map(discount => discount?.type ?? discount?.tag);
|
|
1333
|
+
const extensionType = product?.extension_type || (discountTypes.includes('good_pass') ? 'good_pass' : 'normal');
|
|
1334
|
+
return {
|
|
1335
|
+
product_id: product?.product_id ?? null,
|
|
1336
|
+
product_variant_id: product?.product_variant_id ?? null,
|
|
1337
|
+
extension_type: extensionType,
|
|
1338
|
+
num: (0, _utils3.getSafeProductNum)(product?.num ?? product?.product_quantity),
|
|
1339
|
+
metadata: {
|
|
1340
|
+
unique_identification_number: metadata.unique_identification_number ?? product?.unique_identification_number ?? '',
|
|
1341
|
+
price_override: metadata.price_override ?? '',
|
|
1342
|
+
is_manual_discount: metadata.is_manual_discount ?? '',
|
|
1343
|
+
product_discount_reason: metadata.product_discount_reason ?? ''
|
|
1344
|
+
},
|
|
1345
|
+
product_sku: this.normalizeFingerprintValue({
|
|
1346
|
+
code: sku.code ?? '',
|
|
1347
|
+
barcode: sku.barcode ?? '',
|
|
1348
|
+
variant_id: sku.variant_id ?? product?.product_variant_id ?? 0,
|
|
1349
|
+
variant: sku.variant || [],
|
|
1350
|
+
option: (0, _utils.getProductSkuOptions)(product || {}).map(option => ({
|
|
1351
|
+
id: option?.id ?? option?.option_group_item_id ?? null,
|
|
1352
|
+
option_group_id: option?.option_group_id ?? null,
|
|
1353
|
+
option_group_item_id: option?.option_group_item_id ?? option?.id ?? null,
|
|
1354
|
+
num: Number(option?.num ?? 1) || 1,
|
|
1355
|
+
add_price: this.normalizeFingerprintMoney(option?.add_price ?? option?.price)
|
|
1356
|
+
}))
|
|
1357
|
+
}),
|
|
1358
|
+
product_bundle: this.normalizeFingerprintValue((product?.product_bundle || []).map(bundle => ({
|
|
1359
|
+
bundle_id: bundle?.bundle_id ?? bundle?.id ?? null,
|
|
1360
|
+
bundle_group_id: bundle?.bundle_group_id ?? bundle?.group_id ?? null,
|
|
1361
|
+
bundle_product_id: bundle?.bundle_product_id ?? bundle?._bundle_product_id ?? bundle?.product_id ?? null,
|
|
1362
|
+
bundle_variant_id: bundle?.bundle_variant_id ?? bundle?.product_variant_id ?? 0,
|
|
1363
|
+
num: (0, _utils3.getSafeProductNum)(bundle?.num ?? bundle?.quantity),
|
|
1364
|
+
price_type: bundle?.price_type ?? '',
|
|
1365
|
+
price_type_ext: bundle?.price_type_ext ?? '',
|
|
1366
|
+
option: this.normalizeFingerprintValue((bundle?.option || []).map(option => ({
|
|
1367
|
+
id: option?.id ?? option?.option_group_item_id ?? null,
|
|
1368
|
+
option_group_id: option?.option_group_id ?? null,
|
|
1369
|
+
option_group_item_id: option?.option_group_item_id ?? option?.id ?? null,
|
|
1370
|
+
num: Number(option?.num ?? 1) || 1,
|
|
1371
|
+
add_price: this.normalizeFingerprintMoney(option?.add_price ?? option?.price)
|
|
1372
|
+
})))
|
|
1373
|
+
}))),
|
|
1374
|
+
discount_list: this.normalizeFingerprintValue((product?.discount_list || []).map(discount => ({
|
|
1375
|
+
type: discount?.type ?? discount?.tag ?? '',
|
|
1376
|
+
discount_id: discount?.discount_id ?? discount?.order_discount_id ?? discount?.discount?.resource_id ?? null,
|
|
1377
|
+
amount: this.normalizeFingerprintMoney(discount?.amount)
|
|
1378
|
+
})))
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
buildOrderProductsStructuralFingerprint(products) {
|
|
1382
|
+
const items = (products || []).map(product => this.buildProductStructuralFingerprintItem(product));
|
|
1383
|
+
items.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
|
1384
|
+
return JSON.stringify(items);
|
|
1385
|
+
}
|
|
1386
|
+
getManualDepositOverride(tempOrder) {
|
|
1387
|
+
const override = tempOrder._extend?.manualDepositOverride;
|
|
1388
|
+
if (!override || typeof override !== 'object') return null;
|
|
1389
|
+
if (typeof override.amount !== 'string') return null;
|
|
1390
|
+
if (typeof override.productStructuralFingerprint !== 'string') return null;
|
|
1391
|
+
return override;
|
|
1392
|
+
}
|
|
1393
|
+
setManualDepositOverride(tempOrder, amount) {
|
|
1394
|
+
tempOrder._extend = {
|
|
1395
|
+
...(tempOrder._extend || {}),
|
|
1396
|
+
manualDepositOverride: {
|
|
1397
|
+
amount,
|
|
1398
|
+
productStructuralFingerprint: this.buildOrderProductsStructuralFingerprint(tempOrder.products)
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
clearManualDepositOverride(tempOrder) {
|
|
1403
|
+
if (!tempOrder._extend?.manualDepositOverride) return;
|
|
1404
|
+
tempOrder._extend = {
|
|
1405
|
+
...tempOrder._extend
|
|
1406
|
+
};
|
|
1407
|
+
delete tempOrder._extend.manualDepositOverride;
|
|
1408
|
+
}
|
|
1409
|
+
getOriginalSalesSnapshot(tempOrder) {
|
|
1410
|
+
const snapshot = tempOrder._extend?.originalSalesSnapshot;
|
|
1411
|
+
if (!snapshot || typeof snapshot !== 'object') return null;
|
|
1412
|
+
if (typeof snapshot.productFingerprint !== 'string') return null;
|
|
1413
|
+
return snapshot;
|
|
1414
|
+
}
|
|
1415
|
+
isSnapshotPayableAmountUnchanged(tempOrder, snapshot, snapshotSummary) {
|
|
1416
|
+
const currentPaidPaymentSummary = this.calculatePaidPaymentSummary(tempOrder.payments || []);
|
|
1417
|
+
const currentAmountGap = this.calculateSummaryAmountGap({
|
|
1418
|
+
tempOrder,
|
|
1419
|
+
summary: snapshotSummary,
|
|
1420
|
+
orderPaidAmount: currentPaidPaymentSummary.gapPaidAmount
|
|
1421
|
+
});
|
|
1422
|
+
const snapshotPaidPaymentSummary = this.calculatePaidPaymentSummary(Array.isArray(snapshot.payments) ? snapshot.payments : []);
|
|
1423
|
+
const snapshotAmountGap = this.calculateSummaryAmountGap({
|
|
1424
|
+
tempOrder,
|
|
1425
|
+
summary: snapshotSummary,
|
|
1426
|
+
orderPaidAmount: snapshotPaidPaymentSummary.gapPaidAmount
|
|
1427
|
+
});
|
|
1428
|
+
return currentAmountGap === snapshotAmountGap;
|
|
1429
|
+
}
|
|
1430
|
+
hasShopDiscountChangedFromSnapshot(tempOrder, snapshot) {
|
|
1431
|
+
const snapshotSummary = snapshot.summary && typeof snapshot.summary === 'object' ? snapshot.summary : {};
|
|
1432
|
+
const snapshotShopDiscount = snapshot.shop_discount ?? snapshotSummary.shop_discount;
|
|
1433
|
+
if (snapshotShopDiscount === undefined || snapshotShopDiscount === null || snapshotShopDiscount === '') {
|
|
1434
|
+
return !new _decimal.default(tempOrder.shop_discount || 0).equals(0);
|
|
1435
|
+
}
|
|
1436
|
+
return !new _decimal.default(tempOrder.shop_discount || 0).equals(snapshotShopDiscount || 0);
|
|
1437
|
+
}
|
|
1438
|
+
restoreOriginalSnapshotIfProductsUnchanged(tempOrder, snapshot) {
|
|
1439
|
+
const currentFingerprint = this.buildOrderProductsFingerprint(tempOrder.products);
|
|
1440
|
+
const manualDepositOverride = this.getManualDepositOverride(tempOrder);
|
|
1441
|
+
if (currentFingerprint !== snapshot.productFingerprint) {
|
|
1442
|
+
const currentStructuralFingerprint = this.buildOrderProductsStructuralFingerprint(tempOrder.products);
|
|
1443
|
+
const snapshotStructuralFingerprint = this.buildOrderProductsStructuralFingerprint(Array.isArray(snapshot.products) ? snapshot.products : []);
|
|
1444
|
+
if (currentStructuralFingerprint !== snapshotStructuralFingerprint) return null;
|
|
1445
|
+
}
|
|
1446
|
+
let summary = {
|
|
1447
|
+
...(0, _utils.createEmptySummary)(),
|
|
1448
|
+
...(0, _lodashEs.cloneDeep)(snapshot.summary || {})
|
|
1449
|
+
};
|
|
1450
|
+
const shouldUseManualDepositOverride = Boolean(manualDepositOverride && manualDepositOverride.productStructuralFingerprint === this.buildOrderProductsStructuralFingerprint(tempOrder.products));
|
|
1451
|
+
if (shouldUseManualDepositOverride) {
|
|
1452
|
+
summary = this.applyDepositAmountToSummary(tempOrder, summary, manualDepositOverride.amount);
|
|
1453
|
+
} else if (manualDepositOverride) {
|
|
1454
|
+
this.clearManualDepositOverride(tempOrder);
|
|
1455
|
+
}
|
|
1456
|
+
const isPayableAmountUnchanged = this.isSnapshotPayableAmountUnchanged(tempOrder, snapshot, summary);
|
|
1457
|
+
if (!isPayableAmountUnchanged) return null;
|
|
1458
|
+
if (Array.isArray(snapshot.products)) {
|
|
1459
|
+
tempOrder.products = (0, _lodashEs.cloneDeep)(snapshot.products);
|
|
1460
|
+
}
|
|
1461
|
+
if (Array.isArray(snapshot.surcharges)) {
|
|
1462
|
+
tempOrder.surcharges = (0, _lodashEs.cloneDeep)(snapshot.surcharges);
|
|
1463
|
+
}
|
|
1464
|
+
tempOrder.surcharge_fee = summary.surcharge_fee || tempOrder.surcharge_fee;
|
|
1465
|
+
tempOrder.deposit_amount = summary.deposit_amount || '0.00';
|
|
1466
|
+
tempOrder.is_deposit = summary.deposit_amount !== '0.00' ? 1 : 0;
|
|
1467
|
+
const previousSummary = this.store.summary;
|
|
1468
|
+
this.store.summary = summary;
|
|
1469
|
+
this.updateTempOrderPaymentStatus(tempOrder, {
|
|
1470
|
+
previousSummary,
|
|
1471
|
+
summary
|
|
1472
|
+
});
|
|
1473
|
+
return summary;
|
|
1474
|
+
}
|
|
1475
|
+
clearPersistedAmountFieldsForFullRecalc(products) {
|
|
1476
|
+
const moneyKeys = ['surcharge_fee', 'main_product_attached_bundle_surcharge_fee', 'surcharge_rounding_remainder', 'main_product_attached_bundle_tax_fee', 'tax_fee', 'original_tax_fee', 'tax_fee_rounding_remainder', 'original_tax_fee_rounding_remainder'];
|
|
1477
|
+
const metadataMoneyKeys = ['main_product_attached_bundle_surcharge_fee', 'surcharge_rounding_remainder', 'main_product_attached_bundle_tax_fee', 'tax_fee_rounding_remainder'];
|
|
1478
|
+
for (const product of products || []) {
|
|
1479
|
+
const record = product;
|
|
1480
|
+
for (const key of moneyKeys) delete record[key];
|
|
1481
|
+
if (record.metadata && typeof record.metadata === 'object') {
|
|
1482
|
+
record.metadata = {
|
|
1483
|
+
...record.metadata
|
|
1484
|
+
};
|
|
1485
|
+
for (const key of metadataMoneyKeys) delete record.metadata[key];
|
|
1486
|
+
}
|
|
1487
|
+
for (const bundle of record.product_bundle || []) {
|
|
1488
|
+
for (const key of moneyKeys) delete bundle[key];
|
|
1489
|
+
if (bundle.metadata && typeof bundle.metadata === 'object') {
|
|
1490
|
+
bundle.metadata = {
|
|
1491
|
+
...bundle.metadata
|
|
1492
|
+
};
|
|
1493
|
+
for (const key of metadataMoneyKeys) delete bundle.metadata[key];
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
getPaymentTargetAmount() {
|
|
1499
|
+
const tempOrder = this.ensureTempOrder();
|
|
1500
|
+
const summary = this.store.summary || (0, _utils.createEmptySummary)();
|
|
1501
|
+
const paidPaymentSummary = this.calculatePaidPaymentSummary(tempOrder.payments || []);
|
|
1502
|
+
const refundAmount = this.getSummaryRefundAmount(summary);
|
|
1503
|
+
const netPaidAmount = _decimal.default.max(paidPaymentSummary.gapPaidAmount.minus(refundAmount), 0);
|
|
1504
|
+
return this.calculatePaymentTargetAmount(tempOrder, summary, netPaidAmount);
|
|
1505
|
+
}
|
|
1506
|
+
ensureTempOrder() {
|
|
1507
|
+
if (this.store.tempOrder) {
|
|
1508
|
+
this.ensureExternalSaleNumber(this.store.tempOrder);
|
|
1509
|
+
return this.store.tempOrder;
|
|
1510
|
+
}
|
|
1511
|
+
const newOrder = this.createDefaultTempOrderInstance();
|
|
1512
|
+
this.ensureExternalSaleNumber(newOrder);
|
|
1513
|
+
this.store.tempOrder = newOrder;
|
|
1514
|
+
this.persistTempOrder();
|
|
1515
|
+
return newOrder;
|
|
1516
|
+
}
|
|
1517
|
+
restoreOrder() {
|
|
1518
|
+
this.logInfo('restoreOrder start', {});
|
|
1519
|
+
const freshTempOrder = this.createDefaultTempOrderInstance();
|
|
1520
|
+
this.ensureExternalSaleNumber(freshTempOrder);
|
|
1521
|
+
this.store.tempOrder = freshTempOrder;
|
|
1522
|
+
this.store.summary = (0, _utils.createEmptySummary)();
|
|
1523
|
+
this.store.lastOrderInfo = undefined;
|
|
1524
|
+
this.store.syncState = undefined;
|
|
1525
|
+
void this.store.discount?.setOriginalDiscountList([]);
|
|
1526
|
+
void this.store.discount?.setDiscountList([]);
|
|
1527
|
+
this.persistTempOrder();
|
|
1528
|
+
return freshTempOrder;
|
|
1529
|
+
}
|
|
1530
|
+
getTempOrder() {
|
|
1531
|
+
return this.store.tempOrder;
|
|
1532
|
+
}
|
|
1533
|
+
async replaceTempOrder(tempOrder, options) {
|
|
1534
|
+
if (!(0, _utils.isTempOrder)(tempOrder)) {
|
|
1535
|
+
throw new Error('无效的 tempOrder 数据');
|
|
1536
|
+
}
|
|
1537
|
+
const nextTempOrder = this.normalizeTempOrderForRuntime((0, _lodashEs.cloneDeep)(tempOrder), {
|
|
1538
|
+
ensureIdentity: false
|
|
1539
|
+
});
|
|
1540
|
+
this.store.tempOrder = nextTempOrder;
|
|
1541
|
+
if (options?.recalculateSummary !== false) {
|
|
1542
|
+
await this.recalculateSummary({
|
|
1543
|
+
createIfMissing: false
|
|
1544
|
+
});
|
|
1545
|
+
} else {
|
|
1546
|
+
this.store.summary = (0, _utils.createEmptySummary)();
|
|
1547
|
+
}
|
|
1548
|
+
if (options?.persist !== false) {
|
|
1549
|
+
this.persistTempOrder();
|
|
1550
|
+
}
|
|
1551
|
+
if (options?.saveDraft) {
|
|
1552
|
+
await this.saveDraft();
|
|
1553
|
+
}
|
|
1554
|
+
return nextTempOrder;
|
|
1555
|
+
}
|
|
1556
|
+
getOrderIdentity() {
|
|
1557
|
+
const tempOrder = this.store.tempOrder;
|
|
1558
|
+
if (!tempOrder) return null;
|
|
1559
|
+
const lastOrderInfo = this.store.lastOrderInfo || {};
|
|
1560
|
+
const orderId = tempOrder.order_id ?? lastOrderInfo.order_id ?? lastOrderInfo.id ?? null;
|
|
1561
|
+
return {
|
|
1562
|
+
localId: this.cacheId || tempOrder._extend?.localId,
|
|
1563
|
+
orderId,
|
|
1564
|
+
externalSaleNumber: tempOrder.external_sale_number || lastOrderInfo.external_sale_number || '',
|
|
1565
|
+
orderNumber: tempOrder.order_number || lastOrderInfo.order_number || '',
|
|
1566
|
+
shopOrderNumber: tempOrder.shop_order_number || lastOrderInfo.shop_order_number || '',
|
|
1567
|
+
shopFullOrderNumber: tempOrder.shop_full_order_number || lastOrderInfo.shop_full_order_number || '',
|
|
1568
|
+
isSubmitted: orderId !== null && orderId !== undefined
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
getOrderSyncState() {
|
|
1572
|
+
const tempOrder = this.store.tempOrder;
|
|
1573
|
+
if (!tempOrder) return 'local';
|
|
1574
|
+
if (this.store.syncState) return this.store.syncState;
|
|
1575
|
+
if (this.store.lastOrderInfo) return 'remoteLoaded';
|
|
1576
|
+
if (tempOrder.order_id) return 'submitted';
|
|
1577
|
+
return 'local';
|
|
1578
|
+
}
|
|
1579
|
+
getOrderAmountSnapshot() {
|
|
1580
|
+
const tempOrder = this.store.tempOrder;
|
|
1581
|
+
if (!tempOrder) return null;
|
|
1582
|
+
const summary = this.store.summary || (0, _utils.createEmptySummary)();
|
|
1583
|
+
const depositAmount = tempOrder.deposit_amount || summary.deposit_amount || '0.00';
|
|
1584
|
+
const paidPaymentSummary = this.calculatePaidPaymentSummary(tempOrder.payments || []);
|
|
1585
|
+
const amountGap = this.calculateSummaryAmountGap({
|
|
1586
|
+
tempOrder,
|
|
1587
|
+
summary,
|
|
1588
|
+
orderPaidAmount: paidPaymentSummary.gapPaidAmount
|
|
1589
|
+
});
|
|
1590
|
+
return {
|
|
1591
|
+
isDeposit: tempOrder.is_deposit === 1,
|
|
1592
|
+
depositAmount,
|
|
1593
|
+
expectAmount: summary.expect_amount || '0.00',
|
|
1594
|
+
totalAmount: summary.total_amount || '0.00',
|
|
1595
|
+
amountGap,
|
|
1596
|
+
summary
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
getOrderSnapshot() {
|
|
1600
|
+
const tempOrder = this.store.tempOrder;
|
|
1601
|
+
if (!tempOrder) return null;
|
|
1602
|
+
const identity = this.getOrderIdentity();
|
|
1603
|
+
const amount = this.getOrderAmountSnapshot();
|
|
1604
|
+
if (!identity || !amount) return null;
|
|
1605
|
+
return {
|
|
1606
|
+
identity,
|
|
1607
|
+
syncState: this.getOrderSyncState(),
|
|
1608
|
+
amount,
|
|
1609
|
+
status: tempOrder.status || '',
|
|
1610
|
+
paymentStatus: tempOrder.payment_status || '',
|
|
1611
|
+
shippingStatus: tempOrder.shipping_status || '',
|
|
1612
|
+
note: tempOrder.note || '',
|
|
1613
|
+
customer: {
|
|
1614
|
+
customerId: tempOrder.customer_id ?? null,
|
|
1615
|
+
customerName: tempOrder.customer_name || '',
|
|
1616
|
+
phone: tempOrder.phone || '',
|
|
1617
|
+
email: tempOrder.email || ''
|
|
1618
|
+
},
|
|
1619
|
+
productCount: tempOrder.products?.length || 0,
|
|
1620
|
+
lastOrderInfo: this.store.lastOrderInfo,
|
|
1621
|
+
tempOrder
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
async addNewOrder() {
|
|
1625
|
+
const tempOrder = this.ensureTempOrder();
|
|
1626
|
+
this.ensureExternalSaleNumber(tempOrder);
|
|
1627
|
+
await this.recalculateSummary({
|
|
1628
|
+
createIfMissing: true
|
|
1629
|
+
});
|
|
1630
|
+
this.persistTempOrder();
|
|
1631
|
+
if (this.draftPersistEnabled) {
|
|
1632
|
+
await this.saveDraft();
|
|
1633
|
+
}
|
|
1634
|
+
return tempOrder;
|
|
1635
|
+
}
|
|
1636
|
+
getOrderProducts() {
|
|
1637
|
+
const tempOrder = this.ensureTempOrder();
|
|
1638
|
+
return tempOrder.products;
|
|
1639
|
+
}
|
|
1640
|
+
getProductUid(product) {
|
|
1641
|
+
const metadata = product.metadata && typeof product.metadata === 'object' ? product.metadata : {};
|
|
1642
|
+
const uid = metadata.unique_identification_number || product.unique_identification_number || (0, _utils.createUuidV4)();
|
|
1643
|
+
metadata.unique_identification_number = String(uid);
|
|
1644
|
+
product.metadata = metadata;
|
|
1645
|
+
return String(uid);
|
|
1646
|
+
}
|
|
1647
|
+
sanitizeOrderProductForTempOrder(product) {
|
|
1648
|
+
if (product.metadata && typeof product.metadata === 'object') {
|
|
1649
|
+
delete product.metadata.origin;
|
|
1650
|
+
}
|
|
1651
|
+
return product;
|
|
1652
|
+
}
|
|
1653
|
+
sanitizeTempOrderProducts(tempOrder) {
|
|
1654
|
+
tempOrder.products = (tempOrder.products || []).map(product => this.sanitizeOrderProductForTempOrder(product));
|
|
1655
|
+
}
|
|
1656
|
+
ensureExtend(tempOrder) {
|
|
1657
|
+
if (!tempOrder._extend || typeof tempOrder._extend !== 'object') {
|
|
1658
|
+
tempOrder._extend = {
|
|
1659
|
+
productsByUid: {}
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
if (!tempOrder._extend.productsByUid) {
|
|
1663
|
+
tempOrder._extend.productsByUid = {};
|
|
1664
|
+
}
|
|
1665
|
+
return tempOrder._extend;
|
|
1666
|
+
}
|
|
1667
|
+
captureProductRuntime(tempOrder, rawProduct, normalizedProduct, existedUid) {
|
|
1668
|
+
const uid = existedUid || this.getProductUid(normalizedProduct);
|
|
1669
|
+
const extend = this.ensureExtend(tempOrder);
|
|
1670
|
+
const existed = extend.productsByUid?.[uid] || {};
|
|
1671
|
+
const origin = rawProduct._origin ?? normalizedProduct._origin ?? existed.origin;
|
|
1672
|
+
extend.productsByUid[uid] = {
|
|
1673
|
+
...existed,
|
|
1674
|
+
...(origin !== undefined ? {
|
|
1675
|
+
origin
|
|
1676
|
+
} : {})
|
|
1677
|
+
};
|
|
1678
|
+
delete normalizedProduct._origin;
|
|
1679
|
+
delete normalizedProduct.unique_identification_number;
|
|
1680
|
+
}
|
|
1681
|
+
createLinkedProductAndBooking(params) {
|
|
1682
|
+
const productMetadata = params.product.metadata && typeof params.product.metadata === 'object' ? {
|
|
1683
|
+
...params.product.metadata
|
|
1684
|
+
} : {};
|
|
1685
|
+
const bookingMetadata = params.booking.metadata && typeof params.booking.metadata === 'object' ? {
|
|
1686
|
+
...params.booking.metadata
|
|
1687
|
+
} : {};
|
|
1688
|
+
const productUid = String(productMetadata.unique_identification_number || params.product.unique_identification_number || (0, _utils.createUuidV4)());
|
|
1689
|
+
const bookingUid = String(bookingMetadata.unique_identification_number || (0, _utils.createUuidV4)());
|
|
1690
|
+
productMetadata.unique_identification_number = productUid;
|
|
1691
|
+
bookingMetadata.unique_identification_number = bookingUid;
|
|
1692
|
+
this.syncBookingMetadataHolder(params.booking, bookingMetadata);
|
|
1693
|
+
return {
|
|
1694
|
+
productUid,
|
|
1695
|
+
bookingUid,
|
|
1696
|
+
product: {
|
|
1697
|
+
...params.product,
|
|
1698
|
+
unique_identification_number: productUid,
|
|
1699
|
+
metadata: {
|
|
1700
|
+
...productMetadata,
|
|
1701
|
+
booking_uid: bookingUid
|
|
1702
|
+
},
|
|
1703
|
+
booking_uid: bookingUid
|
|
1704
|
+
},
|
|
1705
|
+
booking: {
|
|
1706
|
+
...params.booking,
|
|
1707
|
+
is_all: (0, _utils.normalizeBookingIsAll)(params.booking),
|
|
1708
|
+
product_uid: productUid,
|
|
1709
|
+
sub_type: (0, _utils.normalizeBookingSubType)(params.booking),
|
|
1710
|
+
metadata: bookingMetadata
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
/**
|
|
1716
|
+
* booking.holder 是提交真源;metadata.holder 作为详情/回显冗余字段同步保存。
|
|
1717
|
+
*
|
|
1718
|
+
* @example
|
|
1719
|
+
* this.syncBookingMetadataHolder(booking, metadata);
|
|
1720
|
+
*/
|
|
1721
|
+
syncBookingMetadataHolder(booking, metadata) {
|
|
1722
|
+
if (booking?.holder !== undefined) {
|
|
1723
|
+
metadata.holder = booking.holder;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
/**
|
|
1728
|
+
* 从单条 booking 的 holder / metadata 中收集 holder form_record。
|
|
1729
|
+
*
|
|
1730
|
+
* @example
|
|
1731
|
+
* const ids = this.collectBookingHolderRecords(booking);
|
|
1732
|
+
*/
|
|
1733
|
+
collectBookingHolderRecords(booking) {
|
|
1734
|
+
const holder = booking?.holder && typeof booking.holder === 'object' ? booking.holder : null;
|
|
1735
|
+
const source = holder?.form_record ?? booking?.metadata?.holder_id;
|
|
1736
|
+
const records = Array.isArray(source) ? source : [source];
|
|
1737
|
+
return records.filter(record => record !== undefined && record !== null && record !== '');
|
|
1738
|
+
}
|
|
1739
|
+
setProductHolderFromBooking(product, booking) {
|
|
1740
|
+
if (!product || !booking) return;
|
|
1741
|
+
const holderRecords = this.collectBookingHolderRecords(booking);
|
|
1742
|
+
if (!product.metadata || typeof product.metadata !== 'object') {
|
|
1743
|
+
product.metadata = {};
|
|
1744
|
+
}
|
|
1745
|
+
product.metadata.holder_id = holderRecords.length > 0 ? holderRecords : null;
|
|
1746
|
+
}
|
|
1747
|
+
syncLinkedBookingHoldersToProducts(tempOrder) {
|
|
1748
|
+
const bookings = tempOrder.bookings || [];
|
|
1749
|
+
const products = tempOrder.products || [];
|
|
1750
|
+
if (!bookings.length || !products.length) return;
|
|
1751
|
+
const bookingByProductUid = new Map();
|
|
1752
|
+
const bookingByBookingUid = new Map();
|
|
1753
|
+
bookings.forEach(booking => {
|
|
1754
|
+
if (!booking || typeof booking !== 'object') return;
|
|
1755
|
+
const productUid = booking.product_uid || booking.metadata?.product_uid;
|
|
1756
|
+
if (productUid !== undefined && productUid !== null && String(productUid) !== '') {
|
|
1757
|
+
bookingByProductUid.set(String(productUid), booking);
|
|
1758
|
+
}
|
|
1759
|
+
const bookingUid = booking.metadata?.unique_identification_number || booking.booking_uid;
|
|
1760
|
+
if (bookingUid !== undefined && bookingUid !== null && String(bookingUid) !== '') {
|
|
1761
|
+
bookingByBookingUid.set(String(bookingUid), booking);
|
|
1762
|
+
}
|
|
1763
|
+
});
|
|
1764
|
+
products.forEach(product => {
|
|
1765
|
+
if (!product || typeof product !== 'object') return;
|
|
1766
|
+
const productUid = product.metadata?.unique_identification_number || product.unique_identification_number;
|
|
1767
|
+
const bookingUid = product.booking_uid || product.metadata?.booking_uid;
|
|
1768
|
+
const linkedBooking = (productUid !== undefined && productUid !== null && String(productUid) !== '' ? bookingByProductUid.get(String(productUid)) : undefined) || (bookingUid !== undefined && bookingUid !== null && String(bookingUid) !== '' ? bookingByBookingUid.get(String(bookingUid)) : undefined);
|
|
1769
|
+
if (linkedBooking) {
|
|
1770
|
+
this.setProductHolderFromBooking(product, linkedBooking);
|
|
1771
|
+
}
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
/**
|
|
1776
|
+
* 按 form_record 顺序读取 booking holder 的主字段名称。
|
|
1777
|
+
*
|
|
1778
|
+
* @example
|
|
1779
|
+
* const names = this.collectBookingHolderNames(booking);
|
|
1780
|
+
*/
|
|
1781
|
+
collectBookingHolderNames(booking) {
|
|
1782
|
+
const holder = booking?.holder && typeof booking.holder === 'object' ? booking.holder : null;
|
|
1783
|
+
const rawName = holder?.name;
|
|
1784
|
+
if (Array.isArray(rawName)) {
|
|
1785
|
+
return rawName.map(name => String(name || '').trim());
|
|
1786
|
+
}
|
|
1787
|
+
if (typeof rawName === 'string') {
|
|
1788
|
+
return rawName.split(',').map(name => name.trim());
|
|
1789
|
+
}
|
|
1790
|
+
return [];
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
/**
|
|
1794
|
+
* 按老 ticketBooking `formatHolder` 语义汇总订单顶层 holder。
|
|
1795
|
+
*
|
|
1796
|
+
* @example
|
|
1797
|
+
* const holder = this.buildTempOrderHolderFromBookings(tempOrder);
|
|
1798
|
+
*/
|
|
1799
|
+
buildTempOrderHolderFromBookings(tempOrder) {
|
|
1800
|
+
const holderRecords = [];
|
|
1801
|
+
const holderNames = [];
|
|
1802
|
+
let baseHolder = null;
|
|
1803
|
+
const seen = new Set();
|
|
1804
|
+
for (const booking of tempOrder.bookings || []) {
|
|
1805
|
+
const holder = booking?.holder && typeof booking.holder === 'object' ? booking.holder : null;
|
|
1806
|
+
if (!baseHolder && holder) {
|
|
1807
|
+
baseHolder = holder;
|
|
1808
|
+
}
|
|
1809
|
+
const records = this.collectBookingHolderRecords(booking);
|
|
1810
|
+
const names = this.collectBookingHolderNames(booking);
|
|
1811
|
+
for (const [index, record] of records.entries()) {
|
|
1812
|
+
const key = String(record);
|
|
1813
|
+
if (seen.has(key)) continue;
|
|
1814
|
+
seen.add(key);
|
|
1815
|
+
holderRecords.push(record);
|
|
1816
|
+
if (names[index]) {
|
|
1817
|
+
holderNames.push(names[index]);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
if (!holderRecords.length) {
|
|
1822
|
+
return null;
|
|
1823
|
+
}
|
|
1824
|
+
const nextHolder = {
|
|
1825
|
+
customer_id: baseHolder?.customer_id ?? tempOrder.customer_id,
|
|
1826
|
+
form_id: baseHolder?.form_id ?? tempOrder.holder?.form_id ?? 0,
|
|
1827
|
+
form_record: holderRecords
|
|
1828
|
+
};
|
|
1829
|
+
if (holderNames.length) {
|
|
1830
|
+
nextHolder.name = holderNames.join(',');
|
|
1831
|
+
}
|
|
1832
|
+
if (baseHolder?.is_source_shop || this.otherParams?.isFranchisee) {
|
|
1833
|
+
nextHolder.is_source_shop = 1;
|
|
1834
|
+
}
|
|
1835
|
+
return nextHolder;
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
/**
|
|
1839
|
+
* 将 booking 级 holder 汇总到 tempOrder 顶层 holder。
|
|
1840
|
+
*
|
|
1841
|
+
* @example
|
|
1842
|
+
* this.syncTempOrderHolderFromBookings(tempOrder);
|
|
1843
|
+
*/
|
|
1844
|
+
syncTempOrderHolderFromBookings(tempOrder) {
|
|
1845
|
+
const bookings = tempOrder.bookings || [];
|
|
1846
|
+
const hasHolderSource = bookings.some(booking => {
|
|
1847
|
+
return booking?.holder !== undefined || booking?.metadata?.holder_id !== undefined;
|
|
1848
|
+
});
|
|
1849
|
+
const nextHolder = this.buildTempOrderHolderFromBookings(tempOrder);
|
|
1850
|
+
if (nextHolder || hasHolderSource || bookings.length === 0) {
|
|
1851
|
+
tempOrder.holder = nextHolder;
|
|
1852
|
+
}
|
|
1853
|
+
this.syncLinkedBookingHoldersToProducts(tempOrder);
|
|
1854
|
+
}
|
|
1855
|
+
getBookingUniqueIdentificationNumber(booking) {
|
|
1856
|
+
const uid = booking?.metadata?.unique_identification_number;
|
|
1857
|
+
if (uid === undefined || uid === null || uid === '') return null;
|
|
1858
|
+
return String(uid);
|
|
1859
|
+
}
|
|
1860
|
+
findBookingIndexByUniqueIdentificationNumber(tempOrder, uniqueIdentificationNumber) {
|
|
1861
|
+
return (tempOrder.bookings || []).findIndex(booking => {
|
|
1862
|
+
return this.getBookingUniqueIdentificationNumber(booking) === uniqueIdentificationNumber;
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
removeLinkedBookingsForProduct(tempOrder, product) {
|
|
1866
|
+
const productUid = product?.metadata?.unique_identification_number || product?.unique_identification_number;
|
|
1867
|
+
const bookingUid = product?.booking_uid || product?.metadata?.booking_uid;
|
|
1868
|
+
if (!productUid && !bookingUid) return;
|
|
1869
|
+
tempOrder.bookings = (tempOrder.bookings || []).filter(booking => {
|
|
1870
|
+
const currentBookingUid = this.getBookingUniqueIdentificationNumber(booking);
|
|
1871
|
+
if (bookingUid && currentBookingUid === String(bookingUid)) return false;
|
|
1872
|
+
if (productUid && String(booking?.product_uid || '') === String(productUid)) return false;
|
|
1873
|
+
return true;
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
// ─── TempOrder: 金额汇总 ───
|
|
1878
|
+
|
|
1879
|
+
getSalesSummary() {
|
|
1880
|
+
if (!this.salesSummaryModuleName) return null;
|
|
1881
|
+
return this.core.getModule(this.salesSummaryModuleName);
|
|
1882
|
+
}
|
|
1883
|
+
applyOrderProductsDepositSummary(tempOrder, summary) {
|
|
1884
|
+
const depositSummary = (0, _utils.calculateOrderProductsDeposit)(tempOrder.products || [], {
|
|
1885
|
+
productsByUid: tempOrder._extend?.productsByUid
|
|
1886
|
+
});
|
|
1887
|
+
const manualDepositOverride = this.getManualDepositOverride(tempOrder);
|
|
1888
|
+
const currentStructuralFingerprint = manualDepositOverride ? this.buildOrderProductsStructuralFingerprint(tempOrder.products) : '';
|
|
1889
|
+
const shouldUseManualDepositOverride = Boolean(manualDepositOverride && manualDepositOverride.productStructuralFingerprint === currentStructuralFingerprint);
|
|
1890
|
+
if (manualDepositOverride && !shouldUseManualDepositOverride) {
|
|
1891
|
+
this.clearManualDepositOverride(tempOrder);
|
|
1892
|
+
}
|
|
1893
|
+
const depositAmount = shouldUseManualDepositOverride ? manualDepositOverride.amount : depositSummary.totalText;
|
|
1894
|
+
const hasDepositAmount = new _decimal.default(depositAmount).gt(0);
|
|
1895
|
+
const nextSummary = {
|
|
1896
|
+
...summary,
|
|
1897
|
+
deposit_amount: depositAmount,
|
|
1898
|
+
deposit: hasDepositAmount ? {
|
|
1899
|
+
total: depositAmount,
|
|
1900
|
+
deposit_policy_ids: depositSummary.deposit_policy_ids,
|
|
1901
|
+
deposit_policy_data: depositSummary.protocols,
|
|
1902
|
+
hasDeposit: true
|
|
1903
|
+
} : undefined
|
|
1904
|
+
};
|
|
1905
|
+
const isExistingDepositDisabled = tempOrder.is_deposit === 0 && new _decimal.default(tempOrder.deposit_amount || 0).gt(0);
|
|
1906
|
+
tempOrder.deposit_amount = depositAmount;
|
|
1907
|
+
tempOrder.is_deposit = hasDepositAmount && !isExistingDepositDisabled ? 1 : 0;
|
|
1908
|
+
return this.applyLastOrderDepositSnapshot(tempOrder, nextSummary);
|
|
1909
|
+
}
|
|
1910
|
+
async recalculateSummary(options) {
|
|
1911
|
+
const tempOrder = options?.createIfMissing ? this.ensureTempOrder() : this.store.tempOrder;
|
|
1912
|
+
if (!tempOrder) return null;
|
|
1913
|
+
this.sanitizeTempOrderProducts(tempOrder);
|
|
1914
|
+
const originalSnapshot = this.getOriginalSalesSnapshot(tempOrder);
|
|
1915
|
+
const hasShopDiscountChanged = originalSnapshot ? this.hasShopDiscountChangedFromSnapshot(tempOrder, originalSnapshot) : false;
|
|
1916
|
+
const shouldReuseOriginalSnapshot = originalSnapshot && !hasShopDiscountChanged;
|
|
1917
|
+
const snapshotSummary = shouldReuseOriginalSnapshot ? this.restoreOriginalSnapshotIfProductsUnchanged(tempOrder, originalSnapshot) : null;
|
|
1918
|
+
if (snapshotSummary) return snapshotSummary;
|
|
1919
|
+
const shouldFullRecalculateFromSnapshot = Boolean(originalSnapshot);
|
|
1920
|
+
const summaryProducts = shouldFullRecalculateFromSnapshot ? (0, _lodashEs.cloneDeep)(tempOrder.products || []) : tempOrder.products;
|
|
1921
|
+
if (shouldFullRecalculateFromSnapshot) {
|
|
1922
|
+
this.clearPersistedAmountFieldsForFullRecalc(summaryProducts || []);
|
|
1923
|
+
}
|
|
1924
|
+
const salesSummary = this.getSalesSummary();
|
|
1925
|
+
const existedSummary = this.store.summary || (0, _utils.createEmptySummary)();
|
|
1926
|
+
const totalRefundAmount = existedSummary.total_refund_amount || '0.00';
|
|
1927
|
+
const paidPaymentSummary = this.calculatePaidPaymentSummary(tempOrder.payments || []);
|
|
1928
|
+
if (!salesSummary) {
|
|
1929
|
+
const emptyBaseSummary = {
|
|
1930
|
+
...(0, _utils.createEmptySummary)(),
|
|
1931
|
+
...existedSummary,
|
|
1932
|
+
total_refund_amount: totalRefundAmount
|
|
1933
|
+
};
|
|
1934
|
+
const emptySummaryBase = {
|
|
1935
|
+
...emptyBaseSummary,
|
|
1936
|
+
customer_paid_amount: paidPaymentSummary.customerPaidAmount.toFixed(2),
|
|
1937
|
+
order_paid_amount: paidPaymentSummary.orderPaidAmount.toFixed(2),
|
|
1938
|
+
pay_service_charge_amount: paidPaymentSummary.payServiceChargeAmount.toFixed(2),
|
|
1939
|
+
rounding_amount: paidPaymentSummary.roundingAmount.toFixed(2)
|
|
1940
|
+
};
|
|
1941
|
+
const emptySummaryWithDeposit = this.applyOrderProductsDepositSummary(tempOrder, emptySummaryBase);
|
|
1942
|
+
const emptySummary = {
|
|
1943
|
+
...emptySummaryWithDeposit,
|
|
1944
|
+
amount_gap: this.calculateSummaryAmountGap({
|
|
1945
|
+
tempOrder,
|
|
1946
|
+
summary: emptySummaryWithDeposit,
|
|
1947
|
+
orderPaidAmount: paidPaymentSummary.gapPaidAmount
|
|
1948
|
+
})
|
|
1949
|
+
};
|
|
1950
|
+
tempOrder.surcharge_fee = emptySummary.surcharge_fee;
|
|
1951
|
+
this.store.summary = emptySummary;
|
|
1952
|
+
this.updateTempOrderPaymentStatus(tempOrder, {
|
|
1953
|
+
previousSummary: existedSummary,
|
|
1954
|
+
summary: emptySummary
|
|
1955
|
+
});
|
|
1956
|
+
return this.store.summary;
|
|
1957
|
+
}
|
|
1958
|
+
const salesSummaryResult = await salesSummary.getSummary({
|
|
1959
|
+
products: summaryProducts,
|
|
1960
|
+
isPriceIncludeTax: tempOrder.is_price_include_tax,
|
|
1961
|
+
shopDiscount: tempOrder.shop_discount,
|
|
1962
|
+
...(this.otherParams?.channel ? {
|
|
1963
|
+
channel: this.otherParams.channel
|
|
1964
|
+
} : {})
|
|
1965
|
+
});
|
|
1966
|
+
if (shouldFullRecalculateFromSnapshot) {
|
|
1967
|
+
tempOrder.products = summaryProducts;
|
|
1968
|
+
}
|
|
1969
|
+
this.syncSummaryProductMetadata(tempOrder.products || []);
|
|
1970
|
+
const roundingAmount = paidPaymentSummary.roundingAmount;
|
|
1971
|
+
const payServiceChargeAmount = paidPaymentSummary.payServiceChargeAmount;
|
|
1972
|
+
const hasSalesSummaryAmount = salesSummaryResult.expect_amount !== undefined || salesSummaryResult.total_amount !== undefined;
|
|
1973
|
+
const expectAmount = hasSalesSummaryAmount ? new _decimal.default(salesSummaryResult.expect_amount ?? salesSummaryResult.total_amount ?? 0) : null;
|
|
1974
|
+
const amountPaymentPatch = hasSalesSummaryAmount || !roundingAmount.eq(0) || !payServiceChargeAmount.eq(0) ? {
|
|
1975
|
+
rounding_amount: roundingAmount.toFixed(2),
|
|
1976
|
+
...(expectAmount ? {
|
|
1977
|
+
total_amount: _decimal.default.max(0, expectAmount.plus(roundingAmount).plus(payServiceChargeAmount)).toFixed(2)
|
|
1978
|
+
} : {})
|
|
1979
|
+
} : {};
|
|
1980
|
+
const normalizedExpectAmountPatch = expectAmount && salesSummaryResult.expect_amount === undefined ? {
|
|
1981
|
+
expect_amount: expectAmount.toFixed(2)
|
|
1982
|
+
} : {};
|
|
1983
|
+
const summaryForGap = this.applyOrderProductsDepositSummary(tempOrder, {
|
|
1984
|
+
...salesSummaryResult,
|
|
1985
|
+
total_refund_amount: totalRefundAmount,
|
|
1986
|
+
...amountPaymentPatch,
|
|
1987
|
+
...normalizedExpectAmountPatch
|
|
1988
|
+
});
|
|
1989
|
+
const summary = {
|
|
1990
|
+
...summaryForGap,
|
|
1991
|
+
total_refund_amount: totalRefundAmount,
|
|
1992
|
+
customer_paid_amount: paidPaymentSummary.customerPaidAmount.toFixed(2),
|
|
1993
|
+
order_paid_amount: paidPaymentSummary.orderPaidAmount.toFixed(2),
|
|
1994
|
+
...amountPaymentPatch,
|
|
1995
|
+
...normalizedExpectAmountPatch,
|
|
1996
|
+
amount_gap: this.calculateSummaryAmountGap({
|
|
1997
|
+
tempOrder,
|
|
1998
|
+
summary: summaryForGap,
|
|
1999
|
+
orderPaidAmount: paidPaymentSummary.gapPaidAmount
|
|
2000
|
+
}),
|
|
2001
|
+
pay_service_charge_amount: payServiceChargeAmount.toFixed(2)
|
|
2002
|
+
};
|
|
2003
|
+
this.store.summary = summary;
|
|
2004
|
+
if (summary.is_price_include_tax !== undefined) {
|
|
2005
|
+
tempOrder.is_price_include_tax = summary.is_price_include_tax;
|
|
2006
|
+
}
|
|
2007
|
+
if (typeof summary.tax_title === 'string' && summary.tax_title) {
|
|
2008
|
+
tempOrder.tax_title = summary.tax_title;
|
|
2009
|
+
}
|
|
2010
|
+
tempOrder.surcharge_fee = summary.surcharge_fee;
|
|
2011
|
+
tempOrder.surcharges = summary.surcharges || [];
|
|
2012
|
+
this.updateTempOrderPaymentStatus(tempOrder, {
|
|
2013
|
+
previousSummary: existedSummary,
|
|
2014
|
+
summary
|
|
2015
|
+
});
|
|
2016
|
+
return this.store.summary;
|
|
2017
|
+
}
|
|
2018
|
+
async getOrderSummary() {
|
|
2019
|
+
const summary = await this.recalculateSummary({
|
|
2020
|
+
createIfMissing: true
|
|
2021
|
+
});
|
|
2022
|
+
if (!summary) return (0, _utils.createEmptySummary)();
|
|
2023
|
+
this.persistTempOrder();
|
|
2024
|
+
return summary;
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
// ─── TempOrder: 备注 ───
|
|
2028
|
+
|
|
2029
|
+
updateTempOrderNote(note, sync = false) {
|
|
2030
|
+
const tempOrder = this.ensureTempOrder();
|
|
2031
|
+
tempOrder.note = String(note || '');
|
|
2032
|
+
this.persistTempOrder();
|
|
2033
|
+
if (sync) {
|
|
2034
|
+
const orderId = tempOrder.order_id;
|
|
2035
|
+
if (!orderId) return tempOrder.note;
|
|
2036
|
+
return this.syncTempOrderNoteToRemote(orderId, tempOrder.note);
|
|
2037
|
+
}
|
|
2038
|
+
return tempOrder.note;
|
|
2039
|
+
}
|
|
2040
|
+
async syncTempOrderNoteToRemote(orderId, note) {
|
|
2041
|
+
await this.request.put(`/order/order/${orderId}/note`, {
|
|
2042
|
+
note
|
|
2043
|
+
});
|
|
2044
|
+
return note;
|
|
2045
|
+
}
|
|
2046
|
+
getTempOrderNote() {
|
|
2047
|
+
return this.store.tempOrder?.note || '';
|
|
2048
|
+
}
|
|
2049
|
+
updateTempOrderShopDiscount(amount) {
|
|
2050
|
+
const tempOrder = this.ensureTempOrder();
|
|
2051
|
+
const discount = _decimal.default.max(new _decimal.default(Number(amount) || 0), 0).toDecimalPlaces(2).toFixed(2);
|
|
2052
|
+
tempOrder.shop_discount = discount;
|
|
2053
|
+
this.persistTempOrder();
|
|
2054
|
+
return tempOrder.shop_discount;
|
|
2055
|
+
}
|
|
2056
|
+
updateTempOrderDepositAmount(amount) {
|
|
2057
|
+
const tempOrder = this.ensureTempOrder();
|
|
2058
|
+
const depositAmount = this.normalizeDepositAmount(amount);
|
|
2059
|
+
const previousSummary = this.store.summary || (0, _utils.createEmptySummary)();
|
|
2060
|
+
const summaryWithDeposit = this.applyDepositAmountToSummary(tempOrder, {
|
|
2061
|
+
...(0, _utils.createEmptySummary)(),
|
|
2062
|
+
...previousSummary
|
|
2063
|
+
}, depositAmount);
|
|
2064
|
+
this.setManualDepositOverride(tempOrder, depositAmount);
|
|
2065
|
+
const paidPaymentSummary = this.calculatePaidPaymentSummary(tempOrder.payments || []);
|
|
2066
|
+
const summary = {
|
|
2067
|
+
...summaryWithDeposit,
|
|
2068
|
+
amount_gap: this.calculateSummaryAmountGap({
|
|
2069
|
+
tempOrder,
|
|
2070
|
+
summary: summaryWithDeposit,
|
|
2071
|
+
orderPaidAmount: paidPaymentSummary.gapPaidAmount
|
|
2072
|
+
})
|
|
2073
|
+
};
|
|
2074
|
+
this.store.summary = summary;
|
|
2075
|
+
this.updateTempOrderPaymentStatus(tempOrder, {
|
|
2076
|
+
previousSummary,
|
|
2077
|
+
summary
|
|
2078
|
+
});
|
|
2079
|
+
this.persistTempOrder();
|
|
2080
|
+
return summary;
|
|
2081
|
+
}
|
|
2082
|
+
updateTempOrderBuzzer(buzzer) {
|
|
2083
|
+
const tempOrder = this.ensureTempOrder();
|
|
2084
|
+
tempOrder.buzzer = String(buzzer || '');
|
|
2085
|
+
this.persistTempOrder();
|
|
2086
|
+
return tempOrder.buzzer;
|
|
2087
|
+
}
|
|
2088
|
+
updateTempOrderContactsInfo(contactsInfo) {
|
|
2089
|
+
const tempOrder = this.ensureTempOrder();
|
|
2090
|
+
tempOrder.contacts_info = contactsInfo && typeof contactsInfo === 'object' && !Array.isArray(contactsInfo) ? contactsInfo : null;
|
|
2091
|
+
this.persistTempOrder();
|
|
2092
|
+
return tempOrder.contacts_info;
|
|
2093
|
+
}
|
|
2094
|
+
buildTempOrderCustomer(params) {
|
|
2095
|
+
if (!params.customerId) return null;
|
|
2096
|
+
const source = params.source ? (0, _lodashEs.cloneDeep)(params.source) : {};
|
|
2097
|
+
const name = source.name ?? source.display_name ?? source.customerName ?? source.customer_name ?? params.customerName;
|
|
2098
|
+
const customerName = source.customer_name ?? source.customerName ?? source.display_name ?? source.name ?? params.customerName;
|
|
2099
|
+
return {
|
|
2100
|
+
...source,
|
|
2101
|
+
id: source.id ?? params.customerId,
|
|
2102
|
+
customer_id: source.customer_id ?? params.customerId,
|
|
2103
|
+
name: String(name || ''),
|
|
2104
|
+
customer_name: String(customerName || ''),
|
|
2105
|
+
country_calling_code: String(source.country_calling_code ?? source.countryCallingCode ?? params.countryCallingCode),
|
|
2106
|
+
phone: String(source.phone ?? params.phone),
|
|
2107
|
+
email: String(source.email ?? params.email)
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
isCustomerBoundDiscount(discount) {
|
|
2111
|
+
const type = discount?.type ?? discount?.tag;
|
|
2112
|
+
return type === 'good_pass' || type === 'discount_card' || type === 'product_discount_card';
|
|
2113
|
+
}
|
|
2114
|
+
shouldKeepCustomerBoundDiscountAfterCustomerChange(discount) {
|
|
2115
|
+
return Boolean(discount?.isScan || discount?.isEditMode);
|
|
2116
|
+
}
|
|
2117
|
+
productHasCustomerBoundDiscount(product) {
|
|
2118
|
+
if ((product?.discount_list || []).some(discount => this.isCustomerBoundDiscount(discount))) {
|
|
2119
|
+
return true;
|
|
2120
|
+
}
|
|
2121
|
+
return (product?.product_bundle || []).some(bundle => (bundle?.discount_list || []).some(discount => this.isCustomerBoundDiscount(discount)));
|
|
2122
|
+
}
|
|
2123
|
+
shouldSkipDiscountCalculation(tempOrder) {
|
|
2124
|
+
if (this.hasActiveCustomerBoundDiscount) return false;
|
|
2125
|
+
const discountList = this.store.discount?.getDiscountList?.() || [];
|
|
2126
|
+
if (discountList.length > 0) return false;
|
|
2127
|
+
return !(tempOrder.products || []).some(product => this.productHasCustomerBoundDiscount(product));
|
|
2128
|
+
}
|
|
2129
|
+
clearCustomerBoundDiscounts(tempOrder) {
|
|
2130
|
+
let didRemoveCustomerBoundDiscount = false;
|
|
2131
|
+
const filterDiscountList = discountList => {
|
|
2132
|
+
if (!Array.isArray(discountList)) return [];
|
|
2133
|
+
return discountList.filter(discount => {
|
|
2134
|
+
const shouldRemove = this.isCustomerBoundDiscount(discount);
|
|
2135
|
+
if (shouldRemove) didRemoveCustomerBoundDiscount = true;
|
|
2136
|
+
return !shouldRemove;
|
|
2137
|
+
});
|
|
2138
|
+
};
|
|
2139
|
+
tempOrder.products = (tempOrder.products || []).map(product => {
|
|
2140
|
+
const nextProduct = {
|
|
2141
|
+
...product,
|
|
2142
|
+
discount_list: filterDiscountList(product.discount_list)
|
|
2143
|
+
};
|
|
2144
|
+
if (Array.isArray(nextProduct.product_bundle)) {
|
|
2145
|
+
nextProduct.product_bundle = nextProduct.product_bundle.map(bundle => ({
|
|
2146
|
+
...bundle,
|
|
2147
|
+
discount_list: filterDiscountList(bundle.discount_list)
|
|
2148
|
+
}));
|
|
2149
|
+
}
|
|
2150
|
+
return nextProduct;
|
|
2151
|
+
});
|
|
2152
|
+
delete tempOrder.discount_list;
|
|
2153
|
+
if (didRemoveCustomerBoundDiscount) {
|
|
2154
|
+
this.hasActiveCustomerBoundDiscount = true;
|
|
2155
|
+
}
|
|
2156
|
+
const discountModule = this.store.discount;
|
|
2157
|
+
if (!discountModule) return;
|
|
2158
|
+
const currentDiscounts = discountModule.getDiscountList?.() || [];
|
|
2159
|
+
const currentOriginalDiscounts = discountModule.getOriginalDiscountList?.() || [];
|
|
2160
|
+
const nextDiscounts = currentDiscounts.filter(discount => {
|
|
2161
|
+
if (this.shouldKeepCustomerBoundDiscountAfterCustomerChange(discount)) return true;
|
|
2162
|
+
return !this.isCustomerBoundDiscount(discount);
|
|
2163
|
+
});
|
|
2164
|
+
const nextOriginalDiscounts = currentOriginalDiscounts.filter(discount => {
|
|
2165
|
+
if (this.shouldKeepCustomerBoundDiscountAfterCustomerChange(discount)) return true;
|
|
2166
|
+
return !this.isCustomerBoundDiscount(discount);
|
|
2167
|
+
});
|
|
2168
|
+
void discountModule.setDiscountList?.(nextDiscounts);
|
|
2169
|
+
void discountModule.setOriginalDiscountList?.(nextOriginalDiscounts);
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
/**
|
|
2173
|
+
* 更新当前 tempOrder 的客户协议字段。
|
|
2174
|
+
*
|
|
2175
|
+
* PaymentModal 只需要修改订单里的客户事实;如果外部还有客户模块需要同步,
|
|
2176
|
+
* 应由调用方通过回调处理,不能把外部 UI 状态写进 tempOrder。
|
|
2177
|
+
*/
|
|
2178
|
+
updateTempOrderCustomer(customer) {
|
|
2179
|
+
const tempOrder = this.ensureTempOrder();
|
|
2180
|
+
const previousCustomerId = tempOrder.customer_id ?? null;
|
|
2181
|
+
const customerId = customer.customer_id ?? customer.customerId ?? customer.id ?? null;
|
|
2182
|
+
const customerName = customer.customer_name ?? customer.customerName ?? customer.display_name ?? customer.name ?? '';
|
|
2183
|
+
tempOrder.customer_id = customerId === null || customerId === undefined || customerId === '' ? null : Number(customerId);
|
|
2184
|
+
tempOrder.customer_name = String(customerName || '');
|
|
2185
|
+
tempOrder.country_calling_code = String(customer.country_calling_code ?? customer.countryCallingCode ?? '');
|
|
2186
|
+
tempOrder.phone = String(customer.phone || '');
|
|
2187
|
+
tempOrder.email = String(customer.email || '');
|
|
2188
|
+
tempOrder.customer = this.buildTempOrderCustomer({
|
|
2189
|
+
source: customer,
|
|
2190
|
+
customerId: tempOrder.customer_id,
|
|
2191
|
+
customerName: tempOrder.customer_name,
|
|
2192
|
+
countryCallingCode: tempOrder.country_calling_code,
|
|
2193
|
+
phone: tempOrder.phone,
|
|
2194
|
+
email: tempOrder.email
|
|
2195
|
+
});
|
|
2196
|
+
const nextCustomerId = tempOrder.customer_id ?? null;
|
|
2197
|
+
this.persistTempOrder();
|
|
2198
|
+
return {
|
|
2199
|
+
customerId: tempOrder.customer_id,
|
|
2200
|
+
customerName: tempOrder.customer_name,
|
|
2201
|
+
phone: tempOrder.phone,
|
|
2202
|
+
email: tempOrder.email
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
setOrderCustomer(patch, snapshot) {
|
|
2206
|
+
if (!patch) {
|
|
2207
|
+
this.clearOrderCustomer();
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
const tempOrder = this.ensureTempOrder();
|
|
2211
|
+
const previousCustomerId = tempOrder.customer_id ?? null;
|
|
2212
|
+
tempOrder.customer_id = patch.customer_id || patch.id || null;
|
|
2213
|
+
tempOrder.customer_name = String(patch.customer_name || '');
|
|
2214
|
+
tempOrder.country_calling_code = String(patch.country_calling_code || '');
|
|
2215
|
+
tempOrder.phone = String(patch.phone || '');
|
|
2216
|
+
tempOrder.email = String(patch.email || '');
|
|
2217
|
+
tempOrder.customer = this.buildTempOrderCustomer({
|
|
2218
|
+
source: snapshot || null,
|
|
2219
|
+
customerId: tempOrder.customer_id,
|
|
2220
|
+
customerName: tempOrder.customer_name,
|
|
2221
|
+
countryCallingCode: tempOrder.country_calling_code,
|
|
2222
|
+
phone: tempOrder.phone,
|
|
2223
|
+
email: tempOrder.email
|
|
2224
|
+
});
|
|
2225
|
+
if (snapshot !== undefined) {
|
|
2226
|
+
const extend = this.ensureExtend(tempOrder);
|
|
2227
|
+
if (snapshot === null) delete extend.customerSnapshot;else extend.customerSnapshot = (0, _lodashEs.cloneDeep)(snapshot);
|
|
2228
|
+
}
|
|
2229
|
+
const nextCustomerId = tempOrder.customer_id ?? null;
|
|
2230
|
+
if (String(previousCustomerId ?? '') !== String(nextCustomerId ?? '')) {
|
|
2231
|
+
this.store.availableWalletIds = [];
|
|
2232
|
+
(0, _utils.clearTempOrderHolderAssignments)(tempOrder);
|
|
2233
|
+
this.clearCustomerBoundDiscounts(tempOrder);
|
|
2234
|
+
this.applyDiscount();
|
|
2235
|
+
}
|
|
2236
|
+
this.persistTempOrder();
|
|
2237
|
+
this.saveDraftInBackground();
|
|
2238
|
+
if (String(previousCustomerId ?? '') !== String(nextCustomerId ?? '')) {
|
|
2239
|
+
this.emitOrderCustomerChange();
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
getOrderCustomer() {
|
|
2243
|
+
const tempOrder = this.store.tempOrder;
|
|
2244
|
+
if (!tempOrder || !tempOrder.customer_id) {
|
|
2245
|
+
return null;
|
|
2246
|
+
}
|
|
2247
|
+
return {
|
|
2248
|
+
customer_id: tempOrder.customer_id,
|
|
2249
|
+
customer_name: tempOrder.customer_name || '',
|
|
2250
|
+
country_calling_code: tempOrder.country_calling_code || '',
|
|
2251
|
+
phone: tempOrder.phone || '',
|
|
2252
|
+
email: tempOrder.email || ''
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
getOrderCustomerSnapshot() {
|
|
2256
|
+
const snapshot = this.store.tempOrder?._extend?.customerSnapshot;
|
|
2257
|
+
return snapshot ? (0, _lodashEs.cloneDeep)(snapshot) : null;
|
|
2258
|
+
}
|
|
2259
|
+
clearOrderCustomer() {
|
|
2260
|
+
const tempOrder = this.ensureTempOrder();
|
|
2261
|
+
(0, _utils.clearTempOrderHolderAssignments)(tempOrder);
|
|
2262
|
+
tempOrder.customer_id = null;
|
|
2263
|
+
tempOrder.customer_name = '';
|
|
2264
|
+
tempOrder.country_calling_code = '';
|
|
2265
|
+
tempOrder.phone = '';
|
|
2266
|
+
tempOrder.email = '';
|
|
2267
|
+
tempOrder.customer = null;
|
|
2268
|
+
if (tempOrder._extend) delete tempOrder._extend.customerSnapshot;
|
|
2269
|
+
this.store.availableWalletIds = [];
|
|
2270
|
+
this.clearCustomerBoundDiscounts(tempOrder);
|
|
2271
|
+
this.persistTempOrder();
|
|
2272
|
+
this.saveDraftInBackground();
|
|
2273
|
+
this.core.effects.emit(_types.OrderHooks.OnOrderCustomerChange, null);
|
|
2274
|
+
}
|
|
2275
|
+
emitOrderCustomerChange() {
|
|
2276
|
+
const patch = this.getOrderCustomer();
|
|
2277
|
+
if (!patch) {
|
|
2278
|
+
this.core.effects.emit(_types.OrderHooks.OnOrderCustomerChange, null);
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
const payload = {
|
|
2282
|
+
patch,
|
|
2283
|
+
snapshot: this.getOrderCustomerSnapshot()
|
|
2284
|
+
};
|
|
2285
|
+
this.core.effects.emit(_types.OrderHooks.OnOrderCustomerChange, payload);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
/**
|
|
2289
|
+
* 读取当前订单的 Sales 协议支付项。
|
|
2290
|
+
*
|
|
2291
|
+
* 这里返回的是 tempOrder.payments,而不是旧 PaymentModule 的本地
|
|
2292
|
+
* payment order / tender session。Sales checkout 以该数组为提交事实来源。
|
|
2293
|
+
*/
|
|
2294
|
+
getOrderPayments() {
|
|
2295
|
+
const tempOrder = this.ensureTempOrder();
|
|
2296
|
+
return tempOrder.payments || [];
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
/**
|
|
2300
|
+
* 用一组支付来源覆盖当前订单支付项。
|
|
2301
|
+
*
|
|
2302
|
+
* 入参允许来自 PaymentModule 的 PaymentItem,内部统一映射为 Sales 协议:
|
|
2303
|
+
* uuid -> metadata.unique_payment_number,id -> custom_payment_id,
|
|
2304
|
+
* isSynced 等运行态字段不会进入 tempOrder.payments。
|
|
2305
|
+
*/
|
|
2306
|
+
setOrderPayments(payments) {
|
|
2307
|
+
const tempOrder = this.ensureTempOrder();
|
|
2308
|
+
tempOrder.payments = (0, _utils.mapPaymentItemsToOrderPayments)(payments);
|
|
2309
|
+
this.persistTempOrder();
|
|
2310
|
+
return tempOrder.payments;
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
/** 追加单个支付项,并立即持久化当前 tempOrder。 */
|
|
2314
|
+
addOrderPayment(payment) {
|
|
2315
|
+
this.logInfo('addOrderPayment', {
|
|
2316
|
+
payment
|
|
2317
|
+
});
|
|
2318
|
+
const tempOrder = this.ensureTempOrder();
|
|
2319
|
+
const orderPaymentType = this.resolveNextOrderPaymentType(tempOrder);
|
|
2320
|
+
const mapped = (0, _utils.mapPaymentItemsToOrderPayments)([this.normalizePaymentSource({
|
|
2321
|
+
...payment,
|
|
2322
|
+
type: orderPaymentType,
|
|
2323
|
+
order_payment_type: orderPaymentType
|
|
2324
|
+
}, {
|
|
2325
|
+
defaultPaid: true
|
|
2326
|
+
})]);
|
|
2327
|
+
tempOrder.payments = [...(tempOrder.payments || []), ...mapped];
|
|
2328
|
+
this.persistTempOrder();
|
|
2329
|
+
void this.recalculateSummary({
|
|
2330
|
+
createIfMissing: true
|
|
2331
|
+
}).catch(error => {
|
|
2332
|
+
this.logError('recalculate summary after addOrderPayment failed', {
|
|
2333
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2334
|
+
});
|
|
2335
|
+
});
|
|
2336
|
+
return tempOrder.payments;
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
/**
|
|
2340
|
+
* 按支付唯一号、后端支付 ID、自定义支付方式 ID 或旧 uuid 更新支付项。
|
|
2341
|
+
*
|
|
2342
|
+
* 该方法服务 PaymentModal 的 void / 编辑场景;更新 metadata 时会做浅合并,
|
|
2343
|
+
* 避免覆盖 unique_payment_number、现金实付/找零等已有元数据。
|
|
2344
|
+
*/
|
|
2345
|
+
updateOrderPayment(paymentIdentity, updates) {
|
|
2346
|
+
const tempOrder = this.ensureTempOrder();
|
|
2347
|
+
const identity = String(paymentIdentity);
|
|
2348
|
+
tempOrder.payments = (tempOrder.payments || []).map(payment => {
|
|
2349
|
+
const uniquePaymentNumber = payment?.metadata?.unique_payment_number;
|
|
2350
|
+
const orderPaymentId = payment?.order_payment_id;
|
|
2351
|
+
const customPaymentId = payment?.custom_payment_id;
|
|
2352
|
+
const uuid = payment?.uuid;
|
|
2353
|
+
const matched = [uniquePaymentNumber, orderPaymentId, customPaymentId, uuid].some(value => value !== undefined && String(value) === identity);
|
|
2354
|
+
if (!matched) return payment;
|
|
2355
|
+
return {
|
|
2356
|
+
...payment,
|
|
2357
|
+
...updates,
|
|
2358
|
+
metadata: {
|
|
2359
|
+
...(payment.metadata || {}),
|
|
2360
|
+
...(updates.metadata || {})
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
2363
|
+
});
|
|
2364
|
+
this.persistTempOrder();
|
|
2365
|
+
return tempOrder.payments;
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
/** 从当前订单支付项中移除指定支付项。 */
|
|
2369
|
+
deleteOrderPayment(paymentIdentity) {
|
|
2370
|
+
const tempOrder = this.ensureTempOrder();
|
|
2371
|
+
const identity = String(paymentIdentity);
|
|
2372
|
+
tempOrder.payments = (tempOrder.payments || []).filter(payment => {
|
|
2373
|
+
const uniquePaymentNumber = payment?.metadata?.unique_payment_number;
|
|
2374
|
+
const orderPaymentId = payment?.order_payment_id;
|
|
2375
|
+
const customPaymentId = payment?.custom_payment_id;
|
|
2376
|
+
const uuid = payment?.uuid;
|
|
2377
|
+
return ![uniquePaymentNumber, orderPaymentId, customPaymentId, uuid].some(value => value !== undefined && String(value) === identity);
|
|
2378
|
+
});
|
|
2379
|
+
this.persistTempOrder();
|
|
2380
|
+
return tempOrder.payments;
|
|
2381
|
+
}
|
|
2382
|
+
confirmPendingVoucherPaymentsInList(payments) {
|
|
2383
|
+
return (payments || []).map(payment => {
|
|
2384
|
+
if (!isPendingVoucherPaymentRecord(payment)) return payment;
|
|
2385
|
+
return {
|
|
2386
|
+
...payment,
|
|
2387
|
+
status: ORDER_PAYMENT_STATUS_PAID
|
|
2388
|
+
};
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2391
|
+
discardPendingVoucherPaymentsFromList(payments) {
|
|
2392
|
+
return (payments || []).filter(payment => !isPendingVoucherPaymentRecord(payment));
|
|
2393
|
+
}
|
|
2394
|
+
prepareVoucherPaymentsForSubmit(payments, confirmPendingVoucherPayments = true) {
|
|
2395
|
+
const mappedPayments = (0, _utils.mapPaymentItemsToOrderPayments)(payments || []);
|
|
2396
|
+
return confirmPendingVoucherPayments ? this.confirmPendingVoucherPaymentsInList(mappedPayments) : this.discardPendingVoucherPaymentsFromList(mappedPayments);
|
|
2397
|
+
}
|
|
2398
|
+
applyPaymentLifecycleChange(tempOrder, options) {
|
|
2399
|
+
this.updateTempOrderPaymentStatus(tempOrder);
|
|
2400
|
+
if (options?.persist !== false) {
|
|
2401
|
+
this.persistTempOrder();
|
|
2402
|
+
}
|
|
2403
|
+
if (options?.recalculateSummary === false) return;
|
|
2404
|
+
const logContext = options?.logContext || 'payment lifecycle change';
|
|
2405
|
+
void this.recalculateSummary({
|
|
2406
|
+
createIfMissing: true
|
|
2407
|
+
}).catch(error => {
|
|
2408
|
+
this.logError(`recalculate summary after ${logContext} failed`, {
|
|
2409
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2410
|
+
});
|
|
2411
|
+
});
|
|
2412
|
+
}
|
|
2413
|
+
confirmPendingVoucherPayments(options) {
|
|
2414
|
+
const tempOrder = this.ensureTempOrder();
|
|
2415
|
+
const payments = tempOrder.payments || [];
|
|
2416
|
+
const hasPendingVoucher = payments.some(payment => isPendingVoucherPaymentRecord(payment));
|
|
2417
|
+
if (!hasPendingVoucher) return payments;
|
|
2418
|
+
tempOrder.payments = this.confirmPendingVoucherPaymentsInList(payments);
|
|
2419
|
+
this.applyPaymentLifecycleChange(tempOrder, {
|
|
2420
|
+
...options,
|
|
2421
|
+
logContext: 'confirmPendingVoucherPayments'
|
|
2422
|
+
});
|
|
2423
|
+
// this.recalculateSummary();
|
|
2424
|
+
return tempOrder.payments;
|
|
2425
|
+
}
|
|
2426
|
+
discardPendingVoucherPayments(options) {
|
|
2427
|
+
const tempOrder = this.ensureTempOrder();
|
|
2428
|
+
const payments = tempOrder.payments || [];
|
|
2429
|
+
const nextPayments = this.discardPendingVoucherPaymentsFromList(payments);
|
|
2430
|
+
if (nextPayments.length === payments.length) return payments;
|
|
2431
|
+
tempOrder.payments = nextPayments;
|
|
2432
|
+
this.applyPaymentLifecycleChange(tempOrder, {
|
|
2433
|
+
...options,
|
|
2434
|
+
logContext: 'discardPendingVoucherPayments'
|
|
2435
|
+
});
|
|
2436
|
+
return tempOrder.payments;
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
/**
|
|
2440
|
+
* 覆盖当前订单中的 wallet pass 支付项。
|
|
2441
|
+
*
|
|
2442
|
+
* 判断口径与 PaymentModal 保持一致:voucher_id 存在且不为 0 的支付项
|
|
2443
|
+
* 视为 wallet pass;其它支付项保持不变。
|
|
2444
|
+
*/
|
|
2445
|
+
updateVoucherOrderPayments(payments, options) {
|
|
2446
|
+
const tempOrder = this.ensureTempOrder();
|
|
2447
|
+
const isOrderPaymentType = value => {
|
|
2448
|
+
return value === 'normal' || value === 'deposit';
|
|
2449
|
+
};
|
|
2450
|
+
const voucherOrderPaymentType = this.resolveNextOrderPaymentType(tempOrder);
|
|
2451
|
+
const withVoucherPaymentType = payment => {
|
|
2452
|
+
const paymentRecord = payment;
|
|
2453
|
+
if (!isVoucherPaymentRecord(paymentRecord)) return payment;
|
|
2454
|
+
const explicitPaymentType = isOrderPaymentType(paymentRecord.order_payment_type) ? paymentRecord.order_payment_type : isOrderPaymentType(paymentRecord.type) ? paymentRecord.type : undefined;
|
|
2455
|
+
const nextPaymentType = explicitPaymentType || voucherOrderPaymentType;
|
|
2456
|
+
const shouldPreserveCustomPaymentType = paymentRecord.custom_payment_type === undefined && paymentRecord.type !== undefined && !isOrderPaymentType(paymentRecord.type);
|
|
2457
|
+
return {
|
|
2458
|
+
...paymentRecord,
|
|
2459
|
+
...(shouldPreserveCustomPaymentType ? {
|
|
2460
|
+
custom_payment_type: paymentRecord.type
|
|
2461
|
+
} : {}),
|
|
2462
|
+
type: nextPaymentType,
|
|
2463
|
+
order_payment_type: nextPaymentType
|
|
2464
|
+
};
|
|
2465
|
+
};
|
|
2466
|
+
const normalizeVoucherPaymentStatus = payment => {
|
|
2467
|
+
const paymentRecord = payment;
|
|
2468
|
+
if (!isVoucherPaymentRecord(paymentRecord)) return payment;
|
|
2469
|
+
if (hasSyncedOrderPaymentRecord(paymentRecord) && (paymentRecord.status === undefined || paymentRecord.status === 'active')) {
|
|
2470
|
+
return {
|
|
2471
|
+
...paymentRecord,
|
|
2472
|
+
status: ORDER_PAYMENT_STATUS_PAID
|
|
2473
|
+
};
|
|
2474
|
+
}
|
|
2475
|
+
if (paymentRecord.status !== undefined && paymentRecord.status !== 'active') {
|
|
2476
|
+
return payment;
|
|
2477
|
+
}
|
|
2478
|
+
return {
|
|
2479
|
+
...paymentRecord,
|
|
2480
|
+
status: options?.status || ORDER_PAYMENT_STATUS_PENDING
|
|
2481
|
+
};
|
|
2482
|
+
};
|
|
2483
|
+
const mappedVouchers = (0, _utils.mapPaymentItemsToOrderPayments)(payments.map(payment => this.normalizePaymentSource(normalizeVoucherPaymentStatus(withVoucherPaymentType(payment)), {
|
|
2484
|
+
defaultPaid: false
|
|
2485
|
+
}))).filter(payment => {
|
|
2486
|
+
return isVoucherPaymentRecord(payment);
|
|
2487
|
+
});
|
|
2488
|
+
const nonVoucherPayments = (tempOrder.payments || []).filter(payment => {
|
|
2489
|
+
return !isVoucherPaymentRecord(payment);
|
|
2490
|
+
});
|
|
2491
|
+
const normalizeCommittedVoucherPayment = payment => {
|
|
2492
|
+
if (hasSyncedOrderPaymentRecord(payment) && (payment.status === undefined || payment.status === 'active')) {
|
|
2493
|
+
return {
|
|
2494
|
+
...payment,
|
|
2495
|
+
status: ORDER_PAYMENT_STATUS_PAID
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
return payment;
|
|
2499
|
+
};
|
|
2500
|
+
const committedVoucherPayments = (tempOrder.payments || []).filter(payment => isCommittedVoucherPaymentRecord(payment)).map(payment => normalizeCommittedVoucherPayment(payment));
|
|
2501
|
+
const committedVoucherPaymentIds = new Set(committedVoucherPayments.filter(payment => hasSyncedOrderPaymentRecord(payment)).map(payment => String(payment.order_payment_id)));
|
|
2502
|
+
const committedVoucherIds = new Set(committedVoucherPayments.map(payment => String(payment.voucher_id)));
|
|
2503
|
+
const replaceableVouchers = mappedVouchers.filter(payment => {
|
|
2504
|
+
if (hasSyncedOrderPaymentRecord(payment) && committedVoucherPaymentIds.has(String(payment.order_payment_id))) {
|
|
2505
|
+
return false;
|
|
2506
|
+
}
|
|
2507
|
+
if (payment.voucher_id !== undefined && committedVoucherIds.has(String(payment.voucher_id))) {
|
|
2508
|
+
return false;
|
|
2509
|
+
}
|
|
2510
|
+
return true;
|
|
2511
|
+
});
|
|
2512
|
+
const cappedVouchers = this.capVoucherPaymentsByRemainingAmount({
|
|
2513
|
+
vouchers: replaceableVouchers,
|
|
2514
|
+
nonVoucherPayments: [...nonVoucherPayments, ...committedVoucherPayments]
|
|
2515
|
+
});
|
|
2516
|
+
tempOrder.payments = [...nonVoucherPayments, ...committedVoucherPayments, ...cappedVouchers];
|
|
2517
|
+
this.updateTempOrderPaymentStatus(tempOrder);
|
|
2518
|
+
this.persistTempOrder();
|
|
2519
|
+
void this.recalculateSummary({
|
|
2520
|
+
createIfMissing: true
|
|
2521
|
+
}).catch(error => {
|
|
2522
|
+
this.logError('recalculate summary after updateVoucherOrderPayments failed', {
|
|
2523
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2524
|
+
});
|
|
2525
|
+
});
|
|
2526
|
+
return tempOrder.payments;
|
|
2527
|
+
}
|
|
2528
|
+
async shouldMergeProductToOrder(params) {
|
|
2529
|
+
const onBeforeMergeProductToOrder = this.orderHooks?.onBeforeMergeProductToOrder;
|
|
2530
|
+
if (!onBeforeMergeProductToOrder) return true;
|
|
2531
|
+
const result = await onBeforeMergeProductToOrder({
|
|
2532
|
+
...params,
|
|
2533
|
+
defaultShouldMerge: true
|
|
2534
|
+
});
|
|
2535
|
+
if (typeof result === 'boolean') return result;
|
|
2536
|
+
if (result && typeof result === 'object' && typeof result.shouldMerge === 'boolean') {
|
|
2537
|
+
return result.shouldMerge;
|
|
2538
|
+
}
|
|
2539
|
+
return true;
|
|
2540
|
+
}
|
|
2541
|
+
hasOrderProductLineNote(product) {
|
|
2542
|
+
return typeof product?.note === 'string' && product.note.trim().length > 0;
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
/**
|
|
2546
|
+
* 判断商品行是否显式绑定了 booking。
|
|
2547
|
+
*
|
|
2548
|
+
* @example
|
|
2549
|
+
* this.hasLinkedBookingUid({ metadata: { booking_uid: 'booking-1' } });
|
|
2550
|
+
*/
|
|
2551
|
+
hasLinkedBookingUid(product) {
|
|
2552
|
+
const bookingUid = product?.booking_uid ?? product?.metadata?.booking_uid;
|
|
2553
|
+
return bookingUid !== undefined && bookingUid !== null && String(bookingUid) !== '';
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
// ─── TempOrder: 商品 CRUD ───
|
|
2557
|
+
|
|
2558
|
+
/**
|
|
2559
|
+
* 预约商品加车后的统一收尾:促销、折扣、summary 重算与持久化(批量拆行时只调用一次)。
|
|
2560
|
+
*
|
|
2561
|
+
* @example
|
|
2562
|
+
* await this.finalizeProductOrderMutation(tempOrder);
|
|
2563
|
+
*/
|
|
2564
|
+
async finalizeProductOrderMutation(tempOrder, options) {
|
|
2565
|
+
this.syncTempOrderHolderFromBookings(tempOrder);
|
|
2566
|
+
if (!options?.skipEditDiscountConfigRefresh) {
|
|
2567
|
+
await this.ensureEditDiscountConfigForProductChange(tempOrder);
|
|
2568
|
+
}
|
|
2569
|
+
if (!options?.skipDiscountRecalculation) {
|
|
2570
|
+
await this.applyPromotion();
|
|
2571
|
+
}
|
|
2572
|
+
if (!options?.skipDiscountRecalculation) {
|
|
2573
|
+
this.applyDiscount();
|
|
2574
|
+
}
|
|
2575
|
+
this.sanitizeTempOrderProducts(tempOrder);
|
|
2576
|
+
await this.recalculateSummary({
|
|
2577
|
+
createIfMissing: true
|
|
2578
|
+
});
|
|
2579
|
+
this.persistTempOrder();
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
/**
|
|
2583
|
+
* 向 tempOrder 追加一条商品行(及可选 booking),不触发 summary 重算。
|
|
2584
|
+
* 带 booking 且 num>1 时由 `addProductToOrder` 拆成多次本方法调用(每次 num=1)。
|
|
2585
|
+
*
|
|
2586
|
+
* @example
|
|
2587
|
+
* await this.appendProductLineToTempOrder(tempOrder, { product_id: 1, num: 1 }, booking);
|
|
2588
|
+
*/
|
|
2589
|
+
async appendProductLineToTempOrder(tempOrder, product, booking) {
|
|
2590
|
+
const linked = booking ? this.createLinkedProductAndBooking({
|
|
2591
|
+
product,
|
|
2592
|
+
booking
|
|
2593
|
+
}) : null;
|
|
2594
|
+
const productToAdd = linked?.product || product;
|
|
2595
|
+
const incomingFingerprint = (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(productToAdd), productToAdd.product_bundle);
|
|
2596
|
+
const normalizedIncoming = (0, _utils.mergeOrderProductDisplayFields)(productToAdd, (0, _utils3.normalizeOrderProduct)(productToAdd));
|
|
2597
|
+
const incomingBookingUid = productToAdd.booking_uid ?? productToAdd.metadata?.booking_uid;
|
|
2598
|
+
if (incomingBookingUid !== undefined && incomingBookingUid !== null && String(incomingBookingUid) !== '') {
|
|
2599
|
+
normalizedIncoming.booking_uid = String(incomingBookingUid);
|
|
2600
|
+
normalizedIncoming.metadata = {
|
|
2601
|
+
...(normalizedIncoming.metadata || {}),
|
|
2602
|
+
booking_uid: String(incomingBookingUid)
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
2605
|
+
normalizedIncoming.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalizedIncoming.discount_list);
|
|
2606
|
+
const hasIncomingGoodPass = this.hasGoodPassDiscount(normalizedIncoming);
|
|
2607
|
+
const hasIncomingProductNote = this.hasOrderProductLineNote(normalizedIncoming);
|
|
2608
|
+
const hasIncomingBookingUid = this.hasLinkedBookingUid(productToAdd) || this.hasLinkedBookingUid(normalizedIncoming);
|
|
2609
|
+
const shouldForceNewLine = !!booking || hasIncomingProductNote || hasIncomingBookingUid;
|
|
2610
|
+
const matchedIndex = hasIncomingGoodPass || shouldForceNewLine ? -1 : tempOrder.products.findIndex(item => {
|
|
2611
|
+
if (this.hasGoodPassDiscount(item)) return false;
|
|
2612
|
+
if (this.hasOrderProductLineNote(item)) return false;
|
|
2613
|
+
if (item.order_detail_id !== undefined && item.order_detail_id !== null) return false;
|
|
2614
|
+
if (item.metadata?.is_edit_for_runtime) return false;
|
|
2615
|
+
if (item.product_id !== productToAdd.product_id) return false;
|
|
2616
|
+
if (item.product_variant_id !== productToAdd.product_variant_id) return false;
|
|
2617
|
+
const existedFingerprint = (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(item), item.product_bundle);
|
|
2618
|
+
return existedFingerprint === incomingFingerprint;
|
|
2619
|
+
});
|
|
2620
|
+
const matchedProduct = matchedIndex === -1 ? null : tempOrder.products[matchedIndex];
|
|
2621
|
+
const existedFingerprint = matchedProduct ? (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(matchedProduct), matchedProduct.product_bundle) : '';
|
|
2622
|
+
const shouldMerge = matchedProduct ? await this.shouldMergeProductToOrder({
|
|
2623
|
+
incomingProduct: productToAdd,
|
|
2624
|
+
normalizedIncoming,
|
|
2625
|
+
matchedProduct,
|
|
2626
|
+
matchedIndex,
|
|
2627
|
+
incomingFingerprint,
|
|
2628
|
+
existedFingerprint,
|
|
2629
|
+
tempOrder,
|
|
2630
|
+
booking
|
|
2631
|
+
}) : false;
|
|
2632
|
+
if (matchedIndex === -1 || !matchedProduct || !shouldMerge) {
|
|
2633
|
+
this.captureProductRuntime(tempOrder, productToAdd, normalizedIncoming);
|
|
2634
|
+
if (linked) {
|
|
2635
|
+
normalizedIncoming.booking_uid = linked.bookingUid;
|
|
2636
|
+
normalizedIncoming.metadata = {
|
|
2637
|
+
...(normalizedIncoming.metadata || {}),
|
|
2638
|
+
booking_uid: linked.bookingUid
|
|
2639
|
+
};
|
|
2640
|
+
}
|
|
2641
|
+
// tempOrder.products.push(normalizedIncoming);
|
|
2642
|
+
tempOrder.products = [...tempOrder.products, normalizedIncoming];
|
|
2643
|
+
} else {
|
|
2644
|
+
const targetProduct = matchedProduct;
|
|
2645
|
+
const targetUid = targetProduct.metadata?.unique_identification_number;
|
|
2646
|
+
const normalizedProduct = (0, _utils.mergeOrderProductDisplayFields)(targetProduct, (0, _utils3.normalizeOrderProduct)({
|
|
2647
|
+
...productToAdd,
|
|
2648
|
+
metadata: {
|
|
2649
|
+
...(productToAdd.metadata || {}),
|
|
2650
|
+
unique_identification_number: targetUid
|
|
2651
|
+
}
|
|
2652
|
+
}));
|
|
2653
|
+
normalizedProduct.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalizedProduct.discount_list);
|
|
2654
|
+
const nextNum = (0, _utils3.getSafeProductNum)(targetProduct.num + normalizedProduct.num);
|
|
2655
|
+
const targetIsManualProductDiscount = isManualProductDiscountProduct(targetProduct);
|
|
2656
|
+
const nextDiscountList = targetIsManualProductDiscount ? (0, _utils.normalizeOrderProductDiscountList)((0, _manualProductDiscount.syncManualProductDiscountProductNum)(targetProduct.discount_list, nextNum)) : normalizedProduct.discount_list;
|
|
2657
|
+
tempOrder.products[matchedIndex] = {
|
|
2658
|
+
...targetProduct,
|
|
2659
|
+
...normalizedProduct,
|
|
2660
|
+
discount_list: nextDiscountList,
|
|
2661
|
+
metadata: {
|
|
2662
|
+
...(targetProduct.metadata || {}),
|
|
2663
|
+
...(normalizedProduct.metadata || {}),
|
|
2664
|
+
unique_identification_number: targetUid
|
|
2665
|
+
},
|
|
2666
|
+
product_sku: {
|
|
2667
|
+
...(targetProduct.product_sku || {
|
|
2668
|
+
option: []
|
|
2669
|
+
}),
|
|
2670
|
+
...(normalizedProduct.product_sku || {
|
|
2671
|
+
option: []
|
|
2672
|
+
}),
|
|
2673
|
+
option: (0, _utils.getProductSkuOptions)(normalizedProduct)
|
|
2674
|
+
},
|
|
2675
|
+
...(targetIsManualProductDiscount ? {
|
|
2676
|
+
selling_price: targetProduct.selling_price,
|
|
2677
|
+
original_price: targetProduct.original_price,
|
|
2678
|
+
payment_price: targetProduct.payment_price,
|
|
2679
|
+
metadata: {
|
|
2680
|
+
...(normalizedProduct.metadata || {}),
|
|
2681
|
+
...(targetProduct.metadata || {}),
|
|
2682
|
+
unique_identification_number: targetUid
|
|
2683
|
+
}
|
|
2684
|
+
} : {}),
|
|
2685
|
+
note: targetProduct.note,
|
|
2686
|
+
order_detail_id: targetProduct.order_detail_id,
|
|
2687
|
+
num: nextNum
|
|
2688
|
+
};
|
|
2689
|
+
this.captureProductRuntime(tempOrder, productToAdd, tempOrder.products[matchedIndex], targetUid);
|
|
2690
|
+
}
|
|
2691
|
+
if (linked) {
|
|
2692
|
+
tempOrder.bookings = [...(tempOrder.bookings || []), linked.booking];
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
/**
|
|
2697
|
+
* 添加商品行。带 booking 时强制独立行;num>1 时拆成多条 num=1 的 product+booking(对齐 BookingByStep / ticketBooking addService)。
|
|
2698
|
+
*
|
|
2699
|
+
* @example
|
|
2700
|
+
* await order.addProductToOrder({ product_id: 1, num: 3 }, { start_time: '10:00' });
|
|
2701
|
+
* // → tempOrder.products 新增 3 行,每行 num=1,bookings 3 条
|
|
2702
|
+
*/
|
|
2703
|
+
async addProductToOrder(product, booking) {
|
|
2704
|
+
const tempOrder = this.ensureTempOrder();
|
|
2705
|
+
if (booking) {
|
|
2706
|
+
const productRecord = product;
|
|
2707
|
+
const splitCount = (0, _utils3.getSafeProductNum)(productRecord.num ?? productRecord.product_quantity ?? 1);
|
|
2708
|
+
if (splitCount > 1) {
|
|
2709
|
+
for (let i = 0; i < splitCount; i += 1) {
|
|
2710
|
+
const singleLine = (0, _lodashEs.cloneDeep)(productRecord);
|
|
2711
|
+
singleLine.num = 1;
|
|
2712
|
+
delete singleLine.product_quantity;
|
|
2713
|
+
await this.appendProductLineToTempOrder(tempOrder, singleLine, (0, _lodashEs.cloneDeep)(booking));
|
|
2714
|
+
}
|
|
2715
|
+
await this.finalizeProductOrderMutation(tempOrder);
|
|
2716
|
+
return tempOrder.products;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
await this.appendProductLineToTempOrder(tempOrder, product, booking);
|
|
2720
|
+
await this.finalizeProductOrderMutation(tempOrder);
|
|
2721
|
+
this.logInfo('addProductToOrder success', {
|
|
2722
|
+
product_id: product.product_id,
|
|
2723
|
+
with_booking: !!booking
|
|
2724
|
+
});
|
|
2725
|
+
return tempOrder.products;
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
/**
|
|
2729
|
+
* 批量添加商品行,所有行追加完成后只触发一次促销、折扣、summary 重算与持久化。
|
|
2730
|
+
*
|
|
2731
|
+
* @example
|
|
2732
|
+
* await order.addProductsToOrder([
|
|
2733
|
+
* { product: { product_id: 1, product_variant_id: 0, num: 1 } },
|
|
2734
|
+
* { product: { product_id: 2, product_variant_id: 0, num: 1 } },
|
|
2735
|
+
* ]);
|
|
2736
|
+
*/
|
|
2737
|
+
async addProductsToOrder(items, options) {
|
|
2738
|
+
const tempOrder = this.ensureTempOrder();
|
|
2739
|
+
if (!Array.isArray(items) || items.length === 0) return tempOrder.products;
|
|
2740
|
+
for (const item of items || []) {
|
|
2741
|
+
const {
|
|
2742
|
+
product,
|
|
2743
|
+
booking
|
|
2744
|
+
} = item;
|
|
2745
|
+
if (!product) continue;
|
|
2746
|
+
if (booking) {
|
|
2747
|
+
const productRecord = product;
|
|
2748
|
+
const splitCount = (0, _utils3.getSafeProductNum)(productRecord.num ?? productRecord.product_quantity ?? 1);
|
|
2749
|
+
if (splitCount > 1) {
|
|
2750
|
+
for (let i = 0; i < splitCount; i += 1) {
|
|
2751
|
+
const singleLine = (0, _lodashEs.cloneDeep)(productRecord);
|
|
2752
|
+
singleLine.num = 1;
|
|
2753
|
+
delete singleLine.product_quantity;
|
|
2754
|
+
await this.appendProductLineToTempOrder(tempOrder, singleLine, (0, _lodashEs.cloneDeep)(booking));
|
|
2755
|
+
}
|
|
2756
|
+
continue;
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
await this.appendProductLineToTempOrder(tempOrder, product, booking);
|
|
2760
|
+
}
|
|
2761
|
+
await this.finalizeProductOrderMutation(tempOrder, options);
|
|
2762
|
+
this.logInfo('addProductsToOrder success', {
|
|
2763
|
+
itemsCount: items.length
|
|
2764
|
+
});
|
|
2765
|
+
return tempOrder.products;
|
|
2766
|
+
}
|
|
2767
|
+
hasGoodPassDiscount(product) {
|
|
2768
|
+
const hasMainGoodPass = (product.discount_list || []).some(item => item?.type === 'good_pass' || item?.tag === 'good_pass');
|
|
2769
|
+
if (hasMainGoodPass) return true;
|
|
2770
|
+
const bundleDiscountList = [];
|
|
2771
|
+
(product.product_bundle || []).forEach(item => {
|
|
2772
|
+
bundleDiscountList.push(...(item?.discount_list || []));
|
|
2773
|
+
});
|
|
2774
|
+
(product?._extend?.other?.bundle || []).forEach(item => {
|
|
2775
|
+
bundleDiscountList.push(...(item?.discount_list || []));
|
|
2776
|
+
});
|
|
2777
|
+
return bundleDiscountList.some(item => item?.type === 'good_pass' || item?.tag === 'good_pass');
|
|
2778
|
+
}
|
|
2779
|
+
getBundleRuntimeIdentity(bundle) {
|
|
2780
|
+
if (!bundle || typeof bundle !== 'object') return '';
|
|
2781
|
+
return [bundle.bundle_id ?? bundle.id ?? '', bundle.bundle_group_id ?? bundle.group_id ?? '', bundle.bundle_product_id ?? bundle._bundle_product_id ?? bundle.product_id ?? '', bundle.bundle_variant_id ?? bundle.product_variant_id ?? 0].join('|');
|
|
2782
|
+
}
|
|
2783
|
+
preservePersistedBundlePriceFields(previousBundles, nextBundles) {
|
|
2784
|
+
if (!Array.isArray(nextBundles)) return nextBundles;
|
|
2785
|
+
if (!Array.isArray(previousBundles) || previousBundles.length === 0) return nextBundles;
|
|
2786
|
+
const previousByIdentity = new Map();
|
|
2787
|
+
previousBundles.forEach(bundle => {
|
|
2788
|
+
const identity = this.getBundleRuntimeIdentity(bundle);
|
|
2789
|
+
if (identity) previousByIdentity.set(identity, bundle);
|
|
2790
|
+
});
|
|
2791
|
+
return nextBundles.map((bundle, index) => {
|
|
2792
|
+
if (!bundle || typeof bundle !== 'object') return bundle;
|
|
2793
|
+
const previous = previousByIdentity.get(this.getBundleRuntimeIdentity(bundle)) || previousBundles[index];
|
|
2794
|
+
if (!previous || typeof previous !== 'object') return bundle;
|
|
2795
|
+
return {
|
|
2796
|
+
...bundle,
|
|
2797
|
+
...((bundle.cover === undefined || bundle.cover === null || bundle.cover === '') && previous.cover !== undefined && previous.cover !== null && previous.cover !== '' ? {
|
|
2798
|
+
cover: previous.cover
|
|
2799
|
+
} : {}),
|
|
2800
|
+
price: previous.price,
|
|
2801
|
+
custom_price: previous.custom_price,
|
|
2802
|
+
bundle_payment_price: previous.bundle_payment_price,
|
|
2803
|
+
bundle_selling_price: previous.bundle_selling_price,
|
|
2804
|
+
original_price: previous.original_price,
|
|
2805
|
+
product_price: previous.product_price
|
|
2806
|
+
};
|
|
2807
|
+
});
|
|
2808
|
+
}
|
|
2809
|
+
applyOrderProductUpdateToTempOrder(tempOrder, params) {
|
|
2810
|
+
const {
|
|
2811
|
+
product_id,
|
|
2812
|
+
product_variant_id,
|
|
2813
|
+
updates,
|
|
2814
|
+
unique_identification_number,
|
|
2815
|
+
identity_key,
|
|
2816
|
+
product_sku,
|
|
2817
|
+
product_bundle,
|
|
2818
|
+
booking
|
|
2819
|
+
} = params;
|
|
2820
|
+
const identityLookup = {
|
|
2821
|
+
product_id,
|
|
2822
|
+
product_variant_id
|
|
2823
|
+
};
|
|
2824
|
+
if (unique_identification_number !== undefined) {
|
|
2825
|
+
identityLookup.unique_identification_number = unique_identification_number;
|
|
2826
|
+
if (identity_key === undefined) {
|
|
2827
|
+
identityLookup.identity_key = unique_identification_number;
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
if (identity_key !== undefined) {
|
|
2831
|
+
identityLookup.identity_key = identity_key;
|
|
2832
|
+
}
|
|
2833
|
+
if (product_sku !== undefined) identityLookup.product_sku = product_sku;
|
|
2834
|
+
if (product_bundle !== undefined) identityLookup.product_bundle = product_bundle;
|
|
2835
|
+
let productIndex = -1;
|
|
2836
|
+
if (unique_identification_number !== undefined) {
|
|
2837
|
+
const targetUid = String(unique_identification_number);
|
|
2838
|
+
productIndex = tempOrder.products.findIndex(item => {
|
|
2839
|
+
return String(item.metadata?.unique_identification_number || item.unique_identification_number || '') === targetUid;
|
|
2840
|
+
});
|
|
2841
|
+
}
|
|
2842
|
+
if (productIndex === -1) {
|
|
2843
|
+
productIndex = (0, _utils3.getProductIdentityIndex)(tempOrder.products, identityLookup);
|
|
2844
|
+
}
|
|
2845
|
+
if (productIndex === -1) {
|
|
2846
|
+
throw new Error('[Order] 目标商品不存在,无法更新');
|
|
2847
|
+
}
|
|
2848
|
+
const targetProduct = tempOrder.products[productIndex];
|
|
2849
|
+
const nextProduct = {
|
|
2850
|
+
...targetProduct,
|
|
2851
|
+
...updates,
|
|
2852
|
+
product_id,
|
|
2853
|
+
product_variant_id,
|
|
2854
|
+
metadata: {
|
|
2855
|
+
...(targetProduct.metadata || {}),
|
|
2856
|
+
...(updates?.metadata || {}),
|
|
2857
|
+
unique_identification_number: targetProduct.metadata?.unique_identification_number || unique_identification_number
|
|
2858
|
+
},
|
|
2859
|
+
product_sku: {
|
|
2860
|
+
...(targetProduct.product_sku || {
|
|
2861
|
+
option: []
|
|
2862
|
+
}),
|
|
2863
|
+
...(updates?.product_sku && typeof updates.product_sku === 'object' ? updates.product_sku : {}),
|
|
2864
|
+
option: Array.isArray(updates?.product_sku?.option) ? updates.product_sku.option : (0, _utils.getProductSkuOptions)(targetProduct)
|
|
2865
|
+
}
|
|
2866
|
+
};
|
|
2867
|
+
nextProduct.num = (0, _utils3.getSafeProductNum)(nextProduct.num);
|
|
2868
|
+
// 兜底:caller 通过 updates 直接改了 selling_price / original_price 时,
|
|
2869
|
+
// 视为 "重新给 source-level 主价"(caller 侧旧约定仍是 main-only 单价)。
|
|
2870
|
+
// 清掉 metadata 里的 source_product_price / main_product_* / price_schema_version,
|
|
2871
|
+
// 让 normalize 把顶层 selling_price 当作 source 来重算 main_original(=source+options) /
|
|
2872
|
+
// main_selling / composite;否则旧 metadata 会反压顶层新值,触发口径不一致。
|
|
2873
|
+
// 如果 caller 走推荐路径(updates.metadata.* 显式指定主价),保留 metadata 由 normalize 认它。
|
|
2874
|
+
const callerUpdatesTopPrice = Object.prototype.hasOwnProperty.call(updates || {}, 'selling_price') || Object.prototype.hasOwnProperty.call(updates || {}, 'original_price');
|
|
2875
|
+
const callerUpdatesMainMeta = updates?.metadata?.main_product_selling_price !== undefined || updates?.metadata?.main_product_original_price !== undefined || updates?.metadata?.source_product_price !== undefined;
|
|
2876
|
+
const callerUpdatesSourcePrice = updates?.metadata?.source_product_price !== undefined;
|
|
2877
|
+
const callerUpdatesMainSellingPrice = updates?.metadata?.main_product_selling_price !== undefined;
|
|
2878
|
+
const callerUpdatesMainOriginalPrice = updates?.metadata?.main_product_original_price !== undefined;
|
|
2879
|
+
const previousLineFingerprint = (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(targetProduct), targetProduct.product_bundle);
|
|
2880
|
+
const nextLineFingerprint = (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(nextProduct), nextProduct.product_bundle);
|
|
2881
|
+
const callerChangesVariant = Number(product_variant_id || 0) !== Number(targetProduct.product_variant_id || 0);
|
|
2882
|
+
const callerChangesLineConfiguration = callerChangesVariant || previousLineFingerprint !== nextLineFingerprint;
|
|
2883
|
+
const isPersistedOrderLine = targetProduct.order_detail_id !== undefined && targetProduct.order_detail_id !== null;
|
|
2884
|
+
const callerChangesDiscountList = Array.isArray(updates?.discount_list) && updates.discount_list.length > 0 && !isSameDiscountList(updates.discount_list, targetProduct.discount_list);
|
|
2885
|
+
const callerUpdatesManualPrice = updates?.metadata?.price_override !== undefined || updates?.metadata?.is_manual_discount !== undefined || updates?.metadata?.product_discount_reason !== undefined || callerChangesDiscountList;
|
|
2886
|
+
const callerAllowsBookingReprice = (() => {
|
|
2887
|
+
if (params.allow_booking_reprice !== true || !booking || !callerUpdatesTopPrice || !callerUpdatesMainMeta) {
|
|
2888
|
+
return false;
|
|
2889
|
+
}
|
|
2890
|
+
const hasChangedPrice = (nextValue, currentValue) => {
|
|
2891
|
+
if (nextValue === undefined || nextValue === null || nextValue === '') {
|
|
2892
|
+
return false;
|
|
2893
|
+
}
|
|
2894
|
+
return !new _decimal.default(Number(nextValue) || 0).equals(new _decimal.default(Number(currentValue) || 0));
|
|
2895
|
+
};
|
|
2896
|
+
return hasChangedPrice(updates.selling_price, targetProduct.selling_price) || hasChangedPrice(updates.original_price, targetProduct.original_price);
|
|
2897
|
+
})();
|
|
2898
|
+
if (isPersistedOrderLine && !callerChangesLineConfiguration && !callerUpdatesManualPrice && !callerAllowsBookingReprice) {
|
|
2899
|
+
nextProduct.selling_price = targetProduct.selling_price;
|
|
2900
|
+
nextProduct.original_price = targetProduct.original_price;
|
|
2901
|
+
nextProduct.payment_price = targetProduct.payment_price;
|
|
2902
|
+
nextProduct.metadata = {
|
|
2903
|
+
...(nextProduct.metadata || {}),
|
|
2904
|
+
source_product_price: targetProduct.metadata?.source_product_price,
|
|
2905
|
+
main_product_selling_price: targetProduct.metadata?.main_product_selling_price,
|
|
2906
|
+
main_product_original_price: targetProduct.metadata?.main_product_original_price,
|
|
2907
|
+
price_schema_version: targetProduct.metadata?.price_schema_version ?? 2
|
|
2908
|
+
};
|
|
2909
|
+
nextProduct.product_bundle = this.preservePersistedBundlePriceFields(targetProduct.product_bundle, nextProduct.product_bundle);
|
|
2910
|
+
} else if (callerChangesLineConfiguration && callerUpdatesSourcePrice && (!callerUpdatesMainSellingPrice || !callerUpdatesMainOriginalPrice)) {
|
|
2911
|
+
// 新配置只传了 source 时,不能把未传的 main_product_* 从旧行继承下来。
|
|
2912
|
+
// 以新 source + 新 option 重建券前价;券后价未显式提供时保留旧主商品折扣额。
|
|
2913
|
+
const previousMainOriginal = new _decimal.default(Number(targetProduct.metadata?.main_product_original_price) || 0);
|
|
2914
|
+
const previousMainSelling = new _decimal.default(Number(targetProduct.metadata?.main_product_selling_price) || previousMainOriginal.toNumber());
|
|
2915
|
+
const preservedDiscount = previousMainOriginal.minus(previousMainSelling);
|
|
2916
|
+
const nextMainOriginal = new _decimal.default(Number(updates.metadata.source_product_price) || 0).plus((0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(nextProduct)));
|
|
2917
|
+
nextProduct.metadata = {
|
|
2918
|
+
...(nextProduct.metadata || {}),
|
|
2919
|
+
source_product_price: String(updates.metadata.source_product_price),
|
|
2920
|
+
main_product_original_price: callerUpdatesMainOriginalPrice ? String(updates.metadata.main_product_original_price) : nextMainOriginal.toDecimalPlaces(2).toFixed(2),
|
|
2921
|
+
main_product_selling_price: callerUpdatesMainSellingPrice ? String(updates.metadata.main_product_selling_price) : nextMainOriginal.minus(preservedDiscount).toDecimalPlaces(2).toFixed(2),
|
|
2922
|
+
price_schema_version: 2
|
|
2923
|
+
};
|
|
2924
|
+
} else if (callerChangesVariant && !callerUpdatesTopPrice && !callerUpdatesMainMeta) {
|
|
2925
|
+
// variant 已变化但 caller 没有显式报价时,旧 v2 metadata 不能继续充当权威价格源。
|
|
2926
|
+
// 清理后由 normalize 优先读取 metadata.origin.variant 中新 variant 的目录价;
|
|
2927
|
+
// 顶层 original/selling 的旧差额仍会作为 legacy discount 迁移到新价格。
|
|
2928
|
+
const existedMeta = nextProduct.metadata || {};
|
|
2929
|
+
nextProduct.metadata = {
|
|
2930
|
+
...existedMeta
|
|
2931
|
+
};
|
|
2932
|
+
delete nextProduct.metadata.source_product_price;
|
|
2933
|
+
delete nextProduct.metadata.main_product_selling_price;
|
|
2934
|
+
delete nextProduct.metadata.main_product_original_price;
|
|
2935
|
+
delete nextProduct.metadata.price_schema_version;
|
|
2936
|
+
} else if (callerChangesLineConfiguration && !callerUpdatesTopPrice && !callerUpdatesMainMeta) {
|
|
2937
|
+
// 仅 option / bundle 变化时,保留历史 source 和主商品折扣金额,
|
|
2938
|
+
// 但必须让 original 与 selling 一起随新的 option 金额移动。
|
|
2939
|
+
const previousOptionSum = (0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(targetProduct));
|
|
2940
|
+
const nextOptionSum = (0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(nextProduct));
|
|
2941
|
+
const previousMainOriginal = new _decimal.default(Number(targetProduct.metadata?.main_product_original_price ?? targetProduct.original_price ?? 0) || 0);
|
|
2942
|
+
const previousMainSelling = new _decimal.default(Number(targetProduct.metadata?.main_product_selling_price ?? targetProduct.selling_price ?? previousMainOriginal.toNumber()) || 0);
|
|
2943
|
+
const preservedDiscount = previousMainOriginal.minus(previousMainSelling);
|
|
2944
|
+
const previousSource = targetProduct.metadata?.source_product_price != null ? new _decimal.default(Number(targetProduct.metadata.source_product_price) || 0) : previousMainOriginal.minus(previousOptionSum);
|
|
2945
|
+
const nextMainOriginal = previousSource.plus(nextOptionSum);
|
|
2946
|
+
nextProduct.metadata = {
|
|
2947
|
+
...(nextProduct.metadata || {}),
|
|
2948
|
+
source_product_price: previousSource.toDecimalPlaces(2).toFixed(2),
|
|
2949
|
+
main_product_original_price: nextMainOriginal.toDecimalPlaces(2).toFixed(2),
|
|
2950
|
+
main_product_selling_price: nextMainOriginal.minus(preservedDiscount).toDecimalPlaces(2).toFixed(2),
|
|
2951
|
+
price_schema_version: 2
|
|
2952
|
+
};
|
|
2953
|
+
} else if (callerUpdatesTopPrice && !callerUpdatesMainMeta) {
|
|
2954
|
+
const existedMeta = nextProduct.metadata || {};
|
|
2955
|
+
nextProduct.metadata = {
|
|
2956
|
+
...existedMeta
|
|
2957
|
+
};
|
|
2958
|
+
delete nextProduct.metadata.source_product_price;
|
|
2959
|
+
delete nextProduct.metadata.main_product_selling_price;
|
|
2960
|
+
delete nextProduct.metadata.main_product_original_price;
|
|
2961
|
+
delete nextProduct.metadata.price_schema_version;
|
|
2962
|
+
}
|
|
2963
|
+
// normalizeOrderProduct 幂等:v2 metadata 存在 → 保留 main_product_* 折扣重算 composite;
|
|
2964
|
+
// 否则 → 把顶层 selling_price 视作 source,派生 main_product_* 并写 price_schema_version: 2。
|
|
2965
|
+
let normalizedProduct = (0, _utils.mergeOrderProductDisplayFields)(nextProduct, (0, _utils3.normalizeOrderProduct)(nextProduct));
|
|
2966
|
+
normalizedProduct.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalizedProduct.discount_list);
|
|
2967
|
+
const preservedBookingUid = nextProduct.booking_uid || nextProduct.metadata?.booking_uid || targetProduct.booking_uid || targetProduct.metadata?.booking_uid;
|
|
2968
|
+
if (!booking && preservedBookingUid) {
|
|
2969
|
+
normalizedProduct.booking_uid = preservedBookingUid;
|
|
2970
|
+
normalizedProduct.metadata = {
|
|
2971
|
+
...(normalizedProduct.metadata || {}),
|
|
2972
|
+
booking_uid: preservedBookingUid
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
if (booking) {
|
|
2976
|
+
const productUid = String(normalizedProduct.metadata?.unique_identification_number || targetProduct.metadata?.unique_identification_number || unique_identification_number || (0, _utils.createUuidV4)());
|
|
2977
|
+
normalizedProduct.metadata = {
|
|
2978
|
+
...(normalizedProduct.metadata || {}),
|
|
2979
|
+
unique_identification_number: productUid
|
|
2980
|
+
};
|
|
2981
|
+
const linked = this.createLinkedProductAndBooking({
|
|
2982
|
+
product: {
|
|
2983
|
+
...normalizedProduct,
|
|
2984
|
+
unique_identification_number: productUid
|
|
2985
|
+
},
|
|
2986
|
+
booking
|
|
2987
|
+
});
|
|
2988
|
+
this.removeLinkedBookingsForProduct(tempOrder, targetProduct);
|
|
2989
|
+
normalizedProduct = {
|
|
2990
|
+
...normalizedProduct,
|
|
2991
|
+
booking_uid: linked.bookingUid,
|
|
2992
|
+
metadata: {
|
|
2993
|
+
...(normalizedProduct.metadata || {}),
|
|
2994
|
+
booking_uid: linked.bookingUid,
|
|
2995
|
+
unique_identification_number: productUid
|
|
2996
|
+
}
|
|
2997
|
+
};
|
|
2998
|
+
tempOrder.bookings = [...(tempOrder.bookings || []), linked.booking];
|
|
2999
|
+
}
|
|
3000
|
+
tempOrder.products[productIndex] = normalizedProduct;
|
|
3001
|
+
this.syncTempOrderHolderFromBookings(tempOrder);
|
|
3002
|
+
this.captureProductRuntime(tempOrder, updates, tempOrder.products[productIndex], tempOrder.products[productIndex].metadata?.unique_identification_number);
|
|
3003
|
+
return tempOrder.products[productIndex].metadata?.unique_identification_number;
|
|
3004
|
+
}
|
|
3005
|
+
async updateOrderProduct(params) {
|
|
3006
|
+
const tempOrder = this.ensureTempOrder();
|
|
3007
|
+
const updatedProductUid = this.applyOrderProductUpdateToTempOrder(tempOrder, params);
|
|
3008
|
+
await this.applyPromotion();
|
|
3009
|
+
this.applyDiscount({
|
|
3010
|
+
reapplyBookingDiscountProductUids: params.allow_booking_discount_reapply && updatedProductUid ? [String(updatedProductUid)] : undefined
|
|
3011
|
+
});
|
|
3012
|
+
this.sanitizeTempOrderProducts(tempOrder);
|
|
3013
|
+
await this.recalculateSummary({
|
|
3014
|
+
createIfMissing: true
|
|
3015
|
+
});
|
|
3016
|
+
this.persistTempOrder();
|
|
3017
|
+
this.logInfo('updateOrderProduct success', {
|
|
3018
|
+
params
|
|
3019
|
+
});
|
|
3020
|
+
return tempOrder.products;
|
|
3021
|
+
}
|
|
3022
|
+
async updateOrderProducts(paramsList) {
|
|
3023
|
+
const tempOrder = this.ensureTempOrder();
|
|
3024
|
+
if (!paramsList.length) {
|
|
3025
|
+
return tempOrder.products;
|
|
3026
|
+
}
|
|
3027
|
+
const reapplyBookingDiscountProductUids = paramsList.reduce((acc, params) => {
|
|
3028
|
+
const updatedProductUid = this.applyOrderProductUpdateToTempOrder(tempOrder, params);
|
|
3029
|
+
// 批量改预约时间时,延迟到最后一次折扣计算里统一恢复已选预约折扣卡。
|
|
3030
|
+
if (params.allow_booking_discount_reapply && updatedProductUid) {
|
|
3031
|
+
acc.push(String(updatedProductUid));
|
|
3032
|
+
}
|
|
3033
|
+
return acc;
|
|
3034
|
+
}, []);
|
|
3035
|
+
await this.finalizeAfterProductsMutation(tempOrder, {
|
|
3036
|
+
reapplyBookingDiscountProductUids: reapplyBookingDiscountProductUids.length ? reapplyBookingDiscountProductUids : undefined
|
|
3037
|
+
});
|
|
3038
|
+
this.logInfo('updateOrderProduct success', {
|
|
3039
|
+
paramsList
|
|
3040
|
+
});
|
|
3041
|
+
return tempOrder.products;
|
|
3042
|
+
}
|
|
3043
|
+
async updateOrderProductQuantity(params) {
|
|
3044
|
+
const {
|
|
3045
|
+
product_id,
|
|
3046
|
+
product_variant_id,
|
|
3047
|
+
num,
|
|
3048
|
+
unique_identification_number,
|
|
3049
|
+
identity_key,
|
|
3050
|
+
product_sku,
|
|
3051
|
+
product_bundle
|
|
3052
|
+
} = params;
|
|
3053
|
+
const tempOrder = this.ensureTempOrder();
|
|
521
3054
|
const identityLookup = {
|
|
522
3055
|
product_id,
|
|
523
3056
|
product_variant_id
|
|
524
3057
|
};
|
|
3058
|
+
if (unique_identification_number !== undefined) {
|
|
3059
|
+
identityLookup.unique_identification_number = unique_identification_number;
|
|
3060
|
+
if (identity_key === undefined) {
|
|
3061
|
+
identityLookup.identity_key = unique_identification_number;
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
525
3064
|
if (identity_key !== undefined) identityLookup.identity_key = identity_key;
|
|
526
|
-
if (
|
|
3065
|
+
if (product_sku !== undefined) identityLookup.product_sku = product_sku;
|
|
527
3066
|
if (product_bundle !== undefined) identityLookup.product_bundle = product_bundle;
|
|
528
|
-
|
|
3067
|
+
let productIndex = -1;
|
|
3068
|
+
if (unique_identification_number !== undefined) {
|
|
3069
|
+
const targetUid = String(unique_identification_number);
|
|
3070
|
+
productIndex = tempOrder.products.findIndex(item => {
|
|
3071
|
+
return String(item.metadata?.unique_identification_number || item.unique_identification_number || '') === targetUid;
|
|
3072
|
+
});
|
|
3073
|
+
}
|
|
529
3074
|
if (productIndex === -1) {
|
|
530
|
-
|
|
3075
|
+
productIndex = (0, _utils3.getProductIdentityIndex)(tempOrder.products, identityLookup);
|
|
3076
|
+
}
|
|
3077
|
+
if (productIndex === -1) {
|
|
3078
|
+
throw new Error('[Order] 目标商品不存在,无法更新数量');
|
|
531
3079
|
}
|
|
532
3080
|
const targetProduct = tempOrder.products[productIndex];
|
|
533
|
-
const
|
|
3081
|
+
const normalizedProduct = (0, _utils3.normalizeOrderProduct)({
|
|
534
3082
|
...targetProduct,
|
|
3083
|
+
num: (0, _utils3.getSafeProductNum)(num),
|
|
3084
|
+
metadata: {
|
|
3085
|
+
...(targetProduct.metadata || {}),
|
|
3086
|
+
unique_identification_number: targetProduct.metadata?.unique_identification_number || unique_identification_number
|
|
3087
|
+
}
|
|
3088
|
+
});
|
|
3089
|
+
normalizedProduct.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalizedProduct.discount_list);
|
|
3090
|
+
tempOrder.products[productIndex] = normalizedProduct;
|
|
3091
|
+
await this.applyPromotion();
|
|
3092
|
+
this.applyDiscount();
|
|
3093
|
+
this.sanitizeTempOrderProducts(tempOrder);
|
|
3094
|
+
await this.recalculateSummary({
|
|
3095
|
+
createIfMissing: true
|
|
3096
|
+
});
|
|
3097
|
+
this.persistTempOrder();
|
|
3098
|
+
this.logInfo('updateOrderProductQuantity success', {
|
|
3099
|
+
params
|
|
3100
|
+
});
|
|
3101
|
+
return tempOrder.products;
|
|
3102
|
+
}
|
|
3103
|
+
updateOrderBooking(params) {
|
|
3104
|
+
const {
|
|
3105
|
+
unique_identification_number,
|
|
3106
|
+
updates
|
|
3107
|
+
} = params;
|
|
3108
|
+
const tempOrder = this.ensureTempOrder();
|
|
3109
|
+
const bookingUid = String(unique_identification_number || '');
|
|
3110
|
+
const bookingIndex = this.findBookingIndexByUniqueIdentificationNumber(tempOrder, bookingUid);
|
|
3111
|
+
if (!bookingUid || bookingIndex === -1) {
|
|
3112
|
+
throw new Error('[Order] 目标 booking 不存在,无法更新');
|
|
3113
|
+
}
|
|
3114
|
+
const targetBooking = tempOrder.bookings[bookingIndex] || {};
|
|
3115
|
+
const nextBooking = {
|
|
3116
|
+
...targetBooking,
|
|
535
3117
|
...updates,
|
|
536
|
-
|
|
537
|
-
|
|
3118
|
+
metadata: {
|
|
3119
|
+
...(targetBooking.metadata || {}),
|
|
3120
|
+
...(updates.metadata || {}),
|
|
3121
|
+
unique_identification_number: bookingUid
|
|
3122
|
+
}
|
|
538
3123
|
};
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
this.
|
|
3124
|
+
this.syncBookingMetadataHolder(nextBooking, nextBooking.metadata);
|
|
3125
|
+
nextBooking.is_all = (0, _utils.normalizeBookingIsAll)(nextBooking);
|
|
3126
|
+
nextBooking.sub_type = (0, _utils.normalizeBookingSubType)(nextBooking);
|
|
3127
|
+
tempOrder.bookings[bookingIndex] = nextBooking;
|
|
3128
|
+
this.syncTempOrderHolderFromBookings(tempOrder);
|
|
3129
|
+
this.persistTempOrder();
|
|
3130
|
+
return tempOrder.bookings;
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
/**
|
|
3134
|
+
* 商品行写路径收尾:促销 → 折扣 → 清洗 → summary → 持久化。
|
|
3135
|
+
* 批量删除等多行变更场景只应调用一次,避免 N 次 applyPromotion。
|
|
3136
|
+
*
|
|
3137
|
+
* @example
|
|
3138
|
+
* await this.finalizeAfterProductsMutation(tempOrder);
|
|
3139
|
+
*/
|
|
3140
|
+
async finalizeAfterProductsMutation(tempOrder, options) {
|
|
3141
|
+
this.syncTempOrderHolderFromBookings(tempOrder);
|
|
3142
|
+
await this.applyPromotion();
|
|
3143
|
+
this.applyDiscount({
|
|
3144
|
+
reapplyBookingDiscountProductUids: options?.reapplyBookingDiscountProductUids
|
|
3145
|
+
});
|
|
3146
|
+
this.sanitizeTempOrderProducts(tempOrder);
|
|
562
3147
|
await this.recalculateSummary({
|
|
563
3148
|
createIfMissing: true
|
|
564
3149
|
});
|
|
565
3150
|
this.persistTempOrder();
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
/**
|
|
3154
|
+
* 批量按 identity 删除购物车行,末尾仅一次促销重算。
|
|
3155
|
+
*
|
|
3156
|
+
* @example
|
|
3157
|
+
* await order.removeProductsFromOrder([identityA, identityB]);
|
|
3158
|
+
*/
|
|
3159
|
+
async removeProductsFromOrder(identities) {
|
|
3160
|
+
const tempOrder = this.ensureTempOrder();
|
|
3161
|
+
if (!identities.length) {
|
|
3162
|
+
return tempOrder.products;
|
|
3163
|
+
}
|
|
3164
|
+
const productsBeforeRemove = tempOrder.products || [];
|
|
3165
|
+
const bookingsBeforeRemove = tempOrder.bookings || [];
|
|
3166
|
+
const matchedProducts = productsBeforeRemove.filter(item => {
|
|
3167
|
+
return identities.some(identity => (0, _utils3.isIdentityMatch)(item, identity));
|
|
3168
|
+
});
|
|
3169
|
+
const removedProducts = [];
|
|
3170
|
+
tempOrder.products = tempOrder.products.filter(item => {
|
|
3171
|
+
const shouldRemove = identities.some(identity => (0, _utils3.isIdentityMatch)(item, identity));
|
|
3172
|
+
if (shouldRemove) removedProducts.push(item);
|
|
3173
|
+
return !shouldRemove;
|
|
3174
|
+
});
|
|
3175
|
+
for (const product of removedProducts) {
|
|
3176
|
+
this.removeLinkedBookingsForProduct(tempOrder, product);
|
|
3177
|
+
}
|
|
3178
|
+
const linkedBookingUidsToRemove = new Set();
|
|
3179
|
+
for (const product of matchedProducts) {
|
|
3180
|
+
const productUid = product?.metadata?.unique_identification_number || product?.unique_identification_number;
|
|
3181
|
+
const productBookingUid = product?.booking_uid || product?.metadata?.booking_uid;
|
|
3182
|
+
const isAddTimeProduct = product?.metadata?.product_add_schedule_time !== undefined && product?.metadata?.product_add_schedule_time !== null;
|
|
3183
|
+
if (isAddTimeProduct) continue;
|
|
3184
|
+
if (productBookingUid !== undefined && productBookingUid !== null && productBookingUid !== '') {
|
|
3185
|
+
linkedBookingUidsToRemove.add(String(productBookingUid));
|
|
3186
|
+
}
|
|
3187
|
+
if (productUid !== undefined && productUid !== null && productUid !== '') {
|
|
3188
|
+
bookingsBeforeRemove.forEach(booking => {
|
|
3189
|
+
const bookingUid = this.getBookingUniqueIdentificationNumber(booking);
|
|
3190
|
+
if (bookingUid && String(booking?.product_uid || '') === String(productUid)) {
|
|
3191
|
+
linkedBookingUidsToRemove.add(bookingUid);
|
|
3192
|
+
}
|
|
3193
|
+
});
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
if (linkedBookingUidsToRemove.size > 0) {
|
|
3197
|
+
// 删除主预约商品时,同步移除挂在同一 booking_uid 下的加时商品,避免残留孤儿行。
|
|
3198
|
+
tempOrder.products = (tempOrder.products || []).filter(line => {
|
|
3199
|
+
const lineBookingUid = line?.metadata?.booking_uid || line?.booking_uid;
|
|
3200
|
+
const isAddTimeProduct = line?.metadata?.product_add_schedule_time !== undefined && line?.metadata?.product_add_schedule_time !== null;
|
|
3201
|
+
return !(isAddTimeProduct && lineBookingUid !== undefined && lineBookingUid !== null && linkedBookingUidsToRemove.has(String(lineBookingUid)));
|
|
3202
|
+
});
|
|
3203
|
+
}
|
|
3204
|
+
await this.finalizeAfterProductsMutation(tempOrder);
|
|
3205
|
+
this.logInfo('removeProductsFromOrder success', {
|
|
3206
|
+
identities
|
|
3207
|
+
});
|
|
566
3208
|
return tempOrder.products;
|
|
567
3209
|
}
|
|
568
3210
|
async removeProductFromOrder(identity) {
|
|
3211
|
+
return this.removeProductsFromOrder([identity]);
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
/**
|
|
3215
|
+
* 仅清空购物车行(products / bookings),不重置整单。
|
|
3216
|
+
* 用于「仅清空商品」;客户、备注、支付等协议字段保持不变。
|
|
3217
|
+
*/
|
|
3218
|
+
async clearOrderCartLines() {
|
|
569
3219
|
const tempOrder = this.ensureTempOrder();
|
|
570
|
-
tempOrder.products =
|
|
3220
|
+
tempOrder.products = [];
|
|
3221
|
+
tempOrder.bookings = [];
|
|
3222
|
+
this.syncTempOrderHolderFromBookings(tempOrder);
|
|
3223
|
+
if (tempOrder._extend && typeof tempOrder._extend === 'object') {
|
|
3224
|
+
tempOrder._extend = {
|
|
3225
|
+
...tempOrder._extend,
|
|
3226
|
+
productsByUid: {}
|
|
3227
|
+
};
|
|
3228
|
+
}
|
|
3229
|
+
await this.applyPromotion();
|
|
571
3230
|
this.applyDiscount();
|
|
3231
|
+
this.sanitizeTempOrderProducts(tempOrder);
|
|
572
3232
|
await this.recalculateSummary({
|
|
573
3233
|
createIfMissing: true
|
|
574
3234
|
});
|
|
575
3235
|
this.persistTempOrder();
|
|
576
|
-
|
|
3236
|
+
this.logInfo('clearOrderCartLines success', {});
|
|
3237
|
+
return tempOrder;
|
|
577
3238
|
}
|
|
578
3239
|
|
|
579
3240
|
// ─── TempOrder: 提交 ───
|
|
580
3241
|
|
|
3242
|
+
buildCurrentSubmitPayloadForLocalPrint(params) {
|
|
3243
|
+
const tempOrder = this.ensureTempOrder();
|
|
3244
|
+
this.persistTempOrder();
|
|
3245
|
+
const payload = (0, _utils.buildSubmitPayload)({
|
|
3246
|
+
tempOrder,
|
|
3247
|
+
cacheId: params?.cacheId ?? this.cacheId,
|
|
3248
|
+
platform: params?.platform || this.otherParams?.platform,
|
|
3249
|
+
businessCode: params?.businessCode ?? this.otherParams?.businessCode ?? this.otherParams?.business_code,
|
|
3250
|
+
channel: params?.channel,
|
|
3251
|
+
type: params?.type,
|
|
3252
|
+
summary: this.store.summary || (0, _utils.createEmptySummary)(),
|
|
3253
|
+
enhance: nextPayload => ({
|
|
3254
|
+
...nextPayload,
|
|
3255
|
+
order_number: tempOrder.order_number ?? nextPayload.order_number ?? null,
|
|
3256
|
+
shop_order_number: tempOrder.shop_order_number ?? nextPayload.shop_order_number ?? null,
|
|
3257
|
+
shop_full_order_number: tempOrder.shop_full_order_number ?? nextPayload.shop_full_order_number ?? null,
|
|
3258
|
+
small_ticket_data_flag: 1
|
|
3259
|
+
})
|
|
3260
|
+
});
|
|
3261
|
+
payload.created_at = undefined;
|
|
3262
|
+
return payload;
|
|
3263
|
+
}
|
|
3264
|
+
async applyLocalPrintOrderNumbers(order) {
|
|
3265
|
+
const tempOrder = this.ensureTempOrder();
|
|
3266
|
+
if (!order || typeof order !== 'object') return tempOrder;
|
|
3267
|
+
const shopOrderNumber = order.shop_order_number;
|
|
3268
|
+
const shopFullOrderNumber = order.shop_full_order_number;
|
|
3269
|
+
if (shopOrderNumber !== undefined && shopOrderNumber !== null && shopOrderNumber !== '') {
|
|
3270
|
+
tempOrder.shop_order_number = String(shopOrderNumber);
|
|
3271
|
+
}
|
|
3272
|
+
if (shopFullOrderNumber !== undefined && shopFullOrderNumber !== null && shopFullOrderNumber !== '') {
|
|
3273
|
+
tempOrder.shop_full_order_number = String(shopFullOrderNumber);
|
|
3274
|
+
}
|
|
3275
|
+
this.persistTempOrder();
|
|
3276
|
+
await this.saveDraft();
|
|
3277
|
+
return tempOrder;
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
/**
|
|
3281
|
+
* 提交当前 Sales tempOrder。
|
|
3282
|
+
*
|
|
3283
|
+
* smallTicketDataFlag 用于 B 端 checkout 小票数据返回。PaymentModal 在支付提交时
|
|
3284
|
+
* 应传 1,确保 /shop/order/sales/checkout 返回打印所需数据。
|
|
3285
|
+
*/
|
|
581
3286
|
async submitTempOrder(params) {
|
|
3287
|
+
return this.runSubmitTempOrder(params);
|
|
3288
|
+
}
|
|
3289
|
+
async submitTempOrderAsync(params) {
|
|
3290
|
+
return this.runSubmitTempOrder({
|
|
3291
|
+
...params,
|
|
3292
|
+
syncMode: true
|
|
3293
|
+
});
|
|
3294
|
+
}
|
|
3295
|
+
async runSubmitTempOrder(params) {
|
|
582
3296
|
const tempOrder = this.ensureTempOrder();
|
|
3297
|
+
const shouldConfirmPendingVoucherPayments = params?.confirmPendingVoucherPayments !== false;
|
|
3298
|
+
const hasPaymentOverride = params?.payments !== undefined;
|
|
3299
|
+
const preparedPaymentOverride = hasPaymentOverride ? this.prepareVoucherPaymentsForSubmit(params?.payments || [], shouldConfirmPendingVoucherPayments) : undefined;
|
|
3300
|
+
if (!hasPaymentOverride) {
|
|
3301
|
+
if (shouldConfirmPendingVoucherPayments) {
|
|
3302
|
+
this.confirmPendingVoucherPayments({
|
|
3303
|
+
persist: false,
|
|
3304
|
+
recalculateSummary: false
|
|
3305
|
+
});
|
|
3306
|
+
} else {
|
|
3307
|
+
this.discardPendingVoucherPayments({
|
|
3308
|
+
persist: false,
|
|
3309
|
+
recalculateSummary: false
|
|
3310
|
+
});
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
583
3313
|
this.persistTempOrder();
|
|
3314
|
+
const latestSummary = (await this.recalculateSummary({
|
|
3315
|
+
createIfMissing: true
|
|
3316
|
+
})) || this.store.summary || (0, _utils.createEmptySummary)();
|
|
584
3317
|
const effectiveCacheId = params?.cacheId ?? this.cacheId;
|
|
3318
|
+
const hasPaymentStatusOverride = params?.paymentStatus !== undefined;
|
|
3319
|
+
const hasSmallTicketDataFlagOverride = params?.smallTicketDataFlag !== undefined;
|
|
3320
|
+
const enhancePayload = hasPaymentOverride || hasPaymentStatusOverride || hasSmallTicketDataFlagOverride || params?.enhancePayload ? (payload, ctx) => {
|
|
3321
|
+
const nextPayload = {
|
|
3322
|
+
...payload,
|
|
3323
|
+
...(hasPaymentOverride ? {
|
|
3324
|
+
payments: preparedPaymentOverride || []
|
|
3325
|
+
} : {}),
|
|
3326
|
+
...(hasPaymentStatusOverride && !shouldConfirmPendingVoucherPayments ? {
|
|
3327
|
+
payment_status: params?.paymentStatus
|
|
3328
|
+
} : {}),
|
|
3329
|
+
...(hasSmallTicketDataFlagOverride ? {
|
|
3330
|
+
small_ticket_data_flag: params?.smallTicketDataFlag
|
|
3331
|
+
} : {})
|
|
3332
|
+
};
|
|
3333
|
+
const enhancedPayload = params?.enhancePayload ? params.enhancePayload(nextPayload, ctx) : nextPayload;
|
|
3334
|
+
return enhancedPayload;
|
|
3335
|
+
} : undefined;
|
|
3336
|
+
const submitSnapshot = this.getLastOrderSubmitSnapshot();
|
|
585
3337
|
const payload = (0, _utils.buildSubmitPayload)({
|
|
586
3338
|
tempOrder,
|
|
587
3339
|
cacheId: effectiveCacheId,
|
|
588
|
-
platform: params?.platform,
|
|
589
|
-
businessCode: params?.businessCode,
|
|
3340
|
+
platform: params?.platform || this.otherParams?.platform,
|
|
3341
|
+
businessCode: submitSnapshot.businessCode ?? params?.businessCode ?? this.otherParams?.businessCode ?? this.otherParams?.business_code,
|
|
590
3342
|
channel: params?.channel,
|
|
591
|
-
type: params?.type,
|
|
592
|
-
|
|
3343
|
+
type: submitSnapshot.type ?? params?.type,
|
|
3344
|
+
summary: latestSummary,
|
|
3345
|
+
request_unique_idempotency_token: params?.request_unique_idempotency_token,
|
|
3346
|
+
enhance: enhancePayload
|
|
593
3347
|
});
|
|
3348
|
+
if (params?.syncMode) {
|
|
3349
|
+
payload.sync_mode = true;
|
|
3350
|
+
}
|
|
3351
|
+
// TODO: 前端生成的时间现在是有问题的,后面要统一优化
|
|
3352
|
+
if (!payload.created_at) {
|
|
3353
|
+
payload.created_at = (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss');
|
|
3354
|
+
}
|
|
3355
|
+
await this.saveDraft();
|
|
594
3356
|
let result;
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
3357
|
+
try {
|
|
3358
|
+
this.logInfo('submitTempOrder calling sales checkout', {
|
|
3359
|
+
orderId: payload.order_id,
|
|
3360
|
+
externalSaleNumber: payload.external_sale_number,
|
|
3361
|
+
requestUniqueIdempotencyToken: payload.request_unique_idempotency_token,
|
|
3362
|
+
paymentStatus: payload.payment_status,
|
|
3363
|
+
paymentsCount: payload.payments?.length || 0,
|
|
3364
|
+
smallTicketDataFlag: payload.small_ticket_data_flag,
|
|
3365
|
+
syncMode: payload.sync_mode
|
|
603
3366
|
});
|
|
604
|
-
|
|
605
|
-
result = {
|
|
606
|
-
data: {
|
|
607
|
-
order_id: tempOrder.order_id
|
|
608
|
-
}
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
} else {
|
|
612
|
-
result = await this.submitScanOrder({
|
|
3367
|
+
result = await this.submitSalesOrder({
|
|
613
3368
|
query: payload
|
|
614
3369
|
});
|
|
3370
|
+
} catch (error) {
|
|
3371
|
+
const backendErrorResponse = this.extractSubmitErrorResponse(error);
|
|
3372
|
+
if (backendErrorResponse) {
|
|
3373
|
+
this.store.syncState = 'failed';
|
|
3374
|
+
this.logSubmitBackendRejected(backendErrorResponse, payload);
|
|
3375
|
+
return backendErrorResponse;
|
|
3376
|
+
}
|
|
3377
|
+
this.store.syncState = 'failed';
|
|
3378
|
+
this.logError('submitTempOrder failed', {
|
|
3379
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3380
|
+
orderId: payload.order_id,
|
|
3381
|
+
externalSaleNumber: payload.external_sale_number,
|
|
3382
|
+
paymentStatus: payload.payment_status,
|
|
3383
|
+
paymentsCount: payload.payments?.length || 0
|
|
3384
|
+
});
|
|
3385
|
+
throw error;
|
|
3386
|
+
}
|
|
3387
|
+
if (this.isSubmitResponseRejected(result)) {
|
|
3388
|
+
this.store.syncState = 'failed';
|
|
3389
|
+
this.logSubmitBackendRejected(result, payload);
|
|
3390
|
+
return result;
|
|
3391
|
+
}
|
|
3392
|
+
const submittedOrderId = this.extractOrderIdFromSubmitResult(result);
|
|
3393
|
+
this.logInfo('runSubmitTempOrder: 提交成功', {
|
|
3394
|
+
submittedOrderId,
|
|
3395
|
+
result,
|
|
3396
|
+
tempOrder: {
|
|
3397
|
+
...tempOrder,
|
|
3398
|
+
_extend: undefined
|
|
3399
|
+
}
|
|
3400
|
+
});
|
|
3401
|
+
if (submittedOrderId !== null && submittedOrderId !== undefined) {
|
|
3402
|
+
tempOrder.order_id = submittedOrderId;
|
|
3403
|
+
const resultRecord = result;
|
|
3404
|
+
await this.markLocalOrderSynced(submittedOrderId, resultRecord?.data || resultRecord || {});
|
|
3405
|
+
} else if (this.isLocalPendingSubmitResult(result)) {
|
|
3406
|
+
this.store.syncState = 'local';
|
|
3407
|
+
} else {
|
|
3408
|
+
this.store.syncState = 'submitted';
|
|
615
3409
|
}
|
|
616
3410
|
return result;
|
|
617
3411
|
}
|
|
3412
|
+
async markLocalOrderSynced(orderId, remoteOrder) {
|
|
3413
|
+
const tempOrder = this.ensureTempOrder();
|
|
3414
|
+
tempOrder.order_id = orderId;
|
|
3415
|
+
this.applyRemoteOrderNumbersToTempOrder(tempOrder, remoteOrder);
|
|
3416
|
+
this.store.lastOrderInfo = remoteOrder;
|
|
3417
|
+
this.store.syncState = 'submitted';
|
|
3418
|
+
this.persistTempOrder();
|
|
3419
|
+
await this.saveDraft();
|
|
3420
|
+
}
|
|
3421
|
+
applyRemoteOrderNumbersToTempOrder(tempOrder, remoteOrder) {
|
|
3422
|
+
const applyOrderNumber = key => {
|
|
3423
|
+
const value = remoteOrder?.[key];
|
|
3424
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
3425
|
+
tempOrder[key] = String(value);
|
|
3426
|
+
}
|
|
3427
|
+
};
|
|
3428
|
+
applyOrderNumber('order_number');
|
|
3429
|
+
applyOrderNumber('shop_order_number');
|
|
3430
|
+
applyOrderNumber('shop_full_order_number');
|
|
3431
|
+
}
|
|
3432
|
+
isSubmitResponseRejected(response) {
|
|
3433
|
+
const isErrorCode = response?.code !== undefined && response.code !== 200 && response.code !== '200';
|
|
3434
|
+
const isErrorStatus = response?.status === false || typeof response?.status === 'number' && response.status >= 400;
|
|
3435
|
+
const isErrorSuccess = response?.success === false;
|
|
3436
|
+
return isErrorCode || isErrorStatus || isErrorSuccess;
|
|
3437
|
+
}
|
|
3438
|
+
isLocalPendingSubmitResult(response) {
|
|
3439
|
+
const data = response?.data && typeof response.data === 'object' ? response.data : response;
|
|
3440
|
+
return Number(data?.need_sync) === 1;
|
|
3441
|
+
}
|
|
3442
|
+
isSubmitErrorResponse(response) {
|
|
3443
|
+
if (!response || typeof response !== 'object') return false;
|
|
3444
|
+
return this.isSubmitResponseRejected(response) || response.message !== undefined || response.msg !== undefined || response.error !== undefined;
|
|
3445
|
+
}
|
|
3446
|
+
extractSubmitErrorResponse(error) {
|
|
3447
|
+
const candidates = [error?.response?.data, error?.data, error?.response?.body, error?.body];
|
|
3448
|
+
return candidates.find(candidate => this.isSubmitErrorResponse(candidate)) || null;
|
|
3449
|
+
}
|
|
3450
|
+
logSubmitBackendRejected(response, payload) {
|
|
3451
|
+
this.logError('submitTempOrder backend rejected', {
|
|
3452
|
+
response,
|
|
3453
|
+
payload: {
|
|
3454
|
+
order_id: payload.order_id,
|
|
3455
|
+
external_sale_number: payload.external_sale_number,
|
|
3456
|
+
payment_status: payload.payment_status,
|
|
3457
|
+
paymentsCount: payload.payments?.length || 0
|
|
3458
|
+
}
|
|
3459
|
+
});
|
|
3460
|
+
}
|
|
3461
|
+
generateIdempotencyToken() {
|
|
3462
|
+
const tempOrder = this.ensureTempOrder();
|
|
3463
|
+
const token = this.buildPaymentSyncIdempotencyToken(tempOrder, tempOrder.payments);
|
|
3464
|
+
return `${token}_${Date.now()}`;
|
|
3465
|
+
}
|
|
3466
|
+
async syncPaymentsToOrder(params) {
|
|
3467
|
+
const tempOrder = this.ensureTempOrder();
|
|
3468
|
+
const mappedPayments = (0, _utils.mapPaymentItemsToOrderPayments)(params.payments || []);
|
|
3469
|
+
const shouldConfirmPendingVoucherPayments = params.confirmPendingVoucherPayments ?? params.submitWhenPaid === true;
|
|
3470
|
+
const effectivePayments = shouldConfirmPendingVoucherPayments ? this.confirmPendingVoucherPaymentsInList(mappedPayments) : params.confirmPendingVoucherPayments === false ? this.discardPendingVoucherPaymentsFromList(mappedPayments) : mappedPayments;
|
|
3471
|
+
const completion = this.getPaymentCompletion(effectivePayments);
|
|
3472
|
+
const orderPaidPaymentTotal = this.calculateOrderPaidPaymentTotal(effectivePayments);
|
|
3473
|
+
const paymentStatus = completion.isOrderFullyPaid ? params.paymentStatus || 'paid' : orderPaidPaymentTotal.gt(0) ? 'partially_paid' : 'payment_processing';
|
|
3474
|
+
tempOrder.payments = effectivePayments;
|
|
3475
|
+
tempOrder.payment_status = paymentStatus;
|
|
3476
|
+
this.persistTempOrder();
|
|
3477
|
+
await this.saveDraft();
|
|
3478
|
+
if (!params.submitWhenPaid) {
|
|
3479
|
+
return completion;
|
|
3480
|
+
}
|
|
3481
|
+
if (this.store.syncState === 'submitting') {
|
|
3482
|
+
return completion;
|
|
3483
|
+
}
|
|
3484
|
+
this.store.syncState = 'submitting';
|
|
3485
|
+
const paymentSyncIdempotencyToken = this.buildPaymentSyncIdempotencyToken(tempOrder, effectivePayments);
|
|
3486
|
+
const submitParams = {
|
|
3487
|
+
payments: effectivePayments,
|
|
3488
|
+
paymentStatus,
|
|
3489
|
+
smallTicketDataFlag: params.smallTicketDataFlag,
|
|
3490
|
+
businessCode: params.businessCode,
|
|
3491
|
+
channel: params.channel,
|
|
3492
|
+
confirmPendingVoucherPayments: true,
|
|
3493
|
+
enhancePayload: payload => {
|
|
3494
|
+
const nextPayload = {
|
|
3495
|
+
...payload,
|
|
3496
|
+
request_unique_idempotency_token: paymentSyncIdempotencyToken
|
|
3497
|
+
};
|
|
3498
|
+
return nextPayload;
|
|
3499
|
+
}
|
|
3500
|
+
};
|
|
3501
|
+
const submitResult = params.checkoutSyncTaskEnabled === false ? await this.submitTempOrderAsync(submitParams) : await this.submitTempOrder(submitParams);
|
|
3502
|
+
return {
|
|
3503
|
+
...completion,
|
|
3504
|
+
submitResult
|
|
3505
|
+
};
|
|
3506
|
+
}
|
|
618
3507
|
createOrder(params) {
|
|
619
3508
|
const order = {
|
|
620
3509
|
type: params?.type || 'appointment_booking',
|
|
@@ -642,6 +3531,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
642
3531
|
item._origin.duration = duration;
|
|
643
3532
|
item._origin.sub_type = durationType;
|
|
644
3533
|
}
|
|
3534
|
+
item._origin.sub_type = (0, _utils.normalizeBookingSubType)(item._origin);
|
|
645
3535
|
const discountList = (0, _utils.getAllDiscountList)(item);
|
|
646
3536
|
item._origin.product.discount_list = discountList;
|
|
647
3537
|
|
|
@@ -718,14 +3608,54 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
718
3608
|
});
|
|
719
3609
|
return this.request.post(fetchUrl, params);
|
|
720
3610
|
}
|
|
3611
|
+
async cancelOrder(params) {
|
|
3612
|
+
if (!Array.isArray(params.order_ids) || params.order_ids.length === 0) {
|
|
3613
|
+
throw new Error('取消订单失败:order_ids 不能为空');
|
|
3614
|
+
}
|
|
3615
|
+
const payload = {
|
|
3616
|
+
order_ids: params.order_ids,
|
|
3617
|
+
status: 'cancelled',
|
|
3618
|
+
change_schedule_event_appointment_status: 1,
|
|
3619
|
+
is_void: 1
|
|
3620
|
+
};
|
|
3621
|
+
this.logInfo('cancelOrder called', {
|
|
3622
|
+
url: '/order/update-status',
|
|
3623
|
+
method: 'PUT',
|
|
3624
|
+
orderIdsCount: params.order_ids.length
|
|
3625
|
+
});
|
|
3626
|
+
return this.request.put('/order/update-status', payload, {
|
|
3627
|
+
isShopApi: true
|
|
3628
|
+
});
|
|
3629
|
+
}
|
|
3630
|
+
async changeBookingStatus(params) {
|
|
3631
|
+
if (!params.booking_id) {
|
|
3632
|
+
throw new Error('变更预约状态失败:booking_id 不能为空');
|
|
3633
|
+
}
|
|
3634
|
+
if (!params.appointment_status) {
|
|
3635
|
+
throw new Error('变更预约状态失败:appointment_status 不能为空');
|
|
3636
|
+
}
|
|
3637
|
+
const url = `/schedule/booking/appointment-status/${params.booking_id}`;
|
|
3638
|
+
const payload = {
|
|
3639
|
+
appointment_status: params.appointment_status
|
|
3640
|
+
};
|
|
3641
|
+
this.logInfo('changeBookingStatus called', {
|
|
3642
|
+
url,
|
|
3643
|
+
method: 'PUT',
|
|
3644
|
+
bookingId: params.booking_id,
|
|
3645
|
+
appointmentStatus: params.appointment_status
|
|
3646
|
+
});
|
|
3647
|
+
return this.request.put(url, payload, {
|
|
3648
|
+
isShopApi: true
|
|
3649
|
+
});
|
|
3650
|
+
}
|
|
721
3651
|
|
|
722
3652
|
/**
|
|
723
3653
|
* Checkout 专用:创建订单到后端
|
|
724
|
-
*
|
|
3654
|
+
*
|
|
725
3655
|
* 专门为 Checkout 模块设计的订单创建方法,
|
|
726
|
-
*
|
|
3656
|
+
* 通过 OS Server 调用 checkout 接口创建订单
|
|
727
3657
|
* 接收已经处理好的订单数据,无需再处理购物车
|
|
728
|
-
*
|
|
3658
|
+
*
|
|
729
3659
|
* @param params Checkout 订单参数(已处理的订单数据)
|
|
730
3660
|
* @returns 后端返回的订单数据(包含订单ID等)
|
|
731
3661
|
*/
|
|
@@ -774,7 +3704,7 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
774
3704
|
}
|
|
775
3705
|
// 记录接口调用详情
|
|
776
3706
|
this.logInfo('Calling backend checkout API', {
|
|
777
|
-
url: '/order/checkout',
|
|
3707
|
+
url: '/shop/order/checkout',
|
|
778
3708
|
orderType: orderData.type,
|
|
779
3709
|
platform: orderData.platform,
|
|
780
3710
|
isDeposit: orderData.is_deposit,
|
|
@@ -796,9 +3726,10 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
796
3726
|
hasOrderId: !!orderData.order_id,
|
|
797
3727
|
smallTicketDataFlag: orderData.small_ticket_data_flag
|
|
798
3728
|
});
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
3729
|
+
const response = await this.request.post('/order/checkout', orderData, {
|
|
3730
|
+
osServer: true,
|
|
3731
|
+
customToast: () => {}
|
|
3732
|
+
});
|
|
802
3733
|
this.logInfo('Order API called successfully', {
|
|
803
3734
|
response
|
|
804
3735
|
});
|
|
@@ -811,44 +3742,446 @@ class OrderModule extends _BaseModule.BaseModule {
|
|
|
811
3742
|
throw error;
|
|
812
3743
|
}
|
|
813
3744
|
}
|
|
814
|
-
async
|
|
3745
|
+
async submitSalesOrder(params) {
|
|
815
3746
|
const {
|
|
816
3747
|
url,
|
|
817
3748
|
query
|
|
818
3749
|
} = params;
|
|
819
3750
|
const fetchUrl = url || '/order/sales/checkout';
|
|
820
3751
|
return this.request.post(fetchUrl, query, {
|
|
3752
|
+
osServer: true,
|
|
821
3753
|
customToast: () => {}
|
|
822
3754
|
});
|
|
823
3755
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
3756
|
+
|
|
3757
|
+
/**
|
|
3758
|
+
* 发送支付链接邮件。
|
|
3759
|
+
*
|
|
3760
|
+
* 调用前必须已经有远端 order_id。BaseSales 会在需要时先提交本地订单,
|
|
3761
|
+
* OrderModule 只负责校验 order_ids 并调用 /order/batch-email。
|
|
3762
|
+
*/
|
|
3763
|
+
async sendCustomerPayLink(params) {
|
|
3764
|
+
const orderIds = params.order_ids || params.orderIds || [];
|
|
3765
|
+
if (!orderIds.length) {
|
|
3766
|
+
throw new Error('发送支付链接前必须先提交本地订单并取得订单 ID');
|
|
3767
|
+
}
|
|
3768
|
+
if (!params.emails?.length) {
|
|
3769
|
+
throw new Error('发送支付链接需要至少一个邮箱');
|
|
3770
|
+
}
|
|
3771
|
+
return this.request.post('/order/batch-email', {
|
|
3772
|
+
order_ids: orderIds,
|
|
3773
|
+
notify_action: params.notify_action || 'order_payment_reminder',
|
|
3774
|
+
emails: params.emails
|
|
836
3775
|
});
|
|
837
3776
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
3777
|
+
async getSalesOrderByLookup(params) {
|
|
3778
|
+
const lookup = params.lookup === undefined || params.lookup === null ? '' : String(params.lookup).trim();
|
|
3779
|
+
if (!lookup) {
|
|
3780
|
+
throw new Error('加载销售详情需要 lookup');
|
|
3781
|
+
}
|
|
3782
|
+
// 如果 lookup 是external_sale_number,则forceRemote强制为 false
|
|
3783
|
+
if (lookup.startsWith('LOCAL_')) {
|
|
3784
|
+
params.forceRemote = false;
|
|
3785
|
+
}
|
|
3786
|
+
const res = await this.request.get(`/order/sales/${encodeURIComponent(lookup)}`, {
|
|
3787
|
+
with: ['products', 'bookings', 'payments', 'customer', 'summary', 'contacts_info'],
|
|
3788
|
+
...(params.forceRemote ? {
|
|
3789
|
+
forceRemote: true
|
|
3790
|
+
} : {})
|
|
3791
|
+
}, {
|
|
3792
|
+
osServer: true
|
|
844
3793
|
});
|
|
845
|
-
if (res.code === 200
|
|
846
|
-
this.store.
|
|
3794
|
+
if (res.code === 200) {
|
|
3795
|
+
this.store.lastOrderInfo = res.data;
|
|
847
3796
|
}
|
|
848
3797
|
return res;
|
|
849
3798
|
}
|
|
3799
|
+
|
|
3800
|
+
/**
|
|
3801
|
+
* 把 sqlite / 后端返回的订单记录直接恢复为 tempOrder。
|
|
3802
|
+
*
|
|
3803
|
+
* 数据源应尽量返回 tempOrder 所需结构;这里仅做 SDK 运行必须字段兜底,
|
|
3804
|
+
* 例如数组字段、商品行唯一值、价格 metadata、summary 派生状态和 _extend。
|
|
3805
|
+
*/
|
|
3806
|
+
async hydrateTempOrderFromRecord(record, options) {
|
|
3807
|
+
const sourceRaw = record && typeof record === 'object' && record.data && record.order_id == null ? record.data : record || {};
|
|
3808
|
+
const identityReadySource = this.seedAnonymousBookingUidsFromProducts(sourceRaw);
|
|
3809
|
+
const normalizedCollections = (0, _orderCollectionIdentity.normalizeOrderCollections)(identityReadySource);
|
|
3810
|
+
const raw = normalizedCollections.order;
|
|
3811
|
+
const hasIdentityChanges = Object.values(normalizedCollections.stats).some(stats => stats.backfilledUidCount > 0 || stats.collapsedDuplicateCount > 0 || stats.uidConflictCount > 0 || stats.anonymousRowCount > 0);
|
|
3812
|
+
if (hasIdentityChanges) {
|
|
3813
|
+
const identityLogMetadata = {
|
|
3814
|
+
collections: normalizedCollections.stats,
|
|
3815
|
+
uidRemapCount: normalizedCollections.uidRemaps.length
|
|
3816
|
+
};
|
|
3817
|
+
const hasIdentityWarnings = Object.values(normalizedCollections.stats).some(stats => stats.uidConflictCount > 0 || stats.anonymousRowCount > 0);
|
|
3818
|
+
if (hasIdentityWarnings) {
|
|
3819
|
+
this.logWarning('Normalized hydrated order collection identities', identityLogMetadata);
|
|
3820
|
+
} else {
|
|
3821
|
+
this.logInfo('Normalized hydrated order collection identities', identityLogMetadata);
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
const nextTempOrder = this.normalizeTempOrderForRuntime(raw, {
|
|
3825
|
+
makeEditMark: true
|
|
3826
|
+
});
|
|
3827
|
+
const rawSummary = raw.summary && typeof raw.summary === 'object' ? raw.summary : {};
|
|
3828
|
+
const summarySurcharges = Array.isArray(rawSummary.surcharges) ? rawSummary.surcharges.map(s => ({
|
|
3829
|
+
...(s || {})
|
|
3830
|
+
})) : (0, _lodashEs.cloneDeep)(nextTempOrder.surcharges || []);
|
|
3831
|
+
this.store.tempOrder = nextTempOrder;
|
|
3832
|
+
this.store.summary = {
|
|
3833
|
+
...(0, _utils.createEmptySummary)(),
|
|
3834
|
+
...rawSummary,
|
|
3835
|
+
surcharges: summarySurcharges
|
|
3836
|
+
};
|
|
3837
|
+
nextTempOrder._extend = {
|
|
3838
|
+
...(nextTempOrder._extend || {}),
|
|
3839
|
+
originalSalesSnapshot: {
|
|
3840
|
+
summary: (0, _lodashEs.cloneDeep)(this.store.summary),
|
|
3841
|
+
products: (0, _lodashEs.cloneDeep)(nextTempOrder.products || []),
|
|
3842
|
+
bookings: (0, _lodashEs.cloneDeep)(nextTempOrder.bookings || []),
|
|
3843
|
+
payments: (0, _lodashEs.cloneDeep)(nextTempOrder.payments || []),
|
|
3844
|
+
surcharges: (0, _lodashEs.cloneDeep)(nextTempOrder.surcharges || []),
|
|
3845
|
+
shop_discount: nextTempOrder.shop_discount || '0.00',
|
|
3846
|
+
productFingerprint: this.buildOrderProductsFingerprint(nextTempOrder.products || [])
|
|
3847
|
+
}
|
|
3848
|
+
};
|
|
3849
|
+
// lastOrderInfo carries the exact backend snapshot used by deposit/type recovery.
|
|
3850
|
+
// Runtime collection repair lives in tempOrder/originalSalesSnapshot and must not
|
|
3851
|
+
// rewrite this protocol snapshot as a side effect of hydration.
|
|
3852
|
+
this.store.lastOrderInfo = sourceRaw.lastOrderInfo || sourceRaw;
|
|
3853
|
+
const hydratedEditDiscounts = this.collectHydratedEditDiscounts(nextTempOrder.products);
|
|
3854
|
+
const currentDiscounts = typeof this.store.discount?.getDiscountList === 'function' ? this.store.discount.getDiscountList() || [] : [];
|
|
3855
|
+
const currentScanDiscounts = currentDiscounts.filter(discount => discount?.isScan);
|
|
3856
|
+
const hydratedDiscountIds = new Set(hydratedEditDiscounts.map(discount => discount?.id));
|
|
3857
|
+
const retainedScanDiscounts = currentScanDiscounts.filter(discount => !hydratedDiscountIds.has(discount?.id));
|
|
3858
|
+
const nextDiscounts = [...hydratedEditDiscounts, ...retainedScanDiscounts];
|
|
3859
|
+
const shouldSkipEmptyDiscountSync = nextDiscounts.length === 0 && currentDiscounts.length === 0;
|
|
3860
|
+
if (!shouldSkipEmptyDiscountSync) {
|
|
3861
|
+
OrderModule.populateSavedAmounts(nextTempOrder.products, nextDiscounts);
|
|
3862
|
+
await this.store.discount?.setOriginalDiscountList(nextDiscounts);
|
|
3863
|
+
await this.store.discount?.setDiscountList(nextDiscounts);
|
|
3864
|
+
}
|
|
3865
|
+
if (options?.recalcOnHydrate) {
|
|
3866
|
+
await this.recalculateSummary({
|
|
3867
|
+
createIfMissing: false
|
|
3868
|
+
});
|
|
3869
|
+
}
|
|
3870
|
+
this.persistTempOrder();
|
|
3871
|
+
return nextTempOrder;
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3874
|
+
/**
|
|
3875
|
+
* 兼容后端详情将 holder 放在 metadata.holder 的历史形态,统一补到运行时展示路径。
|
|
3876
|
+
*
|
|
3877
|
+
* @example
|
|
3878
|
+
* this.normalizeHydratedBookingHolder({ metadata: { holder: { form_record: [1] } } });
|
|
3879
|
+
*/
|
|
3880
|
+
normalizeHydratedBookingHolder(booking) {
|
|
3881
|
+
const next = {
|
|
3882
|
+
...(booking || {})
|
|
3883
|
+
};
|
|
3884
|
+
const metadata = next.metadata && typeof next.metadata === 'object' && !Array.isArray(next.metadata) ? {
|
|
3885
|
+
...next.metadata
|
|
3886
|
+
} : {};
|
|
3887
|
+
const metadataHolder = metadata.holder && typeof metadata.holder === 'object' && !Array.isArray(metadata.holder) ? metadata.holder : null;
|
|
3888
|
+
|
|
3889
|
+
// 编辑详情接口可能只返回 metadata.holder;UI 与提交链路统一读取 booking.holder。
|
|
3890
|
+
if (!next.holder && metadataHolder) {
|
|
3891
|
+
next.holder = {
|
|
3892
|
+
...metadataHolder
|
|
3893
|
+
};
|
|
3894
|
+
}
|
|
3895
|
+
const holder = next.holder && typeof next.holder === 'object' && !Array.isArray(next.holder) ? next.holder : null;
|
|
3896
|
+
const holderRecords = Array.isArray(holder?.form_record) ? holder.form_record.filter(id => id !== undefined && id !== null && id !== '') : [];
|
|
3897
|
+
const hasMetadataHolderId = metadata.holder_id !== undefined && metadata.holder_id !== null && metadata.holder_id !== '' && (!Array.isArray(metadata.holder_id) || metadata.holder_id.length > 0);
|
|
3898
|
+
|
|
3899
|
+
// 卡片和编辑抽屉会从 holder_id 进入展示/表单回显,缺失时由 holder.form_record 补齐。
|
|
3900
|
+
if (!hasMetadataHolderId && holderRecords.length > 0) {
|
|
3901
|
+
metadata.holder_id = holderRecords;
|
|
3902
|
+
}
|
|
3903
|
+
if (Object.keys(metadata).length > 0) {
|
|
3904
|
+
next.metadata = metadata;
|
|
3905
|
+
}
|
|
3906
|
+
return next;
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
/**
|
|
3910
|
+
* 匿名 booking 没有服务端行 ID 时,商品侧已有的 booking_uid 就是唯一可用的强引用。
|
|
3911
|
+
* 在统一规范化生成 UUID 前先复用它;已落库 booking 仍按 schedule_event_id 派生稳定 UID。
|
|
3912
|
+
*/
|
|
3913
|
+
seedAnonymousBookingUidsFromProducts(raw) {
|
|
3914
|
+
if (!raw || typeof raw !== 'object') return {};
|
|
3915
|
+
if (!Array.isArray(raw.bookings) || !Array.isArray(raw.products)) return raw;
|
|
3916
|
+
const bookingUidsByProductUid = new Map();
|
|
3917
|
+
for (const product of raw.products) {
|
|
3918
|
+
const productUid = (0, _orderCollectionIdentity.getOrderCollectionUid)('product', product);
|
|
3919
|
+
const bookingUid = product?.booking_uid || product?.metadata?.booking_uid;
|
|
3920
|
+
if (!productUid || bookingUid === undefined || bookingUid === null || bookingUid === '') {
|
|
3921
|
+
continue;
|
|
3922
|
+
}
|
|
3923
|
+
const candidates = bookingUidsByProductUid.get(productUid) || new Set();
|
|
3924
|
+
candidates.add(String(bookingUid));
|
|
3925
|
+
bookingUidsByProductUid.set(productUid, candidates);
|
|
3926
|
+
}
|
|
3927
|
+
if (bookingUidsByProductUid.size === 0) return raw;
|
|
3928
|
+
const anonymousBookingCountByProductUid = new Map();
|
|
3929
|
+
for (const booking of raw.bookings) {
|
|
3930
|
+
if ((0, _orderCollectionIdentity.getOrderCollectionPersistentId)('booking', booking) || (0, _orderCollectionIdentity.getOrderCollectionUid)('booking', booking)) {
|
|
3931
|
+
continue;
|
|
3932
|
+
}
|
|
3933
|
+
const productUid = booking?.product_uid || booking?.metadata?.product_uid;
|
|
3934
|
+
if (productUid === undefined || productUid === null || productUid === '') continue;
|
|
3935
|
+
const key = String(productUid);
|
|
3936
|
+
anonymousBookingCountByProductUid.set(key, (anonymousBookingCountByProductUid.get(key) || 0) + 1);
|
|
3937
|
+
}
|
|
3938
|
+
return {
|
|
3939
|
+
...raw,
|
|
3940
|
+
bookings: raw.bookings.map(booking => {
|
|
3941
|
+
if ((0, _orderCollectionIdentity.getOrderCollectionPersistentId)('booking', booking) || (0, _orderCollectionIdentity.getOrderCollectionUid)('booking', booking)) {
|
|
3942
|
+
return booking;
|
|
3943
|
+
}
|
|
3944
|
+
const productUid = booking?.product_uid || booking?.metadata?.product_uid;
|
|
3945
|
+
if (productUid === undefined || productUid === null || productUid === '') {
|
|
3946
|
+
return booking;
|
|
3947
|
+
}
|
|
3948
|
+
const productUidKey = String(productUid);
|
|
3949
|
+
// 多条匿名 booking 共用同一商品引用时身份仍然不明确,不能借商品字段猜测合并。
|
|
3950
|
+
if (anonymousBookingCountByProductUid.get(productUidKey) !== 1) {
|
|
3951
|
+
return booking;
|
|
3952
|
+
}
|
|
3953
|
+
const bookingUidCandidates = bookingUidsByProductUid.get(productUidKey);
|
|
3954
|
+
const bookingUid = bookingUidCandidates?.size === 1 ? [...bookingUidCandidates][0] : null;
|
|
3955
|
+
return bookingUid ? (0, _orderCollectionIdentity.setOrderCollectionUid)('booking', booking, bookingUid) : booking;
|
|
3956
|
+
})
|
|
3957
|
+
};
|
|
3958
|
+
}
|
|
3959
|
+
normalizeTempOrderForRuntime(raw, options) {
|
|
3960
|
+
const nextTempOrder = {
|
|
3961
|
+
...this.createDefaultTempOrderInstance(),
|
|
3962
|
+
...(raw || {})
|
|
3963
|
+
};
|
|
3964
|
+
// ES 列表详情使用 id 表示订单主键;OS 详情态统一依赖 order_id 驱动操作按钮。
|
|
3965
|
+
if ((nextTempOrder.order_id === undefined || nextTempOrder.order_id === null) && raw?.id !== undefined && raw?.id !== null) {
|
|
3966
|
+
nextTempOrder.order_id = raw.id;
|
|
3967
|
+
}
|
|
3968
|
+
delete nextTempOrder.summary;
|
|
3969
|
+
delete nextTempOrder.lastOrderInfo;
|
|
3970
|
+
delete nextTempOrder.discount_list;
|
|
3971
|
+
delete nextTempOrder.shop_id;
|
|
3972
|
+
delete nextTempOrder.create_date;
|
|
3973
|
+
delete nextTempOrder.total_amount;
|
|
3974
|
+
delete nextTempOrder.expect_amount;
|
|
3975
|
+
delete nextTempOrder.paid_amount;
|
|
3976
|
+
delete nextTempOrder.shop_note;
|
|
3977
|
+
delete nextTempOrder.start_time;
|
|
3978
|
+
delete nextTempOrder.tax_fee;
|
|
3979
|
+
delete nextTempOrder.total_refund_amount;
|
|
3980
|
+
delete nextTempOrder.holder_id;
|
|
3981
|
+
nextTempOrder.metadata = raw.metadata && typeof raw.metadata === 'object' ? {
|
|
3982
|
+
...raw.metadata
|
|
3983
|
+
} : {};
|
|
3984
|
+
nextTempOrder.holder = raw.holder && typeof raw.holder === 'object' ? {
|
|
3985
|
+
...raw.holder
|
|
3986
|
+
} : null;
|
|
3987
|
+
const rawProducts = Array.isArray(raw.products) ? raw.products : [];
|
|
3988
|
+
nextTempOrder.products = rawProducts.length > 0 ? rawProducts.map(p => this.normalizeHydratedOrderProduct(p, {
|
|
3989
|
+
makeEditMark: options?.makeEditMark
|
|
3990
|
+
})) : [];
|
|
3991
|
+
nextTempOrder.bookings = Array.isArray(raw.bookings) ? raw.bookings.map(b => {
|
|
3992
|
+
const booking = this.normalizeHydratedBookingHolder(b || {});
|
|
3993
|
+
return {
|
|
3994
|
+
...booking,
|
|
3995
|
+
is_all: (0, _utils.normalizeBookingIsAll)(booking),
|
|
3996
|
+
sub_type: (0, _utils.normalizeBookingSubType)(booking)
|
|
3997
|
+
};
|
|
3998
|
+
}) : [];
|
|
3999
|
+
nextTempOrder.payments = Array.isArray(raw.payments) ? raw.payments.map(p => ({
|
|
4000
|
+
...(p || {})
|
|
4001
|
+
})) : [];
|
|
4002
|
+
nextTempOrder.surcharges = Array.isArray(raw.surcharges) ? raw.surcharges.map(s => ({
|
|
4003
|
+
...(s || {})
|
|
4004
|
+
})) : [];
|
|
4005
|
+
nextTempOrder.relation_forms = Array.isArray(raw.relation_forms) ? raw.relation_forms.map(f => ({
|
|
4006
|
+
...(f || {})
|
|
4007
|
+
})) : [];
|
|
4008
|
+
nextTempOrder.contacts = Array.isArray(raw.contacts) ? raw.contacts.map(c => ({
|
|
4009
|
+
...(c || {})
|
|
4010
|
+
})) : [];
|
|
4011
|
+
nextTempOrder.contacts_info = raw.contacts_info && typeof raw.contacts_info === 'object' && !Array.isArray(raw.contacts_info) ? {
|
|
4012
|
+
...raw.contacts_info
|
|
4013
|
+
} : null;
|
|
4014
|
+
nextTempOrder._extend = raw._extend && typeof raw._extend === 'object' ? {
|
|
4015
|
+
...raw._extend,
|
|
4016
|
+
productsByUid: raw._extend.productsByUid || {}
|
|
4017
|
+
} : {
|
|
4018
|
+
productsByUid: {}
|
|
4019
|
+
};
|
|
4020
|
+
const tempOrderExtend = nextTempOrder._extend;
|
|
4021
|
+
const productsByUid = tempOrderExtend.productsByUid || {};
|
|
4022
|
+
for (const [index, product] of nextTempOrder.products.entries()) {
|
|
4023
|
+
const uid = product.metadata?.unique_identification_number;
|
|
4024
|
+
if (!uid) continue;
|
|
4025
|
+
const existed = productsByUid[uid] && typeof productsByUid[uid] === 'object' ? productsByUid[uid] : {};
|
|
4026
|
+
const rawProduct = rawProducts[index] && typeof rawProducts[index] === 'object' ? rawProducts[index] : product;
|
|
4027
|
+
const origin = existed.origin ?? rawProduct._origin ?? rawProduct.metadata?.origin ?? rawProduct;
|
|
4028
|
+
const originSnapshot = (0, _lodashEs.cloneDeep)(origin);
|
|
4029
|
+
const productOptionString = this.buildHydratedProductOptionString(rawProduct) || this.buildHydratedProductOptionString(product);
|
|
4030
|
+
if (productOptionString && originSnapshot && typeof originSnapshot === 'object') {
|
|
4031
|
+
if (originSnapshot.product_option_string === undefined) {
|
|
4032
|
+
originSnapshot.product_option_string = productOptionString;
|
|
4033
|
+
}
|
|
4034
|
+
if (originSnapshot.product_options_stirng === undefined) {
|
|
4035
|
+
originSnapshot.product_options_stirng = productOptionString;
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
productsByUid[uid] = {
|
|
4039
|
+
...existed,
|
|
4040
|
+
origin: originSnapshot
|
|
4041
|
+
};
|
|
4042
|
+
}
|
|
4043
|
+
tempOrderExtend.productsByUid = productsByUid;
|
|
4044
|
+
this.hydrateLinkedBookingIdentity(nextTempOrder);
|
|
4045
|
+
if (options?.ensureIdentity !== false) {
|
|
4046
|
+
this.ensureExternalSaleNumber(nextTempOrder);
|
|
4047
|
+
}
|
|
4048
|
+
nextTempOrder.vouchers = raw.vouchers || [];
|
|
4049
|
+
return nextTempOrder;
|
|
4050
|
+
}
|
|
4051
|
+
hydrateLinkedBookingIdentity(tempOrder) {
|
|
4052
|
+
const bookingUidByProductUid = new Map();
|
|
4053
|
+
const productByUid = new Map();
|
|
4054
|
+
(tempOrder.products || []).forEach(product => {
|
|
4055
|
+
const productUid = product?.metadata?.unique_identification_number || product?.unique_identification_number;
|
|
4056
|
+
if (productUid !== undefined && productUid !== null && String(productUid) !== '') {
|
|
4057
|
+
productByUid.set(String(productUid), product);
|
|
4058
|
+
}
|
|
4059
|
+
});
|
|
4060
|
+
(tempOrder.bookings || []).forEach(booking => {
|
|
4061
|
+
if (!booking || typeof booking !== 'object') return;
|
|
4062
|
+
if (!booking.metadata || typeof booking.metadata !== 'object') {
|
|
4063
|
+
booking.metadata = {};
|
|
4064
|
+
}
|
|
4065
|
+
const productUid = booking.product_uid || booking.metadata.product_uid;
|
|
4066
|
+
if (productUid === undefined || productUid === null || String(productUid) === '') return;
|
|
4067
|
+
const matchedProduct = productByUid.get(String(productUid));
|
|
4068
|
+
const existedProductBookingUid = matchedProduct?.booking_uid || matchedProduct?.metadata?.booking_uid;
|
|
4069
|
+
// 客户端订单详情可能缺少 booking 自身 uid,这里补齐后才能反写到商品行。
|
|
4070
|
+
const bookingUid = String(booking.metadata.unique_identification_number || existedProductBookingUid || (0, _utils.createUuidV4)());
|
|
4071
|
+
booking.metadata.unique_identification_number = bookingUid;
|
|
4072
|
+
bookingUidByProductUid.set(String(productUid), bookingUid);
|
|
4073
|
+
});
|
|
4074
|
+
(tempOrder.products || []).forEach(product => {
|
|
4075
|
+
const productUid = product?.metadata?.unique_identification_number || product?.unique_identification_number;
|
|
4076
|
+
if (productUid === undefined || productUid === null || String(productUid) === '') return;
|
|
4077
|
+
const bookingUid = bookingUidByProductUid.get(String(productUid));
|
|
4078
|
+
if (!bookingUid) return;
|
|
4079
|
+
if (!product.metadata || typeof product.metadata !== 'object') {
|
|
4080
|
+
product.metadata = {};
|
|
4081
|
+
}
|
|
4082
|
+
// booking 已经过统一身份规范化;以它为准同步商品侧旧引用,避免两侧 UID 断链。
|
|
4083
|
+
const linkedBookingUid = String(bookingUid);
|
|
4084
|
+
product.booking_uid = linkedBookingUid;
|
|
4085
|
+
product.metadata.booking_uid = linkedBookingUid;
|
|
4086
|
+
});
|
|
4087
|
+
this.syncLinkedBookingHoldersToProducts(tempOrder);
|
|
4088
|
+
}
|
|
4089
|
+
pickHydratedDisplayText(value) {
|
|
4090
|
+
if (value === undefined || value === null) return '';
|
|
4091
|
+
if (typeof value === 'string') return value;
|
|
4092
|
+
if (typeof value !== 'object') return String(value);
|
|
4093
|
+
return value.auto || value.original || value.en || value['zh-CN'] || value['zh-HK'] || '';
|
|
4094
|
+
}
|
|
4095
|
+
formatHydratedNamePair(group, item) {
|
|
4096
|
+
const groupText = this.pickHydratedDisplayText(group);
|
|
4097
|
+
const itemText = this.pickHydratedDisplayText(item);
|
|
4098
|
+
if (groupText && itemText) return `${groupText}:${itemText}`;
|
|
4099
|
+
return groupText || itemText;
|
|
4100
|
+
}
|
|
4101
|
+
buildHydratedProductOptionString(product) {
|
|
4102
|
+
const segments = [];
|
|
4103
|
+
const sku = product?.product_sku || {};
|
|
4104
|
+
if (Array.isArray(sku.variant)) {
|
|
4105
|
+
for (const variant of sku.variant) {
|
|
4106
|
+
const segment = this.formatHydratedNamePair(variant?.group, variant?.item);
|
|
4107
|
+
if (segment) segments.push(segment);
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
return segments.join(', ');
|
|
4111
|
+
}
|
|
4112
|
+
normalizeHydratedProductOptionItem(optionItem) {
|
|
4113
|
+
const rawNum = optionItem?.num ?? optionItem?.quantity ?? 1;
|
|
4114
|
+
const parsedNum = Number(rawNum);
|
|
4115
|
+
const rawPrice = optionItem?.add_price ?? optionItem?.price ?? 0;
|
|
4116
|
+
const parsedPrice = Number(rawPrice);
|
|
4117
|
+
const normalized = {
|
|
4118
|
+
...optionItem,
|
|
4119
|
+
option_group_item_id: optionItem?.option_group_item_id,
|
|
4120
|
+
option_group_id: optionItem?.option_group_id,
|
|
4121
|
+
num: Number.isFinite(parsedNum) && parsedNum > 0 ? parsedNum : 1,
|
|
4122
|
+
add_price: Number.isFinite(parsedPrice) ? parsedPrice : 0
|
|
4123
|
+
};
|
|
4124
|
+
delete normalized.price;
|
|
4125
|
+
if (optionItem?.name !== undefined) normalized.name = optionItem.name;
|
|
4126
|
+
if (optionItem?.group_name !== undefined) normalized.group_name = optionItem.group_name;
|
|
4127
|
+
if (optionItem?.id !== undefined) normalized.id = optionItem.id;
|
|
4128
|
+
return normalized;
|
|
4129
|
+
}
|
|
4130
|
+
normalizeHydratedOrderProduct(product, options) {
|
|
4131
|
+
let row = {
|
|
4132
|
+
...(product || {})
|
|
4133
|
+
};
|
|
4134
|
+
if (row.num === undefined && row.product_quantity !== undefined) {
|
|
4135
|
+
row.num = row.product_quantity;
|
|
4136
|
+
}
|
|
4137
|
+
const productSku = (0, _utils.ensureProductSku)(row);
|
|
4138
|
+
row.product_sku = {
|
|
4139
|
+
...productSku,
|
|
4140
|
+
option: (0, _utils.normalizeProductSkuOptions)(productSku.option.map(optionItem => this.normalizeHydratedProductOptionItem(optionItem || {})))
|
|
4141
|
+
};
|
|
4142
|
+
delete row[`product_${'option'}_item`];
|
|
4143
|
+
if (!Array.isArray(row.product_bundle)) row.product_bundle = [];
|
|
4144
|
+
if (!row.metadata || typeof row.metadata !== 'object') row.metadata = {};
|
|
4145
|
+
if (row.booking_uid && !row.metadata.booking_uid) {
|
|
4146
|
+
row.metadata.booking_uid = row.booking_uid;
|
|
4147
|
+
}
|
|
4148
|
+
row = (0, _bundleDiscountHydration.restoreHydratedBundleDiscounts)(row, _utils.normalizeOrderProductDiscountList);
|
|
4149
|
+
const existingIdentity = row.metadata.unique_identification_number || row.unique_identification_number;
|
|
4150
|
+
if (!existingIdentity) {
|
|
4151
|
+
row.metadata.unique_identification_number = (0, _utils.createUuidV4)();
|
|
4152
|
+
}
|
|
4153
|
+
// 从云端、本地数据库中获取到的商品数据,先打上 runtime 标记,提供给 UI 做 saved items.
|
|
4154
|
+
if (options?.makeEditMark) {
|
|
4155
|
+
row.metadata.is_edit_for_runtime = true;
|
|
4156
|
+
}
|
|
4157
|
+
const normalized = (0, _utils3.normalizeOrderProduct)(row);
|
|
4158
|
+
normalized.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalized.discount_list);
|
|
4159
|
+
const result = {
|
|
4160
|
+
...row,
|
|
4161
|
+
...normalized,
|
|
4162
|
+
product_sku: normalized.product_sku,
|
|
4163
|
+
metadata: {
|
|
4164
|
+
...row.metadata,
|
|
4165
|
+
...normalized.metadata
|
|
4166
|
+
}
|
|
4167
|
+
};
|
|
4168
|
+
if (result.metadata) delete result.metadata.price_schema_version;
|
|
4169
|
+
const restored = (0, _bundleDiscountHydration.restoreHydratedBundleDiscounts)(result, _utils.normalizeOrderProductDiscountList);
|
|
4170
|
+
return this.sanitizeOrderProductForTempOrder(restored);
|
|
4171
|
+
}
|
|
850
4172
|
getLastOrderInfo() {
|
|
851
|
-
return this.store?.
|
|
4173
|
+
return this.store?.lastOrderInfo;
|
|
4174
|
+
}
|
|
4175
|
+
async getOrderInfo(order_id) {
|
|
4176
|
+
const res = await this.request.get(`/order/sales/${order_id}`, {
|
|
4177
|
+
with: ['products', 'bookings']
|
|
4178
|
+
}, {
|
|
4179
|
+
osServer: true
|
|
4180
|
+
});
|
|
4181
|
+
return res;
|
|
4182
|
+
}
|
|
4183
|
+
getVouchers() {
|
|
4184
|
+
return this.store.tempOrder?.vouchers || [];
|
|
852
4185
|
}
|
|
853
4186
|
}
|
|
854
4187
|
exports.OrderModule = OrderModule;
|