[MERGE] forward port branch saas-12.2 up to 9e74730a26

This commit is contained in:
Christophe Simonis
2019-08-06 18:48:24 +02:00
79 changed files with 668 additions and 99 deletions
+4 -1
View File
@@ -2012,7 +2012,10 @@ class AccountInvoiceLine(models.Model):
return
if not self.product_id:
fpos = self.invoice_id.fiscal_position_id
default_tax = self.invoice_id.type in ('out_invoice', 'out_refund') and self.invoice_id.company_id.account_sale_tax_id or self.invoice_id.company_id.account_purchase_tax_id
if self.invoice_id.type in ('out_invoice', 'out_refund'):
default_tax = self.invoice_id.company_id.account_sale_tax_id
else:
default_tax = self.invoice_id.company_id.account_purchase_tax_id
self.invoice_line_tax_ids = fpos.map_tax(self.account_id.tax_ids or default_tax, partner=self.partner_id).ids
elif not self.price_unit:
self._set_taxes()
@@ -189,10 +189,9 @@ class ReportInvoiceWithPayment(models.AbstractModel):
@api.model
def _get_report_values(self, docids, data=None):
report = self.env['ir.actions.report']._get_report_from_name('account.report_invoice_with_payments')
return {
'doc_ids': docids,
'doc_model': report.model,
'docs': self.env[report.model].browse(docids),
'doc_model': 'account.invoice',
'docs': self.env['account.invoice'].browse(docids),
'report_type': data.get('report_type') if data else '',
}
@@ -932,7 +932,7 @@ var StatementModel = BasicModel.extend({
'link': prop.id,
'tax_ids': [tax.id],
'amount': tax.amount,
'label': tax.name,
'label': prop.label ? prop.label + " " + tax.name : tax.name,
'date': prop.date,
'account_id': tax.account_id ? [tax.account_id, null] : prop.account_id,
'analytic': tax.analytic,
@@ -794,6 +794,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
*/
_onSelectMoveLine: function (event) {
var $el = $(event.target);
$el.prop('disabled', true);
this._destroyPopover($el);
var moveLineId = $el.closest('.mv_line').data('line-id');
this.trigger_up('add_proposition', {'data': moveLineId});
@@ -1299,14 +1299,14 @@ QUnit.module('account', {
await testUtils.dom.click(widget.$('.create .quick_add button:contains(ATOS)'));
assert.strictEqual(widget.$('.accounting_view tbody .cell_label, .accounting_view tbody .cell_right').text().replace(/[\n\r\s$,]+/g, ' '),
" ATOS Banque 1145.62 ATOS Frais 26.71 Tax 20.00% 229.12 Tax 10.00% include 2.67 ", "should display 4 lines");
" ATOS Banque 1145.62 ATOS Frais 26.71 ATOS Banque Tax 20.00% 229.12 ATOS Frais Tax 10.00% include 2.67 ", "should display 4 lines");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label, .accounting_view tfoot .cell_left').text().replace(/[\n\r\s$,]+/g, ' '),
"Open balance229.12", "should display the 'Open balance' line with value in left column");
await testUtils.fields.editAndTrigger(widget.$('.create .create_amount input'), '100',['input']);
assert.strictEqual(widget.$('.accounting_view tbody').text().replace(/[\n\r\s$,]+/g, ' '),
" 101120 New ATOS Banque 1075.00 101130 New ATOS Frais 90.91 101120 New Tax 20.00% 215.00 101300 New Tax 10.00% include 9.09 ",
" 101120 New ATOS Banque 1075.00 101130 New ATOS Frais 90.91 101120 New ATOS Banque Tax 20.00% 215.00 101300 New ATOS Frais Tax 10.00% include 9.09 ",
"should update the value of the 4 lines (because the line must have 100% of the value)");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label, .accounting_view tfoot .cell_left').text().replace(/[\n\r\s$,]+/g, ' '),
"Open balance215.00", "should change the 'Open balance' line because the 20.00% tax is not an include tax");
@@ -14,7 +14,6 @@
<field name="invoice_ids" invisible="1"/>
<field name="email_from" invisible="1" />
<field name="mail_server_id" invisible="1"/>
<field name="reply_to" invisible="1"/>
<div name="option_print">
<field name="is_print" />
<b><label for="is_print"/></b>
@@ -73,8 +73,9 @@ class AccountInvoiceLine(models.Model):
fields.Date.today(),
company_id=self.company_id.id
)
self.account_analytic_id = rec.analytic_id.id
self.analytic_tag_ids = rec.analytic_tag_ids.ids
if rec:
self.account_analytic_id = rec.analytic_id.id
self.analytic_tag_ids = rec.analytic_tag_ids.ids
return res
def _set_additional_fields(self, invoice):
@@ -347,10 +347,13 @@ class AccountVoucher(models.Model):
}
if company_currency != current_currency:
ctx = {}
sign = temp['credit'] and -1 or 1
amount_currency = company_cur._convert(tax_vals['amount'], current_cur, line.company_id,
self.account_date or fields.Date.today(), round=True)
if self.account_date:
ctx['date'] = self.account_date
temp['currency_id'] = current_currency
temp['amount_currency'] = company_cur._convert(tax_vals['amount'], current_cur, line.company_id, self.account_date or fields.Date.today(), round=True)
temp['amount_currency'] = sign * abs(amount_currency)
self.env['account.move.line'].create(temp)
# When global rounding is activated, we must wait until all tax lines are computed to
+2 -2
View File
@@ -92,7 +92,7 @@
<field name="zip" placeholder="ZIP" class="o_address_zip"/>
<field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/>
</div>
<field name="website" widget="url" placeholder="e.g. www.odoo.com"/>
<field name="website" widget="url" placeholder="e.g. https://www.odoo.com"/>
</group>
<group>
<label for="contact_name"/>
@@ -604,7 +604,7 @@
<field name="zip" placeholder="ZIP" class="o_address_zip"/>
<field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/>
</div>
<field name="website" widget="url" placeholder="e.g. www.odoo.com"/>
<field name="website" widget="url" placeholder="e.g. https://www.odoo.com"/>
</group>
<group class="mt48">
+1 -1
View File
@@ -6,7 +6,7 @@
<div class="col-lg-6 offset-lg-3">
<h3>Digest Subscriptions</h3>
<div class="alert alert-success text-center" role="status">
<p>You have been successfully unsubscribed from <string t-field="digest.name"/></p>
<p>You have been successfully unsubscribed from <strong t-field="digest.name"/></p>
</div>
</div>
</div>
@@ -28,10 +28,12 @@ class ResConfigSettings(models.TransientModel):
params = self.env['ir.config_parameter'].sudo()
authorization_code_before = params.get_param('google_drive_authorization_code')
authorization_code = self.google_drive_authorization_code
refresh_token = False
if authorization_code and authorization_code != authorization_code_before:
refresh_token = self.env['google.service'].generate_refresh_token('drive', authorization_code)
params.set_param('google_drive_refresh_token', refresh_token)
if authorization_code != authorization_code_before:
refresh_token = (
self.env['google.service'].generate_refresh_token('drive', authorization_code)
if authorization_code else False
)
params.set_param('google_drive_refresh_token', refresh_token)
@api.multi
def action_setup_token(self):
+7
View File
@@ -47,5 +47,12 @@
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<record id="hr_employee_comp_rule" model="ir.rule">
<field name="name">Employee multi company rule</field>
<field name="model_id" ref="model_hr_employee"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
</data>
</odoo>
+2
View File
@@ -554,6 +554,8 @@ class HolidaysRequest(models.Model):
if not values.get('department_id'):
values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id})
holiday = super(HolidaysRequest, self.with_context(mail_create_nosubscribe=True)).create(values)
if self._context.get('import_file'):
holiday._onchange_leave_dates()
if not self._context.get('leave_fast_create'):
holiday.add_follower(employee_id)
if holiday.validation_type == 'hr':
@@ -367,7 +367,8 @@ class HolidaysAllocation(models.Model):
holiday.message_subscribe(partner_ids=(holiday.employee_id.parent_id.user_id.partner_id | holiday.employee_id.leave_manager_id.partner_id).ids)
if 'employee_id' in values:
holiday._onchange_employee()
holiday.activity_update()
if not self._context.get('import_file'):
holiday.activity_update()
return holiday
@api.multi
@@ -105,5 +105,5 @@
</record>
<menuitem id="hr_timesheet_config_settings_menu_action" name="Settings" parent="hr_timesheet_menu_configuration"
action="hr_timesheet_config_settings_action" sequence="0"/>
action="hr_timesheet_config_settings_action" sequence="0" groups="base.group_system"/>
</odoo>
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo import api, fields, models, tools
class TimesheetAttendance(models.Model):
@@ -17,7 +17,7 @@ class TimesheetAttendance(models.Model):
@api.model_cr
def init(self):
self._cr.execute("DROP VIEW IF EXISTS hr_timesheet_attendance_report")
tools.drop_view_if_exists(self.env.cr, self._table)
self._cr.execute("""CREATE OR REPLACE VIEW %s AS (
SELECT
max(id) AS id,
@@ -276,7 +276,7 @@
"account_common_523","Proveedores de inmovilizado a corto plazo","523","account.data_account_type_payable","l10n_es.account_chart_template_common","l10n_es.account_group_523","True"
"account_common_524","Acreedores por arrendamiento financiero a corto plazo","524","account.data_account_type_current_liabilities","l10n_es.account_chart_template_common","l10n_es.account_group_524","False"
"account_common_525","Efectos a pagar a corto plazo","525","account.data_account_type_current_liabilities","l10n_es.account_chart_template_common","l10n_es.account_group_525","False"
"account_common_526","Dividendo activo a pagar","526","account.data_account_type_current_liabilities","l10n_es.account_chart_template_common","l10n_es.account_group_526","False"
"account_common_526","Dividendo activo a pagar","526","account.data_account_type_payable","l10n_es.account_chart_template_common","l10n_es.account_group_526","True"
"account_common_527","Intereses a corto plazo de deudas con entidades de crédito","527","account.data_account_type_current_liabilities","l10n_es.account_chart_template_common","l10n_es.account_group_527","False"
"account_common_528","Intereses a corto plazo de deudas","528","account.data_account_type_current_liabilities","l10n_es.account_chart_template_common","l10n_es.account_group_528","False"
"account_common_5290","Provisión a corto plazo por retribuciones al personal","5290","account.data_account_type_current_liabilities","l10n_es.account_chart_template_common","l10n_es.account_group_5290","True"
1 id name code user_type_id/id chart_template_id/id group_id/id reconcile
276 account_common_523 Proveedores de inmovilizado a corto plazo 523 account.data_account_type_payable l10n_es.account_chart_template_common l10n_es.account_group_523 True
277 account_common_524 Acreedores por arrendamiento financiero a corto plazo 524 account.data_account_type_current_liabilities l10n_es.account_chart_template_common l10n_es.account_group_524 False
278 account_common_525 Efectos a pagar a corto plazo 525 account.data_account_type_current_liabilities l10n_es.account_chart_template_common l10n_es.account_group_525 False
279 account_common_526 Dividendo activo a pagar 526 account.data_account_type_current_liabilities account.data_account_type_payable l10n_es.account_chart_template_common l10n_es.account_group_526 False True
280 account_common_527 Intereses a corto plazo de deudas con entidades de crédito 527 account.data_account_type_current_liabilities l10n_es.account_chart_template_common l10n_es.account_group_527 False
281 account_common_528 Intereses a corto plazo de deudas 528 account.data_account_type_current_liabilities l10n_es.account_chart_template_common l10n_es.account_group_528 False
282 account_common_5290 Provisión a corto plazo por retribuciones al personal 5290 account.data_account_type_current_liabilities l10n_es.account_chart_template_common l10n_es.account_group_5290 True
+14
View File
@@ -445,6 +445,7 @@
<field name="description">P_IVA21_SP_IN</field>
<field name="type_tax_use">purchase</field>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_10_11'), ref('mod_303_14_15_purchase'), ref('mod_303_36_37'), ref('mod_303_40_41')])]"/>
<field name="amount_type">percent</field>
<field name="amount" eval="21"/>
<field name="tax_group_id" ref="tax_group_iva_21"/>
@@ -550,6 +551,7 @@
<field name="type_tax_use">purchase</field>
<field name="amount_type">percent</field>
<field name="amount" eval="21"/>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_38_39'), ref('mod_303_40_41'), ref('mod_303_10_11'), ref('mod_303_14_15_purchase')])]"/>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
<field name="tax_group_id" ref="tax_group_iva_21"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
@@ -716,6 +718,7 @@
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_12_13'), ref('mod_303_14_15_purchase'), ref('mod_303_28_29'), ref('mod_303_40_41')])]"/>
<field name="name">IVA 4% Adquisición de servicios extracomunitarios</field>
<field name="tax_group_id" ref="tax_group_iva_4"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
@@ -767,6 +770,7 @@
<field name="description">P_IVA10_SP_EX</field>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
<field name="amount_type">percent</field>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_12_13'), ref('mod_303_14_15_purchase'), ref('mod_303_28_29'), ref('mod_303_40_41')])]"/>
<field name="type_tax_use">purchase</field>
<field name="name">IVA 10% Adquisición de servicios extracomunitarios</field>
<field name="tax_group_id" ref="tax_group_iva_10"/>
@@ -820,6 +824,7 @@
<field name="type_tax_use">purchase</field>
<field name="amount" eval="21"/>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_12_13'), ref('mod_303_14_15_purchase'), ref('mod_303_28_29'), ref('mod_303_40_41')])]"/>
<field name="amount_type">percent</field>
<field name="tax_group_id" ref="tax_group_iva_21"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
@@ -869,6 +874,7 @@
<field name="amount" eval="4"/>
<field name="description">P_IVA4_IC_BC</field>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_36_37'), ref('mod_303_40_41'), ref('mod_303_10_11'), ref('mod_303_14_15_purchase')])]"/>
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<field name="name">IVA 4% Adquisición Intracomunitario. Bienes corrientes</field>
@@ -924,6 +930,7 @@
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<field name="name">IVA 4% Adquisición Intracomunitario. Bienes de inversión</field>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_38_39'), ref('mod_303_40_41'), ref('mod_303_10_11'), ref('mod_303_14_15_purchase')])]"/>
<field name="tax_group_id" ref="tax_group_iva_4"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
@@ -972,6 +979,7 @@
<field name="amount" eval="10"/>
<field name="description">P_IVA10_IC_BC</field>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_36_37'), ref('mod_303_40_41'), ref('mod_303_10_11'), ref('mod_303_14_15_purchase')])]"/>
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<field name="name">IVA 10% Adquisición Intracomunitario. Bienes corrientes</field>
@@ -1026,6 +1034,7 @@
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<field name="name">IVA 10% Adquisición Intracomunitario. Bienes de inversión</field>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_38_39'), ref('mod_303_40_41'), ref('mod_303_10_11'), ref('mod_303_14_15_purchase')])]"/>
<field name="tax_group_id" ref="tax_group_iva_10"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
@@ -3068,6 +3077,7 @@
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<field name="name">IVA 10% Adquisición de servicios intracomunitarios</field>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_10_11'), ref('mod_303_14_15_purchase'), ref('mod_303_36_37'), ref('mod_303_40_41')])]"/>
<field name="tax_group_id" ref="tax_group_iva_10"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
@@ -3119,6 +3129,7 @@
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<field name="name">IVA 4% Adquisición de servicios intracomunitarios</field>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_10_11'), ref('mod_303_14_15_purchase'), ref('mod_303_36_37'), ref('mod_303_40_41')])]"/>
<field name="tax_group_id" ref="tax_group_iva_4"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
(0,0, {
@@ -3392,6 +3403,7 @@
<field name="description">P_IVA4_ISP</field>
<field name="name">IVA 4% Compra con Inversión del Sujeto Pasivo Nacional</field>
<field name="type_tax_use">purchase</field>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_28_29'), ref('mod_303_40_41'), ref('mod_303_12_13'), ref('mod_303_14_15_purchase')])]"/>
<field name="amount_type">percent</field>
<field name="amount" eval="4"/>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
@@ -3443,6 +3455,7 @@
<field name="description">P_IVA10_ISP</field>
<field name="name">IVA 10% Compra con Inversión del Sujeto Pasivo Nacional</field>
<field name="type_tax_use">purchase</field>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_28_29'), ref('mod_303_40_41'), ref('mod_303_12_13'), ref('mod_303_14_15_purchase')])]"/>
<field name="amount_type">percent</field>
<field name="amount" eval="10"/>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
@@ -3496,6 +3509,7 @@
<field name="type_tax_use">purchase</field>
<field name="amount_type">percent</field>
<field name="amount" eval="21"/>
<field name="tag_ids" eval="[(6, False, [ref('mod_303_28_29'), ref('mod_303_40_41'), ref('mod_303_12_13'), ref('mod_303_14_15_purchase')])]"/>
<field name="chart_template_id" ref="l10n_es.account_chart_template_common"/>
<field name="tax_group_id" ref="tax_group_iva_21"/>
<field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
+1
View File
@@ -5,6 +5,7 @@
"name" : "Mongolia - Accounting",
"version" : "1.0",
'category': 'Localization',
"author" : "BumanIT LLC, Odoo S.A",
"description": """
This is the module to manage the accounting chart for Mongolia.
===============================================================
@@ -8,7 +8,7 @@
"cuenta115_02","Materia prima y materiales","115.02.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_115_02","False"
"cuenta115_03","Producción en proceso","115.03.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_115_03","False"
"cuenta115_04","Productos terminados","115.04.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_115_04","False"
"cuenta115_05","Mercancías en tránsito","115.05.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_115_05","False"
"cuenta115_05","Mercancías en tránsito","115.05.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_115_05","True"
"cuenta115_06","Mercancías en poder de terceros","115.06.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_115_06","False"
"cuenta118_01","IVA acreditable pagado","118.01.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_118_01","False"
"cuenta119_01","IVA pendiente de pago","119.01.01","account.data_account_type_current_assets","l10n_mx.mx_coa","l10n_mx.account_tag_119_01","False"
1 id name code user_type_id/id chart_template_id/id tag_ids/id reconcile
8 cuenta115_02 Materia prima y materiales 115.02.01 account.data_account_type_current_assets l10n_mx.mx_coa l10n_mx.account_tag_115_02 False
9 cuenta115_03 Producción en proceso 115.03.01 account.data_account_type_current_assets l10n_mx.mx_coa l10n_mx.account_tag_115_03 False
10 cuenta115_04 Productos terminados 115.04.01 account.data_account_type_current_assets l10n_mx.mx_coa l10n_mx.account_tag_115_04 False
11 cuenta115_05 Mercancías en tránsito 115.05.01 account.data_account_type_current_assets l10n_mx.mx_coa l10n_mx.account_tag_115_05 False True
12 cuenta115_06 Mercancías en poder de terceros 115.06.01 account.data_account_type_current_assets l10n_mx.mx_coa l10n_mx.account_tag_115_06 False
13 cuenta118_01 IVA acreditable pagado 118.01.01 account.data_account_type_current_assets l10n_mx.mx_coa l10n_mx.account_tag_118_01 False
14 cuenta119_01 IVA pendiente de pago 119.01.01 account.data_account_type_current_assets l10n_mx.mx_coa l10n_mx.account_tag_119_01 False
+20
View File
@@ -40,6 +40,26 @@
<field name="groups" eval="[(4,ref('lunch.group_lunch_user'))]"/>
</record>
<record id="lunch_order_rule_write" model="ir.rule">
<field name="name">lunch.order: Don't change confirmed order</field>
<field name="model_id" ref="lunch.model_lunch_order"/>
<field name="domain_force">[('state', '!=', 'confirmed'), ('user_id', '=', user.id)]</field>
<field name="perm_read" eval="0"/>
<field name="perm_create" eval="0"/>
<field name="perm_unlink" eval="0"/>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="lunch_order_rule_write_manager" model="ir.rule">
<field name="name">manager can do whatever</field>
<field name="model_id" ref="lunch.model_lunch_order"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="perm_read" eval="0"/>
<field name="perm_create" eval="0"/>
<field name="perm_unlink" eval="0"/>
<field name="groups" eval="[(4, ref('lunch.group_lunch_manager'))]"/>
</record>
<data noupdate="1">
<record id="base.default_user" model="res.users">
<field name="groups_id" eval="[(4,ref('lunch.group_lunch_manager'))]"/>
+11 -2
View File
@@ -313,6 +313,12 @@ class MailActivity(models.Model):
@api.model
def create(self, values):
activity = super(MailActivity, self).create(values)
need_sudo = False
try: # in multicompany, reading the partner might break
partner_id = activity.user_id.partner_id.id
except exceptions.AccessError:
need_sudo = True
partner_id = activity.user_id.sudo().partner_id.id
# send a notification to assigned user; in case of manually done activity also check
# target has rights on document otherwise we prevent its creation. Automated activities
@@ -321,9 +327,12 @@ class MailActivity(models.Model):
if not activity.automated:
activity._check_access_assignation()
if not self.env.context.get('mail_activity_quick_update', False):
activity.action_notify()
if need_sudo:
activity.sudo().action_notify()
else:
activity.action_notify()
self.env[activity.res_model].browse(activity.res_id).message_subscribe(partner_ids=[activity.user_id.partner_id.id])
self.env[activity.res_model].browse(activity.res_id).message_subscribe(partner_ids=[partner_id])
if activity.date_deadline <= fields.Date.today():
self.env['bus.bus'].sendone(
(self._cr.dbname, 'res.partner', activity.user_id.partner_id.id),
+1 -1
View File
@@ -431,7 +431,7 @@ class MailThread(models.AbstractModel):
# ------------------------------------------------------
def with_lang(self):
if 'lang' not in self._context:
if not self._context.get("lang"):
return self.with_context(lang=self.env.user.lang)
return self
+1
View File
@@ -73,6 +73,7 @@ var data = {
"&lt;3": "❤️",
":heart": "❤️", // alt.
"</3": "💔",
"&lt;/3": "💔",
":heart_eyes": "😍",
":turban": "👳",
":+1": "👍",
@@ -506,7 +506,7 @@ MailManager.include({
}
this._removeChannel(channel);
this._mailBus.trigger('unsubscribe_from_channel', data.id);
this.do_notify(_("Unsubscribed"), message);
this.do_notify(_t("Unsubscribed"), message);
}
},
/**
@@ -227,6 +227,8 @@
.o_mail_emoji_container {
@include o-position-absolute(auto, 0, 100%, 0);
border-bottom-width: 1px;
max-height: calc(100vh - 100%);
overflow-y: auto;
}
}
.o_composer_input {
+1 -1
View File
@@ -145,7 +145,7 @@
</template>
<template id="message_origin_link">
<p>This <t t-esc="self._description.lower()"/> has been <span t-if="edit">modified</span><span t-if="not edit">created</span> from:
<p>This <t t-esc="self.env['ir.model']._get(self._name).name.lower()"/> has been <span t-if="edit">modified</span><span t-if="not edit">created</span> from:
<t t-foreach="origin" t-as="o">
<a href="#" t-att-data-oe-model="o._name" t-att-data-oe-id="o.id"> <t t-esc="o.display_name"/></a><span t-if="origin.ids[-1:] != o.ids">, </span>
</t>
@@ -306,7 +306,6 @@ class MailComposer(models.TransientModel):
'attachment_ids': [attach.id for attach in self.attachment_ids],
'author_id': self.author_id.id,
'email_from': self.email_from,
'reply_to': self.reply_to,
'record_name': self.record_name,
'no_auto_thread': self.no_auto_thread,
'mail_server_id': self.mail_server_id.id,
@@ -1,3 +1,32 @@
// TODO remove in master
// Themes scss have to use bg-variant and text-emphasis-variant mixins of BS4
// to properly work. That use was introduced as a fix in 12.0... but the BS4
// mixins are unavailable in the mailing assets. As a stable fix cannot
// introduce static code which requires an XML update to not crash, the mixins
// were duplicated here instead.
@mixin bg-variant($parent, $color) {
#{$parent} {
background-color: $color !important;
}
a#{$parent},
button#{$parent} {
&:hover, &:focus {
background-color: darken($color, 10%) !important;
}
}
}
@mixin text-emphasis-variant($parent, $color) {
#{$parent} {
color: $color !important;
}
a#{$parent} {
&:hover, &:focus {
color: darken($color, 10%) !important;
}
}
}
// ============================
// Mass Mailing "Theme Basic"
// ============================
@@ -24,19 +24,10 @@ $o-mm-def-btn-bg : $o-mm-def-color-gamma;
$o-mm-def-btn-text : $o-mm-def-color-beta;
// ===== Colors =====
.bg-beta {
background-color: $o-mm-def-color-zeta;
color: $o-mm-def-color-beta;
}
.text-beta {
color: $o-mm-def-color-zeta;
}
.bg-delta {
background-color: $o-mm-def-color-eta;
}
.bg-gray-lighter {
background-color: $o-mm-def-color-alpha;
}
@include bg-variant(".bg-beta", $o-mm-def-color-zeta);
@include text-emphasis-variant(".text-beta", $o-mm-def-color-zeta);
@include bg-variant(".bg-delta", $o-mm-def-color-eta);
@include bg-variant(".bg-gray-lighter", $o-mm-def-color-alpha);
td:not([align]) {
// Default browser style but needed so that alignment works on some mail
+1 -1
View File
@@ -205,7 +205,7 @@ class MrpProduction(models.Model):
def _compute_picking_ids(self):
for order in self:
order.picking_ids = self.env['stock.picking'].search([
('group_id', '=', order.procurement_group_id.id),
('group_id', '=', order.procurement_group_id.id), ('group_id', '!=', False),
])
order.delivery_count = len(order.picking_ids)
+2 -1
View File
@@ -62,7 +62,8 @@ class ChangeProductionQty(models.TransientModel):
continue
move = production.move_raw_ids.filtered(lambda x: x.bom_line_id.id == line.id and x.state not in ('done', 'cancel'))
if move:
old_qty = move[0].product_uom_qty
move = move[0]
old_qty = move.product_uom_qty
else:
old_qty = 0
iterate_key = production._get_document_iterate_key(move)
@@ -95,7 +95,7 @@ class ResPartner(models.Model):
})
try:
return jsonrpc(url=url, params=params, timeout=timeout), False
except (ConnectionError, HTTPError, exceptions.AccessError) as exception:
except (ConnectionError, HTTPError, exceptions.AccessError, exceptions.UserError) as exception:
_logger.error('Autocomplete API error: %s' % str(exception))
return False, str(exception)
except InsufficientCreditError as exception:
+13 -1
View File
@@ -335,7 +335,7 @@ class PosOrder(models.Model):
res.append({'data_type': 'tax', 'values': data})
return res
def _create_account_move_line(self, session=None):
def _prepare_account_move_and_lines(self, session=None):
# Tricky, via the workflow, we only have one id in the ids variable
"""Create a account move line of order grouped by products or not."""
@@ -484,6 +484,17 @@ class PosOrder(models.Model):
if self and order.company_id.anglo_saxon_accounting:
add_anglosaxon_lines(grouped_data)
return {
'grouped_data': grouped_data,
'move': move,
}
def _create_account_move_line(self, session=None):
vals = self._prepare_account_move_and_lines(session)
grouped_data = vals['grouped_data']
move = vals['move']
all_lines = []
for group_key, group_data in grouped_data.items():
for value in group_data:
@@ -1162,6 +1173,7 @@ class PosOrderLine(models.Model):
class PosOrderLineLot(models.Model):
_name = "pos.pack.operation.lot"
_description = "Specify product lot/serial number in pos order line"
_rec_name = "lot_name"
pos_order_line_id = fields.Many2one('pos.order.line')
order_id = fields.Many2one('pos.order', related="pos_order_line_id.order_id", readonly=False)
+30 -3
View File
@@ -2619,9 +2619,36 @@ exports.Order = Backbone.Model.extend({
}), 0), this.pos.currency.rounding);
},
get_total_tax: function() {
return round_pr(this.orderlines.reduce((function(sum, orderLine) {
return sum + orderLine.get_tax();
}), 0), this.pos.currency.rounding);
if (this.pos.company.tax_calculation_rounding_method === "round_globally") {
// As always, we need:
// 1. For each tax, sum their amount across all order lines
// 2. Round that result
// 3. Sum all those rounded amounts
var groupTaxes = {};
this.orderlines.each(function (line) {
var taxDetails = line.get_tax_details();
var taxIds = Object.keys(taxDetails);
for (var t = 0; t<taxIds.length; t++) {
var taxId = taxIds[t];
if (!(taxId in groupTaxes)) {
groupTaxes[taxId] = 0;
}
groupTaxes[taxId] += taxDetails[taxId];
}
});
var sum = 0;
var taxIds = Object.keys(groupTaxes);
for (var j = 0; j<taxIds.length; j++) {
var taxAmount = groupTaxes[taxIds[j]];
sum += round_pr(taxAmount, this.pos.currency.rounding);
}
return sum;
} else {
return round_pr(this.orderlines.reduce((function(sum, orderLine) {
return sum + orderLine.get_tax();
}), 0), this.pos.currency.rounding);
}
},
get_total_paid: function() {
return round_pr(this.paymentlines.reduce((function(sum, paymentLine) {
@@ -55,6 +55,7 @@
<field name="price_subtotal_incl" invisible="1" widget="monetary" force_save="1"/>
<field name="tax_ids_after_fiscal_position" widget="many2many_tags" string="Taxes"/>
<field name="tax_ids" widget="many2many_tags" invisible="1"/>
<field name="pack_lot_ids" widget="many2many_tags" groups="stock.group_production_lot"/>
<field name="notice"/>
</group>
</form>
+5
View File
@@ -20,6 +20,11 @@ class MailMessage(models.Model):
message_values = self.read(fields_list)
message_tree = dict((m.id, m) for m in self.sudo())
self._message_read_dict_postprocess(message_values, message_tree)
IrAttachmentSudo = self.env['ir.attachment'].sudo()
for message in message_values:
for attachment in message.get('attachment_ids', []):
if not attachment.get('access_token'):
attachment['access_token'] = IrAttachmentSudo.browse(attachment['id']).generate_access_token()[0]
return message_values
@api.model
@@ -72,7 +72,7 @@
<div class="o_portal_chatter_attachments">
<div class="col-lg-2 col-md-3 text-center" t-foreach='message.attachment_ids' t-as='attachment'>
<a t-attf-href="/web/content/#{attachment.id}?download=true" target="_blank">
<a t-attf-href="/web/content/#{attachment.id}?download=true&amp;access_token=#{attachment.access_token}" target="_blank">
<div class='oe_attachment_embedded o_image' t-att-title="attachment.name" t-att-data-mimetype="attachment.mimetype" t-attf-data-src="/web/image/#{attachment.id}/100x80"/>
<div class='oe_attachment_name'><t t-raw='attachment.name'/></div>
</a>
+1 -1
View File
@@ -399,7 +399,7 @@
<input type="text" name="city" t-attf-class="form-control #{error.get('city') and 'is-invalid' or ''}" t-att-value="city or partner.city" />
</div>
<div t-attf-class="form-group #{error.get('zip') and 'o_has_error' or ''} col-xl-6">
<label class="col-form-label" for="zipcode">Zip / Postal Code</label>
<label class="col-form-label label-optional" for="zipcode">Zip / Postal Code</label>
<input type="text" name="zipcode" t-attf-class="form-control #{error.get('zip') and 'is-invalid' or ''}" t-att-value="zipcode or partner.zip" />
</div>
<div t-attf-class="form-group #{error.get('country_id') and 'o_has_error' or ''} col-xl-6">
-1
View File
@@ -537,7 +537,6 @@
<field name="priority"/>
<field name="stage_id" options='{"group_by_tooltip": {"legend_priority": "Use of stars"}}'/>
<field name="user_id"/>
<field name="user_email"/>
<field name="sequence"/>
<field name="date_deadline"/>
<field name="message_needaction_counter"/>
+1 -1
View File
@@ -270,7 +270,7 @@ class StockRule(models.Model):
'partner_id': partner.id,
'picking_type_id': self.picking_type_id.id,
'company_id': company_id.id,
'currency_id': partner.with_context(force_company=company_id.id).property_purchase_currency_id.id or self.env.user.company_id.currency_id.id,
'currency_id': partner.with_context(force_company=company_id.id).property_purchase_currency_id.id or company_id.currency_id.id,
'dest_address_id': values.get('partner_id', False),
'origin': ', '.join(origins),
'payment_term_id': partner.with_context(force_company=company_id.id).property_supplier_payment_term_id.id,
+2 -1
View File
@@ -1031,7 +1031,8 @@ class SaleOrderLine(models.Model):
for line in self:
fpos = line.order_id.fiscal_position_id or line.order_id.partner_id.property_account_position_id
# If company_id is set, always filter taxes by the company
taxes = line.product_id.taxes_id.filtered(lambda r: not line.company_id or r.company_id == line.company_id)
line_company_id = line.company_id or line.order_id.company_id
taxes = line.product_id.taxes_id.filtered(lambda r: not line_company_id or r.company_id == line_company_id)
line.tax_id = fpos.map_tax(taxes, line.product_id, line.order_id.partner_shipping_id) if fpos else taxes
@api.model
+53
View File
@@ -239,6 +239,59 @@ class TestSaleOrder(TestCommonSaleNoChart):
self.sale_order.amount_untaxed + self.sale_order.amount_tax,
'Taxes should be applied')
def test_so_create_multicompany(self):
# Preparing test Data
user_demo = self.env.ref('base.user_demo')
company_1 = self.env.ref('base.main_company')
company_2 = self.env['res.company'].create({
'name': 'company 2',
'parent_id': company_1.id,
})
user_demo.write({
'groups_id': [(4, self.env.ref('sales_team.group_sale_manager').id, False)],
'company_ids': [(6, False, [company_1.id])],
'company_id': company_1.id,
})
so_partner = self.env.ref('base.res_partner_2')
so_partner.write({
'property_account_position_id': False,
})
tax_company_1 = self.env['account.tax'].create({
'name': 'T1',
'amount': 90,
'company_id': company_1.id,
})
tax_company_2 = self.env['account.tax'].create({
'name': 'T2',
'amount': 90,
'company_id': company_2.id,
})
product_shared = self.env['product.template'].create({
'name': 'shared product',
'taxes_id': [(6, False, [tax_company_1.id, tax_company_2.id])],
})
# Use case
so_1 = self.env['sale.order'].sudo(user_demo.id).create({
'partner_id': so_partner.id,
'company_id': company_1.id,
})
so_1.invalidate_cache()
# This is what is done when importing the csv lines (on sale.order):
# id,order_line/product_id
# __export__.sale_order_37_1bb960ba,Product name
so_1.write({
'order_line': [(0, False, {'product_id': product_shared.product_variant_id.id, 'order_id': so_1.id})],
})
self.assertEqual(set(so_1.order_line.tax_id.ids), set([tax_company_1.id]),
'Only taxes from the right company are put by default')
def test_reconciliation_with_so(self):
# create SO
so = self.env['sale.order'].create({
+1 -2
View File
@@ -464,12 +464,11 @@
<div
t-if="line.discount &gt;= 0"
t-field="line.price_unit"
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
t-att-style="line.discount and 'text-decoration: line-through' or None"
t-att-class="(line.discount and 'text-danger' or '') + ' text-right'"
/>
<div t-if="line.discount">
<t t-esc="(1-line.discount / 100.0) * line.price_unit" t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
<t t-esc="(1-line.discount / 100.0) * line.price_unit" t-options='{"widget": "float", "decimal_precision": "Product Price"}'/>
</div>
</td>
<td t-if="display_discount" t-attf-class="text-right {{ 'd-none d-sm-table-cell' if report_type == 'html' else '' }}">
+2 -2
View File
@@ -114,8 +114,8 @@ class SaleOrderLine(models.Model):
decreased_values = {}
if 'product_uom_qty' in values:
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
increased_lines = self.sudo().filtered(lambda r: r.purchase_line_count and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == -1)
decreased_lines = self.sudo().filtered(lambda r: r.purchase_line_count and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == 1)
increased_lines = self.sudo().filtered(lambda r: r.product_id.service_to_purchase and r.purchase_line_count and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == -1)
decreased_lines = self.sudo().filtered(lambda r: r.product_id.service_to_purchase and r.purchase_line_count and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == 1)
increased_values = {line.id: line.product_uom_qty for line in increased_lines}
decreased_values = {line.id: line.product_uom_qty for line in decreased_lines}
+9 -1
View File
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class ResConfigSettings(models.TransientModel):
@@ -64,6 +65,13 @@ class ResConfigSettings(models.TransientModel):
def _onchange_group_stock_production_lot(self):
if not self.group_stock_production_lot:
self.group_lot_on_delivery_slip = False
tracked_products = self.env['product.template'].search([('tracking', 'in', ['lot', 'serial']),])
if not self.group_stock_production_lot and tracked_products:
names = ", ".join(tracked_products.mapped('display_name') if len(tracked_products) <= 10
else tracked_products[:10].mapped('display_name') + ["..."])
raise UserError(_("You should not remove the 'lots and serial numbers' "
"option while the following products are still tracked by lot "
"or serial number:\n %s") % names)
@api.onchange('group_stock_adv_location')
def onchange_adv_location(self):
+1 -1
View File
@@ -99,7 +99,7 @@
<field name="name">stock_move multi-company</field>
<field name="model_id" search="[('model','=','stock.move')]" model="ir.model"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
<field name="domain_force">['|','|',('company_id','=',False),('company_id','child_of',[user.company_id.id]),('location_dest_id.company_id', '=', False)]</field>
</record>
<record model="ir.rule" id="stock_move_line_rule">
+2
View File
@@ -194,6 +194,7 @@
<record model="ir.ui.view" id="product_form_view_procurement_button">
<field name="name">product.product.procurement</field>
<field name="model">product.product</field>
<field name="groups_id" eval="[(4, ref('stock.group_stock_user'))]"/>
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="arch" type="xml">
<data>
@@ -285,6 +286,7 @@
<record model="ir.ui.view" id="product_template_form_view_procurement_button">
<field name="name">product.template_procurement</field>
<field name="model">product.template</field>
<field name="groups_id" eval="[(4, ref('stock.group_stock_user'))]"/>
<field name="inherit_id" ref="product.product_template_only_form_view"/>
<field name="arch" type="xml">
<data>
+1 -1
View File
@@ -21,7 +21,7 @@ internal transfer document is needed.
""",
'depends': ['sale_purchase', 'sale_stock'],
'data': ['data/stock_data.xml'],
'data': ['data/stock_data.xml', 'views/sale_order_views.xml'],
'installable': True,
'auto_install': False,
}
@@ -45,7 +45,7 @@ class TestDropship(common.TransactionCase):
self.assertAlmostEqual(po_line.product_qty, 1.00)
# Update qty on SO and check PO
so.order_line.product_uom_qty = 2.00
so.write({'order_line': [[1, so.order_line.id, {'product_uom_qty': 2.00}]]})
self.assertAlmostEqual(po_line.product_qty, 2.00)
# Create a new so line
@@ -0,0 +1,24 @@
<?xml version="1.0"?>
<odoo>
<data>
<record id="view_order_form_inherit_sale_stock" model="ir.ui.view">
<field name="name">sale.order.form.sale.dropshipping</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//page/field[@name='order_line']/form//field[@name='product_updatable']" position="after">
<field name="purchase_line_count" invisible="1"/>
</xpath>
<xpath expr="//page/field[@name='order_line']/form//field[@name='product_id']" position="attributes">
<attribute name="attrs">{'readonly': ['|', ('product_updatable', '=', False), ('purchase_line_count', '&gt;', 0)], 'required': [('display_type', '=', False)],}</attribute>
</xpath>
<xpath expr="//page/field[@name='order_line']/tree/field[@name='product_updatable']" position="after">
<field name="purchase_line_count" invisible="1"/>
</xpath>
<xpath expr="//page/field[@name='order_line']/tree/field[@name='product_id']" position="attributes">
<attribute name="attrs">{'readonly': ['|', ('product_updatable', '=', False), ('purchase_line_count', '&gt;', 0)], 'required': [('display_type', '=', False)],}</attribute>
</xpath>
</field>
</record>
</data>
</odoo>
+1 -1
View File
@@ -485,7 +485,7 @@ class TestHeavyMailPerformance(TransactionCase):
'user_id': self.env.uid,
})
self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id)
with self.assertQueryCount(__system__=60, emp=77): # com runbot: 61 - 76 // test_mail only: 60 - 72
with self.assertQueryCount(__system__=61, emp=77): # com runbot: 61 - 76 // test_mail only: 60 - 72
rec.write({'user_id': self.user_portal.id})
self.assertEqual(rec.message_partner_ids, self.partners | self.env.user.partner_id | self.user_portal.partner_id)
# write tracking message
+2 -2
View File
@@ -198,11 +198,11 @@ def concat_xml(file_list):
:param list(str) file_list: list of files to check
:returns: (concatenation_result, checksum)
:rtype: (str, str)
:rtype: (bytes, str)
"""
checksum = hashlib.new('sha1')
if not file_list:
return '', checksum.hexdigest()
return b'', checksum.hexdigest()
root = None
for fname in file_list:
@@ -2526,6 +2526,7 @@ var JournalDashboardGraph = AbstractField.extend({
* Called when the field is detached from the DOM.
*/
on_detach_callback: function () {
this.chart.tooltip.hidden(true);
this._isInDOM = false;
},
@@ -796,6 +796,7 @@ var BasicModel = AbstractModel.extend({
});
_.each(fields, function (field) {
var dataPoint;
record.data[field.name] = null;
if (field.type === 'many2one') {
if (field.value) {
var id = _.isArray(field.value) ? field.value[0] : field.value;
@@ -55,6 +55,7 @@ var CalendarController = AbstractController.extend({
this.readonlyFormViewId = params.readonlyFormViewId;
this.mapping = params.mapping;
this.context = params.context;
this.previousOpen = null;
// The quickCreating attribute ensures that we don't do several create
this.quickCreating = false;
},
@@ -271,7 +272,8 @@ var CalendarController = AbstractController.extend({
title += ': ' + this.renderer.arch.attrs.string;
}
if (this.eventOpenPopup) {
new dialogs.FormViewDialog(self, {
if (this.previousOpen) { this.previousOpen.close(); }
this.previousOpen = new dialogs.FormViewDialog(self, {
res_model: this.modelName,
context: context,
title: title,
@@ -283,7 +285,8 @@ var CalendarController = AbstractController.extend({
}
self.reload();
},
}).open();
});
this.previousOpen.open();
} else {
this.do_action({
type: 'ir.actions.act_window',
@@ -708,7 +708,7 @@ var ListRenderer = BasicRenderer.extend({
}
if (field.type === 'float' || field.type === 'integer' || field.type === 'monetary') {
$th.css({ textAlign: 'right' });
$th.addClass('o_list_number_th');
}
if (config.debug) {
@@ -19,6 +19,9 @@
&:not(.o_column_sortable):hover {
cursor: default;
}
&.o_list_number_th {
text-align: right;
}
}
}
tbody > tr > td:not(.o_list_record_selector) {
@@ -5,6 +5,7 @@ var AbstractStorageService = require('web.AbstractStorageService');
var CalendarView = require('web.CalendarView');
var CalendarRenderer = require('web.CalendarRenderer');
var Dialog = require('web.Dialog');
var ViewDialogs = require('web.view_dialogs');
var fieldUtils = require('web.field_utils');
var mixins = require('web.mixins');
var RamStorage = require('web.RamStorage');
@@ -475,6 +476,63 @@ QUnit.module('Views', {
calendar.destroy();
});
QUnit.test('open multiple event form at the same time', async function (assert) {
assert.expect(2);
var prom = testUtils.makeTestPromise();
var counter = 0;
testUtils.mock.patch(ViewDialogs.FormViewDialog, {
open: function () {
counter++;
this.options = _.omit(this.options, 'fields_view'); // force loadFieldView
return this._super.apply(this, arguments);
},
loadFieldView: function () {
var self = this;
var args = arguments;
var _super = this._super;
return prom.then(function () {
return _super.apply(self, args);
});
},
});
var event = $.Event();
var calendar = await createCalendarView({
View: CalendarView,
model: 'event',
data: this.data,
arch:
'<calendar class="o_calendar_test" '+
'string="Events" ' +
'event_open_popup="true" '+
'date_start="start" '+
'quick_add="False" '+
'date_stop="stop" '+
'all_day="allday" '+
'mode="month" '+
'readonly_form_view_id="1">'+
'<field name="name"/>'+
'</calendar>',
archs: archs,
viewOptions: {
initialDate: initialDate,
},
});
var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)');
for (var i = 0; i < 5; i++) {
await testUtils.dom.triggerMouseEvent($cell, "mousedown");
await testUtils.dom.triggerMouseEvent($cell, "mouseup");
}
prom.resolve();
await testUtils.nextTick();
assert.equal(counter, 5, "there should had been 5 attemps to open a modal");
assert.containsOnce($('body'), '.modal', "there should be only one open modal");
calendar.destroy();
});
QUnit.test('create event with timezone in week mode European locale', async function (assert) {
assert.expect(5);
+1 -5
View File
@@ -133,12 +133,8 @@ class View(models.Model):
('website_id', '!=', None),
])
for specific_parent_view in specific_parent_views:
record.copy({
# Set key to avoid copy() to generate an unique key as
# we want the specific view to have the same key
'key': record.key,
record.with_context(website_id=specific_parent_view.website_id.id).write({
'inherit_id': specific_parent_view.id,
'website_id': specific_parent_view.website_id.id,
})
return records
+6 -1
View File
@@ -149,7 +149,12 @@ class Menu(models.Model):
if menu_id.page_id:
menu_id.page_id = None
else:
page = self.env['website.page'].search(['|', ('url', '=', menu['url']), ('url', '=', '/' + menu['url'])], limit=1)
domain = self.env["website"].website_domain(website_id) + [
"|",
("url", "=", menu["url"]),
("url", "=", "/" + menu["url"]),
]
page = self.env["website.page"].search(domain, limit=1)
if page:
menu['page_id'] = page.id
menu['url'] = page.url
@@ -532,6 +532,34 @@ var ThemeCustomizeDialog = Dialog.extend({
_setActive: function () {
var self = this;
// First enforce that all input groups have only one element checked as
// it is supposed to be (it might not be the case on initialization, for
// exemple if we had data-xmlid="A" and data-xmlid="A,B" and if A and B
// are active, the 2 related inputs would be checked).
var $radioXMLInputs = this.$inputs.filter('[type="radio"][data-xmlid]');
var optionNames = _.uniq(_.map($radioXMLInputs, function (option) {
return option.name;
}));
_.each(optionNames, function (optionName) {
var $inputs = $radioXMLInputs.filter('[name="' + optionName + '"]:checked');
if ($inputs.length > 1) {
$inputs.prop('checked', false);
var maxNbXMLIDs = -1;
var $maxInput = null;
_.each($inputs, function (input) {
var $input = $(input);
var xmlID = $input.data('xmlid');
var nbXMLIDs = xmlID ? xmlID.split(',').length : 0;
if (nbXMLIDs >= maxNbXMLIDs) {
maxNbXMLIDs = nbXMLIDs;
$maxInput = $input;
}
});
$maxInput.prop('checked', true);
}
});
// Look at all options to see if they are enabled or disabled
var $enable = this.$inputs.filter(':checked');
+2 -2
View File
@@ -534,8 +534,8 @@ table.table_desc tr td {
// Default
&.s_carousel_default {
// Controls - chevron
.carousel-control-prev-icon:before { content: "\f053"; margin-left: 1.5rem; }
.carousel-control-next-icon:before { content: "\f054"; margin-right: 1.5rem; }
.carousel-control-prev-icon:before { content: "\f053" #{"/*rtl:'\f054'*/"}; margin-left: 1.5rem; }
.carousel-control-next-icon:before { content: "\f054" #{"/*rtl:'\f053'*/"}; margin-right: 1.5rem; }
.carousel-control-prev-icon:before,
.carousel-control-next-icon:before {
background-color: rgba(0,0,0,0);
+37
View File
@@ -811,6 +811,43 @@ class TestCowViewSaving(common.TransactionCase):
all_title_updated = specific_view.website_meta_title == self.base_view.website_meta_title == "A bug got fixed by updating this field"
self.assertEqual(all_title_updated, True, "Update on top level generic views should also be applied on specific views")
def test_module_new_inherit_view_on_parent_already_forked_xpath_replace(self):
""" Deeper, more specific test of above behavior.
A module install should add/update the COW view (if allowed fields,
eg not modified or prohibited (website_id, inherit_id..)).
This test ensure it does not crash if the child view is a primary view.
"""
View = self.env['ir.ui.view']
# Simulate layout views
base_view = View.create({
'name': 'Main Frontend Layout',
'type': 'qweb',
'arch': '<t t-call="web.layout"><t t-set="head_website"/></t>',
'key': '_portal.frontend_layout',
}).with_context(load_all_views=True)
inherit_view = View.create({
'name': 'Main layout',
'mode': 'extension',
'inherit_id': base_view.id,
'arch': '<xpath expr="//t[@t-set=\'head_website\']" position="replace"><t t-call-assets="web_editor.summernote" t-js="false" groups="website.group_website_publisher"/></xpath>',
'key': '_website.layout',
})
# Trigger cow on website_sale hierarchy for website 1
base_view.with_context(website_id=1).write({'name': 'Main Frontend Layout (W1)'})
# Simulate website_sale_comparison install, that's the real test, it
# should not crash.
View._load_records([dict(xml_id='_website_forum.layout', values={
'name': 'Forum Layout',
'mode': 'primary',
'inherit_id': inherit_view.id,
'arch': '<xpath expr="//t[@t-call-assets=\'web_editor.summernote\'][@t-js=\'false\']" position="attributes"><attribute name="groups"/></xpath>',
'key': '_website_forum.layout',
})])
def test_multiple_inherit_level(self):
""" Test multi-level inheritance:
Base
@@ -283,7 +283,7 @@
</div>
<div class="form-group">
<label class="col-form-label hdd4" for="description">Description</label>
<textarea row="3" name="description" class="form-control description"></textarea>
<textarea rows="3" name="description" class="form-control description"></textarea>
</div>
</main>
<footer class="modal-footer">
@@ -691,7 +691,7 @@
</div>
<div class="form-group">
<label class="col-form-label" for="activity_summary">Details Next Activity</label>
<textarea row="3" name="activity_summary" class="form-control activity_summary"><t t-esc="user_activity.summary"/></textarea>
<textarea rows="3" name="activity_summary" class="form-control activity_summary"><t t-esc="user_activity.summary"/></textarea>
</div>
<div class="form-group">
<label class="col-form-label" for="activity_date_deadline">Next Activity Date</label>
@@ -24,6 +24,9 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
'click .vote_up:not(.karma_required), .vote_down:not(.karma_required)': '_onVotePostClick',
'click .o_js_validation_queue a[href*="/validate"]': '_onValidationQueueClick',
'click .accept_answer:not(.karma_required)': '_onAcceptAnswerClick',
'click .validate_answer [data-href]': '_onAcceptAnswerClick',
'mouseenter .validate_answer [data-href]': '_onRemoveValidAnswerMouse',
'mouseleave .validate_answer [data-href]': '_onRemoveValidAnswerMouse',
'click .favourite_question': '_onFavoriteQuestionClick',
'click .comment_delete': '_onDeleteCommentClick',
'click .notification_close': '_onCloseNotificationClick',
@@ -350,6 +353,8 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
*/
_onAcceptAnswerClick: function (ev) {
ev.preventDefault();
var self = this;
var $acceptAnswerLinks = this.$('.accept_answer');
var $link = $(ev.currentTarget);
this._rpc({
route: $link.data('href'),
@@ -368,19 +373,41 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
$link.parent().append($warning);
}
} else {
$acceptAnswerLinks.removeClass('oe_answer_true')
.addClass('oe_answer_false');
$link.toggleClass('oe_answer_true', !!data)
.toggleClass('oe_answer_false', !data);
// TODO in master, review the utility of this function...
self._onCheckAnswerStatus(ev);
// If we are removing an accepted answer, reload the page as the
// design is quite different with or without an accepted answer.
if ($link.closest('.validate_answer').length) {
window.location.reload();
}
}
});
this._onCheckAnswerStatus(ev);
},
/**
* @private
* @param {Event} ev
*/
_onRemoveValidAnswerMouse: function (ev) {
var hover = (ev.type === 'mouseenter');
$(ev.currentTarget).find('.fa')
.toggleClass('fa-times-circle text-danger', hover)
.toggleClass('fa-check-circle text-success', !hover);
},
/**
* @private
* @param {Event} ev
*/
_onCheckAnswerStatus: function (ev) {
var $link = $(ev.currentTarget);
$link.toggleClass('text-success', !$link.hasClass('oe_answer_true'));
_.each(this.$('.accept_answer'), function (link) {
var $link = $(link);
$link.toggleClass('text-success', $link.hasClass('oe_answer_true'));
});
},
/**
* @private
@@ -47,16 +47,23 @@ $bronze: #eea91e;
}
.validate_answer {
@include o-position-absolute(0,0,0,0);
@include o-position-absolute(0, auto, 0, 0);
width: 4px;
height: 100%;
.bg-success {
@include o-position-absolute(0,0,0,0);
height: 100%;
width: 4px;
z-index: 1; // On top of card content if an alert is displayed inside
> .bg-success {
@include o-position-absolute(0, 0, 0, 0);
}
span {
@include o-position-absolute(10px,0,0,-9px);
> span {
@include o-position-absolute(10px, 50%);
transform: translateX(50%);
font-size: 20px;
&[data-href] {
cursor: pointer;
}
i {
padding: 2px 3px;
border-radius: 100%;
@@ -85,6 +92,10 @@ $bronze: #eea91e;
.favourite_question {
color: $gray-400 !important;
&.forum_favourite_question {
color: $gold !important;
}
}
.oe_grey {
@@ -186,10 +197,6 @@ img.o_forum_avatar_big {
width: auto;
}
.forum_favourite_question {
color: $gold !important;
}
.o_forum_user_bio_expand {
position: absolute;
display: none;
+4 -1
View File
@@ -800,7 +800,10 @@
<div class="card-body">
<div t-if="answer.is_correct" class="validate_answer">
<div class="bg-success"></div>
<span title="Question has accepted answer" role="img" aria-label="Question has accepted answer"><i class="fa fa-check-circle text-success bg-white"/></span>
<span title="Question has accepted answer" role="img" aria-label="Question has accepted answer"
t-attf-data-href="/forum/#{slug(question.forum_id)}/post/#{slug(answer)}/toggle_correct">
<i class="fa fa-check-circle text-success bg-white"/>
</span>
</div>
<div class="row">
<div class="col-1">
@@ -130,3 +130,19 @@ class TestWebsiteSaleCheckoutAddress(odoo.tests.TransactionCase):
self.default_address_values['partner_id'] = new_partner.id
self.WebsiteSaleController.address(**self.default_address_values)
self.assertEqual(new_partner.company_id, self.website.company_id, "Public user edited billing (the partner itself) should not get its company modified.")
def test_04_apply_empty_pl(self):
''' Ensure empty pl code reset the applied pl '''
so = self._create_so(self.env.user.partner_id.id)
eur_pl = self.env['product.pricelist'].create({
'name': 'EUR_test',
'website_id': self.website.id,
'code': 'EUR_test',
})
with MockRequest(self.env, website=self.website, sale_order_id=so.id):
self.WebsiteSaleController.pricelist('EUR_test')
self.assertEqual(so.pricelist_id, eur_pl, "Ensure EUR_test is applied")
self.WebsiteSaleController.pricelist('')
self.assertNotEqual(so.pricelist_id, eur_pl, "Pricelist should be removed when sending an empty pl code")
@@ -33,7 +33,7 @@ class ProductWishlist(models.Model):
else:
wish = self.search([("partner_id", "=", self.env.user.partner_id.id)])
return wish.filtered('product_id.product_tmpl_id.website_published')
return wish.sudo().filtered('product_id.product_tmpl_id.website_published')
@api.model
def _add_to_wishlist(self, pricelist_id, currency_id, website_id, price, product_id, partner_id=False):
+11
View File
@@ -0,0 +1,11 @@
Finland, 2019-07-31
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,
Tatiana Deribina t.i.deribina@gmail.com https://github.com/Tatider
+11
View File
@@ -0,0 +1,11 @@
Vietnam, 2019-07-27
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,
Anh Vu <vuna2004@gmail.com> https://github.com/anhvu-sg
+11
View File
@@ -0,0 +1,11 @@
Poland, 2019-07-31
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,
Karol Rybak karolrybak85@gmail.com https://github.com/karol1985
+11
View File
@@ -0,0 +1,11 @@
Denmark, 2019-07-24
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,
Stefan Andersen <stfnandersen@gmail.com> https://github.com/stfnndrsn
+15
View File
@@ -674,9 +674,19 @@ actual arch.
if node.getparent() is None:
source = copy.deepcopy(spec[0])
else:
replaced_node_tag = None
for child in spec:
if child.get('position') == 'move':
child = extract(child)
if self._context.get('inherit_branding') and not replaced_node_tag and child.tag is not etree.Comment:
# To make a correct branding, we need to
# - know exactly which node has been replaced
# - store it before anything else has altered the Tree
# Do it exactly here :D
child.set('meta-oe-xpath-replacing', node.tag)
replaced_node_tag = node.tag # We just store the replaced node tag on the first child of the xpath replacing it
node.addprevious(child)
node.getparent().remove(node)
elif pos == 'attributes':
@@ -1297,6 +1307,11 @@ actual arch.
if child.get('data-oe-xpath'):
# injected by view inheritance, skip otherwise
# generated xpath is incorrect
# Also, if a node is known to have been replaced during applying xpath
# increment its index to compute an accurate xpath for susequent nodes
replaced_node_tag = child.attrib.pop('meta-oe-xpath-replacing', None)
if replaced_node_tag:
indexes[replaced_node_tag] += 1
self.distribute_branding(child)
else:
indexes[child.tag] += 1
+1 -1
View File
@@ -58,7 +58,7 @@
</rng:optional>
</rng:interleave>
<rng:empty/>
<rng:empty/>
</rng:element>
</rng:define>
<rng:start>
+109
View File
@@ -674,6 +674,115 @@ class TestTemplating(ViewCase):
second.get('data-oe-id'),
"second should come from the extension view")
def test_branding_inherit_replace_node(self):
view1 = self.View.create({
'name': "Base view",
'type': 'qweb',
'arch': """<hello>
<world></world>
<world><t t-esc="hello"/></world>
<world></world>
</hello>
"""
})
self.View.create({
'name': "Extension",
'type': 'qweb',
'inherit_id': view1.id,
'arch': """<xpath expr="/hello/world[1]" position="replace">
<world>Is a ghetto</world>
<world>Wonder when I'll find paradise</world>
</xpath>
"""
})
arch_string = view1.with_context(inherit_branding=True).read_combined(['arch'])['arch']
arch = etree.fromstring(arch_string)
self.View.distribute_branding(arch)
# First world - has been replaced by inheritance
[initial] = arch.xpath('/hello[1]/world[1]')
self.assertEqual(
'/xpath/world[1]',
initial.get('data-oe-xpath'),
'Inherited nodes have correct xpath')
# Second world added by inheritance
[initial] = arch.xpath('/hello[1]/world[2]')
self.assertEqual(
'/xpath/world[2]',
initial.get('data-oe-xpath'),
'Inherited nodes have correct xpath')
# Third world - is not editable
[initial] = arch.xpath('/hello[1]/world[3]')
self.assertFalse(
initial.get('data-oe-xpath'),
'node containing t-esc is not branded')
# The most important assert
# Fourth world - should have a correct oe-xpath, which is 3rd in main view
[initial] = arch.xpath('/hello[1]/world[4]')
self.assertEqual(
'/hello[1]/world[3]',
initial.get('data-oe-xpath'),
"The node's xpath position should be correct")
def test_branding_inherit_replace_node2(self):
view1 = self.View.create({
'name': "Base view",
'type': 'qweb',
'arch': """<hello>
<world></world>
<world><t t-esc="hello"/></world>
<world></world>
</hello>
"""
})
self.View.create({
'name': "Extension",
'type': 'qweb',
'inherit_id': view1.id,
'arch': """<xpath expr="/hello/world[1]" position="replace">
<war>Is a ghetto</war>
<world>Wonder when I'll find paradise</world>
</xpath>
"""
})
arch_string = view1.with_context(inherit_branding=True).read_combined(['arch'])['arch']
arch = etree.fromstring(arch_string)
self.View.distribute_branding(arch)
[initial] = arch.xpath('/hello[1]/war[1]')
self.assertEqual(
'/xpath/war',
initial.get('data-oe-xpath'),
'Inherited nodes have correct xpath')
# First world: from inheritance
[initial] = arch.xpath('/hello[1]/world[1]')
self.assertEqual(
'/xpath/world',
initial.get('data-oe-xpath'),
'Inherited nodes have correct xpath')
# Second world - is not editable
[initial] = arch.xpath('/hello[1]/world[2]')
self.assertFalse(
initial.get('data-oe-xpath'),
'node containing t-esc is not branded')
# The most important assert
# Third world - should have a correct oe-xpath, which is 3rd in main view
[initial] = arch.xpath('/hello[1]/world[3]')
self.assertEqual(
'/hello[1]/world[3]',
initial.get('data-oe-xpath'),
"The node's xpath position should be correct")
def test_branding_primary_inherit(self):
view1 = self.View.create({
'name': "Base view",
+1 -1
View File
@@ -30,7 +30,7 @@
</div>
</group>
<group>
<field name="website" widget="url" placeholder="e.g. www.odoo.com"/>
<field name="website" widget="url" placeholder="e.g. https://www.odoo.com"/>
<field name="phone"/>
<field name="email"/>
<field name="vat"/>
+2 -2
View File
@@ -117,7 +117,7 @@
<field name="zip" placeholder="ZIP" class="o_address_zip"/>
<field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/>
</div>
<field name="website" widget="url" placeholder="e.g. www.odoo.com"/>
<field name="website" widget="url" placeholder="e.g. https://www.odoo.com"/>
</group>
<group>
</group>
@@ -263,7 +263,7 @@
<field name="mobile" widget="phone"/>
<field name="user_ids" invisible="1"/>
<field name="email" widget="email" context="{'gravatar_image': True}" attrs="{'required': [('user_ids','!=', [])]}"/>
<field name="website" widget="url" placeholder="e.g. www.odoo.com"/>
<field name="website" widget="url" placeholder="e.g. https://www.odoo.com"/>
<field name="title" options='{"no_open": True}'
attrs="{'invisible': [('is_company', '=', True)]}"/>
<field name="lang"/>