mantenimento-app 2.1.2 → 2.1.4
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 +593 -110
- package/backend/calculate-model.js +46 -2
- package/frontend/public/app.js +593 -110
- package/frontend/public/index.html +62 -1
- package/frontend/public/styles.css +561 -7
- package/package.json +1 -1
package/frontend/public/app.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const defaultExpenseItems = [
|
|
2
2
|
{ label: "🏠 Affitto", help: "Canone mensile di locazione dell'abitazione." },
|
|
3
|
-
{ label: "🏦 Mutuo casa", help: "Rata mensile del mutuo abitazione." },
|
|
4
3
|
{ label: "🏡 Casa (valore locativo)", help: "Valore locativo teorico della casa in uso, se rilevante." },
|
|
5
4
|
{ label: "💡 Utenze", help: "Luce, gas, acqua, internet e altre utenze domestiche." },
|
|
6
5
|
{ label: "🛒 Cibo/Alimenti", help: "Spesa alimentare mensile imputabile al nucleo familiare." },
|
|
@@ -28,6 +27,8 @@ const defaultExpenseItems = [
|
|
|
28
27
|
let scenarioTransitionTimer = null;
|
|
29
28
|
const SCENARIO_LAB_MAX = 3;
|
|
30
29
|
const SCENARIO_LABELS = ["A", "B", "C"];
|
|
30
|
+
const EXPENSE_DETAIL_MAX_CHARS = 560;
|
|
31
|
+
const EXPENSE_DETAIL_MAX_LINES = 10;
|
|
31
32
|
|
|
32
33
|
const QUOTA_MANTENIMENTO_PERC = 35;
|
|
33
34
|
|
|
@@ -228,6 +229,7 @@ const defaultExpenseItems = [
|
|
|
228
229
|
expenseDetailBtn: "Dettaglio",
|
|
229
230
|
expenseDetailTitle: "Apri dettaglio voce spesa",
|
|
230
231
|
expenseDetailPlaceholder: "Scrivi qui il dettaglio di questa cifra (es. mesi, quota, riferimento).",
|
|
232
|
+
expenseDetailCharsRemaining: "Caratteri rimanenti: {count}",
|
|
231
233
|
expenseRemoveTitle: "Rimuovi voce spesa",
|
|
232
234
|
expenseRemoveBtn: "Rimuovi",
|
|
233
235
|
expenseMinOneAlert: "Deve restare almeno una voce spesa.",
|
|
@@ -254,6 +256,33 @@ const defaultExpenseItems = [
|
|
|
254
256
|
extraAnnHint1: "Quota annuale straordinaria stimata a carico di {spouse} (es. sanitarie non ricorrenti, scolastiche extra, attività non ordinarie).",
|
|
255
257
|
extraAnnHint2: "Quota annuale straordinaria stimata a carico di {spouse} (es. sanitarie non ricorrenti, scolastiche extra, attività non ordinarie).",
|
|
256
258
|
extraMonthlyEstimate: "Quota mensile stimata: {amount}",
|
|
259
|
+
firstHomeBoxTitle: "🏡 Mutuo prima casa ceduta",
|
|
260
|
+
firstHomeBoxNote: "Dichiara se esiste un mutuo sulla prima casa dei coniugi ceduta a uno dei due: il modello considera il trasferimento implicito quando la casa e assegnata al collocatario.",
|
|
261
|
+
firstHomeMortgageEnabledLabel: "Mutuo su prima casa dei coniugi",
|
|
262
|
+
firstHomeMortgageEnabledHint: "Attiva per includere il mutuo della prima casa ceduta nei benefici compensativi.",
|
|
263
|
+
firstHomeMortgageAmountLabel: "Rata mutuo mensile ({currency})",
|
|
264
|
+
firstHomeMortgageAmountHint: "Importo mensile complessivo della rata del mutuo prima casa.",
|
|
265
|
+
firstHomeAssignedToLabel: "Casa assegnata a",
|
|
266
|
+
firstHomeAssignedToHint: "Seleziona il coniuge a cui e ceduta la prima casa.",
|
|
267
|
+
firstHomeAssignedToNone: "Nessuna cessione",
|
|
268
|
+
firstHomeAssignedToSpouse: "Casa ceduta a {spouse}",
|
|
269
|
+
firstHomeSplitLabel: "Quota mutuo a carico {spouse} (%)",
|
|
270
|
+
firstHomeSplitHint: "Percentuale della rata mutuo pagata da {spouse}. La quota dell'altro coniuge e complementare a 100%.",
|
|
271
|
+
firstHomeSplitInfo: "Ripartizione mutuo: {spouse1} {p1}% · {spouse2} {p2}%",
|
|
272
|
+
calcCompBenefitsLabel: "Benefici compensativi gia allocati",
|
|
273
|
+
calcNoTransferWithBenefits: "Nessun trasferimento monetario suggerito. Benefici gia allocati: {benefits}.",
|
|
274
|
+
calcBenefitFamilyAllowance: "Assegno familiare INPS percepito da {spouse}",
|
|
275
|
+
calcBenefitPrimaryHomeMortgage: "Quota mutuo prima casa ceduta al collocatario ({payer} -> {receiver})",
|
|
276
|
+
pdfCompBenefitsSection: "Benefici compensativi gia allocati",
|
|
277
|
+
pdfCompBenefitsItem: "Beneficio",
|
|
278
|
+
pdfCompBenefitsAmount: "Valore {currency}/mese",
|
|
279
|
+
pdfCompBenefitsNone: "Nessun beneficio compensativo aggiuntivo dichiarato.",
|
|
280
|
+
pdfPrimaryHomeMortgage: "Mutuo prima casa ceduta",
|
|
281
|
+
pdfPrimaryHomeNotDeclared: "Non dichiarato",
|
|
282
|
+
pdfPrimaryHomeAssignedTo: "Assegnata a",
|
|
283
|
+
pdfPrimaryHomeMonthlyAmount: "Rata mensile",
|
|
284
|
+
pdfPrimaryHomeSplit: "Ripartizione mutuo",
|
|
285
|
+
pdfPrimaryHomeAppliedOnlyColl: "Considerato solo se casa ceduta al collocatario.",
|
|
257
286
|
pdfExtraordinaryRow: "Spese straordinarie (quota mensile da annuo)",
|
|
258
287
|
liveTotalIncome: "Entrate totali (reddito + assegni + INPS)",
|
|
259
288
|
livePaidToOther: "Assegno mantenimento pagato all'altro coniuge",
|
|
@@ -521,6 +550,7 @@ const defaultExpenseItems = [
|
|
|
521
550
|
expenseDetailBtn: "Detail",
|
|
522
551
|
expenseDetailTitle: "Open expense detail",
|
|
523
552
|
expenseDetailPlaceholder: "Write details for this amount (e.g. months, share, reference).",
|
|
553
|
+
expenseDetailCharsRemaining: "Remaining characters: {count}",
|
|
524
554
|
expenseRemoveTitle: "Remove expense item",
|
|
525
555
|
expenseRemoveBtn: "Remove",
|
|
526
556
|
expenseMinOneAlert: "At least one expense item must remain.",
|
|
@@ -547,6 +577,33 @@ const defaultExpenseItems = [
|
|
|
547
577
|
extraAnnHint1: "Estimated yearly extraordinary share for {spouse} (e.g., non-recurring medical, extra school, non-ordinary activities).",
|
|
548
578
|
extraAnnHint2: "Estimated yearly extraordinary share for {spouse} (e.g., non-recurring medical, extra school, non-ordinary activities).",
|
|
549
579
|
extraMonthlyEstimate: "Estimated monthly share: {amount}",
|
|
580
|
+
firstHomeBoxTitle: "🏡 Assigned primary home mortgage",
|
|
581
|
+
firstHomeBoxNote: "Declare whether there is a mortgage on the spouses' primary home assigned to one spouse: the model counts the implicit transfer when the home is assigned to the custodial parent.",
|
|
582
|
+
firstHomeMortgageEnabledLabel: "Mortgage on spouses' primary home",
|
|
583
|
+
firstHomeMortgageEnabledHint: "Enable to include the assigned primary-home mortgage in compensative benefits.",
|
|
584
|
+
firstHomeMortgageAmountLabel: "Monthly mortgage payment ({currency})",
|
|
585
|
+
firstHomeMortgageAmountHint: "Total monthly amount of the primary-home mortgage payment.",
|
|
586
|
+
firstHomeAssignedToLabel: "Home assigned to",
|
|
587
|
+
firstHomeAssignedToHint: "Select which spouse receives assignment of the primary home.",
|
|
588
|
+
firstHomeAssignedToNone: "No assignment",
|
|
589
|
+
firstHomeAssignedToSpouse: "Home assigned to {spouse}",
|
|
590
|
+
firstHomeSplitLabel: "Mortgage share paid by {spouse} (%)",
|
|
591
|
+
firstHomeSplitHint: "Percentage of the monthly mortgage payment paid by {spouse}. The other spouse share is the complement to 100%.",
|
|
592
|
+
firstHomeSplitInfo: "Mortgage split: {spouse1} {p1}% · {spouse2} {p2}%",
|
|
593
|
+
calcCompBenefitsLabel: "Compensative benefits already allocated",
|
|
594
|
+
calcNoTransferWithBenefits: "No monetary transfer suggested. Already allocated benefits: {benefits}.",
|
|
595
|
+
calcBenefitFamilyAllowance: "INPS family allowance received by {spouse}",
|
|
596
|
+
calcBenefitPrimaryHomeMortgage: "Primary-home mortgage share assigned to custodial parent ({payer} -> {receiver})",
|
|
597
|
+
pdfCompBenefitsSection: "Compensative benefits already allocated",
|
|
598
|
+
pdfCompBenefitsItem: "Benefit",
|
|
599
|
+
pdfCompBenefitsAmount: "Value {currency}/month",
|
|
600
|
+
pdfCompBenefitsNone: "No additional compensative benefits declared.",
|
|
601
|
+
pdfPrimaryHomeMortgage: "Assigned primary-home mortgage",
|
|
602
|
+
pdfPrimaryHomeNotDeclared: "Not declared",
|
|
603
|
+
pdfPrimaryHomeAssignedTo: "Assigned to",
|
|
604
|
+
pdfPrimaryHomeMonthlyAmount: "Monthly payment",
|
|
605
|
+
pdfPrimaryHomeSplit: "Mortgage split",
|
|
606
|
+
pdfPrimaryHomeAppliedOnlyColl: "Counted only when the home is assigned to the custodial parent.",
|
|
550
607
|
pdfExtraordinaryRow: "Extraordinary expenses (monthly share from yearly)",
|
|
551
608
|
liveTotalIncome: "Total income (income + support + INPS)",
|
|
552
609
|
livePaidToOther: "Support paid to the other spouse",
|
|
@@ -692,100 +749,145 @@ const defaultExpenseItems = [
|
|
|
692
749
|
};
|
|
693
750
|
let currentLang = "it";
|
|
694
751
|
let currentCurrency = "EUR";
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
752
|
+
const CALC_API_BASE_STORAGE_KEY = "keylock_calc_api_base";
|
|
753
|
+
const FRONTEND_VARIANT_ENVS = window.KEYLOCK_FRONTEND_VARIANT_ENVS && typeof window.KEYLOCK_FRONTEND_VARIANT_ENVS === "object"
|
|
754
|
+
? window.KEYLOCK_FRONTEND_VARIANT_ENVS
|
|
755
|
+
: {};
|
|
756
|
+
const CALC_API_ENVS = window.KEYLOCK_CALC_API_ENVS && typeof window.KEYLOCK_CALC_API_ENVS === "object"
|
|
757
|
+
? window.KEYLOCK_CALC_API_ENVS
|
|
758
|
+
: {};
|
|
702
759
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
760
|
+
function normalizeApiBase(rawValue) {
|
|
761
|
+
return String(rawValue || "").trim().replace(/\/+$/, "");
|
|
762
|
+
}
|
|
706
763
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
764
|
+
function normalizeFrontendVariantUrl(rawValue) {
|
|
765
|
+
return String(rawValue || "").trim();
|
|
766
|
+
}
|
|
710
767
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
768
|
+
function maybeRedirectFrontendVariant() {
|
|
769
|
+
try {
|
|
770
|
+
const params = new URLSearchParams(window.location.search || "");
|
|
771
|
+
const variant = String(params.get("frontend") || "").trim().toLowerCase();
|
|
772
|
+
if (!variant || variant === "prod" || variant === "default" || variant === "reset") return;
|
|
716
773
|
|
|
717
|
-
|
|
718
|
-
|
|
774
|
+
const targetBase = normalizeFrontendVariantUrl(FRONTEND_VARIANT_ENVS[variant] || "");
|
|
775
|
+
if (!targetBase) return;
|
|
719
776
|
|
|
720
|
-
|
|
721
|
-
|
|
777
|
+
const target = new URL(targetBase, window.location.href);
|
|
778
|
+
const current = new URL(window.location.href);
|
|
722
779
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
if (target.origin === current.origin && target.pathname === current.pathname) return;
|
|
780
|
+
if (target.href === current.href) return;
|
|
781
|
+
if (target.origin === current.origin && target.pathname === current.pathname) return;
|
|
726
782
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
});
|
|
783
|
+
["env", "apiBase"].forEach((k) => {
|
|
784
|
+
if (params.has(k)) target.searchParams.set(k, String(params.get(k)));
|
|
785
|
+
});
|
|
731
786
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
787
|
+
window.location.replace(target.toString());
|
|
788
|
+
} catch (_) {
|
|
789
|
+
// Ignore malformed URLs and continue with default frontend.
|
|
790
|
+
}
|
|
735
791
|
}
|
|
736
|
-
}
|
|
737
792
|
|
|
738
|
-
|
|
793
|
+
maybeRedirectFrontendVariant();
|
|
739
794
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
795
|
+
function resolveNamedApiBase(envName) {
|
|
796
|
+
const key = String(envName || "").trim().toLowerCase();
|
|
797
|
+
if (!key) return "";
|
|
798
|
+
return normalizeApiBase(CALC_API_ENVS[key] || "");
|
|
799
|
+
}
|
|
745
800
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
801
|
+
function resolveCalculationApiBase() {
|
|
802
|
+
const configBase = normalizeApiBase(window.KEYLOCK_CALC_API_BASE || "");
|
|
803
|
+
let storedBase = "";
|
|
749
804
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
805
|
+
try {
|
|
806
|
+
storedBase = normalizeApiBase(localStorage.getItem(CALC_API_BASE_STORAGE_KEY) || "");
|
|
807
|
+
} catch (_) {
|
|
808
|
+
storedBase = "";
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
try {
|
|
812
|
+
const params = new URLSearchParams(window.location.search || "");
|
|
813
|
+
if (params.has("env")) {
|
|
814
|
+
const envName = String(params.get("env") || "").trim().toLowerCase();
|
|
815
|
+
const disable = envName === "off" || envName === "default" || envName === "reset" || envName === "prod";
|
|
816
|
+
const envBase = disable ? "" : resolveNamedApiBase(envName);
|
|
817
|
+
try {
|
|
818
|
+
if (disable || !envBase) {
|
|
819
|
+
localStorage.removeItem(CALC_API_BASE_STORAGE_KEY);
|
|
820
|
+
} else {
|
|
821
|
+
localStorage.setItem(CALC_API_BASE_STORAGE_KEY, envBase);
|
|
822
|
+
}
|
|
823
|
+
} catch (_) {}
|
|
824
|
+
return envBase;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (params.has("apiBase")) {
|
|
828
|
+
const queryBaseRaw = String(params.get("apiBase") || "").trim();
|
|
829
|
+
const disable = queryBaseRaw === "off" || queryBaseRaw === "default" || queryBaseRaw === "reset";
|
|
830
|
+
const queryBase = disable ? "" : normalizeApiBase(queryBaseRaw);
|
|
831
|
+
try {
|
|
832
|
+
if (disable || !queryBase) {
|
|
833
|
+
localStorage.removeItem(CALC_API_BASE_STORAGE_KEY);
|
|
834
|
+
} else {
|
|
835
|
+
localStorage.setItem(CALC_API_BASE_STORAGE_KEY, queryBase);
|
|
836
|
+
}
|
|
837
|
+
} catch (_) {}
|
|
838
|
+
return queryBase;
|
|
839
|
+
}
|
|
840
|
+
} catch (_) {}
|
|
841
|
+
|
|
842
|
+
return storedBase || configBase;
|
|
754
843
|
}
|
|
755
844
|
|
|
756
|
-
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
const
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
845
|
+
function getRuntimeVariantLabels() {
|
|
846
|
+
const labels = [];
|
|
847
|
+
|
|
848
|
+
try {
|
|
849
|
+
const params = new URLSearchParams(window.location.search || "");
|
|
850
|
+
const frontendVariant = String(params.get("frontend") || "").trim().toLowerCase();
|
|
851
|
+
const envVariant = String(params.get("env") || "").trim().toLowerCase();
|
|
852
|
+
const apiBaseVariant = String(params.get("apiBase") || "").trim();
|
|
853
|
+
|
|
854
|
+
if (frontendVariant === "dev" || window.location.host.includes("githack.com")) {
|
|
855
|
+
labels.push("dev-frontend");
|
|
856
|
+
}
|
|
857
|
+
if (envVariant === "dev" || !!apiBaseVariant) {
|
|
858
|
+
labels.push("dev-api");
|
|
859
|
+
}
|
|
860
|
+
} catch (_) {}
|
|
861
|
+
|
|
862
|
+
return labels;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function renderRuntimeBadge() {
|
|
866
|
+
const heroTools = document.querySelector(".hero-tools");
|
|
867
|
+
if (!heroTools) return;
|
|
868
|
+
|
|
869
|
+
const labels = getRuntimeVariantLabels();
|
|
870
|
+
let badge = document.getElementById("runtimeEnvBadge");
|
|
871
|
+
|
|
872
|
+
if (!labels.length) {
|
|
873
|
+
if (badge) badge.remove();
|
|
874
|
+
return;
|
|
770
875
|
}
|
|
771
876
|
|
|
772
|
-
if (
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
if (disable || !queryBase) {
|
|
778
|
-
localStorage.removeItem(CALC_API_BASE_STORAGE_KEY);
|
|
779
|
-
} else {
|
|
780
|
-
localStorage.setItem(CALC_API_BASE_STORAGE_KEY, queryBase);
|
|
781
|
-
}
|
|
782
|
-
} catch (_) {}
|
|
783
|
-
return queryBase;
|
|
877
|
+
if (!badge) {
|
|
878
|
+
badge = document.createElement("div");
|
|
879
|
+
badge.id = "runtimeEnvBadge";
|
|
880
|
+
badge.className = "runtime-badge";
|
|
881
|
+
heroTools.prepend(badge);
|
|
784
882
|
}
|
|
785
|
-
} catch (_) {}
|
|
786
883
|
|
|
787
|
-
|
|
788
|
-
|
|
884
|
+
badge.innerHTML = labels.map((label) => {
|
|
885
|
+
if (label === "dev-frontend") {
|
|
886
|
+
return '<span class="runtime-badge-chip runtime-badge-chip--frontend">DEV FRONTEND</span>';
|
|
887
|
+
}
|
|
888
|
+
return '<span class="runtime-badge-chip runtime-badge-chip--api">DEV API</span>';
|
|
889
|
+
}).join("");
|
|
890
|
+
}
|
|
789
891
|
|
|
790
892
|
function getRuntimeVariantLabels() {
|
|
791
893
|
const labels = [];
|
|
@@ -858,8 +960,8 @@ const defaultExpenseItems = [
|
|
|
858
960
|
}
|
|
859
961
|
|
|
860
962
|
function resolveCalculationApiUrl() {
|
|
861
|
-
|
|
862
|
-
|
|
963
|
+
const calcApiBase = resolveCalculationApiBase();
|
|
964
|
+
if (calcApiBase) return `${calcApiBase}/api/calculate`;
|
|
863
965
|
return "/api/calculate";
|
|
864
966
|
}
|
|
865
967
|
|
|
@@ -1020,6 +1122,16 @@ const defaultExpenseItems = [
|
|
|
1020
1122
|
const permLegendC2 = document.getElementById("permLegendC2");
|
|
1021
1123
|
const extraBoxTitle = document.getElementById("extraBoxTitle");
|
|
1022
1124
|
const extraBoxNote = document.getElementById("extraBoxNote");
|
|
1125
|
+
const firstHomeBoxTitle = document.getElementById("firstHomeBoxTitle");
|
|
1126
|
+
const firstHomeBoxNote = document.getElementById("firstHomeBoxNote");
|
|
1127
|
+
const lblPrimaCasaMutuoEnabled = document.getElementById("lblPrimaCasaMutuoEnabled");
|
|
1128
|
+
const hintPrimaCasaMutuoEnabled = document.getElementById("hintPrimaCasaMutuoEnabled");
|
|
1129
|
+
const lblPrimaCasaMutuoImporto = document.getElementById("lblPrimaCasaMutuoImporto");
|
|
1130
|
+
const hintPrimaCasaMutuoImporto = document.getElementById("hintPrimaCasaMutuoImporto");
|
|
1131
|
+
const lblPrimaCasaAssegnataA = document.getElementById("lblPrimaCasaAssegnataA");
|
|
1132
|
+
const hintPrimaCasaAssegnataA = document.getElementById("hintPrimaCasaAssegnataA");
|
|
1133
|
+
const lblPrimaCasaMutuoPerc1 = document.getElementById("lblPrimaCasaMutuoPerc1");
|
|
1134
|
+
const hintPrimaCasaMutuoPerc1 = document.getElementById("hintPrimaCasaMutuoPerc1");
|
|
1023
1135
|
const lblStraordAnn1 = document.getElementById("lblStraordAnn1");
|
|
1024
1136
|
const lblStraordAnn2 = document.getElementById("lblStraordAnn2");
|
|
1025
1137
|
const hintStraordAnn1 = document.getElementById("hintStraordAnn1");
|
|
@@ -1059,6 +1171,16 @@ const defaultExpenseItems = [
|
|
|
1059
1171
|
if (permLegendC2) permLegendC2.textContent = c2n();
|
|
1060
1172
|
if (extraBoxTitle) extraBoxTitle.textContent = tr("extraBoxTitle");
|
|
1061
1173
|
if (extraBoxNote) extraBoxNote.textContent = tr("extraBoxNote");
|
|
1174
|
+
if (firstHomeBoxTitle) firstHomeBoxTitle.textContent = tr("firstHomeBoxTitle");
|
|
1175
|
+
if (firstHomeBoxNote) firstHomeBoxNote.textContent = tr("firstHomeBoxNote");
|
|
1176
|
+
if (lblPrimaCasaMutuoEnabled) lblPrimaCasaMutuoEnabled.textContent = tr("firstHomeMortgageEnabledLabel");
|
|
1177
|
+
if (hintPrimaCasaMutuoEnabled) hintPrimaCasaMutuoEnabled.title = tr("firstHomeMortgageEnabledHint");
|
|
1178
|
+
if (lblPrimaCasaMutuoImporto) lblPrimaCasaMutuoImporto.textContent = msg("firstHomeMortgageAmountLabel", { currency: currentCurrency });
|
|
1179
|
+
if (hintPrimaCasaMutuoImporto) hintPrimaCasaMutuoImporto.title = tr("firstHomeMortgageAmountHint");
|
|
1180
|
+
if (lblPrimaCasaAssegnataA) lblPrimaCasaAssegnataA.textContent = tr("firstHomeAssignedToLabel");
|
|
1181
|
+
if (hintPrimaCasaAssegnataA) hintPrimaCasaAssegnataA.title = tr("firstHomeAssignedToHint");
|
|
1182
|
+
if (lblPrimaCasaMutuoPerc1) lblPrimaCasaMutuoPerc1.textContent = msg("firstHomeSplitLabel", { spouse: c1n() });
|
|
1183
|
+
if (hintPrimaCasaMutuoPerc1) hintPrimaCasaMutuoPerc1.title = msg("firstHomeSplitHint", { spouse: c1n() });
|
|
1062
1184
|
if (lblStraordAnn1) lblStraordAnn1.textContent = msg("extraAnnLabel1", { spouse: c1n(), currency: currentCurrency });
|
|
1063
1185
|
if (lblStraordAnn2) lblStraordAnn2.textContent = msg("extraAnnLabel2", { spouse: c2n(), currency: currentCurrency });
|
|
1064
1186
|
if (hintStraordAnn1) hintStraordAnn1.title = msg("extraAnnHint1", { spouse: c1n() });
|
|
@@ -1108,6 +1230,10 @@ const defaultExpenseItems = [
|
|
|
1108
1230
|
if (visitorTotalLabel) visitorTotalLabel.textContent = tr("footerVisitorsTotal");
|
|
1109
1231
|
if (visitorActiveLabel) visitorActiveLabel.textContent = tr("footerVisitorsActive");
|
|
1110
1232
|
if (visitorLoggedLabel) visitorLoggedLabel.textContent = tr("footerLoggedUsers");
|
|
1233
|
+
rowsSpese.querySelectorAll("textarea.spese-detail-text").forEach((el) => {
|
|
1234
|
+
updateExpenseDetailCounter(el);
|
|
1235
|
+
});
|
|
1236
|
+
updateFirstHomeMortgageUi();
|
|
1111
1237
|
updateExtraordinaryModuleUi();
|
|
1112
1238
|
updatePermanenceCalendarSummary();
|
|
1113
1239
|
renderVisitorCounters();
|
|
@@ -1182,6 +1308,73 @@ const defaultExpenseItems = [
|
|
|
1182
1308
|
.replaceAll("'", "'");
|
|
1183
1309
|
}
|
|
1184
1310
|
|
|
1311
|
+
function getExpenseDetailMaxHeight(textarea) {
|
|
1312
|
+
if (!textarea) return 0;
|
|
1313
|
+
const style = window.getComputedStyle(textarea);
|
|
1314
|
+
const lineHeight = Number.parseFloat(style.lineHeight) || 18;
|
|
1315
|
+
const borderTop = Number.parseFloat(style.borderTopWidth) || 0;
|
|
1316
|
+
const borderBottom = Number.parseFloat(style.borderBottomWidth) || 0;
|
|
1317
|
+
const paddingTop = Number.parseFloat(style.paddingTop) || 0;
|
|
1318
|
+
const paddingBottom = Number.parseFloat(style.paddingBottom) || 0;
|
|
1319
|
+
return Math.round((lineHeight * EXPENSE_DETAIL_MAX_LINES) + paddingTop + paddingBottom + borderTop + borderBottom);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
function autoResizeExpenseDetailTextarea(textarea, preferredHeight = 0) {
|
|
1323
|
+
if (!textarea) return;
|
|
1324
|
+
const maxHeight = getExpenseDetailMaxHeight(textarea);
|
|
1325
|
+
textarea.style.height = "auto";
|
|
1326
|
+
const scrollHeight = Math.max(textarea.scrollHeight, 0);
|
|
1327
|
+
const targetHeight = preferredHeight > 0
|
|
1328
|
+
? Math.min(maxHeight, Math.max(scrollHeight, preferredHeight))
|
|
1329
|
+
: Math.min(maxHeight, scrollHeight);
|
|
1330
|
+
textarea.style.height = `${Math.max(0, targetHeight)}px`;
|
|
1331
|
+
textarea.style.overflowY = scrollHeight > maxHeight ? "auto" : "hidden";
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
function updateExpenseDetailCounter(textarea) {
|
|
1335
|
+
if (!textarea || !textarea.id) return;
|
|
1336
|
+
const counter = document.getElementById(`${textarea.id}Counter`);
|
|
1337
|
+
if (!counter) return;
|
|
1338
|
+
const maxLen = Number(textarea.getAttribute("maxlength") || EXPENSE_DETAIL_MAX_CHARS);
|
|
1339
|
+
const len = String(textarea.value || "").length;
|
|
1340
|
+
const remaining = Math.max(0, maxLen - len);
|
|
1341
|
+
counter.textContent = msg("expenseDetailCharsRemaining", { count: String(remaining) });
|
|
1342
|
+
counter.classList.toggle("is-limit", remaining <= Math.max(20, Math.round(maxLen * 0.05)));
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
function updateExpenseDetailTextareaUi(textarea, preferredHeight = 0) {
|
|
1346
|
+
if (!textarea) return;
|
|
1347
|
+
autoResizeExpenseDetailTextarea(textarea, preferredHeight);
|
|
1348
|
+
updateExpenseDetailCounter(textarea);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function collectExpenseDetailUiMeta(spouseKey, idx) {
|
|
1352
|
+
const wrap = document.getElementById(`${spouseKey}dw_${idx}`);
|
|
1353
|
+
const textArea = document.getElementById(`${spouseKey}d_${idx}`);
|
|
1354
|
+
const inlineHeight = textArea ? Number.parseFloat(String(textArea.style.height || "0")) : 0;
|
|
1355
|
+
return {
|
|
1356
|
+
open: wrap ? !wrap.classList.contains("is-hidden") : false,
|
|
1357
|
+
height: Number.isFinite(inlineHeight) && inlineHeight > 0 ? inlineHeight : 0
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
function applyExpenseDetailUiMeta(spouseKey, idx, meta) {
|
|
1362
|
+
const wrap = document.getElementById(`${spouseKey}dw_${idx}`);
|
|
1363
|
+
const textarea = document.getElementById(`${spouseKey}d_${idx}`);
|
|
1364
|
+
const btn = rowsSpese.querySelector(`button[data-detail-target='${spouseKey}d_${idx}']`);
|
|
1365
|
+
const open = !!(meta && meta.open);
|
|
1366
|
+
|
|
1367
|
+
if (wrap) wrap.classList.toggle("is-hidden", !open);
|
|
1368
|
+
if (btn) btn.classList.toggle("is-open", open);
|
|
1369
|
+
|
|
1370
|
+
const preferredHeight = Number(meta && meta.height);
|
|
1371
|
+
if (textarea && Number.isFinite(preferredHeight) && preferredHeight > 0) {
|
|
1372
|
+
updateExpenseDetailTextareaUi(textarea, preferredHeight);
|
|
1373
|
+
} else if (textarea) {
|
|
1374
|
+
updateExpenseDetailTextareaUi(textarea);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1185
1378
|
function normalizeExpenseItem(item, fallbackIdx = 0) {
|
|
1186
1379
|
const rawLabel = String(item && item.label ? item.label : "").trim();
|
|
1187
1380
|
const rawHelp = String(item && item.help ? item.help : "").trim();
|
|
@@ -2357,7 +2550,8 @@ const defaultExpenseItems = [
|
|
|
2357
2550
|
<button class="btn-secondary spese-detail-btn" type="button" data-detail-target="c1d_${idx}" data-detail-wrap="c1dw_${idx}" title="${tr("expenseDetailTitle")}"><span class="spese-detail-label">${tr("expenseDetailBtn")}</span></button>
|
|
2358
2551
|
</div>
|
|
2359
2552
|
<div class="spese-detail-wrap is-hidden" id="c1dw_${idx}">
|
|
2360
|
-
<textarea id="c1d_${idx}" class="spese-detail-text" rows="2" maxlength="
|
|
2553
|
+
<textarea id="c1d_${idx}" class="spese-detail-text" rows="2" maxlength="${EXPENSE_DETAIL_MAX_CHARS}" placeholder="${escapeHtml(tr("expenseDetailPlaceholder"))}" aria-describedby="c1d_${idx}Counter"></textarea>
|
|
2554
|
+
<div class="spese-detail-counter" id="c1d_${idx}Counter" aria-live="polite"></div>
|
|
2361
2555
|
</div>
|
|
2362
2556
|
<span class="spese-partial" id="p1_${idx}" title="${tr("expensePartialTitle")}">${tr("expensePartialLabel")}: ${eurTiny(0)}</span>
|
|
2363
2557
|
</div>
|
|
@@ -2369,7 +2563,8 @@ const defaultExpenseItems = [
|
|
|
2369
2563
|
<button class="btn-secondary spese-detail-btn" type="button" data-detail-target="c2d_${idx}" data-detail-wrap="c2dw_${idx}" title="${tr("expenseDetailTitle")}"><span class="spese-detail-label">${tr("expenseDetailBtn")}</span></button>
|
|
2370
2564
|
</div>
|
|
2371
2565
|
<div class="spese-detail-wrap is-hidden" id="c2dw_${idx}">
|
|
2372
|
-
<textarea id="c2d_${idx}" class="spese-detail-text" rows="2" maxlength="
|
|
2566
|
+
<textarea id="c2d_${idx}" class="spese-detail-text" rows="2" maxlength="${EXPENSE_DETAIL_MAX_CHARS}" placeholder="${escapeHtml(tr("expenseDetailPlaceholder"))}" aria-describedby="c2d_${idx}Counter"></textarea>
|
|
2567
|
+
<div class="spese-detail-counter" id="c2d_${idx}Counter" aria-live="polite"></div>
|
|
2373
2568
|
</div>
|
|
2374
2569
|
<span class="spese-partial" id="p2_${idx}" title="${tr("expensePartialTitle")}">${tr("expensePartialLabel")}: ${eurTiny(0)}</span>
|
|
2375
2570
|
</div>
|
|
@@ -2380,6 +2575,9 @@ const defaultExpenseItems = [
|
|
|
2380
2575
|
`;
|
|
2381
2576
|
rowsSpese.appendChild(rowEl);
|
|
2382
2577
|
});
|
|
2578
|
+
rowsSpese.querySelectorAll("textarea.spese-detail-text").forEach((el) => {
|
|
2579
|
+
updateExpenseDetailTextareaUi(el);
|
|
2580
|
+
});
|
|
2383
2581
|
refreshExpenseDetailButtonState();
|
|
2384
2582
|
}
|
|
2385
2583
|
|
|
@@ -2388,7 +2586,9 @@ const defaultExpenseItems = [
|
|
|
2388
2586
|
c1: num(`c1_${i}`),
|
|
2389
2587
|
c2: num(`c2_${i}`),
|
|
2390
2588
|
d1: String(document.getElementById(`c1d_${i}`)?.value || "").trim(),
|
|
2391
|
-
d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim()
|
|
2589
|
+
d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim(),
|
|
2590
|
+
d1Ui: collectExpenseDetailUiMeta("c1", i),
|
|
2591
|
+
d2Ui: collectExpenseDetailUiMeta("c2", i)
|
|
2392
2592
|
}));
|
|
2393
2593
|
}
|
|
2394
2594
|
|
|
@@ -2403,6 +2603,8 @@ const defaultExpenseItems = [
|
|
|
2403
2603
|
if (c2) c2.value = Number.isFinite(Number(row && row.c2)) ? Number(row.c2) : 0;
|
|
2404
2604
|
if (d1) d1.value = String(row && row.d1 ? row.d1 : "");
|
|
2405
2605
|
if (d2) d2.value = String(row && row.d2 ? row.d2 : "");
|
|
2606
|
+
applyExpenseDetailUiMeta("c1", i, row && row.d1Ui ? row.d1Ui : null);
|
|
2607
|
+
applyExpenseDetailUiMeta("c2", i, row && row.d2Ui ? row.d2Ui : null);
|
|
2406
2608
|
});
|
|
2407
2609
|
refreshExpenseDetailButtonState();
|
|
2408
2610
|
}
|
|
@@ -2414,6 +2616,7 @@ const defaultExpenseItems = [
|
|
|
2414
2616
|
const detailEl = document.getElementById(targetId);
|
|
2415
2617
|
const hasNote = !!(detailEl && String(detailEl.value || "").trim());
|
|
2416
2618
|
btn.classList.toggle("has-note", hasNote);
|
|
2619
|
+
btn.setAttribute("aria-label", hasNote ? `${tr("expenseDetailBtn")} ✓` : tr("expenseDetailBtn"));
|
|
2417
2620
|
});
|
|
2418
2621
|
}
|
|
2419
2622
|
|
|
@@ -2967,8 +3170,11 @@ const defaultExpenseItems = [
|
|
|
2967
3170
|
const c2Spese = expenseItems.map((_, idx) => num(`c2_${idx}`));
|
|
2968
3171
|
const c1SpeseDetails = expenseItems.map((_, idx) => String(document.getElementById(`c1d_${idx}`)?.value || "").trim());
|
|
2969
3172
|
const c2SpeseDetails = expenseItems.map((_, idx) => String(document.getElementById(`c2d_${idx}`)?.value || "").trim());
|
|
3173
|
+
const c1SpeseDetailUi = expenseItems.map((_, idx) => collectExpenseDetailUiMeta("c1", idx));
|
|
3174
|
+
const c2SpeseDetailUi = expenseItems.map((_, idx) => collectExpenseDetailUiMeta("c2", idx));
|
|
2970
3175
|
const extra1 = getExtraordinaryMonthly(1);
|
|
2971
3176
|
const extra2 = getExtraordinaryMonthly(2);
|
|
3177
|
+
const firstHome = getFirstHomeMortgageInput();
|
|
2972
3178
|
if (extra1 > 0) c1Spese.push(extra1);
|
|
2973
3179
|
if (extra2 > 0) c2Spese.push(extra2);
|
|
2974
3180
|
|
|
@@ -2989,10 +3195,16 @@ const defaultExpenseItems = [
|
|
|
2989
3195
|
aPag2: num("assegnoPagato2"),
|
|
2990
3196
|
aFam1: num("assegnoFam1"),
|
|
2991
3197
|
aFam2: num("assegnoFam2"),
|
|
3198
|
+
primaCasaMutuoEnabled: firstHome.enabled ? 1 : 0,
|
|
3199
|
+
primaCasaMutuoImporto: firstHome.amount,
|
|
3200
|
+
primaCasaAssegnataA: firstHome.assignedTo,
|
|
3201
|
+
primaCasaMutuoPerc1: firstHome.share1,
|
|
2992
3202
|
straordAnn1: num("straordAnn1"),
|
|
2993
3203
|
straordAnn2: num("straordAnn2"),
|
|
2994
3204
|
c1SpeseDetails,
|
|
2995
3205
|
c2SpeseDetails,
|
|
3206
|
+
c1SpeseDetailUi,
|
|
3207
|
+
c2SpeseDetailUi,
|
|
2996
3208
|
c1Spese,
|
|
2997
3209
|
c2Spese
|
|
2998
3210
|
};
|
|
@@ -3023,14 +3235,26 @@ const defaultExpenseItems = [
|
|
|
3023
3235
|
const aPag2 = Number(payload.aPag2 || 0);
|
|
3024
3236
|
const aFam1 = Number(payload.aFam1 || 0);
|
|
3025
3237
|
const aFam2 = Number(payload.aFam2 || 0);
|
|
3238
|
+
const primaCasaMutuoEnabled = Number(payload.primaCasaMutuoEnabled || 0) > 0;
|
|
3239
|
+
const primaCasaMutuoImporto = Math.max(0, Number(payload.primaCasaMutuoImporto || 0));
|
|
3240
|
+
const primaCasaAssegnataA = (String(payload.primaCasaAssegnataA || "") === "1" || String(payload.primaCasaAssegnataA || "") === "2")
|
|
3241
|
+
? String(payload.primaCasaAssegnataA)
|
|
3242
|
+
: "";
|
|
3243
|
+
const rawMutuoPerc1 = payload.primaCasaMutuoPerc1 === undefined ? 50 : payload.primaCasaMutuoPerc1;
|
|
3244
|
+
const primaCasaMutuoPerc1 = Math.min(100, Math.max(0, Number(rawMutuoPerc1 || 0)));
|
|
3245
|
+
const primaCasaMutuoPerc2 = 100 - primaCasaMutuoPerc1;
|
|
3246
|
+
const quotaMutuoSpese1 = primaCasaMutuoEnabled ? (primaCasaMutuoImporto * (primaCasaMutuoPerc1 / 100)) : 0;
|
|
3247
|
+
const quotaMutuoSpese2 = primaCasaMutuoEnabled ? (primaCasaMutuoImporto - quotaMutuoSpese1) : 0;
|
|
3026
3248
|
|
|
3027
3249
|
const match12 = Math.min(aPag1, aPerc2);
|
|
3028
3250
|
const match21 = Math.min(aPag2, aPerc1);
|
|
3029
3251
|
const esternoPag1 = Math.max(0, aPag1 - match12);
|
|
3030
3252
|
const esternoPag2 = Math.max(0, aPag2 - match21);
|
|
3031
3253
|
|
|
3032
|
-
const
|
|
3033
|
-
const
|
|
3254
|
+
const speseBase1 = (payload.c1Spese || []).reduce((acc, v) => acc + Number(v || 0), 0);
|
|
3255
|
+
const speseBase2 = (payload.c2Spese || []).reduce((acc, v) => acc + Number(v || 0), 0);
|
|
3256
|
+
const spese1 = speseBase1 + quotaMutuoSpese1;
|
|
3257
|
+
const spese2 = speseBase2 + quotaMutuoSpese2;
|
|
3034
3258
|
const speseTot = spese1 + spese2;
|
|
3035
3259
|
|
|
3036
3260
|
const disp1 = r1 + aPerc1 + aFam1 - aPag1 - spese1;
|
|
@@ -3071,6 +3295,31 @@ const defaultExpenseItems = [
|
|
|
3071
3295
|
assegnoDa2a1 = nonCollocatario === 2 ? contributoIndiretto : 0;
|
|
3072
3296
|
}
|
|
3073
3297
|
|
|
3298
|
+
const assegnoBaseDa1a2 = assegnoDa1a2;
|
|
3299
|
+
const assegnoBaseDa2a1 = assegnoDa2a1;
|
|
3300
|
+
|
|
3301
|
+
const primaCasaConsidered = primaCasaMutuoEnabled && primaCasaMutuoImporto > 0
|
|
3302
|
+
&& primaCasaAssegnataA !== ""
|
|
3303
|
+
&& Number(primaCasaAssegnataA) === collocatario;
|
|
3304
|
+
let primaCasaTransfer1to2 = 0;
|
|
3305
|
+
let primaCasaTransfer2to1 = 0;
|
|
3306
|
+
if (primaCasaConsidered) {
|
|
3307
|
+
if (primaCasaAssegnataA === "1") {
|
|
3308
|
+
primaCasaTransfer2to1 = Math.max(0, quotaMutuoSpese2);
|
|
3309
|
+
} else if (primaCasaAssegnataA === "2") {
|
|
3310
|
+
primaCasaTransfer1to2 = Math.max(0, quotaMutuoSpese1);
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
assegnoDa1a2 = Math.max(0, assegnoDa1a2 - primaCasaTransfer1to2);
|
|
3315
|
+
assegnoDa2a1 = Math.max(0, assegnoDa2a1 - primaCasaTransfer2to1);
|
|
3316
|
+
|
|
3317
|
+
const compensativeBenefits = [];
|
|
3318
|
+
if (aFam1 > 0.005) compensativeBenefits.push({ type: "family", to: 1, amount: aFam1 });
|
|
3319
|
+
if (aFam2 > 0.005) compensativeBenefits.push({ type: "family", to: 2, amount: aFam2 });
|
|
3320
|
+
if (primaCasaTransfer1to2 > 0.005) compensativeBenefits.push({ type: "primary-home-mortgage", from: 1, to: 2, amount: primaCasaTransfer1to2 });
|
|
3321
|
+
if (primaCasaTransfer2to1 > 0.005) compensativeBenefits.push({ type: "primary-home-mortgage", from: 2, to: 1, amount: primaCasaTransfer2to1 });
|
|
3322
|
+
|
|
3074
3323
|
const post1 = disp1 - assegnoDa1a2 + assegnoDa2a1;
|
|
3075
3324
|
const post2 = disp2 - assegnoDa2a1 + assegnoDa1a2;
|
|
3076
3325
|
|
|
@@ -3078,6 +3327,7 @@ const defaultExpenseItems = [
|
|
|
3078
3327
|
r1, r2, r1Raw, r2Raw, incomeMode, figli, perm1, perm2,
|
|
3079
3328
|
aPerc1, aPag1, aPerc2, aPag2, aFam1, aFam2,
|
|
3080
3329
|
match12, match21, esternoPag1, esternoPag2,
|
|
3330
|
+
speseBase1, speseBase2, quotaMutuoSpese1, quotaMutuoSpese2,
|
|
3081
3331
|
spese1, spese2, speseTot,
|
|
3082
3332
|
disp1, disp2, peso1, peso2,
|
|
3083
3333
|
mode, simplePerc,
|
|
@@ -3085,6 +3335,11 @@ const defaultExpenseItems = [
|
|
|
3085
3335
|
fabbisognoFigli, quotaTeorica1, quotaTeorica2,
|
|
3086
3336
|
quotaDiretta1, quotaDiretta2,
|
|
3087
3337
|
saldo1, saldo2,
|
|
3338
|
+
assegnoBaseDa1a2, assegnoBaseDa2a1,
|
|
3339
|
+
primaCasaMutuoEnabled, primaCasaMutuoImporto, primaCasaAssegnataA,
|
|
3340
|
+
primaCasaMutuoPerc1, primaCasaMutuoPerc2,
|
|
3341
|
+
primaCasaConsidered, primaCasaTransfer1to2, primaCasaTransfer2to1,
|
|
3342
|
+
compensativeBenefits,
|
|
3088
3343
|
assegnoDa1a2, assegnoDa2a1,
|
|
3089
3344
|
post1, post2
|
|
3090
3345
|
};
|
|
@@ -3167,6 +3422,7 @@ const defaultExpenseItems = [
|
|
|
3167
3422
|
if (lblStraordAnn2) lblStraordAnn2.textContent = msg("extraAnnLabel2", { spouse: c2n(), currency: currentCurrency });
|
|
3168
3423
|
if (hintStraordAnn1) hintStraordAnn1.title = msg("extraAnnHint1", { spouse: c1n() });
|
|
3169
3424
|
if (hintStraordAnn2) hintStraordAnn2.title = msg("extraAnnHint2", { spouse: c2n() });
|
|
3425
|
+
updateFirstHomeMortgageUi();
|
|
3170
3426
|
updateExtraordinaryModuleUi();
|
|
3171
3427
|
updatePermanenceCalendarSummary();
|
|
3172
3428
|
}
|
|
@@ -3176,6 +3432,71 @@ const defaultExpenseItems = [
|
|
|
3176
3432
|
return annual > 0 ? annual / 12 : 0;
|
|
3177
3433
|
}
|
|
3178
3434
|
|
|
3435
|
+
function getFirstHomeMortgageInput() {
|
|
3436
|
+
const enabled = !!document.getElementById("primaCasaMutuoEnabled")?.checked;
|
|
3437
|
+
const amount = Math.max(0, num("primaCasaMutuoImporto"));
|
|
3438
|
+
const assignedToRaw = String(document.getElementById("primaCasaAssegnataA")?.value || "").trim();
|
|
3439
|
+
const assignedTo = (assignedToRaw === "1" || assignedToRaw === "2") ? assignedToRaw : "";
|
|
3440
|
+
const share1 = Math.min(100, Math.max(0, num("primaCasaMutuoPerc1")));
|
|
3441
|
+
const share2 = 100 - share1;
|
|
3442
|
+
return { enabled, amount, assignedTo, share1, share2 };
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
function updateFirstHomeMortgageUi() {
|
|
3446
|
+
const enabledEl = document.getElementById("primaCasaMutuoEnabled");
|
|
3447
|
+
const amountEl = document.getElementById("primaCasaMutuoImporto");
|
|
3448
|
+
const assignedEl = document.getElementById("primaCasaAssegnataA");
|
|
3449
|
+
const shareEl = document.getElementById("primaCasaMutuoPerc1");
|
|
3450
|
+
const splitInfoEl = document.getElementById("primaCasaMutuoSplitInfo");
|
|
3451
|
+
const splitCenterEl = document.getElementById("primaCasaSplitCenter");
|
|
3452
|
+
const splitLeftNameEl = document.getElementById("primaCasaSplitLeftName");
|
|
3453
|
+
const splitRightNameEl = document.getElementById("primaCasaSplitRightName");
|
|
3454
|
+
const splitLeftAmountEl = document.getElementById("primaCasaSplitLeftAmount");
|
|
3455
|
+
const splitRightAmountEl = document.getElementById("primaCasaSplitRightAmount");
|
|
3456
|
+
const splitWrapEl = document.getElementById("primaCasaMutuoSliderWrap");
|
|
3457
|
+
const splitLabelEl = document.getElementById("lblPrimaCasaMutuoPerc1");
|
|
3458
|
+
const splitHintEl = document.getElementById("hintPrimaCasaMutuoPerc1");
|
|
3459
|
+
if (!enabledEl || !amountEl || !assignedEl || !shareEl) return;
|
|
3460
|
+
|
|
3461
|
+
const isEnabled = !!enabledEl.checked;
|
|
3462
|
+
amountEl.disabled = !isEnabled;
|
|
3463
|
+
assignedEl.disabled = !isEnabled;
|
|
3464
|
+
shareEl.disabled = !isEnabled;
|
|
3465
|
+
if (splitWrapEl) splitWrapEl.classList.toggle("is-disabled", !isEnabled);
|
|
3466
|
+
|
|
3467
|
+
const normalizedShare1 = Math.min(100, Math.max(0, num("primaCasaMutuoPerc1")));
|
|
3468
|
+
if (Math.abs(normalizedShare1 - Number(shareEl.value || 0)) > 0.0001) {
|
|
3469
|
+
shareEl.value = normalizedShare1.toFixed(0);
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
const share2 = 100 - normalizedShare1;
|
|
3473
|
+
const amount = Math.max(0, num("primaCasaMutuoImporto"));
|
|
3474
|
+
const quota1 = amount * (normalizedShare1 / 100);
|
|
3475
|
+
const quota2 = amount - quota1;
|
|
3476
|
+
if (splitLabelEl) splitLabelEl.textContent = msg("firstHomeSplitLabel", { spouse: c1n() });
|
|
3477
|
+
if (splitHintEl) splitHintEl.title = msg("firstHomeSplitHint", { spouse: c1n() });
|
|
3478
|
+
if (splitInfoEl) {
|
|
3479
|
+
splitInfoEl.textContent = msg("firstHomeSplitInfo", {
|
|
3480
|
+
spouse1: c1n(),
|
|
3481
|
+
spouse2: c2n(),
|
|
3482
|
+
p1: normalizedShare1.toFixed(0),
|
|
3483
|
+
p2: share2.toFixed(0)
|
|
3484
|
+
});
|
|
3485
|
+
}
|
|
3486
|
+
if (splitCenterEl) splitCenterEl.textContent = `${normalizedShare1.toFixed(0)}% / ${share2.toFixed(0)}%`;
|
|
3487
|
+
if (splitLeftNameEl) splitLeftNameEl.textContent = c1n();
|
|
3488
|
+
if (splitRightNameEl) splitRightNameEl.textContent = c2n();
|
|
3489
|
+
if (splitLeftAmountEl) splitLeftAmountEl.textContent = eur(quota1);
|
|
3490
|
+
if (splitRightAmountEl) splitRightAmountEl.textContent = eur(quota2);
|
|
3491
|
+
|
|
3492
|
+
const noneOpt = assignedEl.querySelector("option[value='']");
|
|
3493
|
+
const spouse1Opt = assignedEl.querySelector("option[value='1']");
|
|
3494
|
+
const spouse2Opt = assignedEl.querySelector("option[value='2']");
|
|
3495
|
+
if (noneOpt) noneOpt.textContent = tr("firstHomeAssignedToNone");
|
|
3496
|
+
if (spouse1Opt) spouse1Opt.textContent = msg("firstHomeAssignedToSpouse", { spouse: c1n() });
|
|
3497
|
+
if (spouse2Opt) spouse2Opt.textContent = msg("firstHomeAssignedToSpouse", { spouse: c2n() });
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3179
3500
|
function updateExtraordinaryModuleUi() {
|
|
3180
3501
|
const month1 = document.getElementById("straordMonth1");
|
|
3181
3502
|
const month2 = document.getElementById("straordMonth2");
|
|
@@ -3635,6 +3956,11 @@ const defaultExpenseItems = [
|
|
|
3635
3956
|
if (!el) return;
|
|
3636
3957
|
el.value = value;
|
|
3637
3958
|
};
|
|
3959
|
+
const setChecked = (id, value) => {
|
|
3960
|
+
const el = document.getElementById(id);
|
|
3961
|
+
if (!el) return;
|
|
3962
|
+
el.checked = !!value;
|
|
3963
|
+
};
|
|
3638
3964
|
|
|
3639
3965
|
setVal("nome1", payload._nome1 || c1n());
|
|
3640
3966
|
setVal("nome2", payload._nome2 || c2n());
|
|
@@ -3660,6 +3986,10 @@ const defaultExpenseItems = [
|
|
|
3660
3986
|
setVal("assegnoPercepito2", Number(payload.aPerc2 || 0));
|
|
3661
3987
|
setVal("assegnoPagato2", Number(payload.aPag2 || 0));
|
|
3662
3988
|
setVal("assegnoFam2", Number(payload.aFam2 || 0));
|
|
3989
|
+
setChecked("primaCasaMutuoEnabled", Number(payload.primaCasaMutuoEnabled || 0) > 0);
|
|
3990
|
+
setVal("primaCasaMutuoImporto", Number(payload.primaCasaMutuoImporto || 0));
|
|
3991
|
+
setVal("primaCasaAssegnataA", (String(payload.primaCasaAssegnataA || "") === "1" || String(payload.primaCasaAssegnataA || "") === "2") ? String(payload.primaCasaAssegnataA) : "");
|
|
3992
|
+
setVal("primaCasaMutuoPerc1", Math.min(100, Math.max(0, Number((payload.primaCasaMutuoPerc1 === undefined ? 50 : payload.primaCasaMutuoPerc1) || 0))));
|
|
3663
3993
|
setVal("straordAnn1", Number(payload.straordAnn1 || 0));
|
|
3664
3994
|
setVal("straordAnn2", Number(payload.straordAnn2 || 0));
|
|
3665
3995
|
|
|
@@ -3667,6 +3997,8 @@ const defaultExpenseItems = [
|
|
|
3667
3997
|
const c2Spese = Array.isArray(payload.c2Spese) ? payload.c2Spese : [];
|
|
3668
3998
|
const c1SpeseDetails = Array.isArray(payload.c1SpeseDetails) ? payload.c1SpeseDetails : [];
|
|
3669
3999
|
const c2SpeseDetails = Array.isArray(payload.c2SpeseDetails) ? payload.c2SpeseDetails : [];
|
|
4000
|
+
const c1SpeseDetailUi = Array.isArray(payload.c1SpeseDetailUi) ? payload.c1SpeseDetailUi : [];
|
|
4001
|
+
const c2SpeseDetailUi = Array.isArray(payload.c2SpeseDetailUi) ? payload.c2SpeseDetailUi : [];
|
|
3670
4002
|
expenseItems.forEach((_, i) => {
|
|
3671
4003
|
const c1 = document.getElementById(`c1_${i}`);
|
|
3672
4004
|
const c2 = document.getElementById(`c2_${i}`);
|
|
@@ -3676,6 +4008,8 @@ const defaultExpenseItems = [
|
|
|
3676
4008
|
if (c2) c2.value = Number(c2Spese[i] || 0);
|
|
3677
4009
|
if (d1) d1.value = String(c1SpeseDetails[i] || "");
|
|
3678
4010
|
if (d2) d2.value = String(c2SpeseDetails[i] || "");
|
|
4011
|
+
applyExpenseDetailUiMeta("c1", i, c1SpeseDetailUi[i]);
|
|
4012
|
+
applyExpenseDetailUiMeta("c2", i, c2SpeseDetailUi[i]);
|
|
3679
4013
|
});
|
|
3680
4014
|
refreshExpenseDetailButtonState();
|
|
3681
4015
|
|
|
@@ -3686,6 +4020,7 @@ const defaultExpenseItems = [
|
|
|
3686
4020
|
};
|
|
3687
4021
|
|
|
3688
4022
|
updateSpouseLabels();
|
|
4023
|
+
updateFirstHomeMortgageUi();
|
|
3689
4024
|
if (payload._permanenceCalendar && typeof payload._permanenceCalendar === "object") {
|
|
3690
4025
|
importPermanenceCalendarState(payload._permanenceCalendar);
|
|
3691
4026
|
syncPermanenza("calendar");
|
|
@@ -3810,6 +4145,22 @@ const defaultExpenseItems = [
|
|
|
3810
4145
|
const peso2Pct = (m.peso2 * 100).toFixed(1);
|
|
3811
4146
|
const days1 = ((m.perm1 / 100) * 30).toFixed(1);
|
|
3812
4147
|
const days2 = ((m.perm2 / 100) * 30).toFixed(1);
|
|
4148
|
+
const compBenefits = getCompensativeBenefitRows(m, c1Name, c2Name);
|
|
4149
|
+
const compBenefitsRowsHtml = compBenefits.length
|
|
4150
|
+
? compBenefits.map((row) => `<tr><td>${escapeHtml(row.label)}</td><td class="num">${eur(row.amount)}</td></tr>`).join("")
|
|
4151
|
+
: `<tr><td colspan="2">${tr("pdfCompBenefitsNone")}</td></tr>`;
|
|
4152
|
+
const primaryHomeAssignedLabel = m.primaCasaAssegnataA === "1"
|
|
4153
|
+
? c1NameEsc
|
|
4154
|
+
: m.primaCasaAssegnataA === "2"
|
|
4155
|
+
? c2NameEsc
|
|
4156
|
+
: tr("pdfPrimaryHomeNotDeclared");
|
|
4157
|
+
const primaryHomeSummaryRows = m.primaCasaMutuoEnabled
|
|
4158
|
+
? `
|
|
4159
|
+
<tr><td>${tr("pdfPrimaryHomeAssignedTo")}</td><td>${primaryHomeAssignedLabel}</td></tr>
|
|
4160
|
+
<tr><td>${tr("pdfPrimaryHomeMonthlyAmount")}</td><td>${eur(m.primaCasaMutuoImporto || 0)}</td></tr>
|
|
4161
|
+
<tr><td>${tr("pdfPrimaryHomeSplit")}</td><td>${c1NameEsc} ${(m.primaCasaMutuoPerc1 || 0).toFixed(0)}% · ${c2NameEsc} ${(m.primaCasaMutuoPerc2 || 0).toFixed(0)}%</td></tr>
|
|
4162
|
+
<tr><td>${tr("pdfPrimaryHomeAppliedOnlyColl")}</td><td>${m.primaCasaConsidered ? "OK" : tr("pdfPrimaryHomeNotDeclared")}</td></tr>`
|
|
4163
|
+
: `<tr><td>${tr("pdfPrimaryHomeMortgage")}</td><td>${tr("pdfPrimaryHomeNotDeclared")}</td></tr>`;
|
|
3813
4164
|
const isAssegno1 = m.assegnoDa1a2 > 0.005;
|
|
3814
4165
|
const isAssegno2 = m.assegnoDa2a1 > 0.005;
|
|
3815
4166
|
const n1 = escapeHtml(c1n());
|
|
@@ -3824,18 +4175,24 @@ const defaultExpenseItems = [
|
|
|
3824
4175
|
let resultDetail;
|
|
3825
4176
|
if (isAssegno1) {
|
|
3826
4177
|
resultHtml = `
|
|
3827
|
-
<div class="spieg-
|
|
3828
|
-
<div class="spieg-
|
|
4178
|
+
<div class="spieg-result-flow">${n1} → ${n2}</div>
|
|
4179
|
+
<div class="spieg-result-formula">${n1}: ${eur(m.quotaTeorica1)} − ${eur(m.quotaDiretta1)}</div>
|
|
4180
|
+
<div class="spieg-result-amount ok">${eur(m.assegnoDa1a2)}</div>
|
|
3829
4181
|
`;
|
|
3830
4182
|
resultDetail = tr("spiegDetailResultTransfer");
|
|
3831
4183
|
} else if (isAssegno2) {
|
|
3832
4184
|
resultHtml = `
|
|
3833
|
-
<div class="spieg-
|
|
3834
|
-
<div class="spieg-
|
|
4185
|
+
<div class="spieg-result-flow">${n2} → ${n1}</div>
|
|
4186
|
+
<div class="spieg-result-formula">${n2}: ${eur(m.quotaTeorica2)} − ${eur(m.quotaDiretta2)}</div>
|
|
4187
|
+
<div class="spieg-result-amount ok">${eur(m.assegnoDa2a1)}</div>
|
|
3835
4188
|
`;
|
|
3836
4189
|
resultDetail = tr("spiegDetailResultTransfer");
|
|
3837
4190
|
} else {
|
|
3838
|
-
|
|
4191
|
+
const benefitRows = getCompensativeBenefitRows(m, c1n(), c2n());
|
|
4192
|
+
const benefitsHtml = benefitRows.length
|
|
4193
|
+
? `<div class="spieg-line" style="margin-top:6px"><strong>${tr("calcCompBenefitsLabel")}:</strong> ${benefitRows.map((row) => `${escapeHtml(row.label)} (${eur(row.amount)})`).join(" | ")}</div>`
|
|
4194
|
+
: "";
|
|
4195
|
+
resultHtml = `<div class="spieg-result-empty ok">${tr("calcNoTransferSuggested")}</div>${benefitsHtml}`;
|
|
3839
4196
|
resultDetail = tr("spiegDetailResultNoTransfer");
|
|
3840
4197
|
}
|
|
3841
4198
|
|
|
@@ -3844,27 +4201,43 @@ const defaultExpenseItems = [
|
|
|
3844
4201
|
<summary class="spieg-title">${tr("spiegTitle")}</summary>
|
|
3845
4202
|
<div class="spieg-grid">
|
|
3846
4203
|
<div class="spieg-item">
|
|
3847
|
-
<div class="spieg-item-label">${tr("spiegRedditiLabel")} ${infoTip(tr("spiegDetailIncome"))}</div>
|
|
4204
|
+
<div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">💸</span>${tr("spiegRedditiLabel")} ${infoTip(tr("spiegDetailIncome"))}</div>
|
|
3848
4205
|
<div class="spieg-item-body">
|
|
3849
|
-
<div class="spieg-
|
|
3850
|
-
|
|
4206
|
+
<div class="spieg-people">
|
|
4207
|
+
<div class="spieg-person spieg-person--left">
|
|
4208
|
+
<div class="spieg-person-name">${n1}</div>
|
|
4209
|
+
<div class="spieg-person-value">${eur(m.disp1)}</div>
|
|
4210
|
+
<div class="spieg-person-sub">${tr("pdfWeight")}: ${peso1Pct}%</div>
|
|
4211
|
+
</div>
|
|
4212
|
+
<div class="spieg-person spieg-person--right">
|
|
4213
|
+
<div class="spieg-person-name">${n2}</div>
|
|
4214
|
+
<div class="spieg-person-value">${eur(m.disp2)}</div>
|
|
4215
|
+
<div class="spieg-person-sub">${tr("pdfWeight")}: ${peso2Pct}%</div>
|
|
4216
|
+
</div>
|
|
4217
|
+
</div>
|
|
3851
4218
|
</div>
|
|
3852
4219
|
</div>
|
|
3853
4220
|
<div class="spieg-item">
|
|
3854
|
-
<div class="spieg-item-label">${tr("spiegSpeseLabel")} ${infoTip(tr("spiegDetailExpense"))}</div>
|
|
4221
|
+
<div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">📝</span>${tr("spiegSpeseLabel")} ${infoTip(tr("spiegDetailExpense"))}</div>
|
|
3855
4222
|
<div class="spieg-item-body">
|
|
3856
|
-
<div class="spieg-
|
|
4223
|
+
<div class="spieg-equation">
|
|
4224
|
+
<span class="spieg-pill">${eur(m.speseTot)}</span>
|
|
4225
|
+
<span class="spieg-op">×</span>
|
|
4226
|
+
<span class="spieg-pill">35%</span>
|
|
4227
|
+
<span class="spieg-op">=</span>
|
|
4228
|
+
<span class="spieg-pill spieg-pill--result">${eur(m.fabbisognoFigli)}</span>
|
|
4229
|
+
</div>
|
|
3857
4230
|
</div>
|
|
3858
4231
|
</div>
|
|
3859
4232
|
<div class="spieg-item">
|
|
3860
|
-
<div class="spieg-item-label">${tr("spiegPermLabel")} ${infoTip(tr("spiegDetailPerm"))}</div>
|
|
4233
|
+
<div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">📅</span>${tr("spiegPermLabel")} ${infoTip(tr("spiegDetailPerm"))}</div>
|
|
3861
4234
|
<div class="spieg-item-body">
|
|
3862
|
-
<div class="spieg-line"><span>${n1}
|
|
3863
|
-
<div class="spieg-line"><span>${n2}
|
|
4235
|
+
<div class="spieg-line spieg-line--kv"><span class="spieg-k">${n1}</span><span class="spieg-v">${m.perm1.toFixed(0)}% (${days1} ${tr("langDaysSuffix")}) → ${eur(m.quotaDiretta1)}</span></div>
|
|
4236
|
+
<div class="spieg-line spieg-line--kv"><span class="spieg-k">${n2}</span><span class="spieg-v">${m.perm2.toFixed(0)}% (${days2} ${tr("langDaysSuffix")}) → ${eur(m.quotaDiretta2)}</span></div>
|
|
3864
4237
|
</div>
|
|
3865
4238
|
</div>
|
|
3866
4239
|
<div class="spieg-item spieg-item--result">
|
|
3867
|
-
<div class="spieg-item-label">${tr("spiegResultLabel")} ${infoTip(resultDetail)}</div>
|
|
4240
|
+
<div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">🎯</span>${tr("spiegResultLabel")} ${infoTip(resultDetail)}</div>
|
|
3868
4241
|
<div class="spieg-item-body spieg-item-body--result">${resultHtml}</div>
|
|
3869
4242
|
</div>
|
|
3870
4243
|
</div>
|
|
@@ -3884,6 +4257,31 @@ const defaultExpenseItems = [
|
|
|
3884
4257
|
return tr("calcModeGenovaName");
|
|
3885
4258
|
}
|
|
3886
4259
|
|
|
4260
|
+
function getCompensativeBenefitRows(m, name1 = c1n(), name2 = c2n()) {
|
|
4261
|
+
const rows = Array.isArray(m && m.compensativeBenefits) ? m.compensativeBenefits : [];
|
|
4262
|
+
return rows
|
|
4263
|
+
.filter((row) => row && Number(row.amount || 0) > 0.005)
|
|
4264
|
+
.map((row) => {
|
|
4265
|
+
const amount = Number(row.amount || 0);
|
|
4266
|
+
if (row.type === "family") {
|
|
4267
|
+
const spouse = Number(row.to) === 2 ? name2 : name1;
|
|
4268
|
+
return { label: msg("calcBenefitFamilyAllowance", { spouse }), amount };
|
|
4269
|
+
}
|
|
4270
|
+
if (row.type === "primary-home-mortgage") {
|
|
4271
|
+
const payer = Number(row.from) === 2 ? name2 : name1;
|
|
4272
|
+
const receiver = Number(row.to) === 2 ? name2 : name1;
|
|
4273
|
+
return { label: msg("calcBenefitPrimaryHomeMortgage", { payer, receiver }), amount };
|
|
4274
|
+
}
|
|
4275
|
+
return { label: tr("calcCompBenefitsLabel"), amount };
|
|
4276
|
+
});
|
|
4277
|
+
}
|
|
4278
|
+
|
|
4279
|
+
function formatCompensativeBenefitsInline(m, name1 = c1n(), name2 = c2n()) {
|
|
4280
|
+
return getCompensativeBenefitRows(m, name1, name2)
|
|
4281
|
+
.map((row) => `${row.label}: ${eur(row.amount)}`)
|
|
4282
|
+
.join(" | ");
|
|
4283
|
+
}
|
|
4284
|
+
|
|
3887
4285
|
function calculate(model = null) {
|
|
3888
4286
|
const m = model || computeModel();
|
|
3889
4287
|
const formulaNote = document.getElementById("formulaNote");
|
|
@@ -3895,6 +4293,7 @@ const defaultExpenseItems = [
|
|
|
3895
4293
|
const normProfileName = escapeHtml(getSelectedNormProfileLabel());
|
|
3896
4294
|
const negotiationPayerName = m.assegnoDa1a2 > 0.005 ? c1n() : (m.assegnoDa2a1 > 0.005 ? c2n() : c1n());
|
|
3897
4295
|
const negotiationReceiverName = m.assegnoDa1a2 > 0.005 ? c2n() : (m.assegnoDa2a1 > 0.005 ? c1n() : c2n());
|
|
4296
|
+
const benefitsInline = formatCompensativeBenefitsInline(m, c1n(), c2n());
|
|
3898
4297
|
|
|
3899
4298
|
let modeSpecific = "";
|
|
3900
4299
|
if (m.mode === "simple") {
|
|
@@ -3953,13 +4352,33 @@ const defaultExpenseItems = [
|
|
|
3953
4352
|
${m.incomeMode === "cu" ? `<br /><strong>${tr("calcIncomeBaseNote")}</strong> ${tr("cuNetNoteText")}` : ""}
|
|
3954
4353
|
`;
|
|
3955
4354
|
|
|
3956
|
-
let
|
|
4355
|
+
let mainHtml = `<span class="result-main-line">${escapeHtml(tr("calcNoTransferSuggested"))}</span>`;
|
|
3957
4356
|
if (m.assegnoDa1a2 > 0.005) {
|
|
3958
|
-
|
|
4357
|
+
mainHtml = `
|
|
4358
|
+
<span class="result-main-flow">${escapeHtml(c1n())} → ${escapeHtml(c2n())}</span>
|
|
4359
|
+
<span class="result-main-amount">${eur(m.assegnoDa1a2)} ${escapeHtml(tr("pdfPerMonth"))}</span>
|
|
4360
|
+
`;
|
|
3959
4361
|
} else if (m.assegnoDa2a1 > 0.005) {
|
|
3960
|
-
|
|
4362
|
+
mainHtml = `
|
|
4363
|
+
<span class="result-main-flow">${escapeHtml(c2n())} → ${escapeHtml(c1n())}</span>
|
|
4364
|
+
<span class="result-main-amount">${eur(m.assegnoDa2a1)} ${escapeHtml(tr("pdfPerMonth"))}</span>
|
|
4365
|
+
`;
|
|
4366
|
+
} else {
|
|
4367
|
+
const benefitRows = getCompensativeBenefitRows(m, c1n(), c2n());
|
|
4368
|
+
if (benefitRows.length) {
|
|
4369
|
+
const benefitsHtml = benefitRows
|
|
4370
|
+
.map((row) => `<li><span>${escapeHtml(row.label)}</span><strong>${eur(row.amount)}</strong></li>`)
|
|
4371
|
+
.join("");
|
|
4372
|
+
mainHtml = `
|
|
4373
|
+
<span class="result-main-line">${escapeHtml(tr("calcNoTransferSuggested"))}</span>
|
|
4374
|
+
<span class="result-main-sub">${escapeHtml(tr("calcCompBenefitsLabel"))}</span>
|
|
4375
|
+
<ul class="result-benefits-list">${benefitsHtml}</ul>
|
|
4376
|
+
`;
|
|
4377
|
+
} else if (benefitsInline) {
|
|
4378
|
+
mainHtml = `<span class="result-main-line">${escapeHtml(msg("calcNoTransferWithBenefits", { benefits: benefitsInline }))}</span>`;
|
|
4379
|
+
}
|
|
3961
4380
|
}
|
|
3962
|
-
resultMain.
|
|
4381
|
+
resultMain.innerHTML = mainHtml;
|
|
3963
4382
|
|
|
3964
4383
|
kpi.innerHTML = "";
|
|
3965
4384
|
|
|
@@ -3977,6 +4396,10 @@ const defaultExpenseItems = [
|
|
|
3977
4396
|
[tr("pdfAmountPerChild"), eur((Math.max(m.assegnoDa1a2, m.assegnoDa2a1)) / m.figli), "warn"]
|
|
3978
4397
|
];
|
|
3979
4398
|
|
|
4399
|
+
if (benefitsInline) {
|
|
4400
|
+
items.push([tr("calcCompBenefitsLabel"), benefitsInline, "warn"]);
|
|
4401
|
+
}
|
|
4402
|
+
|
|
3980
4403
|
if (m.incomeMode === "cu") {
|
|
3981
4404
|
const ratio1 = m.r1Raw > 0 ? ((m.r1 * 12 / m.r1Raw) * 100) : 0;
|
|
3982
4405
|
const ratio2 = m.r2Raw > 0 ? ((m.r2 * 12 / m.r2Raw) * 100) : 0;
|
|
@@ -4089,6 +4512,11 @@ const defaultExpenseItems = [
|
|
|
4089
4512
|
<div class="pdf-explain-formula">${c2NameEsc}: ${eur(m.quotaTeorica2)} − ${eur(m.quotaDiretta2)}</div>
|
|
4090
4513
|
<div class="pdf-explain-amount">${eur(m.assegnoDa2a1)}</div>
|
|
4091
4514
|
`;
|
|
4515
|
+
} else if (compBenefits.length) {
|
|
4516
|
+
explainResultHtml = `
|
|
4517
|
+
<div class="pdf-explain-result-empty">${tr("calcNoTransferSuggested")}</div>
|
|
4518
|
+
<div class="pdf-explain-formula"><strong>${tr("calcCompBenefitsLabel")}:</strong> ${compBenefits.map((row) => `${escapeHtml(row.label)} (${eur(row.amount)})`).join(" | ")}</div>
|
|
4519
|
+
`;
|
|
4092
4520
|
}
|
|
4093
4521
|
|
|
4094
4522
|
const extraSpese1Monthly = getExtraordinaryMonthly(1);
|
|
@@ -4097,7 +4525,7 @@ const defaultExpenseItems = [
|
|
|
4097
4525
|
const el = document.getElementById(`${spouseKey}d_${idx}`);
|
|
4098
4526
|
const raw = String(el && el.value ? el.value : "").trim();
|
|
4099
4527
|
if (!raw) return "";
|
|
4100
|
-
return raw
|
|
4528
|
+
return `${escapeHtml(raw)} <span class="expense-detail-meta">(${raw.length}/${EXPENSE_DETAIL_MAX_CHARS})</span>`;
|
|
4101
4529
|
};
|
|
4102
4530
|
const speseRowsBase = expenseItems.map((item, i) => {
|
|
4103
4531
|
const c1 = num(`c1_${i}`);
|
|
@@ -4105,8 +4533,8 @@ const defaultExpenseItems = [
|
|
|
4105
4533
|
const d1 = formatExpenseDetail(i, "c1");
|
|
4106
4534
|
const d2 = formatExpenseDetail(i, "c2");
|
|
4107
4535
|
const details = [
|
|
4108
|
-
d1 ? `${escapeHtml(c1n())}: ${
|
|
4109
|
-
d2 ? `${escapeHtml(c2n())}: ${
|
|
4536
|
+
d1 ? `${escapeHtml(c1n())}: ${d1}` : "",
|
|
4537
|
+
d2 ? `${escapeHtml(c2n())}: ${d2}` : ""
|
|
4110
4538
|
].filter(Boolean).join("<br>");
|
|
4111
4539
|
return `<tr>
|
|
4112
4540
|
<td>${item.label}</td>
|
|
@@ -4125,7 +4553,16 @@ const defaultExpenseItems = [
|
|
|
4125
4553
|
<td class="expense-detail-cell">–</td>
|
|
4126
4554
|
</tr>`
|
|
4127
4555
|
: "";
|
|
4128
|
-
const
|
|
4556
|
+
const firstHomeExpenseRow = (m.quotaMutuoSpese1 > 0.005 || m.quotaMutuoSpese2 > 0.005)
|
|
4557
|
+
? `<tr>
|
|
4558
|
+
<td>${tr("pdfPrimaryHomeMortgage")}</td>
|
|
4559
|
+
<td class="num">${m.quotaMutuoSpese1 > 0.005 ? eur(m.quotaMutuoSpese1) : "–"}</td>
|
|
4560
|
+
<td class="num">${m.quotaMutuoSpese2 > 0.005 ? eur(m.quotaMutuoSpese2) : "–"}</td>
|
|
4561
|
+
<td class="num bold">${eur((m.quotaMutuoSpese1 || 0) + (m.quotaMutuoSpese2 || 0))}</td>
|
|
4562
|
+
<td class="expense-detail-cell">${tr("firstHomeAssignedToLabel")}: ${primaryHomeAssignedLabel}</td>
|
|
4563
|
+
</tr>`
|
|
4564
|
+
: "";
|
|
4565
|
+
const speseRows = speseRowsBase + firstHomeExpenseRow + extraSpeseRow;
|
|
4129
4566
|
|
|
4130
4567
|
const scenarioMetrics = [
|
|
4131
4568
|
{ label: tr("scenarioColMode"), val: (sm) => getModeName(sm.mode, sm.simplePerc), fmt: (v) => escapeHtml(v), numeric: false },
|
|
@@ -4309,6 +4746,7 @@ const defaultExpenseItems = [
|
|
|
4309
4746
|
tr:nth-child(even) td { background: #f5faf9; }
|
|
4310
4747
|
.num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
4311
4748
|
.bold { font-weight: 700; }
|
|
4749
|
+
.expense-detail-meta { color: #5f7873; font-size: 7pt; font-weight: 600; white-space: nowrap; }
|
|
4312
4750
|
.total-row td { background: #ddf0ec !important; font-weight: 700; border-top: 1.5px solid #74c3b9; }
|
|
4313
4751
|
|
|
4314
4752
|
/* ── TWO-COL LAYOUT ── */
|
|
@@ -4540,6 +4978,7 @@ const defaultExpenseItems = [
|
|
|
4540
4978
|
<tr><td>${tr("pdfChildrenCount")}</td><td>${m.figli}</td></tr>
|
|
4541
4979
|
<tr><td>${tr("pdfPermanence")} ${c1n()}</td><td>${m.perm1.toFixed(0)}%</td></tr>
|
|
4542
4980
|
<tr><td>${tr("pdfPermanence")} ${c2n()}</td><td>${m.perm2.toFixed(0)}%</td></tr>
|
|
4981
|
+
${primaryHomeSummaryRows}
|
|
4543
4982
|
</tbody>
|
|
4544
4983
|
</table>
|
|
4545
4984
|
<table class="data-table">
|
|
@@ -4616,6 +5055,21 @@ const defaultExpenseItems = [
|
|
|
4616
5055
|
</div>
|
|
4617
5056
|
</div>
|
|
4618
5057
|
|
|
5058
|
+
<div class="section">
|
|
5059
|
+
<div class="section-title">${tr("pdfCompBenefitsSection")}</div>
|
|
5060
|
+
<table>
|
|
5061
|
+
<thead>
|
|
5062
|
+
<tr>
|
|
5063
|
+
<th>${tr("pdfCompBenefitsItem")}</th>
|
|
5064
|
+
<th class="num">${msg("pdfCompBenefitsAmount", { currency: currentCurrency })}</th>
|
|
5065
|
+
</tr>
|
|
5066
|
+
</thead>
|
|
5067
|
+
<tbody>
|
|
5068
|
+
${compBenefitsRowsHtml}
|
|
5069
|
+
</tbody>
|
|
5070
|
+
</table>
|
|
5071
|
+
</div>
|
|
5072
|
+
|
|
4619
5073
|
<!-- KPI -->
|
|
4620
5074
|
<div class="section">
|
|
4621
5075
|
<div class="section-title">${tr("pdfKpiSection")}</div>
|
|
@@ -4797,6 +5251,10 @@ ${scenarioLab.length ? `
|
|
|
4797
5251
|
assegnoPercepito2: num("assegnoPercepito2"),
|
|
4798
5252
|
assegnoPagato2: num("assegnoPagato2"),
|
|
4799
5253
|
assegnoFam2: num("assegnoFam2"),
|
|
5254
|
+
primaCasaMutuoEnabled: document.getElementById("primaCasaMutuoEnabled")?.checked ? 1 : 0,
|
|
5255
|
+
primaCasaMutuoImporto: num("primaCasaMutuoImporto"),
|
|
5256
|
+
primaCasaAssegnataA: String(document.getElementById("primaCasaAssegnataA")?.value || ""),
|
|
5257
|
+
primaCasaMutuoPerc1: num("primaCasaMutuoPerc1"),
|
|
4800
5258
|
straordAnn1: num("straordAnn1"),
|
|
4801
5259
|
straordAnn2: num("straordAnn2")
|
|
4802
5260
|
};
|
|
@@ -4804,7 +5262,9 @@ ${scenarioLab.length ? `
|
|
|
4804
5262
|
c1: num(`c1_${i}`),
|
|
4805
5263
|
c2: num(`c2_${i}`),
|
|
4806
5264
|
d1: String(document.getElementById(`c1d_${i}`)?.value || "").trim(),
|
|
4807
|
-
d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim()
|
|
5265
|
+
d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim(),
|
|
5266
|
+
d1Ui: collectExpenseDetailUiMeta("c1", i),
|
|
5267
|
+
d2Ui: collectExpenseDetailUiMeta("c2", i)
|
|
4808
5268
|
}));
|
|
4809
5269
|
const expenseItemsState = expenseItems.map((item) => ({ label: item.label, help: item.help }));
|
|
4810
5270
|
const scenariosState = scenarioLab.map((scenario, idx) => ({
|
|
@@ -4835,7 +5295,12 @@ ${scenarioLab.length ? `
|
|
|
4835
5295
|
if (!state || !state.base || !state.spese) return;
|
|
4836
5296
|
Object.entries(state.base).forEach(([k, v]) => {
|
|
4837
5297
|
const el = document.getElementById(k);
|
|
4838
|
-
if (el)
|
|
5298
|
+
if (!el) return;
|
|
5299
|
+
if (el.type === "checkbox") {
|
|
5300
|
+
el.checked = Number(v || 0) > 0;
|
|
5301
|
+
} else {
|
|
5302
|
+
el.value = v;
|
|
5303
|
+
}
|
|
4839
5304
|
});
|
|
4840
5305
|
if (Array.isArray(state.expenseItems) && state.expenseItems.length) {
|
|
4841
5306
|
expenseItems = state.expenseItems.map((item, idx) => normalizeExpenseItem(item, idx));
|
|
@@ -4876,6 +5341,7 @@ ${scenarioLab.length ? `
|
|
|
4876
5341
|
applyStaticTranslations();
|
|
4877
5342
|
applyUiViewStateToDom();
|
|
4878
5343
|
importPermanenceCalendarState(state.permanenceCalendar);
|
|
5344
|
+
updateFirstHomeMortgageUi();
|
|
4879
5345
|
updateSpouseLabels();
|
|
4880
5346
|
buildExpenseRows();
|
|
4881
5347
|
syncPermanenza("calendar");
|
|
@@ -4888,16 +5354,22 @@ ${scenarioLab.length ? `
|
|
|
4888
5354
|
if (c2) c2.value = row.c2;
|
|
4889
5355
|
if (d1) d1.value = String(row && row.d1 ? row.d1 : "");
|
|
4890
5356
|
if (d2) d2.value = String(row && row.d2 ? row.d2 : "");
|
|
5357
|
+
applyExpenseDetailUiMeta("c1", i, row && row.d1Ui ? row.d1Ui : null);
|
|
5358
|
+
applyExpenseDetailUiMeta("c2", i, row && row.d2Ui ? row.d2Ui : null);
|
|
4891
5359
|
});
|
|
4892
5360
|
refreshExpenseDetailButtonState();
|
|
4893
5361
|
}
|
|
4894
5362
|
|
|
4895
5363
|
function resetAll() {
|
|
4896
|
-
document.querySelectorAll("input[type='number'], input[data-numeric='1']").forEach((el) => {
|
|
5364
|
+
document.querySelectorAll("input[type='number'], input[type='range'], input[data-numeric='1']").forEach((el) => {
|
|
4897
5365
|
if (el.id !== "perm2") {
|
|
4898
5366
|
el.value = el.defaultValue || 0;
|
|
4899
5367
|
}
|
|
4900
5368
|
});
|
|
5369
|
+
const firstHomeEnabled = document.getElementById("primaCasaMutuoEnabled");
|
|
5370
|
+
const firstHomeAssigned = document.getElementById("primaCasaAssegnataA");
|
|
5371
|
+
if (firstHomeEnabled) firstHomeEnabled.checked = !!firstHomeEnabled.defaultChecked;
|
|
5372
|
+
if (firstHomeAssigned) firstHomeAssigned.value = "";
|
|
4901
5373
|
permanenceCalendarState.byMonth = {};
|
|
4902
5374
|
selectedScenarioIdx = -1;
|
|
4903
5375
|
uiViewState.spiegOpen = true;
|
|
@@ -4911,6 +5383,7 @@ ${scenarioLab.length ? `
|
|
|
4911
5383
|
renderPermanenceCalendar(monthValue);
|
|
4912
5384
|
applyPermanenceFromCalendar(monthValue, { silentRender: true });
|
|
4913
5385
|
applyUiViewStateToDom();
|
|
5386
|
+
updateFirstHomeMortgageUi();
|
|
4914
5387
|
syncPermanenza();
|
|
4915
5388
|
renderAll();
|
|
4916
5389
|
}
|
|
@@ -5026,7 +5499,10 @@ ${scenarioLab.length ? `
|
|
|
5026
5499
|
const willOpen = wrap.classList.contains("is-hidden");
|
|
5027
5500
|
wrap.classList.toggle("is-hidden", !willOpen);
|
|
5028
5501
|
detailBtn.classList.toggle("is-open", willOpen);
|
|
5029
|
-
if (
|
|
5502
|
+
if (target) {
|
|
5503
|
+
updateExpenseDetailTextareaUi(target);
|
|
5504
|
+
if (willOpen) target.focus();
|
|
5505
|
+
}
|
|
5030
5506
|
}
|
|
5031
5507
|
return;
|
|
5032
5508
|
}
|
|
@@ -5039,6 +5515,7 @@ ${scenarioLab.length ? `
|
|
|
5039
5515
|
|
|
5040
5516
|
rowsSpese.addEventListener("input", (e) => {
|
|
5041
5517
|
if (e.target && e.target.matches("textarea.spese-detail-text")) {
|
|
5518
|
+
updateExpenseDetailTextareaUi(e.target);
|
|
5042
5519
|
refreshExpenseDetailButtonState();
|
|
5043
5520
|
}
|
|
5044
5521
|
});
|
|
@@ -5145,11 +5622,13 @@ ${scenarioLab.length ? `
|
|
|
5145
5622
|
}
|
|
5146
5623
|
|
|
5147
5624
|
document.addEventListener("input", (e) => {
|
|
5148
|
-
if (e.target && e.target.matches("input[type='number'], input[data-numeric='1']")) {
|
|
5625
|
+
if (e.target && e.target.matches("input[type='number'], input[type='range'], input[data-numeric='1']")) {
|
|
5149
5626
|
if (e.target.id === "perm1") {
|
|
5150
5627
|
syncPermanenza("perm1");
|
|
5151
5628
|
} else if (e.target.id === "perm2") {
|
|
5152
5629
|
syncPermanenza("perm2");
|
|
5630
|
+
} else if (e.target.id === "primaCasaMutuoPerc1" || e.target.id === "primaCasaMutuoImporto") {
|
|
5631
|
+
updateFirstHomeMortgageUi();
|
|
5153
5632
|
} else if (e.target.id === "reddito1" || e.target.id === "reddito2") {
|
|
5154
5633
|
const activeMode = document.getElementById("incomeMode")?.value || "monthly";
|
|
5155
5634
|
incomeValuesByMode[activeMode] = {
|
|
@@ -5170,6 +5649,9 @@ ${scenarioLab.length ? `
|
|
|
5170
5649
|
}
|
|
5171
5650
|
updateModeUi();
|
|
5172
5651
|
renderAll();
|
|
5652
|
+
} else if (e.target && (e.target.id === "primaCasaMutuoEnabled" || e.target.id === "primaCasaAssegnataA")) {
|
|
5653
|
+
updateFirstHomeMortgageUi();
|
|
5654
|
+
renderAll();
|
|
5173
5655
|
}
|
|
5174
5656
|
});
|
|
5175
5657
|
|
|
@@ -5213,6 +5695,7 @@ ${scenarioLab.length ? `
|
|
|
5213
5695
|
updateAuthUi();
|
|
5214
5696
|
renderCloudHistoryPanel();
|
|
5215
5697
|
applyUiViewStateToDom();
|
|
5698
|
+
updateFirstHomeMortgageUi();
|
|
5216
5699
|
syncPermanenza();
|
|
5217
5700
|
incomeModeLast = document.getElementById("incomeMode").value || "monthly";
|
|
5218
5701
|
incomeValuesByMode[incomeModeLast] = {
|