diff options
36 files changed, 139 insertions, 77 deletions
diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 1728aecbe33..9fd3787f03b 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -3,7 +3,7 @@ from odoo import api, fields, models, _ from odoo.osv import expression from odoo.tools import float_is_zero -from odoo.tools import float_compare, float_round +from odoo.tools import float_compare, float_round, float_repr from odoo.tools.misc import formatLang from odoo.exceptions import UserError, ValidationError @@ -591,7 +591,8 @@ class AccountBankStatementLine(models.Model): from_clause = "FROM account_move_line aml JOIN account_account acc ON acc.id = aml.account_id " where_clause = """WHERE aml.company_id = %(company_id)s AND ( - (aml.statement_id IS NULL AND aml.account_id IN %(account_payable_receivable)s) + (aml.statement_id IS NULL AND aml.account_id IN %(account_payable_receivable)s + AND aml.payment_id IS NOT NULL) OR ("""+acc_type+""" AND aml.reconciled = false) )""" @@ -615,7 +616,7 @@ class AccountBankStatementLine(models.Model): precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places params = {'company_id': self.env.user.company_id.id, 'account_payable_receivable': (self.journal_id.default_credit_account_id.id, self.journal_id.default_debit_account_id.id), - 'amount': float_round(amount, precision_digits=precision), + 'amount': float_repr(float_round(amount, precision_digits=precision), precision_digits=precision), 'partner_id': self.partner_id.id, 'excluded_ids': tuple(excluded_ids), 'ref': self.name, @@ -636,9 +637,9 @@ class AccountBankStatementLine(models.Model): # Look for a single move line with the same amount field = currency and 'amount_residual_currency' or 'amount_residual' liquidity_field = currency and 'amount_currency' or amount > 0 and 'debit' or 'credit' - liquidity_amt_clause = currency and '%(amount)s' or 'abs(%(amount)s)' + liquidity_amt_clause = currency and '%(amount)s::numeric' or 'abs(%(amount)s::numeric)' sql_query = self._get_common_sql_query(excluded_ids=excluded_ids) + \ - " AND ("+field+" = %(amount)s OR (acc.internal_type = 'liquidity' AND "+liquidity_field+" = " + liquidity_amt_clause + ")) \ + " AND ("+field+" = %(amount)s::numeric OR (acc.internal_type = 'liquidity' AND "+liquidity_field+" = " + liquidity_amt_clause + ")) \ ORDER BY date_maturity asc, aml.id asc LIMIT 1" self.env.cr.execute(sql_query, params) results = self.env.cr.fetchone() diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index cb013f403db..6606450da4d 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -469,7 +469,7 @@ class AccountInvoice(models.Model): @api.onchange('invoice_line_ids') def _onchange_invoice_line_ids(self): taxes_grouped = self.get_taxes_values() - tax_lines = self.tax_line_ids.browse([]) + tax_lines = self.tax_line_ids.filtered('manual') for tax in taxes_grouped.values(): tax_lines += tax_lines.new(tax) self.tax_line_ids = tax_lines diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml index ade9b2738d7..d73d9ca9f7a 100644 --- a/addons/account/views/report_invoice.xml +++ b/addons/account/views/report_invoice.xml @@ -44,7 +44,7 @@ <strong>Customer Code:</strong> <p t-field="o.partner_id.ref"/> </div> - <div name="reference" class="col-xs-2" t-if="o.reference and o.type == 'in_invoice'"> + <div name="reference" class="col-xs-2" t-if="o.reference"> <strong>Reference:</strong> <p t-field="o.reference"/> </div> diff --git a/addons/base_address_city/models/res_partner.py b/addons/base_address_city/models/res_partner.py index afa5cd759e8..f6fcbf61d42 100644 --- a/addons/base_address_city/models/res_partner.py +++ b/addons/base_address_city/models/res_partner.py @@ -20,6 +20,8 @@ class Partner(models.Model): @api.model def _fields_view_get_address(self, arch): arch = super(Partner, self)._fields_view_get_address(arch) + if not self._context.get('no_address_format'): + return arch # render the partner address accordingly to address_view_id doc = etree.fromstring(arch) for city_node in doc.xpath("//field[@name='city']"): diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml index e56a181a358..fff0436699f 100644 --- a/addons/crm/views/crm_lead_views.xml +++ b/addons/crm/views/crm_lead_views.xml @@ -203,7 +203,7 @@ <group> <group> <field name="name" string="Opportunity Title" placeholder="e.g. Customer Deal"/> - <field name="partner_id"/> + <field name="partner_id" domain="[('customer', '=', True)]" context="{'search_default_customer': 1}"/> <field name="email_from" invisible="1"/> <field name="phone" invisible="1"/> <label for="planned_revenue"/> diff --git a/addons/hr_expense_check/__manifest__.py b/addons/hr_expense_check/__manifest__.py index 90320ac6f64..8fe469b5900 100644 --- a/addons/hr_expense_check/__manifest__.py +++ b/addons/hr_expense_check/__manifest__.py @@ -6,7 +6,6 @@ 'description': """ Print amount in words on checks issued for expenses """, - 'category': 'Accounting & Finance', 'version': '1.0', 'depends': ['account_check_printing', 'hr_expense'], 'auto_install': True, diff --git a/addons/l10n_fr_certification/__manifest__.py b/addons/l10n_fr_certification/__manifest__.py index e83e9102914..7e2ea327d54 100644 --- a/addons/l10n_fr_certification/__manifest__.py +++ b/addons/l10n_fr_certification/__manifest__.py @@ -9,7 +9,7 @@ """, 'depends': ['l10n_fr'], 'installable': True, - 'auto_install': True, + 'auto_install': False, 'application': True, 'data': [ 'views/no_cancel.xml', diff --git a/addons/l10n_fr_certification/models/account.py b/addons/l10n_fr_certification/models/account.py index ecb0f5aff8b..bef0880d7ca 100644 --- a/addons/l10n_fr_certification/models/account.py +++ b/addons/l10n_fr_certification/models/account.py @@ -102,7 +102,7 @@ class AccountMove(models.Model): """ moves = self.search([('state', '=', 'posted'), ('company_id', '=', company_id), - ('l10n_fr_secure_sequence_number', '!=', False)], + ('l10n_fr_secure_sequence_number', '!=', 0)], order="l10n_fr_secure_sequence_number ASC") if not moves: diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py index 8b5f45d3b24..69b095cd0f7 100644 --- a/addons/mrp/models/mrp_workorder.py +++ b/addons/mrp/models/mrp_workorder.py @@ -282,7 +282,7 @@ class MrpWorkorder(models.Model): for move_lot in self.active_move_lot_ids: # Check if move_lot already exists if move_lot.quantity_done <= 0: # rounding... - move_lot.unlink() + move_lot.sudo().unlink() continue if not move_lot.lot_id: raise UserError(_('You should provide a lot for a component')) @@ -291,7 +291,7 @@ class MrpWorkorder(models.Model): if lots: lots[0].quantity_done += move_lot.quantity_done lots[0].lot_produced_id = self.final_lot_id.id - move_lot.unlink() + move_lot.sudo().unlink() else: move_lot.lot_produced_id = self.final_lot_id.id move_lot.done_wo = True diff --git a/addons/mrp/models/stock_quant.py b/addons/mrp/models/stock_quant.py index e94b786f568..6074735a737 100644 --- a/addons/mrp/models/stock_quant.py +++ b/addons/mrp/models/stock_quant.py @@ -11,3 +11,9 @@ class StockQuant(models.Model): 'stock.quant', 'stock_quant_consume_rel', 'produce_quant_id', 'consume_quant_id') produced_quant_ids = fields.Many2many( 'stock.quant', 'stock_quant_consume_rel', 'consume_quant_id', 'produce_quant_id') + + def _prepare_history(self): + vals = super(StockQuant, self)._prepare_history() + vals['consumed_quant_ids'] = [(4, quant.id) for quant in self.consumed_quant_ids] + vals['produced_quant_ids'] = [(4, quant.id) for quant in self.produced_quant_ids] + return vals diff --git a/addons/payment/views/account_payment_views.xml b/addons/payment/views/account_payment_views.xml index a284f69b77f..dd9a5400be3 100644 --- a/addons/payment/views/account_payment_views.xml +++ b/addons/payment/views/account_payment_views.xml @@ -19,8 +19,9 @@ <field name="arch" type="xml"> <field name="payment_method_id" position="after"> <field name="payment_method_code" invisible="1"/> - <field name="payment_token_id" attrs="{'invisible': [('payment_method_code', '!=', 'electronic')], - 'required': [('payment_method_code', '=', 'electronic')]}"/> + <field name="payment_token_id" options="{'no_create': True}" + attrs="{'invisible': [('payment_method_code', '!=', 'electronic')], + 'required': [('payment_method_code', '=', 'electronic')]}"/> </field> </field> </record> diff --git a/addons/payment/views/payment_views.xml b/addons/payment/views/payment_views.xml index ed9ad78f316..3041e23c918 100644 --- a/addons/payment/views/payment_views.xml +++ b/addons/payment/views/payment_views.xml @@ -241,7 +241,7 @@ </group> <group> <field name="acquirer_id"/> - <field name="payment_token_id"/> + <field name="payment_token_id" options="{'no_create': True}"/> <field name="acquirer_reference"/> <field name="create_date"/> <field name="date_validate"/> diff --git a/addons/point_of_sale/i18n/es_PE.po b/addons/point_of_sale/i18n/es_PE.po index a5dd2c6e63e..4f68df80eef 100644 --- a/addons/point_of_sale/i18n/es_PE.po +++ b/addons/point_of_sale/i18n/es_PE.po @@ -2220,9 +2220,8 @@ msgstr "Analisis de Pedidos" #. module: point_of_sale #: code:addons/point_of_sale/models/pos_config.py:180 -#, fuzzy, python-format msgid "POS Order %s" -msgstr "Pedidos de PDV" +msgstr "" #. module: point_of_sale #: model:ir.ui.view,arch_db:point_of_sale.view_pos_order_line_form @@ -2246,9 +2245,8 @@ msgstr "Líneas de Pedido de PDV" #. module: point_of_sale #: code:addons/point_of_sale/models/pos_config.py:191 -#, fuzzy, python-format msgid "POS order line %s" -msgstr "Líneas de Pedido de PDV" +msgstr "" #. module: point_of_sale #: model:ir.ui.view,arch_db:point_of_sale.view_report_pos_order_search diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index 9a16c4d5266..809af84aa87 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -162,6 +162,7 @@ class PosOrder(models.Model): 'origin': self.name, 'account_id': self.partner_id.property_account_receivable_id.id, 'journal_id': self.session_id.config_id.invoice_journal_id.id, + 'company_id': self.company_id.id, 'type': 'out_invoice', 'reference': self.name, 'partner_id': self.partner_id.id, @@ -857,21 +858,14 @@ class PosOrderLine(models.Model): @api.depends('price_unit', 'tax_ids', 'qty', 'discount', 'product_id') def _compute_amount_line_all(self): for line in self: - currency = line.order_id.pricelist_id.currency_id - taxes = line.tax_ids.filtered(lambda tax: tax.company_id.id == line.order_id.company_id.id) - fiscal_position_id = line.order_id.fiscal_position_id - if fiscal_position_id: - taxes = fiscal_position_id.map_tax(taxes, line.product_id, line.order_id.partner_id) - price = self.env['account.tax']._fix_tax_included_price( - line.price_unit * (1 - (line.discount or 0.0) / 100.0), line.product_id.taxes_id, taxes) - line.price_subtotal = line.price_subtotal_incl = price * line.qty - if taxes: - taxes = taxes.compute_all(price, currency, line.qty, product=line.product_id, partner=line.order_id.partner_id or False) - line.price_subtotal = taxes['total_excluded'] - line.price_subtotal_incl = taxes['total_included'] - - line.price_subtotal = currency.round(line.price_subtotal) - line.price_subtotal_incl = currency.round(line.price_subtotal_incl) + fpos = line.order_id.fiscal_position_id + tax_ids_after_fiscal_position = fpos.map_tax(line.tax_ids, line.product_id, line.order_id.partner_id) if fpos else line.tax_ids + price = line.price_unit * (1 - (line.discount or 0.0) / 100.0) + taxes = tax_ids_after_fiscal_position.compute_all(price, line.order_id.pricelist_id.currency_id, line.qty, product=line.product_id, partner=line.order_id.partner_id) + line.update({ + 'price_subtotal_incl': taxes['total_included'], + 'price_subtotal': taxes['total_excluded'], + }) @api.onchange('product_id') def _onchange_product_id(self): @@ -883,8 +877,10 @@ class PosOrderLine(models.Model): price = self.order_id.pricelist_id.get_product_price( self.product_id, self.qty or 1.0, self.order_id.partner_id) self._onchange_qty() - self.price_unit = price - self.tax_ids = self.product_id.taxes_id + self.tax_ids = self.product_id.taxes_id.filtered(lambda r: not self.company_id or r.company_id == self.company_id) + fpos = self.order_id.fiscal_position_id + tax_ids_after_fiscal_position = fpos.map_tax(self.tax_ids, line.product_id, line.order_id.partner_id) if fpos else self.tax_ids + self.price_unit = self.env['account.tax']._fix_tax_included_price(price, self.product_id.taxes_id, tax_ids_after_fiscal_position) @api.onchange('qty', 'discount', 'price_unit', 'tax_ids') def _onchange_qty(self): diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py index 5e2bcba48f6..93939587ee0 100644 --- a/addons/product/models/product_template.py +++ b/addons/product/models/product_template.py @@ -20,7 +20,7 @@ class ProductTemplate(models.Model): if self._context.get('categ_id') or self._context.get('default_categ_id'): return self._context.get('categ_id') or self._context.get('default_categ_id') category = self.env.ref('product.product_category_all', raise_if_not_found=False) - return category and category.id or False + return category.id if category.type == 'normal' else False def _get_default_uom_id(self): return self.env["product.uom"].search([], limit=1, order='id').id diff --git a/addons/rating/controllers/main.py b/addons/rating/controllers/main.py index c167a980e72..817f26aa79e 100644 --- a/addons/rating/controllers/main.py +++ b/addons/rating/controllers/main.py @@ -28,7 +28,7 @@ class Rating(http.Controller): 'rate_name': rate_names[rate], 'rate': rate }) - @http.route(['/rating/<string:token>/<int:rate>/submit_feedback'], type="http", auth="public", method=['post']) + @http.route(['/rating/<string:token>/<int:rate>/submit_feedback'], type="http", auth="public", methods=['post']) def submit_rating(self, token, rate, **kwargs): rating = request.env['rating.rating'].sudo().search([('access_token', '=', token)]) if not rating: diff --git a/addons/sale/__init__.py b/addons/sale/__init__.py index d84fb182e8f..7e12dedf6a4 100644 --- a/addons/sale/__init__.py +++ b/addons/sale/__init__.py @@ -1,6 +1,24 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. + +from functools import partial +import openerp +from openerp import api, SUPERUSER_ID + import models import report import wizard + + +def uninstall_hook(cr, registry): + def update_dashboard_graph_model(dbname): + db_registry = openerp.modules.registry.RegistryManager.new(dbname) + with api.Environment.manage(), db_registry.cursor() as cr: + env = api.Environment(cr, SUPERUSER_ID, {}) + if 'crm.team' in env: + recs = env['crm.team'].search([]) + for rec in recs: + rec._onchange_team_type() + + cr.after("commit", partial(update_dashboard_graph_model, cr.dbname))
\ No newline at end of file diff --git a/addons/sale/__manifest__.py b/addons/sale/__manifest__.py index f44873daa8e..d1be4a05528 100644 --- a/addons/sale/__manifest__.py +++ b/addons/sale/__manifest__.py @@ -65,6 +65,7 @@ The Dashboard for the Sales Manager will include 'data/sale_demo.xml', 'data/product_product_demo.xml', ], + 'uninstall_hook': "uninstall_hook", 'css': ['static/src/css/sale.css'], 'installable': True, 'auto_install': False, diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py index 1d144695f52..eac6318e0d7 100644 --- a/addons/stock/models/stock_quant.py +++ b/addons/stock/models/stock_quant.py @@ -317,12 +317,15 @@ class Quant(models.Model): # price update + accounting entries adjustments solved_quants._price_update(solving_quant.cost) # merge history (and cost?) - solved_quants.write({ - 'history_ids': [(4, history_move.id) for history_move in solving_quant.history_ids] - }) + solved_quants.write(solving_quant._prepare_history()) solving_quant.with_context(force_unlink=True).unlink() solving_quant = remaining_solving_quant + def _prepare_history(self): + return { + 'history_ids': [(4, history_move.id) for history_move in self.history_ids], + } + @api.multi def _price_update(self, newprice): # TDE note: use ACLs instead of sudoing everything diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 85bd770bacc..54bb061ad06 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1003,6 +1003,8 @@ class Binary(http.Controller): elif status != 200 and download: return request.not_found() + height = int(height) + width = int(width) if content and (width or height): # resize maximum 500*500 if width > 500: diff --git a/addons/web/static/src/js/apps.js b/addons/web/static/src/js/apps.js index 7250674272c..bb642ed57e7 100644 --- a/addons/web/static/src/js/apps.js +++ b/addons/web/static/src/js/apps.js @@ -61,7 +61,7 @@ var Apps = Widget.extend({ }, destroy: function() { - $(window).off("message.apps"); + $(window).off("message." + this.uniq); if (this.$ifr) { this.$ifr.remove(); this.$ifr = null; @@ -116,10 +116,11 @@ var Apps = Widget.extend({ qs.debug = session.debug; } var u = $.param.querystring(client.origin + "/apps/embed/client", qs); - var css = {width: '100%', height: '400px'}; + var css = {width: '100%', height: '750px'}; self.$ifr = $('<iframe>').attr('src', u); - $(window).on("message.apps", self.proxy('_on_message')); + self.uniq = _.uniqueId('apps'); + $(window).on("message." + self.uniq, self.proxy('_on_message')); self.on('message:ready', self, function(m) { var w = this.$ifr[0].contentWindow; diff --git a/addons/web_editor/models/ir_http.py b/addons/web_editor/models/ir_http.py index 04845688ea9..d6e2b2d5ee6 100644 --- a/addons/web_editor/models/ir_http.py +++ b/addons/web_editor/models/ir_http.py @@ -15,7 +15,7 @@ class IrHttp(models.AbstractModel): context['editable'] = True if 'edit_translations' in request.httprequest.args and 'edit_translations' not in context: context['edit_translations'] = True - if context.get('lang') != "en_US" and 'translatable' not in context: + if context.get('edit_translations') and 'translatable' not in context: context['translatable'] = True request.context = context return super(IrHttp, cls)._dispatch() diff --git a/addons/web_editor/models/ir_ui_view.py b/addons/web_editor/models/ir_ui_view.py index dc81b6a44d6..b6c421e0e82 100644 --- a/addons/web_editor/models/ir_ui_view.py +++ b/addons/web_editor/models/ir_ui_view.py @@ -48,7 +48,7 @@ class IrUiView(models.Model): if value is not None: # TODO: batch writes? - if self.get_default_lang_code() and field != 'arch' and not isinstance(Model._fields[field], fields.Html): + if not self.env.context.get('lang') and self.get_default_lang_code(): Model.browse(int(el.get('data-oe-id'))).with_context(lang=self.get_default_lang_code()).write({field: value}) else: Model.browse(int(el.get('data-oe-id'))).write({field: value}) diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index c62486d34a0..843db91a50c 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -205,7 +205,7 @@ class Http(models.AbstractModel): path.pop(1) request.context = context return cls.reroute('/'.join(path) or '/') - if path[1] == request.website.default_lang_code: + if request.lang == request.website.default_lang_code: context['edit_translations'] = False if not context.get('tz'): context['tz'] = request.session.get('geoip', {}).get('time_zone') diff --git a/addons/website_blog/controllers/main.py b/addons/website_blog/controllers/main.py index 6ae960fc9c9..4935244956f 100644 --- a/addons/website_blog/controllers/main.py +++ b/addons/website_blog/controllers/main.py @@ -174,6 +174,7 @@ class WebsiteBlog(http.Controller): v['blog'] = blog v['base_url'] = request.env['ir.config_parameter'].sudo().get_param('web.base.url') v['posts'] = request.env['blog.post'].search([('blog_id','=', blog.id)], limit=min(int(limit), 50)) + v['html2plaintext'] = html2plaintext r = request.render("website_blog.blog_feed", v, headers=[('Content-Type', 'application/atom+xml')]) return r diff --git a/addons/website_blog/views/website_blog_templates.xml b/addons/website_blog/views/website_blog_templates.xml index f6f35135484..f6f513668ac 100644 --- a/addons/website_blog/views/website_blog_templates.xml +++ b/addons/website_blog/views/website_blog_templates.xml @@ -557,7 +557,7 @@ <link t-att-href="'%s%s' % (base_url, post.website_url)"/> <id t-esc="'%s%s' % (base_url, post.website_url)"/> <author><name t-esc="post.sudo().author_id.name"/></author> - <summary t-esc="post.subtitle"/> + <summary t-esc="html2plaintext(post.teaser)"/> <updated t-esc="post.write_date.replace(' ', 'T') + 'Z'"/> </entry> </feed> diff --git a/addons/website_form/models/models.py b/addons/website_form/models/models.py index 7ac2c38d715..e9a4614cdae 100644 --- a/addons/website_form/models/models.py +++ b/addons/website_form/models/models.py @@ -20,12 +20,6 @@ class website_form_model(models.Model): website_form_default_field_id = fields.Many2one('ir.model.fields', 'Field for custom form data', domain="[('model', '=', model), ('ttype', '=', 'text')]", help="Specify the field which will contain meta and custom form fields datas.") website_form_label = fields.Char("Label for form action", help="Form action label. Ex: crm.lead could be 'Send an e-mail' and project.issue could be 'Create an Issue'.") - def _all_inherited_model_ids(self): - return list(itertools.chain( - [self.id], - *(m._all_inherited_model_ids() for m in self.inherited_model_ids) - )) - def _get_form_writable_fields(self): """ Restriction of "authorized fields" (fields which can be used in the @@ -33,16 +27,16 @@ class website_form_model(models.Model): builders and are writable. By default no field is writable by the form builder. """ - excluded = { + included = { field.name for field in self.env['ir.model.fields'].sudo().search([ - ('model_id', 'in', self._all_inherited_model_ids()), - ('website_form_blacklisted', '=', True) + ('model_id', '=', self.id), + ('website_form_blacklisted', '=', False) ]) } return { k: v for k, v in self.get_authorized_fields(self.model).iteritems() - if k not in excluded + if k in included } @api.model diff --git a/addons/website_portal_sale/controllers/main.py b/addons/website_portal_sale/controllers/main.py index dc8104a4c42..6731661e5e5 100644 --- a/addons/website_portal_sale/controllers/main.py +++ b/addons/website_portal_sale/controllers/main.py @@ -220,13 +220,29 @@ class website_account(website_account): }) return request.render("website_portal_sale.portal_my_invoices", values) + @http.route(['/my/invoices/pdf/<int:invoice_id>'], type='http', auth="user", website=True) + def portal_get_invoice(self, invoice_id=None, **kw): + invoice = request.env['account.invoice'].browse([invoice_id]) + try: + invoice.check_access_rights('read') + invoice.check_access_rule('read') + except AccessError: + return request.render("website.403") + + pdf = request.env['report'].sudo().get_pdf([invoice_id], 'account.report_invoice') + pdfhttpheaders = [ + ('Content-Type', 'application/pdf'), ('Content-Length', len(pdf)), + ('Content-Disposition', 'attachment; filename=Invoice.pdf;') + ] + return request.make_response(pdf, headers=pdfhttpheaders) + def details_form_validate(self, data): error, error_message = super(website_account, self).details_form_validate(data) # prevent VAT/name change if invoices exist partner = request.env['res.users'].browse(request.uid).partner_id invoices = request.env['account.invoice'].sudo().search_count([('partner_id', '=', partner.id), ('state', 'not in', ['draft', 'cancel'])]) if invoices: - if 'vat' in partner and (data['vat'] or False) != (partner.vat or False): + if 'vat' in data and (data['vat'] or False) != (partner.vat or False): error['vat'] = 'error' error_message.append(_('Changing VAT number is not allowed once invoices have been issued for your account. Please contact us directly for this operation.')) if 'name' in data and (data['name'] or False) != (partner.name or False): diff --git a/addons/website_portal_sale/views/website_portal_sale_templates.xml b/addons/website_portal_sale/views/website_portal_sale_templates.xml index 656c3722644..aa50e1c2f65 100644 --- a/addons/website_portal_sale/views/website_portal_sale_templates.xml +++ b/addons/website_portal_sale/views/website_portal_sale_templates.xml @@ -185,7 +185,7 @@ <t t-foreach="invoices" t-as="invoice"> <tr> <td class= "cell_overflow"> - <a t-att-href="'/report/pdf/account.report_invoice/'+str(invoice.id)" t-att-title="invoice.number"><t t-esc="invoice.number"/></a> + <a t-att-href="'/my/invoices/pdf/'+str(invoice.id)" t-att-title="invoice.number"><t t-esc="invoice.number"/></a> </td> <td><span t-field="invoice.date_invoice"/></td> <td class='hidden-xs'><span t-field="invoice.date_due"/></td> diff --git a/addons/website_sale_digital/controllers/main.py b/addons/website_sale_digital/controllers/main.py index 089f2b97f35..b633596a0bf 100644 --- a/addons/website_sale_digital/controllers/main.py +++ b/addons/website_sale_digital/controllers/main.py @@ -85,7 +85,7 @@ class WebsiteSaleDigital(website_account): # Also check for attachments in the product templates elif res_model == 'product.template': - P = request.env['product.product'] + P = request.env['product.product'].sudo() template_ids = map(lambda x: P.browse(x).product_tmpl_id.id, purchased_products) if res_id not in template_ids: return redirect(self.orders_page) diff --git a/doc/cla/individual/leneo.md b/doc/cla/individual/leneo.md new file mode 100644 index 00000000000..82cb85f012c --- /dev/null +++ b/doc/cla/individual/leneo.md @@ -0,0 +1,11 @@ +Switzerland, 30/06/2017 + +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, + +Frederic Siffert frederic@treyvaud.ch https://github.com/leNeo diff --git a/doc/reference/views.rst b/doc/reference/views.rst index be37aaaa821..386989b75c6 100644 --- a/doc/reference/views.rst +++ b/doc/reference/views.rst @@ -237,6 +237,11 @@ Possible children elements of the list view are: Makes the button ``invisible`` if the record is *not* in one of the listed states + + .. danger:: + + Using ``states`` in combination with ``attrs`` may lead to + unexpected results as domains are combined with a logical AND. ``context`` merged into the view's context when performing the button's Odoo call ``confirm`` diff --git a/odoo/addons/base/ir/ir_sequence.py b/odoo/addons/base/ir/ir_sequence.py index e3c9740cb62..95aa941c86e 100644 --- a/odoo/addons/base/ir/ir_sequence.py +++ b/odoo/addons/base/ir/ir_sequence.py @@ -197,11 +197,11 @@ class IrSequence(models.Model): year = fields.Date.from_string(date).strftime('%Y') date_from = '{}-01-01'.format(year) date_to = '{}-12-31'.format(year) - date_range = self.env['ir.sequence.date_range'].search([('sequence_id', '=', self.id), ('date_from', '>=', date), ('date_from', '<=', date_to)], order='date_from desc') + date_range = self.env['ir.sequence.date_range'].search([('sequence_id', '=', self.id), ('date_from', '>=', date), ('date_from', '<=', date_to)], order='date_from desc', limit=1) if date_range: date_to = datetime.strptime(date_range.date_from, '%Y-%m-%d') + timedelta(days=-1) date_to = date_to.strftime('%Y-%m-%d') - date_range = self.env['ir.sequence.date_range'].search([('sequence_id', '=', self.id), ('date_to', '>=', date_from), ('date_to', '<=', date)], order='date_to desc') + date_range = self.env['ir.sequence.date_range'].search([('sequence_id', '=', self.id), ('date_to', '>=', date_from), ('date_to', '<=', date)], order='date_to desc', limit=1) if date_range: date_from = datetime.strptime(date_range.date_to, '%Y-%m-%d') + timedelta(days=1) date_from = date_from.strftime('%Y-%m-%d') diff --git a/odoo/addons/base/ir/ir_translation.py b/odoo/addons/base/ir/ir_translation.py index 972bb976eda..28ea133b321 100644 --- a/odoo/addons/base/ir/ir_translation.py +++ b/odoo/addons/base/ir/ir_translation.py @@ -240,18 +240,19 @@ class IrTranslation(models.Model): ''' When changing source term of a translation, change its value in db for the associated object, and the src field. ''' - for record in self: - if record.type == 'model': - model_name, field_name = record.name.split(',') - model = self.env[model_name] - field = model._fields[field_name] - if not callable(field.translate): - # Make a context without language information, because we want - # to write on the value stored in db and not on the one - # associated with the current language. Also not removing lang - # from context trigger an error when lang is different. - model.browse(record.res_id).with_context(lang=None).write({field_name: record.source}) - return self.write({'src': self.source}) + self.ensure_one() + if self.type == 'model': + model_name, field_name = self.name.split(',') + model = self.env[model_name] + field = model._fields[field_name] + if not callable(field.translate): + # Make a context without language information, because we want + # to write on the value stored in db and not on the one + # associated with the current language. Also not removing lang + # from context trigger an error when lang is different. + model.browse(self.res_id).with_context(lang=None).write({field_name: self.source}) + if self.src != self.source: + self.write({'src': self.source}) def _search_source(self, operator, value): ''' the source term is stored on 'src' field ''' diff --git a/odoo/addons/base/tests/test_base.py b/odoo/addons/base/tests/test_base.py index 5a45f00e95c..806a9f7fdea 100644 --- a/odoo/addons/base/tests/test_base.py +++ b/odoo/addons/base/tests/test_base.py @@ -526,3 +526,8 @@ class TestGroups(TransactionCase): # create a cycle and check a.implied_ids = d self.assertFalse(a._check_m2m_recursion('implied_ids')) + + def test_res_group_copy(self): + a = self.env['res.groups'].with_context(lang='en_US').create({'name': 'A'}) + b = a.copy() + self.assertFalse(a.name == b.name) diff --git a/odoo/models.py b/odoo/models.py index 7dbeaaad68f..4e76728ed8f 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -3814,7 +3814,7 @@ class BaseModel(object): trans_name, source_id, target_id = get_trans(field, old, new) domain = [('name', '=', trans_name), ('res_id', '=', source_id)] new_val = new_wo_lang[name] - if old.env.lang: + if old.env.lang and callable(field.translate): # the new value *without lang* must be the old value without lang new_wo_lang[name] = old_wo_lang[name] for vals in Translation.search_read(domain): @@ -3823,6 +3823,7 @@ class BaseModel(object): del vals['module'] # duplicated vals is not linked to any module vals['res_id'] = target_id if vals['lang'] == old.env.lang and field.translate is True: + vals['source'] = old_wo_lang[name] # the value should be the new value (given by copy()) vals['value'] = new_val Translation.create(vals) |
