Compare commits

..

No commits in common. "7d1b7609345728eb0a30382037430b0cbea9d0bd" and "de6d25b2566f57a326ebb24de149175318f4b87d" have entirely different histories.

28 changed files with 173 additions and 2975 deletions

968
deno.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -102,7 +102,6 @@ export interface DailyReportRequest {
designation?: string | undefined; designation?: string | undefined;
employeeName?: string | undefined; employeeName?: string | undefined;
employeeCode?: string | undefined; employeeCode?: string | undefined;
fileType: string;
} }
export interface DailySummary { export interface DailySummary {
@ -145,15 +144,23 @@ export interface RangeReportRequest {
fromDate: string; fromDate: string;
toDate: string; toDate: string;
branchId?: number | undefined; branchId?: number | undefined;
departmentId?: number | undefined; departmentId?:
companyId?: number | undefined; | number
designation?: string | undefined; | undefined;
employeeName?: string | undefined; /** NEW */
employeeCode?: companyId?:
| number
| undefined;
/** NEW */
designation?:
| string | string
| undefined; | undefined;
/** <--- ADD THIS LINE */ /** NEW */
fileType: string; employeeName?:
| string
| undefined;
/** NEW */
employeeCode?: string | undefined;
} }
export interface RangeMetrics { export interface RangeMetrics {
@ -270,7 +277,6 @@ export interface PendingRegData {
firstName: string; firstName: string;
lastName: string; lastName: string;
branchName: string; branchName: string;
reviewerName: string;
} }
export interface PendingRegResponse { export interface PendingRegResponse {
@ -286,17 +292,6 @@ export interface ReviewRegRequest {
managedBranchIds: number[]; managedBranchIds: number[];
} }
export interface AllRegularizationsResponse {
success: boolean;
data: PendingRegData[];
}
/** Add to common messages */
export interface ExportReportResponse {
fileContent: Buffer;
fileName: string;
}
function createBaseEmpty(): Empty { function createBaseEmpty(): Empty {
return {}; return {};
} }
@ -1321,7 +1316,6 @@ function createBaseDailyReportRequest(): DailyReportRequest {
designation: undefined, designation: undefined,
employeeName: undefined, employeeName: undefined,
employeeCode: undefined, employeeCode: undefined,
fileType: "",
}; };
} }
@ -1348,9 +1342,6 @@ export const DailyReportRequest: MessageFns<DailyReportRequest> = {
if (message.employeeCode !== undefined) { if (message.employeeCode !== undefined) {
writer.uint32(58).string(message.employeeCode); writer.uint32(58).string(message.employeeCode);
} }
if (message.fileType !== "") {
writer.uint32(66).string(message.fileType);
}
return writer; return writer;
}, },
@ -1417,14 +1408,6 @@ export const DailyReportRequest: MessageFns<DailyReportRequest> = {
message.employeeCode = reader.string(); message.employeeCode = reader.string();
continue; continue;
} }
case 8: {
if (tag !== 66) {
break;
}
message.fileType = reader.string();
continue;
}
} }
if ((tag & 7) === 4 || tag === 0) { if ((tag & 7) === 4 || tag === 0) {
break; break;
@ -1463,11 +1446,6 @@ export const DailyReportRequest: MessageFns<DailyReportRequest> = {
: isSet(object.employee_code) : isSet(object.employee_code)
? globalThis.String(object.employee_code) ? globalThis.String(object.employee_code)
: undefined, : undefined,
fileType: isSet(object.fileType)
? globalThis.String(object.fileType)
: isSet(object.file_type)
? globalThis.String(object.file_type)
: "",
}; };
}, },
@ -1494,9 +1472,6 @@ export const DailyReportRequest: MessageFns<DailyReportRequest> = {
if (message.employeeCode !== undefined) { if (message.employeeCode !== undefined) {
obj.employeeCode = message.employeeCode; obj.employeeCode = message.employeeCode;
} }
if (message.fileType !== "") {
obj.fileType = message.fileType;
}
return obj; return obj;
}, },
@ -1512,7 +1487,6 @@ export const DailyReportRequest: MessageFns<DailyReportRequest> = {
message.designation = object.designation ?? undefined; message.designation = object.designation ?? undefined;
message.employeeName = object.employeeName ?? undefined; message.employeeName = object.employeeName ?? undefined;
message.employeeCode = object.employeeCode ?? undefined; message.employeeCode = object.employeeCode ?? undefined;
message.fileType = object.fileType ?? "";
return message; return message;
}, },
}; };
@ -2073,7 +2047,6 @@ function createBaseRangeReportRequest(): RangeReportRequest {
designation: undefined, designation: undefined,
employeeName: undefined, employeeName: undefined,
employeeCode: undefined, employeeCode: undefined,
fileType: "",
}; };
} }
@ -2103,9 +2076,6 @@ export const RangeReportRequest: MessageFns<RangeReportRequest> = {
if (message.employeeCode !== undefined) { if (message.employeeCode !== undefined) {
writer.uint32(66).string(message.employeeCode); writer.uint32(66).string(message.employeeCode);
} }
if (message.fileType !== "") {
writer.uint32(74).string(message.fileType);
}
return writer; return writer;
}, },
@ -2180,14 +2150,6 @@ export const RangeReportRequest: MessageFns<RangeReportRequest> = {
message.employeeCode = reader.string(); message.employeeCode = reader.string();
continue; continue;
} }
case 9: {
if (tag !== 74) {
break;
}
message.fileType = reader.string();
continue;
}
} }
if ((tag & 7) === 4 || tag === 0) { if ((tag & 7) === 4 || tag === 0) {
break; break;
@ -2235,11 +2197,6 @@ export const RangeReportRequest: MessageFns<RangeReportRequest> = {
: isSet(object.employee_code) : isSet(object.employee_code)
? globalThis.String(object.employee_code) ? globalThis.String(object.employee_code)
: undefined, : undefined,
fileType: isSet(object.fileType)
? globalThis.String(object.fileType)
: isSet(object.file_type)
? globalThis.String(object.file_type)
: "",
}; };
}, },
@ -2269,9 +2226,6 @@ export const RangeReportRequest: MessageFns<RangeReportRequest> = {
if (message.employeeCode !== undefined) { if (message.employeeCode !== undefined) {
obj.employeeCode = message.employeeCode; obj.employeeCode = message.employeeCode;
} }
if (message.fileType !== "") {
obj.fileType = message.fileType;
}
return obj; return obj;
}, },
@ -2288,7 +2242,6 @@ export const RangeReportRequest: MessageFns<RangeReportRequest> = {
message.designation = object.designation ?? undefined; message.designation = object.designation ?? undefined;
message.employeeName = object.employeeName ?? undefined; message.employeeName = object.employeeName ?? undefined;
message.employeeCode = object.employeeCode ?? undefined; message.employeeCode = object.employeeCode ?? undefined;
message.fileType = object.fileType ?? "";
return message; return message;
}, },
}; };
@ -3869,7 +3822,6 @@ function createBasePendingRegData(): PendingRegData {
firstName: "", firstName: "",
lastName: "", lastName: "",
branchName: "", branchName: "",
reviewerName: "",
}; };
} }
@ -3914,9 +3866,6 @@ export const PendingRegData: MessageFns<PendingRegData> = {
if (message.branchName !== "") { if (message.branchName !== "") {
writer.uint32(106).string(message.branchName); writer.uint32(106).string(message.branchName);
} }
if (message.reviewerName !== "") {
writer.uint32(114).string(message.reviewerName);
}
return writer; return writer;
}, },
@ -4031,14 +3980,6 @@ export const PendingRegData: MessageFns<PendingRegData> = {
message.branchName = reader.string(); message.branchName = reader.string();
continue; continue;
} }
case 14: {
if (tag !== 114) {
break;
}
message.reviewerName = reader.string();
continue;
}
} }
if ((tag & 7) === 4 || tag === 0) { if ((tag & 7) === 4 || tag === 0) {
break; break;
@ -4107,11 +4048,6 @@ export const PendingRegData: MessageFns<PendingRegData> = {
: isSet(object.branch_name) : isSet(object.branch_name)
? globalThis.String(object.branch_name) ? globalThis.String(object.branch_name)
: "", : "",
reviewerName: isSet(object.reviewerName)
? globalThis.String(object.reviewerName)
: isSet(object.reviewer_name)
? globalThis.String(object.reviewer_name)
: "",
}; };
}, },
@ -4156,9 +4092,6 @@ export const PendingRegData: MessageFns<PendingRegData> = {
if (message.branchName !== "") { if (message.branchName !== "") {
obj.branchName = message.branchName; obj.branchName = message.branchName;
} }
if (message.reviewerName !== "") {
obj.reviewerName = message.reviewerName;
}
return obj; return obj;
}, },
@ -4180,7 +4113,6 @@ export const PendingRegData: MessageFns<PendingRegData> = {
message.firstName = object.firstName ?? ""; message.firstName = object.firstName ?? "";
message.lastName = object.lastName ?? ""; message.lastName = object.lastName ?? "";
message.branchName = object.branchName ?? ""; message.branchName = object.branchName ?? "";
message.reviewerName = object.reviewerName ?? "";
return message; return message;
}, },
}; };
@ -4413,166 +4345,6 @@ export const ReviewRegRequest: MessageFns<ReviewRegRequest> = {
}, },
}; };
function createBaseAllRegularizationsResponse(): AllRegularizationsResponse {
return { success: false, data: [] };
}
export const AllRegularizationsResponse: MessageFns<AllRegularizationsResponse> = {
encode(message: AllRegularizationsResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.success !== false) {
writer.uint32(8).bool(message.success);
}
for (const v of message.data) {
PendingRegData.encode(v!, writer.uint32(18).fork()).join();
}
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): AllRegularizationsResponse {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
const end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseAllRegularizationsResponse();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
if (tag !== 8) {
break;
}
message.success = reader.bool();
continue;
}
case 2: {
if (tag !== 18) {
break;
}
message.data.push(PendingRegData.decode(reader, reader.uint32()));
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skip(tag & 7);
}
return message;
},
fromJSON(object: any): AllRegularizationsResponse {
return {
success: isSet(object.success) ? globalThis.Boolean(object.success) : false,
data: globalThis.Array.isArray(object?.data) ? object.data.map((e: any) => PendingRegData.fromJSON(e)) : [],
};
},
toJSON(message: AllRegularizationsResponse): unknown {
const obj: any = {};
if (message.success !== false) {
obj.success = message.success;
}
if (message.data?.length) {
obj.data = message.data.map((e) => PendingRegData.toJSON(e));
}
return obj;
},
create<I extends Exact<DeepPartial<AllRegularizationsResponse>, I>>(base?: I): AllRegularizationsResponse {
return AllRegularizationsResponse.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<AllRegularizationsResponse>, I>>(object: I): AllRegularizationsResponse {
const message = createBaseAllRegularizationsResponse();
message.success = object.success ?? false;
message.data = object.data?.map((e) => PendingRegData.fromPartial(e)) || [];
return message;
},
};
function createBaseExportReportResponse(): ExportReportResponse {
return { fileContent: Buffer.alloc(0), fileName: "" };
}
export const ExportReportResponse: MessageFns<ExportReportResponse> = {
encode(message: ExportReportResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.fileContent.length !== 0) {
writer.uint32(10).bytes(message.fileContent);
}
if (message.fileName !== "") {
writer.uint32(18).string(message.fileName);
}
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): ExportReportResponse {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
const end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseExportReportResponse();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
if (tag !== 10) {
break;
}
message.fileContent = Buffer.from(reader.bytes());
continue;
}
case 2: {
if (tag !== 18) {
break;
}
message.fileName = reader.string();
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skip(tag & 7);
}
return message;
},
fromJSON(object: any): ExportReportResponse {
return {
fileContent: isSet(object.fileContent)
? Buffer.from(bytesFromBase64(object.fileContent))
: isSet(object.file_content)
? Buffer.from(bytesFromBase64(object.file_content))
: Buffer.alloc(0),
fileName: isSet(object.fileName)
? globalThis.String(object.fileName)
: isSet(object.file_name)
? globalThis.String(object.file_name)
: "",
};
},
toJSON(message: ExportReportResponse): unknown {
const obj: any = {};
if (message.fileContent.length !== 0) {
obj.fileContent = base64FromBytes(message.fileContent);
}
if (message.fileName !== "") {
obj.fileName = message.fileName;
}
return obj;
},
create<I extends Exact<DeepPartial<ExportReportResponse>, I>>(base?: I): ExportReportResponse {
return ExportReportResponse.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<ExportReportResponse>, I>>(object: I): ExportReportResponse {
const message = createBaseExportReportResponse();
message.fileContent = object.fileContent ?? Buffer.alloc(0);
message.fileName = object.fileName ?? "";
return message;
},
};
/** /**
* ========================================== * ==========================================
* SERVICES * SERVICES
@ -4638,26 +4410,6 @@ export const AttendanceServiceService = {
Buffer.from(SingleRangeReportResponse.encode(value).finish()), Buffer.from(SingleRangeReportResponse.encode(value).finish()),
responseDeserialize: (value: Buffer): SingleRangeReportResponse => SingleRangeReportResponse.decode(value), responseDeserialize: (value: Buffer): SingleRangeReportResponse => SingleRangeReportResponse.decode(value),
}, },
exportDailyReport: {
path: "/ams.AttendanceService/ExportDailyReport" as const,
requestStream: false as const,
responseStream: false as const,
requestSerialize: (value: DailyReportRequest): Buffer => Buffer.from(DailyReportRequest.encode(value).finish()),
requestDeserialize: (value: Buffer): DailyReportRequest => DailyReportRequest.decode(value),
responseSerialize: (value: ExportReportResponse): Buffer =>
Buffer.from(ExportReportResponse.encode(value).finish()),
responseDeserialize: (value: Buffer): ExportReportResponse => ExportReportResponse.decode(value),
},
exportAdminRangeReport: {
path: "/ams.AttendanceService/ExportAdminRangeReport" as const,
requestStream: false as const,
responseStream: false as const,
requestSerialize: (value: RangeReportRequest): Buffer => Buffer.from(RangeReportRequest.encode(value).finish()),
requestDeserialize: (value: Buffer): RangeReportRequest => RangeReportRequest.decode(value),
responseSerialize: (value: ExportReportResponse): Buffer =>
Buffer.from(ExportReportResponse.encode(value).finish()),
responseDeserialize: (value: Buffer): ExportReportResponse => ExportReportResponse.decode(value),
},
} as const; } as const;
export interface AttendanceServiceServer extends UntypedServiceImplementation { export interface AttendanceServiceServer extends UntypedServiceImplementation {
@ -4667,8 +4419,6 @@ export interface AttendanceServiceServer extends UntypedServiceImplementation {
getDailyReport: handleUnaryCall<DailyReportRequest, DailyReportResponse>; getDailyReport: handleUnaryCall<DailyReportRequest, DailyReportResponse>;
getAdminRangeReport: handleUnaryCall<RangeReportRequest, AdminRangeReportResponse>; getAdminRangeReport: handleUnaryCall<RangeReportRequest, AdminRangeReportResponse>;
getSingleEmployeeRangeReport: handleUnaryCall<SingleRangeReportRequest, SingleRangeReportResponse>; getSingleEmployeeRangeReport: handleUnaryCall<SingleRangeReportRequest, SingleRangeReportResponse>;
exportDailyReport: handleUnaryCall<DailyReportRequest, ExportReportResponse>;
exportAdminRangeReport: handleUnaryCall<RangeReportRequest, ExportReportResponse>;
} }
export interface AttendanceServiceClient extends Client { export interface AttendanceServiceClient extends Client {
@ -4762,36 +4512,6 @@ export interface AttendanceServiceClient extends Client {
options: Partial<CallOptions>, options: Partial<CallOptions>,
callback: (error: ServiceError | null, response: SingleRangeReportResponse) => void, callback: (error: ServiceError | null, response: SingleRangeReportResponse) => void,
): ClientUnaryCall; ): ClientUnaryCall;
exportDailyReport(
request: DailyReportRequest,
callback: (error: ServiceError | null, response: ExportReportResponse) => void,
): ClientUnaryCall;
exportDailyReport(
request: DailyReportRequest,
metadata: Metadata,
callback: (error: ServiceError | null, response: ExportReportResponse) => void,
): ClientUnaryCall;
exportDailyReport(
request: DailyReportRequest,
metadata: Metadata,
options: Partial<CallOptions>,
callback: (error: ServiceError | null, response: ExportReportResponse) => void,
): ClientUnaryCall;
exportAdminRangeReport(
request: RangeReportRequest,
callback: (error: ServiceError | null, response: ExportReportResponse) => void,
): ClientUnaryCall;
exportAdminRangeReport(
request: RangeReportRequest,
metadata: Metadata,
callback: (error: ServiceError | null, response: ExportReportResponse) => void,
): ClientUnaryCall;
exportAdminRangeReport(
request: RangeReportRequest,
metadata: Metadata,
options: Partial<CallOptions>,
callback: (error: ServiceError | null, response: ExportReportResponse) => void,
): ClientUnaryCall;
} }
export const AttendanceServiceClient = makeGenericClientConstructor( export const AttendanceServiceClient = makeGenericClientConstructor(
@ -4832,25 +4552,12 @@ export const RegularizationServiceService = {
responseSerialize: (value: SuccessResponse): Buffer => Buffer.from(SuccessResponse.encode(value).finish()), responseSerialize: (value: SuccessResponse): Buffer => Buffer.from(SuccessResponse.encode(value).finish()),
responseDeserialize: (value: Buffer): SuccessResponse => SuccessResponse.decode(value), responseDeserialize: (value: Buffer): SuccessResponse => SuccessResponse.decode(value),
}, },
/** NEW */
getAllRegularizations: {
path: "/ams.RegularizationService/GetAllRegularizations" as const,
requestStream: false as const,
responseStream: false as const,
requestSerialize: (value: Empty): Buffer => Buffer.from(Empty.encode(value).finish()),
requestDeserialize: (value: Buffer): Empty => Empty.decode(value),
responseSerialize: (value: AllRegularizationsResponse): Buffer =>
Buffer.from(AllRegularizationsResponse.encode(value).finish()),
responseDeserialize: (value: Buffer): AllRegularizationsResponse => AllRegularizationsResponse.decode(value),
},
} as const; } as const;
export interface RegularizationServiceServer extends UntypedServiceImplementation { export interface RegularizationServiceServer extends UntypedServiceImplementation {
createRegularizationRequest: handleUnaryCall<CreateRegRequest, SuccessResponse>; createRegularizationRequest: handleUnaryCall<CreateRegRequest, SuccessResponse>;
getPendingRegularizations: handleUnaryCall<PendingRegRequest, PendingRegResponse>; getPendingRegularizations: handleUnaryCall<PendingRegRequest, PendingRegResponse>;
reviewRegularizationRequest: handleUnaryCall<ReviewRegRequest, SuccessResponse>; reviewRegularizationRequest: handleUnaryCall<ReviewRegRequest, SuccessResponse>;
/** NEW */
getAllRegularizations: handleUnaryCall<Empty, AllRegularizationsResponse>;
} }
export interface RegularizationServiceClient extends Client { export interface RegularizationServiceClient extends Client {
@ -4899,22 +4606,6 @@ export interface RegularizationServiceClient extends Client {
options: Partial<CallOptions>, options: Partial<CallOptions>,
callback: (error: ServiceError | null, response: SuccessResponse) => void, callback: (error: ServiceError | null, response: SuccessResponse) => void,
): ClientUnaryCall; ): ClientUnaryCall;
/** NEW */
getAllRegularizations(
request: Empty,
callback: (error: ServiceError | null, response: AllRegularizationsResponse) => void,
): ClientUnaryCall;
getAllRegularizations(
request: Empty,
metadata: Metadata,
callback: (error: ServiceError | null, response: AllRegularizationsResponse) => void,
): ClientUnaryCall;
getAllRegularizations(
request: Empty,
metadata: Metadata,
options: Partial<CallOptions>,
callback: (error: ServiceError | null, response: AllRegularizationsResponse) => void,
): ClientUnaryCall;
} }
export const RegularizationServiceClient = makeGenericClientConstructor( export const RegularizationServiceClient = makeGenericClientConstructor(
@ -4971,14 +4662,6 @@ export const DashboardServiceClient = makeGenericClientConstructor(
serviceName: string; serviceName: string;
}; };
function bytesFromBase64(b64: string): Uint8Array {
return Uint8Array.from(globalThis.Buffer.from(b64, "base64"));
}
function base64FromBytes(arr: Uint8Array): string {
return globalThis.Buffer.from(arr).toString("base64");
}
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
export type DeepPartial<T> = T extends Builtin ? T export type DeepPartial<T> = T extends Builtin ? T

View File

@ -307,8 +307,6 @@ export interface RosterEmployee {
departmentName: string; departmentName: string;
departmentId: number; departmentId: number;
jobTitle: string; jobTitle: string;
managerName: string;
managerId: string;
} }
export interface RosterResponse { export interface RosterResponse {
@ -4774,8 +4772,6 @@ function createBaseRosterEmployee(): RosterEmployee {
departmentName: "", departmentName: "",
departmentId: 0, departmentId: 0,
jobTitle: "", jobTitle: "",
managerName: "",
managerId: "",
}; };
} }
@ -4814,12 +4810,6 @@ export const RosterEmployee: MessageFns<RosterEmployee> = {
if (message.jobTitle !== "") { if (message.jobTitle !== "") {
writer.uint32(90).string(message.jobTitle); writer.uint32(90).string(message.jobTitle);
} }
if (message.managerName !== "") {
writer.uint32(98).string(message.managerName);
}
if (message.managerId !== "") {
writer.uint32(106).string(message.managerId);
}
return writer; return writer;
}, },
@ -4918,22 +4908,6 @@ export const RosterEmployee: MessageFns<RosterEmployee> = {
message.jobTitle = reader.string(); message.jobTitle = reader.string();
continue; continue;
} }
case 12: {
if (tag !== 98) {
break;
}
message.managerName = reader.string();
continue;
}
case 13: {
if (tag !== 106) {
break;
}
message.managerId = reader.string();
continue;
}
} }
if ((tag & 7) === 4 || tag === 0) { if ((tag & 7) === 4 || tag === 0) {
break; break;
@ -5000,16 +4974,6 @@ export const RosterEmployee: MessageFns<RosterEmployee> = {
: isSet(object.job_title) : isSet(object.job_title)
? globalThis.String(object.job_title) ? globalThis.String(object.job_title)
: "", : "",
managerName: isSet(object.managerName)
? globalThis.String(object.managerName)
: isSet(object.manager_name)
? globalThis.String(object.manager_name)
: "",
managerId: isSet(object.managerId)
? globalThis.String(object.managerId)
: isSet(object.manager_id)
? globalThis.String(object.manager_id)
: "",
}; };
}, },
@ -5048,12 +5012,6 @@ export const RosterEmployee: MessageFns<RosterEmployee> = {
if (message.jobTitle !== "") { if (message.jobTitle !== "") {
obj.jobTitle = message.jobTitle; obj.jobTitle = message.jobTitle;
} }
if (message.managerName !== "") {
obj.managerName = message.managerName;
}
if (message.managerId !== "") {
obj.managerId = message.managerId;
}
return obj; return obj;
}, },
@ -5073,8 +5031,6 @@ export const RosterEmployee: MessageFns<RosterEmployee> = {
message.departmentName = object.departmentName ?? ""; message.departmentName = object.departmentName ?? "";
message.departmentId = object.departmentId ?? 0; message.departmentId = object.departmentId ?? 0;
message.jobTitle = object.jobTitle ?? ""; message.jobTitle = object.jobTitle ?? "";
message.managerName = object.managerName ?? "";
message.managerId = object.managerId ?? "";
return message; return message;
}, },
}; };

View File

@ -174,8 +174,6 @@ export interface LeaveApplicationPayload {
dateTo: string; dateTo: string;
isHalfDay: boolean; isHalfDay: boolean;
reason: string; reason: string;
/** NEW */
applicantRole: string;
} }
export interface ApplyLeaveResponse { export interface ApplyLeaveResponse {
@ -195,11 +193,8 @@ export interface ApplyLeaveResponse {
export interface ManagerActionRequest { export interface ManagerActionRequest {
managerId: number; managerId: number;
applicationId: number; applicationId: number;
reason?: /** For rejection */
| string reason?: string | undefined;
| undefined;
/** NEW */
userRole: string;
} }
export interface PendingLeaveData { export interface PendingLeaveData {
@ -251,31 +246,6 @@ export interface LedgerReportResponse {
data: LedgerData[]; data: LedgerData[];
} }
export interface EmptyRequest {
}
export interface AllApplicationData {
applicationId: number;
employeeId: number;
leaveType: string;
dateFrom: string;
dateTo: string;
numberOfDays: number;
status: string;
reason: string;
employeeCode: string;
firstName: string;
lastName: string;
departmentName: string;
jobTitle: string;
managerName: string;
}
export interface AllApplicationsResponse {
success: boolean;
data: AllApplicationData[];
}
function createBaseSuccessResponse(): SuccessResponse { function createBaseSuccessResponse(): SuccessResponse {
return { success: false, message: "" }; return { success: false, message: "" };
} }
@ -2309,7 +2279,6 @@ function createBaseLeaveApplicationPayload(): LeaveApplicationPayload {
dateTo: "", dateTo: "",
isHalfDay: false, isHalfDay: false,
reason: "", reason: "",
applicantRole: "",
}; };
} }
@ -2339,9 +2308,6 @@ export const LeaveApplicationPayload: MessageFns<LeaveApplicationPayload> = {
if (message.reason !== "") { if (message.reason !== "") {
writer.uint32(66).string(message.reason); writer.uint32(66).string(message.reason);
} }
if (message.applicantRole !== "") {
writer.uint32(74).string(message.applicantRole);
}
return writer; return writer;
}, },
@ -2416,14 +2382,6 @@ export const LeaveApplicationPayload: MessageFns<LeaveApplicationPayload> = {
message.reason = reader.string(); message.reason = reader.string();
continue; continue;
} }
case 9: {
if (tag !== 74) {
break;
}
message.applicantRole = reader.string();
continue;
}
} }
if ((tag & 7) === 4 || tag === 0) { if ((tag & 7) === 4 || tag === 0) {
break; break;
@ -2471,11 +2429,6 @@ export const LeaveApplicationPayload: MessageFns<LeaveApplicationPayload> = {
? globalThis.Boolean(object.is_half_day) ? globalThis.Boolean(object.is_half_day)
: false, : false,
reason: isSet(object.reason) ? globalThis.String(object.reason) : "", reason: isSet(object.reason) ? globalThis.String(object.reason) : "",
applicantRole: isSet(object.applicantRole)
? globalThis.String(object.applicantRole)
: isSet(object.applicant_role)
? globalThis.String(object.applicant_role)
: "",
}; };
}, },
@ -2505,9 +2458,6 @@ export const LeaveApplicationPayload: MessageFns<LeaveApplicationPayload> = {
if (message.reason !== "") { if (message.reason !== "") {
obj.reason = message.reason; obj.reason = message.reason;
} }
if (message.applicantRole !== "") {
obj.applicantRole = message.applicantRole;
}
return obj; return obj;
}, },
@ -2524,7 +2474,6 @@ export const LeaveApplicationPayload: MessageFns<LeaveApplicationPayload> = {
message.dateTo = object.dateTo ?? ""; message.dateTo = object.dateTo ?? "";
message.isHalfDay = object.isHalfDay ?? false; message.isHalfDay = object.isHalfDay ?? false;
message.reason = object.reason ?? ""; message.reason = object.reason ?? "";
message.applicantRole = object.applicantRole ?? "";
return message; return message;
}, },
}; };
@ -2698,7 +2647,7 @@ export const ApplyLeaveResponse: MessageFns<ApplyLeaveResponse> = {
}; };
function createBaseManagerActionRequest(): ManagerActionRequest { function createBaseManagerActionRequest(): ManagerActionRequest {
return { managerId: 0, applicationId: 0, reason: undefined, userRole: "" }; return { managerId: 0, applicationId: 0, reason: undefined };
} }
export const ManagerActionRequest: MessageFns<ManagerActionRequest> = { export const ManagerActionRequest: MessageFns<ManagerActionRequest> = {
@ -2712,9 +2661,6 @@ export const ManagerActionRequest: MessageFns<ManagerActionRequest> = {
if (message.reason !== undefined) { if (message.reason !== undefined) {
writer.uint32(26).string(message.reason); writer.uint32(26).string(message.reason);
} }
if (message.userRole !== "") {
writer.uint32(34).string(message.userRole);
}
return writer; return writer;
}, },
@ -2749,14 +2695,6 @@ export const ManagerActionRequest: MessageFns<ManagerActionRequest> = {
message.reason = reader.string(); message.reason = reader.string();
continue; continue;
} }
case 4: {
if (tag !== 34) {
break;
}
message.userRole = reader.string();
continue;
}
} }
if ((tag & 7) === 4 || tag === 0) { if ((tag & 7) === 4 || tag === 0) {
break; break;
@ -2779,11 +2717,6 @@ export const ManagerActionRequest: MessageFns<ManagerActionRequest> = {
? globalThis.Number(object.application_id) ? globalThis.Number(object.application_id)
: 0, : 0,
reason: isSet(object.reason) ? globalThis.String(object.reason) : undefined, reason: isSet(object.reason) ? globalThis.String(object.reason) : undefined,
userRole: isSet(object.userRole)
? globalThis.String(object.userRole)
: isSet(object.user_role)
? globalThis.String(object.user_role)
: "",
}; };
}, },
@ -2798,9 +2731,6 @@ export const ManagerActionRequest: MessageFns<ManagerActionRequest> = {
if (message.reason !== undefined) { if (message.reason !== undefined) {
obj.reason = message.reason; obj.reason = message.reason;
} }
if (message.userRole !== "") {
obj.userRole = message.userRole;
}
return obj; return obj;
}, },
@ -2812,7 +2742,6 @@ export const ManagerActionRequest: MessageFns<ManagerActionRequest> = {
message.managerId = object.managerId ?? 0; message.managerId = object.managerId ?? 0;
message.applicationId = object.applicationId ?? 0; message.applicationId = object.applicationId ?? 0;
message.reason = object.reason ?? undefined; message.reason = object.reason ?? undefined;
message.userRole = object.userRole ?? "";
return message; return message;
}, },
}; };
@ -3607,456 +3536,6 @@ export const LedgerReportResponse: MessageFns<LedgerReportResponse> = {
}, },
}; };
function createBaseEmptyRequest(): EmptyRequest {
return {};
}
export const EmptyRequest: MessageFns<EmptyRequest> = {
encode(_: EmptyRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): EmptyRequest {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
const end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseEmptyRequest();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skip(tag & 7);
}
return message;
},
fromJSON(_: any): EmptyRequest {
return {};
},
toJSON(_: EmptyRequest): unknown {
const obj: any = {};
return obj;
},
create<I extends Exact<DeepPartial<EmptyRequest>, I>>(base?: I): EmptyRequest {
return EmptyRequest.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<EmptyRequest>, I>>(_: I): EmptyRequest {
const message = createBaseEmptyRequest();
return message;
},
};
function createBaseAllApplicationData(): AllApplicationData {
return {
applicationId: 0,
employeeId: 0,
leaveType: "",
dateFrom: "",
dateTo: "",
numberOfDays: 0,
status: "",
reason: "",
employeeCode: "",
firstName: "",
lastName: "",
departmentName: "",
jobTitle: "",
managerName: "",
};
}
export const AllApplicationData: MessageFns<AllApplicationData> = {
encode(message: AllApplicationData, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.applicationId !== 0) {
writer.uint32(8).int32(message.applicationId);
}
if (message.employeeId !== 0) {
writer.uint32(16).int32(message.employeeId);
}
if (message.leaveType !== "") {
writer.uint32(26).string(message.leaveType);
}
if (message.dateFrom !== "") {
writer.uint32(34).string(message.dateFrom);
}
if (message.dateTo !== "") {
writer.uint32(42).string(message.dateTo);
}
if (message.numberOfDays !== 0) {
writer.uint32(49).double(message.numberOfDays);
}
if (message.status !== "") {
writer.uint32(58).string(message.status);
}
if (message.reason !== "") {
writer.uint32(66).string(message.reason);
}
if (message.employeeCode !== "") {
writer.uint32(74).string(message.employeeCode);
}
if (message.firstName !== "") {
writer.uint32(82).string(message.firstName);
}
if (message.lastName !== "") {
writer.uint32(90).string(message.lastName);
}
if (message.departmentName !== "") {
writer.uint32(98).string(message.departmentName);
}
if (message.jobTitle !== "") {
writer.uint32(106).string(message.jobTitle);
}
if (message.managerName !== "") {
writer.uint32(114).string(message.managerName);
}
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): AllApplicationData {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
const end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseAllApplicationData();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
if (tag !== 8) {
break;
}
message.applicationId = reader.int32();
continue;
}
case 2: {
if (tag !== 16) {
break;
}
message.employeeId = reader.int32();
continue;
}
case 3: {
if (tag !== 26) {
break;
}
message.leaveType = reader.string();
continue;
}
case 4: {
if (tag !== 34) {
break;
}
message.dateFrom = reader.string();
continue;
}
case 5: {
if (tag !== 42) {
break;
}
message.dateTo = reader.string();
continue;
}
case 6: {
if (tag !== 49) {
break;
}
message.numberOfDays = reader.double();
continue;
}
case 7: {
if (tag !== 58) {
break;
}
message.status = reader.string();
continue;
}
case 8: {
if (tag !== 66) {
break;
}
message.reason = reader.string();
continue;
}
case 9: {
if (tag !== 74) {
break;
}
message.employeeCode = reader.string();
continue;
}
case 10: {
if (tag !== 82) {
break;
}
message.firstName = reader.string();
continue;
}
case 11: {
if (tag !== 90) {
break;
}
message.lastName = reader.string();
continue;
}
case 12: {
if (tag !== 98) {
break;
}
message.departmentName = reader.string();
continue;
}
case 13: {
if (tag !== 106) {
break;
}
message.jobTitle = reader.string();
continue;
}
case 14: {
if (tag !== 114) {
break;
}
message.managerName = reader.string();
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skip(tag & 7);
}
return message;
},
fromJSON(object: any): AllApplicationData {
return {
applicationId: isSet(object.applicationId)
? globalThis.Number(object.applicationId)
: isSet(object.application_id)
? globalThis.Number(object.application_id)
: 0,
employeeId: isSet(object.employeeId)
? globalThis.Number(object.employeeId)
: isSet(object.employee_id)
? globalThis.Number(object.employee_id)
: 0,
leaveType: isSet(object.leaveType)
? globalThis.String(object.leaveType)
: isSet(object.leave_type)
? globalThis.String(object.leave_type)
: "",
dateFrom: isSet(object.dateFrom)
? globalThis.String(object.dateFrom)
: isSet(object.date_from)
? globalThis.String(object.date_from)
: "",
dateTo: isSet(object.dateTo)
? globalThis.String(object.dateTo)
: isSet(object.date_to)
? globalThis.String(object.date_to)
: "",
numberOfDays: isSet(object.numberOfDays)
? globalThis.Number(object.numberOfDays)
: isSet(object.number_of_days)
? globalThis.Number(object.number_of_days)
: 0,
status: isSet(object.status) ? globalThis.String(object.status) : "",
reason: isSet(object.reason) ? globalThis.String(object.reason) : "",
employeeCode: isSet(object.employeeCode)
? globalThis.String(object.employeeCode)
: isSet(object.employee_code)
? globalThis.String(object.employee_code)
: "",
firstName: isSet(object.firstName)
? globalThis.String(object.firstName)
: isSet(object.first_name)
? globalThis.String(object.first_name)
: "",
lastName: isSet(object.lastName)
? globalThis.String(object.lastName)
: isSet(object.last_name)
? globalThis.String(object.last_name)
: "",
departmentName: isSet(object.departmentName)
? globalThis.String(object.departmentName)
: isSet(object.department_name)
? globalThis.String(object.department_name)
: "",
jobTitle: isSet(object.jobTitle)
? globalThis.String(object.jobTitle)
: isSet(object.job_title)
? globalThis.String(object.job_title)
: "",
managerName: isSet(object.managerName)
? globalThis.String(object.managerName)
: isSet(object.manager_name)
? globalThis.String(object.manager_name)
: "",
};
},
toJSON(message: AllApplicationData): unknown {
const obj: any = {};
if (message.applicationId !== 0) {
obj.applicationId = Math.round(message.applicationId);
}
if (message.employeeId !== 0) {
obj.employeeId = Math.round(message.employeeId);
}
if (message.leaveType !== "") {
obj.leaveType = message.leaveType;
}
if (message.dateFrom !== "") {
obj.dateFrom = message.dateFrom;
}
if (message.dateTo !== "") {
obj.dateTo = message.dateTo;
}
if (message.numberOfDays !== 0) {
obj.numberOfDays = message.numberOfDays;
}
if (message.status !== "") {
obj.status = message.status;
}
if (message.reason !== "") {
obj.reason = message.reason;
}
if (message.employeeCode !== "") {
obj.employeeCode = message.employeeCode;
}
if (message.firstName !== "") {
obj.firstName = message.firstName;
}
if (message.lastName !== "") {
obj.lastName = message.lastName;
}
if (message.departmentName !== "") {
obj.departmentName = message.departmentName;
}
if (message.jobTitle !== "") {
obj.jobTitle = message.jobTitle;
}
if (message.managerName !== "") {
obj.managerName = message.managerName;
}
return obj;
},
create<I extends Exact<DeepPartial<AllApplicationData>, I>>(base?: I): AllApplicationData {
return AllApplicationData.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<AllApplicationData>, I>>(object: I): AllApplicationData {
const message = createBaseAllApplicationData();
message.applicationId = object.applicationId ?? 0;
message.employeeId = object.employeeId ?? 0;
message.leaveType = object.leaveType ?? "";
message.dateFrom = object.dateFrom ?? "";
message.dateTo = object.dateTo ?? "";
message.numberOfDays = object.numberOfDays ?? 0;
message.status = object.status ?? "";
message.reason = object.reason ?? "";
message.employeeCode = object.employeeCode ?? "";
message.firstName = object.firstName ?? "";
message.lastName = object.lastName ?? "";
message.departmentName = object.departmentName ?? "";
message.jobTitle = object.jobTitle ?? "";
message.managerName = object.managerName ?? "";
return message;
},
};
function createBaseAllApplicationsResponse(): AllApplicationsResponse {
return { success: false, data: [] };
}
export const AllApplicationsResponse: MessageFns<AllApplicationsResponse> = {
encode(message: AllApplicationsResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.success !== false) {
writer.uint32(8).bool(message.success);
}
for (const v of message.data) {
AllApplicationData.encode(v!, writer.uint32(18).fork()).join();
}
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): AllApplicationsResponse {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
const end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseAllApplicationsResponse();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
if (tag !== 8) {
break;
}
message.success = reader.bool();
continue;
}
case 2: {
if (tag !== 18) {
break;
}
message.data.push(AllApplicationData.decode(reader, reader.uint32()));
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skip(tag & 7);
}
return message;
},
fromJSON(object: any): AllApplicationsResponse {
return {
success: isSet(object.success) ? globalThis.Boolean(object.success) : false,
data: globalThis.Array.isArray(object?.data) ? object.data.map((e: any) => AllApplicationData.fromJSON(e)) : [],
};
},
toJSON(message: AllApplicationsResponse): unknown {
const obj: any = {};
if (message.success !== false) {
obj.success = message.success;
}
if (message.data?.length) {
obj.data = message.data.map((e) => AllApplicationData.toJSON(e));
}
return obj;
},
create<I extends Exact<DeepPartial<AllApplicationsResponse>, I>>(base?: I): AllApplicationsResponse {
return AllApplicationsResponse.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<AllApplicationsResponse>, I>>(object: I): AllApplicationsResponse {
const message = createBaseAllApplicationsResponse();
message.success = object.success ?? false;
message.data = object.data?.map((e) => AllApplicationData.fromPartial(e)) || [];
return message;
},
};
/** /**
* ========================================== * ==========================================
* SERVICES * SERVICES
@ -4481,56 +3960,6 @@ export const ReportsServiceClient = makeGenericClientConstructor(
serviceName: string; serviceName: string;
}; };
/**
* ==========================================
* HR MANAGER SERVICE (NEW)
* ==========================================
*/
export type HRManagerServiceService = typeof HRManagerServiceService;
export const HRManagerServiceService = {
getAllLeaveApplications: {
path: "/lms.HRManagerService/GetAllLeaveApplications" as const,
requestStream: false as const,
responseStream: false as const,
requestSerialize: (value: EmptyRequest): Buffer => Buffer.from(EmptyRequest.encode(value).finish()),
requestDeserialize: (value: Buffer): EmptyRequest => EmptyRequest.decode(value),
responseSerialize: (value: AllApplicationsResponse): Buffer =>
Buffer.from(AllApplicationsResponse.encode(value).finish()),
responseDeserialize: (value: Buffer): AllApplicationsResponse => AllApplicationsResponse.decode(value),
},
} as const;
export interface HRManagerServiceServer extends UntypedServiceImplementation {
getAllLeaveApplications: handleUnaryCall<EmptyRequest, AllApplicationsResponse>;
}
export interface HRManagerServiceClient extends Client {
getAllLeaveApplications(
request: EmptyRequest,
callback: (error: ServiceError | null, response: AllApplicationsResponse) => void,
): ClientUnaryCall;
getAllLeaveApplications(
request: EmptyRequest,
metadata: Metadata,
callback: (error: ServiceError | null, response: AllApplicationsResponse) => void,
): ClientUnaryCall;
getAllLeaveApplications(
request: EmptyRequest,
metadata: Metadata,
options: Partial<CallOptions>,
callback: (error: ServiceError | null, response: AllApplicationsResponse) => void,
): ClientUnaryCall;
}
export const HRManagerServiceClient = makeGenericClientConstructor(
HRManagerServiceService,
"lms.HRManagerService",
) as unknown as {
new (address: string, credentials: ChannelCredentials, options?: Partial<ClientOptions>): HRManagerServiceClient;
service: typeof HRManagerServiceService;
serviceName: string;
};
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
export type DeepPartial<T> = T extends Builtin ? T export type DeepPartial<T> = T extends Builtin ? T

View File

@ -53,7 +53,7 @@ CREATE TABLE attendance_regularizations (
employee_id INT NOT NULL, -- Logical Reference (The Applicant) employee_id INT NOT NULL, -- Logical Reference (The Applicant)
branch_id INT NOT NULL, branch_id INT NOT NULL,
attendance_id INT NULL, -- Internal FK to processed ledger row attendance_id INT NULL, -- Internal FK to processed ledger row
regularization_type ENUM('MISPUNCH', 'HALF_DAY', 'WFH_REQUEST', 'ON_DUTY') NOT NULL; regularization_type ENUM('MISPUNCH', 'WFH_REQUEST', 'ON_DUTY') NOT NULL,
target_date DATE NOT NULL, target_date DATE NOT NULL,
requested_check_in TIMESTAMP NULL, requested_check_in TIMESTAMP NULL,
requested_check_out TIMESTAMP NULL, requested_check_out TIMESTAMP NULL,

View File

@ -58,8 +58,6 @@ CREATE TABLE leave_applications (
status ENUM('DRAFT', 'PENDING', 'PENDING_LOP', 'APPROVED', 'APPROVED_LOP', 'REJECTED', 'CANCELLED') DEFAULT 'PENDING', status ENUM('DRAFT', 'PENDING', 'PENDING_LOP', 'APPROVED', 'APPROVED_LOP', 'REJECTED', 'CANCELLED') DEFAULT 'PENDING',
manager_approved_by INT NULL, -- Logical Reference to EMS employees table (Approver) manager_approved_by INT NULL, -- Logical Reference to EMS employees table (Approver)
hr_approved_by INT NULL, hr_approved_by INT NULL,
approver_role VARCHAR(20) NOT NULL DEFAULT 'MANAGER',
applicant_role VARCHAR(20) NOT NULL DEFAULT 'EMPLOYEE',
FOREIGN KEY (leave_type_id) REFERENCES leave_types(leave_type_id) ON DELETE RESTRICT, FOREIGN KEY (leave_type_id) REFERENCES leave_types(leave_type_id) ON DELETE RESTRICT,
INDEX idx_lms_status_lookup (employee_id, status) INDEX idx_lms_status_lookup (employee_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@ -12,15 +12,12 @@ service AttendanceService {
rpc GetDailyReport(DailyReportRequest) returns (DailyReportResponse); rpc GetDailyReport(DailyReportRequest) returns (DailyReportResponse);
rpc GetAdminRangeReport(RangeReportRequest) returns (AdminRangeReportResponse); rpc GetAdminRangeReport(RangeReportRequest) returns (AdminRangeReportResponse);
rpc GetSingleEmployeeRangeReport(SingleRangeReportRequest) returns (SingleRangeReportResponse); rpc GetSingleEmployeeRangeReport(SingleRangeReportRequest) returns (SingleRangeReportResponse);
rpc ExportDailyReport(DailyReportRequest) returns (ExportReportResponse);
rpc ExportAdminRangeReport(RangeReportRequest) returns (ExportReportResponse);
} }
service RegularizationService { service RegularizationService {
rpc CreateRegularizationRequest(CreateRegRequest) returns (SuccessResponse); rpc CreateRegularizationRequest(CreateRegRequest) returns (SuccessResponse);
rpc GetPendingRegularizations(PendingRegRequest) returns (PendingRegResponse); rpc GetPendingRegularizations(PendingRegRequest) returns (PendingRegResponse);
rpc ReviewRegularizationRequest(ReviewRegRequest) returns (SuccessResponse); rpc ReviewRegularizationRequest(ReviewRegRequest) returns (SuccessResponse);
rpc GetAllRegularizations(Empty) returns (AllRegularizationsResponse); // NEW
} }
service DashboardService { service DashboardService {
@ -100,7 +97,6 @@ message DailyReportRequest {
optional string designation = 5; // Job Title optional string designation = 5; // Job Title
optional string employee_name = 6; optional string employee_name = 6;
optional string employee_code = 7; optional string employee_code = 7;
string file_type = 8;
} }
message DailySummary { message DailySummary {
@ -140,11 +136,10 @@ message RangeReportRequest {
string to_date = 2; string to_date = 2;
optional int32 branch_id = 3; optional int32 branch_id = 3;
optional int32 department_id = 4; optional int32 department_id = 4;
optional int32 company_id = 5; optional int32 company_id = 5; // NEW
optional string designation = 6; optional string designation = 6; // NEW
optional string employee_name = 7; optional string employee_name = 7; // NEW
optional string employee_code = 8; optional string employee_code = 8; // NEW
string file_type = 9; // <--- ADD THIS LINE
} }
message RangeMetrics { message RangeMetrics {
@ -255,7 +250,6 @@ message PendingRegData {
string first_name = 11; string first_name = 11;
string last_name = 12; string last_name = 12;
string branch_name = 13; string branch_name = 13;
string reviewer_name = 14;
} }
message PendingRegResponse { message PendingRegResponse {
@ -270,18 +264,3 @@ message ReviewRegRequest {
string user_role = 4; string user_role = 4;
repeated int32 managed_branch_ids = 5; repeated int32 managed_branch_ids = 5;
} }
message AllRegularizationsResponse {
bool success = 1;
repeated PendingRegData data = 2;
}
// Add to common messages
message ExportReportResponse {
bytes file_content = 1;
string file_name = 2;
}

View File

@ -320,8 +320,6 @@ message RosterEmployee {
string department_name = 9; string department_name = 9;
int32 department_id = 10; int32 department_id = 10;
string job_title = 11; string job_title = 11;
string manager_name = 12;
string manager_id = 13;
} }
message RosterResponse { message RosterResponse {

View File

@ -173,7 +173,6 @@ message LeaveApplicationPayload {
string date_to = 6; string date_to = 6;
bool is_half_day = 7; bool is_half_day = 7;
string reason = 8; string reason = 8;
string applicant_role = 9; // NEW
} }
message ApplyLeaveResponse { message ApplyLeaveResponse {
@ -191,8 +190,7 @@ message ApplyLeaveResponse {
message ManagerActionRequest { message ManagerActionRequest {
int32 manager_id = 1; int32 manager_id = 1;
int32 application_id = 2; int32 application_id = 2;
optional string reason = 3; optional string reason = 3; // For rejection
string user_role = 4; // NEW
} }
message PendingLeaveData { message PendingLeaveData {
@ -241,36 +239,3 @@ message LedgerReportResponse {
int32 report_year = 3; int32 report_year = 3;
repeated LedgerData data = 4; repeated LedgerData data = 4;
} }
// ==========================================
// HR MANAGER SERVICE (NEW)
// ==========================================
service HRManagerService {
rpc GetAllLeaveApplications(EmptyRequest) returns (AllApplicationsResponse);
}
message EmptyRequest {}
message AllApplicationData {
int32 application_id = 1;
int32 employee_id = 2;
string leave_type = 3;
string date_from = 4;
string date_to = 5;
double number_of_days = 6;
string status = 7;
string reason = 8;
string employee_code = 9;
string first_name = 10;
string last_name = 11;
string department_name = 12;
string job_title = 13;
string manager_name = 14;
}
message AllApplicationsResponse {
bool success = 1;
repeated AllApplicationData data = 2;
}

View File

@ -178,25 +178,5 @@ export const AttendanceServiceImplementation = {
console.error("gRPC getSingleEmployeeRangeReport Error:", error); console.error("gRPC getSingleEmployeeRangeReport Error:", error);
return { success: false, employee: undefined, fromDate: "", toDate: "", totalDays: 0, history: [] }; return { success: false, employee: undefined, fromDate: "", toDate: "", totalDays: 0, history: [] };
} }
},
// Add these to AttendanceServiceImplementation
async exportDailyReport(req: any) {
try {
const result = await AttendanceService.exportDailyReport(req);
return { success: true, fileContent: result.file_content, fileName: result.file_name };
} catch (error: any) {
console.error("gRPC exportDailyReport Error:", error);
return { success: false, fileName: "" };
}
},
async exportAdminRangeReport(req: any) {
try {
const result = await AttendanceService.exportAdminRangeReport(req);
return { success: true, fileContent: result.file_content, fileName: result.file_name };
} catch (error: any) {
console.error("gRPC exportAdminRangeReport Error:", error);
return { success: false, fileName: "" };
}
} }
}; };

View File

@ -69,30 +69,5 @@ export const RegularizationServiceImplementation = {
console.error("gRPC reviewRegularizationRequest Error:", error); console.error("gRPC reviewRegularizationRequest Error:", error);
return { success: false, message: error.message }; 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: reg.target_date ? new Date(reg.target_date).toISOString() : "",
regularizationType: reg.regularization_type || "",
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,
employeeCode: reg.employee_code || "",
firstName: reg.first_name || "",
lastName: reg.last_name || "",
branchName: reg.branch_name || "N/A",
reviewerName: reg.reviewer_name || "N/A"
}));
return { success: true, data: mappedData };
} catch (e: any) {
console.error("gRPC getAllRegularizations Error:", e);
return { success: false, data: [] };
}
} }
}; };

View File

@ -79,7 +79,7 @@ export const findAttendanceByEmployeeIds = async (employeeIds: number[], fromDat
if (employeeIds.length === 0) return []; if (employeeIds.length === 0) return [];
const placeholders = employeeIds.map(() => "?").join(","); const placeholders = employeeIds.map(() => "?").join(",");
// console.log("DB Query Params:", { fromDate, toDate, employeeIds }); console.log("DB Query Params:", { fromDate, toDate, employeeIds });
const [rows]: any = await pool.execute( const [rows]: any = await pool.execute(
`SELECT `SELECT

View File

@ -40,13 +40,3 @@ export const updateRegularizationStatus = async (id: number, status: string, rev
[status, reviewerId, id] [status, reviewerId, id]
); );
}; };
export const findAllRegularizations = async () => {
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
ORDER BY target_date DESC`
);
return rows;
};

View File

@ -2,337 +2,15 @@
import pool from "../db.ts"; import pool from "../db.ts";
import { getEmployeeRoster } from "core/internal-client.ts"; import { getEmployeeRoster } from "core/internal-client.ts";
import * as AttendanceRepo from "../repositories/attendance.repository.ts"; import * as AttendanceRepo from "../repositories/attendance.repository.ts";
import ExcelJS from "npm:exceljs@4.4.0";
import PDFDocument from "npm:pdfkit@0.13.0";
// Helper function to draw tables in PDF
function drawPdfTable(doc: any, headers: string[], rows: any[], columnWidths: number[]) {
const startX = doc.page.margins.left;
let y = doc.y;
const cellPadding = 5;
// 1. Draw Headers
doc.font('Helvetica-Bold').fontSize(9);
let headerHeight = 20;
headers.forEach((header, i) => {
const textHeight = doc.heightOfString(header, { width: columnWidths[i] - cellPadding * 2 });
headerHeight = Math.max(headerHeight, textHeight + cellPadding * 2);
});
let x = startX;
headers.forEach((header, i) => {
// Dark Green background, White text, Black border (Matching Excel)
doc.fill('#063B00').rect(x, y, columnWidths[i], headerHeight).fill();
doc.fillColor('#FFFFFF').text(header, x + cellPadding, y + cellPadding, { width: columnWidths[i] - cellPadding * 2, align: 'center' });
doc.lineWidth(0.5).strokeColor('#000000').rect(x, y, columnWidths[i], headerHeight).stroke();
x += columnWidths[i];
});
y += headerHeight;
// 2. Draw Rows
doc.font('Helvetica').fontSize(8);
rows.forEach((row: any) => {
let rowHeight = 15;
row.forEach((cell: any, i: number) => {
const textHeight = doc.heightOfString(String(cell ?? '-'), { width: columnWidths[i] - cellPadding * 2 });
rowHeight = Math.max(rowHeight, textHeight + cellPadding * 2);
});
if (y + rowHeight > doc.page.height - doc.page.margins.bottom) {
doc.addPage();
y = doc.page.margins.top;
}
x = startX;
row.forEach((cell: any, i: number) => {
let textColor = '#000000'; // Default black
let bgColor = null;
// Apply background and text colors for the Status column (always the last column)
if (i === headers.length - 1) {
const statusStr = String(cell ?? '').toUpperCase();
if (statusStr === 'FULL_DAY') {
bgColor = '#C6EFCE'; textColor = '#006100';
} else if (statusStr === 'ABSENT') {
bgColor = '#FFC7CE'; textColor = '#9C0006';
} else if (statusStr === 'HALF_DAY') {
bgColor = '#FFEB9C'; textColor = '#9C6500';
} else if (statusStr === 'MISPUNCH') {
bgColor = '#B1A0C7'; textColor = '#4B2E83';
} else if (statusStr === 'LATE') {
bgColor = '#FFC000'; textColor = '#000000';
}
}
// Draw background color if applicable
if (bgColor) {
doc.fillColor(bgColor).rect(x, y, columnWidths[i], rowHeight).fill();
}
// Draw text
doc.fillColor(textColor).text(String(cell ?? '-'), x + cellPadding, y + cellPadding, { width: columnWidths[i] - cellPadding * 2 });
// Draw black border for rows
doc.lineWidth(0.5).strokeColor('#000000').rect(x, y, columnWidths[i], rowHeight).stroke();
x += columnWidths[i];
});
y += rowHeight;
});
doc.y = y + 10;
}
// Helper function specifically for Excel Daily Report generation
async function generateExcelDailyReport(roster: any[], formattedDate: string, dayName: string, generatedDate: string, summary: any): Promise<Uint8Array> {
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Daily Report');
// Merge cells across 12 columns (A to L)
worksheet.mergeCells('A1:L1');
const titleCell = worksheet.getCell('A1');
titleCell.value = 'Report Name : Daily Attendance Report';
titleCell.font = { size: 11, bold: true };
titleCell.alignment = { vertical: 'middle', horizontal: 'left' };
worksheet.mergeCells('A2:L2');
const dateCell = worksheet.getCell('A2');
dateCell.value = `Date : ${formattedDate} (${dayName})`;
dateCell.font = { size: 11, bold: true };
dateCell.alignment = { vertical: 'middle', horizontal: 'left' };
worksheet.mergeCells('A3:L3');
const genDateCell = worksheet.getCell('A3');
genDateCell.value = `Generated Date : ${generatedDate}`;
genDateCell.font = { size: 11, bold: true };
genDateCell.alignment = { vertical: 'middle', horizontal: 'left' };
// NEW: Summary Metrics Row
worksheet.mergeCells('A4:L4');
const summaryCell = worksheet.getCell('A4');
summaryCell.value = `Total employees: ${summary.total_active_workforce || 0} | Present: ${summary.present || 0} | Late: ${summary.late || 0} | Mispunches: ${summary.mispunches || 0} | Absent: ${summary.absent || 0}`;
summaryCell.font = { size: 11, bold: true, color: { argb: 'FF000000' } }; // Black text
summaryCell.alignment = { vertical: 'middle', horizontal: 'left' };
worksheet.getRow(5).height = 10; // Spacer row
// Row 6: Headers (12 columns)
const headerRow = worksheet.getRow(6);
headerRow.values = ['Date', 'Day', 'Emp Code', 'Name', 'Company', 'Branch', 'Department', 'Job Role', 'Check-In', 'Check-Out', 'Worked Hours', 'Status'];
// Style Header Row (Green Background, White Text)
headerRow.eachCell((cell) => {
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: '063B00' } // Dark Green
};
cell.font = { bold: true, color: { argb: 'FFFFFFFF' } }; // White text
cell.alignment = { vertical: 'middle', horizontal: 'center' };
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
};
});
headerRow.height = 20;
// Add Data Rows starting from Row 7
roster.forEach((r: any, index: number) => {
const rowIndex = 7 + index;
const row = worksheet.getRow(rowIndex);
row.values = [
formattedDate,
dayName,
r.employee_code,
r.full_name,
r.company_name || '-',
r.branch_name || '-',
r.department_name || '-',
r.designation || '-',
r.check_in ? r.check_in.substring(0, 5) : '-',
r.check_out ? r.check_out.substring(0, 5) : '-',
r.worked_hours,
r.status
];
// Add borders to data cells
row.eachCell({ includeEmpty: true }, (cell, colNumber) => {
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
};
cell.alignment = { vertical: 'middle', horizontal: 'left' };
// Apply colors to the Status column (Column 12 / 'L')
if (colNumber === 12) {
const statusStr = (r.status || '').toUpperCase();
if (statusStr === 'FULL_DAY') {
cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFC6EFCE' } }; // Light Green background
cell.font = { color: { argb: 'FF006100' }, bold: true }; // Dark Green text
} else if (statusStr === 'ABSENT') {
cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFC7CE' } }; // Light Red background
cell.font = { color: { argb: 'FF9C0006' }, bold: true }; // Dark Red text
} else if (statusStr === 'HALF_DAY') {
cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFEB9C' } }; // Light Orange/Yellow background
cell.font = { color: { argb: 'FF9C6500' }, bold: true }; // Dark Orange text
} else if (statusStr === 'MISPUNCH') {
cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFB1A0C7' } }; // Light Purple background
cell.font = { color: { argb: 'FF4B2E83' }, bold: true }; // Dark Purple text
} else if (statusStr === 'LATE') {
cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFC000' } }; // Gold background
cell.font = { color: { argb: 'FF000000' }, bold: true }; // Black text
}
}
});
row.commit();
});
// Set 12 Column Widths (Increased Day width to 70 to prevent "Wednesday" text wrapping)
worksheet.columns = [
{ width: 12 }, // Date
{ width: 65 }, // Day
{ width: 12 }, // Emp Code
{ width: 25 }, // Name
{ width: 22 }, // Company
{ width: 18 }, // Branch
{ width: 22 }, // Department
{ width: 25 }, // Job Role
{ width: 12 }, // Check-In
{ width: 12 }, // Check-Out
{ width: 12 }, // Worked Hours
{ width: 12 }, // Status
];
const buffer = await workbook.xlsx.writeBuffer();
return new Uint8Array(buffer);
}
// Helper function specifically for Excel Monthly Report generation (Matrix Layout)
async function generateExcelMonthlyReport(report: any[], dates: string[], formattedFromDate: string, formattedToDate: string, generatedDate: string): Promise<Uint8Array> {
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Monthly Attendance');
const numDates = dates.length;
const totalCols = 1 + numDates;
// Top Level Headings
worksheet.mergeCells(1, 1, 1, totalCols);
const titleCell = worksheet.getCell(1, 1);
titleCell.value = 'Report Name : Monthly Attendance Report';
titleCell.font = { size: 11, bold: true };
titleCell.alignment = { vertical: 'middle', horizontal: 'left' };
worksheet.mergeCells(2, 1, 2, totalCols);
const periodCell = worksheet.getCell(2, 1);
periodCell.value = `Period : ${formattedFromDate} To ${formattedToDate}`;
periodCell.font = { size: 11, bold: true };
periodCell.alignment = { vertical: 'middle', horizontal: 'left' };
worksheet.mergeCells(3, 1, 3, totalCols);
const genDateCell = worksheet.getCell(3, 1);
genDateCell.value = `Generated Date : ${generatedDate}`;
genDateCell.font = { size: 11, bold: true };
genDateCell.alignment = { vertical: 'middle', horizontal: 'left' };
// Column Widths
worksheet.getColumn(1).width = 12;
for (let i = 0; i < numDates; i++) {
worksheet.getColumn(i + 2).width = 10;
}
let currentRow = 5;
report.forEach((emp: any) => {
const history = emp.attendance_history || [];
// --- Employee Summary Header ---
worksheet.mergeCells(currentRow, 1, currentRow, totalCols);
const empCell = worksheet.getCell(currentRow, 1);
empCell.value = `Employee Code: ${emp.employee_code} | Name: ${emp.first_name} ${emp.last_name} | Company: ${emp.company_name || '-'} | Branch: ${emp.branch_name || '-'} | Department: ${emp.department_name || '-'} | Job Role: ${emp.designation || '-'} | Full Days: ${emp.range_metrics?.full_days || 0} | Half Days: ${emp.range_metrics?.half_days || 0} | Mispunches: ${emp.range_metrics?.mispunches || 0} | Late Arrivals: ${emp.range_metrics?.late_arrivals || 0}`;
empCell.font = { bold: true, size: 10 };
empCell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF2F2F2' } };
empCell.alignment = { vertical: 'middle', horizontal: 'left' };
currentRow++;
// --- Table Headers (Field | Date 1 | Date 2 | ...) ---
const headerRow = worksheet.getRow(currentRow);
headerRow.getCell(1).value = 'Field';
for (let i = 0; i < numDates; i++) {
const dateObj = new Date(dates[i] + 'T00:00:00');
const dayStr = dateObj.toLocaleDateString('en-GB', { day: '2-digit', month: 'short' });
const dayName = dateObj.toLocaleDateString('en-US', { weekday: 'short' });
headerRow.getCell(i + 2).value = `${dayStr}\n${dayName}`;
}
headerRow.eachCell((cell) => {
cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: '063B00' } }; // Dark Green
cell.font = { bold: true, color: { argb: 'FFFFFFFF' } }; // White text
cell.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true };
cell.border = { top: {style:'thin', color:{argb:'FF000000'}}, left: {style:'thin', color:{argb:'FF000000'}}, bottom: {style:'thin', color:{argb:'FF000000'}}, right: {style:'thin', color:{argb:'FF000000'}} };
});
headerRow.height = 25;
currentRow++;
// --- Data Rows (Check-In, Check-Out, Hours, Status) ---
const fields = ['Check-In', 'Check-Out', 'Hours', 'Status'];
fields.forEach((field) => {
const row = worksheet.getRow(currentRow);
row.getCell(1).value = field;
row.getCell(1).font = { bold: true };
row.getCell(1).alignment = { horizontal: 'left' };
row.getCell(1).border = { top: {style:'thin', color:{argb:'FF000000'}}, left: {style:'thin', color:{argb:'FF000000'}}, bottom: {style:'thin', color:{argb:'FF000000'}}, right: {style:'thin', color:{argb:'FF000000'}} };
for (let i = 0; i < numDates; i++) {
const log = history.find((h: any) => h.work_date === dates[i]);
const cell = row.getCell(i + 2);
if (log) {
if (field === 'Check-In') cell.value = log.check_in ? log.check_in.substring(0, 5) : '-';
else if (field === 'Check-Out') cell.value = log.check_out ? log.check_out.substring(0, 5) : '-';
else if (field === 'Hours') cell.value = log.worked_hours;
else if (field === 'Status') {
cell.value = log.final_status;
const statusStr = (log.final_status || '').toUpperCase();
if (statusStr === 'FULL_DAY') { cell.fill = { type:'pattern', pattern:'solid', fgColor:{argb:'FFC6EFCE'} }; cell.font = { color:{argb:'FF006100'}, bold:true }; }
else if (statusStr === 'ABSENT') { cell.fill = { type:'pattern', pattern:'solid', fgColor:{argb:'FFFFC7CE'} }; cell.font = { color:{argb:'FF9C0006'}, bold:true }; }
else if (statusStr === 'HALF_DAY') { cell.fill = { type:'pattern', pattern:'solid', fgColor:{argb:'FFFFEB9C'} }; cell.font = { color:{argb:'FF9C6500'}, bold:true }; }
else if (statusStr === 'MISPUNCH') { cell.fill = { type:'pattern', pattern:'solid', fgColor:{argb:'FFB1A0C7'} }; cell.font = { color:{argb:'FF4B2E83'}, bold:true }; }
else if (statusStr === 'LATE') { cell.fill = { type:'pattern', pattern:'solid', fgColor:{argb:'FFFFC000'} }; cell.font = { color:{argb:'FF000000'}, bold:true }; }
}
} else {
cell.value = '-';
}
cell.border = { top: {style:'thin', color:{argb:'FF000000'}}, left: {style:'thin', color:{argb:'FF000000'}}, bottom: {style:'thin', color:{argb:'FF000000'}}, right: {style:'thin', color:{argb:'FF000000'}} };
cell.alignment = { horizontal: 'center' };
}
currentRow++;
});
currentRow++; // empty row between employees
});
const buffer = await workbook.xlsx.writeBuffer();
return new Uint8Array(buffer);
}
// Helper function to apply filters to the roster // Helper function to apply filters to the roster
const applyRosterFilters = (roster: any[], filters: any) => { const applyRosterFilters = (roster: any[], filters: any) => {
let filtered = [...roster]; let filtered = [...roster];
if (filters.companyId) filtered = filtered.filter(e => e.companyId === Number(filters.companyId)); if (filters.companyId) filtered = filtered.filter(e => e.companyId === Number(filters.companyId));
if (filters.branchId) filtered = filtered.filter(e => e.branchId === Number(filters.branchId)); if (filters.branchId) filtered = filtered.filter(e => e.branchId === Number(filters.branchId));
if (filters.departmentId) filtered = filtered.filter(e => e.departmentId === Number(filters.departmentId)); if (filters.departmentId) filtered = filtered.filter(e => e.departmentId === Number(filters.departmentId));
if (filters.designation) { if (filters.designation) filtered = filtered.filter(e => e.jobTitle?.toLowerCase().includes(filters.designation.toLowerCase()) ?? false);
const desigStr = String(filters.designation).toLowerCase();
const desigNum = Number(filters.designation);
filtered = filtered.filter(e =>
e.jobTitle?.toLowerCase().includes(desigStr) ||
e.jobTitleId === desigNum ||
e.jobRoleId === desigNum
);
}
if (filters.employeeName) filtered = filtered.filter(e => e.fullName?.toLowerCase().includes(filters.employeeName.toLowerCase()) ?? false); if (filters.employeeName) filtered = filtered.filter(e => e.fullName?.toLowerCase().includes(filters.employeeName.toLowerCase()) ?? false);
if (filters.employeeCode) filtered = filtered.filter(e => e.employeeCode?.toLowerCase().includes(filters.employeeCode.toLowerCase()) ?? false); if (filters.employeeCode) filtered = filtered.filter(e => e.employeeCode?.toLowerCase().includes(filters.employeeCode.toLowerCase()) ?? false);
return filtered; return filtered;
@ -358,6 +36,10 @@ export const processDailyAttendance = async (body: any) => {
console.log("AMS Worker Received Body:", JSON.stringify(body)); console.log("AMS Worker Received Body:", JSON.stringify(body));
let datesToProcess: string[] = []; let datesToProcess: string[] = [];
// FIX: Check for camelCase first
// FIX: Protobuf converts undefined to empty string "".
// We must check .length > 0 instead of just truthiness.
// FIX: The generated Protobuf object uses snake_case for this message
if (body.work_date && body.work_date.length > 0) { if (body.work_date && body.work_date.length > 0) {
datesToProcess.push(body.work_date); datesToProcess.push(body.work_date);
} else if (body.start_date?.length > 0 && body.end_date?.length > 0) { } else if (body.start_date?.length > 0 && body.end_date?.length > 0) {
@ -375,6 +57,7 @@ export const processDailyAttendance = async (body: any) => {
await connection.beginTransaction(); await connection.beginTransaction();
const employeeRoster = await getEmployeeRoster(); const employeeRoster = await getEmployeeRoster();
// DEBUG LOG: See if the roster is actually being fetched
console.log(`[AMS Worker] Fetched ${employeeRoster.length} employees from EMS.`); console.log(`[AMS Worker] Fetched ${employeeRoster.length} employees from EMS.`);
if (employeeRoster.length > 0) { if (employeeRoster.length > 0) {
console.log("[AMS Worker] First employee in roster:", employeeRoster[0]); console.log("[AMS Worker] First employee in roster:", employeeRoster[0]);
@ -386,6 +69,7 @@ export const processDailyAttendance = async (body: any) => {
const rawLogs = await AttendanceRepo.findRawLogsByDate(targetDate); const rawLogs = await AttendanceRepo.findRawLogsByDate(targetDate);
if (rawLogs.length === 0) continue; if (rawLogs.length === 0) continue;
// DEBUG LOG: See how many logs were found for this date
console.log(`[AMS Worker] Date ${targetDate}: Found ${rawLogs.length} raw logs.`); console.log(`[AMS Worker] Date ${targetDate}: Found ${rawLogs.length} raw logs.`);
const groupedLogs = rawLogs.reduce((acc: any, log: any) => { const groupedLogs = rawLogs.reduce((acc: any, log: any) => {
@ -397,17 +81,58 @@ export const processDailyAttendance = async (body: any) => {
for (const empCode in groupedLogs) { for (const empCode in groupedLogs) {
const employee = employeeMap.get(empCode); const employee = employeeMap.get(empCode);
// DEBUG LOG: See why employees are being skipped
if (!employee) { if (!employee) {
// console.log(`[AMS Worker] Skipping code ${empCode} - Not found in EMS roster.`);
continue; continue;
} }
if (!employee.isActive) { if (!employee.isActive) {
// console.log(`[AMS Worker] Skipping code ${empCode} - Employee is inactive.`);
continue; continue;
} }
const punches = groupedLogs[empCode]; const punches = groupedLogs[empCode];
const shift = await AttendanceRepo.findShiftDetails(employee.branchId, employee.companyId, connection); const shift = await AttendanceRepo.findShiftDetails(employee.branchId, employee.companyId, connection);
// let check_in = null, check_out = null, worked_hours = 0.0;
// let check_in_status = "ON_TIME", final_status = "ABSENT";
// if (punches.length >= 2) {
// check_in = punches[0];
// check_out = punches[punches.length - 1];
// const firstPunchMs = new Date(check_in.replace(' ', 'T')).getTime();
// const lastPunchMs = new Date(check_out.replace(' ', 'T')).getTime();
// worked_hours = Math.round(((lastPunchMs - firstPunchMs) / (1000 * 60 * 60)) * 100) / 100;
// const rawCheckInTimeStr = check_in.split(' ')[1];
// const [punchH, punchM, punchS] = rawCheckInTimeStr.split(':').map(Number);
// const [shiftH, shiftM, shiftS] = shift.start_time.split(':').map(Number);
// if ((punchH * 3600 + punchM * 60 + punchS) > (shiftH * 3600 + shiftM * 60 + shift.grace_period_minutes * 60)) {
// check_in_status = "LATE";
// }
// if (worked_hours >= 7.0) final_status = "FULL_DAY";
// else if (worked_hours >= 4.0) final_status = "HALF_DAY";
// else final_status = "ABSENT";
// } else if (punches.length === 1) {
// check_in = punches[0];
// check_in_status = "MISPUNCH";
// final_status = "MISPUNCH";
// }
// await AttendanceRepo.upsertProcessedAttendance({
// employeeId: employee.employeeId,
// company_id: employee.companyId,
// branch_id: employee.branchId,
// targetDate, check_in, check_out, worked_hours, check_in_status, final_status
// }, connection);
// 1. Fetch the dynamic policy for this employee's company/branch
const policy = await AttendanceRepo.findAttendancePolicy(employee.companyId, employee.branchId); const policy = await AttendanceRepo.findAttendancePolicy(employee.companyId, employee.branchId);
// Use policy grace period, fallback to shift grace period if policy doesn't exist
const effectiveGracePeriod = policy.late_grace_period_minutes ?? shift.grace_period_minutes; const effectiveGracePeriod = policy.late_grace_period_minutes ?? shift.grace_period_minutes;
let check_in = null, check_out = null, worked_hours = 0.0; let check_in = null, check_out = null, worked_hours = 0.0;
@ -419,16 +144,19 @@ export const processDailyAttendance = async (body: any) => {
const firstPunchMs = new Date(check_in.replace(' ', 'T')).getTime(); const firstPunchMs = new Date(check_in.replace(' ', 'T')).getTime();
let lastPunchMs = new Date(check_out.replace(' ', 'T')).getTime(); let lastPunchMs = new Date(check_out.replace(' ', 'T')).getTime();
// Handle Night Shift edge case
if (shift.is_night_shift && lastPunchMs < firstPunchMs) { if (shift.is_night_shift && lastPunchMs < firstPunchMs) {
lastPunchMs += 24 * 60 * 60 * 1000; lastPunchMs += 24 * 60 * 60 * 1000;
} }
worked_hours = Math.round(((lastPunchMs - firstPunchMs) / (1000 * 60 * 60)) * 100) / 100; worked_hours = Math.round(((lastPunchMs - firstPunchMs) / (1000 * 60 * 60)) * 100) / 100;
// 2. Calculate Base Status using DYNAMIC thresholds
if (worked_hours >= Number(policy.min_hours_full_day)) final_status = "FULL_DAY"; if (worked_hours >= Number(policy.min_hours_full_day)) final_status = "FULL_DAY";
else if (worked_hours >= Number(policy.min_hours_half_day)) final_status = "HALF_DAY"; else if (worked_hours >= Number(policy.min_hours_half_day)) final_status = "HALF_DAY";
else final_status = "ABSENT"; else final_status = "ABSENT";
// 3. Calculate Punctuality
const rawCheckInTimeStr = check_in.split(' ')[1]; const rawCheckInTimeStr = check_in.split(' ')[1];
const [punchH, punchM, punchS] = rawCheckInTimeStr.split(':').map(Number); const [punchH, punchM, punchS] = rawCheckInTimeStr.split(':').map(Number);
const [shiftH, shiftM, shiftS] = shift.start_time.split(':').map(Number); const [shiftH, shiftM, shiftS] = shift.start_time.split(':').map(Number);
@ -440,8 +168,12 @@ export const processDailyAttendance = async (body: any) => {
check_in_status = "LATE"; check_in_status = "LATE";
} }
// 4. Apply Late Penalty (The 3rd Late Rule)
// Rule: Can ONLY downgrade FULL_DAY to HALF_DAY. Cannot affect ABSENT or HALF_DAY.
if (check_in_status === "LATE" && final_status === "FULL_DAY") { if (check_in_status === "LATE" && final_status === "FULL_DAY") {
const latesThisMonth = await AttendanceRepo.countLatesThisMonth(employee.employeeId, targetDate, connection); const latesThisMonth = await AttendanceRepo.countLatesThisMonth(employee.employeeId, targetDate, connection);
// If they already had 2 lates, this is their 3rd. Downgrade to HALF_DAY.
if (latesThisMonth >= policy.max_lates_allowed_per_month) { if (latesThisMonth >= policy.max_lates_allowed_per_month) {
final_status = "HALF_DAY"; final_status = "HALF_DAY";
} }
@ -449,7 +181,7 @@ export const processDailyAttendance = async (body: any) => {
} else if (punches.length === 1) { } else if (punches.length === 1) {
check_in = punches[0]; check_in = punches[0];
check_in_status = "MISPUNCH"; check_in_status = "MISPUNCH"; // Excluded from late penalty logic
final_status = "MISPUNCH"; final_status = "MISPUNCH";
} }
@ -497,6 +229,7 @@ export const getDailyReport = async (req: any) => {
const attendanceRows = await AttendanceRepo.findDailyAttendance(req.date); const attendanceRows = await AttendanceRepo.findDailyAttendance(req.date);
let employeeRoster = await getEmployeeRoster(); let employeeRoster = await getEmployeeRoster();
// Apply filters
employeeRoster = applyRosterFilters(employeeRoster, req); employeeRoster = applyRosterFilters(employeeRoster, req);
const summary = { total_active_workforce: employeeRoster.length, present: 0, late: 0, mispunches: 0, absent: 0 }; const summary = { total_active_workforce: employeeRoster.length, present: 0, late: 0, mispunches: 0, absent: 0 };
@ -537,6 +270,7 @@ export const getDailyReport = async (req: any) => {
export const getAdminRangeReport = async (req: any) => { export const getAdminRangeReport = async (req: any) => {
let employeeRoster = await getEmployeeRoster(); let employeeRoster = await getEmployeeRoster();
// Apply filters
employeeRoster = applyRosterFilters(employeeRoster, req); employeeRoster = applyRosterFilters(employeeRoster, req);
if (employeeRoster.length === 0) return { date_range: { from: req.fromDate, to: req.toDate }, global_summary: { total_records_evaluated: 0, full_days: 0, half_days: 0, late_instances: 0, mispunches: 0 }, report: [] }; if (employeeRoster.length === 0) return { date_range: { from: req.fromDate, to: req.toDate }, global_summary: { total_records_evaluated: 0, full_days: 0, half_days: 0, late_instances: 0, mispunches: 0 }, report: [] };
@ -588,7 +322,7 @@ export const getSingleEmployeeRangeReport = async (employeeId: number, fromDate:
return { return {
employee: { employee: {
employee_id: empProfile.employeeId, employee_id: empProfile.employeeId,
employee_code: empProfile.employeeCode, employee_code: empProfile.employeeCode,
first_name: empProfile.fullName?.split(" ")[0] || "", first_name: empProfile.fullName?.split(" ")[0] || "",
last_name: empProfile.fullName?.split(" ").slice(1).join(" ") || "" last_name: empProfile.fullName?.split(" ").slice(1).join(" ") || ""
@ -602,6 +336,7 @@ export const getSingleEmployeeRangeReport = async (employeeId: number, fromDate:
export const getDashboardMetrics = async (req: any) => { export const getDashboardMetrics = async (req: any) => {
let employeeRoster = await getEmployeeRoster(); let employeeRoster = await getEmployeeRoster();
// Apply filters
employeeRoster = applyRosterFilters(employeeRoster, req); employeeRoster = applyRosterFilters(employeeRoster, req);
const attendanceRows = await AttendanceRepo.findDailyAttendance(req.date); const attendanceRows = await AttendanceRepo.findDailyAttendance(req.date);
@ -618,203 +353,3 @@ export const getDashboardMetrics = async (req: any) => {
total_not_checked_in: totalNotCheckedIn total_not_checked_in: totalNotCheckedIn
}; };
}; };
export const exportDailyReport = async (req: any) => {
const data = await getDailyReport(req);
const roster = data.roster || [];
const summary = data.summary || {};
const fileType = req.fileType || 'excel';
let file_content: Uint8Array;
let file_name: string;
const reportDate = new Date(req.date + 'T00:00:00');
const formattedDate = reportDate.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' });
const dayName = reportDate.toLocaleDateString('en-US', { weekday: 'long' });
const generatedDate = new Date().toLocaleString('en-GB', {
day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit'
});
if (fileType === 'pdf') {
const doc = new PDFDocument({ margin: 30, size: 'A4', layout: 'landscape' });
const chunks: Uint8Array[] = [];
doc.on('data', (chunk: Uint8Array) => chunks.push(chunk));
doc.fontSize(10).fillColor('#000000').text(`Report Name : Daily Attendance Report`, { align: 'left' });
doc.fontSize(10).fillColor('#000000').text(`Date : ${formattedDate} (${dayName})`, { align: 'left' });
doc.fontSize(10).fillColor('#000000').text(`Generated Date : ${generatedDate}`, { align: 'left' });
// Add Summary Metrics Line (Black color)
doc.fontSize(10).fillColor('#000000').text(`Total employees: ${summary.total_active_workforce || 0} | Present: ${summary.present || 0} | Late: ${summary.late || 0} | Mispunches: ${summary.mispunches || 0} | Absent: ${summary.absent || 0}`, { align: 'left' });
doc.moveDown();
const headers = ['Date', 'Day', 'Emp Code', 'Name', 'Company', 'Branch', 'Department', 'Job Role', 'Check-In', 'Check-Out', 'Hrs', 'Status'];
const rows = roster.map((r: any) => [
formattedDate, dayName, r.employee_code, r.full_name, r.company_name || '-', r.branch_name || '-',
r.department_name || '-', r.designation || '-',
r.check_in ? r.check_in.substring(0, 5) : '-',
r.check_out ? r.check_out.substring(0, 5) : '-',
r.worked_hours, r.status
]);
// INCREASED Day column width to 70 to prevent "Wednesday" from wrapping
drawPdfTable(doc, headers, rows, [55, 70, 50, 90, 70, 60, 70, 80, 60, 65, 40, 60]);
doc.end();
await new Promise<void>((resolve) => doc.on('end', resolve));
const totalLength = chunks.reduce((acc, val) => acc + val.length, 0);
file_content = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
file_content.set(chunk, offset);
offset += chunk.length;
}
file_name = `Daily_Report_${req.date}.pdf`;
} else {
file_content = await generateExcelDailyReport(roster, formattedDate, dayName, generatedDate, summary);
file_name = `Daily_Report_${req.date}.xlsx`;
}
return { file_content, file_name };
};
export const exportAdminRangeReport = async (req: any) => {
const data = await getAdminRangeReport(req);
const report = data.report || [];
const fileType = req.fileType || 'excel';
let file_content: Uint8Array;
let file_name: string;
const formattedFromDate = new Date(req.fromDate + 'T00:00:00').toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' });
const formattedToDate = new Date(req.toDate + 'T00:00:00').toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' });
const generatedDate = new Date().toLocaleString('en-GB', {
day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit'
});
const dates = getDaysArray(req.fromDate, req.toDate);
if (fileType === 'pdf') {
const doc = new PDFDocument({ margin: 30, size: 'A4', layout: 'landscape' });
const chunks: Uint8Array[] = [];
doc.on('data', (chunk: Uint8Array) => chunks.push(chunk));
// PDF Top Level Headings
doc.fontSize(10).fillColor('#000000').text('Report Name : Monthly Attendance Report', { align: 'left' });
doc.fontSize(10).fillColor('#000000').text(`Period : ${formattedFromDate} To ${formattedToDate}`, { align: 'left' });
doc.fontSize(10).fillColor('#000000').text(`Generated Date : ${generatedDate}`, { align: 'left' });
doc.moveDown();
const numDates = dates.length;
const fieldColWidth = 50;
const pageWidth = doc.page.width - doc.page.margins.left - doc.page.margins.right;
const dateColWidth = Math.max(18, (pageWidth - fieldColWidth) / numDates);
let y = doc.y;
report.forEach((emp: any) => {
const history = emp.attendance_history || [];
// Check page break
if (y + 80 > doc.page.height - doc.page.margins.bottom) {
doc.addPage();
y = doc.page.margins.top;
}
// --- Employee Summary Header ---
// Added "Name" and changed abbreviations to full words to match Excel logic
doc.font('Helvetica-Bold').fontSize(8).fillColor('#000000');
const summaryText = `Emp Code: ${emp.employee_code} | Name: ${emp.first_name} ${emp.last_name} | Company: ${emp.company_name || '-'} | Branch: ${emp.branch_name || '-'} | Dept: ${emp.department_name || '-'} | Role: ${emp.designation || '-'} | Full: ${emp.range_metrics?.full_days || 0} | Half: ${emp.range_metrics?.half_days || 0} | Mispunches: ${emp.range_metrics?.mispunches || 0} | Late: ${emp.range_metrics?.late_arrivals || 0}`;
doc.fill('#F2F2F2').rect(doc.page.margins.left, y, pageWidth, 18).fill();
doc.fillColor('#000000').text(summaryText, doc.page.margins.left + 5, y + 4, { width: pageWidth - 10 });
y += 20;
// --- Table Header (Field | Date 1 | Date 2 | ...) ---
doc.font('Helvetica-Bold').fontSize(6);
let x = doc.page.margins.left;
doc.fill('#063B00').rect(x, y, fieldColWidth, 16).fill();
doc.fillColor('#FFFFFF').text('Field', x + 2, y + 4, { width: fieldColWidth - 4, align: 'center' });
doc.lineWidth(0.5).strokeColor('#000000').rect(x, y, fieldColWidth, 16).stroke();
x += fieldColWidth;
for (let i = 0; i < numDates; i++) {
const dateObj = new Date(dates[i] + 'T00:00:00');
const dayStr = dateObj.toLocaleDateString('en-GB', { day: '2-digit', month: 'short' });
const dayName = dateObj.toLocaleDateString('en-US', { weekday: 'short' });
doc.fill('#063B00').rect(x, y, dateColWidth, 16).fill();
doc.fillColor('#FFFFFF').text(dayStr, x + 1, y + 2, { width: dateColWidth - 2, align: 'center' });
doc.fillColor('#FFFFFF').text(dayName, x + 1, y + 8, { width: dateColWidth - 2, align: 'center' });
doc.lineWidth(0.5).strokeColor('#000000').rect(x, y, dateColWidth, 16).stroke();
x += dateColWidth;
}
y += 16;
// --- Data Rows (Check-In, Check-Out, Hours, Status) ---
const fields = ['Check-In', 'Check-Out', 'Hours', 'Status'];
doc.font('Helvetica').fontSize(6);
fields.forEach((field) => {
if (y + 14 > doc.page.height - doc.page.margins.bottom) {
doc.addPage();
y = doc.page.margins.top;
}
let xRow = doc.page.margins.left;
doc.fillColor('#000000').text(field, xRow + 2, y + 3, { width: fieldColWidth - 4, align: 'left' });
doc.lineWidth(0.5).strokeColor('#000000').rect(xRow, y, fieldColWidth, 14).stroke();
xRow += fieldColWidth;
for (let i = 0; i < numDates; i++) {
const log = history.find((h: any) => h.work_date === dates[i]);
let text = '-';
let textColor = '#000000';
let bgColor = null;
if (log) {
if (field === 'Check-In') text = log.check_in ? log.check_in.substring(0, 5) : '-';
else if (field === 'Check-Out') text = log.check_out ? log.check_out.substring(0, 5) : '-';
else if (field === 'Hours') text = log.worked_hours ? Number(log.worked_hours).toFixed(2) : '0.00';
else if (field === 'Status') {
// Converted text to F, H, M, A, L while keeping the same colors
const statusStr = (log.final_status || '').toUpperCase();
if (statusStr === 'FULL_DAY') { text = 'F'; bgColor = '#C6EFCE'; textColor = '#006100'; }
else if (statusStr === 'ABSENT') { text = 'A'; bgColor = '#FFC7CE'; textColor = '#9C0006'; }
else if (statusStr === 'HALF_DAY') { text = 'H'; bgColor = '#FFEB9C'; textColor = '#9C6500'; }
else if (statusStr === 'MISPUNCH') { text = 'M'; bgColor = '#B1A0C7'; textColor = '#4B2E83'; }
else if (statusStr === 'LATE') { text = 'L'; bgColor = '#FFC000'; textColor = '#000000'; }
else { text = '-'; }
}
}
if (bgColor) {
doc.fill(bgColor).rect(xRow, y, dateColWidth, 14).fill();
}
doc.fillColor(textColor).text(text, xRow + 1, y + 3, { width: dateColWidth - 2, align: 'center' });
doc.lineWidth(0.5).strokeColor('#000000').rect(xRow, y, dateColWidth, 14).stroke();
xRow += dateColWidth;
}
y += 14;
});
y += 10;
});
doc.end();
await new Promise<void>((resolve) => doc.on('end', resolve));
const totalLength = chunks.reduce((acc, val) => acc + val.length, 0);
file_content = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
file_content.set(chunk, offset);
offset += chunk.length;
}
file_name = `Monthly_Report_${req.fromDate}_to_${req.toDate}.pdf`;
} else {
// Use the new Excel helper function for Matrix format
file_content = await generateExcelMonthlyReport(report, dates, formattedFromDate, formattedToDate, generatedDate);
file_name = `Monthly_Report_${req.fromDate}_to_${req.toDate}.xlsx`;
}
return { file_content, file_name };
};

View File

@ -32,6 +32,7 @@ export const getPendingRegularizations = async (user: any) => {
const employeeIds = [...new Set(rows.map((r: any) => r.employee_id))]; const employeeIds = [...new Set(rows.map((r: any) => r.employee_id))];
const roster = await getEmployeeRoster(employeeIds); const roster = await getEmployeeRoster(employeeIds);
// FIX: Map using camelCase employeeId returned by gRPC
const rosterMap = new Map(roster.map((e: any) => [e.employeeId, e])); const rosterMap = new Map(roster.map((e: any) => [e.employeeId, e]));
return rows.map((req: any) => { return rows.map((req: any) => {
@ -46,36 +47,6 @@ export const getPendingRegularizations = async (user: any) => {
}); });
}; };
export const getAllRegularizations = async () => {
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 getEmployeeRoster(Array.from(allIds));
const rosterMap = new Map(roster.map((e: any) => [e.employeeId, e]));
return rows.map((req: any) => {
const emp = rosterMap.get(Number(req.employee_id));
const reviewer = rosterMap.get(Number(req.reviewed_by_id));
return {
...req,
employee_code: emp?.employeeCode || "N/A",
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"
};
});
};
export const reviewRegularizationRequest = async (user: any, data: any) => { export const reviewRegularizationRequest = async (user: any, data: any) => {
if (!data.regularization_id || !["APPROVED", "REJECTED"].includes(data.action)) { if (!data.regularization_id || !["APPROVED", "REJECTED"].includes(data.action)) {
throw new Error("Invalid payload."); throw new Error("Invalid payload.");
@ -87,17 +58,9 @@ export const reviewRegularizationRequest = async (user: any, data: any) => {
const request = await RegRepo.findRegularizationById(data.regularization_id, connection); const request = await RegRepo.findRegularizationById(data.regularization_id, connection);
if (!request) throw new Error("Regularization request not found."); if (!request) throw new Error("Regularization request not found.");
if (user.role !== AppRole.DIRECTOR && !user.managed_branches.includes(request.branch_id)) {
// AUTHORIZATION LOGIC: Allow Directors globally, but restrict HR Managers to their branches throw new Error("Access Denied: You do not manage this branch.");
if (user.role !== AppRole.DIRECTOR) {
if (user.role !== AppRole.HR_MANAGER) {
throw new Error("Access Denied: Only HR Managers and Directors can review requests.");
}
if (!user.managed_branches.includes(request.branch_id)) {
throw new Error("Access Denied: You do not manage this branch.");
}
} }
if (request.status !== "PENDING") throw new Error("This request has already been processed."); if (request.status !== "PENDING") throw new Error("This request has already been processed.");
await RegRepo.updateRegularizationStatus(data.regularization_id, data.action, user.employee_id, connection); await RegRepo.updateRegularizationStatus(data.regularization_id, data.action, user.employee_id, connection);
@ -114,7 +77,7 @@ export const reviewRegularizationRequest = async (user: any, data: any) => {
const shift = await AttendanceRepo.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) { if (request.requested_check_in && request.requested_check_out) {
// Safely convert Date objects to strings if necessary // FIX: Safely convert Date objects to strings if necessary
rawCheckIn = request.requested_check_in instanceof Date rawCheckIn = request.requested_check_in instanceof Date
? request.requested_check_in.toISOString().replace("T", " ").substring(0, 19) ? request.requested_check_in.toISOString().replace("T", " ").substring(0, 19)
: request.requested_check_in; : request.requested_check_in;
@ -146,7 +109,7 @@ export const reviewRegularizationRequest = async (user: any, data: any) => {
} }
} }
// Use the safe string variables for the DB update // FIX: Use the safe string variables for the DB update
if (request.attendance_id && rawCheckIn && rawCheckOut) { if (request.attendance_id && rawCheckIn && rawCheckOut) {
await AttendanceRepo.updateProcessedAttendanceById(request.attendance_id, { await AttendanceRepo.updateProcessedAttendanceById(request.attendance_id, {
requested_check_in: rawCheckIn, requested_check_in: rawCheckIn,

View File

@ -27,12 +27,12 @@ export const EmployeeServiceImplementation = {
})); }));
return { success: true, count: Number(mappedData.length), data: mappedData }; return { success: true, count: Number(mappedData.length), data: mappedData };
} catch (error: any) { } catch (error: any) {
console.error("gRPC getEmployees Error:", error); console.error("gRPC getEmployees Error:", error);
return { success: false, count: 0, data: [] }; return { success: false, count: 0, data: [] };
} }
}, },
async getEmployeeById(req: any) { async getEmployeeById(req: any) {
try { try {
const emp = await EmployeeService.getEmployeeById(Number(req.id)); const emp = await EmployeeService.getEmployeeById(Number(req.id));
@ -140,7 +140,7 @@ export const EmployeeServiceImplementation = {
} }
}, },
async updateEmployee(req: any) { async updateEmployee(req: any) {
try { try {
const dbPayload = { const dbPayload = {
firstName: req.data?.firstName, firstName: req.data?.firstName,
@ -177,7 +177,7 @@ export const EmployeeServiceImplementation = {
}; };
export const InternalServiceImplementation = { export const InternalServiceImplementation = {
async getRoster(req: any) { async getRoster(req: any) {
try { try {
const ids = req.ids || []; const ids = req.ids || [];
const data = await InternalService.getRoster(ids.length > 0 ? ids.join(",") : undefined); const data = await InternalService.getRoster(ids.length > 0 ? ids.join(",") : undefined);
@ -192,9 +192,7 @@ export const InternalServiceImplementation = {
companyName: emp.company_name || "", companyName: emp.company_name || "",
departmentName: emp.department_name || "", departmentName: emp.department_name || "",
departmentId: Number(emp.department_id) || 0, departmentId: Number(emp.department_id) || 0,
jobTitle: emp.job_title || "", jobTitle: emp.job_title || ""
managerName: emp.manager_name || "No Manager Assigned",
managerId: Number(emp.manager_id) || 0
})); }));
return { success: true, data: mappedData }; return { success: true, data: mappedData };
} catch (error: any) { } catch (error: any) {

View File

@ -3,11 +3,9 @@ import pool from "../db.ts";
export const findInternalRoster = async (ids?: number[]) => { export const findInternalRoster = async (ids?: number[]) => {
let query = ` let query = `
SELECT e.employee_id, e.employee_code, e.is_active, CONCAT_WS(' ', p.first_name, p.last_name) AS full_name, SELECT e.employee_id, e.employee_code, e.is_active, CONCAT(p.first_name, ' ', p.last_name) AS full_name,
b.branch_id, b.branch_name, c.company_id, c.name AS company_name, b.branch_id, b.branch_name, c.company_id, c.name AS company_name,
d.name AS department_name, d.department_id, j.title AS job_title, d.name AS department_name, d.department_id, j.title AS job_title
CONCAT_WS(' ', mgr_p.first_name, mgr_p.last_name) AS manager_name,
ea.reporting_to_id AS manager_id
FROM employees e FROM employees e
JOIN partners p ON e.partner_id = p.partner_id JOIN partners p ON e.partner_id = p.partner_id
JOIN companies c ON e.company_id = c.company_id JOIN companies c ON e.company_id = c.company_id
@ -15,8 +13,6 @@ export const findInternalRoster = async (ids?: number[]) => {
JOIN employee_assignments ea ON e.employee_id = ea.employee_id AND ea.is_current = TRUE JOIN employee_assignments ea ON e.employee_id = ea.employee_id AND ea.is_current = TRUE
JOIN departments d ON ea.department_id = d.department_id JOIN departments d ON ea.department_id = d.department_id
JOIN job_positions j ON ea.job_id = j.job_id JOIN job_positions j ON ea.job_id = j.job_id
LEFT JOIN employees mgr_e ON ea.reporting_to_id = mgr_e.employee_id
LEFT JOIN partners mgr_p ON mgr_e.partner_id = mgr_p.partner_id
WHERE e.is_active = TRUE`; WHERE e.is_active = TRUE`;
const values: any[] = []; const values: any[] = [];
@ -29,3 +25,13 @@ export const findInternalRoster = async (ids?: number[]) => {
const [rows] = await pool.query(query, values); const [rows] = await pool.query(query, values);
return rows; return rows;
}; };
export const findInternalManager = async (employeeId: number) => {
const [rows] = await pool.query(
`SELECT ea.reporting_to_id AS manager_id
FROM employee_assignments ea
WHERE ea.employee_id = ? AND ea.is_current = TRUE`,
[employeeId]
);
return (rows as any[])[0];
};

View File

@ -8,7 +8,6 @@ import { EmployeeServiceClient, LookupServiceClient, ContractServiceClient, Dash
import { AttendanceServiceClient, RegularizationServiceClient, DashboardServiceClient as AmsDashboardClient } from "../../../generated/ams.ts"; import { AttendanceServiceClient, RegularizationServiceClient, DashboardServiceClient as AmsDashboardClient } from "../../../generated/ams.ts";
// LMS Clients // LMS Clients
import { AdminServiceClient, EmployeeServiceClient as LmsEmployeeClient, LeaveServiceClient, ManagerServiceClient, ReportsServiceClient } from "../../../generated/lms.ts"; import { AdminServiceClient, EmployeeServiceClient as LmsEmployeeClient, LeaveServiceClient, ManagerServiceClient, ReportsServiceClient } from "../../../generated/lms.ts";
import { HRManagerServiceClient } from "../../../generated/lms.ts";
const creds = grpc.credentials.createInsecure(); const creds = grpc.credentials.createInsecure();
@ -30,7 +29,6 @@ export const clients = {
leave: new LeaveServiceClient(config.urls.lms, creds), leave: new LeaveServiceClient(config.urls.lms, creds),
manager: new ManagerServiceClient(config.urls.lms, creds), manager: new ManagerServiceClient(config.urls.lms, creds),
reports: new ReportsServiceClient(config.urls.lms, creds), reports: new ReportsServiceClient(config.urls.lms, creds),
hrManager: new HRManagerServiceClient(config.urls.lms, creds)
} }
}; };

View File

@ -173,60 +173,4 @@ router.get("/api/ams/attendance/dashboard/metrics", requireRole([AppRole.DIRECTO
} catch (e: any) { ctx.response.status = 500; ctx.response.body = { error: e.message }; } } catch (e: any) { ctx.response.status = 500; ctx.response.body = { error: e.message }; }
}); });
router.get("/api/ams/attendance/regularize/history", requireRole([AppRole.DIRECTOR, AppRole.HR_MANAGER]), async (ctx: any) => {
try {
const res: any = await callGrpc(clients.ams.regularization, "getAllRegularizations", {});
ctx.response.body = { success: res.success, count: res.data.length, data: res.data };
} catch (e: any) { ctx.response.status = 500; ctx.response.body = { error: e.message }; }
});
router.get("/api/ams/attendance/daily-report/export", requireRole([AppRole.DIRECTOR, AppRole.HR_MANAGER, AppRole.MANAGER]), async (ctx: any) => {
try {
const p = ctx.request.url.searchParams;
const req = {
date: p.get("date") || "",
companyId: p.get("company_id") ? Number(p.get("company_id")) : undefined,
branchId: p.get("branch_id") ? Number(p.get("branch_id")) : undefined,
departmentId: p.get("department_id") ? Number(p.get("department_id")) : undefined,
designation: p.get("designation") || undefined,
employeeName: p.get("employee_name") || undefined,
employeeCode: p.get("employee_code") || undefined,
fileType: p.get("file_type") || "excel" // NEW
};
const res: any = await callGrpc(clients.ams.attendance, "exportDailyReport", req);
ctx.response.headers.set('Content-Disposition', `attachment; filename="${res.fileName}"`);
ctx.response.headers.set('Content-Type', 'application/octet-stream');
ctx.response.body = res.fileContent;
} catch (e: any) {
ctx.response.status = 500;
ctx.response.body = { error: e.message };
}
});
router.get("/api/ams/attendance/admin-report/export", requireRole([AppRole.DIRECTOR, AppRole.HR_MANAGER, AppRole.MANAGER]), async (ctx: any) => {
try {
const p = ctx.request.url.searchParams;
const req = {
fromDate: p.get("from_date") || "",
toDate: p.get("to_date") || "",
branchId: p.get("branch_id") ? Number(p.get("branch_id")) : undefined,
departmentId: p.get("department_id") ? Number(p.get("department_id")) : undefined,
companyId: p.get("company_id") ? Number(p.get("company_id")) : undefined,
designation: p.get("designation") || undefined,
employeeName: p.get("employee_name") || undefined,
employeeCode: p.get("employee_code") || undefined,
fileType: p.get("file_type") || "excel" // NEW
};
const res: any = await callGrpc(clients.ams.attendance, "exportAdminRangeReport", req);
ctx.response.headers.set('Content-Disposition', `attachment; filename="${res.fileName}"`);
ctx.response.headers.set('Content-Type', 'application/octet-stream');
ctx.response.body = res.fileContent;
} catch (e: any) {
ctx.response.status = 500;
ctx.response.body = { error: e.message };
}
});
export default router; export default router;

View File

@ -126,7 +126,6 @@ router.get("/api/lms/holidays/valid-optional", async (ctx: any) => {
} catch (e: any) { ctx.response.status = 500; ctx.response.body = { error: e.message }; } } catch (e: any) { ctx.response.status = 500; ctx.response.body = { error: e.message }; }
}); });
// Inside /api/lms/leaves/apply
router.post("/api/lms/leaves/apply", async (ctx: any) => { router.post("/api/lms/leaves/apply", async (ctx: any) => {
try { try {
const body = await getJsonBody(ctx); const body = await getJsonBody(ctx);
@ -138,8 +137,7 @@ router.post("/api/lms/leaves/apply", async (ctx: any) => {
dateFrom: body.date_from || "", dateFrom: body.date_from || "",
dateTo: body.date_to || "", dateTo: body.date_to || "",
isHalfDay: !!body.is_half_day, isHalfDay: !!body.is_half_day,
reason: body.reason || "", reason: body.reason || ""
applicantRole: ctx.state.user.role // <--- ADD THIS LINE
}; };
const res: any = await callGrpc(clients.lms.leave, "applyForLeave", reqPayload); const res: any = await callGrpc(clients.lms.leave, "applyForLeave", reqPayload);
ctx.response.status = 201; ctx.response.status = 201;
@ -169,28 +167,24 @@ router.get("/api/lms/manager/pending", requireRole([AppRole.DIRECTOR, AppRole.HR
} catch (e: any) { ctx.response.status = 500; ctx.response.body = { error: e.message }; } } catch (e: any) { ctx.response.status = 500; ctx.response.body = { error: e.message }; }
}); });
// Inside /api/lms/manager/leaves/:applicationId/approve
router.post("/api/lms/manager/leaves/:applicationId/approve", requireRole([AppRole.DIRECTOR, AppRole.HR_MANAGER, AppRole.MANAGER]), async (ctx: any) => { router.post("/api/lms/manager/leaves/:applicationId/approve", requireRole([AppRole.DIRECTOR, AppRole.HR_MANAGER, AppRole.MANAGER]), async (ctx: any) => {
try { try {
const reqPayload = { const reqPayload = {
managerId: Number(ctx.state.user.id), managerId: Number(ctx.state.user.id),
applicationId: Number(ctx.params.applicationId), applicationId: Number(ctx.params.applicationId)
userRole: ctx.state.user.role // INJECT ROLE
}; };
const res: any = await callGrpc(clients.lms.manager, "approveLeaveManager", reqPayload); const res: any = await callGrpc(clients.lms.manager, "approveLeaveManager", reqPayload);
ctx.response.body = { success: res.success, message: res.message }; ctx.response.body = { success: res.success, message: res.message };
} catch (e: any) { ctx.response.status = 500; ctx.response.body = { error: e.message }; } } catch (e: any) { ctx.response.status = 500; ctx.response.body = { error: e.message }; }
}); });
// Inside /api/lms/manager/leaves/:applicationId/reject
router.post("/api/lms/manager/leaves/:applicationId/reject", requireRole([AppRole.DIRECTOR, AppRole.HR_MANAGER, AppRole.MANAGER]), async (ctx: any) => { router.post("/api/lms/manager/leaves/:applicationId/reject", requireRole([AppRole.DIRECTOR, AppRole.HR_MANAGER, AppRole.MANAGER]), async (ctx: any) => {
try { try {
const body = await getJsonBody(ctx); const body = await getJsonBody(ctx);
const reqPayload = { const reqPayload = {
managerId: Number(ctx.state.user.id), managerId: Number(ctx.state.user.id),
applicationId: Number(ctx.params.applicationId), applicationId: Number(ctx.params.applicationId),
reason: body.reason || "", reason: body.reason || ""
userRole: ctx.state.user.role // INJECT ROLE
}; };
const res: any = await callGrpc(clients.lms.manager, "rejectLeaveManager", reqPayload); const res: any = await callGrpc(clients.lms.manager, "rejectLeaveManager", reqPayload);
ctx.response.body = { success: res.success, message: res.message }; ctx.response.body = { success: res.success, message: res.message };
@ -219,13 +213,4 @@ router.get("/api/lms/admin/reports/ob-cb", requireRole([AppRole.DIRECTOR, AppRol
} catch (e: any) { ctx.response.status = 500; ctx.response.body = { error: e.message }; } } catch (e: any) { ctx.response.status = 500; ctx.response.body = { error: e.message }; }
}); });
// Add this route for HR to see all applications
router.get("/api/lms/hr/applications", requireRole([AppRole.HR_MANAGER, AppRole.DIRECTOR]), async (ctx: any) => {
try {
const res: any = await callGrpc(clients.lms.hrManager, "getAllLeaveApplications", {});
ctx.response.body = { success: res.success, count: res.data.length, data: res.data };
} catch (e: any) { ctx.response.status = 500; ctx.response.body = { error: e.message }; }
});
export default router; export default router;

View File

@ -1,28 +0,0 @@
import * as HRManagerService from "../services/hr_manager.service.ts";
export const HRManagerServiceImplementation = {
async getAllLeaveApplications(req: any) {
try {
const data = await HRManagerService.getAllApplications();
const mappedData = data.map((p: any) => ({
applicationId: Number(p.application_id) || 0,
employeeId: Number(p.employee_id) || 0,
leaveType: p.leave_type || "",
dateFrom: p.date_from || "",
dateTo: p.date_to || "",
numberOfDays: Number(p.number_of_days) || 0,
status: p.status || "",
reason: p.reason || "", // Ensure reason is mapped
employeeCode: p.employee_code || "",
firstName: p.first_name || "",
lastName: p.last_name || "",
departmentName: p.department_name || "",
jobTitle: p.job_title || "",
managerName: p.manager_name || "N/A" // Ensure managerName is mapped
}));
return { success: true, data: mappedData };
} catch (e: any) {
return { success: false, data: [] };
}
}
};

View File

@ -3,7 +3,8 @@ import * as LeaveService from "../services/leave.service.ts";
export const LeaveServiceImplementation = { export const LeaveServiceImplementation = {
async applyForLeave(req: any) { async applyForLeave(req: any) {
try { try {
// FIX: Map camelCase gRPC payload back to snake_case for the DB service layer
const dbPayload = { const dbPayload = {
employee_id: req.employeeId, employee_id: req.employeeId,
leave_type_id: req.leaveTypeId, leave_type_id: req.leaveTypeId,
@ -13,7 +14,6 @@ export const LeaveServiceImplementation = {
date_to: req.dateTo, date_to: req.dateTo,
is_half_day: req.isHalfDay, is_half_day: req.isHalfDay,
reason: req.reason, reason: req.reason,
applicant_role: req.applicantRole // NEW
}; };
const result = await LeaveService.applyForLeave(dbPayload); const result = await LeaveService.applyForLeave(dbPayload);

View File

@ -5,7 +5,7 @@ export const ManagerServiceImplementation = {
async getPendingManagerLeaves(req: any) { async getPendingManagerLeaves(req: any) {
try { try {
const user = { employee_id: req.employeeId, role: req.role }; const user = { employee_id: req.employeeId, role: req.role };
const data = await ManagerService.getPendingApprovals(user); const data = await ManagerService.getPendingManagerLeaves(user);
const mappedData = data.map((p: any) => ({ const mappedData = data.map((p: any) => ({
applicationId: Number(p.application_id) || 0, applicationId: Number(p.application_id) || 0,
employeeId: Number(p.employee_id) || 0, employeeId: Number(p.employee_id) || 0,
@ -25,15 +25,15 @@ export const ManagerServiceImplementation = {
}, },
async approveLeaveManager(req: any) { async approveLeaveManager(req: any) {
try { try {
const user = { employee_id: req.managerId, role: req.userRole }; // Pass userRole const user = { employee_id: req.managerId };
await ManagerService.approveLeave(user, Number(req.applicationId)); await ManagerService.approveLeaveManager(user, Number(req.applicationId));
return { success: true, message: "Leave approved successfully." }; return { success: true, message: "Leave approved successfully." };
} catch (e: any) { return { success: false, message: e.message }; } } catch (e: any) { return { success: false, message: e.message }; }
}, },
async rejectLeaveManager(req: any) { async rejectLeaveManager(req: any) {
try { try {
const user = { employee_id: req.managerId, role: req.userRole }; // Pass userRole const user = { employee_id: req.managerId };
await ManagerService.rejectLeave(user, Number(req.applicationId)); await ManagerService.rejectLeaveManager(user, Number(req.applicationId));
return { success: true, message: "Leave rejected successfully." }; return { success: true, message: "Leave rejected successfully." };
} catch (e: any) { return { success: false, message: e.message }; } } catch (e: any) { return { success: false, message: e.message }; }
} }

View File

@ -1,12 +1,11 @@
// services/lms/main.ts // services/lms/main.ts
import * as grpc from "@grpc/grpc-js"; import * as grpc from "@grpc/grpc-js";
import { AdminServiceService, EmployeeServiceService, LeaveServiceService, ManagerServiceService, ReportsServiceService, HRManagerServiceService } from "../../generated/lms.ts"; import { AdminServiceService, EmployeeServiceService, LeaveServiceService, ManagerServiceService, ReportsServiceService } from "../../generated/lms.ts";
import { AdminServiceImplementation } from "./handlers/admin.handler.ts"; import { AdminServiceImplementation } from "./handlers/admin.handler.ts";
import { EmployeeServiceImplementation } from "./handlers/employee.handler.ts"; import { EmployeeServiceImplementation } from "./handlers/employee.handler.ts";
import { LeaveServiceImplementation } from "./handlers/leave.handler.ts"; import { LeaveServiceImplementation } from "./handlers/leave.handler.ts";
import { ManagerServiceImplementation } from "./handlers/manager.handler.ts"; import { ManagerServiceImplementation } from "./handlers/manager.handler.ts";
import { ReportsServiceImplementation } from "./handlers/reports.handler.ts"; import { ReportsServiceImplementation } from "./handlers/reports.handler.ts";
import { HRManagerServiceImplementation } from "./handlers/hr_manager.handler.ts";
const PORT = Number(Deno.env.get("LMS_PORT")) || 8003; const PORT = Number(Deno.env.get("LMS_PORT")) || 8003;
@ -31,7 +30,6 @@ server.addService(EmployeeServiceService, wrap(EmployeeServiceImplementation));
server.addService(LeaveServiceService, wrap(LeaveServiceImplementation)); server.addService(LeaveServiceService, wrap(LeaveServiceImplementation));
server.addService(ManagerServiceService, wrap(ManagerServiceImplementation)); server.addService(ManagerServiceService, wrap(ManagerServiceImplementation));
server.addService(ReportsServiceService, wrap(ReportsServiceImplementation)); server.addService(ReportsServiceService, wrap(ReportsServiceImplementation));
server.addService(HRManagerServiceService, wrap(HRManagerServiceImplementation));
server.bindAsync(`0.0.0.0:${PORT}`, grpc.ServerCredentials.createInsecure(), (err, port) => { server.bindAsync(`0.0.0.0:${PORT}`, grpc.ServerCredentials.createInsecure(), (err, port) => {
if (err) { console.error("Failed to start gRPC server:", err); return; } if (err) { console.error("Failed to start gRPC server:", err); return; }

View File

@ -121,11 +121,10 @@ export const findMonthlyUsage = async (employeeId: number, leaveTypeId: number,
return Number(rows[0]?.used_this_month || 0); return Number(rows[0]?.used_this_month || 0);
}; };
// Update insertLeaveApplication
export const insertLeaveApplication = async (data: any, conn: any) => { export const insertLeaveApplication = async (data: any, conn: any) => {
const [result] = await conn.execute( const [result] = await conn.execute(
`INSERT INTO leave_applications (employee_id, leave_type_id, company_id, date_from, date_to, number_of_days, reason, status, manager_approved_by, applicant_role, approver_role) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, `INSERT INTO leave_applications (employee_id, leave_type_id, company_id, date_from, date_to, number_of_days, reason, status, manager_approved_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[data.employee_id, data.leave_type_id, data.company_id, data.date_from, data.date_to, data.number_of_days, data.reason, data.status, data.manager_id, data.applicant_role, data.approver_role] [data.employee_id, data.leave_type_id, data.company_id, data.date_from, data.date_to, data.number_of_days, data.reason, data.status, data.manager_id]
); );
return result.insertId; return result.insertId;
}; };
@ -171,14 +170,14 @@ export const updateAllocationUsedDays = async (employeeId: number, leaveTypeId:
await conn.execute(`UPDATE leave_allocations SET used_days = used_days + ? WHERE employee_id = ? AND leave_type_id = ? AND calendar_year = ?`, [days, employeeId, leaveTypeId, year]); await conn.execute(`UPDATE leave_allocations SET used_days = used_days + ? WHERE employee_id = ? AND leave_type_id = ? AND calendar_year = ?`, [days, employeeId, leaveTypeId, year]);
}; };
// Update rejectLeaveApplication to allow any authorized role to reject export const rejectLeaveApplication = async (applicationId: number, managerId: number) => {
export const rejectLeaveApplication = async (applicationId: number, approverRole: string) => { const [result] = await pool.execute(
let query = `UPDATE leave_applications SET status = 'REJECTED' WHERE application_id = ? AND status IN ('PENDING', 'PENDING_LOP') AND approver_role = ?`; `UPDATE leave_applications SET status = 'REJECTED' WHERE application_id = ? AND manager_approved_by = ? AND status IN ('PENDING', 'PENDING_LOP')`,
const [result] = await pool.execute(query, [applicationId, approverRole]); [applicationId, managerId]
);
return result.affectedRows > 0; return result.affectedRows > 0;
}; };
// ========================================== // ==========================================
// REPORTS // REPORTS
// ========================================== // ==========================================
@ -193,53 +192,3 @@ export const findLedgerReportData = async (year: number, month: number, companyI
); );
return rows; return rows;
}; };
// ==========================================
// HR MANAGER ACTIONS
// ==========================================
export const findAllApplications = async () => {
const [rows] = await pool.execute(
`SELECT la.application_id, la.employee_id, lt.name AS leave_type,
DATE_FORMAT(la.date_from, '%Y-%m-%d') as date_from,
DATE_FORMAT(la.date_to, '%Y-%m-%d') as date_to,
la.number_of_days, la.status, la.reason
FROM leave_applications la
INNER JOIN leave_types lt ON la.leave_type_id = lt.leave_type_id
ORDER BY la.date_from DESC`
);
return rows;
};
// Replace findPendingManagerLeaves with dynamic routing
export const findPendingForApprover = async (approverId: number, approverRole: string) => {
let query = `SELECT la.application_id, la.employee_id, la.leave_type_id, lt.name AS leave_type, la.date_from, la.date_to, la.number_of_days, la.reason, la.status
FROM leave_applications la INNER JOIN leave_types lt ON la.leave_type_id = lt.leave_type_id WHERE la.status IN ('PENDING', 'PENDING_LOP')`;
const params: any[] = [];
if (approverRole === 'MANAGER') {
query += ` AND la.approver_role = 'MANAGER' AND la.manager_approved_by = ?`;
params.push(approverId);
} else if (approverRole === 'HR_MANAGER') {
query += ` AND la.approver_role = 'HR_MANAGER'`;
} else if (approverRole === 'DIRECTOR') {
query += ` AND la.approver_role = 'DIRECTOR'`;
} else {
return [];
}
query += ` ORDER BY la.date_from ASC`;
const [rows] = await pool.execute(query, params);
return rows;
};

View File

@ -1,24 +0,0 @@
import { getEmployeeRoster } from "core/internal-client.ts";
import * as LmsRepo from "../repositories/lms.repository.ts";
export const getAllApplications = async () => {
const rows = await LmsRepo.findAllApplications();
if (rows.length === 0) return [];
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]));
return rows.map((app: any) => {
const emp = rosterMap.get(app.employee_id);
return {
...app,
employee_code: emp?.employeeCode || "N/A",
first_name: emp?.fullName?.split(" ")[0] || "",
last_name: emp?.fullName?.split(" ").slice(1).join(" ") || "",
department_name: emp?.departmentName || "N/A",
job_title: emp?.jobTitle || "N/A",
manager_name: emp?.managerName || "N/A" // <--- ADD THIS LINE!
};
});
};

View File

@ -1,6 +1,7 @@
import { getEmployeeRoster } from './../../../packages/core/internal-client.ts'; // services/lms/services/leave.service.ts
import pool from "../db.ts"; import pool from "../db.ts";
import { calculateLeaveDays } from "core/calendar.service.ts"; import { calculateLeaveDays } from "core/calendar.service.ts";
import { getEmployeeManagerId } from "core/internal-client.ts";
import * as LmsRepo from "../repositories/lms.repository.ts"; import * as LmsRepo from "../repositories/lms.repository.ts";
export const applyForLeave = async (payload: any) => { export const applyForLeave = async (payload: any) => {
@ -31,11 +32,6 @@ export const applyForLeave = async (payload: any) => {
const rawBalance = Number(await LmsRepo.findRawBalance(payload.employee_id, payload.leave_type_id, currentYear) || 0); const rawBalance = Number(await LmsRepo.findRawBalance(payload.employee_id, payload.leave_type_id, currentYear) || 0);
const pendingDays = await LmsRepo.findPendingDays(payload.employee_id, payload.leave_type_id, currentYear); const pendingDays = await LmsRepo.findPendingDays(payload.employee_id, payload.leave_type_id, currentYear);
const availableBalance = rawBalance - pendingDays; const availableBalance = rawBalance - pendingDays;
if (availableBalance <= 0) {
throw new Error("You have insufficient leave balance. Cannot apply for leave.");
}
const usedThisMonth = await LmsRepo.findMonthlyUsage(payload.employee_id, payload.leave_type_id, currentMonth, currentYear); const usedThisMonth = await LmsRepo.findMonthlyUsage(payload.employee_id, payload.leave_type_id, currentMonth, currentYear);
// 4. Determine Paid vs LOP Split // 4. Determine Paid vs LOP Split
@ -50,29 +46,8 @@ export const applyForLeave = async (payload: any) => {
lopDays = requestedDays - paidDays; lopDays = requestedDays - paidDays;
} }
// console.log(`[LMS] Applying leave for Employee ${payload.employee_id}. Applicant Role: ${payload.applicant_role}`); // 5. Fetch Manager ID via HTTP
let approverRole = 'MANAGER'; const managerId = await getEmployeeManagerId(payload.employee_id);
let managerId = null;
if (payload.applicant_role === 'EMPLOYEE') {
approverRole = 'MANAGER';
// NEW: Use getEmployeeRoster instead of getEmployeeManagerId
const roster = await getEmployeeRoster([payload.employee_id]);
if (roster.length === 0) throw new Error("Employee not found in EMS roster.");
managerId = roster[0].managerId;
// console.log(`[LMS] Fetched Manager ID from Roster for Employee: ${managerId}`);
if (!managerId) throw new Error("Could not determine reporting manager. Please assign a manager to this employee in EMS.");
} else if (payload.applicant_role === 'MANAGER') {
approverRole = 'HR_MANAGER';
} else if (payload.applicant_role === 'HR_MANAGER') {
approverRole = 'DIRECTOR';
} else if (payload.applicant_role === 'DIRECTOR') {
approverRole = 'HR_MANAGER';
}
// 6. Execute DB Transaction // 6. Execute DB Transaction
const connection = await pool.getConnection(); const connection = await pool.getConnection();
@ -81,26 +56,12 @@ export const applyForLeave = async (payload: any) => {
const insertedIds = []; const insertedIds = [];
if (paidDays > 0) { if (paidDays > 0) {
const id = await LmsRepo.insertLeaveApplication({ const id = await LmsRepo.insertLeaveApplication({ ...payload, number_of_days: paidDays, status: 'PENDING', manager_id: managerId }, connection);
...payload,
number_of_days: paidDays,
status: 'PENDING',
manager_id: managerId,
applicant_role: payload.applicant_role,
approver_role: approverRole
}, connection);
insertedIds.push(id); insertedIds.push(id);
} }
if (lopDays > 0) { if (lopDays > 0) {
const id = await LmsRepo.insertLeaveApplication({ const id = await LmsRepo.insertLeaveApplication({ ...payload, number_of_days: lopDays, status: 'PENDING_LOP', manager_id: managerId }, connection);
...payload,
number_of_days: lopDays,
status: 'PENDING_LOP',
manager_id: managerId,
applicant_role: payload.applicant_role,
approver_role: approverRole
}, connection);
insertedIds.push(id); insertedIds.push(id);
} }

View File

@ -72,89 +72,3 @@ export const rejectLeaveManager = async (user: any, applicationId: number) => {
if (!success) throw new Error("Leave not found, already processed, or you lack permission."); if (!success) throw new Error("Leave not found, already processed, or you lack permission.");
return true; return true;
}; };
export const getPendingApprovals = async (user: any) => {
const rows = await LmsRepo.findPendingForApprover(user.employee_id, user.role);
if (rows.length === 0) return [];
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]));
return rows.map((app: any) => {
const emp = rosterMap.get(app.employee_id);
return {
...app,
employee_code: emp?.employeeCode || "N/A",
first_name: emp?.fullName?.split(" ")[0] || "",
last_name: emp?.fullName?.split(" ").slice(1).join(" ") || "",
};
});
};
export const approveLeave = async (user: any, applicationId: number) => {
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
const leave = await LmsRepo.findLeaveForUpdate(applicationId, connection);
if (!leave) throw new Error("Leave application not found.");
// Authorization Check
if (user.role === 'MANAGER' && (leave.approver_role !== 'MANAGER' || leave.manager_approved_by !== user.employee_id)) {
throw new Error("You are not authorized to approve this leave.");
}
if (user.role === 'HR_MANAGER' && leave.approver_role !== 'HR_MANAGER') {
throw new Error("You are not authorized to approve this leave.");
}
if (user.role === 'DIRECTOR' && leave.approver_role !== 'DIRECTOR') {
throw new Error("You are not authorized to approve this leave.");
}
const newStatus = leave.status === 'PENDING_LOP' ? 'APPROVED_LOP' : 'APPROVED';
await LmsRepo.updateLeaveStatus(applicationId, newStatus, connection);
// Record who approved it in the correct column
if (user.role === 'MANAGER') {
await connection.execute(`UPDATE leave_applications SET manager_approved_by = ? WHERE application_id = ?`, [user.employee_id, applicationId]);
} else {
await connection.execute(`UPDATE leave_applications SET hr_approved_by = ? WHERE application_id = ?`, [user.employee_id, applicationId]);
}
// Deduct balance only if paid
if (newStatus === 'APPROVED') {
const currentYear = new Date(leave.date_from).getFullYear();
const currentMonth = new Date(leave.date_from).getMonth() + 1;
const alloc = await LmsRepo.findAllocForUpdate(leave.employee_id, leave.leave_type_id, currentYear, connection);
if (alloc) {
const openingBalance = Number(alloc.granted_days) - Number(alloc.used_days);
const closingBalance = openingBalance - Number(leave.number_of_days);
await LmsRepo.insertLedgerTransaction({
employee_id: leave.employee_id, leave_type_id: leave.leave_type_id, application_id: applicationId,
year: currentYear, month: currentMonth, days: leave.number_of_days,
opening_balance: openingBalance, closing_balance: closingBalance,
remarks: `Approved by: ${user.role} ID: ${user.employee_id}`
}, connection);
await LmsRepo.updateAllocationUsedDays(leave.employee_id, leave.leave_type_id, currentYear, leave.number_of_days, connection);
}
}
await connection.commit();
return { new_status: newStatus };
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
};
export const rejectLeave = async (user: any, applicationId: number) => {
const success = await LmsRepo.rejectLeaveApplication(applicationId, user.role);
if (!success) throw new Error("Leave not found, already processed, or you lack permission.");
return true;
};