Skip to content

Commit

Permalink
Merge pull request #128 from pedrobaeza/8.0-stock_picking_invoicing_u…
Browse files Browse the repository at this point in the history
…nified

[8.0] [ADD] stock_picking_invoicing_unified
  • Loading branch information
pedrobaeza committed Mar 10, 2016
2 parents 0510181 + f2f5716 commit 8d9250b
Show file tree
Hide file tree
Showing 9 changed files with 397 additions and 0 deletions.
82 changes: 82 additions & 0 deletions stock_picking_invoicing_unified/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3

===============================
Stock Picking Invoicing Unified
===============================

Odoo allows to select several pickings and click on *Create Draft Invoices*
option to create the corresponding invoice(s)/refund(s). If you have
selected several partners and you have checked the option *Group by partner*,
it will create a single invoice or refund per partner.

But it only takes into account the first picking for selecting the type of the
invoice you are going to create (customer/supplier invoice/refund), mixing all
the lines on it. And not only that: if you have returned pickings, the returned
quantities are summed to the rest, instead of decreasing the amount to invoice,
which is the common practise when you have some returns.

This module fixes this problem, allowing to invoice them all together:
if you have delivered and received goods for the same customer and you
have checked the option *Group by partner*, you will have a single
invoice with the goods delivered and received and the quantities of the
goods received will be negative. So it will avoid you to send both an
invoice and a refund to your customer and have to reconciliate them to
compute the good residual amount.

Usage
=====

* Select several pickings from any of the menus that allows it (
*Warehouse > All Operations* and click on any of the lines,
*Purchases > Invoice Control > On Incoming Shipments*, etc).
* Click on *More > Create Draft Invoices*.
* In the resulting dialog, the proper invoices types that are going to be
created are computed, and you have to select the journals for that types.
* Click on *Create* button, and the invoices will be correctly created.
* The lines of pickings that are not of the greater picking type are created
with negative price unit.

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/95/8.0

Known issues / Roadmap
======================

* Add tests

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/account-invoicing/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
`here <https://github.com/OCA/account-invoicing/issues/new?body=module:%20
stock_picking_invoicing_unified%0Aversion:%20
8.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.


Credits
=======

Contributors
------------
* Ainara Galdona <ainaragaldona@avanzosc.es>
* Pedro M. Baeza <pedro.baeza@serviciobaeza.com>

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.
6 changes: 6 additions & 0 deletions stock_picking_invoicing_unified/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# © 2016 Ainara Galdona <ainaragaldona@avanzosc.es> - Avanzosc S.L.
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html

from . import models
from . import wizard
23 changes: 23 additions & 0 deletions stock_picking_invoicing_unified/__openerp__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# © 2016 Ainara Galdona <ainaragaldona@avanzosc.es> - Avanzosc S.L.
# © 2016 Serv. Tecnol. Avanzados - Pedro M. Baeza
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html

{
"name": "Stock Picking Invoicing Unified",
"summary": "Create invoices/refunds from pickings of different types",
"version": "8.0.1.0.0",
'author': 'Serv. Tecnol. Avanzados - Pedro M. Baeza, '
'AvanzOSC, '
'Odoo Community Association (OCA)',
'website': "http://github.com/OCA/account-invoicing",
"license": "AGPL-3",
'contributors': [
"Ainara Galdona <ainaragaldona@avanzosc.es>",
"Pedro M. Baeza <pedro.baeza@serviciosbaeza.com>"
],
"depends": ['stock_account'],
"category": "Warehouse Management",
"data": ['wizard/stock_invoice_onshipping_view.xml'],
"installable": True
}
67 changes: 67 additions & 0 deletions stock_picking_invoicing_unified/i18n/es.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * stock_picking_invoicing_unified
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-02-19 19:16+0000\n"
"PO-Revision-Date: 2016-02-19 19:16+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: stock_picking_invoicing_unified
#: field:stock.invoice.onshipping,purchase_journal:0
msgid "Purchase Journal"
msgstr "Diario para facturas de compras"

#. module: stock_picking_invoicing_unified
#: field:stock.invoice.onshipping,purchase_refund_journal:0
msgid "Purchase Refund Journal"
msgstr "Diario para rectificaciones de compras"

#. module: stock_picking_invoicing_unified
#: field:stock.invoice.onshipping,sale_journal:0
msgid "Sale Journal"
msgstr "Diario para facturas de ventas"

#. module: stock_picking_invoicing_unified
#: field:stock.invoice.onshipping,sale_refund_journal:0
msgid "Sale Refund Journal"
msgstr "Diario para rectificaciones de ventas"

#. module: stock_picking_invoicing_unified
#: field:stock.invoice.onshipping,show_purchase_journal:0
msgid "Show Purchase Journal"
msgstr "Mostrar diario de compras"

#. module: stock_picking_invoicing_unified
#: field:stock.invoice.onshipping,show_purchase_refund_journal:0
msgid "Show Refund Purchase Journal"
msgstr "Mostrar diario de rectificaciones de compras"

#. module: stock_picking_invoicing_unified
#: field:stock.invoice.onshipping,show_sale_refund_journal:0
msgid "Show Refund Sale Journal"
msgstr "Mostrar diario de rectificaciones de ventas"

#. module: stock_picking_invoicing_unified
#: field:stock.invoice.onshipping,show_sale_journal:0
msgid "Show Sale Journal"
msgstr "Mostrar diario de ventas"

#. module: stock_picking_invoicing_unified
#: model:ir.model,name:stock_picking_invoicing_unified.model_stock_invoice_onshipping
msgid "Stock Invoice Onshipping"
msgstr "Factura en el envío de existencias"

#. module: stock_picking_invoicing_unified
#: model:ir.model,name:stock_picking_invoicing_unified.model_stock_move
msgid "Stock Move"
msgstr "Movimiento de existencias"

5 changes: 5 additions & 0 deletions stock_picking_invoicing_unified/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# (c) 2016 Ainara Galdona <ainaragaldona@avanzosc.es> - Avanzosc S.L.
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html

from . import stock_move
26 changes: 26 additions & 0 deletions stock_picking_invoicing_unified/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# (c) 2016 Ainara Galdona <ainaragaldona@avanzosc.es> - Avanzosc S.L.
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html

from openerp import models, api


class StockMove(models.Model):

_inherit = 'stock.move'

@api.model
def _get_invoice_line_vals(self, move, partner, inv_type):
res = super(StockMove, self)._get_invoice_line_vals(move, partner,
inv_type)
# negative value on quantity
if ((inv_type == 'out_invoice' and
move.location_id.usage == 'customer') or
(inv_type == 'out_refund' and
move.location_dest_id.usage == 'customer') or
(inv_type == 'in_invoice' and
move.location_dest_id.usage == 'supplier') or
(inv_type == 'in_refund' and
move.location_id.usage == 'supplier')):
res['quantity'] *= -1
return res
5 changes: 5 additions & 0 deletions stock_picking_invoicing_unified/wizard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# © 2016 Ainara Galdona <ainaragaldona@avanzosc.es> - Avanzosc S.L.
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html

from . import stock_invoice_onshipping
157 changes: 157 additions & 0 deletions stock_picking_invoicing_unified/wizard/stock_invoice_onshipping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# -*- coding: utf-8 -*-
# © 2016 Ainara Galdona <ainaragaldona@avanzosc.es> - Avanzosc S.L.
# © 2016 Serv. Tecnol. Avanzados - Pedro M. Baeza
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html

from openerp import models, fields, api
from openerp.tools import config


class StockInvoiceOnshipping(models.TransientModel):

_inherit = 'stock.invoice.onshipping'

@api.model
def _default_journal(self, journal_type):
return self.env['account.journal'].search(
[('type', '=', journal_type)])[:1]

journal_id = fields.Many2one(required=False)
sale_journal = fields.Many2one(
comodel_name='account.journal', string='Sale Journal',
domain="[('type', '=', 'sale')]",
default=lambda self: self._default_journal('sale'))
sale_refund_journal = fields.Many2one(
comodel_name='account.journal', string='Sale Refund Journal',
domain="[('type', '=', 'sale_refund')]",
default=lambda self: self._default_journal('sale_refund'))
purchase_journal = fields.Many2one(
comodel_name='account.journal', string='Purchase Journal',
domain="[('type', '=', 'purchase')]",
default=lambda self: self._default_journal('purchase'))
purchase_refund_journal = fields.Many2one(
comodel_name='account.journal', string="Purchase Refund Journal",
domain="[('type', '=', 'purchase_refund')]",
default=lambda self: self._default_journal('purchase_refund'))
show_sale_journal = fields.Boolean(string="Show Sale Journal")
show_sale_refund_journal = fields.Boolean(
string="Show Refund Sale Journal")
show_purchase_journal = fields.Boolean(string="Show Purchase Journal")
show_purchase_refund_journal = fields.Boolean(
string="Show Refund Purchase Journal")

@api.multi
@api.onchange('group')
def onchange_group(self):
self.ensure_one()
(sale_pickings, sale_refund_pickings, purchase_pickings,
purchase_refund_pickings) = self.get_split_pickings()
self.show_sale_journal = bool(sale_pickings)
self.show_sale_refund_journal = bool(sale_refund_pickings)
self.show_purchase_journal = bool(purchase_pickings)
self.show_purchase_refund_journal = bool(purchase_refund_pickings)

@api.multi
def get_partner_sum(self, pickings, partner, inv_type, picking_type,
usage):
move_obj = self.env['stock.move']
pickings = pickings.filtered(lambda x: x.picking_type_id.code ==
picking_type and x.partner_id == partner)
if picking_type == 'outgoing':
moves = pickings.mapped('move_lines').filtered(
lambda x: x.location_dest_id.usage == usage)
else:
moves = pickings.mapped('move_lines').filtered(
lambda x: x.location_id.usage == usage)
return (sum([(move_obj._get_price_unit_invoice(m, inv_type) *
m.product_uom_qty) for m in moves]),
moves.mapped('picking_id'))

@api.multi
def get_split_pickings_grouped(self, pickings):
sale_pickings = self.env['stock.picking']
sale_refund_pickings = self.env['stock.picking']
purchase_pickings = self.env['stock.picking']
purchase_refund_pickings = self.env['stock.picking']

for partner in pickings.mapped('partner_id'):
so_sum, so_pickings = self.get_partner_sum(
pickings, partner, 'out_invoice', 'outgoing', 'customer')
si_sum, si_pickings = self.get_partner_sum(
pickings, partner, 'out_invoice', 'incoming', 'customer')
if (so_sum - si_sum) >= 0:
sale_pickings |= (so_pickings | si_pickings)
else:
sale_refund_pickings |= (so_pickings | si_pickings)
pi_sum, pi_pickings = self.get_partner_sum(
pickings, partner, 'in_invoice', 'incoming', 'supplier')
po_sum, po_pickings = self.get_partner_sum(
pickings, partner, 'in_invoice', 'outgoing', 'supplier')
if (pi_sum - po_sum) >= 0:
purchase_pickings |= (pi_pickings | po_pickings)
else:
purchase_refund_pickings |= (pi_pickings | po_pickings)
return (sale_pickings, sale_refund_pickings, purchase_pickings,
purchase_refund_pickings)

@api.multi
def get_split_pickings_nogrouped(self, pickings):
sale_pickings = pickings.filtered(
lambda x: x.picking_type_id.code == 'outgoing' and
x.move_lines[:1].location_dest_id.usage == 'customer')
# use [:1] instead of [0] to avoid a errors on empty pickings
sale_refund_pickings = pickings.filtered(
lambda x: x.picking_type_id.code == 'incoming' and
x.move_lines[:1].location_id.usage == 'customer')
purchase_pickings = pickings.filtered(
lambda x: x.picking_type_id.code == 'incoming' and
x.move_lines[:1].location_id.usage == 'supplier')
purchase_refund_pickings = pickings.filtered(
lambda x: x.picking_type_id.code == 'outgoing' and
x.move_lines[:1].location_dest_id.usage == 'supplier')
return (sale_pickings, sale_refund_pickings, purchase_pickings,
purchase_refund_pickings)

@api.multi
def get_split_pickings(self):
picking_obj = self.env['stock.picking']
pickings = picking_obj.browse(self.env.context.get('active_ids', []))
if self.group:
return self.get_split_pickings_grouped(pickings)
else:
return self.get_split_pickings_nogrouped(pickings)

@api.multi
def create_invoice(self):
if (config['test_enable'] and
not self.env.context.get('test_picking_invoicing_unified')):
return super(StockInvoiceOnshipping, self).create_invoice()
self.ensure_one()
res = []
(sale_pickings, sale_refund_pickings, purchase_pickings,
purchase_refund_pickings) = self.get_split_pickings()
if sale_pickings:
pickings = sale_pickings.with_context(
date_inv=self.invoice_date, inv_type='out_invoice')
res += pickings.action_invoice_create(
journal_id=self.sale_journal.id,
group=self.group, type='out_invoice')
if sale_refund_pickings:
pickings = sale_refund_pickings.with_context(
date_inv=self.invoice_date, inv_type='out_refund')
res += pickings.action_invoice_create(
journal_id=self.sale_refund_journal.id,
group=self.group, type='out_refund')
if purchase_pickings:
pickings = purchase_pickings.with_context(
date_inv=self.invoice_date, inv_type='in_invoice')
res += pickings.action_invoice_create(
journal_id=self.purchase_journal.id,
group=self.group, type='in_invoice')
if purchase_refund_pickings:
pickings = purchase_refund_pickings.with_context(
date_inv=self.invoice_date, inv_type='in_refund')
res += pickings.action_invoice_create(
journal_id=self.purchase_refund_journal.id, group=self.group,
type='in_refund')
return res

0 comments on commit 8d9250b

Please sign in to comment.