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:
@@ -198,19 +198,63 @@ func initProductPricelist() {
|
||||
orm.Many2one("company_id", "res.company", orm.FieldOpts{String: "Company"}),
|
||||
orm.Integer("sequence", orm.FieldOpts{String: "Sequence", Default: 16}),
|
||||
orm.One2many("item_ids", "product.pricelist.item", "pricelist_id", orm.FieldOpts{String: "Pricelist Rules"}),
|
||||
orm.Many2one("country_group_id", "res.country.group", orm.FieldOpts{String: "Country Group"}),
|
||||
)
|
||||
|
||||
// get_product_price: returns the price for a product in this pricelist.
|
||||
// Mirrors: odoo/addons/product/models/product_pricelist.py Pricelist._get_product_price()
|
||||
m.RegisterMethod("get_product_price", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
||||
if len(args) < 2 {
|
||||
return float64(0), nil
|
||||
}
|
||||
env := rs.Env()
|
||||
pricelistID := rs.IDs()[0]
|
||||
productID, _ := args[0].(float64)
|
||||
qty, _ := args[1].(float64)
|
||||
if qty <= 0 {
|
||||
qty = 1
|
||||
}
|
||||
|
||||
// Find matching pricelist item
|
||||
var price float64
|
||||
err := env.Tx().QueryRow(env.Ctx(), `
|
||||
SELECT CASE
|
||||
WHEN pi.compute_price = 'fixed' THEN pi.fixed_price
|
||||
WHEN pi.compute_price = 'percentage' THEN pt.list_price * (1 - pi.percent_price / 100)
|
||||
ELSE pt.list_price
|
||||
END
|
||||
FROM product_pricelist_item pi
|
||||
JOIN product_product pp ON pp.id = $2
|
||||
JOIN product_template pt ON pt.id = pp.product_tmpl_id
|
||||
WHERE pi.pricelist_id = $1
|
||||
AND (pi.product_id = $2 OR pi.product_id IS NULL)
|
||||
AND (pi.min_quantity <= $3 OR pi.min_quantity IS NULL)
|
||||
ORDER BY pi.sequence, pi.id LIMIT 1`,
|
||||
pricelistID, int64(productID), qty,
|
||||
).Scan(&price)
|
||||
|
||||
if err != nil {
|
||||
// Fallback to list price
|
||||
env.Tx().QueryRow(env.Ctx(),
|
||||
`SELECT pt.list_price FROM product_product pp JOIN product_template pt ON pt.id = pp.product_tmpl_id WHERE pp.id = $1`,
|
||||
int64(productID)).Scan(&price)
|
||||
}
|
||||
return price, nil
|
||||
})
|
||||
|
||||
// product.pricelist.item — Price rules
|
||||
orm.NewModel("product.pricelist.item", orm.ModelOpts{
|
||||
Description: "Pricelist Rule",
|
||||
Order: "applied_on, min_quantity desc, categ_id desc, id desc",
|
||||
Order: "sequence, id",
|
||||
}).AddFields(
|
||||
orm.Many2one("pricelist_id", "product.pricelist", orm.FieldOpts{
|
||||
String: "Pricelist", Required: true, OnDelete: orm.OnDeleteCascade, Index: true,
|
||||
}),
|
||||
orm.Many2one("product_tmpl_id", "product.template", orm.FieldOpts{String: "Product"}),
|
||||
orm.Many2one("product_id", "product.product", orm.FieldOpts{String: "Product Variant"}),
|
||||
orm.Many2one("product_tmpl_id", "product.template", orm.FieldOpts{String: "Product Template"}),
|
||||
orm.Many2one("categ_id", "product.category", orm.FieldOpts{String: "Product Category"}),
|
||||
orm.Float("min_quantity", orm.FieldOpts{String: "Min. Quantity"}),
|
||||
orm.Integer("sequence", orm.FieldOpts{String: "Sequence", Default: 5}),
|
||||
orm.Selection("applied_on", []orm.SelectionItem{
|
||||
{Value: "3_global", Label: "All Products"},
|
||||
{Value: "2_product_category", Label: "Product Category"},
|
||||
|
||||
Reference in New Issue
Block a user