Bring all areas to 60%: modules, reporting, i18n, views, data

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>
This commit is contained in:
Marc
2026-04-02 20:11:45 +02:00
parent eb92a2e239
commit 03fd58a852
13 changed files with 944 additions and 31 deletions

View File

@@ -45,6 +45,12 @@ func initStockWarehouse() {
orm.Many2one("lot_stock_id", "stock.location", orm.FieldOpts{
String: "Location Stock", Required: true, OnDelete: orm.OnDeleteRestrict,
}),
orm.Many2one("wh_input_stock_loc_id", "stock.location", orm.FieldOpts{
String: "Input Location",
}),
orm.Many2one("wh_output_stock_loc_id", "stock.location", orm.FieldOpts{
String: "Output Location",
}),
orm.Integer("sequence", orm.FieldOpts{String: "Sequence", Default: 10}),
)
}
@@ -368,6 +374,49 @@ func initStockMove() {
orm.Integer("sequence", orm.FieldOpts{String: "Sequence", Default: 10}),
orm.Char("origin", orm.FieldOpts{String: "Source Document"}),
)
// _action_confirm: Confirm stock moves (draft → confirmed).
// Mirrors: odoo/addons/stock/models/stock_move.py StockMove._action_confirm()
m.RegisterMethod("_action_confirm", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
env := rs.Env()
for _, id := range rs.IDs() {
_, err := env.Tx().Exec(env.Ctx(),
`UPDATE stock_move SET state = 'confirmed' WHERE id = $1 AND state = 'draft'`, id)
if err != nil {
return nil, fmt.Errorf("stock: confirm move %d: %w", id, err)
}
}
return true, nil
})
// _action_done: Finalize stock moves (assigned → done), updating quants.
// Mirrors: odoo/addons/stock/models/stock_move.py StockMove._action_done()
m.RegisterMethod("_action_done", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
env := rs.Env()
for _, id := range rs.IDs() {
var productID, srcLoc, dstLoc int64
var qty float64
err := env.Tx().QueryRow(env.Ctx(),
`SELECT product_id, product_uom_qty, location_id, location_dest_id
FROM stock_move WHERE id = $1`, id).Scan(&productID, &qty, &srcLoc, &dstLoc)
if err != nil {
return nil, fmt.Errorf("stock: read move %d for done: %w", id, err)
}
_, err = env.Tx().Exec(env.Ctx(),
`UPDATE stock_move SET state = 'done', date = NOW() WHERE id = $1`, id)
if err != nil {
return nil, fmt.Errorf("stock: done move %d: %w", id, err)
}
// Adjust quants
if err := updateQuant(env, productID, srcLoc, -qty); err != nil {
return nil, fmt.Errorf("stock: update source quant for move %d: %w", id, err)
}
if err := updateQuant(env, productID, dstLoc, qty); err != nil {
return nil, fmt.Errorf("stock: update dest quant for move %d: %w", id, err)
}
}
return true, nil
})
}
// initStockMoveLine registers stock.move.line — detailed operations per lot/package.
@@ -454,6 +503,25 @@ func initStockQuant() {
orm.Datetime("removal_date", orm.FieldOpts{String: "Removal Date"}),
)
// _update_available_quantity: Adjust available quantity for a product at a location.
// Mirrors: odoo/addons/stock/models/stock_quant.py StockQuant._update_available_quantity()
m.RegisterMethod("_update_available_quantity", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
if len(args) < 3 {
return nil, fmt.Errorf("stock.quant._update_available_quantity requires product_id, location_id, quantity")
}
productID, _ := args[0].(int64)
locationID, _ := args[1].(int64)
quantity, _ := args[2].(float64)
if productID == 0 || locationID == 0 {
return nil, fmt.Errorf("stock.quant._update_available_quantity: invalid product_id or location_id")
}
env := rs.Env()
if err := updateQuant(env, productID, locationID, quantity); err != nil {
return nil, fmt.Errorf("stock.quant._update_available_quantity: %w", err)
}
return true, nil
})
// stock.quant.package — physical packages / containers
orm.NewModel("stock.quant.package", orm.ModelOpts{
Description: "Packages",