diff --git a/custom_addons/cancel_mrp_order/__init__.py b/custom_addons/cancel_mrp_order/__init__.py new file mode 100644 index 000000000..61e5ca1b5 --- /dev/null +++ b/custom_addons/cancel_mrp_order/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# +from . import models diff --git a/custom_addons/cancel_mrp_order/__manifest__.py b/custom_addons/cancel_mrp_order/__manifest__.py new file mode 100644 index 000000000..614674e5f --- /dev/null +++ b/custom_addons/cancel_mrp_order/__manifest__.py @@ -0,0 +1,21 @@ + +{ + 'name': 'Cancel Manufacturing Order', + 'category': 'Manufacturing', + 'summary': 'Allows users to cancel manufacturing order by clicking ' + 'the button "CANCEL"', + 'description': 'Allow users to cancel manufacturing orders' + 'by clicking on "CANCEL" button. Also able to cancel ' + 'multiple manufacturing orders from tree view by clicking ' + 'on the Cancel Manufacturing Order" from Action menu', + 'author': 'Raman', + 'depends': ['base', 'mrp'], + 'data': [ + 'security/cancel_mrp_order_groups.xml', + 'views/mrp_production_views.xml', + ], + 'license': 'LGPL-3', + 'installable': True, + 'auto_install': False, + 'application': False, +} diff --git a/custom_addons/cancel_mrp_order/doc/RELEASE_NOTES.md b/custom_addons/cancel_mrp_order/doc/RELEASE_NOTES.md new file mode 100644 index 000000000..6a9b83054 --- /dev/null +++ b/custom_addons/cancel_mrp_order/doc/RELEASE_NOTES.md @@ -0,0 +1,7 @@ +## Module + +#### 27.05.2025 +#### Version 18.0.1.0.0 +#### ADD + +- Initial Commit for Cancel Manufacturing Order diff --git a/custom_addons/cancel_mrp_order/models/__init__.py b/custom_addons/cancel_mrp_order/models/__init__.py new file mode 100644 index 000000000..a0c1e3a4a --- /dev/null +++ b/custom_addons/cancel_mrp_order/models/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# +from . import mrp_production diff --git a/custom_addons/cancel_mrp_order/models/mrp_production.py b/custom_addons/cancel_mrp_order/models/mrp_production.py new file mode 100644 index 000000000..a83134d21 --- /dev/null +++ b/custom_addons/cancel_mrp_order/models/mrp_production.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################# +from odoo import models, _ +from odoo.exceptions import UserError +from odoo.tools import float_round +from collections import defaultdict + + +class MrpProduction(models.Model): + """Inherit manufacturing order to add some function """ + _inherit = 'mrp.production' + + def checking_access(self, rec): + """Warning for cancel button in action""" + if self.state == 'done': + rec.action_cancel_manufacturing_order() + else: + raise UserError(_( + "You cannot cancel an order that is not in Done state!")) + + def _create_move_from_existing_move(self, move, factor, location_id, + location_dest_id): + """Create a stock move while creating a manufacturing order""" + return self.env['stock.move'].create({ + 'name': move.name, + 'date': move.create_date, + 'product_id': move.product_id.id, + 'product_uom_qty': move.product_uom_qty * factor, + 'product_uom': move.product_uom.id, + 'procure_method': 'make_to_stock', + 'location_dest_id': location_dest_id.id, + 'location_id': location_id.id, + 'warehouse_id': location_dest_id.warehouse_id.id, + 'company_id': move.company_id.id, + }) + + def action_cancel_manufacturing_order(self): + """To cancel manufacturing order while clicking the button cancel """ + consume_moves = self.env['stock.move'] + produce_moves = self.env['stock.move'] + factor = self.product_qty / self.product_uom_id._compute_quantity( + self.product_qty, self.product_uom_id) + finished_moves = self.move_finished_ids.filtered(lambda l: l.state == 'done') + raw_moves = self.move_raw_ids.filtered(lambda l: l.state == 'done') + for finished_move in finished_moves: + consume_moves += self._create_move_from_existing_move(finished_move, factor, finished_move.location_dest_id, + finished_move.location_id) + for raw_move in raw_moves: + produce_moves += self._create_move_from_existing_move(raw_move, factor, raw_move.location_dest_id, + self.location_dest_id) + if consume_moves: + consume_moves._action_confirm() + if produce_moves: + produce_moves._action_confirm() + finished_moves = consume_moves.filtered(lambda m: m.product_id == self.product_id) + consume_moves -= finished_moves + for finished_move in finished_moves: + if finished_move.has_tracking != 'none': + self.env['stock.move.line'].create({ + 'move_id': finished_move.id, + 'quantity': finished_move.product_uom_qty, + 'product_id': finished_move.product_id.id, + 'product_uom_id': finished_move.product_uom.id, + 'location_id': finished_move.location_id.id, + 'location_dest_id': finished_move.location_dest_id.id, + }) + else: + finished_move.quantity = finished_move.product_uom_qty + qty_already_used = defaultdict(float) + for move in produce_moves | consume_moves: + if move.has_tracking != 'none': + original_move = move in produce_moves and self.move_raw_ids or self.move_finished_ids + moves_lines = original_move.filtered(lambda m: m.product_id == move.product_id).mapped('move_line_ids') + for move_line in moves_lines: + taken_quantity = min(move.product_uom_qty,move_line.quantity - qty_already_used[move_line]) + if taken_quantity: + self.env['stock.move.line'].create({ + 'move_id': move.id, + 'lot_id': move_line.lot_id.id, + 'quantity': taken_quantity, + 'product_id': move.product_id.id, + 'product_uom_id': move_line.product_uom_id.id, + 'location_id': move.location_id.id, + 'location_dest_id': move.location_dest_id.id, + }) + move.product_uom_qty -= taken_quantity + qty_already_used[move_line] += taken_quantity + else: + move.quantity = float_round( + move.product_uom_qty, + precision_rounding=move.product_uom.rounding) + stock_quant = self.env['stock.quant'].search([ + ('product_id', '=', move.product_id.id), + ('location_id', 'in', [self.location_src_id.id]) + ]) + for quant in stock_quant: + quant.sudo().write({'quantity': quant.quantity + move.product_qty}) + + for line in finished_moves: + stock_quant = self.env['stock.quant'].search([ + ('product_id', '=', line.product_id.id), + ('location_id', 'in', [self.location_dest_id.id]) + ]) + for quant in stock_quant: + quant.sudo().write({'quantity': quant.quantity - line.product_qty}) + finished_moves.write({'state': 'done'}) + consume_moves.write({'state': 'done'}) + produce_moves.write({'state': 'done'}) + produced_move_line_ids = produce_moves.mapped('move_line_ids').filtered(lambda ml: ml.quantity > 0) + consume_moves.mapped('move_line_ids').write({'produce_line_ids': [(6, 0, produced_move_line_ids.ids)]}) + raw_moves.sudo().write({'state': 'cancel'}) + raw_moves.mapped('move_line_ids').sudo().write({'state': 'cancel'}) + if self.sudo().mapped('workorder_ids'): + self.sudo().mapped('workorder_ids').write({'state': 'cancel'}) + self.write({'state': 'cancel'}) \ No newline at end of file diff --git a/custom_addons/cancel_mrp_order/security/cancel_mrp_order_groups.xml b/custom_addons/cancel_mrp_order/security/cancel_mrp_order_groups.xml new file mode 100644 index 000000000..4c925bf99 --- /dev/null +++ b/custom_addons/cancel_mrp_order/security/cancel_mrp_order_groups.xml @@ -0,0 +1,8 @@ + + + + + Cancel Manufacturing Orders + + diff --git a/custom_addons/cancel_mrp_order/static/description/icon.png b/custom_addons/cancel_mrp_order/static/description/icon.png new file mode 100644 index 000000000..182f17cd6 Binary files /dev/null and b/custom_addons/cancel_mrp_order/static/description/icon.png differ diff --git a/custom_addons/cancel_mrp_order/views/mrp_production_views.xml b/custom_addons/cancel_mrp_order/views/mrp_production_views.xml new file mode 100644 index 000000000..31c12159b --- /dev/null +++ b/custom_addons/cancel_mrp_order/views/mrp_production_views.xml @@ -0,0 +1,31 @@ + + + + + mrp.production.view.form.inherit.cancel.mrp.order + mrp.production + + + +