135 lines
3.8 KiB
TypeScript
135 lines
3.8 KiB
TypeScript
/**
|
|
* 把旧的 sku_code 渠道规则转换为 internal_sku 全局规则,并停用旧规则。
|
|
*
|
|
* 用法:
|
|
* npx tsx scripts/cleanup-legacy-sku-code-rules.ts
|
|
* npx tsx scripts/cleanup-legacy-sku-code-rules.ts --apply
|
|
*/
|
|
import { closeDb, query } from '../src/db/client.js'
|
|
|
|
type LegacyRule = {
|
|
id: number
|
|
provider: string
|
|
shop_id: string
|
|
match_key: string
|
|
channel: string
|
|
priority: number
|
|
source: string
|
|
}
|
|
|
|
async function resolveInternalSku(rule: LegacyRule): Promise<string> {
|
|
if (rule.channel === 'affiliate_dash') {
|
|
const product = await query<{ canonical_name: string }>(
|
|
`SELECT canonical_name FROM product_catalog
|
|
WHERE canonical_sku = $1 AND status = 'active' AND btrim(canonical_name) <> ''
|
|
LIMIT 1`,
|
|
[`aff:${rule.match_key}`],
|
|
)
|
|
if (product.rows[0]?.canonical_name) return String(product.rows[0].canonical_name).trim()
|
|
}
|
|
|
|
const params: unknown[] = [rule.match_key]
|
|
let scope = ''
|
|
if (rule.provider) {
|
|
params.push(rule.provider)
|
|
scope += ` AND o.provider = $${params.length}`
|
|
}
|
|
if (rule.shop_id) {
|
|
params.push(rule.shop_id)
|
|
scope += ` AND o.shop_id = $${params.length}`
|
|
}
|
|
const items = await query<{ internal_sku: string }>(
|
|
`
|
|
SELECT DISTINCT btrim(oi.manual_sku_key) AS internal_sku
|
|
FROM order_items oi
|
|
JOIN orders o ON o.id = oi.order_id
|
|
WHERE oi.sku_code = $1
|
|
AND btrim(COALESCE(oi.manual_sku_key, '')) ~ '[㐀-䶿一-鿿豈-𠀀-𰀀-𲎯]'
|
|
${scope}
|
|
LIMIT 2
|
|
`,
|
|
params,
|
|
)
|
|
const values = items.rows.map((row) => String(row.internal_sku || '').trim()).filter(Boolean)
|
|
return values.length === 1 ? values[0]! : ''
|
|
}
|
|
|
|
async function main() {
|
|
const apply = process.argv.includes('--apply')
|
|
const rules = await query<LegacyRule>(
|
|
`
|
|
SELECT id, provider, shop_id, match_key, channel, priority, source
|
|
FROM sku_channel_rules
|
|
WHERE match_key_type = 'sku_code' AND enabled = TRUE
|
|
ORDER BY id
|
|
`,
|
|
)
|
|
|
|
const converted: Array<{ id: number; matchKey: string; internalSku: string; channel: string }> =
|
|
[]
|
|
const unresolved: LegacyRule[] = []
|
|
for (const rule of rules.rows) {
|
|
const internalSku = await resolveInternalSku(rule)
|
|
if (!internalSku) {
|
|
unresolved.push(rule)
|
|
continue
|
|
}
|
|
converted.push({ id: rule.id, matchKey: rule.match_key, internalSku, channel: rule.channel })
|
|
if (!apply) continue
|
|
|
|
await query(
|
|
`UPDATE internal_sku_channels
|
|
SET is_fallback = TRUE,
|
|
priority = GREATEST(priority, 90),
|
|
updated_at = NOW()
|
|
WHERE btrim(internal_sku) = $1
|
|
AND channel <> $2
|
|
AND enabled = TRUE
|
|
AND is_fallback = FALSE`,
|
|
[internalSku, rule.channel],
|
|
)
|
|
await query(
|
|
`
|
|
INSERT INTO internal_sku_channels (
|
|
internal_sku, channel, priority, is_fallback, enabled, source
|
|
) VALUES ($1, $2, $3, FALSE, TRUE, 'legacy-rule')
|
|
ON CONFLICT (internal_sku, channel) DO UPDATE SET
|
|
priority = LEAST(internal_sku_channels.priority, EXCLUDED.priority),
|
|
is_fallback = FALSE,
|
|
enabled = TRUE,
|
|
updated_at = NOW()
|
|
`,
|
|
[internalSku, rule.channel, rule.priority],
|
|
)
|
|
await query(
|
|
`UPDATE sku_channel_rules
|
|
SET enabled = FALSE,
|
|
note = CONCAT(COALESCE(note, ''), ' [已迁移 internal_sku:', $2::text, ']'),
|
|
updated_at = NOW()
|
|
WHERE id = $1`,
|
|
[rule.id, internalSku],
|
|
)
|
|
}
|
|
|
|
console.log(
|
|
JSON.stringify(
|
|
{
|
|
apply,
|
|
legacyRules: rules.rows.length,
|
|
convertible: converted.length,
|
|
converted,
|
|
unresolved,
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
)
|
|
}
|
|
|
|
main()
|
|
.catch((error) => {
|
|
console.error(error)
|
|
process.exitCode = 1
|
|
})
|
|
.finally(() => closeDb())
|