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
diff --git a/erpnext/config/projects.py b/erpnext/config/projects.py
index ac11c7e..2f8b92d 100644
--- a/erpnext/config/projects.py
+++ b/erpnext/config/projects.py
@@ -14,6 +14,11 @@
},
{
"type": "doctype",
+ "name": "Project Update",
+ "description": _("Project Update."),
+ },
+ {
+ "type": "doctype",
"name": "Task",
"route": "List/Task",
"description": _("Project activity / task."),
diff --git a/erpnext/projects/doctype/project/README.md b/erpnext/projects/doctype/project/README.md
index b1da6ad..7d513cb 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 cc8433c..682398f 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 @@
);
},
- 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 @@
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 @@
});
},
- 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 @@
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 5d95bd3..7b4d718 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 221c1d3..02a4afb 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -10,6 +10,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
"""
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 @@
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 @@
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 @@
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 @@
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 = "<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
\ 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 0ac7d6f..485aae7 100644
--- a/erpnext/projects/doctype/project/project_dashboard.py
+++ b/erpnext/projects/doctype/project/project_dashboard.py
@@ -8,7 +8,7 @@
'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 0000000..e69de29
--- /dev/null
+++ b/erpnext/projects/doctype/project_update/__init__.py
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 0000000..990c1af
--- /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 0000000..a4eb1ee
--- /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 0000000..8b4249f
--- /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 = "<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
\ No newline at end of file
diff --git a/erpnext/projects/doctype/project_update/test_project_update.js b/erpnext/projects/doctype/project_update/test_project_update.js
new file mode 100644
index 0000000..bda510b
--- /dev/null
+++ b/erpnext/projects/doctype/project_update/test_project_update.js
@@ -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()
+ ]);
+
+});
diff --git a/erpnext/projects/doctype/project_update/test_project_update.py b/erpnext/projects/doctype/project_update/test_project_update.py
new file mode 100644
index 0000000..d5d0919
--- /dev/null
+++ b/erpnext/projects/doctype/project_update/test_project_update.py
@@ -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"]
\ No newline at end of file