package models import ( "fmt" "math" "odoo-go/pkg/orm" ) // initAccountCashRounding registers account.cash.rounding — rounding rules for invoices. // Mirrors: odoo/addons/account/models/account_cash_rounding.py // // Used to round invoice totals to the nearest increment (e.g. 0.05 CHF in Switzerland). // Two strategies: // - "biggest_tax": add the rounding difference to the biggest tax line // - "add_invoice_line": add a separate rounding line func initAccountCashRounding() { m := orm.NewModel("account.cash.rounding", orm.ModelOpts{ Description: "Cash Rounding", Order: "name", }) m.AddFields( orm.Char("name", orm.FieldOpts{String: "Name", Required: true, Translate: true}), orm.Float("rounding", orm.FieldOpts{ String: "Rounding Precision", Required: true, Default: 0.01, Help: "Represent the non-zero value smallest coinage (e.g. 0.05)", }), orm.Selection("strategy", []orm.SelectionItem{ {Value: "biggest_tax", Label: "Modify tax amount"}, {Value: "add_invoice_line", Label: "Add a rounding line"}, }, orm.FieldOpts{String: "Rounding Strategy", Default: "add_invoice_line", Required: true}), orm.Many2one("profit_account_id", "account.account", orm.FieldOpts{ String: "Profit Account", Help: "Account for the rounding line when strategy is add_invoice_line (rounding up)", }), orm.Many2one("loss_account_id", "account.account", orm.FieldOpts{ String: "Loss Account", Help: "Account for the rounding line when strategy is add_invoice_line (rounding down)", }), orm.Selection("rounding_method", []orm.SelectionItem{ {Value: "UP", Label: "Up"}, {Value: "DOWN", Label: "Down"}, {Value: "HALF-UP", Label: "Half-Up"}, }, orm.FieldOpts{String: "Rounding Method", Default: "HALF-UP", Required: true}), orm.Many2one("company_id", "res.company", orm.FieldOpts{String: "Company"}), ) // compute_rounding: round an amount according to this rounding rule. // Returns the rounded amount and the difference. // Mirrors: odoo/addons/account/models/account_cash_rounding.py round() m.RegisterMethod("compute_rounding", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) { if len(args) < 1 { return nil, fmt.Errorf("account: compute_rounding requires an amount argument") } env := rs.Env() roundingID := rs.IDs()[0] amount, ok := toFloat(args[0]) if !ok { return nil, fmt.Errorf("account: invalid amount for rounding") } var precision float64 var method string env.Tx().QueryRow(env.Ctx(), `SELECT COALESCE(rounding, 0.01), COALESCE(rounding_method, 'HALF-UP') FROM account_cash_rounding WHERE id = $1`, roundingID, ).Scan(&precision, &method) if precision <= 0 { precision = 0.01 } var rounded float64 switch method { case "UP": rounded = math.Ceil(amount/precision) * precision case "DOWN": rounded = math.Floor(amount/precision) * precision default: // HALF-UP rounded = math.Round(amount/precision) * precision } // Round to avoid float precision issues rounded = math.Round(rounded*100) / 100 diff := rounded - amount return map[string]interface{}{ "rounded": rounded, "difference": math.Round(diff*100) / 100, }, nil }) } // initAccountInvoiceSend registers the invoice send wizard. // Mirrors: odoo/addons/account/wizard/account_invoice_send.py // // This wizard handles sending invoices by email and/or generating PDF. func initAccountInvoiceSend() { m := orm.NewModel("account.invoice.send", orm.ModelOpts{ Description: "Invoice Send", Type: orm.ModelTransient, }) m.AddFields( orm.Many2many("invoice_ids", "account.move", orm.FieldOpts{ String: "Invoices", Relation: "account_invoice_send_move_rel", Column1: "wizard_id", Column2: "move_id", }), orm.Boolean("is_email", orm.FieldOpts{String: "Email", Default: true}), orm.Boolean("is_print", orm.FieldOpts{String: "Print", Default: false}), orm.Char("partner_ids", orm.FieldOpts{String: "Partners"}), orm.Many2one("template_id", "mail.template", orm.FieldOpts{String: "Email Template"}), orm.Many2one("composer_id", "mail.compose.message", orm.FieldOpts{String: "Composer"}), ) // action_send_and_print: processes the sending/printing of invoices. // Mirrors: odoo/addons/account/wizard/account_invoice_send.py send_and_print_action() m.RegisterMethod("action_send_and_print", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) { // For now, just mark invoices as sent and return close action env := rs.Env() wizID := rs.IDs()[0] // Get invoice IDs from the wizard rows, err := env.Tx().Query(env.Ctx(), `SELECT move_id FROM account_invoice_send_move_rel WHERE wizard_id = $1`, wizID) if err != nil { return map[string]interface{}{"type": "ir.actions.act_window_close"}, nil } defer rows.Close() for rows.Next() { var moveID int64 if err := rows.Scan(&moveID); err != nil { continue } // Mark the invoice as sent (set invoice_sent flag via message) env.Tx().Exec(env.Ctx(), `UPDATE account_move SET ref = COALESCE(ref, '') || ' [Sent]' WHERE id = $1`, moveID) } return map[string]interface{}{ "type": "ir.actions.act_window_close", }, nil }) } // initAccountCashRoundingOnMove extends account.move with cash rounding support. func initAccountCashRoundingOnMove() { ext := orm.ExtendModel("account.move") ext.AddFields( orm.Many2one("invoice_cash_rounding_id", "account.cash.rounding", orm.FieldOpts{ String: "Cash Rounding Method", Help: "Defines the smallest coinage of the currency that can be used to pay", }), ) }