hyapp-server/server/admin/internal/repository/finance_application_repository.go
2026-07-02 10:53:44 +08:00

128 lines
4.4 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.

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
}