[IMP] mrp*: Merge workorder and production scheduled/effective dates
Since starting a workorder and marking it as done replaces the planned dates with the actual ones, there is no need to keep dates which will always end up being the same. task: 3108291 see odoo/enterprise#36102 see odoo/upgrade#4247 closes odoo/odoo#110550 Signed-off-by: Arnold Moyaux (arm) <arm@odoo.com>
This commit is contained in:
@@ -283,7 +283,7 @@
|
||||
<field name="product_id" ref="product_product_computer_desk"/>
|
||||
<field name="product_uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="product_qty">1</field>
|
||||
<field name="date_planned_start" eval="(DateTime.today() + relativedelta(days=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
<field name="date_start" eval="(DateTime.today() + relativedelta(days=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
<field name="bom_id" ref="mrp_bom_desk"/>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -24,15 +24,25 @@ class MrpProduction(models.Model):
|
||||
""" Manufacturing Orders """
|
||||
_name = 'mrp.production'
|
||||
_description = 'Production Order'
|
||||
_date_name = 'date_planned_start'
|
||||
_date_name = 'date_start'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'priority desc, date_planned_start asc,id'
|
||||
_order = 'priority desc, date_start asc,id'
|
||||
|
||||
@api.model
|
||||
def _get_default_date_planned_start(self):
|
||||
def _get_default_date_start(self):
|
||||
if self.env.context.get('default_date_deadline'):
|
||||
date_finished = fields.Datetime.to_datetime(self.env.context.get('default_date_deadline'))
|
||||
date_start = date_finished - relativedelta(hours=1)
|
||||
return date_start
|
||||
return fields.Datetime.now()
|
||||
|
||||
@api.model
|
||||
def _get_default_date_finished(self):
|
||||
if self.env.context.get('default_date_deadline'):
|
||||
return fields.Datetime.to_datetime(self.env.context.get('default_date_deadline'))
|
||||
return fields.Datetime.now()
|
||||
date_start = fields.Datetime.now()
|
||||
date_finished = date_start + relativedelta(hours=1)
|
||||
return date_finished
|
||||
|
||||
@api.model
|
||||
def _get_default_is_locked(self):
|
||||
@@ -98,23 +108,19 @@ class MrpProduction(models.Model):
|
||||
readonly=False, required=True, precompute=True,
|
||||
domain="[('usage','=','internal'), '|', ('company_id', '=', False), ('company_id', '=', company_id)]",
|
||||
help="Location where the system will stock the finished products.")
|
||||
date_planned_start = fields.Datetime(
|
||||
'Scheduled Date', copy=False, default=_get_default_date_planned_start,
|
||||
help="Date at which you plan to start the production.",
|
||||
index=True, required=True)
|
||||
date_planned_finished = fields.Datetime(
|
||||
'Scheduled End Date',
|
||||
compute='_compute_date_planned_finished', store=True,
|
||||
help="Date at which you plan to finish the production.",
|
||||
copy=False)
|
||||
date_deadline = fields.Datetime(
|
||||
'Deadline', copy=False, store=True, readonly=True, compute='_compute_date_deadline',
|
||||
help="Informative date allowing to define when the manufacturing order should be processed at the latest to fulfill delivery on time.")
|
||||
date_start = fields.Datetime('Start Date', copy=False, readonly=True)
|
||||
date_finished = fields.Datetime('End Date', copy=False, readonly=True)
|
||||
|
||||
production_duration_expected = fields.Float("Expected Duration", help="Total expected duration (in minutes)", compute='_compute_production_duration_expected')
|
||||
production_real_duration = fields.Float("Real Duration", help="Total real duration (in minutes)", compute='_compute_production_real_duration')
|
||||
date_start = fields.Datetime(
|
||||
'Start', copy=False, default=_get_default_date_start,
|
||||
help="Date you plan to start production or date you actually started production.",
|
||||
index=True, required=True)
|
||||
date_finished = fields.Datetime(
|
||||
'End', copy=False, default=_get_default_date_finished,
|
||||
compute='_compute_date_finished', store=True,
|
||||
help="Date you expect to finish production or actual date you finished production.")
|
||||
duration_expected = fields.Float("Expected Duration", help="Total expected duration (in minutes)", compute='_compute_duration_expected')
|
||||
duration = fields.Float("Real Duration", help="Total real duration (in minutes)", compute='_compute_duration')
|
||||
|
||||
bom_id = fields.Many2one(
|
||||
'mrp.bom', 'Bill of Material', readonly=False,
|
||||
@@ -305,7 +311,7 @@ class MrpProduction(models.Model):
|
||||
production.location_src_id = production.picking_type_id.default_location_src_id.id or fallback_loc.id
|
||||
production.location_dest_id = production.picking_type_id.default_location_dest_id.id or fallback_loc.id
|
||||
|
||||
@api.depends('state', 'reservation_state', 'date_planned_start', 'move_raw_ids', 'move_raw_ids.forecast_availability', 'move_raw_ids.forecast_expected_date')
|
||||
@api.depends('state', 'reservation_state', 'date_start', 'move_raw_ids', 'move_raw_ids.forecast_availability', 'move_raw_ids.forecast_expected_date')
|
||||
def _compute_components_availability(self):
|
||||
productions = self.filtered(lambda mo: mo.state not in ('cancel', 'done', 'draft'))
|
||||
productions.components_availability_state = 'available'
|
||||
@@ -326,8 +332,8 @@ class MrpProduction(models.Model):
|
||||
forecast_date = max(production.move_raw_ids.filtered('forecast_expected_date').mapped('forecast_expected_date'), default=False)
|
||||
if forecast_date:
|
||||
production.components_availability = _('Exp %s', format_date(self.env, forecast_date))
|
||||
if production.date_planned_start:
|
||||
production.components_availability_state = 'late' if forecast_date > production.date_planned_start else 'expected'
|
||||
if production.date_start:
|
||||
production.components_availability_state = 'late' if forecast_date > production.date_start else 'expected'
|
||||
|
||||
@api.depends('bom_id')
|
||||
def _compute_product_id(self):
|
||||
@@ -378,20 +384,20 @@ class MrpProduction(models.Model):
|
||||
production.date_deadline = min(production.move_finished_ids.filtered('date_deadline').mapped('date_deadline'), default=production.date_deadline or False)
|
||||
|
||||
@api.depends('workorder_ids.duration_expected')
|
||||
def _compute_production_duration_expected(self):
|
||||
def _compute_duration_expected(self):
|
||||
for production in self:
|
||||
production.production_duration_expected = sum(production.workorder_ids.mapped('duration_expected'))
|
||||
production.duration_expected = sum(production.workorder_ids.mapped('duration_expected'))
|
||||
|
||||
@api.depends('workorder_ids.duration')
|
||||
def _compute_production_real_duration(self):
|
||||
def _compute_duration(self):
|
||||
for production in self:
|
||||
production.production_real_duration = sum(production.workorder_ids.mapped('duration'))
|
||||
production.duration = sum(production.workorder_ids.mapped('duration'))
|
||||
|
||||
@api.depends("workorder_ids.date_planned_start", "workorder_ids.date_planned_finished")
|
||||
@api.depends("workorder_ids.date_start", "workorder_ids.date_finished")
|
||||
def _compute_is_planned(self):
|
||||
for production in self:
|
||||
if production.workorder_ids:
|
||||
production.is_planned = any(wo.date_planned_start and wo.date_planned_finished for wo in production.workorder_ids)
|
||||
production.is_planned = any(wo.date_start and wo.date_finished for wo in production.workorder_ids)
|
||||
else:
|
||||
production.is_planned = False
|
||||
|
||||
@@ -661,13 +667,13 @@ class MrpProduction(models.Model):
|
||||
('move_orig_ids', 'in', lines.ids)], limit=1):
|
||||
mo.show_allocation = True
|
||||
|
||||
@api.depends('product_uom_qty', 'date_planned_start')
|
||||
@api.depends('product_uom_qty', 'date_start')
|
||||
def _compute_forecasted_issue(self):
|
||||
for order in self:
|
||||
warehouse = order.location_dest_id.warehouse_id
|
||||
order.forecasted_issue = False
|
||||
if order.product_id:
|
||||
virtual_available = order.product_id.with_context(warehouse=warehouse.id, to_date=order.date_planned_start).virtual_available
|
||||
virtual_available = order.product_id.with_context(warehouse=warehouse.id, to_date=order.date_start).virtual_available
|
||||
if order.state == 'draft':
|
||||
virtual_available += order.product_uom_qty
|
||||
if virtual_available < 0:
|
||||
@@ -678,18 +684,18 @@ class MrpProduction(models.Model):
|
||||
late_stock_moves = self.env['stock.move'].search([('delay_alert_date', operator, value)])
|
||||
return ['|', ('move_raw_ids', 'in', late_stock_moves.ids), ('move_finished_ids', 'in', late_stock_moves.ids)]
|
||||
|
||||
@api.depends('company_id', 'date_planned_start', 'is_planned', 'product_id')
|
||||
def _compute_date_planned_finished(self):
|
||||
@api.depends('company_id', 'date_start', 'is_planned', 'product_id')
|
||||
def _compute_date_finished(self):
|
||||
for production in self:
|
||||
if not production.date_planned_start or production.is_planned:
|
||||
if not production.date_start or production.is_planned:
|
||||
continue
|
||||
days_delay = production.bom_id.produce_delay
|
||||
date_planned_finished = production.date_planned_start + relativedelta(days=days_delay)
|
||||
if date_planned_finished == production.date_planned_start:
|
||||
date_planned_finished = date_planned_finished + relativedelta(hours=1)
|
||||
production.date_planned_finished = date_planned_finished
|
||||
date_finished = production.date_start + relativedelta(days=days_delay)
|
||||
if date_finished == production.date_start:
|
||||
date_finished = date_finished + relativedelta(hours=1)
|
||||
production.date_finished = date_finished
|
||||
|
||||
@api.depends('company_id', 'bom_id', 'product_id', 'product_qty', 'product_uom_id', 'location_src_id', 'date_planned_start')
|
||||
@api.depends('company_id', 'bom_id', 'product_id', 'product_qty', 'product_uom_id', 'location_src_id', 'date_start')
|
||||
def _compute_move_raw_ids(self):
|
||||
for production in self:
|
||||
if production.state != 'draft':
|
||||
@@ -714,13 +720,13 @@ class MrpProduction(models.Model):
|
||||
else:
|
||||
production.move_raw_ids = [Command.delete(move.id) for move in production.move_raw_ids.filtered(lambda m: m.bom_line_id)]
|
||||
|
||||
@api.depends('product_id', 'bom_id', 'product_qty', 'product_uom_id', 'location_dest_id', 'date_planned_finished')
|
||||
@api.depends('product_id', 'bom_id', 'product_qty', 'product_uom_id', 'location_dest_id', 'date_finished')
|
||||
def _compute_move_finished_ids(self):
|
||||
for production in self:
|
||||
if production.state != 'draft':
|
||||
updated_values = {}
|
||||
if production.date_planned_finished:
|
||||
updated_values['date'] = production.date_planned_finished
|
||||
if production.date_finished:
|
||||
updated_values['date'] = production.date_finished
|
||||
if production.date_deadline:
|
||||
updated_values['date_deadline'] = production.date_deadline
|
||||
if 'date' in updated_values or 'date_deadline' in updated_values:
|
||||
@@ -800,15 +806,15 @@ class MrpProduction(models.Model):
|
||||
res = super(MrpProduction, self).write(vals)
|
||||
|
||||
for production in self:
|
||||
if 'date_planned_start' in vals and not self.env.context.get('force_date', False):
|
||||
if 'date_start' in vals and not self.env.context.get('force_date', False):
|
||||
if production.state in ['done', 'cancel']:
|
||||
raise UserError(_('You cannot move a manufacturing order once it is cancelled or done.'))
|
||||
if production.is_planned:
|
||||
production.button_unplan()
|
||||
if vals.get('date_planned_start'):
|
||||
production.move_raw_ids.write({'date': production.date_planned_start, 'date_deadline': production.date_planned_start})
|
||||
if vals.get('date_planned_finished'):
|
||||
production.move_finished_ids.write({'date': production.date_planned_finished})
|
||||
if vals.get('date_start'):
|
||||
production.move_raw_ids.write({'date': production.date_start, 'date_deadline': production.date_start})
|
||||
if vals.get('date_finished'):
|
||||
production.move_finished_ids.write({'date': production.date_finished})
|
||||
if any(field in ['move_raw_ids', 'move_finished_ids', 'workorder_ids'] for field in vals) and production.state != 'draft':
|
||||
production._autoconfirm_production()
|
||||
if production in production_to_replan:
|
||||
@@ -820,10 +826,10 @@ class MrpProduction(models.Model):
|
||||
finished_move_lines.write({'lot_id': vals.get('lot_producing_id')})
|
||||
if 'qty_producing' in vals:
|
||||
finished_move_lines.write({'qty_done': vals.get('qty_producing')})
|
||||
if self._has_workorders() and not production.workorder_ids.operation_id and vals.get('date_planned_start') and not vals.get('date_planned_finished'):
|
||||
new_date_planned_start = fields.Datetime.to_datetime(vals.get('date_planned_start'))
|
||||
if not production.date_planned_finished or new_date_planned_start >= production.date_planned_finished:
|
||||
production.date_planned_finished = new_date_planned_start + datetime.timedelta(hours=1)
|
||||
if self._has_workorders() and not production.workorder_ids.operation_id and vals.get('date_start') and not vals.get('date_finished'):
|
||||
new_date_start = fields.Datetime.to_datetime(vals.get('date_start'))
|
||||
if not production.date_finished or new_date_start >= production.date_finished:
|
||||
production.date_finished = new_date_start + datetime.timedelta(hours=1)
|
||||
return res
|
||||
|
||||
@api.model_create_multi
|
||||
@@ -934,7 +940,7 @@ class MrpProduction(models.Model):
|
||||
'operation_id': operation_id,
|
||||
'byproduct_id': byproduct_id,
|
||||
'name': _('New'),
|
||||
'date': self._get_date_planned_finished(),
|
||||
'date': self.date_finished,
|
||||
'date_deadline': self.date_deadline,
|
||||
'picking_type_id': self.picking_type_id.id,
|
||||
'location_id': self.product_id.with_company(self.company_id).property_stock_production.id,
|
||||
@@ -949,12 +955,6 @@ class MrpProduction(models.Model):
|
||||
'cost_share': cost_share,
|
||||
}
|
||||
|
||||
def _get_date_planned_finished(self):
|
||||
date_planned_finished = self.date_planned_start + relativedelta(days=self.bom_id.produce_delay)
|
||||
if date_planned_finished == self.date_planned_start:
|
||||
date_planned_finished = date_planned_finished + relativedelta(hours=1)
|
||||
return date_planned_finished
|
||||
|
||||
def _get_moves_finished_values(self):
|
||||
moves = []
|
||||
for production in self:
|
||||
@@ -1021,8 +1021,8 @@ class MrpProduction(models.Model):
|
||||
data = {
|
||||
'sequence': bom_line.sequence if bom_line else 10,
|
||||
'name': _('New'),
|
||||
'date': self.date_planned_start,
|
||||
'date_deadline': self.date_planned_start,
|
||||
'date': self.date_start,
|
||||
'date_deadline': self.date_start,
|
||||
'bom_line_id': bom_line.id if bom_line else False,
|
||||
'picking_type_id': self.picking_type_id.id,
|
||||
'product_id': product_id.id,
|
||||
@@ -1307,8 +1307,8 @@ class MrpProduction(models.Model):
|
||||
|
||||
workorders = self.workorder_ids.filtered(lambda w: w.state not in ['done', 'cancel'])
|
||||
self.with_context(force_date=True).write({
|
||||
'date_planned_start': min([workorder.leave_id.date_from for workorder in workorders]),
|
||||
'date_planned_finished': max([workorder.leave_id.date_to for workorder in workorders])
|
||||
'date_start': min([workorder.leave_id.date_from for workorder in workorders]),
|
||||
'date_finished': max([workorder.leave_id.date_to for workorder in workorders])
|
||||
})
|
||||
|
||||
def button_unplan(self):
|
||||
@@ -1319,8 +1319,8 @@ class MrpProduction(models.Model):
|
||||
|
||||
self.workorder_ids.leave_id.unlink()
|
||||
self.workorder_ids.write({
|
||||
'date_planned_start': False,
|
||||
'date_planned_finished': False,
|
||||
'date_start': False,
|
||||
'date_finished': False,
|
||||
})
|
||||
|
||||
def _get_consumption_issues(self):
|
||||
@@ -2050,7 +2050,7 @@ class MrpProduction(models.Model):
|
||||
|
||||
self.with_context(skip_activity=True)._action_cancel()
|
||||
# set the new deadline of origin moves (stock to pre prod)
|
||||
production.move_raw_ids.move_orig_ids.with_context(date_deadline_propagate_ids=set(production.move_raw_ids.ids)).write({'date_deadline': production.date_planned_start})
|
||||
production.move_raw_ids.move_orig_ids.with_context(date_deadline_propagate_ids=set(production.move_raw_ids.ids)).write({'date_deadline': production.date_start})
|
||||
for p in self:
|
||||
p._message_log(body=_('This production has been merge in %s', production.display_name))
|
||||
|
||||
|
||||
@@ -82,14 +82,14 @@ class MrpWorkcenter(models.Model):
|
||||
if workcenter in workcenter.alternative_workcenter_ids:
|
||||
raise ValidationError(_("Workcenter %s cannot be an alternative of itself.", workcenter.name))
|
||||
|
||||
@api.depends('order_ids.duration_expected', 'order_ids.workcenter_id', 'order_ids.state', 'order_ids.date_planned_start')
|
||||
@api.depends('order_ids.duration_expected', 'order_ids.workcenter_id', 'order_ids.state', 'order_ids.date_start')
|
||||
def _compute_workorder_count(self):
|
||||
MrpWorkorder = self.env['mrp.workorder']
|
||||
result = {wid: {} for wid in self._ids}
|
||||
result_duration_expected = {wid: 0 for wid in self._ids}
|
||||
# Count Late Workorder
|
||||
data = MrpWorkorder._read_group(
|
||||
[('workcenter_id', 'in', self.ids), ('state', 'in', ('pending', 'waiting', 'ready')), ('date_planned_start', '<', datetime.now().strftime('%Y-%m-%d'))],
|
||||
[('workcenter_id', 'in', self.ids), ('state', 'in', ('pending', 'waiting', 'ready')), ('date_start', '<', datetime.now().strftime('%Y-%m-%d'))],
|
||||
['workcenter_id'], ['workcenter_id'])
|
||||
count_data = dict((item['workcenter_id'][0], item['workcenter_id_count']) for item in data)
|
||||
# Count All, Pending, Ready, Progress Workorder
|
||||
|
||||
@@ -68,25 +68,18 @@ class MrpWorkorder(models.Model):
|
||||
'resource.calendar.leaves',
|
||||
help='Slot into workcenter calendar once planned',
|
||||
check_company=True, copy=False)
|
||||
date_planned_start = fields.Datetime(
|
||||
'Scheduled Start Date',
|
||||
compute='_compute_dates_planned',
|
||||
inverse='_set_dates_planned',
|
||||
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
|
||||
store=True, copy=False)
|
||||
date_planned_finished = fields.Datetime(
|
||||
'Scheduled End Date',
|
||||
compute='_compute_dates_planned',
|
||||
inverse='_set_dates_planned',
|
||||
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
|
||||
store=True, copy=False)
|
||||
date_start = fields.Datetime(
|
||||
'Start Date', copy=False,
|
||||
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
|
||||
'Start',
|
||||
compute='_compute_dates',
|
||||
inverse='_set_dates',
|
||||
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
|
||||
store=True, copy=False)
|
||||
date_finished = fields.Datetime(
|
||||
'End Date', copy=False,
|
||||
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
|
||||
|
||||
'End',
|
||||
compute='_compute_dates',
|
||||
inverse='_set_dates',
|
||||
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
|
||||
store=True, copy=False)
|
||||
duration_expected = fields.Float(
|
||||
'Expected Duration', digits=(16, 2), compute='_compute_duration_expected',
|
||||
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
|
||||
@@ -138,7 +131,7 @@ class MrpWorkorder(models.Model):
|
||||
|
||||
scrap_ids = fields.One2many('stock.scrap', 'workorder_id')
|
||||
scrap_count = fields.Integer(compute='_compute_scrap_move_count', string='Scrap Move')
|
||||
production_date = fields.Datetime('Production Date', related='production_id.date_planned_start', store=True)
|
||||
production_date = fields.Datetime('Production Date', related='production_id.date_start', store=True)
|
||||
json_popover = fields.Char('Popover Data JSON', compute='_compute_json_popover')
|
||||
show_json_popover = fields.Boolean('Show Popover?', compute='_compute_json_popover')
|
||||
consumption = fields.Selection(related='production_id.consumption')
|
||||
@@ -177,33 +170,33 @@ class MrpWorkorder(models.Model):
|
||||
elif workorder.production_id.reservation_state != 'assigned' and workorder.state == 'ready':
|
||||
workorder.state = 'waiting'
|
||||
|
||||
@api.depends('production_state', 'date_planned_start', 'date_planned_finished')
|
||||
@api.depends('production_state', 'date_start', 'date_finished')
|
||||
def _compute_json_popover(self):
|
||||
if self.ids:
|
||||
conflicted_dict = self._get_conflicted_workorder_ids()
|
||||
for wo in self:
|
||||
infos = []
|
||||
if not wo.date_planned_start or not wo.date_planned_finished or not wo.ids:
|
||||
if not wo.date_start or not wo.date_finished or not wo.ids:
|
||||
wo.show_json_popover = False
|
||||
wo.json_popover = False
|
||||
continue
|
||||
if wo.state in ('pending', 'waiting', 'ready'):
|
||||
previous_wos = wo.blocked_by_workorder_ids
|
||||
prev_start = min([workorder.date_planned_start for workorder in previous_wos]) if previous_wos else False
|
||||
prev_finished = max([workorder.date_planned_finished for workorder in previous_wos]) if previous_wos else False
|
||||
if wo.state == 'pending' and prev_start and not (prev_start > wo.date_planned_start):
|
||||
prev_start = min([workorder.date_start for workorder in previous_wos]) if previous_wos else False
|
||||
prev_finished = max([workorder.date_finished for workorder in previous_wos]) if previous_wos else False
|
||||
if wo.state == 'pending' and prev_start and not (prev_start > wo.date_start):
|
||||
infos.append({
|
||||
'color': 'text-primary',
|
||||
'msg': _("Waiting the previous work order, planned from %(start)s to %(end)s",
|
||||
start=format_datetime(self.env, prev_start, dt_format=False),
|
||||
end=format_datetime(self.env, prev_finished, dt_format=False))
|
||||
})
|
||||
if wo.date_planned_finished < fields.Datetime.now():
|
||||
if wo.date_finished < fields.Datetime.now():
|
||||
infos.append({
|
||||
'color': 'text-warning',
|
||||
'msg': _("The work order should have already been processed.")
|
||||
})
|
||||
if prev_start and prev_start > wo.date_planned_start:
|
||||
if prev_start and prev_start > wo.date_start:
|
||||
infos.append({
|
||||
'color': 'text-danger',
|
||||
'msg': _("Scheduled before the previous work order, planned from %(start)s to %(end)s",
|
||||
@@ -245,26 +238,26 @@ class MrpWorkorder(models.Model):
|
||||
workorder.production_id.qty_producing = workorder.qty_producing
|
||||
workorder.production_id._set_qty_producing()
|
||||
|
||||
# Both `date_planned_start` and `date_planned_finished` are related fields on `leave_id`. Let's say
|
||||
# Both `date_start` and `date_finished` are related fields on `leave_id`. Let's say
|
||||
# we slide a workorder on a gantt view, a single call to write is made with both
|
||||
# fields Changes. As the ORM doesn't batch the write on related fields and instead
|
||||
# makes multiple call, the constraint check_dates() is raised.
|
||||
# That's why the compute and set methods are needed. to ensure the dates are updated
|
||||
# in the same time.
|
||||
@api.depends('leave_id')
|
||||
def _compute_dates_planned(self):
|
||||
def _compute_dates(self):
|
||||
for workorder in self:
|
||||
workorder.date_planned_start = workorder.leave_id.date_from
|
||||
workorder.date_planned_finished = workorder.leave_id.date_to
|
||||
workorder.date_start = workorder.leave_id.date_from
|
||||
workorder.date_finished = workorder.leave_id.date_to
|
||||
|
||||
def _set_dates_planned(self):
|
||||
if not self[0].date_planned_start or not self[0].date_planned_finished:
|
||||
def _set_dates(self):
|
||||
if not self[0].date_start or not self[0].date_finished:
|
||||
if not self.leave_id:
|
||||
return
|
||||
raise UserError(_("It is not possible to unplan one single Work Order. "
|
||||
"You should unplan the Manufacturing Order instead in order to unplan all the linked operations."))
|
||||
date_from = self[0].date_planned_start
|
||||
date_to = self[0].date_planned_finished
|
||||
date_from = self[0].date_start
|
||||
date_to = self[0].date_finished
|
||||
to_write = self.env['mrp.workorder']
|
||||
for wo in self.sudo():
|
||||
if wo.leave_id:
|
||||
@@ -415,25 +408,25 @@ class MrpWorkorder(models.Model):
|
||||
self.name = self.operation_id.name
|
||||
self.workcenter_id = self.operation_id.workcenter_id.id
|
||||
|
||||
@api.onchange('date_planned_start', 'duration_expected', 'workcenter_id')
|
||||
def _onchange_date_planned_start(self):
|
||||
if self.date_planned_start and self.workcenter_id:
|
||||
self.date_planned_finished = self._calculate_date_planned_finished()
|
||||
@api.onchange('date_start', 'duration_expected', 'workcenter_id')
|
||||
def _onchange_date_start(self):
|
||||
if self.date_start and self.workcenter_id:
|
||||
self.date_finished = self._calculate_date_finished()
|
||||
|
||||
def _calculate_date_planned_finished(self, date_planned_start=False):
|
||||
def _calculate_date_finished(self, date_finished=False):
|
||||
return self.workcenter_id.resource_calendar_id.plan_hours(
|
||||
self.duration_expected / 60.0, date_planned_start or self.date_planned_start,
|
||||
self.duration_expected / 60.0, date_finished or self.date_start,
|
||||
compute_leaves=True, domain=[('time_type', 'in', ['leave', 'other'])]
|
||||
)
|
||||
|
||||
@api.onchange('date_planned_finished')
|
||||
def _onchange_date_planned_finished(self):
|
||||
if self.date_planned_start and self.date_planned_finished:
|
||||
@api.onchange('date_finished')
|
||||
def _onchange_date_finished(self):
|
||||
if self.date_start and self.date_finished:
|
||||
self.duration_expected = self._calculate_duration_expected()
|
||||
|
||||
def _calculate_duration_expected(self, date_planned_start=False, date_planned_finished=False):
|
||||
def _calculate_duration_expected(self, date_start=False, date_finished=False):
|
||||
interval = self.workcenter_id.resource_calendar_id.get_work_duration_data(
|
||||
date_planned_start or self.date_planned_start, date_planned_finished or self.date_planned_finished,
|
||||
date_start or self.date_start, date_finished or self.date_finished,
|
||||
domain=[('time_type', 'in', ['leave', 'other'])]
|
||||
)
|
||||
return interval['hours'] * 60
|
||||
@@ -453,30 +446,30 @@ class MrpWorkorder(models.Model):
|
||||
if workorder.state in ('progress', 'done', 'cancel'):
|
||||
raise UserError(_('You cannot change the workcenter of a work order that is in progress or done.'))
|
||||
workorder.leave_id.resource_id = self.env['mrp.workcenter'].browse(values['workcenter_id']).resource_id
|
||||
if 'date_planned_start' in values or 'date_planned_finished' in values:
|
||||
if 'date_start' in values or 'date_finished' in values:
|
||||
for workorder in self:
|
||||
start_date = fields.Datetime.to_datetime(values.get('date_planned_start', workorder.date_planned_start))
|
||||
end_date = fields.Datetime.to_datetime(values.get('date_planned_finished', workorder.date_planned_finished))
|
||||
if start_date and end_date and start_date > end_date:
|
||||
date_start = fields.Datetime.to_datetime(values.get('date_start', workorder.date_start))
|
||||
date_finished = fields.Datetime.to_datetime(values.get('date_finished', workorder.date_finished))
|
||||
if date_start and date_finished and date_start > date_finished:
|
||||
raise UserError(_('The planned end date of the work order cannot be prior to the planned start date, please correct this to save the work order.'))
|
||||
if 'duration_expected' not in values and not self.env.context.get('bypass_duration_calculation'):
|
||||
if values.get('date_planned_start') and values.get('date_planned_finished'):
|
||||
computed_finished_time = workorder._calculate_date_planned_finished(start_date)
|
||||
values['date_planned_finished'] = computed_finished_time
|
||||
elif start_date and end_date:
|
||||
computed_duration = workorder._calculate_duration_expected(date_planned_start=start_date, date_planned_finished=end_date)
|
||||
if values.get('date_start') and values.get('date_finished'):
|
||||
computed_finished_time = workorder._calculate_date_finished(date_start)
|
||||
values['date_finished'] = computed_finished_time
|
||||
elif date_start and date_finished:
|
||||
computed_duration = workorder._calculate_duration_expected(date_start=date_start, date_finished=date_finished)
|
||||
values['duration_expected'] = computed_duration
|
||||
# Update MO dates if the start date of the first WO or the
|
||||
# finished date of the last WO is update.
|
||||
if workorder == workorder.production_id.workorder_ids[0] and 'date_planned_start' in values:
|
||||
if values['date_planned_start']:
|
||||
if workorder == workorder.production_id.workorder_ids[0] and 'date_start' in values:
|
||||
if values['date_start']:
|
||||
workorder.production_id.with_context(force_date=True).write({
|
||||
'date_planned_start': fields.Datetime.to_datetime(values['date_planned_start'])
|
||||
'date_start': fields.Datetime.to_datetime(values['date_start'])
|
||||
})
|
||||
if workorder == workorder.production_id.workorder_ids[-1] and 'date_planned_finished' in values:
|
||||
if values['date_planned_finished']:
|
||||
if workorder == workorder.production_id.workorder_ids[-1] and 'date_finished' in values:
|
||||
if values['date_finished']:
|
||||
workorder.production_id.with_context(force_date=True).write({
|
||||
'date_planned_finished': fields.Datetime.to_datetime(values['date_planned_finished'])
|
||||
'date_finished': fields.Datetime.to_datetime(values['date_finished'])
|
||||
})
|
||||
return super(MrpWorkorder, self).write(values)
|
||||
|
||||
@@ -503,12 +496,12 @@ class MrpWorkorder(models.Model):
|
||||
def _plan_workorder(self, replan=False):
|
||||
self.ensure_one()
|
||||
# Plan workorder after its predecessors
|
||||
start_date = max(self.production_id.date_planned_start, datetime.now())
|
||||
date_start = max(self.production_id.date_start, datetime.now())
|
||||
for workorder in self.blocked_by_workorder_ids:
|
||||
if workorder.state in ['done', 'cancel']:
|
||||
continue
|
||||
workorder._plan_workorder(replan)
|
||||
start_date = max(start_date, workorder.date_planned_finished)
|
||||
date_start = max(date_start, workorder.date_finished)
|
||||
# Plan only suitable workorders
|
||||
if self.state not in ['pending', 'waiting', 'ready']:
|
||||
return
|
||||
@@ -519,7 +512,7 @@ class MrpWorkorder(models.Model):
|
||||
return
|
||||
# Consider workcenter and alternatives
|
||||
workcenters = self.workcenter_id | self.workcenter_id.alternative_workcenter_ids
|
||||
best_finished_date = datetime.max
|
||||
best_date_finished = datetime.max
|
||||
vals = {}
|
||||
for workcenter in workcenters:
|
||||
if not workcenter.resource_calendar_id:
|
||||
@@ -529,28 +522,28 @@ class MrpWorkorder(models.Model):
|
||||
duration_expected = self.duration_expected
|
||||
else:
|
||||
duration_expected = self._get_duration_expected(alternative_workcenter=workcenter)
|
||||
from_date, to_date = workcenter._get_first_available_slot(start_date, duration_expected)
|
||||
from_date, to_date = workcenter._get_first_available_slot(date_start, duration_expected)
|
||||
# If the workcenter is unavailable, try planning on the next one
|
||||
if not from_date:
|
||||
continue
|
||||
# Check if this workcenter is better than the previous ones
|
||||
if to_date and to_date < best_finished_date:
|
||||
best_start_date = from_date
|
||||
best_finished_date = to_date
|
||||
if to_date and to_date < best_date_finished:
|
||||
best_date_start = from_date
|
||||
best_date_finished = to_date
|
||||
best_workcenter = workcenter
|
||||
vals = {
|
||||
'workcenter_id': workcenter.id,
|
||||
'duration_expected': duration_expected,
|
||||
}
|
||||
# If none of the workcenter are available, raise
|
||||
if best_finished_date == datetime.max:
|
||||
if best_date_finished == datetime.max:
|
||||
raise UserError(_('Impossible to plan the workorder. Please check the workcenter availabilities.'))
|
||||
# Create leave on chosen workcenter calendar
|
||||
leave = self.env['resource.calendar.leaves'].create({
|
||||
'name': self.display_name,
|
||||
'calendar_id': best_workcenter.resource_calendar_id.id,
|
||||
'date_from': best_start_date,
|
||||
'date_to': best_finished_date,
|
||||
'date_from': best_date_start,
|
||||
'date_to': best_date_finished,
|
||||
'resource_id': best_workcenter.resource_id.id,
|
||||
'time_type': 'other'
|
||||
})
|
||||
@@ -626,32 +619,33 @@ class MrpWorkorder(models.Model):
|
||||
})
|
||||
if self.state == 'progress':
|
||||
return True
|
||||
start_date = fields.Datetime.now()
|
||||
date_start = fields.Datetime.now()
|
||||
vals = {
|
||||
'state': 'progress',
|
||||
'date_start': start_date,
|
||||
'date_start': date_start,
|
||||
}
|
||||
if not self.leave_id:
|
||||
leave = self.env['resource.calendar.leaves'].create({
|
||||
'name': self.display_name,
|
||||
'calendar_id': self.workcenter_id.resource_calendar_id.id,
|
||||
'date_from': start_date,
|
||||
'date_to': start_date + relativedelta(minutes=self.duration_expected),
|
||||
'date_from': date_start,
|
||||
'date_to': date_start + relativedelta(minutes=self.duration_expected),
|
||||
'resource_id': self.workcenter_id.resource_id.id,
|
||||
'time_type': 'other'
|
||||
})
|
||||
vals['date_finished'] = leave.date_to
|
||||
vals['leave_id'] = leave.id
|
||||
return self.write(vals)
|
||||
else:
|
||||
if not self.date_planned_start or self.date_planned_start > start_date:
|
||||
vals['date_planned_start'] = start_date
|
||||
vals['date_planned_finished'] = self._calculate_date_planned_finished(start_date)
|
||||
if self.date_planned_finished and self.date_planned_finished < start_date:
|
||||
vals['date_planned_finished'] = start_date
|
||||
if not self.date_start or self.date_start > date_start:
|
||||
vals['date_start'] = date_start
|
||||
vals['date_finished'] = self._calculate_date_finished(date_start)
|
||||
if self.date_finished and self.date_finished < date_start:
|
||||
vals['date_finished'] = date_start
|
||||
return self.with_context(bypass_duration_calculation=True).write(vals)
|
||||
|
||||
def button_finish(self):
|
||||
end_date = datetime.now()
|
||||
date_finished = datetime.now()
|
||||
for workorder in self:
|
||||
if workorder.state in ('done', 'cancel'):
|
||||
continue
|
||||
@@ -659,14 +653,11 @@ class MrpWorkorder(models.Model):
|
||||
vals = {
|
||||
'qty_produced': workorder.qty_produced or workorder.qty_producing or workorder.qty_production,
|
||||
'state': 'done',
|
||||
'date_finished': end_date,
|
||||
'date_planned_finished': end_date,
|
||||
'date_finished': date_finished,
|
||||
'costs_hour': workorder.workcenter_id.costs_hour
|
||||
}
|
||||
if not workorder.date_start:
|
||||
vals['date_start'] = end_date
|
||||
if not workorder.date_planned_start or end_date < workorder.date_planned_start:
|
||||
vals['date_planned_start'] = end_date
|
||||
if not workorder.date_start or date_finished < workorder.date_start:
|
||||
vals['date_start'] = date_finished
|
||||
workorder.with_context(bypass_duration_calculation=True).write(vals)
|
||||
return True
|
||||
|
||||
@@ -717,7 +708,6 @@ class MrpWorkorder(models.Model):
|
||||
return self.write({
|
||||
'state': 'done',
|
||||
'date_finished': end_date,
|
||||
'date_planned_finished': end_date,
|
||||
'costs_hour': self.workcenter_id.costs_hour
|
||||
})
|
||||
|
||||
@@ -786,7 +776,7 @@ class MrpWorkorder(models.Model):
|
||||
|
||||
:return: defaultdict with key as workorder id of self and value as related conflicted workorder
|
||||
"""
|
||||
self.flush_model(['state', 'date_planned_start', 'date_planned_finished', 'workcenter_id'])
|
||||
self.flush_model(['state', 'date_start', 'date_finished', 'workcenter_id'])
|
||||
sql = """
|
||||
SELECT wo1.id, wo2.id
|
||||
FROM mrp_workorder wo1, mrp_workorder wo2
|
||||
@@ -796,8 +786,8 @@ class MrpWorkorder(models.Model):
|
||||
AND wo2.state IN ('pending', 'waiting', 'ready')
|
||||
AND wo1.id != wo2.id
|
||||
AND wo1.workcenter_id = wo2.workcenter_id
|
||||
AND (DATE_TRUNC('second', wo2.date_planned_start), DATE_TRUNC('second', wo2.date_planned_finished))
|
||||
OVERLAPS (DATE_TRUNC('second', wo1.date_planned_start), DATE_TRUNC('second', wo1.date_planned_finished))
|
||||
AND (DATE_TRUNC('second', wo2.date_start), DATE_TRUNC('second', wo2.date_finished))
|
||||
OVERLAPS (DATE_TRUNC('second', wo1.date_start), DATE_TRUNC('second', wo1.date_finished))
|
||||
"""
|
||||
self.env.cr.execute(sql, [tuple(self.ids)])
|
||||
res = defaultdict(list)
|
||||
|
||||
@@ -168,7 +168,7 @@ class ProductProduct(models.Model):
|
||||
def _compute_mrp_product_qty(self):
|
||||
date_from = fields.Datetime.to_string(fields.datetime.now() - timedelta(days=365))
|
||||
#TODO: state = done?
|
||||
domain = [('state', '=', 'done'), ('product_id', 'in', self.ids), ('date_planned_start', '>', date_from)]
|
||||
domain = [('state', '=', 'done'), ('product_id', 'in', self.ids), ('date_start', '>', date_from)]
|
||||
read_group_res = self.env['mrp.production']._read_group(domain, ['product_id', 'product_uom_qty'], ['product_id'])
|
||||
mapped_data = dict([(data['product_id'][0], data['product_uom_qty']) for data in read_group_res])
|
||||
for product in self:
|
||||
|
||||
@@ -285,7 +285,7 @@ class StockMove(models.Model):
|
||||
continue
|
||||
# produced products + byproducts
|
||||
values['location_id'] = mo.production_location_id.id
|
||||
values['date'] = mo._get_date_planned_finished()
|
||||
values['date'] = mo.date_finished
|
||||
values['date_deadline'] = mo.date_deadline
|
||||
if not values.get('location_dest_id'):
|
||||
values['location_dest_id'] = mo.location_dest_id.id
|
||||
|
||||
@@ -36,7 +36,7 @@ class StockPickingType(models.Model):
|
||||
domains = {
|
||||
'count_mo_waiting': [('reservation_state', '=', 'waiting')],
|
||||
'count_mo_todo': ['|', ('state', 'in', ('confirmed', 'draft', 'progress', 'to_close')), ('is_planned', '=', True)],
|
||||
'count_mo_late': [('date_planned_start', '<', fields.Date.today()), ('state', '=', 'confirmed')],
|
||||
'count_mo_late': [('date_start', '<', fields.Date.today()), ('state', '=', 'confirmed')],
|
||||
}
|
||||
for field in domains:
|
||||
data = self.env['mrp.production']._read_group(domains[field] +
|
||||
|
||||
@@ -156,8 +156,8 @@ class StockRule(models.Model):
|
||||
'location_dest_id': location_dest_id.id,
|
||||
'bom_id': bom.id,
|
||||
'date_deadline': date_deadline,
|
||||
'date_planned_start': date_planned,
|
||||
'date_planned_finished': fields.Datetime.from_string(values['date_planned']),
|
||||
'date_start': date_planned,
|
||||
'date_finished': fields.Datetime.from_string(values['date_planned']),
|
||||
'procurement_group_id': False,
|
||||
'propagate_cancel': self.propagate_cancel,
|
||||
'orderpoint_id': values.get('orderpoint_id', False) and values.get('orderpoint_id').id,
|
||||
|
||||
@@ -332,7 +332,7 @@ class MrpProduction(models.Model):
|
||||
picking_type = self.env['stock.picking.type'].browse(values['picking_type_id'])
|
||||
return picking_type.default_location_dest_id.id
|
||||
|
||||
def get_date_planned_start(values, counter, random):
|
||||
def get_date_start(values, counter, random):
|
||||
# 95.45 % of picking scheduled between (-10, 30) days and follow a gauss distribution (only +-15% picking is late)
|
||||
delta = random.gauss(10, 10)
|
||||
return now + timedelta(days=delta)
|
||||
@@ -345,7 +345,7 @@ class MrpProduction(models.Model):
|
||||
('product_uom_id', populate.compute(get_product_uom_id)),
|
||||
('product_qty', populate.randint(1, 10)),
|
||||
('picking_type_id', populate.compute(get_picking_type_id)),
|
||||
('date_planned_start', populate.compute(get_date_planned_start)),
|
||||
('date_start', populate.compute(get_date_start)),
|
||||
('location_src_id', populate.compute(get_location_src_id)),
|
||||
('location_dest_id', populate.compute(get_location_dest_id)),
|
||||
('priority', populate.iterate(['0', '1'], [0.95, 0.05])),
|
||||
@@ -399,7 +399,7 @@ class StockMove(models.Model):
|
||||
values['location_dest_id'] = production.production_location_id.id
|
||||
values['picking_type_id'] = production.picking_type_id.id
|
||||
values['name'] = production.name
|
||||
values['date'] = production.date_planned_start
|
||||
values['date'] = production.date_start
|
||||
values['company_id'] = production.company_id.id
|
||||
yield values
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ class ReportMoOverview(models.AbstractModel):
|
||||
'quantity_free': product.uom_id._compute_quantity(product.free_qty, production.product_uom_id) if product.type == 'product' else False,
|
||||
'quantity_on_hand': product.uom_id._compute_quantity(product.qty_available, production.product_uom_id) if product.type == 'product' else False,
|
||||
'quantity_reserved': 0.0,
|
||||
'receipt': self._check_planned_start(production.date_planned_start, self._get_replenishment_receipt(production, components)),
|
||||
'receipt': self._check_planned_start(production.date_start, self._get_replenishment_receipt(production, components)),
|
||||
'mo_cost': company.currency_id.round(mo_cost + operations.get('summary', {}).get('mo_cost', 0.0)),
|
||||
'product_cost': company.currency_id.round(product.standard_price * production.product_uom_qty),
|
||||
'currency_id': company.currency_id.id,
|
||||
@@ -202,7 +202,7 @@ class ReportMoOverview(models.AbstractModel):
|
||||
'quantity_free': product.uom_id._compute_quantity(product.free_qty, move_raw.product_uom) if product.type == 'product' else False,
|
||||
'quantity_on_hand': product.uom_id._compute_quantity(product.qty_available, move_raw.product_uom) if product.type == 'product' else False,
|
||||
'quantity_reserved': self._get_reserved_qty(move_raw, production.warehouse_id, replenish_data),
|
||||
'receipt': self._check_planned_start(production.date_planned_start, self._get_component_receipt(product, move_raw, production.warehouse_id, replenishments, replenish_data)),
|
||||
'receipt': self._check_planned_start(production.date_start, self._get_component_receipt(product, move_raw, production.warehouse_id, replenishments, replenish_data)),
|
||||
'mo_cost': company.currency_id.round(mo_cost if replenishments else product_cost),
|
||||
'product_cost': company.currency_id.round(product_cost),
|
||||
'currency_id': company.currency_id.id,
|
||||
@@ -281,7 +281,7 @@ class ReportMoOverview(models.AbstractModel):
|
||||
replenishment['components'] = self._get_components_data(doc_in, replenish_data, level + 2, replenishment_index)
|
||||
replenishment['operations'] = self._get_operations_data(doc_in, level + 2, replenishment_index)
|
||||
replenishment['summary']['mo_cost'] = currency.round(sum(component.get('summary', {}).get('mo_cost', 0.0) for component in replenishment['components']) + replenishment['operations'].get('summary', {}).get('mo_cost', 0.0))
|
||||
replenishment['summary']['receipt'] = self._check_planned_start(production.date_planned_start, self._get_replenishment_receipt(doc_in, replenishment.get('components', [])))
|
||||
replenishment['summary']['receipt'] = self._check_planned_start(production.date_start, self._get_replenishment_receipt(doc_in, replenishment.get('components', [])))
|
||||
replenishments.append(replenishment)
|
||||
total_ordered += replenishment['summary']['quantity']
|
||||
|
||||
@@ -295,7 +295,7 @@ class ReportMoOverview(models.AbstractModel):
|
||||
rules_delay = sum(rule.delay for rule in resupply_rules)
|
||||
resupply_data = self._get_resupply_data(resupply_rules, rules_delay, missing_quantity, move_raw.product_uom, product, production.warehouse_id)
|
||||
if resupply_data:
|
||||
receipt = self._check_planned_start(production.date_planned_start, self._format_receipt_date('estimated', fields.datetime.today() + timedelta(days=resupply_data['delay'])))
|
||||
receipt = self._check_planned_start(production.date_start, self._format_receipt_date('estimated', fields.datetime.today() + timedelta(days=resupply_data['delay'])))
|
||||
else:
|
||||
receipt = self._format_receipt_date('unavailable')
|
||||
|
||||
@@ -317,7 +317,7 @@ class ReportMoOverview(models.AbstractModel):
|
||||
}}
|
||||
if resupply_data:
|
||||
to_order_line['mo_cost'] = currency.round(resupply_data['cost'] * move_raw.product_uom._compute_quantity(missing_quantity, product.uom_id))
|
||||
to_order_line['receipt'] = self._check_planned_start(production.date_planned_start, self._format_receipt_date('estimated', fields.datetime.today() + timedelta(days=resupply_data['delay']))),
|
||||
to_order_line['receipt'] = self._check_planned_start(production.date_start, self._format_receipt_date('estimated', fields.datetime.today() + timedelta(days=resupply_data['delay']))),
|
||||
else:
|
||||
to_order_line['mo_cost'] = currency.round(product.standard_price * move_raw.product_uom._compute_quantity(missing_quantity, product.uom_id))
|
||||
to_order_line['receipt'] = self._format_receipt_date('unavailable')
|
||||
@@ -330,7 +330,7 @@ class ReportMoOverview(models.AbstractModel):
|
||||
|
||||
def _get_replenishment_receipt(self, doc_in, components):
|
||||
if doc_in._name == 'mrp.production':
|
||||
max_date_start = doc_in.date_planned_start
|
||||
max_date_start = doc_in.date_start
|
||||
all_available = True
|
||||
some_unavailable = False
|
||||
some_estimated = False
|
||||
@@ -344,7 +344,7 @@ class ReportMoOverview(models.AbstractModel):
|
||||
if some_unavailable:
|
||||
return self._format_receipt_date('unavailable')
|
||||
if all_available:
|
||||
return self._format_receipt_date('expected', doc_in.date_planned_finished)
|
||||
return self._format_receipt_date('expected', doc_in.date_finished)
|
||||
|
||||
new_date = max_date_start + timedelta(days=doc_in.product_id.produce_delay)
|
||||
receipt_state = 'estimated' if some_estimated else 'expected'
|
||||
|
||||
@@ -33,7 +33,7 @@ class ReceptionReport(models.AbstractModel):
|
||||
|
||||
def _get_formatted_scheduled_date(self, source):
|
||||
if source._name == 'mrp.production':
|
||||
return format_date(self.env, source.date_planned_start)
|
||||
return format_date(self.env, source.date_start)
|
||||
return super()._get_formatted_scheduled_date(source)
|
||||
|
||||
def _action_assign(self, in_move, out_move):
|
||||
|
||||
@@ -391,13 +391,13 @@ class TestMrpProductionBackorder(TestMrpCommon):
|
||||
The MO and the backorder should be assigned according to the reservation method
|
||||
defined in the default manufacturing operation type
|
||||
"""
|
||||
def create_mo(date_planned_start=False):
|
||||
def create_mo(date_start=False):
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.product_id = self.bom_1.product_id
|
||||
mo_form.bom_id = self.bom_1
|
||||
mo_form.product_qty = 2
|
||||
if date_planned_start:
|
||||
mo_form.date_planned_start = date_planned_start
|
||||
if date_start:
|
||||
mo_form.date_start = date_start
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
return mo
|
||||
|
||||
@@ -57,14 +57,14 @@ class TestMrpOrder(TestMrpCommon):
|
||||
'inventory_quantity': 500
|
||||
}).action_apply_inventory()
|
||||
|
||||
test_date_planned = fields.Datetime.now() - timedelta(days=1)
|
||||
date_start = fields.Datetime.now() - timedelta(days=1)
|
||||
test_quantity = 3.0
|
||||
man_order_form = Form(self.env['mrp.production'].with_user(self.user_mrp_user))
|
||||
man_order_form.product_id = self.product_4
|
||||
man_order_form.bom_id = self.bom_1
|
||||
man_order_form.product_uom_id = self.product_4.uom_id
|
||||
man_order_form.product_qty = test_quantity
|
||||
man_order_form.date_planned_start = test_date_planned
|
||||
man_order_form.date_start = date_start
|
||||
man_order_form.location_src_id = self.location_1
|
||||
man_order_form.location_dest_id = self.warehouse_1.wh_output_stock_loc_id
|
||||
man_order = man_order_form.save()
|
||||
@@ -75,7 +75,7 @@ class TestMrpOrder(TestMrpCommon):
|
||||
|
||||
# check production move
|
||||
production_move = man_order.move_finished_ids
|
||||
self.assertAlmostEqual(production_move.date, test_date_planned + timedelta(hours=1), delta=timedelta(seconds=10))
|
||||
self.assertAlmostEqual(production_move.date, date_start + timedelta(hours=1), delta=timedelta(seconds=10))
|
||||
self.assertEqual(production_move.product_id, self.product_4)
|
||||
self.assertEqual(production_move.product_uom, man_order.product_uom_id)
|
||||
self.assertEqual(production_move.product_qty, man_order.product_qty)
|
||||
@@ -84,7 +84,7 @@ class TestMrpOrder(TestMrpCommon):
|
||||
|
||||
# check consumption moves
|
||||
for move in man_order.move_raw_ids:
|
||||
self.assertEqual(move.date, test_date_planned)
|
||||
self.assertEqual(move.date, date_start)
|
||||
first_move = man_order.move_raw_ids.filtered(lambda move: move.product_id == self.product_2)
|
||||
self.assertEqual(first_move.product_qty, test_quantity / self.bom_1.product_qty * self.product_4.uom_id.factor_inv * 2)
|
||||
first_move = man_order.move_raw_ids.filtered(lambda move: move.product_id == self.product_1)
|
||||
@@ -485,17 +485,17 @@ class TestMrpOrder(TestMrpCommon):
|
||||
|
||||
def test_update_plan_date(self):
|
||||
"""Editing the scheduled date after planning the MO should unplan the MO, and adjust the date on the stock moves"""
|
||||
planned_date = datetime(2023, 5, 15, 9, 0)
|
||||
date_start = datetime(2023, 5, 15, 9, 0)
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.product_id = self.product_4
|
||||
mo_form.bom_id = self.bom_1
|
||||
mo_form.product_qty = 1
|
||||
mo_form.date_planned_start = planned_date
|
||||
mo_form.date_start = date_start
|
||||
mo = mo_form.save()
|
||||
self.assertEqual(mo.move_finished_ids[0].date, datetime(2023, 5, 15, 10, 0))
|
||||
mo.action_confirm()
|
||||
mo.button_plan()
|
||||
mo.date_planned_start = datetime(2024, 5, 15, 9, 0)
|
||||
mo.date_start = datetime(2024, 5, 15, 9, 0)
|
||||
self.assertEqual(mo.move_finished_ids[0].date, datetime(2024, 5, 15, 10, 0))
|
||||
|
||||
def test_rounding(self):
|
||||
@@ -1346,7 +1346,7 @@ class TestMrpOrder(TestMrpCommon):
|
||||
self.assertEqual(move.propagate_cancel, mo.propagate_cancel)
|
||||
self.assertFalse(move.raw_material_production_id)
|
||||
self.assertEqual(move.location_id, mo.production_location_id)
|
||||
self.assertEqual(move.date, mo._get_date_planned_finished())
|
||||
self.assertEqual(move.date, mo.date_finished)
|
||||
self.assertEqual(move.date_deadline, mo.date_deadline)
|
||||
|
||||
mo.move_raw_ids |= move
|
||||
@@ -1580,7 +1580,7 @@ class TestMrpOrder(TestMrpCommon):
|
||||
now = fields.Datetime.now()
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.bom_id = self.bom_3 # product_5 (2), product_4 (8), product_2 (12)
|
||||
mo_form.date_planned_start = now
|
||||
mo_form.date_start = now
|
||||
mo = mo_form.save()
|
||||
self.assertEqual(mo.components_availability, False) # no compute for draft
|
||||
mo.action_confirm()
|
||||
@@ -1607,7 +1607,7 @@ class TestMrpOrder(TestMrpCommon):
|
||||
self.assertEqual(mo.components_availability, f'Exp {format_date(self.env, tommorrow)}')
|
||||
self.assertEqual(mo.components_availability_state, 'late')
|
||||
|
||||
mo.date_planned_start = after_tommorrow
|
||||
mo.date_start = after_tommorrow
|
||||
|
||||
self.assertEqual(mo.components_availability, f'Exp {format_date(self.env, tommorrow)}')
|
||||
self.assertEqual(mo.components_availability_state, 'expected')
|
||||
@@ -1959,10 +1959,10 @@ class TestMrpOrder(TestMrpCommon):
|
||||
})
|
||||
|
||||
# Next Monday at 6:00 am UTC
|
||||
date_planned = (fields.Datetime.now() + timedelta(days=7 - fields.Datetime.now().weekday())).replace(hour=6, minute=0, second=0)
|
||||
date_start = (fields.Datetime.now() + timedelta(days=7 - fields.Datetime.now().weekday())).replace(hour=6, minute=0, second=0)
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.bom_id = bom
|
||||
mo_form.date_planned_start = date_planned
|
||||
mo_form.date_start = date_start
|
||||
mo = mo_form.save()
|
||||
|
||||
mo.workorder_ids[0].duration_expected = 240
|
||||
@@ -1974,11 +1974,11 @@ class TestMrpOrder(TestMrpCommon):
|
||||
# Asia/Bangkok is UTC+7 and the start date is on Monday at 06:00 UTC (i.e., 13:00 UTC+7).
|
||||
# So, in Bangkok, the first workorder uses the entire Monday afternoon slot 13:00 - 17:00 UTC+7 (i.e., 06:00 - 10:00 UTC)
|
||||
# The second job uses the beginning of the Tuesday morning slot: 08:00 - 09:00 UTC+7 (i.e., 01:00 - 02:00 UTC)
|
||||
self.assertEqual(mo.workorder_ids[0].date_planned_start, date_planned)
|
||||
self.assertEqual(mo.workorder_ids[0].date_planned_finished, date_planned + timedelta(hours=4))
|
||||
tuesday = date_planned + timedelta(days=1)
|
||||
self.assertEqual(mo.workorder_ids[1].date_planned_start, tuesday.replace(hour=1))
|
||||
self.assertEqual(mo.workorder_ids[1].date_planned_finished, tuesday.replace(hour=2))
|
||||
self.assertEqual(mo.workorder_ids[0].date_start, date_start)
|
||||
self.assertEqual(mo.workorder_ids[0].date_finished, date_start + timedelta(hours=4))
|
||||
tuesday = date_start + timedelta(days=1)
|
||||
self.assertEqual(mo.workorder_ids[1].date_start, tuesday.replace(hour=1))
|
||||
self.assertEqual(mo.workorder_ids[1].date_finished, tuesday.replace(hour=2))
|
||||
|
||||
def test_backorder_with_overconsumption(self):
|
||||
""" Check that the components of the backorder have the correct quantities
|
||||
@@ -2838,7 +2838,7 @@ class TestMrpOrder(TestMrpCommon):
|
||||
mo_form = Form(self.env['mrp.production'])
|
||||
mo_form.bom_id = self.bom_4
|
||||
with mo_form.workorder_ids.edit(0) as wo_line:
|
||||
wo_line.date_planned_start = datetime.now()
|
||||
wo_line.date_start = datetime.now()
|
||||
mos += mo_form.save()
|
||||
mos.action_confirm()
|
||||
|
||||
@@ -2853,7 +2853,7 @@ class TestMrpOrder(TestMrpCommon):
|
||||
|
||||
self.assertFalse(wo_01.show_json_popover)
|
||||
self.assertFalse(wo_02.show_json_popover)
|
||||
self.assertEqual(wo_01.date_planned_finished, wo_02.date_planned_start)
|
||||
self.assertEqual(wo_01.date_finished, wo_02.date_start)
|
||||
|
||||
@freeze_time('2022-06-28 08:00')
|
||||
def test_replan_workorders02(self):
|
||||
@@ -2876,7 +2876,7 @@ class TestMrpOrder(TestMrpCommon):
|
||||
for mo in mos:
|
||||
with Form(mo) as mo_form:
|
||||
with mo_form.workorder_ids.edit(0) as wo_line:
|
||||
wo_line.date_planned_start = datetime.now()
|
||||
wo_line.date_start = datetime.now()
|
||||
|
||||
wo_01 = mo_01.workorder_ids
|
||||
wo_02 = mo_02.workorder_ids
|
||||
@@ -2887,7 +2887,7 @@ class TestMrpOrder(TestMrpCommon):
|
||||
|
||||
self.assertFalse(wo_01.show_json_popover)
|
||||
self.assertFalse(wo_02.show_json_popover)
|
||||
self.assertEqual(wo_01.date_planned_finished, wo_02.date_planned_start)
|
||||
self.assertEqual(wo_01.date_finished, wo_02.date_start)
|
||||
|
||||
@freeze_time('2022-10-05 12:00')
|
||||
def test_replan_mo_without_bom(self):
|
||||
@@ -2930,27 +2930,27 @@ class TestMrpOrder(TestMrpCommon):
|
||||
with mo_01_form.workorder_ids.new() as workorder:
|
||||
workorder.name = "OP2"
|
||||
workorder.workcenter_id = self.workcenter_3
|
||||
workorder.date_planned_start = datetime(2022, 10, 23, 12)
|
||||
workorder.date_start = datetime(2022, 10, 23, 12)
|
||||
mo_01 = mo_01_form.save()
|
||||
mo_01.action_confirm()
|
||||
|
||||
op_1, op_2 = mo_01.workorder_ids
|
||||
self.assertEqual(op_2.date_planned_start, datetime(2022, 10, 23, 12))
|
||||
self.assertEqual(op_2.date_start, datetime(2022, 10, 23, 12))
|
||||
|
||||
with Form(mo_01) as mo_01_form:
|
||||
with mo_01_form.workorder_ids.edit(0) as workorder:
|
||||
workorder.date_planned_start = datetime(2022, 10, 18, 12)
|
||||
workorder.date_start = datetime(2022, 10, 18, 12)
|
||||
mo_01 = mo_01_form.save()
|
||||
|
||||
self.assertEqual(op_1.date_planned_start, datetime(2022, 10, 18, 12))
|
||||
self.assertEqual(op_1.date_planned_finished, op_2.date_planned_start)
|
||||
self.assertEqual(op_1.date_start, datetime(2022, 10, 18, 12))
|
||||
self.assertEqual(op_1.date_finished, op_2.date_start)
|
||||
|
||||
#Second MO
|
||||
with Form(mo_02) as mo_02_form:
|
||||
with mo_02_form.workorder_ids.new() as workorder:
|
||||
workorder.name = "OP1"
|
||||
workorder.workcenter_id = self.workcenter_2
|
||||
workorder.date_planned_start = datetime(2022, 10, 20, 12)
|
||||
workorder.date_start = datetime(2022, 10, 20, 12)
|
||||
mo_02 = mo_02_form.save()
|
||||
mo_02.action_confirm()
|
||||
|
||||
@@ -2958,11 +2958,11 @@ class TestMrpOrder(TestMrpCommon):
|
||||
with mo_02_form.workorder_ids.new() as workorder:
|
||||
workorder.name = "OP2"
|
||||
workorder.workcenter_id = self.workcenter_3
|
||||
workorder.date_planned_start = datetime(2022, 10, 18, 12)
|
||||
workorder.date_start = datetime(2022, 10, 18, 12)
|
||||
mo_02 = mo_02_form.save()
|
||||
|
||||
op_1, op_2 = mo_02.workorder_ids
|
||||
self.assertEqual(op_1.date_planned_start, datetime(2022, 10, 20, 12))
|
||||
self.assertEqual(op_1.date_start, datetime(2022, 10, 20, 12))
|
||||
self.assertTrue(op_2.show_json_popover)
|
||||
|
||||
@freeze_time('2023-03-01 12:00')
|
||||
@@ -3014,9 +3014,9 @@ class TestMrpOrder(TestMrpCommon):
|
||||
mo = mo_form.save()
|
||||
mo.action_confirm()
|
||||
mo.button_plan()
|
||||
self.assertEqual(mo.workorder_ids[0].date_planned_start, datetime(2023, 3, 1, 12, 0))
|
||||
self.assertEqual(mo.workorder_ids[1].date_planned_start, datetime(2023, 3, 1, 13, 15))
|
||||
self.assertEqual(mo.workorder_ids[2].date_planned_start, datetime(2023, 3, 1, 14, 30))
|
||||
self.assertEqual(mo.workorder_ids[0].date_start, datetime(2023, 3, 1, 12, 0))
|
||||
self.assertEqual(mo.workorder_ids[1].date_start, datetime(2023, 3, 1, 13, 15))
|
||||
self.assertEqual(mo.workorder_ids[2].date_start, datetime(2023, 3, 1, 14, 30))
|
||||
|
||||
# wo_1 completely finished
|
||||
mo_form = Form(mo)
|
||||
@@ -3041,9 +3041,9 @@ class TestMrpOrder(TestMrpCommon):
|
||||
self.assertEqual(mo_backorder.workorder_ids[0].state, 'cancel')
|
||||
self.assertEqual(mo_backorder.workorder_ids[1].state, 'waiting')
|
||||
self.assertEqual(mo_backorder.workorder_ids[2].state, 'pending')
|
||||
self.assertFalse(mo_backorder.workorder_ids[0].date_planned_start)
|
||||
self.assertEqual(mo_backorder.workorder_ids[1].date_planned_start, datetime(2023, 3, 1, 12, 0))
|
||||
self.assertEqual(mo_backorder.workorder_ids[2].date_planned_start, datetime(2023, 3, 1, 12, 45))
|
||||
self.assertFalse(mo_backorder.workorder_ids[0].date_start)
|
||||
self.assertEqual(mo_backorder.workorder_ids[1].date_start, datetime(2023, 3, 1, 12, 0))
|
||||
self.assertEqual(mo_backorder.workorder_ids[2].date_start, datetime(2023, 3, 1, 12, 45))
|
||||
|
||||
def test_compute_product_id(self):
|
||||
"""
|
||||
|
||||
@@ -249,11 +249,11 @@ class TestProcurement(TestMrpCommon):
|
||||
self.assertEqual(len(move_orig), 1, 'the move orig is not created')
|
||||
self.assertEqual(move_orig.product_qty, 10, 'the quantity to produce is not good relative to the move')
|
||||
|
||||
new_sheduled_date = fields.Datetime.to_datetime(mo.date_planned_start) + timedelta(days=5)
|
||||
mo.date_planned_start = new_sheduled_date
|
||||
new_date_start = fields.Datetime.to_datetime(mo.date_start) + timedelta(days=5)
|
||||
mo.date_start = new_date_start
|
||||
|
||||
self.assertAlmostEqual(mo.move_raw_ids.date, mo.date_planned_start, delta=timedelta(seconds=1))
|
||||
self.assertAlmostEqual(mo.move_finished_ids.date, mo.date_planned_finished, delta=timedelta(seconds=1))
|
||||
self.assertAlmostEqual(mo.move_raw_ids.date, mo.date_start, delta=timedelta(seconds=1))
|
||||
self.assertAlmostEqual(mo.move_finished_ids.date, mo.date_finished, delta=timedelta(seconds=1))
|
||||
|
||||
def test_finished_move_cancellation(self):
|
||||
"""Check state of finished move on cancellation of raw moves. """
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<field name="priority" optional="show" widget="priority" nolabel="1"/>
|
||||
<field name="message_needaction" invisible="1"/>
|
||||
<field name="name" decoration-bf="1"/>
|
||||
<field name="date_planned_start" optional="show" widget="remaining_days"/>
|
||||
<field name="date_start" optional="show" widget="remaining_days"/>
|
||||
<field name="date_deadline" widget="remaining_days" attrs="{'invisible': [('state', 'in', ['done', 'cancel'])]}" optional="hide"/>
|
||||
<field name="product_id" readonly="1" optional="show"/>
|
||||
<field name="lot_producing_id" optional="hide"/>
|
||||
@@ -41,8 +41,8 @@
|
||||
<field name="reservation_state" optional="hide" decoration-danger="reservation_state == 'confirmed'" decoration-success="reservation_state == 'assigned'"/>
|
||||
<field name="product_qty" sum="Total Qty" string="Quantity" readonly="1" optional="show"/>
|
||||
<field name="product_uom_id" string="UoM" readonly="1" options="{'no_open':True,'no_create':True}" groups="uom.group_uom" optional="show"/>
|
||||
<field name="production_duration_expected" attrs="{'invisible': [('production_duration_expected', '=', 0)]}" groups="mrp.group_mrp_routings" widget="float_time" sum="Total expected duration" optional="show"/>
|
||||
<field name="production_real_duration" attrs="{'invisible': [('production_real_duration', '=', 0)]}" groups="mrp.group_mrp_routings" widget="float_time" sum="Total real duration" optional="show"/>
|
||||
<field name="duration_expected" attrs="{'invisible': [('duration_expected', '=', 0)]}" groups="mrp.group_mrp_routings" widget="float_time" sum="Total expected duration" optional="show"/>
|
||||
<field name="duration" attrs="{'invisible': [('duration', '=', 0)]}" groups="mrp.group_mrp_routings" widget="float_time" sum="Total real duration" optional="show"/>
|
||||
<field name="company_id" readonly="1" groups="base.group_multi_company" optional="show"/>
|
||||
<field name="state"
|
||||
decoration-success="state in ('done', 'to_close')"
|
||||
@@ -138,7 +138,7 @@
|
||||
</header>
|
||||
<sheet>
|
||||
<field name="reservation_state" invisible="1"/>
|
||||
<field name="date_planned_finished" invisible="1"/>
|
||||
<field name="date_finished" invisible="1"/>
|
||||
<field name="is_locked" invisible="1"/>
|
||||
<field name="qty_produced" invisible="1"/>
|
||||
<field name="unreserve_visible" invisible="1"/>
|
||||
@@ -239,13 +239,14 @@
|
||||
</div>
|
||||
</group>
|
||||
<group name="group_extra_info">
|
||||
<label for="date_planned_start"/>
|
||||
<label for="date_start" string="Scheduled Date" attrs="{'invisible': [('state', 'in', ['progress', 'to_close', 'done', 'cancel'])]}"/>
|
||||
<label for="date_start" string="Start Date" attrs="{'invisible': [('state', 'not in', ['progress', 'to_close', 'done', 'cancel'])]}"/>
|
||||
<div class="o_row">
|
||||
<field name="date_planned_start"
|
||||
attrs="{'readonly': [('state', 'in', ['close', 'cancel'])]}"
|
||||
decoration-warning="state not in ('done', 'cancel') and date_planned_start < now"
|
||||
decoration-danger="state not in ('done', 'cancel') and date_planned_start < current_date"
|
||||
decoration-bf="state not in ('done', 'cancel') and (date_planned_start < current_date or date_planned_start < now)"/>
|
||||
<field name="date_start"
|
||||
attrs="{'readonly': [('state', 'in', ['done', 'to_close', 'cancel'])]}"
|
||||
decoration-warning="state not in ('done', 'cancel') and date_start < now"
|
||||
decoration-danger="state not in ('done', 'cancel') and date_start < current_date"
|
||||
decoration-bf="state not in ('done', 'cancel') and (date_start < current_date or date_start < now)"/>
|
||||
<field name="delay_alert_date" invisible="1"/>
|
||||
<field nolabel="1" name="json_popover" widget="stock_rescheduling_popover" attrs="{'invisible': [('json_popover', '=', False)]}"/>
|
||||
</div>
|
||||
@@ -308,7 +309,7 @@
|
||||
<page string="Components" name="components">
|
||||
<field name="move_raw_ids"
|
||||
widget="mrp_production_components_x2many"
|
||||
context="{'default_date': date_planned_start, 'default_date_deadline': date_planned_start, 'default_location_id': location_src_id, 'default_location_dest_id': production_location_id,
|
||||
context="{'default_date': date_start, 'default_date_deadline': date_start, 'default_location_id': location_src_id, 'default_location_dest_id': production_location_id,
|
||||
'default_warehouse_id': warehouse_id, 'default_state': 'draft', 'default_raw_material_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}"
|
||||
attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}" options="{'delete': [('state', '=', 'draft')]}">
|
||||
<tree default_order="is_done, manual_consumption desc, sequence" editable="bottom">
|
||||
@@ -378,7 +379,7 @@
|
||||
<field name="workorder_ids" attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}" context="{'tree_view_ref': 'mrp.mrp_production_workorder_tree_editable_view', 'default_product_uom_id': product_uom_id, 'from_manufacturing_order': True}"/>
|
||||
</page>
|
||||
<page string="By-Products" name="finished_products" groups="mrp.group_mrp_byproducts">
|
||||
<field name="move_byproduct_ids" context="{'default_date': date_planned_finished, 'default_date_deadline': date_deadline, 'default_location_id': production_location_id, 'default_location_dest_id': location_dest_id, 'default_state': 'draft', 'default_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}" attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}" options="{'delete': [('state', '=', 'draft')]}">
|
||||
<field name="move_byproduct_ids" context="{'default_date': date_finished, 'default_date_deadline': date_deadline, 'default_location_id': production_location_id, 'default_location_dest_id': location_dest_id, 'default_state': 'draft', 'default_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}" attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&', ('state', '=', 'done'), ('is_locked', '=', True)]}" options="{'delete': [('state', '=', 'draft')]}">
|
||||
<tree default_order="is_done,sequence" decoration-muted="is_done" editable="bottom">
|
||||
<field name="product_id" context="{'default_detailed_type': 'product'}" domain="[('id', '!=', parent.product_id)]" required="1"/>
|
||||
<field name="location_dest_id" string="To" readonly="1" force_save="1" groups="stock.group_stock_multi_locations"/>
|
||||
@@ -457,7 +458,7 @@
|
||||
<field name="product_id"/>
|
||||
<field name="product_qty"/>
|
||||
<field name="product_uom_id" options="{'no_open':True,'no_create':True}"/>
|
||||
<field name="date_planned_start"/>
|
||||
<field name="date_start"/>
|
||||
<field name="state"/>
|
||||
<field name="activity_state"/>
|
||||
<progressbar field="activity_state" colors='{"planned": "success", "today": "warning", "overdue": "danger"}'/>
|
||||
@@ -473,7 +474,7 @@
|
||||
</div>
|
||||
<div class="o_kanban_record_bottom">
|
||||
<div class="oe_kanban_bottom_left text-muted">
|
||||
<span><t t-esc="record.name.value"/> <t t-esc="record.date_planned_start.value and record.date_planned_start.value.split(' ')[0] or False"/></span>
|
||||
<span><t t-esc="record.name.value"/> <t t-esc="record.date_start.value and record.date_start.value.split(' ')[0] or False"/></span>
|
||||
<field name="activity_ids" widget="kanban_activity"/>
|
||||
<field name="delay_alert_date" invisible="1"/>
|
||||
<field nolabel="1" name="json_popover" widget="stock_rescheduling_popover" attrs="{'invisible': [('json_popover', '=', False)]}"/>
|
||||
@@ -494,7 +495,7 @@
|
||||
<field name="model">mrp.production</field>
|
||||
<field eval="2" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<calendar date_start="date_planned_start" date_stop="date_planned_finished"
|
||||
<calendar date_start="date_start" date_stop="date_finished"
|
||||
string="Manufacturing Orders" event_limit="5" quick_add="False">
|
||||
<field name="user_id" avatar_field="avatar_128"/>
|
||||
<field name="product_id"/>
|
||||
@@ -508,7 +509,7 @@
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Manufacturing Orders" sample="1">
|
||||
<field name="date_planned_start" type="row"/>
|
||||
<field name="date_start" type="row"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
@@ -518,7 +519,7 @@
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Manufacturing Orders" stacked="0" sample="1">
|
||||
<field name="date_planned_finished"/>
|
||||
<field name="date_finished"/>
|
||||
<field name="product_uom_qty" type="measure"/>
|
||||
<field name="backorder_sequence" invisible="1"/>
|
||||
<field name="qty_producing" string="Quantity Produced"/>
|
||||
@@ -564,8 +565,8 @@
|
||||
domain="[('my_activity_date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
<filter invisible="1" string="Future Activities" name="activities_upcoming_all"
|
||||
domain="[('my_activity_date_deadline', '>', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
<filter name="filter_date_planned_start" string="Scheduled Date" date="date_planned_start"/>
|
||||
<filter name="filter_plan_date" invisible="1" string="Scheduled Date: Last 365 Days" domain="[('date_planned_start', '>', (datetime.datetime.now() + relativedelta(days=-365)).to_utc().strftime('%Y-%m-%d %H:%M:%S'))]"/>
|
||||
<filter name="filter_date_start" string="Date" date="date_start"/>
|
||||
<filter name="filter_plan_date" invisible="1" string="Date: Last 365 Days" domain="[('date_start', '>', (datetime.datetime.now() + relativedelta(days=-365)).to_utc().strftime('%Y-%m-%d %H:%M:%S'))]"/>
|
||||
<separator/>
|
||||
<filter string="Warnings" name="activities_exception"
|
||||
domain="[('activity_exception_decoration', '!=', False)]"/>
|
||||
@@ -574,7 +575,7 @@
|
||||
<filter string="Status" name="status" domain="[]" context="{'group_by': 'state'}"/>
|
||||
<filter string="Material Availability" name="groupby_reservation_state" domain="[]" context="{'group_by': 'reservation_state'}"/>
|
||||
<filter string="Procurement Group" name="procurement_group_id" domain="[]" context="{'group_by': 'procurement_group_id'}"/>
|
||||
<filter string="Scheduled Date" name="scheduled_date" domain="[]" context="{'group_by': 'date_planned_start'}" help="Scheduled Date by Month"/>
|
||||
<filter string="Date" name="date" domain="[]" context="{'group_by': 'date_start'}" help="Date by Month"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<filter string="Pending" name="pending" domain="[('state','=','pending')]"/>
|
||||
<filter string="In Progress" name="progress" domain="[('state','=','progress')]"/>
|
||||
<filter string="Done" name="done" domain="[('state','=', 'done')]"/>
|
||||
<filter string="Late" name="late" domain="[('date_planned_start','<=',time.strftime('%Y-%m-%d'))]"/>
|
||||
<filter string="Late" name="late" domain="[('date_start','<=',time.strftime('%Y-%m-%d'))]"/>
|
||||
<separator/>
|
||||
<filter string="Start Date" name="date_start_filter" date="date_start"/>
|
||||
<group expand="0" string="Group By...">
|
||||
@@ -80,10 +80,8 @@
|
||||
<field name="product_id" optional="show"/>
|
||||
<field name="qty_remaining" optional="show" string="Quantity"/>
|
||||
<field name="finished_lot_id" optional="hide" string="Lot/Serial"/>
|
||||
<field name="date_planned_start" optional="show"/>
|
||||
<field name="date_planned_finished" optional="hide"/>
|
||||
<field name="date_start" optional="hide" readonly="1"/>
|
||||
<field name="date_finished" optional="hide" readonly="1"/>
|
||||
<field name="date_start" optional="show" attrs="{'readonly': [('state', 'in', ['progress', 'done', 'cancel'])]}"/>
|
||||
<field name="date_finished" optional="hide" attrs="{'readonly': [('state', 'in', ['progress', 'done', 'cancel'])]}"/>
|
||||
<field name="duration_expected" widget="float_time" sum="expected duration"/>
|
||||
<field name="duration" widget="mrp_timer"
|
||||
attrs="{'invisible': [('production_state','=', 'draft')], 'readonly': [('is_user_working', '=', True)]}" sum="real duration"/>
|
||||
@@ -154,13 +152,14 @@
|
||||
<field name="product_id" invisible="1"/>
|
||||
<field name="finished_lot_id" invisible="1"/>
|
||||
<group>
|
||||
<group attrs="{'invisible': [('date_planned_start', '=', False)]}">
|
||||
<label for="date_planned_start" string="Planned Date"/>
|
||||
<group attrs="{'invisible': [('date_start', '=', False)]}">
|
||||
<label for="date_start" string="Scheduled Date" attrs="{'invisible': [('state', 'in', ['progress', 'done', 'cancel'])]}"/>
|
||||
<label for="date_start" string="Start Date" attrs="{'invisible': [('state', 'not in', ['progress', 'done', 'cancel'])]}"/>
|
||||
<div class="oe_inline">
|
||||
<field name="is_planned" invisible="1"/>
|
||||
<field name="date_planned_start" class="mr8 oe_inline" attrs="{'required': [('is_planned', '=', True)]}"/>
|
||||
<field name="date_start" class="mr8 oe_inline" attrs="{'required': [('is_planned', '=', True)]}"/>
|
||||
<strong class="mr8 oe_inline">to</strong>
|
||||
<field name="date_planned_finished" class="oe_inline" attrs="{'required': [('is_planned', '=', True)]}"/>
|
||||
<field name="date_finished" class="oe_inline" attrs="{'required': [('is_planned', '=', True)]}"/>
|
||||
<field name="show_json_popover" invisible="1"/>
|
||||
<field name="json_popover" widget="mrp_workorder_popover" class="oe_inline mx-2" attrs="{'invisible': [('show_json_popover', '=', False)]}"/>
|
||||
</div>
|
||||
@@ -232,8 +231,8 @@
|
||||
<field name="name" string="Operation"/>
|
||||
<field name="company_id" optional="hide" groups="base.group_multi_company"/>
|
||||
<field name="workcenter_id"/>
|
||||
<field name="date_planned_start" readonly="1"/>
|
||||
<field name="date_planned_finished" readonly="1"/>
|
||||
<field name="date_start" readonly="1"/>
|
||||
<field name="date_finished" readonly="1"/>
|
||||
<field name="duration_expected" widget="float_time" sum="expected duration"/>
|
||||
<field name="production_state" invisible="1"/>
|
||||
<field name="state" widget="badge" decoration-warning="state == 'progress'" decoration-success="state == 'done'" decoration-info="state not in ('progress', 'done', 'cancel')"
|
||||
@@ -264,13 +263,13 @@
|
||||
<filter string="Draft" name="draft" domain="[('state', '=', 'pending'), ('production_state', '=', 'draft')]"/>
|
||||
<filter string="Finished" name="finish" domain="[('state', '=', 'done')]"/>
|
||||
<separator/>
|
||||
<filter string="Late" name="late" domain="['&', ('date_planned_start', '<', current_date), ('state', '=', 'ready')]"
|
||||
<filter string="Late" name="late" domain="['&', ('date_start', '<', current_date), ('state', '=', 'ready')]"
|
||||
help="Production started late"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Work Center" name="work_center" domain="[]" context="{'group_by': 'workcenter_id'}"/>
|
||||
<filter string="Manufacturing Order" name="production" domain="[]" context="{'group_by': 'production_id'}"/>
|
||||
<filter string="Status" name="status" domain="[]" context="{'group_by': 'state'}"/>
|
||||
<filter string="Scheduled Date" name="scheduled_month" domain="[]" context="{'group_by': 'date_planned_start'}"/>
|
||||
<filter string="Date" name="scheduled_month" domain="[]" context="{'group_by': 'date_start'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
@@ -280,7 +279,7 @@
|
||||
<field name="name">mrp.production.work.order.calendar</field>
|
||||
<field name="model">mrp.workorder</field>
|
||||
<field name="arch" type="xml">
|
||||
<calendar date_stop="date_planned_finished" date_start="date_planned_start" string="Operations" color="workcenter_id" event_limit="5" delete="0" create="0">
|
||||
<calendar date_stop="date_finished" date_start="date_start" string="Operations" color="workcenter_id" event_limit="5" delete="0" create="0">
|
||||
<field name="workcenter_id" filters="1"/>
|
||||
<field name="production_id"/>
|
||||
<field name="state"/>
|
||||
@@ -319,7 +318,7 @@
|
||||
<field name="name">mrp.production.work.order.gantt.production</field>
|
||||
<field name="model">mrp.workorder</field>
|
||||
<field name="arch" type="xml">
|
||||
<gantt class="o_mrp_workorder_gantt" date_stop="date_planned_finished" date_start="date_planned_start" string="Operations" default_group_by="production_id" create="0" delete="0"
|
||||
<gantt class="o_mrp_workorder_gantt" date_stop="date_finished" date_start="date_start" string="Operations" default_group_by="production_id" create="0" delete="0"
|
||||
progress="progress" plan="0"
|
||||
decoration-danger="json_popover and 'text-danger' in json_popover"
|
||||
decoration-success="state == 'done'"
|
||||
@@ -333,7 +332,7 @@
|
||||
sample="1"
|
||||
form_view_id="%(mrp_production_workorder_form_view_inherit)d">
|
||||
|
||||
<field name="date_planned_start"/>
|
||||
<field name="date_start"/>
|
||||
<field name="state"/>
|
||||
<field name="workcenter_id"/>
|
||||
<field name="json_popover"/>
|
||||
@@ -343,8 +342,8 @@
|
||||
<div class="row g-0">
|
||||
<div class="col">
|
||||
<ul class="ps-1 mb-0 list-unstyled">
|
||||
<li><strong>Start Date: </strong> <t t-esc="date_planned_start.toFormat('F')"/></li>
|
||||
<li><strong>Stop Date: </strong> <t t-esc="date_planned_finished.toFormat('F')"/></li>
|
||||
<li><strong>Start Date: </strong> <t t-esc="date_start.toFormat('F')"/></li>
|
||||
<li><strong>Stop Date: </strong> <t t-esc="date_finished.toFormat('F')"/></li>
|
||||
<li><strong>Workcenter: </strong> <t t-esc="workcenter_id[1]"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -359,7 +358,7 @@
|
||||
<field name="name">mrp.workorder.view.gantt</field>
|
||||
<field name="model">mrp.workorder</field>
|
||||
<field name="arch" type="xml">
|
||||
<gantt class="o_mrp_workorder_gantt" date_stop="date_planned_finished" date_start="date_planned_start" string="Operations" default_group_by="workcenter_id" create="0"
|
||||
<gantt class="o_mrp_workorder_gantt" date_stop="date_finished" date_start="date_start" string="Operations" default_group_by="workcenter_id" create="0"
|
||||
delete="0" sample="1"
|
||||
progress="progress" plan="0"
|
||||
decoration-danger="json_popover and 'text-danger' in json_popover"
|
||||
@@ -373,7 +372,7 @@
|
||||
precision="{'day': 'hour:full', 'week': 'day:full', 'month': 'day:full', 'year': 'day:full'}"
|
||||
form_view_id="mrp_production_workorder_form_view_inherit">
|
||||
|
||||
<field name="date_planned_start"/>
|
||||
<field name="date_start"/>
|
||||
<field name="state"/>
|
||||
<field name="workcenter_id"/>
|
||||
<field name="json_popover"/>
|
||||
@@ -383,8 +382,8 @@
|
||||
<div class="row g-0">
|
||||
<div class="col">
|
||||
<ul class="ps-1 mb-0 list-unstyled">
|
||||
<li><strong>Start Date: </strong><t t-esc="date_planned_start.toFormat('F')"/></li>
|
||||
<li><strong>Stop Date: </strong><t t-esc="date_planned_finished.toFormat('F')"/></li>
|
||||
<li><strong>Start Date: </strong><t t-esc="date_start.toFormat('F')"/></li>
|
||||
<li><strong>Stop Date: </strong><t t-esc="date_finished.toFormat('F')"/></li>
|
||||
<li><strong>Workcenter: </strong> <t t-esc="workcenter_id[1]"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -411,7 +410,7 @@
|
||||
<field name="product_id"/>
|
||||
<field name="qty_remaining"/>
|
||||
<field name="qty_production"/>
|
||||
<field name="date_planned_start"/>
|
||||
<field name="date_start"/>
|
||||
<field name="production_date"/>
|
||||
<field name="product_uom_id" force_save="1"/>
|
||||
<field name="operation_id"/>
|
||||
@@ -425,7 +424,7 @@
|
||||
</h4>
|
||||
</div>
|
||||
<div class="o_kanban_workorder_date">
|
||||
<h5><span class="d-flex" t-esc="record.date_planned_start.value or record.production_date.value"/></h5>
|
||||
<h5><span class="d-flex" t-esc="record.date_start.value or record.production_date.value"/></h5>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="ml8">
|
||||
|
||||
@@ -48,13 +48,13 @@ class MrpProductionSplit(models.TransientModel):
|
||||
commands.append(Command.create({
|
||||
'quantity': quantity,
|
||||
'user_id': wizard.production_id.user_id,
|
||||
'date': wizard.production_id.date_planned_start,
|
||||
'date': wizard.production_id.date_start,
|
||||
}))
|
||||
remaining_quantity = float_round(remaining_quantity - quantity, precision_rounding=wizard.product_uom_id.rounding)
|
||||
commands.append(Command.create({
|
||||
'quantity': remaining_quantity,
|
||||
'user_id': wizard.production_id.user_id,
|
||||
'date': wizard.production_id.date_planned_start,
|
||||
'date': wizard.production_id.date_start,
|
||||
}))
|
||||
wizard.production_detailed_vals_ids = commands
|
||||
|
||||
@@ -69,7 +69,7 @@ class MrpProductionSplit(models.TransientModel):
|
||||
productions = self.production_id._split_productions({self.production_id: [detail.quantity for detail in self.production_detailed_vals_ids]})
|
||||
for production, detail in zip(productions, self.production_detailed_vals_ids):
|
||||
production.user_id = detail.user_id
|
||||
production.date_planned_start = detail.date
|
||||
production.date_start = detail.date
|
||||
if self.production_split_multi_id:
|
||||
saved_production_split_multi_id = self.production_split_multi_id.id
|
||||
self.production_split_multi_id.production_ids = [Command.unlink(self.id)]
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<field name="location_dest_id" ref="stock.stock_location_stock"/>
|
||||
<field name="bom_id" ref="mrp.mrp_bom_drawer"/>
|
||||
<field name="create_date" eval="(datetime.now() - relativedelta(weeks=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="date_planned_start" eval="(datetime.now() - relativedelta(weeks=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="date_start" eval="(datetime.now() - relativedelta(weeks=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
</record>
|
||||
|
||||
<record id="mrp_production_drawer_3" model="mrp.production">
|
||||
@@ -20,7 +20,7 @@
|
||||
<field name="location_dest_id" ref="stock.stock_location_stock"/>
|
||||
<field name="bom_id" ref="mrp.mrp_bom_drawer"/>
|
||||
<field name="create_date" eval="(datetime.now() - relativedelta(weeks=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="date_planned_start" eval="(datetime.now() - relativedelta(weeks=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="date_start" eval="(datetime.now() - relativedelta(weeks=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
</record>
|
||||
|
||||
<function model="mrp.production" name="action_confirm" eval="[[
|
||||
|
||||
@@ -79,8 +79,8 @@ class StockMove(models.Model):
|
||||
if move.state in ('done', 'cancel') or not move.is_subcontract:
|
||||
continue
|
||||
move.move_orig_ids.production_id.filtered(lambda p: p.state not in ('done', 'cancel')).write({
|
||||
'date_planned_finished': move.date,
|
||||
'date_planned_start': move.date,
|
||||
'date_start': move.date,
|
||||
'date_finished': move.date,
|
||||
})
|
||||
return res
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ class StockPicking(models.Model):
|
||||
'location_dest_id': subcontract_move.picking_id.partner_id.with_company(subcontract_move.company_id).property_stock_subcontractor.id,
|
||||
'product_qty': subcontract_move.product_uom_qty,
|
||||
'picking_type_id': warehouse.subcontracting_type_id.id,
|
||||
'date_planned_start': subcontract_move.date - relativedelta(days=bom.produce_delay)
|
||||
'date_start': subcontract_move.date - relativedelta(days=bom.produce_delay)
|
||||
}
|
||||
return vals
|
||||
|
||||
@@ -155,7 +155,7 @@ class StockPicking(models.Model):
|
||||
mo = self.env['mrp.production'].with_company(move.company_id).create(self._prepare_subcontract_mo_vals(move, bom))
|
||||
self.env['stock.move'].create(mo._get_moves_raw_values())
|
||||
self.env['stock.move'].create(mo._get_moves_finished_values())
|
||||
mo.date_planned_finished = move.date # Avoid to have the picking late depending of the MO
|
||||
mo.date_finished = move.date # Avoid to have the picking late depending of the MO
|
||||
mo.action_confirm()
|
||||
|
||||
# Link the finished to the receipt move.
|
||||
|
||||
@@ -80,9 +80,9 @@ class TestSaleMrpLeadTime(TestStockCommon):
|
||||
)
|
||||
|
||||
# Check schedule date and deadline of manufacturing order
|
||||
mo_scheduled = out_date - timedelta(days=manufacturing_order.bom_id.produce_delay) - timedelta(days=company.manufacturing_lead)
|
||||
mo_date_start = out_date - timedelta(days=manufacturing_order.bom_id.produce_delay) - timedelta(days=company.manufacturing_lead)
|
||||
self.assertAlmostEqual(
|
||||
fields.Datetime.from_string(manufacturing_order.date_planned_start), mo_scheduled,
|
||||
fields.Datetime.from_string(manufacturing_order.date_start), mo_date_start,
|
||||
delta=timedelta(seconds=1),
|
||||
msg="Schedule date of manufacturing order should be equal to: Schedule date of picking - product's Manufacturing Lead Time - company's Manufacturing Lead Time."
|
||||
)
|
||||
@@ -154,9 +154,9 @@ class TestSaleMrpLeadTime(TestStockCommon):
|
||||
)
|
||||
|
||||
# Check schedule date and deadline date of manufacturing order
|
||||
mo_scheduled = out_date - timedelta(days=manufacturing_order.bom_id.produce_delay) - timedelta(days=self.warehouse_1.delivery_route_id.rule_ids[0].delay) - timedelta(days=self.env.ref('base.main_company').manufacturing_lead)
|
||||
mo_date_start = out_date - timedelta(days=manufacturing_order.bom_id.produce_delay) - timedelta(days=self.warehouse_1.delivery_route_id.rule_ids[0].delay) - timedelta(days=self.env.ref('base.main_company').manufacturing_lead)
|
||||
self.assertAlmostEqual(
|
||||
fields.Datetime.from_string(manufacturing_order.date_planned_start), mo_scheduled,
|
||||
fields.Datetime.from_string(manufacturing_order.date_start), mo_date_start,
|
||||
delta=timedelta(seconds=1),
|
||||
msg="Schedule date of manufacturing order should be equal to: Schedule date of picking - product's Manufacturing Lead Time- delay pull_rule."
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
t-options="{'date_only': True}"/>
|
||||
<span t-if="mo.state in ['draft', 'confirmed', 'progress', 'to_close']"
|
||||
class="text-muted"
|
||||
t-field="mo.date_planned_finished"
|
||||
t-field="mo.date_finished"
|
||||
t-options="{'date_only': True}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user