import io import json import xlsxwriter from odoo import http from odoo.http import request class SaleMarginController(http.Controller): @http.route('/sale_margin/report_excel', type='http', auth='user', methods=['POST'], csrf=False) def report_excel(self, **post): try: # Safely get JSON data from the POST body data = json.loads(request.httprequest.data) or {} col_model = data.get('colModel') grid_data = data.get('gridData') if not col_model or not grid_data: return request.make_response('Missing data', [('Content-Type', 'text/plain')]) # --------------------------------------------- # 1️⃣ Create Excel in memory # --------------------------------------------- output = io.BytesIO() workbook = xlsxwriter.Workbook(output, {'in_memory': True}) worksheet = workbook.add_worksheet('Sale Margin Report') # --------------------------------------------- # 2️⃣ Define Excel formats # --------------------------------------------- title_fmt = workbook.add_format({ 'bold': True, 'font_size': 16, 'font_color': 'white', 'align': 'center', 'valign': 'vcenter', 'bg_color': '#4F81BD', 'border': 1 }) header_fmt = workbook.add_format({ 'bold': True, 'font_size': 11, 'font_color': 'white', 'align': 'center', 'valign': 'vcenter', 'bg_color': '#366092', 'border': 1 }) text_fmt = workbook.add_format({ 'font_size': 10, 'align': 'left', 'valign': 'vcenter', 'border': 1,'text_wrap': False }) float_fmt = workbook.add_format({ 'font_size': 10, 'align': 'right', 'valign': 'vcenter', 'border': 1, 'num_format': '#,##0.00' }) group_fmt = workbook.add_format({ 'bold': True, 'font_size': 11, 'align': 'left', 'bg_color': '#DCE6F1', 'border': 1 }) subtotal_fmt = workbook.add_format({ 'bold': True, 'font_size': 10, 'align': 'right', 'bg_color': '#EAF1DD', 'border': 1, 'num_format': '#,##0.00' }) # --------------------------------------------- # 3️⃣ Title and headers # --------------------------------------------- visible_cols = [c for c in col_model if not c.get('hidden')] visible_cols.pop(-1) last_col = len(visible_cols) - 1 worksheet.merge_range(0, 0, 0, last_col, 'SALE MARGIN REPORT', title_fmt) for col_idx, col in enumerate(visible_cols): worksheet.write(1, col_idx, col.get('title', col.get('dataIndx', '')), header_fmt) worksheet.set_column(col_idx, col_idx, 15) # --------------------------------------------- # 4️⃣ Group data by "tag" # --------------------------------------------- group_field = 'tags' # <-- adjust if your grouping field has a different key name grouped = {} for row in grid_data: group_key = row.get(group_field, 'Ungrouped') grouped.setdefault(group_key, []).append(row) # --------------------------------------------- # 5️⃣ Write grouped data + subtotal per group # --------------------------------------------- row_pos = 2 for group_name, rows in grouped.items(): # Group header worksheet.merge_range(row_pos, 0, row_pos, last_col, f'Group: {group_name}', group_fmt) row_pos += 1 # Track totals per numeric column group_sums = [0.0] * len(visible_cols) # Track margin_percent separately margin_sum = 0 margin_count = 0 margin_idx = None # Find index of margin_percent column for idx, col in enumerate(visible_cols): if col.get('dataIndx') == 'margin_percent': margin_idx = idx break # Write group rows for row_data in rows: for col_idx, col in enumerate(visible_cols): key = col.get('dataIndx') val = row_data.get(key, '') if isinstance(val, (int, float)): if key == 'margin_percent': margin_sum += val margin_count += 1 worksheet.write_number(row_pos, col_idx, val, float_fmt) else: worksheet.write_number(row_pos, col_idx, val, float_fmt) group_sums[col_idx] += val else: worksheet.write(row_pos, col_idx, str(val), text_fmt) row_pos += 1 # Subtotal line worksheet.write(row_pos, 0, f'{group_name} Total', subtotal_fmt) for col_idx, total in enumerate(group_sums): if col_idx > 0 and total != 0: worksheet.write_number(row_pos, col_idx, total, subtotal_fmt) # Write margin_percent average if margin_idx is not None and margin_count > 0: margin_avg = margin_sum / margin_count worksheet.write_number(row_pos, margin_idx, margin_avg, subtotal_fmt) row_pos += 2 # leave a blank line after subtotal # --------------------------------------------- # Finalize and return file # --------------------------------------------- workbook.close() output.seek(0) filecontent = output.read() headers = [ ('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'), ('Content-Disposition', 'attachment; filename="Sale_Margin_Report.xlsx"'), ] return request.make_response(filecontent, headers=headers) except Exception as e: return request.make_response(f"Error: {str(e)}", [('Content-Type', 'text/plain')])