mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
Перевел на multi-tenant
Добавил поставщиков Накладные успешно создаются из фронта
This commit is contained in:
63
internal/domain/account/entity.go
Normal file
63
internal/domain/account/entity.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// User - Пользователь системы (Telegram аккаунт)
|
||||
type User struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
TelegramID int64 `gorm:"uniqueIndex;not null" json:"telegram_id"`
|
||||
Username string `gorm:"type:varchar(100)" json:"username"`
|
||||
FirstName string `gorm:"type:varchar(100)" json:"first_name"`
|
||||
LastName string `gorm:"type:varchar(100)" json:"last_name"`
|
||||
PhotoURL string `gorm:"type:text" json:"photo_url"`
|
||||
|
||||
IsAdmin bool `gorm:"default:false" json:"is_admin"`
|
||||
|
||||
// Связь с серверами
|
||||
Servers []RMSServer `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE" json:"servers,omitempty"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// RMSServer - Настройки подключения к iikoRMS
|
||||
type RMSServer struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id"`
|
||||
|
||||
Name string `gorm:"type:varchar(100);not null" json:"name"` // Название (напр. "Ресторан на Ленина")
|
||||
|
||||
// Credentials
|
||||
BaseURL string `gorm:"type:varchar(255);not null" json:"base_url"`
|
||||
Login string `gorm:"type:varchar(100);not null" json:"login"`
|
||||
EncryptedPassword string `gorm:"type:text;not null" json:"-"` // Пароль храним зашифрованным
|
||||
|
||||
// Billing / Stats
|
||||
InvoiceCount int `gorm:"default:0" json:"invoice_count"` // Счетчик успешно отправленных накладных
|
||||
|
||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Repository интерфейс управления аккаунтами
|
||||
type Repository interface {
|
||||
// Users
|
||||
GetOrCreateUser(telegramID int64, username, first, last string) (*User, error)
|
||||
GetUserByTelegramID(telegramID int64) (*User, error)
|
||||
|
||||
// Servers
|
||||
SaveServer(server *RMSServer) error
|
||||
SetActiveServer(userID, serverID uuid.UUID) error
|
||||
GetActiveServer(userID uuid.UUID) (*RMSServer, error) // Получить активный (первый попавшийся или помеченный)
|
||||
GetAllServers(userID uuid.UUID) ([]RMSServer, error)
|
||||
DeleteServer(serverID uuid.UUID) error
|
||||
|
||||
// Billing
|
||||
IncrementInvoiceCount(serverID uuid.UUID) error
|
||||
}
|
||||
@@ -9,22 +9,25 @@ import (
|
||||
|
||||
// MeasureUnit - Единица измерения (kg, l, pcs)
|
||||
type MeasureUnit struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
|
||||
Name string `gorm:"type:varchar(50);not null" json:"name"`
|
||||
Code string `gorm:"type:varchar(50)" json:"code"`
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
|
||||
RMSServerID uuid.UUID `gorm:"type:uuid;not null;index" json:"-"`
|
||||
Name string `gorm:"type:varchar(50);not null" json:"name"`
|
||||
Code string `gorm:"type:varchar(50)" json:"code"`
|
||||
}
|
||||
|
||||
// ProductContainer - Фасовка (упаковка) товара
|
||||
type ProductContainer struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
|
||||
ProductID uuid.UUID `gorm:"type:uuid;index;not null" json:"product_id"`
|
||||
Name string `gorm:"type:varchar(100);not null" json:"name"`
|
||||
Count decimal.Decimal `gorm:"type:numeric(19,4);not null" json:"count"` // Коэфф. пересчета
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
|
||||
RMSServerID uuid.UUID `gorm:"type:uuid;not null;index" json:"-"`
|
||||
ProductID uuid.UUID `gorm:"type:uuid;index;not null" json:"product_id"`
|
||||
Name string `gorm:"type:varchar(100);not null" json:"name"`
|
||||
Count decimal.Decimal `gorm:"type:numeric(19,4);not null" json:"count"` // Коэфф. пересчета
|
||||
}
|
||||
|
||||
// Product - Номенклатура
|
||||
type Product struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
|
||||
RMSServerID uuid.UUID `gorm:"type:uuid;not null;index" json:"-"`
|
||||
ParentID *uuid.UUID `gorm:"type:uuid;index" json:"parent_id"`
|
||||
Name string `gorm:"type:varchar(255);not null" json:"name"`
|
||||
Type string `gorm:"type:varchar(50);index" json:"type"` // GOODS, DISH, PREPARED
|
||||
@@ -53,11 +56,14 @@ type Product struct {
|
||||
type Repository interface {
|
||||
SaveMeasureUnits(units []MeasureUnit) error
|
||||
SaveProducts(products []Product) error
|
||||
SaveContainer(container ProductContainer) error // Добавление фасовки
|
||||
Search(query string) ([]Product, error)
|
||||
GetAll() ([]Product, error)
|
||||
GetActiveGoods() ([]Product, error)
|
||||
// --- Stores ---
|
||||
SaveContainer(container ProductContainer) error
|
||||
|
||||
Search(serverID uuid.UUID, query string) ([]Product, error)
|
||||
GetActiveGoods(serverID uuid.UUID) ([]Product, error)
|
||||
|
||||
SaveStores(stores []Store) error
|
||||
GetActiveStores() ([]Store, error)
|
||||
GetActiveStores(serverID uuid.UUID) ([]Store, error)
|
||||
|
||||
CountGoods(serverID uuid.UUID) (int64, error)
|
||||
CountStores(serverID uuid.UUID) (int64, error)
|
||||
}
|
||||
|
||||
@@ -6,11 +6,13 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Store - Склад (в терминологии iiko: Entity с типом Account и подтипом INVENTORY_ASSETS)
|
||||
// Store - Склад
|
||||
type Store struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
|
||||
RMSServerID uuid.UUID `gorm:"type:uuid;not null;index" json:"-"`
|
||||
|
||||
Name string `gorm:"type:varchar(255);not null" json:"name"`
|
||||
ParentCorporateID uuid.UUID `gorm:"type:uuid;index" json:"parent_corporate_id"` // ID юр.лица/торгового предприятия
|
||||
ParentCorporateID uuid.UUID `gorm:"type:uuid;index" json:"parent_corporate_id"`
|
||||
IsDeleted bool `gorm:"default:false" json:"is_deleted"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
|
||||
@@ -9,31 +9,31 @@ import (
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// Статусы черновика
|
||||
const (
|
||||
StatusProcessing = "PROCESSING" // OCR в процессе
|
||||
StatusReadyToVerify = "READY_TO_VERIFY" // Распознано, ждет проверки пользователем
|
||||
StatusCompleted = "COMPLETED" // Отправлено в RMS
|
||||
StatusError = "ERROR" // Ошибка обработки
|
||||
StatusCanceled = "CANCELED" // Пользователь отменил
|
||||
StatusDeleted = "DELETED" // Пользователь удалил
|
||||
StatusProcessing = "PROCESSING"
|
||||
StatusReadyToVerify = "READY_TO_VERIFY"
|
||||
StatusCompleted = "COMPLETED"
|
||||
StatusError = "ERROR"
|
||||
StatusCanceled = "CANCELED"
|
||||
StatusDeleted = "DELETED"
|
||||
)
|
||||
|
||||
// DraftInvoice - Черновик накладной
|
||||
type DraftInvoice struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
ChatID int64 `gorm:"index" json:"chat_id"` // ID чата в Telegram (кто прислал)
|
||||
SenderPhotoURL string `gorm:"type:text" json:"photo_url"` // Ссылка на фото
|
||||
Status string `gorm:"type:varchar(50);default:'PROCESSING'" json:"status"`
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
|
||||
// Привязка к аккаунту и серверу
|
||||
UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id"`
|
||||
RMSServerID uuid.UUID `gorm:"type:uuid;not null;index" json:"rms_server_id"`
|
||||
|
||||
SenderPhotoURL string `gorm:"type:text" json:"photo_url"`
|
||||
Status string `gorm:"type:varchar(50);default:'PROCESSING'" json:"status"`
|
||||
|
||||
// Данные для отправки в RMS
|
||||
DocumentNumber string `gorm:"type:varchar(100)" json:"document_number"`
|
||||
DateIncoming *time.Time `json:"date_incoming"`
|
||||
SupplierID *uuid.UUID `gorm:"type:uuid" json:"supplier_id"`
|
||||
|
||||
StoreID *uuid.UUID `gorm:"type:uuid" json:"store_id"`
|
||||
// Связь со складом для Preload
|
||||
Store *catalog.Store `gorm:"foreignKey:StoreID" json:"store,omitempty"`
|
||||
StoreID *uuid.UUID `gorm:"type:uuid" json:"store_id"`
|
||||
Store *catalog.Store `gorm:"foreignKey:StoreID" json:"store,omitempty"`
|
||||
|
||||
Comment string `gorm:"type:text" json:"comment"`
|
||||
RMSInvoiceID *uuid.UUID `gorm:"type:uuid" json:"rms_invoice_id"`
|
||||
@@ -44,38 +44,32 @@ type DraftInvoice struct {
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// DraftInvoiceItem - Позиция черновика
|
||||
type DraftInvoiceItem struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
DraftID uuid.UUID `gorm:"type:uuid;not null;index" json:"draft_id"`
|
||||
|
||||
// --- Результаты OCR (Исходные данные) ---
|
||||
RawName string `gorm:"type:varchar(255);not null" json:"raw_name"` // Текст с чека
|
||||
RawAmount decimal.Decimal `gorm:"type:numeric(19,4)" json:"raw_amount"` // Кол-во, которое увидел OCR
|
||||
RawPrice decimal.Decimal `gorm:"type:numeric(19,4)" json:"raw_price"` // Цена, которую увидел OCR
|
||||
RawName string `gorm:"type:varchar(255);not null" json:"raw_name"`
|
||||
RawAmount decimal.Decimal `gorm:"type:numeric(19,4)" json:"raw_amount"`
|
||||
RawPrice decimal.Decimal `gorm:"type:numeric(19,4)" json:"raw_price"`
|
||||
|
||||
// --- Результат Матчинга и Выбора пользователя ---
|
||||
ProductID *uuid.UUID `gorm:"type:uuid;index" json:"product_id"`
|
||||
Product *catalog.Product `gorm:"foreignKey:ProductID" json:"product,omitempty"`
|
||||
ContainerID *uuid.UUID `gorm:"type:uuid;index" json:"container_id"`
|
||||
Container *catalog.ProductContainer `gorm:"foreignKey:ContainerID" json:"container,omitempty"`
|
||||
|
||||
// Финальные цифры, которые пойдут в накладную
|
||||
Quantity decimal.Decimal `gorm:"type:numeric(19,4);default:0" json:"quantity"`
|
||||
Price decimal.Decimal `gorm:"type:numeric(19,4);default:0" json:"price"`
|
||||
Sum decimal.Decimal `gorm:"type:numeric(19,4);default:0" json:"sum"`
|
||||
|
||||
IsMatched bool `gorm:"default:false" json:"is_matched"` // Удалось ли системе найти пару автоматически
|
||||
IsMatched bool `gorm:"default:false" json:"is_matched"`
|
||||
}
|
||||
|
||||
// Repository интерфейс
|
||||
type Repository interface {
|
||||
Create(draft *DraftInvoice) error
|
||||
GetByID(id uuid.UUID) (*DraftInvoice, error)
|
||||
Update(draft *DraftInvoice) error
|
||||
CreateItems(items []DraftInvoiceItem) error
|
||||
// UpdateItem обновляет конкретную строку (например, при ручном выборе товара)
|
||||
UpdateItem(itemID uuid.UUID, productID *uuid.UUID, containerID *uuid.UUID, qty, price decimal.Decimal) error
|
||||
Delete(id uuid.UUID) error
|
||||
GetActive() ([]DraftInvoice, error)
|
||||
GetActive(userID uuid.UUID) ([]DraftInvoice, error)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
// Invoice - Приходная накладная
|
||||
type Invoice struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;"`
|
||||
RMSServerID uuid.UUID `gorm:"type:uuid;not null;index"`
|
||||
DocumentNumber string `gorm:"type:varchar(100);index"`
|
||||
DateIncoming time.Time `gorm:"index"`
|
||||
SupplierID uuid.UUID `gorm:"type:uuid;index"`
|
||||
@@ -39,6 +40,7 @@ type InvoiceItem struct {
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
GetLastInvoiceDate() (*time.Time, error)
|
||||
GetLastInvoiceDate(serverID uuid.UUID) (*time.Time, error)
|
||||
SaveInvoices(invoices []Invoice) error
|
||||
CountRecent(serverID uuid.UUID, days int) (int64, error)
|
||||
}
|
||||
|
||||
@@ -9,38 +9,45 @@ import (
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// ProductMatch связывает текст из чека с конкретным товаром в iiko
|
||||
// ProductMatch
|
||||
type ProductMatch struct {
|
||||
RawName string `gorm:"type:varchar(255);primary_key" json:"raw_name"`
|
||||
// RawName больше не PrimaryKey, так как в разных серверах один текст может значить разное
|
||||
// Делаем составной ключ или суррогатный ID.
|
||||
// Для простоты GORM: ID - uuid, а уникальность через индекс (RawName + ServerID)
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()"`
|
||||
RMSServerID uuid.UUID `gorm:"type:uuid;not null;index:idx_raw_server,unique"`
|
||||
|
||||
RawName string `gorm:"type:varchar(255);not null;index:idx_raw_server,unique" json:"raw_name"`
|
||||
ProductID uuid.UUID `gorm:"type:uuid;not null;index" json:"product_id"`
|
||||
Product catalog.Product `gorm:"foreignKey:ProductID" json:"product"`
|
||||
|
||||
// Количество и фасовки
|
||||
Quantity decimal.Decimal `gorm:"type:numeric(19,4);default:1" json:"quantity"`
|
||||
ContainerID *uuid.UUID `gorm:"type:uuid;index" json:"container_id"`
|
||||
|
||||
// Для подгрузки данных о фасовке при чтении
|
||||
Container *catalog.ProductContainer `gorm:"foreignKey:ContainerID" json:"container,omitempty"`
|
||||
Quantity decimal.Decimal `gorm:"type:numeric(19,4);default:1" json:"quantity"`
|
||||
ContainerID *uuid.UUID `gorm:"type:uuid;index" json:"container_id"`
|
||||
Container *catalog.ProductContainer `gorm:"foreignKey:ContainerID" json:"container,omitempty"`
|
||||
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// UnmatchedItem хранит строки, которые не удалось распознать, для подсказок
|
||||
// UnmatchedItem тоже стоит делить, чтобы подсказывать пользователю только его нераспознанные,
|
||||
// хотя глобальная база unmatched может быть полезна для аналитики.
|
||||
// Сделаем раздельной для чистоты SaaS.
|
||||
type UnmatchedItem struct {
|
||||
RawName string `gorm:"type:varchar(255);primary_key" json:"raw_name"`
|
||||
Count int `gorm:"default:1" json:"count"` // Сколько раз встречалось
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()"`
|
||||
RMSServerID uuid.UUID `gorm:"type:uuid;not null;index:idx_unm_raw_server,unique"`
|
||||
|
||||
RawName string `gorm:"type:varchar(255);not null;index:idx_unm_raw_server,unique" json:"raw_name"`
|
||||
Count int `gorm:"default:1" json:"count"`
|
||||
LastSeen time.Time `json:"last_seen"`
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
// SaveMatch теперь принимает quantity и containerID
|
||||
SaveMatch(rawName string, productID uuid.UUID, quantity decimal.Decimal, containerID *uuid.UUID) error
|
||||
DeleteMatch(rawName string) error
|
||||
FindMatch(rawName string) (*ProductMatch, error) // Возвращаем полную структуру, чтобы получить qty
|
||||
GetAllMatches() ([]ProductMatch, error)
|
||||
SaveMatch(serverID uuid.UUID, rawName string, productID uuid.UUID, quantity decimal.Decimal, containerID *uuid.UUID) error
|
||||
DeleteMatch(serverID uuid.UUID, rawName string) error
|
||||
FindMatch(serverID uuid.UUID, rawName string) (*ProductMatch, error)
|
||||
GetAllMatches(serverID uuid.UUID) ([]ProductMatch, error)
|
||||
|
||||
UpsertUnmatched(rawName string) error
|
||||
GetTopUnmatched(limit int) ([]UnmatchedItem, error)
|
||||
DeleteUnmatched(rawName string) error
|
||||
UpsertUnmatched(serverID uuid.UUID, rawName string) error
|
||||
GetTopUnmatched(serverID uuid.UUID, limit int) ([]UnmatchedItem, error)
|
||||
DeleteUnmatched(serverID uuid.UUID, rawName string) error
|
||||
}
|
||||
|
||||
@@ -19,8 +19,9 @@ const (
|
||||
|
||||
// StoreOperation - запись из складского отчета
|
||||
type StoreOperation struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()"`
|
||||
ProductID uuid.UUID `gorm:"type:uuid;not null;index"`
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()"`
|
||||
RMSServerID uuid.UUID `gorm:"type:uuid;not null;index"`
|
||||
ProductID uuid.UUID `gorm:"type:uuid;not null;index"`
|
||||
|
||||
// Наш внутренний, "очищенный" тип операции
|
||||
OpType OperationType `gorm:"type:varchar(50);index"`
|
||||
@@ -44,5 +45,5 @@ type StoreOperation struct {
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
SaveOperations(ops []StoreOperation, opType OperationType, dateFrom, dateTo time.Time) error
|
||||
SaveOperations(ops []StoreOperation, serverID uuid.UUID, opType OperationType, dateFrom, dateTo time.Time) error
|
||||
}
|
||||
|
||||
@@ -11,10 +11,11 @@ import (
|
||||
|
||||
// Recipe - Технологическая карта
|
||||
type Recipe struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;"`
|
||||
ProductID uuid.UUID `gorm:"type:uuid;not null;index"`
|
||||
DateFrom time.Time `gorm:"index"`
|
||||
DateTo *time.Time
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;"`
|
||||
RMSServerID uuid.UUID `gorm:"type:uuid;not null;index"`
|
||||
ProductID uuid.UUID `gorm:"type:uuid;not null;index"`
|
||||
DateFrom time.Time `gorm:"index"`
|
||||
DateTo *time.Time
|
||||
|
||||
Product catalog.Product `gorm:"foreignKey:ProductID"`
|
||||
Items []RecipeItem `gorm:"foreignKey:RecipeID;constraint:OnDelete:CASCADE"`
|
||||
@@ -22,11 +23,12 @@ type Recipe struct {
|
||||
|
||||
// RecipeItem - Ингредиент
|
||||
type RecipeItem struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()"`
|
||||
RecipeID uuid.UUID `gorm:"type:uuid;not null;index"`
|
||||
ProductID uuid.UUID `gorm:"type:uuid;not null;index"`
|
||||
AmountIn decimal.Decimal `gorm:"type:numeric(19,4);not null"`
|
||||
AmountOut decimal.Decimal `gorm:"type:numeric(19,4);not null"`
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()"`
|
||||
RMSServerID uuid.UUID `gorm:"type:uuid;not null;index"`
|
||||
RecipeID uuid.UUID `gorm:"type:uuid;not null;index"`
|
||||
ProductID uuid.UUID `gorm:"type:uuid;not null;index"`
|
||||
AmountIn decimal.Decimal `gorm:"type:numeric(19,4);not null"`
|
||||
AmountOut decimal.Decimal `gorm:"type:numeric(19,4);not null"`
|
||||
|
||||
Product catalog.Product `gorm:"foreignKey:ProductID"`
|
||||
}
|
||||
|
||||
29
internal/domain/suppliers/entity.go
Normal file
29
internal/domain/suppliers/entity.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package suppliers
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Supplier - Поставщик (Контрагент)
|
||||
type Supplier struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
|
||||
Name string `gorm:"type:varchar(255);not null" json:"name"`
|
||||
Code string `gorm:"type:varchar(50)" json:"code"`
|
||||
INN string `gorm:"type:varchar(20)" json:"inn"` // taxpayerIdNumber
|
||||
|
||||
// Привязка к конкретному серверу iiko (Multi-tenant)
|
||||
RMSServerID uuid.UUID `gorm:"type:uuid;not null;index" json:"-"`
|
||||
|
||||
IsDeleted bool `gorm:"default:false" json:"is_deleted"`
|
||||
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
SaveBatch(suppliers []Supplier) error
|
||||
// GetRankedByUsage возвращает поставщиков, отсортированных по частоте использования в накладных за N дней
|
||||
GetRankedByUsage(serverID uuid.UUID, daysLookBack int) ([]Supplier, error)
|
||||
Count(serverID uuid.UUID) (int64, error)
|
||||
}
|
||||
Reference in New Issue
Block a user