diff --git a/addons/mrp/models/mrp_routing.py b/addons/mrp/models/mrp_routing.py index 6a47dad4ef0..2b37e2cb17a 100644 --- a/addons/mrp/models/mrp_routing.py +++ b/addons/mrp/models/mrp_routing.py @@ -87,7 +87,8 @@ class MrpRoutingWorkcenter(models.Model): cycle_number = 0 # Never 0 unless infinite item['workcenter_id'].capacity for item in data: total_duration += item['duration'] - cycle_number += tools.float_round((item['qty_produced'] / item['workcenter_id'].capacity or 1.0), precision_digits=0, rounding_method='UP') + capacity = item['workcenter_id']._get_capacity(item.product_id) + cycle_number += tools.float_round((item['qty_produced'] / capacity or 1.0), precision_digits=0, rounding_method='UP') if cycle_number: operation.time_cycle = total_duration / cycle_number else: diff --git a/addons/mrp/models/mrp_workcenter.py b/addons/mrp/models/mrp_workcenter.py index 3a9bb5e5ded..2556ef42103 100644 --- a/addons/mrp/models/mrp_workcenter.py +++ b/addons/mrp/models/mrp_workcenter.py @@ -30,9 +30,9 @@ class MrpWorkcenter(models.Model): note = fields.Html( 'Description', help="Description of the Work Center.") - capacity = fields.Float( + default_capacity = fields.Float( 'Capacity', default=1.0, - help="Number of pieces that can be produced in parallel. In case the work center has a capacity of 5 and you have to produce 10 units on your work order, the usual operation time will be multiplied by 2.") + help="Default number of pieces that can be produced in parallel. In case the work center has a capacity of 5 and you have to produce 10 units on your work order, the usual operation time will be multiplied by 2.") sequence = fields.Integer( 'Sequence', default=1, required=True, help="Gives the sequence order when displaying a list of work centers.") @@ -73,6 +73,8 @@ class MrpWorkcenter(models.Model): help="Alternative workcenters that can be substituted to this one in order to dispatch production" ) tag_ids = fields.Many2many('mrp.workcenter.tag') + capacity_ids = fields.One2many('mrp.workcenter.capacity', 'workcenter_id', string='Product Capacities', + help="Specific number of pieces that can be produced in parallel per product.", copy=True) @api.constrains('alternative_workcenter_ids') def _check_alternative_workcenter(self): @@ -172,9 +174,9 @@ class MrpWorkcenter(models.Model): else: workcenter.performance = 0.0 - @api.constrains('capacity') + @api.constrains('default_capacity') def _check_capacity(self): - if any(workcenter.capacity <= 0.0 for workcenter in self): + if any(workcenter.default_capacity <= 0.0 for workcenter in self): raise exceptions.UserError(_('The capacity must be strictly positive.')) def unblock(self): @@ -287,6 +289,10 @@ class MrpWorkcenter(models.Model): } return res + def _get_capacity(self, product): + product_capacity = self.capacity_ids.filtered(lambda capacity: capacity.product_id == product) + return product_capacity.capacity if product_capacity else self.default_capacity + class WorkcenterTag(models.Model): _name = 'mrp.workcenter.tag' @@ -405,3 +411,19 @@ class MrpWorkcenterProductivity(models.Model): def button_block(self): self.ensure_one() self.workcenter_id.order_ids.end_all() + + +class MrpWorkCenterCapacity(models.Model): + _name = 'mrp.workcenter.capacity' + _description = 'Work Center Capacity' + _check_company_auto = True + + workcenter_id = fields.Many2one('mrp.workcenter', string='Work Center', required=True) + product_id = fields.Many2one('product.product', string='Product', required=True) + product_uom_id = fields.Many2one('uom.uom', string='Product UoM', related='product_id.uom_id') + capacity = fields.Float('Capacity', default=1.0, help="Number of pieces that can be produced in parallel for this product.") + + _sql_constraints = [ + ('positive_capacity', 'CHECK(capacity > 0)', 'Capacity should be a positive number.'), + ('unique_product', 'UNIQUE(workcenter_id, product_id)', 'Product capacity should be unique for each workcenter.'), + ] diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py index 280bf355395..29fc458c4e0 100644 --- a/addons/mrp/models/mrp_workorder.py +++ b/addons/mrp/models/mrp_workorder.py @@ -736,13 +736,15 @@ class MrpWorkorder(models.Model): duration_expected_working = 0 return self.workcenter_id.time_start + self.workcenter_id.time_stop + duration_expected_working * ratio * 100.0 / self.workcenter_id.time_efficiency qty_production = self.production_id.product_uom_id._compute_quantity(self.qty_production, self.production_id.product_id.uom_id) - cycle_number = float_round(qty_production / self.workcenter_id.capacity, precision_digits=0, rounding_method='UP') + capacity = self.workcenter_id._get_capacity(self.product_id) + cycle_number = float_round(qty_production / capacity, precision_digits=0, rounding_method='UP') if alternative_workcenter: # TODO : find a better alternative : the settings of workcenter can change duration_expected_working = (self.duration_expected - self.workcenter_id.time_start - self.workcenter_id.time_stop) * self.workcenter_id.time_efficiency / (100.0 * cycle_number) if duration_expected_working < 0: duration_expected_working = 0 - alternative_wc_cycle_nb = float_round(qty_production / alternative_workcenter.capacity, precision_digits=0, rounding_method='UP') + capacity = alternative_workcenter._get_capacity(self.product_id) + alternative_wc_cycle_nb = float_round(qty_production / capacity, precision_digits=0, rounding_method='UP') return alternative_workcenter.time_start + alternative_workcenter.time_stop + alternative_wc_cycle_nb * duration_expected_working * 100.0 / alternative_workcenter.time_efficiency time_cycle = self.operation_id.time_cycle return self.workcenter_id.time_start + self.workcenter_id.time_stop + cycle_number * time_cycle * 100.0 / self.workcenter_id.time_efficiency diff --git a/addons/mrp/populate/mrp.py b/addons/mrp/populate/mrp.py index 97032e8f640..0472ab701c6 100644 --- a/addons/mrp/populate/mrp.py +++ b/addons/mrp/populate/mrp.py @@ -175,7 +175,7 @@ class MrpWorkcenter(models.Model): ('resource_calendar_id', populate.compute(get_resource_calendar_id)), ('active', populate.iterate([True, False], [0.9, 0.1])), ('code', populate.constant("W/{counter}")), - ('capacity', populate.iterate([0.5, 1.0, 2.0, 5.0], [0.2, 0.4, 0.2, 0.2])), + ('default_capacity', populate.iterate([0.5, 1.0, 2.0, 5.0], [0.2, 0.4, 0.2, 0.2])), ('sequence', populate.randint(1, 1000)), ('color', populate.randint(1, 12)), ('costs_hour', populate.randint(5, 25)), diff --git a/addons/mrp/report/mrp_report_bom_structure.py b/addons/mrp/report/mrp_report_bom_structure.py index b61988b007d..03a60d75620 100644 --- a/addons/mrp/report/mrp_report_bom_structure.py +++ b/addons/mrp/report/mrp_report_bom_structure.py @@ -220,7 +220,8 @@ class ReportBomStructure(models.AbstractModel): for operation in bom.operation_ids: if operation._skip_operation_line(product): continue - operation_cycle = float_round(qty / operation.workcenter_id.capacity, precision_rounding=1, rounding_method='UP') + capacity = operation.workcenter_id._get_capacity(product) + operation_cycle = float_round(qty / capacity, precision_rounding=1, rounding_method='UP') duration_expected = (operation_cycle * operation.time_cycle * 100.0 / operation.workcenter_id.time_efficiency) + (operation.workcenter_id.time_stop + operation.workcenter_id.time_start) total = ((duration_expected / 60.0) * operation.workcenter_id.costs_hour) operations.append({ diff --git a/addons/mrp/security/ir.model.access.csv b/addons/mrp/security/ir.model.access.csv index ae83136a1d6..c50c3f47378 100644 --- a/addons/mrp/security/ir.model.access.csv +++ b/addons/mrp/security/ir.model.access.csv @@ -61,3 +61,4 @@ access_mrp_workcenter_tag_manager,access.mrp.workcenter.tag,model_mrp_workcenter access_mrp_production_split_multi,access.mrp.production.split.multi,model_mrp_production_split_multi,mrp.group_mrp_user,1,1,1,0 access_mrp_production_split,access.mrp.production.split,model_mrp_production_split,mrp.group_mrp_user,1,1,1,0 access_mrp_production_split_line,access.mrp.production.split.line,model_mrp_production_split_line,mrp.group_mrp_user,1,1,1,1 +access_mrp_workcenter_capacity_manager,mrp.workcenter.capacity.manager,model_mrp_workcenter_capacity,mrp.group_mrp_manager,1,1,1,1 diff --git a/addons/mrp/tests/common.py b/addons/mrp/tests/common.py index a6937e6f8f1..3a837202bca 100644 --- a/addons/mrp/tests/common.py +++ b/addons/mrp/tests/common.py @@ -79,21 +79,21 @@ class TestMrpCommon(common2.TestStockCommon): cls.workcenter_1 = cls.env['mrp.workcenter'].create({ 'name': 'Nuclear Workcenter', - 'capacity': 2, + 'default_capacity': 2, 'time_start': 10, 'time_stop': 5, 'time_efficiency': 80, }) cls.workcenter_2 = cls.env['mrp.workcenter'].create({ 'name': 'Simple Workcenter', - 'capacity': 1, + 'default_capacity': 1, 'time_start': 0, 'time_stop': 0, 'time_efficiency': 100, }) cls.workcenter_3 = cls.env['mrp.workcenter'].create({ 'name': 'Double Workcenter', - 'capacity': 2, + 'default_capacity': 2, 'time_start': 0, 'time_stop': 0, 'time_efficiency': 100, diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py index df3efbb55ff..c9f9160b658 100644 --- a/addons/mrp/tests/test_order.py +++ b/addons/mrp/tests/test_order.py @@ -2673,7 +2673,7 @@ class TestMrpOrder(TestMrpCommon): """ workcenter_1 = self.env['mrp.workcenter'].create({ 'name': 'wc1', - 'capacity': 2, + 'default_capacity': 2, 'time_start': 1, 'time_stop': 1, 'time_efficiency': 100, @@ -2681,7 +2681,7 @@ class TestMrpOrder(TestMrpCommon): workcenter_2 = self.env['mrp.workcenter'].create({ 'name': 'wc2', - 'capacity': 2, + 'default_capacity': 2, 'time_start': 10, 'time_stop': 5, 'time_efficiency': 100, @@ -2725,7 +2725,7 @@ class TestMrpOrder(TestMrpCommon): mo.button_unplan() # Update the production capcity - workcenter_2.capacity = 4 + workcenter_2.default_capacity = 4 #MO_2 mo_form = Form(self.env['mrp.production']) diff --git a/addons/mrp/views/mrp_workcenter_views.xml b/addons/mrp/views/mrp_workcenter_views.xml index 68bcba3a743..21ff75d7e90 100644 --- a/addons/mrp/views/mrp_workcenter_views.xml +++ b/addons/mrp/views/mrp_workcenter_views.xml @@ -40,7 +40,7 @@ - + @@ -364,8 +364,7 @@
%
- - +