Merge branch 'develop' into shift_management

This commit is contained in:
Marica 2020-08-17 13:46:51 +05:30 committed by GitHub
commit 39c97bfbe9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 155 additions and 38 deletions

View File

@ -27,4 +27,4 @@ def get_vouchar_detials(column_list, doctype, docname):
for col in column_list: for col in column_list:
sanitize_searchfield(col) sanitize_searchfield(col)
return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s''' 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]

View File

@ -1621,8 +1621,9 @@ def update_multi_mode_option(doc, pos_profile):
pos_payment_method = pos_payment_method.as_dict() pos_payment_method = pos_payment_method.as_dict()
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company) payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
payment_mode[0].default = pos_payment_method.default if payment_mode:
append_payment(payment_mode[0]) payment_mode[0].default = pos_payment_method.default
append_payment(payment_mode[0])
def get_all_mode_of_payments(doc): def get_all_mode_of_payments(doc):
return frappe.db.sql(""" return frappe.db.sql("""

View File

@ -24,8 +24,13 @@ class JobOffer(Document):
check_vacancies = frappe.get_single("HR Settings").check_vacancies check_vacancies = frappe.get_single("HR Settings").check_vacancies
if staffing_plan and check_vacancies: if staffing_plan and check_vacancies:
job_offers = self.get_job_offer(staffing_plan.from_date, staffing_plan.to_date) 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): def on_change(self):
update_job_applicant(self.status, self.job_applicant) update_job_applicant(self.status, self.job_applicant)

View File

@ -5,20 +5,23 @@ cur_frm.add_fetch('employee','employee_name','employee_name');
frappe.ui.form.on("Leave Allocation", { frappe.ui.form.on("Leave Allocation", {
onload: function(frm) { onload: function(frm) {
// Ignore cancellation of doctype on cancel all.
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
if(!frm.doc.from_date) frm.set_value("from_date", frappe.datetime.get_today()); if(!frm.doc.from_date) frm.set_value("from_date", frappe.datetime.get_today());
frm.set_query("employee", function() { frm.set_query("employee", function() {
return { return {
query: "erpnext.controllers.queries.employee_query" query: "erpnext.controllers.queries.employee_query"
} };
}); });
frm.set_query("leave_type", function() { frm.set_query("leave_type", function() {
return { return {
filters: { filters: {
is_lwp: 0 is_lwp: 0
} }
} };
}) });
}, },
refresh: function(frm) { refresh: function(frm) {

View File

@ -19,6 +19,10 @@ frappe.ui.form.on("Leave Application", {
frm.set_query("employee", erpnext.queries.employee); frm.set_query("employee", erpnext.queries.employee);
}, },
onload: function(frm) { onload: function(frm) {
// Ignore cancellation of doctype on cancel all.
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
if (!frm.doc.posting_date) { if (!frm.doc.posting_date) {
frm.set_value("posting_date", frappe.datetime.get_today()); frm.set_value("posting_date", frappe.datetime.get_today());
} }

View File

@ -2,6 +2,10 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Leave Encashment', { frappe.ui.form.on('Leave Encashment', {
onload: function(frm) {
// Ignore cancellation of doctype on cancel all.
frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
},
setup: function(frm) { setup: function(frm) {
frm.set_query("leave_type", function() { frm.set_query("leave_type", function() {
return { return {
@ -33,7 +37,7 @@ frappe.ui.form.on('Leave Encashment', {
doc: frm.doc, doc: frm.doc,
callback: function(r) { callback: function(r) {
frm.refresh_fields(); frm.refresh_fields();
} }
}); });
} }
} }

View File

@ -135,10 +135,7 @@ def create_loan(source_name, target_doc=None, submit=0):
"validation": { "validation": {
"docstatus": ["=", 1] "docstatus": ["=", 1]
}, },
"postprocess": update_accounts, "postprocess": update_accounts
"field_no_map": [
"is_secured_loan"
]
} }
}, target_doc) }, target_doc)

View File

@ -10,22 +10,20 @@ from frappe.utils import nowdate, getdate, add_days, flt
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.general_ledger import make_gl_entries 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.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): class LoanDisbursement(AccountsController):
def validate(self): def validate(self):
self.set_missing_values() 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): def on_submit(self):
self.set_status_and_amounts()
self.make_gl_entries() self.make_gl_entries()
def on_cancel(self): def on_cancel(self):
self.set_status_and_amounts(cancel=1)
self.make_gl_entries(cancel=1) self.make_gl_entries(cancel=1)
self.ignore_linked_doctypes = ['GL Entry'] self.ignore_linked_doctypes = ['GL Entry']
@ -45,29 +43,69 @@ class LoanDisbursement(AccountsController):
def set_status_and_amounts(self, cancel=0): def set_status_and_amounts(self, cancel=0):
loan_details = frappe.get_all("Loan", loan_details = frappe.get_all("Loan",
fields = ["loan_amount", "disbursed_amount", "total_principal_paid", "status", "is_term_loan"], fields = ["loan_amount", "disbursed_amount", "total_payment", "total_principal_paid", "total_interest_payable",
filters= { "name": self.against_loan } "status", "is_term_loan", "is_secured_loan"], filters= { "name": self.against_loan })[0]
)[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)
if cancel: if cancel:
disbursed_amount = loan_details.disbursed_amount - self.disbursed_amount 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: if disbursed_amount == 0:
status = "Sanctioned" status = "Sanctioned"
elif disbursed_amount >= loan_details.disbursed_amount: elif disbursed_amount >= loan_details.loan_amount:
status = "Disbursed" status = "Disbursed"
else: else:
status = "Partially Disbursed" status = "Partially Disbursed"
else: else:
disbursed_amount = self.disbursed_amount + loan_details.disbursed_amount 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")) 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" status = "Disbursed"
else: else:
status = "Partially Disbursed" status = "Partially Disbursed"
@ -75,7 +113,8 @@ class LoanDisbursement(AccountsController):
frappe.db.set_value("Loan", self.against_loan, { frappe.db.set_value("Loan", self.against_loan, {
"disbursement_date": self.disbursement_date, "disbursement_date": self.disbursement_date,
"disbursed_amount": disbursed_amount, "disbursed_amount": disbursed_amount,
"status": status "status": status,
"total_payment": total_payment
}) })
def make_gl_entries(self, cancel=0, adv_adj=0): def make_gl_entries(self, cancel=0, adv_adj=0):
@ -116,3 +155,24 @@ class LoanDisbursement(AccountsController):
if gle_map: if gle_map:
make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj) 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

View File

@ -85,8 +85,8 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i
if no_of_days <= 0: if no_of_days <= 0:
return return
pending_principal_amount = loan.total_payment - loan.total_interest_payable \ pending_principal_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
- loan.total_amount_paid - flt(loan.total_principal_paid)
interest_per_day = (pending_principal_amount * loan.rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100) 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 payable_interest = interest_per_day * no_of_days

View File

@ -9,6 +9,33 @@
<p class="hero-subtitle">{{ greeting_subtitle }}</p> <p class="hero-subtitle">{{ greeting_subtitle }}</p>
{% endif %} {% endif %}
</div> </div>
<div class="search-container">
<div class="website-search" id="search-container">
<div class="dropdown">
<div class="search-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-search">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</div>
<input type="search" class="form-control" placeholder="Search the docs (Press ? to focus)" />
<div class="overflow-hidden shadow dropdown-menu w-100">
</div>
</div>
</div>
<button class="navbar-toggler" type="button"
data-toggle="collapse"
data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div> </div>
</section> </section>
@ -54,5 +81,21 @@
</div> </div>
</section> </section>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{%- block script -%}
<script>
frappe.ready(() => {
frappe.setup_search('#search-container', 'kb');
});
</script>
{%- endblock -%}
{%- block style -%}
<style>
.search-container {
margin-top: 1.2rem;
max-width: 500px;
}
</style>
{%- endblock -%}