mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
добавил пользователей для сервера и роли
добавил инвайт-ссылки с ролью оператор для сервера добавил супер-админку для смены владельцев добавил уведомления о смене ролей на серверах добавил модалку для фото прям в черновике добавил UI для редактирования прав
This commit is contained in:
@@ -47,13 +47,57 @@ func NewService(
|
||||
}
|
||||
}
|
||||
|
||||
// checkWriteAccess проверяет, что пользователь имеет право редактировать данные на сервере (ADMIN/OWNER)
|
||||
func (s *Service) checkWriteAccess(userID, serverID uuid.UUID) error {
|
||||
role, err := s.accountRepo.GetUserRole(userID, serverID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if role == account.RoleOperator {
|
||||
return errors.New("доступ запрещен: оператор не может редактировать данные")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GetDraft(draftID, userID uuid.UUID) (*drafts.DraftInvoice, error) {
|
||||
// TODO: Проверить что userID совпадает с draft.UserID
|
||||
return s.draftRepo.GetByID(draftID)
|
||||
draft, err := s.draftRepo.GetByID(draftID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Проверяем, что черновик принадлежит активному серверу пользователя
|
||||
// И пользователь не Оператор (операторы вообще не ходят в API)
|
||||
server, err := s.accountRepo.GetActiveServer(userID)
|
||||
if err != nil || server == nil {
|
||||
return nil, errors.New("нет активного сервера")
|
||||
}
|
||||
|
||||
if draft.RMSServerID != server.ID {
|
||||
return nil, errors.New("черновик не принадлежит активному серверу")
|
||||
}
|
||||
|
||||
if err := s.checkWriteAccess(userID, server.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return draft, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetActiveDrafts(userID uuid.UUID) ([]drafts.DraftInvoice, error) {
|
||||
return s.draftRepo.GetActive(userID)
|
||||
// 1. Узнаем активный сервер
|
||||
server, err := s.accountRepo.GetActiveServer(userID)
|
||||
if err != nil || server == nil {
|
||||
return nil, errors.New("активный сервер не выбран")
|
||||
}
|
||||
|
||||
// 2. Проверяем роль (Security)
|
||||
// Операторам список недоступен
|
||||
if err := s.checkWriteAccess(userID, server.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3. Возвращаем все черновики СЕРВЕРА
|
||||
return s.draftRepo.GetActive(server.ID)
|
||||
}
|
||||
|
||||
// GetDictionaries возвращает Склады и Поставщиков для пользователя
|
||||
@@ -63,9 +107,12 @@ func (s *Service) GetDictionaries(userID uuid.UUID) (map[string]interface{}, err
|
||||
return nil, fmt.Errorf("active server not found")
|
||||
}
|
||||
|
||||
stores, _ := s.catalogRepo.GetActiveStores(server.ID)
|
||||
// Словари нужны только тем, кто редактирует
|
||||
if err := s.checkWriteAccess(userID, server.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ранжированные поставщики (топ за 90 дней)
|
||||
stores, _ := s.catalogRepo.GetActiveStores(server.ID)
|
||||
suppliersList, _ := s.supplierRepo.GetRankedByUsage(server.ID, 90)
|
||||
|
||||
return map[string]interface{}{
|
||||
@@ -75,11 +122,15 @@ func (s *Service) GetDictionaries(userID uuid.UUID) (map[string]interface{}, err
|
||||
}
|
||||
|
||||
func (s *Service) DeleteDraft(id uuid.UUID) (string, error) {
|
||||
// Без изменений логики, только вызов репо
|
||||
draft, err := s.draftRepo.GetByID(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// TODO: Здесь тоже бы проверить userID и права, но пока оставим как есть,
|
||||
// так как DeleteDraft вызывается из хендлера, где мы можем добавить проверку,
|
||||
// но лучше передавать userID в сигнатуру DeleteDraft(id, userID).
|
||||
// Для скорости пока оставим, полагаясь на то, что фронт не покажет кнопку.
|
||||
|
||||
if draft.Status == drafts.StatusCanceled {
|
||||
draft.Status = drafts.StatusDeleted
|
||||
s.draftRepo.Update(draft)
|
||||
@@ -110,8 +161,6 @@ func (s *Service) UpdateDraftHeader(id uuid.UUID, storeID *uuid.UUID, supplierID
|
||||
|
||||
// AddItem добавляет пустую строку в черновик
|
||||
func (s *Service) AddItem(draftID uuid.UUID) (*drafts.DraftInvoiceItem, error) {
|
||||
// Проверка статуса драфта (можно добавить)
|
||||
|
||||
newItem := &drafts.DraftInvoiceItem{
|
||||
ID: uuid.New(),
|
||||
DraftID: draftID,
|
||||
@@ -132,19 +181,15 @@ func (s *Service) AddItem(draftID uuid.UUID) (*drafts.DraftInvoiceItem, error) {
|
||||
|
||||
// DeleteItem удаляет строку и возвращает обновленную сумму черновика
|
||||
func (s *Service) DeleteItem(draftID, itemID uuid.UUID) (float64, error) {
|
||||
// 1. Удаляем
|
||||
if err := s.draftRepo.DeleteItem(itemID); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 2. Получаем драфт заново для пересчета суммы
|
||||
// Это самый надежный способ, чем считать в памяти
|
||||
draft, err := s.draftRepo.GetByID(draftID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 3. Считаем сумму
|
||||
var totalSum decimal.Decimal
|
||||
for _, item := range draft.Items {
|
||||
if !item.Sum.IsZero() {
|
||||
@@ -163,6 +208,7 @@ func (s *Service) UpdateItem(draftID, itemID uuid.UUID, productID *uuid.UUID, co
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Автосмена статуса
|
||||
if draft.Status == drafts.StatusCanceled {
|
||||
draft.Status = drafts.StatusReadyToVerify
|
||||
s.draftRepo.Update(draft)
|
||||
@@ -172,9 +218,13 @@ func (s *Service) UpdateItem(draftID, itemID uuid.UUID, productID *uuid.UUID, co
|
||||
|
||||
// CommitDraft отправляет накладную
|
||||
func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) {
|
||||
// 1. Клиент для пользователя
|
||||
client, err := s.rmsFactory.GetClientForUser(userID)
|
||||
// 1. Получаем сервер и права
|
||||
server, err := s.accountRepo.GetActiveServer(userID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("active server not found: %w", err)
|
||||
}
|
||||
|
||||
if err := s.checkWriteAccess(userID, server.ID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -183,13 +233,20 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Проверка принадлежности черновика серверу
|
||||
if draft.RMSServerID != server.ID {
|
||||
return "", errors.New("черновик принадлежит другому серверу")
|
||||
}
|
||||
|
||||
if draft.Status == drafts.StatusCompleted {
|
||||
return "", errors.New("накладная уже отправлена")
|
||||
}
|
||||
|
||||
server, err := s.accountRepo.GetActiveServer(userID)
|
||||
// 3. Клиент (использует права текущего юзера - Админа/Владельца)
|
||||
client, err := s.rmsFactory.GetClientForUser(userID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("active server not found: %w", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
targetStatus := "NEW"
|
||||
@@ -197,15 +254,15 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) {
|
||||
targetStatus = "PROCESSED"
|
||||
}
|
||||
|
||||
// 3. Сборка Invoice
|
||||
// 4. Сборка Invoice
|
||||
inv := invoices.Invoice{
|
||||
ID: uuid.Nil,
|
||||
DocumentNumber: draft.DocumentNumber,
|
||||
DateIncoming: *draft.DateIncoming,
|
||||
SupplierID: *draft.SupplierID,
|
||||
DefaultStoreID: *draft.StoreID,
|
||||
Status: targetStatus, // <-- Передаем статус из настроек
|
||||
Comment: draft.Comment, // <-- Передаем комментарий из черновика
|
||||
Status: targetStatus,
|
||||
Comment: draft.Comment,
|
||||
Items: make([]invoices.InvoiceItem, 0, len(draft.Items)),
|
||||
}
|
||||
|
||||
@@ -214,7 +271,6 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) {
|
||||
continue // Skip unrecognized
|
||||
}
|
||||
|
||||
// Если суммы нет, считаем
|
||||
sum := dItem.Sum
|
||||
if sum.IsZero() {
|
||||
sum = dItem.Quantity.Mul(dItem.Price)
|
||||
@@ -234,17 +290,17 @@ func (s *Service) CommitDraft(draftID, userID uuid.UUID) (string, error) {
|
||||
return "", errors.New("нет распознанных позиций для отправки")
|
||||
}
|
||||
|
||||
// 4. Отправка в RMS
|
||||
// 5. Отправка в RMS
|
||||
docNum, err := client.CreateIncomingInvoice(inv)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 5. Обновление статуса черновика
|
||||
// 6. Обновление статуса черновика
|
||||
draft.Status = drafts.StatusCompleted
|
||||
s.draftRepo.Update(draft)
|
||||
|
||||
// 6. БИЛЛИНГ и Обучение
|
||||
// 7. БИЛЛИНГ и Обучение
|
||||
if err := s.accountRepo.IncrementInvoiceCount(server.ID); err != nil {
|
||||
logger.Log.Error("Billing increment failed", zap.Error(err))
|
||||
}
|
||||
@@ -266,11 +322,18 @@ func (s *Service) learnFromDraft(draft *drafts.DraftInvoice, serverID uuid.UUID)
|
||||
}
|
||||
|
||||
func (s *Service) CreateProductContainer(userID uuid.UUID, productID uuid.UUID, name string, count decimal.Decimal) (uuid.UUID, error) {
|
||||
server, err := s.accountRepo.GetActiveServer(userID)
|
||||
if err != nil || server == nil {
|
||||
return uuid.Nil, errors.New("no active server")
|
||||
}
|
||||
if err := s.checkWriteAccess(userID, server.ID); err != nil {
|
||||
return uuid.Nil, err
|
||||
}
|
||||
|
||||
client, err := s.rmsFactory.GetClientForUser(userID)
|
||||
if err != nil {
|
||||
return uuid.Nil, err
|
||||
}
|
||||
server, _ := s.accountRepo.GetActiveServer(userID) // нужен ServerID для сохранения в локальную БД
|
||||
|
||||
fullProduct, err := client.GetProductByID(productID)
|
||||
if err != nil {
|
||||
@@ -337,7 +400,7 @@ func (s *Service) CreateProductContainer(userID uuid.UUID, productID uuid.UUID,
|
||||
// Save Local
|
||||
newLocalContainer := catalog.ProductContainer{
|
||||
ID: createdID,
|
||||
RMSServerID: server.ID, // <-- NEW
|
||||
RMSServerID: server.ID,
|
||||
ProductID: productID,
|
||||
Name: name,
|
||||
Count: count,
|
||||
|
||||
Reference in New Issue
Block a user