From 3ccddc59824190cfc0bdb4b4b23ae72e3808f3bf Mon Sep 17 00:00:00 2001 From: yeuimu <2197651308@qq.com> Date: Wed, 2 Sep 2026 10:22:49 +0800 Subject: [PATCH] feat(api): backfill category/country sort order from reference table --- .../prisma/backfill-category-sort-order.ts | 133 ++++++++++++++++++ 排序表.md | 6 +- 2 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 apps/api/prisma/backfill-category-sort-order.ts diff --git a/apps/api/prisma/backfill-category-sort-order.ts b/apps/api/prisma/backfill-category-sort-order.ts new file mode 100644 index 0000000..27ba60e --- /dev/null +++ b/apps/api/prisma/backfill-category-sort-order.ts @@ -0,0 +1,133 @@ +/** + * 一次性回填:解析 排序表.md → categories.sort_order(二级/款)+ countries.sort_order + * + 沙特国家行 + 中东根更名 + * 幂等:可重复执行;SDS 同步若覆盖根名称,重跑本脚本即可恢复 + */ +import { PrismaClient } from '@prisma/client'; +import { readFileSync } from 'fs'; + +const prisma = new PrismaClient(); + +// 排序表国家名 → 新树根分类名(与库内 category_name 精确对应) +const ROOT_MAP: Record = { + '美国': '美国工厂直发', + '英国': '英国本地直发', + '日本': '日本本地工厂直发', + '墨西哥': '墨西哥工厂本地直发', + '巴西': '巴西本地工厂直发', + '中东': '中东本地工厂直发', // 同时更名沙特 + '波兰': '欧洲波兰工厂直发', + '西班牙': '欧洲西班牙工厂直发', + '德国': '欧洲德国工厂本地直发', + '意大利': '欧洲意大利工厂直发', + '加拿大': '加拿大本地工厂直发', + '澳大利亚': '澳大利亚本地工厂直发', + '韩国': '韩国本地直发', + '中国(国内工厂)': '国内工厂', +}; + +const norm = (s: string) => s.trim().replace(/\s+/g, ''); +const codeOf = (s: string) => (s.trim().match(/^[A-Za-z0-9]+/) ?? [''])[0]; + +async function main() { + const raw = readFileSync(process.env.SORT_TABLE_PATH ?? '/repo/排序表.md', 'utf8'); + let country: string | null = null; + let l2: string | null = null; + const tree: Array<{ country: string; l2: string; l3: string }> = []; + const countryOrder: string[] = []; + for (const line of raw.split('\n')) { + const t = line.trim(); + if (t.startsWith('# ')) { + const name = t.slice(2).trim(); + if (name === '全部' || name.includes('工厂直发国家')) continue; + country = name; + if (!countryOrder.includes(name)) countryOrder.push(name); + } else if (t.startsWith('## ') && country) { + l2 = t.slice(3).trim(); + } else if (t.startsWith('### ') && country && l2) { + tree.push({ country, l2, l3: t.slice(4).trim() }); + } + } + console.log(`parsed: ${countryOrder.length} countries, ${tree.length} leaves`); + + const roots = await prisma.category.findMany({ + where: { parentCategoryId: null, sdsCategoryId: { not: null } }, + include: { children: { include: { children: true } } }, + }); + const rootByName = new Map(roots.map((r) => [r.categoryName, r])); + const unmatched: string[] = []; + + // 1) countries:沙特 upsert + 顺序重写(中国无国家行,跳过) + for (let i = 0; i < countryOrder.length; i++) { + const name = countryOrder[i]; + const dbCountry = name === '中东' ? '沙特' : name; + if (dbCountry === '中国(国内工厂)') continue; + const sortOrder = i + 1; + const existing = await prisma.country.findUnique({ where: { countryName: dbCountry } }); + if (existing) { + await prisma.country.update({ where: { id: existing.id }, data: { sortOrder } }); + } else if (dbCountry === '沙特') { + await prisma.country.create({ data: { countryName: '沙特', sortOrder } }); + console.log('created country: 沙特'); + } + } + + // 2) 中东根 → 沙特 + const meRoot = rootByName.get('中东本地工厂直发'); + if (meRoot) { + await prisma.category.update({ where: { id: meRoot.id }, data: { categoryName: '沙特本地工厂直发' } }); + rootByName.set('沙特本地工厂直发', meRoot); + console.log('renamed root: 中东本地工厂直发 -> 沙特本地工厂直发'); + } + + // 3) 二级/款 sort_order + for (const countryName of countryOrder) { + const rootName = countryName === '中东' ? '沙特本地工厂直发' : ROOT_MAP[countryName]; + const root = rootByName.get(rootName); + if (!root) { + unmatched.push(`ROOT MISS: ${countryName} (expect root "${rootName}")`); + continue; + } + const l2s = root.children; + const l2NamesInOrder: string[] = []; + for (const row of tree) { + if (row.country === countryName && !l2NamesInOrder.includes(row.l2)) l2NamesInOrder.push(row.l2); + } + for (let i = 0; i < l2NamesInOrder.length; i++) { + const target = l2NamesInOrder[i]; + const mid = l2s.find((m) => norm(m.categoryName) === norm(target)); + if (!mid) { + unmatched.push(`L2 MISS: ${countryName} / ${target}`); + continue; + } + await prisma.category.update({ where: { id: mid.id }, data: { sortOrder: i + 1 } }); + const leaves = mid.children; + const l3Names = tree.filter((r) => r.country === countryName && r.l2 === target).map((r) => r.l3); + for (let j = 0; j < l3Names.length; j++) { + const want = norm(l3Names[j]); + const code = norm(codeOf(l3Names[j])); + const leaf = + leaves.find((l) => norm(l.categoryName) === want) ?? + (code ? leaves.find((l) => norm(l.categoryName).startsWith(code)) : undefined); + if (!leaf) { + unmatched.push(`L3 MISS: ${countryName} / ${target} / ${l3Names[j]}`); + continue; + } + await prisma.category.update({ where: { id: leaf.id }, data: { sortOrder: j + 1 } }); + } + } + } + + if (unmatched.length) { + console.error(`UNMATCHED (${unmatched.length}):\n` + unmatched.join('\n')); + process.exit(1); + } + console.log('backfill done'); +} + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(() => prisma.$disconnect()); diff --git a/排序表.md b/排序表.md index 1674419..740b944 100644 --- a/排序表.md +++ b/排序表.md @@ -14,7 +14,7 @@ ### DG100 180G女士彩棉T恤 ### DG101 180G纯棉女士露脐T恤 ### DG102 高弹力女款露脐 -### TDG110 180G大码彩棉女T恤 +### DG110 180G大码彩棉女T恤 ### DG120 牛奶丝T恤 ### DG150 180G纯棉女士T恤 ### DG502 180G女士圆领长袖T恤 @@ -27,7 +27,7 @@ ### DG014 190G小童T恤 ### DG301 190G纯棉哈哈衣 ### DG503 190G小童长袖 -### T5000B 180G吉尔丹童装T恤 +### 5000B 180G吉尔丹童装T恤 ### JSA004 190G牛奶丝小童T恤 ## 男士短裤 ### DG201 300G脏洗短裤 @@ -62,7 +62,7 @@ ## 男士长裤 ### DG210 280G高级抓绒慢跑卫裤 ## 内衣 -### DG170G170G女士无痕三角内裤 +### DG701 170G女士无痕三角内裤 ### JSA010 270G高弹力女士抹胸 ### JSE001 180G女士无痕内衣 ## 家居