461 lines
26 KiB
PL/PgSQL
461 lines
26 KiB
PL/PgSQL
CREATE OR REPLACE PROCEDURE public."Hr_S_TimesheetCalculate"(
|
||
p_TenantId uuid,
|
||
p_EmployeeId uuid DEFAULT NULL,
|
||
p_From date DEFAULT NULL,
|
||
p_To date DEFAULT NULL,
|
||
p_ActorId uuid DEFAULT NULL
|
||
)
|
||
LANGUAGE plpgsql
|
||
AS $$
|
||
DECLARE
|
||
v_From date := p_From;
|
||
v_To date := p_To;
|
||
v_Today date := LOCALTIMESTAMP::date;
|
||
v_OrphanFrom date;
|
||
v_OrphanTo date;
|
||
v_Last date;
|
||
v_LimitText text;
|
||
v_Limit numeric(9, 2);
|
||
v_WeeklyLimit integer;
|
||
v_Now timestamp;
|
||
BEGIN
|
||
-- Günlük puantajı hareketlerden, izin/rapor/eğitim kayıtlarından ve Çalışma Takvimi'nden kurar.
|
||
-- p_EmployeeId boşsa kiracının bütün çalışanları. Kapalı dönemin günleri yazılmaz (yalnız haftalık
|
||
-- birikime girer); elle düzeltilmiş gün (IsManual) girdilerini korur, yalnız süreleri yeniden hesaplanır.
|
||
-- İzin/rapor kaydını yazan prosedürler de bunu çağırdığı için hata fırlatmaz.
|
||
-- Devam takibi çalışana göre iki türlüdür: PDKS Kart No'su olan ve okutması gelmeye başlamış çalışanda
|
||
-- okutmasız iş günü devamsızdır; diğer (elle takip edilen) çalışanda kayıtsız iş günü vardiya saatleriyle "geldi" sayılır
|
||
-- (IsAssumed), İK yalnız gelinmeyen günü işaretler.
|
||
-- Süre farkları T-SQL DATEDIFF(minute) gibi dakika sınırı sayısıdır (saniyeler kesilerek alınır).
|
||
IF v_From IS NULL OR v_To IS NULL OR v_To < v_From THEN
|
||
RETURN;
|
||
END IF;
|
||
|
||
-- Tek çalışanlık hesapta, kaynağı artık olmayan izin/rapor/eğitim günü (talebin tarihi değişti,
|
||
-- rapor ya da katılım silindi) aralığa katılır; böylece eski günler aynı işlemde düzelir.
|
||
IF p_EmployeeId IS NOT NULL THEN
|
||
SELECT MIN(t."WorkDate"), MAX(t."WorkDate")
|
||
INTO v_OrphanFrom, v_OrphanTo
|
||
FROM public."Hr_T_Timesheet" t
|
||
WHERE t."EmployeeId" = p_EmployeeId AND t."IsDeleted" = FALSE AND t."IsManual" = FALSE
|
||
AND t."DayType" IN ('Leave', 'Sick', 'Training')
|
||
AND (t."WorkDate" < v_From OR t."WorkDate" > v_To)
|
||
AND NOT EXISTS (SELECT 1 FROM public."Adm_T_UserSickLeave" s
|
||
WHERE s."UserId" = t."EmployeeId" AND s."IsDeleted" = FALSE
|
||
AND t."WorkDate" BETWEEN s."StartDate"::date AND s."EndDate"::date)
|
||
AND NOT EXISTS (SELECT 1 FROM public."Hr_T_LeaveRequest" q
|
||
WHERE q."EmployeeId" = t."EmployeeId" AND q."IsDeleted" = FALSE AND q."ApprovalStatus" = 'Onaylandı'
|
||
AND t."WorkDate" BETWEEN q."StartDate"::date AND q."EndDate"::date)
|
||
AND NOT EXISTS (SELECT 1 FROM public."Hr_T_TrainingParticipant" p
|
||
INNER JOIN public."Hr_T_Training" g ON g."Id" = p."TrainingId" AND g."IsDeleted" = FALSE
|
||
WHERE p."UserId" = t."EmployeeId" AND p."IsDeleted" = FALSE AND p."AttendanceStatus" = 'Attended'
|
||
AND t."WorkDate" BETWEEN g."StartDate" AND g."EndDate");
|
||
|
||
IF v_OrphanFrom < v_From THEN v_From := v_OrphanFrom; END IF;
|
||
IF v_OrphanTo > v_To THEN v_To := v_OrphanTo; END IF;
|
||
END IF;
|
||
|
||
-- Fazla mesai haftalık birikimle hesaplandığı için aralık tam haftalara (Pzt–Paz) genişler.
|
||
-- 1900-01-01 Pazartesidir; gün sırası oturum ayarlarından bağımsızdır.
|
||
v_From := v_From - ((v_From - DATE '1900-01-01') % 7);
|
||
v_To := v_To + (6 - ((v_To - DATE '1900-01-01') % 7));
|
||
v_Last := CASE WHEN v_To > v_Today THEN v_Today ELSE v_To END;
|
||
IF v_Last < v_From THEN
|
||
RETURN;
|
||
END IF;
|
||
|
||
v_LimitText := btrim(replace(public."Hr_F_HrSetting"(p_TenantId, 'Adm.HrSettings.Attendance.WeeklyLimitHours'), ',', '.'));
|
||
IF v_LimitText ~ '^[+-]?([0-9]+(\.[0-9]*)?|\.[0-9]+)$' THEN
|
||
BEGIN
|
||
v_Limit := v_LimitText::numeric(9, 2);
|
||
EXCEPTION WHEN numeric_value_out_of_range THEN
|
||
v_Limit := NULL;
|
||
END;
|
||
END IF;
|
||
v_WeeklyLimit := trunc(60 * COALESCE(v_Limit, 45))::integer;
|
||
|
||
-- Hafta tatili İzin Yönetimi'nin çalışma günü ayarlarıdır (tek kaynak); tanım yoksa Pzt–Cum.
|
||
DROP TABLE IF EXISTS tc_work;
|
||
CREATE TEMP TABLE tc_work (DayIndex integer PRIMARY KEY, IsWork boolean) ON COMMIT DROP;
|
||
INSERT INTO tc_work (DayIndex, IsWork)
|
||
SELECT d.DayIndex,
|
||
CASE
|
||
WHEN s.Value IS NULL THEN d.DayIndex < 5
|
||
WHEN lower(s.Value) = 'true' THEN TRUE
|
||
ELSE FALSE
|
||
END
|
||
FROM (VALUES
|
||
(0, 'Adm.HrSettings.Leave.WorkMonday'),
|
||
(1, 'Adm.HrSettings.Leave.WorkTuesday'),
|
||
(2, 'Adm.HrSettings.Leave.WorkWednesday'),
|
||
(3, 'Adm.HrSettings.Leave.WorkThursday'),
|
||
(4, 'Adm.HrSettings.Leave.WorkFriday'),
|
||
(5, 'Adm.HrSettings.Leave.WorkSaturday'),
|
||
(6, 'Adm.HrSettings.Leave.WorkSunday')) d (DayIndex, SettingName)
|
||
CROSS JOIN LATERAL (SELECT public."Hr_F_HrSetting"(p_TenantId, d.SettingName)::text AS Value) s;
|
||
|
||
---------------------------------------------------------------------------------------------
|
||
-- Çalışanlar: aralıkta istihdam edilen aktif kullanıcılar + hareketi ya da puantajı olanlar
|
||
---------------------------------------------------------------------------------------------
|
||
DROP TABLE IF EXISTS tc_emp;
|
||
CREATE TEMP TABLE tc_emp
|
||
(
|
||
EmployeeId uuid PRIMARY KEY, HireDate date NULL, TerminationDate date NULL,
|
||
ShiftId uuid NULL, StartMin integer, EndMin integer, BreakMin integer, LateTol integer, EarlyTol integer,
|
||
CardSince date NULL
|
||
) ON COMMIT DROP;
|
||
|
||
-- Kartlı takip: kullanıcı kartında PDKS Kart No varsa ilk PDKS/aktarım okutmasının gününden başlar;
|
||
-- öncesi (kart yeni verildiyse) ve elle girilen hareketler elle takibi değiştirmez.
|
||
INSERT INTO tc_emp (EmployeeId, HireDate, TerminationDate, ShiftId, StartMin, EndMin, BreakMin, LateTol, EarlyTol, CardSince)
|
||
SELECT u."Id", u."HireDate"::date, u."TerminationDate"::date,
|
||
s."ShiftId", s."StartMin", s."EndMin", s."BreakMin", s."LateTol", s."EarlyTol",
|
||
CASE WHEN NULLIF(btrim(u."PdksCardNo"), '') IS NOT NULL THEN
|
||
(SELECT MIN(l."WorkDate") FROM public."Hr_T_AttendanceLog" l
|
||
WHERE l."EmployeeId" = u."Id" AND l."IsDeleted" = FALSE AND l."ProcessError" IS NULL AND l."Source" <> 'Manual')
|
||
END
|
||
FROM public."AbpUsers" u
|
||
INNER JOIN public."Hr_V_AttendanceShift" s ON s."EmployeeId" = u."Id"
|
||
WHERE u."IsDeleted" = FALSE
|
||
AND (u."TenantId" = p_TenantId OR (u."TenantId" IS NULL AND p_TenantId IS NULL))
|
||
AND (p_EmployeeId IS NULL OR u."Id" = p_EmployeeId)
|
||
AND (
|
||
(u."IsActive" = TRUE AND u."HireDate" IS NOT NULL AND u."HireDate"::date <= v_Last
|
||
AND (u."TerminationDate" IS NULL OR u."TerminationDate"::date >= v_From))
|
||
OR EXISTS (SELECT 1 FROM public."Hr_T_AttendanceLog" l
|
||
WHERE l."EmployeeId" = u."Id" AND l."IsDeleted" = FALSE AND l."WorkDate" BETWEEN v_From AND v_Last)
|
||
OR EXISTS (SELECT 1 FROM public."Hr_T_Timesheet" x
|
||
WHERE x."EmployeeId" = u."Id" AND x."IsDeleted" = FALSE AND x."WorkDate" BETWEEN v_From AND v_Last)
|
||
);
|
||
|
||
IF NOT EXISTS (SELECT 1 FROM tc_emp) THEN
|
||
RETURN;
|
||
END IF;
|
||
|
||
---------------------------------------------------------------------------------------------
|
||
-- Günler: hafta tatili, takvim türü, kapalı dönem
|
||
---------------------------------------------------------------------------------------------
|
||
DROP TABLE IF EXISTS tc_day;
|
||
CREATE TEMP TABLE tc_day (WorkDate date PRIMARY KEY, IsWorkDay boolean, CalendarType varchar(16) NULL, IsClosed boolean) ON COMMIT DROP;
|
||
|
||
INSERT INTO tc_day (WorkDate, IsWorkDay, CalendarType, IsClosed)
|
||
SELECT d.WorkDate,
|
||
COALESCE(w.IsWork, FALSE),
|
||
public."Hr_F_WorkCalendarDayType"(p_TenantId, d.WorkDate),
|
||
EXISTS (
|
||
SELECT 1 FROM public."Hr_T_TimesheetPeriod" p
|
||
WHERE p."IsDeleted" = FALSE AND p."Status" = 'Closed'
|
||
AND (p."TenantId" = p_TenantId OR (p."TenantId" IS NULL AND p_TenantId IS NULL))
|
||
AND p."PeriodYear" = EXTRACT(YEAR FROM d.WorkDate)::integer
|
||
AND p."PeriodMonth" = EXTRACT(MONTH FROM d.WorkDate)::integer)
|
||
FROM (SELECT g::date AS WorkDate FROM generate_series(v_From, v_Last, INTERVAL '1 day') g) d
|
||
LEFT JOIN tc_work w ON w.DayIndex = (d.WorkDate - DATE '1900-01-01') % 7;
|
||
|
||
---------------------------------------------------------------------------------------------
|
||
-- Çalışan × gün satırları
|
||
---------------------------------------------------------------------------------------------
|
||
DROP TABLE IF EXISTS tc_row;
|
||
CREATE TEMP TABLE tc_row
|
||
(
|
||
EmployeeId uuid NOT NULL, WorkDate date NOT NULL,
|
||
ShiftId uuid NULL, StartMin integer, EndMin integer, ShiftBreak integer, LateTol integer, EarlyTol integer,
|
||
IsEmployed boolean NOT NULL DEFAULT TRUE, HasCard boolean NOT NULL DEFAULT FALSE, IsAssumed boolean NOT NULL DEFAULT FALSE,
|
||
IsWorkDay boolean, CalendarType varchar(16) NULL, IsClosed boolean,
|
||
ExistingId uuid NULL, IsManual boolean NOT NULL DEFAULT FALSE, HalfDayLeave boolean NOT NULL DEFAULT FALSE,
|
||
LeaveTypeId uuid NULL, TrainingId uuid NULL,
|
||
DayType varchar(16) NULL, FirstIn timestamp NULL, LastOut timestamp NULL, HasEvents boolean NOT NULL DEFAULT FALSE,
|
||
ScanBreak integer NOT NULL DEFAULT 0, BreakMinutes integer NOT NULL DEFAULT 0, NetMinutes integer NOT NULL DEFAULT 0,
|
||
LateMinutes integer NOT NULL DEFAULT 0, EarlyLeaveMinutes integer NOT NULL DEFAULT 0, OvertimeMinutes integer NOT NULL DEFAULT 0,
|
||
IsIncomplete boolean NOT NULL DEFAULT FALSE,
|
||
PRIMARY KEY (EmployeeId, WorkDate)
|
||
) ON COMMIT DROP;
|
||
|
||
INSERT INTO tc_row (EmployeeId, WorkDate, ShiftId, StartMin, EndMin, ShiftBreak, LateTol, EarlyTol,
|
||
IsEmployed, HasCard, IsWorkDay, CalendarType, IsClosed)
|
||
SELECT e.EmployeeId, d.WorkDate, e.ShiftId, e.StartMin, e.EndMin, e.BreakMin, e.LateTol, e.EarlyTol,
|
||
CASE WHEN e.HireDate IS NOT NULL AND e.HireDate <= d.WorkDate
|
||
AND (e.TerminationDate IS NULL OR e.TerminationDate >= d.WorkDate) THEN TRUE ELSE FALSE END,
|
||
CASE WHEN e.CardSince <= d.WorkDate THEN TRUE ELSE FALSE END, d.IsWorkDay, d.CalendarType, d.IsClosed
|
||
FROM tc_emp e
|
||
CROSS JOIN tc_day d;
|
||
|
||
UPDATE tc_row r
|
||
SET ExistingId = t."Id", IsManual = t."IsManual"
|
||
FROM (
|
||
SELECT DISTINCT ON (x."EmployeeId", x."WorkDate") x."EmployeeId", x."WorkDate", x."Id", x."IsManual"
|
||
FROM public."Hr_T_Timesheet" x
|
||
WHERE x."IsDeleted" = FALSE AND x."WorkDate" BETWEEN v_From AND v_Last
|
||
AND (x."TenantId" = p_TenantId OR (x."TenantId" IS NULL AND p_TenantId IS NULL))
|
||
AND x."EmployeeId" IN (SELECT EmployeeId FROM tc_emp)
|
||
ORDER BY x."EmployeeId", x."WorkDate", x."IsManual" DESC NULLS LAST, x."CreationTime" NULLS FIRST
|
||
) t
|
||
WHERE t."EmployeeId" = r.EmployeeId AND t."WorkDate" = r.WorkDate;
|
||
|
||
-- Kapalı dönemin günü olduğu gibi kalır; elle düzeltilmiş günün girdileri korunur.
|
||
UPDATE tc_row r
|
||
SET DayType = t."DayType", LeaveTypeId = t."LeaveTypeId", TrainingId = t."TrainingId", FirstIn = t."FirstIn", LastOut = t."LastOut",
|
||
BreakMinutes = t."BreakMinutes",
|
||
NetMinutes = CASE WHEN r.IsClosed = TRUE THEN t."NetMinutes" ELSE 0 END,
|
||
LateMinutes = CASE WHEN r.IsClosed = TRUE THEN t."LateMinutes" ELSE 0 END,
|
||
EarlyLeaveMinutes = CASE WHEN r.IsClosed = TRUE THEN t."EarlyLeaveMinutes" ELSE 0 END,
|
||
OvertimeMinutes = CASE WHEN r.IsClosed = TRUE THEN ROUND(t."OvertimeHours" * 60, 0)::integer ELSE 0 END,
|
||
IsIncomplete = t."IsIncomplete",
|
||
IsAssumed = CASE WHEN r.IsClosed = TRUE THEN t."IsAssumed" ELSE FALSE END
|
||
FROM public."Hr_T_Timesheet" t
|
||
WHERE t."Id" = r.ExistingId
|
||
AND (r.IsClosed = TRUE OR r.IsManual = TRUE);
|
||
|
||
-- Elle devamsız/izinli/raporlu işaretlenen günde saat tutulmaz.
|
||
UPDATE tc_row
|
||
SET FirstIn = NULL, LastOut = NULL, BreakMinutes = 0
|
||
WHERE IsManual = TRUE AND IsClosed = FALSE AND DayType IN ('Absent', 'Leave', 'Sick');
|
||
|
||
---------------------------------------------------------------------------------------------
|
||
-- Hareketler: ilk giriş, son çıkış, okutulan mola. Yönü "Auto" olan okutma sıraya göre
|
||
-- giriş/çıkış sayılır (tek sıra giriş, çift sıra çıkış).
|
||
-- Yön büyük/küçük harf duyarsız karşılaştırılır (SQL Server harmanlamasıyla aynı sonuç).
|
||
---------------------------------------------------------------------------------------------
|
||
DROP TABLE IF EXISTS tc_event;
|
||
CREATE TEMP TABLE tc_event (EmployeeId uuid, WorkDate date, EventTime timestamp, Dir varchar(8),
|
||
NextTime timestamp NULL, NextDir varchar(8) NULL) ON COMMIT DROP;
|
||
|
||
WITH ev AS (
|
||
SELECT l."EmployeeId" AS EmployeeId, l."WorkDate" AS WorkDate, l."EventTime" AS EventTime,
|
||
MIN(lower(l."Direction")) AS Direction
|
||
FROM public."Hr_T_AttendanceLog" l
|
||
WHERE l."IsDeleted" = FALSE AND l."ProcessError" IS NULL AND l."WorkDate" BETWEEN v_From AND v_Last
|
||
AND (l."TenantId" = p_TenantId OR (l."TenantId" IS NULL AND p_TenantId IS NULL))
|
||
AND l."EmployeeId" IN (SELECT EmployeeId FROM tc_emp)
|
||
GROUP BY l."EmployeeId", l."WorkDate", l."EventTime"
|
||
), numbered AS (
|
||
SELECT ev.*, ROW_NUMBER() OVER (PARTITION BY ev.EmployeeId, ev.WorkDate ORDER BY ev.EventTime) AS Rn
|
||
FROM ev
|
||
), dir AS (
|
||
SELECT n.EmployeeId, n.WorkDate, n.EventTime,
|
||
CASE WHEN n.Direction = 'in' THEN 'In'
|
||
WHEN n.Direction = 'out' THEN 'Out'
|
||
WHEN n.Rn % 2 = 1 THEN 'In' ELSE 'Out' END AS Dir
|
||
FROM numbered n
|
||
)
|
||
INSERT INTO tc_event (EmployeeId, WorkDate, EventTime, Dir, NextTime, NextDir)
|
||
SELECT dir.EmployeeId, dir.WorkDate, dir.EventTime, dir.Dir,
|
||
LEAD(dir.EventTime) OVER (PARTITION BY dir.EmployeeId, dir.WorkDate ORDER BY dir.EventTime),
|
||
LEAD(dir.Dir) OVER (PARTITION BY dir.EmployeeId, dir.WorkDate ORDER BY dir.EventTime)
|
||
FROM dir;
|
||
|
||
UPDATE tc_row r
|
||
SET HasEvents = TRUE,
|
||
FirstIn = COALESCE(s.FirstIn, s.FirstAny),
|
||
LastOut = CASE WHEN s.LastOut > COALESCE(s.FirstIn, s.FirstAny) THEN s.LastOut END
|
||
FROM (
|
||
SELECT e.EmployeeId, e.WorkDate,
|
||
MIN(CASE WHEN e.Dir = 'In' THEN e.EventTime END) AS FirstIn,
|
||
MIN(e.EventTime) AS FirstAny,
|
||
MAX(CASE WHEN e.Dir = 'Out' THEN e.EventTime END) AS LastOut
|
||
FROM tc_event e
|
||
GROUP BY e.EmployeeId, e.WorkDate
|
||
) s
|
||
WHERE s.EmployeeId = r.EmployeeId AND s.WorkDate = r.WorkDate
|
||
AND r.IsClosed = FALSE AND r.IsManual = FALSE;
|
||
|
||
-- Okutulan mola: ilk giriş ile son çıkış arasındaki çıkış → giriş aralıkları.
|
||
UPDATE tc_row r
|
||
SET ScanBreak = COALESCE((
|
||
SELECT SUM((EXTRACT(EPOCH FROM date_trunc('minute', e.NextTime) - date_trunc('minute', e.EventTime)) / 60)::integer)
|
||
FROM tc_event e
|
||
WHERE e.EmployeeId = r.EmployeeId AND e.WorkDate = r.WorkDate
|
||
AND e.Dir = 'Out' AND e.NextDir = 'In'
|
||
AND e.EventTime >= r.FirstIn AND e.NextTime <= r.LastOut), 0)
|
||
WHERE r.IsClosed = FALSE AND r.IsManual = FALSE AND r.FirstIn IS NOT NULL AND r.LastOut IS NOT NULL;
|
||
|
||
---------------------------------------------------------------------------------------------
|
||
-- Gün tipi (öncelik sırasıyla):
|
||
-- Raporlu: takvim günü; SGK istirahati hafta sonunu da kapsar.
|
||
-- Ücretsiz izin (IsPaid = 0; doğum izni dahil): takvim günü; hafta tatili ve genel tatil ücreti doğmaz.
|
||
-- Resmî tatil, Hafta tatili: ücretli izne denk gelen tatil izinden sayılmaz (4857 md. 56);
|
||
-- İzin Yönetimi'nin gün sayısıyla aynı kural.
|
||
-- Ücretli izin > okutmalı Çalışma > Eğitim (katılımcı "Katıldı") > Çalışma / Devamsız.
|
||
-- Okutması olmayan iş günü: kartlı çalışanda devamsız (bugün henüz değil), kartsız çalışanda
|
||
-- (bugün dahil) vardiya saatleriyle çalışılmış sayılır.
|
||
---------------------------------------------------------------------------------------------
|
||
UPDATE tc_row r
|
||
SET HalfDayLeave = CASE WHEN x.LvIsHalfDay = TRUE THEN TRUE ELSE FALSE END,
|
||
LeaveTypeId = x.LvLeaveTypeId,
|
||
TrainingId = x.TrTrainingId,
|
||
DayType = CASE
|
||
WHEN x.SkId IS NOT NULL THEN 'Sick'
|
||
WHEN x.LvId IS NOT NULL AND x.LvIsPaid = FALSE AND (x.LvIsHalfDay = FALSE OR r.FirstIn IS NULL) THEN 'Leave'
|
||
WHEN r.CalendarType = 'Holiday' THEN 'Holiday'
|
||
WHEN r.IsWorkDay = FALSE THEN 'WeeklyOff'
|
||
WHEN x.LvId IS NOT NULL AND (x.LvIsHalfDay = FALSE OR r.FirstIn IS NULL) THEN 'Leave'
|
||
WHEN r.FirstIn IS NOT NULL THEN 'Work'
|
||
WHEN x.TrTrainingId IS NOT NULL THEN 'Training'
|
||
WHEN r.HasCard = FALSE THEN 'Work'
|
||
WHEN r.WorkDate < v_Today THEN 'Absent'
|
||
END
|
||
FROM (
|
||
SELECT r2.EmployeeId, r2.WorkDate, sk.Id AS SkId,
|
||
lv.Id AS LvId, lv.IsHalfDay AS LvIsHalfDay, lv.LeaveTypeId AS LvLeaveTypeId, lv.IsPaid AS LvIsPaid,
|
||
tr.TrainingId AS TrTrainingId
|
||
FROM tc_row r2
|
||
LEFT JOIN LATERAL (
|
||
SELECT s."Id" AS Id
|
||
FROM public."Adm_T_UserSickLeave" s
|
||
WHERE s."UserId" = r2.EmployeeId AND s."IsDeleted" = FALSE
|
||
AND r2.WorkDate BETWEEN s."StartDate"::date AND s."EndDate"::date
|
||
LIMIT 1
|
||
) sk ON TRUE
|
||
LEFT JOIN LATERAL (
|
||
SELECT q."Id" AS Id, q."IsHalfDay" AS IsHalfDay, q."LeaveTypeId" AS LeaveTypeId, lt."IsPaid" AS IsPaid
|
||
FROM public."Hr_T_LeaveRequest" q
|
||
INNER JOIN public."Hr_T_LeaveType" lt ON lt."Id" = q."LeaveTypeId"
|
||
WHERE q."EmployeeId" = r2.EmployeeId AND q."IsDeleted" = FALSE AND q."ApprovalStatus" = 'Onaylandı'
|
||
AND r2.WorkDate BETWEEN q."StartDate"::date AND q."EndDate"::date
|
||
ORDER BY q."IsHalfDay" NULLS FIRST
|
||
LIMIT 1
|
||
) lv ON TRUE
|
||
LEFT JOIN LATERAL (
|
||
SELECT p."TrainingId" AS TrainingId
|
||
FROM public."Hr_T_TrainingParticipant" p
|
||
INNER JOIN public."Hr_T_Training" g ON g."Id" = p."TrainingId" AND g."IsDeleted" = FALSE
|
||
WHERE p."UserId" = r2.EmployeeId AND p."IsDeleted" = FALSE AND p."AttendanceStatus" = 'Attended'
|
||
AND r2.WorkDate BETWEEN g."StartDate" AND g."EndDate"
|
||
ORDER BY g."StartDate" NULLS FIRST
|
||
LIMIT 1
|
||
) tr ON TRUE
|
||
WHERE r2.IsClosed = FALSE AND r2.IsManual = FALSE
|
||
) x
|
||
WHERE x.EmployeeId = r.EmployeeId AND x.WorkDate = r.WorkDate;
|
||
|
||
-- İstihdam dışındaki hareketsiz gün yazılmaz (işe girişten önce, çıkıştan sonra).
|
||
UPDATE tc_row
|
||
SET DayType = NULL
|
||
WHERE IsClosed = FALSE AND IsManual = FALSE AND IsEmployed = FALSE AND HasEvents = FALSE;
|
||
|
||
-- Elle takip edilen çalışanın kayıtsız iş günü ve okutmasız eğitim günü: vardiya saatleri
|
||
-- (arifede yarım gün). Eğitim günü haftalık birikime çalışma gibi girer.
|
||
UPDATE tc_row
|
||
SET IsAssumed = TRUE,
|
||
FirstIn = WorkDate + StartMin * INTERVAL '1 minute',
|
||
LastOut = WorkDate + (StartMin + CASE WHEN CalendarType = 'HalfDay'
|
||
THEN ((EndMin - StartMin + 1440) % 1440) / 2
|
||
ELSE (EndMin - StartMin + 1440) % 1440 END) * INTERVAL '1 minute'
|
||
WHERE IsClosed = FALSE AND IsManual = FALSE AND DayType IN ('Work', 'Training') AND HasEvents = FALSE;
|
||
|
||
---------------------------------------------------------------------------------------------
|
||
-- Süreler: mola = MAX(vardiya molası, okutulan mola); net = son çıkış − ilk giriş − mola.
|
||
-- Geç kalma / erken çıkış toleransı aşılırsa dakikanın tamamı yazılır. Yarım gün (arife)
|
||
-- vardiya süresinin yarısında biter; yarım gün izinli günde geç/erken yazılmaz.
|
||
---------------------------------------------------------------------------------------------
|
||
UPDATE tc_row r
|
||
SET BreakMinutes = CASE
|
||
WHEN m.Gross <= 0 THEN 0
|
||
WHEN r.IsManual = TRUE THEN CASE WHEN r.BreakMinutes > m.Gross THEN m.Gross ELSE r.BreakMinutes END
|
||
WHEN (CASE WHEN r.ScanBreak > r.ShiftBreak THEN r.ScanBreak ELSE r.ShiftBreak END) > m.Gross THEN m.Gross
|
||
ELSE CASE WHEN r.ScanBreak > r.ShiftBreak THEN r.ScanBreak ELSE r.ShiftBreak END
|
||
END,
|
||
IsIncomplete = CASE WHEN r.FirstIn IS NOT NULL AND r.LastOut IS NULL THEN TRUE ELSE FALSE END,
|
||
LateMinutes = CASE
|
||
WHEN r.DayType = 'Work' AND r.HalfDayLeave = FALSE AND r.FirstIn IS NOT NULL AND m.LateDiff > r.LateTol
|
||
THEN m.LateDiff
|
||
ELSE 0 END,
|
||
EarlyLeaveMinutes = CASE
|
||
WHEN r.DayType = 'Work' AND r.HalfDayLeave = FALSE AND r.LastOut IS NOT NULL AND m.EarlyDiff > r.EarlyTol
|
||
THEN m.EarlyDiff
|
||
ELSE 0 END
|
||
FROM (
|
||
SELECT r2.EmployeeId, r2.WorkDate,
|
||
CASE WHEN r2.FirstIn IS NOT NULL AND r2.LastOut IS NOT NULL
|
||
THEN (EXTRACT(EPOCH FROM date_trunc('minute', r2.LastOut) - date_trunc('minute', r2.FirstIn)) / 60)::integer
|
||
ELSE 0 END AS Gross,
|
||
(EXTRACT(EPOCH FROM date_trunc('minute', r2.FirstIn)
|
||
- (r2.WorkDate + r2.StartMin * INTERVAL '1 minute')) / 60)::integer AS LateDiff,
|
||
(EXTRACT(EPOCH FROM (r2.WorkDate + (r2.StartMin + CASE WHEN r2.CalendarType = 'HalfDay'
|
||
THEN ((r2.EndMin - r2.StartMin + 1440) % 1440) / 2
|
||
ELSE (r2.EndMin - r2.StartMin + 1440) % 1440 END) * INTERVAL '1 minute')
|
||
- date_trunc('minute', r2.LastOut)) / 60)::integer AS EarlyDiff
|
||
FROM tc_row r2
|
||
WHERE r2.IsClosed = FALSE AND r2.DayType IS NOT NULL
|
||
) m
|
||
WHERE m.EmployeeId = r.EmployeeId AND m.WorkDate = r.WorkDate;
|
||
|
||
UPDATE tc_row
|
||
SET NetMinutes = CASE
|
||
WHEN FirstIn IS NOT NULL AND LastOut IS NOT NULL
|
||
AND (EXTRACT(EPOCH FROM date_trunc('minute', LastOut) - date_trunc('minute', FirstIn)) / 60)::integer > BreakMinutes
|
||
THEN (EXTRACT(EPOCH FROM date_trunc('minute', LastOut) - date_trunc('minute', FirstIn)) / 60)::integer - BreakMinutes
|
||
ELSE 0 END
|
||
WHERE IsClosed = FALSE AND DayType IS NOT NULL;
|
||
|
||
-- Negatif çıkmasın: tolerans içindeki erken giriş geç kalma değildir.
|
||
UPDATE tc_row SET LateMinutes = 0 WHERE LateMinutes < 0;
|
||
UPDATE tc_row SET EarlyLeaveMinutes = 0 WHERE EarlyLeaveMinutes < 0;
|
||
|
||
---------------------------------------------------------------------------------------------
|
||
-- Fazla mesai (4857 md. 41): hafta tatili ve resmî tatilde çalışılan sürenin tamamı; diğer
|
||
-- günlerde haftanın kümülatif net süresi haftalık sınırı aştığı günden itibaren aşan kısım.
|
||
---------------------------------------------------------------------------------------------
|
||
UPDATE tc_row r
|
||
SET OvertimeMinutes = CASE
|
||
WHEN r.DayType IN ('WeeklyOff', 'Holiday') THEN r.NetMinutes
|
||
ELSE (CASE WHEN w.Cumulative > v_WeeklyLimit THEN w.Cumulative - v_WeeklyLimit ELSE 0 END)
|
||
- (CASE WHEN w.Cumulative - r.NetMinutes > v_WeeklyLimit THEN w.Cumulative - r.NetMinutes - v_WeeklyLimit ELSE 0 END)
|
||
END
|
||
FROM (
|
||
SELECT r2.EmployeeId, r2.WorkDate,
|
||
SUM(CASE WHEN r2.DayType IN ('WeeklyOff', 'Holiday') THEN 0 ELSE r2.NetMinutes END)
|
||
OVER (PARTITION BY r2.EmployeeId, (r2.WorkDate - DATE '1900-01-01') / 7
|
||
ORDER BY r2.WorkDate ROWS UNBOUNDED PRECEDING) AS Cumulative
|
||
FROM tc_row r2
|
||
WHERE r2.DayType IS NOT NULL
|
||
) w
|
||
WHERE w.EmployeeId = r.EmployeeId AND w.WorkDate = r.WorkDate
|
||
AND r.IsClosed = FALSE;
|
||
|
||
---------------------------------------------------------------------------------------------
|
||
-- Yazma
|
||
---------------------------------------------------------------------------------------------
|
||
v_Now := (NOW() AT TIME ZONE 'UTC');
|
||
|
||
UPDATE public."Hr_T_Timesheet" t
|
||
SET "ShiftId" = r.ShiftId,
|
||
"DayType" = r.DayType,
|
||
"LeaveTypeId" = r.LeaveTypeId,
|
||
"TrainingId" = r.TrainingId,
|
||
"FirstIn" = r.FirstIn,
|
||
"LastOut" = r.LastOut,
|
||
"BreakMinutes" = r.BreakMinutes,
|
||
"NetMinutes" = r.NetMinutes,
|
||
"NetHours" = CAST(r.NetMinutes / 60.0 AS numeric(6, 2)),
|
||
"LateMinutes" = r.LateMinutes,
|
||
"EarlyLeaveMinutes" = r.EarlyLeaveMinutes,
|
||
"OvertimeHours" = CAST(r.OvertimeMinutes / 60.0 AS numeric(6, 2)),
|
||
"IsIncomplete" = r.IsIncomplete,
|
||
"IsAssumed" = r.IsAssumed,
|
||
"CalculatedAt" = v_Now,
|
||
"LastModificationTime" = v_Now,
|
||
"LastModifierId" = COALESCE(p_ActorId, t."LastModifierId")
|
||
FROM tc_row r
|
||
WHERE r.ExistingId = t."Id" AND r.IsClosed = FALSE AND r.DayType IS NOT NULL;
|
||
|
||
-- Artık karşılığı olmayan otomatik gün (ör. geriye dönük işten çıkış) kalkar.
|
||
UPDATE public."Hr_T_Timesheet" t
|
||
SET "IsDeleted" = TRUE, "DeletionTime" = v_Now, "DeleterId" = p_ActorId
|
||
FROM tc_row r
|
||
WHERE r.ExistingId = t."Id" AND r.IsClosed = FALSE AND r.IsManual = FALSE AND r.DayType IS NULL;
|
||
|
||
-- PeriodKey SQL Server'daki CONVERT(nvarchar(36), uniqueidentifier) ile aynı (büyük harf) yazılır.
|
||
INSERT INTO public."Hr_T_Timesheet"
|
||
("Id", "TenantId", "EmployeeId", "WorkDate", "PeriodYear", "PeriodMonth", "PeriodKey", "ShiftId", "DayType", "LeaveTypeId", "TrainingId",
|
||
"FirstIn", "LastOut", "BreakMinutes", "NetMinutes", "NetHours", "LateMinutes", "EarlyLeaveMinutes", "OvertimeHours",
|
||
"IsManual", "IsIncomplete", "IsAssumed", "CalculatedAt", "CreationTime", "CreatorId", "IsDeleted")
|
||
SELECT gen_random_uuid(), p_TenantId, r.EmployeeId, r.WorkDate,
|
||
EXTRACT(YEAR FROM r.WorkDate)::integer, EXTRACT(MONTH FROM r.WorkDate)::integer,
|
||
upper(r.EmployeeId::text) || '-' || (EXTRACT(YEAR FROM r.WorkDate)::integer * 100 + EXTRACT(MONTH FROM r.WorkDate)::integer)::text,
|
||
r.ShiftId, r.DayType, r.LeaveTypeId, r.TrainingId, r.FirstIn, r.LastOut, r.BreakMinutes, r.NetMinutes,
|
||
CAST(r.NetMinutes / 60.0 AS numeric(6, 2)), r.LateMinutes, r.EarlyLeaveMinutes,
|
||
CAST(r.OvertimeMinutes / 60.0 AS numeric(6, 2)), FALSE, r.IsIncomplete, r.IsAssumed, v_Now, v_Now, p_ActorId, FALSE
|
||
FROM tc_row r
|
||
WHERE r.IsClosed = FALSE AND r.ExistingId IS NULL AND r.DayType IS NOT NULL;
|
||
END;
|
||
$$;
|