batch2_load.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. package fixture
  2. import (
  3. "context"
  4. "database/sql"
  5. "fmt"
  6. )
  7. // 本文件是第二批的写入层,只写 5 张表:
  8. // t_expenditure(字典,D1)/ t_user_divide_into / t_user_divide_into_log /
  9. // t_user_balance_request / t_expenditure_log
  10. //
  11. // 两种主键口径各自的幂等写法,是本批的核心:
  12. // · D1(显式插 id):主键就是 legacy id,天然幂等 → `ON DUPLICATE KEY UPDATE` 命中 PK
  13. // · biz_no:target 的 id 由自增发号,幂等键是 `uk_biz_no` 唯一键 → `ON DUPLICATE KEY UPDATE`
  14. // 命中唯一键时 **MySQL 只更新指定列,不会改写 id**,所以重跑 id 不变。
  15. // 切记不能写成纯 `INSERT`——那会在第二次跑报 1062,或(若无唯一键)插出重复行。
  16. // upsertExpenditureDict 写收支类型字典(D1)。
  17. func upsertExpenditureDict(ctx context.Context, db *sql.DB, r TargetExpenditure) error {
  18. const q = `
  19. INSERT INTO t_expenditure (id, type, parent_id, name, remark, institution_id)
  20. VALUES (?, ?, ?, ?, ?, ?)
  21. ON DUPLICATE KEY UPDATE
  22. type = VALUES(type),
  23. parent_id = VALUES(parent_id),
  24. name = VALUES(name),
  25. remark = VALUES(remark),
  26. institution_id = VALUES(institution_id)`
  27. if _, err := db.ExecContext(ctx, q, r.ID, r.Type, r.ParentID, r.Name,
  28. nullStr(r.Remark), r.InstitutionID); err != nil {
  29. return fmt.Errorf("写 t_expenditure(id=%d) 失败:%w", r.ID, err)
  30. }
  31. return nil
  32. }
  33. // upsertDivideInto 写分账树节点(自然键 user_id)。
  34. func upsertDivideInto(ctx context.Context, db *sql.DB, r TargetDivideInto) error {
  35. const q = `
  36. INSERT INTO t_user_divide_into
  37. (user_id, type, quota, total_income, total_income_payed,
  38. root_user_id, parent_user_id, left_val, right_val, layer, created_at, updated_at)
  39. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  40. ON DUPLICATE KEY UPDATE
  41. type = VALUES(type),
  42. quota = VALUES(quota),
  43. total_income = VALUES(total_income),
  44. total_income_payed = VALUES(total_income_payed),
  45. root_user_id = VALUES(root_user_id),
  46. parent_user_id = VALUES(parent_user_id),
  47. left_val = VALUES(left_val),
  48. right_val = VALUES(right_val),
  49. layer = VALUES(layer)`
  50. _, err := db.ExecContext(ctx, q, r.UserID, r.Type,
  51. CentsToDecimal(r.QuotaCents), CentsToDecimal(r.TotalIncomeCents),
  52. CentsToDecimal(r.TotalIncomePayedCents),
  53. nullStr(r.RootUserID), nullStr(r.ParentUserID),
  54. r.LeftVal, r.RightVal, r.Layer,
  55. r.CreatedAt.Format("2006-01-02 15:04:05"), r.UpdatedAt.Format("2006-01-02 15:04:05"))
  56. if err != nil {
  57. return fmt.Errorf("写 t_user_divide_into(user=%s) 失败:%w", r.UserID, err)
  58. }
  59. return nil
  60. }
  61. // upsertDivideIntoLog 写分账明细(D1:显式插 legacy id)。
  62. func upsertDivideIntoLog(ctx context.Context, db *sql.DB, r TargetDivideIntoLog) error {
  63. const q = `
  64. INSERT INTO t_user_divide_into_log
  65. (id, type, user_id, student_id, order_no, money, create_time, remark)
  66. VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  67. ON DUPLICATE KEY UPDATE
  68. type = VALUES(type),
  69. user_id = VALUES(user_id),
  70. student_id = VALUES(student_id),
  71. order_no = VALUES(order_no),
  72. money = VALUES(money),
  73. create_time = VALUES(create_time),
  74. remark = VALUES(remark)`
  75. _, err := db.ExecContext(ctx, q, r.ID, r.Type, r.UserID, r.StudentID,
  76. nullStr(r.OrderNo), CentsToDecimal(r.MoneyCents),
  77. r.CreateTime.Format("2006-01-02 15:04:05"), nullStr(r.Remark))
  78. if err != nil {
  79. return fmt.Errorf("写 t_user_divide_into_log(id=%d) 失败:%w", r.ID, err)
  80. }
  81. return nil
  82. }
  83. // upsertBalanceRequest 写充值/退费工单(**biz_no 口径**)。
  84. //
  85. // biz_no 是唯一键 uk_biz_no;命中时只更新业务列,**id 保持首次分配值**。
  86. func upsertBalanceRequest(ctx context.Context, db *sql.DB, r TargetBalanceRequest) error {
  87. const q = `
  88. INSERT INTO t_user_balance_request
  89. (biz_no, type, user_id, institution_id, campus_id, to_institution_id, to_campus_id,
  90. money, transfer_accounts_type, transfer_accounts_time, order_no,
  91. request_user_id, request_time, response_user_id, response_time,
  92. response_msg, approved, item, imgs, remark, created_at, updated_at)
  93. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  94. ON DUPLICATE KEY UPDATE
  95. type = VALUES(type),
  96. user_id = VALUES(user_id),
  97. institution_id = VALUES(institution_id),
  98. campus_id = VALUES(campus_id),
  99. to_institution_id = VALUES(to_institution_id),
  100. to_campus_id = VALUES(to_campus_id),
  101. money = VALUES(money),
  102. transfer_accounts_type = VALUES(transfer_accounts_type),
  103. transfer_accounts_time = VALUES(transfer_accounts_time),
  104. order_no = VALUES(order_no),
  105. request_user_id = VALUES(request_user_id),
  106. request_time = VALUES(request_time),
  107. response_user_id = VALUES(response_user_id),
  108. response_time = VALUES(response_time),
  109. response_msg = VALUES(response_msg),
  110. approved = VALUES(approved),
  111. item = VALUES(item),
  112. imgs = VALUES(imgs),
  113. remark = VALUES(remark)`
  114. _, err := db.ExecContext(ctx, q,
  115. r.BizNo, r.Type, r.UserID, r.InstitutionID, r.CampusID,
  116. nullInt(r.ToInstitutionID), nullInt(r.ToCampusID),
  117. CentsToDecimal(r.MoneyCents), r.TransferAccountsType,
  118. nullTime(r.TransferAccountsTime), nullStr(r.OrderNo),
  119. r.RequestUserID, r.RequestTime.Format("2006-01-02 15:04:05"),
  120. nullStr(r.ResponseUserID), nullTime(r.ResponseTime),
  121. nullStr(r.ResponseMsg), r.Approved, nullStr(r.Item), nullStr(r.Imgs), nullStr(r.Remark),
  122. r.CreatedAt.Format("2006-01-02 15:04:05"), r.UpdatedAt.Format("2006-01-02 15:04:05"))
  123. if err != nil {
  124. return fmt.Errorf("写 t_user_balance_request(biz_no=%s) 失败:%w", r.BizNo, err)
  125. }
  126. return nil
  127. }
  128. // upsertExpenditureLog 写收支记录(**biz_no 口径**)。
  129. func upsertExpenditureLog(ctx context.Context, db *sql.DB, r TargetExpenditureLog) error {
  130. const q = `
  131. INSERT INTO t_expenditure_log
  132. (biz_no, institution_id, campus_id, expenditure_id, cost,
  133. create_user_id, create_time, year, month, remark, imgs)
  134. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  135. ON DUPLICATE KEY UPDATE
  136. institution_id = VALUES(institution_id),
  137. campus_id = VALUES(campus_id),
  138. expenditure_id = VALUES(expenditure_id),
  139. cost = VALUES(cost),
  140. create_user_id = VALUES(create_user_id),
  141. create_time = VALUES(create_time),
  142. year = VALUES(year),
  143. month = VALUES(month),
  144. remark = VALUES(remark),
  145. imgs = VALUES(imgs)`
  146. _, err := db.ExecContext(ctx, q, r.BizNo, r.InstitutionID, r.CampusID, r.ExpenditureID,
  147. CentsToDecimal(r.CostCents), nullStr(r.CreateUserID),
  148. r.CreateTime.Format("2006-01-02 15:04:05"),
  149. nullInt(r.Year), nullInt(r.Month), nullStr(r.Remark), nullStr(r.Imgs))
  150. if err != nil {
  151. return fmt.Errorf("写 t_expenditure_log(biz_no=%s) 失败:%w", r.BizNo, err)
  152. }
  153. return nil
  154. }
  155. func nullInt(v sql.NullInt64) any {
  156. if !v.Valid {
  157. return nil
  158. }
  159. return v.Int64
  160. }
  161. func nullTime(v sql.NullTime) any {
  162. if !v.Valid {
  163. return nil
  164. }
  165. return v.Time.Format("2006-01-02 15:04:05")
  166. }