diff --git a/addons/google_drive/static/src/css/google_drive.css b/addons/google_drive/static/src/css/google_drive.css new file mode 100644 index 00000000000..ce2b22599b0 --- /dev/null +++ b/addons/google_drive/static/src/css/google_drive.css @@ -0,0 +1,5 @@ +li.oe_share_gdoc > a:after { + content: url('/google_drive/static/src/img/drive_icon.png'); + position: absolute; + right: 5px; +} diff --git a/addons/google_drive/static/src/img/drive_icon.png b/addons/google_drive/static/src/img/drive_icon.png index 590dde867f4..1553aa319a6 100644 Binary files a/addons/google_drive/static/src/img/drive_icon.png and b/addons/google_drive/static/src/img/drive_icon.png differ diff --git a/addons/google_drive/static/src/js/gdrive.js b/addons/google_drive/static/src/js/gdrive.js index bc7449c5507..6da06dcd8ec 100644 --- a/addons/google_drive/static/src/js/gdrive.js +++ b/addons/google_drive/static/src/js/gdrive.js @@ -41,7 +41,7 @@ Sidebar.include({ } if (!already_there){ self.add_items('other', [{ - label: res.name+ '', + label: res.name, config_id: res.id, res_id: res_id, res_model: view.dataset.model, diff --git a/addons/google_drive/views/google_drive_templates.xml b/addons/google_drive/views/google_drive_templates.xml index f9481bfd5c5..1906a27696b 100644 --- a/addons/google_drive/views/google_drive_templates.xml +++ b/addons/google_drive/views/google_drive_templates.xml @@ -3,6 +3,7 @@ diff --git a/addons/payment_paypal/controllers/main.py b/addons/payment_paypal/controllers/main.py index 7e4aac4c036..bdf35742bec 100644 --- a/addons/payment_paypal/controllers/main.py +++ b/addons/payment_paypal/controllers/main.py @@ -8,6 +8,7 @@ import urllib2 import werkzeug from odoo import http +from odoo.addons.payment.models.payment_acquirer import ValidationError from odoo.http import request _logger = logging.getLogger(__name__) @@ -86,7 +87,10 @@ class PaypalController(http.Controller): def paypal_ipn(self, **post): """ Paypal IPN. """ _logger.info('Beginning Paypal IPN form_feedback with post data %s', pprint.pformat(post)) # debug - self.paypal_validate_data(**post) + try: + self.paypal_validate_data(**post) + except ValidationError: + _logger.exception('Unable to validate the Paypal payment') return '' @http.route('/payment/paypal/dpn', type='http', auth="none", methods=['POST', 'GET'], csrf=False) diff --git a/addons/point_of_sale/report/pos_invoice.py b/addons/point_of_sale/report/pos_invoice.py index 9ce8b0860ba..92e3a0350e2 100644 --- a/addons/point_of_sale/report/pos_invoice.py +++ b/addons/point_of_sale/report/pos_invoice.py @@ -24,4 +24,4 @@ class PosInvoiceReport(models.AbstractModel): not_invoiced_orders_names = list(map(lambda a: a.name, not_invoiced_posorders)) raise UserError(_('No link to an invoice for %s.') % ', '.join(not_invoiced_orders_names)) - return Report.sudo().render('account.report_invoice', {'docs': self.env['account.invoice'].browse(ids_to_print)}) + return Report.sudo().render('account.report_invoice', {'docs': self.env['account.invoice'].sudo().browse(ids_to_print)}) diff --git a/addons/point_of_sale/views/report_saledetails.xml b/addons/point_of_sale/views/report_saledetails.xml index 9144e47fb0b..7894c3a4cef 100644 --- a/addons/point_of_sale/views/report_saledetails.xml +++ b/addons/point_of_sale/views/report_saledetails.xml @@ -9,7 +9,7 @@

Sales Details

- - + -
diff --git a/addons/product/models/product.py b/addons/product/models/product.py index 056b113acea..0362300e557 100644 --- a/addons/product/models/product.py +++ b/addons/product/models/product.py @@ -96,7 +96,7 @@ class ProductPriceHistory(models.Model): company_id = fields.Many2one('res.company', default=_get_default_company_id, required=True) product_id = fields.Many2one('product.product', 'Product', ondelete='cascade', required=True) - datetime = fields.Datetime('Date', default=fields.Datetime.now()) + datetime = fields.Datetime('Date', default=fields.Datetime.now) cost = fields.Float('Cost', digits=dp.get_precision('Product Price')) diff --git a/addons/purchase/models/account_invoice.py b/addons/purchase/models/account_invoice.py index a7971764734..00e11060444 100644 --- a/addons/purchase/models/account_invoice.py +++ b/addons/purchase/models/account_invoice.py @@ -162,10 +162,16 @@ class AccountInvoice(models.Model): if valuation_price_unit != i_line.price_unit and line['price_unit'] == i_line.price_unit and acc: # price with discount and without tax included price_unit = i_line.price_unit * (1 - (i_line.discount or 0.0) / 100.0) + tax_ids = [] if line['tax_ids']: #line['tax_ids'] is like [(4, tax_id, None), (4, tax_id2, None)...] taxes = self.env['account.tax'].browse([x[1] for x in line['tax_ids']]) price_unit = taxes.compute_all(price_unit, currency=inv.currency_id, quantity=1.0)['total_excluded'] + for tax in taxes: + tax_ids.append((4, tax.id, None)) + for child in tax.children_tax_ids: + if child.type_tax_use != 'none': + tax_ids.append((4, child.id, None)) price_before = line.get('price', 0.0) line.update({'price': round(valuation_price_unit * line['quantity'], account_prec)}) diff_res.append({ @@ -178,6 +184,7 @@ class AccountInvoice(models.Model): 'product_id': line['product_id'], 'uom_id': line['uom_id'], 'account_analytic_id': line['account_analytic_id'], + 'tax_ids': tax_ids, }) return diff_res return [] diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 024c8c3871b..18fdb4a44f5 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -915,6 +915,11 @@ class SaleOrderLine(models.Model): product_currency = None if rule_id: pricelist_item = PricelistItem.browse(rule_id) + if pricelist_item.pricelist_id.discount_policy == 'without_discount': + while pricelist_item.base == 'pricelist' and pricelist_item.base_pricelist_id and pricelist_item.base_pricelist_id.discount_policy == 'without_discount': + price, rule_id = pricelist_item.base_pricelist_id.with_context(uom=uom.id).get_product_price_rule(product, qty, self.order_id.partner_id) + pricelist_item = PricelistItem.browse(rule_id) + if pricelist_item.base == 'standard_price': field_name = 'standard_price' if pricelist_item.base == 'pricelist' and pricelist_item.base_pricelist_id: @@ -951,8 +956,8 @@ class SaleOrderLine(models.Model): self.env.user.has_group('sale.group_discount_per_so_line')): return - context_partner = dict(self.env.context, partner_id=self.order_id.partner_id.id) - pricelist_context = dict(context_partner, uom=self.product_uom.id, date=self.order_id.date_order) + context_partner = dict(self.env.context, partner_id=self.order_id.partner_id.id, date=self.order_id.date_order) + pricelist_context = dict(context_partner, uom=self.product_uom.id) price, rule_id = self.order_id.pricelist_id.with_context(pricelist_context).get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id) new_list_price, currency_id = self.with_context(context_partner)._get_real_price_currency(self.product_id, rule_id, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id) @@ -961,8 +966,7 @@ class SaleOrderLine(models.Model): if price != 0 and new_list_price != 0: if self.product_id.company_id and self.order_id.pricelist_id.currency_id != self.product_id.company_id.currency_id: # new_list_price is in company's currency while price in pricelist currency - ctx = dict(context_partner, date=self.order_id.date_order) - new_list_price = self.env['res.currency'].browse(currency_id).with_context(ctx).compute(new_list_price, self.order_id.pricelist_id.currency_id) + new_list_price = self.env['res.currency'].browse(currency_id).with_context(context_partner).compute(new_list_price, self.order_id.pricelist_id.currency_id) discount = (new_list_price - price) / new_list_price * 100 if discount > 0: self.discount = discount diff --git a/addons/stock/models/stock_inventory.py b/addons/stock/models/stock_inventory.py index f6c36541990..5cc88ee7237 100644 --- a/addons/stock/models/stock_inventory.py +++ b/addons/stock/models/stock_inventory.py @@ -88,8 +88,13 @@ class Inventory(models.Model): exhausted = fields.Boolean('Include Exhausted Products', readonly=True, states={'draft': [('readonly', False)]}) @api.one + @api.depends('product_id', 'line_ids.product_qty') def _compute_total_qty(self): - self.total_qty = sum(self.mapped('line_ids').mapped('product_qty')) + """ For single product inventory, total quantity of the counted """ + if self.product_id: + self.total_qty = sum(self.mapped('line_ids').mapped('product_qty')) + else: + self.total_qty = 0 @api.model def _selection_filter(self): diff --git a/addons/web/static/src/js/views/list_view_editable.js b/addons/web/static/src/js/views/list_view_editable.js index ef9bf1a6062..003b6f18d01 100644 --- a/addons/web/static/src/js/views/list_view_editable.js +++ b/addons/web/static/src/js/views/list_view_editable.js @@ -641,7 +641,7 @@ ListView.include(/** @lends instance.web.ListView# */{ if (saveInfo.created) { return self.start_edition(); } - var options = { wraparound: !self.is_action_enabled('create') }; + options.wraparound = !self.is_action_enabled('create'); var record = self.records[next_record](saveInfo.record, options); if (record === undefined) { return self.start_edition(); diff --git a/addons/web/static/test/registry.js b/addons/web/static/test/registry.js index 132444c332a..58996837ec6 100644 --- a/addons/web/static/test/registry.js +++ b/addons/web/static/test/registry.js @@ -1,8 +1,8 @@ odoo.define_section('web.registry', ['web.Registry'], { beforeEach: function () { - this.Foo = {}; - this.Bar = {}; - this.Foo2 = {}; + this.Foo = {bar: 1}; + this.Bar = {foo: 0}; + this.Foo2 = {fred: -1}; }, }, function (test) { @@ -14,6 +14,8 @@ odoo.define_section('web.registry', ['web.Registry'], { .add('bar', this.Bar); assert.strictEqual(registry.get('foo'), this.Foo); + // make sure we're comparing actual objects instead of just undefineds + assert.deepEqual(registry.get('foo'), {bar: 1}); }); test('extension', function (assert, Registry) { diff --git a/addons/web/static/test/testing.js b/addons/web/static/test/testing.js index 2ec911740b2..927ca512060 100644 --- a/addons/web/static/test/testing.js +++ b/addons/web/static/test/testing.js @@ -16,7 +16,7 @@ odoo.testing.start_services = function () { }; odoo.testing.MockRPC = function (session) { - this.responses = {}; + this.clear(); }; odoo.testing.MockRPC.prototype.clear = function () { @@ -81,8 +81,33 @@ odoo.define_section = function (name, section_deps) { section_body = arguments[3]; } - QUnit.module(name, options); + mock = new odoo.testing.MockRPC(); + function dummyfunc () {}; + + function beforeEach(assert) { + var services = odoo.testing.start_services(); + this.deps = services; + this.getServiceDeps = function getServiceDeps(dep_names) { + return _.map(dep_names, function (name) { return services[name]; }) + } + services['web.core'].qweb.add_template(odoo.testing.templates); + this.mock = mock; + this.mock.interceptRPC(services['web.session']); + this.assert = assert; + (options.beforeEach || dummyfunc).apply(this, [assert].concat(this.getServiceDeps(section_deps))); + } + function afterEach(assert) { + (options.afterEach || dummyfunc).apply(this, [assert].concat(this.getServiceDeps(section_deps))); + this.mock.clear(); + } + + QUnit.module(name, { + before: options.before, + beforeEach: beforeEach, + afterEach: afterEach, + after: options.after + }); function test () { var name = arguments[0], @@ -90,21 +115,11 @@ odoo.define_section = function (name, section_deps) { body = arguments[arguments.length - 1]; QUnit.test(name, function (assert) { - var services = odoo.testing.start_services(); - var deps = _.map(section_deps.concat(dep_names), function (name) { return services[name]; }); - services['web.core'].qweb.add_template(odoo.testing.templates); - mock.clear(); - mock.interceptRPC(services['web.session']); - var info = { - assert: assert, - mock: mock, - deps: _.pick.apply(null, [services].concat(section_deps).concat(dep_names)), - } - return body.apply(info, [assert].concat(deps)); + var deps = this.getServiceDeps(section_deps.concat(dep_names)); + return body.apply(this, [assert].concat(deps)); }); } - var mock = new odoo.testing.MockRPC(); section_body(test, mock); }; diff --git a/addons/web_editor/static/src/js/backend.js b/addons/web_editor/static/src/js/backend.js index ce8643f04de..50551c381b4 100644 --- a/addons/web_editor/static/src/js/backend.js +++ b/addons/web_editor/static/src/js/backend.js @@ -288,6 +288,7 @@ var FieldTextHtml = widget.extend({ return src; }, initialize_content: function () { + var self = this; this.$el.closest('.modal-body').css('max-height', 'none'); this.$iframe = this.$el.find('iframe'); this.document = null; @@ -296,6 +297,14 @@ var FieldTextHtml = widget.extend({ this.editor = false; window.odoo[this.callback+"_updown"] = null; this.$iframe.attr("src", this.get_url()); + this.view.on("load_record", this, function(r){ + if (!self.$body.find('.o_dirty').length){ + var url = self.get_url(); + if(url !== self.$iframe.attr("src")){ + self.$iframe.attr("src", url); + } + } + }); }, on_content_loaded: function () { var self = this; diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js index 710ce9def28..555d4c8142e 100644 --- a/addons/website_sale/static/src/js/website_sale.js +++ b/addons/website_sale/static/src/js/website_sale.js @@ -204,6 +204,19 @@ odoo.define('website_sale.website_sale', function (require) { event.preventDefault(); $(this).closest('form').submit(); } + if ($(this).hasClass('a-submit-disable')){ + $(this).addClass("disabled"); + } + if ($(this).hasClass('a-submit-loading')){ + var loading = ''; + var fa_span = $(this).find('span[class*="fa"]'); + if (fa_span.length){ + fa_span.replaceWith(loading); + } + else{ + $(this).append(loading); + } + } }); $('form.js_attributes input, form.js_attributes select', oe_website_sale).on('change', function (event) { if (!event.isDefaultPrevented()) { @@ -397,7 +410,7 @@ odoo.define('website_sale.website_sale', function (require) { //$("input[name='phone']").attr('placeholder', data.phone_code !== 0 ? '+'+ data.phone_code : ''); // populate states and display - var selectStates = $("select[name='state_id']:visible"); + var selectStates = $("select[name='state_id']"); // dont reload state at first loading (done in qweb) if (selectStates.data('init')===0 || selectStates.find('option').length===1) { if (data.states.length) { diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml index 4492e6d5836..6f8f355acea 100644 --- a/addons/website_sale/views/templates.xml +++ b/addons/website_sale/views/templates.xml @@ -1276,7 +1276,7 @@ Back - + Next diff --git a/addons/website_slides/security/ir.model.access.csv b/addons/website_slides/security/ir.model.access.csv index 39d871b2863..07d087f7db9 100644 --- a/addons/website_slides/security/ir.model.access.csv +++ b/addons/website_slides/security/ir.model.access.csv @@ -1,6 +1,6 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_slide_slide_public,slide.slide.public,model_slide_slide,base.group_public,1,0,0,0 -access_slide_slide_portal,slide.slide.portal,model_slide_slide,base.group_portal,1,1,1,0 +access_slide_slide_portal,slide.slide.portal,model_slide_slide,base.group_portal,1,0,0,0 access_slide_slide_user,slide.slide.user,model_slide_slide,base.group_user,1,1,1,1 access_slide_tag_public,slide.tag.public,model_slide_tag,base.group_public,1,0,0,0 access_slide_tag_portal,slide.tag.portal,model_slide_tag,base.group_portal,1,0,0,0 diff --git a/addons/website_slides/security/website_slides_security.xml b/addons/website_slides/security/website_slides_security.xml index c464e6ff578..fe39060bd3b 100644 --- a/addons/website_slides/security/website_slides_security.xml +++ b/addons/website_slides/security/website_slides_security.xml @@ -17,7 +17,7 @@ Channel: public: published only - + [('website_published', '=', True)] @@ -36,7 +36,7 @@ Slide: public: published only - + [('channel_id.website_published', '=', True), ('website_published', '=', True)] diff --git a/doc/cla/individual/bluesoft83.md b/doc/cla/individual/bluesoft83.md new file mode 100644 index 00000000000..e8caabb578a --- /dev/null +++ b/doc/cla/individual/bluesoft83.md @@ -0,0 +1,11 @@ +Bosnia and Herzegovina, 2017-03-02 + +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, + +Boško Stojaković bluesoft83@gmail.com https://github.com/bluesoft83 diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py index ce5c60e0025..0c5b9b92aff 100644 --- a/odoo/addons/base/ir/ir_model.py +++ b/odoo/addons/base/ir/ir_model.py @@ -136,10 +136,8 @@ class IrModel(models.Model): for model in self: if model.state != 'manual': raise UserError(_("Model '%s' contains module data and cannot be removed!") % model.name) - - # prevent screwing up fields that depend on these models' fields - for model in self: - model.field_id._prepare_update() + # prevent screwing up fields that depend on these models' fields + model.field_id._prepare_update() self._drop_table() res = super(IrModel, self).unlink() diff --git a/odoo/addons/base/res/res_users.py b/odoo/addons/base/res/res_users.py index 45142038b27..e92aaf5315e 100644 --- a/odoo/addons/base/res/res_users.py +++ b/odoo/addons/base/res/res_users.py @@ -183,7 +183,7 @@ class Users(models.Model): def _default_groups(self): default_user = self.env.ref('base.default_user', raise_if_not_found=False) - return (default_user or self.env['res.users']).groups_id + return (default_user or self.env['res.users']).sudo().groups_id def _companies_count(self): return self.env['res.company'].sudo().search_count([])