diff options
| author | Christophe Simonis <chs@odoo.com> | 2018-10-30 11:45:18 +0100 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2018-10-30 11:45:18 +0100 |
| commit | ca3a1f51d83db4befac0836c5d6e377d6266c753 (patch) | |
| tree | f24fa365289ac4efb9690ebfe739a1b405de550e | |
| parent | a299517f8327e9cd6036dbd0920ed5d3e9dac75a (diff) | |
| parent | 4f3a20bb33d5c7c217e2c595a89588effb1425eb (diff) | |
[MERGE] forward port branch saas-11.3 up to 4f3a20bb33d
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 > 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 > 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> |
