gigafibre-fsm/scripts/import-legacy-accounts.js
louispaulb 607ea54b5c refactor: reduce token count, DRY code, consolidate docs
Backend services:
- targo-hub: extract deepGetValue to helpers.js, DRY disconnect reasons
  lookup map, compact CAPABILITIES, consolidate vision.js prompts/schemas,
  extract dispatch scoring weights, trim section dividers across 9 files
- modem-bridge: extract getSession() helper (6 occurrences), resetIdleTimer(),
  consolidate DM query factory, fix duplicate username fill bug, trim headers
  (server.js -36%, tplink-session.js -47%, docker-compose.yml -57%)

Frontend:
- useWifiDiagnostic: extract THRESHOLDS const, split processDiagnostic into
  6 focused helpers (processOnlineStatus, processWanIPs, processRadios,
  processMeshNodes, processClients, checkRadioIssues)
- EquipmentDetail: merge duplicate ROLE_LABELS, remove verbose comments

Documentation (17 → 13 files, -1,400 lines):
- New consolidated README.md (architecture, services, dependencies, auth)
- Merge ECOSYSTEM-OVERVIEW into ARCHITECTURE.md
- Merge MIGRATION-PLAN + ARCHITECTURE-COMPARE + FIELD-GAP + CHANGELOG → MIGRATION.md
- Merge COMPETITIVE-ANALYSIS into PLATFORM-STRATEGY.md
- Update ROADMAP.md with current phase status
- Delete CONTEXT.md (absorbed into README)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 08:39:58 -04:00

261 lines
15 KiB
JavaScript

#!/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) })