mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
179 lines
5.5 KiB
Go
179 lines
5.5 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) ([]catalog.Product, error) {
|
||
var products []catalog.Product
|
||
err := r.db.
|
||
Preload("MainUnit").
|
||
Preload("Containers").
|
||
Where("rms_server_id = ? AND is_deleted = ? AND type IN ?", serverID, false, []string{"GOODS"}).
|
||
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) ([]catalog.Product, error) {
|
||
var products []catalog.Product
|
||
q := "%" + query + "%"
|
||
|
||
err := 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).
|
||
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
|
||
}
|