Browse Source

[MIG] pos_order_return: Migration to 10.0

pull/269/head
David 7 years ago
parent
commit
00db862fdf
  1. 43
      pos_order_return/README.rst
  2. 2
      pos_order_return/__init__.py
  3. 28
      pos_order_return/__manifest__.py
  4. 4
      pos_order_return/demo/product_product.xml
  5. 210
      pos_order_return/i18n/es.po
  6. 0
      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. 0
      pos_order_return/static/description/initial_pos_order_required.png
  12. 0
      pos_order_return/static/description/partial_return_wizard.png
  13. 0
      pos_order_return/static/description/product_returnable_bottle.png
  14. 0
      pos_order_return/static/description/returned_qty_over_initial.png
  15. 0
      pos_order_return/static/description/sum_returned_qty_over_initial.png
  16. 0
      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. 29
      pos_order_return/views/pos_order_view.xml
  20. 7
      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. 7
      pos_order_return/wizard/pos_partial_return_wizard_view.xml
  24. 28
      pos_return_order/__openerp__.py
  25. 6
      pos_return_order/models/__init__.py
  26. 80
      pos_return_order/models/pos_order.py
  27. 65
      pos_return_order/models/pos_order_line.py
  28. 40
      pos_return_order/models/pos_partial_return_wizard.py
  29. 28
      pos_return_order/models/pos_partial_return_wizard_line.py
  30. 15
      pos_return_order/models/product_template.py
  31. BIN
      pos_return_order/static/description/icon.png
  32. 20
      pos_return_order/views/action.xml
  33. 23
      pos_return_order/views/pos_order_line_view.xml

43
pos_return_order/README.rst → pos_order_return/README.rst

@ -1,10 +1,10 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3
==================
PoS - Return Order
==================
================
PoS Order Return
================
This module extends the functionality of odoo Point Of Sale about POS Order
returns.
@ -15,46 +15,48 @@ 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.
Implemented Features
====================
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:
* A wizard that allow to select just some products to return:
.. image:: /pos_order_return/static/description/partial_return_wizard.png
.. image:: /pos_return_order/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_return_order/static/description/returned_qty_over_initial.png
.. 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_return_order/static/description/sum_returned_qty_over_initial.png
.. 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_return_order/static/description/initial_pos_order_required.png
.. image:: /pos_order_return/static/description/initial_pos_order_required.png
Configuration
=============
In some case, you want to let the possibility to allow negative quantity
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, ...
products like returnable products, etc.
In that case, a checkbox is possible on Product Form View to allow such case
.. image:: /pos_return_order/static/description/product_returnable_bottle.png
Usage
=====
.. 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/9.0
:target: https://runbot.odoo-community.org/runbot/184/10.0
Bug Tracker
===========
@ -71,6 +73,7 @@ Contributors
------------
* Sylvain LE GAL <https://twitter.com/legalsylvain>
* David Vidal <david.vidal@tecnativa.com>
Funders
-------

2
pos_return_order/__init__.py → pos_order_return/__init__.py

@ -1,2 +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,
}

4
pos_return_order/demo/product_product.xml → pos_order_return/demo/product_product.xml

@ -2,17 +2,15 @@
<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="pos_categ_id" ref="point_of_sale.soft" />
<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_return_order/static/img/product_returnable_bottle-image.jpg"/>
<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"

0
pos_return_order/i18n/fr.po → pos_order_return/i18n/fr.po

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

0
pos_return_order/static/description/initial_pos_order_required.png → pos_order_return/static/description/initial_pos_order_required.png

Before

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

After

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

0
pos_return_order/static/description/partial_return_wizard.png → pos_order_return/static/description/partial_return_wizard.png

Before

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

After

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

0
pos_return_order/static/description/product_returnable_bottle.png → pos_order_return/static/description/product_returnable_bottle.png

Before

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

After

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

0
pos_return_order/static/description/returned_qty_over_initial.png → pos_order_return/static/description/returned_qty_over_initial.png

Before

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

After

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

0
pos_return_order/static/description/sum_returned_qty_over_initial.png → pos_order_return/static/description/sum_returned_qty_over_initial.png

Before

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

After

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

0
pos_return_order/static/img/product_returnable_bottle-image.jpg → pos_order_return/static/img/product_returnable_bottle-image.jpg

Before

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

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)

29
pos_return_order/views/pos_order_view.xml → pos_order_return/views/pos_order_view.xml

@ -1,12 +1,18 @@
<?xml version="1.0"?>
<!--
Copyright (C) 2016-Today: La Louve (<http://www.lalouve.net/>)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<!-- 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"/>
@ -34,4 +40,17 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
</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>

7
pos_return_order/views/product_product_view.xml → pos_order_return/views/product_product_view.xml

@ -1,9 +1,6 @@
<?xml version="1.0"?>
<!--
Copyright (C) 2016-Today: La Louve (<http://www.lalouve.net/>)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<!-- Copyright 2016-2018 Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).-->
<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,
)

7
pos_return_order/views/pos_partial_return_wizard_view.xml → pos_order_return/wizard/pos_partial_return_wizard_view.xml

@ -1,9 +1,6 @@
<?xml version="1.0"?>
<!--
Copyright (C) 2016-Today: La Louve (<http://www.lalouve.net/>)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<!-- Copyright 2016-2018 Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).-->
<odoo>

28
pos_return_order/__openerp__.py

@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016-Today: La Louve (<http://www.lalouve.net/>)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
'name': 'Point of Sale Return Order',
'version': '9.0.1.0.0',
'category': 'Point Of Sale',
'summary': 'Point of Sale Return Order',
'author': 'La Louve, GRAP, Odoo Community Association (OCA)',
'website': 'http://www.lalouve.net',
'depends': [
'point_of_sale',
],
'data': [
'views/action.xml',
'views/pos_order_view.xml',
'views/pos_order_line_view.xml',
'views/product_product_view.xml',
'views/pos_partial_return_wizard_view.xml',
],
'demo': [
'demo/product_product.xml',
],
'installable': True,
}

6
pos_return_order/models/__init__.py

@ -1,6 +0,0 @@
# -*- coding: utf-8 -*-
from . import product_template
from . import pos_order
from . import pos_order_line
from . import pos_partial_return_wizard
from . import pos_partial_return_wizard_line

80
pos_return_order/models/pos_order.py

@ -1,80 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016-Today: La Louve (<http://www.lalouve.net/>)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import fields, models, api
from openerp.addons import decimal_precision as dp
class PosOrder(models.Model):
_inherit = 'pos.order'
# Column Section
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',
digits=dp.get_precision('Product Unit of Measure'))
# Compute Section
@api.multi
def _compute_refund_order_qty(self):
for order in self:
order.refund_order_qty = len(order.refund_order_ids)
@api.multi
def _blank_refund(self):
self.ensure_one()
# Call super to use original refund algorithm (session management, ...)
ctx = self.env.context.copy()
ctx.update({'do_not_check_negative_qty': True})
res = super(PosOrder, self.with_context(ctx)).refund()
# Link Order
original_order = self[0]
new_order = self.browse(res['res_id'])
new_order.returned_order_id = original_order.id
# Remove created lines and recreate and link Lines
new_order.lines.unlink()
return res, new_order
# Action Section
@api.multi
def refund(self):
res, new_order = self._blank_refund()
for line in self[0].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
# Action Section
@api.multi
def partial_refund(self, partial_return_wizard):
res, new_order = self._blank_refund()
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

65
pos_return_order/models/pos_order_line.py

@ -1,65 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016-Today: La Louve (<http://www.lalouve.net/>)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import fields, models, api
from openerp.exceptions import ValidationError
from openerp.tools.translate import _
class PosOrderLine(models.Model):
_inherit = 'pos.order.line'
# Column Section
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)
# Compute Section
@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
# Constraint Section
@api.one
@api.constrains('returned_line_id', 'qty')
def _check_return_qty(self):
if self.env.context.get('do_not_check_negative_qty', False):
return True
if self.returned_line_id:
if - self.qty > self.returned_line_id.qty:
raise ValidationError(_(
"You can not return %d %s of %s because the original"
" Order line only mentions %d %s.") % (
- self.qty, self.product_id.uom_id.name,
self.product_id.name, self.returned_line_id.qty,
self.product_id.uom_id.name))
elif - self.qty >\
self.returned_line_id.max_returnable_qty([self.id]):
raise ValidationError(_(
"You can not return %d %s of %s because some refunds"
" has been yet done.\n Maximum quantity allowed :"
" %d %s.") % (
- self.qty, self.product_id.uom_id.name,
self.product_id.name,
self.returned_line_id.max_returnable_qty([self.id]),
self.product_id.uom_id.name))
else:
if self.qty < 0 and\
not self.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.") % (
self.qty, self.product_id.uom_id.name,
self.product_id.name))

40
pos_return_order/models/pos_partial_return_wizard.py

@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016-Today: La Louve (<http://www.lalouve.net/>)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields, api
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')
@api.multi
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

28
pos_return_order/models/pos_partial_return_wizard_line.py

@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016-Today: La Louve (<http://www.lalouve.net/>)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import fields, models
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)

15
pos_return_order/models/product_template.py

@ -1,15 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016-Today: La Louve (<http://www.lalouve.net/>)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import fields, models
class ProductTemplate(models.Model):
_inherit = 'product.template'
# Column Section
pos_allow_negative_qty = fields.Boolean(
string='Allow Negative Quantity on PoS')

BIN
pos_return_order/static/description/icon.png

Before

Width: 64  |  Height: 64  |  Size: 4.6 KiB

20
pos_return_order/views/action.xml

@ -1,20 +0,0 @@
<?xml version="1.0"?>
<!--
Copyright (C) 2016-Today: La Louve (<http://www.lalouve.net/>)
@author: 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>
</odoo>

23
pos_return_order/views/pos_order_line_view.xml

@ -1,23 +0,0 @@
<?xml version="1.0"?>
<!--
Copyright (C) 2016-Today: La Louve (<http://www.lalouve.net/>)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<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>
Loading…
Cancel
Save