From aa7a8326a36ecccbb3370296f737a7fb3e9e0369 Mon Sep 17 00:00:00 2001 From: Rutuba Chavda Date: Wed, 17 Jan 2018 12:58:52 +0530 Subject: [PATCH] [ADD]add RMA (Return Merchandise Authorizationing) module --- addons/rma/__init__.py | 4 + addons/rma/__manifest__.py | 39 ++ addons/rma/data/mail_message_subtype_data.xml | 52 ++ addons/rma/data/mail_template_data.xml | 63 ++ addons/rma/data/replacement_reason_data.xml | 50 ++ addons/rma/demo/product_demo.xml | 30 + addons/rma/demo/sale_order_demo.xml | 53 ++ addons/rma/demo/stock_demo.xml | 19 + addons/rma/i18n/rma.pot | 538 ++++++++++++++++++ addons/rma/models/__init__.py | 9 + addons/rma/models/replacement_reason.py | 10 + addons/rma/models/rma_line.py | 40 ++ addons/rma/models/rma_request.py | 186 ++++++ addons/rma/models/sales_team.py | 19 + addons/rma/models/stock_picking.py | 20 + addons/rma/models/stock_production_lot.py | 8 + addons/rma/models/warranty_expire_line.py | 15 + addons/rma/security/ir.model.access.csv | 5 + .../rma/sequences/data_rma_order_sequence.xml | 10 + addons/rma/static/description/icon.png | Bin 0 -> 16189 bytes addons/rma/tests/__init__.py | 3 + addons/rma/tests/test_rma_request.py | 172 ++++++ addons/rma/views/menuitems_view.xml | 20 + addons/rma/views/replacement_reason_view.xml | 41 ++ addons/rma/views/rma_line_view.xml | 73 +++ addons/rma/views/rma_request_view.xml | 186 ++++++ addons/rma/views/sales_team_view.xml | 79 +++ addons/rma/views/stock_picking_view.xml | 15 + .../rma/views/stock_production_lot_view.xml | 13 + .../rma/views/warranty_expire_line_view.xml | 47 ++ addons/rma/wizard/__init__.py | 3 + addons/rma/wizard/stock_return_picking.py | 98 ++++ 32 files changed, 1920 insertions(+) create mode 100644 addons/rma/__init__.py create mode 100644 addons/rma/__manifest__.py create mode 100644 addons/rma/data/mail_message_subtype_data.xml create mode 100644 addons/rma/data/mail_template_data.xml create mode 100644 addons/rma/data/replacement_reason_data.xml create mode 100644 addons/rma/demo/product_demo.xml create mode 100644 addons/rma/demo/sale_order_demo.xml create mode 100644 addons/rma/demo/stock_demo.xml create mode 100644 addons/rma/i18n/rma.pot create mode 100644 addons/rma/models/__init__.py create mode 100644 addons/rma/models/replacement_reason.py create mode 100644 addons/rma/models/rma_line.py create mode 100644 addons/rma/models/rma_request.py create mode 100644 addons/rma/models/sales_team.py create mode 100644 addons/rma/models/stock_picking.py create mode 100644 addons/rma/models/stock_production_lot.py create mode 100644 addons/rma/models/warranty_expire_line.py create mode 100644 addons/rma/security/ir.model.access.csv create mode 100644 addons/rma/sequences/data_rma_order_sequence.xml create mode 100644 addons/rma/static/description/icon.png create mode 100644 addons/rma/tests/__init__.py create mode 100644 addons/rma/tests/test_rma_request.py create mode 100644 addons/rma/views/menuitems_view.xml create mode 100644 addons/rma/views/replacement_reason_view.xml create mode 100644 addons/rma/views/rma_line_view.xml create mode 100644 addons/rma/views/rma_request_view.xml create mode 100644 addons/rma/views/sales_team_view.xml create mode 100644 addons/rma/views/stock_picking_view.xml create mode 100644 addons/rma/views/stock_production_lot_view.xml create mode 100644 addons/rma/views/warranty_expire_line_view.xml create mode 100644 addons/rma/wizard/__init__.py create mode 100644 addons/rma/wizard/stock_return_picking.py 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: +

+ + + + + + + + + + + + + % for line in object.warranty_expire_line: + + + + + + + % endfor + +
ProductLotQuantityWarranty Date
${line.product_id.name}${line.lot_id.name}${line.qty_expired}${line.warranty_date}
+ % 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" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" % for line in object.warranty_expire_line:\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" % endfor\n" +" \n" +"
ProductLotQuantityWarranty Date
${line.product_id.name}${line.lot_id.name}${line.qty_expired}${line.warranty_date}
\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 0000000000000000000000000000000000000000..e4097b19805636ebba46f7a49e54e8ceda4592ad GIT binary patch literal 16189 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 b`2Pa{ZKo`>AZ-MG00000NkvXXu0mjf9QxL4 literal 0 HcmV?d00001 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 + + + + +
+ +
+
+ + + + + + +
+
+
+ + + 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()