153 lines
4.9 KiB
Go
153 lines
4.9 KiB
Go
|
|
package services
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
|
||
|
|
"tyapi-server/internal/domains/finance/entities"
|
||
|
|
"tyapi-server/internal/domains/finance/value_objects"
|
||
|
|
|
||
|
|
"github.com/shopspring/decimal"
|
||
|
|
)
|
||
|
|
|
||
|
|
// InvoiceDomainService 发票领域服务接口
|
||
|
|
// 职责:处理发票领域的业务规则和计算逻辑,不涉及外部依赖
|
||
|
|
type InvoiceDomainService interface {
|
||
|
|
// 验证发票信息完整性
|
||
|
|
ValidateInvoiceInfo(ctx context.Context, info *value_objects.InvoiceInfo, invoiceType value_objects.InvoiceType) error
|
||
|
|
|
||
|
|
// 验证开票金额是否合法(基于业务规则)
|
||
|
|
ValidateInvoiceAmount(ctx context.Context, amount decimal.Decimal, availableAmount decimal.Decimal) error
|
||
|
|
|
||
|
|
// 计算可开票金额(纯计算逻辑)
|
||
|
|
CalculateAvailableAmount(totalRecharged decimal.Decimal, totalGifted decimal.Decimal, totalInvoiced decimal.Decimal) decimal.Decimal
|
||
|
|
|
||
|
|
// 验证发票申请状态转换
|
||
|
|
ValidateStatusTransition(currentStatus entities.ApplicationStatus, targetStatus entities.ApplicationStatus) error
|
||
|
|
|
||
|
|
// 验证发票申请业务规则
|
||
|
|
ValidateInvoiceApplication(ctx context.Context, application *entities.InvoiceApplication) error
|
||
|
|
}
|
||
|
|
|
||
|
|
// InvoiceDomainServiceImpl 发票领域服务实现
|
||
|
|
type InvoiceDomainServiceImpl struct {
|
||
|
|
// 领域服务不依赖仓储,只处理业务规则
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewInvoiceDomainService 创建发票领域服务
|
||
|
|
func NewInvoiceDomainService() InvoiceDomainService {
|
||
|
|
return &InvoiceDomainServiceImpl{}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ValidateInvoiceInfo 验证发票信息完整性
|
||
|
|
func (s *InvoiceDomainServiceImpl) ValidateInvoiceInfo(ctx context.Context, info *value_objects.InvoiceInfo, invoiceType value_objects.InvoiceType) error {
|
||
|
|
if info == nil {
|
||
|
|
return errors.New("发票信息不能为空")
|
||
|
|
}
|
||
|
|
|
||
|
|
switch invoiceType {
|
||
|
|
case value_objects.InvoiceTypeGeneral:
|
||
|
|
return info.ValidateForGeneralInvoice()
|
||
|
|
case value_objects.InvoiceTypeSpecial:
|
||
|
|
return info.ValidateForSpecialInvoice()
|
||
|
|
default:
|
||
|
|
return errors.New("无效的发票类型")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ValidateInvoiceAmount 验证开票金额是否合法(基于业务规则)
|
||
|
|
func (s *InvoiceDomainServiceImpl) ValidateInvoiceAmount(ctx context.Context, amount decimal.Decimal, availableAmount decimal.Decimal) error {
|
||
|
|
if amount.LessThanOrEqual(decimal.Zero) {
|
||
|
|
return errors.New("开票金额必须大于0")
|
||
|
|
}
|
||
|
|
|
||
|
|
if amount.GreaterThan(availableAmount) {
|
||
|
|
return fmt.Errorf("开票金额不能超过可开票金额,可开票金额:%s", availableAmount.String())
|
||
|
|
}
|
||
|
|
|
||
|
|
// 最小开票金额限制
|
||
|
|
minAmount := decimal.NewFromFloat(0.01) // 最小0.01元
|
||
|
|
if amount.LessThan(minAmount) {
|
||
|
|
return fmt.Errorf("开票金额不能少于%s元", minAmount.String())
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// CalculateAvailableAmount 计算可开票金额(纯计算逻辑)
|
||
|
|
func (s *InvoiceDomainServiceImpl) CalculateAvailableAmount(totalRecharged decimal.Decimal, totalGifted decimal.Decimal, totalInvoiced decimal.Decimal) decimal.Decimal {
|
||
|
|
// 可开票金额 = 充值金额 - 已开票金额(不包含赠送金额)
|
||
|
|
availableAmount := totalRecharged.Sub(totalInvoiced)
|
||
|
|
if availableAmount.LessThan(decimal.Zero) {
|
||
|
|
availableAmount = decimal.Zero
|
||
|
|
}
|
||
|
|
return availableAmount
|
||
|
|
}
|
||
|
|
|
||
|
|
// ValidateStatusTransition 验证发票申请状态转换
|
||
|
|
func (s *InvoiceDomainServiceImpl) ValidateStatusTransition(currentStatus entities.ApplicationStatus, targetStatus entities.ApplicationStatus) error {
|
||
|
|
// 定义允许的状态转换
|
||
|
|
allowedTransitions := map[entities.ApplicationStatus][]entities.ApplicationStatus{
|
||
|
|
entities.ApplicationStatusPending: {
|
||
|
|
entities.ApplicationStatusCompleted,
|
||
|
|
entities.ApplicationStatusRejected,
|
||
|
|
},
|
||
|
|
entities.ApplicationStatusCompleted: {
|
||
|
|
// 已完成状态不能再转换
|
||
|
|
},
|
||
|
|
entities.ApplicationStatusRejected: {
|
||
|
|
// 已拒绝状态不能再转换
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
allowedTargets, exists := allowedTransitions[currentStatus]
|
||
|
|
if !exists {
|
||
|
|
return fmt.Errorf("无效的当前状态:%s", currentStatus)
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, allowed := range allowedTargets {
|
||
|
|
if allowed == targetStatus {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return fmt.Errorf("不允许从状态 %s 转换到状态 %s", currentStatus, targetStatus)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ValidateInvoiceApplication 验证发票申请业务规则
|
||
|
|
func (s *InvoiceDomainServiceImpl) ValidateInvoiceApplication(ctx context.Context, application *entities.InvoiceApplication) error {
|
||
|
|
if application == nil {
|
||
|
|
return errors.New("发票申请不能为空")
|
||
|
|
}
|
||
|
|
|
||
|
|
// 验证基础字段
|
||
|
|
if application.UserID == "" {
|
||
|
|
return errors.New("用户ID不能为空")
|
||
|
|
}
|
||
|
|
|
||
|
|
if application.Amount.LessThanOrEqual(decimal.Zero) {
|
||
|
|
return errors.New("申请金额必须大于0")
|
||
|
|
}
|
||
|
|
|
||
|
|
// 验证发票类型
|
||
|
|
if !application.InvoiceType.IsValid() {
|
||
|
|
return errors.New("无效的发票类型")
|
||
|
|
}
|
||
|
|
|
||
|
|
// 验证开票信息
|
||
|
|
if application.CompanyName == "" {
|
||
|
|
return errors.New("公司名称不能为空")
|
||
|
|
}
|
||
|
|
|
||
|
|
if application.TaxpayerID == "" {
|
||
|
|
return errors.New("纳税人识别号不能为空")
|
||
|
|
}
|
||
|
|
|
||
|
|
if application.ReceivingEmail == "" {
|
||
|
|
return errors.New("发票接收邮箱不能为空")
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|