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",