@pisell/pisellos 2.2.124 → 2.2.126
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/core/index.js +1 -0
- package/dist/modules/Quotation/index.d.ts +48 -0
- package/dist/modules/Quotation/index.js +248 -0
- package/dist/modules/Quotation/types.d.ts +42 -0
- package/dist/modules/Quotation/types.js +1 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.js +551 -362
- package/dist/server/modules/order/index.d.ts +7 -0
- package/dist/server/modules/order/index.js +221 -151
- package/dist/solution/Sales/index.d.ts +33 -4
- package/dist/solution/Sales/index.js +94 -38
- package/lib/core/index.js +1 -0
- package/lib/modules/Quotation/index.d.ts +48 -0
- package/lib/modules/Quotation/index.js +152 -0
- package/lib/modules/Quotation/types.d.ts +42 -0
- package/lib/modules/Quotation/types.js +17 -0
- package/lib/server/index.d.ts +11 -0
- package/lib/server/index.js +116 -0
- package/lib/server/modules/order/index.d.ts +7 -0
- package/lib/server/modules/order/index.js +34 -0
- package/lib/solution/Sales/index.d.ts +33 -4
- package/lib/solution/Sales/index.js +98 -48
- package/package.json +1 -1
|
@@ -86,12 +86,41 @@ export declare class SalesImpl extends BaseModule implements Module, SalesModule
|
|
|
86
86
|
*/
|
|
87
87
|
private pickBookingsForCurrentPoint;
|
|
88
88
|
/**
|
|
89
|
-
*
|
|
90
|
-
* -
|
|
91
|
-
* -
|
|
92
|
-
|
|
89
|
+
* 计算 booking 在 current 时刻的执行进度百分比(0-100)。
|
|
90
|
+
* - startAt/endAt 非法或 totalMinutes <= 0 返回 0
|
|
91
|
+
* - current 早于 startAt 返回 0,晚于 endAt 返回 100
|
|
92
|
+
*/
|
|
93
|
+
private calcProgressPercent;
|
|
94
|
+
/**
|
|
95
|
+
* 标准化单条 booking 的分发入口:
|
|
96
|
+
* - 统一过滤 rejected / cancelled(视为没来过)
|
|
97
|
+
* - current === deviceCurrent 走实时分支(信任 appointment_status)
|
|
98
|
+
* - 否则走快照分支(纯时间窗判定,忽略 appointment_status 的 arrived/started/completed 差异)
|
|
93
99
|
*/
|
|
94
100
|
private normalizeMatchedBooking;
|
|
101
|
+
/**
|
|
102
|
+
* 实时模式(current === deviceCurrent):信任 appointment_status
|
|
103
|
+
* - 过滤 new / completed(实时视角下视为非活跃数据)
|
|
104
|
+
* - status 由 getBookingStatus 映射
|
|
105
|
+
* - occupied 且 current > endAt 标记 isTimeout + timeoutTime
|
|
106
|
+
* - reserved 且 current < startAt 标记 not_arrived,否则标记 late
|
|
107
|
+
*/
|
|
108
|
+
private buildRealtimeBooking;
|
|
109
|
+
/**
|
|
110
|
+
* 快照模式(current !== deviceCurrent,过去或未来):忽略 appointment_status
|
|
111
|
+
* 的实时态差异,仅按 booking 时间窗判定,为 current 提供时间点快照视图。
|
|
112
|
+
*
|
|
113
|
+
* - new:视作尚未成为真实预约,过滤
|
|
114
|
+
* - locked:保留 status=locked,不计算 timeout/late/progress
|
|
115
|
+
* - 其他非终态:
|
|
116
|
+
* - startAt/endAt 非法 → 过滤
|
|
117
|
+
* - current > endAt → 过滤(已结束,不入池)
|
|
118
|
+
* - current ∈ [startAt, endAt] → occupied,附带 progressPercent
|
|
119
|
+
* - current < startAt → reserved + not_arrived,附带 remainingReserveTime
|
|
120
|
+
*
|
|
121
|
+
* 快照模式下 isTimeout 恒为 false,lateTime / timeoutTime 始终置空。
|
|
122
|
+
*/
|
|
123
|
+
private buildSnapshotBooking;
|
|
95
124
|
getResourceBookingList(currentTime: string, bookingList?: BookingData[], deviceTime?: string): Promise<SalesResourceBookingItem[]>;
|
|
96
125
|
}
|
|
97
126
|
export { SalesImpl as Sales };
|
|
@@ -429,36 +429,57 @@ export var SalesImpl = /*#__PURE__*/function (_BaseModule) {
|
|
|
429
429
|
}
|
|
430
430
|
|
|
431
431
|
/**
|
|
432
|
-
*
|
|
433
|
-
* -
|
|
434
|
-
* -
|
|
435
|
-
|
|
432
|
+
* 计算 booking 在 current 时刻的执行进度百分比(0-100)。
|
|
433
|
+
* - startAt/endAt 非法或 totalMinutes <= 0 返回 0
|
|
434
|
+
* - current 早于 startAt 返回 0,晚于 endAt 返回 100
|
|
435
|
+
*/
|
|
436
|
+
}, {
|
|
437
|
+
key: "calcProgressPercent",
|
|
438
|
+
value: function calcProgressPercent(current, startAt, endAt) {
|
|
439
|
+
if (!startAt.isValid() || !endAt.isValid()) return 0;
|
|
440
|
+
var totalMinutes = endAt.diff(startAt, 'minute');
|
|
441
|
+
if (totalMinutes <= 0) return 0;
|
|
442
|
+
var elapsedMinutes = current.diff(startAt, 'minute');
|
|
443
|
+
if (elapsedMinutes <= 0) return 0;
|
|
444
|
+
if (elapsedMinutes >= totalMinutes) return 100;
|
|
445
|
+
return Math.floor(elapsedMinutes / totalMinutes * 100);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* 标准化单条 booking 的分发入口:
|
|
450
|
+
* - 统一过滤 rejected / cancelled(视为没来过)
|
|
451
|
+
* - current === deviceCurrent 走实时分支(信任 appointment_status)
|
|
452
|
+
* - 否则走快照分支(纯时间窗判定,忽略 appointment_status 的 arrived/started/completed 差异)
|
|
436
453
|
*/
|
|
437
454
|
}, {
|
|
438
455
|
key: "normalizeMatchedBooking",
|
|
439
456
|
value: function normalizeMatchedBooking(current, deviceCurrent, booking) {
|
|
440
457
|
var _ref3, _booking$appointment_;
|
|
441
458
|
var appointmentStatus = String((_ref3 = (_booking$appointment_ = booking.appointment_status) !== null && _booking$appointment_ !== void 0 ? _booking$appointment_ : booking.status) !== null && _ref3 !== void 0 ? _ref3 : '');
|
|
442
|
-
if (appointmentStatus === '
|
|
443
|
-
|
|
444
|
-
}
|
|
459
|
+
if (appointmentStatus === 'rejected' || appointmentStatus === 'cancelled') return null;
|
|
460
|
+
var startAt = this.toBookingDateTime(booking.start_date, booking.start_time);
|
|
445
461
|
var endAt = this.toBookingDateTime(booking.end_date, booking.end_time);
|
|
446
|
-
|
|
447
|
-
|
|
462
|
+
if (current.isSame(deviceCurrent)) {
|
|
463
|
+
return this.buildRealtimeBooking(current, booking, appointmentStatus, startAt, endAt);
|
|
464
|
+
}
|
|
465
|
+
return this.buildSnapshotBooking(current, booking, appointmentStatus, startAt, endAt);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* 实时模式(current === deviceCurrent):信任 appointment_status
|
|
470
|
+
* - 过滤 new / completed(实时视角下视为非活跃数据)
|
|
471
|
+
* - status 由 getBookingStatus 映射
|
|
472
|
+
* - occupied 且 current > endAt 标记 isTimeout + timeoutTime
|
|
473
|
+
* - reserved 且 current < startAt 标记 not_arrived,否则标记 late
|
|
474
|
+
*/
|
|
475
|
+
}, {
|
|
476
|
+
key: "buildRealtimeBooking",
|
|
477
|
+
value: function buildRealtimeBooking(current, booking, appointmentStatus, startAt, endAt) {
|
|
478
|
+
if (appointmentStatus === 'new' || appointmentStatus === 'completed') return null;
|
|
448
479
|
var bookingStatus = this.getBookingStatus(appointmentStatus);
|
|
449
|
-
var startAt = this.toBookingDateTime(booking.start_date, booking.start_time);
|
|
450
480
|
var isTimeout = bookingStatus === 'occupied' && endAt.isValid() && current.isAfter(endAt);
|
|
451
481
|
var timeoutTime = isTimeout ? current.diff(endAt, 'minute') : undefined;
|
|
452
|
-
var progressPercent =
|
|
453
|
-
if (bookingStatus !== 'occupied') return 0;
|
|
454
|
-
if (!startAt.isValid() || !endAt.isValid()) return 0;
|
|
455
|
-
var totalMinutes = endAt.diff(startAt, 'minute');
|
|
456
|
-
if (totalMinutes <= 0) return 0;
|
|
457
|
-
var elapsedMinutes = current.diff(startAt, 'minute');
|
|
458
|
-
if (elapsedMinutes <= 0) return 0;
|
|
459
|
-
if (elapsedMinutes >= totalMinutes) return 100;
|
|
460
|
-
return Math.floor(elapsedMinutes / totalMinutes * 100);
|
|
461
|
-
}();
|
|
482
|
+
var progressPercent = bookingStatus === 'occupied' ? this.calcProgressPercent(current, startAt, endAt) : 0;
|
|
462
483
|
var reservedStatus;
|
|
463
484
|
var lateTime;
|
|
464
485
|
var remainingReserveTime;
|
|
@@ -471,24 +492,6 @@ export var SalesImpl = /*#__PURE__*/function (_BaseModule) {
|
|
|
471
492
|
lateTime = Math.max(current.diff(startAt, 'minute'), 0);
|
|
472
493
|
}
|
|
473
494
|
}
|
|
474
|
-
// 未来查询投影:查询时间晚于设备时间时,预留+迟到且 end_time 仍在查询时间之后的预约强制视为占用
|
|
475
|
-
if (current.isAfter(deviceCurrent) && bookingStatus === 'reserved' && reservedStatus === 'late' && endAt.isValid() && endAt.isSameOrAfter(current)) {
|
|
476
|
-
bookingStatus = 'occupied';
|
|
477
|
-
reservedStatus = undefined;
|
|
478
|
-
lateTime = undefined;
|
|
479
|
-
remainingReserveTime = undefined;
|
|
480
|
-
isTimeout = false;
|
|
481
|
-
timeoutTime = undefined;
|
|
482
|
-
progressPercent = function () {
|
|
483
|
-
if (!startAt.isValid() || !endAt.isValid()) return 0;
|
|
484
|
-
var totalMinutes = endAt.diff(startAt, 'minute');
|
|
485
|
-
if (totalMinutes <= 0) return 0;
|
|
486
|
-
var elapsedMinutes = current.diff(startAt, 'minute');
|
|
487
|
-
if (elapsedMinutes <= 0) return 0;
|
|
488
|
-
if (elapsedMinutes >= totalMinutes) return 100;
|
|
489
|
-
return Math.floor(elapsedMinutes / totalMinutes * 100);
|
|
490
|
-
}();
|
|
491
|
-
}
|
|
492
495
|
return _objectSpread(_objectSpread({}, booking), {}, {
|
|
493
496
|
status: bookingStatus,
|
|
494
497
|
isTimeout: isTimeout,
|
|
@@ -499,6 +502,59 @@ export var SalesImpl = /*#__PURE__*/function (_BaseModule) {
|
|
|
499
502
|
remainingReserveTime: remainingReserveTime
|
|
500
503
|
});
|
|
501
504
|
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* 快照模式(current !== deviceCurrent,过去或未来):忽略 appointment_status
|
|
508
|
+
* 的实时态差异,仅按 booking 时间窗判定,为 current 提供时间点快照视图。
|
|
509
|
+
*
|
|
510
|
+
* - new:视作尚未成为真实预约,过滤
|
|
511
|
+
* - locked:保留 status=locked,不计算 timeout/late/progress
|
|
512
|
+
* - 其他非终态:
|
|
513
|
+
* - startAt/endAt 非法 → 过滤
|
|
514
|
+
* - current > endAt → 过滤(已结束,不入池)
|
|
515
|
+
* - current ∈ [startAt, endAt] → occupied,附带 progressPercent
|
|
516
|
+
* - current < startAt → reserved + not_arrived,附带 remainingReserveTime
|
|
517
|
+
*
|
|
518
|
+
* 快照模式下 isTimeout 恒为 false,lateTime / timeoutTime 始终置空。
|
|
519
|
+
*/
|
|
520
|
+
}, {
|
|
521
|
+
key: "buildSnapshotBooking",
|
|
522
|
+
value: function buildSnapshotBooking(current, booking, appointmentStatus, startAt, endAt) {
|
|
523
|
+
if (appointmentStatus === 'new') return null;
|
|
524
|
+
if (appointmentStatus === 'locked') {
|
|
525
|
+
return _objectSpread(_objectSpread({}, booking), {}, {
|
|
526
|
+
status: 'locked',
|
|
527
|
+
isTimeout: false,
|
|
528
|
+
timeoutTime: undefined,
|
|
529
|
+
progressPercent: 0,
|
|
530
|
+
lateTime: undefined,
|
|
531
|
+
reserved_status: undefined,
|
|
532
|
+
remainingReserveTime: undefined
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
if (!startAt.isValid() || !endAt.isValid()) return null;
|
|
536
|
+
if (current.isAfter(endAt)) return null;
|
|
537
|
+
if (this.isSameOrAfter(current, startAt) && this.isSameOrBefore(current, endAt)) {
|
|
538
|
+
return _objectSpread(_objectSpread({}, booking), {}, {
|
|
539
|
+
status: 'occupied',
|
|
540
|
+
isTimeout: false,
|
|
541
|
+
timeoutTime: undefined,
|
|
542
|
+
progressPercent: this.calcProgressPercent(current, startAt, endAt),
|
|
543
|
+
lateTime: undefined,
|
|
544
|
+
reserved_status: undefined,
|
|
545
|
+
remainingReserveTime: undefined
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
return _objectSpread(_objectSpread({}, booking), {}, {
|
|
549
|
+
status: 'reserved',
|
|
550
|
+
isTimeout: false,
|
|
551
|
+
timeoutTime: undefined,
|
|
552
|
+
progressPercent: 0,
|
|
553
|
+
lateTime: undefined,
|
|
554
|
+
reserved_status: 'not_arrived',
|
|
555
|
+
remainingReserveTime: Math.max(startAt.diff(current, 'minute'), 0)
|
|
556
|
+
});
|
|
557
|
+
}
|
|
502
558
|
}, {
|
|
503
559
|
key: "getResourceBookingList",
|
|
504
560
|
value: function () {
|
package/lib/core/index.js
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Module, PisellCore, ModuleOptions } from '../../types';
|
|
2
|
+
import { BaseModule } from '../BaseModule';
|
|
3
|
+
import type { ScheduleItem } from '../Schedule/types';
|
|
4
|
+
import type { QuotationItem } from './types';
|
|
5
|
+
export type { QuotationItem, QuotationProductData, QuotationSchedule, QuotationState } from './types';
|
|
6
|
+
export declare class QuotationModule extends BaseModule implements Module {
|
|
7
|
+
protected defaultName: string;
|
|
8
|
+
protected defaultVersion: string;
|
|
9
|
+
private request;
|
|
10
|
+
private store;
|
|
11
|
+
private scheduleResolver?;
|
|
12
|
+
constructor(name?: string, version?: string);
|
|
13
|
+
initialize(core: PisellCore, options: ModuleOptions): Promise<void>;
|
|
14
|
+
loadQuotations(params?: {
|
|
15
|
+
channel?: string;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
getQuotationList(): QuotationItem[];
|
|
18
|
+
/**
|
|
19
|
+
* Look up the quotation price for a specific product (+ optional variant) at a given datetime.
|
|
20
|
+
* Returns the price as a string (e.g. "300.00"), or null if no quotation applies.
|
|
21
|
+
*
|
|
22
|
+
* Priority: iterates quotations already sorted by `sort` ascending (lowest = highest priority).
|
|
23
|
+
* First matching quotation whose schedule covers `datetime` and whose product_data contains
|
|
24
|
+
* the requested productId wins.
|
|
25
|
+
*/
|
|
26
|
+
getPriceForProduct(params: {
|
|
27
|
+
productId: number;
|
|
28
|
+
variantId?: number;
|
|
29
|
+
datetime: string;
|
|
30
|
+
}): string | null;
|
|
31
|
+
getQuotationShelfId(params: {
|
|
32
|
+
productId: number;
|
|
33
|
+
variantId?: number;
|
|
34
|
+
datetime: string;
|
|
35
|
+
}): number;
|
|
36
|
+
/**
|
|
37
|
+
* Batch pre-compute quotation prices for a set of products across multiple time points.
|
|
38
|
+
* Key format: `${productId}:${timePoint}`
|
|
39
|
+
* This avoids repeated schedule matching when the same product appears in multiple resource rows.
|
|
40
|
+
*/
|
|
41
|
+
buildProductPriceMap(params: {
|
|
42
|
+
productIds: number[];
|
|
43
|
+
timePoints: string[];
|
|
44
|
+
}): Map<string, string | null>;
|
|
45
|
+
setScheduleResolver(resolver: (id: number) => ScheduleItem | undefined): void;
|
|
46
|
+
private isQuotationActiveAt;
|
|
47
|
+
private findProductData;
|
|
48
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/modules/Quotation/index.ts
|
|
20
|
+
var Quotation_exports = {};
|
|
21
|
+
__export(Quotation_exports, {
|
|
22
|
+
QuotationModule: () => QuotationModule
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(Quotation_exports);
|
|
25
|
+
var import_BaseModule = require("../BaseModule");
|
|
26
|
+
var import_getDateIsInSchedule = require("../Schedule/getDateIsInSchedule");
|
|
27
|
+
var QuotationModule = class extends import_BaseModule.BaseModule {
|
|
28
|
+
constructor(name, version) {
|
|
29
|
+
super(name, version);
|
|
30
|
+
this.defaultName = "quotation";
|
|
31
|
+
this.defaultVersion = "1.0.0";
|
|
32
|
+
this.store = { list: [] };
|
|
33
|
+
}
|
|
34
|
+
async initialize(core, options) {
|
|
35
|
+
this.core = core;
|
|
36
|
+
this.request = core.getPlugin("request");
|
|
37
|
+
if (!this.request)
|
|
38
|
+
throw new Error("QuotationModule 需要 request 插件支持");
|
|
39
|
+
this.store = { list: [] };
|
|
40
|
+
}
|
|
41
|
+
async loadQuotations(params) {
|
|
42
|
+
var _a;
|
|
43
|
+
const query = {};
|
|
44
|
+
if (params == null ? void 0 : params.channel)
|
|
45
|
+
query.channel = params.channel;
|
|
46
|
+
if ((params == null ? void 0 : params.channel) === "online_store") {
|
|
47
|
+
query.channel = "online-store";
|
|
48
|
+
}
|
|
49
|
+
const res = await this.request.get(
|
|
50
|
+
"/quotation/available",
|
|
51
|
+
query,
|
|
52
|
+
{ useCache: false }
|
|
53
|
+
);
|
|
54
|
+
const list = ((_a = res == null ? void 0 : res.data) == null ? void 0 : _a.list) || (res == null ? void 0 : res.list) || [];
|
|
55
|
+
list.sort((a, b) => a.sort - b.sort);
|
|
56
|
+
this.store.list = list;
|
|
57
|
+
}
|
|
58
|
+
getQuotationList() {
|
|
59
|
+
return this.store.list;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Look up the quotation price for a specific product (+ optional variant) at a given datetime.
|
|
63
|
+
* Returns the price as a string (e.g. "300.00"), or null if no quotation applies.
|
|
64
|
+
*
|
|
65
|
+
* Priority: iterates quotations already sorted by `sort` ascending (lowest = highest priority).
|
|
66
|
+
* First matching quotation whose schedule covers `datetime` and whose product_data contains
|
|
67
|
+
* the requested productId wins.
|
|
68
|
+
*/
|
|
69
|
+
getPriceForProduct(params) {
|
|
70
|
+
const { productId, variantId, datetime } = params;
|
|
71
|
+
for (const quotation of this.store.list) {
|
|
72
|
+
if (!this.isQuotationActiveAt(quotation, datetime))
|
|
73
|
+
continue;
|
|
74
|
+
const match = this.findProductData(quotation.product_data, productId, variantId);
|
|
75
|
+
if (!match)
|
|
76
|
+
continue;
|
|
77
|
+
if (match.value === 0)
|
|
78
|
+
continue;
|
|
79
|
+
return String(match.value);
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
getQuotationShelfId(params) {
|
|
84
|
+
const { productId, variantId, datetime } = params;
|
|
85
|
+
for (const quotation of this.store.list) {
|
|
86
|
+
if (!this.isQuotationActiveAt(quotation, datetime))
|
|
87
|
+
continue;
|
|
88
|
+
const match = this.findProductData(quotation.product_data, productId, variantId);
|
|
89
|
+
if (!match || match.value === 0)
|
|
90
|
+
continue;
|
|
91
|
+
return quotation.id;
|
|
92
|
+
}
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Batch pre-compute quotation prices for a set of products across multiple time points.
|
|
97
|
+
* Key format: `${productId}:${timePoint}`
|
|
98
|
+
* This avoids repeated schedule matching when the same product appears in multiple resource rows.
|
|
99
|
+
*/
|
|
100
|
+
buildProductPriceMap(params) {
|
|
101
|
+
const map = /* @__PURE__ */ new Map();
|
|
102
|
+
if (!this.store.list.length)
|
|
103
|
+
return map;
|
|
104
|
+
for (const productId of params.productIds) {
|
|
105
|
+
for (const timePoint of params.timePoints) {
|
|
106
|
+
const key = `${productId}:${timePoint}`;
|
|
107
|
+
const price = this.getPriceForProduct({ productId, datetime: timePoint });
|
|
108
|
+
if (price !== null) {
|
|
109
|
+
map.set(key, price);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return map;
|
|
114
|
+
}
|
|
115
|
+
setScheduleResolver(resolver) {
|
|
116
|
+
this.scheduleResolver = resolver;
|
|
117
|
+
}
|
|
118
|
+
isQuotationActiveAt(quotation, datetime) {
|
|
119
|
+
var _a;
|
|
120
|
+
if (!((_a = quotation.schedule) == null ? void 0 : _a.length))
|
|
121
|
+
return false;
|
|
122
|
+
const scheduleItems = quotation.schedule.map((s) => {
|
|
123
|
+
var _a2;
|
|
124
|
+
const full = (_a2 = this.scheduleResolver) == null ? void 0 : _a2.call(this, s.id);
|
|
125
|
+
if (full)
|
|
126
|
+
return full;
|
|
127
|
+
return {
|
|
128
|
+
...s,
|
|
129
|
+
repeat_type: s.repeat_type || "none",
|
|
130
|
+
repeat_rule: s.repeat_rule || null,
|
|
131
|
+
time_slot: s.time_slot || []
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
return (0, import_getDateIsInSchedule.getDateIsInSchedule)(datetime, scheduleItems);
|
|
135
|
+
}
|
|
136
|
+
findProductData(productData, productId, variantId) {
|
|
137
|
+
if (variantId && variantId !== 0) {
|
|
138
|
+
const variantMatch = productData.find(
|
|
139
|
+
(p) => p.product_id === productId && p.variant_id === variantId
|
|
140
|
+
);
|
|
141
|
+
if (variantMatch)
|
|
142
|
+
return variantMatch;
|
|
143
|
+
}
|
|
144
|
+
return productData.find(
|
|
145
|
+
(p) => p.product_id === productId && p.variant_id === 0
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
150
|
+
0 && (module.exports = {
|
|
151
|
+
QuotationModule
|
|
152
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface QuotationProductData {
|
|
2
|
+
id: number;
|
|
3
|
+
shelf_id: number;
|
|
4
|
+
product_id: number;
|
|
5
|
+
variant_id: number;
|
|
6
|
+
type: string;
|
|
7
|
+
value: number;
|
|
8
|
+
}
|
|
9
|
+
export interface QuotationSchedule {
|
|
10
|
+
id: number;
|
|
11
|
+
name: string;
|
|
12
|
+
type: 'standard' | 'time-slots' | 'designation';
|
|
13
|
+
start_time: string;
|
|
14
|
+
end_time: string;
|
|
15
|
+
is_all: number;
|
|
16
|
+
repeat_type?: string;
|
|
17
|
+
repeat_rule?: any;
|
|
18
|
+
designation?: any;
|
|
19
|
+
time_slot?: Array<{
|
|
20
|
+
start_time: string;
|
|
21
|
+
end_time: string;
|
|
22
|
+
}>;
|
|
23
|
+
pivot?: Record<string, any>;
|
|
24
|
+
}
|
|
25
|
+
export interface QuotationItem {
|
|
26
|
+
id: number;
|
|
27
|
+
shop_id: number;
|
|
28
|
+
shelf_type_id?: number;
|
|
29
|
+
name: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
status: string;
|
|
32
|
+
sort: number;
|
|
33
|
+
channel: string[];
|
|
34
|
+
created_at?: string;
|
|
35
|
+
updated_at?: string;
|
|
36
|
+
deleted_at?: string | null;
|
|
37
|
+
schedule: QuotationSchedule[];
|
|
38
|
+
product_data: QuotationProductData[];
|
|
39
|
+
}
|
|
40
|
+
export interface QuotationState {
|
|
41
|
+
list: QuotationItem[];
|
|
42
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __copyProps = (to, from, except, desc) => {
|
|
6
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
7
|
+
for (let key of __getOwnPropNames(from))
|
|
8
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
9
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
10
|
+
}
|
|
11
|
+
return to;
|
|
12
|
+
};
|
|
13
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
14
|
+
|
|
15
|
+
// src/modules/Quotation/types.ts
|
|
16
|
+
var types_exports = {};
|
|
17
|
+
module.exports = __toCommonJS(types_exports);
|
package/lib/server/index.d.ts
CHANGED
|
@@ -254,6 +254,17 @@ declare class Server {
|
|
|
254
254
|
* 转发到资源模块去
|
|
255
255
|
*/
|
|
256
256
|
private handleResourceList;
|
|
257
|
+
/**
|
|
258
|
+
* 代理刷新单个本地订单:
|
|
259
|
+
* - 入参:order_id(data 或 url query)
|
|
260
|
+
* - 流程:本地存在性校验 → 调后端 /order/sales/{id}?with[]=... → 覆盖本地 → 落 SQLite → emit onOrdersChanged
|
|
261
|
+
* - 监听级联:onOrdersChanged 触发订单/预约/bookingRemoteCache 三路订阅者推送
|
|
262
|
+
*/
|
|
263
|
+
private handleUpdateLocalOrder;
|
|
264
|
+
/**
|
|
265
|
+
* 从 url 中解析指定 query 参数,兼容相对路径与绝对路径
|
|
266
|
+
*/
|
|
267
|
+
private extractQueryParam;
|
|
257
268
|
/**
|
|
258
269
|
* 从 url 或路由 path 解析 pathname(不含 query,去掉末尾 /)
|
|
259
270
|
*/
|
package/lib/server/index.js
CHANGED
|
@@ -313,6 +313,86 @@ var Server = class {
|
|
|
313
313
|
status: true
|
|
314
314
|
};
|
|
315
315
|
};
|
|
316
|
+
/**
|
|
317
|
+
* 代理刷新单个本地订单:
|
|
318
|
+
* - 入参:order_id(data 或 url query)
|
|
319
|
+
* - 流程:本地存在性校验 → 调后端 /order/sales/{id}?with[]=... → 覆盖本地 → 落 SQLite → emit onOrdersChanged
|
|
320
|
+
* - 监听级联:onOrdersChanged 触发订单/预约/bookingRemoteCache 三路订阅者推送
|
|
321
|
+
*/
|
|
322
|
+
this.handleUpdateLocalOrder = async ({ url, data }) => {
|
|
323
|
+
var _a;
|
|
324
|
+
const rawOrderId = (data && typeof data === "object" ? data.order_id : void 0) ?? this.extractQueryParam(url, "order_id");
|
|
325
|
+
const orderId = rawOrderId === void 0 || rawOrderId === null || rawOrderId === "" ? null : String(rawOrderId).trim();
|
|
326
|
+
this.logInfo("handleUpdateLocalOrder: 开始处理", { url, orderId });
|
|
327
|
+
if (!orderId) {
|
|
328
|
+
this.logWarning("handleUpdateLocalOrder: order_id 缺失", { url, data });
|
|
329
|
+
return { code: 400, status: false, message: "order_id 缺失", data: null };
|
|
330
|
+
}
|
|
331
|
+
if (!this.order) {
|
|
332
|
+
this.logError("handleUpdateLocalOrder: Order 模块未注册");
|
|
333
|
+
return { code: 500, status: false, message: "Order 模块未注册", data: null };
|
|
334
|
+
}
|
|
335
|
+
if (!this.order.getOrderByOrderId(orderId)) {
|
|
336
|
+
this.logInfo("handleUpdateLocalOrder: 本地不存在该订单,忽略", { orderId });
|
|
337
|
+
return {
|
|
338
|
+
code: 200,
|
|
339
|
+
status: true,
|
|
340
|
+
message: "本地无此订单,已忽略",
|
|
341
|
+
data: { overwritten: false }
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
if (!((_a = this.app) == null ? void 0 : _a.request)) {
|
|
345
|
+
this.logError("handleUpdateLocalOrder: app.request 不可用");
|
|
346
|
+
return { code: 500, status: false, message: "app.request 不可用", data: null };
|
|
347
|
+
}
|
|
348
|
+
const backendPath = `/shop/order/sales/${encodeURIComponent(
|
|
349
|
+
orderId
|
|
350
|
+
)}?with%5B%5D=products&with%5B%5D=scheduleEvents&with%5B%5D=customer`;
|
|
351
|
+
try {
|
|
352
|
+
const response = await this.app.request.get(backendPath, void 0, {
|
|
353
|
+
isShopApi: true
|
|
354
|
+
});
|
|
355
|
+
const fresh = (response == null ? void 0 : response.data) ?? response;
|
|
356
|
+
if (!fresh || typeof fresh !== "object" || fresh.order_id == null) {
|
|
357
|
+
this.logError("handleUpdateLocalOrder: 后端返回订单为空", {
|
|
358
|
+
orderId,
|
|
359
|
+
backendPath
|
|
360
|
+
});
|
|
361
|
+
return {
|
|
362
|
+
code: 500,
|
|
363
|
+
status: false,
|
|
364
|
+
message: "后端返回订单为空",
|
|
365
|
+
data: null
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
const { overwritten } = await this.order.overwriteExistingOrder(
|
|
369
|
+
fresh
|
|
370
|
+
);
|
|
371
|
+
this.logInfo("handleUpdateLocalOrder: 覆盖完成", {
|
|
372
|
+
orderId,
|
|
373
|
+
overwritten
|
|
374
|
+
});
|
|
375
|
+
return {
|
|
376
|
+
code: 200,
|
|
377
|
+
status: true,
|
|
378
|
+
message: "",
|
|
379
|
+
data: { overwritten, order_id: orderId }
|
|
380
|
+
};
|
|
381
|
+
} catch (error) {
|
|
382
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
383
|
+
this.logError("handleUpdateLocalOrder: 请求失败", {
|
|
384
|
+
orderId,
|
|
385
|
+
backendPath,
|
|
386
|
+
error: errorMessage
|
|
387
|
+
});
|
|
388
|
+
return {
|
|
389
|
+
code: 500,
|
|
390
|
+
status: false,
|
|
391
|
+
message: errorMessage,
|
|
392
|
+
data: null
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
};
|
|
316
396
|
/**
|
|
317
397
|
* GET /shop/schedule/floor-plan* 前缀路由:读本地 store;支持 subscriberId + callback 订阅更新
|
|
318
398
|
*/
|
|
@@ -958,6 +1038,11 @@ var Server = class {
|
|
|
958
1038
|
method: "get",
|
|
959
1039
|
path: "/shop/form/resource/page",
|
|
960
1040
|
handler: this.handleResourceList.bind(this)
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
method: "get",
|
|
1044
|
+
path: "/update/localOrder",
|
|
1045
|
+
handler: this.handleUpdateLocalOrder.bind(this)
|
|
961
1046
|
}
|
|
962
1047
|
]);
|
|
963
1048
|
this.registerPrefixRoutes([
|
|
@@ -1420,6 +1505,37 @@ var Server = class {
|
|
|
1420
1505
|
};
|
|
1421
1506
|
}
|
|
1422
1507
|
}
|
|
1508
|
+
/**
|
|
1509
|
+
* 从 url 中解析指定 query 参数,兼容相对路径与绝对路径
|
|
1510
|
+
*/
|
|
1511
|
+
extractQueryParam(url, key) {
|
|
1512
|
+
if (!url)
|
|
1513
|
+
return null;
|
|
1514
|
+
try {
|
|
1515
|
+
const target = url.startsWith("http") ? new URL(url) : new URL(url, "http://placeholder.local");
|
|
1516
|
+
const value = target.searchParams.get(key);
|
|
1517
|
+
return value ?? null;
|
|
1518
|
+
} catch {
|
|
1519
|
+
const queryIndex = url.indexOf("?");
|
|
1520
|
+
if (queryIndex < 0)
|
|
1521
|
+
return null;
|
|
1522
|
+
const query = url.slice(queryIndex + 1);
|
|
1523
|
+
for (const pair of query.split("&")) {
|
|
1524
|
+
if (!pair)
|
|
1525
|
+
continue;
|
|
1526
|
+
const [rawKey, rawValue = ""] = pair.split("=");
|
|
1527
|
+
try {
|
|
1528
|
+
if (decodeURIComponent(rawKey) === key) {
|
|
1529
|
+
return decodeURIComponent(rawValue);
|
|
1530
|
+
}
|
|
1531
|
+
} catch {
|
|
1532
|
+
if (rawKey === key)
|
|
1533
|
+
return rawValue;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
return null;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1423
1539
|
/**
|
|
1424
1540
|
* 从 url 或路由 path 解析 pathname(不含 query,去掉末尾 /)
|
|
1425
1541
|
*/
|
|
@@ -40,6 +40,13 @@ export declare class OrderModule extends BaseModule implements Module {
|
|
|
40
40
|
getOrderByOrderId(orderId: OrderId): OrderData | undefined;
|
|
41
41
|
loadOrdersByServer(): Promise<OrderData[]>;
|
|
42
42
|
getOrdersByResourceId(resourceId: string | number): OrderData[];
|
|
43
|
+
/**
|
|
44
|
+
* 仅覆盖本地已存在的订单;不存在则直接跳过,不落库、不 emit。
|
|
45
|
+
* 适用于"按 order_id 主动刷新本地订单详情"的代理场景。
|
|
46
|
+
*/
|
|
47
|
+
overwriteExistingOrder(fresh: OrderData): Promise<{
|
|
48
|
+
overwritten: boolean;
|
|
49
|
+
}>;
|
|
43
50
|
/**
|
|
44
51
|
* 通过 SSE 按自定义 query 拉取订单(支持 select/with 精简字段)
|
|
45
52
|
*/
|