1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# Copyright (c) Camptocamp SA
8
# This file is part of the c2c_timesheet_report module
11
# WARNING: This program as such is intended to be used by professional
12
# programmers who take the whole responsability of assessing all potential
13
# consequences resulting from its eventual inadequacies and bugs
14
# End users who are looking for a ready-to-use solution with commercial
15
# garantees and support are strongly adviced to contract a Free Software
18
# This program is Free Software; you can redistribute it and/or
19
# modify it under the terms of the GNU General Public License
20
# as published by the Free Software Foundation; either version 2
21
# of the License, or (at your option) any later version.
23
# This program is distributed in the hope that it will be useful,
24
# but WITHOUT ANY WARRANTY; without even the implied warranty of
25
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
# GNU General Public License for more details.
28
# You should have received a copy of the GNU General Public License
29
# along with this program; if not, write to the Free Software
30
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
32
##############################################################################
34
from c2c_reporting_tools.translation import _
36
from osv import fields, osv
38
from datetime import datetime, timedelta
39
from mx import DateTime
44
class reminder (osv.osv):
45
_name = "c2c_timesheet_reports.reminder"
46
_description = "Handle the scheduling of messages"
50
'reply_to' : fields.char('Reply To', size=100),
51
'message' : fields.text('Message'),
52
'subject' : fields.char('Subject', size=200),
56
#default cron (the one created if missing)
57
cron = {'active' : False,
59
'interval_number' : 1,
60
'interval_type' : 'weeks',
61
'nextcall' : time.strftime("%Y-%m-%d %H:%M:%S", (datetime.today() + timedelta(days=1)).timetuple() ), #tomorrow same time
64
'model' : 'c2c_timesheet_reports.reminder',
69
#default message (the one created if missing)
70
message = {'reply_to': 'spam@camptocamp.com'
75
def run(self, cr, uid):
76
""" find the reminder recipients and send them an email """
79
companies = self.pool.get('res.company').browse(cr, uid, self.pool.get('res.company').search(cr, uid, []))
81
#for each company, get all recipients
84
recipients += self.get_recipients(cr, uid, {}, c)
86
#get the message to send
87
message_id = self.get_message_id(cr, uid, {})
88
message_data = self.browse(cr, uid, message_id)
91
#send them email if they have an email defined
96
emails.append(r.work_email)
98
tools.email_send(message_data.reply_to, [], message_data.subject, message_data.message, email_bcc=emails)
103
def get_recipients(self, cr, uid, context, company):
104
"""return the list of users that must recieve the email """
106
#get the whole list of employees
107
employees = self.compute_employees_list(cr, uid, context, company)
110
periods = self.compute_periods(company, time.gmtime(), 13)
111
#remove the first one because it's the current one
121
#if the user still in the company?
123
if (e.started != False and e.started > time.strftime('%Y-%m-%d') ) or (e.ended != False and e.ended < time.strftime('%Y-%m-%d') ):
124
#do nothing... this user is not concerned anymore by timesheets
127
#and for each periods
128
for p_index in range(len(periods)):
130
status = self.compute_timesheet_status(cr, uid, context, company, e, p)
132
# if there is a missing sheet or a draft sheet
133
# and the user can receive alerts
134
#then we must alert the user
135
if status in ['Missing', 'Draft'] and e.receive_timesheet_alerts :
137
break # no need to go further for this user, he is now added in the list, go to the next one
144
def compute_periods(self, company, date, periods_number=5):
145
""" return the timeranges to display. This is the 5 last timesheets (depending on the timerange defined for the company) """
149
(last_start_date, last_end_date) = self.get_last_period_dates( company, date)
151
for cpt in range(periods_number):
152
#find the delta between last_XXX_date to XXX_date
153
if company.timesheet_range == 'month':
154
delta = DateTime.RelativeDateTime(months=-cpt)
155
elif company.timesheet_range == 'week':
156
delta = DateTime.RelativeDateTime(weeks=-cpt)
157
elif company.timesheet_range == 'year':
158
delta = DateTime.RelativeDateTime(years=-cpt)
160
start_date = last_start_date + delta
161
end_date = last_end_date + delta
162
periods.append( (start_date, end_date) )
167
def get_last_period_dates(self, company, date):
168
""" return the start date and end date of the last period to display """
170
# return the first day and last day of the month
171
if company.timesheet_range == 'month':
172
start_date = DateTime.Date(date.tm_year, date.tm_mon, 1)
173
end_date = start_date + DateTime.RelativeDateTime(months=+1)
174
#return the first and last days of the week
175
elif company.timesheet_range == 'week':
176
start_date = DateTime.Date(date.tm_year, date.tm_mon, date.tm_mday) + DateTime.RelativeDateTime(weekday=(DateTime.Monday,0))
177
end_date = DateTime.Date(date.tm_year, date.tm_mon, date.tm_mday) + DateTime.RelativeDateTime(weekday=(DateTime.Sunday,0))
178
# return the first and last days of the year
179
elif company.timesheet_range == 'year':
180
start_date = DateTime.Date(date.tm_year, 1, 1)
181
end_date = DateTime.Date(date.tm_year, 12, 31)
183
return (start_date, end_date)
186
def compute_employees_list(self, cr, uid, context, company):
187
""" return a dictionnary of lists of employees ids linked to the companies (param company) """
188
hr_employee_object = self.pool.get('hr.employee')
193
#employees associated with a Tinyerp user
194
users_ids = self.pool.get('res.users').search(cr, uid, [('company_id', '=', company.id)], context=context)
195
employees_users_ids = hr_employee_object.search(cr, uid, [('user_id', 'in', users_ids)])
197
#combine the two employees list, remove duplicates and order by name DESC
198
employees_ids = hr_employee_object.search(cr, uid, [('id', 'in', employees_users_ids)], order="name ASC", context=context)
200
return hr_employee_object.browse(cr, uid, employees_ids, context=context)
205
def compute_timesheet_status(self, cr, uid, context, obj, employee, period):
206
""" return the timesheet status for a user and a period """
210
time_from = time.strptime(str(period[0]),"%Y-%m-%d %H:%M:%S.00")
211
time_to = time.strptime(str(period[1]), "%Y-%m-%d %H:%M:%S.00")
213
#if the starting date is defined and is greater than the date_to, it means the employee wasn't one at this period
214
if employee.started != None and (employee.started != False) and time.strptime(employee.started,"%Y-%m-%d") > time_to:
215
status = 'Not in Company'
216
#if the ending date is defined and is earlier than the date_from, it means the employee wasn't one at this period
217
elif employee.ended != None and (employee.ended != False) and time.strptime(employee.ended, "%Y-%m-%d") < time_from:
218
status = 'Not in Company'
219
#the employee was in the company at this period
223
# does the timesheet exsists in db and what is its status?
224
timeformat = "%Y-%m-%d"
225
date_from = time.strftime(timeformat, time_from )
226
date_to = time.strftime(timeformat, time_to )
230
if employee.user_id.id != False:
231
query = """SELECT state, date_from, date_to FROM hr_timesheet_sheet_sheet
233
AND date_from >= '%s'
235
""" % (employee.user_id.id, date_from, date_to)
237
sheets = cr.dictfetchall()
239
#the tiemsheet does not exists in db
246
if s['state'] == 'draft':
253
def get_cron_id(self, cr, uid, context):
254
"""return the reminder cron's id. Create one if the cron does not exists """
257
cron_obj = self.pool.get('ir.cron')
259
#find the cron that send messages
260
cron_id = cron_obj.search(cr, uid, [('function', 'ilike', self.cron['function']), ('model', 'ilike', self.cron['model'])], context={'active_test': False} )
261
cron_id = int(cron_id[0])
264
print 'warning cron not found one will be created'
265
pass # ignore if the cron is missing cause we are going to create it in db
267
#the cron does not exists
270
self.cron['name'] = _('timesheet status reminder')
271
cron_id = cron_obj.create(cr, uid, self.cron, context)
278
def get_message_id(self, cr, uid, context):
279
""" return the message'id. create one if the message does not exists """
283
#there is only one line in db, let's get it
284
message_id = int(self.search(cr, uid, [], offset=0, limit=1, context=context)[0])
286
#"unable to find the message". I ignore this error and try to create the message if it does not exists
289
#the message does not exists
292
self.message['subject'] = _('Timesheet Reminder')
293
self.message['message'] = _('At least one of your last timesheets is still in draft or is missing. Please take time to complete and confirm it.')
295
message_id = self.create(cr, uid, self.message, context)
301
def get_config(self, cr, uid, context):
302
"""return the reminder config from the db """
304
cron_id = self.get_cron_id(cr, uid, context)
306
cron_data = self.pool.get('ir.cron').browse(cr, uid, cron_id)
308
#there is only one line in db, let's get it
309
message_id = self.get_message_id(cr, uid, context)
310
message_data = self.browse(cr, uid, message_id)
311
return { 'reminder_active': cron_data.active ,
312
'interval_type': cron_data.interval_type,
313
'interval_number': cron_data.interval_number,
314
'reply_to': message_data.reply_to,
315
'message': message_data.message ,
316
'subject': message_data.subject,
317
'nextcall': cron_data.nextcall,
322
def save_config(self, cr, uid, datas, context):
323
"""save the reminder config """
325
cron_id = self.get_cron_id(cr, uid, context)
326
result = self.pool.get('ir.cron').write(cr, uid, [cron_id], {'active': datas['reminder_active'],
327
'interval_number' : datas['interval_number'],
328
'interval_type' : datas['interval_type'],
329
'nextcall' : datas['nextcall'],
332
message_id = self.get_message_id(cr, uid, context)
333
result = self.write(cr, uid, [message_id], {'reply_to': datas['reply_to'],
334
'message': datas['message'],
335
'subject': datas['subject'],
338
#et pour finir, fait un petit tour de passe passe, huhuhu
343
#return an empty dictionnary because actions method must do so...
b'\\ No newline at end of file'