44)
55from erpnext .accounts .party import get_party_account
66from frappe import _ , throw
7- from frappe .desk .form .assign_to import clear , close_all_assignments
7+ from frappe .desk .form .assign_to import close_all_assignments
88from frappe .email .doctype .notification .notification import get_context
9- from frappe .utils import add_days , cstr , date_diff , flt , getdate , today
10- from frappe .utils .data import format_date
11- from frappe .utils .nestedset import NestedSet
12- from erpnext .projects .doctype .task .task import check_if_child_exists , CircularReferenceError
9+ from frappe .utils import add_days , getdate , today
10+ from erpnext .projects .doctype .task .task import Task
1311
1412from one_compliance .one_compliance .utils import (
1513 create_project_completion_todos ,
1614 send_notification ,
1715 send_notification_to_roles ,
1816)
1917
20-
21- class CustomTask (NestedSet ):
22- # begin: auto-generated types
23- # This code is auto-generated. Do not modify anything in this block.
24-
25- from typing import TYPE_CHECKING
26-
27- if TYPE_CHECKING :
28- from erpnext .projects .doctype .task_depends_on .task_depends_on import (
29- TaskDependsOn ,
30- )
31- from frappe .types import DF
32-
33- act_end_date : DF .Date | None
34- act_start_date : DF .Date | None
35- actual_time : DF .Float
36- closing_date : DF .Date | None
37- color : DF .Color | None
38- company : DF .Link | None
39- completed_by : DF .Link | None
40- completed_on : DF .Date | None
41- department : DF .Link | None
42- depends_on : DF .Table [TaskDependsOn ]
43- depends_on_tasks : DF .Code | None
44- description : DF .TextEditor | None
45- duration : DF .Int
46- exp_end_date : DF .Date | None
47- exp_start_date : DF .Date | None
48- expected_time : DF .Float
49- is_group : DF .Check
50- is_milestone : DF .Check
51- is_template : DF .Check
52- issue : DF .Link | None
53- lft : DF .Int
54- old_parent : DF .Data | None
55- parent_task : DF .Link | None
56- priority : DF .Literal ["Low" , "Medium" , "High" , "Urgent" ]
57- progress : DF .Percent
58- project : DF .Link | None
59- review_date : DF .Date | None
60- rgt : DF .Int
61- start : DF .Int
62- status : DF .Literal [
63- "Open" , "Working" , "Pending Review" , "Overdue" , "Template" , "Completed" , "Cancelled"
64- ]
65- subject : DF .Data
66- task_weight : DF .Float
67- template_task : DF .Data | None
68- total_billing_amount : DF .Currency
69- total_costing_amount : DF .Currency
70- type : DF .Link | None
71- # end: auto-generated types
72-
73- nsm_parent_field = "parent_task"
74-
75- def get_customer_details (self ):
76- cust = frappe .db .sql ("select customer_name from `tabCustomer` where name=%s" , self .customer )
77- if cust :
78- ret = {"customer_name" : cust and cust [0 ][0 ] or "" }
79- return ret
80-
18+ class CustomTask (Task ):
8119 def validate (self ):
82- self .validate_dates ()
83- self .validate_progress ()
84- self .validate_status ()
85- self .update_depends_on ()
86- self .validate_dependencies_for_template_task ()
87- self .validate_completed_on ()
20+ super (CustomTask , self ).validate ()
8821 self .validate_reimbursement_check ()
89-
90- def validate_dates (self ):
91- self .validate_from_to_dates ("exp_start_date" , "exp_end_date" )
92- self .validate_from_to_dates ("act_start_date" , "act_end_date" )
93- self .validate_parent_expected_end_date ()
94- self .validate_parent_project_dates ()
95-
96- def validate_parent_expected_end_date (self ):
97- if not self .parent_task or not self .exp_end_date :
98- return
99-
100- parent_exp_end_date = frappe .db .get_value ("Task" , self .parent_task , "exp_end_date" )
101- if not parent_exp_end_date :
102- return
103-
104- if getdate (self .exp_end_date ) > getdate (parent_exp_end_date ):
105- frappe .throw (
106- _ (
107- "Expected End Date should be less than or equal to parent task's Expected End Date {0}."
108- ).format (format_date (parent_exp_end_date )),
109- frappe .exceptions .InvalidDates ,
110- )
111-
112- def validate_parent_project_dates (self ):
113- if not self .project or frappe .flags .in_test :
114- return
115-
116- if project_end_date := frappe .db .get_value ("Project" , self .project , "expected_end_date" ):
117- project_end_date = getdate (project_end_date )
118- for fieldname in ("exp_start_date" , "exp_end_date" , "act_start_date" , "act_end_date" ):
119- task_date = self .get (fieldname )
120- if task_date and date_diff (project_end_date , getdate (task_date )) < 0 :
121- frappe .throw (
122- _ ("{0}'s {1} cannot be after {2}'s Expected End Date." ).format (
123- frappe .bold (frappe .get_desk_link ("Task" , self .name )),
124- _ (self .meta .get_label (fieldname )),
125- frappe .bold (frappe .get_desk_link ("Project" , self .project )),
126- ),
127- frappe .exceptions .InvalidDates ,
128- )
129-
22+
13023 def validate_status (self ):
13124 if self .is_template and self .status != "Template" :
13225 self .status = "Template"
@@ -145,35 +38,6 @@ def validate_status(self):
14538
14639 close_all_assignments (self .doctype , self .name )
14740
148- def validate_progress (self ):
149- if flt (self .progress or 0 ) > 100 :
150- frappe .throw (_ ("Progress % for a task cannot be more than 100." ))
151-
152- if self .status == "Completed" :
153- self .progress = 100
154-
155- def validate_dependencies_for_template_task (self ):
156- if self .is_template :
157- self .validate_parent_template_task ()
158- self .validate_depends_on_tasks ()
159-
160- def validate_parent_template_task (self ):
161- if self .parent_task :
162- if not frappe .db .get_value ("Task" , self .parent_task , "is_template" ):
163- parent_task_format = f"""<a href="#Form/Task/{ self .parent_task } ">{ self .parent_task } </a>"""
164- frappe .throw (_ ("Parent Task {0} is not a Template Task" ).format (parent_task_format ))
165-
166- def validate_depends_on_tasks (self ):
167- if self .depends_on :
168- for task in self .depends_on :
169- if not frappe .db .get_value ("Task" , task .task , "is_template" ):
170- dependent_task_format = f"""<a href="#Form/Task/{ task .task } ">{ task .task } </a>"""
171- frappe .throw (_ ("Dependent Task {0} is not a Template Task" ).format (dependent_task_format ))
172-
173- def validate_completed_on (self ):
174- if self .completed_on and getdate (self .completed_on ) > getdate ():
175- frappe .throw (_ ("Completed On cannot be greater than Today" ))
176-
17741 def validate_reimbursement_check (self ):
17842 '''
17943 Validate Rembursement JV on Task Completion
@@ -183,132 +47,6 @@ def validate_reimbursement_check(self):
18347 title = _ ("Reimbursement Journal Entry Missing" ),
18448 msg = _ ("Please create Reimbursement Journal Entry before marking the task <b>`{0}`</b> as Completed" .format (self .name )))
18549
186- def update_depends_on (self ):
187- depends_on_tasks = ""
188- for d in self .depends_on :
189- if d .task and d .task not in depends_on_tasks :
190- depends_on_tasks += d .task + ","
191- self .depends_on_tasks = depends_on_tasks
192-
193- def update_nsm_model (self ):
194- frappe .utils .nestedset .update_nsm (self )
195-
196- def on_update (self ):
197- self .update_nsm_model ()
198- self .check_recursion ()
199- self .reschedule_dependent_tasks ()
200- self .update_project ()
201- self .unassign_todo ()
202- self .populate_depends_on ()
203-
204- def unassign_todo (self ):
205- if self .status == "Completed" :
206- close_all_assignments (self .doctype , self .name )
207- if self .status == "Cancelled" :
208- clear (self .doctype , self .name )
209-
210- def update_time_and_costing (self ):
211- tl = frappe .db .sql (
212- """select min(from_time) as start_date, max(to_time) as end_date,
213- sum(billing_amount) as total_billing_amount, sum(costing_amount) as total_costing_amount,
214- sum(hours) as time from `tabTimesheet Detail` where task = %s and docstatus=1""" ,
215- self .name ,
216- as_dict = 1 ,
217- )[0 ]
218- if self .status == "Open" :
219- self .status = "Working"
220- self .total_costing_amount = tl .total_costing_amount
221- self .total_billing_amount = tl .total_billing_amount
222- self .actual_time = tl .time
223- self .act_start_date = tl .start_date
224- self .act_end_date = tl .end_date
225-
226- def update_project (self ):
227- if self .project and not self .flags .from_project :
228- frappe .get_cached_doc ("Project" , self .project ).update_project ()
229-
230- def check_recursion (self ):
231- if self .flags .ignore_recursion_check :
232- return
233- check_list = [["task" , "parent" ], ["parent" , "task" ]]
234- for d in check_list :
235- task_list , count = [self .name ], 0
236- while len (task_list ) > count :
237- tasks = frappe .db .sql (
238- " select {} from `tabTask Depends On` where {} = {} " .format (d [0 ], d [1 ], "%s" ),
239- cstr (task_list [count ]),
240- )
241- count = count + 1
242- for b in tasks :
243- if b [0 ] == self .name :
244- frappe .throw (_ ("Circular Reference Error" ), CircularReferenceError )
245- if b [0 ]:
246- task_list .append (b [0 ])
247-
248- if count == 15 :
249- break
250-
251- def reschedule_dependent_tasks (self ):
252- end_date = self .exp_end_date or self .act_end_date
253- if end_date :
254- for task_name in frappe .db .sql (
255- """
256- select name from `tabTask` as parent
257- where parent.project = %(project)s
258- and parent.name in (
259- select parent from `tabTask Depends On` as child
260- where child.task = %(task)s and child.project = %(project)s)
261- """ ,
262- {"project" : self .project , "task" : self .name },
263- as_dict = 1 ,
264- ):
265- task = frappe .get_doc ("Task" , task_name .name )
266- if (
267- task .exp_start_date
268- and task .exp_end_date
269- and task .exp_start_date < getdate (end_date )
270- and task .status == "Open"
271- ):
272- task_duration = date_diff (task .exp_end_date , task .exp_start_date )
273- task .exp_start_date = add_days (end_date , 1 )
274- task .exp_end_date = add_days (task .exp_start_date , task_duration )
275- task .flags .ignore_recursion_check = True
276- task .save ()
277-
278- def has_webform_permission (self ):
279- project_user = frappe .db .get_value (
280- "Project User" , {"parent" : self .project , "user" : frappe .session .user }, "user"
281- )
282- if project_user :
283- return True
284-
285- def populate_depends_on (self ):
286- if self .parent_task :
287- parent = frappe .get_doc ("Task" , self .parent_task )
288- if self .name not in [row .task for row in parent .depends_on ]:
289- parent .append (
290- "depends_on" , {"doctype" : "Task Depends On" , "task" : self .name , "subject" : self .subject }
291- )
292- parent .save ()
293-
294- def on_trash (self ):
295- if check_if_child_exists (self .name ):
296- throw (_ ("Child Task exists for this Task. You can not delete this Task." ))
297-
298- self .update_nsm_model ()
299-
300- def after_delete (self ):
301- self .update_project ()
302-
303- def update_status (self ):
304- if self .status not in ("Cancelled" , "Completed" ) and self .exp_end_date :
305- from datetime import datetime
306-
307- if self .exp_end_date < datetime .now ().date ():
308- self .db_set ("status" , "Overdue" , update_modified = False )
309- self .update_project ()
310-
311-
31250@frappe .whitelist ()
31351def append_users_to_project (doc , method ):
31452 if doc .assigned_to and doc .project :
@@ -704,3 +442,4 @@ def enable_customer_on_task_completion(doc, method):
704442 customer .aml_compliance_checked = 1
705443 customer .save (ignore_permissions = True )
706444 frappe .msgprint (f"Customer { customer .name } has been enabled after AML compliance task completion." )
445+
0 commit comments