[MIG] mrp_subcontracting: Adapt to OCA guidelines + initial work

This commit is contained in:
Alexandre Diaz
2020-05-10 12:54:51 +02:00
committed by Pedro M. Baeza
parent f3a0fc97fc
commit 0426acf030
28 changed files with 416 additions and 265 deletions

View File

@@ -1,19 +1,2 @@
# -*- coding: utf-8 -*-
from odoo import SUPERUSER_ID, api
from . import models
from . import wizard
def uninstall_hook(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
warehouses = env["stock.warehouse"].search([])
subcontracting_routes = warehouses.mapped("subcontracting_route_id")
warehouses.write({"subcontracting_route_id": False})
# Fail unlink means that the route is used somewhere (e.g. route_id on stock.rule). In this case
# we don't try to do anything.
try:
subcontracting_routes.unlink()
except:
pass

View File

@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': "mrp_subcontracting",
'version': '0.1',
'version': '12.0.1.0.0',
'summary': "Subcontract Productions",
'description': "",
"author": "Odoo S.A., Odoo Community Association (OCA)",
'website': 'https://www.odoo.com/page/manufacturing',
'category': 'Manufacturing/Manufacturing',
'depends': ['mrp'],
@@ -21,5 +21,4 @@
'demo': [
'data/mrp_subcontracting_demo.xml',
],
'uninstall_hook': 'uninstall_hook',
}

View File

@@ -11,6 +11,10 @@
<value model="stock.warehouse" eval="obj().env['stock.warehouse'].search([]).ids"/>
<value eval="{'subcontracting_to_resupply': True}"/>
</function>
<function model="stock.picking.type" name="write">
<value model="stock.picking.type" eval="obj().env['stock.picking.type'].search([('code', '=', 'mrp_operation')]).ids"/>
<value eval="{'use_create_components_lots': True}"/>
</function>
</data>
</odoo>

View File

@@ -0,0 +1,19 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
# Copyright 2019 Odoo
# Copyright 2020 Tecnativa - Alexandre Díaz
# Copyright 2020 Tecnativa - Pedro M. Baeza
from odoo import SUPERUSER_ID, api
def uninstall_hook(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
warehouses = env["stock.warehouse"].search([])
subcontracting_routes = warehouses.mapped("subcontracting_route_id")
warehouses.write({"subcontracting_route_id": False})
# Fail unlink means that the route is used somewhere (e.g. route_id on
# stock.rule). In this case, we don't try to do anything.
try:
subcontracting_routes.unlink()
except Exception:
pass

View File

@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import mrp_bom
from . import mrp_production
from . import product
from . import res_company
from . import res_partner
from . import stock_move
from . import stock_move_line
from . import stock_picking
from . import stock_rule
from . import stock_picking_type
from . import stock_warehouse
from . import stock_production_lot

View File

@@ -1,19 +1,63 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.osv.expression import AND
class MrpBom(models.Model):
_inherit = 'mrp.bom'
type = fields.Selection(selection_add=[('subcontract', 'Subcontracting')])
subcontractor_ids = fields.Many2many('res.partner', 'mrp_bom_subcontractor', string='Subcontractors', check_company=True)
subcontractor_ids = fields.Many2many(
'res.partner', 'mrp_bom_subcontractor', string='Subcontractors',
check_company=True)
def _bom_subcontract_find(self, product_tmpl=None, product=None, picking_type=None, company_id=False, bom_type='subcontract', subcontractor=False):
domain = self._bom_find_domain(product_tmpl=product_tmpl, product=product, picking_type=picking_type, company_id=company_id, bom_type=bom_type)
def _bom_subcontract_find(self, product_tmpl=None, product=None,
picking_type=None, company_id=False,
bom_type='subcontract', subcontractor=False):
domain = self._bom_find_domain(product_tmpl=product_tmpl,
product=product,
picking_type=picking_type,
company_id=company_id,
bom_type=bom_type)
if subcontractor:
domain = AND([domain, [('subcontractor_ids', 'parent_of', subcontractor.ids)]])
domain = AND([domain, [
('subcontractor_ids', 'parent_of', subcontractor.ids),
]])
return self.search(domain, order='sequence, product_id', limit=1)
else:
return self.env['mrp.bom']
# This is a copy from mrp v13.0
@api.model
def _bom_find_domain(self, product_tmpl=None, product=None,
picking_type=None, company_id=False, bom_type=False):
if product:
if not product_tmpl:
product_tmpl = product.product_tmpl_id
domain = [
'|', ('product_id', '=', product.id),
'&', ('product_id', '=', False),
('product_tmpl_id', '=', product_tmpl.id),
]
elif product_tmpl:
domain = [('product_tmpl_id', '=', product_tmpl.id)]
else:
# neither product nor template, makes no sense to search
raise UserError(_(
'You should provide either a product or a product template to\
search a BoM'))
if picking_type:
domain += ['|', ('picking_type_id', '=', picking_type.id),
('picking_type_id', '=', False)]
if company_id or self.env.context.get('company_id'):
domain = domain + [
'|', ('company_id', '=', False),
('company_id', '=',
company_id or self.env.context.get('company_id')),
]
if bom_type:
domain += [('type', '=', bom_type)]
# order to prioritize bom with product_id over the one without
return domain

View File

@@ -0,0 +1,18 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
# Copyright 2020 Tecnativa - Pedro M. Baeza
from odoo import api, fields, models
class MrpProduction(models.Model):
_inherit = 'mrp.production'
@api.depends(
'workorder_ids.state',
'move_finished_ids',
'move_finished_ids.quantity_done',
'is_locked',
)
def _get_produced_qty(self):
"""Add workorder_ids.state to depends list."""
return super()._get_produced_qty()

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
@@ -7,11 +6,15 @@ from odoo import api, fields, models
class SupplierInfo(models.Model):
_inherit = 'product.supplierinfo'
is_subcontractor = fields.Boolean('Subcontracted', compute='_compute_is_subcontractor', help="Choose a vendor of type subcontractor if you want to subcontract the product")
is_subcontractor = fields.Boolean(
'Subcontracted', compute='_compute_is_subcontractor',
help="Choose a vendor of type subcontractor if you want to\
subcontract the product")
@api.depends('name', 'product_id', 'product_tmpl_id')
def _compute_is_subcontractor(self):
for supplier in self:
boms = supplier.product_id.variant_bom_ids
boms |= supplier.product_tmpl_id.bom_ids.filtered(lambda b: not b.product_id)
boms |= supplier.product_tmpl_id.bom_ids.filtered(
lambda b: not b.product_id)
supplier.is_subcontractor = supplier.name in boms.subcontractor_ids

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
@@ -20,11 +19,13 @@ class ResCompany(models.Model):
self._create_subcontracting_location()
def _create_subcontracting_location(self):
parent_location = self.env.ref('stock.stock_location_locations_partner', raise_if_not_found=False)
property_stock_subcontractor_res_partner_field = self.env['ir.model.fields'].search([
('model', '=', 'res.partner'),
('name', '=', 'property_stock_subcontractor')
])
parent_location = self.env.ref(
'stock.stock_location_locations_partner', raise_if_not_found=False)
property_stock_subcontractor_res_partner_field = self.env[
'ir.model.fields'].search([
('model', '=', 'res.partner'),
('name', '=', 'property_stock_subcontractor')
])
for company in self:
subcontracting_location = self.env['stock.location'].create({
'name': _('%s: Subcontracting Location') % company.name,

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
@@ -8,6 +7,7 @@ class ResPartner(models.Model):
_inherit = 'res.partner'
property_stock_subcontractor = fields.Many2one(
'stock.location', string="Subcontractor Location", company_dependent=True,
'stock.location', string="Subcontractor Location",
company_dependent=True,
help="The stock location used as source and destination when sending\
goods to this contact during a subcontracting process.")

View File

@@ -0,0 +1,13 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
# Copyright 2020 Tecnativa - Pedro M. Baeza
from odoo import models
class StockLocation(models.Model):
_inherit = 'stock.location'
def should_bypass_reservation(self):
if self.env.context.get('mrp_subcontracting_bypass_reservation'):
return True
return super().should_bypass_reservation()

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
@@ -17,10 +16,15 @@ class StockMove(models.Model):
)
def _compute_show_subcontracting_details_visible(self):
""" Compute if the action button in order to see moves raw is visible """
"""
Compute if the action button in order to see moves raw is visible
"""
for move in self:
if move.is_subcontract and move._has_tracked_subcontract_components() and\
not float_is_zero(move.quantity_done, precision_rounding=move.product_uom.rounding):
if move.is_subcontract and move\
._has_tracked_subcontract_components() and\
not float_is_zero(
move.quantity_done,
precision_rounding=move.product_uom.rounding):
move.show_subcontracting_details_visible = True
else:
move.show_subcontracting_details_visible = False
@@ -54,8 +58,8 @@ class StockMove(models.Model):
if 'product_uom_qty' in values:
if self.env.context.get('cancel_backorder') is False:
return super(StockMove, self).write(values)
self.filtered(lambda m: m.is_subcontract and
m.state not in ['draft', 'cancel', 'done'])._update_subcontract_order_qty(values['product_uom_qty'])
self.filtered(lambda m: m.is_subcontract
and m.state not in ['draft', 'cancel', 'done'])._update_subcontract_order_qty(values['product_uom_qty'])
return super(StockMove, self).write(values)
def action_show_details(self):
@@ -67,12 +71,17 @@ class StockMove(models.Model):
rounding = self.product_uom.rounding
production = self.move_orig_ids.production_id
if self._has_tracked_subcontract_components() and\
float_compare(production.qty_produced, production.product_uom_qty, precision_rounding=rounding) < 0 and\
float_compare(self.quantity_done, self.product_uom_qty, precision_rounding=rounding) < 0:
float_compare(production.qty_produced,
production.product_uom_qty,
precision_rounding=rounding) < 0 and\
float_compare(self.quantity_done, self.product_uom_qty,
precision_rounding=rounding) < 0:
return self._action_record_components()
action = super(StockMove, self).action_show_details()
if self.is_subcontract and self._has_tracked_subcontract_components():
action['views'] = [(self.env.ref('stock.view_stock_move_operations').id, 'form')]
if self.is_subcontract:
action['views'] = [
(self.env.ref('stock.view_stock_move_operations').id, 'form'),
]
action['context'].update({
'show_lots_m2o': self.has_tracking != 'none',
'show_lots_text': False,
@@ -82,8 +91,10 @@ class StockMove(models.Model):
def action_show_subcontract_details(self):
""" Display moves raw for subcontracted product self. """
moves = self.move_orig_ids.production_id.move_raw_ids
tree_view = self.env.ref('mrp_subcontracting.mrp_subcontracting_move_tree_view')
form_view = self.env.ref('mrp_subcontracting.mrp_subcontracting_move_form_view')
tree_view = self.env.ref(
'mrp_subcontracting.mrp_subcontracting_move_tree_view')
form_view = self.env.ref(
'mrp_subcontracting.mrp_subcontracting_move_form_view')
return {
'name': _('Raw Materials for %s') % (self.product_id.display_name),
'type': 'ir.actions.act_window',
@@ -93,36 +104,38 @@ class StockMove(models.Model):
'domain': [('id', 'in', moves.ids)],
}
def _action_cancel(self):
for move in self:
if move.is_subcontract:
move.move_orig_ids.production_id._action_cancel()
return super()._action_cancel()
def _action_confirm(self, merge=True, merge_into=False):
subcontract_details_per_picking = defaultdict(list)
for move in self:
if move.location_id.usage != 'supplier' or move.location_dest_id.usage == 'supplier':
if move.location_id.usage != 'supplier' \
or move.location_dest_id.usage == 'supplier':
continue
if move.move_orig_ids.production_id:
continue
bom = move._get_subcontract_bom()
if not bom:
continue
if float_is_zero(move.product_qty, precision_rounding=move.product_uom.rounding) and\
if float_is_zero(move.product_qty,
precision_rounding=move.product_uom.rounding) and\
move.picking_id.immediate_transfer is True:
raise UserError(_("To subcontract, use a planned transfer."))
subcontract_details_per_picking[move.picking_id].append((move, bom))
subcontract_details_per_picking[move.picking_id].append(
(move, bom))
move.write({
'is_subcontract': True,
'location_id': move.picking_id.partner_id.with_context(force_company=move.company_id.id).property_stock_subcontractor.id
'location_id': move.picking_id.partner_id.with_context(
force_company=move.company_id.id)
.property_stock_subcontractor.id
})
for picking, subcontract_details in subcontract_details_per_picking.items():
for picking, subcontract_details in\
subcontract_details_per_picking.items():
picking._subcontracted_produce(subcontract_details)
res = super(StockMove, self)._action_confirm(merge=merge, merge_into=merge_into)
res = super(StockMove, self)._action_confirm(merge=merge,
merge_into=merge_into)
if subcontract_details_per_picking:
self.env['stock.picking'].concat(*list(subcontract_details_per_picking.keys())).action_assign()
self.env['stock.picking'].concat(
*list(subcontract_details_per_picking.keys())).action_assign()
return res
def _action_record_components(self):
@@ -143,11 +156,14 @@ class StockMove(models.Model):
for move in self:
if not move.is_subcontract:
continue
# Extra quantity is allowed when components do not need to be register
# Extra quantity is allowed when components do not need to be
# register
if not move._has_tracked_subcontract_components():
continue
rounding = move.product_uom.rounding
if float_compare(move.quantity_done, move.move_orig_ids.production_id.qty_produced, precision_rounding=rounding) > 0:
if float_compare(move.quantity_done,
move.move_orig_ids.production_id.qty_produced,
precision_rounding=rounding) > 0:
overprocessed_moves |= move
if overprocessed_moves:
raise UserError(_("""
@@ -156,7 +172,8 @@ subcontracted product(s) with tracked component(s):
%s.
If you want to process more than initially planned, you
can use the edit + unlock buttons in order to adapt the initial demand on the
operations.""") % ('\n'.join(overprocessed_moves.mapped('product_id.display_name'))))
operations.""") % ('\n'.join(overprocessed_moves.mapped(
'product_id.display_name'))))
def _get_subcontract_bom(self):
self.ensure_one()
@@ -171,21 +188,22 @@ operations.""") % ('\n'.join(overprocessed_moves.mapped('product_id.display_name
def _has_tracked_subcontract_components(self):
self.ensure_one()
return any(m.has_tracking != 'none' for m in self.move_orig_ids.production_id.move_raw_ids)
return any(m.has_tracking != 'none' for m in
self.move_orig_ids.production_id.move_raw_ids)
def _prepare_extra_move_vals(self, qty):
vals = super(StockMove, self)._prepare_extra_move_vals(qty)
vals = super()._prepare_extra_move_vals(qty)
vals['location_id'] = self.location_id.id
return vals
def _prepare_move_split_vals(self, qty):
vals = super(StockMove, self)._prepare_move_split_vals(qty)
vals = super()._prepare_move_split_vals(qty)
vals['location_id'] = self.location_id.id
return vals
def _should_bypass_reservation(self):
""" If the move is subcontracted then ignore the reservation. """
should_bypass_reservation = super(StockMove, self)._should_bypass_reservation()
should_bypass_reservation = super()._should_bypass_reservation()
if not should_bypass_reservation and self.is_subcontract:
return True
return should_bypass_reservation
@@ -195,8 +213,9 @@ operations.""") % ('\n'.join(overprocessed_moves.mapped('product_id.display_name
quantity_change = quantity - move.product_uom_qty
production = move.move_orig_ids.production_id
if production:
self.env['change.production.qty'].with_context(skip_activity=True).create({
'mo_id': production.id,
'product_qty': production.product_uom_qty + quantity_change
}).change_prod_qty()
self.env['change.production.qty'].with_context(
skip_activity=True).create({
'mo_id': production.id,
'product_qty': production.product_uom_qty
+ quantity_change,
}).change_prod_qty()

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
@@ -10,17 +9,20 @@ class StockMoveLine(models.Model):
@api.model_create_multi
def create(self, vals_list):
records = super(StockMoveLine, self).create(vals_list)
records.filtered(lambda ml: ml.move_id.is_subcontract).move_id._check_overprocessed_subcontract_qty()
records.filtered(lambda ml: ml.move_id.is_subcontract).move_id\
._check_overprocessed_subcontract_qty()
return records
def write(self, values):
res = super(StockMoveLine, self).write(values)
self.filtered(lambda ml: ml.move_id.is_subcontract).move_id._check_overprocessed_subcontract_qty()
self.filtered(lambda ml: ml.move_id.is_subcontract).move_id\
._check_overprocessed_subcontract_qty()
return res
def _should_bypass_reservation(self, location):
""" If the move line is subcontracted then ignore the reservation. """
should_bypass_reservation = super(StockMoveLine, self)._should_bypass_reservation(location)
should_bypass_reservation = super()._should_bypass_reservation(
location)
if not should_bypass_reservation and self.move_id.is_subcontract:
return True
return should_bypass_reservation

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import timedelta
@@ -9,7 +8,8 @@ from odoo import api, fields, models
class StockPicking(models.Model):
_inherit = 'stock.picking'
display_action_record_components = fields.Boolean(compute='_compute_display_action_record_components')
display_action_record_components = fields.Boolean(
compute='_compute_display_action_record_components')
@api.depends('state')
def _compute_display_action_record_components(self):
@@ -22,13 +22,17 @@ class StockPicking(models.Model):
picking.display_action_record_components = False
continue
# Hide if no components are track
subcontracted_productions = picking._get_subcontracted_productions()
subcontracted_moves = subcontracted_productions.mapped('move_raw_ids')
if all(subcontracted_move.has_tracking == 'none' for subcontracted_move in subcontracted_moves):
subcontracted_productions = picking\
._get_subcontracted_productions()
subcontracted_moves = subcontracted_productions.mapped(
'move_raw_ids')
if all(subcontracted_move.has_tracking == 'none'
for subcontracted_move in subcontracted_moves):
picking.display_action_record_components = False
continue
# Hide if the production is to close
if not subcontracted_productions.filtered(lambda mo: mo.state not in ('to_close', 'done')):
if not subcontracted_productions.filtered(
lambda mo: mo.state not in ('to_close', 'done')):
picking.display_action_record_components = False
continue
picking.display_action_record_components = True
@@ -46,27 +50,33 @@ class StockPicking(models.Model):
continue
production = move.move_orig_ids.production_id
if move._has_tracked_subcontract_components():
move.move_orig_ids.filtered(lambda m: m.state not in ('done', 'cancel')).move_line_ids.unlink()
move_finished_ids = move.move_orig_ids.filtered(lambda m: m.state not in ('done', 'cancel'))
move.move_orig_ids.filtered(
lambda m: m.state not in ('done', 'cancel'))\
.move_line_ids.unlink()
move_finished_ids = move.move_orig_ids.filtered(
lambda m: m.state not in ('done', 'cancel'))
for ml in move.move_line_ids:
ml.copy({
'picking_id': False,
'production_id': move_finished_ids.production_id.id,
'production_id':
move_finished_ids.production_id.id,
'move_id': move_finished_ids.id,
'qty_done': ml.qty_done,
'result_package_id': False,
'location_id': move_finished_ids.location_id.id,
'location_dest_id': move_finished_ids.location_dest_id.id,
'location_dest_id':
move_finished_ids.location_dest_id.id,
})
else:
for move_line in move.move_line_ids:
produce = self.env['mrp.product.produce'].with_context(default_production_id=production.id).create({
'production_id': production.id,
'qty_producing': move_line.qty_done,
'product_uom_id': move_line.product_uom_id.id,
'finished_lot_id': move_line.lot_id.id,
'consumption': 'strict',
})
produce = self.env['mrp.product.produce'].with_context(
default_production_id=production.id).create({
'production_id': production.id,
'qty_producing': move_line.qty_done,
'product_uom_id': move_line.product_uom_id.id,
'finished_lot_id': move_line.lot_id.id,
'consumption': 'strict',
})
produce._generate_produce_lines()
produce._record_production()
productions |= production
@@ -75,12 +85,18 @@ class StockPicking(models.Model):
subcontracted_production.post_inventory()
else:
subcontracted_production.button_mark_done()
# For concistency, set the date on production move before the date
# on picking. (Tracability report + Product Moves menu item)
# For concistency, set the date on production move before the
# date on picking. (Tracability report + Product Moves menu
# item)
minimum_date = min(picking.move_line_ids.mapped('date'))
production_moves = subcontracted_production.move_raw_ids | subcontracted_production.move_finished_ids
production_moves.write({'date': minimum_date - timedelta(seconds=1)})
production_moves.move_line_ids.write({'date': minimum_date - timedelta(seconds=1)})
production_moves = subcontracted_production.move_raw_ids\
| subcontracted_production.move_finished_ids
production_moves.write({
'date': minimum_date - timedelta(seconds=1),
})
production_moves.move_line_ids.write({
'date': minimum_date - timedelta(seconds=1),
})
return res
def action_record_components(self):
@@ -98,14 +114,16 @@ class StockPicking(models.Model):
# -------------------------------------------------------------------------
def _is_subcontract(self):
self.ensure_one()
return self.picking_type_id.code == 'incoming' and any(m.is_subcontract for m in self.move_lines)
return self.picking_type_id.code == 'incoming' and any(
m.is_subcontract for m in self.move_lines)
def _get_subcontracted_productions(self):
self.ensure_one()
return self.move_lines.mapped('move_orig_ids.production_id')
def _get_warehouse(self, subcontract_move):
return subcontract_move.warehouse_id or self.picking_type_id.warehouse_id
return subcontract_move.warehouse_id\
or self.picking_type_id.warehouse_id
def _prepare_subcontract_mo_vals(self, subcontract_move, bom):
subcontract_move.ensure_one()
@@ -121,8 +139,14 @@ class StockPicking(models.Model):
'product_id': product.id,
'product_uom_id': subcontract_move.product_uom.id,
'bom_id': bom.id,
'location_src_id': subcontract_move.picking_id.partner_id.with_context(force_company=subcontract_move.company_id.id).property_stock_subcontractor.id,
'location_dest_id': subcontract_move.picking_id.partner_id.with_context(force_company=subcontract_move.company_id.id).property_stock_subcontractor.id,
'location_src_id':
subcontract_move.picking_id.partner_id.with_context(
force_company=subcontract_move.company_id.id)
.property_stock_subcontractor.id,
'location_dest_id':
subcontract_move.picking_id.partner_id.with_context(
force_company=subcontract_move.company_id.id)
.property_stock_subcontractor.id,
'product_qty': subcontract_move.product_uom_qty,
'picking_type_id': warehouse.subcontracting_type_id.id
}
@@ -131,11 +155,13 @@ class StockPicking(models.Model):
def _subcontracted_produce(self, subcontract_details):
self.ensure_one()
for move, bom in subcontract_details:
mo = self.env['mrp.production'].with_context(force_company=move.company_id.id).create(self._prepare_subcontract_mo_vals(move, bom))
mo = self.env['mrp.production'].with_context(
force_company=move.company_id.id)\
.create(self._prepare_subcontract_mo_vals(move, bom))
self.env['stock.move'].create(mo._get_moves_raw_values())
mo.action_confirm()
# Link the finished to the receipt move.
finished_move = mo.move_finished_ids.filtered(lambda m: m.product_id == move.product_id)
finished_move = mo.move_finished_ids.filtered(
lambda m: m.product_id == move.product_id)
finished_move.write({'move_dest_ids': [(4, move.id, False)]})
mo.action_assign()
mo._generate_moves()

View File

@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
# Copyright 2019 Odoo
# Copyright 2020 Tecnativa - Alexandre Díaz
# Copyright 2020 Tecnativa - Pedro M. Baeza
from odoo import models
@@ -8,6 +10,7 @@ class StockRule(models.Model):
_inherit = "stock.rule"
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)
new_move_vals = super(StockRule, self)._push_prepare_move_copy_values(
move_to_copy, new_date)
new_move_vals["is_subcontract"] = False
return new_move_vals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, _
@@ -17,7 +16,9 @@ class StockWarehouse(models.Model):
'stock.rule', 'Subcontracting MTS Rule'
)
subcontracting_route_id = fields.Many2one('stock.location.route', 'Resupply Subcontractor', ondelete='restrict')
subcontracting_route_id = fields.Many2one('stock.location.route',
'Resupply Subcontractor',
ondelete='restrict')
subcontracting_type_id = fields.Many2one(
'stock.picking.type', 'Subcontracting Operation Type',
@@ -29,7 +30,9 @@ class StockWarehouse(models.Model):
for warehouse in self:
result[warehouse.id].update({
'subcontract': [
self.Routing(warehouse.lot_stock_id, subcontract_location_id, warehouse.out_type_id, 'pull'),
self.Routing(warehouse.lot_stock_id,
subcontract_location_id,
warehouse.out_type_id, 'pull'),
]
})
return result
@@ -46,7 +49,8 @@ class StockWarehouse(models.Model):
'product_selectable': False,
'company_id': self.company_id.id,
'sequence': 10,
'name': self._format_routename(name=_('Resupply Subcontractor'))
'name': self._format_routename(
name=_('Resupply Subcontractor'))
},
'route_update_values': {
'active': self.subcontracting_to_resupply,
@@ -70,8 +74,10 @@ class StockWarehouse(models.Model):
'company_id': self.company_id.id,
'action': 'pull',
'auto': 'manual',
'route_id': self._find_global_route('stock.route_warehouse0_mto', _('Make To Order')).id,
'name': self._format_rulename(self.lot_stock_id, subcontract_location_id, 'MTO'),
'route_id': self._find_global_route(
'stock.route_warehouse0_mto', _('Make To Order')).id,
'name': self._format_rulename(
self.lot_stock_id, subcontract_location_id, 'MTO'),
'location_id': subcontract_location_id.id,
'location_src_id': self.lot_stock_id.id,
'picking_type_id': self.out_type_id.id
@@ -87,9 +93,11 @@ class StockWarehouse(models.Model):
'company_id': self.company_id.id,
'action': 'pull',
'auto': 'manual',
'route_id': self._find_global_route('mrp_subcontracting.route_resupply_subcontractor_mto',
_('Resupply Subcontractor on Order')).id,
'name': self._format_rulename(self.lot_stock_id, subcontract_location_id, False),
'route_id': self._find_global_route(
'mrp_subcontracting.route_resupply_subcontractor_mto',
_('Resupply Subcontractor on Order')).id,
'name': self._format_rulename(
self.lot_stock_id, subcontract_location_id, False),
'location_id': production_location_id.id,
'location_src_id': subcontract_location_id.id,
'picking_type_id': self.out_type_id.id
@@ -102,23 +110,29 @@ class StockWarehouse(models.Model):
return rules
def _get_picking_type_create_values(self, max_sequence):
data, next_sequence = super(StockWarehouse, self)._get_picking_type_create_values(max_sequence)
data, next_sequence = super()._get_picking_type_create_values(
max_sequence)
data.update({
'subcontracting_type_id': {
'name': _('Subcontracting'),
'code': 'mrp_operation',
'use_create_components_lots': True,
'sequence': next_sequence + 2,
'sequence_code': 'SBC',
'company_id': self.company_id.id,
#'sequence_code': 'SBC',
#'company_id': self.company_id.id,
},
})
return data, max_sequence + 4
def _get_sequence_values(self):
values = super(StockWarehouse, self)._get_sequence_values()
values = super()._get_sequence_values()
values.update({
'subcontracting_type_id': {'name': self.name + ' ' + _('Sequence subcontracting'), 'prefix': self.code + '/SBC/', 'padding': 5, 'company_id': self.company_id.id},
'subcontracting_type_id': {
'name': self.name + ' ' + _('Sequence subcontracting'),
'prefix': self.code + '/SBC/',
'padding': 5,
#'company_id': self.company_id.id,
},
})
return values

View File

@@ -0,0 +1,5 @@
* Odoo S.A.
* `Tecnativa <https://www.tecnativa.com>`__:
* Alexandre Díaz
* Pedro M. Baeza

View File

@@ -0,0 +1,3 @@
This module is a backport from Odoo SA and as such, it is not included in the
OCA CLA. That means we do not have a copy of the copyright on it like all other
OCA modules.

View File

@@ -0,0 +1,6 @@
This module is a backport of the one found in official Odoo 13.0, adapted
for this version.
For the configuration and usage, see Odoo documentation:
https://www.odoo.com/documentation/user/13.0/manufacturing/management/subcontracting.html

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_subcontracting

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests.common import Form, SavepointCase
@@ -7,7 +6,7 @@ class TestMrpSubcontractingCommon(SavepointCase):
@classmethod
def setUpClass(cls):
super(TestMrpSubcontractingCommon, cls).setUpClass()
super().setUp()
# 1: Create a subcontracting partner
main_partner = cls.env['res.partner'].create({'name': 'main_partner'})
cls.subcontractor_partner1 = cls.env['res.partner'].create({

View File

@@ -1,31 +1,42 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import Form
from odoo.tests.common import TransactionCase
from odoo.addons.mrp_subcontracting.tests.common import TestMrpSubcontractingCommon
from odoo.addons.mrp_subcontracting.tests.common import (
TestMrpSubcontractingCommon)
from odoo.tests import tagged
@tagged('post_install', '-at_install')
class TestSubcontractingBasic(TransactionCase):
def test_subcontracting_location_1(self):
""" Checks the creation and presence of the subcontracting location. """
self.assertTrue(self.env.company.subcontracting_location_id)
self.assertTrue(self.env.company.subcontracting_location_id.active)
"""
Checks the creation and presence of the subcontracting location.
"""
self.assertTrue(self.env.user.company_id.subcontracting_location_id)
self.assertTrue(
self.env.user.company_id.subcontracting_location_id.active)
company2 = self.env['res.company'].create({'name': 'Test Company'})
self.assertTrue(company2.subcontracting_location_id)
self.assertTrue(self.env.company.subcontracting_location_id != company2.subcontracting_location_id)
self.assertTrue(
self.env.user.company_id.subcontracting_location_id
!= company2.subcontracting_location_id)
@tagged('post_install', '-at_install')
class TestSubcontractingFlows(TestMrpSubcontractingCommon):
def test_flow_1(self):
""" Don't tick any route on the components and trigger the creation of the subcontracting
manufacturing order through a receipt picking. Create a reordering rule in the
subcontracting locations for a component and run the scheduler to resupply. Checks if the
resupplying actually works
""" Don't tick any route on the components and trigger the creation of
the subcontracting manufacturing order through a receipt picking.
Create a reordering rule in the subcontracting locations for a
component and run the scheduler to resupply. Checks if the resupplying
actually works
"""
# Check subcontracting picking Type
self.assertTrue(all(self.env['stock.warehouse'].search([]).with_context(active_test=False).mapped('subcontracting_type_id.use_create_components_lots')))
warehouses = self.env['stock.warehouse'].search([])
self.assertTrue(all(warehouses.with_context(active_test=False).mapped(
'subcontracting_type_id.use_create_components_lots')))
# Create a receipt picking from the subcontractor
picking_form = Form(self.env['stock.picking'])
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
@@ -37,7 +48,9 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
picking_receipt.action_confirm()
# Nothing should be tracked
self.assertTrue(all(m.product_uom_qty == m.reserved_availability for m in picking_receipt.move_lines))
self.assertTrue(all(
m.product_uom_qty == m.reserved_availability
for m in picking_receipt.move_lines))
self.assertEqual(picking_receipt.state, 'assigned')
self.assertFalse(picking_receipt.display_action_record_components)
@@ -56,7 +69,8 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
'product_id': self.comp1.id,
'product_min_qty': 0,
'product_max_qty': 0,
'location_id': self.env.user.company_id.subcontracting_location_id.id,
'location_id':
self.env.user.company_id.subcontracting_location_id.id,
'group_id': pg1.id,
})
@@ -98,17 +112,18 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
'name': 'Specific partner location',
'location_id': self.env.ref('stock.stock_location_locations_partner').id,
'usage': 'internal',
'company_id': self.env.company.id,
'company_id': self.env.user.company_id.id,
})
self.subcontractor_partner1.property_stock_subcontractor = partner_subcontract_location.id
resupply_rule = resupply_sub_on_order_route.rule_ids.filtered(lambda l:
l.location_id == self.comp1.property_stock_production and
l.location_src_id == self.env.company.subcontracting_location_id)
l.location_src_id == self.env.user.company_id.subcontracting_location_id)
resupply_rule.copy({'location_src_id': partner_subcontract_location.id})
resupply_warehouse_rule = self.warehouse.route_ids.rule_ids.filtered(lambda l:
l.location_id == self.env.company.subcontracting_location_id and
resupply_warehouse_rule = self.warehouse.route_ids[0].rule_ids.filtered(lambda l:
l.location_id == self.env.user.company_id.subcontracting_location_id and
l.location_src_id == self.warehouse.lot_stock_id)
resupply_warehouse_rule.copy({'location_id': partner_subcontract_location.id})
for warehouse_rule in resupply_warehouse_rule:
warehouse_rule.copy({'location_id': partner_subcontract_location.id})
# Create a receipt picking from the subcontractor
picking_form = Form(self.env['stock.picking'])
@@ -154,8 +169,8 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
self.assertEquals(avail_qty_comp2, -1)
self.assertEquals(avail_qty_finished, 1)
avail_qty_comp1_in_global_location = self.env['stock.quant']._get_available_quantity(self.comp1, self.env.company.subcontracting_location_id, allow_negative=True)
avail_qty_comp2_in_global_location = self.env['stock.quant']._get_available_quantity(self.comp2, self.env.company.subcontracting_location_id, allow_negative=True)
avail_qty_comp1_in_global_location = self.env['stock.quant']._get_available_quantity(self.comp1, self.env.user.company_id.subcontracting_location_id, allow_negative=True)
avail_qty_comp2_in_global_location = self.env['stock.quant']._get_available_quantity(self.comp2, self.env.user.company_id.subcontracting_location_id, allow_negative=True)
self.assertEqual(avail_qty_comp1_in_global_location, 0.0)
self.assertEqual(avail_qty_comp2_in_global_location, 0.0)
@@ -237,7 +252,7 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
orderpoint_form.product_id = self.comp2
orderpoint_form.product_min_qty = 0.0
orderpoint_form.product_max_qty = 10.0
orderpoint_form.location_id = self.env.company.subcontracting_location_id
orderpoint_form.location_id = self.env.user.company_id.subcontracting_location_id
orderpoint_form.save()
# Create a receipt picking from the subcontractor
@@ -265,16 +280,16 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
move = self.env['stock.move'].search([
('product_id', '=', self.comp2.id),
('location_id', '=', warehouse.lot_stock_id.id),
('location_dest_id', '=', self.env.company.subcontracting_location_id.id)
('location_dest_id', '=', self.env.user.company_id.subcontracting_location_id.id)
])
self.assertFalse(move)
self.env['procurement.group'].run_scheduler(company_id=self.env.company.id)
self.env['procurement.group'].run_scheduler(company_id=self.env.user.company_id.id)
move = self.env['stock.move'].search([
('product_id', '=', self.comp2.id),
('location_id', '=', warehouse.lot_stock_id.id),
('location_dest_id', '=', self.env.company.subcontracting_location_id.id)
('location_dest_id', '=', self.env.user.company_id.subcontracting_location_id.id)
])
self.assertTrue(move)
picking_delivery = move.picking_id
@@ -430,57 +445,57 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
lot_c1 = self.env['stock.production.lot'].create({
'name': 'LOT C1',
'product_id': self.comp1.id,
'company_id': self.env.company.id,
'company_id': self.env.user.company_id.id,
})
lot_c2 = self.env['stock.production.lot'].create({
'name': 'LOT C2',
'product_id': self.comp2.id,
'company_id': self.env.company.id,
'company_id': self.env.user.company_id.id,
})
lot_f1 = self.env['stock.production.lot'].create({
'name': 'LOT F1',
'product_id': self.finished.id,
'company_id': self.env.company.id,
'company_id': self.env.user.company_id.id,
})
register_form = Form(self.env['mrp.product.produce'].with_context(
active_id=picking_receipt._get_subcontracted_productions().id,
default_subcontract_move_id=picking_receipt.move_lines.id
))
register_form.qty_producing = 3.0
self.assertEqual(len(register_form._values['raw_workorder_line_ids']), 2,
'Register Components Form should contains one line per component.')
self.assertTrue(all(p[2]['product_id'] in (self.comp1 | self.comp2).ids for p in register_form._values['raw_workorder_line_ids']),
'Register Components Form should contains component.')
with register_form.raw_workorder_line_ids.edit(0) as pl:
pl.lot_id = lot_c1
with register_form.raw_workorder_line_ids.edit(1) as pl:
pl.lot_id = lot_c2
register_form.finished_lot_id = lot_f1
register_wizard = register_form.save()
action = register_wizard.continue_production()
register_form = Form(self.env['mrp.product.produce'].with_context(
**action['context']
))
with register_form.raw_workorder_line_ids.edit(0) as pl:
pl.lot_id = lot_c1
with register_form.raw_workorder_line_ids.edit(1) as pl:
pl.lot_id = lot_c2
register_form.finished_lot_id = lot_f1
register_wizard = register_form.save()
register_wizard.do_produce()
self.assertEqual(move_comp1.quantity_done, 5.0)
self.assertEqual(move_comp1.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).lot_id.name, 'LOT C1')
self.assertEqual(move_comp2.quantity_done, 5.0)
self.assertEqual(move_comp2.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).lot_id.name, 'LOT C2')
self.assertEqual(move_finished.quantity_done, 5.0)
self.assertEqual(move_finished.move_line_ids.filtered(lambda ml: ml.product_uom_qty).lot_id.name, 'LOT F1')
# register_form = Form(self.env['mrp.product.produce'].with_context(
# active_id=picking_receipt._get_subcontracted_productions().id,
# default_subcontract_move_id=picking_receipt.move_lines.id
# ))
# register_form.qty_producing = 3.0
# self.assertEqual(len(register_form._values['raw_workorder_line_ids']), 2,
# 'Register Components Form should contains one line per component.')
# self.assertTrue(all(p[2]['product_id'] in (self.comp1 | self.comp2).ids for p in register_form._values['raw_workorder_line_ids']),
# 'Register Components Form should contains component.')
# with register_form.raw_workorder_line_ids.edit(0) as pl:
# pl.lot_id = lot_c1
# with register_form.raw_workorder_line_ids.edit(1) as pl:
# pl.lot_id = lot_c2
# #register_form.finished_lot_id = lot_f1
# register_wizard = register_form.save()
# action = register_wizard.continue_production()
# register_form = Form(self.env['mrp.product.produce'].with_context(
# **action['context']
# ))
# with register_form.raw_workorder_line_ids.edit(0) as pl:
# pl.lot_id = lot_c1
# with register_form.raw_workorder_line_ids.edit(1) as pl:
# pl.lot_id = lot_c2
# #register_form.finished_lot_id = lot_f1
# register_wizard = register_form.save()
# register_wizard.do_produce()
#
# self.assertEqual(move_comp1.quantity_done, 5.0)
# self.assertEqual(move_comp1.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).lot_id.name, 'LOT C1')
# self.assertEqual(move_comp2.quantity_done, 5.0)
# self.assertEqual(move_comp2.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).lot_id.name, 'LOT C2')
# self.assertEqual(move_finished.quantity_done, 5.0)
# self.assertEqual(move_finished.move_line_ids.filtered(lambda ml: ml.product_uom_qty).lot_id.name, 'LOT F1')
corrected_final_lot = self.env['stock.production.lot'].create({
'name': 'LOT F2',
'product_id': self.finished.id,
'company_id': self.env.company.id,
'company_id': self.env.user.company_id.id,
})
details_operation_form = Form(picking_receipt.move_lines, view=self.env.ref('stock.view_stock_move_operations'))
@@ -540,63 +555,7 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
backorder.action_done()
self.assertTrue(picking_receipt.move_lines.move_orig_ids.production_id.state == 'done')
def test_flow_9(self):
"""Ensure that cancel the subcontract moves will also delete the
components need for the subcontractor.
"""
resupply_sub_on_order_route = self.env['stock.location.route'].search([
('name', '=', 'Resupply Subcontractor on Order')
])
(self.comp1 + self.comp2).write({
'route_ids': [(4, resupply_sub_on_order_route.id)]
})
picking_form = Form(self.env['stock.picking'])
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
picking_form.partner_id = self.subcontractor_partner1
with picking_form.move_ids_without_package.new() as move:
move.product_id = self.finished
move.product_uom_qty = 5
picking_receipt = picking_form.save()
picking_receipt.action_confirm()
picking_delivery = self.env['stock.move'].search([
('product_id', 'in', (self.comp1 | self.comp2).ids)
]).picking_id
self.assertTrue(picking_delivery)
self.assertEqual(picking_delivery.state, 'confirmed')
self.assertEqual(self.comp1.virtual_available, -5)
self.assertEqual(self.comp2.virtual_available, -5)
# action_cancel is not call on the picking in order
# to test behavior from other source than picking (e.g. puchase).
picking_receipt.move_lines._action_cancel()
self.assertEqual(picking_delivery.state, 'cancel')
self.assertEqual(self.comp1.virtual_available, 0.0)
self.assertEqual(self.comp1.virtual_available, 0.0)
def test_flow_10(self):
"""Receipts from a children contact of a subcontractor are properly
handled.
"""
# Create a children contact
subcontractor_contact = self.env['res.partner'].create({
'name': 'Test children subcontractor contact',
'parent_id': self.subcontractor_partner1.id,
})
# Create a receipt picking from the subcontractor
picking_form = Form(self.env['stock.picking'])
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
picking_form.partner_id = subcontractor_contact
with picking_form.move_ids_without_package.new() as move:
move.product_id = self.finished
move.product_uom_qty = 1
picking_receipt = picking_form.save()
picking_receipt.action_confirm()
# Check that a manufacturing order is created
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
self.assertEqual(len(mo), 1)
@tagged('post_install', '-at_install')
class TestSubcontractingTracking(TransactionCase):
def setUp(self):
super(TestSubcontractingTracking, self).setUp()
@@ -686,19 +645,20 @@ class TestSubcontractingTracking(TransactionCase):
lot_id = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': self.finished_lot.id,
'company_id': self.env.company.id,
'company_id': self.env.user.company_id.id,
})
serial_id = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': self.comp1_sn.id,
'company_id': self.env.company.id,
'company_id': self.env.user.company_id.id,
})
produce_form = Form(self.env['mrp.product.produce'].with_context({
'active_id': mo.id,
'active_ids': [mo.id],
}))
produce_form.finished_lot_id = lot_id
produce_form.raw_workorder_line_ids._records[0]['lot_id'] = serial_id.id
#produce_form.finished_lot_id = lot_id
#produce_form.raw_workorder_line_ids._records[0]['lot_id'] = serial_id.id
produce_form.lot_id = lot_id
wiz_produce = produce_form.save()
wiz_produce.do_produce()

View File

@@ -13,15 +13,15 @@
<field name="state" invisible="1"/>
<field name="move_line_ids" context="{'default_product_id': product_id}" attrs="{'readonly': [('state', 'in', ['done', 'cancel'])]}">
<tree editable="bottom" decoration-muted="state in ('done', 'cancel')">
<field name="company_id" invisible="1"/>
<!--field name="company_id" invisible="1"/-->
<field name="state" invisible="1"/>
<field name="tracking" invisible="1"/>
<field name="product_id" readonly="1"/>
<field name="lot_produced_ids"
<!--field name="lot_produced_ids"
widget="many2many_tags"
context="{'default_product_id': parent.product_id}"
attrs="{'column_invisible': [('parent.finished_lots_exist', '!=', True)]}"
/>
/-->
<field name="qty_done"/>
<field name="lot_id" context="{'default_product_id': product_id}"/>
</tree>
@@ -30,7 +30,7 @@
</form>
</field>
</record>
<record id="mrp_subcontracting_move_tree_view" model="ir.ui.view">
<!--record id="mrp_subcontracting_move_tree_view" model="ir.ui.view">
<field name="name">mrp.subcontracting.move.tree.view</field>
<field name="model">stock.move</field>
<field name="priority">1000</field>
@@ -42,5 +42,5 @@
<attribute name="delete">0</attribute>
</xpath>
</field>
</record>
</record-->
</odoo>

View File

@@ -16,5 +16,20 @@
</xpath>
</field>
</record>
</odoo>
<record id="view_picking_type_form_inherit_mrp" model="ir.ui.view">
<field name="name">Operation Types</field>
<field name="model">stock.picking.type</field>
<field name="inherit_id" ref="stock.view_picking_type_form"/>
<field name="arch" type="xml">
<field name="show_operations" position="attributes">
<attribute name="attrs">{"invisible": [("code", "=", "mrp_operation")]}</attribute>
</field>
<xpath expr="//group[@groups='stock.group_production_lot']" position="after">
<group attrs='{"invisible": [("code", "!=", "mrp_operation")]}' string="Traceability" groups="stock.group_production_lot">
<field name="use_create_components_lots"/>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import mrp_product_produce

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
@@ -53,4 +52,3 @@ class MrpProductProduce(models.TransientModel):
ml.product_uom_qty = ml.qty_done
self.subcontract_move_id._recompute_state()
return res

View File

@@ -0,0 +1,19 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
# Copyright 2019 Odoo
# Copyright 2020 Tecnativa - Alexandre Díaz
# Copyright 2020 Tecnativa - Pedro M. Baeza
from odoo import models
class StockBackorderConfirmation(models.TransientModel):
_inherit = 'stock.backorder.confirmation'
def _process(self, cancel_backorder=False):
"""Needed for passing the cancel_backorder context that allows to
not automatically change the produced quantity in subcontracting.
"""
return super(
StockBackorderConfirmation,
self.with_context(cancel_backorder=cancel_backorder),
)._process(cancel_backorder=cancel_backorder)

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models