[MERGE] forward port branch saas-15 up to cb87388d72

This commit is contained in:
Christophe Simonis
2018-03-07 17:42:02 +01:00
24 changed files with 112 additions and 34 deletions
+1 -1
View File
@@ -290,7 +290,7 @@ class AccountMoveLine(models.Model):
if not cr.fetchone(): if not cr.fetchone():
cr.execute('CREATE INDEX account_move_line_partner_id_ref_idx ON account_move_line (partner_id, ref)') cr.execute('CREATE INDEX account_move_line_partner_id_ref_idx ON account_move_line (partner_id, ref)')
@api.depends('debit', 'credit', 'amount_currency', 'currency_id', 'matched_debit_ids', 'matched_credit_ids', 'matched_debit_ids.amount', 'matched_credit_ids.amount', 'account_id.currency_id', 'move_id.state') @api.depends('debit', 'credit', 'amount_currency', 'currency_id', 'matched_debit_ids', 'matched_credit_ids', 'matched_debit_ids.amount', 'matched_credit_ids.amount', 'move_id.state')
def _amount_residual(self): def _amount_residual(self):
""" Computes the residual amount of a move line from a reconciliable account in the company currency and the line's currency. """ Computes the residual amount of a move line from a reconciliable account in the company currency and the line's currency.
This amount will be 0 for fully reconciled lines or lines from a non-reconciliable account, the original line amount This amount will be 0 for fully reconciled lines or lines from a non-reconciliable account, the original line amount
+7 -1
View File
@@ -4,7 +4,7 @@ from datetime import timedelta, datetime
import calendar import calendar
from odoo import fields, models, api, _ from odoo import fields, models, api, _
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError, UserError
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.tools.float_utils import float_round, float_is_zero from odoo.tools.float_utils import float_round, float_is_zero
@@ -136,6 +136,12 @@ Best Regards,'''))
company.reflect_code_prefix_change(company.cash_account_code_prefix, new_cash_code, digits) company.reflect_code_prefix_change(company.cash_account_code_prefix, new_cash_code, digits)
if values.get('accounts_code_digits'): if values.get('accounts_code_digits'):
company.reflect_code_digits_change(digits) company.reflect_code_digits_change(digits)
#forbid the change of currency_id if there are already some accounting entries existing
if 'currency_id' in values and values['currency_id'] != company.currency_id.id:
if self.env['account.move.line'].search([('company_id', '=', company.id)]):
raise UserError(_('You cannot change the currency of the company since some journal items already exist'))
return super(ResCompany, self).write(values) return super(ResCompany, self).write(values)
@api.model @api.model
+3 -1
View File
@@ -17,7 +17,9 @@ class TestPayment(AccountingTestCase):
self.currency_chf_id = self.env.ref("base.CHF").id self.currency_chf_id = self.env.ref("base.CHF").id
self.currency_usd_id = self.env.ref("base.USD").id self.currency_usd_id = self.env.ref("base.USD").id
self.currency_eur_id = self.env.ref("base.EUR").id self.currency_eur_id = self.env.ref("base.EUR").id
self.env.ref('base.main_company').write({'currency_id': self.currency_eur_id})
company = self.env.ref('base.main_company')
self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", [self.currency_eur_id, company.id])
self.product = self.env.ref("product.product_product_4") self.product = self.env.ref("product.product_product_4")
self.payment_method_manual_in = self.env.ref("account.account_payment_method_manual_in") self.payment_method_manual_in = self.env.ref("account.account_payment_method_manual_in")
self.payment_method_manual_out = self.env.ref("account.account_payment_method_manual_out") self.payment_method_manual_out = self.env.ref("account.account_payment_method_manual_out")
+2 -1
View File
@@ -25,7 +25,8 @@ class TestReconciliation(AccountingTestCase):
self.currency_swiss_id = self.env.ref("base.CHF").id self.currency_swiss_id = self.env.ref("base.CHF").id
self.currency_usd_id = self.env.ref("base.USD").id self.currency_usd_id = self.env.ref("base.USD").id
self.currency_euro_id = self.env.ref("base.EUR").id self.currency_euro_id = self.env.ref("base.EUR").id
self.env.ref('base.main_company').write({'currency_id': self.currency_euro_id}) company = self.env.ref('base.main_company')
self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", [self.currency_euro_id, company.id])
self.account_rcv = partner_agrolait.property_account_receivable_id or self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_receivable').id)], limit=1) self.account_rcv = partner_agrolait.property_account_receivable_id or self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_receivable').id)], limit=1)
self.account_rsa = partner_agrolait.property_account_payable_id or self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_payable').id)], limit=1) self.account_rsa = partner_agrolait.property_account_payable_id or self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_payable').id)], limit=1)
self.product = self.env.ref("product.product_product_4") self.product = self.env.ref("product.product_product_4")
+14 -12
View File
@@ -968,6 +968,8 @@ class Lead(models.Model):
'nb_opportunities': 0, 'nb_opportunities': 0,
} }
today = fields.Date.from_string(fields.Date.context_today(self))
opportunities = self.search([('type', '=', 'opportunity'), ('user_id', '=', self._uid)]) opportunities = self.search([('type', '=', 'opportunity'), ('user_id', '=', self._uid)])
for opp in opportunities: for opp in opportunities:
@@ -975,28 +977,28 @@ class Lead(models.Model):
if opp.activity_date_deadline: if opp.activity_date_deadline:
if opp.date_deadline: if opp.date_deadline:
date_deadline = fields.Date.from_string(opp.date_deadline) date_deadline = fields.Date.from_string(opp.date_deadline)
if date_deadline == date.today(): if date_deadline == today:
result['closing']['today'] += 1 result['closing']['today'] += 1
if date.today() <= date_deadline <= date.today() + timedelta(days=7): if today <= date_deadline <= today + timedelta(days=7):
result['closing']['next_7_days'] += 1 result['closing']['next_7_days'] += 1
if date_deadline < date.today() and not opp.date_closed: if date_deadline < today and not opp.date_closed:
result['closing']['overdue'] += 1 result['closing']['overdue'] += 1
# Next activities # Next activities
for activity in opp.activity_ids: for activity in opp.activity_ids:
date_deadline = fields.Date.from_string(activity.date_deadline) date_deadline = fields.Date.from_string(activity.date_deadline)
if date_deadline == date.today(): if date_deadline == today:
result['activity']['today'] += 1 result['activity']['today'] += 1
if date.today() <= date_deadline <= date.today() + timedelta(days=7): if today <= date_deadline <= today + timedelta(days=7):
result['activity']['next_7_days'] += 1 result['activity']['next_7_days'] += 1
if date_deadline < date.today(): if date_deadline < today:
result['activity']['overdue'] += 1 result['activity']['overdue'] += 1
# Won in Opportunities # Won in Opportunities
if opp.date_closed and opp.stage_id.probability == 100: if opp.date_closed and opp.stage_id.probability == 100:
date_closed = fields.Date.from_string(opp.date_closed) date_closed = fields.Date.from_string(opp.date_closed)
if date.today().replace(day=1) <= date_closed <= date.today(): if today.replace(day=1) <= date_closed <= today:
if opp.planned_revenue: if opp.planned_revenue:
result['won']['this_month'] += opp.planned_revenue result['won']['this_month'] += opp.planned_revenue
elif date.today() + relativedelta(months=-1, day=1) <= date_closed < date.today().replace(day=1): elif today + relativedelta(months=-1, day=1) <= date_closed < today.replace(day=1):
if opp.planned_revenue: if opp.planned_revenue:
result['won']['last_month'] += opp.planned_revenue result['won']['last_month'] += opp.planned_revenue
@@ -1021,9 +1023,9 @@ class Lead(models.Model):
for activity in activites_done: for activity in activites_done:
if activity['date']: if activity['date']:
date_act = fields.Date.from_string(activity['date']) date_act = fields.Date.from_string(activity['date'])
if date.today().replace(day=1) <= date_act <= date.today(): if today.replace(day=1) <= date_act <= today:
result['done']['this_month'] += 1 result['done']['this_month'] += 1
elif date.today() + relativedelta(months=-1, day=1) <= date_act < date.today().replace(day=1): elif today + relativedelta(months=-1, day=1) <= date_act < today.replace(day=1):
result['done']['last_month'] += 1 result['done']['last_month'] += 1
# Meetings # Meetings
@@ -1038,9 +1040,9 @@ class Lead(models.Model):
for meeting in meetings: for meeting in meetings:
if meeting['start']: if meeting['start']:
start = datetime.strptime(meeting['start'], tools.DEFAULT_SERVER_DATETIME_FORMAT).date() start = datetime.strptime(meeting['start'], tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
if start == date.today(): if start == today:
result['meeting']['today'] += 1 result['meeting']['today'] += 1
if date.today() <= start <= date.today() + timedelta(days=7): if today <= start <= today + timedelta(days=7):
result['meeting']['next_7_days'] += 1 result['meeting']['next_7_days'] += 1
result['done']['target'] = self.env.user.target_sales_done result['done']['target'] = self.env.user.target_sales_done
+1
View File
@@ -1,6 +1,7 @@
# -*- 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 . import res_config_settings
from . import delivery_carrier from . import delivery_carrier
from . import delivery_grid from . import delivery_grid
from . import product_template from . import product_template
@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
def set_values(self):
super(ResConfigSettings, self).set_values()
rule = self.env.ref('delivery.delivery_carrier_comp_rule', False)
if rule:
rule.write({'active': not bool(self.company_share_product)})
@@ -6,6 +6,7 @@
<field name="model_id" ref="model_delivery_carrier"/> <field name="model_id" ref="model_delivery_carrier"/>
<field name="global" eval="True"/> <field name="global" eval="True"/>
<field name="domain_force"> ['|',('company_id','=',user.company_id.id),('company_id','=',False)]</field> <field name="domain_force"> ['|',('company_id','=',user.company_id.id),('company_id','=',False)]</field>
<field name="active" eval="False"/>
</record> </record>
</data> </data>
</odoo> </odoo>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="delivery_comp_rule" model="ir.rule">
<field name="name">Delivery multi-company</field>
<field name="model_id" ref="model_delivery_carrier"/>
<field name="global" eval="True"/>
<field name="domain_force"> ['|',('product_id.company_id','=',user.company_id.id),('product_id.company_id','=',False)]</field>
<field name="active" eval="False"/>
</record>
</data>
</odoo>
@@ -238,7 +238,7 @@ class ImLivechatChannelRule(models.Model):
""" """
def _match(rules): def _match(rules):
for rule in rules: for rule in rules:
if re.search(rule.regex_url, url): if re.search(rule.regex_url or '', url):
return rule return rule
return False return False
# first, search the country specific rules (the first match is returned) # first, search the country specific rules (the first match is returned)
+1 -2
View File
@@ -1,7 +1,6 @@
# -*- 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 datetime import date
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from odoo import api, fields, models from odoo import api, fields, models
@@ -31,7 +30,7 @@ class CrmLead(models.Model):
@api.model @api.model
def retrieve_sales_dashboard(self): def retrieve_sales_dashboard(self):
res = super(CrmLead, self).retrieve_sales_dashboard() res = super(CrmLead, self).retrieve_sales_dashboard()
date_today = date.today() date_today = fields.Date.from_string(fields.Date.context_today(self))
res['invoiced'] = { res['invoiced'] = {
'this_month': 0, 'this_month': 0,
+1 -1
View File
@@ -237,7 +237,7 @@ class ProcurementGroup(models.Model):
self.sudo()._procure_orderpoint_confirm(use_new_cursor=use_new_cursor, company_id=company_id) self.sudo()._procure_orderpoint_confirm(use_new_cursor=use_new_cursor, company_id=company_id)
# Search all confirmed stock_moves and try to assign them # Search all confirmed stock_moves and try to assign them
confirmed_moves = self.env['stock.move'].search([('state', '=', 'confirmed')], limit=None, order='priority desc, date_expected asc') confirmed_moves = self.env['stock.move'].search([('state', '=', 'confirmed'), ('product_uom_qty', '!=', 0.0)], limit=None, order='priority desc, date_expected asc')
for moves_chunk in split_every(100, confirmed_moves.ids): for moves_chunk in split_every(100, confirmed_moves.ids):
self.env['stock.move'].browse(moves_chunk)._action_assign() self.env['stock.move'].browse(moves_chunk)._action_assign()
if use_new_cursor: if use_new_cursor:
+1
View File
@@ -680,6 +680,7 @@ class SurveyLabel(models.Model):
value = fields.Char('Suggested value', translate=True, required=True) value = fields.Char('Suggested value', translate=True, required=True)
quizz_mark = fields.Float('Score for this choice', help="A positive score indicates a correct choice; a negative or null score indicates a wrong answer") quizz_mark = fields.Float('Score for this choice', help="A positive score indicates a correct choice; a negative or null score indicates a wrong answer")
@api.one
@api.constrains('question_id', 'question_id_2') @api.constrains('question_id', 'question_id_2')
def _check_question_not_empty(self): def _check_question_not_empty(self):
"""Ensure that field question_id XOR field question_id_2 is not null""" """Ensure that field question_id XOR field question_id_2 is not null"""
@@ -126,6 +126,7 @@ var PivotController = AbstractController.extend({
var table = this.model.exportData(); var table = this.model.exportData();
if(table.measure_row.length + 1 > 256) { if(table.measure_row.length + 1 > 256) {
crash_manager.show_message(_t("For Excel compatibility, data cannot be exported if there are more than 256 columns.\n\nTip: try to flip axis, filter further or reduce the number of measures.")); crash_manager.show_message(_t("For Excel compatibility, data cannot be exported if there are more than 256 columns.\n\nTip: try to flip axis, filter further or reduce the number of measures."));
framework.unblockUI();
return; return;
} }
framework.blockUI(); framework.blockUI();
@@ -135,7 +135,7 @@ var PivotModel = AbstractModel.extend({
exportData: function () { exportData: function () {
var measureNbr = this.data.measures.length; var measureNbr = this.data.measures.length;
var headers = this._computeHeaders(); var headers = this._computeHeaders();
var measureRow = measureNbr > 1 ? _.last(headers) : []; var measureRow = measureNbr >= 1 ? _.last(headers) : [];
var rows = this._computeRows(); var rows = this._computeRows();
var i, j, value; var i, j, value;
headers[0].splice(0,1); headers[0].splice(0,1);
@@ -1293,6 +1293,7 @@ var LinkDialog = Dialog.extend({
this.$('input.email-address').val(match = /mailto:(.+)/.exec(href) ? match[1] : ''); this.$('input.email-address').val(match = /mailto:(.+)/.exec(href) ? match[1] : '');
} else { } else {
this.$('input.url').val(href); this.$('input.url').val(href);
this.$('input.window-new').closest("div").show();
} }
} }
this.preview(); this.preview();
+9 -7
View File
@@ -171,7 +171,8 @@ class WebsiteForm(http.Controller):
return data return data
def insert_record(self, request, model, values, custom, meta=None): def insert_record(self, request, model, values, custom, meta=None):
record = request.env[model.model].sudo().with_context(mail_create_nosubscribe=True).create(values) model_name = model.sudo().model
record = request.env[model_name].sudo().with_context(mail_create_nosubscribe=True).create(values)
if custom or meta: if custom or meta:
default_field = model.website_form_default_field_id default_field = model.website_form_default_field_id
@@ -183,13 +184,13 @@ class WebsiteForm(http.Controller):
# If there is a default field configured for this model, use it. # If there is a default field configured for this model, use it.
# If there isn't, put the custom data in a message instead # If there isn't, put the custom data in a message instead
if default_field.name: if default_field.name:
if default_field.ttype == 'html' or model.model == 'mail.mail': if default_field.ttype == 'html' or model_name == 'mail.mail':
custom_content = nl2br(custom_content) custom_content = nl2br(custom_content)
record.update({default_field.name: custom_content}) record.update({default_field.name: custom_content})
else: else:
values = { values = {
'body': nl2br(custom_content), 'body': nl2br(custom_content),
'model': model.model, 'model': model_name,
'message_type': 'comment', 'message_type': 'comment',
'no_auto_thread': False, 'no_auto_thread': False,
'res_id': record.id, 'res_id': record.id,
@@ -201,7 +202,8 @@ class WebsiteForm(http.Controller):
# Link all files attached on the form # Link all files attached on the form
def insert_attachment(self, model, id_record, files): def insert_attachment(self, model, id_record, files):
orphan_attachment_ids = [] orphan_attachment_ids = []
record = model.env[model.model].browse(id_record) model_name = model.sudo().model
record = model.env[model_name].browse(id_record)
authorized_fields = model.sudo()._get_form_writable_fields() authorized_fields = model.sudo()._get_form_writable_fields()
for file in files: for file in files:
custom_field = file.field_name not in authorized_fields custom_field = file.field_name not in authorized_fields
@@ -209,7 +211,7 @@ class WebsiteForm(http.Controller):
'name': file.field_name if custom_field else file.filename, 'name': file.field_name if custom_field else file.filename,
'datas': base64.encodestring(file.read()), 'datas': base64.encodestring(file.read()),
'datas_fname': file.filename, 'datas_fname': file.filename,
'res_model': model.model, 'res_model': model_name,
'res_id': record.id, 'res_id': record.id,
} }
attachment_id = request.env['ir.attachment'].sudo().create(attachment_value) attachment_id = request.env['ir.attachment'].sudo().create(attachment_value)
@@ -221,10 +223,10 @@ class WebsiteForm(http.Controller):
# If some attachments didn't match a field on the model, # If some attachments didn't match a field on the model,
# we create a mail.message to link them to the record # we create a mail.message to link them to the record
if orphan_attachment_ids: if orphan_attachment_ids:
if model.model != 'mail.mail': if model_name != 'mail.mail':
values = { values = {
'body': _('<p>Attached files : </p>'), 'body': _('<p>Attached files : </p>'),
'model': model.model, 'model': model_name,
'message_type': 'comment', 'message_type': 'comment',
'no_auto_thread': False, 'no_auto_thread': False,
'res_id': id_record, 'res_id': id_record,
@@ -35,5 +35,7 @@ class ResCountry(models.Model):
states = res states = res
break break
states |= carrier.state_ids states |= carrier.state_ids
if not states:
states = states.search([('country_id', '=', self.id)])
res = res & states res = res & states
return res return res
+1 -1
View File
@@ -348,7 +348,7 @@ class Slide(models.Model):
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
for record in self: for record in self:
if record.datas and (not record.document_id or record.slide_type in ['document', 'presentation']): if record.datas and (not record.document_id or record.slide_type in ['document', 'presentation']):
record.embed_code = '<iframe src="%s/slides/embed/%s?page=1" allowFullScreen="true" height="%s" width="%s" frameborder="0"></iframe>' % (base_url, record.id, 315, 420) record.embed_code = '<iframe src="%s/slides/embed/%s?page=1" class="o_wslides_iframe_viewer" allowFullScreen="true" height="%s" width="%s" frameborder="0"></iframe>' % (base_url, record.id, 315, 420)
elif record.slide_type == 'video' and record.document_id: elif record.slide_type == 'video' and record.document_id:
if not record.mime_type: if not record.mime_type:
# embed youtube video # embed youtube video
@@ -109,11 +109,12 @@ var page_widgets = {};
}, },
}); });
$('iframe').ready(function() { $('iframe.o_wslides_iframe_viewer').ready(function() {
// TODO : make it work. For now, once the iframe is loaded, the value of #page_count is // TODO : make it work. For now, once the iframe is loaded, the value of #page_count is
// still now set (the pdf is still loading) // still now set (the pdf is still loading)
var max_page = $('iframe').contents().find('#page_count').val(); var $iframe = $(this);
new SlideSocialEmbed($(this), max_page).setElement($('.oe_slide_js_embed_code_widget')); var max_page = $iframe.contents().find('#page_count').val();
new SlideSocialEmbed($iframe, max_page).setElement($('.oe_slide_js_embed_code_widget'));
}); });
+18
View File
@@ -0,0 +1,18 @@
PromptEQUATION, 2017-09-03
PromptEquation/ OdooGap agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Diogo Duarte dduarte@odoogap.com https://github.com/dduarte-odoogap
List of contributors:
Diogo Duarte dduarte@odoogap.com https://github.com/dduarte-odoogap
Diogo Duarte diogocarvalhoduarte@gmail.com https://github.com/dduarte-odoogap
Carlos Fonseca cfonseca@odoogap.com https://github.com/cfonseca-odoogap
Carlos Fonseca carlosmigueldafonseca@gmail.com https://github.com/cpintofonseca
+11
View File
@@ -0,0 +1,11 @@
India, 18-02-2018
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,
Hilar AK hilarak@gmail.com https://github.com/hilarak
+1 -1
View File
@@ -8,4 +8,4 @@ declaration.
Signed, Signed,
Laurent Bélorgey lb@lalieutenante.com https://github.com/lbelorgey Laurent Bélorgey lb@lalieutenante.com https://github.com/lbelorgey
+5 -1
View File
@@ -17,7 +17,11 @@ class AutoVacuum(models.AbstractModel):
for mname in self.env: for mname in self.env:
model = self.env[mname] model = self.env[mname]
if model.is_transient(): if model.is_transient():
model._transient_vacuum(force=True) try:
with self._cr.savepoint():
model._transient_vacuum(force=True)
except Exception as e:
_logger.warning("Failed to clean transient model %s\n%s", model, str(e))
@api.model @api.model
def _gc_user_logs(self): def _gc_user_logs(self):