diff options
Diffstat (limited to 'addons/mrp/report/mrp_report_bom_structure.py')
| -rw-r--r-- | addons/mrp/report/mrp_report_bom_structure.py | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/addons/mrp/report/mrp_report_bom_structure.py b/addons/mrp/report/mrp_report_bom_structure.py new file mode 100644 index 00000000..a726e342 --- /dev/null +++ b/addons/mrp/report/mrp_report_bom_structure.py @@ -0,0 +1,261 @@ +# -*- coding: utf-8 -*- + +import json + +from odoo import api, models, _ +from odoo.tools import float_round + +class ReportBomStructure(models.AbstractModel): + _name = 'report.mrp.report_bom_structure' + _description = 'BOM Structure Report' + + @api.model + def _get_report_values(self, docids, data=None): + docs = [] + for bom_id in docids: + bom = self.env['mrp.bom'].browse(bom_id) + variant = data.get('variant') + candidates = variant and self.env['product.product'].browse(variant) or bom.product_id or bom.product_tmpl_id.product_variant_ids + quantity = float(data.get('quantity', 1)) + for product_variant_id in candidates.ids: + if data and data.get('childs'): + doc = self._get_pdf_line(bom_id, product_id=product_variant_id, qty=quantity, child_bom_ids=json.loads(data.get('childs'))) + else: + doc = self._get_pdf_line(bom_id, product_id=product_variant_id, qty=quantity, unfolded=True) + doc['report_type'] = 'pdf' + doc['report_structure'] = data and data.get('report_type') or 'all' + docs.append(doc) + if not candidates: + if data and data.get('childs'): + doc = self._get_pdf_line(bom_id, qty=quantity, child_bom_ids=json.loads(data.get('childs'))) + else: + doc = self._get_pdf_line(bom_id, qty=quantity, unfolded=True) + doc['report_type'] = 'pdf' + doc['report_structure'] = data and data.get('report_type') or 'all' + docs.append(doc) + return { + 'doc_ids': docids, + 'doc_model': 'mrp.bom', + 'docs': docs, + } + + @api.model + def get_html(self, bom_id=False, searchQty=1, searchVariant=False): + res = self._get_report_data(bom_id=bom_id, searchQty=searchQty, searchVariant=searchVariant) + res['lines']['report_type'] = 'html' + res['lines']['report_structure'] = 'all' + res['lines']['has_attachments'] = res['lines']['attachments'] or any(component['attachments'] for component in res['lines']['components']) + res['lines'] = self.env.ref('mrp.report_mrp_bom')._render({'data': res['lines']}) + return res + + @api.model + def get_bom(self, bom_id=False, product_id=False, line_qty=False, line_id=False, level=False): + lines = self._get_bom(bom_id=bom_id, product_id=product_id, line_qty=line_qty, line_id=line_id, level=level) + return self.env.ref('mrp.report_mrp_bom_line')._render({'data': lines}) + + @api.model + def get_operations(self, bom_id=False, qty=0, level=0): + bom = self.env['mrp.bom'].browse(bom_id) + lines = self._get_operation_line(bom, float_round(qty / bom.product_qty, precision_rounding=1, rounding_method='UP'), level) + values = { + 'bom_id': bom_id, + 'currency': self.env.company.currency_id, + 'operations': lines, + } + return self.env.ref('mrp.report_mrp_operation_line')._render({'data': values}) + + @api.model + def _get_report_data(self, bom_id, searchQty=0, searchVariant=False): + lines = {} + bom = self.env['mrp.bom'].browse(bom_id) + bom_quantity = searchQty or bom.product_qty or 1 + bom_product_variants = {} + bom_uom_name = '' + + if bom: + bom_uom_name = bom.product_uom_id.name + + # Get variants used for search + if not bom.product_id: + for variant in bom.product_tmpl_id.product_variant_ids: + bom_product_variants[variant.id] = variant.display_name + + lines = self._get_bom(bom_id, product_id=searchVariant, line_qty=bom_quantity, level=1) + return { + 'lines': lines, + 'variants': bom_product_variants, + 'bom_uom_name': bom_uom_name, + 'bom_qty': bom_quantity, + 'is_variant_applied': self.env.user.user_has_groups('product.group_product_variant') and len(bom_product_variants) > 1, + 'is_uom_applied': self.env.user.user_has_groups('uom.group_uom') + } + + def _get_bom(self, bom_id=False, product_id=False, line_qty=False, line_id=False, level=False): + bom = self.env['mrp.bom'].browse(bom_id) + company = bom.company_id or self.env.company + bom_quantity = line_qty + if line_id: + current_line = self.env['mrp.bom.line'].browse(int(line_id)) + bom_quantity = current_line.product_uom_id._compute_quantity(line_qty, bom.product_uom_id) or 0 + # Display bom components for current selected product variant + if product_id: + product = self.env['product.product'].browse(int(product_id)) + else: + product = bom.product_id or bom.product_tmpl_id.product_variant_id + if product: + price = product.uom_id._compute_price(product.with_company(company).standard_price, bom.product_uom_id) * bom_quantity + attachments = self.env['mrp.document'].search(['|', '&', ('res_model', '=', 'product.product'), + ('res_id', '=', product.id), '&', ('res_model', '=', 'product.template'), ('res_id', '=', product.product_tmpl_id.id)]) + else: + # Use the product template instead of the variant + price = bom.product_tmpl_id.uom_id._compute_price(bom.product_tmpl_id.with_company(company).standard_price, bom.product_uom_id) * bom_quantity + attachments = self.env['mrp.document'].search([('res_model', '=', 'product.template'), ('res_id', '=', bom.product_tmpl_id.id)]) + operations = self._get_operation_line(bom, float_round(bom_quantity / bom.product_qty, precision_rounding=1, rounding_method='UP'), 0) + lines = { + 'bom': bom, + 'bom_qty': bom_quantity, + 'bom_prod_name': product.display_name, + 'currency': company.currency_id, + 'product': product, + 'code': bom and bom.display_name or '', + 'price': price, + 'total': sum([op['total'] for op in operations]), + 'level': level or 0, + 'operations': operations, + 'operations_cost': sum([op['total'] for op in operations]), + 'attachments': attachments, + 'operations_time': sum([op['duration_expected'] for op in operations]) + } + components, total = self._get_bom_lines(bom, bom_quantity, product, line_id, level) + lines['components'] = components + lines['total'] += total + return lines + + def _get_bom_lines(self, bom, bom_quantity, product, line_id, level): + components = [] + total = 0 + for line in bom.bom_line_ids: + line_quantity = (bom_quantity / (bom.product_qty or 1.0)) * line.product_qty + if line._skip_bom_line(product): + continue + company = bom.company_id or self.env.company + price = line.product_id.uom_id._compute_price(line.product_id.with_company(company).standard_price, line.product_uom_id) * line_quantity + if line.child_bom_id: + factor = line.product_uom_id._compute_quantity(line_quantity, line.child_bom_id.product_uom_id) / line.child_bom_id.product_qty + sub_total = self._get_price(line.child_bom_id, factor, line.product_id) + else: + sub_total = price + sub_total = self.env.company.currency_id.round(sub_total) + components.append({ + 'prod_id': line.product_id.id, + 'prod_name': line.product_id.display_name, + 'code': line.child_bom_id and line.child_bom_id.display_name or '', + 'prod_qty': line_quantity, + 'prod_uom': line.product_uom_id.name, + 'prod_cost': company.currency_id.round(price), + 'parent_id': bom.id, + 'line_id': line.id, + 'level': level or 0, + 'total': sub_total, + 'child_bom': line.child_bom_id.id, + 'phantom_bom': line.child_bom_id and line.child_bom_id.type == 'phantom' or False, + 'attachments': self.env['mrp.document'].search(['|', '&', + ('res_model', '=', 'product.product'), ('res_id', '=', line.product_id.id), '&', ('res_model', '=', 'product.template'), ('res_id', '=', line.product_id.product_tmpl_id.id)]), + + }) + total += sub_total + return components, total + + def _get_operation_line(self, bom, qty, level): + operations = [] + total = 0.0 + for operation in bom.operation_ids: + operation_cycle = float_round(qty / operation.workcenter_id.capacity, precision_rounding=1, rounding_method='UP') + duration_expected = operation_cycle * operation.time_cycle + operation.workcenter_id.time_stop + operation.workcenter_id.time_start + total = ((duration_expected / 60.0) * operation.workcenter_id.costs_hour) + operations.append({ + 'level': level or 0, + 'operation': operation, + 'name': operation.name + ' - ' + operation.workcenter_id.name, + 'duration_expected': duration_expected, + 'total': self.env.company.currency_id.round(total), + }) + return operations + + def _get_price(self, bom, factor, product): + price = 0 + if bom.operation_ids: + # routing are defined on a BoM and don't have a concept of quantity. + # It means that the operation time are defined for the quantity on + # the BoM (the user produces a batch of products). E.g the user + # product a batch of 10 units with a 5 minutes operation, the time + # will be the 5 for a quantity between 1-10, then doubled for + # 11-20,... + operation_cycle = float_round(factor, precision_rounding=1, rounding_method='UP') + operations = self._get_operation_line(bom, operation_cycle, 0) + price += sum([op['total'] for op in operations]) + + for line in bom.bom_line_ids: + if line._skip_bom_line(product): + continue + if line.child_bom_id: + qty = line.product_uom_id._compute_quantity(line.product_qty * factor, line.child_bom_id.product_uom_id) / line.child_bom_id.product_qty + sub_price = self._get_price(line.child_bom_id, qty, line.product_id) + price += sub_price + else: + prod_qty = line.product_qty * factor + company = bom.company_id or self.env.company + not_rounded_price = line.product_id.uom_id._compute_price(line.product_id.with_context(force_comany=company.id).standard_price, line.product_uom_id) * prod_qty + price += company.currency_id.round(not_rounded_price) + return price + + def _get_pdf_line(self, bom_id, product_id=False, qty=1, child_bom_ids=[], unfolded=False): + + def get_sub_lines(bom, product_id, line_qty, line_id, level): + data = self._get_bom(bom_id=bom.id, product_id=product_id, line_qty=line_qty, line_id=line_id, level=level) + bom_lines = data['components'] + lines = [] + for bom_line in bom_lines: + lines.append({ + 'name': bom_line['prod_name'], + 'type': 'bom', + 'quantity': bom_line['prod_qty'], + 'uom': bom_line['prod_uom'], + 'prod_cost': bom_line['prod_cost'], + 'bom_cost': bom_line['total'], + 'level': bom_line['level'], + 'code': bom_line['code'], + 'child_bom': bom_line['child_bom'], + 'prod_id': bom_line['prod_id'] + }) + if bom_line['child_bom'] and (unfolded or bom_line['child_bom'] in child_bom_ids): + line = self.env['mrp.bom.line'].browse(bom_line['line_id']) + lines += (get_sub_lines(line.child_bom_id, line.product_id.id, bom_line['prod_qty'], line, level + 1)) + if data['operations']: + lines.append({ + 'name': _('Operations'), + 'type': 'operation', + 'quantity': data['operations_time'], + 'uom': _('minutes'), + 'bom_cost': data['operations_cost'], + 'level': level, + }) + for operation in data['operations']: + if unfolded or 'operation-' + str(bom.id) in child_bom_ids: + lines.append({ + 'name': operation['name'], + 'type': 'operation', + 'quantity': operation['duration_expected'], + 'uom': _('minutes'), + 'bom_cost': operation['total'], + 'level': level + 1, + }) + return lines + + bom = self.env['mrp.bom'].browse(bom_id) + product_id = product_id or bom.product_id.id or bom.product_tmpl_id.product_variant_id.id + data = self._get_bom(bom_id=bom_id, product_id=product_id, line_qty=qty) + pdf_lines = get_sub_lines(bom, product_id, qty, False, 1) + data['components'] = [] + data['lines'] = pdf_lines + return data |
