mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
207 lines
6.6 KiB
Go
207 lines
6.6 KiB
Go
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
|
||
}
|