initial commit
This commit is contained in:
306
migrations/00018_create_business_procedures.sql
Normal file
306
migrations/00018_create_business_procedures.sql
Normal 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
|
||||
Reference in New Issue
Block a user