|
@@ -88,6 +88,24 @@ function detectLayerConflict(rows) {
|
|
|
return { shopRows: shopRows.length };
|
|
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) {
|
|
function rowToRecord(row, accountName) {
|
|
|
const fields = {
|
|
const fields = {
|
|
|
[FIELD_COLUMNS.date]: row.date || '',
|
|
[FIELD_COLUMNS.date]: row.date || '',
|
|
@@ -428,12 +446,25 @@ function createAitableService(ctx) {
|
|
|
const pulledKeys = [];
|
|
const pulledKeys = [];
|
|
|
for (const [acc, rows] of groups) {
|
|
for (const [acc, rows] of groups) {
|
|
|
let key = naposService.store.sanitizeKey(acc);
|
|
let key = naposService.store.sanitizeKey(acc);
|
|
|
- const meta = naposService.store.getEntity(key);
|
|
|
|
|
|
|
+ let meta = naposService.store.getEntity(key);
|
|
|
if (!meta || !meta.key) {
|
|
if (!meta || !meta.key) {
|
|
|
const created = naposService.store.upsertEntity({ merchantId: acc, merchantName: acc, chains: [], shops: [] });
|
|
const created = naposService.store.upsertEntity({ merchantId: acc, merchantName: acc, chains: [], shops: [] });
|
|
|
key = created.entity.key;
|
|
key = created.entity.key;
|
|
|
|
|
+ meta = created.entity;
|
|
|
}
|
|
}
|
|
|
const res = naposService.store.upsertRows(key, rows);
|
|
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);
|
|
pulledKeys.push(key);
|
|
|
result.pulled += rows.length;
|
|
result.pulled += rows.length;
|
|
|
result.accounts.push({ account: acc, rows: rows.length, added: res.added, updated: res.updated });
|
|
result.accounts.push({ account: acc, rows: rows.length, added: res.added, updated: res.updated });
|
|
@@ -508,5 +539,5 @@ function createAitableService(ctx) {
|
|
|
module.exports = {
|
|
module.exports = {
|
|
|
createAitableService, FIELD_COLUMNS, DIMENSION_LABEL, SHEET_DEFAULTS,
|
|
createAitableService, FIELD_COLUMNS, DIMENSION_LABEL, SHEET_DEFAULTS,
|
|
|
rowToRecord, recordKey, recordToRow, splitRowsBySheet, resolveSheets, pushSummary,
|
|
rowToRecord, recordKey, recordToRow, splitRowsBySheet, resolveSheets, pushSummary,
|
|
|
- detectLayerConflict,
|
|
|
|
|
|
|
+ detectLayerConflict, treeFromRows,
|
|
|
};
|
|
};
|