mantenimento-app 2.1.2 → 2.1.3
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 +539 -100
- package/backend/calculate-model.js +41 -0
- package/frontend/public/app.js +539 -100
- package/frontend/public/index.html +49 -1
- package/package.json +1 -1
package/app.js
CHANGED
|
@@ -28,6 +28,8 @@ const defaultExpenseItems = [
|
|
|
28
28
|
let scenarioTransitionTimer = null;
|
|
29
29
|
const SCENARIO_LAB_MAX = 3;
|
|
30
30
|
const SCENARIO_LABELS = ["A", "B", "C"];
|
|
31
|
+
const EXPENSE_DETAIL_MAX_CHARS = 560;
|
|
32
|
+
const EXPENSE_DETAIL_MAX_LINES = 10;
|
|
31
33
|
|
|
32
34
|
const QUOTA_MANTENIMENTO_PERC = 35;
|
|
33
35
|
|
|
@@ -228,6 +230,7 @@ const defaultExpenseItems = [
|
|
|
228
230
|
expenseDetailBtn: "Dettaglio",
|
|
229
231
|
expenseDetailTitle: "Apri dettaglio voce spesa",
|
|
230
232
|
expenseDetailPlaceholder: "Scrivi qui il dettaglio di questa cifra (es. mesi, quota, riferimento).",
|
|
233
|
+
expenseDetailCharsRemaining: "Caratteri rimanenti: {count}",
|
|
231
234
|
expenseRemoveTitle: "Rimuovi voce spesa",
|
|
232
235
|
expenseRemoveBtn: "Rimuovi",
|
|
233
236
|
expenseMinOneAlert: "Deve restare almeno una voce spesa.",
|
|
@@ -254,6 +257,33 @@ const defaultExpenseItems = [
|
|
|
254
257
|
extraAnnHint1: "Quota annuale straordinaria stimata a carico di {spouse} (es. sanitarie non ricorrenti, scolastiche extra, attività non ordinarie).",
|
|
255
258
|
extraAnnHint2: "Quota annuale straordinaria stimata a carico di {spouse} (es. sanitarie non ricorrenti, scolastiche extra, attività non ordinarie).",
|
|
256
259
|
extraMonthlyEstimate: "Quota mensile stimata: {amount}",
|
|
260
|
+
firstHomeBoxTitle: "🏡 Mutuo prima casa ceduta",
|
|
261
|
+
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.",
|
|
262
|
+
firstHomeMortgageEnabledLabel: "Mutuo su prima casa dei coniugi",
|
|
263
|
+
firstHomeMortgageEnabledHint: "Attiva per includere il mutuo della prima casa ceduta nei benefici compensativi.",
|
|
264
|
+
firstHomeMortgageAmountLabel: "Rata mutuo mensile ({currency})",
|
|
265
|
+
firstHomeMortgageAmountHint: "Importo mensile complessivo della rata del mutuo prima casa.",
|
|
266
|
+
firstHomeAssignedToLabel: "Casa assegnata a",
|
|
267
|
+
firstHomeAssignedToHint: "Seleziona il coniuge a cui e ceduta la prima casa.",
|
|
268
|
+
firstHomeAssignedToNone: "Nessuna cessione",
|
|
269
|
+
firstHomeAssignedToSpouse: "Casa ceduta a {spouse}",
|
|
270
|
+
firstHomeSplitLabel: "Quota mutuo a carico {spouse} (%)",
|
|
271
|
+
firstHomeSplitHint: "Percentuale della rata mutuo pagata da {spouse}. La quota dell'altro coniuge e complementare a 100%.",
|
|
272
|
+
firstHomeSplitInfo: "Ripartizione mutuo: {spouse1} {p1}% · {spouse2} {p2}%",
|
|
273
|
+
calcCompBenefitsLabel: "Benefici compensativi gia allocati",
|
|
274
|
+
calcNoTransferWithBenefits: "Nessun trasferimento monetario suggerito. Benefici gia allocati: {benefits}.",
|
|
275
|
+
calcBenefitFamilyAllowance: "Assegno familiare INPS percepito da {spouse}",
|
|
276
|
+
calcBenefitPrimaryHomeMortgage: "Quota mutuo prima casa ceduta al collocatario ({payer} -> {receiver})",
|
|
277
|
+
pdfCompBenefitsSection: "Benefici compensativi gia allocati",
|
|
278
|
+
pdfCompBenefitsItem: "Beneficio",
|
|
279
|
+
pdfCompBenefitsAmount: "Valore {currency}/mese",
|
|
280
|
+
pdfCompBenefitsNone: "Nessun beneficio compensativo aggiuntivo dichiarato.",
|
|
281
|
+
pdfPrimaryHomeMortgage: "Mutuo prima casa ceduta",
|
|
282
|
+
pdfPrimaryHomeNotDeclared: "Non dichiarato",
|
|
283
|
+
pdfPrimaryHomeAssignedTo: "Assegnata a",
|
|
284
|
+
pdfPrimaryHomeMonthlyAmount: "Rata mensile",
|
|
285
|
+
pdfPrimaryHomeSplit: "Ripartizione mutuo",
|
|
286
|
+
pdfPrimaryHomeAppliedOnlyColl: "Considerato solo se casa ceduta al collocatario.",
|
|
257
287
|
pdfExtraordinaryRow: "Spese straordinarie (quota mensile da annuo)",
|
|
258
288
|
liveTotalIncome: "Entrate totali (reddito + assegni + INPS)",
|
|
259
289
|
livePaidToOther: "Assegno mantenimento pagato all'altro coniuge",
|
|
@@ -521,6 +551,7 @@ const defaultExpenseItems = [
|
|
|
521
551
|
expenseDetailBtn: "Detail",
|
|
522
552
|
expenseDetailTitle: "Open expense detail",
|
|
523
553
|
expenseDetailPlaceholder: "Write details for this amount (e.g. months, share, reference).",
|
|
554
|
+
expenseDetailCharsRemaining: "Remaining characters: {count}",
|
|
524
555
|
expenseRemoveTitle: "Remove expense item",
|
|
525
556
|
expenseRemoveBtn: "Remove",
|
|
526
557
|
expenseMinOneAlert: "At least one expense item must remain.",
|
|
@@ -547,6 +578,33 @@ const defaultExpenseItems = [
|
|
|
547
578
|
extraAnnHint1: "Estimated yearly extraordinary share for {spouse} (e.g., non-recurring medical, extra school, non-ordinary activities).",
|
|
548
579
|
extraAnnHint2: "Estimated yearly extraordinary share for {spouse} (e.g., non-recurring medical, extra school, non-ordinary activities).",
|
|
549
580
|
extraMonthlyEstimate: "Estimated monthly share: {amount}",
|
|
581
|
+
firstHomeBoxTitle: "🏡 Assigned primary home mortgage",
|
|
582
|
+
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.",
|
|
583
|
+
firstHomeMortgageEnabledLabel: "Mortgage on spouses' primary home",
|
|
584
|
+
firstHomeMortgageEnabledHint: "Enable to include the assigned primary-home mortgage in compensative benefits.",
|
|
585
|
+
firstHomeMortgageAmountLabel: "Monthly mortgage payment ({currency})",
|
|
586
|
+
firstHomeMortgageAmountHint: "Total monthly amount of the primary-home mortgage payment.",
|
|
587
|
+
firstHomeAssignedToLabel: "Home assigned to",
|
|
588
|
+
firstHomeAssignedToHint: "Select which spouse receives assignment of the primary home.",
|
|
589
|
+
firstHomeAssignedToNone: "No assignment",
|
|
590
|
+
firstHomeAssignedToSpouse: "Home assigned to {spouse}",
|
|
591
|
+
firstHomeSplitLabel: "Mortgage share paid by {spouse} (%)",
|
|
592
|
+
firstHomeSplitHint: "Percentage of the monthly mortgage payment paid by {spouse}. The other spouse share is the complement to 100%.",
|
|
593
|
+
firstHomeSplitInfo: "Mortgage split: {spouse1} {p1}% · {spouse2} {p2}%",
|
|
594
|
+
calcCompBenefitsLabel: "Compensative benefits already allocated",
|
|
595
|
+
calcNoTransferWithBenefits: "No monetary transfer suggested. Already allocated benefits: {benefits}.",
|
|
596
|
+
calcBenefitFamilyAllowance: "INPS family allowance received by {spouse}",
|
|
597
|
+
calcBenefitPrimaryHomeMortgage: "Primary-home mortgage share assigned to custodial parent ({payer} -> {receiver})",
|
|
598
|
+
pdfCompBenefitsSection: "Compensative benefits already allocated",
|
|
599
|
+
pdfCompBenefitsItem: "Benefit",
|
|
600
|
+
pdfCompBenefitsAmount: "Value {currency}/month",
|
|
601
|
+
pdfCompBenefitsNone: "No additional compensative benefits declared.",
|
|
602
|
+
pdfPrimaryHomeMortgage: "Assigned primary-home mortgage",
|
|
603
|
+
pdfPrimaryHomeNotDeclared: "Not declared",
|
|
604
|
+
pdfPrimaryHomeAssignedTo: "Assigned to",
|
|
605
|
+
pdfPrimaryHomeMonthlyAmount: "Monthly payment",
|
|
606
|
+
pdfPrimaryHomeSplit: "Mortgage split",
|
|
607
|
+
pdfPrimaryHomeAppliedOnlyColl: "Counted only when the home is assigned to the custodial parent.",
|
|
550
608
|
pdfExtraordinaryRow: "Extraordinary expenses (monthly share from yearly)",
|
|
551
609
|
liveTotalIncome: "Total income (income + support + INPS)",
|
|
552
610
|
livePaidToOther: "Support paid to the other spouse",
|
|
@@ -692,100 +750,145 @@ const defaultExpenseItems = [
|
|
|
692
750
|
};
|
|
693
751
|
let currentLang = "it";
|
|
694
752
|
let currentCurrency = "EUR";
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
753
|
+
const CALC_API_BASE_STORAGE_KEY = "keylock_calc_api_base";
|
|
754
|
+
const FRONTEND_VARIANT_ENVS = window.KEYLOCK_FRONTEND_VARIANT_ENVS && typeof window.KEYLOCK_FRONTEND_VARIANT_ENVS === "object"
|
|
755
|
+
? window.KEYLOCK_FRONTEND_VARIANT_ENVS
|
|
756
|
+
: {};
|
|
757
|
+
const CALC_API_ENVS = window.KEYLOCK_CALC_API_ENVS && typeof window.KEYLOCK_CALC_API_ENVS === "object"
|
|
758
|
+
? window.KEYLOCK_CALC_API_ENVS
|
|
759
|
+
: {};
|
|
702
760
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
761
|
+
function normalizeApiBase(rawValue) {
|
|
762
|
+
return String(rawValue || "").trim().replace(/\/+$/, "");
|
|
763
|
+
}
|
|
706
764
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
765
|
+
function normalizeFrontendVariantUrl(rawValue) {
|
|
766
|
+
return String(rawValue || "").trim();
|
|
767
|
+
}
|
|
710
768
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
769
|
+
function maybeRedirectFrontendVariant() {
|
|
770
|
+
try {
|
|
771
|
+
const params = new URLSearchParams(window.location.search || "");
|
|
772
|
+
const variant = String(params.get("frontend") || "").trim().toLowerCase();
|
|
773
|
+
if (!variant || variant === "prod" || variant === "default" || variant === "reset") return;
|
|
716
774
|
|
|
717
|
-
|
|
718
|
-
|
|
775
|
+
const targetBase = normalizeFrontendVariantUrl(FRONTEND_VARIANT_ENVS[variant] || "");
|
|
776
|
+
if (!targetBase) return;
|
|
719
777
|
|
|
720
|
-
|
|
721
|
-
|
|
778
|
+
const target = new URL(targetBase, window.location.href);
|
|
779
|
+
const current = new URL(window.location.href);
|
|
722
780
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
if (target.origin === current.origin && target.pathname === current.pathname) return;
|
|
781
|
+
if (target.href === current.href) return;
|
|
782
|
+
if (target.origin === current.origin && target.pathname === current.pathname) return;
|
|
726
783
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
});
|
|
784
|
+
["env", "apiBase"].forEach((k) => {
|
|
785
|
+
if (params.has(k)) target.searchParams.set(k, String(params.get(k)));
|
|
786
|
+
});
|
|
731
787
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
788
|
+
window.location.replace(target.toString());
|
|
789
|
+
} catch (_) {
|
|
790
|
+
// Ignore malformed URLs and continue with default frontend.
|
|
791
|
+
}
|
|
735
792
|
}
|
|
736
|
-
}
|
|
737
793
|
|
|
738
|
-
|
|
794
|
+
maybeRedirectFrontendVariant();
|
|
739
795
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
796
|
+
function resolveNamedApiBase(envName) {
|
|
797
|
+
const key = String(envName || "").trim().toLowerCase();
|
|
798
|
+
if (!key) return "";
|
|
799
|
+
return normalizeApiBase(CALC_API_ENVS[key] || "");
|
|
800
|
+
}
|
|
745
801
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
802
|
+
function resolveCalculationApiBase() {
|
|
803
|
+
const configBase = normalizeApiBase(window.KEYLOCK_CALC_API_BASE || "");
|
|
804
|
+
let storedBase = "";
|
|
749
805
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
806
|
+
try {
|
|
807
|
+
storedBase = normalizeApiBase(localStorage.getItem(CALC_API_BASE_STORAGE_KEY) || "");
|
|
808
|
+
} catch (_) {
|
|
809
|
+
storedBase = "";
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
try {
|
|
813
|
+
const params = new URLSearchParams(window.location.search || "");
|
|
814
|
+
if (params.has("env")) {
|
|
815
|
+
const envName = String(params.get("env") || "").trim().toLowerCase();
|
|
816
|
+
const disable = envName === "off" || envName === "default" || envName === "reset" || envName === "prod";
|
|
817
|
+
const envBase = disable ? "" : resolveNamedApiBase(envName);
|
|
818
|
+
try {
|
|
819
|
+
if (disable || !envBase) {
|
|
820
|
+
localStorage.removeItem(CALC_API_BASE_STORAGE_KEY);
|
|
821
|
+
} else {
|
|
822
|
+
localStorage.setItem(CALC_API_BASE_STORAGE_KEY, envBase);
|
|
823
|
+
}
|
|
824
|
+
} catch (_) {}
|
|
825
|
+
return envBase;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (params.has("apiBase")) {
|
|
829
|
+
const queryBaseRaw = String(params.get("apiBase") || "").trim();
|
|
830
|
+
const disable = queryBaseRaw === "off" || queryBaseRaw === "default" || queryBaseRaw === "reset";
|
|
831
|
+
const queryBase = disable ? "" : normalizeApiBase(queryBaseRaw);
|
|
832
|
+
try {
|
|
833
|
+
if (disable || !queryBase) {
|
|
834
|
+
localStorage.removeItem(CALC_API_BASE_STORAGE_KEY);
|
|
835
|
+
} else {
|
|
836
|
+
localStorage.setItem(CALC_API_BASE_STORAGE_KEY, queryBase);
|
|
837
|
+
}
|
|
838
|
+
} catch (_) {}
|
|
839
|
+
return queryBase;
|
|
840
|
+
}
|
|
841
|
+
} catch (_) {}
|
|
842
|
+
|
|
843
|
+
return storedBase || configBase;
|
|
754
844
|
}
|
|
755
845
|
|
|
756
|
-
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
const
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
846
|
+
function getRuntimeVariantLabels() {
|
|
847
|
+
const labels = [];
|
|
848
|
+
|
|
849
|
+
try {
|
|
850
|
+
const params = new URLSearchParams(window.location.search || "");
|
|
851
|
+
const frontendVariant = String(params.get("frontend") || "").trim().toLowerCase();
|
|
852
|
+
const envVariant = String(params.get("env") || "").trim().toLowerCase();
|
|
853
|
+
const apiBaseVariant = String(params.get("apiBase") || "").trim();
|
|
854
|
+
|
|
855
|
+
if (frontendVariant === "dev" || window.location.host.includes("githack.com")) {
|
|
856
|
+
labels.push("dev-frontend");
|
|
857
|
+
}
|
|
858
|
+
if (envVariant === "dev" || !!apiBaseVariant) {
|
|
859
|
+
labels.push("dev-api");
|
|
860
|
+
}
|
|
861
|
+
} catch (_) {}
|
|
862
|
+
|
|
863
|
+
return labels;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
function renderRuntimeBadge() {
|
|
867
|
+
const heroTools = document.querySelector(".hero-tools");
|
|
868
|
+
if (!heroTools) return;
|
|
869
|
+
|
|
870
|
+
const labels = getRuntimeVariantLabels();
|
|
871
|
+
let badge = document.getElementById("runtimeEnvBadge");
|
|
872
|
+
|
|
873
|
+
if (!labels.length) {
|
|
874
|
+
if (badge) badge.remove();
|
|
875
|
+
return;
|
|
770
876
|
}
|
|
771
877
|
|
|
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;
|
|
878
|
+
if (!badge) {
|
|
879
|
+
badge = document.createElement("div");
|
|
880
|
+
badge.id = "runtimeEnvBadge";
|
|
881
|
+
badge.className = "runtime-badge";
|
|
882
|
+
heroTools.prepend(badge);
|
|
784
883
|
}
|
|
785
|
-
} catch (_) {}
|
|
786
884
|
|
|
787
|
-
|
|
788
|
-
|
|
885
|
+
badge.innerHTML = labels.map((label) => {
|
|
886
|
+
if (label === "dev-frontend") {
|
|
887
|
+
return '<span class="runtime-badge-chip runtime-badge-chip--frontend">DEV FRONTEND</span>';
|
|
888
|
+
}
|
|
889
|
+
return '<span class="runtime-badge-chip runtime-badge-chip--api">DEV API</span>';
|
|
890
|
+
}).join("");
|
|
891
|
+
}
|
|
789
892
|
|
|
790
893
|
function getRuntimeVariantLabels() {
|
|
791
894
|
const labels = [];
|
|
@@ -858,8 +961,8 @@ const defaultExpenseItems = [
|
|
|
858
961
|
}
|
|
859
962
|
|
|
860
963
|
function resolveCalculationApiUrl() {
|
|
861
|
-
|
|
862
|
-
|
|
964
|
+
const calcApiBase = resolveCalculationApiBase();
|
|
965
|
+
if (calcApiBase) return `${calcApiBase}/api/calculate`;
|
|
863
966
|
return "/api/calculate";
|
|
864
967
|
}
|
|
865
968
|
|
|
@@ -1020,6 +1123,16 @@ const defaultExpenseItems = [
|
|
|
1020
1123
|
const permLegendC2 = document.getElementById("permLegendC2");
|
|
1021
1124
|
const extraBoxTitle = document.getElementById("extraBoxTitle");
|
|
1022
1125
|
const extraBoxNote = document.getElementById("extraBoxNote");
|
|
1126
|
+
const firstHomeBoxTitle = document.getElementById("firstHomeBoxTitle");
|
|
1127
|
+
const firstHomeBoxNote = document.getElementById("firstHomeBoxNote");
|
|
1128
|
+
const lblPrimaCasaMutuoEnabled = document.getElementById("lblPrimaCasaMutuoEnabled");
|
|
1129
|
+
const hintPrimaCasaMutuoEnabled = document.getElementById("hintPrimaCasaMutuoEnabled");
|
|
1130
|
+
const lblPrimaCasaMutuoImporto = document.getElementById("lblPrimaCasaMutuoImporto");
|
|
1131
|
+
const hintPrimaCasaMutuoImporto = document.getElementById("hintPrimaCasaMutuoImporto");
|
|
1132
|
+
const lblPrimaCasaAssegnataA = document.getElementById("lblPrimaCasaAssegnataA");
|
|
1133
|
+
const hintPrimaCasaAssegnataA = document.getElementById("hintPrimaCasaAssegnataA");
|
|
1134
|
+
const lblPrimaCasaMutuoPerc1 = document.getElementById("lblPrimaCasaMutuoPerc1");
|
|
1135
|
+
const hintPrimaCasaMutuoPerc1 = document.getElementById("hintPrimaCasaMutuoPerc1");
|
|
1023
1136
|
const lblStraordAnn1 = document.getElementById("lblStraordAnn1");
|
|
1024
1137
|
const lblStraordAnn2 = document.getElementById("lblStraordAnn2");
|
|
1025
1138
|
const hintStraordAnn1 = document.getElementById("hintStraordAnn1");
|
|
@@ -1059,6 +1172,16 @@ const defaultExpenseItems = [
|
|
|
1059
1172
|
if (permLegendC2) permLegendC2.textContent = c2n();
|
|
1060
1173
|
if (extraBoxTitle) extraBoxTitle.textContent = tr("extraBoxTitle");
|
|
1061
1174
|
if (extraBoxNote) extraBoxNote.textContent = tr("extraBoxNote");
|
|
1175
|
+
if (firstHomeBoxTitle) firstHomeBoxTitle.textContent = tr("firstHomeBoxTitle");
|
|
1176
|
+
if (firstHomeBoxNote) firstHomeBoxNote.textContent = tr("firstHomeBoxNote");
|
|
1177
|
+
if (lblPrimaCasaMutuoEnabled) lblPrimaCasaMutuoEnabled.textContent = tr("firstHomeMortgageEnabledLabel");
|
|
1178
|
+
if (hintPrimaCasaMutuoEnabled) hintPrimaCasaMutuoEnabled.title = tr("firstHomeMortgageEnabledHint");
|
|
1179
|
+
if (lblPrimaCasaMutuoImporto) lblPrimaCasaMutuoImporto.textContent = msg("firstHomeMortgageAmountLabel", { currency: currentCurrency });
|
|
1180
|
+
if (hintPrimaCasaMutuoImporto) hintPrimaCasaMutuoImporto.title = tr("firstHomeMortgageAmountHint");
|
|
1181
|
+
if (lblPrimaCasaAssegnataA) lblPrimaCasaAssegnataA.textContent = tr("firstHomeAssignedToLabel");
|
|
1182
|
+
if (hintPrimaCasaAssegnataA) hintPrimaCasaAssegnataA.title = tr("firstHomeAssignedToHint");
|
|
1183
|
+
if (lblPrimaCasaMutuoPerc1) lblPrimaCasaMutuoPerc1.textContent = msg("firstHomeSplitLabel", { spouse: c1n() });
|
|
1184
|
+
if (hintPrimaCasaMutuoPerc1) hintPrimaCasaMutuoPerc1.title = msg("firstHomeSplitHint", { spouse: c1n() });
|
|
1062
1185
|
if (lblStraordAnn1) lblStraordAnn1.textContent = msg("extraAnnLabel1", { spouse: c1n(), currency: currentCurrency });
|
|
1063
1186
|
if (lblStraordAnn2) lblStraordAnn2.textContent = msg("extraAnnLabel2", { spouse: c2n(), currency: currentCurrency });
|
|
1064
1187
|
if (hintStraordAnn1) hintStraordAnn1.title = msg("extraAnnHint1", { spouse: c1n() });
|
|
@@ -1108,6 +1231,10 @@ const defaultExpenseItems = [
|
|
|
1108
1231
|
if (visitorTotalLabel) visitorTotalLabel.textContent = tr("footerVisitorsTotal");
|
|
1109
1232
|
if (visitorActiveLabel) visitorActiveLabel.textContent = tr("footerVisitorsActive");
|
|
1110
1233
|
if (visitorLoggedLabel) visitorLoggedLabel.textContent = tr("footerLoggedUsers");
|
|
1234
|
+
rowsSpese.querySelectorAll("textarea.spese-detail-text").forEach((el) => {
|
|
1235
|
+
updateExpenseDetailCounter(el);
|
|
1236
|
+
});
|
|
1237
|
+
updateFirstHomeMortgageUi();
|
|
1111
1238
|
updateExtraordinaryModuleUi();
|
|
1112
1239
|
updatePermanenceCalendarSummary();
|
|
1113
1240
|
renderVisitorCounters();
|
|
@@ -1182,6 +1309,73 @@ const defaultExpenseItems = [
|
|
|
1182
1309
|
.replaceAll("'", "'");
|
|
1183
1310
|
}
|
|
1184
1311
|
|
|
1312
|
+
function getExpenseDetailMaxHeight(textarea) {
|
|
1313
|
+
if (!textarea) return 0;
|
|
1314
|
+
const style = window.getComputedStyle(textarea);
|
|
1315
|
+
const lineHeight = Number.parseFloat(style.lineHeight) || 18;
|
|
1316
|
+
const borderTop = Number.parseFloat(style.borderTopWidth) || 0;
|
|
1317
|
+
const borderBottom = Number.parseFloat(style.borderBottomWidth) || 0;
|
|
1318
|
+
const paddingTop = Number.parseFloat(style.paddingTop) || 0;
|
|
1319
|
+
const paddingBottom = Number.parseFloat(style.paddingBottom) || 0;
|
|
1320
|
+
return Math.round((lineHeight * EXPENSE_DETAIL_MAX_LINES) + paddingTop + paddingBottom + borderTop + borderBottom);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
function autoResizeExpenseDetailTextarea(textarea, preferredHeight = 0) {
|
|
1324
|
+
if (!textarea) return;
|
|
1325
|
+
const maxHeight = getExpenseDetailMaxHeight(textarea);
|
|
1326
|
+
textarea.style.height = "auto";
|
|
1327
|
+
const scrollHeight = Math.max(textarea.scrollHeight, 0);
|
|
1328
|
+
const targetHeight = preferredHeight > 0
|
|
1329
|
+
? Math.min(maxHeight, Math.max(scrollHeight, preferredHeight))
|
|
1330
|
+
: Math.min(maxHeight, scrollHeight);
|
|
1331
|
+
textarea.style.height = `${Math.max(0, targetHeight)}px`;
|
|
1332
|
+
textarea.style.overflowY = scrollHeight > maxHeight ? "auto" : "hidden";
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
function updateExpenseDetailCounter(textarea) {
|
|
1336
|
+
if (!textarea || !textarea.id) return;
|
|
1337
|
+
const counter = document.getElementById(`${textarea.id}Counter`);
|
|
1338
|
+
if (!counter) return;
|
|
1339
|
+
const maxLen = Number(textarea.getAttribute("maxlength") || EXPENSE_DETAIL_MAX_CHARS);
|
|
1340
|
+
const len = String(textarea.value || "").length;
|
|
1341
|
+
const remaining = Math.max(0, maxLen - len);
|
|
1342
|
+
counter.textContent = msg("expenseDetailCharsRemaining", { count: String(remaining) });
|
|
1343
|
+
counter.classList.toggle("is-limit", remaining <= Math.max(20, Math.round(maxLen * 0.05)));
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function updateExpenseDetailTextareaUi(textarea, preferredHeight = 0) {
|
|
1347
|
+
if (!textarea) return;
|
|
1348
|
+
autoResizeExpenseDetailTextarea(textarea, preferredHeight);
|
|
1349
|
+
updateExpenseDetailCounter(textarea);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
function collectExpenseDetailUiMeta(spouseKey, idx) {
|
|
1353
|
+
const wrap = document.getElementById(`${spouseKey}dw_${idx}`);
|
|
1354
|
+
const textArea = document.getElementById(`${spouseKey}d_${idx}`);
|
|
1355
|
+
const inlineHeight = textArea ? Number.parseFloat(String(textArea.style.height || "0")) : 0;
|
|
1356
|
+
return {
|
|
1357
|
+
open: wrap ? !wrap.classList.contains("is-hidden") : false,
|
|
1358
|
+
height: Number.isFinite(inlineHeight) && inlineHeight > 0 ? inlineHeight : 0
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function applyExpenseDetailUiMeta(spouseKey, idx, meta) {
|
|
1363
|
+
const wrap = document.getElementById(`${spouseKey}dw_${idx}`);
|
|
1364
|
+
const textarea = document.getElementById(`${spouseKey}d_${idx}`);
|
|
1365
|
+
const btn = rowsSpese.querySelector(`button[data-detail-target='${spouseKey}d_${idx}']`);
|
|
1366
|
+
const open = !!(meta && meta.open);
|
|
1367
|
+
|
|
1368
|
+
if (wrap) wrap.classList.toggle("is-hidden", !open);
|
|
1369
|
+
if (btn) btn.classList.toggle("is-open", open);
|
|
1370
|
+
|
|
1371
|
+
const preferredHeight = Number(meta && meta.height);
|
|
1372
|
+
if (textarea && Number.isFinite(preferredHeight) && preferredHeight > 0) {
|
|
1373
|
+
updateExpenseDetailTextareaUi(textarea, preferredHeight);
|
|
1374
|
+
} else if (textarea) {
|
|
1375
|
+
updateExpenseDetailTextareaUi(textarea);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1185
1379
|
function normalizeExpenseItem(item, fallbackIdx = 0) {
|
|
1186
1380
|
const rawLabel = String(item && item.label ? item.label : "").trim();
|
|
1187
1381
|
const rawHelp = String(item && item.help ? item.help : "").trim();
|
|
@@ -2357,7 +2551,8 @@ const defaultExpenseItems = [
|
|
|
2357
2551
|
<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
2552
|
</div>
|
|
2359
2553
|
<div class="spese-detail-wrap is-hidden" id="c1dw_${idx}">
|
|
2360
|
-
<textarea id="c1d_${idx}" class="spese-detail-text" rows="2" maxlength="
|
|
2554
|
+
<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>
|
|
2555
|
+
<div class="spese-detail-counter" id="c1d_${idx}Counter" aria-live="polite"></div>
|
|
2361
2556
|
</div>
|
|
2362
2557
|
<span class="spese-partial" id="p1_${idx}" title="${tr("expensePartialTitle")}">${tr("expensePartialLabel")}: ${eurTiny(0)}</span>
|
|
2363
2558
|
</div>
|
|
@@ -2369,7 +2564,8 @@ const defaultExpenseItems = [
|
|
|
2369
2564
|
<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
2565
|
</div>
|
|
2371
2566
|
<div class="spese-detail-wrap is-hidden" id="c2dw_${idx}">
|
|
2372
|
-
<textarea id="c2d_${idx}" class="spese-detail-text" rows="2" maxlength="
|
|
2567
|
+
<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>
|
|
2568
|
+
<div class="spese-detail-counter" id="c2d_${idx}Counter" aria-live="polite"></div>
|
|
2373
2569
|
</div>
|
|
2374
2570
|
<span class="spese-partial" id="p2_${idx}" title="${tr("expensePartialTitle")}">${tr("expensePartialLabel")}: ${eurTiny(0)}</span>
|
|
2375
2571
|
</div>
|
|
@@ -2380,6 +2576,9 @@ const defaultExpenseItems = [
|
|
|
2380
2576
|
`;
|
|
2381
2577
|
rowsSpese.appendChild(rowEl);
|
|
2382
2578
|
});
|
|
2579
|
+
rowsSpese.querySelectorAll("textarea.spese-detail-text").forEach((el) => {
|
|
2580
|
+
updateExpenseDetailTextareaUi(el);
|
|
2581
|
+
});
|
|
2383
2582
|
refreshExpenseDetailButtonState();
|
|
2384
2583
|
}
|
|
2385
2584
|
|
|
@@ -2388,7 +2587,9 @@ const defaultExpenseItems = [
|
|
|
2388
2587
|
c1: num(`c1_${i}`),
|
|
2389
2588
|
c2: num(`c2_${i}`),
|
|
2390
2589
|
d1: String(document.getElementById(`c1d_${i}`)?.value || "").trim(),
|
|
2391
|
-
d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim()
|
|
2590
|
+
d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim(),
|
|
2591
|
+
d1Ui: collectExpenseDetailUiMeta("c1", i),
|
|
2592
|
+
d2Ui: collectExpenseDetailUiMeta("c2", i)
|
|
2392
2593
|
}));
|
|
2393
2594
|
}
|
|
2394
2595
|
|
|
@@ -2403,6 +2604,8 @@ const defaultExpenseItems = [
|
|
|
2403
2604
|
if (c2) c2.value = Number.isFinite(Number(row && row.c2)) ? Number(row.c2) : 0;
|
|
2404
2605
|
if (d1) d1.value = String(row && row.d1 ? row.d1 : "");
|
|
2405
2606
|
if (d2) d2.value = String(row && row.d2 ? row.d2 : "");
|
|
2607
|
+
applyExpenseDetailUiMeta("c1", i, row && row.d1Ui ? row.d1Ui : null);
|
|
2608
|
+
applyExpenseDetailUiMeta("c2", i, row && row.d2Ui ? row.d2Ui : null);
|
|
2406
2609
|
});
|
|
2407
2610
|
refreshExpenseDetailButtonState();
|
|
2408
2611
|
}
|
|
@@ -2414,6 +2617,7 @@ const defaultExpenseItems = [
|
|
|
2414
2617
|
const detailEl = document.getElementById(targetId);
|
|
2415
2618
|
const hasNote = !!(detailEl && String(detailEl.value || "").trim());
|
|
2416
2619
|
btn.classList.toggle("has-note", hasNote);
|
|
2620
|
+
btn.setAttribute("aria-label", hasNote ? `${tr("expenseDetailBtn")} ✓` : tr("expenseDetailBtn"));
|
|
2417
2621
|
});
|
|
2418
2622
|
}
|
|
2419
2623
|
|
|
@@ -2967,8 +3171,11 @@ const defaultExpenseItems = [
|
|
|
2967
3171
|
const c2Spese = expenseItems.map((_, idx) => num(`c2_${idx}`));
|
|
2968
3172
|
const c1SpeseDetails = expenseItems.map((_, idx) => String(document.getElementById(`c1d_${idx}`)?.value || "").trim());
|
|
2969
3173
|
const c2SpeseDetails = expenseItems.map((_, idx) => String(document.getElementById(`c2d_${idx}`)?.value || "").trim());
|
|
3174
|
+
const c1SpeseDetailUi = expenseItems.map((_, idx) => collectExpenseDetailUiMeta("c1", idx));
|
|
3175
|
+
const c2SpeseDetailUi = expenseItems.map((_, idx) => collectExpenseDetailUiMeta("c2", idx));
|
|
2970
3176
|
const extra1 = getExtraordinaryMonthly(1);
|
|
2971
3177
|
const extra2 = getExtraordinaryMonthly(2);
|
|
3178
|
+
const firstHome = getFirstHomeMortgageInput();
|
|
2972
3179
|
if (extra1 > 0) c1Spese.push(extra1);
|
|
2973
3180
|
if (extra2 > 0) c2Spese.push(extra2);
|
|
2974
3181
|
|
|
@@ -2989,10 +3196,16 @@ const defaultExpenseItems = [
|
|
|
2989
3196
|
aPag2: num("assegnoPagato2"),
|
|
2990
3197
|
aFam1: num("assegnoFam1"),
|
|
2991
3198
|
aFam2: num("assegnoFam2"),
|
|
3199
|
+
primaCasaMutuoEnabled: firstHome.enabled ? 1 : 0,
|
|
3200
|
+
primaCasaMutuoImporto: firstHome.amount,
|
|
3201
|
+
primaCasaAssegnataA: firstHome.assignedTo,
|
|
3202
|
+
primaCasaMutuoPerc1: firstHome.share1,
|
|
2992
3203
|
straordAnn1: num("straordAnn1"),
|
|
2993
3204
|
straordAnn2: num("straordAnn2"),
|
|
2994
3205
|
c1SpeseDetails,
|
|
2995
3206
|
c2SpeseDetails,
|
|
3207
|
+
c1SpeseDetailUi,
|
|
3208
|
+
c2SpeseDetailUi,
|
|
2996
3209
|
c1Spese,
|
|
2997
3210
|
c2Spese
|
|
2998
3211
|
};
|
|
@@ -3023,6 +3236,14 @@ const defaultExpenseItems = [
|
|
|
3023
3236
|
const aPag2 = Number(payload.aPag2 || 0);
|
|
3024
3237
|
const aFam1 = Number(payload.aFam1 || 0);
|
|
3025
3238
|
const aFam2 = Number(payload.aFam2 || 0);
|
|
3239
|
+
const primaCasaMutuoEnabled = Number(payload.primaCasaMutuoEnabled || 0) > 0;
|
|
3240
|
+
const primaCasaMutuoImporto = Math.max(0, Number(payload.primaCasaMutuoImporto || 0));
|
|
3241
|
+
const primaCasaAssegnataA = (String(payload.primaCasaAssegnataA || "") === "1" || String(payload.primaCasaAssegnataA || "") === "2")
|
|
3242
|
+
? String(payload.primaCasaAssegnataA)
|
|
3243
|
+
: "";
|
|
3244
|
+
const rawMutuoPerc1 = payload.primaCasaMutuoPerc1 === undefined ? 50 : payload.primaCasaMutuoPerc1;
|
|
3245
|
+
const primaCasaMutuoPerc1 = Math.min(100, Math.max(0, Number(rawMutuoPerc1 || 0)));
|
|
3246
|
+
const primaCasaMutuoPerc2 = 100 - primaCasaMutuoPerc1;
|
|
3026
3247
|
|
|
3027
3248
|
const match12 = Math.min(aPag1, aPerc2);
|
|
3028
3249
|
const match21 = Math.min(aPag2, aPerc1);
|
|
@@ -3071,6 +3292,33 @@ const defaultExpenseItems = [
|
|
|
3071
3292
|
assegnoDa2a1 = nonCollocatario === 2 ? contributoIndiretto : 0;
|
|
3072
3293
|
}
|
|
3073
3294
|
|
|
3295
|
+
const assegnoBaseDa1a2 = assegnoDa1a2;
|
|
3296
|
+
const assegnoBaseDa2a1 = assegnoDa2a1;
|
|
3297
|
+
|
|
3298
|
+
const primaCasaConsidered = primaCasaMutuoEnabled && primaCasaMutuoImporto > 0
|
|
3299
|
+
&& primaCasaAssegnataA !== ""
|
|
3300
|
+
&& Number(primaCasaAssegnataA) === collocatario;
|
|
3301
|
+
let primaCasaTransfer1to2 = 0;
|
|
3302
|
+
let primaCasaTransfer2to1 = 0;
|
|
3303
|
+
if (primaCasaConsidered) {
|
|
3304
|
+
const quotaMutuo1 = primaCasaMutuoImporto * (primaCasaMutuoPerc1 / 100);
|
|
3305
|
+
const quotaMutuo2 = primaCasaMutuoImporto - quotaMutuo1;
|
|
3306
|
+
if (primaCasaAssegnataA === "1") {
|
|
3307
|
+
primaCasaTransfer2to1 = Math.max(0, quotaMutuo2);
|
|
3308
|
+
} else if (primaCasaAssegnataA === "2") {
|
|
3309
|
+
primaCasaTransfer1to2 = Math.max(0, quotaMutuo1);
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
assegnoDa1a2 = Math.max(0, assegnoDa1a2 - primaCasaTransfer1to2);
|
|
3314
|
+
assegnoDa2a1 = Math.max(0, assegnoDa2a1 - primaCasaTransfer2to1);
|
|
3315
|
+
|
|
3316
|
+
const compensativeBenefits = [];
|
|
3317
|
+
if (aFam1 > 0.005) compensativeBenefits.push({ type: "family", to: 1, amount: aFam1 });
|
|
3318
|
+
if (aFam2 > 0.005) compensativeBenefits.push({ type: "family", to: 2, amount: aFam2 });
|
|
3319
|
+
if (primaCasaTransfer1to2 > 0.005) compensativeBenefits.push({ type: "primary-home-mortgage", from: 1, to: 2, amount: primaCasaTransfer1to2 });
|
|
3320
|
+
if (primaCasaTransfer2to1 > 0.005) compensativeBenefits.push({ type: "primary-home-mortgage", from: 2, to: 1, amount: primaCasaTransfer2to1 });
|
|
3321
|
+
|
|
3074
3322
|
const post1 = disp1 - assegnoDa1a2 + assegnoDa2a1;
|
|
3075
3323
|
const post2 = disp2 - assegnoDa2a1 + assegnoDa1a2;
|
|
3076
3324
|
|
|
@@ -3085,6 +3333,11 @@ const defaultExpenseItems = [
|
|
|
3085
3333
|
fabbisognoFigli, quotaTeorica1, quotaTeorica2,
|
|
3086
3334
|
quotaDiretta1, quotaDiretta2,
|
|
3087
3335
|
saldo1, saldo2,
|
|
3336
|
+
assegnoBaseDa1a2, assegnoBaseDa2a1,
|
|
3337
|
+
primaCasaMutuoEnabled, primaCasaMutuoImporto, primaCasaAssegnataA,
|
|
3338
|
+
primaCasaMutuoPerc1, primaCasaMutuoPerc2,
|
|
3339
|
+
primaCasaConsidered, primaCasaTransfer1to2, primaCasaTransfer2to1,
|
|
3340
|
+
compensativeBenefits,
|
|
3088
3341
|
assegnoDa1a2, assegnoDa2a1,
|
|
3089
3342
|
post1, post2
|
|
3090
3343
|
};
|
|
@@ -3167,6 +3420,7 @@ const defaultExpenseItems = [
|
|
|
3167
3420
|
if (lblStraordAnn2) lblStraordAnn2.textContent = msg("extraAnnLabel2", { spouse: c2n(), currency: currentCurrency });
|
|
3168
3421
|
if (hintStraordAnn1) hintStraordAnn1.title = msg("extraAnnHint1", { spouse: c1n() });
|
|
3169
3422
|
if (hintStraordAnn2) hintStraordAnn2.title = msg("extraAnnHint2", { spouse: c2n() });
|
|
3423
|
+
updateFirstHomeMortgageUi();
|
|
3170
3424
|
updateExtraordinaryModuleUi();
|
|
3171
3425
|
updatePermanenceCalendarSummary();
|
|
3172
3426
|
}
|
|
@@ -3176,6 +3430,56 @@ const defaultExpenseItems = [
|
|
|
3176
3430
|
return annual > 0 ? annual / 12 : 0;
|
|
3177
3431
|
}
|
|
3178
3432
|
|
|
3433
|
+
function getFirstHomeMortgageInput() {
|
|
3434
|
+
const enabled = !!document.getElementById("primaCasaMutuoEnabled")?.checked;
|
|
3435
|
+
const amount = Math.max(0, num("primaCasaMutuoImporto"));
|
|
3436
|
+
const assignedToRaw = String(document.getElementById("primaCasaAssegnataA")?.value || "").trim();
|
|
3437
|
+
const assignedTo = (assignedToRaw === "1" || assignedToRaw === "2") ? assignedToRaw : "";
|
|
3438
|
+
const share1 = Math.min(100, Math.max(0, num("primaCasaMutuoPerc1")));
|
|
3439
|
+
const share2 = 100 - share1;
|
|
3440
|
+
return { enabled, amount, assignedTo, share1, share2 };
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
function updateFirstHomeMortgageUi() {
|
|
3444
|
+
const enabledEl = document.getElementById("primaCasaMutuoEnabled");
|
|
3445
|
+
const amountEl = document.getElementById("primaCasaMutuoImporto");
|
|
3446
|
+
const assignedEl = document.getElementById("primaCasaAssegnataA");
|
|
3447
|
+
const shareEl = document.getElementById("primaCasaMutuoPerc1");
|
|
3448
|
+
const splitInfoEl = document.getElementById("primaCasaMutuoSplitInfo");
|
|
3449
|
+
const splitLabelEl = document.getElementById("lblPrimaCasaMutuoPerc1");
|
|
3450
|
+
const splitHintEl = document.getElementById("hintPrimaCasaMutuoPerc1");
|
|
3451
|
+
if (!enabledEl || !amountEl || !assignedEl || !shareEl) return;
|
|
3452
|
+
|
|
3453
|
+
const isEnabled = !!enabledEl.checked;
|
|
3454
|
+
amountEl.disabled = !isEnabled;
|
|
3455
|
+
assignedEl.disabled = !isEnabled;
|
|
3456
|
+
shareEl.disabled = !isEnabled;
|
|
3457
|
+
|
|
3458
|
+
const normalizedShare1 = Math.min(100, Math.max(0, num("primaCasaMutuoPerc1")));
|
|
3459
|
+
if (Math.abs(normalizedShare1 - Number(shareEl.value || 0)) > 0.0001) {
|
|
3460
|
+
shareEl.value = normalizedShare1.toFixed(0);
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
const share2 = 100 - normalizedShare1;
|
|
3464
|
+
if (splitLabelEl) splitLabelEl.textContent = msg("firstHomeSplitLabel", { spouse: c1n() });
|
|
3465
|
+
if (splitHintEl) splitHintEl.title = msg("firstHomeSplitHint", { spouse: c1n() });
|
|
3466
|
+
if (splitInfoEl) {
|
|
3467
|
+
splitInfoEl.textContent = msg("firstHomeSplitInfo", {
|
|
3468
|
+
spouse1: c1n(),
|
|
3469
|
+
spouse2: c2n(),
|
|
3470
|
+
p1: normalizedShare1.toFixed(0),
|
|
3471
|
+
p2: share2.toFixed(0)
|
|
3472
|
+
});
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
const noneOpt = assignedEl.querySelector("option[value='']");
|
|
3476
|
+
const spouse1Opt = assignedEl.querySelector("option[value='1']");
|
|
3477
|
+
const spouse2Opt = assignedEl.querySelector("option[value='2']");
|
|
3478
|
+
if (noneOpt) noneOpt.textContent = tr("firstHomeAssignedToNone");
|
|
3479
|
+
if (spouse1Opt) spouse1Opt.textContent = msg("firstHomeAssignedToSpouse", { spouse: c1n() });
|
|
3480
|
+
if (spouse2Opt) spouse2Opt.textContent = msg("firstHomeAssignedToSpouse", { spouse: c2n() });
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3179
3483
|
function updateExtraordinaryModuleUi() {
|
|
3180
3484
|
const month1 = document.getElementById("straordMonth1");
|
|
3181
3485
|
const month2 = document.getElementById("straordMonth2");
|
|
@@ -3635,6 +3939,11 @@ const defaultExpenseItems = [
|
|
|
3635
3939
|
if (!el) return;
|
|
3636
3940
|
el.value = value;
|
|
3637
3941
|
};
|
|
3942
|
+
const setChecked = (id, value) => {
|
|
3943
|
+
const el = document.getElementById(id);
|
|
3944
|
+
if (!el) return;
|
|
3945
|
+
el.checked = !!value;
|
|
3946
|
+
};
|
|
3638
3947
|
|
|
3639
3948
|
setVal("nome1", payload._nome1 || c1n());
|
|
3640
3949
|
setVal("nome2", payload._nome2 || c2n());
|
|
@@ -3660,6 +3969,10 @@ const defaultExpenseItems = [
|
|
|
3660
3969
|
setVal("assegnoPercepito2", Number(payload.aPerc2 || 0));
|
|
3661
3970
|
setVal("assegnoPagato2", Number(payload.aPag2 || 0));
|
|
3662
3971
|
setVal("assegnoFam2", Number(payload.aFam2 || 0));
|
|
3972
|
+
setChecked("primaCasaMutuoEnabled", Number(payload.primaCasaMutuoEnabled || 0) > 0);
|
|
3973
|
+
setVal("primaCasaMutuoImporto", Number(payload.primaCasaMutuoImporto || 0));
|
|
3974
|
+
setVal("primaCasaAssegnataA", (String(payload.primaCasaAssegnataA || "") === "1" || String(payload.primaCasaAssegnataA || "") === "2") ? String(payload.primaCasaAssegnataA) : "");
|
|
3975
|
+
setVal("primaCasaMutuoPerc1", Math.min(100, Math.max(0, Number((payload.primaCasaMutuoPerc1 === undefined ? 50 : payload.primaCasaMutuoPerc1) || 0))));
|
|
3663
3976
|
setVal("straordAnn1", Number(payload.straordAnn1 || 0));
|
|
3664
3977
|
setVal("straordAnn2", Number(payload.straordAnn2 || 0));
|
|
3665
3978
|
|
|
@@ -3667,6 +3980,8 @@ const defaultExpenseItems = [
|
|
|
3667
3980
|
const c2Spese = Array.isArray(payload.c2Spese) ? payload.c2Spese : [];
|
|
3668
3981
|
const c1SpeseDetails = Array.isArray(payload.c1SpeseDetails) ? payload.c1SpeseDetails : [];
|
|
3669
3982
|
const c2SpeseDetails = Array.isArray(payload.c2SpeseDetails) ? payload.c2SpeseDetails : [];
|
|
3983
|
+
const c1SpeseDetailUi = Array.isArray(payload.c1SpeseDetailUi) ? payload.c1SpeseDetailUi : [];
|
|
3984
|
+
const c2SpeseDetailUi = Array.isArray(payload.c2SpeseDetailUi) ? payload.c2SpeseDetailUi : [];
|
|
3670
3985
|
expenseItems.forEach((_, i) => {
|
|
3671
3986
|
const c1 = document.getElementById(`c1_${i}`);
|
|
3672
3987
|
const c2 = document.getElementById(`c2_${i}`);
|
|
@@ -3676,6 +3991,8 @@ const defaultExpenseItems = [
|
|
|
3676
3991
|
if (c2) c2.value = Number(c2Spese[i] || 0);
|
|
3677
3992
|
if (d1) d1.value = String(c1SpeseDetails[i] || "");
|
|
3678
3993
|
if (d2) d2.value = String(c2SpeseDetails[i] || "");
|
|
3994
|
+
applyExpenseDetailUiMeta("c1", i, c1SpeseDetailUi[i]);
|
|
3995
|
+
applyExpenseDetailUiMeta("c2", i, c2SpeseDetailUi[i]);
|
|
3679
3996
|
});
|
|
3680
3997
|
refreshExpenseDetailButtonState();
|
|
3681
3998
|
|
|
@@ -3686,6 +4003,7 @@ const defaultExpenseItems = [
|
|
|
3686
4003
|
};
|
|
3687
4004
|
|
|
3688
4005
|
updateSpouseLabels();
|
|
4006
|
+
updateFirstHomeMortgageUi();
|
|
3689
4007
|
if (payload._permanenceCalendar && typeof payload._permanenceCalendar === "object") {
|
|
3690
4008
|
importPermanenceCalendarState(payload._permanenceCalendar);
|
|
3691
4009
|
syncPermanenza("calendar");
|
|
@@ -3810,6 +4128,22 @@ const defaultExpenseItems = [
|
|
|
3810
4128
|
const peso2Pct = (m.peso2 * 100).toFixed(1);
|
|
3811
4129
|
const days1 = ((m.perm1 / 100) * 30).toFixed(1);
|
|
3812
4130
|
const days2 = ((m.perm2 / 100) * 30).toFixed(1);
|
|
4131
|
+
const compBenefits = getCompensativeBenefitRows(m, c1Name, c2Name);
|
|
4132
|
+
const compBenefitsRowsHtml = compBenefits.length
|
|
4133
|
+
? compBenefits.map((row) => `<tr><td>${escapeHtml(row.label)}</td><td class="num">${eur(row.amount)}</td></tr>`).join("")
|
|
4134
|
+
: `<tr><td colspan="2">${tr("pdfCompBenefitsNone")}</td></tr>`;
|
|
4135
|
+
const primaryHomeAssignedLabel = m.primaCasaAssegnataA === "1"
|
|
4136
|
+
? c1NameEsc
|
|
4137
|
+
: m.primaCasaAssegnataA === "2"
|
|
4138
|
+
? c2NameEsc
|
|
4139
|
+
: tr("pdfPrimaryHomeNotDeclared");
|
|
4140
|
+
const primaryHomeSummaryRows = m.primaCasaMutuoEnabled
|
|
4141
|
+
? `
|
|
4142
|
+
<tr><td>${tr("pdfPrimaryHomeAssignedTo")}</td><td>${primaryHomeAssignedLabel}</td></tr>
|
|
4143
|
+
<tr><td>${tr("pdfPrimaryHomeMonthlyAmount")}</td><td>${eur(m.primaCasaMutuoImporto || 0)}</td></tr>
|
|
4144
|
+
<tr><td>${tr("pdfPrimaryHomeSplit")}</td><td>${c1NameEsc} ${(m.primaCasaMutuoPerc1 || 0).toFixed(0)}% · ${c2NameEsc} ${(m.primaCasaMutuoPerc2 || 0).toFixed(0)}%</td></tr>
|
|
4145
|
+
<tr><td>${tr("pdfPrimaryHomeAppliedOnlyColl")}</td><td>${m.primaCasaConsidered ? "OK" : tr("pdfPrimaryHomeNotDeclared")}</td></tr>`
|
|
4146
|
+
: `<tr><td>${tr("pdfPrimaryHomeMortgage")}</td><td>${tr("pdfPrimaryHomeNotDeclared")}</td></tr>`;
|
|
3813
4147
|
const isAssegno1 = m.assegnoDa1a2 > 0.005;
|
|
3814
4148
|
const isAssegno2 = m.assegnoDa2a1 > 0.005;
|
|
3815
4149
|
const n1 = escapeHtml(c1n());
|
|
@@ -3824,18 +4158,24 @@ const defaultExpenseItems = [
|
|
|
3824
4158
|
let resultDetail;
|
|
3825
4159
|
if (isAssegno1) {
|
|
3826
4160
|
resultHtml = `
|
|
3827
|
-
<div class="spieg-
|
|
3828
|
-
<div class="spieg-
|
|
4161
|
+
<div class="spieg-result-flow">${n1} → ${n2}</div>
|
|
4162
|
+
<div class="spieg-result-formula">${n1}: ${eur(m.quotaTeorica1)} − ${eur(m.quotaDiretta1)}</div>
|
|
4163
|
+
<div class="spieg-result-amount ok">${eur(m.assegnoDa1a2)}</div>
|
|
3829
4164
|
`;
|
|
3830
4165
|
resultDetail = tr("spiegDetailResultTransfer");
|
|
3831
4166
|
} else if (isAssegno2) {
|
|
3832
4167
|
resultHtml = `
|
|
3833
|
-
<div class="spieg-
|
|
3834
|
-
<div class="spieg-
|
|
4168
|
+
<div class="spieg-result-flow">${n2} → ${n1}</div>
|
|
4169
|
+
<div class="spieg-result-formula">${n2}: ${eur(m.quotaTeorica2)} − ${eur(m.quotaDiretta2)}</div>
|
|
4170
|
+
<div class="spieg-result-amount ok">${eur(m.assegnoDa2a1)}</div>
|
|
3835
4171
|
`;
|
|
3836
4172
|
resultDetail = tr("spiegDetailResultTransfer");
|
|
3837
4173
|
} else {
|
|
3838
|
-
|
|
4174
|
+
const benefitRows = getCompensativeBenefitRows(m, c1n(), c2n());
|
|
4175
|
+
const benefitsHtml = benefitRows.length
|
|
4176
|
+
? `<div class="spieg-line" style="margin-top:6px"><strong>${tr("calcCompBenefitsLabel")}:</strong> ${benefitRows.map((row) => `${escapeHtml(row.label)} (${eur(row.amount)})`).join(" | ")}</div>`
|
|
4177
|
+
: "";
|
|
4178
|
+
resultHtml = `<div class="spieg-result-empty ok">${tr("calcNoTransferSuggested")}</div>${benefitsHtml}`;
|
|
3839
4179
|
resultDetail = tr("spiegDetailResultNoTransfer");
|
|
3840
4180
|
}
|
|
3841
4181
|
|
|
@@ -3844,27 +4184,43 @@ const defaultExpenseItems = [
|
|
|
3844
4184
|
<summary class="spieg-title">${tr("spiegTitle")}</summary>
|
|
3845
4185
|
<div class="spieg-grid">
|
|
3846
4186
|
<div class="spieg-item">
|
|
3847
|
-
<div class="spieg-item-label">${tr("spiegRedditiLabel")} ${infoTip(tr("spiegDetailIncome"))}</div>
|
|
4187
|
+
<div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">💸</span>${tr("spiegRedditiLabel")} ${infoTip(tr("spiegDetailIncome"))}</div>
|
|
3848
4188
|
<div class="spieg-item-body">
|
|
3849
|
-
<div class="spieg-
|
|
3850
|
-
|
|
4189
|
+
<div class="spieg-people">
|
|
4190
|
+
<div class="spieg-person spieg-person--left">
|
|
4191
|
+
<div class="spieg-person-name">${n1}</div>
|
|
4192
|
+
<div class="spieg-person-value">${eur(m.disp1)}</div>
|
|
4193
|
+
<div class="spieg-person-sub">${tr("pdfWeight")}: ${peso1Pct}%</div>
|
|
4194
|
+
</div>
|
|
4195
|
+
<div class="spieg-person spieg-person--right">
|
|
4196
|
+
<div class="spieg-person-name">${n2}</div>
|
|
4197
|
+
<div class="spieg-person-value">${eur(m.disp2)}</div>
|
|
4198
|
+
<div class="spieg-person-sub">${tr("pdfWeight")}: ${peso2Pct}%</div>
|
|
4199
|
+
</div>
|
|
4200
|
+
</div>
|
|
3851
4201
|
</div>
|
|
3852
4202
|
</div>
|
|
3853
4203
|
<div class="spieg-item">
|
|
3854
|
-
<div class="spieg-item-label">${tr("spiegSpeseLabel")} ${infoTip(tr("spiegDetailExpense"))}</div>
|
|
4204
|
+
<div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">📝</span>${tr("spiegSpeseLabel")} ${infoTip(tr("spiegDetailExpense"))}</div>
|
|
3855
4205
|
<div class="spieg-item-body">
|
|
3856
|
-
<div class="spieg-
|
|
4206
|
+
<div class="spieg-equation">
|
|
4207
|
+
<span class="spieg-pill">${eur(m.speseTot)}</span>
|
|
4208
|
+
<span class="spieg-op">×</span>
|
|
4209
|
+
<span class="spieg-pill">35%</span>
|
|
4210
|
+
<span class="spieg-op">=</span>
|
|
4211
|
+
<span class="spieg-pill spieg-pill--result">${eur(m.fabbisognoFigli)}</span>
|
|
4212
|
+
</div>
|
|
3857
4213
|
</div>
|
|
3858
4214
|
</div>
|
|
3859
4215
|
<div class="spieg-item">
|
|
3860
|
-
<div class="spieg-item-label">${tr("spiegPermLabel")} ${infoTip(tr("spiegDetailPerm"))}</div>
|
|
4216
|
+
<div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">📅</span>${tr("spiegPermLabel")} ${infoTip(tr("spiegDetailPerm"))}</div>
|
|
3861
4217
|
<div class="spieg-item-body">
|
|
3862
|
-
<div class="spieg-line"><span>${n1}
|
|
3863
|
-
<div class="spieg-line"><span>${n2}
|
|
4218
|
+
<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>
|
|
4219
|
+
<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
4220
|
</div>
|
|
3865
4221
|
</div>
|
|
3866
4222
|
<div class="spieg-item spieg-item--result">
|
|
3867
|
-
<div class="spieg-item-label">${tr("spiegResultLabel")} ${infoTip(resultDetail)}</div>
|
|
4223
|
+
<div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">🎯</span>${tr("spiegResultLabel")} ${infoTip(resultDetail)}</div>
|
|
3868
4224
|
<div class="spieg-item-body spieg-item-body--result">${resultHtml}</div>
|
|
3869
4225
|
</div>
|
|
3870
4226
|
</div>
|
|
@@ -3884,6 +4240,31 @@ const defaultExpenseItems = [
|
|
|
3884
4240
|
return tr("calcModeGenovaName");
|
|
3885
4241
|
}
|
|
3886
4242
|
|
|
4243
|
+
function getCompensativeBenefitRows(m, name1 = c1n(), name2 = c2n()) {
|
|
4244
|
+
const rows = Array.isArray(m && m.compensativeBenefits) ? m.compensativeBenefits : [];
|
|
4245
|
+
return rows
|
|
4246
|
+
.filter((row) => row && Number(row.amount || 0) > 0.005)
|
|
4247
|
+
.map((row) => {
|
|
4248
|
+
const amount = Number(row.amount || 0);
|
|
4249
|
+
if (row.type === "family") {
|
|
4250
|
+
const spouse = Number(row.to) === 2 ? name2 : name1;
|
|
4251
|
+
return { label: msg("calcBenefitFamilyAllowance", { spouse }), amount };
|
|
4252
|
+
}
|
|
4253
|
+
if (row.type === "primary-home-mortgage") {
|
|
4254
|
+
const payer = Number(row.from) === 2 ? name2 : name1;
|
|
4255
|
+
const receiver = Number(row.to) === 2 ? name2 : name1;
|
|
4256
|
+
return { label: msg("calcBenefitPrimaryHomeMortgage", { payer, receiver }), amount };
|
|
4257
|
+
}
|
|
4258
|
+
return { label: tr("calcCompBenefitsLabel"), amount };
|
|
4259
|
+
});
|
|
4260
|
+
}
|
|
4261
|
+
|
|
4262
|
+
function formatCompensativeBenefitsInline(m, name1 = c1n(), name2 = c2n()) {
|
|
4263
|
+
return getCompensativeBenefitRows(m, name1, name2)
|
|
4264
|
+
.map((row) => `${row.label}: ${eur(row.amount)}`)
|
|
4265
|
+
.join(" | ");
|
|
4266
|
+
}
|
|
4267
|
+
|
|
3887
4268
|
function calculate(model = null) {
|
|
3888
4269
|
const m = model || computeModel();
|
|
3889
4270
|
const formulaNote = document.getElementById("formulaNote");
|
|
@@ -3895,6 +4276,7 @@ const defaultExpenseItems = [
|
|
|
3895
4276
|
const normProfileName = escapeHtml(getSelectedNormProfileLabel());
|
|
3896
4277
|
const negotiationPayerName = m.assegnoDa1a2 > 0.005 ? c1n() : (m.assegnoDa2a1 > 0.005 ? c2n() : c1n());
|
|
3897
4278
|
const negotiationReceiverName = m.assegnoDa1a2 > 0.005 ? c2n() : (m.assegnoDa2a1 > 0.005 ? c1n() : c2n());
|
|
4279
|
+
const benefitsInline = formatCompensativeBenefitsInline(m, c1n(), c2n());
|
|
3898
4280
|
|
|
3899
4281
|
let modeSpecific = "";
|
|
3900
4282
|
if (m.mode === "simple") {
|
|
@@ -3958,6 +4340,10 @@ const defaultExpenseItems = [
|
|
|
3958
4340
|
mainText = `${c1n()} \u2192 ${c2n()}: ${eur(m.assegnoDa1a2)} ${tr("pdfPerMonth")}`;
|
|
3959
4341
|
} else if (m.assegnoDa2a1 > 0.005) {
|
|
3960
4342
|
mainText = `${c2n()} \u2192 ${c1n()}: ${eur(m.assegnoDa2a1)} ${tr("pdfPerMonth")}`;
|
|
4343
|
+
} else {
|
|
4344
|
+
if (benefitsInline) {
|
|
4345
|
+
mainText = msg("calcNoTransferWithBenefits", { benefits: benefitsInline });
|
|
4346
|
+
}
|
|
3961
4347
|
}
|
|
3962
4348
|
resultMain.textContent = mainText;
|
|
3963
4349
|
|
|
@@ -3977,6 +4363,10 @@ const defaultExpenseItems = [
|
|
|
3977
4363
|
[tr("pdfAmountPerChild"), eur((Math.max(m.assegnoDa1a2, m.assegnoDa2a1)) / m.figli), "warn"]
|
|
3978
4364
|
];
|
|
3979
4365
|
|
|
4366
|
+
if (benefitsInline) {
|
|
4367
|
+
items.push([tr("calcCompBenefitsLabel"), benefitsInline, "warn"]);
|
|
4368
|
+
}
|
|
4369
|
+
|
|
3980
4370
|
if (m.incomeMode === "cu") {
|
|
3981
4371
|
const ratio1 = m.r1Raw > 0 ? ((m.r1 * 12 / m.r1Raw) * 100) : 0;
|
|
3982
4372
|
const ratio2 = m.r2Raw > 0 ? ((m.r2 * 12 / m.r2Raw) * 100) : 0;
|
|
@@ -4089,6 +4479,11 @@ const defaultExpenseItems = [
|
|
|
4089
4479
|
<div class="pdf-explain-formula">${c2NameEsc}: ${eur(m.quotaTeorica2)} − ${eur(m.quotaDiretta2)}</div>
|
|
4090
4480
|
<div class="pdf-explain-amount">${eur(m.assegnoDa2a1)}</div>
|
|
4091
4481
|
`;
|
|
4482
|
+
} else if (compBenefits.length) {
|
|
4483
|
+
explainResultHtml = `
|
|
4484
|
+
<div class="pdf-explain-result-empty">${tr("calcNoTransferSuggested")}</div>
|
|
4485
|
+
<div class="pdf-explain-formula"><strong>${tr("calcCompBenefitsLabel")}:</strong> ${compBenefits.map((row) => `${escapeHtml(row.label)} (${eur(row.amount)})`).join(" | ")}</div>
|
|
4486
|
+
`;
|
|
4092
4487
|
}
|
|
4093
4488
|
|
|
4094
4489
|
const extraSpese1Monthly = getExtraordinaryMonthly(1);
|
|
@@ -4097,7 +4492,7 @@ const defaultExpenseItems = [
|
|
|
4097
4492
|
const el = document.getElementById(`${spouseKey}d_${idx}`);
|
|
4098
4493
|
const raw = String(el && el.value ? el.value : "").trim();
|
|
4099
4494
|
if (!raw) return "";
|
|
4100
|
-
return raw
|
|
4495
|
+
return `${escapeHtml(raw)} <span class="expense-detail-meta">(${raw.length}/${EXPENSE_DETAIL_MAX_CHARS})</span>`;
|
|
4101
4496
|
};
|
|
4102
4497
|
const speseRowsBase = expenseItems.map((item, i) => {
|
|
4103
4498
|
const c1 = num(`c1_${i}`);
|
|
@@ -4105,8 +4500,8 @@ const defaultExpenseItems = [
|
|
|
4105
4500
|
const d1 = formatExpenseDetail(i, "c1");
|
|
4106
4501
|
const d2 = formatExpenseDetail(i, "c2");
|
|
4107
4502
|
const details = [
|
|
4108
|
-
d1 ? `${escapeHtml(c1n())}: ${
|
|
4109
|
-
d2 ? `${escapeHtml(c2n())}: ${
|
|
4503
|
+
d1 ? `${escapeHtml(c1n())}: ${d1}` : "",
|
|
4504
|
+
d2 ? `${escapeHtml(c2n())}: ${d2}` : ""
|
|
4110
4505
|
].filter(Boolean).join("<br>");
|
|
4111
4506
|
return `<tr>
|
|
4112
4507
|
<td>${item.label}</td>
|
|
@@ -4309,6 +4704,7 @@ const defaultExpenseItems = [
|
|
|
4309
4704
|
tr:nth-child(even) td { background: #f5faf9; }
|
|
4310
4705
|
.num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
4311
4706
|
.bold { font-weight: 700; }
|
|
4707
|
+
.expense-detail-meta { color: #5f7873; font-size: 7pt; font-weight: 600; white-space: nowrap; }
|
|
4312
4708
|
.total-row td { background: #ddf0ec !important; font-weight: 700; border-top: 1.5px solid #74c3b9; }
|
|
4313
4709
|
|
|
4314
4710
|
/* ── TWO-COL LAYOUT ── */
|
|
@@ -4540,6 +4936,7 @@ const defaultExpenseItems = [
|
|
|
4540
4936
|
<tr><td>${tr("pdfChildrenCount")}</td><td>${m.figli}</td></tr>
|
|
4541
4937
|
<tr><td>${tr("pdfPermanence")} ${c1n()}</td><td>${m.perm1.toFixed(0)}%</td></tr>
|
|
4542
4938
|
<tr><td>${tr("pdfPermanence")} ${c2n()}</td><td>${m.perm2.toFixed(0)}%</td></tr>
|
|
4939
|
+
${primaryHomeSummaryRows}
|
|
4543
4940
|
</tbody>
|
|
4544
4941
|
</table>
|
|
4545
4942
|
<table class="data-table">
|
|
@@ -4616,6 +5013,21 @@ const defaultExpenseItems = [
|
|
|
4616
5013
|
</div>
|
|
4617
5014
|
</div>
|
|
4618
5015
|
|
|
5016
|
+
<div class="section">
|
|
5017
|
+
<div class="section-title">${tr("pdfCompBenefitsSection")}</div>
|
|
5018
|
+
<table>
|
|
5019
|
+
<thead>
|
|
5020
|
+
<tr>
|
|
5021
|
+
<th>${tr("pdfCompBenefitsItem")}</th>
|
|
5022
|
+
<th class="num">${msg("pdfCompBenefitsAmount", { currency: currentCurrency })}</th>
|
|
5023
|
+
</tr>
|
|
5024
|
+
</thead>
|
|
5025
|
+
<tbody>
|
|
5026
|
+
${compBenefitsRowsHtml}
|
|
5027
|
+
</tbody>
|
|
5028
|
+
</table>
|
|
5029
|
+
</div>
|
|
5030
|
+
|
|
4619
5031
|
<!-- KPI -->
|
|
4620
5032
|
<div class="section">
|
|
4621
5033
|
<div class="section-title">${tr("pdfKpiSection")}</div>
|
|
@@ -4797,6 +5209,10 @@ ${scenarioLab.length ? `
|
|
|
4797
5209
|
assegnoPercepito2: num("assegnoPercepito2"),
|
|
4798
5210
|
assegnoPagato2: num("assegnoPagato2"),
|
|
4799
5211
|
assegnoFam2: num("assegnoFam2"),
|
|
5212
|
+
primaCasaMutuoEnabled: document.getElementById("primaCasaMutuoEnabled")?.checked ? 1 : 0,
|
|
5213
|
+
primaCasaMutuoImporto: num("primaCasaMutuoImporto"),
|
|
5214
|
+
primaCasaAssegnataA: String(document.getElementById("primaCasaAssegnataA")?.value || ""),
|
|
5215
|
+
primaCasaMutuoPerc1: num("primaCasaMutuoPerc1"),
|
|
4800
5216
|
straordAnn1: num("straordAnn1"),
|
|
4801
5217
|
straordAnn2: num("straordAnn2")
|
|
4802
5218
|
};
|
|
@@ -4804,7 +5220,9 @@ ${scenarioLab.length ? `
|
|
|
4804
5220
|
c1: num(`c1_${i}`),
|
|
4805
5221
|
c2: num(`c2_${i}`),
|
|
4806
5222
|
d1: String(document.getElementById(`c1d_${i}`)?.value || "").trim(),
|
|
4807
|
-
d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim()
|
|
5223
|
+
d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim(),
|
|
5224
|
+
d1Ui: collectExpenseDetailUiMeta("c1", i),
|
|
5225
|
+
d2Ui: collectExpenseDetailUiMeta("c2", i)
|
|
4808
5226
|
}));
|
|
4809
5227
|
const expenseItemsState = expenseItems.map((item) => ({ label: item.label, help: item.help }));
|
|
4810
5228
|
const scenariosState = scenarioLab.map((scenario, idx) => ({
|
|
@@ -4835,7 +5253,12 @@ ${scenarioLab.length ? `
|
|
|
4835
5253
|
if (!state || !state.base || !state.spese) return;
|
|
4836
5254
|
Object.entries(state.base).forEach(([k, v]) => {
|
|
4837
5255
|
const el = document.getElementById(k);
|
|
4838
|
-
if (el)
|
|
5256
|
+
if (!el) return;
|
|
5257
|
+
if (el.type === "checkbox") {
|
|
5258
|
+
el.checked = Number(v || 0) > 0;
|
|
5259
|
+
} else {
|
|
5260
|
+
el.value = v;
|
|
5261
|
+
}
|
|
4839
5262
|
});
|
|
4840
5263
|
if (Array.isArray(state.expenseItems) && state.expenseItems.length) {
|
|
4841
5264
|
expenseItems = state.expenseItems.map((item, idx) => normalizeExpenseItem(item, idx));
|
|
@@ -4876,6 +5299,7 @@ ${scenarioLab.length ? `
|
|
|
4876
5299
|
applyStaticTranslations();
|
|
4877
5300
|
applyUiViewStateToDom();
|
|
4878
5301
|
importPermanenceCalendarState(state.permanenceCalendar);
|
|
5302
|
+
updateFirstHomeMortgageUi();
|
|
4879
5303
|
updateSpouseLabels();
|
|
4880
5304
|
buildExpenseRows();
|
|
4881
5305
|
syncPermanenza("calendar");
|
|
@@ -4888,6 +5312,8 @@ ${scenarioLab.length ? `
|
|
|
4888
5312
|
if (c2) c2.value = row.c2;
|
|
4889
5313
|
if (d1) d1.value = String(row && row.d1 ? row.d1 : "");
|
|
4890
5314
|
if (d2) d2.value = String(row && row.d2 ? row.d2 : "");
|
|
5315
|
+
applyExpenseDetailUiMeta("c1", i, row && row.d1Ui ? row.d1Ui : null);
|
|
5316
|
+
applyExpenseDetailUiMeta("c2", i, row && row.d2Ui ? row.d2Ui : null);
|
|
4891
5317
|
});
|
|
4892
5318
|
refreshExpenseDetailButtonState();
|
|
4893
5319
|
}
|
|
@@ -4898,6 +5324,10 @@ ${scenarioLab.length ? `
|
|
|
4898
5324
|
el.value = el.defaultValue || 0;
|
|
4899
5325
|
}
|
|
4900
5326
|
});
|
|
5327
|
+
const firstHomeEnabled = document.getElementById("primaCasaMutuoEnabled");
|
|
5328
|
+
const firstHomeAssigned = document.getElementById("primaCasaAssegnataA");
|
|
5329
|
+
if (firstHomeEnabled) firstHomeEnabled.checked = !!firstHomeEnabled.defaultChecked;
|
|
5330
|
+
if (firstHomeAssigned) firstHomeAssigned.value = "";
|
|
4901
5331
|
permanenceCalendarState.byMonth = {};
|
|
4902
5332
|
selectedScenarioIdx = -1;
|
|
4903
5333
|
uiViewState.spiegOpen = true;
|
|
@@ -4911,6 +5341,7 @@ ${scenarioLab.length ? `
|
|
|
4911
5341
|
renderPermanenceCalendar(monthValue);
|
|
4912
5342
|
applyPermanenceFromCalendar(monthValue, { silentRender: true });
|
|
4913
5343
|
applyUiViewStateToDom();
|
|
5344
|
+
updateFirstHomeMortgageUi();
|
|
4914
5345
|
syncPermanenza();
|
|
4915
5346
|
renderAll();
|
|
4916
5347
|
}
|
|
@@ -5026,7 +5457,10 @@ ${scenarioLab.length ? `
|
|
|
5026
5457
|
const willOpen = wrap.classList.contains("is-hidden");
|
|
5027
5458
|
wrap.classList.toggle("is-hidden", !willOpen);
|
|
5028
5459
|
detailBtn.classList.toggle("is-open", willOpen);
|
|
5029
|
-
if (
|
|
5460
|
+
if (target) {
|
|
5461
|
+
updateExpenseDetailTextareaUi(target);
|
|
5462
|
+
if (willOpen) target.focus();
|
|
5463
|
+
}
|
|
5030
5464
|
}
|
|
5031
5465
|
return;
|
|
5032
5466
|
}
|
|
@@ -5039,6 +5473,7 @@ ${scenarioLab.length ? `
|
|
|
5039
5473
|
|
|
5040
5474
|
rowsSpese.addEventListener("input", (e) => {
|
|
5041
5475
|
if (e.target && e.target.matches("textarea.spese-detail-text")) {
|
|
5476
|
+
updateExpenseDetailTextareaUi(e.target);
|
|
5042
5477
|
refreshExpenseDetailButtonState();
|
|
5043
5478
|
}
|
|
5044
5479
|
});
|
|
@@ -5170,6 +5605,9 @@ ${scenarioLab.length ? `
|
|
|
5170
5605
|
}
|
|
5171
5606
|
updateModeUi();
|
|
5172
5607
|
renderAll();
|
|
5608
|
+
} else if (e.target && (e.target.id === "primaCasaMutuoEnabled" || e.target.id === "primaCasaAssegnataA")) {
|
|
5609
|
+
updateFirstHomeMortgageUi();
|
|
5610
|
+
renderAll();
|
|
5173
5611
|
}
|
|
5174
5612
|
});
|
|
5175
5613
|
|
|
@@ -5213,6 +5651,7 @@ ${scenarioLab.length ? `
|
|
|
5213
5651
|
updateAuthUi();
|
|
5214
5652
|
renderCloudHistoryPanel();
|
|
5215
5653
|
applyUiViewStateToDom();
|
|
5654
|
+
updateFirstHomeMortgageUi();
|
|
5216
5655
|
syncPermanenza();
|
|
5217
5656
|
incomeModeLast = document.getElementById("incomeMode").value || "monthly";
|
|
5218
5657
|
incomeValuesByMode[incomeModeLast] = {
|