feat(nexa_coa_setup): batch-reclass 200 historical 411000 lines
All 123 historical out_invoices ($249k revenue, 2022-2023 mostly) had
been posted to the generic l10n_ca '411000 Inside Sales' account, since
the module they predated proper product setup and had no SKU attached.
Keyword-rule script (scripts/reclass_historical_411000.py) routes each
line by description text to the correct Nexa account:
Pattern -> Target account Lines Revenue
Computer & Server Maintenance, Server 4030 Support & 165 $236,259
Backup & Monitoring, Membership Fee Maintenance Contracts
[CUSTCOMP], Custom Computer, HP Desk, 4320 Hardware Resale 24 ~$8,200
Server 2019, Server Rack, 16 Port
POE, CPU:, Cleaning Supplies
ONSITE-, OFFSITE-, Server Setup, 4230 Technical Support 11 ~$3,200
Wiring for — Per-incident/Hourly
Match rate: 200/200 = 100%. Verified the legacy 411000 account now has
zero open-invoice lines.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
62
nexa_coa_setup/scripts/reclass_historical_411000.py
Normal file
62
nexa_coa_setup/scripts/reclass_historical_411000.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""Reclassify all 200 lines on legacy 411000 to proper Nexa accounts based on
|
||||
keyword rules. Run on prod via odoo-shell."""
|
||||
|
||||
acct_4030 = env['account.account'].search([('code', '=', '4030')], limit=1).id # Support & Maintenance
|
||||
acct_4230 = env['account.account'].search([('code', '=', '4230')], limit=1).id # Tech Support hourly
|
||||
acct_4320 = env['account.account'].search([('code', '=', '4320')], limit=1).id # Hardware Resale
|
||||
legacy_acct = env['account.account'].with_context(active_test=False).search([('code', '=', '411000')], limit=1).id
|
||||
|
||||
# (priority, regex pattern (case-insensitive), target_account_id, label)
|
||||
import re
|
||||
RULES = [
|
||||
('Computer & Server Maintenance', acct_4030, 'Support & Maintenance'),
|
||||
('Server Backup & Monitoring', acct_4030, 'Support & Maintenance'),
|
||||
('Membership Fee', acct_4030, 'Support & Maintenance (membership)'),
|
||||
('[CUSTCOMP]', acct_4320, 'Hardware Resale (custom PC)'),
|
||||
('Custom Computer', acct_4320, 'Hardware Resale (custom PC)'),
|
||||
('ustom Computer', acct_4320, 'Hardware Resale (typo'), # the 'ustom Computer' typo entry
|
||||
('HP Desk Computer', acct_4320, 'Hardware Resale (HP desktop)'),
|
||||
('Server 2019', acct_4320, 'Hardware Resale (Windows Server license)'),
|
||||
('Server Rack', acct_4320, 'Hardware Resale (rack)'),
|
||||
('16 Port POE', acct_4320, 'Hardware Resale (switch)'),
|
||||
('CPU:', acct_4320, 'Hardware Resale (custom build)'),
|
||||
('Cleaning Supplies', acct_4320, 'Hardware Resale (consumables)'),
|
||||
('ONSITE-', acct_4230, 'Tech Support — onsite'),
|
||||
('OFFSITE-', acct_4230, 'Tech Support — offsite'),
|
||||
('Onsite Sever Setup', acct_4230, 'Tech Support — setup'),
|
||||
('Server Setup', acct_4230, 'Tech Support — setup'),
|
||||
('Wiring for', acct_4230, 'Tech Support — installation'),
|
||||
]
|
||||
|
||||
# Find all lines on 411000
|
||||
env.cr.execute("""
|
||||
SELECT aml.id, aml.name, aml.credit
|
||||
FROM account_move_line aml
|
||||
JOIN account_move m ON m.id = aml.move_id
|
||||
WHERE aml.account_id = %s AND m.move_type = 'out_invoice'
|
||||
""", (legacy_acct,))
|
||||
lines = env.cr.fetchall()
|
||||
|
||||
bucket = {acct_4030: 0, acct_4230: 0, acct_4320: 0}
|
||||
unmatched = []
|
||||
for line_id, line_name, credit in lines:
|
||||
name = (line_name or '').strip()
|
||||
matched = False
|
||||
for pattern, target, label in RULES:
|
||||
if pattern.lower() in name.lower():
|
||||
env.cr.execute("UPDATE account_move_line SET account_id = %s WHERE id = %s",
|
||||
(target, line_id))
|
||||
bucket[target] += 1
|
||||
matched = True
|
||||
break
|
||||
if not matched:
|
||||
unmatched.append((line_id, name[:60], credit))
|
||||
|
||||
print(f"Reclassified {bucket[acct_4030]} lines -> 4030 Support & Maintenance")
|
||||
print(f"Reclassified {bucket[acct_4230]} lines -> 4230 Tech Support — Hourly")
|
||||
print(f"Reclassified {bucket[acct_4320]} lines -> 4320 Hardware Resale")
|
||||
print(f"Unmatched: {len(unmatched)}")
|
||||
for u in unmatched[:20]:
|
||||
print(f" id={u[0]} amount={u[2]} name={u[1]!r}")
|
||||
env.cr.commit()
|
||||
print(">>> done <<<")
|
||||
Reference in New Issue
Block a user