Merge branch 'feature/add-totals-facturi-balanta'
Features: - Trial balance totals on single line with shared CSS - Renamed "Sold Prec" to "Sume Prec" (correct terminology) - PDF export with class totals (TOTAL CLASA 1-7) and grand total - Updated CLAUDE.md with shared CSS guidelines
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)
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ class InvoiceListResponse(BaseModel):
|
|||||||
page_size: int
|
page_size: int
|
||||||
has_more: bool
|
has_more: bool
|
||||||
accounting_period: Optional[dict] = Field(default=None, description="Perioada contabilă (an, luna)")
|
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):
|
class InvoiceSummary(BaseModel):
|
||||||
"""Rezumat pentru facturi - pentru dashboard"""
|
"""Rezumat pentru facturi - pentru dashboard"""
|
||||||
|
|||||||
@@ -44,4 +44,9 @@ class RegisterListResponse(BaseModel):
|
|||||||
page: int
|
page: int
|
||||||
page_size: int
|
page_size: int
|
||||||
has_more: bool
|
has_more: bool
|
||||||
accounting_period: Optional[AccountingPeriod] = None
|
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')
|
||||||
@@ -49,6 +49,18 @@ class TrialBalancePagination(BaseModel):
|
|||||||
page_size: int = Field(description="Items per page")
|
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):
|
class TrialBalanceResponse(BaseModel):
|
||||||
"""
|
"""
|
||||||
Complete response for trial balance endpoint
|
Complete response for trial balance endpoint
|
||||||
|
|||||||
@@ -142,7 +142,16 @@ class InvoiceService:
|
|||||||
count_query = f"SELECT COUNT(*) FROM ({base_query})"
|
count_query = f"SELECT COUNT(*) FROM ({base_query})"
|
||||||
cursor.execute(count_query, params)
|
cursor.execute(count_query, params)
|
||||||
total_count = cursor.fetchone()[0]
|
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
|
# Get accounting period - use params if provided, else from calendar
|
||||||
if use_param_period:
|
if use_param_period:
|
||||||
accounting_period = {
|
accounting_period = {
|
||||||
@@ -226,7 +235,9 @@ class InvoiceService:
|
|||||||
page=filter_params.page,
|
page=filter_params.page,
|
||||||
page_size=filter_params.page_size,
|
page_size=filter_params.page_size,
|
||||||
has_more=len(invoices) == 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
|
@staticmethod
|
||||||
|
|||||||
@@ -253,6 +253,60 @@ class TreasuryService:
|
|||||||
cursor.execute(count_plsql, count_params)
|
cursor.execute(count_plsql, count_params)
|
||||||
total_count = total_count_var.getvalue()
|
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
|
||||||
|
# Notă: where_clause poate fi gol sau poate conține "WHERE ..."
|
||||||
|
# Dacă e gol, adăugăm WHERE; dacă nu, adăugăm AND
|
||||||
|
dataact_null_cond = " AND dataact IS NULL" if where_clause else " WHERE dataact IS NULL"
|
||||||
|
dataact_not_null_cond = " AND dataact IS NOT NULL" if where_clause else " WHERE 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}{dataact_null_cond};
|
||||||
|
|
||||||
|
-- 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}{dataact_not_null_cond};
|
||||||
|
|
||||||
|
-- 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}{dataact_not_null_cond};
|
||||||
|
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
|
# Procesare rezultate
|
||||||
registers = []
|
registers = []
|
||||||
total_incasari = Decimal('0.00')
|
total_incasari = Decimal('0.00')
|
||||||
@@ -288,7 +342,12 @@ class TreasuryService:
|
|||||||
page=filter_params.page,
|
page=filter_params.page,
|
||||||
page_size=filter_params.page_size,
|
page_size=filter_params.page_size,
|
||||||
has_more=len(registers) == 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
|
@staticmethod
|
||||||
|
|||||||
@@ -135,6 +135,29 @@ class TrialBalanceService:
|
|||||||
cursor.execute(count_query, params)
|
cursor.execute(count_query, params)
|
||||||
total_count = cursor.fetchone()[0]
|
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
|
# Add sorting
|
||||||
base_query += f" ORDER BY {sort_by.upper()} {sort_order.upper()}"
|
base_query += f" ORDER BY {sort_by.upper()} {sort_order.upper()}"
|
||||||
|
|
||||||
@@ -189,5 +212,7 @@ class TrialBalanceService:
|
|||||||
"an": an,
|
"an": an,
|
||||||
"cont_filter": cont_filter,
|
"cont_filter": cont_filter,
|
||||||
"denumire_filter": denumire_filter
|
"denumire_filter": denumire_filter
|
||||||
}
|
},
|
||||||
|
# Totaluri din TOATE înregistrările filtrate (nu doar pagina curentă)
|
||||||
|
"totals": totals
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export const useInvoicesStore = defineStore("invoices", () => {
|
|||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const error = ref(null);
|
const error = ref(null);
|
||||||
const accountingPeriod = ref({ an: null, luna: 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({
|
const filters = ref({
|
||||||
company: null,
|
company: null,
|
||||||
type: "CLIENTI", // CLIENTI or FURNIZORI
|
type: "CLIENTI", // CLIENTI or FURNIZORI
|
||||||
@@ -106,6 +108,9 @@ export const useInvoicesStore = defineStore("invoices", () => {
|
|||||||
invoices.value = response.data.invoices || [];
|
invoices.value = response.data.invoices || [];
|
||||||
pagination.value.totalRecords = response.data.total_count || 0;
|
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
|
// Store accounting period if available
|
||||||
if (response.data.accounting_period) {
|
if (response.data.accounting_period) {
|
||||||
accountingPeriod.value = response.data.accounting_period;
|
accountingPeriod.value = response.data.accounting_period;
|
||||||
@@ -152,6 +157,7 @@ export const useInvoicesStore = defineStore("invoices", () => {
|
|||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
error.value = null;
|
error.value = null;
|
||||||
accountingPeriod.value = { an: null, luna: null };
|
accountingPeriod.value = { an: null, luna: null };
|
||||||
|
totalSoldAll.value = 0;
|
||||||
clearFilters();
|
clearFilters();
|
||||||
pagination.value = {
|
pagination.value = {
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -170,6 +176,7 @@ export const useInvoicesStore = defineStore("invoices", () => {
|
|||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
accountingPeriod,
|
accountingPeriod,
|
||||||
|
totalSoldAll,
|
||||||
filters,
|
filters,
|
||||||
pagination,
|
pagination,
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ export const useTreasuryStore = defineStore("treasury", () => {
|
|||||||
const totals = ref({
|
const totals = ref({
|
||||||
total_incasari: 0,
|
total_incasari: 0,
|
||||||
total_plati: 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 });
|
const accountingPeriod = ref({ an: null, luna: null });
|
||||||
|
|
||||||
@@ -38,6 +43,11 @@ export const useTreasuryStore = defineStore("treasury", () => {
|
|||||||
totals.value = {
|
totals.value = {
|
||||||
total_incasari: response.data.total_incasari,
|
total_incasari: response.data.total_incasari,
|
||||||
total_plati: response.data.total_plati,
|
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
|
// Store accounting period if available
|
||||||
|
|||||||
@@ -11,6 +11,16 @@ export const useTrialBalanceStore = defineStore("trialBalance", () => {
|
|||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const error = ref(null);
|
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({
|
const filters = ref({
|
||||||
luna: new Date().getMonth() + 1, // Current month (1-12)
|
luna: new Date().getMonth() + 1, // Current month (1-12)
|
||||||
an: new Date().getFullYear(), // Current year
|
an: new Date().getFullYear(), // Current year
|
||||||
@@ -83,6 +93,11 @@ export const useTrialBalanceStore = defineStore("trialBalance", () => {
|
|||||||
totalPages: paginationData.total_pages,
|
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 };
|
return { success: true };
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Invalid response format");
|
throw new Error("Invalid response format");
|
||||||
@@ -146,6 +161,14 @@ export const useTrialBalanceStore = defineStore("trialBalance", () => {
|
|||||||
trialBalanceData.value = [];
|
trialBalanceData.value = [];
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
error.value = null;
|
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 = {
|
filters.value = {
|
||||||
luna: new Date().getMonth() + 1,
|
luna: new Date().getMonth() + 1,
|
||||||
an: new Date().getFullYear(),
|
an: new Date().getFullYear(),
|
||||||
@@ -169,6 +192,7 @@ export const useTrialBalanceStore = defineStore("trialBalance", () => {
|
|||||||
trialBalanceData,
|
trialBalanceData,
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
|
totals,
|
||||||
filters,
|
filters,
|
||||||
pagination,
|
pagination,
|
||||||
sorting,
|
sorting,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -115,33 +115,34 @@
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- Summary Stats - Compact, right aligned -->
|
<!-- Summary Stats - Compact, right aligned -->
|
||||||
|
<!-- Folosește totaluri din TOATE înregistrările (backend) nu doar pagina curentă -->
|
||||||
<div v-if="companyStore.selectedCompany" class="summary-stats-inline">
|
<div v-if="companyStore.selectedCompany" class="summary-stats-inline">
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-label">Sold Precedent:</span>
|
<span class="stat-label">Sold Precedent:</span>
|
||||||
<span
|
<span
|
||||||
class="stat-value"
|
class="stat-value"
|
||||||
:class="computedTotals.soldPrecedent >= 0 ? 'incasari' : 'plati'"
|
:class="treasuryStore.totals.sold_precedent_all >= 0 ? 'incasari' : 'plati'"
|
||||||
>{{ formatCurrency(computedTotals.soldPrecedent) }}</span
|
>{{ formatCurrency(treasuryStore.totals.sold_precedent_all) }}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-label">Încasări:</span>
|
<span class="stat-label">Încasări:</span>
|
||||||
<span class="stat-value incasari">{{
|
<span class="stat-value incasari">{{
|
||||||
formatCurrency(computedTotals.incasari)
|
formatCurrency(treasuryStore.totals.total_incasari_all)
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-label">Plăți:</span>
|
<span class="stat-label">Plăți:</span>
|
||||||
<span class="stat-value plati">{{
|
<span class="stat-value plati">{{
|
||||||
formatCurrency(computedTotals.plati)
|
formatCurrency(treasuryStore.totals.total_plati_all)
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-label">Sold Final:</span>
|
<span class="stat-label">Sold Final:</span>
|
||||||
<span
|
<span
|
||||||
class="stat-value"
|
class="stat-value"
|
||||||
:class="computedTotals.soldFinal >= 0 ? 'incasari' : 'plati'"
|
:class="treasuryStore.totals.sold_final_all >= 0 ? 'incasari' : 'plati'"
|
||||||
>{{ formatCurrency(computedTotals.soldFinal) }}</span
|
>{{ formatCurrency(treasuryStore.totals.sold_final_all) }}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -325,34 +326,6 @@ const truncateText = (text, maxLength = 100) => {
|
|||||||
return text.substring(0, maxLength) + "...";
|
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)
|
// Check if current filter is a VALUTA type (to show Valuta column)
|
||||||
const isValutaType = computed(() => {
|
const isValutaType = computed(() => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -128,6 +128,17 @@
|
|||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<!-- Summary Stats - Compact, right aligned -->
|
||||||
|
<!-- Total sold din TOATE facturile filtrate (nu doar pagina curentă) -->
|
||||||
|
<div v-if="companyStore.selectedCompany && invoicesStore.hasInvoices" class="summary-stats-inline">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Total Sold:</span>
|
||||||
|
<span class="stat-value" :class="invoicesStore.totalSoldAll > 0 ? 'plati' : 'incasari'">
|
||||||
|
{{ formatCurrency(invoicesStore.totalSoldAll) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Invoices Table -->
|
<!-- Invoices Table -->
|
||||||
<Card v-if="companyStore.selectedCompany" class="table-card">
|
<Card v-if="companyStore.selectedCompany" class="table-card">
|
||||||
<template #content>
|
<template #content>
|
||||||
@@ -227,14 +238,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
|
|
||||||
<!-- Total Sold -->
|
|
||||||
<div v-if="invoicesStore.hasInvoices" class="total-sold">
|
|
||||||
<span class="total-sold-label">Total Sold:</span>
|
|
||||||
<span class="total-sold-value">{{
|
|
||||||
formatCurrency(totalSold)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@@ -272,12 +275,6 @@ const pagination = ref({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
const totalSold = computed(() => {
|
|
||||||
return invoicesStore.invoiceList.reduce((sum, invoice) => {
|
|
||||||
return sum + (parseFloat(invoice.soldfinal) || 0);
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
const accountingPeriodText = computed(() => {
|
const accountingPeriodText = computed(() => {
|
||||||
// Use the global period store
|
// Use the global period store
|
||||||
return periodStore.selectedPeriod?.display_name || "";
|
return periodStore.selectedPeriod?.display_name || "";
|
||||||
@@ -739,29 +736,6 @@ watch(
|
|||||||
display: block;
|
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 */
|
/* Enhanced striped rows with better contrast - same as Trial Balance */
|
||||||
.table-card :deep(.p-datatable .p-datatable-tbody > tr) {
|
.table-card :deep(.p-datatable .p-datatable-tbody > tr) {
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
|
|||||||
@@ -97,6 +97,35 @@
|
|||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<!-- Summary Totals - Uses shared stats.css -->
|
||||||
|
<!-- Totaluri din TOATE înregistrările filtrate (nu doar pagina curentă) -->
|
||||||
|
<div v-if="companyStore.selectedCompany && trialBalanceStore.hasData" class="summary-stats-inline">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Sume Prec. D:</span>
|
||||||
|
<span class="stat-value">{{ formatCurrency(trialBalanceStore.totals.total_sold_precedent_debit) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Sume Prec. C:</span>
|
||||||
|
<span class="stat-value">{{ formatCurrency(trialBalanceStore.totals.total_sold_precedent_credit) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Rulaj D:</span>
|
||||||
|
<span class="stat-value">{{ formatCurrency(trialBalanceStore.totals.total_rulaj_lunar_debit) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Rulaj C:</span>
|
||||||
|
<span class="stat-value">{{ formatCurrency(trialBalanceStore.totals.total_rulaj_lunar_credit) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Sold Final D:</span>
|
||||||
|
<span class="stat-value">{{ formatCurrency(trialBalanceStore.totals.total_sold_final_debit) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Sold Final C:</span>
|
||||||
|
<span class="stat-value">{{ formatCurrency(trialBalanceStore.totals.total_sold_final_credit) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Trial Balance Table -->
|
<!-- Trial Balance Table -->
|
||||||
<Card v-if="companyStore.selectedCompany" class="table-card">
|
<Card v-if="companyStore.selectedCompany" class="table-card">
|
||||||
<template #content>
|
<template #content>
|
||||||
@@ -151,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%' }"
|
||||||
>
|
>
|
||||||
@@ -164,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%' }"
|
||||||
>
|
>
|
||||||
@@ -434,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,
|
||||||
@@ -496,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
|
||||||
@@ -516,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",
|
||||||
},
|
},
|
||||||
@@ -657,6 +790,8 @@ watch(
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Uses shared CSS: stats.css (.summary-stats-inline, .stat-item, .stat-label, .stat-value) */
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.trial-balance {
|
.trial-balance {
|
||||||
|
|||||||
Reference in New Issue
Block a user