Files
app/src/utils/czech-holidays.ts
BOHA baceb88347 feat: NAS storage for invoices/offers, code cleanup, date/time fixes
- NAS storage for created invoices (PDF via puppeteer), received invoices,
  and offers with auto-save on create/edit
- Deterministic file paths derived from DB fields (no file_path column needed)
- Separate NAS mount points: NAS_FINANCIALS_PATH, NAS_OFFERS_PATH
- Invoice language field (cs/en) stored per invoice, replaces lang modal
- Invoices list filtered by month/year matching KPI card selection
- Centralized date helpers (src/utils/date.ts) replacing all .toISOString()
  calls that returned UTC instead of local time
- Attendance project switching uses exact time (not rounded)
- Comment cleanup: removed ~100 unnecessary/Czech comments
- Removed as-any casts in orders and attendance
- Prisma migrations: add invoice language, drop received_invoices BLOB columns

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:36:39 +01:00

102 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Czech public holidays & work fund calculator.
* Port of PHP CzechHolidays class.
*/
import { localDateStr } from "./date";
const holidayCache = new Map<number, string[]>();
/** Easter Sunday using the Anonymous Gregorian algorithm */
function getEasterSunday(year: number): string {
const a = year % 19;
const b = Math.floor(year / 100);
const c = year % 100;
const d = Math.floor(b / 4);
const e = b % 4;
const f = Math.floor((b + 8) / 25);
const g = Math.floor((b - f + 1) / 3);
const h = (19 * a + b - d - g + 15) % 30;
const i = Math.floor(c / 4);
const k = c % 4;
const l = (32 + 2 * e + 2 * i - h - k) % 7;
const m = Math.floor((a + 11 * h + 22 * l) / 451);
const month = Math.floor((h + l - 7 * m + 114) / 31);
const day = ((h + l - 7 * m + 114) % 31) + 1;
return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
}
/** All Czech public holidays for a year (11 fixed + 2 Easter-based) */
export function getHolidays(year: number): string[] {
if (holidayCache.has(year)) return holidayCache.get(year)!;
const y = String(year);
const holidays = [
`${y}-01-01`, // New Year's / Restoration of Czech Independence
`${y}-05-01`, // Labour Day
`${y}-05-08`, // Victory Day
`${y}-07-05`, // Saints Cyril and Methodius Day
`${y}-07-06`, // Jan Hus Day
`${y}-09-28`, // Czech Statehood Day
`${y}-10-28`, // Czechoslovak Independence Day
`${y}-11-17`, // Freedom and Democracy Day
`${y}-12-24`, // Christmas Eve
`${y}-12-25`, // Christmas Day
`${y}-12-26`, // St. Stephen's Day
];
// Easter-based
const easterSunday = getEasterSunday(year);
const easterDate = new Date(easterSunday);
const goodFriday = new Date(easterDate);
goodFriday.setDate(goodFriday.getDate() - 2);
const easterMonday = new Date(easterDate);
easterMonday.setDate(easterMonday.getDate() + 1);
holidays.push(localDateStr(goodFriday)); // Good Friday
holidays.push(localDateStr(easterMonday)); // Easter Monday
holidays.sort();
holidayCache.set(year, holidays);
return holidays;
}
/** Check if a date string (YYYY-MM-DD) is a Czech public holiday */
export function isHoliday(dateStr: string): boolean {
const year = parseInt(dateStr.substring(0, 4), 10);
return getHolidays(year).includes(dateStr);
}
/** Business days in a month (Mon-Fri excluding public holidays) */
export function getBusinessDaysInMonth(
year: number,
month: number,
upToDay?: number,
): number {
const holidays = getHolidays(year);
let count = 0;
const daysInMonth = new Date(year, month + 1, 0).getDate();
const maxDay = upToDay ? Math.min(upToDay, daysInMonth) : daysInMonth;
for (let day = 1; day <= maxDay; day++) {
const date = new Date(year, month, day);
const dow = date.getDay();
if (dow !== 0 && dow !== 6) {
const dateStr = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
if (!holidays.includes(dateStr)) {
count++;
}
}
}
return count;
}
/** Monthly work fund in hours (business days × 8) */
export function getMonthlyWorkFund(
year: number,
month: number,
upToDay?: number,
): number {
return getBusinessDaysInMonth(year, month, upToDay) * 8;
}