Files
rmser/internal/services/ocr/service.go
SERTY 542beafe0e Перевел на multi-tenant
Добавил поставщиков
Накладные успешно создаются из фронта
2025-12-18 03:56:21 +03:00

200 lines
5.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 ocr
import (
"context"
"fmt"
"github.com/google/uuid"
"github.com/shopspring/decimal"
"rmser/internal/domain/account"
"rmser/internal/domain/catalog"
"rmser/internal/domain/drafts"
"rmser/internal/domain/ocr"
"rmser/internal/infrastructure/ocr_client"
)
type Service struct {
ocrRepo ocr.Repository
catalogRepo catalog.Repository
draftRepo drafts.Repository
accountRepo account.Repository // <-- NEW
pyClient *ocr_client.Client
}
func NewService(
ocrRepo ocr.Repository,
catalogRepo catalog.Repository,
draftRepo drafts.Repository,
accountRepo account.Repository, // <-- NEW
pyClient *ocr_client.Client,
) *Service {
return &Service{
ocrRepo: ocrRepo,
catalogRepo: catalogRepo,
draftRepo: draftRepo,
accountRepo: accountRepo,
pyClient: pyClient,
}
}
// ProcessReceiptImage
func (s *Service) ProcessReceiptImage(ctx context.Context, userID uuid.UUID, imgData []byte) (*drafts.DraftInvoice, error) {
// 1. Получаем активный сервер для UserID
server, err := s.accountRepo.GetActiveServer(userID)
if err != nil || server == nil {
return nil, fmt.Errorf("no active server for user")
}
serverID := server.ID
// 2. Создаем черновик
draft := &drafts.DraftInvoice{
UserID: userID, // <-- Исправлено с ChatID на UserID
RMSServerID: serverID, // <-- NEW
Status: drafts.StatusProcessing,
}
if err := s.draftRepo.Create(draft); err != nil {
return nil, fmt.Errorf("failed to create draft: %w", err)
}
// 3. Отправляем в Python OCR
rawResult, err := s.pyClient.ProcessImage(ctx, imgData, "receipt.jpg")
if err != nil {
draft.Status = drafts.StatusError
_ = s.draftRepo.Update(draft)
return nil, fmt.Errorf("python ocr error: %w", err)
}
// 4. Матчинг (с учетом ServerID)
var draftItems []drafts.DraftInvoiceItem
for _, rawItem := range rawResult.Items {
item := drafts.DraftInvoiceItem{
DraftID: draft.ID,
RawName: rawItem.RawName,
RawAmount: decimal.NewFromFloat(rawItem.Amount),
RawPrice: decimal.NewFromFloat(rawItem.Price),
Quantity: decimal.NewFromFloat(rawItem.Amount),
Price: decimal.NewFromFloat(rawItem.Price),
Sum: decimal.NewFromFloat(rawItem.Sum),
}
match, _ := s.ocrRepo.FindMatch(serverID, rawItem.RawName) // <-- ServerID
if match != nil {
item.IsMatched = true
item.ProductID = &match.ProductID
item.ContainerID = match.ContainerID
} else {
s.ocrRepo.UpsertUnmatched(serverID, rawItem.RawName) // <-- ServerID
}
draftItems = append(draftItems, item)
}
// 5. Сохраняем
draft.Status = drafts.StatusReadyToVerify
s.draftRepo.Update(draft)
s.draftRepo.CreateItems(draftItems)
return draft, nil
}
// Добавить структуры в конец файла
type ContainerForIndex struct {
ID string `json:"id"`
Name string `json:"name"`
Count float64 `json:"count"`
}
type ProductForIndex struct {
ID string `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
MeasureUnit string `json:"measure_unit"`
Containers []ContainerForIndex `json:"containers"`
}
// GetCatalogForIndexing
func (s *Service) GetCatalogForIndexing(userID uuid.UUID) ([]ProductForIndex, error) {
server, err := s.accountRepo.GetActiveServer(userID)
if err != nil || server == nil {
return nil, fmt.Errorf("no server")
}
products, err := s.catalogRepo.GetActiveGoods(server.ID)
if err != nil {
return nil, err
}
result := make([]ProductForIndex, 0, len(products))
for _, p := range products {
uom := ""
if p.MainUnit != nil {
uom = p.MainUnit.Name
}
var conts []ContainerForIndex
for _, c := range p.Containers {
cnt, _ := c.Count.Float64()
conts = append(conts, ContainerForIndex{
ID: c.ID.String(),
Name: c.Name,
Count: cnt,
})
}
result = append(result, ProductForIndex{
ID: p.ID.String(),
Name: p.Name,
Code: p.Code,
MeasureUnit: uom,
Containers: conts,
})
}
return result, nil
}
func (s *Service) SearchProducts(userID uuid.UUID, query string) ([]catalog.Product, error) {
if len(query) < 2 {
return []catalog.Product{}, nil
}
server, err := s.accountRepo.GetActiveServer(userID)
if err != nil || server == nil {
return nil, fmt.Errorf("no server")
}
return s.catalogRepo.Search(server.ID, query)
}
func (s *Service) SaveMapping(userID uuid.UUID, rawName string, productID uuid.UUID, quantity decimal.Decimal, containerID *uuid.UUID) error {
server, err := s.accountRepo.GetActiveServer(userID)
if err != nil || server == nil {
return fmt.Errorf("no server")
}
return s.ocrRepo.SaveMatch(server.ID, rawName, productID, quantity, containerID)
}
func (s *Service) DeleteMatch(userID uuid.UUID, rawName string) error {
server, err := s.accountRepo.GetActiveServer(userID)
if err != nil || server == nil {
return fmt.Errorf("no server")
}
return s.ocrRepo.DeleteMatch(server.ID, rawName)
}
func (s *Service) GetKnownMatches(userID uuid.UUID) ([]ocr.ProductMatch, error) {
server, err := s.accountRepo.GetActiveServer(userID)
if err != nil || server == nil {
return nil, fmt.Errorf("no server")
}
return s.ocrRepo.GetAllMatches(server.ID)
}
func (s *Service) GetUnmatchedItems(userID uuid.UUID) ([]ocr.UnmatchedItem, error) {
server, err := s.accountRepo.GetActiveServer(userID)
if err != nil || server == nil {
return nil, fmt.Errorf("no server")
}
return s.ocrRepo.GetTopUnmatched(server.ID, 50)
}