# -*- coding: utf-8 -*- from odoo import models, fields, api from odoo.exceptions import ValidationError from math import isnan import io import xlsxwriter from odoo.tools.misc import xlsxwriter import base64 class SOS_Sfg_Bom(models.Model): _name = 'sos_sfg_bom' _description = 'BOM of Semi-Finished Goods' name = fields.Many2one('sos_sfg', string="BOM Name") fg_name = fields.Many2many('sos_fg', string="Belongs To") prepared_by_name = fields.Many2one('res.users', string='Prepared by') reporting_to = fields.Many2one('res.users',related="prepared_by_name.reporting_to", string='Prepared by') bom_version=fields.Char(string="BOM Version") pcb_version=fields.Char(string="PCB Version") assembling_charges = fields.Monetary(related="name.assembling_charges",string="Assembling Charges") sfg_bom_line_ids = fields.One2many('sos_sfg_bom_line', 'bom_id', string='BOM Lines', copy=True, ondelete='cascade') currency_id = fields.Many2one('res.currency', string='Currency') overall_total = fields.Monetary(store=True,compute='_compute_overall_total', string="Overall Total", currency_field='currency_id') material_bom_line_ids = fields.Many2many( 'sos_material_bom', # Target model 'sos_sfg_bom_sos_material_bom_rel', # Relation table 'sos_sfg_bom_id', # Column for "sos_sfg_bom" 'sos_material_bom_id', # Column for "sos_material_bom" string='Material BOM Lines', copy=True ) def action_export(self): output = io.BytesIO() workbook = xlsxwriter.Workbook(output, {'in_memory': True}) headers = { 'material_code' : 'Material Code', 'primary_component_id': 'Material Name', 'quantity': 'Quantity', 'inhand_stock_qty':'Inhand Qty', 'uom':'UOM', 'unit_price':'Unit Price', 'total_cost':'Total' } self._write_sheet(workbook, 'Materials', self.sfg_bom_line_ids, headers) workbook.close() output.seek(0) attachment = self.env['ir.attachment'].create({ 'name': f'{self.name.name}_Export.xlsx', 'type': 'binary', 'datas': base64.b64encode(output.read()), 'res_model': self._name, 'res_id': self.id, 'mimetype': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }) return { 'type': 'ir.actions.act_url', 'url': f'/web/content/{attachment.id}?download=true', 'target': 'self', } def _write_sheet(self, workbook, sheet_name, line_records, headers): worksheet = workbook.add_worksheet(sheet_name) number_format = workbook.add_format({'num_format': '#,##0.00'}) bold_format = workbook.add_format({'bold': True}) name_value = getattr(self, 'name', False) worksheet.write(0, 0, 'Name',bold_format) # Label for the first value worksheet.write(0, 1, name_value.display_name if name_value else '') # Handle Many2one field assembling_charges = getattr(self, 'assembling_charges', 0.0) worksheet.write(1, 0, 'Assembly Charges',bold_format) # Label for the second value worksheet.write(1, 1, assembling_charges, number_format) # Assume numeric (float) overall_total = getattr(self, 'overall_total', 0.0) worksheet.write(2, 0, 'Overall Cost (Including assembling charges)',bold_format) # Label for the third value worksheet.write(2, 1, overall_total, number_format) # Assume numeric (float) for col, header in enumerate(headers.values()): worksheet.write(4, col, header,bold_format) for row, record in enumerate(line_records, start=5): for col, field in enumerate(headers.keys()): value = getattr(record, field, '') if isinstance(value, models.Model): worksheet.write(row, col, value.display_name if value else '') elif isinstance(value, (fields.Date, fields.Datetime)): worksheet.write(row, col, value.strftime('%Y-%m-%d') if value else '') else: worksheet.write(row, col, value) @api.depends('sfg_bom_line_ids.total_cost', 'assembling_charges') def _compute_overall_total(self): for record in self: total_costs = sum( 0 if line.total_cost is None or isnan(line.total_cost) else line.total_cost for line in record.sfg_bom_line_ids ) assembling_charges = 0 if isnan(record.assembling_charges) else record.assembling_charges record.overall_total = total_costs + assembling_charges def write(self, vals): res = super(SOS_Sfg_Bom, self).write(vals) self._sync_unit_price_with_sfg() return res @api.model def create(self, vals): res = super(SOS_Sfg_Bom, self).create(vals) res._sync_unit_price_with_sfg() return res def _sync_unit_price_with_sfg(self): sos_sfg_obj = self.env['sos_sfg'] for record in self: sfg_record = sos_sfg_obj.search([('name', '=', record.name.name)], limit=1) if sfg_record: sfg_record.write({'unit_price': record.overall_total}) def action_bulk_upload(self): view_id = self.env.ref('sos_inventory.bulk_upload_wizard_form').id return { 'type': 'ir.actions.act_window', 'name': 'Bulk Upload', 'res_model': 'bulk_upload_wizard', 'view_mode': 'form', 'views': [(view_id, 'form')], 'target': 'new', 'context': { 'default_sos_sfg_id': self.id } } class SOS_Sfg_Bom_Line(models.Model): _name = 'sos_sfg_bom_line' _description = 'BOM Lines of Semi-Finished Goods' bom_id = fields.Many2one('sos_sfg_bom', string="SFG BOM Reference") fg_bom_id = fields.Many2one('sos_fg_bom', string="FG BOM Reference") primary_component_id = fields.Many2one('sos_material', string='Part No', required=True) location = fields.Char(related="primary_component_id.location") inhand_stock_qty = fields.Float(related="primary_component_id.inhand_stock_qty") material_code = fields.Char(related="primary_component_id.material_code") quantity = fields.Float(string="Qty", default=1) inspection_method = fields.Integer(related="primary_component_id.inspection_method",string='Inspection Method',readonly=False) currency_id = fields.Many2one('res.currency', string='Currency') unit_price = fields.Monetary(related='primary_component_id.unit_price', string="Unit Price", currency_field='currency_id', readonly=True) uom = fields.Selection( related='primary_component_id.uom', string="UOM" ) total_cost = fields.Monetary(compute='_compute_total_cost', string="Total Cost", currency_field='currency_id', readonly=True) @api.depends('unit_price', 'quantity') def _compute_total_cost(self): for record in self: record.total_cost = record.unit_price * record.quantity @api.onchange('inspection_method') def _onchange_inspection_method(self): if self.inspection_method: self.primary_component_id.inspection_method = self.inspection_method # @api.model # def create(self, vals): # existing_record = self.search([ # ('primary_component_id', '=', vals.get('primary_component_id')), # ('fg_bom_id', '=', vals.get('fg_bom_id')) # ], limit=1) # if existing_record: # existing_record.quantity += vals.get('quantity', 0) # return existing_record # else: # return super(SOS_Sfg_Bom_Line, self).create(vals)