diff --git a/addons/base/models/res_partner.go b/addons/base/models/res_partner.go index 5ce9ab7..e911b26 100644 --- a/addons/base/models/res_partner.go +++ b/addons/base/models/res_partner.go @@ -20,7 +20,6 @@ func initResPartner() { // -- Identity -- m.AddFields( orm.Char("name", orm.FieldOpts{String: "Name", Required: true, Index: true}), - orm.Char("display_name", orm.FieldOpts{String: "Display Name", Compute: "_compute_display_name", Store: true}), orm.Char("ref", orm.FieldOpts{String: "Reference", Index: true}), orm.Selection("type", []orm.SelectionItem{ {Value: "contact", Label: "Contact"}, diff --git a/addons/purchase/models/purchase_order.go b/addons/purchase/models/purchase_order.go index c96a8e5..aad5d99 100644 --- a/addons/purchase/models/purchase_order.go +++ b/addons/purchase/models/purchase_order.go @@ -107,6 +107,32 @@ func initPurchaseOrder() { orm.Char("origin", orm.FieldOpts{String: "Source Document"}), ) + // -- DefaultGet: Provide dynamic defaults for new records -- + // Mirrors: odoo/addons/purchase/models/purchase_order.py PurchaseOrder.default_get() + // Supplies company_id, currency_id, date_order when creating a new PO. + m.DefaultGet = func(env *orm.Environment, fields []string) orm.Values { + vals := make(orm.Values) + + // Default company from the current user's session + companyID := env.CompanyID() + if companyID > 0 { + vals["company_id"] = companyID + } + + // Default currency from the company + var currencyID int64 + err := env.Tx().QueryRow(env.Ctx(), + `SELECT currency_id FROM res_company WHERE id = $1`, companyID).Scan(¤cyID) + if err == nil && currencyID > 0 { + vals["currency_id"] = currencyID + } + + // Default date_order = now + vals["date_order"] = time.Now().Format("2006-01-02 15:04:05") + + return vals + } + // button_confirm: draft → purchase m.RegisterMethod("button_confirm", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) { env := rs.Env() diff --git a/addons/sale/models/sale_order.go b/addons/sale/models/sale_order.go index e3d0398..9fc8540 100644 --- a/addons/sale/models/sale_order.go +++ b/addons/sale/models/sale_order.go @@ -135,7 +135,7 @@ func initSaleOrder() { `SELECT COALESCE(SUM( product_uom_qty * price_unit * (1 - COALESCE(discount,0)/100) * COALESCE((SELECT t.amount / 100 FROM account_tax t - JOIN sale_order_line_account_tax_rel rel ON rel.account_tax_id = t.id + JOIN account_tax_sale_order_line_rel rel ON rel.account_tax_id = t.id WHERE rel.sale_order_line_id = sol.id LIMIT 1), 0) ), 0) FROM sale_order_line sol WHERE sol.order_id = $1 @@ -156,12 +156,41 @@ func initSaleOrder() { m.RegisterCompute("amount_tax", computeSaleAmounts) m.RegisterCompute("amount_total", computeSaleAmounts) + // -- DefaultGet: Provide dynamic defaults for new records -- + // Mirrors: odoo/addons/sale/models/sale_order.py SaleOrder.default_get() + // Supplies company_id, currency_id, date_order when creating a new quotation. + m.DefaultGet = func(env *orm.Environment, fields []string) orm.Values { + vals := make(orm.Values) + + // Default company from the current user's session + companyID := env.CompanyID() + if companyID > 0 { + vals["company_id"] = companyID + } + + // Default currency from the company + var currencyID int64 + err := env.Tx().QueryRow(env.Ctx(), + `SELECT currency_id FROM res_company WHERE id = $1`, companyID).Scan(¤cyID) + if err == nil && currencyID > 0 { + vals["currency_id"] = currencyID + } + + // Default date_order = now + vals["date_order"] = time.Now().Format("2006-01-02 15:04:05") + + return vals + } + // -- Sequence Hook -- m.BeforeCreate = func(env *orm.Environment, vals orm.Values) error { name, _ := vals["name"].(string) if name == "" || name == "/" { seq, err := orm.NextByCode(env, "sale.order") - if err == nil { + if err != nil { + // Fallback: generate a simple name like purchase.order does + vals["name"] = fmt.Sprintf("SO/%d", time.Now().UnixNano()%100000) + } else { vals["name"] = seq } } diff --git a/odoo-server b/odoo-server index f68bf70..3751050 100755 Binary files a/odoo-server and b/odoo-server differ diff --git a/pkg/orm/model.go b/pkg/orm/model.go index 7f7cc47..41d4865 100644 --- a/pkg/orm/model.go +++ b/pkg/orm/model.go @@ -129,6 +129,13 @@ func (m *Model) addMagicFields() { Readonly: true, })) + // display_name: computed on-the-fly from rec_name, not stored in DB. + // Mirrors: odoo/orm/models.py BaseModel.display_name (computed field on ALL models) + m.AddField(Char("display_name", FieldOpts{ + String: "Display Name", + Compute: "_compute_display_name", + })) + if m.logAccess { m.AddField(Many2one("create_uid", "res.users", FieldOpts{ String: "Created by", diff --git a/pkg/orm/recordset.go b/pkg/orm/recordset.go index 43e80d0..549eee0 100644 --- a/pkg/orm/recordset.go +++ b/pkg/orm/recordset.go @@ -349,12 +349,17 @@ func (rs *Recordset) Read(fields []string) ([]Values, error) { var storedFields []string // Fields that come from the DB query var m2mFields []string // Many2many fields (from junction table) var relatedFields []string // Related fields (from joined table) + wantDisplayName := false // Whether display_name was requested for _, name := range fields { f := m.GetField(name) if f == nil { return nil, fmt.Errorf("orm: field %q not found on %s", name, m.name) } + if name == "display_name" && f.Compute != "" && !f.Store { + wantDisplayName = true + continue + } if f.Type == TypeMany2many { m2mFields = append(m2mFields, name) } else if f.Related != "" && !f.Store { @@ -365,6 +370,25 @@ func (rs *Recordset) Read(fields []string) ([]Values, error) { } } + // If display_name is requested, ensure the rec_name field is fetched from DB + recName := m.recName + if wantDisplayName { + recNameAlreadyFetched := false + for _, sf := range storedFields { + if sf == recName { + recNameAlreadyFetched = true + break + } + } + if !recNameAlreadyFetched { + recF := m.GetField(recName) + if recF != nil && recF.IsStored() { + columns = append(columns, fmt.Sprintf("%q", recF.Column())) + storedFields = append(storedFields, recName) + } + } + } + // Build query var args []interface{} idPlaceholders := make([]string, len(rs.ids)) @@ -426,6 +450,25 @@ func (rs *Recordset) Read(fields []string) ([]Values, error) { } } + // Post-fetch: compute display_name from the rec_name field value. + // Mirrors: odoo/orm/models.py BaseModel._compute_display_name() + if wantDisplayName { + for _, rec := range results { + if nameVal, ok := rec[recName]; ok { + switch v := nameVal.(type) { + case string: + rec["display_name"] = v + case nil: + rec["display_name"] = "" + default: + rec["display_name"] = fmt.Sprintf("%v", v) + } + } else { + rec["display_name"] = "" + } + } + } + // Post-fetch: M2M fields (from junction tables) if len(m2mFields) > 0 && len(rs.ids) > 0 { for _, fname := range m2mFields {