From fee642d547f7e2e13d6c122e1d67a68b0a5395e3 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 1 Mar 2013 18:24:52 +0530 Subject: [PATCH] completed Time Log / Time Log Batch --- .../doctype/sales_invoice/sales_invoice.py | 19 ++++++- .../sales_invoice/sales_invoice_map.js | 16 ++++++ .../sales_invoice/test_sales_invoice.py | 25 +++++++++ .../sales_invoice_item/sales_invoice_item.txt | 11 +++- .../leave_application/leave_application.py | 12 +++- .../test_leave_application.py | 11 +++- patches/patch_list.py | 1 + .../activity_type/test_activity_type.py | 5 ++ projects/doctype/project/test_project.py | 8 +++ projects/doctype/task/test_task.py | 7 +++ projects/doctype/time_log/test_time_log.py | 19 +++++++ projects/doctype/time_log/time_log.js | 5 ++ projects/doctype/time_log/time_log.py | 46 +++++++++++----- projects/doctype/time_log/time_log.txt | 55 ++++++++++++++++--- projects/doctype/time_log/time_log_list.js | 23 +++++++- .../time_log_batch/test_time_log_batch.py | 20 +++++++ .../doctype/time_log_batch/time_log_batch.js | 22 +++++++- .../doctype/time_log_batch/time_log_batch.py | 51 ++++++++++++++++- .../doctype/time_log_batch/time_log_batch.txt | 41 ++++++++++++-- .../time_log_batch_detail.txt | 8 ++- projects/page/projects_home/projects_home.js | 13 ++++- projects/report/__init__.py | 0 projects/report/time_log_summary/__init__.py | 0 .../time_log_summary/time_log_summary.txt | 22 ++++++++ startup/install.py | 1 + startup/observers.py | 1 - startup/open_count.py | 2 + 27 files changed, 402 insertions(+), 42 deletions(-) create mode 100644 accounts/doctype/sales_invoice/sales_invoice_map.js create mode 100644 projects/doctype/activity_type/test_activity_type.py create mode 100644 projects/doctype/project/test_project.py create mode 100644 projects/doctype/task/test_task.py create mode 100644 projects/doctype/time_log/test_time_log.py create mode 100644 projects/doctype/time_log/time_log.js create mode 100644 projects/doctype/time_log_batch/test_time_log_batch.py create mode 100644 projects/report/__init__.py create mode 100644 projects/report/time_log_summary/__init__.py create mode 100644 projects/report/time_log_summary/time_log_summary.txt diff --git a/accounts/doctype/sales_invoice/sales_invoice.py b/accounts/doctype/sales_invoice/sales_invoice.py index 997339866d..7068c72a8a 100644 --- a/accounts/doctype/sales_invoice/sales_invoice.py +++ b/accounts/doctype/sales_invoice/sales_invoice.py @@ -75,6 +75,7 @@ class DocType(SellingController): self.set_aging_date() self.set_against_income_account() self.validate_c_form() + self.validate_time_logs_are_submitted() self.validate_recurring_invoice() def on_submit(self): @@ -104,7 +105,7 @@ class DocType(SellingController): self.update_against_document_in_jv() self.update_c_form() - + self.update_time_log_batch(self.doc.name) self.convert_to_recurring() @@ -122,12 +123,28 @@ class DocType(SellingController): self.check_next_docstatus() sales_com_obj.update_prevdoc_detail(0, self) + self.update_time_log_batch(None) self.make_gl_entries(is_cancel=1) def on_update_after_submit(self): self.validate_recurring_invoice() self.convert_to_recurring() + def update_time_log_batch(self, sales_invoice): + for d in self.doclist.get({"doctype":"Sales Invoice Item"}): + if d.time_log_batch: + tlb = webnotes.bean("Time Log Batch", d.time_log_batch) + tlb.doc.sales_invoice = sales_invoice + tlb.update_after_submit() + + def validate_time_logs_are_submitted(self): + for d in self.doclist.get({"doctype":"Sales Invoice Item"}): + if d.time_log_batch: + status = webnotes.conn.get_value("Time Log Batch", d.time_log_batch, "status") + if status!="Submitted": + webnotes.msgprint(_("Time Log Batch status must be 'Submitted'") + ":" + d.time_log_batch, + raise_exception=True) + def set_pos_fields(self): """Set retail related fields from pos settings""" pos = webnotes.conn.sql("select * from `tabPOS Setting` where ifnull(user,'') = '%s' and company = '%s'" % (session['user'], self.doc.company), as_dict=1) diff --git a/accounts/doctype/sales_invoice/sales_invoice_map.js b/accounts/doctype/sales_invoice/sales_invoice_map.js new file mode 100644 index 0000000000..dec4c6fa7d --- /dev/null +++ b/accounts/doctype/sales_invoice/sales_invoice_map.js @@ -0,0 +1,16 @@ +wn.model.map_info["Sales Invoice"] = { + "Time Log Batch": { + table_map: { + "Sales Invoice Item": "Time Log Batch", + }, + field_map: { + "Sales Invoice Item": { + "basic_rate": "rate", + "time_log_batch": "name", + "qty": "total_hours", + "stock_uom": "=Hour", + "description": "=via Time Logs" + } + }, + } +} \ No newline at end of file diff --git a/accounts/doctype/sales_invoice/test_sales_invoice.py b/accounts/doctype/sales_invoice/test_sales_invoice.py index 6dcb713d76..9059978337 100644 --- a/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -28,6 +28,31 @@ class TestSalesInvoice(unittest.TestCase): jv.cancel() self.assertEquals(webnotes.conn.get_value("Sales Invoice", w.doc.name, "outstanding_amount"), 561.8) + + def test_time_log_batch(self): + tlb = webnotes.bean("Time Log Batch", "_T-Time Log Batch-00001") + tlb.submit() + + w = webnotes.bean(webnotes.copy_doclist(test_records[0])) + w.doclist[1].time_log_batch = "_T-Time Log Batch-00001" + w.insert() + w.submit() + + self.assertEquals(webnotes.conn.get_value("Time Log Batch", "_T-Time Log Batch-00001", "status"), + "Billed") + + self.assertEquals(webnotes.conn.get_value("Time Log", "_T-Time Log-00001", "status"), + "Billed") + + w.cancel() + + self.assertEquals(webnotes.conn.get_value("Time Log Batch", "_T-Time Log Batch-00001", "status"), + "Submitted") + + self.assertEquals(webnotes.conn.get_value("Time Log", "_T-Time Log-00001", "status"), + "Batched for Billing") + + test_dependencies = ["Journal Voucher"] diff --git a/accounts/doctype/sales_invoice_item/sales_invoice_item.txt b/accounts/doctype/sales_invoice_item/sales_invoice_item.txt index 88c5aa857a..ae1afe9af6 100644 --- a/accounts/doctype/sales_invoice_item/sales_invoice_item.txt +++ b/accounts/doctype/sales_invoice_item/sales_invoice_item.txt @@ -1,8 +1,8 @@ [ { - "creation": "2013-01-10 16:34:09", + "creation": "2013-01-29 19:25:49", "docstatus": 0, - "modified": "2013-01-29 16:27:51", + "modified": "2013-03-01 13:41:51", "modified_by": "Administrator", "owner": "Administrator" }, @@ -320,6 +320,13 @@ "read_only": 1, "search_index": 1 }, + { + "doctype": "DocField", + "fieldname": "time_log_batch", + "fieldtype": "Link", + "label": "Time Log Batch", + "options": "Time Log Batch" + }, { "doctype": "DocField", "fieldname": "item_tax_rate", diff --git a/hr/doctype/leave_application/leave_application.py b/hr/doctype/leave_application/leave_application.py index 313f27f8ec..e8cc446ea5 100755 --- a/hr/doctype/leave_application/leave_application.py +++ b/hr/doctype/leave_application/leave_application.py @@ -22,6 +22,7 @@ from webnotes.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_ from webnotes import msgprint class LeaveDayBlockedError(Exception): pass +class OverlapError(Exception): pass from webnotes.model.controller import DocListController class DocType(DocListController): @@ -129,17 +130,22 @@ class DocType(DocListController): (self.doc.leave_type,), raise_exception=1) def validate_leave_overlap(self): + if not self.doc.name: + self.doc.name = "New Leave Application" + for d in webnotes.conn.sql("""select name, leave_type, posting_date, from_date, to_date from `tabLeave Application` where - (from_date <= %(to_date)s and to_date >= %(from_date)s) - and employee = %(employee)s + employee = %(employee)s and docstatus < 2 and status in ("Open", "Approved") + and (from_date between %(from_date)s and %(to_date)s + or to_date between %(from_date)s and %(to_date)s + or %(from_date)s between from_date and to_date) and name != %(name)s""", self.doc.fields, as_dict = 1): - msgprint("Employee : %s has already applied for %s between %s and %s on %s. Please refer Leave Application : %s" % (self.doc.employee, cstr(d['leave_type']), formatdate(d['from_date']), formatdate(d['to_date']), formatdate(d['posting_date']), d['name'], d['name']), raise_exception = 1) + msgprint("Employee : %s has already applied for %s between %s and %s on %s. Please refer Leave Application : %s" % (self.doc.employee, cstr(d['leave_type']), formatdate(d['from_date']), formatdate(d['to_date']), formatdate(d['posting_date']), d['name'], d['name']), raise_exception = OverlapError) def validate_max_days(self): max_days = webnotes.conn.sql("select max_days_allowed from `tabLeave Type` where name = '%s'" %(self.doc.leave_type)) diff --git a/hr/doctype/leave_application/test_leave_application.py b/hr/doctype/leave_application/test_leave_application.py index dc1b463e97..bc4a38c669 100644 --- a/hr/doctype/leave_application/test_leave_application.py +++ b/hr/doctype/leave_application/test_leave_application.py @@ -1,7 +1,7 @@ import webnotes import unittest -from hr.doctype.leave_application.leave_application import LeaveDayBlockedError +from hr.doctype.leave_application.leave_application import LeaveDayBlockedError, OverlapError class TestLeaveApplication(unittest.TestCase): def get_application(self, doclist): @@ -23,13 +23,22 @@ class TestLeaveApplication(unittest.TestCase): from webnotes.profile import add_role add_role("test1@example.com", "HR User") + + # clear other applications + webnotes.conn.sql("delete from `tabLeave Application`") application = self.get_application(test_records[1]) self.assertTrue(application.insert()) + + def test_overlap(self): + application = self.get_application(test_records[1]) + self.assertRaises(OverlapError, application.insert) def test_global_block_list(self): + application = self.get_application(test_records[3]) application.doc.leave_approver = "test@example.com" + webnotes.conn.set_value("Leave Block List", "_Test Leave Block List", "applies_to_all_departments", 1) webnotes.conn.set_value("Employee", "_T-Employee-0002", "department", diff --git a/patches/patch_list.py b/patches/patch_list.py index 11608098c1..42fc554ded 100644 --- a/patches/patch_list.py +++ b/patches/patch_list.py @@ -200,4 +200,5 @@ patch_list = [ 'execute:webnotes.reload_doc("accounts", "Print Format", "Sales Invoice Modern") # 2013-02-26', 'execute:webnotes.reload_doc("accounts", "Print Format", "Sales Invoice Spartan") # 2013-02-26', "execute:(not webnotes.conn.exists('Role', 'Projects Manager')) and webnotes.doc({'doctype':'Role', 'role_name':'Projects Manager'}).insert()", + "execute:(not webnotes.conn.exists('UOM', 'Hour')) and webnotes.doc({'uom_name': 'Unit', 'doctype': 'UOM', 'name': 'Hour'}).insert()", ] \ No newline at end of file diff --git a/projects/doctype/activity_type/test_activity_type.py b/projects/doctype/activity_type/test_activity_type.py new file mode 100644 index 0000000000..77ef7b30bf --- /dev/null +++ b/projects/doctype/activity_type/test_activity_type.py @@ -0,0 +1,5 @@ +test_records = [ + [{"activity_type":"_Test Activity Type"}], + [{"activity_type":"_Test Activity Type 1"}], + [{"activity_type":"_Test Activity Type 2"}] +] \ No newline at end of file diff --git a/projects/doctype/project/test_project.py b/projects/doctype/project/test_project.py new file mode 100644 index 0000000000..bd7460d077 --- /dev/null +++ b/projects/doctype/project/test_project.py @@ -0,0 +1,8 @@ +test_records = [[{ + "project_name": "_Test Project", + "status": "Open" +}], +[{ + "project_name": "_Test Project 1", + "status": "Open" +}]] \ No newline at end of file diff --git a/projects/doctype/task/test_task.py b/projects/doctype/task/test_task.py new file mode 100644 index 0000000000..2e95806cb0 --- /dev/null +++ b/projects/doctype/task/test_task.py @@ -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"] \ No newline at end of file diff --git a/projects/doctype/time_log/test_time_log.py b/projects/doctype/time_log/test_time_log.py new file mode 100644 index 0000000000..1168c01460 --- /dev/null +++ b/projects/doctype/time_log/test_time_log.py @@ -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"] \ No newline at end of file diff --git a/projects/doctype/time_log/time_log.js b/projects/doctype/time_log/time_log.js new file mode 100644 index 0000000000..a6023320f1 --- /dev/null +++ b/projects/doctype/time_log/time_log.js @@ -0,0 +1,5 @@ +$.extend(cur_frm.cscript, { + refresh: function(doc) { + + } +}); \ No newline at end of file diff --git a/projects/doctype/time_log/time_log.py b/projects/doctype/time_log/time_log.py index a605998b07..34969a9de1 100644 --- a/projects/doctype/time_log/time_log.py +++ b/projects/doctype/time_log/time_log.py @@ -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") diff --git a/projects/doctype/time_log/time_log.txt b/projects/doctype/time_log/time_log.txt index a6ad66742d..0e52464402 100644 --- a/projects/doctype/time_log/time_log.txt +++ b/projects/doctype/time_log/time_log.txt @@ -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 }, diff --git a/projects/doctype/time_log/time_log_list.js b/projects/doctype/time_log/time_log_list.js index 13d35c1b06..85c9b2a1ef 100644 --- a/projects/doctype/time_log/time_log_list.js +++ b/projects/doctype/time_log/time_log_list.js @@ -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"); } diff --git a/projects/doctype/time_log_batch/test_time_log_batch.py b/projects/doctype/time_log_batch/test_time_log_batch.py new file mode 100644 index 0000000000..54195c29cb --- /dev/null +++ b/projects/doctype/time_log_batch/test_time_log_batch.py @@ -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", + } +]] \ No newline at end of file diff --git a/projects/doctype/time_log_batch/time_log_batch.js b/projects/doctype/time_log_batch/time_log_batch.js index bd47c0b8d2..6e5165b50f 100644 --- a/projects/doctype/time_log_batch/time_log_batch.js +++ b/projects/doctype/time_log_batch/time_log_batch.js @@ -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" + }); } -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/projects/doctype/time_log_batch/time_log_batch.py b/projects/doctype/time_log_batch/time_log_batch.py index fe4e9a3d9d..6ec0e5b465 100644 --- a/projects/doctype/time_log_batch/time_log_batch.py +++ b/projects/doctype/time_log_batch/time_log_batch.py @@ -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 \ No newline at end of file + 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() + + + \ No newline at end of file diff --git a/projects/doctype/time_log_batch/time_log_batch.txt b/projects/doctype/time_log_batch/time_log_batch.txt index b217f23f13..7fe1827e5b 100644 --- a/projects/doctype/time_log_batch/time_log_batch.txt +++ b/projects/doctype/time_log_batch/time_log_batch.txt @@ -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 }, { diff --git a/projects/doctype/time_log_batch_detail/time_log_batch_detail.txt b/projects/doctype/time_log_batch_detail/time_log_batch_detail.txt index 0f7d23feb5..d1e1eaee2b 100644 --- a/projects/doctype/time_log_batch_detail/time_log_batch_detail.txt +++ b/projects/doctype/time_log_batch_detail/time_log_batch_detail.txt @@ -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" } ] \ No newline at end of file diff --git a/projects/page/projects_home/projects_home.js b/projects/page/projects_home/projects_home.js index 36e3c0bf2b..d9476e93f0 100644 --- a/projects/page/projects_home/projects_home.js +++ b/projects/page/projects_home/projects_home.js @@ -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"); diff --git a/projects/report/__init__.py b/projects/report/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/projects/report/time_log_summary/__init__.py b/projects/report/time_log_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/projects/report/time_log_summary/time_log_summary.txt b/projects/report/time_log_summary/time_log_summary.txt new file mode 100644 index 0000000000..3925a2dd80 --- /dev/null +++ b/projects/report/time_log_summary/time_log_summary.txt @@ -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" + } +] \ No newline at end of file diff --git a/startup/install.py b/startup/install.py index 5ddbf094c8..f584769beb 100644 --- a/startup/install.py +++ b/startup/install.py @@ -162,6 +162,7 @@ def import_defaults(): # UOM {'uom_name': 'Unit', 'doctype': 'UOM', 'name': 'Unit'}, + {'uom_name': 'Unit', 'doctype': 'UOM', 'name': 'Hour'}, {'uom_name': 'Box', 'doctype': 'UOM', 'name': 'Box'}, {'uom_name': 'Ft', 'doctype': 'UOM', 'name': 'Ft'}, {'uom_name': 'Kg', 'doctype': 'UOM', 'name': 'Kg'}, diff --git a/startup/observers.py b/startup/observers.py index 0e17c9d7d6..89980e4fc3 100644 --- a/startup/observers.py +++ b/startup/observers.py @@ -19,5 +19,4 @@ observer_map = { "*:on_submit": "home.update_feed", "Stock Entry:on_submit": "stock.doctype.material_request.material_request.update_completed_qty", "Stock Entry:on_cancel": "stock.doctype.material_request.material_request.update_completed_qty", -# "*:on_update": "webnotes.widgets.moduleview.update_count" } \ No newline at end of file diff --git a/startup/open_count.py b/startup/open_count.py index 7d8dcf8728..916ecbd5e0 100644 --- a/startup/open_count.py +++ b/startup/open_count.py @@ -27,4 +27,6 @@ queries = { "Production Order": {"docstatus":0}, "BOM": {"docstatus":0}, "Timesheet": {"docstatus":0}, + "Time Log": {"status":"Draft"}, + "Time Log Batch": {"status":"Draft"}, } \ No newline at end of file