146 lines
6.4 KiB
Python
146 lines
6.4 KiB
Python
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')]) |