Wire zappier-edge into the live stack and add billing department APIs
Some checks are pending
offline / test (push) Waiting to run
Some checks are pending
offline / test (push) Waiting to run
Fleet now spawns the real zappier and middleware processes. Metered timestamp/receipt/hash calls proxy to middleware when ZAPPIER_UPSTREAM is set. CS credits, sales per-customer pricing, and QuickBooks export are separate repos plugged into zappier-edge admin.
This commit is contained in:
parent
65bfa544b2
commit
a1a5b957fd
44 changed files with 822 additions and 18 deletions
|
|
@ -10,6 +10,8 @@ import { PriceRule, PricingStore, TierConfig } from './pricing';
|
|||
import { PROJECT_ROOT } from './paths';
|
||||
import { billingRows, toCsv, usageTrend } from './reports';
|
||||
import { UsageRepo } from './usage';
|
||||
import { CreditLedger } from './credits';
|
||||
import { invoicesToAccountingCsv, invoicesToQuickBooksIif } from './accounting-export';
|
||||
|
||||
// Issued login tokens (in-memory; a restart simply requires logging in again).
|
||||
const sessions = new Map<string, number>();
|
||||
|
|
@ -139,6 +141,7 @@ export function adminRouter(
|
|||
store: PricingStore,
|
||||
customers: CustomerRepo,
|
||||
accounting: AccountingDeps,
|
||||
credits: CreditLedger = new CreditLedger(),
|
||||
): Router {
|
||||
const router = Router();
|
||||
|
||||
|
|
@ -222,6 +225,70 @@ export function adminRouter(
|
|||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
/* ---------------- customer service: credits ---------------- */
|
||||
|
||||
router.post('/credits', (req, res) => {
|
||||
const { customerId, cents, reason, agent } = req.body ?? {};
|
||||
if (typeof customerId !== 'string' || !Number.isFinite(Number(cents))) {
|
||||
res.status(400).json({ error: 'customerId and cents required' });
|
||||
return;
|
||||
}
|
||||
const customer = customers.list().find((c) => c.id === customerId);
|
||||
if (!customer) {
|
||||
res.status(404).json({ error: 'customer not found' });
|
||||
return;
|
||||
}
|
||||
const delta = Math.trunc(Number(cents));
|
||||
const rec = credits.add({
|
||||
customerId,
|
||||
cents: delta,
|
||||
reason: typeof reason === 'string' ? reason : 'credit adjustment',
|
||||
agent: typeof agent === 'string' ? agent : 'admin',
|
||||
});
|
||||
customers.save({ ...customer, balanceCents: (customer.balanceCents ?? 0) + delta });
|
||||
res.status(201).json(rec);
|
||||
});
|
||||
|
||||
router.get('/credits', (req, res) => {
|
||||
const customerId = typeof req.query.customerId === 'string' ? req.query.customerId : undefined;
|
||||
res.json({ credits: credits.list(customerId) });
|
||||
});
|
||||
|
||||
router.get('/sales/quote/:id', (req, res) => {
|
||||
const customer = customers.list().find((c) => c.id === req.params.id);
|
||||
if (!customer) {
|
||||
res.status(404).json({ error: 'customer not found' });
|
||||
return;
|
||||
}
|
||||
const tier = store.getTiers().find((t) => t.id === customer.tierId);
|
||||
res.json({
|
||||
customerId: customer.id,
|
||||
name: customer.name,
|
||||
tierId: customer.tierId,
|
||||
multiplierOverride: customer.multiplierOverride ?? null,
|
||||
monthlyCreditCents: tier?.monthlyCreditCents ?? 0,
|
||||
listMultiplier: tier?.multiplier ?? 1,
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/exports/quickbooks.iif', (req, res) => {
|
||||
const period = typeof req.query.period === 'string' ? req.query.period : undefined;
|
||||
const invoices = accounting.invoices.list({ ...(period ? { period } : {}) });
|
||||
res
|
||||
.type('text/plain')
|
||||
.set('content-disposition', 'attachment; filename="zappier-quickbooks.iif"')
|
||||
.send(invoicesToQuickBooksIif(invoices));
|
||||
});
|
||||
|
||||
router.get('/exports/accounting.csv', (req, res) => {
|
||||
const period = typeof req.query.period === 'string' ? req.query.period : undefined;
|
||||
const invoices = accounting.invoices.list({ ...(period ? { period } : {}) });
|
||||
res
|
||||
.type('text/csv')
|
||||
.set('content-disposition', 'attachment; filename="zappier-accounting.csv"')
|
||||
.send(invoicesToAccountingCsv(invoices));
|
||||
});
|
||||
|
||||
/* ---------------- accounting: invoices ---------------- */
|
||||
|
||||
router.post('/invoices/generate', (req, res) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue