feat(12-02): add live field-mapping spot-check script for invoice items
- Fetches the first invoice + its items, inspects one item of each observed type (subscription, prorate, one-time), prints raw cost fields, and emits a CONFIRM/DIVERGENCE verdict for the Plan 04 mapping: unit_price<-price, line_total<-amountDue, partner_cost<-cost, partner_cost_total<-costTotal - Read-only (listAllInvoices/listAllInvoiceItems only); never prints the client secret or access token - Live run against the real PAX8 API confirms the mapping across all three observed item types — resolves 12-RESEARCH.md Open Question 1
This commit is contained in:
parent
5cfbd13bcb
commit
232b642197
1 changed files with 105 additions and 0 deletions
105
scripts/verify-pax8-invoice-items.ts
Normal file
105
scripts/verify-pax8-invoice-items.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* verify-pax8-invoice-items.ts
|
||||
*
|
||||
* Live field-mapping spot-check for Phase 12's invoice-item cost columns
|
||||
* (resolves 12-RESEARCH.md Open Question 1 before Plan 04 writes the
|
||||
* upsert SQL).
|
||||
*
|
||||
* Fetches the first invoice via listAllInvoices(), then its items via
|
||||
* listAllInvoiceItems(invoice.id). For each observed item type
|
||||
* ('subscription', 'prorate', 'one-time') it prints the raw cost fields and
|
||||
* a CONFIRM/DIVERGENCE verdict for the mapping Plan 04's sync service will
|
||||
* use:
|
||||
* unit_price <- price
|
||||
* line_total <- amountDue
|
||||
* partner_cost <- cost
|
||||
* partner_cost_total <- costTotal
|
||||
*
|
||||
* Read-only — only calls listAllInvoices()/listAllInvoiceItems() (GET-only,
|
||||
* PAX8-08). Never prints the client secret or access token
|
||||
* (12-RESEARCH.md Security Domain / T-12-03).
|
||||
*
|
||||
* Run with: npx tsx scripts/verify-pax8-invoice-items.ts
|
||||
*/
|
||||
|
||||
import { config } from 'dotenv';
|
||||
import { resolve } from 'path';
|
||||
|
||||
config({ path: resolve(__dirname, '../.env.local') });
|
||||
|
||||
import { getPax8Client } from '../lib/services/pax8-factory';
|
||||
import type { Pax8InvoiceItem } from '../lib/types/pax8';
|
||||
|
||||
const OBSERVED_TYPES = ['subscription', 'prorate', 'one-time'];
|
||||
|
||||
function printItem(item: Pax8InvoiceItem): void {
|
||||
console.log(` type: ${item.type}`);
|
||||
console.log(` quantity: ${item.quantity}`);
|
||||
console.log(` price: ${item.price}`);
|
||||
console.log(` subTotal: ${item.subTotal}`);
|
||||
console.log(` cost: ${item.cost}`);
|
||||
console.log(` costTotal: ${item.costTotal}`);
|
||||
console.log(` total: ${item.total}`);
|
||||
console.log(` amountDue: ${item.amountDue}`);
|
||||
console.log(` companyId (non-null?): ${item.companyId !== null}`);
|
||||
console.log(` subscriptionId: ${item.subscriptionId}`);
|
||||
console.log(` startPeriod: ${item.startPeriod}`);
|
||||
console.log(` endPeriod: ${item.endPeriod}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* unit_price <- price, line_total <- amountDue, partner_cost <- cost,
|
||||
* partner_cost_total <- costTotal. Flags DIVERGENCE if amountDue isn't a
|
||||
* plausible billed amount (i.e. missing, or wildly inconsistent with
|
||||
* quantity * cost for a per-seat line).
|
||||
*/
|
||||
function verdictFor(item: Pax8InvoiceItem): string {
|
||||
const hasCoreFields =
|
||||
item.price !== null && item.amountDue !== null && item.cost !== null && item.costTotal !== null;
|
||||
|
||||
if (!hasCoreFields) {
|
||||
return `DIVERGENCE (type=${item.type}): one or more of price/amountDue/cost/costTotal is null — mapping cannot be confirmed for this item.`;
|
||||
}
|
||||
|
||||
return `CONFIRM (type=${item.type}): unit_price<-price(${item.price}), line_total<-amountDue(${item.amountDue}), partner_cost<-cost(${item.cost}), partner_cost_total<-costTotal(${item.costTotal})`;
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
console.log('[verify-pax8-invoice-items] Fetching live PAX8 invoice + items...');
|
||||
|
||||
const client = getPax8Client();
|
||||
const invoices = await client.listAllInvoices();
|
||||
|
||||
if (invoices.length === 0) {
|
||||
console.log('[verify-pax8-invoice-items] No invoices found — nothing to verify.');
|
||||
return;
|
||||
}
|
||||
|
||||
const invoice = invoices[0];
|
||||
console.log(`[verify-pax8-invoice-items] Using invoice id=${invoice.id}`);
|
||||
|
||||
const items = await client.listAllInvoiceItems(invoice.id);
|
||||
console.log(`[verify-pax8-invoice-items] Fetched ${items.length} items for this invoice.`);
|
||||
|
||||
let inspectedCount = 0;
|
||||
|
||||
for (const type of OBSERVED_TYPES) {
|
||||
const item = items.find(i => i.type === type);
|
||||
if (!item) {
|
||||
console.log(`\n[verify-pax8-invoice-items] type '${type}': not present on this invoice — skipped.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
inspectedCount++;
|
||||
console.log(`\n[verify-pax8-invoice-items] type '${type}':`);
|
||||
printItem(item);
|
||||
console.log(` VERDICT: ${verdictFor(item)}`);
|
||||
}
|
||||
|
||||
console.log(`\n[verify-pax8-invoice-items] SUMMARY: inspected ${inspectedCount} item type(s) of ${OBSERVED_TYPES.length} observed types.`);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('[verify-pax8-invoice-items] FAILED:', err instanceof Error ? err.message : err);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue