128 lines
4.4 KiB
Go
128 lines
4.4 KiB
Go
package repository
|
||
|
||
import (
|
||
"errors"
|
||
"strings"
|
||
|
||
"hyapp-admin-server/internal/model"
|
||
|
||
"gorm.io/gorm"
|
||
"gorm.io/gorm/clause"
|
||
)
|
||
|
||
var ErrFinanceApplicationAlreadyAudited = errors.New("finance application already audited")
|
||
|
||
type FinanceApplicationListOptions struct {
|
||
Page int
|
||
PageSize int
|
||
AppCode string
|
||
Operation string
|
||
Status string
|
||
Keyword string
|
||
ApplicantUserID uint
|
||
}
|
||
|
||
type FinanceApplicationAuditInput struct {
|
||
Decision string
|
||
AuditorUserID uint
|
||
AuditorName string
|
||
AuditRemark string
|
||
AuditedAtMS int64
|
||
WalletCommandID string
|
||
WalletTransactionID string
|
||
WalletAssetType string
|
||
WalletAmountDelta int64
|
||
WalletBalanceAfter int64
|
||
}
|
||
|
||
func (s *Store) CreateFinanceApplication(application *model.FinanceApplication) error {
|
||
if application == nil {
|
||
return errors.New("finance application is required")
|
||
}
|
||
return s.db.Create(application).Error
|
||
}
|
||
|
||
func (s *Store) GetFinanceApplication(id uint) (*model.FinanceApplication, error) {
|
||
var application model.FinanceApplication
|
||
if err := s.db.First(&application, id).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return &application, nil
|
||
}
|
||
|
||
func (s *Store) ListFinanceApplications(options FinanceApplicationListOptions) ([]model.FinanceApplication, int64, error) {
|
||
page, pageSize := normalizePage(options.Page, options.PageSize)
|
||
query := s.db.Model(&model.FinanceApplication{})
|
||
query = applyFinanceApplicationFilters(query, options)
|
||
|
||
var total int64
|
||
if err := query.Count(&total).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
var applications []model.FinanceApplication
|
||
err := query.
|
||
Order("created_at_ms DESC, id DESC").
|
||
Limit(pageSize).
|
||
Offset((page - 1) * pageSize).
|
||
Find(&applications).Error
|
||
return applications, total, err
|
||
}
|
||
|
||
func (s *Store) AuditFinanceApplication(id uint, input FinanceApplicationAuditInput) (*model.FinanceApplication, error) {
|
||
if id == 0 {
|
||
return nil, errors.New("finance application id is required")
|
||
}
|
||
if input.Decision != model.FinanceApplicationStatusApproved && input.Decision != model.FinanceApplicationStatusRejected {
|
||
return nil, errors.New("finance application decision is invalid")
|
||
}
|
||
|
||
var application model.FinanceApplication
|
||
err := s.db.Transaction(func(tx *gorm.DB) error {
|
||
// 审批是一次性状态流转:先用行锁读取当前状态,再只允许 pending 进入终态,防止两个财务并发点击导致后写覆盖先写。
|
||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&application, id).Error; err != nil {
|
||
return err
|
||
}
|
||
if application.Status != model.FinanceApplicationStatusPending {
|
||
return ErrFinanceApplicationAlreadyAudited
|
||
}
|
||
return tx.Model(&application).Updates(map[string]any{
|
||
"status": input.Decision,
|
||
"auditor_user_id": input.AuditorUserID,
|
||
"auditor_name": strings.TrimSpace(input.AuditorName),
|
||
"audit_remark": strings.TrimSpace(input.AuditRemark),
|
||
"audited_at_ms": input.AuditedAtMS,
|
||
"wallet_command_id": strings.TrimSpace(input.WalletCommandID),
|
||
"wallet_transaction_id": strings.TrimSpace(input.WalletTransactionID),
|
||
"wallet_asset_type": strings.TrimSpace(input.WalletAssetType),
|
||
"wallet_amount_delta": input.WalletAmountDelta,
|
||
"wallet_balance_after": input.WalletBalanceAfter,
|
||
"updated_at_ms": input.AuditedAtMS,
|
||
}).Error
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return s.GetFinanceApplication(id)
|
||
}
|
||
|
||
func applyFinanceApplicationFilters(query *gorm.DB, options FinanceApplicationListOptions) *gorm.DB {
|
||
if options.ApplicantUserID > 0 {
|
||
// “我的申请”接口只允许服务端用登录态限定申请人,不能信任前端传申请人 ID,避免普通发起人越权查看他人财务申请。
|
||
query = query.Where("applicant_user_id = ?", options.ApplicantUserID)
|
||
}
|
||
if appCode := strings.TrimSpace(options.AppCode); appCode != "" {
|
||
query = query.Where("app_code = ?", appCode)
|
||
}
|
||
if operation := strings.TrimSpace(options.Operation); operation != "" {
|
||
query = query.Where("operation = ?", operation)
|
||
}
|
||
if status := strings.TrimSpace(options.Status); status != "" && status != "all" {
|
||
query = query.Where("status = ?", status)
|
||
}
|
||
if keyword := strings.TrimSpace(options.Keyword); keyword != "" {
|
||
like := "%" + keyword + "%"
|
||
query = query.Where("target_user_id LIKE ? OR applicant_name LIKE ? OR auditor_name LIKE ? OR CAST(applicant_user_id AS CHAR) LIKE ? OR CAST(id AS CHAR) LIKE ?", like, like, like, like, like)
|
||
}
|
||
return query
|
||
}
|