package finance import ( "context" "fmt" "mime/multipart" "time" "tyapi-server/internal/application/finance/dto" "tyapi-server/internal/domains/finance/entities" finance_repo "tyapi-server/internal/domains/finance/repositories" "tyapi-server/internal/domains/finance/services" "tyapi-server/internal/domains/finance/value_objects" user_repo "tyapi-server/internal/domains/user/repositories" user_service "tyapi-server/internal/domains/user/services" "tyapi-server/internal/infrastructure/external/storage" "github.com/shopspring/decimal" "go.uber.org/zap" ) // ==================== 用户端发票应用服务 ==================== // InvoiceApplicationService 发票应用服务接口 // 职责:跨域协调、数据聚合、事务管理、外部服务调用 type InvoiceApplicationService interface { // ApplyInvoice 申请开票 ApplyInvoice(ctx context.Context, userID string, req ApplyInvoiceRequest) (*dto.InvoiceApplicationResponse, error) // GetUserInvoiceInfo 获取用户发票信息 GetUserInvoiceInfo(ctx context.Context, userID string) (*dto.InvoiceInfoResponse, error) // UpdateUserInvoiceInfo 更新用户发票信息 UpdateUserInvoiceInfo(ctx context.Context, userID string, req UpdateInvoiceInfoRequest) error // GetUserInvoiceRecords 获取用户开票记录 GetUserInvoiceRecords(ctx context.Context, userID string, req GetInvoiceRecordsRequest) (*dto.InvoiceRecordsResponse, error) // DownloadInvoiceFile 下载发票文件 DownloadInvoiceFile(ctx context.Context, userID string, applicationID string) (*dto.FileDownloadResponse, error) // GetAvailableAmount 获取可开票金额 GetAvailableAmount(ctx context.Context, userID string) (*dto.AvailableAmountResponse, error) } // InvoiceApplicationServiceImpl 发票应用服务实现 type InvoiceApplicationServiceImpl struct { // 仓储层依赖 invoiceRepo finance_repo.InvoiceApplicationRepository userInvoiceInfoRepo finance_repo.UserInvoiceInfoRepository userRepo user_repo.UserRepository rechargeRecordRepo finance_repo.RechargeRecordRepository walletRepo finance_repo.WalletRepository // 领域服务依赖 invoiceDomainService services.InvoiceDomainService invoiceAggregateService services.InvoiceAggregateService userInvoiceInfoService services.UserInvoiceInfoService userAggregateService user_service.UserAggregateService // 外部服务依赖 storageService *storage.QiNiuStorageService logger *zap.Logger } // NewInvoiceApplicationService 创建发票应用服务 func NewInvoiceApplicationService( invoiceRepo finance_repo.InvoiceApplicationRepository, userInvoiceInfoRepo finance_repo.UserInvoiceInfoRepository, userRepo user_repo.UserRepository, userAggregateService user_service.UserAggregateService, rechargeRecordRepo finance_repo.RechargeRecordRepository, walletRepo finance_repo.WalletRepository, invoiceDomainService services.InvoiceDomainService, invoiceAggregateService services.InvoiceAggregateService, userInvoiceInfoService services.UserInvoiceInfoService, storageService *storage.QiNiuStorageService, logger *zap.Logger, ) InvoiceApplicationService { return &InvoiceApplicationServiceImpl{ invoiceRepo: invoiceRepo, userInvoiceInfoRepo: userInvoiceInfoRepo, userRepo: userRepo, userAggregateService: userAggregateService, rechargeRecordRepo: rechargeRecordRepo, walletRepo: walletRepo, invoiceDomainService: invoiceDomainService, invoiceAggregateService: invoiceAggregateService, userInvoiceInfoService: userInvoiceInfoService, storageService: storageService, logger: logger, } } // ApplyInvoice 申请开票 func (s *InvoiceApplicationServiceImpl) ApplyInvoice(ctx context.Context, userID string, req ApplyInvoiceRequest) (*dto.InvoiceApplicationResponse, error) { // 1. 验证用户是否存在 user, err := s.userRepo.GetByID(ctx, userID) if err != nil { return nil, err } if user.ID == "" { return nil, fmt.Errorf("用户不存在") } // 2. 验证发票类型 invoiceType := value_objects.InvoiceType(req.InvoiceType) if !invoiceType.IsValid() { return nil, fmt.Errorf("无效的发票类型") } // 3. 获取用户企业认证信息 userWithEnterprise, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, userID) if err != nil { return nil, fmt.Errorf("获取用户企业认证信息失败: %w", err) } // 4. 检查用户是否有企业认证信息 if userWithEnterprise.EnterpriseInfo == nil { return nil, fmt.Errorf("用户未完成企业认证,无法申请开票") } // 5. 获取用户开票信息 userInvoiceInfo, err := s.userInvoiceInfoService.GetUserInvoiceInfoWithEnterpriseInfo( ctx, userID, userWithEnterprise.EnterpriseInfo.CompanyName, userWithEnterprise.EnterpriseInfo.UnifiedSocialCode, ) if err != nil { return nil, err } // 6. 验证开票信息完整性 invoiceInfo := value_objects.NewInvoiceInfo( userInvoiceInfo.CompanyName, userInvoiceInfo.TaxpayerID, userInvoiceInfo.BankName, userInvoiceInfo.BankAccount, userInvoiceInfo.CompanyAddress, userInvoiceInfo.CompanyPhone, userInvoiceInfo.ReceivingEmail, ) if err := s.userInvoiceInfoService.ValidateInvoiceInfo(ctx, invoiceInfo, invoiceType); err != nil { return nil, err } // 7. 计算可开票金额 availableAmount, err := s.calculateAvailableAmount(ctx, userID) if err != nil { return nil, fmt.Errorf("计算可开票金额失败: %w", err) } // 8. 验证开票金额 amount, err := decimal.NewFromString(req.Amount) if err != nil { return nil, fmt.Errorf("无效的金额格式: %w", err) } if err := s.invoiceDomainService.ValidateInvoiceAmount(ctx, amount, availableAmount); err != nil { return nil, err } // 9. 调用聚合服务申请开票 aggregateReq := services.ApplyInvoiceRequest{ InvoiceType: invoiceType, Amount: req.Amount, InvoiceInfo: invoiceInfo, } application, err := s.invoiceAggregateService.ApplyInvoice(ctx, userID, aggregateReq) if err != nil { return nil, err } // 10. 构建响应DTO return &dto.InvoiceApplicationResponse{ ID: application.ID, UserID: application.UserID, InvoiceType: application.InvoiceType, Amount: application.Amount, Status: application.Status, InvoiceInfo: invoiceInfo, CreatedAt: application.CreatedAt, }, nil } // GetUserInvoiceInfo 获取用户发票信息 func (s *InvoiceApplicationServiceImpl) GetUserInvoiceInfo(ctx context.Context, userID string) (*dto.InvoiceInfoResponse, error) { // 1. 获取用户企业认证信息 user, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, userID) if err != nil { return nil, fmt.Errorf("获取用户企业认证信息失败: %w", err) } // 2. 获取企业认证信息 var companyName, taxpayerID string var companyNameReadOnly, taxpayerIDReadOnly bool if user.EnterpriseInfo != nil { companyName = user.EnterpriseInfo.CompanyName taxpayerID = user.EnterpriseInfo.UnifiedSocialCode companyNameReadOnly = true taxpayerIDReadOnly = true } // 3. 获取用户开票信息(包含企业认证信息) userInvoiceInfo, err := s.userInvoiceInfoService.GetUserInvoiceInfoWithEnterpriseInfo(ctx, userID, companyName, taxpayerID) if err != nil { return nil, err } // 4. 构建响应DTO return &dto.InvoiceInfoResponse{ CompanyName: userInvoiceInfo.CompanyName, TaxpayerID: userInvoiceInfo.TaxpayerID, BankName: userInvoiceInfo.BankName, BankAccount: userInvoiceInfo.BankAccount, CompanyAddress: userInvoiceInfo.CompanyAddress, CompanyPhone: userInvoiceInfo.CompanyPhone, ReceivingEmail: userInvoiceInfo.ReceivingEmail, IsComplete: userInvoiceInfo.IsComplete(), MissingFields: userInvoiceInfo.GetMissingFields(), // 字段权限标识 CompanyNameReadOnly: companyNameReadOnly, // 公司名称只读(从企业认证信息获取) TaxpayerIDReadOnly: taxpayerIDReadOnly, // 纳税人识别号只读(从企业认证信息获取) }, nil } // UpdateUserInvoiceInfo 更新用户发票信息 func (s *InvoiceApplicationServiceImpl) UpdateUserInvoiceInfo(ctx context.Context, userID string, req UpdateInvoiceInfoRequest) error { // 1. 获取用户企业认证信息 user, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, userID) if err != nil { return fmt.Errorf("获取用户企业认证信息失败: %w", err) } // 2. 检查用户是否有企业认证信息 if user.EnterpriseInfo == nil { return fmt.Errorf("用户未完成企业认证,无法创建开票信息") } // 3. 创建开票信息对象,公司名称和纳税人识别号从企业认证信息中获取 invoiceInfo := value_objects.NewInvoiceInfo( "", // 公司名称将由服务层从企业认证信息中获取 "", // 纳税人识别号将由服务层从企业认证信息中获取 req.BankName, req.BankAccount, req.CompanyAddress, req.CompanyPhone, req.ReceivingEmail, ) // 4. 使用包含企业认证信息的方法 _, err = s.userInvoiceInfoService.CreateOrUpdateUserInvoiceInfoWithEnterpriseInfo( ctx, userID, invoiceInfo, user.EnterpriseInfo.CompanyName, user.EnterpriseInfo.UnifiedSocialCode, ) return err } // GetUserInvoiceRecords 获取用户开票记录 func (s *InvoiceApplicationServiceImpl) GetUserInvoiceRecords(ctx context.Context, userID string, req GetInvoiceRecordsRequest) (*dto.InvoiceRecordsResponse, error) { // 1. 验证用户是否存在 user, err := s.userRepo.GetByID(ctx, userID) if err != nil { return nil, err } if user.ID == "" { return nil, fmt.Errorf("用户不存在") } // 2. 获取发票申请记录 var status entities.ApplicationStatus if req.Status != "" { status = entities.ApplicationStatus(req.Status) } // 3. 解析时间范围 var startTime, endTime *time.Time if req.StartTime != "" { if t, err := time.Parse("2006-01-02 15:04:05", req.StartTime); err == nil { startTime = &t } } if req.EndTime != "" { if t, err := time.Parse("2006-01-02 15:04:05", req.EndTime); err == nil { endTime = &t } } // 4. 获取发票申请记录(需要更新仓储层方法以支持时间筛选) applications, total, err := s.invoiceRepo.FindByUserIDAndStatusWithTimeRange(ctx, userID, status, startTime, endTime, req.Page, req.PageSize) if err != nil { return nil, err } // 5. 构建响应DTO records := make([]*dto.InvoiceRecordResponse, len(applications)) for i, app := range applications { // 使用快照信息(申请时的开票信息) records[i] = &dto.InvoiceRecordResponse{ ID: app.ID, UserID: app.UserID, InvoiceType: app.InvoiceType, Amount: app.Amount, Status: app.Status, CompanyName: app.CompanyName, // 使用快照的公司名称 TaxpayerID: app.TaxpayerID, // 使用快照的纳税人识别号 BankName: app.BankName, // 使用快照的银行名称 BankAccount: app.BankAccount, // 使用快照的银行账号 CompanyAddress: app.CompanyAddress, // 使用快照的企业地址 CompanyPhone: app.CompanyPhone, // 使用快照的企业电话 ReceivingEmail: app.ReceivingEmail, // 使用快照的接收邮箱 FileName: app.FileName, FileSize: app.FileSize, FileURL: app.FileURL, ProcessedAt: app.ProcessedAt, CreatedAt: app.CreatedAt, RejectReason: app.RejectReason, } } return &dto.InvoiceRecordsResponse{ Records: records, Total: total, Page: req.Page, PageSize: req.PageSize, TotalPages: (int(total) + req.PageSize - 1) / req.PageSize, }, nil } // DownloadInvoiceFile 下载发票文件 func (s *InvoiceApplicationServiceImpl) DownloadInvoiceFile(ctx context.Context, userID string, applicationID string) (*dto.FileDownloadResponse, error) { // 1. 查找申请记录 application, err := s.invoiceRepo.FindByID(ctx, applicationID) if err != nil { return nil, err } if application == nil { return nil, fmt.Errorf("申请记录不存在") } // 2. 验证权限(只能下载自己的发票) if application.UserID != userID { return nil, fmt.Errorf("无权访问此发票") } // 3. 验证状态(只能下载已完成的发票) if application.Status != entities.ApplicationStatusCompleted { return nil, fmt.Errorf("发票尚未通过审核") } // 4. 验证文件信息 if application.FileURL == nil || *application.FileURL == "" { return nil, fmt.Errorf("发票文件不存在") } // 5. 从七牛云下载文件内容 fileContent, err := s.storageService.DownloadFile(ctx, *application.FileURL) if err != nil { return nil, fmt.Errorf("下载文件失败: %w", err) } // 6. 构建响应DTO return &dto.FileDownloadResponse{ FileID: *application.FileID, FileName: *application.FileName, FileSize: *application.FileSize, FileURL: *application.FileURL, FileContent: fileContent, }, nil } // GetAvailableAmount 获取可开票金额 func (s *InvoiceApplicationServiceImpl) GetAvailableAmount(ctx context.Context, userID string) (*dto.AvailableAmountResponse, error) { // 1. 验证用户是否存在 user, err := s.userRepo.GetByID(ctx, userID) if err != nil { return nil, err } if user.ID == "" { return nil, fmt.Errorf("用户不存在") } // 2. 计算可开票金额 availableAmount, err := s.calculateAvailableAmount(ctx, userID) if err != nil { return nil, err } // 3. 获取真实充值金额(支付宝充值+对公转账)和总赠送金额 realRecharged, totalGifted, totalInvoiced, err := s.getAmountSummary(ctx, userID) if err != nil { return nil, err } // 4. 获取待处理申请金额 pendingAmount, err := s.getPendingApplicationsAmount(ctx, userID) if err != nil { return nil, err } // 5. 构建响应DTO return &dto.AvailableAmountResponse{ AvailableAmount: availableAmount, TotalRecharged: realRecharged, // 使用真实充值金额(支付宝充值+对公转账) TotalGifted: totalGifted, TotalInvoiced: totalInvoiced, PendingApplications: pendingAmount, }, nil } // calculateAvailableAmount 计算可开票金额(私有方法) func (s *InvoiceApplicationServiceImpl) calculateAvailableAmount(ctx context.Context, userID string) (decimal.Decimal, error) { // 1. 获取真实充值金额(支付宝充值+对公转账)和总赠送金额 realRecharged, totalGifted, totalInvoiced, err := s.getAmountSummary(ctx, userID) if err != nil { return decimal.Zero, err } // 2. 获取待处理中的申请金额 pendingAmount, err := s.getPendingApplicationsAmount(ctx, userID) if err != nil { return decimal.Zero, err } fmt.Println("realRecharged", realRecharged) fmt.Println("totalGifted", totalGifted) fmt.Println("totalInvoiced", totalInvoiced) fmt.Println("pendingAmount", pendingAmount) // 3. 计算可开票金额:真实充值金额 - 已开票 - 待处理申请 // 可开票金额 = 真实充值金额(支付宝充值+对公转账) - 已开票金额 - 待处理申请金额 availableAmount := realRecharged.Sub(totalInvoiced).Sub(pendingAmount) fmt.Println("availableAmount", availableAmount) // 确保可开票金额不为负数 if availableAmount.LessThan(decimal.Zero) { availableAmount = decimal.Zero } return availableAmount, nil } // getAmountSummary 获取金额汇总(私有方法) func (s *InvoiceApplicationServiceImpl) getAmountSummary(ctx context.Context, userID string) (decimal.Decimal, decimal.Decimal, decimal.Decimal, error) { // 1. 获取用户所有成功的充值记录 rechargeRecords, err := s.rechargeRecordRepo.GetByUserID(ctx, userID) if err != nil { return decimal.Zero, decimal.Zero, decimal.Zero, fmt.Errorf("获取充值记录失败: %w", err) } // 2. 计算真实充值金额(支付宝充值 + 对公转账)和总赠送金额 var realRecharged decimal.Decimal // 真实充值金额:支付宝充值 + 对公转账 var totalGifted decimal.Decimal // 总赠送金额 for _, record := range rechargeRecords { if record.IsSuccess() { if record.RechargeType == entities.RechargeTypeGift { // 赠送金额不计入可开票金额 totalGifted = totalGifted.Add(record.Amount) } else if record.RechargeType == entities.RechargeTypeAlipay || record.RechargeType == entities.RechargeTypeTransfer { // 只有支付宝充值和对公转账计入可开票金额 realRecharged = realRecharged.Add(record.Amount) } } } // 3. 获取用户所有发票申请记录(包括待处理、已完成、已拒绝) applications, _, err := s.invoiceRepo.FindByUserID(ctx, userID, 1, 1000) // 获取所有记录 if err != nil { return decimal.Zero, decimal.Zero, decimal.Zero, fmt.Errorf("获取发票申请记录失败: %w", err) } var totalInvoiced decimal.Decimal for _, application := range applications { // 计算已完成的发票申请金额 if application.IsCompleted() { totalInvoiced = totalInvoiced.Add(application.Amount) } // 注意:待处理中的申请金额不计算在已开票金额中,但会在可开票金额计算时被扣除 } return realRecharged, totalGifted, totalInvoiced, nil } // getPendingApplicationsAmount 获取待处理申请的总金额(私有方法) func (s *InvoiceApplicationServiceImpl) getPendingApplicationsAmount(ctx context.Context, userID string) (decimal.Decimal, error) { // 获取用户所有发票申请记录 applications, _, err := s.invoiceRepo.FindByUserID(ctx, userID, 1, 1000) if err != nil { return decimal.Zero, fmt.Errorf("获取发票申请记录失败: %w", err) } var pendingAmount decimal.Decimal for _, application := range applications { // 只计算待处理状态的申请金额 if application.Status == entities.ApplicationStatusPending { pendingAmount = pendingAmount.Add(application.Amount) } } return pendingAmount, nil } // ==================== 管理员端发票应用服务 ==================== // AdminInvoiceApplicationService 管理员发票应用服务接口 type AdminInvoiceApplicationService interface { // GetPendingApplications 获取发票申请列表(支持筛选) GetPendingApplications(ctx context.Context, req GetPendingApplicationsRequest) (*dto.PendingApplicationsResponse, error) // ApproveInvoiceApplication 通过发票申请 ApproveInvoiceApplication(ctx context.Context, applicationID string, file multipart.File, req ApproveInvoiceRequest) error // RejectInvoiceApplication 拒绝发票申请 RejectInvoiceApplication(ctx context.Context, applicationID string, req RejectInvoiceRequest) error // DownloadInvoiceFile 下载发票文件(管理员) DownloadInvoiceFile(ctx context.Context, applicationID string) (*dto.FileDownloadResponse, error) } // AdminInvoiceApplicationServiceImpl 管理员发票应用服务实现 type AdminInvoiceApplicationServiceImpl struct { invoiceRepo finance_repo.InvoiceApplicationRepository userInvoiceInfoRepo finance_repo.UserInvoiceInfoRepository userRepo user_repo.UserRepository invoiceAggregateService services.InvoiceAggregateService storageService *storage.QiNiuStorageService logger *zap.Logger } // NewAdminInvoiceApplicationService 创建管理员发票应用服务 func NewAdminInvoiceApplicationService( invoiceRepo finance_repo.InvoiceApplicationRepository, userInvoiceInfoRepo finance_repo.UserInvoiceInfoRepository, userRepo user_repo.UserRepository, invoiceAggregateService services.InvoiceAggregateService, storageService *storage.QiNiuStorageService, logger *zap.Logger, ) AdminInvoiceApplicationService { return &AdminInvoiceApplicationServiceImpl{ invoiceRepo: invoiceRepo, userInvoiceInfoRepo: userInvoiceInfoRepo, userRepo: userRepo, invoiceAggregateService: invoiceAggregateService, storageService: storageService, logger: logger, } } // GetPendingApplications 获取发票申请列表(支持筛选) func (s *AdminInvoiceApplicationServiceImpl) GetPendingApplications(ctx context.Context, req GetPendingApplicationsRequest) (*dto.PendingApplicationsResponse, error) { // 1. 解析状态筛选 var status entities.ApplicationStatus if req.Status != "" { status = entities.ApplicationStatus(req.Status) } // 2. 解析时间范围 var startTime, endTime *time.Time if req.StartTime != "" { if t, err := time.Parse("2006-01-02 15:04:05", req.StartTime); err == nil { startTime = &t } } if req.EndTime != "" { if t, err := time.Parse("2006-01-02 15:04:05", req.EndTime); err == nil { endTime = &t } } // 3. 获取发票申请记录(支持筛选) var applications []*entities.InvoiceApplication var total int64 var err error if status != "" { // 按状态筛选 applications, total, err = s.invoiceRepo.FindByStatusWithTimeRange(ctx, status, startTime, endTime, req.Page, req.PageSize) } else { // 获取所有记录(按时间筛选) applications, total, err = s.invoiceRepo.FindAllWithTimeRange(ctx, startTime, endTime, req.Page, req.PageSize) } if err != nil { return nil, err } // 4. 构建响应DTO pendingApplications := make([]*dto.PendingApplicationResponse, len(applications)) for i, app := range applications { // 使用快照信息 pendingApplications[i] = &dto.PendingApplicationResponse{ ID: app.ID, UserID: app.UserID, InvoiceType: app.InvoiceType, Amount: app.Amount, Status: app.Status, CompanyName: app.CompanyName, // 使用快照的公司名称 TaxpayerID: app.TaxpayerID, // 使用快照的纳税人识别号 BankName: app.BankName, // 使用快照的银行名称 BankAccount: app.BankAccount, // 使用快照的银行账号 CompanyAddress: app.CompanyAddress, // 使用快照的企业地址 CompanyPhone: app.CompanyPhone, // 使用快照的企业电话 ReceivingEmail: app.ReceivingEmail, // 使用快照的接收邮箱 FileName: app.FileName, FileSize: app.FileSize, FileURL: app.FileURL, ProcessedAt: app.ProcessedAt, CreatedAt: app.CreatedAt, RejectReason: app.RejectReason, } } return &dto.PendingApplicationsResponse{ Applications: pendingApplications, Total: total, Page: req.Page, PageSize: req.PageSize, TotalPages: (int(total) + req.PageSize - 1) / req.PageSize, }, nil } // ApproveInvoiceApplication 通过发票申请 func (s *AdminInvoiceApplicationServiceImpl) ApproveInvoiceApplication(ctx context.Context, applicationID string, file multipart.File, req ApproveInvoiceRequest) error { // 1. 验证申请是否存在 application, err := s.invoiceRepo.FindByID(ctx, applicationID) if err != nil { return err } if application == nil { return fmt.Errorf("发票申请不存在") } // 2. 验证申请状态 if application.Status != entities.ApplicationStatusPending { return fmt.Errorf("发票申请状态不允许处理") } // 3. 调用聚合服务处理申请 aggregateReq := services.ApproveInvoiceRequest{ AdminNotes: req.AdminNotes, } return s.invoiceAggregateService.ApproveInvoiceApplication(ctx, applicationID, file, aggregateReq) } // RejectInvoiceApplication 拒绝发票申请 func (s *AdminInvoiceApplicationServiceImpl) RejectInvoiceApplication(ctx context.Context, applicationID string, req RejectInvoiceRequest) error { // 1. 验证申请是否存在 application, err := s.invoiceRepo.FindByID(ctx, applicationID) if err != nil { return err } if application == nil { return fmt.Errorf("发票申请不存在") } // 2. 验证申请状态 if application.Status != entities.ApplicationStatusPending { return fmt.Errorf("发票申请状态不允许处理") } // 3. 调用聚合服务处理申请 aggregateReq := services.RejectInvoiceRequest{ Reason: req.Reason, } return s.invoiceAggregateService.RejectInvoiceApplication(ctx, applicationID, aggregateReq) } // ==================== 请求和响应DTO ==================== type ApplyInvoiceRequest struct { InvoiceType string `json:"invoice_type" binding:"required"` // 发票类型:general/special Amount string `json:"amount" binding:"required"` // 开票金额 } type UpdateInvoiceInfoRequest struct { CompanyName string `json:"company_name"` // 公司名称(从企业认证信息获取,用户不可修改) TaxpayerID string `json:"taxpayer_id"` // 纳税人识别号(从企业认证信息获取,用户不可修改) BankName string `json:"bank_name"` // 银行名称 CompanyAddress string `json:"company_address"` // 公司地址 BankAccount string `json:"bank_account"` // 银行账户 CompanyPhone string `json:"company_phone"` // 企业注册电话 ReceivingEmail string `json:"receiving_email" binding:"required,email"` // 发票接收邮箱 } type GetInvoiceRecordsRequest struct { Page int `json:"page"` // 页码 PageSize int `json:"page_size"` // 每页数量 Status string `json:"status"` // 状态筛选 StartTime string `json:"start_time"` // 开始时间 (格式: 2006-01-02 15:04:05) EndTime string `json:"end_time"` // 结束时间 (格式: 2006-01-02 15:04:05) } type GetPendingApplicationsRequest struct { Page int `json:"page"` // 页码 PageSize int `json:"page_size"` // 每页数量 Status string `json:"status"` // 状态筛选:pending/completed/rejected StartTime string `json:"start_time"` // 开始时间 (格式: 2006-01-02 15:04:05) EndTime string `json:"end_time"` // 结束时间 (格式: 2006-01-02 15:04:05) } type ApproveInvoiceRequest struct { AdminNotes string `json:"admin_notes"` // 管理员备注 } type RejectInvoiceRequest struct { Reason string `json:"reason" binding:"required"` // 拒绝原因 } // DownloadInvoiceFile 下载发票文件(管理员) func (s *AdminInvoiceApplicationServiceImpl) DownloadInvoiceFile(ctx context.Context, applicationID string) (*dto.FileDownloadResponse, error) { // 1. 查找申请记录 application, err := s.invoiceRepo.FindByID(ctx, applicationID) if err != nil { return nil, err } if application == nil { return nil, fmt.Errorf("申请记录不存在") } // 2. 验证状态(只能下载已完成的发票) if application.Status != entities.ApplicationStatusCompleted { return nil, fmt.Errorf("发票尚未通过审核") } // 3. 验证文件信息 if application.FileURL == nil || *application.FileURL == "" { return nil, fmt.Errorf("发票文件不存在") } // 4. 从七牛云下载文件内容 fileContent, err := s.storageService.DownloadFile(ctx, *application.FileURL) if err != nil { return nil, fmt.Errorf("下载文件失败: %w", err) } // 5. 构建响应DTO return &dto.FileDownloadResponse{ FileID: *application.FileID, FileName: *application.FileName, FileSize: *application.FileSize, FileURL: *application.FileURL, FileContent: fileContent, }, nil }