You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

185 lines
9.5 KiB

# -*- coding: utf-8 -*-
##############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Aysha Shalin (odoo@cybrosys.info)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import io
import json
import xlsxwriter
from odoo import models
from odoo.tools import date_utils
from odoo.tools import json_default
class AccountMove(models.Model):
""" Inherits the mrp.bom model """
_inherit = 'mrp.bom'
def action_print_bom_structure(self):
""" Generate and export the BOM Structure Report in Excel format """
bom = self.env['mrp.bom'].browse(self.id)
candidates = bom.product_id or bom.product_tmpl_id.product_variant_ids
quantity = bom.product_qty
for product_variant_id in candidates.ids:
doc = self.env['report.mrp.report_bom_structure']._get_pdf_line(
bom.id, product_id=product_variant_id,
qty=quantity, unfolded=True)
return {
'type': 'ir.actions.report',
'data': {'model': 'mrp.bom',
'options': json.dumps(doc,
default=json_default),
'output_format': 'xlsx',
'report_name': 'BoM Structure',
},
'report_type': 'xlsx',
}
def get_xlsx_report(self, data, response):
""" Generate an Excel report with BOM structure and cost """
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
sheet = workbook.add_worksheet()
# Define cell formats
head = workbook.add_format(
{'align': 'left', 'bold': True, 'font_size': '20px'})
format3 = workbook.add_format({'font_size': '15px', 'bold': True})
format4 = workbook.add_format({'font_size': 10})
format6 = workbook.add_format({'font_size': 10, 'bold': True})
format7 = workbook.add_format({'font_size': 10, 'font_color': 'green'})
format8 = workbook.add_format({'font_size': 10, 'font_color': 'red'})
format9 = workbook.add_format({'font_size': 10, 'font_color': 'yellow'})
# Check if there are components or lines in the data
if data and data.get('components') or data.get('lines'):
# Merge and format header cells
sheet.merge_range('A1:F2', 'BoM Structure & Cost', head)
sheet.merge_range('A4:Z5', data['name'], format3)
# Merge and format cells for product information
sheet.merge_range('A7:C7', 'Products', format6)
sheet.merge_range('D7:E7', 'Quantity', format6)
sheet.merge_range('F7:G7', 'Unit of Measure', format6)
sheet.merge_range('H7:I7', 'Ready to Produce', format6)
sheet.merge_range('J7:K7', 'Free to Use / On Hand', format6)
sheet.merge_range('L7:M7', 'Availability', format6)
sheet.merge_range('N7:O7', 'Lead Time', format6)
sheet.merge_range('P7:Q7', 'Route', format6)
sheet.merge_range('R7:S7', 'Product Cost', format6)
sheet.merge_range('T7:U7', 'BOM Cost', format6)
row_start = 9 # Starting row for data
currency_symbol = self.env.user.company_id.currency_id.symbol
# Iterate through lines in the data
for index, value in enumerate(data.get('lines')):
# Calculate leading spaces based on the level
if value['level'] != 0:
space_td = ' ' * value['level']
else:
space_td = ' '
# Merge and format cells for product name
sheet.merge_range('A8:C8', data['name'], format6)
sheet.merge_range('D8:E8', data['quantity'], format4)
sheet.merge_range(f'A{index + row_start}:C{index + row_start}',
space_td + value['name'], format4)
# Merge and format cells for quantity
sheet.merge_range(f'D{index + row_start}:E{index + row_start}',
value['quantity'], format4)
# Merge and format cells for unit of measure
if 'uom' in value:
sheet.merge_range(
f'F{index + row_start}:G{index + row_start}',
value['uom'], format4)
# Merge and format cells for 'Ready to Produce'
if 'producible_qty' in value:
sheet.merge_range('H8:I8', data['producible_qty'], format4)
sheet.merge_range(
f'H{index + row_start}:I{index + row_start}',
value['producible_qty'], format4)
# Merge and format cells for 'Quantity Available / On Hand'
if 'quantity_available' in value:
quantity_available_on_hand = \
f"{value['quantity_available']} / {value['quantity_on_hand']}"
sheet.merge_range(
'J8:K8', f"{data['quantity_available']} / "
f"{data['quantity_on_hand']}", format4)
sheet.merge_range(
f'J{index + row_start}:K{index + row_start}',
quantity_available_on_hand, format4)
# Merge and format cells for 'Availability'
if 'availability_display' in value:
availability_main_text = data['availability_display']
availability_text = value['availability_display']
color_format_main = format7 if (
availability_main_text == 'Available') \
else (
format8 if availability_main_text == 'Not Available'
else format9)
color_format = format7 if availability_text == 'Available' \
else (format8 if availability_text == 'Not Available'
else format9)
sheet.merge_range(
'L8:M8', availability_main_text, color_format_main)
sheet.merge_range(
f'L{index + row_start}:M{index + row_start}',
availability_text, color_format)
# Merge and format cells for 'Product Cost'
if 'prod_cost' in value:
prod_cost_with_symbol = f"{currency_symbol} {data['prod_cost']} "
sheet.merge_range(
'R8:S8', prod_cost_with_symbol, format4)
sheet.merge_range(
f'R{index + row_start}:S{index + row_start}',
f"{currency_symbol} {value['prod_cost']}", format4)
# Merge and format cells for 'BOM Cost'
if 'bom_cost' in value:
bom_cost_with_symbol = f" {currency_symbol} {data['bom_cost']}"
sheet.merge_range(
'T8:U8', bom_cost_with_symbol, format4)
sheet.merge_range(
f'T{index + row_start}:U{index + row_start}',
f" {currency_symbol} {value['bom_cost']}", format4)
# Merge and format cells for 'Route Info'
if 'route_name' in value:
route_info = f"{value['route_name']} {value['route_detail']}"
sheet.merge_range(
f'P{index + row_start}:Q{index + row_start}',
route_info, format4)
# Merge and format cells for 'Lead Time'
if 'lead_time' in value:
lead_time = value['lead_time']
lead_time_days = f"{int(lead_time)} days" if lead_time != 0.0 else "0 days"
sheet.merge_range(
f'N{index + row_start}:O{index + row_start}',
lead_time_days, format4)
# Check if 'prod_cost' is present in the data dictionary
if 'prod_cost' in data:
prod_cost_with_symbol = f"{currency_symbol} {data['prod_cost']}"
bom_cost_with_symbol = f" {currency_symbol} {data['bom_cost']}"
sheet.merge_range(
f'D{index + row_start + 1}:E{index + row_start + 1}',
'UnitCost', format6)
sheet.merge_range(
f'T{index + row_start + 1}:U{index + row_start + 1}',
bom_cost_with_symbol, format4)
sheet.merge_range(
f'R{index + row_start + 1}:S{index + row_start + 1}',
prod_cost_with_symbol, format4)
# Close the workbook, seek to the beginning, and stream the output
workbook.close()
output.seek(0)
response.stream.write(output.read())
output.close()