Files
lislgosms/tools/local/seed-signature-retirement.mjs
T

151 lines
13 KiB
JavaScript

import { PrismaPg } from '../../api/node_modules/@prisma/adapter-pg/dist/index.js';
import { PrismaClient } from '../../api/node_modules/@prisma/client/index.js';
const prisma = new PrismaClient({
adapter: new PrismaPg(process.env.DATABASE_URL ?? 'postgresql://cmpp:cmpp_password@localhost:5432/cmpp_platform?schema=public'),
});
const signatureIds = ['qa-retirement-signature-steady', 'qa-retirement-signature-low', 'qa-retirement-signature-zero', 'qa-retirement-signature-retry', 'qa-retirement-signature-unreported', 'qa-retirement-signature-partial'];
const taskIds = ['qa-retirement-task-steady-mobile', 'qa-retirement-task-steady-unicom', 'qa-retirement-task-low-unicom', 'qa-retirement-task-zero-telecom', 'qa-retirement-task-retry-primary', 'qa-retirement-task-retry-backup', 'qa-retirement-task-partial-mobile'];
const todayKey = new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit' }).format(new Date());
const todayNoon = new Date(`${todayKey}T12:00:00+08:00`);
const approvedAt = new Date(todayNoon.getTime() - 100 * 86_400_000);
try {
await cleanup();
const tenantA = await prisma.tenant.upsert({
where: { code: 'qa-retirement-a' },
update: { name: '清退验收企业A', status: 'active', certificationStatus: 'approved' },
create: { id: 'qa-retirement-tenant-a', code: 'qa-retirement-a', name: '清退验收企业A', status: 'active', certificationStatus: 'approved' },
});
const tenantB = await prisma.tenant.upsert({
where: { code: 'qa-retirement-b' },
update: { name: '清退验收企业B', status: 'active', certificationStatus: 'approved' },
create: { id: 'qa-retirement-tenant-b', code: 'qa-retirement-b', name: '清退验收企业B', status: 'active', certificationStatus: 'approved' },
});
const appA = await prisma.smsApplication.upsert({
where: { cmppAccount: 'qa_retirement_a' },
update: { tenantId: tenantA.id, name: '清退验收通知应用', status: 'active' },
create: { id: 'qa-retirement-app-a', tenantId: tenantA.id, name: '清退验收通知应用', cmppAccount: 'qa_retirement_a', cmppEnterpriseCode: 'QA_RET_A', secretHash: 'local-seed-only', status: 'active' },
});
const appB = await prisma.smsApplication.upsert({
where: { cmppAccount: 'qa_retirement_b' },
update: { tenantId: tenantB.id, name: '清退验收营销应用', status: 'active' },
create: { id: 'qa-retirement-app-b', tenantId: tenantB.id, name: '清退验收营销应用', cmppAccount: 'qa_retirement_b', cmppEnterpriseCode: 'QA_RET_B', secretHash: 'local-seed-only', status: 'active' },
});
const channelAll = await upsertChannel('qa-retirement-all', '验收通道-华东三网', ['mobile', 'unicom', 'telecom'], 42000n);
const channelMu = await upsertChannel('qa-retirement-mu', '验收通道-移动联通', ['mobile', 'unicom'], 39000n);
const channelTelecom = await upsertChannel('qa-retirement-telecom', '验收通道-电信专线', ['telecom'], 41000n);
const steady = await upsertSignature(signatureIds[0], tenantA.id, appA.id, '【验收稳定活跃】');
const low = await upsertSignature(signatureIds[1], tenantA.id, appA.id, '【验收低量预警】');
const zero = await upsertSignature(signatureIds[2], tenantB.id, appB.id, '【验收零量预警】');
const retry = await upsertSignature(signatureIds[3], tenantB.id, appB.id, '【验收跨通道补发】');
const unreported = await upsertSignature(signatureIds[4], tenantA.id, appA.id, '【验收完全未报备】', 'waiting_material');
const partial = await upsertSignature(signatureIds[5], tenantB.id, appB.id, '【验收仅移动已报备】');
await createTask(taskIds[0], tenantA.id, steady.id, channelAll.id, 'mobile');
await createTask(taskIds[1], tenantA.id, steady.id, channelAll.id, 'unicom');
await createTask(taskIds[2], tenantA.id, low.id, channelMu.id, 'unicom');
await createTask(taskIds[3], tenantB.id, zero.id, channelTelecom.id, 'telecom');
await createTask(taskIds[4], tenantB.id, retry.id, channelAll.id, 'mobile');
await createTask(taskIds[5], tenantB.id, retry.id, channelMu.id, 'mobile');
await createTask(taskIds[6], tenantB.id, partial.id, channelAll.id, 'mobile');
await prisma.signatureRetirementRule.upsert({
where: { ruleType_targetKey: { ruleType: 'enterprise_global', targetKey: '' } },
update: { enabled: true, mobileWindowDays: 30, mobileThreshold: 15, unicomWindowDays: 30, unicomThreshold: 15, telecomWindowDays: 30, telecomThreshold: 15, version: { increment: 1 } },
create: { ruleType: 'enterprise_global', targetKey: '', enabled: true, mobileWindowDays: 30, mobileThreshold: 15, unicomWindowDays: 30, unicomThreshold: 15, telecomWindowDays: 30, telecomThreshold: 15 },
});
await prisma.signatureRetirementRule.upsert({
where: { ruleType_targetKey: { ruleType: 'channel_global', targetKey: '' } },
update: { enabled: true, mobileWindowDays: 30, mobileThreshold: 15, unicomWindowDays: 30, unicomThreshold: 15, telecomWindowDays: 30, telecomThreshold: 15, version: { increment: 1 } },
create: { ruleType: 'channel_global', targetKey: '', enabled: true, mobileWindowDays: 30, mobileThreshold: 15, unicomWindowDays: 30, unicomThreshold: 15, telecomWindowDays: 30, telecomThreshold: 15 },
});
for (let day = 1; day <= 30; day += 1) {
await addAcceptedMessage({ key: `steady-${day}`, tenantId: tenantA.id, applicationId: appA.id, signatureId: steady.id, carrier: 'mobile', day, channelId: channelAll.id, delivered: day % 7 !== 0 });
}
for (const day of [3, 10, 20, 29]) {
await addAcceptedMessage({ key: `low-${day}`, tenantId: tenantA.id, applicationId: appA.id, signatureId: low.id, carrier: 'unicom', day, channelId: channelMu.id, delivered: day !== 20 });
}
for (const day of [2, 6, 10, 14, 18, 22, 26, 29]) {
await addRetryMessage({ key: `retry-${day}`, tenantId: tenantB.id, applicationId: appB.id, signatureId: retry.id, day, failedChannelId: channelAll.id, acceptedChannelId: channelMu.id });
}
for (let index = 1; index <= 6; index += 1) {
await addAcceptedMessage({ key: `unreported-${index}`, tenantId: tenantA.id, applicationId: appA.id, signatureId: unreported.id, carrier: 'mobile', day: 0, channelId: channelAll.id, delivered: index <= 5 });
}
for (let index = 1; index <= 4; index += 1) {
await addAcceptedMessage({ key: `partial-telecom-${index}`, tenantId: tenantB.id, applicationId: appB.id, signatureId: partial.id, carrier: 'telecom', day: 0, channelId: channelTelecom.id, delivered: true });
}
console.log(JSON.stringify({
todayKey,
tenants: 2,
applications: 2,
channels: 3,
signatures: 6,
carrierReportTasks: 7,
messageRecords: 52,
scenarios: ['稳定活跃', '低量预警', '零量预警', '跨通道补发', '完全未报备', '仅其他运营商已报备'],
}));
} finally {
await prisma.$disconnect();
}
async function cleanup() {
const detections = await prisma.signatureRetirementDetection.findMany({ where: { signatureId: { in: signatureIds } }, select: { id: true } });
const detectionIds = detections.map((item) => item.id);
await prisma.signatureRetirementMessage.deleteMany({ where: { detectionId: { in: detectionIds } } });
await prisma.signatureRetirementSuppression.deleteMany({ where: { signatureId: { in: signatureIds } } });
await prisma.signatureRetirementDetection.deleteMany({ where: { id: { in: detectionIds } } });
await prisma.signatureRetirementCycle.deleteMany({ where: { signatureId: { in: signatureIds } } });
const messages = await prisma.smsMessageRecord.findMany({ where: { messageId: { startsWith: 'QA-RETIREMENT-' } }, select: { id: true } });
const messageIds = messages.map((item) => item.id);
await prisma.smsReceiptRecord.deleteMany({ where: { messageRecordId: { in: messageIds } } });
await prisma.smsMessageSegmentAudit.deleteMany({ where: { messageRecordId: { in: messageIds } } });
await prisma.smsSubmitRecord.deleteMany({ where: { messageRecordId: { in: messageIds } } });
await prisma.smsMessageRecord.deleteMany({ where: { id: { in: messageIds } } });
await prisma.channelSignatureReportRecord.deleteMany({ where: { taskId: { in: taskIds } } });
await prisma.channelSignatureReportTask.deleteMany({ where: { id: { in: taskIds } } });
}
async function upsertChannel(code, name, carriers, unitPrice) {
return prisma.smsChannel.upsert({
where: { code },
update: { name, carrier: carriers.length === 3 ? 'all' : carriers.length === 1 ? carriers[0] : 'multi', carriers, unitPrice, status: 'disabled' },
create: { code, name, carrier: carriers.length === 3 ? 'all' : carriers.length === 1 ? carriers[0] : 'multi', carriers, sendRegion: '全国', protocol: 'CMPP', gatewayHost: '127.0.0.1', gatewayPort: 17890, enterpriseCode: 'QA_LOCAL', account: code, passwordCipher: 'local-seed-only', srcId: '10690000', cmppVersion: '3.0', rateLimitPerSecond: 100, unitPrice, status: 'disabled' },
});
}
async function upsertSignature(id, tenantId, applicationId, name, reportStatus = 'approved') {
return prisma.smsSignature.upsert({
where: { id },
update: { tenantId, applicationId, name, auditStatus: 'approved', reportStatus, pendingReport: reportStatus !== 'approved' },
create: { id, tenantId, applicationId, name, purpose: '本地签名清退验收', auditStatus: 'approved', reportStatus, pendingReport: reportStatus !== 'approved' },
});
}
async function createTask(id, tenantId, signatureId, channelId, carrier) {
await prisma.channelSignatureReportTask.create({ data: { id, tenantId, signatureId, channelId, carrier, approvedAt, approvalScope: 'carrier_specific', reportType: 'signature', status: 'approved', reason: '本地签名清退验收数据' } });
await prisma.channelSignatureReportRecord.create({ data: { taskId: id, channelId, action: 'local_qa_approved', statusBefore: 'reporting', statusAfter: 'approved', reason: '本地签名清退验收数据', sourceEntry: 'system' } });
}
async function addAcceptedMessage({ key, tenantId, applicationId, signatureId, carrier, day, channelId, delivered }) {
const at = new Date(todayNoon.getTime() - day * 86_400_000);
const record = await prisma.smsMessageRecord.create({ data: { id: `qa-retirement-message-${key}`, tenantId, applicationId, signatureId, messageId: `QA-RETIREMENT-${key.toUpperCase()}`, phoneNumber: carrier === 'mobile' ? '13800138000' : '18600186000', carrier, content: `【本地验收】${key}`, channelId, status: delivered ? 'delivered' : 'submitted', submitStatus: 'accepted', receiptStatus: delivered ? 'delivered' : 'failed', queuedAt: at, submittedAt: at, deliveredAt: delivered ? at : null } });
const submit = await prisma.smsSubmitRecord.create({ data: { id: `qa-retirement-submit-${key}`, tenantId, messageRecordId: record.id, channelId, submitId: `QA-SUBMIT-${key.toUpperCase()}`, gatewayMessageId: `QA-GW-${key.toUpperCase()}`, submitStatus: 'accepted', submittedAt: at, createdAt: at } });
await prisma.smsMessageSegmentAudit.create({ data: { id: `qa-retirement-segment-${key}`, tenantId, messageRecordId: record.id, submitRecordId: submit.id, channelId, submitId: submit.submitId, gatewayMessageId: submit.gatewayMessageId, submitStatus: 'accepted', receiptStatus: delivered ? 'delivered' : 'failed', rawStatus: delivered ? 'DELIVRD' : 'UNDELIV', submittedAt: at, deliveredAt: delivered ? at : null, createdAt: at } });
}
async function addRetryMessage({ key, tenantId, applicationId, signatureId, day, failedChannelId, acceptedChannelId }) {
const at = new Date(todayNoon.getTime() - day * 86_400_000);
const record = await prisma.smsMessageRecord.create({ data: { id: `qa-retirement-message-${key}`, tenantId, applicationId, signatureId, messageId: `QA-RETIREMENT-${key.toUpperCase()}`, phoneNumber: '13800138001', carrier: 'mobile', content: `【本地验收】${key}`, channelId: acceptedChannelId, status: 'delivered', submitStatus: 'accepted', receiptStatus: 'delivered', queuedAt: at, submittedAt: at, deliveredAt: at } });
const failed = await prisma.smsSubmitRecord.create({ data: { id: `qa-retirement-submit-${key}-failed`, tenantId, messageRecordId: record.id, channelId: failedChannelId, submitId: `QA-SUBMIT-${key.toUpperCase()}-FAILED`, submitStatus: 'failed', errorCode: 'LOCAL_TIMEOUT', errorMessage: '本地模拟通道超时,仅用于历史统计', submittedAt: at, createdAt: at } });
await prisma.smsMessageSegmentAudit.create({ data: { id: `qa-retirement-segment-${key}-failed`, tenantId, messageRecordId: record.id, submitRecordId: failed.id, channelId: failedChannelId, submitId: failed.submitId, submitStatus: 'failed', errorCode: 'LOCAL_TIMEOUT', submittedAt: at, createdAt: at } });
const accepted = await prisma.smsSubmitRecord.create({ data: { id: `qa-retirement-submit-${key}-accepted`, tenantId, messageRecordId: record.id, channelId: acceptedChannelId, retryOfSubmitRecordId: failed.id, submitId: `QA-SUBMIT-${key.toUpperCase()}-ACCEPTED`, gatewayMessageId: `QA-GW-${key.toUpperCase()}`, submitStatus: 'accepted', submittedAt: new Date(at.getTime() + 60_000), createdAt: new Date(at.getTime() + 60_000) } });
await prisma.smsMessageSegmentAudit.create({ data: { id: `qa-retirement-segment-${key}-accepted`, tenantId, messageRecordId: record.id, submitRecordId: accepted.id, channelId: acceptedChannelId, submitId: accepted.submitId, gatewayMessageId: accepted.gatewayMessageId, submitStatus: 'accepted', receiptStatus: 'delivered', rawStatus: 'DELIVRD', submittedAt: accepted.submittedAt, deliveredAt: new Date(at.getTime() + 120_000), createdAt: accepted.createdAt } });
}