Merge branch 'develop' into naming-series-proj
This commit is contained in:
commit
a222081316
@ -638,20 +638,12 @@ $.extend(erpnext.journal_entry, {
|
|||||||
return { filters: filters };
|
return { filters: filters };
|
||||||
},
|
},
|
||||||
|
|
||||||
reverse_journal_entry: function(frm) {
|
reverse_journal_entry: function() {
|
||||||
var me = frm.doc;
|
frappe.model.open_mapped_doc({
|
||||||
for(var i=0; i<me.accounts.length; i++) {
|
method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry",
|
||||||
me.accounts[i].credit += me.accounts[i].debit;
|
frm: cur_frm
|
||||||
me.accounts[i].debit = me.accounts[i].credit - me.accounts[i].debit;
|
})
|
||||||
me.accounts[i].credit -= me.accounts[i].debit;
|
},
|
||||||
me.accounts[i].credit_in_account_currency = me.accounts[i].credit;
|
|
||||||
me.accounts[i].debit_in_account_currency = me.accounts[i].debit;
|
|
||||||
me.accounts[i].reference_type = "Journal Entry";
|
|
||||||
me.accounts[i].reference_name = me.name
|
|
||||||
}
|
|
||||||
frm.copy_doc();
|
|
||||||
cur_frm.reload_doc();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$.extend(erpnext.journal_entry, {
|
$.extend(erpnext.journal_entry, {
|
||||||
|
@ -1021,3 +1021,34 @@ def make_inter_company_journal_entry(name, voucher_type, company):
|
|||||||
journal_entry.posting_date = nowdate()
|
journal_entry.posting_date = nowdate()
|
||||||
journal_entry.inter_company_journal_entry_reference = name
|
journal_entry.inter_company_journal_entry_reference = name
|
||||||
return journal_entry.as_dict()
|
return journal_entry.as_dict()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def make_reverse_journal_entry(source_name, target_doc=None, ignore_permissions=False):
|
||||||
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
|
||||||
|
def update_accounts(source, target, source_parent):
|
||||||
|
target.reference_type = "Journal Entry"
|
||||||
|
target.reference_name = source_parent.name
|
||||||
|
|
||||||
|
doclist = get_mapped_doc("Journal Entry", source_name, {
|
||||||
|
"Journal Entry": {
|
||||||
|
"doctype": "Journal Entry",
|
||||||
|
"validation": {
|
||||||
|
"docstatus": ["=", 1]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Journal Entry Account": {
|
||||||
|
"doctype": "Journal Entry Account",
|
||||||
|
"field_map": {
|
||||||
|
"account_currency": "account_currency",
|
||||||
|
"exchange_rate": "exchange_rate",
|
||||||
|
"debit_in_account_currency": "credit_in_account_currency",
|
||||||
|
"debit": "credit",
|
||||||
|
"credit_in_account_currency": "debit_in_account_currency",
|
||||||
|
"credit": "debit",
|
||||||
|
},
|
||||||
|
"postprocess": update_accounts,
|
||||||
|
},
|
||||||
|
}, target_doc, ignore_permissions=ignore_permissions)
|
||||||
|
|
||||||
|
return doclist
|
@ -167,6 +167,49 @@ class TestJournalEntry(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertFalse(gle)
|
self.assertFalse(gle)
|
||||||
|
|
||||||
|
def test_reverse_journal_entry(self):
|
||||||
|
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
|
||||||
|
jv = make_journal_entry("_Test Bank USD - _TC",
|
||||||
|
"Sales - _TC", 100, exchange_rate=50, save=False)
|
||||||
|
|
||||||
|
jv.get("accounts")[1].credit_in_account_currency = 5000
|
||||||
|
jv.get("accounts")[1].exchange_rate = 1
|
||||||
|
jv.submit()
|
||||||
|
|
||||||
|
rjv = make_reverse_journal_entry(jv.name)
|
||||||
|
rjv.posting_date = nowdate()
|
||||||
|
rjv.submit()
|
||||||
|
|
||||||
|
|
||||||
|
gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
|
||||||
|
debit_in_account_currency, credit_in_account_currency
|
||||||
|
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
|
||||||
|
order by account asc""", rjv.name, as_dict=1)
|
||||||
|
|
||||||
|
self.assertTrue(gl_entries)
|
||||||
|
|
||||||
|
|
||||||
|
expected_values = {
|
||||||
|
"_Test Bank USD - _TC": {
|
||||||
|
"account_currency": "USD",
|
||||||
|
"debit": 0,
|
||||||
|
"debit_in_account_currency": 0,
|
||||||
|
"credit": 5000,
|
||||||
|
"credit_in_account_currency": 100,
|
||||||
|
},
|
||||||
|
"Sales - _TC": {
|
||||||
|
"account_currency": "INR",
|
||||||
|
"debit": 5000,
|
||||||
|
"debit_in_account_currency": 5000,
|
||||||
|
"credit": 0,
|
||||||
|
"credit_in_account_currency": 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
|
||||||
|
for i, gle in enumerate(gl_entries):
|
||||||
|
self.assertEqual(expected_values[gle.account][field], gle[field])
|
||||||
|
|
||||||
def test_disallow_change_in_account_currency_for_a_party(self):
|
def test_disallow_change_in_account_currency_for_a_party(self):
|
||||||
# create jv in USD
|
# create jv in USD
|
||||||
jv = make_journal_entry("_Test Bank USD - _TC",
|
jv = make_journal_entry("_Test Bank USD - _TC",
|
||||||
|
@ -1,134 +1,66 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_events_in_timeline": 0,
|
"allow_rename": 1,
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"autoname": "field:title",
|
"autoname": "field:title",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2018-11-22 23:38:39.668804",
|
"creation": "2018-11-22 23:38:39.668804",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"title"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "title",
|
"fieldname": "title",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Title",
|
"label": "Title",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 1
|
"unique": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"hide_heading": 0,
|
"links": [],
|
||||||
"hide_toolbar": 0,
|
"modified": "2020-08-30 19:41:25.783852",
|
||||||
"idx": 0,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2020-01-15 17:14:28.951793",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Tax Category",
|
"name": "Tax Category",
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 1,
|
"export": 1,
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 1,
|
"export": 1,
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Accounts Manager",
|
"role": "Accounts Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 0,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 1,
|
"export": 1,
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Accounts User",
|
"role": "Accounts User",
|
||||||
"set_user_permissions": 0,
|
"share": 1
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"track_changes": 1
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
@ -45,8 +45,8 @@ def validate_accounting_period(gl_map):
|
|||||||
}, as_dict=1)
|
}, as_dict=1)
|
||||||
|
|
||||||
if accounting_periods:
|
if accounting_periods:
|
||||||
frappe.throw(_("You can't create accounting entries in the closed accounting period {0}")
|
frappe.throw(_("You cannot create or cancel any accounting entries with in the closed Accounting Period {0}")
|
||||||
.format(accounting_periods[0].name), ClosedAccountingPeriod)
|
.format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod)
|
||||||
|
|
||||||
def process_gl_map(gl_map, merge_entries=True):
|
def process_gl_map(gl_map, merge_entries=True):
|
||||||
if merge_entries:
|
if merge_entries:
|
||||||
@ -301,8 +301,9 @@ def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
|
|||||||
})
|
})
|
||||||
|
|
||||||
if gl_entries:
|
if gl_entries:
|
||||||
set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no'])
|
validate_accounting_period(gl_entries)
|
||||||
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
|
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
|
||||||
|
set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no'])
|
||||||
|
|
||||||
for entry in gl_entries:
|
for entry in gl_entries:
|
||||||
entry['name'] = None
|
entry['name'] = None
|
||||||
@ -342,7 +343,7 @@ def set_as_cancel(voucher_type, voucher_no):
|
|||||||
"""
|
"""
|
||||||
Set is_cancelled=1 in all original gl entries for the voucher
|
Set is_cancelled=1 in all original gl entries for the voucher
|
||||||
"""
|
"""
|
||||||
frappe.db.sql("""update `tabGL Entry` set is_cancelled = 1,
|
frappe.db.sql("""UPDATE `tabGL Entry` SET is_cancelled = 1,
|
||||||
modified=%s, modified_by=%s
|
modified=%s, modified_by=%s
|
||||||
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
|
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
|
||||||
(now(), frappe.session.user, voucher_type, voucher_no))
|
(now(), frappe.session.user, voucher_type, voucher_no))
|
||||||
|
@ -173,7 +173,7 @@ class PartyLedgerSummaryReport(object):
|
|||||||
from `tabGL Entry` gle
|
from `tabGL Entry` gle
|
||||||
{join}
|
{join}
|
||||||
where
|
where
|
||||||
gle.docstatus < 2 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != ''
|
gle.docstatus < 2 and gle.is_cancelled = 0 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != ''
|
||||||
and gle.posting_date <= %(to_date)s {conditions}
|
and gle.posting_date <= %(to_date)s {conditions}
|
||||||
order by gle.posting_date
|
order by gle.posting_date
|
||||||
""".format(join=join, join_field=join_field, conditions=conditions), self.filters, as_dict=True)
|
""".format(join=join, join_field=join_field, conditions=conditions), self.filters, as_dict=True)
|
||||||
@ -248,7 +248,7 @@ class PartyLedgerSummaryReport(object):
|
|||||||
from
|
from
|
||||||
`tabGL Entry`
|
`tabGL Entry`
|
||||||
where
|
where
|
||||||
docstatus < 2
|
docstatus < 2 and is_cancelled = 0
|
||||||
and (voucher_type, voucher_no) in (
|
and (voucher_type, voucher_no) in (
|
||||||
select voucher_type, voucher_no from `tabGL Entry` gle, `tabAccount` acc
|
select voucher_type, voucher_no from `tabGL Entry` gle, `tabAccount` acc
|
||||||
where acc.name = gle.account and acc.account_type = '{income_or_expense}'
|
where acc.name = gle.account and acc.account_type = '{income_or_expense}'
|
||||||
|
@ -12,7 +12,22 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reqd: 1,
|
"fieldname":"from_date",
|
||||||
|
"label": __("From Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": "80",
|
||||||
|
"reqd": 1,
|
||||||
|
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"to_date",
|
||||||
|
"label": __("To Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": "80",
|
||||||
|
"reqd": 1,
|
||||||
|
"default": frappe.datetime.get_today()
|
||||||
|
},
|
||||||
|
{
|
||||||
default: "",
|
default: "",
|
||||||
options: "Item",
|
options: "Item",
|
||||||
label: __("Item"),
|
label: __("Item"),
|
||||||
@ -45,13 +60,12 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldtype: "Link",
|
fieldtype: "MultiSelectList",
|
||||||
label: __("Supplier Quotation"),
|
label: __("Supplier Quotation"),
|
||||||
options: "Supplier Quotation",
|
|
||||||
fieldname: "supplier_quotation",
|
fieldname: "supplier_quotation",
|
||||||
default: "",
|
default: "",
|
||||||
get_query: () => {
|
get_data: function(txt) {
|
||||||
return { filters: { "docstatus": ["<", 2] } }
|
return frappe.db.get_link_options('Supplier Quotation', txt, {'docstatus': ["<", 2]});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -63,9 +77,30 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
|||||||
get_query: () => {
|
get_query: () => {
|
||||||
return { filters: { "docstatus": ["<", 2] } }
|
return { filters: { "docstatus": ["<", 2] } }
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Check",
|
||||||
|
label: __("Include Expired"),
|
||||||
|
fieldname: "include_expired",
|
||||||
|
default: 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
formatter: (value, row, column, data, default_formatter) => {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
|
||||||
|
if(column.fieldname === "valid_till" && data.valid_till){
|
||||||
|
if(frappe.datetime.get_diff(data.valid_till, frappe.datetime.nowdate()) <= 1){
|
||||||
|
value = `<div style="color:red">${value}</div>`;
|
||||||
|
}
|
||||||
|
else if (frappe.datetime.get_diff(data.valid_till, frappe.datetime.nowdate()) <= 7){
|
||||||
|
value = `<div style="color:darkorange">${value}</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
|
||||||
onload: (report) => {
|
onload: (report) => {
|
||||||
// Create a button for setting the default supplier
|
// Create a button for setting the default supplier
|
||||||
report.page.add_inner_button(__("Select Default Supplier"), () => {
|
report.page.add_inner_button(__("Select Default Supplier"), () => {
|
||||||
|
@ -16,44 +16,49 @@ def execute(filters=None):
|
|||||||
supplier_quotation_data = get_data(filters, conditions)
|
supplier_quotation_data = get_data(filters, conditions)
|
||||||
columns = get_columns()
|
columns = get_columns()
|
||||||
|
|
||||||
data, chart_data = prepare_data(supplier_quotation_data)
|
data, chart_data = prepare_data(supplier_quotation_data, filters)
|
||||||
|
message = get_message()
|
||||||
|
|
||||||
return columns, data, None, chart_data
|
return columns, data, message, chart_data
|
||||||
|
|
||||||
def get_conditions(filters):
|
def get_conditions(filters):
|
||||||
conditions = ""
|
conditions = ""
|
||||||
|
if filters.get("item_code"):
|
||||||
|
conditions += " AND sqi.item_code = %(item_code)s"
|
||||||
|
|
||||||
if filters.get("supplier_quotation"):
|
if filters.get("supplier_quotation"):
|
||||||
conditions += " AND sqi.parent = %(supplier_quotation)s"
|
conditions += " AND sqi.parent in %(supplier_quotation)s"
|
||||||
|
|
||||||
if filters.get("request_for_quotation"):
|
if filters.get("request_for_quotation"):
|
||||||
conditions += " AND sqi.request_for_quotation = %(request_for_quotation)s"
|
conditions += " AND sqi.request_for_quotation = %(request_for_quotation)s"
|
||||||
|
|
||||||
if filters.get("supplier"):
|
if filters.get("supplier"):
|
||||||
conditions += " AND sq.supplier in %(supplier)s"
|
conditions += " AND sq.supplier in %(supplier)s"
|
||||||
|
|
||||||
|
if not filters.get("include_expired"):
|
||||||
|
conditions += " AND sq.status != 'Expired'"
|
||||||
|
|
||||||
return conditions
|
return conditions
|
||||||
|
|
||||||
def get_data(filters, conditions):
|
def get_data(filters, conditions):
|
||||||
if not filters.get("item_code"):
|
|
||||||
return []
|
|
||||||
|
|
||||||
supplier_quotation_data = frappe.db.sql("""SELECT
|
supplier_quotation_data = frappe.db.sql("""SELECT
|
||||||
sqi.parent, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation,
|
sqi.parent, sqi.item_code, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation,
|
||||||
sq.supplier
|
sqi.lead_time_days, sq.supplier, sq.valid_till
|
||||||
FROM
|
FROM
|
||||||
`tabSupplier Quotation Item` sqi,
|
`tabSupplier Quotation Item` sqi,
|
||||||
`tabSupplier Quotation` sq
|
`tabSupplier Quotation` sq
|
||||||
WHERE
|
WHERE
|
||||||
sqi.item_code = %(item_code)s
|
sqi.parent = sq.name
|
||||||
AND sqi.parent = sq.name
|
|
||||||
AND sqi.docstatus < 2
|
AND sqi.docstatus < 2
|
||||||
AND sq.company = %(company)s
|
AND sq.company = %(company)s
|
||||||
AND sq.status != 'Expired'
|
AND sq.transaction_date between %(from_date)s and %(to_date)s
|
||||||
{0}""".format(conditions), filters, as_dict=1)
|
{0}
|
||||||
|
order by sq.transaction_date, sqi.item_code""".format(conditions), filters, as_dict=1)
|
||||||
|
|
||||||
return supplier_quotation_data
|
return supplier_quotation_data
|
||||||
|
|
||||||
def prepare_data(supplier_quotation_data):
|
def prepare_data(supplier_quotation_data, filters):
|
||||||
out, suppliers, qty_list = [], [], []
|
out, suppliers, qty_list, chart_data = [], [], [], []
|
||||||
supplier_wise_map = defaultdict(list)
|
supplier_wise_map = defaultdict(list)
|
||||||
supplier_qty_price_map = {}
|
supplier_qty_price_map = {}
|
||||||
|
|
||||||
@ -70,17 +75,21 @@ def prepare_data(supplier_quotation_data):
|
|||||||
exchange_rate = 1
|
exchange_rate = 1
|
||||||
|
|
||||||
row = {
|
row = {
|
||||||
|
"item_code": data.get('item_code'),
|
||||||
"quotation": data.get("parent"),
|
"quotation": data.get("parent"),
|
||||||
"qty": data.get("qty"),
|
"qty": data.get("qty"),
|
||||||
"price": flt(data.get("rate") * exchange_rate, float_precision),
|
"price": flt(data.get("rate") * exchange_rate, float_precision),
|
||||||
"uom": data.get("uom"),
|
"uom": data.get("uom"),
|
||||||
"request_for_quotation": data.get("request_for_quotation"),
|
"request_for_quotation": data.get("request_for_quotation"),
|
||||||
|
"valid_till": data.get('valid_till'),
|
||||||
|
"lead_time_days": data.get('lead_time_days')
|
||||||
}
|
}
|
||||||
|
|
||||||
# map for report view of form {'supplier1':[{},{},...]}
|
# map for report view of form {'supplier1':[{},{},...]}
|
||||||
supplier_wise_map[supplier].append(row)
|
supplier_wise_map[supplier].append(row)
|
||||||
|
|
||||||
# map for chart preparation of the form {'supplier1': {'qty': 'price'}}
|
# map for chart preparation of the form {'supplier1': {'qty': 'price'}}
|
||||||
|
if filters.get("item_code"):
|
||||||
if not supplier in supplier_qty_price_map:
|
if not supplier in supplier_qty_price_map:
|
||||||
supplier_qty_price_map[supplier] = {}
|
supplier_qty_price_map[supplier] = {}
|
||||||
supplier_qty_price_map[supplier][row["qty"]] = row["price"]
|
supplier_qty_price_map[supplier][row["qty"]] = row["price"]
|
||||||
@ -97,6 +106,7 @@ def prepare_data(supplier_quotation_data):
|
|||||||
for entry in supplier_wise_map[supplier]:
|
for entry in supplier_wise_map[supplier]:
|
||||||
out.append(entry)
|
out.append(entry)
|
||||||
|
|
||||||
|
if filters.get("item_code"):
|
||||||
chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map)
|
chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map)
|
||||||
|
|
||||||
return out, chart_data
|
return out, chart_data
|
||||||
@ -117,9 +127,10 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map):
|
|||||||
data_points_map[qty].append(None)
|
data_points_map[qty].append(None)
|
||||||
|
|
||||||
dataset = []
|
dataset = []
|
||||||
|
currency_symbol = frappe.db.get_value("Currency", frappe.db.get_default("currency"), "symbol")
|
||||||
for qty in qty_list:
|
for qty in qty_list:
|
||||||
datapoints = {
|
datapoints = {
|
||||||
"name": _("Price for Qty ") + str(qty),
|
"name": currency_symbol + " (Qty " + str(qty) + " )",
|
||||||
"values": data_points_map[qty]
|
"values": data_points_map[qty]
|
||||||
}
|
}
|
||||||
dataset.append(datapoints)
|
dataset.append(datapoints)
|
||||||
@ -140,14 +151,21 @@ def get_columns():
|
|||||||
"label": _("Supplier"),
|
"label": _("Supplier"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Supplier",
|
"options": "Supplier",
|
||||||
|
"width": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"label": _("Item"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Item",
|
||||||
"width": 200
|
"width": 200
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "quotation",
|
"fieldname": "uom",
|
||||||
"label": _("Supplier Quotation"),
|
"label": _("UOM"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Supplier Quotation",
|
"options": "UOM",
|
||||||
"width": 200
|
"width": 90
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "qty",
|
"fieldname": "qty",
|
||||||
@ -163,19 +181,43 @@ def get_columns():
|
|||||||
"width": 110
|
"width": 110
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "uom",
|
"fieldname": "quotation",
|
||||||
"label": _("UOM"),
|
"label": _("Supplier Quotation"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "UOM",
|
"options": "Supplier Quotation",
|
||||||
"width": 90
|
"width": 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "valid_till",
|
||||||
|
"label": _("Valid Till"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "lead_time_days",
|
||||||
|
"label": _("Lead Time (Days)"),
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"width": 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "request_for_quotation",
|
"fieldname": "request_for_quotation",
|
||||||
"label": _("Request for Quotation"),
|
"label": _("Request for Quotation"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Request for Quotation",
|
"options": "Request for Quotation",
|
||||||
"width": 200
|
"width": 150
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
def get_message():
|
||||||
|
return """<span class="indicator">
|
||||||
|
Valid till :
|
||||||
|
</span>
|
||||||
|
<span class="indicator orange">
|
||||||
|
Expires in a week or less
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="indicator red">
|
||||||
|
Expires today / Already Expired
|
||||||
|
</span>"""
|
@ -17,6 +17,8 @@ from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loa
|
|||||||
from erpnext.loan_management.doctype.loan.loan import create_loan_security_unpledge
|
from erpnext.loan_management.doctype.loan.loan import create_loan_security_unpledge
|
||||||
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
|
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
|
||||||
from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge
|
from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge
|
||||||
|
from erpnext.loan_management.doctype.loan_disbursement.loan_disbursement import get_disbursal_amount
|
||||||
|
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts
|
||||||
|
|
||||||
class TestLoan(unittest.TestCase):
|
class TestLoan(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -193,18 +195,14 @@ class TestLoan(unittest.TestCase):
|
|||||||
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
|
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
|
||||||
process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
|
process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
|
||||||
|
|
||||||
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
|
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6),
|
||||||
"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
|
"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
|
||||||
repayment_entry.submit()
|
repayment_entry.submit()
|
||||||
|
|
||||||
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
|
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
|
||||||
'paid_principal_amount'])
|
'paid_principal_amount'])
|
||||||
|
|
||||||
unaccrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * 6) \
|
self.assertEquals(flt(amounts[0], 2),flt(accrued_interest_amount, 2))
|
||||||
/ (days_in_year(get_datetime(first_date).year) * 100)
|
|
||||||
|
|
||||||
self.assertEquals(flt(amounts[0] + unaccrued_interest_amount, 3),
|
|
||||||
flt(accrued_interest_amount, 3))
|
|
||||||
self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
|
self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
|
||||||
|
|
||||||
loan.load_from_db()
|
loan.load_from_db()
|
||||||
@ -306,9 +304,6 @@ class TestLoan(unittest.TestCase):
|
|||||||
"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
|
"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
|
||||||
repayment_entry.submit()
|
repayment_entry.submit()
|
||||||
|
|
||||||
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
|
|
||||||
'paid_principal_amount'])
|
|
||||||
|
|
||||||
loan.load_from_db()
|
loan.load_from_db()
|
||||||
self.assertEquals(loan.status, "Loan Closure Requested")
|
self.assertEquals(loan.status, "Loan Closure Requested")
|
||||||
|
|
||||||
@ -323,6 +318,97 @@ class TestLoan(unittest.TestCase):
|
|||||||
self.assertEqual(loan.status, 'Closed')
|
self.assertEqual(loan.status, 'Closed')
|
||||||
self.assertEquals(sum(pledged_qty.values()), 0)
|
self.assertEquals(sum(pledged_qty.values()), 0)
|
||||||
|
|
||||||
|
def test_disbursal_check_with_shortfall(self):
|
||||||
|
pledges = [{
|
||||||
|
"loan_security": "Test Security 2",
|
||||||
|
"qty": 8000.00,
|
||||||
|
"haircut": 50,
|
||||||
|
}]
|
||||||
|
|
||||||
|
loan_application = create_loan_application('_Test Company', self.applicant2,
|
||||||
|
'Stock Loan', pledges, "Repay Over Number of Periods", 12)
|
||||||
|
|
||||||
|
create_pledge(loan_application)
|
||||||
|
|
||||||
|
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
|
||||||
|
loan.submit()
|
||||||
|
|
||||||
|
#Disbursing 7,00,000 from the allowed 10,00,000 according to security pledge
|
||||||
|
make_loan_disbursement_entry(loan.name, 700000)
|
||||||
|
|
||||||
|
frappe.db.sql("""UPDATE `tabLoan Security Price` SET loan_security_price = 100
|
||||||
|
where loan_security='Test Security 2'""")
|
||||||
|
|
||||||
|
create_process_loan_security_shortfall()
|
||||||
|
loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name})
|
||||||
|
self.assertTrue(loan_security_shortfall)
|
||||||
|
|
||||||
|
self.assertEqual(get_disbursal_amount(loan.name), 0)
|
||||||
|
|
||||||
|
frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
|
||||||
|
where loan_security='Test Security 2'""")
|
||||||
|
|
||||||
|
def test_disbursal_check_without_shortfall(self):
|
||||||
|
pledges = [{
|
||||||
|
"loan_security": "Test Security 2",
|
||||||
|
"qty": 8000.00,
|
||||||
|
"haircut": 50,
|
||||||
|
}]
|
||||||
|
|
||||||
|
loan_application = create_loan_application('_Test Company', self.applicant2,
|
||||||
|
'Stock Loan', pledges, "Repay Over Number of Periods", 12)
|
||||||
|
|
||||||
|
create_pledge(loan_application)
|
||||||
|
|
||||||
|
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
|
||||||
|
loan.submit()
|
||||||
|
|
||||||
|
#Disbursing 7,00,000 from the allowed 10,00,000 according to security pledge
|
||||||
|
make_loan_disbursement_entry(loan.name, 700000)
|
||||||
|
|
||||||
|
self.assertEqual(get_disbursal_amount(loan.name), 300000)
|
||||||
|
|
||||||
|
def test_pending_loan_amount_after_closure_request(self):
|
||||||
|
pledge = [{
|
||||||
|
"loan_security": "Test Security 1",
|
||||||
|
"qty": 4000.00
|
||||||
|
}]
|
||||||
|
|
||||||
|
loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
|
||||||
|
create_pledge(loan_application)
|
||||||
|
|
||||||
|
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date=get_first_day(nowdate()))
|
||||||
|
loan.submit()
|
||||||
|
|
||||||
|
self.assertEquals(loan.loan_amount, 1000000)
|
||||||
|
|
||||||
|
first_date = '2019-10-01'
|
||||||
|
last_date = '2019-10-30'
|
||||||
|
|
||||||
|
no_of_days = date_diff(last_date, first_date) + 1
|
||||||
|
|
||||||
|
no_of_days += 6
|
||||||
|
|
||||||
|
accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
|
||||||
|
/ (days_in_year(get_datetime(first_date).year) * 100)
|
||||||
|
|
||||||
|
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
|
||||||
|
process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
|
||||||
|
|
||||||
|
amounts = calculate_amounts(loan.name, add_days(last_date, 6), "Regular Repayment")
|
||||||
|
|
||||||
|
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6),
|
||||||
|
"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
|
||||||
|
repayment_entry.submit()
|
||||||
|
|
||||||
|
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
|
||||||
|
'paid_principal_amount'])
|
||||||
|
|
||||||
|
loan.load_from_db()
|
||||||
|
self.assertEquals(loan.status, "Loan Closure Requested")
|
||||||
|
|
||||||
|
amounts = calculate_amounts(loan.name, add_days(last_date, 6), "Regular Repayment")
|
||||||
|
self.assertEquals(amounts['pending_principal_amount'], 0.0)
|
||||||
|
|
||||||
def create_loan_accounts():
|
def create_loan_accounts():
|
||||||
if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"):
|
if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"):
|
||||||
|
@ -33,18 +33,18 @@ frappe.ui.form.on('Loan Application', {
|
|||||||
|
|
||||||
if (frm.doc.is_secured_loan) {
|
if (frm.doc.is_secured_loan) {
|
||||||
frappe.db.get_value("Loan Security Pledge", {"loan_application": frm.doc.name, "docstatus": 1}, "name", (r) => {
|
frappe.db.get_value("Loan Security Pledge", {"loan_application": frm.doc.name, "docstatus": 1}, "name", (r) => {
|
||||||
if (!r) {
|
if (Object.keys(r).length === 0) {
|
||||||
frm.add_custom_button(__('Loan Security Pledge'), function() {
|
frm.add_custom_button(__('Loan Security Pledge'), function() {
|
||||||
frm.trigger('create_loan_security_pledge')
|
frm.trigger('create_loan_security_pledge');
|
||||||
},__('Create'))
|
},__('Create'))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
frappe.db.get_value("Loan", {"loan_application": frm.doc.name, "docstatus": 1}, "name", (r) => {
|
frappe.db.get_value("Loan", {"loan_application": frm.doc.name, "docstatus": 1}, "name", (r) => {
|
||||||
if (!r) {
|
if (Object.keys(r).length === 0) {
|
||||||
frm.add_custom_button(__('Loan'), function() {
|
frm.add_custom_button(__('Loan'), function() {
|
||||||
frm.trigger('create_loan')
|
frm.trigger('create_loan');
|
||||||
},__('Create'))
|
},__('Create'))
|
||||||
} else {
|
} else {
|
||||||
frm.set_df_property('status', 'read_only', 1);
|
frm.set_df_property('status', 'read_only', 1);
|
||||||
@ -54,7 +54,7 @@ frappe.ui.form.on('Loan Application', {
|
|||||||
},
|
},
|
||||||
create_loan: function(frm) {
|
create_loan: function(frm) {
|
||||||
if (frm.doc.status != "Approved") {
|
if (frm.doc.status != "Approved") {
|
||||||
frappe.throw(__("Cannot create loan until application is approved"))
|
frappe.throw(__("Cannot create loan until application is approved"));
|
||||||
}
|
}
|
||||||
|
|
||||||
frappe.model.open_mapped_doc({
|
frappe.model.open_mapped_doc({
|
||||||
|
@ -67,28 +67,10 @@ class LoanDisbursement(AccountsController):
|
|||||||
disbursed_amount = self.disbursed_amount + loan_details.disbursed_amount
|
disbursed_amount = self.disbursed_amount + loan_details.disbursed_amount
|
||||||
total_payment = loan_details.total_payment
|
total_payment = loan_details.total_payment
|
||||||
|
|
||||||
if disbursed_amount > loan_details.loan_amount and loan_details.is_term_loan:
|
possible_disbursal_amount = get_disbursal_amount(self.against_loan)
|
||||||
frappe.throw(_("Disbursed Amount cannot be greater than loan amount"))
|
|
||||||
|
|
||||||
if loan_details.status == 'Disbursed':
|
if self.disbursed_amount > possible_disbursal_amount:
|
||||||
pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \
|
frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(possible_disbursal_amount))
|
||||||
- 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:
|
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),
|
process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1),
|
||||||
@ -176,3 +158,32 @@ def get_total_pledged_security_value(loan):
|
|||||||
security_value += (loan_security_price_map.get(security) * qty * hair_cut_map.get(security))/100
|
security_value += (loan_security_price_map.get(security) * qty * hair_cut_map.get(security))/100
|
||||||
|
|
||||||
return security_value
|
return security_value
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_disbursal_amount(loan):
|
||||||
|
loan_details = frappe.get_all("Loan", fields = ["loan_amount", "disbursed_amount", "total_payment",
|
||||||
|
"total_principal_paid", "total_interest_payable", "status", "is_term_loan", "is_secured_loan"],
|
||||||
|
filters= { "name": loan })[0]
|
||||||
|
|
||||||
|
if loan_details.is_secured_loan and frappe.get_all('Loan Security Shortfall', filters={'loan': loan,
|
||||||
|
'status': 'Pending'}):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
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 = flt(loan_details.disbursed_amount)
|
||||||
|
|
||||||
|
security_value = 0.0
|
||||||
|
if loan_details.is_secured_loan:
|
||||||
|
security_value = get_total_pledged_security_value(loan)
|
||||||
|
|
||||||
|
if not security_value and not loan_details.is_secured_loan:
|
||||||
|
security_value = flt(loan_details.loan_amount)
|
||||||
|
|
||||||
|
disbursal_amount = flt(security_value) - flt(pending_principal_amount)
|
||||||
|
|
||||||
|
return disbursal_amount
|
||||||
|
|
||||||
|
|
||||||
|
@ -85,8 +85,11 @@ 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
|
||||||
|
|
||||||
|
if loan.status == 'Disbursed':
|
||||||
pending_principal_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
|
pending_principal_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
|
||||||
- flt(loan.total_principal_paid)
|
- flt(loan.total_principal_paid)
|
||||||
|
else:
|
||||||
|
pending_principal_amount = loan.disbursed_amount
|
||||||
|
|
||||||
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
|
||||||
@ -107,7 +110,7 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i
|
|||||||
|
|
||||||
def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_interest, open_loans=None, loan_type=None):
|
def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_interest, open_loans=None, loan_type=None):
|
||||||
query_filters = {
|
query_filters = {
|
||||||
"status": "Disbursed",
|
"status": ('in', ['Disbursed', 'Partially Disbursed']),
|
||||||
"docstatus": 1
|
"docstatus": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,8 +121,9 @@ def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_inte
|
|||||||
|
|
||||||
if not open_loans:
|
if not open_loans:
|
||||||
open_loans = frappe.get_all("Loan",
|
open_loans = frappe.get_all("Loan",
|
||||||
fields=["name", "total_payment", "total_amount_paid", "loan_account", "interest_income_account", "is_term_loan",
|
fields=["name", "total_payment", "total_amount_paid", "loan_account", "interest_income_account",
|
||||||
"disbursement_date", "applicant_type", "applicant", "rate_of_interest", "total_interest_payable", "repayment_start_date"],
|
"is_term_loan", "status", "disbursement_date", "disbursed_amount", "applicant_type", "applicant",
|
||||||
|
"rate_of_interest", "total_interest_payable", "total_principal_paid", "repayment_start_date"],
|
||||||
filters=query_filters)
|
filters=query_filters)
|
||||||
|
|
||||||
for loan in open_loans:
|
for loan in open_loans:
|
||||||
|
@ -116,6 +116,7 @@ class LoanRepayment(AccountsController):
|
|||||||
def allocate_amounts(self, paid_entries):
|
def allocate_amounts(self, paid_entries):
|
||||||
self.set('repayment_details', [])
|
self.set('repayment_details', [])
|
||||||
self.principal_amount_paid = 0
|
self.principal_amount_paid = 0
|
||||||
|
total_interest_paid = 0
|
||||||
interest_paid = self.amount_paid - self.penalty_amount
|
interest_paid = self.amount_paid - self.penalty_amount
|
||||||
|
|
||||||
if self.amount_paid - self.penalty_amount > 0 and paid_entries:
|
if self.amount_paid - self.penalty_amount > 0 and paid_entries:
|
||||||
@ -137,12 +138,19 @@ class LoanRepayment(AccountsController):
|
|||||||
interest_paid = 0
|
interest_paid = 0
|
||||||
paid_principal=0
|
paid_principal=0
|
||||||
|
|
||||||
|
total_interest_paid += interest_amount
|
||||||
self.append('repayment_details', {
|
self.append('repayment_details', {
|
||||||
'loan_interest_accrual': lia,
|
'loan_interest_accrual': lia,
|
||||||
'paid_interest_amount': interest_amount,
|
'paid_interest_amount': interest_amount,
|
||||||
'paid_principal_amount': paid_principal
|
'paid_principal_amount': paid_principal
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if self.payment_type == 'Loan Closure' and total_interest_paid < self.interest_payable:
|
||||||
|
unaccrued_interest = self.interest_payable - total_interest_paid
|
||||||
|
interest_paid -= unaccrued_interest
|
||||||
|
if self.repayment_details:
|
||||||
|
self.repayment_details[-1].paid_interest_amount += unaccrued_interest
|
||||||
|
|
||||||
if interest_paid:
|
if interest_paid:
|
||||||
self.principal_amount_paid += interest_paid
|
self.principal_amount_paid += interest_paid
|
||||||
|
|
||||||
@ -297,7 +305,10 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
|
|||||||
if not final_due_date:
|
if not final_due_date:
|
||||||
final_due_date = add_days(due_date, loan_type_details.grace_period_in_days)
|
final_due_date = add_days(due_date, loan_type_details.grace_period_in_days)
|
||||||
|
|
||||||
|
if against_loan_doc.status in ('Disbursed', 'Loan Closure Requested'):
|
||||||
pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid - against_loan_doc.total_interest_payable
|
pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid - against_loan_doc.total_interest_payable
|
||||||
|
else:
|
||||||
|
pending_principal_amount = against_loan_doc.disbursed_amount
|
||||||
|
|
||||||
if payment_type == "Loan Closure":
|
if payment_type == "Loan Closure":
|
||||||
if due_date:
|
if due_date:
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import get_datetime
|
from frappe.utils import get_datetime, flt
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
|
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
|
||||||
@ -51,13 +51,19 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
|
|||||||
"valid_upto": (">=", update_time)
|
"valid_upto": (">=", update_time)
|
||||||
}, as_list=1))
|
}, as_list=1))
|
||||||
|
|
||||||
loans = frappe.get_all('Loan', fields=['name', 'loan_amount', 'total_principal_paid'],
|
loans = frappe.get_all('Loan', fields=['name', 'loan_amount', 'total_principal_paid', 'total_payment',
|
||||||
filters={'status': 'Disbursed', 'is_secured_loan': 1})
|
'total_interest_payable', 'disbursed_amount', 'status'],
|
||||||
|
filters={'status': ('in',['Disbursed','Partially Disbursed']), 'is_secured_loan': 1})
|
||||||
|
|
||||||
loan_security_map = {}
|
loan_security_map = {}
|
||||||
|
|
||||||
for loan in loans:
|
for loan in loans:
|
||||||
outstanding_amount = loan.loan_amount - loan.total_principal_paid
|
if loan.status == 'Disbursed':
|
||||||
|
outstanding_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
|
||||||
|
- flt(loan.total_principal_paid)
|
||||||
|
else:
|
||||||
|
outstanding_amount = loan.disbursed_amount
|
||||||
|
|
||||||
pledged_securities = get_pledged_security_qty(loan.name)
|
pledged_securities = get_pledged_security_qty(loan.name)
|
||||||
ltv_ratio = ''
|
ltv_ratio = ''
|
||||||
security_value = 0.0
|
security_value = 0.0
|
||||||
|
@ -43,8 +43,10 @@ class LoanSecurityUnpledge(Document):
|
|||||||
"valid_upto": (">=", get_datetime())
|
"valid_upto": (">=", get_datetime())
|
||||||
}, as_list=1))
|
}, as_list=1))
|
||||||
|
|
||||||
loan_amount, principal_paid = frappe.get_value("Loan", self.loan, ['loan_amount', 'total_principal_paid'])
|
total_payment, principal_paid, interest_payable = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid',
|
||||||
pending_principal_amount = loan_amount - principal_paid
|
'total_interest_payable'])
|
||||||
|
|
||||||
|
pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid)
|
||||||
security_value = 0
|
security_value = 0
|
||||||
|
|
||||||
for security in self.securities:
|
for security in self.securities:
|
||||||
@ -60,7 +62,7 @@ class LoanSecurityUnpledge(Document):
|
|||||||
|
|
||||||
security_value += qty_after_unpledge * loan_security_price_map.get(security.loan_security)
|
security_value += qty_after_unpledge * loan_security_price_map.get(security.loan_security)
|
||||||
|
|
||||||
if not security_value and pending_principal_amount > 0:
|
if not security_value and flt(pending_principal_amount, 2) > 0:
|
||||||
frappe.throw("Cannot Unpledge, loan to value ratio is breaching")
|
frappe.throw("Cannot Unpledge, loan to value ratio is breaching")
|
||||||
|
|
||||||
if security_value and (pending_principal_amount/security_value) * 100 > ltv_ratio:
|
if security_value and (pending_principal_amount/security_value) * 100 > ltv_ratio:
|
||||||
|
@ -67,16 +67,16 @@ class MaintenanceSchedule(TransactionBase):
|
|||||||
|
|
||||||
for key in scheduled_date:
|
for key in scheduled_date:
|
||||||
description =frappe._("Reference: {0}, Item Code: {1} and Customer: {2}").format(self.name, d.item_code, self.customer)
|
description =frappe._("Reference: {0}, Item Code: {1} and Customer: {2}").format(self.name, d.item_code, self.customer)
|
||||||
frappe.get_doc({
|
event = frappe.get_doc({
|
||||||
"doctype": "Event",
|
"doctype": "Event",
|
||||||
"owner": email_map.get(d.sales_person, self.owner),
|
"owner": email_map.get(d.sales_person, self.owner),
|
||||||
"subject": description,
|
"subject": description,
|
||||||
"description": description,
|
"description": description,
|
||||||
"starts_on": cstr(key["scheduled_date"]) + " 10:00:00",
|
"starts_on": cstr(key["scheduled_date"]) + " 10:00:00",
|
||||||
"event_type": "Private",
|
"event_type": "Private",
|
||||||
"ref_type": self.doctype,
|
})
|
||||||
"ref_name": self.name
|
event.add_participant(self.doctype, self.name)
|
||||||
}).insert(ignore_permissions=1)
|
event.insert(ignore_permissions=1)
|
||||||
|
|
||||||
frappe.db.set(self, 'status', 'Submitted')
|
frappe.db.set(self, 'status', 'Submitted')
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
from frappe.utils.data import get_datetime, add_days
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
@ -9,4 +10,39 @@ import unittest
|
|||||||
# test_records = frappe.get_test_records('Maintenance Schedule')
|
# test_records = frappe.get_test_records('Maintenance Schedule')
|
||||||
|
|
||||||
class TestMaintenanceSchedule(unittest.TestCase):
|
class TestMaintenanceSchedule(unittest.TestCase):
|
||||||
pass
|
def test_events_should_be_created_and_deleted(self):
|
||||||
|
ms = make_maintenance_schedule()
|
||||||
|
ms.generate_schedule()
|
||||||
|
ms.submit()
|
||||||
|
|
||||||
|
all_events = get_events(ms)
|
||||||
|
self.assertTrue(len(all_events) > 0)
|
||||||
|
|
||||||
|
ms.cancel()
|
||||||
|
events_after_cancel = get_events(ms)
|
||||||
|
self.assertTrue(len(events_after_cancel) == 0)
|
||||||
|
|
||||||
|
def get_events(ms):
|
||||||
|
return frappe.get_all("Event Participants", filters={
|
||||||
|
"reference_doctype": ms.doctype,
|
||||||
|
"reference_docname": ms.name,
|
||||||
|
"parenttype": "Event"
|
||||||
|
})
|
||||||
|
|
||||||
|
def make_maintenance_schedule():
|
||||||
|
ms = frappe.new_doc("Maintenance Schedule")
|
||||||
|
ms.company = "_Test Company"
|
||||||
|
ms.customer = "_Test Customer"
|
||||||
|
ms.transaction_date = get_datetime()
|
||||||
|
|
||||||
|
ms.append("items", {
|
||||||
|
"item_code": "_Test Item",
|
||||||
|
"start_date": get_datetime(),
|
||||||
|
"end_date": add_days(get_datetime(), 32),
|
||||||
|
"periodicity": "Weekly",
|
||||||
|
"no_of_visits": 4,
|
||||||
|
"sales_person": "Sales Team",
|
||||||
|
})
|
||||||
|
ms.insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
return ms
|
||||||
|
@ -785,6 +785,19 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
erpnext.utils.get_shipping_address(this.frm, function(){
|
erpnext.utils.get_shipping_address(this.frm, function(){
|
||||||
set_party_account(set_pricing);
|
set_party_account(set_pricing);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Get default company billing address in Purchase Invoice, Order and Receipt
|
||||||
|
frappe.call({
|
||||||
|
'method': 'frappe.contacts.doctype.address.address.get_default_address',
|
||||||
|
'args': {
|
||||||
|
'doctype': 'Company',
|
||||||
|
'name': this.frm.doc.company
|
||||||
|
},
|
||||||
|
'callback': function(r) {
|
||||||
|
me.frm.set_value('billing_address', r.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
set_party_account(set_pricing);
|
set_party_account(set_pricing);
|
||||||
}
|
}
|
||||||
|
@ -46,5 +46,28 @@ frappe.query_reports["HSN-wise-summary of outward supplies"] = {
|
|||||||
],
|
],
|
||||||
onload: (report) => {
|
onload: (report) => {
|
||||||
fetch_gstins(report);
|
fetch_gstins(report);
|
||||||
|
|
||||||
|
report.page.add_inner_button(__("Download JSON"), function () {
|
||||||
|
var filters = report.get_values();
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.regional.report.hsn_wise_summary_of_outward_supplies.hsn_wise_summary_of_outward_supplies.get_json',
|
||||||
|
args: {
|
||||||
|
data: report.data,
|
||||||
|
report_name: report.report_name,
|
||||||
|
filters: filters
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
const args = {
|
||||||
|
cmd: 'erpnext.regional.report.hsn_wise_summary_of_outward_supplies.hsn_wise_summary_of_outward_supplies.download_json_file',
|
||||||
|
data: r.message.data,
|
||||||
|
report_name: r.message.report_name
|
||||||
|
};
|
||||||
|
open_url_post(frappe.request.url, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -4,11 +4,13 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, erpnext
|
import frappe, erpnext
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import flt
|
from frappe.utils import flt, getdate, cstr
|
||||||
from frappe.model.meta import get_field_precision
|
from frappe.model.meta import get_field_precision
|
||||||
from frappe.utils.xlsxutils import handle_html
|
from frappe.utils.xlsxutils import handle_html
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
import json
|
import json
|
||||||
|
from erpnext.regional.india.utils import get_gst_accounts
|
||||||
|
from erpnext.regional.report.gstr_1.gstr_1 import get_company_gstin_number
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
return _execute(filters)
|
return _execute(filters)
|
||||||
@ -141,7 +143,7 @@ def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoic
|
|||||||
|
|
||||||
tax_details = frappe.db.sql("""
|
tax_details = frappe.db.sql("""
|
||||||
select
|
select
|
||||||
parent, description, item_wise_tax_detail,
|
parent, account_head, item_wise_tax_detail,
|
||||||
base_tax_amount_after_discount_amount
|
base_tax_amount_after_discount_amount
|
||||||
from `tab%s`
|
from `tab%s`
|
||||||
where
|
where
|
||||||
@ -153,11 +155,11 @@ def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoic
|
|||||||
""" % (tax_doctype, '%s', ', '.join(['%s']*len(invoice_item_row)), conditions),
|
""" % (tax_doctype, '%s', ', '.join(['%s']*len(invoice_item_row)), conditions),
|
||||||
tuple([doctype] + list(invoice_item_row)))
|
tuple([doctype] + list(invoice_item_row)))
|
||||||
|
|
||||||
for parent, description, item_wise_tax_detail, tax_amount in tax_details:
|
for parent, account_head, item_wise_tax_detail, tax_amount in tax_details:
|
||||||
description = handle_html(description)
|
|
||||||
if description not in tax_columns and tax_amount:
|
if account_head not in tax_columns and tax_amount:
|
||||||
# as description is text editor earlier and markup can break the column convention in reports
|
# as description is text editor earlier and markup can break the column convention in reports
|
||||||
tax_columns.append(description)
|
tax_columns.append(account_head)
|
||||||
|
|
||||||
if item_wise_tax_detail:
|
if item_wise_tax_detail:
|
||||||
try:
|
try:
|
||||||
@ -175,17 +177,17 @@ def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoic
|
|||||||
for d in item_row_map.get(parent, {}).get(item_code, []):
|
for d in item_row_map.get(parent, {}).get(item_code, []):
|
||||||
item_tax_amount = tax_amount
|
item_tax_amount = tax_amount
|
||||||
if item_tax_amount:
|
if item_tax_amount:
|
||||||
itemised_tax.setdefault((parent, item_code), {})[description] = frappe._dict({
|
itemised_tax.setdefault((parent, item_code), {})[account_head] = frappe._dict({
|
||||||
"tax_amount": flt(item_tax_amount, tax_amount_precision)
|
"tax_amount": flt(item_tax_amount, tax_amount_precision)
|
||||||
})
|
})
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
tax_columns.sort()
|
tax_columns.sort()
|
||||||
for desc in tax_columns:
|
for account_head in tax_columns:
|
||||||
columns.append({
|
columns.append({
|
||||||
"label": desc,
|
"label": account_head,
|
||||||
"fieldname": frappe.scrub(desc),
|
"fieldname": frappe.scrub(account_head),
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"width": 110
|
"width": 110
|
||||||
})
|
})
|
||||||
@ -212,3 +214,76 @@ def get_merged_data(columns, data):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_json(filters, report_name, data):
|
||||||
|
filters = json.loads(filters)
|
||||||
|
report_data = json.loads(data)
|
||||||
|
gstin = filters.get('company_gstin') or get_company_gstin_number(filters["company"])
|
||||||
|
|
||||||
|
if not filters.get('from_date') or not filters.get('to_date'):
|
||||||
|
frappe.throw(_("Please enter From Date and To Date to generate JSON"))
|
||||||
|
|
||||||
|
fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year)
|
||||||
|
|
||||||
|
gst_json = {"version": "GST2.3.4",
|
||||||
|
"hash": "hash", "gstin": gstin, "fp": fp}
|
||||||
|
|
||||||
|
gst_json["hsn"] = {
|
||||||
|
"data": get_hsn_wise_json_data(filters, report_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'report_name': report_name,
|
||||||
|
'data': gst_json
|
||||||
|
}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def download_json_file():
|
||||||
|
'''download json content in a file'''
|
||||||
|
data = frappe._dict(frappe.local.form_dict)
|
||||||
|
frappe.response['filename'] = frappe.scrub("{0}".format(data['report_name'])) + '.json'
|
||||||
|
frappe.response['filecontent'] = data['data']
|
||||||
|
frappe.response['content_type'] = 'application/json'
|
||||||
|
frappe.response['type'] = 'download'
|
||||||
|
|
||||||
|
def get_hsn_wise_json_data(filters, report_data):
|
||||||
|
|
||||||
|
filters = frappe._dict(filters)
|
||||||
|
gst_accounts = get_gst_accounts(filters.company)
|
||||||
|
data = []
|
||||||
|
count = 1
|
||||||
|
|
||||||
|
for hsn in report_data:
|
||||||
|
row = {
|
||||||
|
"num": count,
|
||||||
|
"hsn_sc": hsn.get("gst_hsn_code"),
|
||||||
|
"desc": hsn.get("description"),
|
||||||
|
"uqc": hsn.get("stock_uom").upper(),
|
||||||
|
"qty": hsn.get("stock_qty"),
|
||||||
|
"val": flt(hsn.get("total_amount"), 2),
|
||||||
|
"txval": flt(hsn.get("taxable_amount", 2)),
|
||||||
|
"iamt": 0.0,
|
||||||
|
"camt": 0.0,
|
||||||
|
"samt": 0.0,
|
||||||
|
"csamt": 0.0
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for account in gst_accounts.get('igst_account'):
|
||||||
|
row['iamt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2)
|
||||||
|
|
||||||
|
for account in gst_accounts.get('cgst_account'):
|
||||||
|
row['camt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2)
|
||||||
|
|
||||||
|
for account in gst_accounts.get('sgst_account'):
|
||||||
|
row['samt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2)
|
||||||
|
|
||||||
|
for account in gst_accounts.get('cess_account'):
|
||||||
|
row['csamt'] += flt(hsn.get(frappe.scrub(cstr(account)), 0.0), 2)
|
||||||
|
|
||||||
|
data.append(row)
|
||||||
|
count +=1
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@ -285,9 +285,17 @@ def _make_customer(source_name, ignore_permissions=False):
|
|||||||
return customer
|
return customer
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
except frappe.MandatoryError:
|
except frappe.MandatoryError as e:
|
||||||
|
mandatory_fields = e.args[0].split(':')[1].split(',')
|
||||||
|
mandatory_fields = [customer.meta.get_label(field.strip()) for field in mandatory_fields]
|
||||||
|
|
||||||
frappe.local.message_log = []
|
frappe.local.message_log = []
|
||||||
frappe.throw(_("Please create Customer from Lead {0}").format(lead_name))
|
lead_link = frappe.utils.get_link_to_form("Lead", lead_name)
|
||||||
|
message = _("Could not auto create Customer due to the following missing mandatory field(s):") + "<br>"
|
||||||
|
message += "<br><ul><li>" + "</li><li>".join(mandatory_fields) + "</li></ul>"
|
||||||
|
message += _("Please create Customer from Lead {0}.").format(lead_link)
|
||||||
|
|
||||||
|
frappe.throw(message, title=_("Mandatory Missing"))
|
||||||
else:
|
else:
|
||||||
return customer_name
|
return customer_name
|
||||||
else:
|
else:
|
||||||
|
Loading…
Reference in New Issue
Block a user