mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-06 23:10:26 +00:00
completed Time Log / Time Log Batch
This commit is contained in:
5
projects/doctype/activity_type/test_activity_type.py
Normal file
5
projects/doctype/activity_type/test_activity_type.py
Normal file
@@ -0,0 +1,5 @@
|
||||
test_records = [
|
||||
[{"activity_type":"_Test Activity Type"}],
|
||||
[{"activity_type":"_Test Activity Type 1"}],
|
||||
[{"activity_type":"_Test Activity Type 2"}]
|
||||
]
|
||||
8
projects/doctype/project/test_project.py
Normal file
8
projects/doctype/project/test_project.py
Normal file
@@ -0,0 +1,8 @@
|
||||
test_records = [[{
|
||||
"project_name": "_Test Project",
|
||||
"status": "Open"
|
||||
}],
|
||||
[{
|
||||
"project_name": "_Test Project 1",
|
||||
"status": "Open"
|
||||
}]]
|
||||
7
projects/doctype/task/test_task.py
Normal file
7
projects/doctype/task/test_task.py
Normal file
@@ -0,0 +1,7 @@
|
||||
test_records = [
|
||||
[{"subject": "_Test Task", "project":"_Test Project", "status":"Open"}],
|
||||
[{"subject": "_Test Task 1", "status":"Open"}],
|
||||
[{"subject": "_Test Task 2", "status":"Open"}]
|
||||
]
|
||||
|
||||
test_ignore = ["Customer"]
|
||||
19
projects/doctype/time_log/test_time_log.py
Normal file
19
projects/doctype/time_log/test_time_log.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import webnotes
|
||||
import unittest
|
||||
|
||||
from projects.doctype.time_log.time_log import OverlapError
|
||||
|
||||
class TestTimeLog(unittest.TestCase):
|
||||
def test_duplication(self):
|
||||
ts = webnotes.bean(webnotes.copy_doclist(test_records[0]))
|
||||
self.assertRaises(OverlapError, ts.insert)
|
||||
|
||||
test_records = [[{
|
||||
"from_time": "2013-01-01 10:00:00",
|
||||
"to_time": "2013-01-01 11:00:00",
|
||||
"activity_type": "_Test Activity Type",
|
||||
"note": "_Test Note",
|
||||
"docstatus": 1
|
||||
}]]
|
||||
|
||||
test_ignore = ["Sales Invoice", "Time Log Batch"]
|
||||
5
projects/doctype/time_log/time_log.js
Normal file
5
projects/doctype/time_log/time_log.js
Normal file
@@ -0,0 +1,5 @@
|
||||
$.extend(cur_frm.cscript, {
|
||||
refresh: function(doc) {
|
||||
|
||||
}
|
||||
});
|
||||
@@ -6,6 +6,8 @@ from webnotes import _
|
||||
|
||||
from webnotes.widgets.reportview import build_match_conditions
|
||||
|
||||
class OverlapError(webnotes.ValidationError): pass
|
||||
|
||||
class DocType:
|
||||
def __init__(self, d, dl):
|
||||
self.doc, self.doclist = d, dl
|
||||
@@ -13,28 +15,46 @@ class DocType:
|
||||
def validate(self):
|
||||
self.set_status()
|
||||
self.validate_overlap()
|
||||
self.calculate_total_hours()
|
||||
|
||||
def calculate_total_hours(self):
|
||||
from webnotes.utils import time_diff_in_hours
|
||||
self.doc.hours = time_diff_in_hours(self.doc.to_time, self.doc.from_time)
|
||||
|
||||
def set_status(self):
|
||||
if self.doc.docstatus==0:
|
||||
self.doc.status = "Draft"
|
||||
elif self.doc.docstatus==1:
|
||||
self.doc.status = "Submitted"
|
||||
elif self.doc.docstatus==2:
|
||||
self.doc.status = "Cancelled"
|
||||
self.doc.status = {
|
||||
"0": "Draft",
|
||||
"1": "Submitted",
|
||||
"2": "Cancelled"
|
||||
}[str(self.doc.docstatus or 0)]
|
||||
|
||||
if self.doc.time_log_batch:
|
||||
self.doc.status="Batched for Billing"
|
||||
|
||||
# billed will be set directly
|
||||
|
||||
def validate_overlap(self):
|
||||
if self.doc.sales_invoice:
|
||||
self.doc.status="Billed"
|
||||
|
||||
def validate_overlap(self):
|
||||
existing = webnotes.conn.sql_list("""select name from `tabTime Log` where owner=%s and
|
||||
((from_time between %s and %s) or (to_time between %s and %s)) and name!=%s""",
|
||||
(
|
||||
(from_time between %s and %s) or
|
||||
(to_time between %s and %s) or
|
||||
(%s between from_time and to_time))
|
||||
and name!=%s
|
||||
and docstatus < 2""",
|
||||
(self.doc.owner, self.doc.from_time, self.doc.to_time, self.doc.from_time,
|
||||
self.doc.to_time, self.doc.name))
|
||||
self.doc.to_time, self.doc.from_time, self.doc.name or "No Name"))
|
||||
|
||||
if existing:
|
||||
webnotes.msgprint(_("This Time Log conflicts with") + ":" + ', '.join(existing),
|
||||
raise_exception=True)
|
||||
raise_exception=OverlapError)
|
||||
|
||||
def before_cancel(self):
|
||||
self.set_status()
|
||||
|
||||
def before_update_after_submit(self):
|
||||
self.set_status()
|
||||
|
||||
|
||||
@webnotes.whitelist()
|
||||
def get_events(start, end):
|
||||
match = build_match_conditions("Time Log")
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
{
|
||||
"creation": "2013-02-26 14:58:28",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-02-28 18:41:40",
|
||||
"modified": "2013-03-01 17:48:09",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
{
|
||||
"allow_attach": 1,
|
||||
"autoname": "TL-.######",
|
||||
"autoname": "naming_series:",
|
||||
"description": "Log of Activities performed by users against Tasks that can be used for tracking time, billing.",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Master",
|
||||
@@ -40,14 +40,12 @@
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "status",
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Status",
|
||||
"options": "Draft\nSubmitted\nBatched for Billing\nBilled\nCancelled",
|
||||
"label": "Naming Series",
|
||||
"options": "TL-",
|
||||
"permlevel": 0,
|
||||
"read_only": 1,
|
||||
"reqd": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
@@ -67,12 +65,31 @@
|
||||
"permlevel": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "hours",
|
||||
"fieldtype": "Float",
|
||||
"label": "Hours",
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Status",
|
||||
"options": "Draft\nSubmitted\nBatched for Billing\nBilled\nCancelled",
|
||||
"permlevel": 0,
|
||||
"read_only": 1,
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "activity_type",
|
||||
@@ -127,6 +144,26 @@
|
||||
"options": "Project",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"description": "Will be updated when batched.",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "time_log_batch",
|
||||
"fieldtype": "Link",
|
||||
"label": "Time Log Batch",
|
||||
"options": "Time Log Batch",
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "Will be updated when billed.",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "sales_invoice",
|
||||
"fieldtype": "Link",
|
||||
"label": "Sales Invoice",
|
||||
"options": "Sales Invoice",
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "file_list",
|
||||
@@ -143,7 +180,7 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Sales Invoice",
|
||||
"options": "Time Log",
|
||||
"permlevel": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// render
|
||||
wn.listview_settings['Time Log'] = {
|
||||
add_fields: ["`tabTime Log`.`status`", "`tabTime Log`.`billable`", "`tabTime Log`.`activity_type`"],
|
||||
selectable: true,
|
||||
onload: function(me) {
|
||||
me.appframe.add_button(wn._("Make Time Log Batch"), function() {
|
||||
@@ -16,12 +17,28 @@ wn.listview_settings['Time Log'] = {
|
||||
msgprint(wn._("Time Log is not billable") + ": " + d.name);
|
||||
return;
|
||||
}
|
||||
if(d.sales_invoice) {
|
||||
msgprint(wn._("Time Log has been Invoiced") + ": " + d.name);
|
||||
if(d.status!="Submitted") {
|
||||
msgprint(wn._("Time Log Status must be Submitted."));
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// make batch
|
||||
wn.model.with_doctype("Time Log Batch", function() {
|
||||
var tlb = wn.model.get_new_doc("Time Log Batch");
|
||||
$.each(selected, function(i, d) {
|
||||
var detail = wn.model.get_new_doc("Time Log Batch Detail");
|
||||
$.extend(detail, {
|
||||
"parenttype": "Time Log Batch",
|
||||
"parentfield": "time_log_batch_details",
|
||||
"parent": tlb.name,
|
||||
"time_log": d.name,
|
||||
"activity_type": d.activity_type,
|
||||
"created_by": d.owner,
|
||||
"idx": i+1
|
||||
});
|
||||
})
|
||||
wn.set_route("Form", "Time Log Batch", tlb.name);
|
||||
})
|
||||
|
||||
}, "icon-file-alt");
|
||||
}
|
||||
|
||||
20
projects/doctype/time_log_batch/test_time_log_batch.py
Normal file
20
projects/doctype/time_log_batch/test_time_log_batch.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import webnotes, unittest
|
||||
|
||||
class TimeLogBatchTest(unittest.TestCase):
|
||||
def test_time_log_status(self):
|
||||
self.assertEquals(webnotes.conn.get_value("Time Log", "_T-Time Log-00001", "status"), "Submitted")
|
||||
tlb = webnotes.bean("Time Log Batch", "_T-Time Log Batch-00001")
|
||||
tlb.submit()
|
||||
self.assertEquals(webnotes.conn.get_value("Time Log", "_T-Time Log-00001", "status"), "Batched for Billing")
|
||||
tlb.cancel()
|
||||
self.assertEquals(webnotes.conn.get_value("Time Log", "_T-Time Log-00001", "status"), "Submitted")
|
||||
|
||||
test_records = [[
|
||||
{"rate": "500"},
|
||||
{
|
||||
"doctype": "Time Log Batch Detail",
|
||||
"parenttype": "Time Log Batch",
|
||||
"parentfield": "time_log_batch_details",
|
||||
"time_log": "_T-Time Log-00001",
|
||||
}
|
||||
]]
|
||||
@@ -1,5 +1,6 @@
|
||||
cur_frm.add_fetch("time_log", "activity_type", "activity_type");
|
||||
cur_frm.add_fetch("time_log", "owner", "created_by");
|
||||
cur_frm.add_fetch("time_log", "hours", "hours");
|
||||
|
||||
cur_frm.set_query("time_log", "time_log_batch_details", function(doc) {
|
||||
return {
|
||||
@@ -12,7 +13,24 @@ cur_frm.set_query("time_log", "time_log_batch_details", function(doc) {
|
||||
});
|
||||
|
||||
$.extend(cur_frm.cscript, {
|
||||
refresh: function() {
|
||||
refresh: function(doc) {
|
||||
cur_frm.set_intro({
|
||||
"Draft": wn._("Select Time Logs and Submit to create a new Sales Invoice."),
|
||||
"Submitted": wn._("Click on 'Make Sales Invoice' button to create a new Sales Invoice."),
|
||||
"Billed": wn._("This Time Log Batch has been billed."),
|
||||
"Cancelled": wn._("This Time Log Batch has been cancelled.")
|
||||
}[doc.status]);
|
||||
|
||||
if(doc.status=="Submitted") {
|
||||
cur_frm.add_custom_button("Make Sales Invoice", function() { cur_frm.cscript.make_invoice() },
|
||||
"icon-file-alt");
|
||||
}
|
||||
},
|
||||
make_invoice: function() {
|
||||
var doc = cur_frm.doc;
|
||||
wn.model.map({
|
||||
source: wn.model.get_doclist(doc.doctype, doc.name),
|
||||
target: "Sales Invoice"
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -2,10 +2,59 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
from webnotes import _
|
||||
|
||||
class DocType:
|
||||
def __init__(self, d, dl):
|
||||
self.doc, self.doclist = d, dl
|
||||
|
||||
def validate(self):
|
||||
self.set_status()
|
||||
self.doc.total_hours = 0.0
|
||||
for d in self.doclist.get({"doctype":"Time Log Batch Detail"}):
|
||||
tl = webnotes.doc("Time Log", d.time_log)
|
||||
self.update_time_log_values(d, tl)
|
||||
self.validate_time_log_is_submitted(tl)
|
||||
self.doc.total_hours += float(tl.hours or 0.0)
|
||||
|
||||
def update_time_log_values(self, d, tl):
|
||||
d.fields.update({
|
||||
"hours": tl.hours,
|
||||
"activity_type": tl.activity_type,
|
||||
"created_by": tl.owner
|
||||
})
|
||||
|
||||
def validate_time_log_is_submitted(self, tl):
|
||||
if tl.status != "Submitted":
|
||||
webnotes.msgprint(_("Time Log must have status 'Submitted'") + \
|
||||
" :" + tl.name + " (" + _(tl.status) + ")", raise_exception=True)
|
||||
|
||||
def set_status(self):
|
||||
self.doc.status = {
|
||||
"0": "Draft",
|
||||
"1": "Submitted",
|
||||
"2": "Cancelled"
|
||||
}[str(self.doc.docstatus or 0)]
|
||||
|
||||
if self.doc.sales_invoice:
|
||||
self.doc.status = "Billed"
|
||||
|
||||
def on_submit(self):
|
||||
# update time logs as batched
|
||||
self.update_status(self.doc.name)
|
||||
|
||||
def before_cancel(self):
|
||||
self.update_status(None)
|
||||
|
||||
def before_update_after_submit(self):
|
||||
self.update_status(self.doc.name)
|
||||
|
||||
def update_status(self, time_log_batch):
|
||||
self.set_status()
|
||||
for d in self.doclist.get({"doctype":"Time Log Batch Detail"}):
|
||||
tl = webnotes.bean("Time Log", d.time_log)
|
||||
tl.doc.time_log_batch = time_log_batch
|
||||
tl.doc.sales_invoice = self.doc.sales_invoice
|
||||
tl.update_after_submit()
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-02-28 17:57:33",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-02-28 18:36:28",
|
||||
"modified": "2013-03-01 17:54:57",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@@ -24,6 +24,7 @@
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"doctype": "DocPerm",
|
||||
@@ -57,19 +58,51 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rate"
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Draft",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Status",
|
||||
"options": "Draft\nSubmitted\nBilled\nCancelled",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "Will be updated after Sales Invoice is Submitted.",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "sales_invoice",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Sales Invoice",
|
||||
"options": "Sales Invoice",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "time_log_batch_details",
|
||||
"fieldtype": "Table",
|
||||
"label": "Time Log Batch Details",
|
||||
"options": "Time Log Batch Detail"
|
||||
"options": "Time Log Batch Detail",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"description": "In Hours",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "total_time",
|
||||
"fieldname": "total_hours",
|
||||
"fieldtype": "Float",
|
||||
"label": "Total Time",
|
||||
"in_list_view": 1,
|
||||
"label": "Total Hours",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-02-28 17:56:12",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-02-28 17:56:12",
|
||||
"modified": "2013-03-01 15:20:17",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@@ -47,5 +47,11 @@
|
||||
"fieldtype": "Data",
|
||||
"label": "Activity Type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "hours",
|
||||
"fieldtype": "Float",
|
||||
"label": "Hours"
|
||||
}
|
||||
]
|
||||
@@ -56,7 +56,18 @@ wn.module_page["Projects"] = [
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
{
|
||||
title: wn._("Reports"),
|
||||
right: true,
|
||||
icon: "icon-list",
|
||||
items: [
|
||||
{
|
||||
"label":wn._("Time Log Summary"),
|
||||
route: "Report2/Time Log/Time Log Summary",
|
||||
doctype: "Time Log"
|
||||
},
|
||||
]
|
||||
}]
|
||||
|
||||
pscript['onload_projects-home'] = function(wrapper) {
|
||||
wn.views.moduleview.make(wrapper, "Projects");
|
||||
|
||||
0
projects/report/__init__.py
Normal file
0
projects/report/__init__.py
Normal file
0
projects/report/time_log_summary/__init__.py
Normal file
0
projects/report/time_log_summary/__init__.py
Normal file
22
projects/report/time_log_summary/time_log_summary.txt
Normal file
22
projects/report/time_log_summary/time_log_summary.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"creation": "2013-03-01 17:36:35",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-03-01 18:17:13",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
{
|
||||
"doctype": "Report",
|
||||
"is_standard": "Yes",
|
||||
"json": "{\"filters\":[],\"columns\":[[\"name\",\"Time Log\"],[\"status\",\"Time Log\"],[\"from_time\",\"Time Log\"],[\"hours\",\"Time Log\"],[\"activity_type\",\"Time Log\"],[\"owner\",\"Time Log\"],[\"billable\",\"Time Log\"],[\"time_log_batch\",\"Time Log\"],[\"sales_invoice\",\"Time Log\"]],\"sort_by\":\"Time Log.name\",\"sort_order\":\"desc\",\"sort_by_next\":\"\",\"sort_order_next\":\"desc\"}",
|
||||
"name": "__common__",
|
||||
"ref_doctype": "Time Log",
|
||||
"report_name": "Time Log Summary",
|
||||
"report_type": "Report Builder"
|
||||
},
|
||||
{
|
||||
"doctype": "Report",
|
||||
"name": "Time Log Summary"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user