diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-01-15 17:48:36 +0100 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-01-15 17:48:36 +0100 |
| commit | 952f78445438f7634d08667a8fa3dd88144695ab (patch) | |
| tree | c61579c0fbeb07a35805d0ea77936d1c205f0992 | |
| parent | e033114879cfe4a487549c0abbe37ec18bc151c9 (diff) | |
| parent | 4f2f29953468e4de8aedf73461b0f76dbe1eeca8 (diff) | |
[MERGE] forward port branch 11.0 up to 4f2f2995346
| -rw-r--r-- | addons/account/models/chart_template.py | 5 | ||||
| -rw-r--r-- | addons/mrp/models/mrp_production.py | 5 | ||||
| -rw-r--r-- | addons/mrp/models/stock_move.py | 18 | ||||
| -rw-r--r-- | addons/mrp/tests/test_order.py | 46 | ||||
| -rw-r--r-- | addons/mrp/wizard/change_production_qty.py | 4 | ||||
| -rw-r--r-- | addons/purchase/models/res_partner.py | 31 | ||||
| -rw-r--r-- | addons/purchase/views/purchase_views.xml | 2 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/field_manager_mixin.js | 6 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/view_dialogs.js | 2 | ||||
| -rw-r--r-- | addons/web/static/tests/fields/relational_fields_tests.js | 97 | ||||
| -rw-r--r-- | addons/website_sale/controllers/main.py | 2 | ||||
| -rw-r--r-- | addons/website_slides/views/website_slides_embed.xml | 2 | ||||
| -rw-r--r-- | doc/cla/corporate/merchise.md | 1 | ||||
| -rw-r--r-- | odoo/addons/base/models/ir_actions.py | 2 | ||||
| -rw-r--r-- | odoo/models.py | 6 | ||||
| -rw-r--r-- | odoo/modules/module.py | 6 |
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) |
