R111/A1,A2 建立发票治理计划和海音达设计基线

这个提交包含在:
2026-05-08 10:05:12 +08:00
父节点 8238c197e7
当前提交 77fbfb5eb8
共修改 4 个文件,包含 144 行新增和 1 行删除
@@ -0,0 +1,109 @@
require("dotenv/config");
const { createPrismaClient } = require("../prismaClient");
function printGroup(title, rows, keyFields) {
process.stdout.write(`\n## ${title}\n`);
if (!rows.length) {
process.stdout.write("无重复记录\n");
return;
}
for (const row of rows) {
const key = keyFields.map((field) => row[field]).join(" / ");
process.stdout.write(`\n- ${key}:${row.count} 条\n`);
for (const item of row.items) {
process.stdout.write(
` - ${item.id} status=${item.status} owner=${item.ownerName ?? "-"} form=${item.formNumber ?? "-"} file=${item.originalName ?? "-"} created=${item.createdAt ?? "-"}\n`,
);
}
}
}
async function loadDuplicateGroups(prisma, { byInvoiceNumber = false } = {}) {
const groups = byInvoiceNumber
? await prisma.$queryRaw`
SELECT invoice_code, invoice_number, COUNT(*)::int AS count
FROM "invoice_pool"
WHERE invoice_code IS NOT NULL AND invoice_number IS NOT NULL
GROUP BY invoice_code, invoice_number
HAVING COUNT(*) > 1
ORDER BY COUNT(*) DESC, invoice_code ASC, invoice_number ASC
LIMIT 100
`
: await prisma.$queryRaw`
SELECT file_sha256, COUNT(*)::int AS count
FROM "invoice_pool"
GROUP BY file_sha256
HAVING COUNT(*) > 1
ORDER BY COUNT(*) DESC, file_sha256 ASC
LIMIT 100
`;
const rows = [];
for (const group of groups) {
const where = byInvoiceNumber
? {
invoice_code: group.invoice_code,
invoice_number: group.invoice_number,
}
: { file_sha256: group.file_sha256 };
const items = await prisma.invoicePool.findMany({
where,
orderBy: [{ created_at: "asc" }, { id: "asc" }],
select: {
id: true,
status: true,
original_name: true,
created_at: true,
owner_employee: { select: { name: true } },
linked_reimbursement: { select: { form_number: true } },
},
});
rows.push({
...group,
items: items.map((item) => ({
id: item.id,
status: item.status,
ownerName: item.owner_employee?.name ?? null,
formNumber: item.linked_reimbursement?.form_number ?? null,
originalName: item.original_name ?? null,
createdAt: item.created_at?.toISOString?.() ?? null,
})),
});
}
return rows;
}
(async () => {
const { prisma, pool } = createPrismaClient();
await prisma.$connect();
try {
const statusRows = await prisma.invoicePool.groupBy({
by: ["status"],
_count: { _all: true },
orderBy: { status: "asc" },
});
process.stdout.write("# 发票池重复审计\n\n## 状态分布\n");
for (const row of statusRows) {
process.stdout.write(`- ${row.status}: ${row._count._all}\n`);
}
const [fileGroups, numberGroups] = await Promise.all([
loadDuplicateGroups(prisma),
loadDuplicateGroups(prisma, { byInvoiceNumber: true }),
]);
printGroup("按文件 SHA256 重复", fileGroups, ["file_sha256"]);
printGroup("按发票代码/号码重复", numberGroups, ["invoice_code", "invoice_number"]);
} finally {
await prisma.$disconnect();
await pool.end();
}
})().catch((error) => {
console.error(error);
process.exit(1);
});