Compare commits
No commits in common. "7d1b7609345728eb0a30382037430b0cbea9d0bd" and "de6d25b2566f57a326ebb24de149175318f4b87d" have entirely different histories.
7d1b760934
...
de6d25b256
347
generated/ams.ts
347
generated/ams.ts
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
577
generated/lms.ts
577
generated/lms.ts
@ -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
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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 {
|
||||||
@ -269,19 +263,4 @@ message ReviewRegRequest {
|
|||||||
int32 reviewer_id = 3;
|
int32 reviewer_id = 3;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
@ -240,37 +238,4 @@ message LedgerReportResponse {
|
|||||||
int32 report_month = 2;
|
int32 report_month = 2;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -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: "" };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -14,7 +14,7 @@ export const RegularizationServiceImplementation = {
|
|||||||
requested_check_out: req.requestedCheckOut || null,
|
requested_check_out: req.requestedCheckOut || null,
|
||||||
reason: req.reason,
|
reason: req.reason,
|
||||||
};
|
};
|
||||||
|
|
||||||
await RegService.createRegularizationRequest(dbPayload);
|
await RegService.createRegularizationRequest(dbPayload);
|
||||||
return { success: true, message: "Regularization request submitted successfully." };
|
return { success: true, message: "Regularization request submitted successfully." };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@ -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: [] };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -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
|
||||||
|
|||||||
@ -39,14 +39,4 @@ export const updateRegularizationStatus = async (id: number, status: string, rev
|
|||||||
`UPDATE attendance_regularizations SET status = ?, reviewed_by_id = ? WHERE regularization_id = ?`,
|
`UPDATE attendance_regularizations SET status = ?, reviewed_by_id = ? WHERE regularization_id = ?`,
|
||||||
[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;
|
|
||||||
};
|
};
|
||||||
@ -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) {
|
||||||
@ -374,7 +56,8 @@ export const processDailyAttendance = async (body: any) => {
|
|||||||
try {
|
try {
|
||||||
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) => {
|
||||||
@ -396,18 +80,59 @@ 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";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,7 +207,7 @@ export const processDailyAttendance = async (body: any) => {
|
|||||||
|
|
||||||
export const getEmployeeSummary = async (employeeId: number, startDate: string, endDate: string) => {
|
export const getEmployeeSummary = async (employeeId: number, startDate: string, endDate: string) => {
|
||||||
const rows = await AttendanceRepo.findEmployeeSummary(employeeId, startDate, endDate);
|
const rows = await AttendanceRepo.findEmployeeSummary(employeeId, startDate, endDate);
|
||||||
|
|
||||||
const summaryMetrics = rows.reduce((acc: any, row: any) => {
|
const summaryMetrics = rows.reduce((acc: any, row: any) => {
|
||||||
acc.total_days_tracked++;
|
acc.total_days_tracked++;
|
||||||
acc.total_hours_worked += Number(row.worked_hours || 0);
|
acc.total_hours_worked += Number(row.worked_hours || 0);
|
||||||
@ -496,7 +228,8 @@ export const getEmployeeSummary = async (employeeId: number, startDate: string,
|
|||||||
export const getDailyReport = async (req: any) => {
|
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 };
|
||||||
@ -536,7 +269,8 @@ 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: [] };
|
||||||
@ -563,7 +297,7 @@ export const getAdminRangeReport = async (req: any) => {
|
|||||||
}, { full_days: 0, half_days: 0, mispunches: 0, late_arrivals: 0 });
|
}, { full_days: 0, half_days: 0, mispunches: 0, late_arrivals: 0 });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
employee_id: emp.employeeId,
|
employee_id: emp.employeeId,
|
||||||
employee_code: emp.employeeCode,
|
employee_code: emp.employeeCode,
|
||||||
first_name: emp.fullName?.split(" ")[0] || "",
|
first_name: emp.fullName?.split(" ")[0] || "",
|
||||||
last_name: emp.fullName?.split(" ").slice(1).join(" ") || "",
|
last_name: emp.fullName?.split(" ").slice(1).join(" ") || "",
|
||||||
@ -571,7 +305,7 @@ export const getAdminRangeReport = async (req: any) => {
|
|||||||
department_name: emp.departmentName,
|
department_name: emp.departmentName,
|
||||||
company_name: emp.companyName,
|
company_name: emp.companyName,
|
||||||
designation: emp.jobTitle,
|
designation: emp.jobTitle,
|
||||||
range_metrics: individualMetrics,
|
range_metrics: individualMetrics,
|
||||||
attendance_history: logs
|
attendance_history: logs
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -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(" ") || ""
|
||||||
@ -601,13 +335,14 @@ 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);
|
||||||
|
|
||||||
const checkedInIds = new Set(attendanceRows.map((r: any) => r.employee_id));
|
const checkedInIds = new Set(attendanceRows.map((r: any) => r.employee_id));
|
||||||
|
|
||||||
const totalEmployees = employeeRoster.length;
|
const totalEmployees = employeeRoster.length;
|
||||||
const totalCheckedIn = employeeRoster.filter((emp: any) => checkedInIds.has(emp.employeeId)).length;
|
const totalCheckedIn = employeeRoster.filter((emp: any) => checkedInIds.has(emp.employeeId)).length;
|
||||||
const totalNotCheckedIn = totalEmployees - totalCheckedIn;
|
const totalNotCheckedIn = totalEmployees - totalCheckedIn;
|
||||||
@ -617,204 +352,4 @@ export const getDashboardMetrics = async (req: any) => {
|
|||||||
total_checked_in: totalCheckedIn,
|
total_checked_in: totalCheckedIn,
|
||||||
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 };
|
|
||||||
};
|
};
|
||||||
@ -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,
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export const EmployeeServiceImplementation = {
|
|||||||
employeeCode: emp.employee_code || "",
|
employeeCode: emp.employee_code || "",
|
||||||
isActive: !!emp.is_active,
|
isActive: !!emp.is_active,
|
||||||
firstName: emp.first_name || "",
|
firstName: emp.first_name || "",
|
||||||
lastName: emp.last_name || "",
|
lastName: emp.last_name || "",
|
||||||
workEmail: emp.work_email || "",
|
workEmail: emp.work_email || "",
|
||||||
jobName: emp.job_name || "",
|
jobName: emp.job_name || "",
|
||||||
departmentName: emp.department_name || "",
|
departmentName: emp.department_name || "",
|
||||||
@ -27,15 +27,15 @@ 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));
|
||||||
|
|
||||||
// Helper to format MySQL Date objects to YYYY-MM-DD strings
|
// Helper to format MySQL Date objects to YYYY-MM-DD strings
|
||||||
const formatDate = (dateVal: any) => {
|
const formatDate = (dateVal: any) => {
|
||||||
if (!dateVal) return "";
|
if (!dateVal) return "";
|
||||||
@ -80,7 +80,7 @@ export const EmployeeServiceImplementation = {
|
|||||||
state: a.state || ""
|
state: a.state || ""
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
|
|
||||||
return { success: true, data: mappedData };
|
return { success: true, data: mappedData };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("gRPC getEmployeeById Error:", error);
|
console.error("gRPC getEmployeeById Error:", error);
|
||||||
@ -129,18 +129,18 @@ export const EmployeeServiceImplementation = {
|
|||||||
reportingToId: req.reportingToId || null,
|
reportingToId: req.reportingToId || null,
|
||||||
};
|
};
|
||||||
const result = await EmployeeService.createEmployee(dbPayload);
|
const result = await EmployeeService.createEmployee(dbPayload);
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: "Employee created",
|
message: "Employee created",
|
||||||
employeeId: result.employee_id,
|
employeeId: result.employee_id,
|
||||||
employeeCode: result.employee_code
|
employeeCode: result.employee_code
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return { success: false, message: error.message, employeeId: 0, employeeCode: "" };
|
return { success: false, message: error.message, employeeId: 0, employeeCode: "" };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateEmployee(req: any) {
|
async updateEmployee(req: any) {
|
||||||
try {
|
try {
|
||||||
const dbPayload = {
|
const dbPayload = {
|
||||||
firstName: req.data?.firstName,
|
firstName: req.data?.firstName,
|
||||||
@ -161,12 +161,12 @@ export const EmployeeServiceImplementation = {
|
|||||||
reportingToId: req.data?.reportingToId || null,
|
reportingToId: req.data?.reportingToId || null,
|
||||||
isActive: req.data?.isActive,
|
isActive: req.data?.isActive,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Capture the returned message from the service layer
|
// Capture the returned message from the service layer
|
||||||
const message = await EmployeeService.updateEmployee(Number(req.id), dbPayload);
|
const message = await EmployeeService.updateEmployee(Number(req.id), dbPayload);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: message // Use the dynamic message here!
|
message: message // Use the dynamic message here!
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@ -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) {
|
||||||
|
|||||||
@ -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[] = [];
|
||||||
@ -28,4 +24,14 @@ 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];
|
||||||
};
|
};
|
||||||
@ -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)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
@ -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 };
|
||||||
@ -209,23 +203,14 @@ router.get("/api/lms/admin/reports/ob-cb", requireRole([AppRole.DIRECTOR, AppRol
|
|||||||
companyId: Number(p.get("company_id")) || 0
|
companyId: Number(p.get("company_id")) || 0
|
||||||
};
|
};
|
||||||
const res: any = await callGrpc(clients.lms.reports, "getLedgerReport", reqPayload);
|
const res: any = await callGrpc(clients.lms.reports, "getLedgerReport", reqPayload);
|
||||||
ctx.response.body = {
|
ctx.response.body = {
|
||||||
success: res.success,
|
success: res.success,
|
||||||
report_month: res.reportMonth,
|
report_month: res.reportMonth,
|
||||||
report_year: res.reportYear,
|
report_year: res.reportYear,
|
||||||
count: res.data.length,
|
count: res.data.length,
|
||||||
data: res.data
|
data: res.data
|
||||||
};
|
};
|
||||||
} 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;
|
||||||
@ -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: [] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -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);
|
||||||
|
|||||||
@ -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 }; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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; }
|
||||||
|
|||||||
@ -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
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@ -192,54 +191,4 @@ export const findLedgerReportData = async (year: number, month: number, companyI
|
|||||||
[year, year, month, year, year, month, year, companyId]
|
[year, year, month, year, year, month, year, companyId]
|
||||||
);
|
);
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -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!
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -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) => {
|
||||||
@ -10,7 +11,7 @@ export const applyForLeave = async (payload: any) => {
|
|||||||
// 1. Fetch Config & Rules
|
// 1. Fetch Config & Rules
|
||||||
const branchSettingsRaw = await LmsRepo.findWorkSettings(payload.company_id, payload.branch_id);
|
const branchSettingsRaw = await LmsRepo.findWorkSettings(payload.company_id, payload.branch_id);
|
||||||
const workSettings = { weeklyOffDays: branchSettingsRaw?.weekly_off_days || [0] };
|
const workSettings = { weeklyOffDays: branchSettingsRaw?.weekly_off_days || [0] };
|
||||||
|
|
||||||
const holidays = await LmsRepo.findHolidays(payload.company_id, currentYear, payload.branch_id);
|
const holidays = await LmsRepo.findHolidays(payload.company_id, currentYear, payload.branch_id);
|
||||||
const policy = await LmsRepo.findPolicyRule(payload.leave_type_id, payload.company_id, currentYear, payload.branch_id);
|
const policy = await LmsRepo.findPolicyRule(payload.leave_type_id, payload.company_id, currentYear, payload.branch_id);
|
||||||
|
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -71,90 +71,4 @@ export const rejectLeaveManager = async (user: any, applicationId: number) => {
|
|||||||
const success = await LmsRepo.rejectLeaveApplication(applicationId, user.employee_id);
|
const success = await LmsRepo.rejectLeaveApplication(applicationId, user.employee_id);
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user