sozsoft-platform/configs/seeds/sql/object/Hr_S_TimesheetCalculate.sql
2026-10-05 15:58:59 +03:00

426 lines
23 KiB
Transact-SQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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'App.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'App.HrSettings.Leave.WorkMonday'),
(1, N'App.HrSettings.Leave.WorkTuesday'),
(2, N'App.HrSettings.Leave.WorkWednesday'),
(3, N'App.HrSettings.Leave.WorkThursday'),
(4, N'App.HrSettings.Leave.WorkFriday'),
(5, N'App.HrSettings.Leave.WorkSaturday'),
(6, N'App.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