feat: Portal, Email Inbound, Discuss + module improvements

- Portal: /my/* routes, signup, password reset, portal user support
- Email Inbound: IMAP polling (go-imap/v2), thread matching
- Discuss: mail.channel, long-polling bus, DM, unread count
- Cron: ir.cron runner (goroutine scheduler)
- Bank Import, CSV/Excel Import
- Automation (ir.actions.server)
- Fetchmail service
- HR Payroll model
- Various fixes across account, sale, stock, purchase, crm, hr, project

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marc
2026-04-12 18:41:57 +02:00
parent 2c7c1e6c88
commit 66383adf06
87 changed files with 14696 additions and 654 deletions

View File

@@ -1,6 +1,7 @@
package models
import (
"encoding/json"
"fmt"
"strings"
@@ -296,3 +297,73 @@ func applyWriteoffSuggestion(env *orm.Environment, modelID, stLineID int64, amou
"suggestions": suggestions,
}, nil
}
// initAccountReconcilePreview registers account.reconcile.model.preview (Odoo 18+).
// Transient model for previewing reconciliation results before applying.
// Mirrors: odoo/addons/account/wizard/account_reconcile_model_preview.py
func initAccountReconcilePreview() {
m := orm.NewModel("account.reconcile.model.preview", orm.ModelOpts{
Description: "Reconcile Model Preview",
Type: orm.ModelTransient,
})
m.AddFields(
orm.Many2one("model_id", "account.reconcile.model", orm.FieldOpts{
String: "Reconcile Model", Required: true,
}),
orm.Many2one("statement_line_id", "account.bank.statement.line", orm.FieldOpts{
String: "Statement Line",
}),
orm.Text("preview_data", orm.FieldOpts{
String: "Preview Data", Compute: "_compute_preview",
}),
)
m.RegisterCompute("preview_data", func(rs *orm.Recordset) (orm.Values, error) {
env := rs.Env()
id := rs.IDs()[0]
var modelID int64
var stLineID *int64
env.Tx().QueryRow(env.Ctx(),
`SELECT COALESCE(model_id, 0), statement_line_id
FROM account_reconcile_model_preview WHERE id = $1`, id,
).Scan(&modelID, &stLineID)
if modelID == 0 {
return orm.Values{"preview_data": "[]"}, nil
}
// Read reconcile model lines to preview what would be created
rows, err := env.Tx().Query(env.Ctx(),
`SELECT rml.label, rml.amount_type, rml.amount,
COALESCE(a.code, ''), COALESCE(a.name, '')
FROM account_reconcile_model_line rml
LEFT JOIN account_account a ON a.id = rml.account_id
WHERE rml.model_id = $1
ORDER BY rml.sequence`, modelID)
if err != nil {
return orm.Values{"preview_data": "[]"}, nil
}
defer rows.Close()
var preview []map[string]interface{}
for rows.Next() {
var label, amountType, accCode, accName string
var amount float64
if err := rows.Scan(&label, &amountType, &amount, &accCode, &accName); err != nil {
continue
}
preview = append(preview, map[string]interface{}{
"label": label,
"amount_type": amountType,
"amount": amount,
"account_code": accCode,
"account_name": accName,
})
}
data, _ := json.Marshal(preview)
return orm.Values{"preview_data": string(data)}, nil
})
}