From 2633e292a52366bd14bd64d2aa0da0a2acc8067e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 11 Aug 2020 18:03:28 +0530 Subject: [PATCH 1/5] fix: Is Secured Loan check mapping --- .../doctype/loan_application/loan_application.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py index f051755f67..832557e322 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.py +++ b/erpnext/loan_management/doctype/loan_application/loan_application.py @@ -133,10 +133,7 @@ def create_loan(source_name, target_doc=None, submit=0): "validation": { "docstatus": ["=", 1] }, - "postprocess": update_accounts, - "field_no_map": [ - "is_secured_loan" - ] + "postprocess": update_accounts } }, target_doc) From e1e847a3f7dbf04e03b8e26e3baead808df222c5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 16 Aug 2020 20:57:39 +0530 Subject: [PATCH 2/5] feat: Enhancement in Loan Topups --- .../loan_disbursement/loan_disbursement.py | 94 +++++++++++++++---- .../loan_interest_accrual.py | 4 +- 2 files changed, 79 insertions(+), 19 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py index d44088bee7..6c27e12134 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py @@ -10,22 +10,20 @@ from frappe.utils import nowdate, getdate, add_days, flt from erpnext.controllers.accounts_controller import AccountsController from erpnext.accounts.general_ledger import make_gl_entries from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans +from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty +from frappe.utils import get_datetime class LoanDisbursement(AccountsController): def validate(self): self.set_missing_values() - def before_submit(self): - self.set_status_and_amounts() - - def before_cancel(self): - self.set_status_and_amounts(cancel=1) - def on_submit(self): + self.set_status_and_amounts() self.make_gl_entries() def on_cancel(self): + self.set_status_and_amounts(cancel=1) self.make_gl_entries(cancel=1) self.ignore_linked_doctypes = ['GL Entry'] @@ -45,29 +43,69 @@ class LoanDisbursement(AccountsController): def set_status_and_amounts(self, cancel=0): loan_details = frappe.get_all("Loan", - fields = ["loan_amount", "disbursed_amount", "total_principal_paid", "status", "is_term_loan"], - filters= { "name": self.against_loan } - )[0] - - if loan_details.status == "Disbursed" and not loan_details.is_term_loan: - process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1), - loan=self.against_loan) + fields = ["loan_amount", "disbursed_amount", "total_payment", "total_principal_paid", "total_interest_payable", + "status", "is_term_loan", "is_secured_loan"], filters= { "name": self.against_loan })[0] if cancel: disbursed_amount = loan_details.disbursed_amount - self.disbursed_amount + total_payment = loan_details.total_payment + + if loan_details.disbursed_amount > loan_details.loan_amount: + topup_amount = loan_details.disbursed_amount - loan_details.loan_amount + if topup_amount > self.disbursed_amount: + topup_amount = self.disbursed_amount + + total_payment = total_payment - topup_amount + if disbursed_amount == 0: status = "Sanctioned" - elif disbursed_amount >= loan_details.disbursed_amount: + elif disbursed_amount >= loan_details.loan_amount: status = "Disbursed" else: status = "Partially Disbursed" else: disbursed_amount = self.disbursed_amount + loan_details.disbursed_amount + total_payment = loan_details.total_payment - if flt(disbursed_amount) - flt(loan_details.total_principal_paid) > flt(loan_details.loan_amount): + if disbursed_amount > loan_details.loan_amount and loan_details.is_term_loan: frappe.throw(_("Disbursed Amount cannot be greater than loan amount")) - if flt(disbursed_amount) >= loan_details.disbursed_amount: + if loan_details.status == 'Disbursed': + pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \ + - flt(loan_details.total_principal_paid) + else: + pending_principal_amount = loan_details.disbursed_amount + + security_value = 0.0 + if loan_details.is_secured_loan: + security_value = get_total_pledged_security_value(self.against_loan) + + if not security_value: + security_value = loan_details.loan_amount + + if pending_principal_amount + self.disbursed_amount > flt(security_value): + allowed_amount = security_value - pending_principal_amount + if allowed_amount < 0: + allowed_amount = 0 + + frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(allowed_amount)) + + if loan_details.status == "Disbursed" and not loan_details.is_term_loan: + process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1), + loan=self.against_loan) + + if disbursed_amount > loan_details.loan_amount: + topup_amount = disbursed_amount - loan_details.loan_amount + + if topup_amount < 0: + topup_amount = 0 + + if topup_amount > self.disbursed_amount: + topup_amount = self.disbursed_amount + + total_payment = total_payment + topup_amount + + if flt(disbursed_amount) >= loan_details.loan_amount: status = "Disbursed" else: status = "Partially Disbursed" @@ -75,7 +113,8 @@ class LoanDisbursement(AccountsController): frappe.db.set_value("Loan", self.against_loan, { "disbursement_date": self.disbursement_date, "disbursed_amount": disbursed_amount, - "status": status + "status": status, + "total_payment": total_payment }) def make_gl_entries(self, cancel=0, adv_adj=0): @@ -116,3 +155,24 @@ class LoanDisbursement(AccountsController): if gle_map: make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj) + +def get_total_pledged_security_value(loan): + update_time = get_datetime() + + loan_security_price_map = frappe._dict(frappe.get_all("Loan Security Price", + fields=["loan_security", "loan_security_price"], + filters = { + "valid_from": ("<=", update_time), + "valid_upto": (">=", update_time) + }, as_list=1)) + + hair_cut_map = frappe._dict(frappe.get_all('Loan Security', + fields=["name", "haircut"], as_list=1)) + + security_value = 0.0 + pledged_securities = get_pledged_security_qty(loan) + + for security, qty in pledged_securities.items(): + security_value += (loan_security_price_map.get(security) * qty * hair_cut_map.get(security))/100 + + return security_value diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index b56fa80c7a..c5111fdc93 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -85,8 +85,8 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i if no_of_days <= 0: return - pending_principal_amount = loan.total_payment - loan.total_interest_payable \ - - loan.total_amount_paid + pending_principal_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \ + - flt(loan.total_principal_paid) interest_per_day = (pending_principal_amount * loan.rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100) payable_interest = interest_per_day * no_of_days From 5b45961b9d013ba2a02d525e9bbb9aa44a5eb720 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 17 Aug 2020 10:51:53 +0530 Subject: [PATCH 3/5] fix: the JSON object must be str, bytes or bytearray, not "list" (#23047) Co-authored-by: Rucha Mahabal --- erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py index f28a07431f..88e1055beb 100644 --- a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py +++ b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py @@ -27,4 +27,4 @@ def get_vouchar_detials(column_list, doctype, docname): for col in column_list: sanitize_searchfield(col) return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s''' - .format(columns=", ".join(json.loads(column_list)), doctype=doctype), docname, as_dict=1)[0] + .format(columns=", ".join(column_list), doctype=doctype), docname, as_dict=1)[0] From 084de5f0b2473b61ad56e2cb5d68bdbb4eb2139f Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 17 Aug 2020 13:14:09 +0530 Subject: [PATCH 4/5] fix: handled condition if staffing isn't created (#22992) * fix: handled condition if staffing isn't created * style: reformated code for redability * Update erpnext/hr/doctype/job_offer/job_offer.py Co-authored-by: Marica --- erpnext/hr/doctype/job_offer/job_offer.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py index e7e1a37480..3d68bc8d8e 100644 --- a/erpnext/hr/doctype/job_offer/job_offer.py +++ b/erpnext/hr/doctype/job_offer/job_offer.py @@ -24,8 +24,13 @@ class JobOffer(Document): check_vacancies = frappe.get_single("HR Settings").check_vacancies if staffing_plan and check_vacancies: job_offers = self.get_job_offer(staffing_plan.from_date, staffing_plan.to_date) - if staffing_plan.vacancies - len(job_offers) <= 0: - frappe.throw(_("There are no vacancies under staffing plan {0}").format(frappe.bold(get_link_to_form("Staffing Plan", staffing_plan.parent)))) + + if not staffing_plan.get("vacancies") or staffing_plan.vacancies - len(job_offers) <= 0: + error_variable = 'for ' + frappe.bold(self.designation) + if staffing_plan.get("parent"): + error_variable = frappe.bold(get_link_to_form("Staffing Plan", staffing_plan.parent)) + + frappe.throw(_("There are no vacancies under staffing plan {0}").format(error_variable)) def on_change(self): update_job_applicant(self.status, self.job_applicant) From df7b42833115a0929f71d5d497d0989e3f97653a Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 17 Aug 2020 14:09:54 +0530 Subject: [PATCH 5/5] fix: UI/UX Salary details and salary slip form cleanup (#22536) * fix: UI/UX Salary details * feat: salary slip form cleaup * fix(test): set working days zero if salary slip is based on timesheet * fix(revert): set working days zero if salary slip is based on timesheet * fix: salary slip form cleanup Co-authored-by: Rucha Mahabal --- .../doctype/salary_detail/salary_detail.json | 74 +++++++++++++------ .../doctype/salary_slip/salary_slip.js | 4 +- .../doctype/salary_slip/salary_slip.json | 24 ++++-- 3 files changed, 69 insertions(+), 33 deletions(-) diff --git a/erpnext/payroll/doctype/salary_detail/salary_detail.json b/erpnext/payroll/doctype/salary_detail/salary_detail.json index adb54f26c6..cc87caeae1 100644 --- a/erpnext/payroll/doctype/salary_detail/salary_detail.json +++ b/erpnext/payroll/doctype/salary_detail/salary_detail.json @@ -7,27 +7,30 @@ "field_order": [ "salary_component", "abbr", - "statistical_component", "column_break_3", - "deduct_full_tax_on_selected_payroll_date", + "amount", + "section_break_5", + "additional_salary", + "statistical_component", "depends_on_payment_days", - "is_tax_applicable", "exempted_from_income_tax", + "is_tax_applicable", + "column_break_11", "is_flexible_benefit", "variable_based_on_taxable_salary", + "do_not_include_in_total", + "deduct_full_tax_on_selected_payroll_date", "section_break_2", "condition", + "column_break_18", "amount_based_on_formula", "formula", - "amount", - "do_not_include_in_total", + "section_break_19", "default_amount", "additional_amount", + "column_break_24", "tax_on_flexible_benefit", - "tax_on_additional_salary", - "section_break_11", - "additional_salary", - "condition_and_formula_help" + "tax_on_additional_salary" ], "fields": [ { @@ -110,9 +113,11 @@ "read_only": 1 }, { + "collapsible": 1, "depends_on": "eval:doc.is_flexible_benefit != 1", "fieldname": "section_break_2", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Condtion and formula" }, { "allow_on_submit": 1, @@ -181,23 +186,12 @@ "label": "Tax on additional salary", "read_only": 1 }, - { - "depends_on": "eval:doc.parenttype=='Salary Structure'", - "fieldname": "section_break_11", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval:doc.parenttype=='Salary Structure'", - "fieldname": "condition_and_formula_help", - "fieldtype": "HTML", - "label": "Condition and Formula Help", - "options": "

Condition and Formula Help

\n\n

Notes:

\n\n
    \n
  1. Use field base for using base salary of the Employee
  2. \n
  3. Use Salary Component abbreviations in conditions and formulas. BS = Basic Salary
  4. \n
  5. Use field name for employee details in conditions and formulas. Employment Type = employment_typeBranch = branch
  6. \n
  7. Use field name from Salary Slip in conditions and formulas. Payment Days = payment_daysLeave without pay = leave_without_pay
  8. \n
  9. Direct Amount can also be entered based on Condtion. See example 3
\n\n

Examples

\n
    \n
  1. Calculating Basic Salary based on base\n
    Condition: base < 10000
    \n
    Formula: base * .2
  2. \n
  3. Calculating HRA based on Basic SalaryBS \n
    Condition: BS > 2000
    \n
    Formula: BS * .1
  4. \n
  5. Calculating TDS based on Employment Typeemployment_type \n
    Condition: employment_type==\"Intern\"
    \n
    Amount: 1000
  6. \n
" - }, { "fieldname": "additional_salary", "fieldtype": "Link", "label": "Additional Salary ", - "options": "Additional Salary" + "options": "Additional Salary", + "read_only": 1 }, { "default": "0", @@ -207,11 +201,43 @@ "fieldtype": "Check", "label": "Exempted from Income Tax", "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "label": "Component properties and references ", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "section_break_19", + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 } ], "istable": 1, "links": [], - "modified": "2020-06-22 23:21:26.300951", + "modified": "2020-07-01 12:13:41.956495", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Detail", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js index 4b623e57b4..7b69dbe8d6 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.js +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js @@ -123,13 +123,13 @@ frappe.ui.form.on("Salary Slip", { doc: frm.doc, callback: function(r, rt) { frm.refresh(); - if (frm.doc.absent_days){ + if (r.message){ frm.fields_dict.absent_days.set_description("Unmarked Days is treated as "+ r.message +". You can can change this in " + frappe.utils.get_form_link("Payroll Settings", "Payroll Settings", true)); } } }); } -}) +}); frappe.ui.form.on('Salary Slip Timesheet', { time_sheet: function(frm, dt, dn) { diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json index 27a974ac83..619c45fa4a 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.json +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json @@ -20,15 +20,17 @@ "company", "letter_head", "section_break_10", - "salary_slip_based_on_timesheet", "start_date", "end_date", "salary_structure", + "column_break_18", + "salary_slip_based_on_timesheet", "payroll_frequency", - "column_break_15", + "section_break_20", "total_working_days", "unmarked_days", "leave_without_pay", + "column_break_24", "absent_days", "payment_days", "hourly_wages", @@ -200,10 +202,6 @@ "fieldtype": "Date", "label": "End Date" }, - { - "fieldname": "column_break_15", - "fieldtype": "Column Break" - }, { "fieldname": "salary_structure", "fieldtype": "Link", @@ -490,13 +488,25 @@ "fieldtype": "Float", "hidden": 1, "label": "Unmarked days" + }, + { + "fieldname": "section_break_20", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" } ], "icon": "fa fa-file-text", "idx": 9, "is_submittable": 1, "links": [], - "modified": "2020-07-22 12:41:03.659422", + "modified": "2020-08-11 17:37:54.274384", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Slip",