From 6a730f1764f1fccf134bff2da55b23962650ac3a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 28 Dec 2023 03:08:29 +0530 Subject: [PATCH 1/9] fix: Duplicate Overwritten Salary error for other employee (#1217) (#1218) * fix: Duplicate Overwritten Salary error for other employee missing to add the employee to validation check * chore: fix formatting --------- Co-authored-by: Rucha Mahabal (cherry picked from commit 08411c8b7c194f06356d85a61ad54100eeb27560) Co-authored-by: HoFaks <33111711+HoFaks@users.noreply.github.com> --- hrms/payroll/doctype/additional_salary/additional_salary.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hrms/payroll/doctype/additional_salary/additional_salary.py b/hrms/payroll/doctype/additional_salary/additional_salary.py index eee0063351..9df213a64f 100644 --- a/hrms/payroll/doctype/additional_salary/additional_salary.py +++ b/hrms/payroll/doctype/additional_salary/additional_salary.py @@ -124,6 +124,8 @@ def validate_duplicate_additional_salary(self): "salary_component": self.salary_component, "payroll_date": self.payroll_date, "overwrite_salary_structure_amount": 1, + "employee": self.employee, + "docstatus": 1, }, ) From a760c387d54835e4102b46ebfd8937383ab645f8 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 28 Dec 2023 12:48:48 +0530 Subject: [PATCH 2/9] fix: avoid assigning undefined --- hrms/hr/page/organizational_chart/organizational_chart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrms/hr/page/organizational_chart/organizational_chart.js b/hrms/hr/page/organizational_chart/organizational_chart.js index 239f3c2109..c526f562b9 100644 --- a/hrms/hr/page/organizational_chart/organizational_chart.js +++ b/hrms/hr/page/organizational_chart/organizational_chart.js @@ -7,7 +7,7 @@ frappe.pages['organizational-chart'].on_page_load = function(wrapper) { $(wrapper).bind('show', () => { frappe.require('hierarchy-chart.bundle.js', () => { - let organizational_chart = undefined; + let organizational_chart; let method = 'hrms.hr.page.organizational_chart.organizational_chart.get_children'; if (frappe.is_mobile()) { From f63b42844db90c42cc7fd1ce8d57ee55c606665b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 28 Dec 2023 22:30:24 +0530 Subject: [PATCH 3/9] fix: update job applicant status on interview submit (backport #892) (#1224) * fix: update job applicant status on interview submit * fix: add a dialog box for updating the job applicant status on interview submit * fix: frappe linter issue for translation * refactor: job applicant status update on interview submission * fix: add test case for Job Applicant status update from Interview status change * test: cleanup update job applicant status test --------- Co-authored-by: Rucha Mahabal (cherry picked from commit 604d0eec5a3726d1ad2f6c847a5db5de4b30cc51) Co-authored-by: zeel prajapati --- hrms/hr/doctype/interview/interview.py | 56 ++++++++++++++++++++- hrms/hr/doctype/interview/test_interview.py | 17 ++++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/hrms/hr/doctype/interview/interview.py b/hrms/hr/doctype/interview/interview.py index cfc1ec1179..9c64c36f04 100644 --- a/hrms/hr/doctype/interview/interview.py +++ b/hrms/hr/doctype/interview/interview.py @@ -24,8 +24,10 @@ def validate(self): def on_submit(self): if self.status not in ["Cleared", "Rejected"]: frappe.throw( - _("Only Interviews with Cleared or Rejected status can be submitted."), title=_("Not Allowed") + _("Only Interviews with Cleared or Rejected status can be submitted."), + title=_("Not Allowed"), ) + self.show_job_applicant_update_dialog() def validate_duplicate_interview(self): duplicate_interview = frappe.db.exists( @@ -102,6 +104,29 @@ def set_average_rating(self): total_rating / len(self.interview_details) if len(self.interview_details) else 0 ) + def show_job_applicant_update_dialog(self): + job_applicant_status = self.get_job_applicant_status() + if not job_applicant_status: + return + + job_application_name = frappe.db.get_value("Job Applicant", self.job_applicant, "applicant_name") + + frappe.msgprint( + _("Do you want to update the Job Applicant {0} as {1} based on this interview result?").format( + frappe.bold(job_application_name), frappe.bold(job_applicant_status) + ), + title=_("Update Job Applicant"), + primary_action={ + "label": _("Mark as {0}").format(job_applicant_status), + "server_action": "hrms.hr.doctype.interview.interview.update_job_applicant_status", + "args": {"job_applicant": self.job_applicant, "status": job_applicant_status}, + }, + ) + + def get_job_applicant_status(self) -> str | None: + status_map = {"Cleared": "Accepted", "Rejected": "Rejected"} + return status_map.get(self.status, None) + @frappe.whitelist() def reschedule_interview(self, scheduled_on, from_time, to_time): original_date = self.scheduled_on @@ -148,6 +173,35 @@ def get_recipients(name, for_feedback=0): return recipients +@frappe.whitelist() +def update_job_applicant_status(args): + import json + + try: + if isinstance(args, str): + args = json.loads(args) + + if not args.get("job_applicant"): + frappe.throw(_("Please specify the job applicant to be updated.")) + + job_applicant = frappe.get_doc("Job Applicant", args["job_applicant"]) + job_applicant.status = args["status"] + job_applicant.save() + + frappe.msgprint( + _("Updated the Job Applicant status to {0}").format(job_applicant.status), + alert=True, + indicator="green", + ) + except Exception: + job_applicant.log_error("Failed to update Job Applicant status") + frappe.msgprint( + _("Failed to update the Job Applicant status"), + alert=True, + indicator="red", + ) + + @frappe.whitelist() def get_interviewers(interview_round): return frappe.get_all( diff --git a/hrms/hr/doctype/interview/test_interview.py b/hrms/hr/doctype/interview/test_interview.py index e8184c2f38..846eb1ae0e 100644 --- a/hrms/hr/doctype/interview/test_interview.py +++ b/hrms/hr/doctype/interview/test_interview.py @@ -12,7 +12,10 @@ from erpnext.setup.doctype.designation.test_designation import create_designation -from hrms.hr.doctype.interview.interview import DuplicateInterviewRoundError +from hrms.hr.doctype.interview.interview import ( + DuplicateInterviewRoundError, + update_job_applicant_status, +) from hrms.hr.doctype.job_applicant.job_applicant import get_interview_details from hrms.tests.test_utils import create_job_applicant @@ -109,6 +112,18 @@ def test_get_interview_details_for_applicant_dashboard(self): }, ) + def test_job_applicant_status_update_on_interview_submit(self): + job_applicant = create_job_applicant() + interview = create_interview_and_dependencies(job_applicant.name) + + interview.status = "Cleared" + interview.submit() + + update_job_applicant_status({"job_applicant": job_applicant.name, "status": "Accepted"}) + job_applicant.reload() + + self.assertEqual(job_applicant.status, "Accepted") + def tearDown(self): frappe.db.rollback() From e8d46b31dc52d8783b38ebd766814d3d9151efc4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 29 Dec 2023 10:57:56 +0530 Subject: [PATCH 4/9] feat(Interview): Add setting for sender in interview & interview feedback reminder emails (backport #1033) (#1229) * feat(Interview): Add setting for sender in interview & interview feedback reminder emails (#1033) * feat: Add sender in interview & interview feedback reminder * fix: Add sender field for interview reminder * chore: rearrange fields --------- Co-authored-by: Niraj Gautam Co-authored-by: Rucha Mahabal --- hrms/hr/doctype/hr_settings/hr_settings.js | 7 +++++++ hrms/hr/doctype/hr_settings/hr_settings.json | 20 ++++++++++++++++++-- hrms/hr/doctype/interview/interview.py | 10 ++++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/hrms/hr/doctype/hr_settings/hr_settings.js b/hrms/hr/doctype/hr_settings/hr_settings.js index b85f4aec4d..ce1374498a 100644 --- a/hrms/hr/doctype/hr_settings/hr_settings.js +++ b/hrms/hr/doctype/hr_settings/hr_settings.js @@ -10,6 +10,13 @@ frappe.ui.form.on('HR Settings', { }, }; }); + frm.set_query("hiring_sender", () => { + return { + filters: { + enable_outgoing: 1, + }, + }; + }); } }); diff --git a/hrms/hr/doctype/hr_settings/hr_settings.json b/hrms/hr/doctype/hr_settings/hr_settings.json index 30cf9584e8..30ea9224b3 100644 --- a/hrms/hr/doctype/hr_settings/hr_settings.json +++ b/hrms/hr/doctype/hr_settings/hr_settings.json @@ -38,9 +38,11 @@ "send_interview_reminder", "interview_reminder_template", "remind_before", - "column_break_4", "send_interview_feedback_reminder", "feedback_reminder_notification_template", + "column_break_4", + "hiring_sender", + "hiring_sender_email", "employee_exit_section", "exit_questionnaire_web_form", "column_break_34", @@ -270,6 +272,20 @@ "fieldname": "column_break_hyec", "fieldtype": "Column Break" }, + { + "fieldname": "hiring_sender", + "fieldtype": "Link", + "label": "Sender", + "options": "Email Account" + }, + { + "depends_on": "eval:doc.hiring_sender", + "fetch_from": "hiring_sender.email_id", + "fieldname": "hiring_sender_email", + "fieldtype": "Data", + "label": "Sender Email", + "read_only": 1 + }, { "default": "1", "fieldname": "allow_multiple_shift_assignments", @@ -286,7 +302,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2023-12-07 14:55:37.309553", + "modified": "2023-12-28 23:07:36.317223", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", diff --git a/hrms/hr/doctype/interview/interview.py b/hrms/hr/doctype/interview/interview.py index 9c64c36f04..1294576ff4 100644 --- a/hrms/hr/doctype/interview/interview.py +++ b/hrms/hr/doctype/interview/interview.py @@ -213,7 +213,7 @@ def send_interview_reminder(): reminder_settings = frappe.db.get_value( "HR Settings", "HR Settings", - ["send_interview_reminder", "interview_reminder_template"], + ["send_interview_reminder", "interview_reminder_template", "hiring_sender_email"], as_dict=True, ) @@ -247,6 +247,7 @@ def send_interview_reminder(): recipients = get_recipients(doc.name) frappe.sendmail( + sender=reminder_settings.hiring_sender_email, recipients=recipients, subject=interview_template.subject, message=message, @@ -261,7 +262,11 @@ def send_daily_feedback_reminder(): reminder_settings = frappe.db.get_value( "HR Settings", "HR Settings", - ["send_interview_feedback_reminder", "feedback_reminder_notification_template"], + [ + "send_interview_feedback_reminder", + "feedback_reminder_notification_template", + "hiring_sender_email", + ], as_dict=True, ) @@ -292,6 +297,7 @@ def send_daily_feedback_reminder(): if len(recipients): frappe.sendmail( + sender=reminder_settings.hiring_sender_email, recipients=recipients, subject=interview_feedback_template.subject, message=message, From 2113d4088d65c1ab19dbed5d5822c0333f8d9fad Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 29 Dec 2023 10:58:56 +0530 Subject: [PATCH 5/9] fix: #1226 local variable "days_to_add" referenced before assignment (backport #1227) (#1232) (cherry picked from commit cb3e8038cc0c56f22b52a7b1f9a4393704d210d3) Co-authored-by: Mario Monroy --- hrms/payroll/doctype/salary_slip/salary_slip.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hrms/payroll/doctype/salary_slip/salary_slip.py b/hrms/payroll/doctype/salary_slip/salary_slip.py index d9d9caaffc..ff16236d03 100644 --- a/hrms/payroll/doctype/salary_slip/salary_slip.py +++ b/hrms/payroll/doctype/salary_slip/salary_slip.py @@ -919,6 +919,7 @@ def get_amount_from_formula(self, struct_row, sub_period=1): posting_date = frappe.utils.add_months(self.posting_date, sub_period) else: + days_to_add = 0 if self.payroll_frequency == "Weekly": days_to_add = sub_period * 6 From 49110e0ad88f65096bfccd2540c6483235997664 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 29 Dec 2023 11:27:13 +0530 Subject: [PATCH 6/9] test: fix flaky shift type tests - avoid duplicate holiday date insertion (cherry picked from commit addfc133e07342ef7a3d6a2c91d12964050d67bc) --- hrms/hr/doctype/shift_type/test_shift_type.py | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/hrms/hr/doctype/shift_type/test_shift_type.py b/hrms/hr/doctype/shift_type/test_shift_type.py index 7ed2c13da3..974676080c 100644 --- a/hrms/hr/doctype/shift_type/test_shift_type.py +++ b/hrms/hr/doctype/shift_type/test_shift_type.py @@ -43,7 +43,9 @@ def test_mark_attendance(self): shift_type.process_auto_attendance() - attendance = frappe.db.get_value("Attendance", {"shift": shift_type.name}, "status") + attendance = frappe.db.get_value( + "Attendance", {"employee": employee, "shift": shift_type.name}, "status" + ) self.assertEqual(attendance, "Present") def test_mark_attendance_with_different_shift_start_time(self): @@ -68,7 +70,9 @@ def test_mark_attendance_with_different_shift_start_time(self): shift_type.process_auto_attendance() - attendance = frappe.db.get_value("Attendance", {"shift": shift_type.name}, "status") + attendance = frappe.db.get_value( + "Attendance", {"employee": employee, "shift": shift_type.name}, "status" + ) self.assertEqual(attendance, "Present") def test_attendance_date_for_different_start_and_actual_start_date(self): @@ -129,7 +133,7 @@ def test_entry_and_exit_grace(self): attendance = frappe.db.get_value( "Attendance", - {"shift": shift_type.name}, + {"shift": shift_type.name, "employee": employee}, ["status", "name", "late_entry", "early_exit"], as_dict=True, ) @@ -246,15 +250,7 @@ def test_mark_auto_attendance_on_holiday_enabled(self): # add current date as holiday date = getdate() - holiday_list = frappe.get_doc("Holiday List", self.holiday_list) - holiday_list.append( - "holidays", - { - "holiday_date": date, - "description": "test", - }, - ) - holiday_list.save() + add_date_to_holiday_list(date, self.holiday_list) shift_type = setup_shift_type( shift_type="Test Holiday Shift", mark_auto_attendance_on_holidays=True @@ -285,15 +281,7 @@ def test_mark_auto_attendance_on_holiday_disabled(self): # add current date as holiday date = getdate() - holiday_list = frappe.get_doc("Holiday List", self.holiday_list) - holiday_list.append( - "holidays", - { - "holiday_date": date, - "description": "test", - }, - ) - holiday_list.save() + add_date_to_holiday_list(date, self.holiday_list) shift_type = setup_shift_type( shift_type="Test Holiday Shift", mark_auto_attendance_on_holidays=False @@ -660,3 +648,18 @@ def make_shift_assignment(shift_type, employee, start_date, end_date=None, do_no shift_assignment.submit() return shift_assignment + + +def add_date_to_holiday_list(date: str, holiday_list: str) -> None: + if frappe.db.exists("Holiday", {"parent": holiday_list, "holiday_date": date}): + return + + holiday_list = frappe.get_doc("Holiday List", holiday_list) + holiday_list.append( + "holidays", + { + "holiday_date": date, + "description": "test", + }, + ) + holiday_list.save() From ea2e7586bc0e25b35129fc20cc4eed74867b8f46 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 29 Dec 2023 11:53:44 +0530 Subject: [PATCH 7/9] test: fix flaky interview test (cherry picked from commit 46a1b37a574d6bb34330e8626858086559591994) --- hrms/hr/doctype/interview/test_interview.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/hrms/hr/doctype/interview/test_interview.py b/hrms/hr/doctype/interview/test_interview.py index 846eb1ae0e..a66bef04ca 100644 --- a/hrms/hr/doctype/interview/test_interview.py +++ b/hrms/hr/doctype/interview/test_interview.py @@ -114,10 +114,7 @@ def test_get_interview_details_for_applicant_dashboard(self): def test_job_applicant_status_update_on_interview_submit(self): job_applicant = create_job_applicant() - interview = create_interview_and_dependencies(job_applicant.name) - - interview.status = "Cleared" - interview.submit() + interview = create_interview_and_dependencies(job_applicant.name, status="Cleared") update_job_applicant_status({"job_applicant": job_applicant.name, "status": "Accepted"}) job_applicant.reload() From 9b783a8a7ca2561f5411890b14d0e28818ea4175 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 29 Dec 2023 12:44:04 +0530 Subject: [PATCH 8/9] refactor: Project Profitability report (cherry picked from commit 0359d184a59561d671cf8fa83ea2accae3f129a4) --- .../project_profitability.js | 2 +- .../project_profitability.py | 136 ++++++++++-------- 2 files changed, 75 insertions(+), 63 deletions(-) diff --git a/hrms/hr/report/project_profitability/project_profitability.js b/hrms/hr/report/project_profitability/project_profitability.js index 13ae19bb29..0d8156d3d7 100644 --- a/hrms/hr/report/project_profitability/project_profitability.js +++ b/hrms/hr/report/project_profitability/project_profitability.js @@ -27,7 +27,7 @@ frappe.query_reports["Project Profitability"] = { "default": frappe.datetime.now_date() }, { - "fieldname": "customer_name", + "fieldname": "customer", "label": __("Customer"), "fieldtype": "Link", "options": "Customer" diff --git a/hrms/hr/report/project_profitability/project_profitability.py b/hrms/hr/report/project_profitability/project_profitability.py index aa955bcc47..c524f0941b 100644 --- a/hrms/hr/report/project_profitability/project_profitability.py +++ b/hrms/hr/report/project_profitability/project_profitability.py @@ -3,7 +3,7 @@ import frappe from frappe import _ -from frappe.utils import flt +from frappe.utils import cint, flt def execute(filters=None): @@ -20,81 +20,93 @@ def get_data(filters): 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"), + Timesheet = frappe.qb.DocType("Timesheet") + SalarySlip = frappe.qb.DocType("Salary Slip") + SalesInvoice = frappe.qb.DocType("Sales Invoice") + SalesInvoiceTimesheet = frappe.qb.DocType("Sales Invoice Timesheet") + SalarySlipTimesheet = frappe.qb.DocType("Salary Slip Timesheet") + + query = ( + frappe.qb.from_(SalarySlipTimesheet) + .inner_join(Timesheet) + .on(SalarySlipTimesheet.time_sheet == Timesheet.name) + .inner_join(SalarySlip) + .on(SalarySlipTimesheet.parent == SalarySlip.name) + .inner_join(SalesInvoiceTimesheet) + .on(SalesInvoiceTimesheet.time_sheet == Timesheet.name) + .inner_join(SalesInvoice) + .on(SalesInvoiceTimesheet.parent == SalesInvoice.name) + .select( + SalesInvoice.customer_name, + SalesInvoice.base_grand_total, + SalesInvoice.name.as_("voucher_no"), + Timesheet.employee, + Timesheet.title.as_("employee_name"), + Timesheet.parent_project.as_("project"), + Timesheet.start_date, + Timesheet.end_date, + Timesheet.total_billed_hours, + Timesheet.name.as_("timesheet"), + SalarySlip.base_gross_pay, + SalarySlip.total_working_days, + Timesheet.total_billed_hours, ) - - 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 + .distinct() + .where((SalesInvoice.docstatus == 1) & (SalarySlip.docstatus == 1)) ) - 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")))) + query = query.where(Timesheet.company == filters.get("company")) if filters.get("start_date"): - conditions.append("`tabTimesheet`.start_date>='{0}'".format(filters.get("start_date"))) + query = query.where(Timesheet.start_date >= filters.get("start_date")) if filters.get("end_date"): - conditions.append("`tabTimesheet`.end_date<='{0}'".format(filters.get("end_date"))) + query = query.where(Timesheet.end_date <= 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("customer"): + query = query.where(SalesInvoice.customer == filters.get("customer")) if filters.get("employee"): - conditions.append( - "`tabTimesheet`.employee={0}".format(frappe.db.escape(filters.get("employee"))) - ) + query = query.where(Timesheet.employee == filters.get("employee")) if filters.get("project"): - conditions.append( - "`tabTimesheet`.parent_project={0}".format(frappe.db.escape(filters.get("project"))) + query = query.where(Timesheet.parent_project == filters.get("project")) + + return query.run(as_dict=True) + + +def calculate_cost_and_profit(data): + standard_working_hours = get_standard_working_hours() + precision = cint(frappe.db.get_default("float_precision")) or 2 + + for row in data: + row.utilization = flt( + flt(row.total_billed_hours) / (flt(row.total_working_days) * flt(standard_working_hours)), + precision, + ) + row.fractional_cost = flt(flt(row.base_gross_pay) * flt(row.utilization), precision) + + row.profit = flt( + flt(row.base_grand_total) - flt(row.base_gross_pay) * flt(row.utilization), precision + ) + + return data + + +def get_standard_working_hours() -> float | None: + standard_working_hours = frappe.db.get_single_value("HR Settings", "standard_working_hours") + if not standard_working_hours: + frappe.throw( + _( + "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"), + ) ) - conditions = " and ".join(conditions) - return conditions + return standard_working_hours def get_chart_data(data): @@ -105,7 +117,7 @@ def get_chart_data(data): utilization = [] for entry in data: - labels.append(entry.get("employee_name") + " - " + str(entry.get("end_date"))) + labels.append(f"{entry.get('employee_name')} - {entry.get('end_date')}") utilization.append(entry.get("utilization")) charts = { From 787ab3612fc47bf09220fae759d0f3a4ce66bd00 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 29 Dec 2023 14:18:07 +0530 Subject: [PATCH 9/9] feat: Add new doctype for Job offer Term Template (#1089) (#1237) * feat: Create new doctype for offer term * fix: Update job offer * feat: Add title field in term template * fix: add default perm for HR Manager, clear table on selecting template - fix naming, child table fieldnames should be plural --------- Co-authored-by: Rucha Mahabal (cherry picked from commit dfb97127969d99ea89abeb1b31b73e39712deee0) Co-authored-by: Niraj Gautam --- hrms/hr/doctype/job_offer/job_offer.js | 12 ++++ hrms/hr/doctype/job_offer/job_offer.json | 9 ++- .../job_offer_term_template/__init__.py | 0 .../job_offer_term_template.js | 8 +++ .../job_offer_term_template.json | 63 +++++++++++++++++++ .../job_offer_term_template.py | 9 +++ .../test_job_offer_term_template.py | 9 +++ 7 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 hrms/hr/doctype/job_offer_term_template/__init__.py create mode 100644 hrms/hr/doctype/job_offer_term_template/job_offer_term_template.js create mode 100644 hrms/hr/doctype/job_offer_term_template/job_offer_term_template.json create mode 100644 hrms/hr/doctype/job_offer_term_template/job_offer_term_template.py create mode 100644 hrms/hr/doctype/job_offer_term_template/test_job_offer_term_template.py diff --git a/hrms/hr/doctype/job_offer/job_offer.js b/hrms/hr/doctype/job_offer/job_offer.js index b246ed563e..a8b446f6d8 100755 --- a/hrms/hr/doctype/job_offer/job_offer.js +++ b/hrms/hr/doctype/job_offer/job_offer.js @@ -21,6 +21,18 @@ frappe.ui.form.on("Job Offer", { } }); }, + job_offer_term_template: function (frm) { + if (!frm.doc.job_offer_term_template) return; + + frappe.db.get_doc("Job Offer Term Template", frm.doc.job_offer_term_template).then((doc) => { + frm.clear_table("offer_terms"); + doc.offer_terms.forEach((term) => { + frm.add_child("offer_terms", term); + }) + refresh_field("offer_terms"); + }) + + }, refresh: function (frm) { if ((!frm.doc.__islocal) && (frm.doc.status == 'Accepted') diff --git a/hrms/hr/doctype/job_offer/job_offer.json b/hrms/hr/doctype/job_offer/job_offer.json index c0b7f69e1b..2e90323cdf 100644 --- a/hrms/hr/doctype/job_offer/job_offer.json +++ b/hrms/hr/doctype/job_offer/job_offer.json @@ -16,6 +16,7 @@ "designation", "company", "section_break_4", + "job_offer_term_template", "offer_terms", "section_break_14", "select_terms", @@ -156,11 +157,17 @@ "options": "Job Offer", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "job_offer_term_template", + "fieldtype": "Link", + "label": "Job Offer Term Template", + "options": "Job Offer Term Template" } ], "is_submittable": 1, "links": [], - "modified": "2020-06-25 00:56:24.756395", + "modified": "2023-11-21 13:41:22.022838", "modified_by": "Administrator", "module": "HR", "name": "Job Offer", diff --git a/hrms/hr/doctype/job_offer_term_template/__init__.py b/hrms/hr/doctype/job_offer_term_template/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/hrms/hr/doctype/job_offer_term_template/job_offer_term_template.js b/hrms/hr/doctype/job_offer_term_template/job_offer_term_template.js new file mode 100644 index 0000000000..cc12c42725 --- /dev/null +++ b/hrms/hr/doctype/job_offer_term_template/job_offer_term_template.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Job Offer Term Template", { +// refresh(frm) { + +// }, +// }); diff --git a/hrms/hr/doctype/job_offer_term_template/job_offer_term_template.json b/hrms/hr/doctype/job_offer_term_template/job_offer_term_template.json new file mode 100644 index 0000000000..a768a47452 --- /dev/null +++ b/hrms/hr/doctype/job_offer_term_template/job_offer_term_template.json @@ -0,0 +1,63 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:title", + "creation": "2023-11-21 13:39:20.882706", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "title", + "offer_terms" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "unique": 1 + }, + { + "fieldname": "offer_terms", + "fieldtype": "Table", + "label": "Offer Terms", + "options": "Job Offer Term" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2023-12-29 13:08:24.269275", + "modified_by": "Administrator", + "module": "HR", + "name": "Job Offer Term Template", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/hrms/hr/doctype/job_offer_term_template/job_offer_term_template.py b/hrms/hr/doctype/job_offer_term_template/job_offer_term_template.py new file mode 100644 index 0000000000..e6d0ca50ea --- /dev/null +++ b/hrms/hr/doctype/job_offer_term_template/job_offer_term_template.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class JobOfferTermTemplate(Document): + pass diff --git a/hrms/hr/doctype/job_offer_term_template/test_job_offer_term_template.py b/hrms/hr/doctype/job_offer_term_template/test_job_offer_term_template.py new file mode 100644 index 0000000000..0da68ec2a8 --- /dev/null +++ b/hrms/hr/doctype/job_offer_term_template/test_job_offer_term_template.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestJobOfferTermTemplate(FrappeTestCase): + pass