From a100d99f1dd097c070a3c965c4f3e7a3bcff4fc4 Mon Sep 17 00:00:00 2001 From: Raman Marikanti Date: Thu, 12 Feb 2026 16:39:43 +0530 Subject: [PATCH] excel fix --- .../static/src/js/dashboard.js | 557 +++++++++++++----- 1 file changed, 397 insertions(+), 160 deletions(-) diff --git a/custom_addons/customer_orders/static/src/js/dashboard.js b/custom_addons/customer_orders/static/src/js/dashboard.js index 22cab8c66..85b4a0425 100644 --- a/custom_addons/customer_orders/static/src/js/dashboard.js +++ b/custom_addons/customer_orders/static/src/js/dashboard.js @@ -509,6 +509,7 @@ export class CustomerOrderStatusGrid extends Component { const dayStr = `${day.toString().padStart(2, '0')}/${month}/${year}`; const dayKey = `day_${day}`; + dayColumns.push({ title: dayStr, width: 90, @@ -540,6 +541,7 @@ export class CustomerOrderStatusGrid extends Component { const orderQty = pendingOrder + currentOrder; const fgAvailable = parseFloat(row.fg_qty) || 0; const remainingProduction = Math.max(0, orderQty - fgAvailable); + const remaining_dispatch_qty = Math.max(0, orderQty - parseFloat(row.dispatch_qty) ) // Process date-wise production data const dailyProduction = {}; @@ -584,7 +586,9 @@ export class CustomerOrderStatusGrid extends Component { dispatch_qty: parseFloat(row.dispatch_qty) || 0, dispatched_qty_kg: parseFloat(row.dispatched_qty_kg) || 0, remaining_production: remainingProduction, + remaining_dispatch_qty: remaining_dispatch_qty, date_wise: row.date_wise, + produced_qty: row.produced_qty, ...dailyProduction }; }); @@ -1451,9 +1455,9 @@ export class CustomerOrderStatusGrid extends Component { style: { backgroundColor: '#d4edda', textAlign: 'right' } }, { - title: "Dispatch Qty", + title: "Produced Qty", width: 90, - dataIndx: "dispatch_qty", + dataIndx: "produced_qty", dataType: "float", align: "right", render: (ui) => this.formatInteger(ui.cellData), @@ -1484,6 +1488,36 @@ export class CustomerOrderStatusGrid extends Component { textAlign: 'right' }; } + }, + { + title: "Dispatch Qty", + width: 90, + dataIndx: "dispatch_qty", + dataType: "float", + align: "right", + render: (ui) => this.formatInteger(ui.cellData), + summary: { + type: "sum", + label: "Total:", + render: (ui) => this.formatInteger(ui.data) + }, + style: { backgroundColor: '#e3f2fd', textAlign: 'right' } + }, + { + title: "Remaining Dispatch", + width: 120, + dataIndx: "remaining_dispatch_qty", + dataType: "float", + filter: true, + sortable: true, + align: "right", + render: (ui) => this.formatInteger(ui.cellData), + summary: { + type: "sum", + label: "Total:", + render: (ui) => this.formatInteger(ui.data) + }, + style: { textAlign: 'right', backgroundColor: '#fff3cd' } } ]; @@ -1559,14 +1593,6 @@ export class CustomerOrderStatusGrid extends Component { // EXPORT METHODS // ==================== -// Replace your exportGrid method with this: -// ==================== -// EXPORT METHODS - FIXED FOR DATA EXPORT -// ==================== - -// ==================== -// EXPORT METHODS - WITH MERGED CELLS FOR GROUP HEADERS -// ==================== exportGrid(ev, type) { ev.preventDefault(); @@ -1582,25 +1608,25 @@ exportGrid(ev, type) { gridData = this.state.gridData; columns = this.getOrderGridColumns(); sheetName = 'Customer Order Status'; - filename = `Customer_Order_Status_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xlsx`; + filename = `Customer_Order_Status_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xls`; break; case 'dispatch': gridData = this.state.dispatchData; columns = this.getDispatchGridColumns(); sheetName = 'Dispatch Summary'; - filename = `Dispatch_Summary_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xlsx`; + filename = `Dispatch_Summary_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xls`; break; case 'rm': gridData = this.state.rmData; columns = this.getRMGridColumns(); sheetName = 'Raw Material Availability'; - filename = `RM_Availability_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xlsx`; + filename = `RM_Availability_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xls`; break; case 'dpr': gridData = this.state.dprData; columns = this.getDPRGridColumns(); sheetName = 'Daily Production Report'; - filename = `DPR_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xlsx`; + filename = `DPR_${this.getMonthYearLabel().replace(/ /g, '_')}_${new Date().toISOString().split('T')[0]}.xls`; break; default: return; @@ -1618,7 +1644,7 @@ exportGrid(ev, type) { // Generate HTML table with merged headers const table = this.generateMergedHeaderTable(gridData, columns, sheetName); - // Convert to Excel + // Convert to Excel (MS Excel compatible) this.exportToExcel(table, filename, sheetName); } catch (error) { @@ -1631,135 +1657,27 @@ exportGrid(ev, type) { } // Generate HTML table with merged column headers -generateMergedHeaderTable(data, columns, sheetName) { - // First, analyze column structure to determine merge ranges - const headerRows = this.buildHeaderRows(columns); - const flatColumns = this.flattenColumns(columns); +// Format float for Excel +formatFloatForExcel(value) { + if (value === null || value === undefined || isNaN(value)) return '0.00'; + return parseFloat(value).toFixed(2); +} - let html = ''; +// Format integer for Excel +formatIntegerForExcel(value) { + if (value === null || value === undefined || isNaN(value)) return '0'; + return parseInt(value).toString(); +} - // ==================== HEADER SECTION WITH MERGED CELLS ==================== - html += ''; - - // Generate multi-row header with merged cells - for (let rowIdx = 0; rowIdx < headerRows.length; rowIdx++) { - html += ''; - const row = headerRows[rowIdx]; - - for (let colIdx = 0; colIdx < row.length; colIdx++) { - const cell = row[colIdx]; - if (cell) { - // Skip if this cell was already merged (null in the array) - if (cell.colspan === 0) continue; - - let colspan = cell.colspan || 1; - let rowspan = cell.rowspan || 1; - - html += ``; - } - } - html += ''; - } - html += ''; - - // ==================== BODY SECTION ==================== - html += ''; - - // Data rows - data.forEach((row, index) => { - // Add zebra striping - const bgColor = index % 2 === 0 ? '#FFFFFF' : '#F8F9FA'; - html += ''; - - flatColumns.forEach(col => { - let value = row[col.dataIndx]; - let displayValue = ''; - - // Format value based on data type - if (value === null || value === undefined || value === '' || value === 0) { - displayValue = ''; - } else if (col.dataType === 'float') { - displayValue = parseFloat(value).toLocaleString('en-US', { - minimumFractionDigits: 2, - maximumFractionDigits: 2 - }); - } else if (col.dataType === 'integer') { - displayValue = parseInt(value).toLocaleString('en-US'); - } else { - displayValue = value.toString(); - } - - const align = col.dataType === 'float' || col.dataType === 'integer' ? 'right' : 'left'; - html += ``; - }); - html += ''; - }); - - // ==================== SUMMARY SECTION WITH MERGED CELL ==================== - html += ''; - - // Calculate totals for numeric columns - const totals = {}; - flatColumns.forEach(col => { - if (col.dataType === 'float' || col.dataType === 'integer') { - totals[col.dataIndx] = 0; - data.forEach(row => { - totals[col.dataIndx] += parseFloat(row[col.dataIndx]) || 0; - }); - } - }); - - // Grand Total row with merged first cell - html += ''; - html += ``; - - // Add totals for remaining columns - for (let i = 1; i < flatColumns.length; i++) { - const col = flatColumns[i]; - if (col.dataType === 'float' || col.dataType === 'integer') { - let total = totals[col.dataIndx] || 0; - let formattedTotal = col.dataType === 'float' - ? total.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) - : total.toLocaleString('en-US'); - html += ``; - } else { - html += ``; - } - } - html += ''; - - html += ''; - html += '
${cell.title}
${displayValue}
GRAND TOTAL${formattedTotal}
'; - - return html; +// Escape HTML special characters +escapeHtml(text) { + if (!text) return ''; + return String(text) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); } // Build multi-row header structure with merge information @@ -1854,7 +1772,7 @@ flattenColumns(columns) { flatColumns.push({ dataIndx: col.dataIndx, title: parentTitle ? `${parentTitle} - ${col.title}` : col.title, - width: col.dataIndx === 'product'? 600 : (col.width || 100), + width: col.dataIndx === 'product' ? 500 : (col.width || 100), align: col.align || 'left', dataType: col.dataType || 'string' }); @@ -1866,41 +1784,177 @@ flattenColumns(columns) { return flatColumns; } -// Export to Excel +// Export to Excel (MS Excel compatible) - Gray and White Theme, Arial font, Frozen Header, Fit to Text exportToExcel(htmlTable, filename, sheetName) { + // Calculate header rows depth from the table + const headerDepth = (htmlTable.match(/[\s\S]*?<\/thead>/) || [''])[0]; + const headerRowCount = (headerDepth.match(//g) || []).length; + + // MS Excel specific XML format const excelContent = ` - + + + + + ${this.escapeHtml(sheetName)} + + + 90 + + + + ${6} + ${headerRowCount} + False + False + False + + + + 9000 + 13860 + 0 + 0 + False + False + + -

${sheetName}

-

- ${this.getMonthYearLabel()} | Exported on: ${new Date().toLocaleString()} -

- ${htmlTable} -

- * All quantities are in bags unless specified otherwise -

+ + + + + + + + + + +
+

${this.escapeHtml(sheetName)}

+

+ ${this.escapeHtml(this.getMonthYearLabel())} | Exported on: ${new Date().toLocaleString()} +

+
+ ${htmlTable} +
`; + // Create blob with proper MIME type for MS Excel const blob = new Blob([excelContent], { - type: 'application/vnd.ms-excel' + type: 'application/vnd.ms-excel; charset=UTF-8' }); + // Download file const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; @@ -1916,7 +1970,191 @@ exportToExcel(htmlTable, filename, sheetName) { }); } -// Helper methods for export buttons +// Generate HTML table with merged column headers - Arial font, Auto width, Frozen Header +generateMergedHeaderTable(data, columns, sheetName) { + // First, analyze column structure to determine merge ranges + const headerRows = this.buildHeaderRows(columns); + const flatColumns = this.flattenColumns(columns); + + let html = ''; + + // ==================== COLUMN WIDTHS - AUTO FIT ==================== + html += ''; + flatColumns.forEach(col => { + // Don't set fixed widths - let Excel auto-fit + html += ``; + }); + html += ''; + + // ==================== HEADER SECTION WITH MERGED CELLS ==================== + html += ''; + + // Generate multi-row header with merged cells + for (let rowIdx = 0; rowIdx < headerRows.length; rowIdx++) { + html += ''; + const row = headerRows[rowIdx]; + const headerClass = rowIdx === 0 ? 'header-main' : 'header-sub'; + + for (let colIdx = 0; colIdx < row.length; colIdx++) { + const cell = row[colIdx]; + if (cell) { + // Skip if this cell was already merged + if (cell.colspan === 0) continue; + + let colspan = cell.colspan || 1; + let rowspan = cell.rowspan || 1; + + html += ``; + } + } + html += ''; + } + html += ''; + + // ==================== BODY SECTION ==================== + html += ''; + + // Data rows + data.forEach((row, index) => { + // Add zebra striping - subtle gray + const bgColor = index % 2 === 0 ? '#FFFFFF' : '#F8F8F8'; + const rowClass = index % 2 === 0 ? 'row-even' : 'row-odd'; + + html += ``; + + flatColumns.forEach(col => { + let value = row[col.dataIndx]; + let displayValue = ''; + + // Format value based on data type + if (value === null || value === undefined || value === '' || value === 0) { + displayValue = ''; + } else if (col.dataType === 'float') { + displayValue = this.formatFloatForExcel(value); + } else if (col.dataType === 'integer') { + displayValue = this.formatIntegerForExcel(value); + } else { + displayValue = this.escapeHtml(value.toString()); + } + + const align = col.dataType === 'float' || col.dataType === 'integer' ? 'right' : 'left'; + const isProduct = col.dataIndx === 'product' || col.title?.toLowerCase().includes('product'); + + // Determine if this is a date column (format: YYYY-MM-DD or day_*) + const isDateColumn = col.dataIndx?.match(/^\d{4}-\d{2}-\d{2}$/) || col.dataIndx?.startsWith('day_'); + + let cellStyle = `text-align: ${align}; + padding: 6px 10px; + border: 1px solid #CCCCCC; + background-color: ${bgColor}; + font-family: Calibri; + font-size: 10pt; + color: #333333;`; + + // Product columns - wrap text, auto width + if (isProduct) { + cellStyle += ` white-space: normal; + min-width: 300px; + max-width: 500px; + word-wrap: break-word;`; + } + // Date columns - fixed width, center aligned + else if (isDateColumn) { + cellStyle += ` white-space: nowrap; + text-align: center; + min-width: 80px;`; + } + // Numeric columns - right aligned, monospace numbers + else if (col.dataType === 'float' || col.dataType === 'integer') { + cellStyle += ` white-space: nowrap; + mso-number-format:\\#\\,\\#\\#0\\.00;`; + } + // Other text columns - nowrap by default + else { + cellStyle += ` white-space: nowrap;`; + } + + html += ``; + }); + html += ''; + }); + + // ==================== SUMMARY SECTION WITH MERGED CELL ==================== + html += ''; + + // Calculate totals for numeric columns + const totals = {}; + flatColumns.forEach(col => { + if (col.dataType === 'float' || col.dataType === 'integer') { + totals[col.dataIndx] = 0; + data.forEach(row => { + totals[col.dataIndx] += parseFloat(row[col.dataIndx]) || 0; + }); + } + }); + + // Grand Total row with merged first cell + html += ''; + html += ``; + + // Add totals for remaining columns + for (let i = 1; i < flatColumns.length; i++) { + const col = flatColumns[i]; + if (col.dataType === 'float' || col.dataType === 'integer') { + let total = totals[col.dataIndx] || 0; + let formattedTotal = col.dataType === 'float' + ? this.formatFloatForExcel(total) + : this.formatIntegerForExcel(total); + html += ``; + } else { + html += ``; + } + } + html += ''; + + html += ''; + html += '
${this.escapeHtml(cell.title || '')}
${displayValue}
GRAND TOTAL${formattedTotal}
'; + + return html; +} + +// Flatten nested columns - with auto width + exportOrderGrid(ev) { this.exportGrid(ev, 'order'); } @@ -1932,7 +2170,6 @@ exportRMGrid(ev) { exportDPRGrid(ev) { this.exportGrid(ev, 'dpr'); } - // ==================== // TAB EVENT HANDLER // ====================