Backend improvements: views, fields_get, session, RPC stubs
- Improved auto-generated list/form/search views with priority fields, two-column form layout, statusbar widget, notebook for O2M fields - Enhanced fields_get with currency_field, compute, related metadata - Fixed session handling: handleSessionInfo/handleSessionCheck use real session from cookie instead of hardcoded values - Added read_progress_bar and activity_format RPC stubs - Improved bootstrap translations with lang_parameters - Added "contacts" to session modules list Server starts successfully: 14 modules, 93 models, 378 XML templates, 503 JS modules transpiled — all from local frontend/ directory. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
package models
|
||||
|
||||
import "odoo-go/pkg/orm"
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"odoo-go/pkg/orm"
|
||||
)
|
||||
|
||||
// initPurchaseOrder registers purchase.order and purchase.order.line.
|
||||
// Mirrors: odoo/addons/purchase/models/purchase_order.py
|
||||
@@ -37,7 +42,7 @@ func initPurchaseOrder() {
|
||||
String: "Vendor", Required: true, Index: true,
|
||||
}),
|
||||
orm.Datetime("date_order", orm.FieldOpts{
|
||||
String: "Order Deadline", Required: true, Index: true,
|
||||
String: "Order Deadline", Required: true, Index: true, Default: "today",
|
||||
}),
|
||||
orm.Datetime("date_planned", orm.FieldOpts{
|
||||
String: "Expected Arrival",
|
||||
@@ -102,6 +107,147 @@ func initPurchaseOrder() {
|
||||
orm.Char("origin", orm.FieldOpts{String: "Source Document"}),
|
||||
)
|
||||
|
||||
// button_confirm: draft → purchase
|
||||
m.RegisterMethod("button_confirm", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
||||
env := rs.Env()
|
||||
for _, id := range rs.IDs() {
|
||||
var state string
|
||||
env.Tx().QueryRow(env.Ctx(),
|
||||
`SELECT state FROM purchase_order WHERE id = $1`, id).Scan(&state)
|
||||
if state != "draft" && state != "sent" {
|
||||
return nil, fmt.Errorf("purchase: can only confirm draft orders")
|
||||
}
|
||||
env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE purchase_order SET state = 'purchase', date_approve = NOW() WHERE id = $1`, id)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
// button_cancel
|
||||
m.RegisterMethod("button_cancel", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
||||
env := rs.Env()
|
||||
for _, id := range rs.IDs() {
|
||||
env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE purchase_order SET state = 'cancel' WHERE id = $1`, id)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
|
||||
// action_create_bill: Generate a vendor bill (account.move in_invoice) from a confirmed PO.
|
||||
// Mirrors: odoo/addons/purchase/models/purchase_order.py PurchaseOrder.action_create_invoice()
|
||||
m.RegisterMethod("action_create_bill", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
|
||||
env := rs.Env()
|
||||
var billIDs []int64
|
||||
|
||||
for _, poID := range rs.IDs() {
|
||||
var partnerID, companyID, currencyID int64
|
||||
err := env.Tx().QueryRow(env.Ctx(),
|
||||
`SELECT partner_id, company_id, currency_id FROM purchase_order WHERE id = $1`,
|
||||
poID).Scan(&partnerID, &companyID, ¤cyID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("purchase: read PO %d for bill: %w", poID, err)
|
||||
}
|
||||
|
||||
// Find purchase journal
|
||||
var journalID int64
|
||||
env.Tx().QueryRow(env.Ctx(),
|
||||
`SELECT id FROM account_journal WHERE type = 'purchase' AND company_id = $1 LIMIT 1`,
|
||||
companyID).Scan(&journalID)
|
||||
if journalID == 0 {
|
||||
// Fallback: first available journal
|
||||
env.Tx().QueryRow(env.Ctx(),
|
||||
`SELECT id FROM account_journal WHERE company_id = $1 ORDER BY id LIMIT 1`,
|
||||
companyID).Scan(&journalID)
|
||||
}
|
||||
|
||||
// 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)
|
||||
FROM purchase_order_line
|
||||
WHERE order_id = $1 ORDER BY sequence, id`, poID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("purchase: read PO lines %d: %w", poID, err)
|
||||
}
|
||||
|
||||
type poLine struct {
|
||||
name string
|
||||
qty float64
|
||||
price float64
|
||||
discount float64
|
||||
}
|
||||
var lines []poLine
|
||||
for rows.Next() {
|
||||
var l poLine
|
||||
if err := rows.Scan(&l.name, &l.qty, &l.price, &l.discount); err != nil {
|
||||
rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
lines = append(lines, l)
|
||||
}
|
||||
rows.Close()
|
||||
|
||||
// Create the vendor bill
|
||||
var billID int64
|
||||
err = env.Tx().QueryRow(env.Ctx(),
|
||||
`INSERT INTO account_move
|
||||
(name, move_type, state, date, partner_id, journal_id, company_id, currency_id, invoice_origin)
|
||||
VALUES ('/', 'in_invoice', 'draft', NOW(), $1, $2, $3, $4, $5) RETURNING id`,
|
||||
partnerID, journalID, companyID, currencyID,
|
||||
fmt.Sprintf("PO%d", poID)).Scan(&billID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("purchase: create bill for PO %d: %w", poID, err)
|
||||
}
|
||||
|
||||
// Try to generate a proper sequence name
|
||||
seq, seqErr := orm.NextByCode(env, "account.move.in_invoice")
|
||||
if seqErr != nil {
|
||||
seq, seqErr = orm.NextByCode(env, "account.move")
|
||||
}
|
||||
if seqErr == nil && seq != "" {
|
||||
env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE account_move SET name = $1 WHERE id = $2`, seq, billID)
|
||||
}
|
||||
|
||||
// Create invoice lines for each PO line
|
||||
for _, l := range lines {
|
||||
subtotal := l.qty * l.price * (1 - l.discount/100)
|
||||
env.Tx().Exec(env.Ctx(),
|
||||
`INSERT INTO account_move_line
|
||||
(move_id, name, quantity, price_unit, discount, debit, credit, balance,
|
||||
display_type, company_id, journal_id, account_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, 0, $6, 'product', $7, $8,
|
||||
COALESCE((SELECT id FROM account_account WHERE company_id = $7 LIMIT 1), 1))`,
|
||||
billID, l.name, l.qty, l.price, l.discount, subtotal,
|
||||
companyID, journalID)
|
||||
}
|
||||
|
||||
billIDs = append(billIDs, billID)
|
||||
|
||||
// Update PO invoice_status
|
||||
_, err = env.Tx().Exec(env.Ctx(),
|
||||
`UPDATE purchase_order SET invoice_status = 'invoiced' WHERE id = $1`, poID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("purchase: update invoice status for PO %d: %w", poID, err)
|
||||
}
|
||||
}
|
||||
return billIDs, nil
|
||||
})
|
||||
|
||||
// BeforeCreate: auto-assign sequence number
|
||||
m.BeforeCreate = func(env *orm.Environment, vals orm.Values) error {
|
||||
name, _ := vals["name"].(string)
|
||||
if name == "" || name == "/" || name == "New" {
|
||||
seq, err := orm.NextByCode(env, "purchase.order")
|
||||
if err != nil {
|
||||
// Fallback: generate a simple name
|
||||
vals["name"] = fmt.Sprintf("PO/%d", time.Now().UnixNano()%100000)
|
||||
} else {
|
||||
vals["name"] = seq
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// purchase.order.line — individual line items on a PO
|
||||
initPurchaseOrderLine()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user