From de24a79db5aa689f1e3198d7156bef4bd79f7345 Mon Sep 17 00:00:00 2001 From: Marius Mutu Date: Tue, 9 Dec 2025 15:45:24 +0200 Subject: [PATCH 1/3] feat: Add totals from all filtered records to invoices, treasury, and trial balance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, totals were computed client-side from only the current page data, which gave incorrect results when paginating. Now the backend calculates totals across ALL filtered records and returns them in the API response. - Invoice: Add total_sold_all field for sum of all filtered invoice balances - Treasury: Add sold_precedent_all, total_incasari_all, total_plati_all, sold_final_all - Trial Balance: Add 6-column totals (debit/credit for each balance type) - Frontend stores and views updated to use backend totals 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- reports-app/backend/app/models/invoice.py | 2 + reports-app/backend/app/models/treasury.py | 7 +- .../backend/app/models/trial_balance.py | 12 ++++ .../backend/app/services/invoice_service.py | 15 +++- .../backend/app/services/treasury_service.py | 59 ++++++++++++++- .../app/services/trial_balance_service.py | 27 ++++++- reports-app/frontend/src/stores/invoices.js | 7 ++ reports-app/frontend/src/stores/treasury.js | 10 +++ .../frontend/src/stores/trialBalance.js | 24 +++++++ .../src/views/BankCashRegisterView.vue | 41 ++--------- .../frontend/src/views/InvoicesView.vue | 48 +++---------- .../frontend/src/views/TrialBalanceView.vue | 72 +++++++++++++++++++ 12 files changed, 248 insertions(+), 76 deletions(-) diff --git a/reports-app/backend/app/models/invoice.py b/reports-app/backend/app/models/invoice.py index 4d57f73..844e509 100644 --- a/reports-app/backend/app/models/invoice.py +++ b/reports-app/backend/app/models/invoice.py @@ -64,6 +64,8 @@ class InvoiceListResponse(BaseModel): page_size: int has_more: bool accounting_period: Optional[dict] = Field(default=None, description="Perioada contabilă (an, luna)") + # Total sold din TOATE facturile filtrate (nu doar pagina curentă) + total_sold_all: Decimal = Field(default=Decimal('0.00'), description="Total sold din toate facturile filtrate") class InvoiceSummary(BaseModel): """Rezumat pentru facturi - pentru dashboard""" diff --git a/reports-app/backend/app/models/treasury.py b/reports-app/backend/app/models/treasury.py index 3208868..bc652fd 100644 --- a/reports-app/backend/app/models/treasury.py +++ b/reports-app/backend/app/models/treasury.py @@ -44,4 +44,9 @@ class RegisterListResponse(BaseModel): page: int page_size: int has_more: bool - accounting_period: Optional[AccountingPeriod] = None \ No newline at end of file + accounting_period: Optional[AccountingPeriod] = None + # Totaluri din TOATE înregistrările filtrate (nu doar pagina curentă) + sold_precedent_all: Decimal = Decimal('0.00') + total_incasari_all: Decimal = Decimal('0.00') + total_plati_all: Decimal = Decimal('0.00') + sold_final_all: Decimal = Decimal('0.00') \ No newline at end of file diff --git a/reports-app/backend/app/models/trial_balance.py b/reports-app/backend/app/models/trial_balance.py index 2e84b87..c370411 100644 --- a/reports-app/backend/app/models/trial_balance.py +++ b/reports-app/backend/app/models/trial_balance.py @@ -49,6 +49,18 @@ class TrialBalancePagination(BaseModel): page_size: int = Field(description="Items per page") +class TrialBalanceTotals(BaseModel): + """ + Totals for all 6 columns from all filtered records (not just current page) + """ + total_sold_precedent_debit: Decimal = Decimal('0.00') + total_sold_precedent_credit: Decimal = Decimal('0.00') + total_rulaj_lunar_debit: Decimal = Decimal('0.00') + total_rulaj_lunar_credit: Decimal = Decimal('0.00') + total_sold_final_debit: Decimal = Decimal('0.00') + total_sold_final_credit: Decimal = Decimal('0.00') + + class TrialBalanceResponse(BaseModel): """ Complete response for trial balance endpoint diff --git a/reports-app/backend/app/services/invoice_service.py b/reports-app/backend/app/services/invoice_service.py index fe6d134..e5b44f6 100644 --- a/reports-app/backend/app/services/invoice_service.py +++ b/reports-app/backend/app/services/invoice_service.py @@ -142,7 +142,16 @@ class InvoiceService: count_query = f"SELECT COUNT(*) FROM ({base_query})" cursor.execute(count_query, params) total_count = cursor.fetchone()[0] - + + # Query pentru TOTAL SOLD din TOATE facturile filtrate (nu doar pagina curentă) + total_sold_query = f""" + SELECT NVL(SUM(sold), 0) as total_sold + FROM ({base_query}) + """ + cursor.execute(total_sold_query, params) + total_sold_result = cursor.fetchone() + total_sold_all = Decimal(str(total_sold_result[0])) if total_sold_result else Decimal('0.00') + # Get accounting period - use params if provided, else from calendar if use_param_period: accounting_period = { @@ -226,7 +235,9 @@ class InvoiceService: page=filter_params.page, page_size=filter_params.page_size, has_more=len(invoices) == filter_params.page_size, - accounting_period=accounting_period + accounting_period=accounting_period, + # Total sold din TOATE facturile filtrate + total_sold_all=total_sold_all ) @staticmethod diff --git a/reports-app/backend/app/services/treasury_service.py b/reports-app/backend/app/services/treasury_service.py index 3cb3fab..9840f1b 100644 --- a/reports-app/backend/app/services/treasury_service.py +++ b/reports-app/backend/app/services/treasury_service.py @@ -253,6 +253,58 @@ class TreasuryService: cursor.execute(count_plsql, count_params) total_count = total_count_var.getvalue() + # Query pentru TOTALURI din TOATE înregistrările filtrate (nu doar pagina curentă) + # sold_precedent = suma sold pentru rânduri cu dataact IS NULL + # total_incasari = suma incasari pentru rânduri cu dataact IS NOT NULL + # total_plati = suma plati pentru rânduri cu dataact IS NOT NULL + totals_plsql = f""" + DECLARE + v_an NUMBER; + v_luna NUMBER; + BEGIN + -- Obține anul și luna din parametri sau calendar + {period_select} + + {schema}.PACK_SESIUNE.SETAN(v_an); + {schema}.PACK_SESIUNE.SETLUNA(v_luna); + + -- Sold precedent: suma sold pentru rânduri fără dată (opening balance) + SELECT NVL(SUM(sold), 0) INTO :sold_precedent_all + FROM ({base_select}) sub{where_clause} + WHERE dataact IS NULL; + + -- Total încasări: suma incasari pentru rânduri cu dată (transactions) + SELECT NVL(SUM(incasari), 0) INTO :total_incasari_all + FROM ({base_select}) sub{where_clause} + WHERE dataact IS NOT NULL; + + -- Total plăți: suma plati pentru rânduri cu dată (transactions) + SELECT NVL(SUM(plati), 0) INTO :total_plati_all + FROM ({base_select}) sub{where_clause} + WHERE dataact IS NOT NULL; + END; + """ + + sold_precedent_all_var = cursor.var(oracledb.NUMBER) + total_incasari_all_var = cursor.var(oracledb.NUMBER) + total_plati_all_var = cursor.var(oracledb.NUMBER) + + totals_params = { + 'sold_precedent_all': sold_precedent_all_var, + 'total_incasari_all': total_incasari_all_var, + 'total_plati_all': total_plati_all_var + } + if use_param_period: + totals_params['param_an'] = filter_params.an + totals_params['param_luna'] = filter_params.luna + + cursor.execute(totals_plsql, totals_params) + + sold_precedent_all = Decimal(str(sold_precedent_all_var.getvalue() or 0)) + total_incasari_all = Decimal(str(total_incasari_all_var.getvalue() or 0)) + total_plati_all = Decimal(str(total_plati_all_var.getvalue() or 0)) + sold_final_all = sold_precedent_all + total_incasari_all - total_plati_all + # Procesare rezultate registers = [] total_incasari = Decimal('0.00') @@ -288,7 +340,12 @@ class TreasuryService: page=filter_params.page, page_size=filter_params.page_size, has_more=len(registers) == filter_params.page_size, - accounting_period=AccountingPeriod(an=accounting_year, luna=accounting_month) + accounting_period=AccountingPeriod(an=accounting_year, luna=accounting_month), + # Totaluri din TOATE înregistrările filtrate + sold_precedent_all=sold_precedent_all, + total_incasari_all=total_incasari_all, + total_plati_all=total_plati_all, + sold_final_all=sold_final_all ) @staticmethod diff --git a/reports-app/backend/app/services/trial_balance_service.py b/reports-app/backend/app/services/trial_balance_service.py index a33667f..7fd8132 100644 --- a/reports-app/backend/app/services/trial_balance_service.py +++ b/reports-app/backend/app/services/trial_balance_service.py @@ -135,6 +135,29 @@ class TrialBalanceService: cursor.execute(count_query, params) total_count = cursor.fetchone()[0] + # Query pentru TOTALURI din TOATE înregistrările filtrate (nu doar pagina curentă) + totals_query = f""" + SELECT + NVL(SUM(PRECDEB), 0) as total_prec_deb, + NVL(SUM(PRECCRED), 0) as total_prec_cred, + NVL(SUM(RULDEB), 0) as total_rul_deb, + NVL(SUM(RULCRED), 0) as total_rul_cred, + NVL(SUM(SOLDDEB), 0) as total_sold_deb, + NVL(SUM(SOLDCRED), 0) as total_sold_cred + FROM ({base_query}) + """ + cursor.execute(totals_query, params) + totals_row = cursor.fetchone() + + totals = { + "total_sold_precedent_debit": Decimal(str(totals_row[0])) if totals_row else Decimal('0.00'), + "total_sold_precedent_credit": Decimal(str(totals_row[1])) if totals_row else Decimal('0.00'), + "total_rulaj_lunar_debit": Decimal(str(totals_row[2])) if totals_row else Decimal('0.00'), + "total_rulaj_lunar_credit": Decimal(str(totals_row[3])) if totals_row else Decimal('0.00'), + "total_sold_final_debit": Decimal(str(totals_row[4])) if totals_row else Decimal('0.00'), + "total_sold_final_credit": Decimal(str(totals_row[5])) if totals_row else Decimal('0.00') + } + # Add sorting base_query += f" ORDER BY {sort_by.upper()} {sort_order.upper()}" @@ -189,5 +212,7 @@ class TrialBalanceService: "an": an, "cont_filter": cont_filter, "denumire_filter": denumire_filter - } + }, + # Totaluri din TOATE înregistrările filtrate (nu doar pagina curentă) + "totals": totals } diff --git a/reports-app/frontend/src/stores/invoices.js b/reports-app/frontend/src/stores/invoices.js index f240295..3dbf7f1 100644 --- a/reports-app/frontend/src/stores/invoices.js +++ b/reports-app/frontend/src/stores/invoices.js @@ -8,6 +8,8 @@ export const useInvoicesStore = defineStore("invoices", () => { const isLoading = ref(false); const error = ref(null); const accountingPeriod = ref({ an: null, luna: null }); + // Total sold din TOATE facturile filtrate (nu doar pagina curentă) + const totalSoldAll = ref(0); const filters = ref({ company: null, type: "CLIENTI", // CLIENTI or FURNIZORI @@ -106,6 +108,9 @@ export const useInvoicesStore = defineStore("invoices", () => { invoices.value = response.data.invoices || []; pagination.value.totalRecords = response.data.total_count || 0; + // Store total sold from ALL filtered invoices (not just current page) + totalSoldAll.value = response.data.total_sold_all || 0; + // Store accounting period if available if (response.data.accounting_period) { accountingPeriod.value = response.data.accounting_period; @@ -152,6 +157,7 @@ export const useInvoicesStore = defineStore("invoices", () => { isLoading.value = false; error.value = null; accountingPeriod.value = { an: null, luna: null }; + totalSoldAll.value = 0; clearFilters(); pagination.value = { page: 1, @@ -170,6 +176,7 @@ export const useInvoicesStore = defineStore("invoices", () => { isLoading, error, accountingPeriod, + totalSoldAll, filters, pagination, diff --git a/reports-app/frontend/src/stores/treasury.js b/reports-app/frontend/src/stores/treasury.js index e1b9be7..5c21388 100644 --- a/reports-app/frontend/src/stores/treasury.js +++ b/reports-app/frontend/src/stores/treasury.js @@ -14,6 +14,11 @@ export const useTreasuryStore = defineStore("treasury", () => { const totals = ref({ total_incasari: 0, total_plati: 0, + // Totaluri din TOATE înregistrările filtrate (nu doar pagina curentă) + sold_precedent_all: 0, + total_incasari_all: 0, + total_plati_all: 0, + sold_final_all: 0, }); const accountingPeriod = ref({ an: null, luna: null }); @@ -38,6 +43,11 @@ export const useTreasuryStore = defineStore("treasury", () => { totals.value = { total_incasari: response.data.total_incasari, total_plati: response.data.total_plati, + // Totaluri din TOATE înregistrările filtrate (nu doar pagina curentă) + sold_precedent_all: response.data.sold_precedent_all || 0, + total_incasari_all: response.data.total_incasari_all || 0, + total_plati_all: response.data.total_plati_all || 0, + sold_final_all: response.data.sold_final_all || 0, }; // Store accounting period if available diff --git a/reports-app/frontend/src/stores/trialBalance.js b/reports-app/frontend/src/stores/trialBalance.js index c406ca9..d252234 100644 --- a/reports-app/frontend/src/stores/trialBalance.js +++ b/reports-app/frontend/src/stores/trialBalance.js @@ -11,6 +11,16 @@ export const useTrialBalanceStore = defineStore("trialBalance", () => { const isLoading = ref(false); const error = ref(null); + // Totaluri din TOATE înregistrările filtrate (nu doar pagina curentă) + const totals = ref({ + total_sold_precedent_debit: 0, + total_sold_precedent_credit: 0, + total_rulaj_lunar_debit: 0, + total_rulaj_lunar_credit: 0, + total_sold_final_debit: 0, + total_sold_final_credit: 0, + }); + const filters = ref({ luna: new Date().getMonth() + 1, // Current month (1-12) an: new Date().getFullYear(), // Current year @@ -83,6 +93,11 @@ export const useTrialBalanceStore = defineStore("trialBalance", () => { totalPages: paginationData.total_pages, }; + // Store totals from ALL filtered records (not just current page) + if (response.data.data.totals) { + totals.value = response.data.data.totals; + } + return { success: true }; } else { throw new Error("Invalid response format"); @@ -146,6 +161,14 @@ export const useTrialBalanceStore = defineStore("trialBalance", () => { trialBalanceData.value = []; isLoading.value = false; error.value = null; + totals.value = { + total_sold_precedent_debit: 0, + total_sold_precedent_credit: 0, + total_rulaj_lunar_debit: 0, + total_rulaj_lunar_credit: 0, + total_sold_final_debit: 0, + total_sold_final_credit: 0, + }; filters.value = { luna: new Date().getMonth() + 1, an: new Date().getFullYear(), @@ -169,6 +192,7 @@ export const useTrialBalanceStore = defineStore("trialBalance", () => { trialBalanceData, isLoading, error, + totals, filters, pagination, sorting, diff --git a/reports-app/frontend/src/views/BankCashRegisterView.vue b/reports-app/frontend/src/views/BankCashRegisterView.vue index c0871b0..6ac9276 100644 --- a/reports-app/frontend/src/views/BankCashRegisterView.vue +++ b/reports-app/frontend/src/views/BankCashRegisterView.vue @@ -115,33 +115,34 @@ +
Sold Precedent: {{ formatCurrency(computedTotals.soldPrecedent) }}{{ formatCurrency(treasuryStore.totals.sold_precedent_all) }}
Încasări: {{ - formatCurrency(computedTotals.incasari) + formatCurrency(treasuryStore.totals.total_incasari_all) }}
Plăți: {{ - formatCurrency(computedTotals.plati) + formatCurrency(treasuryStore.totals.total_plati_all) }}
Sold Final: {{ formatCurrency(computedTotals.soldFinal) }}{{ formatCurrency(treasuryStore.totals.sold_final_all) }}
@@ -325,34 +326,6 @@ const truncateText = (text, maxLength = 100) => { return text.substring(0, maxLength) + "..."; }; -// Computed totals - separate sold precedent from actual transactions -// Rows with dataact = null are opening balances (sold precedent) -const computedTotals = computed(() => { - let soldPrecedent = 0; - let incasari = 0; - let plati = 0; - - treasuryStore.registers.forEach((row) => { - if (row.dataact === null || row.dataact === undefined) { - // Opening balance row - the sold value is the opening balance - soldPrecedent += parseFloat(row.sold) || 0; - } else { - // Transaction row - incasari += parseFloat(row.incasari) || 0; - plati += parseFloat(row.plati) || 0; - } - }); - - const soldFinal = soldPrecedent + incasari - plati; - - return { - soldPrecedent, - incasari, - plati, - soldFinal, - }; -}); - // Check if current filter is a VALUTA type (to show Valuta column) const isValutaType = computed(() => { return ( diff --git a/reports-app/frontend/src/views/InvoicesView.vue b/reports-app/frontend/src/views/InvoicesView.vue index 607d97f..5de2ca0 100644 --- a/reports-app/frontend/src/views/InvoicesView.vue +++ b/reports-app/frontend/src/views/InvoicesView.vue @@ -128,6 +128,17 @@ + + +
+
+ Total Sold: + + {{ formatCurrency(invoicesStore.totalSoldAll) }} + +
+
+ - - -
- Total Sold: - {{ - formatCurrency(totalSold) - }} -
@@ -272,12 +275,6 @@ const pagination = ref({ }); // Computed -const totalSold = computed(() => { - return invoicesStore.invoiceList.reduce((sum, invoice) => { - return sum + (parseFloat(invoice.soldfinal) || 0); - }, 0); -}); - const accountingPeriodText = computed(() => { // Use the global period store return periodStore.selectedPeriod?.display_name || ""; @@ -739,29 +736,6 @@ watch( display: block; } -.total-sold { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 1rem; - padding: 1rem; - margin-top: 1rem; - border-top: 2px solid var(--surface-border); - background-color: var(--surface-50); -} - -.total-sold-label { - font-weight: 600; - font-size: 1.1rem; - color: var(--text-color); -} - -.total-sold-value { - font-weight: 700; - font-size: 1.3rem; - color: var(--primary-color); -} - /* Enhanced striped rows with better contrast - same as Trial Balance */ .table-card :deep(.p-datatable .p-datatable-tbody > tr) { transition: background-color 0.2s ease; diff --git a/reports-app/frontend/src/views/TrialBalanceView.vue b/reports-app/frontend/src/views/TrialBalanceView.vue index 6160a62..b336709 100644 --- a/reports-app/frontend/src/views/TrialBalanceView.vue +++ b/reports-app/frontend/src/views/TrialBalanceView.vue @@ -97,6 +97,35 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
Sold PrecedentRulaj LunarSold Final
Debit{{ formatCurrency(trialBalanceStore.totals.total_sold_precedent_debit) }}{{ formatCurrency(trialBalanceStore.totals.total_rulaj_lunar_debit) }}{{ formatCurrency(trialBalanceStore.totals.total_sold_final_debit) }}
Credit{{ formatCurrency(trialBalanceStore.totals.total_sold_precedent_credit) }}{{ formatCurrency(trialBalanceStore.totals.total_rulaj_lunar_credit) }}{{ formatCurrency(trialBalanceStore.totals.total_sold_final_credit) }}
+
+ - + -
- - - - - - - - - - - - - - - - - - - - - - - -
Sold PrecedentRulaj LunarSold Final
Debit{{ formatCurrency(trialBalanceStore.totals.total_sold_precedent_debit) }}{{ formatCurrency(trialBalanceStore.totals.total_rulaj_lunar_debit) }}{{ formatCurrency(trialBalanceStore.totals.total_sold_final_debit) }}
Credit{{ formatCurrency(trialBalanceStore.totals.total_sold_precedent_credit) }}{{ formatCurrency(trialBalanceStore.totals.total_rulaj_lunar_credit) }}{{ formatCurrency(trialBalanceStore.totals.total_sold_final_credit) }}
+
+
+ Sume Prec. D: + {{ formatCurrency(trialBalanceStore.totals.total_sold_precedent_debit) }} +
+
+ Sume Prec. C: + {{ formatCurrency(trialBalanceStore.totals.total_sold_precedent_credit) }} +
+
+ Rulaj D: + {{ formatCurrency(trialBalanceStore.totals.total_rulaj_lunar_debit) }} +
+
+ Rulaj C: + {{ formatCurrency(trialBalanceStore.totals.total_rulaj_lunar_credit) }} +
+
+ Sold Final D: + {{ formatCurrency(trialBalanceStore.totals.total_sold_final_debit) }} +
+
+ Sold Final C: + {{ formatCurrency(trialBalanceStore.totals.total_sold_final_credit) }} +
@@ -180,7 +180,7 @@ @@ -193,7 +193,7 @@ @@ -463,8 +463,8 @@ const exportExcel = async () => { const exportData = allData.map((row) => ({ Cont: row.cont, Denumire: row.denumire, - "Sold Precedent D": parseFloat(row.sold_precedent_debit) || 0, - "Sold Precedent C": parseFloat(row.sold_precedent_credit) || 0, + "Sume Prec. D": parseFloat(row.sold_precedent_debit) || 0, + "Sume Prec. C": parseFloat(row.sold_precedent_credit) || 0, "Rulaj Lunar D": parseFloat(row.rulaj_lunar_debit) || 0, "Rulaj Lunar C": parseFloat(row.rulaj_lunar_credit) || 0, "Sold Final D": parseFloat(row.sold_final_debit) || 0, @@ -525,17 +525,121 @@ const exportPDF = async () => { return; } - // Prepare data for export - const exportData = allData.map((row) => ({ - cont: row.cont, - denumire: row.denumire, - sold_precedent_debit: row.sold_precedent_debit, - sold_precedent_credit: row.sold_precedent_credit, - rulaj_lunar_debit: row.rulaj_lunar_debit, - rulaj_lunar_credit: row.rulaj_lunar_credit, - sold_final_debit: row.sold_final_debit, - sold_final_credit: row.sold_final_credit, - })); + // Group data by account class (first digit) and add class totals + grand total + const groupDataWithTotals = (data) => { + // Sort by account number + const sortedData = [...data].sort((a, b) => + String(a.cont).localeCompare(String(b.cont)) + ); + + const result = []; + const classTotals = {}; // { '1': {sume_prec_d, sume_prec_c, ...}, '2': {...}, ... } + const grandTotal = { + sold_precedent_debit: 0, + sold_precedent_credit: 0, + rulaj_lunar_debit: 0, + rulaj_lunar_credit: 0, + sold_final_debit: 0, + sold_final_credit: 0, + }; + + let currentClass = null; + + sortedData.forEach((row) => { + const accountClass = String(row.cont).charAt(0); + + // Initialize class totals if new class + if (!classTotals[accountClass]) { + classTotals[accountClass] = { + sold_precedent_debit: 0, + sold_precedent_credit: 0, + rulaj_lunar_debit: 0, + rulaj_lunar_credit: 0, + sold_final_debit: 0, + sold_final_credit: 0, + }; + } + + // If class changed and we have a previous class, add its total row + if (currentClass !== null && currentClass !== accountClass) { + result.push({ + cont: "", + denumire: `TOTAL CLASA ${currentClass}`, + sold_precedent_debit: classTotals[currentClass].sold_precedent_debit, + sold_precedent_credit: classTotals[currentClass].sold_precedent_credit, + rulaj_lunar_debit: classTotals[currentClass].rulaj_lunar_debit, + rulaj_lunar_credit: classTotals[currentClass].rulaj_lunar_credit, + sold_final_debit: classTotals[currentClass].sold_final_debit, + sold_final_credit: classTotals[currentClass].sold_final_credit, + _isTotal: true, + }); + } + + currentClass = accountClass; + + // Add the regular row + result.push({ + cont: row.cont, + denumire: row.denumire, + sold_precedent_debit: row.sold_precedent_debit, + sold_precedent_credit: row.sold_precedent_credit, + rulaj_lunar_debit: row.rulaj_lunar_debit, + rulaj_lunar_credit: row.rulaj_lunar_credit, + sold_final_debit: row.sold_final_debit, + sold_final_credit: row.sold_final_credit, + }); + + // Accumulate class totals + classTotals[accountClass].sold_precedent_debit += parseFloat(row.sold_precedent_debit) || 0; + classTotals[accountClass].sold_precedent_credit += parseFloat(row.sold_precedent_credit) || 0; + classTotals[accountClass].rulaj_lunar_debit += parseFloat(row.rulaj_lunar_debit) || 0; + classTotals[accountClass].rulaj_lunar_credit += parseFloat(row.rulaj_lunar_credit) || 0; + classTotals[accountClass].sold_final_debit += parseFloat(row.sold_final_debit) || 0; + classTotals[accountClass].sold_final_credit += parseFloat(row.sold_final_credit) || 0; + + // Accumulate grand total + grandTotal.sold_precedent_debit += parseFloat(row.sold_precedent_debit) || 0; + grandTotal.sold_precedent_credit += parseFloat(row.sold_precedent_credit) || 0; + grandTotal.rulaj_lunar_debit += parseFloat(row.rulaj_lunar_debit) || 0; + grandTotal.rulaj_lunar_credit += parseFloat(row.rulaj_lunar_credit) || 0; + grandTotal.sold_final_debit += parseFloat(row.sold_final_debit) || 0; + grandTotal.sold_final_credit += parseFloat(row.sold_final_credit) || 0; + }); + + // Add last class total + if (currentClass !== null) { + result.push({ + cont: "", + denumire: `TOTAL CLASA ${currentClass}`, + sold_precedent_debit: classTotals[currentClass].sold_precedent_debit, + sold_precedent_credit: classTotals[currentClass].sold_precedent_credit, + rulaj_lunar_debit: classTotals[currentClass].rulaj_lunar_debit, + rulaj_lunar_credit: classTotals[currentClass].rulaj_lunar_credit, + sold_final_debit: classTotals[currentClass].sold_final_debit, + sold_final_credit: classTotals[currentClass].sold_final_credit, + _isTotal: true, + }); + } + + // Add grand total row + result.push({ + cont: "", + denumire: "TOTAL GENERAL", + sold_precedent_debit: grandTotal.sold_precedent_debit, + sold_precedent_credit: grandTotal.sold_precedent_credit, + rulaj_lunar_debit: grandTotal.rulaj_lunar_debit, + rulaj_lunar_credit: grandTotal.rulaj_lunar_credit, + sold_final_debit: grandTotal.sold_final_debit, + sold_final_credit: grandTotal.sold_final_credit, + _isTotal: true, + _isGrandTotal: true, + }); + + return result; + }; + + // Prepare data for export with class totals and grand total + const exportData = groupDataWithTotals(allData); // Define columns for PDF with proper configuration // A4 landscape width: ~297mm total, margins 8mm left+right = 281mm usable @@ -545,13 +649,13 @@ const exportPDF = async () => { { field: "denumire", header: "Denumire Cont", type: "text", width: "auto" }, { field: "sold_precedent_debit", - header: "Sold Prec. D", + header: "Sume Prec. D", type: "number", width: "auto", }, { field: "sold_precedent_credit", - header: "Sold Prec. C", + header: "Sume Prec. C", type: "number", width: "auto", }, @@ -686,48 +790,7 @@ watch( text-align: right; } -/* Totals Table for Trial Balance - 2 rows (Debit/Credit) */ -.totals-table-container { - margin-bottom: var(--space-lg); - display: flex; - justify-content: flex-end; -} - -.totals-table { - border-collapse: collapse; - font-size: 0.9rem; - background: var(--surface-card); - border-radius: var(--border-radius); - overflow: hidden; - box-shadow: var(--shadow-sm); -} - -.totals-table th, -.totals-table td { - padding: 0.5rem 1rem; - text-align: right; -} - -.totals-table th { - background: var(--surface-100); - font-weight: 600; - color: var(--text-color-secondary); -} - -.totals-table .row-label { - text-align: left; - font-weight: 600; - background: var(--surface-50); -} - -.totals-table .numeric { - font-family: var(--font-mono, 'Roboto Mono', monospace); - font-variant-numeric: tabular-nums; -} - -.totals-table tbody tr:first-child td { - border-bottom: 1px solid var(--surface-border); -} +/* Uses shared CSS: stats.css (.summary-stats-inline, .stat-item, .stat-label, .stat-value) */ /* Responsive */ @media (max-width: 768px) {