добавил архив фото, откуда можно удалить и посмотреть

This commit is contained in:
2026-01-19 05:17:15 +03:00
parent bc036197cf
commit 323fc67cd5
12 changed files with 648 additions and 26 deletions

View File

@@ -0,0 +1,119 @@
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)
}