@pisell/pisellos 0.10.19 → 0.10.21

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 (36) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +9 -0
  2. package/dist/modules/OpenData/index.d.ts +10 -1
  3. package/dist/modules/OpenData/index.js +268 -14
  4. package/dist/modules/OpenData/types.d.ts +14 -1
  5. package/dist/modules/OpenData/utils.d.ts +6 -0
  6. package/dist/modules/OpenData/utils.js +50 -5
  7. package/dist/modules/Order/index.d.ts +1 -1
  8. package/dist/plugins/request.d.ts +2 -1
  9. package/dist/plugins/request.js +4 -2
  10. package/dist/server/index.d.ts +5 -0
  11. package/dist/server/index.js +80 -41
  12. package/dist/server/utils/small-ticket.js +52 -0
  13. package/dist/solution/BaseSales/index.js +23 -0
  14. package/dist/solution/BookingByStep/index.d.ts +1 -1
  15. package/dist/solution/BookingTicket/index.d.ts +13 -1
  16. package/dist/solution/BookingTicket/index.js +25 -18
  17. package/dist/solution/Sales/index.js +29 -22
  18. package/dist/solution/UnifiedBookingSales/index.js +25 -18
  19. package/lib/modules/OpenData/index.d.ts +10 -1
  20. package/lib/modules/OpenData/index.js +170 -3
  21. package/lib/modules/OpenData/types.d.ts +14 -1
  22. package/lib/modules/OpenData/utils.d.ts +6 -0
  23. package/lib/modules/OpenData/utils.js +40 -5
  24. package/lib/modules/Order/index.d.ts +1 -1
  25. package/lib/plugins/request.d.ts +2 -1
  26. package/lib/plugins/request.js +3 -2
  27. package/lib/server/index.d.ts +5 -0
  28. package/lib/server/index.js +47 -11
  29. package/lib/server/utils/small-ticket.js +52 -0
  30. package/lib/solution/BaseSales/index.js +21 -0
  31. package/lib/solution/BookingByStep/index.d.ts +1 -1
  32. package/lib/solution/BookingTicket/index.d.ts +13 -1
  33. package/lib/solution/BookingTicket/index.js +3 -0
  34. package/lib/solution/Sales/index.js +3 -0
  35. package/lib/solution/UnifiedBookingSales/index.js +3 -0
  36. package/package.json +1 -1
@@ -1937,29 +1937,36 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
1937
1937
  cachedData = this.store.openData.getOpenData();
1938
1938
  lastFetchedAt = this.store.openData.getLastFetchedAt();
1939
1939
  if (!(!params.force && this.openDataTarget === target && cachedData && lastFetchedAt && Date.now() - lastFetchedAt < OPEN_DATA_CACHE_TTL)) {
1940
- _context11.next = 11;
1940
+ _context11.next = 14;
1941
1941
  break;
1942
1942
  }
1943
+ if (!(typeof this.store.openData.ensureInvoiceTemplateSettings === 'function')) {
1944
+ _context11.next = 13;
1945
+ break;
1946
+ }
1947
+ _context11.next = 13;
1948
+ return this.store.openData.ensureInvoiceTemplateSettings(target);
1949
+ case 13:
1943
1950
  return _context11.abrupt("return", cachedData);
1944
- case 11:
1951
+ case 14:
1945
1952
  if (!this.loadOpenDataInFlight) {
1946
- _context11.next = 19;
1953
+ _context11.next = 22;
1947
1954
  break;
1948
1955
  }
1949
1956
  if (!(this.loadOpenDataInFlightTarget === target)) {
1950
- _context11.next = 16;
1957
+ _context11.next = 19;
1951
1958
  break;
1952
1959
  }
1953
- _context11.next = 15;
1954
- return this.loadOpenDataInFlight;
1955
- case 15:
1956
- return _context11.abrupt("return", _context11.sent);
1957
- case 16:
1958
1960
  _context11.next = 18;
1959
1961
  return this.loadOpenDataInFlight;
1960
1962
  case 18:
1961
- return _context11.abrupt("return", this.loadOpenData(params));
1963
+ return _context11.abrupt("return", _context11.sent);
1962
1964
  case 19:
1965
+ _context11.next = 21;
1966
+ return this.loadOpenDataInFlight;
1967
+ case 21:
1968
+ return _context11.abrupt("return", this.loadOpenData(params));
1969
+ case 22:
1963
1970
  this.loadOpenDataInFlightTarget = target;
1964
1971
  request = this.store.openData.fetchOpenData({
1965
1972
  scope: 'board',
@@ -1967,10 +1974,10 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
1967
1974
  section_code: [].concat(OPEN_DATA_SECTION_CODES)
1968
1975
  });
1969
1976
  this.loadOpenDataInFlight = request;
1970
- _context11.prev = 22;
1971
- _context11.next = 25;
1977
+ _context11.prev = 25;
1978
+ _context11.next = 28;
1972
1979
  return request;
1973
- case 25:
1980
+ case 28:
1974
1981
  openData = _context11.sent;
1975
1982
  if (this.loadOpenDataInFlight === request) {
1976
1983
  this.openDataTarget = target;
@@ -1979,18 +1986,18 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
1979
1986
  this.otherParams.channel = channel;
1980
1987
  }
1981
1988
  return _context11.abrupt("return", openData);
1982
- case 28:
1983
- _context11.prev = 28;
1989
+ case 31:
1990
+ _context11.prev = 31;
1984
1991
  if (this.loadOpenDataInFlight === request) {
1985
1992
  this.loadOpenDataInFlight = null;
1986
1993
  this.loadOpenDataInFlightTarget = null;
1987
1994
  }
1988
- return _context11.finish(28);
1989
- case 31:
1995
+ return _context11.finish(31);
1996
+ case 34:
1990
1997
  case "end":
1991
1998
  return _context11.stop();
1992
1999
  }
1993
- }, _callee11, this, [[22,, 28, 31]]);
2000
+ }, _callee11, this, [[25,, 31, 34]]);
1994
2001
  }));
1995
2002
  function loadOpenData(_x7) {
1996
2003
  return _loadOpenData.apply(this, arguments);
@@ -3,7 +3,7 @@ import { BaseModule } from '../BaseModule';
3
3
  import type { ScheduleModule } from '../Schedule';
4
4
  import type { OpenDataConfig, OpenDataFetchParams, OpenDataAvailabilityResult, InvoiceLayoutConfig, InvoiceTemplateSettings } from './types';
5
5
  export * from './types';
6
- export { computeAvailability, getDefaultInvoiceLayoutConfig, getDefaultInvoiceTemplateSettings, normalizeInvoiceLayoutConfig, normalizeInvoiceTemplateSettings, normalizeRawInvoiceTemplateSettings, } from './utils';
6
+ export { computeAvailability, getDefaultInvoiceLayoutConfig, getDefaultInvoiceTemplateSettings, normalizeInvoiceLayoutConfig, normalizeInvoiceLayoutFromTemplateSettings, normalizeInvoiceTemplateSettings, normalizeRawInvoiceTemplateSettings, resolveInvoiceReceiptLocales, } from './utils';
7
7
  export declare class OpenDataModule extends BaseModule implements Module {
8
8
  protected defaultName: string;
9
9
  protected defaultVersion: string;
@@ -16,6 +16,9 @@ export declare class OpenDataModule extends BaseModule implements Module {
16
16
  private fatherModule;
17
17
  private otherParams;
18
18
  private fetchRevision;
19
+ private invoiceTemplateCacheRevision;
20
+ private invoiceTemplateDocumentCache;
21
+ private invoiceTemplateDocumentInFlight;
19
22
  constructor(name?: string, version?: string);
20
23
  initialize(core: PisellCore, options: ModuleOptions): Promise<void>;
21
24
  /**
@@ -29,6 +32,12 @@ export declare class OpenDataModule extends BaseModule implements Module {
29
32
  getOpenData(): OpenDataConfig | null;
30
33
  getInvoiceLayoutConfig(): InvoiceLayoutConfig;
31
34
  getInvoiceTemplateSettings(): InvoiceTemplateSettings;
35
+ ensureInvoiceTemplateSettings(target: string): Promise<InvoiceTemplateSettings>;
36
+ private commitInvoiceTemplateSettings;
37
+ private resolveInvoiceTemplateSettings;
38
+ private buildInvoiceTemplateCacheKey;
39
+ private fetchInvoiceTemplateDocument;
40
+ private logInvoiceTemplateDocumentFallback;
32
41
  getLastFetchedAt(): number | null;
33
42
  /** 清除内存与 sessionStorage 镜像中的 OpenData,供宿主切店时调用。 */
34
43
  clearCache(): void;
@@ -9,8 +9,10 @@ var _exportNames = {
9
9
  getDefaultInvoiceLayoutConfig: true,
10
10
  getDefaultInvoiceTemplateSettings: true,
11
11
  normalizeInvoiceLayoutConfig: true,
12
+ normalizeInvoiceLayoutFromTemplateSettings: true,
12
13
  normalizeInvoiceTemplateSettings: true,
13
- normalizeRawInvoiceTemplateSettings: true
14
+ normalizeRawInvoiceTemplateSettings: true,
15
+ resolveInvoiceReceiptLocales: true
14
16
  };
15
17
  exports.OpenDataModule = void 0;
16
18
  Object.defineProperty(exports, "computeAvailability", {
@@ -37,6 +39,12 @@ Object.defineProperty(exports, "normalizeInvoiceLayoutConfig", {
37
39
  return _utils.normalizeInvoiceLayoutConfig;
38
40
  }
39
41
  });
42
+ Object.defineProperty(exports, "normalizeInvoiceLayoutFromTemplateSettings", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _utils.normalizeInvoiceLayoutFromTemplateSettings;
46
+ }
47
+ });
40
48
  Object.defineProperty(exports, "normalizeInvoiceTemplateSettings", {
41
49
  enumerable: true,
42
50
  get: function () {
@@ -49,6 +57,12 @@ Object.defineProperty(exports, "normalizeRawInvoiceTemplateSettings", {
49
57
  return _utils.normalizeRawInvoiceTemplateSettings;
50
58
  }
51
59
  });
60
+ Object.defineProperty(exports, "resolveInvoiceReceiptLocales", {
61
+ enumerable: true,
62
+ get: function () {
63
+ return _utils.resolveInvoiceReceiptLocales;
64
+ }
65
+ });
52
66
  var _BaseModule = require("../BaseModule");
53
67
  var _utils = require("./utils");
54
68
  var _types = require("./types");
@@ -63,6 +77,25 @@ Object.keys(_types).forEach(function (key) {
63
77
  }
64
78
  });
65
79
  });
80
+ function isRecord(value) {
81
+ return !!value && typeof value === 'object' && !Array.isArray(value);
82
+ }
83
+ function resolveInvoiceDocumentId(value) {
84
+ if (typeof value === 'number') {
85
+ return Number.isSafeInteger(value) && value > 0 ? String(value) : null;
86
+ }
87
+ if (typeof value !== 'string') return null;
88
+ const normalized = value.trim();
89
+ if (!/^\d+$/.test(normalized)) return null;
90
+ const numericId = Number(normalized);
91
+ return Number.isSafeInteger(numericId) && numericId > 0 ? String(numericId) : null;
92
+ }
93
+ function isLegacyInvoiceTemplate(value) {
94
+ if (typeof value !== 'string' || value.trim().length === 0) return false;
95
+ const normalized = value.trim();
96
+ const numericLike = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
97
+ return !numericLike.test(normalized);
98
+ }
66
99
  class OpenDataModule extends _BaseModule.BaseModule {
67
100
  defaultName = 'openData';
68
101
  defaultVersion = '1.0.0';
@@ -75,6 +108,9 @@ class OpenDataModule extends _BaseModule.BaseModule {
75
108
  fatherModule;
76
109
  otherParams = {};
77
110
  fetchRevision = 0;
111
+ invoiceTemplateCacheRevision = 0;
112
+ invoiceTemplateDocumentCache = new Map();
113
+ invoiceTemplateDocumentInFlight = new Map();
78
114
  constructor(name, version) {
79
115
  super(name, version);
80
116
  }
@@ -83,6 +119,12 @@ class OpenDataModule extends _BaseModule.BaseModule {
83
119
  this.store = options.store;
84
120
  this.store.data = options.initialState?.data ?? null;
85
121
  this.store.lastFetchedAt = options.initialState?.lastFetchedAt ?? null;
122
+ // Document 解析结果只在当前模块实例内复用。页面刷新后即使恢复了原始
123
+ // OpenData,也必须重新查询一次 NocoBase,以取得当前模板内容。
124
+ this.store.invoiceTemplateSettings = null;
125
+ this.store.invoiceTemplateTarget = null;
126
+ this.store.invoiceTemplateDocumentId = null;
127
+ this.store.invoiceTemplateCacheKey = null;
86
128
  this.otherParams = options.otherParams || {};
87
129
  if (this.otherParams.cacheId) {
88
130
  this.openCache = this.otherParams.openCache ?? false;
@@ -132,10 +174,12 @@ class OpenDataModule extends _BaseModule.BaseModule {
132
174
  throw new Error(response?.message || '获取 OpenData 配置失败');
133
175
  }
134
176
  const data = response?.data || {};
177
+ const resolvedInvoiceTemplate = await this.resolveInvoiceTemplateSettings(data, params.target);
135
178
  // clearCache() 或后续请求发生后,旧响应不得复活已失效的 target 配置。
136
179
  if (fetchRevision === this.fetchRevision) {
137
180
  this.store.data = data;
138
181
  this.store.lastFetchedAt = Date.now();
182
+ this.commitInvoiceTemplateSettings(params.target, resolvedInvoiceTemplate);
139
183
  }
140
184
  return data;
141
185
  }
@@ -143,10 +187,126 @@ class OpenDataModule extends _BaseModule.BaseModule {
143
187
  return this.store.data;
144
188
  }
145
189
  getInvoiceLayoutConfig() {
146
- return (0, _utils.normalizeInvoiceLayoutConfig)(this.store.data);
190
+ return (0, _utils.normalizeInvoiceLayoutFromTemplateSettings)(this.getInvoiceTemplateSettings());
147
191
  }
148
192
  getInvoiceTemplateSettings() {
149
- return (0, _utils.normalizeInvoiceTemplateSettings)(this.store.data);
193
+ if (isRecord(this.store.invoiceTemplateSettings)) {
194
+ const documentId = resolveInvoiceDocumentId(this.store.data?.['documents.invoice_template']);
195
+ if (documentId) {
196
+ const target = this.store.invoiceTemplateTarget;
197
+ if (!target || this.store.invoiceTemplateCacheKey !== this.buildInvoiceTemplateCacheKey(target, documentId)) {
198
+ return (0, _utils.getDefaultInvoiceTemplateSettings)();
199
+ }
200
+ }
201
+ return (0, _utils.normalizeRawInvoiceTemplateSettings)(this.store.invoiceTemplateSettings);
202
+ }
203
+ if (isLegacyInvoiceTemplate(this.store.data?.['documents.invoice_template'])) {
204
+ return (0, _utils.normalizeInvoiceTemplateSettings)(this.store.data);
205
+ }
206
+ return (0, _utils.getDefaultInvoiceTemplateSettings)();
207
+ }
208
+ async ensureInvoiceTemplateSettings(target) {
209
+ const normalizedTarget = String(target || '').trim();
210
+ const config = this.store.data;
211
+ if (!normalizedTarget || !config) {
212
+ return (0, _utils.getDefaultInvoiceTemplateSettings)();
213
+ }
214
+ const fetchRevision = this.fetchRevision;
215
+ const resolved = await this.resolveInvoiceTemplateSettings(config, normalizedTarget);
216
+ if (fetchRevision !== this.fetchRevision || this.store.data !== config) {
217
+ return (0, _utils.getDefaultInvoiceTemplateSettings)();
218
+ }
219
+ this.commitInvoiceTemplateSettings(normalizedTarget, resolved);
220
+ return (0, _utils.normalizeRawInvoiceTemplateSettings)(resolved.settings);
221
+ }
222
+ commitInvoiceTemplateSettings(target, resolved) {
223
+ this.store.invoiceTemplateSettings = (0, _utils.normalizeRawInvoiceTemplateSettings)(resolved.settings);
224
+ this.store.invoiceTemplateTarget = target;
225
+ this.store.invoiceTemplateDocumentId = resolved.documentId;
226
+ this.store.invoiceTemplateCacheKey = resolved.cacheKey;
227
+ }
228
+ async resolveInvoiceTemplateSettings(config, target) {
229
+ const rawTemplate = config['documents.invoice_template'];
230
+ const documentId = resolveInvoiceDocumentId(rawTemplate);
231
+ if (!documentId) {
232
+ return {
233
+ settings: isLegacyInvoiceTemplate(rawTemplate) ? (0, _utils.normalizeInvoiceTemplateSettings)(config) : (0, _utils.getDefaultInvoiceTemplateSettings)(),
234
+ documentId: null,
235
+ cacheKey: null
236
+ };
237
+ }
238
+ const cacheKey = this.buildInvoiceTemplateCacheKey(target, documentId);
239
+ if (this.store.invoiceTemplateTarget === target && this.store.invoiceTemplateDocumentId === documentId && this.store.invoiceTemplateCacheKey === cacheKey && isRecord(this.store.invoiceTemplateSettings)) {
240
+ return {
241
+ settings: (0, _utils.normalizeRawInvoiceTemplateSettings)(this.store.invoiceTemplateSettings),
242
+ documentId,
243
+ cacheKey
244
+ };
245
+ }
246
+ const cachedSettings = this.invoiceTemplateDocumentCache.get(cacheKey);
247
+ if (cachedSettings) {
248
+ return {
249
+ settings: (0, _utils.normalizeRawInvoiceTemplateSettings)(cachedSettings),
250
+ documentId,
251
+ cacheKey
252
+ };
253
+ }
254
+ let request = this.invoiceTemplateDocumentInFlight.get(cacheKey);
255
+ if (!request) {
256
+ const cacheRevision = this.invoiceTemplateCacheRevision;
257
+ request = this.fetchInvoiceTemplateDocument(documentId).catch(error => {
258
+ this.logInvoiceTemplateDocumentFallback(error, target, documentId);
259
+ return (0, _utils.getDefaultInvoiceTemplateSettings)();
260
+ }).then(settings => {
261
+ const normalized = (0, _utils.normalizeRawInvoiceTemplateSettings)(settings);
262
+ if (cacheRevision === this.invoiceTemplateCacheRevision) {
263
+ this.invoiceTemplateDocumentCache.set(cacheKey, normalized);
264
+ }
265
+ return normalized;
266
+ }).finally(() => {
267
+ if (this.invoiceTemplateDocumentInFlight.get(cacheKey) === request) {
268
+ this.invoiceTemplateDocumentInFlight.delete(cacheKey);
269
+ }
270
+ });
271
+ this.invoiceTemplateDocumentInFlight.set(cacheKey, request);
272
+ }
273
+ return {
274
+ settings: await request,
275
+ documentId,
276
+ cacheKey
277
+ };
278
+ }
279
+ buildInvoiceTemplateCacheKey(target, documentId) {
280
+ const getData = this.appPlugin?.getData?.();
281
+ return JSON.stringify([getData?.('tenant_id') ?? null, getData?.('shop_id') ?? null, target, documentId]);
282
+ }
283
+ async fetchInvoiceTemplateDocument(documentId) {
284
+ const response = await this.request.get(`/document:get?filterByTk=${encodeURIComponent(documentId)}`, {}, {
285
+ isNocobase: true,
286
+ prefix: false,
287
+ headers: {
288
+ 'x-data-source': 'pisell2'
289
+ },
290
+ customToast: () => {}
291
+ });
292
+ const document = response?.data;
293
+ if (!document || resolveInvoiceDocumentId(document.id) !== documentId) {
294
+ throw new Error(`Invoice Document ${documentId} 返回的 ID 不匹配`);
295
+ }
296
+ if (document.deleted_at !== null && document.deleted_at !== undefined) {
297
+ throw new Error(`Invoice Document ${documentId} 已被删除`);
298
+ }
299
+ if (!isRecord(document.data) || !isRecord(document.data.template_settings)) {
300
+ throw new Error(`Invoice Document ${documentId} 缺少 template_settings`);
301
+ }
302
+ return (0, _utils.normalizeRawInvoiceTemplateSettings)(document.data.template_settings);
303
+ }
304
+ logInvoiceTemplateDocumentFallback(error, target, documentId) {
305
+ console.warn('[OpenDataModule] Invoice Document 加载失败,使用 DEFAULT', {
306
+ target,
307
+ documentId,
308
+ message: error instanceof Error ? error.message : String(error)
309
+ });
150
310
  }
151
311
  getLastFetchedAt() {
152
312
  return this.store.lastFetchedAt ?? null;
@@ -155,8 +315,15 @@ class OpenDataModule extends _BaseModule.BaseModule {
155
315
  /** 清除内存与 sessionStorage 镜像中的 OpenData,供宿主切店时调用。 */
156
316
  clearCache() {
157
317
  this.fetchRevision += 1;
318
+ this.invoiceTemplateCacheRevision += 1;
319
+ this.invoiceTemplateDocumentCache.clear();
320
+ this.invoiceTemplateDocumentInFlight.clear();
158
321
  this.store.data = null;
159
322
  this.store.lastFetchedAt = null;
323
+ this.store.invoiceTemplateSettings = null;
324
+ this.store.invoiceTemplateTarget = null;
325
+ this.store.invoiceTemplateDocumentId = null;
326
+ this.store.invoiceTemplateCacheKey = null;
160
327
  }
161
328
  checkAvailability(scheduleModule) {
162
329
  const scheduleList = scheduleModule ? scheduleModule?.getScheduleListByIds(this.store.data?.['availability.operating_hours'] || []) : undefined;
@@ -32,6 +32,15 @@ export interface InvoiceTemplateSettings {
32
32
  modules: InvoiceTemplateModuleConfig[];
33
33
  [key: string]: unknown;
34
34
  }
35
+ export interface InvoiceDocumentResponse {
36
+ data?: {
37
+ id?: number | string;
38
+ deleted_at?: string | null;
39
+ data?: {
40
+ template_settings?: unknown;
41
+ };
42
+ };
43
+ }
35
44
  export type RawInvoiceEditConfig = string | {
36
45
  language?: unknown;
37
46
  modules?: unknown;
@@ -79,7 +88,7 @@ export interface OpenDataConfig {
79
88
  label: string;
80
89
  value: number;
81
90
  }>;
82
- 'documents.invoice_template'?: string;
91
+ 'documents.invoice_template'?: string | number;
83
92
  'documents.invoice_edit'?: RawInvoiceEditConfig;
84
93
  /** @deprecated 仅保留旧调用方的类型兼容,Invoice 读取逻辑不再消费该字段。 */
85
94
  'documents.invoice'?: RawInvoiceLayoutConfig;
@@ -92,6 +101,10 @@ export interface OpenDataConfig {
92
101
  export interface OpenDataState {
93
102
  data: OpenDataConfig | null;
94
103
  lastFetchedAt: number | null;
104
+ invoiceTemplateSettings?: InvoiceTemplateSettings | null;
105
+ invoiceTemplateTarget?: string | null;
106
+ invoiceTemplateDocumentId?: string | null;
107
+ invoiceTemplateCacheKey?: string | null;
95
108
  }
96
109
  export interface OpenDataFetchParams {
97
110
  scope: string;
@@ -1,6 +1,11 @@
1
1
  import type { OpenDataConfig, OpenDataAvailabilityResult, ComputeAvailabilityParams, InvoiceLayoutConfig, InvoiceTemplateSettings } from './types';
2
2
  export declare function getDefaultInvoiceLayoutConfig(): InvoiceLayoutConfig;
3
3
  export declare function getDefaultInvoiceTemplateSettings(): InvoiceTemplateSettings;
4
+ /**
5
+ * 将非 DEFAULT Invoice 模板启用的语言转换为 small_ticket_data locale。
6
+ * cn_en 是中英组合打印模式,因此展开为 en 与 zh_CN 两份数据。
7
+ */
8
+ export declare function resolveInvoiceReceiptLocales(settings?: InvoiceTemplateSettings | null): string[];
4
9
  /**
5
10
  * 规范化已经提取出的公开 template_settings 值,供打印入口复用。
6
11
  */
@@ -9,6 +14,7 @@ export declare function normalizeRawInvoiceTemplateSettings(value: unknown): Inv
9
14
  * 从 OpenData Documents section 的两个扁平字段中构造 Native Invoice 配置。
10
15
  */
11
16
  export declare function normalizeInvoiceTemplateSettings(config: OpenDataConfig | null | undefined): InvoiceTemplateSettings;
17
+ export declare function normalizeInvoiceLayoutFromTemplateSettings(settings: InvoiceTemplateSettings | null | undefined): InvoiceLayoutConfig;
12
18
  /**
13
19
  * 将 invoice_edit.modules 原样投影为 Web Invoice 使用的 module 字段。
14
20
  */
@@ -7,8 +7,10 @@ exports.computeAvailability = computeAvailability;
7
7
  exports.getDefaultInvoiceLayoutConfig = getDefaultInvoiceLayoutConfig;
8
8
  exports.getDefaultInvoiceTemplateSettings = getDefaultInvoiceTemplateSettings;
9
9
  exports.normalizeInvoiceLayoutConfig = normalizeInvoiceLayoutConfig;
10
+ exports.normalizeInvoiceLayoutFromTemplateSettings = normalizeInvoiceLayoutFromTemplateSettings;
10
11
  exports.normalizeInvoiceTemplateSettings = normalizeInvoiceTemplateSettings;
11
12
  exports.normalizeRawInvoiceTemplateSettings = normalizeRawInvoiceTemplateSettings;
13
+ exports.resolveInvoiceReceiptLocales = resolveInvoiceReceiptLocales;
12
14
  var _dayjs = _interopRequireDefault(require("dayjs"));
13
15
  var _getDateIsInSchedule = require("../Schedule/getDateIsInSchedule");
14
16
  var _types = require("./types");
@@ -42,6 +44,36 @@ function getDefaultInvoiceTemplateSettings() {
42
44
  modules: []
43
45
  };
44
46
  }
47
+ const INVOICE_RECEIPT_LOCALE_ORDER = ['en', 'zh_CN', 'zh_HK', 'ja', 'pt'];
48
+ const INVOICE_RECEIPT_LOCALE_MAPPING = {
49
+ en: ['en'],
50
+ zh_cn: ['zh_CN'],
51
+ zh_hk: ['zh_HK'],
52
+ ja: ['ja'],
53
+ pt: ['pt'],
54
+ cn_en: ['en', 'zh_CN']
55
+ };
56
+
57
+ /**
58
+ * 将非 DEFAULT Invoice 模板启用的语言转换为 small_ticket_data locale。
59
+ * cn_en 是中英组合打印模式,因此展开为 en 与 zh_CN 两份数据。
60
+ */
61
+ function resolveInvoiceReceiptLocales(settings) {
62
+ if (typeof settings?.template !== 'string' || settings.template.trim().length === 0 || settings.template.trim().toUpperCase() === 'DEFAULT' || !isRecord(settings.language)) {
63
+ return [];
64
+ }
65
+ const enabledLocales = new Set();
66
+ Object.entries(settings.language).forEach(([rawKey, count]) => {
67
+ if (typeof count !== 'number' || !Number.isFinite(count) || count <= 0) {
68
+ return;
69
+ }
70
+ const key = rawKey.trim().toLowerCase().replace(/-/g, '_');
71
+ INVOICE_RECEIPT_LOCALE_MAPPING[key]?.forEach(locale => {
72
+ enabledLocales.add(locale);
73
+ });
74
+ });
75
+ return INVOICE_RECEIPT_LOCALE_ORDER.filter(locale => enabledLocales.has(locale));
76
+ }
45
77
  function normalizeInvoiceTemplate(value) {
46
78
  if (typeof value !== 'string' || value.trim().length === 0) return 'DEFAULT';
47
79
  return value.trim().toUpperCase() === 'DEFAULT' ? 'DEFAULT' : value;
@@ -87,16 +119,19 @@ function normalizeInvoiceTemplateSettings(config) {
87
119
  template: normalizeInvoiceTemplate(config?.['documents.invoice_template'])
88
120
  });
89
121
  }
122
+ function normalizeInvoiceLayoutFromTemplateSettings(settings) {
123
+ const modules = normalizeRawInvoiceTemplateSettings(settings).modules;
124
+ if (modules.length === 0) return getDefaultInvoiceLayoutConfig();
125
+ return {
126
+ module: modules
127
+ };
128
+ }
90
129
 
91
130
  /**
92
131
  * 将 invoice_edit.modules 原样投影为 Web Invoice 使用的 module 字段。
93
132
  */
94
133
  function normalizeInvoiceLayoutConfig(config) {
95
- const modules = normalizeInvoiceTemplateSettings(config).modules;
96
- if (modules.length === 0) return getDefaultInvoiceLayoutConfig();
97
- return {
98
- module: modules
99
- };
134
+ return normalizeInvoiceLayoutFromTemplateSettings(normalizeInvoiceTemplateSettings(config));
100
135
  }
101
136
  function toBoolean(value) {
102
137
  if (typeof value === 'boolean') return value;
@@ -528,7 +528,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
528
528
  generateIdempotencyToken(): string;
529
529
  syncPaymentsToOrder<T = any>(params: SyncPaymentsToOrderParams): Promise<SyncPaymentsToOrderResult<T>>;
530
530
  createOrder(params: CommitOrderParams['query']): {
531
- type: "virtual" | "appointment_booking";
531
+ type: "appointment_booking" | "virtual";
532
532
  platform: string;
533
533
  sales_channel: string;
534
534
  order_sales_channel: string;
@@ -69,6 +69,7 @@ export interface RequestOptions {
69
69
  customToast?: () => void;
70
70
  cache?: CacheProps;
71
71
  osServer?: boolean;
72
+ isNocobase?: boolean;
72
73
  prefix?: boolean;
73
74
  callback?: (res: any) => void;
74
75
  subscriberId?: string;
@@ -124,7 +125,7 @@ declare class RequestPluginImpl implements RequestPlugin {
124
125
  /**
125
126
  * GET 请求
126
127
  */
127
- get<T = any>(url: string, options?: RequestOptions): Promise<T>;
128
+ get<T = any>(url: string, dataOrOptions?: any, options?: RequestOptions): Promise<T>;
128
129
  /**
129
130
  * POST 请求
130
131
  */
@@ -99,9 +99,10 @@ class RequestPluginImpl {
99
99
  /**
100
100
  * GET 请求
101
101
  */
102
- get(url, options = {}) {
102
+ get(url, dataOrOptions = {}, options) {
103
+ const requestOptions = options ?? dataOrOptions;
103
104
  return this.request({
104
- ...options,
105
+ ...requestOptions,
105
106
  url,
106
107
  method: 'GET'
107
108
  });
@@ -532,6 +532,11 @@ declare class Server {
532
532
  private handleOrderDraftSubmit;
533
533
  private handlePendingSyncCheckoutOrder;
534
534
  private buildPendingSyncCheckoutOrder;
535
+ /**
536
+ * Checkout 与本地小票必须消费同一份规范化子项快照。
537
+ * 商品/预约只折叠持久化 ID 或协议 UID 相同的行;支付额外兼容 payment_number。
538
+ */
539
+ private normalizeCheckoutCollections;
535
540
  /**
536
541
  * Android 结账自动小票由当前设备设置控制;其它平台保持原有行为。
537
542
  */
@@ -27,6 +27,7 @@ var _filterBookings = require("./modules/order/utils/filterBookings");
27
27
  var _smallTicket = require("./utils/small-ticket");
28
28
  var _BaseSales = require("../solution/BaseSales");
29
29
  var _paymentUtils = require("../modules/Order/payment-utils");
30
+ var _orderCollectionIdentity = require("../modules/Order/utils/orderCollectionIdentity");
30
31
  var _OpenData = require("../modules/OpenData");
31
32
  var _modules = require("./modules");
32
33
  Object.keys(_modules).forEach(function (key) {
@@ -631,7 +632,7 @@ class Server {
631
632
  } = params;
632
633
  const normalizedData = await this.normalizeCheckoutSubmitData(params.data, title);
633
634
  const numberedData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
634
- const checkoutData = this.applyCheckoutFulfillmentBuzzer(numberedData, title);
635
+ const checkoutData = this.normalizeCheckoutCollections(this.applyCheckoutFulfillmentBuzzer(numberedData, title));
635
636
  const remoteCheckoutData = this.buildRemoteCheckoutData(checkoutData, title);
636
637
  if (!this.app?.request?.post) {
637
638
  throw new Error('app.request 不可用');
@@ -3725,7 +3726,8 @@ class Server {
3725
3726
  };
3726
3727
  }
3727
3728
  const printableOrder = await this.withLocalSmallTicketData(params.order, {
3728
- requireFullyPaid: params.requireFullyPaid
3729
+ requireFullyPaid: params.requireFullyPaid,
3730
+ invoiceTemplateSettings: params.invoiceTemplateSettings
3729
3731
  });
3730
3732
  if (!(0, _smallTicket.hasSmallTicketData)(printableOrder.payment_info?.small_ticket_data)) {
3731
3733
  return {
@@ -4114,12 +4116,13 @@ class Server {
4114
4116
  }
4115
4117
  const normalizedData = await this.normalizeCheckoutSubmitData(data, title);
4116
4118
  const numberedData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
4117
- const checkoutData = this.applyCheckoutFulfillmentBuzzer(numberedData, title);
4119
+ const checkoutData = this.normalizeCheckoutCollections(this.applyCheckoutFulfillmentBuzzer(numberedData, title));
4118
4120
  const pendingResult = await this.handlePendingSyncCheckoutOrder({
4119
4121
  backendPath,
4120
4122
  data: checkoutData,
4121
4123
  title,
4122
- reason: 'checkout 已转入设备任务同步'
4124
+ reason: 'checkout 已转入设备任务同步',
4125
+ invoiceTemplateSettings
4123
4126
  });
4124
4127
  if (pendingResult?.status === true) {
4125
4128
  const pendingOrder = pendingResult.data;
@@ -4298,9 +4301,10 @@ class Server {
4298
4301
  backendPath,
4299
4302
  data,
4300
4303
  title,
4301
- reason
4304
+ reason,
4305
+ invoiceTemplateSettings
4302
4306
  } = params;
4303
- const pendingOrder = await this.buildPendingSyncCheckoutOrder(data);
4307
+ const pendingOrder = await this.buildPendingSyncCheckoutOrder(data, invoiceTemplateSettings);
4304
4308
  if (!pendingOrder) {
4305
4309
  this.logError(`${title}: checkout 失败且订单缺少本地存储标识`, {
4306
4310
  backendPath,
@@ -4366,23 +4370,39 @@ class Server {
4366
4370
  };
4367
4371
  }
4368
4372
  }
4369
- async buildPendingSyncCheckoutOrder(data) {
4373
+ async buildPendingSyncCheckoutOrder(data, invoiceTemplateSettings) {
4370
4374
  if (!data || typeof data !== 'object') return null;
4371
4375
  const hasStorageKey = data.external_sale_number !== undefined && data.external_sale_number !== null && data.external_sale_number !== '' ? true : data.order_id !== undefined && data.order_id !== null && data.order_id !== '';
4372
4376
  if (!hasStorageKey) return null;
4373
- const pendingOrder = {
4377
+ const pendingOrder = this.normalizeCheckoutCollections({
4374
4378
  ...data,
4375
4379
  need_sync: 1,
4376
4380
  is_draft_order: 0
4377
- };
4381
+ });
4378
4382
  const productMap = await this.buildSmallTicketProductMap(pendingOrder);
4379
4383
  const orderWithProductTitles = this.withPendingSyncProductTitles(pendingOrder, productMap);
4380
4384
  return this.withLocalSmallTicketData(orderWithProductTitles, {
4381
4385
  requireFullyPaid: true,
4382
- productMap
4386
+ productMap,
4387
+ invoiceTemplateSettings
4383
4388
  });
4384
4389
  }
4385
4390
 
4391
+ /**
4392
+ * Checkout 与本地小票必须消费同一份规范化子项快照。
4393
+ * 商品/预约只折叠持久化 ID 或协议 UID 相同的行;支付额外兼容 payment_number。
4394
+ */
4395
+ normalizeCheckoutCollections(data) {
4396
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return data;
4397
+ const normalized = (0, _orderCollectionIdentity.normalizeOrderCollections)(data, {
4398
+ ensureUid: false
4399
+ }).order;
4400
+ if (Array.isArray(normalized.payments)) {
4401
+ normalized.payments = (0, _paymentUtils.mergeOrderPaymentListsByIdentity)([], normalized.payments);
4402
+ }
4403
+ return normalized;
4404
+ }
4405
+
4386
4406
  /**
4387
4407
  * Android 结账自动小票由当前设备设置控制;其它平台保持原有行为。
4388
4408
  */
@@ -4700,11 +4720,27 @@ class Server {
4700
4720
  if (!this.shouldBuildSmallTicketData(order)) return order;
4701
4721
  if (options?.requireFullyPaid && !this.isSalesOrderFullyPaid(order)) return order;
4702
4722
  try {
4703
- const enrichedOrder = await this.enrichPaymentNamesByCode(order);
4723
+ const normalizedCollections = (0, _orderCollectionIdentity.normalizeOrderCollections)(order, {
4724
+ ensureUid: false
4725
+ });
4726
+ const normalizedOrder = normalizedCollections.order;
4727
+ const collapsedDuplicateCounts = {
4728
+ products: normalizedCollections.stats.product.collapsedDuplicateCount,
4729
+ bookings: normalizedCollections.stats.booking.collapsedDuplicateCount,
4730
+ payments: normalizedCollections.stats.payment.collapsedDuplicateCount
4731
+ };
4732
+ if (Object.values(collapsedDuplicateCounts).some(count => count > 0)) {
4733
+ this.logWarning('withLocalSmallTicketData: 折叠重复订单子项', {
4734
+ order_identifier: normalizedOrder.order_id ?? normalizedOrder.external_sale_number ?? normalizedOrder.shop_order_number ?? null,
4735
+ collapsed_duplicate_counts: collapsedDuplicateCounts
4736
+ });
4737
+ }
4738
+ const enrichedOrder = await this.enrichPaymentNamesByCode(normalizedOrder);
4704
4739
  const productMap = options?.productMap ?? (await this.buildSmallTicketProductMap(enrichedOrder));
4705
4740
  const smallTicketData = (0, _smallTicket.buildSmallTicketData)({
4706
4741
  order: enrichedOrder,
4707
4742
  shopInfo: this.getSmallTicketShopInfo(),
4743
+ locales: (0, _OpenData.resolveInvoiceReceiptLocales)(options?.invoiceTemplateSettings),
4708
4744
  productMap
4709
4745
  });
4710
4746
  return {