package invoices import ( "fmt" "time" "github.com/google/uuid" "github.com/shopspring/decimal" "go.uber.org/zap" "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 rmsFactory *rms.Factory // Здесь можно добавить репозитории каталога и контрагентов для валидации, // но для краткости пока опустим глубокую валидацию. } func NewService(repo invDomain.Repository, draftsRepo drafts.Repository, supplierRepo suppliers.Repository, rmsFactory *rms.Factory) *Service { return &Service{ repo: repo, draftsRepo: draftsRepo, supplierRepo: supplierRepo, 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 } // 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 }