Odoo ERP ported to Go — complete backend + original OWL frontend
Full port of Odoo's ERP system from Python to Go, with the original Odoo JavaScript frontend (OWL framework) running against the Go server. Backend (10,691 LoC Go): - Custom ORM: CRUD, domains→SQL with JOINs, computed fields, sequences - 93 models across 14 modules (base, account, sale, stock, purchase, hr, project, crm, fleet, product, l10n_de, google_address/translate/calendar) - Auth with bcrypt + session cookies - Setup wizard (company, SKR03 chart, admin, demo data) - Double-entry bookkeeping constraint - Sale→Invoice workflow (confirm SO → generate invoice → post) - SKR03 chart of accounts (110 accounts) + German taxes (USt/VSt) - Record rules (multi-company filter) - Google integrations as opt-in modules (Maps, Translate, Calendar) Frontend: - Odoo's original OWL webclient (503 JS modules, 378 XML templates) - JS transpiled via Odoo's js_transpiler (ES modules → odoo.define) - SCSS compiled to CSS (675KB) via dart-sass - XML templates compiled to registerTemplate() JS calls - Static file serving from Odoo source addons - Login page, session management, menu navigation - Contacts list view renders with real data from PostgreSQL Infrastructure: - 14MB single binary (CGO_ENABLED=0) - Docker Compose (Go server + PostgreSQL 16) - Zero phone-home (no outbound calls to odoo.com) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
177
addons/purchase/models/purchase_order.go
Normal file
177
addons/purchase/models/purchase_order.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package models
|
||||
|
||||
import "odoo-go/pkg/orm"
|
||||
|
||||
// initPurchaseOrder registers purchase.order and purchase.order.line.
|
||||
// Mirrors: odoo/addons/purchase/models/purchase_order.py
|
||||
|
||||
func initPurchaseOrder() {
|
||||
// purchase.order — the purchase order header
|
||||
m := orm.NewModel("purchase.order", orm.ModelOpts{
|
||||
Description: "Purchase Order",
|
||||
Order: "priority desc, id desc",
|
||||
RecName: "name",
|
||||
})
|
||||
|
||||
// -- Identity --
|
||||
m.AddFields(
|
||||
orm.Char("name", orm.FieldOpts{
|
||||
String: "Order Reference", Required: true, Index: true, Readonly: true, Default: "New",
|
||||
}),
|
||||
orm.Selection("state", []orm.SelectionItem{
|
||||
{Value: "draft", Label: "RFQ"},
|
||||
{Value: "sent", Label: "RFQ Sent"},
|
||||
{Value: "to approve", Label: "To Approve"},
|
||||
{Value: "purchase", Label: "Purchase Order"},
|
||||
{Value: "cancel", Label: "Cancelled"},
|
||||
}, orm.FieldOpts{String: "Status", Default: "draft", Readonly: true, Index: true}),
|
||||
orm.Selection("priority", []orm.SelectionItem{
|
||||
{Value: "0", Label: "Normal"},
|
||||
{Value: "1", Label: "Urgent"},
|
||||
}, orm.FieldOpts{String: "Priority", Default: "0", Index: true}),
|
||||
)
|
||||
|
||||
// -- Partner & Dates --
|
||||
m.AddFields(
|
||||
orm.Many2one("partner_id", "res.partner", orm.FieldOpts{
|
||||
String: "Vendor", Required: true, Index: true,
|
||||
}),
|
||||
orm.Datetime("date_order", orm.FieldOpts{
|
||||
String: "Order Deadline", Required: true, Index: true,
|
||||
}),
|
||||
orm.Datetime("date_planned", orm.FieldOpts{
|
||||
String: "Expected Arrival",
|
||||
}),
|
||||
orm.Datetime("date_approve", orm.FieldOpts{
|
||||
String: "Confirmation Date", Readonly: true, Index: true,
|
||||
}),
|
||||
)
|
||||
|
||||
// -- Company & Currency --
|
||||
m.AddFields(
|
||||
orm.Many2one("company_id", "res.company", orm.FieldOpts{
|
||||
String: "Company", Required: true, Index: true,
|
||||
}),
|
||||
orm.Many2one("currency_id", "res.currency", orm.FieldOpts{
|
||||
String: "Currency", Required: true,
|
||||
}),
|
||||
)
|
||||
|
||||
// -- Lines --
|
||||
m.AddFields(
|
||||
orm.One2many("order_line", "purchase.order.line", "order_id", orm.FieldOpts{
|
||||
String: "Order Lines",
|
||||
}),
|
||||
)
|
||||
|
||||
// -- Amounts --
|
||||
m.AddFields(
|
||||
orm.Monetary("amount_untaxed", orm.FieldOpts{
|
||||
String: "Untaxed Amount", Compute: "_compute_amount", Store: true, CurrencyField: "currency_id",
|
||||
}),
|
||||
orm.Monetary("amount_tax", orm.FieldOpts{
|
||||
String: "Taxes", Compute: "_compute_amount", Store: true, CurrencyField: "currency_id",
|
||||
}),
|
||||
orm.Monetary("amount_total", orm.FieldOpts{
|
||||
String: "Total", Compute: "_compute_amount", Store: true, CurrencyField: "currency_id",
|
||||
}),
|
||||
)
|
||||
|
||||
// -- Invoice Status --
|
||||
m.AddFields(
|
||||
orm.Selection("invoice_status", []orm.SelectionItem{
|
||||
{Value: "no", Label: "Nothing to Bill"},
|
||||
{Value: "to invoice", Label: "Waiting Bills"},
|
||||
{Value: "invoiced", Label: "Fully Billed"},
|
||||
}, orm.FieldOpts{String: "Billing Status", Compute: "_compute_invoice_status", Store: true}),
|
||||
)
|
||||
|
||||
// -- Accounting --
|
||||
m.AddFields(
|
||||
orm.Many2one("fiscal_position_id", "account.fiscal.position", orm.FieldOpts{
|
||||
String: "Fiscal Position",
|
||||
}),
|
||||
orm.Many2one("payment_term_id", "account.payment.term", orm.FieldOpts{
|
||||
String: "Payment Terms",
|
||||
}),
|
||||
)
|
||||
|
||||
// -- Notes --
|
||||
m.AddFields(
|
||||
orm.Text("notes", orm.FieldOpts{String: "Terms and Conditions"}),
|
||||
orm.Char("origin", orm.FieldOpts{String: "Source Document"}),
|
||||
)
|
||||
|
||||
// purchase.order.line — individual line items on a PO
|
||||
initPurchaseOrderLine()
|
||||
}
|
||||
|
||||
// initPurchaseOrderLine registers purchase.order.line.
|
||||
// Mirrors: odoo/addons/purchase/models/purchase_order_line.py
|
||||
func initPurchaseOrderLine() {
|
||||
m := orm.NewModel("purchase.order.line", orm.ModelOpts{
|
||||
Description: "Purchase Order Line",
|
||||
Order: "order_id, sequence, id",
|
||||
})
|
||||
|
||||
// -- Parent --
|
||||
m.AddFields(
|
||||
orm.Many2one("order_id", "purchase.order", orm.FieldOpts{
|
||||
String: "Order Reference", Required: true, OnDelete: orm.OnDeleteCascade, Index: true,
|
||||
}),
|
||||
orm.Integer("sequence", orm.FieldOpts{String: "Sequence", Default: 10}),
|
||||
)
|
||||
|
||||
// -- Product --
|
||||
m.AddFields(
|
||||
orm.Many2one("product_id", "product.product", orm.FieldOpts{
|
||||
String: "Product", Index: true,
|
||||
}),
|
||||
orm.Char("name", orm.FieldOpts{String: "Description", Required: true}),
|
||||
orm.Float("product_qty", orm.FieldOpts{String: "Quantity", Required: true, Default: 1.0}),
|
||||
orm.Many2one("product_uom", "uom.uom", orm.FieldOpts{
|
||||
String: "Unit of Measure", Required: true,
|
||||
}),
|
||||
)
|
||||
|
||||
// -- Pricing --
|
||||
m.AddFields(
|
||||
orm.Float("price_unit", orm.FieldOpts{String: "Unit Price", Required: true}),
|
||||
orm.Many2many("tax_ids", "account.tax", orm.FieldOpts{String: "Taxes"}),
|
||||
orm.Float("discount", orm.FieldOpts{String: "Discount (%)", Default: 0.0}),
|
||||
orm.Monetary("price_subtotal", orm.FieldOpts{
|
||||
String: "Subtotal", Compute: "_compute_amount", Store: true, CurrencyField: "currency_id",
|
||||
}),
|
||||
orm.Monetary("price_total", orm.FieldOpts{
|
||||
String: "Total", Compute: "_compute_amount", Store: true, CurrencyField: "currency_id",
|
||||
}),
|
||||
orm.Many2one("currency_id", "res.currency", orm.FieldOpts{
|
||||
String: "Currency",
|
||||
}),
|
||||
)
|
||||
|
||||
// -- Dates --
|
||||
m.AddFields(
|
||||
orm.Datetime("date_planned", orm.FieldOpts{String: "Expected Arrival"}),
|
||||
)
|
||||
|
||||
// -- Quantities --
|
||||
m.AddFields(
|
||||
orm.Float("qty_received", orm.FieldOpts{
|
||||
String: "Received Qty", Compute: "_compute_qty_received", Store: true,
|
||||
}),
|
||||
orm.Float("qty_invoiced", orm.FieldOpts{
|
||||
String: "Billed Qty", Compute: "_compute_qty_invoiced", Store: true,
|
||||
}),
|
||||
orm.Float("qty_to_invoice", orm.FieldOpts{
|
||||
String: "To Invoice Quantity", Compute: "_compute_qty_to_invoice", Store: true,
|
||||
}),
|
||||
)
|
||||
|
||||
// -- Company --
|
||||
m.AddFields(
|
||||
orm.Many2one("company_id", "res.company", orm.FieldOpts{
|
||||
String: "Company", Index: true,
|
||||
}),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user