summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-08-21 16:58:17 +0200
committerChristophe Simonis <chs@odoo.com>2018-08-21 16:58:17 +0200
commit70d0148d0da1ccf3727310d8e7c53e2c518a8d6a (patch)
tree00c4884eca26b424989619b778d6ce9216e9493b /addons
parentc8762eaa03cc9dda4c7318251726af1b83a2c10f (diff)
parentfbfb91799eecc5297ab6c86b657bf213343f6cd8 (diff)
[MERGE] forward port branch 11.0 up to fbfb91799ee
Diffstat (limited to 'addons')
-rw-r--r--addons/account/views/report_invoice.xml12
-rw-r--r--addons/base_sparse_field/models/models.py2
-rw-r--r--addons/base_sparse_field/views/views.xml2
-rw-r--r--addons/calendar/models/calendar.py6
-rw-r--r--addons/crm/models/crm_lead.py2
-rw-r--r--addons/document/static/src/js/document.js33
-rw-r--r--addons/document/static/tests/document_tests.js33
-rw-r--r--addons/gamification/models/badge.py8
-rw-r--r--addons/hr_maintenance/models/equipment.py11
-rw-r--r--addons/hr_timesheet/views/project_views.xml2
-rw-r--r--addons/l10n_be_invoice_bba/views/account_invoice_view.xml11
-rw-r--r--addons/l10n_ch/data/account_vat2011_data.xml33
-rw-r--r--addons/l10n_co/data/account.account.template.csv1
-rw-r--r--addons/l10n_fr_hr_payroll/report/report_l10n_fr_fiche_paye.xml2
-rw-r--r--addons/membership/models/account_invoice.py9
-rw-r--r--addons/membership/models/partner.py15
-rw-r--r--addons/mrp/tests/test_order.py39
-rw-r--r--addons/mrp/wizard/mrp_product_produce.py38
-rw-r--r--addons/point_of_sale/models/pos_order.py29
-rw-r--r--addons/purchase/models/purchase.py2
-rw-r--r--addons/sale_stock/models/stock.py9
-rw-r--r--addons/sales_team/models/crm_team.py11
-rw-r--r--addons/stock/models/stock_move.py5
-rw-r--r--addons/stock/models/stock_scrap.py2
-rw-r--r--addons/stock/report/report_deliveryslip.xml4
-rw-r--r--addons/web/static/src/js/views/list/list_controller.js24
-rw-r--r--addons/web/static/src/js/views/list/list_renderer.js6
-rw-r--r--addons/web/static/src/js/views/list/list_view.js3
-rw-r--r--addons/web/static/tests/views/list_tests.js39
-rw-r--r--addons/website_forum/models/res_users.py4
30 files changed, 335 insertions, 62 deletions
diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml
index 5acb2ffcb9f..018f95380ff 100644
--- a/addons/account/views/report_invoice.xml
+++ b/addons/account/views/report_invoice.xml
@@ -112,6 +112,12 @@
<t t-foreach="o._get_tax_amount_by_group()" t-as="amount_by_group">
<tr style="border-bottom:1px solid #dddddd;">
<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>
<span t-esc="amount_by_group[0]"/>
<span>&amp;nbsp;<span>on</span>
@@ -122,12 +128,6 @@
<span t-esc="amount_by_group[3]"/>
</td>
</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>
</t>
<tr class="border-black">
diff --git a/addons/base_sparse_field/models/models.py b/addons/base_sparse_field/models/models.py
index 77ea36a6bd8..703c6466ee9 100644
--- a/addons/base_sparse_field/models/models.py
+++ b/addons/base_sparse_field/models/models.py
@@ -9,7 +9,7 @@ class IrModelFields(models.Model):
ttype = fields.Selection(selection_add=[('serialized', 'serialized')])
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 "
"serialization field, instead of having its own database column. "
"This cannot be changed after creation.",
diff --git a/addons/base_sparse_field/views/views.xml b/addons/base_sparse_field/views/views.xml
index 95c3d23634a..eaf684247af 100644
--- a/addons/base_sparse_field/views/views.xml
+++ b/addons/base_sparse_field/views/views.xml
@@ -22,7 +22,7 @@
<field name="arch" type="xml">
<field name="related" position="before">
<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')]}"/>
</field>
</field>
diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py
index c91f7033b67..8d95dada86c 100644
--- a/addons/calendar/models/calendar.py
+++ b/addons/calendar/models/calendar.py
@@ -947,11 +947,13 @@ class Meeting(models.Model):
@api.onchange('start_date')
def _onchange_start_date(self):
- self.start = self.start_date
+ if self.start_date:
+ self.start = self.start_date
@api.onchange('stop_date')
def _onchange_stop_date(self):
- self.stop = self.stop_date
+ if self.stop_date:
+ self.stop = self.stop_date
####################################################
# Calendar Business, Reccurency, ...
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index c3cd70392c3..cf112b817c0 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -702,7 +702,7 @@ class Lead(models.Model):
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 """
if not email:
- return []
+ return self.env['crm.lead']
partner_match_domain = []
for email in set(email_split(email) + [email]):
partner_match_domain.append(('email_from', '=ilike', email))
diff --git a/addons/document/static/src/js/document.js b/addons/document/static/src/js/document.js
index 0af85179b49..98bfe4e5ef3 100644
--- a/addons/document/static/src/js/document.js
+++ b/addons/document/static/src/js/document.js
@@ -15,12 +15,13 @@ Sidebar.include({
*/
init : function (parent, options) {
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.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
@@ -29,13 +30,16 @@ Sidebar.include({
*/
start: function () {
var _super = this._super.bind(this);
- this._updateAttachments().then(_super);
+ var def = this.hasAttachments ? this._updateAttachments() : $.when();
+ return def.then(_super);
},
/**
* @override
*/
destroy: function () {
- $(window).off(this.fileuploadId);
+ if (this.hasAttachments) {
+ $(window).off(this.fileuploadId);
+ }
this._super.apply(this, arguments);
},
@@ -46,8 +50,10 @@ Sidebar.include({
* @override
*/
updateEnv: function (env) {
- this.env = env;
- this._updateAttachments().then(this._redraw.bind(this));
+ if (this.hasAttachments) {
+ this.env = env;
+ this._updateAttachments().then(this._redraw.bind(this));
+ }
},
//--------------------------------------------------------------------------
@@ -90,10 +96,12 @@ Sidebar.include({
*/
_redraw: function () {
this._super.apply(this, arguments);
- this.$('.o_sidebar_add_attachment .o_form_binary_form')
- .change(this._onAddAttachment.bind(this));
- this.$('.o_sidebar_delete_attachment')
- .click(this._onDeleteAttachment.bind(this));
+ if (this.hasAttachments) {
+ this.$('.o_sidebar_add_attachment .o_form_binary_form')
+ .change(this._onAddAttachment.bind(this));
+ this.$('.o_sidebar_delete_attachment')
+ .click(this._onDeleteAttachment.bind(this));
+ }
},
/**
* Update the attachments to be displayed in the attachment section
@@ -102,6 +110,9 @@ Sidebar.include({
* @private
*/
_updateAttachments: function () {
+ if (this.items.files === undefined) {
+ return $.when();
+ }
var activeId = this.env.activeIds[0];
if (!activeId) {
this.items.files = [];
diff --git a/addons/document/static/tests/document_tests.js b/addons/document/static/tests/document_tests.js
index 338f3e75581..96c699c041b 100644
--- a/addons/document/static/tests/document_tests.js
+++ b/addons/document/static/tests/document_tests.js
@@ -3,6 +3,7 @@ odoo.define('document.tests', function (require) {
var testUtils = require('web.test_utils');
var FormView = require('web.FormView');
+ var ListView = require('web.ListView');
var createView = testUtils.createView;
@@ -16,6 +17,9 @@ odoo.define('document.tests', function (require) {
records: [{
id: 1,
display_name: "first record",
+ }, {
+ id: 2,
+ display_name: "second record",
}]
},
'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");
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();
+ });
});
});
diff --git a/addons/gamification/models/badge.py b/addons/gamification/models/badge.py
index 3ca11577c33..0fd69631650 100644
--- a/addons/gamification/models/badge.py
+++ b/addons/gamification/models/badge.py
@@ -161,13 +161,13 @@ class GamificationBadge(models.Model):
for badge in self:
owners = badge.owner_ids
- badge.stats_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.stats_my_this_month = sum(
+ badge.stat_my = sum(o.user_id == self.env.user for o in owners)
+ badge.stat_this_month = sum(o.create_date >= first_month_day for o in owners)
+ badge.stat_my_this_month = sum(
o.user_id == self.env.user and o.create_date >= first_month_day
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
for o in owners
)
diff --git a/addons/hr_maintenance/models/equipment.py b/addons/hr_maintenance/models/equipment.py
index ebfa6eb03fe..d66f29fdfc6 100644
--- a/addons/hr_maintenance/models/equipment.py
+++ b/addons/hr_maintenance/models/equipment.py
@@ -9,7 +9,7 @@ class MaintenanceEquipment(models.Model):
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')
equipment_assign_to = fields.Selection(
- [('department', 'Department'), ('employee', 'Employee') ,('other', 'Other')],
+ [('department', 'Department'), ('employee', 'Employee'), ('other', 'Other')],
string='Used By',
required=True,
default='employee')
@@ -82,10 +82,11 @@ class MaintenanceRequest(models.Model):
@api.depends('employee_id', 'department_id')
def _compute_owner(self):
- if self.equipment_id.equipment_assign_to == 'employee':
- self.owner_user_id = self.employee_id.user_id.id
- elif self.equipment_id.equipment_assign_to == 'department':
- self.owner_user_id = self.department_id.manager_id.user_id.id
+ for r in self:
+ if r.equipment_id.equipment_assign_to == 'employee':
+ r.owner_user_id = r.employee_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')
def onchange_department_or_employee_id(self):
diff --git a/addons/hr_timesheet/views/project_views.xml b/addons/hr_timesheet/views/project_views.xml
index 619878cf459..75f4f42ece8 100644
--- a/addons/hr_timesheet/views/project_views.xml
+++ b/addons/hr_timesheet/views/project_views.xml
@@ -111,7 +111,7 @@
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<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="effective_hours" widget="float_time" sum="Spent Hours" invisible="1"/>
</field>
diff --git a/addons/l10n_be_invoice_bba/views/account_invoice_view.xml b/addons/l10n_be_invoice_bba/views/account_invoice_view.xml
index 9ab348940a1..ee3e8470e93 100644
--- a/addons/l10n_be_invoice_bba/views/account_invoice_view.xml
+++ b/addons/l10n_be_invoice_bba/views/account_invoice_view.xml
@@ -12,4 +12,15 @@
</field>
</field>
</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>
diff --git a/addons/l10n_ch/data/account_vat2011_data.xml b/addons/l10n_ch/data/account_vat2011_data.xml
index 3dd350761ba..236ba2fb215 100644
--- a/addons/l10n_ch/data/account_vat2011_data.xml
+++ b/addons/l10n_ch/data/account_vat2011_data.xml
@@ -44,6 +44,10 @@
<field name="name">Switzerland VAT Form: grid 381 base</field>
<field name="applicability">taxes</field>
</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">
<field name="name">Switzerland VAT Form: grid 302 tax</field>
<field name="applicability">taxes</field>
@@ -60,6 +64,10 @@
<field name="name">Switzerland VAT Form: grid 381 tax</field>
<field name="applicability">taxes</field>
</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">
<field name="name">Switzerland VAT Form: grid 400</field>
<field name="applicability">taxes</field>
@@ -82,7 +90,7 @@
</record>
<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>
</record>
@@ -320,6 +328,18 @@
<field name="tag_ids" eval="[(6,0,[ref('vat_tag_405')])]"/>
<field name="tax_group_id" ref="tax_group_tva_77"/>
</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">
<field name="name">TVA due a 0% (Exportations)</field>
<field name="amount" eval="0.00"/>
@@ -368,4 +388,15 @@
<field name="tag_ids" eval="[(6,0,[ref('vat_tag_dedouanement')])]"/>
<field name="tax_group_id" ref="tax_group_tva_0"/>
</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>
diff --git a/addons/l10n_co/data/account.account.template.csv b/addons/l10n_co/data/account.account.template.csv
index e73728e96cf..b68f925a074 100644
--- a/addons/l10n_co/data/account.account.template.csv
+++ b/addons/l10n_co/data/account.account.template.csv
@@ -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_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_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_421020,421020,Diferencia en Cambio,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
diff --git a/addons/l10n_fr_hr_payroll/report/report_l10n_fr_fiche_paye.xml b/addons/l10n_fr_hr_payroll/report/report_l10n_fr_fiche_paye.xml
index c9fcc8ae3fd..4f6e38bee9d 100644
--- a/addons/l10n_fr_hr_payroll/report/report_l10n_fr_fiche_paye.xml
+++ b/addons/l10n_fr_hr_payroll/report/report_l10n_fr_fiche_paye.xml
@@ -12,7 +12,7 @@
<h4>Paie du <span t-field="o.date_from"/> au <span t-field="o.date_to"/></h4>
</div>
<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>
<td>
<div t-field="o.employee_id.company_id.partner_id"
diff --git a/addons/membership/models/account_invoice.py b/addons/membership/models/account_invoice.py
index 85b158e4959..90423ab571f 100644
--- a/addons/membership/models/account_invoice.py
+++ b/addons/membership/models/account_invoice.py
@@ -22,6 +22,15 @@ class Invoice(models.Model):
]).write({'date_cancel': fields.Date.today()})
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):
_inherit = 'account.invoice.line'
diff --git a/addons/membership/models/partner.py b/addons/membership/models/partner.py
index 6b66bfb07b5..a5ec46f701f 100644
--- a/addons/membership/models/partner.py
+++ b/addons/membership/models/partner.py
@@ -39,6 +39,8 @@ class Partner(models.Model):
@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.payment_ids',
+ 'member_lines.account_invoice_line.invoice_id.payment_move_line_ids',
+ 'member_lines.account_invoice_line.invoice_id.partner_id',
'free_member',
'member_lines.date_to', 'member_lines.date_from',
'associate_member.membership_state')
@@ -104,18 +106,22 @@ class Partner(models.Model):
if partner.membership_stop and today > partner.membership_stop:
res[partner.id] = 'free' if partner.free_member else 'old'
continue
+ if partner.associate_member:
+ res_state = partner.associate_member._membership_state()
+ res[partner.id] = res_state[partner.associate_member.id]
+ continue
s = 4
if 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.account_invoice_line.invoice_id:
+ if mline.account_invoice_line.invoice_id.partner_id == partner:
mstate = mline.account_invoice_line.invoice_id.state
if mstate == 'paid':
s = 0
inv = mline.account_invoice_line.invoice_id
- for payment in inv.payment_ids:
- if any(payment.invoice_ids.filtered(lambda inv: inv.type == 'out_refund')):
+ for ml in inv.payment_move_line_ids:
+ if any(ml.invoice_id.filtered(lambda inv: inv.type == 'out_refund')):
s = 2
break
elif mstate == 'open' and s != 0:
@@ -144,9 +150,6 @@ class Partner(models.Model):
res[partner.id] = 'none'
if partner.free_member and s != 0:
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
@api.one
diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py
index 6c56fa22275..d8f7f479d58 100644
--- a/addons/mrp/tests/test_order.py
+++ b/addons/mrp/tests/test_order.py
@@ -578,3 +578,42 @@ class TestMrpOrder(TestMrpCommon):
mo.button_mark_done()
self.assertEqual(mo.state, 'done', "Production order should be in done state.")
+
+ def test_product_produce_4(self):
+ """ Possibility to produce with a given raw material in multiple locations. """
+ self.stock_location = self.env.ref('stock.stock_location_stock')
+ self.stock_shelf_1 = self.env.ref('stock.stock_location_components')
+ self.stock_shelf_2 = self.env.ref('stock.stock_location_14')
+ mo, _, p_final, p1, p2 = self.generate_mo(qty_final=1, qty_base_1=5)
+
+ self.env['stock.quant']._update_available_quantity(p1, self.stock_shelf_1, 2)
+ self.env['stock.quant']._update_available_quantity(p1, self.stock_shelf_2, 3)
+ self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 1)
+
+ mo.action_assign()
+ ml_p1 = mo.move_raw_ids.filtered(lambda x: x.product_id == p1).mapped('move_line_ids')
+ ml_p2 = mo.move_raw_ids.filtered(lambda x: x.product_id == p2).mapped('move_line_ids')
+ self.assertEqual(len(ml_p1), 2)
+ self.assertEqual(len(ml_p2), 1)
+
+ # Add some quantity already done to force an extra move line to be created
+ ml_p1[0].qty_done = 1.0
+
+ # Produce baby!
+ product_produce = self.env['mrp.product.produce'].with_context({
+ 'active_id': mo.id,
+ 'active_ids': [mo.id],
+ }).create({
+ 'product_qty': 1.0,
+ })
+ product_produce.do_produce()
+
+ ml_p1 = mo.move_raw_ids.filtered(lambda x: x.product_id == p1).mapped('move_line_ids')
+ self.assertEqual(len(ml_p1), 3)
+ for ml in ml_p1:
+ self.assertIn(ml.qty_done, [1.0, 2.0, 3.0], 'Quantity done should be 1.0, 2.0 or 3.0')
+ self.assertEqual(sum(ml_p1.mapped('qty_done')), 6.0, 'Total qty consumed should be 6.0')
+ self.assertEqual(sum(ml_p1.mapped('product_uom_qty')), 5.0, 'Total qty reserved should be 5.0')
+
+ mo.button_mark_done()
+ self.assertEqual(mo.state, 'done', "Production order should be in done state.")
diff --git a/addons/mrp/wizard/mrp_product_produce.py b/addons/mrp/wizard/mrp_product_produce.py
index 57bffd5d2fe..37e85825cdd 100644
--- a/addons/mrp/wizard/mrp_product_produce.py
+++ b/addons/mrp/wizard/mrp_product_produce.py
@@ -100,8 +100,10 @@ class MrpProductProduce(models.TransientModel):
if self.product_id.tracking != 'none':
qty_to_add = float_round(quantity * move.unit_factor, precision_rounding=rounding)
move._generate_consumed_move_line(qty_to_add, self.lot_id)
- else:
+ elif len(move._get_move_lines()) < 2:
move.quantity_done += float_round(quantity * move.unit_factor, precision_rounding=rounding)
+ else:
+ self._set_quantity_done(move, quantity * move.unit_factor)
for move in self.production_id.move_finished_ids:
if move.product_id.tracking == 'none' and move.state not in ('done', 'cancel'):
rounding = move.product_uom.rounding
@@ -118,6 +120,40 @@ class MrpProductProduce(models.TransientModel):
})
return {'type': 'ir.actions.act_window_close'}
+ def _set_quantity_done(self, move, qty):
+ """
+ Set the given quantity as quantity done on the move through the move lines. The method is
+ able to handle move lines with a different UoM than the move (but honestly, this would be
+ looking for trouble...).
+ @param move: stock.move
+ @param qty: quantity in the UoM of move.product_uom
+ """
+ for ml in move.move_line_ids:
+ # Convert move line qty into move uom
+ ml_qty = ml.product_uom_qty - ml.qty_done
+ if ml.product_uom_id != move.product_uom:
+ ml_qty = ml.product_uom_id._compute_quantity(ml_qty, move.product_uom, round=False)
+
+ taken_qty = min(qty, ml_qty)
+ # Convert taken qty into move line uom
+ if ml.product_uom_id != move.product_uom:
+ taken_qty = move.product_uom._compute_quantity(ml_qty, ml.product_uom_id, round=False)
+
+ # Assign qty_done and explicitly round to make sure there is no inconsistency between
+ # ml.qty_done and qty.
+ taken_qty = float_round(taken_qty, precision_rounding=ml.product_uom_id.rounding)
+ ml.qty_done += taken_qty
+ if ml.product_uom_id != move.product_uom:
+ taken_qty = ml.product_uom_id._compute_quantity(ml_qty, move.product_uom, round=False)
+ qty -= taken_qty
+
+ if float_compare(qty, 0.0, precision_rounding=move.product_uom.rounding) <= 0:
+ break
+ if float_compare(qty, 0.0, precision_rounding=move.product_uom.rounding) > 0:
+ vals = move._prepare_move_line_vals(quantity=0)
+ vals['qty_done'] = qty
+ ml = self.env['stock.move.line'].create(vals)
+
@api.multi
def check_finished_move_lots(self):
produce_move = self.production_id.move_finished_ids.filtered(lambda x: x.product_id == self.product_id and x.state not in ('done', 'cancel'))
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index ece3fc30a60..56d8500a3cd 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -180,7 +180,7 @@ class PosOrder(models.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
move lines in _create_account_move_line method.
@@ -195,10 +195,16 @@ class PosOrder(models.Model):
values['analytic_account_id'],
values['debit'] > 0)
elif data_type == 'tax':
- return ('tax',
- values['partner_id'],
- values['tax_line_id'],
- values['debit'] > 0)
+ order_id = values.pop('order_id', False)
+ tax_key = ('tax',
+ values['partner_id'],
+ 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':
return ('counter_part',
values['partner_id'],
@@ -308,7 +314,7 @@ class PosOrder(models.Model):
'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:
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['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)
+ 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:
grouped_data[key].append(values)
@@ -381,7 +395,8 @@ class PosOrder(models.Model):
'credit': ((tax['amount'] > 0) and tax['amount']) or 0.0,
'debit': ((tax['amount'] < 0) and -tax['amount']) or 0.0,
'tax_line_id': tax['id'],
- 'partner_id': partner_id
+ 'partner_id': partner_id,
+ 'order_id': order.id
})
# round tax lines per order
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index a23b478a61f..7984cd13c8f 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -480,7 +480,7 @@ class PurchaseOrder(models.Model):
'sequence': max(line.product_id.seller_ids.mapped('sequence')) + 1 if line.product_id.seller_ids else 1,
'product_uom': line.product_uom.id,
'min_qty': 0.0,
- 'price': self.currency_id.compute(line.price_unit, currency),
+ 'price': self.currency_id.compute(line.price_unit, currency, round=False),
'currency_id': currency.id,
'delay': 0,
}
diff --git a/addons/sale_stock/models/stock.py b/addons/sale_stock/models/stock.py
index eee0424021c..2a7f9e5560e 100644
--- a/addons/sale_stock/models/stock.py
+++ b/addons/sale_stock/models/stock.py
@@ -36,6 +36,15 @@ class StockMove(models.Model):
#rslt += invoices.mapped('refund_invoice_ids')
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):
_inherit = 'procurement.group'
diff --git a/addons/sales_team/models/crm_team.py b/addons/sales_team/models/crm_team.py
index f3708fc5b10..180e2879cdd 100644
--- a/addons/sales_team/models/crm_team.py
+++ b/addons/sales_team/models/crm_team.py
@@ -8,7 +8,7 @@ from dateutil.relativedelta import relativedelta
import json
from odoo import api, fields, models, _
-from odoo.exceptions import UserError
+from odoo.exceptions import AccessError, UserError
from odoo.release import version
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'))
if not team_id:
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):
- team_id = default_team_id
+ if 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
def _get_default_favorite_user_ids(self):
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index 88aede3ecf3..7f190e8d08a 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -716,7 +716,7 @@ class StockMove(models.Model):
recompute = True
picking = Picking.create(move._get_new_picking_values())
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
# 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
@@ -726,6 +726,9 @@ class StockMove(models.Model):
move.recompute()
return True
+ def _assign_picking_post_process(self, new=False):
+ pass
+
def _get_new_picking_values(self):
""" Prepares a new picking for this move as it could not be assigned to
another picking. This method is designed to be inherited. """
diff --git a/addons/stock/models/stock_scrap.py b/addons/stock/models/stock_scrap.py
index bb985547ede..80fc6e0eabb 100644
--- a/addons/stock/models/stock_scrap.py
+++ b/addons/stock/models/stock_scrap.py
@@ -121,6 +121,8 @@ class StockScrap(models.Model):
def action_validate(self):
self.ensure_one()
+ if self.product_id.type != 'product':
+ return self.do_scrap()
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
available_qty = sum(self.env['stock.quant']._gather(self.product_id,
self.location_id,
diff --git a/addons/stock/report/report_deliveryslip.xml b/addons/stock/report/report_deliveryslip.xml
index 50289bee840..a5a9d7d0fe5 100644
--- a/addons/stock/report/report_deliveryslip.xml
+++ b/addons/stock/report/report_deliveryslip.xml
@@ -11,11 +11,11 @@
<div>
<span><strong>Customer Address:</strong></span>
</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"
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True}'/>
</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"
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True}'/>
</div>
diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js
index 3262196fa51..4629e57eb7f 100644
--- a/addons/web/static/src/js/views/list/list_controller.js
+++ b/addons/web/static/src/js/views/list/list_controller.js
@@ -42,7 +42,7 @@ var ListController = BasicController.extend({
this.toolbarActions = params.toolbarActions || {};
this.editable = params.editable;
this.noLeaf = params.noLeaf;
- this.selectedRecords = []; // there is no selected record by default
+ this.selectedRecords = params.selectedRecords || [];
},
//--------------------------------------------------------------------------
@@ -171,6 +171,25 @@ var ListController = BasicController.extend({
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
@@ -333,7 +352,6 @@ var ListController = BasicController.extend({
* @returns {Deferred}
*/
_update: function () {
- this.selectedRecords = [];
this._toggleSidebar();
return this._super.apply(this, arguments);
},
@@ -538,7 +556,7 @@ var ListController = BasicController.extend({
_onToggleGroup: function (event) {
this.model
.toggleGroup(event.data.group.id)
- .then(this.update.bind(this, {}, {reload: false}));
+ .then(this.update.bind(this, {}, {keepSelection: true, reload: false}));
},
});
diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js
index 31919962d56..a72cd7008eb 100644
--- a/addons/web/static/src/js/views/list/list_renderer.js
+++ b/addons/web/static/src/js/views/list/list_renderer.js
@@ -60,7 +60,7 @@ var ListRenderer = BasicRenderer.extend({
return py.parse(py.tokenize(value));
}).value();
this.hasSelectors = params.hasSelectors;
- this.selection = [];
+ this.selection = params.selectedRecords || [];
this.pagers = []; // instantiated pagers (only for grouped lists)
this.editable = params.editable;
},
@@ -81,7 +81,9 @@ var ListRenderer = BasicRenderer.extend({
*/
updateState: function (state, params) {
this._processColumns(params.columnInvisibleFields || {});
- this.selection = [];
+ if (params.selectedRecords) {
+ this.selection = params.selectedRecords;
+ }
return this._super.apply(this, arguments);
},
diff --git a/addons/web/static/src/js/views/list/list_view.js b/addons/web/static/src/js/views/list/list_view.js
index 3082312e770..bc80017743b 100644
--- a/addons/web/static/src/js/views/list/list_view.js
+++ b/addons/web/static/src/js/views/list/list_view.js
@@ -35,6 +35,7 @@ var ListView = BasicView.extend({
*/
init: function (viewInfo, params) {
this._super.apply(this, arguments);
+ var selectedRecords = []; // there is no selected records by default
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.noLeaf = !!this.loadParams.context.group_by_no_leaf;
this.controllerParams.mode = mode;
+ this.controllerParams.selectedRecords = selectedRecords;
this.rendererParams.arch = this.arch;
this.rendererParams.hasSelectors =
'hasSelectors' in params ? params.hasSelectors : true;
this.rendererParams.editable = params.readonly ? false : this.arch.attrs.editable;
+ this.rendererParams.selectedRecords = selectedRecords;
this.loadParams.limit = this.loadParams.limit || 80;
this.loadParams.type = 'list';
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index 31c89cb0a32..dd73814336f 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -618,6 +618,45 @@ QUnit.module('Views', {
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: {sidebar: 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) {
assert.expect(4);
diff --git a/addons/website_forum/models/res_users.py b/addons/website_forum/models/res_users.py
index 054e3a4d56a..77cbcd7d9e5 100644
--- a/addons/website_forum/models/res_users.py
+++ b/addons/website_forum/models/res_users.py
@@ -88,7 +88,9 @@ class Users(models.Model):
params['forum_id'] = forum_id
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)
- 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
@api.one