[MERGE] forward port branch saas-15 up to 10cb34bcac
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.osv import expression
|
||||
from odoo.tools import float_is_zero, pycompat
|
||||
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
|
||||
|
||||
@@ -606,7 +606,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)
|
||||
)"""
|
||||
@@ -630,7 +631,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,
|
||||
@@ -651,9 +652,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 desc, aml.id desc LIMIT 1"
|
||||
self.env.cr.execute(sql_query, params)
|
||||
results = self.env.cr.fetchone()
|
||||
|
||||
@@ -474,7 +474,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
|
||||
|
||||
@@ -685,7 +685,7 @@ class AccountMoveLine(models.Model):
|
||||
company_currency = line.account_id.company_id.currency_id
|
||||
ret_line = {
|
||||
'id': line.id,
|
||||
'name': line.name != '/' and line.move_id.name + ': ' + line.name or line.move_id.name,
|
||||
'name': line.name and line.name != '/' and line.move_id.name + ': ' + line.name or line.move_id.name,
|
||||
'ref': line.move_id.ref or '',
|
||||
# For reconciliation between statement transactions and already registered payments (eg. checks)
|
||||
# NB : we don't use the 'reconciled' field because the line we're selecting is not the one that gets reconciled
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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']"):
|
||||
|
||||
@@ -254,11 +254,13 @@ class Lead(models.Model):
|
||||
self.update(values)
|
||||
|
||||
@api.constrains('user_id')
|
||||
@api.multi
|
||||
def _valid_team(self):
|
||||
if self.user_id:
|
||||
values = self.with_context(team_id=self.team_id.id)._onchange_user_values(self.user_id.id)
|
||||
if values:
|
||||
self.update(values)
|
||||
for lead in self:
|
||||
if lead.user_id:
|
||||
values = lead.with_context(team_id=lead.team_id.id)._onchange_user_values(lead.user_id.id)
|
||||
if values:
|
||||
lead.update(values)
|
||||
|
||||
@api.onchange('state_id')
|
||||
def _onchange_state(self):
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
""",
|
||||
'depends': ['l10n_fr'],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
'auto_install': False,
|
||||
'application': True,
|
||||
'data': [
|
||||
'views/no_cancel.xml',
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -434,15 +434,14 @@ class MassMailing(models.Model):
|
||||
|
||||
def _compute_next_departure(self):
|
||||
cron_next_call = self.env.ref('mass_mailing.ir_cron_mass_mailing_queue').sudo().nextcall
|
||||
str2dt = fields.Datetime.from_string
|
||||
cron_time = str2dt(cron_next_call)
|
||||
for mass_mailing in self:
|
||||
schedule_date = mass_mailing.schedule_date
|
||||
if schedule_date:
|
||||
if datetime.now() > fields.Datetime.from_string(schedule_date):
|
||||
mass_mailing.next_departure = cron_next_call
|
||||
else:
|
||||
mass_mailing.next_departure = schedule_date
|
||||
if mass_mailing.schedule_date:
|
||||
schedule_date = str2dt(mass_mailing.schedule_date)
|
||||
mass_mailing.next_departure = max(schedule_date, cron_time)
|
||||
else:
|
||||
mass_mailing.next_departure = cron_next_call
|
||||
mass_mailing.next_departure = cron_time
|
||||
|
||||
@api.onchange('mass_mailing_campaign_id')
|
||||
def _onchange_mass_mailing_campaign_id(self):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -164,6 +164,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,
|
||||
@@ -859,21 +860,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):
|
||||
@@ -885,8 +879,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):
|
||||
|
||||
@@ -21,7 +21,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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -4,3 +4,21 @@
|
||||
from . import models
|
||||
from . import report
|
||||
from . import wizard
|
||||
|
||||
from functools import partial
|
||||
import odoo
|
||||
from odoo import api, SUPERUSER_ID
|
||||
|
||||
|
||||
|
||||
def uninstall_hook(cr, registry):
|
||||
def update_dashboard_graph_model(dbname):
|
||||
db_registry = odoo.modules.registry.Registry.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))
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1012,6 +1012,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:
|
||||
|
||||
@@ -59,7 +59,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;
|
||||
@@ -114,10 +114,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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -49,7 +49,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})
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<h4 class="media-heading">
|
||||
<a itemprop="url" t-att-class="event.state == 'done' and 'text-success'" t-attf-href="/event/#{ slug(event) }/#{(not event.menu_id) and 'register' or ''}"><span itemprop="name" t-field="event.name"> </span></a>
|
||||
<small t-if="event.is_participating" class="label label-info">Participating</small>
|
||||
<small t-if="not event.address_id" class="label label-info">Online</small>
|
||||
<small t-if="not event.is_online" class="label label-info">Online</small>
|
||||
<small t-if="not event.website_published" class="label label-danger">unpublished</small>
|
||||
</h4>
|
||||
<div>
|
||||
@@ -68,10 +68,12 @@
|
||||
<div>
|
||||
<i class="fa fa-clock-o"></i> <span itemprop="startDate" t-field="event.with_context(tz=event.date_tz).date_begin" t-options='{"hide_seconds":"True"}'> </span> <i>to</i> <span itemprop="endDate" t-field="event.with_context(tz=event.date_tz).date_end" t-options='{"hide_seconds":"True"}'> </span>
|
||||
</div>
|
||||
<div itemprop="location" t-field="event.address_id" t-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["city"]
|
||||
}'/>
|
||||
<t t-if="not event.is_online">
|
||||
<div itemprop="location" t-field="event.address_id" t-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["city"]
|
||||
}'/>
|
||||
</t>
|
||||
<div class="text-muted" t-if="event.event_type_id">
|
||||
<i class="fa fa-tag"></i> <span t-field="event.event_type_id"/>
|
||||
</div>
|
||||
@@ -333,25 +335,27 @@
|
||||
<div class="col-md-4 css_noprint">
|
||||
<div class="clearfix"/>
|
||||
|
||||
<div class="panel panel-default" t-if="event.address_id">
|
||||
<div class="panel-heading">
|
||||
<h4>Where</h4>
|
||||
<t t-if="not event.is_online">
|
||||
<div class="panel panel-default" t-if="event.address_id">
|
||||
<div class="panel-heading">
|
||||
<h4>Where</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<t t-if="event.google_map_img()">
|
||||
<a t-att-href="event.google_map_link()" target="_BLANK">
|
||||
<img t-att-src="event.google_map_img()" width="100%%"/>
|
||||
</a>
|
||||
</t>
|
||||
<t t-else="1">
|
||||
<a t-att-href="event.google_map_link()" target="_BLANK">Visualize on Google Maps</a>
|
||||
</t>
|
||||
<div itemprop="location" class="mt16 mb8" t-field="event.address_id" t-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["name", "address", "phone", "mobile", "fax", "email"]
|
||||
}'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<t t-if="event.google_map_img()">
|
||||
<a t-att-href="event.google_map_link()" target="_BLANK">
|
||||
<img t-att-src="event.google_map_img()" width="100%%"/>
|
||||
</a>
|
||||
</t>
|
||||
<t t-else="1">
|
||||
<a t-att-href="event.google_map_link()" target="_BLANK">Visualize on Google Maps</a>
|
||||
</t>
|
||||
<div itemprop="location" class="mt16 mb8" t-field="event.address_id" t-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["name", "address", "phone", "mobile", "fax", "email"]
|
||||
}'/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
|
||||
@@ -33,10 +33,6 @@ class EventEvent(models.Model):
|
||||
'title': question.title,
|
||||
'sequence': question.sequence,
|
||||
'is_individual': question.is_individual,
|
||||
'answer_ids': [(0, 0, {
|
||||
'name': answer.name,
|
||||
'sequence': answer.sequence,
|
||||
}) for answer in question.answer_ids]
|
||||
})
|
||||
for question in self.event_type_id.question_ids
|
||||
]
|
||||
@@ -81,6 +77,18 @@ class EventQuestion(models.Model):
|
||||
if any(question.event_type_id and question.event_id for question in self):
|
||||
raise UserError(_('Question should belong to either event category or event but not both'))
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
event_id = vals.get('event_id', False)
|
||||
if event_id:
|
||||
event = self.env['event.event'].browse([event_id])
|
||||
if event.event_type_id.use_questions and event.event_type_id.question_ids:
|
||||
vals['answer_ids'] = vals.get('answer_ids', []) + [(0, 0, {
|
||||
'name': answer.name,
|
||||
'sequence': answer.sequence,
|
||||
}) for answer in event.event_type_id.question_ids.filtered(lambda question: question.title == vals.get('title')).mapped('answer_ids')]
|
||||
return super(EventQuestion, self).create(vals)
|
||||
|
||||
|
||||
class EventAnswer(models.Model):
|
||||
_name = 'event.answer'
|
||||
|
||||
@@ -26,12 +26,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
|
||||
@@ -39,16 +33,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
|
||||
|
||||
@@ -218,20 +218,21 @@ class website_account(website_account):
|
||||
})
|
||||
return request.render("website_portal_sale.portal_my_invoices", values)
|
||||
|
||||
@http.route(['/my/invoices/<int:invoice_id>'], type='http', auth="user", website=True)
|
||||
def portal_my_invoices_report(self, invoice_id, **kw):
|
||||
invoice = request.env['account.invoice'].browse(invoice_id)
|
||||
@http.route(['/my/invoices/pdf/<int:invoice_id>'], type='http', auth="user", website=True)
|
||||
def portal_get_invoice(self, invoice_id, **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")
|
||||
|
||||
# print report as sudo, since it require access to taxes, payment term, ... and portal
|
||||
# does not have those access rights.
|
||||
pdf = request.env['report'].sudo().get_pdf([invoice_id], 'account.report_invoice')
|
||||
pdfhttpheaders = [
|
||||
('Content-Type', 'application/pdf'),
|
||||
('Content-Length', len(pdf)),
|
||||
('Content-Type', 'application/pdf'), ('Content-Length', len(pdf)),
|
||||
('Content-Disposition', 'attachment; filename=Invoice.pdf;')
|
||||
]
|
||||
return request.make_response(pdf, headers=pdfhttpheaders)
|
||||
|
||||
@@ -241,7 +242,7 @@ class website_account(website_account):
|
||||
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):
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
<t t-foreach="invoices" t-as="invoice">
|
||||
<tr>
|
||||
<td class= "cell_overflow">
|
||||
<a t-att-href="'/my/invoices/'+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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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``
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -241,18 +241,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 '''
|
||||
|
||||
@@ -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)
|
||||
|
||||
+2
-1
@@ -3813,7 +3813,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):
|
||||
@@ -3822,6 +3822,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)
|
||||
|
||||
Reference in New Issue
Block a user