summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-01-15 17:48:36 +0100
committerChristophe Simonis <chs@odoo.com>2019-01-15 17:48:36 +0100
commit952f78445438f7634d08667a8fa3dd88144695ab (patch)
treec61579c0fbeb07a35805d0ea77936d1c205f0992
parente033114879cfe4a487549c0abbe37ec18bc151c9 (diff)
parent4f2f29953468e4de8aedf73461b0f76dbe1eeca8 (diff)
[MERGE] forward port branch 11.0 up to 4f2f2995346
-rw-r--r--addons/account/models/chart_template.py5
-rw-r--r--addons/mrp/models/mrp_production.py5
-rw-r--r--addons/mrp/models/stock_move.py18
-rw-r--r--addons/mrp/tests/test_order.py46
-rw-r--r--addons/mrp/wizard/change_production_qty.py4
-rw-r--r--addons/purchase/models/res_partner.py31
-rw-r--r--addons/purchase/views/purchase_views.xml2
-rw-r--r--addons/web/static/src/js/views/field_manager_mixin.js6
-rw-r--r--addons/web/static/src/js/views/view_dialogs.js2
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js97
-rw-r--r--addons/website_sale/controllers/main.py2
-rw-r--r--addons/website_slides/views/website_slides_embed.xml2
-rw-r--r--doc/cla/corporate/merchise.md1
-rw-r--r--odoo/addons/base/models/ir_actions.py2
-rw-r--r--odoo/models.py6
-rw-r--r--odoo/modules/module.py6
16 files changed, 212 insertions, 23 deletions
diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py
index bf5550ff2d1..0341b359546 100644
--- a/addons/account/models/chart_template.py
+++ b/addons/account/models/chart_template.py
@@ -142,8 +142,9 @@ class AccountChartTemplate(models.Model):
'bank_account_code_prefix': self.bank_account_code_prefix,
'cash_account_code_prefix': self.cash_account_code_prefix,
})
- wizard.onchange_chart_template_id()
- wizard.execute()
+ if not wizard.existing_accounting(company):
+ wizard.onchange_chart_template_id()
+ wizard.execute()
@api.multi
def open_select_template_wizard(self):
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index 39ebef74e21..5f2c296aeab 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -438,7 +438,10 @@ class MrpProduction(models.Model):
move = self.move_raw_ids.filtered(lambda x: x.bom_line_id.id == bom_line.id and x.state not in ('done', 'cancel'))
if move:
if quantity > 0:
- move[0].write({'product_uom_qty': quantity})
+ move[0]._decrease_reserved_quanity(quantity)
+ move[0].with_context(do_not_unreserve=True).write({'product_uom_qty': quantity})
+ move[0]._recompute_state()
+ move[0]._action_assign()
move.unit_factor = quantity / move.raw_material_production_id.product_qty
elif quantity < 0: # Do not remove 0 lines
if move[0].quantity_done > 0:
diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py
index befe9ceb9d1..5a729839eff 100644
--- a/addons/mrp/models/stock_move.py
+++ b/addons/mrp/models/stock_move.py
@@ -182,6 +182,24 @@ class StockMove(models.Model):
self.sudo().unlink()
return processed_moves
+ def _decrease_reserved_quanity(self, quantity):
+ """ Decrease the reservation on move lines but keeps the
+ all other data.
+ """
+ move_line_to_unlink = self.env['stock.move.line']
+ for move in self:
+ reserved_quantity = quantity
+ for move_line in self.move_line_ids:
+ if move_line.product_uom_qty > reserved_quantity:
+ move_line.product_uom_qty = reserved_quantity
+ else:
+ move_line.product_uom_qty = 0
+ reserved_quantity -= move_line.product_uom_qty
+ if not move_line.product_uom_qty and not move_line.qty_done:
+ move_line_to_unlink |= move_line
+ move_line_to_unlink.unlink()
+ return True
+
def _prepare_phantom_move_values(self, bom_line, quantity):
return {
'picking_id': self.picking_id.id if self.picking_id else False,
diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py
index d8f7f479d58..fe1d3cef097 100644
--- a/addons/mrp/tests/test_order.py
+++ b/addons/mrp/tests/test_order.py
@@ -617,3 +617,49 @@ class TestMrpOrder(TestMrpCommon):
mo.button_mark_done()
self.assertEqual(mo.state, 'done', "Production order should be in done state.")
+
+ def test_product_produce_5(self):
+ """ Build 5 final products with different consumed lots,
+ then edit the finished quantity and update the Manufacturing
+ order quantity. Then check if the produced quantity do not
+ change and it is possible to close the MO.
+ """
+ self.stock_location = self.env.ref('stock.stock_location_stock')
+ mo, bom, p_final, p1, p2 = self.generate_mo(tracking_base_1='lot')
+ self.assertEqual(len(mo), 1, 'MO should have been created')
+
+ lot_1 = self.env['stock.production.lot'].create({
+ 'name': 'lot1',
+ 'product_id': p1.id,
+ })
+ lot_2 = self.env['stock.production.lot'].create({
+ 'name': 'lot2',
+ 'product_id': p1.id,
+ })
+
+ self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 10, lot_id=lot_1)
+ self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 10, lot_id=lot_2)
+
+ self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5)
+ mo.action_assign()
+
+ produce_wizard = self.env['mrp.product.produce'].with_context({
+ 'active_id': mo.id,
+ 'active_ids': [mo.id],
+ }).create({
+ 'product_qty': 5.0,
+ })
+
+ for produce_line in produce_wizard.produce_line_ids:
+ produce_line.qty_done = produce_line.qty_to_consume
+ produce_wizard.do_produce()
+
+ mo.move_finished_ids.move_line_ids.qty_done -= 1
+ update_quantity_wizard = self.env['change.production.qty'].create({
+ 'mo_id': mo.id,
+ 'product_qty': 4,
+ })
+ update_quantity_wizard.change_prod_qty()
+
+ self.assertEqual(mo.move_raw_ids.filtered(lambda m: m.product_id == p1).quantity_done, 20, 'Update the produce quantity should not impact already produced quantity.')
+ mo.button_mark_done()
diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py
index 3ac03e41798..9ee65f0df1d 100644
--- a/addons/mrp/wizard/change_production_qty.py
+++ b/addons/mrp/wizard/change_production_qty.py
@@ -50,7 +50,6 @@ class ChangeProductionQty(models.TransientModel):
qty_produced = production.product_id.uom_id._compute_quantity(sum(done_moves.mapped('product_qty')), production.product_uom_id)
factor = production.product_uom_id._compute_quantity(production.product_qty - qty_produced, production.bom_id.product_uom_id) / production.bom_id.product_qty
boms, lines = production.bom_id.explode(production.product_id, factor, picking_type=production.bom_id.picking_type_id)
- done_quantities = {move: move.quantity_done for move in production.move_raw_ids}
for line, line_data in lines:
production._update_raw_move(line, line_data)
operation_bom_qty = {}
@@ -60,9 +59,6 @@ class ChangeProductionQty(models.TransientModel):
self._update_product_to_produce(production, production.product_qty - qty_produced)
moves = production.move_raw_ids.filtered(lambda x: x.state not in ('done', 'cancel'))
moves._action_assign()
- for move in production.move_raw_ids:
- if move.quantity_done != done_quantities[move]:
- move._set_quantity_done(done_quantities[move])
for wo in production.workorder_ids:
operation = wo.operation_id
if operation_bom_qty.get(operation.id):
diff --git a/addons/purchase/models/res_partner.py b/addons/purchase/models/res_partner.py
index 477c8fa21a3..22a05206571 100644
--- a/addons/purchase/models/res_partner.py
+++ b/addons/purchase/models/res_partner.py
@@ -11,11 +11,32 @@ class res_partner(models.Model):
@api.multi
def _purchase_invoice_count(self):
- PurchaseOrder = self.env['purchase.order']
- Invoice = self.env['account.invoice']
- for partner in self:
- partner.purchase_order_count = PurchaseOrder.search_count([('partner_id', 'child_of', partner.id)])
- partner.supplier_invoice_count = Invoice.search_count([('partner_id', 'child_of', partner.id), ('type', '=', 'in_invoice')])
+ # retrieve all children partners and prefetch 'parent_id' on them
+ all_partners = self.search([('id', 'child_of', self.ids)])
+ all_partners.read(['parent_id'])
+
+ purchase_order_groups = self.env['purchase.order'].read_group(
+ domain=[('partner_id', 'in', all_partners.ids)],
+ fields=['partner_id'], groupby=['partner_id']
+ )
+ for group in purchase_order_groups:
+ partner = self.browse(group['partner_id'][0])
+ while partner:
+ if partner in self:
+ partner.purchase_order_count += group['partner_id_count']
+ partner = partner.parent_id
+
+ supplier_invoice_groups = self.env['account.invoice'].read_group(
+ domain=[('partner_id', 'in', all_partners.ids),
+ ('type', '=', 'in_invoice')],
+ fields=['partner_id'], groupby=['partner_id']
+ )
+ for group in supplier_invoice_groups:
+ partner = self.browse(group['partner_id'][0])
+ while partner:
+ if partner in self:
+ partner.supplier_invoice_count += group['partner_id_count']
+ partner = partner.parent_id
@api.model
def _commercial_fields(self):
diff --git a/addons/purchase/views/purchase_views.xml b/addons/purchase/views/purchase_views.xml
index 66936967998..b5d970fce89 100644
--- a/addons/purchase/views/purchase_views.xml
+++ b/addons/purchase/views/purchase_views.xml
@@ -235,6 +235,7 @@
<field name="taxes_id" widget="many2many_tags" domain="[('type_tax_use', '=', 'purchase')]" options="{'no_create': True}"/>
<field name="date_planned" widget="date"/>
<field name="account_analytic_id" colspan="2" groups="analytic.group_analytic_accounting"/>
+ <field name="analytic_tag_ids" groups="purchase.group_analytic_accounting" widget="many2many_tags" options="{'color_field': 'color'}"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
</group>
</group>
@@ -244,6 +245,7 @@
</page><page string="Invoices and Incoming Shipments">
<field name="invoice_lines"/>
<field name="move_ids"/>
+ <field name="move_dest_ids" invisible="1"/>
</page>
</notebook>
</sheet>
diff --git a/addons/web/static/src/js/views/field_manager_mixin.js b/addons/web/static/src/js/views/field_manager_mixin.js
index 0bc1b5acfce..2b38ca4403d 100644
--- a/addons/web/static/src/js/views/field_manager_mixin.js
+++ b/addons/web/static/src/js/views/field_manager_mixin.js
@@ -130,8 +130,10 @@ var FieldManagerMixin = {
if ('offset' in data) {
params.offset = data.offset;
}
- this.model.reload(data.id, params).then(function (db_id) {
- data.on_success(self.model.get(db_id));
+ this.mutex.exec(function () {
+ return self.model.reload(data.id, params).then(function (db_id) {
+ data.on_success(self.model.get(db_id));
+ });
});
},
/**
diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js
index c0d3ff5dc46..678da06473d 100644
--- a/addons/web/static/src/js/views/view_dialogs.js
+++ b/addons/web/static/src/js/views/view_dialogs.js
@@ -268,7 +268,7 @@ var SelectCreateDialog = ViewDialog.extend({
event.stopPropagation(); // prevent this event from bubbling up to the action manager
var d = event.data;
var searchData = this._process_search_data(d.domains, d.contexts, d.groupbys);
- this.list_controller.reload(searchData);
+ this.list_controller.reload(_.extend({offset: 0}, searchData));
},
get_controller_context: '_onGetControllerContext',
}),
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 5ce43ef2aa5..412cd916ed3 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -525,6 +525,61 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('search more pager is reset when doing a new search', function (assert) {
+ assert.expect(6);
+ for(var i = 10; i < 180; i++) {
+ this.data.partner.records.push({
+ id: i,
+ display_name: "Partner " + i,
+ });
+ }
+
+ function stringToEvent ($element, string) {
+ for (var i = 0; i < string.length; i++) {
+ var keyAscii = string.charCodeAt(i);
+ $element.val($element.val()+string[i]);
+ $element.trigger($.Event('keyup', {which: keyAscii, keyCode:keyAscii}));
+ }
+ $element.trigger($.Event('keyup', {which: $.ui.keyCode.ENTER, keyCode:$.ui.keyCode.ENTER}));
+ }
+
+ this.data.partner.fields.datetime.searchable = true;
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<sheet>' +
+ '<group>' +
+ '<field name="trululu"/>' +
+ '</group>' +
+ '</sheet>' +
+ '</form>',
+ archs: {
+ 'partner,false,list': '<tree><field name="display_name"/></tree>',
+ 'partner,false,search': '<search><field name="datetime"/><field name="display_name"/></search>',
+ },
+ res_id: 1,
+ });
+
+ form.$buttons.find('.o_form_button_edit').click();
+ var $dropdown = form.$('.o_field_many2one input').autocomplete('widget');
+ form.$('.o_field_many2one input').click();
+ $dropdown.find('.o_m2o_dropdown_option:contains(Search)').mouseenter().click();
+ $('.modal .o_pager_next').click();
+
+ assert.strictEqual($('.o_pager_limit').text(), "1173", "there should be 173 records");
+ assert.strictEqual($('.o_pager_value').text(), "181-160", "should display the second page");
+ assert.strictEqual($('tr.o_data_row').length, 80, "should display 80 record");
+
+ stringToEvent($('.modal .o_searchview_input'), 'first');
+
+ assert.strictEqual($('.o_pager_limit').text(), "11", "there should be 1 record");
+ assert.strictEqual($('.o_pager_value').text(), "11-1", "should display the first page");
+ assert.strictEqual($('tr.o_data_row').length, 1, "should display 1 record");
+ form.destroy();
+ });
+
QUnit.test('onchanges on many2ones trigger when editing record in form view', function (assert) {
assert.expect(10);
@@ -10161,6 +10216,48 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('quickly switch between pages in one2many list', function (assert) {
+ assert.expect(2);
+
+ this.data.partner.records[0].turtles = [1, 2, 3];
+
+ var readDefs = [$.when(), $.Deferred(), $.Deferred()];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="turtles">' +
+ '<tree limit="1">' +
+ '<field name="display_name"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ mockRPC: function (route, args) {
+ var result = this._super.apply(this, arguments);
+ if (args.method === 'read') {
+ var recordID = args.args[0][0];
+ return $.when(readDefs[recordID - 1]).then(_.constant(result));
+ }
+ return result;
+ },
+ res_id: 1,
+ });
+
+ form.$('.o_field_widget[name=turtles] .o_pager_next').click();
+ form.$('.o_field_widget[name=turtles] .o_pager_next').click();
+
+ readDefs[1].resolve();
+
+ assert.strictEqual(form.$('.o_field_widget[name=turtles] .o_data_cell').text(), 'donatello');
+
+ readDefs[2].resolve();
+
+ assert.strictEqual(form.$('.o_field_widget[name=turtles] .o_data_cell').text(), 'raphael');
+
+ form.destroy();
+ });
+
QUnit.module('FieldMany2Many');
QUnit.test('many2many kanban: edition', function (assert) {
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index c44596a2885..5352704b7e4 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -543,7 +543,7 @@ class WebsiteSale(http.Controller):
except ValidationError:
error["vat"] = 'error'
- if [err for err in error.items() if err == 'missing']:
+ if [err for err in error.values() if err == 'missing']:
error_message.append(_('Some required fields are empty.'))
return error, error_message
diff --git a/addons/website_slides/views/website_slides_embed.xml b/addons/website_slides/views/website_slides_embed.xml
index 72614803325..c7010c22e60 100644
--- a/addons/website_slides/views/website_slides_embed.xml
+++ b/addons/website_slides/views/website_slides_embed.xml
@@ -133,7 +133,7 @@
<div class="col-lg-4 col-md-4 col-sm-4 col-xs-4 text-center"></div>
</div>
<div class="col-lg-2 col-md-2 col-sm-2 col-xs-2 pull-right text-right">
- <a t-if="slide.download_security and not is_embedded" target="_top" t-attf-href="/slides/slide/#{slug(slide)}/download" title="Download" id="download">
+ <a t-if="slide.download_security != 'none' and not is_embedded" target="_top" t-attf-href="/slides/slide/#{slug(slide)}/download" title="Download" id="download">
<i class="fa fa-download fa-2x"></i>
</a>
<span id="fullscreen" title="View fullscreen">
diff --git a/doc/cla/corporate/merchise.md b/doc/cla/corporate/merchise.md
index e4cbd3c3f95..64d5b277ccd 100644
--- a/doc/cla/corporate/merchise.md
+++ b/doc/cla/corporate/merchise.md
@@ -21,3 +21,4 @@ Medardo Rodríguez med@merchise.org https://github.com/med-merchise
Abel Firvida firvida@merchise.org
Abel Firvida abel@merchise.org
Mónica Díaz Pena monicadp@merchise.org https://github.com/mdpena
+Larisa González larisa@merchise.org
diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py
index f500909d2e1..656e90aaf0e 100644
--- a/odoo/addons/base/models/ir_actions.py
+++ b/odoo/addons/base/models/ir_actions.py
@@ -388,7 +388,7 @@ class IrActionsServer(models.Model):
# Create
crud_model_id = fields.Many2one('ir.model', string='Create/Write Target Model',
oldname='srcmodel_id', help="Model for record creation / update. Set this field only to specify a different model than the base model.")
- crud_model_name = fields.Char(related='crud_model_id.name', readonly=True)
+ crud_model_name = fields.Char(related='crud_model_id.model', readonly=True)
link_field_id = fields.Many2one('ir.model.fields', string='Link using field',
help="Provide the field used to link the newly created record "
"on the record on used by the server action.")
diff --git a/odoo/models.py b/odoo/models.py
index 8a03dc2d17c..8615b0c20ff 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -2610,14 +2610,16 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
# retrieve results from records; this takes values from the cache and
# computes remaining fields
+ self = self.with_prefetch(self._prefetch.copy())
data = {record: {'id': record.id} for record in self}
missing = set()
use_name_get = (load == '_classic_read')
for name in (stored + inherited + computed):
convert = self._fields[name].convert_to_read
- # read every field with prefetching limited to self; this avoids
+ # restrict the prefetching of self's model to self; this avoids
# computing fields on a larger recordset than self
- for record in self.with_prefetch():
+ self._prefetch[self._name] = set(self._ids)
+ for record in self:
try:
data[record][name] = convert(record[name], record, use_name_get)
except MissingError:
diff --git a/odoo/modules/module.py b/odoo/modules/module.py
index d92e2fbf6d6..214c3642331 100644
--- a/odoo/modules/module.py
+++ b/odoo/modules/module.py
@@ -128,17 +128,17 @@ def initialize_sys_path():
global ad_paths
global hooked
- dd = tools.config.addons_data_dir
+ dd = os.path.normcase(tools.config.addons_data_dir)
if os.access(dd, os.R_OK) and dd not in ad_paths:
ad_paths.append(dd)
for ad in tools.config['addons_path'].split(','):
- ad = os.path.abspath(tools.ustr(ad.strip()))
+ ad = os.path.normcase(os.path.abspath(tools.ustr(ad.strip())))
if ad not in ad_paths:
ad_paths.append(ad)
# add base module path
- base_path = os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons'))
+ base_path = os.path.normcase(os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons')))
if base_path not in ad_paths and os.path.isdir(base_path):
ad_paths.append(base_path)