236 lines
8.5 KiB
JavaScript
236 lines
8.5 KiB
JavaScript
/**
|
|
* Invoice Module - Database
|
|
* Database initialization and tables for invoice module
|
|
*/
|
|
|
|
import { query } from '@hykocx/zen/database';
|
|
|
|
/**
|
|
* Check if a table exists in the database
|
|
* @param {string} tableName - Name of the table to check
|
|
* @returns {Promise<boolean>}
|
|
*/
|
|
async function tableExists(tableName) {
|
|
const result = await query(
|
|
`SELECT EXISTS (
|
|
SELECT FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_name = $1
|
|
)`,
|
|
[tableName]
|
|
);
|
|
return result.rows[0].exists;
|
|
}
|
|
|
|
/**
|
|
* Create all invoice-related tables (includes clients, items, and transactions)
|
|
* @returns {Promise<Object>}
|
|
*/
|
|
export async function createTables() {
|
|
const created = [];
|
|
const skipped = [];
|
|
|
|
// Import clients, items, categories, transactions and recurrences table creation
|
|
const { createClientsTable } = await import('../clients/db.js');
|
|
const { createItemsTable } = await import('./items/db.js');
|
|
const { createCategoriesTable } = await import('./categories/db.js');
|
|
const { createTransactionsTable } = await import('./transactions/db.js');
|
|
const { createRecurrencesTable } = await import('./recurrences/db.js');
|
|
|
|
// Create clients table first (dependency for invoices)
|
|
console.log('\n--- Clients (Invoice Module) ---');
|
|
const clientsResult = await createClientsTable();
|
|
if (clientsResult.created) created.push(clientsResult.tableName);
|
|
else skipped.push(clientsResult.tableName);
|
|
|
|
// Create categories table first (dependency for items)
|
|
console.log('\n--- Categories (Invoice Module) ---');
|
|
const categoriesResult = await createCategoriesTable();
|
|
if (categoriesResult.created) created.push(categoriesResult.tableName);
|
|
else skipped.push(categoriesResult.tableName);
|
|
|
|
// Create items table (depends on categories)
|
|
console.log('\n--- Items (Invoice Module) ---');
|
|
const itemsResult = await createItemsTable();
|
|
if (itemsResult.created) created.push(itemsResult.tableName);
|
|
else skipped.push(itemsResult.tableName);
|
|
|
|
// 1. Main invoices table
|
|
const invoicesTableExists = await tableExists('zen_invoices');
|
|
if (!invoicesTableExists) {
|
|
await query(`
|
|
CREATE TABLE zen_invoices (
|
|
id SERIAL PRIMARY KEY,
|
|
invoice_number VARCHAR(50) UNIQUE NOT NULL,
|
|
token VARCHAR(64) UNIQUE NOT NULL,
|
|
client_id INTEGER NOT NULL REFERENCES zen_clients(id) ON DELETE RESTRICT,
|
|
issue_date DATE NOT NULL,
|
|
due_date DATE NOT NULL,
|
|
subtotal DECIMAL(10, 2) NOT NULL DEFAULT 0,
|
|
tax_rate DECIMAL(5, 2) DEFAULT 0,
|
|
tax_amount DECIMAL(10, 2) DEFAULT 0,
|
|
total_amount DECIMAL(10, 2) NOT NULL,
|
|
paid_amount DECIMAL(10, 2) DEFAULT 0,
|
|
interest_amount DECIMAL(10, 2) DEFAULT 0,
|
|
interest_last_calculated DATE,
|
|
notes TEXT,
|
|
status VARCHAR(50) DEFAULT 'draft',
|
|
paid_at TIMESTAMPTZ,
|
|
first_reminder_days INTEGER DEFAULT 30,
|
|
is_recurring BOOLEAN DEFAULT false,
|
|
recurring_frequency VARCHAR(50),
|
|
recurring_end_date DATE,
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`);
|
|
|
|
await query(`CREATE INDEX idx_zen_invoices_invoice_number ON zen_invoices(invoice_number)`);
|
|
await query(`CREATE INDEX idx_zen_invoices_token ON zen_invoices(token)`);
|
|
await query(`CREATE INDEX idx_zen_invoices_client_id ON zen_invoices(client_id)`);
|
|
await query(`CREATE INDEX idx_zen_invoices_status ON zen_invoices(status)`);
|
|
await query(`CREATE INDEX idx_zen_invoices_due_date ON zen_invoices(due_date)`);
|
|
await query(`CREATE INDEX idx_zen_invoices_interest_calc ON zen_invoices(status, due_date, interest_last_calculated) WHERE status IN ('sent', 'partial', 'overdue')`);
|
|
|
|
created.push('zen_invoices');
|
|
console.log('✓ Created table: zen_invoices');
|
|
} else {
|
|
skipped.push('zen_invoices');
|
|
console.log('- Table already exists: zen_invoices');
|
|
}
|
|
|
|
// 2. Invoice items table
|
|
const itemsTableExists = await tableExists('zen_invoice_items');
|
|
if (!itemsTableExists) {
|
|
await query(`
|
|
CREATE TABLE zen_invoice_items (
|
|
id SERIAL PRIMARY KEY,
|
|
invoice_id INTEGER NOT NULL REFERENCES zen_invoices(id) ON DELETE CASCADE,
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
quantity DECIMAL(10, 2) NOT NULL,
|
|
unit_price DECIMAL(10, 2) NOT NULL,
|
|
total DECIMAL(10, 2) NOT NULL,
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`);
|
|
|
|
await query(`CREATE INDEX idx_zen_invoice_items_invoice_id ON zen_invoice_items(invoice_id)`);
|
|
|
|
created.push('zen_invoice_items');
|
|
console.log('✓ Created table: zen_invoice_items');
|
|
} else {
|
|
skipped.push('zen_invoice_items');
|
|
console.log('- Table already exists: zen_invoice_items');
|
|
}
|
|
|
|
// 3. Invoice reminders table
|
|
const remindersTableExists = await tableExists('zen_invoice_reminders');
|
|
if (!remindersTableExists) {
|
|
await query(`
|
|
CREATE TABLE zen_invoice_reminders (
|
|
id SERIAL PRIMARY KEY,
|
|
invoice_id INTEGER NOT NULL REFERENCES zen_invoices(id) ON DELETE CASCADE,
|
|
reminder_type VARCHAR(50) NOT NULL,
|
|
days_before INTEGER NOT NULL,
|
|
sent_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`);
|
|
|
|
await query(`CREATE INDEX idx_zen_invoice_reminders_invoice_id ON zen_invoice_reminders(invoice_id)`);
|
|
await query(`CREATE INDEX idx_zen_invoice_reminders_sent_at ON zen_invoice_reminders(sent_at)`);
|
|
|
|
created.push('zen_invoice_reminders');
|
|
console.log('✓ Created table: zen_invoice_reminders');
|
|
} else {
|
|
skipped.push('zen_invoice_reminders');
|
|
console.log('- Table already exists: zen_invoice_reminders');
|
|
}
|
|
|
|
// 4. Interac credentials table
|
|
const interacTableExists = await tableExists('zen_invoice_interac');
|
|
if (!interacTableExists) {
|
|
await query(`
|
|
CREATE TABLE zen_invoice_interac (
|
|
id SERIAL PRIMARY KEY,
|
|
client_id INTEGER NOT NULL UNIQUE REFERENCES zen_clients(id) ON DELETE CASCADE,
|
|
security_question VARCHAR(255) NOT NULL,
|
|
security_answer VARCHAR(255) NOT NULL,
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`);
|
|
|
|
await query(`CREATE INDEX idx_zen_invoice_interac_client_id ON zen_invoice_interac(client_id)`);
|
|
|
|
created.push('zen_invoice_interac');
|
|
console.log('✓ Created table: zen_invoice_interac');
|
|
} else {
|
|
skipped.push('zen_invoice_interac');
|
|
console.log('- Table already exists: zen_invoice_interac');
|
|
}
|
|
|
|
// 5. Transactions table (depends on invoices)
|
|
console.log('\n--- Transactions (Invoice Module) ---');
|
|
const transactionsResult = await createTransactionsTable();
|
|
if (transactionsResult.created) created.push(transactionsResult.tableName);
|
|
else skipped.push(transactionsResult.tableName);
|
|
|
|
// 6. Recurrences table (depends on clients and invoices)
|
|
console.log('\n--- Recurrences (Invoice Module) ---');
|
|
const recurrencesResult = await createRecurrencesTable();
|
|
if (recurrencesResult.created) created.push(recurrencesResult.tableName);
|
|
else skipped.push(recurrencesResult.tableName);
|
|
|
|
return { created, skipped };
|
|
}
|
|
|
|
/**
|
|
* Drop all invoice-related tables (includes clients, items, and transactions)
|
|
* @returns {Promise<void>}
|
|
*/
|
|
export async function dropTables() {
|
|
// Import drop functions
|
|
const { dropClientsTable } = await import('../clients/db.js');
|
|
const { dropItemsTable } = await import('./items/db.js');
|
|
const { dropCategoriesTable } = await import('./categories/db.js');
|
|
const { dropTransactionsTable } = await import('./transactions/db.js');
|
|
const { dropRecurrencesTable } = await import('./recurrences/db.js');
|
|
|
|
// Drop recurrences first (depends on invoices)
|
|
await dropRecurrencesTable();
|
|
|
|
// Drop transactions (depends on invoices)
|
|
await dropTransactionsTable();
|
|
|
|
// Drop invoice tables
|
|
const tables = [
|
|
'zen_invoice_reminders',
|
|
'zen_invoice_interac',
|
|
'zen_invoice_items',
|
|
'zen_invoices'
|
|
];
|
|
|
|
for (const tableName of tables) {
|
|
const exists = await tableExists(tableName);
|
|
if (exists) {
|
|
await query(`DROP TABLE IF EXISTS ${tableName} CASCADE`);
|
|
console.log(`✓ Dropped table: ${tableName}`);
|
|
}
|
|
}
|
|
|
|
// Drop items (depends on categories)
|
|
await dropItemsTable();
|
|
|
|
// Drop categories
|
|
await dropCategoriesTable();
|
|
|
|
// Drop clients last
|
|
await dropClientsTable();
|
|
}
|
|
|
|
// Backward compatibility aliases
|
|
export const createInvoiceTables = createTables;
|
|
export const dropInvoiceTables = dropTables;
|