From 9d544c9b385f22c8e0cc775254b8578870113936 Mon Sep 17 00:00:00 2001 From: Ebuka Date: Thu, 22 Aug 2024 13:14:48 +0100 Subject: [PATCH 01/10] Fix Leave Application Approval Error --- one_fm/api/tasks.py | 14 ++++++++++++++ one_fm/overrides/leave_application.py | 15 ++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/one_fm/api/tasks.py b/one_fm/api/tasks.py index d23b504b95..7819840b3b 100644 --- a/one_fm/api/tasks.py +++ b/one_fm/api/tasks.py @@ -1,9 +1,11 @@ import itertools from string import Template from calendar import month, monthrange +from erpnext.crm.utils import get_open_todos from datetime import datetime, timedelta from frappe import enqueue import frappe, erpnext +from frappe.desk.form.assign_to import remove from frappe import _ from frappe.model.workflow import apply_workflow from frappe.utils import ( @@ -368,6 +370,18 @@ def checkin_checkout_query(date, shift_type, log_type): """.format(date=cstr(date), shift_type=shift_type, log_type=log_type, permission_type=permission_type), as_dict=1) return query + + +def remove_assignment(attendance_check,doctype=None): + """Remove all usesr assignments in a document, + It defaults to attendance check if no doctype is set""" + if not doctype: + doctype="Attendance Check" + open_todo = get_open_todos(doctype,attendance_check) + if open_todo: + for each in open_todo: + remove("Attendance Check",attendance_check,each.allocated_to,ignore_permissions=1) + @frappe.whitelist() def get_action_user(employee, shift): """ diff --git a/one_fm/overrides/leave_application.py b/one_fm/overrides/leave_application.py index 24eb7599ea..c060db5779 100644 --- a/one_fm/overrides/leave_application.py +++ b/one_fm/overrides/leave_application.py @@ -1,9 +1,11 @@ from ast import literal_eval import frappe,json + from frappe import _ from frappe.utils import get_fullname, nowdate, add_to_date from hrms.hr.doctype.leave_application.leave_application import * from one_fm.processor import sendemail + from frappe.desk.form.assign_to import add from one_fm.api.notification import create_notification_log from frappe.utils import getdate, date_diff @@ -11,10 +13,13 @@ from one_fm.api.api import push_notification_rest_api_for_leave_application from one_fm.processor import is_user_id_company_prefred_email_in_employee from hrms.hr.utils import get_holidays_for_employee -from one_fm.api.tasks import get_action_user +from one_fm.api.tasks import get_action_user,remove_assignment from .employee import NotifyAttendanceManagerOnStatusChange + + + def close_leaves(leave_ids, user=None): approved_leaves = leave_ids not_approved_leaves = [] @@ -385,10 +390,10 @@ def validate_attendance_check(self): att_checks = frappe.get_all("Attendance Check",{'docstatus':0,'employee':self.employee, 'date': ['between', (getdate(self.from_date), getdate(self.to_date))]},['name']) if att_checks: for each in att_checks: - att_check = frappe.get_doc("Attendance Check",each.name) - att_check.attendance_status= 'On Leave' - att_check.workflow_state = "Approved" - att_check.submit() + frappe.db.set_value("Attendance Check",each.name,'workflow_state','Approved') + frappe.db.set_value("Attendance Check",each.name,'attendance_status','On Leave') + frappe.db.set_value("Attendance Check",each.name,'docstatus',1) + remove_assignment(each.name) frappe.db.commit() except: frappe.log_error(title = "Error Updating Attendance Check",message=frappe.get_traceback()) From 7824f92f163b83517e80d9ead78aece3ec3c54a2 Mon Sep 17 00:00:00 2001 From: Ebuka Date: Sun, 25 Aug 2024 16:13:05 +0100 Subject: [PATCH 02/10] Minor update to remove redundant data --- one_fm/one_fm/doctype/google_sheet_data_export/exporter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/one_fm/one_fm/doctype/google_sheet_data_export/exporter.py b/one_fm/one_fm/doctype/google_sheet_data_export/exporter.py index aedd3cd83e..d92b38991e 100644 --- a/one_fm/one_fm/doctype/google_sheet_data_export/exporter.py +++ b/one_fm/one_fm/doctype/google_sheet_data_export/exporter.py @@ -300,6 +300,7 @@ def add_data(self): .select("*") .where(child_doctype_table.parent == doc.name) .where(child_doctype_table.parentfield == c["parentfield"]) + .where(child_doctype_table.parenttype == self.doctype) .orderby(child_doctype_table.idx) ) for ci, child in enumerate(data_row.run(as_dict=True)): From 27f964ce22d6198d5f1569fcab9332664867c4cf Mon Sep 17 00:00:00 2001 From: Ebuka Date: Mon, 26 Aug 2024 08:02:38 +0100 Subject: [PATCH 03/10] resolve remove assignment method --- one_fm/api/tasks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/one_fm/api/tasks.py b/one_fm/api/tasks.py index 7819840b3b..76ba2da39e 100644 --- a/one_fm/api/tasks.py +++ b/one_fm/api/tasks.py @@ -372,15 +372,15 @@ def checkin_checkout_query(date, shift_type, log_type): -def remove_assignment(attendance_check,doctype=None): - """Remove all usesr assignments in a document, +def remove_assignment(docname,doctype=None): + """Remove all user assignments in a document, It defaults to attendance check if no doctype is set""" if not doctype: doctype="Attendance Check" - open_todo = get_open_todos(doctype,attendance_check) + open_todo = get_open_todos(doctype,docname) if open_todo: for each in open_todo: - remove("Attendance Check",attendance_check,each.allocated_to,ignore_permissions=1) + remove(doctype,docname,each.allocated_to,ignore_permissions=1) @frappe.whitelist() def get_action_user(employee, shift): From 3180a7dc4395fcce082f13b2c09dfb0ad31bbc84 Mon Sep 17 00:00:00 2001 From: Muhammad Ibtesam Arshad Date: Mon, 26 Aug 2024 14:59:56 +0500 Subject: [PATCH 04/10] feat: update employee passport details from work permit --- .../grd/doctype/work_permit/work_permit.json | 43 ++++++++++++++++--- one_fm/grd/doctype/work_permit/work_permit.py | 21 +++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/one_fm/grd/doctype/work_permit/work_permit.json b/one_fm/grd/doctype/work_permit/work_permit.json index 514a314fe2..4c9a074a90 100644 --- a/one_fm/grd/doctype/work_permit/work_permit.json +++ b/one_fm/grd/doctype/work_permit/work_permit.json @@ -94,6 +94,12 @@ "salary_certificate", "column_break_64", "iqrar", + "new_passport_details_section", + "new_passport_type", + "column_break_yjma", + "new_passport_number", + "column_break_jgpb", + "new_passport_expiry_date", "documents_required_section", "documents_required", "amended_from", @@ -573,16 +579,13 @@ "read_only": 1 }, { - "allow_on_submit": 1, "fetch_from": "employee.one_fm_passport_type", "fieldname": "passport_type", "fieldtype": "Data", "label": "Passport Type", - "read_only": 1, - "translatable": 1 + "read_only": 1 }, { - "allow_on_submit": 1, "fetch_from": "employee.passport_number", "fieldname": "passport_number", "fieldtype": "Data", @@ -892,11 +895,41 @@ "options": "User", "print_hide": 1, "read_only": 1 + }, + { + "description": "The details added in this section will update the employee passport details in Employee doctype", + "fieldname": "new_passport_details_section", + "fieldtype": "Section Break", + "label": "New Passport Details" + }, + { + "fieldname": "new_passport_type", + "fieldtype": "Select", + "label": "Passport Type", + "options": "\nDiplomat\nNormal" + }, + { + "fieldname": "column_break_yjma", + "fieldtype": "Column Break" + }, + { + "fieldname": "new_passport_number", + "fieldtype": "Data", + "label": "Passport Number" + }, + { + "fieldname": "column_break_jgpb", + "fieldtype": "Column Break" + }, + { + "fieldname": "new_passport_expiry_date", + "fieldtype": "Date", + "label": "Passport Expiry Date" } ], "is_submittable": 1, "links": [], - "modified": "2024-04-24 12:20:03.593026", + "modified": "2024-08-26 10:15:55.860237", "modified_by": "Administrator", "module": "GRD", "name": "Work Permit", diff --git a/one_fm/grd/doctype/work_permit/work_permit.py b/one_fm/grd/doctype/work_permit/work_permit.py index 6a08fd8900..284a1e104c 100644 --- a/one_fm/grd/doctype/work_permit/work_permit.py +++ b/one_fm/grd/doctype/work_permit/work_permit.py @@ -27,6 +27,7 @@ class WorkPermit(Document): def on_update(self): self.update_work_permit_details_in_tp() + self.update_passport_details_in_employee() self.check_required_document_for_workflow() self.notify() self.send_work_permit_receipt_to_perm_operator() @@ -199,6 +200,26 @@ def update_work_permit_details_in_tp(self): tp.save() tp.reload() + + def update_passport_details_in_employee(self): + """ + runs: `on_update` + param: work_permit object + + This method sets employee passport details in employee doctype + """ + updated_values = {} + + if self.new_passport_type: + updated_values['one_fm_passport_type'] = self.new_passport_type + if self.new_passport_number: + updated_values['passport_number'] = self.new_passport_number + if self.new_passport_expiry_date: + updated_values['valid_upto'] = self.new_passport_expiry_date + + if self.employee and updated_values: + frappe.db.set_value('Employee', self.employee, updated_values) + def on_submit(self): if self.work_permit_type not in ['Cancellation', 'New Kuwaiti', 'Local Transfer'] and self.workflow_state != "Rejected": if self.workflow_state == "Completed" and self.upload_work_permit and self.attach_invoice and self.new_work_permit_expiry_date: From 49b6dcfca9debb9ced744d4081de04e09f75d626 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Mon, 26 Aug 2024 10:11:04 +0000 Subject: [PATCH 05/10] feat: Add On-hold attendance employees to Missing payroll information doctype --- one_fm/overrides/payroll_entry.py | 177 ++++++++++++++++++++++-------- 1 file changed, 133 insertions(+), 44 deletions(-) diff --git a/one_fm/overrides/payroll_entry.py b/one_fm/overrides/payroll_entry.py index f4231a60b8..44de11f946 100644 --- a/one_fm/overrides/payroll_entry.py +++ b/one_fm/overrides/payroll_entry.py @@ -48,7 +48,6 @@ def fill_employee_details(self): # Custom method to fetch Bank Details and update employee list set_bank_details(self, employees) - self.set("employees", employees) self.number_of_employees = len(self.employees) @@ -77,7 +76,8 @@ def make_filters(self): project=project, custom_payroll_cycle_start_date=getdate(self.start_date).day, custom_payroll_cycle_end_date=getdate(self.end_date).day, - payroll_type=self.payroll_type + payroll_type=self.payroll_type, + payroll_entry=self.name ) if not self.salary_slip_based_on_timesheet: @@ -91,7 +91,7 @@ def get_start_end_dates(payroll_frequency, start_date=None, company=None): start_date, end_date = None, None if payroll_frequency == "Monthly": #fetch Payroll date's day - date = frappe.db.get_single_value('HR and Payroll Additional Settings', 'payroll_date') + date = frappe.db.get_single_value("HR and Payroll Additional Settings", "payroll_date") year = getdate().year - 1 if getdate().day < cint(date) and getdate().month == 1 else getdate().year month = getdate().month if getdate().day >= cint(date) else getdate().month - 1 @@ -135,6 +135,18 @@ def get_employee_list( ignore_match_conditions=ignore_match_conditions, ) + set_on_hold_employees( + sal_struct, + filters, + searchfield, + search_string, + fields, + as_dict=as_dict, + limit=limit, + offset=offset, + ignore_match_conditions=ignore_match_conditions, + ) + if as_dict: employees_to_check = {emp.employee: emp for emp in emp_list} else: @@ -229,7 +241,6 @@ def set_filter_conditions(query, filters, qb_object): return query -@frappe.whitelist() def set_bank_details(self, employee_details): """This Funtion Sets the bank Details of an employee. The data is fetched from Bank Account Doctype. @@ -240,22 +251,11 @@ def set_bank_details(self, employee_details): employee_details ([dict): Sets the bank account IBAN code and Bank Code. """ employee_missing_detail = [] - # missing_ssa = [] - # employee_list = ', '.join(['"{}"'.format(employee.employee) for employee in employee_details]) - # if employee_list: - # missing_ssa = frappe.db.sql(""" - # SELECT employee from `tabEmployee` E - # WHERE E.status = 'Active' - # AND E.employment_type != 'Subcontractor' - # AND E.name NOT IN ( - # SELECT employee from `tabSalary Structure Assignment` SE - # ) - # """.format(employee_list), as_dict=True) for employee in employee_details: try: bank_account = frappe.db.get_value("Bank Account",{"party":employee.employee},["iban","bank", "bank_account_no"]) - salary_mode = frappe.db.get_value("Employee", {'name': employee.employee}, ["salary_mode"]) + salary_mode = frappe.db.get_value("Employee", {"name": employee.employee}, ["salary_mode"]) if bank_account: iban, bank, bank_account_no = bank_account else: @@ -263,63 +263,152 @@ def set_bank_details(self, employee_details): if not salary_mode: employee_missing_detail.append(frappe._dict( - {'employee':employee.employee, 'salary_mode':'', 'issue':'No salary mode'})) - elif(salary_mode=='Bank' and bank is None): + {"employee":employee.employee, "salary_mode":"", "issue":"No salary mode"})) + elif(salary_mode=="Bank" and bank is None): employee_missing_detail.append(frappe._dict( - {'employee':employee.employee, 'salary_mode':salary_mode, 'issue':'No bank account'})) + {"employee":employee.employee, "salary_mode":salary_mode, "issue":"No bank account"})) elif(salary_mode=="Bank" and bank_account_no is None): employee_missing_detail.append(frappe._dict( - {'employee':employee.employee, 'salary_mode':salary_mode, 'issue':'No account no.'})) + {"employee":employee.employee, "salary_mode":salary_mode, "issue":"No account no."})) employee.salary_mode = salary_mode employee.iban_number = iban or bank_account_no - bank_code = frappe.db.get_value("Bank", {'name': bank}, ["bank_code"]) + bank_code = frappe.db.get_value("Bank", {"name": bank}, ["bank_code"]) employee.bank_code = bank_code except Exception as e: - frappe.log_error(str(e), 'Payroll Entry') + frappe.log_error(str(e), "Payroll Entry") frappe.throw(str(e)) - # if len(missing_ssa) > 0: - # for e in missing_ssa: - # employee_missing_detail.append(frappe._dict( - # {'employee':e.employee, 'salary_mode':'', 'issue':'No Salary Structure Assignment'})) - # check for missing details, log and report if len(employee_missing_detail) > 0: missing_detail = [] for i in employee_missing_detail: missing_detail.append({ - 'employee':i.employee, - 'salary_mode':i.salary_mode, - 'issue': i.issue + "employee":i.employee, + "salary_mode":i.salary_mode, + "issue": i.issue }) - mpi_exists = frappe.db.exists("Missing Payroll Information",{'payroll_entry': self.name}) + mpi_exists = frappe.db.exists("Missing Payroll Information",{"payroll_entry": self.name}) if mpi_exists: - mpi = frappe.get_doc('Missing Payroll Information', mpi_exists) + mpi = frappe.get_doc("Missing Payroll Information", mpi_exists) # delete previous table data - frappe.db.sql(f""" - DELETE FROM `tabMissing Payroll Information Detail` - WHERE parent="{mpi.name}" - ;""") - mpi.reload() + # frappe.db.sql(f""" + # DELETE FROM `tabMissing Payroll Information Detail` + # WHERE parent="{mpi.name}" + # ;""") + # mpi.reload() for i in missing_detail: - mpi.append('missing_payroll_information_detail', i) + mpi.append("missing_payroll_information_detail", i) mpi.save(ignore_permissions=True) frappe.db.commit() else: mpi = frappe.get_doc({ - 'doctype':"Missing Payroll Information", - 'payroll_entry': self.name, - 'missing_payroll_information_detail':missing_detail + "doctype":"Missing Payroll Information", + "payroll_entry": self.name, + "missing_payroll_information_detail":missing_detail }).insert(ignore_permissions=True) frappe.db.commit() # generate html template to show to user screen message = frappe.render_template( - 'one_fm/api/doc_methods/templates/payroll/bank_issue.html', - context={'employees':employee_missing_detail, 'mpi':mpi} + "one_fm/api/doc_methods/templates/payroll/bank_issue.html", + context={"employees":employee_missing_detail, "mpi":mpi} ) frappe.throw(_(message)) - return employee_details \ No newline at end of file + return employee_details + + +@frappe.whitelist() +def set_on_hold_employees( + sal_struct, + filters, + searchfield=None, + search_string=None, + fields=None, + as_dict=False, + limit=None, + offset=None, + ignore_match_conditions=False, +) -> list: + SalaryStructureAssignment = frappe.qb.DocType("Salary Structure Assignment") + Employee = frappe.qb.DocType("Employee") + Attendance = frappe.qb.DocType("Attendance") + + attendance_sub_query = None + if filters.payroll_type == "Basic": + attendance_sub_query = Employee.employee.isin( + frappe.qb.from_(Attendance) + .select(Attendance.employee) + .where( + (Attendance.attendance_date[filters.start_date:filters.end_date]) + & (Attendance.status == "On Hold") + & (Attendance.roster_type == filters.payroll_type) + ) + ) + elif filters.payroll_type == "Over-Time": + attendance_sub_query = Employee.employee.isin( + frappe.qb.from_(Attendance) + .select(Attendance.employee) + .where( + (Attendance.attendance_date[filters.start_date:filters.end_date]) + & (Attendance.status == "On Hold") + & (Attendance.roster_type == filters.payroll_type) + ) + ) + + query = ( + frappe.qb.from_(Employee) + .join(SalaryStructureAssignment) + .on(Employee.name == SalaryStructureAssignment.employee) + .where( + (SalaryStructureAssignment.docstatus == 1) + & (Employee.status.isin(["Active", "Vacation"])) + & (Employee.company == filters.company) + & ((Employee.date_of_joining <= filters.end_date) | (Employee.date_of_joining.isnull())) + & ((Employee.relieving_date >= filters.start_date) | (Employee.relieving_date.isnull())) + & (SalaryStructureAssignment.salary_structure.isin(sal_struct)) + & (SalaryStructureAssignment.payroll_payable_account == filters.payroll_payable_account) + & (filters.end_date >= SalaryStructureAssignment.from_date) + & (attendance_sub_query) + )) + + query = set_fields_to_select(query, fields) + query = set_searchfield(query, searchfield, search_string, qb_object=Employee) + query = set_filter_conditions(query, filters, qb_object=Employee) + + if not ignore_match_conditions: + query = set_match_conditions(query=query, qb_object=Employee) + + if limit: + query = query.limit(limit) + + if offset: + query = query.offset(offset) + + employees = query.run(as_dict=as_dict) + mpi_exists = frappe.db.exists("Missing Payroll Information", { "payroll_entry": filters.payroll_entry}) + + missing_payroll_info = [] + for employee in employees: + missing_payroll_info.append({ + "employee": employee.employee, + "salary_mode": "N/A", + "issue": "On-Hold attendance records" + }) + + if mpi_exists: + mpi = frappe.get_doc("Missing Payroll Information", mpi_exists) + for info in missing_payroll_info: + mpi.append("missing_payroll_information_detail", info) + mpi.save(ignore_permissions=True) + + else: + mpi = frappe.get_doc({ + "doctype":"Missing Payroll Information", + "payroll_entry": filters.payroll_entry, + "missing_payroll_information_detail": missing_payroll_info + }).insert(ignore_permissions=True) + + frappe.db.commit() \ No newline at end of file From a1aae8ae6acc3f8375fddd2dbd6b77ba45a5141d Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Mon, 26 Aug 2024 12:32:04 +0000 Subject: [PATCH 06/10] fix: Redundant code --- one_fm/overrides/payroll_entry.py | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/one_fm/overrides/payroll_entry.py b/one_fm/overrides/payroll_entry.py index 44de11f946..4caf7a5752 100644 --- a/one_fm/overrides/payroll_entry.py +++ b/one_fm/overrides/payroll_entry.py @@ -336,27 +336,15 @@ def set_on_hold_employees( Employee = frappe.qb.DocType("Employee") Attendance = frappe.qb.DocType("Attendance") - attendance_sub_query = None - if filters.payroll_type == "Basic": - attendance_sub_query = Employee.employee.isin( - frappe.qb.from_(Attendance) - .select(Attendance.employee) - .where( - (Attendance.attendance_date[filters.start_date:filters.end_date]) - & (Attendance.status == "On Hold") - & (Attendance.roster_type == filters.payroll_type) - ) - ) - elif filters.payroll_type == "Over-Time": - attendance_sub_query = Employee.employee.isin( - frappe.qb.from_(Attendance) - .select(Attendance.employee) - .where( - (Attendance.attendance_date[filters.start_date:filters.end_date]) - & (Attendance.status == "On Hold") - & (Attendance.roster_type == filters.payroll_type) - ) + attendance_sub_query = Employee.employee.isin( + frappe.qb.from_(Attendance) + .select(Attendance.employee) + .where( + (Attendance.attendance_date[filters.start_date:filters.end_date]) + & (Attendance.status == "On Hold") + & (Attendance.roster_type == filters.payroll_type) ) + ) query = ( frappe.qb.from_(Employee) From 22b97d669a0569694f817389e5c10827662a0481 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Tue, 27 Aug 2024 06:59:16 +0000 Subject: [PATCH 07/10] feat: Add Missing Payroll Information to Payroll Entry connections tab --- one_fm/one_fm/custom/payroll_entry.json | 107 +++++++++++++++++------- 1 file changed, 77 insertions(+), 30 deletions(-) diff --git a/one_fm/one_fm/custom/payroll_entry.json b/one_fm/one_fm/custom/payroll_entry.json index afd4605299..21654721d0 100644 --- a/one_fm/one_fm/custom/payroll_entry.json +++ b/one_fm/one_fm/custom/payroll_entry.json @@ -157,7 +157,7 @@ "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 10, + "idx": 11, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, @@ -267,14 +267,61 @@ ], "custom_perms": [], "doctype": "Payroll Entry", - "links": [], + "links": [ + { + "creation": "2024-08-27 09:55:29.539844", + "custom": 1, + "docstatus": 0, + "group": null, + "hidden": 0, + "idx": 0, + "is_child_table": 0, + "link_doctype": "Missing Payroll Information", + "link_fieldname": "payroll_entry", + "modified": "2024-08-27 09:55:29.539844", + "modified_by": "Administrator", + "name": "dcug485kh2", + "owner": "Administrator", + "parent": "Payroll Entry", + "parent_doctype": null, + "parentfield": "links", + "parenttype": "Customize Form", + "table_fieldname": null + } + ], "property_setters": [ { "_assign": null, "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2023-11-16 14:49:40.396485", + "creation": "2024-08-27 09:55:30.066461", + "default_value": null, + "doc_type": "Payroll Entry", + "docstatus": 0, + "doctype_or_field": "DocType", + "field_name": null, + "idx": 0, + "is_system_generated": 0, + "modified": "2024-08-27 09:55:30.066461", + "modified_by": "Administrator", + "module": null, + "name": "Payroll Entry-main-links_order", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "links_order", + "property_type": "Small Text", + "row_name": null, + "value": "[\"dcug485kh2\"]" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2024-08-27 09:55:27.836379", "default_value": null, "doc_type": "Payroll Entry", "docstatus": 0, @@ -282,7 +329,7 @@ "field_name": null, "idx": 0, "is_system_generated": 0, - "modified": "2024-08-13 11:58:46.748368", + "modified": "2024-08-27 09:55:27.836379", "modified_by": "Administrator", "module": null, "name": "Payroll Entry-main-field_order", @@ -293,51 +340,51 @@ "property": "field_order", "property_type": "Data", "row_name": null, - "value": "[\"select_payroll_period\", \"posting_date\", \"company\", \"payment_purpose\", \"column_break_5\", \"currency\", \"exchange_rate\", \"payroll_payable_account\", \"status\", \"section_break_cypo\", \"payroll_type\", \"payroll_frequency\", \"start_date\", \"end_date\", \"column_break_13\", \"salary_slip_based_on_timesheet\", \"deduct_tax_for_unclaimed_employee_benefits\", \"deduct_tax_for_unsubmitted_tax_exemption_proof\", \"employees_tab\", \"section_break_17\", \"branch\", \"department\", \"column_break_21\", \"designation\", \"number_of_employees\", \"section_break_24\", \"employees\", \"section_break_26\", \"validate_attendance\", \"attendance_detail_html\", \"accounting_dimensions_tab\", \"accounting_dimensions_section\", \"cost_center\", \"dimension_col_break\", \"project\", \"account\", \"payment_account\", \"column_break_35\", \"bank_account\", \"salary_slips_created\", \"salary_slips_submitted\", \"failure_details_section\", \"error_message\", \"section_break_41\", \"amended_from\", \"connections_tab\"]" + "value": "[\"select_payroll_period\", \"posting_date\", \"company\", \"payment_purpose\", \"column_break_5\", \"currency\", \"exchange_rate\", \"payroll_payable_account\", \"status\", \"section_break_cypo\", \"payroll_type\", \"payroll_frequency\", \"start_date\", \"end_date\", \"column_break_13\", \"salary_slip_based_on_timesheet\", \"deduct_tax_for_unclaimed_employee_benefits\", \"deduct_tax_for_unsubmitted_tax_exemption_proof\", \"employees_tab\", \"section_break_17\", \"branch\", \"department\", \"custom_project_configuration\", \"custom_project_filter\", \"column_break_21\", \"designation\", \"grade\", \"number_of_employees\", \"section_break_24\", \"employees\", \"section_break_26\", \"validate_attendance\", \"attendance_detail_html\", \"accounting_dimensions_tab\", \"accounting_dimensions_section\", \"cost_center\", \"dimension_col_break\", \"project\", \"account\", \"payment_account\", \"column_break_35\", \"bank_account\", \"salary_slips_created\", \"salary_slips_submitted\", \"failure_details_section\", \"error_message\", \"section_break_41\", \"amended_from\", \"connections_tab\"]" }, { "_assign": null, "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2024-08-13 11:56:12.018201", + "creation": "2024-08-13 11:55:57.918303", "default_value": null, "doc_type": "Payroll Entry", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "payroll_frequency", + "field_name": "branch", "idx": 0, "is_system_generated": 0, - "modified": "2024-08-05 15:25:04.412492", + "modified": "2024-08-26 13:00:33.833763", "modified_by": "Administrator", - "module": "One Fm", - "name": "Payroll Entry-payroll_frequency-options", + "module": null, + "name": "Payroll Entry-branch-hidden", "owner": "Administrator", "parent": null, "parentfield": null, "parenttype": null, - "property": "options", - "property_type": "select", + "property": "hidden", + "property_type": "Check", "row_name": null, - "value": "\nMonthly" + "value": "1" }, { "_assign": null, "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2024-08-13 11:55:58.104054", + "creation": "2024-08-13 11:55:57.965212", "default_value": null, "doc_type": "Payroll Entry", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "grade", + "field_name": "department", "idx": 0, "is_system_generated": 0, - "modified": "2024-08-05 12:36:51.244898", + "modified": "2024-08-26 13:00:33.650134", "modified_by": "Administrator", "module": null, - "name": "Payroll Entry-grade-hidden", + "name": "Payroll Entry-department-hidden", "owner": "Administrator", "parent": null, "parentfield": null, @@ -360,7 +407,7 @@ "field_name": "designation", "idx": 0, "is_system_generated": 0, - "modified": "2024-08-05 12:36:50.839069", + "modified": "2024-08-26 13:00:33.499532", "modified_by": "Administrator", "module": null, "name": "Payroll Entry-designation-hidden", @@ -378,18 +425,18 @@ "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2024-08-13 11:55:57.965212", + "creation": "2024-08-13 11:55:58.104054", "default_value": null, "doc_type": "Payroll Entry", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "department", + "field_name": "grade", "idx": 0, "is_system_generated": 0, - "modified": "2024-08-05 12:36:50.547681", + "modified": "2024-08-26 13:00:33.270135", "modified_by": "Administrator", "module": null, - "name": "Payroll Entry-department-hidden", + "name": "Payroll Entry-grade-hidden", "owner": "Administrator", "parent": null, "parentfield": null, @@ -404,26 +451,26 @@ "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2024-08-13 11:55:57.918303", + "creation": "2024-08-13 11:56:12.018201", "default_value": null, "doc_type": "Payroll Entry", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "branch", + "field_name": "payroll_frequency", "idx": 0, "is_system_generated": 0, - "modified": "2024-08-05 12:36:49.870901", + "modified": "2024-08-26 13:00:32.987214", "modified_by": "Administrator", - "module": null, - "name": "Payroll Entry-branch-hidden", + "module": "One Fm", + "name": "Payroll Entry-payroll_frequency-options", "owner": "Administrator", "parent": null, "parentfield": null, "parenttype": null, - "property": "hidden", - "property_type": "Check", + "property": "options", + "property_type": "select", "row_name": null, - "value": "1" + "value": "\nMonthly" } ], "sync_on_migrate": 1 From 5e3d5a3310d6a0c48dcf0975aa1f0f7ed1eb64fe Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Tue, 27 Aug 2024 09:17:26 +0000 Subject: [PATCH 08/10] fix: Set on-hold employees --- one_fm/overrides/payroll_entry.py | 51 +++++++++++++++---------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/one_fm/overrides/payroll_entry.py b/one_fm/overrides/payroll_entry.py index 4caf7a5752..306395457c 100644 --- a/one_fm/overrides/payroll_entry.py +++ b/one_fm/overrides/payroll_entry.py @@ -5,10 +5,8 @@ from frappe.utils import ( add_to_date, cint, - getdate, + getdate ) -from frappe.query_builder.functions import Coalesce, Count - from hrms.payroll.doctype.payroll_entry.payroll_entry import ( PayrollEntry, get_employee_list, get_salary_structure, remove_payrolled_employees, set_fields_to_select, set_searchfield, set_match_conditions) @@ -376,27 +374,28 @@ def set_on_hold_employees( query = query.offset(offset) employees = query.run(as_dict=as_dict) - mpi_exists = frappe.db.exists("Missing Payroll Information", { "payroll_entry": filters.payroll_entry}) - - missing_payroll_info = [] - for employee in employees: - missing_payroll_info.append({ - "employee": employee.employee, - "salary_mode": "N/A", - "issue": "On-Hold attendance records" - }) - - if mpi_exists: - mpi = frappe.get_doc("Missing Payroll Information", mpi_exists) - for info in missing_payroll_info: - mpi.append("missing_payroll_information_detail", info) - mpi.save(ignore_permissions=True) - else: - mpi = frappe.get_doc({ - "doctype":"Missing Payroll Information", - "payroll_entry": filters.payroll_entry, - "missing_payroll_information_detail": missing_payroll_info - }).insert(ignore_permissions=True) - - frappe.db.commit() \ No newline at end of file + if len(employees) > 0: + mpi_exists = frappe.db.exists("Missing Payroll Information", { "payroll_entry": filters.payroll_entry}) + missing_payroll_info = [] + for employee in employees: + missing_payroll_info.append({ + "employee": employee.employee, + "salary_mode": "N/A", + "issue": "On-Hold attendance records" + }) + + if mpi_exists: + mpi = frappe.get_doc("Missing Payroll Information", mpi_exists) + for info in missing_payroll_info: + mpi.append("missing_payroll_information_detail", info) + mpi.save(ignore_permissions=True) + + else: + mpi = frappe.get_doc({ + "doctype":"Missing Payroll Information", + "payroll_entry": filters.payroll_entry, + "missing_payroll_information_detail": missing_payroll_info + }).insert(ignore_permissions=True) + + frappe.db.commit() \ No newline at end of file From fccf18cfbe53bb3f3349d1bcbdff767eb341fcd0 Mon Sep 17 00:00:00 2001 From: Muhammad Ibtesam Arshad Date: Tue, 27 Aug 2024 15:07:39 +0500 Subject: [PATCH 09/10] updated new civil id expiry date on validate --- one_fm/grd/doctype/paci/paci.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/one_fm/grd/doctype/paci/paci.py b/one_fm/grd/doctype/paci/paci.py index 453279793c..b728d0ca3f 100644 --- a/one_fm/grd/doctype/paci/paci.py +++ b/one_fm/grd/doctype/paci/paci.py @@ -16,6 +16,7 @@ class PACI(Document): def validate(self): self.set_grd_values() + self.set_new_expiry_date() def set_grd_values(self): @@ -25,6 +26,9 @@ def set_grd_values(self): self.grd_operator = frappe.db.get_value('GRD Settings', None, 'default_grd_operator') if not self.grd_operator_transfer: self.grd_operator_transfer = frappe.db.get_value('GRD Settings', None, 'default_grd_operator_transfer') + + def set_new_expiry_date(self): + self.new_civil_id_expiry_date = frappe.db.get_value("Employee", self.employee, "work_permit_expiry_date") def on_update(self): From 0e1c1d55a2a455d132509f72c6ab7a91b17943d7 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Tue, 27 Aug 2024 16:05:14 +0000 Subject: [PATCH 10/10] feat: Department wise Salary component account --- .../custom/salary_component_account.json | 102 ++++++++++++++++++ one_fm/overrides/payroll_entry.py | 74 ++++++++++++- 2 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 one_fm/one_fm/custom/salary_component_account.json diff --git a/one_fm/one_fm/custom/salary_component_account.json b/one_fm/one_fm/custom/salary_component_account.json new file mode 100644 index 0000000000..38d8f13a6a --- /dev/null +++ b/one_fm/one_fm/custom/salary_component_account.json @@ -0,0 +1,102 @@ +{ + "custom_fields": [ + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2024-08-26 14:25:46.059132", + "default": null, + "depends_on": null, + "description": "If left empty, will apply to all departments.", + "docstatus": 0, + "dt": "Salary Component Account", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "custom_department", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 3, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_preview": 1, + "in_standard_filter": 1, + "insert_after": "account", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Department", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2024-08-26 14:25:46.059132", + "modified_by": "Administrator", + "module": null, + "name": "Salary Component Account-custom_department", + "no_copy": 0, + "non_negative": 0, + "options": "Department", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + } + ], + "custom_perms": [], + "doctype": "Salary Component Account", + "links": [], + "property_setters": [ + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2024-08-26 14:26:30.843062", + "default_value": null, + "doc_type": "Salary Component Account", + "docstatus": 0, + "doctype_or_field": "DocType", + "field_name": null, + "idx": 0, + "is_system_generated": 0, + "modified": "2024-08-26 14:26:30.843062", + "modified_by": "Administrator", + "module": null, + "name": "Salary Component Account-main-field_order", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "field_order", + "property_type": "Data", + "row_name": null, + "value": "[\"company\", \"account\", \"custom_department\"]" + } + ], + "sync_on_migrate": 1 +} \ No newline at end of file diff --git a/one_fm/overrides/payroll_entry.py b/one_fm/overrides/payroll_entry.py index 306395457c..e405dd5f33 100644 --- a/one_fm/overrides/payroll_entry.py +++ b/one_fm/overrides/payroll_entry.py @@ -5,7 +5,9 @@ from frappe.utils import ( add_to_date, cint, - getdate + getdate, + get_link_to_form, + flt ) from hrms.payroll.doctype.payroll_entry.payroll_entry import ( PayrollEntry, get_employee_list, get_salary_structure, remove_payrolled_employees, set_fields_to_select, set_searchfield, set_match_conditions) @@ -13,6 +15,76 @@ class PayrollEntryOverride(PayrollEntry): + def get_salary_component_total( + self, + component_type=None, + employee_wise_accounting_enabled=False, + ): + salary_components = self.get_salary_components(component_type) + if salary_components: + component_dict = {} + + for item in salary_components: + if not self.should_add_component_to_accrual_jv(component_type, item): + continue + + department = frappe.db.get_value("Employee", item.employee, "department") + employee_cost_centers = self.get_payroll_cost_centers_for_employee( + item.employee, item.salary_structure + ) + employee_advance = self.get_advance_deduction(component_type, item) + + for cost_center, percentage in employee_cost_centers.items(): + amount_against_cost_center = flt(item.amount) * percentage / 100 + + if employee_advance: + self.add_advance_deduction_entry( + item, amount_against_cost_center, cost_center, employee_advance + ) + else: + key = (item.salary_component, cost_center, department) + component_dict[key] = component_dict.get(key, 0) + amount_against_cost_center + + if employee_wise_accounting_enabled: + self.set_employee_based_payroll_payable_entries( + component_type, item.employee, amount_against_cost_center + ) + + account_details = self.get_account(component_dict=component_dict) + + return account_details + + def get_account(self, component_dict=None): + account_dict = {} + for key, amount in component_dict.items(): + component, cost_center, department = key + account = self.get_salary_component_account(component, department) + accounting_key = (account, cost_center) + + account_dict[accounting_key] = account_dict.get(accounting_key, 0) + amount + + return account_dict + + def get_salary_component_account(self, salary_component, department): + account = frappe.db.get_value("Salary Component Account", + {"parent": salary_component, "company": self.company, "custom_department": department}, + "account", cache=True + ) + if not account: + account = frappe.db.get_value("Salary Component Account", + {"parent": salary_component, "company": self.company}, + "account", cache=True + ) + + if not account: + frappe.throw( + _("Please set account in Salary Component {0}").format( + get_link_to_form("Salary Component", salary_component) + ) + ) + + return account + @frappe.whitelist() def fill_employee_details(self): filters = self.make_filters()