#!/usr/bin/env node /** * Import legacy chart of accounts into ERPNext. * Maps the 37 accounts from facturation.targo.ca's compta_comptes table * to the existing ERPNext account tree structure. * * Usage: node scripts/import-legacy-accounts.js * Requires: ERP_URL and ERP_TOKEN env vars (or defaults to targo-hub's config) */ 'use strict' const ERP_URL = process.env.ERP_URL || 'https://erp.gigafibre.ca' const ERP_TOKEN = process.env.ERP_TOKEN || '' const COMPANY = 'TARGO' const ABBR = 'T' if (!ERP_TOKEN) { console.error('ERP_TOKEN env var required. Get it from ERPNext > Settings > API Access') console.error('Or: ssh root@96.125.196.67 "docker exec erpnext-backend bench --site erp.gigafibre.ca show-config" | grep api') process.exit(1) } async function erpFetch(path, opts = {}) { const url = ERP_URL + path const headers = { 'Authorization': 'token ' + ERP_TOKEN, 'Content-Type': 'application/json', ...opts.headers, } const res = await fetch(url, { ...opts, headers }) const data = await res.json().catch(() => null) return { status: res.status, data } } async function accountExists(accountName) { const res = await erpFetch(`/api/resource/Account/${encodeURIComponent(accountName)}`) return res.status === 200 } async function createAccount(doc) { const name = `${doc.account_number ? doc.account_number + ' - ' : ''}${doc.account_name} - ${ABBR}` // Check if already exists if (await accountExists(name)) { console.log(` ✓ EXISTS: ${name}`) return { existed: true, name } } const payload = { doctype: 'Account', company: COMPANY, ...doc, } const res = await erpFetch('/api/resource/Account', { method: 'POST', body: JSON.stringify(payload), }) if (res.status === 200 || res.status === 201) { console.log(` ✅ CREATED: ${name}`) return { created: true, name: res.data?.data?.name || name } } else { const err = res.data?.exc || res.data?._server_messages || res.data?.message || JSON.stringify(res.data) console.error(` ❌ FAILED: ${name} — ${res.status} — ${err}`) return { error: true, name, status: res.status, message: err } } } // ── Account definitions mapped to ERPNext parent groups ────────────── // First, create parent groups that don't exist yet const PARENT_GROUPS = [ // Revenue sub-groups under "Revenus de ventes - T" { account_name: 'Revenus Télécom', parent_account: `Revenus de ventes - ${ABBR}`, root_type: 'Income', is_group: 1, account_type: '', }, { account_name: 'Revenus Internet & Installation', parent_account: `Revenus de ventes - ${ABBR}`, root_type: 'Income', is_group: 1, account_type: '', }, { account_name: 'Revenus Services TI', parent_account: `Revenus de ventes - ${ABBR}`, root_type: 'Income', is_group: 1, account_type: '', }, { account_name: 'Revenus Divers', parent_account: `Revenus de ventes - ${ABBR}`, root_type: 'Income', is_group: 1, account_type: '', }, // Expense sub-group for legacy expense accounts { account_name: 'Frais divers legacy', parent_account: `Frais fixes - ${ABBR}`, root_type: 'Expense', is_group: 1, account_type: '', }, // Taxes sub-group under Duties and Taxes { account_name: 'Taxes de vente legacy', parent_account: `Duties and Taxes - ${ABBR}`, root_type: 'Liability', is_group: 1, account_type: '', }, ] // ── ASSET accounts (1000-1205) ────────────────────────────────────── const ASSET_ACCOUNTS = [ { account_number: '1000', account_name: 'Encaisse PPA', parent_account: `Encaisse - ${ABBR}`, root_type: 'Asset', account_type: 'Cash', is_group: 0 }, { account_number: '1001', account_name: 'Encaisse Paiement direct', parent_account: `Encaisse - ${ABBR}`, root_type: 'Asset', account_type: 'Cash', is_group: 0 }, { account_number: '1002', account_name: 'Encaisse Carte de crédit', parent_account: `Encaisse - ${ABBR}`, root_type: 'Asset', account_type: 'Cash', is_group: 0 }, { account_number: '1003', account_name: 'Encaisse Comptant/Chèques', parent_account: `Encaisse - ${ABBR}`, root_type: 'Asset', account_type: 'Cash', is_group: 0 }, { account_number: '1004', account_name: 'Encaisse Ajustements', parent_account: `Encaisse - ${ABBR}`, root_type: 'Asset', account_type: 'Cash', is_group: 0 }, { account_number: '1005', account_name: 'Encaissement de crédit', parent_account: `Encaisse - ${ABBR}`, root_type: 'Asset', account_type: 'Cash', is_group: 0 }, { account_number: '1050', account_name: 'Petite caisse - bureau', parent_account: `Petite caisse - ${ABBR}`, root_type: 'Asset', account_type: 'Cash', is_group: 0 }, { account_number: '1060', account_name: 'Petite caisse - service à la clientèle', parent_account: `Petite caisse - ${ABBR}`, root_type: 'Asset', account_type: 'Cash', is_group: 0 }, { account_number: '1100', account_name: 'Caisse populaire', parent_account: `Banque - ${ABBR}`, root_type: 'Asset', account_type: 'Bank', is_group: 0 }, { account_number: '1200', account_name: 'Comptes à recevoir legacy', parent_account: `Comptes à recevoir - ${ABBR}`, root_type: 'Asset', account_type: 'Receivable', is_group: 0 }, { account_number: '1205', account_name: 'Provisions mauvaises créances', parent_account: `Comptes à recevoir - ${ABBR}`, root_type: 'Asset', account_type: '', is_group: 0 }, ] // ── LIABILITY accounts (2110-2355) ────────────────────────────────── const LIABILITY_ACCOUNTS = [ { account_number: '2110', account_name: 'Excédent', parent_account: `Passif à court terme - ${ABBR}`, root_type: 'Liability', account_type: '', is_group: 0 }, { account_number: '2115', account_name: 'Dépôt/Acompte client', parent_account: `Passif à court terme - ${ABBR}`, root_type: 'Liability', account_type: '', is_group: 0 }, { account_number: '2300', account_name: 'TPS perçue', parent_account: `Taxes de vente legacy - ${ABBR}`, root_type: 'Liability', account_type: 'Tax', is_group: 0 }, { account_number: '2305', account_name: 'TPS payée', parent_account: `Taxes à recevoir - ${ABBR}`, root_type: 'Asset', account_type: 'Tax', is_group: 0 }, { account_number: '2350', account_name: 'TVQ perçue', parent_account: `Taxes de vente legacy - ${ABBR}`, root_type: 'Liability', account_type: 'Tax', is_group: 0 }, { account_number: '2355', account_name: 'TVQ payée', parent_account: `Taxes à recevoir - ${ABBR}`, root_type: 'Asset', account_type: 'Tax', is_group: 0 }, ] // ── INCOME/REVENUE accounts (4000-4300) ───────────────────────────── const INCOME_ACCOUNTS = [ // Telecom & TV { account_number: '4015', account_name: 'Téléphonie', parent_account: `Revenus Télécom - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4019', account_name: 'Mensualité télévision', parent_account: `Revenus Télécom - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4018', account_name: 'Équipement télé', parent_account: `Revenus Télécom - ${ABBR}`, root_type: 'Income', is_group: 0 }, // Internet & Installation (Fibre + Sans-fil) { account_number: '4020', account_name: 'Mensualité fibre', parent_account: `Revenus Internet & Installation - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4017', account_name: 'Installation et équipement fibre', parent_account: `Revenus Internet & Installation - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4021', account_name: 'Mensualité Internet Haute-Vitesse sans fil', parent_account: `Revenus Internet & Installation - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4022', account_name: 'Installation Internet Haute-Vitesse sans fil', parent_account: `Revenus Internet & Installation - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4023', account_name: 'Équipement Internet Haute-Vitesse sans fil', parent_account: `Revenus Internet & Installation - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4024', account_name: 'Téléchargement supplémentaire', parent_account: `Revenus Internet & Installation - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4027', account_name: 'IP Fixe', parent_account: `Revenus Internet & Installation - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4028', account_name: "Frais d'activation", parent_account: `Revenus Internet & Installation - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4025', account_name: 'Garantie prolongée', parent_account: `Revenus Internet & Installation - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4026', account_name: 'Section de Tour', parent_account: `Revenus Internet & Installation - ${ABBR}`, root_type: 'Income', is_group: 0 }, // Services TI { account_number: '4031', account_name: 'Création de site Internet', parent_account: `Revenus Services TI - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4041', account_name: 'Nom de Domaine', parent_account: `Revenus Services TI - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4042', account_name: 'Hébergement', parent_account: `Revenus Services TI - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4051', account_name: 'Système Informatique', parent_account: `Revenus Services TI - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4052', account_name: 'Revenu - Service Informatique', parent_account: `Revenus Services TI - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4054', account_name: 'Pièces Informatiques', parent_account: `Revenus Services TI - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4001', account_name: 'Location espace cloud', parent_account: `Revenus Services TI - ${ABBR}`, root_type: 'Income', is_group: 0 }, // Divers { account_number: '4000', account_name: 'Revenus autres', parent_account: `Revenus Divers - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4010', account_name: 'Honoraires', parent_account: `Revenus Divers - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4016', account_name: 'Saisonniers (coupons-maraîchers)', parent_account: `Revenus Divers - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4106', account_name: 'Déplacement/temps technicien', parent_account: `Revenus Divers - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4250', account_name: 'Intérêts et frais divers', parent_account: `Revenus Divers - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4260', account_name: 'Frais divers taxables', parent_account: `Revenus Divers - ${ABBR}`, root_type: 'Income', is_group: 0 }, { account_number: '4300', account_name: 'Temporaire', parent_account: `Revenus Divers - ${ABBR}`, root_type: 'Income', is_group: 0 }, ] // ── EXPENSE accounts (7575-7910) ──────────────────────────────────── const EXPENSE_ACCOUNTS = [ { account_number: '7575', account_name: 'Frais PayPal', parent_account: `Frais divers legacy - ${ABBR}`, root_type: 'Expense', is_group: 0 }, { account_number: '7900', account_name: 'Mauvaises créances', parent_account: `Frais divers legacy - ${ABBR}`, root_type: 'Expense', is_group: 0 }, { account_number: '7905', account_name: 'Frais de recouvrement', parent_account: `Frais divers legacy - ${ABBR}`, root_type: 'Expense', is_group: 0 }, { account_number: '7910', account_name: 'Déficit ou surplus de caisse', parent_account: `Frais divers legacy - ${ABBR}`, root_type: 'Expense', is_group: 0 }, ] async function main() { console.log('═══════════════════════════════════════════════════') console.log(' Import Legacy Chart of Accounts into ERPNext') console.log(` Company: ${COMPANY} (${ABBR})`) console.log(` ERP: ${ERP_URL}`) console.log('═══════════════════════════════════════════════════') // Test connection const test = await erpFetch('/api/resource/Company/' + encodeURIComponent(COMPANY)) if (test.status !== 200) { console.error('Cannot connect to ERPNext or company not found. Status:', test.status) process.exit(1) } console.log(`\n✓ Connected to ERPNext — Company: ${test.data?.data?.name}\n`) let created = 0, existed = 0, errors = 0 // Step 1: Create parent groups console.log('── Creating parent groups ────────────────────────') for (const grp of PARENT_GROUPS) { const r = await createAccount(grp) if (r.created) created++ else if (r.existed) existed++ else errors++ } // Step 2: Asset accounts console.log('\n── Asset accounts (1000-1205) ────────────────────') for (const acc of ASSET_ACCOUNTS) { const r = await createAccount(acc) if (r.created) created++ else if (r.existed) existed++ else errors++ } // Step 3: Liability accounts console.log('\n── Liability accounts (2110-2355) ────────────────') for (const acc of LIABILITY_ACCOUNTS) { const r = await createAccount(acc) if (r.created) created++ else if (r.existed) existed++ else errors++ } // Step 4: Income/Revenue accounts console.log('\n── Revenue accounts (4000-4300) ──────────────────') for (const acc of INCOME_ACCOUNTS) { const r = await createAccount(acc) if (r.created) created++ else if (r.existed) existed++ else errors++ } // Step 5: Expense accounts console.log('\n── Expense accounts (7575-7910) ──────────────────') for (const acc of EXPENSE_ACCOUNTS) { const r = await createAccount(acc) if (r.created) created++ else if (r.existed) existed++ else errors++ } console.log('\n═══════════════════════════════════════════════════') console.log(` Results: ${created} created, ${existed} already existed, ${errors} errors`) console.log('═══════════════════════════════════════════════════') } main().catch(e => { console.error('Fatal:', e); process.exit(1) })