diff --git a/CLAUDE.md b/CLAUDE.md index 30e7c88..7a56538 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -186,8 +186,11 @@ ttl_your_data: int = int(os.getenv('CACHE_TTL_YOUR_DATA', '600')) # 10 min defa **Golden Rules**: - ✅ Use global patterns first (`.roa-card`, `.roa-metric`, `.roa-badge-*`) - check `CSS_PATTERNS.md` - ✅ Use design tokens (`var(--color-primary)`) not hardcoded values (`#2563eb`) +- ✅ **Use shared CSS** from `src/assets/css/` - NEVER create new CSS when shared classes exist +- ✅ For inline stats/totals use `.summary-stats-inline`, `.stat-item`, `.stat-label`, `.stat-value` from `stats.css` - ❌ Never use `:deep()` in components (use `src/assets/css/vendor/` for PrimeVue overrides) - ❌ Never duplicate CSS (write once, use everywhere) +- ❌ Never add new scoped CSS for patterns that already exist in shared CSS files **Tables - Unified Column Structure & Filter Buttons**: - ✅ **ALWAYS use separate columns** for related data (Debit | Credit, not Debit+Credit stacked) diff --git a/reports-app/frontend/src/utils/exportUtils.js b/reports-app/frontend/src/utils/exportUtils.js index b4641a2..72281d4 100644 --- a/reports-app/frontend/src/utils/exportUtils.js +++ b/reports-app/frontend/src/utils/exportUtils.js @@ -121,11 +121,16 @@ export const exportToPDF = (data, columns, filename, header) => { const tableColumns = columns.map((col) => col.header); const totalRowIndices = new Set(); // Track which rows are totals + const grandTotalRowIndices = new Set(); // Track grand total rows + const tableRows = data.map((row, rowIndex) => { // Track total rows for special styling if (row._isTotal) { totalRowIndices.add(rowIndex); } + if (row._isGrandTotal) { + grandTotalRowIndices.add(rowIndex); + } return columns.map((col) => { const value = row[col.field]; @@ -180,8 +185,8 @@ export const exportToPDF = (data, columns, filename, header) => { const defaultWidths = { 0: totalWidth * 0.07, // Cont: ~20mm 1: totalWidth * 0.33, // Denumire: ~93mm - 2: totalWidth * 0.1, // Sold Prec D: ~28mm - 3: totalWidth * 0.1, // Sold Prec C: ~28mm + 2: totalWidth * 0.1, // Sume Prec D: ~28mm + 3: totalWidth * 0.1, // Sume Prec C: ~28mm 4: totalWidth * 0.1, // Rulaj D: ~28mm 5: totalWidth * 0.1, // Rulaj C: ~28mm 6: totalWidth * 0.1, // Sold Final D: ~28mm @@ -313,10 +318,16 @@ export const exportToPDF = (data, columns, filename, header) => { const colIndex = data.column.index; const column = columns[colIndex]; - // Style total rows differently (bold, light gray background) - if (totalRowIndices.has(rowIndex)) { + // Style grand total rows (bold, darker gray background) + if (grandTotalRowIndices.has(rowIndex)) { data.cell.styles.fontStyle = "bold"; - data.cell.styles.fillColor = [230, 230, 230]; // Light gray + data.cell.styles.fillColor = [200, 200, 200]; // Darker gray + data.cell.styles.fontSize = 10; + } + // Style class total rows (bold, light gray background) + else if (totalRowIndices.has(rowIndex)) { + data.cell.styles.fontStyle = "bold"; + data.cell.styles.fillColor = [235, 235, 235]; // Light gray } if (column) { @@ -332,6 +343,15 @@ export const exportToPDF = (data, columns, filename, header) => { } } }, + willDrawCell: function (data) { + // Draw double line above grand total row + if (data.section === "body" && grandTotalRowIndices.has(data.row.index)) { + const doc = data.doc; + doc.setDrawColor(100, 100, 100); + doc.setLineWidth(0.5); + doc.line(data.cell.x, data.cell.y, data.cell.x + data.cell.width, data.cell.y); + } + }, }); // Add footer to all pages AFTER table generation diff --git a/reports-app/frontend/src/views/TrialBalanceView.vue b/reports-app/frontend/src/views/TrialBalanceView.vue index b336709..4839076 100644 --- a/reports-app/frontend/src/views/TrialBalanceView.vue +++ b/reports-app/frontend/src/views/TrialBalanceView.vue @@ -97,33 +97,33 @@ - + -
- - - - - - - - - - - - - - - - - - - - - - - -
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) {