Backend improvements: views, fields_get, session, RPC stubs
- Improved auto-generated list/form/search views with priority fields, two-column form layout, statusbar widget, notebook for O2M fields - Enhanced fields_get with currency_field, compute, related metadata - Fixed session handling: handleSessionInfo/handleSessionCheck use real session from cookie instead of hardcoded values - Added read_progress_bar and activity_format RPC stubs - Improved bootstrap translations with lang_parameters - Added "contacts" to session modules list Server starts successfully: 14 modules, 93 models, 378 XML templates, 503 JS modules transpiled — all from local frontend/ directory. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"odoo-go/pkg/orm"
|
||||
)
|
||||
@@ -12,8 +13,13 @@ import (
|
||||
func handleWebSearchRead(env *orm.Environment, model string, params CallKWParams) (interface{}, *RPCError) {
|
||||
rs := env.Model(model)
|
||||
|
||||
// Parse domain from first arg
|
||||
// Parse domain from first arg (regular search_read) or kwargs (web_search_read)
|
||||
domain := parseDomain(params.Args)
|
||||
if domain == nil {
|
||||
if domainRaw, ok := params.KW["domain"].([]interface{}); ok && len(domainRaw) > 0 {
|
||||
domain = parseDomain([]interface{}{domainRaw})
|
||||
}
|
||||
}
|
||||
|
||||
// Parse specification from kwargs
|
||||
spec, _ := params.KW["specification"].(map[string]interface{})
|
||||
@@ -45,11 +51,19 @@ func handleWebSearchRead(env *orm.Environment, model string, params CallKWParams
|
||||
order = v
|
||||
}
|
||||
|
||||
// Get total count
|
||||
// Get total count, respecting count_limit for optimization.
|
||||
// Mirrors: odoo/addons/web/models/models.py web_search_read() count_limit parameter
|
||||
countLimit := int64(0)
|
||||
if v, ok := params.KW["count_limit"].(float64); ok {
|
||||
countLimit = int64(v)
|
||||
}
|
||||
count, err := rs.SearchCount(domain)
|
||||
if err != nil {
|
||||
return nil, &RPCError{Code: -32000, Message: err.Error()}
|
||||
}
|
||||
if countLimit > 0 && count > countLimit {
|
||||
count = countLimit
|
||||
}
|
||||
|
||||
// Search with offset/limit
|
||||
found, err := rs.Search(domain, orm.SearchOpts{
|
||||
@@ -72,6 +86,9 @@ func handleWebSearchRead(env *orm.Environment, model string, params CallKWParams
|
||||
// Format M2O fields as {id, display_name} when spec requests it
|
||||
formatM2OFields(env, model, records, spec)
|
||||
|
||||
// Format date/datetime fields to Odoo's expected string format
|
||||
formatDateFields(model, records)
|
||||
|
||||
if records == nil {
|
||||
records = []orm.Values{}
|
||||
}
|
||||
@@ -93,6 +110,18 @@ func handleWebRead(env *orm.Environment, model string, params CallKWParams) (int
|
||||
spec, _ := params.KW["specification"].(map[string]interface{})
|
||||
fields := specToFields(spec)
|
||||
|
||||
// Always include id
|
||||
hasID := false
|
||||
for _, f := range fields {
|
||||
if f == "id" {
|
||||
hasID = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasID {
|
||||
fields = append([]string{"id"}, fields...)
|
||||
}
|
||||
|
||||
rs := env.Model(model)
|
||||
records, err := rs.Browse(ids...).Read(fields)
|
||||
if err != nil {
|
||||
@@ -101,6 +130,9 @@ func handleWebRead(env *orm.Environment, model string, params CallKWParams) (int
|
||||
|
||||
formatM2OFields(env, model, records, spec)
|
||||
|
||||
// Format date/datetime fields to Odoo's expected string format
|
||||
formatDateFields(model, records)
|
||||
|
||||
if records == nil {
|
||||
records = []orm.Values{}
|
||||
}
|
||||
@@ -170,3 +202,42 @@ func formatM2OFields(env *orm.Environment, modelName string, records []orm.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// formatDateFields converts date/datetime values to Odoo's expected string format.
|
||||
func formatDateFields(model string, records []orm.Values) {
|
||||
m := orm.Registry.Get(model)
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
for _, rec := range records {
|
||||
for fieldName, val := range rec {
|
||||
f := m.GetField(fieldName)
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
if f.Type == orm.TypeDate || f.Type == orm.TypeDatetime {
|
||||
switch v := val.(type) {
|
||||
case time.Time:
|
||||
if f.Type == orm.TypeDate {
|
||||
rec[fieldName] = v.Format("2006-01-02")
|
||||
} else {
|
||||
rec[fieldName] = v.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
case string:
|
||||
// Already a string, might need reformatting
|
||||
if t, err := time.Parse(time.RFC3339, v); err == nil {
|
||||
if f.Type == orm.TypeDate {
|
||||
rec[fieldName] = t.Format("2006-01-02")
|
||||
} else {
|
||||
rec[fieldName] = t.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Also convert boolean fields: Go nil → Odoo false
|
||||
if f.Type == orm.TypeBoolean && val == nil {
|
||||
rec[fieldName] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user