@pisell/pisellos 2.1.129 → 2.1.131
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/model/strategy/adapter/promotion/index.js +9 -0
- package/dist/modules/Order/index.d.ts +7 -6
- package/dist/modules/Order/index.js +137 -42
- package/dist/modules/Order/types.d.ts +32 -6
- package/dist/modules/Order/types.js +2 -0
- package/dist/modules/Order/utils.d.ts +73 -11
- package/dist/modules/Order/utils.js +304 -52
- package/dist/modules/SalesSummary/utils.js +33 -68
- package/dist/modules/ScanOrderLogger/providers/feishu.js +168 -60
- package/dist/modules/ScanOrderLogger/types.d.ts +6 -0
- package/dist/modules/Summary/utils.js +6 -21
- package/dist/solution/ScanOrder/index.d.ts +57 -8
- package/dist/solution/ScanOrder/index.js +1531 -583
- package/dist/solution/ScanOrder/types.d.ts +86 -26
- package/dist/solution/ScanOrder/types.js +20 -1
- package/dist/solution/ScanOrder/utils.d.ts +53 -5
- package/dist/solution/ScanOrder/utils.js +257 -37
- package/dist/solution/VenueBooking/index.d.ts +30 -10
- package/dist/solution/VenueBooking/index.js +460 -217
- package/dist/solution/VenueBooking/types.d.ts +23 -0
- package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -1
- package/dist/solution/VenueBooking/utils/dateSummary.js +1 -1
- package/dist/solution/VenueBooking/utils/resource.d.ts +11 -1
- package/dist/solution/VenueBooking/utils/resource.js +57 -21
- package/dist/solution/VenueBooking/utils/slotMerge.d.ts +5 -0
- package/dist/solution/VenueBooking/utils/slotMerge.js +33 -12
- package/dist/solution/VenueBooking/utils/timeSlot.d.ts +1 -1
- package/dist/solution/VenueBooking/utils/timeSlot.js +259 -62
- package/lib/modules/Order/index.d.ts +7 -6
- package/lib/modules/Order/index.js +123 -31
- package/lib/modules/Order/types.d.ts +32 -6
- package/lib/modules/Order/utils.d.ts +73 -11
- package/lib/modules/Order/utils.js +203 -28
- package/lib/modules/SalesSummary/utils.js +13 -47
- package/lib/modules/ScanOrderLogger/providers/feishu.js +100 -34
- package/lib/modules/ScanOrderLogger/types.d.ts +6 -0
- package/lib/modules/Summary/utils.js +4 -18
- package/lib/solution/ScanOrder/index.d.ts +57 -8
- package/lib/solution/ScanOrder/index.js +713 -117
- package/lib/solution/ScanOrder/types.d.ts +86 -26
- package/lib/solution/ScanOrder/utils.d.ts +53 -5
- package/lib/solution/ScanOrder/utils.js +186 -19
- package/lib/solution/VenueBooking/index.d.ts +30 -10
- package/lib/solution/VenueBooking/index.js +206 -51
- package/lib/solution/VenueBooking/types.d.ts +23 -0
- package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -1
- package/lib/solution/VenueBooking/utils/dateSummary.js +1 -1
- package/lib/solution/VenueBooking/utils/resource.d.ts +11 -1
- package/lib/solution/VenueBooking/utils/resource.js +15 -4
- package/lib/solution/VenueBooking/utils/slotMerge.d.ts +5 -0
- package/lib/solution/VenueBooking/utils/slotMerge.js +29 -12
- package/lib/solution/VenueBooking/utils/timeSlot.d.ts +1 -1
- package/lib/solution/VenueBooking/utils/timeSlot.js +182 -43
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import { Module, ModuleOptions, PisellCore } from '../../types';
|
|
|
2
2
|
import { BaseModule } from '../../modules/BaseModule';
|
|
3
3
|
import { VenueBookingAddLogParams, VenueBookingSlotConfig, VenueDateSummaryItem, VenueSlotSelection, VenueTimeSlotGrid } from './types';
|
|
4
4
|
import type { ScanOrderOrderProduct, ScanOrderOrderProductIdentity } from '../ScanOrder/types';
|
|
5
|
+
import type { UpdateProductInOrderParams } from '../../modules/Order/types';
|
|
5
6
|
import type { OpenDataAvailabilityResult } from '../../modules/OpenData';
|
|
6
7
|
import type { ProductData } from '../../modules/Product/types';
|
|
7
8
|
import { type CartItemSummary, type PaxInfo, type QuantityCheckResult, type QuantityLimitResult } from '../../model/strategy/adapter/itemRule';
|
|
@@ -92,25 +93,48 @@ export declare class VenueBookingImpl extends BaseModule implements Module {
|
|
|
92
93
|
/**
|
|
93
94
|
* 切换单个时段的选中状态(选中/取消)。
|
|
94
95
|
* 内部自动处理连续时段的合并与拆分,订单是唯一真相源。
|
|
96
|
+
*
|
|
97
|
+
* slot.productId 指定当前操作针对的是该 resourceId 下的哪一个商品。
|
|
98
|
+
* 同一资源下不同 productId 之间互相隔离,不会相互合并。
|
|
95
99
|
*/
|
|
96
100
|
toggleSlot(slot: VenueSlotSelection): Promise<ScanOrderOrderProduct[]>;
|
|
97
101
|
/**
|
|
98
102
|
* 获取某资源当前选中的所有独立时段(从订单中解析)。
|
|
103
|
+
* 不传 productId 时返回该资源下所有商品的选中时段;传了则精确匹配。
|
|
99
104
|
*/
|
|
100
|
-
getSelectedSlotsForResource(resourceId: number | string): VenueSlotSelection[];
|
|
105
|
+
getSelectedSlotsForResource(resourceId: number | string, productId?: number): VenueSlotSelection[];
|
|
106
|
+
/** getSelectedSlotsForResource 的 (resourceId, productId) 精确版,内部使用。 */
|
|
107
|
+
private getSelectedSlotsForResourceProduct;
|
|
101
108
|
/**
|
|
102
109
|
* 判断某个时段是否已选中。
|
|
110
|
+
* 不传 productId 时:只要该资源下任一商品在 startTime 被选中即返回 true;传了则精确匹配。
|
|
103
111
|
*/
|
|
104
|
-
isSlotSelected(resourceId: number | string, startTime: string): boolean;
|
|
112
|
+
isSlotSelected(resourceId: number | string, startTime: string, productId?: number): boolean;
|
|
105
113
|
/**
|
|
106
|
-
*
|
|
114
|
+
* 判断指定 (resourceId, productId, startTime) 格子是否应因其它已选项而被禁用。
|
|
115
|
+
* 规则:
|
|
116
|
+
* 1) 同一 resourceId 下若已选了另一个 productId 的同 startTime → 禁用
|
|
117
|
+
* 2) 当前 resource 是组合资源:若其任一 child resource 在 startTime 被选中 → 禁用
|
|
118
|
+
* 3) 当前 resource 是某些组合资源的 child:若该组合资源在 startTime 被选中 → 禁用
|
|
119
|
+
* 4) 两个组合资源的 child 集合有交集,且对方在该 startTime 被选中 → 禁用
|
|
120
|
+
*/
|
|
121
|
+
isSlotDisabledBySelection(params: {
|
|
122
|
+
resourceId: number | string;
|
|
123
|
+
productId: number;
|
|
124
|
+
startTime: string;
|
|
125
|
+
}): boolean;
|
|
126
|
+
/**
|
|
127
|
+
* 获取所有已选时段(按资源分组)。每个 slot 上带 productId,便于 UI 做 per-product 判定。
|
|
107
128
|
*/
|
|
108
129
|
getAllSelectedSlots(): Map<number | string, VenueSlotSelection[]>;
|
|
109
130
|
/**
|
|
110
|
-
*
|
|
131
|
+
* 对指定 (resourceId, productId) 的订单商品进行 reconcile:
|
|
111
132
|
* 清除旧商品 → 合并连续时段 → 重新写入。
|
|
133
|
+
* 同一场地下不同商品互不干扰,各自单独 reconcile。
|
|
112
134
|
*/
|
|
113
|
-
private
|
|
135
|
+
private reconcileOrderForResourceProduct;
|
|
136
|
+
/** 给定一个父 rawResource,返回其 combined_resource.resource_ids 对应的子 rawResource 列表。 */
|
|
137
|
+
private getCombinedChildRawResources;
|
|
114
138
|
setSlotConfig(config: Partial<VenueBookingSlotConfig>): void;
|
|
115
139
|
getSlotConfig(): VenueBookingSlotConfig;
|
|
116
140
|
loadSchedules(): Promise<void>;
|
|
@@ -142,11 +166,7 @@ export declare class VenueBookingImpl extends BaseModule implements Module {
|
|
|
142
166
|
getSummary(): Promise<import("./types").ScanOrderSummary>;
|
|
143
167
|
submitOrder<T = any>(): Promise<T>;
|
|
144
168
|
addProductToOrder(product: Partial<ScanOrderOrderProduct> & ScanOrderOrderProductIdentity): Promise<ScanOrderOrderProduct[]>;
|
|
145
|
-
updateProductInOrder(params:
|
|
146
|
-
product_id: number | null;
|
|
147
|
-
product_variant_id: number;
|
|
148
|
-
updates: Partial<ScanOrderOrderProduct>;
|
|
149
|
-
}): Promise<ScanOrderOrderProduct[]>;
|
|
169
|
+
updateProductInOrder(params: UpdateProductInOrderParams): Promise<ScanOrderOrderProduct[]>;
|
|
150
170
|
removeProductFromOrder(identity: ScanOrderOrderProductIdentity): Promise<ScanOrderOrderProduct[]>;
|
|
151
171
|
getProductList(): Promise<ProductData[]>;
|
|
152
172
|
private loadOpenDataConfig;
|
|
@@ -629,12 +629,14 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
629
629
|
}
|
|
630
630
|
this.store.rawResourceData = rawData;
|
|
631
631
|
for (const item of rawData) {
|
|
632
|
-
const
|
|
633
|
-
if (
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
632
|
+
const mappings = this.resourceProductMap.get(item.resourceId);
|
|
633
|
+
if (!mappings)
|
|
634
|
+
continue;
|
|
635
|
+
for (const mapping of mappings) {
|
|
636
|
+
if (item.main_field)
|
|
637
|
+
mapping.resourceName = item.main_field;
|
|
638
|
+
if (item.resource_form_id)
|
|
639
|
+
mapping.formId = item.resource_form_id;
|
|
638
640
|
}
|
|
639
641
|
}
|
|
640
642
|
this.logMethodSuccess("fetchResourceAvailability", {
|
|
@@ -721,7 +723,11 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
721
723
|
const resolvedSlotConfig = this.syncOperatingHoursToSlotConfig(date);
|
|
722
724
|
let quotationPriceMap;
|
|
723
725
|
if (this.store.quotation) {
|
|
724
|
-
const productIds = [
|
|
726
|
+
const productIds = [
|
|
727
|
+
...new Set(
|
|
728
|
+
[...this.resourceProductMap.values()].flat().map((m) => m.productId)
|
|
729
|
+
)
|
|
730
|
+
];
|
|
725
731
|
const timeLabels = (0, import_timeSlot.generateTimeLabels)(resolvedSlotConfig);
|
|
726
732
|
const timePoints = timeLabels.map((label) => `${date} ${label}`);
|
|
727
733
|
quotationPriceMap = this.store.quotation.buildProductPriceMap({ productIds, timePoints });
|
|
@@ -738,19 +744,31 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
738
744
|
/**
|
|
739
745
|
* 切换单个时段的选中状态(选中/取消)。
|
|
740
746
|
* 内部自动处理连续时段的合并与拆分,订单是唯一真相源。
|
|
747
|
+
*
|
|
748
|
+
* slot.productId 指定当前操作针对的是该 resourceId 下的哪一个商品。
|
|
749
|
+
* 同一资源下不同 productId 之间互相隔离,不会相互合并。
|
|
741
750
|
*/
|
|
742
751
|
async toggleSlot(slot) {
|
|
743
752
|
this.logMethodStart("toggleSlot", {
|
|
744
753
|
resourceId: slot.resourceId,
|
|
754
|
+
productId: slot.productId,
|
|
745
755
|
startTime: slot.startTime
|
|
746
756
|
});
|
|
747
757
|
try {
|
|
748
|
-
const
|
|
749
|
-
if (!
|
|
758
|
+
const mappings = this.resourceProductMap.get(slot.resourceId);
|
|
759
|
+
if (!mappings || !mappings.length) {
|
|
750
760
|
throw new Error(`未找到资源 ${slot.resourceId} 的商品映射`);
|
|
761
|
+
}
|
|
762
|
+
const mapping = mappings.find((m) => m.productId === slot.productId);
|
|
763
|
+
if (!mapping) {
|
|
764
|
+
throw new Error(`资源 ${slot.resourceId} 未关联商品 ${slot.productId}`);
|
|
765
|
+
}
|
|
751
766
|
if (!this.store.order)
|
|
752
767
|
throw new Error("order 模块未初始化");
|
|
753
|
-
const currentSlots = this.
|
|
768
|
+
const currentSlots = this.getSelectedSlotsForResourceProduct(
|
|
769
|
+
slot.resourceId,
|
|
770
|
+
slot.productId
|
|
771
|
+
);
|
|
754
772
|
const existIndex = currentSlots.findIndex((s) => s.startTime === slot.startTime);
|
|
755
773
|
let nextSlots;
|
|
756
774
|
if (existIndex !== -1) {
|
|
@@ -758,13 +776,18 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
758
776
|
} else {
|
|
759
777
|
nextSlots = [...currentSlots, slot];
|
|
760
778
|
}
|
|
761
|
-
await this.
|
|
779
|
+
await this.reconcileOrderForResourceProduct(
|
|
780
|
+
slot.resourceId,
|
|
781
|
+
slot.productId,
|
|
782
|
+
nextSlots
|
|
783
|
+
);
|
|
762
784
|
const products = this.store.order.getOrderProducts();
|
|
763
785
|
await this.refreshItemRuleQuantityLimits();
|
|
764
786
|
await this.refreshCartValidationPassed();
|
|
765
787
|
this.logMethodSuccess("toggleSlot", {
|
|
766
788
|
action: existIndex !== -1 ? "remove" : "add",
|
|
767
789
|
resourceId: slot.resourceId,
|
|
790
|
+
productId: slot.productId,
|
|
768
791
|
slotCount: nextSlots.length
|
|
769
792
|
});
|
|
770
793
|
return products;
|
|
@@ -775,33 +798,106 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
775
798
|
}
|
|
776
799
|
/**
|
|
777
800
|
* 获取某资源当前选中的所有独立时段(从订单中解析)。
|
|
801
|
+
* 不传 productId 时返回该资源下所有商品的选中时段;传了则精确匹配。
|
|
778
802
|
*/
|
|
779
|
-
getSelectedSlotsForResource(resourceId) {
|
|
803
|
+
getSelectedSlotsForResource(resourceId, productId) {
|
|
780
804
|
var _a;
|
|
781
805
|
const tempOrder = (_a = this.store.order) == null ? void 0 : _a.getTempOrder();
|
|
782
806
|
if (!tempOrder)
|
|
783
807
|
return [];
|
|
784
|
-
const venueProducts = tempOrder.products.filter(
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
return
|
|
788
|
-
|
|
789
|
-
|
|
808
|
+
const venueProducts = tempOrder.products.filter((p) => {
|
|
809
|
+
var _a2, _b;
|
|
810
|
+
if (!((_a2 = p.metadata) == null ? void 0 : _a2.venue_booking))
|
|
811
|
+
return false;
|
|
812
|
+
if (String((_b = p.metadata) == null ? void 0 : _b.resource_id) !== String(resourceId))
|
|
813
|
+
return false;
|
|
814
|
+
if (productId != null && Number(p.product_id) !== Number(productId))
|
|
815
|
+
return false;
|
|
816
|
+
return true;
|
|
817
|
+
});
|
|
790
818
|
const slotDuration = this.store.slotConfig.slotDurationMinutes;
|
|
791
819
|
const slots = [];
|
|
792
820
|
for (const product of venueProducts) {
|
|
793
|
-
slots.push(
|
|
821
|
+
slots.push(
|
|
822
|
+
...(0, import_slotMerge.expandMergedSlotToIndividual)(product, slotDuration).map((s) => ({
|
|
823
|
+
...s,
|
|
824
|
+
productId: Number(product.product_id)
|
|
825
|
+
}))
|
|
826
|
+
);
|
|
794
827
|
}
|
|
795
828
|
return slots.sort((a, b) => a.startTime.localeCompare(b.startTime));
|
|
796
829
|
}
|
|
830
|
+
/** getSelectedSlotsForResource 的 (resourceId, productId) 精确版,内部使用。 */
|
|
831
|
+
getSelectedSlotsForResourceProduct(resourceId, productId) {
|
|
832
|
+
return this.getSelectedSlotsForResource(resourceId, productId);
|
|
833
|
+
}
|
|
797
834
|
/**
|
|
798
835
|
* 判断某个时段是否已选中。
|
|
836
|
+
* 不传 productId 时:只要该资源下任一商品在 startTime 被选中即返回 true;传了则精确匹配。
|
|
799
837
|
*/
|
|
800
|
-
isSlotSelected(resourceId, startTime) {
|
|
801
|
-
return this.getSelectedSlotsForResource(resourceId).some((s) => s.startTime === startTime);
|
|
838
|
+
isSlotSelected(resourceId, startTime, productId) {
|
|
839
|
+
return this.getSelectedSlotsForResource(resourceId, productId).some((s) => s.startTime === startTime);
|
|
802
840
|
}
|
|
803
841
|
/**
|
|
804
|
-
*
|
|
842
|
+
* 判断指定 (resourceId, productId, startTime) 格子是否应因其它已选项而被禁用。
|
|
843
|
+
* 规则:
|
|
844
|
+
* 1) 同一 resourceId 下若已选了另一个 productId 的同 startTime → 禁用
|
|
845
|
+
* 2) 当前 resource 是组合资源:若其任一 child resource 在 startTime 被选中 → 禁用
|
|
846
|
+
* 3) 当前 resource 是某些组合资源的 child:若该组合资源在 startTime 被选中 → 禁用
|
|
847
|
+
* 4) 两个组合资源的 child 集合有交集,且对方在该 startTime 被选中 → 禁用
|
|
848
|
+
*/
|
|
849
|
+
isSlotDisabledBySelection(params) {
|
|
850
|
+
const { resourceId, productId, startTime } = params;
|
|
851
|
+
const allSelected = this.getAllSelectedSlots();
|
|
852
|
+
const getSelectedAt = (resId) => {
|
|
853
|
+
const list = allSelected.get(resId) || [];
|
|
854
|
+
return list.filter((s) => s.startTime === startTime);
|
|
855
|
+
};
|
|
856
|
+
const sameResourceSelections = getSelectedAt(resourceId);
|
|
857
|
+
if (sameResourceSelections.some((s) => Number(s.productId) !== Number(productId))) {
|
|
858
|
+
return true;
|
|
859
|
+
}
|
|
860
|
+
const getCombinedChildIds = (resId) => {
|
|
861
|
+
const raw = this.store.rawResourceData.find(
|
|
862
|
+
(r) => String(r.resourceId) === String(resId)
|
|
863
|
+
);
|
|
864
|
+
const combined = raw == null ? void 0 : raw.combined_resource;
|
|
865
|
+
if (combined && combined.status === 1 && Array.isArray(combined.resource_ids)) {
|
|
866
|
+
return combined.resource_ids;
|
|
867
|
+
}
|
|
868
|
+
return [];
|
|
869
|
+
};
|
|
870
|
+
const currentChildIds = getCombinedChildIds(resourceId);
|
|
871
|
+
const currentIsCombined = currentChildIds.length > 0;
|
|
872
|
+
if (currentIsCombined) {
|
|
873
|
+
for (const childId of currentChildIds) {
|
|
874
|
+
if (getSelectedAt(childId).length > 0)
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
for (const [selectedResId, slots] of allSelected) {
|
|
879
|
+
if (!slots.some((s) => s.startTime === startTime))
|
|
880
|
+
continue;
|
|
881
|
+
if (String(selectedResId) === String(resourceId))
|
|
882
|
+
continue;
|
|
883
|
+
const selectedChildIds = getCombinedChildIds(selectedResId);
|
|
884
|
+
if (selectedChildIds.length === 0)
|
|
885
|
+
continue;
|
|
886
|
+
if (selectedChildIds.some((id) => String(id) === String(resourceId))) {
|
|
887
|
+
return true;
|
|
888
|
+
}
|
|
889
|
+
if (currentIsCombined) {
|
|
890
|
+
const intersect = selectedChildIds.some(
|
|
891
|
+
(id) => currentChildIds.some((cid) => String(cid) === String(id))
|
|
892
|
+
);
|
|
893
|
+
if (intersect)
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* 获取所有已选时段(按资源分组)。每个 slot 上带 productId,便于 UI 做 per-product 判定。
|
|
805
901
|
*/
|
|
806
902
|
getAllSelectedSlots() {
|
|
807
903
|
var _a, _b;
|
|
@@ -818,8 +914,14 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
818
914
|
const resourceId = (_b = product.metadata) == null ? void 0 : _b.resource_id;
|
|
819
915
|
if (resourceId == null)
|
|
820
916
|
continue;
|
|
917
|
+
const productId = Number(product.product_id);
|
|
821
918
|
const existing = result.get(resourceId) || [];
|
|
822
|
-
existing.push(
|
|
919
|
+
existing.push(
|
|
920
|
+
...(0, import_slotMerge.expandMergedSlotToIndividual)(product, slotDuration).map((s) => ({
|
|
921
|
+
...s,
|
|
922
|
+
productId
|
|
923
|
+
}))
|
|
924
|
+
);
|
|
823
925
|
result.set(resourceId, existing);
|
|
824
926
|
}
|
|
825
927
|
for (const [key, slots] of result) {
|
|
@@ -828,24 +930,33 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
828
930
|
return result;
|
|
829
931
|
}
|
|
830
932
|
/**
|
|
831
|
-
*
|
|
933
|
+
* 对指定 (resourceId, productId) 的订单商品进行 reconcile:
|
|
832
934
|
* 清除旧商品 → 合并连续时段 → 重新写入。
|
|
935
|
+
* 同一场地下不同商品互不干扰,各自单独 reconcile。
|
|
833
936
|
*/
|
|
834
|
-
async
|
|
835
|
-
const
|
|
836
|
-
if (!
|
|
937
|
+
async reconcileOrderForResourceProduct(resourceId, productId, slots) {
|
|
938
|
+
const mappings = this.resourceProductMap.get(resourceId);
|
|
939
|
+
if (!mappings || !mappings.length || !this.store.order)
|
|
940
|
+
return;
|
|
941
|
+
const mapping = mappings.find((m) => m.productId === productId);
|
|
942
|
+
if (!mapping)
|
|
837
943
|
return;
|
|
838
944
|
const tempOrder = this.store.order.ensureTempOrder();
|
|
945
|
+
const matchesCurrent = (meta, pid) => !!(meta == null ? void 0 : meta.venue_booking) && String(meta == null ? void 0 : meta.resource_id) === String(resourceId) && Number(pid) === Number(productId);
|
|
839
946
|
tempOrder.products = tempOrder.products.filter(
|
|
840
|
-
(p) =>
|
|
841
|
-
var _a, _b;
|
|
842
|
-
return !(((_a = p.metadata) == null ? void 0 : _a.venue_booking) && String((_b = p.metadata) == null ? void 0 : _b.resource_id) === String(resourceId));
|
|
843
|
-
}
|
|
947
|
+
(p) => !matchesCurrent(p.metadata, p.product_id)
|
|
844
948
|
);
|
|
845
949
|
tempOrder.bookings = (tempOrder.bookings || []).filter(
|
|
846
950
|
(b) => {
|
|
847
|
-
var _a, _b;
|
|
848
|
-
|
|
951
|
+
var _a, _b, _c, _d;
|
|
952
|
+
if (!((_a = b.metadata) == null ? void 0 : _a.venue_booking))
|
|
953
|
+
return true;
|
|
954
|
+
if (String((_b = b.metadata) == null ? void 0 : _b.resource_id) !== String(resourceId))
|
|
955
|
+
return true;
|
|
956
|
+
const bookingProductId = ((_c = b.metadata) == null ? void 0 : _c.product_id) ?? ((_d = b == null ? void 0 : b.product) == null ? void 0 : _d.product_id);
|
|
957
|
+
if (bookingProductId == null)
|
|
958
|
+
return false;
|
|
959
|
+
return Number(bookingProductId) !== Number(productId);
|
|
849
960
|
}
|
|
850
961
|
);
|
|
851
962
|
if (!slots.length) {
|
|
@@ -858,6 +969,7 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
858
969
|
const rawResource = this.store.rawResourceData.find(
|
|
859
970
|
(r) => String(r.resourceId) === String(resourceId)
|
|
860
971
|
);
|
|
972
|
+
const childRawResources = this.getCombinedChildRawResources(rawResource);
|
|
861
973
|
const venueProduct = this.getVenueProducts().find((p) => p.id === mapping.productId);
|
|
862
974
|
tempOrder.bookings = tempOrder.bookings || [];
|
|
863
975
|
for (let i = 0; i < merged.length; i++) {
|
|
@@ -868,6 +980,28 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
868
980
|
const endMoment = (0, import_dayjs.default)(group.endTime, "YYYY-MM-DD HH:mm");
|
|
869
981
|
const duration = endMoment.diff(startMoment, "minute");
|
|
870
982
|
const customDepositData = cloneCustomDepositData(venueProduct == null ? void 0 : venueProduct.custom_deposit_data);
|
|
983
|
+
const resourceEntry = {
|
|
984
|
+
relation_type: "form",
|
|
985
|
+
like_status: "common",
|
|
986
|
+
id: resourceId,
|
|
987
|
+
main_field: mapping.resourceName,
|
|
988
|
+
form_id: (rawResource == null ? void 0 : rawResource.form_id) ?? mapping.formId,
|
|
989
|
+
relation_id: resourceId,
|
|
990
|
+
capacity: 1,
|
|
991
|
+
metadata: {}
|
|
992
|
+
};
|
|
993
|
+
if (childRawResources && childRawResources.length) {
|
|
994
|
+
resourceEntry.children = childRawResources.map((child) => ({
|
|
995
|
+
relation_type: "form",
|
|
996
|
+
like_status: "common",
|
|
997
|
+
id: child.resourceId,
|
|
998
|
+
main_field: child.main_field || "",
|
|
999
|
+
form_id: child.form_id ?? child.formId,
|
|
1000
|
+
relation_id: child.resourceId,
|
|
1001
|
+
capacity: child.capacity ?? 1,
|
|
1002
|
+
metadata: {}
|
|
1003
|
+
}));
|
|
1004
|
+
}
|
|
871
1005
|
tempOrder.products.push(
|
|
872
1006
|
(0, import_utils.normalizeOrderProduct)({
|
|
873
1007
|
product_id: mapping.productId,
|
|
@@ -914,16 +1048,7 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
914
1048
|
sub_type: "minutes",
|
|
915
1049
|
duration,
|
|
916
1050
|
like_status: "common",
|
|
917
|
-
resources: [
|
|
918
|
-
relation_type: "form",
|
|
919
|
-
like_status: "common",
|
|
920
|
-
id: resourceId,
|
|
921
|
-
main_field: mapping.resourceName,
|
|
922
|
-
form_id: (rawResource == null ? void 0 : rawResource.form_id) ?? mapping.formId,
|
|
923
|
-
relation_id: resourceId,
|
|
924
|
-
capacity: 1,
|
|
925
|
-
metadata: {}
|
|
926
|
-
}],
|
|
1051
|
+
resources: [resourceEntry],
|
|
927
1052
|
schedule_id: 0,
|
|
928
1053
|
select_date: startMoment.format("YYYY-MM-DD"),
|
|
929
1054
|
start_date: startMoment.format("YYYY-MM-DD"),
|
|
@@ -944,7 +1069,8 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
944
1069
|
mapping,
|
|
945
1070
|
rawResource,
|
|
946
1071
|
bookingUuid,
|
|
947
|
-
productUid: identityKey
|
|
1072
|
+
productUid: identityKey,
|
|
1073
|
+
childResources: childRawResources
|
|
948
1074
|
});
|
|
949
1075
|
tempOrder.bookings.push(booking);
|
|
950
1076
|
}
|
|
@@ -952,6 +1078,24 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
952
1078
|
await this.store.order.recalculateSummary({ createIfMissing: true });
|
|
953
1079
|
this.store.order.persistTempOrder();
|
|
954
1080
|
}
|
|
1081
|
+
/** 给定一个父 rawResource,返回其 combined_resource.resource_ids 对应的子 rawResource 列表。 */
|
|
1082
|
+
getCombinedChildRawResources(rawResource) {
|
|
1083
|
+
if (!rawResource)
|
|
1084
|
+
return void 0;
|
|
1085
|
+
const combined = rawResource.combined_resource;
|
|
1086
|
+
if (!combined || combined.status !== 1 || !Array.isArray(combined.resource_ids) || !combined.resource_ids.length) {
|
|
1087
|
+
return void 0;
|
|
1088
|
+
}
|
|
1089
|
+
const children = [];
|
|
1090
|
+
for (const id of combined.resource_ids) {
|
|
1091
|
+
const child = this.store.rawResourceData.find(
|
|
1092
|
+
(r) => String(r.resourceId) === String(id)
|
|
1093
|
+
);
|
|
1094
|
+
if (child)
|
|
1095
|
+
children.push(child);
|
|
1096
|
+
}
|
|
1097
|
+
return children.length ? children : void 0;
|
|
1098
|
+
}
|
|
955
1099
|
// ─── 配置 ───
|
|
956
1100
|
setSlotConfig(config) {
|
|
957
1101
|
this.baseSlotConfig = {
|
|
@@ -1034,7 +1178,10 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
1034
1178
|
const now = (0, import_dayjs.default)().format("YYYY-MM-DD HH:mm:ss");
|
|
1035
1179
|
for (const product of tempOrder.products) {
|
|
1036
1180
|
if ((_b = product.metadata) == null ? void 0 : _b.venue_booking) {
|
|
1037
|
-
const
|
|
1181
|
+
const mappings = this.resourceProductMap.get(product.metadata.resource_id);
|
|
1182
|
+
if (!mappings || !mappings.length)
|
|
1183
|
+
continue;
|
|
1184
|
+
const mapping = mappings.find((m) => Number(m.productId) === Number(product.product_id)) || mappings[0];
|
|
1038
1185
|
if (!mapping)
|
|
1039
1186
|
continue;
|
|
1040
1187
|
const slots = (0, import_slotMerge.expandMergedSlotToIndividual)(
|
|
@@ -1043,6 +1190,7 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
1043
1190
|
);
|
|
1044
1191
|
const updatedSlots = slots.map((slot) => ({
|
|
1045
1192
|
...slot,
|
|
1193
|
+
productId: mapping.productId,
|
|
1046
1194
|
price: this.store.quotation.getPriceForProduct({
|
|
1047
1195
|
productId: mapping.productId,
|
|
1048
1196
|
datetime: slot.startTime
|
|
@@ -1096,7 +1244,7 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
1096
1244
|
}
|
|
1097
1245
|
/** 勾选/取消勾选某张折扣券,重新计算折扣并持久化 */
|
|
1098
1246
|
async setDiscountSelected(params) {
|
|
1099
|
-
var _a, _b, _c, _d, _e, _f;
|
|
1247
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1100
1248
|
this.logMethodStart("setDiscountSelected", params);
|
|
1101
1249
|
try {
|
|
1102
1250
|
if (!this.store.order)
|
|
@@ -1163,11 +1311,18 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
1163
1311
|
(sum, pd) => sum + (pd.amount || 0),
|
|
1164
1312
|
0
|
|
1165
1313
|
);
|
|
1166
|
-
const
|
|
1167
|
-
product.
|
|
1314
|
+
const optionSum = (0, import_utils3.sumOptionUnitPrice)(product.product_option_item);
|
|
1315
|
+
const sourcePrice = ((_d = product.metadata) == null ? void 0 : _d.source_product_price) ?? (((_e = product.metadata) == null ? void 0 : _e.main_product_original_price) != null ? new import_decimal.default(Number(product.metadata.main_product_original_price) || 0).minus(optionSum).toFixed(2) : product.original_price ?? "0");
|
|
1316
|
+
const newSourceSellingPrice = new import_decimal.default(Number(sourcePrice) || 0).minus(totalPerUnitDiscount).toDecimalPlaces(2).toString();
|
|
1317
|
+
const newMainSellingPrice = new import_decimal.default(Number(newSourceSellingPrice) || 0).plus(optionSum).toDecimalPlaces(2).toFixed(2);
|
|
1168
1318
|
if (product.metadata) {
|
|
1169
|
-
product.metadata.main_product_selling_price =
|
|
1319
|
+
product.metadata.main_product_selling_price = newMainSellingPrice;
|
|
1320
|
+
product.metadata.price_schema_version = 2;
|
|
1170
1321
|
}
|
|
1322
|
+
product.selling_price = (0, import_utils3.composeLinePrice)({
|
|
1323
|
+
mainPrice: newMainSellingPrice,
|
|
1324
|
+
bundle: product.product_bundle
|
|
1325
|
+
});
|
|
1171
1326
|
}
|
|
1172
1327
|
import_Order.OrderModule.populateSavedAmounts(tempOrder.products, nextDiscountList);
|
|
1173
1328
|
await (discountModule == null ? void 0 : discountModule.setDiscountList(nextDiscountList));
|
|
@@ -1175,8 +1330,8 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
|
|
|
1175
1330
|
const afterApplyTarget = this.store.order.getDiscountList().find((d) => d.id === params.discountId) || null;
|
|
1176
1331
|
await this.store.order.recalculateSummary({ createIfMissing: true });
|
|
1177
1332
|
this.store.order.persistTempOrder();
|
|
1178
|
-
const finalSummary = ((
|
|
1179
|
-
const finalProduct = ((
|
|
1333
|
+
const finalSummary = ((_f = this.store.order.getTempOrder()) == null ? void 0 : _f.summary) || null;
|
|
1334
|
+
const finalProduct = ((_h = (_g = this.store.order.getTempOrder()) == null ? void 0 : _g.products) == null ? void 0 : _h[0]) || null;
|
|
1180
1335
|
this.logMethodSuccess("setDiscountSelected", params);
|
|
1181
1336
|
const finalTarget = this.store.order.getDiscountList().find((d) => d.id === params.discountId) || null;
|
|
1182
1337
|
return this.store.order.getDiscountList();
|
|
@@ -36,14 +36,31 @@ export interface VenueTimeSlot {
|
|
|
36
36
|
resourceFormId: number | string;
|
|
37
37
|
capacity: number | null;
|
|
38
38
|
remainingCapacity: number | null;
|
|
39
|
+
/** per-product sub-row slot 上带的 productId;外层合并 slot 不写 */
|
|
40
|
+
productId?: number;
|
|
41
|
+
}
|
|
42
|
+
export interface VenueResourceSubRow {
|
|
43
|
+
productId: number;
|
|
44
|
+
productTitle: string;
|
|
45
|
+
price: string;
|
|
46
|
+
slots: VenueTimeSlot[];
|
|
47
|
+
}
|
|
48
|
+
export interface VenueResourceCombinedInfo {
|
|
49
|
+
resourceIds: Array<number | string>;
|
|
39
50
|
}
|
|
40
51
|
export interface VenueResourceRow {
|
|
41
52
|
resourceId: number | string;
|
|
42
53
|
resourceFormId: number | string;
|
|
43
54
|
resourceName: string;
|
|
55
|
+
/** 单商品时取唯一 mapping.productId;多商品时取第一个 mapping(兼容字段) */
|
|
44
56
|
productId: number;
|
|
45
57
|
productTitle: string;
|
|
46
58
|
slots: VenueTimeSlot[];
|
|
59
|
+
/** 多商品共享同一场地时的子行;单商品不输出 */
|
|
60
|
+
products?: VenueResourceSubRow[];
|
|
61
|
+
hasMultipleProducts?: boolean;
|
|
62
|
+
/** 当前资源是 combined_resource.status===1 时输出 */
|
|
63
|
+
combinedResource?: VenueResourceCombinedInfo;
|
|
47
64
|
}
|
|
48
65
|
export interface VenueTimeSlotGrid {
|
|
49
66
|
date: string;
|
|
@@ -57,6 +74,10 @@ export interface VenueDateSummaryItem {
|
|
|
57
74
|
totalSlots: number;
|
|
58
75
|
availableSlots: number;
|
|
59
76
|
}
|
|
77
|
+
export interface VenueResourceCombinedRaw {
|
|
78
|
+
status: number;
|
|
79
|
+
resource_ids: Array<number | string>;
|
|
80
|
+
}
|
|
60
81
|
export interface VenueResourceRawData {
|
|
61
82
|
resourceId: number | string;
|
|
62
83
|
formId: number | string;
|
|
@@ -69,6 +90,7 @@ export interface VenueResourceRawData {
|
|
|
69
90
|
end_at: string;
|
|
70
91
|
[key: string]: any;
|
|
71
92
|
}>;
|
|
93
|
+
combined_resource?: VenueResourceCombinedRaw;
|
|
72
94
|
[key: string]: any;
|
|
73
95
|
}
|
|
74
96
|
export interface ResourceProductMapping {
|
|
@@ -81,6 +103,7 @@ export interface ResourceProductMapping {
|
|
|
81
103
|
}
|
|
82
104
|
export interface VenueSlotSelection {
|
|
83
105
|
resourceId: number | string;
|
|
106
|
+
productId: number;
|
|
84
107
|
startTime: string;
|
|
85
108
|
endTime: string;
|
|
86
109
|
price: string;
|
|
@@ -5,7 +5,7 @@ export declare function buildDateRangeSummary(params: {
|
|
|
5
5
|
endDate: string;
|
|
6
6
|
config: VenueBookingSlotConfig;
|
|
7
7
|
rawResources: VenueResourceRawData[];
|
|
8
|
-
resourceProductMap: Map<number | string, ResourceProductMapping>;
|
|
8
|
+
resourceProductMap: Map<number | string, ResourceProductMapping[]>;
|
|
9
9
|
quotationModule?: QuotationModule;
|
|
10
10
|
resolveConfig?: (date: string) => VenueBookingSlotConfig;
|
|
11
11
|
}): VenueDateSummaryItem[];
|
|
@@ -45,7 +45,7 @@ function buildDateRangeSummary(params) {
|
|
|
45
45
|
resolveConfig
|
|
46
46
|
} = params;
|
|
47
47
|
const result = [];
|
|
48
|
-
const productIds = quotationModule ? [...new Set([...resourceProductMap.values()].map((m) => m.productId))] : [];
|
|
48
|
+
const productIds = quotationModule ? [...new Set([...resourceProductMap.values()].flat().map((m) => m.productId))] : [];
|
|
49
49
|
let cursor = (0, import_dayjs.default)(startDate);
|
|
50
50
|
const end = (0, import_dayjs.default)(endDate);
|
|
51
51
|
while (!cursor.isAfter(end)) {
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { ProductData } from '../../../modules/Product/types';
|
|
2
2
|
import type { ResourceProductMapping } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* 从商品列表里抽取所有 resource_id:
|
|
5
|
+
* - 商品绑定的 default_resource / optional_resource
|
|
6
|
+
* - 以及这些资源上声明的 combined_resource.resource_ids(若可从 product_resource 读取)
|
|
7
|
+
* 用于一次性拉到子资源数据,判定组合资源的可用性。
|
|
8
|
+
*/
|
|
3
9
|
export declare function extractResourceIds(products: ProductData[]): number[];
|
|
4
|
-
|
|
10
|
+
/**
|
|
11
|
+
* 每个 resourceId 可以被多个商品共享(同一场地关联多种运动商品)。
|
|
12
|
+
* Map 的 value 改为 ResourceProductMapping[],按商品出现顺序保留。
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildResourceProductMap(products: ProductData[]): Map<number | string, ResourceProductMapping[]>;
|
|
@@ -39,6 +39,13 @@ function extractResourceIds(products) {
|
|
|
39
39
|
if (typeof id === "number" && id > 0)
|
|
40
40
|
ids.add(id);
|
|
41
41
|
}
|
|
42
|
+
const combined = resource.combined_resource;
|
|
43
|
+
if (combined && combined.status === 1 && Array.isArray(combined.resource_ids)) {
|
|
44
|
+
for (const id of combined.resource_ids) {
|
|
45
|
+
if (typeof id === "number" && id > 0)
|
|
46
|
+
ids.add(id);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
51
|
return Array.from(ids);
|
|
@@ -58,16 +65,20 @@ function buildResourceProductMap(products) {
|
|
|
58
65
|
...resource.optional_resource || []
|
|
59
66
|
];
|
|
60
67
|
for (const resId of allResourceIds) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
map.set(resId, {
|
|
68
|
+
const existing = map.get(resId);
|
|
69
|
+
const mapping = {
|
|
64
70
|
productId: product.id,
|
|
65
71
|
productTitle: product.title,
|
|
66
72
|
resourceName: resource.title,
|
|
67
73
|
formId: resource.id,
|
|
68
74
|
price: product.price || "0.00",
|
|
69
75
|
resourceType: resource.type || "single"
|
|
70
|
-
}
|
|
76
|
+
};
|
|
77
|
+
if (!existing) {
|
|
78
|
+
map.set(resId, [mapping]);
|
|
79
|
+
} else if (!existing.some((m) => m.productId === mapping.productId)) {
|
|
80
|
+
existing.push(mapping);
|
|
81
|
+
}
|
|
71
82
|
}
|
|
72
83
|
}
|
|
73
84
|
}
|
|
@@ -19,6 +19,11 @@ export interface BuildVenueBookingParams {
|
|
|
19
19
|
rawResource: VenueResourceRawData | undefined;
|
|
20
20
|
bookingUuid: string;
|
|
21
21
|
productUid: string;
|
|
22
|
+
/**
|
|
23
|
+
* 若当前 resource 是组合资源(combined_resource.status === 1),传入其子资源的原始数据,
|
|
24
|
+
* 将作为 resources[0].children 写入 booking,结构与父 resource 保持一致。
|
|
25
|
+
*/
|
|
26
|
+
childResources?: VenueResourceRawData[];
|
|
22
27
|
}
|
|
23
28
|
export declare function buildVenueBookingEntry(params: BuildVenueBookingParams): Record<string, any>;
|
|
24
29
|
/**
|