[IMP] mrp: Move fields from product to bom
Manufacturing lead time & Days to prepare are now handled at bom level Task: 2907698 Part-of: odoo/odoo#107952
This commit is contained in:
@@ -37,6 +37,7 @@
|
||||
<field name="product_tmpl_id" ref="product.product_product_3_product_template"/>
|
||||
<field name="product_uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="sequence">1</field>
|
||||
<field name="produce_delay">10</field>
|
||||
</record>
|
||||
<record id="mrp_routing_workcenter_0" model="mrp.routing.workcenter">
|
||||
<field name="bom_id" ref="mrp_bom_manufacture"/>
|
||||
|
||||
@@ -82,6 +82,13 @@ class MrpBom(models.Model):
|
||||
allow_operation_dependencies = fields.Boolean('Operation Dependencies',
|
||||
help="Create operation level dependencies that will influence both planning and the status of work orders upon MO confirmation. If this feature is ticked, and nothing is specified, Odoo will assume that all operations can be started simultaneously."
|
||||
)
|
||||
produce_delay = fields.Float(
|
||||
'Manufacturing Lead Time', default=0.0,
|
||||
help="Average lead time in days to manufacture this product. In the case of multi-level BOM, the manufacturing lead times of the components will be added. In case the product is subcontracted, this can be used to determine the date at which components should be sent to the subcontractor.")
|
||||
days_to_prepare_mo = fields.Float(
|
||||
string="Days to prepare Manufacturing Order", default=0.0,
|
||||
help="Create and confirm Manufacturing Orders these many days in advance, to have enough time to replenish components or manufacture semi-finished products.\n"
|
||||
"Note that this does not affect the MO scheduled date, which still respects the just-in-time mechanism.")
|
||||
|
||||
_sql_constraints = [
|
||||
('qty_positive', 'check (product_qty > 0)', 'The quantity to produce must be positive!'),
|
||||
@@ -215,6 +222,14 @@ class MrpBom(models.Model):
|
||||
def name_get(self):
|
||||
return [(bom.id, '%s%s' % (bom.code and '%s: ' % bom.code or '', bom.product_tmpl_id.display_name)) for bom in self]
|
||||
|
||||
def action_compute_bom_days(self):
|
||||
company_id = self.env.context.get('default_company_id', self.env.company.id)
|
||||
warehouse = self.env['stock.warehouse'].search([('company_id', '=', company_id)], limit=1)
|
||||
for bom in self:
|
||||
bom_data = self.env['report.mrp.report_bom_structure'].with_context(minimized=True)._get_bom_data(bom, warehouse, bom.product_id, ignore_stock=True)
|
||||
availability_delay = bom_data.get('resupply_avail_delay')
|
||||
bom.days_to_prepare_mo = availability_delay - bom_data.get('lead_time', 0) if availability_delay else 0
|
||||
|
||||
@api.constrains('product_tmpl_id', 'product_id', 'type')
|
||||
def check_kit_has_not_orderpoint(self):
|
||||
product_ids = [pid for bom in self.filtered(lambda bom: bom.type == "phantom")
|
||||
|
||||
@@ -683,7 +683,7 @@ class MrpProduction(models.Model):
|
||||
for production in self:
|
||||
if not production.date_planned_start or production.is_planned:
|
||||
continue
|
||||
days_delay = production.product_id.produce_delay
|
||||
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)
|
||||
@@ -950,7 +950,7 @@ class MrpProduction(models.Model):
|
||||
}
|
||||
|
||||
def _get_date_planned_finished(self):
|
||||
date_planned_finished = self.date_planned_start + relativedelta(days=self.product_id.produce_delay)
|
||||
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
|
||||
|
||||
@@ -29,14 +29,7 @@ class ProductTemplate(models.Model):
|
||||
compute='_compute_used_in_bom_count', compute_sudo=False)
|
||||
mrp_product_qty = fields.Float('Manufactured', digits='Product Unit of Measure',
|
||||
compute='_compute_mrp_product_qty', compute_sudo=False)
|
||||
produce_delay = fields.Float(
|
||||
'Manufacturing Lead Time', default=0.0,
|
||||
help="Average lead time in days to manufacture this product. In the case of multi-level BOM, the manufacturing lead times of the components will be added. In case the product is subcontracted, this can be used to determine the date at which components should be sent to the subcontractor.")
|
||||
is_kits = fields.Boolean(compute='_compute_is_kits', compute_sudo=False)
|
||||
days_to_prepare_mo = fields.Float(
|
||||
string="Days to prepare Manufacturing Order", default=0.0,
|
||||
help="Create and confirm Manufacturing Orders these many days in advance, to have enough time to replenish components or manufacture semi-finished products.\n"
|
||||
"Note that this does not affect the MO scheduled date, which still respects the just-in-time mechanism.")
|
||||
|
||||
def _compute_bom_count(self):
|
||||
for product in self:
|
||||
@@ -87,11 +80,6 @@ class ProductTemplate(models.Model):
|
||||
}
|
||||
return action
|
||||
|
||||
def action_compute_bom_days(self):
|
||||
templates = self.filtered(lambda t: t.bom_count > 0)
|
||||
if templates:
|
||||
return templates.mapped('product_variant_id').action_compute_bom_days()
|
||||
|
||||
def action_archive(self):
|
||||
filtered_products = self.env['mrp.bom.line'].search([('product_id', 'in', self.product_variant_ids.ids)]).product_id.mapped('display_name')
|
||||
res = super().action_archive()
|
||||
@@ -302,15 +290,6 @@ class ProductProduct(models.Model):
|
||||
res['context'].pop('default_product_tmpl_id', None)
|
||||
return res
|
||||
|
||||
def action_compute_bom_days(self):
|
||||
bom_by_products = self.env['mrp.bom']._bom_find(self)
|
||||
company_id = self.env.context.get('default_company_id', self.env.company.id)
|
||||
warehouse = self.env['stock.warehouse'].search([('company_id', '=', company_id)], limit=1)
|
||||
for product in self:
|
||||
bom_data = self.env['report.mrp.report_bom_structure'].with_context(minimized=True)._get_bom_data(bom_by_products[product], warehouse, product, ignore_stock=True)
|
||||
availability_delay = bom_data.get('resupply_avail_delay')
|
||||
product.days_to_prepare_mo = availability_delay - bom_data.get('lead_time', 0) if availability_delay else 0
|
||||
|
||||
def _match_all_variant_values(self, product_template_attribute_value_ids):
|
||||
""" It currently checks that all variant values (`product_template_attribute_value_ids`)
|
||||
are in the product (`self`).
|
||||
|
||||
@@ -65,7 +65,8 @@ class StockWarehouseOrderpoint(models.Model):
|
||||
res = super()._compute_days_to_order()
|
||||
for orderpoint in self:
|
||||
if 'manufacture' in orderpoint.rule_ids.mapped('action'):
|
||||
orderpoint.days_to_order = orderpoint.product_id.days_to_prepare_mo
|
||||
boms = (orderpoint.product_id.variant_bom_ids or orderpoint.product_id.bom_ids)
|
||||
orderpoint.days_to_order = boms and boms[0].days_to_prepare_mo or 0
|
||||
return res
|
||||
|
||||
def _quantity_in_progress(self):
|
||||
|
||||
@@ -117,8 +117,8 @@ class StockRule(models.Model):
|
||||
return self.env['mrp.bom']._bom_find(product_id, picking_type=self.picking_type_id, bom_type='normal', company_id=company_id.id)[product_id]
|
||||
|
||||
def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_dest_id, name, origin, company_id, values, bom):
|
||||
date_planned = self._get_date_planned(product_id, company_id, values)
|
||||
date_deadline = values.get('date_deadline') or date_planned + relativedelta(days=product_id.produce_delay)
|
||||
date_planned = self._get_date_planned(bom, values)
|
||||
date_deadline = values.get('date_deadline') or date_planned + relativedelta(days=bom.produce_delay)
|
||||
mo_values = {
|
||||
'origin': origin,
|
||||
'product_id': product_id.id,
|
||||
@@ -150,9 +150,9 @@ class StockRule(models.Model):
|
||||
})
|
||||
return mo_values
|
||||
|
||||
def _get_date_planned(self, product_id, company_id, values):
|
||||
def _get_date_planned(self, bom_id, values):
|
||||
format_date_planned = fields.Datetime.from_string(values['date_planned'])
|
||||
date_planned = format_date_planned - relativedelta(days=product_id.produce_delay)
|
||||
date_planned = format_date_planned - relativedelta(days=bom_id.produce_delay)
|
||||
if date_planned == format_date_planned:
|
||||
date_planned = date_planned - relativedelta(hours=1)
|
||||
return date_planned
|
||||
@@ -167,7 +167,8 @@ class StockRule(models.Model):
|
||||
if not manufacture_rule:
|
||||
return delay, delay_description
|
||||
manufacture_rule.ensure_one()
|
||||
manufacture_delay = product.produce_delay
|
||||
bom = (product.variant_bom_ids or product.bom_ids)[:1]
|
||||
manufacture_delay = bom.produce_delay
|
||||
delay += manufacture_delay
|
||||
if not bypass_delay_description:
|
||||
delay_description.append((_('Manufacturing Lead Time'), _('+ %d day(s)', manufacture_delay)))
|
||||
@@ -175,7 +176,7 @@ class StockRule(models.Model):
|
||||
delay += security_delay
|
||||
if not bypass_delay_description:
|
||||
delay_description.append((_('Manufacture Security Lead Time'), _('+ %d day(s)', security_delay)))
|
||||
days_to_order = values.get('days_to_order', product.product_tmpl_id.days_to_prepare_mo)
|
||||
days_to_order = values.get('days_to_order', bom.days_to_prepare_mo)
|
||||
if not bypass_delay_description:
|
||||
delay_description.append((_('Days to Supply Components'), _('+ %d day(s)', days_to_order)))
|
||||
return delay + days_to_order, delay_description
|
||||
|
||||
@@ -21,15 +21,6 @@ class ResCompany(models.Model):
|
||||
]
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
def _populate_factories(self):
|
||||
return super()._populate_factories() + [
|
||||
('produce_delay', populate.randint(1, 4)),
|
||||
]
|
||||
|
||||
|
||||
class Warehouse(models.Model):
|
||||
_inherit = 'stock.warehouse'
|
||||
|
||||
@@ -75,6 +66,7 @@ class MrpBom(models.Model):
|
||||
('sequence', populate.randint(1, 1000)),
|
||||
('code', populate.constant("R{counter}")),
|
||||
('ready_to_produce', populate.randomize(['all_available', 'asap'])),
|
||||
('produce_delay', populate.randint(1, 4)),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -499,8 +499,8 @@ class ReportBomStructure(models.AbstractModel):
|
||||
'route_type': 'manufacture',
|
||||
'route_name': manufacture_rules[0].route_id.display_name,
|
||||
'route_detail': bom.display_name,
|
||||
'lead_time': product.produce_delay + rules_delay,
|
||||
'manufacture_delay': product.produce_delay + rules_delay,
|
||||
'lead_time': bom.produce_delay + rules_delay,
|
||||
'manufacture_delay': bom.produce_delay + rules_delay,
|
||||
}
|
||||
return {}
|
||||
|
||||
|
||||
@@ -141,6 +141,17 @@
|
||||
</group>
|
||||
<group>
|
||||
<field name="picking_type_id" attrs="{'invisible': [('type','=','phantom')]}" string="Operation" groups="stock.group_adv_location"/>
|
||||
<label for="produce_delay" string="Manuf. Lead Time"/>
|
||||
<div>
|
||||
<field name="produce_delay" class="oe_inline"/> days
|
||||
</div>
|
||||
<label for="days_to_prepare_mo"/>
|
||||
<div>
|
||||
<field name="days_to_prepare_mo" class="oe_inline"/> days
|
||||
<button name="action_compute_bom_days" string="Compute" type="object"
|
||||
help="Compute the days required to resupply all components from BoM, by either buying or manufacturing the components and/or subassemblies."
|
||||
class="oe_link pt-0"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
@@ -7,21 +7,6 @@
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="stock.view_template_property_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//label[@for='sale_delay']" position="before">
|
||||
<field name="bom_count" string="Bill of Materials" invisible="1" groups="mrp.group_mrp_user"/>
|
||||
<label for="produce_delay" string="Manuf. Lead Time" attrs="{'invisible':[('type','=','service')]}"/>
|
||||
<div attrs="{'invisible':[('type','=','service')]}">
|
||||
<field name="produce_delay" class="oe_inline"/> days
|
||||
</div>
|
||||
<label for="days_to_prepare_mo" attrs="{'invisible':[('type','=','service')]}"/>
|
||||
<div attrs="{'invisible':[('type','=','service')]}">
|
||||
<field name="days_to_prepare_mo" class="oe_inline"/> days
|
||||
<button name="action_compute_bom_days" string="Compute from BoM" type="object" attrs="{'invisible':['|', ('type', '=', 'service'), ('bom_count', '=', 0)]}"
|
||||
groups="mrp.group_mrp_user"
|
||||
help="Compute the days required to resupply all components from BoM, by either buying or manufacturing the components and/or subassemblies."
|
||||
class="oe_link pt-0"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='product_variant_count']" position="after">
|
||||
<field name="is_kits" invisible="1"/>
|
||||
</xpath>
|
||||
|
||||
@@ -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=product.produce_delay)
|
||||
'date_planned_start': subcontract_move.date - relativedelta(days=bom.produce_delay)
|
||||
}
|
||||
return vals
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ class ReportBomStructure(models.AbstractModel):
|
||||
'route_detail': supplier.display_name,
|
||||
'lead_time': supplier.delay + rules_delay,
|
||||
'supplier_delay': supplier.delay + rules_delay,
|
||||
'manufacture_delay': product.produce_delay,
|
||||
'manufacture_delay': bom.produce_delay,
|
||||
'supplier': supplier,
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
|
||||
self.subcontractor_partner1.property_stock_subcontractor = partner_subcontract_location.id
|
||||
# Add a manufacturing lead time to check that the resupply delivery is correctly planned 2 days
|
||||
# before the subcontracting receipt
|
||||
self.finished.produce_delay = 2
|
||||
self.bom.produce_delay = 2
|
||||
|
||||
# Create a receipt picking from the subcontractor
|
||||
picking_form = Form(self.env['stock.picking'])
|
||||
@@ -140,7 +140,7 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
|
||||
# The picking should be a delivery order
|
||||
self.assertEqual(picking.picking_type_id, wh.subcontracting_resupply_type_id)
|
||||
# The date planned should be correct
|
||||
self.assertEqual(picking_receipt.scheduled_date, picking.scheduled_date + relativedelta(days=self.finished.produce_delay))
|
||||
self.assertEqual(picking_receipt.scheduled_date, picking.scheduled_date + relativedelta(days=mo.bom_id.produce_delay))
|
||||
|
||||
self.assertEqual(mo.picking_type_id, wh.subcontracting_type_id)
|
||||
self.assertFalse(mo.picking_type_id.active)
|
||||
|
||||
@@ -20,7 +20,6 @@ class TestSaleMrpLeadTime(TestStockCommon):
|
||||
# `type` is invisible in the view,
|
||||
# and it's a compute field based on `detailed_type` which is the field visible in the view
|
||||
p1.detailed_type = 'product'
|
||||
p1.produce_delay = 5.0
|
||||
p1.sale_delay = 5.0
|
||||
p1.route_ids.clear()
|
||||
p1.route_ids.add(cls.warehouse_1.manufacture_pull_id.route_id)
|
||||
@@ -36,6 +35,7 @@ class TestSaleMrpLeadTime(TestStockCommon):
|
||||
with Form(cls.env['mrp.bom']) as bom:
|
||||
bom.product_tmpl_id = cls.product_1.product_tmpl_id
|
||||
bom.product_qty = 2
|
||||
bom.produce_delay = 5.0
|
||||
with bom.bom_line_ids.new() as line:
|
||||
line.product_id = cls.product_2
|
||||
line.product_qty = 4
|
||||
@@ -80,7 +80,7 @@ class TestSaleMrpLeadTime(TestStockCommon):
|
||||
)
|
||||
|
||||
# Check schedule date and deadline of manufacturing order
|
||||
mo_scheduled = out_date - timedelta(days=self.product_1.produce_delay) - timedelta(days=company.manufacturing_lead)
|
||||
mo_scheduled = 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,
|
||||
delta=timedelta(seconds=1),
|
||||
@@ -154,7 +154,7 @@ class TestSaleMrpLeadTime(TestStockCommon):
|
||||
)
|
||||
|
||||
# Check schedule date and deadline date of manufacturing order
|
||||
mo_scheduled = out_date - timedelta(days=self.product_1.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_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)
|
||||
self.assertAlmostEqual(
|
||||
fields.Datetime.from_string(manufacturing_order.date_planned_start), mo_scheduled,
|
||||
delta=timedelta(seconds=1),
|
||||
|
||||
Reference in New Issue
Block a user