package server import ( "fmt" "strings" "odoo-go/pkg/orm" ) // handleGetViews implements the get_views method. // Mirrors: odoo/addons/base/models/ir_ui_view.py get_views() func handleGetViews(env *orm.Environment, model string, params CallKWParams) (interface{}, *RPCError) { // Parse views list: [[false, "list"], [false, "form"], [false, "search"]] var viewRequests [][]interface{} if len(params.Args) > 0 { if vr, ok := params.Args[0].([]interface{}); ok { viewRequests = make([][]interface{}, len(vr)) for i, v := range vr { if pair, ok := v.([]interface{}); ok { viewRequests[i] = pair } } } } // Also check kwargs if viewRequests == nil { if vr, ok := params.KW["views"].([]interface{}); ok { viewRequests = make([][]interface{}, len(vr)) for i, v := range vr { if pair, ok := v.([]interface{}); ok { viewRequests[i] = pair } } } } views := make(map[string]interface{}) for _, vr := range viewRequests { if len(vr) < 2 { continue } viewType, _ := vr[1].(string) if viewType == "" { continue } // Try to load from ir_ui_view table arch := loadViewArch(env, model, viewType) if arch == "" { // Generate default view arch = generateDefaultView(model, viewType) } views[viewType] = map[string]interface{}{ "arch": arch, "type": viewType, "model": model, "view_id": 0, "field_parent": false, } } // Build models dict with field metadata models := map[string]interface{}{ model: map[string]interface{}{ "fields": fieldsGetForModel(model), }, } return map[string]interface{}{ "views": views, "models": models, }, nil } // loadViewArch tries to load a view from the ir_ui_view table. func loadViewArch(env *orm.Environment, model, viewType string) string { var arch string err := env.Tx().QueryRow(env.Ctx(), `SELECT arch FROM ir_ui_view WHERE model = $1 AND type = $2 AND active = true ORDER BY priority LIMIT 1`, model, viewType, ).Scan(&arch) if err != nil { return "" } return arch } // generateDefaultView creates a minimal view XML for a model. func generateDefaultView(modelName, viewType string) string { m := orm.Registry.Get(modelName) if m == nil { return fmt.Sprintf("<%s>", viewType, viewType) } switch viewType { case "list", "tree": return generateDefaultListView(m) case "form": return generateDefaultFormView(m) case "search": return generateDefaultSearchView(m) case "kanban": return generateDefaultKanbanView(m) default: return fmt.Sprintf("<%s>", viewType, viewType) } } func generateDefaultListView(m *orm.Model) string { // Prioritize important fields first priority := []string{"name", "display_name", "state", "partner_id", "date_order", "date", "amount_total", "amount_untaxed", "email", "phone", "company_id", "user_id", "product_id", "quantity", "price_unit", "price_subtotal"} var fields []string added := make(map[string]bool) // Add priority fields first for _, pf := range priority { f := m.GetField(pf) if f != nil && f.IsStored() && f.Type != orm.TypeBinary { fields = append(fields, fmt.Sprintf(``, pf)) added[pf] = true } } // Fill remaining slots for _, f := range m.Fields() { if len(fields) >= 10 { break } if added[f.Name] || f.Name == "id" || !f.IsStored() || f.Name == "create_uid" || f.Name == "write_uid" || f.Name == "create_date" || f.Name == "write_date" || f.Type == orm.TypeBinary || f.Type == orm.TypeText || f.Type == orm.TypeHTML { continue } fields = append(fields, fmt.Sprintf(``, f.Name)) added[f.Name] = true } return fmt.Sprintf("\n %s\n", strings.Join(fields, "\n ")) } func generateDefaultFormView(m *orm.Model) string { skip := map[string]bool{ "id": true, "create_uid": true, "write_uid": true, "create_date": true, "write_date": true, } // Header with state widget if state field exists var header string if f := m.GetField("state"); f != nil && f.Type == orm.TypeSelection { header = `
` } // Title field (name or display_name) var title string if f := m.GetField("name"); f != nil { title = `

` skip["name"] = true } // Split fields into left/right groups var leftFields, rightFields []string var o2mFields []string count := 0 // Prioritize important fields priority := []string{"partner_id", "date_order", "date", "company_id", "currency_id", "user_id", "journal_id", "product_id", "email", "phone"} for _, pf := range priority { f := m.GetField(pf) if f == nil || skip[pf] || f.Type == orm.TypeBinary { continue } skip[pf] = true line := fmt.Sprintf(` `, pf) if count%2 == 0 { leftFields = append(leftFields, line) } else { rightFields = append(rightFields, line) } count++ } // Add remaining stored fields for _, f := range m.Fields() { if skip[f.Name] || !f.IsStored() || f.Type == orm.TypeBinary { continue } if f.Type == orm.TypeOne2many { o2mFields = append(o2mFields, fmt.Sprintf(` `, f.Name)) continue } if f.Type == orm.TypeMany2many { continue } line := fmt.Sprintf(` `, f.Name) if len(leftFields) <= len(rightFields) { leftFields = append(leftFields, line) } else { rightFields = append(rightFields, line) } if len(leftFields)+len(rightFields) >= 20 { break } } // Build form var buf strings.Builder buf.WriteString("
\n") buf.WriteString(header) buf.WriteString(" \n") buf.WriteString(title) buf.WriteString(" \n") buf.WriteString(" \n") buf.WriteString(strings.Join(leftFields, "\n")) buf.WriteString("\n \n") buf.WriteString(" \n") buf.WriteString(strings.Join(rightFields, "\n")) buf.WriteString("\n \n") buf.WriteString(" \n") // O2M fields in notebook if len(o2mFields) > 0 { buf.WriteString(" \n") buf.WriteString(" \n") buf.WriteString(strings.Join(o2mFields, "\n")) buf.WriteString("\n \n") buf.WriteString(" \n") } buf.WriteString(" \n") buf.WriteString("
") return buf.String() } func generateDefaultSearchView(m *orm.Model) string { var fields []string var filters []string // Search fields searchable := []string{"name", "display_name", "email", "phone", "ref", "partner_id", "company_id", "user_id", "state", "date_order", "date"} for _, sf := range searchable { if f := m.GetField(sf); f != nil { fields = append(fields, fmt.Sprintf(``, sf)) } } if len(fields) == 0 { fields = append(fields, ``) } // Auto-generate filter for state field if f := m.GetField("state"); f != nil && f.Type == orm.TypeSelection { for _, sel := range f.Selection { filters = append(filters, fmt.Sprintf( ``, sel.Label, sel.Value, sel.Value)) } } // Group-by for common fields var groupby []string groupable := []string{"partner_id", "state", "company_id", "user_id", "stage_id"} for _, gf := range groupable { if f := m.GetField(gf); f != nil { groupby = append(groupby, fmt.Sprintf(``, f.String, gf, gf)) } } var buf strings.Builder buf.WriteString("\n") for _, f := range fields { buf.WriteString(" " + f + "\n") } if len(filters) > 0 { buf.WriteString(" \n") for _, f := range filters { buf.WriteString(" " + f + "\n") } } if len(groupby) > 0 { buf.WriteString(" \n") for _, g := range groupby { buf.WriteString(" " + g + "\n") } buf.WriteString(" \n") } buf.WriteString("") return buf.String() } func generateDefaultKanbanView(m *orm.Model) string { nameField := "name" if f := m.GetField("name"); f == nil { nameField = "id" } return fmt.Sprintf(` `, nameField) }