Files
rmser/internal/services/photos/service.go

120 lines
3.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package photos
import (
"errors"
"os"
"github.com/google/uuid"
"rmser/internal/domain/account"
"rmser/internal/domain/drafts"
"rmser/internal/domain/photos"
)
var (
ErrPhotoHasInvoice = errors.New("нельзя удалить фото: есть созданная накладная")
ErrPhotoHasDraft = errors.New("фото связано с черновиком")
)
type Service struct {
photoRepo photos.Repository
draftRepo drafts.Repository
accountRepo account.Repository
}
func NewService(photoRepo photos.Repository, draftRepo drafts.Repository, accountRepo account.Repository) *Service {
return &Service{
photoRepo: photoRepo,
draftRepo: draftRepo,
accountRepo: accountRepo,
}
}
// DTO для ответа
type PhotoWithStatus struct {
photos.ReceiptPhoto
Status photos.PhotoStatus `json:"status"`
CanDelete bool `json:"can_delete"`
CanRegenerate bool `json:"can_regenerate"`
}
func (s *Service) GetPhotosForServer(userID uuid.UUID, page, limit int) ([]PhotoWithStatus, int64, error) {
server, err := s.accountRepo.GetActiveServer(userID)
if err != nil || server == nil {
return nil, 0, errors.New("active server not found")
}
items, total, err := s.photoRepo.GetByServerID(server.ID, page, limit)
if err != nil {
return nil, 0, err
}
result := make([]PhotoWithStatus, len(items))
for i, photo := range items {
status := photos.PhotoStatusOrphan
if photo.InvoiceID != nil {
status = photos.PhotoStatusHasInvoice
} else if photo.DraftID != nil {
status = photos.PhotoStatusHasDraft
}
result[i] = PhotoWithStatus{
ReceiptPhoto: photo,
Status: status,
// Удалить можно только если нет накладной
CanDelete: photo.InvoiceID == nil,
// Пересоздать можно только если это "сирота"
CanRegenerate: photo.DraftID == nil && photo.InvoiceID == nil,
}
}
return result, total, nil
}
func (s *Service) DeletePhoto(userID, photoID uuid.UUID, forceDeleteDraft bool) error {
// Проверка прав
server, err := s.accountRepo.GetActiveServer(userID)
if err != nil || server == nil {
return errors.New("no active server")
}
// Операторы не могут удалять фото из архива (только админы)
role, _ := s.accountRepo.GetUserRole(userID, server.ID)
if role == account.RoleOperator {
return errors.New("access denied")
}
photo, err := s.photoRepo.GetByID(photoID)
if err != nil {
return err
}
// Проверка: есть накладная
if photo.InvoiceID != nil {
return ErrPhotoHasInvoice
}
// Проверка: есть черновик
if photo.DraftID != nil {
if !forceDeleteDraft {
return ErrPhotoHasDraft
}
// Если форсируем удаление - удаляем и черновик
if err := s.draftRepo.Delete(*photo.DraftID); err != nil {
return err
}
}
// Удаляем файл с диска (физически)
// В продакшене лучше делать это асинхронно или не делать вовсе (soft delete),
// но для экономии места удалим.
// Путь в БД может быть относительным или абсолютным, зависит от реализации загрузки.
// В ocr/service мы пишем абсолютный путь.
// Но в поле FilePath у нас путь.
if photo.FilePath != "" {
_ = os.Remove(photo.FilePath)
}
return s.photoRepo.Delete(photoID)
}