From fd4743cc314c4c1f89ec2d6f7968f9fd478ae9b4 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 12 May 2021 16:17:32 +0530 Subject: [PATCH 01/12] refactor: timesheet --- .../doctype/sales_invoice/sales_invoice.js | 51 +- .../doctype/sales_invoice/sales_invoice.json | 3 +- .../doctype/sales_invoice/sales_invoice.py | 24 +- .../sales_invoice_timesheet.json | 233 +--- erpnext/hooks.py | 3 +- erpnext/patches.txt | 1 + .../v7_0/convert_timelog_to_timesheet.py | 2 +- .../doctype/timesheet/test_timesheet.py | 14 +- .../projects/doctype/timesheet/timesheet.js | 87 +- .../projects/doctype/timesheet/timesheet.json | 24 +- .../projects/doctype/timesheet/timesheet.py | 65 +- .../timesheet_detail/timesheet_detail.json | 1160 +++-------------- erpnext/projects/report/billing_summary.py | 4 +- ...ee_hours_utilization_based_on_timesheet.py | 6 +- .../test_employee_util.py | 4 +- .../test_project_profitability.py | 2 +- erpnext/public/js/utils.js | 12 + 17 files changed, 498 insertions(+), 1197 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 7c73ad6c90..2b79e18358 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -685,14 +685,16 @@ frappe.ui.form.on('Sales Invoice', { }, project: function(frm){ - frm.call({ - method: "add_timesheet_data", - doc: frm.doc, - callback: function(r, rt) { - refresh_field(['timesheets']) - } - }) - frm.refresh(); + if (!frm.doc.is_return) { + frm.call({ + method: "add_timesheet_data", + doc: frm.doc, + callback: function(r, rt) { + refresh_field(['timesheets']) + } + }) + frm.refresh(); + } }, onload: function(frm) { @@ -808,27 +810,45 @@ frappe.ui.form.on('Sales Invoice', { }, refresh: function(frm) { - if (frm.doc.project) { + if (frm.doc.project && frm.doc.docstatus===0 && !frm.doc.is_return) { frm.add_custom_button(__('Fetch Timesheet'), function() { let d = new frappe.ui.Dialog({ title: __('Fetch Timesheet'), fields: [ { - "label" : "From", + "label" : __("From"), "fieldname": "from_time", "fieldtype": "Date", "reqd": 1, }, + { + "label" : __("Currency"), + "fieldname": "currency", + "fieldtype": "Link", + "options": "Currency", + "default": frm.doc.currency, + "reqd": 1, + "read_only": 1 + }, { fieldtype: 'Column Break', fieldname: 'col_break_1', }, { - "label" : "To", + "label" : __("To"), "fieldname": "to_time", "fieldtype": "Date", "reqd": 1, - } + }, + { + "label" : __("Project"), + "fieldname": "project", + "fieldtype": "Link", + "options": "Project", + "default": frm.doc.project, + "reqd": 1, + "read_only": 1 + }, ], primary_action: function() { let data = d.get_values(); @@ -837,7 +857,8 @@ frappe.ui.form.on('Sales Invoice', { args: { from_time: data.from_time, to_time: data.to_time, - project: frm.doc.project + project: data.project, + currency: data.currency }, callback: function(r) { if(!r.exc) { @@ -845,9 +866,11 @@ frappe.ui.form.on('Sales Invoice', { frm.clear_table('timesheets') r.message.forEach((d) => { frm.add_child('timesheets',{ + 'activity_type': d.activity_type, + 'description': d.description, 'time_sheet': d.parent, 'billing_hours': d.billing_hours, - 'billing_amount': d.billing_amt, + 'billing_amount': d.billing_amount, 'timesheet_detail': d.name }); }); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index c6c67b4ddc..607e4c43d0 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -748,6 +748,7 @@ { "collapsible": 1, "collapsible_depends_on": "eval:doc.total_billing_amount > 0", + "depends_on": "eval: !doc.is_return", "fieldname": "time_sheet_list", "fieldtype": "Section Break", "hide_days": 1, @@ -1969,7 +1970,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-04-15 23:57:58.766651", + "modified": "2021-05-13 17:53:26.185370", "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 bb74a02606..a008742390 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -125,6 +125,8 @@ class SalesInvoice(SellingController): self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items") if not self.is_return: self.validate_serial_numbers() + else: + self.timesheets = [] self.update_packing_list() self.set_billing_hours_and_amount() self.update_timesheet_billing_for_project() @@ -337,7 +339,7 @@ class SalesInvoice(SellingController): if "Healthcare" in active_domains: manage_invoice_submit_cancel(self, "on_cancel") - + self.unlink_sales_invoice_from_timesheets() self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') def update_status_updater_args(self): @@ -393,6 +395,18 @@ class SalesInvoice(SellingController): if validate_against_credit_limit: check_credit_limit(self.customer, self.company, bypass_credit_limit_check_at_sales_order) + def unlink_sales_invoice_from_timesheets(self): + for row in self.timesheets: + timesheet = frappe.get_doc('Timesheet', row.time_sheet) + for time_log in timesheet.time_logs: + if time_log.sales_invoice == self.name: + time_log.sales_invoice = None + timesheet.calculate_total_amounts() + timesheet.calculate_percentage_billed() + timesheet.flags.ignore_validate_update_after_submit = True + timesheet.set_status() + timesheet.db_update_all() + @frappe.whitelist() def set_missing_values(self, for_validate=False): pos = self.set_pos_fields(for_validate) @@ -427,7 +441,7 @@ class SalesInvoice(SellingController): timesheet.calculate_percentage_billed() timesheet.flags.ignore_validate_update_after_submit = True timesheet.set_status() - timesheet.save() + timesheet.db_update_all() def update_time_sheet_detail(self, timesheet, args, sales_invoice): for data in timesheet.time_logs: @@ -741,8 +755,10 @@ class SalesInvoice(SellingController): self.append('timesheets', { 'time_sheet': data.parent, 'billing_hours': data.billing_hours, - 'billing_amount': data.billing_amt, - 'timesheet_detail': data.name + 'billing_amount': data.billing_amount, + 'timesheet_detail': data.name, + 'activity_type': data.activity_type, + 'description': data.description }) self.calculate_billing_amount_for_timesheet() 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 f7b9aef96c..9321630829 100644 --- a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json +++ b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json @@ -1,172 +1,77 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-06-14 19:21:34.321662", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2016-06-14 19:21:34.321662", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "activity_type", + "description", + "billing_hours", + "billing_amount", + "time_sheet", + "timesheet_detail" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "time_sheet", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Time Sheet", - "length": 0, - "no_copy": 0, - "options": "Timesheet", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "time_sheet", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "label": "Time Sheet", + "options": "Timesheet", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_hours", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Billing Hours", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "billing_hours", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Billing Hours", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Billing Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "billing_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Billing Amount", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 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_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Timesheet Detail", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "allow_on_submit": 1, + "fieldname": "timesheet_detail", + "fieldtype": "Data", + "hidden": 1, + "label": "Timesheet Detail", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "activity_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Activity Type", + "options": "Activity Type", + "read_only": 1 + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-02-18 18:50:44.770361", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Sales Invoice Timesheet", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "istable": 1, + "links": [], + "modified": "2021-05-13 16:52:32.995266", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Sales Invoice Timesheet", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index bb6cd8bdc2..ca8ca871bd 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -304,7 +304,8 @@ doc_events = { # if payment entry not in auto cancel exempted doctypes it will cancel payment entry. auto_cancel_exempted_doctypes= [ "Payment Entry", - "Inpatient Medication Entry" + "Inpatient Medication Entry", + "Timesheet" ] after_migrate = ["erpnext.setup.install.update_select_perm_after_install"] diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 82d223cada..cec2dd5341 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -778,3 +778,4 @@ erpnext.patches.v12_0.add_ewaybill_validity_field erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number erpnext.patches.v13_0.set_pos_closing_as_failed +erpnext.patches.v13_0.rename_billable_to_is_billable_in_timesheet diff --git a/erpnext/patches/v7_0/convert_timelog_to_timesheet.py b/erpnext/patches/v7_0/convert_timelog_to_timesheet.py index 3af6622d96..8c60b5b71e 100644 --- a/erpnext/patches/v7_0/convert_timelog_to_timesheet.py +++ b/erpnext/patches/v7_0/convert_timelog_to_timesheet.py @@ -51,7 +51,7 @@ def execute(): def get_timelog_data(data): return { - 'billable': data.billable, + 'is_billable': data.billable, 'from_time': data.from_time, 'hours': data.hours, 'to_time': data.to_time, diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index d21ac0f2f0..2b0c3abdd7 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -37,7 +37,7 @@ class TestTimesheet(unittest.TestCase): emp = make_employee("test_employee_6@salary.com") make_salary_structure_for_timesheet(emp) - timesheet = make_timesheet(emp, simulate=True, billable=1) + timesheet = make_timesheet(emp, simulate=True, is_billable=1) self.assertEqual(timesheet.total_hours, 2) self.assertEqual(timesheet.total_billable_hours, 2) @@ -49,7 +49,7 @@ class TestTimesheet(unittest.TestCase): emp = make_employee("test_employee_6@salary.com") make_salary_structure_for_timesheet(emp) - timesheet = make_timesheet(emp, simulate=True, billable=0) + timesheet = make_timesheet(emp, simulate=True, is_billable=0) self.assertEqual(timesheet.total_hours, 2) self.assertEqual(timesheet.total_billable_hours, 0) @@ -61,7 +61,7 @@ class TestTimesheet(unittest.TestCase): emp = make_employee("test_employee_6@salary.com", company="_Test Company") salary_structure = make_salary_structure_for_timesheet(emp) - timesheet = make_timesheet(emp, simulate = True, billable=1) + timesheet = make_timesheet(emp, simulate = True, is_billable=1) salary_slip = make_salary_slip(timesheet.name) salary_slip.submit() @@ -82,7 +82,7 @@ class TestTimesheet(unittest.TestCase): def test_sales_invoice_from_timesheet(self): emp = make_employee("test_employee_6@salary.com") - timesheet = make_timesheet(emp, simulate=True, billable=1) + timesheet = make_timesheet(emp, simulate=True, is_billable=1) sales_invoice = make_sales_invoice(timesheet.name, '_Test Item', '_Test Customer') sales_invoice.due_date = nowdate() sales_invoice.submit() @@ -100,7 +100,7 @@ class TestTimesheet(unittest.TestCase): emp = make_employee("test_employee_6@salary.com") project = frappe.get_value("Project", {"project_name": "_Test Project"}) - timesheet = make_timesheet(emp, simulate=True, billable=1, project=project, company='_Test Company') + timesheet = make_timesheet(emp, simulate=True, is_billable=1, project=project, company='_Test Company') sales_invoice = create_sales_invoice(do_not_save=True) sales_invoice.project = project sales_invoice.submit() @@ -171,13 +171,13 @@ def make_salary_structure_for_timesheet(employee, company=None): return salary_structure -def make_timesheet(employee, simulate=False, billable = 0, activity_type="_Test Activity Type", project=None, task=None, company=None): +def make_timesheet(employee, simulate=False, is_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 timesheet.company = company or '_Test Company' timesheet_detail = timesheet.append('time_logs', {}) - timesheet_detail.billable = billable + timesheet_detail.is_billable = is_billable timesheet_detail.activity_type = activity_type timesheet_detail.from_time = now_datetime() timesheet_detail.hours = 2 diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index b123af5d18..5554ed9264 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -90,17 +90,50 @@ frappe.ui.form.on("Timesheet", { } if(frm.doc.per_billed > 0) { frm.fields_dict["time_logs"].grid.toggle_enable("billing_hours", false); - frm.fields_dict["time_logs"].grid.toggle_enable("billable", false); + frm.fields_dict["time_logs"].grid.toggle_enable("is_billable", false); } + frm.trigger('setup_filters'); + }, + + customer: function(frm) { + frm.set_query('parent_project', function(doc) { + return { + filters: { + "customer": doc.customer + } + }; + }); + frm.set_query('project', 'time_logs', function(doc) { + return { + filters: { + "customer": doc.customer + } + }; + }); + frm.refresh(); }, make_invoice: function(frm) { + let fields = [{ + "fieldtype": "Link", + "label": __("Item Code"), + "fieldname": "item_code", + "options": "Item" + }] + + if (!frm.doc.customer) { + fields.push({ + "fieldtype": "Link", + "label": __("Customer"), + "fieldname": "customer", + "options": "Customer", + "default": frm.doc.customer + }); + } + let dialog = new frappe.ui.Dialog({ - title: __("Select Item (optional)"), - fields: [ - {"fieldtype": "Link", "label": __("Item Code"), "fieldname": "item_code", "options":"Item"}, - {"fieldtype": "Link", "label": __("Customer"), "fieldname": "customer", "options":"Customer"} - ] + title: __("Create Sales Invoice"), + fields: fields }); dialog.set_primary_action(__('Create Sales Invoice'), () => { @@ -113,7 +146,8 @@ frappe.ui.form.on("Timesheet", { args: { "source_name": frm.doc.name, "item_code": args.item_code, - "customer": args.customer + "customer": frm.doc.customer || args.customer, + "currency": frm.doc.currency }, freeze: true, callback: function(r) { @@ -136,8 +170,7 @@ frappe.ui.form.on("Timesheet", { parent_project: function(frm) { set_project_in_timelog(frm); - }, - + } }); frappe.ui.form.on("Timesheet Detail", { @@ -196,7 +229,7 @@ frappe.ui.form.on("Timesheet Detail", { calculate_billing_costing_amount(frm, cdt, cdn); }, - billable: function(frm, cdt, cdn) { + is_billable: function(frm, cdt, cdn) { update_billing_hours(frm, cdt, cdn); update_time_rates(frm, cdt, cdn); calculate_billing_costing_amount(frm, cdt, cdn); @@ -239,9 +272,9 @@ var calculate_end_time = function(frm, cdt, cdn) { } }; -var update_billing_hours = function(frm, cdt, cdn){ - var child = locals[cdt][cdn]; - if(!child.billable) { +var update_billing_hours = function(frm, cdt, cdn) { + let child = frappe.get_doc(cdt, cdn); + if (!child.is_billable) { frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0); } else { // bill all hours by default @@ -249,19 +282,19 @@ var update_billing_hours = function(frm, cdt, cdn){ } }; -var update_time_rates = function(frm, cdt, cdn){ - var child = locals[cdt][cdn]; - if(!child.billable){ +var update_time_rates = function(frm, cdt, cdn) { + let child = frappe.get_doc(cdt, cdn); + if (!child.is_billable) { frappe.model.set_value(cdt, cdn, 'billing_rate', 0.0); } }; -var calculate_billing_costing_amount = function(frm, cdt, cdn){ - var child = locals[cdt][cdn]; - var billing_amount = 0.0; - var costing_amount = 0.0; +var calculate_billing_costing_amount = function(frm, cdt, cdn) { + let child = frappe.get_doc(cdt, cdn); + let billing_amount = 0.0; + let costing_amount = 0.0; - if(child.billing_hours && child.billable){ + if (child.billing_hours && child.is_billable) { billing_amount = (child.billing_hours * child.billing_rate); } costing_amount = flt(child.costing_rate * child.hours); @@ -271,18 +304,18 @@ var calculate_billing_costing_amount = function(frm, cdt, cdn){ }; var calculate_time_and_amount = function(frm) { - var tl = frm.doc.time_logs || []; - var total_working_hr = 0; - var total_billing_hr = 0; - var total_billable_amount = 0; - var total_costing_amount = 0; + let tl = frm.doc.time_logs || []; + let total_working_hr = 0; + let total_billing_hr = 0; + let total_billable_amount = 0; + let total_costing_amount = 0; for(var i=0; i Date: Sat, 15 May 2021 20:40:20 +0530 Subject: [PATCH 02/12] adding patch --- .../v13_0/rename_billable_to_is_billable_in_timesheet.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py diff --git a/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py b/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py new file mode 100644 index 0000000000..6860a37559 --- /dev/null +++ b/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + if "billable" in frappe.db.get_table_columns("Timesheet Detail"): + rename_field("Timesheet Detail", "billable", "is_billable") \ No newline at end of file From 42d2f663fa9f22542b2ef29a39f667774d56a907 Mon Sep 17 00:00:00 2001 From: Anupam Date: Mon, 17 May 2021 11:08:26 +0530 Subject: [PATCH 03/12] fix: sider fixes --- erpnext/projects/doctype/timesheet/timesheet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 5554ed9264..28535d7a34 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -119,7 +119,7 @@ frappe.ui.form.on("Timesheet", { "label": __("Item Code"), "fieldname": "item_code", "options": "Item" - }] + }]; if (!frm.doc.customer) { fields.push({ From 9bd779401d23902d161c70502ce091548989a37e Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 18 May 2021 22:41:28 +0530 Subject: [PATCH 04/12] added multi-currency fields --- .../doctype/activity_type/activity_type.js | 4 + .../projects/doctype/timesheet/timesheet.js | 81 +++++++++++++++---- .../projects/doctype/timesheet/timesheet.py | 17 +++- .../timesheet_detail/timesheet_detail.json | 36 ++++++++- 4 files changed, 115 insertions(+), 23 deletions(-) diff --git a/erpnext/projects/doctype/activity_type/activity_type.js b/erpnext/projects/doctype/activity_type/activity_type.js index 7eb3571af1..f1ba882812 100644 --- a/erpnext/projects/doctype/activity_type/activity_type.js +++ b/erpnext/projects/doctype/activity_type/activity_type.js @@ -1,4 +1,8 @@ frappe.ui.form.on("Activity Type", { + onload: function(frm) { + frm.set_currency_labels(["billing_rate", "costing_rate"], frappe.defaults.get_global_default('currency')); + }, + refresh: function(frm) { frm.add_custom_button(__("Activity Cost per Employee"), function() { frappe.route_options = {"activity_type": frm.doc.name}; diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 28535d7a34..532d64994f 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -93,6 +93,7 @@ frappe.ui.form.on("Timesheet", { frm.fields_dict["time_logs"].grid.toggle_enable("is_billable", false); } frm.trigger('setup_filters'); + frm.trigger('set_dynamic_field_label'); }, customer: function(frm) { @@ -113,6 +114,48 @@ frappe.ui.form.on("Timesheet", { frm.refresh(); }, + currency: function(frm) { + let base_currency = frappe.defaults.get_global_default('currency'); + if (base_currency != frm.doc.company) { + frappe.call({ + method: "erpnext.setup.utils.get_exchange_rate", + args: { + from_currency: frm.doc.currency, + to_currency: base_currency + }, + callback: function(r) { + if (r.message) { + frm.set_value('exchange_rate', flt(r.message)); + frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency + " = [?] " + base_currency); + } + } + }); + } + frm.trigger('set_dynamic_field_label'); + }, + + exchange_rate: function(frm) { + $.each(frm.doc.time_logs, function(i, d) { + calculate_billing_costing_amount(frm, d.doctype, d.name); + }); + calculate_time_and_amount(frm); + }, + + set_dynamic_field_label: function(frm) { + let base_currency = frappe.defaults.get_global_default('currency'); + frm.set_currency_labels(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], base_currency); + frm.set_currency_labels(["total_costing_amount", "total_billable_amount", "total_billed_amount"], frm.doc.currency); + frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs"); + frm.set_currency_labels(["billing_rate", "billing_amount", "costing_rate", "costing_amount"], frm.doc.currency, "time_logs"); + + let time_logs_grid = frm.fields_dict.time_logs.grid; + $.each(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], function(i, d) { + if(frappe.meta.get_docfield(time_logs_grid.doctype, d)) + time_logs_grid.set_column_disp(d, frm.doc.currency != base_currency); + }); + frm.refresh_fields(); + }, + make_invoice: function(frm) { let fields = [{ "fieldtype": "Link", @@ -204,35 +247,34 @@ frappe.ui.form.on("Timesheet Detail", { if(frm.doc.parent_project) { frappe.model.set_value(cdt, cdn, 'project', frm.doc.parent_project); } - - var $trigger_again = $('.form-grid').find('.grid-row').find('.btn-open-row'); - $trigger_again.on('click', () => { - $('.form-grid') - .find('[data-fieldname="timer"]') - .append(frappe.render_template("timesheet")); - frm.trigger("control_timer"); - }); }, + hours: function(frm, cdt, cdn) { calculate_end_time(frm, cdt, cdn); + calculate_billing_costing_amount(frm, cdt, cdn); + calculate_time_and_amount(frm); }, billing_hours: function(frm, cdt, cdn) { calculate_billing_costing_amount(frm, cdt, cdn); + calculate_time_and_amount(frm); }, billing_rate: function(frm, cdt, cdn) { calculate_billing_costing_amount(frm, cdt, cdn); + calculate_time_and_amount(frm); }, costing_rate: function(frm, cdt, cdn) { calculate_billing_costing_amount(frm, cdt, cdn); + calculate_time_and_amount(frm); }, is_billable: function(frm, cdt, cdn) { update_billing_hours(frm, cdt, cdn); update_time_rates(frm, cdt, cdn); calculate_billing_costing_amount(frm, cdt, cdn); + calculate_time_and_amount(frm); }, activity_type: function(frm, cdt, cdn) { @@ -240,7 +282,8 @@ frappe.ui.form.on("Timesheet Detail", { method: "erpnext.projects.doctype.timesheet.timesheet.get_activity_cost", args: { employee: frm.doc.employee, - activity_type: frm.selected_doc.activity_type + activity_type: frm.selected_doc.activity_type, + currency: frm.doc.currency }, callback: function(r){ if(r.message){ @@ -290,17 +333,21 @@ var update_time_rates = function(frm, cdt, cdn) { }; var calculate_billing_costing_amount = function(frm, cdt, cdn) { - let child = frappe.get_doc(cdt, cdn); + let row = frappe.get_doc(cdt, cdn); let billing_amount = 0.0; - let costing_amount = 0.0; - - if (child.billing_hours && child.is_billable) { - billing_amount = (child.billing_hours * child.billing_rate); + let base_billing_amount = 0.0; + let exchange_rate = flt(frm.doc.exchange_rate); + frappe.model.set_value(cdt, cdn, 'base_billing_rate', flt(row.billing_rate) * exchange_rate); + frappe.model.set_value(cdt, cdn, 'base_costing_rate', flt(row.costing_rate) * exchange_rate); + if (row.billing_hours && row.is_billable) { + base_billing_amount = flt(row.billing_hours) * flt(row.base_billing_rate); + billing_amount = flt(row.billing_hours) * flt(row.billing_rate); } - costing_amount = flt(child.costing_rate * child.hours); + + frappe.model.set_value(cdt, cdn, 'base_billing_amount', base_billing_amount); + frappe.model.set_value(cdt, cdn, 'base_costing_amount', flt(row.base_costing_rate) * flt(row.hours)); frappe.model.set_value(cdt, cdn, 'billing_amount', billing_amount); - frappe.model.set_value(cdt, cdn, 'costing_amount', costing_amount); - calculate_time_and_amount(frm); + frappe.model.set_value(cdt, cdn, 'costing_amount', flt(row.costing_rate) * flt(row.hours)); }; var calculate_time_and_amount = function(frm) { diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index d864c75279..1ee59aef8b 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -14,6 +14,7 @@ from frappe.model.document import Document from erpnext.manufacturing.doctype.workstation.workstation import (check_if_within_operating_hours, WorkstationHolidayError) from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations +from erpnext.setup.utils import get_exchange_rate class OverlapError(frappe.ValidationError): pass class OverWorkLoggedError(frappe.ValidationError): pass @@ -37,9 +38,9 @@ class Timesheet(Document): self.total_hours = 0.0 self.total_billable_hours = 0.0 self.total_billed_hours = 0.0 - self.total_billable_amount = 0.0 - self.total_costing_amount = 0.0 - self.total_billed_amount = 0.0 + self.total_billable_amount = self.base_total_billable_amount = 0.0 + self.total_costing_amount = self.base_total_costing_amount = 0.0 + self.total_billed_amount = self.base_total_billed_amount = 0.0 for d in self.get("time_logs"): self.update_billing_hours(d) @@ -47,10 +48,13 @@ class Timesheet(Document): self.total_hours += flt(d.hours) self.total_costing_amount += flt(d.costing_amount) + self.base_total_costing_amount += flt(d.base_costing_amount) if d.is_billable: self.total_billable_hours += flt(d.billing_hours) self.total_billable_amount += flt(d.billing_amount) + self.base_total_billable_amount += flt(d.base_billing_amount) self.total_billed_amount += flt(d.billing_amount) if d.sales_invoice else 0.0 + self.base_total_billed_amount += flt(d.base_billing_amount) if d.sales_invoice else 0.0 self.total_billed_hours += flt(d.billing_hours) if d.sales_invoice else 0.0 def calculate_percentage_billed(self): @@ -330,12 +334,17 @@ def set_missing_values(time_sheet, target): }) @frappe.whitelist() -def get_activity_cost(employee=None, activity_type=None): +def get_activity_cost(employee=None, activity_type=None, currency=None): + base_currency = frappe.defaults.get_global_default('currency') rate = frappe.db.get_values("Activity Cost", {"employee": employee, "activity_type": activity_type}, ["costing_rate", "billing_rate"], as_dict=True) if not rate: rate = frappe.db.get_values("Activity Type", {"activity_type": activity_type}, ["costing_rate", "billing_rate"], as_dict=True) + if rate and currency and currency!=base_currency: + exchnage_rate = get_exchange_rate(base_currency, currency) + rate[0]["costing_rate"] = rate[0]["costing_rate"] * exchnage_rate + rate[0]["billing_rate"] = rate[0]["billing_rate"] * exchnage_rate return rate[0] if rate else {} diff --git a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json index 0c9ed0bf20..ee04c612c9 100644 --- a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json +++ b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json @@ -31,9 +31,13 @@ "column_break_8", "billing_hours", "section_break_11", + "base_billing_rate", + "base_billing_amount", + "base_costing_rate", + "base_costing_amount", + "column_break_14", "billing_rate", "billing_amount", - "column_break_14", "costing_rate", "costing_amount" ], @@ -230,12 +234,40 @@ "fieldname": "description", "fieldtype": "Small Text", "label": "Description" + }, + { + "fieldname": "base_billing_rate", + "fieldtype": "Currency", + "label": "Billing Rate", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_billing_amount", + "fieldtype": "Currency", + "label": "Billing Amount", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_costing_rate", + "fieldtype": "Currency", + "label": "Costing Rate", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_costing_amount", + "fieldtype": "Currency", + "label": "Costing Amount", + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-05-15 16:16:10.688694", + "modified": "2021-05-18 12:19:33.205940", "modified_by": "Administrator", "module": "Projects", "name": "Timesheet Detail", From 4e73c8a79f1838de0f0d828da71aaad5110b4e3c Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 19 May 2021 14:58:12 +0530 Subject: [PATCH 05/12] restructuring timesheet fields --- .../projects/doctype/timesheet/timesheet.js | 20 +++++++---- .../projects/doctype/timesheet/timesheet.json | 36 +++++++++++++++++-- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 532d64994f..4512244027 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -145,14 +145,20 @@ frappe.ui.form.on("Timesheet", { let base_currency = frappe.defaults.get_global_default('currency'); frm.set_currency_labels(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], base_currency); frm.set_currency_labels(["total_costing_amount", "total_billable_amount", "total_billed_amount"], frm.doc.currency); - frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs"); - frm.set_currency_labels(["billing_rate", "billing_amount", "costing_rate", "costing_amount"], frm.doc.currency, "time_logs"); - let time_logs_grid = frm.fields_dict.time_logs.grid; - $.each(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], function(i, d) { - if(frappe.meta.get_docfield(time_logs_grid.doctype, d)) - time_logs_grid.set_column_disp(d, frm.doc.currency != base_currency); - }); + frm.toggle_display(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], + frm.doc.currency != base_currency) + + if (frm.doc?.time_logs.length > 0) { + frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs"); + frm.set_currency_labels(["billing_rate", "billing_amount", "costing_rate", "costing_amount"], frm.doc.currency, "time_logs"); + + let time_logs_grid = frm.fields_dict.time_logs.grid; + $.each(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], function(i, d) { + if (frappe.meta.get_docfield(time_logs_grid.doctype, d)) + time_logs_grid.set_column_disp(d, frm.doc.currency != base_currency); + }); + } frm.refresh_fields(); }, diff --git a/erpnext/projects/doctype/timesheet/timesheet.json b/erpnext/projects/doctype/timesheet/timesheet.json index 23e6ede967..75f7478ed1 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.json +++ b/erpnext/projects/doctype/timesheet/timesheet.json @@ -13,6 +13,7 @@ "company", "customer", "currency", + "exchange_rate", "sales_invoice", "column_break_3", "salary_slip", @@ -32,11 +33,14 @@ "total_hours", "billing_details", "total_billable_hours", - "total_billed_hours", - "total_costing_amount", + "base_total_billable_amount", + "base_total_billed_amount", + "base_total_costing_amount", "column_break_10", + "total_billed_hours", "total_billable_amount", "total_billed_amount", + "total_costing_amount", "per_billed", "section_break_18", "note", @@ -283,13 +287,39 @@ "fieldtype": "Link", "label": "Currency", "options": "Currency" + }, + { + "fieldname": "base_total_costing_amount", + "fieldtype": "Currency", + "label": "Total Costing Amount", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_total_billable_amount", + "fieldtype": "Currency", + "label": "Total Billable Amount", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_total_billed_amount", + "fieldtype": "Currency", + "label": "Total Billed Amount", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "exchange_rate", + "fieldtype": "Float", + "label": "Exchange Rate" } ], "icon": "fa fa-clock-o", "idx": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-13 17:13:29.954960", + "modified": "2021-05-18 16:10:08.249619", "modified_by": "Administrator", "module": "Projects", "name": "Timesheet", From aeb88385bbae9470956aefec57d69eb3a6fbc0fa Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 20 May 2021 14:11:36 +0530 Subject: [PATCH 06/12] patch: timesheet changes --- erpnext/patches.txt | 2 +- ...me_billable_to_is_billable_in_timesheet.py | 7 ------ .../patches/v13_0/update_timesheet_changes.py | 24 +++++++++++++++++++ 3 files changed, 25 insertions(+), 8 deletions(-) delete mode 100644 erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py create mode 100644 erpnext/patches/v13_0/update_timesheet_changes.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0caad586e5..d4655e19b9 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -779,4 +779,4 @@ erpnext.patches.v12_0.add_ewaybill_validity_field erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number erpnext.patches.v13_0.set_pos_closing_as_failed -erpnext.patches.v13_0.rename_billable_to_is_billable_in_timesheet +erpnext.patches.v13_0.update_timesheet_changes diff --git a/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py b/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py deleted file mode 100644 index 6860a37559..0000000000 --- a/erpnext/patches/v13_0/rename_billable_to_is_billable_in_timesheet.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.utils.rename_field import rename_field - -def execute(): - if "billable" in frappe.db.get_table_columns("Timesheet Detail"): - rename_field("Timesheet Detail", "billable", "is_billable") \ No newline at end of file diff --git a/erpnext/patches/v13_0/update_timesheet_changes.py b/erpnext/patches/v13_0/update_timesheet_changes.py new file mode 100644 index 0000000000..87178b2f84 --- /dev/null +++ b/erpnext/patches/v13_0/update_timesheet_changes.py @@ -0,0 +1,24 @@ +from __future__ import unicode_literals +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + if "billable" in frappe.db.get_table_columns("Timesheet Detail"): + rename_field("Timesheet Detail", "billable", "is_billable") + + base_currency = frappe.defaults.get_global_default('currency') + frappe.reload_doc("projects", "doctype", "timesheet") + frappe.reload_doc("projects", "doctype", "timesheet_detail") + + frappe.db.sql("""UPDATE `tabTimesheet Detail` + SET base_billing_rate = billing_rate, + base_billing_amount = billing_amount, + base_costing_rate = costing_rate, + base_costing_amount = costing_amount""") + + frappe.db.sql("""UPDATE `tabTimesheet` + SET currency = '{0}', + exchange_rate = 1.0, + base_total_billable_amount = total_billable_amount, + base_total_billed_amount = total_billed_amount, + base_total_costing_amount = total_costing_amount""".format(base_currency)) \ No newline at end of file From aa516e5d178769a07fe1c400f60e96ca54ac36a7 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 20 May 2021 14:12:18 +0530 Subject: [PATCH 07/12] fix: review changes --- erpnext/projects/doctype/timesheet/timesheet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 4512244027..9bb9c38532 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -116,7 +116,7 @@ frappe.ui.form.on("Timesheet", { currency: function(frm) { let base_currency = frappe.defaults.get_global_default('currency'); - if (base_currency != frm.doc.company) { + if (base_currency != frm.doc.currency) { frappe.call({ method: "erpnext.setup.utils.get_exchange_rate", args: { From 1270febfffc16fc6c548487727629b56f57fa354 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 20 May 2021 14:15:58 +0530 Subject: [PATCH 08/12] fix: sider issues --- erpnext/patches/v13_0/update_timesheet_changes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v13_0/update_timesheet_changes.py b/erpnext/patches/v13_0/update_timesheet_changes.py index 87178b2f84..3acce18c63 100644 --- a/erpnext/patches/v13_0/update_timesheet_changes.py +++ b/erpnext/patches/v13_0/update_timesheet_changes.py @@ -11,13 +11,13 @@ def execute(): frappe.reload_doc("projects", "doctype", "timesheet_detail") frappe.db.sql("""UPDATE `tabTimesheet Detail` - SET base_billing_rate = billing_rate, + SET base_billing_rate = billing_rate, base_billing_amount = billing_amount, base_costing_rate = costing_rate, base_costing_amount = costing_amount""") frappe.db.sql("""UPDATE `tabTimesheet` - SET currency = '{0}', + SET currency = '{0}', exchange_rate = 1.0, base_total_billable_amount = total_billable_amount, base_total_billed_amount = total_billed_amount, From 0d8b9a9d0a110d7379ebb9eba9e96d61bb1ca02b Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 20 May 2021 19:53:30 +0530 Subject: [PATCH 09/12] fix(patch): billable field not renamed --- erpnext/patches/v13_0/update_timesheet_changes.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/patches/v13_0/update_timesheet_changes.py b/erpnext/patches/v13_0/update_timesheet_changes.py index 3acce18c63..93b7f8e59a 100644 --- a/erpnext/patches/v13_0/update_timesheet_changes.py +++ b/erpnext/patches/v13_0/update_timesheet_changes.py @@ -3,19 +3,20 @@ import frappe from frappe.model.utils.rename_field import rename_field def execute(): - if "billable" in frappe.db.get_table_columns("Timesheet Detail"): - rename_field("Timesheet Detail", "billable", "is_billable") - - base_currency = frappe.defaults.get_global_default('currency') frappe.reload_doc("projects", "doctype", "timesheet") frappe.reload_doc("projects", "doctype", "timesheet_detail") + if frappe.db.has_column("Timesheet Detail", "billable"): + rename_field("Timesheet Detail", "billable", "is_billable") + + base_currency = frappe.defaults.get_global_default('currency') + frappe.db.sql("""UPDATE `tabTimesheet Detail` SET base_billing_rate = billing_rate, base_billing_amount = billing_amount, base_costing_rate = costing_rate, base_costing_amount = costing_amount""") - + frappe.db.sql("""UPDATE `tabTimesheet` SET currency = '{0}', exchange_rate = 1.0, From be247ec3ded28cec31228a61f9ea8e8ceb29cc88 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 20 May 2021 22:17:20 +0530 Subject: [PATCH 10/12] fix: error message placeholders and sider issues --- erpnext/projects/doctype/timesheet/timesheet.js | 4 ++-- erpnext/projects/doctype/timesheet/timesheet.py | 16 ++++++++-------- erpnext/public/js/utils.js | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 9bb9c38532..63078ea7bd 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -146,10 +146,10 @@ frappe.ui.form.on("Timesheet", { frm.set_currency_labels(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], base_currency); frm.set_currency_labels(["total_costing_amount", "total_billable_amount", "total_billed_amount"], frm.doc.currency); - frm.toggle_display(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], + frm.toggle_display(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], frm.doc.currency != base_currency) - if (frm.doc?.time_logs.length > 0) { + if (frm.doc.time_logs.length > 0) { frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs"); frm.set_currency_labels(["billing_rate", "billing_amount", "costing_rate", "costing_amount"], frm.doc.currency, "time_logs"); diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 1ee59aef8b..d3c21a3728 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -150,7 +150,7 @@ class Timesheet(Document): def validate_project(self, data): if self.parent_project and self.parent_project != data.project: - frappe.throw(_("Row {0}: Project must be same as the one set in the Timesheet: {1}.")).format(data.idx, self.parent_project) + frappe.throw(_("Row {0}: Project must be same as the one set in the Timesheet: {1}.").format(data.idx, self.parent_project)) def validate_overlap_for(self, fieldname, args, value, ignore_validation=False): if not value or ignore_validation: @@ -221,14 +221,14 @@ def get_projectwise_timesheet_data(project=None, parent=None, from_time=None, to if from_time and to_time: condition += "AND CAST(tsd.from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s" - return frappe.db.sql("""SELECT tsd.name as name, - tsd.parent as parent, tsd.billing_hours as billing_hours, - tsd.billing_amount as billing_amount, tsd.activity_type as activity_type, + return frappe.db.sql("""SELECT tsd.name as name, + tsd.parent as parent, tsd.billing_hours as billing_hours, + tsd.billing_amount as billing_amount, tsd.activity_type as activity_type, tsd.description as description, ts.currency as currency - FROM `tabTimesheet Detail` tsd - INNER JOIN `tabTimesheet` ts ON ts.name = tsd.parent - WHERE tsd.parenttype = 'Timesheet' - and tsd.docstatus=1 {0} + FROM `tabTimesheet Detail` tsd + INNER JOIN `tabTimesheet` ts ON ts.name = tsd.parent + WHERE tsd.parenttype = 'Timesheet' + and tsd.docstatus=1 {0} and tsd.is_billable = 1 and tsd.sales_invoice is null""".format(condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time}, as_dict=1) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 39004503a0..ce40ced11f 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -734,7 +734,7 @@ frappe.form.link_formatters['Project'] = function(value, doc) { // if value is blank in report view or project name and name are the same, return as is return value; } -} +}; // add description on posting time $(document).on('app_ready', function() { From 8a407f1ec308ef4a4c142746cebf39e18c72ed53 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 20 May 2021 22:23:44 +0530 Subject: [PATCH 11/12] fix: sider issues --- erpnext/projects/doctype/timesheet/timesheet.js | 2 +- erpnext/projects/doctype/timesheet/timesheet.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 63078ea7bd..84c7b8118b 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -147,7 +147,7 @@ frappe.ui.form.on("Timesheet", { frm.set_currency_labels(["total_costing_amount", "total_billable_amount", "total_billed_amount"], frm.doc.currency); frm.toggle_display(["base_total_costing_amount", "base_total_billable_amount", "base_total_billed_amount"], - frm.doc.currency != base_currency) + frm.doc.currency != base_currency); if (frm.doc.time_logs.length > 0) { frm.set_currency_labels(["base_billing_rate", "base_billing_amount", "base_costing_rate", "base_costing_amount"], base_currency, "time_logs"); diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index d3c21a3728..d42c6ab175 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -137,7 +137,7 @@ class Timesheet(Document): def validate_time_logs(self): for data in self.get('time_logs'): self.validate_overlap(data) - self.validate_task_project(data) + self.set_project(data) self.validate_project(data) def validate_overlap(self, data): @@ -145,7 +145,7 @@ class Timesheet(Document): self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap) self.validate_overlap_for("employee", data, self.employee, settings.ignore_employee_time_overlap) - def validate_task_project(self, data): + def set_project(self, data): data.project = data.project or frappe.db.get_value("Task", data.task, "project") def validate_project(self, data): From a7d0dbb085f23f3cedc1b88b546bcc64cb029d56 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 20 May 2021 23:02:11 +0530 Subject: [PATCH 12/12] fix: calculate total billing amount on fetching timesheets - show timesheet billing amounts in doc currency --- .../doctype/sales_invoice/sales_invoice.js | 57 +++++++++---------- .../doctype/sales_invoice/sales_invoice.json | 5 +- .../sales_invoice_timesheet.json | 3 +- .../projects/doctype/timesheet/timesheet.py | 6 +- 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 747d0a931a..1808005f62 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -820,7 +820,7 @@ frappe.ui.form.on('Sales Invoice', { }, add_timesheet_row: function(frm, row, exchange_rate) { - frm.add_child('timesheets',{ + frm.add_child('timesheets', { 'activity_type': row.activity_type, 'description': row.description, 'time_sheet': row.parent, @@ -828,7 +828,8 @@ frappe.ui.form.on('Sales Invoice', { 'billing_amount': flt(row.billing_amount) * flt(exchange_rate), 'timesheet_detail': row.name }); - frm.refresh_field('timesheets') + frm.refresh_field('timesheets'); + calculate_total_billing_amount(frm); }, refresh: function(frm) { @@ -871,36 +872,32 @@ frappe.ui.form.on('Sales Invoice', { project: data.project }, callback: function(r) { - if(!r.exc) { - if(r.message.length > 0) { - frm.clear_table('timesheets') - r.message.forEach((d) => { - let exchange_rate = 1.0; - if (frm.doc.currency != d.currency) { - frappe.call({ - method: "erpnext.setup.utils.get_exchange_rate", - args: { - from_currency: d.currency, - to_currency: frm.doc.currency - }, - callback: function(r) { - if (r.message) { - exchange_rate = r.message; - frm.events.add_timesheet_row(frm, d, exchange_rate); - } + if (!r.exc && r.message.length > 0) { + frm.clear_table('timesheets') + r.message.forEach((d) => { + let exchange_rate = 1.0; + if (frm.doc.currency != d.currency) { + frappe.call({ + method: 'erpnext.setup.utils.get_exchange_rate', + args: { + from_currency: d.currency, + to_currency: frm.doc.currency + }, + callback: function(r) { + if (r.message) { + exchange_rate = r.message; + frm.events.add_timesheet_row(frm, d, exchange_rate); } - }); - } - else { - frm.events.add_timesheet_row(frm, d, exchange_rate); - } - }); - } - else { - frappe.msgprint(__('No Timesheet Found.')) - } - d.hide(); + } + }); + } else { + frm.events.add_timesheet_row(frm, d, exchange_rate); + } + }); + } else { + frappe.msgprint(__('No Timesheets found with the selected filters.')) } + d.hide(); } }); }, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 48d644cb43..e7dd6b8a60 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -772,6 +772,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Total Billing Amount", + "options": "currency", "print_hide": 1, "read_only": 1 }, @@ -1960,7 +1961,7 @@ "label": "Is Debit Note" }, { - "default": 0, + "default": "0", "depends_on": "grand_total", "fieldname": "disable_rounded_total", "fieldtype": "Check", @@ -1977,7 +1978,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-05-13 17:53:26.185370", + "modified": "2021-05-20 22:48:33.988881", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", 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 9321630829..f069e8dd0b 100644 --- a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json +++ b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json @@ -34,6 +34,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Billing Amount", + "options": "currency", "read_only": 1 }, { @@ -64,7 +65,7 @@ ], "istable": 1, "links": [], - "modified": "2021-05-13 16:52:32.995266", + "modified": "2021-05-20 22:33:57.234846", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Timesheet", diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index d42c6ab175..a3e4577f90 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -342,9 +342,9 @@ def get_activity_cost(employee=None, activity_type=None, currency=None): rate = frappe.db.get_values("Activity Type", {"activity_type": activity_type}, ["costing_rate", "billing_rate"], as_dict=True) if rate and currency and currency!=base_currency: - exchnage_rate = get_exchange_rate(base_currency, currency) - rate[0]["costing_rate"] = rate[0]["costing_rate"] * exchnage_rate - rate[0]["billing_rate"] = rate[0]["billing_rate"] * exchnage_rate + exchange_rate = get_exchange_rate(base_currency, currency) + rate[0]["costing_rate"] = rate[0]["costing_rate"] * exchange_rate + rate[0]["billing_rate"] = rate[0]["billing_rate"] * exchange_rate return rate[0] if rate else {}