From cc46bf97a4e1bd438d170c36d53abd0bef1e3040 Mon Sep 17 00:00:00 2001 From: Nhomar Hernandez Date: Sun, 30 Sep 2018 08:09:48 +0000 Subject: [PATCH 01/12] [FIX] sale: Call the proper method in the test and not the partial one. With this we can test the invoices properlly in other environments where we do not depend on sale due to the fact of this incorrectly set test we hade collateral damage in the localizationsfor example (also related to doesn't have a new created field called correctly in invoice_open and the method action_date_assign was not called propperly. With this fix we do not need to make defensive programming in the localizations in order to allow us to test it properlly. closes odoo/odoo#27316 --- addons/sale/tests/test_sale_to_invoice_and_to_be_invoiced.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sale/tests/test_sale_to_invoice_and_to_be_invoiced.py b/addons/sale/tests/test_sale_to_invoice_and_to_be_invoiced.py index cc4f9cfa438..b5a28a435d3 100644 --- a/addons/sale/tests/test_sale_to_invoice_and_to_be_invoiced.py +++ b/addons/sale/tests/test_sale_to_invoice_and_to_be_invoiced.py @@ -71,7 +71,7 @@ class TestSaleOrderInvoicing(AccountingTestCase): self.assertEqual(sum(order.order_line.mapped('amt_invoiced')), 0.0, 'Sale: the Amount Invoiced for the sale order should be 0.0.') # Now I validate invoice_1. - invoice_1.invoice_validate() + invoice_1.action_invoice_open() self.assertEqual(sum(order.order_line.mapped('amt_to_invoice')), 300.0, 'Sale: the Amount To Invoice for the sale order should be 300.0.') self.assertEqual(sum(order.order_line.mapped('amt_invoiced')), 500.0, 'Sale: the Amount Invoiced for the sale order should be 500.0.') From 8b9cb0565eff4c0d147c3748d653a0c520c21add Mon Sep 17 00:00:00 2001 From: Martin Geubelle Date: Mon, 5 Nov 2018 15:20:54 +0000 Subject: [PATCH 02/12] [FIX] mail: remove `mail_followers` widget support claim This widget claims to support one2many fields but it doesn't work in most cases. This was triggering warnings when this widget was set on another field or in another view (i.e. in Studio). opw-1905803 closes odoo/odoo#28413 --- addons/mail/static/src/js/followers.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/mail/static/src/js/followers.js b/addons/mail/static/src/js/followers.js index 651165dde30..a0f8a8b7be3 100644 --- a/addons/mail/static/src/js/followers.js +++ b/addons/mail/static/src/js/followers.js @@ -28,7 +28,9 @@ var Followers = AbstractField.extend({ 'click .o_remove_follower': '_onRemoveFollower', 'click .o_mail_redirect': '_onRedirect', }, - supportedFieldTypes: ['one2many'], + // this widget only supports one2many but is not generic enough to claim + // that it supports all one2many fields + // supportedFieldTypes: ['one2many'], // inherited init: function(parent, name, record, options) { From 392a8d8f9eb739321ed0d0c1ca0c1d1e0e6a1cbf Mon Sep 17 00:00:00 2001 From: Adrian Torres Date: Mon, 5 Nov 2018 09:05:40 +0000 Subject: [PATCH 03/12] [FIX] base: handle models without associated xids at uninstall If all the planets are correctly aligned, it is possible for a model to not have any xids associated with it, if this happens then the computed field that calculates the records to be deleted during an uninstall may mistakingly add records of this xid-less model to the list of records to be deleted, even if the actual uninstallation won't remove these records. opw-1904139 closes odoo/odoo#28388 --- odoo/addons/base/module/wizard/base_module_uninstall.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/odoo/addons/base/module/wizard/base_module_uninstall.py b/odoo/addons/base/module/wizard/base_module_uninstall.py index 1168e57341c..46665ac1e4d 100644 --- a/odoo/addons/base/module/wizard/base_module_uninstall.py +++ b/odoo/addons/base/module/wizard/base_module_uninstall.py @@ -37,10 +37,12 @@ class BaseModuleUninstall(models.TransientModel): for wizard in self: if wizard.module_id: module_names = set(wizard._get_modules().mapped('name')) - # find the models that have all their XIDs in the given modules + def lost(model): - return all(xid.split('.')[0] in module_names - for xid in ir_models_xids.get(model.id, ())) + xids = ir_models_xids.get(model.id, ()) + return xids and all(xid.split('.')[0] in module_names for xid in xids) + + # find the models that have all their XIDs in the given modules self.model_ids = ir_models.filtered(lost).sorted('name') @api.onchange('module_id') From 69a0af9e8634ba3ad67c2d8368b6fead9b390fbe Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Tue, 6 Nov 2018 12:07:29 +0000 Subject: [PATCH 04/12] [FIX] account: Impossible to make a partial credit note Steps to reproduce the bug: - Create a credit note for an amount of 100$ - Validate it and register a payment - Let's pay an amount of 90$ and mark this credit note as fully paid Bug: An error message was raised saying: Wrong credit or debit value in accounting entry ! DO NOT FORWARD-PORT opw:1893570 closes odoo/odoo#28433 --- addons/account/models/account_payment.py | 18 +++++++++++++++- addons/account/tests/test_payment.py | 26 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index 7d1231ece2e..0e19cd13b55 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -569,8 +569,24 @@ class account_payment(models.Model): # to avoid loss of precision during the currency rate computations. See revision 20935462a0cabeb45480ce70114ff2f4e91eaf79 for a detailed example. total_residual_company_signed = sum(invoice.residual_company_signed for invoice in self.invoice_ids) total_payment_company_signed = self.currency_id.with_context(date=self.payment_date).compute(self.amount, self.company_id.currency_id) - if self.invoice_ids[0].type in ['in_invoice', 'out_refund']: + # amout_wo must be positive for out_invoice and in_refund and negative for in_invoice and out_refund in standard use case + # | total_payment_company_signed | total_residual_company_signed | amount_wo + #---------------------------------------------------------------------------------------------------------------------- + # in_invoice | positive | positive | negative + #---------------------------------------------------------------------------------------------------------------------- + # in_refund | positive | negative | positive + #---------------------------------------------------------------------------------------------------------------------- + # out_invoice | positive | positive | positive + #---------------------------------------------------------------------------------------------------------------------- + # out_refund | positive | negative | negative + #---------------------------------------------------------------------------------------------------------------------- + # DO NOT FORWARD-PORT + if self.invoice_ids[0].type == 'in_invoice': amount_wo = total_payment_company_signed - total_residual_company_signed + elif self.invoice_ids[0].type == 'in_refund': + amount_wo = - total_payment_company_signed - total_residual_company_signed + elif self.invoice_ids[0].type == 'out_refund': + amount_wo = total_payment_company_signed + total_residual_company_signed else: amount_wo = total_residual_company_signed - total_payment_company_signed # Align the sign of the secondary currency writeoff amount with the sign of the writeoff diff --git a/addons/account/tests/test_payment.py b/addons/account/tests/test_payment.py index 9855452e209..8f671f957c9 100644 --- a/addons/account/tests/test_payment.py +++ b/addons/account/tests/test_payment.py @@ -370,3 +370,29 @@ class TestPayment(AccountingTestCase): {'account_id': self.account_payable.id, 'debit': 0.0, 'credit': 8.65, 'amount_currency': -13.22, 'currency_id': self.currency_usd_id}, {'account_id': self.account_receivable.id, 'debit': 0.0, 'credit': 7.7, 'amount_currency': -11.78, 'currency_id': self.currency_usd_id}, ]) + + def test_payment_and_writeoff_out_refund(self): + # Use case: + # Company is in EUR, create a credit note for 100 EUR and register payment of 90. + # Mark invoice as fully paid with a write_off + # Check that all the aml are correctly created. + invoice = self.create_invoice(amount=100, type='out_refund', currency_id=self.currency_eur_id, partner=self.partner_agrolait.id) + # register payment on invoice + payment = self.payment_model.create({'payment_type': 'outbound', + 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id, + 'partner_type': 'customer', + 'partner_id': self.partner_agrolait.id, + 'amount': 90, + 'payment_date': time.strftime('%Y') + '-07-15', + 'payment_difference_handling': 'reconcile', + 'writeoff_account_id': self.account_payable.id, + 'journal_id': self.bank_journal_euro.id, + 'invoice_ids': [(4, invoice.id, None)] + }) + payment.post() + self.check_journal_items(payment.move_line_ids, [ + {'account_id': self.account_eur.id, 'debit': 0.0, 'credit': 90.0, 'amount_currency': 0.0, 'currency_id': False}, + {'account_id': self.account_payable.id, 'debit': 0.0, 'credit': 10.0, 'amount_currency': 0.0, 'currency_id': False}, + {'account_id': self.account_receivable.id, 'debit': 100.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': False}, + ]) + self.assertEqual(invoice.state, 'paid') From c6b0d36622065f4eadc9f0b9da10a77f725fe5f5 Mon Sep 17 00:00:00 2001 From: Arnold Moyaux Date: Wed, 17 Oct 2018 14:32:03 +0000 Subject: [PATCH 05/12] [FIX] stock: quantity done not saved on pager On delivery order, it happens that when it contains more than 40 products and multiple pager. The quantity done are only saved on the first pager and on the other everythings is reset to 0. This issue is not deterministic. The reason is because on the picking view, both stock.move and stock.move.line are present. When the user write the quantity done on a stock.move it exists a function inverse that write qty_done on stock.move.line. However during an onchange, inverse function are not trigger and every field present in the view is returned to the client. It means that data after the onchange are inconsistent since the quantity done write on the move is different than the quantity on the move line. When the user save the form view, the write could be trigger in different order. If the write on stock.move is trigger first, it will write the qty_done with the inverse function but it will be erase by the write on stock.move.line that is trigger after. In order to fix it, we set the stock.move.line as readonly when they are not needed, so they are not send to the server. closes odoo/odoo#27903 --- addons/stock/views/stock_picking_views.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml index 090924b8fd4..1cf3a23377e 100644 --- a/addons/stock/views/stock_picking_views.xml +++ b/addons/stock/views/stock_picking_views.xml @@ -258,7 +258,7 @@ - + From eaa93d1bd491b41b92d50fb1c0f6f6a5e1596557 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Tue, 6 Nov 2018 10:15:01 +0100 Subject: [PATCH 06/12] [FIX] models: update write_date when writing on inherited fields When changing the field `image` on `res.users`, the `write_date` of the user is not updated because the field is inherited from `res.partner`. Related to Issue : 1839603 --- odoo/addons/base/tests/test_orm.py | 11 +++++++++++ odoo/models.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/odoo/addons/base/tests/test_orm.py b/odoo/addons/base/tests/test_orm.py index bbb52f8c80a..3facd2c4570 100644 --- a/odoo/addons/base/tests/test_orm.py +++ b/odoo/addons/base/tests/test_orm.py @@ -315,6 +315,17 @@ class TestInherits(TransactionCase): self.assertEqual(user_bar.name, 'Bar', "name is given from specific partner") self.assertEqual(user_bar.signature, user_foo.signature, "signature should be copied") + @mute_logger('odoo.models') + def test_write_date(self): + """ modifying inherited fields must update write_date """ + user = self.env.user + write_date_before = user.write_date + + # write base64 image + user.write({'image': 'R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='}) + write_date_after = user.write_date + self.assertNotEqual(write_date_before, write_date_after) + CREATE = lambda values: (0, False, values) UPDATE = lambda id, values: (1, id, values) diff --git a/odoo/models.py b/odoo/models.py index 5964cb324d7..e9304afa8ef 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -3102,7 +3102,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): with self.env.protecting(protected_fields, self): # write stored fields with (low-level) method _write - if store_vals: + if store_vals or inverse_vals or inherited_vals: self._write(store_vals) # update parent records (after possibly updating parent fields) From ae3997b8bf7e539f91a64870ced76868550bae12 Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Thu, 11 Oct 2018 17:02:47 +0000 Subject: [PATCH 07/12] [FIX] web: guess resize placeholder Before this commit, if you ask for a small image, you was waiting a picture of 64x64 but in case this image was not found, a placeholder with an other size was returned. Now, we try to guess the asked size, and return a resized placeholder. This bug appear since we change the default placeholder picture with a big one This will fix several issue and avoid to hard code size everywhere in the code Eg: commit 92f837c and commit https://github.com/odoo/odoo/commit/154fc7d9dd550d35b7266af024e54c20e18938fc#diff-9af1af2039d16d6b544d481b4ee1ed7cR144 closes odoo/odoo#27695 --- addons/web/controllers/main.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 35332e4fe79..cc162d57ded 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1073,7 +1073,12 @@ class Binary(http.Controller): if content: image_base64 = base64.b64decode(content) else: - image_base64 = self.placeholder(image='placeholder.png') # could return (contenttype, content) in master + suffix = field.split('_')[-1] + if suffix in ('small', 'medium', 'big'): + encoded_placeholder = base64.b64encode(self.placeholder(image='placeholder.png')) + image_base64 = base64.b64decode(getattr(odoo.tools, 'image_resize_image_%s' % suffix)(encoded_placeholder)) + else: + image_base64 = self.placeholder(image='placeholder.png') # could return (contenttype, content) in master headers = self.force_contenttype(headers, contenttype='image/png') headers.append(('Content-Length', len(image_base64))) From b9e6adcaf7dd87f8a625588a03690e4a9f9cbc02 Mon Sep 17 00:00:00 2001 From: Joren Van Onder Date: Mon, 5 Nov 2018 12:24:23 -0800 Subject: [PATCH 08/12] [FIX] account: reconcile payments against min date_maturity journal item 1. Create a payment term with: - 50% due now - balance due in 14 days 2. Validate an invoice with this payment term and partner Agrolait 3. Create a payment from Agrolait with the 50% due 4. Add this outstanding credit to the invoice It is reconciled with the balance due in 14 days instead of what is due now. Because of this e.g. the Aged Receivable report will show the customer being overdue with a payment. This issue was first fixed in 9.0 at adad192cc0018ce6838feb2a954044ee0b623841. It was then reintroduced in saas-11.4 at d3d26120614139fd7d7e888bd66d21de5158a034. This commit adds a test to avoid regressions like this. To fix the issue this sorts journal items by their Due Date if there is one. Otherwise we use the date from the journal entry as before. Note that before this commit journal items were not sorted at all by the two 'sorted' statements. sorted returns a sorted recordset, it doesn't modify the recordset it's called on. opw-1906665 --- addons/account/models/account_move.py | 4 +- addons/account/tests/test_reconciliation.py | 60 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 7a9662d2937..3ff326973ba 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -862,8 +862,8 @@ class AccountMoveLine(models.Model): # Create list of debit and list of credit move ordered by date-currency debit_moves = self.filtered(lambda r: r.debit != 0 or r.amount_currency > 0) credit_moves = self.filtered(lambda r: r.credit != 0 or r.amount_currency < 0) - debit_moves.sorted(key=lambda a: (a.date, a.currency_id)) - credit_moves.sorted(key=lambda a: (a.date, a.currency_id)) + debit_moves = debit_moves.sorted(key=lambda a: (a.date_maturity or a.date, a.currency_id)) + credit_moves = credit_moves.sorted(key=lambda a: (a.date_maturity or a.date, a.currency_id)) # Compute on which field reconciliation should be based upon: field = self[0].account_id.currency_id and 'amount_residual_currency' or 'amount_residual' #if all lines share the same currency, use amount_residual_currency to avoid currency rounding error diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py index 2579c198bf6..a56c499c1d7 100644 --- a/addons/account/tests/test_reconciliation.py +++ b/addons/account/tests/test_reconciliation.py @@ -974,3 +974,63 @@ class TestReconciliation(AccountingTestCase): # because they owe us still 50 CC. self.assertEqual(invoice_cust_1.state, 'open', 'Invoice is in status %s' % invoice_cust_1.state) + + def test_multiple_term_reconciliation_opw_1906665(self): + '''Test that when registering a payment to an invoice with multiple + payment term lines the reconciliation happens against the line + with the earliest date_maturity + ''' + + payment_term = self.env['account.payment.term'].create({ + 'name': 'Pay in 2 installments', + 'line_ids': [ + # Pay 50% immediately + (0, 0, { + 'value': 'percent', + 'value_amount': 50, + }), + # Pay the rest after 14 days + (0, 0, { + 'value': 'balance', + 'days': 14, + }) + ], + }) + + # can't use self.create_invoice because it validates and we need to set payment_term_id + invoice = self.account_invoice_model.create({ + 'partner_id': self.partner_agrolait_id, + 'payment_term_id': payment_term.id, + 'reference_type': 'none', + 'currency_id': self.currency_usd_id, + 'name': 'Multiple payment terms', + 'account_id': self.account_rcv.id, + 'type': 'out_invoice', + 'date_invoice': time.strftime('%Y') + '-07-01', + }) + self.account_invoice_line_model.create({ + 'product_id': self.product.id, + 'quantity': 1, + 'price_unit': 50, + 'invoice_id': invoice.id, + 'name': self.product.display_name, + 'account_id': self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id)], limit=1).id, + }) + + invoice.action_invoice_open() + + payment = self.env['account.payment'].create({ + 'payment_type': 'inbound', + 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id, + 'partner_type': 'customer', + 'partner_id': self.partner_agrolait_id, + 'amount': 25, + 'currency_id': self.currency_usd_id, + 'journal_id': self.bank_journal_usd.id, + }) + payment.post() + + invoice.assign_outstanding_credit(payment.move_line_ids.filtered('credit').id) + + receivable_lines = invoice.move_id.line_ids.filtered(lambda line: line.account_id == self.account_rcv).sorted('date_maturity')[0] + self.assertTrue(receivable_lines.matched_credit_ids) From 7a117b14dff65bb42c55f3ade4470d0be6ca2970 Mon Sep 17 00:00:00 2001 From: eco-odoo Date: Wed, 7 Nov 2018 08:09:55 +0000 Subject: [PATCH 09/12] [IMP] l10n_uk: rename menu item `England` to `United Kingdom` "British Statements" was wrongly renamed to "England" in a436645ae8016d54c32eadd7d3ec0acb2ae78d9f. closes odoo/odoo#28458 --- addons/l10n_uk/data/l10n_uk_chart_data.xml | 2 +- addons/l10n_uk/i18n/l10n_uk.pot | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/l10n_uk/data/l10n_uk_chart_data.xml b/addons/l10n_uk/data/l10n_uk_chart_data.xml index fd180996726..6318875730e 100644 --- a/addons/l10n_uk/data/l10n_uk_chart_data.xml +++ b/addons/l10n_uk/data/l10n_uk_chart_data.xml @@ -1,6 +1,6 @@ - + Transfer Account diff --git a/addons/l10n_uk/i18n/l10n_uk.pot b/addons/l10n_uk/i18n/l10n_uk.pot index 61971d8a391..788aeef258d 100644 --- a/addons/l10n_uk/i18n/l10n_uk.pot +++ b/addons/l10n_uk/i18n/l10n_uk.pot @@ -17,7 +17,7 @@ msgstr "" #. module: l10n_uk #: model:ir.ui.menu,name:l10n_uk.account_reports_uk_statements_menu -msgid "British Statements" +msgid "United Kindom" msgstr "" #. module: l10n_uk From e5c8071484c883bf78478a39ef2120bcd8f2442d Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 7 Nov 2018 13:24:53 +0000 Subject: [PATCH 10/12] [FIX] account: domain on reconciliation model When loading the Reconciliation Widget, all reconciliation models are retrieved. This is not correct: only the one matching the company and the journal should be retrieved. Fixes #28270 opw-1904475 closes odoo/odoo#28474 --- .../src/js/reconciliation/reconciliation_model.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index 7c5134f8b9e..420897bdf9c 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -392,9 +392,17 @@ var StatementModel = BasicModel.extend({ }; }); }); + var domainReconcile = []; + if (context && context.company_ids) { + domainReconcile.push(['company_id', 'in', context.company_ids]); + } + if (context && context.active_model === 'account.journal' && context.active_ids) { + domainReconcile.push(['journal_id', 'in', [false].concat(context.active_ids)]); + } var def_reconcileModel = this._rpc({ model: 'account.reconcile.model', method: 'search_read', + domain: domainReconcile, }) .then(function (reconcileModels) { self.reconcileModels = reconcileModels; @@ -1171,9 +1179,14 @@ var ManualModel = StatementModel.extend({ self.accounts = _.object(self.account_ids, _.pluck(accounts, 'code')); }); + var domainReconcile = []; + if (context && context.company_ids) { + domainReconcile.push(['company_id', 'in', context.company_ids]); + } var def_reconcileModel = this._rpc({ model: 'account.reconcile.model', method: 'search_read', + domain: domainReconcile, }) .then(function (reconcileModels) { self.reconcileModels = reconcileModels; From d1313d08fc7bc8ab64b68ce39fbad0aeb965cc93 Mon Sep 17 00:00:00 2001 From: Laurent Smet Date: Tue, 2 Oct 2018 15:09:25 +0200 Subject: [PATCH 11/12] [FIX] sale_payment: autoreconcile invoices using transactions - Create a SO - Paid the SO using a transaction - Create an invoice from the SO => The invoice will now be reconciled automatically with the payment transaction Was task: 1890029 Was PR #27390 --- addons/sale_payment/models/__init__.py | 3 +- addons/sale_payment/models/account_invoice.py | 23 ++++++++++ addons/sale_payment/tests/__init__.py | 3 ++ .../tests/test_sale_transaction.py | 44 +++++++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 addons/sale_payment/models/account_invoice.py create mode 100644 addons/sale_payment/tests/__init__.py create mode 100644 addons/sale_payment/tests/test_sale_transaction.py diff --git a/addons/sale_payment/models/__init__.py b/addons/sale_payment/models/__init__.py index 51af63e66bb..ec29df54d63 100644 --- a/addons/sale_payment/models/__init__.py +++ b/addons/sale_payment/models/__init__.py @@ -2,4 +2,5 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import payment -from . import sale_order \ No newline at end of file +from . import sale_order +from . import account_invoice diff --git a/addons/sale_payment/models/account_invoice.py b/addons/sale_payment/models/account_invoice.py new file mode 100644 index 00000000000..2cbd4757024 --- /dev/null +++ b/addons/sale_payment/models/account_invoice.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from odoo import api, models + + +class AccountInvoice(models.Model): + _inherit = 'account.invoice' + + @api.multi + def action_invoice_open(self): + # OVERRIDE + # Auto-reconcile the invoice with payments coming from transactions. + # It's useful when you have a "paid" sale order (using a payment transaction) and you invoice it later. + res = super(AccountInvoice, self).action_invoice_open() + + if not self: + return res + + for invoice in self: + payments = invoice.mapped('transaction_ids.payment_id') + move_lines = payments.mapped('move_line_ids').filtered(lambda line: not line.reconciled and line.credit > 0.0) + for line in move_lines: + invoice.assign_outstanding_credit(line.id) + return res diff --git a/addons/sale_payment/tests/__init__.py b/addons/sale_payment/tests/__init__.py new file mode 100644 index 00000000000..2b187fe50a8 --- /dev/null +++ b/addons/sale_payment/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import test_sale_transaction diff --git a/addons/sale_payment/tests/test_sale_transaction.py b/addons/sale_payment/tests/test_sale_transaction.py new file mode 100644 index 00000000000..61184815168 --- /dev/null +++ b/addons/sale_payment/tests/test_sale_transaction.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +from odoo import tests +from odoo.addons.account.tests.account_test_classes import AccountingTestCase + + +@tests.tagged('post_install', '-at_install') +class TestSaleTransaction(AccountingTestCase): + def test_sale_invoicing_from_transaction(self): + ''' Test the following scenario: + - Create a sale order + - Create a transaction for the sale order. + - Confirm the transaction but no invoice generated automatically. + - Create manually an invoice for this sale order. + => The invoice must be paid. + ''' + product = self.env['product.product'].create({ + 'name': 'Product A', + }) + + order = self.env['sale.order'].create({ + 'partner_id': self.env.ref('base.res_partner_1').id, + 'order_line': [ + (0, False, { + 'product_id': product.id, + 'name': '1 Product', + 'price_unit': 100.0, + }), + ], + }) + + transaction = order._create_payment_transaction({ + 'acquirer_id': self.env.ref('payment.payment_acquirer_transfer').id, + }) + transaction._set_transaction_done() + + # Assert a posted payment has been generated at this point. + self.assertTrue(transaction.payment_id) + self.assertEqual(transaction.payment_id.state, 'posted') + + invoice_ids = order.action_invoice_create() + invoice = self.env['account.invoice'].browse(invoice_ids) + invoice.action_invoice_open() + + self.assertEqual(invoice.state, 'paid') From fe518f1feaabc0101e201b2542acf18deca9693f Mon Sep 17 00:00:00 2001 From: Toufik Ben Jaa Date: Fri, 26 Oct 2018 11:54:49 +0200 Subject: [PATCH 12/12] [FIX] website_sale: performance when creating a cart - When creating an eCommerce cart using the Public User, the code tries to access all the sale orders linked to it. Depending on the count of sale orders linked to the partner, the loading can take a lot of time. To avoid this, we only search for the sale orders of connected users and we only retrieve the last one. --- addons/website_sale/models/website.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/website_sale/models/website.py b/addons/website_sale/models/website.py index 1d6d3217811..47108f1206b 100644 --- a/addons/website_sale/models/website.py +++ b/addons/website_sale/models/website.py @@ -161,8 +161,10 @@ class Website(models.Model): affiliate_id = request.session.get('affiliate_id') salesperson_id = affiliate_id if self.env['res.users'].sudo().browse(affiliate_id).exists() else request.website.salesperson_id.id addr = partner.address_get(['delivery', 'invoice']) - if len(partner.sale_order_ids): # first = me - addr['delivery'] = partner.sale_order_ids[0].partner_shipping_id.id + if not request.website.is_public_user(): + last_sale_order = self.env['sale.order'].search([('partner_id', '=', partner.id)], limit=1, order="date_order desc, id desc") + if last_sale_order: # first = me + addr['delivery'] = last_sale_order.partner_shipping_id.id default_user_id = partner.parent_id.user_id.id or partner.user_id.id values = { 'partner_id': partner.id,