diff --git a/mrp_production_request/__manifest__.py b/mrp_production_request/__manifest__.py index 7f0753003..306f09070 100644 --- a/mrp_production_request/__manifest__.py +++ b/mrp_production_request/__manifest__.py @@ -3,15 +3,14 @@ { "name": "MRP Production Request", "summary": "Allows you to use Manufacturing Request as a previous " - "step to Manufacturing Orders for better manufacture " - "planification.", + "step to Manufacturing Orders for better manufacture " + "planification.", "version": "12.0.1.0.0", "development_status": "Mature", - "maintainers": ['lreficent'], + "maintainers": ["lreficent"], "category": "Manufacturing", "website": "https://github.com/OCA/manufacture", - "author": "Eficent," - "Odoo Community Association (OCA)", + "author": "Eficent," "Odoo Community Association (OCA)", "license": "AGPL-3", "application": False, "installable": True, diff --git a/mrp_production_request/data/mrp_production_request_sequence.xml b/mrp_production_request/data/mrp_production_request_sequence.xml index 9833e2c92..de29499be 100644 --- a/mrp_production_request/data/mrp_production_request_sequence.xml +++ b/mrp_production_request/data/mrp_production_request_sequence.xml @@ -1,15 +1,12 @@ - + - - Manufacturing Request mrp.production.request MR/%(range_year)s/ 5 - + - diff --git a/mrp_production_request/models/mrp_production.py b/mrp_production_request/models/mrp_production.py index 18331ac24..d890427dd 100644 --- a/mrp_production_request/models/mrp_production.py +++ b/mrp_production_request/models/mrp_production.py @@ -9,7 +9,10 @@ class MrpProduction(models.Model): mrp_production_request_id = fields.Many2one( comodel_name="mrp.production.request", - string="Manufacturing Request", copy=False, readonly=True) + string="Manufacturing Request", + copy=False, + readonly=True, + ) def _generate_finished_moves(self): """`move_dest_ids` is a One2many fields in mrp.production, thus we @@ -21,7 +24,7 @@ class MrpProduction(models.Model): move = super()._generate_finished_moves() request = self.mrp_production_request_id if request and request.move_dest_ids: - move.write({ - 'move_dest_ids': [(4, x.id) for x in request.move_dest_ids], - }) + move.write( + {"move_dest_ids": [(4, x.id) for x in request.move_dest_ids],} + ) return move diff --git a/mrp_production_request/models/mrp_production_request.py b/mrp_production_request/models/mrp_production_request.py index 5024a5106..4b4761d1e 100644 --- a/mrp_production_request/models/mrp_production_request.py +++ b/mrp_production_request/models/mrp_production_request.py @@ -1,10 +1,11 @@ # Copyright 2017-19 Eficent Business and IT Consulting Services S.L. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models, _ -import odoo.addons.decimal_precision as dp +from odoo import _, api, fields, models from odoo.exceptions import UserError +import odoo.addons.decimal_precision as dp + class MrpProductionRequest(models.Model): _name = "mrp.production.request" @@ -14,153 +15,239 @@ class MrpProductionRequest(models.Model): @api.model def _company_get(self): - company_id = self.env['res.company']._company_default_get() - return self.env['res.company'].browse(company_id.id) + company_id = self.env["res.company"]._company_default_get() + return self.env["res.company"].browse(company_id.id) @api.model def _get_default_requested_by(self): return self.env.user name = fields.Char( - default="/", required=True, - readonly=True, states={'draft': [('readonly', False)]}) + default="/", + required=True, + readonly=True, + states={"draft": [("readonly", False)]}, + ) origin = fields.Char( - string='Source Document', - readonly=True, states={'draft': [('readonly', False)]}) + string="Source Document", readonly=True, states={"draft": [("readonly", False)]} + ) requested_by = fields.Many2one( - comodel_name='res.users', string='Requested by', + comodel_name="res.users", + string="Requested by", default=lambda self: self._get_default_requested_by(), - required=True, track_visibility='onchange', - readonly=True, states={'draft': [('readonly', False)]}) + required=True, + track_visibility="onchange", + readonly=True, + states={"draft": [("readonly", False)]}, + ) assigned_to = fields.Many2one( - comodel_name='res.users', string='Approver', - track_visibility='onchange', - readonly=True, states={'draft': [('readonly', False)]}, - domain=lambda self: [('groups_id', 'in', self.env.ref( - 'mrp_production_request.' - 'group_mrp_production_request_manager').id)]) - description = fields.Text('Description') + comodel_name="res.users", + string="Approver", + track_visibility="onchange", + readonly=True, + states={"draft": [("readonly", False)]}, + domain=lambda self: [ + ( + "groups_id", + "in", + self.env.ref( + "mrp_production_request." "group_mrp_production_request_manager" + ).id, + ) + ], + ) + description = fields.Text("Description") date_planned_start = fields.Datetime( - 'Deadline Start', copy=False, default=fields.Datetime.now, - index=True, required=True, - states={'confirmed': [('readonly', False)]}, oldname="date_planned") - date_planned_finished = fields.Datetime( - 'Deadline End', copy=False, default=fields.Datetime.now, + "Deadline Start", + copy=False, + default=fields.Datetime.now, index=True, - states={'confirmed': [('readonly', False)]}) + required=True, + states={"confirmed": [("readonly", False)]}, + oldname="date_planned", + ) + date_planned_finished = fields.Datetime( + "Deadline End", + copy=False, + default=fields.Datetime.now, + index=True, + states={"confirmed": [("readonly", False)]}, + ) company_id = fields.Many2one( - comodel_name='res.company', string='Company', - required=True, default=lambda self: self._company_get()) + comodel_name="res.company", + string="Company", + required=True, + default=lambda self: self._company_get(), + ) mrp_production_ids = fields.One2many( - comodel_name="mrp.production", string="Manufacturing Orders", - inverse_name="mrp_production_request_id", readonly=True) + comodel_name="mrp.production", + string="Manufacturing Orders", + inverse_name="mrp_production_request_id", + readonly=True, + ) mrp_production_count = fields.Integer( - compute="_compute_mrp_production_count", - string="MO's Count", + compute="_compute_mrp_production_count", string="MO's Count", ) state = fields.Selection( - selection=[("draft", "Draft"), - ("to_approve", "To Be Approved"), - ("approved", "Approved"), - ("done", "Done"), - ("cancel", "Cancelled")], - index=True, track_visibility='onchange', - required=True, copy=False, default='draft') - procurement_group_id = fields.Many2one( - string='Procurement Group', - comodel_name='procurement.group', - copy=False) - propagate = fields.Boolean( - 'Propagate cancel and split', - help='If checked, when the previous move of the move ' - '(which was generated by a next procurement) is cancelled ' - 'or split, the move generated by this move will too') - product_id = fields.Many2one( - comodel_name="product.product", string="Product", required=True, - domain=[('type', 'in', ['product', 'consu'])], + selection=[ + ("draft", "Draft"), + ("to_approve", "To Be Approved"), + ("approved", "Approved"), + ("done", "Done"), + ("cancel", "Cancelled"), + ], + index=True, track_visibility="onchange", - readonly=True, states={'draft': [('readonly', False)]}) + required=True, + copy=False, + default="draft", + ) + procurement_group_id = fields.Many2one( + string="Procurement Group", comodel_name="procurement.group", copy=False + ) + propagate = fields.Boolean( + "Propagate cancel and split", + help="If checked, when the previous move of the move " + "(which was generated by a next procurement) is cancelled " + "or split, the move generated by this move will too", + ) + product_id = fields.Many2one( + comodel_name="product.product", + string="Product", + required=True, + domain=[("type", "in", ["product", "consu"])], + track_visibility="onchange", + readonly=True, + states={"draft": [("readonly", False)]}, + ) product_tmpl_id = fields.Many2one( - comodel_name='product.template', string='Product Template', - related='product_id.product_tmpl_id') + comodel_name="product.template", + string="Product Template", + related="product_id.product_tmpl_id", + ) product_qty = fields.Float( - string="Required Quantity", required=True, track_visibility='onchange', - digits=dp.get_precision('Product Unit of Measure'), default=1.0, - readonly=True, states={'draft': [('readonly', False)]}) + string="Required Quantity", + required=True, + track_visibility="onchange", + digits=dp.get_precision("Product Unit of Measure"), + default=1.0, + readonly=True, + states={"draft": [("readonly", False)]}, + ) product_uom_id = fields.Many2one( - comodel_name='uom.uom', string='Unit of Measure', - readonly=True, states={'draft': [('readonly', False)]}, - domain="[('category_id', '=', category_uom_id)]") + comodel_name="uom.uom", + string="Unit of Measure", + readonly=True, + states={"draft": [("readonly", False)]}, + domain="[('category_id', '=', category_uom_id)]", + ) category_uom_id = fields.Many2one(related="product_uom_id.category_id") manufactured_qty = fields.Float( string="Quantity in Manufacturing Orders", - compute="_compute_manufactured_qty", store=True, readonly=True, - digits=dp.get_precision('Product Unit of Measure'), - help="Sum of the quantities in Manufacturing Orders (in any state).") - done_qty = fields.Float( - string="Quantity Done", store=True, readonly=True, compute="_compute_manufactured_qty", - digits=dp.get_precision('Product Unit of Measure'), - help="Sum of the quantities in all done Manufacturing Orders.") + store=True, + readonly=True, + digits=dp.get_precision("Product Unit of Measure"), + help="Sum of the quantities in Manufacturing Orders (in any state).", + ) + done_qty = fields.Float( + string="Quantity Done", + store=True, + readonly=True, + compute="_compute_manufactured_qty", + digits=dp.get_precision("Product Unit of Measure"), + help="Sum of the quantities in all done Manufacturing Orders.", + ) pending_qty = fields.Float( - string="Pending Quantity", compute="_compute_manufactured_qty", - store=True, digits=dp.get_precision('Product Unit of Measure'), + string="Pending Quantity", + compute="_compute_manufactured_qty", + store=True, + digits=dp.get_precision("Product Unit of Measure"), readonly=True, help="Quantity pending to add to Manufacturing Orders " - "to fulfill the Manufacturing Request requirement.") + "to fulfill the Manufacturing Request requirement.", + ) bom_id = fields.Many2one( - comodel_name="mrp.bom", string="Bill of Materials", required=True, - readonly=True, states={'draft': [('readonly', False)]}) + comodel_name="mrp.bom", + string="Bill of Materials", + required=True, + readonly=True, + states={"draft": [("readonly", False)]}, + ) routing_id = fields.Many2one( - comodel_name='mrp.routing', string='Routing', - on_delete='setnull', readonly=True, - states={'draft': [('readonly', False)]}, + comodel_name="mrp.routing", + string="Routing", + on_delete="setnull", + readonly=True, + states={"draft": [("readonly", False)]}, help="The list of operations (list of work centers) to produce " - "the finished product. The routing is mainly used to compute " - "work center costs during operations and to plan future loads " - "on work centers based on production plannification.") + "the finished product. The routing is mainly used to compute " + "work center costs during operations and to plan future loads " + "on work centers based on production plannification.", + ) location_src_id = fields.Many2one( - comodel_name='stock.location', string='Raw Materials Location', - default=lambda self: self.env['stock.location'].browse( - self.env['mrp.production']._get_default_location_src_id()), - required=True, readonly=True, states={'draft': [('readonly', False)]}) + comodel_name="stock.location", + string="Raw Materials Location", + default=lambda self: self.env["stock.location"].browse( + self.env["mrp.production"]._get_default_location_src_id() + ), + required=True, + readonly=True, + states={"draft": [("readonly", False)]}, + ) location_dest_id = fields.Many2one( - comodel_name='stock.location', string='Finished Products Location', - default=lambda self: self.env['stock.location'].browse( - self.env['mrp.production']._get_default_location_dest_id()), - required=True, readonly=True, states={'draft': [('readonly', False)]}) + comodel_name="stock.location", + string="Finished Products Location", + default=lambda self: self.env["stock.location"].browse( + self.env["mrp.production"]._get_default_location_dest_id() + ), + required=True, + readonly=True, + states={"draft": [("readonly", False)]}, + ) picking_type_id = fields.Many2one( - comodel_name='stock.picking.type', string='Picking Type', - default=lambda self: self.env['stock.picking.type'].browse( - self.env['mrp.production']._get_default_picking_type()), - required=True, readonly=True, states={'draft': [('readonly', False)]}) + comodel_name="stock.picking.type", + string="Picking Type", + default=lambda self: self.env["stock.picking.type"].browse( + self.env["mrp.production"]._get_default_picking_type() + ), + required=True, + readonly=True, + states={"draft": [("readonly", False)]}, + ) move_dest_ids = fields.One2many( - comodel_name='stock.move', - inverse_name='created_mrp_production_request_id', - string="Stock Movements of Produced Goods") + comodel_name="stock.move", + inverse_name="created_mrp_production_request_id", + string="Stock Movements of Produced Goods", + ) orderpoint_id = fields.Many2one( - comodel_name='stock.warehouse.orderpoint', - string='Orderpoint') + comodel_name="stock.warehouse.orderpoint", string="Orderpoint" + ) _sql_constraints = [ - ('name_uniq', 'unique(name, company_id)', - 'Reference must be unique per Company!'), + ( + "name_uniq", + "unique(name, company_id)", + "Reference must be unique per Company!", + ), ] @api.model def _get_mo_valid_states(self): - return ['planned', 'confirmed', 'progress', 'done'] + return ["planned", "confirmed", "progress", "done"] @api.multi - @api.depends('mrp_production_ids', 'mrp_production_ids.state', 'state') + @api.depends("mrp_production_ids", "mrp_production_ids.state", "state") def _compute_manufactured_qty(self): valid_states = self._get_mo_valid_states() for req in self: done_mo = req.mrp_production_ids.filtered( - lambda mo: mo.state in 'done').mapped('product_qty') + lambda mo: mo.state in "done" + ).mapped("product_qty") req.done_qty = sum(done_mo) valid_mo = req.mrp_production_ids.filtered( - lambda mo: mo.state in valid_states).mapped('product_qty') + lambda mo: mo.state in valid_states + ).mapped("product_qty") req.manufactured_qty = sum(valid_mo) req.pending_qty = max(req.product_qty - req.manufactured_qty, 0.0) @@ -169,25 +256,29 @@ class MrpProductionRequest(models.Model): for rec in self: rec.mrp_production_count = len(rec.mrp_production_ids) - @api.onchange('product_id') + @api.onchange("product_id") def _onchange_product_id(self): self.product_uom_id = self.product_id.uom_id - self.bom_id = self.env['mrp.bom']._bom_find( - product=self.product_id, company_id=self.company_id.id, - picking_type=self.picking_type_id) + self.bom_id = self.env["mrp.bom"]._bom_find( + product=self.product_id, + company_id=self.company_id.id, + picking_type=self.picking_type_id, + ) @api.multi def _subscribe_assigned_user(self, vals): self.ensure_one() - if vals.get('assigned_to'): + if vals.get("assigned_to"): self.message_subscribe( - partner_ids=self.assigned_to.mapped('partner_id').ids) + partner_ids=self.assigned_to.mapped("partner_id").ids + ) @api.model def _create_sequence(self, vals): - if not vals.get('name') or vals.get('name') == '/': - vals['name'] = self.env['ir.sequence'].next_by_code( - 'mrp.production.request') or '/' + if not vals.get("name") or vals.get("name") == "/": + vals["name"] = ( + self.env["ir.sequence"].next_by_code("mrp.production.request") or "/" + ) return vals @api.model @@ -208,59 +299,70 @@ class MrpProductionRequest(models.Model): @api.multi def button_to_approve(self): - self.write({'state': 'to_approve'}) + self.write({"state": "to_approve"}) return True @api.multi def button_approved(self): - self.write({'state': 'approved'}) + self.write({"state": "approved"}) return True @api.multi def button_done(self): - self.write({'state': 'done'}) + self.write({"state": "done"}) return True @api.multi def _check_reset_allowed(self): - if any([s in self._get_mo_valid_states() for s in self.mapped( - 'mrp_production_ids.state')]): + if any( + [ + s in self._get_mo_valid_states() + for s in self.mapped("mrp_production_ids.state") + ] + ): raise UserError( - _("You cannot reset a manufacturing request if the related " - "manufacturing orders are not cancelled.")) + _( + "You cannot reset a manufacturing request if the related " + "manufacturing orders are not cancelled." + ) + ) @api.multi def button_draft(self): self._check_reset_allowed() - self.write({'state': 'draft'}) + self.write({"state": "draft"}) return True @api.multi def _check_cancel_allowed(self): - if any([s == 'done' for s in self.mapped('state')]): + if any([s == "done" for s in self.mapped("state")]): raise UserError( - _('You cannot reject a manufacturing request related to ' - 'done procurement orders.')) + _( + "You cannot reject a manufacturing request related to " + "done procurement orders." + ) + ) @api.multi def button_cancel(self): self._check_cancel_allowed() - self.write({'state': 'cancel'}) - self.mapped('move_dest_ids').filtered( - lambda r: r.state != 'cancel')._action_cancel() + self.write({"state": "cancel"}) + self.mapped("move_dest_ids").filtered( + lambda r: r.state != "cancel" + )._action_cancel() return True @api.multi def action_view_mrp_productions(self): - action = self.env.ref('mrp.mrp_production_action') + action = self.env.ref("mrp.mrp_production_action") result = action.read()[0] - result['context'] = {} - mos = self.mapped('mrp_production_ids') + result["context"] = {} + mos = self.mapped("mrp_production_ids") # choose the view_mode accordingly if len(mos) != 1: - result['domain'] = [('id', 'in', mos.ids)] + result["domain"] = [("id", "in", mos.ids)] elif len(mos) == 1: - form = self.env.ref('mrp.mrp_production_form_view', False) - result['views'] = [(form and form.id or False, 'form')] - result['res_id'] = mos[0].id + form = self.env.ref("mrp.mrp_production_form_view", False) + result["views"] = [(form and form.id or False, "form")] + result["res_id"] = mos[0].id return result diff --git a/mrp_production_request/models/product.py b/mrp_production_request/models/product.py index 82bc7edec..82fe69149 100644 --- a/mrp_production_request/models/product.py +++ b/mrp_production_request/models/product.py @@ -8,7 +8,7 @@ class ProductTemplate(models.Model): _inherit = "product.template" mrp_production_request = fields.Boolean( - string='Manufacturing Request', + string="Manufacturing Request", help="Check this box to generate manufacturing request instead of " - "generating manufacturing orders from procurement.", + "generating manufacturing orders from procurement.", ) diff --git a/mrp_production_request/models/stock_move.py b/mrp_production_request/models/stock_move.py index 591322f94..246910141 100644 --- a/mrp_production_request/models/stock_move.py +++ b/mrp_production_request/models/stock_move.py @@ -8,15 +8,13 @@ class StockMove(models.Model): _inherit = "stock.move" created_mrp_production_request_id = fields.Many2one( - comodel_name='mrp.production.request', - string='Created Production Request', + comodel_name="mrp.production.request", string="Created Production Request", ) @api.model def create(self, vals): - if 'production_id' in vals: - production = self.env['mrp.production'].browse( - vals['production_id']) + if "production_id" in vals: + production = self.env["mrp.production"].browse(vals["production_id"]) if production.mrp_production_request_id: - vals['propagate'] = False + vals["propagate"] = False return super().create(vals) diff --git a/mrp_production_request/models/stock_rule.py b/mrp_production_request/models/stock_rule.py index 4b953d56d..d050a0c5a 100644 --- a/mrp_production_request/models/stock_rule.py +++ b/mrp_production_request/models/stock_rule.py @@ -1,7 +1,7 @@ # Copyright 2018-19 Eficent Business and IT Consulting Services S.L. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, models, _ +from odoo import _, api, models from odoo.exceptions import UserError @@ -10,74 +10,96 @@ class StockRule(models.Model): @api.multi def _prepare_mrp_production_request( - self, product_id, product_qty, product_uom, location_id, name, - origin, values, bom): + self, + product_id, + product_qty, + product_uom, + location_id, + name, + origin, + values, + bom, + ): self.ensure_one() data = self._prepare_mo_vals( - product_id, product_qty, product_uom, location_id, name, - origin, values, bom) - data['state'] = 'to_approve' - orderpoint = values.get('orderpoint_id') + product_id, product_qty, product_uom, location_id, name, origin, values, bom + ) + data["state"] = "to_approve" + orderpoint = values.get("orderpoint_id") if orderpoint: - data['orderpoint_id'] = orderpoint.id - procurement_group = values.get('group_id') + data["orderpoint_id"] = orderpoint.id + procurement_group = values.get("group_id") if procurement_group: - data['procurement_group_id'] = procurement_group.id + data["procurement_group_id"] = procurement_group.id return data @api.multi def _need_production_request(self, product_id): - return self.action == 'manufacture' \ - and product_id.mrp_production_request + return self.action == "manufacture" and product_id.mrp_production_request @api.multi - def _run_production_request(self, product_id, product_qty, product_uom, - location_id, name, origin, values): + def _run_production_request( + self, product_id, product_qty, product_uom, location_id, name, origin, values + ): """Trying to handle this as much similar as possible to Odoo production orders. See `_run_manufacture` in Odoo standard.""" - request_obj = self.env['mrp.production.request'] + request_obj = self.env["mrp.production.request"] request_obj_sudo = request_obj.sudo().with_context( - force_company=values['company_id'].id) + force_company=values["company_id"].id + ) bom = self._get_matching_bom(product_id, values) if not bom: - raise UserError(_( - 'There is no Bill of Material found for the product %s. ' - 'Please define a Bill of Material for this product.') % ( - product_id.display_name,)) + raise UserError( + _( + "There is no Bill of Material found for the product %s. " + "Please define a Bill of Material for this product." + ) + % (product_id.display_name,) + ) # create the MR as SUPERUSER because the current user may not # have the rights to do it (mto product launched by a sale for example) request = request_obj_sudo.create( self._prepare_mrp_production_request( - product_id, product_qty, product_uom, location_id, name, - origin, values, bom)) - origin_production = values.get('move_dest_ids') and \ - values['move_dest_ids'][0].raw_material_production_id or False - orderpoint = values.get('orderpoint_id') + product_id, + product_qty, + product_uom, + location_id, + name, + origin, + values, + bom, + ) + ) + origin_production = ( + values.get("move_dest_ids") + and values["move_dest_ids"][0].raw_material_production_id + or False + ) + orderpoint = values.get("orderpoint_id") if orderpoint: request.message_post_with_view( - 'mail.message_origin_link', - values={'self': request, - 'origin': orderpoint}, - subtype_id=self.env.ref('mail.mt_note').id, + "mail.message_origin_link", + values={"self": request, "origin": orderpoint}, + subtype_id=self.env.ref("mail.mt_note").id, ) if origin_production: request.message_post_with_view( - 'mail.message_origin_link', - values={'self': request, - 'origin': origin_production}, - subtype_id=self.env.ref('mail.mt_note').id, + "mail.message_origin_link", + values={"self": request, "origin": origin_production}, + subtype_id=self.env.ref("mail.mt_note").id, ) return True @api.multi - def _run_manufacture(self, product_id, product_qty, product_uom, - location_id, name, origin, values): + def _run_manufacture( + self, product_id, product_qty, product_uom, location_id, name, origin, values + ): if self._need_production_request(product_id): return self._run_production_request( - product_id, product_qty, product_uom, - location_id, name, origin, values) + product_id, product_qty, product_uom, location_id, name, origin, values + ) return super()._run_manufacture( - product_id, product_qty, product_uom, location_id, name, - origin, values) + product_id, product_qty, product_uom, location_id, name, origin, values + ) diff --git a/mrp_production_request/models/stock_warehouse_orderpoint.py b/mrp_production_request/models/stock_warehouse_orderpoint.py index b19e353c4..b62fdadbe 100644 --- a/mrp_production_request/models/stock_warehouse_orderpoint.py +++ b/mrp_production_request/models/stock_warehouse_orderpoint.py @@ -9,11 +9,14 @@ class Orderpoint(models.Model): def _quantity_in_progress(self): res = super()._quantity_in_progress() - mrp_requests = self.env['mrp.production.request'].search([ - ('state', 'not in', ('done', 'cancel')), - ('orderpoint_id', 'in', self.ids), - ]) + mrp_requests = self.env["mrp.production.request"].search( + [ + ("state", "not in", ("done", "cancel")), + ("orderpoint_id", "in", self.ids), + ] + ) for rec in mrp_requests: res[rec.orderpoint_id.id] += rec.product_uom_id._compute_quantity( - rec.pending_qty, rec.orderpoint_id.product_uom, round=False) + rec.pending_qty, rec.orderpoint_id.product_uom, round=False + ) return res diff --git a/mrp_production_request/security/mrp_production_request_security.xml b/mrp_production_request/security/mrp_production_request_security.xml index c2d7c5368..9594d6c33 100644 --- a/mrp_production_request/security/mrp_production_request_security.xml +++ b/mrp_production_request/security/mrp_production_request_security.xml @@ -1,73 +1,77 @@ - + - - - - - Manufacturing Request - - 20 - - - - User - - - - - - Manager - - - - - - - - - - Manufacturing Request multi-company - - - ['|',('company_id','=',False), + + + Manufacturing Request + + 20 + + + User + + + + + Manager + + + + + + + + Manufacturing Request multi-company + + + ['|',('company_id','=',False), ('company_id','child_of',[user.company_id.id])] - - - - Follow Manufacturing Request - - - - - - - ['|',('requested_by','=',user.id), + + + Follow Manufacturing Request + + + + + + + ['|',('requested_by','=',user.id), ('message_partner_ids', 'in', [user.partner_id.id])] - - - - Manufacturing Request User - - - - - - - [('requested_by','=',user.id)] - - - - Manufacturing Request Line Manager - - - - - - - - - + + + Manufacturing Request User + + + + + + + [('requested_by','=',user.id)] + + + Manufacturing Request Line Manager + + + + + + + + diff --git a/mrp_production_request/tests/test_mrp_production_request.py b/mrp_production_request/tests/test_mrp_production_request.py index 5deeef379..f2ae687e6 100644 --- a/mrp_production_request/tests/test_mrp_production_request.py +++ b/mrp_production_request/tests/test_mrp_production_request.py @@ -1,101 +1,119 @@ # Copyright 2017-18 Eficent Business and IT Consulting Services S.L. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo.tests.common import TransactionCase from odoo import fields from odoo.exceptions import UserError +from odoo.tests.common import TransactionCase class TestMrpProductionRequest(TransactionCase): def setUp(self, *args, **kwargs): super().setUp(*args, **kwargs) - self.production_model = self.env['mrp.production'] - self.request_model = self.env['mrp.production.request'] - self.wiz_model = self.env['mrp.production.request.create.mo'] - self.bom_model = self.env['mrp.bom'] - self.group_model = self.env['procurement.group'] - self.product_model = self.env['product.product'] - self.bom_model = self.env['mrp.bom'] - self.boml_model = self.env['mrp.bom.line'] + self.production_model = self.env["mrp.production"] + self.request_model = self.env["mrp.production.request"] + self.wiz_model = self.env["mrp.production.request.create.mo"] + self.bom_model = self.env["mrp.bom"] + self.group_model = self.env["procurement.group"] + self.product_model = self.env["product.product"] + self.bom_model = self.env["mrp.bom"] + self.boml_model = self.env["mrp.bom.line"] - self.warehouse = self.env.ref('stock.warehouse0') - self.stock_loc = self.env.ref('stock.stock_location_stock') - route_manuf = self.env.ref('mrp.route_warehouse0_manufacture') + self.warehouse = self.env.ref("stock.warehouse0") + self.stock_loc = self.env.ref("stock.stock_location_stock") + route_manuf = self.env.ref("mrp.route_warehouse0_manufacture") # Prepare Products: - self.product = self.env.ref('product.product_product_3') + self.product = self.env.ref("product.product_product_3") self.product.mrp_production_request = True self.product.route_ids = [(4, route_manuf.id, 0)] - self.product_no_bom = self.product_model.create({ - 'name': 'Test Product without BoM', - 'mrp_production_request': True, - 'route_ids': [(6, 0, route_manuf.ids)], - }) - self.product_orderpoint = self.product_model.create({ - 'name': 'Test Product for orderpoint', - 'mrp_production_request': True, - 'route_ids': [(6, 0, route_manuf.ids)], - }) - product_component = self.product_model.create({ - 'name': 'Test component', - 'mrp_production_request': True, - 'route_ids': [(6, 0, route_manuf.ids)], - }) + self.product_no_bom = self.product_model.create( + { + "name": "Test Product without BoM", + "mrp_production_request": True, + "route_ids": [(6, 0, route_manuf.ids)], + } + ) + self.product_orderpoint = self.product_model.create( + { + "name": "Test Product for orderpoint", + "mrp_production_request": True, + "route_ids": [(6, 0, route_manuf.ids)], + } + ) + product_component = self.product_model.create( + { + "name": "Test component", + "mrp_production_request": True, + "route_ids": [(6, 0, route_manuf.ids)], + } + ) # Create Bill of Materials: - self.test_bom_1 = self.bom_model.create({ - 'product_id': self.product_orderpoint.id, - 'product_tmpl_id': self.product_orderpoint.product_tmpl_id.id, - 'product_uom_id': self.product_orderpoint.uom_id.id, - 'product_qty': 1.0, - 'type': 'normal', - }) - self.boml_model.create({ - 'bom_id': self.test_bom_1.id, - 'product_id': product_component.id, - 'product_qty': 1.0, - }) + self.test_bom_1 = self.bom_model.create( + { + "product_id": self.product_orderpoint.id, + "product_tmpl_id": self.product_orderpoint.product_tmpl_id.id, + "product_uom_id": self.product_orderpoint.uom_id.id, + "product_qty": 1.0, + "type": "normal", + } + ) + self.boml_model.create( + { + "bom_id": self.test_bom_1.id, + "product_id": product_component.id, + "product_qty": 1.0, + } + ) # Create Orderpoint: - self.orderpoint = self.env['stock.warehouse.orderpoint'].create({ - 'warehouse_id': self.warehouse.id, - 'location_id': self.warehouse.lot_stock_id.id, - 'product_id': self.product_orderpoint.id, - 'product_min_qty': 10.0, - 'product_max_qty': 50.0, - 'product_uom': self.product_orderpoint.uom_id.id, - }) + self.orderpoint = self.env["stock.warehouse.orderpoint"].create( + { + "warehouse_id": self.warehouse.id, + "location_id": self.warehouse.lot_stock_id.id, + "product_id": self.product_orderpoint.id, + "product_min_qty": 10.0, + "product_max_qty": 50.0, + "product_uom": self.product_orderpoint.uom_id.id, + } + ) # Create Procurement Group: - self.test_group = self.group_model.create({ - 'name': 'TEST', - }) + self.test_group = self.group_model.create({"name": "TEST",}) # Create User: - self.test_user = self.env['res.users'].create({ - 'name': 'John', - 'login': 'test', - }) + self.test_user = self.env["res.users"].create( + {"name": "John", "login": "test",} + ) - def procure(self, group, product, qty=4.0,): + def procure( + self, group, product, qty=4.0, + ): values = { - 'date_planned': fields.Datetime.now(), - 'group_id': group, + "date_planned": fields.Datetime.now(), + "group_id": group, } self.group_model.run( - product, qty, product.uom_id, self.stock_loc, - group.name, group.name, values, + product, + qty, + product.uom_id, + self.stock_loc, + group.name, + group.name, + values, ) return True def test_01_manufacture_request(self): """Tests manufacture request workflow.""" self.procure(self.test_group, self.product) - request = self.request_model.search([ - ('product_id', '=', self.product.id), - ('procurement_group_id', '=', self.test_group.id), - ]) + request = self.request_model.search( + [ + ("product_id", "=", self.product.id), + ("procurement_group_id", "=", self.test_group.id), + ] + ) self.assertEqual(len(request), 1) request.button_to_approve() request.button_draft() @@ -103,13 +121,14 @@ class TestMrpProductionRequest(TransactionCase): request.button_approved() self.assertEqual(request.pending_qty, 4.0) wiz = self.wiz_model.with_context( - active_ids=request.ids, - active_model="mrp.production.request").create({}) + active_ids=request.ids, active_model="mrp.production.request" + ).create({}) wiz.compute_product_line_ids() wiz.mo_qty = 4.0 wiz.create_mo() - mo = self.production_model.search([ - ('mrp_production_request_id', '=', request.id)]) + mo = self.production_model.search( + [("mrp_production_request_id", "=", request.id)] + ) self.assertTrue(mo, "No MO created.") self.assertEqual(request.pending_qty, 0.0) request.button_done() @@ -117,39 +136,42 @@ class TestMrpProductionRequest(TransactionCase): def test_02_assignation(self): """Tests assignation of manufacturing requests.""" randon_bom_id = self.bom_model.search([], limit=1).id - request = self.request_model.create({ - 'assigned_to': self.test_user.id, - 'product_id': self.product.id, - 'product_qty': 5.0, - 'bom_id': randon_bom_id, - }) + request = self.request_model.create( + { + "assigned_to": self.test_user.id, + "product_id": self.product.id, + "product_qty": 5.0, + "bom_id": randon_bom_id, + } + ) request._onchange_product_id() self.assertEqual( - request.bom_id.product_tmpl_id, self.product.product_tmpl_id, - "Wrong Bill of Materials.") - request.write({ - 'assigned_to': self.uid, - }) - self.assertTrue(request.message_follower_ids, - "Followers not added correctly.") + request.bom_id.product_tmpl_id, + self.product.product_tmpl_id, + "Wrong Bill of Materials.", + ) + request.write( + {"assigned_to": self.uid,} + ) + self.assertTrue(request.message_follower_ids, "Followers not added correctly.") def test_03_substract_qty_from_orderpoint(self): """Quantity in Manufacturing Requests should be considered by orderpoints.""" - request = self.request_model.search([ - ('product_id', '=', self.product_orderpoint.id), - ]) + request = self.request_model.search( + [("product_id", "=", self.product_orderpoint.id),] + ) self.assertFalse(request) - self.env['procurement.group'].run_scheduler() - request = self.request_model.search([ - ('product_id', '=', self.product_orderpoint.id), - ]) + self.env["procurement.group"].run_scheduler() + request = self.request_model.search( + [("product_id", "=", self.product_orderpoint.id),] + ) self.assertEqual(len(request), 1) # Running again the scheduler should not generate a new MR. - self.env['procurement.group'].run_scheduler() - request = self.request_model.search([ - ('product_id', '=', self.product_orderpoint.id), - ]) + self.env["procurement.group"].run_scheduler() + request = self.request_model.search( + [("product_id", "=", self.product_orderpoint.id),] + ) self.assertEqual(len(request), 1) def test_04_raise_errors(self): diff --git a/mrp_production_request/views/mrp_production_request_view.xml b/mrp_production_request/views/mrp_production_request_view.xml index 8beeb7523..51b15dd3f 100644 --- a/mrp_production_request/views/mrp_production_request_view.xml +++ b/mrp_production_request/views/mrp_production_request_view.xml @@ -1,201 +1,274 @@ - + - mrp.production.request.form mrp.production.request
-
-
- -
- -
- -
-
- - -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
- mrp.production.request.tree mrp.production.request - - - - - - - - - - - + + + + + + + + + + + - + - mrp.production.request.search mrp.production.request - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - + + + - - Manufacturing Requests ir.actions.act_window @@ -204,30 +277,27 @@ tree,form {"search_default_todo":1} -

+

Click to start a new manufacturing request process. -

+

+

A Manufacturing Request is an instruction to production to produce a certain quantity of a given product.

- - + /> - + Refresh Quantities - + code records._compute_manufactured_qty() -
diff --git a/mrp_production_request/views/mrp_production_view.xml b/mrp_production_request/views/mrp_production_view.xml index f7e89b9ca..25289fe82 100644 --- a/mrp_production_request/views/mrp_production_view.xml +++ b/mrp_production_request/views/mrp_production_view.xml @@ -1,18 +1,15 @@ - + - mrp.production.form - mrp_production_request mrp.production - + - + - diff --git a/mrp_production_request/views/product_template_view.xml b/mrp_production_request/views/product_template_view.xml index 417c7f8fc..6bd11cdf5 100644 --- a/mrp_production_request/views/product_template_view.xml +++ b/mrp_production_request/views/product_template_view.xml @@ -1,18 +1,15 @@ - + - product.template.form - mrp_production_request product.template - + - + - diff --git a/mrp_production_request/wizards/mrp_production_request_create_mo.py b/mrp_production_request/wizards/mrp_production_request_create_mo.py index 0e81b63b7..654d4567c 100644 --- a/mrp_production_request/wizards/mrp_production_request_create_mo.py +++ b/mrp_production_request/wizards/mrp_production_request_create_mo.py @@ -1,10 +1,11 @@ # Copyright 2017-19 Eficent Business and IT Consulting Services S.L. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models, _ -import odoo.addons.decimal_precision as dp +from odoo import _, api, fields, models from odoo.exceptions import UserError +import odoo.addons.decimal_precision as dp + class MrpProductionRequestCreateMo(models.TransientModel): _name = "mrp.production.request.create.mo" @@ -16,14 +17,16 @@ class MrpProductionRequestCreateMo(models.TransientModel): res = self._prepare_lines() product_lines = res[1] for line in product_lines: - self.env['mrp.production.request.create.mo.line'].create( - self._prepare_product_line(line)) + self.env["mrp.production.request.create.mo.line"].create( + self._prepare_product_line(line) + ) self._get_mo_qty() # The wizard must be reloaded in order to show the new product lines action = self.env.ref( - 'mrp_production_request.mrp_production_request_create_mo_action') + "mrp_production_request.mrp_production_request_create_mo_action" + ) res = action.read()[0] - res['res_id'] = self.id + res["res_id"] = self.id return res def _prepare_lines(self): @@ -32,70 +35,71 @@ class MrpProductionRequestCreateMo(models.TransientModel): :return: boms_done, lines_done """ bom_point = self.bom_id - factor = self.mrp_production_request_id.product_uom_id.\ - _compute_quantity(self.pending_qty, bom_point.product_uom_id) + factor = self.mrp_production_request_id.product_uom_id._compute_quantity( + self.pending_qty, bom_point.product_uom_id + ) return bom_point.explode( - self.mrp_production_request_id.product_id, - factor / bom_point.product_qty) + self.mrp_production_request_id.product_id, factor / bom_point.product_qty + ) @api.multi def _get_mo_qty(self): """Propose a qty to create a MO available to produce.""" for rec in self: - bottle_neck = min(rec.product_line_ids.mapped( - 'bottle_neck_factor')) + bottle_neck = min(rec.product_line_ids.mapped("bottle_neck_factor")) bottle_neck = max(min(1, bottle_neck), 0) rec.mo_qty = rec.pending_qty * bottle_neck mrp_production_request_id = fields.Many2one( - comodel_name="mrp.production.request", readonly=True) - bom_id = fields.Many2one( - related='mrp_production_request_id.bom_id', readonly=True) + comodel_name="mrp.production.request", readonly=True + ) + bom_id = fields.Many2one(related="mrp_production_request_id.bom_id", readonly=True) mo_qty = fields.Float( - string="Quantity", - digits=dp.get_precision("Product Unit of Measure")) + string="Quantity", digits=dp.get_precision("Product Unit of Measure") + ) pending_qty = fields.Float( related="mrp_production_request_id.pending_qty", - digits=dp.get_precision("Product Unit of Measure")) - product_uom_id = fields.Many2one( - related="mrp_production_request_id.product_uom_id") + digits=dp.get_precision("Product Unit of Measure"), + ) + product_uom_id = fields.Many2one(related="mrp_production_request_id.product_uom_id") product_line_ids = fields.One2many( comodel_name="mrp.production.request.create.mo.line", string="Products needed", - inverse_name="mrp_production_request_create_mo_id", readonly=True) - date_planned_start = fields.Datetime( - string="Deadline Start", - required=True, - ) - date_planned_finished = fields.Datetime( - string="Deadline End", - required=True, + inverse_name="mrp_production_request_create_mo_id", + readonly=True, ) + date_planned_start = fields.Datetime(string="Deadline Start", required=True,) + date_planned_finished = fields.Datetime(string="Deadline End", required=True,) @api.model def default_get(self, fields): rec = super().default_get(fields) - active_ids = self._context.get('active_ids') - active_model = self._context.get('active_model') + active_ids = self._context.get("active_ids") + active_model = self._context.get("active_model") if not active_ids: - raise UserError(_( - "Programming error: wizard action executed without " - "active_ids in context.")) + raise UserError( + _( + "Programming error: wizard action executed without " + "active_ids in context." + ) + ) request = self.env[active_model].browse(active_ids) - rec.update({ - 'mrp_production_request_id': active_ids[0], - 'date_planned_start': request[0].date_planned_start, - 'date_planned_finished': request[0].date_planned_finished, - }) + rec.update( + { + "mrp_production_request_id": active_ids[0], + "date_planned_start": request[0].date_planned_start, + "date_planned_finished": request[0].date_planned_finished, + } + ) return rec def _prepare_product_line(self, pl): return { - 'product_id': pl[0].product_id.id, - 'product_qty': pl[1]['qty'], - 'product_uom_id': pl[0].product_uom_id.id, - 'mrp_production_request_create_mo_id': self.id, - 'location_id': self.mrp_production_request_id.location_src_id.id, + "product_id": pl[0].product_id.id, + "product_qty": pl[1]["qty"], + "product_uom_id": pl[0].product_uom_id.id, + "mrp_production_request_create_mo_id": self.id, + "location_id": self.mrp_production_request_id.location_src_id.id, } @api.multi @@ -103,35 +107,34 @@ class MrpProductionRequestCreateMo(models.TransientModel): self.ensure_one() request_id = self.mrp_production_request_id return { - 'product_id': request_id.product_id.id, - 'bom_id': request_id.bom_id.id, - 'product_qty': self.mo_qty, - 'product_uom_id': self.product_uom_id.id, - 'mrp_production_request_id': self.mrp_production_request_id.id, - 'origin': request_id.origin, - 'location_src_id': request_id.location_src_id.id, - 'location_dest_id': request_id.location_dest_id.id, - 'picking_type_id': request_id.picking_type_id.id, - 'routing_id': request_id.routing_id.id, - 'date_planned_start': self.date_planned_start, - 'date_planned_finished': self.date_planned_finished, - 'procurement_group_id': request_id.procurement_group_id.id, - 'propagate': request_id.propagate, - 'company_id': request_id.company_id.id, + "product_id": request_id.product_id.id, + "bom_id": request_id.bom_id.id, + "product_qty": self.mo_qty, + "product_uom_id": self.product_uom_id.id, + "mrp_production_request_id": self.mrp_production_request_id.id, + "origin": request_id.origin, + "location_src_id": request_id.location_src_id.id, + "location_dest_id": request_id.location_dest_id.id, + "picking_type_id": request_id.picking_type_id.id, + "routing_id": request_id.routing_id.id, + "date_planned_start": self.date_planned_start, + "date_planned_finished": self.date_planned_finished, + "procurement_group_id": request_id.procurement_group_id.id, + "propagate": request_id.propagate, + "company_id": request_id.company_id.id, } @api.multi def create_mo(self): self.ensure_one() vals = self._prepare_manufacturing_order() - mo = self.env['mrp.production'].create(vals) + mo = self.env["mrp.production"].create(vals) # Open resulting MO: - action = self.env.ref('mrp.mrp_production_action').read()[0] - res = self.env.ref('mrp.mrp_production_form_view') - action.update({ - 'res_id': mo and mo.id, - 'views': [(res and res.id or False, 'form')], - }) + action = self.env.ref("mrp.mrp_production_action").read()[0] + res = self.env.ref("mrp.mrp_production_form_view") + action.update( + {"res_id": mo and mo.id, "views": [(res and res.id or False, "form")],} + ) return action @@ -143,11 +146,13 @@ class MrpProductionRequestCreateMoLine(models.TransientModel): def _compute_available_qty(self): for rec in self: product_available = rec.product_id.with_context( - location=rec.location_id.id).\ - _compute_product_available_not_res_dict()[ - rec.product_id.id]['qty_available_not_res'] + location=rec.location_id.id + )._compute_product_available_not_res_dict()[rec.product_id.id][ + "qty_available_not_res" + ] res = rec.product_id.product_tmpl_id.uom_id._compute_quantity( - product_available, rec.product_uom_id) + product_available, rec.product_uom_id + ) rec.available_qty = res @api.multi @@ -157,19 +162,23 @@ class MrpProductionRequestCreateMoLine(models.TransientModel): rec.bottle_neck_factor = rec.available_qty / rec.product_qty product_id = fields.Many2one( - comodel_name='product.product', string='Product', required=True) + comodel_name="product.product", string="Product", required=True + ) product_qty = fields.Float( - string='Quantity Required', required=True, - digits=dp.get_precision('Product Unit of Measure')) + string="Quantity Required", + required=True, + digits=dp.get_precision("Product Unit of Measure"), + ) product_uom_id = fields.Many2one( - comodel_name='uom.uom', string='UoM', required=True) + comodel_name="uom.uom", string="UoM", required=True + ) mrp_production_request_create_mo_id = fields.Many2one( - comodel_name='mrp.production.request.create.mo') + comodel_name="mrp.production.request.create.mo" + ) available_qty = fields.Float( - string='Quantity Available', compute='_compute_available_qty', - digits=dp.get_precision('Product Unit of Measure')) - bottle_neck_factor = fields.Float( - compute='_compute_bottle_neck_factor') - location_id = fields.Many2one( - comodel_name='stock.location', - required=True) + string="Quantity Available", + compute="_compute_available_qty", + digits=dp.get_precision("Product Unit of Measure"), + ) + bottle_neck_factor = fields.Float(compute="_compute_bottle_neck_factor") + location_id = fields.Many2one(comodel_name="stock.location", required=True) diff --git a/mrp_production_request/wizards/mrp_production_request_create_mo_view.xml b/mrp_production_request/wizards/mrp_production_request_create_mo_view.xml index 62694360b..80748754f 100644 --- a/mrp_production_request/wizards/mrp_production_request_create_mo_view.xml +++ b/mrp_production_request/wizards/mrp_production_request_create_mo_view.xml @@ -1,61 +1,70 @@ - + - mrp.production.request.create.mo.form mrp.production.request.create.mo
- + - - -