Files
odoo_source/addons/mrp/models/mrp_routing.py
T
Nicolas Martinelli 01c567c376 [FIX] mrp: Time Cycle computation
- Create a workcenter with a capacity of 500 units
- Create a BOM with a routing generating one work order in the WC
  created
- Create 3 MO with the following real durations:
  MO 1: 1000 units produced in 420.95 seconds
  MO 2: 1000 units produced in 120.28 seconds
  MO 3: 1000 units produced in 60 seconds
- Create a MO for 1 unit

The Duration expected is computed as

The Time Cycle is computed as:
  (420.95 + 120.28 + 60) / (1000 + 1000 + 1000) = 0.20041
The Total Expected is computed as:
  0 + 0 + (1.0 * 0.20041) * (100.0 / 100.0) = 0.20041

0.20041 seconds is not realistic based on the previous MO real
durations.

This is because the Time Cycle doesn't take into account the workcenter
capacity. If we can produce 60 units in parallel in 60 seconds, that
doesn't mean we can produce 1 unit in 1 second. Therefore, the Time
Cycle should be multiplied by the WC capacity, which gives a Time Cycle:

   500 * (420.95 + 120.28 + 60) / (3000) = 100.205

Which is much closer to the real duration.

opw-2070759

closes odoo/odoo#37548

X-original-commit: 9d3a73f64feb3b9e71c50a04adb6206b6393c757
Signed-off-by: Nicolas Martinelli (nim) <nim@odoo.com>
2019-09-27 17:51:21 +00:00

101 lines
4.9 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
class MrpRouting(models.Model):
""" Specifies routings of work centers """
_name = 'mrp.routing'
_description = 'Routings'
name = fields.Char('Routing', required=True)
active = fields.Boolean(
'Active', default=True,
help="If the active field is set to False, it will allow you to hide the routing without removing it.")
code = fields.Char(
'Reference',
copy=False, default=lambda self: _('New'), readonly=True)
note = fields.Text('Description')
operation_ids = fields.One2many(
'mrp.routing.workcenter', 'routing_id', 'Operations',
copy=True)
company_id = fields.Many2one(
'res.company', 'Company', default=lambda self: self.env.company)
@api.model
def create(self, vals):
if 'code' not in vals or vals['code'] == _('New'):
vals['code'] = self.env['ir.sequence'].next_by_code('mrp.routing') or _('New')
return super(MrpRouting, self).create(vals)
class MrpRoutingWorkcenter(models.Model):
_name = 'mrp.routing.workcenter'
_description = 'Work Center Usage'
_order = 'sequence, id'
_check_company_auto = True
name = fields.Char('Operation', required=True)
workcenter_id = fields.Many2one('mrp.workcenter', 'Work Center', required=True, check_company=True)
sequence = fields.Integer(
'Sequence', default=100,
help="Gives the sequence order when displaying a list of routing Work Centers.")
routing_id = fields.Many2one(
'mrp.routing', 'Parent Routing',
index=True, ondelete='cascade', required=True,
help="The routing contains all the Work Centers used and for how long. This will create work orders afterwards "
"which alters the execution of the manufacturing order.")
note = fields.Text('Description')
company_id = fields.Many2one(
'res.company', 'Company',
readonly=True, related='routing_id.company_id', store=True)
worksheet = fields.Binary('PDF', help="Upload your PDF file.")
worksheet_type = fields.Selection([
('pdf', 'PDF'), ('google_slide', 'Google Slide')],
string="Work Sheet", default="pdf",
help="Defines if you want to use a PDF or a Google Slide as work sheet."
)
worksheet_google_slide = fields.Char('Google Slide', help="Paste the url of your Google Slide. Make sure the access to the document is public.")
time_mode = fields.Selection([
('auto', 'Compute based on real time'),
('manual', 'Set duration manually')], string='Duration Computation',
default='manual')
time_mode_batch = fields.Integer('Based on', default=10)
time_cycle_manual = fields.Float(
'Manual Duration', default=60,
help="Time in minutes. Is the time used in manual mode, or the first time supposed in real time when there are not any work orders yet.")
time_cycle = fields.Float('Duration', compute="_compute_time_cycle")
workorder_count = fields.Integer("# Work Orders", compute="_compute_workorder_count")
batch = fields.Selection([
('no', 'Once all products are processed'),
('yes', 'Once some products are processed')], string='Start Next Operation',
default='no', required=True)
batch_size = fields.Float('Quantity to Process', default=1.0)
workorder_ids = fields.One2many('mrp.workorder', 'operation_id', string="Work Orders")
@api.depends('time_cycle_manual', 'time_mode', 'workorder_ids')
def _compute_time_cycle(self):
manual_ops = self.filtered(lambda operation: operation.time_mode == 'manual')
for operation in manual_ops:
operation.time_cycle = operation.time_cycle_manual
for operation in self - manual_ops:
data = self.env['mrp.workorder'].read_group([
('operation_id', '=', operation.id),
('state', '=', 'done')], ['operation_id', 'duration', 'qty_produced'], ['operation_id'],
limit=operation.time_mode_batch)
count_data = dict((item['operation_id'][0], (item['duration'], item['qty_produced'])) for item in data)
if count_data.get(operation.id) and count_data[operation.id][1]:
operation.time_cycle = (count_data[operation.id][0] / count_data[operation.id][1]) * (operation.workcenter_id.capacity or 1.0)
else:
operation.time_cycle = operation.time_cycle_manual
def _compute_workorder_count(self):
data = self.env['mrp.workorder'].read_group([
('operation_id', 'in', self.ids),
('state', '=', 'done')], ['operation_id'], ['operation_id'])
count_data = dict((item['operation_id'][0], item['operation_id_count']) for item in data)
for operation in self:
operation.workorder_count = count_data.get(operation.id, 0)