payment_seed.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. package initialize
  2. import (
  3. "errors"
  4. "fmt"
  5. "gorm.io/gorm"
  6. "wails-app/internal/dao"
  7. "wails-app/internal/global"
  8. )
  9. type paymentMethodDefault struct {
  10. Code string
  11. Name string
  12. InputMode string
  13. Scenes []string
  14. Enabled bool
  15. Sort int
  16. Description string
  17. }
  18. type paymentEntryDefault struct {
  19. Code string
  20. Name string
  21. Enabled bool
  22. Sort int
  23. Description string
  24. Methods []string
  25. }
  26. // EnsurePaymentConfiguration upgrades both new and existing databases. The
  27. // method and entry tables are deliberately seeded independently: an entry
  28. // controls where payment starts, while a method controls how it settles.
  29. func EnsurePaymentConfiguration() error {
  30. db := global.GVA_DB
  31. if db == nil {
  32. return nil
  33. }
  34. if err := db.AutoMigrate(&dao.PaymentEntryConfig{}, &dao.PaymentEntryMethod{}); err != nil {
  35. return fmt.Errorf("初始化支付入口表失败: %w", err)
  36. }
  37. methods := []paymentMethodDefault{
  38. {Code: "cash", Name: "人工支付", InputMode: "manual_amount", Scenes: []string{"counter"}, Enabled: true, Sort: 10, Description: "岗亭人员录入实收金额并确认收款"},
  39. {Code: "pos", Name: "POS机支付", InputMode: "confirm", Scenes: []string{"counter"}, Enabled: false, Sort: 20, Description: "通过POS机完成支付后由岗亭人员确认"},
  40. {Code: "ticket_qr", Name: "小票扫码支付", InputMode: "external", Scenes: []string{"ticket"}, Enabled: false, Sort: 30, Description: "车主扫描停车小票发起外部支付"},
  41. }
  42. for _, item := range methods {
  43. method := dao.PaymentMethodConfig{}
  44. err := db.Where("code = ?", item.Code).First(&method).Error
  45. if errors.Is(err, gorm.ErrRecordNotFound) {
  46. method = dao.PaymentMethodConfig{
  47. Code: item.Code, Name: item.Name, InputMode: item.InputMode, Scenes: item.Scenes,
  48. Enabled: item.Enabled, Sort: item.Sort, IsSystem: true, Description: item.Description,
  49. }
  50. err = db.Create(&method).Error
  51. }
  52. if err != nil {
  53. return fmt.Errorf("初始化支付方式 %s 失败: %w", item.Code, err)
  54. }
  55. }
  56. // 人工支付是基础能力,升级时也要修复旧数据中被关闭或改型的情况。
  57. cashDefaults := dao.PaymentMethodConfig{
  58. Enabled: true, InputMode: "manual_amount", Scenes: []string{"counter"}, IsSystem: true,
  59. }
  60. if err := db.Model(&dao.PaymentMethodConfig{}).Where("code = ?", "cash").
  61. Select("enabled", "input_mode", "scenes", "is_system").Updates(&cashDefaults).Error; err != nil {
  62. return err
  63. }
  64. entries := []paymentEntryDefault{
  65. {Code: "counter", Name: "收费岗亭", Enabled: true, Sort: 10, Description: "由收费岗亭人员发起收费和放行", Methods: []string{"cash", "pos"}},
  66. {Code: "ticket", Name: "停车小票", Enabled: true, Sort: 20, Description: "由停车小票或数字票发起后续支付", Methods: []string{"cash", "pos", "ticket_qr"}},
  67. }
  68. for _, item := range entries {
  69. entry := dao.PaymentEntryConfig{}
  70. err := db.Where("code = ?", item.Code).First(&entry).Error
  71. if errors.Is(err, gorm.ErrRecordNotFound) {
  72. entry = dao.PaymentEntryConfig{
  73. Code: item.Code, Name: item.Name, Enabled: item.Enabled, Sort: item.Sort,
  74. IsSystem: true, Description: item.Description,
  75. }
  76. if err = db.Create(&entry).Error; err != nil {
  77. return fmt.Errorf("初始化支付入口 %s 失败: %w", item.Code, err)
  78. }
  79. }
  80. if err != nil {
  81. return fmt.Errorf("读取支付入口 %s 失败: %w", item.Code, err)
  82. }
  83. var relationCount int64
  84. if err := db.Model(&dao.PaymentEntryMethod{}).Where("entry_code = ?", item.Code).Count(&relationCount).Error; err != nil {
  85. return err
  86. }
  87. if relationCount == 0 {
  88. for i, methodCode := range item.Methods {
  89. if err := db.Create(&dao.PaymentEntryMethod{EntryCode: item.Code, MethodCode: methodCode, Sort: i * 10}).Error; err != nil {
  90. return fmt.Errorf("初始化支付入口 %s 关联失败: %w", item.Code, err)
  91. }
  92. }
  93. }
  94. }
  95. // Migrate old method.scenes values into the relation table without
  96. // overwriting any explicit entry configuration made by an administrator.
  97. var legacyMethods []dao.PaymentMethodConfig
  98. if err := db.Where("is_legacy = ?", false).Find(&legacyMethods).Error; err != nil {
  99. return err
  100. }
  101. for _, method := range legacyMethods {
  102. for _, entryCode := range method.Scenes {
  103. var entry dao.PaymentEntryConfig
  104. if err := db.Where("code = ?", entryCode).First(&entry).Error; err != nil {
  105. continue
  106. }
  107. var count int64
  108. if err := db.Model(&dao.PaymentEntryMethod{}).Where("entry_code = ? AND method_code = ?", entryCode, method.Code).Count(&count).Error; err != nil {
  109. return err
  110. }
  111. if count == 0 {
  112. if err := db.Create(&dao.PaymentEntryMethod{EntryCode: entryCode, MethodCode: method.Code, Sort: method.Sort}).Error; err != nil {
  113. return err
  114. }
  115. }
  116. }
  117. }
  118. var parkingMenu dao.SysBaseMenu
  119. if err := db.Where("name = ?", "parking").First(&parkingMenu).Error; err == nil {
  120. methodMenu, err := ensurePaymentMenu(db, parkingMenu.ID, "paymentMethodConfig", "paymentMethod", "view/parking/paymentMethod.vue", "支付方式", "credit-card", 8)
  121. if err != nil {
  122. return err
  123. }
  124. entryMenu, err := ensurePaymentMenu(db, parkingMenu.ID, "paymentEntryConfig", "paymentEntry", "view/parking/paymentEntry.vue", "支付入口", "connection", 9)
  125. if err != nil {
  126. return err
  127. }
  128. ticketMenu, err := ensurePaymentMenu(db, parkingMenu.ID, "digitalTicket", "digitalTicket", "view/parking/digitalTicket.vue", "数字票", "tickets", 10)
  129. if err != nil {
  130. return err
  131. }
  132. for _, menu := range []*dao.SysBaseMenu{methodMenu, entryMenu} {
  133. for _, authorityID := range []string{"888", "9527"} {
  134. var count int64
  135. if err := db.Model(&dao.SysAuthorityMenu{}).
  136. Where("sys_base_menu_id = ? AND sys_authority_authority_id = ?", menu.ID, authorityID).Count(&count).Error; err != nil {
  137. return fmt.Errorf("初始化支付菜单权限失败: %w", err)
  138. }
  139. if count == 0 {
  140. link := dao.SysAuthorityMenu{MenuId: fmt.Sprint(menu.ID), AuthorityId: authorityID}
  141. if err := db.Create(&link).Error; err != nil {
  142. return fmt.Errorf("初始化支付菜单权限失败: %w", err)
  143. }
  144. }
  145. }
  146. }
  147. for _, authorityID := range []string{"618", "888", "9527"} {
  148. var count int64
  149. if err := db.Model(&dao.SysAuthorityMenu{}).
  150. Where("sys_base_menu_id = ? AND sys_authority_authority_id = ?", ticketMenu.ID, authorityID).Count(&count).Error; err != nil {
  151. return fmt.Errorf("初始化数字票菜单权限失败: %w", err)
  152. }
  153. if count == 0 {
  154. link := dao.SysAuthorityMenu{MenuId: fmt.Sprint(ticketMenu.ID), AuthorityId: authorityID}
  155. if err := db.Create(&link).Error; err != nil {
  156. return fmt.Errorf("初始化数字票菜单权限失败: %w", err)
  157. }
  158. }
  159. }
  160. }
  161. apis := []dao.SysApi{
  162. {Path: "/payment/methods", Description: "查询支付方式配置", ApiGroup: "支付方式", Method: "GET"},
  163. {Path: "/payment/methods/enabled", Description: "查询入口可用支付方式", ApiGroup: "支付方式", Method: "GET"},
  164. {Path: "/payment/methods/options", Description: "查询支付方式选项", ApiGroup: "支付方式", Method: "GET"},
  165. {Path: "/payment/methods", Description: "新增支付方式", ApiGroup: "支付方式", Method: "POST"},
  166. {Path: "/payment/methods/:id", Description: "更新支付方式", ApiGroup: "支付方式", Method: "PUT"},
  167. {Path: "/payment/methods/:id/status", Description: "启停支付方式", ApiGroup: "支付方式", Method: "PATCH"},
  168. {Path: "/payment/methods/:id", Description: "删除支付方式", ApiGroup: "支付方式", Method: "DELETE"},
  169. {Path: "/payment/entries", Description: "查询支付入口配置", ApiGroup: "支付入口", Method: "GET"},
  170. {Path: "/payment/entries/enabled", Description: "查询启用的支付入口", ApiGroup: "支付入口", Method: "GET"},
  171. {Path: "/payment/entries/options", Description: "查询支付入口选项", ApiGroup: "支付入口", Method: "GET"},
  172. {Path: "/payment/entries", Description: "新增支付入口", ApiGroup: "支付入口", Method: "POST"},
  173. {Path: "/payment/entries/:id", Description: "更新支付入口", ApiGroup: "支付入口", Method: "PUT"},
  174. {Path: "/payment/entries/:id/status", Description: "启停支付入口", ApiGroup: "支付入口", Method: "PATCH"},
  175. {Path: "/payment/entries/:id", Description: "删除支付入口", ApiGroup: "支付入口", Method: "DELETE"},
  176. {Path: "/digital-ticket/summary", Description: "查询数字票汇总", ApiGroup: "数字票", Method: "GET"},
  177. {Path: "/digital-ticket/list", Description: "查询数字票列表", ApiGroup: "数字票", Method: "GET"},
  178. {Path: "/digital-ticket/:ticketNo", Description: "查询数字票详情", ApiGroup: "数字票", Method: "GET"},
  179. {Path: "/digital-ticket/:ticketNo/pay", Description: "确认数字票支付", ApiGroup: "数字票", Method: "POST"},
  180. {Path: "/digital-ticket/:ticketNo/exit", Description: "确认数字票离场", ApiGroup: "数字票", Method: "POST"},
  181. }
  182. for _, api := range apis {
  183. var existing dao.SysApi
  184. err := db.Where("path = ? AND method = ?", api.Path, api.Method).First(&existing).Error
  185. if errors.Is(err, gorm.ErrRecordNotFound) {
  186. err = db.Create(&api).Error
  187. }
  188. if err != nil {
  189. return fmt.Errorf("初始化支付API失败: %w", err)
  190. }
  191. }
  192. if err := db.Exec(`CREATE TABLE IF NOT EXISTS casbin_rule (
  193. id INTEGER PRIMARY KEY AUTOINCREMENT,
  194. ptype TEXT, v0 TEXT, v1 TEXT, v2 TEXT, v3 TEXT, v4 TEXT, v5 TEXT
  195. )`).Error; err != nil {
  196. return err
  197. }
  198. readRules := [][2]string{
  199. {"/payment/methods/enabled", "GET"}, {"/payment/methods/options", "GET"},
  200. {"/payment/entries/enabled", "GET"}, {"/payment/entries/options", "GET"},
  201. }
  202. for _, role := range []string{"618", "888", "9527"} {
  203. for _, rule := range readRules {
  204. if err := ensureCasbinRule(role, rule[0], rule[1]); err != nil {
  205. return err
  206. }
  207. }
  208. }
  209. for _, role := range []string{"888", "9527"} {
  210. for _, rule := range [][2]string{
  211. {"/payment/methods", "GET"}, {"/payment/methods", "POST"},
  212. {"/payment/methods/:id", "PUT"}, {"/payment/methods/:id/status", "PATCH"},
  213. {"/payment/methods/:id", "DELETE"},
  214. {"/payment/entries", "GET"}, {"/payment/entries", "POST"},
  215. {"/payment/entries/:id", "PUT"}, {"/payment/entries/:id/status", "PATCH"},
  216. {"/payment/entries/:id", "DELETE"},
  217. } {
  218. if err := ensureCasbinRule(role, rule[0], rule[1]); err != nil {
  219. return err
  220. }
  221. }
  222. }
  223. for _, role := range []string{"618", "888", "9527"} {
  224. for _, rule := range [][2]string{
  225. {"/digital-ticket/summary", "GET"},
  226. {"/digital-ticket/list", "GET"},
  227. {"/digital-ticket/:ticketNo", "GET"},
  228. {"/digital-ticket/:ticketNo/pay", "POST"},
  229. {"/digital-ticket/:ticketNo/exit", "POST"},
  230. } {
  231. if err := ensureCasbinRule(role, rule[0], rule[1]); err != nil {
  232. return err
  233. }
  234. }
  235. }
  236. return nil
  237. }
  238. func ensurePaymentMenu(db *gorm.DB, parentID uint, name, path, component, title, icon string, sort int) (*dao.SysBaseMenu, error) {
  239. menu := &dao.SysBaseMenu{}
  240. err := db.Where("name = ?", name).First(menu).Error
  241. if errors.Is(err, gorm.ErrRecordNotFound) {
  242. *menu = dao.SysBaseMenu{
  243. MenuLevel: 0, ParentId: parentID, Path: path, Name: name,
  244. Hidden: false, Component: component, Sort: sort,
  245. Meta: dao.Meta{Title: title, Icon: icon},
  246. }
  247. if err := db.Create(menu).Error; err != nil {
  248. return nil, fmt.Errorf("初始化%s菜单失败: %w", title, err)
  249. }
  250. return menu, nil
  251. }
  252. if err != nil {
  253. return nil, fmt.Errorf("读取%s菜单失败: %w", title, err)
  254. }
  255. if err := db.Model(menu).Updates(map[string]interface{}{"parent_id": parentID, "path": path, "component": component, "sort": sort}).Error; err != nil {
  256. return nil, fmt.Errorf("更新%s菜单失败: %w", title, err)
  257. }
  258. return menu, nil
  259. }
  260. func ensureCasbinRule(role, path, method string) error {
  261. db := global.GVA_DB
  262. var count int64
  263. if err := db.Table("casbin_rule").Where("ptype = ? AND v0 = ? AND v1 = ? AND v2 = ?", "p", role, path, method).Count(&count).Error; err != nil {
  264. return err
  265. }
  266. if count == 0 {
  267. return db.Exec("INSERT INTO casbin_rule (ptype, v0, v1, v2, v3, v4, v5) VALUES (?, ?, ?, ?, '', '', '')", "p", role, path, method).Error
  268. }
  269. return nil
  270. }