Files
platforms/backend/api/internal/logic/delivery/finance.go
2026-08-19 23:43:02 +08:00

262 lines
9.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 功能:提供配送点管理端钱包、支付、充值、提现及结算接口。
// 版本v1.1。
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 ListRecord(ctx *gin.Context) { listWalletChild(ctx, &models.WalletRecord{}, "wallet_record") }
func ListRefund(ctx *gin.Context) { listWalletChild(ctx, &models.PaymentRefund{}, "payment_refund") }
// paymentOrderQuery 按统一支付单关联的业务对象限定当前配送点数据范围。
func paymentOrderQuery(point models.DeliveryBasic) *gorm.DB {
query := common.ActiveRecords(db().Model(&models.PaymentOrder{}))
return common.ScopePaymentOrdersByOwner(query, "delivery", point.ID, point.Identity)
}
// ListPayment 返回当前配送点业务范围内的统一支付记录。
func ListPayment(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
listScoped(ctx, &models.PaymentOrder{}, paymentOrderQuery(point), "payment_order.created_at desc")
}
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 GetRecord(ctx *gin.Context) { getWalletChild(ctx, &models.WalletRecord{}, "wallet_record") }
func GetRefund(ctx *gin.Context) { getWalletChild(ctx, &models.PaymentRefund{}, "payment_refund") }
// GetPayment 返回当前配送点业务范围内的单条统一支付记录。
func GetPayment(ctx *gin.Context) {
point, _, ok := currentScope(ctx)
if !ok {
return
}
query := paymentOrderQuery(point).Where("payment_order.identity = ?", ctx.Param("identity"))
respondRecord(ctx, query, &models.PaymentOrder{})
}
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) == "" ||
!common.IsSupportedOrganizationWithdrawalChannel(request.Channel) {
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)
}