form1-sub_sub-fix: правки по вставке из сателитов

This commit is contained in:
tsygankoviva 2026-09-22 18:24:41 +03:00
parent a9fd54398b
commit 370e2e87fe

View File

@ -1,10 +1,3 @@
-- Fix sort.docx items 1-2:
-- - AHR RF depreciation and 5.00.0 tax block order.
-- - FORM_3 R061012024 order between R061012023 and R061012121.
-- - FORM_4 SMETA excludes sections absent from the project estimate template.
-- v3.mv_expense_item_tree sorts by expense_item.id path, so the catalog ids
-- must be renumbered together with all foreign-key references.
ALTER TABLE v3.budget_line DROP CONSTRAINT IF EXISTS budget_line_expense_item_fk; ALTER TABLE v3.budget_line DROP CONSTRAINT IF EXISTS budget_line_expense_item_fk;
ALTER TABLE v3.expense_item DROP CONSTRAINT IF EXISTS expense_item_expense_item_fk; ALTER TABLE v3.expense_item DROP CONSTRAINT IF EXISTS expense_item_expense_item_fk;
ALTER TABLE v3.expense_item_form_type DROP CONSTRAINT IF EXISTS expense_item_form_type_expense_item_fk; ALTER TABLE v3.expense_item_form_type DROP CONSTRAINT IF EXISTS expense_item_form_type_expense_item_fk;
@ -155,22 +148,779 @@ WHERE form_type_code = 'FORM_4'
800, 801, 802, 803, 804, 805, 806, 807, 808 800, 801, 802, 803, 804, 805, 806, 807, 808
); );
DO $block$ CREATE OR REPLACE FUNCTION v3.sync_form2_ahr_satellite_lines(p_form_id integer)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE DECLARE
v_function_definition TEXT; v_form_type varchar;
v_line_id integer;
v_rec record;
BEGIN BEGIN
SELECT pg_get_functiondef('v3.v_summary_view(integer, character varying, integer[])'::regprocedure) SELECT bf.form_type_code
INTO v_function_definition; INTO v_form_type
FROM v3.budget_form bf
WHERE bf.id = p_form_id;
v_function_definition := REPLACE( IF v_form_type IS DISTINCT FROM 'FORM_2' THEN
v_function_definition, RETURN;
'ROW_NUMBER() OVER (ORDER BY t.item_id)', END IF;
'ROW_NUMBER() OVER (ORDER BY t.path)'
);
EXECUTE v_function_definition;
FOR v_rec IN
WITH limit_amounts AS (
SELECT
ei.id AS expense_item_id,
regexp_replace(lt.name, '^[[:space:]]*-[[:space:]]*', '')::varchar AS line_name,
COALESCE(fl.qty_q1, 0) * COALESCE(lt.limit_with_vat, 0) *
(CASE WHEN lt.unit LIKE '%/год' THEN 0.25
WHEN lt.unit LIKE '%/месяц' THEN 3
ELSE 1 END) AS plan_q1,
COALESCE(fl.qty_q2, 0) * COALESCE(lt.limit_with_vat, 0) *
(CASE WHEN lt.unit LIKE '%/год' THEN 0.25
WHEN lt.unit LIKE '%/месяц' THEN 3
ELSE 1 END) AS plan_q2,
COALESCE(fl.qty_q3, 0) * COALESCE(lt.limit_with_vat, 0) *
(CASE WHEN lt.unit LIKE '%/год' THEN 0.25
WHEN lt.unit LIKE '%/месяц' THEN 3
ELSE 1 END) AS plan_q3,
COALESCE(fl.qty_q4, 0) * COALESCE(lt.limit_with_vat, 0) *
(CASE WHEN lt.unit LIKE '%/год' THEN 0.25
WHEN lt.unit LIKE '%/месяц' THEN 3
ELSE 1 END) AS plan_q4
FROM v3.limit_template lt
LEFT JOIN v3.form_limit fl
ON fl.template_id = lt.id
AND fl.budget_form_id = p_form_id
JOIN v3.expense_item ei
ON ei.sheet = 'AHR'
AND ei.direction = 'Support'
AND ei.depth = 3
AND (ei.item_id = lt.expense_item_code OR ei.num_group_id = lt.expense_item_code)
JOIN v3.expense_item_form_type eft
ON eft.expense_item_id = ei.id
AND eft.form_type_code = 'FORM_2'
WHERE lt.row_type = 'LEAF'
)
SELECT * FROM limit_amounts
LOOP
PERFORM v3._upsert_form2_ahr_limit_sync_values(
p_form_id, v_rec.expense_item_id, v_rec.line_name,
v_rec.plan_q1, v_rec.plan_q2, v_rec.plan_q3, v_rec.plan_q4
);
END LOOP;
FOR v_line_id IN
SELECT m.target_line_id
FROM v3.form2_ahr_satellite_line_source m
JOIN v3.budget_line bl ON bl.id = m.target_line_id
WHERE m.source_sheet = 'AHR_RENT'
AND bl.budget_form_id = p_form_id
AND NOT EXISTS (SELECT 1 FROM v3.rent_detail rd WHERE rd.id = m.source_id)
LOOP
DELETE FROM v3.plan WHERE line_id = v_line_id;
DELETE FROM v3.budget_line_quarter WHERE line_id = v_line_id;
DELETE FROM v3.budget_line WHERE id = v_line_id;
END LOOP;
FOR v_rec IN
SELECT
rd.id AS source_id,
rd.line_id AS existing_line_id,
rd.vsp_id,
COALESCE(v.format, 'АХР Аренда #' || rd.id)::varchar AS line_name,
rd.contract_number::varchar AS internal_order,
rd.plan_q1, rd.plan_q2, rd.plan_q3, rd.plan_q4,
rd.actual_jan, rd.actual_feb, rd.actual_mar,
rd.actual_apr, rd.actual_may, rd.actual_jun,
rd.actual_jul, rd.actual_aug, rd.actual_sep,
rd.actual_oct, rd.actual_nov, rd.actual_dec
FROM v3.rent_detail rd
JOIN v3.budget_line bl ON bl.id = rd.line_id
LEFT JOIN v3.vsp v ON v.id = rd.vsp_id
WHERE bl.budget_form_id = p_form_id
ORDER BY rd.id
LOOP
PERFORM v3._upsert_form2_ahr_detail_sync_values(
p_form_id, 314, 'AHR_RENT', v_rec.source_id,
v_rec.line_name, v_rec.internal_order, v_rec.vsp_id, v_rec.existing_line_id,
v_rec.plan_q1, v_rec.plan_q2, v_rec.plan_q3, v_rec.plan_q4,
v_rec.actual_jan, v_rec.actual_feb, v_rec.actual_mar,
v_rec.actual_apr, v_rec.actual_may, v_rec.actual_jun,
v_rec.actual_jul, v_rec.actual_aug, v_rec.actual_sep,
v_rec.actual_oct, v_rec.actual_nov, v_rec.actual_dec
);
UPDATE v3.rent_detail rd
SET line_id = m.target_line_id
FROM v3.form2_ahr_satellite_line_source m
WHERE rd.id = v_rec.source_id
AND m.source_sheet = 'AHR_RENT'
AND m.source_id = rd.id;
END LOOP;
FOR v_line_id IN
SELECT m.target_line_id
FROM v3.form2_ahr_satellite_line_source m
JOIN v3.budget_line bl ON bl.id = m.target_line_id
WHERE m.source_sheet = 'AHR_UTILITY'
AND bl.budget_form_id = p_form_id
AND NOT EXISTS (SELECT 1 FROM v3.utility_detail ud WHERE ud.id = m.source_id)
LOOP
DELETE FROM v3.plan WHERE line_id = v_line_id;
DELETE FROM v3.budget_line_quarter WHERE line_id = v_line_id;
DELETE FROM v3.budget_line WHERE id = v_line_id;
END LOOP;
FOR v_rec IN
SELECT
ud.id AS source_id,
ud.line_id AS existing_line_id,
ud.vsp_id,
COALESCE(v.format, 'АХР Коммунальные услуги #' || ud.id)::varchar AS line_name,
ud.contract_number::varchar AS internal_order,
ud.plan_q1, ud.plan_q2, ud.plan_q3, ud.plan_q4,
ud.actual_jan, ud.actual_feb, ud.actual_mar,
ud.actual_apr, ud.actual_may, ud.actual_jun,
ud.actual_jul, ud.actual_aug, ud.actual_sep,
ud.actual_oct, ud.actual_nov, ud.actual_dec
FROM v3.utility_detail ud
JOIN v3.budget_line bl ON bl.id = ud.line_id
LEFT JOIN v3.vsp v ON v.id = ud.vsp_id
WHERE bl.budget_form_id = p_form_id
ORDER BY ud.id
LOOP
PERFORM v3._upsert_form2_ahr_detail_sync_values(
p_form_id, 24, 'AHR_UTILITY', v_rec.source_id,
v_rec.line_name, v_rec.internal_order, v_rec.vsp_id, v_rec.existing_line_id,
v_rec.plan_q1, v_rec.plan_q2, v_rec.plan_q3, v_rec.plan_q4,
v_rec.actual_jan, v_rec.actual_feb, v_rec.actual_mar,
v_rec.actual_apr, v_rec.actual_may, v_rec.actual_jun,
v_rec.actual_jul, v_rec.actual_aug, v_rec.actual_sep,
v_rec.actual_oct, v_rec.actual_nov, v_rec.actual_dec
);
UPDATE v3.utility_detail ud
SET line_id = m.target_line_id
FROM v3.form2_ahr_satellite_line_source m
WHERE ud.id = v_rec.source_id
AND m.source_sheet = 'AHR_UTILITY'
AND m.source_id = ud.id;
END LOOP;
FOR v_line_id IN
SELECT m.target_line_id
FROM v3.form2_ahr_satellite_line_source m
JOIN v3.budget_line bl ON bl.id = m.target_line_id
WHERE m.source_sheet = 'AHR_SECURITY'
AND bl.budget_form_id = p_form_id
AND NOT EXISTS (SELECT 1 FROM v3.security_detail sd WHERE sd.id = m.source_id)
LOOP
DELETE FROM v3.plan WHERE line_id = v_line_id;
DELETE FROM v3.budget_line_quarter WHERE line_id = v_line_id;
DELETE FROM v3.budget_line WHERE id = v_line_id;
END LOOP;
FOR v_rec IN
SELECT
sd.id AS source_id,
sd.line_id AS existing_line_id,
sd.vsp_id,
COALESCE(v.format, 'АХР Безопасность #' || sd.id)::varchar AS line_name,
sd.contract_number::varchar AS internal_order,
sd.plan_q1, sd.plan_q2, sd.plan_q3, sd.plan_q4,
sd.actual_jan, sd.actual_feb, sd.actual_mar,
sd.actual_apr, sd.actual_may, sd.actual_jun,
sd.actual_jul, sd.actual_aug, sd.actual_sep,
sd.actual_oct, sd.actual_nov, sd.actual_dec
FROM v3.security_detail sd
JOIN v3.budget_line bl ON bl.id = sd.line_id
LEFT JOIN v3.vsp v ON v.id = sd.vsp_id
WHERE bl.budget_form_id = p_form_id
ORDER BY sd.id
LOOP
PERFORM v3._upsert_form2_ahr_detail_sync_values(
p_form_id, 779, 'AHR_SECURITY', v_rec.source_id,
v_rec.line_name, v_rec.internal_order, v_rec.vsp_id, v_rec.existing_line_id,
v_rec.plan_q1, v_rec.plan_q2, v_rec.plan_q3, v_rec.plan_q4,
v_rec.actual_jan, v_rec.actual_feb, v_rec.actual_mar,
v_rec.actual_apr, v_rec.actual_may, v_rec.actual_jun,
v_rec.actual_jul, v_rec.actual_aug, v_rec.actual_sep,
v_rec.actual_oct, v_rec.actual_nov, v_rec.actual_dec
);
UPDATE v3.security_detail sd
SET line_id = m.target_line_id
FROM v3.form2_ahr_satellite_line_source m
WHERE sd.id = v_rec.source_id
AND m.source_sheet = 'AHR_SECURITY'
AND m.source_id = sd.id;
END LOOP;
END; END;
$block$; $function$;
CREATE OR REPLACE FUNCTION v3.sync_form2_ahr_satellite_line(p_form_id integer, p_sheet character varying, p_source_id integer)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
v_rec record;
BEGIN
IF p_sheet = 'AHR_LIMIT' THEN
SELECT
ei.id AS expense_item_id,
regexp_replace(lt.name, '^[[:space:]]*-[[:space:]]*', '')::varchar AS line_name,
COALESCE(fl.qty_q1, 0) * COALESCE(lt.limit_with_vat, 0) *
(CASE WHEN lt.unit LIKE '%/год' THEN 0.25
WHEN lt.unit LIKE '%/месяц' THEN 3
ELSE 1 END) AS plan_q1,
COALESCE(fl.qty_q2, 0) * COALESCE(lt.limit_with_vat, 0) *
(CASE WHEN lt.unit LIKE '%/год' THEN 0.25
WHEN lt.unit LIKE '%/месяц' THEN 3
ELSE 1 END) AS plan_q2,
COALESCE(fl.qty_q3, 0) * COALESCE(lt.limit_with_vat, 0) *
(CASE WHEN lt.unit LIKE '%/год' THEN 0.25
WHEN lt.unit LIKE '%/месяц' THEN 3
ELSE 1 END) AS plan_q3,
COALESCE(fl.qty_q4, 0) * COALESCE(lt.limit_with_vat, 0) *
(CASE WHEN lt.unit LIKE '%/год' THEN 0.25
WHEN lt.unit LIKE '%/месяц' THEN 3
ELSE 1 END) AS plan_q4
INTO v_rec
FROM v3.limit_template lt
LEFT JOIN v3.form_limit fl
ON fl.template_id = lt.id
AND fl.budget_form_id = p_form_id
JOIN v3.expense_item ei
ON ei.sheet = 'AHR'
AND ei.direction = 'Support'
AND ei.depth = 3
AND (ei.item_id = lt.expense_item_code OR ei.num_group_id = lt.expense_item_code)
JOIN v3.expense_item_form_type eft
ON eft.expense_item_id = ei.id
AND eft.form_type_code = 'FORM_2'
WHERE lt.id = p_source_id
AND lt.row_type = 'LEAF';
IF v_rec.expense_item_id IS NOT NULL THEN
PERFORM v3._upsert_form2_ahr_limit_sync_values(
p_form_id, v_rec.expense_item_id, v_rec.line_name,
v_rec.plan_q1, v_rec.plan_q2, v_rec.plan_q3, v_rec.plan_q4
);
END IF;
RETURN;
END IF;
IF p_sheet = 'AHR_RENT' THEN
SELECT
rd.id AS source_id,
rd.line_id AS existing_line_id,
rd.vsp_id,
COALESCE(v.format, 'АХР Аренда #' || rd.id)::varchar AS line_name,
rd.contract_number::varchar AS internal_order,
rd.plan_q1, rd.plan_q2, rd.plan_q3, rd.plan_q4,
rd.actual_jan, rd.actual_feb, rd.actual_mar,
rd.actual_apr, rd.actual_may, rd.actual_jun,
rd.actual_jul, rd.actual_aug, rd.actual_sep,
rd.actual_oct, rd.actual_nov, rd.actual_dec
INTO v_rec
FROM v3.rent_detail rd
JOIN v3.budget_line bl ON bl.id = rd.line_id
LEFT JOIN v3.vsp v ON v.id = rd.vsp_id
WHERE rd.id = p_source_id
AND bl.budget_form_id = p_form_id;
IF v_rec.source_id IS NOT NULL THEN
PERFORM v3._upsert_form2_ahr_detail_sync_values(
p_form_id, 314, 'AHR_RENT', v_rec.source_id,
v_rec.line_name, v_rec.internal_order, v_rec.vsp_id, v_rec.existing_line_id,
v_rec.plan_q1, v_rec.plan_q2, v_rec.plan_q3, v_rec.plan_q4,
v_rec.actual_jan, v_rec.actual_feb, v_rec.actual_mar,
v_rec.actual_apr, v_rec.actual_may, v_rec.actual_jun,
v_rec.actual_jul, v_rec.actual_aug, v_rec.actual_sep,
v_rec.actual_oct, v_rec.actual_nov, v_rec.actual_dec
);
UPDATE v3.rent_detail rd
SET line_id = m.target_line_id
FROM v3.form2_ahr_satellite_line_source m
WHERE rd.id = v_rec.source_id
AND m.source_sheet = 'AHR_RENT'
AND m.source_id = rd.id;
END IF;
RETURN;
END IF;
IF p_sheet = 'AHR_UTILITY' THEN
SELECT
ud.id AS source_id,
ud.line_id AS existing_line_id,
ud.vsp_id,
COALESCE(v.format, 'АХР Коммунальные услуги #' || ud.id)::varchar AS line_name,
ud.contract_number::varchar AS internal_order,
ud.plan_q1, ud.plan_q2, ud.plan_q3, ud.plan_q4,
ud.actual_jan, ud.actual_feb, ud.actual_mar,
ud.actual_apr, ud.actual_may, ud.actual_jun,
ud.actual_jul, ud.actual_aug, ud.actual_sep,
ud.actual_oct, ud.actual_nov, ud.actual_dec
INTO v_rec
FROM v3.utility_detail ud
JOIN v3.budget_line bl ON bl.id = ud.line_id
LEFT JOIN v3.vsp v ON v.id = ud.vsp_id
WHERE ud.id = p_source_id
AND bl.budget_form_id = p_form_id;
IF v_rec.source_id IS NOT NULL THEN
PERFORM v3._upsert_form2_ahr_detail_sync_values(
p_form_id, 24, 'AHR_UTILITY', v_rec.source_id,
v_rec.line_name, v_rec.internal_order, v_rec.vsp_id, v_rec.existing_line_id,
v_rec.plan_q1, v_rec.plan_q2, v_rec.plan_q3, v_rec.plan_q4,
v_rec.actual_jan, v_rec.actual_feb, v_rec.actual_mar,
v_rec.actual_apr, v_rec.actual_may, v_rec.actual_jun,
v_rec.actual_jul, v_rec.actual_aug, v_rec.actual_sep,
v_rec.actual_oct, v_rec.actual_nov, v_rec.actual_dec
);
UPDATE v3.utility_detail ud
SET line_id = m.target_line_id
FROM v3.form2_ahr_satellite_line_source m
WHERE ud.id = v_rec.source_id
AND m.source_sheet = 'AHR_UTILITY'
AND m.source_id = ud.id;
END IF;
RETURN;
END IF;
IF p_sheet = 'AHR_SECURITY' THEN
SELECT
sd.id AS source_id,
sd.line_id AS existing_line_id,
sd.vsp_id,
COALESCE(v.format, 'АХР Безопасность #' || sd.id)::varchar AS line_name,
sd.contract_number::varchar AS internal_order,
sd.plan_q1, sd.plan_q2, sd.plan_q3, sd.plan_q4,
sd.actual_jan, sd.actual_feb, sd.actual_mar,
sd.actual_apr, sd.actual_may, sd.actual_jun,
sd.actual_jul, sd.actual_aug, sd.actual_sep,
sd.actual_oct, sd.actual_nov, sd.actual_dec
INTO v_rec
FROM v3.security_detail sd
JOIN v3.budget_line bl ON bl.id = sd.line_id
LEFT JOIN v3.vsp v ON v.id = sd.vsp_id
WHERE sd.id = p_source_id
AND bl.budget_form_id = p_form_id;
IF v_rec.source_id IS NOT NULL THEN
PERFORM v3._upsert_form2_ahr_detail_sync_values(
p_form_id, 779, 'AHR_SECURITY', v_rec.source_id,
v_rec.line_name, v_rec.internal_order, v_rec.vsp_id, v_rec.existing_line_id,
v_rec.plan_q1, v_rec.plan_q2, v_rec.plan_q3, v_rec.plan_q4,
v_rec.actual_jan, v_rec.actual_feb, v_rec.actual_mar,
v_rec.actual_apr, v_rec.actual_may, v_rec.actual_jun,
v_rec.actual_jul, v_rec.actual_aug, v_rec.actual_sep,
v_rec.actual_oct, v_rec.actual_nov, v_rec.actual_dec
);
UPDATE v3.security_detail sd
SET line_id = m.target_line_id
FROM v3.form2_ahr_satellite_line_source m
WHERE sd.id = v_rec.source_id
AND m.source_sheet = 'AHR_SECURITY'
AND m.source_id = sd.id;
END IF;
END IF;
END;
$function$;
CREATE OR REPLACE FUNCTION v3.add_budget_line(p_form_id integer, p_expense_item_id integer DEFAULT NULL::integer, p_sheet character varying DEFAULT NULL::character varying, p_item_id character varying DEFAULT NULL::character varying, p_section_code character varying DEFAULT NULL::character varying, p_direction character varying DEFAULT NULL::character varying, p_name character varying DEFAULT NULL::character varying, p_internal_order character varying DEFAULT NULL::character varying, p_vsp_id integer DEFAULT NULL::integer, p_project_id integer DEFAULT NULL::integer, p_justification character varying DEFAULT NULL::character varying, p_contract_number character varying DEFAULT NULL::character varying, p_contract_end_date date DEFAULT NULL::date)
RETURNS TABLE(row_type character varying, depth integer, sort_order bigint, data jsonb)
LANGUAGE plpgsql
AS $function$
DECLARE
v_form_type VARCHAR;
v_eid INT;
v_sheet VARCHAR;
v_auto_dir VARCHAR;
v_final_dir VARCHAR;
v_new_id INT;
v_cnt INT;
v_sat_table TEXT;
v_sat_eid INT;
v_parent_line INT;
v_decision JSONB;
v_msg TEXT;
v_user_id INT;
v_line_name VARCHAR;
BEGIN
SELECT form_type_code INTO v_form_type FROM v3.budget_form WHERE id = p_form_id;
IF v_form_type IS NULL THEN
RAISE EXCEPTION 'budget_form id=% не существует', p_form_id;
END IF;
IF v_form_type = 'FORM_3' THEN
RAISE EXCEPTION 'FORM_3 не использует budget_line — см. add_form3_line (TBD)';
END IF;
v_user_id := v3.current_user_id();
v_decision := v3.can_edit(p_form_id, p_sheet, p_direction, NULL, v_user_id);
IF NOT (v_decision->>'ok')::boolean THEN
IF v_decision->>'code' = 'role_not_allowed' THEN
RAISE EXCEPTION 'role_not_allowed: Невозможно вставить строку';
ELSIF v_decision->>'code' = 'org_not_assigned' THEN
RAISE EXCEPTION 'org_not_assigned: Невозможно вставить строку';
ELSIF v_decision->>'code' = 'window_closed' THEN
IF v_decision ? 'opens_at' THEN
v_msg := format('window_closed: opens %s', v_decision->>'opens_at');
ELSIF v_decision->>'closes_at' IS NOT NULL THEN
v_msg := format('window_closed: closed %s', v_decision->>'closes_at');
ELSE
v_msg := 'window_closed: no phase';
END IF;
RAISE EXCEPTION '%', v_msg;
ELSE
RAISE EXCEPTION '%', v_decision->>'code';
END IF;
END IF;
IF p_sheet IN ('AHR_RENT','AHR_UTILITY','AHR_SECURITY') THEN
v_sat_table := CASE p_sheet
WHEN 'AHR_RENT' THEN 'rent_detail'
WHEN 'AHR_UTILITY' THEN 'utility_detail'
WHEN 'AHR_SECURITY' THEN 'security_detail'
END;
v_sat_eid := CASE p_sheet
WHEN 'AHR_RENT' THEN 314
WHEN 'AHR_UTILITY' THEN 24
WHEN 'AHR_SECURITY' THEN 779
END;
IF p_vsp_id IS NULL THEN
RAISE EXCEPTION 'p_vsp_id обязателен для сателлитного листа %', p_sheet;
END IF;
IF NOT EXISTS (SELECT 1 FROM v3.vsp WHERE id = p_vsp_id) THEN
RAISE EXCEPTION 'vsp id=% не существует', p_vsp_id;
END IF;
SELECT id INTO v_parent_line
FROM v3.budget_line
WHERE budget_form_id = p_form_id AND expense_item_id = v_sat_eid
LIMIT 1;
IF v_parent_line IS NULL THEN
INSERT INTO v3.budget_line (budget_form_id, expense_item_id, direction)
VALUES (
p_form_id, v_sat_eid,
CASE v_form_type WHEN 'FORM_2' THEN 'Support' WHEN 'FORM_4' THEN 'Development' ELSE NULL END
) RETURNING id INTO v_parent_line;
END IF;
EXECUTE format(
'INSERT INTO v3.%I (line_id, vsp_id, contract_number, contract_end_date) '
|| 'VALUES ($1, $2, $3, $4) RETURNING id',
v_sat_table
) USING v_parent_line, p_vsp_id, p_contract_number, p_contract_end_date
INTO v_new_id;
PERFORM v3.sync_form2_ahr_satellite_line(p_form_id, p_sheet, v_new_id);
RETURN QUERY
SELECT v.row_type, v.depth, v.sort_order, v.data
FROM v3.v_form_view(p_form_id, p_sheet, NULL, NULL) v
WHERE v.row_type = 'INPUT' AND (v.data->>'id')::INT = v_new_id;
RETURN;
END IF;
IF p_expense_item_id IS NOT NULL THEN
SELECT ei.id, ei.sheet INTO v_eid, v_sheet
FROM v3.expense_item ei WHERE ei.id = p_expense_item_id;
IF v_eid IS NULL THEN
RAISE EXCEPTION 'expense_item id=% не существует', p_expense_item_id;
END IF;
IF NOT EXISTS (
SELECT 1 FROM v3.expense_item_form_type
WHERE expense_item_id = v_eid AND form_type_code = v_form_type
) THEN
RAISE EXCEPTION 'expense_item id=% не привязан к form_type %', v_eid, v_form_type;
END IF;
ELSE
IF p_sheet IS NULL OR p_item_id IS NULL THEN
RAISE EXCEPTION 'нужен либо p_expense_item_id, либо (p_sheet + p_item_id)';
END IF;
SELECT count(*) INTO v_cnt
FROM v3.expense_item ei
JOIN v3.expense_item_form_type eift ON eift.expense_item_id = ei.id
WHERE ei.item_id = p_item_id
AND ei.sheet = p_sheet
AND eift.form_type_code = v_form_type
AND (p_section_code IS NULL OR ei.section_code = p_section_code)
AND (p_direction IS NULL OR ei.direction IS NULL OR ei.direction = p_direction);
IF v_cnt = 0 THEN
RAISE EXCEPTION 'expense_item не найден: sheet=%, item_id=%, section=%, direction=%, form=%',
p_sheet, p_item_id, p_section_code, p_direction, v_form_type;
END IF;
IF v_cnt > 1 THEN
RAISE EXCEPTION 'expense_item не уникален (% совпадений): sheet=%, item_id=%, section=%, direction=%. Уточните p_section_code или передайте p_expense_item_id напрямую',
v_cnt, p_sheet, p_item_id, p_section_code, p_direction;
END IF;
SELECT ei.id, ei.sheet INTO v_eid, v_sheet
FROM v3.expense_item ei
JOIN v3.expense_item_form_type eift ON eift.expense_item_id = ei.id
WHERE ei.item_id = p_item_id
AND ei.sheet = p_sheet
AND eift.form_type_code = v_form_type
AND (p_section_code IS NULL OR ei.section_code = p_section_code)
AND (p_direction IS NULL OR ei.direction IS NULL OR ei.direction = p_direction);
END IF;
v_auto_dir := CASE
WHEN v_form_type = 'FORM_2' THEN 'Support'
WHEN v_form_type = 'FORM_4' THEN 'Development'
WHEN v_form_type = 'FORM_1' AND v_sheet = 'OPER' THEN NULL
ELSE NULL
END;
IF v_form_type = 'FORM_1' AND v_sheet IN ('AHR','CAP') AND p_direction IS NULL THEN
RAISE EXCEPTION 'p_direction обязателен для FORM_1 / sheet=% (Support|Development)', v_sheet;
END IF;
IF v_form_type IN ('FORM_2','FORM_4') AND p_direction IS NOT NULL AND p_direction <> v_auto_dir THEN
RAISE EXCEPTION 'p_direction=% несовместим с form_type=% (фиксирован=%)',
p_direction, v_form_type, v_auto_dir;
END IF;
IF v_form_type = 'FORM_1' AND v_sheet = 'OPER' AND p_direction IS NOT NULL THEN
RAISE EXCEPTION 'p_direction должен быть NULL для FORM_1 / sheet=OPER (получено: %)', p_direction;
END IF;
v_final_dir := COALESCE(p_direction, v_auto_dir);
IF p_vsp_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM v3.vsp WHERE id = p_vsp_id) THEN
RAISE EXCEPTION 'vsp id=% не существует', p_vsp_id;
END IF;
IF p_project_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM v3.form4_project WHERE id = p_project_id) THEN
RAISE EXCEPTION 'form4_project id=% не существует', p_project_id;
END IF;
v_line_name := CASE WHEN v_form_type = 'FORM_4' THEN NULL ELSE '' END;
INSERT INTO v3.budget_line (
budget_form_id, expense_item_id, name, internal_order,
vsp_id, project_id, direction, justification
) VALUES (
p_form_id, v_eid, v_line_name, p_internal_order,
p_vsp_id, p_project_id, v_final_dir, p_justification
) RETURNING id INTO v_new_id;
PERFORM v3.log_event(
'ROW_CREATE', 'ROW',
jsonb_build_object(
'sheet', v_sheet,
'line_id', v_new_id,
'expense_item_id', v_eid,
'direction', v_final_dir,
'name', v_line_name,
'vsp_id', p_vsp_id,
'project_id', p_project_id
),
p_form_id
);
DECLARE v_ei_path INT[];
BEGIN
v_ei_path := v3._expense_item_path_set(ARRAY[v_eid]);
RETURN QUERY
SELECT v.row_type, v.depth, v.sort_order, v.data
FROM v3.v_form_view(p_form_id, v_sheet, NULL, v_final_dir) v
WHERE (v.row_type IN ('ROOT','GROUP','ITEM','SUB_ITEM')
AND (v.data->'header'->>'expense_item_id')::INT = ANY(v_ei_path))
OR (v.row_type = 'INPUT' AND (v.data->>'line_id')::INT = v_new_id)
ORDER BY v.sort_order;
END;
END;
$function$;
CREATE OR REPLACE FUNCTION v3.v_summary_view(p_year integer, p_sheet character varying, p_org_unit_ids integer[] DEFAULT NULL::integer[])
RETURNS TABLE(row_type character varying, depth integer, sort_order bigint, data jsonb)
LANGUAGE plpgsql
STABLE
SET search_path TO 'v3', 'pg_catalog'
AS $function$
BEGIN
IF p_sheet = 'FORM_1' THEN
RETURN QUERY
SELECT
(CASE t.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' ELSE 'ITEM' END)::character varying,
t.depth,
ROW_NUMBER() OVER (ORDER BY t.path),
jsonb_build_object(
'section_code', s.section_code,
'name', s.name,
'plan', jsonb_build_object(
'support', jsonb_build_object('q1', s.plan_support_q1, 'q2', s.plan_support_q2, 'q3', s.plan_support_q3, 'q4', s.plan_support_q4, 'year', s.plan_support_year),
'development', jsonb_build_object('q1', s.plan_development_q1, 'q2', s.plan_development_q2, 'q3', s.plan_development_q3, 'q4', s.plan_development_q4, 'year', s.plan_development_year),
'total_year', s.plan_total_year
),
'approved', jsonb_build_object(
'support', jsonb_build_object('q1', s.approved_support_q1, 'q2', s.approved_support_q2, 'q3', s.approved_support_q3, 'q4', s.approved_support_q4, 'year', s.approved_support_year),
'development', jsonb_build_object('q1', s.approved_development_q1, 'q2', s.approved_development_q2, 'q3', s.approved_development_q3, 'q4', s.approved_development_q4, 'year', s.approved_development_year),
'total_year', s.approved_total_year
),
'fact', jsonb_build_object(
'support', jsonb_build_object('q1', s.fact_support_q1, 'q2', s.fact_support_q2, 'q3', s.fact_support_q3, 'q4', s.fact_support_q4, 'year', s.fact_support_year),
'development', jsonb_build_object('q1', s.fact_development_q1, 'q2', s.fact_development_q2, 'q3', s.fact_development_q3, 'q4', s.fact_development_q4, 'year', s.fact_development_year),
'total_year', s.fact_total_year
),
'corrected', jsonb_build_object(
'support', jsonb_build_object('q2', s.corrected_support_q2, 'q3', s.corrected_support_q3, 'q4', s.corrected_support_q4),
'development', jsonb_build_object('q2', s.corrected_development_q2, 'q3', s.corrected_development_q3, 'q4', s.corrected_development_q4)
)
)
FROM v3.v_1form_ssp(p_year, p_org_unit_ids) s
JOIN v3.mv_expense_item_tree t ON t.item_id = s.section_code AND t.depth < 3;
RETURN;
END IF;
IF p_sheet = 'FORM_2' THEN
RETURN QUERY
SELECT
(CASE t.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' ELSE 'ITEM' END)::character varying,
t.depth,
ROW_NUMBER() OVER (ORDER BY t.path),
jsonb_build_object(
'section_code', s.section_code,
'name', s.name,
'plan', jsonb_build_object(
'support', jsonb_build_object('q1', s.plan_support_q1, 'q2', s.plan_support_q2, 'q3', s.plan_support_q3, 'q4', s.plan_support_q4, 'year', s.plan_support_year),
'development', NULL
),
'approved', jsonb_build_object(
'support', jsonb_build_object('q1', s.approved_support_q1, 'q2', s.approved_support_q2, 'q3', s.approved_support_q3, 'q4', s.approved_support_q4, 'year', s.approved_support_year),
'development', NULL
),
'fact', jsonb_build_object(
'support', jsonb_build_object('q1', s.fact_support_q1, 'q2', s.fact_support_q2, 'q3', s.fact_support_q3, 'q4', s.fact_support_q4, 'year', s.fact_support_year),
'development', NULL
),
'corrected', jsonb_build_object(
'support', jsonb_build_object('q2', s.corrected_support_q2, 'q3', s.corrected_support_q3, 'q4', s.corrected_support_q4),
'development', NULL
)
)
FROM v3.v_2form_rf_p(p_year, p_org_unit_ids) s
JOIN v3.mv_expense_item_tree t ON t.item_id = s.section_code AND t.depth < 3;
RETURN;
END IF;
IF p_sheet = 'FORM_3' THEN
RETURN QUERY
SELECT
(CASE t.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' ELSE 'ITEM' END)::character varying,
t.depth,
ROW_NUMBER() OVER (ORDER BY t.path),
jsonb_build_object(
'section_code', s.section_code,
'name', s.name,
'plan', jsonb_build_object(
'support', NULL,
'development', jsonb_build_object('q1', s.plan_development_q1, 'q2', s.plan_development_q2, 'q3', s.plan_development_q3, 'q4', s.plan_development_q4, 'year', s.plan_development_year)
),
'approved', jsonb_build_object(
'support', NULL,
'development', jsonb_build_object('q1', s.approved_development_q1, 'q2', s.approved_development_q2, 'q3', s.approved_development_q3, 'q4', s.approved_development_q4, 'year', s.approved_development_year)
),
'fact', jsonb_build_object(
'support', NULL,
'development', jsonb_build_object('q1', s.fact_development_q1, 'q2', s.fact_development_q2, 'q3', s.fact_development_q3, 'q4', s.fact_development_q4, 'year', s.fact_development_year)
),
'corrected', jsonb_build_object(
'support', NULL,
'development', jsonb_build_object('q2', s.corrected_development_q2, 'q3', s.corrected_development_q3, 'q4', s.corrected_development_q4)
)
)
FROM v3.v_3form_rf_r(p_year, p_org_unit_ids) s
JOIN v3.mv_expense_item_tree t ON t.item_id = s.section_code AND t.depth < 3;
RETURN;
END IF;
IF p_sheet = 'FORM_4' THEN
RETURN QUERY
SELECT
(CASE t.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' ELSE 'ITEM' END)::character varying,
t.depth,
ROW_NUMBER() OVER (ORDER BY t.path),
jsonb_build_object(
'section_code', s.section_code,
'name', s.name,
'plan', jsonb_build_object(
'support', NULL,
'development', jsonb_build_object('q1', s.plan_development_q1, 'q2', s.plan_development_q2, 'q3', s.plan_development_q3, 'q4', s.plan_development_q4, 'year', s.plan_development_year)
),
'approved', jsonb_build_object(
'support', NULL,
'development', jsonb_build_object('q1', s.approved_development_q1, 'q2', s.approved_development_q2, 'q3', s.approved_development_q3, 'q4', s.approved_development_q4, 'year', s.approved_development_year)
),
'fact', jsonb_build_object(
'support', NULL,
'development', jsonb_build_object('q1', s.fact_development_q1, 'q2', s.fact_development_q2, 'q3', s.fact_development_q3, 'q4', s.fact_development_q4, 'year', s.fact_development_year)
),
'corrected', jsonb_build_object(
'support', NULL,
'development', jsonb_build_object('q1', s.corrected_development_q1, 'q2', s.corrected_development_q2, 'q3', s.corrected_development_q3, 'q4', s.corrected_development_q4, 'year', s.corrected_development_year)
)
)
FROM v3.v_4form_project(p_year, p_org_unit_ids) s
JOIN v3.mv_expense_item_tree t ON t.item_id = s.section_code AND t.depth < 3;
RETURN;
END IF;
IF p_sheet = 'MAIN' THEN
RETURN QUERY
SELECT
(CASE t.depth WHEN 0 THEN 'ROOT' WHEN 1 THEN 'GROUP' ELSE 'ITEM' END)::character varying,
t.depth,
ROW_NUMBER() OVER (ORDER BY t.path),
jsonb_build_object(
'section_code', s.section_code,
'name', s.name,
'plan', jsonb_build_object(
'support', jsonb_build_object('q1', s.plan_support_q1, 'q2', s.plan_support_q2, 'q3', s.plan_support_q3, 'q4', s.plan_support_q4, 'year', s.plan_support_year),
'development', jsonb_build_object('q1', s.plan_development_q1, 'q2', s.plan_development_q2, 'q3', s.plan_development_q3, 'q4', s.plan_development_q4, 'year', s.plan_development_year),
'total_year', s.plan_total_year
),
'approved', jsonb_build_object(
'support', jsonb_build_object('q1', s.approved_support_q1, 'q2', s.approved_support_q2, 'q3', s.approved_support_q3, 'q4', s.approved_support_q4, 'year', s.approved_support_year),
'development', jsonb_build_object('q1', s.approved_development_q1, 'q2', s.approved_development_q2, 'q3', s.approved_development_q3, 'q4', s.approved_development_q4, 'year', s.approved_development_year),
'total_year', s.approved_total_year
),
'fact', jsonb_build_object(
'support', jsonb_build_object('q1', s.fact_support_q1, 'q2', s.fact_support_q2, 'q3', s.fact_support_q3, 'q4', s.fact_support_q4, 'year', s.fact_support_year),
'development', jsonb_build_object('q1', s.fact_development_q1, 'q2', s.fact_development_q2, 'q3', s.fact_development_q3, 'q4', s.fact_development_q4, 'year', s.fact_development_year),
'total_year', s.fact_total_year
),
'corrected', jsonb_build_object(
'support', jsonb_build_object('q2', s.corrected_support_q2, 'q3', s.corrected_support_q3, 'q4', s.corrected_support_q4),
'development', jsonb_build_object('q2', s.corrected_development_q2, 'q3', s.corrected_development_q3, 'q4', s.corrected_development_q4)
)
)
FROM v3.v_main_format(p_year, p_org_unit_ids) s
JOIN v3.mv_expense_item_tree t ON t.item_id = s.section_code AND t.depth < 3;
RETURN;
END IF;
RAISE EXCEPTION 'Unknown summary sheet: %', p_sheet
USING HINT = 'Use FORM_1 / FORM_2 / FORM_3 / FORM_4 / MAIN';
END;
$function$;
ALTER TABLE v3.expense_item ALTER TABLE v3.expense_item
ADD CONSTRAINT expense_item_expense_item_fk ADD CONSTRAINT expense_item_expense_item_fk