Merge lp:~openerp-dev/openobject-addons/7.0-opw-590221 into lp:openobject-addons/7.0

Proposed by Jigar A.
Status: Merged
Merged at revision: 8989
Proposed branch: lp:~openerp-dev/openobject-addons/7.0-opw-590221
Merge into: lp:openobject-addons/7.0
Diff against target: 195 lines (+71/-13)
6 files modified
project_mrp/test/project_task_procurement.yml (+1/-1)
purchase/company.py (+6/-2)
purchase/purchase.py (+27/-3)
sale_stock/company.py (+6/-3)
sale_stock/sale_stock.py (+27/-2)
sale_stock/test/picking_order_policy.yml (+4/-2)
To merge this branch: bzr merge lp:~openerp-dev/openobject-addons/7.0-opw-590221
Reviewer Review Type Date Requested Status
Naresh(OpenERP) (community) Needs Fixing
Olivier Dony (Odoo) Pending
Review via email: mp+155543@code.launchpad.net

Description of the change

timezone issue: when converting the "order date" of Sales Orders or Purchase Orders into the "reference date" for scheduling delivery and procurement, the "order date" is taken as if it was in UTC like all datetime values. In reality it is a fields.date and therefore expressed in the user's timezone.

Test Case
Modules Installed Sale, Purchase Requisition, MRP and PST time zone of User.
Create SO with Order date = 2012-03-26 and When SO is confirmed, it calculates the Expected DO Date like [Sale Order Date (on the SO) + Customer Lead Time (on the SO line, coming from the product) - Security Days (on the company)] so expected date shown to according to PST tz will be 2012-03-25 17:00:00 instead of 2012-03-26 00:00:00. Same implies to PO. as mention on Bug report.

And
Also Improved the confusing and mis-leading Tool-tips for the Company Level Lead time fields Purchase Lead Time and Security Days.

Kindly Review this.
Thank You

To post a comment you must log in.
Revision history for this message
Naresh(OpenERP) (nch-openerp) wrote :

Hi Jigar,

Seems a cool fix ! Such a trap was not cached before where conversion being on a field type of different nature.

However few improvements I feel we should do !

It will be nice if we make this method in general so that any one can use it easily. Say we can define this in
server/openerp/tools/misc.py and then can be accessed where ever its needed. This will reduce code duplicity too.

*Usability* : we can improve a bit the help text added or can have community opinion on this !

*Supplier Order*:

This is the leads/security time for each purchase order. For company security purpose this many days will be removed from the date what suppliers has promised to you.

*Customer Order*:

For company security purpose this many days will be removed from the date, what you have promised to customers,
to cope up with any problems of procurement, final shipping, order negotiation etc.

Thanks,
Naresh Soni
OpenERP Enterprise Services

review: Needs Fixing
Revision history for this message
Jigar A. (ifixthat) wrote :

Hello,
Have Committed the **Usability** Improvements as suggested.
I do agree with that this should be generic Implantation, but by keeping specific handling on addons side then, It is possible for people to update a single module without updating the whole code if we think of stable perspective.
Yes we should forward port this to trunk with adding this as Utility either Under Tools as you suggested, or Under Date field definitions which will target it as Date related function.
Kindly review the issue.
Thank You

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'project_mrp/test/project_task_procurement.yml'
2--- project_mrp/test/project_task_procurement.yml 2012-12-18 16:37:16 +0000
3+++ project_mrp/test/project_task_procurement.yml 2013-04-04 12:34:35 +0000
4@@ -27,7 +27,7 @@
5 assert (not project and not account) or project.analytic_account_id == account, "Project does not correspond."
6 planned_hours = self._convert_qty_company_hours(cr, uid, procurement, context=context)
7 assert task.planned_hours == planned_hours, 'Planned Hours do not correspond.'
8- assert datetime.strptime(task.date_deadline, '%Y-%m-%d') == datetime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S'), 'Deadline does not correspond.'
9+ assert datetime.strptime(task.date_deadline, '%Y-%m-%d') == datetime.strptime(procurement.date_planned[:10], '%Y-%m-%d'), 'Deadline does not correspond.'
10 if procurement.product_id.product_manager:
11 assert task.user_id.id == procurement.product_id.product_manager.id, 'Allocated Person does not correspond with Service Product Manager.'
12 -
13
14=== modified file 'purchase/company.py'
15--- purchase/company.py 2012-12-06 14:56:32 +0000
16+++ purchase/company.py 2013-04-04 12:34:35 +0000
17@@ -24,8 +24,12 @@
18 class company(osv.osv):
19 _inherit = 'res.company'
20 _columns = {
21- 'po_lead': fields.float('Purchase Lead Time', required=True,
22- help="This is the leads/security time for each purchase order."),
23+ 'po_lead': fields.float(
24+ 'Purchase Lead Time', required=True,
25+ help="Margin of error for supplier lead times. When the system"\
26+ "generates Purchase Orders for procuring products,"\
27+ "they will be scheduled that many days earlier "\
28+ "to cope with unexpected supplier delays."),
29 }
30 _defaults = {
31 'po_lead': lambda *a: 1.0,
32
33=== modified file 'purchase/purchase.py'
34--- purchase/purchase.py 2012-12-21 16:48:08 +0000
35+++ purchase/purchase.py 2013-04-04 12:34:35 +0000
36@@ -20,6 +20,8 @@
37 ##############################################################################
38
39 import time
40+import pytz
41+from openerp import SUPERUSER_ID
42 from datetime import datetime
43 from dateutil.relativedelta import relativedelta
44
45@@ -593,11 +595,33 @@
46 wf_service.trg_validate(uid, 'purchase.order', id, 'purchase_cancel', cr)
47 return True
48
49+ def date_to_datetime(self, cr, uid, userdate, context=None):
50+ """ Convert date values expressed in user's timezone to
51+ server-side UTC timestamp, assuming a default arbitrary
52+ time of 12:00 AM - because a time is needed.
53+
54+ :param str userdate: date string in in user time zone
55+ :return: UTC datetime string for server-side use
56+ """
57+ # TODO: move to fields.datetime in server after 7.0
58+ user_datetime = datetime.strptime(userdate, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(hours=12.0)
59+ if context and context.get('tz'):
60+ tz_name = context['tz']
61+ else:
62+ tz_name = self.pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz']
63+ if tz_name:
64+ utc = pytz.timezone('UTC')
65+ context_tz = pytz.timezone(tz_name)
66+ local_timestamp = context_tz.localize(user_datetime, is_dst=False)
67+ user_datetime = local_timestamp.astimezone(utc)
68+ return user_datetime.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
69+ return userdate
70+
71 def _prepare_order_picking(self, cr, uid, order, context=None):
72 return {
73 'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in'),
74 'origin': order.name + ((order.origin and (':' + order.origin)) or ''),
75- 'date': order.date_order,
76+ 'date': self.date_to_datetime(cr, uid, order.date_order, context),
77 'partner_id': order.dest_address_id.id or order.partner_id.id,
78 'invoice_state': '2binvoiced' if order.invoice_method == 'picking' else 'none',
79 'type': 'in',
80@@ -615,8 +639,8 @@
81 'product_uos_qty': order_line.product_qty,
82 'product_uom': order_line.product_uom.id,
83 'product_uos': order_line.product_uom.id,
84- 'date': order_line.date_planned,
85- 'date_expected': order_line.date_planned,
86+ 'date': self.date_to_datetime(cr, uid, order.date_order, context),
87+ 'date_expected': self.date_to_datetime(cr, uid, order.date_order, context),
88 'location_id': order.partner_id.property_stock_supplier.id,
89 'location_dest_id': order.location_id.id,
90 'picking_id': picking_id,
91
92=== modified file 'sale_stock/company.py'
93--- sale_stock/company.py 2012-12-06 14:56:32 +0000
94+++ sale_stock/company.py 2013-04-04 12:34:35 +0000
95@@ -24,9 +24,12 @@
96 class company(osv.osv):
97 _inherit = 'res.company'
98 _columns = {
99- 'security_lead': fields.float('Security Days', required=True,
100- help="This is the days added to what you promise to customers "\
101- "for security purpose"),
102+ 'security_lead': fields.float(
103+ 'Security Days', required=True,
104+ help="Margin of error for dates promised to customers. "\
105+ "Products will be scheduled for procurement and delivery "\
106+ "that many days earlier than the actual promised date, to "\
107+ "cope with unexpected delays in the supply chain."),
108 }
109 _defaults = {
110 'security_lead': 0.0,
111
112=== modified file 'sale_stock/sale_stock.py'
113--- sale_stock/sale_stock.py 2012-12-21 16:48:08 +0000
114+++ sale_stock/sale_stock.py 2013-04-04 12:34:35 +0000
115@@ -25,6 +25,8 @@
116 from openerp.osv import fields, osv
117 from openerp import netsvc
118 from openerp.tools.translate import _
119+import pytz
120+from openerp import SUPERUSER_ID
121
122 class sale_shop(osv.osv):
123 _inherit = "sale.shop"
124@@ -232,6 +234,28 @@
125 res.append(line.procurement_id.id)
126 return res
127
128+ def date_to_datetime(self, cr, uid, userdate, context=None):
129+ """ Convert date values expressed in user's timezone to
130+ server-side UTC timestamp, assuming a default arbitrary
131+ time of 12:00 AM - because a time is needed.
132+
133+ :param str userdate: date string in in user time zone
134+ :return: UTC datetime string for server-side use
135+ """
136+ # TODO: move to fields.datetime in server after 7.0
137+ user_datetime = datetime.strptime(userdate, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(hours=12.0)
138+ if context and context.get('tz'):
139+ tz_name = context['tz']
140+ else:
141+ tz_name = self.pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz']
142+ if tz_name:
143+ utc = pytz.timezone('UTC')
144+ context_tz = pytz.timezone(tz_name)
145+ local_timestamp = context_tz.localize(user_datetime, is_dst=False)
146+ user_datetime = local_timestamp.astimezone(utc)
147+ return user_datetime.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
148+ return userdate
149+
150 # if mode == 'finished':
151 # returns True if all lines are done, False otherwise
152 # if mode == 'canceled':
153@@ -314,7 +338,7 @@
154 return {
155 'name': pick_name,
156 'origin': order.name,
157- 'date': order.date_order,
158+ 'date': self.date_to_datetime(cr, uid, order.date_order, context),
159 'type': 'out',
160 'state': 'auto',
161 'move_type': order.picking_policy,
162@@ -348,7 +372,8 @@
163 return True
164
165 def _get_date_planned(self, cr, uid, order, line, start_date, context=None):
166- date_planned = datetime.strptime(start_date, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(days=line.delay or 0.0)
167+ start_date = self.date_to_datetime(cr, uid, start_date, context)
168+ date_planned = datetime.strptime(start_date, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=line.delay or 0.0)
169 date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
170 return date_planned
171
172
173=== modified file 'sale_stock/test/picking_order_policy.yml'
174--- sale_stock/test/picking_order_policy.yml 2012-12-18 22:50:15 +0000
175+++ sale_stock/test/picking_order_policy.yml 2013-04-04 12:34:35 +0000
176@@ -26,7 +26,8 @@
177 order = self.browse(cr, uid, ref("sale.sale_order_6"))
178 for order_line in order.order_line:
179 procurement = order_line.procurement_id
180- date_planned = datetime.strptime(order.date_order, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(days=order_line.delay or 0.0)
181+ sale_order_date = self.date_to_datetime(cr, uid, order.date_order, context)
182+ date_planned = datetime.strptime(sale_order_date, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=order_line.delay or 0.0)
183 date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
184 assert procurement.date_planned == date_planned, "Scheduled date is not correspond."
185 assert procurement.product_id.id == order_line.product_id.id, "Product is not correspond."
186@@ -60,7 +61,8 @@
187 output_id = sale_order.shop_id.warehouse_id.lot_output_id.id
188 for move in picking.move_lines:
189 order_line = move.sale_line_id
190- date_planned = datetime.strptime(sale_order.date_order, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(days=order_line.delay or 0.0)
191+ sale_order_date = self.date_to_datetime(cr, uid, sale_order.date_order, context)
192+ date_planned = datetime.strptime(sale_order_date, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=order_line.delay or 0.0)
193 date_planned = (date_planned - timedelta(days=sale_order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
194 assert datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT) == datetime.strptime(date_planned, DEFAULT_SERVER_DATETIME_FORMAT), "Excepted Date is not correspond with Planned Date."
195 assert move.product_id.id == order_line.product_id.id,"Product is not correspond."