package service import ( "context" "fmt" "git.kirlllll.ru/volontery/backend/internal/database" "git.kirlllll.ru/volontery/backend/internal/repository" "github.com/jackc/pgx/v5/pgtype" ) // RequestService предоставляет методы для работы с заявками type RequestService struct { requestRepo *repository.RequestRepository } // NewRequestService создает новый RequestService func NewRequestService(requestRepo *repository.RequestRepository) *RequestService { return &RequestService{ requestRepo: requestRepo, } } // CreateRequestInput - входные данные для создания заявки type CreateRequestInput struct { RequesterID int64 `json:"requester_id"` RequestTypeID int64 `json:"request_type_id"` Title string `json:"title"` Description string `json:"description"` Latitude float64 `json:"latitude"` Longitude float64 `json:"longitude"` Address string `json:"address"` City string `json:"city,omitempty"` DesiredCompletionDate *string `json:"desired_completion_date,omitempty"` Urgency string `json:"urgency"` ContactPhone string `json:"contact_phone,omitempty"` ContactNotes string `json:"contact_notes,omitempty"` } // CreateRequest создает новую заявку func (s *RequestService) CreateRequest(ctx context.Context, input CreateRequestInput) (*database.CreateRequestRow, error) { // Валидация if input.Title == "" { return nil, fmt.Errorf("title is required") } if input.Description == "" { return nil, fmt.Errorf("description is required") } if input.Latitude == 0 || input.Longitude == 0 { return nil, fmt.Errorf("location is required") } // Создание заявки return s.requestRepo.Create(ctx, database.CreateRequestParams{ RequesterID: input.RequesterID, RequestTypeID: input.RequestTypeID, Title: input.Title, Description: input.Description, StMakepoint: input.Longitude, StMakepoint_2: input.Latitude, Address: input.Address, City: stringToPgText(input.City), Urgency: stringToPgText(input.Urgency), ContactPhone: stringToPgText(input.ContactPhone), ContactNotes: stringToPgText(input.ContactNotes), }) } // GetRequest получает заявку по ID func (s *RequestService) GetRequest(ctx context.Context, id int64) (*database.GetRequestByIDRow, error) { return s.requestRepo.GetByID(ctx, id) } // GetUserRequests получает заявки пользователя func (s *RequestService) GetUserRequests(ctx context.Context, userID int64, limit, offset int32) ([]database.GetRequestsByRequesterRow, error) { return s.requestRepo.GetByRequester(ctx, database.GetRequestsByRequesterParams{ RequesterID: userID, Limit: limit, Offset: offset, }) } // FindNearbyRequests ищет заявки рядом с точкой func (s *RequestService) FindNearbyRequests(ctx context.Context, lat, lon float64, radiusMeters float64, statuses []database.RequestStatus, limit, offset int32) ([]database.FindRequestsNearbyRow, error) { // Конвертируем []RequestStatus в []string statusStrings := make([]string, len(statuses)) for i, status := range statuses { statusStrings[i] = string(status) } return s.requestRepo.FindNearby(ctx, database.FindRequestsNearbyParams{ StMakepoint: lon, StMakepoint_2: lat, Column3: statusStrings, StDwithin: radiusMeters, Limit: limit, Offset: offset, }) } // FindRequestsInBounds ищет заявки в прямоугольной области (для карты) func (s *RequestService) FindRequestsInBounds(ctx context.Context, statuses []database.RequestStatus, minLon, minLat, maxLon, maxLat float64) ([]database.FindRequestsInBoundsRow, error) { // Конвертируем []RequestStatus в []string statusStrings := make([]string, len(statuses)) for i, status := range statuses { statusStrings[i] = string(status) } return s.requestRepo.FindInBounds(ctx, database.FindRequestsInBoundsParams{ Column1: statusStrings, StMakeenvelope: minLon, StMakeenvelope_2: minLat, StMakeenvelope_3: maxLon, StMakeenvelope_4: maxLat, }) } // CreateVolunteerResponse создает отклик волонтера на заявку func (s *RequestService) CreateVolunteerResponse(ctx context.Context, requestID, volunteerID int64, message string) (*database.VolunteerResponse, error) { return s.requestRepo.CreateVolunteerResponse(ctx, database.CreateVolunteerResponseParams{ RequestID: requestID, VolunteerID: volunteerID, Message: stringToPgText(message), }) } // GetRequestResponses получает отклики на заявку func (s *RequestService) GetRequestResponses(ctx context.Context, requestID int64) ([]database.GetResponsesByRequestRow, error) { return s.requestRepo.GetResponsesByRequest(ctx, requestID) } // ListRequestTypes получает список типов заявок func (s *RequestService) ListRequestTypes(ctx context.Context) ([]database.RequestType, error) { return s.requestRepo.ListTypes(ctx) } // GetPendingModerationRequests получает заявки на модерации func (s *RequestService) GetPendingModerationRequests(ctx context.Context, limit, offset int32) ([]database.GetPendingModerationRequestsRow, error) { return s.requestRepo.GetPendingModerationRequests(ctx, limit, offset) } // ApproveRequest одобряет заявку func (s *RequestService) ApproveRequest(ctx context.Context, requestID, moderatorID int64, comment *string) error { moderationComment := stringToPgText("") if comment != nil { moderationComment = stringToPgText(*comment) } return s.requestRepo.ApproveRequest(ctx, database.ApproveRequestParams{ ID: requestID, ModeratedBy: pgtype.Int8{ Int64: moderatorID, Valid: true, }, ModerationComment: moderationComment, }) } // RejectRequest отклоняет заявку func (s *RequestService) RejectRequest(ctx context.Context, requestID, moderatorID int64, comment string) error { if comment == "" { return fmt.Errorf("rejection comment is required") } return s.requestRepo.RejectRequest(ctx, database.RejectRequestParams{ ID: requestID, ModeratedBy: pgtype.Int8{ Int64: moderatorID, Valid: true, }, ModerationComment: stringToPgText(comment), }) } // GetModeratedRequests получает заявки, модерированные указанным модератором func (s *RequestService) GetModeratedRequests(ctx context.Context, moderatorID int64, limit, offset int32) ([]database.GetModeratedRequestsRow, error) { return s.requestRepo.GetModeratedRequests(ctx, moderatorID, limit, offset) } // AcceptVolunteerResponse принимает отклик волонтера через хранимую процедуру func (s *RequestService) AcceptVolunteerResponse(ctx context.Context, responseID, requesterID int64) (*database.CallAcceptVolunteerResponseRow, error) { return s.requestRepo.AcceptVolunteerResponse(ctx, responseID, requesterID) } // CompleteRequestWithRating завершает заявку с рейтингом через хранимую процедуру func (s *RequestService) CompleteRequestWithRating(ctx context.Context, requestID, requesterID int64, rating int32, comment *string) (*database.CallCompleteRequestWithRatingRow, error) { if rating < 1 || rating > 5 { return nil, fmt.Errorf("rating must be between 1 and 5") } return s.requestRepo.CompleteRequestWithRating(ctx, requestID, requesterID, rating, comment) } // ModerateRequestProcedure модерирует заявку через хранимую процедуру func (s *RequestService) ModerateRequestProcedure(ctx context.Context, requestID, moderatorID int64, action string, comment *string) (*database.CallModerateRequestRow, error) { if action != "approve" && action != "reject" { return nil, fmt.Errorf("action must be 'approve' or 'reject'") } if action == "reject" && (comment == nil || *comment == "") { return nil, fmt.Errorf("comment is required when rejecting") } return s.requestRepo.ModerateRequestProcedure(ctx, requestID, moderatorID, action, comment) }