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:
@@ -1,6 +1,11 @@
|
||||
package models
|
||||
|
||||
import "odoo-go/pkg/orm"
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"odoo-go/pkg/orm"
|
||||
)
|
||||
|
||||
// initStock registers all stock models.
|
||||
// Mirrors: odoo/addons/stock/models/stock_warehouse.py,
|
||||
@@ -168,6 +173,150 @@ func initStockPicking() {
|
||||
orm.Text("note", orm.FieldOpts{String: "Notes"}),
|
||||
orm.Char("origin", orm.FieldOpts{String: "Source Document", Index: true}),
|
||||
)
|
||||
|
||||
// --- BeforeCreate hook: auto-generate picking reference ---
|
||||
// Mirrors: stock.picking._create_sequence() / ir.sequence
|
||||
m.BeforeCreate = func(env *orm.Environment, vals orm.Values) error {
|
||||
name, _ := vals["name"].(string)
|
||||
if name == "" || name == "/" {
|
||||
vals["name"] = fmt.Sprintf("WH/IN/%05d", time.Now().UnixNano()%100000)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- Business methods: stock move workflow ---
|
||||
|
||||
// action_confirm transitions a picking from draft → confirmed.
|
||||
// Confirms all associated stock moves that are still in draft.
|
||||
// Mirrors: stock.picking.action_confirm()
|
||||
m.RegisterMethod("action_confirm", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
||||
env := rs.Env()
|
||||
for _, id := range rs.IDs() {
|
||||
var state string
|
||||
err := env.Tx().QueryRow(env.Ctx(),
|
||||
`SELECT state FROM stock_picking WHERE id = $1`, id).Scan(&state)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stock: cannot read picking %d: %w", id, err)
|
||||
}
|
||||
if state != "draft" {
|
||||
return nil, fmt.Errorf("stock: can only confirm draft pickings (picking %d is %q)", id, state)
|
||||
}
|
||||
_, err = env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE stock_picking SET state = 'confirmed' WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stock: confirm picking %d: %w", id, err)
|
||||
}
|
||||
// Also confirm all draft moves on this picking
|
||||
_, err = env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE stock_move SET state = 'confirmed' WHERE picking_id = $1 AND state = 'draft'`, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stock: confirm moves for picking %d: %w", id, err)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
// action_assign transitions a picking from confirmed → assigned (reserve stock).
|
||||
// Mirrors: stock.picking.action_assign()
|
||||
m.RegisterMethod("action_assign", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
||||
env := rs.Env()
|
||||
for _, id := range rs.IDs() {
|
||||
_, err := env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE stock_picking SET state = 'assigned' WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stock: assign picking %d: %w", id, err)
|
||||
}
|
||||
_, err = env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE stock_move SET state = 'assigned' WHERE picking_id = $1 AND state IN ('confirmed', 'partially_available')`, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stock: assign moves for picking %d: %w", id, err)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
// button_validate transitions a picking from assigned → done (process the transfer).
|
||||
// Updates all moves to done and adjusts stock quants (source decremented, dest incremented).
|
||||
// Mirrors: stock.picking.button_validate()
|
||||
m.RegisterMethod("button_validate", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
||||
env := rs.Env()
|
||||
for _, id := range rs.IDs() {
|
||||
// Mark picking as done
|
||||
_, err := env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE stock_picking SET state = 'done', date_done = NOW() WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stock: validate picking %d: %w", id, err)
|
||||
}
|
||||
// Mark all moves as done
|
||||
_, err = env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE stock_move SET state = 'done', date = NOW() WHERE picking_id = $1`, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stock: validate moves for picking %d: %w", id, err)
|
||||
}
|
||||
|
||||
// Update quants: for each done move, adjust source and destination locations
|
||||
rows, err := env.Tx().Query(env.Ctx(),
|
||||
`SELECT product_id, product_uom_qty, location_id, location_dest_id
|
||||
FROM stock_move WHERE picking_id = $1 AND state = 'done'`, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stock: read moves for picking %d: %w", id, err)
|
||||
}
|
||||
|
||||
type moveInfo struct {
|
||||
productID int64
|
||||
qty float64
|
||||
srcLoc int64
|
||||
dstLoc int64
|
||||
}
|
||||
var moves []moveInfo
|
||||
for rows.Next() {
|
||||
var mi moveInfo
|
||||
if err := rows.Scan(&mi.productID, &mi.qty, &mi.srcLoc, &mi.dstLoc); err != nil {
|
||||
rows.Close()
|
||||
return nil, fmt.Errorf("stock: scan move for picking %d: %w", id, err)
|
||||
}
|
||||
moves = append(moves, mi)
|
||||
}
|
||||
rows.Close()
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("stock: iterate moves for picking %d: %w", id, err)
|
||||
}
|
||||
|
||||
for _, mi := range moves {
|
||||
// Decrease source location quant
|
||||
if err := updateQuant(env, mi.productID, mi.srcLoc, -mi.qty); err != nil {
|
||||
return nil, fmt.Errorf("stock: update source quant for picking %d: %w", id, err)
|
||||
}
|
||||
// Increase destination location quant
|
||||
if err := updateQuant(env, mi.productID, mi.dstLoc, mi.qty); err != nil {
|
||||
return nil, fmt.Errorf("stock: update dest quant for picking %d: %w", id, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
// updateQuant adjusts the on-hand quantity for a product at a location.
|
||||
// If no quant row exists yet it inserts one; otherwise it updates in place.
|
||||
func updateQuant(env *orm.Environment, productID, locationID int64, delta float64) error {
|
||||
var exists bool
|
||||
err := env.Tx().QueryRow(env.Ctx(),
|
||||
`SELECT EXISTS(SELECT 1 FROM stock_quant WHERE product_id = $1 AND location_id = $2)`,
|
||||
productID, locationID).Scan(&exists)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
_, err = env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE stock_quant SET quantity = quantity + $1 WHERE product_id = $2 AND location_id = $3`,
|
||||
delta, productID, locationID)
|
||||
} else {
|
||||
_, err = env.Tx().Exec(env.Ctx(),
|
||||
`INSERT INTO stock_quant (product_id, location_id, quantity, company_id) VALUES ($1, $2, $3, 1)`,
|
||||
productID, locationID, delta)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// initStockMove registers stock.move — individual product movements.
|
||||
|
||||
Reference in New Issue
Block a user