@pisell/pisellos 0.0.567 → 0.0.569
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/index.js +6 -9
- package/dist/model/strategy/adapter/promotion/evaluator.js +3 -2
- package/dist/model/strategy/adapter/walletPass/evaluator.js +0 -2
- package/dist/model/strategy/adapter/walletPass/example.js +3 -26
- package/dist/model/strategy/adapter/walletPass/utils.js +7 -7
- package/dist/model/strategy/strategy-example.js +5 -19
- package/dist/modules/BaseModule.d.ts +2 -2
- package/dist/modules/BaseModule.js +4 -4
- package/dist/modules/BookingContext/index.d.ts +48 -0
- package/dist/modules/BookingContext/index.js +566 -0
- package/dist/modules/BookingContext/types.d.ts +102 -0
- package/dist/modules/BookingContext/types.js +51 -0
- package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.d.ts +16 -0
- package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +302 -0
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +8 -0
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +149 -0
- package/dist/modules/BookingContext/utils/cacheItemToBookingInput.d.ts +76 -0
- package/dist/modules/BookingContext/utils/cacheItemToBookingInput.js +249 -0
- package/dist/modules/BookingContext/utils/flexible.d.ts +28 -0
- package/dist/modules/BookingContext/utils/flexible.js +56 -0
- package/dist/modules/BookingContext/utils/formatResourceList.d.ts +8 -0
- package/dist/modules/BookingContext/utils/formatResourceList.js +38 -0
- package/dist/modules/BookingContext/utils/index.d.ts +11 -0
- package/dist/modules/BookingContext/utils/index.js +11 -0
- package/dist/modules/BookingContext/utils/noResource.d.ts +13 -0
- package/dist/modules/BookingContext/utils/noResource.js +77 -0
- package/dist/modules/BookingContext/utils/orderLineDisplay.d.ts +1 -0
- package/dist/modules/BookingContext/utils/orderLineDisplay.js +36 -0
- package/dist/modules/BookingContext/utils/productExtend.d.ts +72 -0
- package/dist/modules/BookingContext/utils/productExtend.js +339 -0
- package/dist/modules/BookingContext/utils/resourceErrors.d.ts +41 -0
- package/dist/modules/BookingContext/utils/resourceErrors.js +74 -0
- package/dist/modules/BookingContext/utils/resourceSelection.d.ts +7 -0
- package/dist/modules/BookingContext/utils/resourceSelection.js +24 -0
- package/dist/modules/BookingContext/utils/resources.d.ts +80 -0
- package/dist/modules/BookingContext/utils/resources.js +486 -0
- package/dist/modules/BookingContext/utils/serviceTimes.d.ts +55 -0
- package/dist/modules/BookingContext/utils/serviceTimes.js +151 -0
- package/dist/modules/BookingContext/utils/timeSlices.d.ts +42 -0
- package/dist/modules/BookingContext/utils/timeSlices.js +100 -0
- package/dist/modules/Cart/index.js +0 -3
- package/dist/modules/Cart/utils/cartProduct.js +26 -6
- package/dist/modules/Cart/utils/changePrice.js +27 -20
- package/dist/modules/Customer/index.d.ts +20 -0
- package/dist/modules/Customer/index.js +172 -83
- package/dist/modules/Customer/types.d.ts +2 -0
- package/dist/modules/Date/index.js +1 -5
- package/dist/modules/Discount/index.d.ts +2 -1
- package/dist/modules/Discount/index.js +37 -12
- package/dist/modules/Discount/types.d.ts +1 -0
- package/dist/modules/OpenData/index.d.ts +8 -0
- package/dist/modules/OpenData/index.js +35 -17
- package/dist/modules/Order/index.d.ts +449 -24
- package/dist/modules/Order/index.js +5437 -608
- package/dist/modules/Order/types.d.ts +693 -88
- package/dist/modules/Order/types.js +68 -1
- package/dist/modules/Order/utils/bundleDiscountHydration.d.ts +5 -0
- package/dist/modules/Order/utils/bundleDiscountHydration.js +144 -0
- package/dist/modules/Order/utils/manualProductDiscount.d.ts +106 -0
- package/dist/modules/Order/utils/manualProductDiscount.js +210 -0
- package/dist/modules/Order/utils/orderCollectionIdentity.d.ts +53 -0
- package/dist/modules/Order/utils/orderCollectionIdentity.js +405 -0
- package/dist/modules/Order/utils.d.ts +167 -18
- package/dist/modules/Order/utils.js +960 -165
- package/dist/modules/Payment/index.d.ts +10 -9
- package/dist/modules/Payment/index.js +508 -874
- package/dist/modules/Payment/types.d.ts +40 -16
- package/dist/modules/Payment/utils.d.ts +1 -1
- package/dist/modules/Payment/utils.js +4 -3
- package/dist/modules/Payment/walletpass.js +48 -34
- package/dist/modules/Product/types.d.ts +29 -7
- package/dist/modules/ProductList/index.d.ts +17 -13
- package/dist/modules/ProductList/index.js +184 -74
- package/dist/modules/ProductList/types.d.ts +17 -0
- package/dist/modules/Quotation/index.d.ts +22 -2
- package/dist/modules/Quotation/index.js +108 -42
- package/dist/modules/Quotation/types.d.ts +19 -0
- package/dist/modules/ResourcePlanner/index.d.ts +24 -0
- package/dist/modules/ResourcePlanner/index.js +181 -0
- package/dist/modules/ResourcePlanner/planner.d.ts +14 -0
- package/dist/modules/ResourcePlanner/planner.js +1069 -0
- package/dist/modules/ResourcePlanner/types.d.ts +227 -0
- package/dist/modules/ResourcePlanner/types.js +1 -0
- package/dist/modules/Rules/index.d.ts +8 -10
- package/dist/modules/Rules/index.js +219 -114
- package/dist/modules/Rules/types.d.ts +14 -3
- package/dist/modules/SalesSummary/index.d.ts +23 -26
- package/dist/modules/SalesSummary/index.js +152 -46
- package/dist/modules/SalesSummary/types.d.ts +9 -1
- package/dist/modules/SalesSummary/utils.d.ts +3 -22
- package/dist/modules/SalesSummary/utils.js +803 -163
- package/dist/modules/Schedule/getDateIsInSchedule.js +14 -7
- package/dist/modules/Schedule/index.d.ts +18 -5
- package/dist/modules/Schedule/index.js +78 -24
- package/dist/modules/Schedule/types.d.ts +2 -0
- package/dist/modules/Schedule/utils.js +0 -1
- package/dist/modules/Step/index.js +0 -1
- package/dist/modules/Summary/index.js +4 -3
- package/dist/modules/SurchargeList/index.d.ts +18 -0
- package/dist/modules/SurchargeList/index.js +185 -0
- package/dist/modules/SurchargeList/types.d.ts +15 -0
- package/dist/modules/SurchargeList/types.js +1 -0
- package/dist/modules/index.d.ts +3 -0
- package/dist/modules/index.js +4 -1
- package/dist/plugins/app.d.ts +2 -0
- package/dist/plugins/request.d.ts +1 -0
- package/dist/plugins/request.js +1 -4
- package/dist/plugins/window.js +2 -6
- package/dist/server/index.d.ts +462 -8
- package/dist/server/index.js +6137 -714
- package/dist/server/modules/floor-plan/index.d.ts +39 -0
- package/dist/server/modules/floor-plan/index.js +595 -0
- package/dist/server/modules/floor-plan/types.d.ts +43 -0
- package/dist/server/modules/floor-plan/types.js +13 -0
- package/dist/server/modules/index.d.ts +7 -2
- package/dist/server/modules/index.js +6 -0
- package/dist/server/modules/menu/index.js +32 -41
- package/dist/server/modules/order/index.d.ts +207 -14
- package/dist/server/modules/order/index.js +2326 -379
- package/dist/server/modules/order/types.d.ts +76 -5
- package/dist/server/modules/order/types.js +14 -1
- package/dist/server/modules/order/utils/filterBookings.d.ts +7 -1
- package/dist/server/modules/order/utils/filterBookings.js +118 -11
- package/dist/server/modules/order/utils/filterOrders.js +242 -14
- package/dist/server/modules/payment/index.d.ts +28 -0
- package/dist/server/modules/payment/index.js +305 -0
- package/dist/server/modules/payment/types.d.ts +9 -0
- package/dist/server/modules/payment/types.js +1 -0
- package/dist/server/modules/products/index.d.ts +105 -14
- package/dist/server/modules/products/index.js +1397 -540
- package/dist/server/modules/products/types.d.ts +34 -1
- package/dist/server/modules/products/types.js +4 -0
- package/dist/server/modules/quotation/index.d.ts +39 -0
- package/dist/server/modules/quotation/index.js +289 -86
- package/dist/server/modules/resource/index.d.ts +2 -3
- package/dist/server/modules/resource/index.js +105 -84
- package/dist/server/modules/schedule/index.d.ts +6 -4
- package/dist/server/modules/schedule/index.js +137 -114
- package/dist/server/test.json +713 -0
- package/dist/server/types.d.ts +2 -0
- package/dist/server/utils/product.js +2 -3
- package/dist/server/utils/small-ticket.d.ts +71 -0
- package/dist/server/utils/small-ticket.js +963 -0
- package/dist/solution/BaseSales/index.d.ts +378 -0
- package/dist/solution/BaseSales/index.js +4006 -0
- package/dist/solution/BaseSales/types.d.ts +394 -0
- package/dist/solution/BaseSales/types.js +40 -0
- package/dist/solution/BaseSales/utils/cartPromotion.d.ts +287 -0
- package/dist/solution/BaseSales/utils/cartPromotion.js +1450 -0
- package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +144 -0
- package/dist/solution/BaseSales/utils/parseSalesResponse.js +157 -0
- package/dist/solution/BaseSales/utils/quotationPrice.d.ts +18 -0
- package/dist/solution/BaseSales/utils/quotationPrice.js +250 -0
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +63 -0
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +218 -0
- package/dist/solution/BaseSales/utils.d.ts +40 -0
- package/dist/solution/BaseSales/utils.js +263 -0
- package/dist/solution/BookingByStep/index.js +7 -59
- package/dist/solution/BookingByStep/utils/capacity.js +1 -11
- package/dist/solution/BookingByStep/utils/resources.js +1 -3
- package/dist/solution/BookingByStep/utils/timeslots.js +2 -43
- package/dist/solution/BookingTicket/index.d.ts +393 -24
- package/dist/solution/BookingTicket/index.js +2506 -445
- package/dist/solution/BookingTicket/types.d.ts +191 -6
- package/dist/solution/BookingTicket/types.js +83 -10
- package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +117 -0
- package/dist/solution/BookingTicket/utils/addProductDecision.js +346 -0
- package/dist/solution/BookingTicket/utils/addTimeAvailability.d.ts +35 -0
- package/dist/solution/BookingTicket/utils/addTimeAvailability.js +95 -0
- package/dist/solution/BookingTicket/utils/bookingStatus.d.ts +77 -0
- package/dist/solution/BookingTicket/utils/bookingStatus.js +136 -0
- package/dist/solution/BookingTicket/utils/cartView.d.ts +29 -0
- package/dist/solution/BookingTicket/utils/cartView.js +209 -0
- package/dist/solution/BookingTicket/utils/exampleData.d.ts +123 -0
- package/dist/solution/BookingTicket/utils/exampleData.js +183 -0
- package/dist/solution/BookingTicket/utils/orderCustomer.d.ts +4 -0
- package/dist/solution/BookingTicket/utils/orderCustomer.js +31 -0
- package/dist/solution/BookingTicket/utils/resolveBestAddTimePlan.d.ts +185 -0
- package/dist/solution/BookingTicket/utils/resolveBestAddTimePlan.js +438 -0
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +36 -0
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +400 -0
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.d.ts +4 -0
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +41 -1
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +32 -0
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +56 -0
- package/dist/solution/BookingTicket/utils/scan/handleScan.d.ts +3 -1
- package/dist/solution/BookingTicket/utils/scan/handleScan.js +36 -34
- package/dist/solution/BookingTicket/utils/scan/index.d.ts +5 -0
- package/dist/solution/BookingTicket/utils/scan/index.js +26 -18
- package/dist/solution/BookingTicket/utils/scan/scanProductValues.d.ts +21 -0
- package/dist/solution/BookingTicket/utils/scan/scanProductValues.js +150 -0
- package/dist/solution/BookingTicket/utils/sessionCatalogStale.d.ts +23 -0
- package/dist/solution/BookingTicket/utils/sessionCatalogStale.js +78 -0
- package/dist/solution/BuyTickets/index.js +9 -12
- package/dist/solution/Checkout/index.d.ts +1 -0
- package/dist/solution/Checkout/index.js +199 -277
- package/dist/solution/Checkout/utils/index.js +8 -20
- package/dist/solution/RegisterAndLogin/config.js +2 -10
- package/dist/solution/RegisterAndLogin/index.js +30 -76
- package/dist/solution/Sales/index.d.ts +50 -5
- package/dist/solution/Sales/index.js +484 -92
- package/dist/solution/Sales/types.d.ts +26 -2
- package/dist/solution/ScanOrder/index.d.ts +27 -21
- package/dist/solution/ScanOrder/index.js +509 -454
- package/dist/solution/ScanOrder/types.d.ts +33 -11
- package/dist/solution/ScanOrder/types.js +4 -5
- package/dist/solution/ScanOrder/utils.d.ts +4 -1
- package/dist/solution/ScanOrder/utils.js +109 -34
- package/dist/solution/ShopDiscount/index.js +6 -11
- package/dist/solution/UnifiedBookingSales/index.d.ts +183 -0
- package/dist/solution/UnifiedBookingSales/index.js +1871 -0
- package/dist/solution/UnifiedBookingSales/types.d.ts +143 -0
- package/dist/solution/UnifiedBookingSales/types.js +11 -0
- package/dist/solution/VenueBooking/index.d.ts +34 -20
- package/dist/solution/VenueBooking/index.js +630 -505
- package/dist/solution/VenueBooking/sales-section-4-annotated.json +0 -2
- package/dist/solution/VenueBooking/utils/slotMerge.d.ts +6 -0
- package/dist/solution/VenueBooking/utils/slotMerge.js +9 -3
- package/dist/solution/index.d.ts +2 -0
- package/dist/solution/index.js +3 -1
- package/dist/types/index.d.ts +11 -1
- package/dist/utils/task.js +15 -18
- package/dist/utils/watch.js +0 -1
- package/lib/apis/picoding.js +2 -0
- package/lib/core/index.js +130 -131
- package/lib/effects/index.js +46 -57
- package/lib/index.js +92 -53
- package/lib/model/index.js +14 -21
- package/lib/model/strategy/adapter/dataVariant/adapter.js +38 -50
- package/lib/model/strategy/adapter/dataVariant/evaluator.js +162 -236
- package/lib/model/strategy/adapter/dataVariant/examples.js +229 -237
- package/lib/model/strategy/adapter/dataVariant/index.js +39 -34
- package/lib/model/strategy/adapter/dataVariant/type.js +57 -28
- package/lib/model/strategy/adapter/index.js +100 -64
- package/lib/model/strategy/adapter/itemRule/adapter.js +156 -144
- package/lib/model/strategy/adapter/itemRule/evaluator.js +36 -48
- package/lib/model/strategy/adapter/itemRule/examples.js +295 -225
- package/lib/model/strategy/adapter/itemRule/index.js +83 -57
- package/lib/model/strategy/adapter/itemRule/type.js +97 -36
- package/lib/model/strategy/adapter/promotion/adapter.js +84 -63
- package/lib/model/strategy/adapter/promotion/evaluator.js +383 -232
- package/lib/model/strategy/adapter/promotion/examples.js +139 -159
- package/lib/model/strategy/adapter/promotion/index.js +1 -49
- package/lib/model/strategy/adapter/promotion/type.js +199 -35
- package/lib/model/strategy/adapter/type.js +4 -16
- package/lib/model/strategy/adapter/walletPass/evaluator.js +180 -152
- package/lib/model/strategy/adapter/walletPass/example.js +217 -190
- package/lib/model/strategy/adapter/walletPass/index.js +75 -51
- package/lib/model/strategy/adapter/walletPass/locales.js +36 -58
- package/lib/model/strategy/adapter/walletPass/type.js +4 -16
- package/lib/model/strategy/adapter/walletPass/utils.js +654 -402
- package/lib/model/strategy/engine.js +270 -168
- package/lib/model/strategy/index.js +49 -34
- package/lib/model/strategy/strategy-example.js +243 -239
- package/lib/model/strategy/type.js +100 -40
- package/lib/modules/Account/index.js +39 -53
- package/lib/modules/Account/types.js +20 -33
- package/lib/modules/AccountList/index.js +116 -154
- package/lib/modules/AccountList/types.js +30 -31
- package/lib/modules/AccountList/utils.js +18 -30
- package/lib/modules/BaseModule.d.ts +2 -2
- package/lib/modules/BaseModule.js +26 -49
- package/lib/modules/BookingContext/index.d.ts +48 -0
- package/lib/modules/BookingContext/index.js +390 -0
- package/lib/modules/BookingContext/types.d.ts +102 -0
- package/lib/modules/BookingContext/types.js +48 -0
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.d.ts +16 -0
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +286 -0
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +8 -0
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +148 -0
- package/lib/modules/BookingContext/utils/cacheItemToBookingInput.d.ts +76 -0
- package/lib/modules/BookingContext/utils/cacheItemToBookingInput.js +242 -0
- package/lib/modules/BookingContext/utils/flexible.d.ts +28 -0
- package/lib/modules/BookingContext/utils/flexible.js +63 -0
- package/lib/modules/BookingContext/utils/formatResourceList.d.ts +8 -0
- package/lib/modules/BookingContext/utils/formatResourceList.js +35 -0
- package/lib/modules/BookingContext/utils/index.d.ts +11 -0
- package/lib/modules/BookingContext/utils/index.js +126 -0
- package/lib/modules/BookingContext/utils/noResource.d.ts +13 -0
- package/lib/modules/BookingContext/utils/noResource.js +78 -0
- package/lib/modules/BookingContext/utils/orderLineDisplay.d.ts +1 -0
- package/lib/modules/BookingContext/utils/orderLineDisplay.js +29 -0
- package/lib/modules/BookingContext/utils/productExtend.d.ts +72 -0
- package/lib/modules/BookingContext/utils/productExtend.js +330 -0
- package/lib/modules/BookingContext/utils/resourceErrors.d.ts +41 -0
- package/lib/modules/BookingContext/utils/resourceErrors.js +80 -0
- package/lib/modules/BookingContext/utils/resourceSelection.d.ts +7 -0
- package/lib/modules/BookingContext/utils/resourceSelection.js +24 -0
- package/lib/modules/BookingContext/utils/resources.d.ts +80 -0
- package/lib/modules/BookingContext/utils/resources.js +419 -0
- package/lib/modules/BookingContext/utils/serviceTimes.d.ts +55 -0
- package/lib/modules/BookingContext/utils/serviceTimes.js +161 -0
- package/lib/modules/BookingContext/utils/timeSlices.d.ts +42 -0
- package/lib/modules/BookingContext/utils/timeSlices.js +114 -0
- package/lib/modules/Cart/index.js +170 -124
- package/lib/modules/Cart/types.js +46 -51
- package/lib/modules/Cart/utils/cartAccount.js +40 -39
- package/lib/modules/Cart/utils/cartDate.js +56 -61
- package/lib/modules/Cart/utils/cartDiscount.js +28 -33
- package/lib/modules/Cart/utils/cartNote.js +27 -32
- package/lib/modules/Cart/utils/cartProduct.js +331 -244
- package/lib/modules/Cart/utils/cartRelationForms.js +29 -35
- package/lib/modules/Cart/utils/cartResource.js +72 -78
- package/lib/modules/Cart/utils/changePrice.js +37 -67
- package/lib/modules/Cart/utils/index.js +106 -48
- package/lib/modules/Customer/constants.js +13 -34
- package/lib/modules/Customer/index.d.ts +20 -0
- package/lib/modules/Customer/index.js +270 -154
- package/lib/modules/Customer/types.d.ts +2 -0
- package/lib/modules/Customer/types.js +38 -35
- package/lib/modules/Date/index.js +116 -115
- package/lib/modules/Date/types.js +16 -28
- package/lib/modules/Date/utils.js +174 -123
- package/lib/modules/Discount/index.d.ts +2 -1
- package/lib/modules/Discount/index.js +133 -113
- package/lib/modules/Discount/types.d.ts +1 -0
- package/lib/modules/Discount/types.js +9 -31
- package/lib/modules/Guests/index.js +30 -56
- package/lib/modules/Guests/types.js +23 -33
- package/lib/modules/Holder/index.js +209 -189
- package/lib/modules/Holder/types.js +4 -16
- package/lib/modules/Holder/utils.js +20 -49
- package/lib/modules/OpenData/index.d.ts +8 -0
- package/lib/modules/OpenData/index.js +81 -74
- package/lib/modules/OpenData/types.js +4 -16
- package/lib/modules/OpenData/utils.js +44 -78
- package/lib/modules/Order/index.d.ts +449 -24
- package/lib/modules/Order/index.js +3834 -489
- package/lib/modules/Order/types.d.ts +693 -88
- package/lib/modules/Order/types.js +78 -33
- package/lib/modules/Order/utils/bundleDiscountHydration.d.ts +5 -0
- package/lib/modules/Order/utils/bundleDiscountHydration.js +106 -0
- package/lib/modules/Order/utils/manualProductDiscount.d.ts +106 -0
- package/lib/modules/Order/utils/manualProductDiscount.js +207 -0
- package/lib/modules/Order/utils/orderCollectionIdentity.d.ts +53 -0
- package/lib/modules/Order/utils/orderCollectionIdentity.js +353 -0
- package/lib/modules/Order/utils.d.ts +167 -18
- package/lib/modules/Order/utils.js +1247 -345
- package/lib/modules/Payment/cash.js +20 -33
- package/lib/modules/Payment/cashRecommendationAlgorithm.js +145 -76
- package/lib/modules/Payment/eftpos.js +16 -30
- package/lib/modules/Payment/index.d.ts +10 -9
- package/lib/modules/Payment/index.js +542 -502
- package/lib/modules/Payment/mx51.js +1 -0
- package/lib/modules/Payment/types.d.ts +40 -16
- package/lib/modules/Payment/types.js +216 -107
- package/lib/modules/Payment/utils.d.ts +1 -1
- package/lib/modules/Payment/utils.js +53 -49
- package/lib/modules/Payment/walletpass.js +231 -203
- package/lib/modules/Product/index.js +51 -46
- package/lib/modules/Product/types.d.ts +29 -7
- package/lib/modules/Product/types.js +4 -16
- package/lib/modules/Product/utils.js +32 -33
- package/lib/modules/ProductList/index.d.ts +17 -13
- package/lib/modules/ProductList/index.js +160 -118
- package/lib/modules/ProductList/types.d.ts +17 -0
- package/lib/modules/ProductList/types.js +9 -31
- package/lib/modules/Quotation/index.d.ts +22 -2
- package/lib/modules/Quotation/index.js +123 -89
- package/lib/modules/Quotation/types.d.ts +19 -0
- package/lib/modules/Quotation/types.js +4 -16
- package/lib/modules/Resource/index.js +27 -59
- package/lib/modules/Resource/types.js +22 -32
- package/lib/modules/Resource/utils.js +30 -38
- package/lib/modules/ResourcePlanner/index.d.ts +24 -0
- package/lib/modules/ResourcePlanner/index.js +147 -0
- package/lib/modules/ResourcePlanner/planner.d.ts +14 -0
- package/lib/modules/ResourcePlanner/planner.js +895 -0
- package/lib/modules/ResourcePlanner/types.d.ts +227 -0
- package/lib/modules/ResourcePlanner/types.js +5 -0
- package/lib/modules/Rules/index.d.ts +8 -10
- package/lib/modules/Rules/index.js +1286 -894
- package/lib/modules/Rules/types.d.ts +14 -3
- package/lib/modules/Rules/types.js +17 -40
- package/lib/modules/SalesSummary/index.d.ts +23 -26
- package/lib/modules/SalesSummary/index.js +130 -71
- package/lib/modules/SalesSummary/types.d.ts +9 -1
- package/lib/modules/SalesSummary/types.js +4 -16
- package/lib/modules/SalesSummary/utils.d.ts +3 -22
- package/lib/modules/SalesSummary/utils.js +738 -250
- package/lib/modules/ScanOrderLogger/index.js +59 -79
- package/lib/modules/ScanOrderLogger/providers/feishu.js +54 -78
- package/lib/modules/ScanOrderLogger/providers/grafana.js +13 -36
- package/lib/modules/ScanOrderLogger/types.js +4 -16
- package/lib/modules/Schedule/getDateIsInSchedule.js +345 -171
- package/lib/modules/Schedule/index.d.ts +18 -5
- package/lib/modules/Schedule/index.js +139 -114
- package/lib/modules/Schedule/type.js +4 -16
- package/lib/modules/Schedule/types.d.ts +2 -0
- package/lib/modules/Schedule/types.js +4 -16
- package/lib/modules/Schedule/utils.js +433 -291
- package/lib/modules/Step/index.js +40 -52
- package/lib/modules/Step/tyeps.js +4 -16
- package/lib/modules/Summary/index.js +66 -70
- package/lib/modules/Summary/types.js +4 -16
- package/lib/modules/Summary/utils.js +782 -429
- package/lib/modules/SurchargeList/index.d.ts +18 -0
- package/lib/modules/SurchargeList/index.js +95 -0
- package/lib/modules/SurchargeList/types.d.ts +15 -0
- package/lib/modules/SurchargeList/types.js +5 -0
- package/lib/modules/index.d.ts +3 -0
- package/lib/modules/index.js +245 -57
- package/lib/plugins/app.d.ts +2 -0
- package/lib/plugins/app.js +4 -16
- package/lib/plugins/index.js +36 -25
- package/lib/plugins/request.d.ts +1 -0
- package/lib/plugins/request.js +137 -126
- package/lib/plugins/shopStore.js +4 -16
- package/lib/plugins/user.js +4 -16
- package/lib/plugins/window.js +171 -179
- package/lib/server/index.d.ts +462 -8
- package/lib/server/index.js +4668 -713
- package/lib/server/modules/floor-plan/index.d.ts +39 -0
- package/lib/server/modules/floor-plan/index.js +306 -0
- package/lib/server/modules/floor-plan/types.d.ts +43 -0
- package/lib/server/modules/floor-plan/types.js +19 -0
- package/lib/server/modules/index.d.ts +7 -2
- package/lib/server/modules/index.js +106 -59
- package/lib/server/modules/menu/index.js +146 -122
- package/lib/server/modules/menu/types.js +18 -31
- package/lib/server/modules/order/index.d.ts +207 -14
- package/lib/server/modules/order/index.js +1463 -316
- package/lib/server/modules/order/types.d.ts +76 -5
- package/lib/server/modules/order/types.js +117 -32
- package/lib/server/modules/order/utils/filterBookings.d.ts +7 -1
- package/lib/server/modules/order/utils/filterBookings.js +288 -146
- package/lib/server/modules/order/utils/filterOrders.js +318 -64
- package/lib/server/modules/payment/index.d.ts +28 -0
- package/lib/server/modules/payment/index.js +168 -0
- package/lib/server/modules/payment/types.d.ts +9 -0
- package/lib/server/modules/payment/types.js +5 -0
- package/lib/server/modules/products/index.d.ts +105 -14
- package/lib/server/modules/products/index.js +969 -383
- package/lib/server/modules/products/types.d.ts +34 -1
- package/lib/server/modules/products/types.js +44 -34
- package/lib/server/modules/quotation/index.d.ts +39 -0
- package/lib/server/modules/quotation/index.js +220 -77
- package/lib/server/modules/quotation/types.js +21 -31
- package/lib/server/modules/resource/index.d.ts +2 -3
- package/lib/server/modules/resource/index.js +269 -214
- package/lib/server/modules/resource/types.js +42 -33
- package/lib/server/modules/schedule/index.d.ts +6 -4
- package/lib/server/modules/schedule/index.js +158 -132
- package/lib/server/modules/schedule/types.js +14 -21
- package/lib/server/modules/schedule/utils.js +334 -163
- package/lib/server/test.json +713 -0
- package/lib/server/types.d.ts +2 -0
- package/lib/server/types.js +4 -16
- package/lib/server/utils/index.js +25 -23
- package/lib/server/utils/product.js +197 -140
- package/lib/server/utils/schedule.js +34 -41
- package/lib/server/utils/small-ticket.d.ts +71 -0
- package/lib/server/utils/small-ticket.js +926 -0
- package/lib/server/utils/time.js +40 -49
- package/lib/solution/BaseSales/index.d.ts +378 -0
- package/lib/solution/BaseSales/index.js +2367 -0
- package/lib/solution/BaseSales/types.d.ts +394 -0
- package/lib/solution/BaseSales/types.js +47 -0
- package/lib/solution/BaseSales/utils/cartPromotion.d.ts +287 -0
- package/lib/solution/BaseSales/utils/cartPromotion.js +1155 -0
- package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +144 -0
- package/lib/solution/BaseSales/utils/parseSalesResponse.js +140 -0
- package/lib/solution/BaseSales/utils/quotationPrice.d.ts +18 -0
- package/lib/solution/BaseSales/utils/quotationPrice.js +231 -0
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +63 -0
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +215 -0
- package/lib/solution/BaseSales/utils.d.ts +40 -0
- package/lib/solution/BaseSales/utils.js +259 -0
- package/lib/solution/BookingByStep/index.js +1530 -1343
- package/lib/solution/BookingByStep/types.js +40 -99
- package/lib/solution/BookingByStep/utils/capacity.js +192 -160
- package/lib/solution/BookingByStep/utils/products.js +42 -52
- package/lib/solution/BookingByStep/utils/resources.js +527 -330
- package/lib/solution/BookingByStep/utils/stock.js +65 -44
- package/lib/solution/BookingByStep/utils/timeslots.js +129 -131
- package/lib/solution/BookingTicket/index.d.ts +393 -24
- package/lib/solution/BookingTicket/index.js +1459 -160
- package/lib/solution/BookingTicket/types.d.ts +191 -6
- package/lib/solution/BookingTicket/types.js +101 -66
- package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +117 -0
- package/lib/solution/BookingTicket/utils/addProductDecision.js +353 -0
- package/lib/solution/BookingTicket/utils/addTimeAvailability.d.ts +35 -0
- package/lib/solution/BookingTicket/utils/addTimeAvailability.js +94 -0
- package/lib/solution/BookingTicket/utils/bookingStatus.d.ts +77 -0
- package/lib/solution/BookingTicket/utils/bookingStatus.js +136 -0
- package/lib/solution/BookingTicket/utils/cartView.d.ts +29 -0
- package/lib/solution/BookingTicket/utils/cartView.js +200 -0
- package/lib/solution/BookingTicket/utils/exampleData.d.ts +123 -0
- package/lib/solution/BookingTicket/utils/exampleData.js +185 -0
- package/lib/solution/BookingTicket/utils/orderCustomer.d.ts +4 -0
- package/lib/solution/BookingTicket/utils/orderCustomer.js +35 -0
- package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.d.ts +185 -0
- package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.js +387 -0
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +36 -0
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +220 -0
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.d.ts +4 -0
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +85 -80
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +32 -0
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +64 -0
- package/lib/solution/BookingTicket/utils/scan/handleScan.d.ts +3 -1
- package/lib/solution/BookingTicket/utils/scan/handleScan.js +84 -80
- package/lib/solution/BookingTicket/utils/scan/index.d.ts +5 -0
- package/lib/solution/BookingTicket/utils/scan/index.js +103 -95
- package/lib/solution/BookingTicket/utils/scan/scanCache.js +90 -43
- package/lib/solution/BookingTicket/utils/scan/scanProductValues.d.ts +21 -0
- package/lib/solution/BookingTicket/utils/scan/scanProductValues.js +115 -0
- package/lib/solution/BookingTicket/utils/sessionCatalogStale.d.ts +23 -0
- package/lib/solution/BookingTicket/utils/sessionCatalogStale.js +83 -0
- package/lib/solution/BuyTickets/index.js +67 -70
- package/lib/solution/BuyTickets/types.js +22 -38
- package/lib/solution/Checkout/index.d.ts +1 -0
- package/lib/solution/Checkout/index.js +1461 -1169
- package/lib/solution/Checkout/types.js +91 -61
- package/lib/solution/Checkout/utils/index.js +228 -159
- package/lib/solution/PlaceOrder/index.js +55 -0
- package/lib/solution/RegisterAndLogin/config.js +495 -470
- package/lib/solution/RegisterAndLogin/index.js +633 -630
- package/lib/solution/RegisterAndLogin/types.js +189 -76
- package/lib/solution/RegisterAndLogin/utils.js +183 -125
- package/lib/solution/Sales/index.d.ts +50 -5
- package/lib/solution/Sales/index.js +495 -220
- package/lib/solution/Sales/types.d.ts +26 -2
- package/lib/solution/Sales/types.js +27 -33
- package/lib/solution/ScanOrder/index.d.ts +27 -21
- package/lib/solution/ScanOrder/index.js +908 -904
- package/lib/solution/ScanOrder/types.d.ts +33 -11
- package/lib/solution/ScanOrder/types.js +33 -34
- package/lib/solution/ScanOrder/utils.d.ts +4 -1
- package/lib/solution/ScanOrder/utils.js +457 -344
- package/lib/solution/ShopDiscount/index.js +230 -236
- package/lib/solution/ShopDiscount/types.js +15 -37
- package/lib/solution/ShopDiscount/utils.js +309 -172
- package/lib/solution/UnifiedBookingSales/index.d.ts +183 -0
- package/lib/solution/UnifiedBookingSales/index.js +1050 -0
- package/lib/solution/UnifiedBookingSales/types.d.ts +143 -0
- package/lib/solution/UnifiedBookingSales/types.js +14 -0
- package/lib/solution/VenueBooking/index.d.ts +34 -20
- package/lib/solution/VenueBooking/index.js +982 -950
- package/lib/solution/VenueBooking/sales-section-4-annotated.json +0 -2
- package/lib/solution/VenueBooking/types.js +20 -40
- package/lib/solution/VenueBooking/utils/dateSummary.js +22 -54
- package/lib/solution/VenueBooking/utils/resource.js +31 -54
- package/lib/solution/VenueBooking/utils/slotMerge.d.ts +6 -0
- package/lib/solution/VenueBooking/utils/slotMerge.js +96 -108
- package/lib/solution/VenueBooking/utils/smartPricing.js +112 -100
- package/lib/solution/VenueBooking/utils/timeSlot.js +133 -132
- package/lib/solution/VenueBooking/utils.js +130 -67
- package/lib/solution/index.d.ts +2 -0
- package/lib/solution/index.js +124 -37
- package/lib/store/createStore.js +32 -29
- package/lib/types/index.d.ts +11 -1
- package/lib/types/index.js +4 -16
- package/lib/utils/task.js +36 -36
- package/lib/utils/watch.js +81 -47
- package/package.json +2 -1
- package/dist/solution/Checkout/appointmentDemo.json +0 -1
- package/lib/solution/Checkout/appointmentDemo.json +0 -1
|
@@ -1,138 +1,364 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
-
};
|
|
11
|
-
var __copyProps = (to, from, except, desc) => {
|
|
12
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
-
for (let key of __getOwnPropNames(from))
|
|
14
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
-
mod
|
|
26
|
-
));
|
|
27
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
-
|
|
29
|
-
// src/modules/Order/index.ts
|
|
30
|
-
var Order_exports = {};
|
|
31
|
-
__export(Order_exports, {
|
|
32
|
-
OrderModule: () => OrderModule
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
33
5
|
});
|
|
34
|
-
|
|
35
|
-
var
|
|
36
|
-
var
|
|
37
|
-
var
|
|
38
|
-
var
|
|
39
|
-
var
|
|
40
|
-
var
|
|
41
|
-
var
|
|
42
|
-
var
|
|
43
|
-
var
|
|
44
|
-
var
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
6
|
+
exports.OrderModule = void 0;
|
|
7
|
+
var _BaseModule = require("../BaseModule");
|
|
8
|
+
var _types = require("./types");
|
|
9
|
+
var _utils = require("./utils");
|
|
10
|
+
var _utils2 = require("../Product/utils");
|
|
11
|
+
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
12
|
+
var _cartPromotion = require("../../solution/BaseSales/utils/cartPromotion");
|
|
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");
|
|
17
|
+
var _Discount = require("../Discount");
|
|
18
|
+
var _Rules = require("../Rules");
|
|
19
|
+
var _types2 = require("../Rules/types");
|
|
20
|
+
var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
21
|
+
var _lodashEs = require("lodash-es");
|
|
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
|
+
}
|
|
55
|
+
class OrderModule extends _BaseModule.BaseModule {
|
|
56
|
+
defaultName = 'order';
|
|
57
|
+
defaultVersion = '1.0.0';
|
|
58
|
+
store;
|
|
59
|
+
request;
|
|
60
|
+
logger; // LoggerManager 实例
|
|
61
|
+
window;
|
|
62
|
+
cacheId;
|
|
63
|
+
salesSummaryModuleName;
|
|
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());
|
|
49
172
|
}
|
|
173
|
+
|
|
50
174
|
/**
|
|
51
175
|
* Populate `savedAmount` on each discount based on product-level discount details.
|
|
52
176
|
* Only selected discounts get a non-zero value.
|
|
53
177
|
*/
|
|
54
178
|
static populateSavedAmounts(productList, discountList) {
|
|
55
|
-
|
|
56
|
-
const
|
|
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
|
+
};
|
|
57
194
|
for (const product of productList) {
|
|
58
195
|
const qty = product.num || 1;
|
|
196
|
+
const isPersistedProduct = Boolean(product.order_detail_id || product.orderDetailId || product.booking_id);
|
|
59
197
|
for (const pd of product.discount_list || []) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
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
|
+
}
|
|
65
206
|
}
|
|
66
207
|
}
|
|
67
208
|
for (const d of discountList) {
|
|
68
209
|
const key = d.id;
|
|
69
|
-
|
|
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;
|
|
70
212
|
}
|
|
71
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
|
+
}
|
|
251
|
+
constructor(name, version) {
|
|
252
|
+
super(name || 'order', version);
|
|
253
|
+
}
|
|
72
254
|
async initialize(core, options) {
|
|
73
|
-
var _a;
|
|
74
255
|
this.core = core;
|
|
75
256
|
this.store = options.store;
|
|
76
257
|
if (!this.store.tempOrder) {
|
|
77
258
|
this.store.tempOrder = null;
|
|
78
259
|
}
|
|
260
|
+
if (!this.store.summary) {
|
|
261
|
+
this.store.summary = (0, _utils.createEmptySummary)();
|
|
262
|
+
}
|
|
79
263
|
if (!Array.isArray(this.store.availableWalletIds)) {
|
|
80
264
|
this.store.availableWalletIds = [];
|
|
81
265
|
}
|
|
82
|
-
this.request = this.core.getPlugin(
|
|
83
|
-
|
|
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;
|
|
273
|
+
this.window = this.core.getPlugin('window');
|
|
84
274
|
const otherParams = options.otherParams || {};
|
|
85
275
|
this.cacheId = otherParams.cacheId;
|
|
86
276
|
this.salesSummaryModuleName = otherParams.salesSummaryModuleName;
|
|
87
|
-
this.rulesHooksOverride =
|
|
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;
|
|
88
281
|
this.registerDiscountModules(options);
|
|
89
|
-
this.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
282
|
+
this.logInfo('OrderModule initialized successfully');
|
|
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;
|
|
93
302
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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;
|
|
98
319
|
}
|
|
320
|
+
|
|
99
321
|
/**
|
|
100
322
|
* 记录信息日志
|
|
101
323
|
*/
|
|
102
324
|
logInfo(title, metadata) {
|
|
103
325
|
if (this.logger) {
|
|
104
326
|
this.logger.addLog({
|
|
105
|
-
type:
|
|
327
|
+
type: 'info',
|
|
106
328
|
title: `[OrderModule] ${title}`,
|
|
107
329
|
metadata: metadata || {}
|
|
108
330
|
});
|
|
109
331
|
}
|
|
110
332
|
}
|
|
333
|
+
|
|
111
334
|
/**
|
|
112
335
|
* 记录警告日志
|
|
113
336
|
*/
|
|
114
337
|
logWarning(title, metadata) {
|
|
115
338
|
if (this.logger) {
|
|
116
339
|
this.logger.addLog({
|
|
117
|
-
type:
|
|
340
|
+
type: 'warning',
|
|
118
341
|
title: `[OrderModule] ${title}`,
|
|
119
342
|
metadata: metadata || {}
|
|
120
343
|
});
|
|
121
344
|
}
|
|
122
345
|
}
|
|
346
|
+
|
|
123
347
|
/**
|
|
124
348
|
* 记录错误日志
|
|
125
349
|
*/
|
|
126
350
|
logError(title, metadata) {
|
|
127
351
|
if (this.logger) {
|
|
128
352
|
this.logger.addLog({
|
|
129
|
-
type:
|
|
353
|
+
type: 'error',
|
|
130
354
|
title: `[OrderModule] ${title}`,
|
|
131
355
|
metadata: metadata || {}
|
|
132
356
|
});
|
|
133
357
|
}
|
|
134
358
|
}
|
|
359
|
+
|
|
135
360
|
// ─── Discount: 子模块注册 ───
|
|
361
|
+
|
|
136
362
|
registerDiscountModules(options) {
|
|
137
363
|
let targetCacheData = {};
|
|
138
364
|
if (this.cacheId && this.window) {
|
|
@@ -140,14 +366,15 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
140
366
|
if (sessionData) {
|
|
141
367
|
try {
|
|
142
368
|
const data = JSON.parse(sessionData);
|
|
143
|
-
targetCacheData =
|
|
369
|
+
targetCacheData = data?.[this.cacheId] || {};
|
|
144
370
|
} catch {
|
|
371
|
+
// sessionStorage 损坏则忽略,按空 initialState 走
|
|
145
372
|
}
|
|
146
373
|
}
|
|
147
374
|
}
|
|
148
|
-
const discount = new
|
|
375
|
+
const discount = new _Discount.DiscountModule(`${this.name}_discount`);
|
|
149
376
|
this.core.registerModule(discount, {
|
|
150
|
-
initialState: targetCacheData
|
|
377
|
+
initialState: targetCacheData?.[discount.name],
|
|
151
378
|
otherParams: {
|
|
152
379
|
fatherModule: this.name,
|
|
153
380
|
openCache: !!this.cacheId,
|
|
@@ -155,192 +382,400 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
155
382
|
}
|
|
156
383
|
});
|
|
157
384
|
this.store.discount = discount;
|
|
158
|
-
const rules = new
|
|
385
|
+
const rules = new _Rules.RulesModule(`${this.name}_rules`);
|
|
159
386
|
this.core.registerModule(rules, {
|
|
160
387
|
hooks: this.rulesHooksOverride || this.createDefaultRulesHooks()
|
|
161
388
|
});
|
|
162
389
|
this.store.rules = rules;
|
|
163
390
|
}
|
|
164
391
|
createDefaultRulesHooks() {
|
|
165
|
-
return (0,
|
|
392
|
+
return (0, _utils.createDefaultOrderRulesHooks)();
|
|
166
393
|
}
|
|
394
|
+
|
|
167
395
|
// ─── Discount: 公共 API ───
|
|
396
|
+
|
|
168
397
|
async loadDiscountConfig(params) {
|
|
169
|
-
|
|
170
|
-
const
|
|
398
|
+
const orderId = params.orderId || this.store.tempOrder?.order_id || undefined;
|
|
399
|
+
const prepareConfigResult = await this.store.discount?.loadPrepareConfig({
|
|
171
400
|
customer_id: params.customerId,
|
|
172
|
-
action: params.action ||
|
|
401
|
+
action: params.action || (orderId ? 'edit' : 'create'),
|
|
173
402
|
with_good_pass: 1,
|
|
174
403
|
with_discount_card: 1,
|
|
175
404
|
with_wallet_pass_holder: 1,
|
|
176
405
|
with_available_wallet_flag: 1,
|
|
177
406
|
with_available_wallet_pass_flag: 1,
|
|
178
|
-
request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
407
|
+
request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
408
|
+
...(orderId ? {
|
|
409
|
+
order_id: orderId
|
|
410
|
+
} : {})
|
|
411
|
+
});
|
|
412
|
+
const discountList = prepareConfigResult?.discountList;
|
|
413
|
+
this.store.availableWalletIds = prepareConfigResult?.availableWalletIds || [];
|
|
182
414
|
if (discountList) {
|
|
183
|
-
|
|
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);
|
|
184
429
|
}
|
|
185
|
-
if (
|
|
430
|
+
if (params.apply !== false && this.store.tempOrder?.products?.length) {
|
|
186
431
|
this.applyDiscount();
|
|
187
432
|
}
|
|
433
|
+
return this.getDiscountList();
|
|
188
434
|
}
|
|
189
435
|
getDiscountList() {
|
|
190
|
-
|
|
191
|
-
return ((_a = this.store.discount) == null ? void 0 : _a.getDiscountList()) || [];
|
|
436
|
+
return this.store.discount?.getDiscountList() || [];
|
|
192
437
|
}
|
|
193
438
|
getAvailableWalletIds() {
|
|
194
|
-
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
|
+
});
|
|
195
462
|
}
|
|
196
463
|
async scanCode(code, customerId) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
464
|
+
const resultDiscountWithReason = (await this.store.discount?.batchSearch(code, {
|
|
465
|
+
customerId
|
|
466
|
+
})) || {
|
|
467
|
+
discountList: [],
|
|
468
|
+
unavailableReasonKey: null
|
|
469
|
+
};
|
|
470
|
+
const {
|
|
471
|
+
discountList: resultDiscountList,
|
|
472
|
+
unavailableReasonKey
|
|
473
|
+
} = resultDiscountWithReason;
|
|
200
474
|
const rulesModule = this.store.rules;
|
|
201
475
|
if (!rulesModule) {
|
|
202
476
|
return {
|
|
203
|
-
type:
|
|
477
|
+
type: 'clientCalc',
|
|
204
478
|
isAvailable: false,
|
|
205
479
|
discountList: this.getDiscountList(),
|
|
206
|
-
|
|
480
|
+
scannedDiscountList: resultDiscountList,
|
|
481
|
+
unavailableReason: _types2.UnavailableReason.Unknown
|
|
207
482
|
};
|
|
208
483
|
}
|
|
209
484
|
if (!resultDiscountList.length) {
|
|
210
485
|
return {
|
|
211
|
-
type:
|
|
486
|
+
type: 'server',
|
|
212
487
|
isAvailable: false,
|
|
213
488
|
discountList: this.getDiscountList(),
|
|
489
|
+
scannedDiscountList: resultDiscountList,
|
|
214
490
|
unavailableReasonKey
|
|
215
491
|
};
|
|
216
492
|
}
|
|
217
|
-
const withScanList = resultDiscountList.map(
|
|
218
|
-
|
|
219
|
-
|
|
493
|
+
const withScanList = resultDiscountList.map(item => ({
|
|
494
|
+
...item,
|
|
495
|
+
isScan: true
|
|
496
|
+
}));
|
|
497
|
+
const currentSelected = this.getDiscountList().filter(n => n.isSelected);
|
|
498
|
+
if (currentSelected.length && currentSelected.some(n => withScanList.some(m => m.id === n.id))) {
|
|
499
|
+
await this.syncScannedDiscountsToOriginalDiscountList(this.getDiscountList());
|
|
220
500
|
return {
|
|
221
|
-
type:
|
|
501
|
+
type: 'clientCalc',
|
|
222
502
|
isAvailable: true,
|
|
223
|
-
discountList: this.getDiscountList()
|
|
503
|
+
discountList: this.getDiscountList(),
|
|
504
|
+
scannedDiscountList: resultDiscountList
|
|
224
505
|
};
|
|
225
506
|
}
|
|
226
507
|
const tempOrder = this.store.tempOrder;
|
|
227
|
-
const holders =
|
|
228
|
-
const {
|
|
229
|
-
|
|
508
|
+
const holders = tempOrder?.holder?.form_record || [];
|
|
509
|
+
const {
|
|
510
|
+
isAvailable,
|
|
511
|
+
discountList: newDiscountList,
|
|
512
|
+
unavailableReason
|
|
513
|
+
} = rulesModule.isDiscountListAvailable({
|
|
514
|
+
productList: tempOrder?.products || [],
|
|
230
515
|
oldDiscountList: this.getDiscountList(),
|
|
231
516
|
newDiscountList: withScanList,
|
|
517
|
+
orderTotalAmount: Number(this.store.summary?.total_amount || 0),
|
|
232
518
|
holders,
|
|
233
|
-
isFormSubject: !!
|
|
234
|
-
|
|
235
|
-
|
|
519
|
+
isFormSubject: !!tempOrder?.holder?.type && tempOrder.holder.type === 'form'
|
|
520
|
+
}) || {
|
|
521
|
+
isAvailable: false,
|
|
522
|
+
discountList: this.getDiscountList()
|
|
523
|
+
};
|
|
236
524
|
if (isAvailable && newDiscountList) {
|
|
237
|
-
|
|
525
|
+
this.store.discount?.setDiscountList(newDiscountList);
|
|
238
526
|
this.applyDiscount();
|
|
527
|
+
await this.syncScannedDiscountsToOriginalDiscountList(this.getDiscountList());
|
|
239
528
|
}
|
|
240
529
|
return {
|
|
241
|
-
type:
|
|
530
|
+
type: 'clientCalc',
|
|
242
531
|
isAvailable: isAvailable || false,
|
|
243
532
|
discountList: newDiscountList || this.getDiscountList(),
|
|
533
|
+
scannedDiscountList: resultDiscountList,
|
|
244
534
|
unavailableReason
|
|
245
535
|
};
|
|
246
536
|
}
|
|
247
|
-
applyDiscount() {
|
|
248
|
-
var _a, _b, _c, _d, _e;
|
|
537
|
+
applyDiscount(options) {
|
|
249
538
|
const tempOrder = this.store.tempOrder;
|
|
250
|
-
|
|
251
|
-
|
|
539
|
+
// 任意 products CRUD 后都应当重算 discount 状态;即使 products 为空,
|
|
540
|
+
// 也需要让 Rules.calcDiscount 把 DiscountModule 的 isSelected / isAvailable /
|
|
541
|
+
// appliedProductDetails / savedAmount 复位。
|
|
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;
|
|
252
548
|
const rulesModule = this.store.rules;
|
|
253
|
-
if (!rulesModule)
|
|
254
|
-
return;
|
|
255
|
-
const discountList = ((_a = this.store.discount) == null ? void 0 : _a.getDiscountList()) || [];
|
|
256
|
-
const holders = ((_b = tempOrder.holder) == null ? void 0 : _b.form_record_id) ? [{ form_record_id: tempOrder.holder.form_record_id }] : [];
|
|
549
|
+
if (!rulesModule) return;
|
|
257
550
|
const result = rulesModule.calcDiscount({
|
|
258
551
|
productList: tempOrder.products,
|
|
259
552
|
discountList,
|
|
260
|
-
holders,
|
|
261
|
-
isFormSubject:
|
|
262
|
-
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
|
|
263
558
|
});
|
|
264
|
-
if (result
|
|
265
|
-
|
|
559
|
+
if (result?.productList) {
|
|
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
|
+
}));
|
|
266
573
|
}
|
|
267
|
-
if (result
|
|
574
|
+
if (result?.discountList) {
|
|
268
575
|
OrderModule.populateSavedAmounts(result.productList || tempOrder.products, result.discountList);
|
|
269
|
-
|
|
270
|
-
tempOrder.discount_list = result.discountList.filter((d) => d.isSelected);
|
|
576
|
+
this.store.discount?.setDiscountList(result.discountList);
|
|
271
577
|
}
|
|
578
|
+
this.hasActiveCustomerBoundDiscount = (tempOrder.products || []).some(product => this.productHasCustomerBoundDiscount(product));
|
|
272
579
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
580
|
+
|
|
581
|
+
// ─── 促销/赠品 ──────────────────────────────────────
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* 注入促销评估器。
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* order.setPromotionEvaluator(appHelper.utils.promotionEvaluator);
|
|
588
|
+
*/
|
|
589
|
+
setPromotionEvaluator(evaluator) {
|
|
590
|
+
this.promotionEvaluator = evaluator || null;
|
|
591
|
+
}
|
|
592
|
+
|
|
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
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* 为商品目录追加促销标签,供上层 Sales 门面复用。
|
|
608
|
+
*
|
|
609
|
+
* @example
|
|
610
|
+
* const products = order.appendPromotionTags(catalogProducts);
|
|
611
|
+
*/
|
|
612
|
+
appendPromotionTags(products) {
|
|
613
|
+
return (0, _cartPromotion.appendPromotionTags)(products, this.promotionEvaluator);
|
|
286
614
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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) {
|
|
291
632
|
return;
|
|
292
|
-
|
|
633
|
+
}
|
|
634
|
+
const tempOrder = this.store.tempOrder;
|
|
635
|
+
if (!tempOrder) {
|
|
293
636
|
return;
|
|
294
|
-
|
|
295
|
-
|
|
637
|
+
}
|
|
638
|
+
const strategyConfigs = this.promotionEvaluator.getStrategyConfigs?.();
|
|
639
|
+
if (Array.isArray(strategyConfigs) && strategyConfigs.length === 0) {
|
|
296
640
|
return;
|
|
641
|
+
}
|
|
642
|
+
this.isApplyingPromotion = true;
|
|
297
643
|
try {
|
|
298
|
-
const
|
|
299
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
320
683
|
}
|
|
321
|
-
|
|
322
|
-
|
|
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);
|
|
323
714
|
}
|
|
324
715
|
}
|
|
325
|
-
parsedData.products[i] = (0, import_utils3.normalizeOrderProduct)(row);
|
|
326
716
|
}
|
|
327
717
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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;
|
|
331
737
|
}
|
|
332
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。 */
|
|
333
774
|
persistTempOrder() {
|
|
334
|
-
|
|
335
|
-
if (!key || !this.store.tempOrder)
|
|
336
|
-
return;
|
|
337
|
-
if (!this.window)
|
|
338
|
-
return;
|
|
339
|
-
this.window.localStorage.setItem(key, JSON.stringify(this.store.tempOrder));
|
|
775
|
+
// no-op: OrderModule 的刷新恢复不再依赖 localStorage。
|
|
340
776
|
}
|
|
341
777
|
notifyTempOrderChanged() {
|
|
342
|
-
if (!this.store.tempOrder)
|
|
343
|
-
return;
|
|
778
|
+
if (!this.store.tempOrder) return;
|
|
344
779
|
this.store.tempOrder = {
|
|
345
780
|
...this.store.tempOrder,
|
|
346
781
|
products: [...this.store.tempOrder.products],
|
|
@@ -348,309 +783,2783 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
348
783
|
};
|
|
349
784
|
this.persistTempOrder();
|
|
350
785
|
}
|
|
786
|
+
|
|
351
787
|
// ─── TempOrder: 创建 & 获取 ───
|
|
788
|
+
|
|
352
789
|
createDefaultTempOrderInstance() {
|
|
353
|
-
return (0,
|
|
354
|
-
now: (0,
|
|
355
|
-
summary: (0, import_utils.createEmptySummary)()
|
|
790
|
+
return (0, _utils.createDefaultTempOrder)({
|
|
791
|
+
now: (0, _utils.formatDateTime)(new Date())
|
|
356
792
|
});
|
|
357
793
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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('_');
|
|
365
819
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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;
|
|
371
826
|
}
|
|
372
|
-
|
|
373
|
-
|
|
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;
|
|
374
832
|
}
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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}`;
|
|
380
840
|
}
|
|
381
|
-
|
|
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
|
-
|
|
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
|
|
971
|
+
});
|
|
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
|
+
};
|
|
384
982
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
return
|
|
389
|
-
|
|
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));
|
|
390
991
|
}
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
if (!tempOrder)
|
|
394
|
-
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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';
|
|
998
|
+
}
|
|
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)();
|
|
1006
|
+
}
|
|
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';
|
|
1016
|
+
}
|
|
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;
|
|
1035
|
+
}
|
|
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';
|
|
1045
|
+
}
|
|
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);
|
|
400
1106
|
}
|
|
401
|
-
|
|
402
|
-
|
|
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)
|
|
403
1126
|
});
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
1127
|
+
}
|
|
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);
|
|
407
1131
|
}
|
|
408
|
-
|
|
409
|
-
|
|
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);
|
|
1136
|
+
}
|
|
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);
|
|
410
1141
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
return
|
|
417
|
-
}
|
|
418
|
-
async getScanOrderSummary() {
|
|
419
|
-
const summary = await this.recalculateSummary({ createIfMissing: true });
|
|
420
|
-
if (!summary)
|
|
421
|
-
return (0, import_utils.createEmptySummary)();
|
|
422
|
-
this.persistTempOrder();
|
|
423
|
-
return summary;
|
|
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);
|
|
424
1148
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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);
|
|
431
1158
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
return
|
|
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);
|
|
437
1164
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
return tempOrder.contacts_info;
|
|
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);
|
|
443
1169
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
...normalizedProduct,
|
|
461
|
-
num: (0, import_utils3.getSafeProductNum)(targetProduct.num + normalizedProduct.num),
|
|
462
|
-
_origin: normalizedProduct._origin || targetProduct._origin
|
|
463
|
-
};
|
|
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;
|
|
464
1186
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const
|
|
486
|
-
const
|
|
487
|
-
...
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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;
|
|
497
1226
|
}
|
|
498
1227
|
}
|
|
499
|
-
this.applyDiscount();
|
|
500
|
-
await this.recalculateSummary({ createIfMissing: true });
|
|
501
|
-
this.persistTempOrder();
|
|
502
|
-
return tempOrder.products;
|
|
503
1228
|
}
|
|
504
|
-
|
|
505
|
-
var _a, _b, _c;
|
|
1229
|
+
calculateSummaryAmountGap(params) {
|
|
506
1230
|
const {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
1231
|
+
tempOrder,
|
|
1232
|
+
summary,
|
|
1233
|
+
orderPaidAmount
|
|
1234
|
+
} = params;
|
|
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
|
|
513
3052
|
} = params;
|
|
514
3053
|
const tempOrder = this.ensureTempOrder();
|
|
515
3054
|
const identityLookup = {
|
|
516
3055
|
product_id,
|
|
517
3056
|
product_variant_id
|
|
518
3057
|
};
|
|
519
|
-
if (
|
|
520
|
-
identityLookup.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
+
}
|
|
3064
|
+
if (identity_key !== undefined) identityLookup.identity_key = identity_key;
|
|
3065
|
+
if (product_sku !== undefined) identityLookup.product_sku = product_sku;
|
|
3066
|
+
if (product_bundle !== undefined) identityLookup.product_bundle = product_bundle;
|
|
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
|
+
}
|
|
3074
|
+
if (productIndex === -1) {
|
|
3075
|
+
productIndex = (0, _utils3.getProductIdentityIndex)(tempOrder.products, identityLookup);
|
|
3076
|
+
}
|
|
526
3077
|
if (productIndex === -1) {
|
|
527
|
-
throw new Error(
|
|
3078
|
+
throw new Error('[Order] 目标商品不存在,无法更新数量');
|
|
528
3079
|
}
|
|
529
3080
|
const targetProduct = tempOrder.products[productIndex];
|
|
530
|
-
const
|
|
3081
|
+
const normalizedProduct = (0, _utils3.normalizeOrderProduct)({
|
|
531
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,
|
|
532
3117
|
...updates,
|
|
533
|
-
|
|
534
|
-
|
|
3118
|
+
metadata: {
|
|
3119
|
+
...(targetBooking.metadata || {}),
|
|
3120
|
+
...(updates.metadata || {}),
|
|
3121
|
+
unique_identification_number: bookingUid
|
|
3122
|
+
}
|
|
535
3123
|
};
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
nextProduct.metadata = { ...existedMeta };
|
|
542
|
-
delete nextProduct.metadata.source_product_price;
|
|
543
|
-
delete nextProduct.metadata.main_product_selling_price;
|
|
544
|
-
delete nextProduct.metadata.main_product_original_price;
|
|
545
|
-
delete nextProduct.metadata.price_schema_version;
|
|
546
|
-
}
|
|
547
|
-
tempOrder.products[productIndex] = (0, import_utils3.normalizeOrderProduct)(nextProduct);
|
|
548
|
-
this.applyDiscount();
|
|
549
|
-
await this.recalculateSummary({ createIfMissing: true });
|
|
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);
|
|
550
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);
|
|
3147
|
+
await this.recalculateSummary({
|
|
3148
|
+
createIfMissing: true
|
|
3149
|
+
});
|
|
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
|
+
});
|
|
551
3208
|
return tempOrder.products;
|
|
552
3209
|
}
|
|
553
3210
|
async removeProductFromOrder(identity) {
|
|
3211
|
+
return this.removeProductsFromOrder([identity]);
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
/**
|
|
3215
|
+
* 仅清空购物车行(products / bookings),不重置整单。
|
|
3216
|
+
* 用于「仅清空商品」;客户、备注、支付等协议字段保持不变。
|
|
3217
|
+
*/
|
|
3218
|
+
async clearOrderCartLines() {
|
|
554
3219
|
const tempOrder = this.ensureTempOrder();
|
|
555
|
-
tempOrder.products =
|
|
556
|
-
|
|
557
|
-
);
|
|
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();
|
|
558
3230
|
this.applyDiscount();
|
|
559
|
-
|
|
3231
|
+
this.sanitizeTempOrderProducts(tempOrder);
|
|
3232
|
+
await this.recalculateSummary({
|
|
3233
|
+
createIfMissing: true
|
|
3234
|
+
});
|
|
560
3235
|
this.persistTempOrder();
|
|
561
|
-
|
|
3236
|
+
this.logInfo('clearOrderCartLines success', {});
|
|
3237
|
+
return tempOrder;
|
|
562
3238
|
}
|
|
3239
|
+
|
|
563
3240
|
// ─── TempOrder: 提交 ───
|
|
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
|
+
*/
|
|
564
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) {
|
|
565
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
|
+
}
|
|
566
3313
|
this.persistTempOrder();
|
|
567
|
-
const
|
|
568
|
-
|
|
3314
|
+
const latestSummary = (await this.recalculateSummary({
|
|
3315
|
+
createIfMissing: true
|
|
3316
|
+
})) || this.store.summary || (0, _utils.createEmptySummary)();
|
|
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();
|
|
3337
|
+
const payload = (0, _utils.buildSubmitPayload)({
|
|
569
3338
|
tempOrder,
|
|
570
3339
|
cacheId: effectiveCacheId,
|
|
571
|
-
platform: params
|
|
572
|
-
businessCode:
|
|
573
|
-
channel: params
|
|
574
|
-
type:
|
|
575
|
-
|
|
3340
|
+
platform: params?.platform || this.otherParams?.platform,
|
|
3341
|
+
businessCode: submitSnapshot.businessCode ?? params?.businessCode ?? this.otherParams?.businessCode ?? this.otherParams?.business_code,
|
|
3342
|
+
channel: params?.channel,
|
|
3343
|
+
type: submitSnapshot.type ?? params?.type,
|
|
3344
|
+
summary: latestSummary,
|
|
3345
|
+
request_unique_idempotency_token: params?.request_unique_idempotency_token,
|
|
3346
|
+
enhance: enhancePayload
|
|
576
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();
|
|
577
3356
|
let result;
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
|
586
3366
|
});
|
|
587
|
-
|
|
588
|
-
result = {
|
|
589
|
-
data: {
|
|
590
|
-
order_id: tempOrder.order_id
|
|
591
|
-
}
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
} else {
|
|
595
|
-
result = await this.submitScanOrder({
|
|
3367
|
+
result = await this.submitSalesOrder({
|
|
596
3368
|
query: payload
|
|
597
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';
|
|
598
3409
|
}
|
|
599
3410
|
return result;
|
|
600
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
|
+
}
|
|
601
3507
|
createOrder(params) {
|
|
602
|
-
var _a;
|
|
603
3508
|
const order = {
|
|
604
|
-
type:
|
|
3509
|
+
type: params?.type || 'appointment_booking',
|
|
605
3510
|
// 要从外面拿,virtual
|
|
606
|
-
platform:
|
|
607
|
-
sales_channel:
|
|
608
|
-
order_sales_channel:
|
|
3511
|
+
platform: params?.platform === 'pc' ? 'PC' : 'H5',
|
|
3512
|
+
sales_channel: 'my_pisel',
|
|
3513
|
+
order_sales_channel: 'online_store',
|
|
609
3514
|
bookings: [],
|
|
610
|
-
shop_note:
|
|
611
|
-
schedule_date:
|
|
3515
|
+
shop_note: '',
|
|
3516
|
+
schedule_date: '',
|
|
612
3517
|
is_deposit: 0,
|
|
613
3518
|
relation_products: [],
|
|
614
3519
|
relation_forms: [],
|
|
615
|
-
business_code: params
|
|
616
|
-
...(params
|
|
3520
|
+
business_code: params?.business_code,
|
|
3521
|
+
...(params?.extraData || {})
|
|
617
3522
|
};
|
|
618
|
-
let is_deposit = 0;
|
|
3523
|
+
let is_deposit = 0; // 是否存在定金,0 不存在,1 存在
|
|
619
3524
|
if (params.cartItems.length > 0) {
|
|
620
|
-
params.cartItems.forEach(
|
|
621
|
-
var _a2;
|
|
3525
|
+
params.cartItems.forEach(item => {
|
|
622
3526
|
if (!item._origin.duration) {
|
|
623
|
-
const {
|
|
3527
|
+
const {
|
|
3528
|
+
duration,
|
|
3529
|
+
durationType
|
|
3530
|
+
} = (0, _utils.generateDuration)(item);
|
|
624
3531
|
item._origin.duration = duration;
|
|
625
3532
|
item._origin.sub_type = durationType;
|
|
626
3533
|
}
|
|
627
|
-
|
|
3534
|
+
item._origin.sub_type = (0, _utils.normalizeBookingSubType)(item._origin);
|
|
3535
|
+
const discountList = (0, _utils.getAllDiscountList)(item);
|
|
628
3536
|
item._origin.product.discount_list = discountList;
|
|
629
|
-
|
|
630
|
-
|
|
3537
|
+
|
|
3538
|
+
// 为预约补齐 holder 信息
|
|
3539
|
+
if (params?.extraData?.is_add_holder_info) {
|
|
3540
|
+
(0, _utils.ensureCartItemOriginHolder)(item, this.window);
|
|
631
3541
|
}
|
|
632
|
-
|
|
3542
|
+
|
|
3543
|
+
// 购物车是否为普通商品
|
|
3544
|
+
if ((0, _utils2.isNormalProduct)(item._origin)) {
|
|
633
3545
|
order.relation_products.push(item._origin.product);
|
|
634
3546
|
const relationForms = item._origin.relation_forms || [];
|
|
635
3547
|
order.relation_forms.push(...relationForms);
|
|
636
3548
|
delete item._origin.relation_forms;
|
|
637
3549
|
} else {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
);
|
|
3550
|
+
// 合并关联表单
|
|
3551
|
+
const relationForms = (0, _utils.mergeRelationForms)(item._origin.relation_forms || []);
|
|
641
3552
|
item._origin.relation_forms = relationForms;
|
|
642
3553
|
order.bookings.push(item._origin);
|
|
643
3554
|
}
|
|
644
|
-
if (item
|
|
3555
|
+
if (item?.deposit) {
|
|
645
3556
|
is_deposit = 1;
|
|
646
3557
|
}
|
|
647
3558
|
});
|
|
648
|
-
if (order.type ===
|
|
649
|
-
const firstAppointmentCartItem =
|
|
650
|
-
(n) => !(0, import_utils2.isNormalProduct)(n._productOrigin)
|
|
651
|
-
)) == null ? void 0 : _a[0];
|
|
3559
|
+
if (order.type === 'appointment_booking') {
|
|
3560
|
+
const firstAppointmentCartItem = params.cartItems.filter(n => !(0, _utils2.isNormalProduct)(n._productOrigin))?.[0];
|
|
652
3561
|
if (firstAppointmentCartItem) {
|
|
653
|
-
order.schedule_date = firstAppointmentCartItem.start_date +
|
|
3562
|
+
order.schedule_date = firstAppointmentCartItem.start_date + ' ' + firstAppointmentCartItem.start_time + ':00';
|
|
654
3563
|
}
|
|
655
3564
|
}
|
|
656
3565
|
order.is_deposit = is_deposit;
|
|
@@ -658,14 +3567,17 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
658
3567
|
return order;
|
|
659
3568
|
}
|
|
660
3569
|
checkBeforeSubmitOrder(params) {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
cartItemsCount: ((_a = params.cartItems) == null ? void 0 : _a.length) || 0,
|
|
3570
|
+
this.logInfo('checkBeforeSubmitOrder called', {
|
|
3571
|
+
cartItemsCount: params.cartItems?.length || 0,
|
|
664
3572
|
type: params.type
|
|
665
3573
|
});
|
|
666
|
-
const {
|
|
667
|
-
|
|
668
|
-
|
|
3574
|
+
const {
|
|
3575
|
+
cartItems,
|
|
3576
|
+
type
|
|
3577
|
+
} = params;
|
|
3578
|
+
if (type === 'holder') {
|
|
3579
|
+
// 判断cartItems中是否有不存在holder_id的商品,如果有则返回false
|
|
3580
|
+
const hasNoHolderId = cartItems.some(item => !item.holder_id);
|
|
669
3581
|
if (hasNoHolderId) {
|
|
670
3582
|
return false;
|
|
671
3583
|
}
|
|
@@ -673,170 +3585,603 @@ var OrderModule = class extends import_BaseModule.BaseModule {
|
|
|
673
3585
|
return true;
|
|
674
3586
|
}
|
|
675
3587
|
async submitOrder(order) {
|
|
676
|
-
|
|
677
|
-
this.logInfo("submitOrder called", {
|
|
3588
|
+
this.logInfo('submitOrder called', {
|
|
678
3589
|
url: order.url,
|
|
679
3590
|
orderType: order.query.type,
|
|
680
3591
|
platform: order.query.platform,
|
|
681
|
-
cartItemsCount:
|
|
3592
|
+
cartItemsCount: order.query.cartItems?.length || 0
|
|
682
3593
|
});
|
|
683
|
-
const {
|
|
684
|
-
|
|
3594
|
+
const {
|
|
3595
|
+
url,
|
|
3596
|
+
query
|
|
3597
|
+
} = order;
|
|
3598
|
+
const fetchUrl = url || '/order/appointment';
|
|
685
3599
|
const params = this.createOrder(query);
|
|
686
|
-
this.logInfo(
|
|
3600
|
+
this.logInfo('Calling backend order API', {
|
|
687
3601
|
url: fetchUrl,
|
|
688
3602
|
orderType: params.type,
|
|
689
3603
|
platform: params.platform,
|
|
690
3604
|
isDeposit: params.is_deposit,
|
|
691
|
-
bookingsCount:
|
|
692
|
-
relationProductsCount:
|
|
3605
|
+
bookingsCount: params.bookings?.length || 0,
|
|
3606
|
+
relationProductsCount: params.relation_products?.length || 0,
|
|
693
3607
|
scheduleDate: params.schedule_date
|
|
694
3608
|
});
|
|
695
3609
|
return this.request.post(fetchUrl, params);
|
|
696
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
|
+
}
|
|
3651
|
+
|
|
697
3652
|
/**
|
|
698
3653
|
* Checkout 专用:创建订单到后端
|
|
699
|
-
*
|
|
3654
|
+
*
|
|
700
3655
|
* 专门为 Checkout 模块设计的订单创建方法,
|
|
701
|
-
*
|
|
3656
|
+
* 通过 OS Server 调用 checkout 接口创建订单
|
|
702
3657
|
* 接收已经处理好的订单数据,无需再处理购物车
|
|
703
|
-
*
|
|
3658
|
+
*
|
|
704
3659
|
* @param params Checkout 订单参数(已处理的订单数据)
|
|
705
3660
|
* @returns 后端返回的订单数据(包含订单ID等)
|
|
706
3661
|
*/
|
|
707
3662
|
async createOrderByCheckout(params) {
|
|
708
|
-
|
|
709
|
-
const onlineStorePaymentCodeList = [
|
|
710
|
-
params.payments =
|
|
711
|
-
this.logInfo(
|
|
3663
|
+
// 过滤掉由在线店铺下单的 payment 支付数据
|
|
3664
|
+
const onlineStorePaymentCodeList = ['WXPAY', 'WECHAT', 'ALIPAY', 'APPLE_PAY', 'CREDIT_CARD_3DS', 'CREDIT_CARD_TOKEN', 'GOOGLE_PAY', 'GOOGLE_PAY_3DS', 'CREDIT_CARD'];
|
|
3665
|
+
params.payments = params.payments?.filter(p => !onlineStorePaymentCodeList.includes(p.code));
|
|
3666
|
+
this.logInfo('createOrderByCheckout called', {
|
|
712
3667
|
type: params.type,
|
|
713
3668
|
platform: params.platform,
|
|
714
3669
|
is_deposit: params.is_deposit,
|
|
715
3670
|
customer_id: params.customer_id,
|
|
716
|
-
bookingsCount:
|
|
717
|
-
relationProductsCount:
|
|
718
|
-
paymentsCount:
|
|
3671
|
+
bookingsCount: params.bookings?.length || 0,
|
|
3672
|
+
relationProductsCount: params.relation_products?.length || 0,
|
|
3673
|
+
paymentsCount: params.payments?.length || 0,
|
|
719
3674
|
depositAmount: params.deposit_amount,
|
|
720
3675
|
productTaxFee: params.product_tax_fee,
|
|
721
3676
|
note: params.note,
|
|
722
3677
|
scheduleDate: params.schedule_date,
|
|
723
3678
|
hasOrderId: !!params.order_id,
|
|
724
3679
|
orderIdIncluded: params.order_id,
|
|
725
|
-
paymentMethods:
|
|
726
|
-
});
|
|
727
|
-
console.log("[Order] createOrderByCheckout 开始创建订单:", {
|
|
728
|
-
type: params.type,
|
|
729
|
-
platform: params.platform,
|
|
730
|
-
is_deposit: params.is_deposit,
|
|
731
|
-
customer_id: params.customer_id,
|
|
732
|
-
bookingsCount: ((_f = params.bookings) == null ? void 0 : _f.length) || 0,
|
|
733
|
-
relationProductsCount: ((_g = params.relation_products) == null ? void 0 : _g.length) || 0,
|
|
734
|
-
paymentsCount: ((_h = params.payments) == null ? void 0 : _h.length) || 0
|
|
3680
|
+
paymentMethods: params.payments?.map(p => p.code) || []
|
|
735
3681
|
});
|
|
736
3682
|
try {
|
|
3683
|
+
// 构建订单数据,设置默认值并允许 params 覆盖
|
|
737
3684
|
const orderData = {
|
|
738
|
-
sales_channel:
|
|
739
|
-
order_sales_channel:
|
|
740
|
-
shop_note:
|
|
741
|
-
schedule_date:
|
|
3685
|
+
sales_channel: 'my_pisel',
|
|
3686
|
+
order_sales_channel: 'online_store',
|
|
3687
|
+
shop_note: '',
|
|
3688
|
+
schedule_date: '',
|
|
742
3689
|
is_deposit: 0,
|
|
743
3690
|
bookings: [],
|
|
744
3691
|
relation_products: [],
|
|
745
3692
|
relation_forms: [],
|
|
746
3693
|
payments: [],
|
|
747
3694
|
is_full_overwrite_flag: 1,
|
|
748
|
-
...params
|
|
749
|
-
// 使用传入的参数覆盖默认值
|
|
3695
|
+
...params // 使用传入的参数覆盖默认值
|
|
750
3696
|
};
|
|
751
|
-
if (
|
|
3697
|
+
if (orderData.payments?.length) {
|
|
752
3698
|
orderData.small_ticket_data_flag = 1;
|
|
753
3699
|
}
|
|
3700
|
+
|
|
3701
|
+
// 兜底策略,如果是第一次下单,没有 order_id,且没有 schedule_date,则设置为当前时间
|
|
754
3702
|
if (!params.order_id && !orderData.schedule_date) {
|
|
755
|
-
orderData.schedule_date = (0,
|
|
3703
|
+
orderData.schedule_date = (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss');
|
|
756
3704
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
platform: orderData.platform,
|
|
761
|
-
isDeposit: orderData.is_deposit,
|
|
762
|
-
customerId: orderData.customer_id,
|
|
763
|
-
bookingsCount: ((_j = orderData.bookings) == null ? void 0 : _j.length) || 0,
|
|
764
|
-
relationProductsCount: ((_k = orderData.relation_products) == null ? void 0 : _k.length) || 0,
|
|
765
|
-
paymentsCount: ((_l = orderData.payments) == null ? void 0 : _l.length) || 0,
|
|
766
|
-
paymentsMethods: ((_m = orderData.payments) == null ? void 0 : _m.map((p) => p.code)) || []
|
|
767
|
-
});
|
|
768
|
-
this.logInfo("Calling backend checkout API", {
|
|
769
|
-
url: "/order/checkout",
|
|
3705
|
+
// 记录接口调用详情
|
|
3706
|
+
this.logInfo('Calling backend checkout API', {
|
|
3707
|
+
url: '/shop/order/checkout',
|
|
770
3708
|
orderType: orderData.type,
|
|
771
3709
|
platform: orderData.platform,
|
|
772
3710
|
isDeposit: orderData.is_deposit,
|
|
773
3711
|
customerId: orderData.customer_id,
|
|
774
3712
|
depositAmount: orderData.deposit_amount,
|
|
775
|
-
bookingsCount:
|
|
776
|
-
relationProductsCount:
|
|
777
|
-
paymentsCount:
|
|
778
|
-
paymentMethods:
|
|
3713
|
+
bookingsCount: orderData.bookings?.length || 0,
|
|
3714
|
+
relationProductsCount: orderData.relation_products?.length || 0,
|
|
3715
|
+
paymentsCount: orderData.payments?.length || 0,
|
|
3716
|
+
paymentMethods: orderData.payments?.map(p => ({
|
|
779
3717
|
code: p.code,
|
|
780
3718
|
amount: p.amount,
|
|
781
3719
|
type: p.type,
|
|
782
3720
|
hasVoucherId: !!p.voucher_id,
|
|
783
3721
|
orderPaymentType: p.order_payment_type
|
|
784
|
-
}))
|
|
3722
|
+
})) || [],
|
|
785
3723
|
productTaxFee: orderData.product_tax_fee,
|
|
786
3724
|
note: orderData.note,
|
|
787
3725
|
scheduleDate: orderData.schedule_date,
|
|
788
3726
|
hasOrderId: !!orderData.order_id,
|
|
789
3727
|
smallTicketDataFlag: orderData.small_ticket_data_flag
|
|
790
3728
|
});
|
|
791
|
-
const response = await this.request.post(
|
|
792
|
-
|
|
793
|
-
|
|
3729
|
+
const response = await this.request.post('/order/checkout', orderData, {
|
|
3730
|
+
osServer: true,
|
|
3731
|
+
customToast: () => {}
|
|
794
3732
|
});
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
hasOrderId: !!(((_r = response == null ? void 0 : response.data) == null ? void 0 : _r.order_id) || (response == null ? void 0 : response.order_id))
|
|
3733
|
+
this.logInfo('Order API called successfully', {
|
|
3734
|
+
response
|
|
798
3735
|
});
|
|
799
3736
|
return response;
|
|
800
3737
|
} catch (error) {
|
|
801
|
-
console.error(
|
|
802
|
-
this.logInfo(
|
|
3738
|
+
console.error('[Order] createOrderByCheckout 创建订单失败:', error);
|
|
3739
|
+
this.logInfo('Order API called failed', {
|
|
803
3740
|
error: error instanceof Error ? error.message : String(error)
|
|
804
3741
|
});
|
|
805
3742
|
throw error;
|
|
806
3743
|
}
|
|
807
3744
|
}
|
|
808
|
-
async
|
|
809
|
-
const {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
}
|
|
3745
|
+
async submitSalesOrder(params) {
|
|
3746
|
+
const {
|
|
3747
|
+
url,
|
|
3748
|
+
query
|
|
3749
|
+
} = params;
|
|
3750
|
+
const fetchUrl = url || '/order/sales/checkout';
|
|
3751
|
+
return this.request.post(fetchUrl, query, {
|
|
3752
|
+
osServer: true,
|
|
3753
|
+
customToast: () => {}
|
|
3754
|
+
});
|
|
813
3755
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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
|
|
3775
|
+
});
|
|
823
3776
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
|
828
3793
|
});
|
|
829
|
-
if (res.code === 200
|
|
830
|
-
this.store.
|
|
3794
|
+
if (res.code === 200) {
|
|
3795
|
+
this.store.lastOrderInfo = res.data;
|
|
831
3796
|
}
|
|
832
3797
|
return res;
|
|
833
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
|
+
}
|
|
834
4172
|
getLastOrderInfo() {
|
|
835
|
-
|
|
836
|
-
return (_b = (_a = this.store) == null ? void 0 : _a.tempOrder) == null ? void 0 : _b.lastOrderInfo;
|
|
4173
|
+
return this.store?.lastOrderInfo;
|
|
837
4174
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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 || [];
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
exports.OrderModule = OrderModule;
|