Deepen all business modules: methods, validations, workflows

Account:
- action_post: partner validation, line count check, sequence number
  assignment (JOURNAL/YYYY/NNNN format)
- action_register_payment: opens payment wizard from invoice
- remove_move_reconcile: undo reconciliation, reset residuals
- Register Payment button in invoice form (visible when posted+unpaid)

Sale:
- action_cancel: cancels linked draft invoices + SO state
- action_draft: reset cancelled SO to draft
- action_view_invoice: navigate to linked invoices
- Cancel/Reset buttons in form view header

Purchase:
- button_draft: reset cancelled PO to draft
- action_create_bill already existed

Stock:
- action_cancel on picking: cancels moves + picking state

CRM:
- action_set_won_rainbowman: sets Won stage + rainbow effect
- convert_opportunity: lead→opportunity type switch

HR:
- hr.contract model (name, employee, wage, dates, state)

Project:
- action_blocked on task (kanban_state)
- Task stage seed data (New, In Progress, Done, Cancelled)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc
2026-04-03 00:58:01 +02:00
parent 0143ddd655
commit cc1f150732
9 changed files with 258 additions and 0 deletions

View File

@@ -584,6 +584,68 @@ func initSaleOrder() {
}, nil
})
// action_cancel: Cancel a sale order and linked draft invoices.
// Mirrors: odoo/addons/sale/models/sale_order.py SaleOrder.action_cancel()
m.RegisterMethod("action_cancel", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
env := rs.Env()
for _, soID := range rs.IDs() {
// Cancel linked draft invoices
rows, _ := env.Tx().Query(env.Ctx(),
`SELECT id FROM account_move WHERE invoice_origin = (SELECT name FROM sale_order WHERE id = $1) AND state = 'draft'`, soID)
for rows.Next() {
var invID int64
rows.Scan(&invID)
env.Tx().Exec(env.Ctx(), `UPDATE account_move SET state = 'cancel' WHERE id = $1`, invID)
}
rows.Close()
env.Tx().Exec(env.Ctx(), `UPDATE sale_order SET state = 'cancel' WHERE id = $1`, soID)
}
return true, nil
})
// action_draft: Reset a cancelled sale order back to quotation.
// Mirrors: odoo/addons/sale/models/sale_order.py SaleOrder.action_draft()
m.RegisterMethod("action_draft", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
env := rs.Env()
for _, soID := range rs.IDs() {
env.Tx().Exec(env.Ctx(), `UPDATE sale_order SET state = 'draft' WHERE id = $1 AND state = 'cancel'`, soID)
}
return true, nil
})
// action_view_invoice: Open invoices linked to this sale order.
// Mirrors: odoo/addons/sale/models/sale_order.py SaleOrder.action_view_invoice()
m.RegisterMethod("action_view_invoice", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
env := rs.Env()
soID := rs.IDs()[0]
var soName string
env.Tx().QueryRow(env.Ctx(), `SELECT name FROM sale_order WHERE id = $1`, soID).Scan(&soName)
// Find invoices linked to this SO
rows, _ := env.Tx().Query(env.Ctx(),
`SELECT id FROM account_move WHERE invoice_origin = $1`, soName)
var invIDs []interface{}
for rows.Next() {
var id int64
rows.Scan(&id)
invIDs = append(invIDs, id)
}
rows.Close()
if len(invIDs) == 1 {
return map[string]interface{}{
"type": "ir.actions.act_window", "res_model": "account.move",
"res_id": invIDs[0], "view_mode": "form",
"views": [][]interface{}{{nil, "form"}}, "target": "current",
}, nil
}
return map[string]interface{}{
"type": "ir.actions.act_window", "res_model": "account.move",
"view_mode": "list,form", "views": [][]interface{}{{nil, "list"}, {nil, "form"}},
"domain": []interface{}{[]interface{}{"id", "in", invIDs}}, "target": "current",
}, nil
})
// action_create_delivery: Generate a stock picking (delivery) from a confirmed sale order.
// Mirrors: odoo/addons/sale/models/sale_order.py SaleOrder._action_confirm() → _create_picking()
m.RegisterMethod("action_create_delivery", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {