278 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			278 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package services
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"mime/multipart"
 | |
| 	"time"
 | |
| 
 | |
| 	"tyapi-server/internal/domains/finance/entities"
 | |
| 	"tyapi-server/internal/domains/finance/events"
 | |
| 	"tyapi-server/internal/domains/finance/repositories"
 | |
| 	"tyapi-server/internal/domains/finance/value_objects"
 | |
| 
 | |
| 	"tyapi-server/internal/infrastructure/external/storage"
 | |
| 
 | |
| 	"github.com/shopspring/decimal"
 | |
| 	"go.uber.org/zap"
 | |
| )
 | |
| 
 | |
| // ApplyInvoiceRequest 申请开票请求
 | |
| type ApplyInvoiceRequest struct {
 | |
| 	InvoiceType value_objects.InvoiceType  `json:"invoice_type" binding:"required"`
 | |
| 	Amount      string                     `json:"amount" binding:"required"`
 | |
| 	InvoiceInfo *value_objects.InvoiceInfo `json:"invoice_info" binding:"required"`
 | |
| }
 | |
| 
 | |
| // ApproveInvoiceRequest 通过发票申请请求
 | |
| type ApproveInvoiceRequest struct {
 | |
| 	AdminNotes string `json:"admin_notes"`
 | |
| }
 | |
| 
 | |
| // RejectInvoiceRequest 拒绝发票申请请求
 | |
| type RejectInvoiceRequest struct {
 | |
| 	Reason string `json:"reason" binding:"required"`
 | |
| }
 | |
| 
 | |
| // InvoiceAggregateService 发票聚合服务接口
 | |
| // 职责:协调发票申请聚合根的生命周期,调用领域服务进行业务规则验证,发布领域事件
 | |
| type InvoiceAggregateService interface {
 | |
| 	// 申请开票
 | |
| 	ApplyInvoice(ctx context.Context, userID string, req ApplyInvoiceRequest) (*entities.InvoiceApplication, error)
 | |
| 
 | |
| 	// 通过发票申请(上传发票)
 | |
| 	ApproveInvoiceApplication(ctx context.Context, applicationID string, file multipart.File, req ApproveInvoiceRequest) error
 | |
| 
 | |
| 	// 拒绝发票申请
 | |
| 	RejectInvoiceApplication(ctx context.Context, applicationID string, req RejectInvoiceRequest) error
 | |
| }
 | |
| 
 | |
| // InvoiceAggregateServiceImpl 发票聚合服务实现
 | |
| type InvoiceAggregateServiceImpl struct {
 | |
| 	applicationRepo     repositories.InvoiceApplicationRepository
 | |
| 	userInvoiceInfoRepo repositories.UserInvoiceInfoRepository
 | |
| 	domainService       InvoiceDomainService
 | |
| 	qiniuStorageService *storage.QiNiuStorageService
 | |
| 	logger              *zap.Logger
 | |
| 	eventPublisher      EventPublisher
 | |
| }
 | |
| 
 | |
| // EventPublisher 事件发布器接口
 | |
| type EventPublisher interface {
 | |
| 	PublishInvoiceApplicationCreated(ctx context.Context, event *events.InvoiceApplicationCreatedEvent) error
 | |
| 	PublishInvoiceApplicationApproved(ctx context.Context, event *events.InvoiceApplicationApprovedEvent) error
 | |
| 	PublishInvoiceApplicationRejected(ctx context.Context, event *events.InvoiceApplicationRejectedEvent) error
 | |
| 	PublishInvoiceFileUploaded(ctx context.Context, event *events.InvoiceFileUploadedEvent) error
 | |
| }
 | |
| 
 | |
| // NewInvoiceAggregateService 创建发票聚合服务
 | |
| func NewInvoiceAggregateService(
 | |
| 	applicationRepo repositories.InvoiceApplicationRepository,
 | |
| 	userInvoiceInfoRepo repositories.UserInvoiceInfoRepository,
 | |
| 	domainService InvoiceDomainService,
 | |
| 	qiniuStorageService *storage.QiNiuStorageService,
 | |
| 	logger *zap.Logger,
 | |
| 	eventPublisher EventPublisher,
 | |
| ) InvoiceAggregateService {
 | |
| 	return &InvoiceAggregateServiceImpl{
 | |
| 		applicationRepo:     applicationRepo,
 | |
| 		userInvoiceInfoRepo: userInvoiceInfoRepo,
 | |
| 		domainService:       domainService,
 | |
| 		qiniuStorageService: qiniuStorageService,
 | |
| 		logger:              logger,
 | |
| 		eventPublisher:      eventPublisher,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // ApplyInvoice 申请开票
 | |
| func (s *InvoiceAggregateServiceImpl) ApplyInvoice(ctx context.Context, userID string, req ApplyInvoiceRequest) (*entities.InvoiceApplication, error) {
 | |
| 	// 1. 解析金额
 | |
| 	amount, err := decimal.NewFromString(req.Amount)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("无效的金额格式: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// 2. 验证发票信息
 | |
| 	if err := s.domainService.ValidateInvoiceInfo(ctx, req.InvoiceInfo, req.InvoiceType); err != nil {
 | |
| 		return nil, fmt.Errorf("发票信息验证失败: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// 3. 获取用户开票信息
 | |
| 	userInvoiceInfo, err := s.userInvoiceInfoRepo.FindByUserID(ctx, userID)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("获取用户开票信息失败: %w", err)
 | |
| 	}
 | |
| 	if userInvoiceInfo == nil {
 | |
| 		return nil, fmt.Errorf("用户开票信息不存在")
 | |
| 	}
 | |
| 
 | |
| 	// 4. 创建发票申请聚合根
 | |
| 	application := entities.NewInvoiceApplication(userID, req.InvoiceType, amount, userInvoiceInfo.ID)
 | |
| 
 | |
| 	// 5. 设置开票信息快照
 | |
| 	application.SetInvoiceInfoSnapshot(req.InvoiceInfo)
 | |
| 
 | |
| 	// 6. 验证聚合根业务规则
 | |
| 	if err := s.domainService.ValidateInvoiceApplication(ctx, application); err != nil {
 | |
| 		return nil, fmt.Errorf("发票申请业务规则验证失败: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// 7. 保存聚合根
 | |
| 	if err := s.applicationRepo.Create(ctx, application); err != nil {
 | |
| 		return nil, fmt.Errorf("保存发票申请失败: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// 8. 发布领域事件
 | |
| 	event := events.NewInvoiceApplicationCreatedEvent(
 | |
| 		application.ID,
 | |
| 		application.UserID,
 | |
| 		application.InvoiceType,
 | |
| 		application.Amount,
 | |
| 		application.CompanyName,
 | |
| 		application.ReceivingEmail,
 | |
| 	)
 | |
| 
 | |
| 	if err := s.eventPublisher.PublishInvoiceApplicationCreated(ctx, event); err != nil {
 | |
| 		// 记录错误但不影响主流程
 | |
| 		fmt.Printf("发布发票申请创建事件失败: %v\n", err)
 | |
| 	}
 | |
| 
 | |
| 	return application, nil
 | |
| }
 | |
| 
 | |
| // ApproveInvoiceApplication 通过发票申请(上传发票)
 | |
| func (s *InvoiceAggregateServiceImpl) ApproveInvoiceApplication(ctx context.Context, applicationID string, file multipart.File, req ApproveInvoiceRequest) error {
 | |
| 	// 1. 获取发票申请
 | |
| 	application, err := s.applicationRepo.FindByID(ctx, applicationID)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("获取发票申请失败: %w", err)
 | |
| 	}
 | |
| 	if application == nil {
 | |
| 		return fmt.Errorf("发票申请不存在")
 | |
| 	}
 | |
| 
 | |
| 	// 2. 验证状态转换
 | |
| 	if err := s.domainService.ValidateStatusTransition(application.Status, entities.ApplicationStatusCompleted); err != nil {
 | |
| 		return fmt.Errorf("状态转换验证失败: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// 3. 处理文件上传
 | |
| 	// 读取文件内容
 | |
| 	fileBytes, err := io.ReadAll(file)
 | |
| 	if err != nil {
 | |
| 		s.logger.Error("读取上传文件失败", zap.Error(err))
 | |
| 		return fmt.Errorf("读取上传文件失败: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// 生成文件名(使用时间戳确保唯一性)
 | |
| 	fileName := fmt.Sprintf("invoice_%s_%d.pdf", applicationID, time.Now().Unix())
 | |
| 
 | |
| 	// 上传文件到七牛云
 | |
| 	uploadResult, err := s.qiniuStorageService.UploadFile(ctx, fileBytes, fileName)
 | |
| 	if err != nil {
 | |
| 		s.logger.Error("上传发票文件到七牛云失败", zap.String("file_name", fileName), zap.Error(err))
 | |
| 		return fmt.Errorf("上传发票文件到七牛云失败: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// 从上传结果获取文件信息
 | |
| 	fileID := uploadResult.Key
 | |
| 	fileURL := uploadResult.URL
 | |
| 	fileSize := uploadResult.Size
 | |
| 
 | |
| 	// 4. 更新聚合根状态
 | |
| 	application.MarkCompleted("admin_user_id")
 | |
| 	application.SetFileInfo(fileID, fileName, fileURL, fileSize)
 | |
| 	application.AdminNotes = &req.AdminNotes
 | |
| 
 | |
| 	// 5. 保存聚合根
 | |
| 	if err := s.applicationRepo.Update(ctx, application); err != nil {
 | |
| 		return fmt.Errorf("更新发票申请失败: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// 6. 发布领域事件
 | |
| 	approvedEvent := events.NewInvoiceApplicationApprovedEvent(
 | |
| 		application.ID,
 | |
| 		application.UserID,
 | |
| 		application.Amount,
 | |
| 		application.ReceivingEmail,
 | |
| 	)
 | |
| 
 | |
| 	if err := s.eventPublisher.PublishInvoiceApplicationApproved(ctx, approvedEvent); err != nil {
 | |
| 		s.logger.Error("发布发票申请通过事件失败",
 | |
| 			zap.String("application_id", applicationID),
 | |
| 			zap.Error(err),
 | |
| 		)
 | |
| 		// 事件发布失败不影响主流程,只记录日志
 | |
| 	} else {
 | |
| 		s.logger.Info("发票申请通过事件发布成功",
 | |
| 			zap.String("application_id", applicationID),
 | |
| 		)
 | |
| 	}
 | |
| 
 | |
| 	fileUploadedEvent := events.NewInvoiceFileUploadedEvent(
 | |
| 		application.ID,
 | |
| 		application.UserID,
 | |
| 		fileID,
 | |
| 		fileName,
 | |
| 		fileURL,
 | |
| 		application.ReceivingEmail,
 | |
| 		application.CompanyName,
 | |
| 		application.Amount,
 | |
| 		application.InvoiceType,
 | |
| 	)
 | |
| 
 | |
| 	if err := s.eventPublisher.PublishInvoiceFileUploaded(ctx, fileUploadedEvent); err != nil {
 | |
| 		s.logger.Error("发布发票文件上传事件失败",
 | |
| 			zap.String("application_id", applicationID),
 | |
| 			zap.Error(err),
 | |
| 		)
 | |
| 		// 事件发布失败不影响主流程,只记录日志
 | |
| 	} else {
 | |
| 		s.logger.Info("发票文件上传事件发布成功",
 | |
| 			zap.String("application_id", applicationID),
 | |
| 		)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // RejectInvoiceApplication 拒绝发票申请
 | |
| func (s *InvoiceAggregateServiceImpl) RejectInvoiceApplication(ctx context.Context, applicationID string, req RejectInvoiceRequest) error {
 | |
| 	// 1. 获取发票申请
 | |
| 	application, err := s.applicationRepo.FindByID(ctx, applicationID)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("获取发票申请失败: %w", err)
 | |
| 	}
 | |
| 	if application == nil {
 | |
| 		return fmt.Errorf("发票申请不存在")
 | |
| 	}
 | |
| 
 | |
| 	// 2. 验证状态转换
 | |
| 	if err := s.domainService.ValidateStatusTransition(application.Status, entities.ApplicationStatusRejected); err != nil {
 | |
| 		return fmt.Errorf("状态转换验证失败: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// 3. 更新聚合根状态
 | |
| 	application.MarkRejected(req.Reason, "admin_user_id")
 | |
| 
 | |
| 	// 4. 保存聚合根
 | |
| 	if err := s.applicationRepo.Update(ctx, application); err != nil {
 | |
| 		return fmt.Errorf("更新发票申请失败: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	// 5. 发布领域事件
 | |
| 	event := events.NewInvoiceApplicationRejectedEvent(
 | |
| 		application.ID,
 | |
| 		application.UserID,
 | |
| 		req.Reason,
 | |
| 		application.ReceivingEmail,
 | |
| 	)
 | |
| 
 | |
| 	if err := s.eventPublisher.PublishInvoiceApplicationRejected(ctx, event); err != nil {
 | |
| 		fmt.Printf("发布发票申请拒绝事件失败: %v\n", err)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 |