426 lines
23 KiB
Transact-SQL
426 lines
23 KiB
Transact-SQL
CREATE OR ALTER PROCEDURE [dbo].[Hr_S_TimesheetCalculate]
|
||
@TenantId uniqueidentifier,
|
||
@EmployeeId uniqueidentifier = NULL,
|
||
@From date,
|
||
@To date,
|
||
@ActorId uniqueidentifier = NULL
|
||
AS
|
||
BEGIN
|
||
SET NOCOUNT ON;
|
||
|
||
-- Günlük puantajı hareketlerden, izin/rapor/eğitim kayıtlarından ve Çalışma Takvimi'nden kurar.
|
||
-- @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.
|
||
IF @From IS NULL OR @To IS NULL OR @To < @From
|
||
RETURN;
|
||
|
||
DECLARE @Today date = CAST(GETDATE() AS date);
|
||
|
||
-- 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 @EmployeeId IS NOT NULL
|
||
BEGIN
|
||
DECLARE @OrphanFrom date, @OrphanTo date;
|
||
|
||
SELECT @OrphanFrom = MIN(t.WorkDate), @OrphanTo = MAX(t.WorkDate)
|
||
FROM Hr_T_Timesheet t
|
||
WHERE t.EmployeeId = @EmployeeId AND t.IsDeleted = 0 AND t.IsManual = 0
|
||
AND t.DayType IN (N'Leave', N'Sick', N'Training')
|
||
AND (t.WorkDate < @From OR t.WorkDate > @To)
|
||
AND NOT EXISTS (SELECT 1 FROM Adm_T_UserSickLeave s
|
||
WHERE s.UserId = t.EmployeeId AND s.IsDeleted = 0
|
||
AND t.WorkDate BETWEEN CAST(s.StartDate AS date) AND CAST(s.EndDate AS date))
|
||
AND NOT EXISTS (SELECT 1 FROM Hr_T_LeaveRequest q
|
||
WHERE q.EmployeeId = t.EmployeeId AND q.IsDeleted = 0 AND q.ApprovalStatus = N'Onaylandı'
|
||
AND t.WorkDate BETWEEN CAST(q.StartDate AS date) AND CAST(q.EndDate AS date))
|
||
AND NOT EXISTS (SELECT 1 FROM Hr_T_TrainingParticipant p
|
||
INNER JOIN Hr_T_Training g ON g.Id = p.TrainingId AND g.IsDeleted = 0
|
||
WHERE p.UserId = t.EmployeeId AND p.IsDeleted = 0 AND p.AttendanceStatus = N'Attended'
|
||
AND t.WorkDate BETWEEN g.StartDate AND g.EndDate);
|
||
|
||
IF @OrphanFrom < @From SET @From = @OrphanFrom;
|
||
IF @OrphanTo > @To SET @To = @OrphanTo;
|
||
END
|
||
|
||
-- Fazla mesai haftalık birikimle hesaplandığı için aralık tam haftalara (Pzt–Paz) genişler.
|
||
-- 1900-01-01 Pazartesidir; gün sırası DATEFIRST ayarından bağımsızdır.
|
||
SET @From = DATEADD(day, -(DATEDIFF(day, '19000101', @From) % 7), @From);
|
||
SET @To = DATEADD(day, 6 - (DATEDIFF(day, '19000101', @To) % 7), @To);
|
||
DECLARE @Last date = CASE WHEN @To > @Today THEN @Today ELSE @To END;
|
||
IF @Last < @From
|
||
RETURN;
|
||
|
||
DECLARE @WeeklyLimit int = CAST(60 * COALESCE(
|
||
TRY_CONVERT(decimal(9, 2), REPLACE(dbo.Hr_F_HrSetting(@TenantId, N'Adm.HrSettings.Attendance.WeeklyLimitHours'), N',', N'.')), 45) AS int);
|
||
|
||
-- Hafta tatili İzin Yönetimi'nin çalışma günü ayarlarıdır (tek kaynak); tanım yoksa Pzt–Cum.
|
||
DECLARE @Work TABLE (DayIndex int PRIMARY KEY, IsWork bit);
|
||
INSERT INTO @Work (DayIndex, IsWork)
|
||
SELECT d.DayIndex,
|
||
CASE
|
||
WHEN s.Value IS NULL THEN CASE WHEN d.DayIndex < 5 THEN 1 ELSE 0 END
|
||
WHEN LOWER(s.Value) = N'true' THEN 1
|
||
ELSE 0
|
||
END
|
||
FROM (VALUES
|
||
(0, N'Adm.HrSettings.Leave.WorkMonday'),
|
||
(1, N'Adm.HrSettings.Leave.WorkTuesday'),
|
||
(2, N'Adm.HrSettings.Leave.WorkWednesday'),
|
||
(3, N'Adm.HrSettings.Leave.WorkThursday'),
|
||
(4, N'Adm.HrSettings.Leave.WorkFriday'),
|
||
(5, N'Adm.HrSettings.Leave.WorkSaturday'),
|
||
(6, N'Adm.HrSettings.Leave.WorkSunday')) d (DayIndex, SettingName)
|
||
CROSS APPLY (SELECT dbo.Hr_F_HrSetting(@TenantId, d.SettingName) AS Value) s;
|
||
|
||
---------------------------------------------------------------------------------------------
|
||
-- Çalışanlar: aralıkta istihdam edilen aktif kullanıcılar + hareketi ya da puantajı olanlar
|
||
---------------------------------------------------------------------------------------------
|
||
CREATE TABLE #tcEmp
|
||
(
|
||
EmployeeId uniqueidentifier PRIMARY KEY, HireDate date NULL, TerminationDate date NULL,
|
||
ShiftId uniqueidentifier NULL, StartMin int, EndMin int, BreakMin int, LateTol int, EarlyTol int,
|
||
CardSince date NULL
|
||
);
|
||
|
||
-- 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 #tcEmp (EmployeeId, HireDate, TerminationDate, ShiftId, StartMin, EndMin, BreakMin, LateTol, EarlyTol, CardSince)
|
||
SELECT u.Id, CAST(u.HireDate AS date), CAST(u.TerminationDate AS date),
|
||
s.ShiftId, s.StartMin, s.EndMin, s.BreakMin, s.LateTol, s.EarlyTol,
|
||
CASE WHEN NULLIF(LTRIM(RTRIM(u.PdksCardNo)), N'') IS NOT NULL THEN
|
||
(SELECT MIN(l.WorkDate) FROM Hr_T_AttendanceLog l
|
||
WHERE l.EmployeeId = u.Id AND l.IsDeleted = 0 AND l.ProcessError IS NULL AND l.Source <> N'Manual')
|
||
END
|
||
FROM AbpUsers u
|
||
INNER JOIN Hr_V_AttendanceShift s ON s.EmployeeId = u.Id
|
||
WHERE u.IsDeleted = 0
|
||
AND (u.TenantId = @TenantId OR (u.TenantId IS NULL AND @TenantId IS NULL))
|
||
AND (@EmployeeId IS NULL OR u.Id = @EmployeeId)
|
||
AND (
|
||
(u.IsActive = 1 AND u.HireDate IS NOT NULL AND CAST(u.HireDate AS date) <= @Last
|
||
AND (u.TerminationDate IS NULL OR CAST(u.TerminationDate AS date) >= @From))
|
||
OR EXISTS (SELECT 1 FROM Hr_T_AttendanceLog l
|
||
WHERE l.EmployeeId = u.Id AND l.IsDeleted = 0 AND l.WorkDate BETWEEN @From AND @Last)
|
||
OR EXISTS (SELECT 1 FROM Hr_T_Timesheet x
|
||
WHERE x.EmployeeId = u.Id AND x.IsDeleted = 0 AND x.WorkDate BETWEEN @From AND @Last)
|
||
);
|
||
|
||
IF NOT EXISTS (SELECT 1 FROM #tcEmp)
|
||
RETURN;
|
||
|
||
---------------------------------------------------------------------------------------------
|
||
-- Günler: hafta tatili, takvim türü, kapalı dönem
|
||
---------------------------------------------------------------------------------------------
|
||
CREATE TABLE #tcDay (WorkDate date PRIMARY KEY, IsWorkDay bit, CalendarType nvarchar(16) NULL, IsClosed bit);
|
||
|
||
WITH d AS (
|
||
SELECT @From AS WorkDate
|
||
UNION ALL
|
||
SELECT DATEADD(day, 1, WorkDate) FROM d WHERE WorkDate < @Last
|
||
)
|
||
INSERT INTO #tcDay (WorkDate, IsWorkDay, CalendarType, IsClosed)
|
||
SELECT d.WorkDate,
|
||
COALESCE(w.IsWork, 0),
|
||
dbo.Hr_F_WorkCalendarDayType(@TenantId, d.WorkDate),
|
||
CASE WHEN EXISTS (
|
||
SELECT 1 FROM Hr_T_TimesheetPeriod p
|
||
WHERE p.IsDeleted = 0 AND p.Status = N'Closed'
|
||
AND (p.TenantId = @TenantId OR (p.TenantId IS NULL AND @TenantId IS NULL))
|
||
AND p.PeriodYear = YEAR(d.WorkDate) AND p.PeriodMonth = MONTH(d.WorkDate)) THEN 1 ELSE 0 END
|
||
FROM d
|
||
LEFT JOIN @Work w ON w.DayIndex = DATEDIFF(day, '19000101', d.WorkDate) % 7
|
||
OPTION (MAXRECURSION 0);
|
||
|
||
---------------------------------------------------------------------------------------------
|
||
-- Çalışan × gün satırları
|
||
---------------------------------------------------------------------------------------------
|
||
CREATE TABLE #tcRow
|
||
(
|
||
EmployeeId uniqueidentifier NOT NULL, WorkDate date NOT NULL,
|
||
ShiftId uniqueidentifier NULL, StartMin int, EndMin int, ShiftBreak int, LateTol int, EarlyTol int,
|
||
IsEmployed bit NOT NULL DEFAULT 1, HasCard bit NOT NULL DEFAULT 0, IsAssumed bit NOT NULL DEFAULT 0,
|
||
IsWorkDay bit, CalendarType nvarchar(16) NULL, IsClosed bit,
|
||
ExistingId uniqueidentifier NULL, IsManual bit NOT NULL DEFAULT 0, HalfDayLeave bit NOT NULL DEFAULT 0,
|
||
LeaveTypeId uniqueidentifier NULL, TrainingId uniqueidentifier NULL,
|
||
DayType nvarchar(16) NULL, FirstIn datetime2 NULL, LastOut datetime2 NULL, HasEvents bit NOT NULL DEFAULT 0,
|
||
ScanBreak int NOT NULL DEFAULT 0, BreakMinutes int NOT NULL DEFAULT 0, NetMinutes int NOT NULL DEFAULT 0,
|
||
LateMinutes int NOT NULL DEFAULT 0, EarlyLeaveMinutes int NOT NULL DEFAULT 0, OvertimeMinutes int NOT NULL DEFAULT 0,
|
||
IsIncomplete bit NOT NULL DEFAULT 0,
|
||
PRIMARY KEY (EmployeeId, WorkDate)
|
||
);
|
||
|
||
INSERT INTO #tcRow (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 1 ELSE 0 END,
|
||
CASE WHEN e.CardSince <= d.WorkDate THEN 1 ELSE 0 END, d.IsWorkDay, d.CalendarType, d.IsClosed
|
||
FROM #tcEmp e
|
||
CROSS JOIN #tcDay d;
|
||
|
||
UPDATE r
|
||
SET ExistingId = t.Id, IsManual = t.IsManual
|
||
FROM #tcRow r
|
||
CROSS APPLY (
|
||
SELECT TOP 1 x.Id, x.IsManual
|
||
FROM Hr_T_Timesheet x
|
||
WHERE x.EmployeeId = r.EmployeeId AND x.WorkDate = r.WorkDate AND x.IsDeleted = 0
|
||
AND (x.TenantId = @TenantId OR (x.TenantId IS NULL AND @TenantId IS NULL))
|
||
ORDER BY x.IsManual DESC, x.CreationTime
|
||
) t;
|
||
|
||
-- Kapalı dönemin günü olduğu gibi kalır; elle düzeltilmiş günün girdileri korunur.
|
||
UPDATE 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 = 1 THEN t.NetMinutes ELSE 0 END,
|
||
LateMinutes = CASE WHEN r.IsClosed = 1 THEN t.LateMinutes ELSE 0 END,
|
||
EarlyLeaveMinutes = CASE WHEN r.IsClosed = 1 THEN t.EarlyLeaveMinutes ELSE 0 END,
|
||
OvertimeMinutes = CASE WHEN r.IsClosed = 1 THEN CAST(ROUND(t.OvertimeHours * 60, 0) AS int) ELSE 0 END,
|
||
IsIncomplete = t.IsIncomplete,
|
||
IsAssumed = CASE WHEN r.IsClosed = 1 THEN t.IsAssumed ELSE 0 END
|
||
FROM #tcRow r
|
||
INNER JOIN Hr_T_Timesheet t ON t.Id = r.ExistingId
|
||
WHERE r.IsClosed = 1 OR r.IsManual = 1;
|
||
|
||
-- Elle devamsız/izinli/raporlu işaretlenen günde saat tutulmaz.
|
||
UPDATE #tcRow
|
||
SET FirstIn = NULL, LastOut = NULL, BreakMinutes = 0
|
||
WHERE IsManual = 1 AND IsClosed = 0 AND DayType IN (N'Absent', N'Leave', N'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ış).
|
||
---------------------------------------------------------------------------------------------
|
||
CREATE TABLE #tcEvent (EmployeeId uniqueidentifier, WorkDate date, EventTime datetime2, Dir nvarchar(8),
|
||
NextTime datetime2 NULL, NextDir nvarchar(8) NULL);
|
||
|
||
WITH ev AS (
|
||
SELECT l.EmployeeId, l.WorkDate, l.EventTime, MIN(l.Direction) AS Direction
|
||
FROM Hr_T_AttendanceLog l
|
||
WHERE l.IsDeleted = 0 AND l.ProcessError IS NULL AND l.WorkDate BETWEEN @From AND @Last
|
||
AND (l.TenantId = @TenantId OR (l.TenantId IS NULL AND @TenantId IS NULL))
|
||
AND l.EmployeeId IN (SELECT EmployeeId FROM #tcEmp)
|
||
GROUP BY l.EmployeeId, l.WorkDate, l.EventTime
|
||
), numbered AS (
|
||
SELECT ev.*, ROW_NUMBER() OVER (PARTITION BY EmployeeId, WorkDate ORDER BY EventTime) AS Rn
|
||
FROM ev
|
||
), dir AS (
|
||
SELECT EmployeeId, WorkDate, EventTime,
|
||
CASE WHEN Direction IN (N'In', N'Out') THEN Direction WHEN Rn % 2 = 1 THEN N'In' ELSE N'Out' END AS Dir
|
||
FROM numbered
|
||
)
|
||
INSERT INTO #tcEvent (EmployeeId, WorkDate, EventTime, Dir, NextTime, NextDir)
|
||
SELECT EmployeeId, WorkDate, EventTime, Dir,
|
||
LEAD(EventTime) OVER (PARTITION BY EmployeeId, WorkDate ORDER BY EventTime),
|
||
LEAD(Dir) OVER (PARTITION BY EmployeeId, WorkDate ORDER BY EventTime)
|
||
FROM dir;
|
||
|
||
UPDATE r
|
||
SET HasEvents = 1,
|
||
FirstIn = COALESCE(s.FirstIn, s.FirstAny),
|
||
LastOut = CASE WHEN s.LastOut > COALESCE(s.FirstIn, s.FirstAny) THEN s.LastOut END
|
||
FROM #tcRow r
|
||
INNER JOIN (
|
||
SELECT EmployeeId, WorkDate,
|
||
MIN(CASE WHEN Dir = N'In' THEN EventTime END) AS FirstIn,
|
||
MIN(EventTime) AS FirstAny,
|
||
MAX(CASE WHEN Dir = N'Out' THEN EventTime END) AS LastOut
|
||
FROM #tcEvent
|
||
GROUP BY EmployeeId, WorkDate
|
||
) s ON s.EmployeeId = r.EmployeeId AND s.WorkDate = r.WorkDate
|
||
WHERE r.IsClosed = 0 AND r.IsManual = 0;
|
||
|
||
-- Okutulan mola: ilk giriş ile son çıkış arasındaki çıkış → giriş aralıkları.
|
||
UPDATE r
|
||
SET ScanBreak = COALESCE((
|
||
SELECT SUM(DATEDIFF(minute, e.EventTime, e.NextTime))
|
||
FROM #tcEvent e
|
||
WHERE e.EmployeeId = r.EmployeeId AND e.WorkDate = r.WorkDate
|
||
AND e.Dir = N'Out' AND e.NextDir = N'In'
|
||
AND e.EventTime >= r.FirstIn AND e.NextTime <= r.LastOut), 0)
|
||
FROM #tcRow r
|
||
WHERE r.IsClosed = 0 AND r.IsManual = 0 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 r
|
||
SET HalfDayLeave = CASE WHEN lv.IsHalfDay = 1 THEN 1 ELSE 0 END,
|
||
LeaveTypeId = lv.LeaveTypeId,
|
||
TrainingId = tr.TrainingId,
|
||
DayType = CASE
|
||
WHEN sk.Id IS NOT NULL THEN N'Sick'
|
||
WHEN lv.Id IS NOT NULL AND lv.IsPaid = 0 AND (lv.IsHalfDay = 0 OR r.FirstIn IS NULL) THEN N'Leave'
|
||
WHEN r.CalendarType = N'Holiday' THEN N'Holiday'
|
||
WHEN r.IsWorkDay = 0 THEN N'WeeklyOff'
|
||
WHEN lv.Id IS NOT NULL AND (lv.IsHalfDay = 0 OR r.FirstIn IS NULL) THEN N'Leave'
|
||
WHEN r.FirstIn IS NOT NULL THEN N'Work'
|
||
WHEN tr.TrainingId IS NOT NULL THEN N'Training'
|
||
WHEN r.HasCard = 0 THEN N'Work'
|
||
WHEN r.WorkDate < @Today THEN N'Absent'
|
||
END
|
||
FROM #tcRow r
|
||
OUTER APPLY (
|
||
SELECT TOP 1 s.Id
|
||
FROM Adm_T_UserSickLeave s
|
||
WHERE s.UserId = r.EmployeeId AND s.IsDeleted = 0
|
||
AND r.WorkDate BETWEEN CAST(s.StartDate AS date) AND CAST(s.EndDate AS date)
|
||
) sk
|
||
OUTER APPLY (
|
||
SELECT TOP 1 q.Id, q.IsHalfDay, q.LeaveTypeId, lt.IsPaid
|
||
FROM Hr_T_LeaveRequest q
|
||
INNER JOIN Hr_T_LeaveType lt ON lt.Id = q.LeaveTypeId
|
||
WHERE q.EmployeeId = r.EmployeeId AND q.IsDeleted = 0 AND q.ApprovalStatus = N'Onaylandı'
|
||
AND r.WorkDate BETWEEN CAST(q.StartDate AS date) AND CAST(q.EndDate AS date)
|
||
ORDER BY q.IsHalfDay
|
||
) lv
|
||
OUTER APPLY (
|
||
SELECT TOP 1 p.TrainingId
|
||
FROM Hr_T_TrainingParticipant p
|
||
INNER JOIN Hr_T_Training g ON g.Id = p.TrainingId AND g.IsDeleted = 0
|
||
WHERE p.UserId = r.EmployeeId AND p.IsDeleted = 0 AND p.AttendanceStatus = N'Attended'
|
||
AND r.WorkDate BETWEEN g.StartDate AND g.EndDate
|
||
ORDER BY g.StartDate
|
||
) tr
|
||
WHERE r.IsClosed = 0 AND r.IsManual = 0;
|
||
|
||
-- İstihdam dışındaki hareketsiz gün yazılmaz (işe girişten önce, çıkıştan sonra).
|
||
UPDATE #tcRow
|
||
SET DayType = NULL
|
||
WHERE IsClosed = 0 AND IsManual = 0 AND IsEmployed = 0 AND HasEvents = 0;
|
||
|
||
-- 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 #tcRow
|
||
SET IsAssumed = 1,
|
||
FirstIn = DATEADD(minute, StartMin, CAST(WorkDate AS datetime2)),
|
||
LastOut = DATEADD(minute, StartMin + CASE WHEN CalendarType = N'HalfDay'
|
||
THEN ((EndMin - StartMin + 1440) % 1440) / 2
|
||
ELSE (EndMin - StartMin + 1440) % 1440 END,
|
||
CAST(WorkDate AS datetime2))
|
||
WHERE IsClosed = 0 AND IsManual = 0 AND DayType IN (N'Work', N'Training') AND HasEvents = 0;
|
||
|
||
---------------------------------------------------------------------------------------------
|
||
-- 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 r
|
||
SET BreakMinutes = CASE
|
||
WHEN g.Gross <= 0 THEN 0
|
||
WHEN r.IsManual = 1 THEN CASE WHEN r.BreakMinutes > g.Gross THEN g.Gross ELSE r.BreakMinutes END
|
||
WHEN (CASE WHEN r.ScanBreak > r.ShiftBreak THEN r.ScanBreak ELSE r.ShiftBreak END) > g.Gross THEN g.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 1 ELSE 0 END,
|
||
LateMinutes = CASE
|
||
WHEN r.DayType = N'Work' AND r.HalfDayLeave = 0 AND r.FirstIn IS NOT NULL
|
||
AND DATEDIFF(minute, DATEADD(minute, r.StartMin, CAST(r.WorkDate AS datetime2)), r.FirstIn) > r.LateTol
|
||
THEN DATEDIFF(minute, DATEADD(minute, r.StartMin, CAST(r.WorkDate AS datetime2)), r.FirstIn)
|
||
ELSE 0 END,
|
||
EarlyLeaveMinutes = CASE
|
||
WHEN r.DayType = N'Work' AND r.HalfDayLeave = 0 AND r.LastOut IS NOT NULL
|
||
AND DATEDIFF(minute, r.LastOut, DATEADD(minute, sh.ExpectedEnd, CAST(r.WorkDate AS datetime2))) > r.EarlyTol
|
||
THEN DATEDIFF(minute, r.LastOut, DATEADD(minute, sh.ExpectedEnd, CAST(r.WorkDate AS datetime2)))
|
||
ELSE 0 END
|
||
FROM #tcRow r
|
||
CROSS APPLY (
|
||
SELECT CASE WHEN r.FirstIn IS NOT NULL AND r.LastOut IS NOT NULL
|
||
THEN DATEDIFF(minute, r.FirstIn, r.LastOut) ELSE 0 END AS Gross
|
||
) g
|
||
CROSS APPLY (
|
||
SELECT r.StartMin + CASE WHEN r.CalendarType = N'HalfDay'
|
||
THEN ((r.EndMin - r.StartMin + 1440) % 1440) / 2
|
||
ELSE (r.EndMin - r.StartMin + 1440) % 1440 END AS ExpectedEnd
|
||
) sh
|
||
WHERE r.IsClosed = 0 AND r.DayType IS NOT NULL;
|
||
|
||
UPDATE #tcRow
|
||
SET NetMinutes = CASE
|
||
WHEN FirstIn IS NOT NULL AND LastOut IS NOT NULL AND DATEDIFF(minute, FirstIn, LastOut) > BreakMinutes
|
||
THEN DATEDIFF(minute, FirstIn, LastOut) - BreakMinutes
|
||
ELSE 0 END
|
||
WHERE IsClosed = 0 AND DayType IS NOT NULL;
|
||
|
||
-- Negatif çıkmasın: tolerans içindeki erken giriş geç kalma değildir.
|
||
UPDATE #tcRow SET LateMinutes = 0 WHERE LateMinutes < 0;
|
||
UPDATE #tcRow 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.
|
||
---------------------------------------------------------------------------------------------
|
||
WITH w AS (
|
||
SELECT r.IsClosed, r.DayType, r.NetMinutes, r.OvertimeMinutes,
|
||
SUM(CASE WHEN r.DayType IN (N'WeeklyOff', N'Holiday') THEN 0 ELSE r.NetMinutes END)
|
||
OVER (PARTITION BY r.EmployeeId, DATEDIFF(day, '19000101', r.WorkDate) / 7
|
||
ORDER BY r.WorkDate ROWS UNBOUNDED PRECEDING) AS Cumulative
|
||
FROM #tcRow r
|
||
WHERE r.DayType IS NOT NULL
|
||
)
|
||
UPDATE w
|
||
SET OvertimeMinutes = CASE
|
||
WHEN DayType IN (N'WeeklyOff', N'Holiday') THEN NetMinutes
|
||
ELSE (CASE WHEN Cumulative > @WeeklyLimit THEN Cumulative - @WeeklyLimit ELSE 0 END)
|
||
- (CASE WHEN Cumulative - NetMinutes > @WeeklyLimit THEN Cumulative - NetMinutes - @WeeklyLimit ELSE 0 END)
|
||
END
|
||
WHERE IsClosed = 0;
|
||
|
||
---------------------------------------------------------------------------------------------
|
||
-- Yazma
|
||
---------------------------------------------------------------------------------------------
|
||
DECLARE @Now datetime2 = GETUTCDATE();
|
||
|
||
UPDATE 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 decimal(6, 2)),
|
||
LateMinutes = r.LateMinutes,
|
||
EarlyLeaveMinutes = r.EarlyLeaveMinutes,
|
||
OvertimeHours = CAST(r.OvertimeMinutes / 60.0 AS decimal(6, 2)),
|
||
IsIncomplete = r.IsIncomplete,
|
||
IsAssumed = r.IsAssumed,
|
||
CalculatedAt = @Now,
|
||
LastModificationTime = @Now,
|
||
LastModifierId = COALESCE(@ActorId, t.LastModifierId)
|
||
FROM Hr_T_Timesheet t
|
||
INNER JOIN #tcRow r ON r.ExistingId = t.Id
|
||
WHERE r.IsClosed = 0 AND r.DayType IS NOT NULL;
|
||
|
||
-- Artık karşılığı olmayan otomatik gün (ör. geriye dönük işten çıkış) kalkar.
|
||
UPDATE t
|
||
SET IsDeleted = 1, DeletionTime = @Now, DeleterId = @ActorId
|
||
FROM Hr_T_Timesheet t
|
||
INNER JOIN #tcRow r ON r.ExistingId = t.Id
|
||
WHERE r.IsClosed = 0 AND r.IsManual = 0 AND r.DayType IS NULL;
|
||
|
||
INSERT INTO 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 NEWID(), @TenantId, r.EmployeeId, r.WorkDate, YEAR(r.WorkDate), MONTH(r.WorkDate),
|
||
CONVERT(nvarchar(36), r.EmployeeId) + N'-' + CONVERT(nvarchar(6), YEAR(r.WorkDate) * 100 + MONTH(r.WorkDate)),
|
||
r.ShiftId, r.DayType, r.LeaveTypeId, r.TrainingId, r.FirstIn, r.LastOut, r.BreakMinutes, r.NetMinutes,
|
||
CAST(r.NetMinutes / 60.0 AS decimal(6, 2)), r.LateMinutes, r.EarlyLeaveMinutes,
|
||
CAST(r.OvertimeMinutes / 60.0 AS decimal(6, 2)), 0, r.IsIncomplete, r.IsAssumed, @Now, @Now, @ActorId, 0
|
||
FROM #tcRow r
|
||
WHERE r.IsClosed = 0 AND r.ExistingId IS NULL AND r.DayType IS NOT NULL;
|
||
END
|
||
GO
|