diff --git a/api/alembic/versions/sql/0033_form2_old_sort.sql b/api/alembic/versions/sql/0033_form2_old_sort.sql index 438f615..0492c5b 100644 --- a/api/alembic/versions/sql/0033_form2_old_sort.sql +++ b/api/alembic/versions/sql/0033_form2_old_sort.sql @@ -3833,141 +3833,54 @@ $function$ -CREATE OR REPLACE FUNCTION v3._apply_form_cell(p_line_id integer, p_column text, p_value jsonb, p_sheet character varying DEFAULT NULL::character varying, p_form_id integer DEFAULT NULL::integer) - RETURNS void +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_parts TEXT[]; - v_scope TEXT; - v_field TEXT; - v_form_type TEXT; - v_q SMALLINT; - v_actor TEXT; - v_target_table TEXT; - v_target_col TEXT; - v_target_type TEXT; - v_key_kind TEXT; - v_str TEXT; - v_sql TEXT; - v_sat_table TEXT; + 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 - v_parts := string_to_array(p_column, '.'); - - IF p_sheet = 'OTCH9F' THEN - DECLARE v_far_month SMALLINT; - BEGIN - SELECT month INTO v_far_month FROM v3.fixed_asset_report WHERE id = p_line_id; - IF v_far_month IS NULL THEN - RAISE EXCEPTION 'fixed_asset_report.id=% не существует', p_line_id; - END IF; - - IF array_length(v_parts,1) = 1 THEN - IF v_parts[1] = 'equipment_name' THEN - RAISE EXCEPTION 'normative_field: equipment_name (нормативная номенклатура, правится отдельным API)'; - ELSIF v_parts[1] IN ('month','expense_item_id','item_id','section_code','id') THEN - RAISE EXCEPTION 'key_field: % (часть ключа строки, RO)', v_parts[1]; - ELSE - RAISE EXCEPTION 'unknown_column: %', p_column; - END IF; - END IF; - - IF array_length(v_parts,1) <> 2 THEN - RAISE EXCEPTION 'bad_column_format: %', p_column; - END IF; - - IF v_parts[1] = 'totals' THEN - IF v_parts[2] = 'go_balance_only' THEN - v_target_col := 'go_balance_only_amt'; v_target_type := 'NUMERIC'; - ELSIF v_parts[2] IN ('acquired_total','disposed_total','balance_qty','balance_amt') THEN - RAISE EXCEPTION 'computed_field: totals.%', v_parts[2]; - ELSE - RAISE EXCEPTION 'unknown_column: %', p_column; - END IF; - ELSIF v_parts[1] IN ('b_604','b_60415') THEN - IF v_parts[2] LIKE 'opening_%' AND v_far_month <> 1 THEN - RAISE EXCEPTION 'computed_field: % (opening для month>1 вычисляется из base + накопит. дельты)', p_column; - END IF; - IF v_parts[1] = 'b_604' THEN - v_target_col := CASE v_parts[2] - WHEN 'opening_qty' THEN 'opening_qty_604' - WHEN 'opening_amt' THEN 'opening_amt_604' - WHEN 'acquired_qty' THEN 'acquired_qty_604' - WHEN 'acquired_amt' THEN 'acquired_amt_604' - WHEN 'disposed_qty' THEN 'disposed_qty_604' - WHEN 'disposed_amt' THEN 'disposed_amt_604' - END; - IF v_target_col IS NULL THEN - RAISE EXCEPTION 'unknown_column: %', p_column; - END IF; - v_target_type := CASE WHEN v_parts[2] LIKE '%_qty' THEN 'INTEGER' ELSE 'NUMERIC' END; - ELSE -- b_60415 - v_target_col := CASE v_parts[2] - WHEN 'opening_qty' THEN 'opening_qty_60415' - WHEN 'opening_amt' THEN 'opening_amt_60415' - WHEN 'acquired_qty' THEN 'acquired_qty_60415' - WHEN 'acquired_amt' THEN 'acquired_amt_60415' - WHEN 'transferred_qty' THEN 'transferred_qty_60415' - WHEN 'transferred_amt' THEN 'transferred_amt_60415' - END; - IF v_target_col IS NULL THEN - RAISE EXCEPTION 'unknown_column: %', p_column; - END IF; - v_target_type := CASE WHEN v_parts[2] LIKE '%_qty' THEN 'INTEGER' ELSE 'NUMERIC' END; - END IF; - ELSE - RAISE EXCEPTION 'unknown_column: %', p_column; - END IF; - - EXECUTE format( - 'UPDATE v3.fixed_asset_report SET %I = ($2 #>> ''{}'')::%s WHERE id = $1', - v_target_col, v_target_type - ) USING p_line_id, p_value; - RETURN; - END; + 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; - IF p_sheet = 'AHR_LIMIT' THEN - IF p_form_id IS NULL THEN - RAISE EXCEPTION 'internal: p_form_id не передан для AHR_LIMIT'; - 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 EXISTS (SELECT 1 FROM v3.limit_template lt - WHERE lt.id = p_line_id AND lt.row_type = 'LEAF') THEN - RAISE EXCEPTION 'limit_template.id=% не существует или не LEAF (SECTION/GROUP не редактируется)', p_line_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; - - IF array_length(v_parts,1) <> 1 THEN - RAISE EXCEPTION 'bad_column_format: %, expected single key', p_column; - END IF; - - CASE v_parts[1] - WHEN 'qty_q1','qty_q2','qty_q3','qty_q4' THEN - v_target_col := v_parts[1]; v_target_type := 'INTEGER'; - WHEN 'comment' THEN - v_target_col := 'comment'; v_target_type := 'TEXT'; - WHEN 'amount_q1','amount_q2','amount_q3','amount_q4' THEN - RAISE EXCEPTION 'computed_field: % (вычисляется из qty × limit × period_factor)', p_column; - WHEN 'name','unit','section_no','expense_item_code', - 'limit_with_vat','limit_without_vat' THEN - RAISE EXCEPTION 'normative_field: % (нормативный справочник, правится админом отдельно)', p_column; - ELSE - RAISE EXCEPTION 'unknown_column: %', p_column; - END CASE; - - IF (v_target_type = 'NUMERIC' OR v_target_type = 'INTEGER') AND p_value = '""'::jsonb THEN - p_value := '"0"'::jsonb; - END IF; - - EXECUTE format( - 'INSERT INTO v3.form_limit (budget_form_id, template_id, %1$I) ' - || 'VALUES ($1, $2, ($3 #>> ''{}'')::%2$s) ' - || 'ON CONFLICT (budget_form_id, template_id) DO UPDATE SET %1$I = EXCLUDED.%1$I', - v_target_col, v_target_type - ) USING p_form_id, p_line_id, p_value; - RETURN; END IF; IF p_sheet IN ('AHR_RENT','AHR_UTILITY','AHR_SECURITY') THEN @@ -3976,347 +3889,155 @@ BEGIN WHEN 'AHR_UTILITY' THEN 'utility_detail' WHEN 'AHR_SECURITY' THEN 'security_detail' END; + v_sat_eid := CASE p_sheet + WHEN 'AHR_RENT' THEN 293 + WHEN 'AHR_UTILITY' THEN 799 + WHEN 'AHR_SECURITY' THEN 31 + END; - IF array_length(v_parts,1) = 1 THEN - CASE v_parts[1] - WHEN 'contract_number' THEN v_target_col := 'contract_number'; v_target_type := 'TEXT'; - WHEN 'contract_end_date' THEN v_target_col := 'contract_end_date'; v_target_type := 'DATE'; - WHEN 'comment' THEN v_target_col := 'comment'; v_target_type := 'TEXT'; - WHEN 'address','object_type','rented_area','object_area' THEN - RAISE EXCEPTION 'computed_field: % (атрибут v3.vsp, правится отдельно)', p_column; - ELSE RAISE EXCEPTION 'unknown_column: %', p_column; - END CASE; - ELSIF array_length(v_parts,1) = 2 AND v_parts[1] = 'plan' THEN - IF v_parts[2] = 'year' THEN RAISE EXCEPTION 'computed_field: %', p_column; END IF; - IF v_parts[2] NOT IN ('q1','q2','q3','q4') THEN RAISE EXCEPTION 'unknown_column: %', p_column; END IF; - v_target_col := 'plan_' || v_parts[2]; - v_target_type := 'NUMERIC'; - ELSIF array_length(v_parts,1) = 2 AND v_parts[1] LIKE 'fact_q%' THEN - IF v_parts[2] = 'total' THEN RAISE EXCEPTION 'computed_field: %', p_column; END IF; - IF (v_parts[1]='fact_q1' AND v_parts[2] NOT IN ('jan','feb','mar')) OR - (v_parts[1]='fact_q2' AND v_parts[2] NOT IN ('apr','may','jun')) OR - (v_parts[1]='fact_q3' AND v_parts[2] NOT IN ('jul','aug','sep')) OR - (v_parts[1]='fact_q4' AND v_parts[2] NOT IN ('oct','nov','dec')) THEN - RAISE EXCEPTION 'unknown_column: % (месяц вне квартала)', p_column; - END IF; - v_target_col := 'actual_' || v_parts[2]; - v_target_type := 'NUMERIC'; - ELSE - RAISE EXCEPTION 'unknown_column: %', p_column; + 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; - - IF v_target_type = 'NUMERIC' AND p_value = '""'::jsonb THEN - p_value := '"0"'::jsonb; + 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( - 'UPDATE v3.%I SET %I = ($2 #>> ''{}'')::%s WHERE id = $1', - v_sat_table, v_target_col, v_target_type - ) USING p_line_id, p_value; + '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); - GET DIAGNOSTICS v_cnt = ROW_COUNT; IF v_cnt = 0 THEN - RAISE EXCEPTION '%.id=% не существует', v_sat_table, p_line_id; + 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; - RETURN; - END IF; - - IF array_length(v_parts, 1) < 2 THEN - RAISE EXCEPTION 'bad_column_format: %, expected scope.field', p_column; - END IF; - v_scope := v_parts[1]; - v_field := v_parts[2]; - - IF v_scope = 'header' THEN - IF v_field = 'name' THEN - SELECT form_type_code INTO v_form_type from v3.budget_form where id = p_form_id; - -- IF v_form_type = 'FORM_4' THEN - -- RAISE EXCEPTION 'Нельзя редактировать поле name'; - -- END IF; - UPDATE v3.budget_line SET name = (p_value #>> '{}'), updated_at = now() WHERE id = p_line_id; - ELSIF v_field = 'internal_order' THEN - UPDATE v3.budget_line SET internal_order = (p_value #>> '{}'), updated_at = now() WHERE id = p_line_id; - ELSIF v_field = 'justification' THEN - UPDATE v3.budget_line SET justification = (p_value #>> '{}')::TEXT, updated_at = now() WHERE id = p_line_id; - ELSIF v_field = 'vsp_id' THEN - UPDATE v3.budget_line SET vsp_id = (p_value #>> '{}')::INT, updated_at = now() WHERE id = p_line_id; - ELSIF v_field IN ('year','section','item_id','num_group','vsp_address') THEN - RAISE EXCEPTION 'computed_field: %', p_column; - ELSE - RAISE EXCEPTION 'unknown_column: %', p_column; - END IF; - RETURN; - END IF; - - IF v_scope IN ('approved','totals') THEN - RAISE EXCEPTION 'computed_field: %', p_column; - END IF; - - IF v_scope = 'booking' THEN - IF array_length(v_parts, 1) <> 3 THEN - RAISE EXCEPTION 'bad_column_format: %, expected booking.y{2026|2027}.qN', p_column; - END IF; - IF v_parts[2] NOT IN ('y2026','y2027') THEN - RAISE EXCEPTION 'bad_booking_year: % (only y2026/y2027)', v_parts[2]; - END IF; - IF v_parts[3] NOT IN ('q1','q2','q3','q4') THEN - RAISE EXCEPTION 'bad_booking_quarter: %', v_parts[3]; - END IF; - v_target_col := CASE v_parts[2] - WHEN 'y2026' THEN 'expenses_' || v_parts[3] - WHEN 'y2027' THEN 'expenses_next_year_' || v_parts[3] - END; - v_sql := format( - 'INSERT INTO v3.ckk (line_id, %1$I) VALUES ($1, ($2 #>> ''{}'')::NUMERIC) ' - || 'ON CONFLICT (line_id) DO UPDATE SET %1$I = EXCLUDED.%1$I', - v_target_col - ); - EXECUTE v_sql USING p_line_id, p_value; - UPDATE v3.budget_line SET updated_at = now() WHERE id = p_line_id; - RETURN; - END IF; - - - IF v_scope IN ('q1','q2','q3','q4') THEN - v_q := substring(v_scope FROM 2)::SMALLINT; - ELSE - v_q := NULL; - END IF; - - v_actor := CASE v_scope - WHEN 'sequestration' THEN 'DFIP' - WHEN 'seq_dfip' THEN 'DFIP' - WHEN 'seq_ssp' THEN 'SSP_GO' - ELSE NULL END; - - SELECT m.target_table, m.target_col, m.target_type, m.key_kind - INTO v_target_table, v_target_col, v_target_type, v_key_kind - FROM (VALUES - ('plan','q1', 'plan','plan_q1','NUMERIC','line'), - ('plan','q2', 'plan','plan_q2','NUMERIC','line'), - ('plan','q3', 'plan','plan_q3','NUMERIC','line'), - ('plan','q4', 'plan','plan_q4','NUMERIC','line'), - ('plan','comment', 'plan','comment','TEXT', 'line'), - - ('reserve','q1', 'reserve','amount_q1','NUMERIC','line'), - ('reserve','q2', 'reserve','amount_q2','NUMERIC','line'), - ('reserve','q3', 'reserve','amount_q3','NUMERIC','line'), - ('reserve','q4', 'reserve','amount_q4','NUMERIC','line'), - ('reserve','justification','reserve','justification','TEXT','line'), - - ('allocation','order', 'allocation','internal_order','TEXT','line'), - ('allocation','property', 'allocation','property_object','TEXT','line'), - ('allocation','contract_ref', 'allocation','contract_ref','TEXT','line'), - ('allocation','allocation_purpose', 'allocation','allocation_purpose','TEXT','line'), - - ('contract_summary','total', 'contract_summary','total_amount', 'NUMERIC','line'), - ('contract_summary','counterparty', 'contract_summary','counterparty', 'TEXT', 'line'), - ('contract_summary','deadline', 'contract_summary','deadline', 'TEXT', 'line'), - ('contract_summary','comment', 'contract_summary','comment', 'TEXT', 'line'), - ('contract_summary','future_y1', 'contract_summary','future_payments_y1', 'NUMERIC','line'), - ('contract_summary','future_y2', 'contract_summary','future_payments_y2', 'NUMERIC','line'), - ('contract_summary','other_ssp', 'contract_summary','other_ssp_amount', 'NUMERIC','line'), - ('contract_summary','reference', 'contract_summary','reference', 'TEXT','line'), - ('contract_summary','centralized_flag', 'contract_summary','centralized_flag', 'TEXT','line'), - - ('collegial','approved', 'collegial_approval','approved_amount', 'NUMERIC','line'), - ('collegial','protocol', 'collegial_approval','protocol_reference','TEXT', 'line'), - ('collegial','note', 'collegial_approval','note', 'TEXT', 'line'), - - ('ckk','ceiling', 'ckk','ceiling_amount', 'NUMERIC','line'), - ('ckk','q1', 'ckk','expenses_q1', 'NUMERIC','line'), - ('ckk','q2', 'ckk','expenses_q2', 'NUMERIC','line'), - ('ckk','q3', 'ckk','expenses_q3', 'NUMERIC','line'), - ('ckk','q4', 'ckk','expenses_q4', 'NUMERIC','line'), - ('ckk','next_q1', 'ckk','expenses_next_year_q1', 'NUMERIC','line'), - ('ckk','next_q2', 'ckk','expenses_next_year_q2', 'NUMERIC','line'), - ('ckk','next_q3', 'ckk','expenses_next_year_q3', 'NUMERIC','line'), - ('ckk','next_q4', 'ckk','expenses_next_year_q4', 'NUMERIC','line'), - ('ckk','rf_schedule', 'ckk','rf_schedule', 'TEXT', 'line'), - ('ckk','deadline', 'ckk','delivery_deadline', 'TEXT', 'line'), - ('ckk','proc_plan', 'ckk','procurement_plan', 'TEXT', 'line'), - ('ckk','proc_method', 'ckk','procurement_method','TEXT', 'line'), - ('ckk','comment', 'ckk','comment', 'TEXT', 'line'), - - ('contract_detail','counterparty', 'contract_detail','counterparty', 'TEXT', 'line'), - ('contract_detail','reference', 'contract_detail','reference', 'TEXT', 'line'), - ('contract_detail','addenda', 'contract_detail','addenda', 'TEXT', 'line'), - ('contract_detail','subject', 'contract_detail','subject', 'TEXT', 'line'), - ('contract_detail','currency', 'contract_detail','currency', 'TEXT', 'line'), - ('contract_detail','ceiling', 'contract_detail','ceiling_amount','NUMERIC','line'), - ('contract_detail','q1', 'contract_detail','expenses_q1', 'NUMERIC','line'), - ('contract_detail','q2', 'contract_detail','expenses_q2', 'NUMERIC','line'), - ('contract_detail','q3', 'contract_detail','expenses_q3', 'NUMERIC','line'), - ('contract_detail','q4', 'contract_detail','expenses_q4', 'NUMERIC','line'), - ('contract_detail','rf_schedule', 'contract_detail','rf_schedule', 'TEXT', 'line'), - ('contract_detail','vat_rate', 'contract_detail','vat_rate', 'TEXT', 'line'), - ('contract_detail','exchange_rate', 'contract_detail','exchange_rate', 'NUMERIC','line'), - ('contract_detail','amount_foreign', 'contract_detail','amount_foreign','NUMERIC','line'), - ('contract_detail','deadline', 'contract_detail','deadline', 'TEXT', 'line'), - ('contract_detail','payment_scheme', 'contract_detail','payment_scheme','TEXT', 'line'), - ('contract_detail','act', 'contract_detail','act', 'TEXT', 'line'), - ('contract_detail','comment', 'contract_detail','comment', 'TEXT', 'line'), - ('contract_detail','contract_date', 'contract_detail','contract_date', 'DATE', 'line'), - - ('contract','counterparty', 'contract_detail','counterparty', 'TEXT', 'line'), - ('contract','addenda', 'contract_detail','addenda', 'TEXT', 'line'), - ('contract','amount_foreign', 'contract_detail','amount_foreign','NUMERIC','line'), - ('contract','q1', 'contract_detail','expenses_q1', 'NUMERIC','line'), - ('contract','q2', 'contract_detail','expenses_q2', 'NUMERIC','line'), - ('contract','q3', 'contract_detail','expenses_q3', 'NUMERIC','line'), - ('contract','q4', 'contract_detail','expenses_q4', 'NUMERIC','line'), - ('contract','rf_schedule', 'contract_detail','rf_schedule', 'TEXT', 'line'), - ('contract','exchange_rate', 'contract_detail','exchange_rate', 'NUMERIC','line'), - ('contract','reference', 'contract_detail','reference', 'TEXT', 'line'), - ('contract','date', 'contract_detail','contract_date', 'DATE', 'line'), - ('contract','subject', 'contract_detail','subject', 'TEXT', 'line'), - ('contract','currency', 'contract_detail','currency', 'TEXT', 'line'), - ('contract','ceiling', 'contract_detail','ceiling_amount','NUMERIC','line'), - ('contract','vat_rate', 'contract_detail','vat_rate', 'TEXT', 'line'), - ('contract','deadline', 'contract_detail','deadline', 'TEXT', 'line'), - ('contract','scheme', 'contract_detail','payment_scheme','TEXT', 'line'), - ('contract','act', 'contract_detail','act', 'TEXT', 'line'), - ('contract','comment', 'contract_detail','comment', 'TEXT', 'line'), - - ('sequestration','q1', 'sequestration','adj_q1', 'NUMERIC','line_actor'), - ('sequestration','q2', 'sequestration','adj_q2', 'NUMERIC','line_actor'), - ('sequestration','q3', 'sequestration','adj_q3', 'NUMERIC','line_actor'), - ('sequestration','q4', 'sequestration','adj_q4', 'NUMERIC','line_actor'), - ('sequestration','justification','sequestration','justification','TEXT', 'line_actor'), - - ('seq_dfip','q1', 'sequestration','adj_q1', 'NUMERIC','line_actor'), - ('seq_dfip','q2', 'sequestration','adj_q2', 'NUMERIC','line_actor'), - ('seq_dfip','q3', 'sequestration','adj_q3', 'NUMERIC','line_actor'), - ('seq_dfip','q4', 'sequestration','adj_q4', 'NUMERIC','line_actor'), - ('seq_dfip','justification','sequestration','justification','TEXT', 'line_actor'), - - ('seq_ssp','q1', 'sequestration','adj_q1', 'NUMERIC','line_actor'), - ('seq_ssp','q2', 'sequestration','adj_q2', 'NUMERIC','line_actor'), - ('seq_ssp','q3', 'sequestration','adj_q3', 'NUMERIC','line_actor'), - ('seq_ssp','q4', 'sequestration','adj_q4', 'NUMERIC','line_actor'), - ('seq_ssp','justification','sequestration','justification','TEXT', 'line_actor'), - - ('q','adj_current', 'budget_line_quarter','adj_current', 'NUMERIC','line_quarter'), - ('q','adj_ssp', 'budget_line_quarter','adj_ssp', 'NUMERIC','line_quarter'), - ('q','adj_rf', 'budget_line_quarter','adj_rf', 'NUMERIC','line_quarter'), - ('q','adj_reserve', 'budget_line_quarter','adj_reserve', 'NUMERIC','line_quarter'), - ('q','adj_comment', 'budget_line_quarter','adj_comment', 'TEXT', 'line_quarter'), - ('q','target_change', 'budget_line_quarter','target_change', 'NUMERIC','line_quarter'), - ('q','base_correction', 'budget_line_quarter','base_plan_correction', 'NUMERIC','line_quarter'), - ('q','base_correction_comment', 'budget_line_quarter','base_plan_correction_comment','TEXT', 'line_quarter'), - ('q','pay_date', 'budget_line_quarter','payment_date', 'DATE', 'line_quarter'), - ('q','pay_amount', 'budget_line_quarter','payment_amount', 'NUMERIC','line_quarter'), - ('q','pay_ho', 'budget_line_quarter','payment_amount_ho', 'NUMERIC','line_quarter'), - ('q','pay_rf', 'budget_line_quarter','payment_amount_rf', 'NUMERIC','line_quarter'), - ('q','pay_comment', 'budget_line_quarter','payment_comment', 'TEXT', 'line_quarter'), - ('q','pay_act', 'budget_line_quarter','payment_act', 'TEXT', 'line_quarter'), - ('q','actual_m1', 'budget_line_quarter','actual_m1', 'NUMERIC','line_quarter'), - ('q','actual_m2', 'budget_line_quarter','actual_m2', 'NUMERIC','line_quarter'), - ('q','actual_m3', 'budget_line_quarter','actual_m3', 'NUMERIC','line_quarter'), - ('q','actual_spod', 'budget_line_quarter','actual_spod', 'NUMERIC','line_quarter'), - ('q','transfer_q2', 'budget_line_quarter','transfer_to_q2', 'NUMERIC','line_quarter'), - ('q','transfer_q3', 'budget_line_quarter','transfer_to_q3', 'NUMERIC','line_quarter'), - ('q','transfer_q4', 'budget_line_quarter','transfer_to_q4', 'NUMERIC','line_quarter'), - ('q','transfer_econ', 'budget_line_quarter','transfer_to_economy', 'NUMERIC','line_quarter'), - ('q','transfer_q2_delay_acts', 'budget_line_quarter','transfer_delay_acts', 'NUMERIC','line_quarter'), - ('q','transfer_q2_delay_procurement', 'budget_line_quarter','transfer_delay_procurement', 'NUMERIC','line_quarter'), - ('q','transfer_q2_economy_rf', 'budget_line_quarter','transfer_economy_rf', 'NUMERIC','line_quarter'), - ('q','transfer_q3_delay_acts', 'budget_line_quarter','transfer_delay_acts', 'NUMERIC','line_quarter'), - ('q','transfer_q3_delay_procurement', 'budget_line_quarter','transfer_delay_procurement', 'NUMERIC','line_quarter'), - ('q','transfer_q3_economy_rf', 'budget_line_quarter','transfer_economy_rf', 'NUMERIC','line_quarter'), - ('q','transfer_q4_delay_acts', 'budget_line_quarter','transfer_delay_acts', 'NUMERIC','line_quarter'), - ('q','transfer_q4_delay_procurement', 'budget_line_quarter','transfer_delay_procurement', 'NUMERIC','line_quarter'), - ('q','transfer_q4_economy_rf', 'budget_line_quarter','transfer_economy_rf', 'NUMERIC','line_quarter'), - ('q','transfer_next_comment', 'budget_line_quarter','transfer_next_comment', 'TEXT', 'line_quarter'), - ('q','transfer_far_comment', 'budget_line_quarter','transfer_far_comment', 'TEXT', 'line_quarter'), - ('q','rev_eco', 'budget_line_quarter','plan_revision_eco_change','NUMERIC','line_quarter'), - ('q','rev_item', 'budget_line_quarter','plan_revision_item_adj', 'NUMERIC','line_quarter'), - ('q','rev_inc', 'budget_line_quarter','plan_revision_increase', 'NUMERIC','line_quarter'), - ('q','rev_seq', 'budget_line_quarter','plan_revision_sequester', 'NUMERIC','line_quarter'), - ('q','rev_comment', 'budget_line_quarter','plan_revision_comment', 'TEXT', 'line_quarter'), - ('q','revision_inc', 'budget_line_quarter','plan_revision_increase', 'NUMERIC','line_quarter'), - ('q','revision_seq', 'budget_line_quarter','plan_revision_sequester', 'NUMERIC','line_quarter'), - ('q','revision_comment', 'budget_line_quarter','plan_revision_comment', 'TEXT', 'line_quarter'), - ('q','booking_amount', 'budget_line_quarter','booking_amount', 'NUMERIC','line_quarter') - ) AS m(scope, field, target_table, target_col, target_type, key_kind) - WHERE m.scope = (CASE WHEN v_q IS NOT NULL THEN 'q' ELSE v_scope END) - AND m.field = v_field; - - IF v_target_table IS NULL THEN - IF v_field IN ('corrected_plan','new_plan','actual_quarter','booking', - 'rem_booking','rem_actual','residual_after_booking', - 'residual_after_actual','economy','total','year') THEN - RAISE EXCEPTION 'computed_field: %', p_column; - END IF; - RAISE EXCEPTION 'unknown_column: %', p_column; - END IF; - - IF v_target_type = 'NUMERIC' AND p_value = '""'::jsonb THEN - p_value := '"0"'::jsonb; - END IF; - IF v_key_kind = 'line' THEN - IF v_target_type = 'DATE' THEN - v_sql := format( - 'INSERT INTO v3.%1$I (line_id, %2$I) VALUES ($1, to_date($2->>0, ''DD.MM.YYYY'')) ' - || 'ON CONFLICT (line_id) DO UPDATE SET %2$I = EXCLUDED.%2$I', - v_target_table, v_target_col - ); - ELSE - v_sql := format( - 'INSERT INTO v3.%1$I (line_id, %2$I) VALUES ($1, ($2 #>> ''{}'')::%3$s) ' - || 'ON CONFLICT (line_id) DO UPDATE SET %2$I = EXCLUDED.%2$I', - v_target_table, v_target_col, v_target_type - ); - END IF; - EXECUTE v_sql USING p_line_id, p_value; - - ELSIF v_key_kind = 'line_quarter' THEN - IF v_q IS NULL THEN RAISE EXCEPTION 'internal: quarter not set for %', p_column; END IF; - - IF v_target_type = 'DATE' THEN - - v_sql := format( - 'INSERT INTO v3.%1$I (line_id, quarter, %2$I) VALUES ($1, $2, to_date($3->>0, ''DD.MM.YYYY'')) ' - || 'ON CONFLICT (line_id, quarter) DO UPDATE SET %2$I = EXCLUDED.%2$I', - v_target_table, v_target_col - ); - - ELSE - v_sql := format( - 'INSERT INTO v3.%1$I (line_id, quarter, %2$I) VALUES ($1, $2, ($3 #>> ''{}'')::%3$s) ' - || 'ON CONFLICT (line_id, quarter) DO UPDATE SET %2$I = EXCLUDED.%2$I', - v_target_table, v_target_col, v_target_type - ); + 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; - - EXECUTE v_sql USING p_line_id, v_q, p_value; - - ELSIF v_key_kind = 'line_actor' THEN - IF v_actor IS NULL THEN RAISE EXCEPTION 'internal: actor not set for %', p_column; END IF; - - IF v_target_type = 'DATE' THEN - v_sql := format( - 'INSERT INTO v3.%1$I (line_id, actor, %2$I) VALUES ($1, $2, to_date($3->>0, ''DD.MM.YYYY'')) ' - || 'ON CONFLICT (line_id, actor) DO UPDATE SET %2$I = EXCLUDED.%2$I', - v_target_table, v_target_col, v_target_type - ); - ELSE - v_sql := format( - 'INSERT INTO v3.%1$I (line_id, actor, %2$I) VALUES ($1, $2, ($3 #>> ''{}'')::%3$s) ' - || 'ON CONFLICT (line_id, actor) DO UPDATE SET %2$I = EXCLUDED.%2$I', - v_target_table, v_target_col, v_target_type - ); - END IF; - - EXECUTE v_sql USING p_line_id, v_actor, p_value; - - ELSE - RAISE EXCEPTION 'internal: unknown key_kind %', v_key_kind; + 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; - UPDATE v3.budget_line SET updated_at = now() WHERE id = p_line_id; + 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$ -; +$function$;