Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,47 +1,35 @@
// Copyright (c) 2025, rtCamp and contributors
// For license information, please see license.txt

const columnNames = ["revenue", "labour_cost", "avg_cost_rate", "profit", "profit_percentage"];
frappe.query_reports["Client Profitability"] = {
filters: [
{
fieldname: "from",
label: __("From Date"),
fieldtype: "Date",
reqd: 1,
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
},
{
fieldname: "to",
label: __("To Date"),
fieldtype: "Date",
reqd: 1,
default: frappe.datetime.get_today(),
},
{
fieldname: "customer",
label: __("Customer"),
fieldtype: "Link",
reqd: 0,
options: "Customer",
},
{
fieldname: "project_type",
label: __("Project Type"),
fieldtype: "Link",
reqd: 0,
options: "Project Type",
},
],
formatter: function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (columnNames.includes(column.id)) {
if (column.id === "profit_percentage") {
value = `<span style="display:flex;justify-content: flex-end;">${value}%</span>`;
} else {
value = `<span style="display:flex;justify-content: flex-end;">$${value}</span>`;
}
}
return value;
},
filters: [
{
fieldname: "from",
label: __("From Date"),
fieldtype: "Date",
reqd: 1,
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
},
{
fieldname: "to",
label: __("To Date"),
fieldtype: "Date",
reqd: 1,
default: frappe.datetime.get_today(),
},
{
fieldname: "customer",
label: __("Customer"),
fieldtype: "Link",
reqd: 0,
options: "Customer",
},
{
fieldname: "project_type",
label: __("Project Type"),
fieldtype: "Link",
reqd: 0,
options: "Project Type",
},
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@ def get_data(filters=None):
"avg_cost_rate": labor_cost / total_hours if total_hours else 0,
"profit": profit,
"profit_percentage": profit_percentage if profit_percentage > 0 else 0,
"currency": CURRENCY,
}
res.append(data)
res.sort(key=lambda x: x["revenue"], reverse=True)
return res


Expand Down Expand Up @@ -167,22 +169,26 @@ def get_columns():
{
"fieldname": "revenue",
"label": _("Revenue $"),
"fieldtype": "Float",
"fieldtype": "Currency",
"options": "currency",
},
{
"fieldname": "unpaid_revenue",
"label": _("UnPaid Invoices $"),
"fieldtype": "Float",
"fieldtype": "Currency",
"options": "currency",
},
{
"fieldname": "paid_revenue",
"label": _("Paid Invoices $"),
"fieldtype": "Float",
"fieldtype": "Currency",
"options": "currency",
},
{
"fieldname": "labour_cost",
"label": _("Labour Cost $"),
"fieldtype": "Float",
"fieldtype": "Currency",
"options": "currency",
},
{
"fieldname": "total_hours",
Expand All @@ -192,16 +198,18 @@ def get_columns():
{
"fieldname": "avg_cost_rate",
"label": _("Average Cost Rate $"),
"fieldtype": "Float",
"fieldtype": "Currency",
"options": "currency",
},
{
"fieldname": "profit",
"label": _("Profit $"),
"fieldtype": "Float",
"fieldtype": "Currency",
"options": "currency",
},
{
"fieldname": "profit_percentage",
"label": _("Profit %"),
"fieldtype": "Float",
"fieldtype": "Percent",
},
]
10 changes: 3 additions & 7 deletions next_pms/next_pms/report/profit_report/profit_report.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,9 @@ frappe.query_reports["Profit Report"] = {
],
formatter: function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (["revenue", "cost", "hourly_rate", "profit"].includes(column.id)) {
value = `<div style="display:flex;justify-content: flex-end;${
data && column.id && data[column.id] < 0 ? "color:red" : ""
}">$${value}</div>`;
}
if (column.id === "profit_percentage") {
value = `<div style="display:flex;justify-content: flex-end;">${value}%</div>`;
if (["revenue", "cost", "hourly_rate", "profit"].includes(column.id) && data && column.id && data[column.id] < 0) {
var $value = $(value).css("color", "red");
value = $value.wrap("<p></p>").parent().html();
}
return value;
},
Expand Down
41 changes: 31 additions & 10 deletions next_pms/next_pms/report/profit_report/profit_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,20 @@ def get_data(filters=None):
start_date = getdate(filters.get("from_date"))
end_date = getdate(filters.get("to_date"))
employees = get_employees(
department=filters.get("department"), status=filters.get("status"), start_date=start_date, end_date=end_date
department=filters.get("department"),
status=filters.get("status"),
start_date=start_date,
end_date=end_date,
)
emp_names = [employee.employee for employee in employees]

timesheets = get_employees_timesheet_hours(emp_names, start_date, end_date)
billing_amount = get_employees_billable_amount(emp_names, start_date, end_date)
for employee in employees:
emp_timesheet = next((timesheet for timesheet in timesheets if timesheet.employee == employee.employee), None)
emp_timesheet = next(
(timesheet for timesheet in timesheets if timesheet.employee == employee.employee),
None,
)
if employee.status != "Active" and not emp_timesheet:
continue
employee_leave_and_holidays = get_employee_leaves_and_holidays(employee.employee, start_date, end_date)
Expand All @@ -55,7 +61,7 @@ def get_data(filters=None):
employee["non_billable_hours"] = emp_timesheet.get("non_billable_hours", 0)
employee["capacity"] = total_hours
employee["utilization"] = employee["billable_hours"] + employee["non_billable_hours"]
employee["hourly_rate"] = get_employee_salary(employee.employee, "USD").get("hourly_salary", 0)
employee["hourly_rate"] = get_employee_salary(employee.employee, "USD", throw=False).get("hourly_salary", 0)
employee["cost"] = calculate_employee_cost(
employee,
start_date=start_date,
Expand All @@ -74,7 +80,13 @@ def get_data(filters=None):


def calculate_employee_cost(
employee, start_date, end_date, num_of_holidays, num_unpaid_leaves, daily_hours, hourly_rate
employee,
start_date,
end_date,
num_of_holidays,
num_unpaid_leaves,
daily_hours,
hourly_rate,
):
days = 0

Expand Down Expand Up @@ -169,6 +181,7 @@ def get_employees(start_date, end_date, department=None, status=None):
for employee in employees:
joining_date = get_employee_joining_date_based_on_work_history(employee)
employee["date_of_joining"] = joining_date
employee["currency"] = "USD"

return employees

Expand Down Expand Up @@ -235,7 +248,11 @@ def get_columns():
"fieldtype": "Link",
"options": "Employee",
},
{"fieldname": "employee_name", "label": _("Employee Name"), "fieldtype": "Data"},
{
"fieldname": "employee_name",
"label": _("Employee Name"),
"fieldtype": "Data",
},
{
"fieldname": "employee_name",
"label": _("Employee Name"),
Expand Down Expand Up @@ -287,27 +304,31 @@ def get_columns():
{
"fieldname": "hourly_rate",
"label": _("Hourly $"),
"fieldtype": "Float",
"fieldtype": "Currency",
"options": "currency",
},
{
"fieldname": "cost",
"label": _("Cost $"),
"fieldtype": "Float",
"fieldtype": "Currency",
"options": "currency",
},
{
"fieldname": "revenue",
"label": _("Revenue $"),
"fieldtype": "Float",
"fieldtype": "Currency",
"options": "currency",
},
{
"fieldname": "profit",
"label": _("Profit $"),
"fieldtype": "Float",
"fieldtype": "Currency",
"options": "currency",
},
{
"fieldname": "profit_percentage",
"label": _("Profit %"),
"fieldtype": "Float",
"fieldtype": "Percent",
},
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ def get_data(filters=None, has_bu_field=False):

emp.age = employee_age_in_company(emp, end_date=getdate(end_date))

salary_info = get_employee_salary(emp.name, currency)
salary_info = get_employee_salary(emp.name, currency, throw=False)
emp.monthly_salary = salary_info.get("monthly_salary", 0)
emp._monthly_salary = emp.monthly_salary
emp.hourly_salary = salary_info.get("hourly_salary", 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ def get_data(meta, filters=None):
filters={"status": employee_status} if employee_status else {},
order_by="employee_name ASC",
)
for employee in employees:
employee["currency"] = CURRENCY

employee_names = [employee.employee for employee in employees]

resource_allocations = get_allocation_list_for_employee_for_given_range(
Expand Down Expand Up @@ -111,12 +114,14 @@ def get_columns(meta):
{
"fieldname": "potential_revenue",
"label": _("Potential Revenue"),
"fieldtype": "Float",
"fieldtype": "Currency",
"options": "currency",
},
{
"fieldname": "booked_revenue",
"label": _("Booked Revenue"),
"fieldtype": "Float",
"fieldtype": "Currency",
"options": "currency",
},
]
if not has_bu_field:
Expand Down Expand Up @@ -240,7 +245,7 @@ def calculate_and_convert_revenue(


def calculate_and_convert_free_hour_revenue(employee: str, free_hours: float):
hourly_salary = get_employee_salary(employee, CURRENCY).get("hourly_salary", 0)
hourly_salary = get_employee_salary(employee, CURRENCY, throw=False).get("hourly_salary", 0)
return (hourly_salary * 3) * free_hours


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,4 @@ frappe.query_reports["Employee Billability"] = {
},
},
],
formatter: function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (column.id === "billing_percentage") {
value = `<span style="display:flex;justify-content: flex-end;">${value}%</span>`;
}
return value;
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def get_columns():
{"fieldname": "billable_hours", "label": _("Billable Hours"), "fieldtype": "Float"},
{"fieldname": "valuable_hours", "label": _("Valuable Hours"), "fieldtype": "Float"},
{"fieldname": "untracked_hours", "label": _("Untracked Hours"), "fieldtype": "Float"},
{"fieldname": "billing_percentage", "label": _("Billing %"), "fieldtype": "Float"},
{"fieldname": "billing_percentage", "label": _("Billing %"), "fieldtype": "Percent"},
{"fieldname": "deficit", "label": _("Deficit"), "fieldtype": "Float"},
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def get_data(filters=None, has_bu_field=False):
emp.ctc = convert_currency(emp.ctc, emp.currency, currency)
emp.currency = currency

salary_info = get_employee_salary(emp.name, currency)
salary_info = get_employee_salary(emp.name, currency, throw=False)
emp.monthly_salary = salary_info.get("monthly_salary", 0)
emp._monthly_salary = emp.monthly_salary # Store original monthly salary

Expand Down
Loading