DFiP_Budget_planing/api/alembic/versions/sql/0018_technical_number.sql

232 lines
15 KiB
PL/PgSQL

-- ════════════════════════════════════════════════════════════════════════════
-- МИГРАЦИЯ: Автогенерация technical_number + запрет ручного редактирования
-- Формат: YYMM_<код РФ/ВСП 4 цифры><порядковый счётчик 2 цифры>
-- Пример: 2606_180801 (26 — год, 06 — месяц, 1808 — org_unit id, 01 — счётчик)
-- ════════════════════════════════════════════════════════════════════════════
-- Уникальность технического номера проекта
CREATE UNIQUE INDEX IF NOT EXISTS uq_v3_project_technical_number
ON v3.project (technical_number);
-- add_project: генерируем technical_number автоматически при создании проекта
CREATE OR REPLACE FUNCTION v3.add_project(p_name character varying, p_year integer, p_org_unit_id integer, p_level character varying DEFAULT 'project'::character varying, p_parent_id integer DEFAULT NULL::integer, p_project_type character varying DEFAULT NULL::character varying, p_vsp_format character varying DEFAULT NULL::character varying, p_placement_type character varying DEFAULT NULL::character varying, p_object_address character varying DEFAULT NULL::character varying, p_staff_count integer DEFAULT NULL::integer, p_total_area numeric DEFAULT NULL::numeric, p_system_code character varying DEFAULT NULL::character varying)
RETURNS TABLE(project_id integer, limit_report_id integer, current_expenses_report_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
v_pid INT;
v_lim INT;
v_cur INT;
v_seq INT;
v_technical_number VARCHAR;
BEGIN
IF p_name IS NULL OR length(trim(p_name)) = 0 THEN
RAISE EXCEPTION 'p_name обязателен';
END IF;
IF p_year IS NULL THEN
RAISE EXCEPTION 'p_year обязателен';
END IF;
IF p_org_unit_id IS NULL THEN
RAISE EXCEPTION 'p_org_unit_id обязателен';
END IF;
IF NOT EXISTS (SELECT 1 FROM v3.org_unit ou WHERE id = p_org_unit_id AND ou.is_active) THEN
RAISE EXCEPTION 'org_unit id=% не существует', p_org_unit_id;
END IF;
IF p_parent_id IS NOT NULL AND
NOT EXISTS (SELECT 1 FROM v3.project WHERE id = p_parent_id AND level = 'program') THEN
RAISE EXCEPTION 'parent project id=% не существует или не имеет level=program', p_parent_id;
END IF;
-- Порядковый счётчик проекта для данного РФ/ВСП (org_unit)
SELECT COALESCE(MAX(right(technical_number, 2)::int), 0) + 1
INTO v_seq
FROM v3.project
WHERE org_unit_id = p_org_unit_id
AND technical_number IS NOT NULL;
v_technical_number := to_char(now(), 'YYMM') || '_'
|| lpad(p_org_unit_id::text, 4, '0')
|| lpad(v_seq::text, 2, '0');
INSERT INTO v3.project (
name, level, parent_id, org_unit_id,
project_type, vsp_format, placement_type, object_address, staff_count, total_area,
system_code, technical_number
) VALUES (
p_name, p_level, p_parent_id, p_org_unit_id,
p_project_type, p_vsp_format, p_placement_type, p_object_address, p_staff_count, p_total_area,
p_system_code, v_technical_number
) RETURNING id INTO v_pid;
-- Первый год: 2 отчёта (LIMIT + CURRENT_EXPENSES) + snapshot фаз + сетка статей.
SELECT y.limit_report_id, y.current_expenses_report_id
INTO v_lim, v_cur
FROM v3.add_project_year(v_pid, p_year, false) y;
-- Аудит: PROJECT_CREATE — все заполненные поля + id отчётов
PERFORM v3.log_event(
'PROJECT_CREATE', 'PROJECT',
jsonb_strip_nulls(jsonb_build_object(
'project_id', v_pid,
'name', p_name,
'year', p_year,
'level', p_level,
'parent_id', p_parent_id,
'org_unit_id', p_org_unit_id,
'project_type', p_project_type,
'vsp_format', p_vsp_format,
'placement_type', p_placement_type,
'object_address', p_object_address,
'system_code', p_system_code,
'technical_number', v_technical_number,
'staff_count', p_staff_count,
'total_area', p_total_area,
'limit_report_id', v_lim,
'current_expenses_report_id', v_cur
))
);
RETURN QUERY SELECT v_pid, v_lim, v_cur;
END;
$function$
;
-- upd_project: убираем параметр technical_number (редактировать больше нельзя)
DROP FUNCTION IF EXISTS v3.upd_project(integer, varchar, varchar, varchar, varchar, varchar, varchar, varchar, integer, numeric, integer, date, date, date, date, varchar);
CREATE OR REPLACE FUNCTION v3.upd_project(
p_project_id integer,
p_name character varying DEFAULT NULL,
p_status character varying DEFAULT NULL,
p_project_type character varying DEFAULT NULL,
p_vsp_format character varying DEFAULT NULL,
p_placement_type character varying DEFAULT NULL,
p_object_address character varying DEFAULT NULL,
p_staff_count integer DEFAULT NULL,
p_total_area numeric DEFAULT NULL,
p_org_unit_id integer DEFAULT NULL,
p_krf_decision_date date DEFAULT NULL,
p_fk_decision_date date DEFAULT NULL,
p_board_decision_date date DEFAULT NULL,
p_open_relocate_close_date date DEFAULT NULL,
p_funding_by_ko_decision character varying DEFAULT NULL
)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
v_old v3.project%ROWTYPE;
v_changes JSONB := '{}'::jsonb;
BEGIN
SELECT * INTO v_old FROM v3.project WHERE id = p_project_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'project id=% не существует', p_project_id;
END IF;
IF p_name IS NOT NULL AND p_name IS DISTINCT FROM v_old.name THEN
UPDATE v3.project SET name = p_name WHERE id = p_project_id;
v_changes := v_changes || jsonb_build_object('name',
jsonb_build_object('before', v3._diff_val(v_old.name), 'after', v3._diff_val(p_name)));
END IF;
IF p_status IS NOT NULL AND p_status IS DISTINCT FROM v_old.status THEN
UPDATE v3.project SET status = p_status WHERE id = p_project_id;
v_changes := v_changes || jsonb_build_object('status',
jsonb_build_object('before', v3._diff_val(v_old.status), 'after', v3._diff_val(p_status)));
END IF;
IF p_project_type IS NOT NULL AND p_project_type IS DISTINCT FROM v_old.project_type THEN
UPDATE v3.project SET project_type = p_project_type WHERE id = p_project_id;
v_changes := v_changes || jsonb_build_object('project_type',
jsonb_build_object('before', v3._diff_val(v_old.project_type), 'after', v3._diff_val(p_project_type)));
END IF;
IF p_vsp_format IS NOT NULL AND p_vsp_format IS DISTINCT FROM v_old.vsp_format THEN
UPDATE v3.project SET vsp_format = p_vsp_format WHERE id = p_project_id;
v_changes := v_changes || jsonb_build_object('vsp_format',
jsonb_build_object('before', v3._diff_val(v_old.vsp_format), 'after', v3._diff_val(p_vsp_format)));
END IF;
IF p_placement_type IS NOT NULL AND p_placement_type IS DISTINCT FROM v_old.placement_type THEN
UPDATE v3.project SET placement_type = p_placement_type WHERE id = p_project_id;
v_changes := v_changes || jsonb_build_object('placement_type',
jsonb_build_object('before', v3._diff_val(v_old.placement_type), 'after', v3._diff_val(p_placement_type)));
END IF;
IF p_object_address IS NOT NULL AND p_object_address IS DISTINCT FROM v_old.object_address THEN
UPDATE v3.project SET object_address = p_object_address WHERE id = p_project_id;
v_changes := v_changes || jsonb_build_object('object_address',
jsonb_build_object('before', v3._diff_val(v_old.object_address), 'after', v3._diff_val(p_object_address)));
END IF;
IF p_staff_count IS NOT NULL AND p_staff_count IS DISTINCT FROM v_old.staff_count THEN
UPDATE v3.project SET staff_count = p_staff_count WHERE id = p_project_id;
v_changes := v_changes || jsonb_build_object('staff_count',
jsonb_build_object('before', v3._diff_val(v_old.staff_count), 'after', v3._diff_val(p_staff_count)));
END IF;
IF p_total_area IS NOT NULL AND p_total_area IS DISTINCT FROM v_old.total_area THEN
UPDATE v3.project SET total_area = p_total_area WHERE id = p_project_id;
v_changes := v_changes || jsonb_build_object('total_area',
jsonb_build_object('before', v3._diff_val(v_old.total_area), 'after', v3._diff_val(p_total_area)));
END IF;
IF p_org_unit_id IS NOT NULL AND p_org_unit_id IS DISTINCT FROM v_old.org_unit_id THEN
IF NOT EXISTS (SELECT 1 FROM v3.org_unit WHERE id = p_org_unit_id) THEN
RAISE EXCEPTION 'org_unit id=% не существует', p_org_unit_id;
END IF;
UPDATE v3.project SET org_unit_id = p_org_unit_id WHERE id = p_project_id;
v_changes := v_changes || jsonb_build_object('org_unit_id',
jsonb_build_object('before', v3._diff_val(v_old.org_unit_id), 'after', v3._diff_val(p_org_unit_id)));
END IF;
IF p_krf_decision_date IS NOT NULL AND p_krf_decision_date IS DISTINCT FROM v_old.krf_decision_date THEN
UPDATE v3.project SET krf_decision_date = p_krf_decision_date WHERE id = p_project_id;
v_changes := v_changes || jsonb_build_object('krf_decision_date',
jsonb_build_object('before', v3._diff_val(v_old.krf_decision_date), 'after', v3._diff_val(p_krf_decision_date)));
END IF;
IF p_fk_decision_date IS NOT NULL AND p_fk_decision_date IS DISTINCT FROM v_old.fk_decision_date THEN
UPDATE v3.project SET fk_decision_date = p_fk_decision_date WHERE id = p_project_id;
v_changes := v_changes || jsonb_build_object('fk_decision_date',
jsonb_build_object('before', v3._diff_val(v_old.fk_decision_date), 'after', v3._diff_val(p_fk_decision_date)));
END IF;
IF p_board_decision_date IS NOT NULL AND p_board_decision_date IS DISTINCT FROM v_old.board_decision_date THEN
UPDATE v3.project SET board_decision_date = p_board_decision_date WHERE id = p_project_id;
v_changes := v_changes || jsonb_build_object('board_decision_date',
jsonb_build_object('before', v3._diff_val(v_old.board_decision_date), 'after', v3._diff_val(p_board_decision_date)));
END IF;
IF p_open_relocate_close_date IS NOT NULL AND p_open_relocate_close_date IS DISTINCT FROM v_old.open_relocate_close_date THEN
UPDATE v3.project SET open_relocate_close_date = p_open_relocate_close_date WHERE id = p_project_id;
v_changes := v_changes || jsonb_build_object('open_relocate_close_date',
jsonb_build_object('before', v3._diff_val(v_old.open_relocate_close_date), 'after', v3._diff_val(p_open_relocate_close_date)));
END IF;
IF p_funding_by_ko_decision IS NOT NULL AND p_funding_by_ko_decision IS DISTINCT FROM v_old.funding_by_ko_decision THEN
UPDATE v3.project SET funding_by_ko_decision = p_funding_by_ko_decision WHERE id = p_project_id;
v_changes := v_changes || jsonb_build_object('funding_by_ko_decision',
jsonb_build_object('before', v3._diff_val(v_old.funding_by_ko_decision), 'after', v3._diff_val(p_funding_by_ko_decision)));
END IF;
IF v_changes <> '{}'::jsonb THEN
PERFORM v3.log_event(
'PROJECT_UPDATE', 'PROJECT',
jsonb_build_object(
'project_id', p_project_id,
'entity_id', p_project_id,
'changes', v_changes
), null, null, COALESCE(p_org_unit_id, v_old.org_unit_id)
);
END IF;
END;
$function$
;
UPDATE v3.phase_template
SET column_keys = '{plan.q1,plan.q2,plan.q3,plan.q4,plan.year,plan.comment,contract_summary.total,contract_summary.reference,contract_summary.counterparty,contract_summary.deadline,contract_summary.comment,contract_summary.future_y1,contract_summary.future_y2,contract_summary.other_ssp,contract_summary.centralized_flag,allocation.contract_ref,allocation.allocation_purpose,seq_dfip.q1,seq_dfip.q2,seq_dfip.q3,seq_dfip.q4,seq_dfip.year,seq_dfip.justification,reserve.q1,reserve.q2,reserve.q3,reserve.q4,reserve.year,reserve.justification,approved.q1,approved.q2,approved.q3,approved.q4,approved.year,collegial.approved,collegial.protocol,collegial.note,ckk.ceiling,ckk.q1,ckk.q2,ckk.q3,ckk.q4,ckk.rf_schedule,ckk.deadline,ckk.proc_plan,ckk.proc_method,ckk.comment,contract.counterparty,contract.reference,contract.addenda,contract.subject,contract.currency,contract.ceiling,contract.q1,contract.q2,contract.q3,contract.q4,contract.rf_schedule,contract.vat_rate,contract.exchange_rate,contract.amount_foreign,contract.deadline,contract.scheme,contract.act,contract.comment,q1.adj_current,q1.adj_ssp,q1.adj_rf,q1.adj_reserve,q1.adj_comment,q1.corrected_plan,q1.pay_date,q1.pay_amount,q1.pay_ho,q1.pay_rf,q1.pay_comment,q1.pay_act,q1.booking,q1.actual_m1,q1.actual_m2,q1.actual_m3,q1.actual_quarter,q1.residual_after_booking,q1.residual_after_actual,q1.transfer_q2,q1.transfer_q3,q1.transfer_q4,q1.transfer_econ,q1.total,q2.target_change,q2.base_correction,q2.revision_inc,q2.revision_seq,q2.revision_comment,q2.new_plan,q2.adj_current,q2.adj_ssp,q2.adj_rf,q2.adj_reserve,q2.corrected_plan,q2.pay_date,q2.pay_amount,q2.pay_ho,q2.pay_rf,q2.pay_comment,q2.pay_act,q2.booking,q2.actual_m1,q2.actual_m2,q2.actual_m3,q2.actual_quarter,q2.residual_after_booking,q2.residual_after_actual,q2.transfer_q3,q2.transfer_q4,q2.transfer_econ,q2.total,q3.target_change,q3.base_correction,q3.revision_inc,q3.revision_seq,q3.revision_comment,q3.new_plan,q3.adj_current,q3.adj_ssp,q3.adj_rf,q3.adj_reserve,q3.corrected_plan,q3.pay_date,q3.pay_amount,q3.pay_ho,q3.pay_rf,q3.pay_comment,q3.pay_act,q3.booking,q3.actual_m1,q3.actual_m2,q3.actual_m3,q3.actual_quarter,q3.residual_after_booking,q3.residual_after_actual,q3.transfer_q4,q3.transfer_econ,q3.total,q4.target_change,q4.base_correction,q4.revision_inc,q4.revision_seq,q4.revision_comment,q4.new_plan,q4.adj_current,q4.adj_ssp,q4.adj_rf,q4.adj_reserve,q4.corrected_plan,q4.pay_date,q4.pay_amount,q4.pay_ho,q4.pay_rf,q4.pay_comment,q4.pay_act,q4.booking,q4.actual_m1,q4.actual_m2,q4.actual_m3,q4.actual_quarter,q4.actual_spod,q4.residual_after_booking,q4.residual_after_actual,q4.transfer_econ,q4.total,totals.fact_year}'
WHERE form_type = 'FORM_4';
ALTER TABLE v3.form_phase DROP CONSTRAINT chk_v4_form_phase_no_admin;