mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 00:14:50 +00:00
Merge branch 'hotfix'
This commit is contained in:
@@ -5,7 +5,7 @@ import frappe
|
|||||||
from erpnext.hooks import regional_overrides
|
from erpnext.hooks import regional_overrides
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
__version__ = '11.1.46'
|
__version__ = '11.1.47'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
'''Get default company for user'''
|
'''Get default company for user'''
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ frappe.treeview_settings["Account"] = {
|
|||||||
},
|
},
|
||||||
onrender: function(node) {
|
onrender: function(node) {
|
||||||
if(frappe.boot.user.can_read.indexOf("GL Entry") !== -1){
|
if(frappe.boot.user.can_read.indexOf("GL Entry") !== -1){
|
||||||
var dr_or_cr = node.data.balance < 0 ? "Cr" : "Dr";
|
var dr_or_cr = in_list(["Liability", "Income", "Equity"], node.data.root_type) ? "Cr" : "Dr";
|
||||||
if (node.data && node.data.balance!==undefined) {
|
if (node.data && node.data.balance!==undefined) {
|
||||||
$('<span class="balance-area pull-right text-muted small">'
|
$('<span class="balance-area pull-right text-muted small">'
|
||||||
+ (node.data.balance_in_account_currency ?
|
+ (node.data.balance_in_account_currency ?
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=Non
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, silent=False, include_expired_entry=False, current_transaction_amount=0):
|
def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, silent=False, include_expired_entry=False, current_transaction_amount=0):
|
||||||
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
|
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
|
||||||
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
|
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program or lp_details.loyalty_program)
|
||||||
lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry))
|
lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry))
|
||||||
|
|
||||||
tier_spent_level = sorted([d.as_dict() for d in loyalty_program.collection_rules],
|
tier_spent_level = sorted([d.as_dict() for d in loyalty_program.collection_rules],
|
||||||
|
|||||||
@@ -558,7 +558,7 @@ def get_outstanding_reference_documents(args):
|
|||||||
|
|
||||||
# Get negative outstanding sales /purchase invoices
|
# Get negative outstanding sales /purchase invoices
|
||||||
negative_outstanding_invoices = []
|
negative_outstanding_invoices = []
|
||||||
if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"):
|
if args.get("party_type") in ("Supplier", "Customer") and not args.get("voucher_no"):
|
||||||
negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"),
|
negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"),
|
||||||
args.get("party"), args.get("party_account"), party_account_currency, company_currency)
|
args.get("party"), args.get("party_account"), party_account_currency, company_currency)
|
||||||
|
|
||||||
@@ -589,7 +589,7 @@ def get_outstanding_reference_documents(args):
|
|||||||
|
|
||||||
# Get all SO / PO which are not fully billed or aginst which full advance not paid
|
# Get all SO / PO which are not fully billed or aginst which full advance not paid
|
||||||
orders_to_be_billed = []
|
orders_to_be_billed = []
|
||||||
if (args.get("party_type") != "Student"):
|
if (args.get("party_type") in ("Supplier", "Customer")):
|
||||||
orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"),
|
orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"),
|
||||||
args.get("party"), party_account_currency, company_currency)
|
args.get("party"), party_account_currency, company_currency)
|
||||||
|
|
||||||
@@ -601,7 +601,7 @@ def get_orders_to_be_billed(posting_date, party_type, party, party_account_curre
|
|||||||
voucher_type = 'Sales Order'
|
voucher_type = 'Sales Order'
|
||||||
elif party_type == "Supplier":
|
elif party_type == "Supplier":
|
||||||
voucher_type = 'Purchase Order'
|
voucher_type = 'Purchase Order'
|
||||||
elif party_type == "Employee":
|
else:
|
||||||
voucher_type = None
|
voucher_type = None
|
||||||
|
|
||||||
# Add cost center condition
|
# Add cost center condition
|
||||||
|
|||||||
@@ -73,6 +73,12 @@ frappe.query_reports["Accounts Payable"] = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"payment_terms_template",
|
||||||
|
"label": __("Payment Terms Template"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Payment Terms Template"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"supplier_group",
|
"fieldname":"supplier_group",
|
||||||
"label": __("Supplier Group"),
|
"label": __("Supplier Group"),
|
||||||
|
|||||||
@@ -63,6 +63,12 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Supplier"
|
"options": "Supplier"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"payment_terms_template",
|
||||||
|
"label": __("Payment Terms Template"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Payment Terms Template"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"supplier_group",
|
"fieldname":"supplier_group",
|
||||||
"label": __("Supplier Group"),
|
"label": __("Supplier Group"),
|
||||||
|
|||||||
@@ -540,6 +540,10 @@ class ReceivablePayableReport(object):
|
|||||||
where supplier_group=%s)""")
|
where supplier_group=%s)""")
|
||||||
values.append(self.filters.get("supplier_group"))
|
values.append(self.filters.get("supplier_group"))
|
||||||
|
|
||||||
|
if self.filters.get("payment_terms_template"):
|
||||||
|
conditions.append("party in (select name from tabSupplier where payment_terms=%s)")
|
||||||
|
values.append(self.filters.get("payment_terms_template"))
|
||||||
|
|
||||||
accounts = [d.name for d in frappe.get_all("Account",
|
accounts = [d.name for d in frappe.get_all("Account",
|
||||||
filters={"account_type": account_type, "company": self.filters.company})]
|
filters={"account_type": account_type, "company": self.filters.company})]
|
||||||
conditions.append("account in (%s)" % ','.join(['%s'] *len(accounts)))
|
conditions.append("account in (%s)" % ','.join(['%s'] *len(accounts)))
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ def get_columns():
|
|||||||
{
|
{
|
||||||
"fieldname": "payment_entry",
|
"fieldname": "payment_entry",
|
||||||
"label": _("Payment Entry"),
|
"label": _("Payment Entry"),
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Link",
|
||||||
"options": "payment_document",
|
"options": "Payment Entry",
|
||||||
"width": 220
|
"width": 220
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from frappe import _, _dict
|
|||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.accounts.report.financial_statements import get_cost_centers_with_children
|
from erpnext.accounts.report.financial_statements import get_cost_centers_with_children
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
if not filters:
|
if not filters:
|
||||||
@@ -269,7 +270,7 @@ def group_by_field(group_by):
|
|||||||
return 'voucher_no'
|
return 'voucher_no'
|
||||||
|
|
||||||
def initialize_gle_map(gl_entries, filters):
|
def initialize_gle_map(gl_entries, filters):
|
||||||
gle_map = frappe._dict()
|
gle_map = OrderedDict()
|
||||||
group_by = group_by_field(filters.get('group_by'))
|
group_by = group_by_field(filters.get('group_by'))
|
||||||
|
|
||||||
for gle in gl_entries:
|
for gle in gl_entries:
|
||||||
|
|||||||
@@ -104,6 +104,9 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
|
|||||||
# get balance of all entries that exist
|
# get balance of all entries that exist
|
||||||
date = nowdate()
|
date = nowdate()
|
||||||
|
|
||||||
|
if account:
|
||||||
|
acc = frappe.get_doc("Account", account)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
year_start_date = get_fiscal_year(date, verbose=0)[1]
|
year_start_date = get_fiscal_year(date, verbose=0)[1]
|
||||||
except FiscalYearError:
|
except FiscalYearError:
|
||||||
@@ -118,7 +121,7 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
|
|||||||
|
|
||||||
allow_cost_center_in_entry_of_bs_account = get_allow_cost_center_in_entry_of_bs_account()
|
allow_cost_center_in_entry_of_bs_account = get_allow_cost_center_in_entry_of_bs_account()
|
||||||
|
|
||||||
if cost_center and allow_cost_center_in_entry_of_bs_account:
|
if cost_center and (allow_cost_center_in_entry_of_bs_account or acc.report_type =='Profit and Loss'):
|
||||||
cc = frappe.get_doc("Cost Center", cost_center)
|
cc = frappe.get_doc("Cost Center", cost_center)
|
||||||
if cc.is_group:
|
if cc.is_group:
|
||||||
cond.append(""" exists (
|
cond.append(""" exists (
|
||||||
@@ -132,20 +135,13 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
|
|||||||
|
|
||||||
if account:
|
if account:
|
||||||
|
|
||||||
acc = frappe.get_doc("Account", account)
|
|
||||||
|
|
||||||
if not frappe.flags.ignore_account_permission:
|
if not frappe.flags.ignore_account_permission:
|
||||||
acc.check_permission("read")
|
acc.check_permission("read")
|
||||||
|
|
||||||
|
if acc.report_type == 'Profit and Loss':
|
||||||
if not allow_cost_center_in_entry_of_bs_account and acc.report_type == 'Profit and Loss':
|
|
||||||
# for pl accounts, get balance within a fiscal year
|
# for pl accounts, get balance within a fiscal year
|
||||||
cond.append("posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" \
|
cond.append("posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" \
|
||||||
% year_start_date)
|
% year_start_date)
|
||||||
elif allow_cost_center_in_entry_of_bs_account:
|
|
||||||
# for all accounts, get balance within a fiscal year if maintain cost center in balance account is checked
|
|
||||||
cond.append("posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" \
|
|
||||||
% year_start_date)
|
|
||||||
# different filter for group and ledger - improved performance
|
# different filter for group and ledger - improved performance
|
||||||
if acc.is_group:
|
if acc.is_group:
|
||||||
cond.append("""exists (
|
cond.append("""exists (
|
||||||
@@ -721,6 +717,7 @@ def get_children(doctype, parent, company, is_root=False):
|
|||||||
parent_fieldname = 'parent_' + doctype.lower().replace(' ', '_')
|
parent_fieldname = 'parent_' + doctype.lower().replace(' ', '_')
|
||||||
fields = [
|
fields = [
|
||||||
'name as value',
|
'name as value',
|
||||||
|
'root_type',
|
||||||
'is_group as expandable'
|
'is_group as expandable'
|
||||||
]
|
]
|
||||||
filters = [['docstatus', '<', 2]]
|
filters = [['docstatus', '<', 2]]
|
||||||
@@ -728,7 +725,7 @@ def get_children(doctype, parent, company, is_root=False):
|
|||||||
filters.append(['ifnull(`{0}`,"")'.format(parent_fieldname), '=', '' if is_root else parent])
|
filters.append(['ifnull(`{0}`,"")'.format(parent_fieldname), '=', '' if is_root else parent])
|
||||||
|
|
||||||
if is_root:
|
if is_root:
|
||||||
fields += ['root_type', 'report_type', 'account_currency'] if doctype == 'Account' else []
|
fields += ['report_type', 'account_currency'] if doctype == 'Account' else []
|
||||||
filters.append(['company', '=', company])
|
filters.append(['company', '=', company])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ class calculate_taxes_and_totals(object):
|
|||||||
self.calculate()
|
self.calculate()
|
||||||
|
|
||||||
def calculate(self):
|
def calculate(self):
|
||||||
|
if not len(self.doc.get("items")):
|
||||||
|
return
|
||||||
|
|
||||||
self.discount_amount_applied = False
|
self.discount_amount_applied = False
|
||||||
self._calculate()
|
self._calculate()
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ frappe.ui.form.on("Opportunity", {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (frm.doc.opportunity_from && frm.doc.party_name){
|
||||||
|
frm.trigger('set_contact_link');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onload_post_render: function(frm) {
|
onload_post_render: function(frm) {
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from requests_oauthlib import OAuth2Session
|
from requests_oauthlib import OAuth2Session
|
||||||
import json, requests
|
import json
|
||||||
|
import requests
|
||||||
|
import traceback
|
||||||
from erpnext import encode_company_abbr
|
from erpnext import encode_company_abbr
|
||||||
|
|
||||||
# QuickBooks requires a redirect URL, User will be redirect to this URL
|
# QuickBooks requires a redirect URL, User will be redirect to this URL
|
||||||
@@ -32,7 +34,6 @@ def callback(*args, **kwargs):
|
|||||||
class QuickBooksMigrator(Document):
|
class QuickBooksMigrator(Document):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(QuickBooksMigrator, self).__init__(*args, **kwargs)
|
super(QuickBooksMigrator, self).__init__(*args, **kwargs)
|
||||||
from pprint import pprint
|
|
||||||
self.oauth = OAuth2Session(
|
self.oauth = OAuth2Session(
|
||||||
client_id=self.client_id,
|
client_id=self.client_id,
|
||||||
redirect_uri=self.redirect_url,
|
redirect_uri=self.redirect_url,
|
||||||
@@ -46,7 +47,9 @@ class QuickBooksMigrator(Document):
|
|||||||
if self.company:
|
if self.company:
|
||||||
# We need a Cost Center corresponding to the selected erpnext Company
|
# We need a Cost Center corresponding to the selected erpnext Company
|
||||||
self.default_cost_center = frappe.db.get_value('Company', self.company, 'cost_center')
|
self.default_cost_center = frappe.db.get_value('Company', self.company, 'cost_center')
|
||||||
self.default_warehouse = frappe.get_all('Warehouse', filters={"company": self.company, "is_group": 0})[0]["name"]
|
company_warehouses = frappe.get_all('Warehouse', filters={"company": self.company, "is_group": 0})
|
||||||
|
if company_warehouses:
|
||||||
|
self.default_warehouse = company_warehouses[0].name
|
||||||
if self.authorization_endpoint:
|
if self.authorization_endpoint:
|
||||||
self.authorization_url = self.oauth.authorization_url(self.authorization_endpoint)[0]
|
self.authorization_url = self.oauth.authorization_url(self.authorization_endpoint)[0]
|
||||||
|
|
||||||
@@ -218,7 +221,7 @@ class QuickBooksMigrator(Document):
|
|||||||
|
|
||||||
def _fetch_general_ledger(self):
|
def _fetch_general_ledger(self):
|
||||||
try:
|
try:
|
||||||
query_uri = "{}/company/{}/reports/GeneralLedger".format(self.api_endpoint ,self.quickbooks_company_id)
|
query_uri = "{}/company/{}/reports/GeneralLedger".format(self.api_endpoint, self.quickbooks_company_id)
|
||||||
response = self._get(query_uri,
|
response = self._get(query_uri,
|
||||||
params={
|
params={
|
||||||
"columns": ",".join(["tx_date", "txn_type", "credit_amt", "debt_amt"]),
|
"columns": ",".join(["tx_date", "txn_type", "credit_amt", "debt_amt"]),
|
||||||
@@ -493,17 +496,17 @@ class QuickBooksMigrator(Document):
|
|||||||
"account_currency": customer["CurrencyRef"]["value"],
|
"account_currency": customer["CurrencyRef"]["value"],
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
})[0]["name"]
|
})[0]["name"]
|
||||||
except Exception as e:
|
except Exception:
|
||||||
receivable_account = None
|
receivable_account = None
|
||||||
erpcustomer = frappe.get_doc({
|
erpcustomer = frappe.get_doc({
|
||||||
"doctype": "Customer",
|
"doctype": "Customer",
|
||||||
"quickbooks_id": customer["Id"],
|
"quickbooks_id": customer["Id"],
|
||||||
"customer_name" : encode_company_abbr(customer["DisplayName"], self.company),
|
"customer_name": encode_company_abbr(customer["DisplayName"], self.company),
|
||||||
"customer_type" : "Individual",
|
"customer_type": "Individual",
|
||||||
"customer_group" : "Commercial",
|
"customer_group": "Commercial",
|
||||||
"default_currency": customer["CurrencyRef"]["value"],
|
"default_currency": customer["CurrencyRef"]["value"],
|
||||||
"accounts": [{"company": self.company, "account": receivable_account}],
|
"accounts": [{"company": self.company, "account": receivable_account}],
|
||||||
"territory" : "All Territories",
|
"territory": "All Territories",
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
}).insert()
|
}).insert()
|
||||||
if "BillAddr" in customer:
|
if "BillAddr" in customer:
|
||||||
@@ -521,7 +524,7 @@ class QuickBooksMigrator(Document):
|
|||||||
item_dict = {
|
item_dict = {
|
||||||
"doctype": "Item",
|
"doctype": "Item",
|
||||||
"quickbooks_id": item["Id"],
|
"quickbooks_id": item["Id"],
|
||||||
"item_code" : encode_company_abbr(item["Name"], self.company),
|
"item_code": encode_company_abbr(item["Name"], self.company),
|
||||||
"stock_uom": "Unit",
|
"stock_uom": "Unit",
|
||||||
"is_stock_item": 0,
|
"is_stock_item": 0,
|
||||||
"item_group": "All Item Groups",
|
"item_group": "All Item Groups",
|
||||||
@@ -549,14 +552,14 @@ class QuickBooksMigrator(Document):
|
|||||||
erpsupplier = frappe.get_doc({
|
erpsupplier = frappe.get_doc({
|
||||||
"doctype": "Supplier",
|
"doctype": "Supplier",
|
||||||
"quickbooks_id": vendor["Id"],
|
"quickbooks_id": vendor["Id"],
|
||||||
"supplier_name" : encode_company_abbr(vendor["DisplayName"], self.company),
|
"supplier_name": encode_company_abbr(vendor["DisplayName"], self.company),
|
||||||
"supplier_group" : "All Supplier Groups",
|
"supplier_group": "All Supplier Groups",
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
}).insert()
|
}).insert()
|
||||||
if "BillAddr" in vendor:
|
if "BillAddr" in vendor:
|
||||||
self._create_address(erpsupplier, "Supplier", vendor["BillAddr"], "Billing")
|
self._create_address(erpsupplier, "Supplier", vendor["BillAddr"], "Billing")
|
||||||
if "ShipAddr" in vendor:
|
if "ShipAddr" in vendor:
|
||||||
self._create_address(erpsupplier, "Supplier",vendor["ShipAddr"], "Shipping")
|
self._create_address(erpsupplier, "Supplier", vendor["ShipAddr"], "Shipping")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log_error(e)
|
self._log_error(e)
|
||||||
|
|
||||||
@@ -829,7 +832,7 @@ class QuickBooksMigrator(Document):
|
|||||||
"currency": invoice["CurrencyRef"]["value"],
|
"currency": invoice["CurrencyRef"]["value"],
|
||||||
"conversion_rate": invoice.get("ExchangeRate", 1),
|
"conversion_rate": invoice.get("ExchangeRate", 1),
|
||||||
"posting_date": invoice["TxnDate"],
|
"posting_date": invoice["TxnDate"],
|
||||||
"due_date": invoice.get("DueDate", invoice["TxnDate"]),
|
"due_date": invoice.get("DueDate", invoice["TxnDate"]),
|
||||||
"credit_to": credit_to_account,
|
"credit_to": credit_to_account,
|
||||||
"supplier": frappe.get_all("Supplier",
|
"supplier": frappe.get_all("Supplier",
|
||||||
filters={
|
filters={
|
||||||
@@ -1200,7 +1203,7 @@ class QuickBooksMigrator(Document):
|
|||||||
|
|
||||||
|
|
||||||
def _create_address(self, entity, doctype, address, address_type):
|
def _create_address(self, entity, doctype, address, address_type):
|
||||||
try :
|
try:
|
||||||
if not frappe.db.exists({"doctype": "Address", "quickbooks_id": address["Id"]}):
|
if not frappe.db.exists({"doctype": "Address", "quickbooks_id": address["Id"]}):
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"doctype": "Address",
|
"doctype": "Address",
|
||||||
@@ -1252,8 +1255,6 @@ class QuickBooksMigrator(Document):
|
|||||||
|
|
||||||
|
|
||||||
def _log_error(self, execption, data=""):
|
def _log_error(self, execption, data=""):
|
||||||
import json, traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
frappe.log_error(title="QuickBooks Migration Error",
|
frappe.log_error(title="QuickBooks Migration Error",
|
||||||
message="\n".join([
|
message="\n".join([
|
||||||
"Data",
|
"Data",
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
import frappe, math
|
|
||||||
from frappe import _
|
|
||||||
from frappe.utils import flt, rounded
|
|
||||||
from frappe.model.mapper import get_mapped_doc
|
|
||||||
from frappe.model.document import Document
|
|
||||||
|
|
||||||
from erpnext.hr.doctype.employee_loan.employee_loan import get_monthly_repayment_amount, check_repayment_method
|
|
||||||
|
|
||||||
class EmployeeLoanApplication(Document):
|
|
||||||
def validate(self):
|
|
||||||
check_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods)
|
|
||||||
self.validate_loan_amount()
|
|
||||||
self.get_repayment_details()
|
|
||||||
|
|
||||||
def validate_loan_amount(self):
|
|
||||||
maximum_loan_limit = frappe.db.get_value('Loan Type', self.loan_type, 'maximum_loan_amount')
|
|
||||||
if maximum_loan_limit and self.loan_amount > maximum_loan_limit:
|
|
||||||
frappe.throw(_("Loan Amount cannot exceed Maximum Loan Amount of {0}").format(maximum_loan_limit))
|
|
||||||
|
|
||||||
def get_repayment_details(self):
|
|
||||||
if self.repayment_method == "Repay Over Number of Periods":
|
|
||||||
self.repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
|
|
||||||
|
|
||||||
if self.repayment_method == "Repay Fixed Amount per Period":
|
|
||||||
monthly_interest_rate = flt(self.rate_of_interest) / (12 *100)
|
|
||||||
if monthly_interest_rate:
|
|
||||||
monthly_interest_amount = self.loan_amount * monthly_interest_rate
|
|
||||||
if monthly_interest_amount >= self.repayment_amount:
|
|
||||||
frappe.throw(_("Repayment amount {} should be greater than monthly interest amount {}").
|
|
||||||
format(self.repayment_amount, monthly_interest_amount))
|
|
||||||
|
|
||||||
self.repayment_periods = math.ceil((math.log(self.repayment_amount) -
|
|
||||||
math.log(self.repayment_amount - (monthly_interest_amount))) /
|
|
||||||
(math.log(1 + monthly_interest_rate)))
|
|
||||||
else:
|
|
||||||
self.repayment_periods = self.loan_amount / self.repayment_amount
|
|
||||||
|
|
||||||
self.calculate_payable_amount()
|
|
||||||
|
|
||||||
def calculate_payable_amount(self):
|
|
||||||
balance_amount = self.loan_amount
|
|
||||||
self.total_payable_amount = 0
|
|
||||||
self.total_payable_interest = 0
|
|
||||||
|
|
||||||
while(balance_amount > 0):
|
|
||||||
interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100))
|
|
||||||
balance_amount = rounded(balance_amount + interest_amount - self.repayment_amount)
|
|
||||||
|
|
||||||
self.total_payable_interest += interest_amount
|
|
||||||
|
|
||||||
self.total_payable_amount = self.loan_amount + self.total_payable_interest
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def make_employee_loan(source_name, target_doc = None):
|
|
||||||
doclist = get_mapped_doc("Employee Loan Application", source_name, {
|
|
||||||
"Employee Loan Application": {
|
|
||||||
"doctype": "Employee Loan",
|
|
||||||
"validation": {
|
|
||||||
"docstatus": ["=", 1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, target_doc)
|
|
||||||
|
|
||||||
return doclist
|
|
||||||
@@ -39,31 +39,19 @@ frappe.ui.form.on('Loan', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
if (frm.doc.docstatus == 1 && frm.doc.status == "Sanctioned") {
|
if (frm.doc.docstatus == 1) {
|
||||||
frm.add_custom_button(__('Make Disbursement Entry'), function() {
|
if (frm.doc.status == "Sanctioned") {
|
||||||
frm.trigger("make_jv");
|
frm.add_custom_button(__('Create Disbursement Entry'), function() {
|
||||||
})
|
frm.trigger("make_jv");
|
||||||
}
|
}).addClass("btn-primary");
|
||||||
if (frm.doc.repayment_schedule) {
|
} else if (frm.doc.status == "Disbursed" && frm.doc.repayment_start_date && (frm.doc.applicant_type == 'Member' || frm.doc.repay_from_salary == 0)) {
|
||||||
let total_amount_paid = 0;
|
frm.add_custom_button(__('Create Repayment Entry'), function() {
|
||||||
$.each(frm.doc.repayment_schedule || [], function(i, row) {
|
frm.trigger("make_repayment_entry");
|
||||||
if (row.paid) {
|
}).addClass("btn-primary");
|
||||||
total_amount_paid += row.total_payment;
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
frm.set_value("total_amount_paid", total_amount_paid);
|
|
||||||
; }
|
|
||||||
if (frm.doc.docstatus == 1 && frm.doc.repayment_start_date && (frm.doc.applicant_type == 'Member' || frm.doc.repay_from_salary == 0)) {
|
|
||||||
frm.add_custom_button(__('Make Repayment Entry'), function() {
|
|
||||||
frm.trigger("make_repayment_entry");
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
frm.trigger("toggle_fields");
|
frm.trigger("toggle_fields");
|
||||||
},
|
},
|
||||||
status: function (frm) {
|
|
||||||
frm.toggle_reqd("disbursement_date", frm.doc.status == 'Disbursed')
|
|
||||||
frm.toggle_reqd("repayment_start_date", frm.doc.status == 'Disbursed')
|
|
||||||
},
|
|
||||||
|
|
||||||
make_jv: function (frm) {
|
make_jv: function (frm) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"allow_copy": 0,
|
||||||
|
"allow_events_in_timeline": 0,
|
||||||
"allow_guest_to_view": 0,
|
"allow_guest_to_view": 0,
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 0,
|
"allow_rename": 0,
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "applicant_type",
|
"fieldname": "applicant_type",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -53,6 +55,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "applicant",
|
"fieldname": "applicant",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -86,6 +89,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "applicant_name",
|
"fieldname": "applicant_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -118,6 +122,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "loan_application",
|
"fieldname": "loan_application",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -151,6 +156,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "loan_type",
|
"fieldname": "loan_type",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -184,6 +190,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "column_break_3",
|
"fieldname": "column_break_3",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -215,7 +222,8 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"default": "",
|
"default": "Today",
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "posting_date",
|
"fieldname": "posting_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -248,6 +256,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -282,6 +291,7 @@
|
|||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"default": "Sanctioned",
|
"default": "Sanctioned",
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -299,7 +309,7 @@
|
|||||||
"precision": "",
|
"precision": "",
|
||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 1,
|
||||||
"remember_last_selected_value": 0,
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
@@ -316,6 +326,7 @@
|
|||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"depends_on": "eval:doc.applicant_type==\"Employee\"",
|
"depends_on": "eval:doc.applicant_type==\"Employee\"",
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "repay_from_salary",
|
"fieldname": "repay_from_salary",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -348,6 +359,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "section_break_8",
|
"fieldname": "section_break_8",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -380,6 +392,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "loan_amount",
|
"fieldname": "loan_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -415,6 +428,7 @@
|
|||||||
"columns": 0,
|
"columns": 0,
|
||||||
"default": "",
|
"default": "",
|
||||||
"fetch_from": "loan_type.rate_of_interest",
|
"fetch_from": "loan_type.rate_of_interest",
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "rate_of_interest",
|
"fieldname": "rate_of_interest",
|
||||||
"fieldtype": "Percent",
|
"fieldtype": "Percent",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -448,6 +462,8 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"depends_on": "eval:doc.status==\"Disbursed\"",
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "disbursement_date",
|
"fieldname": "disbursement_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -480,6 +496,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "repayment_start_date",
|
"fieldname": "repayment_start_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -499,7 +516,7 @@
|
|||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
"remember_last_selected_value": 0,
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
@@ -512,6 +529,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "column_break_11",
|
"fieldname": "column_break_11",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -544,6 +562,7 @@
|
|||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"default": "Repay Over Number of Periods",
|
"default": "Repay Over Number of Periods",
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "repayment_method",
|
"fieldname": "repayment_method",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -579,6 +598,7 @@
|
|||||||
"columns": 0,
|
"columns": 0,
|
||||||
"default": "",
|
"default": "",
|
||||||
"depends_on": "",
|
"depends_on": "",
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "repayment_periods",
|
"fieldname": "repayment_periods",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -613,6 +633,7 @@
|
|||||||
"columns": 0,
|
"columns": 0,
|
||||||
"default": "",
|
"default": "",
|
||||||
"depends_on": "",
|
"depends_on": "",
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "monthly_repayment_amount",
|
"fieldname": "monthly_repayment_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -646,6 +667,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "account_info",
|
"fieldname": "account_info",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -678,6 +700,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "mode_of_payment",
|
"fieldname": "mode_of_payment",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -711,6 +734,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "payment_account",
|
"fieldname": "payment_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -744,6 +768,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "column_break_9",
|
"fieldname": "column_break_9",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -775,6 +800,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "loan_account",
|
"fieldname": "loan_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -808,6 +834,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "interest_income_account",
|
"fieldname": "interest_income_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -841,6 +868,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "section_break_15",
|
"fieldname": "section_break_15",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -873,6 +901,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "repayment_schedule",
|
"fieldname": "repayment_schedule",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -906,6 +935,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "section_break_17",
|
"fieldname": "section_break_17",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -939,6 +969,7 @@
|
|||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "total_payment",
|
"fieldname": "total_payment",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -972,6 +1003,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "column_break_19",
|
"fieldname": "column_break_19",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -1004,6 +1036,7 @@
|
|||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "total_interest_payable",
|
"fieldname": "total_interest_payable",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -1037,6 +1070,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "total_amount_paid",
|
"fieldname": "total_amount_paid",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -1070,6 +1104,7 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 0,
|
"columns": 0,
|
||||||
|
"fetch_if_empty": 0,
|
||||||
"fieldname": "amended_from",
|
"fieldname": "amended_from",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -1106,7 +1141,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2018-08-21 16:15:53.267145",
|
"modified": "2019-07-10 13:04:20.953694",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Loan",
|
"name": "Loan",
|
||||||
@@ -1149,7 +1184,6 @@
|
|||||||
"set_user_permissions": 0,
|
"set_user_permissions": 0,
|
||||||
"share": 0,
|
"share": 0,
|
||||||
"submit": 0,
|
"submit": 0,
|
||||||
"user_permission_doctypes": "[\"Employee\"]",
|
|
||||||
"write": 0
|
"write": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -6,29 +6,33 @@ from __future__ import unicode_literals
|
|||||||
import frappe, math, json
|
import frappe, math, json
|
||||||
import erpnext
|
import erpnext
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import flt, rounded, add_months, nowdate
|
from frappe.utils import flt, rounded, add_months, nowdate, getdate
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
|
|
||||||
class Loan(AccountsController):
|
class Loan(AccountsController):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
check_repayment_method(self.repayment_method, self.loan_amount, self.monthly_repayment_amount, self.repayment_periods)
|
validate_repayment_method(self.repayment_method, self.loan_amount, self.monthly_repayment_amount, self.repayment_periods)
|
||||||
|
self.set_missing_fields()
|
||||||
|
self.make_repayment_schedule()
|
||||||
|
self.set_repayment_period()
|
||||||
|
self.calculate_totals()
|
||||||
|
|
||||||
|
def set_missing_fields(self):
|
||||||
if not self.company:
|
if not self.company:
|
||||||
self.company = erpnext.get_default_company()
|
self.company = erpnext.get_default_company()
|
||||||
|
|
||||||
if not self.posting_date:
|
if not self.posting_date:
|
||||||
self.posting_date = nowdate()
|
self.posting_date = nowdate()
|
||||||
|
|
||||||
if self.loan_type and not self.rate_of_interest:
|
if self.loan_type and not self.rate_of_interest:
|
||||||
self.rate_of_interest = frappe.db.get_value("Loan Type", self.loan_type, "rate_of_interest")
|
self.rate_of_interest = frappe.db.get_value("Loan Type", self.loan_type, "rate_of_interest")
|
||||||
|
|
||||||
if self.repayment_method == "Repay Over Number of Periods":
|
if self.repayment_method == "Repay Over Number of Periods":
|
||||||
self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
|
self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
|
||||||
|
|
||||||
if self.status == "Repaid/Closed":
|
if self.status == "Repaid/Closed":
|
||||||
self.total_amount_paid = self.total_payment
|
self.total_amount_paid = self.total_payment
|
||||||
if self.status == 'Disbursed' and self.repayment_start_date < self.disbursement_date:
|
|
||||||
frappe.throw(_("Repayment Start Date cannot be before Disbursement Date."))
|
|
||||||
|
|
||||||
if self.status == "Disbursed":
|
|
||||||
self.make_repayment_schedule()
|
|
||||||
self.set_repayment_period()
|
|
||||||
self.calculate_totals()
|
|
||||||
|
|
||||||
def make_jv_entry(self):
|
def make_jv_entry(self):
|
||||||
self.check_permission('write')
|
self.check_permission('write')
|
||||||
@@ -105,20 +109,31 @@ def update_total_amount_paid(doc):
|
|||||||
frappe.db.set_value("Loan", doc.name, "total_amount_paid", total_amount_paid)
|
frappe.db.set_value("Loan", doc.name, "total_amount_paid", total_amount_paid)
|
||||||
|
|
||||||
def update_disbursement_status(doc):
|
def update_disbursement_status(doc):
|
||||||
disbursement = frappe.db.sql("""select posting_date, ifnull(sum(credit_in_account_currency), 0) as disbursed_amount
|
disbursement = frappe.db.sql("""
|
||||||
from `tabGL Entry` where account = %s and against_voucher_type = 'Loan' and against_voucher = %s""",
|
select posting_date, ifnull(sum(credit_in_account_currency), 0) as disbursed_amount
|
||||||
(doc.payment_account, doc.name), as_dict=1)[0]
|
from `tabGL Entry`
|
||||||
if disbursement.disbursed_amount == doc.loan_amount:
|
where account = %s and against_voucher_type = 'Loan' and against_voucher = %s
|
||||||
frappe.db.set_value("Loan", doc.name , "status", "Disbursed")
|
""", (doc.payment_account, doc.name), as_dict=1)[0]
|
||||||
if disbursement.disbursed_amount == 0:
|
|
||||||
frappe.db.set_value("Loan", doc.name , "status", "Sanctioned")
|
|
||||||
if disbursement.disbursed_amount > doc.loan_amount:
|
|
||||||
frappe.throw(_("Disbursed Amount cannot be greater than Loan Amount {0}").format(doc.loan_amount))
|
|
||||||
if disbursement.disbursed_amount > 0:
|
|
||||||
frappe.db.set_value("Loan", doc.name , "disbursement_date", disbursement.posting_date)
|
|
||||||
frappe.db.set_value("Loan", doc.name , "repayment_start_date", disbursement.posting_date)
|
|
||||||
|
|
||||||
def check_repayment_method(repayment_method, loan_amount, monthly_repayment_amount, repayment_periods):
|
disbursement_date = None
|
||||||
|
if not disbursement or disbursement.disbursed_amount == 0:
|
||||||
|
status = "Sanctioned"
|
||||||
|
elif disbursement.disbursed_amount == doc.loan_amount:
|
||||||
|
disbursement_date = disbursement.posting_date
|
||||||
|
status = "Disbursed"
|
||||||
|
elif disbursement.disbursed_amount > doc.loan_amount:
|
||||||
|
frappe.throw(_("Disbursed Amount cannot be greater than Loan Amount {0}").format(doc.loan_amount))
|
||||||
|
|
||||||
|
if status == 'Disbursed' and getdate(disbursement_date) > getdate(frappe.db.get_value("Loan", doc.name, "repayment_start_date")):
|
||||||
|
frappe.throw(_("Disbursement Date cannot be after Loan Repayment Start Date"))
|
||||||
|
|
||||||
|
frappe.db.sql("""
|
||||||
|
update `tabLoan`
|
||||||
|
set status = %s, disbursement_date = %s
|
||||||
|
where name = %s
|
||||||
|
""", (status, disbursement_date, doc.name))
|
||||||
|
|
||||||
|
def validate_repayment_method(repayment_method, loan_amount, monthly_repayment_amount, repayment_periods):
|
||||||
if repayment_method == "Repay Over Number of Periods" and not repayment_periods:
|
if repayment_method == "Repay Over Number of Periods" and not repayment_periods:
|
||||||
frappe.throw(_("Please enter Repayment Periods"))
|
frappe.throw(_("Please enter Repayment Periods"))
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,8 @@ frappe.ui.form.on('Loan Application', {
|
|||||||
},
|
},
|
||||||
add_toolbar_buttons: function(frm) {
|
add_toolbar_buttons: function(frm) {
|
||||||
if (frm.doc.status == "Approved") {
|
if (frm.doc.status == "Approved") {
|
||||||
frm.add_custom_button(__('Loan'), function() {
|
frm.add_custom_button(__('Create Loan'), function() {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
type: "GET",
|
|
||||||
method: "erpnext.hr.doctype.loan_application.loan_application.make_loan",
|
method: "erpnext.hr.doctype.loan_application.loan_application.make_loan",
|
||||||
args: {
|
args: {
|
||||||
"source_name": frm.doc.name
|
"source_name": frm.doc.name
|
||||||
@@ -37,7 +36,7 @@ frappe.ui.form.on('Loan Application', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
}).addClass("btn-primary");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ from frappe.utils import flt, rounded
|
|||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
from erpnext.hr.doctype.loan.loan import get_monthly_repayment_amount, check_repayment_method
|
from erpnext.hr.doctype.loan.loan import get_monthly_repayment_amount, validate_repayment_method
|
||||||
|
|
||||||
class LoanApplication(Document):
|
class LoanApplication(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
check_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods)
|
validate_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods)
|
||||||
self.validate_loan_amount()
|
self.validate_loan_amount()
|
||||||
self.get_repayment_details()
|
self.get_repayment_details()
|
||||||
|
|
||||||
@@ -29,9 +29,12 @@ class LoanApplication(Document):
|
|||||||
if self.repayment_method == "Repay Fixed Amount per Period":
|
if self.repayment_method == "Repay Fixed Amount per Period":
|
||||||
monthly_interest_rate = flt(self.rate_of_interest) / (12 *100)
|
monthly_interest_rate = flt(self.rate_of_interest) / (12 *100)
|
||||||
if monthly_interest_rate:
|
if monthly_interest_rate:
|
||||||
self.repayment_periods = math.ceil((math.log(self.repayment_amount) -
|
min_repayment_amount = self.loan_amount*monthly_interest_rate
|
||||||
math.log(self.repayment_amount - (self.loan_amount*monthly_interest_rate))) /
|
if self.repayment_amount - min_repayment_amount < 0:
|
||||||
(math.log(1 + monthly_interest_rate)))
|
frappe.throw(_("Repayment Amount must be greater than " \
|
||||||
|
+ str(flt(min_repayment_amount, 2))))
|
||||||
|
self.repayment_periods = math.ceil(math.log(self.repayment_amount) -
|
||||||
|
math.log(self.repayment_amount - min_repayment_amount) /(math.log(1 + monthly_interest_rate)))
|
||||||
else:
|
else:
|
||||||
self.repayment_periods = self.loan_amount / self.repayment_amount
|
self.repayment_periods = self.loan_amount / self.repayment_amount
|
||||||
|
|
||||||
|
|||||||
@@ -31,21 +31,22 @@ class TestLoanApplication(unittest.TestCase):
|
|||||||
"rate_of_interest": 9.2,
|
"rate_of_interest": 9.2,
|
||||||
"loan_amount": 250000,
|
"loan_amount": 250000,
|
||||||
"repayment_method": "Repay Over Number of Periods",
|
"repayment_method": "Repay Over Number of Periods",
|
||||||
"repayment_periods": 24
|
"repayment_periods": 18
|
||||||
})
|
})
|
||||||
loan_application.insert()
|
loan_application.insert()
|
||||||
|
|
||||||
|
|
||||||
def test_loan_totals(self):
|
def test_loan_totals(self):
|
||||||
loan_application = frappe.get_doc("Loan Application", {"applicant":self.applicant})
|
loan_application = frappe.get_doc("Loan Application", {"applicant":self.applicant})
|
||||||
self.assertEquals(loan_application.repayment_amount, 11445)
|
|
||||||
self.assertEquals(loan_application.total_payable_interest, 24657)
|
|
||||||
self.assertEquals(loan_application.total_payable_amount, 274657)
|
|
||||||
|
|
||||||
loan_application.repayment_method = "Repay Fixed Amount per Period"
|
self.assertEqual(loan_application.total_payable_interest, 18599)
|
||||||
loan_application.repayment_amount = 15000
|
self.assertEqual(loan_application.total_payable_amount, 268599)
|
||||||
|
self.assertEqual(loan_application.repayment_amount, 14923)
|
||||||
|
|
||||||
|
loan_application.repayment_periods = 24
|
||||||
loan_application.save()
|
loan_application.save()
|
||||||
|
loan_application.reload()
|
||||||
|
|
||||||
self.assertEqual(loan_application.repayment_periods, 18)
|
self.assertEqual(loan_application.total_payable_interest, 24657)
|
||||||
self.assertEqual(loan_application.total_payable_interest, 18506)
|
self.assertEqual(loan_application.total_payable_amount, 274657)
|
||||||
self.assertEqual(loan_application.total_payable_amount, 268506)
|
self.assertEqual(loan_application.repayment_amount, 11445)
|
||||||
@@ -506,6 +506,7 @@ erpnext.patches.v10_0.update_hub_connector_domain
|
|||||||
erpnext.patches.v10_0.set_student_party_type
|
erpnext.patches.v10_0.set_student_party_type
|
||||||
erpnext.patches.v10_0.update_project_in_sle
|
erpnext.patches.v10_0.update_project_in_sle
|
||||||
erpnext.patches.v10_0.fix_reserved_qty_for_sub_contract
|
erpnext.patches.v10_0.fix_reserved_qty_for_sub_contract
|
||||||
|
erpnext.patches.v10_0.repost_requested_qty_for_non_stock_uom_items
|
||||||
erpnext.patches.v11_0.merge_land_unit_with_location
|
erpnext.patches.v11_0.merge_land_unit_with_location
|
||||||
erpnext.patches.v11_0.add_index_on_nestedset_doctypes
|
erpnext.patches.v11_0.add_index_on_nestedset_doctypes
|
||||||
erpnext.patches.v11_0.remove_modules_setup_page
|
erpnext.patches.v11_0.remove_modules_setup_page
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Copyright (c) 2019, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||||
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty
|
||||||
|
|
||||||
|
count=0
|
||||||
|
for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse
|
||||||
|
from `tabMaterial Request Item` where docstatus = 1 and stock_uom<>uom"""):
|
||||||
|
try:
|
||||||
|
count += 1
|
||||||
|
update_bin_qty(item_code, warehouse, {
|
||||||
|
"indented_qty": get_indented_qty(item_code, warehouse),
|
||||||
|
})
|
||||||
|
if count % 200 == 0:
|
||||||
|
frappe.db.commit()
|
||||||
|
except:
|
||||||
|
frappe.db.rollback()
|
||||||
@@ -1290,11 +1290,14 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(!r.exc) {
|
if(!r.exc) {
|
||||||
me.frm.set_value("taxes", r.message);
|
if(me.frm.doc.shipping_rule && me.frm.doc.taxes) {
|
||||||
|
for (let tax of r.message) {
|
||||||
|
me.frm.add_child("taxes", tax);
|
||||||
|
}
|
||||||
|
|
||||||
if(me.frm.doc.shipping_rule) {
|
refresh_field("taxes");
|
||||||
me.frm.script_manager.trigger("shipping_rule");
|
|
||||||
} else {
|
} else {
|
||||||
|
me.frm.set_value("taxes", r.message);
|
||||||
me.calculate_taxes_and_totals();
|
me.calculate_taxes_and_totals();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
|
|||||||
var me = this;
|
var me = this;
|
||||||
if (this.frm.doc.customer) {
|
if (this.frm.doc.customer) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details",
|
method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details_with_points",
|
||||||
args: {
|
args: {
|
||||||
"customer": me.frm.doc.customer,
|
"customer": me.frm.doc.customer,
|
||||||
"expiry_date": me.frm.doc.posting_date,
|
"expiry_date": me.frm.doc.posting_date,
|
||||||
@@ -1694,7 +1694,13 @@ class Payment {
|
|||||||
fieldtype: 'Check',
|
fieldtype: 'Check',
|
||||||
label: 'Redeem Loyalty Points',
|
label: 'Redeem Loyalty Points',
|
||||||
fieldname: 'redeem_loyalty_points',
|
fieldname: 'redeem_loyalty_points',
|
||||||
onchange: () => {
|
onchange: async function () {
|
||||||
|
if (!cint(me.dialog.get_value('redeem_loyalty_points'))) {
|
||||||
|
await Promise.all([
|
||||||
|
me.frm.set_value('loyalty_points', 0),
|
||||||
|
me.dialog.set_value('loyalty_points', 0)
|
||||||
|
]);
|
||||||
|
}
|
||||||
me.update_cur_frm_value("redeem_loyalty_points", () => {
|
me.update_cur_frm_value("redeem_loyalty_points", () => {
|
||||||
frappe.flags.redeem_loyalty_points = false;
|
frappe.flags.redeem_loyalty_points = false;
|
||||||
me.update_loyalty_points();
|
me.update_loyalty_points();
|
||||||
@@ -1838,13 +1844,14 @@ class Payment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
update_loyalty_points() {
|
async update_loyalty_points() {
|
||||||
if (this.dialog.get_value("redeem_loyalty_points")) {
|
const { loyalty_points, loyalty_amount } = this.frm.doc;
|
||||||
this.dialog.set_value("loyalty_points", this.frm.doc.loyalty_points);
|
await Promise.all([
|
||||||
this.dialog.set_value("loyalty_amount", this.frm.doc.loyalty_amount);
|
this.dialog.set_value("loyalty_points", loyalty_points),
|
||||||
this.update_payment_amount();
|
this.dialog.set_value("loyalty_amount", loyalty_amount)
|
||||||
this.show_paid_amount();
|
]);
|
||||||
}
|
this.update_payment_amount();
|
||||||
|
this.show_paid_amount();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ def place_order():
|
|||||||
quotation.flags.ignore_permissions = True
|
quotation.flags.ignore_permissions = True
|
||||||
quotation.submit()
|
quotation.submit()
|
||||||
|
|
||||||
if quotation.lead:
|
if quotation.quotation_to == 'Lead' and quotation.party_name:
|
||||||
# company used to create customer accounts
|
# company used to create customer accounts
|
||||||
frappe.defaults.set_user_default("company", quotation.company)
|
frappe.defaults.set_user_default("company", quotation.company)
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ def get_reserved_qty(item_code, warehouse):
|
|||||||
return flt(reserved_qty[0][0]) if reserved_qty else 0
|
return flt(reserved_qty[0][0]) if reserved_qty else 0
|
||||||
|
|
||||||
def get_indented_qty(item_code, warehouse):
|
def get_indented_qty(item_code, warehouse):
|
||||||
indented_qty = frappe.db.sql("""select sum(mr_item.qty - mr_item.ordered_qty)
|
indented_qty = frappe.db.sql("""select sum((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor)
|
||||||
from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
|
from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
|
||||||
where mr_item.item_code=%s and mr_item.warehouse=%s
|
where mr_item.item_code=%s and mr_item.warehouse=%s
|
||||||
and mr_item.qty > mr_item.ordered_qty and mr_item.parent=mr.name
|
and mr_item.qty > mr_item.ordered_qty and mr_item.parent=mr.name
|
||||||
|
|||||||
Reference in New Issue
Block a user