package modules import ( "fmt" "odoo-go/pkg/orm" ) // ResolveDependencies returns modules in topological order (dependencies first). // Mirrors: odoo/modules/module_graph.py Graph.add_modules() // // Uses Kahn's algorithm for topological sort. func ResolveDependencies(moduleNames []string) ([]string, error) { // Build adjacency list and in-degree map inDegree := make(map[string]int) dependents := make(map[string][]string) // module → modules that depend on it // Initialize all requested modules for _, name := range moduleNames { if _, exists := inDegree[name]; !exists { inDegree[name] = 0 } } // Build graph from dependencies for _, name := range moduleNames { m := Get(name) if m == nil { return nil, fmt.Errorf("modules: %q not found", name) } for _, dep := range m.Depends { // Ensure dependency is in our set if _, exists := inDegree[dep]; !exists { inDegree[dep] = 0 } inDegree[name]++ dependents[dep] = append(dependents[dep], name) } } // Kahn's algorithm var queue []string for name, degree := range inDegree { if degree == 0 { queue = append(queue, name) } } var sorted []string for len(queue) > 0 { // Pop first element current := queue[0] queue = queue[1:] sorted = append(sorted, current) // Reduce in-degree for dependents for _, dep := range dependents[current] { inDegree[dep]-- if inDegree[dep] == 0 { queue = append(queue, dep) } } } // Check for circular dependencies if len(sorted) != len(inDegree) { var circular []string for name, degree := range inDegree { if degree > 0 { circular = append(circular, name) } } return nil, fmt.Errorf("modules: circular dependency detected among: %v", circular) } return sorted, nil } // LoadModules initializes all modules in dependency order. // Mirrors: odoo/modules/loading.py load_modules() func LoadModules(moduleNames []string) error { sorted, err := ResolveDependencies(moduleNames) if err != nil { return err } // Phase 1: Call Init() for each module (registers models and fields) for _, name := range sorted { m := Get(name) if m == nil { continue } if m.Init != nil { m.Init() } } // Phase 2: Call PostInit() after all models are registered for _, name := range sorted { m := Get(name) if m == nil { continue } if m.PostInit != nil { m.PostInit() } } // Phase 3: Build computed field dependency maps orm.SetupAllComputes() return nil }