init
This commit is contained in:
commit
7073d67489
6
.env.example
Normal file
6
.env.example
Normal file
@ -0,0 +1,6 @@
|
||||
# fleks planning production
|
||||
PSQL_HOSTNAME=
|
||||
PSQL_USERNAME=
|
||||
PSQL_PASSWORD=
|
||||
PSQL_DATABASE=
|
||||
PSQL_PORT=5432
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.vscode
|
||||
.bkup
|
||||
.env
|
||||
node_modules
|
0
README.md
Normal file
0
README.md
Normal file
32
api/auth.js
Normal file
32
api/auth.js
Normal file
@ -0,0 +1,32 @@
|
||||
export const login = async (username, password) => {
|
||||
const apiProdUrl = 'https://backend.fleks.works/api/users/login/'
|
||||
|
||||
let schema = {
|
||||
username,
|
||||
password,
|
||||
"user_type": "dashboard",
|
||||
"breakpoint": true,
|
||||
"login_from": "dashboard"
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(apiProdUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(schema)
|
||||
});
|
||||
|
||||
const data = await response.json().then((data) => {
|
||||
console.log({ data })
|
||||
return data
|
||||
});
|
||||
const token = data?.results?.token
|
||||
return token
|
||||
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return err
|
||||
}
|
||||
}
|
84
api/jobs.js
Normal file
84
api/jobs.js
Normal file
@ -0,0 +1,84 @@
|
||||
import express from 'express';
|
||||
import fetch from 'node-fetch';
|
||||
import { promises as fs } from 'fs';
|
||||
import dotenv from 'dotenv';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { convertToAmsterdamTime } from './utils.js';
|
||||
import { PauzeManager } from '../services/PauzeManager.js';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
export const getJobs = async (req, res) => {
|
||||
const apiKey = req.body.apiKey;
|
||||
const apiProdUrl = `https://api.fleks.works/v1/jobs/?limit=100000&page=1&start_date=2024-06-26&isArchived=false`;
|
||||
|
||||
try {
|
||||
const timeTableData = await fs.readFile(path.join('data', 'colorcrew_data_transformed_prod.json'), 'utf8');
|
||||
const timeTable = JSON.parse(timeTableData);
|
||||
|
||||
const response = await fetch(apiProdUrl, {
|
||||
headers: {
|
||||
'x-api-key': apiKey
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const pauzeManager = new PauzeManager();
|
||||
|
||||
const updatedJobs = await pauzeManager.updateJobsWithPauze(data.results, timeTable, convertToAmsterdamTime);
|
||||
|
||||
await pauzeManager.updateJobsInDatabase(37, updatedJobs)
|
||||
|
||||
res.json({ amountOfJobs: updatedJobs.length, updatedJobs: updatedJobs });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: 'Error fetching data' });
|
||||
}
|
||||
};
|
||||
|
||||
export const processAndUpdateJobs = async (req, res) => {
|
||||
const { tenantId, jobs } = req.body;
|
||||
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({ error: 'Tenant ID is required' });
|
||||
}
|
||||
|
||||
if (!jobs || !Array.isArray(jobs)) {
|
||||
return res.status(400).json({ error: 'Error with fetching Jobs' });
|
||||
}
|
||||
|
||||
try {
|
||||
const timeTableData = await fs.readFile(path.join(__dirname, 'data', 'colorcrew_data_transformed_prod.json'), 'utf8');
|
||||
const timeTable = JSON.parse(timeTableData);
|
||||
const pauzeManager = new PauzeManager();
|
||||
|
||||
const updatedJobs = pauzeManager.updateJobsWithPauze(jobs, timeTable, convertToAmsterdamTime);
|
||||
|
||||
const result = await pauzeManager.updateJobsInDatabase(tenantId, updatedJobs);
|
||||
|
||||
res.json({ message: 'Jobs updated successfully.', result });
|
||||
} catch (error) {
|
||||
console.error('Error updating jobs:', error);
|
||||
res.status(500).json({ error: 'Error updating jobs' });
|
||||
}
|
||||
};
|
||||
|
||||
export const removePauzeFromJobs = async (req, res) => {
|
||||
const pauzeManager = new PauzeManager();
|
||||
|
||||
try {
|
||||
await pauzeManager.removePauzeFromDatabase(8);
|
||||
res.json({ message: 'Pauze removed from job descriptions successfully.' });
|
||||
} catch (error) {
|
||||
console.error('Error removing pauze from job descriptions:', error);
|
||||
res.status(500).json({ error: 'Error removing pauze from job descriptions' });
|
||||
}
|
||||
};
|
||||
|
||||
export default router;
|
110
api/shifts.js
Normal file
110
api/shifts.js
Normal file
@ -0,0 +1,110 @@
|
||||
import { login } from './auth.js'
|
||||
import { convertToAmsterdamTime } from "./utils.js";
|
||||
|
||||
export const getShifts = async (req, res) => {
|
||||
const apiKey = req.body.apiKey;
|
||||
// const apiProdUrl = 'https://api.fleks.works/v1/shifts/?limit=100000&page=1&isArchived=false&is_approved=false&is_exported=false&is_invoiced=false&is_paid_out=true&noShiftComments=true';
|
||||
const apiProdUrl = 'https://api.fleks.works/v1/shifts/?limit=100000&page=1&isArchived=false&is_approved=false&noShiftComments=true';
|
||||
|
||||
try {
|
||||
const response = await fetch(apiProdUrl, {
|
||||
headers: {
|
||||
'x-api-key': apiKey
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const shifts = data.results.map(shift => {
|
||||
const startDate = new Date(shift.start_date);
|
||||
const endDate = new Date(shift.end_date);
|
||||
const startAmsterdamTime = convertToAmsterdamTime(startDate);
|
||||
const endAmsterdamTime = convertToAmsterdamTime(endDate);
|
||||
|
||||
return {
|
||||
...shift,
|
||||
start_time: startAmsterdamTime,
|
||||
end_time: endAmsterdamTime
|
||||
};
|
||||
});
|
||||
|
||||
res.json({ ...data, results: shifts });
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
res.status(500).json({ error: 'Error fetching data' });
|
||||
}
|
||||
}
|
||||
|
||||
export const updateShifts = async (req, res) => {
|
||||
const { config, shifts } = req.body; // Expected type = { config , shifts }
|
||||
|
||||
if (!Array.isArray(shifts)) {
|
||||
console.error('Request body should be an array of updates');
|
||||
return res.status(400).json({ error: 'Request body should be an array of updates' });
|
||||
}
|
||||
|
||||
const results = {
|
||||
success: [],
|
||||
failed: []
|
||||
};
|
||||
|
||||
const getToken = async () => {
|
||||
return await login(config?.userName, config?.passWord);
|
||||
}
|
||||
|
||||
let token = await getToken();
|
||||
|
||||
if (!token) {
|
||||
const error = `token not obtained`;
|
||||
console.log(error);
|
||||
return res.status(403).send({ error });
|
||||
}
|
||||
|
||||
const updateShift = async (update) => {
|
||||
const { uuid, break_hours } = update;
|
||||
const apiProdUrl = `https://backend.fleks.works/api/jobs/workflow-fields/${uuid}/`;
|
||||
|
||||
try {
|
||||
const response = await fetch(apiProdUrl, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `JWT ${token}`
|
||||
},
|
||||
body: JSON.stringify({ break_hours })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
console.log(`Successfully updated break hours for UUID: ${uuid}`);
|
||||
results.success.push({ uuid, message: data.message });
|
||||
} else {
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
console.log(`Token invalid. Reattempting login for UUID: ${uuid}`);
|
||||
token = await getToken();
|
||||
if (token) {
|
||||
return updateShift(update);
|
||||
} else {
|
||||
console.error(`Failed to update break hours for UUID: ${uuid}`, data);
|
||||
results.failed.push({ uuid, error: 'Failed to reauthenticate and obtain new token' });
|
||||
}
|
||||
} else {
|
||||
console.error(`Failed to update break hours for UUID: ${uuid}`, data);
|
||||
results.failed.push({ uuid, error: data.message || data });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error updating break hours for UUID: ${uuid}`, error);
|
||||
results.failed.push({ uuid, error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
for (const update of shifts) {
|
||||
await updateShift(update);
|
||||
}
|
||||
|
||||
console.log(`Finished processing updates. Success: ${results.success.length}, Failed: ${results.failed.length}`);
|
||||
|
||||
res.json(results);
|
||||
}
|
7
api/utils.js
Normal file
7
api/utils.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { utcToZonedTime, format } from 'date-fns-tz';
|
||||
|
||||
export function convertToAmsterdamTime(date) {
|
||||
const timeZone = 'Europe/Amsterdam';
|
||||
const zonedDate = utcToZonedTime(date, timeZone);
|
||||
return format(zonedDate, 'HH:mm:ss', { timeZone });
|
||||
}
|
21
certs/server.cert
Normal file
21
certs/server.cert
Normal file
@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDazCCAlOgAwIBAgIUeRKUyCrB8DwgDrYFncZqCWfRnnYwDQYJKoZIhvcNAQEL
|
||||
BQAwRTELMAkGA1UEBhMCbmwxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA1MzAyMTIwMTlaFw0yNDA2
|
||||
MjkyMTIwMTlaMEUxCzAJBgNVBAYTAm5sMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQCgO95YntjwazFQCkeiJUdnwMAwVwfgih0ZSQ0JhfoT
|
||||
L1IC3D9me5a7AO3cWEEWkYMbF2/dfcb6IhfZPD6+nzy9JMqnDTUCOPZqf64fd/tG
|
||||
j/+Adm1vZ+LH9l6p6iQ48fve3huZV5iqZnecrNKZurPfEEJTZPP0IdCDTVppBVrk
|
||||
Ayt84wpofE2t989rW8gKCWR1vG4BWb8eZ935SjaAXnWVfY+qK6zoVOjFPL7do2W6
|
||||
NcH2S0+0rTZEXIbIvoGcWBJ5PdLHQjs2QX92nQPdjMIadqGDzxEeo6gP1wuTZQyw
|
||||
/NB9GwclDyppoMPt4vaG6DrSaKjej6841I5nsNMhWxflAgMBAAGjUzBRMB0GA1Ud
|
||||
DgQWBBTsmvBk+GsgdXYKW0sVK66xJkN3+DAfBgNVHSMEGDAWgBTsmvBk+GsgdXYK
|
||||
W0sVK66xJkN3+DAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBW
|
||||
otOxOXsJ/BG8e1usaEzp0l3wB7lrUAEnufMjpQEE3MqboToIDd8xfY3PNXrSHcvy
|
||||
3hPUHVjBg0P0neyK6xZhl940UJLya6plzWnAik2WDN27o5jkG/MEeirKyiOjZlZo
|
||||
Z6l831EO4YTS4jutVcvXYaohF/nk4ERe1L5dGRIDwA9cS37CUWtAkx67eANrXWiF
|
||||
A8BAG6SxGOwJPRbsmZMW9yL/GCrvLnmr98fLa611mN720UYNjQw+d4HMT3rJJLY1
|
||||
uTJ/JUL6hLwWjoBYT7/ypRSlJFJhG9/iyak9AaeUJ6st98M/cIz4gc2uLyudaLCV
|
||||
CdmTuMfiHGJogF9WeiWR
|
||||
-----END CERTIFICATE-----
|
28
certs/server.key
Normal file
28
certs/server.key
Normal file
@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCgO95YntjwazFQ
|
||||
CkeiJUdnwMAwVwfgih0ZSQ0JhfoTL1IC3D9me5a7AO3cWEEWkYMbF2/dfcb6IhfZ
|
||||
PD6+nzy9JMqnDTUCOPZqf64fd/tGj/+Adm1vZ+LH9l6p6iQ48fve3huZV5iqZnec
|
||||
rNKZurPfEEJTZPP0IdCDTVppBVrkAyt84wpofE2t989rW8gKCWR1vG4BWb8eZ935
|
||||
SjaAXnWVfY+qK6zoVOjFPL7do2W6NcH2S0+0rTZEXIbIvoGcWBJ5PdLHQjs2QX92
|
||||
nQPdjMIadqGDzxEeo6gP1wuTZQyw/NB9GwclDyppoMPt4vaG6DrSaKjej6841I5n
|
||||
sNMhWxflAgMBAAECggEBAJJ6iGWNORZ3d4oLC7cfyyn+2/KU7P+IYteFn2RwVM8K
|
||||
+DbjLxY5ru5fCBLhnwbJmQfAIiRh4e8yEYkmeNl76mOiaZvTB/1zI1jyRbRA07WK
|
||||
1/CQ0rQATSGtiJZeFCT2meEAPEyu9kH4ECprFs8wDVTCoU9pP1aTPvF5WkgdfBxp
|
||||
kw0NyrROxO4Q+/LXbrE2PNQIKyK4JNeZvVm9YHpEl8QFOo/JpuYLWoykSScldgHr
|
||||
xEaRar4U10o+XJK+kOT7KJCxLL3Ednmi2yZOS7288Bj+98IYUaci7hcg1Z/ZN45R
|
||||
c0a5W/mzM8ptT+fhb1RczAzhEX79/H84RyAqiEWsmRUCgYEAzp5xIzRxzg4NF846
|
||||
GZxeoWsoEgw0TZmtpTINVq9NQCfoM1IMyz8lP6O51jEp8gN0xme5N6bpDRmH59xO
|
||||
lQEOHDeHRWEgUSs0PuBv4BjYux0SxZo0XJo9HskL/cRxgs30X8fqJm7C4f3/I6nT
|
||||
HELMJkQGSOJyuOOIWIq/a1L6+jsCgYEAxodzlV3DzyeQU3a+23emKfj7ugy1790f
|
||||
923dYm92g1Friug2yCBvpXklqgv17Io+L2Qs3GImveFdw4ADbPcfxbC7LINZtLCk
|
||||
YX8iZsXxmzUzEz9m0sobm64cSOn5d8NDXeJrAB9pw7g2Mm6FdSaJIli4Fv47AO8C
|
||||
73jRxCuo9F8CgYBvjvq1MGbWA54sIUwbceNiMlJDVFWVJImuLRUonaQPJLzpoL6J
|
||||
qsF41/TJ4mesZRNS4MQPeU5RpVxM4xWGvDgbIhwmaKejS7l8zX96NtAmTy9Ig9cL
|
||||
vLeNfK29yagkIQF2CaGyOJF+pb5xSgtTMfm6G3ZtOd8JVsjSTa/Gydn66wKBgHKU
|
||||
OmE6fIhSjTmejwibRYtz59S5AUgulwR2pA7rxbqEg0zoOLXIAqe+A77gqE6cesdf
|
||||
SYToIPP13ee3OkLpXaz7EwvdwyhFypl6hqBKHec2DQRO00lU3Bo9opVydEhqqbbF
|
||||
tnubpa8P4je5Ec1LMFpiWdzrXaJsT4VmdaqCiECBAoGBAJgxMGiIkKYpJU4g8J1t
|
||||
5EIIPMGUKwuKbN1ZW16zh1GwkVLXevebXlnBgPMQKjdnAxHM3OuGP9ReP1aVpo4J
|
||||
TaBvl+Fa9PtanirwnhYbAtNWoK5OqqSfXcdaLurSlyolZzirOpeNIwPJiJLAeuT5
|
||||
VWsuyC89pTi3ww6iCSFOtuqm
|
||||
-----END PRIVATE KEY-----
|
46
compare.js
Normal file
46
compare.js
Normal file
@ -0,0 +1,46 @@
|
||||
import fs from 'fs/promises';
|
||||
|
||||
const filePath1 = './data/colorcrew_data_transformed.json.bkup2';
|
||||
const filePath2 = './data/colorcrew_data_transformed_prod.json';
|
||||
|
||||
export async function compareFiles() {
|
||||
try {
|
||||
// Read the files from the hardcoded paths
|
||||
const json1 = JSON.parse(await fs.readFile(filePath1, 'utf8'));
|
||||
const json2 = JSON.parse(await fs.readFile(filePath2, 'utf8'));
|
||||
|
||||
const result = compareTimeIntervals(json1, json2);
|
||||
if (result.length > 0) {
|
||||
console.log('Differences in break hours:', JSON.stringify(result, null, 2));
|
||||
console.log(`Total differences: ${result.length}`);
|
||||
} else {
|
||||
console.log('No differences in break hours for matching start and end times.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error reading or parsing files:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to compare arrays of time intervals
|
||||
export function compareTimeIntervals(arr1, arr2) {
|
||||
const differences = [];
|
||||
|
||||
arr1.forEach((item1) => {
|
||||
const matchingItem = arr2.find(item2 => item1.start_time === item2.start_time && item1.end_time === item2.end_time);
|
||||
|
||||
if (matchingItem) {
|
||||
if (item1.break_hours !== matchingItem.break_hours) {
|
||||
differences.push({
|
||||
start_time: item1.start_time,
|
||||
end_time: item1.end_time,
|
||||
break_hours_in_first_file: item1.break_hours,
|
||||
break_hours_in_second_file: matchingItem.break_hours
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return differences;
|
||||
}
|
||||
|
||||
compareFiles();
|
54
compare.test.js
Normal file
54
compare.test.js
Normal file
@ -0,0 +1,54 @@
|
||||
import { compareTimeIntervals } from './compare.js';
|
||||
import chalk from 'chalk';
|
||||
|
||||
// Test 1: Dummy data with differences in break_hours
|
||||
const jsonWithDifferences1 = [
|
||||
{
|
||||
"start_time": "12:15",
|
||||
"end_time": "18:00",
|
||||
"break_hours": "0.25"
|
||||
}
|
||||
];
|
||||
|
||||
const jsonWithDifferences2 = [
|
||||
{
|
||||
"start_time": "12:15",
|
||||
"end_time": "18:00",
|
||||
"break_hours": "0.50" // Different break_hours
|
||||
}
|
||||
];
|
||||
|
||||
// Test 2: Dummy data with no differences in break_hours
|
||||
const jsonWithoutDifferences1 = [
|
||||
{
|
||||
"start_time": "12:15",
|
||||
"end_time": "18:00",
|
||||
"break_hours": "0.25"
|
||||
}
|
||||
];
|
||||
|
||||
const jsonWithoutDifferences2 = [
|
||||
{
|
||||
"start_time": "12:15",
|
||||
"end_time": "18:00",
|
||||
"break_hours": "0.25" // Same break_hours
|
||||
}
|
||||
];
|
||||
|
||||
// Function to run a single test with result expectations
|
||||
function runTest(testName, json1, json2, shouldFindDifference) {
|
||||
const result = compareTimeIntervals(json1, json2);
|
||||
|
||||
// Log the test name, result, and total number of differences found
|
||||
if ((result.length > 0 && shouldFindDifference) || (result.length === 0 && !shouldFindDifference)) {
|
||||
console.log(`Test: ${testName} - ${chalk.green('OK')}`);
|
||||
console.log(`Total differences: ${result.length}`);
|
||||
} else {
|
||||
console.log(`Test: ${testName} - ${chalk.red('FAILED')}`);
|
||||
console.log(`Total differences: ${result.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Run tests
|
||||
runTest("Test with Differences", jsonWithDifferences1, jsonWithDifferences2, true); // Should expect 1 difference, return OK
|
||||
runTest("Test without Differences", jsonWithoutDifferences1, jsonWithoutDifferences2, false); // Should expect 0 differences, return OK
|
1192
data/colorcrew_data.csv
Normal file
1192
data/colorcrew_data.csv
Normal file
File diff suppressed because it is too large
Load Diff
5962
data/colorcrew_data_transformed.json
Normal file
5962
data/colorcrew_data_transformed.json
Normal file
File diff suppressed because it is too large
Load Diff
5967
data/colorcrew_data_transformed.json.bkup1
Normal file
5967
data/colorcrew_data_transformed.json.bkup1
Normal file
File diff suppressed because it is too large
Load Diff
5967
data/colorcrew_data_transformed.json.bkup2
Normal file
5967
data/colorcrew_data_transformed.json.bkup2
Normal file
File diff suppressed because it is too large
Load Diff
5967
data/colorcrew_data_transformed_prod.json
Normal file
5967
data/colorcrew_data_transformed_prod.json
Normal file
File diff suppressed because it is too large
Load Diff
1895
package-lock.json
generated
Normal file
1895
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
package.json
Normal file
25
package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "colorcrew_breakhour-_transform",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "server.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "nodemon server.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"chalk": "^5.3.0",
|
||||
"cors": "^2.8.5",
|
||||
"date-fns": "^2.30.0",
|
||||
"date-fns-tz": "^2.0.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"node-cron": "^3.0.3",
|
||||
"node-fetch": "^3.3.2",
|
||||
"open": "^10.1.0",
|
||||
"pg": "^8.12.0"
|
||||
}
|
||||
}
|
332
public/index.html
Normal file
332
public/index.html
Normal file
@ -0,0 +1,332 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Break Hour Processing</title>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// const url = 'https://breakhours.fleks.c7a.nl'
|
||||
const url = 'https://localhost:22000'
|
||||
|
||||
const shiftsUrl = `${url}/api/shifts`;
|
||||
const timeTableUrl = `${url}/api/time-table`;
|
||||
const postUrl = `${url}/api/shift-update`;
|
||||
const putUrl = `${url}/api/shifts`;
|
||||
|
||||
document.getElementById('refresh-data').addEventListener('click', async () => {
|
||||
const apiKey = document.getElementById('api-key').value;
|
||||
const userName = document.getElementById('username').value;
|
||||
const passWord = document.getElementById('password').value;
|
||||
|
||||
if (!userName || !passWord || !apiKey) {
|
||||
alert('Please enter username, password and apiKey');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const shiftsData = await fetchData('POST', shiftsUrl, apiKey);
|
||||
const timeTable = await fetchData('GET', timeTableUrl);
|
||||
|
||||
const resultContainer = document.getElementById('result');
|
||||
resultContainer.innerHTML = '';
|
||||
|
||||
const summaryStats = { totalNeedsChange: 0, totalDoesNotNeedChange: 0, totalNoMatch: 0 };
|
||||
const shiftsTable = createShiftsTable(shiftsData, timeTable, summaryStats);
|
||||
const statsTable = createStatsTable(summaryStats, shiftsData.results.length);
|
||||
|
||||
resultContainer.appendChild(statsTable);
|
||||
resultContainer.appendChild(shiftsTable);
|
||||
|
||||
const selectAllCheckbox = document.getElementById('select-all');
|
||||
if (selectAllCheckbox) {
|
||||
selectAllCheckbox.addEventListener('click', () => {
|
||||
const checkboxes = document.querySelectorAll('.shift-row.needs-change input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => checkbox.checked = true);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch data:', error);
|
||||
alert('Error fetching data. Check the console for more details.');
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', async (event) => {
|
||||
if (event.target.classList.contains('post-button')) {
|
||||
const row = event.target.closest('tr');
|
||||
const shiftUuid = event.target.dataset.uuid;
|
||||
const newBreakMinutes = parseInt(row.cells[7].textContent.split(' ')[0]); // Assuming new break hours are in the format "XX min"
|
||||
const newBreakHours = minutesToHoursMinutes(newBreakMinutes); // Convert to h:mm format
|
||||
|
||||
const userName = document.getElementById('username').value;
|
||||
const passWord = document.getElementById('password').value;
|
||||
const apiKey = document.getElementById('api-key').value;
|
||||
const limit = document.getElementById('limit').value || 0;
|
||||
|
||||
if (!userName || !passWord || !apiKey) {
|
||||
alert('Please enter username, password and apiKey');
|
||||
return;
|
||||
}
|
||||
|
||||
const body = [
|
||||
{ uuid: shiftUuid, break_hours: newBreakHours, userName, passWord, limit: null }
|
||||
];
|
||||
|
||||
try {
|
||||
await fetch(putUrl, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
alert(`Break hour update for ${shiftUuid} was successful.`);
|
||||
} catch (error) {
|
||||
alert(`Break hour update for ${shiftUuid} failed.`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('queue-requests').addEventListener('click', async () => {
|
||||
|
||||
const userName = document.getElementById('username').value;
|
||||
const passWord = document.getElementById('password').value;
|
||||
const apiKey = document.getElementById('api-key').value;
|
||||
const limit = 0 // document.getElementById('limit').value || 0;
|
||||
|
||||
|
||||
if (!userName || !passWord || !apiKey) {
|
||||
alert('Please enter username, password and apiKey');
|
||||
return;
|
||||
}
|
||||
|
||||
const config = { userName, passWord, apiKey, limit }
|
||||
|
||||
const selectedCheckboxes = document.querySelectorAll('.shift-row input[type="checkbox"]:checked');
|
||||
const shifts = [];
|
||||
|
||||
selectedCheckboxes.forEach(checkbox => {
|
||||
const row = checkbox.closest('tr');
|
||||
const shiftUuid = checkbox.dataset.uuid;
|
||||
const newBreakMinutes = parseInt(row.cells[7].textContent.split(' ')[0]);
|
||||
const newBreakHours = minutesToHoursMinutes(newBreakMinutes);
|
||||
|
||||
shifts.push({ uuid: shiftUuid, break_hours: newBreakHours });
|
||||
});
|
||||
|
||||
if (shifts.length === 0) {
|
||||
alert('No shifts selected.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await fetch(putUrl, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ config, shifts })
|
||||
});
|
||||
alert('Break hour updates were successful.');
|
||||
} catch (error) {
|
||||
alert('Break hour updates failed.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function minutesToHoursMinutes(minutes) {
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const mins = minutes % 60;
|
||||
return `${hours}:${mins.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
async function fetchData(method, url, apiKey) {
|
||||
const options = {
|
||||
method: method || 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
|
||||
if (method === 'POST') {
|
||||
options.body = JSON.stringify({ apiKey })
|
||||
}
|
||||
|
||||
const response = await fetch(url, options);
|
||||
if (!response.ok) throw new Error('Network response was not ok.');
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
function createShiftsTable(shiftsData, timeTable, summaryStats) {
|
||||
const sortedShifts = shiftsData.results.sort((a, b) => a.project_title.localeCompare(b.project_title));
|
||||
|
||||
const table = document.createElement('table');
|
||||
table.innerHTML = `
|
||||
<tr>
|
||||
<th><input type="checkbox" id="select-all"></th>
|
||||
<th>Shift UUID</th>
|
||||
<th>Job ID</th>
|
||||
<th>Project Title</th>
|
||||
<th>Start Time</th>
|
||||
<th>End Time</th>
|
||||
<th>Old Break Hours (Decimal/Minutes)</th>
|
||||
<th>New Break Hours</th>
|
||||
<th>Post</th>
|
||||
</tr>`;
|
||||
|
||||
sortedShifts.forEach(shift => {
|
||||
const row = createShiftRow(shift, timeTable, summaryStats);
|
||||
table.appendChild(row);
|
||||
});
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
function createShiftRow(shift, timeTable, summaryStats) {
|
||||
const startTime = shift.start_time.slice(0, 5);
|
||||
const endTime = shift.end_time.slice(0, 5);
|
||||
const matchedTime = timeTable.find(entry =>
|
||||
entry.start_time.replace('.', ':') === startTime &&
|
||||
entry.end_time.replace('.', ':') === endTime
|
||||
);
|
||||
|
||||
const oldBreakDecimal = parseFloat(shift.break_compensation);
|
||||
const oldBreakMinutes = Math.round(oldBreakDecimal * 60);
|
||||
const newBreakMinutes = matchedTime ? Math.round(parseFloat(matchedTime.break_hours) * 60) : "N/A";
|
||||
|
||||
const statusColor = newBreakMinutes === "N/A" ? 'red' : (oldBreakMinutes === newBreakMinutes ? 'green' : 'orange');
|
||||
const statusClass = newBreakMinutes === "N/A" ? 'no-match' : (oldBreakMinutes === newBreakMinutes ? 'no-change' : 'needs-change');
|
||||
updateSummaryStats(newBreakMinutes, oldBreakMinutes, summaryStats);
|
||||
|
||||
const tr = document.createElement('tr');
|
||||
tr.classList.add('shift-row', statusClass);
|
||||
tr.innerHTML = `
|
||||
<td><input type="checkbox" data-uuid="${shift.shifts_uuid}"></td>
|
||||
<td>${shift.shifts_uuid}</td>
|
||||
<td>${shift.job_id}</td>
|
||||
<td>${shift.project_title}</td>
|
||||
<td>${startTime}</td>
|
||||
<td>${endTime}</td>
|
||||
<td>${oldBreakDecimal.toFixed(2)} / ${oldBreakMinutes} min</td>
|
||||
<td style="background-color: ${statusColor}">${newBreakMinutes} min</td>
|
||||
<td><button class="post-button" data-uuid="${shift.shifts_uuid}">Post</button></td>
|
||||
`;
|
||||
return tr;
|
||||
}
|
||||
|
||||
function updateSummaryStats(newBreakMinutes, oldBreakMinutes, summaryStats) {
|
||||
if (newBreakMinutes === "N/A") summaryStats.totalNoMatch++;
|
||||
else if (oldBreakMinutes === newBreakMinutes) summaryStats.totalDoesNotNeedChange++;
|
||||
else summaryStats.totalNeedsChange++;
|
||||
}
|
||||
|
||||
function createStatsTable(summaryStats, totalShifts) {
|
||||
const table = document.createElement('table');
|
||||
table.classList.add('stats-table');
|
||||
table.innerHTML = `<tr><th>Type</th><th>Count</th><th>Show</th></tr>
|
||||
<tr>
|
||||
<td>Needs Change</td><td>${summaryStats.totalNeedsChange}</td>
|
||||
<td><input type="checkbox" checked onclick="toggleVisibility('needs-change', this.checked)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Does Not Need Change</td><td>${summaryStats.totalDoesNotNeedChange}</td>
|
||||
<td><input type="checkbox" checked onclick="toggleVisibility('no-change', this.checked)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>No Match</td><td>${summaryStats.totalNoMatch}</td>
|
||||
<td><input type="checkbox" checked onclick="toggleVisibility('no-match', this.checked)"></td>
|
||||
</tr>
|
||||
<tr><th>Total Shifts</th><th colspan="2">${totalShifts}</th></tr>`;
|
||||
return table;
|
||||
}
|
||||
|
||||
function toggleVisibility(className, isVisible) {
|
||||
const rows = document.querySelectorAll(`.${className}`);
|
||||
rows.forEach(row => {
|
||||
row.style.display = isVisible ? '' : 'none';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.inputs-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.inputs-container input {
|
||||
flex: 1;
|
||||
padding: 5px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.button-container button {
|
||||
padding: 5px 10px;
|
||||
font-size: 1em;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
table,
|
||||
th,
|
||||
td {
|
||||
border: 1px solid black;
|
||||
border-collapse: collapse;
|
||||
margin: 10px 0;
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
td:nth-child(1),
|
||||
th:nth-child(1) {
|
||||
width: 11.11%;
|
||||
}
|
||||
|
||||
.stats-table th:nth-child(1),
|
||||
.stats-table td:nth-child(1) {
|
||||
width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Break Hour Processing</h1>
|
||||
<div class="inputs-container">
|
||||
<input type="text" id="username" placeholder="Enter your username" size="30">
|
||||
<input type="password" id="password" placeholder="Enter your password" size="30">
|
||||
<input type="text" id="api-key" placeholder="Enter your API key here" size="30">
|
||||
</div>
|
||||
|
||||
<div class="button-container">
|
||||
<button id="refresh-data">Refresh Data</button>
|
||||
<button id="queue-requests">Queue Requests</button>
|
||||
</div>
|
||||
<div id="result"></div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
16
router.js
Normal file
16
router.js
Normal file
@ -0,0 +1,16 @@
|
||||
import express from 'express';
|
||||
import { getJobs, processAndUpdateJobs, removePauzeFromJobs } from './api/jobs.js';
|
||||
import { getShifts, updateShifts } from './api/shifts.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Jobs
|
||||
router.get('/jobs', getJobs);
|
||||
router.post('/jobs/process', processAndUpdateJobs);
|
||||
router.post('/jobs/remove-pauze', removePauzeFromJobs);
|
||||
|
||||
// Shifts
|
||||
router.post('/shifts', getShifts);
|
||||
router.put('/shifts', updateShifts);
|
||||
|
||||
export default router;
|
64
server.js
Normal file
64
server.js
Normal file
@ -0,0 +1,64 @@
|
||||
import express from 'express';
|
||||
import fetch from 'node-fetch';
|
||||
import { promises as fs } from 'fs';
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
import cors from 'cors';
|
||||
import open from 'open';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import router from './router.js'
|
||||
import { login } from './api/auth.js'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const app = express();
|
||||
const port = 22000;
|
||||
|
||||
app.use(cors())
|
||||
app.use(express.json({ limit: '50mb' }));
|
||||
app.use(express.static('data'));
|
||||
app.use(express.static('public'));
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||
});
|
||||
|
||||
app.get('/api/time-table', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'data', 'colorcrew_data_transformed_prod.json'));
|
||||
});
|
||||
|
||||
///////////
|
||||
// Routes
|
||||
|
||||
// auth
|
||||
app.post('/api/login', async (req, res) => {
|
||||
const { userName, passWord } = req.body
|
||||
const token = await login(userName, passWord)
|
||||
if (token) {
|
||||
res.status(200).send({ token })
|
||||
} else {
|
||||
res.status(403).send({ error: 'token not found' })
|
||||
}
|
||||
})
|
||||
|
||||
app.use('/api', router);
|
||||
|
||||
async function startServer() {
|
||||
try {
|
||||
const options = {
|
||||
key: await fs.readFile('./certs/server.key'),
|
||||
cert: await fs.readFile('./certs/server.cert')
|
||||
};
|
||||
|
||||
https.createServer(options, app).listen(port, () => {
|
||||
console.log(`HTTPS Server running at https://localhost:${port}`);
|
||||
// open(`https://localhost:${port}`);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error starting HTTPS server:', err);
|
||||
}
|
||||
}
|
||||
|
||||
startServer();
|
207
services/PauzeManager.js
Normal file
207
services/PauzeManager.js
Normal file
@ -0,0 +1,207 @@
|
||||
import { query } from './db.js';
|
||||
|
||||
export class PauzeManager {
|
||||
constructor(identifier = 'Pauze: ', suffix = '') {
|
||||
this.identifier = identifier;
|
||||
this.suffix = suffix;
|
||||
this.pauzeRegex = new RegExp(`${this.identifier}\\d+\\s*min\\.?\\s*(--\\s*)?`);
|
||||
}
|
||||
|
||||
parseTime(timeString) {
|
||||
const [hours, minutes] = timeString.split(':').map(Number);
|
||||
return { hours, minutes };
|
||||
}
|
||||
|
||||
isMatchingTime(jobStart, jobEnd, entryStart, entryEnd) {
|
||||
const jobStartTime = this.parseTime(jobStart);
|
||||
const jobEndTime = this.parseTime(jobEnd);
|
||||
const entryStartTime = this.parseTime(entryStart);
|
||||
const entryEndTime = this.parseTime(entryEnd);
|
||||
|
||||
const jobStartMinutes = jobStartTime.hours * 60 + jobStartTime.minutes;
|
||||
const jobEndMinutes = jobEndTime.hours * 60 + jobEndTime.minutes;
|
||||
const entryStartMinutes = entryStartTime.hours * 60 + entryStartTime.minutes;
|
||||
const entryEndMinutes = entryEndTime.hours * 60 + entryEndTime.minutes;
|
||||
|
||||
const normalizedJobEndMinutes = jobEndMinutes < jobStartMinutes ? jobEndMinutes + 24 * 60 : jobEndMinutes;
|
||||
const normalizedEntryEndMinutes = entryEndMinutes < entryStartMinutes ? entryEndMinutes + 24 * 60 : entryEndMinutes;
|
||||
|
||||
return jobStartMinutes === entryStartMinutes && normalizedJobEndMinutes === normalizedEntryEndMinutes;
|
||||
}
|
||||
|
||||
convertBreakHoursToPauze(breakHours) {
|
||||
const hours = parseFloat(breakHours);
|
||||
const minutes = Math.round(hours * 60);
|
||||
return `${this.identifier}${minutes} min${this.suffix}`;
|
||||
}
|
||||
|
||||
updateFunctionDescription(description, pauzeText) {
|
||||
if (!description) return pauzeText;
|
||||
|
||||
const match = description.match(this.pauzeRegex);
|
||||
if (match) {
|
||||
const trailingDashes = match[0].includes('--') ? ' -- ' : '';
|
||||
return description.replace(this.pauzeRegex, `${pauzeText}${trailingDashes}`);
|
||||
} else {
|
||||
return `${pauzeText}${description.trim() ? ' -- ' + description : ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
// updateFunctionDescription(description, pauzeText) {
|
||||
// if (!description) return pauzeText;
|
||||
|
||||
// if (this.pauzeRegex.test(description)) {
|
||||
// return description.replace(this.pauzeRegex, pauzeText);
|
||||
// } else {
|
||||
// return `${pauzeText}${description.trim() ? ' -- ' + description : ''}`;
|
||||
// }
|
||||
// }
|
||||
|
||||
updateJobsWithPauze(jobs, timeTable, convertToAmsterdamTime) {
|
||||
return jobs.map(job => {
|
||||
const startDate = new Date(job.start_date);
|
||||
const endDate = new Date(job.end_date);
|
||||
const startAmsterdamTime = convertToAmsterdamTime(startDate);
|
||||
const endAmsterdamTime = convertToAmsterdamTime(endDate);
|
||||
|
||||
const formattedStartTime = startAmsterdamTime.slice(0, 5);
|
||||
const formattedEndTime = endAmsterdamTime.slice(0, 5);
|
||||
|
||||
const matchedTime = timeTable.find(entry =>
|
||||
this.isMatchingTime(formattedStartTime, formattedEndTime, entry.start_time, entry.end_time)
|
||||
);
|
||||
|
||||
const oldBreakDecimal = parseFloat(job.break_hours);
|
||||
const oldBreakMinutes = Math.round(oldBreakDecimal * 60);
|
||||
const newBreakMinutes = matchedTime ? Math.round(parseFloat(matchedTime.break_hours) * 60) : "N/A";
|
||||
|
||||
let updatedDescription = job.function_description;
|
||||
|
||||
if (matchedTime) {
|
||||
const pauzeText = this.convertBreakHoursToPauze(matchedTime.break_hours);
|
||||
updatedDescription = this.updateFunctionDescription(job.function_description, pauzeText);
|
||||
}
|
||||
|
||||
return {
|
||||
job_id: job.job_id,
|
||||
job_uuid: job.job_uuid,
|
||||
project_title: job.project_title,
|
||||
start_time: startAmsterdamTime,
|
||||
end_time: endAmsterdamTime,
|
||||
function_description: updatedDescription,
|
||||
break_minutes: {
|
||||
old: oldBreakMinutes,
|
||||
new: newBreakMinutes
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async updateJobsInDatabase(tenantId, jobs) {
|
||||
if (!tenantId) {
|
||||
throw new Error("Tenant ID is required");
|
||||
}
|
||||
|
||||
const updatePromises = jobs.map(job => {
|
||||
const queryText = `
|
||||
UPDATE public.jobs_jobs
|
||||
SET function_description = $2
|
||||
WHERE uuid = $3
|
||||
AND tenant_id = $1
|
||||
`;
|
||||
|
||||
const queryValues = [
|
||||
tenantId,
|
||||
job.function_description,
|
||||
job.job_uuid
|
||||
];
|
||||
|
||||
return query(queryText, queryValues);
|
||||
});
|
||||
|
||||
await Promise.all(updatePromises);
|
||||
}
|
||||
|
||||
async getAllJobs(tenantId) {
|
||||
const queryText = `
|
||||
SELECT *
|
||||
FROM public.jobs_jobs
|
||||
WHERE tenant_id = $1
|
||||
`;
|
||||
const queryValues = [tenantId];
|
||||
|
||||
const res = await query(queryText, queryValues);
|
||||
return res.rows;
|
||||
}
|
||||
|
||||
async getJobByUUID(jobUUID) {
|
||||
const queryText = `
|
||||
SELECT *
|
||||
FROM public.jobs_jobs
|
||||
WHERE uuid = $1
|
||||
`;
|
||||
const queryValues = [jobUUID];
|
||||
|
||||
const res = await query(queryText, queryValues);
|
||||
return res.rows[0];
|
||||
}
|
||||
|
||||
async getAllJobsWithPauze() {
|
||||
const queryText = `
|
||||
SELECT *
|
||||
FROM public.jobs_jobs
|
||||
WHERE function_description LIKE $1
|
||||
`;
|
||||
const queryValues = [`%${this.identifier}%`];
|
||||
|
||||
const res = await query(queryText, queryValues);
|
||||
return res.rows;
|
||||
}
|
||||
|
||||
async getJobsWithPauze(tenantId) {
|
||||
const queryText = `
|
||||
SELECT *
|
||||
FROM public.jobs_jobs
|
||||
WHERE tenant_id = $1 AND function_description LIKE '%Pauze%'
|
||||
`;
|
||||
const queryValues = [tenantId];
|
||||
|
||||
const res = await query(queryText, queryValues);
|
||||
return res.rows;
|
||||
}
|
||||
|
||||
async removePauzeFromDatabase(tenantId) {
|
||||
if (!tenantId) {
|
||||
throw new Error("Tenant ID is required");
|
||||
}
|
||||
|
||||
const jobsWithPauze = await this.getAllJobsWithPauze();
|
||||
|
||||
console.log({ jobspauze: jobsWithPauze[0] })
|
||||
|
||||
const jobsWithoutPauze = jobsWithPauze.map(job => {
|
||||
let description = job.function_description;
|
||||
|
||||
if (!description) return job;
|
||||
|
||||
// Remove the pauze text
|
||||
let updatedDescription = description.replace(this.pauzeRegex, '').trim();
|
||||
|
||||
if (updatedDescription.startsWith('--')) {
|
||||
updatedDescription = updatedDescription.slice(2).trim();
|
||||
}
|
||||
|
||||
if (updatedDescription.endsWith('--')) {
|
||||
updatedDescription = updatedDescription.slice(0, -2).trim();
|
||||
}
|
||||
|
||||
return {
|
||||
...job,
|
||||
function_description: updatedDescription
|
||||
};
|
||||
});
|
||||
|
||||
await this.updateJobsInDatabase(tenantId, jobsWithoutPauze);
|
||||
}
|
||||
|
||||
}
|
25
services/db.js
Normal file
25
services/db.js
Normal file
@ -0,0 +1,25 @@
|
||||
import pkg from 'pg';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const { Pool } = pkg;
|
||||
|
||||
const pool = new Pool({
|
||||
user: process.env.PSQL_USERNAME,
|
||||
host: process.env.PSQL_HOSTNAME,
|
||||
database: process.env.PSQL_DATABASE,
|
||||
password: process.env.PSQL_PASSWORD,
|
||||
port: process.env.PSQL_PORT,
|
||||
});
|
||||
|
||||
pool.on('connect', () => {
|
||||
console.log('Connected to the database');
|
||||
});
|
||||
|
||||
pool.on('error', (err, client) => {
|
||||
console.error('Unknown database error', err);
|
||||
process.exit(-1);
|
||||
});
|
||||
|
||||
export const query = (text, params) => pool.query(text, params);
|
36
transform.js
Normal file
36
transform.js
Normal file
@ -0,0 +1,36 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
|
||||
function formatTime(decimalTime) {
|
||||
const [hours, minutes] = decimalTime.split('.');
|
||||
return `${hours.padStart(2, '0')}:${(minutes || '0').padEnd(2, '0')}`;
|
||||
}
|
||||
|
||||
function csvToJson(filePath) {
|
||||
const data = fs.readFileSync(filePath, 'utf8');
|
||||
const rows = data.trim().split('\n');
|
||||
const result = rows.map(row => {
|
||||
const parts = row.split(',');
|
||||
const start_time = formatTime(parts[0]);
|
||||
const end_time = formatTime(parts[1]);
|
||||
const break_hours = parseFloat(parts[2]).toFixed(2);
|
||||
|
||||
return {
|
||||
start_time,
|
||||
end_time,
|
||||
break_hours
|
||||
};
|
||||
});
|
||||
return JSON.stringify(result, null, 4);
|
||||
}
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const csvFilePath = path.join(__dirname, './data/colorcrew_data.csv');
|
||||
const jsonOutput = csvToJson(csvFilePath);
|
||||
const outputFilePath = path.join(__dirname, './data/colorcrew_data_transformed.json');
|
||||
|
||||
fs.writeFileSync(outputFilePath, jsonOutput, 'utf8');
|
Loading…
Reference in New Issue
Block a user