mantenimento-app 2.4.9 → 2.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app.js +105 -25
- package/backend/calculate-model.js +3 -1
- package/frontend/public/app.js +105 -25
- package/frontend/public/index.html +33 -13
- package/frontend/public/styles.css +45 -0
- package/package.json +1 -1
package/app.js
CHANGED
|
@@ -31,6 +31,7 @@ const defaultExpenseItems = [
|
|
|
31
31
|
const EXPENSE_DETAIL_MAX_CHARS = 560;
|
|
32
32
|
const EXPENSE_DETAIL_MAX_LINES = 10;
|
|
33
33
|
const EXPENSE_DETAIL_MAX_ROWS = 8;
|
|
34
|
+
const EXPENSE_DETAIL_NOTE_SEPARATOR = "\n---\n";
|
|
34
35
|
|
|
35
36
|
const QUOTA_MANTENIMENTO_PERC = 35;
|
|
36
37
|
|
|
@@ -256,6 +257,7 @@ const defaultExpenseItems = [
|
|
|
256
257
|
expenseDetailColDue: "Scadenza",
|
|
257
258
|
expenseDetailAddRow: "Aggiungi riga",
|
|
258
259
|
expenseDetailRemoveRow: "Rimuovi riga",
|
|
260
|
+
expenseDetailFreeTextPlaceholder: "Note libere aggiuntive...",
|
|
259
261
|
expenseDetailCharsRemaining: "Caratteri rimanenti: {count}",
|
|
260
262
|
expenseRemoveTitle: "Rimuovi voce spesa",
|
|
261
263
|
expenseRemoveBtn: "Rimuovi",
|
|
@@ -291,6 +293,8 @@ const defaultExpenseItems = [
|
|
|
291
293
|
firstHomeLocativeValueHint: "Valore locativo mensile della casa assegnata, usato per valorizzare il beneficio economico implicito.",
|
|
292
294
|
firstHomeMortgageAmountLabel: "Rata mutuo mensile ({currency})",
|
|
293
295
|
firstHomeMortgageAmountHint: "Importo mensile complessivo della rata del mutuo prima casa.",
|
|
296
|
+
firstHomeMortgageExpiryLabel: "Scadenza mutuo",
|
|
297
|
+
firstHomeMortgageExpiryHint: "Data prevista di estinzione del mutuo prima casa.",
|
|
294
298
|
firstHomeAssignedToLabel: "Casa assegnata a",
|
|
295
299
|
firstHomeAssignedToHint: "Seleziona il coniuge a cui e ceduta la prima casa.",
|
|
296
300
|
firstHomeAssignedToNone: "Nessuna cessione",
|
|
@@ -313,6 +317,7 @@ const defaultExpenseItems = [
|
|
|
313
317
|
pdfPrimaryHomeNotDeclared: "Non dichiarato",
|
|
314
318
|
pdfPrimaryHomeAssignedTo: "Assegnata a",
|
|
315
319
|
pdfPrimaryHomeMonthlyAmount: "Rata mensile",
|
|
320
|
+
pdfPrimaryHomeExpiryDate: "Scadenza mutuo",
|
|
316
321
|
pdfPrimaryHomeSplit: "Ripartizione mutuo",
|
|
317
322
|
pdfPrimaryHomeAppliedOnlyColl: "Considerato solo se casa ceduta al collocatario.",
|
|
318
323
|
pdfExtraordinaryRow: "Spese straordinarie (quota mensile da annuo)",
|
|
@@ -623,6 +628,7 @@ const defaultExpenseItems = [
|
|
|
623
628
|
expenseDetailColDue: "Due date",
|
|
624
629
|
expenseDetailAddRow: "Add row",
|
|
625
630
|
expenseDetailRemoveRow: "Remove row",
|
|
631
|
+
expenseDetailFreeTextPlaceholder: "Additional free notes...",
|
|
626
632
|
expenseDetailCharsRemaining: "Remaining characters: {count}",
|
|
627
633
|
expenseRemoveTitle: "Remove expense item",
|
|
628
634
|
expenseRemoveBtn: "Remove",
|
|
@@ -658,6 +664,8 @@ const defaultExpenseItems = [
|
|
|
658
664
|
firstHomeLocativeValueHint: "Monthly rental value of the assigned home, used to value the implicit economic benefit.",
|
|
659
665
|
firstHomeMortgageAmountLabel: "Monthly mortgage payment ({currency})",
|
|
660
666
|
firstHomeMortgageAmountHint: "Total monthly amount of the primary-home mortgage payment.",
|
|
667
|
+
firstHomeMortgageExpiryLabel: "Mortgage expiry date",
|
|
668
|
+
firstHomeMortgageExpiryHint: "Expected payoff date of the primary-home mortgage.",
|
|
661
669
|
firstHomeAssignedToLabel: "Home assigned to",
|
|
662
670
|
firstHomeAssignedToHint: "Select which spouse receives assignment of the primary home.",
|
|
663
671
|
firstHomeAssignedToNone: "No assignment",
|
|
@@ -680,6 +688,7 @@ const defaultExpenseItems = [
|
|
|
680
688
|
pdfPrimaryHomeNotDeclared: "Not declared",
|
|
681
689
|
pdfPrimaryHomeAssignedTo: "Assigned to",
|
|
682
690
|
pdfPrimaryHomeMonthlyAmount: "Monthly payment",
|
|
691
|
+
pdfPrimaryHomeExpiryDate: "Mortgage expiry date",
|
|
683
692
|
pdfPrimaryHomeSplit: "Mortgage split",
|
|
684
693
|
pdfPrimaryHomeAppliedOnlyColl: "Counted only when the home is assigned to the custodial parent.",
|
|
685
694
|
pdfExtraordinaryRow: "Extraordinary expenses (monthly share from yearly)",
|
|
@@ -1234,6 +1243,8 @@ const defaultExpenseItems = [
|
|
|
1234
1243
|
const hintPrimaCasaValoreLocativo = document.getElementById("hintPrimaCasaValoreLocativo");
|
|
1235
1244
|
const lblPrimaCasaMutuoImporto = document.getElementById("lblPrimaCasaMutuoImporto");
|
|
1236
1245
|
const hintPrimaCasaMutuoImporto = document.getElementById("hintPrimaCasaMutuoImporto");
|
|
1246
|
+
const lblPrimaCasaMutuoScadenza = document.getElementById("lblPrimaCasaMutuoScadenza");
|
|
1247
|
+
const hintPrimaCasaMutuoScadenza = document.getElementById("hintPrimaCasaMutuoScadenza");
|
|
1237
1248
|
const lblPrimaCasaAssegnataA = document.getElementById("lblPrimaCasaAssegnataA");
|
|
1238
1249
|
const hintPrimaCasaAssegnataA = document.getElementById("hintPrimaCasaAssegnataA");
|
|
1239
1250
|
const lblPrimaCasaMutuoPerc1 = document.getElementById("lblPrimaCasaMutuoPerc1");
|
|
@@ -1299,6 +1310,8 @@ const defaultExpenseItems = [
|
|
|
1299
1310
|
if (hintPrimaCasaValoreLocativo) hintPrimaCasaValoreLocativo.title = tr("firstHomeLocativeValueHint");
|
|
1300
1311
|
if (lblPrimaCasaMutuoImporto) lblPrimaCasaMutuoImporto.textContent = msg("firstHomeMortgageAmountLabel", { currency: currentCurrency });
|
|
1301
1312
|
if (hintPrimaCasaMutuoImporto) hintPrimaCasaMutuoImporto.title = tr("firstHomeMortgageAmountHint");
|
|
1313
|
+
if (lblPrimaCasaMutuoScadenza) lblPrimaCasaMutuoScadenza.textContent = tr("firstHomeMortgageExpiryLabel");
|
|
1314
|
+
if (hintPrimaCasaMutuoScadenza) hintPrimaCasaMutuoScadenza.title = tr("firstHomeMortgageExpiryHint");
|
|
1302
1315
|
if (lblPrimaCasaAssegnataA) lblPrimaCasaAssegnataA.textContent = tr("firstHomeAssignedToLabel");
|
|
1303
1316
|
if (hintPrimaCasaAssegnataA) hintPrimaCasaAssegnataA.title = tr("firstHomeAssignedToHint");
|
|
1304
1317
|
if (lblPrimaCasaMutuoPerc1) lblPrimaCasaMutuoPerc1.textContent = tr("firstHomeSplitLabel");
|
|
@@ -1421,6 +1434,18 @@ const defaultExpenseItems = [
|
|
|
1421
1434
|
return `${short} ${currentCurrency}`;
|
|
1422
1435
|
}
|
|
1423
1436
|
|
|
1437
|
+
function formatIsoDate(value) {
|
|
1438
|
+
const iso = String(value || "").trim();
|
|
1439
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(iso)) return "";
|
|
1440
|
+
const d = new Date(`${iso}T00:00:00`);
|
|
1441
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
1442
|
+
return new Intl.DateTimeFormat(getCurrentLocale(), {
|
|
1443
|
+
year: "numeric",
|
|
1444
|
+
month: "2-digit",
|
|
1445
|
+
day: "2-digit"
|
|
1446
|
+
}).format(d);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1424
1449
|
function escapeHtml(value) {
|
|
1425
1450
|
return String(value || "")
|
|
1426
1451
|
.replaceAll("&", "&")
|
|
@@ -1471,25 +1496,48 @@ const defaultExpenseItems = [
|
|
|
1471
1496
|
syncExpenseDetailTableFromStore(textarea);
|
|
1472
1497
|
}
|
|
1473
1498
|
|
|
1474
|
-
function
|
|
1475
|
-
const
|
|
1499
|
+
function parseExpenseDetailPayload(raw) {
|
|
1500
|
+
const source = String(raw || "").trim();
|
|
1501
|
+
let tablePart = source;
|
|
1502
|
+
let notePart = "";
|
|
1503
|
+
const sepIdx = source.indexOf(EXPENSE_DETAIL_NOTE_SEPARATOR);
|
|
1504
|
+
if (sepIdx >= 0) {
|
|
1505
|
+
tablePart = source.slice(0, sepIdx).trim();
|
|
1506
|
+
notePart = source.slice(sepIdx + EXPENSE_DETAIL_NOTE_SEPARATOR.length).trim();
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
const tableLines = tablePart
|
|
1476
1510
|
.split(/\r?\n/)
|
|
1477
1511
|
.map((line) => line.trim())
|
|
1478
1512
|
.filter(Boolean);
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1513
|
+
const rows = [];
|
|
1514
|
+
const looseNoteLines = [];
|
|
1515
|
+
tableLines.forEach((line) => {
|
|
1516
|
+
const cols = line.split("|").map((part) => part.trim());
|
|
1517
|
+
if (cols.length >= 2) {
|
|
1518
|
+
rows.push({
|
|
1519
|
+
what: cols[0] || "",
|
|
1520
|
+
amount: cols[1] || "",
|
|
1521
|
+
due: cols.slice(2).join(" | ") || ""
|
|
1522
|
+
});
|
|
1523
|
+
} else {
|
|
1524
|
+
looseNoteLines.push(line);
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
const resolvedNote = [notePart, looseNoteLines.join("\n")]
|
|
1529
|
+
.filter(Boolean)
|
|
1530
|
+
.join(notePart && looseNoteLines.length ? "\n" : "")
|
|
1531
|
+
.trim();
|
|
1532
|
+
|
|
1533
|
+
return {
|
|
1534
|
+
rows: (rows.length ? rows : [{ what: "", amount: "", due: "" }]).slice(0, EXPENSE_DETAIL_MAX_ROWS),
|
|
1535
|
+
note: resolvedNote
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
function parseExpenseDetailRows(raw) {
|
|
1540
|
+
return parseExpenseDetailPayload(raw).rows;
|
|
1493
1541
|
}
|
|
1494
1542
|
|
|
1495
1543
|
function sanitizeExpenseDetailCell(value) {
|
|
@@ -1510,7 +1558,14 @@ const defaultExpenseItems = [
|
|
|
1510
1558
|
return compact.map((row) => `${row.what} | ${row.amount} | ${row.due}`.trim()).join("\n");
|
|
1511
1559
|
}
|
|
1512
1560
|
|
|
1513
|
-
function
|
|
1561
|
+
function serializeExpenseDetailPayload(rows, note) {
|
|
1562
|
+
const tablePart = serializeExpenseDetailRows(rows);
|
|
1563
|
+
const safeNote = String(note || "").trim();
|
|
1564
|
+
if (!safeNote) return tablePart;
|
|
1565
|
+
return `${tablePart}${tablePart ? EXPENSE_DETAIL_NOTE_SEPARATOR : ""}${safeNote}`;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
function buildExpenseDetailTableHtml(textareaId, rows, note = "") {
|
|
1514
1569
|
const safeRows = (Array.isArray(rows) && rows.length ? rows : [{ what: "", amount: "", due: "" }])
|
|
1515
1570
|
.slice(0, EXPENSE_DETAIL_MAX_ROWS);
|
|
1516
1571
|
const rowsHtml = safeRows.map((row) => {
|
|
@@ -1535,6 +1590,7 @@ const defaultExpenseItems = [
|
|
|
1535
1590
|
<tbody>${rowsHtml}</tbody>
|
|
1536
1591
|
</table>
|
|
1537
1592
|
<button type="button" class="btn-secondary spese-detail-add-row" data-row-add="${textareaId}">${escapeHtml(tr("expenseDetailAddRow"))}</button>
|
|
1593
|
+
<textarea class="spese-detail-note" data-detail-note-for="${textareaId}" rows="2" maxlength="360" placeholder="${escapeHtml(tr("expenseDetailFreeTextPlaceholder"))}">${escapeHtml(note || "")}</textarea>
|
|
1538
1594
|
</div>`;
|
|
1539
1595
|
}
|
|
1540
1596
|
|
|
@@ -1552,7 +1608,9 @@ const defaultExpenseItems = [
|
|
|
1552
1608
|
const textareaId = String(tableWrap.getAttribute("data-detail-table") || "");
|
|
1553
1609
|
const textarea = textareaId ? document.getElementById(textareaId) : null;
|
|
1554
1610
|
if (!textarea) return;
|
|
1555
|
-
const
|
|
1611
|
+
const noteEl = tableWrap.querySelector(`textarea[data-detail-note-for='${textareaId}']`);
|
|
1612
|
+
const note = String(noteEl && noteEl.value ? noteEl.value : "");
|
|
1613
|
+
const serialized = serializeExpenseDetailPayload(readExpenseDetailRowsFromTable(tableWrap), note);
|
|
1556
1614
|
textarea.value = serialized.slice(0, EXPENSE_DETAIL_MAX_CHARS);
|
|
1557
1615
|
updateExpenseDetailCounter(textarea);
|
|
1558
1616
|
}
|
|
@@ -1562,10 +1620,12 @@ const defaultExpenseItems = [
|
|
|
1562
1620
|
const host = document.getElementById(`${textarea.id}TableHost`);
|
|
1563
1621
|
if (!host) return;
|
|
1564
1622
|
const existing = host.querySelector(`[data-detail-table='${textarea.id}']`);
|
|
1565
|
-
const
|
|
1623
|
+
const existingNote = existing ? String(existing.querySelector(`textarea[data-detail-note-for='${textarea.id}']`)?.value || "") : "";
|
|
1624
|
+
const existingSerialized = serializeExpenseDetailPayload(existing ? readExpenseDetailRowsFromTable(existing) : [], existingNote);
|
|
1566
1625
|
const storeSerialized = String(textarea.value || "").trim();
|
|
1567
1626
|
if (existing && existingSerialized === storeSerialized) return;
|
|
1568
|
-
|
|
1627
|
+
const payload = parseExpenseDetailPayload(storeSerialized);
|
|
1628
|
+
host.innerHTML = buildExpenseDetailTableHtml(textarea.id, payload.rows, payload.note);
|
|
1569
1629
|
syncExpenseDetailStoreFromTable(host.querySelector(`[data-detail-table='${textarea.id}']`));
|
|
1570
1630
|
}
|
|
1571
1631
|
|
|
@@ -3724,6 +3784,7 @@ const defaultExpenseItems = [
|
|
|
3724
3784
|
primaCasaMutuoEnabled: firstHome.enabled ? 1 : 0,
|
|
3725
3785
|
primaCasaValoreLocativo: num("primaCasaValoreLocativo"),
|
|
3726
3786
|
primaCasaMutuoImporto: firstHome.amount,
|
|
3787
|
+
primaCasaMutuoScadenza: firstHome.expiry,
|
|
3727
3788
|
primaCasaAssegnataA: firstHome.assignedTo,
|
|
3728
3789
|
primaCasaMutuoPerc1: firstHome.share1,
|
|
3729
3790
|
straordAnn1: num("straordAnn1"),
|
|
@@ -3765,6 +3826,8 @@ const defaultExpenseItems = [
|
|
|
3765
3826
|
const aFam2 = Number(payload.aFam2 || 0);
|
|
3766
3827
|
const primaCasaMutuoEnabled = Number(payload.primaCasaMutuoEnabled || 0) > 0;
|
|
3767
3828
|
const primaCasaMutuoImporto = Math.max(0, Number(payload.primaCasaMutuoImporto || 0));
|
|
3829
|
+
const primaCasaMutuoScadenzaRaw = String(payload.primaCasaMutuoScadenza || "").trim();
|
|
3830
|
+
const primaCasaMutuoScadenza = /^\d{4}-\d{2}-\d{2}$/.test(primaCasaMutuoScadenzaRaw) ? primaCasaMutuoScadenzaRaw : "";
|
|
3768
3831
|
const primaCasaAssegnataA = (String(payload.primaCasaAssegnataA || "") === "1" || String(payload.primaCasaAssegnataA || "") === "2")
|
|
3769
3832
|
? String(payload.primaCasaAssegnataA)
|
|
3770
3833
|
: "";
|
|
@@ -3904,7 +3967,7 @@ const defaultExpenseItems = [
|
|
|
3904
3967
|
quotaDiretta1, quotaDiretta2,
|
|
3905
3968
|
saldo1, saldo2,
|
|
3906
3969
|
assegnoBaseDa1a2, assegnoBaseDa2a1,
|
|
3907
|
-
primaCasaMutuoEnabled, primaCasaMutuoImporto, primaCasaAssegnataA, primaCasaValoreLocativo,
|
|
3970
|
+
primaCasaMutuoEnabled, primaCasaMutuoImporto, primaCasaMutuoScadenza, primaCasaAssegnataA, primaCasaValoreLocativo,
|
|
3908
3971
|
primaCasaMutuoPerc1, primaCasaMutuoPerc2,
|
|
3909
3972
|
primaCasaConsidered, primaCasaTransfer1to2, primaCasaTransfer2to1,
|
|
3910
3973
|
compensativeBenefits,
|
|
@@ -4008,16 +4071,19 @@ const defaultExpenseItems = [
|
|
|
4008
4071
|
function getFirstHomeMortgageInput() {
|
|
4009
4072
|
const enabled = !!document.getElementById("primaCasaMutuoEnabled")?.checked;
|
|
4010
4073
|
const amount = Math.max(0, num("primaCasaMutuoImporto"));
|
|
4074
|
+
const expiryRaw = String(document.getElementById("primaCasaMutuoScadenza")?.value || "").trim();
|
|
4075
|
+
const expiry = /^\d{4}-\d{2}-\d{2}$/.test(expiryRaw) ? expiryRaw : "";
|
|
4011
4076
|
const assignedToRaw = String(document.getElementById("primaCasaAssegnataA")?.value || "").trim();
|
|
4012
4077
|
const assignedTo = (assignedToRaw === "1" || assignedToRaw === "2") ? assignedToRaw : "";
|
|
4013
4078
|
const share1 = Math.min(100, Math.max(0, num("primaCasaMutuoPerc1")));
|
|
4014
4079
|
const share2 = 100 - share1;
|
|
4015
|
-
return { enabled, amount, assignedTo, share1, share2 };
|
|
4080
|
+
return { enabled, amount, expiry, assignedTo, share1, share2 };
|
|
4016
4081
|
}
|
|
4017
4082
|
|
|
4018
4083
|
function updateFirstHomeMortgageUi() {
|
|
4019
4084
|
const enabledEl = document.getElementById("primaCasaMutuoEnabled");
|
|
4020
4085
|
const amountEl = document.getElementById("primaCasaMutuoImporto");
|
|
4086
|
+
const expiryEl = document.getElementById("primaCasaMutuoScadenza");
|
|
4021
4087
|
const assignedEl = document.getElementById("primaCasaAssegnataA");
|
|
4022
4088
|
const shareEl = document.getElementById("primaCasaMutuoPerc1");
|
|
4023
4089
|
const splitInfoEl = document.getElementById("primaCasaMutuoSplitInfo");
|
|
@@ -4033,6 +4099,7 @@ const defaultExpenseItems = [
|
|
|
4033
4099
|
|
|
4034
4100
|
const isEnabled = !!enabledEl.checked;
|
|
4035
4101
|
amountEl.disabled = !isEnabled;
|
|
4102
|
+
if (expiryEl) expiryEl.disabled = !isEnabled;
|
|
4036
4103
|
assignedEl.disabled = !isEnabled;
|
|
4037
4104
|
shareEl.disabled = !isEnabled;
|
|
4038
4105
|
if (splitWrapEl) splitWrapEl.classList.toggle("is-disabled", !isEnabled);
|
|
@@ -4563,6 +4630,7 @@ const defaultExpenseItems = [
|
|
|
4563
4630
|
setVal("assegnoFam2", Number(payload.aFam2 || 0));
|
|
4564
4631
|
setChecked("primaCasaMutuoEnabled", Number(payload.primaCasaMutuoEnabled || 0) > 0);
|
|
4565
4632
|
setVal("primaCasaMutuoImporto", Number(payload.primaCasaMutuoImporto || 0));
|
|
4633
|
+
setVal("primaCasaMutuoScadenza", String(payload.primaCasaMutuoScadenza || ""));
|
|
4566
4634
|
setVal("primaCasaAssegnataA", (String(payload.primaCasaAssegnataA || "") === "1" || String(payload.primaCasaAssegnataA || "") === "2") ? String(payload.primaCasaAssegnataA) : "");
|
|
4567
4635
|
setVal("primaCasaMutuoPerc1", Math.min(100, Math.max(0, Number((payload.primaCasaMutuoPerc1 === undefined ? 50 : payload.primaCasaMutuoPerc1) || 0))));
|
|
4568
4636
|
setVal("straordAnn1", Number(payload.straordAnn1 || 0));
|
|
@@ -4737,6 +4805,7 @@ const defaultExpenseItems = [
|
|
|
4737
4805
|
? `
|
|
4738
4806
|
<tr><td>${tr("pdfPrimaryHomeAssignedTo")}</td><td>${primaryHomeAssignedLabel}</td></tr>
|
|
4739
4807
|
<tr><td>${tr("pdfPrimaryHomeMonthlyAmount")}</td><td>${eur(m.primaCasaMutuoImporto || 0)}</td></tr>
|
|
4808
|
+
<tr><td>${tr("pdfPrimaryHomeExpiryDate")}</td><td>${escapeHtml(formatIsoDate(m.primaCasaMutuoScadenza) || tr("pdfPrimaryHomeNotDeclared"))}</td></tr>
|
|
4740
4809
|
<tr><td>${tr("pdfPrimaryHomeSplit")}</td><td>${c1NameEsc} ${(m.primaCasaMutuoPerc1 || 0).toFixed(0)}% · ${c2NameEsc} ${(m.primaCasaMutuoPerc2 || 0).toFixed(0)}%</td></tr>
|
|
4741
4810
|
<tr><td>${tr("pdfPrimaryHomeAppliedOnlyColl")}</td><td>${m.primaCasaConsidered ? "OK" : tr("pdfPrimaryHomeNotDeclared")}</td></tr>`
|
|
4742
4811
|
: `<tr><td>${tr("pdfPrimaryHomeMortgage")}</td><td>${tr("pdfPrimaryHomeNotDeclared")}</td></tr>`;
|
|
@@ -5255,6 +5324,7 @@ const defaultExpenseItems = [
|
|
|
5255
5324
|
? `
|
|
5256
5325
|
<tr><td>${tr("pdfPrimaryHomeAssignedTo")}</td><td>${primaryHomeAssignedLabel}</td></tr>
|
|
5257
5326
|
<tr><td>${tr("pdfPrimaryHomeMonthlyAmount")}</td><td>${eur(m.primaCasaMutuoImporto || 0)}</td></tr>
|
|
5327
|
+
<tr><td>${tr("pdfPrimaryHomeExpiryDate")}</td><td>${escapeHtml(formatIsoDate(m.primaCasaMutuoScadenza) || tr("pdfPrimaryHomeNotDeclared"))}</td></tr>
|
|
5258
5328
|
<tr><td>${tr("pdfPrimaryHomeSplit")}</td><td>${c1NameEsc} ${(m.primaCasaMutuoPerc1 || 0).toFixed(0)}% · ${c2NameEsc} ${(m.primaCasaMutuoPerc2 || 0).toFixed(0)}%</td></tr>
|
|
5259
5329
|
<tr><td>${tr("pdfPrimaryHomeAppliedOnlyColl")}</td><td>${m.primaCasaConsidered ? "OK" : tr("pdfPrimaryHomeNotDeclared")}</td></tr>`
|
|
5260
5330
|
: `<tr><td>${tr("pdfPrimaryHomeMortgage")}</td><td>${tr("pdfPrimaryHomeNotDeclared")}</td></tr>`;
|
|
@@ -6163,6 +6233,7 @@ ${scenarioLab.length ? `
|
|
|
6163
6233
|
primaCasaMutuoEnabled: document.getElementById("primaCasaMutuoEnabled")?.checked ? 1 : 0,
|
|
6164
6234
|
primaCasaValoreLocativo: num("primaCasaValoreLocativo"),
|
|
6165
6235
|
primaCasaMutuoImporto: num("primaCasaMutuoImporto"),
|
|
6236
|
+
primaCasaMutuoScadenza: String(document.getElementById("primaCasaMutuoScadenza")?.value || ""),
|
|
6166
6237
|
primaCasaAssegnataA: String(document.getElementById("primaCasaAssegnataA")?.value || ""),
|
|
6167
6238
|
primaCasaMutuoPerc1: num("primaCasaMutuoPerc1"),
|
|
6168
6239
|
straordAnn1: num("straordAnn1"),
|
|
@@ -6290,8 +6361,10 @@ ${scenarioLab.length ? `
|
|
|
6290
6361
|
});
|
|
6291
6362
|
const firstHomeEnabled = document.getElementById("primaCasaMutuoEnabled");
|
|
6292
6363
|
const firstHomeAssigned = document.getElementById("primaCasaAssegnataA");
|
|
6364
|
+
const firstHomeExpiry = document.getElementById("primaCasaMutuoScadenza");
|
|
6293
6365
|
if (firstHomeEnabled) firstHomeEnabled.checked = !!firstHomeEnabled.defaultChecked;
|
|
6294
6366
|
if (firstHomeAssigned) firstHomeAssigned.value = "";
|
|
6367
|
+
if (firstHomeExpiry) firstHomeExpiry.value = firstHomeExpiry.defaultValue || "";
|
|
6295
6368
|
permanenceCalendarState.byMonth = {};
|
|
6296
6369
|
speseConvivenzaAutoMode = true;
|
|
6297
6370
|
selectedScenarioIdx = -1;
|
|
@@ -6446,10 +6519,11 @@ ${scenarioLab.length ? `
|
|
|
6446
6519
|
const textareaId = String(addRowBtn.getAttribute("data-row-add") || "");
|
|
6447
6520
|
const textarea = textareaId ? document.getElementById(textareaId) : null;
|
|
6448
6521
|
if (!textarea) return;
|
|
6449
|
-
const
|
|
6522
|
+
const payload = parseExpenseDetailPayload(textarea.value);
|
|
6523
|
+
const rows = payload.rows;
|
|
6450
6524
|
if (rows.length < EXPENSE_DETAIL_MAX_ROWS) {
|
|
6451
6525
|
rows.push({ what: "", amount: "", due: "" });
|
|
6452
|
-
textarea.value =
|
|
6526
|
+
textarea.value = serializeExpenseDetailPayload(rows, payload.note);
|
|
6453
6527
|
syncExpenseDetailTableFromStore(textarea);
|
|
6454
6528
|
refreshExpenseDetailButtonState();
|
|
6455
6529
|
}
|
|
@@ -6491,6 +6565,12 @@ ${scenarioLab.length ? `
|
|
|
6491
6565
|
const tableWrap = e.target.closest("[data-detail-table]");
|
|
6492
6566
|
syncExpenseDetailStoreFromTable(tableWrap);
|
|
6493
6567
|
refreshExpenseDetailButtonState();
|
|
6568
|
+
return;
|
|
6569
|
+
}
|
|
6570
|
+
if (e.target && e.target.matches(".spese-detail-note")) {
|
|
6571
|
+
const tableWrap = e.target.closest("[data-detail-table]");
|
|
6572
|
+
syncExpenseDetailStoreFromTable(tableWrap);
|
|
6573
|
+
refreshExpenseDetailButtonState();
|
|
6494
6574
|
}
|
|
6495
6575
|
});
|
|
6496
6576
|
|
|
@@ -6630,7 +6710,7 @@ ${scenarioLab.length ? `
|
|
|
6630
6710
|
}
|
|
6631
6711
|
updateModeUi();
|
|
6632
6712
|
renderAll();
|
|
6633
|
-
} else if (e.target && (e.target.id === "primaCasaMutuoEnabled" || e.target.id === "primaCasaAssegnataA")) {
|
|
6713
|
+
} else if (e.target && (e.target.id === "primaCasaMutuoEnabled" || e.target.id === "primaCasaAssegnataA" || e.target.id === "primaCasaMutuoScadenza")) {
|
|
6634
6714
|
updateFirstHomeMortgageUi();
|
|
6635
6715
|
renderAll();
|
|
6636
6716
|
}
|
|
@@ -35,6 +35,8 @@ function calculateModel(input) {
|
|
|
35
35
|
const primaCasaMutuoEnabled = toNumber(input.primaCasaMutuoEnabled) > 0;
|
|
36
36
|
const primaCasaValoreLocativo = Math.max(0, toNumber(input.primaCasaValoreLocativo));
|
|
37
37
|
const primaCasaMutuoImporto = Math.max(0, toNumber(input.primaCasaMutuoImporto));
|
|
38
|
+
const primaCasaMutuoScadenzaRaw = String(input.primaCasaMutuoScadenza || '').trim();
|
|
39
|
+
const primaCasaMutuoScadenza = /^\d{4}-\d{2}-\d{2}$/.test(primaCasaMutuoScadenzaRaw) ? primaCasaMutuoScadenzaRaw : '';
|
|
38
40
|
const primaCasaAssegnataA = String(input.primaCasaAssegnataA || '');
|
|
39
41
|
const rawMutuoPerc1 = input.primaCasaMutuoPerc1 === undefined ? 50 : input.primaCasaMutuoPerc1;
|
|
40
42
|
const primaCasaMutuoPerc1 = clamp(toNumber(rawMutuoPerc1), 0, 100);
|
|
@@ -163,7 +165,7 @@ function calculateModel(input) {
|
|
|
163
165
|
quotaDiretta1, quotaDiretta2,
|
|
164
166
|
saldo1, saldo2,
|
|
165
167
|
assegnoBaseDa1a2, assegnoBaseDa2a1,
|
|
166
|
-
primaCasaMutuoEnabled, primaCasaValoreLocativo, primaCasaMutuoImporto,
|
|
168
|
+
primaCasaMutuoEnabled, primaCasaValoreLocativo, primaCasaMutuoImporto, primaCasaMutuoScadenza,
|
|
167
169
|
primaCasaAssegnataA: assigned,
|
|
168
170
|
primaCasaMutuoPerc1, primaCasaMutuoPerc2,
|
|
169
171
|
primaCasaConsidered, primaCasaTransfer1to2, primaCasaTransfer2to1,
|
package/frontend/public/app.js
CHANGED
|
@@ -31,6 +31,7 @@ const defaultExpenseItems = [
|
|
|
31
31
|
const EXPENSE_DETAIL_MAX_CHARS = 560;
|
|
32
32
|
const EXPENSE_DETAIL_MAX_LINES = 10;
|
|
33
33
|
const EXPENSE_DETAIL_MAX_ROWS = 8;
|
|
34
|
+
const EXPENSE_DETAIL_NOTE_SEPARATOR = "\n---\n";
|
|
34
35
|
|
|
35
36
|
const QUOTA_MANTENIMENTO_PERC = 35;
|
|
36
37
|
|
|
@@ -256,6 +257,7 @@ const defaultExpenseItems = [
|
|
|
256
257
|
expenseDetailColDue: "Scadenza",
|
|
257
258
|
expenseDetailAddRow: "Aggiungi riga",
|
|
258
259
|
expenseDetailRemoveRow: "Rimuovi riga",
|
|
260
|
+
expenseDetailFreeTextPlaceholder: "Note libere aggiuntive...",
|
|
259
261
|
expenseDetailCharsRemaining: "Caratteri rimanenti: {count}",
|
|
260
262
|
expenseRemoveTitle: "Rimuovi voce spesa",
|
|
261
263
|
expenseRemoveBtn: "Rimuovi",
|
|
@@ -291,6 +293,8 @@ const defaultExpenseItems = [
|
|
|
291
293
|
firstHomeLocativeValueHint: "Valore locativo mensile della casa assegnata, usato per valorizzare il beneficio economico implicito.",
|
|
292
294
|
firstHomeMortgageAmountLabel: "Rata mutuo mensile ({currency})",
|
|
293
295
|
firstHomeMortgageAmountHint: "Importo mensile complessivo della rata del mutuo prima casa.",
|
|
296
|
+
firstHomeMortgageExpiryLabel: "Scadenza mutuo",
|
|
297
|
+
firstHomeMortgageExpiryHint: "Data prevista di estinzione del mutuo prima casa.",
|
|
294
298
|
firstHomeAssignedToLabel: "Casa assegnata a",
|
|
295
299
|
firstHomeAssignedToHint: "Seleziona il coniuge a cui e ceduta la prima casa.",
|
|
296
300
|
firstHomeAssignedToNone: "Nessuna cessione",
|
|
@@ -313,6 +317,7 @@ const defaultExpenseItems = [
|
|
|
313
317
|
pdfPrimaryHomeNotDeclared: "Non dichiarato",
|
|
314
318
|
pdfPrimaryHomeAssignedTo: "Assegnata a",
|
|
315
319
|
pdfPrimaryHomeMonthlyAmount: "Rata mensile",
|
|
320
|
+
pdfPrimaryHomeExpiryDate: "Scadenza mutuo",
|
|
316
321
|
pdfPrimaryHomeSplit: "Ripartizione mutuo",
|
|
317
322
|
pdfPrimaryHomeAppliedOnlyColl: "Considerato solo se casa ceduta al collocatario.",
|
|
318
323
|
pdfExtraordinaryRow: "Spese straordinarie (quota mensile da annuo)",
|
|
@@ -623,6 +628,7 @@ const defaultExpenseItems = [
|
|
|
623
628
|
expenseDetailColDue: "Due date",
|
|
624
629
|
expenseDetailAddRow: "Add row",
|
|
625
630
|
expenseDetailRemoveRow: "Remove row",
|
|
631
|
+
expenseDetailFreeTextPlaceholder: "Additional free notes...",
|
|
626
632
|
expenseDetailCharsRemaining: "Remaining characters: {count}",
|
|
627
633
|
expenseRemoveTitle: "Remove expense item",
|
|
628
634
|
expenseRemoveBtn: "Remove",
|
|
@@ -658,6 +664,8 @@ const defaultExpenseItems = [
|
|
|
658
664
|
firstHomeLocativeValueHint: "Monthly rental value of the assigned home, used to value the implicit economic benefit.",
|
|
659
665
|
firstHomeMortgageAmountLabel: "Monthly mortgage payment ({currency})",
|
|
660
666
|
firstHomeMortgageAmountHint: "Total monthly amount of the primary-home mortgage payment.",
|
|
667
|
+
firstHomeMortgageExpiryLabel: "Mortgage expiry date",
|
|
668
|
+
firstHomeMortgageExpiryHint: "Expected payoff date of the primary-home mortgage.",
|
|
661
669
|
firstHomeAssignedToLabel: "Home assigned to",
|
|
662
670
|
firstHomeAssignedToHint: "Select which spouse receives assignment of the primary home.",
|
|
663
671
|
firstHomeAssignedToNone: "No assignment",
|
|
@@ -680,6 +688,7 @@ const defaultExpenseItems = [
|
|
|
680
688
|
pdfPrimaryHomeNotDeclared: "Not declared",
|
|
681
689
|
pdfPrimaryHomeAssignedTo: "Assigned to",
|
|
682
690
|
pdfPrimaryHomeMonthlyAmount: "Monthly payment",
|
|
691
|
+
pdfPrimaryHomeExpiryDate: "Mortgage expiry date",
|
|
683
692
|
pdfPrimaryHomeSplit: "Mortgage split",
|
|
684
693
|
pdfPrimaryHomeAppliedOnlyColl: "Counted only when the home is assigned to the custodial parent.",
|
|
685
694
|
pdfExtraordinaryRow: "Extraordinary expenses (monthly share from yearly)",
|
|
@@ -1234,6 +1243,8 @@ const defaultExpenseItems = [
|
|
|
1234
1243
|
const hintPrimaCasaValoreLocativo = document.getElementById("hintPrimaCasaValoreLocativo");
|
|
1235
1244
|
const lblPrimaCasaMutuoImporto = document.getElementById("lblPrimaCasaMutuoImporto");
|
|
1236
1245
|
const hintPrimaCasaMutuoImporto = document.getElementById("hintPrimaCasaMutuoImporto");
|
|
1246
|
+
const lblPrimaCasaMutuoScadenza = document.getElementById("lblPrimaCasaMutuoScadenza");
|
|
1247
|
+
const hintPrimaCasaMutuoScadenza = document.getElementById("hintPrimaCasaMutuoScadenza");
|
|
1237
1248
|
const lblPrimaCasaAssegnataA = document.getElementById("lblPrimaCasaAssegnataA");
|
|
1238
1249
|
const hintPrimaCasaAssegnataA = document.getElementById("hintPrimaCasaAssegnataA");
|
|
1239
1250
|
const lblPrimaCasaMutuoPerc1 = document.getElementById("lblPrimaCasaMutuoPerc1");
|
|
@@ -1299,6 +1310,8 @@ const defaultExpenseItems = [
|
|
|
1299
1310
|
if (hintPrimaCasaValoreLocativo) hintPrimaCasaValoreLocativo.title = tr("firstHomeLocativeValueHint");
|
|
1300
1311
|
if (lblPrimaCasaMutuoImporto) lblPrimaCasaMutuoImporto.textContent = msg("firstHomeMortgageAmountLabel", { currency: currentCurrency });
|
|
1301
1312
|
if (hintPrimaCasaMutuoImporto) hintPrimaCasaMutuoImporto.title = tr("firstHomeMortgageAmountHint");
|
|
1313
|
+
if (lblPrimaCasaMutuoScadenza) lblPrimaCasaMutuoScadenza.textContent = tr("firstHomeMortgageExpiryLabel");
|
|
1314
|
+
if (hintPrimaCasaMutuoScadenza) hintPrimaCasaMutuoScadenza.title = tr("firstHomeMortgageExpiryHint");
|
|
1302
1315
|
if (lblPrimaCasaAssegnataA) lblPrimaCasaAssegnataA.textContent = tr("firstHomeAssignedToLabel");
|
|
1303
1316
|
if (hintPrimaCasaAssegnataA) hintPrimaCasaAssegnataA.title = tr("firstHomeAssignedToHint");
|
|
1304
1317
|
if (lblPrimaCasaMutuoPerc1) lblPrimaCasaMutuoPerc1.textContent = tr("firstHomeSplitLabel");
|
|
@@ -1421,6 +1434,18 @@ const defaultExpenseItems = [
|
|
|
1421
1434
|
return `${short} ${currentCurrency}`;
|
|
1422
1435
|
}
|
|
1423
1436
|
|
|
1437
|
+
function formatIsoDate(value) {
|
|
1438
|
+
const iso = String(value || "").trim();
|
|
1439
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(iso)) return "";
|
|
1440
|
+
const d = new Date(`${iso}T00:00:00`);
|
|
1441
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
1442
|
+
return new Intl.DateTimeFormat(getCurrentLocale(), {
|
|
1443
|
+
year: "numeric",
|
|
1444
|
+
month: "2-digit",
|
|
1445
|
+
day: "2-digit"
|
|
1446
|
+
}).format(d);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1424
1449
|
function escapeHtml(value) {
|
|
1425
1450
|
return String(value || "")
|
|
1426
1451
|
.replaceAll("&", "&")
|
|
@@ -1471,25 +1496,48 @@ const defaultExpenseItems = [
|
|
|
1471
1496
|
syncExpenseDetailTableFromStore(textarea);
|
|
1472
1497
|
}
|
|
1473
1498
|
|
|
1474
|
-
function
|
|
1475
|
-
const
|
|
1499
|
+
function parseExpenseDetailPayload(raw) {
|
|
1500
|
+
const source = String(raw || "").trim();
|
|
1501
|
+
let tablePart = source;
|
|
1502
|
+
let notePart = "";
|
|
1503
|
+
const sepIdx = source.indexOf(EXPENSE_DETAIL_NOTE_SEPARATOR);
|
|
1504
|
+
if (sepIdx >= 0) {
|
|
1505
|
+
tablePart = source.slice(0, sepIdx).trim();
|
|
1506
|
+
notePart = source.slice(sepIdx + EXPENSE_DETAIL_NOTE_SEPARATOR.length).trim();
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
const tableLines = tablePart
|
|
1476
1510
|
.split(/\r?\n/)
|
|
1477
1511
|
.map((line) => line.trim())
|
|
1478
1512
|
.filter(Boolean);
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1513
|
+
const rows = [];
|
|
1514
|
+
const looseNoteLines = [];
|
|
1515
|
+
tableLines.forEach((line) => {
|
|
1516
|
+
const cols = line.split("|").map((part) => part.trim());
|
|
1517
|
+
if (cols.length >= 2) {
|
|
1518
|
+
rows.push({
|
|
1519
|
+
what: cols[0] || "",
|
|
1520
|
+
amount: cols[1] || "",
|
|
1521
|
+
due: cols.slice(2).join(" | ") || ""
|
|
1522
|
+
});
|
|
1523
|
+
} else {
|
|
1524
|
+
looseNoteLines.push(line);
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
const resolvedNote = [notePart, looseNoteLines.join("\n")]
|
|
1529
|
+
.filter(Boolean)
|
|
1530
|
+
.join(notePart && looseNoteLines.length ? "\n" : "")
|
|
1531
|
+
.trim();
|
|
1532
|
+
|
|
1533
|
+
return {
|
|
1534
|
+
rows: (rows.length ? rows : [{ what: "", amount: "", due: "" }]).slice(0, EXPENSE_DETAIL_MAX_ROWS),
|
|
1535
|
+
note: resolvedNote
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
function parseExpenseDetailRows(raw) {
|
|
1540
|
+
return parseExpenseDetailPayload(raw).rows;
|
|
1493
1541
|
}
|
|
1494
1542
|
|
|
1495
1543
|
function sanitizeExpenseDetailCell(value) {
|
|
@@ -1510,7 +1558,14 @@ const defaultExpenseItems = [
|
|
|
1510
1558
|
return compact.map((row) => `${row.what} | ${row.amount} | ${row.due}`.trim()).join("\n");
|
|
1511
1559
|
}
|
|
1512
1560
|
|
|
1513
|
-
function
|
|
1561
|
+
function serializeExpenseDetailPayload(rows, note) {
|
|
1562
|
+
const tablePart = serializeExpenseDetailRows(rows);
|
|
1563
|
+
const safeNote = String(note || "").trim();
|
|
1564
|
+
if (!safeNote) return tablePart;
|
|
1565
|
+
return `${tablePart}${tablePart ? EXPENSE_DETAIL_NOTE_SEPARATOR : ""}${safeNote}`;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
function buildExpenseDetailTableHtml(textareaId, rows, note = "") {
|
|
1514
1569
|
const safeRows = (Array.isArray(rows) && rows.length ? rows : [{ what: "", amount: "", due: "" }])
|
|
1515
1570
|
.slice(0, EXPENSE_DETAIL_MAX_ROWS);
|
|
1516
1571
|
const rowsHtml = safeRows.map((row) => {
|
|
@@ -1535,6 +1590,7 @@ const defaultExpenseItems = [
|
|
|
1535
1590
|
<tbody>${rowsHtml}</tbody>
|
|
1536
1591
|
</table>
|
|
1537
1592
|
<button type="button" class="btn-secondary spese-detail-add-row" data-row-add="${textareaId}">${escapeHtml(tr("expenseDetailAddRow"))}</button>
|
|
1593
|
+
<textarea class="spese-detail-note" data-detail-note-for="${textareaId}" rows="2" maxlength="360" placeholder="${escapeHtml(tr("expenseDetailFreeTextPlaceholder"))}">${escapeHtml(note || "")}</textarea>
|
|
1538
1594
|
</div>`;
|
|
1539
1595
|
}
|
|
1540
1596
|
|
|
@@ -1552,7 +1608,9 @@ const defaultExpenseItems = [
|
|
|
1552
1608
|
const textareaId = String(tableWrap.getAttribute("data-detail-table") || "");
|
|
1553
1609
|
const textarea = textareaId ? document.getElementById(textareaId) : null;
|
|
1554
1610
|
if (!textarea) return;
|
|
1555
|
-
const
|
|
1611
|
+
const noteEl = tableWrap.querySelector(`textarea[data-detail-note-for='${textareaId}']`);
|
|
1612
|
+
const note = String(noteEl && noteEl.value ? noteEl.value : "");
|
|
1613
|
+
const serialized = serializeExpenseDetailPayload(readExpenseDetailRowsFromTable(tableWrap), note);
|
|
1556
1614
|
textarea.value = serialized.slice(0, EXPENSE_DETAIL_MAX_CHARS);
|
|
1557
1615
|
updateExpenseDetailCounter(textarea);
|
|
1558
1616
|
}
|
|
@@ -1562,10 +1620,12 @@ const defaultExpenseItems = [
|
|
|
1562
1620
|
const host = document.getElementById(`${textarea.id}TableHost`);
|
|
1563
1621
|
if (!host) return;
|
|
1564
1622
|
const existing = host.querySelector(`[data-detail-table='${textarea.id}']`);
|
|
1565
|
-
const
|
|
1623
|
+
const existingNote = existing ? String(existing.querySelector(`textarea[data-detail-note-for='${textarea.id}']`)?.value || "") : "";
|
|
1624
|
+
const existingSerialized = serializeExpenseDetailPayload(existing ? readExpenseDetailRowsFromTable(existing) : [], existingNote);
|
|
1566
1625
|
const storeSerialized = String(textarea.value || "").trim();
|
|
1567
1626
|
if (existing && existingSerialized === storeSerialized) return;
|
|
1568
|
-
|
|
1627
|
+
const payload = parseExpenseDetailPayload(storeSerialized);
|
|
1628
|
+
host.innerHTML = buildExpenseDetailTableHtml(textarea.id, payload.rows, payload.note);
|
|
1569
1629
|
syncExpenseDetailStoreFromTable(host.querySelector(`[data-detail-table='${textarea.id}']`));
|
|
1570
1630
|
}
|
|
1571
1631
|
|
|
@@ -3724,6 +3784,7 @@ const defaultExpenseItems = [
|
|
|
3724
3784
|
primaCasaMutuoEnabled: firstHome.enabled ? 1 : 0,
|
|
3725
3785
|
primaCasaValoreLocativo: num("primaCasaValoreLocativo"),
|
|
3726
3786
|
primaCasaMutuoImporto: firstHome.amount,
|
|
3787
|
+
primaCasaMutuoScadenza: firstHome.expiry,
|
|
3727
3788
|
primaCasaAssegnataA: firstHome.assignedTo,
|
|
3728
3789
|
primaCasaMutuoPerc1: firstHome.share1,
|
|
3729
3790
|
straordAnn1: num("straordAnn1"),
|
|
@@ -3765,6 +3826,8 @@ const defaultExpenseItems = [
|
|
|
3765
3826
|
const aFam2 = Number(payload.aFam2 || 0);
|
|
3766
3827
|
const primaCasaMutuoEnabled = Number(payload.primaCasaMutuoEnabled || 0) > 0;
|
|
3767
3828
|
const primaCasaMutuoImporto = Math.max(0, Number(payload.primaCasaMutuoImporto || 0));
|
|
3829
|
+
const primaCasaMutuoScadenzaRaw = String(payload.primaCasaMutuoScadenza || "").trim();
|
|
3830
|
+
const primaCasaMutuoScadenza = /^\d{4}-\d{2}-\d{2}$/.test(primaCasaMutuoScadenzaRaw) ? primaCasaMutuoScadenzaRaw : "";
|
|
3768
3831
|
const primaCasaAssegnataA = (String(payload.primaCasaAssegnataA || "") === "1" || String(payload.primaCasaAssegnataA || "") === "2")
|
|
3769
3832
|
? String(payload.primaCasaAssegnataA)
|
|
3770
3833
|
: "";
|
|
@@ -3904,7 +3967,7 @@ const defaultExpenseItems = [
|
|
|
3904
3967
|
quotaDiretta1, quotaDiretta2,
|
|
3905
3968
|
saldo1, saldo2,
|
|
3906
3969
|
assegnoBaseDa1a2, assegnoBaseDa2a1,
|
|
3907
|
-
primaCasaMutuoEnabled, primaCasaMutuoImporto, primaCasaAssegnataA, primaCasaValoreLocativo,
|
|
3970
|
+
primaCasaMutuoEnabled, primaCasaMutuoImporto, primaCasaMutuoScadenza, primaCasaAssegnataA, primaCasaValoreLocativo,
|
|
3908
3971
|
primaCasaMutuoPerc1, primaCasaMutuoPerc2,
|
|
3909
3972
|
primaCasaConsidered, primaCasaTransfer1to2, primaCasaTransfer2to1,
|
|
3910
3973
|
compensativeBenefits,
|
|
@@ -4008,16 +4071,19 @@ const defaultExpenseItems = [
|
|
|
4008
4071
|
function getFirstHomeMortgageInput() {
|
|
4009
4072
|
const enabled = !!document.getElementById("primaCasaMutuoEnabled")?.checked;
|
|
4010
4073
|
const amount = Math.max(0, num("primaCasaMutuoImporto"));
|
|
4074
|
+
const expiryRaw = String(document.getElementById("primaCasaMutuoScadenza")?.value || "").trim();
|
|
4075
|
+
const expiry = /^\d{4}-\d{2}-\d{2}$/.test(expiryRaw) ? expiryRaw : "";
|
|
4011
4076
|
const assignedToRaw = String(document.getElementById("primaCasaAssegnataA")?.value || "").trim();
|
|
4012
4077
|
const assignedTo = (assignedToRaw === "1" || assignedToRaw === "2") ? assignedToRaw : "";
|
|
4013
4078
|
const share1 = Math.min(100, Math.max(0, num("primaCasaMutuoPerc1")));
|
|
4014
4079
|
const share2 = 100 - share1;
|
|
4015
|
-
return { enabled, amount, assignedTo, share1, share2 };
|
|
4080
|
+
return { enabled, amount, expiry, assignedTo, share1, share2 };
|
|
4016
4081
|
}
|
|
4017
4082
|
|
|
4018
4083
|
function updateFirstHomeMortgageUi() {
|
|
4019
4084
|
const enabledEl = document.getElementById("primaCasaMutuoEnabled");
|
|
4020
4085
|
const amountEl = document.getElementById("primaCasaMutuoImporto");
|
|
4086
|
+
const expiryEl = document.getElementById("primaCasaMutuoScadenza");
|
|
4021
4087
|
const assignedEl = document.getElementById("primaCasaAssegnataA");
|
|
4022
4088
|
const shareEl = document.getElementById("primaCasaMutuoPerc1");
|
|
4023
4089
|
const splitInfoEl = document.getElementById("primaCasaMutuoSplitInfo");
|
|
@@ -4033,6 +4099,7 @@ const defaultExpenseItems = [
|
|
|
4033
4099
|
|
|
4034
4100
|
const isEnabled = !!enabledEl.checked;
|
|
4035
4101
|
amountEl.disabled = !isEnabled;
|
|
4102
|
+
if (expiryEl) expiryEl.disabled = !isEnabled;
|
|
4036
4103
|
assignedEl.disabled = !isEnabled;
|
|
4037
4104
|
shareEl.disabled = !isEnabled;
|
|
4038
4105
|
if (splitWrapEl) splitWrapEl.classList.toggle("is-disabled", !isEnabled);
|
|
@@ -4563,6 +4630,7 @@ const defaultExpenseItems = [
|
|
|
4563
4630
|
setVal("assegnoFam2", Number(payload.aFam2 || 0));
|
|
4564
4631
|
setChecked("primaCasaMutuoEnabled", Number(payload.primaCasaMutuoEnabled || 0) > 0);
|
|
4565
4632
|
setVal("primaCasaMutuoImporto", Number(payload.primaCasaMutuoImporto || 0));
|
|
4633
|
+
setVal("primaCasaMutuoScadenza", String(payload.primaCasaMutuoScadenza || ""));
|
|
4566
4634
|
setVal("primaCasaAssegnataA", (String(payload.primaCasaAssegnataA || "") === "1" || String(payload.primaCasaAssegnataA || "") === "2") ? String(payload.primaCasaAssegnataA) : "");
|
|
4567
4635
|
setVal("primaCasaMutuoPerc1", Math.min(100, Math.max(0, Number((payload.primaCasaMutuoPerc1 === undefined ? 50 : payload.primaCasaMutuoPerc1) || 0))));
|
|
4568
4636
|
setVal("straordAnn1", Number(payload.straordAnn1 || 0));
|
|
@@ -4737,6 +4805,7 @@ const defaultExpenseItems = [
|
|
|
4737
4805
|
? `
|
|
4738
4806
|
<tr><td>${tr("pdfPrimaryHomeAssignedTo")}</td><td>${primaryHomeAssignedLabel}</td></tr>
|
|
4739
4807
|
<tr><td>${tr("pdfPrimaryHomeMonthlyAmount")}</td><td>${eur(m.primaCasaMutuoImporto || 0)}</td></tr>
|
|
4808
|
+
<tr><td>${tr("pdfPrimaryHomeExpiryDate")}</td><td>${escapeHtml(formatIsoDate(m.primaCasaMutuoScadenza) || tr("pdfPrimaryHomeNotDeclared"))}</td></tr>
|
|
4740
4809
|
<tr><td>${tr("pdfPrimaryHomeSplit")}</td><td>${c1NameEsc} ${(m.primaCasaMutuoPerc1 || 0).toFixed(0)}% · ${c2NameEsc} ${(m.primaCasaMutuoPerc2 || 0).toFixed(0)}%</td></tr>
|
|
4741
4810
|
<tr><td>${tr("pdfPrimaryHomeAppliedOnlyColl")}</td><td>${m.primaCasaConsidered ? "OK" : tr("pdfPrimaryHomeNotDeclared")}</td></tr>`
|
|
4742
4811
|
: `<tr><td>${tr("pdfPrimaryHomeMortgage")}</td><td>${tr("pdfPrimaryHomeNotDeclared")}</td></tr>`;
|
|
@@ -5255,6 +5324,7 @@ const defaultExpenseItems = [
|
|
|
5255
5324
|
? `
|
|
5256
5325
|
<tr><td>${tr("pdfPrimaryHomeAssignedTo")}</td><td>${primaryHomeAssignedLabel}</td></tr>
|
|
5257
5326
|
<tr><td>${tr("pdfPrimaryHomeMonthlyAmount")}</td><td>${eur(m.primaCasaMutuoImporto || 0)}</td></tr>
|
|
5327
|
+
<tr><td>${tr("pdfPrimaryHomeExpiryDate")}</td><td>${escapeHtml(formatIsoDate(m.primaCasaMutuoScadenza) || tr("pdfPrimaryHomeNotDeclared"))}</td></tr>
|
|
5258
5328
|
<tr><td>${tr("pdfPrimaryHomeSplit")}</td><td>${c1NameEsc} ${(m.primaCasaMutuoPerc1 || 0).toFixed(0)}% · ${c2NameEsc} ${(m.primaCasaMutuoPerc2 || 0).toFixed(0)}%</td></tr>
|
|
5259
5329
|
<tr><td>${tr("pdfPrimaryHomeAppliedOnlyColl")}</td><td>${m.primaCasaConsidered ? "OK" : tr("pdfPrimaryHomeNotDeclared")}</td></tr>`
|
|
5260
5330
|
: `<tr><td>${tr("pdfPrimaryHomeMortgage")}</td><td>${tr("pdfPrimaryHomeNotDeclared")}</td></tr>`;
|
|
@@ -6163,6 +6233,7 @@ ${scenarioLab.length ? `
|
|
|
6163
6233
|
primaCasaMutuoEnabled: document.getElementById("primaCasaMutuoEnabled")?.checked ? 1 : 0,
|
|
6164
6234
|
primaCasaValoreLocativo: num("primaCasaValoreLocativo"),
|
|
6165
6235
|
primaCasaMutuoImporto: num("primaCasaMutuoImporto"),
|
|
6236
|
+
primaCasaMutuoScadenza: String(document.getElementById("primaCasaMutuoScadenza")?.value || ""),
|
|
6166
6237
|
primaCasaAssegnataA: String(document.getElementById("primaCasaAssegnataA")?.value || ""),
|
|
6167
6238
|
primaCasaMutuoPerc1: num("primaCasaMutuoPerc1"),
|
|
6168
6239
|
straordAnn1: num("straordAnn1"),
|
|
@@ -6290,8 +6361,10 @@ ${scenarioLab.length ? `
|
|
|
6290
6361
|
});
|
|
6291
6362
|
const firstHomeEnabled = document.getElementById("primaCasaMutuoEnabled");
|
|
6292
6363
|
const firstHomeAssigned = document.getElementById("primaCasaAssegnataA");
|
|
6364
|
+
const firstHomeExpiry = document.getElementById("primaCasaMutuoScadenza");
|
|
6293
6365
|
if (firstHomeEnabled) firstHomeEnabled.checked = !!firstHomeEnabled.defaultChecked;
|
|
6294
6366
|
if (firstHomeAssigned) firstHomeAssigned.value = "";
|
|
6367
|
+
if (firstHomeExpiry) firstHomeExpiry.value = firstHomeExpiry.defaultValue || "";
|
|
6295
6368
|
permanenceCalendarState.byMonth = {};
|
|
6296
6369
|
speseConvivenzaAutoMode = true;
|
|
6297
6370
|
selectedScenarioIdx = -1;
|
|
@@ -6446,10 +6519,11 @@ ${scenarioLab.length ? `
|
|
|
6446
6519
|
const textareaId = String(addRowBtn.getAttribute("data-row-add") || "");
|
|
6447
6520
|
const textarea = textareaId ? document.getElementById(textareaId) : null;
|
|
6448
6521
|
if (!textarea) return;
|
|
6449
|
-
const
|
|
6522
|
+
const payload = parseExpenseDetailPayload(textarea.value);
|
|
6523
|
+
const rows = payload.rows;
|
|
6450
6524
|
if (rows.length < EXPENSE_DETAIL_MAX_ROWS) {
|
|
6451
6525
|
rows.push({ what: "", amount: "", due: "" });
|
|
6452
|
-
textarea.value =
|
|
6526
|
+
textarea.value = serializeExpenseDetailPayload(rows, payload.note);
|
|
6453
6527
|
syncExpenseDetailTableFromStore(textarea);
|
|
6454
6528
|
refreshExpenseDetailButtonState();
|
|
6455
6529
|
}
|
|
@@ -6491,6 +6565,12 @@ ${scenarioLab.length ? `
|
|
|
6491
6565
|
const tableWrap = e.target.closest("[data-detail-table]");
|
|
6492
6566
|
syncExpenseDetailStoreFromTable(tableWrap);
|
|
6493
6567
|
refreshExpenseDetailButtonState();
|
|
6568
|
+
return;
|
|
6569
|
+
}
|
|
6570
|
+
if (e.target && e.target.matches(".spese-detail-note")) {
|
|
6571
|
+
const tableWrap = e.target.closest("[data-detail-table]");
|
|
6572
|
+
syncExpenseDetailStoreFromTable(tableWrap);
|
|
6573
|
+
refreshExpenseDetailButtonState();
|
|
6494
6574
|
}
|
|
6495
6575
|
});
|
|
6496
6576
|
|
|
@@ -6630,7 +6710,7 @@ ${scenarioLab.length ? `
|
|
|
6630
6710
|
}
|
|
6631
6711
|
updateModeUi();
|
|
6632
6712
|
renderAll();
|
|
6633
|
-
} else if (e.target && (e.target.id === "primaCasaMutuoEnabled" || e.target.id === "primaCasaAssegnataA")) {
|
|
6713
|
+
} else if (e.target && (e.target.id === "primaCasaMutuoEnabled" || e.target.id === "primaCasaAssegnataA" || e.target.id === "primaCasaMutuoScadenza")) {
|
|
6634
6714
|
updateFirstHomeMortgageUi();
|
|
6635
6715
|
renderAll();
|
|
6636
6716
|
}
|
|
@@ -358,11 +358,37 @@
|
|
|
358
358
|
</label>
|
|
359
359
|
<input id="primaCasaMutuoEnabled" type="checkbox" />
|
|
360
360
|
</div>
|
|
361
|
-
<div class="field">
|
|
362
|
-
<
|
|
363
|
-
<
|
|
364
|
-
|
|
365
|
-
|
|
361
|
+
<div class="field first-home-mortgage-table-field">
|
|
362
|
+
<div class="first-home-mortgage-table-wrap">
|
|
363
|
+
<table class="first-home-mortgage-table" aria-label="Dati mutuo prima casa">
|
|
364
|
+
<thead>
|
|
365
|
+
<tr>
|
|
366
|
+
<th>
|
|
367
|
+
<span class="label-row"><span id="lblPrimaCasaValoreLocativo">Casa (valore locativo) ({currency})</span>
|
|
368
|
+
<span class="hint" id="hintPrimaCasaValoreLocativo" title="Valore locativo mensile della casa assegnata, usato per valorizzare il beneficio economico implicito.">i</span>
|
|
369
|
+
</span>
|
|
370
|
+
</th>
|
|
371
|
+
<th>
|
|
372
|
+
<span class="label-row"><span id="lblPrimaCasaMutuoImporto">Rata mutuo mensile ({currency})</span>
|
|
373
|
+
<span class="hint" id="hintPrimaCasaMutuoImporto" title="Importo mensile complessivo della rata del mutuo prima casa.">i</span>
|
|
374
|
+
</span>
|
|
375
|
+
</th>
|
|
376
|
+
<th>
|
|
377
|
+
<span class="label-row"><span id="lblPrimaCasaMutuoScadenza">Scadenza mutuo</span>
|
|
378
|
+
<span class="hint" id="hintPrimaCasaMutuoScadenza" title="Data prevista di estinzione del mutuo prima casa.">i</span>
|
|
379
|
+
</span>
|
|
380
|
+
</th>
|
|
381
|
+
</tr>
|
|
382
|
+
</thead>
|
|
383
|
+
<tbody>
|
|
384
|
+
<tr>
|
|
385
|
+
<td><input id="primaCasaValoreLocativo" type="number" min="0" step="50" value="1000" /></td>
|
|
386
|
+
<td><input id="primaCasaMutuoImporto" type="number" min="0" step="50" value="0" /></td>
|
|
387
|
+
<td><input id="primaCasaMutuoScadenza" type="date" value="" /></td>
|
|
388
|
+
</tr>
|
|
389
|
+
</tbody>
|
|
390
|
+
</table>
|
|
391
|
+
</div>
|
|
366
392
|
</div>
|
|
367
393
|
<div class="field">
|
|
368
394
|
<label for="primaCasaAssegnataA" class="label-row"><span id="lblPrimaCasaAssegnataA">Casa assegnata a</span>
|
|
@@ -374,15 +400,9 @@
|
|
|
374
400
|
<option value="2">Coniuge 2</option>
|
|
375
401
|
</select>
|
|
376
402
|
</div>
|
|
377
|
-
<div class="field">
|
|
378
|
-
<label for="primaCasaMutuoImporto" class="label-row"><span id="lblPrimaCasaMutuoImporto">Rata mutuo mensile ({currency})</span>
|
|
379
|
-
<span class="hint" id="hintPrimaCasaMutuoImporto" title="Importo mensile complessivo della rata del mutuo prima casa.">i</span>
|
|
380
|
-
</label>
|
|
381
|
-
<input id="primaCasaMutuoImporto" type="number" min="0" step="50" value="0" />
|
|
382
|
-
</div>
|
|
383
403
|
<div class="field first-home-split-field">
|
|
384
404
|
<label for="primaCasaMutuoPerc1" class="label-row"><span id="lblPrimaCasaMutuoPerc1">Quota mutuo a carico</span>
|
|
385
|
-
<span class="hint" id="hintPrimaCasaMutuoPerc1" title="Percentuale della rata mutuo pagata da Coniuge
|
|
405
|
+
<span class="hint" id="hintPrimaCasaMutuoPerc1" title="Percentuale della rata mutuo pagata da ciascun Coniuge.">i</span>
|
|
386
406
|
</label>
|
|
387
407
|
<div class="mortgage-split-slider" id="primaCasaMutuoSliderWrap">
|
|
388
408
|
<div class="mortgage-split-side mortgage-split-side-left" id="primaCasaSplitLeft">
|
|
@@ -630,7 +650,7 @@
|
|
|
630
650
|
<script src="supabase.min.js"></script>
|
|
631
651
|
<script src="fabric.min.js"></script>
|
|
632
652
|
<script src="html2pdf.bundle.min.js"></script>
|
|
633
|
-
<script src="app.js?v=2.4.
|
|
653
|
+
<script src="app.js?v=2.4.11"></script>
|
|
634
654
|
</body>
|
|
635
655
|
</html>
|
|
636
656
|
|
|
@@ -949,6 +949,35 @@
|
|
|
949
949
|
grid-column: 1 / -1;
|
|
950
950
|
}
|
|
951
951
|
|
|
952
|
+
.extra-box-first-home .first-home-mortgage-table-field {
|
|
953
|
+
grid-column: 1 / -1;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
.first-home-mortgage-table-wrap {
|
|
957
|
+
overflow-x: auto;
|
|
958
|
+
padding-bottom: 2px;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
.first-home-mortgage-table {
|
|
962
|
+
width: 100%;
|
|
963
|
+
min-width: 560px;
|
|
964
|
+
border-collapse: separate;
|
|
965
|
+
border-spacing: 6px;
|
|
966
|
+
table-layout: fixed;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
.first-home-mortgage-table th {
|
|
970
|
+
text-align: left;
|
|
971
|
+
font-size: 0.8rem;
|
|
972
|
+
color: #2a5954;
|
|
973
|
+
font-weight: 800;
|
|
974
|
+
padding: 0 2px;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
.first-home-mortgage-table td {
|
|
978
|
+
padding: 0;
|
|
979
|
+
}
|
|
980
|
+
|
|
952
981
|
.extra-box-first-home .field {
|
|
953
982
|
border-radius: 12px;
|
|
954
983
|
padding: 8px 9px 7px;
|
|
@@ -966,6 +995,7 @@
|
|
|
966
995
|
}
|
|
967
996
|
|
|
968
997
|
.extra-box-first-home .field input[type="number"],
|
|
998
|
+
.extra-box-first-home .field input[type="date"],
|
|
969
999
|
.extra-box-first-home .field select {
|
|
970
1000
|
border-width: 1.5px;
|
|
971
1001
|
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.8) inset;
|
|
@@ -982,6 +1012,7 @@
|
|
|
982
1012
|
}
|
|
983
1013
|
|
|
984
1014
|
.extra-box-first-home .field input[type="number"]:focus,
|
|
1015
|
+
.extra-box-first-home .field input[type="date"]:focus,
|
|
985
1016
|
.extra-box-first-home .field select:focus {
|
|
986
1017
|
border-color: rgba(27, 141, 127, 0.4);
|
|
987
1018
|
box-shadow: 0 0 0 3px rgba(27, 141, 127, 0.14), 0 1px 0 rgba(255, 255, 255, 0.85) inset;
|
|
@@ -1592,6 +1623,20 @@
|
|
|
1592
1623
|
font-size: 0.67rem;
|
|
1593
1624
|
}
|
|
1594
1625
|
|
|
1626
|
+
.spese-detail-note {
|
|
1627
|
+
width: 100%;
|
|
1628
|
+
margin-top: 6px;
|
|
1629
|
+
border: 1px solid #bfd5d0;
|
|
1630
|
+
border-radius: 6px;
|
|
1631
|
+
background: #ffffff;
|
|
1632
|
+
color: #214b47;
|
|
1633
|
+
font-size: 0.72rem;
|
|
1634
|
+
line-height: 1.3;
|
|
1635
|
+
padding: 6px 7px;
|
|
1636
|
+
resize: vertical;
|
|
1637
|
+
min-height: 46px;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1595
1640
|
.spese-detail-counter {
|
|
1596
1641
|
margin-top: 3px;
|
|
1597
1642
|
font-size: 0.66rem;
|