@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.
Files changed (54) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +9 -0
  2. package/dist/modules/Order/index.d.ts +7 -6
  3. package/dist/modules/Order/index.js +137 -42
  4. package/dist/modules/Order/types.d.ts +32 -6
  5. package/dist/modules/Order/types.js +2 -0
  6. package/dist/modules/Order/utils.d.ts +73 -11
  7. package/dist/modules/Order/utils.js +304 -52
  8. package/dist/modules/SalesSummary/utils.js +33 -68
  9. package/dist/modules/ScanOrderLogger/providers/feishu.js +168 -60
  10. package/dist/modules/ScanOrderLogger/types.d.ts +6 -0
  11. package/dist/modules/Summary/utils.js +6 -21
  12. package/dist/solution/ScanOrder/index.d.ts +57 -8
  13. package/dist/solution/ScanOrder/index.js +1531 -583
  14. package/dist/solution/ScanOrder/types.d.ts +86 -26
  15. package/dist/solution/ScanOrder/types.js +20 -1
  16. package/dist/solution/ScanOrder/utils.d.ts +53 -5
  17. package/dist/solution/ScanOrder/utils.js +257 -37
  18. package/dist/solution/VenueBooking/index.d.ts +30 -10
  19. package/dist/solution/VenueBooking/index.js +460 -217
  20. package/dist/solution/VenueBooking/types.d.ts +23 -0
  21. package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -1
  22. package/dist/solution/VenueBooking/utils/dateSummary.js +1 -1
  23. package/dist/solution/VenueBooking/utils/resource.d.ts +11 -1
  24. package/dist/solution/VenueBooking/utils/resource.js +57 -21
  25. package/dist/solution/VenueBooking/utils/slotMerge.d.ts +5 -0
  26. package/dist/solution/VenueBooking/utils/slotMerge.js +33 -12
  27. package/dist/solution/VenueBooking/utils/timeSlot.d.ts +1 -1
  28. package/dist/solution/VenueBooking/utils/timeSlot.js +259 -62
  29. package/lib/modules/Order/index.d.ts +7 -6
  30. package/lib/modules/Order/index.js +123 -31
  31. package/lib/modules/Order/types.d.ts +32 -6
  32. package/lib/modules/Order/utils.d.ts +73 -11
  33. package/lib/modules/Order/utils.js +203 -28
  34. package/lib/modules/SalesSummary/utils.js +13 -47
  35. package/lib/modules/ScanOrderLogger/providers/feishu.js +100 -34
  36. package/lib/modules/ScanOrderLogger/types.d.ts +6 -0
  37. package/lib/modules/Summary/utils.js +4 -18
  38. package/lib/solution/ScanOrder/index.d.ts +57 -8
  39. package/lib/solution/ScanOrder/index.js +713 -117
  40. package/lib/solution/ScanOrder/types.d.ts +86 -26
  41. package/lib/solution/ScanOrder/utils.d.ts +53 -5
  42. package/lib/solution/ScanOrder/utils.js +186 -19
  43. package/lib/solution/VenueBooking/index.d.ts +30 -10
  44. package/lib/solution/VenueBooking/index.js +206 -51
  45. package/lib/solution/VenueBooking/types.d.ts +23 -0
  46. package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -1
  47. package/lib/solution/VenueBooking/utils/dateSummary.js +1 -1
  48. package/lib/solution/VenueBooking/utils/resource.d.ts +11 -1
  49. package/lib/solution/VenueBooking/utils/resource.js +15 -4
  50. package/lib/solution/VenueBooking/utils/slotMerge.d.ts +5 -0
  51. package/lib/solution/VenueBooking/utils/slotMerge.js +29 -12
  52. package/lib/solution/VenueBooking/utils/timeSlot.d.ts +1 -1
  53. package/lib/solution/VenueBooking/utils/timeSlot.js +182 -43
  54. package/package.json +1 -1
@@ -22,58 +22,124 @@ __export(feishu_exports, {
22
22
  feishuLoggerProvider: () => feishuLoggerProvider
23
23
  });
24
24
  module.exports = __toCommonJS(feishu_exports);
25
- function safeStringify(payload) {
26
- if (payload === void 0 || payload === null)
25
+ var DEFAULT_THROTTLE_MS = 3e3;
26
+ var pendingQueue = [];
27
+ var flushTimer = null;
28
+ var cachedProviderConfig;
29
+ function safeStringify(value) {
30
+ if (value === void 0 || value === null)
27
31
  return "";
32
+ if (typeof value === "string")
33
+ return value;
34
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
35
+ return String(value);
36
+ }
28
37
  try {
29
- return JSON.stringify(payload);
38
+ return JSON.stringify(value);
30
39
  } catch (error) {
31
40
  return `[unserializable: ${String(error)}]`;
32
41
  }
33
42
  }
34
- function createFeishuMessageContent(contentArr) {
35
- return JSON.stringify(
36
- contentArr.map((item) => [
37
- { tag: "text", text: `${item.key}: ` },
38
- { tag: "text", text: item.value }
39
- ])
40
- );
43
+ function isWebhookUsable(webhook) {
44
+ if (!webhook)
45
+ return false;
46
+ if (webhook.includes("REPLACE_ME"))
47
+ return false;
48
+ return true;
49
+ }
50
+ function buildRecordSegments(record) {
51
+ const context = record.context || {};
52
+ return [
53
+ [{ tag: "text", text: `[${record.level}] ${record.timestamp} ${record.title}
54
+ ` }],
55
+ [{ tag: "text", text: `缓存标识: ${safeStringify(context.cacheId)}
56
+ ` }],
57
+ [{ tag: "text", text: `日志来源: ${safeStringify(context)}
58
+ ` }],
59
+ [{ tag: "text", text: `日志内容: ${safeStringify(record.payload)}
60
+ ` }],
61
+ [{ tag: "text", text: `扩展信息: ${safeStringify(record.extra)}
62
+ ` }],
63
+ [{ tag: "text", text: "------\n" }]
64
+ ];
65
+ }
66
+ function buildFeishuBody(records) {
67
+ const title = records.length === 1 ? records[0].title : `ScanOrder 日志批量 (${records.length} 条)`;
68
+ const content = [];
69
+ records.forEach((record) => {
70
+ buildRecordSegments(record).forEach((segment) => content.push(segment));
71
+ });
72
+ return JSON.stringify({
73
+ msg_type: "post",
74
+ content: JSON.stringify({
75
+ post: {
76
+ zh_cn: {
77
+ title,
78
+ content
79
+ }
80
+ }
81
+ })
82
+ });
83
+ }
84
+ async function postToFeishu(webhook, body) {
85
+ await fetch(webhook, {
86
+ method: "POST",
87
+ headers: {
88
+ "Content-Type": "application/json"
89
+ },
90
+ body
91
+ });
92
+ }
93
+ async function flushQueue() {
94
+ var _a;
95
+ flushTimer = null;
96
+ if (!pendingQueue.length)
97
+ return;
98
+ const records = pendingQueue.splice(0, pendingQueue.length);
99
+ const webhook = (_a = cachedProviderConfig == null ? void 0 : cachedProviderConfig.feishu) == null ? void 0 : _a.webhook;
100
+ if (!isWebhookUsable(webhook)) {
101
+ return;
102
+ }
103
+ if (typeof fetch !== "function") {
104
+ console.warn("[ScanOrderLogger] 当前环境不支持 fetch,跳过 Feishu 日志批量上报");
105
+ return;
106
+ }
107
+ try {
108
+ await postToFeishu(webhook, buildFeishuBody(records));
109
+ } catch (error) {
110
+ console.warn("[ScanOrderLogger] Feishu 批量上报失败", error);
111
+ }
41
112
  }
42
113
  var feishuLoggerProvider = {
43
114
  async send(payload) {
44
- var _a, _b;
115
+ var _a, _b, _c, _d;
116
+ cachedProviderConfig = payload.providerConfig;
45
117
  const webhook = (_b = (_a = payload.providerConfig) == null ? void 0 : _a.feishu) == null ? void 0 : _b.webhook;
46
- if (webhook && webhook.includes("REPLACE_ME"))
47
- return;
48
118
  if (!webhook) {
49
119
  console.warn("[ScanOrderLogger] Feishu webhook 未配置,跳过日志上报");
50
120
  return;
51
121
  }
122
+ if (!isWebhookUsable(webhook))
123
+ return;
52
124
  if (typeof fetch !== "function") {
53
125
  console.warn("[ScanOrderLogger] 当前环境不支持 fetch,跳过 Feishu 日志上报");
54
126
  return;
55
127
  }
56
- const { record } = payload;
57
- const contentArr = [
58
- { key: "日志级别", value: record.level },
59
- { key: "日志时间", value: record.timestamp },
60
- { key: "日志标题", value: record.title },
61
- { key: "缓存标识", value: record.context.cacheId || "" },
62
- { key: "日志来源", value: safeStringify(record.context) },
63
- { key: "日志内容", value: safeStringify(record.payload) },
64
- { key: "扩展信息", value: safeStringify(record.extra) }
65
- ];
66
- const contentStr = createFeishuMessageContent(contentArr);
67
- await fetch(webhook, {
68
- headers: {
69
- "Content-Type": "application/json"
70
- },
71
- method: "POST",
72
- body: JSON.stringify({
73
- msg_type: "post",
74
- content: `{"post":{"zh_cn":{"title":"${record.title}","content":${contentStr}}}}`
75
- })
76
- });
128
+ const throttleMs = ((_d = (_c = payload.providerConfig) == null ? void 0 : _c.feishu) == null ? void 0 : _d.throttleMs) ?? DEFAULT_THROTTLE_MS;
129
+ if (throttleMs <= 0) {
130
+ try {
131
+ await postToFeishu(webhook, buildFeishuBody([payload.record]));
132
+ } catch (error) {
133
+ console.warn("[ScanOrderLogger] Feishu 日志上报失败", error);
134
+ }
135
+ return;
136
+ }
137
+ pendingQueue.push(payload.record);
138
+ if (!flushTimer) {
139
+ flushTimer = setTimeout(() => {
140
+ void flushQueue();
141
+ }, throttleMs);
142
+ }
77
143
  }
78
144
  };
79
145
  // Annotate the CommonJS export names for ESM import in node:
@@ -2,6 +2,12 @@ export type ScanOrderLogLevel = 'info' | 'warning' | 'error' | 'debug';
2
2
  export type ScanOrderLoggerProviderType = 'feishu' | 'grafana';
3
3
  export interface ScanOrderLoggerProviderFeishuConfig {
4
4
  webhook?: string;
5
+ /**
6
+ * 节流窗口毫秒数,默认 3000ms
7
+ * - >0:首条日志进入队列并启动定时器,窗口结束后合并成一条 Feishu post 统一发送
8
+ * - <=0:关闭节流,保持每条日志立即单独发送
9
+ */
10
+ throttleMs?: number;
5
11
  }
6
12
  export interface ScanOrderLoggerProviderGrafanaConfig {
7
13
  endpoint?: string;
@@ -423,25 +423,11 @@ var getBundleItemIsDiscountPrice = (item) => {
423
423
  var getBundleItemIsMarkupOrDiscountPrice = (item) => {
424
424
  return getBundleItemIsMarkupPrice(item) || getBundleItemIsDiscountPrice(item);
425
425
  };
426
- var getDiscountAmount = (discounts) => {
427
- return (discounts || []).reduce((total, discount) => {
428
- return total.add(new import_decimal.default(discount.amount || 0));
429
- }, new import_decimal.default(0)).toNumber();
430
- };
431
426
  var getMainProductTotal = (item) => {
432
- var _a, _b, _c, _d, _e, _f;
433
- let total = new import_decimal.default((item == null ? void 0 : item.main_product_selling_price) ?? ((_a = item == null ? void 0 : item.metadata) == null ? void 0 : _a.main_product_selling_price) ?? item.price ?? 0);
434
- const discount = ((_c = (_b = item == null ? void 0 : item._origin) == null ? void 0 : _b.product) == null ? void 0 : _c.discount_list) || ((_f = (_e = (_d = item == null ? void 0 : item._originData) == null ? void 0 : _d.product) == null ? void 0 : _e.discount_list) == null ? void 0 : _f.filter((item2) => {
435
- var _a2;
436
- return !((_a2 = item2 == null ? void 0 : item2.metadata) == null ? void 0 : _a2.custom_product_bundle_map_id);
437
- })) || [];
438
- const mainProductDiscountAmount = getDiscountAmount(discount);
439
- total = total.minus(mainProductDiscountAmount);
440
- if ((item == null ? void 0 : item.option) && Array.isArray(item == null ? void 0 : item.option)) {
441
- total = total.add(item == null ? void 0 : item.option.reduce((t, option) => {
442
- return t.add(new import_decimal.default(option.price || 0).mul(option.num || 1));
443
- }, new import_decimal.default(0)));
444
- }
427
+ var _a, _b;
428
+ let total = new import_decimal.default(
429
+ (item == null ? void 0 : item.main_product_selling_price) ?? ((_a = item == null ? void 0 : item.metadata) == null ? void 0 : _a.main_product_selling_price) ?? ((_b = item == null ? void 0 : item.metadata) == null ? void 0 : _b.main_product_original_price) ?? 0
430
+ );
445
431
  for (let bundleItem of (item == null ? void 0 : item.bundle) || []) {
446
432
  if (getBundleItemIsMarkupOrDiscountPrice(bundleItem)) {
447
433
  const bundleItemTotal = new import_decimal.default(bundleItem.bundle_selling_price ?? bundleItem.price ?? 0);
@@ -1,6 +1,8 @@
1
1
  import { Module, ModuleOptions, PisellCore } from '../../types';
2
2
  import { BaseModule } from '../../modules/BaseModule';
3
- import { ScanOrderAddLogParams, ScanOrderAvailabilityInfo, ScanOrderOrderProduct, ScanOrderOrderProductIdentity, ScanOrderTableConfigApiData } from './types';
3
+ import { ScanOrderAddLogParams, ScanOrderAvailabilityInfo, ScanOrderOrderProduct, ScanOrderOrderProductIdentity, ScanOrderScanCodeResult } from './types';
4
+ import type { UpdateProductInOrderParams } from '../../modules/Order/types';
5
+ import type { Discount } from '../../modules/Discount/types';
4
6
  import { type CartItemSummary, type PaxInfo, type QuantityCheckResult, type QuantityLimitResult } from '../../model/strategy/adapter/itemRule';
5
7
  import type { StrategyConfig } from '../../model/strategy/type';
6
8
  export * from './types';
@@ -29,6 +31,10 @@ export declare class ScanOrderImpl extends BaseModule implements Module {
29
31
  private itemRuleRuntimeConfig;
30
32
  /** 最近一次 checkResourceAvailable 从预约规则 link 拉取的商品快照 */
31
33
  private enabledReservationRuleProducts;
34
+ private loginEffectDisposers;
35
+ private customerLoginRefreshInFlight;
36
+ private customerLoginRefreshIdInFlight;
37
+ private static readonly PISELL1_LOGIN_SUCCESS;
32
38
  private getScanOrderLoggerContext;
33
39
  private serializeError;
34
40
  private addScanOrderLog;
@@ -36,14 +42,23 @@ export declare class ScanOrderImpl extends BaseModule implements Module {
36
42
  private logMethodSuccess;
37
43
  private logMethodError;
38
44
  addLog(params: ScanOrderAddLogParams): Promise<void>;
45
+ private normalizeCustomerId;
46
+ private resolveCustomerIdFromLoginPayload;
47
+ private clearLoginEffectListeners;
48
+ private registerLoginEffect;
49
+ private registerCustomerLoginListeners;
50
+ private refreshOrderMarketingAfterLogin;
39
51
  constructor(name?: string, version?: string);
52
+ /** 与 `otherParams.cacheId` 一致,供宿主在 URL 变化时判断是否需要重新注册模块 */
53
+ getCacheId(): string | undefined;
54
+ private destroyRegisteredChildModules;
40
55
  initialize(core: PisellCore, options?: ModuleOptions): Promise<void>;
41
56
  destroy(): Promise<void>;
42
57
  retryInit(): Promise<void>;
43
58
  refresh(): Promise<void>;
44
59
  getStatus(): import("./types").ScanOrderStatus;
45
60
  getEntryContext(): import("./types").ScanOrderEntryContext | null;
46
- getConfig(): ScanOrderTableConfigApiData;
61
+ getConfig(): Record<string, any>;
47
62
  getItemRuleQuantityLimits(): QuantityLimitResult[];
48
63
  getCartValidationPassed(): boolean | null;
49
64
  getCartValidation(): {
@@ -52,17 +67,34 @@ export declare class ScanOrderImpl extends BaseModule implements Module {
52
67
  };
53
68
  getTempOrder(): import("./types").ScanOrderTempOrder | null;
54
69
  updateTempOrderNote(note: string): string;
70
+ setPickupReferenceMode(mode: 'counter_pickup' | 'table_service'): {
71
+ service_type: 'dine_in';
72
+ pickup_reference_mode: 'counter_pickup' | 'table_service';
73
+ };
74
+ setPickupRef(buzzer: string): string;
55
75
  private ensureTempOrder;
56
76
  addNewOrder(): Promise<import("./types").ScanOrderTempOrder>;
77
+ restoreOrder(): Promise<import("./types").ScanOrderTempOrder>;
57
78
  getOrderProducts(): ScanOrderOrderProduct[];
58
79
  getSummary(): Promise<import("./types").ScanOrderSummary>;
80
+ getDiscountList(): Discount[];
81
+ scanCode(code: string, customerId?: number): Promise<ScanOrderScanCodeResult>;
82
+ setDiscountSelected(params: {
83
+ discountId: number;
84
+ isSelected: boolean;
85
+ }): Promise<void>;
86
+ onCustomerLogin(params: {
87
+ customerId: number;
88
+ }): Promise<void>;
89
+ private buildSubmitPayloadEnhancer;
59
90
  submitScanOrder<T = any>(): Promise<T>;
60
91
  addProductToOrder(product: Partial<ScanOrderOrderProduct> & ScanOrderOrderProductIdentity): Promise<ScanOrderOrderProduct[]>;
61
- updateProductInOrder(params: {
62
- product_id: number | null;
63
- product_variant_id: number;
64
- updates: Partial<ScanOrderOrderProduct>;
65
- }): Promise<ScanOrderOrderProduct[]>;
92
+ updateProductInOrder(params: UpdateProductInOrderParams): Promise<ScanOrderOrderProduct[]>;
93
+ /**
94
+ * 设置单行商品备注(与整单 `updateTempOrderNote` 区分)。
95
+ * 多行同 SKU 时必须传入与删除/更新一致的 identity(如 `identity_key`)。
96
+ */
97
+ setOrderProductLineNote(identity: ScanOrderOrderProductIdentity, note: string): Promise<ScanOrderOrderProduct[]>;
66
98
  removeProductFromOrder(identity: ScanOrderOrderProductIdentity): Promise<ScanOrderOrderProduct[]>;
67
99
  private loadRuntimeConfigs;
68
100
  private syncItemRuleConfigsFromDineInConfig;
@@ -78,7 +110,8 @@ export declare class ScanOrderImpl extends BaseModule implements Module {
78
110
  private refreshCartValidationPassed;
79
111
  setItemRuleRuntimeConfig(config?: ScanOrderItemRuleRuntimeConfig): Promise<void>;
80
112
  private normalizeResourceState;
81
- private fetchTableConfigByResourceId;
113
+ private resolveResourceSelectType;
114
+ private fetchResourceOccupyDetailByResourceId;
82
115
  checkResourceAvailable(resourceId: string, hasOrderId: boolean): Promise<ScanOrderAvailabilityInfo>;
83
116
  getAdditionalOrderInfo(): Promise<{
84
117
  orderId: string;
@@ -92,6 +125,22 @@ export declare class ScanOrderImpl extends BaseModule implements Module {
92
125
  setOtherParams(params: Record<string, any>, { cover }?: {
93
126
  cover?: boolean;
94
127
  }): Promise<void>;
128
+ private static readonly UI_STATE_KEY_PREFIX;
129
+ private getUIStateBucketKey;
130
+ private readUIStateBucket;
131
+ private writeUIStateBucket;
132
+ setUIState(key: string, value: any): void;
133
+ getUIState<T = any>(key: string): T | undefined;
134
+ deleteUIState(key: string): void;
135
+ clearUIState(): void;
95
136
  setEntryPaxNumber(number: number): Promise<void>;
96
137
  getEntryPaxNumber(): number | null;
138
+ getFulfillmentModes(): {
139
+ enablePickup: boolean;
140
+ enableTableService: boolean;
141
+ };
142
+ checkManualPickupRef(): {
143
+ enabled: boolean;
144
+ manualInputType?: string;
145
+ };
97
146
  }