From f32ce8ba46c65ab3385ecaef82bd05fcb4509f41 Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Sun, 10 May 2020 12:56:44 +0200 Subject: [PATCH] [MIG] mrp_subcontracting: Adapt to v12 + make tests to pass --- mrp_subcontracting/README.rst | 87 ++++ mrp_subcontracting/__init__.py | 3 + mrp_subcontracting/__manifest__.py | 17 +- .../data/mrp_subcontracting_data.xml | 5 - .../data/mrp_subcontracting_demo.xml | 2 +- mrp_subcontracting/models/__init__.py | 6 +- mrp_subcontracting/models/mrp_bom.py | 25 +- mrp_subcontracting/models/mrp_production.py | 2 +- mrp_subcontracting/models/product.py | 8 +- mrp_subcontracting/models/res_company.py | 15 +- mrp_subcontracting/models/res_partner.py | 5 +- mrp_subcontracting/models/stock_move.py | 96 ++-- mrp_subcontracting/models/stock_move_line.py | 47 +- mrp_subcontracting/models/stock_picking.py | 71 +-- mrp_subcontracting/models/stock_warehouse.py | 10 +- .../static/description/index.html | 435 ++++++++++++++++ mrp_subcontracting/tests/__init__.py | 2 +- mrp_subcontracting/tests/common.py | 8 +- .../tests/test_subcontracting.py | 489 ++++++++++-------- mrp_subcontracting/views/mrp_bom_views.xml | 3 +- .../views/res_partner_views.xml | 1 + mrp_subcontracting/views/stock_move_views.xml | 40 +- .../views/stock_picking_views.xml | 15 - mrp_subcontracting/wizard/__init__.py | 3 +- .../wizard/mrp_product_produce.py | 60 +-- .../wizard/stock_picking_return.py | 30 +- 26 files changed, 1059 insertions(+), 426 deletions(-) create mode 100644 mrp_subcontracting/README.rst create mode 100644 mrp_subcontracting/static/description/index.html diff --git a/mrp_subcontracting/README.rst b/mrp_subcontracting/README.rst new file mode 100644 index 000000000..d8b33e7d8 --- /dev/null +++ b/mrp_subcontracting/README.rst @@ -0,0 +1,87 @@ +======================= +Subcontract Productions +======================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/github-OCA%2Fmanufacture-lightgray.png?logo=github + :target: https://github.com/OCA/manufacture/tree/12.0/mrp_subcontracting + :alt: OCA/manufacture +.. |badge3| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/manufacture-12-0/manufacture-12-0-mrp_subcontracting + :alt: Translate me on Weblate +.. |badge4| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/129/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| + +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 + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Odoo S.A. +* Tecnativa + +Contributors +~~~~~~~~~~~~ + +* Odoo S.A. +* `Tecnativa `__: + + * Alexandre Díaz + * Pedro M. Baeza + +Other credits +~~~~~~~~~~~~~ + +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. + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/manufacture `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/mrp_subcontracting/__init__.py b/mrp_subcontracting/__init__.py index 9b4296142..a2276d08d 100644 --- a/mrp_subcontracting/__init__.py +++ b/mrp_subcontracting/__init__.py @@ -1,2 +1,5 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + from . import models from . import wizard +from .hooks import uninstall_hook diff --git a/mrp_subcontracting/__manifest__.py b/mrp_subcontracting/__manifest__.py index 204d4eef6..0fdb69d4b 100644 --- a/mrp_subcontracting/__manifest__.py +++ b/mrp_subcontracting/__manifest__.py @@ -1,13 +1,14 @@ -# 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 { - 'name': "mrp_subcontracting", + 'name': "Subcontract Productions", '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', + "author": "Odoo S.A., Tecnativa, Odoo Community Association (OCA)", + 'website': 'https://github.com/OCA/manufacture', + 'category': 'Manufacturing Orders & BOMs', 'depends': ['mrp'], 'data': [ 'data/mrp_subcontracting_data.xml', @@ -21,4 +22,6 @@ 'demo': [ 'data/mrp_subcontracting_demo.xml', ], + "uninstall_hook": "uninstall_hook", + "license": "LGPL-3", } diff --git a/mrp_subcontracting/data/mrp_subcontracting_data.xml b/mrp_subcontracting/data/mrp_subcontracting_data.xml index ba367ad98..7f6955e7e 100644 --- a/mrp_subcontracting/data/mrp_subcontracting_data.xml +++ b/mrp_subcontracting/data/mrp_subcontracting_data.xml @@ -11,10 +11,5 @@ - - - - - diff --git a/mrp_subcontracting/data/mrp_subcontracting_demo.xml b/mrp_subcontracting/data/mrp_subcontracting_demo.xml index 2cc211052..2c1dffdd2 100644 --- a/mrp_subcontracting/data/mrp_subcontracting_demo.xml +++ b/mrp_subcontracting/data/mrp_subcontracting_demo.xml @@ -29,4 +29,4 @@ - \ No newline at end of file + diff --git a/mrp_subcontracting/models/__init__.py b/mrp_subcontracting/models/__init__.py index 238bdbf03..d24a7e395 100644 --- a/mrp_subcontracting/models/__init__.py +++ b/mrp_subcontracting/models/__init__.py @@ -1,13 +1,13 @@ -# Part of Odoo. See LICENSE file for full copyright and licensing details. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). from . import mrp_bom from . import mrp_production from . import product from . import res_company from . import res_partner +from . import stock_location from . import stock_move from . import stock_move_line from . import stock_picking -from . import stock_picking_type +from . import stock_rule from . import stock_warehouse -from . import stock_production_lot diff --git a/mrp_subcontracting/models/mrp_bom.py b/mrp_subcontracting/models/mrp_bom.py index 57a201426..a1793e5bb 100644 --- a/mrp_subcontracting/models/mrp_bom.py +++ b/mrp_subcontracting/models/mrp_bom.py @@ -1,7 +1,9 @@ -# 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 _, api, fields, models -from odoo.exceptions import UserError +from odoo import api, fields, models from odoo.osv.expression import AND @@ -10,8 +12,7 @@ class MrpBom(models.Model): type = fields.Selection(selection_add=[('subcontract', 'Subcontracting')]) subcontractor_ids = fields.Many2many( - 'res.partner', 'mrp_bom_subcontractor', string='Subcontractors', - check_company=True) + 'res.partner', 'mrp_bom_subcontractor', string='Subcontractors') def _bom_subcontract_find(self, product_tmpl=None, product=None, picking_type=None, company_id=False, @@ -29,10 +30,12 @@ class MrpBom(models.Model): 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): + """Helper method that is present on v13 but not in v12. We recreate + it here with v12 conditions. + """ if product: if not product_tmpl: product_tmpl = product.product_tmpl_id @@ -43,21 +46,13 @@ class MrpBom(models.Model): ] 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')), - ] + 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 diff --git a/mrp_subcontracting/models/mrp_production.py b/mrp_subcontracting/models/mrp_production.py index 5f50b11ed..1dc9803db 100644 --- a/mrp_subcontracting/models/mrp_production.py +++ b/mrp_subcontracting/models/mrp_production.py @@ -1,7 +1,7 @@ # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). # Copyright 2020 Tecnativa - Pedro M. Baeza -from odoo import api, fields, models +from odoo import api, models class MrpProduction(models.Model): diff --git a/mrp_subcontracting/models/product.py b/mrp_subcontracting/models/product.py index 27ef4834c..1e0467840 100644 --- a/mrp_subcontracting/models/product.py +++ b/mrp_subcontracting/models/product.py @@ -1,4 +1,7 @@ -# 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 api, fields, models @@ -17,4 +20,5 @@ class SupplierInfo(models.Model): boms = supplier.product_id.variant_bom_ids boms |= supplier.product_tmpl_id.bom_ids.filtered( lambda b: not b.product_id) - supplier.is_subcontractor = supplier.name in boms.subcontractor_ids + supplier.is_subcontractor = ( + supplier.name in boms.mapped('subcontractor_ids')) diff --git a/mrp_subcontracting/models/res_company.py b/mrp_subcontracting/models/res_company.py index 988b17ad9..b5e317e28 100644 --- a/mrp_subcontracting/models/res_company.py +++ b/mrp_subcontracting/models/res_company.py @@ -1,4 +1,7 @@ -# 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 api, fields, models, _ @@ -14,9 +17,15 @@ class ResCompany(models.Model): [('subcontracting_location_id', '=', False)]) company_without_subcontracting_loc._create_subcontracting_location() - def _create_per_company_locations(self): - super(ResCompany, self)._create_per_company_locations() + def create_transit_location(self): + """As there's no standard method for creating locations and we must + create the subcontracting location before the warehouse creation, we + inherit this method for performing the subcontracting location + creation as well. + """ + res = super().create_transit_location() self._create_subcontracting_location() + return res def _create_subcontracting_location(self): parent_location = self.env.ref( diff --git a/mrp_subcontracting/models/res_partner.py b/mrp_subcontracting/models/res_partner.py index 8b7fc9a8b..e537ea7ba 100644 --- a/mrp_subcontracting/models/res_partner.py +++ b/mrp_subcontracting/models/res_partner.py @@ -1,4 +1,7 @@ -# 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 fields, models diff --git a/mrp_subcontracting/models/stock_move.py b/mrp_subcontracting/models/stock_move.py index 81755d753..072db4a16 100644 --- a/mrp_subcontracting/models/stock_move.py +++ b/mrp_subcontracting/models/stock_move.py @@ -1,4 +1,7 @@ -# 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 collections import defaultdict @@ -16,24 +19,22 @@ 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 raw moves 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): - move.show_subcontracting_details_visible = True - else: - move.show_subcontracting_details_visible = False + move.show_subcontracting_details_visible = ( + move.is_subcontract and + move._has_tracked_subcontract_components() and + not float_is_zero( + move.quantity_done, + precision_rounding=move.product_uom.rounding + ) + ) def _compute_show_details_visible(self): """ If the move is subcontract and the components are tracked. Then the show details button is visible. """ - res = super(StockMove, self)._compute_show_details_visible() + res = super()._compute_show_details_visible() for move in self: if not move.is_subcontract: continue @@ -45,11 +46,11 @@ class StockMove(models.Model): def copy(self, default=None): self.ensure_one() if not self.is_subcontract or 'location_id' in default: - return super(StockMove, self).copy(default=default) + return super().copy(default=default) if not default: default = {} default['location_id'] = self.picking_id.location_id.id - return super(StockMove, self).copy(default=default) + return super().copy(default=default) def write(self, values): """ If the initial demand is updated then also update the linked @@ -57,10 +58,12 @@ 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']) - return super(StockMove, self).write(values) + return super().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']) + return super().write(values) def action_show_details(self): """ Open the produce wizard in order to register tracked components for @@ -77,8 +80,8 @@ class StockMove(models.Model): 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: + action = super().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'), ] @@ -104,6 +107,12 @@ 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: @@ -138,10 +147,23 @@ class StockMove(models.Model): *list(subcontract_details_per_picking.keys())).action_assign() return res + def _action_assign(self): + """As we don't have the bypass reservation method in v12 at stock.move + level, we have to trick this method for splitting the assign in + 2 steps, classifying previously the subcontract moves and then + faking location_id.should_bypass_reservation method through + context. + """ + subcontract_moves = self.filtered('is_subcontract') + res = super(StockMove, self - subcontract_moves)._action_assign() + super(StockMove, subcontract_moves.with_context( + mrp_subcontracting_bypass_reservation=True))._action_assign() + return res + def _action_record_components(self): action = self.env.ref('mrp.act_mrp_product_produce').read()[0] action['context'] = dict( - default_production_id=self.move_orig_ids.production_id.id, + active_id=self.move_orig_ids.production_id.id, default_subcontract_move_id=self.id ) return action @@ -161,9 +183,11 @@ class StockMove(models.Model): 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, + sum(move.move_orig_ids.mapped('production_id.qty_produced')), + precision_rounding=rounding + ) > 0: overprocessed_moves |= move if overprocessed_moves: raise UserError(_(""" @@ -177,19 +201,18 @@ operations.""") % ('\n'.join(overprocessed_moves.mapped( def _get_subcontract_bom(self): self.ensure_one() - bom = self.env['mrp.bom'].sudo()._bom_subcontract_find( + return self.env['mrp.bom'].sudo()._bom_subcontract_find( product=self.product_id, picking_type=self.picking_type_id, company_id=self.company_id.id, bom_type='subcontract', subcontractor=self.picking_id.partner_id, ) - return bom 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) + self.move_orig_ids.mapped('production_id.move_raw_ids')) def _prepare_extra_move_vals(self, qty): vals = super()._prepare_extra_move_vals(qty) @@ -201,21 +224,12 @@ operations.""") % ('\n'.join(overprocessed_moves.mapped( 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()._should_bypass_reservation() - if not should_bypass_reservation and self.is_subcontract: - return True - return should_bypass_reservation - def _update_subcontract_order_qty(self, quantity): for move in self: 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'].create({ + 'mo_id': production.id, + 'product_qty': production.product_uom_qty + quantity_change + }).change_prod_qty() diff --git a/mrp_subcontracting/models/stock_move_line.py b/mrp_subcontracting/models/stock_move_line.py index 457f1b2c1..e39c47210 100644 --- a/mrp_subcontracting/models/stock_move_line.py +++ b/mrp_subcontracting/models/stock_move_line.py @@ -1,4 +1,7 @@ -# 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 api, models @@ -9,20 +12,38 @@ 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).mapped( + '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() + # Same explanation as for stock.move.action_assign() + subcontract_amls = self.filtered(lambda x: x.move_id.is_subcontract) + if (self - subcontract_amls) or not subcontract_amls: + res = super(StockMoveLine, self - subcontract_amls).write(values) + if subcontract_amls: + res = super(StockMoveLine, subcontract_amls.with_context( + mrp_subcontracting_bypass_reservation=True)).write(values) + self.filtered(lambda ml: ml.move_id.is_subcontract).mapped( + '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()._should_bypass_reservation( - location) - if not should_bypass_reservation and self.move_id.is_subcontract: - return True - return should_bypass_reservation + def unlink(self): + # Same explanation as for stock.move.action_assign() + subcontract_amls = self.filtered(lambda x: x.move_id.is_subcontract) + if (self - subcontract_amls) or not subcontract_amls: + res = super(StockMoveLine, self - subcontract_amls).unlink() + if subcontract_amls: + res = super(StockMoveLine, subcontract_amls.with_context( + mrp_subcontracting_bypass_reservation=True)).unlink() + return res + + def _action_done(self): + # Same explanation as for stock.move.action_assign() + subcontract_amls = self.filtered(lambda x: x.move_id.is_subcontract) + if (self - subcontract_amls) or not subcontract_amls: + res = super(StockMoveLine, self - subcontract_amls)._action_done() + if subcontract_amls: + res = super(StockMoveLine, subcontract_amls.with_context( + mrp_subcontracting_bypass_reservation=True))._action_done() + return res diff --git a/mrp_subcontracting/models/stock_picking.py b/mrp_subcontracting/models/stock_picking.py index d6345dcd8..589ae708c 100644 --- a/mrp_subcontracting/models/stock_picking.py +++ b/mrp_subcontracting/models/stock_picking.py @@ -1,4 +1,7 @@ -# 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 datetime import timedelta @@ -31,8 +34,9 @@ class StockPicking(models.Model): 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: ( + not mo.check_to_done and mo.state != 'done' + )): picking.display_action_record_components = False continue picking.display_action_record_components = True @@ -42,17 +46,16 @@ class StockPicking(models.Model): # ------------------------------------------------------------------------- def action_done(self): - res = super(StockPicking, self).action_done() productions = self.env['mrp.production'] for picking in self: for move in picking.move_lines: if not move.is_subcontract: continue - production = move.move_orig_ids.production_id + production = move.move_orig_ids.mapped('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() + 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: @@ -72,31 +75,33 @@ class StockPicking(models.Model): 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_id': production.product_id.id, + 'product_qty': move_line.qty_done, 'product_uom_id': move_line.product_uom_id.id, - 'finished_lot_id': move_line.lot_id.id, - 'consumption': 'strict', + 'lot_id': move_line.lot_id.id, }) - produce._generate_produce_lines() - produce._record_production() + produce._onchange_product_qty() + produce.do_produce() productions |= production - for subcontracted_production in productions: - if subcontracted_production.state == 'progress': - 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) - 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), - }) + for subcontracted_production in productions: + if subcontracted_production.check_to_done: + subcontracted_production.button_mark_done() + else: + subcontracted_production.post_inventory() + res = super(StockPicking, self).action_done() + for subcontracted_production in productions: + # For consistency, set the date on production move before the + # date on picking. (Traceability 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.mapped('move_line_ids').write({ + 'date': minimum_date - timedelta(seconds=1), + }) return res def action_record_components(self): @@ -156,12 +161,10 @@ class StockPicking(models.Model): 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)) - self.env['stock.move'].create(mo._get_moves_raw_values()) - + force_company=move.company_id.id + ).create(self._prepare_subcontract_mo_vals(move, bom)) # Link the finished to the receipt move. 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._generate_moves() + mo.action_assign() diff --git a/mrp_subcontracting/models/stock_warehouse.py b/mrp_subcontracting/models/stock_warehouse.py index f7cfeb309..a4a6e42c6 100644 --- a/mrp_subcontracting/models/stock_warehouse.py +++ b/mrp_subcontracting/models/stock_warehouse.py @@ -1,4 +1,7 @@ -# 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 fields, models, _ @@ -116,10 +119,7 @@ class StockWarehouse(models.Model): '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, }, }) return data, max_sequence + 4 @@ -131,7 +131,7 @@ class StockWarehouse(models.Model): 'name': self.name + ' ' + _('Sequence subcontracting'), 'prefix': self.code + '/SBC/', 'padding': 5, - #'company_id': self.company_id.id, + 'company_id': self.company_id.id, }, }) return values diff --git a/mrp_subcontracting/static/description/index.html b/mrp_subcontracting/static/description/index.html new file mode 100644 index 000000000..46015e8f4 --- /dev/null +++ b/mrp_subcontracting/static/description/index.html @@ -0,0 +1,435 @@ + + + + + + +Subcontract Productions + + + +
+

Subcontract Productions

+ + +

Beta OCA/manufacture Translate me on Weblate Try me on Runbot

+

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

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Odoo S.A.
  • +
  • Tecnativa
  • +
+
+
+

Contributors

+
    +
  • Odoo S.A.
  • +
  • Tecnativa:
      +
    • Alexandre Díaz
    • +
    • Pedro M. Baeza
    • +
    +
  • +
+
+
+

Other credits

+

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.

+
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/manufacture project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/mrp_subcontracting/tests/__init__.py b/mrp_subcontracting/tests/__init__.py index 62235e74f..2573fd5a4 100644 --- a/mrp_subcontracting/tests/__init__.py +++ b/mrp_subcontracting/tests/__init__.py @@ -1,3 +1,3 @@ -# Part of Odoo. See LICENSE file for full copyright and licensing details. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). from . import test_subcontracting diff --git a/mrp_subcontracting/tests/common.py b/mrp_subcontracting/tests/common.py index ffa645120..339dd30d5 100644 --- a/mrp_subcontracting/tests/common.py +++ b/mrp_subcontracting/tests/common.py @@ -1,12 +1,16 @@ -# 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.tests.common import Form, SavepointCase + class TestMrpSubcontractingCommon(SavepointCase): @classmethod def setUpClass(cls): - super().setUp() + super().setUpClass() # 1: Create a subcontracting partner main_partner = cls.env['res.partner'].create({'name': 'main_partner'}) cls.subcontractor_partner1 = cls.env['res.partner'].create({ diff --git a/mrp_subcontracting/tests/test_subcontracting.py b/mrp_subcontracting/tests/test_subcontracting.py index 4a841dd81..a604e022d 100644 --- a/mrp_subcontracting/tests/test_subcontracting.py +++ b/mrp_subcontracting/tests/test_subcontracting.py @@ -1,9 +1,11 @@ -# 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.tests import Form from odoo.tests.common import TransactionCase -from odoo.addons.mrp_subcontracting.tests.common import ( - TestMrpSubcontractingCommon) +from .common import TestMrpSubcontractingCommon from odoo.tests import tagged @@ -33,10 +35,6 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): component and run the scheduler to resupply. Checks if the resupplying actually works """ - # Check subcontracting picking Type - 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') @@ -46,14 +44,12 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): move.product_uom_qty = 1 picking_receipt = picking_form.save() 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.assertEqual(picking_receipt.state, 'assigned') self.assertFalse(picking_receipt.display_action_record_components) - # Check the created manufacturing order mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)]) self.assertEqual(len(mo), 1) @@ -61,7 +57,6 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): wh = picking_receipt.picking_type_id.warehouse_id self.assertEquals(mo.picking_type_id, wh.subcontracting_type_id) self.assertFalse(mo.picking_type_id.active) - # Create a RR pg1 = self.env['procurement.group'].create({}) self.env['stock.warehouse.orderpoint'].create({ @@ -73,7 +68,6 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): self.env.user.company_id.subcontracting_location_id.id, 'group_id': pg1.id, }) - # Run the scheduler and check the created picking self.env['procurement.group'].run_scheduler() picking = self.env['stock.picking'].search([('group_id', '=', pg1.id)]) @@ -82,49 +76,68 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): picking_receipt.move_lines.quantity_done = 1 picking_receipt.button_validate() self.assertEquals(mo.state, 'done') - - # Available quantities should be negative at the subcontracting location for each components - avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True) - avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True) - avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished, wh.lot_stock_id) + # Available quantities should be negative at the subcontracting + # location for each components + avail_qty_comp1 = self.env['stock.quant']._get_available_quantity( + self.comp1, + self.subcontractor_partner1.property_stock_subcontractor, + allow_negative=True) + avail_qty_comp2 = self.env['stock.quant']._get_available_quantity( + self.comp2, + self.subcontractor_partner1.property_stock_subcontractor, + allow_negative=True) + avail_qty_finished = self.env['stock.quant']._get_available_quantity( + self.finished, wh.lot_stock_id) self.assertEquals(avail_qty_comp1, -1) self.assertEquals(avail_qty_comp2, -1) self.assertEquals(avail_qty_finished, 1) - # Ensure returns to subcontractor location - return_form = Form(self.env['stock.return.picking'].with_context(active_id=picking_receipt.id, active_model='stock.picking')) - return_wizard = return_form.save() + return_wizard = self.env['stock.return.picking'].with_context( + active_id=picking_receipt.id, active_model='stock.picking', + ).create({}) return_picking_id, pick_type_id = return_wizard._create_returns() return_picking = self.env['stock.picking'].browse(return_picking_id) self.assertEqual(len(return_picking), 1) - self.assertEqual(return_picking.move_lines.location_dest_id, self.subcontractor_partner1.property_stock_subcontractor) + self.assertEqual( + return_picking.move_lines.location_dest_id, + self.subcontractor_partner1.property_stock_subcontractor) def test_flow_2(self): - """ Tick "Resupply Subcontractor on Order" on the components and trigger the creation of - the subcontracting manufacturing order through a receipt picking. Checks if the resupplying - actually works. Also set a different subcontracting location on the partner. + """ Tick "Resupply Subcontractor on Order" on the components and + trigger the creation of the subcontracting manufacturing order through + a receipt picking. Checks if the resupplying actually works. Also set a + different subcontracting location on the partner. """ - # Tick "resupply subconractor on order" - 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, None)]}) + # Tick "resupply subcontractor on order" + 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)]}) # Create a different subcontract location partner_subcontract_location = self.env['stock.location'].create({ 'name': 'Specific partner location', - 'location_id': self.env.ref('stock.stock_location_locations_partner').id, + 'location_id': self.env.ref( + 'stock.stock_location_locations_partner').id, 'usage': 'internal', '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.user.company_id.subcontracting_location_id) - resupply_rule.copy({'location_src_id': partner_subcontract_location.id}) - 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) + 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.user.company_id.subcontracting_location_id)) + resupply_rule.copy({ + 'location_src_id': partner_subcontract_location.id}) + resupply_warehouse_rule = ( + self.warehouse.mapped('route_ids.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))) for warehouse_rule in resupply_warehouse_rule: - warehouse_rule.copy({'location_id': partner_subcontract_location.id}) - + warehouse_rule.copy({ + 'location_id': partner_subcontract_location.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') @@ -134,62 +147,71 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): move.product_uom_qty = 1 picking_receipt = picking_form.save() picking_receipt.action_confirm() - # Nothing should be tracked self.assertFalse(picking_receipt.display_action_record_components) - # Pickings should directly be created mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)]) self.assertEqual(len(mo.picking_ids), 1) self.assertEquals(mo.state, 'confirmed') self.assertEqual(len(mo.picking_ids.move_lines), 2) - picking = mo.picking_ids wh = picking.picking_type_id.warehouse_id - # The picking should be a delivery order self.assertEquals(picking.picking_type_id, wh.out_type_id) - self.assertEquals(mo.picking_type_id, wh.subcontracting_type_id) self.assertFalse(mo.picking_type_id.active) - # No manufacturing order for `self.comp2` - comp2mo = self.env['mrp.production'].search([('bom_id', '=', self.comp2_bom.id)]) + comp2mo = self.env['mrp.production'].search([ + ('bom_id', '=', self.comp2_bom.id)]) self.assertEqual(len(comp2mo), 0) - picking_receipt.move_lines.quantity_done = 1 picking_receipt.button_validate() self.assertEquals(mo.state, 'done') - - # Available quantities should be negative at the subcontracting location for each components - avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True) - avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True) - avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished, wh.lot_stock_id) + # Available quantities should be negative at the subcontracting + # location for each components + avail_qty_comp1 = self.env['stock.quant']._get_available_quantity( + self.comp1, + self.subcontractor_partner1.property_stock_subcontractor, + allow_negative=True) + avail_qty_comp2 = self.env['stock.quant']._get_available_quantity( + self.comp2, + self.subcontractor_partner1.property_stock_subcontractor, + allow_negative=True) + avail_qty_finished = self.env['stock.quant']._get_available_quantity( + self.finished, wh.lot_stock_id) self.assertEquals(avail_qty_comp1, -1) 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.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) + 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) def test_flow_3(self): - """ Tick "Resupply Subcontractor on Order" and "MTO" on the components and trigger the - creation of the subcontracting manufacturing order through a receipt picking. Checks if the - resupplying actually works. One of the component has also "manufacture" set and a BOM - linked. Checks that an MO is created for this one. + """ Tick "Resupply Subcontractor on Order" and "MTO" on the components + and trigger the creation of the subcontracting manufacturing order + through a receipt picking. Checks if the resupplying actually works. + One of the component has also "manufacture" set and a BOM linked. + Checks that an MO is created for this one. """ - # Tick "resupply subconractor on order" - 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, None)]}) - + # Tick "resupply subcontractor on order" + resupply_sub_on_order_route = self.env.ref( + 'mrp_subcontracting.route_resupply_subcontractor_mto') + (self.comp1 + self.comp2).write({ + 'route_ids': [(4, resupply_sub_on_order_route.id, None)]}) # Tick "manufacture" and MTO on self.comp2 - mto_route = self.env['stock.location.route'].search([('name', '=', 'Replenish on Order (MTO)')]) - manufacture_route = self.env['stock.location.route'].search([('name', '=', 'Manufacture')]) + mto_route = self.env.ref('stock.route_warehouse0_mto') + manufacture_route = self.env.ref('mrp.route_warehouse0_manufacture') self.comp2.write({'route_ids': [(4, manufacture_route.id, None)]}) self.comp2.write({'route_ids': [(4, mto_route.id, None)]}) - # 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') @@ -199,38 +221,41 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): move.product_uom_qty = 1 picking_receipt = picking_form.save() picking_receipt.action_confirm() - # Nothing should be tracked self.assertFalse(picking_receipt.display_action_record_components) - # Pickings should directly be created mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)]) self.assertEquals(mo.state, 'confirmed') - picking_delivery = mo.picking_ids self.assertEqual(len(picking_delivery), 1) self.assertEqual(len(picking_delivery.move_lines), 2) self.assertEquals(picking_delivery.origin, picking_receipt.name) - self.assertEquals(picking_delivery.partner_id, picking_receipt.partner_id) - + self.assertEquals( + picking_delivery.partner_id, picking_receipt.partner_id) # The picking should be a delivery order wh = picking_receipt.picking_type_id.warehouse_id self.assertEquals(mo.picking_ids.picking_type_id, wh.out_type_id) - self.assertEquals(mo.picking_type_id, wh.subcontracting_type_id) self.assertFalse(mo.picking_type_id.active) - # As well as a manufacturing order for `self.comp2` - comp2mo = self.env['mrp.production'].search([('bom_id', '=', self.comp2_bom.id)]) + comp2mo = self.env['mrp.production'].search([ + ('bom_id', '=', self.comp2_bom.id)]) self.assertEqual(len(comp2mo), 1) picking_receipt.move_lines.quantity_done = 1 picking_receipt.button_validate() self.assertEquals(mo.state, 'done') - - # Available quantities should be negative at the subcontracting location for each components - avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True) - avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True) - avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished, wh.lot_stock_id) + # Available quantities should be negative at the subcontracting + # location for each components + avail_qty_comp1 = self.env['stock.quant']._get_available_quantity( + self.comp1, + self.subcontractor_partner1.property_stock_subcontractor, + allow_negative=True) + avail_qty_comp2 = self.env['stock.quant']._get_available_quantity( + self.comp2, + self.subcontractor_partner1.property_stock_subcontractor, + allow_negative=True) + avail_qty_finished = self.env['stock.quant']._get_available_quantity( + self.finished, wh.lot_stock_id) self.assertEquals(avail_qty_comp1, -1) self.assertEquals(avail_qty_comp2, -1) self.assertEquals(avail_qty_finished, 1) @@ -238,23 +263,24 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): def test_flow_4(self): """ Tick "Manufacture" and "MTO" on the components and trigger the creation of the subcontracting manufacturing order through a receipt - picking. Checks that the delivery to the subcontractor is not created - at the receipt creation. Then run the scheduler and check that - the delivery and MO exist. + picking. Checks that the delivery to the subcontractor and MO exist. + + NOTE: This is different from v13, as the MO doesn't have a draft state, + and thus, we can't control through scheduler the creation of the rest + of the elements. """ # Tick "manufacture" and MTO on self.comp2 - mto_route = self.env['stock.location.route'].search([('name', '=', 'Replenish on Order (MTO)')]) - manufacture_route = self.env['stock.location.route'].search([('name', '=', 'Manufacture')]) + mto_route = self.env.ref('stock.route_warehouse0_mto') + manufacture_route = self.env.ref('mrp.route_warehouse0_manufacture') self.comp2.write({'route_ids': [(4, manufacture_route.id, None)]}) self.comp2.write({'route_ids': [(4, mto_route.id, None)]}) - orderpoint_form = Form(self.env['stock.warehouse.orderpoint']) 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.user.company_id.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 picking_form = Form(self.env['stock.picking']) picking_form.picking_type_id = self.env.ref('stock.picking_type_in') @@ -264,60 +290,42 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): move.product_uom_qty = 1 picking_receipt = picking_form.save() picking_receipt.action_confirm() - warehouse = picking_receipt.picking_type_id.warehouse_id - # Pickings should directly be created mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)]) self.assertEquals(mo.state, 'confirmed') - - picking_delivery = mo.picking_ids - self.assertFalse(picking_delivery) - - picking_delivery = self.env['stock.picking'].search([('origin', 'ilike', '%' + picking_receipt.name + '%')]) - self.assertFalse(picking_delivery) - move = self.env['stock.move'].search([ ('product_id', '=', self.comp2.id), ('location_id', '=', warehouse.lot_stock_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.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.user.company_id.subcontracting_location_id.id) + ('location_dest_id', '=', + self.env.user.company_id.subcontracting_location_id.id), ]) self.assertTrue(move) picking_delivery = move.picking_id self.assertTrue(picking_delivery) - self.assertEqual(move.product_uom_qty, 11.0) - + self.assertEqual(move.product_uom_qty, 1.0) # As well as a manufacturing order for `self.comp2` - comp2mo = self.env['mrp.production'].search([('bom_id', '=', self.comp2_bom.id)]) + comp2mo = self.env['mrp.production'].search([ + ('bom_id', '=', self.comp2_bom.id)]) self.assertEqual(len(comp2mo), 1) def test_flow_5(self): """ Check that the correct BoM is chosen accordingly to the partner """ # We create a second partner of type subcontractor - main_partner_2 = self.env['res.partner'].create({'name': 'main_partner'}) + main_partner_2 = self.env['res.partner'].create({ + 'name': 'main_partner'}) subcontractor_partner2 = self.env['res.partner'].create({ 'name': 'subcontractor_partner', 'parent_id': main_partner_2.id, 'company_id': self.env.ref('base.main_company').id }) - # We create a different BoM for the same product comp3 = self.env['product.product'].create({ 'name': 'Component1', 'type': 'product', 'categ_id': self.env.ref('product.product_category_all').id, }) - bom_form = Form(self.env['mrp.bom']) bom_form.type = 'subcontract' bom_form.product_tmpl_id = self.finished.product_tmpl_id @@ -328,11 +336,11 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): bom_line.product_id = comp3 bom_line.product_qty = 1 bom2 = bom_form.save() - # We assign the second BoM to the new partner - self.bom.write({'subcontractor_ids': [(4, self.subcontractor_partner1.id, None)]}) - bom2.write({'subcontractor_ids': [(4, subcontractor_partner2.id, None)]}) - + self.bom.write({'subcontractor_ids': [ + (4, self.subcontractor_partner1.id, None)]}) + bom2.write({'subcontractor_ids': [ + (4, subcontractor_partner2.id, None)]}) # Create a receipt picking from the subcontractor1 picking_form = Form(self.env['stock.picking']) picking_form.picking_type_id = self.env.ref('stock.picking_type_in') @@ -342,7 +350,6 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): move.product_uom_qty = 1 picking_receipt1 = picking_form.save() picking_receipt1.action_confirm() - # Create a receipt picking from the subcontractor2 picking_form = Form(self.env['stock.picking']) picking_form.picking_type_id = self.env.ref('stock.picking_type_in') @@ -352,9 +359,10 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): move.product_uom_qty = 1 picking_receipt2 = picking_form.save() picking_receipt2.action_confirm() - - mo_pick1 = picking_receipt1.move_lines.mapped('move_orig_ids.production_id') - mo_pick2 = picking_receipt2.move_lines.mapped('move_orig_ids.production_id') + mo_pick1 = picking_receipt1.move_lines.mapped( + 'move_orig_ids.production_id') + mo_pick2 = picking_receipt2.move_lines.mapped( + 'move_orig_ids.production_id') self.assertEquals(len(mo_pick1), 1) self.assertEquals(len(mo_pick2), 1) self.assertEquals(mo_pick1.bom_id, self.bom) @@ -364,7 +372,8 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): """ Extra quantity on the move. """ # We create a second partner of type subcontractor - main_partner_2 = self.env['res.partner'].create({'name': 'main_partner'}) + main_partner_2 = self.env['res.partner'].create({ + 'name': 'main_partner'}) subcontractor_partner2 = self.env['res.partner'].create({ 'name': 'subcontractor_partner', 'parent_id': main_partner_2.id, @@ -391,8 +400,10 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): bom2 = bom_form.save() # We assign the second BoM to the new partner - self.bom.write({'subcontractor_ids': [(4, self.subcontractor_partner1.id, None)]}) - bom2.write({'subcontractor_ids': [(4, subcontractor_partner2.id, None)]}) + self.bom.write({'subcontractor_ids': [ + (4, self.subcontractor_partner1.id, None)]}) + bom2.write({'subcontractor_ids': [ + (4, subcontractor_partner2.id, None)]}) # Create a receipt picking from the subcontractor1 picking_form = Form(self.env['stock.picking']) @@ -407,7 +418,8 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): picking_receipt.move_lines.quantity_done = 3.0 picking_receipt.action_done() mo = picking_receipt._get_subcontracted_productions() - move_comp1 = mo.move_raw_ids.filtered(lambda m: m.product_id == self.comp1) + move_comp1 = mo.move_raw_ids.filtered( + lambda m: m.product_id == self.comp1) move_comp3 = mo.move_raw_ids.filtered(lambda m: m.product_id == comp3) self.assertEqual(sum(move_comp1.mapped('product_uom_qty')), 3.0) self.assertEqual(sum(move_comp3.mapped('product_uom_qty')), 6.0) @@ -419,8 +431,8 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): def test_flow_7(self): """ Process a subcontracting receipt with tracked component and - finished product. Simulate the regiter components button. - Once the components are registered, try to do a correction on exisiting + finished product. Simulate the register components button. + Once the components are registered, try to do a correction on existing move lines and check that the subcontracting document is updated. """ # Create a receipt picking from the subcontractor @@ -434,101 +446,114 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): picking_receipt = picking_form.save() picking_receipt.action_confirm() mo = picking_receipt.move_lines.move_orig_ids.production_id - move_comp1 = mo.move_raw_ids.filtered(lambda m: m.product_id == self.comp1) - move_comp2 = mo.move_raw_ids.filtered(lambda m: m.product_id == self.comp2) + move_comp1 = mo.move_raw_ids.filtered( + lambda m: m.product_id == self.comp1) + move_comp2 = mo.move_raw_ids.filtered( + lambda m: m.product_id == self.comp2) # move_finished is linked to receipt and not MO finished move. move_finished = picking_receipt.move_lines - self.assertEqual(move_comp1.quantity_done, 0) self.assertEqual(move_comp2.quantity_done, 0) - lot_c1 = self.env['stock.production.lot'].create({ 'name': 'LOT C1', 'product_id': self.comp1.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.user.company_id.id, }) lot_f1 = self.env['stock.production.lot'].create({ 'name': 'LOT F1', 'product_id': self.finished.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') - + context = { + 'active_id': picking_receipt._get_subcontracted_productions().id, + 'default_subcontract_move_id': picking_receipt.move_lines.id, + } + register_form = Form(self.env['mrp.product.produce'].with_context( + context)) + register_form.product_qty = 3.0 + self.assertEqual( + len(register_form._values['produce_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['produce_line_ids']), + 'Register Components Form should contains component.') + with register_form.produce_line_ids.edit(0) as pl: + pl.lot_id = lot_c1 + with register_form.produce_line_ids.edit(1) as pl: + pl.lot_id = lot_c2 + register_form.lot_id = lot_f1 + register_wizard = register_form.save() + register_wizard.do_produce() + register_form = Form(self.env['mrp.product.produce'].with_context( + context)) + with register_form.produce_line_ids.edit(0) as pl: + pl.lot_id = lot_c1 + with register_form.produce_line_ids.edit(1) as pl: + pl.lot_id = lot_c2 + register_form.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.mapped('lot_id.name')[0], 'LOT C1') + self.assertEqual(move_comp2.quantity_done, 5.0) + self.assertEqual( + move_comp2.move_line_ids.mapped('lot_id.name')[0], 'LOT C2') + self.assertEqual(move_finished.quantity_done, 5.0) + self.assertEqual( + move_finished.move_line_ids.mapped('lot_id.name')[0], 'LOT F1') corrected_final_lot = self.env['stock.production.lot'].create({ 'name': 'LOT F2', 'product_id': self.finished.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')) + details_operation_form = Form( + picking_receipt.move_lines, + view=self.env.ref('stock.view_stock_move_operations')) for i in range(len(details_operation_form._values['move_line_ids'])): with details_operation_form.move_line_ids.edit(i) as ml: if ml._values['qty_done']: ml.lot_id = corrected_final_lot details_operation_form.save() - move_raw_comp_1 = picking_receipt.move_lines.move_orig_ids.production_id.move_raw_ids.filtered(lambda m: m.product_id == self.comp1) - move_raw_comp_2 = picking_receipt.move_lines.move_orig_ids.production_id.move_raw_ids.filtered(lambda m: m.product_id == self.comp2) - - details_subcontract_moves_form = Form(move_raw_comp_1, view=self.env.ref('mrp_subcontracting.mrp_subcontracting_move_form_view')) - for i in range(len(details_subcontract_moves_form._values['move_line_ids'])): + orig_moves = picking_receipt.move_lines.move_orig_ids + move_raw_comp_1 = orig_moves.production_id.move_raw_ids.filtered( + lambda m: m.product_id == self.comp1) + move_raw_comp_2 = orig_moves.production_id.move_raw_ids.filtered( + lambda m: m.product_id == self.comp2) + details_subcontract_moves_form = Form( + move_raw_comp_1, + view=self.env.ref( + 'mrp_subcontracting.mrp_subcontracting_move_form_view')) + for i in range(len( + details_subcontract_moves_form._values['move_line_ids'])): with details_subcontract_moves_form.move_line_ids.edit(i) as sc: if sc._values['qty_done']: - sc.lot_produced_ids.remove(index=0) - sc.lot_produced_ids.add(corrected_final_lot) + sc.lot_produced_id = corrected_final_lot details_subcontract_moves_form.save() - details_subcontract_moves_form = Form(move_raw_comp_2, view=self.env.ref('mrp_subcontracting.mrp_subcontracting_move_form_view')) - for i in range(len(details_subcontract_moves_form._values['move_line_ids'])): + details_subcontract_moves_form = Form( + move_raw_comp_2, + view=self.env.ref( + 'mrp_subcontracting.mrp_subcontracting_move_form_view')) + for i in range(len( + details_subcontract_moves_form._values['move_line_ids'])): with details_subcontract_moves_form.move_line_ids.edit(i) as sc: if sc._values['qty_done']: - sc.lot_produced_ids.remove(index=0) - sc.lot_produced_ids.add(corrected_final_lot) + sc.lot_produced_id = corrected_final_lot details_subcontract_moves_form.save() - - self.assertEqual(move_comp1.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).lot_produced_ids.name, 'LOT F2') - self.assertEqual(move_comp2.move_line_ids.filtered(lambda ml: not ml.product_uom_qty).lot_produced_ids.name, 'LOT F2') + self.assertEqual( + move_comp1.move_line_ids.mapped('lot_produced_id.name')[0], + 'LOT F2') + self.assertEqual( + move_comp2.move_line_ids.mapped('lot_produced_id.name')[0], + 'LOT F2') def test_flow_8(self): - 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, None)]}) - + 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, None)]}) # 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') @@ -538,29 +563,67 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): move.product_uom_qty = 5 picking_receipt = picking_form.save() picking_receipt.action_confirm() - picking_receipt.move_lines.quantity_done = 3 backorder_wiz = picking_receipt.button_validate() - backorder_wiz = self.env['stock.backorder.confirmation'].browse(backorder_wiz['res_id']) + backorder_wiz = self.env['stock.backorder.confirmation'].browse( + backorder_wiz['res_id']) backorder_wiz.process() - - backorder = self.env['stock.picking'].search([('backorder_id', '=', picking_receipt.id)]) + backorder = self.env['stock.picking'].search([ + ('backorder_id', '=', picking_receipt.id)]) self.assertTrue(backorder) self.assertEqual(backorder.move_lines.product_uom_qty, 2) - subcontract_order = backorder.move_lines.move_orig_ids.production_id.filtered(lambda p: p.state != 'done') + orig_moves = backorder.move_lines.move_orig_ids + subcontract_order = orig_moves.mapped('production_id').filtered( + lambda p: p.state != 'done') self.assertTrue(subcontract_order) self.assertEqual(subcontract_order.product_uom_qty, 5) self.assertEqual(subcontract_order.qty_produced, 3) backorder.move_lines.quantity_done = 2 backorder.action_done() - self.assertTrue(picking_receipt.move_lines.move_orig_ids.production_id.state == 'done') + orig_moves = picking_receipt.move_lines.move_orig_ids + self.assertTrue(orig_moves.mapped('production_id').state == 'done') + + def test_flow_9(self): + """Ensure that cancel the subcontract moves will also delete the + components need for the subcontractor. + """ + # TODO: Fix + 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) + ]).mapped('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) + @tagged('post_install', '-at_install') class TestSubcontractingTracking(TransactionCase): def setUp(self): super(TestSubcontractingTracking, self).setUp() # 1: Create a subcontracting partner - main_company_1 = self.env['res.partner'].create({'name': 'main_partner'}) + main_company_1 = self.env['res.partner'].create({ + 'name': 'main_partner'}) self.subcontractor_partner1 = self.env['res.partner'].create({ 'name': 'Subcontractor 1', 'parent_id': main_company_1.id, @@ -601,7 +664,8 @@ class TestSubcontractingTracking(TransactionCase): self.bom_tracked = bom_form.save() def test_flow_tracked_1(self): - """ This test mimics test_flow_1 but with a BoM that has tracking included in it. + """ This test mimics test_flow_1 but with a BoM that has tracking + included in it. """ # Create a receipt picking from the subcontractor picking_form = Form(self.env['stock.picking']) @@ -612,19 +676,17 @@ class TestSubcontractingTracking(TransactionCase): move.product_uom_qty = 1 picking_receipt = picking_form.save() picking_receipt.action_confirm() - # We should be able to call the 'record_components' button self.assertTrue(picking_receipt.display_action_record_components) - # Check the created manufacturing order - mo = self.env['mrp.production'].search([('bom_id', '=', self.bom_tracked.id)]) + mo = self.env['mrp.production'].search([ + ('bom_id', '=', self.bom_tracked.id)]) self.assertEqual(len(mo), 1) self.assertEquals(mo.state, 'confirmed') self.assertEqual(len(mo.picking_ids), 0) wh = picking_receipt.picking_type_id.warehouse_id self.assertEquals(mo.picking_type_id, wh.subcontracting_type_id) self.assertFalse(mo.picking_type_id.active) - # Create a RR pg1 = self.env['procurement.group'].create({}) self.env['stock.warehouse.orderpoint'].create({ @@ -632,48 +694,51 @@ class TestSubcontractingTracking(TransactionCase): 'product_id': self.comp1_sn.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, }) - # Run the scheduler and check the created picking self.env['procurement.group'].run_scheduler() picking = self.env['stock.picking'].search([('group_id', '=', pg1.id)]) self.assertEqual(len(picking), 1) self.assertEquals(picking.picking_type_id, wh.out_type_id) - lot_id = self.env['stock.production.lot'].create({ 'name': 'lot1', 'product_id': self.finished_lot.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.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.lot_id = lot_id + with produce_form.produce_line_ids.edit(0) as pl: + pl.lot_id = serial_id produce_form.lot_id = lot_id wiz_produce = produce_form.save() wiz_produce.do_produce() - # We should not be able to call the 'record_components' button self.assertFalse(picking_receipt.display_action_record_components) - picking_receipt.move_lines.quantity_done = 1 picking_receipt.move_lines.move_line_ids.lot_id = lot_id.id picking_receipt.button_validate() self.assertEquals(mo.state, 'done') - - # Available quantities should be negative at the subcontracting location for each components - avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1_sn, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True) - avail_qty_comp2 = self.env['stock.quant']._get_available_quantity(self.comp2, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True) - avail_qty_finished = self.env['stock.quant']._get_available_quantity(self.finished_lot, wh.lot_stock_id) + # Available quantities should be negative at the subcontracting + # location for each components + avail_qty_comp1 = self.env['stock.quant']._get_available_quantity( + self.comp1_sn, + self.subcontractor_partner1.property_stock_subcontractor, + allow_negative=True) + avail_qty_comp2 = self.env['stock.quant']._get_available_quantity( + self.comp2, + self.subcontractor_partner1.property_stock_subcontractor, + allow_negative=True) + avail_qty_finished = self.env['stock.quant']._get_available_quantity( + self.finished_lot, wh.lot_stock_id) self.assertEquals(avail_qty_comp1, -1) self.assertEquals(avail_qty_comp2, -1) self.assertEquals(avail_qty_finished, 1) diff --git a/mrp_subcontracting/views/mrp_bom_views.xml b/mrp_subcontracting/views/mrp_bom_views.xml index dacf4362a..9242080e3 100644 --- a/mrp_subcontracting/views/mrp_bom_views.xml +++ b/mrp_subcontracting/views/mrp_bom_views.xml @@ -6,9 +6,8 @@ - + - diff --git a/mrp_subcontracting/views/res_partner_views.xml b/mrp_subcontracting/views/res_partner_views.xml index ad54ee15c..4c648d2c6 100644 --- a/mrp_subcontracting/views/res_partner_views.xml +++ b/mrp_subcontracting/views/res_partner_views.xml @@ -7,6 +7,7 @@ + diff --git a/mrp_subcontracting/views/stock_move_views.xml b/mrp_subcontracting/views/stock_move_views.xml index e0e963258..3d156d021 100644 --- a/mrp_subcontracting/views/stock_move_views.xml +++ b/mrp_subcontracting/views/stock_move_views.xml @@ -13,15 +13,13 @@ - - + /> @@ -30,17 +28,35 @@ - + diff --git a/mrp_subcontracting/views/stock_picking_views.xml b/mrp_subcontracting/views/stock_picking_views.xml index 621628037..e5bd0be43 100644 --- a/mrp_subcontracting/views/stock_picking_views.xml +++ b/mrp_subcontracting/views/stock_picking_views.xml @@ -17,19 +17,4 @@ - - Operation Types - stock.picking.type - - - - {"invisible": [("code", "=", "mrp_operation")]} - - - - - - - - diff --git a/mrp_subcontracting/wizard/__init__.py b/mrp_subcontracting/wizard/__init__.py index bb75397f0..2a56e5148 100644 --- a/mrp_subcontracting/wizard/__init__.py +++ b/mrp_subcontracting/wizard/__init__.py @@ -1,4 +1,5 @@ -# Part of Odoo. See LICENSE file for full copyright and licensing details. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). from . import mrp_product_produce +from . import stock_backorder_confirmation from . import stock_picking_return diff --git a/mrp_subcontracting/wizard/mrp_product_produce.py b/mrp_subcontracting/wizard/mrp_product_produce.py index 215dab713..604b080a8 100644 --- a/mrp_subcontracting/wizard/mrp_product_produce.py +++ b/mrp_subcontracting/wizard/mrp_product_produce.py @@ -1,52 +1,54 @@ -# 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 fields, models from odoo.tools.float_utils import float_is_zero + class MrpProductProduce(models.TransientModel): _inherit = 'mrp.product.produce' - subcontract_move_id = fields.Many2one('stock.move', 'stock move from the subcontract picking', check_company=True) + subcontract_move_id = fields.Many2one( + 'stock.move', 'stock move from the subcontract picking') - def continue_production(self): - action = super(MrpProductProduce, self).continue_production() - action['context'] = dict(action['context'], default_subcontract_move_id=self.subcontract_move_id.id) - return action + def _get_todo(self, production): + """This method will return remaining todo quantity of production.""" + main_product_moves = production.move_finished_ids.filtered( + lambda x: x.product_id == production.product_id) + todo_quantity = production.product_qty - sum( + main_product_moves.mapped('quantity_done')) + return todo_quantity if (todo_quantity > 0) else 0 - def _generate_produce_lines(self): - """ When the wizard is called in backend, the onchange that create the - produce lines is not trigger. This method generate them and is used with - _record_production to appropriately set the lot_produced_id and - appropriately create raw stock move lines. - """ - self.ensure_one() - moves = (self.move_raw_ids | self.move_finished_ids).filtered( - lambda move: move.state not in ('done', 'cancel') - ) - for move in moves: - qty_to_consume = self._prepare_component_quantity(move, self.qty_producing) - line_values = self._generate_lines_values(move, qty_to_consume) - self.env['mrp.product.produce.line'].create(line_values) - - def _update_finished_move(self): + def do_produce(self): """ After producing, set the move line on the subcontract picking. """ - res = super(MrpProductProduce, self)._update_finished_move() + res = super().do_produce() if self.subcontract_move_id: self.env['stock.move.line'].create({ 'move_id': self.subcontract_move_id.id, 'picking_id': self.subcontract_move_id.picking_id.id, 'product_id': self.product_id.id, 'location_id': self.subcontract_move_id.location_id.id, - 'location_dest_id': self.subcontract_move_id.location_dest_id.id, + 'location_dest_id': ( + self.subcontract_move_id.location_dest_id.id), 'product_uom_qty': 0, 'product_uom_id': self.product_uom_id.id, - 'qty_done': self.qty_producing, - 'lot_id': self.finished_lot_id and self.finished_lot_id.id, + 'qty_done': self.product_qty, + 'lot_id': self.lot_id.id, }) if not self._get_todo(self.production_id): - ml_reserved = self.subcontract_move_id.move_line_ids.filtered(lambda ml: - float_is_zero(ml.qty_done, precision_rounding=ml.product_uom_id.rounding) and - not float_is_zero(ml.product_uom_qty, precision_rounding=ml.product_uom_id.rounding)) + ml_reserved = self.subcontract_move_id.move_line_ids.filtered( + lambda ml: ( + float_is_zero( + ml.qty_done, + precision_rounding=ml.product_uom_id.rounding + ) and not float_is_zero( + ml.product_uom_qty, + precision_rounding=ml.product_uom_id.rounding + ) + ) + ) ml_reserved.unlink() for ml in self.subcontract_move_id.move_line_ids: ml.product_uom_qty = ml.qty_done diff --git a/mrp_subcontracting/wizard/stock_picking_return.py b/mrp_subcontracting/wizard/stock_picking_return.py index 82aa8d9ba..360592f68 100644 --- a/mrp_subcontracting/wizard/stock_picking_return.py +++ b/mrp_subcontracting/wizard/stock_picking_return.py @@ -1,29 +1,17 @@ -# 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 -from odoo import api, models -from odoo.osv.expression import OR class ReturnPicking(models.TransientModel): _inherit = 'stock.return.picking' - @api.onchange('picking_id') - def _onchange_picking_id(self): - res = super(ReturnPicking, self)._onchange_picking_id() - if not any(self.product_return_moves.filtered(lambda r: r.quantity > 0).move_id.mapped('is_subcontract')): - return res - subcontract_location = self.picking_id.partner_id.with_context(force_company=self.picking_id.company_id.id).property_stock_subcontractor - self.location_id = subcontract_location.id - domain_location = OR([ - ['|', ('id', '=', self.original_location_id.id), ('return_location', '=', True)], - [('id', '=', subcontract_location.id)] - ]) - if not res: - res = {'domain': {'location_id': domain_location}} - else: - res['domain'] = {'location_id': domain_location} - return res - def _prepare_move_default_values(self, return_line, new_picking): - vals = super(ReturnPicking, self)._prepare_move_default_values(return_line, new_picking) + vals = super()._prepare_move_default_values(return_line, new_picking) vals['is_subcontract'] = False + if return_line.move_id.is_subcontract: + vals['location_dest_id'] = return_line.move_id.location_id.id return vals