package initialize import ( "errors" "fmt" "gorm.io/gorm" "wails-app/internal/dao" "wails-app/internal/global" ) type paymentMethodDefault struct { Code string Name string InputMode string Scenes []string Enabled bool Sort int Description string } type paymentEntryDefault struct { Code string Name string Enabled bool Sort int Description string Methods []string } // EnsurePaymentConfiguration upgrades both new and existing databases. The // method and entry tables are deliberately seeded independently: an entry // controls where payment starts, while a method controls how it settles. func EnsurePaymentConfiguration() error { db := global.GVA_DB if db == nil { return nil } if err := db.AutoMigrate(&dao.PaymentEntryConfig{}, &dao.PaymentEntryMethod{}); err != nil { return fmt.Errorf("初始化支付入口表失败: %w", err) } methods := []paymentMethodDefault{ {Code: "cash", Name: "人工支付", InputMode: "manual_amount", Scenes: []string{"counter"}, Enabled: true, Sort: 10, Description: "岗亭人员录入实收金额并确认收款"}, {Code: "pos", Name: "POS机支付", InputMode: "confirm", Scenes: []string{"counter"}, Enabled: false, Sort: 20, Description: "通过POS机完成支付后由岗亭人员确认"}, {Code: "ticket_qr", Name: "小票扫码支付", InputMode: "external", Scenes: []string{"ticket"}, Enabled: false, Sort: 30, Description: "车主扫描停车小票发起外部支付"}, } for _, item := range methods { method := dao.PaymentMethodConfig{} err := db.Where("code = ?", item.Code).First(&method).Error if errors.Is(err, gorm.ErrRecordNotFound) { method = dao.PaymentMethodConfig{ Code: item.Code, Name: item.Name, InputMode: item.InputMode, Scenes: item.Scenes, Enabled: item.Enabled, Sort: item.Sort, IsSystem: true, Description: item.Description, } err = db.Create(&method).Error } if err != nil { return fmt.Errorf("初始化支付方式 %s 失败: %w", item.Code, err) } } // 人工支付是基础能力,升级时也要修复旧数据中被关闭或改型的情况。 cashDefaults := dao.PaymentMethodConfig{ Enabled: true, InputMode: "manual_amount", Scenes: []string{"counter"}, IsSystem: true, } if err := db.Model(&dao.PaymentMethodConfig{}).Where("code = ?", "cash"). Select("enabled", "input_mode", "scenes", "is_system").Updates(&cashDefaults).Error; err != nil { return err } entries := []paymentEntryDefault{ {Code: "counter", Name: "收费岗亭", Enabled: true, Sort: 10, Description: "由收费岗亭人员发起收费和放行", Methods: []string{"cash", "pos"}}, {Code: "ticket", Name: "停车小票", Enabled: true, Sort: 20, Description: "由停车小票或数字票发起后续支付", Methods: []string{"cash", "pos", "ticket_qr"}}, } for _, item := range entries { entry := dao.PaymentEntryConfig{} err := db.Where("code = ?", item.Code).First(&entry).Error if errors.Is(err, gorm.ErrRecordNotFound) { entry = dao.PaymentEntryConfig{ Code: item.Code, Name: item.Name, Enabled: item.Enabled, Sort: item.Sort, IsSystem: true, Description: item.Description, } if err = db.Create(&entry).Error; err != nil { return fmt.Errorf("初始化支付入口 %s 失败: %w", item.Code, err) } } if err != nil { return fmt.Errorf("读取支付入口 %s 失败: %w", item.Code, err) } var relationCount int64 if err := db.Model(&dao.PaymentEntryMethod{}).Where("entry_code = ?", item.Code).Count(&relationCount).Error; err != nil { return err } if relationCount == 0 { for i, methodCode := range item.Methods { if err := db.Create(&dao.PaymentEntryMethod{EntryCode: item.Code, MethodCode: methodCode, Sort: i * 10}).Error; err != nil { return fmt.Errorf("初始化支付入口 %s 关联失败: %w", item.Code, err) } } } } // Migrate old method.scenes values into the relation table without // overwriting any explicit entry configuration made by an administrator. var legacyMethods []dao.PaymentMethodConfig if err := db.Where("is_legacy = ?", false).Find(&legacyMethods).Error; err != nil { return err } for _, method := range legacyMethods { for _, entryCode := range method.Scenes { var entry dao.PaymentEntryConfig if err := db.Where("code = ?", entryCode).First(&entry).Error; err != nil { continue } var count int64 if err := db.Model(&dao.PaymentEntryMethod{}).Where("entry_code = ? AND method_code = ?", entryCode, method.Code).Count(&count).Error; err != nil { return err } if count == 0 { if err := db.Create(&dao.PaymentEntryMethod{EntryCode: entryCode, MethodCode: method.Code, Sort: method.Sort}).Error; err != nil { return err } } } } var parkingMenu dao.SysBaseMenu if err := db.Where("name = ?", "parking").First(&parkingMenu).Error; err == nil { methodMenu, err := ensurePaymentMenu(db, parkingMenu.ID, "paymentMethodConfig", "paymentMethod", "view/parking/paymentMethod.vue", "支付方式", "credit-card", 8) if err != nil { return err } entryMenu, err := ensurePaymentMenu(db, parkingMenu.ID, "paymentEntryConfig", "paymentEntry", "view/parking/paymentEntry.vue", "支付入口", "connection", 9) if err != nil { return err } ticketMenu, err := ensurePaymentMenu(db, parkingMenu.ID, "digitalTicket", "digitalTicket", "view/parking/digitalTicket.vue", "数字票", "tickets", 10) if err != nil { return err } for _, menu := range []*dao.SysBaseMenu{methodMenu, entryMenu} { for _, authorityID := range []string{"888", "9527"} { var count int64 if err := db.Model(&dao.SysAuthorityMenu{}). Where("sys_base_menu_id = ? AND sys_authority_authority_id = ?", menu.ID, authorityID).Count(&count).Error; err != nil { return fmt.Errorf("初始化支付菜单权限失败: %w", err) } if count == 0 { link := dao.SysAuthorityMenu{MenuId: fmt.Sprint(menu.ID), AuthorityId: authorityID} if err := db.Create(&link).Error; err != nil { return fmt.Errorf("初始化支付菜单权限失败: %w", err) } } } } for _, authorityID := range []string{"618", "888", "9527"} { var count int64 if err := db.Model(&dao.SysAuthorityMenu{}). Where("sys_base_menu_id = ? AND sys_authority_authority_id = ?", ticketMenu.ID, authorityID).Count(&count).Error; err != nil { return fmt.Errorf("初始化数字票菜单权限失败: %w", err) } if count == 0 { link := dao.SysAuthorityMenu{MenuId: fmt.Sprint(ticketMenu.ID), AuthorityId: authorityID} if err := db.Create(&link).Error; err != nil { return fmt.Errorf("初始化数字票菜单权限失败: %w", err) } } } } apis := []dao.SysApi{ {Path: "/payment/methods", Description: "查询支付方式配置", ApiGroup: "支付方式", Method: "GET"}, {Path: "/payment/methods/enabled", Description: "查询入口可用支付方式", ApiGroup: "支付方式", Method: "GET"}, {Path: "/payment/methods/options", Description: "查询支付方式选项", ApiGroup: "支付方式", Method: "GET"}, {Path: "/payment/methods", Description: "新增支付方式", ApiGroup: "支付方式", Method: "POST"}, {Path: "/payment/methods/:id", Description: "更新支付方式", ApiGroup: "支付方式", Method: "PUT"}, {Path: "/payment/methods/:id/status", Description: "启停支付方式", ApiGroup: "支付方式", Method: "PATCH"}, {Path: "/payment/methods/:id", Description: "删除支付方式", ApiGroup: "支付方式", Method: "DELETE"}, {Path: "/payment/entries", Description: "查询支付入口配置", ApiGroup: "支付入口", Method: "GET"}, {Path: "/payment/entries/enabled", Description: "查询启用的支付入口", ApiGroup: "支付入口", Method: "GET"}, {Path: "/payment/entries/options", Description: "查询支付入口选项", ApiGroup: "支付入口", Method: "GET"}, {Path: "/payment/entries", Description: "新增支付入口", ApiGroup: "支付入口", Method: "POST"}, {Path: "/payment/entries/:id", Description: "更新支付入口", ApiGroup: "支付入口", Method: "PUT"}, {Path: "/payment/entries/:id/status", Description: "启停支付入口", ApiGroup: "支付入口", Method: "PATCH"}, {Path: "/payment/entries/:id", Description: "删除支付入口", ApiGroup: "支付入口", Method: "DELETE"}, {Path: "/digital-ticket/summary", Description: "查询数字票汇总", ApiGroup: "数字票", Method: "GET"}, {Path: "/digital-ticket/list", Description: "查询数字票列表", ApiGroup: "数字票", Method: "GET"}, {Path: "/digital-ticket/:ticketNo", Description: "查询数字票详情", ApiGroup: "数字票", Method: "GET"}, {Path: "/digital-ticket/:ticketNo/pay", Description: "确认数字票支付", ApiGroup: "数字票", Method: "POST"}, {Path: "/digital-ticket/:ticketNo/exit", Description: "确认数字票离场", ApiGroup: "数字票", Method: "POST"}, } for _, api := range apis { var existing dao.SysApi err := db.Where("path = ? AND method = ?", api.Path, api.Method).First(&existing).Error if errors.Is(err, gorm.ErrRecordNotFound) { err = db.Create(&api).Error } if err != nil { return fmt.Errorf("初始化支付API失败: %w", err) } } if err := db.Exec(`CREATE TABLE IF NOT EXISTS casbin_rule ( id INTEGER PRIMARY KEY AUTOINCREMENT, ptype TEXT, v0 TEXT, v1 TEXT, v2 TEXT, v3 TEXT, v4 TEXT, v5 TEXT )`).Error; err != nil { return err } readRules := [][2]string{ {"/payment/methods/enabled", "GET"}, {"/payment/methods/options", "GET"}, {"/payment/entries/enabled", "GET"}, {"/payment/entries/options", "GET"}, } for _, role := range []string{"618", "888", "9527"} { for _, rule := range readRules { if err := ensureCasbinRule(role, rule[0], rule[1]); err != nil { return err } } } for _, role := range []string{"888", "9527"} { for _, rule := range [][2]string{ {"/payment/methods", "GET"}, {"/payment/methods", "POST"}, {"/payment/methods/:id", "PUT"}, {"/payment/methods/:id/status", "PATCH"}, {"/payment/methods/:id", "DELETE"}, {"/payment/entries", "GET"}, {"/payment/entries", "POST"}, {"/payment/entries/:id", "PUT"}, {"/payment/entries/:id/status", "PATCH"}, {"/payment/entries/:id", "DELETE"}, } { if err := ensureCasbinRule(role, rule[0], rule[1]); err != nil { return err } } } for _, role := range []string{"618", "888", "9527"} { for _, rule := range [][2]string{ {"/digital-ticket/summary", "GET"}, {"/digital-ticket/list", "GET"}, {"/digital-ticket/:ticketNo", "GET"}, {"/digital-ticket/:ticketNo/pay", "POST"}, {"/digital-ticket/:ticketNo/exit", "POST"}, } { if err := ensureCasbinRule(role, rule[0], rule[1]); err != nil { return err } } } return nil } func ensurePaymentMenu(db *gorm.DB, parentID uint, name, path, component, title, icon string, sort int) (*dao.SysBaseMenu, error) { menu := &dao.SysBaseMenu{} err := db.Where("name = ?", name).First(menu).Error if errors.Is(err, gorm.ErrRecordNotFound) { *menu = dao.SysBaseMenu{ MenuLevel: 0, ParentId: parentID, Path: path, Name: name, Hidden: false, Component: component, Sort: sort, Meta: dao.Meta{Title: title, Icon: icon}, } if err := db.Create(menu).Error; err != nil { return nil, fmt.Errorf("初始化%s菜单失败: %w", title, err) } return menu, nil } if err != nil { return nil, fmt.Errorf("读取%s菜单失败: %w", title, err) } if err := db.Model(menu).Updates(map[string]interface{}{"parent_id": parentID, "path": path, "component": component, "sort": sort}).Error; err != nil { return nil, fmt.Errorf("更新%s菜单失败: %w", title, err) } return menu, nil } func ensureCasbinRule(role, path, method string) error { db := global.GVA_DB var count int64 if err := db.Table("casbin_rule").Where("ptype = ? AND v0 = ? AND v1 = ? AND v2 = ?", "p", role, path, method).Count(&count).Error; err != nil { return err } if count == 0 { return db.Exec("INSERT INTO casbin_rule (ptype, v0, v1, v2, v3, v4, v5) VALUES (?, ?, ?, ?, '', '', '')", "p", role, path, method).Error } return nil }