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} } 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"}}, 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...) } // 3. Сохраняем контейнеры 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) GetAll() ([]catalog.Product, error) { var products []catalog.Product err := r.db.Preload("MainUnit").Find(&products).Error return products, err } // Вспомогательная функция сортировки (оставляем как была) 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 } // GetActiveGoods возвращает только активные товары c подгруженной единицей измерения // GetActiveGoods оптимизирован: подгружаем Units и Containers func (r *pgRepository) GetActiveGoods() ([]catalog.Product, error) { var products []catalog.Product err := r.db. Preload("MainUnit"). Preload("Containers"). // <-- Подгружаем фасовки Where("is_deleted = ? AND type IN ?", false, []string{"GOODS"}). Order("name ASC"). Find(&products).Error return products, err } 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 } func (r *pgRepository) GetActiveStores() ([]catalog.Store, error) { var stores []catalog.Store err := r.db.Where("is_deleted = ?", false).Order("name ASC").Find(&stores).Error return stores, err } // SaveContainer сохраняет или обновляет одну фасовку func (r *pgRepository) SaveContainer(container catalog.ProductContainer) error { return r.db.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "id"}}, UpdateAll: true, }).Create(&container).Error } // Search ищет товары по названию, артикулу или коду (ILIKE) func (r *pgRepository) Search(query string) ([]catalog.Product, error) { var products []catalog.Product // Оборачиваем в проценты для поиска подстроки q := "%" + query + "%" err := r.db. Preload("MainUnit"). Preload("Containers"). // Обязательно грузим фасовки, они нужны для выбора Where("is_deleted = ? AND type = ?", false, "GOODS"). Where("name ILIKE ? OR code ILIKE ? OR num ILIKE ?", q, q, q). Order("name ASC"). Limit(20). // Ограничиваем выдачу, чтобы не перегружать фронт Find(&products).Error return products, err }