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:
@@ -45,8 +45,9 @@ type Model struct {
|
||||
checkCompany bool // Enforce multi-company record rules
|
||||
|
||||
// Hooks
|
||||
BeforeCreate func(env *Environment, vals Values) error // Called before INSERT
|
||||
DefaultGet func(env *Environment, fields []string) Values // Dynamic defaults (e.g., from DB)
|
||||
BeforeCreate func(env *Environment, vals Values) error // Called before INSERT
|
||||
BeforeWrite func(env *Environment, ids []int64, vals Values) error // Called before UPDATE — for state guards
|
||||
DefaultGet func(env *Environment, fields []string) Values // Dynamic defaults (e.g., from DB)
|
||||
Constraints []ConstraintFunc // Validation constraints
|
||||
Methods map[string]MethodFunc // Named business methods
|
||||
|
||||
@@ -453,3 +454,32 @@ func (m *Model) Many2manyTableSQL() []string {
|
||||
}
|
||||
return stmts
|
||||
}
|
||||
|
||||
// StateGuard returns a BeforeWrite function that prevents modifications on records
|
||||
// in certain states, except for explicitly allowed fields.
|
||||
// Eliminates the duplicated guard pattern across sale.order, purchase.order,
|
||||
// account.move, and stock.picking.
|
||||
func StateGuard(table, stateCondition string, allowedFields []string, errMsg string) func(env *Environment, ids []int64, vals Values) error {
|
||||
allowed := make(map[string]bool, len(allowedFields))
|
||||
for _, f := range allowedFields {
|
||||
allowed[f] = true
|
||||
}
|
||||
return func(env *Environment, ids []int64, vals Values) error {
|
||||
if _, changingState := vals["state"]; changingState {
|
||||
return nil
|
||||
}
|
||||
var count int
|
||||
err := env.Tx().QueryRow(env.Ctx(),
|
||||
fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE id = ANY($1) AND %s`, table, stateCondition), ids,
|
||||
).Scan(&count)
|
||||
if err != nil || count == 0 {
|
||||
return nil
|
||||
}
|
||||
for field := range vals {
|
||||
if !allowed[field] {
|
||||
return fmt.Errorf("%s: %s", table, errMsg)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user