stock_orderpoint.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. from odoo import _, api, fields, models
  4. from odoo.tools.float_utils import float_is_zero
  5. from odoo.osv.expression import AND
  6. from dateutil.relativedelta import relativedelta
  7. class StockWarehouseOrderpoint(models.Model):
  8. _inherit = 'stock.warehouse.orderpoint'
  9. show_bom = fields.Boolean('Show BoM column', compute='_compute_show_bom')
  10. bom_id = fields.Many2one(
  11. 'mrp.bom', string='Bill of Materials', check_company=True,
  12. domain="[('type', '=', 'normal'), '&', '|', ('company_id', '=', company_id), ('company_id', '=', False), '|', ('product_id', '=', product_id), '&', ('product_id', '=', False), ('product_tmpl_id', '=', product_tmpl_id)]")
  13. manufacturing_visibility_days = fields.Float(default=0.0, help="Visibility Days applied on the manufacturing routes.")
  14. def _get_replenishment_order_notification(self):
  15. self.ensure_one()
  16. domain = [('orderpoint_id', 'in', self.ids)]
  17. if self.env.context.get('written_after'):
  18. domain = AND([domain, [('write_date', '>', self.env.context.get('written_after'))]])
  19. production = self.env['mrp.production'].search(domain, limit=1)
  20. if production:
  21. action = self.env.ref('mrp.action_mrp_production_form')
  22. return {
  23. 'type': 'ir.actions.client',
  24. 'tag': 'display_notification',
  25. 'params': {
  26. 'title': _('The following replenishment order has been generated'),
  27. 'message': '%s',
  28. 'links': [{
  29. 'label': production.name,
  30. 'url': f'#action={action.id}&id={production.id}&model=mrp.production'
  31. }],
  32. 'sticky': False,
  33. }
  34. }
  35. return super()._get_replenishment_order_notification()
  36. @api.depends('route_id')
  37. def _compute_show_bom(self):
  38. manufacture_route = []
  39. for res in self.env['stock.rule'].search_read([('action', '=', 'manufacture')], ['route_id']):
  40. manufacture_route.append(res['route_id'][0])
  41. for orderpoint in self:
  42. orderpoint.show_bom = orderpoint.route_id.id in manufacture_route
  43. def _compute_visibility_days(self):
  44. res = super()._compute_visibility_days()
  45. for orderpoint in self:
  46. if 'manufacture' in orderpoint.rule_ids.mapped('action'):
  47. orderpoint.visibility_days = orderpoint.manufacturing_visibility_days
  48. return res
  49. def _set_visibility_days(self):
  50. res = super()._set_visibility_days()
  51. for orderpoint in self:
  52. if 'manufacture' in orderpoint.rule_ids.mapped('action'):
  53. orderpoint.manufacturing_visibility_days = orderpoint.visibility_days
  54. return res
  55. def _compute_days_to_order(self):
  56. res = super()._compute_days_to_order()
  57. for orderpoint in self:
  58. if 'manufacture' in orderpoint.rule_ids.mapped('action'):
  59. orderpoint.days_to_order = orderpoint.product_id.days_to_prepare_mo
  60. return res
  61. def _quantity_in_progress(self):
  62. bom_kits = self.env['mrp.bom']._bom_find(self.product_id, bom_type='phantom')
  63. bom_kit_orderpoints = {
  64. orderpoint: bom_kits[orderpoint.product_id]
  65. for orderpoint in self
  66. if orderpoint.product_id in bom_kits
  67. }
  68. orderpoints_without_kit = self - self.env['stock.warehouse.orderpoint'].concat(*bom_kit_orderpoints.keys())
  69. res = super(StockWarehouseOrderpoint, orderpoints_without_kit)._quantity_in_progress()
  70. for orderpoint in bom_kit_orderpoints:
  71. dummy, bom_sub_lines = bom_kit_orderpoints[orderpoint].explode(orderpoint.product_id, 1)
  72. ratios_qty_available = []
  73. # total = qty_available + in_progress
  74. ratios_total = []
  75. for bom_line, bom_line_data in bom_sub_lines:
  76. component = bom_line.product_id
  77. if component.type != 'product' or float_is_zero(bom_line_data['qty'], precision_rounding=bom_line.product_uom_id.rounding):
  78. continue
  79. uom_qty_per_kit = bom_line_data['qty'] / bom_line_data['original_qty']
  80. qty_per_kit = bom_line.product_uom_id._compute_quantity(uom_qty_per_kit, bom_line.product_id.uom_id, raise_if_failure=False)
  81. if not qty_per_kit:
  82. continue
  83. qty_by_product_location, dummy = component._get_quantity_in_progress(orderpoint.location_id.ids)
  84. qty_in_progress = qty_by_product_location.get((component.id, orderpoint.location_id.id), 0.0)
  85. qty_available = component.qty_available / qty_per_kit
  86. ratios_qty_available.append(qty_available)
  87. ratios_total.append(qty_available + (qty_in_progress / qty_per_kit))
  88. # For a kit, the quantity in progress is :
  89. # (the quantity if we have received all in-progress components) - (the quantity using only available components)
  90. product_qty = min(ratios_total or [0]) - min(ratios_qty_available or [0])
  91. res[orderpoint.id] = orderpoint.product_id.uom_id._compute_quantity(product_qty, orderpoint.product_uom, round=False)
  92. bom_manufacture = self.env['mrp.bom']._bom_find(orderpoints_without_kit.product_id, bom_type='normal')
  93. bom_manufacture = self.env['mrp.bom'].concat(*bom_manufacture.values())
  94. productions_group = self.env['mrp.production'].read_group(
  95. [('bom_id', 'in', bom_manufacture.ids), ('state', '=', 'draft'), ('orderpoint_id', 'in', orderpoints_without_kit.ids)],
  96. ['orderpoint_id', 'product_qty', 'product_uom_id'],
  97. ['orderpoint_id', 'product_uom_id'], lazy=False)
  98. for p in productions_group:
  99. uom = self.env['uom.uom'].browse(p['product_uom_id'][0])
  100. orderpoint = self.env['stock.warehouse.orderpoint'].browse(p['orderpoint_id'][0])
  101. res[orderpoint.id] += uom._compute_quantity(
  102. p['product_qty'], orderpoint.product_uom, round=False)
  103. return res
  104. def _get_qty_multiple_to_order(self):
  105. """ Calculates the minimum quantity that can be ordered according to the qty and UoM of the BoM
  106. """
  107. self.ensure_one()
  108. qty_multiple_to_order = super()._get_qty_multiple_to_order()
  109. if 'manufacture' in self.rule_ids.mapped('action'):
  110. bom = self.env['mrp.bom']._bom_find(self.product_id, bom_type='normal')[self.product_id]
  111. return bom.product_uom_id._compute_quantity(bom.product_qty, self.product_uom)
  112. return qty_multiple_to_order
  113. def _set_default_route_id(self):
  114. route_id = self.env['stock.rule'].search([
  115. ('action', '=', 'manufacture')
  116. ], limit=1).route_id
  117. orderpoint_wh_bom = self.filtered(lambda o: o.product_id.bom_ids)
  118. if route_id and orderpoint_wh_bom and (not self.product_id.route_ids or route_id in self.product_id.route_ids):
  119. orderpoint_wh_bom.route_id = route_id[0].id
  120. return super()._set_default_route_id()
  121. def _prepare_procurement_values(self, date=False, group=False):
  122. values = super()._prepare_procurement_values(date=date, group=group)
  123. values['bom_id'] = self.bom_id
  124. return values
  125. def _post_process_scheduler(self):
  126. """ Confirm the productions only after all the orderpoints have run their
  127. procurement to avoid the new procurement created from the production conflict
  128. with them. """
  129. self.env['mrp.production'].sudo().search([
  130. ('orderpoint_id', 'in', self.ids),
  131. ('move_raw_ids', '!=', False),
  132. ('state', '=', 'draft'),
  133. ]).action_confirm()
  134. return super()._post_process_scheduler()