mirror of
https://github.com/ForgeFlow/stock-rma.git
synced 2025-01-21 12:57:49 +02:00
[ADD] rma_put_away
This commit is contained in:
committed by
DavidJForgeFlow
parent
48b5cd7e64
commit
ce71a05b8a
47
rma_put_away/README.rst
Normal file
47
rma_put_away/README.rst
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
|
||||||
|
:alt: License LGPL-3
|
||||||
|
|
||||||
|
============
|
||||||
|
RMA Put Away
|
||||||
|
============
|
||||||
|
|
||||||
|
This module allows you to put away the products after you have received them.
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
=============
|
||||||
|
|
||||||
|
Go to *RMA / Configuration / Customer Operations* and define there:
|
||||||
|
|
||||||
|
#. The Put Away Policy
|
||||||
|
#. The route that you wish to use to put away the products.
|
||||||
|
#. The default destination location (optional).
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
#. Go to a Customer RMA.
|
||||||
|
#. Click on *Put Away*.
|
||||||
|
#. Indicate the quantity that you want to put away and destination location.
|
||||||
|
|
||||||
|
Bug Tracker
|
||||||
|
===========
|
||||||
|
|
||||||
|
Bugs are tracked on `GitHub Issues
|
||||||
|
<https://github.com/Eficent/stock-rma/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.
|
||||||
|
|
||||||
|
Credits
|
||||||
|
=======
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Jordi Ballester Alomar <jordi.ballester@ForgeFlow.com>
|
||||||
|
* David Jimenez <david.jimenez@ForgeFlow.com>
|
||||||
|
|
||||||
|
|
||||||
|
Maintainer
|
||||||
|
----------
|
||||||
|
|
||||||
|
This module is maintained by ForgeFlow
|
||||||
2
rma_put_away/__init__.py
Normal file
2
rma_put_away/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from . import wizards
|
||||||
|
from . import models
|
||||||
18
rma_put_away/__manifest__.py
Normal file
18
rma_put_away/__manifest__.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "RMA Put Away",
|
||||||
|
"version": "14.0.1.0.0",
|
||||||
|
"license": "LGPL-3",
|
||||||
|
"category": "RMA",
|
||||||
|
"summary": "Allows to put away the received products in odoo",
|
||||||
|
"author": "ForgeFlow",
|
||||||
|
"website": "https://github.com/ForgeFlow/stock-rma",
|
||||||
|
"depends": ["rma"],
|
||||||
|
"data": [
|
||||||
|
"security/ir.model.access.csv",
|
||||||
|
"views/rma_operation_view.xml",
|
||||||
|
"views/rma_order_line_view.xml",
|
||||||
|
"views/rma_order_view.xml",
|
||||||
|
"wizards/rma_put_away_view.xml",
|
||||||
|
],
|
||||||
|
"installable": True,
|
||||||
|
}
|
||||||
5
rma_put_away/models/__init__.py
Normal file
5
rma_put_away/models/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from . import rma_operation
|
||||||
|
from . import stock_move
|
||||||
|
from . import stock_rule
|
||||||
|
from . import rma_order_line
|
||||||
|
from . import rma_order
|
||||||
29
rma_put_away/models/rma_operation.py
Normal file
29
rma_put_away/models/rma_operation.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Copyright 2022 ForgeFlow S.L.
|
||||||
|
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.html).
|
||||||
|
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class RmaOperation(models.Model):
|
||||||
|
_inherit = "rma.operation"
|
||||||
|
|
||||||
|
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",
|
||||||
|
)
|
||||||
|
put_away_route_id = fields.Many2one(
|
||||||
|
comodel_name="stock.location.route",
|
||||||
|
string="Put Away Route",
|
||||||
|
domain=[("rma_selectable", "=", True)],
|
||||||
|
default=lambda self: self._default_routes(),
|
||||||
|
)
|
||||||
|
|
||||||
|
put_away_location_id = fields.Many2one(
|
||||||
|
comodel_name="stock.location",
|
||||||
|
string="Put Away Destination Location",
|
||||||
|
)
|
||||||
31
rma_put_away/models/rma_order.py
Normal file
31
rma_put_away/models/rma_order.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Copyright 2022 ForgeFlow S.L.
|
||||||
|
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class RmaOrder(models.Model):
|
||||||
|
_inherit = "rma.order"
|
||||||
|
|
||||||
|
def _compute_put_away_count(self):
|
||||||
|
for order in self:
|
||||||
|
pickings = (
|
||||||
|
order.mapped("rma_line_ids.move_ids")
|
||||||
|
.filtered(lambda m: m.is_rma_put_away)
|
||||||
|
.mapped("picking_id")
|
||||||
|
)
|
||||||
|
order.put_away_count = len(pickings)
|
||||||
|
|
||||||
|
put_away_count = fields.Integer(
|
||||||
|
compute="_compute_put_away_count", string="# Put Away"
|
||||||
|
)
|
||||||
|
|
||||||
|
def action_view_put_away_transfers(self):
|
||||||
|
self.ensure_one()
|
||||||
|
action = self.env.ref("stock.action_picking_tree_all")
|
||||||
|
result = action.sudo().read()[0]
|
||||||
|
pickings = self.env["stock.picking"]
|
||||||
|
for line in self.rma_line_ids:
|
||||||
|
pickings |= line.move_ids.filtered(lambda m: m.is_rma_put_away).mapped(
|
||||||
|
"picking_id"
|
||||||
|
)
|
||||||
|
return self._view_shipments(result, pickings)
|
||||||
119
rma_put_away/models/rma_order_line.py
Normal file
119
rma_put_away/models/rma_order_line.py
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# Copyright 2022 ForgeFlow S.L.
|
||||||
|
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class RmaOrderLine(models.Model):
|
||||||
|
_inherit = "rma.order.line"
|
||||||
|
|
||||||
|
@api.depends(
|
||||||
|
"move_ids",
|
||||||
|
"move_ids.state",
|
||||||
|
"move_ids.is_rma_put_away",
|
||||||
|
"qty_put_away",
|
||||||
|
"put_away_policy",
|
||||||
|
"product_qty",
|
||||||
|
)
|
||||||
|
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
|
||||||
|
|
||||||
|
@api.depends("move_ids", "move_ids.state", "move_ids.is_rma_put_away")
|
||||||
|
def _compute_qty_in_put_away(self):
|
||||||
|
product_obj = self.env["uom.uom"]
|
||||||
|
for rec in self:
|
||||||
|
qty = 0.0
|
||||||
|
for move in rec.move_ids.filtered(
|
||||||
|
lambda m: m.state in ["draft", "confirmed", "assigned"]
|
||||||
|
and m.is_rma_put_away
|
||||||
|
):
|
||||||
|
qty += product_obj._compute_quantity(move.product_uom_qty, rec.uom_id)
|
||||||
|
rec.qty_in_put_away = qty
|
||||||
|
|
||||||
|
@api.depends("move_ids", "move_ids.state", "move_ids.is_rma_put_away")
|
||||||
|
def _compute_qty_put_away(self):
|
||||||
|
product_obj = self.env["uom.uom"]
|
||||||
|
for rec in self:
|
||||||
|
qty = 0.0
|
||||||
|
for move in rec.move_ids.filtered(
|
||||||
|
lambda m: m.state in ["done"] and m.is_rma_put_away
|
||||||
|
):
|
||||||
|
qty += product_obj._compute_quantity(move.product_uom_qty, rec.uom_id)
|
||||||
|
rec.qty_put_away = qty
|
||||||
|
|
||||||
|
def _compute_put_away_count(self):
|
||||||
|
for line in self:
|
||||||
|
pickings = self.move_ids.filtered(lambda m: m.is_rma_put_away).mapped(
|
||||||
|
"picking_id"
|
||||||
|
)
|
||||||
|
line.put_away_count = len(pickings)
|
||||||
|
|
||||||
|
qty_to_put_away = fields.Float(
|
||||||
|
string="Qty To Put Away",
|
||||||
|
copy=False,
|
||||||
|
digits="Product Unit of Measure",
|
||||||
|
readonly=True,
|
||||||
|
compute="_compute_qty_to_put_away",
|
||||||
|
store=True,
|
||||||
|
)
|
||||||
|
qty_in_put_away = fields.Float(
|
||||||
|
string="Qty In Put Away",
|
||||||
|
copy=False,
|
||||||
|
digits="Product Unit of Measure",
|
||||||
|
readonly=True,
|
||||||
|
compute="_compute_qty_in_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,
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
readonly=False,
|
||||||
|
)
|
||||||
|
put_away_count = fields.Integer(
|
||||||
|
compute="_compute_put_away_count", string="# Put Aways"
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.onchange("operation_id")
|
||||||
|
def _onchange_operation_id(self):
|
||||||
|
res = super(RmaOrderLine, self)._onchange_operation_id()
|
||||||
|
if self.operation_id:
|
||||||
|
self.put_away_policy = self.operation_id.put_away_policy or "no"
|
||||||
|
return res
|
||||||
|
|
||||||
|
def action_view_put_away_transfers(self):
|
||||||
|
action = self.env.ref("stock.action_picking_tree_all")
|
||||||
|
result = action.sudo().read()[0]
|
||||||
|
pickings = self.env["stock.picking"]
|
||||||
|
for line in self:
|
||||||
|
if line.lot_id:
|
||||||
|
line.move_ids.lot_ids |= line.lot_id
|
||||||
|
line.move_ids.move_line_ids.lot_id = line.lot_id
|
||||||
|
pickings |= line.move_ids.filtered(lambda m: m.is_rma_put_away).mapped(
|
||||||
|
"picking_id"
|
||||||
|
)
|
||||||
|
# choose the view_mode accordingly
|
||||||
|
if len(pickings) != 1:
|
||||||
|
result["domain"] = "[('id', 'in', " + str(pickings.ids) + ")]"
|
||||||
|
elif len(pickings) == 1:
|
||||||
|
res = self.env.ref("stock.view_picking_form", False)
|
||||||
|
result["views"] = [(res and res.id or False, "form")]
|
||||||
|
result["res_id"] = pickings.ids[0]
|
||||||
|
return result
|
||||||
21
rma_put_away/models/stock_move.py
Normal file
21
rma_put_away/models/stock_move.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Copyright 2022 ForgeFlow S.L.
|
||||||
|
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
|
||||||
|
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class StockMove(models.Model):
|
||||||
|
_inherit = "stock.move"
|
||||||
|
|
||||||
|
is_rma_put_away = fields.Boolean(
|
||||||
|
string="Is RMA Put Away",
|
||||||
|
copy=False,
|
||||||
|
help="This Stock Move has been created from a Put Away operation in "
|
||||||
|
"the RMA.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _is_in_out_rma_move(self, op, states, location_type):
|
||||||
|
res = super(StockMove, self)._is_in_out_rma_move(op, states, location_type)
|
||||||
|
if self.is_rma_put_away:
|
||||||
|
return False
|
||||||
|
return res
|
||||||
33
rma_put_away/models/stock_rule.py
Normal file
33
rma_put_away/models/stock_rule.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Copyright 2022 ForgeFlow S.L.
|
||||||
|
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
|
||||||
|
|
||||||
|
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_rma_put_away" in values:
|
||||||
|
res["is_rma_put_away"] = values.get("is_rma_put_away")
|
||||||
|
return res
|
||||||
7
rma_put_away/security/ir.model.access.csv
Normal file
7
rma_put_away/security/ir.model.access.csv
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
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,rma.group_rma_manager,1,1,1,1
|
||||||
|
access_rma_put_away_wizard_customer,rma.order.manager,model_rma_make_put_away_wizard,rma.group_rma_customer_user,1,1,1,1
|
||||||
|
access_rma_put_away_wizard_supplier,rma.order.manager,model_rma_make_put_away_wizard,rma.group_rma_supplier_user,1,1,1,1
|
||||||
|
access_rma_put_away_item_wizard,rma.order.manager,model_rma_make_put_away_item_wizard,rma.group_rma_manager,1,1,1,1
|
||||||
|
access_rma_put_away_item_wizard_customer,rma.order.manager,model_rma_make_put_away_item_wizard,rma.group_rma_customer_user,1,1,1,1
|
||||||
|
access_rma_put_away_item_wizard_supplier,rma.order.manager,model_rma_make_put_away_item_wizard,rma.group_rma_supplier_user,1,1,1,1
|
||||||
|
1
rma_put_away/tests/__init__.py
Normal file
1
rma_put_away/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import test_rma_put_away
|
||||||
235
rma_put_away/tests/test_rma_put_away.py
Normal file
235
rma_put_away/tests/test_rma_put_away.py
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
from odoo.tests import common
|
||||||
|
|
||||||
|
|
||||||
|
class TestRmaPutAway(common.SingleTransactionCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super(TestRmaPutAway, cls).setUpClass()
|
||||||
|
|
||||||
|
cls.rma_obj = cls.env["rma.order"]
|
||||||
|
cls.rma_make_picking = cls.env["rma_make_picking.wizard"]
|
||||||
|
cls.rma_line_obj = cls.env["rma.order.line"]
|
||||||
|
cls.rma_op_obj = cls.env["rma.operation"]
|
||||||
|
cls.rma_make_put_away_wiz = cls.env["rma_make_put_away.wizard"]
|
||||||
|
cls.product_obj = cls.env["product.product"]
|
||||||
|
cls.partner_obj = cls.env["res.partner"]
|
||||||
|
|
||||||
|
cls.rma_route_cust = cls.env.ref("rma.route_rma_customer")
|
||||||
|
|
||||||
|
# Create customer
|
||||||
|
cls.customer1 = cls.partner_obj.create({"name": "Customer 1"})
|
||||||
|
|
||||||
|
# Create products
|
||||||
|
cls.product_1 = cls.product_obj.create(
|
||||||
|
{"name": "Test Product 1", "type": "product", "list_price": 100.0}
|
||||||
|
)
|
||||||
|
cls.product_2 = cls.product_obj.create(
|
||||||
|
{
|
||||||
|
"name": "Test Product 2",
|
||||||
|
"type": "product",
|
||||||
|
"list_price": 150.0,
|
||||||
|
"tracking": "lot",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.lot = cls.env["stock.production.lot"].create(
|
||||||
|
{
|
||||||
|
"name": "Lot for tests",
|
||||||
|
"product_id": cls.product_2.id,
|
||||||
|
"company_id": cls.env.ref("base.main_company").id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.warehouse = cls.env["stock.warehouse"].create(
|
||||||
|
{
|
||||||
|
"name": "Warehouse",
|
||||||
|
"reception_steps": "one_step",
|
||||||
|
"delivery_steps": "ship_only",
|
||||||
|
"code": "WH1",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.warehouse_rma = cls.env["stock.warehouse"].create(
|
||||||
|
{
|
||||||
|
"name": "Warehouse RMA",
|
||||||
|
"reception_steps": "one_step",
|
||||||
|
"delivery_steps": "ship_only",
|
||||||
|
"code": "WH2",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.rma_loc = cls.env["stock.location"].create(
|
||||||
|
{"name": "WH RMA", "location_id": cls.warehouse_rma.view_location_id.id}
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.put_away_loc = cls.env["stock.location"].create(
|
||||||
|
{
|
||||||
|
"name": "WH Put Away Location",
|
||||||
|
"location_id": cls.warehouse.view_location_id.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.route1 = cls.env["stock.location.route"].create(
|
||||||
|
{
|
||||||
|
"name": "Transfer WH1",
|
||||||
|
"rma_selectable": True,
|
||||||
|
"sequence": 10,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.env["stock.rule"].create(
|
||||||
|
{
|
||||||
|
"name": "Transfer",
|
||||||
|
"route_id": cls.route1.id,
|
||||||
|
"location_src_id": cls.rma_loc.id,
|
||||||
|
"location_id": cls.put_away_loc.id,
|
||||||
|
"action": "pull",
|
||||||
|
"picking_type_id": cls.warehouse.int_type_id.id,
|
||||||
|
"procure_method": "make_to_stock",
|
||||||
|
"warehouse_id": cls.warehouse.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create RMA group and operation:
|
||||||
|
cls.rma_group = cls.rma_obj.create({"partner_id": cls.customer1.id})
|
||||||
|
|
||||||
|
cls.operation_1 = cls.rma_op_obj.create(
|
||||||
|
{
|
||||||
|
"code": "TEST1",
|
||||||
|
"name": "Operation 1",
|
||||||
|
"type": "customer",
|
||||||
|
"receipt_policy": "ordered",
|
||||||
|
"put_away_policy": "received",
|
||||||
|
"put_away_route_id": cls.route1.id,
|
||||||
|
"put_away_location_id": cls.put_away_loc.id,
|
||||||
|
"in_route_id": cls.rma_route_cust.id,
|
||||||
|
"out_route_id": cls.rma_route_cust.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.operation_2 = cls.rma_op_obj.create(
|
||||||
|
{
|
||||||
|
"code": "TEST2",
|
||||||
|
"name": "Operation 2",
|
||||||
|
"type": "customer",
|
||||||
|
"receipt_policy": "ordered",
|
||||||
|
"put_away_policy": "ordered",
|
||||||
|
"put_away_route_id": cls.route1.id,
|
||||||
|
"put_away_location_id": cls.put_away_loc.id,
|
||||||
|
"in_route_id": cls.rma_route_cust.id,
|
||||||
|
"out_route_id": cls.rma_route_cust.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_01_rma_put_away_without_lot(self):
|
||||||
|
"""Generate a Sales Order from a customer RMA."""
|
||||||
|
rma = self.rma_line_obj.create(
|
||||||
|
{
|
||||||
|
"partner_id": self.customer1.id,
|
||||||
|
"product_id": self.product_1.id,
|
||||||
|
"operation_id": self.operation_1.id,
|
||||||
|
"uom_id": self.product_1.uom_id.id,
|
||||||
|
"in_route_id": self.operation_1.in_route_id.id,
|
||||||
|
"out_route_id": self.operation_1.out_route_id.id,
|
||||||
|
"in_warehouse_id": self.operation_1.in_warehouse_id.id,
|
||||||
|
"out_warehouse_id": self.operation_1.out_warehouse_id.id,
|
||||||
|
"location_id": self.rma_loc.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
rma._onchange_operation_id()
|
||||||
|
rma.action_rma_to_approve()
|
||||||
|
wizard = self.rma_make_picking.with_context(
|
||||||
|
{
|
||||||
|
"active_ids": rma.id,
|
||||||
|
"active_model": "rma.order.line",
|
||||||
|
"picking_type": "incoming",
|
||||||
|
"active_id": 1,
|
||||||
|
}
|
||||||
|
).create({})
|
||||||
|
wizard._create_picking()
|
||||||
|
wizard = self.rma_make_put_away_wiz.with_context(
|
||||||
|
{
|
||||||
|
"active_ids": rma.id,
|
||||||
|
"active_model": "rma.order.line",
|
||||||
|
"item_ids": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
"line_id": rma.id,
|
||||||
|
"product_id": rma.product_id.id,
|
||||||
|
"product_qty": rma.product_qty,
|
||||||
|
"location_id": rma.location_id,
|
||||||
|
"qty_to_put_away": rma.qty_to_put_away,
|
||||||
|
"uom_id": rma.uom_id.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
).create({})
|
||||||
|
action = wizard.action_create_put_away()
|
||||||
|
picking = self.env["stock.picking"].browse([action["res_id"]])
|
||||||
|
self.assertEqual(picking.location_dest_id.id, self.put_away_loc.id)
|
||||||
|
self.assertEqual(picking.location_id.id, self.rma_loc.id)
|
||||||
|
move = picking.move_ids_without_package
|
||||||
|
self.assertEqual(move.product_id.id, self.product_1.id)
|
||||||
|
self.assertEqual(move.product_uom_qty, 1)
|
||||||
|
move.quantity_done = 1
|
||||||
|
self.assertTrue(picking.action_assign())
|
||||||
|
self.assertTrue(picking.button_validate())
|
||||||
|
|
||||||
|
def test_02_rma_put_away_with_lot(self):
|
||||||
|
"""Generate a Sales Order from a customer RMA."""
|
||||||
|
rma = self.rma_line_obj.create(
|
||||||
|
{
|
||||||
|
"partner_id": self.customer1.id,
|
||||||
|
"product_id": self.product_2.id,
|
||||||
|
"lot_id": self.lot.id,
|
||||||
|
"operation_id": self.operation_1.id,
|
||||||
|
"uom_id": self.product_2.uom_id.id,
|
||||||
|
"in_route_id": self.operation_1.in_route_id.id,
|
||||||
|
"out_route_id": self.operation_1.out_route_id.id,
|
||||||
|
"in_warehouse_id": self.operation_1.in_warehouse_id.id,
|
||||||
|
"out_warehouse_id": self.operation_1.out_warehouse_id.id,
|
||||||
|
"location_id": self.rma_loc.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
rma._onchange_operation_id()
|
||||||
|
rma._onchange_lot_id()
|
||||||
|
rma.action_rma_to_approve()
|
||||||
|
wizard = self.rma_make_picking.with_context(
|
||||||
|
{
|
||||||
|
"active_ids": rma.id,
|
||||||
|
"active_model": "rma.order.line",
|
||||||
|
"picking_type": "incoming",
|
||||||
|
"active_id": 1,
|
||||||
|
}
|
||||||
|
).create({})
|
||||||
|
wizard._create_picking()
|
||||||
|
wizard = self.rma_make_put_away_wiz.with_context(
|
||||||
|
{
|
||||||
|
"active_ids": rma.id,
|
||||||
|
"active_model": "rma.order.line",
|
||||||
|
"item_ids": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
"line_id": rma.id,
|
||||||
|
"product_id": rma.product_id.id,
|
||||||
|
"product_qty": rma.product_qty,
|
||||||
|
"lot_id": rma.lot_id.id,
|
||||||
|
"location_id": rma.location_id,
|
||||||
|
"qty_to_put_away": rma.qty_to_put_away,
|
||||||
|
"uom_id": rma.uom_id.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
).create({})
|
||||||
|
action = wizard.action_create_put_away()
|
||||||
|
picking = self.env["stock.picking"].browse([action["res_id"]])
|
||||||
|
self.assertEqual(picking.location_dest_id.id, self.put_away_loc.id)
|
||||||
|
self.assertEqual(picking.location_id.id, self.rma_loc.id)
|
||||||
|
move = picking.move_ids_without_package
|
||||||
|
self.assertEqual(move.product_id.id, self.product_2.id)
|
||||||
|
self.assertEqual(move.product_uom_qty, 1)
|
||||||
|
self.assertEqual(move.lot_ids.id, self.lot.id)
|
||||||
|
move.quantity_done = 1
|
||||||
|
move.move_line_ids.lot_id = self.lot
|
||||||
|
self.assertTrue(picking.button_validate())
|
||||||
32
rma_put_away/views/rma_operation_view.xml
Normal file
32
rma_put_away/views/rma_operation_view.xml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="rma_operation_tree" model="ir.ui.view">
|
||||||
|
<field name="name">rma.operation.tree</field>
|
||||||
|
<field name="model">rma.operation</field>
|
||||||
|
<field name="inherit_id" ref="rma.rma_operation_tree" />
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="delivery_policy" position="after">
|
||||||
|
<field name="put_away_policy" />
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="rma_operation_form" model="ir.ui.view">
|
||||||
|
<field name="name">rma.operation.form - rma_put_away</field>
|
||||||
|
<field name="model">rma.operation</field>
|
||||||
|
<field name="inherit_id" ref="rma.rma_operation_form" />
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="delivery_policy" position="after">
|
||||||
|
<field name="put_away_policy" />
|
||||||
|
</field>
|
||||||
|
<group name="outbound" position="after">
|
||||||
|
<group name="put_away" string="Put Away">
|
||||||
|
<field name="put_away_route_id" string="Route" />
|
||||||
|
<field name="put_away_location_id" string="Destination Location" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
55
rma_put_away/views/rma_order_line_view.xml
Normal file
55
rma_put_away/views/rma_order_line_view.xml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_rma_line_form" model="ir.ui.view">
|
||||||
|
<field name="name">rma.order.line.form</field>
|
||||||
|
<field name="model">rma.order.line</field>
|
||||||
|
<field name="inherit_id" ref="rma.view_rma_line_form" />
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<button name='action_view_out_shipments' position="after">
|
||||||
|
<button
|
||||||
|
type="object"
|
||||||
|
name="action_view_put_away_transfers"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-truck"
|
||||||
|
groups="stock.group_stock_user"
|
||||||
|
attrs="{'invisible': [('put_away_count', '=', 0)]}"
|
||||||
|
>
|
||||||
|
<field
|
||||||
|
name="put_away_count"
|
||||||
|
widget="statinfo"
|
||||||
|
string="Put Aways"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</button>
|
||||||
|
<group name="quantities" position="inside">
|
||||||
|
<group>
|
||||||
|
<field name="qty_to_put_away" />
|
||||||
|
<field name="qty_in_put_away" />
|
||||||
|
<field name="qty_put_away" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<field name="delivery_policy" position="after">
|
||||||
|
<field name="put_away_policy" />
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_rma_rma_line_filter" model="ir.ui.view">
|
||||||
|
<field name="name">rma.order.line.select - rma_put_away</field>
|
||||||
|
<field name="model">rma.order.line</field>
|
||||||
|
<field name="inherit_id" ref="rma.view_rma_rma_line_filter" />
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<group name="stock_quantities" position="after">
|
||||||
|
<group name="put_away_quantities" groups="stock.group_stock_user">
|
||||||
|
<filter
|
||||||
|
domain="[('state','!=', 'done'),('qty_to_put_away','>',0.0)]"
|
||||||
|
help="To Put Away"
|
||||||
|
name="to_put_away"
|
||||||
|
/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
25
rma_put_away/views/rma_order_view.xml
Normal file
25
rma_put_away/views/rma_order_view.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_rma_form" model="ir.ui.view">
|
||||||
|
<field name="name">rma.order.form</field>
|
||||||
|
<field name="model">rma.order</field>
|
||||||
|
<field name="inherit_id" ref="rma.view_rma_form" />
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<button name="action_view_out_shipments" position="after">
|
||||||
|
<button
|
||||||
|
type="object"
|
||||||
|
name="action_view_put_away_transfers"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-truck"
|
||||||
|
groups="stock.group_stock_user"
|
||||||
|
attrs="{'invisible': [('put_away_count', '=', 0)]}"
|
||||||
|
>
|
||||||
|
<field name="put_away_count" widget="statinfo" string="Put Aways" />
|
||||||
|
</button>
|
||||||
|
</button>
|
||||||
|
<xpath expr="//field[@name='rma_line_ids']/tree" position="inside">
|
||||||
|
<field name="put_away_policy" invisible="True" />
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
1
rma_put_away/wizards/__init__.py
Normal file
1
rma_put_away/wizards/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import rma_make_put_away
|
||||||
141
rma_put_away/wizards/rma_make_put_away.py
Normal file
141
rma_put_away/wizards/rma_make_put_away.py
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# Copyright 2022 ForgeFlow S.L.
|
||||||
|
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0.html).
|
||||||
|
|
||||||
|
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(
|
||||||
|
comodel_name="rma_make_put_away_item.wizard",
|
||||||
|
inverse_name="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,
|
||||||
|
"location_id": line.operation_id.put_away_location_id.id,
|
||||||
|
"uom_id": line.uom_id.id,
|
||||||
|
"qty_to_put_away": line.qty_to_put_away,
|
||||||
|
"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)
|
||||||
|
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"""
|
||||||
|
procurements = []
|
||||||
|
errors = []
|
||||||
|
for item in self.item_ids:
|
||||||
|
line = item.line_id
|
||||||
|
if line.state != "approved":
|
||||||
|
raise ValidationError(_("RMA %s is not approved") % line.name)
|
||||||
|
procurement = self._prepare_procurement(item)
|
||||||
|
procurements.append(procurement)
|
||||||
|
try:
|
||||||
|
self.env["procurement.group"].run(procurements)
|
||||||
|
except UserError as error:
|
||||||
|
errors.append(error.args[0])
|
||||||
|
if errors:
|
||||||
|
raise UserError("\n".join(errors))
|
||||||
|
return procurements
|
||||||
|
|
||||||
|
def action_create_put_away(self):
|
||||||
|
self._create_put_away()
|
||||||
|
action = self.item_ids.line_id.action_view_put_away_transfers()
|
||||||
|
return action
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_procurement_data(self, item):
|
||||||
|
line = item.line_id
|
||||||
|
route = line.operation_id.put_away_route_id
|
||||||
|
if not route:
|
||||||
|
raise ValidationError(_("No route specified"))
|
||||||
|
procurement_data = {
|
||||||
|
"name": line.rma_id and line.rma_id.name or line.name,
|
||||||
|
"origin": line.name,
|
||||||
|
"date_planned": time.strftime(DT_FORMAT),
|
||||||
|
"product_id": item.product_id.id,
|
||||||
|
"product_qty": item.product_qty,
|
||||||
|
"qty_to_put_away": item.product_qty,
|
||||||
|
"product_uom": line.product_id.product_tmpl_id.uom_id.id,
|
||||||
|
"location_id": item.location_id.id,
|
||||||
|
"rma_line_id": line.id,
|
||||||
|
"route_ids": route,
|
||||||
|
"company_id": line.company_id,
|
||||||
|
"is_rma_put_away": True,
|
||||||
|
}
|
||||||
|
return procurement_data
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _prepare_procurement(self, item):
|
||||||
|
line = item.line_id
|
||||||
|
values = self._get_procurement_data(item)
|
||||||
|
values = dict(values, rma_line_id=item.line_id, rma_id=item.line_id.rma_id)
|
||||||
|
procurement = self.env["procurement.group"].Procurement(
|
||||||
|
item.line_id.product_id,
|
||||||
|
item.product_qty,
|
||||||
|
item.line_id.product_id.product_tmpl_id.uom_id,
|
||||||
|
item.location_id,
|
||||||
|
values.get("origin"),
|
||||||
|
values.get("origin"),
|
||||||
|
line.company_id,
|
||||||
|
values,
|
||||||
|
)
|
||||||
|
return procurement
|
||||||
|
|
||||||
|
|
||||||
|
class RmaMakePutAwayItem(models.TransientModel):
|
||||||
|
_name = "rma_make_put_away_item.wizard"
|
||||||
|
_description = "Items to Put Away"
|
||||||
|
|
||||||
|
wiz_id = fields.Many2one("rma_make_put_away.wizard", string="Wizard", required=True)
|
||||||
|
line_id = fields.Many2one(
|
||||||
|
"rma.order.line", string="RMA order Line", ondelete="cascade"
|
||||||
|
)
|
||||||
|
rma_id = fields.Many2one("rma.order", related="line_id.rma_id", string="RMA Group")
|
||||||
|
product_id = fields.Many2one("product.product", string="Product")
|
||||||
|
product_qty = fields.Float(
|
||||||
|
related="line_id.product_qty",
|
||||||
|
string="Quantity Ordered",
|
||||||
|
copy=False,
|
||||||
|
digits="Product Unit of Measure",
|
||||||
|
)
|
||||||
|
location_id = fields.Many2one("stock.location", string="Destination Location")
|
||||||
|
qty_to_put_away = fields.Float(
|
||||||
|
string="Quantity To put away", digits="Product Unit of Measure"
|
||||||
|
)
|
||||||
|
uom_id = fields.Many2one("uom.uom", string="Unit of Measure")
|
||||||
72
rma_put_away/wizards/rma_put_away_view.xml
Normal file
72
rma_put_away/wizards/rma_put_away_view.xml
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_rma_put_away" model="ir.ui.view">
|
||||||
|
<field name="name">Create Put Away</field>
|
||||||
|
<field name="model">rma_make_put_away.wizard</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Select lines for picking" name="lines">
|
||||||
|
<separator string="Select lines for put away" />
|
||||||
|
<field name="item_ids">
|
||||||
|
<tree string="RMA Lines" editable="bottom" create="false">
|
||||||
|
<field name="rma_id" groups="rma.group_rma_groups" />
|
||||||
|
<field name="product_id" />
|
||||||
|
<field name="product_qty" />
|
||||||
|
<field name="qty_to_put_away" />
|
||||||
|
<field name="location_id" />
|
||||||
|
<field name="line_id" invisible="1" />
|
||||||
|
<field name="uom_id" groups="uom.group_uom" />
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
<footer>
|
||||||
|
<button
|
||||||
|
string="Confirm"
|
||||||
|
name="action_create_put_away"
|
||||||
|
type="object"
|
||||||
|
class="oe_highlight"
|
||||||
|
/>
|
||||||
|
or
|
||||||
|
<button
|
||||||
|
name="action_cancel"
|
||||||
|
string="Cancel"
|
||||||
|
class="oe_link"
|
||||||
|
special="cancel"
|
||||||
|
/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_rma_put_away" model="ir.actions.act_window">
|
||||||
|
<field name="name">Create Put Away</field>
|
||||||
|
<field name="type">ir.actions.act_window</field>
|
||||||
|
<field name="res_model">rma_make_put_away.wizard</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="target">new</field>
|
||||||
|
<field name="view_id" ref="view_rma_put_away" />
|
||||||
|
<field name="groups_id" eval="[(4, ref('stock.group_stock_user'))]" />
|
||||||
|
<field name="binding_model_id" ref="rma.model_rma_order_line" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_rma_line_button_put_away_form" model="ir.ui.view">
|
||||||
|
<field name="name">rma.order.line.form</field>
|
||||||
|
<field name="model">rma.order.line</field>
|
||||||
|
<field name="inherit_id" ref="rma.view_rma_line_button_form" />
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<header position="inside">
|
||||||
|
<button
|
||||||
|
name="%(action_rma_put_away)d"
|
||||||
|
string="Put Away"
|
||||||
|
class="oe_highlight"
|
||||||
|
attrs="{'invisible':['|', ('qty_to_put_away', '=', 0), ('state', '!=', 'approved')]}"
|
||||||
|
type="action"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
name="%(action_rma_put_away)d"
|
||||||
|
string="Put Away"
|
||||||
|
attrs="{'invisible':['|', ('qty_to_put_away', '!=', 0), ('state', '!=', 'approved')]}"
|
||||||
|
type="action"
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
Reference in New Issue
Block a user