Forráskód Böngészése

fix: 云端拉取反推连锁/门店结构回填主体注册表(修复只读机「0 家门店」与门店筛选不可用)

KenZ1117 2 hete
szülő
commit
7fccac1ba5
2 módosított fájl, 45 hozzáadás és 2 törlés
  1. 12 0
      scripts/smoke.js
  2. 33 2
      server/services/aitable.js

+ 12 - 0
scripts/smoke.js

@@ -359,6 +359,18 @@ async function main() {
       // 无门店行 → 放行
       expect(aitableLib.detectLayerConflict([mk('chain', { validOrderCnt: 5 })]) === null, '无门店行被误拦');
     });
+    await check('treeFromRows → 从拉回的行反推连锁/门店(跳过 company 的 -1 与无 ID 店)', async () => {
+      const t = aitableLib.treeFromRows([
+        { scope: 'shop', shopId: 's1', shopName: '面馆', chainId: 'c1', chainName: '甲连锁' },
+        { scope: 'shop', shopId: 's2', shopName: '粉店', chainId: 'c2', chainName: '乙连锁' },
+        { scope: 'chain', shopId: '', chainId: 'c1', chainName: '甲连锁' },
+        { scope: 'company', shopId: '', chainId: '-1', chainName: '' },
+        { scope: 'shop', shopId: '', shopName: '无ID店', chainId: '', chainName: '' },
+      ]);
+      expect(t.chains.length === 2 && t.shops.length === 2, `chains=${t.chains.length} shops=${t.shops.length}`);
+      expect(t.chains.every((c) => c.chainId !== '-1'), 'company 的 -1 不应成为连锁');
+      expect(t.shops.every((s) => s.shopName && s.chainId), '门店条目缺名称/连锁');
+    });
 
     // ---- 问数两段式:结果摘要与兜底话术(纯函数,不调对话模型) ----
     const nlQueryLib = require('../nl-query.js');

+ 33 - 2
server/services/aitable.js

@@ -88,6 +88,24 @@ function detectLayerConflict(rows) {
   return { shopRows: shopRows.length };
 }
 
+/** 从拉回的行反推连锁/门店结构(纯函数):只读机没有商家客户端、拿不到 shopTree,
+ *  云端拉取是它唯一的结构来源;不回填的话主体显示「0 家门店」且无法按门店筛选。 */
+function treeFromRows(rows) {
+  const chains = new Map();
+  const shops = new Map();
+  for (const r of rows) {
+    const chainId = String(r.chainId || '');
+    const chainName = String(r.chainName || '');
+    if ((r.scope === 'chain' || r.scope === 'shop') && chainId && chainId !== '-1' && !chains.has(chainId)) {
+      chains.set(chainId, { chainId, chainName });
+    }
+    if (r.scope === 'shop' && r.shopId && !shops.has(String(r.shopId))) {
+      shops.set(String(r.shopId), { shopId: String(r.shopId), shopName: String(r.shopName || ''), chainId, chainName });
+    }
+  }
+  return { chains: [...chains.values()], shops: [...shops.values()] };
+}
+
 function rowToRecord(row, accountName) {
   const fields = {
     [FIELD_COLUMNS.date]: row.date || '',
@@ -428,12 +446,25 @@ function createAitableService(ctx) {
     const pulledKeys = [];
     for (const [acc, rows] of groups) {
       let key = naposService.store.sanitizeKey(acc);
-      const meta = naposService.store.getEntity(key);
+      let meta = naposService.store.getEntity(key);
       if (!meta || !meta.key) {
         const created = naposService.store.upsertEntity({ merchantId: acc, merchantName: acc, chains: [], shops: [] });
         key = created.entity.key;
+        meta = created.entity;
       }
       const res = naposService.store.upsertRows(key, rows);
+      // 反推连锁/门店结构回填注册表(与已有树取并集,已有条目优先、保留 busyLevel 等字段)
+      const derived = treeFromRows(rows);
+      const chains = new Map((meta.chains || []).map((c) => [String(c.chainId), c]));
+      for (const c of derived.chains) if (!chains.has(c.chainId)) chains.set(c.chainId, c);
+      const shops = new Map((meta.shops || []).map((s) => [String(s.shopId), s]));
+      for (const s of derived.shops) if (!shops.has(s.shopId)) shops.set(s.shopId, s);
+      naposService.store.upsertEntity({
+        merchantId: meta.merchantId || acc,
+        merchantName: meta.name || acc,
+        chains: [...chains.values()],
+        shops: [...shops.values()],
+      });
       pulledKeys.push(key);
       result.pulled += rows.length;
       result.accounts.push({ account: acc, rows: rows.length, added: res.added, updated: res.updated });
@@ -508,5 +539,5 @@ function createAitableService(ctx) {
 module.exports = {
   createAitableService, FIELD_COLUMNS, DIMENSION_LABEL, SHEET_DEFAULTS,
   rowToRecord, recordKey, recordToRow, splitRowsBySheet, resolveSheets, pushSummary,
-  detectLayerConflict,
+  detectLayerConflict, treeFromRows,
 };