@pisell/pisellos 2.2.123 → 2.2.125
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/model/strategy/adapter/promotion/index.js +9 -0
- package/dist/modules/Product/index.d.ts +1 -1
- 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 +8 -1
- package/dist/server/index.js +25 -6
- package/dist/solution/BookingTicket/index.d.ts +1 -1
- package/dist/solution/Sales/index.d.ts +33 -4
- package/dist/solution/Sales/index.js +94 -38
- package/lib/modules/Product/index.d.ts +1 -1
- 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 +8 -1
- package/lib/server/index.js +28 -8
- package/lib/solution/BookingTicket/index.d.ts +1 -1
- package/lib/solution/Sales/index.d.ts +33 -4
- package/lib/solution/Sales/index.js +98 -48
- package/package.json +1 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// 导出评估器
|
|
2
|
+
export { PromotionEvaluator } from "./evaluator";
|
|
3
|
+
|
|
4
|
+
// 导出适配器
|
|
5
|
+
export { PromotionAdapter } from "./adapter";
|
|
6
|
+
export { default } from "./adapter";
|
|
7
|
+
|
|
8
|
+
// 导出策略配置示例常量
|
|
9
|
+
export { X_ITEMS_FOR_Y_PRICE_STRATEGY, BUY_X_GET_Y_FREE_STRATEGY } from "./examples";
|
|
@@ -49,5 +49,5 @@ export declare class Product extends BaseModule implements Module {
|
|
|
49
49
|
getCategories(): ProductCategory[];
|
|
50
50
|
setOtherParams(key: string, value: any): void;
|
|
51
51
|
getOtherParams(): any;
|
|
52
|
-
getProductType(): "
|
|
52
|
+
getProductType(): "duration" | "session" | "normal";
|
|
53
53
|
}
|
|
@@ -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,248 @@
|
|
|
1
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
2
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
3
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
4
|
+
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
|
|
5
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
6
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
7
|
+
function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw new Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator.return && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw new Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, catch: function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw new Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
|
|
8
|
+
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
|
|
9
|
+
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
|
|
10
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
11
|
+
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
|
|
12
|
+
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
|
13
|
+
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
|
|
14
|
+
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
|
|
15
|
+
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
|
|
16
|
+
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); }
|
|
17
|
+
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
|
|
18
|
+
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
|
|
19
|
+
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
|
|
20
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
21
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
|
|
22
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
23
|
+
import { BaseModule } from "../BaseModule";
|
|
24
|
+
import { getDateIsInSchedule } from "../Schedule/getDateIsInSchedule";
|
|
25
|
+
export var QuotationModule = /*#__PURE__*/function (_BaseModule) {
|
|
26
|
+
_inherits(QuotationModule, _BaseModule);
|
|
27
|
+
var _super = _createSuper(QuotationModule);
|
|
28
|
+
function QuotationModule(name, version) {
|
|
29
|
+
var _this;
|
|
30
|
+
_classCallCheck(this, QuotationModule);
|
|
31
|
+
_this = _super.call(this, name, version);
|
|
32
|
+
_defineProperty(_assertThisInitialized(_this), "defaultName", 'quotation');
|
|
33
|
+
_defineProperty(_assertThisInitialized(_this), "defaultVersion", '1.0.0');
|
|
34
|
+
_defineProperty(_assertThisInitialized(_this), "request", void 0);
|
|
35
|
+
_defineProperty(_assertThisInitialized(_this), "store", {
|
|
36
|
+
list: []
|
|
37
|
+
});
|
|
38
|
+
_defineProperty(_assertThisInitialized(_this), "scheduleResolver", void 0);
|
|
39
|
+
return _this;
|
|
40
|
+
}
|
|
41
|
+
_createClass(QuotationModule, [{
|
|
42
|
+
key: "initialize",
|
|
43
|
+
value: function () {
|
|
44
|
+
var _initialize = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(core, options) {
|
|
45
|
+
return _regeneratorRuntime().wrap(function _callee$(_context) {
|
|
46
|
+
while (1) switch (_context.prev = _context.next) {
|
|
47
|
+
case 0:
|
|
48
|
+
this.core = core;
|
|
49
|
+
this.request = core.getPlugin('request');
|
|
50
|
+
if (this.request) {
|
|
51
|
+
_context.next = 4;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
throw new Error('QuotationModule 需要 request 插件支持');
|
|
55
|
+
case 4:
|
|
56
|
+
this.store = {
|
|
57
|
+
list: []
|
|
58
|
+
};
|
|
59
|
+
case 5:
|
|
60
|
+
case "end":
|
|
61
|
+
return _context.stop();
|
|
62
|
+
}
|
|
63
|
+
}, _callee, this);
|
|
64
|
+
}));
|
|
65
|
+
function initialize(_x, _x2) {
|
|
66
|
+
return _initialize.apply(this, arguments);
|
|
67
|
+
}
|
|
68
|
+
return initialize;
|
|
69
|
+
}()
|
|
70
|
+
}, {
|
|
71
|
+
key: "loadQuotations",
|
|
72
|
+
value: function () {
|
|
73
|
+
var _loadQuotations = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(params) {
|
|
74
|
+
var _data;
|
|
75
|
+
var query, res, list;
|
|
76
|
+
return _regeneratorRuntime().wrap(function _callee2$(_context2) {
|
|
77
|
+
while (1) switch (_context2.prev = _context2.next) {
|
|
78
|
+
case 0:
|
|
79
|
+
query = {};
|
|
80
|
+
if (params !== null && params !== void 0 && params.channel) query.channel = params.channel;
|
|
81
|
+
if ((params === null || params === void 0 ? void 0 : params.channel) === 'online_store') {
|
|
82
|
+
query.channel = 'online-store';
|
|
83
|
+
}
|
|
84
|
+
_context2.next = 5;
|
|
85
|
+
return this.request.get('/quotation/available', query, {
|
|
86
|
+
useCache: false
|
|
87
|
+
});
|
|
88
|
+
case 5:
|
|
89
|
+
res = _context2.sent;
|
|
90
|
+
list = (res === null || res === void 0 || (_data = res.data) === null || _data === void 0 ? void 0 : _data.list) || (res === null || res === void 0 ? void 0 : res.list) || [];
|
|
91
|
+
list.sort(function (a, b) {
|
|
92
|
+
return a.sort - b.sort;
|
|
93
|
+
});
|
|
94
|
+
this.store.list = list;
|
|
95
|
+
case 9:
|
|
96
|
+
case "end":
|
|
97
|
+
return _context2.stop();
|
|
98
|
+
}
|
|
99
|
+
}, _callee2, this);
|
|
100
|
+
}));
|
|
101
|
+
function loadQuotations(_x3) {
|
|
102
|
+
return _loadQuotations.apply(this, arguments);
|
|
103
|
+
}
|
|
104
|
+
return loadQuotations;
|
|
105
|
+
}()
|
|
106
|
+
}, {
|
|
107
|
+
key: "getQuotationList",
|
|
108
|
+
value: function getQuotationList() {
|
|
109
|
+
return this.store.list;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Look up the quotation price for a specific product (+ optional variant) at a given datetime.
|
|
114
|
+
* Returns the price as a string (e.g. "300.00"), or null if no quotation applies.
|
|
115
|
+
*
|
|
116
|
+
* Priority: iterates quotations already sorted by `sort` ascending (lowest = highest priority).
|
|
117
|
+
* First matching quotation whose schedule covers `datetime` and whose product_data contains
|
|
118
|
+
* the requested productId wins.
|
|
119
|
+
*/
|
|
120
|
+
}, {
|
|
121
|
+
key: "getPriceForProduct",
|
|
122
|
+
value: function getPriceForProduct(params) {
|
|
123
|
+
var productId = params.productId,
|
|
124
|
+
variantId = params.variantId,
|
|
125
|
+
datetime = params.datetime;
|
|
126
|
+
var _iterator = _createForOfIteratorHelper(this.store.list),
|
|
127
|
+
_step;
|
|
128
|
+
try {
|
|
129
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
130
|
+
var quotation = _step.value;
|
|
131
|
+
if (!this.isQuotationActiveAt(quotation, datetime)) continue;
|
|
132
|
+
var match = this.findProductData(quotation.product_data, productId, variantId);
|
|
133
|
+
if (!match) continue;
|
|
134
|
+
if (match.value === 0) continue;
|
|
135
|
+
return String(match.value);
|
|
136
|
+
}
|
|
137
|
+
} catch (err) {
|
|
138
|
+
_iterator.e(err);
|
|
139
|
+
} finally {
|
|
140
|
+
_iterator.f();
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}, {
|
|
145
|
+
key: "getQuotationShelfId",
|
|
146
|
+
value: function getQuotationShelfId(params) {
|
|
147
|
+
var productId = params.productId,
|
|
148
|
+
variantId = params.variantId,
|
|
149
|
+
datetime = params.datetime;
|
|
150
|
+
var _iterator2 = _createForOfIteratorHelper(this.store.list),
|
|
151
|
+
_step2;
|
|
152
|
+
try {
|
|
153
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
154
|
+
var quotation = _step2.value;
|
|
155
|
+
if (!this.isQuotationActiveAt(quotation, datetime)) continue;
|
|
156
|
+
var match = this.findProductData(quotation.product_data, productId, variantId);
|
|
157
|
+
if (!match || match.value === 0) continue;
|
|
158
|
+
return quotation.id;
|
|
159
|
+
}
|
|
160
|
+
} catch (err) {
|
|
161
|
+
_iterator2.e(err);
|
|
162
|
+
} finally {
|
|
163
|
+
_iterator2.f();
|
|
164
|
+
}
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Batch pre-compute quotation prices for a set of products across multiple time points.
|
|
170
|
+
* Key format: `${productId}:${timePoint}`
|
|
171
|
+
* This avoids repeated schedule matching when the same product appears in multiple resource rows.
|
|
172
|
+
*/
|
|
173
|
+
}, {
|
|
174
|
+
key: "buildProductPriceMap",
|
|
175
|
+
value: function buildProductPriceMap(params) {
|
|
176
|
+
var map = new Map();
|
|
177
|
+
if (!this.store.list.length) return map;
|
|
178
|
+
var _iterator3 = _createForOfIteratorHelper(params.productIds),
|
|
179
|
+
_step3;
|
|
180
|
+
try {
|
|
181
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
182
|
+
var productId = _step3.value;
|
|
183
|
+
var _iterator4 = _createForOfIteratorHelper(params.timePoints),
|
|
184
|
+
_step4;
|
|
185
|
+
try {
|
|
186
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
187
|
+
var timePoint = _step4.value;
|
|
188
|
+
var key = "".concat(productId, ":").concat(timePoint);
|
|
189
|
+
var price = this.getPriceForProduct({
|
|
190
|
+
productId: productId,
|
|
191
|
+
datetime: timePoint
|
|
192
|
+
});
|
|
193
|
+
if (price !== null) {
|
|
194
|
+
map.set(key, price);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} catch (err) {
|
|
198
|
+
_iterator4.e(err);
|
|
199
|
+
} finally {
|
|
200
|
+
_iterator4.f();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} catch (err) {
|
|
204
|
+
_iterator3.e(err);
|
|
205
|
+
} finally {
|
|
206
|
+
_iterator3.f();
|
|
207
|
+
}
|
|
208
|
+
return map;
|
|
209
|
+
}
|
|
210
|
+
}, {
|
|
211
|
+
key: "setScheduleResolver",
|
|
212
|
+
value: function setScheduleResolver(resolver) {
|
|
213
|
+
this.scheduleResolver = resolver;
|
|
214
|
+
}
|
|
215
|
+
}, {
|
|
216
|
+
key: "isQuotationActiveAt",
|
|
217
|
+
value: function isQuotationActiveAt(quotation, datetime) {
|
|
218
|
+
var _quotation$schedule,
|
|
219
|
+
_this2 = this;
|
|
220
|
+
if (!((_quotation$schedule = quotation.schedule) !== null && _quotation$schedule !== void 0 && _quotation$schedule.length)) return false;
|
|
221
|
+
var scheduleItems = quotation.schedule.map(function (s) {
|
|
222
|
+
var _this2$scheduleResolv;
|
|
223
|
+
var full = (_this2$scheduleResolv = _this2.scheduleResolver) === null || _this2$scheduleResolv === void 0 ? void 0 : _this2$scheduleResolv.call(_this2, s.id);
|
|
224
|
+
if (full) return full;
|
|
225
|
+
return _objectSpread(_objectSpread({}, s), {}, {
|
|
226
|
+
repeat_type: s.repeat_type || 'none',
|
|
227
|
+
repeat_rule: s.repeat_rule || null,
|
|
228
|
+
time_slot: s.time_slot || []
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
return getDateIsInSchedule(datetime, scheduleItems);
|
|
232
|
+
}
|
|
233
|
+
}, {
|
|
234
|
+
key: "findProductData",
|
|
235
|
+
value: function findProductData(productData, productId, variantId) {
|
|
236
|
+
if (variantId && variantId !== 0) {
|
|
237
|
+
var variantMatch = productData.find(function (p) {
|
|
238
|
+
return p.product_id === productId && p.variant_id === variantId;
|
|
239
|
+
});
|
|
240
|
+
if (variantMatch) return variantMatch;
|
|
241
|
+
}
|
|
242
|
+
return productData.find(function (p) {
|
|
243
|
+
return p.product_id === productId && p.variant_id === 0;
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}]);
|
|
247
|
+
return QuotationModule;
|
|
248
|
+
}(BaseModule);
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
package/dist/server/index.d.ts
CHANGED
|
@@ -206,7 +206,9 @@ declare class Server {
|
|
|
206
206
|
*/
|
|
207
207
|
private shouldUseLocalBookingQuery;
|
|
208
208
|
/**
|
|
209
|
-
* 日历 SSE 精简 with
|
|
209
|
+
* 日历 SSE 精简 with:由调用方显式声明 compact_fields 决定
|
|
210
|
+
* - compact_fields === true → 精简字段(周/月视图)
|
|
211
|
+
* - 其余(false / 未传)→ ['all'](日视图/表格/平面图合并)
|
|
210
212
|
*/
|
|
211
213
|
private resolveBookingSalesWith;
|
|
212
214
|
/**
|
|
@@ -214,6 +216,11 @@ declare class Server {
|
|
|
214
216
|
* business_code / payment_status 不直接用于渲染,但 matchOrder 过滤依赖
|
|
215
217
|
*/
|
|
216
218
|
private resolveBookingSalesSelect;
|
|
219
|
+
/**
|
|
220
|
+
* 日历(compact_fields)场景下要直接在 SSE 层排除的订单类型:
|
|
221
|
+
* 这些类型不会出现在日历上,拉取它们只会浪费带宽并增加前端过滤成本。
|
|
222
|
+
*/
|
|
223
|
+
private readonly BOOKING_SALES_COMPACT_EXCLUDE_TYPES;
|
|
217
224
|
private normalizeBookingRemoteQueryPayload;
|
|
218
225
|
/**
|
|
219
226
|
* 远端预约查询内存缓存开关:默认 false,仅显式 true 才启用。
|
package/dist/server/index.js
CHANGED
|
@@ -325,6 +325,11 @@ var Server = /*#__PURE__*/function () {
|
|
|
325
325
|
return _ref11.apply(this, arguments);
|
|
326
326
|
};
|
|
327
327
|
}());
|
|
328
|
+
/**
|
|
329
|
+
* 日历(compact_fields)场景下要直接在 SSE 层排除的订单类型:
|
|
330
|
+
* 这些类型不会出现在日历上,拉取它们只会浪费带宽并增加前端过滤成本。
|
|
331
|
+
*/
|
|
332
|
+
_defineProperty(this, "BOOKING_SALES_COMPACT_EXCLUDE_TYPES", ['virtual', 'shipping', 'delivery', 'pickup']);
|
|
328
333
|
/**
|
|
329
334
|
* 处理预约列表查询
|
|
330
335
|
* 今天:注册订阅者 + 本地数据筛选;非今天:清理订阅者 + 走真实 API
|
|
@@ -1647,12 +1652,19 @@ var Server = /*#__PURE__*/function () {
|
|
|
1647
1652
|
}
|
|
1648
1653
|
|
|
1649
1654
|
/**
|
|
1650
|
-
* 日历 SSE 精简 with
|
|
1655
|
+
* 日历 SSE 精简 with:由调用方显式声明 compact_fields 决定
|
|
1656
|
+
* - compact_fields === true → 精简字段(周/月视图)
|
|
1657
|
+
* - 其余(false / 未传)→ ['all'](日视图/表格/平面图合并)
|
|
1651
1658
|
*/
|
|
1652
1659
|
}, {
|
|
1653
1660
|
key: "resolveBookingSalesWith",
|
|
1654
|
-
value: function resolveBookingSalesWith(
|
|
1655
|
-
|
|
1661
|
+
value: function resolveBookingSalesWith(queryPayload) {
|
|
1662
|
+
if ((queryPayload === null || queryPayload === void 0 ? void 0 : queryPayload.compact_fields) === true) {
|
|
1663
|
+
return ['bookings:booking_id,start_date,start_time,end_date,end_time,holder,metadata,parent_id,item_type', 'customer:id,display_name,phone', 'products'
|
|
1664
|
+
// 'resources'
|
|
1665
|
+
];
|
|
1666
|
+
}
|
|
1667
|
+
return ['all'];
|
|
1656
1668
|
}
|
|
1657
1669
|
|
|
1658
1670
|
/**
|
|
@@ -1667,11 +1679,18 @@ var Server = /*#__PURE__*/function () {
|
|
|
1667
1679
|
}, {
|
|
1668
1680
|
key: "normalizeBookingRemoteQueryPayload",
|
|
1669
1681
|
value: function normalizeBookingRemoteQueryPayload(data, withFields) {
|
|
1670
|
-
|
|
1682
|
+
var isFullWith = withFields.length === 1 && withFields[0] === 'all';
|
|
1683
|
+
var isCompact = (data === null || data === void 0 ? void 0 : data.compact_fields) === true;
|
|
1684
|
+
return _objectSpread(_objectSpread(_objectSpread(_objectSpread({}, data), {}, {
|
|
1671
1685
|
form_record_ids: undefined,
|
|
1672
1686
|
enable_remote_memory_cache: undefined,
|
|
1673
|
-
|
|
1674
|
-
|
|
1687
|
+
compact_fields: undefined,
|
|
1688
|
+
with: withFields
|
|
1689
|
+
}, isFullWith ? {} : {
|
|
1690
|
+
select: this.resolveBookingSalesSelect()
|
|
1691
|
+
}), isCompact ? {
|
|
1692
|
+
exclude_types: this.BOOKING_SALES_COMPACT_EXCLUDE_TYPES
|
|
1693
|
+
} : {}), {}, {
|
|
1675
1694
|
chunk_size: 50
|
|
1676
1695
|
});
|
|
1677
1696
|
}
|
|
@@ -136,7 +136,7 @@ export declare class BookingTicketImpl extends BaseModule implements Module {
|
|
|
136
136
|
* 获取当前的客户搜索条件
|
|
137
137
|
* @returns 当前搜索条件
|
|
138
138
|
*/
|
|
139
|
-
getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "
|
|
139
|
+
getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "skip" | "num">;
|
|
140
140
|
/**
|
|
141
141
|
* 获取客户列表状态(包含滚动加载相关状态)
|
|
142
142
|
* @returns 客户状态
|
|
@@ -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 () {
|
|
@@ -49,5 +49,5 @@ export declare class Product extends BaseModule implements Module {
|
|
|
49
49
|
getCategories(): ProductCategory[];
|
|
50
50
|
setOtherParams(key: string, value: any): void;
|
|
51
51
|
getOtherParams(): any;
|
|
52
|
-
getProductType(): "
|
|
52
|
+
getProductType(): "duration" | "session" | "normal";
|
|
53
53
|
}
|
|
@@ -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
|
@@ -206,7 +206,9 @@ declare class Server {
|
|
|
206
206
|
*/
|
|
207
207
|
private shouldUseLocalBookingQuery;
|
|
208
208
|
/**
|
|
209
|
-
* 日历 SSE 精简 with
|
|
209
|
+
* 日历 SSE 精简 with:由调用方显式声明 compact_fields 决定
|
|
210
|
+
* - compact_fields === true → 精简字段(周/月视图)
|
|
211
|
+
* - 其余(false / 未传)→ ['all'](日视图/表格/平面图合并)
|
|
210
212
|
*/
|
|
211
213
|
private resolveBookingSalesWith;
|
|
212
214
|
/**
|
|
@@ -214,6 +216,11 @@ declare class Server {
|
|
|
214
216
|
* business_code / payment_status 不直接用于渲染,但 matchOrder 过滤依赖
|
|
215
217
|
*/
|
|
216
218
|
private resolveBookingSalesSelect;
|
|
219
|
+
/**
|
|
220
|
+
* 日历(compact_fields)场景下要直接在 SSE 层排除的订单类型:
|
|
221
|
+
* 这些类型不会出现在日历上,拉取它们只会浪费带宽并增加前端过滤成本。
|
|
222
|
+
*/
|
|
223
|
+
private readonly BOOKING_SALES_COMPACT_EXCLUDE_TYPES;
|
|
217
224
|
private normalizeBookingRemoteQueryPayload;
|
|
218
225
|
/**
|
|
219
226
|
* 远端预约查询内存缓存开关:默认 false,仅显式 true 才启用。
|
package/lib/server/index.js
CHANGED
|
@@ -225,6 +225,16 @@ var Server = class {
|
|
|
225
225
|
}
|
|
226
226
|
return { code: 200, message: "ok", status: true };
|
|
227
227
|
};
|
|
228
|
+
/**
|
|
229
|
+
* 日历(compact_fields)场景下要直接在 SSE 层排除的订单类型:
|
|
230
|
+
* 这些类型不会出现在日历上,拉取它们只会浪费带宽并增加前端过滤成本。
|
|
231
|
+
*/
|
|
232
|
+
this.BOOKING_SALES_COMPACT_EXCLUDE_TYPES = [
|
|
233
|
+
"virtual",
|
|
234
|
+
"shipping",
|
|
235
|
+
"delivery",
|
|
236
|
+
"pickup"
|
|
237
|
+
];
|
|
228
238
|
/**
|
|
229
239
|
* 处理预约列表查询
|
|
230
240
|
* 今天:注册订阅者 + 本地数据筛选;非今天:清理订阅者 + 走真实 API
|
|
@@ -1022,14 +1032,20 @@ var Server = class {
|
|
|
1022
1032
|
return !this.isRangeBothNotToday(range[0], range[1]);
|
|
1023
1033
|
}
|
|
1024
1034
|
/**
|
|
1025
|
-
* 日历 SSE 精简 with
|
|
1035
|
+
* 日历 SSE 精简 with:由调用方显式声明 compact_fields 决定
|
|
1036
|
+
* - compact_fields === true → 精简字段(周/月视图)
|
|
1037
|
+
* - 其余(false / 未传)→ ['all'](日视图/表格/平面图合并)
|
|
1026
1038
|
*/
|
|
1027
|
-
resolveBookingSalesWith(
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1039
|
+
resolveBookingSalesWith(queryPayload) {
|
|
1040
|
+
if ((queryPayload == null ? void 0 : queryPayload.compact_fields) === true) {
|
|
1041
|
+
return [
|
|
1042
|
+
"bookings:booking_id,start_date,start_time,end_date,end_time,holder,metadata,parent_id,item_type",
|
|
1043
|
+
"customer:id,display_name,phone",
|
|
1044
|
+
"products"
|
|
1045
|
+
// 'resources'
|
|
1046
|
+
];
|
|
1047
|
+
}
|
|
1048
|
+
return ["all"];
|
|
1033
1049
|
}
|
|
1034
1050
|
/**
|
|
1035
1051
|
* 日历 SSE 精简 select:渲染 + filterBookingsFromOrders 所需的主表字段
|
|
@@ -1039,12 +1055,16 @@ var Server = class {
|
|
|
1039
1055
|
return "status,payment_status,business_code,phone,customer_name,schedule_date,created_at,metadata";
|
|
1040
1056
|
}
|
|
1041
1057
|
normalizeBookingRemoteQueryPayload(data, withFields) {
|
|
1058
|
+
const isFullWith = withFields.length === 1 && withFields[0] === "all";
|
|
1059
|
+
const isCompact = (data == null ? void 0 : data.compact_fields) === true;
|
|
1042
1060
|
return {
|
|
1043
1061
|
...data,
|
|
1044
1062
|
form_record_ids: void 0,
|
|
1045
1063
|
enable_remote_memory_cache: void 0,
|
|
1064
|
+
compact_fields: void 0,
|
|
1046
1065
|
with: withFields,
|
|
1047
|
-
select: this.resolveBookingSalesSelect(),
|
|
1066
|
+
...isFullWith ? {} : { select: this.resolveBookingSalesSelect() },
|
|
1067
|
+
...isCompact ? { exclude_types: this.BOOKING_SALES_COMPACT_EXCLUDE_TYPES } : {},
|
|
1048
1068
|
chunk_size: 50
|
|
1049
1069
|
};
|
|
1050
1070
|
}
|
|
@@ -136,7 +136,7 @@ export declare class BookingTicketImpl extends BaseModule implements Module {
|
|
|
136
136
|
* 获取当前的客户搜索条件
|
|
137
137
|
* @returns 当前搜索条件
|
|
138
138
|
*/
|
|
139
|
-
getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "
|
|
139
|
+
getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "skip" | "num">;
|
|
140
140
|
/**
|
|
141
141
|
* 获取客户列表状态(包含滚动加载相关状态)
|
|
142
142
|
* @returns 客户状态
|
|
@@ -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 };
|
|
@@ -315,39 +315,54 @@ var SalesImpl = class extends import_BaseModule.BaseModule {
|
|
|
315
315
|
return fallback ? [fallback] : [];
|
|
316
316
|
}
|
|
317
317
|
/**
|
|
318
|
-
*
|
|
319
|
-
* -
|
|
320
|
-
* -
|
|
321
|
-
|
|
318
|
+
* 计算 booking 在 current 时刻的执行进度百分比(0-100)。
|
|
319
|
+
* - startAt/endAt 非法或 totalMinutes <= 0 返回 0
|
|
320
|
+
* - current 早于 startAt 返回 0,晚于 endAt 返回 100
|
|
321
|
+
*/
|
|
322
|
+
calcProgressPercent(current, startAt, endAt) {
|
|
323
|
+
if (!startAt.isValid() || !endAt.isValid())
|
|
324
|
+
return 0;
|
|
325
|
+
const totalMinutes = endAt.diff(startAt, "minute");
|
|
326
|
+
if (totalMinutes <= 0)
|
|
327
|
+
return 0;
|
|
328
|
+
const elapsedMinutes = current.diff(startAt, "minute");
|
|
329
|
+
if (elapsedMinutes <= 0)
|
|
330
|
+
return 0;
|
|
331
|
+
if (elapsedMinutes >= totalMinutes)
|
|
332
|
+
return 100;
|
|
333
|
+
return Math.floor(elapsedMinutes / totalMinutes * 100);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* 标准化单条 booking 的分发入口:
|
|
337
|
+
* - 统一过滤 rejected / cancelled(视为没来过)
|
|
338
|
+
* - current === deviceCurrent 走实时分支(信任 appointment_status)
|
|
339
|
+
* - 否则走快照分支(纯时间窗判定,忽略 appointment_status 的 arrived/started/completed 差异)
|
|
322
340
|
*/
|
|
323
341
|
normalizeMatchedBooking(current, deviceCurrent, booking) {
|
|
324
342
|
const appointmentStatus = String(booking.appointment_status ?? booking.status ?? "");
|
|
325
|
-
if (appointmentStatus === "
|
|
343
|
+
if (appointmentStatus === "rejected" || appointmentStatus === "cancelled")
|
|
326
344
|
return null;
|
|
327
|
-
|
|
345
|
+
const startAt = this.toBookingDateTime(booking.start_date, booking.start_time);
|
|
328
346
|
const endAt = this.toBookingDateTime(booking.end_date, booking.end_time);
|
|
329
|
-
|
|
330
|
-
|
|
347
|
+
if (current.isSame(deviceCurrent)) {
|
|
348
|
+
return this.buildRealtimeBooking(current, booking, appointmentStatus, startAt, endAt);
|
|
349
|
+
}
|
|
350
|
+
return this.buildSnapshotBooking(current, booking, appointmentStatus, startAt, endAt);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* 实时模式(current === deviceCurrent):信任 appointment_status
|
|
354
|
+
* - 过滤 new / completed(实时视角下视为非活跃数据)
|
|
355
|
+
* - status 由 getBookingStatus 映射
|
|
356
|
+
* - occupied 且 current > endAt 标记 isTimeout + timeoutTime
|
|
357
|
+
* - reserved 且 current < startAt 标记 not_arrived,否则标记 late
|
|
358
|
+
*/
|
|
359
|
+
buildRealtimeBooking(current, booking, appointmentStatus, startAt, endAt) {
|
|
360
|
+
if (appointmentStatus === "new" || appointmentStatus === "completed")
|
|
331
361
|
return null;
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
let progressPercent = (() => {
|
|
337
|
-
if (bookingStatus !== "occupied")
|
|
338
|
-
return 0;
|
|
339
|
-
if (!startAt.isValid() || !endAt.isValid())
|
|
340
|
-
return 0;
|
|
341
|
-
const totalMinutes = endAt.diff(startAt, "minute");
|
|
342
|
-
if (totalMinutes <= 0)
|
|
343
|
-
return 0;
|
|
344
|
-
const elapsedMinutes = current.diff(startAt, "minute");
|
|
345
|
-
if (elapsedMinutes <= 0)
|
|
346
|
-
return 0;
|
|
347
|
-
if (elapsedMinutes >= totalMinutes)
|
|
348
|
-
return 100;
|
|
349
|
-
return Math.floor(elapsedMinutes / totalMinutes * 100);
|
|
350
|
-
})();
|
|
362
|
+
const bookingStatus = this.getBookingStatus(appointmentStatus);
|
|
363
|
+
const isTimeout = bookingStatus === "occupied" && endAt.isValid() && current.isAfter(endAt);
|
|
364
|
+
const timeoutTime = isTimeout ? current.diff(endAt, "minute") : void 0;
|
|
365
|
+
const progressPercent = bookingStatus === "occupied" ? this.calcProgressPercent(current, startAt, endAt) : 0;
|
|
351
366
|
let reservedStatus;
|
|
352
367
|
let lateTime;
|
|
353
368
|
let remainingReserveTime;
|
|
@@ -360,27 +375,6 @@ var SalesImpl = class extends import_BaseModule.BaseModule {
|
|
|
360
375
|
lateTime = Math.max(current.diff(startAt, "minute"), 0);
|
|
361
376
|
}
|
|
362
377
|
}
|
|
363
|
-
if (current.isAfter(deviceCurrent) && bookingStatus === "reserved" && reservedStatus === "late" && endAt.isValid() && endAt.isSameOrAfter(current)) {
|
|
364
|
-
bookingStatus = "occupied";
|
|
365
|
-
reservedStatus = void 0;
|
|
366
|
-
lateTime = void 0;
|
|
367
|
-
remainingReserveTime = void 0;
|
|
368
|
-
isTimeout = false;
|
|
369
|
-
timeoutTime = void 0;
|
|
370
|
-
progressPercent = (() => {
|
|
371
|
-
if (!startAt.isValid() || !endAt.isValid())
|
|
372
|
-
return 0;
|
|
373
|
-
const totalMinutes = endAt.diff(startAt, "minute");
|
|
374
|
-
if (totalMinutes <= 0)
|
|
375
|
-
return 0;
|
|
376
|
-
const elapsedMinutes = current.diff(startAt, "minute");
|
|
377
|
-
if (elapsedMinutes <= 0)
|
|
378
|
-
return 0;
|
|
379
|
-
if (elapsedMinutes >= totalMinutes)
|
|
380
|
-
return 100;
|
|
381
|
-
return Math.floor(elapsedMinutes / totalMinutes * 100);
|
|
382
|
-
})();
|
|
383
|
-
}
|
|
384
378
|
return {
|
|
385
379
|
...booking,
|
|
386
380
|
status: bookingStatus,
|
|
@@ -392,6 +386,62 @@ var SalesImpl = class extends import_BaseModule.BaseModule {
|
|
|
392
386
|
remainingReserveTime
|
|
393
387
|
};
|
|
394
388
|
}
|
|
389
|
+
/**
|
|
390
|
+
* 快照模式(current !== deviceCurrent,过去或未来):忽略 appointment_status
|
|
391
|
+
* 的实时态差异,仅按 booking 时间窗判定,为 current 提供时间点快照视图。
|
|
392
|
+
*
|
|
393
|
+
* - new:视作尚未成为真实预约,过滤
|
|
394
|
+
* - locked:保留 status=locked,不计算 timeout/late/progress
|
|
395
|
+
* - 其他非终态:
|
|
396
|
+
* - startAt/endAt 非法 → 过滤
|
|
397
|
+
* - current > endAt → 过滤(已结束,不入池)
|
|
398
|
+
* - current ∈ [startAt, endAt] → occupied,附带 progressPercent
|
|
399
|
+
* - current < startAt → reserved + not_arrived,附带 remainingReserveTime
|
|
400
|
+
*
|
|
401
|
+
* 快照模式下 isTimeout 恒为 false,lateTime / timeoutTime 始终置空。
|
|
402
|
+
*/
|
|
403
|
+
buildSnapshotBooking(current, booking, appointmentStatus, startAt, endAt) {
|
|
404
|
+
if (appointmentStatus === "new")
|
|
405
|
+
return null;
|
|
406
|
+
if (appointmentStatus === "locked") {
|
|
407
|
+
return {
|
|
408
|
+
...booking,
|
|
409
|
+
status: "locked",
|
|
410
|
+
isTimeout: false,
|
|
411
|
+
timeoutTime: void 0,
|
|
412
|
+
progressPercent: 0,
|
|
413
|
+
lateTime: void 0,
|
|
414
|
+
reserved_status: void 0,
|
|
415
|
+
remainingReserveTime: void 0
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
if (!startAt.isValid() || !endAt.isValid())
|
|
419
|
+
return null;
|
|
420
|
+
if (current.isAfter(endAt))
|
|
421
|
+
return null;
|
|
422
|
+
if (this.isSameOrAfter(current, startAt) && this.isSameOrBefore(current, endAt)) {
|
|
423
|
+
return {
|
|
424
|
+
...booking,
|
|
425
|
+
status: "occupied",
|
|
426
|
+
isTimeout: false,
|
|
427
|
+
timeoutTime: void 0,
|
|
428
|
+
progressPercent: this.calcProgressPercent(current, startAt, endAt),
|
|
429
|
+
lateTime: void 0,
|
|
430
|
+
reserved_status: void 0,
|
|
431
|
+
remainingReserveTime: void 0
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
...booking,
|
|
436
|
+
status: "reserved",
|
|
437
|
+
isTimeout: false,
|
|
438
|
+
timeoutTime: void 0,
|
|
439
|
+
progressPercent: 0,
|
|
440
|
+
lateTime: void 0,
|
|
441
|
+
reserved_status: "not_arrived",
|
|
442
|
+
remainingReserveTime: Math.max(startAt.diff(current, "minute"), 0)
|
|
443
|
+
};
|
|
444
|
+
}
|
|
395
445
|
async getResourceBookingList(currentTime, bookingList = [], deviceTime = currentTime) {
|
|
396
446
|
var _a;
|
|
397
447
|
const current = (0, import_dayjs.default)(currentTime);
|