diff --git a/erpnext/projects/doctype/project_control/project_control.py b/erpnext/projects/doctype/project_control/project_control.py index 82cb1c77e7..6d41ea95dc 100644 --- a/erpnext/projects/doctype/project_control/project_control.py +++ b/erpnext/projects/doctype/project_control/project_control.py @@ -1,13 +1,13 @@ # Please edit this list and import only required elements import webnotes -from webnotes.utils import add_days, add_months, add_years, cint, cstr, date_diff, default_fields, flt, fmt_money, formatdate, generate_hash, getTraceback, get_defaults, get_first_day, get_last_day, getdate, has_common, month_name, now, nowdate, replace_newlines, sendmail, set_default, str_esc_quote, user_format, validate_email_add +from webnotes.utils import add_days, add_months, add_years, cint, cstr, date_diff, default_fields, flt, fmt_money, formatdate, generate_hash, getTraceback, get_defaults, get_first_day, get_last_day, getdate, has_common, month_name, now, nowdate, replace_newlines, set_default, str_esc_quote, user_format, validate_email_add, add_days from webnotes.model import db_exists from webnotes.model.doc import Document, addchild, removechild, getchildren, make_autoname, SuperDocType from webnotes.model.doclist import getlist, copy_doclist from webnotes.model.code import get_obj, get_server_obj, run_server_obj, updatedb, check_syntax from webnotes import session, form, is_testing, msgprint, errprint - +from webnotes.utils.email_lib import sendmail set = webnotes.conn.set sql = webnotes.conn.sql get_value = webnotes.conn.get_value @@ -18,99 +18,125 @@ convert_to_lists = webnotes.conn.convert_to_lists class DocType: - def __init__(self,d,dl): - self.doc, self.doclist = d,dl - - def get_projects(self, arg): - # project list - pl=[] - status={} - if arg == 'Open': - pl = [p[0] for p in sql("select name from `tabProject` where status = 'Open' order by creation desc limit 20")] - for p1 in pl: - status[p1] = 'Open' - elif arg == 'Completed': - pl = [p[0] for p in sql("select name from `tabProject` where status = 'Completed' order by creation desc limit 20")] - for p2 in pl: - status[p2] = 'Completed' - elif arg == 'Cancelled': - pl = [p[0] for p in sql("select name from `tabProject` where status = 'Cancelled' order by creation desc limit 20")] - for p3 in pl: - status[p3] = 'Cancelled' - else: - #pl = [p[0] for p in sql("select name from `tabProject` order by creation desc limit 20")] - pl1 = sql("select name, status from `tabProject` order by creation desc limit 20", as_dict=1) - for p4 in pl1: - status[p4['name']] = p4['status'] - pl.append(p4['name']) - - # milestones in the next 7 days for active projects - ml = convert_to_lists(sql("select t1.milestone_date, t1.milestone, t1.parent from `tabProject Milestone` t1, tabProject t2 where t1.parent = t2.name and t2.status='Open' and DATEDIFF(t1.milestone_date, CURDATE()) BETWEEN 0 AND 7 ORDER BY t1.milestone_date ASC")) + def __init__(self,d,dl): + self.doc, self.doclist = d,dl + + def get_projects(self, arg): + # project list + pl=[] + status={} + if arg == 'Open': + pl = [p[0] for p in sql("select name from `tabProject` where status = 'Open' order by creation desc limit 20")] + for p1 in pl: + status[p1] = 'Open' + elif arg == 'Completed': + pl = [p[0] for p in sql("select name from `tabProject` where status = 'Completed' order by creation desc limit 20")] + for p2 in pl: + status[p2] = 'Completed' + elif arg == 'Cancelled': + pl = [p[0] for p in sql("select name from `tabProject` where status = 'Cancelled' order by creation desc limit 20")] + for p3 in pl: + status[p3] = 'Cancelled' + else: + #pl = [p[0] for p in sql("select name from `tabProject` order by creation desc limit 20")] + pl1 = sql("select name, status from `tabProject` order by creation desc limit 20", as_dict=1) + for p4 in pl1: + status[p4['name']] = p4['status'] + pl.append(p4['name']) + + # milestones in the next 7 days for active projects + ml = convert_to_lists(sql("select t1.milestone_date, t1.milestone, t1.parent from `tabProject Milestone` t1, tabProject t2 where t1.parent = t2.name and t2.status='Open' and DATEDIFF(t1.milestone_date, CURDATE()) BETWEEN 0 AND 7 ORDER BY t1.milestone_date ASC")) - # percent of activity completed per project - comp = {} - n_tasks = {} - - for p in pl: - t1 = sql('select count(*) from tabTicket where project=%s and docstatus!=2', p)[0][0] - n_tasks[p] = t1 or 0 - if t1: - t2 = sql('select count(*) from tabTicket where project=%s and docstatus!=2 and status="Closed"', p)[0][0] - comp[p] = cint(flt(t2)*100/t1) - - return {'pl':pl, 'ml':ml, 'comp':comp, 'n_tasks':n_tasks, 'status':status} - - def get_resources(self): - ret = {} + # percent of activity completed per project + comp = {} + n_tasks = {} + + for p in pl: + t1 = sql('select count(*) from tabTicket where project=%s and docstatus!=2', p)[0][0] + n_tasks[p] = t1 or 0 + if t1: + t2 = sql('select count(*) from tabTicket where project=%s and docstatus!=2 and status="Closed"', p)[0][0] + comp[p] = cint(flt(t2)*100/t1) + + return {'pl':pl, 'ml':ml, 'comp':comp, 'n_tasks':n_tasks, 'status':status} + + def get_resources(self): + ret = {} - # resource list - rl = sql("select distinct allocated_to, assignee_email from tabTicket") + # resource list + rl = sql("select distinct allocated_to, assignee_email from tabTicket") - # get open & closed tickets - for r in rl: - if r[0]: - ret[r[1]] = {} - ret[r[1]]['id'] = r[0] - ret[r[1]]['Total'] = sql("select count(*) from tabTicket where allocated_to=%s and docstatus!=2", r[0])[0][0] - ret[r[1]]['Closed'] = sql("select count(*) from tabTicket where allocated_to=%s and status='Closed' and docstatus!=2", r[0])[0][0] - ret[r[1]]['percent'] = cint(flt(ret[r[1]]['Closed']) * 100 / ret[r[1]]['Total']) + # get open & closed tickets + for r in rl: + if r[0]: + ret[r[1]] = {} + ret[r[1]]['id'] = r[0] + ret[r[1]]['Total'] = sql("select count(*) from tabTicket where allocated_to=%s and docstatus!=2", r[0])[0][0] + ret[r[1]]['Closed'] = sql("select count(*) from tabTicket where allocated_to=%s and status='Closed' and docstatus!=2", r[0])[0][0] + ret[r[1]]['percent'] = cint(flt(ret[r[1]]['Closed']) * 100 / ret[r[1]]['Total']) - return ret + return ret - # -------------------------------------------------------------- - # for Gantt Chart + # -------------------------------------------------------------- + # for Gantt Chart - def get_init_data(self, arg=''): - pl = [p[0] for p in sql('select name from tabProject where docstatus != 2')] - rl = [p[0] for p in sql('select distinct allocated_to from tabTicket where docstatus != 2 and ifnull(allocated_to,"") != ""')] - return {'pl':pl, 'rl':rl} + def get_init_data(self, arg=''): + pl = [p[0] for p in sql('select name from tabProject where docstatus != 2')] + rl = [p[0] for p in sql('select distinct allocated_to from tabTicket where docstatus != 2 and ifnull(allocated_to,"") != ""')] + return {'pl':pl, 'rl':rl} - def get_tasks(self, arg): - start_date, end_date, project, resource = arg.split('~~~') + def get_tasks(self, arg): + start_date, end_date, project, resource = arg.split('~~~') - cl = '' - if project and project != 'All': - cl = " and ifnull(project,'') = '%s'" % project + cl = '' + if project and project != 'All': + cl = " and ifnull(project,'') = '%s'" % project - if resource and resource != 'All': - cl = " and ifnull(allocated_to,'') = '%s'" % resource + if resource and resource != 'All': + cl = " and ifnull(allocated_to,'') = '%s'" % resource - tl = sql(""" - select subject, allocated_to, project, exp_start_date, exp_end_date, priority, status, name - from tabTicket - where - ((exp_start_date between '%(st)s' and '%(end)s') or - (exp_end_date between '%(st)s' and '%(end)s') or - (exp_start_date < '%(st)s' and exp_end_date > '%(end)s')) %(cond)s order by exp_start_date limit 100""" % {'st': start_date, 'end': end_date, 'cond':cl}) + tl = sql(""" + select subject, allocated_to, project, exp_start_date, exp_end_date, priority, status, name + from tabTicket + where + ((exp_start_date between '%(st)s' and '%(end)s') or + (exp_end_date between '%(st)s' and '%(end)s') or + (exp_start_date < '%(st)s' and exp_end_date > '%(end)s')) %(cond)s order by exp_start_date limit 100""" % {'st': start_date, 'end': end_date, 'cond':cl}) - return convert_to_lists(tl) - - def declare_proj_completed(self, arg): - chk = sql("select name from `tabTicket` where project=%s and status='Open'", arg) - if chk: - chk_lst = [x[0] for x in chk] - msgprint("Task(s) "+','.join(chk_lst)+" has staus 'Open'. Please submit all tasks against this project before closing the project.") - return cstr('false') - else: - sql("update `tabProject` set status = 'Completed' where name = %s", arg) - return cstr('true') \ No newline at end of file + return convert_to_lists(tl) + + def declare_proj_completed(self, arg): + chk = sql("select name from `tabTicket` where project=%s and status='Open'", arg) + if chk: + chk_lst = [x[0] for x in chk] + msgprint("Task(s) "+','.join(chk_lst)+" has staus 'Open'. Please submit all tasks against this project before closing the project.") + return cstr('false') + else: + sql("update `tabProject` set status = 'Completed' where name = %s", arg) + return cstr('true') + + +def sent_reminder_task(): + task_list = sql(""" + select subject, allocated_to, project, exp_start_date, exp_end_date, + priority, status, name, senders_name, opening_date, review_date, description + from tabTicket + where task_email_notify=1 + and sent_reminder=0 + and status='Open' + and exp_start_date is not null""",as_dict=1) + for i in task_list: + if date_diff(i['exp_start_date'],nowdate()) ==2: + msg2="""
This is a reminder for the task %(name)s has been assigned to you + by %(senders_name)s on %(opening_date)s
+Project: %(project)s
+Review Date: %(review_date)s
+Details: %(description)s
+If you have already completed this task, please update the system
+Good Luck!
+(This notification is autogenerated)
""" % i + sendmail(i['allocated_to'], sender='automail@webnotestech.com', msg=msg2,send_now=1, \ + subject='A task has been assigned') + sql("update `tabTicket` set sent_reminder='1' where name='%(name)s' and allocated_to= '%(allocated_to)s'" % i) + diff --git a/erpnext/projects/doctype/ticket/ticket.py b/erpnext/projects/doctype/ticket/ticket.py index 4d3e988f11..5cd19283eb 100644 --- a/erpnext/projects/doctype/ticket/ticket.py +++ b/erpnext/projects/doctype/ticket/ticket.py @@ -1,12 +1,14 @@ # Please edit this list and import only required elements import webnotes -from webnotes.utils import add_days, add_months, add_years, cint, cstr, date_diff, default_fields, flt, fmt_money, formatdate, generate_hash, getTraceback, get_defaults, get_first_day, get_last_day, getdate, has_common, month_name, now, nowdate, replace_newlines, sendmail, set_default, str_esc_quote, user_format, validate_email_add +from webnotes.utils import add_days, add_months, add_years, cint, cstr, date_diff, default_fields, flt, fmt_money, formatdate, generate_hash, getTraceback, get_defaults, get_first_day, get_last_day, getdate, has_common, month_name, now, nowdate, replace_newlines, set_default, str_esc_quote, user_format, validate_email_add +from webnotes.utils.email_lib import sendmail from webnotes.model import db_exists from webnotes.model.doc import Document, addchild, removechild, getchildren, make_autoname, SuperDocType from webnotes.model.doclist import getlist, copy_doclist from webnotes.model.code import get_obj, get_server_obj, run_server_obj, updatedb, check_syntax from webnotes import session, form, is_testing, msgprint, errprint + sql = webnotes.conn.sql set = webnotes.conn.set get_value = webnotes.conn.get_value @@ -15,161 +17,196 @@ get_value = webnotes.conn.get_value class DocType: - def __init__(self,doc,doclist=[]): - self.doc = doc - self.doclist = doclist - - def get_project_details(self): - cust = sql("select customer, customer_name from `tabProject` where name = %s", self.doc.project) - if cust: - ret = {'customer': cust and cust[0][0] or '', 'customer_name': cust and cust[0][1] or ''} - return ret - - def get_customer_details(self): - cust = sql("select customer_name from `tabCustomer` where name=%s", self.doc.customer) - if cust: - ret = {'customer_name': cust and cust[0][0] or ''} - return ret - - def get_allocated_to_name(self): - as_em = sql("select first_name, last_name from `tabProfile` where name=%s",str(self.doc.allocated_to)) - ret = { 'allocated_to_name' : as_em and (as_em[0][0] + ' ' + as_em[0][1]) or ''} - return ret + def __init__(self,doc,doclist=[]): + self.doc = doc + self.doclist = doclist + + def get_project_details(self): + cust = sql("select customer, customer_name from `tabProject` where name = %s", self.doc.project) + if cust: + ret = {'customer': cust and cust[0][0] or '', 'customer_name': cust and cust[0][1] or ''} + return ret + + def get_customer_details(self): + cust = sql("select customer_name from `tabCustomer` where name=%s", self.doc.customer) + if cust: + ret = {'customer_name': cust and cust[0][0] or ''} + return ret + + def get_allocated_to_name(self): + as_em = sql("select first_name, last_name from `tabProfile` where name=%s",str(self.doc.allocated_to)) + ret = { 'allocated_to_name' : as_em and (as_em[0][0] + ' ' + as_em[0][1]) or ''} + return ret - # validate - #-------------------------------------------- + # validate + #-------------------------------------------- - def validate(self): - if not self.doc.opening_date: - msgprint("Please enter Opening Date.") - raise Exception - elif getdate(self.doc.opening_date) > getdate(nowdate()): - msgprint("Opening date can not be future date") - raise Exception - - if self.doc.exp_start_date and self.doc.exp_end_date and getdate(self.doc.exp_start_date) > getdate(self.doc.exp_end_date): - msgprint("'Expected Start Date' can not be greater than 'Expected End Date'") - raise Exception - - if self.doc.act_start_date and self.doc.act_end_date and getdate(self.doc.act_start_date) > getdate(self.doc.act_end_date): - msgprint("'Actual Start Date' can not be greater than 'Actual End Date'") - raise Exception - - if self.doc.opening_date and self.doc.review_date and getdate(self.doc.opening_date) > getdate(self.doc.review_date): - msgprint("Review Date should be greater than or equal to Opening Date ") - raise Exception - - if self.doc.closing_date and self.doc.review_date and getdate(self.doc.closing_date) < getdate(self.doc.review_date): - msgprint("Closing Date should be greater than or equal to Review Date ") - raise Exception + def validate(self): + if not self.doc.opening_date: + msgprint("Please enter Opening Date.") + raise Exception + elif getdate(self.doc.opening_date) > getdate(nowdate()): + msgprint("Opening date can not be future date") + raise Exception + + if self.doc.exp_start_date and self.doc.exp_end_date and getdate(self.doc.exp_start_date) > getdate(self.doc.exp_end_date): + msgprint("'Expected Start Date' can not be greater than 'Expected End Date'") + raise Exception + + if self.doc.act_start_date and self.doc.act_end_date and getdate(self.doc.act_start_date) > getdate(self.doc.act_end_date): + msgprint("'Actual Start Date' can not be greater than 'Actual End Date'") + raise Exception + + if self.doc.opening_date and self.doc.review_date and getdate(self.doc.opening_date) > getdate(self.doc.review_date): + msgprint("Review Date should be greater than or equal to Opening Date ") + raise Exception + + if self.doc.closing_date and self.doc.review_date and getdate(self.doc.closing_date) < getdate(self.doc.review_date): + msgprint("Closing Date should be greater than or equal to Review Date ") + raise Exception + + # on update + #-------------------------------------------- + + def on_update(self): + if self.doc.status =='Open' and self.doc.allocated_to: + if self.doc.task_email_notify and (self.doc.allocated_to != self.doc.allocated_to_old): + self.doc.sent_reminder = 0 + self.doc.allocated_to_old = self.doc.allocated_to + self.sent_notification() + if self.doc.exp_start_date: + sql("delete from tabEvent where ref_type='Task' and ref_name=%s", self.doc.name) + self.add_calendar_event() + else: + msgprint("An Expeted start date has not been set for this task.Please set a, 'Expected Start date'\ + to add an event to allocated persons calender.You can save a task without this also.") + + + def validate_for_pending_review(self): + if not self.doc.allocated_to: + msgprint("Please enter allocated_to.") + raise Exception + self.validate_with_timesheet_dates() + + #Sent Notification + def sent_notification(self): + msg2="""This is a Notification for the task %(name)s that has been assigned to you + by %(senders_name)s on %(opening_date)s
+Project: %(project)s
+Review Date: %(review_date)s
+Details: %(description)s
+You will also recieve another reminder 2 days before the commencement of the task
+Good Luck!
+(This notification is autogenerated)
""" % i + sendmail(self.doc.allocated_to, sender='automail@webnotestech.com', msg=msg2,send_now=1,\ + subject='A task has been assigned') + + + + #validate before closing task + def validate_for_closed(self): + self.check_non_submitted_timesheets() + self.get_actual_total_hrs() + + def check_non_submitted_timesheets(self): + chk = sql("select t1.name from `tabTimesheet` t1, `tabTimesheet Detail` t2 where t2.parent=t1.name and t2.task_id=%s and t1.status='Draft'", self.doc.name) + if chk: + chk_lst = [x[0] for x in chk] + msgprint("Please submit timesheet(s) : "+','.join(chk_lst)+" before declaring this task as completed. As details of this task present in timesheet(s)") + raise Exception + + #calculate actual total hours taken to complete task from timesheets + def get_actual_total_hrs(self): + import datetime + import time + chk = sql("select t2.act_total_hrs from `tabTimesheet` t1, `tabTimesheet Detail` t2 where t2.parent = t1.name and t2.task_id = %s and t1.status = 'Submitted' and ifnull(t2.act_total_hrs, '')!='' order by t1.timesheet_date asc", self.doc.name) + if chk: + chk_lst = [x[0] for x in chk] + actual_total = total =0 + + for m in chk_lst: + m1, m2=[], 0 + m1 = m.split(":") + m2 = (datetime.timedelta(minutes=cint(m1[1]), hours=cint(m1[0]))).seconds + total = total + m2 + + actual_total = time.strftime("%H:%M", time.gmtime(total)) + set(self.doc, 'act_total_hrs', actual_total) + + # validate and fetch actual start and end date + def validate_with_timesheet_dates(self): + chk = sql("select t1.name, t1.timesheet_date from `tabTimesheet` t1, `tabTimesheet Detail` t2 where t2.parent = t1.name and t2.task_id = %s and t1.status = 'Submitted' order by t1.timesheet_date asc", self.doc.name, as_dict=1) + if chk: + if self.doc.act_start_date: + if chk[0]['timesheet_date'] > getdate(self.doc.act_start_date) or chk[0]['timesheet_date'] < getdate(self.doc.act_start_date): + msgprint("Actual start date of this task is "+cstr(chk[0]['timesheet_date'])+" as per timesheet "+cstr(chk[0]['name'])) + raise Exception + else: + self.doc.act_start_date = chk[0]['timesheet_date'] + + if self.doc.act_end_date: + if chk[len(chk)-1]['timesheet_date'] < getdate(self.doc.act_end_date) or chk[len(chk)-1]['timesheet_date'] > getdate(self.doc.act_end_date): + msgprint("Actual end date of this task is "+cstr(chk[len(chk)-1]['timesheet_date'])+" as per timesheet "+cstr(chk[len(chk)-1]['name'])) + raise Exception + else: + self.doc.act_end_date = chk[len(chk)-1]['timesheet_date'] + + def set_for_review(self): + self.check_non_submitted_timesheets() + self.validate_for_pending_review() + self.get_actual_total_hrs() + self.doc.review_date = nowdate() + self.doc.status = 'Pending Review' + self.doc.save() + return cstr('true') + + def reopen_task(self): + self.doc.status = 'Open' + self.doc.save() + return cstr('true') + + def declare_completed(self): + if self.doc.status == 'Open': + self.validate_for_pending_review() + self.doc.review_date = nowdate() + else: + self.validate_with_timesheet_dates() + self.validate_for_closed() + self.doc.closing_date = nowdate() + self.doc.status = 'Closed' + self.remove_event_from_calender() + self.doc.docstatus = 1 + self.doc.save() + return cstr('true') + + def remove_event_from_calender(self): + sql("delete from tabEvent where ref_type='Task' and ref_name=%s", self.doc.name) + self.doc.save() + + def cancel_task(self): + chk = sql("select distinct t1.name from `tabTimesheet` t1, `tabTimesheet Detail` t2 where t2.parent = t1.name and t2.task_id = %s and t1.status!='Cancelled'", self.doc.name) + if chk: + chk_lst = [x[0] for x in chk] + msgprint("Timesheet(s) "+','.join(chk_lst)+" created against this task. Thus can not be cancelled") + raise Exception + else: + self.doc.status = 'Cancelled' + self.doc.docstatus = 2 + self.remove_event_from_calender() + self.doc.save() + return cstr('true') + + + def add_calendar_event(self): + event = Document('Event') + event.owner = self.doc.allocated_to + event.description ='' + event.event_date = self.doc.exp_start_date and self.doc.exp_start_date or '' + event.event_hour = self.doc.event_hour and self.doc.event_hour or '10:00' + event.event_type = 'Private' + event.ref_type = 'Task' + event.ref_name = self.doc.name + event.save(1) - # on update - #-------------------------------------------- - - def on_update(self): - if (self.doc.status =='Open') and (self.doc.task_email_notify==1): - msg2= 'A task %s has been assigned to you by %s on %s