mirror of
https://github.com/ForgeFlow/stock-rma.git
synced 2025-01-21 12:57:49 +02:00
[ADD]rma_internal_transfer
This commit is contained in:
44
rma_internal_transfer/README.rst
Normal file
44
rma_internal_transfer/README.rst
Normal file
@@ -0,0 +1,44 @@
|
||||
.. image:: https://img.shields.io/badge/license-LGPL--3-blue.png
|
||||
:target: https://www.gnu.org/licenses/lgpl
|
||||
:alt: License: LGPL-3
|
||||
|
||||
=====================
|
||||
RMA Internal Transfer
|
||||
=====================
|
||||
|
||||
Sometimes the repair location is not the same as the receiving location
|
||||
and useres wants to perform this action manual instead of putaway
|
||||
strategies. In this case an internal tranfer has to be done.
|
||||
|
||||
This module just adds a button that eases this task
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
In the RMA from view there is a new button "Internal Transfer". The user
|
||||
just need to change the default location according its needs.
|
||||
|
||||
|
||||
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 smash it by providing detailed and welcomed feedback.
|
||||
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Aaron Henriquez <ahenriquez@eficent.com>
|
||||
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
|
||||
This module is maintained by Eficent.
|
||||
4
rma_internal_transfer/__init__.py
Normal file
4
rma_internal_transfer/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# Copyright (C) 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
from . import wizards
|
||||
16
rma_internal_transfer/__manifest__.py
Normal file
16
rma_internal_transfer/__manifest__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# Copyright (C) 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
{
|
||||
'name': 'RMA Internal Transfer',
|
||||
'version': '12.0.2.3.0',
|
||||
'license': 'LGPL-3',
|
||||
'category': 'RMA',
|
||||
'summary': 'Internal transfers. Made easy.',
|
||||
'author': "Eficent, Odoo Community Association (OCA)",
|
||||
'website': 'https://github.com/Eficent/stock-rma',
|
||||
'depends': ['rma'],
|
||||
'data': ['wizards/rma_internal_transfer.xml',
|
||||
],
|
||||
'installable': True,
|
||||
}
|
||||
1
rma_internal_transfer/tests/__init__.py
Normal file
1
rma_internal_transfer/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_internal_transfer
|
||||
132
rma_internal_transfer/tests/test_internal_transfer.py
Normal file
132
rma_internal_transfer/tests/test_internal_transfer.py
Normal file
@@ -0,0 +1,132 @@
|
||||
# © 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
from odoo.tests import common
|
||||
|
||||
|
||||
class TestRmaInternalTransfer(common.SavepointCase):
|
||||
""" Test the routes and the quantities """
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestRmaInternalTransfer, cls).setUpClass()
|
||||
|
||||
cls.rma_make_picking = cls.env['rma_internal_transfer.wizard']
|
||||
cls.stockpicking = cls.env['stock.picking']
|
||||
cls.rma_add_stock_move = cls.env['rma_add_stock_move']
|
||||
cls.rma = cls.env['rma.order']
|
||||
cls.rma_line = cls.env['rma.order.line']
|
||||
cls.product_1 = cls.env.ref('product.product_product_25')
|
||||
cls.partner_id = cls.env.ref('base.res_partner_2')
|
||||
cls.stock_location = cls.env.ref('stock.stock_location_stock')
|
||||
wh = cls.env.ref('stock.warehouse0')
|
||||
cls.product_uom_id = cls.env.ref('uom.product_uom_unit')
|
||||
cls.stock_rma_location = wh.lot_rma_id
|
||||
cls.customer_location = cls.env.ref(
|
||||
'stock.stock_location_customers')
|
||||
cls.supplier_location = cls.env.ref(
|
||||
'stock.stock_location_suppliers')
|
||||
# Customer RMA:
|
||||
products2move = [(cls.product_1, 3)]
|
||||
cls.rma_customer_id = cls._create_rma_from_move(
|
||||
products2move, 'customer', cls.env.ref('base.res_partner_2'),
|
||||
dropship=False)
|
||||
cls.rma_customer_id.rma_line_ids._onchange_operation_id()
|
||||
seq = cls.env['ir.sequence'].search([], limit=1)
|
||||
cls.env['stock.picking.type'].create({
|
||||
"name": "TEST INTERNAL",
|
||||
"sequence_id": seq.id,
|
||||
"default_location_src_id": cls.stock_rma_location.id,
|
||||
"default_location_dest_id": cls.stock_rma_location.id,
|
||||
"warehouse_id": wh.id,
|
||||
"code": "internal"})
|
||||
|
||||
@classmethod
|
||||
def _create_picking(cls, partner):
|
||||
return cls.stockpicking.create({
|
||||
'partner_id': partner.id,
|
||||
'picking_type_id': cls.env.ref('stock.picking_type_in').id,
|
||||
'location_id': cls.stock_location.id,
|
||||
'location_dest_id': cls.supplier_location.id
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def _prepare_move(cls, product, qty, src, dest, picking_in):
|
||||
res = {
|
||||
'partner_id': cls.partner_id.id,
|
||||
'product_id': product.id,
|
||||
'name': product.partner_ref,
|
||||
'state': 'confirmed',
|
||||
'product_uom': cls.product_uom_id.id or product.uom_id.id,
|
||||
'product_uom_qty': qty,
|
||||
'origin': 'Test RMA',
|
||||
'location_id': src.id,
|
||||
'location_dest_id': dest.id,
|
||||
'picking_id': picking_in.id
|
||||
}
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def _create_rma_from_move(cls, products2move, type, partner, dropship,
|
||||
supplier_address_id=None):
|
||||
picking_in = cls._create_picking(partner)
|
||||
|
||||
moves = []
|
||||
for item in products2move:
|
||||
move_values = cls._prepare_move(
|
||||
item[0], item[1], cls.stock_location,
|
||||
cls.customer_location, picking_in)
|
||||
moves.append(cls.env['stock.move'].create(move_values))
|
||||
|
||||
# Create the RMA from the stock_move
|
||||
rma_id = cls.rma.create(
|
||||
{
|
||||
'reference': '0001',
|
||||
'type': type,
|
||||
'partner_id': partner.id,
|
||||
'company_id': cls.env.ref('base.main_company').id
|
||||
})
|
||||
for move in moves:
|
||||
if type == 'customer':
|
||||
wizard = cls.rma_add_stock_move.new(
|
||||
{'stock_move_id': move.id, 'customer': True,
|
||||
'active_ids': rma_id.id,
|
||||
'rma_id': rma_id.id,
|
||||
'partner_id': move.partner_id.id,
|
||||
'active_model': 'rma.order',
|
||||
}
|
||||
)
|
||||
wizard.with_context({
|
||||
'stock_move_id': move.id, 'customer': True,
|
||||
'active_ids': rma_id.id,
|
||||
'partner_id': move.partner_id.id,
|
||||
'active_model': 'rma.order',
|
||||
}).default_get([str(move.id),
|
||||
str(cls.partner_id.id)])
|
||||
data = wizard.with_context(customer=1).\
|
||||
_prepare_rma_line_from_stock_move(move)
|
||||
wizard.add_lines()
|
||||
|
||||
for operation in move.product_id.rma_customer_operation_id:
|
||||
operation.in_route_id = False
|
||||
move.product_id.categ_id.rma_customer_operation_id = False
|
||||
move.product_id.rma_customer_operation_id = False
|
||||
wizard._prepare_rma_line_from_stock_move(move)
|
||||
|
||||
cls.line = cls.rma_line.create(data)
|
||||
cls.line.action_rma_to_approve()
|
||||
cls.line.action_rma_approve()
|
||||
return rma_id
|
||||
|
||||
def test_0_internal_transfer(cls):
|
||||
wizard = cls.rma_make_picking.with_context({
|
||||
'active_ids': cls.rma_customer_id.rma_line_ids.ids,
|
||||
'active_model': 'rma.order.line',
|
||||
'picking_type': 'internal',
|
||||
'active_id': cls.rma_customer_id.id
|
||||
}).create({})
|
||||
|
||||
wizard.action_create_picking()
|
||||
res = cls.rma_customer_id.rma_line_ids.action_view_in_shipments()
|
||||
cls.assertTrue('res_id' in res,
|
||||
"Incorrect number of pickings created")
|
||||
4
rma_internal_transfer/wizards/__init__.py
Normal file
4
rma_internal_transfer/wizards/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# Copyright (C) 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
from . import rma_internal_transfer
|
||||
147
rma_internal_transfer/wizards/rma_internal_transfer.py
Normal file
147
rma_internal_transfer/wizards/rma_internal_transfer.py
Normal file
@@ -0,0 +1,147 @@
|
||||
# Copyright (C) 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
import odoo.addons.decimal_precision as dp
|
||||
|
||||
|
||||
class RmaMakePicking(models.TransientModel):
|
||||
_name = 'rma_internal_transfer.wizard'
|
||||
_description = 'Wizard to create pickings from rma lines'
|
||||
|
||||
@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,
|
||||
'wiz_id': self.env.context['active_id'],
|
||||
}
|
||||
return values
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
"""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(RmaMakePicking, self).default_get(fields)
|
||||
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)])
|
||||
|
||||
types = self.env['stock.picking.type'].search(
|
||||
[('code', '=', 'internal'),
|
||||
('warehouse_id.company_id', '=',
|
||||
line.in_warehouse_id.id)])[:1]
|
||||
if not types:
|
||||
raise ValidationError(_('Please define an internal picking type '
|
||||
'for this warehouse'))
|
||||
res['location_id'] = lines.mapped('location_id').ids[0]
|
||||
res['picking_type_id'] = types.id
|
||||
res['location_dest_id'] = lines.mapped('location_id').ids[0]
|
||||
res['item_ids'] = items
|
||||
context.update({'items_ids': items})
|
||||
return res
|
||||
|
||||
item_ids = fields.One2many(
|
||||
'rma_internal_transfer.wizard.item',
|
||||
'wiz_id', string='Items')
|
||||
picking_type_id = fields.Many2one('stock.picking.type', 'Operation Type',
|
||||
required=True)
|
||||
location_id = fields.Many2one('stock.location', 'Source Location',
|
||||
required=True)
|
||||
location_dest_id = fields.Many2one(
|
||||
'stock.location', 'Destination Location', required=True)
|
||||
|
||||
def find_procurement_group(self):
|
||||
if self.mapped('item_ids.line_id.rma_id'):
|
||||
return self.env['procurement.group'].search([
|
||||
('rma_id', 'in', self.mapped('item_ids.line_id.rma_id').ids)])
|
||||
else:
|
||||
return self.env['procurement.group'].search([
|
||||
('rma_line_id', 'in', self.mapped('item_ids.line_id').ids)])
|
||||
|
||||
@api.multi
|
||||
def action_create_picking(self):
|
||||
self.ensure_one()
|
||||
picking_obj = self.env['stock.picking']
|
||||
type_obj = self.env['stock.picking.type']
|
||||
user_obj = self.env['res.users']
|
||||
company_id = user_obj.browse(self._uid).company_id.id
|
||||
types = type_obj.search([('code', '=', 'internal'),
|
||||
('warehouse_id.company_id', '=', company_id)])
|
||||
if not types:
|
||||
raise UserError(_("Make sure you have at least an internal picking"
|
||||
"type defined for your company's warehouse."))
|
||||
picking_type = types[0]
|
||||
group_id = self.find_procurement_group().id
|
||||
picking = picking_obj.create({
|
||||
'picking_type_id': picking_type.id,
|
||||
'location_dest_id': self.location_dest_id.id,
|
||||
'location_id': self.location_id.id,
|
||||
'move_lines': [(0, 0, {
|
||||
'name': item.line_id.name,
|
||||
'product_id': item.product_id.id,
|
||||
'product_uom_qty': item.product_qty,
|
||||
'product_uom': item.uom_id.id,
|
||||
'state': 'draft',
|
||||
'group_id': group_id,
|
||||
'rma_line_id': item.line_id.id,
|
||||
}) for item in self.item_ids]
|
||||
})
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Stock Picking',
|
||||
'res_model': 'stock.picking',
|
||||
'res_id': picking.id,
|
||||
'view_mode': 'form',
|
||||
'view_type': 'form',
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def action_cancel(self):
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
|
||||
class RmaMakePickingItem(models.TransientModel):
|
||||
_name = "rma_internal_transfer.wizard.item"
|
||||
_description = "Items to receive"
|
||||
|
||||
wiz_id = fields.Many2one(
|
||||
'rma_internal_transfer.wizard',
|
||||
string='Wizard', required=True)
|
||||
line_id = fields.Many2one('rma.order.line',
|
||||
string='RMA order Line',
|
||||
readonly=True,
|
||||
ondelete='cascade')
|
||||
rma_id = fields.Many2one('rma.order',
|
||||
related='line_id.rma_id',
|
||||
string='RMA Group',
|
||||
readonly=True)
|
||||
product_id = fields.Many2one('product.product', string='Product',
|
||||
required=True)
|
||||
product_qty = fields.Float(
|
||||
related='line_id.product_qty',
|
||||
string='Quantity Ordered', copy=False,
|
||||
digits=dp.get_precision('Product Unit of Measure'))
|
||||
uom_id = fields.Many2one('uom.uom', string='Unit of Measure')
|
||||
78
rma_internal_transfer/wizards/rma_internal_transfer.xml
Executable file
78
rma_internal_transfer/wizards/rma_internal_transfer.xml
Executable file
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<odoo>
|
||||
|
||||
<record id="view_rma_picking_internal" model="ir.ui.view">
|
||||
<field name="name">Internal Transfer</field>
|
||||
<field name="model">rma_internal_transfer.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Select lines for transfer" name="lines">
|
||||
<separator string="Select lines for picking"/>
|
||||
<group name="main">
|
||||
<group name="locations">
|
||||
<field name="picking_type_id"/>
|
||||
<field name="location_id"/>
|
||||
<field name="location_dest_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<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="uom_id" groups="uom.group_uom"/>
|
||||
</tree>
|
||||
</field>
|
||||
<footer>
|
||||
<button
|
||||
string="Confirm"
|
||||
name="action_create_picking" 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_picking_internal" model="ir.actions.act_window">
|
||||
<field name="name">Create Internal Transfer</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">rma_internal_transfer.wizard</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="view_id" ref="view_rma_picking_internal"/>
|
||||
<field name="groups_id" eval="[(4, ref('stock.group_stock_user'))]"/>
|
||||
<field name="context">{'picking_type': 'internal'}</field>
|
||||
<field name="binding_model_id" ref="rma.model_rma_order_line"/>
|
||||
</record>
|
||||
|
||||
<record id="view_rma_line_button_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="%(rma.action_rma_picking_out)d" position="after">
|
||||
<button name="%(action_rma_picking_internal)d" states="approved"
|
||||
string="Internal Transfer" class="oe_highlight"
|
||||
type="action"/>
|
||||
</button>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_rma_line_supplier_button_form" model="ir.ui.view">
|
||||
<field name="name">rma.order.line.supplier.form</field>
|
||||
<field name="model">rma.order.line</field>
|
||||
<field name="inherit_id" ref="rma.view_rma_line_supplier_button_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<button name="%(rma.action_rma_picking_out)d" position="after">
|
||||
<button name="%(action_rma_picking_internal)d" states="approved"
|
||||
string="Internal Transfer" class="oe_highlight"
|
||||
type="action"/>
|
||||
</button>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user