Merge branch 'develop' into item-wise-purchase-registry-item-name-error
This commit is contained in:
commit
953960b193
@ -2,7 +2,6 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Accounting Dimension', {
|
frappe.ui.form.on('Accounting Dimension', {
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
frm.set_query('document_type', () => {
|
frm.set_query('document_type', () => {
|
||||||
let invalid_doctypes = frappe.model.core_doctypes_list;
|
let invalid_doctypes = frappe.model.core_doctypes_list;
|
||||||
|
@ -203,7 +203,7 @@ def get_dimension_with_children(doctype, dimension):
|
|||||||
return all_dimensions
|
return all_dimensions
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_dimension_filters():
|
def get_dimensions(with_cost_center_and_project=False):
|
||||||
dimension_filters = frappe.db.sql("""
|
dimension_filters = frappe.db.sql("""
|
||||||
SELECT label, fieldname, document_type
|
SELECT label, fieldname, document_type
|
||||||
FROM `tabAccounting Dimension`
|
FROM `tabAccounting Dimension`
|
||||||
@ -214,6 +214,18 @@ def get_dimension_filters():
|
|||||||
FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
|
FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
|
||||||
WHERE c.parent = p.name""", as_dict=1)
|
WHERE c.parent = p.name""", as_dict=1)
|
||||||
|
|
||||||
|
if with_cost_center_and_project:
|
||||||
|
dimension_filters.extend([
|
||||||
|
{
|
||||||
|
'fieldname': 'cost_center',
|
||||||
|
'document_type': 'Cost Center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'project',
|
||||||
|
'document_type': 'Project'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
default_dimensions_map = {}
|
default_dimensions_map = {}
|
||||||
for dimension in default_dimensions:
|
for dimension in default_dimensions:
|
||||||
default_dimensions_map.setdefault(dimension.company, {})
|
default_dimensions_map.setdefault(dimension.company, {})
|
||||||
|
@ -11,37 +11,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import d
|
|||||||
|
|
||||||
class TestAccountingDimension(unittest.TestCase):
|
class TestAccountingDimension(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
frappe.set_user("Administrator")
|
create_dimension()
|
||||||
|
|
||||||
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
|
|
||||||
dimension = frappe.get_doc({
|
|
||||||
"doctype": "Accounting Dimension",
|
|
||||||
"document_type": "Department",
|
|
||||||
}).insert()
|
|
||||||
else:
|
|
||||||
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
|
|
||||||
dimension1.disabled = 0
|
|
||||||
dimension1.save()
|
|
||||||
|
|
||||||
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
|
|
||||||
dimension1 = frappe.get_doc({
|
|
||||||
"doctype": "Accounting Dimension",
|
|
||||||
"document_type": "Location",
|
|
||||||
})
|
|
||||||
|
|
||||||
dimension1.append("dimension_defaults", {
|
|
||||||
"company": "_Test Company",
|
|
||||||
"reference_document": "Location",
|
|
||||||
"default_dimension": "Block 1",
|
|
||||||
"mandatory_for_bs": 1
|
|
||||||
})
|
|
||||||
|
|
||||||
dimension1.insert()
|
|
||||||
dimension1.save()
|
|
||||||
else:
|
|
||||||
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
|
|
||||||
dimension1.disabled = 0
|
|
||||||
dimension1.save()
|
|
||||||
|
|
||||||
def test_dimension_against_sales_invoice(self):
|
def test_dimension_against_sales_invoice(self):
|
||||||
si = create_sales_invoice(do_not_save=1)
|
si = create_sales_invoice(do_not_save=1)
|
||||||
@ -101,6 +71,38 @@ class TestAccountingDimension(unittest.TestCase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
disable_dimension()
|
disable_dimension()
|
||||||
|
|
||||||
|
def create_dimension():
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
|
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
|
||||||
|
frappe.get_doc({
|
||||||
|
"doctype": "Accounting Dimension",
|
||||||
|
"document_type": "Department",
|
||||||
|
}).insert()
|
||||||
|
else:
|
||||||
|
dimension = frappe.get_doc("Accounting Dimension", "Department")
|
||||||
|
dimension.disabled = 0
|
||||||
|
dimension.save()
|
||||||
|
|
||||||
|
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
|
||||||
|
dimension1 = frappe.get_doc({
|
||||||
|
"doctype": "Accounting Dimension",
|
||||||
|
"document_type": "Location",
|
||||||
|
})
|
||||||
|
|
||||||
|
dimension1.append("dimension_defaults", {
|
||||||
|
"company": "_Test Company",
|
||||||
|
"reference_document": "Location",
|
||||||
|
"default_dimension": "Block 1",
|
||||||
|
"mandatory_for_bs": 1
|
||||||
|
})
|
||||||
|
|
||||||
|
dimension1.insert()
|
||||||
|
dimension1.save()
|
||||||
|
else:
|
||||||
|
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
|
||||||
|
dimension1.disabled = 0
|
||||||
|
dimension1.save()
|
||||||
|
|
||||||
def disable_dimension():
|
def disable_dimension():
|
||||||
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
|
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Accounting Dimension Filter', {
|
||||||
|
refresh: function(frm, cdt, cdn) {
|
||||||
|
if (frm.doc.accounting_dimension) {
|
||||||
|
frm.set_df_property('dimensions', 'label', frm.doc.accounting_dimension, cdn, 'dimension_value');
|
||||||
|
}
|
||||||
|
|
||||||
|
let help_content =
|
||||||
|
`<table class="table table-bordered" style="background-color: #f9f9f9;">
|
||||||
|
<tr><td>
|
||||||
|
<p>
|
||||||
|
<i class="fa fa-hand-right"></i>
|
||||||
|
{{__('Note: On checking Is Mandatory the accounting dimension will become mandatory against that specific account for all accounting transactions')}}
|
||||||
|
</p>
|
||||||
|
</td></tr>
|
||||||
|
</table>`;
|
||||||
|
|
||||||
|
frm.set_df_property('dimension_filter_help', 'options', help_content);
|
||||||
|
},
|
||||||
|
onload: function(frm) {
|
||||||
|
frm.set_query('applicable_on_account', 'accounts', function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'company': frm.doc.company
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.db.get_list('Accounting Dimension',
|
||||||
|
{fields: ['document_type']}).then((res) => {
|
||||||
|
let options = ['Cost Center', 'Project'];
|
||||||
|
|
||||||
|
res.forEach((dimension) => {
|
||||||
|
options.push(dimension.document_type);
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_df_property('accounting_dimension', 'options', options);
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.trigger('setup_filters');
|
||||||
|
},
|
||||||
|
|
||||||
|
setup_filters: function(frm) {
|
||||||
|
let filters = {};
|
||||||
|
|
||||||
|
if (frm.doc.accounting_dimension) {
|
||||||
|
frappe.model.with_doctype(frm.doc.accounting_dimension, function() {
|
||||||
|
if (frappe.model.is_tree(frm.doc.accounting_dimension)) {
|
||||||
|
filters['is_group'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frappe.meta.has_field(frm.doc.accounting_dimension, 'company')) {
|
||||||
|
filters['company'] = frm.doc.company;
|
||||||
|
}
|
||||||
|
|
||||||
|
frm.set_query('dimension_value', 'dimensions', function() {
|
||||||
|
return {
|
||||||
|
filters: filters
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
accounting_dimension: function(frm) {
|
||||||
|
frm.clear_table("dimensions");
|
||||||
|
let row = frm.add_child("dimensions");
|
||||||
|
row.accounting_dimension = frm.doc.accounting_dimension;
|
||||||
|
frm.refresh_field("dimensions");
|
||||||
|
frm.trigger('setup_filters');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on('Allowed Dimension', {
|
||||||
|
dimensions_add: function(frm, cdt, cdn) {
|
||||||
|
let row = locals[cdt][cdn];
|
||||||
|
row.accounting_dimension = frm.doc.accounting_dimension;
|
||||||
|
frm.refresh_field("dimensions");
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,134 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "format:{accounting_dimension}-{#####}",
|
||||||
|
"creation": "2020-11-08 18:28:11.906146",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"accounting_dimension",
|
||||||
|
"disabled",
|
||||||
|
"column_break_2",
|
||||||
|
"company",
|
||||||
|
"allow_or_restrict",
|
||||||
|
"section_break_4",
|
||||||
|
"accounts",
|
||||||
|
"column_break_6",
|
||||||
|
"dimensions",
|
||||||
|
"section_break_10",
|
||||||
|
"dimension_filter_help"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "accounting_dimension",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Accounting Dimension",
|
||||||
|
"reqd": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_2",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_4",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hide_border": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_6",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "allow_or_restrict",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Allow Or Restrict Dimension",
|
||||||
|
"options": "Allow\nRestrict",
|
||||||
|
"reqd": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "accounts",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Applicable On Account",
|
||||||
|
"options": "Applicable On Account",
|
||||||
|
"reqd": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.accounting_dimension",
|
||||||
|
"fieldname": "dimensions",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Applicable Dimension",
|
||||||
|
"options": "Allowed Dimension",
|
||||||
|
"reqd": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "disabled",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Disabled",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dimension_filter_help",
|
||||||
|
"fieldtype": "HTML",
|
||||||
|
"label": "Dimension Filter Help",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_10",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-12-16 15:27:23.659285",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Accounting Dimension Filter",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright, (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _, scrub
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class AccountingDimensionFilter(Document):
|
||||||
|
def validate(self):
|
||||||
|
self.validate_applicable_accounts()
|
||||||
|
|
||||||
|
def validate_applicable_accounts(self):
|
||||||
|
accounts = frappe.db.sql(
|
||||||
|
"""
|
||||||
|
SELECT a.applicable_on_account as account
|
||||||
|
FROM `tabApplicable On Account` a, `tabAccounting Dimension Filter` d
|
||||||
|
WHERE d.name = a.parent
|
||||||
|
and d.name != %s
|
||||||
|
and d.accounting_dimension = %s
|
||||||
|
""", (self.name, self.accounting_dimension), as_dict=1)
|
||||||
|
|
||||||
|
account_list = [d.account for d in accounts]
|
||||||
|
|
||||||
|
for account in self.get('accounts'):
|
||||||
|
if account.applicable_on_account in account_list:
|
||||||
|
frappe.throw(_("Row {0}: {1} account already applied for Accounting Dimension {2}").format(
|
||||||
|
account.idx, frappe.bold(account.applicable_on_account), frappe.bold(self.accounting_dimension)))
|
||||||
|
|
||||||
|
def get_dimension_filter_map():
|
||||||
|
filters = frappe.db.sql("""
|
||||||
|
SELECT
|
||||||
|
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
||||||
|
p.allow_or_restrict, a.is_mandatory
|
||||||
|
FROM
|
||||||
|
`tabApplicable On Account` a, `tabAllowed Dimension` d,
|
||||||
|
`tabAccounting Dimension Filter` p
|
||||||
|
WHERE
|
||||||
|
p.name = a.parent
|
||||||
|
AND p.disabled = 0
|
||||||
|
AND p.name = d.parent
|
||||||
|
""", as_dict=1)
|
||||||
|
|
||||||
|
dimension_filter_map = {}
|
||||||
|
|
||||||
|
for f in filters:
|
||||||
|
f.fieldname = scrub(f.accounting_dimension)
|
||||||
|
|
||||||
|
build_map(dimension_filter_map, f.fieldname, f.applicable_on_account, f.dimension_value,
|
||||||
|
f.allow_or_restrict, f.is_mandatory)
|
||||||
|
|
||||||
|
return dimension_filter_map
|
||||||
|
|
||||||
|
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
|
||||||
|
map_object.setdefault((dimension, account), {
|
||||||
|
'allowed_dimensions': [],
|
||||||
|
'is_mandatory': is_mandatory,
|
||||||
|
'allow_or_restrict': allow_or_restrict
|
||||||
|
})
|
||||||
|
map_object[(dimension, account)]['allowed_dimensions'].append(filter_value)
|
@ -0,0 +1,99 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension, disable_dimension
|
||||||
|
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
||||||
|
|
||||||
|
class TestAccountingDimensionFilter(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
create_dimension()
|
||||||
|
create_accounting_dimension_filter()
|
||||||
|
self.invoice_list = []
|
||||||
|
|
||||||
|
def test_allowed_dimension_validation(self):
|
||||||
|
si = create_sales_invoice(do_not_save=1)
|
||||||
|
si.items[0].cost_center = 'Main - _TC'
|
||||||
|
si.department = 'Accounts - _TC'
|
||||||
|
si.location = 'Block 1'
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
self.assertRaises(InvalidAccountDimensionError, si.submit)
|
||||||
|
self.invoice_list.append(si)
|
||||||
|
|
||||||
|
def test_mandatory_dimension_validation(self):
|
||||||
|
si = create_sales_invoice(do_not_save=1)
|
||||||
|
si.department = ''
|
||||||
|
si.location = 'Block 1'
|
||||||
|
|
||||||
|
# Test with no department for Sales Account
|
||||||
|
si.items[0].department = ''
|
||||||
|
si.items[0].cost_center = '_Test Cost Center 2 - _TC'
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
self.assertRaises(MandatoryAccountDimensionError, si.submit)
|
||||||
|
self.invoice_list.append(si)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
disable_dimension_filter()
|
||||||
|
disable_dimension()
|
||||||
|
|
||||||
|
for si in self.invoice_list:
|
||||||
|
si.load_from_db()
|
||||||
|
if si.docstatus == 1:
|
||||||
|
si.cancel()
|
||||||
|
|
||||||
|
def create_accounting_dimension_filter():
|
||||||
|
if not frappe.db.get_value('Accounting Dimension Filter',
|
||||||
|
{'accounting_dimension': 'Cost Center'}):
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Accounting Dimension Filter',
|
||||||
|
'accounting_dimension': 'Cost Center',
|
||||||
|
'allow_or_restrict': 'Allow',
|
||||||
|
'company': '_Test Company',
|
||||||
|
'accounts': [{
|
||||||
|
'applicable_on_account': 'Sales - _TC',
|
||||||
|
}],
|
||||||
|
'dimensions': [{
|
||||||
|
'accounting_dimension': 'Cost Center',
|
||||||
|
'dimension_value': '_Test Cost Center 2 - _TC'
|
||||||
|
}]
|
||||||
|
}).insert()
|
||||||
|
else:
|
||||||
|
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
|
||||||
|
doc.disabled = 0
|
||||||
|
doc.save()
|
||||||
|
|
||||||
|
if not frappe.db.get_value('Accounting Dimension Filter',
|
||||||
|
{'accounting_dimension': 'Department'}):
|
||||||
|
frappe.get_doc({
|
||||||
|
'doctype': 'Accounting Dimension Filter',
|
||||||
|
'accounting_dimension': 'Department',
|
||||||
|
'allow_or_restrict': 'Allow',
|
||||||
|
'company': '_Test Company',
|
||||||
|
'accounts': [{
|
||||||
|
'applicable_on_account': 'Sales - _TC',
|
||||||
|
'is_mandatory': 1
|
||||||
|
}],
|
||||||
|
'dimensions': [{
|
||||||
|
'accounting_dimension': 'Department',
|
||||||
|
'dimension_value': 'Accounts - _TC'
|
||||||
|
}]
|
||||||
|
}).insert()
|
||||||
|
else:
|
||||||
|
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
|
||||||
|
doc.disabled = 0
|
||||||
|
doc.save()
|
||||||
|
|
||||||
|
def disable_dimension_filter():
|
||||||
|
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
|
||||||
|
doc.disabled = 1
|
||||||
|
doc.save()
|
||||||
|
|
||||||
|
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
|
||||||
|
doc.disabled = 1
|
||||||
|
doc.save()
|
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-11-08 18:22:36.001131",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"accounting_dimension",
|
||||||
|
"dimension_value"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "accounting_dimension",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Accounting Dimension",
|
||||||
|
"options": "DocType",
|
||||||
|
"read_only": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dimension_value",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"options": "accounting_dimension",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-11-23 09:56:19.744200",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Allowed Dimension",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class AllowedDimension(Document):
|
||||||
|
pass
|
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-11-08 18:20:00.944449",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"applicable_on_account",
|
||||||
|
"is_mandatory"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "applicable_on_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Accounts",
|
||||||
|
"options": "Account",
|
||||||
|
"reqd": 1,
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "is_mandatory",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Is Mandatory",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-11-22 19:55:13.324136",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Applicable On Account",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class ApplicableOnAccount(Document):
|
||||||
|
pass
|
@ -1,5 +1,6 @@
|
|||||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
frappe.provide('erpnext.integrations');
|
||||||
|
|
||||||
frappe.ui.form.on('Bank', {
|
frappe.ui.form.on('Bank', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
@ -20,7 +21,12 @@ frappe.ui.form.on('Bank', {
|
|||||||
frm.set_df_property('address_and_contact', 'hidden', 0);
|
frm.set_df_property('address_and_contact', 'hidden', 0);
|
||||||
frappe.contacts.render_address_and_contact(frm);
|
frappe.contacts.render_address_and_contact(frm);
|
||||||
}
|
}
|
||||||
},
|
if (frm.doc.plaid_access_token) {
|
||||||
|
frm.add_custom_button(__('Refresh Plaid Link'), () => {
|
||||||
|
new erpnext.integrations.refreshPlaidLink(frm.doc.plaid_access_token);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -40,4 +46,79 @@ let add_fields_to_mapping_table = function (frm) {
|
|||||||
frm.doc.name).options = options;
|
frm.doc.name).options = options;
|
||||||
|
|
||||||
frm.fields_dict.bank_transaction_mapping.grid.refresh();
|
frm.fields_dict.bank_transaction_mapping.grid.refresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
||||||
|
constructor(access_token) {
|
||||||
|
this.access_token = access_token;
|
||||||
|
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
|
||||||
|
this.init_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init_config() {
|
||||||
|
this.plaid_env = await frappe.db.get_single_value('Plaid Settings', 'plaid_env');
|
||||||
|
this.token = await this.get_link_token_for_update();
|
||||||
|
this.init_plaid();
|
||||||
|
}
|
||||||
|
|
||||||
|
async get_link_token_for_update() {
|
||||||
|
const token = frappe.xcall(
|
||||||
|
'erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_link_token_for_update',
|
||||||
|
{ access_token: this.access_token }
|
||||||
|
)
|
||||||
|
if (!token) {
|
||||||
|
frappe.throw(__('Cannot retrieve link token for update. Check Error Log for more information'));
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
init_plaid() {
|
||||||
|
const me = this;
|
||||||
|
me.loadScript(me.plaidUrl)
|
||||||
|
.then(() => {
|
||||||
|
me.onScriptLoaded(me);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
if (me.linkHandler) {
|
||||||
|
me.linkHandler.open();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
me.onScriptError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadScript(src) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
if (document.querySelector("script[src='" + src + "']")) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const el = document.createElement('script');
|
||||||
|
el.type = 'text/javascript';
|
||||||
|
el.async = true;
|
||||||
|
el.src = src;
|
||||||
|
el.addEventListener('load', resolve);
|
||||||
|
el.addEventListener('error', reject);
|
||||||
|
el.addEventListener('abort', reject);
|
||||||
|
document.head.appendChild(el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onScriptLoaded(me) {
|
||||||
|
me.linkHandler = Plaid.create({
|
||||||
|
env: me.plaid_env,
|
||||||
|
token: me.token,
|
||||||
|
onSuccess: me.plaid_success
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onScriptError(error) {
|
||||||
|
frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information"));
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
plaid_success(token, response) {
|
||||||
|
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -1,24 +1,9 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
frappe.ui.form.on('Budget', {
|
frappe.ui.form.on('Budget', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
frm.set_query("cost_center", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
company: frm.doc.company
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
frm.set_query("project", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
company: frm.doc.company
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
frm.set_query("account", "accounts", function() {
|
frm.set_query("account", "accounts", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
@ -26,16 +11,18 @@ frappe.ui.form.on('Budget', {
|
|||||||
report_type: "Profit and Loss",
|
report_type: "Profit and Loss",
|
||||||
is_group: 0
|
is_group: 0
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
frm.set_query("monthly_distribution", function() {
|
frm.set_query("monthly_distribution", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
fiscal_year: frm.doc.fiscal_year
|
fiscal_year: frm.doc.fiscal_year
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
|
@ -122,8 +122,10 @@ class TestBudget(unittest.TestCase):
|
|||||||
|
|
||||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||||
|
|
||||||
|
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||||
|
|
||||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||||
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
|
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project=project, posting_date=nowdate())
|
||||||
|
|
||||||
self.assertRaises(BudgetError, jv.submit)
|
self.assertRaises(BudgetError, jv.submit)
|
||||||
|
|
||||||
@ -147,8 +149,11 @@ class TestBudget(unittest.TestCase):
|
|||||||
|
|
||||||
budget = make_budget(budget_against="Project")
|
budget = make_budget(budget_against="Project")
|
||||||
|
|
||||||
|
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||||
|
|
||||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||||
"_Test Bank - _TC", 250000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
|
"_Test Bank - _TC", 250000, "_Test Cost Center - _TC",
|
||||||
|
project=project, posting_date=nowdate())
|
||||||
|
|
||||||
self.assertRaises(BudgetError, jv.submit)
|
self.assertRaises(BudgetError, jv.submit)
|
||||||
|
|
||||||
@ -159,10 +164,10 @@ class TestBudget(unittest.TestCase):
|
|||||||
|
|
||||||
budget = make_budget(budget_against="Cost Center")
|
budget = make_budget(budget_against="Cost Center")
|
||||||
month = now_datetime().month
|
month = now_datetime().month
|
||||||
if month > 10:
|
if month > 9:
|
||||||
month = 10
|
month = 9
|
||||||
|
|
||||||
for i in range(month):
|
for i in range(month+1):
|
||||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||||
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
|
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
|
||||||
|
|
||||||
@ -181,12 +186,14 @@ class TestBudget(unittest.TestCase):
|
|||||||
|
|
||||||
budget = make_budget(budget_against="Project")
|
budget = make_budget(budget_against="Project")
|
||||||
month = now_datetime().month
|
month = now_datetime().month
|
||||||
if month > 10:
|
if month > 9:
|
||||||
month = 10
|
month = 9
|
||||||
|
|
||||||
for i in range(month):
|
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||||
|
for i in range(month + 1):
|
||||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||||
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")
|
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True,
|
||||||
|
project=project)
|
||||||
|
|
||||||
self.assertTrue(frappe.db.get_value("GL Entry",
|
self.assertTrue(frappe.db.get_value("GL Entry",
|
||||||
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
|
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
|
||||||
@ -289,7 +296,7 @@ def make_budget(**args):
|
|||||||
budget = frappe.new_doc("Budget")
|
budget = frappe.new_doc("Budget")
|
||||||
|
|
||||||
if budget_against == "Project":
|
if budget_against == "Project":
|
||||||
budget.project = "_Test Project"
|
budget.project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||||
else:
|
else:
|
||||||
budget.cost_center =cost_center or "_Test Cost Center - _TC"
|
budget.cost_center =cost_center or "_Test Cost Center - _TC"
|
||||||
|
|
||||||
|
@ -11,8 +11,10 @@ from frappe.model.meta import get_field_precision
|
|||||||
from erpnext.accounts.party import validate_party_gle_currency, validate_party_frozen_disabled
|
from erpnext.accounts.party import validate_party_gle_currency, validate_party_frozen_disabled
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
from erpnext.exceptions import InvalidAccountCurrency
|
from erpnext.exceptions import InvalidAccountCurrency, InvalidAccountDimensionError, MandatoryAccountDimensionError
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts
|
||||||
|
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import get_dimension_filter_map
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
exclude_from_linked_with = True
|
exclude_from_linked_with = True
|
||||||
class GLEntry(Document):
|
class GLEntry(Document):
|
||||||
@ -39,6 +41,7 @@ class GLEntry(Document):
|
|||||||
if not from_repost:
|
if not from_repost:
|
||||||
self.validate_account_details(adv_adj)
|
self.validate_account_details(adv_adj)
|
||||||
self.validate_dimensions_for_pl_and_bs()
|
self.validate_dimensions_for_pl_and_bs()
|
||||||
|
self.validate_allowed_dimensions()
|
||||||
|
|
||||||
validate_frozen_account(self.account, adv_adj)
|
validate_frozen_account(self.account, adv_adj)
|
||||||
validate_balance_type(self.account, adv_adj)
|
validate_balance_type(self.account, adv_adj)
|
||||||
@ -76,11 +79,9 @@ class GLEntry(Document):
|
|||||||
.format(self.voucher_type, self.voucher_no, self.account))
|
.format(self.voucher_type, self.voucher_no, self.account))
|
||||||
|
|
||||||
def validate_dimensions_for_pl_and_bs(self):
|
def validate_dimensions_for_pl_and_bs(self):
|
||||||
|
|
||||||
account_type = frappe.db.get_value("Account", self.account, "report_type")
|
account_type = frappe.db.get_value("Account", self.account, "report_type")
|
||||||
|
|
||||||
for dimension in get_checks_for_pl_and_bs_accounts():
|
for dimension in get_checks_for_pl_and_bs_accounts():
|
||||||
|
|
||||||
if account_type == "Profit and Loss" \
|
if account_type == "Profit and Loss" \
|
||||||
and self.company == dimension.company and dimension.mandatory_for_pl and not dimension.disabled:
|
and self.company == dimension.company and dimension.mandatory_for_pl and not dimension.disabled:
|
||||||
if not self.get(dimension.fieldname):
|
if not self.get(dimension.fieldname):
|
||||||
@ -93,6 +94,25 @@ class GLEntry(Document):
|
|||||||
frappe.throw(_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.")
|
frappe.throw(_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.")
|
||||||
.format(dimension.label, self.account))
|
.format(dimension.label, self.account))
|
||||||
|
|
||||||
|
def validate_allowed_dimensions(self):
|
||||||
|
dimension_filter_map = get_dimension_filter_map()
|
||||||
|
for key, value in iteritems(dimension_filter_map):
|
||||||
|
dimension = key[0]
|
||||||
|
account = key[1]
|
||||||
|
|
||||||
|
if self.account == account:
|
||||||
|
if value['is_mandatory'] and not self.get(dimension):
|
||||||
|
frappe.throw(_("{0} is mandatory for account {1}").format(
|
||||||
|
frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), MandatoryAccountDimensionError)
|
||||||
|
|
||||||
|
if value['allow_or_restrict'] == 'Allow':
|
||||||
|
if self.get(dimension) and self.get(dimension) not in value['allowed_dimensions']:
|
||||||
|
frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
|
||||||
|
frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
|
||||||
|
else:
|
||||||
|
if self.get(dimension) and self.get(dimension) in value['allowed_dimensions']:
|
||||||
|
frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
|
||||||
|
frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
|
||||||
|
|
||||||
def check_pl_account(self):
|
def check_pl_account(self):
|
||||||
if self.is_opening=='Yes' and \
|
if self.is_opening=='Yes' and \
|
||||||
@ -137,9 +157,10 @@ class GLEntry(Document):
|
|||||||
frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}")
|
frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}")
|
||||||
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
|
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
|
||||||
|
|
||||||
if self.cost_center and _check_is_group():
|
if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \
|
||||||
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""")
|
and self.cost_center and _check_is_group():
|
||||||
.format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
|
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot
|
||||||
|
be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
|
||||||
|
|
||||||
def validate_party(self):
|
def validate_party(self):
|
||||||
validate_party_frozen_disabled(self.party_type, self.party)
|
validate_party_frozen_disabled(self.party_type, self.party)
|
||||||
|
@ -20,7 +20,8 @@ def get_data():
|
|||||||
'items': ['Purchase Invoice', 'Purchase Order', 'Purchase Receipt']
|
'items': ['Purchase Invoice', 'Purchase Order', 'Purchase Receipt']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'items': ['Item']
|
'label': _('Stock'),
|
||||||
|
'items': ['Item Groups', 'Item']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,8 @@ frappe.ui.form.on("Journal Entry", {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
voucher_type: function(frm){
|
voucher_type: function(frm){
|
||||||
@ -197,6 +199,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
|||||||
this.load_defaults();
|
this.load_defaults();
|
||||||
this.setup_queries();
|
this.setup_queries();
|
||||||
this.setup_balance_formatter();
|
this.setup_balance_formatter();
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
onload_post_render: function() {
|
onload_post_render: function() {
|
||||||
@ -222,15 +225,6 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
|||||||
return erpnext.journal_entry.account_query(me.frm);
|
return erpnext.journal_entry.account_query(me.frm);
|
||||||
});
|
});
|
||||||
|
|
||||||
me.frm.set_query("cost_center", "accounts", function(doc, cdt, cdn) {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
company: me.frm.doc.company,
|
|
||||||
is_group: 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) {
|
me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) {
|
||||||
const row = locals[cdt][cdn];
|
const row = locals[cdt][cdn];
|
||||||
|
|
||||||
@ -406,6 +400,8 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cur_frm.cscript.update_totals(doc);
|
cur_frm.cscript.update_totals(doc);
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.copy_dimension_from_first_row(this.frm, cdt, cdn, 'accounts');
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -160,7 +160,7 @@ class TestJournalEntry(unittest.TestCase):
|
|||||||
self.assertFalse(gle)
|
self.assertFalse(gle)
|
||||||
|
|
||||||
def test_reverse_journal_entry(self):
|
def test_reverse_journal_entry(self):
|
||||||
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
|
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
|
||||||
jv = make_journal_entry("_Test Bank USD - _TC",
|
jv = make_journal_entry("_Test Bank USD - _TC",
|
||||||
"Sales - _TC", 100, exchange_rate=50, save=False)
|
"Sales - _TC", 100, exchange_rate=50, save=False)
|
||||||
|
|
||||||
@ -299,15 +299,20 @@ class TestJournalEntry(unittest.TestCase):
|
|||||||
|
|
||||||
def test_jv_with_project(self):
|
def test_jv_with_project(self):
|
||||||
from erpnext.projects.doctype.project.test_project import make_project
|
from erpnext.projects.doctype.project.test_project import make_project
|
||||||
project = make_project({
|
|
||||||
'project_name': 'Journal Entry Project',
|
if not frappe.db.exists("Project", {"project_name": "Journal Entry Project"}):
|
||||||
'project_template_name': 'Test Project Template',
|
project = make_project({
|
||||||
'start_date': '2020-01-01'
|
'project_name': 'Journal Entry Project',
|
||||||
})
|
'project_template_name': 'Test Project Template',
|
||||||
|
'start_date': '2020-01-01'
|
||||||
|
})
|
||||||
|
project_name = project.name
|
||||||
|
else:
|
||||||
|
project_name = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||||
|
|
||||||
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
|
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
|
||||||
for d in jv.accounts:
|
for d in jv.accounts:
|
||||||
d.project = project.project_name
|
d.project = project_name
|
||||||
jv.voucher_type = "Bank Entry"
|
jv.voucher_type = "Bank Entry"
|
||||||
jv.multi_currency = 0
|
jv.multi_currency = 0
|
||||||
jv.cheque_no = "112233"
|
jv.cheque_no = "112233"
|
||||||
@ -317,10 +322,10 @@ class TestJournalEntry(unittest.TestCase):
|
|||||||
|
|
||||||
expected_values = {
|
expected_values = {
|
||||||
"_Test Cash - _TC": {
|
"_Test Cash - _TC": {
|
||||||
"project": project.project_name
|
"project": project_name
|
||||||
},
|
},
|
||||||
"_Test Bank - _TC": {
|
"_Test Bank - _TC": {
|
||||||
"project": project.project_name
|
"project": project_name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
frappe.ui.form.on('Loyalty Program', {
|
frappe.ui.form.on('Loyalty Program', {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
var help_content =
|
var help_content =
|
||||||
@ -46,20 +48,17 @@ frappe.ui.form.on('Loyalty Program', {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("cost_center", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
company: frm.doc.company
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
frm.set_value("company", frappe.defaults.get_user_default("Company"));
|
frm.set_value("company", frappe.defaults.get_user_default("Company"));
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
if (frm.doc.loyalty_program_type === "Single Tier Program" && frm.doc.collection_rules.length > 1) {
|
if (frm.doc.loyalty_program_type === "Single Tier Program" && frm.doc.collection_rules.length > 1) {
|
||||||
frappe.throw(__("Please select the Multiple Tier Program type for more than one collection rules."));
|
frappe.throw(__("Please select the Multiple Tier Program type for more than one collection rules."));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
company: function(frm) {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -36,6 +36,8 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
|
|||||||
frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
|
frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
|
||||||
frm.page.set_indicator(__('In Progress'), 'orange');
|
frm.page.set_indicator(__('In Progress'), 'orange');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
@ -100,6 +102,7 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
invoice_type: function(frm) {
|
invoice_type: function(frm) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
{% include "erpnext/public/js/controllers/accounts.js" %}
|
{% include "erpnext/public/js/controllers/accounts.js" %}
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
frappe.ui.form.on('Payment Entry', {
|
frappe.ui.form.on('Payment Entry', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
@ -8,6 +9,8 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
|
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
|
||||||
if (!frm.doc.paid_to) frm.set_value("paid_to_account_currency", null);
|
if (!frm.doc.paid_to) frm.set_value("paid_to_account_currency", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
@ -88,15 +91,6 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("cost_center", "deductions", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
"is_group": 0,
|
|
||||||
"company": frm.doc.company
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
frm.set_query("reference_doctype", "references", function() {
|
frm.set_query("reference_doctype", "references", function() {
|
||||||
if (frm.doc.party_type=="Customer") {
|
if (frm.doc.party_type=="Customer") {
|
||||||
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
||||||
@ -167,6 +161,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
company: function(frm) {
|
company: function(frm) {
|
||||||
frm.events.hide_unhide_fields(frm);
|
frm.events.hide_unhide_fields(frm);
|
||||||
frm.events.set_dynamic_labels(frm);
|
frm.events.set_dynamic_labels(frm);
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
contact_person: function(frm) {
|
contact_person: function(frm) {
|
||||||
@ -401,6 +396,8 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
|
|
||||||
set_account_currency_and_balance: function(frm, account, currency_field,
|
set_account_currency_and_balance: function(frm, account, currency_field,
|
||||||
balance_field, callback_function) {
|
balance_field, callback_function) {
|
||||||
|
|
||||||
|
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||||
if (frm.doc.posting_date && account) {
|
if (frm.doc.posting_date && account) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details",
|
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details",
|
||||||
@ -427,6 +424,14 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
|
|
||||||
if(!frm.doc.paid_amount && frm.doc.received_amount)
|
if(!frm.doc.paid_amount && frm.doc.received_amount)
|
||||||
frm.events.received_amount(frm);
|
frm.events.received_amount(frm);
|
||||||
|
|
||||||
|
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency
|
||||||
|
&& frm.doc.paid_amount != frm.doc.received_amount) {
|
||||||
|
if (company_currency != frm.doc.paid_from_account_currency &&
|
||||||
|
frm.doc.payment_type == "Pay") {
|
||||||
|
frm.doc.paid_amount = frm.doc.received_amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
|
@ -8,7 +8,7 @@ from frappe import _
|
|||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (get_accounting_dimensions,
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (get_accounting_dimensions,
|
||||||
get_dimension_filters)
|
get_dimensions)
|
||||||
|
|
||||||
class PeriodClosingVoucher(AccountsController):
|
class PeriodClosingVoucher(AccountsController):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@ -58,7 +58,7 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
for dimension in accounting_dimensions:
|
for dimension in accounting_dimensions:
|
||||||
dimension_fields.append('t1.{0}'.format(dimension))
|
dimension_fields.append('t1.{0}'.format(dimension))
|
||||||
|
|
||||||
dimension_filters, default_dimensions = get_dimension_filters()
|
dimension_filters, default_dimensions = get_dimensions()
|
||||||
|
|
||||||
pl_accounts = self.get_pl_balances(dimension_fields)
|
pl_accounts = self.get_pl_balances(dimension_fields)
|
||||||
|
|
||||||
|
@ -57,6 +57,8 @@ frappe.ui.form.on('POS Profile', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
@ -67,6 +69,7 @@ frappe.ui.form.on('POS Profile', {
|
|||||||
|
|
||||||
company: function(frm) {
|
company: function(frm) {
|
||||||
frm.trigger("toggle_display_account_head");
|
frm.trigger("toggle_display_account_head");
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
toggle_display_account_head: function(frm) {
|
toggle_display_account_head: function(frm) {
|
||||||
|
@ -26,6 +26,11 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
company: function() {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
onload: function() {
|
onload: function() {
|
||||||
this._super();
|
this._super();
|
||||||
|
|
||||||
@ -41,6 +46,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
|||||||
if (this.frm.doc.supplier && this.frm.doc.__islocal) {
|
if (this.frm.doc.supplier && this.frm.doc.__islocal) {
|
||||||
this.frm.trigger('supplier');
|
this.frm.trigger('supplier');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(doc) {
|
refresh: function(doc) {
|
||||||
@ -498,7 +505,7 @@ cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
|
|||||||
frappe.ui.form.on("Purchase Invoice", {
|
frappe.ui.form.on("Purchase Invoice", {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.custom_make_buttons = {
|
frm.custom_make_buttons = {
|
||||||
'Purchase Invoice': 'Debit Note',
|
'Purchase Invoice': 'Return / Debit Note',
|
||||||
'Payment Entry': 'Payment'
|
'Payment Entry': 'Payment'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,15 +518,6 @@ frappe.ui.form.on("Purchase Invoice", {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.set_query("cost_center", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
company: frm.doc.company,
|
|
||||||
is_group: 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
|
@ -426,26 +426,31 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_total_purchase_cost_for_project(self):
|
def test_total_purchase_cost_for_project(self):
|
||||||
make_project({'project_name':'_Test Project'})
|
if not frappe.db.exists("Project", {"project_name": "_Test Project for Purchase"}):
|
||||||
|
project = make_project({'project_name':'_Test Project for Purchase'})
|
||||||
|
else:
|
||||||
|
project = frappe.get_doc("Project", {"project_name": "_Test Project for Purchase"})
|
||||||
|
|
||||||
existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
|
existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
|
||||||
from `tabPurchase Invoice Item` where project = '_Test Project' and docstatus=1""")
|
from `tabPurchase Invoice Item`
|
||||||
|
where project = '{0}'
|
||||||
|
and docstatus=1""".format(project.name))
|
||||||
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
|
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
|
||||||
|
|
||||||
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project="_Test Project")
|
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project=project.name)
|
||||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
|
||||||
existing_purchase_cost + 15000)
|
existing_purchase_cost + 15000)
|
||||||
|
|
||||||
pi1 = make_purchase_invoice(qty=10, project="_Test Project")
|
pi1 = make_purchase_invoice(qty=10, project=project.name)
|
||||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
|
||||||
existing_purchase_cost + 15500)
|
existing_purchase_cost + 15500)
|
||||||
|
|
||||||
pi1.cancel()
|
pi1.cancel()
|
||||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
|
||||||
existing_purchase_cost + 15000)
|
existing_purchase_cost + 15000)
|
||||||
|
|
||||||
pi.cancel()
|
pi.cancel()
|
||||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), existing_purchase_cost)
|
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"), existing_purchase_cost)
|
||||||
|
|
||||||
def test_return_purchase_invoice_with_perpetual_inventory(self):
|
def test_return_purchase_invoice_with_perpetual_inventory(self):
|
||||||
pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
|
pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
|
||||||
@ -860,17 +865,17 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
|
|
||||||
pi = make_purchase_invoice(credit_to="Creditors - _TC" ,do_not_save=1)
|
pi = make_purchase_invoice(credit_to="Creditors - _TC" ,do_not_save=1)
|
||||||
pi.items[0].project = item_project.project_name
|
pi.items[0].project = item_project.name
|
||||||
pi.project = project.project_name
|
pi.project = project.name
|
||||||
|
|
||||||
pi.submit()
|
pi.submit()
|
||||||
|
|
||||||
expected_values = {
|
expected_values = {
|
||||||
"Creditors - _TC": {
|
"Creditors - _TC": {
|
||||||
"project": project.project_name
|
"project": project.name
|
||||||
},
|
},
|
||||||
"_Test Account Cost for Goods Sold - _TC": {
|
"_Test Account Cost for Goods Sold - _TC": {
|
||||||
"project": item_project.project_name
|
"project": item_project.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,14 +5,17 @@
|
|||||||
cur_frm.pformat.print_heading = 'Invoice';
|
cur_frm.pformat.print_heading = 'Invoice';
|
||||||
|
|
||||||
{% include 'erpnext/selling/sales_common.js' %};
|
{% include 'erpnext/selling/sales_common.js' %};
|
||||||
|
|
||||||
|
|
||||||
frappe.provide("erpnext.accounts");
|
frappe.provide("erpnext.accounts");
|
||||||
|
|
||||||
|
|
||||||
erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.extend({
|
erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.extend({
|
||||||
setup: function(doc) {
|
setup: function(doc) {
|
||||||
this.setup_posting_date_time_check();
|
this.setup_posting_date_time_check();
|
||||||
this._super(doc);
|
this._super(doc);
|
||||||
},
|
},
|
||||||
|
company: function() {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||||
|
},
|
||||||
onload: function() {
|
onload: function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
this._super();
|
this._super();
|
||||||
@ -33,6 +36,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
|||||||
me.frm.refresh_fields();
|
me.frm.refresh_fields();
|
||||||
}
|
}
|
||||||
erpnext.queries.setup_warehouse_query(this.frm);
|
erpnext.queries.setup_warehouse_query(this.frm);
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(doc, dt, dn) {
|
refresh: function(doc, dt, dn) {
|
||||||
@ -571,15 +575,6 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("cost_center", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
company: frm.doc.company,
|
|
||||||
is_group: 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
frm.set_query("unrealized_profit_loss_account", function() {
|
frm.set_query("unrealized_profit_loss_account", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
@ -592,7 +587,7 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
|
|
||||||
frm.custom_make_buttons = {
|
frm.custom_make_buttons = {
|
||||||
'Delivery Note': 'Delivery',
|
'Delivery Note': 'Delivery',
|
||||||
'Sales Invoice': 'Sales Return',
|
'Sales Invoice': 'Return / Credit Note',
|
||||||
'Payment Request': 'Payment Request',
|
'Payment Request': 'Payment Request',
|
||||||
'Payment Entry': 'Payment'
|
'Payment Entry': 'Payment'
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, erpnext
|
import frappe, erpnext
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate, get_link_to_form
|
from frappe.utils import cint, flt, getdate, add_days, cstr, nowdate, get_link_to_form, formatdate
|
||||||
from frappe import _, msgprint, throw
|
from frappe import _, msgprint, throw
|
||||||
from erpnext.accounts.party import get_party_account, get_due_date
|
from erpnext.accounts.party import get_party_account, get_due_date
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
@ -179,7 +179,7 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
# this sequence because outstanding may get -ve
|
# this sequence because outstanding may get -ve
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
if self.update_stock == 1:
|
if self.update_stock == 1:
|
||||||
self.repost_future_sle_and_gle()
|
self.repost_future_sle_and_gle()
|
||||||
|
|
||||||
@ -261,10 +261,10 @@ class SalesInvoice(SellingController):
|
|||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
|
|
||||||
self.make_gl_entries_on_cancel()
|
self.make_gl_entries_on_cancel()
|
||||||
|
|
||||||
if self.update_stock == 1:
|
if self.update_stock == 1:
|
||||||
self.repost_future_sle_and_gle()
|
self.repost_future_sle_and_gle()
|
||||||
|
|
||||||
frappe.db.set(self, 'status', 'Cancelled')
|
frappe.db.set(self, 'status', 'Cancelled')
|
||||||
|
|
||||||
if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction":
|
if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction":
|
||||||
@ -549,7 +549,12 @@ class SalesInvoice(SellingController):
|
|||||||
self.against_income_account = ','.join(against_acc)
|
self.against_income_account = ','.join(against_acc)
|
||||||
|
|
||||||
def add_remarks(self):
|
def add_remarks(self):
|
||||||
if not self.remarks: self.remarks = 'No Remarks'
|
if not self.remarks:
|
||||||
|
if self.po_no and self.po_date:
|
||||||
|
self.remarks = _("Against Customer Order {0} dated {1}").format(self.po_no,
|
||||||
|
formatdate(self.po_date))
|
||||||
|
else:
|
||||||
|
self.remarks = _("No Remarks")
|
||||||
|
|
||||||
def validate_auto_set_posting_time(self):
|
def validate_auto_set_posting_time(self):
|
||||||
# Don't auto set the posting date and time if invoice is amended
|
# Don't auto set the posting date and time if invoice is amended
|
||||||
@ -1694,6 +1699,7 @@ def get_mode_of_payment_info(mode_of_payment, company):
|
|||||||
where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""",
|
where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""",
|
||||||
(company, mode_of_payment), as_dict=1)
|
(company, mode_of_payment), as_dict=1)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def create_dunning(source_name, target_doc=None):
|
def create_dunning(source_name, target_doc=None):
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text, calculate_interest_and_amount
|
from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text, calculate_interest_and_amount
|
||||||
|
@ -1573,17 +1573,17 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
|
|
||||||
sales_invoice = create_sales_invoice(do_not_save=1)
|
sales_invoice = create_sales_invoice(do_not_save=1)
|
||||||
sales_invoice.items[0].project = item_project.project_name
|
sales_invoice.items[0].project = item_project.name
|
||||||
sales_invoice.project = project.project_name
|
sales_invoice.project = project.name
|
||||||
|
|
||||||
sales_invoice.submit()
|
sales_invoice.submit()
|
||||||
|
|
||||||
expected_values = {
|
expected_values = {
|
||||||
"Debtors - _TC": {
|
"Debtors - _TC": {
|
||||||
"project": project.project_name
|
"project": project.name
|
||||||
},
|
},
|
||||||
"Sales - _TC": {
|
"Sales - _TC": {
|
||||||
"project": item_project.project_name
|
"project": item_project.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Shipping Rule', {
|
frappe.provide('erpnext.accounts.dimensions');
|
||||||
refresh: function(frm) {
|
|
||||||
frm.set_query("cost_center", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
company: frm.doc.company
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
frappe.ui.form.on('Shipping Rule', {
|
||||||
|
onload: function(frm) {
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
|
company: function(frm) {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh: function(frm) {
|
||||||
frm.set_query("account", function() {
|
frm.set_query("account", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
|
@ -446,7 +446,7 @@ class Subscription(Document):
|
|||||||
if not self.generate_invoice_at_period_start:
|
if not self.generate_invoice_at_period_start:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.is_new_subscription():
|
if self.is_new_subscription() and getdate() >= getdate(self.current_invoice_start):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check invoice dates and make sure it doesn't have outstanding invoices
|
# Check invoice dates and make sure it doesn't have outstanding invoices
|
||||||
|
@ -59,23 +59,111 @@ def validate_filters(filters):
|
|||||||
|
|
||||||
def get_columns(filters):
|
def get_columns(filters):
|
||||||
return [
|
return [
|
||||||
_("Payment Document") + ":: 100",
|
{
|
||||||
_("Payment Entry") + ":Dynamic Link/"+_("Payment Document")+":140",
|
"fieldname": "payment_document",
|
||||||
_("Party Type") + "::100",
|
"label": _("Payment Document Type"),
|
||||||
_("Party") + ":Dynamic Link/Party Type:140",
|
"fieldtype": "Data",
|
||||||
_("Posting Date") + ":Date:100",
|
"width": 100
|
||||||
_("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == _("Outgoing") else ":Link/Sales Invoice:130"),
|
},
|
||||||
_("Invoice Posting Date") + ":Date:130",
|
{
|
||||||
_("Payment Due Date") + ":Date:130",
|
"fieldname": "payment_entry",
|
||||||
_("Debit") + ":Currency:120",
|
"label": _("Payment Document"),
|
||||||
_("Credit") + ":Currency:120",
|
"fieldtype": "Dynamic Link",
|
||||||
_("Remarks") + "::150",
|
"options": "payment_document",
|
||||||
_("Age") +":Int:40",
|
"width": 160
|
||||||
"0-30:Currency:100",
|
},
|
||||||
"30-60:Currency:100",
|
{
|
||||||
"60-90:Currency:100",
|
"fieldname": "party_type",
|
||||||
_("90-Above") + ":Currency:100",
|
"label": _("Party Type"),
|
||||||
_("Delay in payment (Days)") + "::150"
|
"fieldtype": "Data",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "party",
|
||||||
|
"label": _("Party"),
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"options": "party_type",
|
||||||
|
"width": 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_date",
|
||||||
|
"label": _("Posting Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "invoice",
|
||||||
|
"label": _("Invoice"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Purchase Invoice" if filters.get("payment_type") == _("Outgoing") else "Sales Invoice",
|
||||||
|
"width": 160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "invoice_posting_date",
|
||||||
|
"label": _("Invoice Posting Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "due_date",
|
||||||
|
"label": _("Payment Due Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "debit",
|
||||||
|
"label": _("Debit"),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "credit",
|
||||||
|
"label": _("Credit"),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "remarks",
|
||||||
|
"label": _("Remarks"),
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "age",
|
||||||
|
"label": _("Age"),
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"width": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "range1",
|
||||||
|
"label": "0-30",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "range2",
|
||||||
|
"label": "30-60",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "range3",
|
||||||
|
"label": "60-90",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "range4",
|
||||||
|
"label": _("90 Above"),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "delay_in_payment",
|
||||||
|
"label": _("Delay in payment (Days)"),
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"width": 100
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_conditions(filters):
|
def get_conditions(filters):
|
||||||
|
@ -48,7 +48,7 @@ class CropCycle(Document):
|
|||||||
|
|
||||||
def import_disease_tasks(self, disease, start_date):
|
def import_disease_tasks(self, disease, start_date):
|
||||||
disease_doc = frappe.get_doc('Disease', disease)
|
disease_doc = frappe.get_doc('Disease', disease)
|
||||||
self.create_task(disease_doc.treatment_task, self.name, start_date)
|
self.create_task(disease_doc.treatment_task, self.project, start_date)
|
||||||
|
|
||||||
def create_project(self, period, crop_tasks):
|
def create_project(self, period, crop_tasks):
|
||||||
project = frappe.get_doc({
|
project = frappe.get_doc({
|
||||||
|
@ -71,4 +71,4 @@ def check_task_creation():
|
|||||||
|
|
||||||
|
|
||||||
def check_project_creation():
|
def check_project_creation():
|
||||||
return True if frappe.db.exists('Project', 'Basil from seed 2017') else False
|
return True if frappe.db.exists('Project', {'project_name': 'Basil from seed 2017'}) else False
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.provide("erpnext.asset");
|
frappe.provide("erpnext.asset");
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
frappe.ui.form.on('Asset', {
|
frappe.ui.form.on('Asset', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
@ -32,13 +33,11 @@ frappe.ui.form.on('Asset', {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("cost_center", function() {
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
return {
|
},
|
||||||
"filters": {
|
|
||||||
"company": frm.doc.company,
|
company: function(frm) {
|
||||||
}
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
frappe.ui.form.on('Asset Value Adjustment', {
|
frappe.ui.form.on('Asset Value Adjustment', {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.add_fetch('company', 'cost_center', 'cost_center');
|
frm.add_fetch('company', 'cost_center', 'cost_center');
|
||||||
@ -13,11 +15,19 @@ frappe.ui.form.on('Asset Value Adjustment', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
if(frm.is_new() && frm.doc.asset) {
|
if(frm.is_new() && frm.doc.asset) {
|
||||||
frm.trigger("set_current_asset_value");
|
frm.trigger("set_current_asset_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
company: function(frm) {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
asset: function(frm) {
|
asset: function(frm) {
|
||||||
frm.trigger("set_current_asset_value");
|
frm.trigger("set_current_asset_value");
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
frappe.provide("erpnext.buying");
|
frappe.provide("erpnext.buying");
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
{% include 'erpnext/public/js/controllers/buying.js' %};
|
{% include 'erpnext/public/js/controllers/buying.js' %};
|
||||||
|
|
||||||
frappe.ui.form.on("Purchase Order", {
|
frappe.ui.form.on("Purchase Order", {
|
||||||
@ -30,6 +30,10 @@ frappe.ui.form.on("Purchase Order", {
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
company: function(frm) {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
set_schedule_date(frm);
|
set_schedule_date(frm);
|
||||||
if (!frm.doc.transaction_date){
|
if (!frm.doc.transaction_date){
|
||||||
@ -39,6 +43,8 @@ frappe.ui.form.on("Purchase Order", {
|
|||||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||||
return erpnext.queries.warehouse(frm.doc);
|
return erpnext.queries.warehouse(frm.doc);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -58,8 +64,8 @@ frappe.ui.form.on("Purchase Order Item", {
|
|||||||
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
|
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
|
||||||
setup: function() {
|
setup: function() {
|
||||||
this.frm.custom_make_buttons = {
|
this.frm.custom_make_buttons = {
|
||||||
'Purchase Receipt': 'Receipt',
|
'Purchase Receipt': 'Purchase Receipt',
|
||||||
'Purchase Invoice': 'Invoice',
|
'Purchase Invoice': 'Purchase Invoice',
|
||||||
'Stock Entry': 'Material to Supplier',
|
'Stock Entry': 'Material to Supplier',
|
||||||
'Payment Entry': 'Payment',
|
'Payment Entry': 'Payment',
|
||||||
}
|
}
|
||||||
|
@ -75,62 +75,70 @@ frappe.query_reports["Purchase Analytics"] = {
|
|||||||
return Object.assign(options, {
|
return Object.assign(options, {
|
||||||
checkboxColumn: true,
|
checkboxColumn: true,
|
||||||
events: {
|
events: {
|
||||||
onCheckRow: function(data) {
|
onCheckRow: function (data) {
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const data_doctype = $(
|
||||||
|
data[2].html
|
||||||
|
)[0].attributes.getNamedItem("data-doctype").value;
|
||||||
|
const tree_type = frappe.query_report.filters[0].value;
|
||||||
|
if (data_doctype != tree_type) return;
|
||||||
|
|
||||||
row_name = data[2].content;
|
row_name = data[2].content;
|
||||||
length = data.length;
|
length = data.length;
|
||||||
|
|
||||||
var tree_type = frappe.query_report.filters[0].value;
|
if (tree_type == "Supplier") {
|
||||||
|
row_values = data
|
||||||
if(tree_type == "Supplier" || tree_type == "Item") {
|
.slice(4, length - 1)
|
||||||
row_values = data.slice(4,length-1).map(function (column) {
|
.map(function (column) {
|
||||||
return column.content;
|
return column.content;
|
||||||
})
|
});
|
||||||
}
|
} else if (tree_type == "Item") {
|
||||||
else {
|
row_values = data
|
||||||
row_values = data.slice(3,length-1).map(function (column) {
|
.slice(5, length - 1)
|
||||||
return column.content;
|
.map(function (column) {
|
||||||
})
|
return column.content;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
row_values = data
|
||||||
|
.slice(3, length - 1)
|
||||||
|
.map(function (column) {
|
||||||
|
return column.content;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
entry = {
|
entry = {
|
||||||
'name':row_name,
|
name: row_name,
|
||||||
'values':row_values
|
values: row_values,
|
||||||
}
|
};
|
||||||
|
|
||||||
let raw_data = frappe.query_report.chart.data;
|
let raw_data = frappe.query_report.chart.data;
|
||||||
let new_datasets = raw_data.datasets;
|
let new_datasets = raw_data.datasets;
|
||||||
|
|
||||||
var found = false;
|
let element_found = new_datasets.some((element, index, array)=>{
|
||||||
|
if(element.name == row_name){
|
||||||
for(var i=0; i < new_datasets.length;i++){
|
array.splice(index, 1)
|
||||||
if(new_datasets[i].name == row_name){
|
return true
|
||||||
found = true;
|
|
||||||
new_datasets.splice(i,1);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
if(!found){
|
if (!element_found) {
|
||||||
new_datasets.push(entry);
|
new_datasets.push(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_data = {
|
let new_data = {
|
||||||
labels: raw_data.labels,
|
labels: raw_data.labels,
|
||||||
datasets: new_datasets
|
datasets: new_datasets,
|
||||||
}
|
};
|
||||||
|
chart_options = {
|
||||||
setTimeout(() => {
|
data: new_data,
|
||||||
frappe.query_report.chart.update(new_data)
|
type: "line",
|
||||||
},500)
|
};
|
||||||
|
frappe.query_report.render_chart(chart_options);
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
frappe.query_report.chart.draw(true);
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
frappe.query_report.raw_chart_data = new_data;
|
frappe.query_report.raw_chart_data = new_data;
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,9 +35,10 @@ def update_last_purchase_rate(doc, is_submit):
|
|||||||
frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx))
|
frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx))
|
||||||
|
|
||||||
# update last purchsae rate
|
# update last purchsae rate
|
||||||
if last_purchase_rate:
|
frappe.db.set_value('Item', d.item_code, 'last_purchase_rate', flt(last_purchase_rate))
|
||||||
frappe.db.sql("""update `tabItem` set last_purchase_rate = %s where name = %s""",
|
|
||||||
(flt(last_purchase_rate), d.item_code))
|
|
||||||
|
|
||||||
|
|
||||||
def validate_for_items(doc):
|
def validate_for_items(doc):
|
||||||
items = []
|
items = []
|
||||||
|
@ -493,6 +493,41 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
'company': filters.get("company", "")
|
'company': filters.get("company", "")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
|
def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import get_dimension_filter_map
|
||||||
|
dimension_filters = get_dimension_filter_map()
|
||||||
|
dimension_filters = dimension_filters.get((filters.get('dimension'),filters.get('account')))
|
||||||
|
query_filters = []
|
||||||
|
|
||||||
|
meta = frappe.get_meta(doctype)
|
||||||
|
if meta.is_tree:
|
||||||
|
query_filters.append(['is_group', '=', 0])
|
||||||
|
|
||||||
|
if meta.has_field('company'):
|
||||||
|
query_filters.append(['company', '=', filters.get('company')])
|
||||||
|
|
||||||
|
if txt:
|
||||||
|
query_filters.append([searchfield, 'LIKE', "%%%s%%" % txt])
|
||||||
|
|
||||||
|
if dimension_filters:
|
||||||
|
if dimension_filters['allow_or_restrict'] == 'Allow':
|
||||||
|
query_selector = 'in'
|
||||||
|
else:
|
||||||
|
query_selector = 'not in'
|
||||||
|
|
||||||
|
if len(dimension_filters['allowed_dimensions']) == 1:
|
||||||
|
dimensions = tuple(dimension_filters['allowed_dimensions'] * 2)
|
||||||
|
else:
|
||||||
|
dimensions = tuple(dimension_filters['allowed_dimensions'])
|
||||||
|
|
||||||
|
query_filters.append(['name', query_selector, dimensions])
|
||||||
|
|
||||||
|
output = frappe.get_all(doctype, filters=query_filters)
|
||||||
|
result = [d.name for d in output]
|
||||||
|
|
||||||
|
return [(d,) for d in set(result)]
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
|
@ -328,6 +328,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
|||||||
target_doc.po_detail = source_doc.po_detail
|
target_doc.po_detail = source_doc.po_detail
|
||||||
target_doc.pr_detail = source_doc.pr_detail
|
target_doc.pr_detail = source_doc.pr_detail
|
||||||
target_doc.purchase_invoice_item = source_doc.name
|
target_doc.purchase_invoice_item = source_doc.name
|
||||||
|
target_doc.price_list_rate = 0
|
||||||
|
|
||||||
elif doctype == "Delivery Note":
|
elif doctype == "Delivery Note":
|
||||||
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
|
returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
|
||||||
@ -353,6 +354,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
|||||||
target_doc.dn_detail = source_doc.dn_detail
|
target_doc.dn_detail = source_doc.dn_detail
|
||||||
target_doc.expense_account = source_doc.expense_account
|
target_doc.expense_account = source_doc.expense_account
|
||||||
target_doc.sales_invoice_item = source_doc.name
|
target_doc.sales_invoice_item = source_doc.name
|
||||||
|
target_doc.price_list_rate = 0
|
||||||
if default_warehouse_for_sales_return:
|
if default_warehouse_for_sales_return:
|
||||||
target_doc.warehouse = default_warehouse_for_sales_return
|
target_doc.warehouse = default_warehouse_for_sales_return
|
||||||
|
|
||||||
|
@ -233,7 +233,7 @@ class SellingController(StockController):
|
|||||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||||
'dn_detail': d.get("dn_detail"),
|
'dn_detail': d.get("dn_detail"),
|
||||||
'incoming_rate': p.incoming_rate
|
'incoming_rate': p.get("incoming_rate")
|
||||||
}))
|
}))
|
||||||
else:
|
else:
|
||||||
il.append(frappe._dict({
|
il.append(frappe._dict({
|
||||||
@ -252,7 +252,7 @@ class SellingController(StockController):
|
|||||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||||
'dn_detail': d.get("dn_detail"),
|
'dn_detail': d.get("dn_detail"),
|
||||||
'incoming_rate': d.incoming_rate
|
'incoming_rate': d.get("incoming_rate")
|
||||||
}))
|
}))
|
||||||
return il
|
return il
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import unittest
|
|||||||
|
|
||||||
from erpnext.stock.doctype.item.test_item import set_item_variant_settings
|
from erpnext.stock.doctype.item.test_item import set_item_variant_settings
|
||||||
from erpnext.controllers.item_variant import copy_attributes_to_variant, make_variant_item_code
|
from erpnext.controllers.item_variant import copy_attributes_to_variant, make_variant_item_code
|
||||||
|
from erpnext.stock.doctype.quality_inspection.test_quality_inspection import create_quality_inspection_parameter
|
||||||
|
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
|
||||||
@ -56,6 +57,8 @@ def make_quality_inspection_template():
|
|||||||
|
|
||||||
qc = frappe.new_doc("Quality Inspection Template")
|
qc = frappe.new_doc("Quality Inspection Template")
|
||||||
qc.quality_inspection_template_name = qc_template
|
qc.quality_inspection_template_name = qc_template
|
||||||
|
|
||||||
|
create_quality_inspection_parameter("Moisture")
|
||||||
qc.append('item_quality_inspection_parameter', {
|
qc.append('item_quality_inspection_parameter', {
|
||||||
"specification": "Moisture",
|
"specification": "Moisture",
|
||||||
"value": "< 5%",
|
"value": "< 5%",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
frappe.ui.form.on('Fee Schedule', {
|
frappe.ui.form.on('Fee Schedule', {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.add_fetch('fee_structure', 'receivable_account', 'receivable_account');
|
frm.add_fetch('fee_structure', 'receivable_account', 'receivable_account');
|
||||||
@ -8,6 +9,10 @@ frappe.ui.form.on('Fee Schedule', {
|
|||||||
frm.add_fetch('fee_structure', 'cost_center', 'cost_center');
|
frm.add_fetch('fee_structure', 'cost_center', 'cost_center');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
company: function(frm) {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
frm.set_query('receivable_account', function(doc) {
|
frm.set_query('receivable_account', function(doc) {
|
||||||
return {
|
return {
|
||||||
@ -50,6 +55,8 @@ frappe.ui.form.on('Fee Schedule', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
frappe.ui.form.on('Fee Structure', {
|
frappe.ui.form.on('Fee Structure', {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.add_fetch('company', 'default_receivable_account', 'receivable_account');
|
frm.add_fetch('company', 'default_receivable_account', 'receivable_account');
|
||||||
@ -8,6 +10,10 @@ frappe.ui.form.on('Fee Structure', {
|
|||||||
frm.add_fetch('company', 'cost_center', 'cost_center');
|
frm.add_fetch('company', 'cost_center', 'cost_center');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
company: function(frm) {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
frm.set_query('academic_term', function() {
|
frm.set_query('academic_term', function() {
|
||||||
return {
|
return {
|
||||||
@ -35,6 +41,8 @@ frappe.ui.form.on('Fee Structure', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
frappe.ui.form.on("Fees", {
|
frappe.ui.form.on("Fees", {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
@ -9,15 +10,19 @@ frappe.ui.form.on("Fees", {
|
|||||||
frm.add_fetch("fee_structure", "cost_center", "cost_center");
|
frm.add_fetch("fee_structure", "cost_center", "cost_center");
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: function(frm){
|
company: function(frm) {
|
||||||
frm.set_query("academic_term",function(){
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
|
onload: function(frm) {
|
||||||
|
frm.set_query("academic_term", function() {
|
||||||
return{
|
return{
|
||||||
"filters":{
|
"filters": {
|
||||||
"academic_year": (frm.doc.academic_year)
|
"academic_year": (frm.doc.academic_year)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
frm.set_query("fee_structure",function(){
|
frm.set_query("fee_structure", function() {
|
||||||
return{
|
return{
|
||||||
"filters":{
|
"filters":{
|
||||||
"academic_year": (frm.doc.academic_year)
|
"academic_year": (frm.doc.academic_year)
|
||||||
@ -45,6 +50,8 @@ frappe.ui.form.on("Fees", {
|
|||||||
if (!frm.doc.posting_date) {
|
if (!frm.doc.posting_date) {
|
||||||
frm.doc.posting_date = frappe.datetime.get_today();
|
frm.doc.posting_date = frappe.datetime.get_today();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
|
@ -124,21 +124,24 @@ class ProgramEnrollment(Document):
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_program_courses(doctype, txt, searchfield, start, page_len, filters):
|
def get_program_courses(doctype, txt, searchfield, start, page_len, filters):
|
||||||
if filters.get('program'):
|
if not filters.get('program'):
|
||||||
return frappe.db.sql("""select course, course_name from `tabProgram Course`
|
frappe.msgprint(_("Please select a Program first."))
|
||||||
where parent = %(program)s and course like %(txt)s {match_cond}
|
return []
|
||||||
order by
|
|
||||||
if(locate(%(_txt)s, course), locate(%(_txt)s, course), 99999),
|
return frappe.db.sql("""select course, course_name from `tabProgram Course`
|
||||||
idx desc,
|
where parent = %(program)s and course like %(txt)s {match_cond}
|
||||||
`tabProgram Course`.course asc
|
order by
|
||||||
limit {start}, {page_len}""".format(
|
if(locate(%(_txt)s, course), locate(%(_txt)s, course), 99999),
|
||||||
match_cond=get_match_cond(doctype),
|
idx desc,
|
||||||
start=start,
|
`tabProgram Course`.course asc
|
||||||
page_len=page_len), {
|
limit {start}, {page_len}""".format(
|
||||||
"txt": "%{0}%".format(txt),
|
match_cond=get_match_cond(doctype),
|
||||||
"_txt": txt.replace('%', ''),
|
start=start,
|
||||||
"program": filters['program']
|
page_len=page_len), {
|
||||||
})
|
"txt": "%{0}%".format(txt),
|
||||||
|
"_txt": txt.replace('%', ''),
|
||||||
|
"program": filters['program']
|
||||||
|
})
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
|
@ -29,14 +29,11 @@ class PlaidConnector():
|
|||||||
response = self.client.Item.public_token.exchange(public_token)
|
response = self.client.Item.public_token.exchange(public_token)
|
||||||
access_token = response["access_token"]
|
access_token = response["access_token"]
|
||||||
return access_token
|
return access_token
|
||||||
|
|
||||||
def get_link_token(self):
|
def get_token_request(self, update_mode=False):
|
||||||
country_codes = ["US", "CA", "FR", "IE", "NL", "ES", "GB"] if self.settings.enable_european_access else ["US", "CA"]
|
country_codes = ["US", "CA", "FR", "IE", "NL", "ES", "GB"] if self.settings.enable_european_access else ["US", "CA"]
|
||||||
token_request = {
|
args = {
|
||||||
"client_name": self.client_name,
|
"client_name": self.client_name,
|
||||||
"client_id": self.settings.plaid_client_id,
|
|
||||||
"secret": self.settings.plaid_secret,
|
|
||||||
"products": self.products,
|
|
||||||
# only allow Plaid-supported languages and countries (LAST: Sep-19-2020)
|
# only allow Plaid-supported languages and countries (LAST: Sep-19-2020)
|
||||||
"language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
|
"language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
|
||||||
"country_codes": country_codes,
|
"country_codes": country_codes,
|
||||||
@ -45,6 +42,20 @@ class PlaidConnector():
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if update_mode:
|
||||||
|
args["access_token"] = self.access_token
|
||||||
|
else:
|
||||||
|
args.update({
|
||||||
|
"client_id": self.settings.plaid_client_id,
|
||||||
|
"secret": self.settings.plaid_secret,
|
||||||
|
"products": self.products,
|
||||||
|
})
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
def get_link_token(self, update_mode=False):
|
||||||
|
token_request = self.get_token_request(update_mode)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = self.client.LinkToken.create(token_request)
|
response = self.client.LinkToken.create(token_request)
|
||||||
except InvalidRequestError:
|
except InvalidRequestError:
|
||||||
|
@ -12,9 +12,25 @@ frappe.ui.form.on('Plaid Settings', {
|
|||||||
|
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
if (frm.doc.enabled) {
|
if (frm.doc.enabled) {
|
||||||
frm.add_custom_button('Link a new bank account', () => {
|
frm.add_custom_button(__('Link a new bank account'), () => {
|
||||||
new erpnext.integrations.plaidLink(frm);
|
new erpnext.integrations.plaidLink(frm);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.add_custom_button(__("Sync Now"), () => {
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.enqueue_synchronization",
|
||||||
|
freeze: true,
|
||||||
|
callback: () => {
|
||||||
|
let bank_transaction_link = '<a href="#List/Bank Transaction">Bank Transaction</a>';
|
||||||
|
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __("Sync Started"),
|
||||||
|
message: __("The sync has started in the background, please check the {0} list for new records.", [bank_transaction_link]),
|
||||||
|
alert: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).addClass("btn-primary");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -30,10 +46,18 @@ erpnext.integrations.plaidLink = class plaidLink {
|
|||||||
this.product = ["auth", "transactions"];
|
this.product = ["auth", "transactions"];
|
||||||
this.plaid_env = this.frm.doc.plaid_env;
|
this.plaid_env = this.frm.doc.plaid_env;
|
||||||
this.client_name = frappe.boot.sitename;
|
this.client_name = frappe.boot.sitename;
|
||||||
this.token = await this.frm.call("get_link_token").then(resp => resp.message);
|
this.token = await this.get_link_token();
|
||||||
this.init_plaid();
|
this.init_plaid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async get_link_token() {
|
||||||
|
const token = await this.frm.call("get_link_token").then(resp => resp.message);
|
||||||
|
if (!token) {
|
||||||
|
frappe.throw(__('Cannot retrieve link token. Check Error Log for more information'));
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
init_plaid() {
|
init_plaid() {
|
||||||
const me = this;
|
const me = this;
|
||||||
me.loadScript(me.plaidUrl)
|
me.loadScript(me.plaidUrl)
|
||||||
@ -78,8 +102,8 @@ erpnext.integrations.plaidLink = class plaidLink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onScriptError(error) {
|
onScriptError(error) {
|
||||||
frappe.msgprint("There was an issue connecting to Plaid's authentication server");
|
frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information"));
|
||||||
frappe.msgprint(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
plaid_success(token, response) {
|
plaid_success(token, response) {
|
||||||
@ -107,4 +131,4 @@ erpnext.integrations.plaidLink = class plaidLink {
|
|||||||
});
|
});
|
||||||
}, __("Select a company"), __("Continue"));
|
}, __("Select a company"), __("Continue"));
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -166,7 +166,6 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
|
|||||||
related_bank = frappe.db.get_values("Bank Account", bank_account, ["bank", "integration_id"], as_dict=True)
|
related_bank = frappe.db.get_values("Bank Account", bank_account, ["bank", "integration_id"], as_dict=True)
|
||||||
access_token = frappe.db.get_value("Bank", related_bank[0].bank, "plaid_access_token")
|
access_token = frappe.db.get_value("Bank", related_bank[0].bank, "plaid_access_token")
|
||||||
account_id = related_bank[0].integration_id
|
account_id = related_bank[0].integration_id
|
||||||
|
|
||||||
else:
|
else:
|
||||||
access_token = frappe.db.get_value("Bank", bank, "plaid_access_token")
|
access_token = frappe.db.get_value("Bank", bank, "plaid_access_token")
|
||||||
account_id = None
|
account_id = None
|
||||||
@ -228,13 +227,23 @@ def new_bank_transaction(transaction):
|
|||||||
|
|
||||||
def automatic_synchronization():
|
def automatic_synchronization():
|
||||||
settings = frappe.get_doc("Plaid Settings", "Plaid Settings")
|
settings = frappe.get_doc("Plaid Settings", "Plaid Settings")
|
||||||
|
|
||||||
if settings.enabled == 1 and settings.automatic_sync == 1:
|
if settings.enabled == 1 and settings.automatic_sync == 1:
|
||||||
plaid_accounts = frappe.get_all("Bank Account", filters={"integration_id": ["!=", ""]}, fields=["name", "bank"])
|
enqueue_synchronization()
|
||||||
|
|
||||||
for plaid_account in plaid_accounts:
|
@frappe.whitelist()
|
||||||
frappe.enqueue(
|
def enqueue_synchronization():
|
||||||
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions",
|
plaid_accounts = frappe.get_all("Bank Account",
|
||||||
bank=plaid_account.bank,
|
filters={"integration_id": ["!=", ""]},
|
||||||
bank_account=plaid_account.name
|
fields=["name", "bank"])
|
||||||
)
|
|
||||||
|
for plaid_account in plaid_accounts:
|
||||||
|
frappe.enqueue(
|
||||||
|
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions",
|
||||||
|
bank=plaid_account.bank,
|
||||||
|
bank_account=plaid_account.name
|
||||||
|
)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_link_token_for_update(access_token):
|
||||||
|
plaid = PlaidConnector(access_token)
|
||||||
|
return plaid.get_link_token(update_mode=True)
|
||||||
|
@ -6,3 +6,5 @@ class PartyFrozen(frappe.ValidationError): pass
|
|||||||
class InvalidAccountCurrency(frappe.ValidationError): pass
|
class InvalidAccountCurrency(frappe.ValidationError): pass
|
||||||
class InvalidCurrency(frappe.ValidationError): pass
|
class InvalidCurrency(frappe.ValidationError): pass
|
||||||
class PartyDisabled(frappe.ValidationError):pass
|
class PartyDisabled(frappe.ValidationError):pass
|
||||||
|
class InvalidAccountDimensionError(frappe.ValidationError): pass
|
||||||
|
class MandatoryAccountDimensionError(frappe.ValidationError): pass
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
"enable_free_follow_ups",
|
"enable_free_follow_ups",
|
||||||
"max_visits",
|
"max_visits",
|
||||||
"valid_days",
|
"valid_days",
|
||||||
|
"inpatient_settings_section",
|
||||||
|
"allow_discharge_despite_unbilled_services",
|
||||||
|
"do_not_bill_inpatient_encounters",
|
||||||
"healthcare_service_items",
|
"healthcare_service_items",
|
||||||
"inpatient_visit_charge_item",
|
"inpatient_visit_charge_item",
|
||||||
"op_consulting_charge_item",
|
"op_consulting_charge_item",
|
||||||
@ -302,11 +305,28 @@
|
|||||||
"fieldname": "enable_free_follow_ups",
|
"fieldname": "enable_free_follow_ups",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Enable Free Follow-ups"
|
"label": "Enable Free Follow-ups"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "inpatient_settings_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Inpatient Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "allow_discharge_despite_unbilled_services",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Allow Discharge Despite Unbilled Healthcare Services"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "do_not_bill_inpatient_encounters",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Do Not Bill Patient Encounters for Inpatients"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-07-08 15:17:21.543218",
|
"modified": "2021-01-13 09:04:35.877700",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Healthcare",
|
"module": "Healthcare",
|
||||||
"name": "Healthcare Settings",
|
"name": "Healthcare Settings",
|
||||||
|
@ -5,6 +5,7 @@ frappe.ui.form.on('Inpatient Medication Entry', {
|
|||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
// Ignore cancellation of doctype on cancel all
|
// Ignore cancellation of doctype on cancel all
|
||||||
frm.ignore_doctypes_on_cancel_all = ['Stock Entry'];
|
frm.ignore_doctypes_on_cancel_all = ['Stock Entry'];
|
||||||
|
frm.fields_dict['medication_orders'].grid.wrapper.find('.grid-add-row').hide();
|
||||||
|
|
||||||
frm.set_query('item_code', () => {
|
frm.set_query('item_code', () => {
|
||||||
return {
|
return {
|
||||||
|
@ -139,7 +139,6 @@
|
|||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Inpatient Medication Orders",
|
"label": "Inpatient Medication Orders",
|
||||||
"options": "Inpatient Medication Entry Detail",
|
"options": "Inpatient Medication Entry Detail",
|
||||||
"read_only": 1,
|
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -180,7 +179,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-11-03 13:22:37.820707",
|
"modified": "2021-01-11 12:37:46.749659",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Healthcare",
|
"module": "Healthcare",
|
||||||
"name": "Inpatient Medication Entry",
|
"name": "Inpatient Medication Entry",
|
||||||
|
@ -15,8 +15,6 @@ class InpatientMedicationEntry(Document):
|
|||||||
self.validate_medication_orders()
|
self.validate_medication_orders()
|
||||||
|
|
||||||
def get_medication_orders(self):
|
def get_medication_orders(self):
|
||||||
self.validate_datetime_filters()
|
|
||||||
|
|
||||||
# pull inpatient medication orders based on selected filters
|
# pull inpatient medication orders based on selected filters
|
||||||
orders = get_pending_medication_orders(self)
|
orders = get_pending_medication_orders(self)
|
||||||
|
|
||||||
@ -27,22 +25,6 @@ class InpatientMedicationEntry(Document):
|
|||||||
self.set('medication_orders', [])
|
self.set('medication_orders', [])
|
||||||
frappe.msgprint(_('No pending medication orders found for selected criteria'))
|
frappe.msgprint(_('No pending medication orders found for selected criteria'))
|
||||||
|
|
||||||
def validate_datetime_filters(self):
|
|
||||||
if self.from_date and self.to_date:
|
|
||||||
self.validate_from_to_dates('from_date', 'to_date')
|
|
||||||
|
|
||||||
if self.from_date and getdate(self.from_date) > getdate():
|
|
||||||
frappe.throw(_('From Date cannot be after the current date.'))
|
|
||||||
|
|
||||||
if self.to_date and getdate(self.to_date) > getdate():
|
|
||||||
frappe.throw(_('To Date cannot be after the current date.'))
|
|
||||||
|
|
||||||
if self.from_time and self.from_time > nowtime():
|
|
||||||
frappe.throw(_('From Time cannot be after the current time.'))
|
|
||||||
|
|
||||||
if self.to_time and self.to_time > nowtime():
|
|
||||||
frappe.throw(_('To Time cannot be after the current time.'))
|
|
||||||
|
|
||||||
def add_mo_to_table(self, orders):
|
def add_mo_to_table(self, orders):
|
||||||
# Add medication orders in the child table
|
# Add medication orders in the child table
|
||||||
self.set('medication_orders', [])
|
self.set('medication_orders', [])
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, json
|
import frappe, json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import today, now_datetime, getdate, get_datetime
|
from frappe.utils import today, now_datetime, getdate, get_datetime, get_link_to_form
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.desk.reportview import get_match_cond
|
from frappe.desk.reportview import get_match_cond
|
||||||
|
|
||||||
@ -113,6 +113,7 @@ def schedule_inpatient(args):
|
|||||||
inpatient_record.status = 'Admission Scheduled'
|
inpatient_record.status = 'Admission Scheduled'
|
||||||
inpatient_record.save(ignore_permissions = True)
|
inpatient_record.save(ignore_permissions = True)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def schedule_discharge(args):
|
def schedule_discharge(args):
|
||||||
discharge_order = json.loads(args)
|
discharge_order = json.loads(args)
|
||||||
@ -126,16 +127,19 @@ def schedule_discharge(args):
|
|||||||
frappe.db.set_value('Patient', discharge_order['patient'], 'inpatient_status', inpatient_record.status)
|
frappe.db.set_value('Patient', discharge_order['patient'], 'inpatient_status', inpatient_record.status)
|
||||||
frappe.db.set_value('Patient Encounter', inpatient_record.discharge_encounter, 'inpatient_status', inpatient_record.status)
|
frappe.db.set_value('Patient Encounter', inpatient_record.discharge_encounter, 'inpatient_status', inpatient_record.status)
|
||||||
|
|
||||||
|
|
||||||
def set_details_from_ip_order(inpatient_record, ip_order):
|
def set_details_from_ip_order(inpatient_record, ip_order):
|
||||||
for key in ip_order:
|
for key in ip_order:
|
||||||
inpatient_record.set(key, ip_order[key])
|
inpatient_record.set(key, ip_order[key])
|
||||||
|
|
||||||
|
|
||||||
def set_ip_child_records(inpatient_record, inpatient_record_child, encounter_child):
|
def set_ip_child_records(inpatient_record, inpatient_record_child, encounter_child):
|
||||||
for item in encounter_child:
|
for item in encounter_child:
|
||||||
table = inpatient_record.append(inpatient_record_child)
|
table = inpatient_record.append(inpatient_record_child)
|
||||||
for df in table.meta.get('fields'):
|
for df in table.meta.get('fields'):
|
||||||
table.set(df.fieldname, item.get(df.fieldname))
|
table.set(df.fieldname, item.get(df.fieldname))
|
||||||
|
|
||||||
|
|
||||||
def check_out_inpatient(inpatient_record):
|
def check_out_inpatient(inpatient_record):
|
||||||
if inpatient_record.inpatient_occupancies:
|
if inpatient_record.inpatient_occupancies:
|
||||||
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
||||||
@ -144,54 +148,88 @@ def check_out_inpatient(inpatient_record):
|
|||||||
inpatient_occupancy.check_out = now_datetime()
|
inpatient_occupancy.check_out = now_datetime()
|
||||||
frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
|
frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
|
||||||
|
|
||||||
|
|
||||||
def discharge_patient(inpatient_record):
|
def discharge_patient(inpatient_record):
|
||||||
validate_invoiced_inpatient(inpatient_record)
|
validate_inpatient_invoicing(inpatient_record)
|
||||||
inpatient_record.discharge_date = today()
|
inpatient_record.discharge_date = today()
|
||||||
inpatient_record.status = "Discharged"
|
inpatient_record.status = "Discharged"
|
||||||
|
|
||||||
inpatient_record.save(ignore_permissions = True)
|
inpatient_record.save(ignore_permissions = True)
|
||||||
|
|
||||||
def validate_invoiced_inpatient(inpatient_record):
|
|
||||||
pending_invoices = []
|
def validate_inpatient_invoicing(inpatient_record):
|
||||||
|
if frappe.db.get_single_value("Healthcare Settings", "allow_discharge_despite_unbilled_services"):
|
||||||
|
return
|
||||||
|
|
||||||
|
pending_invoices = get_pending_invoices(inpatient_record)
|
||||||
|
|
||||||
|
if pending_invoices:
|
||||||
|
message = _("Cannot mark Inpatient Record as Discharged since there are unbilled services. ")
|
||||||
|
|
||||||
|
formatted_doc_rows = ''
|
||||||
|
|
||||||
|
for doctype, docnames in pending_invoices.items():
|
||||||
|
formatted_doc_rows += """
|
||||||
|
<td>{0}</td>
|
||||||
|
<td>{1}</td>
|
||||||
|
</tr>""".format(doctype, docnames)
|
||||||
|
|
||||||
|
message += """
|
||||||
|
<table class='table'>
|
||||||
|
<thead>
|
||||||
|
<th>{0}</th>
|
||||||
|
<th>{1}</th>
|
||||||
|
</thead>
|
||||||
|
{2}
|
||||||
|
</table>
|
||||||
|
""".format(_("Healthcare Service"), _("Documents"), formatted_doc_rows)
|
||||||
|
|
||||||
|
frappe.throw(message, title=_("Unbilled Services"), is_minimizable=True, wide=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_pending_invoices(inpatient_record):
|
||||||
|
pending_invoices = {}
|
||||||
if inpatient_record.inpatient_occupancies:
|
if inpatient_record.inpatient_occupancies:
|
||||||
service_unit_names = False
|
service_unit_names = False
|
||||||
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
||||||
if inpatient_occupancy.invoiced != 1:
|
if not inpatient_occupancy.invoiced:
|
||||||
if service_unit_names:
|
if service_unit_names:
|
||||||
service_unit_names += ", " + inpatient_occupancy.service_unit
|
service_unit_names += ", " + inpatient_occupancy.service_unit
|
||||||
else:
|
else:
|
||||||
service_unit_names = inpatient_occupancy.service_unit
|
service_unit_names = inpatient_occupancy.service_unit
|
||||||
if service_unit_names:
|
if service_unit_names:
|
||||||
pending_invoices.append("Inpatient Occupancy (" + service_unit_names + ")")
|
pending_invoices["Inpatient Occupancy"] = service_unit_names
|
||||||
|
|
||||||
docs = ["Patient Appointment", "Patient Encounter", "Lab Test", "Clinical Procedure"]
|
docs = ["Patient Appointment", "Patient Encounter", "Lab Test", "Clinical Procedure"]
|
||||||
|
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
doc_name_list = get_inpatient_docs_not_invoiced(doc, inpatient_record)
|
doc_name_list = get_unbilled_inpatient_docs(doc, inpatient_record)
|
||||||
if doc_name_list:
|
if doc_name_list:
|
||||||
pending_invoices = get_pending_doc(doc, doc_name_list, pending_invoices)
|
pending_invoices = get_pending_doc(doc, doc_name_list, pending_invoices)
|
||||||
|
|
||||||
if pending_invoices:
|
return pending_invoices
|
||||||
frappe.throw(_("Can not mark Inpatient Record Discharged, there are Unbilled Invoices {0}").format(", "
|
|
||||||
.join(pending_invoices)), title=_('Unbilled Invoices'))
|
|
||||||
|
|
||||||
def get_pending_doc(doc, doc_name_list, pending_invoices):
|
def get_pending_doc(doc, doc_name_list, pending_invoices):
|
||||||
if doc_name_list:
|
if doc_name_list:
|
||||||
doc_ids = False
|
doc_ids = False
|
||||||
for doc_name in doc_name_list:
|
for doc_name in doc_name_list:
|
||||||
|
doc_link = get_link_to_form(doc, doc_name.name)
|
||||||
if doc_ids:
|
if doc_ids:
|
||||||
doc_ids += ", "+doc_name.name
|
doc_ids += ", " + doc_link
|
||||||
else:
|
else:
|
||||||
doc_ids = doc_name.name
|
doc_ids = doc_link
|
||||||
if doc_ids:
|
if doc_ids:
|
||||||
pending_invoices.append(doc + " (" + doc_ids + ")")
|
pending_invoices[doc] = doc_ids
|
||||||
|
|
||||||
return pending_invoices
|
return pending_invoices
|
||||||
|
|
||||||
def get_inpatient_docs_not_invoiced(doc, inpatient_record):
|
|
||||||
|
def get_unbilled_inpatient_docs(doc, inpatient_record):
|
||||||
return frappe.db.get_list(doc, filters = {'patient': inpatient_record.patient,
|
return frappe.db.get_list(doc, filters = {'patient': inpatient_record.patient,
|
||||||
'inpatient_record': inpatient_record.name, 'docstatus': 1, 'invoiced': 0})
|
'inpatient_record': inpatient_record.name, 'docstatus': 1, 'invoiced': 0})
|
||||||
|
|
||||||
|
|
||||||
def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None):
|
def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None):
|
||||||
inpatient_record.admitted_datetime = check_in
|
inpatient_record.admitted_datetime = check_in
|
||||||
inpatient_record.status = 'Admitted'
|
inpatient_record.status = 'Admitted'
|
||||||
@ -203,6 +241,7 @@ def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=N
|
|||||||
frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_status', 'Admitted')
|
frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_status', 'Admitted')
|
||||||
frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_record', inpatient_record.name)
|
frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_record', inpatient_record.name)
|
||||||
|
|
||||||
|
|
||||||
def transfer_patient(inpatient_record, service_unit, check_in):
|
def transfer_patient(inpatient_record, service_unit, check_in):
|
||||||
item_line = inpatient_record.append('inpatient_occupancies', {})
|
item_line = inpatient_record.append('inpatient_occupancies', {})
|
||||||
item_line.service_unit = service_unit
|
item_line.service_unit = service_unit
|
||||||
@ -212,6 +251,7 @@ def transfer_patient(inpatient_record, service_unit, check_in):
|
|||||||
|
|
||||||
frappe.db.set_value("Healthcare Service Unit", service_unit, "occupancy_status", "Occupied")
|
frappe.db.set_value("Healthcare Service Unit", service_unit, "occupancy_status", "Occupied")
|
||||||
|
|
||||||
|
|
||||||
def patient_leave_service_unit(inpatient_record, check_out, leave_from):
|
def patient_leave_service_unit(inpatient_record, check_out, leave_from):
|
||||||
if inpatient_record.inpatient_occupancies:
|
if inpatient_record.inpatient_occupancies:
|
||||||
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
||||||
@ -221,6 +261,7 @@ def patient_leave_service_unit(inpatient_record, check_out, leave_from):
|
|||||||
frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
|
frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
|
||||||
inpatient_record.save(ignore_permissions = True)
|
inpatient_record.save(ignore_permissions = True)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_leave_from(doctype, txt, searchfield, start, page_len, filters):
|
def get_leave_from(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
@ -8,6 +8,8 @@ import unittest
|
|||||||
from frappe.utils import now_datetime, today
|
from frappe.utils import now_datetime, today
|
||||||
from frappe.utils.make_random import get_random
|
from frappe.utils.make_random import get_random
|
||||||
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
|
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
|
||||||
|
from erpnext.healthcare.doctype.lab_test.test_lab_test import create_patient_encounter
|
||||||
|
from erpnext.healthcare.utils import get_encounters_to_invoice
|
||||||
|
|
||||||
class TestInpatientRecord(unittest.TestCase):
|
class TestInpatientRecord(unittest.TestCase):
|
||||||
def test_admit_and_discharge(self):
|
def test_admit_and_discharge(self):
|
||||||
@ -40,6 +42,60 @@ class TestInpatientRecord(unittest.TestCase):
|
|||||||
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record"))
|
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record"))
|
||||||
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status"))
|
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status"))
|
||||||
|
|
||||||
|
def test_allow_discharge_despite_unbilled_services(self):
|
||||||
|
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||||
|
setup_inpatient_settings(key="allow_discharge_despite_unbilled_services", value=1)
|
||||||
|
patient = create_patient()
|
||||||
|
# Schedule Admission
|
||||||
|
ip_record = create_inpatient(patient)
|
||||||
|
ip_record.expected_length_of_stay = 0
|
||||||
|
ip_record.save(ignore_permissions = True)
|
||||||
|
|
||||||
|
# Admit
|
||||||
|
service_unit = get_healthcare_service_unit()
|
||||||
|
admit_patient(ip_record, service_unit, now_datetime())
|
||||||
|
|
||||||
|
# Discharge
|
||||||
|
schedule_discharge(frappe.as_json({"patient": patient}))
|
||||||
|
self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
|
||||||
|
|
||||||
|
ip_record = frappe.get_doc("Inpatient Record", ip_record.name)
|
||||||
|
# Should not validate Pending Invoices
|
||||||
|
ip_record.discharge()
|
||||||
|
|
||||||
|
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record"))
|
||||||
|
self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status"))
|
||||||
|
|
||||||
|
setup_inpatient_settings(key="allow_discharge_despite_unbilled_services", value=0)
|
||||||
|
|
||||||
|
def test_do_not_bill_patient_encounters_for_inpatients(self):
|
||||||
|
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||||
|
setup_inpatient_settings(key="do_not_bill_inpatient_encounters", value=1)
|
||||||
|
patient = create_patient()
|
||||||
|
# Schedule Admission
|
||||||
|
ip_record = create_inpatient(patient)
|
||||||
|
ip_record.expected_length_of_stay = 0
|
||||||
|
ip_record.save(ignore_permissions = True)
|
||||||
|
|
||||||
|
# Admit
|
||||||
|
service_unit = get_healthcare_service_unit()
|
||||||
|
admit_patient(ip_record, service_unit, now_datetime())
|
||||||
|
|
||||||
|
# Patient Encounter
|
||||||
|
patient_encounter = create_patient_encounter()
|
||||||
|
encounters = get_encounters_to_invoice(patient, "_Test Company")
|
||||||
|
encounter_ids = [entry.reference_name for entry in encounters]
|
||||||
|
self.assertFalse(patient_encounter.name in encounter_ids)
|
||||||
|
|
||||||
|
# Discharge
|
||||||
|
schedule_discharge(frappe.as_json({"patient": patient}))
|
||||||
|
self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
|
||||||
|
|
||||||
|
ip_record = frappe.get_doc("Inpatient Record", ip_record.name)
|
||||||
|
mark_invoiced_inpatient_occupancy(ip_record)
|
||||||
|
discharge_patient(ip_record)
|
||||||
|
setup_inpatient_settings(key="do_not_bill_inpatient_encounters", value=0)
|
||||||
|
|
||||||
def test_validate_overlap_admission(self):
|
def test_validate_overlap_admission(self):
|
||||||
frappe.db.sql("""delete from `tabInpatient Record`""")
|
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||||
patient = create_patient()
|
patient = create_patient()
|
||||||
@ -63,6 +119,13 @@ def mark_invoiced_inpatient_occupancy(ip_record):
|
|||||||
inpatient_occupancy.invoiced = 1
|
inpatient_occupancy.invoiced = 1
|
||||||
ip_record.save(ignore_permissions = True)
|
ip_record.save(ignore_permissions = True)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_inpatient_settings(key, value):
|
||||||
|
settings = frappe.get_single("Healthcare Settings")
|
||||||
|
settings.set(key, value)
|
||||||
|
settings.save()
|
||||||
|
|
||||||
|
|
||||||
def create_inpatient(patient):
|
def create_inpatient(patient):
|
||||||
patient_obj = frappe.get_doc('Patient', patient)
|
patient_obj = frappe.get_doc('Patient', patient)
|
||||||
inpatient_record = frappe.new_doc('Inpatient Record')
|
inpatient_record = frappe.new_doc('Inpatient Record')
|
||||||
@ -78,6 +141,7 @@ def create_inpatient(patient):
|
|||||||
inpatient_record.scheduled_date = today()
|
inpatient_record.scheduled_date = today()
|
||||||
return inpatient_record
|
return inpatient_record
|
||||||
|
|
||||||
|
|
||||||
def get_healthcare_service_unit():
|
def get_healthcare_service_unit():
|
||||||
service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
|
service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
|
||||||
if not service_unit:
|
if not service_unit:
|
||||||
@ -105,6 +169,7 @@ def get_healthcare_service_unit():
|
|||||||
return service_unit.name
|
return service_unit.name
|
||||||
return service_unit
|
return service_unit
|
||||||
|
|
||||||
|
|
||||||
def get_service_unit_type():
|
def get_service_unit_type():
|
||||||
service_unit_type = get_random("Healthcare Service Unit Type", filters={"inpatient_occupancy": 1})
|
service_unit_type = get_random("Healthcare Service Unit Type", filters={"inpatient_occupancy": 1})
|
||||||
|
|
||||||
@ -116,6 +181,7 @@ def get_service_unit_type():
|
|||||||
return service_unit_type.name
|
return service_unit_type.name
|
||||||
return service_unit_type
|
return service_unit_type
|
||||||
|
|
||||||
|
|
||||||
def create_patient():
|
def create_patient():
|
||||||
patient = frappe.db.exists('Patient', '_Test IPD Patient')
|
patient = frappe.db.exists('Patient', '_Test IPD Patient')
|
||||||
if not patient:
|
if not patient:
|
||||||
|
@ -23,8 +23,10 @@ class TestPatientAppointment(unittest.TestCase):
|
|||||||
self.assertEquals(appointment.status, 'Open')
|
self.assertEquals(appointment.status, 'Open')
|
||||||
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2))
|
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2))
|
||||||
self.assertEquals(appointment.status, 'Scheduled')
|
self.assertEquals(appointment.status, 'Scheduled')
|
||||||
create_encounter(appointment)
|
encounter = create_encounter(appointment)
|
||||||
self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
|
self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
|
||||||
|
encounter.cancel()
|
||||||
|
self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
|
||||||
|
|
||||||
def test_start_encounter(self):
|
def test_start_encounter(self):
|
||||||
patient, medical_department, practitioner = create_healthcare_docs()
|
patient, medical_department, practitioner = create_healthcare_docs()
|
||||||
|
@ -5,10 +5,10 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
from frappe.utils import getdate, flt
|
from frappe.utils import getdate, flt, nowdate
|
||||||
from erpnext.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type
|
from erpnext.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type
|
||||||
from erpnext.healthcare.doctype.therapy_plan.therapy_plan import make_therapy_session, make_sales_invoice
|
from erpnext.healthcare.doctype.therapy_plan.therapy_plan import make_therapy_session, make_sales_invoice
|
||||||
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient
|
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient, create_appointment
|
||||||
|
|
||||||
class TestTherapyPlan(unittest.TestCase):
|
class TestTherapyPlan(unittest.TestCase):
|
||||||
def test_creation_on_encounter_submission(self):
|
def test_creation_on_encounter_submission(self):
|
||||||
@ -28,6 +28,15 @@ class TestTherapyPlan(unittest.TestCase):
|
|||||||
frappe.get_doc(session).submit()
|
frappe.get_doc(session).submit()
|
||||||
self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
|
self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
|
||||||
|
|
||||||
|
patient, medical_department, practitioner = create_healthcare_docs()
|
||||||
|
appointment = create_appointment(patient, practitioner, nowdate())
|
||||||
|
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name)
|
||||||
|
session = frappe.get_doc(session)
|
||||||
|
session.submit()
|
||||||
|
self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
|
||||||
|
session.cancel()
|
||||||
|
self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
|
||||||
|
|
||||||
def test_therapy_plan_from_template(self):
|
def test_therapy_plan_from_template(self):
|
||||||
patient = create_patient()
|
patient = create_patient()
|
||||||
template = create_therapy_plan_template()
|
template = create_therapy_plan_template()
|
||||||
|
@ -47,7 +47,7 @@ class TherapyPlan(Document):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_therapy_session(therapy_plan, patient, therapy_type, company):
|
def make_therapy_session(therapy_plan, patient, therapy_type, company, appointment=None):
|
||||||
therapy_type = frappe.get_doc('Therapy Type', therapy_type)
|
therapy_type = frappe.get_doc('Therapy Type', therapy_type)
|
||||||
|
|
||||||
therapy_session = frappe.new_doc('Therapy Session')
|
therapy_session = frappe.new_doc('Therapy Session')
|
||||||
@ -58,6 +58,7 @@ def make_therapy_session(therapy_plan, patient, therapy_type, company):
|
|||||||
therapy_session.duration = therapy_type.default_duration
|
therapy_session.duration = therapy_type.default_duration
|
||||||
therapy_session.rate = therapy_type.rate
|
therapy_session.rate = therapy_type.rate
|
||||||
therapy_session.exercises = therapy_type.exercises
|
therapy_session.exercises = therapy_type.exercises
|
||||||
|
therapy_session.appointment = appointment
|
||||||
|
|
||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test:
|
||||||
therapy_session.start_date = today()
|
therapy_session.start_date = today()
|
||||||
|
@ -19,6 +19,15 @@ frappe.ui.form.on('Therapy Session', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query('appointment', function() {
|
||||||
|
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'status': ['in', ['Open', 'Scheduled']]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
|
@ -43,7 +43,14 @@ class TherapySession(Document):
|
|||||||
self.update_sessions_count_in_therapy_plan()
|
self.update_sessions_count_in_therapy_plan()
|
||||||
insert_session_medical_record(self)
|
insert_session_medical_record(self)
|
||||||
|
|
||||||
|
def on_update(self):
|
||||||
|
if self.appointment:
|
||||||
|
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed')
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
|
if self.appointment:
|
||||||
|
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open')
|
||||||
|
|
||||||
self.update_sessions_count_in_therapy_plan(on_cancel=True)
|
self.update_sessions_count_in_therapy_plan(on_cancel=True)
|
||||||
|
|
||||||
def update_sessions_count_in_therapy_plan(self, on_cancel=False):
|
def update_sessions_count_in_therapy_plan(self, on_cancel=False):
|
||||||
|
@ -77,11 +77,13 @@ def get_appointments_to_invoice(patient, company):
|
|||||||
|
|
||||||
|
|
||||||
def get_encounters_to_invoice(patient, company):
|
def get_encounters_to_invoice(patient, company):
|
||||||
|
if not isinstance(patient, str):
|
||||||
|
patient = patient.name
|
||||||
encounters_to_invoice = []
|
encounters_to_invoice = []
|
||||||
encounters = frappe.get_list(
|
encounters = frappe.get_list(
|
||||||
'Patient Encounter',
|
'Patient Encounter',
|
||||||
fields=['*'],
|
fields=['*'],
|
||||||
filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1}
|
filters={'patient': patient, 'company': company, 'invoiced': False, 'docstatus': 1}
|
||||||
)
|
)
|
||||||
if encounters:
|
if encounters:
|
||||||
for encounter in encounters:
|
for encounter in encounters:
|
||||||
@ -90,6 +92,10 @@ def get_encounters_to_invoice(patient, company):
|
|||||||
income_account = None
|
income_account = None
|
||||||
service_item = None
|
service_item = None
|
||||||
if encounter.practitioner:
|
if encounter.practitioner:
|
||||||
|
if encounter.inpatient_record and \
|
||||||
|
frappe.db.get_single_value('Healthcare Settings', 'do_not_bill_inpatient_encounters'):
|
||||||
|
continue
|
||||||
|
|
||||||
service_item, practitioner_charge = get_service_item_and_practitioner_charge(encounter)
|
service_item, practitioner_charge = get_service_item_and_practitioner_charge(encounter)
|
||||||
income_account = get_income_account(encounter.practitioner, encounter.company)
|
income_account = get_income_account(encounter.practitioner, encounter.company)
|
||||||
|
|
||||||
|
@ -813,7 +813,7 @@
|
|||||||
"idx": 24,
|
"idx": 24,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-16 15:02:04.283657",
|
"modified": "2021-01-01 16:54:33.477439",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Employee",
|
"name": "Employee",
|
||||||
@ -855,7 +855,6 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
|
||||||
"search_fields": "employee_name",
|
"search_fields": "employee_name",
|
||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
|
@ -38,7 +38,8 @@ class TestEmployeeOnboarding(unittest.TestCase):
|
|||||||
onboarding.insert()
|
onboarding.insert()
|
||||||
onboarding.submit()
|
onboarding.submit()
|
||||||
|
|
||||||
self.assertEqual(onboarding.project, 'Employee Onboarding : Test Researcher - test@researcher.com')
|
project_name = frappe.db.get_value("Project", onboarding.project, "project_name")
|
||||||
|
self.assertEqual(project_name, 'Employee Onboarding : Test Researcher - test@researcher.com')
|
||||||
|
|
||||||
# don't allow making employee if onboarding is not complete
|
# don't allow making employee if onboarding is not complete
|
||||||
self.assertRaises(IncompleteTaskError, make_employee, onboarding.name)
|
self.assertRaises(IncompleteTaskError, make_employee, onboarding.name)
|
||||||
|
@ -2,11 +2,21 @@
|
|||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
frappe.provide("erpnext.hr");
|
frappe.provide("erpnext.hr");
|
||||||
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
erpnext.hr.ExpenseClaimController = frappe.ui.form.Controller.extend({
|
frappe.ui.form.on('Expense Claim', {
|
||||||
expense_type: function(doc, cdt, cdn) {
|
onload: function(frm) {
|
||||||
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
|
},
|
||||||
|
company: function(frm) {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on('Expense Claim Detail', {
|
||||||
|
expense_type: function(frm, cdt, cdn) {
|
||||||
var d = locals[cdt][cdn];
|
var d = locals[cdt][cdn];
|
||||||
if(!doc.company) {
|
if (!frm.doc.company) {
|
||||||
d.expense_type = "";
|
d.expense_type = "";
|
||||||
frappe.msgprint(__("Please set the Company"));
|
frappe.msgprint(__("Please set the Company"));
|
||||||
this.frm.refresh_fields();
|
this.frm.refresh_fields();
|
||||||
@ -20,7 +30,7 @@ erpnext.hr.ExpenseClaimController = frappe.ui.form.Controller.extend({
|
|||||||
method: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_claim_account_and_cost_center",
|
method: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_claim_account_and_cost_center",
|
||||||
args: {
|
args: {
|
||||||
"expense_claim_type": d.expense_type,
|
"expense_claim_type": d.expense_type,
|
||||||
"company": doc.company
|
"company": frm.doc.company
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if (r.message) {
|
if (r.message) {
|
||||||
@ -32,8 +42,6 @@ erpnext.hr.ExpenseClaimController = frappe.ui.form.Controller.extend({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$.extend(cur_frm.cscript, new erpnext.hr.ExpenseClaimController({frm: cur_frm}));
|
|
||||||
|
|
||||||
cur_frm.add_fetch('employee', 'company', 'company');
|
cur_frm.add_fetch('employee', 'company', 'company');
|
||||||
cur_frm.add_fetch('employee','employee_name','employee_name');
|
cur_frm.add_fetch('employee','employee_name','employee_name');
|
||||||
cur_frm.add_fetch('expense_type','description','description');
|
cur_frm.add_fetch('expense_type','description','description');
|
||||||
@ -167,15 +175,6 @@ frappe.ui.form.on("Expense Claim", {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("cost_center", "expenses", function() {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
"company": frm.doc.company,
|
|
||||||
"is_group": 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
frm.set_query("payable_account", function() {
|
frm.set_query("payable_account", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
|
@ -20,35 +20,36 @@ class TestExpenseClaim(unittest.TestCase):
|
|||||||
frappe.db.sql("""delete from `tabProject` where name = "_Test Project 1" """)
|
frappe.db.sql("""delete from `tabProject` where name = "_Test Project 1" """)
|
||||||
frappe.db.sql("update `tabExpense Claim` set project = '', task = ''")
|
frappe.db.sql("update `tabExpense Claim` set project = '', task = ''")
|
||||||
|
|
||||||
frappe.get_doc({
|
project = frappe.get_doc({
|
||||||
"project_name": "_Test Project 1",
|
"project_name": "_Test Project 1",
|
||||||
"doctype": "Project"
|
"doctype": "Project"
|
||||||
}).save()
|
})
|
||||||
|
project.save()
|
||||||
|
|
||||||
task = frappe.get_doc(dict(
|
task = frappe.get_doc(dict(
|
||||||
doctype = 'Task',
|
doctype = 'Task',
|
||||||
subject = '_Test Project Task 1',
|
subject = '_Test Project Task 1',
|
||||||
status = 'Open',
|
status = 'Open',
|
||||||
project = '_Test Project 1'
|
project = project.name
|
||||||
)).insert()
|
)).insert()
|
||||||
|
|
||||||
task_name = task.name
|
task_name = task.name
|
||||||
payable_account = get_payable_account(company_name)
|
payable_account = get_payable_account(company_name)
|
||||||
|
|
||||||
make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", "_Test Project 1", task_name)
|
make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", project.name, task_name)
|
||||||
|
|
||||||
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200)
|
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200)
|
||||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200)
|
self.assertEqual(frappe.db.get_value("Project", project.name, "total_expense_claim"), 200)
|
||||||
|
|
||||||
expense_claim2 = make_expense_claim(payable_account, 600, 500, company_name, "Travel Expenses - _TC4","_Test Project 1", task_name)
|
expense_claim2 = make_expense_claim(payable_account, 600, 500, company_name, "Travel Expenses - _TC4", project.name, task_name)
|
||||||
|
|
||||||
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 700)
|
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 700)
|
||||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 700)
|
self.assertEqual(frappe.db.get_value("Project", project.name, "total_expense_claim"), 700)
|
||||||
|
|
||||||
expense_claim2.cancel()
|
expense_claim2.cancel()
|
||||||
|
|
||||||
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200)
|
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200)
|
||||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200)
|
self.assertEqual(frappe.db.get_value("Project", project.name, "total_expense_claim"), 200)
|
||||||
|
|
||||||
def test_expense_claim_status(self):
|
def test_expense_claim_status(self):
|
||||||
payable_account = get_payable_account(company_name)
|
payable_account = get_payable_account(company_name)
|
||||||
|
@ -11,15 +11,24 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"applicant_name",
|
"applicant_name",
|
||||||
"email_id",
|
"email_id",
|
||||||
|
"phone_number",
|
||||||
|
"country",
|
||||||
"status",
|
"status",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"job_title",
|
"job_title",
|
||||||
"source",
|
"source",
|
||||||
"source_name",
|
"source_name",
|
||||||
|
"applicant_rating",
|
||||||
"section_break_6",
|
"section_break_6",
|
||||||
"notes",
|
"notes",
|
||||||
"cover_letter",
|
"cover_letter",
|
||||||
"resume_attachment"
|
"resume_attachment",
|
||||||
|
"resume_link",
|
||||||
|
"section_break_16",
|
||||||
|
"currency",
|
||||||
|
"column_break_18",
|
||||||
|
"lower_range",
|
||||||
|
"upper_range"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -91,12 +100,65 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Notes",
|
"label": "Notes",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "phone_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Phone Number",
|
||||||
|
"options": "Phone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "country",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Country",
|
||||||
|
"options": "Country"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "resume_link",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Resume Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "applicant_rating",
|
||||||
|
"fieldtype": "Rating",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Applicant Rating"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_16",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Salary Expectation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "lower_range",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Lower Range",
|
||||||
|
"options": "currency",
|
||||||
|
"precision": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "upper_range",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Upper Range",
|
||||||
|
"options": "currency",
|
||||||
|
"precision": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_18",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Currency",
|
||||||
|
"options": "Currency"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-user",
|
"icon": "fa fa-user",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-01-13 16:19:39.113330",
|
"modified": "2020-09-18 12:39:02.557563",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Job Applicant",
|
"name": "Job Applicant",
|
||||||
|
@ -1,456 +1,188 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
"autoname": "field:route",
|
||||||
"allow_import": 0,
|
"creation": "2013-01-15 16:13:36",
|
||||||
"allow_rename": 0,
|
"description": "Description of a Job Opening",
|
||||||
"autoname": "field:route",
|
"doctype": "DocType",
|
||||||
"beta": 0,
|
"document_type": "Document",
|
||||||
"creation": "2013-01-15 16:13:36",
|
"engine": "InnoDB",
|
||||||
"custom": 0,
|
"field_order": [
|
||||||
"description": "Description of a Job Opening",
|
"job_title",
|
||||||
"docstatus": 0,
|
"company",
|
||||||
"doctype": "DocType",
|
"status",
|
||||||
"document_type": "Document",
|
"column_break_5",
|
||||||
"editable_grid": 0,
|
"designation",
|
||||||
"engine": "InnoDB",
|
"department",
|
||||||
|
"staffing_plan",
|
||||||
|
"planned_vacancies",
|
||||||
|
"section_break_6",
|
||||||
|
"publish",
|
||||||
|
"route",
|
||||||
|
"column_break_12",
|
||||||
|
"job_application_route",
|
||||||
|
"section_break_14",
|
||||||
|
"description",
|
||||||
|
"section_break_16",
|
||||||
|
"currency",
|
||||||
|
"lower_range",
|
||||||
|
"upper_range",
|
||||||
|
"column_break_20",
|
||||||
|
"publish_salary_range"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "job_title",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Data",
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"label": "Job Title",
|
||||||
"columns": 0,
|
"reqd": 1
|
||||||
"fieldname": "job_title",
|
},
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Job Title",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "company",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Link",
|
||||||
"bold": 0,
|
"label": "Company",
|
||||||
"collapsible": 0,
|
"options": "Company",
|
||||||
"columns": 0,
|
"reqd": 1
|
||||||
"fieldname": "company",
|
},
|
||||||
"fieldtype": "Link",
|
|
||||||
"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": "Company",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Company",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "status",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Select",
|
||||||
"bold": 0,
|
"in_list_view": 1,
|
||||||
"collapsible": 0,
|
"in_standard_filter": 1,
|
||||||
"columns": 0,
|
"label": "Status",
|
||||||
"fieldname": "status",
|
"options": "Open\nClosed"
|
||||||
"fieldtype": "Select",
|
},
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
|
||||||
"label": "Status",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Open\nClosed",
|
|
||||||
"permlevel": 0,
|
|
||||||
"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": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "column_break_5",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Column Break"
|
||||||
"bold": 0,
|
},
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break_5",
|
|
||||||
"fieldtype": "Column Break",
|
|
||||||
"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,
|
|
||||||
"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": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "designation",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Link",
|
||||||
"bold": 0,
|
"label": "Designation",
|
||||||
"collapsible": 0,
|
"options": "Designation",
|
||||||
"columns": 0,
|
"reqd": 1
|
||||||
"fieldname": "designation",
|
},
|
||||||
"fieldtype": "Link",
|
|
||||||
"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": "Designation",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Designation",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "department",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Link",
|
||||||
"bold": 0,
|
"label": "Department",
|
||||||
"collapsible": 0,
|
"options": "Department"
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "department",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"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": "Department",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Department",
|
|
||||||
"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": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "staffing_plan",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Link",
|
||||||
"bold": 0,
|
"label": "Staffing Plan",
|
||||||
"collapsible": 0,
|
"options": "Staffing Plan",
|
||||||
"columns": 0,
|
"read_only": 1
|
||||||
"fieldname": "staffing_plan",
|
},
|
||||||
"fieldtype": "Link",
|
|
||||||
"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": "Staffing Plan",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Staffing Plan",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"depends_on": "staffing_plan",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "planned_vacancies",
|
||||||
"bold": 0,
|
"fieldtype": "Int",
|
||||||
"collapsible": 0,
|
"label": "Planned number of Positions",
|
||||||
"columns": 0,
|
"read_only": 1
|
||||||
"depends_on": "staffing_plan",
|
},
|
||||||
"fieldname": "planned_vacancies",
|
|
||||||
"fieldtype": "Int",
|
|
||||||
"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": "Planned number of Positions",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"fieldname": "section_break_6",
|
||||||
"allow_on_submit": 0,
|
"fieldtype": "Section Break"
|
||||||
"bold": 0,
|
},
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "section_break_6",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"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,
|
|
||||||
"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": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"default": "0",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "publish",
|
||||||
"bold": 0,
|
"fieldtype": "Check",
|
||||||
"collapsible": 0,
|
"label": "Publish on website"
|
||||||
"columns": 0,
|
},
|
||||||
"fieldname": "publish",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"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": "Publish on website",
|
|
||||||
"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": 0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"depends_on": "publish",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "route",
|
||||||
"bold": 0,
|
"fieldtype": "Data",
|
||||||
"collapsible": 0,
|
"label": "Route",
|
||||||
"columns": 0,
|
|
||||||
"depends_on": "publish",
|
|
||||||
"fieldname": "route",
|
|
||||||
"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": "Route",
|
|
||||||
"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
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
"description": "Job profile, qualifications required etc.",
|
||||||
"allow_on_submit": 0,
|
"fieldname": "description",
|
||||||
"bold": 0,
|
"fieldtype": "Text Editor",
|
||||||
"collapsible": 0,
|
"in_list_view": 1,
|
||||||
"columns": 0,
|
"label": "Description"
|
||||||
"description": "Job profile, qualifications required etc.",
|
},
|
||||||
"fieldname": "description",
|
{
|
||||||
"fieldtype": "Text Editor",
|
"fieldname": "column_break_12",
|
||||||
"hidden": 0,
|
"fieldtype": "Column Break"
|
||||||
"ignore_user_permissions": 0,
|
},
|
||||||
"ignore_xss_filter": 0,
|
{
|
||||||
"in_filter": 0,
|
"fieldname": "section_break_14",
|
||||||
"in_global_search": 0,
|
"fieldtype": "Section Break"
|
||||||
"in_list_view": 1,
|
},
|
||||||
"in_standard_filter": 0,
|
{
|
||||||
"label": "Description",
|
"collapsible": 1,
|
||||||
"length": 0,
|
"fieldname": "section_break_16",
|
||||||
"no_copy": 0,
|
"fieldtype": "Section Break"
|
||||||
"permlevel": 0,
|
},
|
||||||
"print_hide": 0,
|
{
|
||||||
"print_hide_if_no_value": 0,
|
"fieldname": "currency",
|
||||||
"read_only": 0,
|
"fieldtype": "Link",
|
||||||
"remember_last_selected_value": 0,
|
"label": "Currency",
|
||||||
"report_hide": 0,
|
"options": "Currency"
|
||||||
"reqd": 0,
|
},
|
||||||
"search_index": 0,
|
{
|
||||||
"set_only_once": 0,
|
"fieldname": "lower_range",
|
||||||
"translatable": 0,
|
"fieldtype": "Currency",
|
||||||
"unique": 0
|
"label": "Lower Range",
|
||||||
|
"options": "currency",
|
||||||
|
"precision": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "upper_range",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Upper Range",
|
||||||
|
"options": "currency",
|
||||||
|
"precision": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_20",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "publish",
|
||||||
|
"description": "Route to the custom Job Application Webform",
|
||||||
|
"fieldname": "job_application_route",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Job Application Route"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "publish_salary_range",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Publish Salary Range"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"icon": "fa fa-bookmark",
|
||||||
"hide_heading": 0,
|
"idx": 1,
|
||||||
"hide_toolbar": 0,
|
"links": [],
|
||||||
"icon": "fa fa-bookmark",
|
"modified": "2020-09-18 11:23:29.488923",
|
||||||
"idx": 1,
|
"modified_by": "Administrator",
|
||||||
"image_view": 0,
|
"module": "HR",
|
||||||
"in_create": 0,
|
"name": "Job Opening",
|
||||||
"is_submittable": 0,
|
"owner": "Administrator",
|
||||||
"issingle": 0,
|
|
||||||
"istable": 0,
|
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2018-05-20 15:38:44.705823",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "HR",
|
|
||||||
"name": "Job Opening",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"create": 1,
|
||||||
"cancel": 0,
|
"delete": 1,
|
||||||
"create": 1,
|
"email": 1,
|
||||||
"delete": 1,
|
"print": 1,
|
||||||
"email": 1,
|
"read": 1,
|
||||||
"export": 0,
|
"report": 1,
|
||||||
"if_owner": 0,
|
"role": "HR User",
|
||||||
"import": 0,
|
"share": 1,
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "HR User",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"read": 1,
|
||||||
"cancel": 0,
|
"role": "Guest"
|
||||||
"create": 0,
|
|
||||||
"delete": 0,
|
|
||||||
"email": 0,
|
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 0,
|
|
||||||
"read": 1,
|
|
||||||
"report": 0,
|
|
||||||
"role": "Guest",
|
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 0,
|
|
||||||
"submit": 0,
|
|
||||||
"write": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
"sort_field": "modified",
|
||||||
"read_only": 0,
|
"sort_order": "ASC"
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_order": "ASC",
|
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
@ -43,9 +43,8 @@ class JobOpening(WebsiteGenerator):
|
|||||||
current_count = designation_counts['employee_count'] + designation_counts['job_openings']
|
current_count = designation_counts['employee_count'] + designation_counts['job_openings']
|
||||||
|
|
||||||
if self.planned_vacancies <= current_count:
|
if self.planned_vacancies <= current_count:
|
||||||
frappe.throw(_("Job Openings for designation {0} already open \
|
frappe.throw(_("Job Openings for designation {0} already open or hiring completed as per Staffing Plan {1}").format(
|
||||||
or hiring completed as per Staffing Plan {1}"
|
self.designation, self.staffing_plan))
|
||||||
.format(self.designation, self.staffing_plan)))
|
|
||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
context.parents = [{'route': 'jobs', 'title': _('All Jobs') }]
|
context.parents = [{'route': 'jobs', 'title': _('All Jobs') }]
|
||||||
@ -56,7 +55,8 @@ def get_list_context(context):
|
|||||||
context.get_list = get_job_openings
|
context.get_list = get_job_openings
|
||||||
|
|
||||||
def get_job_openings(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by=None):
|
def get_job_openings(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by=None):
|
||||||
fields = ['name', 'status', 'job_title', 'description']
|
fields = ['name', 'status', 'job_title', 'description', 'publish_salary_range',
|
||||||
|
'lower_range', 'upper_range', 'currency', 'job_application_route']
|
||||||
|
|
||||||
filters = filters or {}
|
filters = filters or {}
|
||||||
filters.update({
|
filters.update({
|
||||||
|
@ -1,9 +1,18 @@
|
|||||||
<div class="my-5">
|
<div class="my-5">
|
||||||
<h3>{{ doc.job_title }}</h3>
|
<h3>{{ doc.job_title }}</h3>
|
||||||
<p>{{ doc.description }}</p>
|
<p>{{ doc.description }}</p>
|
||||||
|
{%- if doc.publish_salary_range -%}
|
||||||
|
<p><b>{{_("Salary range per month")}}: </b>{{ frappe.format_value(frappe.utils.flt(doc.lower_range), currency=doc.currency) }} - {{ frappe.format_value(frappe.utils.flt(doc.upper_range), currency=doc.currency) }}</p>
|
||||||
|
{% endif %}
|
||||||
<div>
|
<div>
|
||||||
<a class="btn btn-primary"
|
{%- if doc.job_application_route -%}
|
||||||
href="/job_application?new=1&job_title={{ doc.name }}">
|
<a class='btn btn-primary'
|
||||||
|
href='/{{doc.job_application_route}}?new=1&job_title={{ doc.name }}'>
|
||||||
{{ _("Apply Now") }}</a>
|
{{ _("Apply Now") }}</a>
|
||||||
|
{% else %}
|
||||||
|
<a class='btn btn-primary'
|
||||||
|
href='/job_application?new=1&job_title={{ doc.name }}'>
|
||||||
|
{{ _("Apply Now") }}</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
"employee",
|
"employee",
|
||||||
"employee_name",
|
"employee_name",
|
||||||
"department",
|
"department",
|
||||||
|
"company",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"leave_type",
|
"leave_type",
|
||||||
"from_date",
|
"from_date",
|
||||||
@ -219,6 +220,15 @@
|
|||||||
"label": "Leave Policy Assignment",
|
"label": "Leave Policy Assignment",
|
||||||
"options": "Leave Policy Assignment",
|
"options": "Leave Policy Assignment",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "employee.company",
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-ok",
|
"icon": "fa fa-ok",
|
||||||
@ -226,7 +236,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-08-20 14:25:10.314323",
|
"modified": "2021-01-04 18:46:13.184104",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Leave Allocation",
|
"name": "Leave Allocation",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"creation": "2019-05-09 15:47:39.760406",
|
"creation": "2019-05-09 15:47:39.760406",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
@ -8,6 +9,7 @@
|
|||||||
"leave_type",
|
"leave_type",
|
||||||
"transaction_type",
|
"transaction_type",
|
||||||
"transaction_name",
|
"transaction_name",
|
||||||
|
"company",
|
||||||
"leaves",
|
"leaves",
|
||||||
"column_break_7",
|
"column_break_7",
|
||||||
"from_date",
|
"from_date",
|
||||||
@ -106,12 +108,22 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Holiday List",
|
"label": "Holiday List",
|
||||||
"options": "Holiday List"
|
"options": "Holiday List"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "employee.company",
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"modified": "2020-09-04 12:16:36.569066",
|
"links": [],
|
||||||
|
"modified": "2021-01-04 18:47:45.146652",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "HR",
|
"module": "HR",
|
||||||
"name": "Leave Ledger Entry",
|
"name": "Leave Ledger Entry",
|
||||||
|
@ -1,86 +1,200 @@
|
|||||||
{
|
{
|
||||||
"accept_payment": 0,
|
"accept_payment": 0,
|
||||||
"allow_comments": 1,
|
"allow_comments": 1,
|
||||||
"allow_delete": 0,
|
"allow_delete": 0,
|
||||||
"allow_edit": 1,
|
"allow_edit": 1,
|
||||||
"allow_incomplete": 0,
|
"allow_incomplete": 0,
|
||||||
"allow_multiple": 1,
|
"allow_multiple": 1,
|
||||||
"allow_print": 0,
|
"allow_print": 0,
|
||||||
"amount": 0.0,
|
"amount": 0.0,
|
||||||
"amount_based_on_field": 0,
|
"amount_based_on_field": 0,
|
||||||
"creation": "2016-09-10 02:53:16.598314",
|
"apply_document_permissions": 0,
|
||||||
"doc_type": "Job Applicant",
|
"client_script": "frappe.web_form.on('resume_link', (field, value) => {\n if (!frappe.utils.is_url(value)) {\n frappe.msgprint(__('Resume link not valid'));\n }\n});\n",
|
||||||
"docstatus": 0,
|
"creation": "2016-09-10 02:53:16.598314",
|
||||||
"doctype": "Web Form",
|
"doc_type": "Job Applicant",
|
||||||
"idx": 0,
|
"docstatus": 0,
|
||||||
"introduction_text": "",
|
"doctype": "Web Form",
|
||||||
"is_standard": 1,
|
"idx": 0,
|
||||||
"login_required": 0,
|
"introduction_text": "",
|
||||||
"max_attachment_size": 0,
|
"is_standard": 1,
|
||||||
"modified": "2016-12-20 00:21:44.081622",
|
"login_required": 0,
|
||||||
"modified_by": "Administrator",
|
"max_attachment_size": 0,
|
||||||
"module": "HR",
|
"modified": "2020-10-07 19:27:17.143355",
|
||||||
"name": "job-application",
|
"modified_by": "Administrator",
|
||||||
"owner": "Administrator",
|
"module": "HR",
|
||||||
"published": 1,
|
"name": "job-application",
|
||||||
"route": "job_application",
|
"owner": "Administrator",
|
||||||
"show_sidebar": 1,
|
"published": 1,
|
||||||
"sidebar_items": [],
|
"route": "job_application",
|
||||||
"success_message": "Thank you for applying.",
|
"route_to_success_link": 0,
|
||||||
"success_url": "/jobs",
|
"show_attachments": 0,
|
||||||
"title": "Job Application",
|
"show_in_grid": 0,
|
||||||
|
"show_sidebar": 1,
|
||||||
|
"sidebar_items": [],
|
||||||
|
"success_message": "Thank you for applying.",
|
||||||
|
"success_url": "/jobs",
|
||||||
|
"title": "Job Application",
|
||||||
"web_form_fields": [
|
"web_form_fields": [
|
||||||
{
|
{
|
||||||
"fieldname": "job_title",
|
"allow_read_on_all_link_options": 0,
|
||||||
"fieldtype": "Data",
|
"fieldname": "job_title",
|
||||||
"hidden": 0,
|
"fieldtype": "Data",
|
||||||
"label": "Job Opening",
|
"hidden": 0,
|
||||||
"max_length": 0,
|
"label": "Job Opening",
|
||||||
"max_value": 0,
|
"max_length": 0,
|
||||||
"options": "",
|
"max_value": 0,
|
||||||
"read_only": 1,
|
"options": "",
|
||||||
"reqd": 0
|
"read_only": 1,
|
||||||
},
|
"reqd": 0,
|
||||||
|
"show_in_filter": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "applicant_name",
|
"allow_read_on_all_link_options": 0,
|
||||||
"fieldtype": "Data",
|
"fieldname": "applicant_name",
|
||||||
"hidden": 0,
|
"fieldtype": "Data",
|
||||||
"label": "Applicant Name",
|
"hidden": 0,
|
||||||
"max_length": 0,
|
"label": "Applicant Name",
|
||||||
"max_value": 0,
|
"max_length": 0,
|
||||||
"read_only": 0,
|
"max_value": 0,
|
||||||
"reqd": 1
|
"read_only": 0,
|
||||||
},
|
"reqd": 1,
|
||||||
|
"show_in_filter": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "email_id",
|
"allow_read_on_all_link_options": 0,
|
||||||
"fieldtype": "Data",
|
"fieldname": "email_id",
|
||||||
"hidden": 0,
|
"fieldtype": "Data",
|
||||||
"label": "Email Address",
|
"hidden": 0,
|
||||||
"max_length": 0,
|
"label": "Email Address",
|
||||||
"max_value": 0,
|
"max_length": 0,
|
||||||
"options": "Email",
|
"max_value": 0,
|
||||||
"read_only": 0,
|
"options": "Email",
|
||||||
"reqd": 1
|
"read_only": 0,
|
||||||
},
|
"reqd": 1,
|
||||||
|
"show_in_filter": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "cover_letter",
|
"allow_read_on_all_link_options": 0,
|
||||||
"fieldtype": "Text",
|
"fieldname": "phone_number",
|
||||||
"hidden": 0,
|
"fieldtype": "Data",
|
||||||
"label": "Cover Letter",
|
"hidden": 0,
|
||||||
"max_length": 0,
|
"label": "Phone Number",
|
||||||
"max_value": 0,
|
"max_length": 0,
|
||||||
"read_only": 0,
|
"max_value": 0,
|
||||||
"reqd": 0
|
"options": "Phone",
|
||||||
},
|
"read_only": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"show_in_filter": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "resume_attachment",
|
"allow_read_on_all_link_options": 0,
|
||||||
"fieldtype": "Attach",
|
"fieldname": "country",
|
||||||
"hidden": 0,
|
"fieldtype": "Link",
|
||||||
"label": "Resume Attachment",
|
"hidden": 0,
|
||||||
"max_length": 0,
|
"label": "Country of Residence",
|
||||||
"max_value": 0,
|
"max_length": 0,
|
||||||
"read_only": 0,
|
"max_value": 0,
|
||||||
"reqd": 0
|
"options": "Country",
|
||||||
|
"read_only": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"show_in_filter": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_read_on_all_link_options": 0,
|
||||||
|
"fieldname": "cover_letter",
|
||||||
|
"fieldtype": "Text",
|
||||||
|
"hidden": 0,
|
||||||
|
"label": "Cover Letter",
|
||||||
|
"max_length": 0,
|
||||||
|
"max_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"show_in_filter": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_read_on_all_link_options": 0,
|
||||||
|
"fieldname": "resume_link",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 0,
|
||||||
|
"label": "Resume Link",
|
||||||
|
"max_length": 0,
|
||||||
|
"max_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"show_in_filter": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_read_on_all_link_options": 0,
|
||||||
|
"fieldname": "",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hidden": 0,
|
||||||
|
"label": "Expected Salary Range per month",
|
||||||
|
"max_length": 0,
|
||||||
|
"max_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"reqd": 1,
|
||||||
|
"show_in_filter": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_read_on_all_link_options": 0,
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"hidden": 0,
|
||||||
|
"label": "Currency",
|
||||||
|
"max_length": 0,
|
||||||
|
"max_value": 0,
|
||||||
|
"options": "Currency",
|
||||||
|
"read_only": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"show_in_filter": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_read_on_all_link_options": 0,
|
||||||
|
"fieldname": "",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"hidden": 0,
|
||||||
|
"max_length": 0,
|
||||||
|
"max_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"show_in_filter": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_read_on_all_link_options": 0,
|
||||||
|
"fieldname": "lower_range",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"label": "Lower Range",
|
||||||
|
"max_length": 0,
|
||||||
|
"max_value": 0,
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"show_in_filter": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_read_on_all_link_options": 0,
|
||||||
|
"fieldname": "",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"hidden": 0,
|
||||||
|
"max_length": 0,
|
||||||
|
"max_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"show_in_filter": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_read_on_all_link_options": 0,
|
||||||
|
"fieldname": "upper_range",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 0,
|
||||||
|
"label": "Upper Range",
|
||||||
|
"max_length": 0,
|
||||||
|
"max_value": 0,
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"show_in_filter": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -23,7 +23,7 @@
|
|||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"label": "Reports",
|
"label": "Reports",
|
||||||
"links": "[\n {\n \"doctype\": \"Loan Repayment\",\n \"is_query_report\": true,\n \"label\": \"Loan Repayment and Closure\",\n \"name\": \"Loan Repayment and Closure\",\n \"route\": \"#query-report/Loan Repayment and Closure\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Security Pledge\",\n \"is_query_report\": true,\n \"label\": \"Loan Security Status\",\n \"name\": \"Loan Security Status\",\n \"route\": \"#query-report/Loan Security Status\",\n \"type\": \"report\"\n }\n]"
|
"links": "[\n {\n \"doctype\": \"Loan Repayment\",\n \"is_query_report\": true,\n \"label\": \"Loan Repayment and Closure\",\n \"name\": \"Loan Repayment and Closure\",\n \"route\": \"#query-report/Loan Repayment and Closure\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Security Pledge\",\n \"is_query_report\": true,\n \"label\": \"Loan Security Status\",\n \"name\": \"Loan Security Status\",\n \"route\": \"#query-report/Loan Security Status\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Interest Accrual\",\n \"is_query_report\": true,\n \"label\": \"Loan Interest Report\",\n \"name\": \"Loan Interest Report\",\n \"route\": \"#query-report/Loan Interest Report\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Security\",\n \"is_query_report\": true,\n \"label\": \"Loan Security Exposure\",\n \"name\": \"Loan Security Exposure\",\n \"route\": \"#query-report/Loan Security Exposure\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Security\",\n \"is_query_report\": true,\n \"label\": \"Applicant-Wise Loan Security Exposure\",\n \"name\": \"Applicant-Wise Loan Security Exposure\",\n \"route\": \"#query-report/Applicant-Wise Loan Security Exposure\",\n \"type\": \"report\"\n }\n]"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"category": "Modules",
|
"category": "Modules",
|
||||||
@ -38,7 +38,7 @@
|
|||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"label": "Loan",
|
"label": "Loan",
|
||||||
"modified": "2020-10-17 12:59:50.336085",
|
"modified": "2021-01-17 07:21:22.092184",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Loan",
|
"name": "Loan",
|
||||||
|
@ -202,7 +202,9 @@ def request_loan_closure(loan, posting_date=None):
|
|||||||
|
|
||||||
# checking greater than 0 as there may be some minor precision error
|
# checking greater than 0 as there may be some minor precision error
|
||||||
if pending_amount < write_off_limit:
|
if pending_amount < write_off_limit:
|
||||||
# update status as loan closure requested
|
# Auto create loan write off and update status as loan closure requested
|
||||||
|
write_off = make_loan_write_off(loan)
|
||||||
|
write_off.submit()
|
||||||
frappe.db.set_value('Loan', loan, 'status', 'Loan Closure Requested')
|
frappe.db.set_value('Loan', loan, 'status', 'Loan Closure Requested')
|
||||||
else:
|
else:
|
||||||
frappe.throw(_("Cannot close loan as there is an outstanding of {0}").format(pending_amount))
|
frappe.throw(_("Cannot close loan as there is an outstanding of {0}").format(pending_amount))
|
||||||
@ -336,13 +338,13 @@ def create_loan_security_unpledge(unpledge_map, loan, company, applicant_type, a
|
|||||||
return unpledge_request
|
return unpledge_request
|
||||||
|
|
||||||
def validate_employee_currency_with_company_currency(applicant, company):
|
def validate_employee_currency_with_company_currency(applicant, company):
|
||||||
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_employee_currency
|
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_employee_currency
|
||||||
if not applicant:
|
if not applicant:
|
||||||
frappe.throw(_("Please select Applicant"))
|
frappe.throw(_("Please select Applicant"))
|
||||||
if not company:
|
if not company:
|
||||||
frappe.throw(_("Please select Company"))
|
frappe.throw(_("Please select Company"))
|
||||||
employee_currency = get_employee_currency(applicant)
|
employee_currency = get_employee_currency(applicant)
|
||||||
company_currency = erpnext.get_company_currency(company)
|
company_currency = erpnext.get_company_currency(company)
|
||||||
if employee_currency != company_currency:
|
if employee_currency != company_currency:
|
||||||
frappe.throw(_("Loan cannot be repayed from salary for Employee {0} because salary is processed in currency {1}")
|
frappe.throw(_("Loan cannot be repayed from salary for Employee {0} because salary is processed in currency {1}")
|
||||||
.format(applicant, employee_currency))
|
.format(applicant, employee_currency))
|
||||||
|
@ -321,7 +321,7 @@ class TestLoan(unittest.TestCase):
|
|||||||
self.assertEquals(sum(pledged_qty.values()), 0)
|
self.assertEquals(sum(pledged_qty.values()), 0)
|
||||||
|
|
||||||
amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 5))
|
amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 5))
|
||||||
self.assertTrue(amounts['pending_principal_amount'] < 0)
|
self.assertEqual(amounts['pending_principal_amount'], 0)
|
||||||
self.assertEquals(amounts['payable_principal_amount'], 0.0)
|
self.assertEquals(amounts['payable_principal_amount'], 0.0)
|
||||||
self.assertEqual(amounts['interest_amount'], 0)
|
self.assertEqual(amounts['interest_amount'], 0)
|
||||||
|
|
||||||
@ -362,6 +362,27 @@ class TestLoan(unittest.TestCase):
|
|||||||
unpledge_request.load_from_db()
|
unpledge_request.load_from_db()
|
||||||
self.assertEqual(unpledge_request.docstatus, 1)
|
self.assertEqual(unpledge_request.docstatus, 1)
|
||||||
|
|
||||||
|
def test_santined_loan_security_unpledge(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='2019-10-01')
|
||||||
|
loan.submit()
|
||||||
|
|
||||||
|
self.assertEquals(loan.loan_amount, 1000000)
|
||||||
|
|
||||||
|
unpledge_map = {'Test Security 1': 4000}
|
||||||
|
unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1)
|
||||||
|
unpledge_request.submit()
|
||||||
|
unpledge_request.status = 'Approved'
|
||||||
|
unpledge_request.save()
|
||||||
|
unpledge_request.submit()
|
||||||
|
|
||||||
def test_disbursal_check_with_shortfall(self):
|
def test_disbursal_check_with_shortfall(self):
|
||||||
pledges = [{
|
pledges = [{
|
||||||
"loan_security": "Test Security 2",
|
"loan_security": "Test Security 2",
|
||||||
@ -452,7 +473,7 @@ class TestLoan(unittest.TestCase):
|
|||||||
self.assertEquals(loan.status, "Loan Closure Requested")
|
self.assertEquals(loan.status, "Loan Closure Requested")
|
||||||
|
|
||||||
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
|
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
|
||||||
self.assertTrue(amounts['pending_principal_amount'] < 0.0)
|
self.assertEqual(amounts['pending_principal_amount'], 0.0)
|
||||||
|
|
||||||
def test_partial_unaccrued_interest_payment(self):
|
def test_partial_unaccrued_interest_payment(self):
|
||||||
pledge = [{
|
pledge = [{
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
"paid_principal_amount",
|
"paid_principal_amount",
|
||||||
"column_break_14",
|
"column_break_14",
|
||||||
"interest_amount",
|
"interest_amount",
|
||||||
|
"total_pending_interest_amount",
|
||||||
"paid_interest_amount",
|
"paid_interest_amount",
|
||||||
"penalty_amount",
|
"penalty_amount",
|
||||||
"section_break_15",
|
"section_break_15",
|
||||||
@ -172,13 +173,19 @@
|
|||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Last Accrual Date",
|
"label": "Last Accrual Date",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_pending_interest_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Pending Interest Amount",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-11-07 05:49:25.448875",
|
"modified": "2021-01-10 00:15:21.544140",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Loan Interest Accrual",
|
"name": "Loan Interest Accrual",
|
||||||
|
@ -100,6 +100,8 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i
|
|||||||
interest_per_day = get_per_day_interest(pending_principal_amount, loan.rate_of_interest, posting_date)
|
interest_per_day = get_per_day_interest(pending_principal_amount, loan.rate_of_interest, posting_date)
|
||||||
payable_interest = interest_per_day * no_of_days
|
payable_interest = interest_per_day * no_of_days
|
||||||
|
|
||||||
|
pending_amounts = calculate_amounts(loan.name, posting_date, payment_type='Loan Closure')
|
||||||
|
|
||||||
args = frappe._dict({
|
args = frappe._dict({
|
||||||
'loan': loan.name,
|
'loan': loan.name,
|
||||||
'applicant_type': loan.applicant_type,
|
'applicant_type': loan.applicant_type,
|
||||||
@ -108,7 +110,8 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i
|
|||||||
'loan_account': loan.loan_account,
|
'loan_account': loan.loan_account,
|
||||||
'pending_principal_amount': pending_principal_amount,
|
'pending_principal_amount': pending_principal_amount,
|
||||||
'interest_amount': payable_interest,
|
'interest_amount': payable_interest,
|
||||||
'penalty_amount': calculate_amounts(loan.name, posting_date)['penalty_amount'],
|
'total_pending_interest_amount': pending_amounts['interest_amount'],
|
||||||
|
'penalty_amount': pending_amounts['penalty_amount'],
|
||||||
'process_loan_interest': process_loan_interest,
|
'process_loan_interest': process_loan_interest,
|
||||||
'posting_date': posting_date,
|
'posting_date': posting_date,
|
||||||
'accrual_type': accrual_type
|
'accrual_type': accrual_type
|
||||||
@ -202,6 +205,7 @@ def make_loan_interest_accrual_entry(args):
|
|||||||
loan_interest_accrual.loan_account = args.loan_account
|
loan_interest_accrual.loan_account = args.loan_account
|
||||||
loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, precision)
|
loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, precision)
|
||||||
loan_interest_accrual.interest_amount = flt(args.interest_amount, precision)
|
loan_interest_accrual.interest_amount = flt(args.interest_amount, precision)
|
||||||
|
loan_interest_accrual.total_pending_interest_amount = flt(args.total_pending_interest_amount, precision)
|
||||||
loan_interest_accrual.penalty_amount = flt(args.penalty_amount, precision)
|
loan_interest_accrual.penalty_amount = flt(args.penalty_amount, precision)
|
||||||
loan_interest_accrual.posting_date = args.posting_date or nowdate()
|
loan_interest_accrual.posting_date = args.posting_date or nowdate()
|
||||||
loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest
|
loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest
|
||||||
|
@ -37,10 +37,8 @@ class TestLoanInterestAccrual(unittest.TestCase):
|
|||||||
|
|
||||||
loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge)
|
loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge)
|
||||||
create_pledge(loan_application)
|
create_pledge(loan_application)
|
||||||
|
|
||||||
loan = create_demand_loan(self.applicant, "Demand Loan", loan_application,
|
loan = create_demand_loan(self.applicant, "Demand Loan", loan_application,
|
||||||
posting_date=get_first_day(nowdate()))
|
posting_date=get_first_day(nowdate()))
|
||||||
|
|
||||||
loan.submit()
|
loan.submit()
|
||||||
|
|
||||||
first_date = '2019-10-01'
|
first_date = '2019-10-01'
|
||||||
@ -50,11 +48,46 @@ class TestLoanInterestAccrual(unittest.TestCase):
|
|||||||
|
|
||||||
accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
|
accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
|
||||||
/ (days_in_year(get_datetime(first_date).year) * 100)
|
/ (days_in_year(get_datetime(first_date).year) * 100)
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
loan_interest_accural = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name})
|
loan_interest_accural = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name})
|
||||||
|
|
||||||
self.assertEquals(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0))
|
self.assertEquals(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0))
|
||||||
|
|
||||||
|
def test_accumulated_amounts(self):
|
||||||
|
pledge = [{
|
||||||
|
"loan_security": "Test Security 1",
|
||||||
|
"qty": 4000.00
|
||||||
|
}]
|
||||||
|
|
||||||
|
loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge)
|
||||||
|
create_pledge(loan_application)
|
||||||
|
loan = create_demand_loan(self.applicant, "Demand Loan", loan_application,
|
||||||
|
posting_date=get_first_day(nowdate()))
|
||||||
|
loan.submit()
|
||||||
|
|
||||||
|
first_date = '2019-10-01'
|
||||||
|
last_date = '2019-10-30'
|
||||||
|
|
||||||
|
no_of_days = date_diff(last_date, first_date) + 1
|
||||||
|
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)
|
||||||
|
loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name})
|
||||||
|
|
||||||
|
self.assertEquals(flt(loan_interest_accrual.interest_amount, 0), flt(accrued_interest_amount, 0))
|
||||||
|
|
||||||
|
next_start_date = '2019-10-31'
|
||||||
|
next_end_date = '2019-11-29'
|
||||||
|
|
||||||
|
no_of_days = date_diff(next_end_date, next_start_date) + 1
|
||||||
|
process = process_loan_interest_accrual_for_demand_loans(posting_date=next_end_date)
|
||||||
|
new_accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
|
||||||
|
/ (days_in_year(get_datetime(first_date).year) * 100)
|
||||||
|
|
||||||
|
total_pending_interest_amount = flt(accrued_interest_amount + new_accrued_interest_amount, 0)
|
||||||
|
|
||||||
|
loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name,
|
||||||
|
'process_loan_interest_accrual': process})
|
||||||
|
self.assertEquals(flt(loan_interest_accrual.total_pending_interest_amount, 0), total_pending_interest_amount)
|
||||||
|
@ -377,7 +377,7 @@ def get_amounts(amounts, against_loan, posting_date):
|
|||||||
amounts["penalty_amount"] = flt(penalty_amount, precision)
|
amounts["penalty_amount"] = flt(penalty_amount, precision)
|
||||||
amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision)
|
amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision)
|
||||||
amounts["pending_accrual_entries"] = pending_accrual_entries
|
amounts["pending_accrual_entries"] = pending_accrual_entries
|
||||||
amounts["unaccrued_interest"] = unaccrued_interest
|
amounts["unaccrued_interest"] = flt(unaccrued_interest, precision)
|
||||||
|
|
||||||
if final_due_date:
|
if final_due_date:
|
||||||
amounts["due_date"] = final_due_date
|
amounts["due_date"] = final_due_date
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"loan_security",
|
"loan_security",
|
||||||
|
"loan_security_name",
|
||||||
"loan_security_type",
|
"loan_security_type",
|
||||||
"column_break_2",
|
"column_break_2",
|
||||||
"uom",
|
"uom",
|
||||||
@ -79,10 +80,18 @@
|
|||||||
"label": "Loan Security Type",
|
"label": "Loan Security Type",
|
||||||
"options": "Loan Security Type",
|
"options": "Loan Security Type",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "loan_security.loan_security_name",
|
||||||
|
"fieldname": "loan_security_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Loan Security Name",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-06-11 03:41:33.900340",
|
"modified": "2021-01-17 07:41:49.598086",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Loan Security Price",
|
"name": "Loan Security Price",
|
||||||
|
@ -44,10 +44,16 @@ class LoanSecurityUnpledge(Document):
|
|||||||
"valid_upto": (">=", get_datetime())
|
"valid_upto": (">=", get_datetime())
|
||||||
}, as_list=1))
|
}, as_list=1))
|
||||||
|
|
||||||
total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid',
|
loan_details = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid',
|
||||||
'total_interest_payable', 'written_off_amount'])
|
'total_interest_payable', 'written_off_amount', 'disbursed_amount', 'status'], as_dict=1)
|
||||||
|
|
||||||
|
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) - flt(loan_details.written_off_amount)
|
||||||
|
else:
|
||||||
|
pending_principal_amount = flt(loan_details.disbursed_amount) - flt(loan_details.total_interest_payable) \
|
||||||
|
- flt(loan_details.total_principal_paid) - flt(loan_details.written_off_amount)
|
||||||
|
|
||||||
pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount)
|
|
||||||
security_value = 0
|
security_value = 0
|
||||||
unpledge_qty_map = {}
|
unpledge_qty_map = {}
|
||||||
ltv_ratio = 0
|
ltv_ratio = 0
|
||||||
|
@ -144,17 +144,17 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"description": "Pending amount that will be automatically ignored on loan closure request ",
|
"description": "Loan Write Off will be automatically created on loan closure request if pending amount is below this limit",
|
||||||
"fieldname": "write_off_amount",
|
"fieldname": "write_off_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Write Off Amount ",
|
"label": "Auto Write Off Amount ",
|
||||||
"options": "Company:company:default_currency"
|
"options": "Company:company:default_currency"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-26 07:13:55.029811",
|
"modified": "2021-01-17 06:51:26.082879",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Loan Type",
|
"name": "Loan Type",
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"loan_security",
|
"loan_security",
|
||||||
|
"loan_security_name",
|
||||||
"loan_security_type",
|
"loan_security_type",
|
||||||
"loan_security_code",
|
"loan_security_code",
|
||||||
"uom",
|
"uom",
|
||||||
@ -85,11 +86,18 @@
|
|||||||
"label": "Post Haircut Amount",
|
"label": "Post Haircut Amount",
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "loan_security.loan_security_name",
|
||||||
|
"fieldname": "loan_security_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Loan Security Name",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-11-05 10:07:15.424937",
|
"modified": "2021-01-17 07:41:12.452514",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Pledge",
|
"name": "Pledge",
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-02-01 08:14:05.845161",
|
"modified": "2021-01-17 03:59:14.494557",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Process Loan Security Shortfall",
|
"name": "Process Loan Security Shortfall",
|
||||||
@ -45,7 +45,9 @@
|
|||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
|
"select": 1,
|
||||||
"share": 1,
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -57,7 +59,9 @@
|
|||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Loan Manager",
|
"role": "Loan Manager",
|
||||||
|
"select": 1,
|
||||||
"share": 1,
|
"share": 1,
|
||||||
|
"submit": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"loan_security",
|
"loan_security",
|
||||||
|
"loan_security_name",
|
||||||
"qty",
|
"qty",
|
||||||
"loan_security_price",
|
"loan_security_price",
|
||||||
"amount",
|
"amount",
|
||||||
@ -56,12 +57,19 @@
|
|||||||
"label": "Post Haircut Amount",
|
"label": "Post Haircut Amount",
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "loan_security.loan_security_name",
|
||||||
|
"fieldname": "loan_security_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Loan Security Name",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-11-05 10:07:37.542344",
|
"modified": "2021-01-17 07:29:01.671722",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Proposed Pledge",
|
"name": "Proposed Pledge",
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"loan_security",
|
"loan_security",
|
||||||
|
"loan_security_name",
|
||||||
"loan_security_type",
|
"loan_security_type",
|
||||||
"loan_security_code",
|
"loan_security_code",
|
||||||
"haircut",
|
"haircut",
|
||||||
@ -61,12 +62,19 @@
|
|||||||
"fieldtype": "Percent",
|
"fieldtype": "Percent",
|
||||||
"label": "Haircut",
|
"label": "Haircut",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "loan_security.loan_security_name",
|
||||||
|
"fieldname": "loan_security_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Loan Security Name",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-11-05 10:07:28.106961",
|
"modified": "2021-01-17 07:36:20.212342",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Unpledge",
|
"name": "Unpledge",
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
frappe.query_reports["Applicant-Wise Loan Security Exposure"] = {
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"fieldname":"company",
|
||||||
|
"label": __("Company"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Company",
|
||||||
|
"default": frappe.defaults.get_user_default("Company"),
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2021-01-15 23:48:38.913514",
|
||||||
|
"disable_prepared_report": 0,
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"modified": "2021-01-15 23:48:38.913514",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Loan Management",
|
||||||
|
"name": "Applicant-Wise Loan Security Exposure",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Loan Security",
|
||||||
|
"report_name": "Applicant-Wise Loan Security Exposure",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "System Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Loan Manager"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
import erpnext
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import get_datetime, flt
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
columns = get_columns(filters)
|
||||||
|
data = get_data(filters)
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
|
def get_columns(filters):
|
||||||
|
columns = [
|
||||||
|
{"label": _("Applicant Type"), "fieldname": "applicant_type", "options": "DocType", "width": 100},
|
||||||
|
{"label": _("Applicant Name"), "fieldname": "applicant_name", "fieldtype": "Dynamic Link", "options": "applicant_type", "width": 150},
|
||||||
|
{"label": _("Loan Security"), "fieldname": "loan_security", "fieldtype": "Link", "options": "Loan Security", "width": 160},
|
||||||
|
{"label": _("Loan Security Code"), "fieldname": "loan_security_code", "fieldtype": "Data", "width": 100},
|
||||||
|
{"label": _("Loan Security Name"), "fieldname": "loan_security_name", "fieldtype": "Data", "width": 150},
|
||||||
|
{"label": _("Haircut"), "fieldname": "haircut", "fieldtype": "Percent", "width": 100},
|
||||||
|
{"label": _("Loan Security Type"), "fieldname": "loan_security_type", "fieldtype": "Link", "options": "Loan Security Type", "width": 120},
|
||||||
|
{"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80},
|
||||||
|
{"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "width": 100},
|
||||||
|
{"label": _("Latest Price"), "fieldname": "latest_price", "fieldtype": "Currency", "options": "currency", "width": 100},
|
||||||
|
{"label": _("Current Value"), "fieldname": "current_value", "fieldtype": "Currency", "options": "currency", "width": 100},
|
||||||
|
{"label": _("% Of Applicant Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "width": 100},
|
||||||
|
{"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
|
||||||
|
]
|
||||||
|
|
||||||
|
return columns
|
||||||
|
|
||||||
|
def get_data(filters):
|
||||||
|
data = []
|
||||||
|
loan_security_details = get_loan_security_details(filters)
|
||||||
|
pledge_values, total_value_map, applicant_type_map = get_applicant_wise_total_loan_security_qty(filters,
|
||||||
|
loan_security_details)
|
||||||
|
|
||||||
|
currency = erpnext.get_company_currency(filters.get('company'))
|
||||||
|
|
||||||
|
for key, qty in iteritems(pledge_values):
|
||||||
|
row = {}
|
||||||
|
current_value = flt(qty * loan_security_details.get(key[1])['latest_price'])
|
||||||
|
row.update(loan_security_details.get(key[1]))
|
||||||
|
row.update({
|
||||||
|
'applicant_type': applicant_type_map.get(key[0]),
|
||||||
|
'applicant_name': key[0],
|
||||||
|
'total_qty': qty,
|
||||||
|
'current_value': current_value,
|
||||||
|
'portfolio_percent': flt(current_value * 100 / total_value_map.get(key[0]), 2),
|
||||||
|
'currency': currency
|
||||||
|
})
|
||||||
|
|
||||||
|
data.append(row)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_loan_security_details(filters):
|
||||||
|
security_detail_map = {}
|
||||||
|
|
||||||
|
loan_security_price_map = frappe._dict(frappe.db.sql("""
|
||||||
|
SELECT loan_security, loan_security_price
|
||||||
|
FROM `tabLoan Security Price` t1
|
||||||
|
WHERE valid_from >= (SELECT MAX(valid_from) FROM `tabLoan Security Price` t2
|
||||||
|
WHERE t1.loan_security = t2.loan_security)
|
||||||
|
""", as_list=1))
|
||||||
|
|
||||||
|
loan_security_details = frappe.get_all('Loan Security', fields=['name as loan_security',
|
||||||
|
'loan_security_code', 'loan_security_name', 'haircut', 'loan_security_type',
|
||||||
|
'disabled'])
|
||||||
|
|
||||||
|
for security in loan_security_details:
|
||||||
|
security.update({'latest_price': flt(loan_security_price_map.get(security.loan_security))})
|
||||||
|
security_detail_map.setdefault(security.loan_security, security)
|
||||||
|
|
||||||
|
return security_detail_map
|
||||||
|
|
||||||
|
def get_applicant_wise_total_loan_security_qty(filters, loan_security_details):
|
||||||
|
current_pledges = {}
|
||||||
|
total_value_map = {}
|
||||||
|
applicant_type_map = {}
|
||||||
|
applicant_wise_unpledges = {}
|
||||||
|
conditions = ""
|
||||||
|
|
||||||
|
if filters.get('company'):
|
||||||
|
conditions = "AND company = %(company)s"
|
||||||
|
|
||||||
|
unpledges = frappe.db.sql("""
|
||||||
|
SELECT up.applicant, u.loan_security, sum(u.qty) as qty
|
||||||
|
FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
|
||||||
|
WHERE u.parent = up.name
|
||||||
|
AND up.status = 'Approved'
|
||||||
|
{conditions}
|
||||||
|
GROUP BY up.applicant, u.loan_security
|
||||||
|
""".format(conditions=conditions), filters, as_dict=1)
|
||||||
|
|
||||||
|
for unpledge in unpledges:
|
||||||
|
applicant_wise_unpledges.setdefault((unpledge.applicant, unpledge.loan_security), unpledge.qty)
|
||||||
|
|
||||||
|
pledges = frappe.db.sql("""
|
||||||
|
SELECT lp.applicant_type, lp.applicant, p.loan_security, sum(p.qty) as qty
|
||||||
|
FROM `tabLoan Security Pledge` lp, `tabPledge`p
|
||||||
|
WHERE p.parent = lp.name
|
||||||
|
AND lp.status = 'Pledged'
|
||||||
|
{conditions}
|
||||||
|
GROUP BY lp.applicant, p.loan_security
|
||||||
|
""".format(conditions=conditions), filters, as_dict=1)
|
||||||
|
|
||||||
|
for security in pledges:
|
||||||
|
current_pledges.setdefault((security.applicant, security.loan_security), security.qty)
|
||||||
|
total_value_map.setdefault(security.applicant, 0.0)
|
||||||
|
applicant_type_map.setdefault(security.applicant, security.applicant_type)
|
||||||
|
|
||||||
|
current_pledges[(security.applicant, security.loan_security)] -= \
|
||||||
|
applicant_wise_unpledges.get((security.applicant, security.loan_security), 0.0)
|
||||||
|
|
||||||
|
total_value_map[security.applicant] += current_pledges.get((security.applicant, security.loan_security)) \
|
||||||
|
* loan_security_details.get(security.loan_security)['latest_price']
|
||||||
|
|
||||||
|
return current_pledges, total_value_map, applicant_type_map
|
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
frappe.query_reports["Loan Interest Report"] = {
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"fieldname":"company",
|
||||||
|
"label": __("Company"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Company",
|
||||||
|
"default": frappe.defaults.get_user_default("Company"),
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 1,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2021-01-10 02:03:26.742693",
|
||||||
|
"disable_prepared_report": 0,
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"modified": "2021-01-10 02:03:26.742693",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Loan Management",
|
||||||
|
"name": "Loan Interest Report",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Loan Interest Accrual",
|
||||||
|
"report_name": "Loan Interest Report",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "System Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Loan Manager"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
import erpnext
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import flt, getdate, add_days
|
||||||
|
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
columns = get_columns(filters)
|
||||||
|
data = get_active_loan_details(filters)
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
def get_columns(filters):
|
||||||
|
columns = [
|
||||||
|
{"label": _("Loan"), "fieldname": "loan", "fieldtype": "Link", "options": "Loan", "width": 160},
|
||||||
|
{"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 160},
|
||||||
|
{"label": _("Applicant Type"), "fieldname": "applicant_type", "options": "DocType", "width": 100},
|
||||||
|
{"label": _("Applicant Name"), "fieldname": "applicant_name", "fieldtype": "Dynamic Link", "options": "applicant_type", "width": 150},
|
||||||
|
{"label": _("Loan Type"), "fieldname": "loan_type", "fieldtype": "Link", "options": "Loan Type", "width": 100},
|
||||||
|
{"label": _("Sanctioned Amount"), "fieldname": "sanctioned_amount", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||||
|
{"label": _("Disbursed Amount"), "fieldname": "disbursed_amount", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||||
|
{"label": _("Penalty Amount"), "fieldname": "penalty", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||||
|
{"label": _("Accrued Interest"), "fieldname": "accrued_interest", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||||
|
{"label": _("Total Repayment"), "fieldname": "total_repayment", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||||
|
{"label": _("Principal Outstanding"), "fieldname": "principal_outstanding", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||||
|
{"label": _("Interest Outstanding"), "fieldname": "interest_outstanding", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||||
|
{"label": _("Total Outstanding"), "fieldname": "total_outstanding", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||||
|
{"label": _("Undue Booked Interest"), "fieldname": "undue_interest", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||||
|
{"label": _("Interest %"), "fieldname": "rate_of_interest", "fieldtype": "Percent", "width": 100},
|
||||||
|
{"label": _("Penalty Interest %"), "fieldname": "penalty_interest", "fieldtype": "Percent", "width": 100},
|
||||||
|
{"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
|
||||||
|
]
|
||||||
|
|
||||||
|
return columns
|
||||||
|
|
||||||
|
def get_active_loan_details(filters):
|
||||||
|
|
||||||
|
filter_obj = {"status": ("!=", "Closed")}
|
||||||
|
if filters.get('company'):
|
||||||
|
filter_obj.update({'company': filters.get('company')})
|
||||||
|
|
||||||
|
loan_details = frappe.get_all("Loan",
|
||||||
|
fields=["name as loan", "applicant_type", "applicant as applicant_name", "loan_type",
|
||||||
|
"disbursed_amount", "rate_of_interest", "total_payment", "total_principal_paid",
|
||||||
|
"total_interest_payable", "written_off_amount", "status"],
|
||||||
|
filters=filter_obj)
|
||||||
|
|
||||||
|
loan_list = [d.loan for d in loan_details]
|
||||||
|
|
||||||
|
sanctioned_amount_map = get_sanctioned_amount_map()
|
||||||
|
penal_interest_rate_map = get_penal_interest_rate_map()
|
||||||
|
payments = get_payments(loan_list)
|
||||||
|
accrual_map = get_interest_accruals(loan_list)
|
||||||
|
currency = erpnext.get_company_currency(filters.get('company'))
|
||||||
|
|
||||||
|
for loan in loan_details:
|
||||||
|
loan.update({
|
||||||
|
"sanctioned_amount": flt(sanctioned_amount_map.get(loan.applicant_name)),
|
||||||
|
"principal_outstanding": flt(loan.total_payment) - flt(loan.total_principal_paid) \
|
||||||
|
- flt(loan.total_interest_payable) - flt(loan.written_off_amount),
|
||||||
|
"total_repayment": flt(payments.get(loan.loan)),
|
||||||
|
"accrued_interest": flt(accrual_map.get(loan.loan, {}).get("accrued_interest")),
|
||||||
|
"interest_outstanding": flt(accrual_map.get(loan.loan, {}).get("interest_outstanding")),
|
||||||
|
"penalty": flt(accrual_map.get(loan.loan, {}).get("penalty")),
|
||||||
|
"penalty_interest": penal_interest_rate_map.get(loan.loan_type),
|
||||||
|
"undue_interest": flt(accrual_map.get(loan.loan, {}).get("undue_interest")),
|
||||||
|
"currency": currency
|
||||||
|
})
|
||||||
|
|
||||||
|
loan['total_outstanding'] = loan['principal_outstanding'] + loan['interest_outstanding'] \
|
||||||
|
+ loan['penalty']
|
||||||
|
|
||||||
|
return loan_details
|
||||||
|
|
||||||
|
def get_sanctioned_amount_map():
|
||||||
|
return frappe._dict(frappe.get_all("Sanctioned Loan Amount", fields=["applicant", "sanctioned_amount_limit"],
|
||||||
|
as_list=1))
|
||||||
|
|
||||||
|
def get_payments(loans):
|
||||||
|
return frappe._dict(frappe.get_all("Loan Repayment", fields=["against_loan", "sum(amount_paid)"],
|
||||||
|
filters={"against_loan": ("in", loans)}, group_by="against_loan", as_list=1))
|
||||||
|
|
||||||
|
def get_interest_accruals(loans):
|
||||||
|
accrual_map = {}
|
||||||
|
|
||||||
|
interest_accruals = frappe.get_all("Loan Interest Accrual",
|
||||||
|
fields=["loan", "interest_amount", "posting_date", "penalty_amount",
|
||||||
|
"paid_interest_amount", "accrual_type"], filters={"loan": ("in", loans)}, order_by="posting_date desc")
|
||||||
|
|
||||||
|
for entry in interest_accruals:
|
||||||
|
accrual_map.setdefault(entry.loan, {
|
||||||
|
"accrued_interest": 0.0,
|
||||||
|
"undue_interest": 0.0,
|
||||||
|
"interest_outstanding": 0.0,
|
||||||
|
"last_accrual_date": '',
|
||||||
|
"due_date": ''
|
||||||
|
})
|
||||||
|
|
||||||
|
if entry.accrual_type == 'Regular':
|
||||||
|
if not accrual_map[entry.loan]['due_date']:
|
||||||
|
accrual_map[entry.loan]['due_date'] = add_days(entry.posting_date, 1)
|
||||||
|
if not accrual_map[entry.loan]['last_accrual_date']:
|
||||||
|
accrual_map[entry.loan]['last_accrual_date'] = entry.posting_date
|
||||||
|
|
||||||
|
due_date = accrual_map[entry.loan]['due_date']
|
||||||
|
last_accrual_date = accrual_map[entry.loan]['last_accrual_date']
|
||||||
|
|
||||||
|
if due_date and getdate(entry.posting_date) < getdate(due_date):
|
||||||
|
accrual_map[entry.loan]["interest_outstanding"] += entry.interest_amount - entry.paid_interest_amount
|
||||||
|
else:
|
||||||
|
accrual_map[entry.loan]['undue_interest'] += entry.interest_amount - entry.paid_interest_amount
|
||||||
|
|
||||||
|
accrual_map[entry.loan]["accrued_interest"] += entry.interest_amount
|
||||||
|
|
||||||
|
if last_accrual_date and getdate(entry.posting_date) == last_accrual_date:
|
||||||
|
accrual_map[entry.loan]["penalty"] = entry.penalty_amount
|
||||||
|
|
||||||
|
return accrual_map
|
||||||
|
|
||||||
|
def get_penal_interest_rate_map():
|
||||||
|
return frappe._dict(frappe.get_all("Loan Type", fields=["name", "penalty_interest_rate"], as_list=1))
|
@ -103,7 +103,7 @@ def get_data(filters):
|
|||||||
|
|
||||||
loan_repayments = frappe.get_all("Loan Repayment",
|
loan_repayments = frappe.get_all("Loan Repayment",
|
||||||
filters = query_filters,
|
filters = query_filters,
|
||||||
fields=["posting_date", "applicant", "name", "against_loan", "payment_type", "payable_amount",
|
fields=["posting_date", "applicant", "name", "against_loan", "payable_amount",
|
||||||
"pending_principal_amount", "interest_payable", "penalty_amount", "amount_paid"]
|
"pending_principal_amount", "interest_payable", "penalty_amount", "amount_paid"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user