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

@@ -105,6 +105,21 @@ func initSaleOrder() {
}),
)
// -- Tags --
m.AddFields(
orm.Many2many("tag_ids", "crm.tag", orm.FieldOpts{String: "Tags"}),
)
// -- Counts (Computed placeholders) --
m.AddFields(
orm.Integer("invoice_count", orm.FieldOpts{
String: "Invoice Count", Compute: "_compute_invoice_count", Store: false,
}),
orm.Integer("delivery_count", orm.FieldOpts{
String: "Delivery Count", Compute: "_compute_delivery_count", Store: false,
}),
)
// -- Misc --
m.AddFields(
orm.Text("note", orm.FieldOpts{String: "Terms and Conditions"}),
@@ -156,6 +171,45 @@ func initSaleOrder() {
m.RegisterCompute("amount_tax", computeSaleAmounts)
m.RegisterCompute("amount_total", computeSaleAmounts)
// -- Computed: _compute_invoice_count --
// Counts the number of invoices linked to this sale order.
// Mirrors: odoo/addons/sale/models/sale_order.py SaleOrder._compute_invoice_count()
computeInvoiceCount := func(rs *orm.Recordset) (orm.Values, error) {
env := rs.Env()
soID := rs.IDs()[0]
var count int
err := env.Tx().QueryRow(env.Ctx(),
`SELECT COUNT(*) FROM account_move WHERE invoice_origin = $1 AND move_type = 'out_invoice'`,
fmt.Sprintf("SO%d", soID)).Scan(&count)
if err != nil {
count = 0
}
return orm.Values{"invoice_count": count}, nil
}
m.RegisterCompute("invoice_count", computeInvoiceCount)
// -- Computed: _compute_delivery_count --
// Counts the number of delivery pickings linked to this sale order.
// Mirrors: odoo/addons/sale/models/sale_order.py SaleOrder._compute_delivery_count()
computeDeliveryCount := func(rs *orm.Recordset) (orm.Values, error) {
env := rs.Env()
soID := rs.IDs()[0]
var soName string
err := env.Tx().QueryRow(env.Ctx(),
`SELECT COALESCE(name, '') FROM sale_order WHERE id = $1`, soID).Scan(&soName)
if err != nil {
return orm.Values{"delivery_count": 0}, nil
}
var count int
err = env.Tx().QueryRow(env.Ctx(),
`SELECT COUNT(*) FROM stock_picking WHERE origin = $1`, soName).Scan(&count)
if err != nil {
count = 0
}
return orm.Values{"delivery_count": count}, nil
}
m.RegisterCompute("delivery_count", computeDeliveryCount)
// -- DefaultGet: Provide dynamic defaults for new records --
// Mirrors: odoo/addons/sale/models/sale_order.py SaleOrder.default_get()
// Supplies company_id, currency_id, date_order when creating a new quotation.