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 Precedent |
- Rulaj Lunar |
- Sold 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) {