[IMP] mrp{,_subcontracting_purchase}, {purchase_,}stock: add DTPMO to lead days
For subcontracting, we need to consider both vendor lead time and
manufacturing lead time, and DTPMO (Days To Prepare MO) on the BOM.
Subcontracting delay =
max(Vendor lead time, Manufacturing lead time + DTPMO) + Days to Purchase + Purchase security lead time
Same thing applied to Bom overview, except:
1. Availability state will be computed based on the delay time of it's
components. DTPMO on the bom won't be take into account.
2. Lead time will use the DTPMO on the BOM. DTPMO will be added to
the lead time when it's a manufacturing bom or when it's a
subcontracting bom with Manufacturing Lead Time + DTPMO > Vendor Lead Time
Task-3081481
Part-of: odoo/odoo#137810
This commit is contained in:
committed by
William Henrotin
parent
6c749a4b0d
commit
78e44dc5a3
@@ -297,8 +297,7 @@ class MrpBom(models.Model):
|
||||
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
|
||||
bom.days_to_prepare_mo = self.env['report.mrp.report_bom_structure']._get_max_component_delay(bom_data['components'])
|
||||
|
||||
@api.constrains('product_tmpl_id', 'product_id', 'type')
|
||||
def check_kit_has_not_orderpoint(self):
|
||||
|
||||
@@ -36,8 +36,3 @@ class Company(models.Model):
|
||||
def _create_per_company_sequences(self):
|
||||
super(Company, self)._create_per_company_sequences()
|
||||
self._create_unbuild_sequence()
|
||||
|
||||
def _get_security_by_rule_action(self):
|
||||
res = super()._get_security_by_rule_action()
|
||||
res['manufacture'] = self.manufacturing_lead
|
||||
return res
|
||||
|
||||
@@ -190,25 +190,29 @@ class StockRule(models.Model):
|
||||
"""Add the product and company manufacture delay to the cumulative delay
|
||||
and cumulative description.
|
||||
"""
|
||||
delay, delay_description = super()._get_lead_days(product, **values)
|
||||
delays, delay_description = super()._get_lead_days(product, **values)
|
||||
bypass_delay_description = self.env.context.get('bypass_delay_description')
|
||||
manufacture_rule = self.filtered(lambda r: r.action == 'manufacture')
|
||||
if not manufacture_rule:
|
||||
return delay, delay_description
|
||||
return delays, delay_description
|
||||
manufacture_rule.ensure_one()
|
||||
bom = values.get('bom') or self.env['mrp.bom']._bom_find(product, picking_type=manufacture_rule.picking_type_id, company_id=manufacture_rule.company_id.id)[product]
|
||||
manufacture_delay = bom.produce_delay
|
||||
delay += manufacture_delay
|
||||
delays['total_delay'] += manufacture_delay
|
||||
delays['manufacture_delay'] += manufacture_delay
|
||||
if not bypass_delay_description:
|
||||
delay_description.append((_('Manufacturing Lead Time'), _('+ %d day(s)', manufacture_delay)))
|
||||
security_delay = manufacture_rule.picking_type_id.company_id.manufacturing_lead
|
||||
delay += security_delay
|
||||
if not bypass_delay_description:
|
||||
delay_description.append((_('Manufacture Security Lead Time'), _('+ %d day(s)', security_delay)))
|
||||
if bom.type == 'normal':
|
||||
security_delay = self.picking_type_id.company_id.manufacturing_lead
|
||||
delays['total_delay'] += security_delay
|
||||
delays['security_lead_days'] += 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', bom.days_to_prepare_mo)
|
||||
delays['total_delay'] += days_to_order
|
||||
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
|
||||
return delays, delay_description
|
||||
|
||||
def _push_prepare_move_copy_values(self, move_to_copy, new_date):
|
||||
new_move_vals = super(StockRule, self)._push_prepare_move_copy_values(move_to_copy, new_date)
|
||||
|
||||
@@ -37,18 +37,19 @@ class ReportBomStructure(models.AbstractModel):
|
||||
@api.model
|
||||
def _compute_production_capacities(self, bom_qty, bom_data):
|
||||
date_today = self.env.context.get('from_date', fields.date.today())
|
||||
same_delay = bom_data['lead_time'] == bom_data['availability_delay']
|
||||
lead_time = bom_data['lead_time'] - bom_data['bom'].days_to_prepare_mo
|
||||
same_delay = lead_time == bom_data['availability_delay']
|
||||
res = {}
|
||||
if bom_data.get('producible_qty', 0):
|
||||
# Check if something is producible today, at the earliest time possible considering product's lead time.
|
||||
res['earliest_capacity'] = bom_data['producible_qty']
|
||||
res['earliest_date'] = format_date(self.env, date_today + timedelta(days=bom_data['lead_time']))
|
||||
res['earliest_date'] = format_date(self.env, date_today + timedelta(days=lead_time))
|
||||
|
||||
if bom_data['availability_state'] != 'unavailable':
|
||||
if same_delay:
|
||||
# Means that stock will be resupplied at date_today, so the whole manufacture can start at date_today.
|
||||
res['earliest_capacity'] = bom_qty
|
||||
res['earliest_date'] = format_date(self.env, date_today + timedelta(days=bom_data['lead_time']))
|
||||
res['earliest_date'] = format_date(self.env, date_today + timedelta(days=bom_data['availability_delay']))
|
||||
else:
|
||||
res['leftover_capacity'] = bom_qty - bom_data.get('producible_qty', 0)
|
||||
res['leftover_date'] = format_date(self.env, date_today + timedelta(days=bom_data['availability_delay']))
|
||||
@@ -247,7 +248,6 @@ class ReportBomStructure(models.AbstractModel):
|
||||
'route_type': route_info.get('route_type', ''),
|
||||
'route_name': route_info.get('route_name', ''),
|
||||
'route_detail': route_info.get('route_detail', ''),
|
||||
'lead_time': route_info.get('lead_time', False),
|
||||
'currency': company.currency_id,
|
||||
'currency_id': company.currency_id.id,
|
||||
'product': product,
|
||||
@@ -314,6 +314,8 @@ class ReportBomStructure(models.AbstractModel):
|
||||
bom_report_line['bom_cost'] *= bom_report_line['cost_share']
|
||||
|
||||
availabilities = self._get_availabilities(product, current_quantity, product_info, bom_key, quantities_info, level, ignore_stock, components, report_line=bom_report_line)
|
||||
# in case of subcontracting, lead_time will be calculated with components availability delay
|
||||
bom_report_line['lead_time'] = route_info.get('lead_time', False)
|
||||
bom_report_line.update(availabilities)
|
||||
|
||||
if level == 0:
|
||||
@@ -588,8 +590,9 @@ class ReportBomStructure(models.AbstractModel):
|
||||
'route_type': 'manufacture',
|
||||
'route_name': manufacture_rules[0].route_id.display_name,
|
||||
'route_detail': bom.display_name,
|
||||
'lead_time': bom.produce_delay + rules_delay + manufacturing_lead,
|
||||
'lead_time': bom.produce_delay + rules_delay + manufacturing_lead + bom.days_to_prepare_mo,
|
||||
'manufacture_delay': bom.produce_delay + rules_delay + manufacturing_lead,
|
||||
'bom': bom,
|
||||
}
|
||||
return {}
|
||||
|
||||
|
||||
@@ -72,15 +72,16 @@ class ReportBomStructure(models.AbstractModel):
|
||||
subcontract_rules = [rule for rule in rules if rule.action == 'buy' and bom and bom.type == 'subcontract']
|
||||
if subcontract_rules:
|
||||
supplier = product._select_seller(quantity=quantity, uom_id=product.uom_id, params={'subcontractor_ids': bom.subcontractor_ids})
|
||||
# for subcontracting, we can't decide the lead time without component's resupply availability
|
||||
# we only return necessary info and calculate the lead time late when we have component's data
|
||||
if supplier:
|
||||
return {
|
||||
'route_type': 'subcontract',
|
||||
'route_name': subcontract_rules[0].route_id.display_name,
|
||||
'route_detail': supplier.display_name,
|
||||
'lead_time': supplier.delay + rules_delay,
|
||||
'supplier_delay': supplier.delay + rules_delay,
|
||||
'manufacture_delay': bom.produce_delay,
|
||||
'lead_time': rules_delay,
|
||||
'supplier': supplier,
|
||||
'bom': bom,
|
||||
}
|
||||
|
||||
return res
|
||||
@@ -106,11 +107,16 @@ class ReportBomStructure(models.AbstractModel):
|
||||
|
||||
@api.model
|
||||
def _get_resupply_availability(self, route_info, components):
|
||||
resupply_state, resupply_delay = super()._get_resupply_availability(route_info, components)
|
||||
if route_info.get('route_type') == 'subcontract':
|
||||
max_component_delay = self._get_max_component_delay(components)
|
||||
if max_component_delay is False:
|
||||
return ('unavailable', False)
|
||||
produce_delay = route_info.get('manufacture_delay', 0) + max_component_delay
|
||||
supplier_delay = route_info.get('supplier_delay', 0)
|
||||
return ('estimated', max(produce_delay, supplier_delay))
|
||||
return super()._get_resupply_availability(route_info, components)
|
||||
# Calculate the lead time for subcontracting, keep same as `_get_lead_days`
|
||||
vendor_lead_time = route_info['supplier'].delay
|
||||
manufacture_lead_time = route_info['bom'].produce_delay
|
||||
subcontract_delay = resupply_delay if resupply_delay else 0
|
||||
subcontract_delay += max(vendor_lead_time, manufacture_lead_time + max_component_delay)
|
||||
route_info['lead_time'] += max(vendor_lead_time, manufacture_lead_time + route_info['bom'].days_to_prepare_mo)
|
||||
return ('estimated', subcontract_delay)
|
||||
return (resupply_state, resupply_delay)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import account_move_line
|
||||
from . import stock_picking
|
||||
from . import stock_valuation_layer
|
||||
from . import purchase_order
|
||||
from . import stock_picking
|
||||
from . import stock_rule
|
||||
from . import stock_valuation_layer
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
from odoo import models, _
|
||||
|
||||
|
||||
class StockRule(models.Model):
|
||||
_inherit = 'stock.rule'
|
||||
|
||||
def _get_lead_days(self, product, **values):
|
||||
"""For subcontracting, we need to consider both vendor lead time and
|
||||
manufacturing lead time, and DTPMO (Days To Prepare MO).
|
||||
Subcontracting delay =
|
||||
max(Vendor lead time, Manufacturing lead time + DTPMO) + Days to Purchase + Purchase security lead time
|
||||
"""
|
||||
bypass_delay_description = self.env.context.get('bypass_delay_description')
|
||||
buy_rule = self.filtered(lambda r: r.action == 'buy')
|
||||
seller = 'supplierinfo' in values and values['supplierinfo'] or product.with_company(buy_rule.company_id)._select_seller(quantity=None)
|
||||
if not buy_rule or not seller:
|
||||
return super()._get_lead_days(product, **values)
|
||||
seller = seller[0]
|
||||
bom = self.env['mrp.bom'].sudo()._bom_subcontract_find(
|
||||
product,
|
||||
company_id=buy_rule.picking_type_id.company_id.id,
|
||||
bom_type='subcontract',
|
||||
subcontractor=seller.partner_id)
|
||||
if not bom:
|
||||
return super()._get_lead_days(product, **values)
|
||||
|
||||
delays, delay_description = super(StockRule, self - buy_rule)._get_lead_days(product, **values)
|
||||
extra_delays, extra_delay_description = super(StockRule, buy_rule.with_context(ignore_vendor_lead_time=True))._get_lead_days(product, **values)
|
||||
if seller.delay >= bom.produce_delay + bom.days_to_prepare_mo:
|
||||
delays['total_delay'] += seller.delay
|
||||
delays['purchase_delay'] += seller.delay
|
||||
if not bypass_delay_description:
|
||||
delay_description.append((_('Vendor Lead Time'), _('+ %d day(s)', seller.delay)))
|
||||
else:
|
||||
manufacture_delay = bom.produce_delay
|
||||
delays['total_delay'] += manufacture_delay
|
||||
# set manufacture_delay to purchase_delay so that PO can be created with correct date
|
||||
delays['purchase_delay'] += manufacture_delay
|
||||
if not bypass_delay_description:
|
||||
delay_description.append((_('Manufacturing Lead Time'), _('+ %d day(s)', manufacture_delay)))
|
||||
days_to_order = bom.days_to_prepare_mo
|
||||
delays['total_delay'] += days_to_order
|
||||
# add dtpmo to purchase_delay so that PO can be created with correct date
|
||||
delays['purchase_delay'] += days_to_order
|
||||
if not bypass_delay_description:
|
||||
extra_delay_description.append((_('Days to Supply Components'), _('+ %d day(s)', days_to_order)))
|
||||
|
||||
for key, value in extra_delays.items():
|
||||
delays[key] += value
|
||||
return delays, delay_description + extra_delay_description
|
||||
@@ -10,3 +10,14 @@ class ReportBomStructure(models.AbstractModel):
|
||||
@api.model
|
||||
def _is_buy_route(self, rules, product, bom):
|
||||
return super()._is_buy_route(rules, product, bom) and (not bom or bom.type != 'subcontract')
|
||||
|
||||
@api.model
|
||||
def _get_resupply_availability(self, route_info, components):
|
||||
resupply_state, resupply_delay = super()._get_resupply_availability(route_info, components)
|
||||
if route_info.get('route_type') == 'subcontract':
|
||||
# always add `Purchase security lead days` and `Days to Purchase`
|
||||
extra_delay = route_info['bom'].company_id.po_lead + route_info['bom'].company_id.days_to_purchase
|
||||
route_info['lead_time'] += extra_delay
|
||||
subcontract_delay = (resupply_delay if resupply_delay else 0) + extra_delay
|
||||
return ('estimated', subcontract_delay)
|
||||
return (resupply_state, resupply_delay)
|
||||
|
||||
@@ -382,3 +382,103 @@ class MrpSubcontractingPurchaseTest(TestMrpSubcontractingCommon):
|
||||
{'location_dest_id': stock_location_id.id, 'quantity': 10.0, 'state': 'done'},
|
||||
{'location_dest_id': subco_location_id.id, 'quantity': 1.0, 'state': 'done'},
|
||||
])
|
||||
|
||||
def test_subcontracting_lead_days(self):
|
||||
""" Test the lead days computation for subcontracting. Subcontracting delay =
|
||||
max(Vendor lead time, Manufacturing lead time + DTPMO) + Days to Purchase + Purchase security lead time
|
||||
"""
|
||||
rule = self.env['stock.rule'].search([('action', '=', 'buy')], limit=1)
|
||||
self.env.company.manufacturing_lead = 114514 # should never be used
|
||||
|
||||
self.env.company.po_lead = 1
|
||||
self.env.company.days_to_purchase = 2
|
||||
# Case 1 Vendor lead time >= Manufacturing lead time + DTPMO
|
||||
seller = self.env['product.supplierinfo'].create({
|
||||
'product_tmpl_id': self.finished.product_tmpl_id.id,
|
||||
'partner_id': self.subcontractor_partner1.id,
|
||||
'price': 12.0,
|
||||
'delay': 10
|
||||
})
|
||||
self.bom.produce_delay = 3
|
||||
self.bom.days_to_prepare_mo = 4
|
||||
delays, _ = rule._get_lead_days(self.finished, supplierinfo=seller)
|
||||
self.assertEqual(delays['total_delay'], seller.delay + self.env.company.po_lead + self.env.company.days_to_purchase)
|
||||
# Case 2 Vendor lead time < Manufacturing lead time + DTPMO
|
||||
self.bom.produce_delay = 5
|
||||
self.bom.days_to_prepare_mo = 6
|
||||
delays, _ = rule._get_lead_days(self.finished, supplierinfo=seller)
|
||||
self.assertEqual(delays['total_delay'], self.bom.produce_delay + self.bom.days_to_prepare_mo + self.env.company.po_lead + self.env.company.days_to_purchase)
|
||||
|
||||
def test_subcontracting_lead_days_on_overview(self):
|
||||
"""Test on the BOM overview, the lead days and resupply availability are
|
||||
correctly computed. The dtpmo on the bom should be used for the lead days,
|
||||
while the resupply availability should be based on the calculated dtpmo.
|
||||
"""
|
||||
# should never be used
|
||||
self.env.company.manufacturing_lead = 114514
|
||||
# should be added in all cases
|
||||
self.env.company.po_lead = 5
|
||||
self.env.company.days_to_purchase = 5
|
||||
|
||||
rule = self.env['stock.rule'].search([('action', '=', 'buy')], limit=1)
|
||||
(self.finished | self.comp1 | self.comp2).route_ids = [(6, None, [rule.route_id.id])]
|
||||
self.comp2_bom.active = False
|
||||
self.env['product.supplierinfo'].create({
|
||||
'product_tmpl_id': self.finished.product_tmpl_id.id,
|
||||
'partner_id': self.subcontractor_partner1.id,
|
||||
'price': 648.0,
|
||||
'delay': 15
|
||||
})
|
||||
self.env['product.supplierinfo'].create({
|
||||
'product_tmpl_id': self.comp1.product_tmpl_id.id,
|
||||
'partner_id': self.subcontractor_partner1.id,
|
||||
'price': 648.0,
|
||||
'delay': 10
|
||||
})
|
||||
self.env['product.supplierinfo'].create({
|
||||
'product_tmpl_id': self.comp2.product_tmpl_id.id,
|
||||
'partner_id': self.subcontractor_partner1.id,
|
||||
'price': 648.0,
|
||||
'delay': 6
|
||||
})
|
||||
self.bom.produce_delay = 10
|
||||
self.bom.days_to_prepare_mo = 0
|
||||
|
||||
# Case 1: Vendor lead time >= Manufacturing lead time + DTPMO on BOM
|
||||
bom_data = self.env['report.mrp.report_bom_structure']._get_bom_data(self.bom, self.warehouse, self.finished)
|
||||
self.assertEqual(bom_data['lead_time'], 15 + 5 + 5 + 0,
|
||||
"Lead time = Purchase lead time(finished) + Days to Purchase + Purchase security lead time + DTPMO on BOM")
|
||||
self.assertEqual(bom_data['resupply_avail_delay'], 10 + 5 + 5 + 20,
|
||||
"Component avail delay = Purchase lead time(comp1) + Days to Purchase + Purchase security lead time + Calculated DTPMO")
|
||||
|
||||
# Case 2: Vendor lead time < Manufacturing lead time + DTPMO on BOM
|
||||
self.bom.action_compute_bom_days()
|
||||
self.assertEqual(self.bom.days_to_prepare_mo, 10 + 5 + 5,
|
||||
"DTPMO = Purchase lead time(comp1) + Days to Purchase + Purchase security lead time")
|
||||
self.bom.days_to_prepare_mo = 10
|
||||
bom_data = self.env['report.mrp.report_bom_structure']._get_bom_data(self.bom, self.warehouse, self.finished)
|
||||
self.assertEqual(bom_data['lead_time'], 10 + 5 + 5 + 10,
|
||||
"Lead time = Manufacturing lead time + Days to Purchase + Purchase security lead time + DTPMO on BOM")
|
||||
self.assertEqual(bom_data['resupply_avail_delay'], 10 + 5 + 5 + 20,
|
||||
"Component avail delay = Manufacturing lead time + Days to Purchase + Purchase security lead time + Calculated DTPMO")
|
||||
|
||||
# Update stock for components, calculate DTPMO should be 0
|
||||
self.env['stock.quant']._update_available_quantity(self.comp1, self.warehouse.lot_stock_id, 100)
|
||||
self.env['stock.quant']._update_available_quantity(self.comp2, self.warehouse.lot_stock_id, 100)
|
||||
self.env.invalidate_all() # invalidate cache to get updated qty_available
|
||||
# Case 1: Vendor lead time >= Manufacturing lead time + DTPMO on BOM
|
||||
self.bom.days_to_prepare_mo = 2
|
||||
bom_data = self.env['report.mrp.report_bom_structure']._get_bom_data(self.bom, self.warehouse, self.finished)
|
||||
self.assertEqual(bom_data['lead_time'], 15 + 5 + 5,
|
||||
"Lead time = Purchase lead time(finished) + Days to Purchase + Purchase security lead time")
|
||||
for component in bom_data['components']:
|
||||
self.assertEqual(component['availability_state'], 'available')
|
||||
# Case 2: Vendor lead time < Manufacturing lead time + DTPMO on BOM
|
||||
self.bom.action_compute_bom_days()
|
||||
self.assertEqual(self.bom.days_to_prepare_mo, 10 + 5 + 5,
|
||||
"DTPMO = Purchase lead time(comp1) + Days to Purchase + Purchase security lead time")
|
||||
bom_data = self.env['report.mrp.report_bom_structure']._get_bom_data(self.bom, self.warehouse, self.finished)
|
||||
self.assertEqual(bom_data['lead_time'], 10 + 5 + 5 + 20,
|
||||
"Lead time = Manufacturing lead time + Days to Purchase + Purchase security lead time + DTPMO on BOM")
|
||||
for component in bom_data['components']:
|
||||
self.assertEqual(component['availability_state'], 'available')
|
||||
|
||||
@@ -10,9 +10,3 @@ class ResCompany(models.Model):
|
||||
days_to_purchase = fields.Float(
|
||||
string='Days to Purchase',
|
||||
help="Days needed to confirm a PO, define when a PO should be validated")
|
||||
|
||||
|
||||
def _get_security_by_rule_action(self):
|
||||
res = super()._get_security_by_rule_action()
|
||||
res['buy'] = self.po_lead
|
||||
return res
|
||||
|
||||
@@ -161,23 +161,29 @@ class StockRule(models.Model):
|
||||
and cumulative description. The company lead time is always displayed for onboarding
|
||||
purpose in order to indicate that those options are available.
|
||||
"""
|
||||
delay, delay_description = super()._get_lead_days(product, **values)
|
||||
delays, delay_description = super()._get_lead_days(product, **values)
|
||||
bypass_delay_description = self.env.context.get('bypass_delay_description')
|
||||
buy_rule = self.filtered(lambda r: r.action == 'buy')
|
||||
seller = 'supplierinfo' in values and values['supplierinfo'] or product.with_company(buy_rule.company_id)._select_seller(quantity=None)
|
||||
if not buy_rule or not seller:
|
||||
return delay, delay_description
|
||||
return delays, delay_description
|
||||
buy_rule.ensure_one()
|
||||
supplier_delay = seller[0].delay
|
||||
if supplier_delay and not bypass_delay_description:
|
||||
delay_description.append((_('Vendor Lead Time'), _('+ %d day(s)', supplier_delay)))
|
||||
if not self.env.context.get('ignore_vendor_lead_time'):
|
||||
supplier_delay = seller[0].delay
|
||||
delays['total_delay'] += supplier_delay
|
||||
delays['purchase_delay'] += supplier_delay
|
||||
if not bypass_delay_description:
|
||||
delay_description.append((_('Vendor Lead Time'), _('+ %d day(s)', supplier_delay)))
|
||||
security_delay = buy_rule.picking_type_id.company_id.po_lead
|
||||
delays['total_delay'] += security_delay
|
||||
delays['security_lead_days'] += security_delay
|
||||
if not bypass_delay_description:
|
||||
delay_description.append((_('Purchase Security Lead Time'), _('+ %d day(s)', security_delay)))
|
||||
days_to_order = values.get('days_to_order', buy_rule.company_id.days_to_purchase)
|
||||
days_to_order = buy_rule.company_id.days_to_purchase
|
||||
delays['total_delay'] += days_to_order
|
||||
if not bypass_delay_description:
|
||||
delay_description.append((_('Days to Purchase'), _('+ %d day(s)', days_to_order)))
|
||||
return delay + supplier_delay + security_delay + days_to_order, delay_description
|
||||
return delays, delay_description
|
||||
|
||||
@api.model
|
||||
def _get_procurements_to_merge_groupby(self, procurement):
|
||||
@@ -266,7 +272,7 @@ class StockRule(models.Model):
|
||||
params values: values of procurements
|
||||
params origins: procuremets origins to write on the PO
|
||||
"""
|
||||
purchase_date = min([fields.Datetime.from_string(value['date_planned']) - relativedelta(days=int(value['supplier'].delay)) for value in values])
|
||||
purchase_date = min([value.get('date_order') or fields.Datetime.from_string(value['date_planned']) - relativedelta(days=int(value['supplier'].delay)) for value in values])
|
||||
|
||||
# Since the procurements are grouped if they share the same domain for
|
||||
# PO but the PO does not exist. In this case it will create the PO from
|
||||
|
||||
@@ -595,12 +595,13 @@ class Product(models.Model):
|
||||
else:
|
||||
return self._get_rules_from_location(rule.location_src_id, seen_rules=seen_rules | rule)
|
||||
|
||||
def _get_date_with_security_lead_days(self, date, location, route_ids=False):
|
||||
def _get_dates_info(self, date, location, route_ids=False):
|
||||
rules = self._get_rules_from_location(location, route_ids=route_ids)
|
||||
for action, days in location.company_id._get_security_by_rule_action().items():
|
||||
if action in rules.mapped('action'):
|
||||
date -= relativedelta(days=days)
|
||||
return date
|
||||
delays, _ = rules.with_context(bypass_delay_description=True)._get_lead_days(self)
|
||||
return {
|
||||
'date_planned': date - relativedelta(days=delays['security_lead_days']),
|
||||
'date_order': date - relativedelta(days=delays['security_lead_days'] + delays['purchase_delay']),
|
||||
}
|
||||
|
||||
def _get_only_qty_available(self):
|
||||
""" Get only quantities available, it is equivalent to read qty_available
|
||||
|
||||
@@ -206,6 +206,3 @@ class Company(models.Model):
|
||||
'partner_id': company.partner_id.id
|
||||
} for company in companies])
|
||||
return companies
|
||||
|
||||
def _get_security_by_rule_action(self):
|
||||
return {}
|
||||
|
||||
@@ -1407,12 +1407,13 @@ Please change the quantity done or the rounding precision of your unit of measur
|
||||
elif self.rule_id.group_propagation_option == 'none':
|
||||
group_id = False
|
||||
product_id = self.product_id.with_context(lang=self._get_lang())
|
||||
date = self._get_mto_procurement_date()
|
||||
dates_info = {'date_planned': self._get_mto_procurement_date()}
|
||||
if self.location_id.warehouse_id and self.location_id.warehouse_id.lot_stock_id.parent_path in self.location_id.parent_path:
|
||||
date = self.product_id._get_date_with_security_lead_days(self.date, self.location_id, route_ids=self.route_ids)
|
||||
dates_info = self.product_id._get_dates_info(self.date, self.location_id, route_ids=self.route_ids)
|
||||
return {
|
||||
'product_description_variants': self.description_picking and self.description_picking.replace(product_id._get_description(self.picking_type_id), ''),
|
||||
'date_planned': date,
|
||||
'date_planned': dates_info.get('date_planned'),
|
||||
'date_order': dates_info.get('date_order'),
|
||||
'date_deadline': self.date_deadline,
|
||||
'move_dest_ids': self,
|
||||
'group_id': group_id,
|
||||
|
||||
@@ -79,6 +79,7 @@ class StockWarehouseOrderpoint(models.Model):
|
||||
qty_forecast = fields.Float('Forecast', readonly=True, compute='_compute_qty', digits='Product Unit of Measure')
|
||||
qty_to_order = fields.Float('To Order', compute='_compute_qty_to_order', store=True, readonly=False, digits='Product Unit of Measure')
|
||||
|
||||
#TODO: remove this field in master
|
||||
days_to_order = fields.Float(compute='_compute_days_to_order', help="Numbers of days in advance that replenishments demands are created.")
|
||||
visibility_days = fields.Float(
|
||||
compute='_compute_visibility_days', inverse='_set_visibility_days', readonly=False,
|
||||
@@ -113,7 +114,7 @@ class StockWarehouseOrderpoint(models.Model):
|
||||
continue
|
||||
values = orderpoint._get_lead_days_values()
|
||||
lead_days, dummy = orderpoint.rule_ids._get_lead_days(orderpoint.product_id, **values)
|
||||
lead_days_date = fields.Date.today() + relativedelta.relativedelta(days=lead_days)
|
||||
lead_days_date = fields.Date.today() + relativedelta.relativedelta(days=lead_days['total_delay'])
|
||||
orderpoint.lead_days_date = lead_days_date
|
||||
|
||||
@api.depends('route_id', 'product_id', 'location_id', 'company_id', 'warehouse_id', 'product_id.route_ids')
|
||||
@@ -352,7 +353,7 @@ class StockWarehouseOrderpoint(models.Model):
|
||||
# group product by lead_days and location in order to read virtual_available
|
||||
# in batch
|
||||
rules = product._get_rules_from_location(loc)
|
||||
lead_days = rules.with_context(bypass_delay_description=True)._get_lead_days(product)[0]
|
||||
lead_days = rules.with_context(bypass_delay_description=True)._get_lead_days(product)[0]['total_delay']
|
||||
ploc_per_day[(lead_days, loc)].add(product.id)
|
||||
|
||||
# recompute virtual_available with lead days
|
||||
@@ -481,11 +482,12 @@ class StockWarehouseOrderpoint(models.Model):
|
||||
comming from an orderpoint. This method could be override in order to add other custom key that could
|
||||
be used in move/po creation.
|
||||
"""
|
||||
date_planned = date or fields.Date.today()
|
||||
date_planned = self.product_id._get_date_with_security_lead_days(date_planned, self.location_id, route_ids=self.route_id)
|
||||
date_deadline = date or fields.Date.today()
|
||||
dates_info = self.product_id._get_dates_info(date_deadline, self.location_id, route_ids=self.route_id)
|
||||
return {
|
||||
'route_ids': self.route_id,
|
||||
'date_planned': date_planned,
|
||||
'date_planned': dates_info['date_planned'],
|
||||
'date_order': dates_info['date_order'],
|
||||
'date_deadline': date or False,
|
||||
'warehouse_id': self.warehouse_id,
|
||||
'orderpoint_id': self,
|
||||
|
||||
@@ -359,12 +359,14 @@ class StockRule(models.Model):
|
||||
:param product: the product of the procurement
|
||||
:type product: :class:`~odoo.addons.product.models.product.ProductProduct`
|
||||
:return: the cumulative delay and cumulative delay's description
|
||||
:rtype: tuple[int, list[str, str]]
|
||||
:rtype: tuple[defaultdict(float), list[str, str]]
|
||||
"""
|
||||
delays = defaultdict(float)
|
||||
delay = sum(self.filtered(lambda r: r.action in ['pull', 'pull_push']).mapped('delay'))
|
||||
delays['total_delay'] += delay
|
||||
global_visibility_days = self.env['ir.config_parameter'].sudo().get_param('stock.visibility_days')
|
||||
if global_visibility_days:
|
||||
delay += int(global_visibility_days)
|
||||
delays['total_delay'] += int(global_visibility_days)
|
||||
if self.env.context.get('bypass_delay_description'):
|
||||
delay_description = []
|
||||
else:
|
||||
@@ -375,7 +377,7 @@ class StockRule(models.Model):
|
||||
]
|
||||
if global_visibility_days:
|
||||
delay_description.append((_('Global Visibility Days'), _('+ %d day(s)', int(global_visibility_days))))
|
||||
return delay, delay_description
|
||||
return delays, delay_description
|
||||
|
||||
|
||||
class ProcurementGroup(models.Model):
|
||||
|
||||
@@ -118,7 +118,7 @@ class StockReplenishmentOption(models.TransientModel):
|
||||
@api.depends('replenishment_info_id')
|
||||
def _compute_lead_time(self):
|
||||
for record in self:
|
||||
lead_time = record.route_id.rule_ids._get_lead_days(record.product_id)[0]
|
||||
lead_time = record.route_id.rule_ids._get_lead_days(record.product_id)[0]['total_delay'] #TO FIX: use _get_rule to avoid singleton issue
|
||||
record.lead_time = str(lead_time) + " days"
|
||||
|
||||
@api.depends('warehouse_id', 'free_qty', 'uom', 'qty_to_order')
|
||||
|
||||
Reference in New Issue
Block a user