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, } } // Always include search view (client expects it) if _, hasSearch := views["search"]; !hasSearch { arch := loadViewArch(env, model, "search") if arch == "" { arch = generateDefaultView(model, "search") } views["search"] = map[string]interface{}{ "arch": arch, "type": "search", "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.Name == "password" || f.Name == "password_crypt" || 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, "password": true, "password_crypt": true, } // Header with action buttons and state widget // Mirrors: odoo form views with
var header string if f := m.GetField("state"); f != nil && f.Type == orm.TypeSelection { var buttons []string // Generate buttons from registered methods that look like actions if m.Methods != nil { actionMethods := []struct{ method, label, stateFilter string }{ {"action_confirm", "Confirm", "draft"}, {"action_post", "Post", "draft"}, {"action_done", "Done", "confirmed"}, {"action_cancel", "Cancel", ""}, {"button_cancel", "Cancel", ""}, {"button_draft", "Reset to Draft", "cancel"}, {"action_send", "Send", "posted"}, {"create_invoices", "Create Invoice", "sale"}, } for _, am := range actionMethods { if _, ok := m.Methods[am.method]; ok { attrs := "" if am.stateFilter != "" { attrs = fmt.Sprintf(` invisible="state != '%s'"`, am.stateFilter) } btnClass := "btn-secondary" if am.method == "action_confirm" || am.method == "action_post" { btnClass = "btn-primary" } buttons = append(buttons, fmt.Sprintf( `