package ocr import ( "strings" "time" "gorm.io/gorm" "gorm.io/gorm/clause" "rmser/internal/domain/ocr" "github.com/google/uuid" "github.com/shopspring/decimal" ) type pgRepository struct { db *gorm.DB } func NewRepository(db *gorm.DB) ocr.Repository { return &pgRepository{db: db} } func (r *pgRepository) SaveMatch(serverID uuid.UUID, rawName string, productID uuid.UUID, quantity decimal.Decimal, containerID *uuid.UUID) error { normalized := strings.ToLower(strings.TrimSpace(rawName)) match := ocr.ProductMatch{ RMSServerID: serverID, RawName: normalized, ProductID: productID, Quantity: quantity, ContainerID: containerID, } return r.db.Transaction(func(tx *gorm.DB) error { // Используем OnConflict по составному индексу (raw_name, rms_server_id) // Но GORM может потребовать названия ограничения. // Проще сделать через Where().Assign().FirstOrCreate() или явно указать Columns если индекс есть. // В Entity мы указали `uniqueIndex:idx_raw_server`. if err := tx.Clauses(clause.OnConflict{ // Указываем оба поля, входящие в unique index Columns: []clause.Column{{Name: "raw_name"}, {Name: "rms_server_id"}}, DoUpdates: clause.AssignmentColumns([]string{"product_id", "quantity", "container_id", "updated_at"}), }).Create(&match).Error; err != nil { return err } // Удаляем из Unmatched для этого сервера if err := tx.Where("rms_server_id = ? AND raw_name = ?", serverID, normalized).Delete(&ocr.UnmatchedItem{}).Error; err != nil { return err } return nil }) } func (r *pgRepository) DeleteMatch(serverID uuid.UUID, rawName string) error { normalized := strings.ToLower(strings.TrimSpace(rawName)) return r.db.Where("rms_server_id = ? AND raw_name = ?", serverID, normalized).Delete(&ocr.ProductMatch{}).Error } func (r *pgRepository) FindMatch(serverID uuid.UUID, rawName string) (*ocr.ProductMatch, error) { normalized := strings.ToLower(strings.TrimSpace(rawName)) var match ocr.ProductMatch err := r.db.Preload("Container"). Where("rms_server_id = ? AND raw_name = ?", serverID, normalized). First(&match).Error if err != nil { if err == gorm.ErrRecordNotFound { return nil, nil } return nil, err } return &match, nil } func (r *pgRepository) GetAllMatches(serverID uuid.UUID) ([]ocr.ProductMatch, error) { var matches []ocr.ProductMatch err := r.db. Preload("Product"). Preload("Product.MainUnit"). Preload("Product.Containers"). Preload("Container"). Where("rms_server_id = ?", serverID). Order("updated_at DESC"). Find(&matches).Error return matches, err } func (r *pgRepository) UpsertUnmatched(serverID uuid.UUID, rawName string) error { normalized := strings.ToLower(strings.TrimSpace(rawName)) if normalized == "" { return nil } item := ocr.UnmatchedItem{ RMSServerID: serverID, RawName: normalized, Count: 1, LastSeen: time.Now(), } return r.db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "raw_name"}, {Name: "rms_server_id"}}, DoUpdates: clause.Assignments(map[string]interface{}{ "count": gorm.Expr("unmatched_items.count + 1"), "last_seen": time.Now(), }), }).Create(&item).Error } func (r *pgRepository) GetTopUnmatched(serverID uuid.UUID, limit int) ([]ocr.UnmatchedItem, error) { var items []ocr.UnmatchedItem err := r.db.Where("rms_server_id = ?", serverID). Order("count DESC, last_seen DESC"). Limit(limit). Find(&items).Error return items, err } func (r *pgRepository) DeleteUnmatched(serverID uuid.UUID, rawName string) error { normalized := strings.ToLower(strings.TrimSpace(rawName)) return r.db.Where("rms_server_id = ? AND raw_name = ?", serverID, normalized).Delete(&ocr.UnmatchedItem{}).Error }