diff --git a/server/cmd/migrate/migration/version-local/1789113000000_syb_product_filter_menu.go b/server/cmd/migrate/migration/version-local/1789113000000_syb_product_filter_menu.go new file mode 100644 index 0000000..1cb17dc --- /dev/null +++ b/server/cmd/migrate/migration/version-local/1789113000000_syb_product_filter_menu.go @@ -0,0 +1,91 @@ +package version_local + +import ( + "fmt" + "runtime" + + "go-admin/app/goauto/access" + "go-admin/cmd/migrate/migration" + migrationmodels "go-admin/cmd/migrate/migration/models" + common "go-admin/common/models" + + "gorm.io/gorm" +) + +func init() { + _, file, _, _ := runtime.Caller(0) + migration.Migrate.SetVersion(migration.GetFilename(file), migrateSYBProductFilterMenu) +} + +// migrateSYBProductFilterMenu adds the 「SYB 商品过滤」 menu entry for #269. +// +// `[必须]` Adding a module to access.GoAutoModules() is not enough to make its +// page reachable. Menus are written by the versioned migration that first +// created them, and that version is already recorded in sys_migration on every +// existing database — it never runs again. Startup only reconciles the casbin +// API matrix (access.ReconcilePurchaserPermissions), not sys_menu. Without this +// migration the page exists, the API answers and the route resolves, yet nobody +// can navigate to it on an already-migrated database. +func migrateSYBProductFilterMenu(db *gorm.DB, version string) error { + return db.Transaction(func(tx *gorm.DB) error { + module, ok := goAutoModuleByKey(access.ModuleSYBProductFilters) + if !ok { + return fmt.Errorf("module %q is not registered in access.GoAutoModules()", access.ModuleSYBProductFilters) + } + + // The page belongs beside 「SYB 店铺」 under 采采管理, which is where the + // admin already manages which SYB data enters the system. + var parent migrationmodels.SysMenu + if err := tx.Where("menu_name = ?", "GoAutoCollectionManagement").First(&parent).Error; err != nil { + return err + } + + child, _, err := upsertGoAutoMenu(tx, migrationmodels.SysMenu{ + MenuName: module.RouteName, + Title: module.Title, + Icon: module.Icon, + Path: module.Path + "/index", + MenuType: "C", + Action: "无", + ParentId: parent.MenuId, + Component: module.Component, + Sort: module.Sort, + Visible: "0", + IsFrame: "1", + }) + if err != nil { + return err + } + if err = tx.Model(&child).Update("paths", fmt.Sprintf("/0/%d/%d", parent.MenuId, child.MenuId)).Error; err != nil { + return err + } + + // Bind to both roles the module declares. 采购员 needs it because the + // module is PurchaserDefault; 管理员 needs it because only an admin may + // change the rules (the write routes are RequireRoleKey("admin")). + roleKeys := []string{"admin"} + if module.PurchaserDefault { + roleKeys = append(roleKeys, access.RolePurchaser) + } + for _, key := range roleKeys { + var role migrationmodels.SysRole + if err = tx.Where("role_key = ?", key).First(&role).Error; err != nil { + return err + } + if err = tx.Model(&role).Association("SysMenu").Append(&child); err != nil { + return err + } + } + + return tx.Create(&common.Migration{Version: version}).Error + }) +} + +func goAutoModuleByKey(key string) (access.ModuleDefinition, bool) { + for _, module := range access.GoAutoModules() { + if module.Key == key { + return module, true + } + } + return access.ModuleDefinition{}, false +}