mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
0202-финиш перед десктопом
пересчет поправил редактирование с перепроведением галка автопроведения работает рекомендации починил
This commit is contained in:
@@ -7,8 +7,10 @@ import (
|
||||
"time"
|
||||
|
||||
"rmser/internal/domain/account"
|
||||
"rmser/pkg/logger"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -78,7 +80,8 @@ func (r *pgRepository) ConnectServer(userID uuid.UUID, rawURL, login, encryptedP
|
||||
var created bool
|
||||
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
err := tx.Where("base_url = ?", cleanURL).First(&server).Error
|
||||
// Сначала ищем среди удаленных серверов
|
||||
err := tx.Unscoped().Where("base_url = ?", cleanURL).First(&server).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return err
|
||||
}
|
||||
@@ -100,8 +103,17 @@ func (r *pgRepository) ConnectServer(userID uuid.UUID, rawURL, login, encryptedP
|
||||
return err
|
||||
}
|
||||
created = true
|
||||
} else if server.DeletedAt.Valid {
|
||||
// --- СЦЕНАРИЙ 2: ВОССТАНОВЛЕНИЕ УДАЛЕННОГО СЕРВЕРА ---
|
||||
// Восстанавливаем сервер, сохраняя старые значения Balance, InvoiceCount и ID
|
||||
server.Name = name
|
||||
server.DeletedAt = gorm.DeletedAt{} // Сбрасываем deleted_at
|
||||
if err := tx.Save(&server).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
created = true // При восстановлении пользователь становится владельцем
|
||||
} else {
|
||||
// --- СЦЕНАРИЙ 2: СУЩЕСТВУЮЩИЙ СЕРВЕР ---
|
||||
// --- СЦЕНАРИЙ 3: СУЩЕСТВУЮЩИЙ АКТИВНЫЙ СЕРВЕР ---
|
||||
var userCount int64
|
||||
tx.Model(&account.ServerUser{}).Where("server_id = ?", server.ID).Count(&userCount)
|
||||
if userCount >= int64(server.MaxUsers) {
|
||||
@@ -156,9 +168,92 @@ func (r *pgRepository) SaveServerSettings(server *account.RMSServer) error {
|
||||
"root_group_guid": server.RootGroupGUID,
|
||||
"auto_process": server.AutoProcess,
|
||||
"max_users": server.MaxUsers,
|
||||
"sync_interval": server.SyncInterval,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// UpdateLastActivity обновляет время последней активности пользователя
|
||||
func (r *pgRepository) UpdateLastActivity(serverID uuid.UUID) error {
|
||||
result := r.db.Model(&account.RMSServer{}).
|
||||
Where("id = ?", serverID).
|
||||
Update("last_activity_at", gorm.Expr("NOW()"))
|
||||
|
||||
if result.Error != nil {
|
||||
logger.Log.Error("Failed to update last_activity_at",
|
||||
zap.String("server_id", serverID.String()),
|
||||
zap.Error(result.Error))
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
logger.Log.Warn("UpdateLastActivity: server not found",
|
||||
zap.String("server_id", serverID.String()))
|
||||
return fmt.Errorf("сервер не найден")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateLastSync обновляет время последней успешной синхронизации
|
||||
func (r *pgRepository) UpdateLastSync(serverID uuid.UUID) error {
|
||||
result := r.db.Model(&account.RMSServer{}).
|
||||
Where("id = ?", serverID).
|
||||
Update("last_sync_at", gorm.Expr("NOW()"))
|
||||
|
||||
if result.Error != nil {
|
||||
logger.Log.Error("Failed to update last_sync_at",
|
||||
zap.String("server_id", serverID.String()),
|
||||
zap.Error(result.Error))
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
logger.Log.Warn("UpdateLastSync: server not found",
|
||||
zap.String("server_id", serverID.String()))
|
||||
return fmt.Errorf("сервер не найден")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetServersForSync возвращает серверы, готовые для синхронизации
|
||||
func (r *pgRepository) GetServersForSync(idleThreshold time.Duration) ([]account.RMSServer, error) {
|
||||
var servers []account.RMSServer
|
||||
|
||||
// Конвертируем duration в минуты для SQL
|
||||
idleMinutes := int(idleThreshold.Minutes())
|
||||
|
||||
query := `
|
||||
SELECT * FROM rms_servers
|
||||
WHERE
|
||||
deleted_at IS NULL
|
||||
AND (
|
||||
-- Случай 1: Настало время периодической синхронизации
|
||||
(EXTRACT(EPOCH FROM (NOW() - COALESCE(last_sync_at, '1970-01-01'::timestamp))) / 60) >= sync_interval
|
||||
OR
|
||||
-- Случай 2: Прошло N мин с последней активности, и активность была ПОЗЖЕ синхронизации
|
||||
(
|
||||
last_activity_at > last_sync_at
|
||||
AND (EXTRACT(EPOCH FROM (NOW() - last_activity_at)) / 60) >= ?
|
||||
)
|
||||
)
|
||||
`
|
||||
|
||||
err := r.db.Raw(query, idleMinutes).Scan(&servers).Error
|
||||
if err != nil {
|
||||
logger.Log.Error("Failed to get servers for sync",
|
||||
zap.Int("idle_threshold_minutes", idleMinutes),
|
||||
zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Log.Info("Servers ready for sync",
|
||||
zap.Int("count", len(servers)),
|
||||
zap.Int("idle_threshold_minutes", idleMinutes))
|
||||
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
func (r *pgRepository) SetActiveServer(userID, serverID uuid.UUID) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// Проверка доступа
|
||||
@@ -252,7 +347,7 @@ func (r *pgRepository) GetAllAvailableServers(userID uuid.UUID) ([]account.RMSSe
|
||||
}
|
||||
|
||||
func (r *pgRepository) DeleteServer(serverID uuid.UUID) error {
|
||||
// Полное удаление сервера и всех связей
|
||||
// Мягкое удаление сервера и всех связей
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("server_id = ?", serverID).Delete(&account.ServerUser{}).Error; err != nil {
|
||||
return err
|
||||
|
||||
@@ -160,20 +160,23 @@ func (r *pgRepository) GetActive(serverID uuid.UUID) ([]drafts.DraftInvoice, err
|
||||
return list, err
|
||||
}
|
||||
|
||||
func (r *pgRepository) GetRMSInvoiceIDToPhotoURLMap(serverID uuid.UUID) (map[uuid.UUID]string, error) {
|
||||
func (r *pgRepository) GetLinkedDraftsMap(serverID uuid.UUID) (map[uuid.UUID]drafts.LinkedDraftInfo, error) {
|
||||
var draftsList []drafts.DraftInvoice
|
||||
err := r.db.
|
||||
Select("rms_invoice_id", "sender_photo_url").
|
||||
Select("id", "rms_invoice_id", "sender_photo_url").
|
||||
Where("rms_server_id = ? AND rms_invoice_id IS NOT NULL", serverID).
|
||||
Find(&draftsList).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[uuid.UUID]string)
|
||||
result := make(map[uuid.UUID]drafts.LinkedDraftInfo)
|
||||
for _, d := range draftsList {
|
||||
if d.RMSInvoiceID != nil {
|
||||
result[*d.RMSInvoiceID] = d.SenderPhotoURL
|
||||
result[*d.RMSInvoiceID] = drafts.LinkedDraftInfo{
|
||||
DraftID: d.ID,
|
||||
PhotoURL: d.SenderPhotoURL,
|
||||
}
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
|
||||
@@ -87,3 +87,17 @@ func (r *pgRepository) CountRecent(serverID uuid.UUID, days int) (int64, error)
|
||||
Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *pgRepository) GetStats(serverID uuid.UUID) (total int64, lastMonth int64, last24h int64, err error) {
|
||||
query := `
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE status != 'DELETED') as total,
|
||||
COUNT(*) FILTER (WHERE status != 'DELETED' AND created_at >= NOW() - INTERVAL '1 month') as last_month,
|
||||
COUNT(*) FILTER (WHERE status != 'DELETED' AND created_at >= NOW() - INTERVAL '24 hours') as last_24h
|
||||
FROM invoices
|
||||
WHERE rms_server_id = $1
|
||||
`
|
||||
|
||||
err = r.db.Raw(query, serverID).Row().Scan(&total, &lastMonth, &last24h)
|
||||
return total, lastMonth, last24h, err
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user