package models import ( "fmt" "odoo-go/pkg/orm" ) // initIrCron registers ir.cron — Scheduled actions. // Mirrors: odoo/addons/base/models/ir_cron.py class IrCron // // Defines recurring tasks executed by the scheduler. func initIrCron() { m := orm.NewModel("ir.cron", orm.ModelOpts{ Description: "Scheduled Actions", Order: "name", }) m.AddFields( orm.Char("name", orm.FieldOpts{String: "Name", Required: true}), orm.Boolean("active", orm.FieldOpts{String: "Active", Default: true}), orm.Many2one("user_id", "res.users", orm.FieldOpts{String: "User", Required: true}), orm.Integer("interval_number", orm.FieldOpts{String: "Interval Number", Default: 1}), orm.Selection("interval_type", []orm.SelectionItem{ {Value: "minutes", Label: "Minutes"}, {Value: "hours", Label: "Hours"}, {Value: "days", Label: "Days"}, {Value: "weeks", Label: "Weeks"}, {Value: "months", Label: "Months"}, }, orm.FieldOpts{String: "Interval Type", Default: "months"}), orm.Integer("numbercall", orm.FieldOpts{String: "Number of Calls", Default: -1}), orm.Datetime("nextcall", orm.FieldOpts{String: "Next Execution Date", Required: true}), orm.Datetime("lastcall", orm.FieldOpts{String: "Last Execution Date"}), orm.Integer("priority", orm.FieldOpts{String: "Priority", Default: 5}), orm.Char("code", orm.FieldOpts{String: "Python Code"}), orm.Many2one("model_id", "ir.model", orm.FieldOpts{String: "Model"}), // Execution target (simplified: direct model+method instead of ir.actions.server) orm.Char("model_name", orm.FieldOpts{String: "Model Name"}), orm.Char("method_name", orm.FieldOpts{String: "Method Name"}), // Failure tracking orm.Integer("failure_count", orm.FieldOpts{String: "Failure Count", Default: 0}), orm.Datetime("first_failure_date", orm.FieldOpts{String: "First Failure Date"}), ) // Constraint: validate model_name and method_name against the registry. // Prevents setting arbitrary/invalid model+method combos on cron jobs. m.AddConstraint(func(rs *orm.Recordset) error { records, err := rs.Read([]string{"model_name", "method_name"}) if err != nil || len(records) == 0 { return nil } rec := records[0] modelName, _ := rec["model_name"].(string) methodName, _ := rec["method_name"].(string) if modelName == "" && methodName == "" { return nil // both empty is OK (legacy code-based crons) } if modelName != "" { model := orm.Registry.Get(modelName) if model == nil { return fmt.Errorf("ir.cron: model %q not found in registry", modelName) } if methodName != "" && model.Methods != nil { if _, ok := model.Methods[methodName]; !ok { return fmt.Errorf("ir.cron: method %q not found on model %q", methodName, modelName) } } } return nil }) // method_direct_trigger — manually trigger a cron job. // Mirrors: odoo/addons/base/models/ir_cron.py method_direct_trigger m.RegisterMethod("method_direct_trigger", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) { // Admin-only: only uid=1 or superuser may trigger cron jobs directly env := rs.Env() if env.UID() != 1 && !env.IsSuperuser() { return nil, fmt.Errorf("ir.cron: method_direct_trigger requires admin privileges") } records, err := rs.Read([]string{"model_name", "method_name"}) if err != nil { return nil, fmt.Errorf("ir.cron: method_direct_trigger read failed: %w", err) } if len(records) == 0 { return nil, fmt.Errorf("ir.cron: method_direct_trigger: no record found") } rec := records[0] modelName, _ := rec["model_name"].(string) methodName, _ := rec["method_name"].(string) if modelName == "" || methodName == "" { return nil, fmt.Errorf("ir.cron: model_name or method_name not set") } // Validate model_name against registry (prevents calling arbitrary models) model := orm.Registry.Get(modelName) if model == nil { return nil, fmt.Errorf("ir.cron: model %q not found in registry", modelName) } if model.Methods == nil { return nil, fmt.Errorf("ir.cron: model %q has no methods", modelName) } method, ok := model.Methods[methodName] if !ok { return nil, fmt.Errorf("ir.cron: method %q not found on model %q", methodName, modelName) } result, err := method(env.Model(modelName), args...) if err != nil { return nil, fmt.Errorf("ir.cron: %s.%s failed: %w", modelName, methodName, err) } return result, nil }) }