Bring odoo-go to ~70%: read_group, record rules, admin, sessions
Phase 1: read_group/web_read_group with SQL GROUP BY, aggregates (sum/avg/min/max/count/array_agg/sum_currency), date granularity, M2O groupby resolution to [id, display_name]. Phase 2: Record rules with domain_force parsing (Python literal parser), global AND + group OR merging. Domain operators: child_of, parent_of, any, not any compiled to SQL hierarchy/EXISTS queries. Phase 3: Button dispatch via /web/dataset/call_button, method return values interpreted as actions. Payment register wizard (account.payment.register) for sale→invoice→pay flow. Phase 4: ir.filters, ir.default, product fields expanded, SO line product_id onchange, ir_model+ir_model_fields DB seeding. Phase 5: CSV export (/web/export/csv), attachment upload/download via ir.attachment, fields_get with aggregator hints. Admin/System: Session persistence (PostgreSQL-backed), ir.config_parameter with get_param/set_param, ir.cron, ir.logging, res.lang, res.config.settings with company-related fields, Settings form view. Technical menu with Views/Actions/Parameters/Security/Logging sub-menus. User change_password, preferences. Password never exposed in UI/API. Bugfixes: false→nil for varchar/int fields, int32 in toInt64, call_button route with trailing slash, create_invoices returns action, search view always included, get_formview_action, name_create, ir.http stub. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,14 +7,20 @@ import "odoo-go/pkg/orm"
|
||||
func initProductCategory() {
|
||||
m := orm.NewModel("product.category", orm.ModelOpts{
|
||||
Description: "Product Category",
|
||||
Order: "name",
|
||||
Order: "complete_name",
|
||||
})
|
||||
|
||||
m.AddFields(
|
||||
orm.Char("name", orm.FieldOpts{String: "Name", Required: true, Translate: true}),
|
||||
orm.Char("complete_name", orm.FieldOpts{
|
||||
String: "Complete Name", Compute: "_compute_complete_name", Store: true,
|
||||
}),
|
||||
orm.Many2one("parent_id", "product.category", orm.FieldOpts{
|
||||
String: "Parent Category", Index: true, OnDelete: orm.OnDeleteCascade,
|
||||
}),
|
||||
orm.One2many("child_id", "product.category", "parent_id", orm.FieldOpts{
|
||||
String: "Child Categories",
|
||||
}),
|
||||
orm.Many2one("property_account_income_categ_id", "account.account", orm.FieldOpts{
|
||||
String: "Income Account",
|
||||
Help: "This account will be used when validating a customer invoice.",
|
||||
@@ -83,6 +89,8 @@ func initProductTemplate() {
|
||||
}, orm.FieldOpts{String: "Product Type", Required: true, Default: "consu"}),
|
||||
orm.Float("list_price", orm.FieldOpts{String: "Sales Price", Default: 1.0}),
|
||||
orm.Float("standard_price", orm.FieldOpts{String: "Cost"}),
|
||||
orm.Char("default_code", orm.FieldOpts{String: "Internal Reference", Index: true}),
|
||||
orm.Char("barcode", orm.FieldOpts{String: "Barcode", Index: true}),
|
||||
orm.Many2one("categ_id", "product.category", orm.FieldOpts{
|
||||
String: "Product Category", Required: true,
|
||||
}),
|
||||
@@ -93,6 +101,15 @@ func initProductTemplate() {
|
||||
orm.Boolean("purchase_ok", orm.FieldOpts{String: "Can be Purchased", Default: true}),
|
||||
orm.Boolean("active", orm.FieldOpts{String: "Active", Default: true}),
|
||||
orm.Text("description", orm.FieldOpts{String: "Description", Translate: true}),
|
||||
orm.Text("description_sale", orm.FieldOpts{
|
||||
String: "Sales Description", Translate: true,
|
||||
Help: "Description used in sales orders, quotations, and invoices.",
|
||||
}),
|
||||
orm.Text("description_purchase", orm.FieldOpts{
|
||||
String: "Purchase Description", Translate: true,
|
||||
Help: "Description used in purchase orders.",
|
||||
}),
|
||||
orm.Binary("image_1920", orm.FieldOpts{String: "Image"}),
|
||||
orm.Many2many("taxes_id", "account.tax", orm.FieldOpts{
|
||||
String: "Customer Taxes",
|
||||
Help: "Default taxes used when selling the product.",
|
||||
@@ -106,6 +123,10 @@ func initProductTemplate() {
|
||||
|
||||
// initProductProduct registers product.product — a concrete product variant.
|
||||
// Mirrors: odoo/addons/product/models/product_product.py
|
||||
//
|
||||
// In Odoo, product.product delegates to product.template via _inherits.
|
||||
// Here we define the variant-specific fields plus Related fields that proxy
|
||||
// key template fields for convenience (the web client reads these directly).
|
||||
func initProductProduct() {
|
||||
m := orm.NewModel("product.product", orm.ModelOpts{
|
||||
Description: "Product",
|
||||
@@ -116,11 +137,48 @@ func initProductProduct() {
|
||||
orm.Many2one("product_tmpl_id", "product.template", orm.FieldOpts{
|
||||
String: "Product Template", Required: true, OnDelete: orm.OnDeleteCascade, Index: true,
|
||||
}),
|
||||
|
||||
// Variant-specific fields
|
||||
orm.Char("default_code", orm.FieldOpts{String: "Internal Reference", Index: true}),
|
||||
orm.Char("barcode", orm.FieldOpts{String: "Barcode", Index: true}),
|
||||
orm.Float("volume", orm.FieldOpts{String: "Volume", Help: "The volume in m3."}),
|
||||
orm.Float("weight", orm.FieldOpts{String: "Weight", Help: "The weight in kg."}),
|
||||
orm.Boolean("active", orm.FieldOpts{String: "Active", Default: true}),
|
||||
|
||||
// Related fields from product.template — proxied for direct access.
|
||||
// Mirrors: product_product.py fields with related='product_tmpl_id.xxx'
|
||||
orm.Char("name", orm.FieldOpts{
|
||||
String: "Name", Related: "product_tmpl_id.name",
|
||||
}),
|
||||
orm.Float("lst_price", orm.FieldOpts{
|
||||
String: "Public Price", Related: "product_tmpl_id.list_price",
|
||||
}),
|
||||
orm.Float("standard_price", orm.FieldOpts{
|
||||
String: "Cost", Related: "product_tmpl_id.standard_price",
|
||||
}),
|
||||
orm.Many2one("categ_id", "product.category", orm.FieldOpts{
|
||||
String: "Product Category", Related: "product_tmpl_id.categ_id",
|
||||
}),
|
||||
orm.Many2one("uom_id", "uom.uom", orm.FieldOpts{
|
||||
String: "Unit of Measure", Related: "product_tmpl_id.uom_id",
|
||||
}),
|
||||
orm.Selection("type", []orm.SelectionItem{
|
||||
{Value: "consu", Label: "Consumable"},
|
||||
{Value: "service", Label: "Service"},
|
||||
{Value: "storable", Label: "Storable Product"},
|
||||
}, orm.FieldOpts{String: "Product Type", Related: "product_tmpl_id.type"}),
|
||||
orm.Boolean("sale_ok", orm.FieldOpts{
|
||||
String: "Can be Sold", Related: "product_tmpl_id.sale_ok",
|
||||
}),
|
||||
orm.Boolean("purchase_ok", orm.FieldOpts{
|
||||
String: "Can be Purchased", Related: "product_tmpl_id.purchase_ok",
|
||||
}),
|
||||
orm.Text("description_sale", orm.FieldOpts{
|
||||
String: "Sales Description", Related: "product_tmpl_id.description_sale",
|
||||
}),
|
||||
orm.Binary("image_1920", orm.FieldOpts{
|
||||
String: "Image", Related: "product_tmpl_id.image_1920",
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user