Die Cornerstone-ID als eigenes, freiwilliges Feld
Cornerstone fuehrt jede Person unter einer eigenen Kennung. Der Export dorthin trug in "User ID" und "Username" bisher die Alpenwerk-UUID -- richtig, solange es nichts Besseres gab, aber nicht die Kennung, unter der Cornerstone die Person kennt. Jetzt steht dort diese Spalte. Freiwillig, weil die Zuordnungstabelle des Kunden fuer 434 der 784 Personen keine Kennung liefert. Eindeutig, weil eine Kennung genau einer Person gehoert. Als Text, weil fuehrende Nullen in einer numerischen Spalte verlorengingen. Ohne hinterlegte Kennung bleiben User ID und Username **leer**. Ein Rueckfall auf die UUID braechte zwei Kennungsarten in eine Datei, ohne dass es auffiele, und legte in Cornerstone eine zweite Person neben der bestehenden an. Eine fehlende Angabe soll fehlen; dafuer gibt es einen eigenen Test. Fuenf Funktionen mussten mit -- dieselbe Liste und derselbe Grund wie bei der Firmen-E-Mail: hire_employee und rehire_employee teilen sich den Schritt "Person", change_employee_data macht das Feld aenderbar, apply_due_pending_changes sorgt dafuer, dass eine datierte Aenderung nicht verfaellt, app_feld_karte haelt den Eintrag in der Historie richtigstellbar. Die Migration ist wieder erzeugt, nicht abgeschrieben, und prueft jede der fuenf einzeln. Erfasst wird das Feld in der Akte, in "Daten aendern", bei Einstellung und Wiedereintritt sowie ueber den Massenimport; es steht im Mitarbeiterexport und fuellt im Cornerstone-Export User ID und Username.
This commit is contained in:
@@ -85,6 +85,8 @@ export async function hireEmployee(payload: {
|
|||||||
* der in unserem Namen Einladungen verschickt.
|
* der in unserem Namen Einladungen verschickt.
|
||||||
*/
|
*/
|
||||||
company_email?: string;
|
company_email?: string;
|
||||||
|
/** Kennung im Lernsystem Cornerstone. Freiwillig. */
|
||||||
|
cornerstone_id?: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
position_id?: string;
|
position_id?: string;
|
||||||
team_id?: string;
|
team_id?: string;
|
||||||
@@ -281,6 +283,7 @@ export async function changeEmployeeData(payload: {
|
|||||||
export async function rehireEmployee(payload: {
|
export async function rehireEmployee(payload: {
|
||||||
employee_id: string;
|
employee_id: string;
|
||||||
company_email?: string;
|
company_email?: string;
|
||||||
|
cornerstone_id?: string;
|
||||||
rehire_date: string;
|
rehire_date: string;
|
||||||
position_id: string;
|
position_id: string;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ function employeeExportColumns(
|
|||||||
{ header: "Wohnsitzland", get: (e) => e.address_country },
|
{ header: "Wohnsitzland", get: (e) => e.address_country },
|
||||||
{ header: "Private E-Mail", get: (e) => e.email },
|
{ header: "Private E-Mail", get: (e) => e.email },
|
||||||
{ header: "Firmen-E-Mail", get: (e) => e.company_email },
|
{ header: "Firmen-E-Mail", get: (e) => e.company_email },
|
||||||
|
{ header: "Cornerstone-ID", get: (e) => e.cornerstone_id },
|
||||||
{ header: "Private Telefonnummer", get: (e) => e.phone },
|
{ header: "Private Telefonnummer", get: (e) => e.phone },
|
||||||
{ header: "Bereich", get: (e) => (e.org_unit_id ? (lookups.divisionName.get(e.org_unit_id) ?? "") : "") },
|
{ header: "Bereich", get: (e) => (e.org_unit_id ? (lookups.divisionName.get(e.org_unit_id) ?? "") : "") },
|
||||||
{ header: "Abteilung", get: (e) => (e.org_unit_id ? (lookups.departmentName.get(e.org_unit_id) ?? "") : "") },
|
{ header: "Abteilung", get: (e) => (e.org_unit_id ? (lookups.departmentName.get(e.org_unit_id) ?? "") : "") },
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export function DatenAendernPanel({
|
|||||||
const [addressCountry, setAddressCountry] = useState(employee.address_country ?? "Österreich");
|
const [addressCountry, setAddressCountry] = useState(employee.address_country ?? "Österreich");
|
||||||
const [email, setEmail] = useState(employee.email ?? "");
|
const [email, setEmail] = useState(employee.email ?? "");
|
||||||
const [companyEmail, setCompanyEmail] = useState(employee.company_email ?? "");
|
const [companyEmail, setCompanyEmail] = useState(employee.company_email ?? "");
|
||||||
|
const [cornerstoneId, setCornerstoneId] = useState(employee.cornerstone_id ?? "");
|
||||||
const [phone, setPhone] = useState(employee.phone ?? "");
|
const [phone, setPhone] = useState(employee.phone ?? "");
|
||||||
|
|
||||||
const [employmentType, setEmploymentType] = useState<EmploymentType>(employee.employment_type);
|
const [employmentType, setEmploymentType] = useState<EmploymentType>(employee.employment_type);
|
||||||
@@ -163,6 +164,7 @@ export function DatenAendernPanel({
|
|||||||
// Leerzeichen stehen geblieben ist, als Inhalt in der Spalte an.
|
// Leerzeichen stehen geblieben ist, als Inhalt in der Spalte an.
|
||||||
email: email.trim(),
|
email: email.trim(),
|
||||||
company_email: companyEmail.trim(),
|
company_email: companyEmail.trim(),
|
||||||
|
cornerstone_id: cornerstoneId.trim(),
|
||||||
phone: phone.trim(),
|
phone: phone.trim(),
|
||||||
// Der Notfallkontakt läuft über seinen eigenen Abschnitt weiter unten
|
// Der Notfallkontakt läuft über seinen eigenen Abschnitt weiter unten
|
||||||
// — wie die Angehörigen. Stünde er zusätzlich hier, schrieben zwei
|
// — wie die Angehörigen. Stünde er zusätzlich hier, schrieben zwei
|
||||||
@@ -309,6 +311,7 @@ export function DatenAendernPanel({
|
|||||||
</Field>
|
</Field>
|
||||||
<TextField label="E-Mail (privat)" dense type="email" value={email} onChange={setEmail} />
|
<TextField label="E-Mail (privat)" dense type="email" value={email} onChange={setEmail} />
|
||||||
<TextField label="Firmen-E-Mail" dense type="email" value={companyEmail} onChange={setCompanyEmail} />
|
<TextField label="Firmen-E-Mail" dense type="email" value={companyEmail} onChange={setCompanyEmail} />
|
||||||
|
<TextField label="Cornerstone-ID" dense value={cornerstoneId} onChange={setCornerstoneId} />
|
||||||
<TextField label="Telefon" dense type="tel" value={phone} onChange={setPhone} />
|
<TextField label="Telefon" dense type="tel" value={phone} onChange={setPhone} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export function StammdatenTab({ employee, location, dependents }: { employee: Em
|
|||||||
["Land", employee.address_country ?? "–"],
|
["Land", employee.address_country ?? "–"],
|
||||||
["Private E-Mail", employee.email ?? "–"],
|
["Private E-Mail", employee.email ?? "–"],
|
||||||
["Firmen-E-Mail", employee.company_email ?? "–"],
|
["Firmen-E-Mail", employee.company_email ?? "–"],
|
||||||
|
["Cornerstone-ID", employee.cornerstone_id ?? "–"],
|
||||||
["Private Telefonnummer", employee.phone ?? "–"],
|
["Private Telefonnummer", employee.phone ?? "–"],
|
||||||
// Der Standort ist keine Angabe zur Person, sondern die Betriebsstätte —
|
// Der Standort ist keine Angabe zur Person, sondern die Betriebsstätte —
|
||||||
// er steht deshalb am Ende und nicht zwischen Adresse und Land, wo man
|
// er steht deshalb am Ende und nicht zwischen Adresse und Land, wo man
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ export function HireWizard({ open, onClose, openPositions, locations, resumeDraf
|
|||||||
sv_nummer: draft.svNummer || undefined,
|
sv_nummer: draft.svNummer || undefined,
|
||||||
email: draft.email.trim() || undefined,
|
email: draft.email.trim() || undefined,
|
||||||
company_email: draft.companyEmail.trim() || undefined,
|
company_email: draft.companyEmail.trim() || undefined,
|
||||||
|
cornerstone_id: draft.cornerstoneId.trim() || undefined,
|
||||||
phone: draft.phone || undefined,
|
phone: draft.phone || undefined,
|
||||||
position_id: draft.positionId,
|
position_id: draft.positionId,
|
||||||
location_id: draft.locationId,
|
location_id: draft.locationId,
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ export function RehireWizard({ open, onClose, employee, openPositions, locations
|
|||||||
sv_nummer: draft.svNummer.trim(),
|
sv_nummer: draft.svNummer.trim(),
|
||||||
email: draft.email.trim(),
|
email: draft.email.trim(),
|
||||||
company_email: draft.companyEmail.trim(),
|
company_email: draft.companyEmail.trim(),
|
||||||
|
cornerstone_id: draft.cornerstoneId.trim(),
|
||||||
phone: draft.phone.trim(),
|
phone: draft.phone.trim(),
|
||||||
location_id: draft.locationId,
|
location_id: draft.locationId,
|
||||||
// Anschrift, Staatsbürgerschaft und Aufenthaltstitel stehen bewusst
|
// Anschrift, Staatsbürgerschaft und Aufenthaltstitel stehen bewusst
|
||||||
|
|||||||
@@ -74,6 +74,12 @@ export function StepPerson({ draft, update, locations, onNummerBefund, nummerGes
|
|||||||
value={draft.companyEmail}
|
value={draft.companyEmail}
|
||||||
onChange={(companyEmail) => update({ companyEmail })}
|
onChange={(companyEmail) => update({ companyEmail })}
|
||||||
/>
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Cornerstone-ID"
|
||||||
|
value={draft.cornerstoneId}
|
||||||
|
onChange={(cornerstoneId) => update({ cornerstoneId })}
|
||||||
|
hint="Freiwillig. Füllt im Cornerstone-Export „User ID“ und „Username“."
|
||||||
|
/>
|
||||||
<TextField label="Private Telefonnummer" type="tel" value={draft.phone} onChange={(phone) => update({ phone })} />
|
<TextField label="Private Telefonnummer" type="tel" value={draft.phone} onChange={(phone) => update({ phone })} />
|
||||||
</div>
|
</div>
|
||||||
<SelectField
|
<SelectField
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ export type HireDraftData = {
|
|||||||
svNummer: string;
|
svNummer: string;
|
||||||
email: string;
|
email: string;
|
||||||
companyEmail: string;
|
companyEmail: string;
|
||||||
|
/** Kennung im Lernsystem Cornerstone. Freiwillig. */
|
||||||
|
cornerstoneId: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
locationId: string;
|
locationId: string;
|
||||||
positionId: string;
|
positionId: string;
|
||||||
@@ -84,6 +86,7 @@ export const EMPTY_HIRE_DRAFT: HireDraftData = {
|
|||||||
svNummer: "",
|
svNummer: "",
|
||||||
email: "",
|
email: "",
|
||||||
companyEmail: "",
|
companyEmail: "",
|
||||||
|
cornerstoneId: "",
|
||||||
phone: "",
|
phone: "",
|
||||||
locationId: "",
|
locationId: "",
|
||||||
positionId: "",
|
positionId: "",
|
||||||
|
|||||||
866
db/migrations/20260928100000_cornerstone_id.sql
Normal file
866
db/migrations/20260928100000_cornerstone_id.sql
Normal file
@@ -0,0 +1,866 @@
|
|||||||
|
-- Die Cornerstone-ID als eigenes, freiwilliges Feld.
|
||||||
|
--
|
||||||
|
-- Cornerstone (CSOD) führt jede Person unter einer eigenen Kennung. Der
|
||||||
|
-- Export dorthin (lib/cornerstone.ts) trug in „User ID" und „Username"
|
||||||
|
-- bisher die Alpenwerk-UUID — richtig, solange es nichts Besseres gab, aber
|
||||||
|
-- eben nicht die Kennung, unter der Cornerstone die Person kennt. Ab jetzt
|
||||||
|
-- steht dort diese Spalte.
|
||||||
|
--
|
||||||
|
-- Freiwillig: für 434 der 784 Personen liefert die Zuordnungstabelle des
|
||||||
|
-- Kunden kein „#N/A", also keine Kennung. Ein Pflichtfeld hätte jede
|
||||||
|
-- Änderung an diesen Zeilen blockiert.
|
||||||
|
--
|
||||||
|
-- Eindeutig: eine Kennung gehört genau einer Person. Mehrere ohne Kennung
|
||||||
|
-- stören den Index nicht, weil null nie gleich null ist.
|
||||||
|
--
|
||||||
|
-- Text und keine Zahl: die Kennungen sind achtstellig, zwei sind
|
||||||
|
-- siebenstellig, und führende Nullen wären in einer Zahlenspalte verloren.
|
||||||
|
-- Gerechnet wird damit ohnehin nicht.
|
||||||
|
--
|
||||||
|
-- Fünf Funktionen müssen mit, weil `create or replace` die ganze Fassung
|
||||||
|
-- ersetzt und ein ausgelassenes Feld dort still verschwindet — dieselbe
|
||||||
|
-- Liste und derselbe Grund wie bei der Firmen-E-Mail (20260923120000):
|
||||||
|
-- hire_employee und rehire_employee teilen sich den Schritt „Person",
|
||||||
|
-- change_employee_data macht das Feld änderbar, apply_due_pending_changes
|
||||||
|
-- sorgt dafür, dass eine auf später datierte Änderung nicht verfällt, und
|
||||||
|
-- app_feld_karte, damit der Eintrag in der Historie richtigstellbar bleibt.
|
||||||
|
|
||||||
|
alter table employees add column if not exists cornerstone_id text;
|
||||||
|
|
||||||
|
comment on column employees.cornerstone_id is
|
||||||
|
'Kennung der Person im Lernsystem Cornerstone (CSOD). Freiwillig, eindeutig. Füllt User ID und Username im Cornerstone-Export.';
|
||||||
|
|
||||||
|
create unique index if not exists employees_cornerstone_id_key on employees (cornerstone_id);
|
||||||
|
|
||||||
|
|
||||||
|
-- ── hire_employee (Vorlage: 20260923120000_firmenemail.sql) ──────────
|
||||||
|
CREATE OR REPLACE FUNCTION public.hire_employee(payload jsonb)
|
||||||
|
RETURNS uuid
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SET search_path TO 'public', 'pg_temp'
|
||||||
|
AS $function$
|
||||||
|
declare
|
||||||
|
v_id uuid;
|
||||||
|
v_position_id uuid := (payload->>'position_id')::uuid;
|
||||||
|
v_entry date := (payload->>'entry_date')::date;
|
||||||
|
v_besetzt uuid;
|
||||||
|
v_schutz boolean := coalesce((payload->>'has_kuendigungsschutz')::boolean, false);
|
||||||
|
v_behindert boolean := coalesce((payload->>'ist_beguenstigt_behindert')::boolean, false);
|
||||||
|
begin
|
||||||
|
perform require_hr_admin();
|
||||||
|
|
||||||
|
if v_position_id is null then
|
||||||
|
raise exception 'Es muss eine Planstelle angegeben werden.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if payload->>'personnel_number' is null or btrim(payload->>'personnel_number') = '' then
|
||||||
|
raise exception 'Es muss eine Personalnummer angegeben werden.';
|
||||||
|
end if;
|
||||||
|
if exists (select 1 from employees where personnel_number = (payload->>'personnel_number')::int) then
|
||||||
|
raise exception 'Die Personalnummer % ist bereits vergeben.', payload->>'personnel_number';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
declare
|
||||||
|
v_ab date;
|
||||||
|
v_bis date;
|
||||||
|
begin
|
||||||
|
select valid_from, valid_to into v_ab, v_bis from om_positions where id = v_position_id;
|
||||||
|
if v_ab is null then
|
||||||
|
raise exception 'Die Planstelle existiert nicht.';
|
||||||
|
end if;
|
||||||
|
if v_entry < v_ab then
|
||||||
|
raise exception 'Die Planstelle gilt erst ab %. Ein Eintritt am % ist darauf nicht möglich.', v_ab, v_entry;
|
||||||
|
end if;
|
||||||
|
if v_bis is not null and v_entry >= v_bis then
|
||||||
|
raise exception 'Die Planstelle gilt nur bis %. Ein Eintritt am % ist darauf nicht möglich.', v_bis, v_entry;
|
||||||
|
end if;
|
||||||
|
end;
|
||||||
|
|
||||||
|
select pa.employee_id into v_besetzt
|
||||||
|
from position_assignments pa
|
||||||
|
where pa.position_id = v_position_id
|
||||||
|
and (pa.valid_to is null or pa.valid_to > v_entry);
|
||||||
|
if v_besetzt is not null then
|
||||||
|
raise exception 'Diese Planstelle ist bereits besetzt.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
insert into employees (
|
||||||
|
personnel_number, first_name, last_name, gender, birth_date, sv_nummer, nationality, email, company_email, cornerstone_id, 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, mitarbeiterart, collective_agreement, work_days,
|
||||||
|
is_betriebsrat, has_dienstwagen, is_laterale_fuehrung, is_c_level,
|
||||||
|
has_kuendigungsschutz, kuendigungsschutz_grund, kuendigungsschutz_ab, kuendigungsschutz_bis,
|
||||||
|
ist_beguenstigt_behindert, behinderung_grad, behinderung_ab, behinderung_bis,
|
||||||
|
hat_aufenthaltstitel, aufenthaltstitel_bis,
|
||||||
|
dienstwagen_art, emergency_contact_name, emergency_contact_phone, emergency_contact_relation
|
||||||
|
)
|
||||||
|
values (
|
||||||
|
(payload->>'personnel_number')::int, payload->>'first_name', payload->>'last_name', (payload->>'gender')::gender_type,
|
||||||
|
(payload->>'birth_date')::date, payload->>'sv_nummer',
|
||||||
|
coalesce(payload->>'nationality', 'Österreich'), payload->>'email', payload->>'company_email', payload->>'cornerstone_id', 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(nullif(payload->>'mitarbeiterart', ''), 'Standard'),
|
||||||
|
coalesce((payload->>'collective_agreement')::collective_agreement, 'Süßwaren'),
|
||||||
|
coalesce(nullif(array(select jsonb_array_elements_text(payload->'work_days'))::text[], '{}'), '{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),
|
||||||
|
v_schutz,
|
||||||
|
-- Grund, Beginn und Ende nur, wenn der Schutz überhaupt gesetzt ist:
|
||||||
|
-- sonst bliebe ein Rest ohne Bezug stehen, und die Bedingungen wiesen
|
||||||
|
-- den ganzen Vorgang zurück.
|
||||||
|
case when v_schutz then nullif(payload->>'kuendigungsschutz_grund', '') else null end,
|
||||||
|
case when v_schutz then nullif(payload->>'kuendigungsschutz_ab', '')::date else null end,
|
||||||
|
case when v_schutz then nullif(payload->>'kuendigungsschutz_bis', '')::date else null end,
|
||||||
|
v_behindert,
|
||||||
|
case when v_behindert then nullif(payload->>'behinderung_grad', '')::integer else null end,
|
||||||
|
case when v_behindert then nullif(payload->>'behinderung_ab', '')::date else null end,
|
||||||
|
case when v_behindert then nullif(payload->>'behinderung_bis', '')::date else null end,
|
||||||
|
coalesce((payload->>'hat_aufenthaltstitel')::boolean, false),
|
||||||
|
case when coalesce((payload->>'hat_aufenthaltstitel')::boolean, false)
|
||||||
|
then nullif(payload->>'aufenthaltstitel_bis', '')::date else null end,
|
||||||
|
case when coalesce((payload->>'has_dienstwagen')::boolean, false) then coalesce(nullif(payload->>'dienstwagen_art', ''), 'Verbrenner') else null end,
|
||||||
|
nullif(payload->>'emergency_contact_name', ''),
|
||||||
|
nullif(payload->>'emergency_contact_phone', ''),
|
||||||
|
nullif(payload->>'emergency_contact_relation', '')
|
||||||
|
)
|
||||||
|
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 (app_current_user_id(), current_actor_name(), 'Neueinstellung',
|
||||||
|
(payload->>'first_name') || ' ' || (payload->>'last_name'), v_id, 'Eintritt am ' || v_entry);
|
||||||
|
|
||||||
|
return v_id;
|
||||||
|
end;
|
||||||
|
$function$;
|
||||||
|
|
||||||
|
-- ── rehire_employee (Vorlage: 20260923120000_firmenemail.sql) ──────────
|
||||||
|
CREATE OR REPLACE FUNCTION public.rehire_employee(payload jsonb)
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SET search_path TO 'public', 'pg_temp'
|
||||||
|
AS $function$
|
||||||
|
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;
|
||||||
|
v_alt employees%rowtype;
|
||||||
|
v_besetzt uuid;
|
||||||
|
v_schutz boolean;
|
||||||
|
v_behindert boolean;
|
||||||
|
begin
|
||||||
|
perform require_hr_admin();
|
||||||
|
|
||||||
|
select * into v_alt from employees where id = v_employee_id;
|
||||||
|
if v_alt.id is null then
|
||||||
|
raise exception 'Die Person existiert nicht.';
|
||||||
|
end if;
|
||||||
|
v_name := v_alt.first_name || ' ' || v_alt.last_name;
|
||||||
|
|
||||||
|
if v_position_id is null then
|
||||||
|
raise exception 'Für die Wiedereinstellung muss eine Planstelle angegeben werden.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- Dieselben Prüfungen wie bei einer Neueinstellung. Sie fehlten hier, und
|
||||||
|
-- das hiess: eine Wiedereinstellung auf eine besetzte oder noch nicht
|
||||||
|
-- gültige Planstelle scheiterte erst an einem Index oder gar nicht.
|
||||||
|
declare
|
||||||
|
v_ab date;
|
||||||
|
v_bis date;
|
||||||
|
begin
|
||||||
|
select valid_from, valid_to into v_ab, v_bis from om_positions where id = v_position_id;
|
||||||
|
if v_ab is null then
|
||||||
|
raise exception 'Die Planstelle existiert nicht.';
|
||||||
|
end if;
|
||||||
|
if v_date < v_ab then
|
||||||
|
raise exception 'Die Planstelle gilt erst ab %. Ein Eintritt am % ist darauf nicht möglich.', v_ab, v_date;
|
||||||
|
end if;
|
||||||
|
if v_bis is not null and v_date >= v_bis then
|
||||||
|
raise exception 'Die Planstelle gilt nur bis %. Ein Eintritt am % ist darauf nicht möglich.', v_bis, v_date;
|
||||||
|
end if;
|
||||||
|
end;
|
||||||
|
|
||||||
|
select pa.employee_id into v_besetzt
|
||||||
|
from position_assignments pa
|
||||||
|
where pa.position_id = v_position_id
|
||||||
|
and (pa.valid_to is null or pa.valid_to > v_date);
|
||||||
|
if v_besetzt is not null then
|
||||||
|
raise exception 'Diese Planstelle ist bereits besetzt.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- Die beiden Kennzeichen zuerst: an ihnen hängen Felder, die ohne sie nicht
|
||||||
|
-- dastehen dürfen (chk_kuendigungsschutz_grund, chk_behinderung).
|
||||||
|
v_schutz := coalesce((payload->>'has_kuendigungsschutz')::boolean, v_alt.has_kuendigungsschutz);
|
||||||
|
v_behindert := coalesce((payload->>'ist_beguenstigt_behindert')::boolean, v_alt.ist_beguenstigt_behindert);
|
||||||
|
|
||||||
|
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,
|
||||||
|
austrittsart = null,
|
||||||
|
|
||||||
|
first_name = coalesce(payload->>'first_name', first_name),
|
||||||
|
last_name = coalesce(payload->>'last_name', last_name),
|
||||||
|
gender = coalesce((payload->>'gender')::gender_type, gender),
|
||||||
|
birth_date = coalesce((payload->>'birth_date')::date, birth_date),
|
||||||
|
sv_nummer = case when payload ? 'sv_nummer' then nullif(payload->>'sv_nummer', '') else sv_nummer end,
|
||||||
|
nationality = coalesce(payload->>'nationality', nationality),
|
||||||
|
email = case when payload ? 'email' then nullif(payload->>'email', '') else email end,
|
||||||
|
company_email = case when payload ? 'company_email' then nullif(payload->>'company_email', '') else company_email end,
|
||||||
|
cornerstone_id = case when payload ? 'cornerstone_id' then nullif(payload->>'cornerstone_id', '') else cornerstone_id end,
|
||||||
|
phone = case when payload ? 'phone' then nullif(payload->>'phone', '') else phone end,
|
||||||
|
address = coalesce(payload->>'address', address),
|
||||||
|
postal_code = coalesce(payload->>'postal_code', postal_code),
|
||||||
|
city = coalesce(payload->>'city', city),
|
||||||
|
address_country = coalesce(payload->>'address_country', address_country),
|
||||||
|
location_id = coalesce((payload->>'location_id')::uuid, location_id),
|
||||||
|
title_prefix = case when payload ? 'title_prefix'
|
||||||
|
then coalesce(array(select jsonb_array_elements_text(payload->'title_prefix')), '{}')
|
||||||
|
else title_prefix end,
|
||||||
|
title_suffix = case when payload ? 'title_suffix'
|
||||||
|
then coalesce(array(select jsonb_array_elements_text(payload->'title_suffix')), '{}')
|
||||||
|
else title_suffix end,
|
||||||
|
emergency_contact_name = case when payload ? 'emergency_contact_name' then nullif(payload->>'emergency_contact_name', '') else emergency_contact_name end,
|
||||||
|
emergency_contact_phone = case when payload ? 'emergency_contact_phone' then nullif(payload->>'emergency_contact_phone', '') else emergency_contact_phone end,
|
||||||
|
emergency_contact_relation = case when payload ? 'emergency_contact_relation' then nullif(payload->>'emergency_contact_relation', '') else emergency_contact_relation end,
|
||||||
|
hat_aufenthaltstitel = coalesce((payload->>'hat_aufenthaltstitel')::boolean, hat_aufenthaltstitel),
|
||||||
|
aufenthaltstitel_bis = case
|
||||||
|
when coalesce((payload->>'hat_aufenthaltstitel')::boolean, hat_aufenthaltstitel)
|
||||||
|
then case when payload ? 'aufenthaltstitel_bis' then nullif(payload->>'aufenthaltstitel_bis', '')::date else aufenthaltstitel_bis end
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Die Tätigkeit kommt von der Planstelle, nicht aus dem payload: sie
|
||||||
|
-- steht am Platz, den die Person einnimmt, und nicht an ihr.
|
||||||
|
job_title = (select j.title from om_positions p join jobs j on j.id = p.job_id where p.id = v_position_id),
|
||||||
|
employment_type = coalesce((payload->>'employment_type')::employment_type, employment_type),
|
||||||
|
weekly_hours = coalesce((payload->>'weekly_hours')::numeric, weekly_hours),
|
||||||
|
contract_type = coalesce((payload->>'contract_type')::contract_type, contract_type),
|
||||||
|
contract_end_date = case when payload ? 'contract_end_date' then nullif(payload->>'contract_end_date', '')::date else contract_end_date end,
|
||||||
|
paygrade = coalesce((payload->>'paygrade')::paygrade_type, paygrade),
|
||||||
|
source = coalesce((payload->>'source')::source_type, source),
|
||||||
|
|
||||||
|
worker_type = coalesce((payload->>'worker_type')::worker_type, worker_type),
|
||||||
|
mitarbeiterart = coalesce(nullif(payload->>'mitarbeiterart', ''), mitarbeiterart),
|
||||||
|
collective_agreement = coalesce((payload->>'collective_agreement')::collective_agreement, collective_agreement),
|
||||||
|
work_days = case when payload ? 'work_days'
|
||||||
|
then coalesce(nullif(array(select jsonb_array_elements_text(payload->'work_days'))::text[], '{}'), work_days)
|
||||||
|
else work_days end,
|
||||||
|
is_betriebsrat = coalesce((payload->>'is_betriebsrat')::boolean, is_betriebsrat),
|
||||||
|
has_dienstwagen = coalesce((payload->>'has_dienstwagen')::boolean, has_dienstwagen),
|
||||||
|
dienstwagen_art = case
|
||||||
|
when coalesce((payload->>'has_dienstwagen')::boolean, has_dienstwagen)
|
||||||
|
then coalesce(nullif(payload->>'dienstwagen_art', ''), dienstwagen_art, 'Verbrenner')
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
is_laterale_fuehrung = coalesce((payload->>'is_laterale_fuehrung')::boolean, is_laterale_fuehrung),
|
||||||
|
is_c_level = coalesce((payload->>'is_c_level')::boolean, is_c_level),
|
||||||
|
|
||||||
|
has_kuendigungsschutz = v_schutz,
|
||||||
|
kuendigungsschutz_grund = case when v_schutz then
|
||||||
|
case when payload ? 'kuendigungsschutz_grund' then nullif(payload->>'kuendigungsschutz_grund', '') else kuendigungsschutz_grund end
|
||||||
|
else null end,
|
||||||
|
kuendigungsschutz_ab = case when v_schutz then
|
||||||
|
case when payload ? 'kuendigungsschutz_ab' then nullif(payload->>'kuendigungsschutz_ab', '')::date else kuendigungsschutz_ab end
|
||||||
|
else null end,
|
||||||
|
kuendigungsschutz_bis = case when v_schutz then
|
||||||
|
case when payload ? 'kuendigungsschutz_bis' then nullif(payload->>'kuendigungsschutz_bis', '')::date else kuendigungsschutz_bis end
|
||||||
|
else null end,
|
||||||
|
|
||||||
|
ist_beguenstigt_behindert = v_behindert,
|
||||||
|
behinderung_grad = case when v_behindert then
|
||||||
|
case when payload ? 'behinderung_grad' then nullif(payload->>'behinderung_grad', '')::integer else behinderung_grad end
|
||||||
|
else null end,
|
||||||
|
behinderung_ab = case when v_behindert then
|
||||||
|
case when payload ? 'behinderung_ab' then nullif(payload->>'behinderung_ab', '')::date else behinderung_ab end
|
||||||
|
else null end,
|
||||||
|
behinderung_bis = case when v_behindert then
|
||||||
|
case when payload ? 'behinderung_bis' then nullif(payload->>'behinderung_bis', '')::date else behinderung_bis end
|
||||||
|
else null end
|
||||||
|
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 || ' 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 (app_current_user_id(), current_actor_name(), 'Wiedereinstellung', v_name, v_employee_id,
|
||||||
|
'Wiedereintritt am ' || v_date);
|
||||||
|
end;
|
||||||
|
$function$;
|
||||||
|
|
||||||
|
-- ── app_feld_karte (Vorlage: 20260923120000_firmenemail.sql) ──────────
|
||||||
|
CREATE OR REPLACE FUNCTION public.app_feld_karte()
|
||||||
|
RETURNS jsonb
|
||||||
|
LANGUAGE sql
|
||||||
|
IMMUTABLE
|
||||||
|
SET search_path TO 'public', 'pg_temp'
|
||||||
|
AS $function$
|
||||||
|
select jsonb_build_object(
|
||||||
|
'Vorname', jsonb_build_array('first_name', 'text', 'person'),
|
||||||
|
'Nachname', jsonb_build_array('last_name', 'text', 'person'),
|
||||||
|
'Geschlecht', jsonb_build_array('gender', 'gender_type', 'person'),
|
||||||
|
'Geburtsdatum', jsonb_build_array('birth_date', 'date', 'person'),
|
||||||
|
'SV-Nummer', jsonb_build_array('sv_nummer', 'text', 'person'),
|
||||||
|
'Staatsbürgerschaft', jsonb_build_array('nationality', 'text', 'person'),
|
||||||
|
'Adresse', jsonb_build_array('address', 'text', 'person'),
|
||||||
|
'Postleitzahl', jsonb_build_array('postal_code', 'text', 'person'),
|
||||||
|
'Ort', jsonb_build_array('city', 'text', 'person'),
|
||||||
|
'Land', jsonb_build_array('address_country', 'text', 'person'),
|
||||||
|
'E-Mail', jsonb_build_array('email', 'text', 'person'),
|
||||||
|
'Firmen-E-Mail', jsonb_build_array('company_email', 'text', 'person'),
|
||||||
|
'Cornerstone-ID', jsonb_build_array('cornerstone_id', 'text', 'person'),
|
||||||
|
'Telefon', jsonb_build_array('phone', 'text', 'person'),
|
||||||
|
'Notfallkontakt', jsonb_build_array('emergency_contact_name', 'text', 'person'),
|
||||||
|
'Notfallkontakt Telefon', jsonb_build_array('emergency_contact_phone', 'text', 'person'),
|
||||||
|
'Notfallkontakt Verhältnis', jsonb_build_array('emergency_contact_relation', 'text', 'person'),
|
||||||
|
'Aufenthaltstitel', jsonb_build_array('hat_aufenthaltstitel', 'boolean', 'person'),
|
||||||
|
'Aufenthaltstitel bis', jsonb_build_array('aufenthaltstitel_bis', 'date', 'person'),
|
||||||
|
'Titel (vorangestellt)', jsonb_build_array('title_prefix', 'liste', 'person'),
|
||||||
|
'Titel (nachgestellt)', jsonb_build_array('title_suffix', 'liste', 'person'),
|
||||||
|
'Beschäftigungsausmaß', jsonb_build_array('employment_type', 'employment_type', 'contract'),
|
||||||
|
'Wochenstunden', jsonb_build_array('weekly_hours', 'numeric', 'contract'),
|
||||||
|
'Vertragsart', jsonb_build_array('contract_type', 'contract_type', 'contract'),
|
||||||
|
'Befristet bis', jsonb_build_array('contract_end_date', 'date', 'contract'),
|
||||||
|
'Angestellte:r/Arbeiter:in', jsonb_build_array('worker_type', 'worker_type', 'role'),
|
||||||
|
'Mitarbeiterart', jsonb_build_array('mitarbeiterart', 'text', 'role'),
|
||||||
|
'Besetzungsart', jsonb_build_array('source', 'source_type', 'role'),
|
||||||
|
'Kollektivvertrag', jsonb_build_array('collective_agreement', 'collective_agreement', 'role'),
|
||||||
|
'Arbeitstage', jsonb_build_array('work_days', 'liste', 'role'),
|
||||||
|
'Betriebsrat', jsonb_build_array('is_betriebsrat', 'boolean', 'role'),
|
||||||
|
'Dienstwagen', jsonb_build_array('has_dienstwagen', 'boolean', 'role'),
|
||||||
|
'Laterale Führung', jsonb_build_array('is_laterale_fuehrung', 'boolean', 'role'),
|
||||||
|
'C-Level', jsonb_build_array('is_c_level', 'boolean', 'role'),
|
||||||
|
'Dienstwagen Antrieb', jsonb_build_array('dienstwagen_art', 'text', 'role'),
|
||||||
|
'Besonderer Kündigungsschutz', jsonb_build_array('has_kuendigungsschutz', 'boolean', 'role'),
|
||||||
|
'Kündigungsschutz Personenkreis', jsonb_build_array('kuendigungsschutz_grund', 'text', 'role'),
|
||||||
|
'Kündigungsschutz ab', jsonb_build_array('kuendigungsschutz_ab', 'date', 'role'),
|
||||||
|
'Kündigungsschutz bis', jsonb_build_array('kuendigungsschutz_bis', 'date', 'role'),
|
||||||
|
'Begünstigt behindert', jsonb_build_array('ist_beguenstigt_behindert', 'boolean', 'role'),
|
||||||
|
-- 'integer' und nicht 'numeric': der Typ geht in update_history_entry
|
||||||
|
-- unmittelbar als Umwandlung ins SQL (`%L::%s`). 'numeric' liefe über
|
||||||
|
-- eine zweite, stillschweigende Umwandlung auf die Spalte und rundete
|
||||||
|
-- dabei — der Grad ist eine ganze Zahl aus einem Bescheid.
|
||||||
|
'Grad der Behinderung', jsonb_build_array('behinderung_grad', 'integer', 'role'),
|
||||||
|
'Behinderung ab', jsonb_build_array('behinderung_ab', 'date', 'role'),
|
||||||
|
'Behinderung bis', jsonb_build_array('behinderung_bis', 'date', 'role'),
|
||||||
|
'Teilzeitvariante', jsonb_build_array('teilzeit_art', 'text', 'role'),
|
||||||
|
'Teilzeit bis', jsonb_build_array('teilzeit_bis', 'date', 'role'),
|
||||||
|
-- Die Felder der Abwesenheit. Dritter Eintrag null: sie gehören zu
|
||||||
|
-- keiner Gruppe im payload einer geplanten Änderung, weil Abwesenheit
|
||||||
|
-- und Rückkehr ihre eigenen Vorgänge haben.
|
||||||
|
'Status', jsonb_build_array('status', 'employment_status', null),
|
||||||
|
'Art der Abwesenheit', jsonb_build_array('absence_type', 'text', null),
|
||||||
|
'Abwesend ab', jsonb_build_array('karenz_start_date', 'date', null),
|
||||||
|
'Geplante Rückkehr', jsonb_build_array('karenz_return_date', 'date', null)
|
||||||
|
);
|
||||||
|
$function$;
|
||||||
|
|
||||||
|
-- ── change_employee_data (Vorlage: 20260923160000_leere_angaben_sind_null.sql) ──────────
|
||||||
|
CREATE OR REPLACE FUNCTION public.change_employee_data(payload jsonb)
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SET search_path TO 'public', 'pg_temp'
|
||||||
|
AS $function$
|
||||||
|
declare
|
||||||
|
v_employee_id uuid := (payload->>'employee_id')::uuid;
|
||||||
|
v_effective_date date := coalesce(nullif(payload->>'effective_date', '')::date, current_date);
|
||||||
|
v_old employees%rowtype;
|
||||||
|
v_name text;
|
||||||
|
v_person_changes jsonb := '[]'::jsonb;
|
||||||
|
v_contract_changes jsonb := '[]'::jsonb;
|
||||||
|
v_person jsonb := payload->'person';
|
||||||
|
v_contract jsonb := payload->'contract';
|
||||||
|
v_role jsonb := payload->'role';
|
||||||
|
v_immediate boolean;
|
||||||
|
v_new_work_days text[];
|
||||||
|
v_new_title_prefix text[];
|
||||||
|
v_new_title_suffix text[];
|
||||||
|
v_pending_id uuid;
|
||||||
|
begin
|
||||||
|
perform require_hr_admin();
|
||||||
|
select * into v_old from employees where id = v_employee_id;
|
||||||
|
v_name := v_old.first_name || ' ' || v_old.last_name;
|
||||||
|
v_immediate := v_effective_date <= current_date;
|
||||||
|
|
||||||
|
-- Der `?`-Test bleibt: ein fehlender Schlüssel heisst „nicht übermittelt",
|
||||||
|
-- nicht „geleert". Ohne ihn würde jedes nicht gesendete Feld als Änderung
|
||||||
|
-- auf null gemeldet.
|
||||||
|
if v_person ? 'first_name' then v_person_changes := app_aenderung(v_person_changes, 'Vorname', v_old.first_name, v_person->>'first_name'); end if;
|
||||||
|
if v_person ? 'last_name' then v_person_changes := app_aenderung(v_person_changes, 'Nachname', v_old.last_name, v_person->>'last_name'); end if;
|
||||||
|
if v_person ? 'gender' then v_person_changes := app_aenderung(v_person_changes, 'Geschlecht', v_old.gender::text, v_person->>'gender'); end if;
|
||||||
|
-- Datumswerte über ::date::text vergleichen, damit „2026-8-3" und
|
||||||
|
-- „2026-08-03" nicht als Änderung gelten.
|
||||||
|
if v_person ? 'birth_date' then v_person_changes := app_aenderung(v_person_changes, 'Geburtsdatum', v_old.birth_date::text, (nullif(v_person->>'birth_date','')::date)::text); end if;
|
||||||
|
if v_person ? 'sv_nummer' then v_person_changes := app_aenderung(v_person_changes, 'SV-Nummer', v_old.sv_nummer, v_person->>'sv_nummer'); end if;
|
||||||
|
if v_person ? 'nationality' then v_person_changes := app_aenderung(v_person_changes, 'Staatsbürgerschaft', v_old.nationality, v_person->>'nationality'); end if;
|
||||||
|
if v_person ? 'address' then v_person_changes := app_aenderung(v_person_changes, 'Adresse', v_old.address, v_person->>'address'); end if;
|
||||||
|
if v_person ? 'postal_code' then v_person_changes := app_aenderung(v_person_changes, 'Postleitzahl', v_old.postal_code, v_person->>'postal_code'); end if;
|
||||||
|
if v_person ? 'city' then v_person_changes := app_aenderung(v_person_changes, 'Ort', v_old.city, v_person->>'city'); end if;
|
||||||
|
if v_person ? 'address_country' then v_person_changes := app_aenderung(v_person_changes, 'Land', v_old.address_country, v_person->>'address_country'); end if;
|
||||||
|
if v_person ? 'email' then v_person_changes := app_aenderung(v_person_changes, 'E-Mail', v_old.email, v_person->>'email'); end if;
|
||||||
|
if v_person ? 'phone' then v_person_changes := app_aenderung(v_person_changes, 'Telefon', v_old.phone, v_person->>'phone'); end if;
|
||||||
|
if v_person ? 'company_email' then v_person_changes := app_aenderung(v_person_changes, 'Firmen-E-Mail', v_old.company_email, v_person->>'company_email'); end if;
|
||||||
|
if v_person ? 'cornerstone_id' then v_person_changes := app_aenderung(v_person_changes, 'Cornerstone-ID', v_old.cornerstone_id, v_person->>'cornerstone_id'); end if;
|
||||||
|
if v_person ? 'emergency_contact_name' then v_person_changes := app_aenderung(v_person_changes, 'Notfallkontakt', v_old.emergency_contact_name, v_person->>'emergency_contact_name'); end if;
|
||||||
|
if v_person ? 'emergency_contact_phone' then v_person_changes := app_aenderung(v_person_changes, 'Notfallkontakt Telefon', v_old.emergency_contact_phone, v_person->>'emergency_contact_phone'); end if;
|
||||||
|
if v_person ? 'emergency_contact_relation' then v_person_changes := app_aenderung(v_person_changes, 'Notfallkontakt Verhältnis', v_old.emergency_contact_relation, v_person->>'emergency_contact_relation'); end if;
|
||||||
|
if v_person ? 'hat_aufenthaltstitel' then v_person_changes := app_aenderung(v_person_changes, 'Aufenthaltstitel', v_old.hat_aufenthaltstitel::text, v_person->>'hat_aufenthaltstitel'); end if;
|
||||||
|
if v_person ? 'aufenthaltstitel_bis' then v_person_changes := app_aenderung(v_person_changes, 'Aufenthaltstitel bis', v_old.aufenthaltstitel_bis::text, (nullif(v_person->>'aufenthaltstitel_bis','')::date)::text); end if;
|
||||||
|
|
||||||
|
if v_person ? 'title_prefix' then
|
||||||
|
v_new_title_prefix := coalesce((select array_agg(elem) from jsonb_array_elements_text(v_person->'title_prefix') elem), '{}');
|
||||||
|
v_person_changes := app_aenderung(v_person_changes, 'Titel (vorangestellt)',
|
||||||
|
array_to_string(v_old.title_prefix, ', '), array_to_string(v_new_title_prefix, ', '));
|
||||||
|
end if;
|
||||||
|
if v_person ? 'title_suffix' then
|
||||||
|
v_new_title_suffix := coalesce((select array_agg(elem) from jsonb_array_elements_text(v_person->'title_suffix') elem), '{}');
|
||||||
|
v_person_changes := app_aenderung(v_person_changes, 'Titel (nachgestellt)',
|
||||||
|
array_to_string(v_old.title_suffix, ', '), array_to_string(v_new_title_suffix, ', '));
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if v_contract ? 'employment_type' then v_contract_changes := app_aenderung(v_contract_changes, 'Beschäftigungsausmaß', v_old.employment_type::text, v_contract->>'employment_type'); end if;
|
||||||
|
-- Über ::numeric::text, damit „38.50" und „38.5" gleich zählen.
|
||||||
|
if v_contract ? 'weekly_hours' then v_contract_changes := app_aenderung(v_contract_changes, 'Wochenstunden', v_old.weekly_hours::text, (nullif(v_contract->>'weekly_hours','')::numeric)::text); end if;
|
||||||
|
if v_contract ? 'contract_type' then v_contract_changes := app_aenderung(v_contract_changes, 'Vertragsart', v_old.contract_type::text, v_contract->>'contract_type'); end if;
|
||||||
|
if v_contract ? 'contract_end_date' then v_contract_changes := app_aenderung(v_contract_changes, 'Befristet bis', v_old.contract_end_date::text, (nullif(v_contract->>'contract_end_date','')::date)::text); end if;
|
||||||
|
|
||||||
|
if v_role ? 'worker_type' then v_contract_changes := app_aenderung(v_contract_changes, 'Angestellte:r/Arbeiter:in', v_old.worker_type::text, v_role->>'worker_type'); end if;
|
||||||
|
if v_role ? 'mitarbeiterart' then v_contract_changes := app_aenderung(v_contract_changes, 'Mitarbeiterart', v_old.mitarbeiterart, v_role->>'mitarbeiterart'); end if;
|
||||||
|
if v_role ? 'source' then v_contract_changes := app_aenderung(v_contract_changes, 'Besetzungsart', v_old.source::text, v_role->>'source'); end if;
|
||||||
|
if v_role ? 'collective_agreement' then v_contract_changes := app_aenderung(v_contract_changes, 'Kollektivvertrag', v_old.collective_agreement::text, v_role->>'collective_agreement'); end if;
|
||||||
|
if v_role ? 'work_days' then
|
||||||
|
v_new_work_days := coalesce((select array_agg(elem) from jsonb_array_elements_text(v_role->'work_days') elem), '{}');
|
||||||
|
v_contract_changes := app_aenderung(v_contract_changes, 'Arbeitstage',
|
||||||
|
array_to_string(v_old.work_days, ', '), array_to_string(v_new_work_days, ', '));
|
||||||
|
end if;
|
||||||
|
if v_role ? 'is_betriebsrat' then v_contract_changes := app_aenderung(v_contract_changes, 'Betriebsrat', v_old.is_betriebsrat::text, v_role->>'is_betriebsrat'); end if;
|
||||||
|
if v_role ? 'has_dienstwagen' then v_contract_changes := app_aenderung(v_contract_changes, 'Dienstwagen', v_old.has_dienstwagen::text, v_role->>'has_dienstwagen'); end if;
|
||||||
|
if v_role ? 'is_laterale_fuehrung' then v_contract_changes := app_aenderung(v_contract_changes, 'Laterale Führung', v_old.is_laterale_fuehrung::text, v_role->>'is_laterale_fuehrung'); end if;
|
||||||
|
if v_role ? 'is_c_level' then v_contract_changes := app_aenderung(v_contract_changes, 'C-Level', v_old.is_c_level::text, v_role->>'is_c_level'); end if;
|
||||||
|
if v_role ? 'has_kuendigungsschutz' then v_contract_changes := app_aenderung(v_contract_changes, 'Besonderer Kündigungsschutz', v_old.has_kuendigungsschutz::text, v_role->>'has_kuendigungsschutz'); end if;
|
||||||
|
if v_role ? 'kuendigungsschutz_grund' then v_contract_changes := app_aenderung(v_contract_changes, 'Kündigungsschutz Personenkreis', v_old.kuendigungsschutz_grund, nullif(v_role->>'kuendigungsschutz_grund', '')); end if;
|
||||||
|
if v_role ? 'kuendigungsschutz_ab' then v_contract_changes := app_aenderung(v_contract_changes, 'Kündigungsschutz ab', v_old.kuendigungsschutz_ab::text, (nullif(v_role->>'kuendigungsschutz_ab','')::date)::text); end if;
|
||||||
|
if v_role ? 'kuendigungsschutz_bis' then v_contract_changes := app_aenderung(v_contract_changes, 'Kündigungsschutz bis', v_old.kuendigungsschutz_bis::text, (nullif(v_role->>'kuendigungsschutz_bis','')::date)::text); end if;
|
||||||
|
if v_role ? 'ist_beguenstigt_behindert' then v_contract_changes := app_aenderung(v_contract_changes, 'Begünstigt behindert', v_old.ist_beguenstigt_behindert::text, v_role->>'ist_beguenstigt_behindert'); end if;
|
||||||
|
if v_role ? 'behinderung_grad' then v_contract_changes := app_aenderung(v_contract_changes, 'Grad der Behinderung', v_old.behinderung_grad::text, (nullif(v_role->>'behinderung_grad','')::integer)::text); end if;
|
||||||
|
if v_role ? 'behinderung_ab' then v_contract_changes := app_aenderung(v_contract_changes, 'Behinderung ab', v_old.behinderung_ab::text, (nullif(v_role->>'behinderung_ab','')::date)::text); end if;
|
||||||
|
if v_role ? 'behinderung_bis' then v_contract_changes := app_aenderung(v_contract_changes, 'Behinderung bis', v_old.behinderung_bis::text, (nullif(v_role->>'behinderung_bis','')::date)::text); end if;
|
||||||
|
if v_role ? 'teilzeit_art' then v_contract_changes := app_aenderung(v_contract_changes, 'Teilzeitvariante', v_old.teilzeit_art, nullif(v_role->>'teilzeit_art', '')); end if;
|
||||||
|
if v_role ? 'teilzeit_bis' then v_contract_changes := app_aenderung(v_contract_changes, 'Teilzeit bis', v_old.teilzeit_bis::text, (nullif(v_role->>'teilzeit_bis','')::date)::text); end if;
|
||||||
|
if v_role ? 'dienstwagen_art' then v_contract_changes := app_aenderung(v_contract_changes, 'Dienstwagen Antrieb', v_old.dienstwagen_art, nullif(v_role->>'dienstwagen_art', '')); end if;
|
||||||
|
|
||||||
|
if v_immediate then
|
||||||
|
update employees set
|
||||||
|
first_name = coalesce(v_person->>'first_name', first_name),
|
||||||
|
last_name = coalesce(v_person->>'last_name', last_name),
|
||||||
|
gender = coalesce((v_person->>'gender')::gender_type, gender),
|
||||||
|
birth_date = coalesce((v_person->>'birth_date')::date, birth_date),
|
||||||
|
sv_nummer = case when v_person ? 'sv_nummer' then nullif(v_person->>'sv_nummer', '') else sv_nummer end,
|
||||||
|
nationality = coalesce(v_person->>'nationality', nationality),
|
||||||
|
address = case when v_person ? 'address' then nullif(v_person->>'address', '') else address end,
|
||||||
|
postal_code = case when v_person ? 'postal_code' then nullif(v_person->>'postal_code', '') else postal_code end,
|
||||||
|
city = case when v_person ? 'city' then nullif(v_person->>'city', '') else city end,
|
||||||
|
address_country = case when v_person ? 'address_country' then nullif(v_person->>'address_country', '') else address_country end,
|
||||||
|
email = case when v_person ? 'email' then nullif(v_person->>'email', '') else email end,
|
||||||
|
phone = case when v_person ? 'phone' then nullif(v_person->>'phone', '') else phone end,
|
||||||
|
company_email = case when v_person ? 'company_email' then nullif(v_person->>'company_email', '') else company_email end,
|
||||||
|
cornerstone_id = case when v_person ? 'cornerstone_id' then nullif(v_person->>'cornerstone_id', '') else cornerstone_id end,
|
||||||
|
emergency_contact_name = case when v_person ? 'emergency_contact_name' then nullif(v_person->>'emergency_contact_name', '') else emergency_contact_name end,
|
||||||
|
emergency_contact_phone = case when v_person ? 'emergency_contact_phone' then nullif(v_person->>'emergency_contact_phone', '') else emergency_contact_phone end,
|
||||||
|
emergency_contact_relation = case when v_person ? 'emergency_contact_relation' then nullif(v_person->>'emergency_contact_relation', '') else emergency_contact_relation end,
|
||||||
|
hat_aufenthaltstitel = coalesce((v_person->>'hat_aufenthaltstitel')::boolean, hat_aufenthaltstitel),
|
||||||
|
-- Fällt der Titel weg, fällt das Datum mit. Sonst bliebe ein
|
||||||
|
-- Enddatum ohne Titel stehen, und chk_aufenthaltstitel_bis liesse
|
||||||
|
-- die ganze Änderung scheitern statt das Offensichtliche zu tun.
|
||||||
|
aufenthaltstitel_bis = case
|
||||||
|
when coalesce((v_person->>'hat_aufenthaltstitel')::boolean, hat_aufenthaltstitel) then
|
||||||
|
case when v_person ? 'aufenthaltstitel_bis'
|
||||||
|
then nullif(v_person->>'aufenthaltstitel_bis','')::date
|
||||||
|
else aufenthaltstitel_bis end
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
title_prefix = case when v_person ? 'title_prefix' then v_new_title_prefix else title_prefix end,
|
||||||
|
title_suffix = case when v_person ? 'title_suffix' then v_new_title_suffix else title_suffix end,
|
||||||
|
employment_type = coalesce((v_contract->>'employment_type')::employment_type, employment_type),
|
||||||
|
weekly_hours = coalesce((v_contract->>'weekly_hours')::numeric, weekly_hours),
|
||||||
|
contract_type = coalesce((v_contract->>'contract_type')::contract_type, contract_type),
|
||||||
|
contract_end_date = case when v_contract ? 'contract_end_date' then nullif(v_contract->>'contract_end_date','')::date else contract_end_date end,
|
||||||
|
worker_type = coalesce((v_role->>'worker_type')::worker_type, worker_type),
|
||||||
|
-- Ohne nullif: die Spalte ist NOT NULL, ein leerer Text waere kein
|
||||||
|
-- geloeschter Wert, sondern ein Wert, den chk_mitarbeiterart abweist.
|
||||||
|
mitarbeiterart = coalesce(nullif(v_role->>'mitarbeiterart', ''), mitarbeiterart),
|
||||||
|
source = coalesce((nullif(v_role->>'source', ''))::source_type, source),
|
||||||
|
collective_agreement = coalesce((v_role->>'collective_agreement')::collective_agreement, collective_agreement),
|
||||||
|
work_days = case when v_role ? 'work_days' then v_new_work_days else work_days end,
|
||||||
|
is_betriebsrat = coalesce((v_role->>'is_betriebsrat')::boolean, is_betriebsrat),
|
||||||
|
has_dienstwagen = coalesce((v_role->>'has_dienstwagen')::boolean, has_dienstwagen),
|
||||||
|
is_laterale_fuehrung = coalesce((v_role->>'is_laterale_fuehrung')::boolean, is_laterale_fuehrung),
|
||||||
|
is_c_level = coalesce((v_role->>'is_c_level')::boolean, is_c_level),
|
||||||
|
has_kuendigungsschutz = coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz),
|
||||||
|
teilzeit_art = case when v_role ? 'teilzeit_art' then nullif(v_role->>'teilzeit_art', '') else teilzeit_art end,
|
||||||
|
-- Ohne Variante kein Enddatum: chk_teilzeit_bis verlangt es so, und
|
||||||
|
-- ein Datum ohne Sache wäre ein Rest, den niemand mehr deutet.
|
||||||
|
teilzeit_bis = case
|
||||||
|
when coalesce(nullif(v_role->>'teilzeit_art', ''), case when v_role ? 'teilzeit_art' then null else teilzeit_art end) is null then null
|
||||||
|
when v_role ? 'teilzeit_bis' then nullif(v_role->>'teilzeit_bis','')::date
|
||||||
|
else teilzeit_bis
|
||||||
|
end,
|
||||||
|
-- Fällt der Schutz weg, fallen Grund und beide Daten mit. Andernfalls
|
||||||
|
-- bliebe ein Rest ohne Bezug stehen — die Bedingungen verbieten das,
|
||||||
|
-- und der Vorgang schlüge fehl, statt das Offensichtliche zu tun.
|
||||||
|
kuendigungsschutz_grund = case
|
||||||
|
when coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz) then
|
||||||
|
case when v_role ? 'kuendigungsschutz_grund'
|
||||||
|
then nullif(v_role->>'kuendigungsschutz_grund','')
|
||||||
|
else kuendigungsschutz_grund end
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
kuendigungsschutz_ab = case
|
||||||
|
when coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz) then
|
||||||
|
case when v_role ? 'kuendigungsschutz_ab'
|
||||||
|
then nullif(v_role->>'kuendigungsschutz_ab','')::date
|
||||||
|
else kuendigungsschutz_ab end
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
kuendigungsschutz_bis = case
|
||||||
|
when coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz) then
|
||||||
|
case when v_role ? 'kuendigungsschutz_bis'
|
||||||
|
then nullif(v_role->>'kuendigungsschutz_bis','')::date
|
||||||
|
else kuendigungsschutz_bis end
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
ist_beguenstigt_behindert = coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert),
|
||||||
|
behinderung_grad = case
|
||||||
|
when coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert) then
|
||||||
|
case when v_role ? 'behinderung_grad' then nullif(v_role->>'behinderung_grad','')::integer else behinderung_grad end
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
behinderung_ab = case
|
||||||
|
when coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert) then
|
||||||
|
case when v_role ? 'behinderung_ab' then nullif(v_role->>'behinderung_ab','')::date else behinderung_ab end
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
behinderung_bis = case
|
||||||
|
when coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert) then
|
||||||
|
case when v_role ? 'behinderung_bis' then nullif(v_role->>'behinderung_bis','')::date else behinderung_bis end
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
dienstwagen_art = case
|
||||||
|
when coalesce((v_role->>'has_dienstwagen')::boolean, has_dienstwagen) then
|
||||||
|
coalesce(nullif(v_role->>'dienstwagen_art', ''), dienstwagen_art, 'Verbrenner')
|
||||||
|
else null
|
||||||
|
end
|
||||||
|
where id = v_employee_id;
|
||||||
|
elsif jsonb_array_length(v_person_changes) > 0 or jsonb_array_length(v_contract_changes) > 0 then
|
||||||
|
insert into pending_org_changes (employee_id, change_type, effective_date, payload)
|
||||||
|
values (v_employee_id, 'contract_change', v_effective_date, payload)
|
||||||
|
returning id into v_pending_id;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if jsonb_array_length(v_person_changes) > 0 then
|
||||||
|
insert into employee_history (employee_id, event_date, event_type, description, changes, pending_id)
|
||||||
|
values (v_employee_id, v_effective_date, 'Stammdatenänderung',
|
||||||
|
'Geänderte Felder: ' || app_aenderungsfelder(v_person_changes) || ', wirksam ab ' || v_effective_date, v_person_changes, v_pending_id);
|
||||||
|
insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details, changes)
|
||||||
|
values (app_current_user_id(), current_actor_name(), 'Stammdatenänderung', v_name, v_employee_id,
|
||||||
|
app_aenderungsfelder(v_person_changes) || ', wirksam ab ' || v_effective_date, v_person_changes);
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if jsonb_array_length(v_contract_changes) > 0 then
|
||||||
|
insert into employee_history (employee_id, event_date, event_type, description, changes, pending_id)
|
||||||
|
values (v_employee_id, v_effective_date, 'Vertragsänderung',
|
||||||
|
'Geänderte Felder: ' || app_aenderungsfelder(v_contract_changes) || ', wirksam ab ' || v_effective_date, v_contract_changes, v_pending_id);
|
||||||
|
insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details, changes)
|
||||||
|
values (app_current_user_id(), current_actor_name(), 'Vertragsänderung', v_name, v_employee_id,
|
||||||
|
app_aenderungsfelder(v_contract_changes) || ', wirksam ab ' || v_effective_date, v_contract_changes);
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- ── Die Übernahme ────────────────────────────────────────────────
|
||||||
|
--
|
||||||
|
-- Nur in dieser einen Richtung. Wer von extern nach intern wechselt, wird
|
||||||
|
-- übernommen; der umgekehrte Weg ist kein Vorgang, den es fachlich gibt.
|
||||||
|
--
|
||||||
|
-- Das Ereignis entsteht **zusätzlich** zur Vertragsänderung darüber und
|
||||||
|
-- nicht an ihrer Stelle: die eine hält fest, dass ein Feld sich geändert
|
||||||
|
-- hat und worauf (und lässt sich darüber zurücknehmen), das andere, dass
|
||||||
|
-- dieser Wechsel eine Übernahme war — und nur das lässt sich zählen.
|
||||||
|
--
|
||||||
|
-- `v_effective_date`, nicht `now()`: eine auf später datierte Übernahme
|
||||||
|
-- gehört in die Akte auf den Tag, an dem sie gilt. Bei einer sofortigen
|
||||||
|
-- ist das heute.
|
||||||
|
if v_role ? 'source' and v_old.source::text = 'Extern' and v_role->>'source' = 'Intern' then
|
||||||
|
insert into employee_history (employee_id, event_date, event_type, description, pending_id)
|
||||||
|
values (v_employee_id, v_effective_date, 'Übernahme',
|
||||||
|
'Übernahme von extern auf intern, wirksam ab ' || v_effective_date, v_pending_id);
|
||||||
|
|
||||||
|
insert into audit_log (actor_user_id, actor_name, action, target_label, target_employee_id, details)
|
||||||
|
values (app_current_user_id(), current_actor_name(), 'Übernahme', v_name, v_employee_id,
|
||||||
|
'Von extern auf intern, wirksam ab ' || v_effective_date);
|
||||||
|
end if;
|
||||||
|
end;
|
||||||
|
$function$;
|
||||||
|
|
||||||
|
-- ── apply_due_pending_changes (Vorlage: 20260924180000_datierte_aenderung_vollstaendig.sql) ──────────
|
||||||
|
CREATE OR REPLACE FUNCTION public.apply_due_pending_changes()
|
||||||
|
RETURNS integer
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path TO 'public', 'pg_temp'
|
||||||
|
AS $function$
|
||||||
|
declare
|
||||||
|
v_rec record;
|
||||||
|
v_count integer := 0;
|
||||||
|
v_role jsonb;
|
||||||
|
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
|
||||||
|
-- Die Besetzung wandert mit. Hier stand zuletzt nur noch das Setzen
|
||||||
|
-- des Titels aus einem Feld, das in diesem payload gar nicht vorkommt
|
||||||
|
-- — die Fassung von vor der Umstellung auf Planstellen. Eine auf
|
||||||
|
-- später datierte Versetzung galt damit am Stichtag als angewendet
|
||||||
|
-- und bewegte niemanden.
|
||||||
|
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;
|
||||||
|
-- Eine Beförderung kann die Planstelle wechseln. Ohne diesen Zweig
|
||||||
|
-- stünde am Stichtag der neue Titel an der Person und sie säsze
|
||||||
|
-- weiterhin auf der alten Stelle — im Organigramm unverändert.
|
||||||
|
if nullif(v_rec.payload->>'target_position_id', '') is not null 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);
|
||||||
|
end if;
|
||||||
|
|
||||||
|
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),
|
||||||
|
-- Auch bei einer *geplanten* Rückkehr: sonst käme jemand am
|
||||||
|
-- Stichtag mit reduzierten Stunden zurück, und der Grund dafür
|
||||||
|
-- wäre verschwunden.
|
||||||
|
teilzeit_art = case when v_rec.payload ? 'teilzeit_art'
|
||||||
|
then nullif(v_rec.payload->>'teilzeit_art', '') else teilzeit_art end,
|
||||||
|
teilzeit_bis = case when v_rec.payload ? 'teilzeit_art'
|
||||||
|
then nullif(v_rec.payload->>'teilzeit_bis', '')::date else teilzeit_bis end
|
||||||
|
where id = v_rec.employee_id;
|
||||||
|
|
||||||
|
elsif v_rec.change_type = 'contract_change' then
|
||||||
|
v_role := v_rec.payload->'role';
|
||||||
|
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 = case when v_rec.payload->'person' ? 'sv_nummer' then nullif(v_rec.payload->'person'->>'sv_nummer', '') else sv_nummer end,
|
||||||
|
nationality = coalesce(v_rec.payload->'person'->>'nationality', nationality),
|
||||||
|
address = case when v_rec.payload->'person' ? 'address' then nullif(v_rec.payload->'person'->>'address', '') else address end,
|
||||||
|
postal_code = case when v_rec.payload->'person' ? 'postal_code' then nullif(v_rec.payload->'person'->>'postal_code', '') else postal_code end,
|
||||||
|
city = case when v_rec.payload->'person' ? 'city' then nullif(v_rec.payload->'person'->>'city', '') else city end,
|
||||||
|
address_country = case when v_rec.payload->'person' ? 'address_country' then nullif(v_rec.payload->'person'->>'address_country', '') else address_country end,
|
||||||
|
email = case when v_rec.payload->'person' ? 'email' then nullif(v_rec.payload->'person'->>'email', '') else email end,
|
||||||
|
phone = case when v_rec.payload->'person' ? 'phone' then nullif(v_rec.payload->'person'->>'phone', '') else phone end,
|
||||||
|
company_email = case when v_rec.payload->'person' ? 'company_email' then nullif(v_rec.payload->'person'->>'company_email', '') else company_email end,
|
||||||
|
cornerstone_id = case when v_rec.payload->'person' ? 'cornerstone_id' then nullif(v_rec.payload->'person'->>'cornerstone_id', '') else cornerstone_id end,
|
||||||
|
-- Der Notfallkontakt und die Titel fehlten hier. Eine auf später
|
||||||
|
-- datierte Änderung stand damit in der Akte, kam aber nie an der
|
||||||
|
-- Person an: change_employee_data schreibt sie sofort, dieser Zweig
|
||||||
|
-- kannte sie nicht. Dieselbe Lücke wie einst bei der Gruppe `role`
|
||||||
|
-- (20260915120000).
|
||||||
|
emergency_contact_name = case when v_rec.payload->'person' ? 'emergency_contact_name'
|
||||||
|
then nullif(v_rec.payload->'person'->>'emergency_contact_name', '') else emergency_contact_name end,
|
||||||
|
emergency_contact_phone = case when v_rec.payload->'person' ? 'emergency_contact_phone'
|
||||||
|
then nullif(v_rec.payload->'person'->>'emergency_contact_phone', '') else emergency_contact_phone end,
|
||||||
|
emergency_contact_relation = case when v_rec.payload->'person' ? 'emergency_contact_relation'
|
||||||
|
then nullif(v_rec.payload->'person'->>'emergency_contact_relation', '') else emergency_contact_relation end,
|
||||||
|
-- Wie work_days weiter unten: eine leere Liste ist eine Angabe, kein
|
||||||
|
-- fehlender Wert, deshalb coalesce auf '{}' statt auf den alten Stand.
|
||||||
|
title_prefix = case when v_rec.payload->'person' ? 'title_prefix'
|
||||||
|
then coalesce((select array_agg(elem) from jsonb_array_elements_text(v_rec.payload->'person'->'title_prefix') elem), '{}')
|
||||||
|
else title_prefix end,
|
||||||
|
title_suffix = case when v_rec.payload->'person' ? 'title_suffix'
|
||||||
|
then coalesce((select array_agg(elem) from jsonb_array_elements_text(v_rec.payload->'person'->'title_suffix') elem), '{}')
|
||||||
|
else title_suffix end,
|
||||||
|
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),
|
||||||
|
contract_end_date = case when v_rec.payload->'contract' ? 'contract_end_date'
|
||||||
|
then nullif(v_rec.payload->'contract'->>'contract_end_date','')::date
|
||||||
|
else contract_end_date end,
|
||||||
|
worker_type = coalesce((v_role->>'worker_type')::worker_type, worker_type),
|
||||||
|
mitarbeiterart = coalesce(nullif(v_role->>'mitarbeiterart', ''), mitarbeiterart),
|
||||||
|
source = coalesce((nullif(v_role->>'source', ''))::source_type, source),
|
||||||
|
collective_agreement = coalesce((v_role->>'collective_agreement')::collective_agreement, collective_agreement),
|
||||||
|
work_days = case when v_role ? 'work_days'
|
||||||
|
then coalesce((select array_agg(elem) from jsonb_array_elements_text(v_role->'work_days') elem), '{}')
|
||||||
|
else work_days end,
|
||||||
|
is_betriebsrat = coalesce((v_role->>'is_betriebsrat')::boolean, is_betriebsrat),
|
||||||
|
has_dienstwagen = coalesce((v_role->>'has_dienstwagen')::boolean, has_dienstwagen),
|
||||||
|
is_laterale_fuehrung = coalesce((v_role->>'is_laterale_fuehrung')::boolean, is_laterale_fuehrung),
|
||||||
|
is_c_level = coalesce((v_role->>'is_c_level')::boolean, is_c_level),
|
||||||
|
dienstwagen_art = case
|
||||||
|
when coalesce((v_role->>'has_dienstwagen')::boolean, has_dienstwagen) then
|
||||||
|
coalesce(nullif(v_role->>'dienstwagen_art', ''), dienstwagen_art, 'Verbrenner')
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
teilzeit_art = case when v_role ? 'teilzeit_art' then nullif(v_role->>'teilzeit_art', '') else teilzeit_art end,
|
||||||
|
teilzeit_bis = case
|
||||||
|
when coalesce(nullif(v_role->>'teilzeit_art', ''), case when v_role ? 'teilzeit_art' then null else teilzeit_art end) is null then null
|
||||||
|
when v_role ? 'teilzeit_bis' then nullif(v_role->>'teilzeit_bis','')::date
|
||||||
|
else teilzeit_bis
|
||||||
|
end,
|
||||||
|
hat_aufenthaltstitel = coalesce((v_rec.payload->'person'->>'hat_aufenthaltstitel')::boolean, hat_aufenthaltstitel),
|
||||||
|
aufenthaltstitel_bis = case
|
||||||
|
when coalesce((v_rec.payload->'person'->>'hat_aufenthaltstitel')::boolean, hat_aufenthaltstitel) then
|
||||||
|
case when v_rec.payload->'person' ? 'aufenthaltstitel_bis'
|
||||||
|
then nullif(v_rec.payload->'person'->>'aufenthaltstitel_bis','')::date
|
||||||
|
else aufenthaltstitel_bis end
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
has_kuendigungsschutz = coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz),
|
||||||
|
kuendigungsschutz_grund = case
|
||||||
|
when coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz) then
|
||||||
|
case when v_role ? 'kuendigungsschutz_grund' then nullif(v_role->>'kuendigungsschutz_grund','') else kuendigungsschutz_grund end
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
kuendigungsschutz_ab = case
|
||||||
|
when coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz) then
|
||||||
|
case when v_role ? 'kuendigungsschutz_ab' then nullif(v_role->>'kuendigungsschutz_ab','')::date else kuendigungsschutz_ab end
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
kuendigungsschutz_bis = case
|
||||||
|
when coalesce((v_role->>'has_kuendigungsschutz')::boolean, has_kuendigungsschutz) then
|
||||||
|
case when v_role ? 'kuendigungsschutz_bis' then nullif(v_role->>'kuendigungsschutz_bis','')::date else kuendigungsschutz_bis end
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
ist_beguenstigt_behindert = coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert),
|
||||||
|
behinderung_grad = case
|
||||||
|
when coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert) then
|
||||||
|
case when v_role ? 'behinderung_grad' then nullif(v_role->>'behinderung_grad','')::integer else behinderung_grad end
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
behinderung_ab = case
|
||||||
|
when coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert) then
|
||||||
|
case when v_role ? 'behinderung_ab' then nullif(v_role->>'behinderung_ab','')::date else behinderung_ab end
|
||||||
|
else null
|
||||||
|
end,
|
||||||
|
behinderung_bis = case
|
||||||
|
when coalesce((v_role->>'ist_beguenstigt_behindert')::boolean, ist_beguenstigt_behindert) then
|
||||||
|
case when v_role ? 'behinderung_bis' then nullif(v_role->>'behinderung_bis','')::date else behinderung_bis end
|
||||||
|
else null
|
||||||
|
end
|
||||||
|
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')::text,
|
||||||
|
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;
|
||||||
|
$function$;
|
||||||
|
|
||||||
|
-- Selbstprüfung.
|
||||||
|
do $$
|
||||||
|
declare
|
||||||
|
v_fn text;
|
||||||
|
begin
|
||||||
|
if not exists (
|
||||||
|
select 1 from information_schema.columns
|
||||||
|
where table_schema = 'public' and table_name = 'employees' and column_name = 'cornerstone_id'
|
||||||
|
) then
|
||||||
|
raise exception 'Die Spalte cornerstone_id fehlt.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- Jede der fünf einzeln: fehlt sie in einer, verschwindet das Feld genau
|
||||||
|
-- in deren Weg, ohne dass es auffällt.
|
||||||
|
foreach v_fn in array array[
|
||||||
|
'public.hire_employee(jsonb)',
|
||||||
|
'public.rehire_employee(jsonb)',
|
||||||
|
'public.change_employee_data(jsonb)',
|
||||||
|
'public.apply_due_pending_changes()'
|
||||||
|
] loop
|
||||||
|
if pg_get_functiondef(v_fn::regprocedure) not like '%cornerstone_id%' then
|
||||||
|
raise exception '% kennt die Cornerstone-ID nicht.', v_fn;
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
|
||||||
|
if app_feld_karte() -> 'Cornerstone-ID' is null then
|
||||||
|
raise exception 'Die Feldkarte kennt die Cornerstone-ID nicht — der Eintrag in der Historie waere nicht korrigierbar.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- Und was zuletzt hier verlorenging, bleibt drin.
|
||||||
|
if pg_get_functiondef('public.apply_due_pending_changes()'::regprocedure) not like '%emergency_contact_name%' then
|
||||||
|
raise exception 'Der Nachtlauf hat den Notfallkontakt wieder verloren.';
|
||||||
|
end if;
|
||||||
|
if pg_get_functiondef('public.apply_due_pending_changes()'::regprocedure) not like '%target_position_id%' then
|
||||||
|
raise exception 'Der Nachtlauf hat die Zielplanstelle wieder verloren.';
|
||||||
|
end if;
|
||||||
|
end
|
||||||
|
$$;
|
||||||
@@ -160,6 +160,7 @@ export type CornerstoneQuelle = {
|
|||||||
title_suffix: string[];
|
title_suffix: string[];
|
||||||
gender: string;
|
gender: string;
|
||||||
company_email: string | null;
|
company_email: string | null;
|
||||||
|
cornerstone_id: string | null;
|
||||||
address: string | null;
|
address: string | null;
|
||||||
postal_code: string | null;
|
postal_code: string | null;
|
||||||
city: string | null;
|
city: string | null;
|
||||||
@@ -187,19 +188,24 @@ export type CornerstoneKontext = {
|
|||||||
export function baueCornerstoneZeile(p: CornerstoneQuelle, k: CornerstoneKontext): CornerstoneZeile {
|
export function baueCornerstoneZeile(p: CornerstoneQuelle, k: CornerstoneKontext): CornerstoneZeile {
|
||||||
// Zwei Kennungen, zwei Herkünfte:
|
// Zwei Kennungen, zwei Herkünfte:
|
||||||
//
|
//
|
||||||
// User ID + Username die UUID aus Alpenwerk (employees.id)
|
// User ID + Username die Cornerstone-ID (employees.cornerstone_id)
|
||||||
// Local System ID die Personalnummer, wie LOGA sie vergibt
|
// Local System ID die Personalnummer, wie LOGA sie vergibt
|
||||||
//
|
//
|
||||||
// Benutzername und Benutzer-ID sind in Alpenwerk stets derselbe Wert —
|
// Benutzername und Benutzer-ID sind stets derselbe Wert — liefen sie
|
||||||
// liefen sie auseinander, zeigte Cornerstone zwei Kennungen für eine
|
// auseinander, zeigte Cornerstone zwei Kennungen für eine Person, und
|
||||||
// Person, und jeder Verweis darauf träfe womöglich die falsche.
|
// jeder Verweis darauf träfe womöglich die falsche.
|
||||||
//
|
//
|
||||||
// Die UUID und nicht die Personalnummer, weil sie die Kennung ist, die
|
// Hier stand die Alpenwerk-UUID, solange es nichts Besseres gab. Seit
|
||||||
// sich nie ändert: eine Personalnummer kann berichtigt werden, und dann
|
// 20260928100000 führt Alpenwerk die Kennung mit, unter der Cornerstone
|
||||||
// wäre die Person in Cornerstone eine andere.
|
// die Person selbst kennt; die UUID sagte dort niemandem etwas.
|
||||||
|
//
|
||||||
|
// **Leer, wenn keine hinterlegt ist.** Ein Rückfall auf die UUID brächte
|
||||||
|
// zwei Kennungsarten in eine Datei, ohne dass es jemandem auffiele — und
|
||||||
|
// legte in Cornerstone eine zweite Person neben der bestehenden an. Eine
|
||||||
|
// fehlende Kennung ist eine fehlende Angabe und soll als solche auffallen.
|
||||||
//
|
//
|
||||||
// Der Anmeldename des Verzeichnisdienstes steht nur im Customfield AD.
|
// Der Anmeldename des Verzeichnisdienstes steht nur im Customfield AD.
|
||||||
const kennung = p.id;
|
const kennung = p.cornerstone_id ?? "";
|
||||||
const nummer = String(p.personnel_number);
|
const nummer = String(p.personnel_number);
|
||||||
const adName = benutzername(p.first_name, p.last_name);
|
const adName = benutzername(p.first_name, p.last_name);
|
||||||
const kst = p.position_id ? k.kostenstelle.get(p.position_id) : undefined;
|
const kst = p.position_id ? k.kostenstelle.get(p.position_id) : undefined;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export async function bestandLaden(tx: Tx): Promise<Bestand> {
|
|||||||
.select(["position_id"])
|
.select(["position_id"])
|
||||||
.where((eb) => eb.or([eb("valid_to", "is", null), eb("valid_to", ">=", heute)]))
|
.where((eb) => eb.or([eb("valid_to", "is", null), eb("valid_to", ">=", heute)]))
|
||||||
.execute(),
|
.execute(),
|
||||||
tx.selectFrom("employees").select(["id", "personnel_number", "email", "company_email", "sv_nummer"]).execute(),
|
tx.selectFrom("employees").select(["id", "personnel_number", "email", "company_email", "cornerstone_id", "sv_nummer"]).execute(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const besetzt = new Set(besetzungen.map((b) => b.position_id));
|
const besetzt = new Set(besetzungen.map((b) => b.position_id));
|
||||||
@@ -73,6 +73,7 @@ export async function bestandLaden(tx: Tx): Promise<Bestand> {
|
|||||||
// kann sie fehlen.
|
// kann sie fehlen.
|
||||||
emails: new Set(personen.filter((e) => e.email).map((e) => e.email!.toLowerCase())),
|
emails: new Set(personen.filter((e) => e.email).map((e) => e.email!.toLowerCase())),
|
||||||
firmenEmails: new Set(personen.filter((e) => e.company_email).map((e) => e.company_email!.toLowerCase())),
|
firmenEmails: new Set(personen.filter((e) => e.company_email).map((e) => e.company_email!.toLowerCase())),
|
||||||
|
cornerstoneIds: new Set(personen.filter((e) => e.cornerstone_id).map((e) => e.cornerstone_id!)),
|
||||||
svNummern: new Set(personen.filter((e) => e.sv_nummer).map((e) => normalizeSvnr(e.sv_nummer!))),
|
svNummern: new Set(personen.filter((e) => e.sv_nummer).map((e) => normalizeSvnr(e.sv_nummer!))),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -211,6 +212,7 @@ export async function laden(
|
|||||||
address_country: txt(w.address_country),
|
address_country: txt(w.address_country),
|
||||||
email: txt(w.email)!,
|
email: txt(w.email)!,
|
||||||
company_email: txt(w.company_email),
|
company_email: txt(w.company_email),
|
||||||
|
cornerstone_id: txt(w.cornerstone_id),
|
||||||
phone: txt(w.phone),
|
phone: txt(w.phone),
|
||||||
title_prefix: liste(w.title_prefix) ?? [],
|
title_prefix: liste(w.title_prefix) ?? [],
|
||||||
title_suffix: liste(w.title_suffix) ?? [],
|
title_suffix: liste(w.title_suffix) ?? [],
|
||||||
|
|||||||
@@ -240,6 +240,14 @@ export const BLATT_PERSONEN: BlattSchema = {
|
|||||||
hinweis: "Dienstliche Adresse. Freiwillig, eindeutig. Geht in den Export für die Mitarbeiterbefragung.",
|
hinweis: "Dienstliche Adresse. Freiwillig, eindeutig. Geht in den Export für die Mitarbeiterbefragung.",
|
||||||
beispiel: "s.aigner@manner.com",
|
beispiel: "s.aigner@manner.com",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Cornerstone-ID",
|
||||||
|
ziel: "cornerstone_id",
|
||||||
|
pflicht: false,
|
||||||
|
typ: { art: "text" },
|
||||||
|
hinweis: "Kennung im Lernsystem Cornerstone. Freiwillig, eindeutig. Füllt dort „User ID“ und „Username“.",
|
||||||
|
beispiel: "10000382",
|
||||||
|
},
|
||||||
{ name: "Private Telefonnummer", ziel: "phone", pflicht: false, typ: { art: "text" }, hinweis: "", beispiel: "+43 660 1234567" },
|
{ name: "Private Telefonnummer", ziel: "phone", pflicht: false, typ: { art: "text" }, hinweis: "", beispiel: "+43 660 1234567" },
|
||||||
{ name: "Adresse", ziel: "address", pflicht: false, typ: { art: "text" }, hinweis: "", beispiel: "Hauptstraße 1" },
|
{ name: "Adresse", ziel: "address", pflicht: false, typ: { art: "text" }, hinweis: "", beispiel: "Hauptstraße 1" },
|
||||||
{ name: "PLZ", ziel: "postal_code", pflicht: false, typ: { art: "text" }, hinweis: "", beispiel: "1010" },
|
{ name: "PLZ", ziel: "postal_code", pflicht: false, typ: { art: "text" }, hinweis: "", beispiel: "1010" },
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export type Bestand = {
|
|||||||
personalnummern: Map<number, string>;
|
personalnummern: Map<number, string>;
|
||||||
emails: Set<string>;
|
emails: Set<string>;
|
||||||
firmenEmails: Set<string>;
|
firmenEmails: Set<string>;
|
||||||
|
cornerstoneIds: Set<string>;
|
||||||
svNummern: Set<string>;
|
svNummern: Set<string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ export const LEERER_BESTAND: Bestand = {
|
|||||||
personalnummern: new Map(),
|
personalnummern: new Map(),
|
||||||
emails: new Set(),
|
emails: new Set(),
|
||||||
firmenEmails: new Set(),
|
firmenEmails: new Set(),
|
||||||
|
cornerstoneIds: new Set(),
|
||||||
svNummern: new Set(),
|
svNummern: new Set(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -265,6 +267,7 @@ export function pruefe(blaetter: ImportSheet[], bestand: Bestand = LEERER_BESTAN
|
|||||||
const personalnummern = new Set([...bestand.personalnummern.keys()]);
|
const personalnummern = new Set([...bestand.personalnummern.keys()]);
|
||||||
const emails = new Set([...bestand.emails]);
|
const emails = new Set([...bestand.emails]);
|
||||||
const firmenEmails = new Set([...bestand.firmenEmails]);
|
const firmenEmails = new Set([...bestand.firmenEmails]);
|
||||||
|
const cornerstoneIds = new Set([...bestand.cornerstoneIds]);
|
||||||
const svNummern = new Set([...bestand.svNummern]);
|
const svNummern = new Set([...bestand.svNummern]);
|
||||||
const belegtePlanstellen = new Map<string, number>();
|
const belegtePlanstellen = new Map<string, number>();
|
||||||
const eintritte = new Map<number, string>();
|
const eintritte = new Map<number, string>();
|
||||||
@@ -297,6 +300,17 @@ export function pruefe(blaetter: ImportSheet[], bestand: Bestand = LEERER_BESTAN
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Die Kennung gehört genau einer Person; die Spalte trägt einen
|
||||||
|
// eindeutigen Index. Ohne Prüfung hier bräche erst das Schreiben ab —
|
||||||
|
// mitten im Import und ohne Zeilennummer.
|
||||||
|
const cornerstoneId = s(w.cornerstone_id);
|
||||||
|
if (cornerstoneId) {
|
||||||
|
if (cornerstoneIds.has(cornerstoneId)) {
|
||||||
|
melde("Personen", z.zeile, "Cornerstone-ID", "Kommt bereits vor.", cornerstoneId);
|
||||||
|
}
|
||||||
|
cornerstoneIds.add(cornerstoneId);
|
||||||
|
}
|
||||||
|
|
||||||
const geburt = s(w.birth_date);
|
const geburt = s(w.birth_date);
|
||||||
const svRoh = s(w.sv_nummer);
|
const svRoh = s(w.sv_nummer);
|
||||||
if (svRoh) {
|
if (svRoh) {
|
||||||
|
|||||||
@@ -214,6 +214,14 @@ export type Database = {
|
|||||||
* das Haus verlassen. Der Honestly-Export nimmt diese hier.
|
* das Haus verlassen. Der Honestly-Export nimmt diese hier.
|
||||||
*/
|
*/
|
||||||
company_email: string | null;
|
company_email: string | null;
|
||||||
|
/**
|
||||||
|
* Kennung der Person im Lernsystem Cornerstone (CSOD).
|
||||||
|
*
|
||||||
|
* Freiwillig und eindeutig. Als Text geführt — führende Nullen
|
||||||
|
* gingen in einer numerischen Spalte verloren, und gerechnet wird
|
||||||
|
* mit der Kennung ohnehin nicht.
|
||||||
|
*/
|
||||||
|
cornerstone_id: string | null;
|
||||||
phone: string | null;
|
phone: string | null;
|
||||||
job_title: string;
|
job_title: string;
|
||||||
location_id: string;
|
location_id: string;
|
||||||
@@ -294,6 +302,7 @@ export type Database = {
|
|||||||
address_country?: string | null;
|
address_country?: string | null;
|
||||||
email?: string | null;
|
email?: string | null;
|
||||||
company_email?: string | null;
|
company_email?: string | null;
|
||||||
|
cornerstone_id?: string | null;
|
||||||
phone?: string | null;
|
phone?: string | null;
|
||||||
job_title: string;
|
job_title: string;
|
||||||
location_id: string;
|
location_id: string;
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ export function vorbelegungAus(employee: EmployeeRow): Partial<HireDraftData> {
|
|||||||
// stillschweigend. Sichtbar vorbelegt lässt sie sich bewusst ersetzen
|
// stillschweigend. Sichtbar vorbelegt lässt sie sich bewusst ersetzen
|
||||||
// oder leeren.
|
// oder leeren.
|
||||||
companyEmail: employee.company_email ?? "",
|
companyEmail: employee.company_email ?? "",
|
||||||
|
// Aus demselben Grund vorbelegt wie die Firmenadresse: der Assistent
|
||||||
|
// schickt das Feld in jedem Fall mit, und leer geliefert löschte es die
|
||||||
|
// gespeicherte Kennung stillschweigend.
|
||||||
|
cornerstoneId: employee.cornerstone_id ?? "",
|
||||||
phone: employee.phone ?? "",
|
phone: employee.phone ?? "",
|
||||||
locationId: employee.location_id,
|
locationId: employee.location_id,
|
||||||
besetzung: employee.source,
|
besetzung: employee.source,
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ const VORGABE =
|
|||||||
"User ID,Local System ID,Prefix,First Name,Middle Name,Last Name,Suffix,Username,Approver,Manager,Absent,Allow Reconciliation,Email,Required Training Approvals,Gender,Exempt,Ethnicity,Months of Service,Compensation Currency,Address Line 1,Address Line 2,City,State,Zip,Mailstop,Country,Original Hire Date,Last Hire Date,Phone,Mobile,Home Phone,Fax,Status,User activation date,User de-activation date,Personal Email,Language,Time Zone,User Type,Employment Status,Leave Reason,Termination Type,Termination Reason,Termination Date,Eligible For Rehire,Rehired Employee,Division ID,Position ID,Grade ID,Cost Center ID,Location ID,Retention Rules,Organisationsstufe,Customfield ID AD,Customfield ID Doxis,Customfield ID Interflex,Customfield ID LGVplus";
|
"User ID,Local System ID,Prefix,First Name,Middle Name,Last Name,Suffix,Username,Approver,Manager,Absent,Allow Reconciliation,Email,Required Training Approvals,Gender,Exempt,Ethnicity,Months of Service,Compensation Currency,Address Line 1,Address Line 2,City,State,Zip,Mailstop,Country,Original Hire Date,Last Hire Date,Phone,Mobile,Home Phone,Fax,Status,User activation date,User de-activation date,Personal Email,Language,Time Zone,User Type,Employment Status,Leave Reason,Termination Type,Termination Reason,Termination Date,Eligible For Rehire,Rehired Employee,Division ID,Position ID,Grade ID,Cost Center ID,Location ID,Retention Rules,Organisationsstufe,Customfield ID AD,Customfield ID Doxis,Customfield ID Interflex,Customfield ID LGVplus";
|
||||||
|
|
||||||
const UUID = "8f2b1c40-9d3e-4a77-b2c1-5e6f70a81234";
|
const UUID = "8f2b1c40-9d3e-4a77-b2c1-5e6f70a81234";
|
||||||
|
// Die Kennung, unter der Cornerstone die Person kennt — seit 20260928100000
|
||||||
|
// fuehrt Alpenwerk sie mit, und sie steht in User ID und Username.
|
||||||
|
const CS_ID = "10000382";
|
||||||
|
|
||||||
const kontext: CornerstoneKontext = {
|
const kontext: CornerstoneKontext = {
|
||||||
managerNummer: new Map([["chef", 1000]]),
|
managerNummer: new Map([["chef", 1000]]),
|
||||||
@@ -47,6 +50,7 @@ function person(teil: Partial<CornerstoneQuelle> = {}): CornerstoneQuelle {
|
|||||||
title_suffix: [],
|
title_suffix: [],
|
||||||
gender: "m",
|
gender: "m",
|
||||||
company_email: "m.test@alpenwerk.test.com",
|
company_email: "m.test@alpenwerk.test.com",
|
||||||
|
cornerstone_id: CS_ID,
|
||||||
address: "Teststrasse 2",
|
address: "Teststrasse 2",
|
||||||
postal_code: "1010",
|
postal_code: "1010",
|
||||||
city: "Wien",
|
city: "Wien",
|
||||||
@@ -142,7 +146,7 @@ describe("Eine Zeile gegen das Muster vom 27.09.2026", () => {
|
|||||||
"Local System ID": "9002",
|
"Local System ID": "9002",
|
||||||
"First Name": "Max",
|
"First Name": "Max",
|
||||||
"Last Name": "Alpentest",
|
"Last Name": "Alpentest",
|
||||||
Username: UUID,
|
Username: CS_ID,
|
||||||
Email: "m.test@alpenwerk.test.com",
|
Email: "m.test@alpenwerk.test.com",
|
||||||
Gender: "male",
|
Gender: "male",
|
||||||
"Compensation Currency": "EUR",
|
"Compensation Currency": "EUR",
|
||||||
@@ -246,12 +250,24 @@ describe("Benutzername und Benutzer-ID", () => {
|
|||||||
expect(z.Username).toBe(z["User ID"]);
|
expect(z.Username).toBe(z["User ID"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sind die UUID aus Alpenwerk, nicht die Personalnummer", () => {
|
it("sind die Cornerstone-ID, nicht die UUID und nicht die Personalnummer", () => {
|
||||||
// Die UUID aendert sich nie; eine Personalnummer kann berichtigt werden,
|
// Hier stand die Alpenwerk-UUID, solange Alpenwerk die Kennung des
|
||||||
// und dann waere die Person in Cornerstone eine andere.
|
// Lernsystems nicht kannte. Sie sagte dort niemandem etwas.
|
||||||
const z = zeile();
|
const z = zeile();
|
||||||
expect(z["User ID"]).toBe(UUID);
|
expect(z["User ID"]).toBe(CS_ID);
|
||||||
expect(z.Username).toBe(UUID);
|
expect(z.Username).toBe(CS_ID);
|
||||||
|
expect(z["User ID"]).not.toBe(UUID);
|
||||||
|
expect(z["User ID"]).not.toBe("9002");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("bleiben leer, wenn keine Cornerstone-ID hinterlegt ist", () => {
|
||||||
|
// Kein Rueckfall auf die UUID: das braechte zwei Kennungsarten in eine
|
||||||
|
// Datei, ohne dass es auffiele, und legte in Cornerstone eine zweite
|
||||||
|
// Person neben der bestehenden an. Eine fehlende Angabe soll fehlen.
|
||||||
|
const z = baueCornerstoneZeile(person({ cornerstone_id: null }), kontext);
|
||||||
|
expect(z["User ID"]).toBe("");
|
||||||
|
expect(z.Username).toBe("");
|
||||||
|
expect(JSON.stringify(z)).not.toContain(UUID);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("fuehren die LOGA-Personalnummer in Local System ID", () => {
|
it("fuehren die LOGA-Personalnummer in Local System ID", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user