diff --git a/pos_invoicing/__openerp__.py b/pos_invoicing/__manifest__.py similarity index 64% rename from pos_invoicing/__openerp__.py rename to pos_invoicing/__manifest__.py index 52e877d7..ca80289e 100644 --- a/pos_invoicing/__openerp__.py +++ b/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, } diff --git a/pos_invoicing/demo/res_groups.xml b/pos_invoicing/demo/res_groups.xml deleted file mode 100644 index 2c5779c4..00000000 --- a/pos_invoicing/demo/res_groups.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - diff --git a/pos_invoicing/i18n/fr.po b/pos_invoicing/i18n/fr.po index 675f5b7c..a79f6952 100644 --- a/pos_invoicing/i18n/fr.po +++ b/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." diff --git a/pos_invoicing/i18n/pos_invoicing.pot b/pos_invoicing/i18n/pos_invoicing.pot new file mode 100644 index 00000000..51cc170a --- /dev/null +++ b/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 "" + diff --git a/pos_invoicing/models/__init__.py b/pos_invoicing/models/__init__.py index e64dd454..10c66764 100644 --- a/pos_invoicing/models/__init__.py +++ b/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 diff --git a/pos_invoicing/models/account_invoice.py b/pos_invoicing/models/account_invoice.py index 3c0d9285..40da0a1b 100644 --- a/pos_invoicing/models/account_invoice.py +++ b/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): diff --git a/pos_invoicing/models/account_payment.py b/pos_invoicing/models/account_payment.py new file mode 100644 index 00000000..c414fe02 --- /dev/null +++ b/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() diff --git a/pos_invoicing/models/account_voucher.py b/pos_invoicing/models/account_voucher.py deleted file mode 100644 index 216979d7..00000000 --- a/pos_invoicing/models/account_voucher.py +++ /dev/null @@ -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 diff --git a/pos_invoicing/models/pos_order.py b/pos_invoicing/models/pos_order.py index 545f6b55..8d52330b 100644 --- a/pos_invoicing/models/pos_order.py +++ b/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 diff --git a/pos_invoicing/models/pos_session.py b/pos_invoicing/models/pos_session.py index 8f74195f..3485181d 100644 --- a/pos_invoicing/models/pos_session.py +++ b/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 diff --git a/pos_invoicing/readme/DESCRIPTION.rst b/pos_invoicing/readme/DESCRIPTION.rst index 6875c12b..fe8605cb 100644 --- a/pos_invoicing/readme/DESCRIPTION.rst +++ b/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 diff --git a/pos_invoicing/readme/ROADMAP.rst b/pos_invoicing/readme/ROADMAP.rst deleted file mode 100644 index e09f972b..00000000 --- a/pos_invoicing/readme/ROADMAP.rst +++ /dev/null @@ -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``. diff --git a/pos_invoicing/static/description/account_invoice_form.png b/pos_invoicing/static/description/account_invoice_form.png index 1ee85da2..9bf2c6cf 100644 Binary files a/pos_invoicing/static/description/account_invoice_form.png and b/pos_invoicing/static/description/account_invoice_form.png differ diff --git a/pos_invoicing/static/description/icon.png b/pos_invoicing/static/description/icon.png deleted file mode 100644 index 7f52ccf0..00000000 Binary files a/pos_invoicing/static/description/icon.png and /dev/null differ diff --git a/pos_invoicing/tests/__init__.py b/pos_invoicing/tests/__init__.py index 17b82062..d9b96c4f 100644 --- a/pos_invoicing/tests/__init__.py +++ b/pos_invoicing/tests/__init__.py @@ -1,2 +1 @@ -# coding: utf-8 from . import test_module diff --git a/pos_invoicing/tests/test_module.py b/pos_invoicing/tests/test_module.py index a7dc96ae..cb106689 100644 --- a/pos_invoicing/tests/test_module.py +++ b/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 diff --git a/pos_invoicing/views/view_account_invoice.xml b/pos_invoicing/views/view_account_invoice.xml index 13a9fba2..2beabcbe 100644 --- a/pos_invoicing/views/view_account_invoice.xml +++ b/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). --> - + account.invoice - - + - + - +