@pisell/pisellos 2.3.154 → 2.3.156

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.
@@ -328,7 +328,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
328
328
  date: string;
329
329
  status: string;
330
330
  week: string;
331
- weekNum: 0 | 1 | 2 | 3 | 5 | 4 | 6;
331
+ weekNum: 0 | 5 | 3 | 1 | 2 | 4 | 6;
332
332
  }[]>;
333
333
  submitTimeSlot(timeSlots: TimeSliceItem): void;
334
334
  private getScheduleDataByIds;
@@ -595,7 +595,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
595
595
  generateIdempotencyToken(): string;
596
596
  syncPaymentsToOrder<T = any>(params: SyncPaymentsToOrderParams): Promise<SyncPaymentsToOrderResult<T>>;
597
597
  createOrder(params: CommitOrderParams['query']): {
598
- type: "virtual" | "appointment_booking";
598
+ type: "appointment_booking" | "virtual";
599
599
  platform: string;
600
600
  sales_channel: string;
601
601
  order_sales_channel: string;
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "isFulfillmentTimingBooking", {
11
11
  }
12
12
  });
13
13
  var _loadSalesOrderSnapshot = require("../../shared/sales/loadSalesOrderSnapshot");
14
+ var _localEftposState = require("./localEftposState");
14
15
  var _BaseModule = require("../BaseModule");
15
16
  var _types = require("./types");
16
17
  var _utils = require("./utils");
@@ -3173,6 +3174,18 @@ class OrderModule extends _BaseModule.BaseModule {
3173
3174
  const matchMode = options.matchBy === 'compat' || options.matchBy === 'legacy' ? 'compat' : 'payment_number';
3174
3175
  const matchedPayment = (0, _paymentUtils.resolveOrderPaymentIdentity)(tempOrder.payments || [], identity, matchMode);
3175
3176
  if (!matchedPayment) {
3177
+ try {
3178
+ void Promise.resolve(this.logger?.addLog?.({
3179
+ type: 'warning',
3180
+ title: '[PaymentTrace] payment-missing',
3181
+ metadata: {
3182
+ moduleName: this.name,
3183
+ saleId: tempOrder.external_sale_number,
3184
+ paymentNumber: identity,
3185
+ currentPaymentNumbers: (tempOrder.payments || []).map(p => p.payment_number)
3186
+ }
3187
+ })).catch(() => {});
3188
+ } catch {/* 日志失败不影响原异常。 */}
3176
3189
  throw new Error(`未找到支付项: ${identity}`);
3177
3190
  }
3178
3191
  let updatedPayment = null;
@@ -4716,6 +4729,7 @@ class OrderModule extends _BaseModule.BaseModule {
4716
4729
  }
4717
4730
  async runSubmitTempOrder(params) {
4718
4731
  const tempOrder = this.ensureTempOrder();
4732
+ if ((0, _localEftposState.requiresLocalPaymentRefresh)(tempOrder)) throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
4719
4733
  this.prepareVirtualSettlementTempOrder(tempOrder);
4720
4734
  const isOnlineStorePlatform = this.isOnlineStorePlatform();
4721
4735
  const shouldConfirmPendingVoucherPayments = params?.confirmPendingVoucherPayments !== false;
@@ -5190,7 +5204,7 @@ class OrderModule extends _BaseModule.BaseModule {
5190
5204
  query
5191
5205
  } = params;
5192
5206
  const fetchUrl = url || '/order/sales/checkout';
5193
- const requestQuery = this.isSalesCheckoutHandledByOsServer(fetchUrl) ? query : (0, _osPrivatePayload.stripOsPrivatePayloadFields)(query);
5207
+ const requestQuery = this.isSalesCheckoutHandledByOsServer(fetchUrl) ? query : (0, _localEftposState.stripLocalEftposState)((0, _osPrivatePayload.stripOsPrivatePayloadFields)(query));
5194
5208
  return this.request.post(fetchUrl, requestQuery, {
5195
5209
  osServer: true,
5196
5210
  customToast: () => {}
@@ -0,0 +1,13 @@
1
+ /** Local reconciliation state; never part of a cloud checkout payload. */
2
+ export declare const LOCAL_EFTPOS_REFRESH_REQUIRED = "_local_eftpos_refresh_required";
3
+ export declare const LOCAL_EFTPOS_FINALIZED = "_local_eftpos_finalized";
4
+ export declare const PAYMENT_REFRESH_MESSAGE = "\u8BA2\u5355\u9700\u8981\u5237\u65B0\uFF0C\u8BF7\u8054\u7F51\u5237\u65B0\u8BA2\u5355\u540E\u518D\u4ED8\u6B3E";
5
+ export interface LocalEftposFinalization {
6
+ saleId: string | number;
7
+ paymentNumber: string;
8
+ result: 'failed' | 'not_started';
9
+ failedTransactionNumbers: string[];
10
+ }
11
+ export declare function requiresLocalPaymentRefresh(order: any): boolean;
12
+ export declare function isLocalFinalizedPayment(payment: any): boolean;
13
+ export declare function stripLocalEftposState<T extends Record<string, any>>(order: T): T;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.PAYMENT_REFRESH_MESSAGE = exports.LOCAL_EFTPOS_REFRESH_REQUIRED = exports.LOCAL_EFTPOS_FINALIZED = void 0;
7
+ exports.isLocalFinalizedPayment = isLocalFinalizedPayment;
8
+ exports.requiresLocalPaymentRefresh = requiresLocalPaymentRefresh;
9
+ exports.stripLocalEftposState = stripLocalEftposState;
10
+ /** Local reconciliation state; never part of a cloud checkout payload. */
11
+ const LOCAL_EFTPOS_REFRESH_REQUIRED = exports.LOCAL_EFTPOS_REFRESH_REQUIRED = '_local_eftpos_refresh_required';
12
+ const LOCAL_EFTPOS_FINALIZED = exports.LOCAL_EFTPOS_FINALIZED = '_local_eftpos_finalized';
13
+ const PAYMENT_REFRESH_MESSAGE = exports.PAYMENT_REFRESH_MESSAGE = '订单需要刷新,请联网刷新订单后再付款';
14
+ function requiresLocalPaymentRefresh(order) {
15
+ return order?.metadata?.[LOCAL_EFTPOS_REFRESH_REQUIRED] === true;
16
+ }
17
+ function isLocalFinalizedPayment(payment) {
18
+ return payment?.status === 'voided' && payment?.metadata?.[LOCAL_EFTPOS_FINALIZED] === true;
19
+ }
20
+ function stripLocalEftposState(order) {
21
+ const result = {
22
+ ...order
23
+ };
24
+ if (order.metadata) {
25
+ result.metadata = {
26
+ ...order.metadata
27
+ };
28
+ delete result.metadata[LOCAL_EFTPOS_REFRESH_REQUIRED];
29
+ }
30
+ if (Array.isArray(order.payments)) {
31
+ result.payments = order.payments.filter(payment => !isLocalFinalizedPayment(payment)).map(payment => {
32
+ if (!payment.metadata?.[LOCAL_EFTPOS_FINALIZED]) return payment;
33
+ const metadata = {
34
+ ...payment.metadata
35
+ };
36
+ delete metadata[LOCAL_EFTPOS_FINALIZED];
37
+ return {
38
+ ...payment,
39
+ metadata
40
+ };
41
+ });
42
+ }
43
+ return result;
44
+ }
@@ -499,6 +499,7 @@ declare class Server {
499
499
  private buildLocalPaymentSurchargeUpdate;
500
500
  private handleGetLocalPayment;
501
501
  private handleUpdateLocalPayment;
502
+ private handleFinalizeLocalEftposPayment;
502
503
  private runLocalPaymentUpdateInQueue;
503
504
  private processLocalPaymentUpdate;
504
505
  private processSuccessfulLocalPayment;
@@ -7,6 +7,7 @@ var _exportNames = {};
7
7
  exports.default = void 0;
8
8
  var _dayjs = _interopRequireDefault(require("dayjs"));
9
9
  var _decimal = _interopRequireDefault(require("decimal.js"));
10
+ var _localEftposState = require("../modules/Order/localEftposState");
10
11
  var _lodashEs = require("lodash-es");
11
12
  var _products = require("./modules/products");
12
13
  var _menu = require("./modules/menu");
@@ -646,7 +647,7 @@ class Server {
646
647
  const normalizedData = await this.normalizeCheckoutSubmitData(params.data, title);
647
648
  const numberedData = await this.ensureCheckoutOrderNumbers(normalizedData, title);
648
649
  const checkoutData = this.normalizeCheckoutCollections((0, _osPrivatePayload.stripOsPrivatePayloadFields)(this.applyCheckoutFulfillmentBuzzer(numberedData, title)));
649
- const remoteCheckoutData = this.buildRemoteCheckoutData(checkoutData, title);
650
+ const remoteCheckoutData = (0, _localEftposState.stripLocalEftposState)(this.buildRemoteCheckoutData(checkoutData, title));
650
651
  const deviceHeaders = await this.buildCheckoutDeviceHeaders(title);
651
652
  if (!this.app?.request?.post) {
652
653
  throw new Error('app.request 不可用');
@@ -784,7 +785,7 @@ class Server {
784
785
  }
785
786
  const freshPayments = freshRecord.payments;
786
787
  const mergedPayments = (0, _paymentUtils.mergeOrderPaymentListsByIdentity)(localPayments, freshPayments, {
787
- preserveUnmatchedExistingWhen: _paymentUtils.isIdentifiedPendingOrderPayment
788
+ preserveUnmatchedExistingWhen: payment => (0, _paymentUtils.isIdentifiedPendingOrderPayment)(payment) || (0, _localEftposState.isLocalFinalizedPayment)(payment)
788
789
  });
789
790
  const remoteIdentityKeys = new Set(freshPayments.flatMap(payment => (0, _paymentUtils.getOrderPaymentIdentityKeys)(payment)));
790
791
  const preservedPending = localPayments.filter(payment => (0, _paymentUtils.isIdentifiedPendingOrderPayment)(payment) && !(0, _paymentUtils.getOrderPaymentIdentityKeys)(payment).some(key => remoteIdentityKeys.has(key)));
@@ -1337,6 +1338,19 @@ class Server {
1337
1338
  method: 'post',
1338
1339
  path: '/shop/local/updateLocalPayment',
1339
1340
  handler: this.handleUpdateLocalPayment.bind(this)
1341
+ }, {
1342
+ method: 'post',
1343
+ path: '/shop/local/finalizeLocalEftposPayment',
1344
+ handler: this.handleFinalizeLocalEftposPayment.bind(this)
1345
+ }, {
1346
+ method: 'post',
1347
+ path: '/shop/local/orderPaymentReadiness',
1348
+ handler: async ({
1349
+ data
1350
+ }) => ({
1351
+ code: 200,
1352
+ data: await this.order?.getLocalOrderByLookup(data?.sale_id)
1353
+ })
1340
1354
  }, {
1341
1355
  method: 'post',
1342
1356
  path: '/shop/machinecode/batch',
@@ -2998,7 +3012,7 @@ class Server {
2998
3012
  data: null
2999
3013
  };
3000
3014
  }
3001
- const queueKey = `${String(saleId)}:${String(paymentNumber)}`;
3015
+ const queueKey = String(saleId);
3002
3016
  return this.runLocalPaymentUpdateInQueue(queueKey, () => this.processLocalPaymentUpdate({
3003
3017
  operationId,
3004
3018
  saleId,
@@ -3010,6 +3024,42 @@ class Server {
3010
3024
  hasSurchargeInput
3011
3025
  }), requestContext);
3012
3026
  };
3027
+ handleFinalizeLocalEftposPayment = async ({
3028
+ data
3029
+ }) => {
3030
+ const {
3031
+ sale_id: saleId,
3032
+ payment_number: paymentNumber,
3033
+ result,
3034
+ failed_transaction_numbers: numbers = []
3035
+ } = data || {};
3036
+ if (!saleId || !paymentNumber || !Array.isArray(numbers) || numbers.some(number => typeof number !== 'string' || !number.trim()) || result !== 'failed' && result !== 'not_started' || result === 'failed' && numbers.length === 0 || result === 'not_started' && numbers.length !== 0) {
3037
+ return {
3038
+ code: 400,
3039
+ status: false,
3040
+ message: '缺少明确的支付结果或交易编号',
3041
+ data: null
3042
+ };
3043
+ }
3044
+ return this.runLocalPaymentUpdateInQueue(String(saleId), async () => {
3045
+ if (!this.order) throw new Error('Order 模块未注册');
3046
+ const finalized = await this.order.finalizeLocalEftposPayment({
3047
+ saleId,
3048
+ paymentNumber,
3049
+ result,
3050
+ failedTransactionNumbers: [...new Set(numbers)]
3051
+ });
3052
+ return {
3053
+ code: 200,
3054
+ status: true,
3055
+ local_only: true,
3056
+ data: finalized
3057
+ };
3058
+ }, {
3059
+ sale_id: saleId,
3060
+ payment_number: paymentNumber
3061
+ });
3062
+ };
3013
3063
  async runLocalPaymentUpdateInQueue(key, task, context) {
3014
3064
  const queuedAt = Date.now();
3015
3065
  const hasPreviousTask = this.localPaymentUpdateQueues.has(key);
@@ -3769,7 +3819,7 @@ class Server {
3769
3819
  device_id: debugDeviceId,
3770
3820
  payload: {
3771
3821
  backendPath: params.backendPath,
3772
- data: params.data,
3822
+ data: (0, _localEftposState.stripLocalEftposState)(params.data),
3773
3823
  invoice: this.normalizePrintInvoiceLayoutConfig(params.invoiceLayoutConfig),
3774
3824
  template_settings: this.normalizePrintInvoiceTemplateSettings(params.invoiceTemplateSettings),
3775
3825
  external_sale_number: params.data?.external_sale_number,
@@ -4324,6 +4374,16 @@ class Server {
4324
4374
  backendPath,
4325
4375
  title
4326
4376
  } = params;
4377
+ const lookup = params.data?.order_id || params.data?.external_sale_number;
4378
+ const localOrder = lookup && (await this.order?.getLocalOrderByLookup?.(lookup));
4379
+ if ((0, _localEftposState.requiresLocalPaymentRefresh)(params.data) || (0, _localEftposState.requiresLocalPaymentRefresh)(localOrder)) {
4380
+ return {
4381
+ code: 409,
4382
+ status: false,
4383
+ message: _localEftposState.PAYMENT_REFRESH_MESSAGE,
4384
+ data: null
4385
+ };
4386
+ }
4327
4387
  const extractedInvoice = this.extractInvoiceLayoutConfig(params.data, {
4328
4388
  includePublicInvoice: true
4329
4389
  });
@@ -5072,7 +5132,15 @@ class Server {
5072
5132
  order: null
5073
5133
  };
5074
5134
  }
5075
- await this.order.upsertOrdersFromRemote([fresh]);
5135
+ const localBeforeRefresh = this.order.getLocalOrderFromMemoryByLookup?.(lookup) || (this.shouldForceRemoteSalesDetail(data) ? await this.order.getLocalOrderByLookup(lookup) : null);
5136
+ if ((0, _localEftposState.requiresLocalPaymentRefresh)(localBeforeRefresh)) {
5137
+ if (String(fresh.order_id) !== String(localBeforeRefresh.order_id)) {
5138
+ throw new Error('远端订单身份不匹配,请重新刷新');
5139
+ }
5140
+ await this.order.completeLocalPaymentRefresh(fresh);
5141
+ } else {
5142
+ await this.order.upsertOrdersFromRemote([fresh]);
5143
+ }
5076
5144
  let savedOrder = await this.order.getLocalOrderByLookup(lookup);
5077
5145
  if (!savedOrder && fresh.order_id !== undefined && fresh.order_id !== null && String(fresh.order_id) !== String(lookup)) {
5078
5146
  savedOrder = await this.order.getLocalOrderByLookup(fresh.order_id);
@@ -123,11 +123,24 @@ export declare class OrderModule extends BaseModule implements Module {
123
123
  paymentNumber: OrderId;
124
124
  operationId?: string;
125
125
  updates: Record<string, any>;
126
+ finalizeUnsuccessful?: boolean;
126
127
  }): Promise<{
127
128
  order: OrderData;
128
129
  payment: Record<string, any>;
129
130
  previousPayment: Record<string, any>;
130
131
  } | null>;
132
+ finalizeLocalEftposPayment(params: {
133
+ saleId: OrderId;
134
+ paymentNumber: OrderId;
135
+ result: 'failed' | 'not_started';
136
+ failedTransactionNumbers: string[];
137
+ }): Promise<{
138
+ order: OrderData;
139
+ payment: Record<string, any>;
140
+ previousPayment: Record<string, any>;
141
+ } | null>;
142
+ /** Only a successful full detail GET may release the persisted payment gate. */
143
+ completeLocalPaymentRefresh(fresh: OrderData): Promise<OrderData>;
131
144
  loadOrdersByServer(options?: {
132
145
  trigger?: string;
133
146
  }): Promise<OrderData[]>;
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.OrderModule = void 0;
7
7
  var _lodashEs = require("lodash-es");
8
+ var _localEftposState = require("../../../modules/Order/localEftposState");
8
9
  var _dayjs = _interopRequireDefault(require("dayjs"));
9
10
  var _BaseModule = require("../../../modules/BaseModule");
10
11
  var _orderCollectionIdentity = require("../../../modules/Order/utils/orderCollectionIdentity");
@@ -259,9 +260,9 @@ class OrderModule extends _BaseModule.BaseModule {
259
260
  mergeAuthoritativeOrderCollection(field, existing, incoming, uidRemaps = [], options = {}) {
260
261
  const kind = ORDER_COLLECTION_KIND_BY_FIELD[field];
261
262
  const existingItems = Array.isArray(existing) ? existing : [];
262
- if (field === 'payments' && options.preserveIdentifiedPendingPayments) {
263
+ if (field === 'payments' && (options.preserveIdentifiedPendingPayments || existingItems.some(_localEftposState.isLocalFinalizedPayment))) {
263
264
  return (0, _paymentUtils.mergeOrderPaymentListsByIdentity)(existingItems, incoming, {
264
- preserveUnmatchedExistingWhen: _paymentUtils.isIdentifiedPendingOrderPayment
265
+ preserveUnmatchedExistingWhen: payment => options.preserveIdentifiedPendingPayments === true && (0, _paymentUtils.isIdentifiedPendingOrderPayment)(payment) || (0, _localEftposState.isLocalFinalizedPayment)(payment)
265
266
  });
266
267
  }
267
268
  return incoming.map(incomingItem => {
@@ -753,6 +754,13 @@ class OrderModule extends _BaseModule.BaseModule {
753
754
  return null;
754
755
  }
755
756
  const previousPayment = (0, _lodashEs.cloneDeep)(found.payment);
757
+ if (params.finalizeUnsuccessful && (found.payment.status === 'paid' || found.payment.metadata?.transactions?.some(transaction => transaction.status === 'success') || (0, _localEftposState.isLocalFinalizedPayment)(found.payment))) {
758
+ return {
759
+ order: found.order,
760
+ payment: found.payment,
761
+ previousPayment
762
+ };
763
+ }
756
764
  const nextPayment = (0, _paymentUtils.mergeOrderPaymentRecord)(found.payment, {
757
765
  ...params.updates,
758
766
  updated_at: params.updates.updated_at || (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss')
@@ -762,6 +770,13 @@ class OrderModule extends _BaseModule.BaseModule {
762
770
  payments: (found.order.payments || []).map((payment, index) => index === found.paymentIndex ? nextPayment : payment),
763
771
  updated_at: (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss')
764
772
  };
773
+ if (params.finalizeUnsuccessful && this.hasRealOrderId(found.order) && Number(found.order.need_sync || 0) === 0 && !(nextOrder.payments || []).some(_paymentUtils.isPendingOrderPayment)) {
774
+ nextOrder.is_draft_order = 0;
775
+ nextOrder.metadata = {
776
+ ...nextOrder.metadata,
777
+ [_localEftposState.LOCAL_EFTPOS_REFRESH_REQUIRED]: true
778
+ };
779
+ }
765
780
  const paymentContext = {
766
781
  ...logContext,
767
782
  matched_by: found.matchedBy,
@@ -813,6 +828,60 @@ class OrderModule extends _BaseModule.BaseModule {
813
828
  previousPayment
814
829
  };
815
830
  }
831
+ async finalizeLocalEftposPayment(params) {
832
+ const found = await this.getLocalPayment(params);
833
+ if (!found) throw new Error('未找到本地支付记录');
834
+ const transactions = found.payment.metadata?.transactions || [];
835
+ if (transactions.some(transaction => transaction.status !== 'fail' && transaction.status !== 'success' && !params.failedTransactionNumbers.includes(String(transaction.number)))) {
836
+ throw new Error('仍有结果未知的交易,保留待确认支付');
837
+ }
838
+ if (params.result === 'not_started' && transactions.length > 0 && !(0, _localEftposState.isLocalFinalizedPayment)(found.payment) && found.payment.status !== 'paid') {
839
+ throw new Error('支付已有交易记录,不能按未发起交易结束');
840
+ }
841
+ return this.updateLocalPayment({
842
+ ...params,
843
+ finalizeUnsuccessful: true,
844
+ updates: {
845
+ status: 'voided',
846
+ metadata: {
847
+ [_localEftposState.LOCAL_EFTPOS_FINALIZED]: true,
848
+ transactions: params.failedTransactionNumbers.map(number => ({
849
+ number,
850
+ status: 'fail'
851
+ }))
852
+ }
853
+ }
854
+ });
855
+ }
856
+
857
+ /** Only a successful full detail GET may release the persisted payment gate. */
858
+ async completeLocalPaymentRefresh(fresh) {
859
+ const existing = await this.getLocalOrderByLookup(fresh.order_id);
860
+ if (!existing || !(0, _localEftposState.requiresLocalPaymentRefresh)(existing)) {
861
+ await this.upsertOrdersFromRemote([fresh]);
862
+ return (await this.getLocalOrderByLookup(fresh.order_id)) || fresh;
863
+ }
864
+ if (this.isPendingSyncOrder(existing) || this.isDraftOrder(existing) || (existing.payments || []).some(_paymentUtils.isPendingOrderPayment)) {
865
+ throw new Error('订单仍有未结束支付或待同步改动,暂不能刷新');
866
+ }
867
+ if (!Array.isArray(fresh.products) || !fresh.summary) {
868
+ throw new Error('远端订单详情不完整,请重新刷新');
869
+ }
870
+ const nextOrder = this.mergeOrderRecords(existing, this.normalizeRemoteSyncedOrder(fresh));
871
+ nextOrder.metadata = {
872
+ ...nextOrder.metadata
873
+ };
874
+ delete nextOrder.metadata[_localEftposState.LOCAL_EFTPOS_REFRESH_REQUIRED];
875
+ // Do not publish/clear the gate in memory until SQLite has accepted the full snapshot.
876
+ await this.updateOrderInSQLite(nextOrder, 'completeLocalPaymentRefresh', {
877
+ throwOnError: true
878
+ });
879
+ const index = this.findOrderIndexByIdentity(this.store.list, existing);
880
+ this.store.list = index < 0 ? [...this.store.list, nextOrder] : this.store.list.map((order, i) => i === index ? nextOrder : order);
881
+ this.syncOrdersMap();
882
+ await this.emitOrdersChanged([nextOrder], 'completeLocalPaymentRefresh');
883
+ return nextOrder;
884
+ }
816
885
  async loadOrdersByServer(options = {}) {
817
886
  let orderList = [];
818
887
  let hasFetchedRemoteSnapshot = false;
@@ -1997,7 +2066,7 @@ class OrderModule extends _BaseModule.BaseModule {
1997
2066
  return payments.filter(payment => (0, _paymentUtils.isIdentifiedPendingOrderPayment)(payment));
1998
2067
  }
1999
2068
  shouldProtectLocalOrderFromRemoteSnapshot(order) {
2000
- return this.isPendingSyncOrder(order) || this.isDraftOrder(order) || this.getIdentifiedPendingPayments(order).length > 0;
2069
+ return this.isPendingSyncOrder(order) || this.isDraftOrder(order) || this.getIdentifiedPendingPayments(order).length > 0 || (0, _localEftposState.requiresLocalPaymentRefresh)(order) || (order.payments || []).some(_localEftposState.isLocalFinalizedPayment);
2001
2070
  }
2002
2071
  getUnmatchedIdentifiedPendingPayments(existing, incoming) {
2003
2072
  const existingPayments = Array.isArray(existing) ? existing : [];
@@ -2019,6 +2088,12 @@ class OrderModule extends _BaseModule.BaseModule {
2019
2088
  const secondaryRecord = secondary;
2020
2089
  const existingRecord = existing;
2021
2090
  const incomingRecord = incoming;
2091
+ if ((0, _localEftposState.requiresLocalPaymentRefresh)(existing)) {
2092
+ mergedRecord.metadata = {
2093
+ ...mergedRecord.metadata,
2094
+ [_localEftposState.LOCAL_EFTPOS_REFRESH_REQUIRED]: true
2095
+ };
2096
+ }
2022
2097
  const childUidRemaps = [];
2023
2098
  for (const field of fieldsToPreserve) {
2024
2099
  if (!this.isBlankIdentityValue(preferredRecord[field])) {
@@ -1,5 +1,6 @@
1
1
  import { Module, ModuleOptions, PisellCore } from '../../types';
2
2
  import { BaseModule } from '../../modules/BaseModule';
3
+ import { type LocalEftposFinalization } from '../../modules/Order/localEftposState';
3
4
  import { BaseSalesOrderProduct, BaseSalesOrderProductIdentity, BaseSalesPaymentStatus, BaseSalesSubmitSalesOrderParams, BaseSalesUpdateOrderProductQuantityParams, BaseSalesCalculateProductBookingPriceParams, BaseSalesPriceRecalculationContextChange, BaseSalesProductBookingPriceResult, BaseSalesQuotationCustomerScopeInfo, BaseSalesScanCodeResult, BaseSalesCashPaymentOptions, BaseSalesCashPaymentConfig, BaseSalesPayCashParams, BaseSalesPayCashResult, BaseSalesCommitPaymentMethodParams, BaseSalesCommitPaymentMethodResult, BaseSalesConfirmWalletPaymentResult } from './types';
4
5
  import { OrderModule } from '../../modules/Order';
5
6
  import { type PickupTimingBookingInput } from '../../modules/Order/pickupTiming';
@@ -105,6 +106,7 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
105
106
  private isCurrentSalesOrderRecord;
106
107
  setServerOrderChangeSyncEnabled(enabled: boolean): void;
107
108
  private queueLatestServerOrderChange;
109
+ private shouldSyncServerOrderChanges;
108
110
  private drainQueuedServerOrderChanges;
109
111
  private syncCurrentSalesDetailFromServerOrderChange;
110
112
  private registerServerOrderChangeSync;
@@ -353,6 +355,10 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
353
355
  * payments 会交给 OrderModule 统一映射为 Sales 协议支付项。
354
356
  */
355
357
  submitSalesOrder<T = any>(params?: BaseSalesSubmitSalesOrderParams): Promise<T>;
358
+ private pendingLocalEftposFinalizations;
359
+ finalizeLocalEftposPayment(params: LocalEftposFinalization): Promise<any>;
360
+ /** Entry gate also reads SQLite state, so stale sessions/restarts cannot bypass it. */
361
+ ensureSalesOrderReadyForPayment(): Promise<void>;
356
362
  saveSalesOrderDraft<T = any>(params?: {
357
363
  platform?: string;
358
364
  businessCode?: string;
@@ -8,6 +8,7 @@ var _exportNames = {
8
8
  };
9
9
  exports.BaseSalesImpl = void 0;
10
10
  var _BaseModule = require("../../modules/BaseModule");
11
+ var _localEftposState = require("../../modules/Order/localEftposState");
11
12
  var _types = require("./types");
12
13
  Object.keys(_types).forEach(function (key) {
13
14
  if (key === "default" || key === "__esModule") return;
@@ -590,8 +591,12 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
590
591
  }
591
592
  this.queuedServerOrderChanges.push(snapshot);
592
593
  }
594
+ shouldSyncServerOrderChanges() {
595
+ const payments = this.store.order?.getTempOrder()?.payments || [];
596
+ return this.serverOrderChangeSyncEnabled || payments.some(_localEftposState.isLocalFinalizedPayment) && !payments.some(_paymentUtils.isPendingOrderPayment);
597
+ }
593
598
  async drainQueuedServerOrderChanges() {
594
- if (!this.serverOrderChangeSyncEnabled || this.salesDetailLoadInFlightCount > 0 || this.serverOrderChangeHydrateInFlight || this.queuedServerOrderChanges.length === 0) return;
599
+ if (!this.shouldSyncServerOrderChanges() || this.salesDetailLoadInFlightCount > 0 || this.serverOrderChangeHydrateInFlight || this.queuedServerOrderChanges.length === 0) return;
595
600
  const changedOrders = this.queuedServerOrderChanges;
596
601
  this.queuedServerOrderChanges = [];
597
602
  await this.syncCurrentSalesDetailFromServerOrderChange({
@@ -599,8 +604,9 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
599
604
  });
600
605
  }
601
606
  async syncCurrentSalesDetailFromServerOrderChange(payload) {
602
- const changedOrders = Array.isArray(payload) ? payload : Array.isArray(payload?.changedOrders) ? payload.changedOrders : payload?.data && typeof payload.data === 'object' ? [payload.data] : [];
603
- if (!this.serverOrderChangeSyncEnabled) return;
607
+ const fullSnapshotAfterFinalization = Array.isArray(payload?.list) && this.store.order?.getTempOrder()?.payments?.some(_localEftposState.isLocalFinalizedPayment);
608
+ const changedOrders = Array.isArray(payload) ? payload : Array.isArray(payload?.changedOrders) && payload.changedOrders.length > 0 ? payload.changedOrders : fullSnapshotAfterFinalization ? payload.list : payload?.data && typeof payload.data === 'object' ? [payload.data] : [];
609
+ if (!this.shouldSyncServerOrderChanges()) return;
604
610
 
605
611
  // 显式详情加载期间 current 可能仍是上一单,先按订单身份 latest-only 缓存;
606
612
  // load 完成后再用新的 current 过滤,避免恰好到达的最新支付快照丢失。
@@ -1043,7 +1049,30 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1043
1049
  customerId: params.customerId,
1044
1050
  channel: params.channel
1045
1051
  });
1046
- return productIds.map(productId => productId === null ? null : productsById.get(productId) || null);
1052
+ const shouldAlignSelectedVariantPrice = handledByOsServer && this.preferAuthoritativeProductQueryPrice();
1053
+ return productIds.map((productId, index) => {
1054
+ const authoritativeProduct = productId === null ? null : productsById.get(productId) || null;
1055
+ if (!shouldAlignSelectedVariantPrice || !authoritativeProduct) return authoritativeProduct;
1056
+ const selectedProduct = selectedProducts[index];
1057
+ const selectedVariantId = this.getSelectedProductVariantId(selectedProduct);
1058
+ if (!selectedVariantId || this.getSelectedProductVariantId(authoritativeProduct) === selectedVariantId) {
1059
+ return authoritativeProduct;
1060
+ }
1061
+
1062
+ // OS 返回的是目录商品;已命中的智能价格不能被规格基础价覆盖。
1063
+ const appliedFields = authoritativeProduct._dataVariant?.appliedFields;
1064
+ if (Array.isArray(appliedFields) && ['price', 'selling_price', 'original_price', 'base_price'].some(field => appliedFields.includes(field))) {
1065
+ return authoritativeProduct;
1066
+ }
1067
+ const selectedVariant = Array.isArray(authoritativeProduct.variant) ? authoritativeProduct.variant.find(variant => Number(variant?.id) === selectedVariantId) : undefined;
1068
+ const selectedPrice = [selectedVariant?.price, selectedVariant?.selling_price, selectedVariant?.base_price, params.priceQueries[index].fallback_price, selectedProduct.price, selectedProduct.selling_price, selectedProduct.original_price].find(value => (typeof value === 'number' || typeof value === 'string' && value.trim() !== '') && Number.isFinite(Number(value)));
1069
+
1070
+ // 每行独立保留所选规格价格(含 0 元),避免同商品多规格共用目录对象串价。
1071
+ return selectedPrice === undefined ? authoritativeProduct : {
1072
+ ...authoritativeProduct,
1073
+ price: selectedPrice
1074
+ };
1075
+ });
1047
1076
  }
1048
1077
  const queryPayload = this.buildPriceQueryPayload({
1049
1078
  ids,
@@ -2728,6 +2757,102 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2728
2757
  };
2729
2758
  return this.shouldUseCheckoutSyncTask() ? this.store.order.submitTempOrder(submitParams) : this.store.order.submitTempOrderAsync(submitParams);
2730
2759
  }
2760
+ pendingLocalEftposFinalizations = new Map();
2761
+ async finalizeLocalEftposPayment(params) {
2762
+ const key = `${params.saleId}:${params.paymentNumber}`;
2763
+ // If SQLite rejects the write, retry this known result before another payment can start.
2764
+ this.pendingLocalEftposFinalizations.set(key, params);
2765
+ const response = await this.request.post('/local/finalizeLocalEftposPayment', {
2766
+ sale_id: params.saleId,
2767
+ payment_number: params.paymentNumber,
2768
+ result: params.result,
2769
+ failed_transaction_numbers: params.failedTransactionNumbers
2770
+ }, {
2771
+ osServer: true,
2772
+ customToast: () => {}
2773
+ });
2774
+ if (response?.code !== 200 || !response?.data?.order) {
2775
+ throw new Error(response?.message || '本地支付收尾失败,请刷新订单后重试');
2776
+ }
2777
+ this.pendingLocalEftposFinalizations.delete(key);
2778
+ const {
2779
+ order,
2780
+ payment
2781
+ } = response.data;
2782
+ const temp = this.getTempOrder();
2783
+ const isCurrentOrder = String(temp?.external_sale_number || temp?.order_id) === String(params.saleId) || temp?.order_id && String(temp.order_id) === String(order.order_id);
2784
+ if (isCurrentOrder) {
2785
+ // Persisted by the local route already. Saving a draft here would re-enable protection.
2786
+ if (temp?.payments?.some(item => String(item.payment_number) === params.paymentNumber)) {
2787
+ await this.updateOrderPayment(params.paymentNumber, payment, {
2788
+ persistDraft: false,
2789
+ submitWhenPaid: false
2790
+ });
2791
+ }
2792
+ if ((0, _localEftposState.requiresLocalPaymentRefresh)(order)) {
2793
+ // Set the in-memory gate before the GET so a failed refresh also blocks this page.
2794
+ const currentTemp = this.getTempOrder();
2795
+ if (currentTemp) currentTemp.metadata = {
2796
+ ...currentTemp.metadata,
2797
+ ...order.metadata
2798
+ };
2799
+ try {
2800
+ await this.loadSalesDetail({
2801
+ orderId: order.order_id,
2802
+ forceRemote: true,
2803
+ merge: false
2804
+ });
2805
+ } catch {
2806
+ throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
2807
+ }
2808
+ }
2809
+ } else if ((0, _localEftposState.requiresLocalPaymentRefresh)(order)) {
2810
+ const refreshed = await this.store.order?.getSalesOrderByLookup({
2811
+ lookup: order.order_id,
2812
+ forceRemote: true
2813
+ });
2814
+ if (refreshed?.code !== 200) throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
2815
+ }
2816
+ return response.data;
2817
+ }
2818
+
2819
+ /** Entry gate also reads SQLite state, so stale sessions/restarts cannot bypass it. */
2820
+ async ensureSalesOrderReadyForPayment() {
2821
+ const temp = this.getTempOrder();
2822
+ const unfinished = [...this.pendingLocalEftposFinalizations.values()].filter(params => String(params.saleId) === String(temp?.external_sale_number) || String(params.saleId) === String(temp?.order_id));
2823
+ if (unfinished.length > 0) {
2824
+ for (const params of unfinished) await this.finalizeLocalEftposPayment(params);
2825
+ throw new Error('订单已更新,请确认商品和金额后重新付款');
2826
+ }
2827
+ const lookup = temp?.order_id || temp?.external_sale_number;
2828
+ let localOrder = null;
2829
+ if (lookup && this.core?.server?.hasRoute?.('post', '/shop/local/orderPaymentReadiness')) {
2830
+ const response = await this.request.post('/local/orderPaymentReadiness', {
2831
+ sale_id: lookup
2832
+ }, {
2833
+ osServer: true,
2834
+ customToast: () => {}
2835
+ });
2836
+ if (response?.code !== 200) throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
2837
+ localOrder = response.data;
2838
+ }
2839
+ if (!(0, _localEftposState.requiresLocalPaymentRefresh)(temp) && !(0, _localEftposState.requiresLocalPaymentRefresh)(localOrder)) return;
2840
+ const orderId = localOrder?.order_id || temp?.order_id;
2841
+ if (!orderId || String(orderId) === String(temp?.external_sale_number)) {
2842
+ throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
2843
+ }
2844
+ try {
2845
+ await this.loadSalesDetail({
2846
+ orderId,
2847
+ forceRemote: true,
2848
+ merge: false
2849
+ });
2850
+ } catch {
2851
+ throw new Error(_localEftposState.PAYMENT_REFRESH_MESSAGE);
2852
+ }
2853
+ // The initiating UI may have captured the old amount before this await.
2854
+ throw new Error('订单已刷新,请确认商品和金额后重新付款');
2855
+ }
2731
2856
  async saveSalesOrderDraft(params) {
2732
2857
  if (!this.store.order) {
2733
2858
  throw new Error('BaseSales 解决方案需要 order 模块支持');
@@ -328,7 +328,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
328
328
  date: string;
329
329
  status: string;
330
330
  week: string;
331
- weekNum: 0 | 1 | 2 | 3 | 5 | 4 | 6;
331
+ weekNum: 0 | 5 | 3 | 1 | 2 | 4 | 6;
332
332
  }[]>;
333
333
  submitTimeSlot(timeSlots: TimeSliceItem): void;
334
334
  private getScheduleDataByIds;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@pisell/pisellos",
4
- "version": "2.3.154",
4
+ "version": "2.3.156",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",