Compare commits
No commits in common. "131cf6860089acfa2ba041973f157cb819fc0001" and "7d1b7609345728eb0a30382037430b0cbea9d0bd" have entirely different histories.
131cf68600
...
7d1b760934
36
deno.lock
generated
36
deno.lock
generated
@ -1247,42 +1247,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"remote": {
|
||||
"https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975",
|
||||
"https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834",
|
||||
"https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293",
|
||||
"https://deno.land/std@0.224.0/assert/assert_array_includes.ts": "14c5094471bc8e4a7895fc6aa5a184300d8a1879606574cb1cd715ef36a4a3c7",
|
||||
"https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74",
|
||||
"https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd",
|
||||
"https://deno.land/std@0.224.0/assert/assert_false.ts": "3e9be8e33275db00d952e9acb0cd29481a44fa0a4af6d37239ff58d79e8edeff",
|
||||
"https://deno.land/std@0.224.0/assert/assert_greater.ts": "5e57b201fd51b64ced36c828e3dfd773412c1a6120c1a5a99066c9b261974e46",
|
||||
"https://deno.land/std@0.224.0/assert/assert_greater_or_equal.ts": "9870030f997a08361b6f63400273c2fb1856f5db86c0c3852aab2a002e425c5b",
|
||||
"https://deno.land/std@0.224.0/assert/assert_instance_of.ts": "e22343c1fdcacfaea8f37784ad782683ec1cf599ae9b1b618954e9c22f376f2c",
|
||||
"https://deno.land/std@0.224.0/assert/assert_is_error.ts": "f856b3bc978a7aa6a601f3fec6603491ab6255118afa6baa84b04426dd3cc491",
|
||||
"https://deno.land/std@0.224.0/assert/assert_less.ts": "60b61e13a1982865a72726a5fa86c24fad7eb27c3c08b13883fb68882b307f68",
|
||||
"https://deno.land/std@0.224.0/assert/assert_less_or_equal.ts": "d2c84e17faba4afe085e6c9123a63395accf4f9e00150db899c46e67420e0ec3",
|
||||
"https://deno.land/std@0.224.0/assert/assert_match.ts": "ace1710dd3b2811c391946954234b5da910c5665aed817943d086d4d4871a8b7",
|
||||
"https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29",
|
||||
"https://deno.land/std@0.224.0/assert/assert_not_instance_of.ts": "3434a669b4d20cdcc5359779301a0588f941ffdc2ad68803c31eabdb4890cf7a",
|
||||
"https://deno.land/std@0.224.0/assert/assert_not_match.ts": "df30417240aa2d35b1ea44df7e541991348a063d9ee823430e0b58079a72242a",
|
||||
"https://deno.land/std@0.224.0/assert/assert_not_strict_equals.ts": "37f73880bd672709373d6dc2c5f148691119bed161f3020fff3548a0496f71b8",
|
||||
"https://deno.land/std@0.224.0/assert/assert_object_match.ts": "411450fd194fdaabc0089ae68f916b545a49d7b7e6d0026e84a54c9e7eed2693",
|
||||
"https://deno.land/std@0.224.0/assert/assert_rejects.ts": "4bee1d6d565a5b623146a14668da8f9eb1f026a4f338bbf92b37e43e0aa53c31",
|
||||
"https://deno.land/std@0.224.0/assert/assert_strict_equals.ts": "b4f45f0fd2e54d9029171876bd0b42dd9ed0efd8f853ab92a3f50127acfa54f5",
|
||||
"https://deno.land/std@0.224.0/assert/assert_string_includes.ts": "496b9ecad84deab72c8718735373feb6cdaa071eb91a98206f6f3cb4285e71b8",
|
||||
"https://deno.land/std@0.224.0/assert/assert_throws.ts": "c6508b2879d465898dab2798009299867e67c570d7d34c90a2d235e4553906eb",
|
||||
"https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917",
|
||||
"https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47",
|
||||
"https://deno.land/std@0.224.0/assert/fail.ts": "0eba674ffb47dff083f02ced76d5130460bff1a9a68c6514ebe0cdea4abadb68",
|
||||
"https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3",
|
||||
"https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73",
|
||||
"https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19",
|
||||
"https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5",
|
||||
"https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6",
|
||||
"https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2",
|
||||
"https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e",
|
||||
"https://deno.land/std@0.224.0/testing/mock.ts": "a963181c2860b6ba3eb60e08b62c164d33cf5da7cd445893499b2efda20074db"
|
||||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@oak/oak@^17.1.3",
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
// @ts-nocheck
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.12.0
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
// @ts-nocheck
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.12.0
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
// @ts-nocheck
|
||||
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-ts_proto v2.12.0
|
||||
|
||||
@ -10,15 +10,4 @@ podman exec hrms_proto_compiler sh -c "
|
||||
-I ./protos \
|
||||
./protos/*.proto
|
||||
"
|
||||
|
||||
# FIX: Prepend // @ts-nocheck to all generated files to prevent Deno strict type-checking errors
|
||||
for file in generated/*.ts; do
|
||||
if [ -f "$file" ]; then
|
||||
# Check if the first line is not already @ts-nocheck
|
||||
if ! head -n 1 "$file" | grep -q "@ts-nocheck"; then
|
||||
echo "// @ts-nocheck" | cat - "$file" > temp && mv temp "$file"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "âś… gRPC code generation complete!"
|
||||
@ -1,30 +1,10 @@
|
||||
// services/ams/handlers/regularization.handler.ts
|
||||
import * as RegService from "../services/regularization.service.ts";
|
||||
|
||||
// FIX: Timezone safe date formatters
|
||||
const formatDateTime = (dateVal: any): string => {
|
||||
if (!dateVal) return "";
|
||||
if (dateVal instanceof Date) {
|
||||
const offset = dateVal.getTimezoneOffset() * 60000;
|
||||
const localDate = new Date(dateVal.getTime() - offset);
|
||||
return localDate.toISOString().replace("T", " ").substring(0, 19);
|
||||
}
|
||||
return String(dateVal).replace("T", " ").substring(0, 19);
|
||||
};
|
||||
|
||||
const formatDate = (dateVal: any): string => {
|
||||
if (!dateVal) return "";
|
||||
if (dateVal instanceof Date) {
|
||||
const offset = dateVal.getTimezoneOffset() * 60000;
|
||||
const localDate = new Date(dateVal.getTime() - offset);
|
||||
return localDate.toISOString().split("T")[0];
|
||||
}
|
||||
return String(dateVal).split("T")[0];
|
||||
};
|
||||
|
||||
export const RegularizationServiceImplementation = {
|
||||
async createRegularizationRequest(req: any) {
|
||||
try {
|
||||
// Map camelCase gRPC payload back to snake_case for the DB service layer
|
||||
const dbPayload = {
|
||||
attendance_id: req.attendanceId || null,
|
||||
employee_id: req.employeeId,
|
||||
@ -53,10 +33,10 @@ export const RegularizationServiceImplementation = {
|
||||
const mappedData = data.map((reg: any) => ({
|
||||
regularizationId: Number(reg.regularization_id) || 0,
|
||||
employeeId: Number(reg.employee_id) || 0,
|
||||
targetDate: formatDate(reg.target_date),
|
||||
targetDate: reg.target_date || "",
|
||||
regularizationType: reg.regularization_type || "",
|
||||
requestedCheckIn: formatDateTime(reg.requested_check_in) || undefined,
|
||||
requestedCheckOut: formatDateTime(reg.requested_check_out) || undefined,
|
||||
requestedCheckIn: reg.requested_check_in || undefined,
|
||||
requestedCheckOut: reg.requested_check_out || undefined,
|
||||
reason: reg.reason || "",
|
||||
status: reg.status || "",
|
||||
branchId: Number(reg.branch_id) || 0,
|
||||
@ -90,17 +70,16 @@ export const RegularizationServiceImplementation = {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
},
|
||||
|
||||
async getAllRegularizations(req: any) {
|
||||
try {
|
||||
const data = await RegService.getAllRegularizations();
|
||||
const mappedData = data.map((reg: any) => ({
|
||||
regularizationId: Number(reg.regularization_id) || 0,
|
||||
employeeId: Number(reg.employee_id) || 0,
|
||||
targetDate: formatDate(reg.target_date),
|
||||
targetDate: reg.target_date ? new Date(reg.target_date).toISOString() : "",
|
||||
regularizationType: reg.regularization_type || "",
|
||||
requestedCheckIn: formatDateTime(reg.requested_check_in) || "",
|
||||
requestedCheckOut: formatDateTime(reg.requested_check_out) || "",
|
||||
requestedCheckIn: reg.requested_check_in ? new Date(reg.requested_check_in).toISOString() : "",
|
||||
requestedCheckOut: reg.requested_check_out ? new Date(reg.requested_check_out).toISOString() : "",
|
||||
reason: reg.reason || "",
|
||||
status: reg.status || "",
|
||||
branchId: Number(reg.branch_id) || 0,
|
||||
|
||||
@ -21,14 +21,12 @@ export const findPendingRegularizations = async (branchIds: number[] | null) =>
|
||||
}
|
||||
query += ` ORDER BY target_date DESC`;
|
||||
|
||||
// FIX: Cast result as any[]
|
||||
const [rows]: any[] = await pool.query(query, params);
|
||||
const [rows] = await pool.query(query, params);
|
||||
return rows;
|
||||
};
|
||||
|
||||
export const findRegularizationById = async (id: number, conn: any) => {
|
||||
// FIX: Cast result as any[]
|
||||
const [rows]: any[] = await conn.execute(
|
||||
const [rows]: any = await conn.execute(
|
||||
`SELECT regularization_id, attendance_id, branch_id, employee_id, requested_check_in, requested_check_out, status
|
||||
FROM attendance_regularizations WHERE regularization_id = ?`,
|
||||
[id]
|
||||
@ -44,8 +42,7 @@ export const updateRegularizationStatus = async (id: number, status: string, rev
|
||||
};
|
||||
|
||||
export const findAllRegularizations = async () => {
|
||||
// FIX: Cast result as any[]
|
||||
const [rows]: any[] = await pool.execute(
|
||||
const [rows] = await pool.execute(
|
||||
`SELECT regularization_id, employee_id, target_date, regularization_type,
|
||||
requested_check_in, requested_check_out, reason, status, branch_id, reviewed_by_id
|
||||
FROM attendance_regularizations
|
||||
|
||||
@ -1,102 +0,0 @@
|
||||
// services/ams/services/regularization.service.test.ts
|
||||
import { assertEquals, assertRejects} from "https://deno.land/std@0.224.0/assert/mod.ts";
|
||||
import { stub } from "https://deno.land/std@0.224.0/testing/mock.ts";
|
||||
import * as RegService from "./regularization.service.ts";
|
||||
import { __deps } from "./regularization.service.ts";
|
||||
import { formatDateTime } from "./regularization.service.ts";
|
||||
|
||||
|
||||
Deno.test("createRegularizationRequest should throw error if target_date is a Sunday", async () => {
|
||||
const payload = {
|
||||
employee_id: 135,
|
||||
reason: "Forgot to punch",
|
||||
regularization_type: "MISPUNCH",
|
||||
target_date: "2026-07-12", // This is a Sunday
|
||||
};
|
||||
|
||||
// We expect this promise to reject with a specific error
|
||||
await assertRejects(
|
||||
() => RegService.createRegularizationRequest(payload),
|
||||
Error,
|
||||
"Cannot apply for regularization on a weekly off (Sunday)."
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("createRegularizationRequest should throw error if missing required fields", async () => {
|
||||
const payload = {
|
||||
employee_id: 135,
|
||||
// missing reason and target_date
|
||||
};
|
||||
|
||||
await assertRejects(
|
||||
() => RegService.createRegularizationRequest(payload as any),
|
||||
Error,
|
||||
"Missing required fields."
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("createRegularizationRequest should throw error if employee is not found in EMS", async () => {
|
||||
const payload = {
|
||||
employee_id: 999,
|
||||
reason: "Forgot to punch",
|
||||
regularization_type: "MISPUNCH",
|
||||
target_date: "2026-07-15",
|
||||
};
|
||||
|
||||
// FIX: Stub the method on the __deps object
|
||||
const rosterStub = stub(__deps, "getEmployeeRoster", () => Promise.resolve([]));
|
||||
|
||||
try {
|
||||
await assertRejects(
|
||||
() => RegService.createRegularizationRequest(payload),
|
||||
Error,
|
||||
"Employee not found in EMS."
|
||||
);
|
||||
} finally {
|
||||
rosterStub.restore();
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("createRegularizationRequest should succeed and insert into DB for valid data", async () => {
|
||||
const payload = {
|
||||
employee_id: 135,
|
||||
reason: "Forgot to punch",
|
||||
regularization_type: "MISPUNCH",
|
||||
target_date: "2026-07-15",
|
||||
requested_check_in: "2026-07-15 10:00:00",
|
||||
requested_check_out: "2026-07-15 19:00:00"
|
||||
};
|
||||
|
||||
// FIX: Stub the methods on the __deps object
|
||||
const rosterStub = stub(__deps, "getEmployeeRoster", () =>
|
||||
Promise.resolve([{ employeeId: 135, branchId: 1 } as any])
|
||||
);
|
||||
|
||||
let insertedData: any = null;
|
||||
const insertStub = stub(__deps, "insertRegularization", async (data: any) => {
|
||||
insertedData = data;
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
try {
|
||||
await RegService.createRegularizationRequest(payload);
|
||||
|
||||
assertEquals(insertedData.employee_id, 135);
|
||||
assertEquals(insertedData.branch_id, 1);
|
||||
assertEquals(insertedData.target_date, "2026-07-15");
|
||||
|
||||
} finally {
|
||||
rosterStub.restore();
|
||||
insertStub.restore();
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("formatDateTime should not shift the date forward due to timezone", () => {
|
||||
// Simulate MySQL returning a Date object for "2026-07-12 18:30:00" local time
|
||||
const mysqlDateObject = new Date(2026, 6, 12, 18, 30, 0); // Month is 0-indexed (6 = July)
|
||||
|
||||
const formattedString = formatDateTime(mysqlDateObject);
|
||||
|
||||
// Assert it stays on the 12th, doesn't shift to the 13th
|
||||
assertEquals(formattedString?.startsWith("2026-07-12"), true);
|
||||
});
|
||||
@ -1,62 +1,21 @@
|
||||
// services/ams/services/regularization.service.ts
|
||||
import pool from "../db.ts";
|
||||
import { AppRole } from "core/auth.ts";
|
||||
//import { getEmployeeRoster } from "core/internal-client.ts";
|
||||
import * as InternalClient from "core/internal-client.ts"; // CHANGED TO NAMESPACE
|
||||
import { getEmployeeRoster } from "core/internal-client.ts";
|
||||
import * as RegRepo from "../repositories/regularization.repository.ts";
|
||||
import * as AttendanceRepo from "../repositories/attendance.repository.ts";
|
||||
|
||||
// FIX: Dependency Injection Object for test mocking
|
||||
export const __deps = {
|
||||
getEmployeeRoster: InternalClient.getEmployeeRoster,
|
||||
insertRegularization: RegRepo.insertRegularization,
|
||||
findPendingRegularizations: RegRepo.findPendingRegularizations,
|
||||
findAllRegularizations: RegRepo.findAllRegularizations,
|
||||
findRegularizationById: RegRepo.findRegularizationById,
|
||||
updateRegularizationStatus: RegRepo.updateRegularizationStatus,
|
||||
findShiftDetails: AttendanceRepo.findShiftDetails,
|
||||
updateProcessedAttendanceById: AttendanceRepo.updateProcessedAttendanceById,
|
||||
};
|
||||
|
||||
// FIX: Timezone safe formatter for recalculating attendance
|
||||
export const formatDateTime = (dateVal: any): string | undefined => {
|
||||
if (!dateVal) return undefined;
|
||||
if (dateVal instanceof Date) {
|
||||
const offset = dateVal.getTimezoneOffset() * 60000;
|
||||
const localDate = new Date(dateVal.getTime() - offset);
|
||||
return localDate.toISOString().replace("T", " ").substring(0, 19);
|
||||
}
|
||||
return String(dateVal).replace("T", " ").substring(0, 19);
|
||||
};
|
||||
|
||||
export const createRegularizationRequest = async (data: any) => {
|
||||
if (!data.employee_id || !data.reason || !data.regularization_type || !data.target_date) {
|
||||
throw new Error("Missing required fields.");
|
||||
}
|
||||
|
||||
// FIX: Weekend validation check
|
||||
const targetDateObj = new Date(data.target_date);
|
||||
const dayOfWeek = targetDateObj.getDay(); // 0 = Sunday, 6 = Saturday
|
||||
|
||||
// Assuming Sunday (0) is the default weekly off.
|
||||
if (dayOfWeek === 0) {
|
||||
throw new Error("Cannot apply for regularization on a weekly off (Sunday).");
|
||||
}
|
||||
|
||||
// Validate requested check-in date matches target date
|
||||
if (data.requested_check_in) {
|
||||
const checkInDate = new Date(data.requested_check_in).toISOString().split('T')[0];
|
||||
if (checkInDate !== data.target_date) {
|
||||
throw new Error("Requested Check-In date does not match the Target Date.");
|
||||
}
|
||||
}
|
||||
|
||||
const roster = await __deps.getEmployeeRoster([data.employee_id]);
|
||||
const roster = await getEmployeeRoster([data.employee_id]);
|
||||
if (roster.length === 0) throw new Error("Employee not found in EMS.");
|
||||
|
||||
// Fallback to 0 if branchId is undefined to prevent NOT NULL constraint crash
|
||||
const branch_id = roster[0].branchId || 0;
|
||||
|
||||
await __deps.insertRegularization({ ...data, branch_id });
|
||||
await RegRepo.insertRegularization({ ...data, branch_id });
|
||||
return true;
|
||||
};
|
||||
|
||||
@ -67,11 +26,11 @@ export const getPendingRegularizations = async (user: any) => {
|
||||
if (!branchIds || branchIds.length === 0) return [];
|
||||
}
|
||||
|
||||
const rows = await __deps.findPendingRegularizations(branchIds);
|
||||
const rows = await RegRepo.findPendingRegularizations(branchIds);
|
||||
if (rows.length === 0) return [];
|
||||
|
||||
const employeeIds = [...new Set(rows.map((r: any) => Number(r.employee_id)))] as number[];
|
||||
const roster = await __deps.getEmployeeRoster(employeeIds);
|
||||
const employeeIds = [...new Set(rows.map((r: any) => r.employee_id))];
|
||||
const roster = await getEmployeeRoster(employeeIds);
|
||||
|
||||
const rosterMap = new Map(roster.map((e: any) => [e.employeeId, e]));
|
||||
|
||||
@ -88,16 +47,17 @@ export const getPendingRegularizations = async (user: any) => {
|
||||
};
|
||||
|
||||
export const getAllRegularizations = async () => {
|
||||
const rows = await __deps.findAllRegularizations();
|
||||
const rows = await RegRepo.findAllRegularizations();
|
||||
if (rows.length === 0) return [];
|
||||
|
||||
// Collect both employee_id and reviewed_by_id to fetch their names from EMS
|
||||
const allIds = new Set<number>();
|
||||
rows.forEach((r: any) => {
|
||||
if (r.employee_id) allIds.add(Number(r.employee_id));
|
||||
if (r.reviewed_by_id) allIds.add(Number(r.reviewed_by_id));
|
||||
});
|
||||
|
||||
const roster = await __deps.getEmployeeRoster(Array.from(allIds));
|
||||
const roster = await getEmployeeRoster(Array.from(allIds));
|
||||
const rosterMap = new Map(roster.map((e: any) => [e.employeeId, e]));
|
||||
|
||||
return rows.map((req: any) => {
|
||||
@ -110,6 +70,7 @@ export const getAllRegularizations = async () => {
|
||||
first_name: emp?.fullName?.split(" ")[0] || "",
|
||||
last_name: emp?.fullName?.split(" ").slice(1).join(" ") || "",
|
||||
branch_name: emp?.branchName || "N/A",
|
||||
// FIX: EMS Roster returns 'fullName', not 'firstName'/'lastName'
|
||||
reviewer_name: reviewer?.fullName || "Pending Review"
|
||||
};
|
||||
});
|
||||
@ -123,10 +84,11 @@ export const reviewRegularizationRequest = async (user: any, data: any) => {
|
||||
const connection = await pool.getConnection();
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
const request = await __deps.findRegularizationById(data.regularization_id, connection);
|
||||
const request = await RegRepo.findRegularizationById(data.regularization_id, connection);
|
||||
|
||||
if (!request) throw new Error("Regularization request not found.");
|
||||
|
||||
// AUTHORIZATION LOGIC: Allow Directors globally, but restrict HR Managers to their branches
|
||||
if (user.role !== AppRole.DIRECTOR) {
|
||||
if (user.role !== AppRole.HR_MANAGER) {
|
||||
throw new Error("Access Denied: Only HR Managers and Directors can review requests.");
|
||||
@ -138,24 +100,29 @@ export const reviewRegularizationRequest = async (user: any, data: any) => {
|
||||
|
||||
if (request.status !== "PENDING") throw new Error("This request has already been processed.");
|
||||
|
||||
// FIX: Use __deps
|
||||
await __deps.updateRegularizationStatus(data.regularization_id, data.action, user.employee_id, connection);
|
||||
await RegRepo.updateRegularizationStatus(data.regularization_id, data.action, user.employee_id, connection);
|
||||
|
||||
if (data.action === "APPROVED") {
|
||||
let worked_hours = 0.0, final_status = "FULL_DAY", check_in_status = "ON_TIME";
|
||||
let rawCheckIn: string | undefined;
|
||||
let rawCheckOut: string | undefined;
|
||||
|
||||
const rawCheckIn = formatDateTime(request.requested_check_in);
|
||||
const rawCheckOut = formatDateTime(request.requested_check_out);
|
||||
|
||||
// FIX: Use __deps
|
||||
const roster = await __deps.getEmployeeRoster([request.employee_id]);
|
||||
const roster = await getEmployeeRoster([request.employee_id]);
|
||||
const emp = roster[0];
|
||||
|
||||
if (emp) {
|
||||
// FIX: Use __deps
|
||||
const shift = await __deps.findShiftDetails(emp.branchId, emp.companyId, connection);
|
||||
const shift = await AttendanceRepo.findShiftDetails(emp.branchId, emp.companyId, connection);
|
||||
|
||||
if (request.requested_check_in && request.requested_check_out) {
|
||||
// Safely convert Date objects to strings if necessary
|
||||
rawCheckIn = request.requested_check_in instanceof Date
|
||||
? request.requested_check_in.toISOString().replace("T", " ").substring(0, 19)
|
||||
: request.requested_check_in;
|
||||
|
||||
rawCheckOut = request.requested_check_out instanceof Date
|
||||
? request.requested_check_out.toISOString().replace("T", " ").substring(0, 19)
|
||||
: request.requested_check_out;
|
||||
|
||||
if (rawCheckIn && rawCheckOut) {
|
||||
const checkInMs = new Date(rawCheckIn.replace(" ", "T")).getTime();
|
||||
const checkOutMs = new Date(rawCheckOut.replace(" ", "T")).getTime();
|
||||
const effectiveCheckOutMs = checkOutMs < checkInMs ? checkOutMs + (24 * 60 * 60 * 1000) : checkOutMs;
|
||||
@ -165,6 +132,7 @@ export const reviewRegularizationRequest = async (user: any, data: any) => {
|
||||
else if (worked_hours >= 4.0) final_status = "HALF_DAY";
|
||||
else final_status = "ABSENT";
|
||||
|
||||
// rawCheckIn is already "YYYY-MM-DD HH:MM:SS"
|
||||
const timePart = rawCheckIn.split(" ")[1];
|
||||
const [punchH, punchM, punchS] = timePart.split(":").map(Number);
|
||||
const [shiftH, shiftM, shiftS] = shift.start_time.split(":").map(Number);
|
||||
@ -172,28 +140,21 @@ export const reviewRegularizationRequest = async (user: any, data: any) => {
|
||||
const punchTotalSeconds = punchH * 3600 + punchM * 60 + punchS;
|
||||
const shiftCutoffSeconds = shiftH * 3600 + shiftM * 60 + (shift.grace_period_minutes * 60);
|
||||
|
||||
if (punchTotalSeconds > shiftCutoffSeconds) check_in_status = "LATE";
|
||||
if (punchTotalSeconds > shiftCutoffSeconds) {
|
||||
check_in_status = "LATE";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let attendanceIdToUpdate = request.attendance_id;
|
||||
if (!attendanceIdToUpdate) {
|
||||
const [attRows]: any[] = await connection.execute(
|
||||
`SELECT attendance_id FROM processed_daily_attendance WHERE employee_id = ? AND work_date = ?`,
|
||||
[request.employee_id, request.target_date]
|
||||
);
|
||||
if (attRows.length > 0) attendanceIdToUpdate = attRows[0].attendance_id;
|
||||
}
|
||||
|
||||
if (attendanceIdToUpdate && (rawCheckIn || rawCheckOut)) {
|
||||
// FIX: Use __deps
|
||||
await __deps.updateProcessedAttendanceById(attendanceIdToUpdate, {
|
||||
requested_check_in: rawCheckIn,
|
||||
requested_check_out: rawCheckOut,
|
||||
worked_hours,
|
||||
final_status,
|
||||
check_in_status
|
||||
}, connection);
|
||||
}
|
||||
// Use the safe string variables for the DB update
|
||||
if (request.attendance_id && rawCheckIn && rawCheckOut) {
|
||||
await AttendanceRepo.updateProcessedAttendanceById(request.attendance_id, {
|
||||
requested_check_in: rawCheckIn,
|
||||
requested_check_out: rawCheckOut,
|
||||
worked_hours,
|
||||
final_status,
|
||||
check_in_status
|
||||
}, connection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user