diff --git a/web/dochazka_template.xlsx b/web/dochazka_template.xlsx index 166b1c0..3a4766a 100644 Binary files a/web/dochazka_template.xlsx and b/web/dochazka_template.xlsx differ diff --git a/web/export_dochazka.py b/web/export_dochazka.py index 2ff5fcf..67f33c6 100644 --- a/web/export_dochazka.py +++ b/web/export_dochazka.py @@ -38,7 +38,7 @@ TEMPLATE_NAMES = [ 'Vörös Pavel', 'Janouš Petr', 'Dvořák Václav', - 'Kropáček Ondřej', + 'Kópáček Ondřej', 'Toman Milan', 'Hanzlík Marek', 'Vondrák Pavel', @@ -52,7 +52,7 @@ PERSON_DATA = { 'Žemlička Miroslav': {'auto': 'AUS', 'km_tkb': 40, 'km_sat': 0}, 'Franek Lukáš': {'auto': 'AUS', 'km_tkb': 40, 'km_sat': 0}, 'Dvořák Václav': {'auto': 'AUS', 'km_tkb': 0, 'km_sat': 0}, - 'Kropáček Ondřej': {'auto': 'AUS', 'km_tkb': 0, 'km_sat': 0}, # TODO confirm auto/km + 'Kópáček Ondřej': {'auto': 'AUS', 'km_tkb': 0, 'km_sat': 0}, # TODO confirm auto/km 'Toman Milan': {'auto': 'AUS', 'km_tkb': 0, 'km_sat': 0}, 'Kohl David': {'auto': 'AUS', 'km_tkb': 0, 'km_sat': 0}, 'Milisavljevič Jovica':{'auto': 'AUS', 'km_tkb': 0, 'km_sat': 0}, diff --git a/web/export_full_excel.py b/web/export_full_excel.py index a790f89..a0fabc5 100644 --- a/web/export_full_excel.py +++ b/web/export_full_excel.py @@ -26,7 +26,7 @@ CELL_COLORS = { 'A': 'FFE082', 'B': 'B0BEC5', 'D': 'FFFF00', 'D/2': 'FFF9C4', 'N': 'FF4444', 'U': '92D050', - 'O': 'FFC000', 'x': 'E0E0E0', + 'O': 'FFFF00', 'x': 'E0E0E0', # O = otcovská: same look as D } # Czech weekday names indexed by Python's weekday() (Monday=0) @@ -36,7 +36,7 @@ CZECH_DAYS = ['Pondělí', 'Úterý', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota', TKB_ROWS = { 'Pauzer Libor': 7, 'Vörös Pavel': 8, 'Janouš Petr': 9, 'Franek Lukáš': 10, 'Svoboda Daniel': 11, 'Dvořák Václav': 12, - 'Kropáček Ondřej': 13, + 'Kópáček Ondřej': 13, 'Vondrák Pavel': 14, 'Čeleda Olda': 15, 'Hanzlík Marek': 16, 'Kohl David': 17, 'Dittrich Vladimír': 18, 'Toman Milan': 19, 'Zábranský Petr': 20, 'Žemlička Miroslav': 21, 'Teslík Hynek': 22, @@ -434,7 +434,7 @@ def main(): continue worked = 0.0 vacation = 0.0 # D = 8h, D/2 = 4h — reduces person's FPD target - def collect(v): + def collect(v, is_workday): nonlocal worked, vacation if not v: return @@ -442,6 +442,11 @@ def main(): vacation += 8 elif v == 'D/2': vacation += 4 + elif v in ('N', 'O'): + # sick / paternity leave on a working day is credited as a normal + # worked day, so it neither helps nor hurts the balance (as in FpdCheck.tsx) + if is_workday: + worked += 8 else: worked += hours_for(v) for d in schedule['dayIndex']: @@ -450,13 +455,14 @@ def main(): cd = person['data'].get(str(d['idx'])) if not cd: continue + is_workday = not d.get('weekend') and (d['month'], d['day']) not in holidays is_split = bool(cd.get('dayColor') or cd.get('nightColor') or cd.get('dayValue') or cd.get('nightValue')) if is_split: - collect(cd.get('dayValue')) - collect(cd.get('nightValue')) + collect(cd.get('dayValue'), is_workday) + collect(cd.get('nightValue'), is_workday) else: - collect(cd.get('v')) + collect(cd.get('v'), is_workday) person_fpd = month_fpd - vacation diff = worked - person_fpd dst = ws.cell(row, 72) diff --git a/web/export_harmonogram.py b/web/export_harmonogram.py index 0b6bc3e..a389963 100644 --- a/web/export_harmonogram.py +++ b/web/export_harmonogram.py @@ -42,8 +42,8 @@ HALF_TIME_NAMES = {'Dittrich Vladimír'} ROSTER = [ ('Dvořák Václav (VN)', 'TKB', 1247, 'Dvořák Václav'), - # Kropáček Ondřej — part-time student, not in CIS (no personal number); add here - # if he joins full time: ('Kropáček Ondřej', 'TKB', , 'Kropáček Ondřej'), + # Kópáček Ondřej — part-time student, not in CIS (no personal number); add here + # if he joins full time: ('Kópáček Ondřej', 'TKB', , 'Kópáček Ondřej'), ('Vondrák Pavel (NN)', 'TKB', 454, 'Vondrák Pavel'), ('Čeleda Olda (NN)', 'TKB', 463, 'Čeleda Olda'), ('Hanzlík Marek (VN)', 'TKB', 1366, 'Hanzlík Marek'), diff --git a/web/public/full_template.xlsx b/web/public/full_template.xlsx index ccea9d1..427b91d 100644 Binary files a/web/public/full_template.xlsx and b/web/public/full_template.xlsx differ diff --git a/web/server.js b/web/server.js index 3c37544..230e6ab 100644 --- a/web/server.js +++ b/web/server.js @@ -94,6 +94,19 @@ function fileMeta(file) { } } +// People renamed after data already existed: fix the name wherever it is stored +// (files on disk at startup, and every incoming save from a client that still has the old name) +const PERSON_RENAMES = { 'Kropáček Ondřej': 'Kópáček Ondřej' } + +function normalizePeople(data) { + let changed = false + for (const p of (data && data.people) || []) { + const fixed = PERSON_RENAMES[p.name] + if (fixed) { p.name = fixed; changed = true } + } + return changed +} + // Pick draft or published data for a request (?view=published) function pickData(file, view) { return view === 'published' && file.published ? file.published : file.data @@ -130,10 +143,13 @@ migrateIfNeeded() // Give every existing file a published snapshot (one-time, idempotent) for (const f of listScheduleFiles()) { const file = loadScheduleFile(f.id) - if (file && ensurePublished(file)) { - saveScheduleFile(file) - console.log(`Published snapshot initialised for ${file.name} (${file.id})`) - } + if (!file) continue + let dirty = false + if (ensurePublished(file)) { dirty = true; console.log(`Published snapshot initialised for ${file.name} (${file.id})`) } + const renamedDraft = normalizePeople(file.data) + const renamedPublished = normalizePeople(file.published) + if (renamedDraft || renamedPublished) { dirty = true; console.log(`People renamed in ${file.name} (${file.id})`) } + if (dirty) saveScheduleFile(file) } // --- Diff computation --- @@ -389,6 +405,7 @@ app.post('/api/files', (req, res) => { if (!data || (!data.people && !data.stations) || !data.dayIndex) { return res.status(400).json({ error: 'Invalid schedule data (need people/stations and dayIndex)' }) } + normalizePeople(data) const now = new Date().toISOString() const fileObj = { id: crypto.randomUUID(), @@ -419,6 +436,7 @@ app.put('/api/files/:id', (req, res) => { if ((!data.people && !data.stations) || !data.dayIndex) { return res.status(400).json({ error: 'Invalid schedule data (need people/stations and dayIndex)' }) } + normalizePeople(data) file.data = data } file.modifiedAt = new Date().toISOString() @@ -443,6 +461,7 @@ app.post('/api/files/:id/publish', (req, res) => { if ((!data.people && !data.stations) || !data.dayIndex) { return res.status(400).json({ error: 'Invalid schedule data (need people/stations and dayIndex)' }) } + normalizePeople(data) file.data = data file.modifiedAt = now } diff --git a/web/src/FpdCheck.tsx b/web/src/FpdCheck.tsx index 8e4e032..61681f1 100644 --- a/web/src/FpdCheck.tsx +++ b/web/src/FpdCheck.tsx @@ -68,9 +68,9 @@ export function computeFpd( // D vacation on a working day reduces the FPD target. if (v === 'D') { if (isWorkDay) vacationHours += dailyHours } else if (v === 'D/2') { if (isWorkDay) vacationHours += dailyHours / 2 } - // N sick on a working day is credited as a normal worked day (daily hours), - // so sick leave neither helps nor hurts the FPD balance. - else if (v === 'N') { if (isWorkDay) workedHours += dailyHours } + // N (sick) and O (otcovská dovolená) on a working day are credited as a normal + // worked day (daily hours), so they neither help nor hurt the FPD balance. + else if (v === 'N' || v === 'O') { if (isWorkDay) workedHours += dailyHours } else workedHours += getHoursForValue(v) } if (isSplit) { diff --git a/web/src/cellColors.ts b/web/src/cellColors.ts index f81b740..146d04a 100644 --- a/web/src/cellColors.ts +++ b/web/src/cellColors.ts @@ -17,7 +17,7 @@ const CELL_STYLES: Record = { 'D/2': { bg: '#FFF9C4', text: '#333', label: 'Půl den dovolená' }, 'N': { bg: '#FF4444', text: '#fff', label: 'Nemocenská' }, 'U': { bg: '#92D050', text: '#333', label: 'Uzavěra' }, - 'O': { bg: '#FFC000', text: '#333', label: 'Otčovská' }, + 'O': { bg: '#FFFF00', text: '#333', label: 'Otcovská dovolená' }, // looks like D, counts like N 'x': { bg: '#E0E0E0', text: '#999', label: 'Bez zkušeností VN' }, } diff --git a/web/src/data.json b/web/src/data.json index 1d4198e..3862ff8 100644 --- a/web/src/data.json +++ b/web/src/data.json @@ -2965,7 +2965,7 @@ }, { "id": "tkb-kropacek", - "name": "Kropáček Ondřej", + "name": "Kópáček Ondřej", "group": "TKB", "data": {} },