Initial commit: ROA2WEB - FastAPI + Vue.js + Telegram Bot
Modern ERP Reports Application with microservices architecture Tech Stack: - Backend: FastAPI + python-oracledb (Oracle DB integration) - Frontend: Vue.js 3 + PrimeVue + Vite - Telegram Bot: python-telegram-bot + SQLite - Infrastructure: Shared database pool, JWT authentication, SSH tunnel Features: - FastAPI backend with async Oracle connection pool - Vue.js 3 responsive frontend with PrimeVue components - Telegram bot alternative interface - Microservices architecture with shared components - Complete deployment support (Linux Docker + Windows IIS) - Comprehensive testing (Playwright E2E + pytest) Repository Structure: - reports-app/ - Main application (backend, frontend, telegram-bot) - shared/ - Shared components (database pool, auth, utils) - deployment/ - Deployment scripts (Linux & Windows) - docs/ - Project documentation - security/ - Security scanning and git hooks
This commit is contained in:
0
reports-app/frontend/src/utils/__init__.py
Normal file
0
reports-app/frontend/src/utils/__init__.py
Normal file
221
reports-app/frontend/src/utils/exportUtils.js
Normal file
221
reports-app/frontend/src/utils/exportUtils.js
Normal file
@@ -0,0 +1,221 @@
|
||||
import * as XLSX from 'xlsx';
|
||||
import jsPDF from 'jspdf';
|
||||
import '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',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
}).format(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Export data to Excel
|
||||
* @param {Array} data - Array of objects to export
|
||||
* @param {String} filename - Name of the file (without extension)
|
||||
* @param {String} sheetName - Name of the Excel sheet
|
||||
*/
|
||||
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`);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Excel export failed:', error);
|
||||
return { success: false, error };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Export data to PDF
|
||||
* @param {Array} data - Array of objects to export
|
||||
* @param {Array} columns - Column definitions [{field: 'key', header: 'Display Name', type: 'currency'}]
|
||||
* @param {String} filename - Name of the file (without extension)
|
||||
* @param {String} title - Title for the PDF document
|
||||
*/
|
||||
export const exportToPDF = (data, columns, filename, title) => {
|
||||
try {
|
||||
// Check if data exists
|
||||
if (!data || data.length === 0) {
|
||||
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' };
|
||||
}
|
||||
|
||||
const doc = new jsPDF('landscape', 'mm', 'a4');
|
||||
|
||||
// Add title
|
||||
doc.setFontSize(16);
|
||||
doc.text(title, 14, 15);
|
||||
|
||||
// Add generation date
|
||||
doc.setFontSize(10);
|
||||
doc.text(`Generat: ${new Date().toLocaleString('ro-RO')}`, 14, 25);
|
||||
|
||||
// Prepare table data
|
||||
const tableColumns = columns.map(col => col.header);
|
||||
const tableRows = data.map(row =>
|
||||
columns.map(col => {
|
||||
const value = row[col.field];
|
||||
if (col.type === 'currency') {
|
||||
return formatCurrency(value);
|
||||
}
|
||||
return value || '-';
|
||||
})
|
||||
);
|
||||
|
||||
// Check if autoTable is available
|
||||
if (typeof doc.autoTable === 'function') {
|
||||
// Add table using autoTable
|
||||
doc.autoTable({
|
||||
head: [tableColumns],
|
||||
body: tableRows,
|
||||
startY: 30,
|
||||
styles: {
|
||||
fontSize: 9,
|
||||
cellPadding: 2,
|
||||
halign: 'center'
|
||||
},
|
||||
headStyles: {
|
||||
fillColor: [102, 126, 234],
|
||||
textColor: 255,
|
||||
fontStyle: 'bold'
|
||||
},
|
||||
alternateRowStyles: { fillColor: [245, 245, 245] },
|
||||
columnStyles: {
|
||||
// Right align currency columns
|
||||
...Object.fromEntries(
|
||||
columns.map((col, index) =>
|
||||
col.type === 'currency' ? [index, { halign: 'right' }] : null
|
||||
).filter(Boolean)
|
||||
)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Fallback: manual table creation
|
||||
let yPos = 40;
|
||||
|
||||
// Draw headers
|
||||
doc.setFontSize(10);
|
||||
doc.setFont(undefined, 'bold');
|
||||
tableColumns.forEach((header, index) => {
|
||||
doc.text(header, 14 + (index * 40), yPos);
|
||||
});
|
||||
|
||||
// Draw rows
|
||||
doc.setFont(undefined, 'normal');
|
||||
tableRows.forEach((row, rowIndex) => {
|
||||
yPos += 10;
|
||||
row.forEach((cell, cellIndex) => {
|
||||
doc.text(String(cell), 14 + (cellIndex * 40), yPos);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Save 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' };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Export General Totals table
|
||||
*/
|
||||
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: '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': '-'
|
||||
}
|
||||
];
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Export Sold Net Breakdown table
|
||||
*/
|
||||
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: '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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Export Trend Data
|
||||
*/
|
||||
export const exportTrendData = (trendsData, period, chartType) => {
|
||||
if (!trendsData || !trendsData.labels || !trendsData.datasets) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data = trendsData.labels.map((label, index) => {
|
||||
const row = { Perioada: label };
|
||||
|
||||
trendsData.datasets.forEach(dataset => {
|
||||
const value = dataset.data[index];
|
||||
row[dataset.label] = value || 0;
|
||||
});
|
||||
|
||||
return row;
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
0
reports-app/frontend/src/utils/index.js
Normal file
0
reports-app/frontend/src/utils/index.js
Normal file
Reference in New Issue
Block a user