feat: add platform invoice dunning workflow

This commit is contained in:
Codex
2026-06-30 05:27:58 +08:00
parent f9f96bee63
commit c74432ce98
25 changed files with 858 additions and 23 deletions

View File

@@ -12,6 +12,7 @@
"commerce:once": "tsx src/index.ts --once --job commerce",
"provider-bills:once": "tsx src/index.ts --once --job provider-bills",
"platform-billing:once": "tsx src/index.ts --once --job platform-billing",
"platform-dunning:once": "tsx src/index.ts --once --job platform-dunning",
"assets:once": "tsx src/index.ts --once --job assets",
"imports:once": "tsx src/index.ts --once --job imports",
"public-banks:once": "tsx src/index.ts --once --job public-banks",

View File

@@ -22,6 +22,8 @@ export interface WorkerConfig {
platformBillingDaysAhead: number;
platformBillingDueDays: number;
platformBillingWorkerId: string;
platformDunningBatchSize: number;
platformDunningWorkerId: string;
assetBatchSize: number;
assetMinAgeSeconds: number;
assetRecheckIntervalSeconds: number;
@@ -174,6 +176,8 @@ const loadedConfig: WorkerConfig = {
platformBillingDaysAhead: envNumber('WORKER_PLATFORM_BILLING_DAYS_AHEAD', 45),
platformBillingDueDays: envNumber('WORKER_PLATFORM_BILLING_DUE_DAYS', 15),
platformBillingWorkerId: envString('WORKER_PLATFORM_BILLING_ID', `platform-billing-${process.pid}`),
platformDunningBatchSize: envNumber('WORKER_PLATFORM_DUNNING_BATCH_SIZE', 100),
platformDunningWorkerId: envString('WORKER_PLATFORM_DUNNING_ID', `platform-dunning-${process.pid}`),
assetBatchSize: envNumber('WORKER_ASSET_BATCH_SIZE', 50),
assetMinAgeSeconds: envNumber('WORKER_ASSET_MIN_AGE_SECONDS', 300),
assetRecheckIntervalSeconds: envNumber('WORKER_ASSET_RECHECK_INTERVAL_SECONDS', 60 * 60 * 24),

View File

@@ -51,6 +51,16 @@ async function runOnce() {
);
return;
}
if (job === 'platform-dunning') {
const { processPlatformDunningBatch } = await import('./jobs/platform-dunning.js');
const result = await processPlatformDunningBatch();
console.log(
`[worker] platform-dunning batch processed=${result.processed}`
+ ` markedOverdue=${result.markedOverdue} reminderCreated=${result.reminderCreated}`
+ ` skippedReminder=${result.skippedReminder}`,
);
return;
}
if (job === 'assets') {
const result = await processAssetBatch();
console.log(

View File

@@ -0,0 +1,34 @@
import { pool } from '../db.js';
import { config } from '../config.js';
import { processOverduePlatformInvoices } from '../../../api/src/features/platform-admin/service.js';
interface PlatformDunningWorkerResult {
processed: number;
markedOverdue: number;
reminderCreated: number;
skippedReminder: number;
}
export async function processPlatformDunningBatch(limit = config.platformDunningBatchSize): Promise<PlatformDunningWorkerResult> {
const client = await pool.connect();
try {
await client.query('begin');
const result = await processOverduePlatformInvoices(client, {
channel: 'internal',
limit,
workerId: config.platformDunningWorkerId,
});
await client.query('commit');
return {
processed: Number(result.processed || 0),
markedOverdue: Number(result.markedOverdue || 0),
reminderCreated: Number(result.reminderCreated || 0),
skippedReminder: Number(result.skippedReminder || 0),
};
} catch (error) {
await client.query('rollback').catch(() => {});
throw error;
} finally {
client.release();
}
}