Browse Source

[MIG] pos_invoicing from 8.0 to 12.0

pull/370/head
Sylvain LE GAL 5 years ago
parent
commit
aabc8174a4
  1. 15
      pos_invoicing/__manifest__.py
  2. 13
      pos_invoicing/demo/res_groups.xml
  3. 38
      pos_invoicing/i18n/fr.po
  4. 56
      pos_invoicing/i18n/pos_invoicing.pot
  5. 3
      pos_invoicing/models/__init__.py
  6. 14
      pos_invoicing/models/account_invoice.py
  7. 22
      pos_invoicing/models/account_payment.py
  8. 28
      pos_invoicing/models/account_voucher.py
  9. 13
      pos_invoicing/models/pos_order.py
  10. 59
      pos_invoicing/models/pos_session.py
  11. 43
      pos_invoicing/readme/DESCRIPTION.rst
  12. 4
      pos_invoicing/readme/ROADMAP.rst
  13. BIN
      pos_invoicing/static/description/account_invoice_form.png
  14. BIN
      pos_invoicing/static/description/icon.png
  15. 1
      pos_invoicing/tests/__init__.py
  16. 148
      pos_invoicing/tests/test_module.py
  17. 9
      pos_invoicing/views/view_account_invoice.xml

15
pos_invoicing/__openerp__.py → pos_invoicing/__manifest__.py

@ -1,4 +1,3 @@
# coding: utf-8
# Copyright (C) 2013 - Today: GRAP (http://www.grap.coop)
# @author: Julien WESTE
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
@ -6,10 +5,10 @@
{
'name': 'Point Of Sale - Invoicing',
'summary': 'Handle invoicing from Point Of Sale',
'version': '8.0.3.0.0',
'version': '12.0.3.0.0',
'category': 'Point of Sale',
'author': 'GRAP',
'website': 'http://www.grap.coop',
'author': 'GRAP, Odoo Community Association (OCA)',
'website': 'http://www.github.com/OCA/pos',
'license': 'AGPL-3',
'depends': [
'point_of_sale',
@ -17,11 +16,5 @@
'data': [
'views/view_account_invoice.xml',
],
'demo': [
'demo/res_groups.xml',
],
'images': [
'static/description/account_invoice_form.png',
],
'installable': False,
'installable': True,
}

13
pos_invoicing/demo/res_groups.xml

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2015 - Today: GRAP (http://www.grap.coop)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<openerp><data>
<record id="account.group_account_manager" model="res.groups">
<field name="users" eval="[(4, ref('base.user_root'))]"/>
</record>
</data></openerp>

38
pos_invoicing/i18n/fr.po

@ -1,13 +1,13 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_invoicing
# * pos_invoicing
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-08-02 13:14+0000\n"
"PO-Revision-Date: 2018-08-02 13:14+0000\n"
"POT-Creation-Date: 2019-07-11 16:18+0000\n"
"PO-Revision-Date: 2019-07-11 16:18+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@ -16,12 +16,7 @@ msgstr ""
"Plural-Forms: \n"
#. module: pos_invoicing
#: model:ir.model,name:pos_invoicing.model_account_voucher
msgid "Accounting Voucher"
msgstr "Justificatif comptable"
#. module: pos_invoicing
#: help:account.invoice,pos_pending_payment:0
#: model:ir.model.fields,help:pos_invoicing.field_account_invoice__pos_pending_payment
msgid "Indicates an invoice for which there are pending payments in the Point of Sale. \n"
"The invoice will be marked as paid when the session will be closed."
msgstr "La case est cochée si il y a des paiements en cours dans le point de vente. \n"
@ -33,18 +28,29 @@ msgid "Invoice"
msgstr "Facture"
#. module: pos_invoicing
#: view:account.invoice:pos_invoicing.view_account_invoice_form
#: field:account.invoice,pos_pending_payment:0
#: model:ir.model,name:pos_invoicing.model_account_payment
msgid "Payments"
msgstr "Paiements"
#. module: pos_invoicing
#: model:ir.model.fields,field_description:pos_invoicing.field_account_invoice__pos_pending_payment
msgid "PoS - Pending Payment"
msgstr "PdV - Paiement en cours"
#. module: pos_invoicing
#: model:ir.model,name:pos_invoicing.model_pos_order
msgid "Point of Sale"
msgstr "Point de Vente"
#: model:ir.model,name:pos_invoicing.model_pos_session
msgid "Point of Sale Session"
msgstr "Session du point de vente"
#. module: pos_invoicing
#: code:addons/pos_invoicing/models/account_invoice.py:37
#: code:addons/pos_invoicing/models/account_invoice.py:36
#: code:addons/pos_invoicing/models/account_invoice.py:38
#, python-format
msgid "You can not realize this action on the invoice(s) %s because there are pending payments in the Point of Sale."
msgstr "Vous ne pouvez pas réaliser cette action sur la / les facture(s) %s car il y a des paiements en cours dans le point de vente."
#. module: pos_invoicing
#: code:addons/pos_invoicing/models/account_payment.py:17
#, python-format
msgid "You can not realize this action on the payments(s) %s because there are pending payments in the Point of Sale."
msgstr "Vous ne pouvez pas réaliser cette action sur la / les paiement(s) %s car il y a des paiements en cours dans le point de vente."

56
pos_invoicing/i18n/pos_invoicing.pot

@ -0,0 +1,56 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_invoicing
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-07-11 16:17+0000\n"
"PO-Revision-Date: 2019-07-11 16:17+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_invoicing
#: model:ir.model.fields,help:pos_invoicing.field_account_invoice__pos_pending_payment
msgid "Indicates an invoice for which there are pending payments in the Point of Sale. \n"
"The invoice will be marked as paid when the session will be closed."
msgstr ""
#. module: pos_invoicing
#: model:ir.model,name:pos_invoicing.model_account_invoice
msgid "Invoice"
msgstr ""
#. module: pos_invoicing
#: model:ir.model,name:pos_invoicing.model_account_payment
msgid "Payments"
msgstr ""
#. module: pos_invoicing
#: model:ir.model.fields,field_description:pos_invoicing.field_account_invoice__pos_pending_payment
msgid "PoS - Pending Payment"
msgstr ""
#. module: pos_invoicing
#: model:ir.model,name:pos_invoicing.model_pos_session
msgid "Point of Sale Session"
msgstr ""
#. module: pos_invoicing
#: code:addons/pos_invoicing/models/account_invoice.py:36
#: code:addons/pos_invoicing/models/account_invoice.py:38
#, python-format
msgid "You can not realize this action on the invoice(s) %s because there are pending payments in the Point of Sale."
msgstr ""
#. module: pos_invoicing
#: code:addons/pos_invoicing/models/account_payment.py:17
#, python-format
msgid "You can not realize this action on the payments(s) %s because there are pending payments in the Point of Sale."
msgstr ""

3
pos_invoicing/models/__init__.py

@ -1,5 +1,4 @@
# coding: utf-8
from . import pos_order
from . import pos_session
from . import account_invoice
from . import account_voucher
from . import account_payment

14
pos_invoicing/models/account_invoice.py

@ -1,11 +1,10 @@
# coding: utf-8
# Copyright (C) 2018 - Today: GRAP (http://www.grap.coop)
# @author: Julien WESTE
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import _, api, fields, models
from openerp.exceptions import Warning as UserError
from odoo import _, api, fields, models
from odoo.exceptions import Warning as UserError
class AccountInvoice(models.Model):
@ -25,9 +24,12 @@ class AccountInvoice(models.Model):
return super(AccountInvoice, self).action_cancel()
@api.multi
def invoice_pay_customer(self):
self._check_pos_pending_payment()
return super(AccountInvoice, self).invoice_pay_customer()
def _get_outstanding_info_JSON(self):
self.ensure_one()
if self.pos_pending_payment:
return
else:
return super()._get_outstanding_info_JSON()
@api.multi
def _check_pos_pending_payment(self):

22
pos_invoicing/models/account_payment.py

@ -0,0 +1,22 @@
# Copyright (C) 2019 - Today: GRAP (http://www.grap.coop)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, api, models
from odoo.exceptions import Warning as UserError
class AccountPayment(models.Model):
_inherit = 'account.payment'
@api.multi
def post(self):
payments = self.filtered(
lambda x: any(x.mapped('invoice_ids.pos_pending_payment')))
if payments:
raise UserError(_(
"You can not realize this action on the payments(s) %s because"
" there are pending payments in the Point of Sale.") % (
', '.join(
[x for x in payments.mapped('communication') if x])))
return super().post()

28
pos_invoicing/models/account_voucher.py

@ -1,28 +0,0 @@
# coding: utf-8
# Copyright (C) 2013 - Today: GRAP (http://www.grap.coop)
# @author: Julien WESTE
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import api, models
class AccountVoucher(models.Model):
_inherit = 'account.voucher'
# Override section
@api.multi
def recompute_voucher_lines(
self, partner_id, journal_id, price, currency_id, ttype, date):
move_line_obj = self.env['account.move.line']
res = super(AccountVoucher, self).recompute_voucher_lines(
partner_id, journal_id, price, currency_id, ttype, date)
for voucher_type in ['line_dr_ids', 'line_cr_ids']:
for voucher_line in res['value'][voucher_type]:
move_line = move_line_obj.browse(voucher_line['move_line_id'])
if move_line.invoice.pos_pending_payment:
res['value'][voucher_type].remove(voucher_line)
return res

13
pos_invoicing/models/pos_order.py

@ -1,18 +1,17 @@
# coding: utf-8
# Copyright (C) 2013 - Today: GRAP (http://www.grap.coop)
# @author: Julien WESTE
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import api, models
from odoo import models
class PosOrder(models.Model):
_inherit = 'pos.order'
@api.multi
def action_invoice(self):
res = super(PosOrder, self).action_invoice()
self.mapped('invoice_id').write({'pos_pending_payment': True})
self.mapped('invoice_id').signal_workflow('invoice_open')
def _prepare_invoice(self):
res = super()._prepare_invoice()
res.update({
'pos_pending_payment': True,
})
return res

59
pos_invoicing/models/pos_session.py

@ -1,4 +1,3 @@
# coding: utf-8
# Copyright (C) 2013 - Today: GRAP (http://www.grap.coop)
# @author: Julien WESTE
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
@ -6,7 +5,7 @@
import logging
from openerp import api, models
from odoo import api, models
_logger = logging.getLogger(__name__)
@ -15,56 +14,8 @@ class PosSession(models.Model):
_inherit = 'pos.session'
@api.multi
def wkf_action_close(self):
move_line_obj = self.env['account.move.line']
res = super(PosSession, self).wkf_action_close()
# Get All Pos Order invoiced during the current Sessions
orders = self.order_ids.filtered(lambda x: x.invoice_id)
for order in orders:
# Get accounting partner
partner = order.partner_id.parent_id or order.partner_id
# Search all Sale Move Lines to reconcile in Sale Journal
sale_move_lines = []
sale_total = 0
for move_line in order.invoice_id.move_id.line_id:
if (move_line.partner_id.id == partner.id and
move_line.account_id.type == 'receivable'):
sale_move_lines.append(move_line)
sale_total += move_line.debit - move_line.credit
# Search all move Line to reconcile in Payment Journals
payment_move_lines = []
payment_total = 0
statement_ids = order.mapped('statement_ids.statement_id').ids
move_lines = move_line_obj.search([
('statement_id', 'in', statement_ids),
('partner_id', '=', partner.id),
('reconcile_id', '=', False)])
for move_line in move_lines:
if (move_line.account_id.type == 'receivable'):
payment_move_lines.append(move_line)
payment_total += move_line.debit - move_line.credit
# Try to reconcile
if payment_total != - sale_total:
# Unable to reconcile
_logger.warning(
"Unable to reconcile the payment of %s #%d."
"(partner : %s)" % (
order.name, order.id, partner.name))
else:
# Reconcile move lines
move_lines = move_line_obj.browse(
[x.id for x in sale_move_lines] +
[x.id for x in payment_move_lines])
move_lines.reconcile('manual', False, False, False)
# Unflag the invoice as 'PoS Pending Payment'
order.invoice_id.pos_pending_payment = False
def action_pos_session_close(self):
res = super().action_pos_session_close()
orders = self.mapped('order_ids').filtered(lambda x: x.invoice_id)
orders.mapped('invoice_id').write({'pos_pending_payment': False})
return res

43
pos_invoicing/readme/DESCRIPTION.rst

@ -1,25 +1,38 @@
When you pay a pos_order, and then create an invoice :
This module extend the Point of Sale Odoo module, regarding invoicing.
* you mustn't register a payment against the invoice as the payment
already exists in POS
* The POS payment will be reconciled with the invoice when the session
is closed
* You mustn't modify the invoice because the amount could become
different from the one registered in POS. Thus we have to
automatically validate the created invoice
This module prevent to make some mistakes in Odoo Point of Sale
regarding invoices generated via Point of Sale.
Functionality
-------------
About the invoices created from POS after payment:
Without this module
~~~~~~~~~~~~~~~~~~~
* automatically validate them and don't allow modifications
* Disable the Pay Button
* Don't display them in the Customer Payment tool
When an invoice generated from Point of Sale is confirmed
it is in a 'open' state, until the session is closed, and the entries are
generated. At this step, invoice will be marked as 'paid' and the related
accounting moves will be reconcilied.
So, as long as the session is not closed, any user can:
* cancel the invoice;
* register a payment;
* reconcile the invoice with an existing payment;
All that action should be prohibited.
With that module
~~~~~~~~~~~~~~~~
All those actions will not be possible anymore.
Note that the changes only impact the opened invoice coming from point of sale,
before the session is closed.
Technically
-----------
add a ``pos_pending_payment`` field on the ``account.invoice`` to mark the
* add a ``pos_pending_payment`` field on the ``account.invoice`` to mark the
items that shouldn't be paid.
This field is checked when the invoice is created from point of sale,
and is unchecked, when the session is closed.
.. figure:: ../static/description/account_invoice_form.png

4
pos_invoicing/readme/ROADMAP.rst

@ -1,4 +0,0 @@
* This module reconcile invoiced orders only if a customer has one invoice per
session.
* It should be great to use the OCA module ``pos_autoreconcile``.

BIN
pos_invoicing/static/description/account_invoice_form.png

Before

Width: 852  |  Height: 430  |  Size: 32 KiB

After

Width: 1196  |  Height: 385  |  Size: 38 KiB

BIN
pos_invoicing/static/description/icon.png

Before

Width: 64  |  Height: 64  |  Size: 4.0 KiB

1
pos_invoicing/tests/__init__.py

@ -1,2 +1 @@
# coding: utf-8
from . import test_module

148
pos_invoicing/tests/test_module.py

@ -1,92 +1,102 @@
# coding: utf-8
# Copyright (C) 2013 - Today: GRAP (http://www.grap.coop)
# @author: Julien WESTE
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import time
from openerp.tests.common import TransactionCase
from odoo import fields
from odoo.tests.common import TransactionCase
from odoo.exceptions import Warning as UserError
class TestPosInvoicing(TransactionCase):
"""Tests for POS Invoicing Module"""
class TestModule(TransactionCase):
def setUp(self):
super(TestPosInvoicing, self).setUp()
super(TestModule, self).setUp()
# Get Registry
self.session_obj = self.env['pos.session']
self.order_obj = self.env['pos.order']
self.PosOrder = self.env['pos.order']
self.AccountPayment = self.env['account.payment']
# Get Object
self.config = self.env.ref('point_of_sale.pos_config_main')
self.partner_A = self.env.ref('base.res_partner_2')
self.partner_B = self.env.ref('base.res_partner_12')
self.product = self.env.ref('product.product_product_48')
self.payment_journal = self.env.ref('account.cash_journal')
self.pos_product = self.env.ref('point_of_sale.whiteboard_pen')
self.pricelist = self.env.ref('product.list0')
self.partner = self.env.ref('base.res_partner_12')
# Test Section
def test_01_invoice_with_payment(self):
"""Test the workflow: Draft Order -> Payment -> Invoice"""
# Opening Session
session = self.session_obj.create({'config_id': self.config.id})
# TODO FIXME, for the time being, reconciliation is not
# set if a customer make many invoices in the same pos session.
# self._create_order(session, self.partner_A, 100, True)
# self._create_order(session, self.partner_A, 200, True)
self._create_order(session, self.partner_B, 400, True)
# Create a new pos config and open it
self.pos_config = self.env.ref('point_of_sale.pos_config_main').copy()
self.pos_config.open_session_cb()
# The Invoice must be unpayable but in 'open' state
# Invoice created by order should be in open state
invoiced_orders = session.mapped('order_ids').filtered(
lambda x: x.state == 'invoiced')
invoices = invoiced_orders.mapped('invoice_id')
self.assertEquals(
[x for x in invoices.mapped('state') if x != 'open'], [],
"All invoices generated from PoS should be in the 'open' state"
" when session is opened")
# Test Section
def test_order_invoice(self):
order = self._create_order()
self.assertEquals(
[x for x in invoices.mapped('pos_pending_payment') if not x],
[],
"All invoices generated from PoS should be marked as PoS Pending"
" Payment when session is opened")
# Check if invoice is correctly set
self.assertEquals(order.invoice_id.pos_pending_payment, True)
# Close Session
session.signal_workflow('close')
# Try to register payment should fail on this invoice should fail
with self.assertRaises(UserError):
payment = self.register_payment(order.invoice_id)
payment.post()
self.assertEquals(
[x for x in invoices.mapped('state') if x != 'paid'], [],
"All invoices generated from PoS should be in the 'paid' state"
" when session is closed")
# Try to register a payment not linked to this invoice should be ok
payment = self.register_payment()
payment.post()
self.assertEquals(
[x for x in invoices.mapped('pos_pending_payment') if x], [],
"Invoices generated from PoS should not be marked as PoS Pending"
" Payment when session is closed")
# Once closed check if the invoice is correctly set
self.pos_config.current_session_id.action_pos_session_closing_control()
self.assertEquals(order.invoice_id.pos_pending_payment, False)
# Private Section
def _create_order(self, session, partner, amount, with_invoice):
# create Pos Order
order = self.order_obj.create({
'session_id': session.id,
'partner_id': partner.id,
'lines': [[0, False, {
'product_id': self.product.id,
'qty': 1,
'price_unit': amount,
}]],
def register_payment(self, invoice_id=False):
journal = self.pos_config.journal_ids[0]
return self.AccountPayment.create({
'invoice_ids': invoice_id and [(4, invoice_id.id, None)] or False,
'payment_type': 'inbound',
'partner_type': 'customer',
'payment_date': fields.Datetime.now(),
'partner_id': self.partner.id,
'amount': 0.9,
'journal_id': journal.id,
'payment_method_id': journal.inbound_payment_method_ids[0].id,
})
# Finish Payment
self.order_obj.add_payment(order.id, {
'journal': self.payment_journal.id,
'payment_date': time.strftime('%Y-%m-%d'),
'amount': amount,
})
# Mark as Paid
order.signal_workflow('paid')
if with_invoice:
order.action_invoice()
def _create_order(self):
# Create order
order_data = {
'id': u'0006-001-0010',
'to_invoice': True,
'data': {
'pricelist_id': self.pricelist.id,
'user_id': 1,
'name': 'Order 0006-001-0010',
'partner_id': self.partner.id,
'amount_paid': 0.9,
'pos_session_id': self.pos_config.current_session_id.id,
'lines': [[0, 0, {
'product_id': self.pos_product.id,
'price_unit': 0.9,
'qty': 1,
'price_subtotal': 0.9,
'price_subtotal_incl': 0.9,
}]],
'statement_ids': [[0, 0, {
'journal_id': self.pos_config.journal_ids[0].id,
'amount': 0.9,
'name': fields.Datetime.now(),
'account_id':
self.env.user.partner_id.property_account_receivable_id.id,
'statement_id':
self.pos_config.current_session_id.statement_ids[0].id,
}]],
'creation_date': u'2018-09-27 15:51:03',
'amount_tax': 0,
'fiscal_position_id': False,
'uid': u'00001-001-0001',
'amount_return': 0,
'sequence_number': 1,
'amount_total': 0.9,
}}
result = self.PosOrder.create_from_ui([order_data])
order = self.PosOrder.browse(result[0])
return order

9
pos_invoicing/views/view_account_invoice.xml

@ -5,17 +5,16 @@ Copyright (C) 2013 - Today: GRAP (http://www.grap.coop)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<openerp><data>
<odoo>
<record id="view_account_invoice_form" model="ir.ui.view">
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account.invoice_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='payment_ids']" position="before">
<label for="pos_pending_payment">PoS - Pending Payment</label>
<field name="reference" position="after">
<field name="pos_pending_payment"/>
</xpath>
</field>
</field>
</record>
</data></openerp>
</odoo>
Loading…
Cancel
Save