diff --git a/functions/src/dooraccess/essl.ts b/functions/src/dooraccess/essl.ts index c70f687..8be6a88 100644 --- a/functions/src/dooraccess/essl.ts +++ b/functions/src/dooraccess/essl.ts @@ -13,6 +13,10 @@ export interface EmployeeCodeRequest { employeeCode: string; } +export interface PushLogRequest extends EmployeeCodeRequest { + attendanceDate: string; +} + export interface UpdateEmployeeExRequest { employeeCode: string; employeeName: string; @@ -190,6 +194,66 @@ function parseDeleteEmployeeResponse(soapResponse: string): string | null { return resultText; } +function createGetEmployeePunchLogsRequest(username: string, password: string, + employeeCode: string, attendanceDate: string): string | null { + const soapRequest = ` + + + + cosqclient + 3bbb58d5 + 1 + 2025-05-24 + + +`; + return soapRequest; +} + +function createDateFromTime(date: Date, timeString: string): Date { + const [hour, minute, second] = timeString.split(':').map(str => parseInt(str, 10)); + const newDate = new Date(date.getTime()); + newDate.setHours(hour); + newDate.setMinutes(minute); + newDate.setSeconds(second); + return newDate; +} + +function parseGetEmployeePunchLogsResponse(soapResponse: string, attendanceDate: string): Date[] { + const rootDate = new Date(attendanceDate); + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(soapResponse, "text/xml"); + const currentElement = xmlDoc.documentElement.firstChild as HTMLElement; + const resultText = currentElement.textContent; + const punchLogs: Date[] = []; + const parts = resultText!.split(';'); + for (const part of parts) { + try { + const logDateTime = new Date(part); + if (isNaN(logDateTime.getTime())) { + throw new Error('Invalid date format'); + } + punchLogs.push(logDateTime); + } catch { + try { + const timeParts = part.split(','); + for (const timePart of timeParts) { + try { + const logDateTime = createDateFromTime(rootDate, timePart); + punchLogs.push(logDateTime); + } catch { + continue; + } + } + } catch { + continue; + } + } + } + const sortedLogs = punchLogs.sort((a, b) => b.getTime() - a.getTime()); + return sortedLogs; +} + async function sendSoapRequest(soapRequest: string, endpoint: string) { try { const headers: any = { @@ -215,14 +279,14 @@ async function sendSoapRequest(soapRequest: string, endpoint: string) { async function getUserDetails(username: string, password: string, - employeeCode: string, endpoint: string) { + employeeCode: string, endpoint: string): Promise { const soapRequest = createGetEmployeeDetailsRequest(username, password, employeeCode); const soapResponse = await sendSoapRequest(soapRequest!, endpoint); const parsedResponse = parseGetEmployeeDetailsResponse(soapResponse); return parsedResponse; } -async function updateEmployeeEx(username: string, +async function updateUserEx(username: string, password: string, request: UpdateEmployeeExRequest, endpoint: string) { @@ -241,6 +305,16 @@ async function deleteEmplyee(username: string, return parsedResponse; } +async function getEmployeePunchLogs(username: string, + password: string, + employeeCode: string, + attendanceDate: string, endpoint: string): Promise { + const soapRequest = createGetEmployeePunchLogsRequest(username, password, employeeCode, attendanceDate); + const soapResponse = await sendSoapRequest(soapRequest!, endpoint); + const parsedResponse = parseGetEmployeePunchLogsResponse(soapResponse, attendanceDate); + return parsedResponse; +} + export const esslGetUserDetails = onRequest({ region: '#{SERVICES_RGN}#' }, async (request: Request, response: Response) => { @@ -339,7 +413,7 @@ export const esslUpdateUser = onRequest({ if (!updateEmployeeExRequest) { throw new Error('Missing request params'); } - const result = await updateEmployeeEx(username, password, updateEmployeeExRequest, endpoint); + const result = await updateUserEx(username, password, updateEmployeeExRequest, endpoint); response.send(result); } catch (error: any) { logger.error(error); @@ -398,3 +472,66 @@ export const esslDeleteUser = onRequest({ response.send(result); }); }); + +export const esslGetEmployeePunchLogs = onRequest({ + region: '#{SERVICES_RGN}#' +}, async (request: Request, response: Response) => { + return corsHandler(request, response, async () => { + let username: string | null = request.body.username as string; + let password: string | null = request.body.password as string; + let endpoint: string | null = request.body.endpoint as string; + let gymId: string | null = request.body.gymId as string; + + const pushLogRequst = request.body.params as PushLogRequest; + + if (!username) { + throw new Error('Missing username or password'); + } + username = username.trim(); + if (!password) { + if (!gymId) { + throw new Error('Missing password or gymId'); + } + // todo: Get password from gym configuration by decrypting with private key + } + password = password.trim(); + if (!pushLogRequst) { + throw new Error('Missing request params'); + } + const employeeCode = pushLogRequst.employeeCode; + if (!employeeCode) { + throw new Error('Missing employeeCode'); + } + const attendanceDate = pushLogRequst.attendanceDate; + if (!attendanceDate) { + throw new Error('Missing attendanceDate'); + } + const isValidDate = /^\d{4}-\d{2}-\d{2}$/; + if (!attendanceDate.match(isValidDate)) { + throw new Error('attendanceDate is not in the valid format YYYY-MM-DD'); + } + if (!endpoint) { + throw new Error('Missing endpoint'); + } + if (!endpoint || endpoint.trim() === '') { + throw new Error('Missing endpoint'); + } + try { + new URL(endpoint); + } catch (_) { + throw new Error('Endpoint is not a valid URI or URL'); + } + if (!endpoint.endsWith('/webservice.asmx')) { + if (endpoint.endsWith('/')) { + endpoint = endpoint.substring(0, endpoint.length - 1); + } + endpoint += '/webservice.asmx'; + } + const result = await getEmployeePunchLogs(username, + password, + employeeCode, + attendanceDate, + endpoint); + response.send(result); + }); +}); diff --git a/functions/src/dooraccess/index.ts b/functions/src/dooraccess/index.ts index 587a1da..ad7da6e 100644 --- a/functions/src/dooraccess/index.ts +++ b/functions/src/dooraccess/index.ts @@ -1 +1,4 @@ -export { esslGetUserDetails, esslUpdateUser, esslDeleteUser } from './essl'; \ No newline at end of file +export { + esslGetUserDetails, esslUpdateUser, + esslDeleteUser, esslGetEmployeePunchLogs +} from './essl'; \ No newline at end of file diff --git a/functions/src/index.ts b/functions/src/index.ts index 515cc3b..1156209 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -17,4 +17,7 @@ export { processNotificationOnCreate } from './notifications'; export * from './payments'; export { getPlaceDetails, getPlacesAutocomplete } from './places'; export { registerClient } from './users'; -export { esslGetUserDetails, esslUpdateUser, esslDeleteUser } from './dooraccess'; +export { + esslGetUserDetails, esslUpdateUser, + esslDeleteUser, esslGetEmployeePunchLogs +} from './dooraccess';