initial commit

This commit is contained in:
2025-11-29 00:28:21 +05:00
parent 46229acc82
commit ec3b03a935
76 changed files with 13492 additions and 0 deletions

View File

@@ -0,0 +1,306 @@
-- +goose Up
-- +goose StatementBegin
-- =========================================
-- ФУНКЦИЯ: accept_volunteer_response
-- Принятие отклика волонтера с назначением на заявку
-- =========================================
CREATE OR REPLACE FUNCTION accept_volunteer_response(
p_response_id BIGINT,
p_requester_id BIGINT
)
RETURNS TABLE (
success BOOLEAN,
message TEXT,
out_request_id BIGINT,
out_volunteer_id BIGINT
) AS $$
DECLARE
v_request_id BIGINT;
v_volunteer_id BIGINT;
v_request_status request_status;
v_response_status VARCHAR(20);
v_assigned_volunteer_id BIGINT;
v_request_requester_id BIGINT;
BEGIN
-- Получаем информацию об отклике и связанной заявке
SELECT
vr.request_id,
vr.volunteer_id,
vr.status,
r.status,
r.assigned_volunteer_id,
r.requester_id
INTO
v_request_id,
v_volunteer_id,
v_response_status,
v_request_status,
v_assigned_volunteer_id,
v_request_requester_id
FROM volunteer_responses vr
JOIN requests r ON r.id = vr.request_id
WHERE vr.id = p_response_id
AND r.deleted_at IS NULL;
-- Проверка: отклик существует
IF v_request_id IS NULL THEN
RETURN QUERY SELECT FALSE, 'Volunteer response not found'::TEXT, NULL::BIGINT, NULL::BIGINT;
RETURN;
END IF;
-- Проверка: заявка принадлежит заявителю
IF v_request_requester_id != p_requester_id THEN
RETURN QUERY SELECT FALSE, 'You are not the owner of this request'::TEXT, v_request_id, v_volunteer_id;
RETURN;
END IF;
-- Проверка: заявка в статусе 'approved'
IF v_request_status != 'approved' THEN
RETURN QUERY SELECT FALSE, format('Request must be in approved status, current status: %s', v_request_status)::TEXT, v_request_id, v_volunteer_id;
RETURN;
END IF;
-- Проверка: отклик в статусе 'pending'
IF v_response_status != 'pending' THEN
RETURN QUERY SELECT FALSE, format('Response must be in pending status, current status: %s', v_response_status)::TEXT, v_request_id, v_volunteer_id;
RETURN;
END IF;
-- Проверка: заявка еще не взята другим волонтером
IF v_assigned_volunteer_id IS NOT NULL THEN
RETURN QUERY SELECT FALSE, 'Request already assigned to another volunteer'::TEXT, v_request_id, v_volunteer_id;
RETURN;
END IF;
-- Все проверки пройдены, выполняем операцию
-- 1. Принимаем отклик
UPDATE volunteer_responses SET
status = 'accepted',
accepted_at = CURRENT_TIMESTAMP,
updated_at = CURRENT_TIMESTAMP
WHERE id = p_response_id;
-- 2. Назначаем волонтера на заявку и меняем статус
UPDATE requests SET
assigned_volunteer_id = v_volunteer_id,
status = 'in_progress',
updated_at = CURRENT_TIMESTAMP
WHERE id = v_request_id;
-- 3. Отклоняем все остальные отклики на эту заявку
UPDATE volunteer_responses SET
status = 'rejected',
rejected_at = CURRENT_TIMESTAMP,
updated_at = CURRENT_TIMESTAMP
WHERE request_id = v_request_id
AND id != p_response_id
AND status = 'pending';
-- Триггер log_request_status_change автоматически создаст запись в request_status_history
RETURN QUERY SELECT TRUE, 'Volunteer response accepted successfully'::TEXT, v_request_id, v_volunteer_id;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION accept_volunteer_response IS 'Атомарное принятие отклика волонтера: обновление статусов заявки и отклика, отклонение остальных откликов. Предотвращает race conditions.';
-- =========================================
-- ФУНКЦИЯ: complete_request_with_rating
-- Завершение заявки с обязательным выставлением рейтинга
-- =========================================
CREATE OR REPLACE FUNCTION complete_request_with_rating(
p_request_id BIGINT,
p_requester_id BIGINT,
p_rating INTEGER,
p_comment TEXT DEFAULT NULL
)
RETURNS TABLE (
success BOOLEAN,
message TEXT,
out_rating_id BIGINT
) AS $$
DECLARE
v_request_status request_status;
v_volunteer_id BIGINT;
v_requester_id BIGINT;
v_response_id BIGINT;
v_rating_id BIGINT;
BEGIN
-- Получаем информацию о заявке
SELECT
r.status,
r.assigned_volunteer_id,
r.requester_id
INTO
v_request_status,
v_volunteer_id,
v_requester_id
FROM requests r
WHERE r.id = p_request_id
AND r.deleted_at IS NULL;
-- Проверка: заявка существует
IF v_request_status IS NULL THEN
RETURN QUERY SELECT FALSE, 'Request not found'::TEXT, 0::BIGINT;
RETURN;
END IF;
-- Проверка: заявка принадлежит заявителю
IF v_requester_id != p_requester_id THEN
RETURN QUERY SELECT FALSE, 'You are not the owner of this request'::TEXT, 0::BIGINT;
RETURN;
END IF;
-- Проверка: заявка в статусе 'in_progress'
IF v_request_status != 'in_progress' THEN
RETURN QUERY SELECT FALSE, format('Request must be in in_progress status, current status: %s', v_request_status)::TEXT, 0::BIGINT;
RETURN;
END IF;
-- Проверка: есть назначенный волонтер
IF v_volunteer_id IS NULL THEN
RETURN QUERY SELECT FALSE, 'Request has no assigned volunteer'::TEXT, 0::BIGINT;
RETURN;
END IF;
-- Проверка: рейтинг от 1 до 5
IF p_rating < 1 OR p_rating > 5 THEN
RETURN QUERY SELECT FALSE, 'Rating must be between 1 and 5'::TEXT, 0::BIGINT;
RETURN;
END IF;
-- Все проверки пройдены, выполняем операцию
-- 1. Завершаем заявку
UPDATE requests SET
status = 'completed',
completed_at = CURRENT_TIMESTAMP,
updated_at = CURRENT_TIMESTAMP
WHERE id = p_request_id;
-- 2. Получаем ID отклика (для связи с рейтингом)
SELECT id INTO v_response_id
FROM volunteer_responses
WHERE request_id = p_request_id
AND volunteer_id = v_volunteer_id
AND status = 'accepted'
LIMIT 1;
-- 3. Создаем рейтинг
INSERT INTO ratings (
volunteer_response_id,
volunteer_id,
requester_id,
request_id,
rating,
comment
) VALUES (
v_response_id,
v_volunteer_id,
p_requester_id,
p_request_id,
p_rating,
p_comment
) RETURNING id INTO v_rating_id;
-- Триггер update_volunteer_rating автоматически пересчитает рейтинг волонтера
-- Триггер log_request_status_change автоматически создаст запись в request_status_history
RETURN QUERY SELECT TRUE, 'Request completed and rating saved successfully'::TEXT, v_rating_id;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION complete_request_with_rating IS 'Атомарное завершение заявки с обязательным выставлением рейтинга волонтеру. Триггер автоматически пересчитает средний рейтинг волонтера.';
-- =========================================
-- ФУНКЦИЯ: moderate_request
-- Универсальная функция модерации заявок
-- =========================================
CREATE OR REPLACE FUNCTION moderate_request(
p_request_id BIGINT,
p_moderator_id BIGINT,
p_action TEXT, -- 'approve' или 'reject'
p_comment TEXT DEFAULT NULL
)
RETURNS TABLE (
success BOOLEAN,
message TEXT
) AS $$
DECLARE
v_request_status request_status;
v_has_permission BOOLEAN;
v_new_status request_status;
BEGIN
-- Проверка: модератор имеет право модерировать
SELECT has_permission(p_moderator_id, 'moderate_requests') INTO v_has_permission;
IF NOT v_has_permission THEN
RETURN QUERY SELECT FALSE, 'You do not have permission to moderate requests'::TEXT;
RETURN;
END IF;
-- Получаем текущий статус заявки
SELECT status INTO v_request_status
FROM requests
WHERE id = p_request_id
AND deleted_at IS NULL;
-- Проверка: заявка существует
IF v_request_status IS NULL THEN
RETURN QUERY SELECT FALSE, 'Request not found'::TEXT;
RETURN;
END IF;
-- Проверка: заявка в статусе 'pending_moderation'
IF v_request_status != 'pending_moderation' THEN
RETURN QUERY SELECT FALSE, format('Request must be in pending_moderation status, current status: %s', v_request_status)::TEXT;
RETURN;
END IF;
-- Определяем новый статус
IF p_action = 'approve' THEN
v_new_status := 'approved';
ELSIF p_action = 'reject' THEN
v_new_status := 'rejected';
-- При отклонении комментарий обязателен
IF p_comment IS NULL OR trim(p_comment) = '' THEN
RETURN QUERY SELECT FALSE, 'Comment is required when rejecting a request'::TEXT;
RETURN;
END IF;
ELSE
RETURN QUERY SELECT FALSE, format('Invalid action: %s. Must be approve or reject', p_action)::TEXT;
RETURN;
END IF;
-- Все проверки пройдены, выполняем модерацию
UPDATE requests SET
status = v_new_status,
moderated_by = p_moderator_id,
moderated_at = CURRENT_TIMESTAMP,
moderation_comment = p_comment,
updated_at = CURRENT_TIMESTAMP
WHERE id = p_request_id;
-- Триггер audit_moderation_action автоматически создаст запись в moderator_actions
-- Триггер log_request_status_change автоматически создаст запись в request_status_history
RETURN QUERY SELECT TRUE, format('Request %s successfully', p_action)::TEXT;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION moderate_request IS 'Универсальная функция модерации заявок с проверкой прав, валидацией статуса и автоматическим аудитом через триггеры.';
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP FUNCTION IF EXISTS moderate_request;
DROP FUNCTION IF EXISTS complete_request_with_rating;
DROP FUNCTION IF EXISTS accept_volunteer_response;
-- +goose StatementEnd