UNPKG

@debito/hippo-lib

Version:

Double-entry accounting library for CouchDB

280 lines (246 loc) • 10.7 kB
import config from '../config.js'; import Account from '../Account.js'; import COA from '../coa.js'; // Getter to access database instance function getDb() { return config.db; } /** * LedgerTemplate helper utility for creating accounts from templates * Provides standardized account creation patterns for vendors, customers, etc. */ class LedgerTemplate { /** * Creates an account using a template * @param {string} templateKey - Key of the template to use * @param {string} name - Name to use for the account (e.g., "ABC Corporation") * @param {Object} overrides - Optional field overrides {key, balance, hierarchy, tags, etc.} * @returns {Promise<Account>} The created account instance */ static async createAccountFromTemplate(templateKey, name, overrides = {}) { try { // Get the template const template = await this.getTemplate(templateKey); if (!template) { throw new Error(`Template '${templateKey}' not found`); } // Use override key if provided, otherwise generate from name and template pattern const key = overrides.key || this.generateKey(name, template.keyPattern); // Generate label from name const label = this.generateLabel(name, template.labelPattern); // Apply template values with overrides const accountData = { key, label, accountType: overrides.accountType || template.accountType, balance: overrides.balance !== undefined ? overrides.balance : template.defaultBalance, hierarchy: overrides.hierarchy || template.hierarchy, tags: overrides.tags || [...template.tags] }; // Validate that we have all required fields if (!accountData.key || !accountData.label || !accountData.accountType) { throw new Error('Template must specify key, label, and accountType'); } // Create the account const account = await Account.new( accountData.key, accountData.label, accountData.accountType, accountData.balance, accountData.hierarchy, accountData.tags ); return account; } catch (error) { throw new Error(`Failed to create account from template: ${error.message}`); } } /** * Gets a template by key from the stored templates * @param {string} templateKey - Key of the template * @returns {Promise<Object|null>} Template object or null if not found */ static async getTemplate(templateKey) { try { const templates = await this.listTemplates(); return templates.find(template => template.key === templateKey) || null; } catch (error) { throw new Error(`Failed to get template: ${error.message}`); } } /** * Lists all available templates * @returns {Promise<Array>} Array of template objects */ static async listTemplates() { try { // Get templates from COA settings const coa = await COA.loadCOAFromDB(); return coa.ledgerTemplates || []; } catch (error) { throw new Error(`Failed to list templates: ${error.message}`); } } /** * Adds a new custom template * @param {Object} templateData - Template definition * @returns {Promise<Object>} Updated COA document */ static async addTemplate(templateData) { try { // Validate template data const requiredFields = ['key', 'label', 'accountType', 'hierarchy', 'labelPattern', 'keyPattern']; for (const field of requiredFields) { if (!templateData[field]) { throw new Error(`Template must have ${field}`); } } // Get current COA const coa = await COA.loadCOAFromDB(); // Check if template already exists const existingTemplates = coa.ledgerTemplates || []; const existingTemplate = existingTemplates.find(t => t.key === templateData.key); if (existingTemplate) { throw new Error(`Template '${templateData.key}' already exists`); } // Add template with defaults const newTemplate = { key: templateData.key, label: templateData.label, description: templateData.description || '', accountType: templateData.accountType, hierarchy: templateData.hierarchy, tags: templateData.tags || [], labelPattern: templateData.labelPattern, keyPattern: templateData.keyPattern, defaultBalance: templateData.defaultBalance || 0 }; // Add to templates array if (!coa.ledgerTemplates) { coa.ledgerTemplates = []; } coa.ledgerTemplates.push(newTemplate); coa.updatedAt = new Date().toISOString(); // Update COA document const response = await getDb().insert(coa); coa._rev = response.rev; return coa; } catch (error) { throw new Error(`Failed to add template: ${error.message}`); } } /** * Removes a custom template (cannot remove default templates) * @param {string} templateKey - Key of template to remove * @returns {Promise<Object>} Updated COA document */ static async removeTemplate(templateKey) { try { // Get current COA const coa = await COA.loadCOAFromDB(); if (!coa.ledgerTemplates) { throw new Error('No templates found'); } // Find template const templateIndex = coa.ledgerTemplates.findIndex(t => t.key === templateKey); if (templateIndex === -1) { throw new Error(`Template '${templateKey}' not found`); } // Remove template coa.ledgerTemplates.splice(templateIndex, 1); coa.updatedAt = new Date().toISOString(); // Update COA document const response = await getDb().insert(coa); coa._rev = response.rev; return coa; } catch (error) { throw new Error(`Failed to remove template: ${error.message}`); } } /** * Generates a key from name using the pattern * @param {string} name - The name to convert * @param {string} pattern - The key pattern (e.g., "{name-slug}") * @returns {string} Generated key */ static generateKey(name, pattern) { // Convert name to slug format const slug = name .toLowerCase() .trim() .replace(/[^a-z0-9\s-]/g, '') // Remove special characters .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/-+/g, '-') // Replace multiple hyphens with single .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens // Replace pattern placeholders return pattern .replace('{name-slug}', slug) .replace('{name}', name); } /** * Generates a label from name using the pattern * @param {string} name - The name to use * @param {string} pattern - The label pattern (e.g., "{name}") * @returns {string} Generated label */ static generateLabel(name, pattern) { // Replace pattern placeholders return pattern .replace('{name}', name) .replace('{name-slug}', this.generateKey(name, '{name-slug}')); } /** * Utility method to print available templates to console * @param {Array} templates - Optional templates array, if not provided will fetch from DB */ static async printTemplates(templates = null) { try { if (!templates) { templates = await this.listTemplates(); } console.log('\n' + '='.repeat(60)); console.log('AVAILABLE LEDGER TEMPLATES'); console.log('='.repeat(60)); if (templates.length === 0) { console.log('No templates available'); return; } for (const template of templates) { console.log(`\nšŸ“‹ ${template.label.toUpperCase()} (${template.key})`); console.log(` Description: ${template.description || 'No description'}`); console.log(` Account Type: ${template.accountType}`); console.log(` Hierarchy: ${template.hierarchy}`); console.log(` Tags: [${template.tags.join(', ')}]`); console.log(` Example Key: ${this.generateKey('ABC Corporation', template.keyPattern)}`); console.log(` Example Label: ${this.generateLabel('ABC Corporation', template.labelPattern)}`); } console.log('\n' + '='.repeat(60)); } catch (error) { console.error('Failed to print templates:', error.message); } } /** * Utility method to demonstrate template usage * @param {string} templateKey - Template key to demonstrate * @param {string} exampleName - Example name to use */ static async demonstrateTemplate(templateKey, exampleName = 'Example Company') { try { const template = await this.getTemplate(templateKey); if (!template) { throw new Error(`Template '${templateKey}' not found`); } console.log(`\nšŸ” TEMPLATE DEMONSTRATION: ${template.label} (${template.key})`); console.log(`Using example name: "${exampleName}"`); console.log(`Generated key: ${this.generateKey(exampleName, template.keyPattern)}`); console.log(`Generated label: ${this.generateLabel(exampleName, template.labelPattern)}`); console.log(`Account type: ${template.accountType}`); console.log(`Hierarchy: ${template.hierarchy}`); console.log(`Tags: [${template.tags.join(', ')}]`); console.log(`Default balance: ${template.defaultBalance}`); } catch (error) { console.error('Template demonstration failed:', error.message); } } } export default LedgerTemplate;