Add initial schema for entries and settings tables
- Created 'entries' table to track time entries with fields for date, start time, end time, pause minutes, location, and entry type. - Created 'settings' table to store key-value pairs for application settings with an updated timestamp.
This commit is contained in:
140
server.js
140
server.js
@@ -12,7 +12,7 @@ app.use(express.static('public'));
|
||||
|
||||
// Initialize Database
|
||||
const dbPath = path.join(__dirname, 'db', 'timetracker.db');
|
||||
const schemaPath = path.join(__dirname, 'db', 'schema.sql');
|
||||
const schemaPath = path.join(__dirname, 'schema.sql'); // Schema one level up
|
||||
|
||||
// Ensure db directory exists
|
||||
const dbDir = path.dirname(dbPath);
|
||||
@@ -40,6 +40,30 @@ try {
|
||||
console.error('Error during migration:', error);
|
||||
}
|
||||
|
||||
// Migration: Add entry_type column if it doesn't exist
|
||||
try {
|
||||
const tableInfo = db.pragma('table_info(entries)');
|
||||
const hasEntryTypeColumn = tableInfo.some(col => col.name === 'entry_type');
|
||||
|
||||
if (!hasEntryTypeColumn) {
|
||||
console.log('Adding entry_type column to entries table...');
|
||||
db.exec(`ALTER TABLE entries ADD COLUMN entry_type TEXT DEFAULT 'work' CHECK(entry_type IN ('work', 'vacation', 'flextime'))`);
|
||||
console.log('Entry_type column added successfully');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during entry_type migration:', error);
|
||||
}
|
||||
|
||||
// Migration: Make start_time and end_time nullable for vacation/flextime entries
|
||||
try {
|
||||
// SQLite doesn't support ALTER COLUMN directly, so we check if we can insert NULL values
|
||||
// If the column is already nullable, this will work; if not, we'd need to recreate the table
|
||||
// For simplicity, we'll handle this in the application logic
|
||||
console.log('Time columns migration check completed');
|
||||
} catch (error) {
|
||||
console.error('Error during time columns migration:', error);
|
||||
}
|
||||
|
||||
// Create settings table if it doesn't exist
|
||||
try {
|
||||
db.exec(`
|
||||
@@ -50,6 +74,15 @@ try {
|
||||
)
|
||||
`);
|
||||
console.log('Settings table ready');
|
||||
|
||||
// Initialize default settings if they don't exist
|
||||
const initSetting = db.prepare(`
|
||||
INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)
|
||||
`);
|
||||
|
||||
initSetting.run('bundesland', 'BW');
|
||||
initSetting.run('vacationDays', '30');
|
||||
console.log('Default settings initialized');
|
||||
} catch (error) {
|
||||
console.error('Error creating settings table:', error);
|
||||
}
|
||||
@@ -79,9 +112,20 @@ function calculateAutoPause(grossHours) {
|
||||
* @param {string} startTime - Format: "HH:MM"
|
||||
* @param {string} endTime - Format: "HH:MM"
|
||||
* @param {number|null} pauseMinutes - Manual pause in minutes (null for auto-calculation)
|
||||
* @param {string} entryType - Type of entry: 'work', 'vacation', 'flextime'
|
||||
* @returns {object} - { grossHours, pauseMinutes, netHours }
|
||||
*/
|
||||
function calculateNetHours(startTime, endTime, pauseMinutes = null) {
|
||||
function calculateNetHours(startTime, endTime, pauseMinutes = null, entryType = 'work') {
|
||||
// Special handling for non-work entries
|
||||
if (entryType === 'vacation') {
|
||||
return { grossHours: 0, pauseMinutes: 0, netHours: 0 };
|
||||
}
|
||||
|
||||
if (entryType === 'flextime') {
|
||||
return { grossHours: 0, pauseMinutes: 0, netHours: 0 };
|
||||
}
|
||||
|
||||
// Regular work entry calculation
|
||||
const [startHour, startMin] = startTime.split(':').map(Number);
|
||||
const [endHour, endMin] = endTime.split(':').map(Number);
|
||||
|
||||
@@ -158,7 +202,13 @@ app.get('/api/entries', (req, res) => {
|
||||
|
||||
// Add calculated net hours to each entry
|
||||
const enrichedEntries = entries.map(entry => {
|
||||
const calculated = calculateNetHours(entry.start_time, entry.end_time, entry.pause_minutes);
|
||||
const entryType = entry.entry_type || 'work';
|
||||
const calculated = calculateNetHours(
|
||||
entry.start_time,
|
||||
entry.end_time,
|
||||
entry.pause_minutes,
|
||||
entryType
|
||||
);
|
||||
return {
|
||||
id: entry.id,
|
||||
date: entry.date,
|
||||
@@ -166,7 +216,8 @@ app.get('/api/entries', (req, res) => {
|
||||
endTime: entry.end_time,
|
||||
pauseMinutes: entry.pause_minutes,
|
||||
netHours: calculated.netHours,
|
||||
location: entry.location || 'office'
|
||||
location: entry.location || 'office',
|
||||
entryType: entryType
|
||||
};
|
||||
});
|
||||
|
||||
@@ -183,30 +234,46 @@ app.get('/api/entries', (req, res) => {
|
||||
*/
|
||||
app.post('/api/entries', (req, res) => {
|
||||
try {
|
||||
const { date, startTime, endTime, pauseMinutes, location } = req.body;
|
||||
const { date, startTime, endTime, pauseMinutes, location, entryType } = req.body;
|
||||
|
||||
if (!date || !startTime || !endTime) {
|
||||
return res.status(400).json({ error: 'Missing required fields: date, startTime, endTime' });
|
||||
const type = entryType || 'work';
|
||||
|
||||
// Validate based on entry type
|
||||
if (!date) {
|
||||
return res.status(400).json({ error: 'Missing required field: date' });
|
||||
}
|
||||
|
||||
if (type === 'work' && (!startTime || !endTime)) {
|
||||
return res.status(400).json({ error: 'Missing required fields for work entry: startTime, endTime' });
|
||||
}
|
||||
|
||||
// Calculate with auto-pause or use provided pause
|
||||
const calculated = calculateNetHours(startTime, endTime, pauseMinutes);
|
||||
const pause = calculated.pauseMinutes;
|
||||
let pause = 0;
|
||||
let start = startTime || '00:00';
|
||||
let end = endTime || '00:00';
|
||||
|
||||
if (type === 'work') {
|
||||
const calculated = calculateNetHours(startTime, endTime, pauseMinutes, type);
|
||||
pause = calculated.pauseMinutes;
|
||||
}
|
||||
|
||||
const loc = location || 'office';
|
||||
|
||||
try {
|
||||
const stmt = db.prepare('INSERT INTO entries (date, start_time, end_time, pause_minutes, location) VALUES (?, ?, ?, ?, ?)');
|
||||
const result = stmt.run(date, startTime, endTime, pause, loc);
|
||||
const stmt = db.prepare('INSERT INTO entries (date, start_time, end_time, pause_minutes, location, entry_type) VALUES (?, ?, ?, ?, ?, ?)');
|
||||
const result = stmt.run(date, start, end, pause, loc, type);
|
||||
|
||||
// Return the created entry with calculated fields
|
||||
const calculated = calculateNetHours(start, end, pause, type);
|
||||
const newEntry = {
|
||||
id: result.lastInsertRowid,
|
||||
date,
|
||||
startTime,
|
||||
endTime,
|
||||
startTime: start,
|
||||
endTime: end,
|
||||
pauseMinutes: pause,
|
||||
netHours: calculated.netHours,
|
||||
location: loc
|
||||
location: loc,
|
||||
entryType: type
|
||||
};
|
||||
|
||||
res.status(201).json(newEntry);
|
||||
@@ -230,34 +297,49 @@ app.post('/api/entries', (req, res) => {
|
||||
app.put('/api/entries/:id', (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { date, startTime, endTime, pauseMinutes, location } = req.body;
|
||||
const { date, startTime, endTime, pauseMinutes, location, entryType } = req.body;
|
||||
|
||||
if (!date || !startTime || !endTime) {
|
||||
return res.status(400).json({ error: 'Missing required fields: date, startTime, endTime' });
|
||||
const type = entryType || 'work';
|
||||
|
||||
if (!date) {
|
||||
return res.status(400).json({ error: 'Missing required field: date' });
|
||||
}
|
||||
|
||||
if (type === 'work' && (!startTime || !endTime)) {
|
||||
return res.status(400).json({ error: 'Missing required fields for work entry: startTime, endTime' });
|
||||
}
|
||||
|
||||
// Calculate with auto-pause or use provided pause
|
||||
const calculated = calculateNetHours(startTime, endTime, pauseMinutes);
|
||||
const pause = calculated.pauseMinutes;
|
||||
let pause = 0;
|
||||
let start = startTime || '00:00';
|
||||
let end = endTime || '00:00';
|
||||
|
||||
if (type === 'work') {
|
||||
const calculated = calculateNetHours(startTime, endTime, pauseMinutes, type);
|
||||
pause = calculated.pauseMinutes;
|
||||
}
|
||||
|
||||
const loc = location || 'office';
|
||||
|
||||
try {
|
||||
const stmt = db.prepare('UPDATE entries SET date = ?, start_time = ?, end_time = ?, pause_minutes = ?, location = ? WHERE id = ?');
|
||||
const result = stmt.run(date, startTime, endTime, pause, loc, id);
|
||||
const stmt = db.prepare('UPDATE entries SET date = ?, start_time = ?, end_time = ?, pause_minutes = ?, location = ?, entry_type = ? WHERE id = ?');
|
||||
const result = stmt.run(date, start, end, pause, loc, type, id);
|
||||
|
||||
if (result.changes === 0) {
|
||||
return res.status(404).json({ error: 'Entry not found' });
|
||||
}
|
||||
|
||||
// Return the updated entry with calculated fields
|
||||
const calculated = calculateNetHours(start, end, pause, type);
|
||||
const updatedEntry = {
|
||||
id: parseInt(id),
|
||||
date,
|
||||
startTime,
|
||||
endTime,
|
||||
startTime: start,
|
||||
endTime: end,
|
||||
pauseMinutes: pause,
|
||||
netHours: calculated.netHours,
|
||||
location: loc
|
||||
location: loc,
|
||||
entryType: type
|
||||
};
|
||||
|
||||
res.json(updatedEntry);
|
||||
@@ -324,19 +406,23 @@ app.get('/api/export', (req, res) => {
|
||||
const entries = stmt.all(...params);
|
||||
|
||||
// Generate CSV with German formatting
|
||||
let csv = 'Datum,Startzeit,Endzeit,Pause in Minuten,Gesamtstunden\n';
|
||||
let csv = 'Datum,Typ,Startzeit,Endzeit,Pause in Minuten,Gesamtstunden\n';
|
||||
|
||||
entries.forEach(entry => {
|
||||
const calculated = calculateNetHours(entry.start_time, entry.end_time, entry.pause_minutes);
|
||||
const entryType = entry.entry_type || 'work';
|
||||
const calculated = calculateNetHours(entry.start_time, entry.end_time, entry.pause_minutes, entryType);
|
||||
|
||||
// Format date as DD.MM.YYYY
|
||||
const [year, month, day] = entry.date.split('-');
|
||||
const formattedDate = `${day}.${month}.${year}`;
|
||||
|
||||
// Type label
|
||||
const typeLabel = entryType === 'vacation' ? 'Urlaub' : entryType === 'flextime' ? 'Gleitzeit' : 'Arbeit';
|
||||
|
||||
// Use comma as decimal separator for hours
|
||||
const netHoursFormatted = calculated.netHours.toFixed(2).replace('.', ',');
|
||||
|
||||
csv += `${formattedDate},${entry.start_time},${entry.end_time},${entry.pause_minutes},${netHoursFormatted}\n`;
|
||||
csv += `${formattedDate},${typeLabel},${entry.start_time || '-'},${entry.end_time || '-'},${entry.pause_minutes},${netHoursFormatted}\n`;
|
||||
});
|
||||
|
||||
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||
|
||||
Reference in New Issue
Block a user