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

@@ -1414,10 +1414,11 @@ func initAccountBankStatement() {
)
// Bank statement line
orm.NewModel("account.bank.statement.line", orm.ModelOpts{
stLine := orm.NewModel("account.bank.statement.line", orm.ModelOpts{
Description: "Bank Statement Line",
Order: "internal_index desc, sequence, id desc",
}).AddFields(
})
stLine.AddFields(
orm.Many2one("statement_id", "account.bank.statement", orm.FieldOpts{String: "Statement"}),
orm.Many2one("move_id", "account.move", orm.FieldOpts{String: "Journal Entry", Required: true}),
orm.Many2one("journal_id", "account.journal", orm.FieldOpts{String: "Journal", Required: true}),
@@ -1434,7 +1435,50 @@ func initAccountBankStatement() {
orm.Integer("sequence", orm.FieldOpts{String: "Sequence"}),
orm.Char("internal_index", orm.FieldOpts{String: "Internal Index"}),
orm.Boolean("is_reconciled", orm.FieldOpts{String: "Is Reconciled"}),
orm.Many2one("move_line_id", "account.move.line", orm.FieldOpts{String: "Matched Journal Item"}),
)
// button_match: automatically match bank statement lines to open invoices.
// Mirrors: odoo/addons/account/models/account_bank_statement_line.py _find_or_create_bank_statement()
stLine.RegisterMethod("button_match", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
env := rs.Env()
for _, lineID := range rs.IDs() {
var amount float64
var partnerID *int64
err := env.Tx().QueryRow(env.Ctx(),
`SELECT COALESCE(amount::float8, 0), partner_id FROM account_bank_statement_line WHERE id = $1`, lineID,
).Scan(&amount, &partnerID)
if err != nil {
return nil, fmt.Errorf("account: read statement line %d: %w", lineID, err)
}
if partnerID == nil {
continue
}
// Find unreconciled move lines for this partner with matching amount
var matchLineID int64
env.Tx().QueryRow(env.Ctx(),
`SELECT l.id FROM account_move_line l
JOIN account_move m ON m.id = l.move_id AND m.state = 'posted'
JOIN account_account a ON a.id = l.account_id
WHERE l.partner_id = $1
AND a.account_type IN ('asset_receivable', 'liability_payable')
AND ABS(COALESCE(l.amount_residual::float8, 0)) BETWEEN ABS($2) * 0.99 AND ABS($2) * 1.01
AND COALESCE(l.amount_residual, 0) != 0
ORDER BY ABS(COALESCE(l.amount_residual::float8, 0) - ABS($2)) LIMIT 1`,
*partnerID, amount,
).Scan(&matchLineID)
if matchLineID > 0 {
// Mark as matched
env.Tx().Exec(env.Ctx(),
`UPDATE account_bank_statement_line SET move_line_id = $1, is_reconciled = true WHERE id = $2`,
matchLineID, lineID)
}
}
return true, nil
})
}
// -- Helper functions for argument parsing in business methods --