diff --git a/repair_scrap/README.rst b/repair_scrap/README.rst new file mode 100644 index 00000000..e69de29b diff --git a/repair_scrap/__init__.py b/repair_scrap/__init__.py new file mode 100644 index 00000000..aee8895e --- /dev/null +++ b/repair_scrap/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/repair_scrap/__manifest__.py b/repair_scrap/__manifest__.py new file mode 100644 index 00000000..473d4bf5 --- /dev/null +++ b/repair_scrap/__manifest__.py @@ -0,0 +1,20 @@ +{ + "name": "Repair Scrap", + "version": "15.0.1.0.0", + "license": "AGPL-3", + "category": "Repair", + "summary": """Repair Scrap""", + "author": "ForgeFlow, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/repair", + "depends": [ + "repair_type", + ], + "data": [ + "security/ir.model.access.csv", + "views/repair_order_view.xml", + "views/stock_scrap_view.xml", + "views/repair_type_views.xml", + "wizards/repair_scrap_view.xml", + ], + "installable": True, +} diff --git a/repair_scrap/models/__init__.py b/repair_scrap/models/__init__.py new file mode 100644 index 00000000..8921ce1c --- /dev/null +++ b/repair_scrap/models/__init__.py @@ -0,0 +1,5 @@ +from . import repair_order +from . import repair_type +from . import stock_move +from . import stock_rule +from . import stock_scrap diff --git a/repair_scrap/models/repair_order.py b/repair_scrap/models/repair_order.py new file mode 100644 index 00000000..440c83b3 --- /dev/null +++ b/repair_scrap/models/repair_order.py @@ -0,0 +1,26 @@ +from odoo import fields, models + + +class RepairOrder(models.Model): + _inherit = "repair.order" + + scrap_count = fields.Integer(compute="_compute_scrap_count", string="# Scrap") + + scrap_ids = fields.One2many("stock.scrap", "repair_id") + + def _compute_scrap_count(self): + for order in self: + order.scrap_count = len(order.scrap_ids) + + def action_view_scrap_transfers(self): + self.ensure_one() + action = self.env.ref("stock.action_stock_scrap") + result = action.sudo().read()[0] + scraps = self.env["stock.scrap"].search([("origin", "=", self.name)]) + if len(scraps) > 1: + result["domain"] = [("id", "in", scraps.ids)] + elif len(scraps) == 1: + res = self.env.ref("stock.stock_scrap_form_view", False) + result["views"] = [(res and res.id or False, "form")] + result["res_id"] = scraps.ids[0] + return result diff --git a/repair_scrap/models/repair_type.py b/repair_scrap/models/repair_type.py new file mode 100644 index 00000000..2b864b81 --- /dev/null +++ b/repair_scrap/models/repair_type.py @@ -0,0 +1,10 @@ +from odoo import fields, models + + +class RepairType(models.Model): + _inherit = "repair.type" + + scrap_location_id = fields.Many2one( + comodel_name="stock.location", + string="Scrap Destination Location", + ) diff --git a/repair_scrap/models/stock_move.py b/repair_scrap/models/stock_move.py new file mode 100644 index 00000000..15a406a0 --- /dev/null +++ b/repair_scrap/models/stock_move.py @@ -0,0 +1,11 @@ +from odoo import fields, models + + +class StockMove(models.Model): + _inherit = "stock.move" + + is_repair_scrap = fields.Boolean( + string="Is repair Scrap", + copy=False, + help="This Stock Move has been created from a Scrap operation in the Repair.", + ) diff --git a/repair_scrap/models/stock_rule.py b/repair_scrap/models/stock_rule.py new file mode 100644 index 00000000..71ea5816 --- /dev/null +++ b/repair_scrap/models/stock_rule.py @@ -0,0 +1,30 @@ +from odoo import models + + +class StockRule(models.Model): + _inherit = "stock.rule" + + def _get_stock_move_values( + self, + product_id, + product_qty, + product_uom, + location_id, + name, + origin, + company_id, + values, + ): + res = super()._get_stock_move_values( + product_id, + product_qty, + product_uom, + location_id, + name, + origin, + company_id, + values, + ) + if "is_repair_scrap" in values: + res["is_repair_scrap"] = values.get("is_repair_scrap") + return res diff --git a/repair_scrap/models/stock_scrap.py b/repair_scrap/models/stock_scrap.py new file mode 100644 index 00000000..80ad367a --- /dev/null +++ b/repair_scrap/models/stock_scrap.py @@ -0,0 +1,33 @@ +from odoo import fields, models + + +class StockScrap(models.Model): + _inherit = "stock.scrap" + + repair_id = fields.Many2one("repair.order", string="Repair order") + + is_repair_scrap = fields.Boolean( + default=False, + copy=False, + help="This Stock Move has been created from a Scrap operation in Repair.", + ) + + def do_scrap(self): + res = super(StockScrap, self).do_scrap() + if self.is_repair_scrap: + self.move_id.is_repair_scrap = True + return res + + def _prepare_move_values(self): + res = super(StockScrap, self)._prepare_move_values() + res["repair_id"] = self.repair_id.id + return res + + def action_view_repair_order(self): + action = self.env.ref("repair.action_repair_order_tree") + res = self.env.ref("repair.view_repair_order_form", False) + result = action.sudo().read()[0] + # choose the view_mode accordingly + result["views"] = [(res and res.id or False, "form")] + result["res_id"] = self.repair_id.id + return result diff --git a/repair_scrap/readme/CONTRIBUTORS.rst b/repair_scrap/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..f896b2f2 --- /dev/null +++ b/repair_scrap/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Thiago Mulero diff --git a/repair_scrap/readme/DESCRIPTION.rst b/repair_scrap/readme/DESCRIPTION.rst new file mode 100644 index 00000000..ab05db33 --- /dev/null +++ b/repair_scrap/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +Manage repair scraps diff --git a/repair_scrap/security/ir.model.access.csv b/repair_scrap/security/ir.model.access.csv new file mode 100644 index 00000000..5f38558a --- /dev/null +++ b/repair_scrap/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_repair_scrap_wizard,Wizard repair scrap user,model_repair_make_scrap_wizard,stock.group_stock_user,1,1,1,1 +access_repair_scrap_wizard_item,Wizard item repair scrap user,model_repair_make_scrap_item_wizard,stock.group_stock_user,1,1,1,1 diff --git a/repair_scrap/static/description/icon.png b/repair_scrap/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/repair_scrap/static/description/icon.png differ diff --git a/repair_scrap/tests/__init__.py b/repair_scrap/tests/__init__.py new file mode 100644 index 00000000..30ff857d --- /dev/null +++ b/repair_scrap/tests/__init__.py @@ -0,0 +1 @@ +from . import test_repair_scrap diff --git a/repair_scrap/tests/test_repair_scrap.py b/repair_scrap/tests/test_repair_scrap.py new file mode 100644 index 00000000..e624ba34 --- /dev/null +++ b/repair_scrap/tests/test_repair_scrap.py @@ -0,0 +1,100 @@ +from odoo.tests.common import TransactionCase + + +class TestRepair(TransactionCase): + def setUp(self, *args, **kwargs): + super().setUp(*args, **kwargs) + self.repair_obj = self.env["repair.order"] + self.company = self.env.ref("base.main_company") + self.product = self.env.ref("product.product_product_4") + self.stock_location_stock = self.env.ref("stock.stock_location_stock") + self.repair_make_scrap_wiz = self.env["repair_make_scrap.wizard"] + self.wh = self.env.ref("stock.warehouse0") + self.scrap_loc = self.env["stock.location"].create( + { + "name": "WH Scrap Location", + "location_id": self.wh.view_location_id.id, + "scrap_location": True, + } + ) + self.repair_type = self.env["repair.type"].create( + { + "name": "Scrap Repair", + "scrap_location_id": self.scrap_loc.id, + } + ) + self.lot = self.env["stock.production.lot"].create( + { + "name": "lot test", + "product_id": self.product.id, + "company_id": self.company.id, + } + ) + self._update_product_stock(1, self.lot.id) + + def _update_product_stock(self, qty, lot_id=False, location=None): + quant = self.env["stock.quant"].create( + { + "product_id": self.product.id, + "location_id": (location.id if location else self.wh.lot_stock_id.id), + "lot_id": lot_id, + "inventory_quantity": qty, + } + ) + quant.action_apply_inventory() + + def test_01_repair_scrap(self): + repair = self.repair_obj.create( + { + "product_id": self.product.id, + "product_uom": self.product.uom_id.id, + "repair_type_id": self.repair_type.id, + "location_id": self.stock_location_stock.id, + "lot_id": self.lot.id, + "operations": [ + ( + 0, + 0, + { + "name": "TEST", + "location_id": self.stock_location_stock.id, + "location_dest_id": self.product.property_stock_production.id, + "product_id": self.product.id, + "product_uom": self.product.uom_id.id, + "product_uom_qty": 1.0, + "price_unit": 50.0, + "state": "draft", + "type": "add", + "company_id": self.company.id, + "lot_id": self.lot.id, + }, + ) + ], + } + ) + repair.action_validate() + wizard = self.repair_make_scrap_wiz.with_context( + **{ + "active_ids": repair.id, + "active_model": "repair.order", + "item_ids": [ + 0, + 0, + { + "line_id": repair.id, + "product_id": repair.product_id.id, + "product_qty": repair.product_qty, + "location_id": repair.location_id, + "uom_id": repair.product_id.uom_id.id, + }, + ], + } + ).create({}) + action = wizard.action_create_scrap() + scrap = self.env["stock.scrap"].browse([action["res_id"]]) + self.assertEqual(scrap.location_id.id, self.stock_location_stock.id) + self.assertEqual(scrap.scrap_location_id.id, self.scrap_loc.id) + self.assertEqual(scrap.move_id.id, False) + self.assertEqual(scrap.lot_id.id, self.lot.id) + scrap.action_validate() + self.assertEqual(scrap.move_id.product_id.id, self.product.id) diff --git a/repair_scrap/views/repair_order_view.xml b/repair_scrap/views/repair_order_view.xml new file mode 100644 index 00000000..16086b9c --- /dev/null +++ b/repair_scrap/views/repair_order_view.xml @@ -0,0 +1,22 @@ + + + + repair.form - Scrap + repair.order + + +
+ +
+
+
+
diff --git a/repair_scrap/views/repair_type_views.xml b/repair_scrap/views/repair_type_views.xml new file mode 100644 index 00000000..6a2aa717 --- /dev/null +++ b/repair_scrap/views/repair_type_views.xml @@ -0,0 +1,31 @@ + + + + + Repair Types List - Scrap + repair.type + + + + + + + + + + Repair Types Form - Scrap + repair.type + + + + + + + + + + + diff --git a/repair_scrap/views/stock_scrap_view.xml b/repair_scrap/views/stock_scrap_view.xml new file mode 100644 index 00000000..0667f9d9 --- /dev/null +++ b/repair_scrap/views/stock_scrap_view.xml @@ -0,0 +1,23 @@ + + + + stock.scrap.form - Repair + stock.scrap + + + + + + + + diff --git a/repair_scrap/wizards/__init__.py b/repair_scrap/wizards/__init__.py new file mode 100644 index 00000000..b2df95cb --- /dev/null +++ b/repair_scrap/wizards/__init__.py @@ -0,0 +1 @@ +from . import repair_make_scrap diff --git a/repair_scrap/wizards/repair_make_scrap.py b/repair_scrap/wizards/repair_make_scrap.py new file mode 100644 index 00000000..7cdc7bcc --- /dev/null +++ b/repair_scrap/wizards/repair_make_scrap.py @@ -0,0 +1,113 @@ +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class RepairMakeScrap(models.TransientModel): + _name = "repair_make_scrap.wizard" + _description = "Wizard to create scrap from repair" + + item_ids = fields.One2many( + comodel_name="repair_make_scrap_item.wizard", + inverse_name="wiz_id", + string="Items", + ) + + @api.returns("repair.order") + def _prepare_item(self, line): + values = { + "product_id": line.product_id.id, + "product_qty": line.product_qty, + "location_id": line.location_id.id, + "scrap_location_id": line.repair_type_id.scrap_location_id.id, + "uom_id": line.product_id.uom_id.id, + "repair_id": line.id, + "lot_id": line.lot_id.id, + } + return values + + @api.model + def default_get(self, fields_list): + context = self._context.copy() + res = super(RepairMakeScrap, self).default_get(fields_list) + repair_obj = self.env["repair.order"] + repair_ids = self.env.context["active_ids"] or [] + active_model = self.env.context["active_model"] + + if not repair_ids: + return res + assert active_model == "repair.order", "Bad context propagation" + + items = [] + lines = repair_obj.browse(repair_ids) + for line in lines: + items.append([0, 0, self._prepare_item(line)]) + res["item_ids"] = items + context.update({"items_ids": items}) + return res + + def _create_scrap(self): + scraps = [] + for item in self.item_ids: + repair = item.repair_id + if repair.state == "draft" or repair.state == "cancel": + raise ValidationError(_("Repair %s is not confirmed") % repair.name) + scrap = self._prepare_scrap(item) + scraps.append(scrap) + item.repair_id.scrap_ids |= scrap + return scraps + + def action_create_scrap(self): + self._create_scrap() + return self.item_ids[0].repair_id.action_view_scrap_transfers() + + @api.model + def _prepare_scrap(self, item): + repair = item.repair_id + scrap = self.env["stock.scrap"].create( + { + "name": repair.id and repair.name, + "origin": repair.name, + "product_id": repair.product_id.id, + "scrap_qty": item.product_qty, + "product_uom_id": repair.product_id.product_tmpl_id.uom_id.id, + "lot_id": item.lot_id.id, + "location_id": item.location_id.id, + "scrap_location_id": item.scrap_location_id.id, + "repair_id": repair.id, + "create_date": fields.Datetime.now(), + "company_id": repair.company_id.id, + "is_repair_scrap": True, + } + ) + return scrap + + +class RepairMakeScrapItem(models.TransientModel): + _name = "repair_make_scrap_item.wizard" + _description = "Items to Scrap" + + wiz_id = fields.Many2one("repair_make_scrap.wizard", string="Wizard", required=True) + repair_id = fields.Many2one( + "repair.order", string="repair Order", ondelete="cascade", required=True + ) + product_id = fields.Many2one("product.product", string="Product", required=True) + product_qty = fields.Float( + string="Quantity Ordered", + copy=False, + digits="Product Unit of Measure", + ) + company_id = fields.Many2one("res.company", related="repair_id.company_id") + location_id = fields.Many2one( + "stock.location", + string="Source Location", + required=True, + domain="[('usage', '=', 'internal'), ('company_id', 'in', [company_id, False])]", + ) + scrap_location_id = fields.Many2one( + "stock.location", + string="Scrap Location", + required=True, + domain="[('scrap_location', '=', True)]", + ) + uom_id = fields.Many2one("uom.uom", string="Unit of Measure") + lot_id = fields.Many2one("stock.production.lot", string="Lot/Serial") diff --git a/repair_scrap/wizards/repair_scrap_view.xml b/repair_scrap/wizards/repair_scrap_view.xml new file mode 100644 index 00000000..56fd5c6b --- /dev/null +++ b/repair_scrap/wizards/repair_scrap_view.xml @@ -0,0 +1,66 @@ + + + + Create Scrap + repair_make_scrap.wizard + +
+ + + + + + + + + + + + + +
+
+ +
+
+ + + Create Scrap + ir.actions.act_window + repair_make_scrap.wizard + form + new + + + + + + + repair.form - Scrap Button + repair.order + + +
+
+
+
+
diff --git a/setup/repair_scrap/odoo/addons/repair_scrap b/setup/repair_scrap/odoo/addons/repair_scrap new file mode 120000 index 00000000..8feff083 --- /dev/null +++ b/setup/repair_scrap/odoo/addons/repair_scrap @@ -0,0 +1 @@ +../../../../repair_scrap \ No newline at end of file diff --git a/setup/repair_scrap/setup.py b/setup/repair_scrap/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/repair_scrap/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)