Files
tp/main_dc/yalarba/api_yal/internal/domain/appeal/service.go
T
valitovgaziz 318075d686 On branch main
modified:   internal/domain/appeal/dto.go
	new file:   internal/domain/appeal/handler.go
	modified:   internal/domain/appeal/router.go
	modified:   internal/domain/appeal/service.go
	modified:   internal/models/appeal.go
	modified:   internal/router/router.go
fix bag with no embeded the Base into appeal
2026-05-21 05:04:34 +05:00

452 lines
13 KiB
Go

package appeal
import (
"errors"
"time"
"api_yal/internal/models"
"api_yal/internal/repository"
)
// AppealService интерфейс сервиса обращений
type AppealService interface {
// Create создает новое обращение
Create(req *CreateAppealRequest, authorID *uint, ipAddress, userAgent string) (*models.Appeal, error)
// GetByID возвращает обращение по ID
GetByID(id uint) (*models.Appeal, error)
// Update обновляет обращение
Update(id uint, req *UpdateAppealRequest, userID uint, isAdmin bool) (*models.Appeal, error)
// Delete удаляет обращение (мягкое удаление)
Delete(id uint, userID uint, isAdmin bool) error
// List возвращает список обращений
List(offset, limit int, filters map[string]interface{}) ([]models.Appeal, int64, error)
// UpdateStatus обновляет статус обращения
UpdateStatus(id uint, req *UpdateStatusRequest, userID uint) error
// AssignTo назначает ответственного
AssignTo(id uint, req *AssignRequest, userID uint) error
// Resolve решает обращение
Resolve(id uint, req *ResolveRequest, userID uint) error
// GetHistory возвращает историю изменений
GetHistory(appealID uint, offset, limit int) ([]models.AppealHistory, int64, error)
// GetStatistics возвращает статистику по обращениям
GetStatistics() (*AppealStatisticsResponse, error)
// GetMyAppeals возвращает обращения текущего пользователя
GetMyAppeals(userID uint, offset, limit int) ([]models.Appeal, int64, error)
// GetAppealsByAuthor возвращает обращения автора
GetAppealsByAuthor(authorID uint, offset, limit int) ([]models.Appeal, int64, error)
}
type appealServiceImpl struct {
appealRepo repository.AppealRepository
}
// NewAppealService создает новый экземпляр сервиса обращений
func NewAppealService(appealRepo repository.AppealRepository) AppealService {
return &appealServiceImpl{
appealRepo: appealRepo,
}
}
// Create создает новое обращение
func (s *appealServiceImpl) Create(req *CreateAppealRequest, authorID *uint, ipAddress, userAgent string) (*models.Appeal, error) {
// Валидация
if req.Title == "" {
return nil, errors.New("title is required")
}
if req.Message == "" {
return nil, errors.New("message is required")
}
// Конвертация типов
appealType := models.AppealType(req.Type)
if !isValidAppealType(appealType) {
return nil, errors.New("invalid appeal type")
}
priority := models.AppealPriority(req.Priority)
if req.Priority == "" {
priority = models.AppealPriorityMedium
}
if !isValidAppealPriority(priority) {
return nil, errors.New("invalid appeal priority")
}
appeal := &models.Appeal{
Type: appealType,
Status: models.AppealStatusNew,
Priority: priority,
Title: req.Title,
Message: req.Message,
AuthorID: authorID,
ObjectID: req.ObjectID,
FeedbackID: req.FeedbackID,
CommentID: req.CommentID,
ContactName: req.ContactName,
ContactEmail: req.ContactEmail,
ContactPhone: req.ContactPhone,
Attachments: req.Attachments,
Category: req.Category,
Labels: req.Labels,
CustomData: req.CustomData,
IPAddress: ipAddress,
UserAgent: userAgent,
}
if err := s.appealRepo.Create(appeal); err != nil {
return nil, err
}
// Создаем запись в истории
history := &models.AppealHistory{
AppealID: appeal.ID,
UserID: authorID,
OldStatus: "",
NewStatus: models.AppealStatusNew,
Comment: "Обращение создано",
}
_ = s.appealRepo.CreateHistory(history)
return appeal, nil
}
// GetByID возвращает обращение по ID
func (s *appealServiceImpl) GetByID(id uint) (*models.Appeal, error) {
return s.appealRepo.GetByID(id)
}
// Update обновляет обращение
func (s *appealServiceImpl) Update(id uint, req *UpdateAppealRequest, userID uint, isAdmin bool) (*models.Appeal, error) {
appeal, err := s.appealRepo.GetByID(id)
if err != nil {
return nil, err
}
// Проверка прав: только автор или админ может редактировать
if !isAdmin && (appeal.AuthorID == nil || *appeal.AuthorID != userID) {
return nil, errors.New("permission denied")
}
// Только новые обращения можно редактировать
if !isAdmin && appeal.Status != models.AppealStatusNew {
return nil, errors.New("only new appeals can be edited")
}
if req.Title != nil {
appeal.Title = *req.Title
}
if req.Message != nil {
appeal.Message = *req.Message
}
if req.Priority != nil {
priority := models.AppealPriority(*req.Priority)
if isValidAppealPriority(priority) {
appeal.Priority = priority
}
}
if req.Category != nil {
appeal.Category = *req.Category
}
if req.Labels != nil {
appeal.Labels = req.Labels
}
if req.Attachments != nil {
appeal.Attachments = req.Attachments
}
if req.CustomData != nil {
appeal.CustomData = req.CustomData
}
if err := s.appealRepo.Update(appeal); err != nil {
return nil, err
}
return appeal, nil
}
// Delete удаляет обращение
func (s *appealServiceImpl) Delete(id uint, userID uint, isAdmin bool) error {
appeal, err := s.appealRepo.GetByID(id)
if err != nil {
return err
}
// Проверка прав: только автор или админ может удалить
if !isAdmin && (appeal.AuthorID == nil || *appeal.AuthorID != userID) {
return errors.New("permission denied")
}
return s.appealRepo.Delete(id)
}
// List возвращает список обращений
func (s *appealServiceImpl) List(offset, limit int, filters map[string]interface{}) ([]models.Appeal, int64, error) {
if offset < 0 {
offset = 0
}
if limit <= 0 || limit > 100 {
limit = 20
}
var appeals []models.Appeal
var total int64
var err error
// Применяем фильтры
if status, ok := filters["status"].(string); ok {
appeals, err = s.appealRepo.ListByStatus(models.AppealStatus(status), offset, limit)
if err == nil {
total, _ = s.appealRepo.Count()
}
} else if appealType, ok := filters["type"].(string); ok {
appeals, err = s.appealRepo.ListByType(models.AppealType(appealType), offset, limit)
if err == nil {
total, _ = s.appealRepo.Count()
}
} else if priority, ok := filters["priority"].(string); ok {
appeals, err = s.appealRepo.ListByPriority(models.AppealPriority(priority), offset, limit)
if err == nil {
total, _ = s.appealRepo.Count()
}
} else if query, ok := filters["search"].(string); ok && query != "" {
appeals, err = s.appealRepo.Search(query, offset, limit)
if err == nil {
total = int64(len(appeals))
}
} else {
appeals, err = s.appealRepo.List(offset, limit)
if err == nil {
total, _ = s.appealRepo.Count()
}
}
if err != nil {
return nil, 0, err
}
return appeals, total, nil
}
// UpdateStatus обновляет статус обращения
func (s *appealServiceImpl) UpdateStatus(id uint, req *UpdateStatusRequest, userID uint) error {
appeal, err := s.appealRepo.GetByID(id)
if err != nil {
return err
}
oldStatus := appeal.Status
newStatus := models.AppealStatus(req.Status)
if !isValidAppealStatus(newStatus) {
return errors.New("invalid appeal status")
}
if err := s.appealRepo.UpdateStatus(id, newStatus); err != nil {
return err
}
// Создаем запись в истории
history := &models.AppealHistory{
AppealID: id,
UserID: &userID,
OldStatus: oldStatus,
NewStatus: newStatus,
Comment: req.Comment,
}
if err := s.appealRepo.CreateHistory(history); err != nil {
return err
}
return nil
}
// AssignTo назначает ответственного
func (s *appealServiceImpl) AssignTo(id uint, req *AssignRequest, userID uint) error {
appeal, err := s.appealRepo.GetByID(id)
if err != nil {
return err
}
if err := s.appealRepo.AssignTo(id, req.AssignedToID); err != nil {
return err
}
// Создаем запись в истории
comment := "Назначен ответственный"
if req.AssignedToID == nil {
comment = "Ответственный снят"
}
history := &models.AppealHistory{
AppealID: id,
UserID: &userID,
OldStatus: appeal.Status,
NewStatus: appeal.Status,
Comment: comment,
}
_ = s.appealRepo.CreateHistory(history)
return nil
}
// Resolve решает обращение
func (s *appealServiceImpl) Resolve(id uint, req *ResolveRequest, userID uint) error {
appeal, err := s.appealRepo.GetByID(id)
if err != nil {
return err
}
now := time.Now()
appeal.Status = models.AppealStatusResolved
appeal.ResolvedAt = &now
appeal.ResolvedBy = &userID
appeal.Resolution = req.Resolution
if err := s.appealRepo.Update(appeal); err != nil {
return err
}
// Создаем запись в истории
history := &models.AppealHistory{
AppealID: id,
UserID: &userID,
OldStatus: appeal.Status,
NewStatus: models.AppealStatusResolved,
Comment: "Обращение решено: " + req.Resolution,
}
if err := s.appealRepo.CreateHistory(history); err != nil {
return err
}
return nil
}
// GetHistory возвращает историю изменений
func (s *appealServiceImpl) GetHistory(appealID uint, offset, limit int) ([]models.AppealHistory, int64, error) {
if offset < 0 {
offset = 0
}
if limit <= 0 || limit > 100 {
limit = 20
}
histories, err := s.appealRepo.ListHistory(appealID, offset, limit)
if err != nil {
return nil, 0, err
}
// Для подсчета общего количества нужно реализовать отдельный метод
total := int64(len(histories))
return histories, total, nil
}
// GetStatistics возвращает статистику по обращениям
func (s *appealServiceImpl) GetStatistics() (*AppealStatisticsResponse, error) {
total, err := s.appealRepo.Count()
if err != nil {
return nil, err
}
// Здесь нужно добавить агрегационные запросы для статистики
// Для простоты возвращаем базовую структуру
stats := &AppealStatisticsResponse{
Total: total,
ByStatus: make(map[string]int64),
ByType: make(map[string]int64),
ByPriority: make(map[string]int64),
AvgResolveTime: 0,
}
// TODO: Реализовать подсчет статистики через raw SQL запросы
return stats, nil
}
// GetMyAppeals возвращает обращения текущего пользователя
func (s *appealServiceImpl) GetMyAppeals(userID uint, offset, limit int) ([]models.Appeal, int64, error) {
// Получаем все обращения и фильтруем по автору
allAppeals, err := s.appealRepo.List(0, 1000)
if err != nil {
return nil, 0, err
}
var myAppeals []models.Appeal
for _, appeal := range allAppeals {
if appeal.AuthorID != nil && *appeal.AuthorID == userID {
myAppeals = append(myAppeals, appeal)
}
}
total := int64(len(myAppeals))
start := offset
end := offset + limit
if start > len(myAppeals) {
return []models.Appeal{}, total, nil
}
if end > len(myAppeals) {
end = len(myAppeals)
}
return myAppeals[start:end], total, nil
}
// GetAppealsByAuthor возвращает обращения автора
func (s *appealServiceImpl) GetAppealsByAuthor(authorID uint, offset, limit int) ([]models.Appeal, int64, error) {
allAppeals, err := s.appealRepo.List(0, 1000)
if err != nil {
return nil, 0, err
}
var authorAppeals []models.Appeal
for _, appeal := range allAppeals {
if appeal.AuthorID != nil && *appeal.AuthorID == authorID {
authorAppeals = append(authorAppeals, appeal)
}
}
total := int64(len(authorAppeals))
start := offset
end := offset + limit
if start > len(authorAppeals) {
return []models.Appeal{}, total, nil
}
if end > len(authorAppeals) {
end = len(authorAppeals)
}
return authorAppeals[start:end], total, nil
}
// Вспомогательные функции валидации
func isValidAppealType(t models.AppealType) bool {
switch t {
case models.AppealTypeComplaint, models.AppealTypeSuggestion, models.AppealTypeWish, models.AppealTypeQuestion, models.AppealTypeOther:
return true
}
return false
}
func isValidAppealStatus(s models.AppealStatus) bool {
switch s {
case models.AppealStatusNew, models.AppealStatusInProgress, models.AppealStatusResolved, models.AppealStatusRejected, models.AppealStatusClosed:
return true
}
return false
}
func isValidAppealPriority(p models.AppealPriority) bool {
switch p {
case models.AppealPriorityLow, models.AppealPriorityMedium, models.AppealPriorityHigh, models.AppealPriorityCritical:
return true
}
return false
}