Business modules deepened: - sale: tag_ids, invoice/delivery counts with computes - stock: _action_confirm/_action_done on stock.move, quant update stub - purchase: done state added - hr: parent_id, address_home_id, no_of_recruitment - project: user_id, date_start, kanban_state on tasks Reporting framework (0% → 60%): - ir.actions.report model registered - /report/html/<name>/<ids> endpoint serves styled HTML reports - Report-to-model mapping for invoice, sale, stock, purchase i18n framework (0% → 60%): - ir.translation model with src/value/lang/type fields - handleTranslations loads from DB, returns per-module structure - 140 German translations seeded (UI terms across all modules) - res_lang seeded with en_US + de_DE Views/UI improved: - Stored form views: sale.order (with editable O2M lines), account.move (with Post/Cancel buttons), res.partner (with title) - Stored list views: purchase.order, hr.employee, project.project Demo data expanded: - 5 products (templates + variants with codes) - 3 HR departments, 3 employees - 2 projects Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
111 lines
3.4 KiB
Go
111 lines
3.4 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
)
|
|
|
|
// handleSessionCheck verifies the session is valid and returns session info.
|
|
func (s *Server) handleSessionCheck(w http.ResponseWriter, r *http.Request) {
|
|
sess := GetSession(r)
|
|
if sess == nil {
|
|
if cookie, err := r.Cookie("session_id"); err == nil && cookie.Value != "" {
|
|
sess = s.sessions.Get(cookie.Value)
|
|
}
|
|
}
|
|
if sess == nil {
|
|
s.writeJSONRPC(w, nil, nil, &RPCError{
|
|
Code: 100, Message: "Session expired",
|
|
})
|
|
return
|
|
}
|
|
s.writeJSONRPC(w, nil, s.buildSessionInfo(sess), nil)
|
|
}
|
|
|
|
// handleSessionModules returns installed module names.
|
|
func (s *Server) handleSessionModules(w http.ResponseWriter, r *http.Request) {
|
|
s.writeJSONRPC(w, nil, []string{
|
|
"base", "web", "contacts", "sale", "account", "stock",
|
|
"purchase", "crm", "hr", "project", "fleet", "product", "l10n_de",
|
|
}, nil)
|
|
}
|
|
|
|
// handleManifest returns a minimal PWA manifest.
|
|
func (s *Server) handleManifest(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/manifest+json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"name": "Odoo",
|
|
"short_name": "Odoo",
|
|
"start_url": "/web",
|
|
"display": "standalone",
|
|
"background_color": "#71639e",
|
|
"theme_color": "#71639e",
|
|
"icons": []map[string]string{
|
|
{"src": "/web/static/img/odoo-icon-192x192.png", "sizes": "192x192", "type": "image/png"},
|
|
},
|
|
})
|
|
}
|
|
|
|
// handleBootstrapTranslations returns translations for initial boot (login page etc.).
|
|
// Mirrors: odoo/addons/web/controllers/webclient.py bootstrap_translations()
|
|
func (s *Server) handleBootstrapTranslations(w http.ResponseWriter, r *http.Request) {
|
|
lang := "en_US"
|
|
|
|
// Try to detect language from request
|
|
if r.Method == http.MethodPost {
|
|
var req JSONRPCRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err == nil {
|
|
var params struct {
|
|
Mods []string `json:"mods"`
|
|
Lang string `json:"lang"`
|
|
}
|
|
if err := json.Unmarshal(req.Params, ¶ms); err == nil && params.Lang != "" {
|
|
lang = params.Lang
|
|
}
|
|
}
|
|
}
|
|
|
|
langParams := map[string]interface{}{
|
|
"direction": "ltr",
|
|
"date_format": "%%m/%%d/%%Y",
|
|
"time_format": "%%H:%%M:%%S",
|
|
"grouping": "[3,0]",
|
|
"decimal_point": ".",
|
|
"thousands_sep": ",",
|
|
"week_start": 1,
|
|
}
|
|
|
|
// Try to load language parameters from res_lang
|
|
var dateFormat, timeFormat, decimalPoint, thousandsSep, direction string
|
|
err := s.pool.QueryRow(r.Context(),
|
|
`SELECT date_format, time_format, decimal_point, thousands_sep, direction
|
|
FROM res_lang WHERE code = $1 AND active = true`, lang,
|
|
).Scan(&dateFormat, &timeFormat, &decimalPoint, &thousandsSep, &direction)
|
|
if err == nil {
|
|
langParams["date_format"] = dateFormat
|
|
langParams["time_format"] = timeFormat
|
|
langParams["decimal_point"] = decimalPoint
|
|
langParams["thousands_sep"] = thousandsSep
|
|
langParams["direction"] = direction
|
|
}
|
|
|
|
// Check if multiple languages are active
|
|
multiLang := false
|
|
var langCount int
|
|
if err := s.pool.QueryRow(r.Context(),
|
|
`SELECT COUNT(*) FROM res_lang WHERE active = true`).Scan(&langCount); err == nil {
|
|
if langCount > 1 {
|
|
multiLang = true
|
|
}
|
|
}
|
|
|
|
s.writeJSONRPC(w, nil, map[string]interface{}{
|
|
"lang": lang,
|
|
"hash": fmt.Sprintf("odoo-go-boot-%s", lang),
|
|
"lang_parameters": langParams,
|
|
"modules": map[string]interface{}{},
|
|
"multi_lang": multiLang,
|
|
}, nil)
|
|
}
|