forked from wangziqi/gongxue-base
feat: add platform subscription invoice batching
This commit is contained in:
@@ -1232,6 +1232,110 @@ async function testPlatformTenantOperationsAndAudit() {
|
||||
assert.ok(actions.has('platform.tenant.billing_profile_upserted'), 'platform audit should include billing profile update');
|
||||
assert.ok(actions.has('platform.tenant.status_updated'), 'platform audit should include status update');
|
||||
|
||||
const candidates = await request('/api/platform-admin/invoices/subscription-candidates', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
query: { tenantIds: tenantId, daysAhead: 400, limit: 20 },
|
||||
});
|
||||
const candidate = candidates.items?.find(item => item.tenantId === tenantId);
|
||||
assert.ok(candidate?.subscriptionId, 'platform admin should preview subscription invoice candidates');
|
||||
assert.equal(candidate.hasExistingInvoice, false, 'new tenant subscription should not have an existing invoice');
|
||||
|
||||
const invalidCandidateQuery = await request('/api/platform-admin/invoices/subscription-candidates', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
query: { tenantIds: 'not-a-uuid' },
|
||||
expectStatus: 400,
|
||||
});
|
||||
assert.equal(invalidCandidateQuery.code, 'INVALID_UUID', 'subscription candidate query should reject malformed UUID filters');
|
||||
|
||||
const candidateBySubscription = await request('/api/platform-admin/invoices/subscription-candidates', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
query: { subscriptionIds: candidate.subscriptionId, daysAhead: 400, limit: 20 },
|
||||
});
|
||||
assert.equal(candidateBySubscription.items?.length, 1, 'subscription candidate query should support precise subscription filters');
|
||||
assert.equal(candidateBySubscription.items?.[0]?.subscriptionId, candidate.subscriptionId, 'subscription candidate filter should return the requested subscription');
|
||||
|
||||
const dryRunBatch = await request('/api/platform-admin/invoices/from-subscriptions-batch', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
method: 'POST',
|
||||
body: {
|
||||
tenantIds: [tenantId],
|
||||
daysAhead: 400,
|
||||
dryRun: true,
|
||||
dueDate: '2026-07-31',
|
||||
note: 'integration dry-run',
|
||||
},
|
||||
});
|
||||
assert.equal(dryRunBatch.item?.dryRun, true, 'batch subscription invoice dry-run should not create invoices');
|
||||
assert.ok(dryRunBatch.item?.items?.some(item => item.subscriptionId === candidate.subscriptionId), 'dry-run should include the candidate subscription');
|
||||
|
||||
const invalidBatchStatus = await request('/api/platform-admin/invoices/from-subscriptions-batch', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
method: 'POST',
|
||||
body: {
|
||||
tenantIds: [tenantId],
|
||||
daysAhead: 400,
|
||||
status: 'unexpected',
|
||||
},
|
||||
expectStatus: 400,
|
||||
});
|
||||
assert.equal(invalidBatchStatus.code, 'INVALID_INVOICE_STATUS', 'batch subscription invoice should reject invalid invoice statuses');
|
||||
|
||||
const batchCreated = await request('/api/platform-admin/invoices/from-subscriptions-batch', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
method: 'POST',
|
||||
body: {
|
||||
tenantIds: [tenantId],
|
||||
daysAhead: 400,
|
||||
dueDate: '2026-07-31',
|
||||
note: 'integration batch invoice',
|
||||
},
|
||||
});
|
||||
assert.equal(batchCreated.item?.createdCount, 1, 'batch subscription invoice should create one invoice');
|
||||
assert.equal(batchCreated.item?.skippedCount, 0, 'first batch subscription invoice should not skip');
|
||||
|
||||
const duplicateBatch = await request('/api/platform-admin/invoices/from-subscriptions-batch', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
method: 'POST',
|
||||
body: {
|
||||
tenantIds: [tenantId],
|
||||
daysAhead: 400,
|
||||
dueDate: '2026-07-31',
|
||||
},
|
||||
});
|
||||
assert.equal(duplicateBatch.item?.createdCount, 0, 'duplicate batch invoice run should not create another invoice');
|
||||
|
||||
const duplicateSingle = await request('/api/platform-admin/invoices/from-subscription', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
method: 'POST',
|
||||
body: { tenantId },
|
||||
expectStatus: 409,
|
||||
});
|
||||
assert.equal(duplicateSingle.code, 'SUBSCRIPTION_INVOICE_EXISTS', 'single subscription invoice should reject duplicates');
|
||||
|
||||
const auditAfterBatch = await request('/api/platform-admin/audit-logs', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
query: { q: 'platform.invoice.subscription_batch_created', limit: 20 },
|
||||
});
|
||||
assert.ok(auditAfterBatch.items?.some(item => item.action === 'platform.invoice.subscription_batch_created'), 'batch invoice creation should write platform audit');
|
||||
|
||||
const studentAuditDenied = await request('/api/platform-admin/audit-logs', {
|
||||
tenantId: false,
|
||||
userId: USER_ID,
|
||||
|
||||
Reference in New Issue
Block a user