редактирование и удаление сопоставлений

список накладных с позициями
This commit is contained in:
2025-12-29 10:46:05 +03:00
parent c2d382cb6a
commit 310a64e3ba
30 changed files with 1250 additions and 8173 deletions

View File

@@ -28,9 +28,11 @@ type DraftInvoice struct {
SenderPhotoURL string `gorm:"type:text" json:"photo_url"`
Status string `gorm:"type:varchar(50);default:'PROCESSING'" json:"status"`
DocumentNumber string `gorm:"type:varchar(100)" json:"document_number"`
DateIncoming *time.Time `json:"date_incoming"`
SupplierID *uuid.UUID `gorm:"type:uuid" json:"supplier_id"`
DocumentNumber string `gorm:"type:varchar(100)" json:"document_number"`
// Входящий номер документа
IncomingDocumentNumber string `gorm:"type:varchar(100)" json:"incoming_document_number"`
DateIncoming *time.Time `json:"date_incoming"`
SupplierID *uuid.UUID `gorm:"type:uuid" json:"supplier_id"`
StoreID *uuid.UUID `gorm:"type:uuid" json:"store_id"`
Store *catalog.Store `gorm:"foreignKey:StoreID" json:"store,omitempty"`
@@ -67,6 +69,7 @@ type DraftInvoiceItem struct {
type Repository interface {
Create(draft *DraftInvoice) error
GetByID(id uuid.UUID) (*DraftInvoice, error)
GetByRMSInvoiceID(rmsInvoiceID uuid.UUID) (*DraftInvoice, error)
Update(draft *DraftInvoice) error
CreateItems(items []DraftInvoiceItem) error
UpdateItem(itemID uuid.UUID, productID *uuid.UUID, containerID *uuid.UUID, qty, price decimal.Decimal) error
@@ -76,4 +79,7 @@ type Repository interface {
// GetActive возвращает активные черновики для СЕРВЕРА (а не юзера)
GetActive(serverID uuid.UUID) ([]DraftInvoice, error)
// GetRMSInvoiceIDToPhotoURLMap возвращает мапу rms_invoice_id -> sender_photo_url для сервера, где rms_invoice_id не NULL
GetRMSInvoiceIDToPhotoURLMap(serverID uuid.UUID) (map[uuid.UUID]string, error)
}

View File

@@ -42,6 +42,7 @@ type InvoiceItem struct {
}
type Repository interface {
GetByID(id uuid.UUID) (*Invoice, error)
GetLastInvoiceDate(serverID uuid.UUID) (*time.Time, error)
GetByPeriod(serverID uuid.UUID, from, to time.Time) ([]Invoice, error)
SaveInvoices(invoices []Invoice) error

View File

@@ -23,6 +23,7 @@ type Supplier struct {
type Repository interface {
SaveBatch(suppliers []Supplier) error
GetByID(id uuid.UUID) (*Supplier, error)
// GetRankedByUsage возвращает поставщиков, отсортированных по частоте использования в накладных за N дней
GetRankedByUsage(serverID uuid.UUID, daysLookBack int) ([]Supplier, error)
Count(serverID uuid.UUID) (int64, error)

View File

@@ -38,17 +38,32 @@ func (r *pgRepository) GetByID(id uuid.UUID) (*drafts.DraftInvoice, error) {
return &draft, nil
}
func (r *pgRepository) GetByRMSInvoiceID(rmsInvoiceID uuid.UUID) (*drafts.DraftInvoice, error) {
var draft drafts.DraftInvoice
err := r.db.
Where("rms_invoice_id = ?", rmsInvoiceID).
First(&draft).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return &draft, nil
}
func (r *pgRepository) Update(draft *drafts.DraftInvoice) error {
return r.db.Model(draft).Updates(map[string]interface{}{
"status": draft.Status,
"document_number": draft.DocumentNumber,
"date_incoming": draft.DateIncoming,
"supplier_id": draft.SupplierID,
"store_id": draft.StoreID,
"comment": draft.Comment,
"rms_invoice_id": draft.RMSInvoiceID,
"rms_server_id": draft.RMSServerID,
"updated_at": gorm.Expr("NOW()"),
"status": draft.Status,
"document_number": draft.DocumentNumber,
"incoming_document_number": draft.IncomingDocumentNumber, // Добавлено поле для входящего номера документа
"date_incoming": draft.DateIncoming,
"supplier_id": draft.SupplierID,
"store_id": draft.StoreID,
"comment": draft.Comment,
"rms_invoice_id": draft.RMSInvoiceID,
"rms_server_id": draft.RMSServerID,
"updated_at": gorm.Expr("NOW()"),
}).Error
}
@@ -107,3 +122,23 @@ func (r *pgRepository) GetActive(serverID uuid.UUID) ([]drafts.DraftInvoice, err
return list, err
}
// GetRMSInvoiceIDToPhotoURLMap возвращает мапу rms_invoice_id -> sender_photo_url для сервера, где rms_invoice_id не NULL
func (r *pgRepository) GetRMSInvoiceIDToPhotoURLMap(serverID uuid.UUID) (map[uuid.UUID]string, error) {
var draftsList []drafts.DraftInvoice
err := r.db.
Select("rms_invoice_id", "sender_photo_url").
Where("rms_server_id = ? AND rms_invoice_id IS NOT NULL", serverID).
Find(&draftsList).Error
if err != nil {
return nil, err
}
result := make(map[uuid.UUID]string)
for _, d := range draftsList {
if d.RMSInvoiceID != nil {
result[*d.RMSInvoiceID] = d.SenderPhotoURL
}
}
return result, nil
}

View File

@@ -18,6 +18,19 @@ func NewRepository(db *gorm.DB) invoices.Repository {
return &pgRepository{db: db}
}
func (r *pgRepository) GetByID(id uuid.UUID) (*invoices.Invoice, error) {
var inv invoices.Invoice
err := r.db.
Preload("Items").
Preload("Items.Product").
Where("id = ?", id).
First(&inv).Error
if err != nil {
return nil, err
}
return &inv, nil
}
func (r *pgRepository) GetLastInvoiceDate(serverID uuid.UUID) (*time.Time, error) {
var inv invoices.Invoice
// Ищем последнюю накладную только для этого сервера
@@ -35,7 +48,8 @@ func (r *pgRepository) GetByPeriod(serverID uuid.UUID, from, to time.Time) ([]in
var list []invoices.Invoice
err := r.db.
Preload("Items").
Where("rms_server_id = ? AND date_incoming BETWEEN ? AND ?", serverID, from, to).
Preload("Items.Product").
Where("rms_server_id = ? AND date_incoming BETWEEN ? AND ? AND status != ?", serverID, from, to, "DELETED").
Order("date_incoming DESC").
Find(&list).Error
return list, err

View File

@@ -81,6 +81,7 @@ func (r *pgRepository) GetAllMatches(serverID uuid.UUID) ([]ocr.ProductMatch, er
err := r.db.
Preload("Product").
Preload("Product.MainUnit").
Preload("Product.Containers").
Preload("Container").
Where("rms_server_id = ?", serverID).
Order("updated_at DESC").

View File

@@ -51,6 +51,15 @@ func (r *pgRepository) GetRankedByUsage(serverID uuid.UUID, daysLookBack int) ([
return result, err
}
func (r *pgRepository) GetByID(id uuid.UUID) (*suppliers.Supplier, error) {
var supplier suppliers.Supplier
err := r.db.Where("id = ? AND is_deleted = ?", id, false).First(&supplier).Error
if err != nil {
return nil, err
}
return &supplier, nil
}
func (r *pgRepository) Count(serverID uuid.UUID) (int64, error) {
var count int64
err := r.db.Model(&suppliers.Supplier{}).

View File

@@ -572,12 +572,13 @@ func (c *Client) CreateIncomingInvoice(inv invoices.Invoice) (string, error) {
}
reqDTO := IncomingInvoiceImportXML{
DocumentNumber: inv.DocumentNumber,
DateIncoming: inv.DateIncoming.Format("02.01.2006"),
DefaultStore: inv.DefaultStoreID.String(),
Supplier: inv.SupplierID.String(),
Status: status,
Comment: comment,
DocumentNumber: inv.DocumentNumber,
IncomingDocumentNumber: inv.IncomingDocumentNumber, // Присваиваем входящий номер документа из домена
DateIncoming: inv.DateIncoming.Format("02.01.2006"),
DefaultStore: inv.DefaultStoreID.String(),
Supplier: inv.SupplierID.String(),
Status: status,
Comment: comment,
}
if inv.ID != uuid.Nil {

View File

@@ -195,10 +195,11 @@ type StoreReportItemXML struct {
// IncomingInvoiceImportXML описывает структуру для POST запроса импорта
type IncomingInvoiceImportXML struct {
XMLName xml.Name `xml:"document"`
ID string `xml:"id,omitempty"` // GUID, если редактируем
DocumentNumber string `xml:"documentNumber,omitempty"`
DateIncoming string `xml:"dateIncoming,omitempty"` // Format: dd.MM.yyyy
XMLName xml.Name `xml:"document"`
ID string `xml:"id,omitempty"` // GUID, если редактируем
DocumentNumber string `xml:"documentNumber,omitempty"`
IncomingDocumentNumber string `xml:"incomingDocumentNumber,omitempty"` // Входящий номер документа
DateIncoming string `xml:"dateIncoming,omitempty"` // Format: dd.MM.yyyy
Invoice string `xml:"invoice,omitempty"` // Номер счет-фактуры
DefaultStore string `xml:"defaultStore"` // GUID склада (обязательно)
Supplier string `xml:"supplier"` // GUID поставщика (обязательно)

View File

@@ -152,7 +152,7 @@ func (s *Service) DeleteDraft(id uuid.UUID) (string, error) {
return draft.Status, nil
}
func (s *Service) UpdateDraftHeader(id uuid.UUID, storeID *uuid.UUID, supplierID *uuid.UUID, date time.Time, comment string) error {
func (s *Service) UpdateDraftHeader(id uuid.UUID, storeID *uuid.UUID, supplierID *uuid.UUID, date time.Time, comment string, incomingDocNum string) error {
draft, err := s.draftRepo.GetByID(id)
if err != nil {
return err
@@ -164,6 +164,7 @@ func (s *Service) UpdateDraftHeader(id uuid.UUID, storeID *uuid.UUID, supplierID
draft.SupplierID = supplierID
draft.DateIncoming = &date
draft.Comment = comment
draft.IncomingDocumentNumber = incomingDocNum
return s.draftRepo.Update(draft)
}
@@ -270,14 +271,15 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) {
// 4. Сборка Invoice
inv := invoices.Invoice{
ID: uuid.Nil,
DocumentNumber: draft.DocumentNumber,
DateIncoming: *draft.DateIncoming,
SupplierID: *draft.SupplierID,
DefaultStoreID: *draft.StoreID,
Status: targetStatus,
Comment: draft.Comment,
Items: make([]invoices.InvoiceItem, 0, len(draft.Items)),
ID: uuid.Nil,
DocumentNumber: draft.DocumentNumber,
DateIncoming: *draft.DateIncoming,
SupplierID: *draft.SupplierID,
DefaultStoreID: *draft.StoreID,
Status: targetStatus,
Comment: draft.Comment,
IncomingDocumentNumber: draft.IncomingDocumentNumber,
Items: make([]invoices.InvoiceItem, 0, len(draft.Items)),
}
for _, dItem := range draft.Items {
@@ -338,7 +340,25 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) {
return "", err
}
// 6. Обновление статуса черновика
// 6. Поиск UUID созданной накладной
invoices, err := client.FetchInvoices(*draft.DateIncoming, *draft.DateIncoming)
if err != nil {
logger.Log.Warn("Не удалось получить список накладных для поиска UUID", zap.Error(err), zap.Time("date", *draft.DateIncoming))
} else {
found := false
for _, invoice := range invoices {
if invoice.DocumentNumber == docNum {
draft.RMSInvoiceID = &invoice.ID
found = true
break
}
}
if !found {
logger.Log.Warn("UUID созданной накладной не найден", zap.String("document_number", docNum), zap.Time("date", *draft.DateIncoming))
}
}
// 7. Обновление статуса черновика
draft.Status = drafts.StatusCompleted
s.draftRepo.Update(draft)
@@ -471,6 +491,8 @@ type UnifiedInvoiceDTO struct {
ItemsCount int `json:"items_count"`
CreatedAt time.Time `json:"created_at"`
IsAppCreated bool `json:"is_app_created"`
PhotoURL string `json:"photo_url"`
ItemsPreview string `json:"items_preview"`
}
func (s *Service) GetUnifiedList(userID uuid.UUID, from, to time.Time) ([]UnifiedInvoiceDTO, error) {
@@ -491,6 +513,12 @@ func (s *Service) GetUnifiedList(userID uuid.UUID, from, to time.Time) ([]Unifie
return nil, err
}
// 3. Получаем мапу rms_invoice_id -> sender_photo_url
photoMap, err := s.draftRepo.GetRMSInvoiceIDToPhotoURLMap(server.ID)
if err != nil {
return nil, err
}
result := make([]UnifiedInvoiceDTO, 0, len(draftsList)+len(invoicesList))
// Маппим черновики
@@ -510,6 +538,19 @@ func (s *Service) GetUnifiedList(userID uuid.UUID, from, to time.Time) ([]Unifie
date = *d.DateIncoming
}
// Формируем ItemsPreview для черновиков
var itemsPreview string
if len(d.Items) > 0 {
names := make([]string, 0, 3)
for i, it := range d.Items {
if i >= 3 {
break
}
names = append(names, it.RawName)
}
itemsPreview = strings.Join(names, ", ")
}
result = append(result, UnifiedInvoiceDTO{
ID: d.ID,
Type: "DRAFT",
@@ -522,6 +563,8 @@ func (s *Service) GetUnifiedList(userID uuid.UUID, from, to time.Time) ([]Unifie
ItemsCount: len(d.Items),
CreatedAt: d.CreatedAt,
IsAppCreated: true,
PhotoURL: d.SenderPhotoURL,
ItemsPreview: itemsPreview,
})
}
@@ -533,7 +576,29 @@ func (s *Service) GetUnifiedList(userID uuid.UUID, from, to time.Time) ([]Unifie
}
val, _ := sum.Float64()
isOurs := strings.Contains(strings.ToUpper(inv.Comment), "RMSER")
// Определяем IsAppCreated и PhotoURL через мапу
isAppCreated := false
photoURL := ""
if url, exists := photoMap[inv.ID]; exists {
isAppCreated = true
photoURL = url
}
// Формируем ItemsPreview для синхронизированных накладных
var itemsPreview string
if len(inv.Items) > 0 {
names := make([]string, 0, 3)
for i, it := range inv.Items {
if i >= 3 {
break
}
// Предполагаем, что Product подгружен, иначе нужно добавить Preload
if it.Product.Name != "" {
names = append(names, it.Product.Name)
}
}
itemsPreview = strings.Join(names, ", ")
}
result = append(result, UnifiedInvoiceDTO{
ID: inv.ID,
@@ -545,7 +610,9 @@ func (s *Service) GetUnifiedList(userID uuid.UUID, from, to time.Time) ([]Unifie
TotalSum: val,
ItemsCount: len(inv.Items),
CreatedAt: inv.CreatedAt,
IsAppCreated: isOurs,
IsAppCreated: isAppCreated,
PhotoURL: photoURL,
ItemsPreview: itemsPreview,
})
}
@@ -553,3 +620,34 @@ func (s *Service) GetUnifiedList(userID uuid.UUID, from, to time.Time) ([]Unifie
// (Здесь можно добавить библиотеку sort или оставить как есть, если БД уже отсортировала части)
return result, nil
}
func (s *Service) GetInvoiceDetails(invoiceID, userID uuid.UUID) (*invoices.Invoice, string, error) {
// Получить накладную
inv, err := s.invoiceRepo.GetByID(invoiceID)
if err != nil {
return nil, "", err
}
// Проверить, что пользователь имеет доступ к серверу накладной
server, err := s.accountRepo.GetActiveServer(userID)
if err != nil || server == nil {
return nil, "", errors.New("нет активного сервера")
}
if inv.RMSServerID != server.ID {
return nil, "", errors.New("накладная не принадлежит активному серверу")
}
// Попытаться найти черновик по rms_invoice_id
draft, err := s.draftRepo.GetByRMSInvoiceID(invoiceID)
if err != nil {
return nil, "", err
}
photoURL := ""
if draft != nil {
photoURL = draft.SenderPhotoURL
}
return inv, photoURL, nil
}

View File

@@ -8,20 +8,28 @@ import (
"github.com/shopspring/decimal"
"go.uber.org/zap"
"rmser/internal/domain/invoices"
"rmser/internal/domain/drafts"
invDomain "rmser/internal/domain/invoices"
"rmser/internal/domain/suppliers"
"rmser/internal/infrastructure/rms"
"rmser/pkg/logger"
)
type Service struct {
rmsClient rms.ClientI
repo invDomain.Repository
draftsRepo drafts.Repository
supplierRepo suppliers.Repository
rmsFactory *rms.Factory
// Здесь можно добавить репозитории каталога и контрагентов для валидации,
// но для краткости пока опустим глубокую валидацию.
}
func NewService(rmsClient rms.ClientI) *Service {
func NewService(repo invDomain.Repository, draftsRepo drafts.Repository, supplierRepo suppliers.Repository, rmsFactory *rms.Factory) *Service {
return &Service{
rmsClient: rmsClient,
repo: repo,
draftsRepo: draftsRepo,
supplierRepo: supplierRepo,
rmsFactory: rmsFactory,
}
}
@@ -39,7 +47,7 @@ type CreateRequestDTO struct {
}
// SendInvoiceToRMS валидирует DTO, собирает доменную модель и отправляет в RMS
func (s *Service) SendInvoiceToRMS(req CreateRequestDTO) (string, error) {
func (s *Service) SendInvoiceToRMS(req CreateRequestDTO, userID uuid.UUID) (string, error) {
// 1. Базовая валидация
if len(req.Items) == 0 {
return "", fmt.Errorf("список товаров пуст")
@@ -51,20 +59,20 @@ func (s *Service) SendInvoiceToRMS(req CreateRequestDTO) (string, error) {
}
// 2. Сборка доменной модели
inv := invoices.Invoice{
inv := invDomain.Invoice{
ID: uuid.Nil, // Новый документ
DocumentNumber: req.DocumentNumber,
DateIncoming: dateInc,
SupplierID: req.SupplierID,
DefaultStoreID: req.StoreID,
Status: "NEW",
Items: make([]invoices.InvoiceItem, 0, len(req.Items)),
Items: make([]invDomain.InvoiceItem, 0, len(req.Items)),
}
for _, itemDTO := range req.Items {
sum := itemDTO.Amount.Mul(itemDTO.Price) // Пересчитываем сумму
inv.Items = append(inv.Items, invoices.InvoiceItem{
inv.Items = append(inv.Items, invDomain.InvoiceItem{
ProductID: itemDTO.ProductID,
Amount: itemDTO.Amount,
Price: itemDTO.Price,
@@ -72,15 +80,89 @@ func (s *Service) SendInvoiceToRMS(req CreateRequestDTO) (string, error) {
})
}
// 3. Отправка через клиент
// 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 := s.rmsClient.CreateIncomingInvoice(inv)
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
}

View File

@@ -249,3 +249,14 @@ func (s *Service) GetUnmatchedItems(userID uuid.UUID) ([]ocr.UnmatchedItem, erro
}
return s.ocrRepo.GetTopUnmatched(server.ID, 50)
}
func (s *Service) DiscardUnmatched(userID uuid.UUID, rawName string) error {
server, err := s.accountRepo.GetActiveServer(userID)
if err != nil || server == nil {
return fmt.Errorf("no server")
}
if err := s.checkWriteAccess(userID, server.ID); err != nil {
return err
}
return s.ocrRepo.DeleteUnmatched(server.ID, rawName)
}

View File

@@ -160,10 +160,11 @@ func (h *DraftsHandler) UpdateItem(c *gin.Context) {
}
type CommitRequestDTO struct {
DateIncoming string `json:"date_incoming"` // YYYY-MM-DD
StoreID string `json:"store_id"`
SupplierID string `json:"supplier_id"`
Comment string `json:"comment"`
DateIncoming string `json:"date_incoming"` // YYYY-MM-DD
StoreID string `json:"store_id"`
SupplierID string `json:"supplier_id"`
Comment string `json:"comment"`
IncomingDocNum string `json:"incoming_doc_num"`
}
func (h *DraftsHandler) CommitDraft(c *gin.Context) {
@@ -196,7 +197,7 @@ func (h *DraftsHandler) CommitDraft(c *gin.Context) {
return
}
if err := h.service.UpdateDraftHeader(draftID, &storeID, &supplierID, date, req.Comment); err != nil {
if err := h.service.UpdateDraftHeader(draftID, &storeID, &supplierID, date, req.Comment, req.IncomingDocNum); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update header: " + err.Error()})
return
}

View File

@@ -4,14 +4,17 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
"rmser/internal/services/drafts"
invService "rmser/internal/services/invoices"
"rmser/pkg/logger"
)
type InvoiceHandler struct {
service *invService.Service
service *invService.Service
draftsService *drafts.Service
}
func NewInvoiceHandler(service *invService.Service) *InvoiceHandler {
@@ -35,7 +38,8 @@ func (h *InvoiceHandler) SendInvoice(c *gin.Context) {
return
}
docNum, err := h.service.SendInvoiceToRMS(req)
userID := c.MustGet("userID").(uuid.UUID)
docNum, err := h.service.SendInvoiceToRMS(req, userID)
if err != nil {
logger.Log.Error("Ошибка отправки накладной", zap.Error(err))
// Возвращаем 502 Bad Gateway, т.к. ошибка скорее всего на стороне RMS
@@ -48,3 +52,36 @@ func (h *InvoiceHandler) SendInvoice(c *gin.Context) {
"created_number": docNum,
})
}
// GetInvoice godoc
// @Summary Получить детали накладной
// @Description Возвращает детали синхронизированной накладной по ID
// @Tags invoices
// @Produce json
// @Param id path string true "Invoice ID"
// @Success 200 {object} invService.InvoiceDetailsDTO
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Failure 500 {object} map[string]string
func (h *InvoiceHandler) GetInvoice(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "ID накладной обязателен"})
return
}
uuidID, err := uuid.Parse(id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Неверный формат ID"})
return
}
dto, err := h.service.GetInvoice(uuidID)
if err != nil {
logger.Log.Error("Ошибка получения накладной", zap.Error(err), zap.String("id", id))
c.JSON(http.StatusInternalServerError, gin.H{"error": "Ошибка получения накладной"})
return
}
c.JSON(http.StatusOK, dto)
}

View File

@@ -132,3 +132,20 @@ func (h *OCRHandler) GetUnmatched(c *gin.Context) {
}
c.JSON(http.StatusOK, items)
}
func (h *OCRHandler) DiscardUnmatched(c *gin.Context) {
userID := c.MustGet("userID").(uuid.UUID)
rawName := c.Query("raw_name")
if rawName == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "raw_name is required"})
return
}
if err := h.service.DiscardUnmatched(userID, rawName); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"status": "discarded"})
}