feat(vacay): comp/flex leave type (#1074) + configurable-year backend (#737)

Comp/Flex days (#1074) — complete. A second leave type that does not deduct from the vacation entitlement (flextime / overtime offset). New kind column on vacay_entries, excluded from usedDays via a CASE sum; toggleEntry carries kind through service/nest/store with change-detection; desktop and mobile get a comp toolbar toggle next to the half-day one (orthogonal, so a half comp day is free); comp segments render as a diagonal hatch of the person colour, per-segment for split days (Version B). comp_used is surfaced separately in getStats. Translated into all 23 locales.

Configurable vacation year (#737) — backend only. Per-user vacay_user_settings (calendar / fiscal / anniversary), resolveYearWindow, and usedDays / compUsedDays / getStats / carry-over now computed over the leave-year [start,end) window. 'calendar' resolves byte-identically to the old date-prefix behaviour, so nothing changes for existing users and the feature is inert until the settings UI lands. Frontend (settings selector + calendar window rollover), the settings endpoint, window-aware getEntries and holiday loading are still to do — see the PR description.
This commit is contained in:
Maurice
2026-07-24 20:40:15 +02:00
committed by Maurice
parent c61203e607
commit c475e5b04c
38 changed files with 329 additions and 67 deletions
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'نصف يوم',
'vacay.modeHalfHint': 'تسجيل أنصاف أيام الإجازة',
'vacay.fullDay': 'يوم كامل',
'vacay.modeComp': 'تعويض / مرن',
'vacay.modeCompHint': 'تسجيل أيام التعويض / الوقت المرن (مجانًا — دون استخدام إجازة)',
'vacay.compHalf': 'نصف يوم تعويضي',
'vacay.entitlement': 'الاستحقاق',
'vacay.entitlementDays': 'الأيام',
'vacay.used': 'المستخدم',
+3
View File
@@ -31,6 +31,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Meio dia',
'vacay.modeHalfHint': 'Registrar meio dia de férias',
'vacay.fullDay': 'Dia inteiro',
'vacay.modeComp': 'Banco de horas',
'vacay.modeCompHint': 'Registrar dias de banco de horas (grátis — sem usar férias)',
'vacay.compHalf': 'Meio dia de banco de horas',
'vacay.entitlement': 'Direito',
'vacay.entitlementDays': 'Dias',
'vacay.used': 'Usados',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Mitja jornada',
'vacay.modeHalfHint': 'Registra mitges jornades de vacances',
'vacay.fullDay': 'Jornada completa',
'vacay.modeComp': 'Comp. / Flexi',
'vacay.modeCompHint': 'Registra dies de compensació/flexibilitat (gratis — no gasta vacances)',
'vacay.compHalf': 'Mig dia de compensació',
'vacay.entitlement': 'Dret',
'vacay.entitlementDays': 'Dies',
'vacay.used': 'Utilitzats',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Půlden',
'vacay.modeHalfHint': 'Zapsat půldenní dovolenou',
'vacay.fullDay': 'Celý den',
'vacay.modeComp': 'Náhradní volno',
'vacay.modeCompHint': 'Zapsat náhradní / pružné volno (zdarma — bez čerpání dovolené)',
'vacay.compHalf': 'Půlden náhradního volna',
'vacay.entitlement': 'Nárok',
'vacay.entitlementDays': 'Dní',
'vacay.used': 'Vyčerpáno',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Halbtag',
'vacay.modeHalfHint': 'Halbe Urlaubstage eintragen',
'vacay.fullDay': 'Ganzer Tag',
'vacay.modeComp': 'Ausgleich',
'vacay.modeCompHint': 'Ausgleichs-/Gleittage eintragen (kostenlos — kein Urlaub)',
'vacay.compHalf': 'Halber Ausgleichstag',
'vacay.entitlement': 'Urlaubsanspruch',
'vacay.entitlementDays': 'Tage',
'vacay.used': 'Weg',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Half day',
'vacay.modeHalfHint': 'Log half vacation days',
'vacay.fullDay': 'Full day',
'vacay.modeComp': 'Comp / Flex',
'vacay.modeCompHint': 'Log comp / flex days (free — no vacation used)',
'vacay.compHalf': 'Half comp day',
'vacay.entitlement': 'Entitlement',
'vacay.entitlementDays': 'Days',
'vacay.used': 'Used',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Medio día',
'vacay.modeHalfHint': 'Registrar medios días de vacaciones',
'vacay.fullDay': 'Día completo',
'vacay.modeComp': 'Compensación',
'vacay.modeCompHint': 'Registrar días de compensación/flexibles (gratis — sin usar vacaciones)',
'vacay.compHalf': 'Medio día de compensación',
'vacay.entitlement': 'Derecho',
'vacay.entitlementDays': 'Días',
'vacay.used': 'Usados',
+3
View File
@@ -31,6 +31,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Demi-journée',
'vacay.modeHalfHint': 'Enregistrer des demi-journées de congé',
'vacay.fullDay': 'Journée complète',
'vacay.modeComp': 'Récup / RTT',
'vacay.modeCompHint': 'Enregistrer les jours de récupération / RTT (gratuit — aucun congé utilisé)',
'vacay.compHalf': 'Demi-journée de récupération',
'vacay.entitlement': 'Droits',
'vacay.entitlementDays': 'Jours',
'vacay.used': 'Utilisés',
+3
View File
@@ -31,6 +31,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Μισή μέρα',
'vacay.modeHalfHint': 'Καταχώριση μισής ημέρας άδειας',
'vacay.fullDay': 'Ολόκληρη μέρα',
'vacay.modeComp': 'Ρεπό',
'vacay.modeCompHint': 'Καταχώριση ημερών ρεπό/ευέλικτου ωραρίου (δωρεάν — χωρίς χρήση αδείας)',
'vacay.compHalf': 'Μισή μέρα ρεπό',
'vacay.entitlement': 'Δικαίωμα',
'vacay.entitlementDays': 'Ημέρες',
'vacay.used': 'Χρησιμοποιημένες',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Fél nap',
'vacay.modeHalfHint': 'Fél szabadnapok rögzítése',
'vacay.fullDay': 'Egész nap',
'vacay.modeComp': 'Csúsztatás',
'vacay.modeCompHint': 'Csúsztatás / rugalmas napok rögzítése (ingyenes — nincs szabadságfelhasználás)',
'vacay.compHalf': 'Fél csúsztatásnap',
'vacay.entitlement': 'Szabadságkeret',
'vacay.entitlementDays': 'nap',
'vacay.used': 'Felhasznált',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Setengah hari',
'vacay.modeHalfHint': 'Catat setengah hari cuti',
'vacay.fullDay': 'Sehari penuh',
'vacay.modeComp': 'Pengganti / Fleksi',
'vacay.modeCompHint': 'Catat hari pengganti / fleksi (gratis — tanpa memotong cuti)',
'vacay.compHalf': 'Setengah hari pengganti',
'vacay.entitlement': 'Jatah Cuti',
'vacay.entitlementDays': 'Hari',
'vacay.used': 'Terpakai',
+3
View File
@@ -31,6 +31,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Mezza giornata',
'vacay.modeHalfHint': 'Registra mezze giornate di ferie',
'vacay.fullDay': 'Giornata intera',
'vacay.modeComp': 'Recupero / Flex',
'vacay.modeCompHint': 'Registra giorni di recupero / flex (gratis — nessuna ferie usata)',
'vacay.compHalf': 'Mezza giornata di recupero',
'vacay.entitlement': 'Disponibilità',
'vacay.entitlementDays': 'Giorni',
'vacay.used': 'Usati',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': '半休',
'vacay.modeHalfHint': '半休を登録',
'vacay.fullDay': '全休',
'vacay.modeComp': '代休・フレックス',
'vacay.modeCompHint': '代休・フレックス日を登録(休暇消費なし)',
'vacay.compHalf': '半代休',
'vacay.entitlement': '付与日数',
'vacay.entitlementDays': '日',
'vacay.used': '使用済み',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': '반차',
'vacay.modeHalfHint': '반차 등록',
'vacay.fullDay': '종일',
'vacay.modeComp': '대체휴무',
'vacay.modeCompHint': '대체/보상 휴무 등록 (무료 — 휴가 차감 없음)',
'vacay.compHalf': '반일 대체휴무',
'vacay.entitlement': '휴가 일수',
'vacay.entitlementDays': '일',
'vacay.used': '사용',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Halve dag',
'vacay.modeHalfHint': 'Halve vakantiedagen invoeren',
'vacay.fullDay': 'Hele dag',
'vacay.modeComp': 'Compensatie',
'vacay.modeCompHint': 'Compensatie-/flexdagen invoeren (gratis — geen vakantie gebruikt)',
'vacay.compHalf': 'Halve compensatiedag',
'vacay.entitlement': 'Recht',
'vacay.entitlementDays': 'Dagen',
'vacay.used': 'Gebruikt',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Pół dnia',
'vacay.modeHalfHint': 'Zapisz pół dnia urlopu',
'vacay.fullDay': 'Cały dzień',
'vacay.modeComp': 'Odbiór',
'vacay.modeCompHint': 'Zapisz dni odbioru / czasu wolnego (za darmo — bez urlopu)',
'vacay.compHalf': 'Pół dnia odbioru',
'vacay.entitlement': 'Wymiar',
'vacay.entitlementDays': 'Dni',
'vacay.used': 'Wykorzystane',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Полдня',
'vacay.modeHalfHint': 'Отмечать полдня отпуска',
'vacay.fullDay': 'Полный день',
'vacay.modeComp': 'Отгул',
'vacay.modeCompHint': 'Отмечать отгулы (бесплатно — отпуск не расходуется)',
'vacay.compHalf': 'Полдня отгула',
'vacay.entitlement': 'Право на отпуск',
'vacay.entitlementDays': 'Дни',
'vacay.used': 'Использовано',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Halvdag',
'vacay.modeHalfHint': 'Registrera halva semesterdagar',
'vacay.fullDay': 'Heldag',
'vacay.modeComp': 'Komp / Flex',
'vacay.modeCompHint': 'Registrera komp-/flexdagar (gratis — ingen semester används)',
'vacay.compHalf': 'Halv kompdag',
'vacay.entitlement': 'Rättighet',
'vacay.entitlementDays': 'Dagar',
'vacay.used': 'Använt',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Yarım gün',
'vacay.modeHalfHint': 'Yarım izin günü ekle',
'vacay.fullDay': 'Tam gün',
'vacay.modeComp': 'Serbest zaman',
'vacay.modeCompHint': 'Serbest zaman / esnek gün ekle (ücretsiz — izin kullanılmaz)',
'vacay.compHalf': 'Yarım serbest zaman günü',
'vacay.entitlement': 'Yetki',
'vacay.entitlementDays': 'Günler',
'vacay.used': 'Kullanılmış',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Пів дня',
'vacay.modeHalfHint': 'Позначати пів дня відпустки',
'vacay.fullDay': 'Повний день',
'vacay.modeComp': 'Відгул',
'vacay.modeCompHint': 'Позначати дні відгулу (безкоштовно — відпустка не використовується)',
'vacay.compHalf': 'Пів дня відгулу',
'vacay.entitlement': 'Право на відпустку',
'vacay.entitlementDays': 'Дні',
'vacay.used': 'Використано',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': 'Nửa ngày',
'vacay.modeHalfHint': 'Ghi nửa ngày nghỉ',
'vacay.fullDay': 'Cả ngày',
'vacay.modeComp': 'Nghỉ bù',
'vacay.modeCompHint': 'Ghi ngày nghỉ bù (miễn phí — không dùng phép)',
'vacay.compHalf': 'Nửa ngày nghỉ bù',
'vacay.entitlement': 'Quyền lợi',
'vacay.entitlementDays': 'Ngày',
'vacay.used': 'Đã dùng',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': '半天',
'vacay.modeHalfHint': '記錄半天假期',
'vacay.fullDay': '全天',
'vacay.modeComp': '補休 / 彈性',
'vacay.modeCompHint': '記錄補休/彈性假(免費 — 不佔用年假)',
'vacay.compHalf': '半天補休',
'vacay.entitlement': '年假額度',
'vacay.entitlementDays': '天',
'vacay.used': '已用',
+3
View File
@@ -30,6 +30,9 @@ const vacay: TranslationStrings = {
'vacay.modeHalf': '半天',
'vacay.modeHalfHint': '记录半天假期',
'vacay.fullDay': '全天',
'vacay.modeComp': '调休',
'vacay.modeCompHint': '记录调休/弹性假(免费 — 不占用年假)',
'vacay.compHalf': '半天调休',
'vacay.entitlement': '年假额度',
'vacay.entitlementDays': '天',
'vacay.used': '已用',
+3
View File
@@ -51,6 +51,9 @@ export const vacayToggleEntryRequestSchema = z.object({
target_user_id: z.union([z.number(), z.string()]).optional(),
// Half vacation days (#552): 0.5 logs a half day, 1 (or omitted) a full day.
fraction: z.union([z.literal(0.5), z.literal(1)]).optional(),
// Leave type (#1074): 'comp' logs a flex/comp day (does not touch the
// entitlement), 'vacation' (or omitted) a regular vacation day.
kind: z.enum(['vacation', 'comp']).optional(),
});
export type VacayToggleEntryRequest = z.infer<typeof vacayToggleEntryRequestSchema>;