@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
|
@@ -11,6 +11,10 @@ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" ==
|
|
|
11
11
|
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
|
|
12
12
|
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
13
13
|
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
14
|
+
import dayjs from 'dayjs';
|
|
15
|
+
import Decimal from 'decimal.js';
|
|
16
|
+
import { composeLinePrice, createUuidV4, sumOptionUnitPrice } from "../../modules/Order/utils";
|
|
17
|
+
|
|
14
18
|
/**
|
|
15
19
|
* 构建金额全为 0 的空 summary。
|
|
16
20
|
* 作为尚未计算金额时的兜底默认值,避免下游因 undefined 报错。
|
|
@@ -366,14 +370,75 @@ export function attachItemRuleLimitsToTopLevelProducts(productList, limits) {
|
|
|
366
370
|
}
|
|
367
371
|
|
|
368
372
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
|
|
373
|
+
* 将选项行 / 套餐行归一成稳定 JSON,用于「同 SKU 是否合并」判断。
|
|
374
|
+
* 数组元素先按主键排序再序列化,避免顺序差异导致误判为不同行。
|
|
375
|
+
*/
|
|
376
|
+
export function buildProductLineFingerprint(productOptionItem, productBundle) {
|
|
377
|
+
var optArr = Array.isArray(productOptionItem) ? productOptionItem : [];
|
|
378
|
+
var normalizedOpts = optArr.map(function (item) {
|
|
379
|
+
var _item$price;
|
|
380
|
+
return {
|
|
381
|
+
product_option_item_id: Number(item === null || item === void 0 ? void 0 : item.product_option_item_id) || 0,
|
|
382
|
+
option_group_id: Number(item === null || item === void 0 ? void 0 : item.option_group_id) || 0,
|
|
383
|
+
num: typeof (item === null || item === void 0 ? void 0 : item.num) === 'number' && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1,
|
|
384
|
+
price: String((_item$price = item === null || item === void 0 ? void 0 : item.price) !== null && _item$price !== void 0 ? _item$price : '')
|
|
385
|
+
};
|
|
386
|
+
}).sort(function (p, q) {
|
|
387
|
+
if (p.product_option_item_id !== q.product_option_item_id) {
|
|
388
|
+
return p.product_option_item_id - q.product_option_item_id;
|
|
389
|
+
}
|
|
390
|
+
if (p.option_group_id !== q.option_group_id) return p.option_group_id - q.option_group_id;
|
|
391
|
+
if (p.num !== q.num) return p.num - q.num;
|
|
392
|
+
return p.price.localeCompare(q.price);
|
|
393
|
+
});
|
|
394
|
+
var bundleArr = Array.isArray(productBundle) ? productBundle : [];
|
|
395
|
+
var normalizedBundles = bundleArr.map(function (item) {
|
|
396
|
+
var _ref, _item$bundle_id;
|
|
397
|
+
return {
|
|
398
|
+
bundle_id: Number((_ref = (_item$bundle_id = item === null || item === void 0 ? void 0 : item.bundle_id) !== null && _item$bundle_id !== void 0 ? _item$bundle_id : item === null || item === void 0 ? void 0 : item.id) !== null && _ref !== void 0 ? _ref : 0) || 0,
|
|
399
|
+
product_id: Number(item === null || item === void 0 ? void 0 : item.product_id) || 0,
|
|
400
|
+
num: typeof (item === null || item === void 0 ? void 0 : item.num) === 'number' && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1
|
|
401
|
+
};
|
|
402
|
+
}).sort(function (p, q) {
|
|
403
|
+
if (p.bundle_id !== q.bundle_id) return p.bundle_id - q.bundle_id;
|
|
404
|
+
if (p.product_id !== q.product_id) return p.product_id - q.product_id;
|
|
405
|
+
return p.num - q.num;
|
|
406
|
+
});
|
|
407
|
+
return JSON.stringify([normalizedOpts, normalizedBundles]);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* removeProductFromOrder 仅传 SKU 时的通配 identity(对象上未声明选项键)。
|
|
412
|
+
* 与显式 `product_option_item: []` 区分:后者只匹配「无选项」行。
|
|
413
|
+
*/
|
|
414
|
+
export function isSkuOnlyDeleteIdentity(x) {
|
|
415
|
+
if (x.identity_key) return false;
|
|
416
|
+
return !('product_option_item' in x) && !('product_bundle' in x);
|
|
417
|
+
}
|
|
418
|
+
function fingerprintForIdentityWithOptionKeys(x) {
|
|
419
|
+
var row = x;
|
|
420
|
+
var opts = 'product_option_item' in row ? Array.isArray(row.product_option_item) ? row.product_option_item : [] : [];
|
|
421
|
+
var buds = 'product_bundle' in row ? Array.isArray(row.product_bundle) ? row.product_bundle : [] : [];
|
|
422
|
+
return buildProductLineFingerprint(opts, buds);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* 判断两个商品 identity 是否匹配(不透明 identity 契约)。
|
|
427
|
+
*
|
|
428
|
+
* 调用约定:始终 `isIdentityMatch(line, callerIdentity)`。
|
|
429
|
+
* - 若 SKU(product_id + product_variant_id)不一致 → 不匹配。
|
|
430
|
+
* - **双方都带** `identity_key` → 严格字符串相等比较(不再猜测合成 key / metadata 桥接)。
|
|
431
|
+
* 这是 opaque identity 契约的标准路径:UI 删改时透传 SDK 回灌的 identity_key。
|
|
432
|
+
* - 否则(即至少一侧未声明 identity_key)→ 进入「SKU 通配 / 显式空选项 / 指纹」回退路径,
|
|
433
|
+
* 忽略线侧 identity_key,按内容语义匹配。这给「行已 opaque、调用方却想按 SKU 通配 / 选项指纹删除」留兼容入口。
|
|
372
434
|
*/
|
|
373
435
|
export function isIdentityMatch(a, b) {
|
|
374
436
|
if (a.product_id !== b.product_id || a.product_variant_id !== b.product_variant_id) return false;
|
|
375
|
-
|
|
376
|
-
|
|
437
|
+
var aHasKey = typeof a.identity_key === 'string' && a.identity_key.length > 0;
|
|
438
|
+
var bHasKey = typeof b.identity_key === 'string' && b.identity_key.length > 0;
|
|
439
|
+
if (aHasKey && bHasKey) return a.identity_key === b.identity_key;
|
|
440
|
+
if (isSkuOnlyDeleteIdentity(a) || isSkuOnlyDeleteIdentity(b)) return true;
|
|
441
|
+
return fingerprintForIdentityWithOptionKeys(a) === fingerprintForIdentityWithOptionKeys(b);
|
|
377
442
|
}
|
|
378
443
|
|
|
379
444
|
/**
|
|
@@ -387,34 +452,35 @@ export function getProductIdentityIndex(products, identity) {
|
|
|
387
452
|
}
|
|
388
453
|
|
|
389
454
|
/**
|
|
390
|
-
*
|
|
455
|
+
* 对外部传入的商品对象做归一化(v2 composite 语义):
|
|
391
456
|
* - 补全可选字段默认值(未传则使用兜底值,避免后续计算时因 undefined 导致异常)
|
|
392
457
|
* - 对 num 调用 getSafeProductNum 做安全处理
|
|
393
458
|
* - 保留 _origin 供后续业务流程(如促销规则)使用
|
|
459
|
+
*
|
|
460
|
+
* 价格字段语义(metadata 权威源 + composite 派生):
|
|
461
|
+
* - `metadata.source_product_price`:主商品/variant 基础价(已应用报价单),**不含 option**、
|
|
462
|
+
* **不含折扣**。是推导 main_product_* 的起点。variant 分支优先读 `metadata.origin.variant[vid].price`。
|
|
463
|
+
* - `metadata.main_product_original_price`:`source + Σ(option.price × option.num)`,**含 option**、
|
|
464
|
+
* **不含折扣**。
|
|
465
|
+
* - `metadata.main_product_selling_price`:`main_product_original_price - 主商品券 per-unit amount`,
|
|
466
|
+
* **含 option**、**含折扣**。
|
|
467
|
+
* - 行级 `selling_price` = `main_product_selling_price + Σ(bundle_selling_price × num)`。
|
|
468
|
+
* - 行级 `original_price` = `main_product_original_price + Σ(bundle 原价 × num)`。
|
|
469
|
+
*
|
|
470
|
+
* 迁移与幂等:
|
|
471
|
+
* - `metadata.price_schema_version === 2` → 已新语义归一化,保留 main_product_* 原值(保留折扣)。
|
|
472
|
+
* - 其它情况(v1 / 缺字段 / 无 metadata)→ 按"main_product_selling_price 曾是 main-only"的旧约定
|
|
473
|
+
* 反推 legacyDiscount,再以新 source + options 基准重算。最终统一打上 `price_schema_version: 2`。
|
|
474
|
+
* - 因此多次 normalize 不会重复叠加 option/bundle。
|
|
394
475
|
*/
|
|
395
476
|
export function normalizeOrderProduct(product) {
|
|
396
|
-
var _product$is_charge_ta;
|
|
477
|
+
var _metadata$origin, _variantList$find, _product$is_charge_ta;
|
|
397
478
|
var metadata = _objectSpread({}, product.metadata || {});
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
// 初次加购时两者相等(入口层应保证 caller 传的 original_price == selling_price);
|
|
404
|
-
// 券应用后由 Rules 引擎只改动 selling_price,保留 original_price 以便还原 / 展示划线对比。
|
|
405
|
-
// 注意:这里 caller 若显式传入了 original_price(例如 Rules 回写或单测场景),要尊重该值,
|
|
406
|
-
// 让券后状态能正确通过再次 normalize。
|
|
407
|
-
var resolvedSellingPrice = product.selling_price || '0.00';
|
|
408
|
-
var resolvedOriginalPrice = product.original_price || resolvedSellingPrice;
|
|
409
|
-
if (metadata.main_product_original_price === undefined) {
|
|
410
|
-
metadata.main_product_original_price = resolvedOriginalPrice;
|
|
411
|
-
}
|
|
412
|
-
if (metadata.main_product_selling_price === undefined) {
|
|
413
|
-
metadata.main_product_selling_price = resolvedSellingPrice;
|
|
414
|
-
}
|
|
415
|
-
if (metadata.source_product_price === undefined) {
|
|
416
|
-
var _ref, _product$_origin$pric, _product$_origin, _product$_origin2;
|
|
417
|
-
metadata.source_product_price = (_ref = (_product$_origin$pric = (_product$_origin = product._origin) === null || _product$_origin === void 0 ? void 0 : _product$_origin.price) !== null && _product$_origin$pric !== void 0 ? _product$_origin$pric : (_product$_origin2 = product._origin) === null || _product$_origin2 === void 0 ? void 0 : _product$_origin2.base_price) !== null && _ref !== void 0 ? _ref : resolvedSellingPrice;
|
|
479
|
+
// 不透明 identity 契约:每条订单行必须带 identity_key。
|
|
480
|
+
// 调用方未传时由 SDK 自动生成 UUID,后续 update/remove 只做严格比对,避免猜测合成 key。
|
|
481
|
+
var resolvedIdentityKey = product.identity_key && String(product.identity_key).length > 0 ? String(product.identity_key) : createUuidV4();
|
|
482
|
+
if (!metadata.unique_identification_number) {
|
|
483
|
+
metadata.unique_identification_number = resolvedIdentityKey;
|
|
418
484
|
}
|
|
419
485
|
var normalizedBundle = (product.product_bundle || []).map(function (item) {
|
|
420
486
|
var _ref2, _item$bundle_selling_, _ref3, _ref4, _item$custom_price;
|
|
@@ -423,20 +489,104 @@ export function normalizeOrderProduct(product) {
|
|
|
423
489
|
custom_price: (_ref3 = (_ref4 = (_item$custom_price = item.custom_price) !== null && _item$custom_price !== void 0 ? _item$custom_price : item.bundle_selling_price) !== null && _ref4 !== void 0 ? _ref4 : item.price) !== null && _ref3 !== void 0 ? _ref3 : '0.00'
|
|
424
490
|
});
|
|
425
491
|
});
|
|
492
|
+
var normalizedOptions = product.product_option_item || [];
|
|
493
|
+
var optionSum = sumOptionUnitPrice(normalizedOptions);
|
|
494
|
+
|
|
495
|
+
// 1) 解析 source_product_price。
|
|
496
|
+
// 优先级:
|
|
497
|
+
// 1) metadata.source_product_price(v2 权威)
|
|
498
|
+
// 2) variantPrice(命中 variant_id,从 metadata.origin.variant[vid].price 读)
|
|
499
|
+
// 3) _origin.price / _origin.base_price(后端语义的基础价)
|
|
500
|
+
// 4) v1 兼容:无 v2 标记但存在 metadata.main_product_original_price(v1 main-only,即旧 source)
|
|
501
|
+
// 5) 入参 original_price(v1 addProduct 约定:original_price 为 pre-discount 基础价)
|
|
502
|
+
// 6) 入参 selling_price(最末兜底,新添加路径 caller 只给 selling_price)
|
|
503
|
+
var isV2 = metadata.price_schema_version === 2 || metadata.price_schema_version === '2';
|
|
504
|
+
var variantId = Number(product.product_variant_id || 0);
|
|
505
|
+
var variantList = variantId ? metadata === null || metadata === void 0 || (_metadata$origin = metadata.origin) === null || _metadata$origin === void 0 ? void 0 : _metadata$origin.variant : null;
|
|
506
|
+
var variantPrice = Array.isArray(variantList) ? (_variantList$find = variantList.find(function (v) {
|
|
507
|
+
return Number(v === null || v === void 0 ? void 0 : v.id) === variantId;
|
|
508
|
+
})) === null || _variantList$find === void 0 ? void 0 : _variantList$find.price : undefined;
|
|
509
|
+
var resolvedSource = function (_product$_origin, _product$_origin2, _ref5, _product$original_pri) {
|
|
510
|
+
if (metadata.source_product_price !== undefined) {
|
|
511
|
+
return String(metadata.source_product_price);
|
|
512
|
+
}
|
|
513
|
+
if (variantPrice !== undefined && variantPrice !== null) {
|
|
514
|
+
return String(variantPrice);
|
|
515
|
+
}
|
|
516
|
+
var originPrice = (_product$_origin = product._origin) === null || _product$_origin === void 0 ? void 0 : _product$_origin.price;
|
|
517
|
+
if (originPrice !== undefined && originPrice !== null) {
|
|
518
|
+
return String(originPrice);
|
|
519
|
+
}
|
|
520
|
+
var originBasePrice = (_product$_origin2 = product._origin) === null || _product$_origin2 === void 0 ? void 0 : _product$_origin2.base_price;
|
|
521
|
+
if (originBasePrice !== undefined && originBasePrice !== null) {
|
|
522
|
+
return String(originBasePrice);
|
|
523
|
+
}
|
|
524
|
+
if (!isV2 && metadata.main_product_original_price !== undefined) {
|
|
525
|
+
return String(metadata.main_product_original_price);
|
|
526
|
+
}
|
|
527
|
+
return (_ref5 = (_product$original_pri = product.original_price) !== null && _product$original_pri !== void 0 ? _product$original_pri : product.selling_price) !== null && _ref5 !== void 0 ? _ref5 : '0.00';
|
|
528
|
+
}();
|
|
529
|
+
|
|
530
|
+
// 2) 派生 main_product_original_price(含 option、不含折扣)
|
|
531
|
+
var mainOriginalDec = new Decimal(Number(resolvedSource) || 0).plus(optionSum);
|
|
532
|
+
var mainOriginalStr = mainOriginalDec.toDecimalPlaces(2).toFixed(2);
|
|
533
|
+
|
|
534
|
+
// 3) 派生 main_product_selling_price(含 option、含主商品折扣)
|
|
535
|
+
// - v2 数据:直接沿用 metadata.main_product_selling_price(保留折扣)
|
|
536
|
+
// - v1 metadata:main_product_selling_price 旧语义是 main-only(≈ source-level 折后价),
|
|
537
|
+
// 反推 legacyDiscount = main_original(旧) - main_selling(旧),再用新 main_original - legacyDiscount
|
|
538
|
+
// - v1 addProduct 入参:top-level selling_price < original_price 表示主商品折扣;
|
|
539
|
+
// 用差额作为 legacyDiscount 映射到新 main_selling
|
|
540
|
+
// - 其它:视为无折扣,main_selling = main_original
|
|
541
|
+
var mainSellingDec;
|
|
542
|
+
if (isV2 && metadata.main_product_selling_price != null) {
|
|
543
|
+
mainSellingDec = new Decimal(Number(metadata.main_product_selling_price) || 0);
|
|
544
|
+
} else if (metadata.main_product_selling_price != null && metadata.main_product_original_price != null) {
|
|
545
|
+
var legacyOriginal = new Decimal(Number(metadata.main_product_original_price) || 0);
|
|
546
|
+
var legacySelling = new Decimal(Number(metadata.main_product_selling_price) || 0);
|
|
547
|
+
var legacyDiscount = legacyOriginal.minus(legacySelling);
|
|
548
|
+
mainSellingDec = mainOriginalDec.minus(legacyDiscount);
|
|
549
|
+
} else if (product.original_price != null && product.selling_price != null && new Decimal(Number(product.original_price) || 0).greaterThan(new Decimal(Number(product.selling_price) || 0))) {
|
|
550
|
+
var topOriginal = new Decimal(Number(product.original_price) || 0);
|
|
551
|
+
var topSelling = new Decimal(Number(product.selling_price) || 0);
|
|
552
|
+
var _legacyDiscount = topOriginal.minus(topSelling);
|
|
553
|
+
mainSellingDec = mainOriginalDec.minus(_legacyDiscount);
|
|
554
|
+
} else {
|
|
555
|
+
mainSellingDec = mainOriginalDec;
|
|
556
|
+
}
|
|
557
|
+
var mainSellingStr = mainSellingDec.toDecimalPlaces(2).toFixed(2);
|
|
558
|
+
|
|
559
|
+
// 4) 落盘 metadata:三字段 + schema 版本 sentinel
|
|
560
|
+
metadata.source_product_price = resolvedSource;
|
|
561
|
+
metadata.main_product_original_price = mainOriginalStr;
|
|
562
|
+
metadata.main_product_selling_price = mainSellingStr;
|
|
563
|
+
metadata.price_schema_version = 2;
|
|
564
|
+
|
|
565
|
+
// 5) 合成行级 composite(main 已含 option,本步只叠 bundle)
|
|
566
|
+
var composedSellingPrice = composeLinePrice({
|
|
567
|
+
mainPrice: mainSellingStr,
|
|
568
|
+
bundle: normalizedBundle
|
|
569
|
+
});
|
|
570
|
+
var composedOriginalPrice = composeLinePrice({
|
|
571
|
+
mainPrice: mainOriginalStr,
|
|
572
|
+
bundle: normalizedBundle,
|
|
573
|
+
useOriginalBundle: true
|
|
574
|
+
});
|
|
426
575
|
return {
|
|
427
576
|
order_detail_id: product.order_detail_id || null,
|
|
428
577
|
product_id: product.product_id,
|
|
429
578
|
num: getSafeProductNum(product.num),
|
|
430
579
|
product_variant_id: product.product_variant_id,
|
|
431
|
-
identity_key:
|
|
432
|
-
product_option_item:
|
|
433
|
-
selling_price:
|
|
434
|
-
original_price:
|
|
580
|
+
identity_key: resolvedIdentityKey,
|
|
581
|
+
product_option_item: normalizedOptions,
|
|
582
|
+
selling_price: composedSellingPrice,
|
|
583
|
+
original_price: composedOriginalPrice,
|
|
435
584
|
tax_fee: product.tax_fee || '0.00',
|
|
436
585
|
is_charge_tax: (_product$is_charge_ta = product.is_charge_tax) !== null && _product$is_charge_ta !== void 0 ? _product$is_charge_ta : 0,
|
|
437
586
|
discount_list: product.discount_list || [],
|
|
438
587
|
product_bundle: normalizedBundle,
|
|
439
588
|
metadata: metadata,
|
|
589
|
+
note: product.note != null ? String(product.note) : '',
|
|
440
590
|
_origin: product._origin
|
|
441
591
|
};
|
|
442
592
|
}
|
|
@@ -508,16 +658,54 @@ export function hasCustomCapacityProduct(products) {
|
|
|
508
658
|
});
|
|
509
659
|
}
|
|
510
660
|
|
|
661
|
+
/**
|
|
662
|
+
* 根据预约规则商品的 resource.type 计算桌台是否已被"占满"。
|
|
663
|
+
* - single:只要有 `lastOrderId` 即视为占用
|
|
664
|
+
* - multiple:当前时间落在 `capacity_list[i]` 的 `[start_at, end_at]`(inclusive)内的 pax 之和 >= 总容量
|
|
665
|
+
* - 其他('capacity' / undefined):返回 false,不施加限制
|
|
666
|
+
*/
|
|
667
|
+
export function computeResourceIsFull(params) {
|
|
668
|
+
var resourceSelectType = params.resourceSelectType,
|
|
669
|
+
lastOrderId = params.lastOrderId,
|
|
670
|
+
capacityList = params.capacityList,
|
|
671
|
+
capacity = params.capacity;
|
|
672
|
+
if (resourceSelectType === 'single') return Boolean(lastOrderId);
|
|
673
|
+
if (resourceSelectType !== 'multiple') return false;
|
|
674
|
+
var totalCapacity = Number(capacity);
|
|
675
|
+
if (!Number.isFinite(totalCapacity) || totalCapacity <= 0) return false;
|
|
676
|
+
var now = dayjs();
|
|
677
|
+
var occupied = 0;
|
|
678
|
+
var _iterator8 = _createForOfIteratorHelper(capacityList || []),
|
|
679
|
+
_step8;
|
|
680
|
+
try {
|
|
681
|
+
for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
|
|
682
|
+
var slot = _step8.value;
|
|
683
|
+
var start = dayjs(slot === null || slot === void 0 ? void 0 : slot.start_at);
|
|
684
|
+
var end = dayjs(slot === null || slot === void 0 ? void 0 : slot.end_at);
|
|
685
|
+
if (!start.isValid() || !end.isValid()) continue;
|
|
686
|
+
if ((now.isAfter(start) || now.isSame(start)) && (now.isBefore(end) || now.isSame(end))) {
|
|
687
|
+
var pax = Number(slot === null || slot === void 0 ? void 0 : slot.pax);
|
|
688
|
+
if (Number.isFinite(pax) && pax > 0) occupied += pax;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
} catch (err) {
|
|
692
|
+
_iterator8.e(err);
|
|
693
|
+
} finally {
|
|
694
|
+
_iterator8.f();
|
|
695
|
+
}
|
|
696
|
+
return occupied >= totalCapacity;
|
|
697
|
+
}
|
|
698
|
+
|
|
511
699
|
/**
|
|
512
700
|
* 在商品列表中找到第一个 `capacity.type === 'custom'` 的商品,取其 `custom` 数组第一项的 min/max。
|
|
513
701
|
* 仅返回有限数字字段;若均无法解析则返回 `undefined`。
|
|
514
702
|
*/
|
|
515
703
|
export function pickFirstCustomCapacityPaxBounds(products) {
|
|
516
|
-
var
|
|
517
|
-
|
|
704
|
+
var _iterator9 = _createForOfIteratorHelper(products),
|
|
705
|
+
_step9;
|
|
518
706
|
try {
|
|
519
|
-
for (
|
|
520
|
-
var p =
|
|
707
|
+
for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
|
|
708
|
+
var p = _step9.value;
|
|
521
709
|
var cap = p === null || p === void 0 ? void 0 : p.capacity;
|
|
522
710
|
if (!cap || cap.type !== 'custom') continue;
|
|
523
711
|
if (!Array.isArray(cap.custom) || cap.custom.length === 0) continue;
|
|
@@ -536,9 +724,41 @@ export function pickFirstCustomCapacityPaxBounds(products) {
|
|
|
536
724
|
if (out.min !== undefined || out.max !== undefined) return out;
|
|
537
725
|
}
|
|
538
726
|
} catch (err) {
|
|
539
|
-
|
|
727
|
+
_iterator9.e(err);
|
|
540
728
|
} finally {
|
|
541
|
-
|
|
729
|
+
_iterator9.f();
|
|
730
|
+
}
|
|
731
|
+
return undefined;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* 第一个 `capacity.type === 'custom'` 的商品,取其 `custom[0].id`(提交 booking metadata.capacity 维度 id)。
|
|
736
|
+
* 无匹配时返回 `undefined`,调用方应回退为 `0`。
|
|
737
|
+
*/
|
|
738
|
+
export function pickFirstCustomCapacityDimensionId(products) {
|
|
739
|
+
var _iterator10 = _createForOfIteratorHelper(products),
|
|
740
|
+
_step10;
|
|
741
|
+
try {
|
|
742
|
+
for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
|
|
743
|
+
var p = _step10.value;
|
|
744
|
+
var cap = p === null || p === void 0 ? void 0 : p.capacity;
|
|
745
|
+
if (!cap || cap.type !== 'custom') continue;
|
|
746
|
+
if (!Array.isArray(cap.custom) || cap.custom.length === 0) continue;
|
|
747
|
+
var row = cap.custom[0];
|
|
748
|
+
if (!row || _typeof(row) !== 'object') continue;
|
|
749
|
+
var id = row.id;
|
|
750
|
+
if (id === null || id === undefined || id === '') continue;
|
|
751
|
+
if (typeof id === 'number' && Number.isFinite(id)) return id;
|
|
752
|
+
if (typeof id === 'string') {
|
|
753
|
+
var trimmed = id.trim();
|
|
754
|
+
if (!trimmed) continue;
|
|
755
|
+
return trimmed;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
} catch (err) {
|
|
759
|
+
_iterator10.e(err);
|
|
760
|
+
} finally {
|
|
761
|
+
_iterator10.f();
|
|
542
762
|
}
|
|
543
763
|
return undefined;
|
|
544
764
|
}
|
|
@@ -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;
|