diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 72648753a7..dc341d7ad7 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -66,6 +66,7 @@ class JournalEntry(AccountsController): self.update_expense_claim() self.update_inter_company_jv() self.update_invoice_discounting() + self.update_status_for_full_and_final_statement() check_if_stock_and_account_balance_synced(self.posting_date, self.company, self.doctype, self.name) @@ -83,6 +84,7 @@ class JournalEntry(AccountsController): self.unlink_inter_company_jv() self.unlink_asset_adjustment_entry() self.update_invoice_discounting() + self.update_status_for_full_and_final_statement() def get_title(self): return self.pay_to_recd_from or self.accounts[0].account @@ -98,6 +100,15 @@ class JournalEntry(AccountsController): for voucher_no in list(set(order_list)): frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid() + def update_status_for_full_and_final_statement(self): + for entry in self.accounts: + if entry.reference_type == "Full and Final Statement": + if self.docstatus == 1: + frappe.db.set_value("Full and Final Statement", entry.reference_name, "status", "Paid") + elif self.docstatus == 2: + frappe.db.set_value("Full and Final Statement", entry.reference_name, "status", "Unpaid") + + def validate_inter_company_accounts(self): if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference: doc = frappe.get_doc("Journal Entry", self.inter_company_journal_entry_reference) diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json index a89fefde07..dff883aef9 100644 --- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json +++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -202,7 +202,7 @@ "fieldname": "reference_type", "fieldtype": "Select", "label": "Reference Type", - "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees" + "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement" }, { "fieldname": "reference_name", @@ -280,7 +280,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-06-26 14:06:54.833738", + "modified": "2021-08-30 21:27:32.200299", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry Account", diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.js b/erpnext/hr/doctype/employee_referral/employee_referral.js index 9c99bbbefa..8722019fb1 100644 --- a/erpnext/hr/doctype/employee_referral/employee_referral.js +++ b/erpnext/hr/doctype/employee_referral/employee_referral.js @@ -43,8 +43,6 @@ frappe.ui.form.on("Employee Referral", { }); } - - }, create_job_applicant: function(frm) { frappe.model.open_mapped_doc({ diff --git a/erpnext/hr/doctype/full_and_final_asset/__init__.py b/erpnext/hr/doctype/full_and_final_asset/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.js b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.js new file mode 100644 index 0000000000..1965b46651 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Full and Final Asset', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.json b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.json new file mode 100644 index 0000000000..3ad8335049 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.json @@ -0,0 +1,64 @@ +{ + "actions": [], + "creation": "2021-06-28 13:36:58.658985", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "reference", + "asset_name", + "date", + "status", + "description" + ], + "fields": [ + { + "fieldname": "reference", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Reference", + "options": "Asset Movement", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Owned\nReturned", + "reqd": 1 + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + }, + { + "fieldname": "asset_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Asset Name", + "read_only": 1 + }, + { + "fieldname": "date", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Date", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-07-15 15:17:31.309834", + "modified_by": "Administrator", + "module": "HR", + "name": "Full and Final Asset", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.py b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.py new file mode 100644 index 0000000000..23372240c6 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class FullandFinalAsset(Document): + pass diff --git a/erpnext/hr/doctype/full_and_final_asset/test_full_and_final_asset.py b/erpnext/hr/doctype/full_and_final_asset/test_full_and_final_asset.py new file mode 100644 index 0000000000..6d59c99a16 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_asset/test_full_and_final_asset.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + +class TestFullandFinalAsset(unittest.TestCase): + pass diff --git a/erpnext/hr/doctype/full_and_final_outstanding_statement/__init__.py b/erpnext/hr/doctype/full_and_final_outstanding_statement/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.json b/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.json new file mode 100644 index 0000000000..be242e2c44 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.json @@ -0,0 +1,96 @@ +{ + "actions": [], + "creation": "2021-06-28 13:32:02.167317", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "component", + "reference_document_type", + "reference_document", + "account", + "paid_via_salary_slip", + "column_break_4", + "amount", + "status", + "remark" + ], + "fields": [ + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "columns": 2, + "default": "Unsettled", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Settled\nUnsettled" + }, + { + "fieldname": "remark", + "fieldtype": "Small Text", + "label": "Remark" + }, + { + "columns": 2, + "depends_on": "reference_document_type", + "fieldname": "reference_document", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Reference Document", + "mandatory_depends_on": "reference_document_type", + "options": "reference_document_type", + "search_index": 1 + }, + { + "columns": 2, + "fieldname": "component", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Component", + "reqd": 1 + }, + { + "fieldname": "account", + "fieldtype": "Link", + "label": "Account", + "options": "Account" + }, + { + "columns": 2, + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount" + }, + { + "columns": 2, + "fieldname": "reference_document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Reference Document Type", + "options": "DocType" + }, + { + "default": "0", + "fieldname": "paid_via_salary_slip", + "fieldtype": "Check", + "label": "Paid via Salary Slip" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-07-20 16:59:34.447934", + "modified_by": "Administrator", + "module": "HR", + "name": "Full and Final Outstanding Statement", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.py b/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.py new file mode 100644 index 0000000000..d53cd91642 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class FullandFinalOutstandingStatement(Document): + pass diff --git a/erpnext/hr/doctype/full_and_final_statement/__init__.py b/erpnext/hr/doctype/full_and_final_statement/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.js b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.js new file mode 100644 index 0000000000..074d85b709 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.js @@ -0,0 +1,115 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Full and Final Statement', { + refresh: function(frm) { + frm.events.set_queries(frm, "payables"); + frm.events.set_queries(frm, "receivables"); + + if (frm.doc.docstatus == 1 && frm.doc.status == "Unpaid") { + frm.add_custom_button(__("Create Journal Entry"), function () { + frm.events.create_journal_entry(frm); + }); + } + }, + + set_queries: function(frm, type) { + frm.set_query("reference_document_type", type, function () { + let modules = ["HR", "Payroll", "Loan Management"]; + return { + filters: { + istable: 0, + issingle: 0, + module: ["In", modules] + } + }; + }); + + let filters = {}; + + frm.set_query('reference_document', type, function(doc, cdt, cdn) { + let fnf_doc = frappe.get_doc(cdt, cdn); + + frappe.model.with_doctype(fnf_doc.reference_document_type, function() { + if (frappe.model.is_tree(fnf_doc.reference_document_type)) { + filters['is_group'] = 0; + } + + if (frappe.meta.has_field(fnf_doc.reference_document_type, 'company')) { + filters['company'] = frm.doc.company; + } + + if (frappe.meta.has_field(fnf_doc.reference_document_type, 'employee')) { + filters['employee'] = frm.doc.employee; + } + }); + + return { + filters: filters + }; + }); + }, + + employee: function(frm) { + frm.events.get_outstanding_statements(frm); + }, + + get_outstanding_statements: function(frm) { + if (frm.doc.employee) { + frappe.call({ + method: "get_outstanding_statements", + doc: frm.doc, + callback: function() { + frm.refresh(); + } + }); + } + }, + + create_journal_entry: function(frm) { + frappe.call({ + method: "create_journal_entry", + doc: frm.doc, + callback: function(r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + } +}); + +frappe.ui.form.on("Full and Final Outstanding Statement", { + reference_document: function(frm, cdt, cdn) { + var child = locals[cdt][cdn]; + if (child.reference_document_type && child.reference_document) { + frappe.call({ + method: "erpnext.hr.doctype.full_and_final_statement.full_and_final_statement.get_account_and_amount", + args: { + ref_doctype: child.reference_document_type, + ref_document: child.reference_document + }, + callback: function(r) { + if (r.message) { + frappe.model.set_value(cdt, cdn, "account", r.message[0]); + frappe.model.set_value(cdt, cdn, "amount", r.message[1]); + } + } + }); + } + }, + + amount: function(frm) { + var total_payable_amount = 0; + var total_receivable_amount = 0; + + frm.doc.payables.forEach(element => { + total_payable_amount = total_payable_amount + element.amount; + }); + + frm.doc.receivables.forEach(element => { + total_receivable_amount = total_receivable_amount + element.amount; + }); + frm.set_value("total_payable_amount", flt(total_payable_amount)); + frm.set_value("total_receivable_amount", flt(total_receivable_amount)); + } +}); diff --git a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.json b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.json new file mode 100644 index 0000000000..ebcf36dfd9 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.json @@ -0,0 +1,231 @@ +{ + "actions": [], + "autoname": "HR-FNF-.YYYY.-.#####", + "creation": "2021-06-28 13:17:36.050459", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "employee", + "employee_name", + "transaction_date", + "column_break_12", + "company", + "status", + "amended_from", + "employee_details_section", + "date_of_joining", + "relieving_date", + "column_break_4", + "designation", + "department", + "section_break_8", + "payables", + "section_break_10", + "receivables", + "totals_section", + "total_payable_amount", + "column_break_21", + "total_receivable_amount", + "section_break_15", + "assets_allocated" + ], + "fields": [ + { + "fieldname": "employee", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Employee", + "options": "Employee", + "reqd": 1 + }, + { + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "label": "Employee Name", + "read_only": 1 + }, + { + "fetch_from": "employee.designation", + "fieldname": "designation", + "fieldtype": "Link", + "label": "Designation", + "options": "Designation", + "read_only": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "default": "Unpaid", + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Paid\nUnpaid", + "read_only": 1 + }, + { + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Full and Final Statement", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "label": "Payables" + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break", + "label": "Receivables" + }, + { + "fieldname": "assets_allocated", + "fieldtype": "Table", + "options": "Full and Final Asset" + }, + { + "fetch_from": "employee.relieving_date", + "fieldname": "relieving_date", + "fieldtype": "Date", + "label": "Relieving Date ", + "read_only": 1, + "reqd": 1 + }, + { + "fetch_from": "employee.date_of_joining", + "fieldname": "date_of_joining", + "fieldtype": "Date", + "label": "Date of Joining", + "read_only": 1 + }, + { + "fieldname": "section_break_15", + "fieldtype": "Section Break", + "label": "Assets Allocated" + }, + { + "fetch_from": "employee.company", + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "read_only": 1 + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fieldname": "payables", + "fieldtype": "Table", + "options": "Full and Final Outstanding Statement" + }, + { + "fieldname": "receivables", + "fieldtype": "Table", + "options": "Full and Final Outstanding Statement" + }, + { + "fieldname": "employee_details_section", + "fieldtype": "Section Break", + "label": "Employee Details" + }, + { + "fieldname": "transaction_date", + "fieldtype": "Date", + "in_standard_filter": 1, + "label": "Transaction Date", + "reqd": 1 + }, + { + "fieldname": "totals_section", + "fieldtype": "Section Break", + "label": "Totals" + }, + { + "fieldname": "total_payable_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Total Payable Amount", + "read_only": 1 + }, + { + "fieldname": "column_break_21", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_receivable_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Total Receivable Amount", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-08-30 21:11:09.892560", + "modified_by": "Administrator", + "module": "HR", + "name": "Full and Final Statement", + "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 User", + "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", + "title_field": "employee_name", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py new file mode 100644 index 0000000000..4c98cf1adb --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py @@ -0,0 +1,176 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.utils import get_link_to_form, today, flt +from frappe.model.document import Document + +class FullandFinalStatement(Document): + def validate(self): + self.get_outstanding_statements() + if self.docstatus == 1: + self.validate_settlement("payables") + self.validate_settlement("receivables") + self.validate_asset() + + def validate_settlement(self, component_type): + for data in self.get(component_type, []): + if data.status == "Unsettled": + frappe.throw(_("Settle all Payables and Receivables before submission")) + + def validate_asset(self): + for data in self.assets_allocated: + if data.status == "Owned": + frappe.throw(_("All allocated assets should be returned before submission")) + + @frappe.whitelist() + def get_outstanding_statements(self): + if self.relieving_date: + if not len(self.get("payables", [])): + components = self.get_payable_component() + self.create_component_row(components, "payables") + if not len(self.get("receivables", [])): + components = self.get_receivable_component() + self.create_component_row(components, "receivables") + + if not len(self.get("assets_allocated", [])): + for data in self.get_assets_movement(): + self.append("assets_allocated", data) + else: + frappe.throw(_("Set Relieving Date for Employee: {0}").format(get_link_to_form("Employee", self.employee))) + + def create_component_row(self, components, component_type): + for component in components: + self.append(component_type, { + "status": "Unsettled", + "reference_document_type": component if component != "Bonus" else "Additional Salary", + "component": component + }) + + + def get_payable_component(self): + return [ + "Salary Slip", + "Gratuity", + "Expense Claim", + "Bonus", + "Leave Encashment", + ] + + def get_receivable_component(self): + return [ + "Loan", + "Employee Advance", + ] + + def get_assets_movement(self): + asset_movements = frappe.get_all("Asset Movement Item", + filters = {"docstatus": 1}, + fields = ["asset", "from_employee", "to_employee", "parent", "asset_name"], + or_filters = { + "from_employee": self.employee, + "to_employee": self.employee + } + ) + + data = [] + inward_movements = [] + outward_movements = [] + for movement in asset_movements: + if movement.to_employee and movement.to_employee == self.employee: + inward_movements.append(movement) + + if movement.from_employee and movement.from_employee == self.employee: + outward_movements.append(movement) + + for movement in inward_movements: + outwards_count = [movement.asset for movement in outward_movements].count(movement.asset) + inwards_counts = [movement.asset for movement in inward_movements].count(movement.asset) + + if inwards_counts > outwards_count: + data.append({ + "reference": movement.parent, + "asset_name": movement.asset_name, + "date": frappe.db.get_value("Asset Movement", movement.parent, "transaction_date"), + "status": "Owned" + }) + return data + + @frappe.whitelist() + def create_journal_entry(self): + precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") + jv = frappe.new_doc("Journal Entry") + jv.company = self.company + jv.voucher_type = "Bank Entry" + jv.posting_date = today() + + difference = self.total_payable_amount - self.total_receivable_amount + + for data in self.payables: + if data.amount > 0 and not data.paid_via_salary_slip: + account_dict = { + "account": data.account, + "debit_in_account_currency": flt(data.amount, precision) + } + if data.reference_document_type == "Expense Claim": + account_dict["party_type"] = "Employee" + account_dict["party"] = self.employee + + jv.append("accounts", account_dict) + + for data in self.receivables: + if data.amount > 0: + account_dict = { + "account": data.account, + "credit_in_account_currency": flt(data.amount, precision) + } + if data.reference_document_type == "Employee Advance": + account_dict["party_type"] = "Employee" + account_dict["party"] = self.employee + + jv.append("accounts", account_dict) + + jv.append("accounts", { + "credit_in_account_currency": difference if difference > 0 else 0, + "debit_in_account_currency": -(difference) if difference < 0 else 0, + "reference_type": self.doctype, + "reference_name": self.name + }) + return jv + +@frappe.whitelist() +def get_account_and_amount(ref_doctype, ref_document): + if not ref_doctype or not ref_document: + return None + + if ref_doctype == "Salary Slip": + salary_details = frappe.db.get_value("Salary Slip", ref_document, ["payroll_entry", "net_pay"], as_dict=1) + amount = salary_details.net_pay + payable_account = frappe.db.get_value("Payroll Entry", salary_details.payroll_entry, "payroll_payable_account") if salary_details.payroll_entry else None + return [payable_account, amount] + + if ref_doctype == "Gratuity": + payable_account, amount = frappe.db.get_value("Gratuity", ref_document, ["payable_account", "amount"]) + return [payable_account, amount] + + if ref_doctype == "Expense Claim": + details = frappe.db.get_value("Expense Claim", ref_document, + ["payable_account", "grand_total", "total_amount_reimbursed", "total_advance_amount"], as_dict=True) + payable_account = details.payable_account + amount = details.grand_total - (details.total_amount_reimbursed + details.total_advance_amount) + return [payable_account, amount] + + if ref_doctype == "Loan": + details = frappe.db.get_value("Loan", ref_document, + ["payment_account", "total_payment", "total_amount_paid"], as_dict=1) + payment_account = details.payment_account + amount = details.total_payment - details.total_amount_paid + return [payment_account, amount] + + if ref_doctype == "Employee Advance": + details = frappe.db.get_value("Employee Advance", ref_document, + ["advance_account","paid_amount", "claimed_amount", "return_amount"], as_dict = 1) + payment_account = details.advance_account + amount = details.paid_amount - (details.claimed_amount + details.return_amount) + return [payment_account, amount] diff --git a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement_list.js b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement_list.js new file mode 100644 index 0000000000..4aedec7c89 --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement_list.js @@ -0,0 +1,11 @@ +frappe.listview_settings["Full and Final Statement"] = { + get_indicator: function(doc) { + var colors = { + "Draft": "red", + "Unpaid": "orange", + "Paid": "green", + "Cancelled": "red" + }; + return [__(doc.status), colors[doc.status], "status,=," + doc.status]; + } +}; diff --git a/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py b/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py new file mode 100644 index 0000000000..8ecc129ccd --- /dev/null +++ b/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py @@ -0,0 +1,71 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import frappe +from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.assets.doctype.asset.test_asset import create_asset_data +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt +from frappe.utils import today, add_days +import unittest + +class TestFullandFinalStatement(unittest.TestCase): + + def setUp(self): + create_asset_data() + + def tearDown(self): + frappe.db.sql("Delete from `tabFull and Final Statement`") + frappe.db.sql("Delete from `tabAsset`") + frappe.db.sql("Delete from `tabAsset Movement`") + + def test_check_bootstraped_data_asset_movement_and_jv_creation(self): + employee = make_employee("test_fnf@example.com", company="_Test Company") + movement = create_asset_movement(employee) + frappe.db.set_value("Employee", employee, "relieving_date", add_days(today(), 30)) + fnf = create_full_and_final_statement(employee) + + payables_bootstraped_component = ["Salary Slip", "Gratuity", + "Expense Claim", "Bonus", "Leave Encashment"] + + receivable_bootstraped_component = ["Loan", "Employee Advance"] + + #checking payable s and receivables bootstraped value + self.assertEqual([payable.component for payable in fnf.payables], payables_bootstraped_component) + self.assertEqual([receivable.component for receivable in fnf.receivables], receivable_bootstraped_component) + + #checking allocated asset + self.assertIn(movement, [asset.reference for asset in fnf.assets_allocated]) + +def create_full_and_final_statement(employee): + fnf = frappe.new_doc("Full and Final Statement") + fnf.employee = employee + fnf.transaction_date = today() + fnf.save() + return fnf + +def create_asset_movement(employee): + asset_name = create_asset() + movement = frappe.new_doc("Asset Movement") + movement.company = "_Test Company" + movement.purpose = "Issue" + movement.transaction_date = today() + + movement.append("assets", { + "asset": asset_name, + "to_employee": employee + }) + + movement.save() + movement.submit() + return movement.name + +def create_asset(): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name") + asset = frappe.get_doc("Asset", asset_name) + asset.calculate_depreciation = 0 + asset.available_for_use_date = today() + asset.submit() + return asset_name diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json index 575fa7be6f..9c5d0c1b0e 100644 --- a/erpnext/hr/workspace/hr/hr.json +++ b/erpnext/hr/workspace/hr/hr.json @@ -223,6 +223,17 @@ "onboard": 0, "type": "Link" }, + { + "dependencies": "Employee", + "hidden": 0, + "is_query_report": 0, + "label": "Full and Final Statement", + "link_count": 0, + "link_to": "Full and Final Statement", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -931,7 +942,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:59.842918", + "modified": "2021-08-31 12:18:59.842918", "modified_by": "Administrator", "module": "HR", "name": "HR", diff --git a/erpnext/payroll/doctype/gratuity/gratuity.js b/erpnext/payroll/doctype/gratuity/gratuity.js index 377f3c6491..d4f7c9ca09 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.js +++ b/erpnext/payroll/doctype/gratuity/gratuity.js @@ -3,13 +3,6 @@ frappe.ui.form.on('Gratuity', { setup: function (frm) { - frm.set_query('salary_component', function () { - return { - filters: { - type: "Earning" - } - }; - }); frm.set_query("expense_account", function () { return { filters: { @@ -31,7 +24,7 @@ frappe.ui.form.on('Gratuity', { }); }, refresh: function (frm) { - if (frm.doc.docstatus === 1 && frm.doc.pay_via_salary_slip === 0 && frm.doc.status === "Unpaid") { + if (frm.doc.docstatus == 1 && frm.doc.status == "Unpaid") { frm.add_custom_button(__("Create Payment Entry"), function () { return frappe.call({ method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry', diff --git a/erpnext/payroll/doctype/gratuity/gratuity.json b/erpnext/payroll/doctype/gratuity/gratuity.json index 5cffd7eebf..48a9ce4759 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.json +++ b/erpnext/payroll/doctype/gratuity/gratuity.json @@ -16,9 +16,6 @@ "company", "gratuity_rule", "section_break_5", - "pay_via_salary_slip", - "payroll_date", - "salary_component", "payable_account", "expense_account", "mode_of_payment", @@ -49,26 +46,12 @@ "read_only": 1, "reqd": 1 }, - { - "default": "1", - "fieldname": "pay_via_salary_slip", - "fieldtype": "Check", - "label": "Pay via Salary Slip" - }, { "fieldname": "posting_date", "fieldtype": "Date", "label": "Posting date", "reqd": 1 }, - { - "depends_on": "eval: doc.pay_via_salary_slip == 1", - "fieldname": "salary_component", - "fieldtype": "Link", - "label": "Salary Component", - "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 1", - "options": "Salary Component" - }, { "default": "0", "fieldname": "current_work_experience", @@ -95,20 +78,18 @@ "reqd": 1 }, { - "depends_on": "eval: doc.pay_via_salary_slip == 0", "fieldname": "expense_account", "fieldtype": "Link", "label": "Expense Account", - "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", - "options": "Account" + "options": "Account", + "reqd": 1 }, { - "depends_on": "eval: doc.pay_via_salary_slip == 0", "fieldname": "mode_of_payment", "fieldtype": "Link", "label": "Mode of Payment", - "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", - "options": "Mode of Payment" + "options": "Mode of Payment", + "reqd": 1 }, { "fieldname": "gratuity_rule", @@ -161,13 +142,6 @@ "fieldname": "column_break_15", "fieldtype": "Column Break" }, - { - "depends_on": "eval: doc.pay_via_salary_slip == 1", - "fieldname": "payroll_date", - "fieldtype": "Date", - "label": "Payroll Date", - "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 1" - }, { "default": "0", "depends_on": "eval:doc.pay_via_salary_slip == 0", @@ -177,26 +151,23 @@ "read_only": 1 }, { - "depends_on": "eval: doc.pay_via_salary_slip == 0", "fieldname": "payable_account", "fieldtype": "Link", "label": "Payable Account", - "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", - "options": "Account" + "options": "Account", + "reqd": 1 }, { - "depends_on": "eval: doc.pay_via_salary_slip == 0", "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0", "options": "Cost Center" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-02 18:21:11.971488", + "modified": "2021-07-02 15:05:57.396398", "modified_by": "Administrator", "module": "Payroll", "name": "Gratuity", diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py index 31a6af3a07..8217bc3ae4 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity.py +++ b/erpnext/payroll/doctype/gratuity/gratuity.py @@ -19,10 +19,7 @@ class Gratuity(AccountsController): self.status = "Unpaid" def on_submit(self): - if self.pay_via_salary_slip: - self.create_additional_salary() - else: - self.create_gl_entries() + self.create_gl_entries() def on_cancel(self): self.ignore_linked_doctypes = ['GL Entry'] @@ -65,19 +62,6 @@ class Gratuity(AccountsController): return gl_entry - def create_additional_salary(self): - if self.pay_via_salary_slip: - additional_salary = frappe.new_doc('Additional Salary') - additional_salary.employee = self.employee - additional_salary.salary_component = self.salary_component - additional_salary.overwrite_salary_structure_amount = 0 - additional_salary.amount = self.amount - additional_salary.payroll_date = self.payroll_date - additional_salary.company = self.company - additional_salary.ref_doctype = self.doctype - additional_salary.ref_docname = self.name - additional_salary.submit() - def set_total_advance_paid(self): paid_amount = frappe.db.sql(""" select ifnull(sum(debit_in_account_currency), 0) as paid_amount diff --git a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py index 483e346a32..23c99b1cbd 100644 --- a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py +++ b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py @@ -11,10 +11,6 @@ def get_data(): { 'label': _('Payment'), 'items': ['Payment Entry'] - }, - { - 'label': _('Additional Salary'), - 'items': ['Additional Salary'] } ] } diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py index 6c3b6fbae5..8cb47283a5 100644 --- a/erpnext/payroll/doctype/gratuity/test_gratuity.py +++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py @@ -22,19 +22,18 @@ class TestGratuity(unittest.TestCase): def setUp(self): frappe.db.sql("DELETE FROM `tabGratuity`") - frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'") def test_get_last_salary_slip_should_return_none_for_new_employee(self): new_employee = make_employee("new_employee@salary.com", company='_Test Company') salary_slip = get_last_salary_slip(new_employee) assert salary_slip is None - def test_check_gratuity_amount_based_on_current_slab_and_additional_salary_creation(self): + def test_check_gratuity_amount_based_on_current_slab(self): employee, sal_slip = create_employee_and_get_last_salary_slip() rule = get_gratuity_rule("Rule Under Unlimited Contract on termination (UAE)") - gratuity = create_gratuity(pay_via_salary_slip = 1, employee=employee, rule=rule.name) + gratuity = create_gratuity(employee=employee, rule=rule.name) #work experience calculation date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date']) @@ -62,9 +61,6 @@ class TestGratuity(unittest.TestCase): self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2)) - #additional salary creation (Pay via salary slip) - self.assertTrue(frappe.db.exists("Additional Salary", {"ref_docname": gratuity.name})) - def test_check_gratuity_amount_based_on_all_previous_slabs(self): employee, sal_slip = create_employee_and_get_last_salary_slip() rule = get_gratuity_rule("Rule Under Limited Contract (UAE)") @@ -142,14 +138,9 @@ def create_gratuity(**args): gratuity.employee = args.employee gratuity.posting_date = getdate() gratuity.gratuity_rule = args.rule or "Rule Under Limited Contract (UAE)" - gratuity.pay_via_salary_slip = args.pay_via_salary_slip or 0 - if gratuity.pay_via_salary_slip: - gratuity.payroll_date = getdate() - gratuity.salary_component = "Performance Bonus" - else: - gratuity.expense_account = args.expense_account or 'Payment Account - _TC' - gratuity.payable_account = args.payable_account or get_payable_account("_Test Company") - gratuity.mode_of_payment = args.mode_of_payment or 'Cash' + gratuity.expense_account = args.expense_account or 'Payment Account - _TC' + gratuity.payable_account = args.payable_account or get_payable_account("_Test Company") + gratuity.mode_of_payment = args.mode_of_payment or 'Cash' gratuity.save() gratuity.submit()