package models import ( "fmt" "odoo-go/pkg/orm" ) // TaxResult holds the result of computing a tax on an amount. type TaxResult struct { TaxID int64 TaxName string Amount float64 // tax amount Base float64 // base amount (before tax) AccountID int64 // account to post tax to } // ComputeTax calculates tax for a given base amount. // Mirrors: odoo/addons/account/models/account_tax.py AccountTax._compute_amount() // // Supports amount_type: percent, fixed, division, group. // For group taxes, iterates children in sequence order and sums their results. func ComputeTax(env *orm.Environment, taxID int64, baseAmount float64) (*TaxResult, error) { var name string var amount float64 var amountType string var priceInclude bool err := env.Tx().QueryRow(env.Ctx(), `SELECT name, amount, amount_type, COALESCE(price_include, false) FROM account_tax WHERE id = $1`, taxID, ).Scan(&name, &amount, &amountType, &priceInclude) if err != nil { return nil, err } // Handle group taxes: iterate child taxes and sum their amounts. // Mirrors: odoo/addons/account/models/account_tax.py AccountTax._compute_amount() group branch if amountType == "group" { return computeGroupTax(env, taxID, name, baseAmount) } var taxAmount float64 switch amountType { case "percent": if priceInclude { taxAmount = baseAmount - (baseAmount / (1 + amount/100)) } else { taxAmount = baseAmount * amount / 100 } case "fixed": taxAmount = amount case "division": if priceInclude { taxAmount = baseAmount - (baseAmount / (1 + amount/100)) } else { taxAmount = baseAmount * amount / 100 } } accountID := findTaxAccount(env, taxID) return &TaxResult{ TaxID: taxID, TaxName: name, Amount: taxAmount, Base: baseAmount, AccountID: accountID, }, nil } // ComputeTaxes calculates multiple taxes on a base amount, returning all results. // Mirrors: odoo/addons/account/models/account_tax.py AccountTax.compute_all() func ComputeTaxes(env *orm.Environment, taxIDs []int64, baseAmount float64) ([]*TaxResult, error) { var results []*TaxResult for _, taxID := range taxIDs { result, err := ComputeTax(env, taxID, baseAmount) if err != nil { return nil, fmt.Errorf("account: compute tax %d: %w", taxID, err) } results = append(results, result) } return results, nil } // computeGroupTax handles amount_type='group': reads child taxes and sums their amounts. // Mirrors: odoo/addons/account/models/account_tax.py AccountTax._compute_amount() for groups func computeGroupTax(env *orm.Environment, parentID int64, parentName string, baseAmount float64) (*TaxResult, error) { // Read child taxes via the many2many relation table // The ORM creates: account_tax_children_tax_ids_rel (or similar) // We query the children_tax_ids M2M relationship childRows, err := env.Tx().Query(env.Ctx(), `SELECT ct.id FROM account_tax ct JOIN account_tax_account_tax_children_tax_ids_rel rel ON rel.account_tax_id2 = ct.id WHERE rel.account_tax_id1 = $1 ORDER BY ct.sequence, ct.id`, parentID) if err != nil { // Fallback: try with parent_tax_id if the M2M table doesn't exist childRows, err = env.Tx().Query(env.Ctx(), `SELECT id FROM account_tax WHERE parent_tax_id = $1 ORDER BY sequence, id`, parentID) if err != nil { return &TaxResult{ TaxID: parentID, TaxName: parentName, Amount: 0, Base: baseAmount, }, nil } } defer childRows.Close() var totalTax float64 var lastAccountID int64 currentBase := baseAmount for childRows.Next() { var childID int64 if err := childRows.Scan(&childID); err != nil { continue } childResult, err := ComputeTax(env, childID, currentBase) if err != nil { continue } totalTax += childResult.Amount if childResult.AccountID > 0 { lastAccountID = childResult.AccountID } // Check if this child tax affects the base of subsequent taxes var includeBase bool env.Tx().QueryRow(env.Ctx(), `SELECT COALESCE(include_base_amount, false) FROM account_tax WHERE id = $1`, childID, ).Scan(&includeBase) if includeBase { currentBase += childResult.Amount } } if lastAccountID == 0 { lastAccountID = findTaxAccount(env, parentID) } return &TaxResult{ TaxID: parentID, TaxName: parentName, Amount: totalTax, Base: baseAmount, AccountID: lastAccountID, }, nil } // findTaxAccount looks up the account for a tax from its repartition lines. func findTaxAccount(env *orm.Environment, taxID int64) int64 { var accountID int64 env.Tx().QueryRow(env.Ctx(), `SELECT COALESCE(account_id, 0) FROM account_tax_repartition_line WHERE tax_id = $1 AND repartition_type = 'tax' AND document_type = 'invoice' LIMIT 1`, taxID, ).Scan(&accountID) // Fallback: use the USt account 1776 (SKR03) if accountID == 0 { env.Tx().QueryRow(env.Ctx(), `SELECT id FROM account_account WHERE code = '1776' LIMIT 1`, ).Scan(&accountID) } return accountID }