[MERGE] forward port branch saas-14 up to 97258040d8

This commit is contained in:
Christophe Simonis
2017-07-04 14:40:20 +02:00
36 changed files with 139 additions and 77 deletions
@@ -3,7 +3,7 @@
from odoo import api, fields, models, _ from odoo import api, fields, models, _
from odoo.osv import expression from odoo.osv import expression
from odoo.tools import float_is_zero 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.tools.misc import formatLang
from odoo.exceptions import UserError, ValidationError 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 " 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 where_clause = """WHERE aml.company_id = %(company_id)s
AND ( 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 OR
("""+acc_type+""" AND aml.reconciled = false) ("""+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 precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places
params = {'company_id': self.env.user.company_id.id, 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), '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, 'partner_id': self.partner_id.id,
'excluded_ids': tuple(excluded_ids), 'excluded_ids': tuple(excluded_ids),
'ref': self.name, 'ref': self.name,
@@ -636,9 +637,9 @@ class AccountBankStatementLine(models.Model):
# Look for a single move line with the same amount # Look for a single move line with the same amount
field = currency and 'amount_residual_currency' or 'amount_residual' field = currency and 'amount_residual_currency' or 'amount_residual'
liquidity_field = currency and 'amount_currency' or amount > 0 and 'debit' or 'credit' 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) + \ 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" ORDER BY date_maturity asc, aml.id asc LIMIT 1"
self.env.cr.execute(sql_query, params) self.env.cr.execute(sql_query, params)
results = self.env.cr.fetchone() results = self.env.cr.fetchone()
+1 -1
View File
@@ -469,7 +469,7 @@ class AccountInvoice(models.Model):
@api.onchange('invoice_line_ids') @api.onchange('invoice_line_ids')
def _onchange_invoice_line_ids(self): def _onchange_invoice_line_ids(self):
taxes_grouped = self.get_taxes_values() 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(): for tax in taxes_grouped.values():
tax_lines += tax_lines.new(tax) tax_lines += tax_lines.new(tax)
self.tax_line_ids = tax_lines self.tax_line_ids = tax_lines
+1 -1
View File
@@ -44,7 +44,7 @@
<strong>Customer Code:</strong> <strong>Customer Code:</strong>
<p t-field="o.partner_id.ref"/> <p t-field="o.partner_id.ref"/>
</div> </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> <strong>Reference:</strong>
<p t-field="o.reference"/> <p t-field="o.reference"/>
</div> </div>
@@ -20,6 +20,8 @@ class Partner(models.Model):
@api.model @api.model
def _fields_view_get_address(self, arch): def _fields_view_get_address(self, arch):
arch = super(Partner, self)._fields_view_get_address(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 # render the partner address accordingly to address_view_id
doc = etree.fromstring(arch) doc = etree.fromstring(arch)
for city_node in doc.xpath("//field[@name='city']"): for city_node in doc.xpath("//field[@name='city']"):
+1 -1
View File
@@ -203,7 +203,7 @@
<group> <group>
<group> <group>
<field name="name" string="Opportunity Title" placeholder="e.g. Customer Deal"/> <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="email_from" invisible="1"/>
<field name="phone" invisible="1"/> <field name="phone" invisible="1"/>
<label for="planned_revenue"/> <label for="planned_revenue"/>
-1
View File
@@ -6,7 +6,6 @@
'description': """ 'description': """
Print amount in words on checks issued for expenses Print amount in words on checks issued for expenses
""", """,
'category': 'Accounting &amp; Finance',
'version': '1.0', 'version': '1.0',
'depends': ['account_check_printing', 'hr_expense'], 'depends': ['account_check_printing', 'hr_expense'],
'auto_install': True, 'auto_install': True,
+1 -1
View File
@@ -9,7 +9,7 @@
""", """,
'depends': ['l10n_fr'], 'depends': ['l10n_fr'],
'installable': True, 'installable': True,
'auto_install': True, 'auto_install': False,
'application': True, 'application': True,
'data': [ 'data': [
'views/no_cancel.xml', 'views/no_cancel.xml',
@@ -102,7 +102,7 @@ class AccountMove(models.Model):
""" """
moves = self.search([('state', '=', 'posted'), moves = self.search([('state', '=', 'posted'),
('company_id', '=', company_id), ('company_id', '=', company_id),
('l10n_fr_secure_sequence_number', '!=', False)], ('l10n_fr_secure_sequence_number', '!=', 0)],
order="l10n_fr_secure_sequence_number ASC") order="l10n_fr_secure_sequence_number ASC")
if not moves: if not moves:
+2 -2
View File
@@ -282,7 +282,7 @@ class MrpWorkorder(models.Model):
for move_lot in self.active_move_lot_ids: for move_lot in self.active_move_lot_ids:
# Check if move_lot already exists # Check if move_lot already exists
if move_lot.quantity_done <= 0: # rounding... if move_lot.quantity_done <= 0: # rounding...
move_lot.unlink() move_lot.sudo().unlink()
continue continue
if not move_lot.lot_id: if not move_lot.lot_id:
raise UserError(_('You should provide a lot for a component')) raise UserError(_('You should provide a lot for a component'))
@@ -291,7 +291,7 @@ class MrpWorkorder(models.Model):
if lots: if lots:
lots[0].quantity_done += move_lot.quantity_done lots[0].quantity_done += move_lot.quantity_done
lots[0].lot_produced_id = self.final_lot_id.id lots[0].lot_produced_id = self.final_lot_id.id
move_lot.unlink() move_lot.sudo().unlink()
else: else:
move_lot.lot_produced_id = self.final_lot_id.id move_lot.lot_produced_id = self.final_lot_id.id
move_lot.done_wo = True move_lot.done_wo = True
+6
View File
@@ -11,3 +11,9 @@ class StockQuant(models.Model):
'stock.quant', 'stock_quant_consume_rel', 'produce_quant_id', 'consume_quant_id') 'stock.quant', 'stock_quant_consume_rel', 'produce_quant_id', 'consume_quant_id')
produced_quant_ids = fields.Many2many( produced_quant_ids = fields.Many2many(
'stock.quant', 'stock_quant_consume_rel', 'consume_quant_id', 'produce_quant_id') '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
@@ -19,8 +19,9 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="payment_method_id" position="after"> <field name="payment_method_id" position="after">
<field name="payment_method_code" invisible="1"/> <field name="payment_method_code" invisible="1"/>
<field name="payment_token_id" attrs="{'invisible': [('payment_method_code', '!=', 'electronic')], <field name="payment_token_id" options="{'no_create': True}"
'required': [('payment_method_code', '=', 'electronic')]}"/> attrs="{'invisible': [('payment_method_code', '!=', 'electronic')],
'required': [('payment_method_code', '=', 'electronic')]}"/>
</field> </field>
</field> </field>
</record> </record>
+1 -1
View File
@@ -241,7 +241,7 @@
</group> </group>
<group> <group>
<field name="acquirer_id"/> <field name="acquirer_id"/>
<field name="payment_token_id"/> <field name="payment_token_id" options="{'no_create': True}"/>
<field name="acquirer_reference"/> <field name="acquirer_reference"/>
<field name="create_date"/> <field name="create_date"/>
<field name="date_validate"/> <field name="date_validate"/>
+2 -4
View File
@@ -2220,9 +2220,8 @@ msgstr "Analisis de Pedidos"
#. module: point_of_sale #. module: point_of_sale
#: code:addons/point_of_sale/models/pos_config.py:180 #: code:addons/point_of_sale/models/pos_config.py:180
#, fuzzy, python-format
msgid "POS Order %s" msgid "POS Order %s"
msgstr "Pedidos de PDV" msgstr ""
#. module: point_of_sale #. module: point_of_sale
#: model:ir.ui.view,arch_db:point_of_sale.view_pos_order_line_form #: 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 #. module: point_of_sale
#: code:addons/point_of_sale/models/pos_config.py:191 #: code:addons/point_of_sale/models/pos_config.py:191
#, fuzzy, python-format
msgid "POS order line %s" msgid "POS order line %s"
msgstr "Líneas de Pedido de PDV" msgstr ""
#. module: point_of_sale #. module: point_of_sale
#: model:ir.ui.view,arch_db:point_of_sale.view_report_pos_order_search #: model:ir.ui.view,arch_db:point_of_sale.view_report_pos_order_search
+13 -17
View File
@@ -162,6 +162,7 @@ class PosOrder(models.Model):
'origin': self.name, 'origin': self.name,
'account_id': self.partner_id.property_account_receivable_id.id, 'account_id': self.partner_id.property_account_receivable_id.id,
'journal_id': self.session_id.config_id.invoice_journal_id.id, 'journal_id': self.session_id.config_id.invoice_journal_id.id,
'company_id': self.company_id.id,
'type': 'out_invoice', 'type': 'out_invoice',
'reference': self.name, 'reference': self.name,
'partner_id': self.partner_id.id, 'partner_id': self.partner_id.id,
@@ -857,21 +858,14 @@ class PosOrderLine(models.Model):
@api.depends('price_unit', 'tax_ids', 'qty', 'discount', 'product_id') @api.depends('price_unit', 'tax_ids', 'qty', 'discount', 'product_id')
def _compute_amount_line_all(self): def _compute_amount_line_all(self):
for line in self: for line in self:
currency = line.order_id.pricelist_id.currency_id fpos = line.order_id.fiscal_position_id
taxes = line.tax_ids.filtered(lambda tax: tax.company_id.id == line.order_id.company_id.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
fiscal_position_id = line.order_id.fiscal_position_id price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
if fiscal_position_id: 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)
taxes = fiscal_position_id.map_tax(taxes, line.product_id, line.order_id.partner_id) line.update({
price = self.env['account.tax']._fix_tax_included_price( 'price_subtotal_incl': taxes['total_included'],
line.price_unit * (1 - (line.discount or 0.0) / 100.0), line.product_id.taxes_id, taxes) 'price_subtotal': taxes['total_excluded'],
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)
@api.onchange('product_id') @api.onchange('product_id')
def _onchange_product_id(self): def _onchange_product_id(self):
@@ -883,8 +877,10 @@ class PosOrderLine(models.Model):
price = self.order_id.pricelist_id.get_product_price( price = self.order_id.pricelist_id.get_product_price(
self.product_id, self.qty or 1.0, self.order_id.partner_id) self.product_id, self.qty or 1.0, self.order_id.partner_id)
self._onchange_qty() self._onchange_qty()
self.price_unit = price self.tax_ids = self.product_id.taxes_id.filtered(lambda r: not self.company_id or r.company_id == self.company_id)
self.tax_ids = self.product_id.taxes_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') @api.onchange('qty', 'discount', 'price_unit', 'tax_ids')
def _onchange_qty(self): def _onchange_qty(self):
+1 -1
View File
@@ -20,7 +20,7 @@ class ProductTemplate(models.Model):
if self._context.get('categ_id') or self._context.get('default_categ_id'): 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') 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) 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): def _get_default_uom_id(self):
return self.env["product.uom"].search([], limit=1, order='id').id return self.env["product.uom"].search([], limit=1, order='id').id
+1 -1
View File
@@ -28,7 +28,7 @@ class Rating(http.Controller):
'rate_name': rate_names[rate], 'rate': rate '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): def submit_rating(self, token, rate, **kwargs):
rating = request.env['rating.rating'].sudo().search([('access_token', '=', token)]) rating = request.env['rating.rating'].sudo().search([('access_token', '=', token)])
if not rating: if not rating:
+18
View File
@@ -1,6 +1,24 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # 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 models
import report import report
import wizard 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))
+1
View File
@@ -65,6 +65,7 @@ The Dashboard for the Sales Manager will include
'data/sale_demo.xml', 'data/sale_demo.xml',
'data/product_product_demo.xml', 'data/product_product_demo.xml',
], ],
'uninstall_hook': "uninstall_hook",
'css': ['static/src/css/sale.css'], 'css': ['static/src/css/sale.css'],
'installable': True, 'installable': True,
'auto_install': False, 'auto_install': False,
+6 -3
View File
@@ -317,12 +317,15 @@ class Quant(models.Model):
# price update + accounting entries adjustments # price update + accounting entries adjustments
solved_quants._price_update(solving_quant.cost) solved_quants._price_update(solving_quant.cost)
# merge history (and cost?) # merge history (and cost?)
solved_quants.write({ solved_quants.write(solving_quant._prepare_history())
'history_ids': [(4, history_move.id) for history_move in solving_quant.history_ids]
})
solving_quant.with_context(force_unlink=True).unlink() solving_quant.with_context(force_unlink=True).unlink()
solving_quant = remaining_solving_quant 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 @api.multi
def _price_update(self, newprice): def _price_update(self, newprice):
# TDE note: use ACLs instead of sudoing everything # TDE note: use ACLs instead of sudoing everything
+2
View File
@@ -1003,6 +1003,8 @@ class Binary(http.Controller):
elif status != 200 and download: elif status != 200 and download:
return request.not_found() return request.not_found()
height = int(height)
width = int(width)
if content and (width or height): if content and (width or height):
# resize maximum 500*500 # resize maximum 500*500
if width > 500: if width > 500:
+4 -3
View File
@@ -61,7 +61,7 @@ var Apps = Widget.extend({
}, },
destroy: function() { destroy: function() {
$(window).off("message.apps"); $(window).off("message." + this.uniq);
if (this.$ifr) { if (this.$ifr) {
this.$ifr.remove(); this.$ifr.remove();
this.$ifr = null; this.$ifr = null;
@@ -116,10 +116,11 @@ var Apps = Widget.extend({
qs.debug = session.debug; qs.debug = session.debug;
} }
var u = $.param.querystring(client.origin + "/apps/embed/client", qs); 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); 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) { self.on('message:ready', self, function(m) {
var w = this.$ifr[0].contentWindow; var w = this.$ifr[0].contentWindow;
+1 -1
View File
@@ -15,7 +15,7 @@ class IrHttp(models.AbstractModel):
context['editable'] = True context['editable'] = True
if 'edit_translations' in request.httprequest.args and 'edit_translations' not in context: if 'edit_translations' in request.httprequest.args and 'edit_translations' not in context:
context['edit_translations'] = True 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 context['translatable'] = True
request.context = context request.context = context
return super(IrHttp, cls)._dispatch() return super(IrHttp, cls)._dispatch()
+1 -1
View File
@@ -48,7 +48,7 @@ class IrUiView(models.Model):
if value is not None: if value is not None:
# TODO: batch writes? # 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}) Model.browse(int(el.get('data-oe-id'))).with_context(lang=self.get_default_lang_code()).write({field: value})
else: else:
Model.browse(int(el.get('data-oe-id'))).write({field: value}) Model.browse(int(el.get('data-oe-id'))).write({field: value})
+1 -1
View File
@@ -205,7 +205,7 @@ class Http(models.AbstractModel):
path.pop(1) path.pop(1)
request.context = context request.context = context
return cls.reroute('/'.join(path) or '/') 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 context['edit_translations'] = False
if not context.get('tz'): if not context.get('tz'):
context['tz'] = request.session.get('geoip', {}).get('time_zone') context['tz'] = request.session.get('geoip', {}).get('time_zone')
+1
View File
@@ -174,6 +174,7 @@ class WebsiteBlog(http.Controller):
v['blog'] = blog v['blog'] = blog
v['base_url'] = request.env['ir.config_parameter'].sudo().get_param('web.base.url') 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['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')]) r = request.render("website_blog.blog_feed", v, headers=[('Content-Type', 'application/atom+xml')])
return r return r
@@ -557,7 +557,7 @@
<link t-att-href="'%s%s' % (base_url, post.website_url)"/> <link t-att-href="'%s%s' % (base_url, post.website_url)"/>
<id t-esc="'%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> <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'"/> <updated t-esc="post.write_date.replace(' ', 'T') + 'Z'"/>
</entry> </entry>
</feed> </feed>
+4 -10
View File
@@ -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_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'.") 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): def _get_form_writable_fields(self):
""" """
Restriction of "authorized fields" (fields which can be used in the 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 builders and are writable. By default no field is writable by the
form builder. form builder.
""" """
excluded = { included = {
field.name field.name
for field in self.env['ir.model.fields'].sudo().search([ for field in self.env['ir.model.fields'].sudo().search([
('model_id', 'in', self._all_inherited_model_ids()), ('model_id', '=', self.id),
('website_form_blacklisted', '=', True) ('website_form_blacklisted', '=', False)
]) ])
} }
return { return {
k: v for k, v in self.get_authorized_fields(self.model).iteritems() k: v for k, v in self.get_authorized_fields(self.model).iteritems()
if k not in excluded if k in included
} }
@api.model @api.model
+17 -1
View File
@@ -220,13 +220,29 @@ class website_account(website_account):
}) })
return request.render("website_portal_sale.portal_my_invoices", values) 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): def details_form_validate(self, data):
error, error_message = super(website_account, self).details_form_validate(data) error, error_message = super(website_account, self).details_form_validate(data)
# prevent VAT/name change if invoices exist # prevent VAT/name change if invoices exist
partner = request.env['res.users'].browse(request.uid).partner_id 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'])]) invoices = request.env['account.invoice'].sudo().search_count([('partner_id', '=', partner.id), ('state', 'not in', ['draft', 'cancel'])])
if invoices: 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['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.')) 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): if 'name' in data and (data['name'] or False) != (partner.name or False):
@@ -185,7 +185,7 @@
<t t-foreach="invoices" t-as="invoice"> <t t-foreach="invoices" t-as="invoice">
<tr> <tr>
<td class= "cell_overflow"> <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>
<td><span t-field="invoice.date_invoice"/></td> <td><span t-field="invoice.date_invoice"/></td>
<td class='hidden-xs'><span t-field="invoice.date_due"/></td> <td class='hidden-xs'><span t-field="invoice.date_due"/></td>
@@ -85,7 +85,7 @@ class WebsiteSaleDigital(website_account):
# Also check for attachments in the product templates # Also check for attachments in the product templates
elif res_model == 'product.template': 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) template_ids = map(lambda x: P.browse(x).product_tmpl_id.id, purchased_products)
if res_id not in template_ids: if res_id not in template_ids:
return redirect(self.orders_page) return redirect(self.orders_page)
+11
View File
@@ -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
+5
View File
@@ -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 Makes the button ``invisible`` if the record is *not* in one of the
listed states listed states
.. danger::
Using ``states`` in combination with ``attrs`` may lead to
unexpected results as domains are combined with a logical AND.
``context`` ``context``
merged into the view's context when performing the button's Odoo call merged into the view's context when performing the button's Odoo call
``confirm`` ``confirm``
+2 -2
View File
@@ -197,11 +197,11 @@ class IrSequence(models.Model):
year = fields.Date.from_string(date).strftime('%Y') year = fields.Date.from_string(date).strftime('%Y')
date_from = '{}-01-01'.format(year) date_from = '{}-01-01'.format(year)
date_to = '{}-12-31'.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: if date_range:
date_to = datetime.strptime(date_range.date_from, '%Y-%m-%d') + timedelta(days=-1) date_to = datetime.strptime(date_range.date_from, '%Y-%m-%d') + timedelta(days=-1)
date_to = date_to.strftime('%Y-%m-%d') 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: if date_range:
date_from = datetime.strptime(date_range.date_to, '%Y-%m-%d') + timedelta(days=1) date_from = datetime.strptime(date_range.date_to, '%Y-%m-%d') + timedelta(days=1)
date_from = date_from.strftime('%Y-%m-%d') date_from = date_from.strftime('%Y-%m-%d')
+13 -12
View File
@@ -240,18 +240,19 @@ class IrTranslation(models.Model):
''' When changing source term of a translation, change its value in db ''' When changing source term of a translation, change its value in db
for the associated object, and the src field. for the associated object, and the src field.
''' '''
for record in self: self.ensure_one()
if record.type == 'model': if self.type == 'model':
model_name, field_name = record.name.split(',') model_name, field_name = self.name.split(',')
model = self.env[model_name] model = self.env[model_name]
field = model._fields[field_name] field = model._fields[field_name]
if not callable(field.translate): if not callable(field.translate):
# Make a context without language information, because we want # Make a context without language information, because we want
# to write on the value stored in db and not on the one # to write on the value stored in db and not on the one
# associated with the current language. Also not removing lang # associated with the current language. Also not removing lang
# from context trigger an error when lang is different. # from context trigger an error when lang is different.
model.browse(record.res_id).with_context(lang=None).write({field_name: record.source}) model.browse(self.res_id).with_context(lang=None).write({field_name: self.source})
return self.write({'src': self.source}) if self.src != self.source:
self.write({'src': self.source})
def _search_source(self, operator, value): def _search_source(self, operator, value):
''' the source term is stored on 'src' field ''' ''' the source term is stored on 'src' field '''
+5
View File
@@ -526,3 +526,8 @@ class TestGroups(TransactionCase):
# create a cycle and check # create a cycle and check
a.implied_ids = d a.implied_ids = d
self.assertFalse(a._check_m2m_recursion('implied_ids')) 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)
+2 -1
View File
@@ -3814,7 +3814,7 @@ class BaseModel(object):
trans_name, source_id, target_id = get_trans(field, old, new) trans_name, source_id, target_id = get_trans(field, old, new)
domain = [('name', '=', trans_name), ('res_id', '=', source_id)] domain = [('name', '=', trans_name), ('res_id', '=', source_id)]
new_val = new_wo_lang[name] 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 # the new value *without lang* must be the old value without lang
new_wo_lang[name] = old_wo_lang[name] new_wo_lang[name] = old_wo_lang[name]
for vals in Translation.search_read(domain): 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 del vals['module'] # duplicated vals is not linked to any module
vals['res_id'] = target_id vals['res_id'] = target_id
if vals['lang'] == old.env.lang and field.translate is True: 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()) # the value should be the new value (given by copy())
vals['value'] = new_val vals['value'] = new_val
Translation.create(vals) Translation.create(vals)