diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 4886deaa719..47f1a5c1c34 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -462,26 +462,41 @@ cur_frm.set_query("asset", "items", function(doc, cdt, cdn) { frappe.ui.form.on('Sales Invoice', { setup: function(frm){ frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function(doc, cdt, cdn){ - return { - filters: [ - ["Timesheet", "status", "in", ["Submitted", "Payslip"]] - ] + return{ + query: "erpnext.projects.doctype.timesheet.timesheet.get_timesheet", + filters: {'project': doc.project} } } - } -}) + }, -frappe.ui.form.on('Sales Invoice Timesheet', { - time_sheet: function(frm){ + project: function(frm){ frm.call({ - method: "calculate_billing_amount_from_timesheet", + method: "add_timesheet_data", doc: frm.doc, callback: function(r, rt) { - refresh_field('total_billing_amount') + refresh_field(['timesheets']) } }) } }) -cur_frm.add_fetch("time_sheet", "total_billing_hours", "billing_hours"); -cur_frm.add_fetch("time_sheet", "total_billing_amount", "billing_amount"); \ No newline at end of file +frappe.ui.form.on('Sales Invoice Timesheet', { + time_sheet: function(frm, cdt, cdn){ + var d = locals[cdt][cdn]; + frappe.call({ + method: "erpnext.projects.doctype.timesheet.timesheet.get_timesheet_data", + args: { + 'name': d.time_sheet, + 'project': frm.doc.project || null + }, + callback: function(r, rt) { + if(r.message){ + data = r.message; + frappe.model.set_value(cdt, cdn, "billing_hours", data.billing_hours); + frappe.model.set_value(cdt, cdn, "billing_amount", data.billing_amount); + frappe.model.set_value(cdt, cdn, "timesheet_detail", data.timesheet_detail); + } + } + }) + } +}) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 562eaaac53a..99b479348ac 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -337,6 +337,34 @@ "set_only_once": 0, "unique": 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": 1, + "in_list_view": 0, + "label": "Project", + "length": 0, + "no_copy": 0, + "oldfieldname": "project_name", + "oldfieldtype": "Link", + "options": "Project", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -2873,34 +2901,6 @@ "set_only_once": 0, "unique": 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": 1, - "in_list_view": 0, - "label": "Project", - "length": 0, - "no_copy": 0, - "oldfieldname": "project_name", - "oldfieldtype": "Link", - "options": "Project", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -3867,7 +3867,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2016-08-31 15:47:32.064861", + "modified": "2016-09-08 09:05:02.895682", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 8b2113aed55..e9f142d206e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -14,6 +14,7 @@ from erpnext.accounts.doctype.sales_invoice.pos import update_multi_mode_option from erpnext.controllers.selling_controller import SellingController from erpnext.accounts.utils import get_account_currency from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so +from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data from erpnext.accounts.doctype.asset.depreciation \ import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal @@ -84,7 +85,7 @@ class SalesInvoice(SellingController): self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items") self.update_packing_list() self.set_billing_hours_and_amount() - self.calculate_billing_amount_from_timesheet() + self.update_timesheet_billing_for_project() def before_save(self): set_account_for_mode_of_payment(self) @@ -221,11 +222,21 @@ class SalesInvoice(SellingController): for d in self.timesheets: if d.time_sheet: timesheet = frappe.get_doc("Timesheet", d.time_sheet) - timesheet.sales_invoice = sales_invoice + self.update_time_sheet_detail(timesheet, d, sales_invoice) + timesheet.calculate_total_amounts() + timesheet.calculate_percentage_billed() timesheet.flags.ignore_validate_update_after_submit = True timesheet.set_status() timesheet.save() + def update_time_sheet_detail(self, timesheet, args, sales_invoice): + for data in timesheet.time_logs: + if (self.project and args.timesheet_detail == data.name) or \ + (not self.project and not data.sales_invoice) or \ + (not sales_invoice and data.sales_invoice == self.name): + data.sales_invoice = sales_invoice + if self.project: return + def on_update(self): self.set_paid_amount() @@ -450,13 +461,32 @@ class SalesInvoice(SellingController): def set_billing_hours_and_amount(self): for timesheet in self.timesheets: ts_doc = frappe.get_doc('Timesheet', timesheet.time_sheet) - if not timesheet.billing_hours and ts_doc.total_billing_hours: - timesheet.billing_hours = ts_doc.total_billing_hours + if not timesheet.billing_hours and ts_doc.total_billable_hours: + timesheet.billing_hours = ts_doc.total_billable_hours - if not timesheet.billing_amount and ts_doc.total_billing_amount: - timesheet.billing_amount = ts_doc.total_billing_amount + if not timesheet.billing_amount and ts_doc.total_billable_amount: + timesheet.billing_amount = ts_doc.total_billable_amount - def calculate_billing_amount_from_timesheet(self): + def update_timesheet_billing_for_project(self): + if not self.timesheets and self.project: + self.add_timesheet_data() + else: + self.calculate_billing_amount_for_timesheet() + + def add_timesheet_data(self): + self.set('timesheets', []) + if self.project: + for data in get_projectwise_timesheet_data(self.project): + self.append('timesheets', { + 'time_sheet': data.parent, + 'billing_hours': data.billing_hours, + 'billing_amount': data.billing_amt, + 'timesheet_detail': data.name + }) + + self.calculate_billing_amount_for_timesheet() + + def calculate_billing_amount_for_timesheet(self): total_billing_amount = 0.0 for data in self.timesheets: if data.billing_amount: diff --git a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json index 25dd3cb845c..1191ea71539 100644 --- a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json +++ b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json @@ -14,6 +14,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "time_sheet", "fieldtype": "Link", "hidden": 0, @@ -40,6 +41,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "billing_hours", "fieldtype": "Float", "hidden": 0, @@ -65,6 +67,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "billing_amount", "fieldtype": "Currency", "hidden": 0, @@ -85,6 +88,32 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "timesheet_detail", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Timesheet Detail", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "hide_heading": 0, @@ -97,7 +126,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2016-08-22 21:32:55.504103", + "modified": "2016-09-09 14:01:04.095775", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Timesheet", diff --git a/erpnext/demo/user/projects.py b/erpnext/demo/user/projects.py index 505ccfd077f..98024476793 100644 --- a/erpnext/demo/user/projects.py +++ b/erpnext/demo/user/projects.py @@ -22,7 +22,7 @@ def make_timesheet_for_projects(current_date ): ts = make_timesheet(employee, simulate = True, billable = 1, activity_type=get_random("Activity Type"), project=data.project, task =data.name) - if flt(ts.total_billing_amount) > 0.0: + if flt(ts.total_billable_amount) > 0.0: make_sales_invoice_for_timesheet(ts.name) frappe.db.commit() diff --git a/erpnext/docs/user/manual/en/projects/timesheet/timesheet-against-project.md b/erpnext/docs/user/manual/en/projects/timesheet/timesheet-against-project.md index 7460a4b9a0d..a8b2a6bf18c 100644 --- a/erpnext/docs/user/manual/en/projects/timesheet/timesheet-against-project.md +++ b/erpnext/docs/user/manual/en/projects/timesheet/timesheet-against-project.md @@ -8,6 +8,9 @@ Timesheets can be tracked against Project and Tasks so that you can get reports To bill Customer based on Timesheet, check "Is Billable" in the Timesheet created against Project and Task. To learn more about billing Customer from Timesheet, click [here]({{docs_base_url}}/user/manual/en/projects/timesheet/sales-invoice-from-timesheet.html). +User can also make invoice against timesheet by selecting the project on the invoice. System will fetch the records from the timesheet based on selected project, for mode detail check below video + + ####Project Costing When creating Timesheet, Employee will have to select an Activity Type. For each Activity Type, you can create an Activity Cost master. In the Activity Cost, Billing Rate and Costing rate is defined for each Employee. diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0706a2cd01f..802e045d653 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -295,7 +295,8 @@ erpnext.patches.v7_0.rename_prevdoc_fields erpnext.patches.v7_0.rename_time_sheet_doctype execute:frappe.delete_doc_if_exists("Report", "Customers Not Buying Since Long Time") erpnext.patches.v7_0.make_is_group_fieldtype_as_check -execute:frappe.reload_doc('projects', 'doctype', 'timesheet', force=True) #2016-08-23 +execute:frappe.reload_doc('projects', 'doctype', 'timesheet') #2016-09-12 +erpnext.patches.v7_1.rename_field_timesheet execute:frappe.delete_doc_if_exists("Report", "Employee Holiday Attendance") execute:frappe.delete_doc_if_exists("DocType", "Payment Tool") execute:frappe.delete_doc_if_exists("DocType", "Payment Tool Detail") @@ -323,4 +324,5 @@ erpnext.patches.v7_0.update_missing_employee_in_timesheet erpnext.patches.v7_0.update_status_for_timesheet erpnext.patches.v7_0.set_party_name_in_payment_entry erpnext.patches.v7_1.set_student_guardian -erpnext.patches.v7_0.update_conversion_factor_in_supplier_quotation_item \ No newline at end of file +erpnext.patches.v7_0.update_conversion_factor_in_supplier_quotation_item +erpnext.patches.v7_1.move_sales_invoice_from_parent_to_child_timesheet \ No newline at end of file diff --git a/erpnext/patches/v7_0/move_timelogbatch_from_salesinvoiceitem_to_salesinvoicetimesheet.py b/erpnext/patches/v7_0/move_timelogbatch_from_salesinvoiceitem_to_salesinvoicetimesheet.py index 695c552f066..a365f656057 100644 --- a/erpnext/patches/v7_0/move_timelogbatch_from_salesinvoiceitem_to_salesinvoicetimesheet.py +++ b/erpnext/patches/v7_0/move_timelogbatch_from_salesinvoiceitem_to_salesinvoicetimesheet.py @@ -3,12 +3,12 @@ import frappe def execute(): frappe.reload_doc('accounts', 'doctype', 'sales_invoice') frappe.reload_doc('accounts', 'doctype', 'sales_invoice_payment') - for time_sheet in frappe.db.sql(""" select sales_invoice, name, total_billing_amount from `tabTimesheet` + for time_sheet in frappe.db.sql(""" select sales_invoice, name, total_billable_amount from `tabTimesheet` where sales_invoice is not null and docstatus < 2""", as_dict=True): si_doc = frappe.get_doc('Sales Invoice', time_sheet.sales_invoice) ts = si_doc.append('timesheets',{}) ts.time_sheet = time_sheet.name - ts.billing_amount = time_sheet.total_billing_amount + ts.billing_amount = time_sheet.total_billable_amount si_doc.update_time_sheet(time_sheet.sales_invoice) si_doc.flags.ignore_validate_update_after_submit = True si_doc.save() \ No newline at end of file diff --git a/erpnext/patches/v7_1/move_sales_invoice_from_parent_to_child_timesheet.py b/erpnext/patches/v7_1/move_sales_invoice_from_parent_to_child_timesheet.py new file mode 100644 index 00000000000..d1ec7c697e7 --- /dev/null +++ b/erpnext/patches/v7_1/move_sales_invoice_from_parent_to_child_timesheet.py @@ -0,0 +1,20 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('projects', 'doctype', 'timesheet_detail') + frappe.reload_doc('accounts', 'doctype', 'sales_invoice_timesheet') + + frappe.db.sql(""" update + `tabTimesheet` as ts, + (select + sum(billing_amount) as billing_amount, sum(billing_hours) as billing_hours, time_sheet + from `tabSales Invoice Timesheet` where docstatus = 1 group by time_sheet + ) as sit + set + ts.total_billed_amount = sit.billing_amount, ts.total_billed_hours = sit.billing_hours, + ts.per_billed = ((sit.billing_amount * 100)/ts.total_billable_amount) + where ts.name = sit.time_sheet and ts.docstatus = 1""") + + frappe.db.sql(""" update `tabTimesheet Detail` tsd, `tabTimesheet` ts set tsd.sales_invoice = ts.sales_invoice + where tsd.parent = ts.name and ts.sales_invoice is not null""") \ No newline at end of file diff --git a/erpnext/patches/v7_1/rename_field_timesheet.py b/erpnext/patches/v7_1/rename_field_timesheet.py new file mode 100644 index 00000000000..3690a2e79d4 --- /dev/null +++ b/erpnext/patches/v7_1/rename_field_timesheet.py @@ -0,0 +1,11 @@ +from __future__ import unicode_literals +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + doctype = 'Timesheet' + fields_dict = {'total_billing_amount': 'total_billable_amount', 'total_billing_hours': 'total_billable_hours'} + + for old_fieldname, new_fieldname in fields_dict.items(): + if old_fieldname in frappe.db.get_table_columns(doctype): + rename_field(doctype, old_fieldname, new_fieldname) diff --git a/erpnext/patches/v7_1/update_total_billing_hours.py b/erpnext/patches/v7_1/update_total_billing_hours.py index a38b88d594d..b9c96028f52 100644 --- a/erpnext/patches/v7_1/update_total_billing_hours.py +++ b/erpnext/patches/v7_1/update_total_billing_hours.py @@ -5,10 +5,10 @@ def execute(): frappe.reload_doc('projects', 'doctype', 'timesheet_detail') frappe.reload_doc('accounts', 'doctype', 'sales_invoice_timesheet') - frappe.db.sql("""update tabTimesheet set total_billing_hours=total_hours - where total_billing_amount>0 and docstatus = 1""") + frappe.db.sql("""update tabTimesheet set total_billable_hours=total_hours + where total_billable_amount>0 and docstatus = 1""") frappe.db.sql("""update `tabTimesheet Detail` set billing_hours=hours where docstatus < 2""") - frappe.db.sql(""" update `tabSales Invoice Timesheet` set billing_hours = (select total_billing_hours from `tabTimesheet` + frappe.db.sql(""" update `tabSales Invoice Timesheet` set billing_hours = (select total_billable_hours from `tabTimesheet` where name = time_sheet) where time_sheet is not null""") \ No newline at end of file diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index 8e7e562442c..369be6da3cf 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -9,20 +9,22 @@ import datetime from frappe.utils import now_datetime, nowdate from erpnext.projects.doctype.timesheet.timesheet import OverlapError from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice class TestTimesheet(unittest.TestCase): def test_timesheet_billing_amount(self): salary_structure = make_salary_structure("_T-Employee-0001") - timesheet = make_timesheet("_T-Employee-0001", True) + timesheet = make_timesheet("_T-Employee-0001", simulate = True, billable=1) self.assertEquals(timesheet.total_hours, 2) - self.assertEquals(timesheet.total_billing_hours, 2) + self.assertEquals(timesheet.total_billable_hours, 2) self.assertEquals(timesheet.time_logs[0].billing_rate, 50) self.assertEquals(timesheet.time_logs[0].billing_amount, 100) + self.assertEquals(timesheet.total_billable_amount, 100) def test_salary_slip_from_timesheet(self): salary_structure = make_salary_structure("_T-Employee-0001") - timesheet = make_timesheet("_T-Employee-0001", simulate = True) + timesheet = make_timesheet("_T-Employee-0001", simulate = True, billable=1) salary_slip = make_salary_slip(timesheet.name) salary_slip.submit() @@ -51,11 +53,20 @@ class TestTimesheet(unittest.TestCase): item.rate = 100 sales_invoice.submit() - timesheet = frappe.get_doc('Timesheet', timesheet.name) self.assertEquals(sales_invoice.total_billing_amount, 100) self.assertEquals(timesheet.status, 'Billed') + def test_timesheet_billing_based_on_project(self): + timesheet = make_timesheet("_T-Employee-0001", simulate=True, billable=1, project = '_Test Project', company='_Test Company') + sales_invoice = create_sales_invoice(do_not_save=True) + sales_invoice.project = '_Test Project' + sales_invoice.submit() + + ts = frappe.get_doc('Timesheet', timesheet.name) + self.assertEquals(ts.per_billed, 100) + self.assertEquals(ts.time_logs[0].sales_invoice, sales_invoice.name) + def make_salary_structure(employee): name = frappe.db.get_value('Salary Structure Employee', {'employee': employee}, 'parent') if name: @@ -93,7 +104,7 @@ def make_salary_structure(employee): return salary_structure -def make_timesheet(employee, simulate=False, billable = 0, activity_type="_Test Activity Type", project=None, task=None): +def make_timesheet(employee, simulate=False, billable = 0, activity_type="_Test Activity Type", project=None, task=None, company=None): update_activity_type(activity_type) timesheet = frappe.new_doc("Timesheet") timesheet.employee = employee @@ -105,6 +116,7 @@ def make_timesheet(employee, simulate=False, billable = 0, activity_type="_Test timesheet_detail.to_time = timesheet_detail.from_time + datetime.timedelta(hours= timesheet_detail.hours) timesheet_detail.project = project timesheet_detail.task = task + timesheet_detail.company = company or '_Test Company' for data in timesheet.get('time_logs'): if simulate: diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 42fe005dfcc..7d0e71ae99e 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -21,6 +21,14 @@ frappe.ui.form.on("Timesheet", { } } } + + frm.fields_dict['time_logs'].grid.get_field('project').get_query = function() { + return{ + filters: { + 'company': frm.doc.company + } + } + } }, onload: function(frm){ @@ -31,7 +39,7 @@ frappe.ui.form.on("Timesheet", { refresh: function(frm) { if(frm.doc.docstatus==1) { - if(!frm.doc.sales_invoice && frm.doc.total_billing_amount > 0){ + if(frm.doc.per_billed < 100){ frm.add_custom_button(__("Make Sales Invoice"), function() { frm.trigger("make_invoice") }, "icon-file-alt"); } @@ -42,8 +50,9 @@ frappe.ui.form.on("Timesheet", { } } - if(frm.doc.sales_invoice) { + if(frm.doc.per_billed > 0) { cur_frm.fields_dict["time_logs"].grid.toggle_enable("billing_hours", false); + cur_frm.fields_dict["time_logs"].grid.toggle_enable("billable", false); } }, @@ -150,19 +159,22 @@ var calculate_time_and_amount = function(frm) { var tl = frm.doc.time_logs || []; total_working_hr = 0; total_billing_hr = 0; - total_billing_amount = 0; + total_billable_amount = 0; total_costing_amount = 0; for(var i=0; i 0 and self.total_billable_amount > 0: + self.per_billed = (self.total_billed_amount * 100) / self.total_billable_amount def update_billing_hours(self, args): if cint(args.billing_hours) == 0: @@ -52,7 +62,7 @@ class Timesheet(Document): "2": "Cancelled" }[str(self.docstatus or 0)] - if self.sales_invoice: + if self.per_billed == 100: self.status = "Billed" if self.salary_slip: @@ -236,31 +246,70 @@ class Timesheet(Document): def update_cost(self): for data in self.time_logs: - if data.activity_type and (not data.billing_amount or not data.costing_amount): + if data.activity_type and data.billable: rate = get_activity_cost(self.employee, data.activity_type) hours = data.billing_hours or 0 if rate: - data.billing_rate = flt(rate.get('billing_rate')) - data.costing_rate = flt(rate.get('costing_rate')) + data.billing_rate = flt(rate.get('billing_rate')) if flt(data.billing_rate) == 0 else data.billing_rate + data.costing_rate = flt(rate.get('costing_rate')) if flt(data.costing_rate) == 0 else data.costing_rate data.billing_amount = data.billing_rate * hours data.costing_amount = data.costing_rate * hours +@frappe.whitelist() +def get_projectwise_timesheet_data(project, parent=None): + cond = '' + if parent: + cond = "and parent = %(parent)s" + + return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt + from `tabTimesheet Detail` where docstatus=1 and project = %(project)s {0} and billable = 1 + and sales_invoice is null""".format(cond), {'project': project, 'parent': parent}, as_dict=1) + +@frappe.whitelist() +def get_timesheet(doctype, txt, searchfield, start, page_len, filters): + if not filters: filters = {} + + condition = "" + if filters.get("project"): + condition = "and tsd.project = %(project)s" + + return frappe.db.sql("""select distinct tsd.parent from `tabTimesheet Detail` tsd, + `tabTimesheet` ts where + ts.status in ('Submitted', 'Payslip') and tsd.parent = ts.name and + tsd.docstatus = 1 and ts.total_billable_amount > 0 + and tsd.parent LIKE %(txt)s {condition} + order by tsd.parent limit %(start)s, %(page_len)s""" + .format(condition=condition), { + "txt": "%%%s%%" % frappe.db.escape(txt), + "start": start, "page_len": page_len, 'project': filters.get("project") + }) + +@frappe.whitelist() +def get_timesheet_data(name, project): + if project and project!='': + data = get_projectwise_timesheet_data(project, name) + else: + data = frappe.get_all('Timesheet', + fields = ["(total_billable_amount - total_billed_amount) as billing_amt", "total_billable_hours as billing_hours"], filters = {'name': name}) + + return { + 'billing_hours': data[0].billing_hours, + 'billing_amount': data[0].billing_amt, + 'timesheet_detail': data[0].name if project and project!= '' else None + } + @frappe.whitelist() def make_sales_invoice(source_name, target=None): target = frappe.new_doc("Sales Invoice") + timesheet = frappe.get_doc('Timesheet', source_name) - target.append("timesheets", get_mapped_doc("Timesheet", source_name, { - "Timesheet": { - "doctype": "Sales Invoice Timesheet", - "field_map": { - "total_billing_amount": "billing_amount", - "total_billing_hours": "billing_hours", - "name": "time_sheet" - }, - } - })) - - target.run_method("calculate_billing_amount_from_timesheet") + target.append('timesheets', { + 'time_sheet': timesheet.name, + 'billing_hours': flt(timesheet.total_billable_hours) - flt(timesheet.total_billed_hours), + 'billing_amount': flt(timesheet.total_billable_amount) - flt(timesheet.total_billed_amount) + }) + + target.run_method("calculate_billing_amount_for_timesheet") return target @@ -300,7 +349,7 @@ def get_activity_cost(employee=None, activity_type=None): ["costing_rate", "billing_rate"], as_dict=True) return rate[0] if rate else {} - + @frappe.whitelist() def get_events(start, end, filters=None): """Returns events for Gantt / Calendar view rendering. diff --git a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json index 9bd4b8df7e3..b1d32329ac6 100644 --- a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json +++ b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json @@ -10,33 +10,6 @@ "document_type": "Document", "editable_grid": 1, "fields": [ - { - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 1, - "depends_on": "", - "fieldname": "billable", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Bill", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -165,192 +138,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "billable", - "fieldname": "section_break_11", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "fieldname": "billing_hours", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Billing Hours", - "length": 0, - "no_copy": 0, - "permlevel": 1, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "billing_rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Billing Rate", - "length": 0, - "no_copy": 0, - "permlevel": 1, - "precision": "2", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "costing_rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Costing Rate", - "length": 0, - "no_copy": 0, - "permlevel": 1, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_14", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "depends_on": "", - "description": "", - "fieldname": "billing_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Billing Amount", - "length": 0, - "no_copy": 0, - "permlevel": 1, - "precision": "2", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "description": "", - "fieldname": "costing_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Costing Amount", - "length": 0, - "no_copy": 0, - "permlevel": 1, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -403,55 +190,28 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "project", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Project", - "length": 0, - "no_copy": 0, - "options": "Project", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "", - "fieldname": "task", + "depends_on": "eval:parent.production_order", + "fieldname": "workstation", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Task", + "label": "Workstation", "length": 0, "no_copy": 0, - "options": "Task", + "options": "Workstation", "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -483,34 +243,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:parent.production_order", - "fieldname": "workstation", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Workstation", - "length": 0, - "no_copy": 0, - "options": "Workstation", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -565,6 +297,429 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "project_details", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 3, + "fieldname": "project", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Project", + "length": 0, + "no_copy": 0, + "options": "Project", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 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_list_view": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "task", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Task", + "length": 0, + "no_copy": 0, + "options": "Task", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 1, + "depends_on": "", + "fieldname": "billable", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Bill", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_8", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "billable", + "fieldname": "billing_hours", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Billing Hours", + "length": 0, + "no_copy": 0, + "permlevel": 1, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "billable", + "fieldname": "section_break_11", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "billing_rate", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Billing Rate", + "length": 0, + "no_copy": 0, + "permlevel": 1, + "precision": "2", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "depends_on": "", + "description": "", + "fieldname": "billing_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Billing Amount", + "length": 0, + "no_copy": 0, + "permlevel": 1, + "precision": "2", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_14", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "costing_rate", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Costing Rate", + "length": 0, + "no_copy": 0, + "permlevel": 1, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "description": "", + "fieldname": "costing_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Costing Amount", + "length": 0, + "no_copy": 0, + "permlevel": 1, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reference", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Reference", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "sales_invoice", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Sales Invoice", + "length": 0, + "no_copy": 1, + "options": "Sales Invoice", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "hide_heading": 0, @@ -577,7 +732,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2016-08-26 03:00:11.431794", + "modified": "2016-09-09 13:36:03.057513", "modified_by": "Administrator", "module": "Projects", "name": "Timesheet Detail",