From f26df86ea9e36c5d82a944b1500d4df2cb798f3e Mon Sep 17 00:00:00 2001 From: Commandant Custo Date: Fri, 28 Aug 2015 10:36:25 +0200 Subject: [PATCH 01/20] [FIX] portal_sale: fix default domains Quotations/Sales orders menu in the portal must copy the behavior of the menus in the regular back-end. Backport of e5c5b1cc23f3f95af9b2dd056f21ab0dfbd63fd8 closes #8271 opw:648186 --- addons/portal_sale/portal_sale_view.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/portal_sale/portal_sale_view.xml b/addons/portal_sale/portal_sale_view.xml index 80dff872377..be4cf460a6b 100644 --- a/addons/portal_sale/portal_sale_view.xml +++ b/addons/portal_sale/portal_sale_view.xml @@ -44,7 +44,8 @@ ir.actions.act_window sale.order tree,form - {"search_default_draft":1} + [('state','in',('draft','sent','cancel'))] + {} We haven't sent you any quotation. @@ -55,7 +56,8 @@ sale.order tree,form - {"search_default_sales":1} + [('state', 'not in', ('draft', 'sent', 'cancel'))] + {} We haven't sent you any sales order. From 0fb00808b2dcd4504a0aa533319274a7bd7faa84 Mon Sep 17 00:00:00 2001 From: Thomas Rehn Date: Wed, 12 Nov 2014 14:30:11 +0100 Subject: [PATCH 02/20] [FIX] account_bank_statement_extensions: lines confirmation Confirm lines of account cash statement on closing The `account_bank_statement_extensions` adds a state field to account.bank.statement.line and sets the status to 'confirm' when a bank statement is closed. When a cash statement is closed, the lines remained in draft. Closes #3584 --- .../account_bank_statement.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/addons/account_bank_statement_extensions/account_bank_statement.py b/addons/account_bank_statement_extensions/account_bank_statement.py index 60dcc6dfa25..27518ac756c 100644 --- a/addons/account_bank_statement_extensions/account_bank_statement.py +++ b/addons/account_bank_statement_extensions/account_bank_statement.py @@ -38,8 +38,7 @@ class account_bank_statement(osv.osv): res = super(account_bank_statement, self).write(cr, uid, ids, vals, context=context) return res - def button_confirm_bank(self, cr, uid, ids, context=None): - super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context) + def confirm_statement_lines(self, cr, uid, ids, context=None): for st in self.browse(cr, uid, ids, context=context): if st.line_ids: cr.execute("UPDATE account_bank_statement_line \ @@ -47,6 +46,16 @@ class account_bank_statement(osv.osv): (tuple([x.id for x in st.line_ids]),)) return True + def button_confirm_bank(self, cr, uid, ids, context=None): + res = super(account_bank_statement, self).button_confirm_bank(cr, uid, ids, context=context) + self.confirm_statement_lines(cr, uid, ids, context=context) + return res + + def button_confirm_cash(self, cr, uid, ids, context=None): + res = super(account_bank_statement, self).button_confirm_cash(cr, uid, ids, context=context) + self.confirm_statement_lines(cr, uid, ids, context=context) + return res + def button_cancel(self, cr, uid, ids, context=None): super(account_bank_statement, self).button_cancel(cr, uid, ids, context=context) for st in self.browse(cr, uid, ids, context=context): From f243a01795a628d335396ed69e6f82c063301231 Mon Sep 17 00:00:00 2001 From: matt454357 Date: Wed, 2 Sep 2015 11:46:02 +0200 Subject: [PATCH 03/20] [FIX] hr_timesheet_sheet: Attendances & timesheets timezone When creating timesheet, default from-date and to-date are adjusted for user timezone. Fixes #3627 User timezone considered when assigning attendance records to timesheet Fixes #3628 Closes #3632 --- addons/hr_timesheet_sheet/hr_timesheet_sheet.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py index cb2b9f94046..730fb656581 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py @@ -192,7 +192,7 @@ class hr_timesheet_sheet(osv.osv): return (datetime.today() + relativedelta(weekday=0, days=-6)).strftime('%Y-%m-%d') elif r=='year': return time.strftime('%Y-01-01') - return time.strftime('%Y-%m-%d') + return fields.date.context_today(self, cr, uid, context) def _default_date_to(self, cr, uid, context=None): user = self.pool.get('res.users').browse(cr, uid, uid, context=context) @@ -203,7 +203,7 @@ class hr_timesheet_sheet(osv.osv): return (datetime.today() + relativedelta(weekday=6)).strftime('%Y-%m-%d') elif r=='year': return time.strftime('%Y-12-31') - return time.strftime('%Y-%m-%d') + return fields.date.context_today(self, cr, uid, context) def _default_employee(self, cr, uid, context=None): emp_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id','=',uid)], context=context) @@ -409,8 +409,12 @@ class hr_attendance(osv.osv): INNER JOIN resource_resource r ON (e.resource_id = r.id) ON (a.employee_id = e.id) - WHERE %(date_to)s >= date_trunc('day', a.name) - AND %(date_from)s <= a.name + LEFT JOIN res_users u + ON r.user_id = u.id + LEFT JOIN res_partner p + ON u.partner_id = p.id + WHERE %(date_to)s >= date_trunc('day', a.name AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC')) + AND %(date_from)s <= date_trunc('day', a.name AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC')) AND %(user_id)s = r.user_id GROUP BY a.id""", {'date_from': ts.date_from, 'date_to': ts.date_to, From c3b847daa8622da7488ea61452ad7227390991dd Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Tue, 1 Sep 2015 15:57:41 +0200 Subject: [PATCH 04/20] [FIX] delivery: total passed to get_price_from_picking The total used to check which delivery.grid.line to apply must be in the currency of the company because the delivery.grid.line records with variable == Price are expressed in the currency of the company. opw:647799 --- addons/delivery/delivery.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/delivery/delivery.py b/addons/delivery/delivery.py index e8e2fe79ee8..2333877b8b5 100644 --- a/addons/delivery/delivery.py +++ b/addons/delivery/delivery.py @@ -223,6 +223,7 @@ class delivery_grid(osv.osv): quantity += q total = (order.amount_total or 0.0) - total_delivery + total = self.pool['res.currency'].compute(cr, uid, order.currency_id.id, order.company_id.currency_id.id, total, context=context) return self.get_price_from_picking(cr, uid, id, total,weight, volume, quantity, context=context) def get_price_from_picking(self, cr, uid, id, total, weight, volume, quantity, context=None): From 1cfe1910db8b110e1293ed2ca396d27769287f81 Mon Sep 17 00:00:00 2001 From: Matthieu Dietrich Date: Tue, 18 Nov 2014 11:11:21 +0100 Subject: [PATCH 05/20] [FIX] account: Remove superfluous bracket for analytic account domain With the bracket in place, the domain does not consider the last part, "('state','not in',('close','cancelled'))". Closes #3714 --- addons/account/account_invoice_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index ab6c921c6ca..fb3a49dc9de 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -61,7 +61,7 @@ - + From 56a64834770a75699d7f9d5409601d08cfe49174 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 2 Sep 2015 16:27:57 +0200 Subject: [PATCH 06/20] [FIX] stock: inventory filters check used the wrong groups --- addons/stock/stock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index c47adb6d6cd..f25665852e5 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -2653,9 +2653,9 @@ class stock_inventory(osv.osv): if stock_settings.group_stock_tracking_owner: res_filter.append(('owner', _('One owner only'))) res_filter.append(('product_owner', _('One product for a specific owner'))) - if stock_settings.group_stock_tracking_lot: + if stock_settings.group_stock_production_lot: res_filter.append(('lot', _('One Lot/Serial Number'))) - if stock_settings.group_stock_packaging: + if stock_settings.group_stock_tracking_lot: res_filter.append(('pack', _('A Pack'))) return res_filter From ea07b7dc2bf455a8fe0dec577ef9794a9a53fee9 Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Wed, 2 Sep 2015 17:43:19 +0200 Subject: [PATCH 07/20] [FIX] website: don't escape if embedded html field We should escape arch ir.ui.view arch content, but we should not escape embedded html field content. --- addons/website/static/src/js/website.editor.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index 43df01d3514..212014feb2a 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -425,9 +425,11 @@ saveElement: function ($el) { // escape text nodes for xml saving var escaped_el = $el.clone(); - escaped_el.find('*').addBack().not('script,style').contents().each(function(){ + var to_escape = escaped_el.find('*').addBack(); + to_escape = to_escape.not(to_escape.filter('script,style,[data-oe-model][data-oe-model!="ir.ui.view"]').find('*').addBack()); + to_escape.contents().each(function(){ if(this.nodeType == 3) { - this.nodeValue = _.escape(this.nodeValue); + this.nodeValue = $('
').text(this.nodeValue).html(); } }); var markup = escaped_el.prop('outerHTML'); From ea2745b866760f824c47a8f84df166ff2cea7f38 Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Thu, 3 Sep 2015 09:50:07 +0200 Subject: [PATCH 08/20] [FIX] delivery: conversion rate according to the date When using compute in model "res.currency", the date must be set in the context to have the right conversion rate. opw:647799 --- addons/delivery/delivery.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/delivery/delivery.py b/addons/delivery/delivery.py index 2333877b8b5..275c19cd861 100644 --- a/addons/delivery/delivery.py +++ b/addons/delivery/delivery.py @@ -223,7 +223,9 @@ class delivery_grid(osv.osv): quantity += q total = (order.amount_total or 0.0) - total_delivery - total = self.pool['res.currency'].compute(cr, uid, order.currency_id.id, order.company_id.currency_id.id, total, context=context) + ctx = context.copy() + ctx['date'] = order.date_order + total = self.pool['res.currency'].compute(cr, uid, order.currency_id.id, order.company_id.currency_id.id, total, context=ctx) return self.get_price_from_picking(cr, uid, id, total,weight, volume, quantity, context=context) def get_price_from_picking(self, cr, uid, id, total, weight, volume, quantity, context=None): From a69af9c908da34786f7b8267b79665d9540a033f Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Thu, 3 Sep 2015 11:57:58 +0200 Subject: [PATCH 09/20] [FIX] addons: propagate context when get sequence With 3e82c94d we use the timezone in the context to format date sequences when formatting an ir.sequence. This commit adds the other missing context when getting a sequence, so these sequences are also dependant on the timezone. closes #8351 opw-646487 --- addons/account/account_bank_statement.py | 2 +- addons/analytic/analytic.py | 2 +- addons/point_of_sale/point_of_sale.py | 2 +- addons/purchase/purchase.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py index e9c1b5a3758..6bd6f76dab0 100644 --- a/addons/account/account_bank_statement.py +++ b/addons/account/account_bank_statement.py @@ -944,7 +944,7 @@ class account_bank_statement_line(osv.osv): 'currency_id': fields.many2one('res.currency', 'Currency', help="The optional other currency if it is a multi-currency entry."), } _defaults = { - 'name': lambda self,cr,uid,context={}: self.pool.get('ir.sequence').get(cr, uid, 'account.bank.statement.line'), + 'name': lambda self,cr,uid,context={}: self.pool.get('ir.sequence').get(cr, uid, 'account.bank.statement.line', context=context), 'date': lambda self,cr,uid,context={}: context.get('date', fields.date.context_today(self,cr,uid,context=context)), } diff --git a/addons/analytic/analytic.py b/addons/analytic/analytic.py index 975b8d6b63a..136489cb621 100644 --- a/addons/analytic/analytic.py +++ b/addons/analytic/analytic.py @@ -251,7 +251,7 @@ class account_analytic_account(osv.osv): _defaults = { 'type': 'normal', 'company_id': _default_company, - 'code' : lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'account.analytic.account'), + 'code' : lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'account.analytic.account', context=context), 'state': 'open', 'user_id': lambda self, cr, uid, ctx: uid, 'partner_id': lambda self, cr, uid, ctx: ctx.get('partner_id', False), diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 5ba591b39a7..fdb64a665bd 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -1380,7 +1380,7 @@ class pos_order_line(osv.osv): } _defaults = { - 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'pos.order.line'), + 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'pos.order.line', context=context), 'qty': lambda *a: 1, 'discount': lambda *a: 0.0, 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id, diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index c5f985f8b82..91f465bbdce 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -343,7 +343,7 @@ class purchase_order(osv.osv): def create(self, cr, uid, vals, context=None): if vals.get('name','/')=='/': - vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'purchase.order') or '/' + vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'purchase.order', context=context) or '/' context = dict(context or {}, mail_create_nolog=True) order = super(purchase_order, self).create(cr, uid, vals, context=context) self.message_post(cr, uid, [order], body=_("RFQ created"), context=context) @@ -1522,7 +1522,7 @@ class procurement_order(osv.osv): po_line_id = po_line_obj.create(cr, SUPERUSER_ID, line_vals, context=context) linked_po_ids.append(procurement.id) else: - name = seq_obj.get(cr, uid, 'purchase.order') or _('PO: %s') % procurement.name + name = seq_obj.get(cr, uid, 'purchase.order', context=context) or _('PO: %s') % procurement.name po_vals = { 'name': name, 'origin': procurement.origin, From 64e1c28e358ec06a7683249fd7fae1ba631c1933 Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Thu, 3 Sep 2015 11:13:16 +0200 Subject: [PATCH 10/20] [FIX] resource: get_working_intervals_of_day To compute start and end date of a working interval this function replaced hour in datetime object without taking into account the time zone. The start and end date taking respectively from calendar_working_day.hour_from and calendar_working_day.hour_to are in the time zone of the user and the datetime object are compared in UTC to avoid schedule's gap. This is why the start and end must be converted in UTC after being replaced by these hours. The tz_info must be removed from dates because it's forbidden to compare naive and aware dates. opw:648349 --- addons/resource/resource.py | 13 +++++--- addons/resource/tests/test_resource.py | 45 +++++++++++++------------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/addons/resource/resource.py b/addons/resource/resource.py index 67a287564e6..ca31366ba40 100644 --- a/addons/resource/resource.py +++ b/addons/resource/resource.py @@ -28,6 +28,7 @@ from openerp import tools from openerp.osv import fields, osv from openerp.tools.float_utils import float_compare from openerp.tools.translate import _ +import pytz class resource_calendar(osv.osv): """ Calendar model for a resource. It has @@ -335,11 +336,13 @@ class resource_calendar(osv.osv): return intervals working_intervals = [] + tz_info = fields.datetime.context_timestamp(cr, uid, work_dt, context=context).tzinfo for calendar_working_day in self.get_attendances_for_weekdays(cr, uid, id, [start_dt.weekday()], context): - working_interval = ( - work_dt.replace(hour=int(calendar_working_day.hour_from)), - work_dt.replace(hour=int(calendar_working_day.hour_to)) - ) + x = work_dt.replace(hour=int(calendar_working_day.hour_from)) + y = work_dt.replace(hour=int(calendar_working_day.hour_to)) + x = x.replace(tzinfo=tz_info).astimezone(pytz.UTC).replace(tzinfo=None) + y = y.replace(tzinfo=tz_info).astimezone(pytz.UTC).replace(tzinfo=None) + working_interval = (x, y) working_intervals += self.interval_remove_leaves(working_interval, work_limits) # find leave intervals @@ -598,7 +601,7 @@ class resource_calendar(osv.osv): for dt_str, hours, calendar_id in date_and_hours_by_cal: result = self.schedule_hours( cr, uid, calendar_id, hours, - day_dt=datetime.datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S').replace(minute=0, second=0), + day_dt=datetime.datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S').replace(second=0), compute_leaves=True, resource_id=resource, default_interval=(8, 16) ) diff --git a/addons/resource/tests/test_resource.py b/addons/resource/tests/test_resource.py index afa8ac08784..335aa80a62c 100644 --- a/addons/resource/tests/test_resource.py +++ b/addons/resource/tests/test_resource.py @@ -153,13 +153,13 @@ class TestResource(TestResourceCommon): _format = '%Y-%m-%d %H:%M:%S' # Test: day0 without leaves: 1 interval - intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date1) + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date1, context={'tz': 'UTC'}) self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working intervals') self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 09:08:07', _format), 'resource_calendar: wrong working intervals') self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong working intervals') # Test: day3 without leaves: 2 interval - intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date2) + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date2, context={'tz': 'UTC'}) self.assertEqual(len(intervals), 2, 'resource_calendar: wrong working intervals') self.assertEqual(intervals[0][0], datetime.strptime('2013-02-15 10:11:12', _format), 'resource_calendar: wrong working intervals') self.assertEqual(intervals[0][1], datetime.strptime('2013-02-15 13:00:00', _format), 'resource_calendar: wrong working intervals') @@ -167,7 +167,7 @@ class TestResource(TestResourceCommon): self.assertEqual(intervals[1][1], datetime.strptime('2013-02-15 23:00:00', _format), 'resource_calendar: wrong working intervals') # Test: day0 with leaves outside range: 1 interval - intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=0), compute_leaves=True) + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=0), compute_leaves=True, context={'tz': 'UTC'}) self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working intervals') self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 08:00:00', _format), 'resource_calendar: wrong working intervals') self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong working intervals') @@ -176,7 +176,7 @@ class TestResource(TestResourceCommon): intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=8) + relativedelta(days=7), end_dt=self.date1.replace(hour=15, minute=45, second=30) + relativedelta(days=7), - compute_leaves=True) + compute_leaves=True, context={'tz': 'UTC'}) self.assertEqual(len(intervals), 2, 'resource_calendar: wrong working intervals') self.assertEqual(intervals[0][0], datetime.strptime('2013-02-19 08:08:07', _format), 'resource_calendar: wrong working intervals') self.assertEqual(intervals[0][1], datetime.strptime('2013-02-19 09:00:00', _format), 'resource_calendar: wrong working intervals') @@ -189,18 +189,18 @@ class TestResource(TestResourceCommon): _format = '%Y-%m-%d %H:%M:%S' # Test: day1, beginning at 10:30 -> work from 10:30 (arrival) until 16:00 - intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=10, minute=30, second=0)) + intervals = self.resource_calendar.get_working_intervals_of_day(cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=10, minute=30, second=0), context={'tz': 'UTC'}) self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working interval / day computing') self.assertEqual(intervals[0][0], datetime.strptime('2013-02-12 10:30:00', _format), 'resource_calendar: wrong working interval / day computing') self.assertEqual(intervals[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong working interval / day computing') # Test: hour computation for same interval, should give 5.5 - wh = self.resource_calendar.get_working_hours_of_date(cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=10, minute=30, second=0)) + wh = self.resource_calendar.get_working_hours_of_date(cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=10, minute=30, second=0), context={'tz': 'UTC'}) self.assertEqual(wh, 5.5, 'resource_calendar: wrong working interval / day time computing') # Test: day1+7 on leave, without leave computation intervals = self.resource_calendar.get_working_intervals_of_day( cr, uid, self.calendar_id, - start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=7) + start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=7), context={'tz': 'UTC'} ) # Result: day1 (08->16) self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working interval/day computing') @@ -211,7 +211,7 @@ class TestResource(TestResourceCommon): intervals = self.resource_calendar.get_working_intervals_of_day( cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=7), - compute_leaves=True + compute_leaves=True, context={'tz': 'UTC'} ) # Result: day1 (08->09 + 12->16) self.assertEqual(len(intervals), 2, 'resource_calendar: wrong working interval/day computing') @@ -224,7 +224,7 @@ class TestResource(TestResourceCommon): intervals = self.resource_calendar.get_working_intervals_of_day( cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=14), - compute_leaves=True + compute_leaves=True, context={'tz': 'UTC'} ) # Result: day1 (08->16) self.assertEqual(len(intervals), 1, 'resource_calendar: wrong working interval/day computing') @@ -236,7 +236,7 @@ class TestResource(TestResourceCommon): cr, uid, self.calendar_id, start_dt=self.date1.replace(hour=7, minute=0, second=0) + relativedelta(days=14), compute_leaves=True, - resource_id=self.resource1_id + resource_id=self.resource1_id, context={'tz': 'UTC'} ) # Result: nothing, because on leave self.assertEqual(len(intervals), 0, 'resource_calendar: wrong working interval/day computing') @@ -268,7 +268,7 @@ class TestResource(TestResourceCommon): # (datetime.datetime(2013, 2, 8, 16, 0), datetime.datetime(2013, 2, 8, 23, 0)) # (datetime.datetime(2013, 2, 12, 8, 0), datetime.datetime(2013, 2, 12, 9, 0)) - res = self.resource_calendar.schedule_hours(cr, uid, self.calendar_id, -40, day_dt=self.date1.replace(minute=0, second=0)) + res = self.resource_calendar.schedule_hours(cr, uid, self.calendar_id, -40, day_dt=self.date1.replace(minute=0, second=0), context={'tz': 'UTC'}) # current day, limited at 09:00 because of day_dt specified -> 1 hour self.assertEqual(res[-1][0], datetime.strptime('2013-02-12 08:00:00', _format), 'resource_calendar: wrong hours scheduling') self.assertEqual(res[-1][1], datetime.strptime('2013-02-12 09:00:00', _format), 'resource_calendar: wrong hours scheduling') @@ -307,7 +307,7 @@ class TestResource(TestResourceCommon): res = self.resource_calendar.schedule_hours( cr, uid, self.calendar_id, 40, - day_dt=self.date1.replace(minute=0, second=0) + day_dt=self.date1.replace(minute=0, second=0), context={'tz': 'UTC'} ) self.assertEqual(res[0][0], datetime.strptime('2013-02-12 09:00:00', _format), 'resource_calendar: wrong hours scheduling') self.assertEqual(res[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong hours scheduling') @@ -341,7 +341,8 @@ class TestResource(TestResourceCommon): cr, uid, self.calendar_id, 40, day_dt=self.date1.replace(minute=0, second=0), compute_leaves=True, - resource_id=self.resource1_id + resource_id=self.resource1_id, + context={'tz': 'UTC'} ) self.assertEqual(res[0][0], datetime.strptime('2013-02-12 09:00:00', _format), 'resource_calendar: wrong hours scheduling') self.assertEqual(res[0][1], datetime.strptime('2013-02-12 16:00:00', _format), 'resource_calendar: wrong hours scheduling') @@ -376,7 +377,7 @@ class TestResource(TestResourceCommon): cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), - resource_id=self.resource1_id, exclude_leaves=True) + resource_id=self.resource1_id, exclude_leaves=True, context={'tz': 'UTC'}) self.assertEqual(res, 40.0, 'resource_calendar: wrong _interval_hours_get compatibility computation') # new API: resource without leaves @@ -385,7 +386,7 @@ class TestResource(TestResourceCommon): cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), - compute_leaves=False, resource_id=self.resource1_id) + compute_leaves=False, resource_id=self.resource1_id, context={'tz': 'UTC'}) self.assertEqual(res, 40.0, 'resource_calendar: wrong get_working_hours computation') # old API: resource and leaves @@ -394,7 +395,7 @@ class TestResource(TestResourceCommon): cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), - resource_id=self.resource1_id, exclude_leaves=False) + resource_id=self.resource1_id, exclude_leaves=False, context={'tz': 'UTC'}) self.assertEqual(res, 33.0, 'resource_calendar: wrong _interval_hours_get compatibility computation') # new API: resource and leaves @@ -403,7 +404,7 @@ class TestResource(TestResourceCommon): cr, uid, self.calendar_id, self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0) + relativedelta(days=7), - compute_leaves=True, resource_id=self.resource1_id) + compute_leaves=True, resource_id=self.resource1_id, context={'tz': 'UTC'}) self.assertEqual(res, 33.0, 'resource_calendar: wrong get_working_hours computation') # -------------------------------------------------- @@ -416,7 +417,7 @@ class TestResource(TestResourceCommon): self.date1.replace(hour=6, minute=0), self.date2.replace(hour=23, minute=0), compute_leaves=True, resource_id=self.resource1_id, - default_interval=(8, 16)) + default_interval=(8, 16), context={'tz': 'UTC'}) self.assertEqual(res, 32.0, 'resource_calendar: wrong get_working_hours computation') def test_50_calendar_schedule_days(self): @@ -428,14 +429,14 @@ class TestResource(TestResourceCommon): # Test1: with calendar # -------------------------------------------------- - res = self.resource_calendar.schedule_days_get_date(cr, uid, self.calendar_id, 5, day_date=self.date1) + res = self.resource_calendar.schedule_days_get_date(cr, uid, self.calendar_id, 5, day_date=self.date1, context={'tz': 'UTC'}) self.assertEqual(res.date(), datetime.strptime('2013-02-26 00:0:00', _format).date(), 'resource_calendar: wrong days scheduling') - res = self.resource_calendar.schedule_days_get_date(cr, uid, self.calendar_id, -2, day_date=self.date1) + res = self.resource_calendar.schedule_days_get_date(cr, uid, self.calendar_id, -2, day_date=self.date1, context={'tz': 'UTC'}) self.assertEqual(res.date(), datetime.strptime('2013-02-08 00:00:00', _format).date(), 'resource_calendar: wrong days scheduling') res = self.resource_calendar.schedule_days_get_date( cr, uid, self.calendar_id, 5, day_date=self.date1, - compute_leaves=True, resource_id=self.resource1_id) + compute_leaves=True, resource_id=self.resource1_id, context={'tz': 'UTC'}) self.assertEqual(res.date(), datetime.strptime('2013-03-01 00:0:00', _format).date(), 'resource_calendar: wrong days scheduling') # -------------------------------------------------- @@ -443,7 +444,7 @@ class TestResource(TestResourceCommon): # -------------------------------------------------- # Without calendar, should only count days -> 12 -> 16, 5 days with default intervals - res = self.resource_calendar.schedule_days_get_date(cr, uid, None, 5, day_date=self.date1, default_interval=(8, 16)) + res = self.resource_calendar.schedule_days_get_date(cr, uid, None, 5, day_date=self.date1, default_interval=(8, 16), context={'tz': 'UTC'}) self.assertEqual(res, datetime.strptime('2013-02-16 16:00:00', _format), 'resource_calendar: wrong days scheduling') def seconds(td): From bd9cbdfc41584c829be48f6a30abb7be3a8a63fb Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Fri, 4 Sep 2015 12:57:18 +0200 Subject: [PATCH 11/20] [FIX] web: sql constraint translation The `check` decorator expects the context to be in the `kwargs` or to be the last arguments of the method. The `call_kw` route, which is decorated by the `check` decorator, like every route, expands its kwargs arguments. Therefore, once in the `check` decorator, the context is located in the `kwargs` key of the kwargs. (More simply, instead of `kwargs.get('context', {})` it's `kwargs.get('kwargs', {}).get('context', {})` As the context is not retrieved correctly, the lang is not set correctly either, and the sql constraints were not translated. In 7.0, it worked, because there was a double check, as the call_kw was called trough an rpc (`dispatch_rpc`) call, which was decorated by the `check` as well. As a fix for 8.0, we apply the same logic, we perform a double check, with an indirection. The check decorator should probably be refactored, but this cannot be done in a stable release such as 8.0. Closes #3634 --- addons/web/controllers/main.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 8e844e816c7..6c6c9f1fe57 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -33,6 +33,7 @@ import openerp import openerp.modules.registry from openerp.addons.base.ir.ir_qweb import AssetsBundle, QWebTemplateNotFound from openerp.modules import get_module_resource +from openerp.service import model as service_model from openerp.tools import topological_sort from openerp.tools.translate import _ from openerp.tools import ustr @@ -933,7 +934,10 @@ class DataSet(http.Controller): if method.startswith('_'): raise Exception("Access Denied: Underscore prefixed methods cannot be remotely called") - return getattr(request.registry.get(model), method)(request.cr, request.uid, *args, **kwargs) + @service_model.check + def checked_call(__dbname, *args, **kwargs): + return getattr(request.registry.get(model), method)(request.cr, request.uid, *args, **kwargs) + return checked_call(request.db, *args, **kwargs) @http.route('/web/dataset/call', type='json', auth="user") def call(self, model, method, args, domain_id=None, context_id=None): From 67b1be7f579d8e500bf05fc8ba35e21dc6f4e402 Mon Sep 17 00:00:00 2001 From: "Adrien Peiffer (ACSONE)" Date: Thu, 13 Aug 2015 11:49:56 +0200 Subject: [PATCH 12/20] [IMP] website_hr_recruitment: recruitment form customization Add the possibility to add some fields on website recruitment form Closes #8049 --- addons/website_hr_recruitment/controllers/main.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/addons/website_hr_recruitment/controllers/main.py b/addons/website_hr_recruitment/controllers/main.py index 112c3142bbe..23eb55849bb 100644 --- a/addons/website_hr_recruitment/controllers/main.py +++ b/addons/website_hr_recruitment/controllers/main.py @@ -90,6 +90,12 @@ class website_hr_recruitment(http.Controller): 'default': default, }) + def _get_applicant_char_fields(self): + return ['email_from', 'partner_name', 'description'] + + def _get_applicant_relational_fields(self): + return ['department_id', 'job_id'] + @http.route('/jobs/thankyou', methods=['POST'], type='http', auth="public", website=True) def jobs_thankyou(self, **post): error = {} @@ -110,9 +116,9 @@ class website_hr_recruitment(http.Controller): 'source_id' : env.ref('hr_recruitment.source_website_company').id, 'name': '%s\'s Application' % post.get('partner_name'), } - for f in ['email_from', 'partner_name', 'description']: + for f in self._get_applicant_char_fields(): value[f] = post.get(f) - for f in ['department_id', 'job_id']: + for f in self._get_applicant_relational_fields(): value[f] = int(post.get(f) or 0) # Retro-compatibility for saas-3. "phone" field should be replace by "partner_phone" in the template in trunk. value['partner_phone'] = post.pop('phone', False) From 1658bee8d45d37d7fabd0ac230b894a4a6b89303 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Fri, 4 Sep 2015 13:17:34 +0200 Subject: [PATCH 13/20] [FIX] models: copy_data call in new API. Context are frozendict in the new API. Therefore, they cannot be altered. Any attempt to alter a frozendict will lead to a crash. Fixes #7362 --- openerp/models.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openerp/models.py b/openerp/models.py index b0c7762bbd7..8b047c246ff 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -4718,8 +4718,10 @@ class BaseModel(object): context = {} # avoid recursion through already copied records in case of circular relationship - seen_map = context.setdefault('__copy_data_seen', {}) - if id in seen_map.setdefault(self._name, []): + if '__copy_data_seen' not in context: + context = dict(context, __copy_data_seen=defaultdict(list)) + seen_map = context['__copy_data_seen'] + if id in seen_map[self._name]: return seen_map[self._name].append(id) @@ -4789,8 +4791,10 @@ class BaseModel(object): context = {} # avoid recursion through already copied records in case of circular relationship - seen_map = context.setdefault('__copy_translations_seen',{}) - if old_id in seen_map.setdefault(self._name,[]): + if '__copy_data_seen' not in context: + context = dict(context, __copy_data_seen=defaultdict(list)) + seen_map = context['__copy_data_seen'] + if old_id in seen_map[self._name]: return seen_map[self._name].append(old_id) From f5e5bbdae0d799b10fa6e59547d14b1a42e87f89 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Fri, 4 Sep 2015 10:06:11 +0200 Subject: [PATCH 14/20] [FIX] expression: fix search on one2many field with inherited inverse field Consider the following setting: - on model A, field F is computed, stored, and depends on field G - on model A, field one2many G to model B, with inverse field H - on model B, field many2one H is inherited (_inherits) from model C - on model C, field many2one H is stored When adding records of model B, the field F must be recomputed. In order to determine which records to recompute, one searches model A with a domain like [(G, 'in', ids)]. In expression.py, this is resolved with an SQL query like select H from B where id in {ids} This query fails, since the field H is not stored in model B. This happens in general if H is not stored (it may be any computed field). In that case, one should instead browse records from B, and read field H through the ORM. A test case has been added: it introduces a many2one field in a parent model, and a one2many field using the inherited many2one on a child model. The test checks whether one can search on the one2many field. --- openerp/addons/test_inherit/models.py | 8 ++++++++ openerp/addons/test_inherit/tests/test_inherit.py | 12 ++++++++++++ openerp/osv/expression.py | 12 +++++++++--- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/openerp/addons/test_inherit/models.py b/openerp/addons/test_inherit/models.py index 466318e22f5..a6d5aa96e9c 100644 --- a/openerp/addons/test_inherit/models.py +++ b/openerp/addons/test_inherit/models.py @@ -38,6 +38,7 @@ class mother(models.Model): _inherit = 'test.inherit.mother' field_in_mother = fields.Char() + partner_id = fields.Many2one('res.partner') # extend the name field: make it required and change its default value name = fields.Char(required=True, default='Bar') @@ -71,4 +72,11 @@ class daughter(models.Model): # change the default value of an inherited field name = fields.Char(default='Baz') + +class res_partner(models.Model): + _inherit = 'res.partner' + + # define a one2many field based on the inherited field partner_id + daughter_ids = fields.One2many('test.inherit.daughter', 'partner_id') + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/addons/test_inherit/tests/test_inherit.py b/openerp/addons/test_inherit/tests/test_inherit.py index 8867629be61..cda98193473 100644 --- a/openerp/addons/test_inherit/tests/test_inherit.py +++ b/openerp/addons/test_inherit/tests/test_inherit.py @@ -65,5 +65,17 @@ class test_inherits(common.TransactionCase): self.assertEqual(mother._columns['state'].selection, [('a', 'A'), ('b', 'B'), ('c', 'C'), ('d', 'D')]) + def test_50_search_one2many(self): + """ check search on one2many field based on inherited many2one field. """ + # create a daughter record attached to partner Demo + partner_demo = self.env.ref('base.partner_demo') + daughter = self.env['test.inherit.daughter'].create({'partner_id': partner_demo.id}) + self.assertEqual(daughter.partner_id, partner_demo) + self.assertIn(daughter, partner_demo.daughter_ids) + + # search the partner from the daughter record + partners = self.env['res.partner'].search([('daughter_ids', 'in', daughter.ids)]) + self.assertIn(partner_demo, partners) + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py index 7f962c260fa..dedd043ca90 100644 --- a/openerp/osv/expression.py +++ b/openerp/osv/expression.py @@ -138,6 +138,7 @@ import traceback import openerp.modules from . import fields +from .. import SUPERUSER_ID from ..models import MAGIC_COLUMNS, BaseModel import openerp.tools as tools @@ -941,11 +942,16 @@ class expression(object): call_null = False push(create_substitution_leaf(leaf, FALSE_LEAF, model)) else: - ids2 = select_from_where(cr, column._fields_id, comodel._table, 'id', ids2, operator) - if ids2: + # determine ids1 <-- column._fields_id --- ids2 + if comodel._fields[column._fields_id].store: + ids1 = select_from_where(cr, column._fields_id, comodel._table, 'id', ids2, operator) + else: + recs = comodel.browse(cr, SUPERUSER_ID, ids2, {'prefetch_fields': False}) + ids1 = recs.mapped(column._fields_id).ids + if ids1: call_null = False o2m_op = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in' - push(create_substitution_leaf(leaf, ('id', o2m_op, ids2), model)) + push(create_substitution_leaf(leaf, ('id', o2m_op, ids1), model)) if call_null: o2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in' From 50f915f3ac0ba4b72b606db7597f0a698ed66d89 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Fri, 4 Sep 2015 13:26:52 +0200 Subject: [PATCH 15/20] [FIX] models: oversight introduced in 1658bee8d45d37d7fabd0ac230b894a4a6b89303 --- openerp/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openerp/models.py b/openerp/models.py index 8b047c246ff..fe901078e62 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -4791,9 +4791,9 @@ class BaseModel(object): context = {} # avoid recursion through already copied records in case of circular relationship - if '__copy_data_seen' not in context: - context = dict(context, __copy_data_seen=defaultdict(list)) - seen_map = context['__copy_data_seen'] + if '__copy_translations_seen' not in context: + context = dict(context, __copy_translations_seen=defaultdict(list)) + seen_map = context['__copy_translations_seen'] if old_id in seen_map[self._name]: return seen_map[self._name].append(old_id) From a519527c7227ddcdce314c8537530f7011878cf5 Mon Sep 17 00:00:00 2001 From: "Adrien Peiffer (ACSONE)" Date: Fri, 4 Sep 2015 11:08:26 +0200 Subject: [PATCH 16/20] [FIX] hr: Set name field translatable The job name can be displayed on the website, which can be translated in several language. It must therefore be translatable. Closes #8373 --- addons/hr/hr.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/hr/hr.py b/addons/hr/hr.py index 7e433e915d7..cbad87a7c03 100644 --- a/addons/hr/hr.py +++ b/addons/hr/hr.py @@ -96,7 +96,8 @@ class hr_job(osv.Model): _description = "Job Position" _inherit = ['mail.thread', 'ir.needaction_mixin'] _columns = { - 'name': fields.char('Job Name', required=True, select=True), + 'name': fields.char('Job Name', required=True, select=True, + translate=True), 'expected_employees': fields.function(_get_nbr_employees, string='Total Forecasted Employees', help='Expected number of employees for this job position after new recruitment.', store = { From b429621676c23071962c660b7b107d105a0b9007 Mon Sep 17 00:00:00 2001 From: "Adrien Peiffer (ACSONE)" Date: Fri, 4 Sep 2015 11:12:13 +0200 Subject: [PATCH 17/20] [FIX] hr: Show address doesn't work without always_reload option The address of the company is supposed to be displayed in the form view. Without this, it's not the case. Closes #8373 --- addons/hr_recruitment/hr_recruitment_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index f02e65a6e16..c4c276e4589 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -350,7 +350,7 @@