# -*- coding: utf-8 -*- from odoo import models, fields, api from odoo.exceptions import ValidationError from datetime import datetime import base64 from io import BytesIO from openpyxl import Workbook from openpyxl.styles import Font, Alignment, Border, Side from openpyxl.utils import get_column_letter class StockMovementReportWizard(models.TransientModel): _name = 'stock_movement_report_wizard' _description = 'Stock Movement Report Wizard' date_from = fields.Date(string='From Date', required=True, default=lambda self: datetime.now().replace(day=1)) date_to = fields.Date(string='To Date', required=True, default=lambda self: datetime.now()) excel_file = fields.Binary(string='Excel Report', readonly=True) file_name = fields.Char(string='File Name', readonly=True) def generate_excel_report(self): """Generate and download the Excel report.""" if self.date_from > self.date_to: raise ValidationError("From Date must be before To Date.") # Prepare data and generate Excel file data = self._prepare_report_data() excel_file = self._generate_excel(data) # Save the file and ensure the record is persisted self.write({ 'excel_file': excel_file, 'file_name': f"Stock_Movement_Report_{self.date_from}_to_{self.date_to}.xlsx" }) # Commit the transaction to ensure the record is saved self.env.cr.commit() # Verify the file was saved if not self.excel_file: raise ValidationError("Failed to generate or save the Excel file.") # Return URL-based download action return { 'type': 'ir.actions.act_url', 'url': f'/web/content?model={self._name}&id={self.id}&field=excel_file&filename_field=file_name&download=true', 'target': 'self', } def _prepare_report_data(self): """Prepare data for full stock movement summary report.""" domain = [('date', '>=', self.date_from), ('date', '<=', self.date_to)] transactions = self.env['sos_material_transaction_history'].search(domain) # Build material data with in/out qty and price material_summary = {} for trans in transactions: mat = trans.ref_id if mat.id not in material_summary: material_summary[mat.id] = { 'part_no': mat.part_no, 'name': mat.name or '', 'uom': mat.uom, 'price': round(mat.unit_price or 0.0, 2), 'opening_bal_qty':mat.opening_bal_qty, 'in_qty': 0.0, 'out_qty': 0.0, } if trans.action == 'in': material_summary[mat.id]['in_qty'] += trans.quantity elif trans.action == 'out': material_summary[mat.id]['out_qty'] += trans.quantity # Include materials with no transactions as well all_materials = self.env['sos_material'].search([]) for mat in all_materials: if mat.id not in material_summary: material_summary[mat.id] = { 'part_no': mat.part_no, 'name': mat.name or '', 'uom': mat.uom, 'price': round(mat.unit_price or 0.0, 2), 'opening_bal_qty':mat.opening_bal_qty, 'in_qty': 0.0, 'out_qty': 0.0, } return { 'date_from': self.date_from, 'date_to': self.date_to, 'summary': list(material_summary.values()) } def _generate_excel(self, data): """Generate Excel file showing all material movements within date range.""" wb = Workbook() ws = wb.active ws.title = "All Material Movement" # Styles header_font = Font(bold=True) header_align = Alignment(horizontal='center', vertical='center') border = Border( left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin'), ) # Title ws['A1'] = f"Material Movement Report ({data['date_from']} to {data['date_to']})" ws.merge_cells('A1:F1') ws['A1'].font = Font(bold=True, size=14) ws['A1'].alignment = header_align # Header Row row = 3 headers = ["Part No", "Name", "UOM", "Price","Opening Balance(Sep 2-2024)", "In Qty", "Out Qty"] for col, header in enumerate(headers, 1): cell = ws.cell(row=row, column=col) cell.value = header cell.font = header_font cell.alignment = header_align cell.border = border row += 1 # Data Rows for stock in data['summary']: ws.cell(row=row, column=1).value = stock['part_no'] ws.cell(row=row, column=2).value = stock['name'] ws.cell(row=row, column=3).value = stock['uom'] ws.cell(row=row, column=4).value = round(stock['price'], 2) ws.cell(row=row, column=5).value = stock['opening_bal_qty'] ws.cell(row=row, column=6).value = stock['in_qty'] ws.cell(row=row, column=7).value = stock['out_qty'] for col in range(1, 8): ws.cell(row=row, column=col).border = border row += 1 # Adjust column widths for col in range(1, 8): max_length = 0 col_letter = get_column_letter(col) for r in ws.iter_rows(min_row=1, max_row=ws.max_row, min_col=col, max_col=col): for cell in r: if cell.value: max_length = max(max_length, len(str(cell.value))) ws.column_dimensions[col_letter].width = max_length + 2 # Save to BytesIO and return as base64 output = BytesIO() wb.save(output) output.seek(0) return base64.b64encode(output.read())