package catalog import ( "rmser/internal/domain/catalog" "github.com/google/uuid" "gorm.io/gorm" "gorm.io/gorm/clause" ) type pgRepository struct { db *gorm.DB } func NewRepository(db *gorm.DB) catalog.Repository { return &pgRepository{db: db} } // --- Запись (Save) --- // При сохранении мы предполагаем, что serverID уже проставлен в Entity в слое Service. // Но для надежности можно передавать serverID в метод Save, однако Service должен это контролировать. // Оставим контракт Save(products []Product), где внутри products уже заполнен RMSServerID. func (r *pgRepository) SaveMeasureUnits(units []catalog.MeasureUnit) error { if len(units) == 0 { return nil } return r.db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "id"}}, // ID глобально уникален (UUID), конфликтов между серверами не будет UpdateAll: true, }).CreateInBatches(units, 100).Error } func (r *pgRepository) SaveProducts(products []catalog.Product) error { sorted := sortProductsByHierarchy(products) return r.db.Transaction(func(tx *gorm.DB) error { // 1. Продукты if err := tx.Omit("Containers").Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "id"}}, UpdateAll: true, }).CreateInBatches(sorted, 100).Error; err != nil { return err } // 2. Контейнеры var allContainers []catalog.ProductContainer for _, p := range products { allContainers = append(allContainers, p.Containers...) } if len(allContainers) > 0 { if err := tx.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "id"}}, UpdateAll: true, }).CreateInBatches(allContainers, 100).Error; err != nil { return err } } return nil }) } func (r *pgRepository) SaveContainer(container catalog.ProductContainer) error { return r.db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "id"}}, UpdateAll: true, }).Create(&container).Error } func (r *pgRepository) SaveStores(stores []catalog.Store) error { if len(stores) == 0 { return nil } return r.db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "id"}}, UpdateAll: true, }).CreateInBatches(stores, 100).Error } // --- Чтение (Read) с фильтрацией по ServerID --- func (r *pgRepository) GetAll() ([]catalog.Product, error) { // Этот метод был legacy и грузил всё. Теперь он опасен без serverID. // Оставляем заглушку или удаляем. Лучше удалить из интерфейса, но пока вернем пустой список // чтобы не ломать сборку, пока не почистим вызовы. return nil, nil } func (r *pgRepository) GetActiveGoods(serverID uuid.UUID, rootGroupID *uuid.UUID) ([]catalog.Product, error) { var products []catalog.Product db := r.db.Preload("MainUnit").Preload("Containers"). Where("rms_server_id = ? AND is_deleted = ? AND type IN ?", serverID, false, []string{"GOODS"}) if rootGroupID != nil && *rootGroupID != uuid.Nil { // Используем Recursive CTE для поиска всех дочерних элементов папки subQuery := r.db.Raw(` WITH RECURSIVE subnodes AS ( SELECT id FROM products WHERE id = ? UNION ALL SELECT p.id FROM products p INNER JOIN subnodes s ON p.parent_id = s.id ) SELECT id FROM subnodes `, rootGroupID) db = db.Where("id IN (?)", subQuery) } err := db.Order("name ASC").Find(&products).Error return products, err } func (r *pgRepository) GetActiveStores(serverID uuid.UUID) ([]catalog.Store, error) { var stores []catalog.Store err := r.db.Where("rms_server_id = ? AND is_deleted = ?", serverID, false).Order("name ASC").Find(&stores).Error return stores, err } func (r *pgRepository) Search(serverID uuid.UUID, query string, rootGroupID *uuid.UUID) ([]catalog.Product, error) { var products []catalog.Product q := "%" + query + "%" db := r.db.Preload("MainUnit").Preload("Containers"). Where("rms_server_id = ? AND is_deleted = ? AND type = ?", serverID, false, "GOODS"). Where("(name ILIKE ? OR code ILIKE ? OR num ILIKE ?)", q, q, q) if rootGroupID != nil && *rootGroupID != uuid.Nil { subQuery := r.db.Raw(` WITH RECURSIVE subnodes AS ( SELECT id FROM products WHERE id = ? UNION ALL SELECT p.id FROM products p INNER JOIN subnodes s ON p.parent_id = s.id ) SELECT id FROM subnodes `, rootGroupID) db = db.Where("id IN (?)", subQuery) } err := db.Order("name ASC").Limit(20).Find(&products).Error return products, err } // sortProductsByHierarchy - вспомогательная функция, оставляем как есть (копипаст из старого файла) func sortProductsByHierarchy(products []catalog.Product) []catalog.Product { if len(products) == 0 { return products } childrenMap := make(map[uuid.UUID][]catalog.Product) var roots []catalog.Product allIDs := make(map[uuid.UUID]struct{}, len(products)) for _, p := range products { allIDs[p.ID] = struct{}{} } for _, p := range products { if p.ParentID == nil { roots = append(roots, p) } else { if _, exists := allIDs[*p.ParentID]; exists { childrenMap[*p.ParentID] = append(childrenMap[*p.ParentID], p) } else { roots = append(roots, p) } } } result := make([]catalog.Product, 0, len(products)) queue := roots for len(queue) > 0 { current := queue[0] queue = queue[1:] result = append(result, current) if children, ok := childrenMap[current.ID]; ok { queue = append(queue, children...) delete(childrenMap, current.ID) } } for _, remaining := range childrenMap { result = append(result, remaining...) } return result } func (r *pgRepository) CountGoods(serverID uuid.UUID) (int64, error) { var count int64 err := r.db.Model(&catalog.Product{}). Where("rms_server_id = ? AND type IN ? AND is_deleted = ?", serverID, []string{"GOODS"}, false). Count(&count).Error return count, err } func (r *pgRepository) CountStores(serverID uuid.UUID) (int64, error) { var count int64 err := r.db.Model(&catalog.Store{}). Where("rms_server_id = ? AND is_deleted = ?", serverID, false). Count(&count).Error return count, err } func (r *pgRepository) GetGroups(serverID uuid.UUID) ([]catalog.Product, error) { var groups []catalog.Product // iiko присылает группы с типом "GROUP" err := r.db.Where("rms_server_id = ? AND type = ? AND is_deleted = ?", serverID, "GROUP", false). Order("name ASC"). Find(&groups).Error return groups, err }