summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-11-09 14:51:55 +0100
committerChristophe Simonis <chs@odoo.com>2018-11-09 14:51:55 +0100
commit3e4b9d4d66c18cb620a3b6a8be76018d4ce177ec (patch)
treeb10db9f40733d6673f8b450e4e9bd9beebf44bac
parent9e8c63e1ec09f92f8a946c5b14dbcea9dab7c447 (diff)
parent639bd896353648ea433dfc3954fc1bce3e14aea9 (diff)
[MERGE] forward port branch saas-11.4 up to 639bd896353
-rw-r--r--addons/account/models/account_move.py4
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_model.js13
-rw-r--r--addons/account/tests/test_payment.py26
-rw-r--r--addons/account/tests/test_reconciliation.py60
-rw-r--r--addons/l10n_uk/data/l10n_uk_chart_data.xml2
-rw-r--r--addons/mail/static/src/js/followers.js4
-rw-r--r--addons/sale/models/account_invoice.py17
-rw-r--r--addons/sale/tests/__init__.py1
-rw-r--r--addons/sale/tests/test_sale_transaction.py45
-rw-r--r--addons/website_sale/models/website.py6
-rw-r--r--odoo/addons/base/tests/test_orm.py11
-rw-r--r--odoo/addons/base/wizard/base_module_uninstall.py8
-rw-r--r--odoo/models.py2
13 files changed, 189 insertions, 10 deletions
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 0f39b6c32f7..2c082616a52 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -877,8 +877,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/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js
index cff4d7b91be..b9504bd6ee6 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_model.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js
@@ -376,9 +376,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;
@@ -1253,9 +1261,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;
diff --git a/addons/account/tests/test_payment.py b/addons/account/tests/test_payment.py
index f7ffd6fc49f..353f9dcb310 100644
--- a/addons/account/tests/test_payment.py
+++ b/addons/account/tests/test_payment.py
@@ -407,6 +407,32 @@ class TestPayment(AccountingTestCase):
self.assertTrue(payment.move_line_ids.filtered(lambda l: l.account_id == invoice.account_id)[0].full_reconcile_id)
self.assertEqual(invoice.state, 'paid')
+ 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')
+
def test_payment_and_writeoff_in_other_currency_2(self):
# Use case:
# Company is in EUR, create a supplier bill of 5325.6 USD and register payment of 5325 USD, at a different rate
diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py
index 53cdc1a6be5..14f1d589d86 100644
--- a/addons/account/tests/test_reconciliation.py
+++ b/addons/account/tests/test_reconciliation.py
@@ -971,3 +971,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)
diff --git a/addons/l10n_uk/data/l10n_uk_chart_data.xml b/addons/l10n_uk/data/l10n_uk_chart_data.xml
index 70a81b228a2..4125fd23b98 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 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
- <menuitem id="account_reports_uk_statements_menu" name="England" parent="account.menu_finance_reports" sequence="0" groups="account.group_account_user"/>
+ <menuitem id="account_reports_uk_statements_menu" name="United Kingdom" parent="account.menu_finance_reports" sequence="0" groups="account.group_account_user"/>
<!-- Chart template -->
<record id="l10n_uk" model="account.chart.template">
diff --git a/addons/mail/static/src/js/followers.js b/addons/mail/static/src/js/followers.js
index 2b440aa3477..0a760516607 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) {
diff --git a/addons/sale/models/account_invoice.py b/addons/sale/models/account_invoice.py
index bcbd21350fc..cd1c2f993ac 100644
--- a/addons/sale/models/account_invoice.py
+++ b/addons/sale/models/account_invoice.py
@@ -51,6 +51,23 @@ class AccountInvoice(models.Model):
self.comment = company.with_context(lang=self.partner_id.lang).sale_note
@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
+
+ @api.multi
def action_invoice_paid(self):
res = super(AccountInvoice, self).action_invoice_paid()
todo = set()
diff --git a/addons/sale/tests/__init__.py b/addons/sale/tests/__init__.py
index 7401ded6d8d..08ee2e8d381 100644
--- a/addons/sale/tests/__init__.py
+++ b/addons/sale/tests/__init__.py
@@ -7,3 +7,4 @@ from . import test_sale_pricelist
from . import test_onchange
from . import test_reinvoice
from . import test_access_rights
+from . import test_sale_transaction
diff --git a/addons/sale/tests/test_sale_transaction.py b/addons/sale/tests/test_sale_transaction.py
new file mode 100644
index 00000000000..d63db15dd5a
--- /dev/null
+++ b/addons/sale/tests/test_sale_transaction.py
@@ -0,0 +1,45 @@
+# -*- 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()
+ transaction._post_process_after_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')
diff --git a/addons/website_sale/models/website.py b/addons/website_sale/models/website.py
index b071ea16a95..50a8ab084ff 100644
--- a/addons/website_sale/models/website.py
+++ b/addons/website_sale/models/website.py
@@ -169,8 +169,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'])
- 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,
diff --git a/odoo/addons/base/tests/test_orm.py b/odoo/addons/base/tests/test_orm.py
index 2663658ac3c..af515299d21 100644
--- a/odoo/addons/base/tests/test_orm.py
+++ b/odoo/addons/base/tests/test_orm.py
@@ -357,6 +357,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/addons/base/wizard/base_module_uninstall.py b/odoo/addons/base/wizard/base_module_uninstall.py
index ac224dcc82b..a3f53f38c31 100644
--- a/odoo/addons/base/wizard/base_module_uninstall.py
+++ b/odoo/addons/base/wizard/base_module_uninstall.py
@@ -40,10 +40,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')
diff --git a/odoo/models.py b/odoo/models.py
index dae750654f5..1af6cd9c173 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -3253,7 +3253,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)