package delivery import ( "errors" "math" "strings" "time" "git.apinb.com/bsm-sdk/core/errcode" "git.apinb.com/bsm-sdk/core/infra" "git.apinb.com/heqiapp/platforms/backend/api/internal/config" "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common" "git.apinb.com/heqiapp/platforms/backend/api/internal/models" "github.com/gin-gonic/gin" "gorm.io/gorm" "gorm.io/gorm/clause" ) func currentWallet(ctx *gin.Context, point models.DeliveryBasic) (models.WalletBasic, bool) { var wallet models.WalletBasic if err := common.ActiveRecords(db()).Where("owner_type = ? AND owner_id = ? AND owner_identity = ?", "delivery", point.ID, point.Identity).First(&wallet).Error; err != nil { common.RespondRecordError(ctx, err) return wallet, false } return wallet, true } func ListWallet(ctx *gin.Context) { point, _, ok := currentScope(ctx) if ok { listScoped(ctx, &models.WalletBasic{}, common.ActiveRecords(db().Model(&models.WalletBasic{})). Where("owner_type = ? AND owner_id = ?", "delivery", point.ID), "wallet_basic.created_at desc") } } func GetWallet(ctx *gin.Context) { point, _, ok := currentScope(ctx) if !ok { return } var wallet models.WalletBasic respondRecord(ctx, common.ActiveRecords(db()).Where( "identity = ? AND owner_type = ? AND owner_id = ?", ctx.Param("identity"), "delivery", point.ID), &wallet) } func listWalletChild(ctx *gin.Context, model any, table string) { point, _, ok := currentScope(ctx) if !ok { return } query := common.ActiveRecords(db().Model(model)). Joins("JOIN wallet_basic ON wallet_basic.id = "+table+".wallet_basic_id"). Where("wallet_basic.owner_type = ? AND wallet_basic.owner_id = ?", "delivery", point.ID) listScoped(ctx, model, query, table+".created_at desc") } func ListBank(ctx *gin.Context) { listWalletChild(ctx, &models.WalletBank{}, "wallet_bank") } func ListPayment(ctx *gin.Context) { listWalletChild(ctx, &models.PaymentOrder{}, "payment_order") } func ListRecord(ctx *gin.Context) { listWalletChild(ctx, &models.WalletRecord{}, "wallet_record") } func ListRefund(ctx *gin.Context) { listWalletChild(ctx, &models.PaymentRefund{}, "payment_refund") } func ListRecharge(ctx *gin.Context) { point, _, ok := currentScope(ctx) if !ok { return } query := common.ActiveRecords(db().Model(&models.WalletRecord{})). Joins("JOIN wallet_basic ON wallet_basic.id = wallet_record.wallet_basic_id"). Where("wallet_basic.owner_type = ? AND wallet_basic.owner_id = ? AND wallet_record.trade_type = ?", "delivery", point.ID, "delivery_admin_recharge") listScoped(ctx, &models.WalletRecord{}, query, "wallet_record.created_at desc") } func ListApplyCash(ctx *gin.Context) { listWalletChild(ctx, &models.WalletApplyCash{}, "wallet_apply_cash") } func getWalletChild(ctx *gin.Context, model any, table string) { point, _, ok := currentScope(ctx) if !ok { return } query := common.ActiveRecords(db().Model(model)). Joins("JOIN wallet_basic ON wallet_basic.id = "+table+".wallet_basic_id"). Where(table+".identity = ? AND wallet_basic.owner_type = ? AND wallet_basic.owner_id = ?", ctx.Param("identity"), "delivery", point.ID) respondRecord(ctx, query, model) } func GetBank(ctx *gin.Context) { getWalletChild(ctx, &models.WalletBank{}, "wallet_bank") } func GetPayment(ctx *gin.Context) { getWalletChild(ctx, &models.PaymentOrder{}, "payment_order") } func GetRecord(ctx *gin.Context) { getWalletChild(ctx, &models.WalletRecord{}, "wallet_record") } func GetRefund(ctx *gin.Context) { getWalletChild(ctx, &models.PaymentRefund{}, "payment_refund") } func GetRecharge(ctx *gin.Context) { point, _, ok := currentScope(ctx) if !ok { return } var record models.WalletRecord query := common.ActiveRecords(db().Model(&models.WalletRecord{})). Joins("JOIN wallet_basic ON wallet_basic.id = wallet_record.wallet_basic_id"). Where("wallet_record.identity = ? AND wallet_basic.owner_type = ? AND wallet_basic.owner_id = ? AND wallet_record.trade_type = ?", ctx.Param("identity"), "delivery", point.ID, "delivery_admin_recharge") respondRecord(ctx, query, &record) } func GetApplyCash(ctx *gin.Context) { getWalletChild(ctx, &models.WalletApplyCash{}, "wallet_apply_cash") } func Recharge(ctx *gin.Context) { account, point, _, ok := CurrentDeliveryAccount(ctx) if !ok { return } var request struct { RequestNo string `json:"request_no" binding:"required,max=128"` Amount int64 `json:"amount" binding:"required"` Reason string `json:"reason" binding:"required,max=1000"` Remark string `json:"remark" binding:"max=2000"` } if err := ctx.ShouldBindJSON(&request); err != nil || request.Amount <= 0 || request.Amount > config.Spec.Global.ManualRechargeMaxAmount || strings.TrimSpace(request.RequestNo) == "" || strings.TrimSpace(request.Reason) == "" { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } var record models.WalletRecord err := db().Transaction(func(tx *gorm.DB) error { if err := tx.Where("request_no = ?", request.RequestNo).First(&record).Error; err == nil { return nil } else if !errors.Is(err, gorm.ErrRecordNotFound) { return err } var wallet models.WalletBasic if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where("owner_type = ? AND owner_id = ? AND owner_identity = ?", "delivery", point.ID, point.Identity). First(&wallet).Error; err != nil { return err } result := tx.Model(&wallet).Where("status = ? AND balance <= ?", common.StatusEnable, math.MaxInt64-request.Amount). Update("balance", gorm.Expr("balance + ?", request.Amount)) if result.Error != nil || result.RowsAffected != 1 { return errors.New("wallet disabled or balance overflow") } if err := tx.First(&wallet, wallet.ID).Error; err != nil { return err } now := time.Now() record = models.WalletRecord{ Entity: common.NewEntity(common.StatusEnable), WalletBasicID: wallet.ID, RecordNo: models.NewIdentity(), RequestNo: request.RequestNo, Direction: "income", TradeType: "delivery_admin_recharge", Amount: request.Amount, BalanceAfter: wallet.Balance, WithdrawalBalanceAfter: wallet.WithdrawalBalance, InTradeNo: request.RequestNo, PayChannel: "delivery_admin", OperatorIdentity: account.Identity, OperatorName: account.DisplayName, Ymd: int32(now.Year()*10000 + int(now.Month())*100 + now.Day()), Ym: int32(now.Year()*100 + int(now.Month())), Remark: strings.TrimSpace(request.Reason + " " + request.Remark), } return tx.Create(&record).Error }) if err != nil { infra.Response.Error(ctx, err) return } common.RespondCreatedResource(ctx, record) } func CreateApplyCash(ctx *gin.Context) { account, point, _, ok := CurrentDeliveryAccount(ctx) if !ok { return } wallet, ok := currentWallet(ctx, point) if !ok { return } var request struct { WalletBankIdentity string `json:"wallet_bank_identity"` RequestNo string `json:"request_no" binding:"required,max=128"` Amount int64 `json:"amount" binding:"required"` Channel string `json:"channel" binding:"required,max=32"` Remark string `json:"remark" binding:"max=2000"` } if err := ctx.ShouldBindJSON(&request); err != nil || request.Amount <= 0 || strings.TrimSpace(request.RequestNo) == "" { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } var apply models.WalletApplyCash err := db().Transaction(func(tx *gorm.DB) error { var bankID uint64 if request.WalletBankIdentity != "" { var bank models.WalletBank if err := common.ActiveRecords(tx).Where("identity = ? AND wallet_basic_id = ?", request.WalletBankIdentity, wallet.ID).First(&bank).Error; err != nil { return err } bankID = bank.ID } var createErr error apply, _, createErr = common.CreateReservedWithdrawal(tx, common.WalletWithdrawalInput{ WalletBasicID: wallet.ID, WalletBankID: bankID, RequestNo: request.RequestNo, Amount: request.Amount, Channel: request.Channel, Remark: request.Remark, OperatorIdentity: account.Identity, OperatorName: account.DisplayName, }) return createErr }) if err != nil { infra.Response.Error(ctx, err) return } common.RespondCreatedResource(ctx, apply) } func ListSettlement(ctx *gin.Context) { point, _, ok := currentScope(ctx) if ok { listScoped(ctx, &models.FinSettlement{}, common.ActiveRecords(db().Model(&models.FinSettlement{})). Where("subject_type = ? AND subject_id = ?", "delivery", point.ID), "fin_settlement.created_at desc") } } func GetSettlement(ctx *gin.Context) { point, _, ok := currentScope(ctx) if !ok { return } var settlement models.FinSettlement respondRecord(ctx, common.ActiveRecords(db()).Where( "identity = ? AND subject_type = ? AND subject_id = ?", ctx.Param("identity"), "delivery", point.ID), &settlement) }