UNPKG

@pisell/pisellos

Version:

一个可扩展的前端模块化SDK框架,支持插件系统

544 lines (542 loc) 19 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/server/modules/order/index.ts var order_exports = {}; __export(order_exports, { OrderModule: () => OrderModule }); module.exports = __toCommonJS(order_exports); var import_lodash_es = require("lodash-es"); var import_dayjs = __toESM(require("dayjs")); var import_BaseModule = require("../../../modules/BaseModule"); var import_types = require("./types"); var INDEXDB_STORE_NAME = "orders"; var ORDER_LAST_FULL_FETCH_AT_STORAGE_KEY = "server_order_last_full_fetch_at"; var OrderModule = class extends import_BaseModule.BaseModule { constructor(name, version) { super(name, version); this.defaultName = "order"; this.defaultVersion = "1.0.0"; this.pendingSyncMessages = []; this.ORDER_SYNC_DEBOUNCE_MS = 5e3; // Map<resource_id, OrderId[]> 资源到订单的倒排索引 this.resourceIdIndex = /* @__PURE__ */ new Map(); } async initialize(core, options) { var _a, _b; this.core = core; this.store = options == null ? void 0 : options.store; if (!this.store.map) this.store.map = /* @__PURE__ */ new Map(); const appPlugin = core.getPlugin("app"); const app = appPlugin == null ? void 0 : appPlugin.getApp(); if (app) { this.dbManager = app.sqlite; this.logger = app.logger; this.storage = app.storage; const coreData = app.data.store.getStore().getDataByModel("core"); if (!((_a = this.store.createdAtQuery) == null ? void 0 : _a.sales_time_between) && (coreData == null ? void 0 : coreData.core)) { const today = (0, import_dayjs.default)(); if (coreData == null ? void 0 : coreData.core.operating_day_boundary) { const operatingDayBoundary = coreData.core.operating_day_boundary; if (operatingDayBoundary.type === "start_time") { const boundaryTime = String(operatingDayBoundary.time || "").trim(); const boundaryStart = (0, import_dayjs.default)(`${today.format("YYYY-MM-DD")} ${boundaryTime}`); const boundaryEnd = boundaryStart.add(1, "day"); if (boundaryStart.isValid()) { this.store.createdAtQuery = { sales_time_between: [ boundaryStart.format("YYYY-MM-DD HH:mm:ss"), boundaryEnd.format("YYYY-MM-DD HH:mm:ss") ] }; } else { this.store.createdAtQuery = { sales_time_between: [ today.startOf("day").format("YYYY-MM-DD HH:mm:ss"), today.endOf("day").format("YYYY-MM-DD HH:mm:ss") ] }; } } else { this.store.createdAtQuery = { sales_time_between: [ today.startOf("day").format("YYYY-MM-DD HH:mm:ss"), today.endOf("day").format("YYYY-MM-DD HH:mm:ss") ] }; } } else { this.store.createdAtQuery = { sales_time_between: [today.startOf("day").format("YYYY-MM-DD HH:mm:ss"), today.endOf("day").format("YYYY-MM-DD HH:mm:ss")] }; } } } if (Array.isArray((_b = options == null ? void 0 : options.initialState) == null ? void 0 : _b.list)) { this.store.list = options.initialState.list; this.syncOrdersMap(); this.core.effects.emit(import_types.OrderHooks.onOrdersChanged, this.store.list); } else { this.store.list = []; this.store.map = /* @__PURE__ */ new Map(); } this.initOrderDataSource(); this.setupOrderSync(); this.logInfo("OrderServer模块初始化完成", { hasDbManager: !!this.dbManager, hasLogger: !!this.logger, initialOrderCount: this.store.list.length }); } /** * 记录信息日志 * @param title 日志标题 * @param metadata 日志元数据 */ logInfo(title, metadata) { try { if (this.logger) { this.logger.addLog({ type: "info", title: `[OrderServerModule] ${title}`, metadata: metadata || {} }); } } catch { } } /** * 记录错误日志 * @param title 日志标题 * @param metadata 日志元数据 */ logError(title, metadata) { try { if (this.logger) { this.logger.addLog({ type: "error", title: `[OrderServerModule] ${title}`, metadata: metadata || {} }); } } catch { } } async preload() { const orders = await this.loadOrdersByServer(); if (orders.length === 0) return; this.store.list = (0, import_lodash_es.cloneDeep)(orders); this.syncOrdersMap(); this.core.effects.emit(import_types.OrderHooks.onOrdersChanged, this.store.list); } getOrders() { return this.store.list; } getOrderById(id) { return this.store.map.get(this.getIdKey(id)); } /** 按 order_id 查询(推荐) */ getOrderByOrderId(orderId) { return this.store.map.get(this.getIdKey(orderId)); } async loadOrdersByServer() { let orderList = []; if (this.orderDataSource) { try { const data = await this.orderDataSource.run({ http: { query: this.store.createdAtQuery } }); orderList = data || []; this.markOrderPulledAtNow(); } catch { orderList = []; } } await this.saveOrdersToSQLite(orderList); await this.core.effects.emit(import_types.OrderHooks.onOrdersLoaded, orderList); return orderList; } getOrdersByResourceId(resourceId) { const ids = this.resourceIdIndex.get(String(resourceId)) || []; return ids.map((id) => this.store.map.get(id)).filter((order) => !!order); } getRoutes() { return []; } destroy() { var _a; if (this.syncTimer) { clearTimeout(this.syncTimer); this.syncTimer = void 0; } this.pendingSyncMessages = []; if ((_a = this.orderDataSource) == null ? void 0 : _a.destroy) this.orderDataSource.destroy(); super.destroy(); } syncOrdersMap() { this.store.map.clear(); this.resourceIdIndex.clear(); for (const order of this.store.list) { const id = order.order_id; if (id === void 0 || id === null) continue; this.store.map.set(this.getIdKey(id), order); const resourceIds = this.extractResourceIds(order); for (const resourceId of resourceIds) { const key = String(resourceId); const existing = this.resourceIdIndex.get(key) || []; if (existing.some((eid) => this.getIdKey(eid) === this.getIdKey(id))) continue; existing.push(id); this.resourceIdIndex.set(key, existing); } } } extractResourceIds(order) { const ids = /* @__PURE__ */ new Set(); const visitResources = (resources) => { if (!Array.isArray(resources)) return; resources.forEach((resource) => { const rid = resource == null ? void 0 : resource.relation_id; if (rid !== void 0 && rid !== null && rid !== "") ids.add(String(rid)); if (Array.isArray(resource == null ? void 0 : resource.children)) visitResources(resource.children); }); }; if (Array.isArray(order == null ? void 0 : order.bookings)) { order.bookings.forEach((booking) => visitResources((booking == null ? void 0 : booking.resources) || [])); } return [...ids]; } getOrderDateKey(order) { var _a, _b, _c, _d; const candidates = [ order == null ? void 0 : order.schedule_date, order == null ? void 0 : order.create_date, order == null ? void 0 : order.created_at, (_b = (_a = order == null ? void 0 : order.bookings) == null ? void 0 : _a[0]) == null ? void 0 : _b.start_date, (_d = (_c = order == null ? void 0 : order.bookings) == null ? void 0 : _c[0]) == null ? void 0 : _d.select_date ]; for (const v of candidates) { if (!v) continue; const text = String(v); if (!text) continue; return text.split("T")[0].split(" ")[0]; } return void 0; } /** * 初始化 OrderDataSource 实例 */ initOrderDataSource() { var _a, _b; const OrderDataSourceClass = (_b = (_a = this.core.serverOptions) == null ? void 0 : _a.All_DATA_SOURCES) == null ? void 0 : _b.OrderDataSource; if (!OrderDataSourceClass) return; this.orderDataSource = new OrderDataSourceClass(); } /** * 初始化 pubsub 订阅,监听订单变更 */ setupOrderSync() { if (!this.orderDataSource) return; const shouldSkipHttpPull = this.hasPulledOrdersToday(); const syncPayload = { pubsub: { callback: (res) => { const message = this.normalizeOrderSyncMessage(res); if (!message) return; this.pendingSyncMessages.push(message); if (this.syncTimer) clearTimeout(this.syncTimer); this.syncTimer = setTimeout(() => { this.processOrderSyncMessages(); }, this.ORDER_SYNC_DEBOUNCE_MS); } } }; syncPayload.http = { query: this.store.createdAtQuery, skipHttp: shouldSkipHttpPull }; this.orderDataSource.run(syncPayload).then(() => { if (!shouldSkipHttpPull) this.markOrderPulledAtNow(); }).catch(() => { }); } /** * 处理防抖后的同步消息批次 * * 后端统一发送 change 消息,不再区分新增/编辑/删除。 * - 单条(id/order_id):若携带 body/data 则直接 upsert 到本地 * - 批量(ids):通过 HTTP 按 ids 增量拉取,再 merge 到本地 */ async processOrderSyncMessages() { var _a; const messages = [...this.pendingSyncMessages]; this.pendingSyncMessages = []; if (messages.length === 0) return; const upsertOrders = /* @__PURE__ */ new Map(); const refreshIds = []; for (const msg of messages) { const hasBatchIds = Array.isArray(msg.ids) && msg.ids.length > 0; const singleId = msg.id ?? msg.order_id; const hasSingleId = !hasBatchIds && singleId !== void 0 && singleId !== null; const rawBody = msg.body || msg.data; const normalizedBody = this.normalizeOrderSyncPayload(rawBody); if (hasSingleId && normalizedBody) { upsertOrders.set(this.getIdKey(normalizedBody.order_id), normalizedBody); continue; } if (hasBatchIds) refreshIds.push(...msg.ids); else if (hasSingleId) refreshIds.push(singleId); else if ((_a = msg.relation_order_ids) == null ? void 0 : _a.length) refreshIds.push(...msg.relation_order_ids); } const uniqueRefreshIds = this.uniqueOrderIds(refreshIds); const upsertList = [...upsertOrders.values()]; if (upsertList.length > 0) await this.mergeOrdersToStore(upsertList); if (uniqueRefreshIds.length > 0) { const freshOrders = await this.fetchOrdersByHttp(uniqueRefreshIds); if (freshOrders.length > 0) await this.mergeOrdersToStore(freshOrders); } if (upsertList.length === 0 && uniqueRefreshIds.length === 0) return; await this.core.effects.emit(import_types.OrderHooks.onOrdersSyncCompleted, null); } /** * 通过 HTTP 按 ids 增量拉取订单 */ async fetchOrdersByHttp(ids) { if (!this.orderDataSource || ids.length === 0) return []; try { if (typeof this.orderDataSource.fetchOrdersByIds === "function") { const orderList2 = await this.orderDataSource.fetchOrdersByIds(ids); return orderList2 || []; } const orderList = await this.orderDataSource.run({ http: { query: { ids } } }); return orderList || []; } catch { return []; } } /** * 将增量订单合并到 store */ async mergeOrdersToStore(freshOrders) { const freshMap = /* @__PURE__ */ new Map(); for (const order of freshOrders) { const id = order == null ? void 0 : order.order_id; if (id === void 0 || id === null) continue; freshMap.set(this.getIdKey(id), order); } const updatedList = this.store.list.map((order) => { const id = order.order_id; if (id === void 0 || id === null) return order; const key = this.getIdKey(id); if (!freshMap.has(key)) return order; const fresh = freshMap.get(key); freshMap.delete(key); return fresh; }); for (const order of freshMap.values()) { updatedList.push(order); } this.store.list = updatedList; this.syncOrdersMap(); await this.saveOrdersToSQLite(this.store.list); this.core.effects.emit(import_types.OrderHooks.onOrdersChanged, this.store.list); } normalizeOrderSyncMessage(res) { const data = (res == null ? void 0 : res.data) || res; if (!data) return null; const payload = (data == null ? void 0 : data.data) && typeof data.data === "object" ? data.data : void 0; const normalized = { ...data, _channelKey: data.module || "sales_sync" }; if (!normalized.type && typeof data.event === "string") normalized.type = data.event; if (!normalized.id && data.id !== void 0) normalized.id = data.id; if (!normalized.id && data.order_id !== void 0) normalized.id = data.order_id; if (!normalized.order_id && data.order_id !== void 0) normalized.order_id = data.order_id; if (!normalized.order_id && (payload == null ? void 0 : payload.order_id) !== void 0) normalized.order_id = payload.order_id; if (!normalized.body && (payload == null ? void 0 : payload.order_id) !== void 0) normalized.body = payload; if (!normalized.data && (payload == null ? void 0 : payload.order_id) !== void 0) normalized.data = payload; return normalized; } normalizeOrderSyncPayload(payload) { if (!payload) return null; const orderId = payload.order_id; if (orderId === void 0 || orderId === null) return null; return { ...payload, order_id: orderId }; } uniqueOrderIds(ids) { const idMap = /* @__PURE__ */ new Map(); for (const id of ids) { idMap.set(this.getIdKey(id), id); } return [...idMap.values()]; } getIdKey(id) { return String(id); } /** * 当前订单拉取窗口: * - 优先使用 createdAtQuery.sales_time_between(已按营业日边界初始化) * - 无法解析时退化为自然日窗口 */ getCurrentPullWindow() { var _a, _b; const now = (0, import_dayjs.default)(); const range = (_b = (_a = this.store) == null ? void 0 : _a.createdAtQuery) == null ? void 0 : _b.sales_time_between; if (Array.isArray(range) && range.length >= 2) { const startAt2 = (0, import_dayjs.default)(range[0]); const endExclusiveAt = (0, import_dayjs.default)(range[1]); if (startAt2.isValid() && endExclusiveAt.isValid() && endExclusiveAt.isAfter(startAt2)) { return { startAt: startAt2, endExclusiveAt }; } } const startAt = now.startOf("day"); return { startAt, endExclusiveAt: startAt.add(1, "day") }; } hasPulledOrdersToday() { const lastFullFetchAt = this.getStorageItem(ORDER_LAST_FULL_FETCH_AT_STORAGE_KEY); if (!lastFullFetchAt) return false; const parsed = (0, import_dayjs.default)(lastFullFetchAt); if (!parsed.isValid()) return false; const { startAt, endExclusiveAt } = this.getCurrentPullWindow(); return (parsed.isAfter(startAt) || parsed.isSame(startAt)) && parsed.isBefore(endExclusiveAt); } markOrderPulledAtNow() { this.setStorageItem( ORDER_LAST_FULL_FETCH_AT_STORAGE_KEY, (0, import_dayjs.default)().format("YYYY-MM-DD HH:mm:ss") ); } getStorageItem(key) { var _a; try { if ((_a = this.storage) == null ? void 0 : _a.getStorage) return this.storage.getStorage(key); if (typeof globalThis !== "undefined" && (globalThis == null ? void 0 : globalThis.localStorage)) { return globalThis.localStorage.getItem(key); } } catch { } return null; } setStorageItem(key, value) { var _a; try { if ((_a = this.storage) == null ? void 0 : _a.setStorage) { this.storage.setStorage(key, value); return; } if (typeof globalThis !== "undefined" && (globalThis == null ? void 0 : globalThis.localStorage)) { globalThis.localStorage.setItem(key, value); } } catch { } } async loadOrdersFromSQLite() { if (!this.dbManager) return []; try { const orders = await this.dbManager.getAll(INDEXDB_STORE_NAME); return orders || []; } catch { return []; } } async saveOrdersToSQLite(orderList) { if (!this.dbManager) return; try { await this.dbManager.clear(INDEXDB_STORE_NAME); if (orderList.length === 0) return; await this.dbManager.bulkAdd(INDEXDB_STORE_NAME, orderList); } catch (error) { this.logError("保存订单到 SQLite 失败", { error: error instanceof Error ? error.message : String(error), orderList: orderList.length }); } } async clear() { this.store.list = []; this.store.map = /* @__PURE__ */ new Map(); this.resourceIdIndex.clear(); if (this.dbManager) { await this.dbManager.clear(INDEXDB_STORE_NAME); } this.setStorageItem(ORDER_LAST_FULL_FETCH_AT_STORAGE_KEY, ""); this.core.effects.emit(import_types.OrderHooks.onOrdersChanged, this.store.list); } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { OrderModule });