Deep dive: Account, Stock, Sale, Purchase — +800 LOC business logic

Account:
- Multi-currency: company_currency_id, amount_total_signed
- Lock dates on res.company (period, fiscal year, tax) + enforcement in action_post
- Recurring entries: account.move.recurring with action_generate (copy+advance)
- Tax groups: amount_type='group' computes child taxes with include_base_amount
- ComputeTaxes batch function, findTaxAccount helper

Stock:
- Lot/Serial tracking: enhanced stock.lot with expiration dates + qty compute
- Routes: stock.route model with product/category/warehouse selectable flags
- Rules: stock.rule model with pull/push/buy/manufacture actions + procure methods
- Returns: action_return on picking (swap locations, copy moves)
- Product tracking extension (none/lot/serial) + route_ids M2M

Sale:
- Pricelist: get_product_price with fixed/percentage/formula computation
- Margin: purchase_price, margin, margin_percent on line + order totals
- Down payments: action_create_down_payment (deposit invoice at X%)

Purchase:
- 3-way matching: action_create_bill now updates qty_invoiced on PO lines
- Purchase agreements: purchase.requisition + line with state machine

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc
2026-04-03 19:05:39 +02:00
parent d9171191af
commit b8fa4719ad
14 changed files with 802 additions and 17 deletions

View File

@@ -0,0 +1,81 @@
package models
import "odoo-go/pkg/orm"
// initPurchaseAgreement registers purchase.requisition and purchase.requisition.line.
// Mirrors: odoo/addons/purchase_requisition/models/purchase_requisition.py
func initPurchaseAgreement() {
m := orm.NewModel("purchase.requisition", orm.ModelOpts{
Description: "Purchase Agreement",
Order: "name desc",
})
m.AddFields(
orm.Char("name", orm.FieldOpts{String: "Reference", Readonly: true, Default: "New"}),
orm.Many2one("user_id", "res.users", orm.FieldOpts{String: "Responsible"}),
orm.Selection("type_id", []orm.SelectionItem{
{Value: "blanket", Label: "Blanket Order"},
{Value: "purchase", Label: "Purchase Tender"},
}, orm.FieldOpts{String: "Agreement Type", Default: "blanket"}),
orm.Selection("state", []orm.SelectionItem{
{Value: "draft", Label: "Draft"},
{Value: "ongoing", Label: "Confirmed"},
{Value: "in_progress", Label: "Bid Selection"},
{Value: "open", Label: "Bid Selection"},
{Value: "done", Label: "Closed"},
{Value: "cancel", Label: "Cancelled"},
}, orm.FieldOpts{String: "Status", Default: "draft"}),
orm.Many2one("company_id", "res.company", orm.FieldOpts{String: "Company"}),
orm.Date("date_end", orm.FieldOpts{String: "Agreement Deadline"}),
orm.One2many("line_ids", "purchase.requisition.line", "requisition_id", orm.FieldOpts{String: "Lines"}),
)
// action_confirm: draft → ongoing
m.RegisterMethod("action_confirm", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
env := rs.Env()
for _, id := range rs.IDs() {
env.Tx().Exec(env.Ctx(),
`UPDATE purchase_requisition SET state = 'ongoing' WHERE id = $1 AND state = 'draft'`, id)
}
return true, nil
})
// action_done: close the agreement
m.RegisterMethod("action_done", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
env := rs.Env()
for _, id := range rs.IDs() {
env.Tx().Exec(env.Ctx(),
`UPDATE purchase_requisition SET state = 'done' WHERE id = $1`, id)
}
return true, nil
})
// action_cancel
m.RegisterMethod("action_cancel", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
env := rs.Env()
for _, id := range rs.IDs() {
env.Tx().Exec(env.Ctx(),
`UPDATE purchase_requisition SET state = 'cancel' WHERE id = $1`, id)
}
return true, nil
})
initPurchaseRequisitionLine()
}
func initPurchaseRequisitionLine() {
orm.NewModel("purchase.requisition.line", orm.ModelOpts{
Description: "Purchase Agreement Line",
Order: "id",
}).AddFields(
orm.Many2one("requisition_id", "purchase.requisition", orm.FieldOpts{
String: "Agreement", Required: true, OnDelete: orm.OnDeleteCascade,
}),
orm.Many2one("product_id", "product.product", orm.FieldOpts{
String: "Product", Required: true,
}),
orm.Float("product_qty", orm.FieldOpts{String: "Quantity"}),
orm.Float("price_unit", orm.FieldOpts{String: "Unit Price"}),
)
}