-- Umstieg auf das SAP-OM-Modell: Altbestand überführen, Altmodell entfernen.
--
-- Läuft nach 20260727120000 (Tabellen) und 20260727120100 (Berichtslinie).
--
-- Warum ein Schnitt und keine schrittweise Migration: Sobald eine
-- Abteilungsleitung besetzt ist, liefert das alte resolve_manager_for()
-- falsche Ergebnisse. Es sucht die Bereichsleitung über
-- "division_id = X and team_id is null and org_level = 1" — eine
-- Abteilungsleitung erfüllt dieselbe Bedingung, und das LIMIT 1 greift dann
-- willkürlich eine von beiden.
--
-- Der Bestand wird überführt, nicht gelöscht. Direkt nach dem Ausführen ist
-- das Organigramm gefüllt.
--
-- Die Abteilungsleitungen entstehen als *unbesetzte* Planstellen: es gibt
-- niemanden, der sie innehat, und erfundene Zuordnungen wären schlechter als
-- eine sichtbare Lücke. Die Aufwärtsregel überspringt sie, bis sie besetzt
-- sind — die Berichtslinie bleibt durchgängig.

begin;

-- ═══ 1. Organisationseinheiten ═══════════════════════════════════

-- Wurzel. Die bisherige Pseudo-Division "Geschäftsführung" wird sie, damit
-- die Personen, die daran hingen, ihre Einheit behalten.
insert into org_units (id, org_number, name, parent_id, unit_type, valid_from)
select id, '10000000', 'Alpenwerk Industrie GmbH', null, 'Gesellschaft', '2000-01-01'
from divisions where name = 'Geschäftsführung';

-- Falls es sie nicht gab, eine neue Wurzel anlegen.
insert into org_units (org_number, name, parent_id, unit_type, valid_from)
select '10000000', 'Alpenwerk Industrie GmbH', null, 'Gesellschaft', '2000-01-01'
where not exists (select 1 from org_units where unit_type = 'Gesellschaft');

insert into org_units (id, org_number, name, parent_id, unit_type, valid_from)
select d.id, d.org_number, d.name,
       (select id from org_units where unit_type = 'Gesellschaft'),
       'Bereich', '2000-01-01'
from divisions d
where d.name <> 'Geschäftsführung';

insert into org_units (id, org_number, name, parent_id, unit_type, valid_from)
select dep.id, dep.org_number, dep.name,
       coalesce((select u.id from org_units u where u.id = dep.division_id),
                (select id from org_units where unit_type = 'Gesellschaft')),
       'Abteilung', '2000-01-01'
from departments dep;

insert into org_units (id, org_number, name, parent_id, unit_type, valid_from)
select t.id, t.org_number, t.name, t.department_id, 'Team', '2000-01-01'
from teams t;

-- ═══ 2. Job-Katalog ══════════════════════════════════════════════
-- Vollständig *vor* den Planstellen, die darauf verweisen. Enthält auch die
-- Titel der neuen Abteilungsleitungen.
insert into jobs (code, title)
select 'J' || lpad(row_number() over (order by title)::text, 4, '0'), title
from (
  select distinct job_title as title from employees where job_title is not null
  union
  select distinct title from positions where title is not null
  union
  select distinct 'Abteilungsleitung ' || name from org_units where unit_type = 'Abteilung'
) t
on conflict (title) do nothing;

-- ═══ 3. Planstellen und Besetzungen ══════════════════════════════

-- Die Zuordnung Person -> Planstelle wird einmal festgelegt und dann von
-- beiden Inserts benutzt. Zwei unabhängig berechnete Fensterfunktionen
-- wären hier die klassische Fehlerquelle: sie sehen gleich aus und ordnen
-- doch verschieden.
-- Kein "on commit drop": im SQL-Editor hängt es vom Transaktionsverhalten
-- ab, wann das greift, und eine zu früh verschwundene Zuordnungstabelle
-- wäre schwer zu diagnostizieren. Wird am Ende explizit entfernt.
create temporary table om_pos_map (
  employee_id uuid primary key,
  position_id uuid not null default gen_random_uuid(),
  seq bigint
);

insert into om_pos_map (employee_id, seq)
select id, row_number() over (order by org_level, personnel_number) from employees;

insert into om_positions (id, position_number, org_unit_id, job_id, is_chief, valid_from)
select
  m.position_id,
  '6' || lpad(m.seq::text, 7, '0'),
  case
    when e.org_level = 0 then (select id from org_units where unit_type = 'Gesellschaft')
    when e.org_level = 1 then coalesce(e.division_id, (select id from org_units where unit_type = 'Gesellschaft'))
    else coalesce(e.team_id, (select id from org_units where unit_type = 'Gesellschaft'))
  end,
  (select j.id from jobs j where j.title = e.job_title),
  (e.org_level <= 1 or (e.org_level = 2 and e.is_lead)),
  e.entry_date
from employees e
join om_pos_map m on m.employee_id = e.id;

-- Wer ausgetreten ist, hat eine beendete Besetzung: die Planstelle ist
-- wieder frei, die Historie bleibt.
--
-- greatest(): employees erlaubt exit_date = entry_date, die Prüfregel auf
-- position_assignments verlangt aber valid_to > valid_from. Ein
-- gleichtägiger Ein- und Austritt würde die Migration sonst abbrechen.
insert into position_assignments (position_id, employee_id, valid_from, valid_to)
select m.position_id, e.id, e.entry_date,
       case when e.exit_date is null then null else greatest(e.exit_date, e.entry_date + 1) end
from employees e
join om_pos_map m on m.employee_id = e.id;

-- Unbesetzte Abteilungsleitungen — die Ebene, die im Altmodell fehlte.
insert into om_positions (position_number, org_unit_id, job_id, is_chief, valid_from)
select '69' || lpad(row_number() over (order by u.org_number)::text, 6, '0'),
       u.id,
       (select id from jobs where title = 'Abteilungsleitung ' || u.name),
       true,
       current_date
from org_units u
where u.unit_type = 'Abteilung'
  and not exists (select 1 from om_positions p where p.org_unit_id = u.id and p.is_chief);

-- Bisher offene Stellen werden unbesetzte Planstellen. is_chief nur, wenn
-- die Einheit noch keine Leitung hat — der Unique-Index liesse es sonst
-- ohnehin nicht zu.
insert into om_positions (position_number, org_unit_id, job_id, is_chief, valid_from)
select p.position_number, p.team_id,
       (select id from jobs j where j.title = p.title),
       p.is_lead and not exists (select 1 from om_positions o where o.org_unit_id = p.team_id and o.is_chief),
       p.valid_from
from positions p
where p.status = 'open'
  and exists (select 1 from org_units u where u.id = p.team_id);

-- Abbruch, bevor das Altmodell fällt: lieber eine gescheiterte Migration
-- als ein halb überführter Bestand ohne Rückweg.
do $$
declare v_fehlend int;
begin
  select count(*) into v_fehlend
    from employees e
    where not exists (select 1 from position_assignments pa where pa.employee_id = e.id);
  if v_fehlend > 0 then
    raise exception 'Abbruch: % Mitarbeitende ohne Planstelle.', v_fehlend;
  end if;

  select count(*) into v_fehlend from om_positions where job_id is null;
  if v_fehlend > 0 then
    raise exception 'Abbruch: % Planstellen ohne Job.', v_fehlend;
  end if;

  select count(*) into v_fehlend
    from org_units u
    where u.parent_id is null and u.unit_type <> 'Gesellschaft';
  if v_fehlend > 0 then
    raise exception 'Abbruch: % Einheiten ohne Elternteil.', v_fehlend;
  end if;
end $$;

drop table om_pos_map;

-- ═══ 4. Altmodell entfernen ══════════════════════════════════════

-- Vorgemerkte Änderungen verweisen über team_id auf das Altmodell. Sie sind
-- transient; halb übersetzt wären sie schlimmer als verworfen.
delete from pending_org_changes where status = 'pending';

drop trigger if exists trg_track_employee_assignment on employees;
drop function if exists fn_track_employee_assignment();
drop table if exists employee_assignments;

-- Leitet division_id aus team_id ab und haengt damit an einer Spalte, die
-- gleich faellt. Im neuen Modell uebernimmt die Einheit der Planstelle
-- diese Rolle, der Trigger wird ersatzlos entfernt.
drop trigger if exists trg_employees_set_org_unit on employees;
drop function if exists fn_set_employee_org_unit();

-- Die View selektiert team_id/division_id/manager_id/org_level/is_lead und
-- blockiert damit das Entfernen dieser Spalten. Sie wird von der Anwendung
-- nirgends benutzt und ersatzlos entfernt; die Ableitung über
-- om_reporting_lines() tritt an ihre Stelle.
drop view if exists employees_directory;

-- Funktionen auf den Alt-Spalten. Die weiterhin benötigten werden in
-- Abschnitt 5 neu angelegt; Reorganisation und Ausschreibung folgen mit der
-- Umstellung der Oberfläche.
drop function if exists resolve_manager_for(uuid, boolean, uuid);
drop function if exists staff_position_internally(jsonb);
drop function if exists create_position(jsonb);
drop function if exists delete_position(uuid);
drop function if exists apply_reorg(jsonb);
drop function if exists undo_reorg(uuid);
drop function if exists hire_employee(jsonb);
drop function if exists terminate_employee(jsonb);
drop function if exists transfer_employee(jsonb);
drop function if exists rehire_employee(jsonb);
drop function if exists record_karenz_return(jsonb);
drop function if exists apply_due_pending_changes();

alter table employees
  drop column if exists division_id,
  drop column if exists team_id,
  drop column if exists manager_id,
  drop column if exists org_level,
  drop column if exists is_lead;

drop table if exists positions;
drop table if exists teams;
drop table if exists departments;
drop table if exists divisions;

-- Mit der positions-Tabelle verschwindet ihr Trigger, nicht aber dessen
-- Funktion und die Nummernvergabe, die nur als Spaltenvorgabe dort benutzt
-- wurde. om_positions vergibt seine Nummern selbst.
drop function if exists fn_set_position_org_unit();
drop function if exists generate_position_number();

-- ═══ 5. Mutationen im neuen Modell ═══════════════════════════════
-- Die Berichtslinie wird nicht mehr mitgeschrieben, sondern abgeleitet.
-- Das entfernt aus jeder dieser Funktionen die Manager-Nachführung — beim
-- Austritt etwa entfällt das Umhängen der direkten Berichte vollständig,
-- weil sie ohnehin auf die nächste besetzte Ebene hochrutschen.

create or replace function hire_employee(payload jsonb)
returns uuid language plpgsql as $$
declare
  v_id uuid;
  v_position_id uuid := (payload->>'position_id')::uuid;
  v_entry date := (payload->>'entry_date')::date;
  v_besetzt uuid;
begin
  perform require_hr_admin();

  if v_position_id is null then
    raise exception 'Es muss eine Planstelle angegeben werden.';
  end if;

  select pa.employee_id into v_besetzt
    from position_assignments pa
    where pa.position_id = v_position_id and pa.valid_to is null;
  if v_besetzt is not null then
    raise exception 'Diese Planstelle ist bereits besetzt.';
  end if;

  insert into employees (
    first_name, last_name, gender, birth_date, sv_nummer, nationality, email, phone,
    address, postal_code, city, address_country, location_id, job_title,
    employment_type, weekly_hours, contract_type, contract_end_date, paygrade,
    source, status, entry_date, title_prefix, title_suffix,
    worker_type, collective_agreement, work_days,
    is_betriebsrat, has_dienstwagen, is_laterale_fuehrung, is_c_level
  )
  values (
    payload->>'first_name', payload->>'last_name', (payload->>'gender')::gender_type,
    (payload->>'birth_date')::date, payload->>'sv_nummer',
    coalesce(payload->>'nationality', 'Österreich'), payload->>'email', payload->>'phone',
    payload->>'address', payload->>'postal_code', payload->>'city',
    coalesce(payload->>'address_country', 'Österreich'),
    (payload->>'location_id')::uuid,
    (select j.title from om_positions p join jobs j on j.id = p.job_id where p.id = v_position_id),
    coalesce((payload->>'employment_type')::employment_type, 'Vollzeit'),
    coalesce((payload->>'weekly_hours')::numeric, 38.5),
    coalesce((payload->>'contract_type')::contract_type, 'unbefristet'),
    nullif(payload->>'contract_end_date', '')::date,
    coalesce((payload->>'paygrade')::paygrade_type, 'B'),
    coalesce((payload->>'source')::source_type, 'Extern'),
    case when v_entry > current_date then 'Geplant' else 'Aktiv' end::employment_status,
    v_entry,
    coalesce(array(select jsonb_array_elements_text(payload->'title_prefix')), '{}'),
    coalesce(array(select jsonb_array_elements_text(payload->'title_suffix')), '{}'),
    coalesce((payload->>'worker_type')::worker_type, 'Angestellte:r'),
    coalesce((payload->>'collective_agreement')::collective_agreement, 'Süßwaren'),
    coalesce(array(select jsonb_array_elements_text(payload->'work_days'))::weekday[], '{Mo,Di,Mi,Do,Fr}'),
    coalesce((payload->>'is_betriebsrat')::boolean, false),
    coalesce((payload->>'has_dienstwagen')::boolean, false),
    coalesce((payload->>'is_laterale_fuehrung')::boolean, false),
    coalesce((payload->>'is_c_level')::boolean, false)
  )
  returning id into v_id;

  insert into position_assignments (position_id, employee_id, valid_from)
    values (v_position_id, v_id, v_entry);

  insert into employee_history (employee_id, event_date, event_type, description)
    values (v_id, v_entry, 'Eintritt', 'Eintritt auf Planstelle ' ||
      (select position_number from om_positions where id = v_position_id));

  insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details)
    values (auth.uid(), current_actor_name(), 'Neueinstellung',
      payload->>'first_name' || ' ' || payload->>'last_name', v_id, 'Eintritt am ' || v_entry);

  return v_id;
end;
$$;

create or replace function terminate_employee(payload jsonb)
returns void language plpgsql as $$
declare
  v_employee_id uuid := (payload->>'employee_id')::uuid;
  v_exit date := (payload->>'exit_date')::date;
  v_name text;
begin
  perform require_hr_admin();
  select first_name || ' ' || last_name into v_name from employees where id = v_employee_id;

  update employees set
    status = case when v_exit <= current_date then 'Ausgetreten' else status end,
    exit_date = v_exit,
    exit_reason = payload->>'exit_reason'
  where id = v_employee_id;

  -- Die Planstelle wird frei. Direkte Berichte müssen nicht umgehängt
  -- werden: die Berichtslinie wird abgeleitet und rutscht von selbst auf
  -- die nächste besetzte Ebene.
  update position_assignments set valid_to = v_exit
    where employee_id = v_employee_id and valid_to is null;

  insert into employee_history (employee_id, event_date, event_type, description)
    values (v_employee_id, v_exit, 'Austritt', 'Austritt (' || coalesce(payload->>'exit_reason', '-') || ')');

  insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details)
    values (auth.uid(), current_actor_name(), 'Austritt', v_name, v_employee_id, 'Austritt am ' || v_exit);
end;
$$;

-- Versetzung ist im OM-Modell ein Wechsel der Planstelle: die alte
-- Besetzung endet, die neue beginnt. Bereich, Abteilung und Team ergeben
-- sich aus der Einheit der Zielplanstelle und werden nicht mehr mitgeführt.
create or replace function transfer_employee(payload jsonb)
returns void language plpgsql as $$
declare
  v_employee_id uuid := (payload->>'employee_id')::uuid;
  v_target_position uuid := (payload->>'target_position_id')::uuid;
  v_effective date := coalesce(nullif(payload->>'effective_date','')::date, current_date);
  v_name text;
  v_besetzt uuid;
begin
  perform require_hr_admin();
  select first_name || ' ' || last_name into v_name from employees where id = v_employee_id;

  select pa.employee_id into v_besetzt
    from position_assignments pa
    where pa.position_id = v_target_position and pa.valid_to is null;
  if v_besetzt is not null and v_besetzt <> v_employee_id then
    raise exception 'Die Zielplanstelle ist bereits besetzt.';
  end if;

  if v_effective <= current_date then
    update position_assignments set valid_to = v_effective
      where employee_id = v_employee_id and valid_to is null;
    insert into position_assignments (position_id, employee_id, valid_from)
      values (v_target_position, v_employee_id, v_effective);
    update employees set job_title =
      (select j.title from om_positions p join jobs j on j.id = p.job_id where p.id = v_target_position)
      where id = v_employee_id;
  else
    insert into pending_org_changes (employee_id, change_type, effective_date, payload)
      values (v_employee_id, 'transfer', v_effective,
        jsonb_build_object('target_position_id', v_target_position));
  end if;

  insert into employee_history (employee_id, event_date, event_type, description)
    values (v_employee_id, v_effective, 'Versetzung', 'Versetzung auf Planstelle ' ||
      (select position_number from om_positions where id = v_target_position));

  insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details)
    values (auth.uid(), current_actor_name(), 'Versetzung', v_name, v_employee_id, 'Wirksam ab ' || v_effective);
end;
$$;

create or replace function rehire_employee(payload jsonb)
returns void language plpgsql as $$
declare
  v_employee_id uuid := (payload->>'employee_id')::uuid;
  v_date date := (payload->>'rehire_date')::date;
  v_position_id uuid := (payload->>'position_id')::uuid;
  v_name text;
begin
  perform require_hr_admin();
  select first_name || ' ' || last_name into v_name from employees where id = v_employee_id;

  if v_position_id is null then
    raise exception 'Für die Wiedereinstellung muss eine Planstelle angegeben werden.';
  end if;

  update employees set
    status = case when v_date <= current_date then 'Aktiv' else 'Geplant' end,
    entry_date = v_date,
    exit_date = null,
    exit_reason = null
  where id = v_employee_id;

  insert into position_assignments (position_id, employee_id, valid_from)
    values (v_position_id, v_employee_id, v_date);

  insert into employee_history (employee_id, event_date, event_type, description)
    values (v_employee_id, v_date, 'Wiedereintritt', 'Wiedereinstellung zum ' || v_date);

  insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details)
    values (auth.uid(), current_actor_name(), 'Wiedereinstellung', v_name, v_employee_id, 'Wiedereintritt am ' || v_date);
end;
$$;

create or replace function record_karenz_return(payload jsonb)
returns void language plpgsql as $$
declare
  v_employee_id uuid := (payload->>'employee_id')::uuid;
  v_return_date date := (payload->>'return_date')::date;
  v_name text;
  v_employment_type employment_type;
  v_weekly_hours numeric;
  v_karenz_start date;
  v_absence_type text;
begin
  perform require_hr_admin();
  select first_name || ' ' || last_name, karenz_start_date, absence_type
    into v_name, v_karenz_start, v_absence_type
    from employees where id = v_employee_id;

  if v_karenz_start is not null and v_return_date <= v_karenz_start then
    raise exception 'Das Rückkehrdatum muss nach dem Beginn der Langzeitabwesenheit (%) liegen.', v_karenz_start;
  end if;

  if payload->>'employment_mode' = 'Vollzeit' then
    v_employment_type := 'Vollzeit'; v_weekly_hours := 38.5;
  elsif payload->>'employment_mode' = 'Teilzeit' then
    v_employment_type := 'Teilzeit'; v_weekly_hours := (payload->>'weekly_hours')::numeric;
  end if;

  if v_return_date <= current_date then
    -- Keine Manager-Nachführung mehr nötig: wer aus der Abwesenheit
    -- zurückkehrt, ist wieder anwesend, und die abgeleitete Berichtslinie
    -- fällt automatisch von der Vertretung auf ihn zurück.
    update employees set
      status = 'Aktiv',
      karenz_return_date = null,
      karenz_start_date = null,
      absence_type = null,
      employment_type = coalesce(v_employment_type, employment_type),
      weekly_hours = coalesce(v_weekly_hours, weekly_hours)
    where id = v_employee_id;
  else
    update employees set karenz_return_date = v_return_date where id = v_employee_id;
    insert into pending_org_changes (employee_id, change_type, effective_date, payload)
      values (v_employee_id, 'karenz_return', v_return_date,
        jsonb_build_object('employment_type', v_employment_type, 'weekly_hours', v_weekly_hours));
  end if;

  insert into employee_history (employee_id, event_date, event_type, description)
    values (v_employee_id, v_return_date, 'Rückkehr',
      'Rückkehr aus ' || coalesce(v_absence_type, 'Langzeitabwesenheit') || ' am ' || v_return_date);

  insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details)
    values (auth.uid(), current_actor_name(), 'Rückkehr', v_name, v_employee_id, 'Rückkehr am ' || v_return_date);
end;
$$;

create or replace function apply_due_pending_changes()
returns int language plpgsql security definer set search_path = public as $$
declare
  v_rec record;
  v_count int := 0;
begin
  for v_rec in
    select * from pending_org_changes
    where status = 'pending' and effective_date <= current_date
    order by effective_date, created_at
  loop
    if v_rec.change_type = 'transfer' then
      update position_assignments set valid_to = v_rec.effective_date
        where employee_id = v_rec.employee_id and valid_to is null;
      insert into position_assignments (position_id, employee_id, valid_from)
        values ((v_rec.payload->>'target_position_id')::uuid, v_rec.employee_id, v_rec.effective_date);
      update employees set job_title = (
        select j.title from om_positions p join jobs j on j.id = p.job_id
        where p.id = (v_rec.payload->>'target_position_id')::uuid
      ) where id = v_rec.employee_id;

    elsif v_rec.change_type = 'promotion' then
      update employees set
        job_title = coalesce(v_rec.payload->>'new_title', job_title),
        paygrade = coalesce((v_rec.payload->>'new_paygrade')::paygrade_type, paygrade)
      where id = v_rec.employee_id;

    elsif v_rec.change_type = 'karenz_start' then
      update employees set
        status = 'Karenz',
        karenz_return_date = (v_rec.payload->>'planned_return_date')::date,
        absence_type = coalesce(nullif(v_rec.payload->>'absence_type', ''), absence_type)
      where id = v_rec.employee_id;

    elsif v_rec.change_type = 'karenz_return' then
      update employees set
        status = 'Aktiv',
        karenz_return_date = null,
        karenz_start_date = null,
        absence_type = null,
        employment_type = coalesce((v_rec.payload->>'employment_type')::employment_type, employment_type),
        weekly_hours = coalesce((v_rec.payload->>'weekly_hours')::numeric, weekly_hours)
      where id = v_rec.employee_id;

    elsif v_rec.change_type = 'contract_change' then
      update employees set
        first_name = coalesce(v_rec.payload->'person'->>'first_name', first_name),
        last_name = coalesce(v_rec.payload->'person'->>'last_name', last_name),
        gender = coalesce((v_rec.payload->'person'->>'gender')::gender_type, gender),
        birth_date = coalesce((v_rec.payload->'person'->>'birth_date')::date, birth_date),
        sv_nummer = coalesce(v_rec.payload->'person'->>'sv_nummer', sv_nummer),
        nationality = coalesce(v_rec.payload->'person'->>'nationality', nationality),
        address = coalesce(v_rec.payload->'person'->>'address', address),
        postal_code = coalesce(v_rec.payload->'person'->>'postal_code', postal_code),
        city = coalesce(v_rec.payload->'person'->>'city', city),
        address_country = coalesce(v_rec.payload->'person'->>'address_country', address_country),
        email = coalesce(v_rec.payload->'person'->>'email', email),
        phone = coalesce(v_rec.payload->'person'->>'phone', phone),
        employment_type = coalesce((v_rec.payload->'contract'->>'employment_type')::employment_type, employment_type),
        weekly_hours = coalesce((v_rec.payload->'contract'->>'weekly_hours')::numeric, weekly_hours),
        contract_type = coalesce((v_rec.payload->'contract'->>'contract_type')::contract_type, contract_type)
      where id = v_rec.employee_id;

    elsif v_rec.change_type = 'dependent_add' then
      insert into employee_dependents (employee_id, first_name, last_name, relationship, sv_nummer, birth_date)
        values (v_rec.employee_id, v_rec.payload->>'first_name', v_rec.payload->>'last_name',
          (v_rec.payload->>'relationship')::relationship_type,
          nullif(v_rec.payload->>'sv_nummer', ''), (v_rec.payload->>'birth_date')::date);

    elsif v_rec.change_type = 'dependent_remove' then
      delete from employee_dependents where id = (v_rec.payload->>'dependent_id')::uuid;
    end if;

    update pending_org_changes set status = 'applied', applied_at = now() where id = v_rec.id;
    v_count := v_count + 1;
  end loop;

  return v_count;
end;
$$;

commit;
