331 lines
14 KiB
Python
331 lines
14 KiB
Python
from alembic import op
|
||
import sqlalchemy as sa
|
||
|
||
|
||
revision = "0005"
|
||
down_revision = "0004"
|
||
branch_labels = None
|
||
depends_on = None
|
||
|
||
|
||
def upgrade() -> None:
|
||
op.execute('ALTER TABLE v3.phase_template ALTER COLUMN opens_at TYPE timestamp USING opens_at::timestamp')
|
||
op.execute('ALTER TABLE v3.phase_template ALTER COLUMN closes_at TYPE timestamp USING closes_at::timestamp')
|
||
op.execute("ALTER TABLE v3.org_unit ADD COLUMN utc_offset VARCHAR(3) DEFAULT '+03' NOT NULL")
|
||
# заводим фазу с нужной таймзоной
|
||
op.execute(
|
||
"""
|
||
CREATE OR REPLACE FUNCTION v3.copy_template_to_form(p_form_id integer)
|
||
RETURNS integer
|
||
LANGUAGE plpgsql
|
||
AS $function$
|
||
DECLARE
|
||
v_form_type VARCHAR;
|
||
v_inserted INTEGER;
|
||
v_utc_offset VARCHAR(3);
|
||
v_org_unit INTEGER;
|
||
BEGIN
|
||
SELECT form_type_code, org_unit_id INTO v_form_type, v_org_unit
|
||
FROM v3.budget_form
|
||
WHERE id = p_form_id;
|
||
|
||
IF NOT FOUND THEN
|
||
RAISE EXCEPTION 'budget_form id=% не существует', p_form_id;
|
||
END IF;
|
||
|
||
SELECT utc_offset INTO v_utc_offset
|
||
FROM v3.org_unit
|
||
WHERE id = v_org_unit;
|
||
|
||
INSERT INTO v3.form_phase
|
||
(budget_form_id, sheet, phase_code, role, column_keys, opens_at, closes_at)
|
||
SELECT p_form_id, pt.sheet, pt.phase_code, pt.role,
|
||
pt.column_keys, (pt.opens_at::text || v_utc_offset)::timestamptz,
|
||
(pt.closes_at::text || v_utc_offset)::timestamptz
|
||
FROM v3.phase_template pt
|
||
WHERE pt.form_type = v_form_type
|
||
ON CONFLICT (budget_form_id, sheet, phase_code) DO NOTHING;
|
||
|
||
GET DIAGNOSTICS v_inserted = ROW_COUNT;
|
||
RETURN v_inserted;
|
||
END;
|
||
$function$
|
||
;
|
||
"""
|
||
)
|
||
# Создавая фазу добавляем таймзону орг юнита
|
||
op.execute(
|
||
"""
|
||
CREATE OR REPLACE FUNCTION v3.add_form_phase(p_budget_form_id integer, p_sheet character varying, p_phase_code character varying, p_role character varying, p_column_keys text[], p_opens_at timestamp, p_closes_at timestamp)
|
||
RETURNS v3.form_phase
|
||
LANGUAGE plpgsql
|
||
AS $function$
|
||
DECLARE
|
||
v_phase v3.form_phase;
|
||
v_org_unit_id INT;
|
||
v_org_unit_title VARCHAR;
|
||
v_form_type_code VARCHAR;
|
||
v_year INT;
|
||
v_task_name TEXT;
|
||
v_phase_id TEXT;
|
||
v_utc_offset VARCHAR(3);
|
||
BEGIN
|
||
SELECT bf.org_unit_id, ou.title, bf.form_type_code, bf.year
|
||
INTO v_org_unit_id, v_org_unit_title, v_form_type_code, v_year
|
||
FROM v3.budget_form bf
|
||
LEFT JOIN v3.org_unit ou ON ou.id = bf.org_unit_id
|
||
WHERE bf.id = p_budget_form_id;
|
||
|
||
SELECT utc_offset INTO v_utc_offset
|
||
FROM v3.org_unit
|
||
WHERE id = v_org_unit_id;
|
||
|
||
INSERT INTO v3.form_phase(
|
||
budget_form_id, sheet, phase_code, role, column_keys, opens_at, closes_at
|
||
)
|
||
VALUES (
|
||
p_budget_form_id,
|
||
p_sheet,
|
||
p_phase_code,
|
||
p_role,
|
||
p_column_keys,
|
||
CASE WHEN p_opens_at IS NOT NULL THEN (p_opens_at::text || v_utc_offset)::timestamptz ELSE NULL END,
|
||
CASE WHEN p_closes_at IS NOT NULL THEN (p_closes_at::text || v_utc_offset)::timestamptz ELSE NULL END
|
||
|
||
)
|
||
RETURNING * INTO v_phase;
|
||
|
||
|
||
v_task_name := trim(concat(
|
||
COALESCE(v_form_type_code, ''),
|
||
CASE WHEN v_year IS NOT NULL THEN ' ' || v_year::TEXT ELSE '' END,
|
||
CASE WHEN v_org_unit_title IS NOT NULL THEN ' [' || v_org_unit_title || ']' ELSE '' END
|
||
));
|
||
v_phase_id := format('%s:%s:%s', p_budget_form_id, p_sheet, p_phase_code);
|
||
|
||
PERFORM v3.log_event(
|
||
'ACCESS_WINDOW_CHANGE', 'ACCESS',
|
||
jsonb_build_object(
|
||
'entity_type', 'form_phase',
|
||
'entity_id', p_budget_form_id,
|
||
'action', 'create',
|
||
'phase_id', v_phase_id,
|
||
'phase_name', p_phase_code,
|
||
'task_id', p_budget_form_id,
|
||
'form_id', p_budget_form_id,
|
||
'budget_form_id', p_budget_form_id,
|
||
'org_unit_id', v_org_unit_id,
|
||
'org_unit_name', v_org_unit_title,
|
||
'sheet', p_sheet,
|
||
'phase_code', p_phase_code,
|
||
'role', v_phase.role,
|
||
'column_keys', v_phase.column_keys,
|
||
'opens_at', v_phase.opens_at,
|
||
'closes_at', v_phase.closes_at
|
||
),
|
||
p_budget_form_id, null, v_org_unit_id
|
||
);
|
||
|
||
RETURN v_phase;
|
||
END;
|
||
$function$
|
||
;
|
||
"""
|
||
)
|
||
# Обнволяя фазу добавляем таймзону орг юнита
|
||
op.execute(
|
||
"""
|
||
CREATE OR REPLACE FUNCTION v3.upd_form_phase(p_budget_form_id integer, p_sheet character varying, p_phase_code character varying, p_role character varying DEFAULT NULL::character varying, p_column_keys text[] DEFAULT NULL::text[], p_opens_at timestamp without time zone DEFAULT NULL::timestamp without time zone, p_closes_at timestamp without time zone DEFAULT NULL::timestamp without time zone)
|
||
RETURNS v3.form_phase
|
||
LANGUAGE plpgsql
|
||
AS $function$
|
||
DECLARE
|
||
v_old v3.form_phase;
|
||
v_new v3.form_phase;
|
||
v_is_extend BOOLEAN := FALSE;
|
||
v_has_non_extend_change BOOLEAN := FALSE;
|
||
v_has_window_change BOOLEAN := FALSE;
|
||
v_changes JSONB := '{}'::jsonb;
|
||
v_org_unit_id INT;
|
||
v_org_unit_title VARCHAR;
|
||
v_form_type_code VARCHAR;
|
||
v_year INT;
|
||
v_task_name TEXT;
|
||
v_phase_id TEXT;
|
||
v_utc_offset VARCHAR(3);
|
||
v_opens_at timestamptz;
|
||
v_closes_at timestamptz;
|
||
BEGIN
|
||
SELECT * INTO v_old
|
||
FROM v3.form_phase
|
||
WHERE budget_form_id = p_budget_form_id
|
||
AND sheet = p_sheet
|
||
AND phase_code = p_phase_code;
|
||
|
||
IF NOT FOUND THEN
|
||
RAISE EXCEPTION 'form_phase не найден (form_id=%, sheet=%, phase=%)',
|
||
p_budget_form_id, p_sheet, p_phase_code;
|
||
END IF;
|
||
|
||
SELECT bf.org_unit_id, ou.title, bf.form_type_code, bf.year, ou.utc_offset
|
||
INTO v_org_unit_id, v_org_unit_title, v_form_type_code, v_year, v_utc_offset
|
||
FROM v3.budget_form bf
|
||
LEFT JOIN v3.org_unit ou ON ou.id = bf.org_unit_id
|
||
WHERE bf.id = p_budget_form_id;
|
||
v_task_name := trim(concat(
|
||
COALESCE(v_form_type_code, ''),
|
||
CASE WHEN v_year IS NOT NULL THEN ' ' || v_year::TEXT ELSE '' END,
|
||
CASE WHEN v_org_unit_title IS NOT NULL THEN ' [' || v_org_unit_title || ']' ELSE '' END
|
||
));
|
||
|
||
IF p_opens_at is not null THEN
|
||
v_opens_at = (p_opens_at::text || v_utc_offset)::timestamptz;
|
||
END IF;
|
||
IF p_closes_at is not null THEN
|
||
v_closes_at = (p_closes_at::text || v_utc_offset)::timestamptz;
|
||
END IF;
|
||
|
||
UPDATE v3.form_phase
|
||
SET role = COALESCE(p_role, role),
|
||
column_keys = COALESCE(p_column_keys, column_keys),
|
||
opens_at = COALESCE(v_opens_at, opens_at),
|
||
closes_at = COALESCE(v_closes_at, closes_at)
|
||
WHERE budget_form_id = p_budget_form_id
|
||
AND sheet = p_sheet
|
||
AND phase_code = p_phase_code
|
||
RETURNING * INTO v_new;
|
||
|
||
|
||
v_phase_id := format('%s:%s:%s', p_budget_form_id, p_sheet, p_phase_code);
|
||
|
||
v_is_extend := v_new.closes_at > v_old.closes_at;
|
||
v_has_non_extend_change := v_old.role IS DISTINCT FROM v_new.role
|
||
OR v_old.column_keys IS DISTINCT FROM v_new.column_keys
|
||
OR v_old.opens_at IS DISTINCT FROM v_new.opens_at;
|
||
v_has_window_change := v_has_non_extend_change
|
||
OR (v_old.closes_at IS DISTINCT FROM v_new.closes_at AND NOT v_is_extend);
|
||
|
||
IF v_old.role IS DISTINCT FROM v_new.role THEN
|
||
v_changes := v_changes || jsonb_build_object(
|
||
'role', jsonb_build_object('before', v_old.role, 'after', v_new.role)
|
||
);
|
||
END IF;
|
||
IF v_old.column_keys IS DISTINCT FROM v_new.column_keys THEN
|
||
v_changes := v_changes || jsonb_build_object(
|
||
'column_keys', jsonb_build_object('before', v_old.column_keys, 'after', v_new.column_keys)
|
||
);
|
||
END IF;
|
||
IF v_old.opens_at IS DISTINCT FROM v_new.opens_at THEN
|
||
v_changes := v_changes || jsonb_build_object(
|
||
'opens_at', jsonb_build_object(
|
||
'before', v_old.opens_at,
|
||
'after', v_new.opens_at
|
||
)
|
||
);
|
||
END IF;
|
||
IF v_old.closes_at IS DISTINCT FROM v_new.closes_at THEN
|
||
v_changes := v_changes || jsonb_build_object(
|
||
'closes_at', jsonb_build_object(
|
||
'before', v_old.closes_at,
|
||
'after', v_new.closes_at
|
||
)
|
||
);
|
||
END IF;
|
||
|
||
IF v_is_extend THEN
|
||
PERFORM v3.log_event(
|
||
'ACCESS_EXTEND', 'ACCESS',
|
||
jsonb_build_object(
|
||
'phase_id', v_phase_id,
|
||
'phase_name', p_phase_code,
|
||
'task_id', p_budget_form_id,
|
||
'task_name', NULLIF(v_task_name, ''),
|
||
'form_id', p_budget_form_id,
|
||
'budget_form_id', p_budget_form_id,
|
||
'org_unit_id', v_org_unit_id,
|
||
'org_unit_name', v_org_unit_title,
|
||
'sheet', p_sheet,
|
||
'phase_code', p_phase_code,
|
||
'closes_at_before', v_old.closes_at,
|
||
'closes_at_after', v_new.closes_at
|
||
),
|
||
p_budget_form_id
|
||
);
|
||
END IF;
|
||
|
||
IF v_has_window_change THEN
|
||
PERFORM v3.log_event(
|
||
'ACCESS_WINDOW_CHANGE', 'ACCESS',
|
||
jsonb_build_object(
|
||
'entity_type', 'form_phase',
|
||
'entity_id', p_budget_form_id,
|
||
'action', 'update',
|
||
'phase_id', v_phase_id,
|
||
'phase_name', p_phase_code,
|
||
'task_id', p_budget_form_id,
|
||
'task_name', NULLIF(v_task_name, ''),
|
||
'form_id', p_budget_form_id,
|
||
'budget_form_id', p_budget_form_id,
|
||
'org_unit_id', v_org_unit_id,
|
||
'org_unit_name', v_org_unit_title,
|
||
'sheet', p_sheet,
|
||
'phase_code', p_phase_code,
|
||
'changes', v_changes
|
||
),
|
||
p_budget_form_id, null, v_org_unit_id
|
||
);
|
||
END IF;
|
||
|
||
RETURN v_new;
|
||
END;
|
||
$function$
|
||
;
|
||
"""
|
||
)
|
||
|
||
# при обновлении таймзоны орг юнита обновляем актуальные фазы, связанные с ними
|
||
op.execute(
|
||
"""
|
||
CREATE OR REPLACE FUNCTION v3.upd_phases_timezones(p_org_unit_id integer, p_old_tz varchar(3), p_new_tz varchar(3))
|
||
RETURNS integer
|
||
LANGUAGE plpgsql
|
||
AS $function$
|
||
DECLARE
|
||
v_result integer;
|
||
BEGIN
|
||
IF p_old_tz = p_new_tz THEN
|
||
RETURN 0;
|
||
END IF;
|
||
WITH updated AS (
|
||
UPDATE v3.form_phase
|
||
SET opens_at = ((opens_at AT TIME zone (p_old_tz || '::00')::interval)::text || p_new_tz)::timestamptz,
|
||
closes_at = ((closes_at AT TIME zone (p_old_tz || '::00')::interval)::text || p_new_tz)::timestamptz
|
||
WHERE budget_form_id IN (SELECT id FROM v3.budget_form WHERE org_unit_id = p_org_unit_id)
|
||
AND (
|
||
opens_at >= CURRENT_TIMESTAMP
|
||
OR closes_at >= CURRENT_TIMESTAMP
|
||
)
|
||
RETURNING *
|
||
)
|
||
SELECT COUNT(*) INTO v_result FROM updated;
|
||
RETURN v_result;
|
||
|
||
END;
|
||
$function$
|
||
;
|
||
"""
|
||
)
|
||
# op.add_column(
|
||
# 'org_unit',
|
||
# sa.Column(
|
||
# 'utc_offset',
|
||
# sa.String(3),
|
||
# nullable=False,
|
||
# server_default='+03',
|
||
# )
|
||
# )
|
||
|
||
|
||
def downgrade() -> None:
|
||
pass
|