From 7d898ad72fec888e644e9c54f85ae1782254db19 Mon Sep 17 00:00:00 2001 From: Graeme Gellatly Date: Thu, 16 Aug 2018 11:45:52 +0200 Subject: [PATCH 01/42] [FIX] purchase: rounding in supplier info - Set 'Product Price' accuracy to 5 places - Create a PO for a product to a new supplier - Enter price at 3.14561 / unit Supplier info entry rounded to 3.15, while it should be 3.14561. Closes #22253 Fixes #22252 --- addons/purchase/models/purchase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 42f946f1d2b..0a66dca7d33 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -455,7 +455,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, } From 2479c797f7cd2c4e3cb24133628df00189f56778 Mon Sep 17 00:00:00 2001 From: Toufik Benjaa Date: Thu, 16 Aug 2018 11:24:24 +0200 Subject: [PATCH 02/42] [FIX] sale_timesheet: Perf issue on projects overview - Generating the overview for a project may have a high memory usage for projects with a lot of tasks. This is due to the usage of "mapped" which stores data in the cache. To avoid this, we instead use "search_read". --- addons/sale_timesheet/controllers/main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/addons/sale_timesheet/controllers/main.py b/addons/sale_timesheet/controllers/main.py index f3e5bb80e85..1b1d2b19ce2 100644 --- a/addons/sale_timesheet/controllers/main.py +++ b/addons/sale_timesheet/controllers/main.py @@ -3,6 +3,7 @@ from ast import literal_eval import babel from dateutil.relativedelta import relativedelta +import itertools from odoo import http, fields, _ from odoo.http import request @@ -82,7 +83,12 @@ class SaleTimesheetController(http.Controller): # # 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_data = request.env['account.analytic.line'].read_group(repartition_domain, ['employee_id', 'timesheet_invoice_type', 'unit_amount'], ['employee_id', 'timesheet_invoice_type'], lazy=False) From 69dd3dfa90524c835eac217ddbf339d5ce53e72e Mon Sep 17 00:00:00 2001 From: Darshil Trivedi Date: Wed, 4 Jul 2018 15:31:09 +0530 Subject: [PATCH 03/42] [FIX] stock: scrap: bypass confirmation wizard if not storable If a user wants to scrap more quantity than he has in stock, we return to him a confirmation wizard. This doesn't make sense when scrapping consumables products, as there are no quants for these kind of products. task 1864026 --- addons/stock/models/stock_scrap.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/stock/models/stock_scrap.py b/addons/stock/models/stock_scrap.py index 18be25ee1d2..517814573bc 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, From b30dd020fd3e51b822b26b7b7d1cf0a2e6f9690a Mon Sep 17 00:00:00 2001 From: Dennis Sluijk Date: Thu, 16 Aug 2018 14:08:15 +0200 Subject: [PATCH 04/42] [FIX] hr_timesheet: use float_time widget To display the sum as a time and not a float number Closes #26405 --- addons/hr_timesheet/i18n/hr_timesheet.pot | 9 +++++++-- addons/hr_timesheet/views/project_views.xml | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/addons/hr_timesheet/i18n/hr_timesheet.pot b/addons/hr_timesheet/i18n/hr_timesheet.pot index bdf5c37cc5b..b8894aa9cac 100644 --- a/addons/hr_timesheet/i18n/hr_timesheet.pot +++ b/addons/hr_timesheet/i18n/hr_timesheet.pot @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 11.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-19 13:42+0000\n" -"PO-Revision-Date: 2018-02-19 13:42+0000\n" +"POT-Creation-Date: 2018-08-16 14:27+0000\n" +"PO-Revision-Date: 2018-08-16 14:27+0000\n" "Last-Translator: <>\n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -203,6 +203,11 @@ msgstr "" msgid "Hours Spent" msgstr "" +#. module: hr_timesheet +#: model:ir.ui.view,arch_db:hr_timesheet.view_task_tree2_inherited +msgid "Initially Planned Hours" +msgstr "" + #. module: hr_timesheet #: model:ir.model.fields,field_description:hr_timesheet.field_res_config_settings_module_project_timesheet_holidays #: model:ir.ui.view,arch_db:hr_timesheet.res_config_settings_view_form diff --git a/addons/hr_timesheet/views/project_views.xml b/addons/hr_timesheet/views/project_views.xml index 50afd40a4d1..8b6a8dc1386 100644 --- a/addons/hr_timesheet/views/project_views.xml +++ b/addons/hr_timesheet/views/project_views.xml @@ -97,7 +97,7 @@ - + From 35bc8fec94a17a50a80695d65d55584eb3a373a6 Mon Sep 17 00:00:00 2001 From: Martin Duvergey Date: Wed, 20 Jun 2018 18:42:44 +0200 Subject: [PATCH 05/42] [CLA] JANUS signs CCLA Closes #25365 --- doc/cla/corporate/janus.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 doc/cla/corporate/janus.md diff --git a/doc/cla/corporate/janus.md b/doc/cla/corporate/janus.md new file mode 100644 index 00000000000..4269c11aa22 --- /dev/null +++ b/doc/cla/corporate/janus.md @@ -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 From 750e70e0a12c11d50998a44dff447f07fd7d06c3 Mon Sep 17 00:00:00 2001 From: Joren Van Onder Date: Thu, 16 Aug 2018 14:55:06 -0700 Subject: [PATCH 06/42] [FIX] l10n_co: remove duplicate account template Introduced by 986a7550a5332cffd1c1eacc4705d5843e442fb6. --- addons/l10n_co/data/account.account.template.csv | 1 - 1 file changed, 1 deletion(-) 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 From 412c471e78472a47160a1e05d9c49c61d4317305 Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Fri, 10 Aug 2018 11:45:02 +0200 Subject: [PATCH 07/42] [FIX] tools: sanitize allow white-space style OPW 1870847 --- odoo/tools/mail.py | 1 + 1 file changed, 1 insertion(+) diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py index 266cb4f20c4..098a2447d16 100644 --- a/odoo/tools/mail.py +++ b/odoo/tools/mail.py @@ -48,6 +48,7 @@ class _Cleaner(clean.Cleaner): 'float', 'vertical-align', 'display', 'padding', 'padding-top', 'padding-left', 'padding-bottom', 'padding-right', 'margin', 'margin-top', 'margin-left', 'margin-bottom', 'margin-right', + 'white-space', # box model 'border', 'border-color', 'border-radius', 'border-style', 'border-width', 'height', 'margin', 'padding', 'width', 'max-width', 'min-width', From f0028bc837bc93e355d5bda7be1223142b485718 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Tue, 14 Aug 2018 12:43:06 +0200 Subject: [PATCH 08/42] [FIX] mrp: raw materials and multi-location - Create a BOM for Product P with the following component: Product C, 5.0 units - Make some stock for C: 2 units in location 1 3 units in location 2 - Create a MO for P, reserve. When trying to produce an error arise: > Cannot set the done quantity from this stock move, work directly with > the move lines. If the user tries to unlock the MO in order to set the done quantity on the move lines, the same error will arise when trying to produce. In the specific scope of the production, we set the quantity done on the move line instead of directly on the move, to avoid this limitation. opw-1871241 Co-authored-by: Arnold Moyaux --- addons/mrp/tests/test_order.py | 39 ++++++++++++++++++++++++ addons/mrp/wizard/mrp_product_produce.py | 38 ++++++++++++++++++++++- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py index 56b87b7e61d..656156a9cde 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 d854e667822..077c62f04b1 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')) From b4cbf0bb61590680ae0e56a254d72e966977b8d2 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Fri, 17 Aug 2018 10:26:14 +0200 Subject: [PATCH 09/42] [FIX] calendar: date start When creating and editing a new calendar.event record from the calendar view, the start_datetime field does not prefill from the day that was selected on the calendar. Introduced in e08f999468a933ad73dc140e274107c992b5645c opw-1875276 --- addons/calendar/models/calendar.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index 279cdb630eb..8929eb30c3a 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -861,11 +861,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, ... From 9f1534ee8cbc92897b46cf75dc81bfa66037c423 Mon Sep 17 00:00:00 2001 From: len-odoo Date: Fri, 17 Aug 2018 10:52:12 +0200 Subject: [PATCH 10/42] [FIX] stock: add origin to the chatter when creating a picking from a sale order Before v11, the picking origin was added to the chatter, which is a useful feature. We add it back. opw 1874379 --- addons/sale_stock/models/stock.py | 8 ++++++++ addons/stock/models/stock_move.py | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/addons/sale_stock/models/stock.py b/addons/sale_stock/models/stock.py index b13e71d6c6f..0253ea70381 100644 --- a/addons/sale_stock/models/stock.py +++ b/addons/sale_stock/models/stock.py @@ -43,6 +43,14 @@ class StockMove(models.Model): line.qty_delivered = line._get_delivered_qty() return res + 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/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 7b5ee057159..0dac3ad176d 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -704,7 +704,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 @@ -714,6 +714,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. """ From 80118dd8a24249e5e008eae1178fccfc9ba68fb4 Mon Sep 17 00:00:00 2001 From: Kirti Choudhary Date: Thu, 19 Oct 2017 16:58:06 +0200 Subject: [PATCH 11/42] [IMP] document: added QUnit testcase note: backport 562d467 from saas-11.2 to 11.0 to add a similar test related to #26213 --- .../document/static/tests/document_tests.js | 78 +++++++++++++++++++ addons/document/views/document_templates.xml | 6 ++ 2 files changed, 84 insertions(+) create mode 100644 addons/document/static/tests/document_tests.js diff --git a/addons/document/static/tests/document_tests.js b/addons/document/static/tests/document_tests.js new file mode 100644 index 00000000000..9fd1e7d25ae --- /dev/null +++ b/addons/document/static/tests/document_tests.js @@ -0,0 +1,78 @@ +odoo.define('document.tests', function (require) { + "use strict"; + + var testUtils = require('web.test_utils'); + var FormView = require('web.FormView'); + + var createView = testUtils.createView; + + QUnit.module('DocumentTest', { + beforeEach: function () { + this.data = { + partner: { + fields: { + display_name: { string: "Displayed name", type: "char" } + }, + records: [{ + id: 1, + display_name: "first record", + }] + }, + 'ir.attachment': { + fields: { + type: { string: "Type", type: "char" }, + name: {string: "Name", type: "char"}, + res_id: {string: "ResId", type: "integer"}, + res_model: {string: "ResModel", type: "char"} + }, + records: [{ + id: 1, + type:"binary", + name: "attachment1", + res_id: 1, + res_model: 'partner' + },{ + id: 2, + type:"binary", + name: "attachment2", + res_id: 1, + res_model: 'partner' + }] + } + }; + } + }, function () { + QUnit.module('DocView'); + QUnit.test('documentAttachmentTest', function (assert) { + assert.expect(3); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '
', + res_id: 1, + viewOptions: {sidebar: true}, + mockRPC: function (route, args) { + if (args.method === 'search_read' && args.model === 'ir.attachment') { + return $.when(this.data['ir.attachment'].records); + } + if (route === '/web/dataset/call_kw/ir.attachment/unlink') { + assert.strictEqual(args.args[0], 1, "Should have correct id of the attachment to be deleted"); + } + return this._super.apply(this, arguments); + } + }); + + assert.strictEqual(form.sidebar.$('.o_sidebar_delete_attachment').length, 2, "there should be two attachments"); + form.sidebar.$('.o_sidebar_delete_attachment:eq(0)').click(); + $('.modal-footer .btn-primary').click(); + assert.strictEqual(form.sidebar.$('.o_sidebar_delete_attachment').length, 1, "there should be only one attachment"); + form.destroy(); + }); + }); +}); diff --git a/addons/document/views/document_templates.xml b/addons/document/views/document_templates.xml index abb2d13f2fc..1b7b999fdd1 100644 --- a/addons/document/views/document_templates.xml +++ b/addons/document/views/document_templates.xml @@ -5,4 +5,10 @@ + +