diff --git a/addons/rma/__init__.py b/addons/rma/__init__.py
new file mode 100644
index 00000000..a31f3c40
--- /dev/null
+++ b/addons/rma/__init__.py
@@ -0,0 +1,4 @@
+# Part of Flectra See LICENSE file for full copyright and licensing details.
+
+from . import models
+from . import wizard
diff --git a/addons/rma/__manifest__.py b/addons/rma/__manifest__.py
new file mode 100644
index 00000000..32e9e729
--- /dev/null
+++ b/addons/rma/__manifest__.py
@@ -0,0 +1,39 @@
+# Part of Flectra See LICENSE file for full copyright and licensing details.
+
+{
+ 'name': 'Return Merchandise Authorizationing',
+ 'version': '1.0',
+ 'author': 'Flectra',
+ 'website': 'https://flectrahq.com',
+ 'description': """
+ Return Merchandise Authorizationing is part of the process of a
+ consumer returning a product to receive a refund, replacement or repair
+ during the product's warranty period.
+ """,
+ 'category': 'Sales',
+ 'depends': ['sale_management', 'stock', 'purchase'],
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'data/mail_message_subtype_data.xml',
+ 'data/mail_template_data.xml',
+ 'data/replacement_reason_data.xml',
+ 'sequences/data_rma_order_sequence.xml',
+ 'views/rma_request_view.xml',
+ 'views/replacement_reason_view.xml',
+ 'views/rma_line_view.xml',
+ 'views/stock_production_lot_view.xml',
+ 'views/stock_picking_view.xml',
+ 'views/warranty_expire_line_view.xml',
+ 'views/sales_team_view.xml',
+ 'views/menuitems_view.xml',
+
+ ],
+
+ 'demo': [
+ 'demo/product_demo.xml',
+ 'demo/stock_demo.xml',
+ 'demo/sale_order_demo.xml',
+ ],
+
+ 'application': True,
+}
diff --git a/addons/rma/data/mail_message_subtype_data.xml b/addons/rma/data/mail_message_subtype_data.xml
new file mode 100644
index 00000000..f93b5fcc
--- /dev/null
+++ b/addons/rma/data/mail_message_subtype_data.xml
@@ -0,0 +1,52 @@
+
+
+
+
+ Request Created
+
+ rma.request
+
+ Replacement Request is Created
+
+
+ Request Created
+ 10
+ rma.request
+
+
+ team_id
+
+
+
+ Request Confirmed
+
+ rma.request
+
+ Replacement Request has been
+ Confirmed
+
+
+ Request Confirmed
+ 11
+ rma.request
+
+
+ team_id
+
+
+
+ Replacement created
+
+ rma.request
+
+ Products have been replaced
+
+
+ Replacement created
+ 12
+ rma.request
+
+
+ team_id
+
+
\ No newline at end of file
diff --git a/addons/rma/data/mail_template_data.xml b/addons/rma/data/mail_template_data.xml
new file mode 100644
index 00000000..27f8bd20
--- /dev/null
+++ b/addons/rma/data/mail_template_data.xml
@@ -0,0 +1,63 @@
+
+
+
+
+ Warranty: Product Warranty email
+
+ ${(object.user_id.email and '%s <%s>' % (object.user_id.name, object.user_id.email) or '')|safe}
+
+ ${object.name or 'n/a'}
+ ${object.partner_id.id}
+
+
+ ${object.partner_id.lang}
+
+ Dear ${object.partner_id.name}
+ We are sorry to hear about the problem you have had
+ with your product.
+
+ We would like to be able to make the necessary
+ adjustment at no charge to you,
+ but unfortunately the warranty is expired of
+ following products:
+
+
+
+
+
+
+ Product
+ Lot
+ Quantity
+ Warranty Date
+
+
+
+ % for line in object.warranty_expire_line:
+
+ ${line.product_id.name}
+ ${line.lot_id.name}
+ ${line.qty_expired}
+ ${line.warranty_date}
+
+ % endfor
+
+
+ % else:
+
+
Dear ${object.partner_id.name}
+
We are sorry to hear about the problem you have had
+ with your product.
+
We would like to be able to make the necessary
+ adjustment at no charge to you, We are going to
+ replace your product. We will deliver it to
+ you as possible.
+
+
+ % endif
+]]>
+
+
+
diff --git a/addons/rma/data/replacement_reason_data.xml b/addons/rma/data/replacement_reason_data.xml
new file mode 100644
index 00000000..c1493eb0
--- /dev/null
+++ b/addons/rma/data/replacement_reason_data.xml
@@ -0,0 +1,50 @@
+
+
+
+ Better price available
+
+
+
+ Incompatible or not useful
+
+
+
+ Product damaged, but shipping box OK
+
+
+
+ Item arrived too late
+
+
+
+ Missing parts or accessories
+
+
+
+ Both product and shipping box damaged
+
+
+
+ Wrong item was sent
+
+
+
+ No longer needed
+
+
+
+ Item defective or doesn't work
+
+
+
+ Didn't approve purchase
+
+
+
+ Performace or quality not adequate
+
+
+
+ Inaccurate website description
+
+
diff --git a/addons/rma/demo/product_demo.xml b/addons/rma/demo/product_demo.xml
new file mode 100644
index 00000000..396eb64b
--- /dev/null
+++ b/addons/rma/demo/product_demo.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ Demo Product1
+
+ 100.0
+ 200.0
+ product
+
+
+ Demo product1
+
+ DEMO-PROD1
+ lot
+
+
+ Demo Product2
+
+ 200.0
+ 250.0
+ product
+
+
+ Demo product2
+
+ DEMO-PROD2
+ lot
+
+
diff --git a/addons/rma/demo/sale_order_demo.xml b/addons/rma/demo/sale_order_demo.xml
new file mode 100644
index 00000000..70a72a7d
--- /dev/null
+++ b/addons/rma/demo/sale_order_demo.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Demo Product1
+
+ 5
+
+ 150.00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Demo Product1
+
+ 5
+
+ 150.00
+
+
+
+
+ Demo Product2
+
+ 10
+
+ 200.00
+
+
diff --git a/addons/rma/demo/stock_demo.xml b/addons/rma/demo/stock_demo.xml
new file mode 100644
index 00000000..ae3c1e8e
--- /dev/null
+++ b/addons/rma/demo/stock_demo.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+ 20.0
+
+
+
+
+
+
+
+ 30.0
+
+
+
diff --git a/addons/rma/i18n/rma.pot b/addons/rma/i18n/rma.pot
new file mode 100644
index 00000000..abc0550c
--- /dev/null
+++ b/addons/rma/i18n/rma.pot
@@ -0,0 +1,538 @@
+# Translation of Flectra Server.
+# This file contains the translation of the following modules:
+# * rma
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Flectra Server 1.0alpha\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-12-25 11:22+0000\n"
+"PO-Revision-Date: 2017-12-25 11:22+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: rma
+#: model:mail.template,body_html:rma.email_template_notify_warranty_new
+msgid "\n"
+" % if object.warranty_expire_line:\n"
+"
\n"
+"
Dear ${object.partner_id.name}
\n"
+"
We are sorry to hear about the problem you have had\n"
+" with your product.\n"
+" We would like to be able to make the necessary\n"
+" adjustment at no charge to you,\n"
+" but unfortunately the warranty is expired of\n"
+" following products:\n"
+"
\n"
+"
\n"
+"\n"
+" \n"
+" \n"
+" \n"
+" Product \n"
+" Lot \n"
+" Quantity \n"
+" Warranty Date \n"
+" \n"
+" \n"
+" \n"
+" % for line in object.warranty_expire_line:\n"
+" \n"
+" ${line.product_id.name} \n"
+" ${line.lot_id.name} \n"
+" ${line.qty_expired} \n"
+" ${line.warranty_date} \n"
+" \n"
+" % endfor\n"
+" \n"
+"
\n"
+" % else:\n"
+" \n"
+"
Dear ${object.partner_id.name}
\n"
+"
We are sorry to hear about the problem you have had\n"
+" with your product.\n"
+" We would like to be able to make the necessary\n"
+" adjustment at no charge to you, We are going to\n"
+" replace your product. We will deliver it to\n"
+" you as possible.\n"
+"
\n"
+"
\n"
+" % endif\n"
+"\n"
+" "
+msgstr ""
+
+#. module: rma
+#: model:mail.template,subject:rma.email_template_notify_warranty_new
+msgid "${object.name or 'n/a'}"
+msgstr ""
+
+#. module: rma
+#: code:addons/rma/models/rma_request.py:147
+#, python-format
+msgid "Compose Email"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.view,arch_db:rma.view_rma_request_form
+msgid "Confirm"
+msgstr ""
+
+#. module: rma
+#: selection:rma.request,state:0
+msgid "Confirmed"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.view,arch_db:rma.view_rma_request_form
+msgid "Create Replacement"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_replacement_reason_create_uid
+#: model:ir.model.fields,field_description:rma.field_rma_line_create_uid
+#: model:ir.model.fields,field_description:rma.field_rma_request_create_uid
+#: model:ir.model.fields,field_description:rma.field_warranty_expire_line_create_uid
+msgid "Created by"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_replacement_reason_create_date
+#: model:ir.model.fields,field_description:rma.field_rma_line_create_date
+#: model:ir.model.fields,field_description:rma.field_rma_request_create_date
+#: model:ir.model.fields,field_description:rma.field_warranty_expire_line_create_date
+msgid "Created on"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_request_partner_id
+msgid "Customer"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_line_qty_delivered
+msgid "Delivered Quantity"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_request_picking_count
+#: model:ir.model.fields,field_description:rma.field_rma_request_picking_ids
+#: model:ir.ui.view,arch_db:rma.view_rma_request_form
+msgid "Delivery"
+msgstr ""
+
+#. module: rma
+#: model:product.product,name:rma.demo_product_1
+#: model:product.template,name:rma.demo_product_1_product_template
+#: model:stock.inventory.line,product_name:rma.demo_stock_inventory_line_1
+msgid "Demo Product1"
+msgstr ""
+
+#. module: rma
+#: model:product.product,name:rma.demo_product_2
+#: model:product.template,name:rma.demo_product_2_product_template
+#: model:stock.inventory.line,product_name:rma.demo_stock_inventory_line_2
+msgid "Demo Product2"
+msgstr ""
+
+#. module: rma
+#: model:product.product,description:rma.demo_product_1
+#: model:product.template,description:rma.demo_product_1_product_template
+msgid "Demo product1\n"
+" "
+msgstr ""
+
+#. module: rma
+#: model:product.product,description:rma.demo_product_2
+#: model:product.template,description:rma.demo_product_2_product_template
+msgid "Demo product2\n"
+" "
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_replacement_reason_display_name
+#: model:ir.model.fields,field_description:rma.field_rma_line_display_name
+#: model:ir.model.fields,field_description:rma.field_rma_request_display_name
+#: model:ir.model.fields,field_description:rma.field_warranty_expire_line_display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: rma
+#: selection:rma.request,state:0
+msgid "Draft"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_warranty_expire_line_qty_expired
+msgid "Expired Quantity"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.view,arch_db:rma.view_rma_request_filter
+msgid "Group By"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_replacement_reason_id
+#: model:ir.model.fields,field_description:rma.field_rma_line_id
+#: model:ir.model.fields,field_description:rma.field_rma_request_id
+#: model:ir.model.fields,field_description:rma.field_warranty_expire_line_id
+msgid "ID"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_replacement_reason___last_update
+#: model:ir.model.fields,field_description:rma.field_rma_line___last_update
+#: model:ir.model.fields,field_description:rma.field_rma_request___last_update
+#: model:ir.model.fields,field_description:rma.field_warranty_expire_line___last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_replacement_reason_write_uid
+#: model:ir.model.fields,field_description:rma.field_rma_line_write_uid
+#: model:ir.model.fields,field_description:rma.field_rma_request_write_uid
+#: model:ir.model.fields,field_description:rma.field_warranty_expire_line_write_uid
+msgid "Last Updated by"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_replacement_reason_write_date
+#: model:ir.model.fields,field_description:rma.field_rma_line_write_date
+#: model:ir.model.fields,field_description:rma.field_rma_request_write_date
+#: model:ir.model.fields,field_description:rma.field_warranty_expire_line_write_date
+msgid "Last Updated on"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_warranty_expire_line_warranty_date
+msgid "Lot Warranty Date"
+msgstr ""
+
+#. module: rma
+#: model:ir.model,name:rma.model_stock_production_lot
+msgid "Lot/Serial"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.view,arch_db:rma.view_rma_request_filter
+msgid "My Requests"
+msgstr ""
+
+#. module: rma
+#: code:addons/rma/wizard/stock_return_picking.py:62
+#, python-format
+msgid "No products to replace (only lines in Done state andnot fully replaced yet can be replaced)!"
+msgstr ""
+
+#. module: rma
+#: model:ir.actions.act_window,help:rma.replacement_request_action_reports
+msgid "No replacement request."
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_crm_team_replacements_count
+msgid "Number of replacements"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.view,arch_db:rma.view_rma_request_filter
+msgid "Partner"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_request_picking_id
+msgid "Picking Number"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_line_product_id
+#: model:ir.model.fields,field_description:rma.field_warranty_expire_line_product_id
+msgid "Product"
+msgstr ""
+
+#. module: rma
+#: model:mail.message.subtype,description:rma.mt_request_replaced
+msgid "Products have been replaced"
+msgstr ""
+
+#. module: rma
+#: model:ir.model,name:rma.model_rma_line
+msgid "RMA Line"
+msgstr ""
+
+#. module: rma
+#: model:ir.actions.act_window,name:rma.act_open_rma_line_view
+#: model:ir.model.fields,field_description:rma.field_rma_request_rma_line
+#: model:ir.ui.view,arch_db:rma.view_rma_line_form
+#: model:ir.ui.view,arch_db:rma.view_rma_request_form
+msgid "RMA Lines"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_request_name
+#: model:ir.model.fields,field_description:rma.field_stock_picking_rma_id
+msgid "RMA Order Number"
+msgstr ""
+
+#. module: rma
+#: model:ir.actions.act_window,name:rma.act_open_rma_request_view
+#: model:ir.actions.act_window,name:rma.action_open_rma_request_replacementteam
+#: model:ir.ui.view,arch_db:rma.view_rma_request_filter
+msgid "RMA Request"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_line_rma_id
+msgid "RMA Request Number"
+msgstr ""
+
+#. module: rma
+#: model:ir.model,name:rma.model_rma_request
+msgid "RMA Reuqest"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_warranty_expire_line_rma_id
+msgid "RMA Reuqest Number"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_replacement_reason_name
+#: model:ir.model.fields,field_description:rma.field_rma_line_reason_id
+#: model:ir.ui.view,arch_db:rma.view_replacement_reason_form
+msgid "Reason"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_line_remark
+msgid "Remark"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.menu,name:rma.menu_request_rma
+msgid "Replace Request"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_crm_team_use_replacement
+#: model:ir.ui.menu,name:rma.rma_request_menu
+#: model:ir.ui.view,arch_db:rma.crm_team_replacement_teams_view_kanban
+#: model:ir.ui.view,arch_db:rma.replacement_view_kanban
+#: selection:rma.request,type:0
+msgid "Replacement"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.view,arch_db:rma.view_rma_request_filter
+#: selection:rma.request,state:0
+msgid "Replacement Created"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_line_qty_replaced
+msgid "Replacement Quantity"
+msgstr ""
+
+#. module: rma
+#: model:ir.actions.act_window,name:rma.act_open_replacement_reason_view
+#: model:ir.model,name:rma.model_replacement_reason
+#: model:ir.ui.menu,name:rma.menu_replacement_reason
+msgid "Replacement Reason"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.view,arch_db:rma.view_rma_line_graph
+msgid "Replacement Request"
+msgstr ""
+
+#. module: rma
+#: model:mail.message.subtype,description:rma.mt_request_confirm
+msgid "Replacement Request has been\n"
+" Confirmed"
+msgstr ""
+
+#. module: rma
+#: model:mail.message.subtype,description:rma.mt_request_create
+msgid "Replacement Request is Created"
+msgstr ""
+
+#. module: rma
+#: model:ir.actions.act_window,name:rma.replacement_request_action_reports
+msgid "Replacement Requests"
+msgstr ""
+
+#. module: rma
+#: model:mail.message.subtype,name:rma.mt_request_replaced
+#: model:mail.message.subtype,name:rma.mt_salesteam_request_replaced
+msgid "Replacement created"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.view,arch_db:rma.crm_team_replacement_teams_view_kanban
+msgid "Replacements"
+msgstr ""
+
+#. module: rma
+#: code:addons/rma/models/rma_line.py:35
+#, python-format
+msgid "Replacemet quantity of %s should not be greater than ordered quantity."
+msgstr ""
+
+#. module: rma
+#: model:mail.message.subtype,name:rma.mt_request_confirm
+#: model:mail.message.subtype,name:rma.mt_salesteam_request_confirm
+msgid "Request Confirmed"
+msgstr ""
+
+#. module: rma
+#: model:mail.message.subtype,name:rma.mt_request_create
+#: model:mail.message.subtype,name:rma.mt_salesteam_request
+msgid "Request Created"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_request_date
+msgid "Request Date"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.view,arch_db:rma.view_rma_request_filter
+msgid "Request Month"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_request_state
+msgid "Request Status"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_request_type
+msgid "Request Type"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.view,arch_db:rma.view_rma_request_form
+msgid "Requests for RMA"
+msgstr ""
+
+#. module: rma
+#: model:ir.model,name:rma.model_stock_return_picking
+msgid "Return Picking"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_request_sale_order_id
+msgid "SO Number"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.view,arch_db:rma.view_rma_request_filter
+msgid "Sale Order"
+msgstr ""
+
+#. module: rma
+#: model:ir.model,name:rma.model_crm_team
+#: model:ir.ui.view,arch_db:rma.view_rma_request_filter
+msgid "Sales Channel"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.view,arch_db:rma.view_rma_line_filter
+msgid "Search RMA Line"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.view,arch_db:rma.view_rma_request_filter
+msgid "Search RMA Request"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.view,arch_db:rma.view_rma_request_form
+msgid "Send By Email"
+msgstr ""
+
+#. module: rma
+#: model:ir.ui.view,arch_db:rma.view_rma_request_filter
+msgid "State"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_line_move_line_id
+msgid "Stock Move"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_warranty_expire_line_lot_id
+msgid "Stock production lot"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_line_team_id
+#: model:ir.model.fields,field_description:rma.field_rma_request_team_id
+msgid "Team"
+msgstr ""
+
+#. module: rma
+#: model:ir.model,name:rma.model_stock_picking
+msgid "Transfer"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_line_uom_id
+msgid "UOM"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_rma_request_user_id
+msgid "User"
+msgstr ""
+
+#. module: rma
+#: model:ir.actions.act_window,name:rma.act_open_warranty_expire_line_view
+#: model:ir.model,name:rma.model_warranty_expire_line
+#: model:ir.model.fields,field_description:rma.field_rma_request_warranty_expire_line
+#: model:ir.ui.view,arch_db:rma.view_rma_request_form
+#: model:ir.ui.view,arch_db:rma.view_warranty_expire_line_form
+msgid "Warranty Expire Lines"
+msgstr ""
+
+#. module: rma
+#: model:ir.model.fields,field_description:rma.field_stock_production_lot_warranty_date
+msgid "Warranty Expiry Date"
+msgstr ""
+
+#. module: rma
+#: code:addons/rma/models/rma_line.py:30
+#, python-format
+msgid "You can only replace %s quantity for product %s as its warranty has been expired."
+msgstr ""
+
+#. module: rma
+#: code:addons/rma/models/rma_request.py:183
+#, python-format
+msgid "You cannot delete a request which is not in draft state."
+msgstr ""
+
+#. module: rma
+#: code:addons/rma/wizard/stock_return_picking.py:16
+#, python-format
+msgid "You may only replace single picking at a time!"
+msgstr ""
+
+#. module: rma
+#: code:addons/rma/wizard/stock_return_picking.py:26
+#, python-format
+msgid "You may only return Done pickings"
+msgstr ""
+
+#. module: rma
+#: code:addons/rma/models/rma_request.py:174
+#, python-format
+msgid "You must select rma lines!"
+msgstr ""
+
diff --git a/addons/rma/models/__init__.py b/addons/rma/models/__init__.py
new file mode 100644
index 00000000..b65c16e5
--- /dev/null
+++ b/addons/rma/models/__init__.py
@@ -0,0 +1,9 @@
+# Part of Flectra See LICENSE file for full copyright and licensing details.
+
+from . import rma_line
+from . import warranty_expire_line
+from . import rma_request
+from . import stock_production_lot
+from . import stock_picking
+from . import sales_team
+from . import replacement_reason
diff --git a/addons/rma/models/replacement_reason.py b/addons/rma/models/replacement_reason.py
new file mode 100644
index 00000000..5790576b
--- /dev/null
+++ b/addons/rma/models/replacement_reason.py
@@ -0,0 +1,10 @@
+# Part of Flectra See LICENSE file for full copyright and licensing details.
+
+from flectra import fields, models
+
+
+class ReplacementReason(models.Model):
+ _name = "replacement.reason"
+ _description = "Replacement Reason"
+
+ name = fields.Char(string='Reason')
diff --git a/addons/rma/models/rma_line.py b/addons/rma/models/rma_line.py
new file mode 100644
index 00000000..3f775ca4
--- /dev/null
+++ b/addons/rma/models/rma_line.py
@@ -0,0 +1,40 @@
+# Part of Flectra See LICENSE file for full copyright and licensing details.
+
+from flectra import api, fields, models, _
+from flectra.exceptions import UserError
+
+
+class RMALine(models.Model):
+ _name = "rma.line"
+ _description = "RMA Line"
+
+ product_id = fields.Many2one('product.product', string='Product')
+ uom_id = fields.Many2one('product.uom', string='UOM')
+ qty_delivered = fields.Float(string='Delivered Quantity')
+ qty_replaced = fields.Float(string='Replacement Quantity')
+ rma_id = fields.Many2one('rma.request', string='RMA Request Number')
+ move_line_id = fields.Many2one('stock.move', string='Stock Move')
+ reason_id = fields.Many2one("replacement.reason", sting='Reason for RMA')
+ remark = fields.Text(sting='Remark')
+ team_id = fields.Many2one('crm.team', 'Team', related='rma_id.team_id')
+
+ @api.onchange('qty_replaced')
+ def _onchange_qty_replaced(self):
+ if self.product_id.tracking != 'none':
+ replaceable_qty = sum(line.qty_done for line in
+ self.move_line_id.move_line_ids if
+ line.lot_id.warranty_date and
+ line.lot_id.warranty_date >=
+ self.rma_id.date)
+ if self.qty_replaced > replaceable_qty:
+ raise UserError(_('You can only replace %s quantity for '
+ 'product %s as its warranty has been '
+ 'expired.') % (replaceable_qty,
+ self.product_id.name))
+ elif self.qty_replaced > self.qty_delivered:
+ raise UserError(_('Replacemet quantity of %s should not be '
+ 'greater than ordered quantity.') %
+ (self.product_id.name))
+
+ if self.qty_replaced == 0:
+ raise UserError(_('Replacement quantity should not be 0.'))
diff --git a/addons/rma/models/rma_request.py b/addons/rma/models/rma_request.py
new file mode 100644
index 00000000..5a855172
--- /dev/null
+++ b/addons/rma/models/rma_request.py
@@ -0,0 +1,186 @@
+# Part of Flectra See LICENSE file for full copyright and licensing details.
+
+from flectra import api, fields, models, _
+from flectra.exceptions import UserError
+
+
+class RmaRequest(models.Model):
+ _name = "rma.request"
+ _inherit = ['mail.thread', 'mail.activity.mixin']
+ _description = "RMA Request"
+
+ name = fields.Char(string='RMA Order Number')
+ sale_order_id = fields.Many2one('sale.order', string='SO Number')
+ picking_id = fields.Many2one('stock.picking', string='Picking Number')
+ date = fields.Date(string='Request Date',
+ default=fields.Date.context_today)
+ partner_id = fields.Many2one('res.partner', string='Customer')
+ type = fields.Selection([
+ ('replacement', 'Replacement')
+ ], string='Request Type')
+ rma_line = fields.One2many('rma.line', 'rma_id', string='RMA Lines')
+ warranty_expire_line = fields.One2many('warranty.expire.line', 'rma_id',
+ string='Warranty Expire Lines')
+ state = fields.Selection([
+ ('draft', 'Draft'),
+ ('confirmed', 'Confirmed'),
+ ('replacement_created', 'Replacement Created'),
+ ], string='Request Status', track_visibility='onchange', readonly=True,
+ copy=False, default='draft')
+ picking_count = fields.Integer(string='Delivery',
+ compute="_compute_picking")
+ picking_ids = fields.Many2many('stock.picking',
+ string='Delivery',
+ compute="_compute_picking")
+ user_id = fields.Many2one('res.users', string='User',
+ default=lambda self: self.env.user)
+ team_id = fields.Many2one('crm.team', string='Team')
+
+ @api.multi
+ def _compute_picking(self):
+ for request in self:
+ picking_ids = self.env['stock.picking'].search([(
+ 'rma_id', '=', request.id)])
+ request.picking_ids = picking_ids and picking_ids.ids or False
+ request.picking_count = len(picking_ids)
+
+ @api.onchange('sale_order_id')
+ def _get_partner(self):
+ if self.sale_order_id:
+ self.partner_id = self.sale_order_id.partner_id and \
+ self.sale_order_id.partner_id.id or False
+ self.team_id = self.sale_order_id.team_id and \
+ self.sale_order_id.team_id.id or False
+
+ @api.onchange('picking_id')
+ def _get_rma_lines(self):
+ if self.picking_id:
+ move_line_ids = self.env['stock.move'].search([(
+ 'picking_id', '=', self.picking_id.id)])
+ move_lines = [(5, 0, 0)]
+ for line in move_line_ids:
+ move_lines.append((0, 0, {
+ 'product_id': line.product_id.id,
+ 'uom_id': line.product_uom.id,
+ 'qty_delivered': line.quantity_done,
+ 'qty_replaced': sum(line.qty_done for line in
+ line.move_line_ids if
+ line.lot_id.warranty_date and
+ line.lot_id.warranty_date >= self.date
+ ),
+ 'rma_id': self.id,
+ 'move_line_id': line.id
+ }))
+ self.rma_line = move_lines
+
+ @api.onchange('rma_line', 'date')
+ def _get_warranty_lines(self):
+ warranty_lines = [(5, 0, 0)]
+ for line in self.rma_line:
+ if line.move_line_id and \
+ line.move_line_id.product_id.tracking != 'none':
+ for move_line in line.move_line_id.move_line_ids:
+ if move_line.lot_id.warranty_date and \
+ move_line.lot_id.warranty_date < self.date:
+ warranty_lines.append((0, 0, {
+ 'product_id': move_line.product_id.id,
+ 'lot_id': move_line.lot_id.id,
+ 'warranty_date':
+ move_line.lot_id.warranty_date,
+ 'qty_expired': sum(line.qty_done for line in
+ line.move_line_id.move_line_ids
+ if line.lot_id.warranty_date and
+ line.lot_id.
+ warranty_date < self.date),
+ 'rma_id': self.id,
+ }))
+ self.warranty_expire_line = warranty_lines
+
+ @api.model
+ def create(self, vals):
+ vals.update({
+ 'name': self.env['ir.sequence'].next_by_code(
+ 'rma_order')
+ })
+ return super(RmaRequest, self).create(vals)
+
+ @api.multi
+ def action_create_delivery(self):
+ self.ensure_one()
+ action = self.env.ref('stock.action_picking_tree_all')
+ result = action.read()[0]
+ if len(self.picking_ids) != 1:
+ result.update({
+ 'domain': "[('id', 'in', " + str(self.picking_ids.ids) + ")]"
+ })
+ elif len(self.picking_ids) == 1:
+ res = self.env.ref('rma.view_picking_form', False)
+ result.update({
+ 'views': [(res and res.id or False, 'form')],
+ 'res_id': self.picking_ids.id
+ })
+ return result
+
+ @api.multi
+ def action_notify_warranty(self):
+ self.ensure_one()
+ ir_model_data = self.env['ir.model.data']
+ try:
+ template_id = ir_model_data.get_object_reference(
+ 'rma', 'email_template_notify_warranty_new')[1]
+ except ValueError:
+ template_id = False
+ try:
+ compose_form_id = ir_model_data.get_object_reference(
+ 'mail', 'email_compose_message_wizard_form')[1]
+ except ValueError:
+ compose_form_id = False
+ ctx = dict(
+ default_model='rma.request',
+ default_res_id=self.id,
+ default_use_template=bool(template_id),
+ default_template_id=template_id,
+ default_composition_mode='comment',
+ force_email=True
+ )
+ return {
+ 'name': _('Compose Email'),
+ 'type': 'ir.actions.act_window',
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': 'mail.compose.message',
+ 'views': [(compose_form_id, 'form')],
+ 'view_id': compose_form_id,
+ 'target': 'new',
+ 'context': ctx,
+ }
+
+ @api.multi
+ def _track_subtype(self, init_values):
+ self.ensure_one()
+ if 'state' in init_values and self.state == 'draft' and self.rma_line:
+ return 'rma.mt_request_create'
+ elif 'state' in init_values and self.state == 'confirmed' and \
+ self.rma_line:
+ return 'rma.mt_request_confirm'
+ elif 'state' in init_values and self.state == 'replacement_created':
+ return 'rma.mt_request_replaced'
+ return super(RmaRequest, self)._track_subtype(init_values)
+
+ @api.multi
+ def action_confirm_request(self):
+ self.ensure_one()
+ if not self.rma_line:
+ raise UserError(_('You must select rma lines!'))
+ for line in self.rma_line:
+ line._onchange_qty_replaced()
+ self.state = 'confirmed'
+
+ @api.multi
+ def unlink(self):
+ for request in self:
+ if request.state != 'draft':
+ raise UserError(_(
+ 'You cannot delete a request which is not in draft '
+ 'state.'))
+ return super(RmaRequest, self).unlink()
diff --git a/addons/rma/models/sales_team.py b/addons/rma/models/sales_team.py
new file mode 100644
index 00000000..42b30d23
--- /dev/null
+++ b/addons/rma/models/sales_team.py
@@ -0,0 +1,19 @@
+# Part of Flectra See LICENSE file for full copyright and licensing details.
+
+from flectra import fields, models
+
+
+class CrmTeam(models.Model):
+ _inherit = 'crm.team'
+
+ use_replacement = fields.Boolean(string='Replacement')
+ replacements_count = fields.Integer(
+ compute='_compute_replacements',
+ string='Number of replacements')
+
+ def _compute_replacements(self):
+ for replace in self:
+ if replace.use_replacement:
+ rma_ids = self.env['rma.request'].search([
+ ('team_id.id', '=', replace.id)])
+ replace.replacements_count = len(rma_ids)
diff --git a/addons/rma/models/stock_picking.py b/addons/rma/models/stock_picking.py
new file mode 100644
index 00000000..4d672511
--- /dev/null
+++ b/addons/rma/models/stock_picking.py
@@ -0,0 +1,20 @@
+# Part of Flectra See LICENSE file for full copyright and licensing details.
+
+from flectra import api, fields, models
+
+
+class StockPicking(models.Model):
+ _inherit = 'stock.picking'
+
+ rma_id = fields.Many2one('rma.request', string='RMA Order Number')
+
+ @api.model
+ def create(self, vals):
+ result = super(StockPicking, self).create(vals)
+ rma_id = False
+ if self._context.get('active_model') == 'stock.picking':
+ rma_id = result.rma_id and result.rma_id.id or False
+ elif self._context.get('active_model') == 'rma.request':
+ rma_id = self._context.get('active_id')
+ result.update({'rma_id': rma_id})
+ return result
diff --git a/addons/rma/models/stock_production_lot.py b/addons/rma/models/stock_production_lot.py
new file mode 100644
index 00000000..a769892e
--- /dev/null
+++ b/addons/rma/models/stock_production_lot.py
@@ -0,0 +1,8 @@
+# Part of Flectra See LICENSE file for full copyright and licensing details.
+
+from flectra import fields, models
+
+
+class StockProductionLot(models.Model):
+ _inherit = 'stock.production.lot'
+ warranty_date = fields.Date(string='Warranty Expiry Date')
diff --git a/addons/rma/models/warranty_expire_line.py b/addons/rma/models/warranty_expire_line.py
new file mode 100644
index 00000000..751ce681
--- /dev/null
+++ b/addons/rma/models/warranty_expire_line.py
@@ -0,0 +1,15 @@
+# Part of Flectra See LICENSE file for full copyright and licensing details.
+
+from flectra import fields, models
+
+
+class WarrantyExpireLine(models.Model):
+ _name = "warranty.expire.line"
+ _description = "Warranty Expire Lines"
+
+ rma_id = fields.Many2one('rma.request', string='RMA Request Number')
+ product_id = fields.Many2one('product.product', string='Product')
+ lot_id = fields.Many2one('stock.production.lot',
+ string='Stock production lot')
+ qty_expired = fields.Float(string="Expired Quantity")
+ warranty_date = fields.Date('Lot Warranty Date')
diff --git a/addons/rma/security/ir.model.access.csv b/addons/rma/security/ir.model.access.csv
new file mode 100644
index 00000000..3df44237
--- /dev/null
+++ b/addons/rma/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_request,access_rma_request,model_rma_request,,1,1,1,0
+access_rma_line,access_rma_line,model_rma_line,,1,1,1,0
+access_warranty_expire_line,access_warranty_expire_line,model_warranty_expire_line,,1,1,1,0
+access_replacement_reason,access_replacement_reason,model_replacement_reason,,1,1,1,0
diff --git a/addons/rma/sequences/data_rma_order_sequence.xml b/addons/rma/sequences/data_rma_order_sequence.xml
new file mode 100644
index 00000000..377b7fdb
--- /dev/null
+++ b/addons/rma/sequences/data_rma_order_sequence.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ RMA Order Sequence
+ rma_order
+ RMA
+ 4
+
+
diff --git a/addons/rma/static/description/icon.png b/addons/rma/static/description/icon.png
new file mode 100644
index 00000000..e4097b19
Binary files /dev/null and b/addons/rma/static/description/icon.png differ
diff --git a/addons/rma/tests/__init__.py b/addons/rma/tests/__init__.py
new file mode 100644
index 00000000..d353a324
--- /dev/null
+++ b/addons/rma/tests/__init__.py
@@ -0,0 +1,3 @@
+# Part of Flectra See LICENSE file for full copyright and licensing details.
+
+from . import test_rma_request
diff --git a/addons/rma/tests/test_rma_request.py b/addons/rma/tests/test_rma_request.py
new file mode 100644
index 00000000..037c0ee9
--- /dev/null
+++ b/addons/rma/tests/test_rma_request.py
@@ -0,0 +1,172 @@
+# Part of Flectra See LICENSE file for full copyright and licensing details.
+
+from flectra.tests.common import TransactionCase
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+
+
+class TestSaleOrder(TransactionCase):
+ def setUp(self):
+ super(TestSaleOrder, self).setUp()
+
+ self.demo_product1_id = self.env.ref('rma.demo_product_1')
+ self.demo_product2_id = self.env.ref('rma.demo_product_2')
+
+ self.product1_lot_id = self.env['stock.production.lot'].create({
+ 'name': 'L001',
+ 'product_id': self.demo_product1_id.id,
+ 'warranty_date': datetime.now() + relativedelta(years=1)
+ })
+ self.change_prod1_qty_id = self.env[
+ 'stock.change.product.qty'].create({
+ 'product_id': self.demo_product1_id.id,
+ 'new_quantity': 10,
+ 'lot_id': self.product1_lot_id.id
+ })
+ self.change_prod1_qty_id.change_product_qty()
+ self.sale_order_id_1 = self.env.ref(
+ 'rma.demo_sale_order_rma_request1')
+
+ self.product2_lot_id = self.env['stock.production.lot'].create({
+ 'name': 'L002',
+ 'product_id': self.demo_product2_id.id,
+ 'warranty_date': datetime.now() - relativedelta(years=1)
+ })
+ self.change_prod2_qty_id = self.env[
+ 'stock.change.product.qty'].create({
+ 'product_id': self.demo_product2_id.id,
+ 'new_quantity': 10,
+ 'lot_id': self.product2_lot_id.id
+ })
+ self.change_prod2_qty_id.change_product_qty()
+ self.sale_order_id_2 = self.env.ref(
+ 'rma.demo_sale_order_rma_request2')
+
+ def test_00_rma_request(self):
+ self.sale_order_id_1.action_confirm()
+ picking_ids = self.sale_order_id_1.picking_ids[0]
+ for move_line in picking_ids.move_lines[0].move_line_ids:
+ move_line.lot_id = self.product1_lot_id.id
+ move_line.qty_done = 5
+ picking_ids.button_validate()
+ picking_ids.action_done()
+
+ self.rma_id_1 = self.env['rma.request'].create({
+ 'sale_order_id': self.sale_order_id_1.id,
+ 'picking_id': picking_ids[0].id,
+ 'date': datetime.now() - relativedelta(days=15),
+ 'partner_id': self.sale_order_id_1.partner_id.id,
+ 'type': 'replacement'
+ })
+ self.assertEquals(self.rma_id_1.state, 'draft')
+
+ self.rma_id_1._get_rma_lines()
+ self.assertTrue((len(self.rma_id_1.rma_line.ids)) != 0,
+ 'You can not create replacement request!')
+
+ self.rma_id_1._get_warranty_lines()
+ self.assertEquals((len(self.rma_id_1.warranty_expire_line.ids)), 0,
+ 'This Replacement request should not have expiry '
+ 'product!')
+
+ for rma_line in self.rma_id_1.rma_line:
+ replaceable_qty = sum(line.qty_done for line in
+ rma_line.move_line_id.move_line_ids if
+ line.lot_id.warranty_date and
+ line.lot_id.warranty_date >=
+ self.rma_id_1.date)
+ self.assertTrue(rma_line.qty_replaced <= replaceable_qty,
+ "You can only replace %d quantity for %s" %
+ (replaceable_qty, rma_line.product_id.name))
+
+ self.rma_id_1.action_confirm_request()
+ self.assertEquals(self.rma_id_1.state, 'confirmed')
+
+ context = {"active_model": 'rma.request',
+ "active_ids": [self.rma_id_1.id], "active_id":
+ self.rma_id_1.id, "rma": True}
+ self.return_picking_id_1 = self.env[
+ 'stock.return.picking'].with_context(context).create(dict(
+ picking_id=self.rma_id_1.picking_id.id,
+ ))
+ self.return_picking_id_1.create_returns()
+ self.assertEquals(self.rma_id_1.state, 'replacement_created')
+ self.assertTrue(len(self.sale_order_id_1.picking_ids.ids) > 1,
+ 'Product has not been replaced yet')
+
+ incoming_shipment = False
+ for pick in self.sale_order_id_1.picking_ids:
+ if pick.picking_type_code == 'incoming':
+ incoming_shipment = True
+ for move_line in pick.move_lines[0].move_line_ids:
+ move_line.lot_id = self.product1_lot_id.id
+ move_line.qty_done = 5
+ pick.button_validate()
+ pick.action_done()
+ self.assertTrue(incoming_shipment, 'Incoming shipment is not created')
+
+ def test_01_rma_request(self):
+ self.sale_order_id_2.action_confirm()
+ picking_ids = self.sale_order_id_2.picking_ids[0]
+ for move_line in picking_ids.move_lines[0].move_line_ids:
+ move_line.lot_id = self.product2_lot_id.id
+ move_line.qty_done = 10
+ for move_line in picking_ids.move_lines[1].move_line_ids:
+ move_line.lot_id = self.product1_lot_id.id
+ move_line.qty_done = 5
+ picking_ids.button_validate()
+ picking_ids.action_done()
+
+ self.rma_id_2 = self.env['rma.request'].create({
+ 'sale_order_id': self.sale_order_id_2.id,
+ 'picking_id': picking_ids[0].id,
+ 'date': datetime.now() - relativedelta(days=10),
+ 'partner_id': self.sale_order_id_2.partner_id.id,
+ 'type': 'replacement'
+ })
+ self.assertEquals(self.rma_id_2.state, 'draft')
+ self.rma_id_2._get_rma_lines()
+ self.assertTrue((len(self.rma_id_2.rma_line.ids)) != 0,
+ 'You can not create replacement request!')
+
+ self.rma_id_2._get_warranty_lines()
+ self.assertEquals((len(self.rma_id_2.warranty_expire_line.ids)), 1,
+ 'Replacement request must have expiry product!')
+
+ for rma_line in self.rma_id_2.rma_line:
+ replaceable_qty = sum(line.qty_done for line in
+ rma_line.move_line_id.move_line_ids if
+ line.lot_id.warranty_date and
+ line.lot_id.warranty_date >=
+ self.rma_id_2.date)
+ self.assertTrue(rma_line.qty_replaced <= replaceable_qty,
+ "You can only return %d quantity for %s" %
+ (replaceable_qty, rma_line.product_id.name))
+
+ self.rma_id_2.state = 'confirmed'
+ self.assertEquals(self.rma_id_2.state, 'confirmed')
+
+ context = {"active_model": 'rma.request',
+ "active_ids": [self.rma_id_2.id], "active_id":
+ self.rma_id_2.id, "rma": True}
+ self.return_picking_id_2 = self.env[
+ 'stock.return.picking'].with_context(context).create(dict(
+ picking_id=self.rma_id_2.picking_id.id,
+ ))
+ self.return_picking_id_2.create_returns()
+ self.assertEquals(self.rma_id_2.state, 'replacement_created')
+ self.assertTrue(len(self.sale_order_id_2.picking_ids.ids) > 1,
+ 'Product has not been replaced yet')
+
+ incoming_shipment = False
+ for pick in self.sale_order_id_2.picking_ids:
+ if pick.picking_type_code == 'incoming':
+ incoming_shipment = True
+ for move_line in pick.move_lines[0].move_line_ids:
+ move_line.lot_id = self.product1_lot_id.id
+ move_line.qty_done = 10
+ self.assertEquals(len(pick.move_lines[0].move_line_ids.ids), 1,
+ 'Only one product can be returned')
+ pick.button_validate()
+ pick.action_done()
+ self.assertTrue(incoming_shipment, 'Incoming shipment is not created')
diff --git a/addons/rma/views/menuitems_view.xml b/addons/rma/views/menuitems_view.xml
new file mode 100644
index 00000000..b3c81367
--- /dev/null
+++ b/addons/rma/views/menuitems_view.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/addons/rma/views/replacement_reason_view.xml b/addons/rma/views/replacement_reason_view.xml
new file mode 100644
index 00000000..54c93dde
--- /dev/null
+++ b/addons/rma/views/replacement_reason_view.xml
@@ -0,0 +1,41 @@
+
+
+
+ replacement.reason.tree
+ replacement.reason
+ tree
+
+
+
+
+
+
+
+
+
+ replacement.reason.form
+ replacement.reason
+ form
+
+
+
+
+
+
+
+ Replacement Reason
+ ir.actions.act_window
+ replacement.reason
+ form
+ tree,form
+ []
+ {}
+
+
diff --git a/addons/rma/views/rma_line_view.xml b/addons/rma/views/rma_line_view.xml
new file mode 100644
index 00000000..762b375a
--- /dev/null
+++ b/addons/rma/views/rma_line_view.xml
@@ -0,0 +1,73 @@
+
+
+
+
+ rma.line.form
+ rma.line
+ form
+
+
+
+
+
+
+
+ RMA Lines
+ ir.actions.act_window
+ rma.line
+ form
+ tree,form
+ []
+ {}
+
+
+
+ rma.line.search.view
+ rma.line
+
+
+
+
+
+
+
+
+ rma.line.graph
+ rma.line
+
+
+
+
+
+
+
+
+
+ Replacement Requests
+ rma.line
+ graph,tree,form
+
+
+ No replacement request.
+
+
+ {'search_default_team_id': active_id}
+
+
diff --git a/addons/rma/views/rma_request_view.xml b/addons/rma/views/rma_request_view.xml
new file mode 100644
index 00000000..71de7f05
--- /dev/null
+++ b/addons/rma/views/rma_request_view.xml
@@ -0,0 +1,186 @@
+
+
+
+ rma.request.tree
+ rma.request
+ tree
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ rma.request.form
+ rma.request
+ form
+
+
+
+
+
+
+
+ rma.request.search.view
+ rma.request
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RMA Request
+ ir.actions.act_window
+ rma.request
+ form
+ tree,form
+ []
+ {}
+
+
+
+ RMA Request
+ ir.actions.act_window
+ rma.request
+ form
+ tree,form
+ []
+ {
+ 'search_default_team_id': [active_id],
+ 'default_team_id': active_id,
+ }
+
+
+
+
+
diff --git a/addons/rma/views/sales_team_view.xml b/addons/rma/views/sales_team_view.xml
new file mode 100644
index 00000000..b03e371b
--- /dev/null
+++ b/addons/rma/views/sales_team_view.xml
@@ -0,0 +1,79 @@
+
+
+
+ replacement.team.form
+ crm.team
+
+
+
+
+
+
+
+
+
+
+
+
+ replacement.team.kanban
+ crm.team
+
+
+
+
+
+ Replacements
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ replacement.kanban
+ crm.team
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/rma/views/stock_picking_view.xml b/addons/rma/views/stock_picking_view.xml
new file mode 100644
index 00000000..a1dc36b1
--- /dev/null
+++ b/addons/rma/views/stock_picking_view.xml
@@ -0,0 +1,15 @@
+
+
+
+
+ stock.picking.form
+ stock.picking
+
+
+
+
+
+
+
+
+
diff --git a/addons/rma/views/stock_production_lot_view.xml b/addons/rma/views/stock_production_lot_view.xml
new file mode 100644
index 00000000..ffcf1729
--- /dev/null
+++ b/addons/rma/views/stock_production_lot_view.xml
@@ -0,0 +1,13 @@
+
+
+
+ stock.production.lot.form.inherit
+ stock.production.lot
+
+
+
+
+
+
+
+
diff --git a/addons/rma/views/warranty_expire_line_view.xml b/addons/rma/views/warranty_expire_line_view.xml
new file mode 100644
index 00000000..256109ce
--- /dev/null
+++ b/addons/rma/views/warranty_expire_line_view.xml
@@ -0,0 +1,47 @@
+
+
+
+ warranty.expire..line.tree
+ warranty.expire.line
+ tree
+
+
+
+
+
+
+
+
+
+
+
+
+
+ warranty.expire.line.form
+ warranty.expire.line
+ form
+
+
+
+
+
+
+
+ Warranty Expire Lines
+ ir.actions.act_window
+ warranty.expire.line
+ form
+ tree,form
+ []
+ {}
+
+
diff --git a/addons/rma/wizard/__init__.py b/addons/rma/wizard/__init__.py
new file mode 100644
index 00000000..031c3e3b
--- /dev/null
+++ b/addons/rma/wizard/__init__.py
@@ -0,0 +1,3 @@
+# Part of Flectra See LICENSE file for full copyright and licensing details.
+
+from . import stock_return_picking
diff --git a/addons/rma/wizard/stock_return_picking.py b/addons/rma/wizard/stock_return_picking.py
new file mode 100644
index 00000000..d7fc48e5
--- /dev/null
+++ b/addons/rma/wizard/stock_return_picking.py
@@ -0,0 +1,98 @@
+# Part of Flectra See LICENSE file for full copyright and licensing details.
+
+from flectra import api, models, _
+from flectra.exceptions import UserError
+
+
+class ReturnPicking(models.TransientModel):
+ _inherit = 'stock.return.picking'
+
+ @api.model
+ def default_get(self, fields):
+ if self._context.get('rma') and self._context.get('active_model') ==\
+ 'rma.request':
+ res = {}
+ if len(self.env.context.get('active_ids', list())) > 1:
+ raise UserError(_(
+ "You may only replace single picking at a time!"))
+ move_dest_exists = False
+ product_return_moves = []
+ rma_id = self.env['rma.request'].browse(
+ self.env.context.get('active_id'))
+ picking = rma_id.picking_id
+ if picking:
+ res.update({'picking_id': picking.id})
+ if picking.state != 'done':
+ raise UserError(_("You may only return Done pickings"))
+
+ for move_line in picking.move_lines:
+ qty = move_line.product_qty or 0.00
+ if move_line.move_line_ids:
+ product_ids = [line.product_id for line in
+ move_line.move_line_ids]
+ vals = {}
+ for rma_line in rma_id.rma_line:
+ vals.update({
+ rma_line.product_id.id: rma_line.qty_replaced,
+ })
+ for prod_id in product_ids:
+ if vals.get(prod_id.id):
+ qty = vals.get(prod_id.id)
+ if move_line.scrapped:
+ continue
+ if move_line.move_dest_ids:
+ move_dest_exists = True
+ quantity = qty - sum(
+ move_line.move_dest_ids.filtered(
+ lambda m: m.state in [
+ 'partially_available', 'assigned', 'done']
+ ).mapped('move_line_ids').mapped('product_qty'))
+ product = [line.product_id for line in rma_id.rma_line]
+ for pid in product:
+ if move_line.product_id.id == pid.id:
+ product_return_moves.append(
+ (0, 0,
+ {
+ 'product_id': move_line.product_id.id,
+ 'quantity': quantity,
+ 'move_id': move_line.id
+ }))
+
+ if not product_return_moves:
+ raise UserError(_(
+ "No products to replace (only lines in Done state and"
+ "not fully replaced yet can be replaced)!"))
+ if 'product_return_moves' in fields:
+ res.update({'product_return_moves': product_return_moves})
+ if 'move_dest_exists' in fields:
+ res.update({'move_dest_exists': move_dest_exists})
+ if 'parent_location_id' in fields and picking.\
+ location_id.usage == 'internal':
+ res.update(
+ {
+ 'parent_location_id':
+ picking.picking_type_id.warehouse_id and
+ picking.picking_type_id.warehouse_id.
+ view_location_id.id or picking.
+ location_id.location_id.id
+ })
+ if 'original_location_id' in fields:
+ res.update(
+ {'original_location_id': picking.location_id.id})
+ if 'location_id' in fields:
+ location_id = picking.location_id.id
+ if picking.picking_type_id.return_picking_type_id.\
+ default_location_dest_id.return_location:
+ location_id = picking.picking_type_id.\
+ return_picking_type_id.default_location_dest_id.id
+ res['location_id'] = location_id
+ return res
+ return super(ReturnPicking, self).default_get(fields)
+
+ def create_returns(self):
+ if self._context.get('rma') and self._context.get('active_model') == \
+ 'rma.request':
+ rma_id = self.env['rma.request'].browse(
+ self.env.context.get('active_id'))
+ rma_id.state = 'replacement_created'
+ return super(ReturnPicking, self).create_returns()