diff --git a/reports-app/backend/.env.test b/reports-app/backend/.env.test index a4d9b9f..0e362c0 100644 --- a/reports-app/backend/.env.test +++ b/reports-app/backend/.env.test @@ -7,8 +7,9 @@ ORACLE_PASSWORD=ROMFASTSOFT ORACLE_DSN=localhost:1526/roa # Test credentials for pytest (user exists in Oracle TEST) -TEST_ORACLE_USER=MARIUS M -TEST_ORACLE_PASS=123 +# Quotes required for shell (source), dotenv strips them automatically +TEST_ORACLE_USER="MARIUS M" +TEST_ORACLE_PASS="123" # Test company - MARIUSM_AUTO schema (only schema with full data in TEST) # Other schemas (ACN, DANUBE, EMS) don't have required tables diff --git a/reports-app/frontend/src/stores/invoices.js b/reports-app/frontend/src/stores/invoices.js index 8b91baa..f240295 100644 --- a/reports-app/frontend/src/stores/invoices.js +++ b/reports-app/frontend/src/stores/invoices.js @@ -67,8 +67,11 @@ export const useInvoicesStore = defineStore("invoices", () => { // Convert Date object to YYYY-MM-DD string format (LOCAL date, not UTC) if (filters.value.dateFrom instanceof Date) { const year = filters.value.dateFrom.getFullYear(); - const month = String(filters.value.dateFrom.getMonth() + 1).padStart(2, '0'); - const day = String(filters.value.dateFrom.getDate()).padStart(2, '0'); + const month = String(filters.value.dateFrom.getMonth() + 1).padStart( + 2, + "0", + ); + const day = String(filters.value.dateFrom.getDate()).padStart(2, "0"); params.date_from = `${year}-${month}-${day}`; } else { params.date_from = filters.value.dateFrom; @@ -78,8 +81,11 @@ export const useInvoicesStore = defineStore("invoices", () => { // Convert Date object to YYYY-MM-DD string format (LOCAL date, not UTC) if (filters.value.dateTo instanceof Date) { const year = filters.value.dateTo.getFullYear(); - const month = String(filters.value.dateTo.getMonth() + 1).padStart(2, '0'); - const day = String(filters.value.dateTo.getDate()).padStart(2, '0'); + const month = String(filters.value.dateTo.getMonth() + 1).padStart( + 2, + "0", + ); + const day = String(filters.value.dateTo.getDate()).padStart(2, "0"); params.date_to = `${year}-${month}-${day}`; } else { params.date_to = filters.value.dateTo; @@ -93,8 +99,8 @@ export const useInvoicesStore = defineStore("invoices", () => { const response = await apiService.get(`/invoices/`, { params: { company: companyCode, - ...params - } + ...params, + }, }); invoices.value = response.data.invoices || []; diff --git a/reports-app/frontend/src/stores/treasury.js b/reports-app/frontend/src/stores/treasury.js index c13c36f..cee72d4 100644 --- a/reports-app/frontend/src/stores/treasury.js +++ b/reports-app/frontend/src/stores/treasury.js @@ -13,7 +13,7 @@ export const useTreasuryStore = defineStore("treasury", () => { }); const totals = ref({ total_incasari: 0, - total_plati: 0 + total_plati: 0, }); const loadBankCashRegister = async (companyId, filters = {}) => { @@ -25,18 +25,18 @@ export const useTreasuryStore = defineStore("treasury", () => { company: companyId, page: pagination.value.page + 1, page_size: pagination.value.rows, - ...filters + ...filters, }; - const response = await apiService.get('/treasury/bank-cash-register', { - params + const response = await apiService.get("/treasury/bank-cash-register", { + params, }); registers.value = response.data.registers || []; pagination.value.totalRecords = response.data.total_count || 0; totals.value = { total_incasari: response.data.total_incasari, - total_plati: response.data.total_plati + total_plati: response.data.total_plati, }; return { success: true }; @@ -72,6 +72,6 @@ export const useTreasuryStore = defineStore("treasury", () => { totals, loadBankCashRegister, setPagination, - reset + reset, }; -}); \ No newline at end of file +}); diff --git a/reports-app/frontend/src/utils/exportUtils.js b/reports-app/frontend/src/utils/exportUtils.js index 52a07bd..03f5ea2 100644 --- a/reports-app/frontend/src/utils/exportUtils.js +++ b/reports-app/frontend/src/utils/exportUtils.js @@ -1,17 +1,17 @@ -import * as XLSX from 'xlsx'; -import { jsPDF } from 'jspdf'; -import autoTable from 'jspdf-autotable'; +import * as XLSX from "xlsx"; +import { jsPDF } from "jspdf"; +import autoTable from "jspdf-autotable"; /** * Format currency values for export */ const formatCurrency = (value) => { - if (value == null || value === '-') return '-'; - return new Intl.NumberFormat('ro-RO', { - style: 'currency', - currency: 'RON', + if (value == null || value === "-") return "-"; + return new Intl.NumberFormat("ro-RO", { + style: "currency", + currency: "RON", minimumFractionDigits: 0, - maximumFractionDigits: 0 + maximumFractionDigits: 0, }).format(value); }; @@ -21,15 +21,18 @@ const formatCurrency = (value) => { * @param {String} filename - Name of the file (without extension) * @param {String} sheetName - Name of the Excel sheet */ -export const exportToExcel = (data, filename, sheetName = 'Sheet1') => { +export const exportToExcel = (data, filename, sheetName = "Sheet1") => { try { const ws = XLSX.utils.json_to_sheet(data); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, sheetName); - XLSX.writeFile(wb, `${filename}_${new Date().toISOString().split('T')[0]}.xlsx`); + XLSX.writeFile( + wb, + `${filename}_${new Date().toISOString().split("T")[0]}.xlsx`, + ); return { success: true }; } catch (error) { - console.error('Excel export failed:', error); + console.error("Excel export failed:", error); return { success: false, error }; } }; @@ -38,12 +41,12 @@ export const exportToExcel = (data, filename, sheetName = 'Sheet1') => { * Format number for PDF export */ const formatNumberForPDF = (value) => { - if (value == null || value === '' || value === '-') return '-'; + if (value == null || value === "" || value === "-") return "-"; const num = parseFloat(value); - if (isNaN(num)) return '-'; - return new Intl.NumberFormat('ro-RO', { + if (isNaN(num)) return "-"; + return new Intl.NumberFormat("ro-RO", { minimumFractionDigits: 2, - maximumFractionDigits: 2 + maximumFractionDigits: 2, }).format(num); }; @@ -58,17 +61,17 @@ export const exportToPDF = (data, columns, filename, header) => { try { // Check if data exists if (!data || data.length === 0) { - console.error('No data to export'); - return { success: false, error: 'No data available' }; + console.error("No data to export"); + return { success: false, error: "No data available" }; } // Check if jsPDF is properly imported - if (typeof jsPDF === 'undefined') { - console.error('jsPDF not properly imported'); - return { success: false, error: 'PDF library not available' }; + if (typeof jsPDF === "undefined") { + console.error("jsPDF not properly imported"); + return { success: false, error: "PDF library not available" }; } - const doc = new jsPDF('landscape', 'mm', 'a4'); + const doc = new jsPDF("landscape", "mm", "a4"); const pageWidth = doc.internal.pageSize.getWidth(); const pageHeight = doc.internal.pageSize.getHeight(); const marginLeft = 8; @@ -79,38 +82,38 @@ export const exportToPDF = (data, columns, filename, header) => { const addHeader = () => { // Line 1: Company name (left aligned, bold, larger font) doc.setFontSize(13); - doc.setFont(undefined, 'bold'); - const companyName = header.companyName || 'N/A'; + doc.setFont(undefined, "bold"); + const companyName = header.companyName || "N/A"; doc.text(companyName, marginLeft, 15); // Line 2: Title "Balanta de Verificare" (centered) doc.setFontSize(14); - doc.setFont(undefined, 'bold'); - const titleWidth = doc.getTextWidth(header.title || ''); + doc.setFont(undefined, "bold"); + const titleWidth = doc.getTextWidth(header.title || ""); const titleX = marginLeft + (contentWidth - titleWidth) / 2; - doc.text(header.title || '', titleX, 24); + doc.text(header.title || "", titleX, 24); // Line 3: Period (centered, below title) doc.setFontSize(11); - doc.setFont(undefined, 'normal'); - const periodText = header.period || ''; + doc.setFont(undefined, "normal"); + const periodText = header.period || ""; const periodWidth = doc.getTextWidth(periodText); const periodX = marginLeft + (contentWidth - periodWidth) / 2; doc.text(periodText, periodX, 32); }; // Prepare table data - const tableColumns = columns.map(col => col.header); - const tableRows = data.map(row => - columns.map(col => { + const tableColumns = columns.map((col) => col.header); + const tableRows = data.map((row) => + columns.map((col) => { const value = row[col.field]; - if (col.type === 'currency') { + if (col.type === "currency") { return formatCurrency(value); - } else if (col.type === 'number') { + } else if (col.type === "number") { return formatNumberForPDF(value); } - return value || '-'; - }) + return value || "-"; + }), ); // Function to add footer (called for each page) @@ -119,8 +122,12 @@ export const exportToPDF = (data, columns, filename, header) => { // Left side: Generation date doc.setFontSize(8); - doc.setFont(undefined, 'normal'); - doc.text(`Generat: ${new Date().toLocaleString('ro-RO')}`, marginLeft, footerY); + doc.setFont(undefined, "normal"); + doc.text( + `Generat: ${new Date().toLocaleString("ro-RO")}`, + marginLeft, + footerY, + ); // Right side: Page numbers const pageText = `Pagina ${pageNum} din ${totalPages}`; @@ -129,7 +136,7 @@ export const exportToPDF = (data, columns, filename, header) => { }; // Check if autoTable is available - if (typeof autoTable === 'function') { + if (typeof autoTable === "function") { // Build column styles - jspdf-autotable uses numeric keys const columnStyles = {}; @@ -142,37 +149,37 @@ export const exportToPDF = (data, columns, filename, header) => { columns.forEach((col, index) => { // Use custom width if provided, otherwise auto - if (col.width && typeof col.width === 'number') { + if (col.width && typeof col.width === "number") { widthAllocations[index] = totalWidth * col.width; - } else if (col.width === 'auto') { - widthAllocations[index] = 'auto'; + } else if (col.width === "auto") { + widthAllocations[index] = "auto"; } else { // Default width allocation for Trial Balance (8 columns) const defaultWidths = { - 0: totalWidth * 0.07, // Cont: ~20mm - 1: totalWidth * 0.33, // Denumire: ~93mm - 2: totalWidth * 0.10, // Sold Prec D: ~28mm - 3: totalWidth * 0.10, // Sold Prec C: ~28mm - 4: totalWidth * 0.10, // Rulaj D: ~28mm - 5: totalWidth * 0.10, // Rulaj C: ~28mm - 6: totalWidth * 0.10, // Sold Final D: ~28mm - 7: totalWidth * 0.10, // Sold Final C: ~28mm + 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 + 4: totalWidth * 0.1, // Rulaj D: ~28mm + 5: totalWidth * 0.1, // Rulaj C: ~28mm + 6: totalWidth * 0.1, // Sold Final D: ~28mm + 7: totalWidth * 0.1, // Sold Final C: ~28mm }; - widthAllocations[index] = defaultWidths[index] || 'auto'; + widthAllocations[index] = defaultWidths[index] || "auto"; } }); columns.forEach((col, index) => { columnStyles[index] = { - cellWidth: widthAllocations[index] + cellWidth: widthAllocations[index], }; // Set alignment based on type - if (col.type === 'number' || col.type === 'currency') { - columnStyles[index].halign = 'right'; - } else if (col.type === 'text') { + if (col.type === "number" || col.type === "currency") { + columnStyles[index].halign = "right"; + } else if (col.type === "text") { // All text columns aligned left (including Cont) - columnStyles[index].halign = 'left'; + columnStyles[index].halign = "left"; } }); @@ -186,44 +193,49 @@ export const exportToPDF = (data, columns, filename, header) => { styles: { fontSize: 9, cellPadding: 2.5, - valign: 'middle', + valign: "middle", lineColor: [200, 200, 200], lineWidth: 0.1, - overflow: 'linebreak' + overflow: "linebreak", }, headStyles: { fillColor: [41, 128, 185], textColor: 255, - fontStyle: 'bold', - halign: 'center', + fontStyle: "bold", + halign: "center", fontSize: 9, - cellPadding: 2.5 + cellPadding: 2.5, }, alternateRowStyles: { - fillColor: [248, 248, 248] + fillColor: [248, 248, 248], }, columnStyles: columnStyles, - margin: { left: marginLeft, right: marginRight, top: tableStartY, bottom: 15 }, + margin: { + left: marginLeft, + right: marginRight, + top: tableStartY, + bottom: 15, + }, tableWidth: pageWidth - marginLeft - marginRight, // Use full page width - theme: 'grid', - didDrawPage: function(data) { + theme: "grid", + didDrawPage: function (data) { // Add header to each page addHeader(); }, - didParseCell: function(data) { + didParseCell: function (data) { // Force alignment based on column type (body cells only) - if (data.section === 'body') { + if (data.section === "body") { const colIndex = data.column.index; const column = columns[colIndex]; if (column) { - if (column.type === 'number' || column.type === 'currency') { - data.cell.styles.halign = 'right'; - } else if (column.type === 'text') { + if (column.type === "number" || column.type === "currency") { + data.cell.styles.halign = "right"; + } else if (column.type === "text") { if (colIndex === 0) { - data.cell.styles.halign = 'center'; + data.cell.styles.halign = "center"; } else { - data.cell.styles.halign = 'left'; + data.cell.styles.halign = "left"; } } } @@ -247,18 +259,18 @@ export const exportToPDF = (data, columns, filename, header) => { // Draw headers doc.setFontSize(8); - doc.setFont(undefined, 'bold'); + doc.setFont(undefined, "bold"); tableColumns.forEach((header, index) => { - doc.text(header, 14 + (index * 35), yPos); + doc.text(header, 14 + index * 35, yPos); }); // Draw rows - doc.setFont(undefined, 'normal'); + doc.setFont(undefined, "normal"); doc.setFontSize(7); tableRows.forEach((row, rowIndex) => { yPos += 7; row.forEach((cell, cellIndex) => { - doc.text(String(cell), 14 + (cellIndex * 35), yPos); + doc.text(String(cell), 14 + cellIndex * 35, yPos); }); }); @@ -267,11 +279,11 @@ export const exportToPDF = (data, columns, filename, header) => { } // Save PDF - doc.save(`${filename}_${new Date().toISOString().split('T')[0]}.pdf`); + doc.save(`${filename}_${new Date().toISOString().split("T")[0]}.pdf`); return { success: true }; } catch (error) { - console.error('PDF export error details:', error); - return { success: false, error: error.message || 'PDF generation failed' }; + console.error("PDF export error details:", error); + return { success: false, error: error.message || "PDF generation failed" }; } }; @@ -281,31 +293,31 @@ export const exportToPDF = (data, columns, filename, header) => { export const exportGeneralTotals = (summaryData) => { const data = [ { - Tip: 'Clienți', - 'Total Facturat': summaryData?.clienti_total_facturat || 0, - 'Total Încasat': summaryData?.clienti_total_incasat || 0, - 'Sold Net': summaryData?.clienti_sold_total || 0, - 'Sold În Termen': summaryData?.clienti_sold_in_termen || 0, - 'Sold Restant': summaryData?.clienti_sold_restant || 0 + Tip: "Clienți", + "Total Facturat": summaryData?.clienti_total_facturat || 0, + "Total Încasat": summaryData?.clienti_total_incasat || 0, + "Sold Net": summaryData?.clienti_sold_total || 0, + "Sold În Termen": summaryData?.clienti_sold_in_termen || 0, + "Sold Restant": summaryData?.clienti_sold_restant || 0, }, { - Tip: 'Furnizori', - 'Total Facturat': summaryData?.furnizori_total_facturat || 0, - 'Total Achitat': summaryData?.furnizori_total_achitat || 0, - 'Sold Net': summaryData?.furnizori_sold_total || 0, - 'Sold În Termen': summaryData?.furnizori_sold_in_termen || 0, - 'Sold Restant': summaryData?.furnizori_sold_restant || 0 + Tip: "Furnizori", + "Total Facturat": summaryData?.furnizori_total_facturat || 0, + "Total Achitat": summaryData?.furnizori_total_achitat || 0, + "Sold Net": summaryData?.furnizori_sold_total || 0, + "Sold În Termen": summaryData?.furnizori_sold_in_termen || 0, + "Sold Restant": summaryData?.furnizori_sold_restant || 0, }, { - Tip: 'Trezorerie', - 'Total Facturat': '-', - 'Total Încasat/Achitat': '-', - 'Sold Net': summaryData?.trezorerie_sold || 0, - 'Sold În Termen': '-', - 'Sold Restant': '-' - } + Tip: "Trezorerie", + "Total Facturat": "-", + "Total Încasat/Achitat": "-", + "Sold Net": summaryData?.trezorerie_sold || 0, + "Sold În Termen": "-", + "Sold Restant": "-", + }, ]; - + return data; }; @@ -315,27 +327,27 @@ export const exportGeneralTotals = (summaryData) => { export const exportSoldNetBreakdown = (summaryData) => { const data = [ { - Categorie: 'Clienți - Restant', - 'TOTAL': summaryData?.clienti_sold_restant || 0, - '7 zile': summaryData?.clienti_restant_7 || 0, - '14 zile': summaryData?.clienti_restant_14 || 0, - '30 zile': summaryData?.clienti_restant_30 || 0, - '60 zile': summaryData?.clienti_restant_60 || 0, - '90 zile': summaryData?.clienti_restant_90 || 0, - '90+ zile': summaryData?.clienti_restant_over_90 || 0 + Categorie: "Clienți - Restant", + TOTAL: summaryData?.clienti_sold_restant || 0, + "7 zile": summaryData?.clienti_restant_7 || 0, + "14 zile": summaryData?.clienti_restant_14 || 0, + "30 zile": summaryData?.clienti_restant_30 || 0, + "60 zile": summaryData?.clienti_restant_60 || 0, + "90 zile": summaryData?.clienti_restant_90 || 0, + "90+ zile": summaryData?.clienti_restant_over_90 || 0, }, { - Categorie: 'Furnizori - Restant', - 'TOTAL': summaryData?.furnizori_sold_restant || 0, - '7 zile': summaryData?.furnizori_restant_7 || 0, - '14 zile': summaryData?.furnizori_restant_14 || 0, - '30 zile': summaryData?.furnizori_restant_30 || 0, - '60 zile': summaryData?.furnizori_restant_60 || 0, - '90 zile': summaryData?.furnizori_restant_90 || 0, - '90+ zile': summaryData?.furnizori_restant_over_90 || 0 - } + Categorie: "Furnizori - Restant", + TOTAL: summaryData?.furnizori_sold_restant || 0, + "7 zile": summaryData?.furnizori_restant_7 || 0, + "14 zile": summaryData?.furnizori_restant_14 || 0, + "30 zile": summaryData?.furnizori_restant_30 || 0, + "60 zile": summaryData?.furnizori_restant_60 || 0, + "90 zile": summaryData?.furnizori_restant_90 || 0, + "90+ zile": summaryData?.furnizori_restant_over_90 || 0, + }, ]; - + return data; }; @@ -349,14 +361,14 @@ export const exportTrendData = (trendsData, period, chartType) => { const data = trendsData.labels.map((label, index) => { const row = { Perioada: label }; - - trendsData.datasets.forEach(dataset => { + + trendsData.datasets.forEach((dataset) => { const value = dataset.data[index]; row[dataset.label] = value || 0; }); - + return row; }); return data; -}; \ No newline at end of file +}; diff --git a/reports-app/frontend/src/views/BankCashRegisterView.vue b/reports-app/frontend/src/views/BankCashRegisterView.vue index 8a45df2..54ff4ce 100644 --- a/reports-app/frontend/src/views/BankCashRegisterView.vue +++ b/reports-app/frontend/src/views/BankCashRegisterView.vue @@ -26,19 +26,22 @@
Total Încasări
Total Plăți
{{ stats.total_hits }} hits / {{ stats.total_hits + stats.total_misses }} total requests
++ {{ stats.total_hits }} hits / + {{ stats.total_hits + stats.total_misses }} total requests +