From 20ed57f874176161a043dee834b25c7f0b3dc175 Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Tue, 6 Jun 2017 16:48:38 +0200 Subject: [PATCH 1/7] [FIX] calendar: _get_recurrent_date_by_event Let's create in google calendar a reccurent event with all day set and a end date. When synchronising google calendar with Odoo(by clicking on button "Sync with Google"). It raised on error because the start date had a time zone but not the end date(the UNTIL in the rrule). opw:745665 --- addons/calendar/models/calendar.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index 8db6909b847..8c18fe2e24b 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -553,15 +553,18 @@ class Meeting(models.Model): if not event_date: event_date = datetime.now() - # Convert the event date to saved timezone (or context tz) as it'll - # define the correct hour/day asked by the user to repeat for recurrence. - event_date = event_date.astimezone(timezone) # transform "+hh:mm" timezone - rset1 = rrule.rrulestr(str(self.rrule), dtstart=event_date, forceset=True) + if self.allday and self.rrule and 'Z' not in self.rrule: + rset1 = rrule.rrulestr(str(self.rrule), dtstart=event_date.replace(tzinfo=None), forceset=True, ignoretz=True) + else: + # Convert the event date to saved timezone (or context tz) as it'll + # define the correct hour/day asked by the user to repeat for recurrence. + event_date = event_date.astimezone(timezone) # transform "+hh:mm" timezone + rset1 = rrule.rrulestr(str(self.rrule), dtstart=event_date, forceset=True, tzinfos={}) recurring_meetings = self.search([('recurrent_id', '=', self.id), '|', ('active', '=', False), ('active', '=', True)]) for meeting in recurring_meetings: rset1._exdate.append(todate(meeting.recurrent_id_date)) - return [d.astimezone(pytz.UTC) for d in rset1] + return [d.astimezone(pytz.UTC) if d.tzinfo else d for d in rset1] @api.multi def _get_recurrency_end_date(self): From c638fbdf232b39585749405fe7d7d5a225041ad9 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Wed, 7 Jun 2017 11:18:56 +0200 Subject: [PATCH 2/7] [FIX] stock: ACL for product attributes for Inventory managers If only Inventory is installed, only the admin could create new product attributes for products variants. Other managers couldn't. This revision adds the ACL according to what is done in other modules, such as in `sale`. Mostlikely, we should create a product manager group, for which this rights are given, and make the other groups (sales manager, inventory manager) inherit from this one. opw-745735 --- addons/stock/security/ir.model.access.csv | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/stock/security/ir.model.access.csv b/addons/stock/security/ir.model.access.csv index 3eef34a4bda..0f540b9aeb1 100644 --- a/addons/stock/security/ir.model.access.csv +++ b/addons/stock/security/ir.model.access.csv @@ -75,3 +75,7 @@ access_barcode_rule_stock_manager,barcode.rule.stock.manager,barcodes.model_barc access_stock_forecast_user,report.stock.forecast.user,model_report_stock_forecast,stock.group_stock_user,1,0,0,0 access_stock_forecast_manager,report.stock.forecast.manager,model_report_stock_forecast,stock.group_stock_manager,1,1,1,1 access_stock_scrap_user,stock.scrap.user,model_stock_scrap,stock.group_stock_user,1,1,1,0 +access_product_attribute_manager,product.attribute manager,product.model_product_attribute,stock.group_stock_manager,1,1,1,1 +access_product_attribute_value_manager,product.attribute manager value,product.model_product_attribute_value,stock.group_stock_manager,1,1,1,1 +access_product_attribute_price_manager,product.attribute manager price,product.model_product_attribute_price,stock.group_stock_manager,1,1,1,1 +access_product_attribute_line_manager,product.attribute manager line,product.model_product_attribute_line,stock.group_stock_manager,1,1,1,1 From 7b210167604d4450d0b52dfddd39ca3ccc672564 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Tue, 6 Jun 2017 20:25:59 +0200 Subject: [PATCH 3/7] [FIX] stock: unlink scrap Forbids to unlink a done scrap. opw-746294 --- addons/stock/i18n/stock.pot | 8 +++++++- addons/stock/models/stock_scrap.py | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/addons/stock/i18n/stock.pot b/addons/stock/i18n/stock.pot index e7182b88541..e01b6291165 100644 --- a/addons/stock/i18n/stock.pot +++ b/addons/stock/i18n/stock.pot @@ -6566,7 +6566,13 @@ msgid "You cannot cancel a stock move that has been set to 'Done'." msgstr "" #. module: stock -#: code:addons/stock/models/stock_inventory.py:352 +#: code:addons/stock/models/stock_scrap.py:72 +#, python-format +msgid "You cannot delete a scrap which is done." +msgstr "" + +#. module: stock +#: code:addons/stock/models/stock_inventory.py:374 #, python-format msgid "You cannot have two inventory adjustements in state 'in Progess' with the same product(%s), same location(%s), same package, same owner and same lot. Please first validatethe first inventory adjustement with this product before creating another one." msgstr "" diff --git a/addons/stock/models/stock_scrap.py b/addons/stock/models/stock_scrap.py index 355f0139d04..16be4d9b73b 100644 --- a/addons/stock/models/stock_scrap.py +++ b/addons/stock/models/stock_scrap.py @@ -66,6 +66,12 @@ class StockScrap(models.Model): scrap.do_scrap() return scrap + @api.multi + def unlink(self): + if 'done' in self.mapped('state'): + raise UserError(_('You cannot delete a scrap which is done.')) + return super(StockScrap, self).unlink() + def _get_origin_moves(self): return self.picking_id and self.picking_id.move_lines.filtered(lambda x: x.product_id == self.product_id) From 2332df6bc7380e742e7d3dfb945124e21078087d Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Thu, 18 May 2017 09:55:41 +0200 Subject: [PATCH 4/7] [FIX] mrp: Post inventory button without finished product Let's consider P1, P2 and P3 with a cost=1$, three stockable products where P1 and P2 are in the same product category PC1 with a Stock valuation account = SVA1 and P3 is in the product category PC2 with a Stock valuation accout = SVA2. PC1 and PC2 are set with costing method in real price and Inventory valuation = perpetual(automated). Steps to reproduce this issue: -Create a BOM where P3 is composed by 1 P1 and 1 P2 -Create a PO for P1 and P2 with a cost of 5$ for each one -Create a MO to produce 1 P3 -Check availability -Insert manually the consumed product(1 P1 and 1 P2) -The button "Post Inventory" appeared, clicked on it -Check the general ledger, two moves with debit=5$ has been created for P1 and P2 in PC1(it's ok) -Produce the MO and click on "Post Inventory" again -Check the general ledger, two moves have been created, one with a credit of 2$ on PC1 and one of debit of 2$ on PC2(it was wrong) The expected moves for the last step is: a credit of 10$ on PC1 and a debit of 10$ on PC2 because the purchase price must be considered as P1 and P2 are in real price. NB: Everything is ok if the button "Post Inventory" is clicked after the production. opw:741748 --- addons/mrp/models/mrp_production.py | 7 +++++-- addons/stock/models/product.py | 3 +++ addons/stock_account/models/product.py | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 8b0b7c48dea..097876b0b6b 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -208,8 +208,11 @@ class MrpProduction(models.Model): @api.depends('move_raw_ids.quantity_done', 'move_finished_ids.quantity_done') def _compute_post_visible(self): for order in self: - order.post_visible = any(order.move_raw_ids.filtered(lambda x: (x.quantity_done) > 0 and (x.state not in ['done', 'cancel']))) or \ - any(order.move_finished_ids.filtered(lambda x: (x.quantity_done) > 0 and (x.state not in ['done', 'cancel']))) + if order.product_tmpl_id._is_cost_method_standard(): + order.post_visible = any((x.quantity_done > 0 and x.state not in ['done', 'cancel']) for x in order.move_raw_ids) or \ + any((x.quantity_done > 0 and x.state not in ['done' 'cancel']) for x in order.move_finished_ids) + else: + order.post_visible = any((x.quantity_done > 0 and x.state not in ['done' 'cancel']) for x in order.move_finished_ids) @api.multi @api.depends('workorder_ids.state', 'move_finished_ids') diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py index b48d4f8570d..5a71c1fccf1 100644 --- a/addons/stock/models/product.py +++ b/addons/stock/models/product.py @@ -416,6 +416,9 @@ class ProductTemplate(models.Model): relation="stock.location.route", string="Category Routes", related='categ_id.total_route_ids') + def _is_cost_method_standard(self): + return True + def _compute_quantities(self): res = self._compute_quantities_dict() for template in self: diff --git a/addons/stock_account/models/product.py b/addons/stock_account/models/product.py index 1e4ac7ffbe1..1b15d2aa066 100644 --- a/addons/stock_account/models/product.py +++ b/addons/stock_account/models/product.py @@ -51,6 +51,9 @@ class ProductTemplate(models.Model): def _compute_cost_method(self): self.cost_method = self.property_cost_method or self.categ_id.property_cost_method + def _is_cost_method_standard(self): + return self.property_cost_method == 'standard' + @api.one def _set_cost_method(self): return self.write({'property_cost_method': self.cost_method}) From 506ac455376d605bc360764ae958835b3da195e9 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 7 Jun 2017 11:24:44 +0200 Subject: [PATCH 5/7] [FIX] resource: get working hours - Use the demo resource calendar (40h/week) - Create a project forecast for the Administrator - Set the 'Start date' and 'End date' to 2017-07-07 - Plan 8 hours The % of time should be 100 %, but it's not always the case. The core of the issue comes from `get_working_hours`. Case 1: Call with start_dt = end_dt = 2017-07-07 00:00:00 `get_working_hours_of_date` is called twice with the following start/end dt: - 2017-06-07 00:00:00, 2017-06-07 00:00:00 => 0 hours - 2017-06-08 00:00:00, 2017-06-08 23:59:59.999999 => 8 hours This is obviously not correct, we don't care about what happens on 2017-06-08. The number of hours is correct in total, but it comes from the wrong day. Case 2: Call with start_dt = 2017-07-07 00:00:00 and end_dt = 2017-07-07 23:59:59 `get_working_hours_of_date` is called twice with the following start/end dt: - 2017-06-07 00:00:00, 2017-06-07 23:59:59.999999 => 8 hours - 2017-06-08 00:00:00, 2017-06-08 23:59:59.999999 => 8 hours This is obviously not correct either... We change how the `until` parameter is computed. This way, the correct call is the second call (Case 2), and `get_working_hours_of_date` is calld once with the following start/end dt: - 2017-06-07 00:00:00, 2017-06-07 23:59:59.999999 => 8 hours opw-745253 --- addons/resource/models/resource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/resource/models/resource.py b/addons/resource/models/resource.py index 5551d1c6838..cb0270b7bfa 100644 --- a/addons/resource/models/resource.py +++ b/addons/resource/models/resource.py @@ -369,7 +369,7 @@ class ResourceCalendar(models.Model): resource_id=None, default_interval=None): hours = 0.0 for day in rrule.rrule(rrule.DAILY, dtstart=start_dt, - until=(end_dt + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0), + until=end_dt.replace(hour=23, minute=59, second=59, microsecond=999999), byweekday=self.get_weekdays()): day_start_dt = day.replace(hour=0, minute=0, second=0, microsecond=0) if start_dt and day.date() == start_dt.date(): From 65e49baedeeab52ad9ba9c1e001449527118197a Mon Sep 17 00:00:00 2001 From: Richard Mathot Date: Wed, 7 Jun 2017 16:09:42 +0200 Subject: [PATCH 6/7] [DOC] website_event_sale: think multicurrency when writing tests --- .../website_event_sale/static/src/js/website.tour.event_sale.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/website_event_sale/static/src/js/website.tour.event_sale.js b/addons/website_event_sale/static/src/js/website.tour.event_sale.js index de23a9b4b67..327afe03a56 100644 --- a/addons/website_event_sale/static/src/js/website.tour.event_sale.js +++ b/addons/website_event_sale/static/src/js/website.tour.event_sale.js @@ -75,7 +75,7 @@ tour.register('event_buy_tickets', { trigger: 'a[href="/shop/confirm_order"]:contains("Confirm")', }, { - content: "Check that the subtotal is 5,500.00", + content: "Check that the subtotal is 5,500.00 USD", // this test will fail if the currency of the main company is not USD trigger: '#order_total_untaxed .oe_currency_value:contains("5,500.00")', run: function () {}, // it's a check }, From c6b9b69f6a483e7ccebf0b823240b71a8f426fd3 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 7 Jun 2017 22:46:25 +0200 Subject: [PATCH 7/7] [FIX] base_import_module: allow selective access to module files As a consequence of rev. 76cd8d2558d2a1fc11681dbc4a134ba06fb698c0, imported modules were unable to access their resource files during import. Rather than further modifying the file_open API to whitelist paths (the whole thing needs a redesign in master), we temporarily whitelist the temporary directory by including it in the global addons_paths, making sure to undo it afterwards. This gives all lower level function access the resource files via file_open, without having to pass around whitelisted paths through many different calls. --- addons/base_import_module/models/ir_module.py | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py index e81035bda31..b8be8f11865 100644 --- a/addons/base_import_module/models/ir_module.py +++ b/addons/base_import_module/models/ir_module.py @@ -96,18 +96,23 @@ class view(osv.osv): raise osv.except_osv(_('Error !'), msg % zf.filename) with openerp.tools.osutil.tempdir() as module_dir: - z.extractall(module_dir) - dirs = [d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))] - for mod_name in dirs: - module_names.append(mod_name) - try: - # assert mod_name.startswith('theme_') - path = opj(module_dir, mod_name) - self.import_module(cr, uid, mod_name, path, force=force, context=context) - success.append(mod_name) - except Exception, e: - _logger.exception('Error while importing module') - errors[mod_name] = tools.ustr(e) + import openerp.modules as addons + try: + addons.module.ad_paths.append(module_dir) + z.extractall(module_dir) + dirs = [d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))] + for mod_name in dirs: + module_names.append(mod_name) + try: + # assert mod_name.startswith('theme_') + path = opj(module_dir, mod_name) + self.import_module(cr, uid, mod_name, path, force=force, context=context) + success.append(mod_name) + except Exception, e: + _logger.exception('Error while importing module') + errors[mod_name] = tools.ustr(e) + finally: + addons.module.ad_paths.remove(module_dir) r = ["Successfully imported module '%s'" % mod for mod in success] for mod, error in errors.items(): r.append("Error while importing module '%s': %r" % (mod, error))