Files
rmser/internal/services/invoices/service.go
SERTY 88620f3fb6 0202-финиш перед десктопом
пересчет поправил
редактирование с перепроведением
галка автопроведения работает
рекомендации починил
2026-02-02 13:53:38 +03:00

208 lines
6.5 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 invoices
import (
"fmt"
"time"
"github.com/google/uuid"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"rmser/internal/domain/account"
"rmser/internal/domain/drafts"
invDomain "rmser/internal/domain/invoices"
"rmser/internal/domain/suppliers"
"rmser/internal/infrastructure/rms"
"rmser/pkg/logger"
)
type Service struct {
repo invDomain.Repository
draftsRepo drafts.Repository
supplierRepo suppliers.Repository
accountRepo account.Repository
rmsFactory *rms.Factory
// Здесь можно добавить репозитории каталога и контрагентов для валидации,
// но для краткости пока опустим глубокую валидацию.
}
func NewService(repo invDomain.Repository, draftsRepo drafts.Repository, supplierRepo suppliers.Repository, accountRepo account.Repository, rmsFactory *rms.Factory) *Service {
return &Service{
repo: repo,
draftsRepo: draftsRepo,
supplierRepo: supplierRepo,
accountRepo: accountRepo,
rmsFactory: rmsFactory,
}
}
// CreateRequestDTO - структура входящего JSON запроса от фронта/OCR
type CreateRequestDTO struct {
DocumentNumber string `json:"document_number"`
DateIncoming string `json:"date_incoming"` // YYYY-MM-DD
SupplierID uuid.UUID `json:"supplier_id"`
StoreID uuid.UUID `json:"store_id"`
Items []struct {
ProductID uuid.UUID `json:"product_id"`
Amount decimal.Decimal `json:"amount"`
Price decimal.Decimal `json:"price"`
} `json:"items"`
}
// SendInvoiceToRMS валидирует DTO, собирает доменную модель и отправляет в RMS
func (s *Service) SendInvoiceToRMS(req CreateRequestDTO, userID uuid.UUID) (string, error) {
// 1. Базовая валидация
if len(req.Items) == 0 {
return "", fmt.Errorf("список товаров пуст")
}
dateInc, err := time.Parse("2006-01-02", req.DateIncoming)
if err != nil {
return "", fmt.Errorf("неверный формат даты (ожидается YYYY-MM-DD): %v", err)
}
// 2. Сборка доменной модели
inv := invDomain.Invoice{
ID: uuid.Nil, // Новый документ
DocumentNumber: req.DocumentNumber,
DateIncoming: dateInc,
SupplierID: req.SupplierID,
DefaultStoreID: req.StoreID,
Status: "NEW",
Items: make([]invDomain.InvoiceItem, 0, len(req.Items)),
}
for _, itemDTO := range req.Items {
sum := itemDTO.Amount.Mul(itemDTO.Price) // Пересчитываем сумму
inv.Items = append(inv.Items, invDomain.InvoiceItem{
ProductID: itemDTO.ProductID,
Amount: itemDTO.Amount,
Price: itemDTO.Price,
Sum: sum,
})
}
// 3. Получение клиента RMS
client, err := s.rmsFactory.GetClientForUser(userID)
if err != nil {
return "", fmt.Errorf("ошибка получения клиента RMS: %w", err)
}
// 4. Отправка через клиент
logger.Log.Info("Отправка накладной в RMS",
zap.String("supplier", req.SupplierID.String()),
zap.Int("items_count", len(inv.Items)))
docNum, err := client.CreateIncomingInvoice(inv)
if err != nil {
return "", err
}
return docNum, nil
}
// InvoiceStatsDTO - DTO для статистики накладных
type InvoiceStatsDTO struct {
Total int64 `json:"total"`
LastMonth int64 `json:"last_month"`
Last24h int64 `json:"last_24h"`
}
// InvoiceDetailsDTO - DTO для ответа на запрос деталей накладной
type InvoiceDetailsDTO struct {
ID uuid.UUID `json:"id"`
Number string `json:"number"`
Date string `json:"date"`
Status string `json:"status"`
Supplier struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
} `json:"supplier"`
Items []struct {
Name string `json:"name"`
Quantity float64 `json:"quantity"`
Price float64 `json:"price"`
Total float64 `json:"total"`
} `json:"items"`
PhotoURL *string `json:"photo_url"`
}
// GetInvoice возвращает детали синхронизированной накладной по ID
func (s *Service) GetInvoice(id uuid.UUID) (*InvoiceDetailsDTO, error) {
// 1. Получить накладную из репозитория
inv, err := s.repo.GetByID(id)
if err != nil {
return nil, fmt.Errorf("ошибка получения накладной: %w", err)
}
// 2. Получить поставщика
supplier, err := s.supplierRepo.GetByID(inv.SupplierID)
if err != nil {
return nil, fmt.Errorf("ошибка получения поставщика: %w", err)
}
// 3. Проверить, есть ли draft с photo_url
var photoURL *string
draft, err := s.draftsRepo.GetByRMSInvoiceID(inv.ID)
if err == nil && draft != nil {
photoURL = &draft.SenderPhotoURL
}
// 4. Собрать DTO
dto := &InvoiceDetailsDTO{
ID: inv.ID,
Number: inv.DocumentNumber,
Date: inv.DateIncoming.Format("2006-01-02"),
Status: "COMPLETED", // Для синхронизированных накладных статус всегда COMPLETED
Items: make([]struct {
Name string `json:"name"`
Quantity float64 `json:"quantity"`
Price float64 `json:"price"`
Total float64 `json:"total"`
}, len(inv.Items)),
PhotoURL: photoURL,
}
dto.Supplier.ID = supplier.ID
dto.Supplier.Name = supplier.Name
for i, item := range inv.Items {
dto.Items[i].Name = item.Product.Name
dto.Items[i].Quantity, _ = item.Amount.Float64()
dto.Items[i].Price, _ = item.Price.Float64()
dto.Items[i].Total, _ = item.Sum.Float64()
}
return dto, nil
}
// GetStats возвращает статистику по накладным для пользователя
func (s *Service) GetStats(userID uuid.UUID) (*InvoiceStatsDTO, error) {
// Получаем активный сервер пользователя
server, err := s.accountRepo.GetActiveServer(userID)
if err != nil {
return nil, fmt.Errorf("ошибка получения активного сервера: %w", err)
}
if server == nil {
return &InvoiceStatsDTO{
Total: 0,
LastMonth: 0,
Last24h: 0,
}, nil
}
// Получаем статистику из репозитория
total, lastMonth, last24h, err := s.repo.GetStats(server.ID)
if err != nil {
return nil, fmt.Errorf("ошибка получения статистики: %w", err)
}
return &InvoiceStatsDTO{
Total: total,
LastMonth: lastMonth,
Last24h: last24h,
}, nil
}