0202-финиш перед десктопом

пересчет поправил
редактирование с перепроведением
галка автопроведения работает
рекомендации починил
This commit is contained in:
2026-02-02 13:53:38 +03:00
parent 10882f55c8
commit 88620f3fb6
37 changed files with 1905 additions and 11162 deletions

View File

@@ -3,12 +3,12 @@ package recommendations
import (
"fmt"
"strconv"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
"rmser/internal/domain/operations"
"rmser/internal/domain/recommendations"
"gorm.io/gorm"
)
type pgRepository struct {
@@ -21,11 +21,18 @@ func NewRepository(db *gorm.DB) recommendations.Repository {
// --- Методы Хранения ---
func (r *pgRepository) SaveAll(items []recommendations.Recommendation) error {
func (r *pgRepository) SaveAll(serverID uuid.UUID, items []recommendations.Recommendation) error {
return r.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Session(&gorm.Session{AllowGlobalUpdate: true}).Unscoped().Delete(&recommendations.Recommendation{}).Error; err != nil {
// Удаляем только записи ЭТОГО сервера
if err := tx.Where("rms_server_id = ?", serverID).Delete(&recommendations.Recommendation{}).Error; err != nil {
return err
}
// Проставляем server_id для всех записей
for i := range items {
items[i].RMSServerID = serverID
}
if len(items) > 0 {
if err := tx.CreateInBatches(items, 100).Error; err != nil {
return err
@@ -35,16 +42,16 @@ func (r *pgRepository) SaveAll(items []recommendations.Recommendation) error {
})
}
func (r *pgRepository) GetAll() ([]recommendations.Recommendation, error) {
func (r *pgRepository) GetAll(serverID uuid.UUID) ([]recommendations.Recommendation, error) {
var items []recommendations.Recommendation
err := r.db.Find(&items).Error
err := r.db.Where("rms_server_id = ?", serverID).Find(&items).Error
return items, err
}
// --- Методы Аналитики ---
// 1. Товары (GOODS/PREPARED), не используемые в техкартах
func (r *pgRepository) FindUnusedGoods() ([]recommendations.Recommendation, error) {
func (r *pgRepository) FindUnusedGoods(serverID uuid.UUID) ([]recommendations.Recommendation, error) {
var results []recommendations.Recommendation
query := `
@@ -54,27 +61,30 @@ func (r *pgRepository) FindUnusedGoods() ([]recommendations.Recommendation, erro
'Товар не используется ни в одной техкарте' as reason,
? as type
FROM products p
WHERE p.type IN ('GOODS', 'PREPARED')
AND p.is_deleted = false -- Проверка на удаление
WHERE p.rms_server_id = ?
AND p.type IN ('GOODS', 'PREPARED')
AND p.is_deleted = false
AND p.id NOT IN (
SELECT DISTINCT product_id FROM recipe_items
SELECT DISTINCT ri.product_id FROM recipe_items ri
JOIN recipes r ON ri.recipe_id = r.id
WHERE r.rms_server_id = ?
)
AND p.id NOT IN (
SELECT DISTINCT product_id FROM recipes
SELECT DISTINCT r.product_id FROM recipes r
WHERE r.rms_server_id = ?
)
ORDER BY p.name ASC
`
if err := r.db.Raw(query, recommendations.TypeUnused).Scan(&results).Error; err != nil {
if err := r.db.Raw(query, recommendations.TypeUnused, serverID, serverID, serverID).Scan(&results).Error; err != nil {
return nil, err
}
return results, nil
}
// 2. Закупается, но нет в техкартах
func (r *pgRepository) FindPurchasedButUnused(days int) ([]recommendations.Recommendation, error) {
func (r *pgRepository) FindPurchasedButUnused(serverID uuid.UUID, days int) ([]recommendations.Recommendation, error) {
var results []recommendations.Recommendation
dateFrom := time.Now().AddDate(0, 0, -days)
query := `
SELECT DISTINCT
@@ -84,26 +94,33 @@ func (r *pgRepository) FindPurchasedButUnused(days int) ([]recommendations.Recom
? as type
FROM store_operations so
JOIN products p ON so.product_id = p.id
WHERE
so.op_type = ?
AND so.period_from >= ?
AND p.is_deleted = false -- Проверка на удаление
AND p.id NOT IN (
SELECT DISTINCT product_id FROM recipe_items
WHERE
so.rms_server_id = ?
AND so.op_type = ?
AND so.period_to >= CURRENT_DATE - INTERVAL '1 day'
AND p.is_deleted = false
AND p.id NOT IN (
SELECT DISTINCT ri.product_id FROM recipe_items ri
JOIN recipes r ON ri.recipe_id = r.id
WHERE r.rms_server_id = ?
)
ORDER BY p.name ASC
`
if err := r.db.Raw(query, recommendations.TypePurchasedButUnused, operations.OpTypePurchase, dateFrom).Scan(&results).Error; err != nil {
if err := r.db.Raw(query,
recommendations.TypePurchasedButUnused,
serverID,
operations.OpTypePurchase,
serverID,
).Scan(&results).Error; err != nil {
return nil, err
}
return results, nil
}
// 3. Ингредиенты в актуальных техкартах без закупок
func (r *pgRepository) FindNoIncomingIngredients(days int) ([]recommendations.Recommendation, error) {
func (r *pgRepository) FindNoIncomingIngredients(serverID uuid.UUID, days int) ([]recommendations.Recommendation, error) {
var results []recommendations.Recommendation
dateFrom := time.Now().AddDate(0, 0, -days)
query := `
SELECT
@@ -115,31 +132,38 @@ func (r *pgRepository) FindNoIncomingIngredients(days int) ([]recommendations.Re
JOIN recipes r ON ri.recipe_id = r.id
JOIN products p ON ri.product_id = p.id
JOIN products parent ON r.product_id = parent.id
WHERE
(r.date_to IS NULL OR r.date_to >= CURRENT_DATE)
WHERE
r.rms_server_id = ?
AND (r.date_to IS NULL OR r.date_to >= CURRENT_DATE)
AND p.type = 'GOODS'
AND p.is_deleted = false -- Сам ингредиент не удален
AND parent.is_deleted = false -- Блюдо, в которое он входит, не удалено
AND p.is_deleted = false
AND parent.is_deleted = false
AND p.id NOT IN (
SELECT product_id
FROM store_operations
WHERE op_type = ?
AND period_from >= ?
SELECT so.product_id
FROM store_operations so
WHERE so.rms_server_id = ?
AND so.op_type = ?
AND so.period_to >= CURRENT_DATE - INTERVAL '1 day'
)
GROUP BY p.id, p.name
ORDER BY p.name ASC
`
if err := r.db.Raw(query, strconv.Itoa(days), recommendations.TypeNoIncoming, operations.OpTypePurchase, dateFrom).Scan(&results).Error; err != nil {
if err := r.db.Raw(query,
strconv.Itoa(days),
recommendations.TypeNoIncoming,
serverID,
serverID,
operations.OpTypePurchase,
).Scan(&results).Error; err != nil {
return nil, err
}
return results, nil
}
// 4. Товары, которые закупаем, но не расходуем ("Висяки")
func (r *pgRepository) FindStaleGoods(days int) ([]recommendations.Recommendation, error) {
func (r *pgRepository) FindStaleGoods(serverID uuid.UUID, days int) ([]recommendations.Recommendation, error) {
var results []recommendations.Recommendation
dateFrom := time.Now().AddDate(0, 0, -days)
query := `
SELECT DISTINCT
@@ -149,30 +173,38 @@ func (r *pgRepository) FindStaleGoods(days int) ([]recommendations.Recommendatio
? as type
FROM store_operations so
JOIN products p ON so.product_id = p.id
WHERE
so.op_type = ?
AND so.period_from >= ?
AND p.is_deleted = false -- Проверка на удаление
AND p.id NOT IN (
SELECT product_id
FROM store_operations
WHERE op_type = ?
AND period_from >= ?
WHERE
so.rms_server_id = ?
AND so.op_type = ?
AND so.period_to >= CURRENT_DATE - INTERVAL '1 day'
AND p.is_deleted = false
AND p.id NOT IN (
SELECT so2.product_id
FROM store_operations so2
WHERE so2.rms_server_id = ?
AND so2.op_type = ?
AND so2.period_to >= CURRENT_DATE - INTERVAL '1 day'
)
ORDER BY p.name ASC
`
reason := fmt.Sprintf("Были закупки, но нет расхода за %d дн.", days)
if err := r.db.Raw(query, reason, recommendations.TypeStale, operations.OpTypePurchase, dateFrom, operations.OpTypeUsage, dateFrom).
Scan(&results).Error; err != nil {
if err := r.db.Raw(query,
reason,
recommendations.TypeStale,
serverID,
operations.OpTypePurchase,
serverID,
operations.OpTypeUsage,
).Scan(&results).Error; err != nil {
return nil, err
}
return results, nil
}
// 5. Блюдо используется в техкарте другого блюда
func (r *pgRepository) FindDishesInRecipes() ([]recommendations.Recommendation, error) {
func (r *pgRepository) FindDishesInRecipes(serverID uuid.UUID) ([]recommendations.Recommendation, error) {
var results []recommendations.Recommendation
query := `
@@ -186,23 +218,23 @@ func (r *pgRepository) FindDishesInRecipes() ([]recommendations.Recommendation,
JOIN recipes r ON ri.recipe_id = r.id
JOIN products parent ON r.product_id = parent.id
WHERE
child.type = 'DISH'
AND child.is_deleted = false -- Вложенное блюдо не удалено
AND parent.is_deleted = false -- Родительское блюдо не удалено
r.rms_server_id = ?
AND child.type = 'DISH'
AND child.is_deleted = false
AND parent.is_deleted = false
AND (r.date_to IS NULL OR r.date_to >= CURRENT_DATE)
ORDER BY child.name ASC
`
if err := r.db.Raw(query, recommendations.TypeDishInRecipe).Scan(&results).Error; err != nil {
if err := r.db.Raw(query, recommendations.TypeDishInRecipe, serverID).Scan(&results).Error; err != nil {
return nil, err
}
return results, nil
}
// 6. Есть расход (Usage), но нет прихода (Purchase)
func (r *pgRepository) FindUsageWithoutPurchase(days int) ([]recommendations.Recommendation, error) {
func (r *pgRepository) FindUsageWithoutPurchase(serverID uuid.UUID, days int) ([]recommendations.Recommendation, error) {
var results []recommendations.Recommendation
dateFrom := time.Now().AddDate(0, 0, -days)
query := `
SELECT DISTINCT
@@ -212,30 +244,31 @@ func (r *pgRepository) FindUsageWithoutPurchase(days int) ([]recommendations.Rec
? as type
FROM store_operations so
JOIN products p ON so.product_id = p.id
WHERE
so.op_type = ? -- Есть расход (продажа/списание)
AND so.period_from >= ?
AND p.type = 'GOODS' -- Только для товаров
AND p.is_deleted = false -- Товар жив
AND p.id NOT IN ( -- Но не было закупок
SELECT product_id
FROM store_operations
WHERE op_type = ?
AND period_from >= ?
WHERE
so.rms_server_id = ?
AND so.op_type = ?
AND so.period_to >= CURRENT_DATE - INTERVAL '1 day'
AND p.type = 'GOODS'
AND p.is_deleted = false
AND p.id NOT IN (
SELECT so2.product_id
FROM store_operations so2
WHERE so2.rms_server_id = ?
AND so2.op_type = ?
AND so2.period_to >= CURRENT_DATE - INTERVAL '1 day'
)
ORDER BY p.name ASC
`
reason := fmt.Sprintf("Товар расходуется (продажи/списания), но не закупался последние %d дн.", days)
// Аргументы: reason, type, OpUsage, date, OpPurchase, date
if err := r.db.Raw(query,
reason,
recommendations.TypeUsageNoIncoming,
serverID,
operations.OpTypeUsage,
dateFrom,
serverID,
operations.OpTypePurchase,
dateFrom,
).Scan(&results).Error; err != nil {
return nil, err
}