Merge pull request #25701 from anupamvs/timesheet-refactor
refactor: timesheet
This commit is contained in:
commit
722cfdb7e5
@ -17,7 +17,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
|||||||
var me = this;
|
var me = this;
|
||||||
this._super();
|
this._super();
|
||||||
|
|
||||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice'];
|
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet'];
|
||||||
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||||
// show debit_to in print format
|
// show debit_to in print format
|
||||||
this.frm.set_df_property("debit_to", "print_hide", 0);
|
this.frm.set_df_property("debit_to", "print_hide", 0);
|
||||||
@ -695,6 +695,7 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
project: function(frm){
|
project: function(frm){
|
||||||
|
if (!frm.doc.is_return) {
|
||||||
frm.call({
|
frm.call({
|
||||||
method: "add_timesheet_data",
|
method: "add_timesheet_data",
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
@ -703,6 +704,7 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
frm.refresh();
|
frm.refresh();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
@ -817,14 +819,27 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
add_timesheet_row: function(frm, row, exchange_rate) {
|
||||||
|
frm.add_child('timesheets', {
|
||||||
|
'activity_type': row.activity_type,
|
||||||
|
'description': row.description,
|
||||||
|
'time_sheet': row.parent,
|
||||||
|
'billing_hours': row.billing_hours,
|
||||||
|
'billing_amount': flt(row.billing_amount) * flt(exchange_rate),
|
||||||
|
'timesheet_detail': row.name
|
||||||
|
});
|
||||||
|
frm.refresh_field('timesheets');
|
||||||
|
calculate_total_billing_amount(frm);
|
||||||
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
if (frm.doc.project) {
|
if (frm.doc.docstatus===0 && !frm.doc.is_return) {
|
||||||
frm.add_custom_button(__('Fetch Timesheet'), function() {
|
frm.add_custom_button(__('Fetch Timesheet'), function() {
|
||||||
let d = new frappe.ui.Dialog({
|
let d = new frappe.ui.Dialog({
|
||||||
title: __('Fetch Timesheet'),
|
title: __('Fetch Timesheet'),
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
"label" : "From",
|
"label" : __("From"),
|
||||||
"fieldname": "from_time",
|
"fieldname": "from_time",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
@ -834,11 +849,18 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
fieldname: 'col_break_1',
|
fieldname: 'col_break_1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label" : "To",
|
"label" : __("To"),
|
||||||
"fieldname": "to_time",
|
"fieldname": "to_time",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"label" : __("Project"),
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Project",
|
||||||
|
"default": frm.doc.project
|
||||||
|
},
|
||||||
],
|
],
|
||||||
primary_action: function() {
|
primary_action: function() {
|
||||||
let data = d.get_values();
|
let data = d.get_values();
|
||||||
@ -847,28 +869,36 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
args: {
|
args: {
|
||||||
from_time: data.from_time,
|
from_time: data.from_time,
|
||||||
to_time: data.to_time,
|
to_time: data.to_time,
|
||||||
project: frm.doc.project
|
project: data.project
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(!r.exc) {
|
if (!r.exc && r.message.length > 0) {
|
||||||
if(r.message.length > 0) {
|
|
||||||
frm.clear_table('timesheets')
|
frm.clear_table('timesheets')
|
||||||
r.message.forEach((d) => {
|
r.message.forEach((d) => {
|
||||||
frm.add_child('timesheets',{
|
let exchange_rate = 1.0;
|
||||||
'time_sheet': d.parent,
|
if (frm.doc.currency != d.currency) {
|
||||||
'billing_hours': d.billing_hours,
|
frappe.call({
|
||||||
'billing_amount': d.billing_amt,
|
method: 'erpnext.setup.utils.get_exchange_rate',
|
||||||
'timesheet_detail': d.name
|
args: {
|
||||||
});
|
from_currency: d.currency,
|
||||||
});
|
to_currency: frm.doc.currency
|
||||||
frm.refresh_field('timesheets')
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
exchange_rate = r.message;
|
||||||
|
frm.events.add_timesheet_row(frm, d, exchange_rate);
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
frappe.msgprint(__('No Timesheet Found.'))
|
});
|
||||||
|
} else {
|
||||||
|
frm.events.add_timesheet_row(frm, d, exchange_rate);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
frappe.msgprint(__('No Timesheets found with the selected filters.'))
|
||||||
}
|
}
|
||||||
d.hide();
|
d.hide();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
primary_action_label: __('Get Timesheets')
|
primary_action_label: __('Get Timesheets')
|
||||||
|
@ -749,6 +749,7 @@
|
|||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"collapsible_depends_on": "eval:doc.total_billing_amount > 0",
|
"collapsible_depends_on": "eval:doc.total_billing_amount > 0",
|
||||||
|
"depends_on": "eval: !doc.is_return",
|
||||||
"fieldname": "time_sheet_list",
|
"fieldname": "time_sheet_list",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
@ -771,6 +772,7 @@
|
|||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "Total Billing Amount",
|
"label": "Total Billing Amount",
|
||||||
|
"options": "currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -1959,7 +1961,7 @@
|
|||||||
"label": "Is Debit Note"
|
"label": "Is Debit Note"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": 0,
|
"default": "0",
|
||||||
"depends_on": "grand_total",
|
"depends_on": "grand_total",
|
||||||
"fieldname": "disable_rounded_total",
|
"fieldname": "disable_rounded_total",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
@ -1976,7 +1978,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2021-04-23 22:36:32.916354",
|
"modified": "2021-05-20 22:48:33.988881",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
@ -125,6 +125,8 @@ class SalesInvoice(SellingController):
|
|||||||
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
|
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
|
||||||
if not self.is_return:
|
if not self.is_return:
|
||||||
self.validate_serial_numbers()
|
self.validate_serial_numbers()
|
||||||
|
else:
|
||||||
|
self.timesheets = []
|
||||||
self.update_packing_list()
|
self.update_packing_list()
|
||||||
self.set_billing_hours_and_amount()
|
self.set_billing_hours_and_amount()
|
||||||
self.update_timesheet_billing_for_project()
|
self.update_timesheet_billing_for_project()
|
||||||
@ -337,7 +339,7 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
if "Healthcare" in active_domains:
|
if "Healthcare" in active_domains:
|
||||||
manage_invoice_submit_cancel(self, "on_cancel")
|
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')
|
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
|
||||||
|
|
||||||
def update_status_updater_args(self):
|
def update_status_updater_args(self):
|
||||||
@ -393,6 +395,18 @@ class SalesInvoice(SellingController):
|
|||||||
if validate_against_credit_limit:
|
if validate_against_credit_limit:
|
||||||
check_credit_limit(self.customer, self.company, bypass_credit_limit_check_at_sales_order)
|
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()
|
@frappe.whitelist()
|
||||||
def set_missing_values(self, for_validate=False):
|
def set_missing_values(self, for_validate=False):
|
||||||
pos = self.set_pos_fields(for_validate)
|
pos = self.set_pos_fields(for_validate)
|
||||||
@ -427,7 +441,7 @@ class SalesInvoice(SellingController):
|
|||||||
timesheet.calculate_percentage_billed()
|
timesheet.calculate_percentage_billed()
|
||||||
timesheet.flags.ignore_validate_update_after_submit = True
|
timesheet.flags.ignore_validate_update_after_submit = True
|
||||||
timesheet.set_status()
|
timesheet.set_status()
|
||||||
timesheet.save()
|
timesheet.db_update_all()
|
||||||
|
|
||||||
def update_time_sheet_detail(self, timesheet, args, sales_invoice):
|
def update_time_sheet_detail(self, timesheet, args, sales_invoice):
|
||||||
for data in timesheet.time_logs:
|
for data in timesheet.time_logs:
|
||||||
@ -741,8 +755,10 @@ class SalesInvoice(SellingController):
|
|||||||
self.append('timesheets', {
|
self.append('timesheets', {
|
||||||
'time_sheet': data.parent,
|
'time_sheet': data.parent,
|
||||||
'billing_hours': data.billing_hours,
|
'billing_hours': data.billing_hours,
|
||||||
'billing_amount': data.billing_amt,
|
'billing_amount': data.billing_amount,
|
||||||
'timesheet_detail': data.name
|
'timesheet_detail': data.name,
|
||||||
|
'activity_type': data.activity_type,
|
||||||
|
'description': data.description
|
||||||
})
|
})
|
||||||
|
|
||||||
self.calculate_billing_amount_for_timesheet()
|
self.calculate_billing_amount_for_timesheet()
|
||||||
|
@ -1,172 +1,78 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"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",
|
"creation": "2016-06-14 19:21:34.321662",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"activity_type",
|
||||||
|
"description",
|
||||||
|
"billing_hours",
|
||||||
|
"billing_amount",
|
||||||
|
"time_sheet",
|
||||||
|
"timesheet_detail"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "time_sheet",
|
"fieldname": "time_sheet",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Time Sheet",
|
"label": "Time Sheet",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Timesheet",
|
"options": "Timesheet",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "billing_hours",
|
"fieldname": "billing_hours",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Billing Hours",
|
"label": "Billing Hours",
|
||||||
"length": 0,
|
"read_only": 1
|
||||||
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "billing_amount",
|
"fieldname": "billing_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Billing Amount",
|
"label": "Billing Amount",
|
||||||
"length": 0,
|
"options": "currency",
|
||||||
"no_copy": 0,
|
"read_only": 1
|
||||||
"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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "timesheet_detail",
|
"fieldname": "timesheet_detail",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"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",
|
"label": "Timesheet Detail",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
"read_only": 1
|
||||||
"read_only": 1,
|
},
|
||||||
"remember_last_selected_value": 0,
|
{
|
||||||
"report_hide": 0,
|
"fieldname": "activity_type",
|
||||||
"reqd": 0,
|
"fieldtype": "Link",
|
||||||
"search_index": 0,
|
"in_list_view": 1,
|
||||||
"set_only_once": 0,
|
"label": "Activity Type",
|
||||||
"translatable": 0,
|
"options": "Activity Type",
|
||||||
"unique": 0
|
"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,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"links": [],
|
||||||
"modified": "2019-02-18 18:50:44.770361",
|
"modified": "2021-05-20 22:33:57.234846",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Timesheet",
|
"name": "Sales Invoice Timesheet",
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"track_changes": 1
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
@ -779,3 +779,4 @@ erpnext.patches.v12_0.add_ewaybill_validity_field
|
|||||||
erpnext.patches.v13_0.germany_make_custom_fields
|
erpnext.patches.v13_0.germany_make_custom_fields
|
||||||
erpnext.patches.v13_0.germany_fill_debtor_creditor_number
|
erpnext.patches.v13_0.germany_fill_debtor_creditor_number
|
||||||
erpnext.patches.v13_0.set_pos_closing_as_failed
|
erpnext.patches.v13_0.set_pos_closing_as_failed
|
||||||
|
erpnext.patches.v13_0.update_timesheet_changes
|
||||||
|
25
erpnext/patches/v13_0/update_timesheet_changes.py
Normal file
25
erpnext/patches/v13_0/update_timesheet_changes.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.model.utils.rename_field import rename_field
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
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,
|
||||||
|
base_total_billable_amount = total_billable_amount,
|
||||||
|
base_total_billed_amount = total_billed_amount,
|
||||||
|
base_total_costing_amount = total_costing_amount""".format(base_currency))
|
@ -51,7 +51,7 @@ def execute():
|
|||||||
|
|
||||||
def get_timelog_data(data):
|
def get_timelog_data(data):
|
||||||
return {
|
return {
|
||||||
'billable': data.billable,
|
'is_billable': data.billable,
|
||||||
'from_time': data.from_time,
|
'from_time': data.from_time,
|
||||||
'hours': data.hours,
|
'hours': data.hours,
|
||||||
'to_time': data.to_time,
|
'to_time': data.to_time,
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
frappe.ui.form.on("Activity Type", {
|
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) {
|
refresh: function(frm) {
|
||||||
frm.add_custom_button(__("Activity Cost per Employee"), function() {
|
frm.add_custom_button(__("Activity Cost per Employee"), function() {
|
||||||
frappe.route_options = {"activity_type": frm.doc.name};
|
frappe.route_options = {"activity_type": frm.doc.name};
|
||||||
|
@ -37,7 +37,7 @@ class TestTimesheet(unittest.TestCase):
|
|||||||
emp = make_employee("test_employee_6@salary.com")
|
emp = make_employee("test_employee_6@salary.com")
|
||||||
|
|
||||||
make_salary_structure_for_timesheet(emp)
|
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_hours, 2)
|
||||||
self.assertEqual(timesheet.total_billable_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")
|
emp = make_employee("test_employee_6@salary.com")
|
||||||
|
|
||||||
make_salary_structure_for_timesheet(emp)
|
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_hours, 2)
|
||||||
self.assertEqual(timesheet.total_billable_hours, 0)
|
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")
|
emp = make_employee("test_employee_6@salary.com", company="_Test Company")
|
||||||
|
|
||||||
salary_structure = make_salary_structure_for_timesheet(emp)
|
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 = make_salary_slip(timesheet.name)
|
||||||
salary_slip.submit()
|
salary_slip.submit()
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ class TestTimesheet(unittest.TestCase):
|
|||||||
def test_sales_invoice_from_timesheet(self):
|
def test_sales_invoice_from_timesheet(self):
|
||||||
emp = make_employee("test_employee_6@salary.com")
|
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 = make_sales_invoice(timesheet.name, '_Test Item', '_Test Customer')
|
||||||
sales_invoice.due_date = nowdate()
|
sales_invoice.due_date = nowdate()
|
||||||
sales_invoice.submit()
|
sales_invoice.submit()
|
||||||
@ -100,7 +100,7 @@ class TestTimesheet(unittest.TestCase):
|
|||||||
emp = make_employee("test_employee_6@salary.com")
|
emp = make_employee("test_employee_6@salary.com")
|
||||||
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
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 = create_sales_invoice(do_not_save=True)
|
||||||
sales_invoice.project = project
|
sales_invoice.project = project
|
||||||
sales_invoice.submit()
|
sales_invoice.submit()
|
||||||
@ -171,13 +171,13 @@ def make_salary_structure_for_timesheet(employee, company=None):
|
|||||||
return salary_structure
|
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)
|
update_activity_type(activity_type)
|
||||||
timesheet = frappe.new_doc("Timesheet")
|
timesheet = frappe.new_doc("Timesheet")
|
||||||
timesheet.employee = employee
|
timesheet.employee = employee
|
||||||
timesheet.company = company or '_Test Company'
|
timesheet.company = company or '_Test Company'
|
||||||
timesheet_detail = timesheet.append('time_logs', {})
|
timesheet_detail = timesheet.append('time_logs', {})
|
||||||
timesheet_detail.billable = billable
|
timesheet_detail.is_billable = is_billable
|
||||||
timesheet_detail.activity_type = activity_type
|
timesheet_detail.activity_type = activity_type
|
||||||
timesheet_detail.from_time = now_datetime()
|
timesheet_detail.from_time = now_datetime()
|
||||||
timesheet_detail.hours = 2
|
timesheet_detail.hours = 2
|
||||||
|
@ -90,17 +90,99 @@ frappe.ui.form.on("Timesheet", {
|
|||||||
}
|
}
|
||||||
if(frm.doc.per_billed > 0) {
|
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("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');
|
||||||
|
frm.trigger('set_dynamic_field_label');
|
||||||
|
},
|
||||||
|
|
||||||
|
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();
|
||||||
|
},
|
||||||
|
|
||||||
|
currency: function(frm) {
|
||||||
|
let base_currency = frappe.defaults.get_global_default('currency');
|
||||||
|
if (base_currency != frm.doc.currency) {
|
||||||
|
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.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();
|
||||||
},
|
},
|
||||||
|
|
||||||
make_invoice: function(frm) {
|
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({
|
let dialog = new frappe.ui.Dialog({
|
||||||
title: __("Select Item (optional)"),
|
title: __("Create Sales Invoice"),
|
||||||
fields: [
|
fields: fields
|
||||||
{"fieldtype": "Link", "label": __("Item Code"), "fieldname": "item_code", "options":"Item"},
|
|
||||||
{"fieldtype": "Link", "label": __("Customer"), "fieldname": "customer", "options":"Customer"}
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
dialog.set_primary_action(__('Create Sales Invoice'), () => {
|
dialog.set_primary_action(__('Create Sales Invoice'), () => {
|
||||||
@ -113,7 +195,8 @@ frappe.ui.form.on("Timesheet", {
|
|||||||
args: {
|
args: {
|
||||||
"source_name": frm.doc.name,
|
"source_name": frm.doc.name,
|
||||||
"item_code": args.item_code,
|
"item_code": args.item_code,
|
||||||
"customer": args.customer
|
"customer": frm.doc.customer || args.customer,
|
||||||
|
"currency": frm.doc.currency
|
||||||
},
|
},
|
||||||
freeze: true,
|
freeze: true,
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
@ -136,8 +219,7 @@ frappe.ui.form.on("Timesheet", {
|
|||||||
|
|
||||||
parent_project: function(frm) {
|
parent_project: function(frm) {
|
||||||
set_project_in_timelog(frm);
|
set_project_in_timelog(frm);
|
||||||
},
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on("Timesheet Detail", {
|
frappe.ui.form.on("Timesheet Detail", {
|
||||||
@ -171,35 +253,34 @@ frappe.ui.form.on("Timesheet Detail", {
|
|||||||
if(frm.doc.parent_project) {
|
if(frm.doc.parent_project) {
|
||||||
frappe.model.set_value(cdt, cdn, 'project', 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) {
|
hours: function(frm, cdt, cdn) {
|
||||||
calculate_end_time(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) {
|
billing_hours: function(frm, cdt, cdn) {
|
||||||
calculate_billing_costing_amount(frm, cdt, cdn);
|
calculate_billing_costing_amount(frm, cdt, cdn);
|
||||||
|
calculate_time_and_amount(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
billing_rate: function(frm, cdt, cdn) {
|
billing_rate: function(frm, cdt, cdn) {
|
||||||
calculate_billing_costing_amount(frm, cdt, cdn);
|
calculate_billing_costing_amount(frm, cdt, cdn);
|
||||||
|
calculate_time_and_amount(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
costing_rate: function(frm, cdt, cdn) {
|
costing_rate: function(frm, cdt, cdn) {
|
||||||
calculate_billing_costing_amount(frm, cdt, cdn);
|
calculate_billing_costing_amount(frm, cdt, cdn);
|
||||||
|
calculate_time_and_amount(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
billable: function(frm, cdt, cdn) {
|
is_billable: function(frm, cdt, cdn) {
|
||||||
update_billing_hours(frm, cdt, cdn);
|
update_billing_hours(frm, cdt, cdn);
|
||||||
update_time_rates(frm, cdt, cdn);
|
update_time_rates(frm, cdt, cdn);
|
||||||
calculate_billing_costing_amount(frm, cdt, cdn);
|
calculate_billing_costing_amount(frm, cdt, cdn);
|
||||||
|
calculate_time_and_amount(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
activity_type: function(frm, cdt, cdn) {
|
activity_type: function(frm, cdt, cdn) {
|
||||||
@ -207,7 +288,8 @@ frappe.ui.form.on("Timesheet Detail", {
|
|||||||
method: "erpnext.projects.doctype.timesheet.timesheet.get_activity_cost",
|
method: "erpnext.projects.doctype.timesheet.timesheet.get_activity_cost",
|
||||||
args: {
|
args: {
|
||||||
employee: frm.doc.employee,
|
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){
|
callback: function(r){
|
||||||
if(r.message){
|
if(r.message){
|
||||||
@ -240,8 +322,8 @@ var calculate_end_time = function(frm, cdt, cdn) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var update_billing_hours = function(frm, cdt, cdn) {
|
var update_billing_hours = function(frm, cdt, cdn) {
|
||||||
var child = locals[cdt][cdn];
|
let child = frappe.get_doc(cdt, cdn);
|
||||||
if(!child.billable) {
|
if (!child.is_billable) {
|
||||||
frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0);
|
frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0);
|
||||||
} else {
|
} else {
|
||||||
// bill all hours by default
|
// bill all hours by default
|
||||||
@ -250,39 +332,43 @@ var update_billing_hours = function(frm, cdt, cdn){
|
|||||||
};
|
};
|
||||||
|
|
||||||
var update_time_rates = function(frm, cdt, cdn) {
|
var update_time_rates = function(frm, cdt, cdn) {
|
||||||
var child = locals[cdt][cdn];
|
let child = frappe.get_doc(cdt, cdn);
|
||||||
if(!child.billable){
|
if (!child.is_billable) {
|
||||||
frappe.model.set_value(cdt, cdn, 'billing_rate', 0.0);
|
frappe.model.set_value(cdt, cdn, 'billing_rate', 0.0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var calculate_billing_costing_amount = function(frm, cdt, cdn) {
|
var calculate_billing_costing_amount = function(frm, cdt, cdn) {
|
||||||
var child = locals[cdt][cdn];
|
let row = frappe.get_doc(cdt, cdn);
|
||||||
var billing_amount = 0.0;
|
let billing_amount = 0.0;
|
||||||
var costing_amount = 0.0;
|
let base_billing_amount = 0.0;
|
||||||
|
let exchange_rate = flt(frm.doc.exchange_rate);
|
||||||
if(child.billing_hours && child.billable){
|
frappe.model.set_value(cdt, cdn, 'base_billing_rate', flt(row.billing_rate) * exchange_rate);
|
||||||
billing_amount = (child.billing_hours * child.billing_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, 'billing_amount', billing_amount);
|
||||||
frappe.model.set_value(cdt, cdn, 'costing_amount', costing_amount);
|
frappe.model.set_value(cdt, cdn, 'costing_amount', flt(row.costing_rate) * flt(row.hours));
|
||||||
calculate_time_and_amount(frm);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var calculate_time_and_amount = function(frm) {
|
var calculate_time_and_amount = function(frm) {
|
||||||
var tl = frm.doc.time_logs || [];
|
let tl = frm.doc.time_logs || [];
|
||||||
var total_working_hr = 0;
|
let total_working_hr = 0;
|
||||||
var total_billing_hr = 0;
|
let total_billing_hr = 0;
|
||||||
var total_billable_amount = 0;
|
let total_billable_amount = 0;
|
||||||
var total_costing_amount = 0;
|
let total_costing_amount = 0;
|
||||||
for(var i=0; i<tl.length; i++) {
|
for(var i=0; i<tl.length; i++) {
|
||||||
if (tl[i].hours) {
|
if (tl[i].hours) {
|
||||||
total_working_hr += tl[i].hours;
|
total_working_hr += tl[i].hours;
|
||||||
total_billable_amount += tl[i].billing_amount;
|
total_billable_amount += tl[i].billing_amount;
|
||||||
total_costing_amount += tl[i].costing_amount;
|
total_costing_amount += tl[i].costing_amount;
|
||||||
|
|
||||||
if(tl[i].billable){
|
if (tl[i].is_billable) {
|
||||||
total_billing_hr += tl[i].billing_hours;
|
total_billing_hr += tl[i].billing_hours;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,9 @@
|
|||||||
"title",
|
"title",
|
||||||
"naming_series",
|
"naming_series",
|
||||||
"company",
|
"company",
|
||||||
|
"customer",
|
||||||
|
"currency",
|
||||||
|
"exchange_rate",
|
||||||
"sales_invoice",
|
"sales_invoice",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"salary_slip",
|
"salary_slip",
|
||||||
@ -30,11 +33,14 @@
|
|||||||
"total_hours",
|
"total_hours",
|
||||||
"billing_details",
|
"billing_details",
|
||||||
"total_billable_hours",
|
"total_billable_hours",
|
||||||
"total_billed_hours",
|
"base_total_billable_amount",
|
||||||
"total_costing_amount",
|
"base_total_billed_amount",
|
||||||
|
"base_total_costing_amount",
|
||||||
"column_break_10",
|
"column_break_10",
|
||||||
|
"total_billed_hours",
|
||||||
"total_billable_amount",
|
"total_billable_amount",
|
||||||
"total_billed_amount",
|
"total_billed_amount",
|
||||||
|
"total_costing_amount",
|
||||||
"per_billed",
|
"per_billed",
|
||||||
"section_break_18",
|
"section_break_18",
|
||||||
"note",
|
"note",
|
||||||
@ -176,7 +182,6 @@
|
|||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "total_hours",
|
"fieldname": "total_hours",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Total Working Hours",
|
"label": "Total Working Hours",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -199,7 +204,6 @@
|
|||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"fieldname": "total_billed_hours",
|
"fieldname": "total_billed_hours",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Total Billed Hours",
|
"label": "Total Billed Hours",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
@ -209,6 +213,7 @@
|
|||||||
"fieldname": "total_costing_amount",
|
"fieldname": "total_costing_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Total Costing Amount",
|
"label": "Total Costing Amount",
|
||||||
|
"options": "currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -222,6 +227,7 @@
|
|||||||
"fieldname": "total_billable_amount",
|
"fieldname": "total_billable_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Total Billable Amount",
|
"label": "Total Billable Amount",
|
||||||
|
"options": "currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -229,6 +235,7 @@
|
|||||||
"fieldname": "total_billed_amount",
|
"fieldname": "total_billed_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Total Billed Amount",
|
"label": "Total Billed Amount",
|
||||||
|
"options": "currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -236,6 +243,7 @@
|
|||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"fieldname": "per_billed",
|
"fieldname": "per_billed",
|
||||||
"fieldtype": "Percent",
|
"fieldtype": "Percent",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "% Amount Billed",
|
"label": "% Amount Billed",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
@ -265,13 +273,53 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Project",
|
"label": "Project",
|
||||||
"options": "Project"
|
"options": "Project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Customer",
|
||||||
|
"options": "Customer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "customer.default_currency",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
|
"fieldname": "currency",
|
||||||
|
"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",
|
"icon": "fa fa-clock-o",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-01-08 20:51:14.590080",
|
"modified": "2021-05-18 16:10:08.249619",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Timesheet",
|
"name": "Timesheet",
|
||||||
|
@ -14,6 +14,7 @@ from frappe.model.document import Document
|
|||||||
from erpnext.manufacturing.doctype.workstation.workstation import (check_if_within_operating_hours,
|
from erpnext.manufacturing.doctype.workstation.workstation import (check_if_within_operating_hours,
|
||||||
WorkstationHolidayError)
|
WorkstationHolidayError)
|
||||||
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
|
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 OverlapError(frappe.ValidationError): pass
|
||||||
class OverWorkLoggedError(frappe.ValidationError): pass
|
class OverWorkLoggedError(frappe.ValidationError): pass
|
||||||
@ -37,9 +38,9 @@ class Timesheet(Document):
|
|||||||
self.total_hours = 0.0
|
self.total_hours = 0.0
|
||||||
self.total_billable_hours = 0.0
|
self.total_billable_hours = 0.0
|
||||||
self.total_billed_hours = 0.0
|
self.total_billed_hours = 0.0
|
||||||
self.total_billable_amount = 0.0
|
self.total_billable_amount = self.base_total_billable_amount = 0.0
|
||||||
self.total_costing_amount = 0.0
|
self.total_costing_amount = self.base_total_costing_amount = 0.0
|
||||||
self.total_billed_amount = 0.0
|
self.total_billed_amount = self.base_total_billed_amount = 0.0
|
||||||
|
|
||||||
for d in self.get("time_logs"):
|
for d in self.get("time_logs"):
|
||||||
self.update_billing_hours(d)
|
self.update_billing_hours(d)
|
||||||
@ -47,10 +48,13 @@ class Timesheet(Document):
|
|||||||
|
|
||||||
self.total_hours += flt(d.hours)
|
self.total_hours += flt(d.hours)
|
||||||
self.total_costing_amount += flt(d.costing_amount)
|
self.total_costing_amount += flt(d.costing_amount)
|
||||||
if d.billable:
|
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_hours += flt(d.billing_hours)
|
||||||
self.total_billable_amount += flt(d.billing_amount)
|
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.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
|
self.total_billed_hours += flt(d.billing_hours) if d.sales_invoice else 0.0
|
||||||
|
|
||||||
def calculate_percentage_billed(self):
|
def calculate_percentage_billed(self):
|
||||||
@ -59,7 +63,7 @@ class Timesheet(Document):
|
|||||||
self.per_billed = (self.total_billed_amount * 100) / self.total_billable_amount
|
self.per_billed = (self.total_billed_amount * 100) / self.total_billable_amount
|
||||||
|
|
||||||
def update_billing_hours(self, args):
|
def update_billing_hours(self, args):
|
||||||
if args.billable:
|
if args.is_billable:
|
||||||
if flt(args.billing_hours) == 0.0:
|
if flt(args.billing_hours) == 0.0:
|
||||||
args.billing_hours = args.hours
|
args.billing_hours = args.hours
|
||||||
else:
|
else:
|
||||||
@ -133,16 +137,20 @@ class Timesheet(Document):
|
|||||||
def validate_time_logs(self):
|
def validate_time_logs(self):
|
||||||
for data in self.get('time_logs'):
|
for data in self.get('time_logs'):
|
||||||
self.validate_overlap(data)
|
self.validate_overlap(data)
|
||||||
self.validate_task_project()
|
self.set_project(data)
|
||||||
|
self.validate_project(data)
|
||||||
|
|
||||||
def validate_overlap(self, data):
|
def validate_overlap(self, data):
|
||||||
settings = frappe.get_single('Projects Settings')
|
settings = frappe.get_single('Projects Settings')
|
||||||
self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap)
|
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)
|
self.validate_overlap_for("employee", data, self.employee, settings.ignore_employee_time_overlap)
|
||||||
|
|
||||||
def validate_task_project(self):
|
def set_project(self, data):
|
||||||
for log in self.time_logs:
|
data.project = data.project or frappe.db.get_value("Task", data.task, "project")
|
||||||
log.project = log.project or frappe.db.get_value("Task", log.task, "project")
|
|
||||||
|
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))
|
||||||
|
|
||||||
def validate_overlap_for(self, fieldname, args, value, ignore_validation=False):
|
def validate_overlap_for(self, fieldname, args, value, ignore_validation=False):
|
||||||
if not value or ignore_validation:
|
if not value or ignore_validation:
|
||||||
@ -189,7 +197,7 @@ class Timesheet(Document):
|
|||||||
|
|
||||||
def update_cost(self):
|
def update_cost(self):
|
||||||
for data in self.time_logs:
|
for data in self.time_logs:
|
||||||
if data.activity_type or data.billable:
|
if data.activity_type or data.is_billable:
|
||||||
rate = get_activity_cost(self.employee, data.activity_type)
|
rate = get_activity_cost(self.employee, data.activity_type)
|
||||||
hours = data.billing_hours or 0
|
hours = data.billing_hours or 0
|
||||||
costing_hours = data.billing_hours or data.hours or 0
|
costing_hours = data.billing_hours or data.hours or 0
|
||||||
@ -200,20 +208,29 @@ class Timesheet(Document):
|
|||||||
data.costing_amount = data.costing_rate * costing_hours
|
data.costing_amount = data.costing_rate * costing_hours
|
||||||
|
|
||||||
def update_time_rates(self, ts_detail):
|
def update_time_rates(self, ts_detail):
|
||||||
if not ts_detail.billable:
|
if not ts_detail.is_billable:
|
||||||
ts_detail.billing_rate = 0.0
|
ts_detail.billing_rate = 0.0
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_projectwise_timesheet_data(project, parent=None, from_time=None, to_time=None):
|
def get_projectwise_timesheet_data(project=None, parent=None, from_time=None, to_time=None):
|
||||||
condition = ''
|
condition = ''
|
||||||
|
if project:
|
||||||
|
condition += "and tsd.project = %(project)s"
|
||||||
if parent:
|
if parent:
|
||||||
condition = "AND parent = %(parent)s"
|
condition += "AND tsd.parent = %(parent)s"
|
||||||
if from_time and to_time:
|
if from_time and to_time:
|
||||||
condition += "AND CAST(from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s"
|
condition += "AND CAST(tsd.from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s"
|
||||||
|
|
||||||
return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt
|
return frappe.db.sql("""SELECT tsd.name as name,
|
||||||
from `tabTimesheet Detail` where parenttype = 'Timesheet' and docstatus=1 and project = %(project)s {0} and billable = 1
|
tsd.parent as parent, tsd.billing_hours as billing_hours,
|
||||||
and sales_invoice is null""".format(condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time}, as_dict=1)
|
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}
|
||||||
|
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)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
@ -250,7 +267,7 @@ def get_timesheet_data(name, project):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_sales_invoice(source_name, item_code=None, customer=None):
|
def make_sales_invoice(source_name, item_code=None, customer=None, currency=None):
|
||||||
target = frappe.new_doc("Sales Invoice")
|
target = frappe.new_doc("Sales Invoice")
|
||||||
timesheet = frappe.get_doc('Timesheet', source_name)
|
timesheet = frappe.get_doc('Timesheet', source_name)
|
||||||
|
|
||||||
@ -268,6 +285,9 @@ def make_sales_invoice(source_name, item_code=None, customer=None):
|
|||||||
if customer:
|
if customer:
|
||||||
target.customer = customer
|
target.customer = customer
|
||||||
|
|
||||||
|
if currency:
|
||||||
|
target.currency = currency
|
||||||
|
|
||||||
if item_code:
|
if item_code:
|
||||||
target.append('items', {
|
target.append('items', {
|
||||||
'item_code': item_code,
|
'item_code': item_code,
|
||||||
@ -275,10 +295,15 @@ def make_sales_invoice(source_name, item_code=None, customer=None):
|
|||||||
'rate': billing_rate
|
'rate': billing_rate
|
||||||
})
|
})
|
||||||
|
|
||||||
|
for time_log in timesheet.time_logs:
|
||||||
|
if time_log.is_billable:
|
||||||
target.append('timesheets', {
|
target.append('timesheets', {
|
||||||
'time_sheet': timesheet.name,
|
'time_sheet': timesheet.name,
|
||||||
'billing_hours': hours,
|
'billing_hours': time_log.billing_hours,
|
||||||
'billing_amount': billing_amount
|
'billing_amount': time_log.billing_amount,
|
||||||
|
'timesheet_detail': time_log.name,
|
||||||
|
'activity_type': time_log.activity_type,
|
||||||
|
'description': time_log.description
|
||||||
})
|
})
|
||||||
|
|
||||||
target.run_method("calculate_billing_amount_for_timesheet")
|
target.run_method("calculate_billing_amount_for_timesheet")
|
||||||
@ -309,12 +334,17 @@ def set_missing_values(time_sheet, target):
|
|||||||
})
|
})
|
||||||
|
|
||||||
@frappe.whitelist()
|
@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,
|
rate = frappe.db.get_values("Activity Cost", {"employee": employee,
|
||||||
"activity_type": activity_type}, ["costing_rate", "billing_rate"], as_dict=True)
|
"activity_type": activity_type}, ["costing_rate", "billing_rate"], as_dict=True)
|
||||||
if not rate:
|
if not rate:
|
||||||
rate = frappe.db.get_values("Activity Type", {"activity_type": activity_type},
|
rate = frappe.db.get_values("Activity Type", {"activity_type": activity_type},
|
||||||
["costing_rate", "billing_rate"], as_dict=True)
|
["costing_rate", "billing_rate"], as_dict=True)
|
||||||
|
if rate and currency and currency!=base_currency:
|
||||||
|
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 {}
|
return rate[0] if rate else {}
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -126,7 +126,7 @@ def get_timesheet_details(filters, timesheet_list):
|
|||||||
timesheet_details = frappe.get_all(
|
timesheet_details = frappe.get_all(
|
||||||
"Timesheet Detail",
|
"Timesheet Detail",
|
||||||
filters = timesheet_details_filter,
|
filters = timesheet_details_filter,
|
||||||
fields=["from_time", "to_time", "hours", "billable", "billing_hours", "billing_rate", "parent"]
|
fields=["from_time", "to_time", "hours", "is_billable", "billing_hours", "billing_rate", "parent"]
|
||||||
)
|
)
|
||||||
|
|
||||||
timesheet_details_map = frappe._dict()
|
timesheet_details_map = frappe._dict()
|
||||||
@ -139,7 +139,7 @@ def get_billable_and_total_duration(activity, start_time, end_time):
|
|||||||
precision = frappe.get_precision("Timesheet Detail", "hours")
|
precision = frappe.get_precision("Timesheet Detail", "hours")
|
||||||
activity_duration = time_diff_in_hours(end_time, start_time)
|
activity_duration = time_diff_in_hours(end_time, start_time)
|
||||||
billing_duration = 0.0
|
billing_duration = 0.0
|
||||||
if activity.billable:
|
if activity.is_billable:
|
||||||
billing_duration = activity.billing_hours
|
billing_duration = activity.billing_hours
|
||||||
if activity_duration != activity.billing_hours:
|
if activity_duration != activity.billing_hours:
|
||||||
billing_duration = activity_duration * activity.billing_hours / activity.hours
|
billing_duration = activity_duration * activity.billing_hours / activity.hours
|
||||||
|
@ -140,7 +140,7 @@ class EmployeeHoursReport:
|
|||||||
additional_filters += f"AND tt.{field} = '{self.filters.get(field)}'"
|
additional_filters += f"AND tt.{field} = '{self.filters.get(field)}'"
|
||||||
|
|
||||||
self.filtered_time_logs = frappe.db.sql('''
|
self.filtered_time_logs = frappe.db.sql('''
|
||||||
SELECT tt.employee AS employee, ttd.hours AS hours, ttd.billable AS billable, ttd.project AS project
|
SELECT tt.employee AS employee, ttd.hours AS hours, ttd.is_billable AS is_billable, ttd.project AS project
|
||||||
FROM `tabTimesheet Detail` AS ttd
|
FROM `tabTimesheet Detail` AS ttd
|
||||||
JOIN `tabTimesheet` AS tt
|
JOIN `tabTimesheet` AS tt
|
||||||
ON ttd.parent = tt.name
|
ON ttd.parent = tt.name
|
||||||
@ -153,14 +153,14 @@ class EmployeeHoursReport:
|
|||||||
def generate_stats_by_employee(self):
|
def generate_stats_by_employee(self):
|
||||||
self.stats_by_employee = frappe._dict()
|
self.stats_by_employee = frappe._dict()
|
||||||
|
|
||||||
for emp, hours, billable, project in self.filtered_time_logs:
|
for emp, hours, is_billable, project in self.filtered_time_logs:
|
||||||
self.stats_by_employee.setdefault(
|
self.stats_by_employee.setdefault(
|
||||||
emp, frappe._dict()
|
emp, frappe._dict()
|
||||||
).setdefault('billed_hours', 0.0)
|
).setdefault('billed_hours', 0.0)
|
||||||
|
|
||||||
self.stats_by_employee[emp].setdefault('non_billed_hours', 0.0)
|
self.stats_by_employee[emp].setdefault('non_billed_hours', 0.0)
|
||||||
|
|
||||||
if billable:
|
if is_billable:
|
||||||
self.stats_by_employee[emp]['billed_hours'] += flt(hours, 2)
|
self.stats_by_employee[emp]['billed_hours'] += flt(hours, 2)
|
||||||
else:
|
else:
|
||||||
self.stats_by_employee[emp]['non_billed_hours'] += flt(hours, 2)
|
self.stats_by_employee[emp]['non_billed_hours'] += flt(hours, 2)
|
||||||
|
@ -31,7 +31,7 @@ class TestEmployeeUtilization(unittest.TestCase):
|
|||||||
timesheet1.append("time_logs", {
|
timesheet1.append("time_logs", {
|
||||||
"activity_type": get_random("Activity Type"),
|
"activity_type": get_random("Activity Type"),
|
||||||
"hours": 5,
|
"hours": 5,
|
||||||
"billable": 1,
|
"is_billable": 1,
|
||||||
"from_time": '2021-04-01 13:30:00.000000',
|
"from_time": '2021-04-01 13:30:00.000000',
|
||||||
"to_time": '2021-04-01 18:30:00.000000'
|
"to_time": '2021-04-01 18:30:00.000000'
|
||||||
})
|
})
|
||||||
@ -46,7 +46,7 @@ class TestEmployeeUtilization(unittest.TestCase):
|
|||||||
timesheet2.append("time_logs", {
|
timesheet2.append("time_logs", {
|
||||||
"activity_type": get_random("Activity Type"),
|
"activity_type": get_random("Activity Type"),
|
||||||
"hours": 10,
|
"hours": 10,
|
||||||
"billable": 0,
|
"is_billable": 0,
|
||||||
"from_time": '2021-04-01 13:30:00.000000',
|
"from_time": '2021-04-01 13:30:00.000000',
|
||||||
"to_time": '2021-04-01 23:30:00.000000',
|
"to_time": '2021-04-01 23:30:00.000000',
|
||||||
"project": cls.test_project.name
|
"project": cls.test_project.name
|
||||||
|
@ -14,7 +14,7 @@ class TestProjectProfitability(unittest.TestCase):
|
|||||||
if not frappe.db.exists('Salary Component', 'Timesheet Component'):
|
if not frappe.db.exists('Salary Component', 'Timesheet Component'):
|
||||||
frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert()
|
frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert()
|
||||||
make_salary_structure_for_timesheet(emp, company='_Test Company')
|
make_salary_structure_for_timesheet(emp, company='_Test Company')
|
||||||
self.timesheet = make_timesheet(emp, simulate = True, billable=1)
|
self.timesheet = make_timesheet(emp, simulate = True, is_billable=1)
|
||||||
self.salary_slip = make_salary_slip(self.timesheet.name)
|
self.salary_slip = make_salary_slip(self.timesheet.name)
|
||||||
self.salary_slip.submit()
|
self.salary_slip.submit()
|
||||||
self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer')
|
self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer')
|
||||||
|
@ -724,6 +724,18 @@ frappe.form.link_formatters['Employee'] = function(value, doc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frappe.form.link_formatters['Project'] = function(value, doc) {
|
||||||
|
if (doc && value && doc.project_name && doc.project_name !== value && doc.project === value) {
|
||||||
|
return value + ': ' + doc.project_name;
|
||||||
|
} else if (!value && doc.doctype && doc.project_name) {
|
||||||
|
// format blank value in child table
|
||||||
|
return doc.project;
|
||||||
|
} else {
|
||||||
|
// 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
|
// add description on posting time
|
||||||
$(document).on('app_ready', function() {
|
$(document).on('app_ready', function() {
|
||||||
if(!frappe.datetime.is_timezone_same()) {
|
if(!frappe.datetime.is_timezone_same()) {
|
||||||
|
Loading…
Reference in New Issue
Block a user