Merge pull request #127 from ForgeFlow/13.0-mig-rma

[13.0][MIG] rma
This commit is contained in:
Aaron ForgeFlow
2020-01-20 16:58:17 +01:00
committed by GitHub
49 changed files with 5349 additions and 5 deletions

View File

@@ -36,10 +36,10 @@ env:
global:
- VERSION="13.0" TESTS="0" LINT_CHECK="0" MAKEPOT="0"
install:
- git clone -b master-eficent https://github.com/ForgeFlow/maintainer-quality-tools.git $HOME/maintainer-quality-tools
- export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
- travis_install_nightly
install:
- git clone -b master-eficent https://github.com/ForgeFlow/maintainer-quality-tools.git $HOME/maintainer-quality-tools
- export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
- travis_install_nightly
script:
- travis_run_tests

View File

@@ -162,4 +162,4 @@ General Public License ever published by the Free Software Foundation.
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
Library.

136
rma/README.rst Normal file
View File

@@ -0,0 +1,136 @@
======================================
RMA (Return Merchandise Authorization)
======================================
A Return Merchandise Authorization (RMA), is a part of the process of
returning a product in order to receive a refund, replacement, or repair
during the product's warranty period.
The purchaser of the product must contact the manufacturer (or distributor
or retailer) to obtain authorization to return the product.
The resulting RMA number must be displayed on or included in the returned
product's packaging.
The issuance of an RMA is a key gatekeeping moment in the reverse logistics
cycle, providing the vendor with a final opportunity to diagnose and correct
the customer's problem with the product (such as improper installation or
configuration) before the customer permanently relinquishes ownership
of the product to the manufacturer, commonly referred to as a return.
As returns are costly for the vendor and inconvenient for the customer,
any return that can be prevented benefits both parties.
**Table of contents**
.. contents::
:local:
Configuration
=============
Security
~~~~~~~~
Go to Settings > Users and assign the appropiate permissions to users.
Different security groups grant distinct levels of access to the RMA features.
* Users in group "RMA Customer User" or "RMA Supplier User" can access to,
create and process RMA's associated to customers or suppliers respectively.
* Users in group "RMA Manager" can access to, create, approve and process RMA's
associated to both customers and suppliers.
RMA Approval Policy
~~~~~~~~~~~~~~~~~~~
There are two RMA approval policies in product catogories:
* One step: Always auto-approve RMAs that only contain products within
categories with this policy.
* Two steps: A RMA order containing a product within a category with this
policy will request the RMA manager approval.
In order to change the approval policy of a product category follow the next
steps:
#. Go to *Inventory > Configuration > Products > Product Categories*.
#. Select one and change the field *RMA Approval Policy* to your convenience.
Other Settings
~~~~~~~~~~~~~~
#. Go to RMA > Configuration > Settings > Return Merchandising
Authorization and select the option "Display 3 fields on rma: partner,
invoice address, delivery address" if needed.
#. Go to RMA > Configuration > Warehouse management > Warehouses and add
a default RMA location and RMA picking type for customers and suppliers RMA
picking type. In case the warehouse is configured to use routes, you need to
create at least one route per rma type with at least two push rules (one for
inbound another for outbound) it's very important to select the type of
operation supplier if we are moving in the company and customer if we are
moving out of the company.
Usage
=====
RMA are accessible though Inventory menu. There's four menus, divided by type.
Users can access to the list of RMA or RMA lines.
Create an RMA:
#. Select a partner. Enter RMA lines associated to an existing picking, or
manually.
#. Request approval and approve.
#. Click on RMA Lines button.
#. Click on more and select an option: "Receive products", "Create Delivery
Order".
#. Go back to the RMA. Set the RMA to done if not further action is required.
Known issues / Roadmap
======================
* Picking operations report in customer RMA dropshipping case is showing
"Vendor Address" while it should be "Customer Address".
* Dropshipping always counted as a delivery on the smart buttons.
* Uninstall hook.
* Constraints instead of required fields on rma.order.line.
* Rename type field on rma.order and rma.order.line
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/ForgeFlow/stock-rma/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/ForgeFlow/stock-rma/issues/new?body=module:%20rma%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* ForgeFlow S.L.
Contributors
~~~~~~~~~~~~
* Jordi Ballester Alomar <jordi.ballester@forgeflow.com>
* Aaron Henriquez <ahenriquez@forgeflow.com>
* Lois Rilo <lois.rilo@forgeflow.com>
* Bhavesh Odedra <bodedra@opensourceintegrators.com>
* Akim Juillerat <akim.juillerat@camptocamp.com>
* Alexandre Fayolle <alexandre.fayolle@camptocamp.com>
* Chafique Delli <chafique.delli@akretion.com>
* Héctor Villarreal <hector.villarreal@forgeflow.com>
Maintainers
~~~~~~~~~~~
This module is maintained by the ForgeFlow.
This module is part of the `ForgeFlow/stock-rma <https://github.com/ForgeFlow/stock-rma/tree/13.0/rma>`_ project on GitHub.

5
rma/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
# Copyright (C) 2017-20 ForgeFlow S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
from . import models
from . import wizards

38
rma/__manifest__.py Normal file
View File

@@ -0,0 +1,38 @@
# Copyright (C) 2017-20 ForgeFlow S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
{
"name": "RMA (Return Merchandise Authorization)",
"version": "13.0.1.0.0",
"license": "LGPL-3",
"category": "RMA",
"summary": "Introduces the return merchandise authorization (RMA) process "
"in odoo",
"author": "ForgeFlow",
"website": "https://github.com/ForgeFlow/stock-rma",
"depends": ["stock", "mail", "web"],
"demo": ["demo/stock_demo.xml"],
"data": [
"security/rma.xml",
"security/ir.model.access.csv",
"data/rma_sequence.xml",
"data/stock_data.xml",
"data/rma_operation.xml",
"report/rma_report.xml",
"report/rma_report_templates.xml",
"views/rma_order_view.xml",
"views/rma_operation_view.xml",
"views/rma_order_line_view.xml",
"views/stock_view.xml",
"views/stock_warehouse.xml",
"views/product_view.xml",
"views/res_partner_view.xml",
"views/res_config_settings_views.xml",
"views/rma_menu.xml",
"wizards/rma_make_picking_view.xml",
"wizards/rma_add_stock_move_view.xml",
"wizards/rma_order_line_make_supplier_rma_view.xml",
],
"installable": True,
"application": True,
}

View File

@@ -0,0 +1,45 @@
<?xml version="1.0"?>
<odoo noupdate="1">
<record id="rma_operation_customer_replace" model="rma.operation">
<field name="name">Replace After Receive</field>
<field name="code">RPL-C</field>
<field name="receipt_policy">ordered</field>
<field name="delivery_policy">received</field>
<field name="type">customer</field>
<field name="in_route_id" ref="rma.route_rma_customer"/>
<field name="out_route_id" ref="rma.route_rma_customer"/>
</record>
<record id="rma_operation_supplier_replace" model="rma.operation">
<field name="name">Replace</field>
<field name="code">RPL-S</field>
<field name="receipt_policy">ordered</field>
<field name="delivery_policy">ordered</field>
<field name="type">supplier</field>
<field name="in_route_id" ref="rma.route_rma_supplier"/>
<field name="out_route_id" ref="rma.route_rma_supplier"/>
</record>
<record id="rma_operation_ds_replace" model="rma.operation">
<field name="name">Dropship - Deliver to vendor</field>
<field name="code">DS-RPL-C</field>
<field name="receipt_policy">ordered</field>
<field name="delivery_policy">no</field>
<field name="type">customer</field>
<field name="customer_to_supplier" eval="True"/>
<field name="out_route_id" ref="rma.route_rma_dropship"/>
<field name="in_route_id" ref="rma.route_rma_dropship"/>
</record>
<record id="rma_operation_ds_replace_supplier" model="rma.operation">
<field name="name">Dropship - Deliver to customer</field>
<field name="code">DS-RPL-S</field>
<field name="receipt_policy">no</field>
<field name="delivery_policy">ordered</field>
<field name="type">supplier</field>
<field name="supplier_to_customer" eval="True"/>
<field name="out_route_id" ref="rma.route_rma_dropship"/>
<field name="in_route_id" ref="rma.route_rma_dropship"/>
</record>
</odoo>

42
rma/data/rma_sequence.xml Normal file
View File

@@ -0,0 +1,42 @@
<?xml version="1.0"?>
<odoo noupdate="1">
<record id="rma_seq" model="ir.sequence">
<field name="name">Customer RMA sequence</field>
<field name="code">rma.order.customer</field>
<field name="padding">5</field>
<field name="prefix">RMAG/%(year)s/</field>
<field eval="1" name="number_next"/>
<field eval="1" name="number_increment"/>
</record>
<record id="rma_supplier_seq" model="ir.sequence">
<field name="name">Supplier RMA sequence</field>
<field name="code">rma.order.supplier</field>
<field name="padding">5</field>
<field name="prefix">RTVG/%(year)s/</field>
<field eval="1" name="number_next"/>
<field eval="1" name="number_increment"/>
<field name="company_id" eval="False"/>
</record>
<record id="rma_line_seq" model="ir.sequence">
<field name="name">Customer RMA Line sequence</field>
<field name="code">rma.order.line.customer</field>
<field name="padding">5</field>
<field name="prefix">RMA/%(year)s/</field>
<field eval="1" name="number_next"/>
<field eval="1" name="number_increment"/>
<field name="company_id" eval="False"/>
</record>
<record id="rma_line_supplier_seq" model="ir.sequence">
<field name="name">Supplier RMA Line sequence</field>
<field name="code">rma.order.line.supplier</field>
<field name="padding">5</field>
<field name="prefix">RTV/%(year)s/</field>
<field eval="1" name="number_next"/>
<field eval="1" name="number_increment"/>
<field name="company_id" eval="False"/>
</record>
</odoo>

216
rma/data/stock_data.xml Normal file
View File

@@ -0,0 +1,216 @@
<?xml version="1.0"?>
<odoo noupdate="1">
<record id="location_rma" model="stock.location">
<field name="name">WH RMA</field>
<field name="usage">internal</field>
<field name="location_id" ref="stock.stock_location_stock"/>
</record>
<record id="seq_picking_type_rma_cust_out" model="ir.sequence">
<field name="name">DOC</field>
<field name="prefix">RCO</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<record id="seq_picking_type_rma_cust_in" model="ir.sequence">
<field name="name">INC</field>
<field name="prefix">RCI</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<record id="seq_picking_type_rma_sup_out" model="ir.sequence">
<field name="name">DOS</field>
<field name="prefix">RSO</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<record id="seq_picking_type_rma_sup_in" model="ir.sequence">
<field name="name">INS</field>
<field name="prefix">RSI</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<record id="seq_picking_type_rma_dropship" model="ir.sequence">
<field name="name">DSS</field>
<field name="prefix">DSS</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<record id="picking_type_rma_cust_out" model="stock.picking.type">
<field name="name">RMA → Customer</field>
<field name="sequence_id" ref="seq_picking_type_rma_cust_out"/>
<field name="sequence_code">RMA → Customer</field>
<field name="default_location_src_id" ref="rma.location_rma"/>
<field name="default_location_dest_id"
ref="stock.stock_location_customers"/>
<field name="warehouse_id" eval="False"/>
<field name="code">outgoing</field>
</record>
<record id="picking_type_rma_cust_in" model="stock.picking.type">
<field name="name">Customer → RMA</field>
<field name="sequence_id" ref="seq_picking_type_rma_cust_in"/>
<field name="sequence_code">Customer → RMA</field>
<field name="default_location_src_id" ref="stock.stock_location_customers"/>
<field name="default_location_dest_id"
ref="rma.location_rma"/>
<field name="warehouse_id" eval="False"/>
<field name="code">incoming</field>
</record>
<record id="picking_type_rma_sup_out" model="stock.picking.type">
<field name="name">RMA -> Supplier</field>
<field name="sequence_id" ref="seq_picking_type_rma_sup_out"/>
<field name="sequence_code">RMA -> Supplier</field>
<field name="default_location_src_id" ref="rma.location_rma"/>
<field name="default_location_dest_id"
ref="stock.stock_location_suppliers"/>
<field name="warehouse_id" eval="False"/>
<field name="code">outgoing</field>
</record>
<record id="picking_type_rma_sup_in" model="stock.picking.type">
<field name="name">Supplier -> RMA</field>
<field name="sequence_id" ref="seq_picking_type_rma_sup_in"/>
<field name="sequence_code">Supplier -> RMA</field>
<field name="default_location_src_id" ref="stock.stock_location_suppliers"/>
<field name="default_location_dest_id"
ref="rma.location_rma"/>
<field name="warehouse_id" eval="False"/>
<field name="code">incoming</field>
</record>
<record id="picking_type_rma_dropship_return" model="stock.picking.type">
<field name="name">Customer -> Supplier</field>
<field name="sequence_id" ref="seq_picking_type_rma_dropship"/>
<field name="sequence_code">Customer -> Supplier</field>
<field name="default_location_src_id"
ref="stock.stock_location_customers"/>
<field name="default_location_dest_id"
ref="stock.stock_location_suppliers"/>
<field name="warehouse_id" eval="False"/>
<field name="code">incoming</field>
</record>
<record id="picking_type_rma_dropship" model="stock.picking.type">
<field name="name">Supplier -> Customer</field>
<field name="sequence_id" ref="seq_picking_type_rma_dropship"/>
<field name="sequence_code">Supplier -> Customer</field>
<field name="default_location_src_id"
ref="stock.stock_location_suppliers"/>
<field name="default_location_dest_id"
ref="stock.stock_location_customers"/>
<field name="warehouse_id" eval="False"/>
<field name="code">incoming</field>
</record>
<record id="stock.warehouse0" model="stock.warehouse">
<field name="lot_rma_id" ref="location_rma"/>
<field name="rma_cust_out_type_id" ref="picking_type_rma_cust_out"/>
<field name="rma_cust_in_type_id" ref="picking_type_rma_cust_in"/>
<field name="rma_sup_out_type_id" ref="picking_type_rma_sup_out"/>
<field name="rma_sup_in_type_id" ref="picking_type_rma_sup_in"/>
</record>
<record id="route_rma_customer" model='stock.location.route'>
<field name="name">RMA Customer</field>
<field name="company_id" eval="0"/>
<field name="sequence">10</field>
<field name="product_selectable" eval="False"/>
<field name="product_categ_selectable" eval="False"/>
<field name="warehouse_selectable" eval="False"/>
<field name="rma_selectable" eval="True"/>
</record>
<record id="route_rma_supplier" model='stock.location.route'>
<field name="name">RMA Supplier</field>
<field name="company_id" eval="0"/>
<field name="sequence">10</field>
<field name="product_selectable" eval="False"/>
<field name="product_categ_selectable" eval="False"/>
<field name="warehouse_selectable" eval="False"/>
<field name="rma_selectable" eval="True"/>
</record>
<record id="route_rma_dropship" model='stock.location.route'>
<field name="name">RMA Dropship</field>
<field name="company_id" eval="0"/>
<field name="sequence">10</field>
<field name="product_selectable" eval="False"/>
<field name="product_categ_selectable" eval="False"/>
<field name="warehouse_selectable" eval="False"/>
<field name="rma_selectable" eval="True"/>
</record>
<record id="rule_rma_customer_in_pull" model="stock.rule">
<field name="name">Customer → RMA</field>
<field name="action">pull</field>
<field name="warehouse_id" ref="stock.warehouse0"/>
<field name="location_src_id" ref="stock.stock_location_customers"/>
<field name="location_id" ref="rma.location_rma"/>
<field name="procure_method">make_to_stock</field>
<field name="route_id" ref="rma.route_rma_customer"/>
<field name="picking_type_id" ref="rma.picking_type_rma_cust_in"/>
</record>
<record id="rule_rma_customer_out_pull" model="stock.rule">
<field name="name">RMA → Customer</field>
<field name="action">pull</field>
<field name="warehouse_id" ref="stock.warehouse0"/>
<field name="location_src_id" ref="rma.location_rma"/>
<field name="location_id" ref="stock.stock_location_customers"/>
<field name="procure_method">make_to_stock</field>
<field name="route_id" ref="rma.route_rma_customer"/>
<field name="picking_type_id" ref="rma.picking_type_rma_cust_out"/>
</record>
<record id="rule_rma_supplier_out_pull" model="stock.rule">
<field name="name">RMA → Supplier</field>
<field name="action">pull</field>
<field name="warehouse_id" ref="stock.warehouse0"/>
<field name="location_id" ref="stock.stock_location_suppliers"/>
<field name="location_src_id" ref="rma.location_rma"/>
<field name="procure_method">make_to_stock</field>
<field name="route_id" ref="rma.route_rma_supplier"/>
<field name="picking_type_id" ref="rma.picking_type_rma_sup_out"/>
</record>
<record id="rule_rma_supplier_in_pull" model="stock.rule">
<field name="name">Supplier → RMA</field>
<field name="action">pull</field>
<field name="warehouse_id" ref="stock.warehouse0"/>
<field name="location_id" ref="rma.location_rma"/>
<field name="location_src_id" ref="stock.stock_location_suppliers"/>
<field name="procure_method">make_to_stock</field>
<field name="route_id" ref="rma.route_rma_supplier"/>
<field name="picking_type_id" ref="rma.picking_type_rma_sup_in"/>
</record>
<record id="rule_rma_dropship_return" model="stock.rule">
<field name="name">Customer → Supplier</field>
<field name="action">pull</field>
<field name="location_id" ref="stock.stock_location_suppliers"/>
<field name="location_src_id" ref="stock.stock_location_customers"/>
<field name="procure_method">make_to_stock</field>
<field name="route_id" ref="rma.route_rma_dropship"/>
<field name="picking_type_id"
ref="rma.picking_type_rma_dropship_return"/>
</record>
<record id="rule_rma_dropship" model="stock.rule">
<field name="name">Supplier → Customer</field>
<field name="action">pull</field>
<field name="location_id" ref="stock.stock_location_customers"/>
<field name="location_src_id" ref="stock.stock_location_suppliers"/>
<field name="procure_method">make_to_stock</field>
<field name="route_id" ref="rma.route_rma_dropship"/>
<field name="picking_type_id"
ref="rma.picking_type_rma_dropship"/>
</record>
</odoo>

121
rma/demo/stock_demo.xml Normal file
View File

@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<!--Sequences-->
<record id="seq_picking_type_rma_cust_out" model="ir.sequence">
<field name="name">RMA Customer Sequence out</field>
<field name="prefix">RMA/CUST/OUT</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<record id="seq_picking_type_rma_cust_in" model="ir.sequence">
<field name="name">RMA Customer Sequence in</field>
<field name="prefix">RMA/CUST/IN</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<record id="seq_picking_type_rma_sup_out" model="ir.sequence">
<field name="name">RMA Supplier Sequence out</field>
<field name="prefix">RMA/SUPP/OUT</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<record id="seq_picking_type_rma_sup_in" model="ir.sequence">
<field name="name">RMA Supplier Sequence in</field>
<field name="prefix">RMA/SUPP/IN</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<record id="seq_picking_type_rma_dropship" model="ir.sequence">
<field name="name">RMA Dropship</field>
<field name="prefix">RMA/DS</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<!--Dropshipping Types-->
<record id="picking_type_rma_dropship_return" model="stock.picking.type">
<field name="name">Customer -> Supplier</field>
<field name="sequence_id" ref="seq_picking_type_rma_dropship"/>
<field name="default_location_src_id"
ref="stock.stock_location_customers"/>
<field name="default_location_dest_id"
ref="stock.stock_location_suppliers"/>
<field name="warehouse_id" eval="False"/>
<field name="code">incoming</field>
</record>
<record id="picking_type_rma_dropship" model="stock.picking.type">
<field name="name">Supplier -> Customer</field>
<field name="sequence_id" ref="seq_picking_type_rma_dropship"/>
<field name="default_location_src_id"
ref="stock.stock_location_suppliers"/>
<field name="default_location_dest_id"
ref="stock.stock_location_customers"/>
<field name="warehouse_id" eval="False"/>
<field name="code">incoming</field>
</record>
<!--Routes-->
<record id="route_rma_customer" model="stock.location.route">
<field name="name">RMA Customer</field>
<field name="sequence">10</field>
<field name="product_selectable" eval="False"/>
<field name="product_categ_selectable" eval="False"/>
<field name="warehouse_selectable" eval="False"/>
<field name="rma_selectable" eval="True"/>
</record>
<record id="route_rma_supplier" model="stock.location.route">
<field name="name">RMA Supplier</field>
<field name="sequence">10</field>
<field name="product_selectable" eval="False"/>
<field name="product_categ_selectable" eval="False"/>
<field name="warehouse_selectable" eval="False"/>
<field name="rma_selectable" eval="True"/>
</record>
<record id="route_rma_dropship" model="stock.location.route">
<field name="name">RMA Dropship</field>
<field name="sequence">10</field>
<field name="product_selectable" eval="False"/>
<field name="product_categ_selectable" eval="False"/>
<field name="warehouse_selectable" eval="False"/>
<field name="rma_selectable" eval="True"/>
</record>
<!--Dropship rules-->
<record id="rule_rma_dropship_return" model="stock.rule">
<field name="name">Customer → Supplier</field>
<field name="action">pull</field>
<field name="warehouse_id" ref="stock.warehouse0"/>
<field name="location_id" ref="stock.stock_location_suppliers"/>
<field name="location_src_id" ref="stock.stock_location_customers"/>
<field name="procure_method">make_to_stock</field>
<field name="route_id" ref="rma.route_rma_dropship"/>
<field name="picking_type_id"
ref="rma.picking_type_rma_dropship_return"/>
</record>
<record id="rule_rma_dropship" model="stock.rule">
<field name="name">Supplier → Customer</field>
<field name="action">pull</field>
<field name="warehouse_id" ref="stock.warehouse0"/>
<field name="location_id" ref="stock.stock_location_customers"/>
<field name="location_src_id" ref="stock.stock_location_suppliers"/>
<field name="procure_method">make_to_stock</field>
<field name="route_id" ref="rma.route_rma_dropship"/>
<field name="picking_type_id"
ref="rma.picking_type_rma_dropship"/>
</record>
<!-- Enable the RMA in warehouse0 -->
<record id="stock.warehouse0" model="stock.warehouse">
<field name="rma_in_this_wh" eval="True"/>
</record>
</odoo>

13
rma/models/__init__.py Normal file
View File

@@ -0,0 +1,13 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
from . import rma_order
from . import rma_order_line
from . import rma_operation
from . import stock
from . import stock_warehouse
from . import product
from . import product_category
from . import procurement
from . import res_partner
from . import res_company
from . import res_config_settings

52
rma/models/procurement.py Normal file
View File

@@ -0,0 +1,52 @@
# Copyright (C) 2017-20 ForgeFlow S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
from odoo import fields, models
class StockRule(models.Model):
_inherit = "stock.rule"
def _get_stock_move_values(
self,
product_id,
product_qty,
product_uom,
location_id,
name,
origin,
company_id,
values,
):
res = super(StockRule, self)._get_stock_move_values(
product_id,
product_qty,
product_uom,
location_id,
name,
origin,
company_id,
values,
)
if "rma_line_id" in values:
line = values.get("rma_line_id")
res["rma_line_id"] = line.id
if line.delivery_address_id:
res["partner_id"] = line.delivery_address_id.id
else:
res["partner_id"] = line.rma_id.partner_id.id
dest_loc = self.env["stock.location"].browse([res["location_dest_id"]])[0]
if dest_loc.usage == "internal":
res["price_unit"] = line.price_unit
return res
class ProcurementGroup(models.Model):
_inherit = "procurement.group"
rma_id = fields.Many2one(
comodel_name="rma.order", string="RMA", ondelete="set null"
)
rma_line_id = fields.Many2one(
comodel_name="rma.order.line", string="RMA line", ondelete="set null"
)

18
rma/models/product.py Normal file
View File

@@ -0,0 +1,18 @@
# Copyright (C) 2017-20 ForgeFlow S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
from odoo import fields, models
class ProductTemplate(models.Model):
_inherit = "product.template"
rma_customer_operation_id = fields.Many2one(
comodel_name="rma.operation", string="Default RMA Customer Operation"
)
rma_supplier_operation_id = fields.Many2one(
comodel_name="rma.operation", string="Default RMA Supplier Operation"
)
rma_approval_policy = fields.Selection(
related="categ_id.rma_approval_policy", readonly=True
)

View File

@@ -0,0 +1,26 @@
# Copyright 2017 ForgeFlow
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
from odoo import fields, models
class ProductCategory(models.Model):
_inherit = "product.category"
rma_approval_policy = fields.Selection(
selection=[("one_step", "One step"), ("two_step", "Two steps")],
string="RMA Approval Policy",
required=True,
default="one_step",
help="Options: \n "
"* One step: Always auto-approve RMAs that only contain "
"products within categories with this policy.\n"
"* Two steps: A RMA containing a product within a category with "
"this policy will request the RMA manager approval.",
)
rma_customer_operation_id = fields.Many2one(
comodel_name="rma.operation", string="Default RMA Customer Operation"
)
rma_supplier_operation_id = fields.Many2one(
comodel_name="rma.operation", string="Default RMA Supplier Operation"
)

14
rma/models/res_company.py Normal file
View File

@@ -0,0 +1,14 @@
from odoo import fields, models
class ResCompany(models.Model):
_inherit = "res.company"
group_rma_delivery_address = fields.Boolean(
string="RMA addresses",
help="Display 3 fields on rma: partner, invoice address, delivery address",
)
group_rma_lines = fields.Boolean(
string="Use RMA groups", help="Group RMA lines in one RMA group"
)

View File

@@ -0,0 +1,20 @@
# Copyright (C) 2017-20 ForgeFlow S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
group_rma_delivery_address = fields.Boolean(
related="company_id.group_rma_delivery_address",
implied_group="rma.group_rma_delivery_invoice_address",
readonly=False,
)
group_rma_lines = fields.Boolean(
related="company_id.group_rma_lines",
readonly=False,
implied_group="rma.group_rma_groups",
)

23
rma/models/res_partner.py Normal file
View File

@@ -0,0 +1,23 @@
# Copyright 2017 ForgeFlow
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import fields, models
class ResPartner(models.Model):
_inherit = "res.partner"
def _compute_rma_line_count(self):
for rec in self:
rec.rma_line_count = len(rec.rma_line_ids)
rma_line_ids = fields.One2many(
comodel_name="rma.order.line", string="RMAs", inverse_name="partner_id"
)
rma_line_count = fields.Integer(compute="_compute_rma_line_count")
def action_open_partner_rma(self):
action = self.env.ref("rma.action_rma_customer_lines")
result = action.read()[0]
result["context"] = {"search_default_partner_id": self.id}
return result

View File

@@ -0,0 +1,98 @@
# Copyright 2017-18 ForgeFlow
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
from odoo import api, fields, models
class RmaOperation(models.Model):
_name = "rma.operation"
_description = "RMA Operation"
@api.model
def _default_warehouse_id(self):
company_id = self.env.user.company_id.id
warehouse = self.env["stock.warehouse"].search(
[("company_id", "=", company_id)], limit=1
)
return warehouse
@api.model
def _default_customer_location_id(self):
return self.env.ref("stock.stock_location_customers") or False
@api.model
def _default_supplier_location_id(self):
return self.env.ref("stock.stock_location_suppliers") or False
@api.model
def _default_routes(self):
op_type = self.env.context.get("default_type")
if op_type == "customer":
return self.env.ref("rma.route_rma_customer")
elif op_type == "supplier":
return self.env.ref("rma.route_rma_supplier")
name = fields.Char("Description", required=True)
code = fields.Char("Code", required=True)
active = fields.Boolean(string="Active", default=True)
receipt_policy = fields.Selection(
[
("no", "Not required"),
("ordered", "Based on Ordered Quantities"),
("delivered", "Based on Delivered Quantities"),
],
string="Receipts Policy",
default="no",
)
delivery_policy = fields.Selection(
[
("no", "Not required"),
("ordered", "Based on Ordered Quantities"),
("received", "Based on Received Quantities"),
],
string="Delivery Policy",
default="no",
)
in_route_id = fields.Many2one(
comodel_name="stock.location.route",
string="Inbound Route",
domain=[("rma_selectable", "=", True)],
default=lambda self: self._default_routes(),
)
out_route_id = fields.Many2one(
comodel_name="stock.location.route",
string="Outbound Route",
domain=[("rma_selectable", "=", True)],
default=lambda self: self._default_routes(),
)
customer_to_supplier = fields.Boolean(
string="The customer will send to the supplier"
)
supplier_to_customer = fields.Boolean(
string="The supplier will send to the customer"
)
in_warehouse_id = fields.Many2one(
comodel_name="stock.warehouse",
string="Inbound Warehouse",
default=lambda self: self._default_warehouse_id(),
)
out_warehouse_id = fields.Many2one(
comodel_name="stock.warehouse",
string="Outbound Warehouse",
default=lambda self: self._default_warehouse_id(),
)
location_id = fields.Many2one("stock.location", "Send To This Company Location")
type = fields.Selection(
[("customer", "Customer"), ("supplier", "Supplier")],
string="Used in RMA of this type",
required=True,
)
rma_line_ids = fields.One2many(
comodel_name="rma.order.line", inverse_name="operation_id", string="RMA lines"
)
company_id = fields.Many2one(
comodel_name="res.company",
string="Company",
required=True,
default=lambda self: self.env.user.company_id,
)

295
rma/models/rma_order.py Normal file
View File

@@ -0,0 +1,295 @@
# Copyright (C) 2017-20 ForgeFlow S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
from datetime import datetime
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class RmaOrder(models.Model):
_name = "rma.order"
_description = "RMA Group"
_inherit = ["mail.thread"]
@api.model
def _get_default_type(self):
if "supplier" in self.env.context:
return "supplier"
return "customer"
def _compute_in_shipment_count(self):
for rec in self:
picking_ids = []
if not rec.rma_line_ids:
rec.in_shipment_count = 0
continue
for line in rec.rma_line_ids:
for move in line.move_ids:
if move.location_dest_id.usage == "internal":
picking_ids.append(move.picking_id.id)
else:
if line.customer_to_supplier:
picking_ids.append(move.picking_id.id)
shipments = list(set(picking_ids))
rec.in_shipment_count = len(shipments)
def _compute_out_shipment_count(self):
picking_ids = []
for rec in self:
if not rec.rma_line_ids:
rec.out_shipment_count = 0
continue
for line in rec.rma_line_ids:
for move in line.move_ids:
if move.location_dest_id.usage in ("supplier", "customer"):
if not line.customer_to_supplier:
picking_ids.append(move.picking_id.id)
shipments = list(set(picking_ids))
rec.out_shipment_count = len(shipments)
def _compute_supplier_line_count(self):
self.supplier_line_count = len(
self.rma_line_ids.filtered(lambda r: r.supplier_rma_line_ids)
)
def _compute_line_count(self):
for rec in self:
rec.line_count = len(rec._get_valid_lines())
@api.depends("rma_line_ids", "rma_line_ids.state")
def _compute_state(self):
for rec in self:
rma_line_done = self.env["rma.order.line"].search_count(
[("id", "in", rec.rma_line_ids.ids), ("state", "=", "done")]
)
rma_line_approved = self.env["rma.order.line"].search_count(
[("id", "in", rec.rma_line_ids.ids), ("state", "=", "approved")]
)
rma_line_to_approve = self.env["rma.order.line"].search_count(
[("id", "in", rec.rma_line_ids.ids), ("state", "=", "to_approve")]
)
if rma_line_done != 0:
state = "done"
elif rma_line_approved != 0:
state = "approved"
elif rma_line_to_approve != 0:
state = "to_approve"
else:
state = "draft"
rec.state = state
@api.model
def _default_date_rma(self):
return datetime.now()
@api.model
def _default_warehouse_id(self):
warehouse = self.env["stock.warehouse"].search(
[("company_id", "=", self.env.user.company_id.id)], limit=1
)
return warehouse
name = fields.Char(string="Group Number", index=True, copy=False)
type = fields.Selection(
[("customer", "Customer"), ("supplier", "Supplier")],
string="Type",
required=True,
default=lambda self: self._get_default_type(),
readonly=True,
)
reference = fields.Char(
string="Partner Reference", help="The partner reference of this RMA order."
)
comment = fields.Text("Additional Information")
date_rma = fields.Datetime(
string="Order Date", index=True, default=lambda self: self._default_date_rma()
)
partner_id = fields.Many2one(
comodel_name="res.partner", string="Partner", required=True
)
rma_line_ids = fields.One2many("rma.order.line", "rma_id", string="RMA lines")
in_shipment_count = fields.Integer(
compute="_compute_in_shipment_count", string="# of Invoices"
)
out_shipment_count = fields.Integer(
compute="_compute_out_shipment_count", string="# of Outgoing Shipments"
)
line_count = fields.Integer(compute="_compute_line_count", string="# of RMA lines")
supplier_line_count = fields.Integer(
compute="_compute_supplier_line_count", string="# of Supplier RMAs"
)
company_id = fields.Many2one(
"res.company",
string="Company",
required=True,
default=lambda self: self.env.user.company_id,
)
assigned_to = fields.Many2one(
comodel_name="res.users",
track_visibility="onchange",
default=lambda self: self.env.uid,
)
requested_by = fields.Many2one(
comodel_name="res.users",
track_visibility="onchange",
default=lambda self: self.env.uid,
)
in_warehouse_id = fields.Many2one(
comodel_name="stock.warehouse",
string="Inbound Warehouse",
required=True,
default=_default_warehouse_id,
)
customer_to_supplier = fields.Boolean("The customer will send to the supplier")
supplier_to_customer = fields.Boolean("The supplier will send to the customer")
supplier_address_id = fields.Many2one(
comodel_name="res.partner",
string="Supplier Address",
help="Address of the supplier in case of Customer RMA operation " "dropship.",
)
customer_address_id = fields.Many2one(
comodel_name="res.partner",
string="Customer Address",
help="Address of the customer in case of Supplier RMA operation " "dropship.",
)
state = fields.Selection(
compute=_compute_state,
selection=[
("draft", "Draft"),
("to_approve", "To Approve"),
("approved", "Approved"),
("done", "Done"),
],
string="State",
default="draft",
store=True,
)
@api.constrains("partner_id", "rma_line_ids")
def _check_partner_id(self):
if self.rma_line_ids and self.partner_id != self.mapped(
"rma_line_ids.partner_id"
):
raise UserError(_("Group partner and RMA's partner must be the same."))
if len(self.mapped("rma_line_ids.partner_id")) > 1:
raise UserError(_("All grouped RMA's should have same partner."))
@api.model
def create(self, vals):
if self.env.context.get("supplier") or vals.get("type") == "supplier":
vals["name"] = self.env["ir.sequence"].next_by_code("rma.order.supplier")
else:
vals["name"] = self.env["ir.sequence"].next_by_code("rma.order.customer")
return super(RmaOrder, self).create(vals)
def action_view_in_shipments(self):
action = self.env.ref("stock.action_picking_tree_all")
result = action.read()[0]
picking_ids = []
for line in self.rma_line_ids:
for move in line.move_ids:
if move.location_dest_id.usage == "internal":
picking_ids.append(move.picking_id.id)
else:
if line.customer_to_supplier:
picking_ids.append(move.picking_id.id)
if picking_ids:
shipments = list(set(picking_ids))
# choose the view_mode accordingly
if len(shipments) > 1:
result["domain"] = [("id", "in", shipments)]
else:
res = self.env.ref("stock.view_picking_form", False)
result["views"] = [(res and res.id or False, "form")]
result["res_id"] = shipments[0]
return result
def action_view_out_shipments(self):
action = self.env.ref("stock.action_picking_tree_all")
result = action.read()[0]
picking_ids = []
for line in self.rma_line_ids:
for move in line.move_ids:
if move.location_dest_id.usage in ("supplier", "customer"):
if not line.customer_to_supplier:
picking_ids.append(move.picking_id.id)
if picking_ids:
shipments = list(set(picking_ids))
# choose the view_mode accordingly
if len(shipments) != 1:
result["domain"] = [("id", "in", shipments)]
else:
res = self.env.ref("stock.view_picking_form", False)
result["views"] = [(res and res.id or False, "form")]
result["res_id"] = shipments[0]
return result
def _get_valid_lines(self):
""":return: A recordset of rma lines.
"""
self.ensure_one()
return self.rma_line_ids
def action_view_lines(self):
if self.type == "customer":
action = self.env.ref("rma.action_rma_customer_lines")
res = self.env.ref("rma.view_rma_line_form", False)
else:
action = self.env.ref("rma.action_rma_supplier_lines")
res = self.env.ref("rma.view_rma_line_supplier_form", False)
result = action.read()[0]
lines = self._get_valid_lines()
# choose the view_mode accordingly
if len(lines.ids) != 1:
result["domain"] = [("id", "in", lines.ids)]
else:
result["views"] = [(res and res.id or False, "form")]
result["res_id"] = lines.id
result["context"] = {}
return result
def action_view_supplier_lines(self):
action = self.env.ref("rma.action_rma_supplier_lines")
result = action.read()[0]
lines = self.rma_line_ids
for line_id in lines:
related_lines = [line.id for line in line_id.supplier_rma_line_ids]
# choose the view_mode accordingly
if len(related_lines) != 1:
result["domain"] = [("id", "in", related_lines)]
else:
res = self.env.ref("rma.view_rma_line_supplier_form", False)
result["views"] = [(res and res.id or False, "form")]
result["res_id"] = related_lines[0]
return result
@api.onchange("in_warehouse_id")
def _onchange_in_warehouse_id(self):
if self.in_warehouse_id and self.rma_line_ids:
self.rma_line_ids.write(
{
"in_warehouse_id": self.in_warehouse_id.id,
"location_id": self.in_warehouse_id.lot_rma_id.id,
}
)
@api.onchange("customer_to_supplier", "supplier_address_id")
def _onchange_customer_to_supplier(self):
if self.type == "customer" and self.rma_line_ids:
self.rma_line_ids.write(
{
"customer_to_supplier": self.customer_to_supplier,
"supplier_address_id": self.supplier_address_id.id,
}
)
@api.onchange("supplier_to_customer", "customer_address_id")
def _onchange_supplier_to_customer(self):
if self.type == "supplier" and self.rma_line_ids:
self.rma_line_ids.write(
{
"supplier_to_customer": self.supplier_to_customer,
"customer_address_id": self.customer_address_id.id,
}
)

View File

@@ -0,0 +1,724 @@
# Copyright (C) 2017-20 ForgeFlow S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
import operator
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
ops = {"=": operator.eq, "!=": operator.ne}
class RmaOrderLine(models.Model):
_name = "rma.order.line"
_description = "RMA"
_inherit = ["mail.thread"]
@api.model
def _get_default_type(self):
if "supplier" in self.env.context:
return "supplier"
return "customer"
@api.model
def _default_warehouse_id(self):
rma_id = self.env.context.get("default_rma_id", False)
warehouse = self.env["stock.warehouse"]
if rma_id:
rma = self.env["rma.order"].browse(rma_id)
warehouse = self.env["stock.warehouse"].search(
[("company_id", "=", rma.company_id.id)], limit=1
)
return warehouse
@api.model
def _default_location_id(self):
wh = self._default_warehouse_id()
return wh.lot_rma_id
@api.onchange("partner_id")
def _onchange_delivery_address(self):
self.delivery_address_id = self.env["res.partner"].browse(
self.partner_id.address_get(["delivery"])["delivery"]
)
def _compute_in_shipment_count(self):
for line in self:
picking_ids = []
for move in line.move_ids:
if move.location_dest_id.usage == "internal":
picking_ids.append(move.picking_id.id)
else:
if line.customer_to_supplier:
picking_ids.append(move.picking_id.id)
shipments = list(set(picking_ids))
line.in_shipment_count = len(shipments)
def _compute_out_shipment_count(self):
picking_ids = []
for line in self:
for move in line.move_ids:
if move.location_dest_id.usage in ("supplier", "customer"):
if not line.customer_to_supplier:
picking_ids.append(move.picking_id.id)
shipments = list(set(picking_ids))
line.out_shipment_count = len(shipments)
def _get_rma_move_qty(self, states, direction="in"):
for rec in self:
product_obj = self.env["uom.uom"]
qty = 0.0
if direction == "in":
op = ops["="]
else:
op = ops["!="]
for move in rec.move_ids.filtered(
lambda m: m.state in states and op(m.location_id.usage, rec.type)
):
qty += product_obj._compute_quantity(move.product_uom_qty, rec.uom_id)
return qty
@api.depends(
"move_ids",
"move_ids.state",
"qty_received",
"receipt_policy",
"product_qty",
"type",
)
def _compute_qty_to_receive(self):
for rec in self:
rec.qty_to_receive = 0.0
if rec.receipt_policy == "ordered":
rec.qty_to_receive = rec.product_qty - rec.qty_received
elif rec.receipt_policy == "delivered":
rec.qty_to_receive = rec.qty_delivered - rec.qty_received
@api.depends(
"move_ids",
"move_ids.state",
"delivery_policy",
"product_qty",
"type",
"qty_delivered",
"qty_received",
)
def _compute_qty_to_deliver(self):
for rec in self:
rec.qty_to_deliver = 0.0
if rec.delivery_policy == "ordered":
rec.qty_to_deliver = rec.product_qty - rec.qty_delivered
elif rec.delivery_policy == "received":
rec.qty_to_deliver = rec.qty_received - rec.qty_delivered
@api.depends("move_ids", "move_ids.state", "type")
def _compute_qty_incoming(self):
for rec in self:
qty = rec._get_rma_move_qty(
("draft", "confirmed", "assigned"), direction="in"
)
rec.qty_incoming = qty
@api.depends("move_ids", "move_ids.state", "type")
def _compute_qty_received(self):
for rec in self:
qty = rec._get_rma_move_qty("done", direction="in")
rec.qty_received = qty
@api.depends("move_ids", "move_ids.state", "type")
def _compute_qty_outgoing(self):
for rec in self:
qty = rec._get_rma_move_qty(
("draft", "confirmed", "assigned"), direction="out"
)
rec.qty_outgoing = qty
@api.depends("move_ids", "move_ids.state", "type")
def _compute_qty_delivered(self):
for rec in self:
qty = rec._get_rma_move_qty("done", direction="out")
rec.qty_delivered = qty
@api.model
def _get_supplier_rma_qty(self):
return sum(
self.supplier_rma_line_ids.filtered(lambda r: r.state != "cancel").mapped(
"product_qty"
)
)
@api.depends(
"customer_to_supplier",
"supplier_rma_line_ids",
"move_ids",
"move_ids.state",
"qty_received",
"receipt_policy",
"product_qty",
"type",
)
def _compute_qty_supplier_rma(self):
for rec in self:
if rec.customer_to_supplier:
supplier_rma_qty = rec._get_supplier_rma_qty()
rec.qty_to_supplier_rma = rec.product_qty - supplier_rma_qty
rec.qty_in_supplier_rma = supplier_rma_qty
else:
rec.qty_to_supplier_rma = 0.0
rec.qty_in_supplier_rma = 0.0
def _compute_rma_line_count(self):
for rec in self.filtered(lambda r: r.type == "customer"):
rec.rma_line_count = len(rec.supplier_rma_line_ids)
for rec in self.filtered(lambda r: r.type == "supplier"):
rec.rma_line_count = len(rec.customer_rma_id)
delivery_address_id = fields.Many2one(
comodel_name="res.partner",
string="Partner delivery address",
readonly=True,
states={"draft": [("readonly", False)]},
help="This address will be used to deliver repaired or replacement "
"products.",
)
rma_id = fields.Many2one(
comodel_name="rma.order",
string="RMA Group",
track_visibility="onchange",
readonly=True,
)
name = fields.Char(
string="Reference",
required=True,
default="/",
readonly=True,
states={"draft": [("readonly", False)]},
help="Add here the supplier RMA #. Otherwise an internal code is" " assigned.",
copy=False,
)
description = fields.Text(string="Description")
conditions = fields.Html(string="Terms and conditions")
origin = fields.Char(
string="Source Document",
readonly=True,
states={"draft": [("readonly", False)]},
help="Reference of the document that produced this rma.",
)
state = fields.Selection(
selection=[
("draft", "Draft"),
("to_approve", "To Approve"),
("approved", "Approved"),
("done", "Done"),
],
string="State",
default="draft",
track_visibility="onchange",
)
operation_id = fields.Many2one(
comodel_name="rma.operation",
string="Operation",
readonly=True,
states={"draft": [("readonly", False)]},
)
assigned_to = fields.Many2one(
comodel_name="res.users",
track_visibility="onchange",
default=lambda self: self.env.uid,
)
requested_by = fields.Many2one(
comodel_name="res.users",
track_visibility="onchange",
default=lambda self: self.env.uid,
)
partner_id = fields.Many2one(
comodel_name="res.partner",
required=True,
store=True,
track_visibility="onchange",
string="Partner",
readonly=True,
states={"draft": [("readonly", False)]},
)
sequence = fields.Integer(
default=10, help="Gives the sequence of this line when displaying the rma."
)
product_id = fields.Many2one(
comodel_name="product.product",
string="Product",
ondelete="restrict",
required=True,
readonly=True,
states={"draft": [("readonly", False)]},
)
product_tracking = fields.Selection(related="product_id.tracking")
lot_id = fields.Many2one(
comodel_name="stock.production.lot",
string="Lot/Serial Number",
readonly=True,
states={"draft": [("readonly", False)]},
)
product_qty = fields.Float(
string="Return Qty",
copy=False,
default=1.0,
digits="Product Unit of Measure",
readonly=True,
states={"draft": [("readonly", False)]},
)
uom_id = fields.Many2one(
comodel_name="uom.uom",
string="Unit of Measure",
required=True,
readonly=True,
states={"draft": [("readonly", False)]},
)
price_unit = fields.Monetary(
string="Price Unit", readonly=True, states={"draft": [("readonly", False)]}
)
in_shipment_count = fields.Integer(
compute="_compute_in_shipment_count", string="# of Shipments"
)
out_shipment_count = fields.Integer(
compute="_compute_out_shipment_count", string="# of Deliveries"
)
move_ids = fields.One2many(
"stock.move", "rma_line_id", string="Stock Moves", readonly=True, copy=False
)
reference_move_id = fields.Many2one(
comodel_name="stock.move",
string="Originating Stock Move",
copy=False,
readonly=True,
states={"draft": [("readonly", False)]},
)
currency_id = fields.Many2one(
"res.currency",
string="Currency",
default=lambda self: self.env.user.company_id.currency_id,
)
company_id = fields.Many2one(
comodel_name="res.company",
string="Company",
required=True,
default=lambda self: self.env.user.company_id,
)
type = fields.Selection(
selection=[("customer", "Customer"), ("supplier", "Supplier")],
string="Type",
required=True,
default=lambda self: self._get_default_type(),
readonly=True,
)
customer_to_supplier = fields.Boolean(
"The customer will send to the supplier",
readonly=True,
states={"draft": [("readonly", False)]},
)
supplier_to_customer = fields.Boolean(
"The supplier will send to the customer",
readonly=True,
states={"draft": [("readonly", False)]},
)
receipt_policy = fields.Selection(
[
("no", "Not required"),
("ordered", "Based on Ordered Quantities"),
("delivered", "Based on Delivered Quantities"),
],
required=True,
string="Receipts Policy",
default="no",
readonly=True,
states={"draft": [("readonly", False)]},
)
delivery_policy = fields.Selection(
[
("no", "Not required"),
("ordered", "Based on Ordered Quantities"),
("received", "Based on Received Quantities"),
],
required=True,
string="Delivery Policy",
default="no",
readonly=True,
states={"draft": [("readonly", False)]},
)
in_route_id = fields.Many2one(
"stock.location.route",
string="Inbound Route",
required=True,
domain=[("rma_selectable", "=", True)],
readonly=True,
states={"draft": [("readonly", False)]},
)
out_route_id = fields.Many2one(
"stock.location.route",
string="Outbound Route",
required=True,
domain=[("rma_selectable", "=", True)],
readonly=True,
states={"draft": [("readonly", False)]},
)
in_warehouse_id = fields.Many2one(
comodel_name="stock.warehouse",
string="Inbound Warehouse",
required=True,
readonly=True,
states={"draft": [("readonly", False)]},
default=lambda self: self._default_warehouse_id(),
)
out_warehouse_id = fields.Many2one(
comodel_name="stock.warehouse",
string="Outbound Warehouse",
required=True,
readonly=True,
states={"draft": [("readonly", False)]},
default=lambda self: self._default_warehouse_id(),
)
location_id = fields.Many2one(
comodel_name="stock.location",
string="Send To This Company Location",
required=True,
readonly=True,
states={"draft": [("readonly", False)]},
default=lambda self: self._default_location_id(),
)
customer_rma_id = fields.Many2one(
"rma.order.line", string="Customer RMA line", ondelete="cascade"
)
supplier_rma_line_ids = fields.One2many("rma.order.line", "customer_rma_id")
rma_line_count = fields.Integer(
compute="_compute_rma_line_count", string="# of RMA lines associated"
)
supplier_address_id = fields.Many2one(
comodel_name="res.partner",
readonly=True,
states={"draft": [("readonly", False)]},
string="Supplier Address",
help="Address of the supplier in case of Customer RMA operation " "dropship.",
)
customer_address_id = fields.Many2one(
comodel_name="res.partner",
readonly=True,
states={"draft": [("readonly", False)]},
string="Customer Address",
help="Address of the customer in case of Supplier RMA operation " "dropship.",
)
qty_to_receive = fields.Float(
string="Qty To Receive",
digits="Product Unit of Measure",
compute="_compute_qty_to_receive",
store=True,
)
qty_incoming = fields.Float(
string="Incoming Qty",
copy=False,
readonly=True,
digits="Product Unit of Measure",
compute="_compute_qty_incoming",
store=True,
)
qty_received = fields.Float(
string="Qty Received",
copy=False,
digits="Product Unit of Measure",
compute="_compute_qty_received",
store=True,
)
qty_to_deliver = fields.Float(
string="Qty To Deliver",
copy=False,
digits="Product Unit of Measure",
readonly=True,
compute="_compute_qty_to_deliver",
store=True,
)
qty_outgoing = fields.Float(
string="Outgoing Qty",
copy=False,
readonly=True,
digits="Product Unit of Measure",
compute="_compute_qty_outgoing",
store=True,
)
qty_delivered = fields.Float(
string="Qty Delivered",
copy=False,
digits="Product Unit of Measure",
readonly=True,
compute="_compute_qty_delivered",
store=True,
)
qty_to_supplier_rma = fields.Float(
string="Qty to send to Supplier RMA",
digits="Product Unit of Measure",
readonly=True,
compute="_compute_qty_supplier_rma",
store=True,
)
qty_in_supplier_rma = fields.Float(
string="Qty in Supplier RMA",
digits="Product Unit of Measure",
readonly=True,
compute="_compute_qty_supplier_rma",
store=True,
)
under_warranty = fields.Boolean(
string="Under Warranty?", readonly=True, states={"draft": [("readonly", False)]}
)
def _prepare_rma_line_from_stock_move(self, sm, lot=False):
if not self.type:
self.type = self._get_default_type()
if self.type == "customer":
operation = (
sm.product_id.rma_customer_operation_id
or sm.product_id.categ_id.rma_customer_operation_id
)
else:
operation = (
sm.product_id.rma_supplier_operation_id
or sm.product_id.categ_id.rma_supplier_operation_id
)
if not operation:
operation = self.env["rma.operation"].search(
[("type", "=", self.type)], limit=1
)
if not operation:
raise ValidationError(_("Please define an operation first."))
if not operation.in_route_id or not operation.out_route_id:
route = self.env["stock.location.route"].search(
[("rma_selectable", "=", True)], limit=1
)
if not route:
raise ValidationError(_("Please define an RMA route."))
if not operation.in_warehouse_id or not operation.out_warehouse_id:
warehouse = self.env["stock.warehouse"].search(
[("company_id", "=", self.company_id.id), ("lot_rma_id", "!=", False)],
limit=1,
)
if not warehouse:
raise ValidationError(
_("Please define a warehouse with a default RMA " "location.")
)
data = {
"product_id": sm.product_id.id,
"lot_id": lot and lot.id or False,
"origin": sm.picking_id.name or sm.name,
"uom_id": sm.product_uom.id,
"product_qty": sm.product_uom_qty,
"delivery_address_id": sm.picking_id.partner_id.id,
"operation_id": operation.id,
"receipt_policy": operation.receipt_policy,
"delivery_policy": operation.delivery_policy,
"in_warehouse_id": operation.in_warehouse_id.id or warehouse.id,
"out_warehouse_id": operation.out_warehouse_id.id or warehouse.id,
"in_route_id": operation.in_route_id.id or route.id,
"out_route_id": operation.out_route_id.id or route.id,
"location_id": (
operation.location_id.id
or operation.in_warehouse_id.lot_rma_id.id
or warehouse.lot_rma_id.id
),
}
return data
@api.onchange("reference_move_id")
def _onchange_reference_move_id(self):
self.ensure_one()
sm = self.reference_move_id
if not sm:
return
if sm.move_line_ids.lot_id:
if len(sm.move_line_ids.lot_id) > 1:
raise UserError(_("To manage lots use RMA groups."))
else:
data = self._prepare_rma_line_from_stock_move(
sm, lot=sm.move_line_ids.lot_id[0]
)
self.update(data)
else:
data = self._prepare_rma_line_from_stock_move(sm, lot=False)
self.update(data)
self._remove_other_data_origin("reference_move_id")
@api.constrains("reference_move_id", "partner_id")
def _check_move_partner(self):
for rec in self:
if (
rec.reference_move_id
and rec.reference_move_id.picking_id.partner_id != rec.partner_id
):
raise ValidationError(
_(
"RMA customer and originating stock move customer "
"doesn't match."
)
)
def _remove_other_data_origin(self, exception):
if not exception == "reference_move_id":
self.reference_move_id = False
return True
def action_rma_to_approve(self):
self.write({"state": "to_approve"})
for rec in self:
if rec.product_id.rma_approval_policy == "one_step":
rec.action_rma_approve()
return True
def action_rma_draft(self):
if self.in_shipment_count or self.out_shipment_count:
raise UserError(_("You cannot reset to draft a RMA with related pickings."))
self.write({"state": "draft"})
return True
def action_rma_approve(self):
self.write({"state": "approved"})
return True
def action_rma_done(self):
self.write({"state": "done"})
return True
@api.model
def create(self, vals):
if not vals.get("name") or vals.get("name") == "/":
if self.env.context.get("supplier"):
vals["name"] = self.env["ir.sequence"].next_by_code(
"rma.order.line.supplier"
)
else:
vals["name"] = self.env["ir.sequence"].next_by_code(
"rma.order.line.customer"
)
return super(RmaOrderLine, self).create(vals)
@api.onchange("product_id")
def _onchange_product_id(self):
result = {}
if not self.product_id:
return result
self.uom_id = self.product_id.uom_id.id
self.price_unit = self.product_id.standard_price
if not self.type:
self.type = self._get_default_type()
if self.type == "customer":
self.operation_id = (
self.product_id.rma_customer_operation_id
or self.product_id.categ_id.rma_customer_operation_id
)
else:
self.operation_id = (
self.product_id.rma_supplier_operation_id
or self.product_id.categ_id.rma_supplier_operation_id
)
if self.lot_id.product_id != self.product_id:
self.lot_id = False
return {"domain": {"lot_id": [("product_id", "=", self.product_id.id)]}}
@api.onchange("operation_id")
def _onchange_operation_id(self):
result = {}
if not self.operation_id:
return result
self.receipt_policy = self.operation_id.receipt_policy
self.delivery_policy = self.operation_id.delivery_policy
self.in_warehouse_id = self.operation_id.in_warehouse_id
self.out_warehouse_id = self.operation_id.out_warehouse_id
self.location_id = (
self.operation_id.location_id or self.in_warehouse_id.lot_rma_id
)
self.customer_to_supplier = self.operation_id.customer_to_supplier
self.supplier_to_customer = self.operation_id.supplier_to_customer
self.in_route_id = self.operation_id.in_route_id
self.out_route_id = self.operation_id.out_route_id
return result
@api.onchange("customer_to_supplier", "type")
def _onchange_receipt_policy(self):
if self.type == "supplier" and self.customer_to_supplier:
self.receipt_policy = "no"
elif self.type == "customer" and self.supplier_to_customer:
self.delivery_policy = "no"
@api.onchange("lot_id")
def _onchange_lot_id(self):
product = self.lot_id.product_id
if product:
self.product_id = product
self.uom_id = product.uom_id
def action_view_in_shipments(self):
action = self.env.ref("stock.action_picking_tree_all")
result = action.read()[0]
picking_ids = []
for line in self:
for move in line.move_ids:
if move.location_dest_id.usage == "internal":
picking_ids.append(move.picking_id.id)
else:
if line.customer_to_supplier:
picking_ids.append(move.picking_id.id)
shipments = list(set(picking_ids))
# choose the view_mode accordingly
if len(shipments) != 1:
result["domain"] = "[('id', 'in', " + str(shipments) + ")]"
elif len(shipments) == 1:
res = self.env.ref("stock.view_picking_form", False)
result["views"] = [(res and res.id or False, "form")]
result["res_id"] = shipments[0]
return result
def action_view_out_shipments(self):
action = self.env.ref("stock.action_picking_tree_all")
result = action.read()[0]
picking_ids = []
for line in self:
for move in line.move_ids:
if move.location_dest_id.usage in ("supplier", "customer"):
if not line.customer_to_supplier:
picking_ids.append(move.picking_id.id)
shipments = list(set(picking_ids))
# choose the view_mode accordingly
if len(shipments) != 1:
result["domain"] = "[('id', 'in', " + str(shipments) + ")]"
elif len(shipments) == 1:
res = self.env.ref("stock.view_picking_form", False)
result["views"] = [(res and res.id or False, "form")]
result["res_id"] = shipments[0]
return result
def action_view_rma_lines(self):
if self.type == "customer":
# from customer we link to supplier rma
action = self.env.ref("rma.action_rma_supplier_lines")
rma_lines = self.supplier_rma_line_ids.ids
res = self.env.ref("rma.view_rma_line_supplier_form", False)
else:
# from supplier we link to customer rma
action = self.env.ref("rma.action_rma_customer_lines")
rma_lines = self.customer_rma_id.ids
res = self.env.ref("rma.view_rma_line_form", False)
result = action.read()[0]
# choose the view_mode accordingly
if rma_lines and len(rma_lines) != 1:
result["domain"] = rma_lines.ids
elif len(rma_lines) == 1:
result["views"] = [(res and res.id or False, "form")]
result["res_id"] = rma_lines[0]
return result
@api.constrains("partner_id", "rma_id")
def _check_partner_id(self):
if self.rma_id and self.partner_id != self.rma_id.partner_id:
raise ValidationError(
_("Group partner and RMA's partner must be the same.")
)

46
rma/models/stock.py Normal file
View File

@@ -0,0 +1,46 @@
# Copyright (C) 2017-20 ForgeFlow S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
from odoo import api, fields, models
class StockPicking(models.Model):
_inherit = "stock.picking"
def action_assign(self):
"""When you try to bring back a product from a customer location,
it may happen that there is no quants available to perform the
picking."""
res = super(StockPicking, self).action_assign()
for picking in self:
for move in picking.move_lines:
if (
move.rma_line_id
and move.state == "confirmed"
and move.location_id.usage == "customer"
):
move.force_assign()
return res
class StockMove(models.Model):
_inherit = "stock.move"
rma_line_id = fields.Many2one(
"rma.order.line", string="RMA line", ondelete="restrict"
)
@api.model
def create(self, vals):
if vals.get("group_id"):
group = self.env["procurement.group"].browse(vals["group_id"])
if group.rma_line_id:
vals["rma_line_id"] = group.rma_line_id.id
return super(StockMove, self).create(vals)
def _action_assign(self):
res = super(StockMove, self)._action_assign()
for move in self:
if move.rma_line_id:
move.partner_id = move.rma_line_id.partner_id.id or False
return res

View File

@@ -0,0 +1,269 @@
# Copyright (C) 2017-20 ForgeFlow S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
from odoo import _, fields, models
class StockWarehouse(models.Model):
_inherit = "stock.warehouse"
lot_rma_id = fields.Many2one(
comodel_name="stock.location", string="RMA Location"
) # not readonly to have the possibility to edit location and
# propagate to rma rules (add a auto-update when writing this field?)
rma_cust_out_type_id = fields.Many2one(
comodel_name="stock.picking.type", string="RMA Customer out Type", readonly=True
)
rma_sup_out_type_id = fields.Many2one(
comodel_name="stock.picking.type", string="RMA Supplier out Type", readonly=True
)
rma_cust_in_type_id = fields.Many2one(
comodel_name="stock.picking.type", string="RMA Customer in Type", readonly=True
)
rma_sup_in_type_id = fields.Many2one(
comodel_name="stock.picking.type", string="RMA Supplier in Type", readonly=True
)
rma_in_this_wh = fields.Boolean(
string="RMA in this Warehouse",
help="If set, it will create RMA location, picking types and routes "
"for this warehouse.",
)
rma_customer_in_pull_id = fields.Many2one(
comodel_name="stock.rule", string="RMA Customer In Rule"
)
rma_customer_out_pull_id = fields.Many2one(
comodel_name="stock.rule", string="RMA Customer Out Rule"
)
rma_supplier_in_pull_id = fields.Many2one(
comodel_name="stock.rule", string="RMA Supplier In Rule"
)
rma_supplier_out_pull_id = fields.Many2one(
comodel_name="stock.rule", string="RMA Supplier Out Rule"
)
def _get_rma_types(self):
return [
self.rma_cust_out_type_id,
self.rma_sup_out_type_id,
self.rma_cust_in_type_id,
self.rma_sup_in_type_id,
]
def _rma_types_available(self):
self.ensure_one()
rma_types = self._get_rma_types()
for r_type in rma_types:
if not r_type:
return False
return True
def write(self, vals):
if "rma_in_this_wh" in vals:
if vals.get("rma_in_this_wh"):
for wh in self:
# RMA location:
if not wh.lot_rma_id:
wh.lot_rma_id = self.env["stock.location"].create(
{
"name": "RMA",
"usage": "internal",
"location_id": wh.lot_stock_id.id,
"company_id": wh.company_id.id,
}
)
# RMA types
if not wh._rma_types_available():
wh._create_rma_picking_types()
else:
for r_type in wh._get_rma_types():
if r_type:
r_type.active = True
# RMA rules:
wh._create_or_update_rma_pull()
else:
for wh in self:
for r_type in wh._get_rma_types():
if r_type:
r_type.active = False
# Unlink rules:
self.mapped("rma_customer_in_pull_id").unlink()
self.mapped("rma_customer_out_pull_id").unlink()
self.mapped("rma_supplier_in_pull_id").unlink()
self.mapped("rma_supplier_out_pull_id").unlink()
return super(StockWarehouse, self).write(vals)
def _create_rma_picking_types(self):
picking_type_obj = self.env["stock.picking.type"]
customer_loc, supplier_loc = self._get_partner_locations()
for wh in self:
other_pick_type = picking_type_obj.search(
[("warehouse_id", "=", wh.id)], order="sequence desc", limit=1
)
color = other_pick_type.color if other_pick_type else 0
max_sequence = other_pick_type and other_pick_type.sequence or 0
# create rma_cust_out_type_id:
rma_cust_out_type_id = picking_type_obj.create(
{
"name": _("Customer RMA Deliveries"),
"warehouse_id": wh.id,
"code": "outgoing",
"use_create_lots": True,
"use_existing_lots": False,
"sequence_id": self.env.ref("rma.seq_picking_type_rma_cust_out").id,
"default_location_src_id": wh.lot_rma_id.id,
"default_location_dest_id": customer_loc.id,
"sequence": max_sequence,
"color": color,
"sequence_code": "RMA → Customer",
}
)
# create rma_sup_out_type_id:
rma_sup_out_type_id = picking_type_obj.create(
{
"name": _("Supplier RMA Deliveries"),
"warehouse_id": wh.id,
"code": "outgoing",
"use_create_lots": True,
"use_existing_lots": False,
"sequence_id": self.env.ref("rma.seq_picking_type_rma_sup_out").id,
"default_location_src_id": wh.lot_rma_id.id,
"default_location_dest_id": supplier_loc.id,
"sequence": max_sequence,
"color": color,
"sequence_code": "Customer → RMA",
}
)
# create rma_cust_in_type_id:
rma_cust_in_type_id = picking_type_obj.create(
{
"name": _("Customer RMA Receipts"),
"warehouse_id": wh.id,
"code": "incoming",
"use_create_lots": True,
"use_existing_lots": False,
"sequence_id": self.env.ref("rma.seq_picking_type_rma_cust_in").id,
"default_location_src_id": customer_loc.id,
"default_location_dest_id": wh.lot_rma_id.id,
"sequence": max_sequence,
"color": color,
"sequence_code": "RMA -> Supplier",
}
)
# create rma_sup_in_type_id:
rma_sup_in_type_id = picking_type_obj.create(
{
"name": _("Supplier RMA Receipts"),
"warehouse_id": wh.id,
"code": "incoming",
"use_create_lots": True,
"use_existing_lots": False,
"sequence_id": self.env.ref("rma.seq_picking_type_rma_sup_in").id,
"default_location_src_id": supplier_loc.id,
"default_location_dest_id": wh.lot_rma_id.id,
"sequence": max_sequence,
"color": color,
"sequence_code": "Supplier -> RMA",
}
)
wh.write(
{
"rma_cust_out_type_id": rma_cust_out_type_id.id,
"rma_sup_out_type_id": rma_sup_out_type_id.id,
"rma_cust_in_type_id": rma_cust_in_type_id.id,
"rma_sup_in_type_id": rma_sup_in_type_id.id,
}
)
return True
def get_rma_rules_dict(self):
self.ensure_one()
rma_rules = dict()
customer_loc, supplier_loc = self._get_partner_locations()
rma_rules["rma_customer_in"] = {
"name": self._format_rulename(self, customer_loc, self.lot_rma_id.name),
"action": "pull",
"warehouse_id": self.id,
"company_id": self.company_id.id,
"location_src_id": customer_loc.id,
"location_id": self.lot_rma_id.id,
"procure_method": "make_to_stock",
"route_id": self.env.ref("rma.route_rma_customer").id,
"picking_type_id": self.rma_cust_in_type_id.id,
"active": True,
}
rma_rules["rma_customer_out"] = {
"name": self._format_rulename(self, self.lot_rma_id, customer_loc.name),
"action": "pull",
"warehouse_id": self.id,
"company_id": self.company_id.id,
"location_src_id": self.lot_rma_id.id,
"location_id": customer_loc.id,
"procure_method": "make_to_stock",
"route_id": self.env.ref("rma.route_rma_customer").id,
"picking_type_id": self.rma_cust_out_type_id.id,
"active": True,
}
rma_rules["rma_supplier_in"] = {
"name": self._format_rulename(self, supplier_loc, self.lot_rma_id.name),
"action": "pull",
"warehouse_id": self.id,
"company_id": self.company_id.id,
"location_src_id": supplier_loc.id,
"location_id": self.lot_rma_id.id,
"procure_method": "make_to_stock",
"route_id": self.env.ref("rma.route_rma_supplier").id,
"picking_type_id": self.rma_sup_in_type_id.id,
"active": True,
}
rma_rules["rma_supplier_out"] = {
"name": self._format_rulename(self, self.lot_rma_id, supplier_loc.name),
"action": "pull",
"warehouse_id": self.id,
"company_id": self.company_id.id,
"location_src_id": self.lot_rma_id.id,
"location_id": supplier_loc.id,
"procure_method": "make_to_stock",
"route_id": self.env.ref("rma.route_rma_supplier").id,
"picking_type_id": self.rma_sup_out_type_id.id,
"active": True,
}
return rma_rules
def _create_or_update_rma_pull(self):
rule_obj = self.env["stock.rule"]
for wh in self:
rules_dict = wh.get_rma_rules_dict()
if wh.rma_customer_in_pull_id:
wh.rma_customer_in_pull_id.write(rules_dict["rma_customer_in"])
else:
wh.rma_customer_in_pull_id = rule_obj.create(
rules_dict["rma_customer_in"]
)
if wh.rma_customer_out_pull_id:
wh.rma_customer_out_pull_id.write(rules_dict["rma_customer_out"])
else:
wh.rma_customer_out_pull_id = rule_obj.create(
rules_dict["rma_customer_out"]
)
if wh.rma_supplier_in_pull_id:
wh.rma_supplier_in_pull_id.write(rules_dict["rma_supplier_in"])
else:
wh.rma_supplier_in_pull_id = rule_obj.create(
rules_dict["rma_supplier_in"]
)
if wh.rma_supplier_out_pull_id:
wh.rma_supplier_out_pull_id.write(rules_dict["rma_supplier_out"])
else:
wh.rma_supplier_out_pull_id = rule_obj.create(
rules_dict["rma_supplier_out"]
)
return True
class StockLocationRoute(models.Model):
_inherit = "stock.location.route"
rma_selectable = fields.Boolean(string="Selectable on RMA Lines")

41
rma/readme/CONFIGURE.rst Normal file
View File

@@ -0,0 +1,41 @@
Security
--------
Go to Settings > Users and assign the appropiate permissions to users.
Different security groups grant distinct levels of access to the RMA features.
* Users in group "RMA Customer User" or "RMA Supplier User" can access to,
create and process RMA's associated to customers or suppliers respectively.
* Users in group "RMA Manager" can access to, create, approve and process RMA's
associated to both customers and suppliers.
RMA Approval Policy
-------------------
There are two RMA approval policies in product catogories:
* One step: Always auto-approve RMAs that only contain products within
categories with this policy.
* Two steps: A RMA order containing a product within a category with this
policy will request the RMA manager approval.
In order to change the approval policy of a product category follow the next
steps:
#. Go to *Inventory > Configuration > Products > Product Categories*.
#. Select one and change the field *RMA Approval Policy* to your convenience.
Other Settings
--------------
#. Go to RMA > Configuration > Settings > Return Merchandising
Authorization and select the option "Display 3 fields on rma: partner,
invoice address, delivery address" if needed.
#. Go to RMA > Configuration > Warehouse management > Warehouses and add
a default RMA location and RMA picking type for customers and suppliers RMA
picking type. In case the warehouse is configured to use routes, you need to
create at least one route per rma type with at least two push rules (one for
inbound another for outbound) it's very important to select the type of
operation supplier if we are moving in the company and customer if we are
moving out of the company.

View File

@@ -0,0 +1,8 @@
* Jordi Ballester Alomar <jordi.ballester@forgeflow.com>
* Aaron Henriquez <ahenriquez@forgeflow.com>
* Lois Rilo <lois.rilo@forgeflow.com>
* Bhavesh Odedra <bodedra@opensourceintegrators.com>
* Akim Juillerat <akim.juillerat@camptocamp.com>
* Alexandre Fayolle <alexandre.fayolle@camptocamp.com>
* Chafique Delli <chafique.delli@akretion.com>
* Héctor Villarreal <hector.villarreal@forgeflow.com>

View File

@@ -0,0 +1,18 @@
A Return Merchandise Authorization (RMA), is a part of the process of
returning a product in order to receive a refund, replacement, or repair
during the product's warranty period.
The purchaser of the product must contact the manufacturer (or distributor
or retailer) to obtain authorization to return the product.
The resulting RMA number must be displayed on or included in the returned
product's packaging.
The issuance of an RMA is a key gatekeeping moment in the reverse logistics
cycle, providing the vendor with a final opportunity to diagnose and correct
the customer's problem with the product (such as improper installation or
configuration) before the customer permanently relinquishes ownership
of the product to the manufacturer, commonly referred to as a return.
As returns are costly for the vendor and inconvenient for the customer,
any return that can be prevented benefits both parties.

6
rma/readme/ROADMAP.rst Normal file
View File

@@ -0,0 +1,6 @@
* Picking operations report in customer RMA dropshipping case is showing
"Vendor Address" while it should be "Customer Address".
* Dropshipping always counted as a delivery on the smart buttons.
* Uninstall hook.
* Constraints instead of required fields on rma.order.line.
* Rename type field on rma.order and rma.order.line

12
rma/readme/USAGE.rst Normal file
View File

@@ -0,0 +1,12 @@
RMA are accessible though Inventory menu. There's four menus, divided by type.
Users can access to the list of RMA or RMA lines.
Create an RMA:
#. Select a partner. Enter RMA lines associated to an existing picking, or
manually.
#. Request approval and approve.
#. Click on RMA Lines button.
#. Click on more and select an option: "Receive products", "Create Delivery
Order".
#. Go back to the RMA. Set the RMA to done if not further action is required.

21
rma/report/rma_report.xml Normal file
View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<report
id="rma_order_line_report"
string="RMA"
model="rma.order.line"
report_type="qweb-pdf"
file="rma.report_rma_order_line"
name="rma.report_rma_order_line"
/>
<report
id="rma_order_report"
string="RMA Group"
model="rma.order"
report_type="qweb-pdf"
file="rma.report_rma_order"
name="rma.report_rma_order"
/>
</data>
</odoo>

View File

@@ -0,0 +1,200 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_rma_order_line_document">
<t t-call="web.external_layout">
<t t-set="doc" t-value="doc.with_context({'lang':doc.partner_id.lang})" />
<div class="page">
<div class="oe_structure"/>
<div class="row">
<div class="col-6">
<t t-if="(doc.customer_to_supplier==False and doc.type=='customer') or (doc.supplier_to_customer==False and doc.type=='supplier')">
<strong>Shipping address:</strong>
<div class="mt8">
<div t-field="doc.in_warehouse_id.partner_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": True, "phone_icons": True}'/>
<p t-if="doc.in_warehouse_id.partner_id.vat">VAT: <span t-field="doc.in_warehouse_id.partner_id.vat"/></p>
</div>
</t>
<div t-if="doc.customer_to_supplier and doc.type=='customer'" class="mt8">
<strong>Shipping address:</strong>
<div t-field="doc.supplier_address_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": True, "phone_icons": True}'/>
<p t-if="doc.supplier_address_id.vat">VAT: <span t-field="doc.supplier_address_id.vat"/></p>
</div>
<div t-if="doc.supplier_to_customer and doc.type=='supplier'" class="mt8">
<strong>Shipping address:</strong>
<div t-field="doc.customer_address_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": True, "phone_icons": True}'/>
<p t-if="doc.customer_address_id.vat">VAT: <span t-field="doc.customer_address_id.vat"/></p>
</div>
</div>
<div class="col-3 offset-1">
<strong t-if="doc.type=='customer'">Customer:</strong>
<strong t-if="doc.type=='supplier'">Supplier:</strong>
<div t-field="doc.partner_id"
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}' />
</div>
</div>
<h2 class="mt32">
<span t-field="doc.name"/>
</h2>
<div class="row mt32 mb32" id="informations">
<div t-if="doc.origin" class="col-3">
<strong>Origin Reference:</strong>
<p t-field="doc.origin"/>
</div>
<div t-if="doc.create_date" class="col-3">
<strong>Date Ordered:</strong>
<p t-field="doc.create_date"/>
</div>
<div t-if="doc.requested_by.name" class="col-3">
<strong>Contact Person:</strong>
<p t-field="doc.requested_by.name"/>
</div>
<div name="operation_id" t-if="doc.operation_id" class="col-3">
<strong>Operation:</strong>
<p t-field="doc.operation_id.name"/>
</div>
</div>
<table class="table table-condensed">
<thead>
<tr>
<th>Product</th>
<th class="text-right">Quantity</th>
<th class="text-right">Unit Price</th>
</tr>
</thead>
<tbody>
<tr>
<td><span t-field="doc.product_id"/></td>
<td class="text-right">
<span t-field="doc.product_qty"/>
<span t-field="doc.uom_id" groups="uom.group_uom"/>
</td>
<td class="text-right">
<span t-field="doc.price_unit" t-options='{"widget": "monetary", "display_currency": doc.currency_id}'/>
</td>
</tr>
</tbody>
</table>
<div t-if="doc.description">
<strong><p>Description</p></strong>
<span t-field="doc.description"></span>
</div>
<div t-if="doc.conditions">
<strong><p>Term and conditions</p></strong>
<span t-field="doc.conditions"></span>
</div>
</div>
</t>
</template>
<template id="report_rma_order_line">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="rma.report_rma_order_line_document" t-lang="doc.partner_id.lang"/>
</t>
</t>
</template>
<template id="report_rma_order_document">
<t t-call="web.external_layout">
<t t-set="doc" t-value="doc.with_context({'lang':doc.partner_id.lang})" />
<div class="page">
<div class="oe_structure"/>
<div class="row">
<div class="col-6">
<t t-if="(doc.customer_to_supplier==False and doc.type=='customer') or (doc.supplier_to_customer==False and doc.type=='supplier')">
<strong>Shipping address:</strong>
<div class="mt8">
<div t-field="doc.in_warehouse_id.partner_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": True, "phone_icons": True}'/>
<p t-if="doc.in_warehouse_id.partner_id.vat">VAT: <span t-field="doc.in_warehouse_id.partner_id.vat"/></p>
</div>
</t>
<div t-if="doc.customer_to_supplier and doc.type=='customer'" class="mt8">
<strong>Shipping address:</strong>
<div t-field="doc.supplier_address_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": True, "phone_icons": True}'/>
<p t-if="doc.supplier_address_id.vat">VAT: <span t-field="doc.supplier_address_id.vat"/></p>
</div>
<div t-if="doc.supplier_to_customer and doc.type=='supplier'" class="mt8">
<strong>Shipping address:</strong>
<div t-field="doc.customer_address_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": True, "phone_icons": True}'/>
<p t-if="doc.customer_address_id.vat">VAT: <span t-field="doc.customer_address_id.vat"/></p>
</div>
</div>
<div class="col-3 offset-1">
<strong t-if="doc.type=='customer'">Customer:</strong>
<strong t-if="doc.type=='supplier'">Supplier:</strong>
<div t-field="doc.partner_id"
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}' />
</div>
</div>
<h2 class="mt32">
<span t-field="doc.name"/>
</h2>
<div class="row mt32 mb32" id="informations">
<div t-if="doc.reference" class="col-3">
<strong>Reference:</strong>
<p t-field="doc.reference"/>
</div>
<div t-if="doc.date_rma" class="col-3">
<strong>Date Ordered:</strong>
<p t-field="doc.date_rma"/>
</div>
<div t-if="doc.requested_by.name" class="col-3">
<strong>Contact Person:</strong>
<p t-field="doc.requested_by.name"/>
</div>
</div>
<table class="table table-condensed">
<thead>
<tr>
<th>Origin</th>
<th>Operation</th>
<th>Product</th>
<th class="text-right">Quantity</th>
<th class="text-right">Unit Price</th>
</tr>
</thead>
<tbody>
<tr t-foreach="doc.rma_line_ids" t-as="l">
<td><span t-field="l.origin"/></td>
<td><span t-field="l.operation_id.name"/></td>
<td><span t-field="l.product_id"/></td>
<td class="text-right">
<span t-field="l.product_qty"/>
<span t-field="l.uom_id" groups="product.group_uom"/>
</td>
<td class="text-right">
<span t-field="l.price_unit"/>
</td>
</tr>
</tbody>
</table>
<div t-if="doc.comment">
<strong><p>Additional Information</p></strong>
<span t-field="doc.comment"></span>
</div>
</div>
</t>
</template>
<template id="report_rma_order">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="rma.report_rma_order_document" t-lang="doc.partner_id.lang"/>
</t>
</t>
</template>
</odoo>

View File

@@ -0,0 +1,11 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_rma_user_customer,rma.order.customer.user,model_rma_order,group_rma_customer_user,1,1,1,1
access_rma_user_supplier,rma.order.supplier.user,model_rma_order,group_rma_supplier_user,1,1,1,1
access_rma_manager,rma.order.manager,model_rma_order,group_rma_manager,1,1,1,1
access_rma_line_customer_user,rma.order.line.customer.user,model_rma_order_line,group_rma_customer_user,1,1,1,1
access_rma_line_supplier_user,rma.order.line.customer.supplier,model_rma_order_line,group_rma_supplier_user,1,1,1,1
access_rma_line_manager,rma.order.line,model_rma_order_line,group_rma_manager,1,1,1,1
access_rma_operation_manager,access_rma_operation,model_rma_operation,group_rma_manager,1,1,1,1
access_rma_operation_customer_user,access_rma_operation,model_rma_operation,group_rma_customer_user,1,0,0,0
access_rma_operation_supplier_user,access_rma_operation,model_rma_operation,group_rma_supplier_user,1,0,0,0
access_rma_order_line_user,access_rma_order_line,model_rma_order_line,base.group_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_rma_user_customer rma.order.customer.user model_rma_order group_rma_customer_user 1 1 1 1
3 access_rma_user_supplier rma.order.supplier.user model_rma_order group_rma_supplier_user 1 1 1 1
4 access_rma_manager rma.order.manager model_rma_order group_rma_manager 1 1 1 1
5 access_rma_line_customer_user rma.order.line.customer.user model_rma_order_line group_rma_customer_user 1 1 1 1
6 access_rma_line_supplier_user rma.order.line.customer.supplier model_rma_order_line group_rma_supplier_user 1 1 1 1
7 access_rma_line_manager rma.order.line model_rma_order_line group_rma_manager 1 1 1 1
8 access_rma_operation_manager access_rma_operation model_rma_operation group_rma_manager 1 1 1 1
9 access_rma_operation_customer_user access_rma_operation model_rma_operation group_rma_customer_user 1 0 0 0
10 access_rma_operation_supplier_user access_rma_operation model_rma_operation group_rma_supplier_user 1 0 0 0
11 access_rma_order_line_user access_rma_order_line model_rma_order_line base.group_user 1 0 0 0

73
rma/security/rma.xml Normal file
View File

@@ -0,0 +1,73 @@
<?xml version="1.0"?>
<odoo>
<data noupdate="0">
<record model="ir.module.category" id="module_category_rma">
<field name="name">RMA</field>
<field name="parent_id" ref="base.module_category_sales"/>
<field name="sequence">30</field>
</record>
<record id="group_rma_customer_user" model="res.groups">
<field name="name">RMA Customer User</field>
<field name="implied_ids" eval="[(4, ref('stock.group_stock_user'))]"/>
<field name="category_id" ref="module_category_rma"/>
</record>
<record id="group_rma_supplier_user" model="res.groups">
<field name="name">RMA Supplier User</field>
<field name="implied_ids" eval="[(4, ref('stock.group_stock_user'))]"/>
<field name="category_id" ref="module_category_rma"/>
</record>
<record id="group_rma_manager" model="res.groups">
<field name="name">RMA Manager</field>
<field name="implied_ids"
eval="[(4, ref('rma.group_rma_customer_user')),
(4, ref('rma.group_rma_supplier_user'))]"/>
<field name="users" eval="[(4, ref('base.user_root'))]"/>
<field name="category_id" ref="module_category_rma"/>
</record>
<record id="group_rma_delivery_invoice_address" model="res.groups">
<field name="name">Addresses in RMA</field>
<field name="category_id" ref="module_category_rma"/>
<field name="users" eval="[(4, ref('base.user_root'))]"/>
</record>
<record id="group_rma_groups" model="res.groups">
<field name="name">RMA Groups</field>
<field name="category_id" ref="module_category_rma"/>
</record>
<record id="stock.group_stock_manager" model="res.groups">
<field name="implied_ids" eval="[(4, ref('group_rma_manager'))]"/>
</record>
<record id="stock.group_stock_user" model="res.groups">
<field name="implied_ids"
eval="[(4, ref('group_rma_customer_user')),
(4, ref('group_rma_supplier_user'))]"/>
</record>
<record model="ir.rule" id="rma_order_rule">
<field name="name">rma order multi-company</field>
<field name="model_id" search="[('model','=','rma.order')]" model="ir.model"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<record model="ir.rule" id="rma_order_line_rule">
<field name="name">rma order line multi-company</field>
<field name="model_id" search="[('model','=','rma.order.line')]" model="ir.model"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<record model="ir.rule" id="rma_operation_rule">
<field name="name">rma operation multi-company</field>
<field name="model_id" search="[('model','=','rma.operation')]" model="ir.model"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
</data>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

4
rma/tests/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
# Copyright (C) 2017-20 ForgeFlow S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
from . import test_rma

794
rma/tests/test_rma.py Normal file
View File

@@ -0,0 +1,794 @@
# © 2017 ForgeFlow
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
from odoo.exceptions import ValidationError
from odoo.tests import common
class TestRma(common.SavepointCase):
""" Test the routes and the quantities """
@classmethod
def setUpClass(cls):
super(TestRma, cls).setUpClass()
cls.rma_make_picking = cls.env["rma_make_picking.wizard"]
cls.make_supplier_rma = cls.env["rma.order.line.make.supplier.rma"]
cls.rma_add_stock_move = cls.env["rma_add_stock_move"]
cls.stockpicking = cls.env["stock.picking"]
cls.rma = cls.env["rma.order"]
cls.rma_line = cls.env["rma.order.line"]
cls.rma_op = cls.env["rma.operation"]
cls.product_product_model = cls.env["product.product"]
cls.rma_cust_replace_op_id = cls.env.ref("rma.rma_operation_customer_replace")
cls.rma_sup_replace_op_id = cls.env.ref("rma.rma_operation_supplier_replace")
cls.rma_ds_replace_op_id = cls.env.ref("rma.rma_operation_ds_replace")
cls.category = cls._create_product_category(
"one_step", cls.rma_cust_replace_op_id, cls.rma_sup_replace_op_id
)
cls.product_id = cls._create_product("PT0")
cls.product_1 = cls._create_product("PT1")
cls.product_2 = cls._create_product("PT2")
cls.product_3 = cls._create_product("PT3")
cls.uom_unit = cls.env.ref("uom.product_uom_unit")
cls.env.user.company_id.group_rma_delivery_address = True
cls.env.user.company_id.group_rma_lines = True
cls.partner_id = cls.env.ref("base.res_partner_2")
cls.stock_location = cls.env.ref("stock.stock_location_stock")
wh = cls.env.ref("stock.warehouse0")
cls.stock_rma_location = wh.lot_rma_id
cls.customer_location = cls.env.ref("stock.stock_location_customers")
cls.supplier_location = cls.env.ref("stock.stock_location_suppliers")
cls.product_uom_id = cls.env.ref("uom.product_uom_unit")
# Customer RMA:
products2move = [(cls.product_1, 3), (cls.product_2, 5), (cls.product_3, 2)]
cls.rma_customer_id = cls._create_rma_from_move(
products2move, "customer", cls.env.ref("base.res_partner_2"), dropship=False
)
# Dropship:
cls.rma_droship_id = cls._create_rma_from_move(
products2move,
"customer",
cls.env.ref("base.res_partner_2"),
dropship=True,
supplier_address_id=cls.env.ref("base.res_partner_3"),
)
# Supplier RMA:
cls.rma_supplier_id = cls._create_rma_from_move(
products2move, "supplier", cls.env.ref("base.res_partner_2"), dropship=False
)
@classmethod
def _create_product_category(
cls, rma_approval_policy, rma_customer_operation_id, rma_supplier_operation_id
):
return cls.env["product.category"].create(
{
"name": "Test category",
"rma_approval_policy": rma_approval_policy,
"rma_customer_operation_id": rma_customer_operation_id.id,
"rma_supplier_operation_id": rma_supplier_operation_id.id,
}
)
@classmethod
def _create_product(cls, name):
return cls.product_product_model.create(
{"name": name, "categ_id": cls.category.id, "type": "product"}
)
@classmethod
def _create_picking(cls, partner):
return cls.stockpicking.create(
{
"partner_id": partner.id,
"picking_type_id": cls.env.ref("stock.picking_type_in").id,
"location_id": cls.stock_location.id,
"location_dest_id": cls.supplier_location.id,
}
)
@classmethod
def _create_rma_from_move(
cls, products2move, r_type, partner, dropship, supplier_address_id=None
):
picking_in = cls._create_picking(partner)
moves = []
if r_type == "customer":
for item in products2move:
move_values = cls._prepare_move(
item[0],
item[1],
cls.stock_location,
cls.customer_location,
picking_in,
)
moves.append(cls.env["stock.move"].create(move_values))
else:
for item in products2move:
move_values = cls._prepare_move(
item[0],
item[1],
cls.supplier_location,
cls.stock_rma_location,
picking_in,
)
moves.append(cls.env["stock.move"].create(move_values))
# Create the RMA from the stock_move
rma_id = cls.rma.create(
{
"reference": "0001",
"type": r_type,
"partner_id": partner.id,
"company_id": cls.env.ref("base.main_company").id,
}
)
for move in moves:
if r_type == "customer":
wizard = cls.rma_add_stock_move.new(
{
"move_ids": [(4, move.id)],
"rma_id": rma_id.id,
"partner_id": move.partner_id.id,
}
)
wizard.with_context(
{
"move_ids": [(4, move.id)],
"reference_move_id": move.id,
"customer": True,
"active_ids": rma_id.id,
"partner_id": move.partner_id.id,
"active_model": "rma.order",
}
).default_get([str(move.id), str(cls.partner_id.id)])
data = wizard.with_context(
customer=1
)._prepare_rma_line_from_stock_move(move)
else:
wizard = cls.rma_add_stock_move.new(
{
"move_ids": [(4, move.id)],
"rma_id": rma_id.id,
"partner_id": move.partner_id.id,
}
)
wizard.with_context(
{
"move_ids": [(4, move.id)],
"reference_move_id": move.id,
"active_ids": rma_id.id,
"partner_id": move.partner_id.id,
"active_model": "rma.order",
}
).default_get([str(move.id), str(cls.partner_id.id)])
data = wizard._prepare_rma_line_from_stock_move(move)
data["type"] = "supplier"
if dropship:
data.update(
customer_to_supplier=dropship,
operation_id=cls.rma_ds_replace_op_id.id,
supplier_address_id=supplier_address_id.id,
)
cls.line = cls.rma_line.create(data)
cls.line._onchange_product_id()
cls.line._onchange_operation_id()
cls.line.action_rma_to_approve()
rma_id._get_default_type()
rma_id._compute_in_shipment_count()
rma_id._compute_out_shipment_count()
rma_id._compute_supplier_line_count()
rma_id._compute_line_count()
rma_id.action_view_in_shipments()
rma_id.action_view_out_shipments()
rma_id.action_view_lines()
rma_id.partner_id.action_open_partner_rma()
rma_id.partner_id._compute_rma_line_count()
return rma_id
@classmethod
def _prepare_move(cls, product, qty, src, dest, picking_in):
location_id = src.id
return {
"name": product.name,
"partner_id": picking_in.partner_id.id,
"origin": picking_in.name,
"company_id": picking_in.picking_type_id.warehouse_id.company_id.id,
"product_id": product.id,
"product_uom": product.uom_id.id,
"state": "draft",
"product_uom_qty": qty,
"location_id": location_id,
"location_dest_id": dest.id,
"move_line_ids": [
(
0,
0,
{
"product_id": product.id,
"product_uom_id": product.uom_id.id,
"qty_done": qty,
"location_id": location_id,
"location_dest_id": dest.id,
"package_id": False,
"owner_id": False,
"lot_id": False,
},
)
],
"picking_id": picking_in.id,
}
def _check_equal_quantity(self, qty1, qty2, msg):
self.assertEquals(qty1, qty2, msg)
def test_01_rma_order_line(self):
for line in self.rma_customer_id.rma_line_ids:
line.with_context(
{"default_rma_id": line.rma_id.id}
)._default_warehouse_id()
line._default_location_id()
line._onchange_delivery_address()
line._compute_in_shipment_count()
line._compute_out_shipment_count()
# check assert if call reference_move_id onchange
self.assertEquals(line.product_id, line.reference_move_id.product_id)
self.assertEquals(line.product_qty, line.reference_move_id.product_uom_qty)
self.assertEquals(
line.location_id.location_id, line.reference_move_id.location_id
)
self.assertEquals(line.origin, line.reference_move_id.picking_id.name)
self.assertEquals(
line.delivery_address_id, line.reference_move_id.picking_partner_id
)
self.assertEquals(
line.qty_to_receive, line.reference_move_id.product_uom_qty
)
line._onchange_product_id()
line._onchange_operation_id()
# check assert if call operation_id onchange
self.assertEquals(line.operation_id.receipt_policy, line.receipt_policy)
data = {"customer_to_supplier": line.customer_to_supplier}
line = self.rma_line.new(data)
line._onchange_receipt_policy()
data = {"lot_id": line.lot_id.id}
line = self.rma_line.new(data)
line._onchange_lot_id()
line.action_view_in_shipments()
line.action_view_out_shipments()
self.rma_customer_id.action_view_supplier_lines()
with self.assertRaises(ValidationError):
line.rma_id.partner_id = self.partner_id.id
self.rma_customer_id.rma_line_ids[0].partner_id = self.env.ref(
"base.res_partner_3"
).id
self.rma_customer_id.action_view_supplier_lines()
def test_02_customer_rma(self):
self.rma_customer_id.rma_line_ids.action_rma_to_approve()
wizard = self.rma_make_picking.with_context(
{
"active_ids": self.rma_customer_id.rma_line_ids.ids,
"active_model": "rma.order.line",
"picking_type": "incoming",
"active_id": 1,
}
).create({})
wizard._create_picking()
res = self.rma_customer_id.rma_line_ids.action_view_in_shipments()
self.assertTrue("res_id" in res, "Incorrect number of pickings" "created")
picking = self.env["stock.picking"].browse(res["res_id"])
self.assertEquals(len(picking), 1, "Incorrect number of pickings created")
moves = picking.move_lines
self.assertEquals(len(moves), 3, "Incorrect number of moves created")
lines = self.rma_customer_id.rma_line_ids
self.assertEquals(
list(set(lines.mapped("qty_received"))), [0], "Wrong qty received"
)
self.assertEquals(
list(set(lines.mapped("qty_to_deliver"))), [0], "Wrong qty to deliver"
)
self.assertEquals(
list(set(lines.mapped("qty_outgoing"))), [0], "Wrong qty outgoing"
)
self.assertEquals(
list(set(lines.mapped("qty_delivered"))), [0], "Wrong qty delivered"
)
# product specific
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_to_receive,
3,
"Wrong qty to receive",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_incoming,
3,
"Wrong qty incoming",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_to_receive,
5,
"Wrong qty to receive",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_incoming,
5,
"Wrong qty incoming",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_to_receive,
2,
"Wrong qty to receive",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_incoming,
2,
"Wrong qty incoming",
)
picking.action_assign()
for mv in picking.move_lines:
mv.quantity_done = mv.product_uom_qty
picking.action_done()
lines = self.rma_customer_id.rma_line_ids
self.assertEquals(
list(set(lines.mapped("qty_to_receive"))), [0], "Wrong qty to_receive"
)
self.assertEquals(
list(set(lines.mapped("qty_incoming"))), [0], "Wrong qty incoming"
)
self.assertEquals(
list(set(lines.mapped("qty_outgoing"))), [0], "Wrong qty outgoing"
)
self.assertEquals(
list(set(lines.mapped("qty_delivered"))), [0], "Wrong qty delivered"
)
# product specific
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_received,
3,
"Wrong qty received",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_to_deliver,
3,
"Wrong qty to_deliver",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_received,
5,
"Wrong qty received",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_to_deliver,
5,
"Wrong qty to_deliver",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_received,
2,
"Wrong qty received",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_to_deliver,
2,
"Wrong qty to_deliver",
)
wizard = self.rma_make_picking.with_context(
{
"active_id": 1,
"active_ids": self.rma_customer_id.rma_line_ids.ids,
"active_model": "rma.order.line",
"picking_type": "outgoing",
}
).create({})
wizard._create_picking()
res = self.rma_customer_id.rma_line_ids.action_view_out_shipments()
self.assertTrue("res_id" in res, "Incorrect number of pickings" "created")
picking = self.env["stock.picking"].browse(res["res_id"])
moves = picking.move_lines
self.assertEquals(len(moves), 3, "Incorrect number of moves created")
lines = self.rma_customer_id.rma_line_ids
self.assertEquals(
list(set(lines.mapped("qty_to_receive"))), [0], "Wrong qty to_receive"
)
self.assertEquals(
list(set(lines.mapped("qty_incoming"))), [0], "Wrong qty incoming"
)
self.assertEquals(
list(set(lines.mapped("qty_delivered"))), [0], "Wrong qty delivered"
)
# product specific
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_to_deliver,
3,
"Wrong qty to_deliver",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_outgoing,
3,
"Wrong qty outgoing",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_to_deliver,
5,
"Wrong qty to_deliver",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_outgoing,
5,
"Wrong qty outgoing",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_to_deliver,
2,
"Wrong qty to_deliver",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_outgoing,
2,
"Wrong qty outgoing",
)
picking.action_assign()
for mv in picking.move_lines:
mv.quantity_done = mv.product_uom_qty
picking.action_done()
lines = self.rma_customer_id.rma_line_ids
self.assertEquals(
list(set(lines.mapped("qty_to_receive"))), [0], "Wrong qty to_receive"
)
self.assertEquals(
list(set(lines.mapped("qty_incoming"))), [0], "Wrong qty incoming"
)
self.assertEquals(
list(set(lines.mapped("qty_outgoing"))), [0], "Wrong qty_outgoing"
)
# product specific
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_received,
3,
"Wrong qty_received",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_delivered,
3,
"Wrong qty_delivered",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_received,
5,
"Wrong qty_received",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_delivered,
5,
"Wrong qty_delivered",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_received,
2,
"Wrong qty_received",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_delivered,
2,
"Wrong qty_delivered",
)
self.line.action_rma_done()
self.assertEquals(self.line.state, "done", "Wrong State")
self.rma_customer_id.action_view_in_shipments()
self.rma_customer_id.action_view_out_shipments()
self.rma_customer_id.action_view_lines()
# DROPSHIP
def test_03_dropship(self):
for line in self.rma_droship_id.rma_line_ids:
line.operation_id = self.rma_ds_replace_op_id
line._onchange_operation_id()
line._onchange_delivery_address()
line.action_rma_to_approve()
line.action_rma_approve()
wizard = self.rma_make_picking.with_context(
{
"active_id": 1,
"active_ids": self.rma_droship_id.rma_line_ids.ids,
"active_model": "rma.order.line",
"picking_type": "incoming",
}
).create({})
wizard._create_picking()
res = self.rma_droship_id.rma_line_ids.action_view_in_shipments()
self.assertTrue("res_id" in res, "Incorrect number of pickings" "created")
picking = self.env["stock.picking"].browse(res["res_id"])
self.assertEquals(len(picking), 1, "Incorrect number of pickings created")
moves = picking.move_lines
self.assertEquals(len(moves), 3, "Incorrect number of moves created")
wizard = self.make_supplier_rma.with_context(
{
"active_ids": self.rma_droship_id.rma_line_ids.ids,
"active_model": "rma.order.line",
"active_id": 1,
}
).create({})
res = wizard.make_supplier_rma()
supplier_rma = self.rma.browse(res["res_id"])
lines = supplier_rma.rma_line_ids
self.assertEquals(
list(set(lines.mapped("qty_received"))), [0], "Wrong qty_received"
)
self.assertEquals(
list(set(lines.mapped("qty_outgoing"))), [0], "Wrong qty_outgoing"
)
self.assertEquals(
list(set(lines.mapped("qty_delivered"))), [0], "qty_delivered"
)
# product specific
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_to_deliver,
3,
"Wrong qty_to_deliver",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_to_deliver,
5,
"Wrong qty_to_deliver",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_to_deliver,
2,
"Wrong qty_to_deliver",
)
lines = self.rma_droship_id.rma_line_ids
self._check_equal_quantity(
lines.filtered(
lambda l: l.product_id == self.product_1
).qty_in_supplier_rma,
3,
"Wrong qty_in_supplier_rma",
)
self._check_equal_quantity(
lines.filtered(
lambda l: l.product_id == self.product_2
).qty_in_supplier_rma,
5,
"Wrong qty_in_supplier_rma",
)
self._check_equal_quantity(
lines.filtered(
lambda l: l.product_id == self.product_3
).qty_in_supplier_rma,
2,
"Wrong qty_in_supplier_rma",
)
self.assertEquals(
list(set(lines.mapped("qty_to_supplier_rma"))),
[0],
"Wrong qty_to_supplier_rma",
)
for line in self.rma_droship_id.rma_line_ids:
line.action_rma_done()
self.assertEquals(line.mapped("state"), ["done"], "Wrong State")
# Supplier RMA
def test_04_supplier_rma(self):
self.rma_supplier_id.rma_line_ids.action_rma_to_approve()
self.rma_supplier_id.rma_line_ids.operation_id = self.rma_sup_replace_op_id
self.rma_supplier_id.rma_line_ids._onchange_operation_id()
self.rma_supplier_id.rma_line_ids._onchange_delivery_address()
wizard = self.rma_make_picking.with_context(
{
"active_ids": self.rma_supplier_id.rma_line_ids.ids,
"active_model": "rma.order.line",
"picking_type": "outgoing",
"active_id": 2,
}
).create({})
wizard._create_picking()
res = self.rma_supplier_id.rma_line_ids.action_view_out_shipments()
self.assertTrue("res_id" in res, "Incorrect number of pickings" "created")
picking = self.env["stock.picking"].browse(res["res_id"])
moves = picking.move_lines
self.assertEquals(len(moves), 3, "Incorrect number of moves created")
lines = self.rma_supplier_id.rma_line_ids
self.assertEquals(
list(set(lines.mapped("qty_received"))), [0], "Wrong qty_received"
)
self.assertEquals(
list(set(lines.mapped("qty_delivered"))), [0], "qty_delivered"
)
# product specific
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_to_receive,
3,
"Wrong qty_to_receive",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_to_deliver,
3,
"Wrong qty_to_deliver",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_to_receive,
5,
"Wrong qty_to_receive",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_to_deliver,
5,
"Wrong qty_to_deliver",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_to_receive,
2,
"Wrong qty_to_receive",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_to_deliver,
2,
"Wrong qty_to_deliver",
)
self.assertEquals(
list(set(lines.mapped("qty_incoming"))), [0], "Wrong qty_incoming"
)
picking.action_assign()
for mv in picking.move_lines:
mv.quantity_done = mv.product_uom_qty
picking.action_done()
self.assertEquals(
list(set(lines.mapped("qty_incoming"))), [0], "Wrong qty_incoming"
)
self.assertEquals(
list(set(lines.mapped("qty_to_deliver"))), [0], "Wrong qty_to_deliver"
)
self.assertEquals(
list(set(lines.mapped("qty_received"))), [0], "Wrong qty_received"
)
self.assertEquals(list(set(lines.mapped("qty_outgoing"))), [0], "qty_outgoing")
# product specific
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_delivered,
3,
"Wrong qty_delivered",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_to_receive,
3,
"Wrong qty_to_receive",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_received,
0,
"Wrong qty_received",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_delivered,
5,
"Wrong qty_delivered",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_received,
0,
"Wrong qty_received",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_delivered,
2,
"Wrong qty_delivered",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_received,
0,
"Wrong qty_received",
)
wizard = self.rma_make_picking.with_context(
{
"active_id": 1,
"active_ids": self.rma_supplier_id.rma_line_ids.ids,
"active_model": "rma.order.line",
"picking_type": "incoming",
}
).create({})
wizard._create_picking()
res = self.rma_supplier_id.rma_line_ids.action_view_in_shipments()
self.assertTrue("res_id" in res, "Incorrect number of pickings" "created")
pickings = self.env["stock.picking"].browse(res["res_id"])
self.assertEquals(len(pickings), 1, "Incorrect number of pickings created")
picking_in = pickings[0]
moves = picking_in.move_lines
self.assertEquals(len(moves), 3, "Incorrect number of moves created")
lines = self.rma_supplier_id.rma_line_ids
self.assertEquals(
list(set(lines.mapped("qty_to_deliver"))), [0], "qty_to_deliver"
)
# product specific
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_to_receive,
3,
"Wrong qty_to_receive",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_to_receive,
5,
"Wrong qty_to_receive",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_to_receive,
2,
"Wrong qty_to_receive",
)
picking_in.action_confirm()
picking_in.action_assign()
for mv in picking_in.move_line_ids:
mv.qty_done = mv.product_uom_qty
picking_in.action_done()
self.assertEquals(
list(set(lines.mapped("qty_outgoing"))), [0], "Wrong qty_outgoing"
)
self.assertEquals(
list(set(lines.mapped("qty_incoming"))), [0], "Wrong qty_incoming"
)
self.assertEquals(
list(set(lines.mapped("qty_to_deliver"))), [0], "qty_to_deliver"
)
# product specific
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_delivered,
3,
"Wrong qty_delivered",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_1).qty_received,
3,
"Wrong qty_received",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_delivered,
5,
"Wrong qty_delivered",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_2).qty_received,
5,
"Wrong qty_received",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_delivered,
2,
"Wrong qty_delivered",
)
self._check_equal_quantity(
lines.filtered(lambda l: l.product_id == self.product_3).qty_received,
2,
"Wrong qty_received",
)
for line in self.rma_supplier_id.rma_line_ids:
line.action_rma_done()
self.assertEquals(line.mapped("state"), ["done"], "Wrong State")

View File

@@ -0,0 +1,40 @@
<?xml version="1.0"?>
<odoo>
<record id="product_category_form_view" model="ir.ui.view">
<field name="name">product.category.form</field>
<field name="model">product.category</field>
<field name="inherit_id" ref="stock.product_category_form_view_inherit"/>
<field name="arch" type="xml">
<field name="removal_strategy_id" position="after">
<group name="rma">
<field name="rma_approval_policy" groups="rma.group_rma_customer_user,rma.group_rma_supplier_user"/>
<field name="rma_customer_operation_id"
groups="rma.group_rma_customer_user,rma.group_rma_supplier_user"
domain="[('type','=','customer')]"/>
<field name="rma_supplier_operation_id"
groups="rma.group_rma_customer_user,rma.group_rma_supplier_user"
domain="[('type','=','supplier')]"/>
</group>
</field>
</field>
</record>
<record id="product_template_form_view" model="ir.ui.view">
<field name="name">product.template.stock.property.form.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<group name="inventory" position="inside">
<group name="rma" groups="rma.group_rma_customer_user,rma.group_rma_supplier_user">
<field name="rma_approval_policy"/>
<field name="rma_customer_operation_id"
groups="rma.group_rma_customer_user,rma.group_rma_supplier_user"
domain="[('type','=','customer')]"/>
<field name="rma_supplier_operation_id"
groups="rma.group_rma_customer_user,rma.group_rma_supplier_user"
domain="[('type','=','supplier')]"/>
</group>
</group>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.rma</field>
<field name="model">res.config.settings</field>
<field name="priority" eval="10"/>
<field name="inherit_id" ref="base.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//div[hasclass('settings')]" position="inside">
<div class="app_settings_block" data-string="RMA" string="RMA" data-key="rma" groups="rma.group_rma_manager">
<h2>Return Merchandise Authorization</h2>
<div class="row mt16 o_settings_container">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="group_rma_delivery_address"/>
</div>
<div class="o_setting_right_pane">
<label for="group_rma_delivery_address"/>
<div class="text-muted">
Display 3 fields on rma: partner, invoice address, delivery address.
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="group_rma_lines"/>
</div>
<div class="o_setting_right_pane">
<label for="group_rma_lines"/>
<div class="text-muted">
Group RMA lines in one RMA group
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
<record id="action_rma_config_settings" model="ir.actions.act_window">
<field name="name">Settings</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.config.settings</field>
<field name="view_mode">form</field>
<field name="target">inline</field>
<field name="context">{'module' : 'rma'}</field>
</record>
</odoo>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0"?>
<odoo>
<record id="view_partner_form" model="ir.ui.view">
<field name="name">res.partner.form - rma</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button type="object"
name="action_open_partner_rma"
class="oe_stat_button"
icon="fa-eject"
context="{'res_partner_search_mode': 'customer'}"
groups="rma.group_rma_customer_user,rma.group_rma_supplier_user">
<field name="rma_line_count" widget="statinfo"
string="RMAs"/>
</button>
</div>
</field>
</record>
</odoo>

78
rma/views/rma_menu.xml Normal file
View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<menuitem
id="menu_rma_root"
name="RMA"
groups="rma.group_rma_customer_user,rma.group_rma_supplier_user,rma.group_rma_manager"
web_icon="rma,static/description/icon.png"
sequence="100"/>
<!-- Customer RMA -->
<menuitem id="menu_customer_rma"
name="Customer RMA"
groups="rma.group_rma_customer_user,rma.group_rma_supplier_user"
sequence="30"
parent="menu_rma_root"/>
<menuitem
id="menu_rma_act_customer"
sequence="50"
parent="menu_customer_rma"
groups="rma.group_rma_groups"
action="action_rma_customer"/>
<menuitem
id="menu_rma_line_customer"
sequence="20"
parent="menu_customer_rma"
groups="rma.group_rma_customer_user"
action="action_rma_customer_lines"/>
<!-- Supplier RMA -->
<menuitem id="menu_supplier_rma"
name="Supplier RMA"
groups="rma.group_rma_customer_user,rma.group_rma_supplier_user"
sequence="31"
parent="menu_rma_root"/>
<menuitem
id="menu_rma_act_supplier"
sequence="50"
parent="menu_supplier_rma"
groups="rma.group_rma_groups"
action="action_rma_supplier"/>
<menuitem
id="menu_rma_line_supplier"
sequence="20"
parent="menu_supplier_rma"
groups="rma.group_rma_supplier_user"
action="action_rma_supplier_lines"/>
<!-- Settings-->
<menuitem id="menu_rma_config"
name="Configuration"
groups="rma.group_rma_manager"
sequence="999"
parent="menu_rma_root"/>
<menuitem id="menu_rma_global_settings" name="Settings"
parent="rma.menu_rma_config"
sequence="1" action="action_rma_config_settings"
groups="base.group_system"/>
<menuitem id="menu_rma_operation_customer"
name="Customer Operations"
groups="rma.group_rma_manager"
sequence="35"
parent="rma.menu_rma_config"
action="action_rma_operation_customer"/>
<menuitem id="menu_rma_operation_supplier"
name="Supplier Operations"
groups="rma.group_rma_manager"
sequence="40"
parent="rma.menu_rma_config"
action="action_rma_operation_supplier"/>
</odoo>

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="rma_operation_tree" model="ir.ui.view">
<field name="name">rma.operation.tree</field>
<field name="model">rma.operation</field>
<field name="arch" type="xml">
<tree string="RMA Operations">
<field name="active" invisible="1"/>
<field name="code"/>
<field name="name"/>
<field name="receipt_policy"/>
<field name="delivery_policy"/>
</tree>
</field>
</record>
<record id="rma_operation_form" model="ir.ui.view">
<field name="name">rma.operation.form</field>
<field name="model">rma.operation</field>
<field name="arch" type="xml">
<form string="RMA Operations">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object"
class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button"
options='{"terminology": "archive"}'/>
</button>
</div>
<group colspan="4" col="2">
<group name="description"
string="Description">
<field name="code"/>
<field name="name"/>
<field name="type"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group name="policies"
string="Policies">
<field name="receipt_policy"/>
<field name="delivery_policy"/>
</group>
<group name="inbound" string="Inbound">
<field name="in_route_id"/>
<field name="in_warehouse_id"/>
<field name="location_id"
domain="[('usage', '=', 'internal')]"/>
<field name="customer_to_supplier"
attrs="{'invisible':[('type', '=', 'supplier')]}"/>
</group>
<group name="outbound" string="Outbound">
<field name="out_route_id"/>
<field name="out_warehouse_id"/>
<field name="supplier_to_customer"
attrs="{'invisible':[('type', '=', 'customer')]}"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_rma_operation_customer" model="ir.actions.act_window">
<field name="name">Customer Operations</field>
<field name="res_model">rma.operation</field>
<field name="view_mode">tree,form</field>
<field name="context">{'default_type': "customer"}</field>
<field name="domain">[('type','=', 'customer')]</field>
<field name="view_id" ref="rma_operation_tree"/>
</record>
<record id="action_rma_operation_supplier" model="ir.actions.act_window">
<field name="name">Supplier Operations</field>
<field name="res_model">rma.operation</field>
<field name="view_mode">tree,form</field>
<field name="context">{'default_type': "supplier"}</field>
<field name="domain">[('type','=', 'supplier')]</field>
<field name="view_id" ref="rma_operation_tree"/>
</record>
</odoo>

View File

@@ -0,0 +1,459 @@
<?xml version="1.0"?>
<odoo>
<data>
<record id="view_rma_line_tree" model="ir.ui.view">
<field name="name">rma.order.line.tree</field>
<field name="model">rma.order.line</field>
<field name="arch" type="xml">
<tree string="RMA Line"
decoration-info="state in ('draft','to_approve')">
<field name="name"/>
<field name="partner_id"/>
<field name="product_id"/>
<field name="lot_id" groups="stock.group_production_lot"/>
<field name="rma_id" groups="rma.group_rma_groups"/>
<field name="origin"/>
<field name="operation_id"/>
<field name="supplier_address_id"/>
<field name="uom_id" groups="uom.group_uom"/>
<field name="product_qty"/>
<field name="price_unit"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="view_rma_line_supplier_tree" model="ir.ui.view">
<field name="name">rma.order.line.supplier.tree</field>
<field name="model">rma.order.line</field>
<field name="arch" type="xml">
<tree string="RMA Line"
decoration-info="state in ('draft','to_approve')">
<field name="name"/>
<field name="partner_id"/>
<field name="product_id"/>
<field name="lot_id" groups="stock.group_production_lot"/>
<field name="rma_id" groups="rma.group_rma_groups"/>
<field name="origin"/>
<field name="operation_id" domain="[('type','=','supplier')]"/>
<field name="uom_id" groups="uom.group_uom"/>
<field name="product_qty"/>
<field name="price_unit"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="view_rma_line_supplier_form" model="ir.ui.view">
<field name="name">rma.order.line.supplier.form</field>
<field name="model">rma.order.line</field>
<field name="arch" type="xml">
<form string="RMA Line">
<header>
<button name="action_rma_to_approve" type="object"
string="Request Approval"
attrs="{'invisible':[('state', '!=', 'draft')]}"
class="oe_highlight" groups="rma.group_rma_customer_user"/>
<button name="action_rma_draft" type="object"
string="Back to Draft"
attrs="{'invisible':[('state', '=', 'draft')]}" class="oe_highlight"
groups="rma.group_rma_customer_user,rma.group_rma_supplier_user"/>
<button name="action_rma_approve" type="object"
string="Approve"
attrs="{'invisible':[('state', '!=', 'to_approve')]}" class="oe_highlight" groups="rma.group_rma_customer_user"/>
<button name="action_rma_done" type="object"
string="Done"
attrs="{'invisible':[('state', 'in', ('done', 'draft'))]}"
groups="rma.group_rma_customer_user"/>
<field name="state" widget="statusbar" nolabel="1"/>
</header>
<sheet>
<div name="button_box" class="oe_button_box">
<button type="object" name="action_view_in_shipments"
class="oe_stat_button"
icon="fa-truck"
groups="stock.group_stock_user">
<field name="in_shipment_count" widget="statinfo"
string="Shipments"/>
</button>
<button type="object" name="action_view_out_shipments"
class="oe_stat_button"
icon="fa-truck"
groups="stock.group_stock_user">
<field name="out_shipment_count" widget="statinfo"
string="Deliveries"/>
</button>
<button type="object" name="action_view_rma_lines"
class="oe_stat_button"
icon="fa-link"
groups="stock.group_stock_user">
<field name="rma_line_count" widget="statinfo"
string="Customer RMA"/>
</button>
</div>
<div class="oe_title" name="title">
<h1>
<field name="name" readonly="1"
invisible="context.get('hide_title',False)"/>
</h1>
</div>
<group>
<group name="partner">
<field name="partner_id"
context="{'res_partner_search_mode': 'supplier'}"
string="Supplier"/>
<field name="delivery_address_id"
domain="['|', ('parent_id', '=', partner_id),('id', '=', partner_id)]"
context="{'show_address': 1}"
options="{'always_reload': 1}"
groups='rma.group_rma_delivery_invoice_address'/>
</group>
</group>
<group>
<group name="product" string="Product">
<field name="product_id"/>
<newline/>
<field name="product_tracking" invisible="1"/>
<field name="lot_id"
groups="stock.group_production_lot"
attrs="{'required': [('product_tracking', 'in', ('serial', 'lot'))]}"
domain="[('product_id', '=', product_id)]"/>
</group>
<group name="product_qty" string="Quantity">
<field name="product_qty"/>
<field name="price_unit"/>
<field name="uom_id" groups="uom.group_uom"/>
</group>
</group>
<group name="main_info" string="Origin">
<field name="reference_move_id"
options="{'no_create': True}"
domain="[('picking_id.partner_id', '=', partner_id),
('location_id.usage', '=', 'supplier'),
('state', '=', 'done')]"/>
</group>
<group>
<group name="operation" string="Operation">
<field name="operation_id"
domain="[('type','=','supplier')]"/>
<field name="receipt_policy"/>
<field name="delivery_policy"/>
</group>
<group name="description" string="Description">
<field name="description" nolabel="1"/>
</group>
<group name="contact" string="Contact">
<field name="requested_by" readonly="1"/>
<field name="assigned_to"/>
</group>
</group>
<notebook>
<page name="route" string="Routes">
<group>
<group name="inbound" string="Inbound">
<field name="in_warehouse_id"/>
<field name="location_id"
domain="[('usage', '=', 'internal')]"/>
<field name="in_route_id" groups="stock.group_adv_location"/>
</group>
<group name="outbound" string="Outbound">
<field name="out_warehouse_id"/>
<field name="out_route_id" groups="stock.group_adv_location"/>
<field name="supplier_to_customer"/>
<field name="customer_address_id"
context="{'show_address': 1}"
options="{'always_reload': 1}"
attrs="{'required':[('supplier_to_customer', '=', True)],
'invisible':[('supplier_to_customer', '=', False)]}"/>
</group>
</group>
</page>
<page name="stock" string="Stock Moves">
<field name="move_ids" nolabel="1" readonly="1"/>
</page>
<page name="quantities" string="Quantities">
<group>
<group name="quantities" col="4" string="Quantities">
<group name="receive">
<field name="qty_to_receive"/>
<field name="qty_incoming"/>
<field name="qty_received"/>
</group>
<group name="deliver">
<field name="qty_to_deliver"/>
<field name="qty_outgoing"/>
<field name="qty_delivered"/>
</group>
</group>
</group>
</page>
<page name="other" string="Other Info">
<group name="general" string="General">
<field name="conditions"/>
<field name="rma_id" groups="rma.group_rma_groups"/>
<field name="origin"/>
</group>
<group name="company" string="Company"
groups="base.group_multi_company">
<field name="company_id"/>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record id="view_rma_line_form" model="ir.ui.view">
<field name="name">rma.order.line.form</field>
<field name="model">rma.order.line</field>
<field name="arch" type="xml">
<form string="RMA Line">
<header>
<button name="action_rma_to_approve" type="object"
string="Request Approval"
attrs="{'invisible':[('state', '!=', 'draft')]}"
class="oe_highlight" groups="rma.group_rma_customer_user"/>
<button name="action_rma_draft" type="object"
string="Back to Draft"
attrs="{'invisible':[('state', '=', 'draft')]}" class="oe_highlight"
groups="rma.group_rma_customer_user,rma.group_rma_supplier_user"/>
<button name="action_rma_approve" type="object"
string="Approve"
attrs="{'invisible':[('state', '!=', 'to_approve')]}" class="oe_highlight" groups="rma.group_rma_customer_user"/>
<button name="action_rma_done" type="object"
string="Done"
attrs="{'invisible':[('state', 'in', ('done', 'draft'))]}"
groups="rma.group_rma_customer_user"/>
<field name="state" widget="statusbar" nolabel="1"/>
</header>
<sheet>
<div name="button_box" class="oe_button_box">
<button type="object" name="action_view_in_shipments"
class="oe_stat_button"
icon="fa-truck"
groups="stock.group_stock_user">
<field name="in_shipment_count" widget="statinfo"
string="Shipments"/>
</button>
<button type="object" name="action_view_out_shipments"
class="oe_stat_button"
icon="fa-truck"
groups="stock.group_stock_user">
<field name="out_shipment_count" widget="statinfo"
string="Deliveries"/>
</button>
<!--Move this button to rma_account-->
<!--<button type="object" name="action_view_invoice"
class="oe_stat_button"
icon="fa-pencil-square-o"
string="Origin Inv">
</button>-->
<button type="object" name="action_view_rma_lines"
class="oe_stat_button"
icon="fa-link"
groups="stock.group_stock_user">
<field name="rma_line_count" widget="statinfo"
string="Supplier RMA"/>
</button>
</div>
<div class="oe_title" name="title">
<h1>
<field name="name" readonly="1"
invisible="context.get('hide_title',False)"/>
</h1>
</div>
<group name="partner">
<group name="partner">
<field name="partner_id"
context="{'res_partner_search_mode': 'customer'}"
string="Customer"/>
<field name="delivery_address_id"
domain="['|', ('parent_id', '=', partner_id),
('id', '=', partner_id)]"
context="{'show_address': 1}"
options="{'always_reload': 1}"
groups='rma.group_rma_delivery_invoice_address'/>
<field name="under_warranty"/>
</group>
</group>
<group>
<group name="product" string="Product">
<field name="product_id"/>
<newline/>
<field name="product_tracking" invisible="1"/>
<field name="lot_id"
groups="stock.group_production_lot"
attrs="{'required': [('product_tracking', 'in', ('serial', 'lot'))]}"/>
</group>
<group name="product_qty" string="Quantity">
<field name="product_qty"/>
<field name="uom_id" groups="uom.group_uom"/>
<field name="price_unit"/>
</group>
</group>
<group name="main_info" string="Origin">
<field name="reference_move_id"
options="{'no_create': True}"
domain="[('picking_id.partner_id', '=', partner_id),
('location_dest_id.usage', '=', 'customer'),
('state', '=', 'done')]"/>
</group>
<group>
<group name="operation" string="Operation">
<field name="operation_id" domain="[('type','=','customer')]"/>
<field name="receipt_policy"/>
<field name="delivery_policy"/>
</group>
<group name="description" string="Description">
<field name="description" nolabel="1"/>
</group>
</group>
<group>
<group name="contact" string="Contact">
<field name="requested_by" readonly="1"/>
<field name="assigned_to"/>
</group>
</group>
<notebook>
<page name="route" string="Routes">
<group name="inbound" string="Inbound">
<field name="in_warehouse_id"/>
<field name="location_id"
domain="[('usage', '=', 'internal')]"/>
<field name="in_route_id" groups="stock.group_adv_location"/>
<field name="customer_to_supplier"/>
<field name="supplier_address_id"
context="{'show_address': 1}"
options="{'always_reload': 1}"
attrs="{'required':[('customer_to_supplier', '=', True)],
'invisible':[('customer_to_supplier', '=', False)]}"/>
</group>
<group name="outbound" string="Outbound">
<field name="out_warehouse_id"/>
<field name="out_route_id" groups="stock.group_adv_location"/>
</group>
</page>
<page name="stock" string="Stock Moves">
<field name="move_ids" nolabel="1" readonly="1"/>
</page>
<page name="quantities" string="Quantities">
<group name="quantities" col="4" string="Quantities">
<group name="receive">
<field name="qty_to_receive"/>
<field name="qty_incoming"/>
<field name="qty_received"/>
</group>
<group name="deliver">
<field name="qty_to_deliver"/>
<field name="qty_outgoing"/>
<field name="qty_delivered"/>
</group>
<group name="supplier_rma"
attrs="{'invisible':[('customer_to_supplier','=',False)]}">
<field name="qty_to_supplier_rma"/>
<field name="qty_in_supplier_rma"/>
</group>
</group>
</page>
<page name="other" string="Other Info">
<group name="general" string="General">
<field name="conditions"/>
<field name="rma_id" groups="rma.group_rma_groups"/>
<field name="origin"/>
</group>
<group name="company" string="Company"
groups="base.group_multi_company">
<field name="company_id"/>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record id="view_rma_rma_line_filter" model="ir.ui.view">
<field name="name">rma.order.line.select</field>
<field name="model">rma.order.line</field>
<field name="arch" type="xml">
<search string="Search RMA line">
<field name="name"/>
<field name="rma_id" groups="rma.group_rma_groups"/>
<field name="partner_id"/>
<field name="requested_by"/>
<field name="assigned_to"/>
<field name="product_id"/>
<field name="lot_id"/>
<separator/>
<filter name="assigned_to" domain="[('assigned_to','=',uid)]" help="My RMAs"/>
<separator/>
<group name="stock_quantities" groups="stock.group_stock_user">
<filter name="to_receive" domain="[('state','!=', 'done'),('qty_to_receive','>',0.0)]" help="To Receive"/>
<filter name="to_deliver" domain="[('state','!=', 'done'),('qty_to_deliver','>',0.0)]" help="To Deliver"/>
</group>
<group name="rma_supplier_quantities">
<filter name="to_send_to_supplier" domain="[('state','!=', 'done'),('qty_to_supplier_rma','>',0.0)]" help="To Send to Supplier RMA"/>
</group>
<group expand="0" string="Group By">
<filter name="status" string="State" domain="[]"
context="{'group_by':'state'}"/>
<filter name="partner" string="Partner" domain="[]"
context="{'group_by':'partner_id'}"/>
<filter name="operation" string="Operation" domain="[]"
context="{'group_by':'operation_id'}"/>
<filter name="product" string="Product" domain="[]"
context="{'group_by':'product_id'}"/>
<filter name="to_receive" string="To Receive" domain="[('qty_to_receive', '>', 0)]"
context="{'group_by':'partner_id'}"/>
<filter name="to_deliver" string="To Deliver" domain="[('qty_to_deliver', '>', 0)]"
context="{'group_by':'partner_id'}"/>
</group>
</search>
</field>
</record>
<record id="action_rma_customer_lines" model="ir.actions.act_window">
<field name="name">Customer RMA</field>
<field name="res_model">rma.order.line</field>
<field name="domain">[('type','=', 'customer')]</field>
<field name="context">{"search_default_assigned_to":uid}</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_rma_line_tree"/>
</record>
<record id="action_rma_supplier_lines" model="ir.actions.act_window">
<field name="name">Supplier RMA</field>
<field name="res_model">rma.order.line</field>
<field name="domain">[('type','=', 'supplier')]</field>
<field name="context">{"search_default_assigned_to":uid, "supplier":1}</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_rma_line_supplier_tree"/>
</record>
<record id="action_rma_line_supplier_tree"
model="ir.actions.act_window.view">
<field eval="3" name="sequence"/>
<field name="view_mode">tree</field>
<field name="act_window_id" ref="action_rma_supplier_lines"/>
</record>
<record id="action_rma_line_supplier_form"
model="ir.actions.act_window.view">
<field eval="4" name="sequence"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_rma_line_supplier_form"/>
<field name="act_window_id" ref="action_rma_supplier_lines"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,292 @@
<?xml version="1.0"?>
<odoo>
<record id="view_rma_tree" model="ir.ui.view">
<field name="name">rma.order.tree</field>
<field name="model">rma.order</field>
<field name="arch" type="xml">
<tree string="RMA">
<field name="name"/>
<field name="reference"/>
<field name="partner_id" groups="base.group_user" string="Customer"/>
<field name="date_rma"/>
</tree>
</field>
</record>
<record id="view_rma_supplier_tree" model="ir.ui.view">
<field name="name">rma.order.supplier.tree</field>
<field name="model">rma.order</field>
<field name="arch" type="xml">
<tree string="RMA">
<field name="name"/>
<field name="reference"/>
<field name="partner_id" groups="base.group_user"
string="Supplier"/>
<field name="date_rma"/>
</tree>
</field>
</record>
<record id="view_rma_form" model="ir.ui.view">
<field name="name">rma.order.form</field>
<field name="model">rma.order</field>
<field name="arch" type="xml">
<form string="RMA">
<header/>
<sheet name='rma' string="RMA">
<div class="oe_button_box" name="button_box">
<button type="object" name="action_view_in_shipments"
class="oe_stat_button"
icon="fa-truck"
groups="stock.group_stock_user">
<field name="in_shipment_count" widget="statinfo"
string="Shipments"/>
</button>
<button type="object" name="action_view_out_shipments"
class="oe_stat_button"
icon="fa-truck"
groups="stock.group_stock_user">
<field name="out_shipment_count" widget="statinfo"
string="Deliveries"/>
</button>
<button type="object" name="action_view_lines"
class="oe_stat_button"
icon="fa-list"
groups="rma.group_rma_customer_user">
<field name="line_count" widget="statinfo"
string="RMA Lines"/>
</button>
<button type="object" name="action_view_supplier_lines"
class="oe_stat_button"
attrs="{'invisible':[('supplier_line_count', '=', 0)]}"
icon="fa-list"
groups="rma.group_rma_supplier_user">
<field name="supplier_line_count" widget="statinfo"
string="Rel. RMA Lines"/>
</button>
</div>
<div class="oe_title" name="title">
<h1>
<field name="name" readonly="1"/>
</h1>
</div>
<group>
<group name="info">
<field name="partner_id"
context="{'res_partner_search_mode': 'customer'}"
string="Customer"
colspan="4"
/>
<field name="date_rma" attrs="{'readonly':[('state', '!=', 'draft')]}"/>
</group>
<group name="comments">
<field name="comment"/>
<field name="type" readonly="1" invisible="1"/>
</group>
</group>
<group>
<group name="inbound_route" string="Inbound">
<field name="in_warehouse_id" attrs="{'readonly':[('state', '!=', 'draft')]}"/>
<field name="customer_to_supplier" attrs="{'readonly':[('state', '!=', 'draft')]}"/>
<field name="state" invisible="True"/>
<field name="supplier_address_id"
context="{'show_address': 1}"
options="{'always_reload': 1}"
attrs="{'required':[('customer_to_supplier', '=', True)],
'invisible':[('customer_to_supplier', '=', False)],
'readonly':[('state', '!=', 'draft')]}"/>
</group>
</group>
<group>
<group name="contact" string="Contact">
<field name="requested_by" readonly="1"/>
<field name="assigned_to" attrs="{'readonly':[('state', '!=', 'draft')]}"/>
<field name="company_id"
groups="base.group_multi_company"/>
</group>
</group>
<notebook colspan="4">
<page string="RMA Lines" name="lines">
<field name="rma_line_ids" context="{'default_rma_id': active_id,
'default_partner_id': partner_id,
'hide_title': True}">
<tree string="RMA Lines" name="lines">
<field name="sequence" widget="handle"/>
<field name="state" invisible="True"/>
<field name="product_id"/>
<field name="lot_id" groups="stock.group_production_lot"/>
<field name="name"/>
<field name="uom_id" groups="uom.group_uom"/>
<field name="operation_id"/>
<field name="product_qty"/>
<field name="price_unit"/>
</tree>
</field>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record id="view_rma_supplier_form" model="ir.ui.view">
<field name="name">rma.order.supplier.form</field>
<field name="model">rma.order</field>
<field name="arch" type="xml">
<form string="RMA">
<header/>
<sheet string="RMA">
<div class="oe_button_box" name="button_box">
<button type="object" name="action_view_in_shipments"
class="oe_stat_button"
icon="fa-truck"
groups="stock.group_stock_user">
<field name="in_shipment_count" widget="statinfo"
string="Shipments"/>
</button>
<button type="object" name="action_view_out_shipments"
class="oe_stat_button"
icon="fa-truck"
groups="stock.group_stock_user">
<field name="out_shipment_count" widget="statinfo"
string="Deliveries"/>
</button>
<button type="object" name="action_view_lines"
class="oe_stat_button"
icon="fa-list"
groups="rma.group_rma_supplier_user">
<field name="line_count" widget="statinfo"
string="RMA Lines"/>
</button>
</div>
<div class="oe_title" name="title">
<h1>
<field name="name" readonly="1"/>
</h1>
</div>
<group>
<group name="partner">
<field name="state" invisible="1"/>
<field name="partner_id"
context="{'res_partner_search_mode': 'supplier'}"
string="Supplier"
attrs="{'readonly':[('state', '!=', 'draft')]}"/>
<field name="reference"/>
<field name="date_rma" attrs="{'readonly':[('state', '!=', 'draft')]}"/>
</group>
<group>
<field name="comment"/>
<field name="type" readonly="1" invisible="1"/>
</group>
</group>
<group name="route">
<group name="inbound_route" string="Inbound">
<field name="in_warehouse_id" attrs="{'readonly':[('state', '!=', 'draft')]}"/>
</group>
<group name="outbound_route" string="Outbound">
<field name="supplier_to_customer" attrs="{'readonly':[('state', '!=', 'draft')]}"/>
<field name="customer_address_id"
context="{'show_address': 1}"
options="{'always_reload': 1}"
attrs="{'required':[('supplier_to_customer', '=', True)],
'invisible':[('supplier_to_customer', '=', False)],
'readonly':[('state', '!=', 'draft')]}"/>
</group>
</group>
<group>
<group name="contact" string="Contact">
<field name="requested_by" readonly="1"/>
<field name="assigned_to" attrs="{'readonly':[('state', '!=', 'draft')]}"/>
<field name="company_id"
groups="base.group_multi_company"/>
</group>
</group>
<notebook colspan="4">
<page string="RMA Lines" name="lines">
<field name="rma_line_ids"
context="{'form_view_ref':'rma.view_rma_line_supplier_form',
'default_rma_id': active_id,
'default_partner_id': partner_id,
'supplier': 1,
'hide_title': True}">
<tree string="RMA Lines">
<field name="sequence" widget="handle"/>
<field name="state" invisible="True"/>
<field name="name"/>
<field name="product_id"/>
<field name="lot_id" groups="stock.group_production_lot"/>
<field name="name"/>
<field name="uom_id" groups="uom.group_uom"/>
<field name="operation_id"/>
<field name="product_qty"/>
<field name="origin"/>
</tree>
</field>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record id="view_rma_rma_filter" model="ir.ui.view">
<field name="name">rma.order.select</field>
<field name="model">rma.order</field>
<field name="arch" type="xml">
<search string="Search RMA">
<field name="reference"/>
<field name="name"/>
<field name="partner_id"/>
<separator/>
<group expand="0" string="Group By">
<filter name="partner" string="Partner" domain="[]"
context="{'group_by':'partner_id'}"/>
<filter name="rma_date" string="RMA Date" domain="[]"
context="{'group_by':'date_rma'}"/>
</group>
</search>
</field>
</record>
<record id="action_rma_customer" model="ir.actions.act_window">
<field name="name">Customer RMA Group</field>
<field name="res_model">rma.order</field>
<field name="domain">[('type','=', 'customer')]</field>
<field name="context">{'customer':1}</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_rma_tree"/>
</record>
<record id="action_rma_supplier" model="ir.actions.act_window">
<field name="name">Supplier RMA Group</field>
<field name="res_model">rma.order</field>
<field name="domain">[('type','=', 'supplier')]</field>
<field name="context">{'supplier':1}</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_rma_supplier_tree"/>
</record>
<record id="action_rma_supplier_tree"
model="ir.actions.act_window.view">
<field eval="1" name="sequence"/>
<field name="view_mode">tree</field>
<field name="act_window_id" ref="action_rma_supplier"/>
</record>
<record id="action_rma_supplier_form"
model="ir.actions.act_window.view">
<field eval="2" name="sequence"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_rma_supplier_form"/>
<field name="act_window_id" ref="action_rma_supplier"/>
</record>
</odoo>

26
rma/views/stock_view.xml Normal file
View File

@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<odoo>
<record id="view_move_form" model="ir.ui.view">
<field name="name">rma.move.form</field>
<field name="model">stock.move</field>
<field name="inherit_id" ref="stock.view_move_form" />
<field name="arch" type="xml">
<group name="origin_grp" position="after">
<group name="rma">
<field name="rma_line_id"/>
</group>
</group>
</field>
</record>
<record id="stock_location_route_form_view_inherit_rma_stock" model="ir.ui.view">
<field name="name">stock.location.route.form</field>
<field name="inherit_id" ref="stock.stock_location_route_form_view"/>
<field name="model">stock.location.route</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='warehouse_ids']" position="before">
<field name="rma_selectable" string="RMA Order Lines"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0"?>
<odoo>
<record id="view_warehouse_form" model="ir.ui.view">
<field name="name">view_warehouse_form</field>
<field name="model">stock.warehouse</field>
<field name="inherit_id" ref="stock.view_warehouse"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='wh_output_stock_loc_id']" position="after">
<field name="lot_rma_id"/>
</xpath>
<field name="resupply_wh_ids" position="after">
<field name="rma_in_this_wh"/>
</field>
<xpath expr="//field[@name='out_type_id']" position="after">
<field name="rma_cust_in_type_id"/>
<field name="rma_cust_out_type_id"/>
<field name="rma_sup_in_type_id"/>
<field name="rma_sup_out_type_id"/>
</xpath>
</field>
</record>
</odoo>

6
rma/wizards/__init__.py Normal file
View File

@@ -0,0 +1,6 @@
# Copyright (C) 2017-20 ForgeFlow S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
from . import rma_add_stock_move
from . import rma_make_picking
from . import rma_order_line_make_supplier_rma

View File

@@ -0,0 +1,125 @@
# Copyright (C) 2017-20 ForgeFlow S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class RmaAddStockMove(models.TransientModel):
_name = "rma_add_stock_move"
_description = "Wizard to add rma lines from pickings"
@api.model
def default_get(self, fields_list):
res = super(RmaAddStockMove, self).default_get(fields_list)
rma_obj = self.env["rma.order"]
rma_id = self.env.context["active_ids"] or []
active_model = self.env.context["active_model"]
if not rma_id:
return res
assert active_model == "rma.order", "Bad context propagation"
rma = rma_obj.browse(rma_id)
res["rma_id"] = rma.id
res["partner_id"] = rma.partner_id.id
res["move_ids"] = False
return res
rma_id = fields.Many2one(
comodel_name="rma.order", string="RMA Order", readonly=True, ondelete="cascade"
)
partner_id = fields.Many2one(
comodel_name="res.partner", string="Partner", readonly=True
)
move_ids = fields.Many2many(
comodel_name="stock.move",
string="Stock Moves",
domain="[('state', '=', 'done')]",
)
def _prepare_rma_line_from_stock_move(self, sm, lot=False):
if self.env.context.get("customer"):
operation = (
sm.product_id.rma_customer_operation_id
or sm.product_id.categ_id.rma_customer_operation_id
)
else:
operation = (
sm.product_id.rma_supplier_operation_id
or sm.product_id.categ_id.rma_supplier_operation_id
)
if not operation:
operation = self.env["rma.operation"].search(
[("type", "=", self.rma_id.type)], limit=1
)
if not operation:
raise ValidationError(_("Please define an operation first"))
if not operation.in_route_id or not operation.out_route_id:
route = self.env["stock.location.route"].search(
[("rma_selectable", "=", True)], limit=1
)
if not route:
raise ValidationError(_("Please define an RMA route"))
if not operation.in_warehouse_id or not operation.out_warehouse_id:
warehouse = self.env["stock.warehouse"].search(
[
("company_id", "=", self.rma_id.company_id.id),
("lot_rma_id", "!=", False),
],
limit=1,
)
if not warehouse:
raise ValidationError(
_("Please define a warehouse with a default RMA location")
)
data = {
"partner_id": self.partner_id.id,
"reference_move_id": sm.id,
"product_id": sm.product_id.id,
"lot_id": lot and lot.id or False,
"origin": sm.picking_id.name or sm.name,
"uom_id": sm.product_uom.id,
"operation_id": operation.id,
"product_qty": sm.product_uom_qty,
"delivery_address_id": sm.picking_id.partner_id.id,
"rma_id": self.rma_id.id,
"receipt_policy": operation.receipt_policy,
"delivery_policy": operation.delivery_policy,
"in_warehouse_id": operation.in_warehouse_id.id or warehouse.id,
"out_warehouse_id": operation.out_warehouse_id.id or warehouse.id,
"in_route_id": operation.in_route_id.id or route.id,
"out_route_id": operation.out_route_id.id or route.id,
"location_id": (
operation.location_id.id
or operation.in_warehouse_id.lot_rma_id.id
or warehouse.lot_rma_id.id
),
}
return data
@api.model
def _get_existing_stock_moves(self):
existing_move_lines = []
for rma_line in self.rma_id.rma_line_ids:
existing_move_lines.append(rma_line.reference_move_id)
return existing_move_lines
def add_lines(self):
rma_line_obj = self.env["rma.order.line"]
existing_stock_moves = self._get_existing_stock_moves()
for sm in self.move_ids:
if sm not in existing_stock_moves:
if sm.product_id.tracking == "none":
data = self._prepare_rma_line_from_stock_move(sm, lot=False)
rma_line_obj.with_context(default_rma_id=self.rma_id.id).create(
data
)
else:
lot_ids = [x.lot_id.id for x in sm.move_line_ids if x.lot_id]
data = self._prepare_rma_line_from_stock_move(sm, lot=lot_ids[0])
rma_line_obj.with_context(default_rma_id=self.rma_id.id).create(
data
)
return {"type": "ir.actions.act_window_close"}

View File

@@ -0,0 +1,121 @@
<?xml version="1.0"?>
<odoo>
<record id="view_rma_add_stock_move_customer" model="ir.ui.view">
<field name="name">rma.add.stock.move.customer</field>
<field name="model">rma_add_stock_move</field>
<field name="arch" type="xml">
<form string="Select Move">
<group>
<field name="partner_id"
domain="[('customer','=',True)]"
string="Customer"/>
</group>
<separator string="Select Stock Moves to add"/>
<field name="move_ids"
domain="[('picking_id.partner_id', '=', partner_id), ('location_dest_id.usage', '=', 'customer')]">
<tree>
<field name="product_id"/>
<field name="product_uom_qty"/>
<field name="product_uom" string="Unit of Measure" groups="uom.group_uom"/>
<field name="picking_id"/>
<field name="location_id" groups="stock.group_stock_multi_locations"/>
<field name="location_dest_id" groups="stock.group_stock_multi_locations"/>
<field name="create_date" groups="base.group_no_one"/>
<field name="date" string="Date" groups="base.group_no_one"/>
<field name="date_expected" string="Date Expected"/>
<field name="state"/>
</tree>
</field>
<footer>
<button
string="Confirm"
name="add_lines" type="object"
class="oe_highlight"/>
or
<button name="action_cancel"
string="Cancel" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_rma_add_stock_move_customer"
model="ir.actions.act_window">
<field name="name">Add from Stock Move</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">rma_add_stock_move</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="view_id" ref="view_rma_add_stock_move_customer"/>
<field name="groups_id"
eval="[(4, ref('rma.group_rma_customer_user'))]"/>
</record>
<record id="view_rma_add_stock_move_customer_form" model="ir.ui.view">
<field name="name">rma.order.form - stock.move wizard</field>
<field name="model">rma.order</field>
<field name="inherit_id" ref="rma.view_rma_form"/>
<field name="arch" type="xml">
<xpath expr="//header" position="inside">
<button name="%(action_rma_add_stock_move_customer)d"
string="Add From Stock Move"
type="action"/>
</xpath>
</field>
</record>
<record id="view_rma_add_stock_move_supplier" model="ir.ui.view">
<field name="name">rma.add.stock.move.supplier</field>
<field name="model">rma_add_stock_move</field>
<field name="arch" type="xml">
<form string="Select Move">
<group>
<field name="partner_id"
domain="[('supplier','=',True)]"
string="Supplier"/>
</group>
<separator string="Select Stock Moves to add"/>
<field name="move_ids"
domain="[('picking_id.partner_id', '=', partner_id), ('location_id.usage', '=', 'supplier')]"/>
<footer>
<button
string="Confirm"
name="add_lines" type="object"
class="oe_highlight"/>
or
<button name="action_cancel"
string="Cancel" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_rma_add_stock_move_supplier"
model="ir.actions.act_window">
<field name="name">Add from Stock Move</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">rma_add_stock_move</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="view_id" ref="view_rma_add_stock_move_supplier"/>
<field name="groups_id"
eval="[(4, ref('rma.group_rma_supplier_user'))]"/>
</record>
<record id="view_rma_add_stock_move_button_supplier_form"
model="ir.ui.view">
<field name="name">rma.order.supplier.form - stock.move wizard</field>
<field name="model">rma.order</field>
<field name="inherit_id" ref="rma.view_rma_supplier_form"/>
<field name="arch" type="xml">
<xpath expr="//header" position="inside">
<button name="%(action_rma_add_stock_move_supplier)d"
string="Add From Stock Move"
type="action"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,228 @@
# Copyright (C) 2017-20 ForgeFlow S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
import time
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DT_FORMAT
class RmaMakePicking(models.TransientModel):
_name = "rma_make_picking.wizard"
_description = "Wizard to create pickings from rma lines"
@api.returns("rma.order.line")
def _prepare_item(self, line):
values = {
"product_id": line.product_id.id,
"product_qty": line.product_qty,
"uom_id": line.uom_id.id,
"qty_to_receive": line.qty_to_receive,
"qty_to_deliver": line.qty_to_deliver,
"line_id": line.id,
"rma_id": line.rma_id and line.rma_id.id or False,
"wiz_id": self.env.context["active_id"],
}
return values
@api.model
def default_get(self, fields_list):
"""Default values for wizard, if there is more than one supplier on
lines the supplier field is empty otherwise is the unique line
supplier.
"""
context = self._context.copy()
res = super(RmaMakePicking, self).default_get(fields_list)
rma_line_obj = self.env["rma.order.line"]
rma_line_ids = self.env.context["active_ids"] or []
active_model = self.env.context["active_model"]
if not rma_line_ids:
return res
assert active_model == "rma.order.line", "Bad context propagation"
items = []
lines = rma_line_obj.browse(rma_line_ids)
if len(lines.mapped("partner_id")) > 1:
raise ValidationError(
_(
"Only RMA lines from the same partner can be processed at "
"the same time"
)
)
for line in lines:
items.append([0, 0, self._prepare_item(line)])
res["item_ids"] = items
context.update({"items_ids": items})
return res
item_ids = fields.One2many("rma_make_picking.wizard.item", "wiz_id", string="Items")
def find_procurement_group(self, item):
if item.line_id.rma_id:
return self.env["procurement.group"].search(
[("rma_id", "=", item.line_id.rma_id.id)]
)
else:
return self.env["procurement.group"].search(
[("rma_line_id", "=", item.line_id.id)]
)
def _get_procurement_group_data(self, item):
group_data = {
"partner_id": item.line_id.partner_id.id,
"name": item.line_id.rma_id.name or item.line_id.name,
"rma_id": item.line_id.rma_id and item.line_id.rma_id.id or False,
"rma_line_id": item.line_id.id if not item.line_id.rma_id else False,
}
return group_data
@api.model
def _get_address(self, item):
if item.line_id.customer_to_supplier:
delivery_address = item.line_id.supplier_address_id
elif item.line_id.supplier_to_customer:
delivery_address = item.line_id.customer_address_id
elif item.line_id.delivery_address_id:
delivery_address = item.line_id.delivery_address_id
elif item.line_id.partner_id:
delivery_address = item.line_id.partner_id
else:
raise ValidationError(_("Unknown delivery address"))
return delivery_address
@api.model
def _get_address_location(self, delivery_address_id, a_type):
if a_type == "supplier":
return delivery_address_id.property_stock_supplier
elif a_type == "customer":
return delivery_address_id.property_stock_customer
@api.model
def _get_procurement_data(self, item, group, qty, picking_type):
line = item.line_id
delivery_address_id = self._get_address(item)
if picking_type == "incoming":
if line.customer_to_supplier:
location = self._get_address_location(delivery_address_id, "supplier")
elif line.supplier_to_customer:
location = self._get_address_location(delivery_address_id, "customer")
else:
location = line.location_id
warehouse = line.in_warehouse_id
route = line.in_route_id
else:
location = self._get_address_location(delivery_address_id, line.type)
warehouse = line.out_warehouse_id
route = line.out_route_id
if not route:
raise ValidationError(_("No route specified"))
if not warehouse:
raise ValidationError(_("No warehouse specified"))
procurement_data = {
"name": line.rma_id and line.rma_id.name or line.name,
"group_id": group,
"origin": line.name,
"warehouse_id": warehouse,
"date_planned": time.strftime(DT_FORMAT),
"product_id": item.product_id,
"product_qty": qty,
"partner_id": delivery_address_id.id,
"product_uom": line.product_id.product_tmpl_id.uom_id.id,
"location_id": location,
"rma_line_id": line.id,
"route_ids": route,
}
return procurement_data
@api.model
def _create_procurement(self, item, picking_type):
errors = []
group = self.find_procurement_group(item)
if not group:
pg_data = self._get_procurement_group_data(item)
group = self.env["procurement.group"].create(pg_data)
if picking_type == "incoming":
qty = item.qty_to_receive
else:
qty = item.qty_to_deliver
values = self._get_procurement_data(item, group, qty, picking_type)
values = dict(values, rma_line_id=item.line_id, rma_id=item.line_id.rma_id)
# create picking
procurements = []
try:
procurement = group.Procurement(
item.line_id.product_id,
qty,
item.line_id.product_id.product_tmpl_id.uom_id,
values.get("location_id"),
values.get("origin"),
values.get("origin"),
self.env.company,
values,
)
procurements.append(procurement)
self.env["procurement.group"].run(procurements)
except UserError as error:
errors.append(error.name)
if errors:
raise UserError("\n".join(errors))
return procurements
def _create_picking(self):
"""Method called when the user clicks on create picking"""
picking_type = self.env.context.get("picking_type")
procurements = []
for item in self.item_ids:
line = item.line_id
if line.state != "approved":
raise ValidationError(_("RMA %s is not approved") % line.name)
if line.receipt_policy == "no" and picking_type == "incoming":
raise ValidationError(_("No shipments needed for this operation"))
if line.delivery_policy == "no" and picking_type == "outgoing":
raise ValidationError(_("No deliveries needed for this operation"))
procurement = self._create_procurement(item, picking_type)
procurements.extend(procurement)
return procurements
def action_create_picking(self):
self._create_picking()
picking_type = self.env.context.get("picking_type")
if picking_type == "outgoing":
action = self.item_ids.line_id.action_view_out_shipments()
else:
action = self.item_ids.line_id.action_view_in_shipments()
return action
def action_cancel(self):
return {"type": "ir.actions.act_window_close"}
class RmaMakePickingItem(models.TransientModel):
_name = "rma_make_picking.wizard.item"
_description = "Items to receive"
wiz_id = fields.Many2one("rma_make_picking.wizard", string="Wizard", required=True)
line_id = fields.Many2one(
"rma.order.line", string="RMA order Line", readonly=True, ondelete="cascade"
)
rma_id = fields.Many2one(
"rma.order", related="line_id.rma_id", string="RMA Group", readonly=True
)
product_id = fields.Many2one("product.product", string="Product", readonly=True)
product_qty = fields.Float(
related="line_id.product_qty",
string="Quantity Ordered",
copy=False,
digits="Product Unit of Measure",
readonly=True,
)
qty_to_receive = fields.Float(
string="Quantity to Receive", digits="Product Unit of Measure"
)
qty_to_deliver = fields.Float(
string="Quantity To Deliver", digits="Product Unit of Measure"
)
uom_id = fields.Many2one("uom.uom", string="Unit of Measure", readonly=True)

View File

@@ -0,0 +1,116 @@
<?xml version="1.0"?>
<odoo>
<record id="view_rma_picking" model="ir.ui.view">
<field name="name">rma_picking</field>
<field name="model">rma_make_picking.wizard</field>
<field name="arch" type="xml">
<form string="Select lines for picking" name="lines">
<separator string="Select lines for picking"/>
<field name="item_ids">
<tree string="RMA Lines" editable="bottom" create="false">
<field name="rma_id" groups="rma.group_rma_groups"/>
<field name="product_id"/>
<field name="product_qty"/>
<field name="uom_id" groups="uom.group_uom"/>
<field name="qty_to_receive" readonly="0"/>
</tree>
</field>
<footer>
<button
string="Confirm"
name="action_create_picking" type="object"
class="oe_highlight"/>
or
<button name="action_cancel"
string="Cancel" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="view_rma_picking_out" model="ir.ui.view">
<field name="name">Create Delivery</field>
<field name="model">rma_make_picking.wizard</field>
<field name="arch" type="xml">
<form string="Select lines for picking" name="lines">
<separator string="Select lines for picking"/>
<field name="item_ids">
<tree string="RMA Lines" editable="bottom" create="false">
<field name="rma_id" groups="rma.group_rma_groups"/>
<field name="product_id"/>
<field name="product_qty"/>
<field name="uom_id" groups="uom.group_uom"/>
<field name="qty_to_deliver" readonly="0"/>
</tree>
</field>
<footer>
<button
string="Confirm"
name="action_create_picking" type="object"
class="oe_highlight"/>
or
<button name="action_cancel"
string="Cancel" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_rma_picking_in" model="ir.actions.act_window">
<field name="name">Create Incoming Shipment</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">rma_make_picking.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="view_id" ref="view_rma_picking"/>
<field name="groups_id" eval="[(4, ref('stock.group_stock_user'))]"/>
<field name="context">{'picking_type': 'incoming'}</field>
<field name="binding_model_id" ref="rma.model_rma_order_line"/>
</record>
<record id="action_rma_picking_out" model="ir.actions.act_window">
<field name="name">Create Delivery</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">rma_make_picking.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="view_id" ref="view_rma_picking_out"/>
<field name="groups_id" eval="[(4, ref('stock.group_stock_user'))]"/>
<field name="context">{'picking_type': 'outgoing'}</field>
<field name="binding_model_id" ref="rma.model_rma_order_line"/>
</record>
<record id="view_rma_line_button_form" model="ir.ui.view">
<field name="name">rma.order.line.form</field>
<field name="model">rma.order.line</field>
<field name="inherit_id" ref="rma.view_rma_line_form"/>
<field name="arch" type="xml">
<header position="inside">
<button name="%(action_rma_picking_in)d" states="approved"
string="Create Incoming Shipment" class="oe_highlight"
type="action"/>
<button name="%(action_rma_picking_out)d" states="approved"
string="Create Delivery" class="oe_highlight"
type="action"/>
</header>
</field>
</record>
<record id="view_rma_line_supplier_button_form" model="ir.ui.view">
<field name="name">rma.order.line.supplier.form</field>
<field name="model">rma.order.line</field>
<field name="inherit_id" ref="rma.view_rma_line_supplier_form"/>
<field name="arch" type="xml">
<header position="inside">
<button name="%(action_rma_picking_in)d" states="approved"
string="Create Incoming Shipment" class="oe_highlight"
type="action"/>
<button name="%(action_rma_picking_out)d" states="approved"
string="Create Delivery" class="oe_highlight"
type="action"/>
</header>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,211 @@
# Copyright 2017 ForgeFlow
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class RmaLineMakeSupplierRma(models.TransientModel):
_name = "rma.order.line.make.supplier.rma"
_description = "RMA Line Make Supplier RMA"
partner_id = fields.Many2one(
comodel_name="res.partner",
string="Supplier",
domain=[("supplier", "=", True)],
required=True,
)
item_ids = fields.One2many(
comodel_name="rma.order.line.make.supplier.rma.item",
inverse_name="wiz_id",
string="Items",
)
supplier_rma_id = fields.Many2one(
comodel_name="rma.order", string="Supplier RMA Order Group"
)
@api.model
def _get_default_operation(self):
"""Dropshipping is the most common use case of this wizard, thus
trying to default to a dropshipping operation first."""
operation = self.env["rma.operation"].search(
[("type", "=", "supplier"), ("supplier_to_customer", "=", True)], limit=1
)
if not operation:
operation = self.env["rma.operation"].search(
[("type", "=", "supplier")], limit=1
)
return operation
@api.model
def _prepare_item(self, line):
operation = self._get_default_operation()
return {
"line_id": line.id,
"product_id": line.product_id.id,
"name": line.name,
"product_qty": line.qty_to_supplier_rma,
"uom_id": line.uom_id.id,
"operation_id": operation.id if operation else False,
}
@api.model
def default_get(self, fields_list):
res = super(RmaLineMakeSupplierRma, self).default_get(fields_list)
rma_line_obj = self.env["rma.order.line"]
rma_line_ids = self.env.context["active_ids"] or []
active_model = self.env.context["active_model"]
if not rma_line_ids:
return res
assert active_model == "rma.order.line", "Bad context propagation"
items = []
lines = rma_line_obj.browse(rma_line_ids)
for line in lines:
items.append([0, 0, self._prepare_item(line)])
suppliers = lines.mapped(
lambda r: r.supplier_address_id.parent_id or r.supplier_address_id
)
if len(suppliers) > 1:
raise ValidationError(
_(
"Only RMA lines from the same supplier can be "
"processed at the same time"
)
)
res["partner_id"] = suppliers.id
res["item_ids"] = items
return res
@api.model
def _prepare_supplier_rma(self, company):
if not self.partner_id:
raise ValidationError(_("Enter a supplier."))
return {
"partner_id": self.partner_id.id,
"type": "supplier",
"company_id": company.id,
}
@api.model
def _prepare_supplier_rma_line(self, rma, item):
if item.operation_id:
operation = item.operation_id
else:
operation = self._get_default_operation()
if not operation.in_route_id or not operation.out_route_id:
route = self.env["stock.location.route"].search(
[("rma_selectable", "=", True)], limit=1
)
if not route:
raise ValidationError(_("Please define an RMA route"))
if not operation.in_warehouse_id or not operation.out_warehouse_id:
warehouse = self.env["stock.warehouse"].search(
[
("company_id", "=", self.rma_id.company_id.id),
("lot_rma_id", "!=", False),
],
limit=1,
)
if not warehouse:
raise ValidationError(
_("Please define a warehouse with a default RMA location")
)
data = {
"partner_id": self.partner_id.id,
"type": "supplier",
"origin": item.line_id.rma_id.name,
"customer_address_id": item.line_id.delivery_address_id.id
or item.line_id.partner_id.id,
"delivery_address_id": self.partner_id.id,
"product_id": item.line_id.product_id.id,
"customer_rma_id": item.line_id.id,
"product_qty": item.product_qty,
"rma_id": rma.id if rma else False,
"uom_id": item.line_id.uom_id.id,
"operation_id": operation.id,
"receipt_policy": operation.receipt_policy,
"delivery_policy": operation.delivery_policy,
"supplier_to_customer": operation.supplier_to_customer,
"in_warehouse_id": operation.in_warehouse_id.id or warehouse.id,
"out_warehouse_id": operation.out_warehouse_id.id or warehouse.id,
"in_route_id": operation.in_route_id.id or route.id,
"out_route_id": operation.out_route_id.id or route.id,
"location_id": (
operation.location_id.id
or operation.in_warehouse_id.lot_rma_id.id
or warehouse.lot_rma_id.id
),
}
return data
def make_supplier_rma(self):
self = self.with_context(supplier=True, customer=False)
rma_obj = self.env["rma.order"]
rma_line_obj = self.env["rma.order.line"]
rma = False
for item in self.item_ids:
line = item.line_id
if item.product_qty <= 0.0:
raise ValidationError(_("Enter a positive quantity."))
if self.supplier_rma_id:
rma = self.supplier_rma_id
if not rma and len(self.item_ids) > 1:
rma_data = self._prepare_supplier_rma(line.company_id)
rma = rma_obj.create(rma_data)
rma_line_data = self._prepare_supplier_rma_line(rma, item)
rma_line = rma_line_obj.create(rma_line_data)
if rma:
return {
"name": _("Supplier RMA"),
"view_mode": "form",
"res_model": "rma.order",
"view_id": False,
"res_id": rma.id,
"context": {"supplier": True, "customer": False},
"type": "ir.actions.act_window",
}
else:
return {
"name": _("Supplier RMA Line"),
"view_mode": "form",
"res_model": "rma.order.line",
"view_id": False,
"res_id": rma_line.id,
"context": {"supplier": True, "customer": False},
"type": "ir.actions.act_window",
}
class RmaLineMakeRmaOrderItem(models.TransientModel):
_name = "rma.order.line.make.supplier.rma.item"
_description = "RMA Line Make Supplier RMA Item"
wiz_id = fields.Many2one(
"rma.order.line.make.supplier.rma",
string="Wizard",
required=True,
ondelete="cascade",
readonly=True,
)
line_id = fields.Many2one(
"rma.order.line", string="RMA Line", required=True, ondelete="cascade"
)
rma_id = fields.Many2one(
"rma.order", related="line_id.rma_id", string="RMA Order", readonly=True
)
product_id = fields.Many2one(
"product.product", related="line_id.product_id", readony=True
)
name = fields.Char(related="line_id.name", readonly=True)
uom_id = fields.Many2one("uom.uom", string="UoM", readonly=True)
product_qty = fields.Float(string="Quantity", digits="Product UoS")
operation_id = fields.Many2one(
comodel_name="rma.operation",
string="Operation",
domain=[("type", "=", "supplier")],
)

View File

@@ -0,0 +1,75 @@
<?xml version="1.0"?>
<!-- Copyright 2016 ForgeFlow
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0) -->
<odoo>
<record id="view_rma_order_line_make_supplier_rma"
model="ir.ui.view">
<field name="name">RMA Line Make Supplier RMA</field>
<field name="model">rma.order.line.make.supplier.rma</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Create Supplier RMA">
<separator string="Existing Supplier RMA to update:"/>
<newline/>
<group>
<field name="supplier_rma_id" domain="[('partner_id', '=', partner_id)]"/>
</group>
<newline/>
<separator
string="New RMA details:"/>
<newline/>
<group>
<field name="partner_id"/>
</group>
<newline/>
<group>
<field name="item_ids" nolabel="1" colspan="2">
<tree string="Details" editable="bottom" create="false">
<field name="line_id"
options="{'no_open': true}"/>
<field name="product_id"/>
<field name="name"/>
<field name="operation_id"/>
<field name="product_qty"/>
<field name="uom_id"
groups="uom.group_uom"/>
</tree>
</field>
</group>
<newline/>
<footer colspan="2">
<button name="make_supplier_rma"
string="Create/Update Supplier RMA"
type="object"
class="oe_highlight"/>
<button special="cancel" string="Cancel" class="oe_link"/>
</footer>
</form>
</field>
</record>
<record id="action_rma_order_line_make_supplier_rma"
model="ir.actions.act_window">
<field name="name">Create Supplier RMA</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">rma.order.line.make.supplier.rma</field>
<field name="view_mode">form</field>
<field name="view_id" ref="view_rma_order_line_make_supplier_rma"/>
<field name="target">new</field>
<field name="binding_model_id" ref="rma.model_rma_order_line"/>
</record>
<record id="view_rma_line_supplier_rma_button_form" model="ir.ui.view">
<field name="name">rma.order.line.supplier.rma.form</field>
<field name="model">rma.order.line</field>
<field name="inherit_id" ref="rma.view_rma_line_form"/>
<field name="arch" type="xml">
<header position="inside">
<button name="%(action_rma_order_line_make_supplier_rma)d"
states="approved"
string="Create Supplier RMA" class="oe_highlight"
type="action"/>
</header>
</field>
</record>
</odoo>