From 7b849fe8ded55759f6322042fc6e07b4c503a922 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 26 Aug 2026 20:01:13 +0000 Subject: [PATCH] Add unused vacation days field to reduce expected hours MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Subtract a non-negative integer of unused vacation days (default 0) from expected work time at 8 hours per day. Co-authored-by: César Ordaz --- app.js | 19 ++++++++++++++++++- index.html | 12 ++++++++++++ worktime.js | 25 ++++++++++++++++++++++--- worktime.test.js | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 4 deletions(-) diff --git a/app.js b/app.js index cb86587..7ee2e53 100644 --- a/app.js +++ b/app.js @@ -6,6 +6,7 @@ var endInput = document.getElementById("end-date"); var hoursInput = document.getElementById("worked-h"); var minutesInput = document.getElementById("worked-m"); + var vacationInput = document.getElementById("unused-vacation"); var errorEl = document.getElementById("form-error"); var daysEl = document.getElementById("working-days"); var expectedEl = document.getElementById("expected-hours"); @@ -29,6 +30,7 @@ if (!endInput.value) endInput.value = todayIso(); if (!hoursInput.value) hoursInput.value = "0"; if (!minutesInput.value) minutesInput.value = "0"; + if (!vacationInput.value) vacationInput.value = "0"; } function showError(message) { @@ -38,10 +40,13 @@ function render() { var parts = Worktime.parseDurationParts(hoursInput.value, minutesInput.value); + var vacation = Worktime.parseNonNegativeInt(vacationInput.value); var result = Worktime.calculate( startInput.value, endInput.value, - parts ? parts.hmm : "invalid" + parts ? parts.hmm : "invalid", + undefined, + vacation == null ? "invalid" : vacation ); if (!result.ok && result.error === "range") { @@ -64,6 +69,16 @@ return; } + if (vacation == null || (!result.ok && result.error === "vacation")) { + showError("Usa un número entero de días, sin negativos."); + daysEl.textContent = result.days != null ? String(result.days) : "—"; + expectedEl.textContent = Worktime.formatHmm(result.expected); + differenceEl.textContent = "—"; + differenceEl.classList.remove("is-behind"); + dailyEl.textContent = "—"; + return; + } + showError(""); daysEl.textContent = String(result.days); expectedEl.textContent = Worktime.formatHmm(result.expected); @@ -86,4 +101,6 @@ hoursInput.addEventListener("change", render); minutesInput.addEventListener("input", render); minutesInput.addEventListener("change", render); + vacationInput.addEventListener("input", render); + vacationInput.addEventListener("change", render); })(); diff --git a/index.html b/index.html index 3e6466e..301b936 100644 --- a/index.html +++ b/index.html @@ -59,6 +59,18 @@

Horas laborales

+
+ + +
diff --git a/worktime.js b/worktime.js index 64a5b01..80143e6 100644 --- a/worktime.js +++ b/worktime.js @@ -70,6 +70,19 @@ return days * perDay; } + /** Non-negative integer field. Empty → 0. Rejects decimals and negatives. */ + function parseNonNegativeInt(raw) { + if (raw == null || raw === "") return 0; + if (typeof raw === "number") { + if (!isFinite(raw) || raw < 0 || Math.floor(raw) !== raw) return null; + return raw; + } + var s = String(raw).trim(); + if (s === "") return 0; + if (!/^\d+$/.test(s)) return null; + return Number(s); + } + /** Hours + minutes fields (Android numeric keypad). Empty → 0. Minutes must be 0–59. */ function parseDurationParts(hoursRaw, minutesRaw) { function asInt(raw) { @@ -106,18 +119,23 @@ return sign + h + ":" + String(min).padStart(2, "0"); } - function calculate(startIso, endIso, workedText, holidays) { + function calculate(startIso, endIso, workedText, holidays, unusedVacationDays) { var days = countWorkingDays(startIso, endIso, holidays); var worked = parseHmm(workedText); + var vacation = parseNonNegativeInt(unusedVacationDays); if (days == null) { return { ok: false, error: "range", days: 0, expected: null, worked: worked, difference: null, daily: null }; } if (worked == null) { - return { ok: false, error: "hours", days: days, expected: days * HOURS_PER_DAY, worked: null, difference: null, daily: null }; + var expectedOnHoursError = vacation == null ? days * HOURS_PER_DAY : (days - vacation) * HOURS_PER_DAY; + return { ok: false, error: "hours", days: days, expected: expectedOnHoursError, worked: null, difference: null, daily: null }; + } + if (vacation == null) { + return { ok: false, error: "vacation", days: days, expected: days * HOURS_PER_DAY, worked: worked, difference: null, daily: null }; } - var expected = days * HOURS_PER_DAY; + var expected = (days - vacation) * HOURS_PER_DAY; var difference = worked - expected; var daily = days === 0 ? null : worked / days; return { ok: true, error: null, days: days, expected: expected, worked: worked, difference: difference, daily: daily }; @@ -131,6 +149,7 @@ expectedHours: expectedHours, parseHmm: parseHmm, parseDurationParts: parseDurationParts, + parseNonNegativeInt: parseNonNegativeInt, formatHmm: formatHmm, calculate: calculate, }; diff --git a/worktime.test.js b/worktime.test.js index e0f271a..924c961 100644 --- a/worktime.test.js +++ b/worktime.test.js @@ -97,3 +97,35 @@ test("zero working days does not divide by zero", () => { assert.equal(r.days, 0); assert.equal(r.daily, null); }); + +test("unused vacation days default to 0 and subtract 8 h each from expected", () => { + const none = W.calculate("2025-01-02", "2025-01-03", "16:00"); + assert.equal(none.ok, true); + assert.equal(none.expected, 16); + assert.equal(none.difference, 0); + + const one = W.calculate("2025-01-02", "2025-01-03", "16:00", undefined, 1); + assert.equal(one.ok, true); + assert.equal(one.days, 2); + assert.equal(one.expected, 8); + assert.equal(one.difference, 8); + assert.equal(one.daily, 8); + + const empty = W.calculate("2025-01-02", "2025-01-03", "16:00", undefined, ""); + assert.equal(empty.ok, true); + assert.equal(empty.expected, 16); +}); + +test("unused vacation days reject negatives and non-integers", () => { + assert.equal(W.parseNonNegativeInt(""), 0); + assert.equal(W.parseNonNegativeInt("0"), 0); + assert.equal(W.parseNonNegativeInt("3"), 3); + assert.equal(W.parseNonNegativeInt(-1), null); + assert.equal(W.parseNonNegativeInt("-1"), null); + assert.equal(W.parseNonNegativeInt("1.5"), null); + assert.equal(W.parseNonNegativeInt("abc"), null); + + const r = W.calculate("2025-01-02", "2025-01-03", "16:00", undefined, "-1"); + assert.equal(r.ok, false); + assert.equal(r.error, "vacation"); +});