Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
146 lines
5.0 KiB
TypeScript
146 lines
5.0 KiB
TypeScript
import type { Knex } from 'knex';
|
|
import { ensureDefaultContractForClient } from './defaultContract';
|
|
|
|
export type ClientBillingSettings = {
|
|
zeroDollarInvoiceHandling?: 'normal' | 'finalized';
|
|
suppressZeroDollarInvoices?: boolean;
|
|
enableCreditExpiration?: boolean;
|
|
creditExpirationDays?: number;
|
|
creditExpirationNotificationDays?: number[];
|
|
};
|
|
|
|
type DbClientBillingSettings = {
|
|
tenant: string;
|
|
client_id: string;
|
|
zero_dollar_invoice_handling: 'normal' | 'finalized';
|
|
suppress_zero_dollar_invoices: boolean;
|
|
enable_credit_expiration: boolean;
|
|
credit_expiration_days: number;
|
|
credit_expiration_notification_days: number[];
|
|
};
|
|
|
|
async function ensureClientBillingSettingsRowInTransaction(
|
|
trx: Knex.Transaction,
|
|
params: { tenant: string; clientId: string }
|
|
): Promise<{ created: boolean }> {
|
|
const existing = await trx('client_billing_settings')
|
|
.where({ tenant: params.tenant, client_id: params.clientId })
|
|
.select('client_id')
|
|
.first();
|
|
if (existing) {
|
|
await ensureDefaultContractForClient(trx, params);
|
|
return { created: false };
|
|
}
|
|
|
|
const defaults = await trx('default_billing_settings')
|
|
.where({ tenant: params.tenant })
|
|
.first()
|
|
.select(
|
|
'zero_dollar_invoice_handling',
|
|
'suppress_zero_dollar_invoices',
|
|
'credit_expiration_days',
|
|
'credit_expiration_notification_days',
|
|
'enable_credit_expiration'
|
|
);
|
|
|
|
await trx('client_billing_settings').insert({
|
|
tenant: params.tenant,
|
|
client_id: params.clientId,
|
|
zero_dollar_invoice_handling: defaults?.zero_dollar_invoice_handling ?? 'normal',
|
|
suppress_zero_dollar_invoices: defaults?.suppress_zero_dollar_invoices ?? false,
|
|
credit_expiration_days: defaults?.credit_expiration_days ?? 365,
|
|
credit_expiration_notification_days: defaults?.credit_expiration_notification_days ?? [30, 7, 1],
|
|
enable_credit_expiration: defaults?.enable_credit_expiration ?? true,
|
|
created_at: trx.fn.now(),
|
|
updated_at: trx.fn.now()
|
|
});
|
|
|
|
await ensureDefaultContractForClient(trx, params);
|
|
return { created: true };
|
|
}
|
|
|
|
export async function ensureClientBillingSettingsRow(
|
|
knexOrTrx: Knex | Knex.Transaction,
|
|
params: { tenant: string; clientId: string }
|
|
): Promise<{ created: boolean }> {
|
|
if (isKnexTransaction(knexOrTrx)) {
|
|
return ensureClientBillingSettingsRowInTransaction(knexOrTrx, params);
|
|
}
|
|
|
|
return (knexOrTrx as Knex).transaction(async (trx) =>
|
|
ensureClientBillingSettingsRowInTransaction(trx, params)
|
|
);
|
|
}
|
|
|
|
export async function getClientBillingSettings(
|
|
knexOrTrx: Knex | Knex.Transaction,
|
|
tenant: string,
|
|
clientId: string
|
|
): Promise<ClientBillingSettings | null> {
|
|
const row = await knexOrTrx<DbClientBillingSettings>('client_billing_settings')
|
|
.where({ tenant, client_id: clientId })
|
|
.first()
|
|
.select(
|
|
'zero_dollar_invoice_handling',
|
|
'suppress_zero_dollar_invoices',
|
|
'enable_credit_expiration',
|
|
'credit_expiration_days',
|
|
'credit_expiration_notification_days'
|
|
);
|
|
|
|
if (!row) return null;
|
|
|
|
return {
|
|
zeroDollarInvoiceHandling: row.zero_dollar_invoice_handling,
|
|
suppressZeroDollarInvoices: row.suppress_zero_dollar_invoices,
|
|
enableCreditExpiration: row.enable_credit_expiration,
|
|
creditExpirationDays: row.credit_expiration_days,
|
|
creditExpirationNotificationDays: row.credit_expiration_notification_days,
|
|
};
|
|
}
|
|
|
|
export async function updateClientBillingSettings(
|
|
knexOrTrx: Knex | Knex.Transaction,
|
|
tenant: string,
|
|
clientId: string,
|
|
settings: ClientBillingSettings | null
|
|
): Promise<void> {
|
|
if (!isKnexTransaction(knexOrTrx)) {
|
|
await (knexOrTrx as Knex).transaction(async (trx) => {
|
|
await updateClientBillingSettings(trx, tenant, clientId, settings);
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (settings === null) {
|
|
await knexOrTrx('client_billing_settings').where({ tenant, client_id: clientId }).del();
|
|
return;
|
|
}
|
|
|
|
const updates: Partial<DbClientBillingSettings> = {};
|
|
if (settings.zeroDollarInvoiceHandling !== undefined) {
|
|
updates.zero_dollar_invoice_handling = settings.zeroDollarInvoiceHandling;
|
|
}
|
|
if (settings.suppressZeroDollarInvoices !== undefined) {
|
|
updates.suppress_zero_dollar_invoices = settings.suppressZeroDollarInvoices;
|
|
}
|
|
if (settings.enableCreditExpiration !== undefined) {
|
|
updates.enable_credit_expiration = settings.enableCreditExpiration;
|
|
}
|
|
if (settings.creditExpirationDays !== undefined) {
|
|
updates.credit_expiration_days = settings.creditExpirationDays;
|
|
}
|
|
if (settings.creditExpirationNotificationDays !== undefined) {
|
|
updates.credit_expiration_notification_days = settings.creditExpirationNotificationDays;
|
|
}
|
|
|
|
await ensureClientBillingSettingsRow(knexOrTrx, { tenant, clientId });
|
|
await knexOrTrx('client_billing_settings')
|
|
.where({ tenant, client_id: clientId })
|
|
.update({ ...updates, updated_at: knexOrTrx.fn.now() });
|
|
}
|
|
|
|
function isKnexTransaction(knexOrTrx: Knex | Knex.Transaction): knexOrTrx is Knex.Transaction {
|
|
return typeof (knexOrTrx as any).commit === 'function' && typeof (knexOrTrx as any).rollback === 'function';
|
|
}
|