fix(goauto): add the missing menu migration for SYB product filters #269

Registering a module in access.GoAutoModules() does not make its page
reachable on an existing database. sys_menu rows are written by the
versioned migration that first created them, and that version is already
recorded in sys_migration everywhere — it never runs again. Startup only
reconciles the casbin API matrix, not menus.

So on a fresh database the page appeared and on every real one it did not:
the table, the API and the route were all present with no way to navigate
there. This adds a migration that upserts the entry under 采采管理 beside
「SYB 店铺」 and binds it to both 管理员 and 采购员.

Verified by deleting the row from an already-migrated database and
re-running migrate: 新执行 1 个,跳过 42 个, row restored with both roles.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
This commit is contained in:
QiuSW
2026-09-11 16:49:56 +08:00
co-authored by Claude Opus 5
parent 78df997fc3
commit cad9f6d185
@@ -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
}