318 lines
9.0 KiB
Go
318 lines
9.0 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"license-manager/internal/models"
|
|
"license-manager/internal/repository"
|
|
pkgcontext "license-manager/pkg/context"
|
|
"license-manager/pkg/i18n"
|
|
"strings"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// CuInvoiceService 客户端发票服务接口
|
|
type CuInvoiceService interface {
|
|
// ========== 客户端发票操作 ==========
|
|
CreateInvoice(ctx context.Context, cuUserID string, req *models.InvoiceCreateRequest) (*models.Invoice, error)
|
|
GetInvoice(ctx context.Context, invoiceID, cuUserID string) (*models.Invoice, error)
|
|
GetUserInvoices(ctx context.Context, cuUserID string, req *models.InvoiceListRequest) (*models.InvoiceListResponse, error)
|
|
GetInvoiceSummary(ctx context.Context, cuUserID string) (*models.InvoiceSummaryResponse, error)
|
|
DownloadInvoice(ctx context.Context, invoiceID, cuUserID string) (string, error)
|
|
UpdateInvoice(ctx context.Context, invoiceID, cuUserID string, req *models.InvoiceUpdateRequest) (*models.Invoice, error)
|
|
}
|
|
|
|
type cuInvoiceService struct {
|
|
repo repository.CuInvoiceRepository
|
|
orderRepo repository.CuOrderRepository
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewCuInvoiceService(repo repository.CuInvoiceRepository, orderRepo repository.CuOrderRepository, db *gorm.DB) CuInvoiceService {
|
|
return &cuInvoiceService{
|
|
repo: repo,
|
|
orderRepo: orderRepo,
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// ========== 客户端发票操作实现 ==========
|
|
|
|
func (s *cuInvoiceService) CreateInvoice(ctx context.Context, cuUserID string, req *models.InvoiceCreateRequest) (*models.Invoice, error) {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 验证订单是否存在且属于当前用户
|
|
order, err := s.orderRepo.GetByID(req.OrderID)
|
|
if err != nil {
|
|
return nil, i18n.NewI18nError("700002", lang)
|
|
}
|
|
|
|
if order.CuUserID != cuUserID {
|
|
return nil, i18n.NewI18nError("700002", lang)
|
|
}
|
|
|
|
// 验证订单状态必须为已支付
|
|
if order.Status != "paid" {
|
|
return nil, i18n.NewI18nError("700002", lang)
|
|
}
|
|
|
|
// 检查是否已存在发票
|
|
exists, err := s.repo.CheckOrderInvoiceExists(req.OrderID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if exists {
|
|
return nil, i18n.NewI18nError("700003", lang)
|
|
}
|
|
|
|
// 验证发票类型和纳税人识别号
|
|
if req.InvoiceType != models.InvoiceTypePersonal && (req.TaxpayerID == nil || *req.TaxpayerID == "") {
|
|
return nil, i18n.NewI18nError("700004", lang)
|
|
}
|
|
|
|
// 生成发票申请号
|
|
invoiceNo := s.generateInvoiceNo()
|
|
|
|
// 创建发票记录
|
|
now := time.Now()
|
|
invoice := &models.Invoice{
|
|
InvoiceNo: invoiceNo,
|
|
OrderID: req.OrderID,
|
|
OrderNo: order.OrderNo,
|
|
CustomerID: order.CustomerID,
|
|
CuUserID: cuUserID,
|
|
Amount: order.TotalAmount,
|
|
Status: models.InvoiceStatusPending,
|
|
InvoiceType: req.InvoiceType,
|
|
Title: req.Title,
|
|
TaxpayerID: req.TaxpayerID,
|
|
Content: req.Content,
|
|
ReceiverEmail: req.ReceiverEmail,
|
|
Remark: req.Remark,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
err = s.repo.Create(invoice)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return invoice, nil
|
|
}
|
|
|
|
func (s *cuInvoiceService) GetInvoice(ctx context.Context, invoiceID, cuUserID string) (*models.Invoice, error) {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
invoice, err := s.repo.GetByID(invoiceID)
|
|
if err != nil {
|
|
return nil, i18n.NewI18nError("700001", lang)
|
|
}
|
|
|
|
// 验证权限:只能查看自己的发票
|
|
if invoice.CuUserID != cuUserID {
|
|
return nil, i18n.NewI18nError("700001", lang)
|
|
}
|
|
|
|
return invoice, nil
|
|
}
|
|
|
|
func (s *cuInvoiceService) GetUserInvoices(ctx context.Context, cuUserID string, req *models.InvoiceListRequest) (*models.InvoiceListResponse, error) {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 解析日期筛选
|
|
var createdAtStart, createdAtEnd *time.Time
|
|
if req.ApplyDate != "" {
|
|
start, end, err := s.parseDateRange(req.ApplyDate)
|
|
if err != nil {
|
|
return nil, i18n.NewI18nError("900001", lang)
|
|
}
|
|
createdAtStart = start
|
|
createdAtEnd = end
|
|
}
|
|
|
|
invoices, total, err := s.repo.GetByCuUserID(ctx, cuUserID, req, createdAtStart, createdAtEnd)
|
|
if err != nil {
|
|
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
// 转换为响应结构并填充多语言显示字段
|
|
invoiceResponses := make([]*models.InvoiceResponse, len(invoices))
|
|
for i, invoice := range invoices {
|
|
invoiceResponses[i] = invoice.ToResponse()
|
|
s.fillDisplayFields(invoiceResponses[i], lang)
|
|
}
|
|
|
|
return &models.InvoiceListResponse{
|
|
Invoices: invoiceResponses,
|
|
TotalCount: total,
|
|
Page: req.Page,
|
|
PageSize: req.PageSize,
|
|
}, nil
|
|
}
|
|
|
|
func (s *cuInvoiceService) GetInvoiceSummary(ctx context.Context, cuUserID string) (*models.InvoiceSummaryResponse, error) {
|
|
return s.repo.GetCuUserInvoiceSummary(ctx, cuUserID)
|
|
}
|
|
|
|
func (s *cuInvoiceService) DownloadInvoice(ctx context.Context, invoiceID, cuUserID string) (string, error) {
|
|
invoice, err := s.GetInvoice(ctx, invoiceID, cuUserID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
if invoice.Status != models.InvoiceStatusIssued {
|
|
return "", i18n.NewI18nError("700005", lang)
|
|
}
|
|
|
|
if invoice.InvoiceFileURL == nil || *invoice.InvoiceFileURL == "" {
|
|
return "", i18n.NewI18nError("700005", lang)
|
|
}
|
|
|
|
return *invoice.InvoiceFileURL, nil
|
|
}
|
|
|
|
func (s *cuInvoiceService) UpdateInvoice(ctx context.Context, invoiceID, cuUserID string, req *models.InvoiceUpdateRequest) (*models.Invoice, error) {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 获取原发票
|
|
invoice, err := s.repo.GetByID(invoiceID)
|
|
if err != nil {
|
|
return nil, i18n.NewI18nError("700001", lang)
|
|
}
|
|
|
|
// 权限校验
|
|
if invoice.CuUserID != cuUserID {
|
|
return nil, i18n.NewI18nError("700001", lang)
|
|
}
|
|
|
|
// 状态校验:只有被驳回的发票可以修改
|
|
if invoice.Status != models.InvoiceStatusRejected {
|
|
return nil, i18n.NewI18nError("700004", lang)
|
|
}
|
|
|
|
// 业务逻辑校验:发票类型和纳税人识别号
|
|
// 如果传入了新的 InvoiceType 或者 TaxpayerID,我们要校验最终的结果
|
|
finalInvoiceType := invoice.InvoiceType
|
|
if req.InvoiceType != nil {
|
|
finalInvoiceType = *req.InvoiceType
|
|
}
|
|
|
|
finalTaxpayerID := invoice.TaxpayerID
|
|
if req.TaxpayerID != nil {
|
|
finalTaxpayerID = req.TaxpayerID // This handles explicitly setting it to nil or "" as well if needed? Wait, if they set *req.TaxpayerID == "" it will just replace it.
|
|
}
|
|
|
|
if finalInvoiceType != models.InvoiceTypePersonal && (finalTaxpayerID == nil || *finalTaxpayerID == "") {
|
|
return nil, i18n.NewI18nError("700004", lang)
|
|
}
|
|
|
|
// 更新字段(仅更新非空字段)
|
|
if req.InvoiceType != nil {
|
|
invoice.InvoiceType = *req.InvoiceType
|
|
}
|
|
if req.Title != nil {
|
|
invoice.Title = *req.Title
|
|
}
|
|
if req.TaxpayerID != nil {
|
|
invoice.TaxpayerID = req.TaxpayerID
|
|
}
|
|
if req.Content != nil {
|
|
invoice.Content = *req.Content
|
|
}
|
|
if req.ReceiverEmail != nil {
|
|
invoice.ReceiverEmail = *req.ReceiverEmail
|
|
}
|
|
if req.Remark != nil {
|
|
invoice.Remark = req.Remark
|
|
}
|
|
|
|
// 重置状态和驳回信息
|
|
invoice.Status = models.InvoiceStatusPending
|
|
invoice.RejectReason = nil
|
|
invoice.Suggestion = nil
|
|
invoice.RejectedAt = nil
|
|
invoice.RejectedBy = nil
|
|
|
|
// 更新数据库
|
|
err = s.repo.Update(invoice)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return invoice, nil
|
|
}
|
|
|
|
// ========== 私有辅助方法 ==========
|
|
|
|
// fillDisplayFields 填充响应结构的多语言显示字段
|
|
func (s *cuInvoiceService) fillDisplayFields(response *models.InvoiceResponse, lang string) {
|
|
response.StatusDisplay = i18n.GetEnumMessage("invoice_status", response.Status, lang)
|
|
response.InvoiceTypeDisplay = i18n.GetEnumMessage("invoice_type", response.InvoiceType, lang)
|
|
}
|
|
|
|
// generateInvoiceNo 生成发票申请号
|
|
func (s *cuInvoiceService) generateInvoiceNo() string {
|
|
now := time.Now()
|
|
timestamp := now.UnixNano() / int64(time.Millisecond) % 1000000000 // 取纳秒后9位
|
|
return fmt.Sprintf("INV%04d%02d%02d%09d",
|
|
now.Year(), now.Month(), now.Day(), timestamp)
|
|
}
|
|
|
|
// parseDateRange 解析日期范围
|
|
func (s *cuInvoiceService) parseDateRange(dateStr string) (*time.Time, *time.Time, error) {
|
|
parts := strings.Split(dateStr, ",")
|
|
if len(parts) == 1 {
|
|
start, _, err := s.parseFlexibleTime(strings.TrimSpace(dateStr))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
end := start.AddDate(0, 0, 1)
|
|
return &start, &end, nil
|
|
}
|
|
if len(parts) != 2 {
|
|
return nil, nil, fmt.Errorf("invalid date range")
|
|
}
|
|
|
|
startText := strings.TrimSpace(parts[0])
|
|
endText := strings.TrimSpace(parts[1])
|
|
if startText == "" || endText == "" {
|
|
return nil, nil, fmt.Errorf("invalid date range")
|
|
}
|
|
|
|
start, _, err := s.parseFlexibleTime(startText)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
end, endDateOnly, err := s.parseFlexibleTime(endText)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if endDateOnly {
|
|
end = end.AddDate(0, 0, 1)
|
|
}
|
|
if end.Before(start) {
|
|
start, end = end, start
|
|
}
|
|
|
|
return &start, &end, nil
|
|
}
|
|
|
|
func (s *cuInvoiceService) parseFlexibleTime(value string) (time.Time, bool, error) {
|
|
if t, err := time.Parse(time.RFC3339Nano, value); err == nil {
|
|
return t, false, nil
|
|
}
|
|
if t, err := time.Parse(time.RFC3339, value); err == nil {
|
|
return t, false, nil
|
|
}
|
|
t, err := time.Parse("2006-01-02", value)
|
|
if err != nil {
|
|
return time.Time{}, false, err
|
|
}
|
|
return t, true, nil
|
|
}
|