Deepen Account + Stock modules significantly

Account (+400 LOC):
- Accounting reports: Trial Balance, Balance Sheet, Profit & Loss,
  Aged Receivable/Payable, General Ledger (SQL-based generation)
- account.report + account.report.line models
- Analytic accounting: account.analytic.plan, account.analytic.account,
  account.analytic.line models
- Bank statement matching (button_match with 1% tolerance)
- 6 default report definitions seeded
- 8 new actions + 12 new menus (Vendor Bills, Payments, Bank Statements,
  Reporting, Configuration with Chart of Accounts/Journals/Taxes)

Stock (+230 LOC):
- Stock valuation: price_unit + value (computed) on moves and quants
- Reorder rules: stock.warehouse.orderpoint with min/max qty,
  qty_on_hand compute from quants, action_replenish method
- Scrap: stock.scrap model with action_validate (quant transfer)
- Inventory adjustment: stock.quant.adjust wizard (set qty directly)
- Scrap location seeded

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc
2026-04-03 14:57:33 +02:00
parent e0d8bc81d3
commit d9171191af
6 changed files with 632 additions and 4 deletions

View File

@@ -319,6 +319,7 @@ func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) err
seedChartOfAccounts(ctx, tx, cfg)
seedStockData(ctx, tx)
seedViews(ctx, tx)
seedAccountReports(ctx, tx)
seedActions(ctx, tx)
seedMenus(ctx, tx)
@@ -489,7 +490,8 @@ func seedStockData(ctx context.Context, tx pgx.Tx) {
(3, 'Virtual Locations', 'Virtual Locations', 'view', true, NULL, 1),
(4, 'WH', 'Physical Locations/WH', 'internal', true, 1, 1),
(5, 'Customers', 'Partner Locations/Customers', 'customer', true, 2, 1),
(6, 'Vendors', 'Partner Locations/Vendors', 'supplier', true, 2, 1)
(6, 'Vendors', 'Partner Locations/Vendors', 'supplier', true, 2, 1),
(7, 'Scrap', 'Virtual Locations/Scrap', 'inventory', true, 3, 1)
ON CONFLICT (id) DO NOTHING`)
// Default warehouse (must come before picking types due to FK)
@@ -1064,6 +1066,16 @@ func seedActions(ctx context.Context, tx pgx.Tx) {
{111, "Menus", "ir.ui.menu", "list,form", "[]", "{}", "current", 80, 0, "base", "action_ir_ui_menu"},
{112, "Access Rights", "ir.model.access", "list,form", "[]", "{}", "current", 80, 0, "base", "action_ir_model_access"},
{113, "Record Rules", "ir.rule", "list,form", "[]", "{}", "current", 80, 0, "base", "action_ir_rule"},
// Accounting reports
{200, "Accounting Reports", "account.report", "list,form", "[]", "{}", "current", 80, 0, "account", "action_account_report"},
{201, "Vendor Bills", "account.move", "list,form", `[("move_type","in",["in_invoice","in_refund"])]`, `{"default_move_type":"in_invoice"}`, "current", 80, 0, "account", "action_move_in_invoice_type"},
{202, "Payments", "account.payment", "list,form", "[]", "{}", "current", 80, 0, "account", "action_account_payments"},
{203, "Bank Statements", "account.bank.statement", "list,form", "[]", "{}", "current", 80, 0, "account", "action_bank_statement_tree"},
{204, "Chart of Accounts", "account.account", "list,form", "[]", "{}", "current", 80, 0, "account", "action_account_form"},
{205, "Journals", "account.journal", "list,form", "[]", "{}", "current", 80, 0, "account", "action_account_journal_form"},
{206, "Taxes", "account.tax", "list,form", "[]", "{}", "current", 80, 0, "account", "action_tax_form"},
{207, "Analytic Accounts", "account.analytic.account", "list,form", "[]", "{}", "current", 80, 0, "account", "action_analytic_account_form"},
}
for _, a := range actions {
@@ -1115,6 +1127,20 @@ func seedMenus(ctx context.Context, tx pgx.Tx) {
// ── Invoicing ────────────────────────────────────────────
{2, "Invoicing", nil, 20, "ir.actions.act_window,2", "fa-book,#71639e,#FFFFFF", "account", "menu_finance"},
{20, "Invoices", p(2), 10, "ir.actions.act_window,2", "", "account", "menu_finance_invoices"},
{22, "Vendor Bills", p(2), 20, "ir.actions.act_window,201", "", "account", "menu_finance_vendor_bills"},
{23, "Payments", p(2), 30, "ir.actions.act_window,202", "", "account", "menu_finance_payments"},
{24, "Bank Statements", p(2), 40, "ir.actions.act_window,203", "", "account", "menu_finance_bank_statements"},
// Invoicing → Reporting
{25, "Reporting", p(2), 50, "", "", "account", "menu_finance_reporting"},
{250, "Accounting Reports", p(25), 10, "ir.actions.act_window,200", "", "account", "menu_finance_reports"},
// Invoicing → Configuration
{26, "Configuration", p(2), 90, "", "", "account", "menu_finance_configuration"},
{260, "Chart of Accounts", p(26), 10, "ir.actions.act_window,204", "", "account", "menu_finance_chart_of_accounts"},
{261, "Journals", p(26), 20, "ir.actions.act_window,205", "", "account", "menu_finance_journals"},
{262, "Taxes", p(26), 30, "ir.actions.act_window,206", "", "account", "menu_finance_taxes"},
{263, "Analytic Accounts", p(26), 40, "ir.actions.act_window,207", "", "account", "menu_finance_analytic_accounts"},
// ── Sales ────────────────────────────────────────────────
{3, "Sales", nil, 30, "ir.actions.act_window,3", "fa-bar-chart,#71639e,#FFFFFF", "sale", "menu_sale_root"},
@@ -1213,6 +1239,24 @@ func seedMenus(ctx context.Context, tx pgx.Tx) {
log.Printf("db: seeded %d menus with XML IDs", len(menus))
}
// seedAccountReports creates default accounting report definitions.
// Mirrors: odoo/addons/account_reports/data/account_report_data.xml
func seedAccountReports(ctx context.Context, tx pgx.Tx) {
log.Println("db: seeding accounting reports...")
tx.Exec(ctx, `
INSERT INTO account_report (id, name, report_type, sequence, active) VALUES
(1, 'Trial Balance', 'trial_balance', 10, true),
(2, 'Balance Sheet', 'balance_sheet', 20, true),
(3, 'Profit and Loss', 'profit_loss', 30, true),
(4, 'Aged Receivable', 'aged_receivable', 40, true),
(5, 'Aged Payable', 'aged_payable', 50, true),
(6, 'General Ledger', 'general_ledger', 60, true)
ON CONFLICT (id) DO NOTHING`)
log.Println("db: seeded 6 accounting reports")
}
// seedDemoData creates example records for testing.
func seedDemoData(ctx context.Context, tx pgx.Tx) {
log.Println("db: loading demo data...")