diff --git a/erpnext/config/projects.py b/erpnext/config/projects.py index ac11c7e45c..2f8b92d3d8 100644 --- a/erpnext/config/projects.py +++ b/erpnext/config/projects.py @@ -12,6 +12,11 @@ def get_data(): "name": "Project", "description": _("Project master."), }, + { + "type": "doctype", + "name": "Project Update", + "description": _("Project Update."), + }, { "type": "doctype", "name": "Task", diff --git a/erpnext/projects/doctype/project/README.md b/erpnext/projects/doctype/project/README.md index b1da6ad328..7d513cb74e 100644 --- a/erpnext/projects/doctype/project/README.md +++ b/erpnext/projects/doctype/project/README.md @@ -1 +1 @@ -Project details. Projects can be internal or external and can have Tasks, Milestones associated to it. \ No newline at end of file +Project details. Projects can be internal or external and can have Tasks, Milestones associated to it. diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index cc8433c5e6..682398fa9e 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -1,18 +1,15 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt - frappe.ui.form.on("Project", { - setup: function(frm) { + setup: function (frm) { frm.set_indicator_formatter('title', - function(doc) { + function (doc) { let indicator = 'orange'; if (doc.status == 'Overdue') { indicator = 'red'; - } - else if (doc.status == 'Cancelled') { + } else if (doc.status == 'Cancelled') { indicator = 'dark grey'; - } - else if (doc.status == 'Closed') { + } else if (doc.status == 'Closed') { indicator = 'green'; } return indicator; @@ -20,10 +17,10 @@ frappe.ui.form.on("Project", { ); }, - onload: function(frm) { + onload: function (frm) { var so = frappe.meta.get_docfield("Project", "sales_order"); - so.get_route_options_for_new_doc = function(field) { - if(frm.is_new()) return; + so.get_route_options_for_new_doc = function (field) { + if (frm.is_new()) return; return { "customer": frm.doc.customer, "project_name": frm.doc.name @@ -32,14 +29,14 @@ frappe.ui.form.on("Project", { frm.set_query('customer', 'erpnext.controllers.queries.customer_query'); - frm.set_query("user", "users", function() { + frm.set_query("user", "users", function () { return { - query:"erpnext.projects.doctype.project.project.get_users_for_project" + query: "erpnext.projects.doctype.project.project.get_users_for_project" } }); // sales order - frm.set_query('sales_order', function() { + frm.set_query('sales_order', function () { var filters = { 'project': ["in", frm.doc.__islocal ? [""] : [frm.doc.name, ""]] }; @@ -54,15 +51,17 @@ frappe.ui.form.on("Project", { }); }, - refresh: function(frm) { - if(frm.doc.__islocal) { + refresh: function (frm) { + if (frm.doc.__islocal) { frm.web_link && frm.web_link.remove(); } else { frm.add_web_link("/projects?project=" + encodeURIComponent(frm.doc.name)); - if(frappe.model.can_read("Task")) { - frm.add_custom_button(__("Gantt Chart"), function() { - frappe.route_options = {"project": frm.doc.name}; + if (frappe.model.can_read("Task")) { + frm.add_custom_button(__("Gantt Chart"), function () { + frappe.route_options = { + "project": frm.doc.name + }; frappe.set_route("List", "Task", "Gantt"); }); } @@ -70,65 +69,69 @@ frappe.ui.form.on("Project", { frm.trigger('show_dashboard'); } }, - tasks_refresh: function(frm) { + tasks_refresh: function (frm) { var grid = frm.get_field('tasks').grid; - grid.wrapper.find('select[data-fieldname="status"]').each(function() { - if($(this).val()==='Open') { + grid.wrapper.find('select[data-fieldname="status"]').each(function () { + if ($(this).val() === 'Open') { $(this).addClass('input-indicator-open'); } else { $(this).removeClass('input-indicator-open'); } }); }, - show_dashboard: function(frm) { - if(frm.doc.__onload.activity_summary.length) { - var hours = $.map(frm.doc.__onload.activity_summary, function(d) { return d.total_hours }); - var max_count = Math.max.apply(null, hours); - var sum = hours.reduce(function(a, b) { return a + b; }, 0); - var section = frm.dashboard.add_section( - frappe.render_template('project_dashboard', - { - data: frm.doc.__onload.activity_summary, - max_count: max_count, - sum: sum - })); - - section.on('click', '.time-sheet-link', function() { - var activity_type = $(this).attr('data-activity_type'); - frappe.set_route('List', 'Timesheet', - {'activity_type': activity_type, 'project': frm.doc.name, 'status': ["!=", "Cancelled"]}); - }); - } - } -}); - -frappe.ui.form.on("Project Task", { - edit_task: function(frm, doctype, name) { + edit_task: function (frm, doctype, name) { var doc = frappe.get_doc(doctype, name); - if(doc.task_id) { + if (doc.task_id) { frappe.set_route("Form", "Task", doc.task_id); } else { frappe.msgprint(__("Save the document first.")); } }, - edit_timesheet: function(frm, cdt, cdn) { + edit_timesheet: function (frm, cdt, cdn) { var child = locals[cdt][cdn]; - frappe.route_options = {"project": frm.doc.project_name, "task": child.task_id}; + frappe.route_options = { + "project": frm.doc.project_name, + "task": child.task_id + }; frappe.set_route("List", "Timesheet"); }, - - make_timesheet: function(frm, cdt, cdn) { + make_timesheet: function (frm, cdt, cdn) { var child = locals[cdt][cdn]; - frappe.model.with_doctype('Timesheet', function() { - var doc = frappe.model.get_new_doc('Timesheet'); - var row = frappe.model.add_child(doc, 'time_logs'); - row.project = frm.doc.project_name; - row.task = child.task_id; - frappe.set_route('Form', doc.doctype, doc.name); - }) + frappe.model.with_doctype('Timesheet', function () { + var doc = frappe.model.get_new_doc('Timesheet'); + var row = frappe.model.add_child(doc, 'time_logs'); + row.project = frm.doc.project_name; + row.task = child.task_id; + frappe.set_route('Form', doc.doctype, doc.name); + }) }, - status: function(frm, doctype, name) { + status: function (frm, doctype, name) { frm.trigger('tasks_refresh'); }, + }); + + +frappe.ui.form.on("Project", "validate", function (frm) { + frappe.call({ + method: "erpnext.projects.doctype.project.project.times_check", + args: { + "from1": frm.doc.from, + "to": frm.doc.to, + "first_email": frm.doc.first_email, + "second_email": frm.doc.second_email, + "daily_time_to_send": frm.doc.daily_time_to_send, + "weekly_time_to_send": frm.doc.weekly_time_to_send + + }, + callback: function (r) { + frm.set_value("from", r.message.from1); + frm.set_value("to", r.message.to); + frm.set_value("first_email", r.message.first_email); + frm.set_value("second_email", r.message.second_email); + frm.set_value("daily_time_to_send", r.message.daily_time_to_send); + frm.set_value("weekly_time_to_send", r.message.weekly_time_to_send); + } + }); +}); \ No newline at end of file diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 5d95bd3904..7b4d7188e2 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -1272,6 +1272,347 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "monitor_progress", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Monitor Progress", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "collect_progress", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Collect Progress", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.collect_progress == true", + "fieldname": "frequency", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Frequency To Collect Progress", + "length": 0, + "no_copy": 0, + "options": "Hourly\nTwice Daily\nDaily\nWeekly", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_45", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.frequency == \"Hourly\" && doc.collect_progress == true)", + "fieldname": "from", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "From", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.frequency == \"Hourly\" && doc.collect_progress == true)", + "fieldname": "to", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "To", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.frequency == \"Twice Daily\" && doc.collect_progress == true)\n\n", + "fieldname": "first_email", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "First Email", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.frequency == \"Twice Daily\" && doc.collect_progress == true)", + "fieldname": "second_email", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Second Email", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.frequency == \"Daily\" && doc.collect_progress == true)", + "fieldname": "daily_time_to_send", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Time to send", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)", + "fieldname": "day_to_send", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Day to Send", + "length": 0, + "no_copy": 0, + "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)", + "fieldname": "weekly_time_to_send", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Time to send", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "has_web_view": 0, @@ -1285,7 +1626,7 @@ "issingle": 0, "istable": 0, "max_attachments": 4, - "modified": "2017-12-10 08:40:46.843201", + "modified": "2018-01-29 11:48:21.156697", "modified_by": "Administrator", "module": "Projects", "name": "Project", diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 221c1d3137..02a4afb6d5 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -10,6 +10,7 @@ from frappe import _ from frappe.model.document import Document from erpnext.controllers.queries import get_filters_cond from frappe.desk.reportview import get_match_cond +import datetime from six import iteritems @@ -79,7 +80,8 @@ class Project(Document): if task.task_weight or 0 > 0: sum = sum + task.task_weight if sum > 0 and sum != 1: - frappe.throw(_("Total of all task weights should be 1. Please adjust weights of all Project tasks accordingly")) + frappe.throw( + _("Total of all task weights should be 1. Please adjust weights of all Project tasks accordingly")) def sync_tasks(self): """sync tasks and remove table""" @@ -105,7 +107,7 @@ class Project(Document): task.flags.ignore_links = True task.flags.from_project = True task.flags.ignore_feed = True - task.save(ignore_permissions = True) + task.save(ignore_permissions=True) task_names.append(task.name) # delete @@ -127,18 +129,18 @@ class Project(Document): self.update_percent_complete() self.update_costing() self.flags.dont_sync_tasks = True - self.save(ignore_permissions = True) + self.save(ignore_permissions=True) def after_insert(self): if self.sales_order: frappe.db.set_value("Sales Order", self.sales_order, "project", self.name) - def update_percent_complete(self): total = frappe.db.sql("""select count(name) from tabTask where project=%s""", self.name)[0][0] if not total and self.percent_complete: self.percent_complete = 0 - if (self.percent_complete_method == "Task Completion" and total > 0) or (not self.percent_complete_method and total > 0): + if (self.percent_complete_method == "Task Completion" and total > 0) or ( + not self.percent_complete_method and total > 0): completed = frappe.db.sql("""select count(name) from tabTask where project=%s and status in ('Closed', 'Cancelled')""", self.name)[0][0] self.percent_complete = flt(flt(completed) / total * 100, 2) @@ -153,8 +155,8 @@ class Project(Document): project=%s""", self.name)[0][0] if weight_sum == 1: weighted_progress = frappe.db.sql("""select progress,task_weight from tabTask where - project=%s""", self.name,as_dict=1) - pct_complete=0 + project=%s""", self.name, as_dict=1) + pct_complete = 0 for row in weighted_progress: pct_complete += row["progress"] * row["task_weight"] self.percent_complete = flt(flt(pct_complete), 2) @@ -172,7 +174,7 @@ class Project(Document): sum(total_sanctioned_amount) as total_sanctioned_amount from `tabExpense Claim` where project = %s and docstatus = 1""", - self.name, as_dict=1)[0] + self.name, as_dict=1)[0] self.actual_start_date = from_time_sheet.start_date self.actual_end_date = from_time_sheet.end_date @@ -186,10 +188,11 @@ class Project(Document): self.update_sales_amount() self.update_billed_amount() - self.gross_margin = flt(self.total_billed_amount) - (flt(self.total_costing_amount) + flt(self.total_expense_claim) + flt(self.total_purchase_cost)) + self.gross_margin = flt(self.total_billed_amount) - ( + flt(self.total_costing_amount) + flt(self.total_expense_claim) + flt(self.total_purchase_cost)) if self.total_billed_amount: - self.per_gross_margin = (self.gross_margin / flt(self.total_billed_amount)) *100 + self.per_gross_margin = (self.gross_margin / flt(self.total_billed_amount)) * 100 def update_purchase_costing(self): total_purchase_cost = frappe.db.sql("""select sum(base_net_amount) @@ -209,13 +212,12 @@ class Project(Document): self.total_billed_amount = total_billed_amount and total_billed_amount[0][0] or 0 - def send_welcome_email(self): url = get_url("/project/?name={0}".format(self.name)) messages = ( - _("You have been invited to collaborate on the project: {0}".format(self.name)), - url, - _("Join") + _("You have been invited to collaborate on the project: {0}".format(self.name)), + url, + _("Join") ) content = """ @@ -224,9 +226,10 @@ class Project(Document): """ for user in self.users: - if user.welcome_email_sent==0: - frappe.sendmail(user.user, subject=_("Project Collaboration Invitation"), content=content.format(*messages)) - user.welcome_email_sent=1 + if user.welcome_email_sent == 0: + frappe.sendmail(user.user, subject=_("Project Collaboration Invitation"), + content=content.format(*messages)) + user.welcome_email_sent = 1 def on_update(self): self.load_tasks() @@ -256,18 +259,23 @@ class Project(Document): depends_on_tasks = _task.depends_on_tasks depends_on_tasks = [x for x in depends_on_tasks.split(',') if x] - dependency_map[task.title] = [ x['subject'] for x in frappe.get_list( + dependency_map[task.title] = [x['subject'] for x in frappe.get_list( 'Task Depends On', {"parent": name}, ['subject'])] + for key, value in dependency_map.iteritems(): + task_name = frappe.db.get_value('Task', {"subject": key, "project": self.name}) + for key, value in iteritems(dependency_map): task_name = frappe.db.get_value('Task', {"subject": key, "project": self.name }) + task_doc = frappe.get_doc('Task', task_name) for dt in value: - dt_name = frappe.db.get_value('Task', {"subject": dt, "project": self.name }) + dt_name = frappe.db.get_value('Task', {"subject": dt, "project": self.name}) task_doc.append('depends_on', {"task": dt_name}) task_doc.save() + def get_timeline_data(doctype, name): '''Return timeline for attendance''' return dict(frappe.db.sql('''select unix_timestamp(from_time), count(*) @@ -276,6 +284,7 @@ def get_timeline_data(doctype, name): and docstatus < 2 group by date(from_time)''', name)) + def get_project_list(doctype, txt, filters, limit_start, limit_page_length=20, order_by="modified"): return frappe.db.sql('''select distinct project.* from tabProject project, `tabProject User` project_user @@ -286,9 +295,10 @@ def get_project_list(doctype, txt, filters, limit_start, limit_page_length=20, o order by project.modified desc limit {0}, {1} '''.format(limit_start, limit_page_length), - {'user':frappe.session.user}, - as_dict=True, - update={'doctype':'Project'}) + {'user': frappe.session.user}, + as_dict=True, + update={'doctype': 'Project'}) + def get_list_context(context=None): return { @@ -315,16 +325,85 @@ def get_users_for_project(doctype, txt, searchfield, start, page_len, filters): idx desc, name, full_name limit %(start)s, %(page_len)s""".format(**{ - 'key': searchfield, - 'fcond': get_filters_cond(doctype, filters, conditions), - 'mcond': get_match_cond(doctype) - }), { - 'txt': "%%%s%%" % txt, - '_txt': txt.replace("%", ""), - 'start': start, - 'page_len': page_len - }) + 'key': searchfield, + 'fcond': get_filters_cond(doctype, filters, conditions), + 'mcond': get_match_cond(doctype) + }), { + 'txt': "%%%s%%" % txt, + '_txt': txt.replace("%", ""), + 'start': start, + 'page_len': page_len + }) + @frappe.whitelist() def get_cost_center_name(project): return frappe.db.get_value("Project", project, "cost_center") + +@frappe.whitelist() +def hourly_reminder(): + project = frappe.db.sql("""SELECT `tabProject`.name FROM `tabProject` WHERE `tabProject`.frequency = "Hourly" and (CURTIME() BETWEEN `tabProject`.from and `tabProject`.to) AND `tabProject`.collect_progress = 1 ORDER BY `tabProject`.name;""") + create_project_update(project) + +@frappe.whitelist() +def twice_daily_reminder(): + project = frappe.db.sql("""SELECT `tabProject User`.user FROM `tabProject User` INNER JOIN `tabProject` ON `tabProject`.project_name = `tabProject User`.parent WHERE (`tabProject`.frequency = "Twice Daily") AND ((`tabProject`.first_email BETWEEN DATE_ADD(curtime(), INTERVAL -15 MINUTE) AND DATE_ADD(curtime(), INTERVAL 15 MINUTE)) OR (`tabProject`.second_email BETWEEN DATE_ADD(curtime(), INTERVAL -15 MINUTE) AND DATE_ADD(curtime(), INTERVAL 15 MINUTE))) AND `tabProject`.collect_progress = 1;""") + create_project_update(project) + +@frappe.whitelist() +def daily_reminder(): + project = frappe.db.sql("""SELECT `tabProject User`.user FROM `tabProject User` INNER JOIN `tabProject` ON `tabProject`.project_name = `tabProject User`.parent WHERE (`tabProject`.frequency = "Daily") AND (`tabProject`.daily_time_to_send BETWEEN DATE_ADD(curtime(), INTERVAL -15 MINUTE) AND DATE_ADD(curtime(), INTERVAL 15 MINUTE)) AND `tabProject`.collect_progress = 1;""") + create_project_update(project) + +@frappe.whitelist() +def weekly(): + today = datetime.datetime.now().strftime("%A") + project = frappe.db.sql("""SELECT `tabProject User`.user FROM `tabProject User` INNER JOIN `tabProject` ON `tabProject`.project_name = `tabProject User`.parent WHERE (`tabProject`.frequency = "Weekly") AND (`tabProject`.day_to_send = %s) AND (`tabProject`.weekly_time_to_send BETWEEN DATE_ADD(curtime(), INTERVAL -15 MINUTE) AND DATE_ADD(curtime(), INTERVAL 15 MINUTE)) AND `tabProject`.collect_progress = 1""", today) + create_project_update(project) + +@frappe.whitelist() +def times_check(from1, to, first_email, second_email, daily_time_to_send, weekly_time_to_send): + from1 = datetime.datetime.strptime(from1, "%H:%M:%S") + from1 = from1.strftime("%H:00:00") + to = datetime.datetime.strptime(to, "%H:%M:%S") + to = to.strftime("%H:00:00") + first_email = datetime.datetime.strptime(first_email, "%H:%M:%S") + first_email = first_email.strftime("%H:00:00") + second_email = datetime.datetime.strptime(second_email, "%H:%M:%S") + second_email = second_email.strftime("%H:00:00") + daily_time_to_send = datetime.datetime.strptime(daily_time_to_send, "%H:%M:%S") + daily_time_to_send = daily_time_to_send.strftime("%H:00:00") + weekly_time_to_send = datetime.datetime.strptime(weekly_time_to_send, "%H:%M:%S") + weekly_time_to_send = weekly_time_to_send.strftime("%H:00:00") + return {"from1": from1, "to": to, "first_email": first_email, "second_email": second_email,"daily_time_to_send": daily_time_to_send, "weekly_time_to_send": weekly_time_to_send} + + +#Call this function in order to generate the Project Update for a specific project +def create_project_update(project): + data = [] + date_today = datetime.date.today() + time_now = frappe.utils.now_datetime().strftime('%H:%M:%S') + for projects in project: + project_update_dict = { + "doctype" : "Project Update", + "project" : projects[0], + "date": date_today, + "time": time_now, + "naming_series": "UPDATE-.project.-.YY.MM.DD.-" + } + project_update = frappe.get_doc(project_update_dict) + project_update.insert() + #you can edit your local_host + local_host = "http://localhost:8003" + project_update_url = "" % (local_host +"/desk#Form/Project%20Update/" + (project_update.name)) + ("CREATE PROJECT UPDATE" + "") + data.append(project_update_url) + + email = frappe.db.sql("""SELECT user from `tabProject User` WHERE parent = %s;""", project[0]) + for emails in email: + frappe.sendmail( + recipients=emails, + subject=frappe._(projects[0]), + header=[frappe._("Please Update your Project Status"), 'blue'], + message= project_update_url + ) + return data \ No newline at end of file diff --git a/erpnext/projects/doctype/project/project_dashboard.py b/erpnext/projects/doctype/project/project_dashboard.py index 0ac7d6fc39..485aae77e7 100644 --- a/erpnext/projects/doctype/project/project_dashboard.py +++ b/erpnext/projects/doctype/project/project_dashboard.py @@ -8,7 +8,7 @@ def get_data(): 'transactions': [ { 'label': _('Project'), - 'items': ['Task', 'Timesheet', 'Expense Claim', 'Issue'] + 'items': ['Task', 'Timesheet', 'Expense Claim', 'Issue' , 'Project Update'] }, { 'label': _('Material'), diff --git a/erpnext/projects/doctype/project_update/__init__.py b/erpnext/projects/doctype/project_update/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/projects/doctype/project_update/project_update.js b/erpnext/projects/doctype/project_update/project_update.js new file mode 100644 index 0000000000..990c1afd9a --- /dev/null +++ b/erpnext/projects/doctype/project_update/project_update.js @@ -0,0 +1,17 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Project Update', { + refresh: function() { + + }, + + onload: function (frm) { + frm.set_value("naming_series", "UPDATE-.project.-.YY.MM.DD.-.####"); + }, + + validate: function (frm) { + frm.set_value("time", frappe.datetime.now_time()); + frm.set_value("date", frappe.datetime.nowdate()); + } +}); diff --git a/erpnext/projects/doctype/project_update/project_update.json b/erpnext/projects/doctype/project_update/project_update.json new file mode 100644 index 0000000000..a4eb1eed14 --- /dev/null +++ b/erpnext/projects/doctype/project_update/project_update.json @@ -0,0 +1,366 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "naming_series:", + "beta": 0, + "creation": "2018-01-18 09:44:47.565494", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "project", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Project", + "length": 0, + "no_copy": 0, + "options": "Project", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "time", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Time", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "progress", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "How is the Project Progressing Right Now?", + "length": 0, + "no_copy": 0, + "options": "Not Updated\nGreat/Quickly\nGood/Steady\nChallenging/Slow\nProblematic/Stuck", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "users", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Users", + "length": 0, + "no_copy": 0, + "options": "Project User", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "progress_details", + "fieldtype": "Text Editor", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Progress Details", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amended_from", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Amended From", + "length": 0, + "no_copy": 1, + "options": "Project Update", + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "naming_series", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Series", + "length": 0, + "no_copy": 0, + "options": "UPDATE-.project.-.YY.MM.DD.-.####", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2018-02-14 10:50:16.794621", + "modified_by": "Administrator", + "module": "Projects", + "name": "Project Update", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects User", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/projects/doctype/project_update/project_update.py b/erpnext/projects/doctype/project_update/project_update.py new file mode 100644 index 0000000000..8b4249f920 --- /dev/null +++ b/erpnext/projects/doctype/project_update/project_update.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class ProjectUpdate(Document): + pass + +@frappe.whitelist() +def daily_reminder(): + project = frappe.db.sql("""SELECT `tabProject`.project_name,`tabProject`.frequency,`tabProject`.expected_start_date,`tabProject`.expected_end_date,`tabProject`.percent_complete FROM `tabProject`;""") + for projects in project: + project_name = projects[0] + frequency = projects[1] + date_start = projects[2] + date_end = projects [3] + progress = projects [4] + draft = frappe.db.sql("""SELECT count(docstatus) from `tabProject Update` WHERE `tabProject Update`.project = %s AND `tabProject Update`.docstatus = 0;""",project_name) + for drafts in draft: + number_of_drafts = drafts[0] + update = frappe.db.sql("""SELECT name,date,time,progress,progress_details FROM `tabProject Update` WHERE `tabProject Update`.project = %s AND date = DATE_ADD(CURDATE(), INTERVAL -1 DAY);""",project_name) + email_sending(project_name,frequency,date_start,date_end,progress,number_of_drafts,update) + +def email_sending(project_name,frequency,date_start,date_end,progress,number_of_drafts,update): + + holiday = frappe.db.sql("""SELECT holiday_date FROM `tabHoliday` where holiday_date = CURDATE();""") + msg = "
Project Name: " + project_name + "
Frequency: " + " " + frequency + "
Update Reminder:" + " " + str(date_start) + "
Expected Date End:" + " " + str(date_end) + "
Percent Progress:" + " " + str(progress) + "
Number of Updates:" + " " + str(len(update)) + "
" + "Number of drafts:" + " " + str(number_of_drafts) + "
" + msg += """Project ID | Date Updated | Time Updated | Project Status | Notes | """ + for updates in update: + msg += "
---|---|---|---|---|
" + str(updates[0]) + " | " + str(updates[1]) + " | " + str(updates[2]) + " | " + str(updates[3]) + " | " + "" + str(updates[4]) + " |