Slink/sos_inventory/wizard/stock_movement_report_wizar...

158 lines
5.9 KiB
Python
Executable File

# -*- 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())