feat: add Kropáček Ondřej + May–Jul feature work (half-cell marks, copy/paste, exports, roles)
New worker: Kropáček Ondřej (TKB, part-time student) inserted between Dvořák Václav and Vondrák Pavel in data.json, the full-month Excel template (new row 13, everything below shifted) and the docházka template (13 person rows). Not added to the harmonogram/CIS roster — he has no CIS personal number. Also checkpoints the uncommitted work deployed to production between May and July 2026: - half-cell day/night preference marks (color + text per half) - cell copy/paste (Ctrl+C/V, context menu), incl. from info rows - light/dark theme toggle, TKB people filter, 162 filter - full-month Excel export (template) and harmonogram export for CIS - docházka export: half-time workers, retired flag, contractor handling - third login role "cis" (viewer + export access) - retired flag hides a person from UI/FPD while keeping past data Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,10 @@ STRAVNE_8H = 155 # meal allowance for an 8h workday
|
||||
DAN = 0.15 # tax factor → 1.15 multiplier
|
||||
INTERNET = 500 # Janouš Petr monthly internet reimbursement
|
||||
|
||||
# Template name list — rows 8-19 of dochazka_template.xlsx.
|
||||
# Part-time people: 4h/day instead of 8h → half FPD target, D/N/O deduct 4h.
|
||||
HALF_TIME_NAMES = {'Dittrich Vladimír'}
|
||||
|
||||
# Template name list — rows 8-20 of dochazka_template.xlsx.
|
||||
# Only these people are exported, in this order.
|
||||
TEMPLATE_NAMES = [
|
||||
'Dittrich Vladimír',
|
||||
@@ -35,6 +38,7 @@ TEMPLATE_NAMES = [
|
||||
'Vörös Pavel',
|
||||
'Janouš Petr',
|
||||
'Dvořák Václav',
|
||||
'Kropáček Ondřej',
|
||||
'Toman Milan',
|
||||
'Hanzlík Marek',
|
||||
'Vondrák Pavel',
|
||||
@@ -48,6 +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
|
||||
'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},
|
||||
@@ -75,12 +80,12 @@ PERSON_DATA = {
|
||||
|
||||
# Template layout (1-based row numbers)
|
||||
TPL_FIRST = 8 # first data row
|
||||
TPL_LAST = 19 # last data row (12 people)
|
||||
TPL_CELKEM = 20
|
||||
TPL_HIDDEN1 = 22
|
||||
TPL_HIDDEN2 = 23
|
||||
TPL_IT_HDR = 24
|
||||
TPL_IT1 = 25 # first IT person
|
||||
TPL_LAST = 20 # last data row (13 people)
|
||||
TPL_CELKEM = 21
|
||||
TPL_HIDDEN1 = 23
|
||||
TPL_HIDDEN2 = 24
|
||||
TPL_IT_HDR = 25
|
||||
TPL_IT1 = 26 # first IT person
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
def get_czech_holidays(year):
|
||||
@@ -132,37 +137,54 @@ def compute(schedule, month, year):
|
||||
fpd = work_days * 8
|
||||
|
||||
tkb_results = []
|
||||
# Only include people that are in the template — in template order
|
||||
# Only include people that are in the template — in template order.
|
||||
# Retired people are skipped for months where they have no data.
|
||||
tkb_by_name = {p['name']: p for p in people if p.get('group')=='TKB'}
|
||||
tkb_people = [tkb_by_name[name] for name in TEMPLATE_NAMES if name in tkb_by_name]
|
||||
def _has_month_data(p):
|
||||
return any((p['data'].get(str(d['idx'])) or {}).get('v') for d in month_days)
|
||||
tkb_people = []
|
||||
for name in TEMPLATE_NAMES:
|
||||
p = tkb_by_name.get(name)
|
||||
if not p: continue
|
||||
if p.get('retired') and not _has_month_data(p): continue
|
||||
tkb_people.append(p)
|
||||
for person in tkb_people:
|
||||
daily_hours = 4 if person['name'] in HALF_TIME_NAMES else 8
|
||||
r=o=n=0; dov=nem=otc=0; minus_fpd=0
|
||||
sichet_12h = 0 # shifts with ≥12 h
|
||||
sichet_under_12h = 0 # shifts with <12 h
|
||||
uzavery = 0 # 'U' days (closures)
|
||||
|
||||
for d in month_days:
|
||||
v = (person['data'].get(str(d['idx'])) or {}).get('v', '')
|
||||
cell = person['data'].get(str(d['idx'])) or {}
|
||||
is_w = (not d['weekend'] and
|
||||
Date(d['year'],d['month'],d['day']) not in holidays)
|
||||
if v=='A':
|
||||
dr,do=split_day(12); r+=dr; o+=do
|
||||
sichet_12h += 1
|
||||
elif v=='B':
|
||||
nr,no,nn=split_night(12); r+=nr; o+=no; n+=nn
|
||||
sichet_12h += 1
|
||||
elif v=='D': dov+=1; minus_fpd += 8 if is_w else 0
|
||||
elif v=='N': nem+=1; minus_fpd += 8 if is_w else 0
|
||||
elif v=='O': otc+=1; minus_fpd += 8 if is_w else 0
|
||||
elif v=='U': uzavery += 1
|
||||
elif v not in ('x','U','') and v:
|
||||
try:
|
||||
h=float(v)
|
||||
if h>0:
|
||||
dr,do=split_day(h); r+=dr; o+=do
|
||||
if h >= 12: sichet_12h += 1
|
||||
else: sichet_under_12h += 1
|
||||
except (ValueError,TypeError): pass
|
||||
# Split cells carry two half-day values (dayValue + nightValue);
|
||||
# process both halves, otherwise the single 'v'.
|
||||
is_split = bool(cell.get('dayColor') or cell.get('nightColor')
|
||||
or cell.get('dayValue') or cell.get('nightValue'))
|
||||
values = ([cell.get('dayValue',''), cell.get('nightValue','')]
|
||||
if is_split else [cell.get('v','')])
|
||||
for v in values:
|
||||
if not v: continue
|
||||
if v=='A':
|
||||
dr,do=split_day(12); r+=dr; o+=do
|
||||
sichet_12h += 1
|
||||
elif v=='B':
|
||||
nr,no,nn=split_night(12); r+=nr; o+=no; n+=nn
|
||||
sichet_12h += 1
|
||||
elif v=='D': dov+=1; minus_fpd += daily_hours if is_w else 0
|
||||
elif v=='N': nem+=1; minus_fpd += daily_hours if is_w else 0
|
||||
elif v=='O': otc+=1; minus_fpd += daily_hours if is_w else 0
|
||||
elif v=='U': uzavery += 1
|
||||
elif v not in ('x','U',''):
|
||||
try:
|
||||
h=float(v)
|
||||
if h>0:
|
||||
dr,do=split_day(h); r+=dr; o+=do
|
||||
if h >= 12: sichet_12h += 1
|
||||
else: sichet_under_12h += 1
|
||||
except (ValueError,TypeError): pass
|
||||
|
||||
# PMS stored at negative dayIdx = -month
|
||||
# PMS = práce mimo směnu: hours added to R/O but NOT counted as shifts
|
||||
@@ -176,7 +198,8 @@ def compute(schedule, month, year):
|
||||
except (ValueError,TypeError): pass
|
||||
|
||||
total = r+o+n
|
||||
o_navic = total - fpd + minus_fpd
|
||||
person_fpd = work_days * daily_hours
|
||||
o_navic = total - person_fpd + minus_fpd
|
||||
r_fpd, o_fpd, n_fpd = r, o, n
|
||||
if o_navic > 0:
|
||||
pom = o_navic
|
||||
@@ -192,7 +215,7 @@ def compute(schedule, month, year):
|
||||
|
||||
# --- Financial calculations (sichtovnice.py) ---
|
||||
# Per-person FPD days (adjusted for D/N/O on working days)
|
||||
person_fpd_dny = work_days - (minus_fpd / 8)
|
||||
person_fpd_dny = work_days - (minus_fpd / daily_hours)
|
||||
|
||||
# R (Jine1): meal allowance supplement
|
||||
# 1.15 × (sichet_12h × 236 + sichet_<12h × 155 − fpd_per_person × 155)
|
||||
@@ -394,7 +417,7 @@ def generate_excel(data):
|
||||
rows = {int(r.get('r')): r for r in sd.findall(f'{{{NS}}}row')}
|
||||
|
||||
n_people = len(data['tkb'])
|
||||
n_tmpl = TPL_LAST - TPL_FIRST + 1 # 12
|
||||
n_tmpl = TPL_LAST - TPL_FIRST + 1 # 13
|
||||
|
||||
# ── ROW 3: month metadata ──
|
||||
r3 = rows[3]
|
||||
@@ -403,7 +426,7 @@ def generate_excel(data):
|
||||
set_cell_num(r3, 7, 3, data['work_days']) # G3
|
||||
set_cell_num(r3, 8, 3, data['fpd']) # H3
|
||||
|
||||
# ── DATA ROWS 8–19 ──
|
||||
# ── DATA ROWS 8–20 ──
|
||||
for i in range(n_tmpl):
|
||||
row_num = TPL_FIRST + i
|
||||
r = rows[row_num]
|
||||
|
||||
Reference in New Issue
Block a user