mirror of
https://github.com/bringout/oca-ocb-mrp.git
synced 2026-04-26 02:32:05 +02:00
19.0 vanilla
This commit is contained in:
parent
accf5918df
commit
6e65e8c877
688 changed files with 225434 additions and 199401 deletions
|
|
@ -1,24 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import collections
|
||||
from datetime import timedelta
|
||||
from itertools import groupby
|
||||
import operator as py_operator
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools import groupby
|
||||
from odoo.tools.float_utils import float_round, float_is_zero
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
OPERATORS = {
|
||||
PY_OPERATORS = {
|
||||
'<': py_operator.lt,
|
||||
'>': py_operator.gt,
|
||||
'<=': py_operator.le,
|
||||
'>=': py_operator.ge,
|
||||
'=': py_operator.eq,
|
||||
'!=': py_operator.ne
|
||||
'!=': py_operator.ne,
|
||||
'in': lambda elem, container: elem in container,
|
||||
'not in': lambda elem, container: elem not in container,
|
||||
}
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = "product.template"
|
||||
|
||||
|
|
@ -28,42 +28,31 @@ class ProductTemplate(models.Model):
|
|||
compute='_compute_bom_count', compute_sudo=False)
|
||||
used_in_bom_count = fields.Integer('# of BoM Where is Used',
|
||||
compute='_compute_used_in_bom_count', compute_sudo=False)
|
||||
mrp_product_qty = fields.Float('Manufactured', digits='Product Unit of Measure',
|
||||
mrp_product_qty = fields.Float('Manufactured', digits='Product Unit',
|
||||
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', search='_search_is_kits')
|
||||
days_to_prepare_mo = fields.Float(
|
||||
string="Days to prepare Manufacturing Order", default=0.0,
|
||||
help="Create and confirm Manufacturing Orders this many days in advance, to have enough time to replenish components or manufacture semi-finished products.\n"
|
||||
"Note that security lead times will also be considered when appropriate.")
|
||||
|
||||
def _compute_bom_count(self):
|
||||
for product in self:
|
||||
product.bom_count = self.env['mrp.bom'].search_count(['|', ('product_tmpl_id', '=', product.id), ('byproduct_ids.product_id.product_tmpl_id', '=', product.id)])
|
||||
product.bom_count = self.env['mrp.bom'].search_count(
|
||||
['|', ('product_tmpl_id', 'in', product.ids), ('byproduct_ids.product_id.product_tmpl_id', 'in', product.ids)]
|
||||
)
|
||||
|
||||
@api.depends_context('company')
|
||||
def _compute_is_kits(self):
|
||||
domain = [('company_id', 'in', [False, self.env.company.id]),
|
||||
('product_tmpl_id', 'in', self.ids),
|
||||
('active', '=', True),
|
||||
('type', '=', 'phantom')]
|
||||
bom_mapping = self.env['mrp.bom'].sudo()._read_group(
|
||||
domain, ['product_tmpl_id'], ['product_tmpl_id'], orderby='id')
|
||||
kits_ids = {b['product_tmpl_id'][0] for b in bom_mapping}
|
||||
domain = [('product_tmpl_id', 'in', self.ids), ('type', '=', 'phantom'), '|', ('company_id', '=', False), ('company_id', '=', self.env.company.id)]
|
||||
bom_mapping = self.env['mrp.bom'].sudo().search_read(domain, ['product_tmpl_id'])
|
||||
kits_ids = set(b['product_tmpl_id'][0] for b in bom_mapping)
|
||||
for template in self:
|
||||
template.is_kits = (template.id in kits_ids)
|
||||
|
||||
def _search_is_kits(self, operator, value):
|
||||
assert operator in ('=', '!='), 'Unsupported operator'
|
||||
if operator != 'in':
|
||||
return NotImplemented
|
||||
bom_tmpl_query = self.env['mrp.bom'].sudo()._search(
|
||||
[('company_id', 'in', [False] + self.env.companies.ids),
|
||||
('type', '=', 'phantom'), ('active', '=', True)])
|
||||
neg = ''
|
||||
if (operator == '=' and not value) or (operator == '!=' and value):
|
||||
neg = 'not '
|
||||
return [('id', neg + 'inselect', bom_tmpl_query.subselect('product_tmpl_id'))]
|
||||
return [('id', 'in', bom_tmpl_query.subselect('product_tmpl_id'))]
|
||||
|
||||
def _compute_show_qty_status_button(self):
|
||||
super()._compute_show_qty_status_button()
|
||||
|
|
@ -72,17 +61,20 @@ class ProductTemplate(models.Model):
|
|||
template.show_on_hand_qty_status_button = template.product_variant_count <= 1
|
||||
template.show_forecasted_qty_status_button = False
|
||||
|
||||
def _should_open_product_quants(self):
|
||||
return super()._should_open_product_quants() or self.is_kits
|
||||
|
||||
def _compute_used_in_bom_count(self):
|
||||
for template in self:
|
||||
template.used_in_bom_count = self.env['mrp.bom'].search_count(
|
||||
[('bom_line_ids.product_tmpl_id', '=', template.id)])
|
||||
[('bom_line_ids.product_tmpl_id', 'in', template.ids)])
|
||||
|
||||
def write(self, values):
|
||||
if 'active' in values:
|
||||
self.filtered(lambda p: p.active != values['active']).with_context(active_test=False).bom_ids.write({
|
||||
'active': values['active']
|
||||
def write(self, vals):
|
||||
if 'active' in vals:
|
||||
self.filtered(lambda p: p.active != vals['active']).with_context(active_test=False).bom_ids.write({
|
||||
'active': vals['active']
|
||||
})
|
||||
return super().write(values)
|
||||
return super().write(vals)
|
||||
|
||||
def action_used_in_bom(self):
|
||||
self.ensure_one()
|
||||
|
|
@ -92,22 +84,16 @@ class ProductTemplate(models.Model):
|
|||
|
||||
def _compute_mrp_product_qty(self):
|
||||
for template in self:
|
||||
template.mrp_product_qty = float_round(sum(template.mapped('product_variant_ids').mapped('mrp_product_qty')), precision_rounding=template.uom_id.rounding)
|
||||
template.mrp_product_qty = template.uom_id.round(sum(template.mapped('product_variant_ids').mapped('mrp_product_qty')))
|
||||
|
||||
def action_view_mos(self):
|
||||
action = self.env["ir.actions.actions"]._for_xml_id("mrp.mrp_production_report")
|
||||
action = self.env["ir.actions.actions"]._for_xml_id("mrp.mrp_production_action")
|
||||
action['domain'] = [('state', '=', 'done'), ('product_tmpl_id', 'in', self.ids)]
|
||||
action['context'] = {
|
||||
'graph_measure': 'product_uom_qty',
|
||||
'search_default_filter_plan_date': 1,
|
||||
}
|
||||
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), ('bom_id.active', '=', True)]).product_id.mapped('display_name')
|
||||
res = super().action_archive()
|
||||
|
|
@ -125,6 +111,10 @@ class ProductTemplate(models.Model):
|
|||
}
|
||||
return res
|
||||
|
||||
def _get_backend_root_menu_ids(self):
|
||||
return super()._get_backend_root_menu_ids() + [self.env.ref('mrp.menu_mrp_root').id]
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = "product.product"
|
||||
|
||||
|
|
@ -134,34 +124,50 @@ class ProductProduct(models.Model):
|
|||
compute='_compute_bom_count', compute_sudo=False)
|
||||
used_in_bom_count = fields.Integer('# BoM Where Used',
|
||||
compute='_compute_used_in_bom_count', compute_sudo=False)
|
||||
mrp_product_qty = fields.Float('Manufactured', digits='Product Unit of Measure',
|
||||
mrp_product_qty = fields.Float('Manufactured', digits='Product Unit',
|
||||
compute='_compute_mrp_product_qty', compute_sudo=False)
|
||||
is_kits = fields.Boolean(compute="_compute_is_kits", search='_search_is_kits')
|
||||
|
||||
# Catalog related fields
|
||||
product_catalog_product_is_in_bom = fields.Boolean(
|
||||
compute='_compute_product_is_in_bom_and_mo',
|
||||
search='_search_product_is_in_bom',
|
||||
)
|
||||
|
||||
product_catalog_product_is_in_mo = fields.Boolean(
|
||||
compute='_compute_product_is_in_bom_and_mo',
|
||||
search='_search_product_is_in_mo',
|
||||
)
|
||||
|
||||
def _compute_bom_count(self):
|
||||
for product in self:
|
||||
product.bom_count = self.env['mrp.bom'].search_count(['|', '|', ('byproduct_ids.product_id', '=', product.id), ('product_id', '=', product.id), '&', ('product_id', '=', False), ('product_tmpl_id', '=', product.product_tmpl_id.id)])
|
||||
product.bom_count = self.env['mrp.bom'].search_count([
|
||||
'|', '|', ('byproduct_ids.product_id', 'in', product.ids), ('product_id', 'in', product.ids),
|
||||
'&', ('product_id', '=', False), ('product_tmpl_id', 'in', product.product_tmpl_id.ids),
|
||||
])
|
||||
|
||||
@api.depends_context('company')
|
||||
def _compute_is_kits(self):
|
||||
domain = [
|
||||
'&', ('company_id', 'in', [False, self.env.company.id]),
|
||||
'&', ('active', '=', True),
|
||||
'&', ('type', '=', 'phantom'),
|
||||
'|', ('product_id', 'in', self.ids),
|
||||
'&', ('product_id', '=', False),
|
||||
('product_tmpl_id', 'in', self.product_tmpl_id.ids)]
|
||||
tmpl_bom_mapping = self.env['mrp.bom'].sudo()._read_group(
|
||||
domain, ['product_tmpl_id'], ['product_tmpl_id'], orderby='id')
|
||||
product_bom_mapping = self.env['mrp.bom'].sudo()._read_group(
|
||||
domain, ['product_id'], ['product_id'], orderby='id')
|
||||
kits_template_ids = {b['product_tmpl_id'][0] for b in tmpl_bom_mapping}
|
||||
kits_product_ids = {b['product_id'][0] for b in product_bom_mapping if b['product_id']}
|
||||
domain = ['&', '&', ('type', '=', 'phantom'),
|
||||
'|', ('company_id', '=', False),
|
||||
('company_id', '=', self.env.company.id),
|
||||
'|', ('product_id', 'in', self.ids),
|
||||
'&', ('product_id', '=', False),
|
||||
('product_tmpl_id', 'in', self.product_tmpl_id.ids)]
|
||||
bom_mapping = self.env['mrp.bom'].sudo().search_read(domain, ['product_tmpl_id', 'product_id'])
|
||||
kits_template_ids = set([])
|
||||
kits_product_ids = set([])
|
||||
for bom_data in bom_mapping:
|
||||
if bom_data['product_id']:
|
||||
kits_product_ids.add(bom_data['product_id'][0])
|
||||
else:
|
||||
kits_template_ids.add(bom_data['product_tmpl_id'][0])
|
||||
for product in self:
|
||||
product.is_kits = (product.id in kits_product_ids or product.product_tmpl_id.id in kits_template_ids)
|
||||
|
||||
def _search_is_kits(self, operator, value):
|
||||
assert operator in ('=', '!='), 'Unsupported operator'
|
||||
if operator != 'in':
|
||||
return NotImplemented
|
||||
bom_tmpl_query = self.env['mrp.bom'].sudo()._search(
|
||||
[('company_id', 'in', [False] + self.env.companies.ids),
|
||||
('active', '=', True),
|
||||
|
|
@ -169,13 +175,10 @@ class ProductProduct(models.Model):
|
|||
bom_product_query = self.env['mrp.bom'].sudo()._search(
|
||||
[('company_id', 'in', [False] + self.env.companies.ids),
|
||||
('type', '=', 'phantom'), ('product_id', '!=', False)])
|
||||
neg = ''
|
||||
op = '|'
|
||||
if (operator == '=' and not value) or (operator == '!=' and value):
|
||||
neg = 'not '
|
||||
op = '&'
|
||||
return [op, ('product_tmpl_id', neg + 'inselect', bom_tmpl_query.subselect('product_tmpl_id')),
|
||||
('id', neg + 'inselect', bom_product_query.subselect('product_id'))]
|
||||
return [
|
||||
'|', ('product_tmpl_id', 'in', bom_tmpl_query.subselect('product_tmpl_id')),
|
||||
('id', 'in', bom_product_query.subselect('product_id'))
|
||||
]
|
||||
|
||||
def _compute_show_qty_status_button(self):
|
||||
super()._compute_show_qty_status_button()
|
||||
|
|
@ -186,14 +189,44 @@ class ProductProduct(models.Model):
|
|||
|
||||
def _compute_used_in_bom_count(self):
|
||||
for product in self:
|
||||
product.used_in_bom_count = self.env['mrp.bom'].search_count([('bom_line_ids.product_id', '=', product.id)])
|
||||
product.used_in_bom_count = self.env['mrp.bom'].search_count(
|
||||
[('bom_line_ids.product_id', 'in', product.ids)])
|
||||
|
||||
def write(self, values):
|
||||
if 'active' in values:
|
||||
self.filtered(lambda p: p.active != values['active']).with_context(active_test=False).variant_bom_ids.write({
|
||||
'active': values['active']
|
||||
@api.depends_context('order_id')
|
||||
def _compute_product_is_in_bom_and_mo(self):
|
||||
# Just to enable the _search method
|
||||
self.product_catalog_product_is_in_bom = False
|
||||
self.product_catalog_product_is_in_mo = False
|
||||
|
||||
def _search_product_is_in_bom(self, operator, value):
|
||||
if operator != 'in':
|
||||
return NotImplemented
|
||||
product_ids = self.env['mrp.bom.line'].search([
|
||||
('bom_id', '=', self.env.context.get('order_id', '')),
|
||||
]).product_id.ids
|
||||
return [('id', operator, product_ids)]
|
||||
|
||||
def _search_product_is_in_mo(self, operator, value):
|
||||
if operator != 'in':
|
||||
return NotImplemented
|
||||
product_ids = self.env['mrp.production'].search([
|
||||
('id', 'in', [self.env.context.get('order_id', '')]),
|
||||
]).move_raw_ids.product_id.ids
|
||||
return [('id', operator, product_ids)]
|
||||
|
||||
def write(self, vals):
|
||||
if 'active' in vals:
|
||||
self.filtered(lambda p: p.active != vals['active']).with_context(active_test=False).variant_bom_ids.write({
|
||||
'active': vals['active']
|
||||
})
|
||||
return super().write(values)
|
||||
return super().write(vals)
|
||||
|
||||
def get_total_routes(self):
|
||||
routes = super().get_total_routes()
|
||||
if self.bom_ids:
|
||||
manufacture_routes = self.env['stock.rule'].search([('action', '=', 'manufacture')]).route_id
|
||||
routes |= manufacture_routes
|
||||
return routes
|
||||
|
||||
def get_components(self):
|
||||
""" Return the components list ids in case of kit product.
|
||||
|
|
@ -202,7 +235,7 @@ class ProductProduct(models.Model):
|
|||
bom_kit = self.env['mrp.bom']._bom_find(self, bom_type='phantom')[self]
|
||||
if bom_kit:
|
||||
boms, bom_sub_lines = bom_kit.explode(self, 1)
|
||||
return [bom_line.product_id.id for bom_line, data in bom_sub_lines if bom_line.product_id.type == 'product']
|
||||
return [bom_line.product_id.id for bom_line, data in bom_sub_lines if bom_line.product_id.is_storable]
|
||||
else:
|
||||
return super(ProductProduct, self).get_components()
|
||||
|
||||
|
|
@ -213,16 +246,16 @@ class ProductProduct(models.Model):
|
|||
return action
|
||||
|
||||
def _compute_mrp_product_qty(self):
|
||||
date_from = fields.Datetime.to_string(fields.datetime.now() - timedelta(days=365))
|
||||
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)]
|
||||
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])
|
||||
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:sum'])
|
||||
mapped_data = {product.id: qty for product, qty in read_group_res}
|
||||
for product in self:
|
||||
if not product.id:
|
||||
product.mrp_product_qty = 0.0
|
||||
continue
|
||||
product.mrp_product_qty = float_round(mapped_data.get(product.id, 0), precision_rounding=product.uom_id.rounding)
|
||||
product.mrp_product_qty = product.uom_id.round(mapped_data.get(product.id, 0))
|
||||
|
||||
def _compute_quantities_dict(self, lot_id, owner_id, package_id, from_date=False, to_date=False):
|
||||
""" When the product is a kit, this override computes the fields :
|
||||
|
|
@ -271,7 +304,7 @@ class ProductProduct(models.Model):
|
|||
component = component.with_context(mrp_compute_quantities=qties).with_prefetch(prefetch_component_ids)
|
||||
qty_per_kit = 0
|
||||
for bom_line, bom_line_data in bom_sub_lines:
|
||||
if component.type != 'product' or float_is_zero(bom_line_data['qty'], precision_rounding=bom_line.product_uom_id.rounding):
|
||||
if not component.is_storable or bom_line.product_uom_id.is_zero(bom_line_data['qty']):
|
||||
# As BoMs allow components with 0 qty, a.k.a. optionnal components, we simply skip those
|
||||
# to avoid a division by zero. The same logic is applied to non-storable products as those
|
||||
# products have 0 qty available.
|
||||
|
|
@ -280,30 +313,29 @@ class ProductProduct(models.Model):
|
|||
qty_per_kit += bom_line.product_uom_id._compute_quantity(uom_qty_per_kit, bom_line.product_id.uom_id, round=False, raise_if_failure=False)
|
||||
if not qty_per_kit:
|
||||
continue
|
||||
rounding = component.uom_id.rounding
|
||||
component_res = (
|
||||
qties.get(component.id)
|
||||
if component.id in qties
|
||||
else {
|
||||
"virtual_available": float_round(component.virtual_available, precision_rounding=rounding),
|
||||
"qty_available": float_round(component.qty_available, precision_rounding=rounding),
|
||||
"incoming_qty": float_round(component.incoming_qty, precision_rounding=rounding),
|
||||
"outgoing_qty": float_round(component.outgoing_qty, precision_rounding=rounding),
|
||||
"free_qty": float_round(component.free_qty, precision_rounding=rounding),
|
||||
"virtual_available": component.uom_id.round(component.virtual_available),
|
||||
"qty_available": component.uom_id.round(component.qty_available),
|
||||
"incoming_qty": component.uom_id.round(component.incoming_qty),
|
||||
"outgoing_qty": component.uom_id.round(component.outgoing_qty),
|
||||
"free_qty": component.uom_id.round(component.free_qty),
|
||||
}
|
||||
)
|
||||
ratios_virtual_available.append(float_round(component_res["virtual_available"] / qty_per_kit, precision_rounding=rounding, rounding_method='DOWN'))
|
||||
ratios_qty_available.append(float_round(component_res["qty_available"] / qty_per_kit, precision_rounding=rounding, rounding_method='DOWN'))
|
||||
ratios_incoming_qty.append(float_round(component_res["incoming_qty"] / qty_per_kit, precision_rounding=rounding, rounding_method='DOWN'))
|
||||
ratios_outgoing_qty.append(float_round(component_res["outgoing_qty"] / qty_per_kit, precision_rounding=rounding, rounding_method='DOWN'))
|
||||
ratios_free_qty.append(float_round(component_res["free_qty"] / qty_per_kit, precision_rounding=rounding, rounding_method='DOWN'))
|
||||
ratios_virtual_available.append(component.uom_id.round(component_res["virtual_available"] / qty_per_kit, rounding_method='DOWN'))
|
||||
ratios_qty_available.append(component.uom_id.round(component_res["qty_available"] / qty_per_kit, rounding_method='DOWN'))
|
||||
ratios_incoming_qty.append(component.uom_id.round(component_res["incoming_qty"] / qty_per_kit, rounding_method='DOWN'))
|
||||
ratios_outgoing_qty.append(component.uom_id.round(component_res["outgoing_qty"] / qty_per_kit, rounding_method='DOWN'))
|
||||
ratios_free_qty.append(component.uom_id.round(component_res["free_qty"] / qty_per_kit, rounding_method='DOWN'))
|
||||
if bom_sub_lines and ratios_virtual_available: # Guard against all cnsumable bom: at least one ratio should be present.
|
||||
res[product.id] = {
|
||||
'virtual_available': float_round(min(ratios_virtual_available) * bom_kits[product].product_qty, precision_rounding=rounding) // 1,
|
||||
'qty_available': float_round(min(ratios_qty_available) * bom_kits[product].product_qty, precision_rounding=rounding) // 1,
|
||||
'incoming_qty': float_round(min(ratios_incoming_qty) * bom_kits[product].product_qty, precision_rounding=rounding) // 1,
|
||||
'outgoing_qty': float_round(min(ratios_outgoing_qty) * bom_kits[product].product_qty, precision_rounding=rounding) // 1,
|
||||
'free_qty': float_round(min(ratios_free_qty) * bom_kits[product].product_qty, precision_rounding=rounding) // 1,
|
||||
'virtual_available': component.uom_id.round(min(ratios_virtual_available) * bom_kits[product].product_qty) // 1,
|
||||
'qty_available': component.uom_id.round(min(ratios_qty_available) * bom_kits[product].product_qty) // 1,
|
||||
'incoming_qty': component.uom_id.round(min(ratios_incoming_qty) * bom_kits[product].product_qty) // 1,
|
||||
'outgoing_qty': component.uom_id.round(min(ratios_outgoing_qty) * bom_kits[product].product_qty) // 1,
|
||||
'free_qty': component.uom_id.round(min(ratios_free_qty) * bom_kits[product].product_qty) // 1,
|
||||
}
|
||||
else:
|
||||
res[product.id] = {
|
||||
|
|
@ -340,28 +372,9 @@ class ProductProduct(models.Model):
|
|||
components |= self.env['product.product'].concat(*[l[0].product_id for l in bom_sub_lines])
|
||||
res = super(ProductProduct, components).action_open_quants()
|
||||
if bom_kits:
|
||||
res['context']['single_product'] = False
|
||||
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)
|
||||
if bom_data.get('availability_state') == 'unavailable' and not bom_data.get('components_available', True):
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': _('Cannot compute days to prepare due to missing route info for at least 1 component or for the final product.'),
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
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`).
|
||||
|
|
@ -376,19 +389,19 @@ class ProductProduct(models.Model):
|
|||
# * the attributes are a subset of the attributes of the line.
|
||||
return len(self.product_template_attribute_value_ids & product_template_attribute_value_ids) == len(product_template_attribute_value_ids.attribute_id)
|
||||
|
||||
def _count_returned_sn_products(self, sn_lot):
|
||||
res = self.env['stock.move.line'].search_count([
|
||||
('lot_id', '=', sn_lot.id),
|
||||
('qty_done', '=', 1),
|
||||
('state', '=', 'done'),
|
||||
def _count_returned_sn_products_domain(self, sn_lot, or_domains):
|
||||
or_domains.append([
|
||||
('production_id', '=', False),
|
||||
('location_id.usage', '=', 'production'),
|
||||
('move_id.unbuild_id', '!=', False),
|
||||
])
|
||||
return super()._count_returned_sn_products(sn_lot) + res
|
||||
return super()._count_returned_sn_products_domain(sn_lot, or_domains)
|
||||
|
||||
def _search_qty_available_new(self, operator, value, lot_id=False, owner_id=False, package_id=False):
|
||||
'''extending the method in stock.product to take into account kits'''
|
||||
op = PY_OPERATORS.get(operator)
|
||||
if not op:
|
||||
return NotImplemented
|
||||
product_ids = super(ProductProduct, self)._search_qty_available_new(operator, value, lot_id, owner_id, package_id)
|
||||
kit_boms = self.env['mrp.bom'].search([('type', "=", 'phantom')])
|
||||
kit_products = self.env['product.product']
|
||||
|
|
@ -398,8 +411,10 @@ class ProductProduct(models.Model):
|
|||
else:
|
||||
kit_products |= kit.product_tmpl_id.product_variant_ids
|
||||
for product in kit_products:
|
||||
if OPERATORS[operator](product.qty_available, value):
|
||||
if op(product.qty_available, value):
|
||||
product_ids.append(product.id)
|
||||
elif product.id in product_ids:
|
||||
product_ids.pop(product_ids.index(product.id))
|
||||
return list(set(product_ids))
|
||||
|
||||
def action_archive(self):
|
||||
|
|
@ -418,3 +433,45 @@ class ProductProduct(models.Model):
|
|||
},
|
||||
}
|
||||
return res
|
||||
|
||||
def _get_backend_root_menu_ids(self):
|
||||
return super()._get_backend_root_menu_ids() + [self.env.ref('mrp.menu_mrp_root').id]
|
||||
|
||||
def _update_uom(self, to_uom_id):
|
||||
for uom, product_template, boms in self.env['mrp.bom']._read_group(
|
||||
[('product_tmpl_id', 'in', self.product_tmpl_id.ids)],
|
||||
['product_uom_id', 'product_tmpl_id'],
|
||||
['id:recordset'],
|
||||
):
|
||||
if product_template.uom_id != uom:
|
||||
raise UserError(_('As other units of measure (ex : %(problem_uom)s) '
|
||||
'than %(uom)s have already been used for this product, the change of unit of measure can not be done.'
|
||||
'If you want to change it, please archive the product and create a new one.',
|
||||
problem_uom=uom.name, uom=product_template.uom_id.name))
|
||||
boms.product_uom_id = to_uom_id
|
||||
|
||||
for uom, product, bom_lines in self.env['mrp.bom.line']._read_group(
|
||||
[('product_id', 'in', self.ids)],
|
||||
['product_uom_id', 'product_id'],
|
||||
['id:recordset'],
|
||||
):
|
||||
if product.product_tmpl_id.uom_id != uom:
|
||||
raise UserError(_('As other units of measure (ex : %(problem_uom)s) '
|
||||
'than %(uom)s have already been used for this product, the change of unit of measure can not be done.'
|
||||
'If you want to change it, please archive the product and create a new one.',
|
||||
problem_uom=uom.name, uom=product.product_tmpl_id.uom_id.name))
|
||||
bom_lines.product_uom_id = to_uom_id
|
||||
|
||||
for uom, product, productions in self.env['mrp.production']._read_group(
|
||||
[('product_id', 'in', self.ids)],
|
||||
['product_uom_id', 'product_id'],
|
||||
['id:recordset'],
|
||||
):
|
||||
if product.product_tmpl_id.uom_id != uom:
|
||||
raise UserError(_('As other units of measure (ex : %(problem_uom)s) '
|
||||
'than %(uom)s have already been used for this product, the change of unit of measure can not be done.'
|
||||
'If you want to change it, please archive the product and create a new one.',
|
||||
problem_uom=uom.name, uom=product.product_tmpl_id.uom_id.name))
|
||||
productions.product_uom_id = to_uom_id
|
||||
|
||||
return super()._update_uom(to_uom_id)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue