feat: Improve trial balance display and PDF export with class totals
- Change totals display to single horizontal line using shared CSS - Rename column headers from "Sold Prec" to "Sume Prec" (correct terminology) - Add class totals (TOTAL CLASA 1-7) and grand total to PDF export - Style class totals with light gray background, grand total with darker gray - Update CLAUDE.md with rule to use shared CSS instead of creating new styles 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -186,8 +186,11 @@ ttl_your_data: int = int(os.getenv('CACHE_TTL_YOUR_DATA', '600')) # 10 min defa
|
|||||||
**Golden Rules**:
|
**Golden Rules**:
|
||||||
- ✅ Use global patterns first (`.roa-card`, `.roa-metric`, `.roa-badge-*`) - check `CSS_PATTERNS.md`
|
- ✅ 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 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 use `:deep()` in components (use `src/assets/css/vendor/` for PrimeVue overrides)
|
||||||
- ❌ Never duplicate CSS (write once, use everywhere)
|
- ❌ 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**:
|
**Tables - Unified Column Structure & Filter Buttons**:
|
||||||
- ✅ **ALWAYS use separate columns** for related data (Debit | Credit, not Debit+Credit stacked)
|
- ✅ **ALWAYS use separate columns** for related data (Debit | Credit, not Debit+Credit stacked)
|
||||||
|
|||||||
@@ -121,11 +121,16 @@ export const exportToPDF = (data, columns, filename, header) => {
|
|||||||
const tableColumns = columns.map((col) => col.header);
|
const tableColumns = columns.map((col) => col.header);
|
||||||
const totalRowIndices = new Set(); // Track which rows are totals
|
const totalRowIndices = new Set(); // Track which rows are totals
|
||||||
|
|
||||||
|
const grandTotalRowIndices = new Set(); // Track grand total rows
|
||||||
|
|
||||||
const tableRows = data.map((row, rowIndex) => {
|
const tableRows = data.map((row, rowIndex) => {
|
||||||
// Track total rows for special styling
|
// Track total rows for special styling
|
||||||
if (row._isTotal) {
|
if (row._isTotal) {
|
||||||
totalRowIndices.add(rowIndex);
|
totalRowIndices.add(rowIndex);
|
||||||
}
|
}
|
||||||
|
if (row._isGrandTotal) {
|
||||||
|
grandTotalRowIndices.add(rowIndex);
|
||||||
|
}
|
||||||
|
|
||||||
return columns.map((col) => {
|
return columns.map((col) => {
|
||||||
const value = row[col.field];
|
const value = row[col.field];
|
||||||
@@ -180,8 +185,8 @@ export const exportToPDF = (data, columns, filename, header) => {
|
|||||||
const defaultWidths = {
|
const defaultWidths = {
|
||||||
0: totalWidth * 0.07, // Cont: ~20mm
|
0: totalWidth * 0.07, // Cont: ~20mm
|
||||||
1: totalWidth * 0.33, // Denumire: ~93mm
|
1: totalWidth * 0.33, // Denumire: ~93mm
|
||||||
2: totalWidth * 0.1, // Sold Prec D: ~28mm
|
2: totalWidth * 0.1, // Sume Prec D: ~28mm
|
||||||
3: totalWidth * 0.1, // Sold Prec C: ~28mm
|
3: totalWidth * 0.1, // Sume Prec C: ~28mm
|
||||||
4: totalWidth * 0.1, // Rulaj D: ~28mm
|
4: totalWidth * 0.1, // Rulaj D: ~28mm
|
||||||
5: totalWidth * 0.1, // Rulaj C: ~28mm
|
5: totalWidth * 0.1, // Rulaj C: ~28mm
|
||||||
6: totalWidth * 0.1, // Sold Final D: ~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 colIndex = data.column.index;
|
||||||
const column = columns[colIndex];
|
const column = columns[colIndex];
|
||||||
|
|
||||||
// Style total rows differently (bold, light gray background)
|
// Style grand total rows (bold, darker gray background)
|
||||||
if (totalRowIndices.has(rowIndex)) {
|
if (grandTotalRowIndices.has(rowIndex)) {
|
||||||
data.cell.styles.fontStyle = "bold";
|
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) {
|
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
|
// Add footer to all pages AFTER table generation
|
||||||
|
|||||||
@@ -97,33 +97,33 @@
|
|||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- Summary Totals - 2 rows (Debit/Credit) for visual balance verification -->
|
<!-- Summary Totals - Uses shared stats.css -->
|
||||||
<!-- Totaluri din TOATE înregistrările filtrate (nu doar pagina curentă) -->
|
<!-- Totaluri din TOATE înregistrările filtrate (nu doar pagina curentă) -->
|
||||||
<div v-if="companyStore.selectedCompany && trialBalanceStore.hasData" class="totals-table-container">
|
<div v-if="companyStore.selectedCompany && trialBalanceStore.hasData" class="summary-stats-inline">
|
||||||
<table class="totals-table">
|
<div class="stat-item">
|
||||||
<thead>
|
<span class="stat-label">Sume Prec. D:</span>
|
||||||
<tr>
|
<span class="stat-value">{{ formatCurrency(trialBalanceStore.totals.total_sold_precedent_debit) }}</span>
|
||||||
<th></th>
|
</div>
|
||||||
<th>Sold Precedent</th>
|
<div class="stat-item">
|
||||||
<th>Rulaj Lunar</th>
|
<span class="stat-label">Sume Prec. C:</span>
|
||||||
<th>Sold Final</th>
|
<span class="stat-value">{{ formatCurrency(trialBalanceStore.totals.total_sold_precedent_credit) }}</span>
|
||||||
</tr>
|
</div>
|
||||||
</thead>
|
<div class="stat-item">
|
||||||
<tbody>
|
<span class="stat-label">Rulaj D:</span>
|
||||||
<tr>
|
<span class="stat-value">{{ formatCurrency(trialBalanceStore.totals.total_rulaj_lunar_debit) }}</span>
|
||||||
<td class="row-label">Debit</td>
|
</div>
|
||||||
<td class="numeric">{{ formatCurrency(trialBalanceStore.totals.total_sold_precedent_debit) }}</td>
|
<div class="stat-item">
|
||||||
<td class="numeric">{{ formatCurrency(trialBalanceStore.totals.total_rulaj_lunar_debit) }}</td>
|
<span class="stat-label">Rulaj C:</span>
|
||||||
<td class="numeric">{{ formatCurrency(trialBalanceStore.totals.total_sold_final_debit) }}</td>
|
<span class="stat-value">{{ formatCurrency(trialBalanceStore.totals.total_rulaj_lunar_credit) }}</span>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="stat-item">
|
||||||
<td class="row-label">Credit</td>
|
<span class="stat-label">Sold Final D:</span>
|
||||||
<td class="numeric">{{ formatCurrency(trialBalanceStore.totals.total_sold_precedent_credit) }}</td>
|
<span class="stat-value">{{ formatCurrency(trialBalanceStore.totals.total_sold_final_debit) }}</span>
|
||||||
<td class="numeric">{{ formatCurrency(trialBalanceStore.totals.total_rulaj_lunar_credit) }}</td>
|
</div>
|
||||||
<td class="numeric">{{ formatCurrency(trialBalanceStore.totals.total_sold_final_credit) }}</td>
|
<div class="stat-item">
|
||||||
</tr>
|
<span class="stat-label">Sold Final C:</span>
|
||||||
</tbody>
|
<span class="stat-value">{{ formatCurrency(trialBalanceStore.totals.total_sold_final_credit) }}</span>
|
||||||
</table>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Trial Balance Table -->
|
<!-- Trial Balance Table -->
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
|
|
||||||
<Column
|
<Column
|
||||||
field="sold_precedent_debit"
|
field="sold_precedent_debit"
|
||||||
header="Sold Prec. D"
|
header="Sume Prec. D"
|
||||||
sortable
|
sortable
|
||||||
:style="{ width: '10%' }"
|
:style="{ width: '10%' }"
|
||||||
>
|
>
|
||||||
@@ -193,7 +193,7 @@
|
|||||||
|
|
||||||
<Column
|
<Column
|
||||||
field="sold_precedent_credit"
|
field="sold_precedent_credit"
|
||||||
header="Sold Prec. C"
|
header="Sume Prec. C"
|
||||||
sortable
|
sortable
|
||||||
:style="{ width: '10%' }"
|
:style="{ width: '10%' }"
|
||||||
>
|
>
|
||||||
@@ -463,8 +463,8 @@ const exportExcel = async () => {
|
|||||||
const exportData = allData.map((row) => ({
|
const exportData = allData.map((row) => ({
|
||||||
Cont: row.cont,
|
Cont: row.cont,
|
||||||
Denumire: row.denumire,
|
Denumire: row.denumire,
|
||||||
"Sold Precedent D": parseFloat(row.sold_precedent_debit) || 0,
|
"Sume Prec. D": parseFloat(row.sold_precedent_debit) || 0,
|
||||||
"Sold Precedent C": parseFloat(row.sold_precedent_credit) || 0,
|
"Sume Prec. C": parseFloat(row.sold_precedent_credit) || 0,
|
||||||
"Rulaj Lunar D": parseFloat(row.rulaj_lunar_debit) || 0,
|
"Rulaj Lunar D": parseFloat(row.rulaj_lunar_debit) || 0,
|
||||||
"Rulaj Lunar C": parseFloat(row.rulaj_lunar_credit) || 0,
|
"Rulaj Lunar C": parseFloat(row.rulaj_lunar_credit) || 0,
|
||||||
"Sold Final D": parseFloat(row.sold_final_debit) || 0,
|
"Sold Final D": parseFloat(row.sold_final_debit) || 0,
|
||||||
@@ -525,17 +525,121 @@ const exportPDF = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare data for export
|
// Group data by account class (first digit) and add class totals + grand total
|
||||||
const exportData = allData.map((row) => ({
|
const groupDataWithTotals = (data) => {
|
||||||
cont: row.cont,
|
// Sort by account number
|
||||||
denumire: row.denumire,
|
const sortedData = [...data].sort((a, b) =>
|
||||||
sold_precedent_debit: row.sold_precedent_debit,
|
String(a.cont).localeCompare(String(b.cont))
|
||||||
sold_precedent_credit: row.sold_precedent_credit,
|
);
|
||||||
rulaj_lunar_debit: row.rulaj_lunar_debit,
|
|
||||||
rulaj_lunar_credit: row.rulaj_lunar_credit,
|
const result = [];
|
||||||
sold_final_debit: row.sold_final_debit,
|
const classTotals = {}; // { '1': {sume_prec_d, sume_prec_c, ...}, '2': {...}, ... }
|
||||||
sold_final_credit: row.sold_final_credit,
|
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
|
// Define columns for PDF with proper configuration
|
||||||
// A4 landscape width: ~297mm total, margins 8mm left+right = 281mm usable
|
// 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: "denumire", header: "Denumire Cont", type: "text", width: "auto" },
|
||||||
{
|
{
|
||||||
field: "sold_precedent_debit",
|
field: "sold_precedent_debit",
|
||||||
header: "Sold Prec. D",
|
header: "Sume Prec. D",
|
||||||
type: "number",
|
type: "number",
|
||||||
width: "auto",
|
width: "auto",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "sold_precedent_credit",
|
field: "sold_precedent_credit",
|
||||||
header: "Sold Prec. C",
|
header: "Sume Prec. C",
|
||||||
type: "number",
|
type: "number",
|
||||||
width: "auto",
|
width: "auto",
|
||||||
},
|
},
|
||||||
@@ -686,48 +790,7 @@ watch(
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Totals Table for Trial Balance - 2 rows (Debit/Credit) */
|
/* Uses shared CSS: stats.css (.summary-stats-inline, .stat-item, .stat-label, .stat-value) */
|
||||||
.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|||||||
Reference in New Issue
Block a user