diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 872859134bd..7b1a467a0e7 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -675,7 +675,7 @@ class AccountInvoice(models.Model): self.ensure_one() credit_aml = self.env['account.move.line'].browse(credit_aml_id) if not credit_aml.currency_id and self.currency_id != self.company_id.currency_id: - credit_aml.with_context(allow_amount_currency=True).write({ + credit_aml.with_context(allow_amount_currency=True, check_move_validity=False).write({ 'amount_currency': self.company_id.currency_id.with_context(date=credit_aml.date).compute(credit_aml.balance, self.currency_id), 'currency_id': self.currency_id.id}) if credit_aml.payment_id: diff --git a/addons/account/models/res_config.py b/addons/account/models/res_config.py index 0b7284bcc54..0073e8049db 100644 --- a/addons/account/models/res_config.py +++ b/addons/account/models/res_config.py @@ -76,10 +76,8 @@ class AccountConfigSettings(models.TransientModel): def set_default_product_taxes(self): """ Set the product taxes if they have changed """ ir_values_obj = self.env['ir.values'] - if self.default_sale_tax_id: - ir_values_obj.sudo().set_default('product.template', "taxes_id", [self.default_sale_tax_id.id], for_all_users=True, company_id=self.company_id.id) - if self.default_purchase_tax_id: - ir_values_obj.sudo().set_default('product.template', "supplier_taxes_id", [self.default_purchase_tax_id.id], for_all_users=True, company_id=self.company_id.id) + ir_values_obj.sudo().set_default('product.template', "taxes_id", [self.default_sale_tax_id.id] if self.default_sale_tax_id else False, for_all_users=True, company_id=self.company_id.id) + ir_values_obj.sudo().set_default('product.template', "supplier_taxes_id", [self.default_purchase_tax_id.id] if self.default_purchase_tax_id else False, for_all_users=True, company_id=self.company_id.id) def set_chart_of_accounts(self): """ install a chart of accounts for the given company (if required) """ diff --git a/addons/account/wizard/account_invoice_refund.py b/addons/account/wizard/account_invoice_refund.py index c449bbfcb6b..cddfd61c776 100644 --- a/addons/account/wizard/account_invoice_refund.py +++ b/addons/account/wizard/account_invoice_refund.py @@ -74,7 +74,7 @@ class AccountInvoiceRefund(models.TransientModel): for tmpline in refund.move_id.line_ids: if tmpline.account_id.id == inv.account_id.id: to_reconcile_lines += tmpline - to_reconcile_lines.filtered(lambda l: l.reconciled == False).reconcile() + to_reconcile_lines.filtered(lambda l: l.reconciled == False).reconcile() if mode == 'modify': invoice = inv.read(inv_obj._get_refund_modify_read_fields()) invoice = invoice[0] diff --git a/addons/account_voucher/models/account_voucher.py b/addons/account_voucher/models/account_voucher.py index 0f120e000d8..48b38c90487 100644 --- a/addons/account_voucher/models/account_voucher.py +++ b/addons/account_voucher/models/account_voucher.py @@ -393,7 +393,7 @@ class AccountVoucherLine(models.Model): if product.description_purchase: values['name'] += '\n' + product.description_purchase else: - values['price_unit'] = product.lst_price + values['price_unit'] = price_unit or product.lst_price taxes = product.taxes_id or account.tax_ids if product.description_sale: values['name'] += '\n' + product.description_sale @@ -403,7 +403,7 @@ class AccountVoucherLine(models.Model): if company and currency: if company.currency_id != currency: if type == 'purchase': - values['price_unit'] = product.standard_price + values['price_unit'] = price_unit or product.standard_price values['price_unit'] = values['price_unit'] * currency.rate return {'value': values, 'domain': {}} diff --git a/addons/base_import/models/base_import.py b/addons/base_import/models/base_import.py index b9e22ad9925..e970d1ca3ad 100644 --- a/addons/base_import/models/base_import.py +++ b/addons/base_import/models/base_import.py @@ -664,7 +664,13 @@ class Import(models.TransientModel): }] _logger.info('importing %d rows...', len(data)) - import_result = self.env[self.res_model].with_context(import_file=True).load(import_fields, data) + + model = self.env[self.res_model].with_context(import_file=True) + defer_parent_store = self.env.context.get('defer_parent_store_computation', True) + if defer_parent_store and model._parent_store: + model = model.with_context(defer_parent_store_computation=True) + + import_result = model.load(import_fields, data) _logger.info('done') # If transaction aborted, RELEASE SAVEPOINT is going to raise diff --git a/addons/google_account/models/google_service.py b/addons/google_account/models/google_service.py index cd046ec95a7..c8287500813 100644 --- a/addons/google_account/models/google_service.py +++ b/addons/google_account/models/google_service.py @@ -8,6 +8,7 @@ import urllib2 import werkzeug.urls from odoo import api, fields, models, registry, _ +from odoo.exceptions import UserError from odoo.http import request @@ -124,6 +125,9 @@ class GoogleService(models.TransientModel): client_id = get_param('google_%s_client_id' % (service,), default=False) client_secret = get_param('google_%s_client_secret' % (service,), default=False) + if not client_id or not client_secret: + raise UserError(_("The account for the Google service '%s' is not configured") % service) + headers = {"content-type": "application/x-www-form-urlencoded"} data = werkzeug.url_encode({ 'refresh_token': refresh_token, diff --git a/addons/hw_proxy/controllers/main.py b/addons/hw_proxy/controllers/main.py index 91aee8708e8..cd90ffd16ed 100644 --- a/addons/hw_proxy/controllers/main.py +++ b/addons/hw_proxy/controllers/main.py @@ -4,6 +4,7 @@ import commands import logging import time +import subprocess from threading import Lock @@ -22,7 +23,7 @@ BANNED_DEVICES = set([ ]) -# drivers modules must add to drivers an object with a get_status() method +# drivers modules must add to drivers an object with a get_status() method # so that 'status' can return the status of all active drivers drivers = {} @@ -107,7 +108,7 @@ class Proxy(http.Controller): if not (device_id in BANNED_DEVICES): resp+= "
"+device_name+"
\n" count += 1 - + if count == 0: resp += "
No USB Device Found
" @@ -125,7 +126,7 @@ class Proxy(http.Controller): """ % subprocess.check_output('lsusb -v', shell=True) return request.make_response(resp,{ - 'Cache-Control': 'no-cache', + 'Cache-Control': 'no-cache', 'Content-Type': 'text/html; charset=utf-8', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET', @@ -166,7 +167,7 @@ class Proxy(http.Controller): @http.route('/hw_proxy/payment_request', type='json', auth='none', cors='*') def payment_request(self, price): """ - The PoS will activate the method payment + The PoS will activate the method payment """ print "payment_request: price:"+str(price) return 'ok' @@ -174,7 +175,7 @@ class Proxy(http.Controller): @http.route('/hw_proxy/payment_status', type='json', auth='none', cors='*') def payment_status(self): print "payment_status" - return { 'status':'waiting' } + return { 'status':'waiting' } @http.route('/hw_proxy/payment_cancel', type='json', auth='none', cors='*') def payment_cancel(self): @@ -206,12 +207,12 @@ class Proxy(http.Controller): @http.route('/hw_proxy/is_scanner_connected', type='json', auth='none', cors='*') def is_scanner_connected(self, receipt): - print 'is_scanner_connected?' + print 'is_scanner_connected?' return False @http.route('/hw_proxy/scanner', type='json', auth='none', cors='*') def scanner(self, receipt): - print 'scanner' + print 'scanner' time.sleep(10) return '' diff --git a/addons/mail/controllers/main.py b/addons/mail/controllers/main.py index 766fb9d5078..c7b01e29fbd 100644 --- a/addons/mail/controllers/main.py +++ b/addons/mail/controllers/main.py @@ -117,7 +117,7 @@ class MailController(http.Controller): follower_id = None follower_recs = request.env['mail.followers'].sudo().browse(follower_ids) res_ids = follower_recs.mapped('res_id') - request.env[res_model].browse(res_ids).check_access_rule("write") + request.env[res_model].browse(res_ids).check_access_rule("read") for follower in follower_recs: is_uid = partner_id == follower.partner_id follower_id = follower.id if is_uid else follower_id diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 35114a2542b..b55cc79f922 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -294,8 +294,11 @@ class MailComposer(models.TransientModel): 'mail_server_id': self.mail_server_id.id, 'mail_activity_type_id': self.mail_activity_type_id.id, } + # mass mailing: rendering override wizard static values if mass_mail_mode and self.model: + if self.model in self.env and hasattr(self.env[self.model], 'message_get_email_values'): + mail_values.update(self.env[self.model].browse(res_id).message_get_email_values()) # keep a copy unless specifically requested, reset record name (avoid browsing records) mail_values.update(notification=not self.auto_delete_message, model=self.model, res_id=res_id, record_name=False) # auto deletion of mail_mail diff --git a/addons/payment_stripe/models/payment.py b/addons/payment_stripe/models/payment.py index e43e0cbd71f..0fe3dc746d9 100644 --- a/addons/payment_stripe/models/payment.py +++ b/addons/payment_stripe/models/payment.py @@ -17,8 +17,8 @@ STRIPE_HEADERS = {'Stripe-Version': '2016-03-07'} # The following currencies are integer only, see https://stripe.com/docs/currencies#zero-decimal INT_CURRENCIES = [ - 'BIF', 'XAF', 'XPF', 'CLP', 'KMF', 'DJF', 'GNF', 'JPY', 'MGA', 'PYGí', 'RWF', 'KRW', 'VUV', - 'VND', 'XOF' + u'BIF', u'XAF', u'XPF', u'CLP', u'KMF', u'DJF', u'GNF', u'JPY', u'MGA', u'PYGí', u'RWF', u'KRW', + u'VUV', u'VND', u'XOF' ]; @@ -116,10 +116,13 @@ class PaymentTransactionStripe(models.Model): def _stripe_form_get_tx_from_data(self, data): """ Given a data dict coming from stripe, verify it and find the related transaction record. """ - reference = data['metadata']['reference'] + reference = data.get('metadata', {}).get('reference') if not reference: - error_msg = _('Stripe: invalid reply received from provider, missing reference') - _logger.error(error_msg, data['metadata']) + error_msg = _( + 'Stripe: invalid reply received from provider, missing reference. Additional message: %s' + % data.get('error', {}).get('message', '') + ) + _logger.error(error_msg) raise ValidationError(error_msg) tx = self.search([('reference', '=', reference)]) if not tx: diff --git a/addons/payment_stripe/static/src/js/stripe.js b/addons/payment_stripe/static/src/js/stripe.js index 77570e9b3ce..14e19775747 100644 --- a/addons/payment_stripe/static/src/js/stripe.js +++ b/addons/payment_stripe/static/src/js/stripe.js @@ -2,6 +2,11 @@ odoo.define('payment_stripe.stripe', function(require) { "use strict"; var ajax = require('web.ajax'); + var core = require('web.core'); + var _t = core._t; + var qweb = core.qweb; + ajax.loadXML('/payment_stripe/static/src/xml/stripe_templates.xml', qweb); + // The following currencies are integer only, see // https://stripe.com/docs/currencies#zero-decimal var int_currencies = [ @@ -33,6 +38,10 @@ odoo.define('payment_stripe.stripe', function(require) { }).done(function(data){ handler.isTokenGenerate = false; window.location.href = data; + }).fail(function(){ + var msg = arguments && arguments[1] && arguments[1].data && arguments[1].data.message; + var wizard = $(qweb.render('stripe.error', {'msg': msg || _t('Payment error')})); + wizard.appendTo($('body')).modal({'keyboard': true}); }); }, }); diff --git a/addons/payment_stripe/static/src/xml/stripe_templates.xml b/addons/payment_stripe/static/src/xml/stripe_templates.xml new file mode 100644 index 00000000000..c5966fe6274 --- /dev/null +++ b/addons/payment_stripe/static/src/xml/stripe_templates.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/addons/point_of_sale/static/src/js/tests.js b/addons/point_of_sale/static/src/js/tests.js index 0c578088b20..c12d5c5bb0e 100644 --- a/addons/point_of_sale/static/src/js/tests.js +++ b/addons/point_of_sale/static/src/js/tests.js @@ -110,7 +110,8 @@ odoo.define('point_of_sale.Tour', function (require) { trigger: ".header-button", }, { content: "confirm closing the frontend", - trigger: ".header-button", + trigger: ".header-button.confirm", + run: function() {}, //it's a check, }]); tour.register('pos_basic_order', { test: true, url: '/pos/web' }, steps); diff --git a/addons/pos_restaurant/static/src/js/multiprint.js b/addons/pos_restaurant/static/src/js/multiprint.js index 8dc63ea07fc..65956ffa66d 100644 --- a/addons/pos_restaurant/static/src/js/multiprint.js +++ b/addons/pos_restaurant/static/src/js/multiprint.js @@ -191,7 +191,12 @@ models.Order = models.Order.extend({ var product_id = line.get_product().id; if (typeof resume[line_hash] === 'undefined') { - resume[line_hash] = { qty: qty, note: note, product_id: product_id }; + resume[line_hash] = { + qty: qty, + note: note, + product_id: product_id, + product_name_wrapped: line.generate_wrapped_product_name(), + }; } else { resume[line_hash].qty += qty; } @@ -222,6 +227,7 @@ models.Order = models.Order.extend({ add.push({ 'id': curr.product_id, 'name': this.pos.db.get_product_by_id(curr.product_id).display_name, + 'name_wrapped': curr.product_name_wrapped, 'note': curr.note, 'qty': curr.qty, }); @@ -229,6 +235,7 @@ models.Order = models.Order.extend({ add.push({ 'id': curr.product_id, 'name': this.pos.db.get_product_by_id(curr.product_id).display_name, + 'name_wrapped': curr.product_name_wrapped, 'note': curr.note, 'qty': curr.qty - old.qty, }); @@ -236,6 +243,7 @@ models.Order = models.Order.extend({ rem.push({ 'id': curr.product_id, 'name': this.pos.db.get_product_by_id(curr.product_id).display_name, + 'name_wrapped': curr.product_name_wrapped, 'note': curr.note, 'qty': old.qty - curr.qty, }); @@ -248,6 +256,7 @@ models.Order = models.Order.extend({ rem.push({ 'id': old.product_id, 'name': this.pos.db.get_product_by_id(old.product_id).display_name, + 'name_wrapped': old.product_name_wrapped, 'note': old.note, 'qty': old.qty, }); diff --git a/addons/pos_restaurant/static/src/xml/multiprint.xml b/addons/pos_restaurant/static/src/xml/multiprint.xml index 4f3d0cf7379..282d07421c2 100644 --- a/addons/pos_restaurant/static/src/xml/multiprint.xml +++ b/addons/pos_restaurant/static/src/xml/multiprint.xml @@ -8,6 +8,15 @@ + + + + + + + + + - + + NOTE @@ -55,8 +65,9 @@ - + + NOTE diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py index b0cc6e2d2db..492223df2fe 100644 --- a/addons/product/models/product_template.py +++ b/addons/product/models/product_template.py @@ -403,7 +403,7 @@ class ProductTemplate(models.Model): updated_products.write({'attribute_value_ids': [(4, value_id.id)]}) # list of values combination - existing_variants = [set(variant.attribute_value_ids.ids) for variant in tmpl_id.product_variant_ids] + existing_variants = [set(variant.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant).ids) for variant in tmpl_id.product_variant_ids] variant_matrix = itertools.product(*(line.value_ids for line in tmpl_id.attribute_line_ids if line.value_ids and line.value_ids[0].attribute_id.create_variant)) variant_matrix = map(lambda record_list: reduce(lambda x, y: x+y, record_list, self.env['product.attribute.value']), variant_matrix) to_create_variants = filter(lambda rec_set: set(rec_set.ids) not in existing_variants, variant_matrix) @@ -412,9 +412,9 @@ class ProductTemplate(models.Model): variants_to_activate = self.env['product.product'] variants_to_unlink = self.env['product.product'] for product_id in tmpl_id.product_variant_ids: - if not product_id.active and product_id.attribute_value_ids in variant_matrix: + if not product_id.active and product_id.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant) in variant_matrix: variants_to_activate |= product_id - elif product_id.attribute_value_ids not in variant_matrix: + elif product_id.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant) not in variant_matrix: variants_to_unlink |= product_id if variants_to_activate: variants_to_activate.write({'active': True}) diff --git a/addons/project/models/project.py b/addons/project/models/project.py index ed029b1a4f3..5f1b2996ec4 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -407,7 +407,7 @@ class Task(models.Model): @api.onchange('project_id') def _onchange_project(self): default_partner_id = self.env.context.get('default_partner_id') - default_partner = self.env['res.partner'].browse(default_partner_id) if default_partner_id else None + default_partner = self.env['res.partner'].browse(default_partner_id) if default_partner_id else self.env['res.partner'] if self.project_id: self.partner_id = self.project_id.partner_id or default_partner self.stage_id = self.stage_find(self.project_id.id, [('fold', '=', False)]) diff --git a/addons/project_issue/views/project_project_views.xml b/addons/project_issue/views/project_project_views.xml index b5169400bb3..c5bdc5f75a9 100644 --- a/addons/project_issue/views/project_project_views.xml +++ b/addons/project_issue/views/project_project_views.xml @@ -24,7 +24,7 @@ - + @@ -35,7 +35,7 @@
- + diff --git a/addons/report/views/report_paperformat_views.xml b/addons/report/views/report_paperformat_views.xml index 433ed8cc304..39a1b9bdaff 100644 --- a/addons/report/views/report_paperformat_views.xml +++ b/addons/report/views/report_paperformat_views.xml @@ -29,7 +29,7 @@ - + diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml index 7148d2c137e..d30dcebd686 100644 --- a/addons/sale/views/sale_views.xml +++ b/addons/sale/views/sale_views.xml @@ -244,9 +244,9 @@
days
-
+ + stock.inventory.line.tree2 + stock.inventory.line + + + + + + + + + + + + + + + + + + + stock.inventory.line.search + stock.inventory.line + + + + + + + + + + + stock.inventory.filter stock.inventory @@ -56,6 +90,19 @@ +
+ +