| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 |
- // 余额账户仓储的「老带新结算」增量(AC-08)——`total_recommended_amount` 列 + 107 流水定位。
- //
- // 为什么要独立文件:`expense_repo.go` 是 AC-03/AC-04/AC-05 的共用文件,本批只**新增**
- // 一个列名常量与两个只读/统计方法(对既有行为零改动),集中在本文件便于评审与回滚。
- //
- // dump 依据(`sp_courses_activity_use_activity_recommend`,yuansiwei_finance.sql:763344-763366):
- //
- // UPDATE t_user_expense_school SET balance=balance+m, total_recommended_amount=total_recommended_amount+m
- // WHERE userid=? AND schoolid=? AND campusid=?;
- // UPDATE t_user_expense SET balance=balance+m, total_recommended_amount=total_recommended_amount+m
- // WHERE userid=?;
- // INSERT INTO t_user_expense_log(userid,type,logid,money,create_userid,create_time,remark)
- // VALUES(?,107,inputOrder,m,...);
- //
- // 两条 UPDATE 都是「balance 与 total_recommended_amount 同一语句双增」——拆开会出现
- // "余额加了、累计推荐额没加"的中间态(对账/双跑会读到半成品),故走 `ColumnDelta` 多列原子增减。
- package repository
- import (
- "context"
- "errors"
- "strings"
- "gorm.io/gorm"
- "yuansiwei/unified-backend/internal/academic/model"
- infraerrors "yuansiwei/unified-backend/internal/infra/errors"
- )
- // ExpenseColumnTotalRecommendedAmount 累计推荐金额列(老带新双边入账累加)。
- //
- // 该列**不在** AC-03 的初始白名单里(当时无写入场景),AC-08 结算需要按 dump 与
- // `balance` 同一语句双增,故补入白名单;`isExpenseColumn` 已同步(见 expense_repo.go)。
- const ExpenseColumnTotalRecommendedAmount = "total_recommended_amount"
- // FindLogByLogID 按「用户 + 流水 logid + type」取一条余额流水(老带新 107 的幂等回读)。
- //
- // 为什么不用 `FindLogByRequest`:老带新 dump 写的是 `logid=inputOrder`,
- // `request_id` 为 NULL(无幂等键语义)→ 幂等回放只能靠 `(user_id, log_id, type)` 定位。
- //
- // 入参:
- // - ctx 请求上下文;
- // - tx 事务句柄(可 nil:nil 时用连接池或 ctx 内事务);
- // - userID 用户(空 → ErrBadParam);logID 外部单号(空 → ErrBadParam);typ 流水类型。
- //
- // 出参:(*model.UserExpenseLog, nil) 命中;(**nil**, nil) 无该流水;ErrBadParam / ErrDBError。
- func (r *ExpenseRepo) FindLogByLogID(
- ctx context.Context, tx *gorm.DB, userID, logID string, typ int32,
- ) (*model.UserExpenseLog, error) {
- if strings.TrimSpace(userID) == "" || strings.TrimSpace(logID) == "" {
- return nil, infraerrors.WithCause(infraerrors.ErrBadParam,
- errString("repository: FindLogByLogID 需要 user_id 与 log_id"))
- }
- db, err := r.queryDB(ctx, tx)
- if err != nil {
- return nil, err
- }
- var out model.UserExpenseLog
- err = db.Model(&model.UserExpenseLog{}).
- Where("user_id = ? AND log_id = ? AND type = ?", userID, logID, typ).
- Order("id ASC").
- Take(&out).Error
- if err != nil {
- if isRecordNotFound(err) {
- return nil, nil
- }
- return nil, infraerrors.WithCause(infraerrors.ErrDBError, err)
- }
- return &out, nil
- }
- // isRecordNotFound 判定 gorm 的"无行"错误(errors.Is 兼容驱动/包装差异)。
- func isRecordNotFound(err error) bool {
- return errors.Is(err, gorm.ErrRecordNotFound)
- }
|