Browse Source

Merge pull request #269 from Tecnativa/10.0-mig-pos_order_return

[MIG] pos_order_return: Migration to 10.0
pull/280/head
Pedro M. Baeza 7 years ago
committed by GitHub
parent
commit
c46de542af
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 99
      pos_order_return/README.rst
  2. 4
      pos_order_return/__init__.py
  3. 28
      pos_order_return/__manifest__.py
  4. 16
      pos_order_return/demo/product_product.xml
  5. 210
      pos_order_return/i18n/es.po
  6. 189
      pos_order_return/i18n/fr.po
  7. 4
      pos_order_return/models/__init__.py
  8. 198
      pos_order_return/models/pos_order.py
  9. 13
      pos_order_return/models/product_template.py
  10. BIN
      pos_order_return/static/description/icon.png
  11. BIN
      pos_order_return/static/description/initial_pos_order_required.png
  12. BIN
      pos_order_return/static/description/partial_return_wizard.png
  13. BIN
      pos_order_return/static/description/product_returnable_bottle.png
  14. BIN
      pos_order_return/static/description/returned_qty_over_initial.png
  15. BIN
      pos_order_return/static/description/sum_returned_qty_over_initial.png
  16. BIN
      pos_order_return/static/img/product_returnable_bottle-image.jpg
  17. 3
      pos_order_return/tests/__init__.py
  18. 102
      pos_order_return/tests/test_pos_order_return.py
  19. 56
      pos_order_return/views/pos_order_view.xml
  20. 17
      pos_order_return/views/product_product_view.xml
  21. 3
      pos_order_return/wizard/__init__.py
  22. 72
      pos_order_return/wizard/pos_partial_return_wizard.py
  23. 29
      pos_order_return/wizard/pos_partial_return_wizard_view.xml

99
pos_order_return/README.rst

@ -0,0 +1,99 @@
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3
================
PoS Order Return
================
This module extends the functionality of odoo Point Of Sale about POS Order
returns.
With this module, it is now forbidden to return more quantity than the initial
one.
A link is created between the returned Order and the initial Order.
A link is created between the returned Order Line and the initial Order Line.
Usage
=====
Select an PoS Order an choose either *Return Products* (full return of the
order) or *Partial Return*. In this case, a wizard allows to select just some
products and quantities to return:
.. image:: /pos_order_return/static/description/partial_return_wizard.png
Register the refund payment to finish the return. If the original order was
invoiced, a refund invoice will be made.
Implemented Constraints
-----------------------
* User can not return more products than the initial quantity:
.. image:: /pos_order_return/static/description/returned_qty_over_initial.png
* If a line has been partially refund, only a reduced quantity can be returned:
.. image:: /pos_order_return/static/description/sum_returned_qty_over_initial.png
* It is not possible to set a negative quantity if the initial Pos Order is
not indicated:
.. image:: /pos_order_return/static/description/initial_pos_order_required.png
Configuration
=============
In some cases, you may want to let the possibility to allow negative quantity
in a PoS Order, without mentioning initial order. This can happen for special
products like returnable products, etc.
In that case, a checkbox is possible on Product Form View to allow such case
.. image:: /pos_order_return/static/description/product_returnable_bottle.png
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/184/10.0
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/pos/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smash it by providing detailed and welcomed feedback.
Credits
=======
Contributors
------------
* Sylvain LE GAL <https://twitter.com/legalsylvain>
* David Vidal <david.vidal@tecnativa.com>
Funders
-------
The development of this module has been financially supported by:
* La Louve (www.lalouve.net)
* GRAP, Groupement Régional Alimentaire de Proximité (www.grap.coop)
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit https://odoo-community.org.

4
pos_order_return/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import models
from . import wizard

28
pos_order_return/__manifest__.py

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Copyright 2016-2018 Sylvain LE GAL (https://twitter.com/legalsylvain)
# Copyright 2018 David Vidal <david.vidal@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
'name': 'Point of Sale Order Return',
'version': '10.0.1.0.0',
'category': 'Point Of Sale',
'author': 'La Louve, '
'GRAP, '
'Tecnativa, '
'Odoo Community Association (OCA)',
'license': 'AGPL-3',
'website': 'https://www.github.com/OCA/pos',
'depends': [
'point_of_sale',
],
'data': [
'wizard/pos_partial_return_wizard_view.xml',
'views/pos_order_view.xml',
'views/product_product_view.xml',
],
'demo': [
'demo/product_product.xml',
],
'installable': True,
}

16
pos_order_return/demo/product_product.xml

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="product_product_returnable_bottle" model="product.product">
<field name="name">Returnable Bottle</field>
<field name="default_code">RET-BOTL</field>
<field name="categ_id" ref="product.product_category_all" />
<field name="uom_id" ref="product.product_uom_unit" />
<field name="sale_ok" eval="True" />
<field name="available_in_pos" eval="True" />
<field name="pos_allow_negative_qty" eval="True" />
<field name="list_price" eval="0.20" />
<field name="image" type="base64" file="pos_order_return/static/img/product_returnable_bottle-image.jpg"/>
</record>
</odoo>

210
pos_order_return/i18n/es.po

@ -0,0 +1,210 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_order_return
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-05-03 12:50+0000\n"
"PO-Revision-Date: 2018-05-03 12:50+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_product_product_pos_allow_negative_qty
#: model:ir.model.fields,field_description:pos_order_return.field_product_template_pos_allow_negative_qty
msgid "Allow Negative Quantity on PoS"
msgstr "Allow Negative Quantity on PoS"
#. module: pos_order_return
#: model:ir.ui.view,arch_db:pos_order_return.view_partial_return_wizard_form
msgid "Cancel"
msgstr "Cancelar"
#. module: pos_order_return
#: model:ir.model.fields,help:pos_order_return.field_pos_partial_return_wizard_line_max_returnable_qty
msgid "Compute maximum quantity that can be returned for this line, depending of the quantity of the line and other possible refunds."
msgstr "Calcula la cantidad máxima que puede ser devuelta para esta línea, dependiendo de la cantidad de la línea y otras devoluciones anteriores"
#. module: pos_order_return
#: model:ir.ui.view,arch_db:pos_order_return.view_partial_return_wizard_form
msgid "Confirm"
msgstr "Confirmar"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_create_uid
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_line_create_uid
msgid "Created by"
msgstr "Creado por"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_create_date
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_line_create_date
msgid "Created on"
msgstr "Creado el"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_display_name
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_line_display_name
msgid "Display Name"
msgstr "Nombre a mostrar"
#. module: pos_order_return
#: code:addons/pos_order_return/models/pos_order.py:186
#, python-format
msgid "For legal and traceability reasons, you can not set a negative quantity (%d %s of %s), without using return wizard."
msgstr "Por razones legales y de trazabilidad, no puede establecer una cantidad negativa (%d %s of %s), sin en el asistente de devoluciones."
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_id
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_line_id
msgid "ID"
msgstr "ID"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_line_initial_qty
msgid "Initial Quantity"
msgstr "Cantidad inicial"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard___last_update
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_line___last_update
msgid "Last Modified on"
msgstr "Última modificación en"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_line_write_uid
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_write_uid
msgid "Last Updated by"
msgstr "Última actualización por"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_line_write_date
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_write_date
msgid "Last Updated on"
msgstr "Última actualización el"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_line_pos_order_line_id
msgid "Line To Return"
msgstr "Línea a devolver"
#. module: pos_order_return
#: model:ir.model,name:pos_order_return.model_pos_order_line
msgid "Lines of Point of Sale"
msgstr "Líneas del Terminal Punto de Venta"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_line_ids
#: model:ir.ui.view,arch_db:pos_order_return.view_partial_return_wizard_form
msgid "Lines to Return"
msgstr "Líneas a devolver"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_order_id
msgid "Order to Return"
msgstr "Pedido a devolver"
#. module: pos_order_return
#: model:ir.ui.view,arch_db:pos_order_return.view_partial_return_wizard_form
#: model:ir.ui.view,arch_db:pos_order_return.view_pos_order_form
msgid "Partial Return"
msgstr "Devolición parcial"
#. module: pos_order_return
#: model:ir.actions.act_window,name:pos_order_return.action_pos_partial_return_wizard
msgid "Partial Return Wizard"
msgstr "Asistente de devolución parcial"
#. module: pos_order_return
#: model:ir.model,name:pos_order_return.model_pos_order
msgid "Point of Sale Orders"
msgstr "Pedidos del TPV"
#. module: pos_order_return
#: model:ir.model,name:pos_order_return.model_product_template
msgid "Product Template"
msgstr "Plantilla de producto"
#. module: pos_order_return
#: model:ir.model.fields,help:pos_order_return.field_pos_partial_return_wizard_line_initial_qty
msgid "Quantity of Product initially sold"
msgstr "Cantidad de producto vendida inicialmente"
#. module: pos_order_return
#: model:ir.ui.view,arch_db:pos_order_return.view_pos_order_form
#: model:ir.ui.view,arch_db:pos_order_return.view_pos_order_line_form
msgid "Refund"
msgstr "Factura rectificativa"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_order_line_refund_line_ids
msgid "Refund Lines"
msgstr "Líneas de devolución"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_order_refund_order_ids
msgid "Refund Orders"
msgstr "Pedidos de devolución"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_order_refund_order_qty
msgid "Refund Orders Quantity"
msgstr "Cantidad de pedidos de devolución"
#. module: pos_order_return
#: model:product.product,name:pos_order_return.product_product_returnable_bottle
#: model:product.template,name:pos_order_return.product_product_returnable_bottle_product_template
msgid "Returnable Bottle"
msgstr "Botella retornable"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_line_max_returnable_qty
msgid "Returnable Quantity"
msgstr "Cantidad retornable"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_order_line_returned_line_id
#: model:ir.model.fields,field_description:pos_order_return.field_pos_order_returned_order_id
msgid "Returned Order"
msgstr "Pedido devuelto"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_line_qty
msgid "Returned Quantity"
msgstr "Cantidad devuelta"
#. module: pos_order_return
#: model:ir.model.fields,field_description:pos_order_return.field_pos_partial_return_wizard_line_wizard_id
msgid "Wizard"
msgstr "Asistente"
#. module: pos_order_return
#: code:addons/pos_order_return/models/pos_order.py:175
#, python-format
msgid "You can not return %d %s of %s because some refunds has been yet done.\n"
" Maximum quantity allowed : %d %s."
msgstr "No puede devolver %d %s de %s porque ya se ha devuelto una parte.\n"
" Catidad máxima permitida : %d %s."
#. module: pos_order_return
#: code:addons/pos_order_return/models/pos_order.py:166
#, python-format
msgid "You can not return %d %s of %s because the original Order line only mentions %d %s."
msgstr "No puede devolver %d %s de %s porque el pedido original solo menciona %d %s."
#. module: pos_order_return
#: model:ir.model,name:pos_order_return.model_pos_partial_return_wizard
msgid "pos.partial.return.wizard"
msgstr "pos.partial.return.wizard"
#. module: pos_order_return
#: model:ir.model,name:pos_order_return.model_pos_partial_return_wizard_line
msgid "pos.partial.return.wizard.line"
msgstr "pos.partial.return.wizard.line"

189
pos_order_return/i18n/fr.po

@ -0,0 +1,189 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_return_order
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 9.0c\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-04-03 23:54+0000\n"
"PO-Revision-Date: 2016-04-03 23:54+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_product_template_pos_allow_negative_qty
msgid "Allow Negative Quantity on PoS"
msgstr "PdV - Autoriser les quantités négatives"
#. module: pos_return_order
#: model:ir.ui.view,arch_db:pos_return_order.view_partial_return_wizard_form
msgid "Cancel"
msgstr "Annuler"
#. module: pos_return_order
#: model:ir.model.fields,help:pos_return_order.field_pos_partial_return_wizard_line_max_returnable_qty
msgid "Compute maximum quantity that can be returned for this line, depending of the quantity of the line and other possible refunds."
msgstr "Calcule la quantité maximum qui peut être retournée pour cette ligne, en fonction de la quantité de la ligne original, et des possibles retours."
#. module: pos_return_order
#: model:ir.ui.view,arch_db:pos_return_order.view_partial_return_wizard_form
msgid "Confirm"
msgstr "Confirmer"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_create_uid
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_line_create_uid
msgid "Created by"
msgstr "Créé par"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_create_date
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_line_create_date
msgid "Created on"
msgstr "Créé le"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_display_name
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_line_display_name
msgid "Display Name"
msgstr "Nom affiché"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_id
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_line_id
msgid "ID"
msgstr "ID"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_line_initial_qty
msgid "Initial Quantity"
msgstr "Quantité initial"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard___last_update
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_line___last_update
msgid "Last Modified on"
msgstr "Dernière modification le"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_line_write_uid
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_write_uid
msgid "Last Updated by"
msgstr "Mis à jour par"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_line_write_date
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_write_date
msgid "Last Updated on"
msgstr "Mis à jour le"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_line_pos_order_line_id
msgid "Line To Return"
msgstr "Ligne à retourner"
#. module: pos_return_order
#: model:ir.model,name:pos_return_order.model_pos_order_line
msgid "Lines of Point of Sale"
msgstr "Lignes de Points de Vente"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_line_ids
#: model:ir.ui.view,arch_db:pos_return_order.view_partial_return_wizard_form
msgid "Lines to Return"
msgstr "Lignes à retourner"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_order_id
msgid "Order to Return"
msgstr "Vente à retourner"
#. module: pos_return_order
#: model:ir.ui.view,arch_db:pos_return_order.view_partial_return_wizard_form
#: model:ir.ui.view,arch_db:pos_return_order.view_pos_order_form
msgid "Partial Return"
msgstr "Retourner partiellement"
#. module: pos_return_order
#: model:ir.actions.act_window,name:pos_return_order.action_pos_partial_return_wizard
msgid "Partial Return Wizard"
msgstr "Assistant de retour partiel"
#. module: pos_return_order
#: model:ir.model,name:pos_return_order.model_pos_order
msgid "Point of Sale"
msgstr "Point de vente"
#. module: pos_return_order
#: model:ir.model,name:pos_return_order.model_product_template
msgid "Product Template"
msgstr "Modèle d'article"
#. module: pos_return_order
#: model:ir.model.fields,help:pos_return_order.field_pos_partial_return_wizard_line_initial_qty
msgid "Quantity of Product initially sold"
msgstr "Quantité de produit initialement vendue"
#. module: pos_return_order
#: model:ir.ui.view,arch_db:pos_return_order.view_pos_order_form
#: model:ir.ui.view,arch_db:pos_return_order.view_pos_order_line_form
msgid "Refund"
msgstr "Avoir"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_order_line_refund_line_ids
msgid "Refund Lines"
msgstr "Lignes de vente retournées"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_order_refund_order_ids
msgid "Refund Orders"
msgstr "Ventes retournées"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_order_refund_order_qty
msgid "Refund Orders Quantity"
msgstr "Nombre de ventes retournées"
#. module: pos_return_order
#: model:product.product,name:pos_return_order.product_product_returnable_bottle
#: model:product.template,name:pos_return_order.product_product_returnable_bottle_product_template
msgid "Returnable Bottle"
msgstr "Bouteille consignée"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_line_max_returnable_qty
msgid "Returnable Quantity"
msgstr "Quantité retournable"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_order_line_returned_line_id
#: model:ir.model.fields,field_description:pos_return_order.field_pos_order_returned_order_id
msgid "Returned Order"
msgstr "Vente retournée"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_line_qty
msgid "Returned Quantity"
msgstr "Quantité retournée"
#. module: pos_return_order
#: model:ir.model.fields,field_description:pos_return_order.field_pos_partial_return_wizard_line_wizard_id
msgid "Wizard"
msgstr "Assistant"
#. module: pos_return_order
#: model:ir.model,name:pos_return_order.model_pos_partial_return_wizard
msgid "pos.partial.return.wizard"
msgstr "pos.partial.return.wizard"
#. module: pos_return_order
#: model:ir.model,name:pos_return_order.model_pos_partial_return_wizard_line
msgid "pos.partial.return.wizard.line"
msgstr "pos.partial.return.wizard.line"

4
pos_order_return/models/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import product_template
from . import pos_order

198
pos_order_return/models/pos_order.py

@ -0,0 +1,198 @@
# -*- coding: utf-8 -*-
# Copyright 2016-2018 Sylvain LE GAL (https://twitter.com/legalsylvain)
# Copyright 2018 David Vidal <david.vidal@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class PosOrder(models.Model):
_inherit = 'pos.order'
returned_order_id = fields.Many2one(
comodel_name='pos.order',
string='Returned Order',
readonly=True,
)
refund_order_ids = fields.One2many(
comodel_name='pos.order',
inverse_name='returned_order_id',
string='Refund Orders',
readonly=True,
)
refund_order_qty = fields.Integer(
compute='_compute_refund_order_qty',
string='Refund Orders Quantity',
)
def _compute_refund_order_qty(self):
order_data = self.env['pos.order'].read_group(
[('returned_order_id', 'in', self.ids)],
['returned_order_id'], ['returned_order_id']
)
mapped_data = dict(
[(order['returned_order_id'][0], order['returned_order_id_count'])
for order in order_data])
for order in self:
order.refund_order_qty = mapped_data.get(order.id, 0)
def _blank_refund(self, res):
self.ensure_one()
new_order = self.browse(res['res_id'])
new_order.returned_order_id = self
# Remove created lines and recreate and link Lines
new_order.lines.unlink()
return new_order
def _prepare_invoice(self):
res = super(PosOrder, self)._prepare_invoice()
if not self.returned_order_id:
return res
res.update({
'origin': self.name,
'type': 'out_refund',
'refund_invoice_id': self.returned_order_id.invoice_id.id,
})
return res
def _action_create_invoice_line(self, line=False, invoice_id=False):
line = super(PosOrder, self
)._action_create_invoice_line(line, invoice_id)
if not self.returned_order_id:
return line
# Goes to refund invoice thus it should be positive
line.quantity = -line.quantity
return line
def _action_pos_order_invoice(self):
"""Wrap common process"""
self.action_pos_order_invoice()
self.invoice_id.sudo().action_invoice_open()
self.account_move = self.invoice_id.move_id
def refund(self):
# Call super to use original refund algorithm (session management, ...)
ctx = dict(self.env.context, do_not_check_negative_qty=True)
res = super(PosOrder, self.with_context(ctx)).refund()
new_order = self._blank_refund(res)
for line in self.lines:
qty = - line.max_returnable_qty([])
if qty != 0:
copy_line = line.copy()
copy_line.write({
'order_id': new_order.id,
'returned_line_id': line.id,
'qty': qty,
})
return res
def partial_refund(self, partial_return_wizard):
ctx = dict(self.env.context, partial_refund=True)
res = self.with_context(ctx).refund()
new_order = self._blank_refund(res)
for wizard_line in partial_return_wizard.line_ids:
qty = -wizard_line.qty
if qty != 0:
copy_line = wizard_line.pos_order_line_id.copy()
copy_line.write({
'order_id': new_order.id,
'returned_line_id': wizard_line.pos_order_line_id.id,
'qty': qty,
})
return res
def action_pos_order_paid(self):
if self.returned_order_id and self.returned_order_id.invoice_id:
self._action_pos_order_invoice()
return super(PosOrder, self).action_pos_order_paid()
def _create_picking_return(self):
self.ensure_one()
picking = self.returned_order_id.picking_id
ctx = dict(self.env.context,
active_ids=picking.ids, active_id=picking.id)
wizard = self.env['stock.return.picking'].with_context(ctx).create({})
# Discard not returned lines
wizard.product_return_moves.filtered(
lambda x: x.product_id not in self.mapped(
'lines.product_id')).unlink()
to_return = {}
for product in self.lines.mapped('product_id'):
to_return[product] = -sum(
self.lines.filtered(
lambda x: x.product_id == product).mapped('qty'))
for move in wizard.product_return_moves:
if to_return[move.product_id] < move.quantity:
move.quantity = to_return[move.product_id]
to_return[move.product_id] -= move.quantity
return wizard
def create_picking(self):
"""Odoo bases return picking if the quantities are negative, but it's
not linked to the original one"""
res = super(PosOrder, self.filtered(lambda x: not x.returned_order_id)
).create_picking()
for order in self.filtered('returned_order_id'):
wizard = order._create_picking_return()
res = wizard.create_returns()
order.write({'picking_id': res['res_id']})
return res
class PosOrderLine(models.Model):
_inherit = 'pos.order.line'
returned_line_id = fields.Many2one(
comodel_name='pos.order.line',
string='Returned Order',
readonly=True,
)
refund_line_ids = fields.One2many(
comodel_name='pos.order.line',
inverse_name='returned_line_id',
string='Refund Lines',
readonly=True,
)
@api.model
def max_returnable_qty(self, ignored_line_ids):
qty = self.qty
for refund_line in self.refund_line_ids:
if refund_line.id not in ignored_line_ids:
qty += refund_line.qty
return qty
@api.constrains('returned_line_id', 'qty')
def _check_return_qty(self):
if self.env.context.get('do_not_check_negative_qty', False):
return True
for line in self:
if line.returned_line_id and -line.qty > line.returned_line_id.qty:
raise ValidationError(_(
"You can not return %d %s of %s because the original "
"Order line only mentions %d %s."
) % (-line.qty, line.product_id.uom_id.name,
line.product_id.name, line.returned_line_id.qty,
line.product_id.uom_id.name))
if (line.returned_line_id and
-line.qty >
line.returned_line_id.max_returnable_qty([line.id])):
raise ValidationError(_(
"You can not return %d %s of %s because some refunds"
" has been yet done.\n Maximum quantity allowed :"
" %d %s."
) % (-line.qty, line.product_id.uom_id.name,
line.product_id.name,
line.returned_line_id.max_returnable_qty([line.id]),
line.product_id.uom_id.name))
if (not line.returned_line_id and
line.qty < 0 and not
line.product_id.product_tmpl_id.pos_allow_negative_qty):
raise ValidationError(_(
"For legal and traceability reasons, you can not set a"
" negative quantity (%d %s of %s), without using "
"return wizard."
) % (line.qty, line.product_id.uom_id.name,
line.product_id.name))

13
pos_order_return/models/product_template.py

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright 2016-2018 Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import fields, models
class ProductTemplate(models.Model):
_inherit = 'product.template'
pos_allow_negative_qty = fields.Boolean(
string='Allow Negative Quantity on PoS',
)

BIN
pos_order_return/static/description/icon.png

After

Width: 375  |  Height: 375  |  Size: 7.7 KiB

BIN
pos_order_return/static/description/initial_pos_order_required.png

After

Width: 590  |  Height: 179  |  Size: 10 KiB

BIN
pos_order_return/static/description/partial_return_wizard.png

After

Width: 890  |  Height: 386  |  Size: 24 KiB

BIN
pos_order_return/static/description/product_returnable_bottle.png

After

Width: 849  |  Height: 324  |  Size: 48 KiB

BIN
pos_order_return/static/description/returned_qty_over_initial.png

After

Width: 592  |  Height: 181  |  Size: 10 KiB

BIN
pos_order_return/static/description/sum_returned_qty_over_initial.png

After

Width: 592  |  Height: 182  |  Size: 12 KiB

BIN
pos_order_return/static/img/product_returnable_bottle-image.jpg

After

Width: 600  |  Height: 600  |  Size: 22 KiB

3
pos_order_return/tests/__init__.py

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import test_pos_order_return

102
pos_order_return/tests/test_pos_order_return.py

@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Tecnativa - David Vidal
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo.tests import common
@common.at_install(False)
@common.post_install(True)
class TestPOSOrderReturn(common.HttpCase):
def setUp(self):
super(TestPOSOrderReturn, self).setUp()
self.partner = self.env['res.partner'].create({
'name': 'Mr. Odoo',
})
self.product_1 = self.env['product.product'].create({
'name': 'Test product 1',
'standard_price': 1.0,
'type': 'product',
'pos_allow_negative_qty': False,
'taxes_id': False,
})
self.product_2 = self.env['product.product'].create({
'name': 'Test product 2',
'standard_price': 1.0,
'type': 'product',
'pos_allow_negative_qty': True,
'taxes_id': False,
})
self.PosOrder = self.env['pos.order']
self.pos_config = self.env.ref('point_of_sale.pos_config_main')
self.pos_config.open_session_cb()
self.pos_order = self.PosOrder.create({
'session_id': self.pos_config.current_session_id.id,
'partner_id': self.partner.id,
'pricelist_id': self.partner.property_product_pricelist.id,
'lines': [
(0, 0, {
'name': 'POSLINE/0001',
'product_id': self.product_1.id,
'price_unit': 450,
'qty': 2.0,
}),
(0, 0, {
'name': 'POSLINE/0002',
'product_id': self.product_2.id,
'price_unit': 450,
'qty': 2.0,
}),
(0, 0, {
'name': 'POSLINE/0003',
'product_id': self.product_1.id,
'price_unit': 450,
'qty': 2.0,
}),
],
})
pos_make_payment = self.env['pos.make.payment'].with_context({
'active_ids': [self.pos_order.id],
'active_id': self.pos_order.id,
}).create({})
pos_make_payment.with_context(active_id=self.pos_order.id).check()
self.pos_order.create_picking()
res = self.pos_order.action_pos_order_invoice()
self.invoice = self.env['account.invoice'].browse(res['res_id'])
def test_pos_order_full_refund(self):
self.pos_order.refund()
refund_order = self.pos_order.refund_order_ids
self.assertEqual(len(refund_order), 1)
pos_make_payment = self.env['pos.make.payment'].with_context({
'active_ids': refund_order.ids,
'active_id': refund_order.id,
}).create({})
pos_make_payment.with_context(active_id=refund_order.id).check()
refund_invoice = refund_order.invoice_id
self.assertEqual(refund_invoice.refund_invoice_id, self.invoice)
# Partner balance is 0
self.assertEqual(sum(
self.partner.mapped('invoice_ids.amount_total_signed')), 0)
def test_pos_order_partial_refund(self):
partial_refund = self.env['pos.partial.return.wizard'].with_context({
'active_ids': self.pos_order.ids,
'active_id': self.pos_order.id,
}).create({})
# Return just 1 item from line POSLINE/0001
partial_refund.line_ids[0].qty = 1
# Return 2 items from line POSLINE/0003
partial_refund.line_ids[1].qty = 2
partial_refund.confirm()
refund_order = self.pos_order.refund_order_ids
self.assertEqual(len(refund_order), 1)
self.assertEqual(len(refund_order.lines), 2)
pos_make_payment = self.env['pos.make.payment'].with_context({
'active_ids': refund_order.ids,
'active_id': refund_order.id,
}).create({})
pos_make_payment.with_context(active_id=refund_order.id).check()
# Partner balance is 1350
self.assertEqual(sum(
self.partner.mapped('invoice_ids.amount_total_signed')), 1350)

56
pos_order_return/views/pos_order_view.xml

@ -0,0 +1,56 @@
<?xml version="1.0"?>
<!-- Copyright 2016-2018 Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).-->
<odoo>
<record id="action_pos_partial_return_wizard" model="ir.actions.act_window">
<field name="name">Partial Return Wizard</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.partial.return.wizard</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<record id="view_pos_order_form" model="ir.ui.view">
<field name="model">pos.order</field>
<field name="inherit_id" ref="point_of_sale.view_pos_pos_form"/>
<field name="arch" type="xml">
<button name="refund" position="attributes">
<attribute name="attrs">{'invisible':['|', ('state','=','draft'), ('returned_order_id', '!=', False)]}</attribute>
</button>
<button name="refund" position="after">
<button name="%(action_pos_partial_return_wizard)d" string="Partial Return" type="action"
attrs="{'invisible':['|', ('state','=','draft'), ('returned_order_id', '!=', False)]}"/>
</button>
<notebook>
<page string="Refund" attrs="{'invisible':[('returned_order_id', '=', False), ('refund_order_qty', '=', 0)]}">
<field name="returned_order_id" attrs="{'invisible':[('returned_order_id', '=', False)]}"/>
<field name="refund_order_ids" attrs="{'invisible':[('refund_order_qty', '=', 0)]}"/>
<field name="refund_order_qty" invisible="1"/>
</page>
</notebook>
<xpath expr="//field[@name='lines']/form/group" position="after">
<group col="4" string="Refund">
<field name="returned_line_id" colspan="4"/>
<field name="refund_line_ids" />
</group>
</xpath>
</field>
</record>
<record id="view_pos_order_line_form" model="ir.ui.view">
<field name="model">pos.order.line</field>
<field name="inherit_id" ref="point_of_sale.view_pos_order_line_form"/>
<field name="arch" type="xml">
<group position="after">
<group col="4" string="Refund">
<field name="returned_line_id" colspan="4"/>
<field name="refund_line_ids" />
</group>
</group>
</field>
</record>
</odoo>

17
pos_order_return/views/product_product_view.xml

@ -0,0 +1,17 @@
<?xml version="1.0"?>
<!-- Copyright 2016-2018 Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).-->
<odoo>
<record id="view_product_template_form" model="ir.ui.view">
<field name="model">product.template</field>
<field name="inherit_id" ref="point_of_sale.product_template_form_view"/>
<field name="arch" type="xml">
<field name="to_weight" position="after">
<field name="pos_allow_negative_qty" />
</field>
</field>
</record>
</odoo>

3
pos_order_return/wizard/__init__.py

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import pos_partial_return_wizard

72
pos_order_return/wizard/pos_partial_return_wizard.py

@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
# Copyright 2016-2018 Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
class PosPartialReturnWizard(models.TransientModel):
_name = 'pos.partial.return.wizard'
order_id = fields.Many2one(
comodel_name='pos.order',
string='Order to Return',
)
line_ids = fields.One2many(
comodel_name='pos.partial.return.wizard.line',
inverse_name='wizard_id',
string='Lines to Return',
)
def confirm(self):
self.ensure_one()
return self[0].order_id.partial_refund(self[0])
@api.model
def default_get(self, fields):
order_obj = self.env['pos.order']
res = super(PosPartialReturnWizard, self).default_get(fields)
order = order_obj.browse(self.env.context.get('active_id', False))
if order:
line_ids = []
for line in order.lines:
line_ids.append((0, 0, {
'pos_order_line_id': line.id,
'initial_qty': line.qty,
'max_returnable_qty': line.max_returnable_qty([]),
}))
res.update({
'order_id': order.id,
'line_ids': line_ids})
return res
class PosPartialReturnWizardLine(models.TransientModel):
_name = 'pos.partial.return.wizard.line'
wizard_id = fields.Many2one(
comodel_name='pos.partial.return.wizard',
string='Wizard',
)
pos_order_line_id = fields.Many2one(
comodel_name='pos.order.line',
required=True,
readonly=True,
string='Line To Return',
)
initial_qty = fields.Float(
string='Initial Quantity',
readonly=True,
help="Quantity of Product initially sold",
)
max_returnable_qty = fields.Float(
string='Returnable Quantity',
readonly=True,
help="Compute maximum quantity that can be returned for this line, "
"depending of the quantity of the line and other possible "
"refunds.",
)
qty = fields.Float(
string='Returned Quantity',
default=0.0,
)

29
pos_order_return/wizard/pos_partial_return_wizard_view.xml

@ -0,0 +1,29 @@
<?xml version="1.0"?>
<!-- Copyright 2016-2018 Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).-->
<odoo>
<record id="view_partial_return_wizard_form" model="ir.ui.view">
<field name="model">pos.partial.return.wizard</field>
<field name="arch" type="xml">
<form string="Partial Return">
<group string="Lines to Return">
<field name="line_ids" nolabel="1">
<tree editable="bottom">
<field name="pos_order_line_id"/>
<field name="initial_qty"/>
<field name="max_returnable_qty"/>
<field name="qty"/>
</tree>
</field>
</group>
<footer>
<button string="Confirm" name="confirm" type="object" class="btn-primary"/>
<button string="Cancel" special="cancel" class="btn-default"/>
</footer>
</form>
</field>
</record>
</odoo>
Loading…
Cancel
Save