diff --git a/rma_free_of_charge_true/__init__.py b/rma_free_of_charge_true/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/rma_free_of_charge_true/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/rma_free_of_charge_true/__manifest__.py b/rma_free_of_charge_true/__manifest__.py new file mode 100644 index 00000000..a4b7fc3a --- /dev/null +++ b/rma_free_of_charge_true/__manifest__.py @@ -0,0 +1,16 @@ +{ + "name": "RMA Free of Charge True", + "summary": "Make the field free of charge true by default", + "version": "14.0.1.1.0", + "author": "ForgeFlow, " "Odoo Community Association (OCA)", + "website": "https://github.com/ForgeFlow/stock-rma", + "category": "RMA", + "depends": ["account_move_line_rma_order_line"], + "license": "AGPL-3", + "data": [ + + ], + "installable": True, + "maintainers": ["DavidJForgeFlow"], + "development_status": "Beta", +} diff --git a/rma_free_of_charge_true/models/__init__.py b/rma_free_of_charge_true/models/__init__.py new file mode 100644 index 00000000..afb14633 --- /dev/null +++ b/rma_free_of_charge_true/models/__init__.py @@ -0,0 +1 @@ +from . import rma_line_make_purchase_order_item diff --git a/rma_free_of_charge_true/models/rma_line_make_purchase_order_item.py b/rma_free_of_charge_true/models/rma_line_make_purchase_order_item.py new file mode 100644 index 00000000..6aa6b351 --- /dev/null +++ b/rma_free_of_charge_true/models/rma_line_make_purchase_order_item.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class RmaLineMakePurchaseOrderItem(models.TransientModel): + _inherit = "rma.order.line.make.purchase.order.item" + + free_of_charge = fields.Boolean(default="True") diff --git a/rma_put_away/__init__.py b/rma_put_away/__init__.py new file mode 100644 index 00000000..5cb1c491 --- /dev/null +++ b/rma_put_away/__init__.py @@ -0,0 +1 @@ +from . import wizards diff --git a/rma_put_away/__manifest__.py b/rma_put_away/__manifest__.py new file mode 100644 index 00000000..9654420c --- /dev/null +++ b/rma_put_away/__manifest__.py @@ -0,0 +1,15 @@ +{ + "name": "RMA Put Away", + "version": "14.0.1.0.0", + "license": "LGPL-3", + "category": "RMA", + "summary": "Allows to put away the recieved products" + "in odoo", + "author": "ForgeFlow", + "website": "https://github.com/ForgeFlow/stock-rma", + "depends": ["rma"], + "data": [ + "views/rma_put_away_view.xml", + ], + "installable": True, +} diff --git a/rma_put_away/models/__init__.py b/rma_put_away/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/rma_put_away/models/rma_order_line.py b/rma_put_away/models/rma_order_line.py new file mode 100644 index 00000000..8da9323d --- /dev/null +++ b/rma_put_away/models/rma_order_line.py @@ -0,0 +1,41 @@ +from odoo import _, api, fields, models + + +class RmaOrderLine(models.Model): + _inherit = "rma.order.line" + + qty_to_put_away = fields.Float( + string="Qty To Put Away", + digits="Product Unit of Measure", + compute="_compute_qty_to_put_away", + store=True, + ) + + qty_put_away = fields.Float( + string="Qty Put Away", + copy=False, + digits="Product Unit of Measure", + readonly=True, + compute="_compute_qty_put_away", + store=True, + help="Quantity Put Away.", + ) + + put_away_policy = fields.Selection( + selection=[ + ("no", "Not required"), + ("ordered", "Based on Ordered Quantities"), + ("received", "Based on Received Quantities"), + ], + string="Put Away Policy", + default="no", + required=True, + ) + + def _compute_qty_to_put_away(self): + for rec in self: + rec.qty_to_put_away = 0.0 + if rec.put_away_policy == "ordered": + rec.qty_to_put_away = rec.product_qty - rec.qty_put_away + elif rec.put_away_policy == "received": + rec.qty_to_put_away = rec.qty_received - rec.qty_put_away diff --git a/rma_put_away/security/ir.model.access.csv b/rma_put_away/security/ir.model.access.csv new file mode 100644 index 00000000..e62b89af --- /dev/null +++ b/rma_put_away/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_rma_put_away_wizard,rma.order.manager,model_rma_make_put_away.wizard,group_rma_manager,1,1,1,1 +access_rma_put_away_wizard_customer,rma.order.manager,model_rma_make_put_away.wizard,group_rma_customer_user,1,1,1,1 +access_rma_put_away_wizard_supplier,rma.order.manager,model_rma_make_put_away.wizard,group_rma_supplier_user,1,1,1,1 + diff --git a/rma_put_away/views/rma_put_away_view.xml b/rma_put_away/views/rma_put_away_view.xml new file mode 100644 index 00000000..3b033c24 --- /dev/null +++ b/rma_put_away/views/rma_put_away_view.xml @@ -0,0 +1,71 @@ + + + + Create Put Away + rma_make_put_away.wizard + +
+ + + + + + + + + + +
+
+ +
+
+ + + Create Put Away + ir.actions.act_window + rma_make_put_away.wizard + form + new + + + {'picking_type': 'internal'} + + + + + rma.order.line.form + rma.order.line + + +
+
+
+
+
diff --git a/rma_put_away/wizards/__init__.py b/rma_put_away/wizards/__init__.py new file mode 100644 index 00000000..abe4c211 --- /dev/null +++ b/rma_put_away/wizards/__init__.py @@ -0,0 +1 @@ +from . import rma_make_put_away diff --git a/rma_put_away/wizards/rma_make_put_away.py b/rma_put_away/wizards/rma_make_put_away.py new file mode 100644 index 00000000..6a4eba8a --- /dev/null +++ b/rma_put_away/wizards/rma_make_put_away.py @@ -0,0 +1,214 @@ +import time + +from odoo import _, api, fields, models +from odoo.exceptions import UserError, ValidationError +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DT_FORMAT + + +class RmaMakePutAway(models.TransientModel): + _name = "rma_make_put_away.wizard" + _description = "Wizard to create put away from rma lines" + + item_ids = fields.One2many("rma_make_picking.wizard.item", "wiz_id", string="Items") + + @api.returns("rma.order.line") + def _prepare_item(self, line): + values = { + "product_id": line.product_id.id, + "product_qty": line.product_qty, + "uom_id": line.uom_id.id, + "qty_to_receive": line.qty_to_receive, + "qty_to_deliver": line.qty_to_deliver, + "line_id": line.id, + "rma_id": line.rma_id and line.rma_id.id or False, + } + return values + + @api.model + def default_get(self, fields_list): + """Default values for wizard, if there is more than one supplier on + lines the supplier field is empty otherwise is the unique line + supplier. + """ + context = self._context.copy() + res = super(RmaMakePutAway, self).default_get(fields_list) + rma_line_obj = self.env["rma.order.line"] + rma_line_ids = self.env.context["active_ids"] or [] + active_model = self.env.context["active_model"] + + if not rma_line_ids: + return res + assert active_model == "rma.order.line", "Bad context propagation" + + items = [] + lines = rma_line_obj.browse(rma_line_ids) + if len(lines.mapped("partner_id")) > 1: + raise ValidationError( + _( + "Only RMA lines from the same partner can be processed at " + "the same time" + ) + ) + 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_put_away(self): + """Method called when the user clicks on create picking""" + picking_type = "internal" + procurements = [] + for item in self.item_ids: + line = item.line_id + if line.state != "approved": + raise ValidationError(_("RMA %s is not approved") % line.name) + if line.receipt_policy == "no" and picking_type == "incoming": + raise ValidationError(_("No shipments needed for this operation")) + if line.delivery_policy == "no" and picking_type == "outgoing": + raise ValidationError(_("No deliveries needed for this operation")) + procurement = self._create_procurement(item, picking_type) + procurements.extend(procurement) + return procurements + + def action_create_put_away(self): + self._create_put_away() + move_line_model = self.env["stock.move.line"] + picking_type = "internal" + pickings = self.mapped("item_ids.line_id")._get_in_pickings() + action = self.item_ids.line_id.action_view_in_shipments() + if picking_type == "internal": + # Force the reservation of the RMA specific lot for incoming shipments. + # FIXME: still needs fixing, not reserving appropriate serials. + for move in pickings.move_lines.filtered( + lambda x: x.state not in ("draft", "cancel", "done") + and x.rma_line_id + and x.product_id.tracking in ("lot", "serial") + and x.rma_line_id.lot_id + ): + # Force the reservation of the RMA specific lot for incoming shipments. + move.move_line_ids.unlink() + if move.product_id.tracking == "serial": + move.write( + { + "lot_ids": [(6, 0, move.rma_line_id.lot_id.ids)], + } + ) + move.move_line_ids.write( + { + "product_uom_qty": 1, + "qty_done": 0, + } + ) + elif move.product_id.tracking == "lot": + if picking_type == "internal": + qty = self.item_ids.filtered( + lambda x: x.line_id.id == move.rma_line_id.id + ).qty_to_receive + else: + qty = self.item_ids.filtered( + lambda x: x.line_id.id == move.rma_line_id.id + ).qty_to_deliver + move_line_data = move._prepare_move_line_vals() + move_line_data.update( + { + "lot_id": move.rma_line_id.lot_id.id, + "product_uom_id": move.product_id.uom_id.id, + "qty_done": 0, + "product_uom_qty": qty, + } + ) + move_line_model.create(move_line_data) + + pickings.with_context(force_no_bypass_reservation=True).action_assign() + return action + + @api.model + def _get_address(self, item): + if item.line_id.customer_to_supplier: + delivery_address = item.line_id.supplier_address_id + elif item.line_id.supplier_to_customer: + delivery_address = item.line_id.customer_address_id + elif item.line_id.delivery_address_id: + delivery_address = item.line_id.delivery_address_id + elif item.line_id.partner_id: + delivery_address = item.line_id.partner_id + else: + raise ValidationError(_("Unknown delivery address")) + return delivery_address + + @api.model + def _get_address_location(self, delivery_address_id, a_type): + if a_type == "supplier": + return delivery_address_id.property_stock_supplier + elif a_type == "customer": + return delivery_address_id.property_stock_customer + + @api.model + def _get_procurement_data(self, item, group, qty, picking_type): + line = item.line_id + delivery_address_id = self._get_address(item) + location = self._get_address_location(delivery_address_id, line.type) + warehouse = line.out_warehouse_id + route = line.out_route_id + if not route: + raise ValidationError(_("No route specified")) + if not warehouse: + raise ValidationError(_("No warehouse specified")) + procurement_data = { + "name": line.rma_id and line.rma_id.name or line.name, + "group_id": group, + "origin": line.name, + "warehouse_id": warehouse, + "date_planned": time.strftime(DT_FORMAT), + "product_id": item.product_id, + "product_qty": qty, + "partner_id": delivery_address_id.id, + "product_uom": line.product_id.product_tmpl_id.uom_id.id, + "location_id": location, + "rma_line_id": line.id, + "route_ids": route, + } + return procurement_data + + @api.model + def _create_procurement(self, item, picking_type): + errors = [] + group = self.find_procurement_group(item) + if not group: + pg_data = self._get_procurement_group_data(item) + group = self.env["procurement.group"].create(pg_data) + qty = item.product_qty + values = self._get_procurement_data(item, group, qty, picking_type) + values = dict(values, rma_line_id=item.line_id, rma_id=item.line_id.rma_id) + # create picking + procurements = [] + try: + procurement = group.Procurement( + item.line_id.product_id, + qty, + item.line_id.product_id.product_tmpl_id.uom_id, + values.get("location_id"), + values.get("origin"), + values.get("origin"), + self.env.company, + values, + ) + + procurements.append(procurement) + self.env["procurement.group"].run(procurements) + except UserError as error: + errors.append(error.name) + if errors: + raise UserError("\n".join(errors)) + return procurements + + def find_procurement_group(self, item): + if item.line_id.rma_id: + return self.env["procurement.group"].search( + [("rma_id", "=", item.line_id.rma_id.id)] + ) + else: + return self.env["procurement.group"].search( + [("rma_line_id", "=", item.line_id.id)] + )