Перевел на multi-tenant

Добавил поставщиков
Накладные успешно создаются из фронта
This commit is contained in:
2025-12-18 03:56:21 +03:00
parent 47ec8094e5
commit 542beafe0e
38 changed files with 1942 additions and 977 deletions

View File

@@ -20,6 +20,7 @@ import (
"rmser/internal/domain/catalog"
"rmser/internal/domain/invoices"
"rmser/internal/domain/recipes"
"rmser/internal/domain/suppliers"
"rmser/pkg/logger"
)
@@ -33,6 +34,7 @@ type ClientI interface {
Logout() error
FetchCatalog() ([]catalog.Product, error)
FetchStores() ([]catalog.Store, error)
FetchSuppliers() ([]suppliers.Supplier, error)
FetchMeasureUnits() ([]catalog.MeasureUnit, error)
FetchRecipes(dateFrom, dateTo time.Time) ([]recipes.Recipe, error)
FetchInvoices(from, to time.Time) ([]invoices.Invoice, error)
@@ -755,3 +757,39 @@ func (c *Client) UpdateProduct(product ProductFullDTO) (*ProductFullDTO, error)
return result.Response, nil
}
// FetchSuppliers загружает список поставщиков через XML API
func (c *Client) FetchSuppliers() ([]suppliers.Supplier, error) {
// Endpoint /resto/api/suppliers
resp, err := c.doRequest("GET", "/resto/api/suppliers", nil)
if err != nil {
return nil, fmt.Errorf("get suppliers error: %w", err)
}
defer resp.Body.Close()
var xmlData SuppliersListXML
if err := xml.NewDecoder(resp.Body).Decode(&xmlData); err != nil {
return nil, fmt.Errorf("xml decode suppliers error: %w", err)
}
var result []suppliers.Supplier
for _, emp := range xmlData.Employees {
id, err := uuid.Parse(emp.ID)
if err != nil {
continue
}
isDeleted := emp.Deleted == "true"
result = append(result, suppliers.Supplier{
ID: id,
Name: emp.Name,
Code: emp.Code,
INN: emp.TaxpayerIdNumber,
IsDeleted: isDeleted,
// RMSServerID проставляется в сервисе перед сохранением
})
}
return result, nil
}

View File

@@ -243,3 +243,18 @@ type ErrorDTO struct {
Code string `json:"code"`
Value string `json:"value"`
}
// --- Suppliers XML (Legacy API /resto/api/suppliers) ---
type SuppliersListXML struct {
XMLName xml.Name `xml:"employees"`
Employees []SupplierXML `xml:"employee"`
}
type SupplierXML struct {
ID string `xml:"id"`
Name string `xml:"name"`
Code string `xml:"code"`
TaxpayerIdNumber string `xml:"taxpayerIdNumber"` // ИНН
Deleted string `xml:"deleted"` // "true" / "false"
}

View File

@@ -0,0 +1,115 @@
package rms
import (
"fmt"
"sync"
"github.com/google/uuid"
"go.uber.org/zap"
"rmser/internal/domain/account"
"rmser/pkg/crypto"
"rmser/pkg/logger"
)
// Factory управляет созданием и переиспользованием клиентов RMS
type Factory struct {
accountRepo account.Repository
cryptoManager *crypto.CryptoManager
// Кэш активных клиентов: ServerID -> *Client
mu sync.RWMutex
clients map[uuid.UUID]*Client
}
func NewFactory(repo account.Repository, cm *crypto.CryptoManager) *Factory {
return &Factory{
accountRepo: repo,
cryptoManager: cm,
clients: make(map[uuid.UUID]*Client),
}
}
// GetClientByServerID возвращает готовый клиент для конкретного сервера
func (f *Factory) GetClientByServerID(serverID uuid.UUID) (ClientI, error) {
// 1. Пытаемся найти в кэше (быстрый путь)
f.mu.RLock()
client, exists := f.clients[serverID]
f.mu.RUnlock()
if exists {
return client, nil
}
// 2. Если нет в кэше - ищем в БД (медленный путь)
// Здесь нам нужен метод GetServerByID, но в репо есть только GetAll/GetActive.
// Для MVP загрузим все сервера юзера и найдем нужный, либо (лучше) добавим метод в репо позже.
// ПОКА: предполагаем, что factory используется в контексте User, поэтому лучше метод GetClientForUser
return nil, fmt.Errorf("client not found in cache (use GetClientForUser or implement GetServerByID)")
}
// GetClientForUser находит активный сервер пользователя и возвращает клиент
func (f *Factory) GetClientForUser(userID uuid.UUID) (ClientI, error) {
// 1. Получаем настройки активного сервера из БД
server, err := f.accountRepo.GetActiveServer(userID)
if err != nil {
return nil, fmt.Errorf("db error: %w", err)
}
if server == nil {
return nil, fmt.Errorf("у пользователя нет активного сервера RMS")
}
// 2. Проверяем кэш по ID сервера
f.mu.RLock()
cachedClient, exists := f.clients[server.ID]
f.mu.RUnlock()
if exists {
return cachedClient, nil
}
// 3. Создаем новый клиент под блокировкой (защита от гонки)
f.mu.Lock()
defer f.mu.Unlock()
// Double check
if cachedClient, exists := f.clients[server.ID]; exists {
return cachedClient, nil
}
// Расшифровка пароля
plainPass, err := f.cryptoManager.Decrypt(server.EncryptedPassword)
if err != nil {
return nil, fmt.Errorf("ошибка расшифровки пароля RMS: %w", err)
}
// Создание клиента
newClient := NewClient(server.BaseURL, server.Login, plainPass)
// Можно сразу проверить авторизацию (опционально, но полезно для fail-fast)
// if err := newClient.Auth(); err != nil { ... }
// Но лучше лениво, чтобы не тормозить старт.
f.clients[server.ID] = newClient
// Запускаем очистку старых клиентов из мапы? Пока нет, iiko токены живут не вечно,
// но структура Client легкая. Можно добавить TTL позже.
logger.Log.Info("RMS Factory: Client created and cached",
zap.String("server_name", server.Name),
zap.String("user_id", userID.String()))
return newClient, nil
}
// CreateClientFromRawCredentials создает клиент без сохранения в кэш (для тестов подключения)
func (f *Factory) CreateClientFromRawCredentials(url, login, password string) *Client {
return NewClient(url, login, password)
}
// ClearCache сбрасывает кэш для сервера (например, при смене пароля)
func (f *Factory) ClearCache(serverID uuid.UUID) {
f.mu.Lock()
defer f.mu.Unlock()
delete(f.clients, serverID)
}