Daily Reminder (#12938)

* for email sending

* test commit for new branch

* Removed test changes

* test commit renzo

* Email sending

* Project Uo

* Project Update

* "My first commit"

* "My second commit"

* "My second commit"

* Holiday is included

* delete idea

* first pull

* first pull

* "My third commit"

* Delete idea

* delete again

* "Edit Time"

* "Edit Time"

* Naming series and bug fixing

* "Edit current day and time"

* "Proper naming series, hidden time and date"

* Project and Project Update dashboard

* dashboard

* Remove hooks for PR

* Remove hooks for PR

* Remove hooks for PR

* Deleted project_time.py

* Corrected indention

* Hook back to original

* Delete project_time.py

* "Modified time"

* Fix indention

* Sample

* FRM

* FRM

* Time fix

* Hooks original state

* "Modified time"

* Added permission to Project User

* Added function/method to be called in order to create project update for the specific project

* Naming series

* this is not included

* Fix minor bug

* Indent again

* "Reformat Code"

* "Check Indent"

* Indent again and again

* semi colon

* "Check Again Indent"

* "Check again Indent"

* "Check again Indent"

* ind

* "Check again Indent"

* "Check again Indent"

* Generate Project update
With button in email

* []

* "Erro Summary"

* "Add syntax for the communcation"

* "add summary code"

* "Modified Summary code"

* "Modified Summary code"

* "Fix update ID and set localhost"

* Fix time and date error in project_update
Fix naming series problem in project_update

* included "not updated" in project update

* Bug in Number of Drafts

* "add notes in summary"

* Correct code

* With error

* Removed the method

* Minor fixes

* Correction for daily summary
This commit is contained in:
renzodemie 2018-03-20 14:28:15 +08:00 committed by Nabin Hait
parent aa4d7a3436
commit 417dfed214
12 changed files with 981 additions and 92 deletions

View File

@ -12,6 +12,11 @@ def get_data():
"name": "Project", "name": "Project",
"description": _("Project master."), "description": _("Project master."),
}, },
{
"type": "doctype",
"name": "Project Update",
"description": _("Project Update."),
},
{ {
"type": "doctype", "type": "doctype",
"name": "Task", "name": "Task",

View File

@ -1 +1 @@
Project details. Projects can be internal or external and can have Tasks, Milestones associated to it. Project details. Projects can be internal or external and can have Tasks, Milestones associated to it.

View File

@ -1,18 +1,15 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.ui.form.on("Project", { frappe.ui.form.on("Project", {
setup: function(frm) { setup: function (frm) {
frm.set_indicator_formatter('title', frm.set_indicator_formatter('title',
function(doc) { function (doc) {
let indicator = 'orange'; let indicator = 'orange';
if (doc.status == 'Overdue') { if (doc.status == 'Overdue') {
indicator = 'red'; indicator = 'red';
} } else if (doc.status == 'Cancelled') {
else if (doc.status == 'Cancelled') {
indicator = 'dark grey'; indicator = 'dark grey';
} } else if (doc.status == 'Closed') {
else if (doc.status == 'Closed') {
indicator = 'green'; indicator = 'green';
} }
return indicator; 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"); var so = frappe.meta.get_docfield("Project", "sales_order");
so.get_route_options_for_new_doc = function(field) { so.get_route_options_for_new_doc = function (field) {
if(frm.is_new()) return; if (frm.is_new()) return;
return { return {
"customer": frm.doc.customer, "customer": frm.doc.customer,
"project_name": frm.doc.name "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('customer', 'erpnext.controllers.queries.customer_query');
frm.set_query("user", "users", function() { frm.set_query("user", "users", function () {
return { return {
query:"erpnext.projects.doctype.project.project.get_users_for_project" query: "erpnext.projects.doctype.project.project.get_users_for_project"
} }
}); });
// sales order // sales order
frm.set_query('sales_order', function() { frm.set_query('sales_order', function () {
var filters = { var filters = {
'project': ["in", frm.doc.__islocal ? [""] : [frm.doc.name, ""]] 'project': ["in", frm.doc.__islocal ? [""] : [frm.doc.name, ""]]
}; };
@ -54,15 +51,17 @@ frappe.ui.form.on("Project", {
}); });
}, },
refresh: function(frm) { refresh: function (frm) {
if(frm.doc.__islocal) { if (frm.doc.__islocal) {
frm.web_link && frm.web_link.remove(); frm.web_link && frm.web_link.remove();
} else { } else {
frm.add_web_link("/projects?project=" + encodeURIComponent(frm.doc.name)); frm.add_web_link("/projects?project=" + encodeURIComponent(frm.doc.name));
if(frappe.model.can_read("Task")) { if (frappe.model.can_read("Task")) {
frm.add_custom_button(__("Gantt Chart"), function() { frm.add_custom_button(__("Gantt Chart"), function () {
frappe.route_options = {"project": frm.doc.name}; frappe.route_options = {
"project": frm.doc.name
};
frappe.set_route("List", "Task", "Gantt"); frappe.set_route("List", "Task", "Gantt");
}); });
} }
@ -70,65 +69,69 @@ frappe.ui.form.on("Project", {
frm.trigger('show_dashboard'); frm.trigger('show_dashboard');
} }
}, },
tasks_refresh: function(frm) { tasks_refresh: function (frm) {
var grid = frm.get_field('tasks').grid; var grid = frm.get_field('tasks').grid;
grid.wrapper.find('select[data-fieldname="status"]').each(function() { grid.wrapper.find('select[data-fieldname="status"]').each(function () {
if($(this).val()==='Open') { if ($(this).val() === 'Open') {
$(this).addClass('input-indicator-open'); $(this).addClass('input-indicator-open');
} else { } else {
$(this).removeClass('input-indicator-open'); $(this).removeClass('input-indicator-open');
} }
}); });
}, },
show_dashboard: function(frm) { edit_task: function (frm, doctype, name) {
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) {
var doc = frappe.get_doc(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); frappe.set_route("Form", "Task", doc.task_id);
} else { } else {
frappe.msgprint(__("Save the document first.")); frappe.msgprint(__("Save the document first."));
} }
}, },
edit_timesheet: function(frm, cdt, cdn) { edit_timesheet: function (frm, cdt, cdn) {
var child = locals[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"); frappe.set_route("List", "Timesheet");
}, },
make_timesheet: function (frm, cdt, cdn) {
make_timesheet: function(frm, cdt, cdn) {
var child = locals[cdt][cdn]; var child = locals[cdt][cdn];
frappe.model.with_doctype('Timesheet', function() { frappe.model.with_doctype('Timesheet', function () {
var doc = frappe.model.get_new_doc('Timesheet'); var doc = frappe.model.get_new_doc('Timesheet');
var row = frappe.model.add_child(doc, 'time_logs'); var row = frappe.model.add_child(doc, 'time_logs');
row.project = frm.doc.project_name; row.project = frm.doc.project_name;
row.task = child.task_id; row.task = child.task_id;
frappe.set_route('Form', doc.doctype, doc.name); frappe.set_route('Form', doc.doctype, doc.name);
}) })
}, },
status: function(frm, doctype, name) { status: function (frm, doctype, name) {
frm.trigger('tasks_refresh'); 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);
}
});
});

View File

@ -1272,6 +1272,347 @@
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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, "has_web_view": 0,
@ -1285,7 +1626,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 4, "max_attachments": 4,
"modified": "2017-12-10 08:40:46.843201", "modified": "2018-01-29 11:48:21.156697",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Project", "name": "Project",

View File

@ -10,6 +10,7 @@ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.controllers.queries import get_filters_cond from erpnext.controllers.queries import get_filters_cond
from frappe.desk.reportview import get_match_cond from frappe.desk.reportview import get_match_cond
import datetime
from six import iteritems from six import iteritems
@ -79,7 +80,8 @@ class Project(Document):
if task.task_weight or 0 > 0: if task.task_weight or 0 > 0:
sum = sum + task.task_weight sum = sum + task.task_weight
if sum > 0 and sum != 1: 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): def sync_tasks(self):
"""sync tasks and remove table""" """sync tasks and remove table"""
@ -105,7 +107,7 @@ class Project(Document):
task.flags.ignore_links = True task.flags.ignore_links = True
task.flags.from_project = True task.flags.from_project = True
task.flags.ignore_feed = True task.flags.ignore_feed = True
task.save(ignore_permissions = True) task.save(ignore_permissions=True)
task_names.append(task.name) task_names.append(task.name)
# delete # delete
@ -127,18 +129,18 @@ class Project(Document):
self.update_percent_complete() self.update_percent_complete()
self.update_costing() self.update_costing()
self.flags.dont_sync_tasks = True self.flags.dont_sync_tasks = True
self.save(ignore_permissions = True) self.save(ignore_permissions=True)
def after_insert(self): def after_insert(self):
if self.sales_order: if self.sales_order:
frappe.db.set_value("Sales Order", self.sales_order, "project", self.name) frappe.db.set_value("Sales Order", self.sales_order, "project", self.name)
def update_percent_complete(self): def update_percent_complete(self):
total = frappe.db.sql("""select count(name) from tabTask where project=%s""", self.name)[0][0] total = frappe.db.sql("""select count(name) from tabTask where project=%s""", self.name)[0][0]
if not total and self.percent_complete: if not total and self.percent_complete:
self.percent_complete = 0 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 completed = frappe.db.sql("""select count(name) from tabTask where
project=%s and status in ('Closed', 'Cancelled')""", self.name)[0][0] project=%s and status in ('Closed', 'Cancelled')""", self.name)[0][0]
self.percent_complete = flt(flt(completed) / total * 100, 2) self.percent_complete = flt(flt(completed) / total * 100, 2)
@ -153,8 +155,8 @@ class Project(Document):
project=%s""", self.name)[0][0] project=%s""", self.name)[0][0]
if weight_sum == 1: if weight_sum == 1:
weighted_progress = frappe.db.sql("""select progress,task_weight from tabTask where weighted_progress = frappe.db.sql("""select progress,task_weight from tabTask where
project=%s""", self.name,as_dict=1) project=%s""", self.name, as_dict=1)
pct_complete=0 pct_complete = 0
for row in weighted_progress: for row in weighted_progress:
pct_complete += row["progress"] * row["task_weight"] pct_complete += row["progress"] * row["task_weight"]
self.percent_complete = flt(flt(pct_complete), 2) self.percent_complete = flt(flt(pct_complete), 2)
@ -172,7 +174,7 @@ class Project(Document):
sum(total_sanctioned_amount) as total_sanctioned_amount sum(total_sanctioned_amount) as total_sanctioned_amount
from `tabExpense Claim` where project = %s from `tabExpense Claim` where project = %s
and docstatus = 1""", 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_start_date = from_time_sheet.start_date
self.actual_end_date = from_time_sheet.end_date self.actual_end_date = from_time_sheet.end_date
@ -186,10 +188,11 @@ class Project(Document):
self.update_sales_amount() self.update_sales_amount()
self.update_billed_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: 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): def update_purchase_costing(self):
total_purchase_cost = frappe.db.sql("""select sum(base_net_amount) 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 self.total_billed_amount = total_billed_amount and total_billed_amount[0][0] or 0
def send_welcome_email(self): def send_welcome_email(self):
url = get_url("/project/?name={0}".format(self.name)) url = get_url("/project/?name={0}".format(self.name))
messages = ( messages = (
_("You have been invited to collaborate on the project: {0}".format(self.name)), _("You have been invited to collaborate on the project: {0}".format(self.name)),
url, url,
_("Join") _("Join")
) )
content = """ content = """
@ -224,9 +226,10 @@ class Project(Document):
""" """
for user in self.users: for user in self.users:
if user.welcome_email_sent==0: if user.welcome_email_sent == 0:
frappe.sendmail(user.user, subject=_("Project Collaboration Invitation"), content=content.format(*messages)) frappe.sendmail(user.user, subject=_("Project Collaboration Invitation"),
user.welcome_email_sent=1 content=content.format(*messages))
user.welcome_email_sent = 1
def on_update(self): def on_update(self):
self.load_tasks() self.load_tasks()
@ -256,18 +259,23 @@ class Project(Document):
depends_on_tasks = _task.depends_on_tasks depends_on_tasks = _task.depends_on_tasks
depends_on_tasks = [x for x in depends_on_tasks.split(',') if x] 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'])] '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): for key, value in iteritems(dependency_map):
task_name = frappe.db.get_value('Task', {"subject": key, "project": self.name }) task_name = frappe.db.get_value('Task', {"subject": key, "project": self.name })
task_doc = frappe.get_doc('Task', task_name) task_doc = frappe.get_doc('Task', task_name)
for dt in value: 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.append('depends_on', {"task": dt_name})
task_doc.save() task_doc.save()
def get_timeline_data(doctype, name): def get_timeline_data(doctype, name):
'''Return timeline for attendance''' '''Return timeline for attendance'''
return dict(frappe.db.sql('''select unix_timestamp(from_time), count(*) return dict(frappe.db.sql('''select unix_timestamp(from_time), count(*)
@ -276,6 +284,7 @@ def get_timeline_data(doctype, name):
and docstatus < 2 and docstatus < 2
group by date(from_time)''', name)) group by date(from_time)''', name))
def get_project_list(doctype, txt, filters, limit_start, limit_page_length=20, order_by="modified"): def get_project_list(doctype, txt, filters, limit_start, limit_page_length=20, order_by="modified"):
return frappe.db.sql('''select distinct project.* return frappe.db.sql('''select distinct project.*
from tabProject project, `tabProject User` project_user 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 order by project.modified desc
limit {0}, {1} limit {0}, {1}
'''.format(limit_start, limit_page_length), '''.format(limit_start, limit_page_length),
{'user':frappe.session.user}, {'user': frappe.session.user},
as_dict=True, as_dict=True,
update={'doctype':'Project'}) update={'doctype': 'Project'})
def get_list_context(context=None): def get_list_context(context=None):
return { return {
@ -315,16 +325,85 @@ def get_users_for_project(doctype, txt, searchfield, start, page_len, filters):
idx desc, idx desc,
name, full_name name, full_name
limit %(start)s, %(page_len)s""".format(**{ limit %(start)s, %(page_len)s""".format(**{
'key': searchfield, 'key': searchfield,
'fcond': get_filters_cond(doctype, filters, conditions), 'fcond': get_filters_cond(doctype, filters, conditions),
'mcond': get_match_cond(doctype) 'mcond': get_match_cond(doctype)
}), { }), {
'txt': "%%%s%%" % txt, 'txt': "%%%s%%" % txt,
'_txt': txt.replace("%", ""), '_txt': txt.replace("%", ""),
'start': start, 'start': start,
'page_len': page_len 'page_len': page_len
}) })
@frappe.whitelist() @frappe.whitelist()
def get_cost_center_name(project): def get_cost_center_name(project):
return frappe.db.get_value("Project", project, "cost_center") 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 = "<a class = 'btn btn-primary' href=%s target='_blank'>" % (local_host +"/desk#Form/Project%20Update/" + (project_update.name)) + ("CREATE PROJECT UPDATE" + "</a>")
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

View File

@ -8,7 +8,7 @@ def get_data():
'transactions': [ 'transactions': [
{ {
'label': _('Project'), 'label': _('Project'),
'items': ['Task', 'Timesheet', 'Expense Claim', 'Issue'] 'items': ['Task', 'Timesheet', 'Expense Claim', 'Issue' , 'Project Update']
}, },
{ {
'label': _('Material'), 'label': _('Material'),

View File

@ -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());
}
});

View File

@ -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
}

View File

@ -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 = "<p>Project Name: " + project_name + "</p><p>Frequency: " + " " + frequency + "</p><p>Update Reminder:" + " " + str(date_start) + "</p><p>Expected Date End:" + " " + str(date_end) + "</p><p>Percent Progress:" + " " + str(progress) + "</p><p>Number of Updates:" + " " + str(len(update)) + "</p>" + "</p><p>Number of drafts:" + " " + str(number_of_drafts) + "</p>"
msg += """</u></b></p><table class='table table-bordered'><tr>
<th>Project ID</th><th>Date Updated</th><th>Time Updated</th><th>Project Status</th><th>Notes</th>"""
for updates in update:
msg += "<tr><td>" + str(updates[0]) + "</td><td>" + str(updates[1]) + "</td><td>" + str(updates[2]) + "</td><td>" + str(updates[3]) + "</td>" + "</td><td>" + str(updates[4]) + "</td></tr>"
msg += "</table>"
if len(holiday) == 0:
email = frappe.db.sql("""SELECT user from `tabProject User` WHERE parent = %s;""", project_name)
for emails in email:
frappe.sendmail(recipients=emails,subject=frappe._(project_name + ' ' + 'Summary'),message = msg)
else:
pass

View File

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Project Update", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Project Update
() => frappe.tests.make('Project Update', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestProjectUpdate(unittest.TestCase):
pass
test_records = frappe.get_test_records('Project Update')
test_ignore = ["Sales Order"]