- 新增 ProductInventorySnapshot / ProductInventorySnapshotRun 模型与正式迁移(条码×实际仓库) - 同步任务复用现有库存/云仓/档案读取,临时表+事务原子替换,advisory lock 防重叠,失败保留上一份快照 - 新增 15 分钟 systemd timer 安装脚本与同步脚本(身份校验、凭据走环境变量) - 列表/导出接口改读快照并展示 syncedAt,保留全零行过滤与导出字段选择
70 lines
2.1 KiB
TypeScript
70 lines
2.1 KiB
TypeScript
import { loadEnvConfig } from '@next/env'
|
|
|
|
loadEnvConfig(process.cwd())
|
|
|
|
const CONFIRM_TOKEN = 'sync-product-inventory-snapshot'
|
|
|
|
function arg(name: string) {
|
|
const prefix = `--${name}=`
|
|
return process.argv.slice(2).find((value) => value.startsWith(prefix))?.slice(prefix.length) ?? ''
|
|
}
|
|
|
|
function required(name: string) {
|
|
const value = process.env[name]?.trim()
|
|
if (!value) throw new Error(`${name} is required.`)
|
|
return value
|
|
}
|
|
|
|
function identity(url: string) {
|
|
const parsed = new URL(url)
|
|
return [
|
|
parsed.protocol.replace(':', ''),
|
|
decodeURIComponent(parsed.username || ''),
|
|
parsed.hostname,
|
|
parsed.port || '',
|
|
parsed.pathname.replace(/^\//, ''),
|
|
parsed.searchParams.get('schema') ?? '',
|
|
].join('|')
|
|
}
|
|
|
|
function sourceIdentity() {
|
|
return [
|
|
'sqlserver',
|
|
required('JST_REPORT_DB_USER'),
|
|
required('JST_REPORT_DB_HOST'),
|
|
process.env.JST_REPORT_DB_PORT?.trim() || '1433',
|
|
required('JST_REPORT_DB_NAME'),
|
|
process.env.JST_REPORT_DB_SCHEMA?.trim() || 'rpt',
|
|
].join('|')
|
|
}
|
|
|
|
async function main() {
|
|
if (!process.argv.includes('--apply') || arg('confirm') !== CONFIRM_TOKEN) {
|
|
throw new Error(`写入商品库存快照需要 --apply 和 --confirm=${CONFIRM_TOKEN}`)
|
|
}
|
|
const databaseUrl = required('DATABASE_URL')
|
|
if (arg('expected-database-identity') !== identity(databaseUrl)) {
|
|
throw new Error('expected database identity mismatch')
|
|
}
|
|
if (arg('expected-source-database-identity') !== sourceIdentity()) {
|
|
throw new Error('expected source identity mismatch')
|
|
}
|
|
|
|
const { prisma } = await import('../src/lib/prisma')
|
|
const { productInventorySnapshotService } = await import(
|
|
'../src/server/services/product-inventory-snapshot.service'
|
|
)
|
|
try {
|
|
console.log(JSON.stringify({ stage: 'start' }))
|
|
const result = await productInventorySnapshotService.sync()
|
|
console.log(JSON.stringify({ stage: 'completed', ...result }, null, 2))
|
|
} finally {
|
|
await prisma.$disconnect()
|
|
}
|
|
}
|
|
|
|
void main().catch((error) => {
|
|
console.error(error instanceof Error ? error.message : String(error))
|
|
process.exitCode = 1
|
|
})
|