Files
backend/migrations/00018_create_business_procedures.sql
2025-12-13 22:34:01 +05:00

307 lines
11 KiB
PL/PgSQL
Raw 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.

-- +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