diff --git a/erpnext/accounts/report/unpaid_expense_claim/__init__.py b/erpnext/accounts/report/unpaid_expense_claim/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.js b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.js deleted file mode 100644 index f0ba78c960..0000000000 --- a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.js +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.query_reports["Unpaid Expense Claim"] = { - "filters": [ - { - "fieldname": "employee", - "label": __("Employee"), - "fieldtype": "Link", - "options": "Employee" - } - ] -} diff --git a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.json b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.json deleted file mode 100644 index 966d5bb85f..0000000000 --- a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2017-01-04 16:26:18.309717", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 2, - "is_standard": "Yes", - "modified": "2017-02-24 19:59:29.747039", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Unpaid Expense Claim", - "owner": "Administrator", - "ref_doctype": "Expense Claim", - "report_name": "Unpaid Expense Claim", - "report_type": "Script Report", - "roles": [ - { - "role": "HR Manager" - }, - { - "role": "Expense Approver" - }, - { - "role": "HR User" - } - ] -} \ No newline at end of file diff --git a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py deleted file mode 100644 index 62b4f63b3a..0000000000 --- a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe import _ - - -def execute(filters=None): - columns, data = [], [] - columns = get_columns() - data = get_unclaimed_expese_claims(filters) - return columns, data - - -def get_columns(): - return [ - _("Employee") + ":Link/Employee:120", - _("Employee Name") + "::120", - _("Expense Claim") + ":Link/Expense Claim:120", - _("Sanctioned Amount") + ":Currency:120", - _("Paid Amount") + ":Currency:120", - _("Outstanding Amount") + ":Currency:150", - ] - - -def get_unclaimed_expese_claims(filters): - cond = "1=1" - if filters.get("employee"): - cond = "ec.employee = %(employee)s" - - return frappe.db.sql( - """ - select - ec.employee, ec.employee_name, ec.name, ec.total_sanctioned_amount, ec.total_amount_reimbursed, - sum(gle.credit_in_account_currency - gle.debit_in_account_currency) as outstanding_amt - from - `tabExpense Claim` ec, `tabGL Entry` gle - where - gle.against_voucher_type = "Expense Claim" and gle.against_voucher = ec.name - and gle.party is not null and ec.docstatus = 1 and ec.is_paid = 0 and {cond} group by ec.name - having - outstanding_amt > 0 - """.format( - cond=cond - ), - filters, - as_list=1, - ) diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/__init__.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js deleted file mode 100644 index 9a30b99f9b..0000000000 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.js +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt -/* eslint-disable */ - -frappe.query_reports["Employee Hours Utilization Based On Timesheet"] = { - "filters": [ - { - fieldname: "company", - label: __("Company"), - fieldtype: "Link", - options: "Company", - default: frappe.defaults.get_user_default("Company"), - reqd: 1 - }, - { - fieldname: "from_date", - label: __("From Date"), - fieldtype: "Date", - default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), - reqd: 1 - }, - { - fieldname:"to_date", - label: __("To Date"), - fieldtype: "Date", - default: frappe.datetime.now_date(), - reqd: 1 - }, - { - fieldname: "employee", - label: __("Employee"), - fieldtype: "Link", - options: "Employee" - }, - { - fieldname: "department", - label: __("Department"), - fieldtype: "Link", - options: "Department" - }, - { - fieldname: "project", - label: __("Project"), - fieldtype: "Link", - options: "Project" - } - ] -}; diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.json b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.json deleted file mode 100644 index 5ff8186572..0000000000 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "add_total_row": 0, - "columns": [], - "creation": "2021-04-05 19:23:43.838623", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "filters": [], - "idx": 0, - "is_standard": "Yes", - "modified": "2021-04-05 19:23:43.838623", - "modified_by": "Administrator", - "module": "Projects", - "name": "Employee Hours Utilization Based On Timesheet", - "owner": "Administrator", - "prepared_report": 0, - "ref_doctype": "Timesheet", - "report_name": "Employee Hours Utilization Based On Timesheet", - "report_type": "Script Report", - "roles": [] -} \ No newline at end of file diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py deleted file mode 100644 index a89e6f039e..0000000000 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py +++ /dev/null @@ -1,261 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -import frappe -from frappe import _ -from frappe.utils import flt, getdate - - -def execute(filters=None): - return EmployeeHoursReport(filters).run() - - -class EmployeeHoursReport: - """Employee Hours Utilization Report Based On Timesheet""" - - def __init__(self, filters=None): - self.filters = frappe._dict(filters or {}) - - self.from_date = getdate(self.filters.from_date) - self.to_date = getdate(self.filters.to_date) - - self.validate_dates() - self.validate_standard_working_hours() - - def validate_dates(self): - self.day_span = (self.to_date - self.from_date).days - - if self.day_span <= 0: - frappe.throw(_("From Date must come before To Date")) - - def validate_standard_working_hours(self): - self.standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours") - if not self.standard_working_hours: - msg = _( - "The metrics for this report are calculated based on the Standard Working Hours. Please set {0} in {1}." - ).format( - frappe.bold("Standard Working Hours"), - frappe.utils.get_link_to_form("HR Settings", "HR Settings"), - ) - - frappe.throw(msg) - - def run(self): - self.generate_columns() - self.generate_data() - self.generate_report_summary() - self.generate_chart_data() - - return self.columns, self.data, None, self.chart, self.report_summary - - def generate_columns(self): - self.columns = [ - { - "label": _("Employee"), - "options": "Employee", - "fieldname": "employee", - "fieldtype": "Link", - "width": 230, - }, - { - "label": _("Department"), - "options": "Department", - "fieldname": "department", - "fieldtype": "Link", - "width": 120, - }, - {"label": _("Total Hours (T)"), "fieldname": "total_hours", "fieldtype": "Float", "width": 120}, - { - "label": _("Billed Hours (B)"), - "fieldname": "billed_hours", - "fieldtype": "Float", - "width": 170, - }, - { - "label": _("Non-Billed Hours (NB)"), - "fieldname": "non_billed_hours", - "fieldtype": "Float", - "width": 170, - }, - { - "label": _("Untracked Hours (U)"), - "fieldname": "untracked_hours", - "fieldtype": "Float", - "width": 170, - }, - { - "label": _("% Utilization (B + NB) / T"), - "fieldname": "per_util", - "fieldtype": "Percentage", - "width": 200, - }, - { - "label": _("% Utilization (B / T)"), - "fieldname": "per_util_billed_only", - "fieldtype": "Percentage", - "width": 200, - }, - ] - - def generate_data(self): - self.generate_filtered_time_logs() - self.generate_stats_by_employee() - self.set_employee_department_and_name() - - if self.filters.department: - self.filter_stats_by_department() - - self.calculate_utilizations() - - self.data = [] - - for emp, data in self.stats_by_employee.items(): - row = frappe._dict() - row["employee"] = emp - row.update(data) - self.data.append(row) - - # Sort by descending order of percentage utilization - self.data.sort(key=lambda x: x["per_util"], reverse=True) - - def filter_stats_by_department(self): - filtered_data = frappe._dict() - for emp, data in self.stats_by_employee.items(): - if data["department"] == self.filters.department: - filtered_data[emp] = data - - # Update stats - self.stats_by_employee = filtered_data - - def generate_filtered_time_logs(self): - additional_filters = "" - - filter_fields = ["employee", "project", "company"] - - for field in filter_fields: - if self.filters.get(field): - if field == "project": - additional_filters += f"AND ttd.{field} = '{self.filters.get(field)}'" - else: - additional_filters += f"AND tt.{field} = '{self.filters.get(field)}'" - - self.filtered_time_logs = frappe.db.sql( - """ - SELECT tt.employee AS employee, ttd.hours AS hours, ttd.is_billable AS is_billable, ttd.project AS project - FROM `tabTimesheet Detail` AS ttd - JOIN `tabTimesheet` AS tt - ON ttd.parent = tt.name - WHERE tt.employee IS NOT NULL - AND tt.start_date BETWEEN '{0}' AND '{1}' - AND tt.end_date BETWEEN '{0}' AND '{1}' - {2} - """.format( - self.filters.from_date, self.filters.to_date, additional_filters - ) - ) - - def generate_stats_by_employee(self): - self.stats_by_employee = frappe._dict() - - for emp, hours, is_billable, project in self.filtered_time_logs: - self.stats_by_employee.setdefault(emp, frappe._dict()).setdefault("billed_hours", 0.0) - - self.stats_by_employee[emp].setdefault("non_billed_hours", 0.0) - - if is_billable: - self.stats_by_employee[emp]["billed_hours"] += flt(hours, 2) - else: - self.stats_by_employee[emp]["non_billed_hours"] += flt(hours, 2) - - def set_employee_department_and_name(self): - for emp in self.stats_by_employee: - emp_name = frappe.db.get_value("Employee", emp, "employee_name") - emp_dept = frappe.db.get_value("Employee", emp, "department") - - self.stats_by_employee[emp]["department"] = emp_dept - self.stats_by_employee[emp]["employee_name"] = emp_name - - def calculate_utilizations(self): - TOTAL_HOURS = flt(self.standard_working_hours * self.day_span, 2) - for emp, data in self.stats_by_employee.items(): - data["total_hours"] = TOTAL_HOURS - data["untracked_hours"] = flt(TOTAL_HOURS - data["billed_hours"] - data["non_billed_hours"], 2) - - # To handle overtime edge-case - if data["untracked_hours"] < 0: - data["untracked_hours"] = 0.0 - - data["per_util"] = flt( - ((data["billed_hours"] + data["non_billed_hours"]) / TOTAL_HOURS) * 100, 2 - ) - data["per_util_billed_only"] = flt((data["billed_hours"] / TOTAL_HOURS) * 100, 2) - - def generate_report_summary(self): - self.report_summary = [] - - if not self.data: - return - - avg_utilization = 0.0 - avg_utilization_billed_only = 0.0 - total_billed, total_non_billed = 0.0, 0.0 - total_untracked = 0.0 - - for row in self.data: - avg_utilization += row["per_util"] - avg_utilization_billed_only += row["per_util_billed_only"] - total_billed += row["billed_hours"] - total_non_billed += row["non_billed_hours"] - total_untracked += row["untracked_hours"] - - avg_utilization /= len(self.data) - avg_utilization = flt(avg_utilization, 2) - - avg_utilization_billed_only /= len(self.data) - avg_utilization_billed_only = flt(avg_utilization_billed_only, 2) - - THRESHOLD_PERCENTAGE = 70.0 - self.report_summary = [ - { - "value": f"{avg_utilization}%", - "indicator": "Red" if avg_utilization < THRESHOLD_PERCENTAGE else "Green", - "label": _("Avg Utilization"), - "datatype": "Percentage", - }, - { - "value": f"{avg_utilization_billed_only}%", - "indicator": "Red" if avg_utilization_billed_only < THRESHOLD_PERCENTAGE else "Green", - "label": _("Avg Utilization (Billed Only)"), - "datatype": "Percentage", - }, - {"value": total_billed, "label": _("Total Billed Hours"), "datatype": "Float"}, - {"value": total_non_billed, "label": _("Total Non-Billed Hours"), "datatype": "Float"}, - ] - - def generate_chart_data(self): - self.chart = {} - - labels = [] - billed_hours = [] - non_billed_hours = [] - untracked_hours = [] - - for row in self.data: - labels.append(row.get("employee_name")) - billed_hours.append(row.get("billed_hours")) - non_billed_hours.append(row.get("non_billed_hours")) - untracked_hours.append(row.get("untracked_hours")) - - self.chart = { - "data": { - "labels": labels[:30], - "datasets": [ - {"name": _("Billed Hours"), "values": billed_hours[:30]}, - {"name": _("Non-Billed Hours"), "values": non_billed_hours[:30]}, - {"name": _("Untracked Hours"), "values": untracked_hours[:30]}, - ], - }, - "type": "bar", - "barOptions": {"stacked": True}, - } diff --git a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py b/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py deleted file mode 100644 index 98fa6f0306..0000000000 --- a/erpnext/projects/report/employee_hours_utilization_based_on_timesheet/test_employee_util.py +++ /dev/null @@ -1,199 +0,0 @@ -import unittest - -import frappe -from frappe.utils.make_random import get_random - -from erpnext.projects.doctype.project.test_project import make_project -from erpnext.projects.report.employee_hours_utilization_based_on_timesheet.employee_hours_utilization_based_on_timesheet import ( - execute, -) -from erpnext.setup.doctype.employee.test_employee import make_employee - - -class TestEmployeeUtilization(unittest.TestCase): - @classmethod - def setUpClass(cls): - # Create test employee - cls.test_emp1 = make_employee("test1@employeeutil.com", "_Test Company") - cls.test_emp2 = make_employee("test2@employeeutil.com", "_Test Company") - - # Create test project - cls.test_project = make_project({"project_name": "_Test Project"}) - - # Create test timesheets - cls.create_test_timesheets() - - frappe.db.set_value("HR Settings", "HR Settings", "standard_working_hours", 9) - - @classmethod - def create_test_timesheets(cls): - timesheet1 = frappe.new_doc("Timesheet") - timesheet1.employee = cls.test_emp1 - timesheet1.company = "_Test Company" - - timesheet1.append( - "time_logs", - { - "activity_type": get_random("Activity Type"), - "hours": 5, - "is_billable": 1, - "from_time": "2021-04-01 13:30:00.000000", - "to_time": "2021-04-01 18:30:00.000000", - }, - ) - - timesheet1.save() - timesheet1.submit() - - timesheet2 = frappe.new_doc("Timesheet") - timesheet2.employee = cls.test_emp2 - timesheet2.company = "_Test Company" - - timesheet2.append( - "time_logs", - { - "activity_type": get_random("Activity Type"), - "hours": 10, - "is_billable": 0, - "from_time": "2021-04-01 13:30:00.000000", - "to_time": "2021-04-01 23:30:00.000000", - "project": cls.test_project.name, - }, - ) - - timesheet2.save() - timesheet2.submit() - - @classmethod - def tearDownClass(cls): - # Delete time logs - frappe.db.sql( - """ - DELETE FROM `tabTimesheet Detail` - WHERE parent IN ( - SELECT name - FROM `tabTimesheet` - WHERE company = '_Test Company' - ) - """ - ) - - frappe.db.sql("DELETE FROM `tabTimesheet` WHERE company='_Test Company'") - frappe.db.sql(f"DELETE FROM `tabProject` WHERE name='{cls.test_project.name}'") - - def test_utilization_report_with_required_filters_only(self): - filters = {"company": "_Test Company", "from_date": "2021-04-01", "to_date": "2021-04-03"} - - report = execute(filters) - - expected_data = self.get_expected_data_for_test_employees() - self.assertEqual(report[1], expected_data) - - def test_utilization_report_for_single_employee(self): - filters = { - "company": "_Test Company", - "from_date": "2021-04-01", - "to_date": "2021-04-03", - "employee": self.test_emp1, - } - - report = execute(filters) - - emp1_data = frappe.get_doc("Employee", self.test_emp1) - expected_data = [ - { - "employee": self.test_emp1, - "employee_name": "test1@employeeutil.com", - "billed_hours": 5.0, - "non_billed_hours": 0.0, - "department": emp1_data.department, - "total_hours": 18.0, - "untracked_hours": 13.0, - "per_util": 27.78, - "per_util_billed_only": 27.78, - } - ] - - self.assertEqual(report[1], expected_data) - - def test_utilization_report_for_project(self): - filters = { - "company": "_Test Company", - "from_date": "2021-04-01", - "to_date": "2021-04-03", - "project": self.test_project.name, - } - - report = execute(filters) - - emp2_data = frappe.get_doc("Employee", self.test_emp2) - expected_data = [ - { - "employee": self.test_emp2, - "employee_name": "test2@employeeutil.com", - "billed_hours": 0.0, - "non_billed_hours": 10.0, - "department": emp2_data.department, - "total_hours": 18.0, - "untracked_hours": 8.0, - "per_util": 55.56, - "per_util_billed_only": 0.0, - } - ] - - self.assertEqual(report[1], expected_data) - - def test_utilization_report_for_department(self): - emp1_data = frappe.get_doc("Employee", self.test_emp1) - filters = { - "company": "_Test Company", - "from_date": "2021-04-01", - "to_date": "2021-04-03", - "department": emp1_data.department, - } - - report = execute(filters) - - expected_data = self.get_expected_data_for_test_employees() - self.assertEqual(report[1], expected_data) - - def test_report_summary_data(self): - filters = {"company": "_Test Company", "from_date": "2021-04-01", "to_date": "2021-04-03"} - - report = execute(filters) - summary = report[4] - expected_summary_values = ["41.67%", "13.89%", 5.0, 10.0] - - self.assertEqual(len(summary), 4) - - for i in range(4): - self.assertEqual(summary[i]["value"], expected_summary_values[i]) - - def get_expected_data_for_test_employees(self): - emp1_data = frappe.get_doc("Employee", self.test_emp1) - emp2_data = frappe.get_doc("Employee", self.test_emp2) - - return [ - { - "employee": self.test_emp2, - "employee_name": "test2@employeeutil.com", - "billed_hours": 0.0, - "non_billed_hours": 10.0, - "department": emp2_data.department, - "total_hours": 18.0, - "untracked_hours": 8.0, - "per_util": 55.56, - "per_util_billed_only": 0.0, - }, - { - "employee": self.test_emp1, - "employee_name": "test1@employeeutil.com", - "billed_hours": 5.0, - "non_billed_hours": 0.0, - "department": emp1_data.department, - "total_hours": 18.0, - "untracked_hours": 13.0, - "per_util": 27.78, - "per_util_billed_only": 27.78, - }, - ] diff --git a/erpnext/projects/report/project_profitability/__init__.py b/erpnext/projects/report/project_profitability/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/projects/report/project_profitability/project_profitability.js b/erpnext/projects/report/project_profitability/project_profitability.js deleted file mode 100644 index 13ae19bb29..0000000000 --- a/erpnext/projects/report/project_profitability/project_profitability.js +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt -/* eslint-disable */ - -frappe.query_reports["Project Profitability"] = { - "filters": [ - { - "fieldname": "company", - "label": __("Company"), - "fieldtype": "Link", - "options": "Company", - "default": frappe.defaults.get_user_default("Company"), - "reqd": 1 - }, - { - "fieldname": "start_date", - "label": __("Start Date"), - "fieldtype": "Date", - "reqd": 1, - "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1) - }, - { - "fieldname": "end_date", - "label": __("End Date"), - "fieldtype": "Date", - "reqd": 1, - "default": frappe.datetime.now_date() - }, - { - "fieldname": "customer_name", - "label": __("Customer"), - "fieldtype": "Link", - "options": "Customer" - }, - { - "fieldname": "employee", - "label": __("Employee"), - "fieldtype": "Link", - "options": "Employee" - }, - { - "fieldname": "project", - "label": __("Project"), - "fieldtype": "Link", - "options": "Project" - } - ] -}; diff --git a/erpnext/projects/report/project_profitability/project_profitability.json b/erpnext/projects/report/project_profitability/project_profitability.json deleted file mode 100644 index 0b092cd2c0..0000000000 --- a/erpnext/projects/report/project_profitability/project_profitability.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "add_total_row": 0, - "columns": [], - "creation": "2021-04-16 15:50:28.914872", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "filters": [], - "idx": 0, - "is_standard": "Yes", - "modified": "2021-04-16 15:50:48.490866", - "modified_by": "Administrator", - "module": "Projects", - "name": "Project Profitability", - "owner": "Administrator", - "prepared_report": 0, - "ref_doctype": "Timesheet", - "report_name": "Project Profitability", - "report_type": "Script Report", - "roles": [ - { - "role": "HR User" - }, - { - "role": "Accounts User" - }, - { - "role": "Employee" - }, - { - "role": "Projects User" - }, - { - "role": "Manufacturing User" - }, - { - "role": "Employee Self Service" - }, - { - "role": "HR Manager" - } - ] -} \ No newline at end of file diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py deleted file mode 100644 index abbbaf5d92..0000000000 --- a/erpnext/projects/report/project_profitability/project_profitability.py +++ /dev/null @@ -1,198 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import frappe -from frappe import _ -from frappe.utils import flt - - -def execute(filters=None): - data = get_data(filters) - columns = get_columns() - charts = get_chart_data(data) - return columns, data, None, charts - - -def get_data(filters): - data = get_rows(filters) - data = calculate_cost_and_profit(data) - return data - - -def get_rows(filters): - conditions = get_conditions(filters) - standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours") - if not standard_working_hours: - msg = _( - "The metrics for this report are calculated based on the Standard Working Hours. Please set {0} in {1}." - ).format( - frappe.bold("Standard Working Hours"), - frappe.utils.get_link_to_form("HR Settings", "HR Settings"), - ) - - frappe.msgprint(msg) - return [] - - sql = """ - SELECT - * - FROM - (SELECT - si.customer_name,si.base_grand_total, - si.name as voucher_no,tabTimesheet.employee, - tabTimesheet.title as employee_name,tabTimesheet.parent_project as project, - tabTimesheet.start_date,tabTimesheet.end_date, - tabTimesheet.total_billed_hours,tabTimesheet.name as timesheet, - ss.base_gross_pay,ss.total_working_days, - tabTimesheet.total_billed_hours/(ss.total_working_days * {0}) as utilization - FROM - `tabSalary Slip Timesheet` as sst join `tabTimesheet` on tabTimesheet.name = sst.time_sheet - join `tabSales Invoice Timesheet` as sit on sit.time_sheet = tabTimesheet.name - join `tabSales Invoice` as si on si.name = sit.parent and si.status != "Cancelled" - join `tabSalary Slip` as ss on ss.name = sst.parent and ss.status != "Cancelled" """.format( - standard_working_hours - ) - if conditions: - sql += """ - WHERE - {0}) as t""".format( - conditions - ) - return frappe.db.sql(sql, filters, as_dict=True) - - -def calculate_cost_and_profit(data): - for row in data: - row.fractional_cost = flt(row.base_gross_pay) * flt(row.utilization) - row.profit = flt(row.base_grand_total) - flt(row.base_gross_pay) * flt(row.utilization) - return data - - -def get_conditions(filters): - conditions = [] - - if filters.get("company"): - conditions.append("tabTimesheet.company={0}".format(frappe.db.escape(filters.get("company")))) - - if filters.get("start_date"): - conditions.append("tabTimesheet.start_date>='{0}'".format(filters.get("start_date"))) - - if filters.get("end_date"): - conditions.append("tabTimesheet.end_date<='{0}'".format(filters.get("end_date"))) - - if filters.get("customer_name"): - conditions.append("si.customer_name={0}".format(frappe.db.escape(filters.get("customer_name")))) - - if filters.get("employee"): - conditions.append("tabTimesheet.employee={0}".format(frappe.db.escape(filters.get("employee")))) - - if filters.get("project"): - conditions.append( - "tabTimesheet.parent_project={0}".format(frappe.db.escape(filters.get("project"))) - ) - - conditions = " and ".join(conditions) - return conditions - - -def get_chart_data(data): - if not data: - return None - - labels = [] - utilization = [] - - for entry in data: - labels.append(entry.get("employee_name") + " - " + str(entry.get("end_date"))) - utilization.append(entry.get("utilization")) - - charts = { - "data": {"labels": labels, "datasets": [{"name": "Utilization", "values": utilization}]}, - "type": "bar", - "colors": ["#84BDD5"], - } - return charts - - -def get_columns(): - return [ - { - "fieldname": "customer_name", - "label": _("Customer"), - "fieldtype": "Link", - "options": "Customer", - "width": 150, - }, - { - "fieldname": "employee", - "label": _("Employee"), - "fieldtype": "Link", - "options": "Employee", - "width": 130, - }, - {"fieldname": "employee_name", "label": _("Employee Name"), "fieldtype": "Data", "width": 120}, - { - "fieldname": "voucher_no", - "label": _("Sales Invoice"), - "fieldtype": "Link", - "options": "Sales Invoice", - "width": 120, - }, - { - "fieldname": "timesheet", - "label": _("Timesheet"), - "fieldtype": "Link", - "options": "Timesheet", - "width": 120, - }, - { - "fieldname": "project", - "label": _("Project"), - "fieldtype": "Link", - "options": "Project", - "width": 100, - }, - { - "fieldname": "base_grand_total", - "label": _("Bill Amount"), - "fieldtype": "Currency", - "options": "currency", - "width": 100, - }, - { - "fieldname": "base_gross_pay", - "label": _("Cost"), - "fieldtype": "Currency", - "options": "currency", - "width": 100, - }, - { - "fieldname": "profit", - "label": _("Profit"), - "fieldtype": "Currency", - "options": "currency", - "width": 100, - }, - {"fieldname": "utilization", "label": _("Utilization"), "fieldtype": "Percentage", "width": 100}, - { - "fieldname": "fractional_cost", - "label": _("Fractional Cost"), - "fieldtype": "Int", - "width": 120, - }, - { - "fieldname": "total_billed_hours", - "label": _("Total Billed Hours"), - "fieldtype": "Int", - "width": 150, - }, - {"fieldname": "start_date", "label": _("Start Date"), "fieldtype": "Date", "width": 100}, - {"fieldname": "end_date", "label": _("End Date"), "fieldtype": "Date", "width": 100}, - { - "label": _("Currency"), - "fieldname": "currency", - "fieldtype": "Link", - "options": "Currency", - "width": 80, - }, - ] diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py deleted file mode 100644 index 381d63912b..0000000000 --- a/erpnext/projects/report/project_profitability/test_project_profitability.py +++ /dev/null @@ -1,72 +0,0 @@ -import frappe -from frappe.tests.utils import FrappeTestCase -from frappe.utils import add_days, getdate - -from erpnext.projects.doctype.timesheet.test_timesheet import ( - make_salary_structure_for_timesheet, - make_timesheet, -) -from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice -from erpnext.projects.report.project_profitability.project_profitability import execute -from erpnext.setup.doctype.employee.test_employee import make_employee - - -class TestProjectProfitability(FrappeTestCase): - def setUp(self): - frappe.db.sql("delete from `tabTimesheet`") - emp = make_employee("test_employee_9@salary.com", company="_Test Company") - - if not frappe.db.exists("Salary Component", "Timesheet Component"): - frappe.get_doc( - {"doctype": "Salary Component", "salary_component": "Timesheet Component"} - ).insert() - - make_salary_structure_for_timesheet(emp, company="_Test Company") - date = getdate() - - self.timesheet = make_timesheet(emp, is_billable=1) - self.salary_slip = make_salary_slip(self.timesheet.name) - self.salary_slip.start_date = self.timesheet.start_date - - holidays = self.salary_slip.get_holidays_for_employee(date, date) - if holidays: - frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1) - - self.salary_slip.submit() - self.sales_invoice = make_sales_invoice(self.timesheet.name, "_Test Item", "_Test Customer") - self.sales_invoice.due_date = date - self.sales_invoice.submit() - - frappe.db.set_value("HR Settings", None, "standard_working_hours", 8) - frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0) - - def test_project_profitability(self): - filters = { - "company": "_Test Company", - "start_date": add_days(self.timesheet.start_date, -3), - "end_date": self.timesheet.start_date, - } - - report = execute(filters) - - row = report[1][0] - timesheet = frappe.get_doc("Timesheet", self.timesheet.name) - - self.assertEqual(self.sales_invoice.customer, row.customer_name) - self.assertEqual(timesheet.title, row.employee_name) - self.assertEqual(self.sales_invoice.base_grand_total, row.base_grand_total) - self.assertEqual(self.salary_slip.base_gross_pay, row.base_gross_pay) - self.assertEqual(timesheet.total_billed_hours, row.total_billed_hours) - self.assertEqual(self.salary_slip.total_working_days, row.total_working_days) - - standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours") - utilization = timesheet.total_billed_hours / ( - self.salary_slip.total_working_days * standard_working_hours - ) - self.assertEqual(utilization, row.utilization) - - profit = self.sales_invoice.base_grand_total - self.salary_slip.base_gross_pay * utilization - self.assertEqual(profit, row.profit) - - fractional_cost = self.salary_slip.base_gross_pay * utilization - self.assertEqual(fractional_cost, row.fractional_cost)