Account module massive expansion: 2499→5049 LOC (+2550)
New models (12): - account.asset: depreciation (linear/degressive), journal entry generation - account.edi.format + account.edi.document: UBL 2.1 XML e-invoicing - account.followup.line: payment follow-up escalation levels - account.reconcile.model + lines: automatic bank reconciliation rules - crossovered.budget + lines + account.budget.post: budgeting system - account.cash.rounding: invoice rounding (UP/DOWN/HALF-UP) - account.payment.method + lines: payment method definitions - account.invoice.send: invoice sending wizard Enhanced existing: - account.move: action_reverse (credit notes), access_url, invoice_has_outstanding - account.move.line: tax_tag_ids, analytic_distribution, date_maturity, matching_number - Entry hash chain integrity (SHA-256, secure_sequence_number) - Report HTML rendering for all 6 report types - res.partner extended with followup status + overdue tracking Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
129
addons/account/models/account_report_html.go
Normal file
129
addons/account/models/account_report_html.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"odoo-go/pkg/orm"
|
||||
)
|
||||
|
||||
// RenderReportHTML generates an HTML table for a report type.
|
||||
// Mirrors: odoo/addons/account_reports/models/account_report.py _get_html()
|
||||
//
|
||||
// Supports: trial_balance, balance_sheet, profit_loss, aged_receivable,
|
||||
// aged_payable, general_ledger.
|
||||
func RenderReportHTML(env *orm.Environment, reportType string) (string, error) {
|
||||
var data interface{}
|
||||
var err error
|
||||
|
||||
switch reportType {
|
||||
case "trial_balance":
|
||||
data, err = generateTrialBalance(env)
|
||||
case "balance_sheet":
|
||||
data, err = generateBalanceSheet(env)
|
||||
case "profit_loss":
|
||||
data, err = generateProfitLoss(env)
|
||||
case "aged_receivable":
|
||||
data, err = generateAgedReport(env, "asset_receivable")
|
||||
case "aged_payable":
|
||||
data, err = generateAgedReport(env, "liability_payable")
|
||||
case "general_ledger":
|
||||
data, err = generateGeneralLedger(env)
|
||||
default:
|
||||
return "", fmt.Errorf("unknown report: %s", reportType)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
result, ok := data.(map[string]interface{})
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid report data")
|
||||
}
|
||||
lines, _ := result["lines"].([]map[string]interface{})
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(`<!DOCTYPE html><html><head><meta charset="utf-8"><style>
|
||||
body{font-family:Arial,Helvetica,sans-serif;margin:20px;color:#333}
|
||||
table{width:100%;border-collapse:collapse;margin-top:12px}
|
||||
th,td{border:1px solid #ddd;padding:6px 8px;text-align:right}
|
||||
th{background:#875a7b;color:white;font-weight:600}
|
||||
td:first-child,th:first-child{text-align:left}
|
||||
tr:last-child{font-weight:bold;background:#f5f5f5}
|
||||
tr:hover{background:#faf5f8}
|
||||
h2{color:#875a7b;margin-bottom:4px}
|
||||
.report-date{color:#888;font-size:0.85em;margin-bottom:12px}
|
||||
</style></head><body>`)
|
||||
|
||||
// Build table based on report type
|
||||
switch reportType {
|
||||
case "trial_balance":
|
||||
b.WriteString("<h2>Trial Balance</h2>")
|
||||
b.WriteString(`<table><tr><th>Code</th><th>Account</th><th>Debit</th><th>Credit</th><th>Balance</th></tr>`)
|
||||
for _, l := range lines {
|
||||
b.WriteString(fmt.Sprintf("<tr><td>%v</td><td>%v</td><td>%.2f</td><td>%.2f</td><td>%.2f</td></tr>",
|
||||
l["code"], l["name"], toF(l["debit"]), toF(l["credit"]), toF(l["balance"])))
|
||||
}
|
||||
b.WriteString("</table>")
|
||||
|
||||
case "balance_sheet":
|
||||
b.WriteString("<h2>Balance Sheet</h2>")
|
||||
b.WriteString(`<table><tr><th>Section</th><th>Code</th><th>Account</th><th>Balance</th></tr>`)
|
||||
for _, l := range lines {
|
||||
b.WriteString(fmt.Sprintf("<tr><td>%v</td><td>%v</td><td>%v</td><td>%.2f</td></tr>",
|
||||
l["section"], l["code"], l["name"], toF(l["balance"])))
|
||||
}
|
||||
b.WriteString("</table>")
|
||||
|
||||
case "profit_loss":
|
||||
b.WriteString("<h2>Profit & Loss</h2>")
|
||||
b.WriteString(`<table><tr><th>Section</th><th>Code</th><th>Account</th><th>Amount</th></tr>`)
|
||||
for _, l := range lines {
|
||||
b.WriteString(fmt.Sprintf("<tr><td>%v</td><td>%v</td><td>%v</td><td>%.2f</td></tr>",
|
||||
l["section"], l["code"], l["name"], toF(l["balance"])))
|
||||
}
|
||||
b.WriteString("</table>")
|
||||
|
||||
case "aged_receivable", "aged_payable":
|
||||
title := "Aged Receivable"
|
||||
if reportType == "aged_payable" {
|
||||
title = "Aged Payable"
|
||||
}
|
||||
b.WriteString(fmt.Sprintf("<h2>%s</h2>", title))
|
||||
b.WriteString(`<table><tr><th>Partner</th><th>Current</th><th>1-30</th><th>31-60</th><th>61-90+</th><th>Total</th></tr>`)
|
||||
for _, l := range lines {
|
||||
b.WriteString(fmt.Sprintf("<tr><td>%v</td><td>%.2f</td><td>%.2f</td><td>%.2f</td><td>%.2f</td><td>%.2f</td></tr>",
|
||||
l["partner"], toF(l["current"]), toF(l["1-30"]), toF(l["31-60"]), toF(l["61-90+"]), toF(l["total"])))
|
||||
}
|
||||
b.WriteString("</table>")
|
||||
|
||||
case "general_ledger":
|
||||
b.WriteString("<h2>General Ledger</h2>")
|
||||
b.WriteString(`<table><tr><th>Account</th><th>Move</th><th>Date</th><th>Label</th><th>Debit</th><th>Credit</th></tr>`)
|
||||
for _, l := range lines {
|
||||
b.WriteString(fmt.Sprintf("<tr><td>%v %v</td><td>%v</td><td>%v</td><td>%v</td><td>%.2f</td><td>%.2f</td></tr>",
|
||||
l["account_code"], l["account_name"], l["move"], l["date"], l["label"],
|
||||
toF(l["debit"]), toF(l["credit"])))
|
||||
}
|
||||
b.WriteString("</table>")
|
||||
}
|
||||
|
||||
b.WriteString("</body></html>")
|
||||
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
// toF converts various numeric types to float64 for formatting.
|
||||
func toF(v interface{}) float64 {
|
||||
switch n := v.(type) {
|
||||
case float64:
|
||||
return n
|
||||
case int64:
|
||||
return float64(n)
|
||||
case int:
|
||||
return float64(n)
|
||||
case int32:
|
||||
return float64(n)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user