Files
tyapi-server/internal/application/finance/invoice_application_service.go
2025-08-02 02:54:21 +08:00

751 lines
27 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 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
}