[MERGE] forward port branch saas-11.3 up to 6cc7d3f945
This commit is contained in:
@@ -112,6 +112,12 @@
|
|||||||
<t t-foreach="o._get_tax_amount_by_group()" t-as="amount_by_group">
|
<t t-foreach="o._get_tax_amount_by_group()" t-as="amount_by_group">
|
||||||
<tr style="border-bottom:1px solid #dddddd;">
|
<tr style="border-bottom:1px solid #dddddd;">
|
||||||
<t t-if="len(o.tax_line_ids) == 1 and o.amount_untaxed == amount_by_group[2]">
|
<t t-if="len(o.tax_line_ids) == 1 and o.amount_untaxed == amount_by_group[2]">
|
||||||
|
<td><span t-esc="amount_by_group[0]"/></td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-esc="amount_by_group[3]" />
|
||||||
|
</td>
|
||||||
|
</t>
|
||||||
|
<t t-else="">
|
||||||
<td>
|
<td>
|
||||||
<span t-esc="amount_by_group[0]"/>
|
<span t-esc="amount_by_group[0]"/>
|
||||||
<span>&nbsp;<span>on</span>
|
<span>&nbsp;<span>on</span>
|
||||||
@@ -122,12 +128,6 @@
|
|||||||
<span t-esc="amount_by_group[3]"/>
|
<span t-esc="amount_by_group[3]"/>
|
||||||
</td>
|
</td>
|
||||||
</t>
|
</t>
|
||||||
<t t-else="">
|
|
||||||
<td><span t-esc="amount_by_group[0]"/></td>
|
|
||||||
<td class="text-right">
|
|
||||||
<span t-esc="amount_by_group[3]" />
|
|
||||||
</td>
|
|
||||||
</t>
|
|
||||||
</tr>
|
</tr>
|
||||||
</t>
|
</t>
|
||||||
<tr class="border-black">
|
<tr class="border-black">
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class IrModelFields(models.Model):
|
|||||||
|
|
||||||
ttype = fields.Selection(selection_add=[('serialized', 'serialized')])
|
ttype = fields.Selection(selection_add=[('serialized', 'serialized')])
|
||||||
serialization_field_id = fields.Many2one('ir.model.fields', string='Serialization Field',
|
serialization_field_id = fields.Many2one('ir.model.fields', string='Serialization Field',
|
||||||
ondelete='cascade', domain="[('ttype','=','serialized')]",
|
ondelete='cascade', domain="[('ttype','=','serialized'), ('model_id', '=', model_id)]",
|
||||||
help="If set, this field will be stored in the sparse structure of the "
|
help="If set, this field will be stored in the sparse structure of the "
|
||||||
"serialization field, instead of having its own database column. "
|
"serialization field, instead of having its own database column. "
|
||||||
"This cannot be changed after creation.",
|
"This cannot be changed after creation.",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<field name="related" position="before">
|
<field name="related" position="before">
|
||||||
<field name="serialization_field_id"
|
<field name="serialization_field_id"
|
||||||
domain="[('ttype','=','serialized'), ('model_id','=',model_id)]"
|
context="{'default_model_id': model_id, 'default_ttype': 'serialized'}"
|
||||||
attrs="{'readonly': [('state','=','base')]}"/>
|
attrs="{'readonly': [('state','=','base')]}"/>
|
||||||
</field>
|
</field>
|
||||||
</field>
|
</field>
|
||||||
|
|||||||
@@ -950,11 +950,13 @@ class Meeting(models.Model):
|
|||||||
|
|
||||||
@api.onchange('start_date')
|
@api.onchange('start_date')
|
||||||
def _onchange_start_date(self):
|
def _onchange_start_date(self):
|
||||||
self.start = self.start_date
|
if self.start_date:
|
||||||
|
self.start = self.start_date
|
||||||
|
|
||||||
@api.onchange('stop_date')
|
@api.onchange('stop_date')
|
||||||
def _onchange_stop_date(self):
|
def _onchange_stop_date(self):
|
||||||
self.stop = self.stop_date
|
if self.stop_date:
|
||||||
|
self.stop = self.stop_date
|
||||||
|
|
||||||
####################################################
|
####################################################
|
||||||
# Calendar Business, Reccurency, ...
|
# Calendar Business, Reccurency, ...
|
||||||
|
|||||||
@@ -708,7 +708,7 @@ class Lead(models.Model):
|
|||||||
def _get_duplicated_leads_by_emails(self, partner_id, email, include_lost=False):
|
def _get_duplicated_leads_by_emails(self, partner_id, email, include_lost=False):
|
||||||
""" Search for opportunities that have the same partner and that arent done or cancelled """
|
""" Search for opportunities that have the same partner and that arent done or cancelled """
|
||||||
if not email:
|
if not email:
|
||||||
return []
|
return self.env['crm.lead']
|
||||||
partner_match_domain = []
|
partner_match_domain = []
|
||||||
for email in set(email_split(email) + [email]):
|
for email in set(email_split(email) + [email]):
|
||||||
partner_match_domain.append(('email_from', '=ilike', email))
|
partner_match_domain.append(('email_from', '=ilike', email))
|
||||||
|
|||||||
@@ -15,12 +15,13 @@ Sidebar.include({
|
|||||||
*/
|
*/
|
||||||
init : function (parent, options) {
|
init : function (parent, options) {
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
if (options.viewType === "form") {
|
this.hasAttachments = options.viewType === "form";
|
||||||
|
if (this.hasAttachments) {
|
||||||
this.sections.splice(1, 0, { 'name' : 'files', 'label' : _t('Attachment(s)'), });
|
this.sections.splice(1, 0, { 'name' : 'files', 'label' : _t('Attachment(s)'), });
|
||||||
this.items.files = [];
|
this.items.files = [];
|
||||||
|
this.fileuploadId = _.uniqueId('oe_fileupload');
|
||||||
|
$(window).on(this.fileuploadId, this._onFileUploaded.bind(this));
|
||||||
}
|
}
|
||||||
this.fileuploadId = _.uniqueId('oe_fileupload');
|
|
||||||
$(window).on(this.fileuploadId, this._onFileUploaded.bind(this));
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Get the attachment linked to the record when the toolbar started
|
* Get the attachment linked to the record when the toolbar started
|
||||||
@@ -29,13 +30,16 @@ Sidebar.include({
|
|||||||
*/
|
*/
|
||||||
start: function () {
|
start: function () {
|
||||||
var _super = this._super.bind(this);
|
var _super = this._super.bind(this);
|
||||||
this._updateAttachments().then(_super);
|
var def = this.hasAttachments ? this._updateAttachments() : $.when();
|
||||||
|
return def.then(_super);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
$(window).off(this.fileuploadId);
|
if (this.hasAttachments) {
|
||||||
|
$(window).off(this.fileuploadId);
|
||||||
|
}
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -46,8 +50,10 @@ Sidebar.include({
|
|||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
updateEnv: function (env) {
|
updateEnv: function (env) {
|
||||||
this.env = env;
|
if (this.hasAttachments) {
|
||||||
this._updateAttachments().then(this._redraw.bind(this));
|
this.env = env;
|
||||||
|
this._updateAttachments().then(this._redraw.bind(this));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
@@ -90,10 +96,12 @@ Sidebar.include({
|
|||||||
*/
|
*/
|
||||||
_redraw: function () {
|
_redraw: function () {
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
this.$('.o_sidebar_add_attachment .o_form_binary_form')
|
if (this.hasAttachments) {
|
||||||
.change(this._onAddAttachment.bind(this));
|
this.$('.o_sidebar_add_attachment .o_form_binary_form')
|
||||||
this.$('.o_sidebar_delete_attachment')
|
.change(this._onAddAttachment.bind(this));
|
||||||
.click(this._onDeleteAttachment.bind(this));
|
this.$('.o_sidebar_delete_attachment')
|
||||||
|
.click(this._onDeleteAttachment.bind(this));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Update the attachments to be displayed in the attachment section
|
* Update the attachments to be displayed in the attachment section
|
||||||
@@ -102,6 +110,9 @@ Sidebar.include({
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_updateAttachments: function () {
|
_updateAttachments: function () {
|
||||||
|
if (this.items.files === undefined) {
|
||||||
|
return $.when();
|
||||||
|
}
|
||||||
var activeId = this.env.activeIds[0];
|
var activeId = this.env.activeIds[0];
|
||||||
if (!activeId) {
|
if (!activeId) {
|
||||||
this.items.files = [];
|
this.items.files = [];
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ odoo.define('document.tests', function (require) {
|
|||||||
|
|
||||||
var testUtils = require('web.test_utils');
|
var testUtils = require('web.test_utils');
|
||||||
var FormView = require('web.FormView');
|
var FormView = require('web.FormView');
|
||||||
|
var ListView = require('web.ListView');
|
||||||
|
|
||||||
var createView = testUtils.createView;
|
var createView = testUtils.createView;
|
||||||
|
|
||||||
@@ -16,6 +17,9 @@ odoo.define('document.tests', function (require) {
|
|||||||
records: [{
|
records: [{
|
||||||
id: 1,
|
id: 1,
|
||||||
display_name: "first record",
|
display_name: "first record",
|
||||||
|
}, {
|
||||||
|
id: 2,
|
||||||
|
display_name: "second record",
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
'ir.attachment': {
|
'ir.attachment': {
|
||||||
@@ -74,5 +78,34 @@ odoo.define('document.tests', function (require) {
|
|||||||
assert.strictEqual(form.sidebar.$('.o_sidebar_delete_attachment').length, 1, "there should be only one attachment");
|
assert.strictEqual(form.sidebar.$('.o_sidebar_delete_attachment').length, 1, "there should be only one attachment");
|
||||||
form.destroy();
|
form.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('no attachment on list view', function (assert) {
|
||||||
|
assert.expect(4);
|
||||||
|
|
||||||
|
var list = createView({
|
||||||
|
View: ListView,
|
||||||
|
model: 'partner',
|
||||||
|
data: this.data,
|
||||||
|
groupBy: ['display_name'],
|
||||||
|
viewOptions: {sidebar: true},
|
||||||
|
arch: '<tree string="Partners">' +
|
||||||
|
'<field name="display_name"/>' +
|
||||||
|
'</tree>',
|
||||||
|
mockRPC: function (route, args) {
|
||||||
|
assert.step(args.model);
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// select record then trigger render
|
||||||
|
list.$('.o_group_header:last').click();
|
||||||
|
list.$('.o_data_row input').click();
|
||||||
|
list.$('.o_group_header:first').click();
|
||||||
|
|
||||||
|
assert.verifySteps(['partner', 'partner', 'partner'],
|
||||||
|
"ir.attachment not called when selecting record in list view");
|
||||||
|
|
||||||
|
list.destroy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -161,13 +161,13 @@ class GamificationBadge(models.Model):
|
|||||||
|
|
||||||
for badge in self:
|
for badge in self:
|
||||||
owners = badge.owner_ids
|
owners = badge.owner_ids
|
||||||
badge.stats_my = sum(o.user_id == self.env.user for o in owners)
|
badge.stat_my = sum(o.user_id == self.env.user for o in owners)
|
||||||
badge.stats_this_month = sum(o.create_date >= first_month_day for o in owners)
|
badge.stat_this_month = sum(o.create_date >= first_month_day for o in owners)
|
||||||
badge.stats_my_this_month = sum(
|
badge.stat_my_this_month = sum(
|
||||||
o.user_id == self.env.user and o.create_date >= first_month_day
|
o.user_id == self.env.user and o.create_date >= first_month_day
|
||||||
for o in owners
|
for o in owners
|
||||||
)
|
)
|
||||||
badge.stats_my_monthly_sending = sum(
|
badge.stat_my_monthly_sending = sum(
|
||||||
o.create_uid == self.env.user and o.create_date >= first_month_day
|
o.create_uid == self.env.user and o.create_date >= first_month_day
|
||||||
for o in owners
|
for o in owners
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -417,7 +417,7 @@
|
|||||||
<kanban class="o_kanban_mobile">
|
<kanban class="o_kanban_mobile">
|
||||||
<templates>
|
<templates>
|
||||||
<t t-name="kanban-box">
|
<t t-name="kanban-box">
|
||||||
<div t-attf-class="oe_kanban_global_click">
|
<div class="oe_kanban_global_click">
|
||||||
<div>
|
<div>
|
||||||
<strong><field name="name"/></strong>
|
<strong><field name="name"/></strong>
|
||||||
</div>
|
</div>
|
||||||
@@ -426,7 +426,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>Vacancies : <field name="expected_employees"/></span>
|
<span>Vacancies : <field name="expected_employees"/></span>
|
||||||
<span t-att-class="'pull-right label label-success' if record.state.raw_value == 'recruit' else 'pull-right label label-danger'">
|
<span t-att-class="record.state.raw_value == 'recruit' and 'pull-right label label-success' or 'pull-right label label-danger'">
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -622,12 +622,12 @@ class HolidaysRequest(models.Model):
|
|||||||
elif holiday.state == 'confirm':
|
elif holiday.state == 'confirm':
|
||||||
holiday.activity_schedule(
|
holiday.activity_schedule(
|
||||||
'hr_holidays.mail_act_leave_approval', fields.Date.today(),
|
'hr_holidays.mail_act_leave_approval', fields.Date.today(),
|
||||||
user_id=holiday._get_responsible_for_approval().id)
|
user_id=holiday.sudo()._get_responsible_for_approval().id)
|
||||||
elif holiday.state == 'validate1':
|
elif holiday.state == 'validate1':
|
||||||
holiday.activity_feedback(['hr_holidays.mail_act_leave_approval'])
|
holiday.activity_feedback(['hr_holidays.mail_act_leave_approval'])
|
||||||
holiday.activity_schedule(
|
holiday.activity_schedule(
|
||||||
'hr_holidays.mail_act_leave_second_approval', fields.Date.today(),
|
'hr_holidays.mail_act_leave_second_approval', fields.Date.today(),
|
||||||
user_id=holiday._get_responsible_for_approval().id)
|
user_id=holiday.sudo()._get_responsible_for_approval().id)
|
||||||
elif holiday.state == 'validate':
|
elif holiday.state == 'validate':
|
||||||
to_do |= holiday
|
to_do |= holiday
|
||||||
elif holiday.state == 'refuse':
|
elif holiday.state == 'refuse':
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class MaintenanceEquipment(models.Model):
|
|||||||
employee_id = fields.Many2one('hr.employee', string='Assigned to Employee', track_visibility='onchange')
|
employee_id = fields.Many2one('hr.employee', string='Assigned to Employee', track_visibility='onchange')
|
||||||
department_id = fields.Many2one('hr.department', string='Assigned to Department', track_visibility='onchange')
|
department_id = fields.Many2one('hr.department', string='Assigned to Department', track_visibility='onchange')
|
||||||
equipment_assign_to = fields.Selection(
|
equipment_assign_to = fields.Selection(
|
||||||
[('department', 'Department'), ('employee', 'Employee') ,('other', 'Other')],
|
[('department', 'Department'), ('employee', 'Employee'), ('other', 'Other')],
|
||||||
string='Used By',
|
string='Used By',
|
||||||
required=True,
|
required=True,
|
||||||
default='employee')
|
default='employee')
|
||||||
@@ -82,10 +82,11 @@ class MaintenanceRequest(models.Model):
|
|||||||
|
|
||||||
@api.depends('employee_id', 'department_id')
|
@api.depends('employee_id', 'department_id')
|
||||||
def _compute_owner(self):
|
def _compute_owner(self):
|
||||||
if self.equipment_id.equipment_assign_to == 'employee':
|
for r in self:
|
||||||
self.owner_user_id = self.employee_id.user_id.id
|
if r.equipment_id.equipment_assign_to == 'employee':
|
||||||
elif self.equipment_id.equipment_assign_to == 'department':
|
r.owner_user_id = r.employee_id.user_id.id
|
||||||
self.owner_user_id = self.department_id.manager_id.user_id.id
|
elif r.equipment_id.equipment_assign_to == 'department':
|
||||||
|
r.owner_user_id = r.department_id.manager_id.user_id.id
|
||||||
|
|
||||||
@api.onchange('employee_id', 'department_id')
|
@api.onchange('employee_id', 'department_id')
|
||||||
def onchange_department_or_employee_id(self):
|
def onchange_department_or_employee_id(self):
|
||||||
|
|||||||
@@ -111,7 +111,7 @@
|
|||||||
<field eval="2" name="priority"/>
|
<field eval="2" name="priority"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<field name="user_id" position="after">
|
<field name="user_id" position="after">
|
||||||
<field name="planned_hours"/>
|
<field name="planned_hours" widget="float_time" sum="Initially Planned Hours" />
|
||||||
<field name="remaining_hours" widget="float_time" sum="Remaining Hours" readonly="1"/>
|
<field name="remaining_hours" widget="float_time" sum="Remaining Hours" readonly="1"/>
|
||||||
<field name="effective_hours" widget="float_time" sum="Spent Hours" invisible="1"/>
|
<field name="effective_hours" widget="float_time" sum="Spent Hours" invisible="1"/>
|
||||||
</field>
|
</field>
|
||||||
|
|||||||
@@ -12,4 +12,15 @@
|
|||||||
</field>
|
</field>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="supplier_invoice_bbacomm_form" model="ir.ui.view">
|
||||||
|
<field name="name">account.invoice.supplier.form</field>
|
||||||
|
<field name="model">account.invoice</field>
|
||||||
|
<field name="inherit_id" ref="account.invoice_supplier_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="reference" position="before">
|
||||||
|
<field name="reference_type" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -44,6 +44,10 @@
|
|||||||
<field name="name">Switzerland VAT Form: grid 381 base</field>
|
<field name="name">Switzerland VAT Form: grid 381 base</field>
|
||||||
<field name="applicability">taxes</field>
|
<field name="applicability">taxes</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record id="vat_tag_382_a" model="account.account.tag">
|
||||||
|
<field name="name">Switzerland VAT Form: grid 382 base</field>
|
||||||
|
<field name="applicability">taxes</field>
|
||||||
|
</record>
|
||||||
<record id="vat_tag_302_b" model="account.account.tag">
|
<record id="vat_tag_302_b" model="account.account.tag">
|
||||||
<field name="name">Switzerland VAT Form: grid 302 tax</field>
|
<field name="name">Switzerland VAT Form: grid 302 tax</field>
|
||||||
<field name="applicability">taxes</field>
|
<field name="applicability">taxes</field>
|
||||||
@@ -60,6 +64,10 @@
|
|||||||
<field name="name">Switzerland VAT Form: grid 381 tax</field>
|
<field name="name">Switzerland VAT Form: grid 381 tax</field>
|
||||||
<field name="applicability">taxes</field>
|
<field name="applicability">taxes</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record id="vat_tag_382_b" model="account.account.tag">
|
||||||
|
<field name="name">Switzerland VAT Form: grid 382 tax</field>
|
||||||
|
<field name="applicability">taxes</field>
|
||||||
|
</record>
|
||||||
<record id="vat_tag_400" model="account.account.tag">
|
<record id="vat_tag_400" model="account.account.tag">
|
||||||
<field name="name">Switzerland VAT Form: grid 400</field>
|
<field name="name">Switzerland VAT Form: grid 400</field>
|
||||||
<field name="applicability">taxes</field>
|
<field name="applicability">taxes</field>
|
||||||
@@ -82,7 +90,7 @@
|
|||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="vat_tag_dedouanement" model="account.account.tag">
|
<record id="vat_tag_dedouanement" model="account.account.tag">
|
||||||
<field name="name">Switzerland VAT Form: 100% dédouanement (base in grid 400)</field>
|
<field name="name">Switzerland VAT Form: 100% dédouanement (base in grid 400)</field>
|
||||||
<field name="applicability">taxes</field>
|
<field name="applicability">taxes</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
@@ -320,6 +328,18 @@
|
|||||||
<field name="tag_ids" eval="[(6,0,[ref('vat_tag_405')])]"/>
|
<field name="tag_ids" eval="[(6,0,[ref('vat_tag_405')])]"/>
|
||||||
<field name="tax_group_id" ref="tax_group_tva_77"/>
|
<field name="tax_group_id" ref="tax_group_tva_77"/>
|
||||||
</record>
|
</record>
|
||||||
|
<record model="account.tax.template" id="vat_77_purchase_return">
|
||||||
|
<field name="name">TVA due a 7.7% (TN) (return)</field>
|
||||||
|
<field name="description">7.7% achat (return)</field>
|
||||||
|
<field name="amount" eval="-7.7"/>
|
||||||
|
<field name="amount_type">percent</field>
|
||||||
|
<field name="sequence" eval="0"/>
|
||||||
|
<field name="chart_template_id" ref="l10nch_chart_template"/>
|
||||||
|
<field name="type_tax_use">purchase</field>
|
||||||
|
<field name="refund_account_id" ref="ch_coa_1170"/>
|
||||||
|
<field name="account_id" ref="ch_coa_1170"/>
|
||||||
|
<field name="tag_ids" eval="[(6,0,[ref('vat_tag_382_a'),ref('vat_tag_382_b')])]"/>
|
||||||
|
</record>
|
||||||
<record model="account.tax.template" id="vat_XO">
|
<record model="account.tax.template" id="vat_XO">
|
||||||
<field name="name">TVA due a 0% (Exportations)</field>
|
<field name="name">TVA due a 0% (Exportations)</field>
|
||||||
<field name="amount" eval="0.00"/>
|
<field name="amount" eval="0.00"/>
|
||||||
@@ -368,4 +388,15 @@
|
|||||||
<field name="tag_ids" eval="[(6,0,[ref('vat_tag_dedouanement')])]"/>
|
<field name="tag_ids" eval="[(6,0,[ref('vat_tag_dedouanement')])]"/>
|
||||||
<field name="tax_group_id" ref="tax_group_tva_0"/>
|
<field name="tax_group_id" ref="tax_group_tva_0"/>
|
||||||
</record>
|
</record>
|
||||||
|
<!--# for reverse charge or VAT on Acquisition (group of taxes)-->
|
||||||
|
<record model="account.tax.template" id="vat_77_purchase_reverse">
|
||||||
|
<field name="description">7.7% achat</field>
|
||||||
|
<field name="name">TVA 7.7% sur achat service a l'etranger (reverse charge)</field>
|
||||||
|
<field name="amount" eval="7.7"/>
|
||||||
|
<field name="amount_type">group</field>
|
||||||
|
<field name="type_tax_use">purchase</field>
|
||||||
|
<field name="children_tax_ids" eval="[(6, 0, [ref('vat_77_purchase_return'), ref('vat_77_purchase')])]"/>
|
||||||
|
<field name="chart_template_id" ref="l10nch_chart_template"/>
|
||||||
|
<field name="tag_ids" eval="[(6,0,[ref('vat_tag_382_a'), ref('vat_tag_382_b')])]"/>
|
||||||
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -173,7 +173,6 @@ co_puc_413568,413568,Venta de equipo profesional y científico,FALSE,account.dat
|
|||||||
co_puc_413570,413570,"Venta de loterías, rifas, chance, apuestas y similares",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
co_puc_413570,413570,"Venta de loterías, rifas, chance, apuestas y similares",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||||
co_puc_413572,413572,Reparación de efectos personales y electrodomésticos,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
co_puc_413572,413572,Reparación de efectos personales y electrodomésticos,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||||
co_puc_413595,413595,Venta de Otros Productos,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
co_puc_413595,413595,Venta de Otros Productos,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||||
co_puc_413595,413595,Venta de otros productos,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
|
||||||
co_puc_415505,415505,Arrendamientos de Bienes Inmuebles,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
co_puc_415505,415505,Arrendamientos de Bienes Inmuebles,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||||
co_puc_421005,421005,Intereses,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
co_puc_421005,421005,Intereses,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||||
co_puc_421020,421020,Diferencia en Cambio,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
co_puc_421020,421020,Diferencia en Cambio,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||||
|
|||||||
|
@@ -12,7 +12,7 @@
|
|||||||
<h4>Paie du <span t-field="o.date_from"/> au <span t-field="o.date_to"/></h4>
|
<h4>Paie du <span t-field="o.date_from"/> au <span t-field="o.date_to"/></h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-4 pull-right">
|
<div class="col-xs-4 pull-right">
|
||||||
<table class="table table-bordered" stype="border: 2px solid black;">
|
<table class="table table-bordered" style="border: 2px solid black;">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div t-field="o.employee_id.company_id.partner_id"
|
<div t-field="o.employee_id.company_id.partner_id"
|
||||||
|
|||||||
@@ -723,7 +723,7 @@ class MailThread(models.AbstractModel):
|
|||||||
view_title = _('View')
|
view_title = _('View')
|
||||||
|
|
||||||
default_groups = [
|
default_groups = [
|
||||||
('user', lambda partner: bool(partner.user_ids) and not any(user.share for user in partner.user_ids), {}),
|
('user', lambda partner: bool(partner.user_ids) and any(not user.share for user in partner.user_ids), {}),
|
||||||
('portal', lambda partner: bool(partner.user_ids) and all(user.share for user in partner.user_ids), {
|
('portal', lambda partner: bool(partner.user_ids) and all(user.share for user in partner.user_ids), {
|
||||||
'has_button_access': False,
|
'has_button_access': False,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ class Invoice(models.Model):
|
|||||||
]).write({'date_cancel': fields.Date.today()})
|
]).write({'date_cancel': fields.Date.today()})
|
||||||
return super(Invoice, self).action_cancel()
|
return super(Invoice, self).action_cancel()
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def write(self, vals):
|
||||||
|
'''Change the partner on related membership_line'''
|
||||||
|
if 'partner_id' in vals:
|
||||||
|
self.env['membership.membership_line'].search([
|
||||||
|
('account_invoice_line', 'in', self.mapped('invoice_line_ids').ids)
|
||||||
|
]).write({'partner': vals['partner_id']})
|
||||||
|
return super(Invoice, self).write(vals)
|
||||||
|
|
||||||
|
|
||||||
class AccountInvoiceLine(models.Model):
|
class AccountInvoiceLine(models.Model):
|
||||||
_inherit = 'account.invoice.line'
|
_inherit = 'account.invoice.line'
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ class Partner(models.Model):
|
|||||||
@api.depends('member_lines.account_invoice_line.invoice_id.state',
|
@api.depends('member_lines.account_invoice_line.invoice_id.state',
|
||||||
'member_lines.account_invoice_line.invoice_id.invoice_line_ids',
|
'member_lines.account_invoice_line.invoice_id.invoice_line_ids',
|
||||||
'member_lines.account_invoice_line.invoice_id.payment_ids',
|
'member_lines.account_invoice_line.invoice_id.payment_ids',
|
||||||
|
'member_lines.account_invoice_line.invoice_id.payment_move_line_ids',
|
||||||
|
'member_lines.account_invoice_line.invoice_id.partner_id',
|
||||||
'free_member',
|
'free_member',
|
||||||
'member_lines.date_to', 'member_lines.date_from',
|
'member_lines.date_to', 'member_lines.date_from',
|
||||||
'associate_member.membership_state')
|
'associate_member.membership_state')
|
||||||
@@ -104,18 +106,22 @@ class Partner(models.Model):
|
|||||||
if partner.membership_stop and today > partner.membership_stop:
|
if partner.membership_stop and today > partner.membership_stop:
|
||||||
res[partner.id] = 'free' if partner.free_member else 'old'
|
res[partner.id] = 'free' if partner.free_member else 'old'
|
||||||
continue
|
continue
|
||||||
|
if partner.associate_member:
|
||||||
|
res_state = partner.associate_member._membership_state()
|
||||||
|
res[partner.id] = res_state[partner.associate_member.id]
|
||||||
|
continue
|
||||||
|
|
||||||
s = 4
|
s = 4
|
||||||
if partner.member_lines:
|
if partner.member_lines:
|
||||||
for mline in partner.member_lines:
|
for mline in partner.member_lines:
|
||||||
if (mline.date_to or '0000-00-00') >= today and (mline.date_from or '0000-00-00') <= today:
|
if (mline.date_to or '0000-00-00') >= today and (mline.date_from or '0000-00-00') <= today:
|
||||||
if mline.account_invoice_line.invoice_id:
|
if mline.account_invoice_line.invoice_id.partner_id == partner:
|
||||||
mstate = mline.account_invoice_line.invoice_id.state
|
mstate = mline.account_invoice_line.invoice_id.state
|
||||||
if mstate == 'paid':
|
if mstate == 'paid':
|
||||||
s = 0
|
s = 0
|
||||||
inv = mline.account_invoice_line.invoice_id
|
inv = mline.account_invoice_line.invoice_id
|
||||||
for payment in inv.payment_ids:
|
for ml in inv.payment_move_line_ids:
|
||||||
if any(payment.invoice_ids.filtered(lambda inv: inv.type == 'out_refund')):
|
if any(ml.invoice_id.filtered(lambda inv: inv.type == 'out_refund')):
|
||||||
s = 2
|
s = 2
|
||||||
break
|
break
|
||||||
elif mstate == 'open' and s != 0:
|
elif mstate == 'open' and s != 0:
|
||||||
@@ -144,9 +150,6 @@ class Partner(models.Model):
|
|||||||
res[partner.id] = 'none'
|
res[partner.id] = 'none'
|
||||||
if partner.free_member and s != 0:
|
if partner.free_member and s != 0:
|
||||||
res[partner.id] = 'free'
|
res[partner.id] = 'free'
|
||||||
if partner.associate_member:
|
|
||||||
res_state = partner.associate_member._membership_state()
|
|
||||||
res[partner.id] = res_state[partner.associate_member.id]
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ class PosOrder(models.Model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _get_account_move_line_group_data_type_key(self, data_type, values):
|
def _get_account_move_line_group_data_type_key(self, data_type, values, options={}):
|
||||||
"""
|
"""
|
||||||
Return a tuple which will be used as a key for grouping account
|
Return a tuple which will be used as a key for grouping account
|
||||||
move lines in _create_account_move_line method.
|
move lines in _create_account_move_line method.
|
||||||
@@ -195,10 +195,16 @@ class PosOrder(models.Model):
|
|||||||
values['analytic_account_id'],
|
values['analytic_account_id'],
|
||||||
values['debit'] > 0)
|
values['debit'] > 0)
|
||||||
elif data_type == 'tax':
|
elif data_type == 'tax':
|
||||||
return ('tax',
|
order_id = values.pop('order_id', False)
|
||||||
values['partner_id'],
|
tax_key = ('tax',
|
||||||
values['tax_line_id'],
|
values['partner_id'],
|
||||||
values['debit'] > 0)
|
values['tax_line_id'],
|
||||||
|
values['debit'] > 0)
|
||||||
|
if options.get('rounding_method') == 'round_globally':
|
||||||
|
tax_key = ('tax',
|
||||||
|
values['tax_line_id'],
|
||||||
|
order_id)
|
||||||
|
return tax_key
|
||||||
elif data_type == 'counter_part':
|
elif data_type == 'counter_part':
|
||||||
return ('counter_part',
|
return ('counter_part',
|
||||||
values['partner_id'],
|
values['partner_id'],
|
||||||
@@ -308,7 +314,7 @@ class PosOrder(models.Model):
|
|||||||
'move_id': move.id,
|
'move_id': move.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
key = self._get_account_move_line_group_data_type_key(data_type, values)
|
key = self._get_account_move_line_group_data_type_key(data_type, values, {'rounding_method': rounding_method})
|
||||||
if not key:
|
if not key:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -322,6 +328,14 @@ class PosOrder(models.Model):
|
|||||||
current_value['quantity'] = current_value.get('quantity', 0.0) + values.get('quantity', 0.0)
|
current_value['quantity'] = current_value.get('quantity', 0.0) + values.get('quantity', 0.0)
|
||||||
current_value['credit'] = current_value.get('credit', 0.0) + values.get('credit', 0.0)
|
current_value['credit'] = current_value.get('credit', 0.0) + values.get('credit', 0.0)
|
||||||
current_value['debit'] = current_value.get('debit', 0.0) + values.get('debit', 0.0)
|
current_value['debit'] = current_value.get('debit', 0.0) + values.get('debit', 0.0)
|
||||||
|
if key[0] == 'tax' and rounding_method == 'round_globally':
|
||||||
|
if current_value['debit'] - current_value['credit'] > 0:
|
||||||
|
current_value['debit'] = current_value['debit'] - current_value['credit']
|
||||||
|
current_value['credit'] = 0
|
||||||
|
else:
|
||||||
|
current_value['credit'] = current_value['credit'] - current_value['debit']
|
||||||
|
current_value['debit'] = 0
|
||||||
|
|
||||||
else:
|
else:
|
||||||
grouped_data[key].append(values)
|
grouped_data[key].append(values)
|
||||||
|
|
||||||
@@ -381,7 +395,8 @@ class PosOrder(models.Model):
|
|||||||
'credit': ((tax['amount'] > 0) and tax['amount']) or 0.0,
|
'credit': ((tax['amount'] > 0) and tax['amount']) or 0.0,
|
||||||
'debit': ((tax['amount'] < 0) and -tax['amount']) or 0.0,
|
'debit': ((tax['amount'] < 0) and -tax['amount']) or 0.0,
|
||||||
'tax_line_id': tax['id'],
|
'tax_line_id': tax['id'],
|
||||||
'partner_id': partner_id
|
'partner_id': partner_id,
|
||||||
|
'order_id': order.id
|
||||||
})
|
})
|
||||||
|
|
||||||
# round tax lines per order
|
# round tax lines per order
|
||||||
|
|||||||
@@ -862,14 +862,19 @@ class Task(models.Model):
|
|||||||
groups = super(Task, self)._notify_get_groups(message, groups)
|
groups = super(Task, self)._notify_get_groups(message, groups)
|
||||||
|
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|
||||||
|
new_group = (
|
||||||
|
'group_project_user',
|
||||||
|
lambda partner: bool(partner.user_ids) and any(user.has_group('project.group_project_user') for user in partner.user_ids),
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
if not self.user_id and not self.stage_id.fold:
|
if not self.user_id and not self.stage_id.fold:
|
||||||
take_action = self._notify_get_action_link('assign')
|
take_action = self._notify_get_action_link('assign')
|
||||||
project_actions = [{'url': take_action, 'title': _('I take it')}]
|
project_actions = [{'url': take_action, 'title': _('I take it')}]
|
||||||
new_group = (
|
new_group[2]['actions'] = project_actions
|
||||||
'group_project_user', lambda partner: bool(partner.user_ids) and any(user.has_group('project.group_project_user') for user in partner.user_ids), {
|
|
||||||
'actions': project_actions,
|
groups = [new_group] + groups
|
||||||
})
|
|
||||||
groups = [new_group] + groups
|
|
||||||
|
|
||||||
for group_name, group_method, group_data in groups:
|
for group_name, group_method, group_data in groups:
|
||||||
if group_name == 'customer':
|
if group_name == 'customer':
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ class PurchaseOrder(models.Model):
|
|||||||
'sequence': max(line.product_id.seller_ids.mapped('sequence')) + 1 if line.product_id.seller_ids else 1,
|
'sequence': max(line.product_id.seller_ids.mapped('sequence')) + 1 if line.product_id.seller_ids else 1,
|
||||||
'product_uom': line.product_uom.id,
|
'product_uom': line.product_uom.id,
|
||||||
'min_qty': 0.0,
|
'min_qty': 0.0,
|
||||||
'price': self.currency_id._convert(line.price_unit, currency, line.company_id, line.date_order or fields.Date.today()),
|
'price': self.currency_id._convert(line.price_unit, currency, line.company_id, line.date_order or fields.Date.today(), round=False),
|
||||||
'currency_id': currency.id,
|
'currency_id': currency.id,
|
||||||
'delay': 0,
|
'delay': 0,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,15 @@ class StockMove(models.Model):
|
|||||||
#rslt += invoices.mapped('refund_invoice_ids')
|
#rslt += invoices.mapped('refund_invoice_ids')
|
||||||
return rslt
|
return rslt
|
||||||
|
|
||||||
|
def _assign_picking_post_process(self, new=False):
|
||||||
|
super(StockMove, self)._assign_picking_post_process(new=new)
|
||||||
|
if new and self.sale_line_id and self.sale_line_id.order_id:
|
||||||
|
self.picking_id.message_post_with_view(
|
||||||
|
'mail.message_origin_link',
|
||||||
|
values={'self': self.picking_id, 'origin': self.sale_line_id.order_id},
|
||||||
|
subtype_id=self.env.ref('mail.mt_note').id)
|
||||||
|
|
||||||
|
|
||||||
class ProcurementGroup(models.Model):
|
class ProcurementGroup(models.Model):
|
||||||
_inherit = 'procurement.group'
|
_inherit = 'procurement.group'
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
from ast import literal_eval
|
from ast import literal_eval
|
||||||
import babel
|
import babel
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
import itertools
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from odoo import http, fields, _
|
from odoo import http, fields, _
|
||||||
@@ -88,7 +89,12 @@ class SaleTimesheetController(http.Controller):
|
|||||||
#
|
#
|
||||||
# Time Repartition (per employee per billable types)
|
# Time Repartition (per employee per billable types)
|
||||||
#
|
#
|
||||||
employees = projects.mapped('tasks.user_id.employee_ids') | request.env['account.analytic.line'].search([('project_id', 'in', projects.ids)]).mapped('employee_id')
|
user_ids = request.env['project.task'].sudo().search_read([('project_id', 'in', projects.ids), ('user_id', '!=', False)], ['user_id'])
|
||||||
|
user_ids = [user_id['user_id'][0] for user_id in user_ids]
|
||||||
|
employee_ids = request.env['res.users'].sudo().search_read([('id', 'in', user_ids)], ['employee_ids'])
|
||||||
|
# flatten the list of list
|
||||||
|
employee_ids = list(itertools.chain.from_iterable([employee_id['employee_ids'] for employee_id in employee_ids]))
|
||||||
|
employees = request.env['hr.employee'].sudo().browse(employee_ids) | request.env['account.analytic.line'].search([('project_id', 'in', projects.ids)]).mapped('employee_id')
|
||||||
repartition_domain = [('project_id', 'in', projects.ids), ('employee_id', '!=', False), ('timesheet_invoice_type', '!=', False)] # force billable type
|
repartition_domain = [('project_id', 'in', projects.ids), ('employee_id', '!=', False), ('timesheet_invoice_type', '!=', False)] # force billable type
|
||||||
repartition_data = request.env['account.analytic.line'].read_group(repartition_domain, ['employee_id', 'timesheet_invoice_type', 'unit_amount'], ['employee_id', 'timesheet_invoice_type'], lazy=False)
|
repartition_data = request.env['account.analytic.line'].read_group(repartition_domain, ['employee_id', 'timesheet_invoice_type', 'unit_amount'], ['employee_id', 'timesheet_invoice_type'], lazy=False)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from dateutil.relativedelta import relativedelta
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from odoo import api, fields, models, _
|
from odoo import api, fields, models, _
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import AccessError, UserError
|
||||||
from odoo.release import version
|
from odoo.release import version
|
||||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
|
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
|
||||||
|
|
||||||
@@ -33,8 +33,13 @@ class CrmTeam(models.Model):
|
|||||||
team_id = self.env['crm.team'].browse(self.env.context.get('default_team_id'))
|
team_id = self.env['crm.team'].browse(self.env.context.get('default_team_id'))
|
||||||
if not team_id:
|
if not team_id:
|
||||||
default_team_id = self.env.ref('sales_team.team_sales_department', raise_if_not_found=False)
|
default_team_id = self.env.ref('sales_team.team_sales_department', raise_if_not_found=False)
|
||||||
if default_team_id and (self.env.context.get('default_type') != 'lead' or default_team_id.use_leads):
|
if default_team_id:
|
||||||
team_id = default_team_id
|
try:
|
||||||
|
default_team_id.check_access_rule('read')
|
||||||
|
except AccessError:
|
||||||
|
return self.env['crm.team']
|
||||||
|
if self.env.context.get('default_type') != 'lead' or default_team_id.use_leads and default_team_id.active:
|
||||||
|
team_id = default_team_id
|
||||||
return team_id
|
return team_id
|
||||||
|
|
||||||
def _get_default_favorite_user_ids(self):
|
def _get_default_favorite_user_ids(self):
|
||||||
|
|||||||
@@ -720,7 +720,7 @@ class StockMove(models.Model):
|
|||||||
recompute = True
|
recompute = True
|
||||||
picking = Picking.create(move._get_new_picking_values())
|
picking = Picking.create(move._get_new_picking_values())
|
||||||
move.write({'picking_id': picking.id})
|
move.write({'picking_id': picking.id})
|
||||||
|
move._assign_picking_post_process(new=recompute)
|
||||||
# If this method is called in batch by a write on a one2many and
|
# If this method is called in batch by a write on a one2many and
|
||||||
# at some point had to create a picking, some next iterations could
|
# at some point had to create a picking, some next iterations could
|
||||||
# try to find back the created picking. As we look for it by searching
|
# try to find back the created picking. As we look for it by searching
|
||||||
@@ -730,6 +730,9 @@ class StockMove(models.Model):
|
|||||||
move.recompute()
|
move.recompute()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _assign_picking_post_process(self, new=False):
|
||||||
|
pass
|
||||||
|
|
||||||
def _get_new_picking_values(self):
|
def _get_new_picking_values(self):
|
||||||
""" Prepares a new picking for this move as it could not be assigned to
|
""" Prepares a new picking for this move as it could not be assigned to
|
||||||
another picking. This method is designed to be inherited. """
|
another picking. This method is designed to be inherited. """
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ class StockScrap(models.Model):
|
|||||||
|
|
||||||
def action_validate(self):
|
def action_validate(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
if self.product_id.type != 'product':
|
||||||
|
return self.do_scrap()
|
||||||
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
|
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
|
||||||
available_qty = sum(self.env['stock.quant']._gather(self.product_id,
|
available_qty = sum(self.env['stock.quant']._gather(self.product_id,
|
||||||
self.location_id,
|
self.location_id,
|
||||||
|
|||||||
@@ -11,11 +11,11 @@
|
|||||||
<div>
|
<div>
|
||||||
<span><strong>Customer Address:</strong></span>
|
<span><strong>Customer Address:</strong></span>
|
||||||
</div>
|
</div>
|
||||||
<div t-if="o.move_lines and o.move_lines[0].partner_id and not o.partner_id" name="partner_header">
|
<div t-if="o.move_lines and o.move_lines[0].partner_id" name="partner_header">
|
||||||
<div t-field="o.move_lines[0].partner_id"
|
<div t-field="o.move_lines[0].partner_id"
|
||||||
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True}'/>
|
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True}'/>
|
||||||
</div>
|
</div>
|
||||||
<div t-if="o.partner_id" name="partner_header">
|
<div t-if="not (o.move_lines and o.move_lines[0].partner_id) and o.partner_id" name="partner_header">
|
||||||
<div t-field="o.partner_id"
|
<div t-field="o.partner_id"
|
||||||
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True}'/>
|
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True}'/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ return AbstractRenderer.extend({
|
|||||||
on_attach_callback: function () {
|
on_attach_callback: function () {
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
this.isInDOM = true;
|
this.isInDOM = true;
|
||||||
this._renderGraph();
|
this._render();
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ var ListController = BasicController.extend({
|
|||||||
this.toolbarActions = params.toolbarActions || {};
|
this.toolbarActions = params.toolbarActions || {};
|
||||||
this.editable = params.editable;
|
this.editable = params.editable;
|
||||||
this.noLeaf = params.noLeaf;
|
this.noLeaf = params.noLeaf;
|
||||||
this.selectedRecords = []; // there is no selected record by default
|
this.selectedRecords = params.selectedRecords || [];
|
||||||
},
|
},
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
@@ -177,6 +177,25 @@ var ListController = BasicController.extend({
|
|||||||
this._toggleSidebar();
|
this._toggleSidebar();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Overrides to update the list of selected records
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
update: function (params, options) {
|
||||||
|
var self = this;
|
||||||
|
if (options && options.keepSelection) {
|
||||||
|
// filter out removed records from selection
|
||||||
|
var res_ids = this.model.get(this.handle).res_ids;
|
||||||
|
this.selectedRecords = _.filter(this.selectedRecords, function (id) {
|
||||||
|
return _.contains(res_ids, self.model.get(id).res_id);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.selectedRecords = [];
|
||||||
|
}
|
||||||
|
params.selectedRecords = this.selectedRecords;
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
// Private
|
// Private
|
||||||
@@ -339,7 +358,6 @@ var ListController = BasicController.extend({
|
|||||||
* @returns {Deferred}
|
* @returns {Deferred}
|
||||||
*/
|
*/
|
||||||
_update: function () {
|
_update: function () {
|
||||||
this.selectedRecords = [];
|
|
||||||
this._toggleSidebar();
|
this._toggleSidebar();
|
||||||
return this._super.apply(this, arguments);
|
return this._super.apply(this, arguments);
|
||||||
},
|
},
|
||||||
@@ -544,7 +562,7 @@ var ListController = BasicController.extend({
|
|||||||
_onToggleGroup: function (event) {
|
_onToggleGroup: function (event) {
|
||||||
this.model
|
this.model
|
||||||
.toggleGroup(event.data.group.id)
|
.toggleGroup(event.data.group.id)
|
||||||
.then(this.update.bind(this, {}, {reload: false}));
|
.then(this.update.bind(this, {}, {keepSelection: true, reload: false}));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ var ListRenderer = BasicRenderer.extend({
|
|||||||
return py.parse(py.tokenize(value));
|
return py.parse(py.tokenize(value));
|
||||||
}).value();
|
}).value();
|
||||||
this.hasSelectors = params.hasSelectors;
|
this.hasSelectors = params.hasSelectors;
|
||||||
this.selection = [];
|
this.selection = params.selectedRecords || [];
|
||||||
this.pagers = []; // instantiated pagers (only for grouped lists)
|
this.pagers = []; // instantiated pagers (only for grouped lists)
|
||||||
this.editable = params.editable;
|
this.editable = params.editable;
|
||||||
},
|
},
|
||||||
@@ -81,7 +81,9 @@ var ListRenderer = BasicRenderer.extend({
|
|||||||
*/
|
*/
|
||||||
updateState: function (state, params) {
|
updateState: function (state, params) {
|
||||||
this._processColumns(params.columnInvisibleFields || {});
|
this._processColumns(params.columnInvisibleFields || {});
|
||||||
this.selection = [];
|
if (params.selectedRecords) {
|
||||||
|
this.selection = params.selectedRecords;
|
||||||
|
}
|
||||||
return this._super.apply(this, arguments);
|
return this._super.apply(this, arguments);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ var ListView = BasicView.extend({
|
|||||||
*/
|
*/
|
||||||
init: function (viewInfo, params) {
|
init: function (viewInfo, params) {
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
|
var selectedRecords = []; // there is no selected records by default
|
||||||
|
|
||||||
var mode = this.arch.attrs.editable && !params.readonly ? "edit" : "readonly";
|
var mode = this.arch.attrs.editable && !params.readonly ? "edit" : "readonly";
|
||||||
|
|
||||||
@@ -43,11 +44,13 @@ var ListView = BasicView.extend({
|
|||||||
this.controllerParams.toolbarActions = viewInfo.toolbar;
|
this.controllerParams.toolbarActions = viewInfo.toolbar;
|
||||||
this.controllerParams.noLeaf = !!this.loadParams.context.group_by_no_leaf;
|
this.controllerParams.noLeaf = !!this.loadParams.context.group_by_no_leaf;
|
||||||
this.controllerParams.mode = mode;
|
this.controllerParams.mode = mode;
|
||||||
|
this.controllerParams.selectedRecords = selectedRecords;
|
||||||
|
|
||||||
this.rendererParams.arch = this.arch;
|
this.rendererParams.arch = this.arch;
|
||||||
this.rendererParams.hasSelectors =
|
this.rendererParams.hasSelectors =
|
||||||
'hasSelectors' in params ? params.hasSelectors : true;
|
'hasSelectors' in params ? params.hasSelectors : true;
|
||||||
this.rendererParams.editable = params.readonly ? false : this.arch.attrs.editable;
|
this.rendererParams.editable = params.readonly ? false : this.arch.attrs.editable;
|
||||||
|
this.rendererParams.selectedRecords = selectedRecords;
|
||||||
|
|
||||||
this.loadParams.limit = this.loadParams.limit || 80;
|
this.loadParams.limit = this.loadParams.limit || 80;
|
||||||
this.loadParams.type = 'list';
|
this.loadParams.type = 'list';
|
||||||
|
|||||||
@@ -190,6 +190,25 @@ QUnit.module('Views', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test('no content helper', function (assert) {
|
QUnit.test('no content helper', function (assert) {
|
||||||
|
assert.expect(2);
|
||||||
|
this.data.foo.records = [];
|
||||||
|
|
||||||
|
var graph = createView({
|
||||||
|
View: GraphView,
|
||||||
|
model: "foo",
|
||||||
|
data: this.data,
|
||||||
|
arch: '<graph string="Gloups">' +
|
||||||
|
'<field name="product_id"/>' +
|
||||||
|
'</graph>',
|
||||||
|
});
|
||||||
|
assert.strictEqual(graph.$('div.o_graph_svg_container svg.nvd3-svg').length, 0,
|
||||||
|
"should not contain a div with a svg element");
|
||||||
|
assert.strictEqual(graph.$('div.o_view_nocontent').length, 1,
|
||||||
|
"should display the no content helper");
|
||||||
|
graph.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test('no content helper after update', function (assert) {
|
||||||
var done = assert.async();
|
var done = assert.async();
|
||||||
assert.expect(4);
|
assert.expect(4);
|
||||||
|
|
||||||
|
|||||||
@@ -663,6 +663,45 @@ QUnit.module('Views', {
|
|||||||
list.destroy();
|
list.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test('selection is kept on render without reload', function (assert) {
|
||||||
|
assert.expect(5);
|
||||||
|
|
||||||
|
var list = createView({
|
||||||
|
View: ListView,
|
||||||
|
model: 'foo',
|
||||||
|
data: this.data,
|
||||||
|
groupBy: ['foo'],
|
||||||
|
viewOptions: {hasSidebar: true},
|
||||||
|
arch: '<tree>' +
|
||||||
|
'<field name="foo"/>' +
|
||||||
|
'<field name="int_field" sum="Sum"/>' +
|
||||||
|
'</tree>',
|
||||||
|
});
|
||||||
|
|
||||||
|
// open blip grouping and check all lines
|
||||||
|
list.$('.o_group_header:contains("blip (2)")').click();
|
||||||
|
list.$('.o_data_row input').click();
|
||||||
|
assert.strictEqual(true, list.sidebar.$el.is(':visible'),
|
||||||
|
"element checked so sidebar")
|
||||||
|
|
||||||
|
// open yop grouping and verify blip are still checked
|
||||||
|
list.$('.o_group_header:contains("yop (1)")').click()
|
||||||
|
assert.strictEqual(2, list.$('.o_data_row input:checked').length,
|
||||||
|
"opening a grouping does not uncheck others");
|
||||||
|
assert.strictEqual(true, list.sidebar.$el.is(':visible'),
|
||||||
|
"element checked so sidebar")
|
||||||
|
|
||||||
|
// close and open blip grouping and verify blip are unchecked
|
||||||
|
list.$('.o_group_header:contains("blip (2)")').click();
|
||||||
|
list.$('.o_group_header:contains("blip (2)")').click();
|
||||||
|
assert.strictEqual(0, list.$('.o_data_row input:checked').length,
|
||||||
|
"opening and closing a grouping uncheck its elements");
|
||||||
|
assert.strictEqual(false, list.sidebar.$el.is(':visible'),
|
||||||
|
"no element checked so no sidebar")
|
||||||
|
|
||||||
|
list.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
QUnit.test('aggregates are computed correctly', function (assert) {
|
QUnit.test('aggregates are computed correctly', function (assert) {
|
||||||
assert.expect(4);
|
assert.expect(4);
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,9 @@ class Users(models.Model):
|
|||||||
params['forum_id'] = forum_id
|
params['forum_id'] = forum_id
|
||||||
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')
|
||||||
token_url = base_url + '/forum/validate_email?%s' % urls.url_encode(params)
|
token_url = base_url + '/forum/validate_email?%s' % urls.url_encode(params)
|
||||||
activation_template.sudo().with_context(token_url=token_url).send_mail(self.id, force_send=True)
|
with self._cr.savepoint():
|
||||||
|
activation_template.sudo().with_context(token_url=token_url).send_mail(
|
||||||
|
self.id, force_send=True, raise_exception=True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
|
|||||||
@@ -260,8 +260,8 @@
|
|||||||
|
|
||||||
<t t-esc="question.child_count"/>
|
<t t-esc="question.child_count"/>
|
||||||
<t t-if="question.post_type == 'question'">
|
<t t-if="question.post_type == 'question'">
|
||||||
<t t-if="question.child_count<=1">View</t>
|
<t t-if="question.child_count<=1">Answer</t>
|
||||||
<t t-else="">Views</t>
|
<t t-else="">Answers</t>
|
||||||
</t>
|
</t>
|
||||||
<t t-if="question.post_type != 'question'">
|
<t t-if="question.post_type != 'question'">
|
||||||
<t t-if="question.child_count<=1">Comment</t>
|
<t t-if="question.child_count<=1">Comment</t>
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ declaration.
|
|||||||
|
|
||||||
Signed,
|
Signed,
|
||||||
|
|
||||||
Stijn Houben stijn.houben@dynapps.be https://github.com/stijnh92
|
Pieter Paulussen pieter.paulussen@dynapps.be https://github.com/PieterPaulussen
|
||||||
|
|
||||||
List of contributors:
|
List of contributors:
|
||||||
|
|
||||||
Stijn Houben stijn.houben@dynapps.be https://github.com/stijnh92
|
Pieter Paulussen pieter.paulussen@dynapps.be https://github.com/PieterPaulussen
|
||||||
Raf Ven raf.ven@dynapps.be https://github.com/rven
|
Raf Ven raf.ven@dynapps.be https://github.com/rven
|
||||||
Kurt Schmitz kurt.schmitz@dynapps.be https://github.com/kurt-schmitz
|
Kurt Schmitz kurt.schmitz@dynapps.be https://github.com/kurt-schmitz
|
||||||
Rod Schouteden rod.schouteden@dynapps.be https://github.com/schout-it
|
Rod Schouteden rod.schouteden@dynapps.be https://github.com/schout-it
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
France, 2018-06-20
|
||||||
|
|
||||||
|
JANUS 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,
|
||||||
|
|
||||||
|
Martin Duvergey martin.duvergey@gpsante.fr https://github.com/mduvergey
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
|
||||||
|
Martin Duvergey martin.duvergey@gpsante.fr https://github.com/mduvergey
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
South Africa, 2018-08-20
|
||||||
|
|
||||||
|
Paradigm Digital (Pty) Ltd 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,
|
||||||
|
|
||||||
|
Habib Ayob habib@paradigmdigital.co.za https://github.com/paradigmdigital
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
|
||||||
|
Habib Ayob habib@paradigmdigital.co.za https://github.com/h4818
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
Ukraine, 2018-06-11
|
||||||
|
|
||||||
|
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,
|
||||||
|
|
||||||
|
Paziuk Oleksandr o.paziuk@mobilunity.com https://github.com/oleksandrpaziuk
|
||||||
@@ -202,6 +202,30 @@ class TestORM(TransactionCase):
|
|||||||
group_user.write({'users': [(3, user.id)]})
|
group_user.write({'users': [(3, user.id)]})
|
||||||
self.assertTrue(user.share)
|
self.assertTrue(user.share)
|
||||||
|
|
||||||
|
@mute_logger('odoo.models')
|
||||||
|
def test_unlink_with_property(self):
|
||||||
|
""" Verify that unlink removes the related ir.property as unprivileged user """
|
||||||
|
user = self.env['res.users'].create({
|
||||||
|
'name': 'Justine Bridou',
|
||||||
|
'login': 'saucisson',
|
||||||
|
'groups_id': [4, self.ref('base.group_partner_manager')],
|
||||||
|
})
|
||||||
|
p1 = self.env['res.partner'].sudo(user).create({'name': 'Zorro'})
|
||||||
|
p1_prop = self.env['ir.property'].sudo(user).create({
|
||||||
|
'name': 'Slip en laine',
|
||||||
|
'res_id': 'res.partner,{}'.format(p1.id),
|
||||||
|
'fields_id': self.env['ir.model.fields'].search([
|
||||||
|
('model', '=', 'res.partner'), ('name', '=', 'ref')], limit=1).id,
|
||||||
|
'value_text': 'Nain poilu',
|
||||||
|
'type': 'char',
|
||||||
|
})
|
||||||
|
|
||||||
|
# Unlink with unprivileged user
|
||||||
|
p1.unlink()
|
||||||
|
|
||||||
|
# ir.property is deleted
|
||||||
|
self.assertEqual(
|
||||||
|
p1_prop.exists(), self.env['ir.property'], 'p1_prop should have been deleted')
|
||||||
|
|
||||||
class TestInherits(TransactionCase):
|
class TestInherits(TransactionCase):
|
||||||
""" test the behavior of the orm for models that use _inherits;
|
""" test the behavior of the orm for models that use _inherits;
|
||||||
|
|||||||
+1
-2
@@ -2948,9 +2948,8 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
|
|||||||
|
|
||||||
# Delete the records' properties.
|
# Delete the records' properties.
|
||||||
with self.env.norecompute():
|
with self.env.norecompute():
|
||||||
self.env['ir.property'].search([('res_id', 'in', refs)]).unlink()
|
|
||||||
|
|
||||||
self.check_access_rule('unlink')
|
self.check_access_rule('unlink')
|
||||||
|
self.env['ir.property'].search([('res_id', 'in', refs)]).sudo().unlink()
|
||||||
|
|
||||||
cr = self._cr
|
cr = self._cr
|
||||||
Data = self.env['ir.model.data'].sudo().with_context({})
|
Data = self.env['ir.model.data'].sudo().with_context({})
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class _Cleaner(clean.Cleaner):
|
|||||||
'float', 'vertical-align', 'display',
|
'float', 'vertical-align', 'display',
|
||||||
'padding', 'padding-top', 'padding-left', 'padding-bottom', 'padding-right',
|
'padding', 'padding-top', 'padding-left', 'padding-bottom', 'padding-right',
|
||||||
'margin', 'margin-top', 'margin-left', 'margin-bottom', 'margin-right',
|
'margin', 'margin-top', 'margin-left', 'margin-bottom', 'margin-right',
|
||||||
|
'white-space',
|
||||||
# box model
|
# box model
|
||||||
'border', 'border-color', 'border-radius', 'border-style', 'border-width', 'border-top',
|
'border', 'border-color', 'border-radius', 'border-style', 'border-width', 'border-top',
|
||||||
'height', 'width', 'max-width', 'min-width', 'min-height',
|
'height', 'width', 'max-width', 'min-width', 'min-height',
|
||||||
|
|||||||
Reference in New Issue
Block a user