[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:
JF Aubert
2023-03-07 01:59:16 +01:00
parent c5e58589cd
commit 6825c440d5
14 changed files with 48 additions and 63 deletions
+1
View File
@@ -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"/>
+15
View File
@@ -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")
+2 -2
View File
@@ -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
-21
View File
@@ -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`).
+2 -1
View File
@@ -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):
+7 -6
View File
@@ -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
+1 -9
View File
@@ -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 {}
+11
View File
@@ -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>
-15
View File
@@ -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),