diff --git a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py index f28a07431fe..88e1055beb4 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] diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py index e7e1a37480b..3d68bc8d8e0 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) diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py index 71773f14599..bac6e638d74 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.py +++ b/erpnext/loan_management/doctype/loan_application/loan_application.py @@ -135,10 +135,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) diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py index d44088bee74..6c27e121347 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 b56fa80c7ae..c5111fdc930 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 diff --git a/erpnext/payroll/doctype/salary_detail/salary_detail.json b/erpnext/payroll/doctype/salary_detail/salary_detail.json index adb54f26c6d..cc87caeae1a 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": "
Notes:
\n\nbase for using base salary of the EmployeeBS = Basic SalaryEmployment Type = employment_typeBranch = branchPayment Days = payment_daysLeave without pay = leave_without_paybase\nCondition: base < 10000\nFormula: base * .2BS \nCondition: BS > 2000\nFormula: BS * .1employment_type \nCondition: employment_type==\"Intern\"\nAmount: 1000