mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
пересчет поправил редактирование с перепроведением галка автопроведения работает рекомендации починил
208 lines
6.5 KiB
Go
208 lines
6.5 KiB
Go
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
|
||
}
|