feat: implement gas and delivery admin systems
This commit is contained in:
210
backend/api/internal/logic/delivery/finance.go
Normal file
210
backend/api/internal/logic/delivery/finance.go
Normal file
@@ -0,0 +1,210 @@
|
||||
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.WalletPayment{}, "wallet_payment") }
|
||||
func ListRecord(ctx *gin.Context) { listWalletChild(ctx, &models.WalletRecord{}, "wallet_record") }
|
||||
func ListRefund(ctx *gin.Context) { listWalletChild(ctx, &models.WalletRefund{}, "wallet_refund") }
|
||||
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.WalletPayment{}, "wallet_payment") }
|
||||
func GetRecord(ctx *gin.Context) { getWalletChild(ctx, &models.WalletRecord{}, "wallet_record") }
|
||||
func GetRefund(ctx *gin.Context) { getWalletChild(ctx, &models.WalletRefund{}, "wallet_refund") }
|
||||
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) {
|
||||
point, _, ok := currentScope(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 pending int64
|
||||
if err := db().Model(&models.WalletApplyCash{}).Where("wallet_basic_id = ? AND apply_status = ? AND status <> ?",
|
||||
wallet.ID, common.StatusPending, common.StatusArchived).Select("COALESCE(SUM(amount), 0)").Scan(&pending).Error; err != nil ||
|
||||
request.Amount > wallet.WithdrawalBalance-pending {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
var bankID uint64
|
||||
if request.WalletBankIdentity != "" {
|
||||
var bank models.WalletBank
|
||||
if err := common.ActiveRecords(db()).Where("identity = ? AND wallet_basic_id = ?",
|
||||
request.WalletBankIdentity, wallet.ID).First(&bank).Error; err != nil {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
bankID = bank.ID
|
||||
}
|
||||
apply := models.WalletApplyCash{
|
||||
Entity: common.NewEntity(common.StatusEnable), ApplyStatus: common.StatusPending,
|
||||
WalletBasicID: wallet.ID, WalletBankID: bankID, CashNo: models.NewIdentity(), RequestNo: request.RequestNo,
|
||||
Amount: request.Amount, Channel: request.Channel, Remark: request.Remark,
|
||||
}
|
||||
if err := db().Create(&apply).Error; 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)
|
||||
}
|
||||
Reference in New Issue
Block a user