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:
@@ -3,4 +3,5 @@ package models
|
||||
func Init() {
|
||||
initPurchaseOrder()
|
||||
initPurchaseOrderLine()
|
||||
initPurchaseAgreement()
|
||||
}
|
||||
|
||||
81
addons/purchase/models/purchase_agreement.go
Normal file
81
addons/purchase/models/purchase_agreement.go
Normal 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"}),
|
||||
)
|
||||
}
|
||||
@@ -199,7 +199,7 @@ func initPurchaseOrder() {
|
||||
|
||||
// Read PO lines to generate invoice lines
|
||||
rows, err := env.Tx().Query(env.Ctx(),
|
||||
`SELECT COALESCE(name,''), COALESCE(product_qty,1), COALESCE(price_unit,0), COALESCE(discount,0)
|
||||
`SELECT id, COALESCE(name,''), COALESCE(product_qty,1), COALESCE(price_unit,0), COALESCE(discount,0)
|
||||
FROM purchase_order_line
|
||||
WHERE order_id = $1 ORDER BY sequence, id`, poID)
|
||||
if err != nil {
|
||||
@@ -207,6 +207,7 @@ func initPurchaseOrder() {
|
||||
}
|
||||
|
||||
type poLine struct {
|
||||
id int64
|
||||
name string
|
||||
qty float64
|
||||
price float64
|
||||
@@ -215,7 +216,7 @@ func initPurchaseOrder() {
|
||||
var lines []poLine
|
||||
for rows.Next() {
|
||||
var l poLine
|
||||
if err := rows.Scan(&l.name, &l.qty, &l.price, &l.discount); err != nil {
|
||||
if err := rows.Scan(&l.id, &l.name, &l.qty, &l.price, &l.discount); err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
@@ -258,6 +259,13 @@ func initPurchaseOrder() {
|
||||
companyID, journalID)
|
||||
}
|
||||
|
||||
// Update qty_invoiced on PO lines
|
||||
for _, l := range lines {
|
||||
env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE purchase_order_line SET qty_invoiced = COALESCE(qty_invoiced, 0) + $1 WHERE id = $2`,
|
||||
l.qty, l.id)
|
||||
}
|
||||
|
||||
billIDs = append(billIDs, billID)
|
||||
|
||||
// Update PO invoice_status
|
||||
|
||||
Reference in New Issue
Block a user