package models import ( "fmt" "odoo-go/pkg/orm" ) // initAccountJournal registers account.journal — where entries are posted. // Mirrors: odoo/addons/account/models/account_journal.py func initAccountJournal() { m := orm.NewModel("account.journal", orm.ModelOpts{ Description: "Journal", Order: "sequence, type, code", }) m.AddFields( orm.Char("name", orm.FieldOpts{String: "Journal Name", Required: true, Translate: true}), orm.Char("code", orm.FieldOpts{String: "Short Code", Required: true, Size: 5}), orm.Boolean("active", orm.FieldOpts{String: "Active", Default: true}), orm.Selection("type", []orm.SelectionItem{ {Value: "sale", Label: "Sales"}, {Value: "purchase", Label: "Purchase"}, {Value: "cash", Label: "Cash"}, {Value: "bank", Label: "Bank"}, {Value: "general", Label: "Miscellaneous"}, {Value: "credit", Label: "Credit Card"}, }, orm.FieldOpts{String: "Type", Required: true}), orm.Many2one("company_id", "res.company", orm.FieldOpts{String: "Company", Required: true}), orm.Many2one("currency_id", "res.currency", orm.FieldOpts{String: "Currency"}), orm.Many2one("default_account_id", "account.account", orm.FieldOpts{String: "Default Account"}), orm.Many2one("suspense_account_id", "account.account", orm.FieldOpts{String: "Suspense Account"}), orm.Many2one("profit_account_id", "account.account", orm.FieldOpts{String: "Profit Account"}), orm.Many2one("loss_account_id", "account.account", orm.FieldOpts{String: "Loss Account"}), orm.Many2one("sequence_id", "ir.sequence", orm.FieldOpts{String: "Entry Sequence"}), orm.Integer("sequence", orm.FieldOpts{String: "Sequence", Default: 10}), orm.Integer("color", orm.FieldOpts{String: "Color"}), orm.Char("bank_acc_number", orm.FieldOpts{String: "Account Number"}), orm.Many2one("bank_id", "res.bank", orm.FieldOpts{String: "Bank"}), orm.Many2one("bank_account_id", "res.partner.bank", orm.FieldOpts{String: "Bank Account"}), orm.Boolean("restrict_mode_hash_table", orm.FieldOpts{String: "Lock Posted Entries with Hash"}), ) } // initAccountMove registers account.move — the core journal entry / invoice model. // Mirrors: odoo/addons/account/models/account_move.py // // account.move is THE central model in Odoo accounting. It represents: // - Journal entries (manual bookkeeping) // - Customer invoices / credit notes // - Vendor bills / refunds // - Receipts func initAccountMove() { m := orm.NewModel("account.move", orm.ModelOpts{ Description: "Journal Entry", Order: "date desc, name desc, id desc", RecName: "name", }) // -- Identity -- m.AddFields( orm.Char("name", orm.FieldOpts{String: "Number", Index: true, Readonly: true, Default: "/"}), orm.Date("date", orm.FieldOpts{String: "Date", Required: true, Index: true}), orm.Char("ref", orm.FieldOpts{String: "Reference"}), ) // -- Type & State -- m.AddFields( orm.Selection("move_type", []orm.SelectionItem{ {Value: "entry", Label: "Journal Entry"}, {Value: "out_invoice", Label: "Customer Invoice"}, {Value: "out_refund", Label: "Customer Credit Note"}, {Value: "in_invoice", Label: "Vendor Bill"}, {Value: "in_refund", Label: "Vendor Credit Note"}, {Value: "out_receipt", Label: "Sales Receipt"}, {Value: "in_receipt", Label: "Purchase Receipt"}, }, orm.FieldOpts{String: "Type", Required: true, Default: "entry", Index: true}), orm.Selection("state", []orm.SelectionItem{ {Value: "draft", Label: "Draft"}, {Value: "posted", Label: "Posted"}, {Value: "cancel", Label: "Cancelled"}, }, orm.FieldOpts{String: "Status", Default: "draft", Required: true, Readonly: true, Index: true}), orm.Selection("payment_state", []orm.SelectionItem{ {Value: "not_paid", Label: "Not Paid"}, {Value: "in_payment", Label: "In Payment"}, {Value: "paid", Label: "Paid"}, {Value: "partial", Label: "Partially Paid"}, {Value: "reversed", Label: "Reversed"}, {Value: "blocked", Label: "Blocked"}, }, orm.FieldOpts{String: "Payment Status", Compute: "_compute_payment_state", Store: true}), ) // -- Relationships -- m.AddFields( orm.Many2one("journal_id", "account.journal", orm.FieldOpts{ String: "Journal", Required: true, Index: true, }), orm.Many2one("company_id", "res.company", orm.FieldOpts{ String: "Company", Required: true, Index: true, }), orm.Many2one("currency_id", "res.currency", orm.FieldOpts{ String: "Currency", Required: true, }), orm.Many2one("partner_id", "res.partner", orm.FieldOpts{ String: "Partner", Index: true, }), orm.Many2one("commercial_partner_id", "res.partner", orm.FieldOpts{ String: "Commercial Entity", Related: "partner_id.commercial_partner_id", }), orm.Many2one("fiscal_position_id", "account.fiscal.position", orm.FieldOpts{ String: "Fiscal Position", }), orm.Many2one("partner_bank_id", "res.partner.bank", orm.FieldOpts{ String: "Recipient Bank", }), ) // -- Lines -- m.AddFields( orm.One2many("line_ids", "account.move.line", "move_id", orm.FieldOpts{ String: "Journal Items", }), orm.One2many("invoice_line_ids", "account.move.line", "move_id", orm.FieldOpts{ String: "Invoice Lines", }), ) // -- Amounts (Computed) -- m.AddFields( orm.Monetary("amount_untaxed", orm.FieldOpts{String: "Untaxed Amount", Compute: "_compute_amount", Store: true, CurrencyField: "currency_id"}), orm.Monetary("amount_tax", orm.FieldOpts{String: "Tax", Compute: "_compute_amount", Store: true, CurrencyField: "currency_id"}), orm.Monetary("amount_total", orm.FieldOpts{String: "Total", Compute: "_compute_amount", Store: true, CurrencyField: "currency_id"}), orm.Monetary("amount_residual", orm.FieldOpts{String: "Amount Due", Compute: "_compute_amount", Store: true, CurrencyField: "currency_id"}), orm.Monetary("amount_total_in_currency_signed", orm.FieldOpts{String: "Total in Currency Signed", Compute: "_compute_amount", Store: true}), ) // -- Invoice specific -- m.AddFields( orm.Date("invoice_date", orm.FieldOpts{String: "Invoice/Bill Date"}), orm.Date("invoice_date_due", orm.FieldOpts{String: "Due Date"}), orm.Char("invoice_origin", orm.FieldOpts{String: "Source Document"}), orm.Many2one("invoice_payment_term_id", "account.payment.term", orm.FieldOpts{ String: "Payment Terms", }), orm.Text("narration", orm.FieldOpts{String: "Terms and Conditions"}), ) // -- Technical -- m.AddFields( orm.Boolean("auto_post", orm.FieldOpts{String: "Auto-post"}), orm.Char("sequence_prefix", orm.FieldOpts{String: "Sequence Prefix"}), orm.Integer("sequence_number", orm.FieldOpts{String: "Sequence Number"}), ) // -- Computed Fields -- // _compute_amount: sums invoice lines to produce totals. // Mirrors: odoo/addons/account/models/account_move.py AccountMove._compute_amount() computeAmount := func(rs *orm.Recordset) (orm.Values, error) { env := rs.Env() moveID := rs.IDs()[0] var untaxed, tax, total float64 rows, err := env.Tx().Query(env.Ctx(), `SELECT COALESCE(SUM(debit), 0), COALESCE(SUM(credit), 0) FROM account_move_line WHERE move_id = $1`, moveID) if err != nil { return nil, err } defer rows.Close() if rows.Next() { var debitSum, creditSum float64 if err := rows.Scan(&debitSum, &creditSum); err != nil { return nil, err } total = debitSum // For invoices, total = sum of debits (or credits) if debitSum > creditSum { total = debitSum } else { total = creditSum } // Tax lines have display_type='tax', product lines don't untaxed = total // Simplified: full total as untaxed for now } rows.Close() // Get actual tax amount from tax lines err = env.Tx().QueryRow(env.Ctx(), `SELECT COALESCE(SUM(ABS(balance)), 0) FROM account_move_line WHERE move_id = $1 AND display_type = 'tax'`, moveID).Scan(&tax) if err != nil { tax = 0 } if tax > 0 { untaxed = total - tax } return orm.Values{ "amount_untaxed": untaxed, "amount_tax": tax, "amount_total": total, "amount_residual": total, // Simplified: residual = total until payments }, nil } m.RegisterCompute("amount_untaxed", computeAmount) m.RegisterCompute("amount_tax", computeAmount) m.RegisterCompute("amount_total", computeAmount) m.RegisterCompute("amount_residual", computeAmount) // -- Business Methods: State Transitions -- // Mirrors: odoo/addons/account/models/account_move.py action_post(), button_cancel() // action_post: draft → posted m.RegisterMethod("action_post", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) { env := rs.Env() for _, id := range rs.IDs() { var state string err := env.Tx().QueryRow(env.Ctx(), `SELECT state FROM account_move WHERE id = $1`, id).Scan(&state) if err != nil { return nil, err } if state != "draft" { return nil, fmt.Errorf("account: can only post draft entries (current: %s)", state) } // Check balanced var debitSum, creditSum float64 env.Tx().QueryRow(env.Ctx(), `SELECT COALESCE(SUM(debit),0), COALESCE(SUM(credit),0) FROM account_move_line WHERE move_id = $1`, id).Scan(&debitSum, &creditSum) diff := debitSum - creditSum if diff < -0.005 || diff > 0.005 { return nil, fmt.Errorf("account: cannot post unbalanced entry (debit=%.2f, credit=%.2f)", debitSum, creditSum) } if _, err := env.Tx().Exec(env.Ctx(), `UPDATE account_move SET state = 'posted' WHERE id = $1`, id); err != nil { return nil, err } } return true, nil }) // button_cancel: posted → cancel (or draft → cancel) m.RegisterMethod("button_cancel", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) { env := rs.Env() for _, id := range rs.IDs() { if _, err := env.Tx().Exec(env.Ctx(), `UPDATE account_move SET state = 'cancel' WHERE id = $1`, id); err != nil { return nil, err } } return true, nil }) // button_draft: cancel → draft m.RegisterMethod("button_draft", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) { env := rs.Env() for _, id := range rs.IDs() { var state string err := env.Tx().QueryRow(env.Ctx(), `SELECT state FROM account_move WHERE id = $1`, id).Scan(&state) if err != nil { return nil, err } if state != "cancel" { return nil, fmt.Errorf("account: can only reset cancelled entries to draft (current: %s)", state) } if _, err := env.Tx().Exec(env.Ctx(), `UPDATE account_move SET state = 'draft' WHERE id = $1`, id); err != nil { return nil, err } } return true, nil }) // -- Double-Entry Constraint -- // SUM(debit) must equal SUM(credit) per journal entry. // Mirrors: odoo/addons/account/models/account_move.py _check_balanced() m.AddConstraint(func(rs *orm.Recordset) error { env := rs.Env() for _, id := range rs.IDs() { var debitSum, creditSum float64 err := env.Tx().QueryRow(env.Ctx(), `SELECT COALESCE(SUM(debit), 0), COALESCE(SUM(credit), 0) FROM account_move_line WHERE move_id = $1`, id, ).Scan(&debitSum, &creditSum) if err != nil { return err } // Allow empty moves (no lines yet) if debitSum == 0 && creditSum == 0 { continue } diff := debitSum - creditSum if diff < -0.005 || diff > 0.005 { return fmt.Errorf("account: journal entry is unbalanced — debit=%.2f credit=%.2f (diff=%.2f)", debitSum, creditSum, diff) } } return nil }) // -- BeforeCreate Hook: Generate sequence number -- m.BeforeCreate = func(env *orm.Environment, vals orm.Values) error { name, _ := vals["name"].(string) if name == "" || name == "/" { moveType, _ := vals["move_type"].(string) code := "account.move" switch moveType { case "out_invoice", "out_refund", "out_receipt": code = "account.move.out_invoice" case "in_invoice", "in_refund", "in_receipt": code = "account.move.in_invoice" } seq, err := orm.NextByCode(env, code) if err != nil { // Fallback to generic sequence seq, err = orm.NextByCode(env, "account.move") if err != nil { return nil // No sequence configured, keep "/" } } vals["name"] = seq } return nil } } // initAccountMoveLine registers account.move.line — journal items / invoice lines. // Mirrors: odoo/addons/account/models/account_move_line.py // // CRITICAL: In double-entry bookkeeping, sum(debit) must equal sum(credit) per move. func initAccountMoveLine() { m := orm.NewModel("account.move.line", orm.ModelOpts{ Description: "Journal Item", Order: "date desc, id", }) // -- Parent -- m.AddFields( orm.Many2one("move_id", "account.move", orm.FieldOpts{ String: "Journal Entry", Required: true, OnDelete: orm.OnDeleteCascade, Index: true, }), orm.Char("move_name", orm.FieldOpts{String: "Journal Entry Name", Related: "move_id.name"}), orm.Date("date", orm.FieldOpts{String: "Date", Related: "move_id.date", Store: true, Index: true}), orm.Many2one("journal_id", "account.journal", orm.FieldOpts{String: "Journal", Index: true}), ) // -- Accounts -- m.AddFields( orm.Many2one("account_id", "account.account", orm.FieldOpts{ String: "Account", Required: true, Index: true, }), orm.Many2one("partner_id", "res.partner", orm.FieldOpts{String: "Partner", Index: true}), orm.Many2one("company_id", "res.company", orm.FieldOpts{String: "Company", Required: true}), orm.Many2one("company_currency_id", "res.currency", orm.FieldOpts{String: "Company Currency"}), orm.Many2one("currency_id", "res.currency", orm.FieldOpts{String: "Currency"}), ) // -- Amounts (Double-Entry) -- m.AddFields( orm.Monetary("debit", orm.FieldOpts{String: "Debit", Default: 0.0, CurrencyField: "company_currency_id"}), orm.Monetary("credit", orm.FieldOpts{String: "Credit", Default: 0.0, CurrencyField: "company_currency_id"}), orm.Monetary("balance", orm.FieldOpts{String: "Balance", Compute: "_compute_balance", Store: true, CurrencyField: "company_currency_id"}), orm.Monetary("amount_currency", orm.FieldOpts{String: "Amount in Currency", CurrencyField: "currency_id"}), orm.Float("amount_residual", orm.FieldOpts{String: "Residual Amount"}), orm.Float("amount_residual_currency", orm.FieldOpts{String: "Residual Amount in Currency"}), ) // -- Invoice line fields -- m.AddFields( orm.Char("name", orm.FieldOpts{String: "Label"}), orm.Float("quantity", orm.FieldOpts{String: "Quantity", Default: 1.0}), orm.Float("price_unit", orm.FieldOpts{String: "Unit Price"}), orm.Float("discount", orm.FieldOpts{String: "Discount (%)"}), orm.Float("price_subtotal", orm.FieldOpts{String: "Subtotal", Compute: "_compute_totals", Store: true}), orm.Float("price_total", orm.FieldOpts{String: "Total", Compute: "_compute_totals", Store: true}), ) // -- Tax -- m.AddFields( orm.Many2many("tax_ids", "account.tax", orm.FieldOpts{String: "Taxes"}), orm.Many2one("tax_line_id", "account.tax", orm.FieldOpts{String: "Originator Tax"}), orm.Many2one("tax_group_id", "account.tax.group", orm.FieldOpts{String: "Tax Group"}), orm.Many2one("tax_repartition_line_id", "account.tax.repartition.line", orm.FieldOpts{ String: "Tax Repartition Line", }), ) // -- Display -- m.AddFields( orm.Selection("display_type", []orm.SelectionItem{ {Value: "product", Label: "Product"}, {Value: "cogs", Label: "COGS"}, {Value: "tax", Label: "Tax"}, {Value: "rounding", Label: "Rounding"}, {Value: "payment_term", Label: "Payment Term"}, {Value: "line_section", Label: "Section"}, {Value: "line_note", Label: "Note"}, {Value: "epd", Label: "Early Payment Discount"}, }, orm.FieldOpts{String: "Display Type", Default: "product"}), orm.Integer("sequence", orm.FieldOpts{String: "Sequence", Default: 10}), ) // -- Reconciliation -- m.AddFields( orm.Boolean("reconciled", orm.FieldOpts{String: "Reconciled"}), orm.Many2one("full_reconcile_id", "account.full.reconcile", orm.FieldOpts{String: "Matching"}), ) } // initAccountPayment registers account.payment. // Mirrors: odoo/addons/account/models/account_payment.py func initAccountPayment() { m := orm.NewModel("account.payment", orm.ModelOpts{ Description: "Payments", Order: "date desc, name desc", }) m.AddFields( orm.Char("name", orm.FieldOpts{String: "Name", Readonly: true}), orm.Many2one("move_id", "account.move", orm.FieldOpts{ String: "Journal Entry", Required: true, OnDelete: orm.OnDeleteCascade, }), orm.Selection("payment_type", []orm.SelectionItem{ {Value: "outbound", Label: "Send"}, {Value: "inbound", Label: "Receive"}, }, orm.FieldOpts{String: "Payment Type", Required: true}), orm.Selection("partner_type", []orm.SelectionItem{ {Value: "customer", Label: "Customer"}, {Value: "supplier", Label: "Vendor"}, }, orm.FieldOpts{String: "Partner Type"}), orm.Selection("state", []orm.SelectionItem{ {Value: "draft", Label: "Draft"}, {Value: "in_process", Label: "In Process"}, {Value: "paid", Label: "Paid"}, {Value: "canceled", Label: "Cancelled"}, {Value: "rejected", Label: "Rejected"}, }, orm.FieldOpts{String: "Status", Default: "draft"}), orm.Date("date", orm.FieldOpts{String: "Date", Required: true}), orm.Monetary("amount", orm.FieldOpts{String: "Amount", Required: true, CurrencyField: "currency_id"}), orm.Many2one("currency_id", "res.currency", orm.FieldOpts{String: "Currency", Required: true}), orm.Many2one("journal_id", "account.journal", orm.FieldOpts{String: "Journal", Required: true}), orm.Many2one("partner_id", "res.partner", orm.FieldOpts{String: "Customer/Vendor"}), orm.Many2one("company_id", "res.company", orm.FieldOpts{String: "Company", Required: true}), orm.Many2one("partner_bank_id", "res.partner.bank", orm.FieldOpts{String: "Recipient Bank Account"}), orm.Many2one("destination_account_id", "account.account", orm.FieldOpts{String: "Destination Account"}), orm.Boolean("is_reconciled", orm.FieldOpts{String: "Is Reconciled"}), orm.Boolean("is_matched", orm.FieldOpts{String: "Is Matched With a Bank Statement"}), orm.Char("payment_reference", orm.FieldOpts{String: "Payment Reference"}), orm.Char("payment_method_code", orm.FieldOpts{String: "Payment Method Code"}), ) } // initAccountPaymentTerm registers payment terms. // Mirrors: odoo/addons/account/models/account_payment_term.py func initAccountPaymentTerm() { m := orm.NewModel("account.payment.term", orm.ModelOpts{ Description: "Payment Terms", Order: "sequence, id", }) m.AddFields( orm.Char("name", orm.FieldOpts{String: "Payment Terms", Required: true, Translate: true}), orm.Boolean("active", orm.FieldOpts{String: "Active", Default: true}), orm.Text("note", orm.FieldOpts{String: "Description on the Invoice", Translate: true}), orm.One2many("line_ids", "account.payment.term.line", "payment_id", orm.FieldOpts{String: "Terms"}), orm.Integer("sequence", orm.FieldOpts{String: "Sequence", Default: 10}), orm.Many2one("company_id", "res.company", orm.FieldOpts{String: "Company"}), orm.Selection("early_discount", []orm.SelectionItem{ {Value: "none", Label: "None"}, {Value: "mixed", Label: "On early payment"}, }, orm.FieldOpts{String: "Early Discount", Default: "none"}), orm.Float("discount_percentage", orm.FieldOpts{String: "Discount %"}), orm.Integer("discount_days", orm.FieldOpts{String: "Discount Days"}), ) // Payment term lines — each line defines a portion orm.NewModel("account.payment.term.line", orm.ModelOpts{ Description: "Payment Terms Line", Order: "sequence, id", }).AddFields( orm.Many2one("payment_id", "account.payment.term", orm.FieldOpts{ String: "Payment Terms", Required: true, OnDelete: orm.OnDeleteCascade, }), orm.Selection("value", []orm.SelectionItem{ {Value: "balance", Label: "Balance"}, {Value: "percent", Label: "Percent"}, {Value: "fixed", Label: "Fixed Amount"}, }, orm.FieldOpts{String: "Type", Required: true, Default: "balance"}), orm.Float("value_amount", orm.FieldOpts{String: "Value"}), orm.Integer("nb_days", orm.FieldOpts{String: "Days", Required: true, Default: 0}), orm.Integer("sequence", orm.FieldOpts{String: "Sequence", Default: 10}), ) } // initAccountReconcile registers reconciliation models. // Mirrors: odoo/addons/account/models/account_reconcile_model.py func initAccountReconcile() { // Full reconcile — groups partial reconciles orm.NewModel("account.full.reconcile", orm.ModelOpts{ Description: "Full Reconcile", }).AddFields( orm.Char("name", orm.FieldOpts{String: "Name", Required: true}), orm.One2many("partial_reconcile_ids", "account.partial.reconcile", "full_reconcile_id", orm.FieldOpts{String: "Reconciliation Parts"}), orm.One2many("reconciled_line_ids", "account.move.line", "full_reconcile_id", orm.FieldOpts{String: "Matched Journal Items"}), orm.Many2one("exchange_move_id", "account.move", orm.FieldOpts{String: "Exchange Rate Entry"}), ) // Partial reconcile — matches debit ↔ credit lines orm.NewModel("account.partial.reconcile", orm.ModelOpts{ Description: "Partial Reconcile", }).AddFields( orm.Many2one("debit_move_id", "account.move.line", orm.FieldOpts{String: "Debit line", Required: true, Index: true}), orm.Many2one("credit_move_id", "account.move.line", orm.FieldOpts{String: "Credit line", Required: true, Index: true}), orm.Many2one("full_reconcile_id", "account.full.reconcile", orm.FieldOpts{String: "Full Reconcile"}), orm.Monetary("amount", orm.FieldOpts{String: "Amount", Required: true}), orm.Monetary("debit_amount_currency", orm.FieldOpts{String: "Debit Amount Currency"}), orm.Monetary("credit_amount_currency", orm.FieldOpts{String: "Credit Amount Currency"}), orm.Many2one("company_id", "res.company", orm.FieldOpts{String: "Company"}), orm.Many2one("debit_currency_id", "res.currency", orm.FieldOpts{String: "Debit Currency"}), orm.Many2one("credit_currency_id", "res.currency", orm.FieldOpts{String: "Credit Currency"}), orm.Many2one("exchange_move_id", "account.move", orm.FieldOpts{String: "Exchange Rate Entry"}), ) } // initAccountBankStatement registers bank statement models. // Mirrors: odoo/addons/account/models/account_bank_statement.py func initAccountBankStatement() { m := orm.NewModel("account.bank.statement", orm.ModelOpts{ Description: "Bank Statement", Order: "date desc, name desc, id desc", }) m.AddFields( orm.Char("name", orm.FieldOpts{String: "Reference"}), orm.Date("date", orm.FieldOpts{String: "Date", Required: true}), orm.Many2one("journal_id", "account.journal", orm.FieldOpts{String: "Journal", Required: true}), orm.Many2one("company_id", "res.company", orm.FieldOpts{String: "Company", Required: true}), orm.Float("balance_start", orm.FieldOpts{String: "Starting Balance"}), orm.Float("balance_end_real", orm.FieldOpts{String: "Ending Balance"}), orm.Float("balance_end", orm.FieldOpts{String: "Computed Balance", Compute: "_compute_balance_end"}), orm.One2many("line_ids", "account.bank.statement.line", "statement_id", orm.FieldOpts{String: "Statement Lines"}), ) // Bank statement line orm.NewModel("account.bank.statement.line", orm.ModelOpts{ Description: "Bank Statement Line", Order: "internal_index desc, sequence, id desc", }).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}), orm.Many2one("company_id", "res.company", orm.FieldOpts{String: "Company", Required: true}), orm.Many2one("partner_id", "res.partner", orm.FieldOpts{String: "Partner"}), orm.Char("payment_ref", orm.FieldOpts{String: "Label"}), orm.Date("date", orm.FieldOpts{String: "Date", Required: true}), orm.Monetary("amount", orm.FieldOpts{String: "Amount", Required: true, CurrencyField: "currency_id"}), orm.Many2one("currency_id", "res.currency", orm.FieldOpts{String: "Currency"}), orm.Char("transaction_type", orm.FieldOpts{String: "Transaction Type"}), orm.Char("account_number", orm.FieldOpts{String: "Bank Account Number"}), orm.Char("partner_name", orm.FieldOpts{String: "Partner Name"}), orm.Char("narration", orm.FieldOpts{String: "Notes"}), 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"}), ) }