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

@@ -2,6 +2,7 @@ package models
import (
"fmt"
"log"
"odoo-go/pkg/orm"
)
@@ -73,12 +74,14 @@ func initCrmAnalysis() {
// Win rate
var total, won int64
_ = env.Tx().QueryRow(env.Ctx(),
if err := env.Tx().QueryRow(env.Ctx(),
`SELECT COUNT(*), COALESCE(SUM(CASE WHEN s.is_won THEN 1 ELSE 0 END), 0)
FROM crm_lead l
JOIN crm_stage s ON s.id = l.stage_id
WHERE l.type = 'opportunity'`,
).Scan(&total, &won)
).Scan(&total, &won); err != nil {
log.Printf("warning: crm win rate query failed: %v", err)
}
winRate := float64(0)
if total > 0 {
@@ -99,12 +102,14 @@ func initCrmAnalysis() {
env := rs.Env()
var totalLeads, convertedLeads int64
_ = env.Tx().QueryRow(env.Ctx(), `
if err := env.Tx().QueryRow(env.Ctx(), `
SELECT
COUNT(*) FILTER (WHERE type = 'lead'),
COUNT(*) FILTER (WHERE type = 'opportunity' AND date_conversion IS NOT NULL)
FROM crm_lead WHERE active = true`,
).Scan(&totalLeads, &convertedLeads)
).Scan(&totalLeads, &convertedLeads); err != nil {
log.Printf("warning: crm conversion data query failed: %v", err)
}
conversionRate := float64(0)
if totalLeads > 0 {
@@ -113,19 +118,23 @@ func initCrmAnalysis() {
// Average days to convert
var avgDaysConvert float64
_ = env.Tx().QueryRow(env.Ctx(), `
if err := env.Tx().QueryRow(env.Ctx(), `
SELECT COALESCE(AVG(EXTRACT(EPOCH FROM (date_conversion - create_date)) / 86400), 0)
FROM crm_lead
WHERE type = 'opportunity' AND date_conversion IS NOT NULL AND active = true`,
).Scan(&avgDaysConvert)
).Scan(&avgDaysConvert); err != nil {
log.Printf("warning: crm avg days to convert query failed: %v", err)
}
// Average days to close (won)
var avgDaysClose float64
_ = env.Tx().QueryRow(env.Ctx(), `
if err := env.Tx().QueryRow(env.Ctx(), `
SELECT COALESCE(AVG(EXTRACT(EPOCH FROM (date_closed - create_date)) / 86400), 0)
FROM crm_lead
WHERE state = 'won' AND date_closed IS NOT NULL`,
).Scan(&avgDaysClose)
).Scan(&avgDaysClose); err != nil {
log.Printf("warning: crm avg days to close query failed: %v", err)
}
return map[string]interface{}{
"total_leads": totalLeads,