v1.0.0
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
finance_services "tyapi-server/internal/domains/finance/services"
|
||||
product_services "tyapi-server/internal/domains/product/services"
|
||||
user_repositories "tyapi-server/internal/domains/user/repositories"
|
||||
"tyapi-server/internal/shared/crypto"
|
||||
"tyapi-server/internal/shared/database"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
@@ -33,6 +34,9 @@ type ApiApplicationService interface {
|
||||
|
||||
// 获取用户API调用记录
|
||||
GetUserApiCalls(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*dto.ApiCallListResponse, error)
|
||||
|
||||
// 管理端API调用记录
|
||||
GetAdminApiCalls(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*dto.ApiCallListResponse, error)
|
||||
}
|
||||
|
||||
type ApiApplicationServiceImpl struct {
|
||||
@@ -43,13 +47,14 @@ type ApiApplicationServiceImpl struct {
|
||||
walletService finance_services.WalletAggregateService
|
||||
productManagementService *product_services.ProductManagementService
|
||||
productSubscriptionService *product_services.ProductSubscriptionService
|
||||
userRepo user_repositories.UserRepository
|
||||
txManager *database.TransactionManager
|
||||
config *config.Config
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewApiApplicationService(apiCallService services.ApiCallAggregateService, apiUserService services.ApiUserAggregateService, apiRequestService *services.ApiRequestService, apiCallRepository repositories.ApiCallRepository, walletService finance_services.WalletAggregateService, productManagementService *product_services.ProductManagementService, productSubscriptionService *product_services.ProductSubscriptionService, txManager *database.TransactionManager, config *config.Config, logger *zap.Logger) ApiApplicationService {
|
||||
return &ApiApplicationServiceImpl{apiCallService: apiCallService, apiUserService: apiUserService, apiRequestService: apiRequestService, apiCallRepository: apiCallRepository, walletService: walletService, productManagementService: productManagementService, productSubscriptionService: productSubscriptionService, txManager: txManager, config: config, logger: logger}
|
||||
func NewApiApplicationService(apiCallService services.ApiCallAggregateService, apiUserService services.ApiUserAggregateService, apiRequestService *services.ApiRequestService, apiCallRepository repositories.ApiCallRepository, walletService finance_services.WalletAggregateService, productManagementService *product_services.ProductManagementService, productSubscriptionService *product_services.ProductSubscriptionService, userRepo user_repositories.UserRepository, txManager *database.TransactionManager, config *config.Config, logger *zap.Logger) ApiApplicationService {
|
||||
return &ApiApplicationServiceImpl{apiCallService: apiCallService, apiUserService: apiUserService, apiRequestService: apiRequestService, apiCallRepository: apiCallRepository, walletService: walletService, productManagementService: productManagementService, productSubscriptionService: productSubscriptionService, userRepo: userRepo, txManager: txManager, config: config, logger: logger}
|
||||
}
|
||||
|
||||
// CallApi 应用服务层统一入口
|
||||
@@ -405,3 +410,80 @@ func (s *ApiApplicationServiceImpl) GetUserApiCalls(ctx context.Context, userID
|
||||
Size: options.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAdminApiCalls 获取管理端API调用记录
|
||||
func (s *ApiApplicationServiceImpl) GetAdminApiCalls(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*dto.ApiCallListResponse, error) {
|
||||
// 查询API调用记录(包含产品名称)
|
||||
productNameMap, calls, total, err := s.apiCallRepository.ListWithFiltersAndProductName(ctx, filters, options)
|
||||
if err != nil {
|
||||
s.logger.Error("查询API调用记录失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为响应DTO
|
||||
var items []dto.ApiCallRecordResponse
|
||||
for _, call := range calls {
|
||||
item := dto.ApiCallRecordResponse{
|
||||
ID: call.ID,
|
||||
AccessId: call.AccessId,
|
||||
UserId: *call.UserId,
|
||||
TransactionId: call.TransactionId,
|
||||
ClientIp: call.ClientIp,
|
||||
Status: call.Status,
|
||||
StartAt: call.StartAt.Format("2006-01-02 15:04:05"),
|
||||
CreatedAt: call.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
UpdatedAt: call.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
|
||||
// 处理可选字段
|
||||
if call.ProductId != nil {
|
||||
item.ProductId = call.ProductId
|
||||
}
|
||||
// 从映射中获取产品名称
|
||||
if productName, exists := productNameMap[call.ID]; exists {
|
||||
item.ProductName = &productName
|
||||
}
|
||||
if call.EndAt != nil {
|
||||
endAt := call.EndAt.Format("2006-01-02 15:04:05")
|
||||
item.EndAt = &endAt
|
||||
}
|
||||
if call.Cost != nil {
|
||||
cost := call.Cost.String()
|
||||
item.Cost = &cost
|
||||
}
|
||||
if call.ErrorType != nil {
|
||||
item.ErrorType = call.ErrorType
|
||||
}
|
||||
if call.ErrorMsg != nil {
|
||||
item.ErrorMsg = call.ErrorMsg
|
||||
// 添加翻译后的错误信息
|
||||
item.TranslatedErrorMsg = utils.TranslateErrorMsg(call.ErrorType, call.ErrorMsg)
|
||||
}
|
||||
|
||||
// 获取用户信息和企业名称
|
||||
if call.UserId != nil {
|
||||
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, *call.UserId)
|
||||
if err == nil {
|
||||
companyName := "未知企业"
|
||||
if user.EnterpriseInfo != nil {
|
||||
companyName = user.EnterpriseInfo.CompanyName
|
||||
}
|
||||
item.CompanyName = &companyName
|
||||
item.User = &dto.UserSimpleResponse{
|
||||
ID: user.ID,
|
||||
CompanyName: companyName,
|
||||
Phone: user.Phone,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return &dto.ApiCallListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: options.Page,
|
||||
Size: options.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -54,10 +54,19 @@ type ApiCallRecordResponse struct {
|
||||
ErrorType *string `json:"error_type,omitempty"`
|
||||
ErrorMsg *string `json:"error_msg,omitempty"`
|
||||
TranslatedErrorMsg *string `json:"translated_error_msg,omitempty"`
|
||||
CompanyName *string `json:"company_name,omitempty"`
|
||||
User *UserSimpleResponse `json:"user,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// UserSimpleResponse 用户简单信息响应
|
||||
type UserSimpleResponse struct {
|
||||
ID string `json:"id"`
|
||||
CompanyName string `json:"company_name"`
|
||||
Phone string `json:"phone"`
|
||||
}
|
||||
|
||||
type ApiCallListResponse struct {
|
||||
Items []ApiCallRecordResponse `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
|
||||
121
internal/application/finance/dto/invoice_responses.go
Normal file
121
internal/application/finance/dto/invoice_responses.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/finance/entities"
|
||||
"tyapi-server/internal/domains/finance/value_objects"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// InvoiceApplicationResponse 发票申请响应
|
||||
type InvoiceApplicationResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
InvoiceType value_objects.InvoiceType `json:"invoice_type"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
Status entities.ApplicationStatus `json:"status"`
|
||||
InvoiceInfo *value_objects.InvoiceInfo `json:"invoice_info"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// InvoiceInfoResponse 发票信息响应
|
||||
type InvoiceInfoResponse struct {
|
||||
CompanyName string `json:"company_name"` // 从企业认证信息获取,只读
|
||||
TaxpayerID string `json:"taxpayer_id"` // 从企业认证信息获取,只读
|
||||
BankName string `json:"bank_name"` // 用户可编辑
|
||||
BankAccount string `json:"bank_account"` // 用户可编辑
|
||||
CompanyAddress string `json:"company_address"` // 用户可编辑
|
||||
CompanyPhone string `json:"company_phone"` // 用户可编辑
|
||||
ReceivingEmail string `json:"receiving_email"` // 用户可编辑
|
||||
IsComplete bool `json:"is_complete"`
|
||||
MissingFields []string `json:"missing_fields,omitempty"`
|
||||
// 字段权限标识
|
||||
CompanyNameReadOnly bool `json:"company_name_read_only"` // 公司名称是否只读
|
||||
TaxpayerIDReadOnly bool `json:"taxpayer_id_read_only"` // 纳税人识别号是否只读
|
||||
}
|
||||
|
||||
// InvoiceRecordResponse 发票记录响应
|
||||
type InvoiceRecordResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
InvoiceType value_objects.InvoiceType `json:"invoice_type"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
Status entities.ApplicationStatus `json:"status"`
|
||||
// 开票信息(快照数据)
|
||||
CompanyName string `json:"company_name"` // 公司名称
|
||||
TaxpayerID string `json:"taxpayer_id"` // 纳税人识别号
|
||||
BankName string `json:"bank_name"` // 开户银行
|
||||
BankAccount string `json:"bank_account"` // 银行账号
|
||||
CompanyAddress string `json:"company_address"` // 企业地址
|
||||
CompanyPhone string `json:"company_phone"` // 企业电话
|
||||
ReceivingEmail string `json:"receiving_email"` // 接收邮箱
|
||||
// 文件信息
|
||||
FileName *string `json:"file_name,omitempty"`
|
||||
FileSize *int64 `json:"file_size,omitempty"`
|
||||
FileURL *string `json:"file_url,omitempty"`
|
||||
// 时间信息
|
||||
ProcessedAt *time.Time `json:"processed_at,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
// 拒绝原因
|
||||
RejectReason *string `json:"reject_reason,omitempty"`
|
||||
}
|
||||
|
||||
// InvoiceRecordsResponse 发票记录列表响应
|
||||
type InvoiceRecordsResponse struct {
|
||||
Records []*InvoiceRecordResponse `json:"records"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
// FileDownloadResponse 文件下载响应
|
||||
type FileDownloadResponse struct {
|
||||
FileID string `json:"file_id"`
|
||||
FileName string `json:"file_name"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
FileURL string `json:"file_url"`
|
||||
FileContent []byte `json:"file_content"`
|
||||
}
|
||||
|
||||
// AvailableAmountResponse 可开票金额响应
|
||||
type AvailableAmountResponse struct {
|
||||
AvailableAmount decimal.Decimal `json:"available_amount"` // 可开票金额
|
||||
TotalRecharged decimal.Decimal `json:"total_recharged"` // 总充值金额
|
||||
TotalGifted decimal.Decimal `json:"total_gifted"` // 总赠送金额
|
||||
TotalInvoiced decimal.Decimal `json:"total_invoiced"` // 已开票金额
|
||||
PendingApplications decimal.Decimal `json:"pending_applications"` // 待处理申请金额
|
||||
}
|
||||
|
||||
// PendingApplicationResponse 待处理申请响应
|
||||
type PendingApplicationResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
InvoiceType value_objects.InvoiceType `json:"invoice_type"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
Status entities.ApplicationStatus `json:"status"`
|
||||
CompanyName string `json:"company_name"`
|
||||
TaxpayerID string `json:"taxpayer_id"`
|
||||
BankName string `json:"bank_name"`
|
||||
BankAccount string `json:"bank_account"`
|
||||
CompanyAddress string `json:"company_address"`
|
||||
CompanyPhone string `json:"company_phone"`
|
||||
ReceivingEmail string `json:"receiving_email"`
|
||||
FileName *string `json:"file_name,omitempty"`
|
||||
FileSize *int64 `json:"file_size,omitempty"`
|
||||
FileURL *string `json:"file_url,omitempty"`
|
||||
ProcessedAt *time.Time `json:"processed_at,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
RejectReason *string `json:"reject_reason,omitempty"`
|
||||
}
|
||||
|
||||
// PendingApplicationsResponse 待处理申请列表响应
|
||||
type PendingApplicationsResponse struct {
|
||||
Applications []*PendingApplicationResponse `json:"applications"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
@@ -58,6 +58,8 @@ type RechargeRecordResponse struct {
|
||||
TransferOrderID string `json:"transfer_order_id,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
OperatorID string `json:"operator_id,omitempty"`
|
||||
CompanyName string `json:"company_name,omitempty"`
|
||||
User *UserSimpleResponse `json:"user,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -71,6 +73,8 @@ type WalletTransactionResponse struct {
|
||||
ProductID string `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
Amount decimal.Decimal `json:"amount"`
|
||||
CompanyName string `json:"company_name,omitempty"`
|
||||
User *UserSimpleResponse `json:"user,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -112,3 +116,10 @@ type AlipayRechargeBonusRuleResponse struct {
|
||||
RechargeAmount float64 `json:"recharge_amount"`
|
||||
BonusAmount float64 `json:"bonus_amount"`
|
||||
}
|
||||
|
||||
// UserSimpleResponse 用户简单信息响应
|
||||
type UserSimpleResponse struct {
|
||||
ID string `json:"id"`
|
||||
CompanyName string `json:"company_name"`
|
||||
Phone string `json:"phone"`
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ type FinanceApplicationService interface {
|
||||
// 获取用户钱包交易记录
|
||||
GetUserWalletTransactions(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.WalletTransactionListResponse, error)
|
||||
|
||||
// 管理端消费记录
|
||||
GetAdminWalletTransactions(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.WalletTransactionListResponse, error)
|
||||
|
||||
// 获取用户充值记录
|
||||
GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
finance_entities "tyapi-server/internal/domains/finance/entities"
|
||||
finance_repositories "tyapi-server/internal/domains/finance/repositories"
|
||||
finance_services "tyapi-server/internal/domains/finance/services"
|
||||
user_repositories "tyapi-server/internal/domains/user/repositories"
|
||||
"tyapi-server/internal/shared/database"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
"tyapi-server/internal/shared/payment"
|
||||
@@ -27,6 +28,7 @@ type FinanceApplicationServiceImpl struct {
|
||||
rechargeRecordService finance_services.RechargeRecordService
|
||||
walletTransactionRepository finance_repositories.WalletTransactionRepository
|
||||
alipayOrderRepo finance_repositories.AlipayOrderRepository
|
||||
userRepo user_repositories.UserRepository
|
||||
txManager *database.TransactionManager
|
||||
logger *zap.Logger
|
||||
config *config.Config
|
||||
@@ -39,6 +41,7 @@ func NewFinanceApplicationService(
|
||||
rechargeRecordService finance_services.RechargeRecordService,
|
||||
walletTransactionRepository finance_repositories.WalletTransactionRepository,
|
||||
alipayOrderRepo finance_repositories.AlipayOrderRepository,
|
||||
userRepo user_repositories.UserRepository,
|
||||
txManager *database.TransactionManager,
|
||||
logger *zap.Logger,
|
||||
config *config.Config,
|
||||
@@ -49,6 +52,7 @@ func NewFinanceApplicationService(
|
||||
rechargeRecordService: rechargeRecordService,
|
||||
walletTransactionRepository: walletTransactionRepository,
|
||||
alipayOrderRepo: alipayOrderRepo,
|
||||
userRepo: userRepo,
|
||||
txManager: txManager,
|
||||
logger: logger,
|
||||
config: config,
|
||||
@@ -290,6 +294,55 @@ func (s *FinanceApplicationServiceImpl) GetUserWalletTransactions(ctx context.Co
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAdminWalletTransactions 获取管理端钱包交易记录
|
||||
func (s *FinanceApplicationServiceImpl) GetAdminWalletTransactions(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.WalletTransactionListResponse, error) {
|
||||
// 查询钱包交易记录(包含产品名称)
|
||||
productNameMap, transactions, total, err := s.walletTransactionRepository.ListWithFiltersAndProductName(ctx, filters, options)
|
||||
if err != nil {
|
||||
s.logger.Error("查询管理端钱包交易记录失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为响应DTO
|
||||
var items []responses.WalletTransactionResponse
|
||||
for _, transaction := range transactions {
|
||||
item := responses.WalletTransactionResponse{
|
||||
ID: transaction.ID,
|
||||
UserID: transaction.UserID,
|
||||
ApiCallID: transaction.ApiCallID,
|
||||
TransactionID: transaction.TransactionID,
|
||||
ProductID: transaction.ProductID,
|
||||
ProductName: productNameMap[transaction.ProductID], // 从映射中获取产品名称
|
||||
Amount: transaction.Amount,
|
||||
CreatedAt: transaction.CreatedAt,
|
||||
UpdatedAt: transaction.UpdatedAt,
|
||||
}
|
||||
|
||||
// 获取用户信息和企业名称
|
||||
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, transaction.UserID)
|
||||
if err == nil {
|
||||
companyName := "未知企业"
|
||||
if user.EnterpriseInfo != nil {
|
||||
companyName = user.EnterpriseInfo.CompanyName
|
||||
}
|
||||
item.CompanyName = companyName
|
||||
item.User = &responses.UserSimpleResponse{
|
||||
ID: user.ID,
|
||||
CompanyName: companyName,
|
||||
Phone: user.Phone,
|
||||
}
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return &responses.WalletTransactionListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: options.Page,
|
||||
Size: options.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
// HandleAlipayCallback 处理支付宝回调
|
||||
@@ -592,19 +645,19 @@ func (s *FinanceApplicationServiceImpl) GetUserRechargeRecords(ctx context.Conte
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAdminRechargeRecords 管理员获取充值记录
|
||||
// GetAdminRechargeRecords 获取管理端充值记录
|
||||
func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error) {
|
||||
// 查询所有充值记录(管理员可以查看所有用户的充值记录)
|
||||
// 查询充值记录
|
||||
records, err := s.rechargeRecordService.GetAll(ctx, filters, options)
|
||||
if err != nil {
|
||||
s.logger.Error("查询管理员充值记录失败", zap.Error(err))
|
||||
s.logger.Error("查询管理端充值记录失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
total, err := s.rechargeRecordService.Count(ctx, filters)
|
||||
if err != nil {
|
||||
s.logger.Error("统计管理员充值记录失败", zap.Error(err))
|
||||
s.logger.Error("统计管理端充值记录失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -612,14 +665,14 @@ func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Cont
|
||||
var items []responses.RechargeRecordResponse
|
||||
for _, record := range records {
|
||||
item := responses.RechargeRecordResponse{
|
||||
ID: record.ID,
|
||||
UserID: record.UserID,
|
||||
Amount: record.Amount,
|
||||
RechargeType: string(record.RechargeType),
|
||||
Status: string(record.Status),
|
||||
Notes: record.Notes,
|
||||
CreatedAt: record.CreatedAt,
|
||||
UpdatedAt: record.UpdatedAt,
|
||||
ID: record.ID,
|
||||
UserID: record.UserID,
|
||||
Amount: record.Amount,
|
||||
RechargeType: string(record.RechargeType),
|
||||
Status: string(record.Status),
|
||||
Notes: record.Notes,
|
||||
CreatedAt: record.CreatedAt,
|
||||
UpdatedAt: record.UpdatedAt,
|
||||
}
|
||||
|
||||
// 根据充值类型设置相应的订单号
|
||||
@@ -630,6 +683,21 @@ func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Cont
|
||||
item.TransferOrderID = *record.TransferOrderID
|
||||
}
|
||||
|
||||
// 获取用户信息和企业名称
|
||||
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, record.UserID)
|
||||
if err == nil {
|
||||
companyName := "未知企业"
|
||||
if user.EnterpriseInfo != nil {
|
||||
companyName = user.EnterpriseInfo.CompanyName
|
||||
}
|
||||
item.CompanyName = companyName
|
||||
item.User = &responses.UserSimpleResponse{
|
||||
ID: user.ID,
|
||||
CompanyName: companyName,
|
||||
Phone: user.Phone,
|
||||
}
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
|
||||
750
internal/application/finance/invoice_application_service.go
Normal file
750
internal/application/finance/invoice_application_service.go
Normal file
@@ -0,0 +1,750 @@
|
||||
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
|
||||
}
|
||||
@@ -4,10 +4,16 @@ package queries
|
||||
type ListSubscriptionsQuery struct {
|
||||
Page int `form:"page" binding:"omitempty,min=1" comment:"页码"`
|
||||
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100" comment:"每页数量"`
|
||||
UserID string `form:"-" comment:"用户ID"`
|
||||
UserID string `form:"user_id" binding:"omitempty" comment:"用户ID"`
|
||||
Keyword string `form:"keyword" binding:"omitempty,max=100" comment:"搜索关键词"`
|
||||
SortBy string `form:"sort_by" binding:"omitempty,oneof=created_at updated_at price" comment:"排序字段"`
|
||||
SortOrder string `form:"sort_order" binding:"omitempty,sort_order" comment:"排序方向"`
|
||||
|
||||
// 新增筛选字段
|
||||
CompanyName string `form:"company_name" binding:"omitempty,max=100" comment:"企业名称"`
|
||||
ProductName string `form:"product_name" binding:"omitempty,max=100" comment:"产品名称"`
|
||||
StartTime string `form:"start_time" binding:"omitempty,datetime=2006-01-02 15:04:05" comment:"订阅开始时间"`
|
||||
EndTime string `form:"end_time" binding:"omitempty,datetime=2006-01-02 15:04:05" comment:"订阅结束时间"`
|
||||
}
|
||||
|
||||
// GetSubscriptionQuery 获取订阅详情查询
|
||||
|
||||
@@ -4,6 +4,13 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// UserSimpleResponse 用户简单信息响应
|
||||
type UserSimpleResponse struct {
|
||||
ID string `json:"id" comment:"用户ID"`
|
||||
CompanyName string `json:"company_name" comment:"公司名称"`
|
||||
Phone string `json:"phone" comment:"手机号"`
|
||||
}
|
||||
|
||||
// SubscriptionInfoResponse 订阅详情响应
|
||||
type SubscriptionInfoResponse struct {
|
||||
ID string `json:"id" comment:"订阅ID"`
|
||||
@@ -13,6 +20,7 @@ type SubscriptionInfoResponse struct {
|
||||
APIUsed int64 `json:"api_used" comment:"已使用API调用次数"`
|
||||
|
||||
// 关联信息
|
||||
User *UserSimpleResponse `json:"user,omitempty" comment:"用户信息"`
|
||||
Product *ProductSimpleResponse `json:"product,omitempty" comment:"产品信息"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||
|
||||
@@ -2,9 +2,6 @@ package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
@@ -14,22 +11,26 @@ import (
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
repoQueries "tyapi-server/internal/domains/product/repositories/queries"
|
||||
product_service "tyapi-server/internal/domains/product/services"
|
||||
user_repositories "tyapi-server/internal/domains/user/repositories"
|
||||
)
|
||||
|
||||
// SubscriptionApplicationServiceImpl 订阅应用服务实现
|
||||
// 负责业务流程编排、事务管理、数据转换,不直接操作仓库
|
||||
type SubscriptionApplicationServiceImpl struct {
|
||||
productSubscriptionService *product_service.ProductSubscriptionService
|
||||
userRepo user_repositories.UserRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewSubscriptionApplicationService 创建订阅应用服务
|
||||
func NewSubscriptionApplicationService(
|
||||
productSubscriptionService *product_service.ProductSubscriptionService,
|
||||
userRepo user_repositories.UserRepository,
|
||||
logger *zap.Logger,
|
||||
) SubscriptionApplicationService {
|
||||
return &SubscriptionApplicationServiceImpl{
|
||||
productSubscriptionService: productSubscriptionService,
|
||||
userRepo: userRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
@@ -37,19 +38,7 @@ func NewSubscriptionApplicationService(
|
||||
// UpdateSubscriptionPrice 更新订阅价格
|
||||
// 业务流程:1. 获取订阅 2. 更新价格 3. 保存订阅
|
||||
func (s *SubscriptionApplicationServiceImpl) UpdateSubscriptionPrice(ctx context.Context, cmd *commands.UpdateSubscriptionPriceCommand) error {
|
||||
// 1. 获取现有订阅
|
||||
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 更新订阅价格
|
||||
subscription.Price = decimal.NewFromFloat(cmd.Price)
|
||||
|
||||
// 3. 保存订阅
|
||||
// 这里需要扩展领域服务来支持更新操作
|
||||
// 暂时返回错误
|
||||
return fmt.Errorf("更新订阅价格功能暂未实现")
|
||||
return s.productSubscriptionService.UpdateSubscriptionPrice(ctx, cmd.ID, cmd.Price)
|
||||
}
|
||||
|
||||
// CreateSubscription 创建订阅
|
||||
@@ -74,12 +63,16 @@ func (s *SubscriptionApplicationServiceImpl) GetSubscriptionByID(ctx context.Con
|
||||
// 业务流程:1. 获取订阅列表 2. 构建响应数据
|
||||
func (s *SubscriptionApplicationServiceImpl) ListSubscriptions(ctx context.Context, query *appQueries.ListSubscriptionsQuery) (*responses.SubscriptionListResponse, error) {
|
||||
repoQuery := &repoQueries.ListSubscriptionsQuery{
|
||||
Page: query.Page,
|
||||
PageSize: query.PageSize,
|
||||
UserID: query.UserID, // 管理员可以按用户筛选
|
||||
Keyword: query.Keyword,
|
||||
SortBy: query.SortBy,
|
||||
SortOrder: query.SortOrder,
|
||||
Page: query.Page,
|
||||
PageSize: query.PageSize,
|
||||
UserID: query.UserID, // 管理员可以按用户筛选
|
||||
Keyword: query.Keyword,
|
||||
SortBy: query.SortBy,
|
||||
SortOrder: query.SortOrder,
|
||||
CompanyName: query.CompanyName,
|
||||
ProductName: query.ProductName,
|
||||
StartTime: query.StartTime,
|
||||
EndTime: query.EndTime,
|
||||
}
|
||||
subscriptions, total, err := s.productSubscriptionService.ListSubscriptions(ctx, repoQuery)
|
||||
if err != nil {
|
||||
@@ -104,12 +97,16 @@ func (s *SubscriptionApplicationServiceImpl) ListSubscriptions(ctx context.Conte
|
||||
// 业务流程:1. 获取用户订阅列表 2. 构建响应数据
|
||||
func (s *SubscriptionApplicationServiceImpl) ListMySubscriptions(ctx context.Context, userID string, query *appQueries.ListSubscriptionsQuery) (*responses.SubscriptionListResponse, error) {
|
||||
repoQuery := &repoQueries.ListSubscriptionsQuery{
|
||||
Page: query.Page,
|
||||
PageSize: query.PageSize,
|
||||
UserID: userID, // 强制设置为当前用户ID
|
||||
Keyword: query.Keyword,
|
||||
SortBy: query.SortBy,
|
||||
SortOrder: query.SortOrder,
|
||||
Page: query.Page,
|
||||
PageSize: query.PageSize,
|
||||
UserID: userID, // 强制设置为当前用户ID
|
||||
Keyword: query.Keyword,
|
||||
SortBy: query.SortBy,
|
||||
SortOrder: query.SortOrder,
|
||||
CompanyName: query.CompanyName,
|
||||
ProductName: query.ProductName,
|
||||
StartTime: query.StartTime,
|
||||
EndTime: query.EndTime,
|
||||
}
|
||||
subscriptions, total, err := s.productSubscriptionService.ListSubscriptions(ctx, repoQuery)
|
||||
if err != nil {
|
||||
@@ -173,42 +170,56 @@ func (s *SubscriptionApplicationServiceImpl) GetSubscriptionUsage(ctx context.Co
|
||||
// GetSubscriptionStats 获取订阅统计信息
|
||||
// 业务流程:1. 获取订阅统计 2. 构建响应数据
|
||||
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionStats(ctx context.Context) (*responses.SubscriptionStatsResponse, error) {
|
||||
// 这里需要扩展领域服务来支持统计功能
|
||||
// 暂时返回默认值
|
||||
stats, err := s.productSubscriptionService.GetSubscriptionStats(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &responses.SubscriptionStatsResponse{
|
||||
TotalSubscriptions: 0,
|
||||
TotalRevenue: 0,
|
||||
TotalSubscriptions: stats["total_subscriptions"].(int64),
|
||||
TotalRevenue: stats["total_revenue"].(float64),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetMySubscriptionStats 获取我的订阅统计信息
|
||||
// 业务流程:1. 获取用户订阅统计 2. 构建响应数据
|
||||
func (s *SubscriptionApplicationServiceImpl) GetMySubscriptionStats(ctx context.Context, userID string) (*responses.SubscriptionStatsResponse, error) {
|
||||
// 获取用户订阅数量
|
||||
subscriptions, err := s.productSubscriptionService.GetUserSubscriptions(ctx, userID)
|
||||
stats, err := s.productSubscriptionService.GetUserSubscriptionStats(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 计算总收益
|
||||
var totalRevenue float64
|
||||
for _, subscription := range subscriptions {
|
||||
totalRevenue += subscription.Price.InexactFloat64()
|
||||
}
|
||||
|
||||
|
||||
return &responses.SubscriptionStatsResponse{
|
||||
TotalSubscriptions: int64(len(subscriptions)),
|
||||
TotalRevenue: totalRevenue,
|
||||
TotalSubscriptions: stats["total_subscriptions"].(int64),
|
||||
TotalRevenue: stats["total_revenue"].(float64),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// convertToSubscriptionInfoResponse 转换为订阅信息响应
|
||||
func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponse(subscription *entities.Subscription) *responses.SubscriptionInfoResponse {
|
||||
// 查询用户信息
|
||||
var userInfo *responses.UserSimpleResponse
|
||||
if subscription.UserID != "" {
|
||||
user, err := s.userRepo.GetByIDWithEnterpriseInfo(context.Background(), subscription.UserID)
|
||||
if err == nil {
|
||||
companyName := "未知公司"
|
||||
if user.EnterpriseInfo != nil {
|
||||
companyName = user.EnterpriseInfo.CompanyName
|
||||
}
|
||||
userInfo = &responses.UserSimpleResponse{
|
||||
ID: user.ID,
|
||||
CompanyName: companyName,
|
||||
Phone: user.Phone,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &responses.SubscriptionInfoResponse{
|
||||
ID: subscription.ID,
|
||||
UserID: subscription.UserID,
|
||||
ProductID: subscription.ProductID,
|
||||
Price: subscription.Price.InexactFloat64(),
|
||||
User: userInfo,
|
||||
Product: s.convertToProductSimpleResponse(subscription.Product),
|
||||
APIUsed: subscription.APIUsed,
|
||||
CreatedAt: subscription.CreatedAt,
|
||||
|
||||
@@ -31,6 +31,19 @@ type EnterpriseInfoItem struct {
|
||||
LegalPersonPhone string `json:"legal_person_phone"`
|
||||
EnterpriseAddress string `json:"enterprise_address"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
|
||||
// 合同信息
|
||||
Contracts []*ContractInfoItem `json:"contracts,omitempty"`
|
||||
}
|
||||
|
||||
// ContractInfoItem 合同信息项
|
||||
type ContractInfoItem struct {
|
||||
ID string `json:"id"`
|
||||
ContractName string `json:"contract_name"`
|
||||
ContractType string `json:"contract_type"` // 合同类型代码
|
||||
ContractTypeName string `json:"contract_type_name"` // 合同类型中文名称
|
||||
ContractFileURL string `json:"contract_file_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// UserListResponse 用户列表响应
|
||||
@@ -46,4 +59,9 @@ type UserStatsResponse struct {
|
||||
TotalUsers int64 `json:"total_users"`
|
||||
ActiveUsers int64 `json:"active_users"`
|
||||
CertifiedUsers int64 `json:"certified_users"`
|
||||
}
|
||||
|
||||
// UserDetailResponse 用户详情响应
|
||||
type UserDetailResponse struct {
|
||||
*UserListItem
|
||||
}
|
||||
@@ -20,5 +20,6 @@ type UserApplicationService interface {
|
||||
|
||||
// 管理员功能
|
||||
ListUsers(ctx context.Context, query *queries.ListUsersQuery) (*responses.UserListResponse, error)
|
||||
GetUserDetail(ctx context.Context, userID string) (*responses.UserDetailResponse, error)
|
||||
GetUserStats(ctx context.Context) (*responses.UserStatsResponse, error)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ type UserApplicationServiceImpl struct {
|
||||
userAuthService *user_service.UserAuthService
|
||||
smsCodeService *user_service.SMSCodeService
|
||||
walletService finance_service.WalletAggregateService
|
||||
contractService user_service.ContractAggregateService
|
||||
eventBus interfaces.EventBus
|
||||
jwtAuth *middleware.JWTAuthMiddleware
|
||||
logger *zap.Logger
|
||||
@@ -35,6 +36,7 @@ func NewUserApplicationService(
|
||||
userAuthService *user_service.UserAuthService,
|
||||
smsCodeService *user_service.SMSCodeService,
|
||||
walletService finance_service.WalletAggregateService,
|
||||
contractService user_service.ContractAggregateService,
|
||||
eventBus interfaces.EventBus,
|
||||
jwtAuth *middleware.JWTAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
@@ -44,6 +46,7 @@ func NewUserApplicationService(
|
||||
userAuthService: userAuthService,
|
||||
smsCodeService: smsCodeService,
|
||||
walletService: walletService,
|
||||
contractService: contractService,
|
||||
eventBus: eventBus,
|
||||
jwtAuth: jwtAuth,
|
||||
logger: logger,
|
||||
@@ -342,6 +345,23 @@ func (s *UserApplicationServiceImpl) ListUsers(ctx context.Context, query *queri
|
||||
EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress,
|
||||
CreatedAt: user.EnterpriseInfo.CreatedAt,
|
||||
}
|
||||
|
||||
// 获取企业合同信息
|
||||
contracts, err := s.contractService.FindByUserID(ctx, user.ID)
|
||||
if err == nil && len(contracts) > 0 {
|
||||
contractItems := make([]*responses.ContractInfoItem, 0, len(contracts))
|
||||
for _, contract := range contracts {
|
||||
contractItems = append(contractItems, &responses.ContractInfoItem{
|
||||
ID: contract.ID,
|
||||
ContractName: contract.ContractName,
|
||||
ContractType: string(contract.ContractType),
|
||||
ContractTypeName: contract.GetContractTypeName(),
|
||||
ContractFileURL: contract.ContractFileURL,
|
||||
CreatedAt: contract.CreatedAt,
|
||||
})
|
||||
}
|
||||
item.EnterpriseInfo.Contracts = contractItems
|
||||
}
|
||||
}
|
||||
|
||||
// 添加钱包余额信息
|
||||
@@ -363,6 +383,72 @@ func (s *UserApplicationServiceImpl) ListUsers(ctx context.Context, query *queri
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetUserDetail 获取用户详情(管理员功能)
|
||||
// 业务流程:1. 查询用户详情 2. 构建响应数据
|
||||
func (s *UserApplicationServiceImpl) GetUserDetail(ctx context.Context, userID string) (*responses.UserDetailResponse, error) {
|
||||
// 1. 查询用户详情(包含企业信息)
|
||||
user, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. 构建响应数据
|
||||
item := &responses.UserListItem{
|
||||
ID: user.ID,
|
||||
Phone: user.Phone,
|
||||
UserType: user.UserType,
|
||||
Username: user.Username,
|
||||
IsActive: user.Active,
|
||||
IsCertified: user.IsCertified,
|
||||
LoginCount: user.LoginCount,
|
||||
LastLoginAt: user.LastLoginAt,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}
|
||||
|
||||
// 添加企业信息
|
||||
if user.EnterpriseInfo != nil {
|
||||
item.EnterpriseInfo = &responses.EnterpriseInfoItem{
|
||||
ID: user.EnterpriseInfo.ID,
|
||||
CompanyName: user.EnterpriseInfo.CompanyName,
|
||||
UnifiedSocialCode: user.EnterpriseInfo.UnifiedSocialCode,
|
||||
LegalPersonName: user.EnterpriseInfo.LegalPersonName,
|
||||
LegalPersonPhone: user.EnterpriseInfo.LegalPersonPhone,
|
||||
EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress,
|
||||
CreatedAt: user.EnterpriseInfo.CreatedAt,
|
||||
}
|
||||
|
||||
// 获取企业合同信息
|
||||
contracts, err := s.contractService.FindByUserID(ctx, user.ID)
|
||||
if err == nil && len(contracts) > 0 {
|
||||
contractItems := make([]*responses.ContractInfoItem, 0, len(contracts))
|
||||
for _, contract := range contracts {
|
||||
contractItems = append(contractItems, &responses.ContractInfoItem{
|
||||
ID: contract.ID,
|
||||
ContractName: contract.ContractName,
|
||||
ContractType: string(contract.ContractType),
|
||||
ContractTypeName: contract.GetContractTypeName(),
|
||||
ContractFileURL: contract.ContractFileURL,
|
||||
CreatedAt: contract.CreatedAt,
|
||||
})
|
||||
}
|
||||
item.EnterpriseInfo.Contracts = contractItems
|
||||
}
|
||||
}
|
||||
|
||||
// 添加钱包余额信息
|
||||
wallet, err := s.walletService.LoadWalletByUserId(ctx, user.ID)
|
||||
if err == nil && wallet != nil {
|
||||
item.WalletBalance = wallet.Balance.String()
|
||||
} else {
|
||||
item.WalletBalance = "0"
|
||||
}
|
||||
|
||||
return &responses.UserDetailResponse{
|
||||
UserListItem: item,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetUserStats 获取用户统计信息(管理员功能)
|
||||
// 业务流程:1. 查询用户统计信息 2. 构建响应数据
|
||||
func (s *UserApplicationServiceImpl) GetUserStats(ctx context.Context) (*responses.UserStatsResponse, error) {
|
||||
|
||||
Reference in New Issue
Block a user