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>
261 lines
15 KiB
JavaScript
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) })
|