feat(api): backfill category/country sort order from reference table
This commit is contained in:
@@ -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<string, string> = {
|
||||
'美国': '美国工厂直发',
|
||||
'英国': '英国本地直发',
|
||||
'日本': '日本本地工厂直发',
|
||||
'墨西哥': '墨西哥工厂本地直发',
|
||||
'巴西': '巴西本地工厂直发',
|
||||
'中东': '中东本地工厂直发', // 同时更名沙特
|
||||
'波兰': '欧洲波兰工厂直发',
|
||||
'西班牙': '欧洲西班牙工厂直发',
|
||||
'德国': '欧洲德国工厂本地直发',
|
||||
'意大利': '欧洲意大利工厂直发',
|
||||
'加拿大': '加拿大本地工厂直发',
|
||||
'澳大利亚': '澳大利亚本地工厂直发',
|
||||
'韩国': '韩国本地直发',
|
||||
'中国(国内工厂)': '国内工厂',
|
||||
};
|
||||
|
||||
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());
|
||||
Reference in New Issue
Block a user