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 | ||
|  | } |