package orm import ( "fmt" "log" ) // SetupAllInherits generates Related fields for all models with _inherits declarations. // Must be called after all models are registered but before computes are set up. // Mirrors: odoo/orm/model_classes.py _add_inherited_fields() func SetupAllInherits() { for _, m := range Registry.Models() { if len(m.inherits) == 0 { continue } setupModelInherits(m) } } func setupModelInherits(m *Model) { for parentName, fkField := range m.inherits { parent := Registry.Get(parentName) if parent == nil { log.Printf("inherits: parent model %q not found for %s", parentName, m.Name()) continue } // Verify FK field exists on child fkDef := m.GetField(fkField) if fkDef == nil { log.Printf("inherits: FK field %q not found on %s", fkField, m.Name()) continue } // For each parent field, create a Related field on child if not already defined inherited := 0 for _, fname := range parent.fieldOrder { // Skip magic fields (child has its own) if fname == "id" || fname == "display_name" || fname == "create_uid" || fname == "create_date" || fname == "write_uid" || fname == "write_date" { continue } // Don't override fields already defined on child if existing := m.GetField(fname); existing != nil { continue } pf := parent.GetField(fname) if pf == nil { continue } // Create Related field: "fk_field.field_name" relatedPath := fkField + "." + fname inheritedField := &Field{ Name: fname, Type: pf.Type, String: pf.String, Help: pf.Help, Related: relatedPath, Comodel: pf.Comodel, InverseField: pf.InverseField, Selection: pf.Selection, CurrencyField: pf.CurrencyField, Readonly: pf.Readonly, Required: false, // Inherited fields are not required on child model: m, column: fname, } // Add to model's field maps m.fields[fname] = inheritedField m.allFields[fname] = inheritedField m.fieldOrder = append(m.fieldOrder, fname) inherited++ } log.Printf("inherits: %s inherits %d fields from %s via %s", m.Name(), inherited, parentName, fkField) } } // preprocessInheritsCreate separates inherited field values and creates/updates parent records. // Must be called in Create() BEFORE ApplyDefaults. // Mirrors: odoo/orm/models.py create() _inherits handling func preprocessInheritsCreate(env *Environment, m *Model, vals Values) error { if len(m.inherits) == 0 { return nil } for parentName, fkField := range m.inherits { parent := Registry.Get(parentName) if parent == nil { continue } // Separate parent vals from child vals parentVals := make(Values) for fieldName, val := range vals { // Check if this field belongs to parent (not child's own stored field) childField := m.fields[fieldName] parentField := parent.GetField(fieldName) if parentField != nil && fieldName != fkField && (childField == nil || childField.Related != "") { parentVals[fieldName] = val delete(vals, fieldName) } } // Check if FK is already provided fkVal, hasFk := vals[fkField] if hasFk && fkVal != nil { // FK provided — update existing parent record with any inherited vals fkID := int64(0) switch v := fkVal.(type) { case float64: fkID = int64(v) case int64: fkID = v case int32: fkID = int64(v) } if fkID > 0 && len(parentVals) > 0 { parentRS := env.Model(parentName).Browse(fkID) if err := parentRS.Write(parentVals); err != nil { return fmt.Errorf("inherits: update %s(%d): %w", parentName, fkID, err) } } } else { // No FK — create parent record first if len(parentVals) == 0 { // Need at least something for the parent (e.g., name) // Check if name is in child vals but not moved to parent if nameVal, ok := vals["name"]; ok { parentVals["name"] = nameVal } } parentRS := env.Model(parentName) created, err := parentRS.Create(parentVals) if err != nil { return fmt.Errorf("inherits: create %s: %w", parentName, err) } vals[fkField] = created.ID() } } return nil }