summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-10-30 11:45:18 +0100
committerChristophe Simonis <chs@odoo.com>2018-10-30 11:45:18 +0100
commitca3a1f51d83db4befac0836c5d6e377d6266c753 (patch)
treef24fa365289ac4efb9690ebfe739a1b405de550e
parenta299517f8327e9cd6036dbd0920ed5d3e9dac75a (diff)
parent4f3a20bb33d5c7c217e2c595a89588effb1425eb (diff)
[MERGE] forward port branch saas-11.3 up to 4f3a20bb33d
-rw-r--r--addons/account/models/account_invoice.py2
-rw-r--r--addons/account/models/account_payment.py2
-rw-r--r--addons/account/tests/test_payment.py26
-rw-r--r--addons/hr_attendance/models/hr_attendance.py2
-rw-r--r--addons/mail/static/src/js/composer.js3
-rw-r--r--addons/mail/static/src/xml/thread.xml4
-rw-r--r--addons/mail/static/tests/chatter_tests.js59
-rw-r--r--addons/point_of_sale/static/src/css/pos.css13
-rw-r--r--addons/point_of_sale/static/src/js/chrome.js3
-rw-r--r--addons/point_of_sale/static/src/js/devices.js19
-rw-r--r--addons/point_of_sale/static/src/js/models.js22
-rw-r--r--addons/point_of_sale/static/src/xml/pos.xml2
-rw-r--r--addons/sale/static/src/js/sale.js2
-rw-r--r--addons/sale/static/tests/sales_team_dashboard_tests.js70
-rw-r--r--addons/sale/views/assets.xml6
-rw-r--r--doc/_extensions/odoo_ext/translator.py2
-rw-r--r--doc/cla/individual/anilkesariya.md12
-rw-r--r--doc/cla/individual/ark74.md11
-rw-r--r--odoo/addons/base/models/ir_module.py7
-rw-r--r--odoo/cli/templates/default/__manifest__.py.template2
-rw-r--r--odoo/modules/migration.py4
21 files changed, 244 insertions, 29 deletions
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 58626c1066e..b80f4890233 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -107,7 +107,7 @@ class AccountInvoice(models.Model):
def _compute_residual(self):
residual = 0.0
residual_company_signed = 0.0
- sign = self.type in ['in_refund', 'out_refund'] and -1 or 1
+ sign = self.type in ['in_invoice', 'out_refund'] and -1 or 1
for line in self.sudo().move_id.line_ids:
if line.account_id == self.account_id:
residual_company_signed += line.amount_residual
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index dccf457a22a..646974d952e 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -198,7 +198,7 @@ class account_abstract_payment(models.AbstractModel):
total = 0.0
groups = groupby(invoices, lambda i: i.currency_id)
for payment_currency, payment_invoices in groups:
- amount_total = sum([MAP_INVOICE_TYPE_PAYMENT_SIGN[i.type] * i.residual_signed for i in payment_invoices])
+ amount_total = sum(i.residual_signed for i in payment_invoices)
if payment_currency == currency:
total += amount_total
else:
diff --git a/addons/account/tests/test_payment.py b/addons/account/tests/test_payment.py
index 9c5c1bba010..67883604d18 100644
--- a/addons/account/tests/test_payment.py
+++ b/addons/account/tests/test_payment.py
@@ -464,6 +464,32 @@ class TestPayment(AccountingTestCase):
#check the invoice status
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_3(self):
# Use case related in revision 20935462a0cabeb45480ce70114ff2f4e91eaf79
# Invoice made in secondary currency for which the rate to the company currency
diff --git a/addons/hr_attendance/models/hr_attendance.py b/addons/hr_attendance/models/hr_attendance.py
index 7a88460c20a..6d9e679bb5f 100644
--- a/addons/hr_attendance/models/hr_attendance.py
+++ b/addons/hr_attendance/models/hr_attendance.py
@@ -81,7 +81,7 @@ class HrAttendance(models.Model):
('employee_id', '=', attendance.employee_id.id),
('check_out', '=', False),
('id', '!=', attendance.id),
- ])
+ ], order='check_in desc', limit=1)
if no_check_out_attendances:
raise exceptions.ValidationError(_("Cannot create new attendance record for %(empl_name)s, the employee hasn't checked out since %(datetime)s") % {
'empl_name': attendance.employee_id.name,
diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js
index 64cc48285e5..b15a446d7a9 100644
--- a/addons/mail/static/src/js/composer.js
+++ b/addons/mail/static/src/js/composer.js
@@ -486,10 +486,13 @@ var BasicComposer = Widget.extend({
},
send_message: function () {
+ var $button = this.$('.o_composer_button_send');
+
if (this.is_empty() || !this.do_check_attachment_upload()) {
return;
}
+ $button.prop('disabled', true);
clearTimeout(this.canned_timeout);
var self = this;
this.preprocess_message().then(function (message) {
diff --git a/addons/mail/static/src/xml/thread.xml b/addons/mail/static/src/xml/thread.xml
index 325f274f958..e4bb5b43e8e 100644
--- a/addons/mail/static/src/xml/thread.xml
+++ b/addons/mail/static/src/xml/thread.xml
@@ -217,6 +217,7 @@
</span>
</p>
<div class="o_thread_message_content">
+ <t t-raw="message.body"/>
<t t-if="message.tracking_value_ids and message.tracking_value_ids.length &gt; 0">
<t t-if="message.subtype_description">
<p><t t-esc="message.subtype_description"/></p>
@@ -224,9 +225,6 @@
<t t-call="mail.ChatThread.MessageTracking"/>
</t>
<p t-if="options.display_subject and message.display_subject" class="o_mail_subject">Subject: <t t-esc="message.subject"/></p>
- <t t-if="!(message.tracking_value_ids and message.tracking_value_ids.length &gt; 0)">
- <t t-raw="message.body"/>
- </t>
<t t-if="message.attachment_ids.length > 0">
<div t-if="message.images.length > 0" class="o_attachments_previews">
<t t-foreach="message.images" t-as="attachment">
diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js
index a8b8a3adadf..feb8bf65119 100644
--- a/addons/mail/static/tests/chatter_tests.js
+++ b/addons/mail/static/tests/chatter_tests.js
@@ -613,6 +613,65 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
});
});
+QUnit.test('chatter: post a message disable the send button', function(assert) {
+ assert.expect(3);
+ var bus = new Bus();
+ var BusService = createBusService(bus);
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ services: [ChatManager, BusService],
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<field name="foo"/>' +
+ '</sheet>' +
+ '<div class="oe_chatter">' +
+ '<field name="message_ids" widget="mail_thread"/>' +
+ '</div>' +
+ '</form>',
+ res_id: 2,
+ session: {},
+ mockRPC: function (route, args) {
+ if (args.method === 'message_get_suggested_recipients') {
+ return $.when({2: []});
+ }
+ if (args.method === 'message_post') {
+ assert.ok(form.$('.o_composer_button_send').prop("disabled"),
+ "Send button should be disabled when a message is being sent");
+ return $.when(57923);
+ }
+ if (args.method === 'message_format') {
+ return $.when([{
+ author_id: ["42", "Me"],
+ model: 'partner',
+ }]);
+ }
+ return this._super(route, args);
+ },
+ intercepts: {
+ get_messages: function (ev) {
+ ev.stopPropagation();
+ ev.data.callback($.when([]));
+ },
+ get_bus: function (ev) {
+ ev.stopPropagation();
+ ev.data.callback(bus);
+ },
+ },
+ });
+
+ form.$('.o_chatter_button_new_message').click();
+ assert.notOk(form.$('.o_composer_button_send').prop('disabled'),
+ "Send button should be enabled when posting a message");
+ form.$('.oe_chatter .o_composer_text_field:first()').val("My first message");
+ form.$('.oe_chatter .o_composer_button_send').click();
+ form.$('.o_chatter_button_new_message').click();
+ assert.notOk(form.$('.o_composer_button_send').prop('disabled'),
+ "Send button should be enabled when posting another message");
+ form.destroy();
+});
+
QUnit.test('chatter: post a message and switch in edit mode', function (assert) {
assert.expect(5);
diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css
index 904e5f1b432..599d8c23d31 100644
--- a/addons/point_of_sale/static/src/css/pos.css
+++ b/addons/point_of_sale/static/src/css/pos.css
@@ -2152,6 +2152,19 @@ td {
overflow: auto;
height: 120px;
}
+.pos .modal-dialog .popup-textinput{
+ display: flex;
+ flex-direction: column;
+}
+.pos .modal-dialog .popup-textinput .title,
+.pos .modal-dialog .popup-textinput .footer{
+ flex: none;
+}
+.pos .modal-dialog .popup-textinput textarea{
+ resize: none;
+ flex: auto;
+ margin: 0 20px 80px;
+}
.pos .popup .title {
background: rgba(255,255,255,0.5);
margin: 0;
diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js
index a049417d98d..5099e40989b 100644
--- a/addons/point_of_sale/static/src/js/chrome.js
+++ b/addons/point_of_sale/static/src/js/chrome.js
@@ -742,6 +742,9 @@ var Chrome = PosBaseWidget.extend(AbstractAction.prototype, {
if(err.message === 'XmlHttpRequestError '){
title = 'Network Failure (XmlHttpRequestError)';
body = 'The Point of Sale could not be loaded due to a network problem.\n Please check your internet connection.';
+ }else if(err.message === 'TLSError'){
+ title = 'Https connection to IoT Box failed';
+ body = 'Make sure you are using IoT Box v18.10 or higher.\n\n Navigate to ' + err.url + ' to accept the certificate of your IoT Box.';
}else if(err.code === 200){
title = err.data.message;
body = err.data.debug;
diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js
index f45a48319ba..c1db7f61884 100644
--- a/addons/point_of_sale/static/src/js/devices.js
+++ b/addons/point_of_sale/static/src/js/devices.js
@@ -192,11 +192,15 @@ var ProxyDevice = core.Class.extend(mixins.PropertiesMixin,{
// try harder when we remember a good proxy url
found_url = this.try_hard_to_connect(localStorage.hw_proxy_url, options)
.then(null,function(){
- return self.find_proxy(options);
+ if (window.location.protocol != 'https:'){
+ return self.find_proxy(options);
+ }
});
}else{
// just find something quick
- found_url = this.find_proxy(options);
+ if (window.location.protocol != 'https:'){
+ found_url = this.find_proxy(options);
+ }
}
success = found_url.then(function(url){
@@ -248,16 +252,17 @@ var ProxyDevice = core.Class.extend(mixins.PropertiesMixin,{
// try several time to connect to a known proxy url
try_hard_to_connect: function(url,options){
options = options || {};
- var port = ':' + (options.port || '8069');
+ var protocol = window.location.protocol;
+ var port = ( !options.port && protocol == "https:") ? ':443' : ':' + (options.port || '8069');
this.set_connection_status('connecting');
if(url.indexOf('//') < 0){
- url = 'http://'+url;
+ url = protocol + '//' + url;
}
if(url.indexOf(':',5) < 0){
- url = url+port;
+ url = url + port;
}
// try real hard to connect to url, with a 1sec timeout and up to 'retries' retries
@@ -273,11 +278,11 @@ var ProxyDevice = core.Class.extend(mixins.PropertiesMixin,{
.done(function(){
done.resolve(url);
})
- .fail(function(){
+ .fail(function(resp){
if(retries > 0){
try_real_hard_to_connect(url,retries-1,done);
}else{
- done.reject();
+ done.reject(resp.statusText, url);
}
});
return done;
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index ed0794d2cff..46f05bd6001 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -127,13 +127,21 @@ exports.PosModel = Backbone.Model.extend({
progress: function(prog){
self.chrome.loading_progress(prog);
},
- }).then(function(){
- if(self.config.iface_scan_via_proxy){
- self.barcode_reader.connect_to_proxy();
- }
- }).always(function(){
- done.resolve();
- });
+ }).then(
+ function(){
+ if(self.config.iface_scan_via_proxy){
+ self.barcode_reader.connect_to_proxy();
+ }
+ done.resolve();
+ },
+ function(statusText, url){
+ if (statusText == 'error' && window.location.protocol == 'https:') {
+ var error = {message: 'TLSError', url: url};
+ self.chrome.loading_error(error);
+ } else {
+ done.resolve();
+ }
+ });
return done;
},
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 195514ec51b..be41beac9cf 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -1117,7 +1117,7 @@
<div class="modal-dialog">
<div class="popup popup-textinput">
<p class="title"><t t-esc=" widget.options.title || '' " /></p>
- <textarea rows="10" cols="40"><t t-esc="widget.options.value" /></textarea>
+ <textarea><t t-esc="widget.options.value" /></textarea>
<div class="footer">
<div class="button confirm">
Ok
diff --git a/addons/sale/static/src/js/sale.js b/addons/sale/static/src/js/sale.js
index 1121a38501a..374508f721b 100644
--- a/addons/sale/static/src/js/sale.js
+++ b/addons/sale/static/src/js/sale.js
@@ -39,6 +39,8 @@ KanbanRecord.include({
.done(function() {
self.trigger_up('kanban_record_update', {id: self.id});
});
+ // TODO: previous lines can be refactored as follows (in master)
+ // self.trigger_up('kanban_record_update', {invoiced_target: value});
}
});
},
diff --git a/addons/sale/static/tests/sales_team_dashboard_tests.js b/addons/sale/static/tests/sales_team_dashboard_tests.js
new file mode 100644
index 00000000000..b1043dd64e1
--- /dev/null
+++ b/addons/sale/static/tests/sales_team_dashboard_tests.js
@@ -0,0 +1,70 @@
+odoo.define('sale.dashboard_tests', function (require) {
+"use strict";
+
+var KanbanView = require('web.KanbanView');
+var testUtils = require('web.test_utils');
+
+var createView = testUtils.createView;
+
+QUnit.module('Sales Team Dashboard', {
+ beforeEach: function () {
+ this.data = {
+ 'crm.team': {
+ fields: {
+ foo: {string: "Foo", type: 'char'},
+ invoiced_target: {string: "Invoiced_target", type: 'integer'},
+ },
+ records: [
+ {id: 1, foo: "yop"},
+ ],
+ },
+ };
+ }
+});
+
+QUnit.test('edit target with several o_kanban_primary_bottom divs', function (assert) {
+ assert.expect(4);
+
+ var kanban = createView({
+ View: KanbanView,
+ model: 'crm.team',
+ data: this.data,
+ arch: '<kanban>' +
+ '<templates>' +
+ '<t t-name="kanban-box">' +
+ '<div class="container o_kanban_card_content">' +
+ '<a href="#" class="sales_team_target_definition o_inline_link">' +
+ 'Click to define a target</a>' +
+ '<div class="col-12 o_kanban_primary_bottom"/>' +
+ '<div class="col-12 o_kanban_primary_bottom bottom_block"/>' +
+ '</div>' +
+ '</t>' +
+ '</templates>' +
+ '</kanban>',
+ mockRPC: function (route, args) {
+ if (args.method === 'write') {
+ assert.strictEqual(args.args[1].invoiced_target, 123,
+ "new value is correctly saved");
+ }
+ return this._super.apply(this, arguments);
+ },
+
+ });
+
+ assert.strictEqual(kanban.$('.o_kanban_view .sales_team_target_definition').length, 1,
+ "should have classname 'sales_team_target_definition'");
+ assert.strictEqual(kanban.$('.o_kanban_primary_bottom').length, 2,
+ "should have two divs with classname 'o_kanban_primary_bottom'");
+
+ kanban.$('a.sales_team_target_definition').click();
+ assert.strictEqual(kanban.$('.o_kanban_primary_bottom:last input').length, 1,
+ "should have rendered an input in the last o_kanban_primary_bottom div");
+
+ kanban.$('.o_kanban_primary_bottom:last input').focus();
+ kanban.$('.o_kanban_primary_bottom:last input').val('123');
+ kanban.$('.o_kanban_primary_bottom:last input').blur();
+
+ kanban.destroy();
+});
+
+});
diff --git a/addons/sale/views/assets.xml b/addons/sale/views/assets.xml
index a736833aabc..ad123fd2a19 100644
--- a/addons/sale/views/assets.xml
+++ b/addons/sale/views/assets.xml
@@ -11,4 +11,10 @@
<link rel="stylesheet" type="text/scss" href="/sale/static/src/scss/sale_portal.scss"/>
</xpath>
</template>
+
+ <template id="qunit_suite" name="sales_team_dashboard_tests" inherit_id="web.qunit_suite">
+ <xpath expr="//t[@t-set='head']" position="inside">
+ <script type="text/javascript" src="/sale/static/tests/sales_team_dashboard_tests.js"></script>
+ </xpath>
+ </template>
</odoo>
diff --git a/doc/_extensions/odoo_ext/translator.py b/doc/_extensions/odoo_ext/translator.py
index da4e8731628..a71a2f127fb 100644
--- a/doc/_extensions/odoo_ext/translator.py
+++ b/doc/_extensions/odoo_ext/translator.py
@@ -208,7 +208,7 @@ class BootstrapTranslator(nodes.NodeVisitor, object):
else:
opts = {}
- def warner(msg):
+ def warner(msg, **kw):
self.builder.warn(msg, (self.builder.current_docname, node.line))
highlighted = self.builder.highlighter.highlight_block(
node.rawsource, lang, opts=opts, warn=warner, linenos=linenos,
diff --git a/doc/cla/individual/anilkesariya.md b/doc/cla/individual/anilkesariya.md
new file mode 100644
index 00000000000..2b4158360eb
--- /dev/null
+++ b/doc/cla/individual/anilkesariya.md
@@ -0,0 +1,12 @@
+India, 17, Sept - 2018
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Anil Kesariya anil.r.kesariya@gmail.com https://github.com/AnilKesariya
+
diff --git a/doc/cla/individual/ark74.md b/doc/cla/individual/ark74.md
new file mode 100644
index 00000000000..e729859d95f
--- /dev/null
+++ b/doc/cla/individual/ark74.md
@@ -0,0 +1,11 @@
+Mexico, 2018-10-24
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Luis Guzman ark@switnet.org https://github.com/ark74
diff --git a/odoo/addons/base/models/ir_module.py b/odoo/addons/base/models/ir_module.py
index 5cc2278f540..3511b786444 100644
--- a/odoo/addons/base/models/ir_module.py
+++ b/odoo/addons/base/models/ir_module.py
@@ -693,11 +693,10 @@ class Module(models.Model):
mod.write(updated_values)
else:
mod_path = modules.get_module_path(mod_name)
- if not mod_path:
+ if not mod_path or not terp:
continue
- if not terp or not terp.get('installable', True):
- continue
- mod = self.create(dict(name=mod_name, state='uninstalled', **values))
+ state = "uninstalled" if terp.get('installable', True) else "uninstallable"
+ mod = self.create(dict(name=mod_name, state=state, **values))
res[1] += 1
mod._update_dependencies(terp.get('depends', []))
diff --git a/odoo/cli/templates/default/__manifest__.py.template b/odoo/cli/templates/default/__manifest__.py.template
index 024d23c74d5..8d62533e6b0 100644
--- a/odoo/cli/templates/default/__manifest__.py.template
+++ b/odoo/cli/templates/default/__manifest__.py.template
@@ -14,7 +14,7 @@
'website': "http://www.yourcompany.com",
# Categories can be used to filter modules in modules listing
- # Check https://github.com/odoo/odoo/blob/master/odoo/addons/base/module/module_data.xml
+ # Check https://github.com/odoo/odoo/blob/saas-11.4/odoo/addons/base/data/ir_module_category_data.xml
# for the full list
'category': 'Uncategorized',
'version': '0.1',
diff --git a/odoo/modules/migration.py b/odoo/modules/migration.py
index 28a9c1c170d..57d217e770d 100644
--- a/odoo/modules/migration.py
+++ b/odoo/modules/migration.py
@@ -56,8 +56,8 @@ class MigrationManager(object):
function. Theses files must respect a directory tree structure: A 'migrations' folder
which containt a folder by version. Version can be 'module' version or 'server.module'
version (in this case, the files will only be processed by this version of the server).
- Python file names must start by `pre` or `post` and will be executed, respectively,
- before and after the module initialisation. `end` scripts are run after all modules have
+ Python file names must start by `pre-` or `post-` and will be executed, respectively,
+ before and after the module initialisation. `end-` scripts are run after all modules have
been updated.
Example:
<moduledir>