Merge branch 'develop' of github.com:frappe/erpnext into version-13-beta-pre-release
This commit is contained in:
commit
0c34a711eb
4
.github/helper/documentation.py
vendored
4
.github/helper/documentation.py
vendored
@ -21,8 +21,8 @@ def docs_link_exists(body):
|
||||
if word.startswith('http') and uri_validator(word):
|
||||
parsed_url = urlparse(word)
|
||||
if parsed_url.netloc == "github.com":
|
||||
_, org, repo, _type, ref = parsed_url.path.split('/')
|
||||
if org == "frappe" and repo in docs_repos:
|
||||
parts = parsed_url.path.split('/')
|
||||
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
|
||||
return True
|
||||
|
||||
|
||||
|
@ -132,16 +132,10 @@ def allow_regional(fn):
|
||||
|
||||
return caller
|
||||
|
||||
def get_last_membership():
|
||||
def get_last_membership(member):
|
||||
'''Returns last membership if exists'''
|
||||
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
|
||||
dict(member=frappe.session.user, paid=1), order_by='to_date desc', limit=1)
|
||||
dict(member=member, paid=1), order_by='to_date desc', limit=1)
|
||||
|
||||
return last_membership and last_membership[0]
|
||||
|
||||
def is_member():
|
||||
'''Returns true if the user is still a member'''
|
||||
last_membership = get_last_membership()
|
||||
if last_membership and getdate(last_membership.to_date) > getdate():
|
||||
return True
|
||||
return False
|
||||
if last_membership:
|
||||
return last_membership[0]
|
||||
|
@ -910,98 +910,8 @@
|
||||
},
|
||||
"is_group": 1
|
||||
},
|
||||
"Passiva": {
|
||||
"Passiva - Verbindlichkeiten": {
|
||||
"root_type": "Liability",
|
||||
"A - Eigenkapital": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"I - Gezeichnetes Kapital": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Gezeichnetes Kapital": {
|
||||
"account_type": "Equity",
|
||||
"account_number": "2900"
|
||||
},
|
||||
"Ausstehende Einlagen auf das gezeichnete Kapital": {
|
||||
"account_number": "2910",
|
||||
"is_group": 1
|
||||
}
|
||||
},
|
||||
"II - Kapitalr\u00fccklage": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Kapitalr\u00fccklage": {
|
||||
"account_number": "2920"
|
||||
}
|
||||
},
|
||||
"III - Gewinnr\u00fccklagen": {
|
||||
"account_type": "Equity",
|
||||
"1 - gesetzliche R\u00fccklage": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Gesetzliche R\u00fccklage": {
|
||||
"account_number": "2930"
|
||||
}
|
||||
},
|
||||
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1
|
||||
},
|
||||
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
||||
"account_number": "2950"
|
||||
}
|
||||
},
|
||||
"4 - andere Gewinnr\u00fccklagen": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
|
||||
"is_group": 1,
|
||||
"Gewinnr\u00fccklagen (BilMoG)": {
|
||||
"account_number": "2963"
|
||||
},
|
||||
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
|
||||
"account_number": "2964"
|
||||
},
|
||||
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
|
||||
"account_number": "2965"
|
||||
},
|
||||
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
|
||||
"account_number": "2966"
|
||||
}
|
||||
},
|
||||
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
|
||||
"account_number": "2967"
|
||||
},
|
||||
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
||||
"account_number": "2968"
|
||||
},
|
||||
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
||||
"account_number": "2969"
|
||||
}
|
||||
},
|
||||
"is_group": 1
|
||||
},
|
||||
"IV - Gewinnvortrag/Verlustvortrag": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Gewinnvortrag vor Verwendung": {
|
||||
"account_number": "2970"
|
||||
},
|
||||
"Verlustvortrag vor Verwendung": {
|
||||
"account_number": "2978"
|
||||
}
|
||||
},
|
||||
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1
|
||||
},
|
||||
"Einlagen stiller Gesellschafter": {
|
||||
"account_number": "9295"
|
||||
}
|
||||
},
|
||||
"B - R\u00fcckstellungen": {
|
||||
"is_group": 1,
|
||||
"1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": {
|
||||
@ -1618,6 +1528,143 @@
|
||||
},
|
||||
"is_group": 1
|
||||
},
|
||||
"Passiva - Eigenkapital": {
|
||||
"root_type": "Equity",
|
||||
"A - Eigenkapital": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"I - Gezeichnetes Kapital": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Gezeichnetes Kapital": {
|
||||
"account_number": "2900",
|
||||
"account_type": "Equity"
|
||||
},
|
||||
"Gesch\u00e4ftsguthaben der verbleibenden Mitglieder": {
|
||||
"account_number": "2901"
|
||||
},
|
||||
"Gesch\u00e4ftsguthaben der ausscheidenden Mitglieder": {
|
||||
"account_number": "2902"
|
||||
},
|
||||
"Gesch\u00e4ftsguthaben aus gek\u00fcndigten Gesch\u00e4ftsanteilen": {
|
||||
"account_number": "2903"
|
||||
},
|
||||
"R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
|
||||
"account_number": "2906"
|
||||
},
|
||||
"Gegenkonto R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
|
||||
"account_number": "2907"
|
||||
},
|
||||
"Kapitalerh\u00f6hung aus Gesellschaftsmitteln": {
|
||||
"account_number": "2908"
|
||||
},
|
||||
"Ausstehende Einlagen auf das gezeichnete Kapital, nicht eingefordert": {
|
||||
"account_number": "2910"
|
||||
}
|
||||
},
|
||||
"II - Kapitalr\u00fccklage": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Kapitalr\u00fccklage": {
|
||||
"account_number": "2920"
|
||||
},
|
||||
"Kapitalr\u00fccklage durch Ausgabe von Anteilen \u00fcber Nennbetrag": {
|
||||
"account_number": "2925"
|
||||
},
|
||||
"Kapitalr\u00fccklage durch Ausgabe von Schuldverschreibungen": {
|
||||
"account_number": "2926"
|
||||
},
|
||||
"Kapitalr\u00fccklage durch Zuzahlungen gegen Gew\u00e4hrung eines Vorzugs": {
|
||||
"account_number": "2927"
|
||||
},
|
||||
"Kapitalr\u00fccklage durch Zuzahlungen in das Eigenkapital": {
|
||||
"account_number": "2928"
|
||||
},
|
||||
"Nachschusskapital (Gegenkonto 1299)": {
|
||||
"account_number": "2929"
|
||||
}
|
||||
},
|
||||
"III - Gewinnr\u00fccklagen": {
|
||||
"account_type": "Equity",
|
||||
"1 - gesetzliche R\u00fccklage": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Gesetzliche R\u00fccklage": {
|
||||
"account_number": "2930"
|
||||
}
|
||||
},
|
||||
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
|
||||
"account_number": "2935"
|
||||
}
|
||||
},
|
||||
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
||||
"account_number": "2950"
|
||||
}
|
||||
},
|
||||
"4 - andere Gewinnr\u00fccklagen": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Andere Gewinnr\u00fccklagen": {
|
||||
"account_number": "2960"
|
||||
},
|
||||
"Andere Gewinnr\u00fccklagen aus dem Erwerb eigener Anteile": {
|
||||
"account_number": "2961"
|
||||
},
|
||||
"Eigenkapitalanteil von Wertaufholungen": {
|
||||
"account_number": "2962"
|
||||
},
|
||||
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
|
||||
"is_group": 1,
|
||||
"Gewinnr\u00fccklagen (BilMoG)": {
|
||||
"account_number": "2963"
|
||||
},
|
||||
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
|
||||
"account_number": "2964"
|
||||
},
|
||||
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
|
||||
"account_number": "2965"
|
||||
},
|
||||
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
|
||||
"account_number": "2966"
|
||||
}
|
||||
},
|
||||
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
|
||||
"account_number": "2967"
|
||||
},
|
||||
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
||||
"account_number": "2968"
|
||||
},
|
||||
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
||||
"account_number": "2969"
|
||||
}
|
||||
},
|
||||
"is_group": 1
|
||||
},
|
||||
"IV - Gewinnvortrag/Verlustvortrag": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1,
|
||||
"Gewinnvortrag vor Verwendung": {
|
||||
"account_number": "2970"
|
||||
},
|
||||
"Verlustvortrag vor Verwendung": {
|
||||
"account_number": "2978"
|
||||
}
|
||||
},
|
||||
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
|
||||
"account_type": "Equity",
|
||||
"is_group": 1
|
||||
},
|
||||
"Einlagen stiller Gesellschafter": {
|
||||
"account_number": "9295"
|
||||
}
|
||||
}
|
||||
},
|
||||
"1 - Umsatzerl\u00f6se": {
|
||||
"root_type": "Income",
|
||||
"is_group": 1,
|
||||
|
@ -2,7 +2,6 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Accounting Dimension', {
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.set_query('document_type', () => {
|
||||
let invalid_doctypes = frappe.model.core_doctypes_list;
|
||||
|
@ -203,7 +203,7 @@ def get_dimension_with_children(doctype, dimension):
|
||||
return all_dimensions
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_dimension_filters():
|
||||
def get_dimensions(with_cost_center_and_project=False):
|
||||
dimension_filters = frappe.db.sql("""
|
||||
SELECT label, fieldname, document_type
|
||||
FROM `tabAccounting Dimension`
|
||||
@ -214,6 +214,18 @@ def get_dimension_filters():
|
||||
FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
|
||||
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 = {}
|
||||
for dimension in default_dimensions:
|
||||
default_dimensions_map.setdefault(dimension.company, {})
|
||||
|
@ -11,37 +11,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import d
|
||||
|
||||
class TestAccountingDimension(unittest.TestCase):
|
||||
def setUp(self):
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
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()
|
||||
create_dimension()
|
||||
|
||||
def test_dimension_against_sales_invoice(self):
|
||||
si = create_sales_invoice(do_not_save=1)
|
||||
@ -101,6 +71,38 @@ class TestAccountingDimension(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
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():
|
||||
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
|
||||
// For license information, please see license.txt
|
||||
frappe.provide('erpnext.integrations');
|
||||
|
||||
frappe.ui.form.on('Bank', {
|
||||
onload: function(frm) {
|
||||
@ -20,7 +21,12 @@ frappe.ui.form.on('Bank', {
|
||||
frm.set_df_property('address_and_contact', 'hidden', 0);
|
||||
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.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
|
||||
// For license information, please see license.txt
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on('Budget', {
|
||||
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() {
|
||||
return {
|
||||
filters: {
|
||||
@ -26,16 +11,18 @@ frappe.ui.form.on('Budget', {
|
||||
report_type: "Profit and Loss",
|
||||
is_group: 0
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("monthly_distribution", function() {
|
||||
return {
|
||||
filters: {
|
||||
fiscal_year: frm.doc.fiscal_year
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
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")
|
||||
|
||||
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||
|
||||
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)
|
||||
|
||||
@ -147,8 +149,11 @@ class TestBudget(unittest.TestCase):
|
||||
|
||||
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",
|
||||
"_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)
|
||||
|
||||
@ -159,10 +164,10 @@ class TestBudget(unittest.TestCase):
|
||||
|
||||
budget = make_budget(budget_against="Cost Center")
|
||||
month = now_datetime().month
|
||||
if month > 10:
|
||||
month = 10
|
||||
if month > 9:
|
||||
month = 9
|
||||
|
||||
for i in range(month):
|
||||
for i in range(month+1):
|
||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_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")
|
||||
month = now_datetime().month
|
||||
if month > 10:
|
||||
month = 10
|
||||
if month > 9:
|
||||
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",
|
||||
"_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",
|
||||
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
|
||||
@ -289,7 +296,7 @@ def make_budget(**args):
|
||||
budget = frappe.new_doc("Budget")
|
||||
|
||||
if budget_against == "Project":
|
||||
budget.project = "_Test Project"
|
||||
budget.project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||
else:
|
||||
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.utils import get_account_currency
|
||||
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_filter.accounting_dimension_filter import get_dimension_filter_map
|
||||
from six import iteritems
|
||||
|
||||
exclude_from_linked_with = True
|
||||
class GLEntry(Document):
|
||||
@ -39,6 +41,7 @@ class GLEntry(Document):
|
||||
if not from_repost:
|
||||
self.validate_account_details(adv_adj)
|
||||
self.validate_dimensions_for_pl_and_bs()
|
||||
self.validate_allowed_dimensions()
|
||||
|
||||
validate_frozen_account(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))
|
||||
|
||||
def validate_dimensions_for_pl_and_bs(self):
|
||||
|
||||
account_type = frappe.db.get_value("Account", self.account, "report_type")
|
||||
|
||||
for dimension in get_checks_for_pl_and_bs_accounts():
|
||||
|
||||
if account_type == "Profit and Loss" \
|
||||
and self.company == dimension.company and dimension.mandatory_for_pl and not dimension.disabled:
|
||||
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}.")
|
||||
.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):
|
||||
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}")
|
||||
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
|
||||
|
||||
if self.cost_center and _check_is_group():
|
||||
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)))
|
||||
if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \
|
||||
and self.cost_center and _check_is_group():
|
||||
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):
|
||||
validate_party_frozen_disabled(self.party_type, self.party)
|
||||
|
@ -20,7 +20,8 @@ def get_data():
|
||||
'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){
|
||||
@ -197,6 +199,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
||||
this.load_defaults();
|
||||
this.setup_queries();
|
||||
this.setup_balance_formatter();
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||
},
|
||||
|
||||
onload_post_render: function() {
|
||||
@ -222,15 +225,6 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
||||
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) {
|
||||
const row = locals[cdt][cdn];
|
||||
|
||||
@ -406,6 +400,8 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
||||
}
|
||||
}
|
||||
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)
|
||||
|
||||
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",
|
||||
"Sales - _TC", 100, exchange_rate=50, save=False)
|
||||
|
||||
@ -299,15 +299,20 @@ class TestJournalEntry(unittest.TestCase):
|
||||
|
||||
def test_jv_with_project(self):
|
||||
from erpnext.projects.doctype.project.test_project import make_project
|
||||
project = make_project({
|
||||
'project_name': 'Journal Entry Project',
|
||||
'project_template_name': 'Test Project Template',
|
||||
'start_date': '2020-01-01'
|
||||
})
|
||||
|
||||
if not frappe.db.exists("Project", {"project_name": "Journal Entry Project"}):
|
||||
project = make_project({
|
||||
'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)
|
||||
for d in jv.accounts:
|
||||
d.project = project.project_name
|
||||
d.project = project_name
|
||||
jv.voucher_type = "Bank Entry"
|
||||
jv.multi_currency = 0
|
||||
jv.cheque_no = "112233"
|
||||
@ -317,10 +322,10 @@ class TestJournalEntry(unittest.TestCase):
|
||||
|
||||
expected_values = {
|
||||
"_Test Cash - _TC": {
|
||||
"project": project.project_name
|
||||
"project": project_name
|
||||
},
|
||||
"_Test Bank - _TC": {
|
||||
"project": project.project_name
|
||||
"project": project_name
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on('Loyalty Program', {
|
||||
setup: function(frm) {
|
||||
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"));
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
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."));
|
||||
}
|
||||
},
|
||||
|
||||
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.page.set_indicator(__('In Progress'), 'orange');
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
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) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
{% include "erpnext/public/js/controllers/accounts.js" %}
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on('Payment Entry', {
|
||||
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_to) frm.set_value("paid_to_account_currency", null);
|
||||
}
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
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() {
|
||||
if (frm.doc.party_type=="Customer") {
|
||||
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
||||
@ -167,6 +161,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
company: function(frm) {
|
||||
frm.events.hide_unhide_fields(frm);
|
||||
frm.events.set_dynamic_labels(frm);
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
contact_person: function(frm) {
|
||||
@ -401,6 +396,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
|
||||
set_account_currency_and_balance: function(frm, account, currency_field,
|
||||
balance_field, callback_function) {
|
||||
|
||||
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||
if (frm.doc.posting_date && account) {
|
||||
frappe.call({
|
||||
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)
|
||||
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.controllers.accounts_controller import AccountsController
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (get_accounting_dimensions,
|
||||
get_dimension_filters)
|
||||
get_dimensions)
|
||||
|
||||
class PeriodClosingVoucher(AccountsController):
|
||||
def validate(self):
|
||||
@ -58,7 +58,7 @@ class PeriodClosingVoucher(AccountsController):
|
||||
for dimension in accounting_dimensions:
|
||||
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)
|
||||
|
||||
|
@ -266,6 +266,8 @@ class POSInvoice(SalesInvoice):
|
||||
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
|
||||
if not self.pos_profile:
|
||||
pos_profile = get_pos_profile(self.company) or {}
|
||||
if not pos_profile:
|
||||
frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
|
||||
self.pos_profile = pos_profile.get('name')
|
||||
|
||||
profile = {}
|
||||
|
@ -57,6 +57,8 @@ frappe.ui.form.on('POS Profile', {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
@ -67,6 +69,7 @@ frappe.ui.form.on('POS Profile', {
|
||||
|
||||
company: function(frm) {
|
||||
frm.trigger("toggle_display_account_head");
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
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() {
|
||||
this._super();
|
||||
|
||||
@ -41,6 +46,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
if (this.frm.doc.supplier && this.frm.doc.__islocal) {
|
||||
this.frm.trigger('supplier');
|
||||
}
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(doc) {
|
||||
@ -498,7 +505,7 @@ cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
|
||||
frappe.ui.form.on("Purchase Invoice", {
|
||||
setup: function(frm) {
|
||||
frm.custom_make_buttons = {
|
||||
'Purchase Invoice': 'Debit Note',
|
||||
'Purchase Invoice': 'Return / Debit Note',
|
||||
'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) {
|
||||
|
@ -426,32 +426,37 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
)
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project="_Test Project")
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
||||
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project=project.name)
|
||||
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
|
||||
existing_purchase_cost + 15000)
|
||||
|
||||
pi1 = make_purchase_invoice(qty=10, project="_Test Project")
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
|
||||
pi1 = make_purchase_invoice(qty=10, project=project.name)
|
||||
self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
|
||||
existing_purchase_cost + 15500)
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
|
||||
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
|
||||
|
||||
return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2,
|
||||
return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2,
|
||||
company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
|
||||
cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
|
||||
|
||||
@ -860,17 +865,17 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
})
|
||||
|
||||
pi = make_purchase_invoice(credit_to="Creditors - _TC" ,do_not_save=1)
|
||||
pi.items[0].project = item_project.project_name
|
||||
pi.project = project.project_name
|
||||
pi.items[0].project = item_project.name
|
||||
pi.project = project.name
|
||||
|
||||
pi.submit()
|
||||
|
||||
expected_values = {
|
||||
"Creditors - _TC": {
|
||||
"project": project.project_name
|
||||
"project": project.name
|
||||
},
|
||||
"_Test Account Cost for Goods Sold - _TC": {
|
||||
"project": item_project.project_name
|
||||
"project": item_project.name
|
||||
}
|
||||
}
|
||||
|
||||
@ -1026,7 +1031,7 @@ def make_purchase_invoice_against_cost_center(**args):
|
||||
pi.is_return = args.is_return
|
||||
pi.credit_to = args.return_against or "Creditors - _TC"
|
||||
pi.is_subcontracted = args.is_subcontracted or "No"
|
||||
if args.supplier_warehouse:
|
||||
if args.supplier_warehouse:
|
||||
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
|
||||
|
||||
pi.append("items", {
|
||||
|
@ -5,14 +5,17 @@
|
||||
cur_frm.pformat.print_heading = 'Invoice';
|
||||
|
||||
{% include 'erpnext/selling/sales_common.js' %};
|
||||
|
||||
|
||||
frappe.provide("erpnext.accounts");
|
||||
|
||||
|
||||
erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.extend({
|
||||
setup: function(doc) {
|
||||
this.setup_posting_date_time_check();
|
||||
this._super(doc);
|
||||
},
|
||||
company: function() {
|
||||
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||
},
|
||||
onload: function() {
|
||||
var me = this;
|
||||
this._super();
|
||||
@ -33,6 +36,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
me.frm.refresh_fields();
|
||||
}
|
||||
erpnext.queries.setup_warehouse_query(this.frm);
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||
},
|
||||
|
||||
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() {
|
||||
return {
|
||||
filters: {
|
||||
@ -592,7 +587,7 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
|
||||
frm.custom_make_buttons = {
|
||||
'Delivery Note': 'Delivery',
|
||||
'Sales Invoice': 'Sales Return',
|
||||
'Sales Invoice': 'Return / Credit Note',
|
||||
'Payment Request': 'Payment Request',
|
||||
'Payment Entry': 'Payment'
|
||||
},
|
||||
|
@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe, erpnext
|
||||
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 erpnext.accounts.party import get_party_account, get_due_date
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
@ -179,7 +179,10 @@ class SalesInvoice(SellingController):
|
||||
|
||||
# this sequence because outstanding may get -ve
|
||||
self.make_gl_entries()
|
||||
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
@ -261,10 +264,10 @@ class SalesInvoice(SellingController):
|
||||
self.update_stock_ledger()
|
||||
|
||||
self.make_gl_entries_on_cancel()
|
||||
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
|
||||
frappe.db.set(self, 'status', 'Cancelled')
|
||||
|
||||
if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction":
|
||||
@ -549,7 +552,12 @@ class SalesInvoice(SellingController):
|
||||
self.against_income_account = ','.join(against_acc)
|
||||
|
||||
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):
|
||||
# Don't auto set the posting date and time if invoice is amended
|
||||
@ -1694,6 +1702,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""",
|
||||
(company, mode_of_payment), as_dict=1)
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_dunning(source_name, target_doc=None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text, calculate_interest_and_amount
|
||||
|
@ -688,7 +688,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertTrue(gle)
|
||||
|
||||
def test_pos_gl_entry_with_perpetual_inventory(self):
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||
|
||||
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
|
||||
@ -745,7 +745,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(pos_return.get('payments')[0].amount, -1000)
|
||||
|
||||
def test_pos_change_amount(self):
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||
|
||||
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
|
||||
@ -1573,17 +1573,17 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
})
|
||||
|
||||
sales_invoice = create_sales_invoice(do_not_save=1)
|
||||
sales_invoice.items[0].project = item_project.project_name
|
||||
sales_invoice.project = project.project_name
|
||||
sales_invoice.items[0].project = item_project.name
|
||||
sales_invoice.project = project.name
|
||||
|
||||
sales_invoice.submit()
|
||||
|
||||
expected_values = {
|
||||
"Debtors - _TC": {
|
||||
"project": project.project_name
|
||||
"project": project.name
|
||||
},
|
||||
"Sales - _TC": {
|
||||
"project": item_project.project_name
|
||||
"project": item_project.name
|
||||
}
|
||||
}
|
||||
|
||||
@ -1841,7 +1841,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
|
||||
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
|
||||
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
|
||||
|
||||
|
||||
def test_einvoice_submission_without_irn(self):
|
||||
# init
|
||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
|
||||
@ -1857,27 +1857,10 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
# reset
|
||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
|
||||
frappe.flags.country = country
|
||||
|
||||
|
||||
def test_einvoice_json(self):
|
||||
from erpnext.regional.india.e_invoice.utils import make_einvoice
|
||||
|
||||
customer_gstin = '27AACCM7806M1Z3'
|
||||
customer_gstin_dtls = {
|
||||
'LegalName': '_Test Customer', 'TradeName': '_Test Customer', 'AddrLoc': '_Test City',
|
||||
'StateCode': '27', 'AddrPncd': '410038', 'AddrBno': '_Test Bldg',
|
||||
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
|
||||
}
|
||||
company_gstin = '27AAECE4835E1ZR'
|
||||
company_gstin_dtls = {
|
||||
'LegalName': '_Test Company', 'TradeName': '_Test Company', 'AddrLoc': '_Test City',
|
||||
'StateCode': '27', 'AddrPncd': '401108', 'AddrBno': '_Test Bldg',
|
||||
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
|
||||
}
|
||||
# set cache gstin details to avoid fetching details which will require connection to GSP servers
|
||||
frappe.local.gstin_cache = {}
|
||||
frappe.local.gstin_cache[customer_gstin] = customer_gstin_dtls
|
||||
frappe.local.gstin_cache[company_gstin] = company_gstin_dtls
|
||||
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
si.naming_series = 'INV-2020-.#####'
|
||||
si.items = []
|
||||
@ -1930,12 +1913,12 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
|
||||
self.assertEqual(value_details['IgstVal'], total_item_igst_value)
|
||||
|
||||
self.assertEqual(
|
||||
value_details['TotInvVal'],
|
||||
value_details['AssVal'] + value_details['CgstVal']
|
||||
+ value_details['SgstVal'] + value_details['IgstVal']
|
||||
calculated_invoice_value = \
|
||||
value_details['AssVal'] + value_details['CgstVal'] \
|
||||
+ value_details['SgstVal'] + value_details['IgstVal'] \
|
||||
+ value_details['OthChrg'] - value_details['Discount']
|
||||
)
|
||||
|
||||
self.assertTrue(value_details['TotInvVal'] - calculated_invoice_value < 0.1)
|
||||
|
||||
self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
|
||||
self.assertTrue(einvoice['EwbDtls'])
|
||||
|
@ -1,16 +1,18 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.ui.form.on('Shipping Rule', {
|
||||
refresh: function(frm) {
|
||||
frm.set_query("cost_center", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
}
|
||||
})
|
||||
frappe.provide('erpnext.accounts.dimensions');
|
||||
|
||||
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() {
|
||||
return {
|
||||
filters: {
|
||||
|
@ -446,7 +446,7 @@ class Subscription(Document):
|
||||
if not self.generate_invoice_at_period_start:
|
||||
return False
|
||||
|
||||
if self.is_new_subscription():
|
||||
if self.is_new_subscription() and getdate() >= getdate(self.current_invoice_start):
|
||||
return True
|
||||
|
||||
# Check invoice dates and make sure it doesn't have outstanding invoices
|
||||
|
@ -47,21 +47,22 @@ def get_data(filters):
|
||||
|
||||
for d in gl_entries:
|
||||
asset_data = assets_details.get(d.against_voucher)
|
||||
if not asset_data.get("accumulated_depreciation_amount"):
|
||||
asset_data.accumulated_depreciation_amount = d.debit
|
||||
else:
|
||||
asset_data.accumulated_depreciation_amount += d.debit
|
||||
if asset_data:
|
||||
if not asset_data.get("accumulated_depreciation_amount"):
|
||||
asset_data.accumulated_depreciation_amount = d.debit
|
||||
else:
|
||||
asset_data.accumulated_depreciation_amount += d.debit
|
||||
|
||||
row = frappe._dict(asset_data)
|
||||
row.update({
|
||||
"depreciation_amount": d.debit,
|
||||
"depreciation_date": d.posting_date,
|
||||
"amount_after_depreciation": (flt(row.gross_purchase_amount) -
|
||||
flt(row.accumulated_depreciation_amount)),
|
||||
"depreciation_entry": d.voucher_no
|
||||
})
|
||||
row = frappe._dict(asset_data)
|
||||
row.update({
|
||||
"depreciation_amount": d.debit,
|
||||
"depreciation_date": d.posting_date,
|
||||
"amount_after_depreciation": (flt(row.gross_purchase_amount) -
|
||||
flt(row.accumulated_depreciation_amount)),
|
||||
"depreciation_entry": d.voucher_no
|
||||
})
|
||||
|
||||
data.append(row)
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
|
@ -53,8 +53,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
|
||||
row = {
|
||||
'item_code': d.item_code,
|
||||
'item_name': item_record.item_name,
|
||||
'item_group': item_record.item_group,
|
||||
'item_name': item_record.item_name if item_record else d.item_name,
|
||||
'item_group': item_record.item_group if item_record else d.item_group,
|
||||
'description': d.description,
|
||||
'invoice': d.parent,
|
||||
'posting_date': d.posting_date,
|
||||
@ -316,6 +316,7 @@ def get_items(filters, additional_query_columns):
|
||||
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
|
||||
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
|
||||
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
|
||||
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`,
|
||||
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
|
||||
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
|
||||
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
|
||||
|
@ -59,23 +59,111 @@ def validate_filters(filters):
|
||||
|
||||
def get_columns(filters):
|
||||
return [
|
||||
_("Payment Document") + ":: 100",
|
||||
_("Payment Entry") + ":Dynamic Link/"+_("Payment Document")+":140",
|
||||
_("Party Type") + "::100",
|
||||
_("Party") + ":Dynamic Link/Party Type:140",
|
||||
_("Posting Date") + ":Date: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",
|
||||
_("Debit") + ":Currency:120",
|
||||
_("Credit") + ":Currency:120",
|
||||
_("Remarks") + "::150",
|
||||
_("Age") +":Int:40",
|
||||
"0-30:Currency:100",
|
||||
"30-60:Currency:100",
|
||||
"60-90:Currency:100",
|
||||
_("90-Above") + ":Currency:100",
|
||||
_("Delay in payment (Days)") + "::150"
|
||||
{
|
||||
"fieldname": "payment_document",
|
||||
"label": _("Payment Document Type"),
|
||||
"fieldtype": "Data",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_entry",
|
||||
"label": _("Payment Document"),
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "payment_document",
|
||||
"width": 160
|
||||
},
|
||||
{
|
||||
"fieldname": "party_type",
|
||||
"label": _("Party Type"),
|
||||
"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):
|
||||
|
@ -48,7 +48,7 @@ class CropCycle(Document):
|
||||
|
||||
def import_disease_tasks(self, disease, start_date):
|
||||
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):
|
||||
project = frappe.get_doc({
|
||||
|
@ -71,4 +71,4 @@ def check_task_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
|
||||
|
||||
frappe.provide("erpnext.asset");
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on('Asset', {
|
||||
onload: function(frm) {
|
||||
@ -32,13 +33,11 @@ frappe.ui.form.on('Asset', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("cost_center", function() {
|
||||
return {
|
||||
"filters": {
|
||||
"company": frm.doc.company,
|
||||
}
|
||||
};
|
||||
});
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
setup: function(frm) {
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on('Asset Value Adjustment', {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch('company', 'cost_center', 'cost_center');
|
||||
@ -13,11 +15,19 @@ frappe.ui.form.on('Asset Value Adjustment', {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
if(frm.is_new() && frm.doc.asset) {
|
||||
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) {
|
||||
frm.trigger("set_current_asset_value");
|
||||
},
|
||||
|
@ -13,17 +13,14 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g
|
||||
class AssetValueAdjustment(Document):
|
||||
def validate(self):
|
||||
self.validate_date()
|
||||
self.set_difference_amount()
|
||||
self.set_current_asset_value()
|
||||
self.set_difference_amount()
|
||||
|
||||
def on_submit(self):
|
||||
self.make_depreciation_entry()
|
||||
self.reschedule_depreciations(self.new_asset_value)
|
||||
|
||||
def on_cancel(self):
|
||||
if self.journal_entry:
|
||||
frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry))
|
||||
|
||||
self.reschedule_depreciations(self.current_asset_value)
|
||||
|
||||
def validate_date(self):
|
||||
|
@ -2,7 +2,7 @@
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.provide("erpnext.buying");
|
||||
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
{% include 'erpnext/public/js/controllers/buying.js' %};
|
||||
|
||||
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) {
|
||||
set_schedule_date(frm);
|
||||
if (!frm.doc.transaction_date){
|
||||
@ -39,6 +43,8 @@ frappe.ui.form.on("Purchase Order", {
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||
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({
|
||||
setup: function() {
|
||||
this.frm.custom_make_buttons = {
|
||||
'Purchase Receipt': 'Receipt',
|
||||
'Purchase Invoice': 'Invoice',
|
||||
'Purchase Receipt': 'Purchase Receipt',
|
||||
'Purchase Invoice': 'Purchase Invoice',
|
||||
'Stock Entry': 'Material to Supplier',
|
||||
'Payment Entry': 'Payment',
|
||||
}
|
||||
|
@ -75,62 +75,70 @@ frappe.query_reports["Purchase Analytics"] = {
|
||||
return Object.assign(options, {
|
||||
checkboxColumn: true,
|
||||
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;
|
||||
length = data.length;
|
||||
|
||||
var tree_type = frappe.query_report.filters[0].value;
|
||||
|
||||
if(tree_type == "Supplier" || tree_type == "Item") {
|
||||
row_values = data.slice(4,length-1).map(function (column) {
|
||||
return column.content;
|
||||
})
|
||||
}
|
||||
else {
|
||||
row_values = data.slice(3,length-1).map(function (column) {
|
||||
return column.content;
|
||||
})
|
||||
if (tree_type == "Supplier") {
|
||||
row_values = data
|
||||
.slice(4, length - 1)
|
||||
.map(function (column) {
|
||||
return column.content;
|
||||
});
|
||||
} else if (tree_type == "Item") {
|
||||
row_values = data
|
||||
.slice(5, length - 1)
|
||||
.map(function (column) {
|
||||
return column.content;
|
||||
});
|
||||
} else {
|
||||
row_values = data
|
||||
.slice(3, length - 1)
|
||||
.map(function (column) {
|
||||
return column.content;
|
||||
});
|
||||
}
|
||||
|
||||
entry = {
|
||||
'name':row_name,
|
||||
'values':row_values
|
||||
}
|
||||
entry = {
|
||||
name: row_name,
|
||||
values: row_values,
|
||||
};
|
||||
|
||||
let raw_data = frappe.query_report.chart.data;
|
||||
let new_datasets = raw_data.datasets;
|
||||
|
||||
var found = false;
|
||||
|
||||
for(var i=0; i < new_datasets.length;i++){
|
||||
if(new_datasets[i].name == row_name){
|
||||
found = true;
|
||||
new_datasets.splice(i,1);
|
||||
break;
|
||||
let element_found = new_datasets.some((element, index, array)=>{
|
||||
if(element.name == row_name){
|
||||
array.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if(!found){
|
||||
if (!element_found) {
|
||||
new_datasets.push(entry);
|
||||
}
|
||||
|
||||
let new_data = {
|
||||
labels: raw_data.labels,
|
||||
datasets: new_datasets
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
frappe.query_report.chart.update(new_data)
|
||||
},500)
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
frappe.query_report.chart.draw(true);
|
||||
}, 1000)
|
||||
datasets: new_datasets,
|
||||
};
|
||||
chart_options = {
|
||||
data: new_data,
|
||||
type: "line",
|
||||
};
|
||||
frappe.query_report.render_chart(chart_options);
|
||||
|
||||
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))
|
||||
|
||||
# update last purchsae rate
|
||||
if last_purchase_rate:
|
||||
frappe.db.sql("""update `tabItem` set last_purchase_rate = %s where name = %s""",
|
||||
(flt(last_purchase_rate), d.item_code))
|
||||
frappe.db.set_value('Item', d.item_code, 'last_purchase_rate', flt(last_purchase_rate))
|
||||
|
||||
|
||||
|
||||
|
||||
def validate_for_items(doc):
|
||||
items = []
|
||||
|
@ -336,7 +336,7 @@ class BuyingController(StockController):
|
||||
raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
|
||||
|
||||
consumed_qty = raw_material_data.get('qty', 0)
|
||||
consumed_serial_nos = raw_material_data.get('serial_nos', '')
|
||||
consumed_serial_nos = raw_material_data.get('serial_no', '')
|
||||
consumed_batch_nos = raw_material_data.get('batch_nos', '')
|
||||
|
||||
transferred_qty = raw_material.qty
|
||||
|
@ -493,6 +493,41 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
'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.validate_and_sanitize_search_inputs
|
||||
@ -620,6 +655,34 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(query, filters)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, filters):
|
||||
query = """
|
||||
select name
|
||||
from `tabHealthcare Service Unit`
|
||||
where
|
||||
is_group = 0
|
||||
and company = {company}
|
||||
and name like {txt}""".format(
|
||||
company = frappe.db.escape(filters.get('company')), txt = frappe.db.escape('%{0}%'.format(txt)))
|
||||
|
||||
if filters and filters.get('inpatient_record'):
|
||||
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
|
||||
service_unit = get_current_healthcare_service_unit(filters.get('inpatient_record'))
|
||||
|
||||
# if the patient is admitted, then appointments should be allowed against the admission service unit,
|
||||
# inspite of it being an Inpatient Occupancy service unit
|
||||
if service_unit:
|
||||
query += " and (allow_appointments = 1 or name = {service_unit})".format(service_unit = frappe.db.escape(service_unit))
|
||||
else:
|
||||
query += " and allow_appointments = 1"
|
||||
else:
|
||||
query += " and allow_appointments = 1"
|
||||
|
||||
return frappe.db.sql(query, filters)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
@ -191,7 +191,7 @@ class SellingController(StockController):
|
||||
for it in self.get("items"):
|
||||
if not it.item_code:
|
||||
continue
|
||||
|
||||
|
||||
last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"])
|
||||
last_purchase_rate_in_sales_uom = last_purchase_rate * (it.conversion_factor or 1)
|
||||
if flt(it.base_net_rate) < flt(last_purchase_rate_in_sales_uom):
|
||||
@ -233,7 +233,7 @@ class SellingController(StockController):
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||
'dn_detail': d.get("dn_detail"),
|
||||
'incoming_rate': p.incoming_rate
|
||||
'incoming_rate': p.get("incoming_rate")
|
||||
}))
|
||||
else:
|
||||
il.append(frappe._dict({
|
||||
@ -252,7 +252,7 @@ class SellingController(StockController):
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||
'dn_detail': d.get("dn_detail"),
|
||||
'incoming_rate': d.incoming_rate
|
||||
'incoming_rate': d.get("incoming_rate")
|
||||
}))
|
||||
return il
|
||||
|
||||
@ -391,7 +391,7 @@ class SellingController(StockController):
|
||||
})
|
||||
if item_row.warehouse:
|
||||
sle.dependant_sle_voucher_detail_no = item_row.name
|
||||
|
||||
|
||||
return sle
|
||||
|
||||
def set_po_nos(self, for_validate=False):
|
||||
|
@ -6,6 +6,7 @@ import unittest
|
||||
|
||||
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.stock.doctype.quality_inspection.test_quality_inspection import create_quality_inspection_parameter
|
||||
|
||||
from six import string_types
|
||||
|
||||
@ -56,6 +57,8 @@ def make_quality_inspection_template():
|
||||
|
||||
qc = frappe.new_doc("Quality Inspection Template")
|
||||
qc.quality_inspection_template_name = qc_template
|
||||
|
||||
create_quality_inspection_parameter("Moisture")
|
||||
qc.append('item_quality_inspection_parameter', {
|
||||
"specification": "Moisture",
|
||||
"value": "< 5%",
|
||||
|
@ -126,7 +126,7 @@ class Appointment(Document):
|
||||
add_assignemnt({
|
||||
'doctype': self.doctype,
|
||||
'name': self.name,
|
||||
'assign_to': existing_assignee
|
||||
'assign_to': [existing_assignee]
|
||||
})
|
||||
return
|
||||
if self._assign:
|
||||
@ -139,7 +139,7 @@ class Appointment(Document):
|
||||
add_assignemnt({
|
||||
'doctype': self.doctype,
|
||||
'name': self.name,
|
||||
'assign_to': agent
|
||||
'assign_to': [agent]
|
||||
})
|
||||
break
|
||||
|
||||
|
@ -8,12 +8,12 @@
|
||||
"is_mandatory": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2020-05-14 17:38:27.496696",
|
||||
"modified": "2021-01-21 15:28:52.483839",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Opportunity",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Opportunity",
|
||||
"show_full_form": 0,
|
||||
"show_full_form": 1,
|
||||
"title": "Create Opportunity",
|
||||
"validate_action": 1
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
frappe.ui.form.on('Fee Schedule', {
|
||||
setup: function(frm) {
|
||||
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');
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
frm.set_query('receivable_account', function(doc) {
|
||||
return {
|
||||
@ -50,6 +55,8 @@ frappe.ui.form.on('Fee Schedule', {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on('Fee Structure', {
|
||||
setup: function(frm) {
|
||||
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');
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
frm.set_query('academic_term', function() {
|
||||
return {
|
||||
@ -35,6 +41,8 @@ frappe.ui.form.on('Fee Structure', {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on("Fees", {
|
||||
setup: function(frm) {
|
||||
@ -9,15 +10,19 @@ frappe.ui.form.on("Fees", {
|
||||
frm.add_fetch("fee_structure", "cost_center", "cost_center");
|
||||
},
|
||||
|
||||
onload: function(frm){
|
||||
frm.set_query("academic_term",function(){
|
||||
company: function(frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
frm.set_query("academic_term", function() {
|
||||
return{
|
||||
"filters":{
|
||||
"filters": {
|
||||
"academic_year": (frm.doc.academic_year)
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.set_query("fee_structure",function(){
|
||||
frm.set_query("fee_structure", function() {
|
||||
return{
|
||||
"filters":{
|
||||
"academic_year": (frm.doc.academic_year)
|
||||
@ -45,6 +50,8 @@ frappe.ui.form.on("Fees", {
|
||||
if (!frm.doc.posting_date) {
|
||||
frm.doc.posting_date = frappe.datetime.get_today();
|
||||
}
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
@ -124,21 +124,24 @@ class ProgramEnrollment(Document):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_program_courses(doctype, txt, searchfield, start, page_len, filters):
|
||||
if filters.get('program'):
|
||||
return frappe.db.sql("""select course, course_name from `tabProgram Course`
|
||||
where parent = %(program)s and course like %(txt)s {match_cond}
|
||||
order by
|
||||
if(locate(%(_txt)s, course), locate(%(_txt)s, course), 99999),
|
||||
idx desc,
|
||||
`tabProgram Course`.course asc
|
||||
limit {start}, {page_len}""".format(
|
||||
match_cond=get_match_cond(doctype),
|
||||
start=start,
|
||||
page_len=page_len), {
|
||||
"txt": "%{0}%".format(txt),
|
||||
"_txt": txt.replace('%', ''),
|
||||
"program": filters['program']
|
||||
})
|
||||
if not filters.get('program'):
|
||||
frappe.msgprint(_("Please select a Program first."))
|
||||
return []
|
||||
|
||||
return frappe.db.sql("""select course, course_name from `tabProgram Course`
|
||||
where parent = %(program)s and course like %(txt)s {match_cond}
|
||||
order by
|
||||
if(locate(%(_txt)s, course), locate(%(_txt)s, course), 99999),
|
||||
idx desc,
|
||||
`tabProgram Course`.course asc
|
||||
limit {start}, {page_len}""".format(
|
||||
match_cond=get_match_cond(doctype),
|
||||
start=start,
|
||||
page_len=page_len), {
|
||||
"txt": "%{0}%".format(txt),
|
||||
"_txt": txt.replace('%', ''),
|
||||
"program": filters['program']
|
||||
})
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
|
@ -29,14 +29,11 @@ class PlaidConnector():
|
||||
response = self.client.Item.public_token.exchange(public_token)
|
||||
access_token = response["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"]
|
||||
token_request = {
|
||||
args = {
|
||||
"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)
|
||||
"language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
|
||||
"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:
|
||||
response = self.client.LinkToken.create(token_request)
|
||||
except InvalidRequestError:
|
||||
|
@ -12,9 +12,25 @@ frappe.ui.form.on('Plaid Settings', {
|
||||
|
||||
refresh: function (frm) {
|
||||
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);
|
||||
});
|
||||
|
||||
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.plaid_env = this.frm.doc.plaid_env;
|
||||
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();
|
||||
}
|
||||
|
||||
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() {
|
||||
const me = this;
|
||||
me.loadScript(me.plaidUrl)
|
||||
@ -78,8 +102,8 @@ erpnext.integrations.plaidLink = class plaidLink {
|
||||
}
|
||||
|
||||
onScriptError(error) {
|
||||
frappe.msgprint("There was an issue connecting to Plaid's authentication server");
|
||||
frappe.msgprint(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) {
|
||||
@ -107,4 +131,4 @@ erpnext.integrations.plaidLink = class plaidLink {
|
||||
});
|
||||
}, __("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)
|
||||
access_token = frappe.db.get_value("Bank", related_bank[0].bank, "plaid_access_token")
|
||||
account_id = related_bank[0].integration_id
|
||||
|
||||
else:
|
||||
access_token = frappe.db.get_value("Bank", bank, "plaid_access_token")
|
||||
account_id = None
|
||||
@ -228,13 +227,23 @@ def new_bank_transaction(transaction):
|
||||
|
||||
def automatic_synchronization():
|
||||
settings = frappe.get_doc("Plaid Settings", "Plaid Settings")
|
||||
|
||||
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.enqueue(
|
||||
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions",
|
||||
bank=plaid_account.bank,
|
||||
bank_account=plaid_account.name
|
||||
)
|
||||
@frappe.whitelist()
|
||||
def enqueue_synchronization():
|
||||
plaid_accounts = frappe.get_all("Bank Account",
|
||||
filters={"integration_id": ["!=", ""]},
|
||||
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 InvalidCurrency(frappe.ValidationError): pass
|
||||
class PartyDisabled(frappe.ValidationError):pass
|
||||
class InvalidAccountDimensionError(frappe.ValidationError): pass
|
||||
class MandatoryAccountDimensionError(frappe.ValidationError): pass
|
||||
|
@ -100,7 +100,6 @@ class ClinicalProcedure(Document):
|
||||
allow_start = self.set_actual_qty()
|
||||
if allow_start:
|
||||
self.db_set('status', 'In Progress')
|
||||
insert_clinical_procedure_to_medical_record(self)
|
||||
return 'success'
|
||||
return 'insufficient stock'
|
||||
|
||||
@ -247,21 +246,3 @@ def make_procedure(source_name, target_doc=None):
|
||||
}, target_doc, set_missing_values)
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
def insert_clinical_procedure_to_medical_record(doc):
|
||||
subject = frappe.bold(_("Clinical Procedure conducted: ")) + cstr(doc.procedure_template) + "<br>"
|
||||
if doc.practitioner:
|
||||
subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
|
||||
if subject and doc.notes:
|
||||
subject += '<br/>' + doc.notes
|
||||
|
||||
medical_record = frappe.new_doc('Patient Medical Record')
|
||||
medical_record.patient = doc.patient
|
||||
medical_record.subject = subject
|
||||
medical_record.status = 'Open'
|
||||
medical_record.communication_date = doc.start_date
|
||||
medical_record.reference_doctype = 'Clinical Procedure'
|
||||
medical_record.reference_name = doc.name
|
||||
medical_record.reference_owner = doc.owner
|
||||
medical_record.save(ignore_permissions=True)
|
||||
|
@ -17,6 +17,9 @@
|
||||
"enable_free_follow_ups",
|
||||
"max_visits",
|
||||
"valid_days",
|
||||
"inpatient_settings_section",
|
||||
"allow_discharge_despite_unbilled_services",
|
||||
"do_not_bill_inpatient_encounters",
|
||||
"healthcare_service_items",
|
||||
"inpatient_visit_charge_item",
|
||||
"op_consulting_charge_item",
|
||||
@ -302,11 +305,28 @@
|
||||
"fieldname": "enable_free_follow_ups",
|
||||
"fieldtype": "Check",
|
||||
"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,
|
||||
"links": [],
|
||||
"modified": "2020-07-08 15:17:21.543218",
|
||||
"modified": "2021-01-13 09:04:35.877700",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Healthcare Settings",
|
||||
|
@ -5,6 +5,7 @@ frappe.ui.form.on('Inpatient Medication Entry', {
|
||||
refresh: function(frm) {
|
||||
// Ignore cancellation of doctype on cancel all
|
||||
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', () => {
|
||||
return {
|
||||
|
@ -139,7 +139,6 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "Inpatient Medication Orders",
|
||||
"options": "Inpatient Medication Entry Detail",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@ -180,7 +179,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-03 13:22:37.820707",
|
||||
"modified": "2021-01-11 12:37:46.749659",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Inpatient Medication Entry",
|
||||
|
@ -15,8 +15,6 @@ class InpatientMedicationEntry(Document):
|
||||
self.validate_medication_orders()
|
||||
|
||||
def get_medication_orders(self):
|
||||
self.validate_datetime_filters()
|
||||
|
||||
# pull inpatient medication orders based on selected filters
|
||||
orders = get_pending_medication_orders(self)
|
||||
|
||||
@ -27,22 +25,6 @@ class InpatientMedicationEntry(Document):
|
||||
self.set('medication_orders', [])
|
||||
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):
|
||||
# Add medication orders in the child table
|
||||
self.set('medication_orders', [])
|
||||
@ -282,7 +264,7 @@ def get_filters(entry):
|
||||
|
||||
def get_current_healthcare_service_unit(inpatient_record):
|
||||
ip_record = frappe.get_doc('Inpatient Record', inpatient_record)
|
||||
if ip_record.inpatient_occupancies:
|
||||
if ip_record.status in ['Admitted', 'Discharge Scheduled'] and ip_record.inpatient_occupancies:
|
||||
return ip_record.inpatient_occupancies[-1].service_unit
|
||||
return
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe, json
|
||||
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.desk.reportview import get_match_cond
|
||||
|
||||
@ -113,6 +113,7 @@ def schedule_inpatient(args):
|
||||
inpatient_record.status = 'Admission Scheduled'
|
||||
inpatient_record.save(ignore_permissions = True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def schedule_discharge(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 Encounter', inpatient_record.discharge_encounter, 'inpatient_status', inpatient_record.status)
|
||||
|
||||
|
||||
def set_details_from_ip_order(inpatient_record, ip_order):
|
||||
for key in ip_order:
|
||||
inpatient_record.set(key, ip_order[key])
|
||||
|
||||
|
||||
def set_ip_child_records(inpatient_record, inpatient_record_child, encounter_child):
|
||||
for item in encounter_child:
|
||||
table = inpatient_record.append(inpatient_record_child)
|
||||
for df in table.meta.get('fields'):
|
||||
table.set(df.fieldname, item.get(df.fieldname))
|
||||
|
||||
|
||||
def check_out_inpatient(inpatient_record):
|
||||
if 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()
|
||||
frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
|
||||
|
||||
|
||||
def discharge_patient(inpatient_record):
|
||||
validate_invoiced_inpatient(inpatient_record)
|
||||
validate_inpatient_invoicing(inpatient_record)
|
||||
inpatient_record.discharge_date = today()
|
||||
inpatient_record.status = "Discharged"
|
||||
|
||||
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:
|
||||
service_unit_names = False
|
||||
for inpatient_occupancy in inpatient_record.inpatient_occupancies:
|
||||
if inpatient_occupancy.invoiced != 1:
|
||||
if not inpatient_occupancy.invoiced:
|
||||
if service_unit_names:
|
||||
service_unit_names += ", " + inpatient_occupancy.service_unit
|
||||
else:
|
||||
service_unit_names = inpatient_occupancy.service_unit
|
||||
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"]
|
||||
|
||||
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:
|
||||
pending_invoices = get_pending_doc(doc, doc_name_list, pending_invoices)
|
||||
|
||||
if pending_invoices:
|
||||
frappe.throw(_("Can not mark Inpatient Record Discharged, there are Unbilled Invoices {0}").format(", "
|
||||
.join(pending_invoices)), title=_('Unbilled Invoices'))
|
||||
return pending_invoices
|
||||
|
||||
|
||||
def get_pending_doc(doc, doc_name_list, pending_invoices):
|
||||
if doc_name_list:
|
||||
doc_ids = False
|
||||
for doc_name in doc_name_list:
|
||||
doc_link = get_link_to_form(doc, doc_name.name)
|
||||
if doc_ids:
|
||||
doc_ids += ", "+doc_name.name
|
||||
doc_ids += ", " + doc_link
|
||||
else:
|
||||
doc_ids = doc_name.name
|
||||
doc_ids = doc_link
|
||||
if doc_ids:
|
||||
pending_invoices.append(doc + " (" + doc_ids + ")")
|
||||
pending_invoices[doc] = doc_ids
|
||||
|
||||
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,
|
||||
'inpatient_record': inpatient_record.name, 'docstatus': 1, 'invoiced': 0})
|
||||
|
||||
|
||||
def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None):
|
||||
inpatient_record.admitted_datetime = check_in
|
||||
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_record', inpatient_record.name)
|
||||
|
||||
|
||||
def transfer_patient(inpatient_record, service_unit, check_in):
|
||||
item_line = inpatient_record.append('inpatient_occupancies', {})
|
||||
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")
|
||||
|
||||
|
||||
def patient_leave_service_unit(inpatient_record, check_out, leave_from):
|
||||
if 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")
|
||||
inpatient_record.save(ignore_permissions = True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
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.make_random import get_random
|
||||
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):
|
||||
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_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):
|
||||
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||
patient = create_patient()
|
||||
@ -63,6 +119,13 @@ def mark_invoiced_inpatient_occupancy(ip_record):
|
||||
inpatient_occupancy.invoiced = 1
|
||||
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):
|
||||
patient_obj = frappe.get_doc('Patient', patient)
|
||||
inpatient_record = frappe.new_doc('Inpatient Record')
|
||||
@ -78,11 +141,16 @@ def create_inpatient(patient):
|
||||
inpatient_record.scheduled_date = today()
|
||||
return inpatient_record
|
||||
|
||||
def get_healthcare_service_unit():
|
||||
service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
|
||||
|
||||
def get_healthcare_service_unit(unit_name=None):
|
||||
if not unit_name:
|
||||
service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
|
||||
else:
|
||||
service_unit = frappe.db.exists("Healthcare Service Unit", {"healthcare_service_unit_name": unit_name})
|
||||
|
||||
if not service_unit:
|
||||
service_unit = frappe.new_doc("Healthcare Service Unit")
|
||||
service_unit.healthcare_service_unit_name = "Test Service Unit Ip Occupancy"
|
||||
service_unit.healthcare_service_unit_name = unit_name or "Test Service Unit Ip Occupancy"
|
||||
service_unit.company = "_Test Company"
|
||||
service_unit.service_unit_type = get_service_unit_type()
|
||||
service_unit.inpatient_occupancy = 1
|
||||
@ -105,6 +173,7 @@ def get_healthcare_service_unit():
|
||||
return service_unit.name
|
||||
return service_unit
|
||||
|
||||
|
||||
def get_service_unit_type():
|
||||
service_unit_type = get_random("Healthcare Service Unit Type", filters={"inpatient_occupancy": 1})
|
||||
|
||||
@ -116,6 +185,7 @@ def get_service_unit_type():
|
||||
return service_unit_type.name
|
||||
return service_unit_type
|
||||
|
||||
|
||||
def create_patient():
|
||||
patient = frappe.db.exists('Patient', '_Test IPD Patient')
|
||||
if not patient:
|
||||
|
@ -359,6 +359,7 @@
|
||||
{
|
||||
"fieldname": "normal_test_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Normal Test Result",
|
||||
"options": "Normal Test Result",
|
||||
"print_hide": 1
|
||||
},
|
||||
@ -380,6 +381,7 @@
|
||||
{
|
||||
"fieldname": "sensitivity_test_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Sensitivity Test Result",
|
||||
"options": "Sensitivity Test Result",
|
||||
"print_hide": 1,
|
||||
"report_hide": 1
|
||||
@ -529,6 +531,7 @@
|
||||
{
|
||||
"fieldname": "descriptive_test_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Descriptive Test Result",
|
||||
"options": "Descriptive Test Result",
|
||||
"print_hide": 1,
|
||||
"report_hide": 1
|
||||
@ -549,13 +552,14 @@
|
||||
{
|
||||
"fieldname": "organism_test_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Organism Test Result",
|
||||
"options": "Organism Test Result",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-30 18:18:38.516215",
|
||||
"modified": "2020-11-30 11:04:17.195848",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Lab Test",
|
||||
|
@ -17,11 +17,9 @@ class LabTest(Document):
|
||||
self.validate_result_values()
|
||||
self.db_set('submitted_date', getdate())
|
||||
self.db_set('status', 'Completed')
|
||||
insert_lab_test_to_medical_record(self)
|
||||
|
||||
def on_cancel(self):
|
||||
self.db_set('status', 'Cancelled')
|
||||
delete_lab_test_from_medical_record(self)
|
||||
self.reload()
|
||||
|
||||
def on_update(self):
|
||||
@ -330,60 +328,6 @@ def get_employee_by_user_id(user_id):
|
||||
return frappe.get_doc('Employee', emp_id)
|
||||
return None
|
||||
|
||||
def insert_lab_test_to_medical_record(doc):
|
||||
table_row = False
|
||||
subject = cstr(doc.lab_test_name)
|
||||
if doc.practitioner:
|
||||
subject += frappe.bold(_('Healthcare Practitioner: '))+ doc.practitioner + '<br>'
|
||||
if doc.normal_test_items:
|
||||
item = doc.normal_test_items[0]
|
||||
comment = ''
|
||||
if item.lab_test_comment:
|
||||
comment = str(item.lab_test_comment)
|
||||
table_row = frappe.bold(_('Lab Test Conducted: ')) + item.lab_test_name
|
||||
|
||||
if item.lab_test_event:
|
||||
table_row += frappe.bold(_('Lab Test Event: ')) + item.lab_test_event
|
||||
|
||||
if item.result_value:
|
||||
table_row += ' ' + frappe.bold(_('Lab Test Result: ')) + item.result_value
|
||||
|
||||
if item.normal_range:
|
||||
table_row += ' ' + _('Normal Range: ') + item.normal_range
|
||||
table_row += ' ' + comment
|
||||
|
||||
elif doc.descriptive_test_items:
|
||||
item = doc.descriptive_test_items[0]
|
||||
|
||||
if item.lab_test_particulars and item.result_value:
|
||||
table_row = item.lab_test_particulars + ' ' + item.result_value
|
||||
|
||||
elif doc.sensitivity_test_items:
|
||||
item = doc.sensitivity_test_items[0]
|
||||
|
||||
if item.antibiotic and item.antibiotic_sensitivity:
|
||||
table_row = item.antibiotic + ' ' + item.antibiotic_sensitivity
|
||||
|
||||
if table_row:
|
||||
subject += '<br>' + table_row
|
||||
if doc.lab_test_comment:
|
||||
subject += '<br>' + cstr(doc.lab_test_comment)
|
||||
|
||||
medical_record = frappe.new_doc('Patient Medical Record')
|
||||
medical_record.patient = doc.patient
|
||||
medical_record.subject = subject
|
||||
medical_record.status = 'Open'
|
||||
medical_record.communication_date = doc.result_date
|
||||
medical_record.reference_doctype = 'Lab Test'
|
||||
medical_record.reference_name = doc.name
|
||||
medical_record.reference_owner = doc.owner
|
||||
medical_record.save(ignore_permissions = True)
|
||||
|
||||
def delete_lab_test_from_medical_record(self):
|
||||
medical_record_id = frappe.db.sql('select name from `tabPatient Medical Record` where reference_name=%s', (self.name))
|
||||
|
||||
if medical_record_id and medical_record_id[0][0]:
|
||||
frappe.delete_doc('Patient Medical Record', medical_record_id[0][0])
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_lab_test_prescribed(patient):
|
||||
|
@ -22,6 +22,7 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
filters: {'status': 'Active'}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('practitioner', function() {
|
||||
return {
|
||||
filters: {
|
||||
@ -29,16 +30,27 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.set_query('service_unit', function(){
|
||||
|
||||
frm.set_query('service_unit', function() {
|
||||
return {
|
||||
query: 'erpnext.controllers.queries.get_healthcare_service_units',
|
||||
filters: {
|
||||
'is_group': false,
|
||||
'allow_appointments': true,
|
||||
'company': frm.doc.company
|
||||
company: frm.doc.company,
|
||||
inpatient_record: frm.doc.inpatient_record
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('therapy_plan', function() {
|
||||
return {
|
||||
filters: {
|
||||
'patient': frm.doc.patient
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.trigger('set_therapy_type_filter');
|
||||
|
||||
if (frm.is_new()) {
|
||||
frm.page.set_primary_action(__('Check Availability'), function() {
|
||||
if (!frm.doc.patient) {
|
||||
@ -136,6 +148,24 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
}
|
||||
},
|
||||
|
||||
therapy_plan: function(frm) {
|
||||
frm.trigger('set_therapy_type_filter');
|
||||
},
|
||||
|
||||
set_therapy_type_filter: function(frm) {
|
||||
if (frm.doc.therapy_plan) {
|
||||
frm.call('get_therapy_types').then(r => {
|
||||
frm.set_query('therapy_type', function() {
|
||||
return {
|
||||
filters: {
|
||||
'name': ['in', r.message]
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
therapy_type: function(frm) {
|
||||
if (frm.doc.therapy_type) {
|
||||
frappe.db.get_value('Therapy Type', frm.doc.therapy_type, 'default_duration', (r) => {
|
||||
|
@ -23,9 +23,9 @@
|
||||
"procedure_template",
|
||||
"get_procedure_from_encounter",
|
||||
"procedure_prescription",
|
||||
"therapy_plan",
|
||||
"therapy_type",
|
||||
"get_prescribed_therapies",
|
||||
"therapy_plan",
|
||||
"practitioner",
|
||||
"practitioner_name",
|
||||
"department",
|
||||
@ -284,7 +284,7 @@
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.patient;",
|
||||
"depends_on": "eval:doc.patient && doc.therapy_plan;",
|
||||
"fieldname": "therapy_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Therapy",
|
||||
@ -292,17 +292,16 @@
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.patient && doc.__islocal;",
|
||||
"depends_on": "eval:doc.patient && doc.therapy_plan && doc.__islocal;",
|
||||
"fieldname": "get_prescribed_therapies",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Prescribed Therapies"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.patient && doc.therapy_type",
|
||||
"depends_on": "eval: doc.patient;",
|
||||
"fieldname": "therapy_plan",
|
||||
"fieldtype": "Link",
|
||||
"label": "Therapy Plan",
|
||||
"mandatory_depends_on": "eval: doc.patient && doc.therapy_type",
|
||||
"options": "Therapy Plan"
|
||||
},
|
||||
{
|
||||
@ -348,7 +347,7 @@
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-05-21 03:04:21.400893",
|
||||
"modified": "2020-12-16 13:16:58.578503",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Patient Appointment",
|
||||
|
@ -18,6 +18,7 @@ from erpnext.healthcare.utils import check_fee_validity, get_service_item_and_pr
|
||||
class PatientAppointment(Document):
|
||||
def validate(self):
|
||||
self.validate_overlaps()
|
||||
self.validate_service_unit()
|
||||
self.set_appointment_datetime()
|
||||
self.validate_customer_created()
|
||||
self.set_status()
|
||||
@ -68,6 +69,19 @@ class PatientAppointment(Document):
|
||||
overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4])
|
||||
frappe.throw(overlapping_details, title=_('Appointments Overlapping'))
|
||||
|
||||
def validate_service_unit(self):
|
||||
if self.inpatient_record and self.service_unit:
|
||||
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
|
||||
|
||||
is_inpatient_occupancy_unit = frappe.db.get_value('Healthcare Service Unit', self.service_unit,
|
||||
'inpatient_occupancy')
|
||||
service_unit = get_current_healthcare_service_unit(self.inpatient_record)
|
||||
if is_inpatient_occupancy_unit and service_unit != self.service_unit:
|
||||
msg = _('Patient {0} is not admitted in the service unit {1}').format(frappe.bold(self.patient), frappe.bold(self.service_unit)) + '<br>'
|
||||
msg += _('Appointment for service units with Inpatient Occupancy can only be created against the unit where patient has been admitted.')
|
||||
frappe.throw(msg, title=_('Invalid Healthcare Service Unit'))
|
||||
|
||||
|
||||
def set_appointment_datetime(self):
|
||||
self.appointment_datetime = "%s %s" % (self.appointment_date, self.appointment_time or "00:00:00")
|
||||
|
||||
@ -91,6 +105,17 @@ class PatientAppointment(Document):
|
||||
if fee_validity:
|
||||
frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till))
|
||||
|
||||
def get_therapy_types(self):
|
||||
if not self.therapy_plan:
|
||||
return
|
||||
|
||||
therapy_types = []
|
||||
doc = frappe.get_doc('Therapy Plan', self.therapy_plan)
|
||||
for entry in doc.therapy_plan_details:
|
||||
therapy_types.append(entry.therapy_type)
|
||||
|
||||
return therapy_types
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def check_payment_fields_reqd(patient):
|
||||
@ -145,7 +170,7 @@ def invoice_appointment(appointment_doc):
|
||||
sales_invoice.flags.ignore_mandatory = True
|
||||
sales_invoice.save(ignore_permissions=True)
|
||||
sales_invoice.submit()
|
||||
frappe.msgprint(_('Sales Invoice {0} created'.format(sales_invoice.name)), alert=True)
|
||||
frappe.msgprint(_('Sales Invoice {0} created').format(sales_invoice.name), alert=True)
|
||||
frappe.db.set_value('Patient Appointment', appointment_doc.name, 'invoiced', 1)
|
||||
frappe.db.set_value('Patient Appointment', appointment_doc.name, 'ref_sales_invoice', sales_invoice.name)
|
||||
|
||||
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||
import unittest
|
||||
import frappe
|
||||
from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter
|
||||
from frappe.utils import nowdate, add_days
|
||||
from frappe.utils import nowdate, add_days, now_datetime
|
||||
from frappe.utils.make_random import get_random
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
|
||||
@ -23,8 +23,10 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
self.assertEquals(appointment.status, 'Open')
|
||||
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2))
|
||||
self.assertEquals(appointment.status, 'Scheduled')
|
||||
create_encounter(appointment)
|
||||
encounter = create_encounter(appointment)
|
||||
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):
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
@ -76,6 +78,59 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
|
||||
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'status'), 'Cancelled')
|
||||
|
||||
def test_appointment_booking_for_admission_service_unit(self):
|
||||
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
|
||||
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \
|
||||
create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
|
||||
|
||||
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
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('Test Service Unit Ip Occupancy')
|
||||
admit_patient(ip_record, service_unit, now_datetime())
|
||||
|
||||
appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit)
|
||||
self.assertEqual(appointment.service_unit, service_unit)
|
||||
|
||||
# Discharge
|
||||
schedule_discharge(frappe.as_json({'patient': patient}))
|
||||
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
|
||||
mark_invoiced_inpatient_occupancy(ip_record1)
|
||||
discharge_patient(ip_record1)
|
||||
|
||||
def test_invalid_healthcare_service_unit_validation(self):
|
||||
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
|
||||
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \
|
||||
create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
|
||||
|
||||
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
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('Test Service Unit Ip Occupancy')
|
||||
admit_patient(ip_record, service_unit, now_datetime())
|
||||
|
||||
appointment_service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy for Appointment')
|
||||
appointment = create_appointment(patient, practitioner, nowdate(), service_unit=appointment_service_unit, save=0)
|
||||
self.assertRaises(frappe.exceptions.ValidationError, appointment.save)
|
||||
|
||||
# Discharge
|
||||
schedule_discharge(frappe.as_json({'patient': patient}))
|
||||
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
|
||||
mark_invoiced_inpatient_occupancy(ip_record1)
|
||||
discharge_patient(ip_record1)
|
||||
|
||||
|
||||
def create_healthcare_docs():
|
||||
patient = create_patient()
|
||||
@ -123,7 +178,7 @@ def create_encounter(appointment):
|
||||
encounter.submit()
|
||||
return encounter
|
||||
|
||||
def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0):
|
||||
def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0, service_unit=None, save=1):
|
||||
item = create_healthcare_service_items()
|
||||
frappe.db.set_value('Healthcare Settings', None, 'inpatient_visit_charge_item', item)
|
||||
frappe.db.set_value('Healthcare Settings', None, 'op_consulting_charge_item', item)
|
||||
@ -134,12 +189,15 @@ def create_appointment(patient, practitioner, appointment_date, invoice=0, proce
|
||||
appointment.appointment_date = appointment_date
|
||||
appointment.company = '_Test Company'
|
||||
appointment.duration = 15
|
||||
if service_unit:
|
||||
appointment.service_unit = service_unit
|
||||
if invoice:
|
||||
appointment.mode_of_payment = 'Cash'
|
||||
appointment.paid_amount = 500
|
||||
if procedure_template:
|
||||
appointment.procedure_template = create_clinical_procedure_template().get('name')
|
||||
appointment.save(ignore_permissions=True)
|
||||
if save:
|
||||
appointment.save(ignore_permissions=True)
|
||||
return appointment
|
||||
|
||||
def create_healthcare_service_items():
|
||||
@ -150,6 +208,7 @@ def create_healthcare_service_items():
|
||||
item.item_name = 'Consulting Charges'
|
||||
item.item_group = 'Services'
|
||||
item.is_stock_item = 0
|
||||
item.stock_uom = 'Nos'
|
||||
item.save()
|
||||
return item.name
|
||||
|
||||
|
@ -210,7 +210,7 @@
|
||||
{
|
||||
"fieldname": "drug_prescription",
|
||||
"fieldtype": "Table",
|
||||
"label": "Items",
|
||||
"label": "Drug Prescription",
|
||||
"options": "Drug Prescription"
|
||||
},
|
||||
{
|
||||
@ -328,7 +328,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-16 21:00:08.644531",
|
||||
"modified": "2020-11-30 10:39:00.783119",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Patient Encounter",
|
||||
|
@ -17,10 +17,6 @@ class PatientEncounter(Document):
|
||||
def on_update(self):
|
||||
if self.appointment:
|
||||
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed')
|
||||
update_encounter_medical_record(self)
|
||||
|
||||
def after_insert(self):
|
||||
insert_encounter_to_medical_record(self)
|
||||
|
||||
def on_submit(self):
|
||||
if self.therapies:
|
||||
@ -33,8 +29,6 @@ class PatientEncounter(Document):
|
||||
if self.inpatient_record and self.drug_prescription:
|
||||
delete_ip_medication_order(self)
|
||||
|
||||
delete_medical_record(self)
|
||||
|
||||
def set_title(self):
|
||||
self.title = _('{0} with {1}').format(self.patient_name or self.patient,
|
||||
self.practitioner_name or self.practitioner)[:100]
|
||||
@ -102,61 +96,7 @@ def create_therapy_plan(encounter):
|
||||
frappe.msgprint(_('Therapy Plan {0} created successfully.').format(frappe.bold(doc.name)), alert=True)
|
||||
|
||||
|
||||
def insert_encounter_to_medical_record(doc):
|
||||
subject = set_subject_field(doc)
|
||||
medical_record = frappe.new_doc('Patient Medical Record')
|
||||
medical_record.patient = doc.patient
|
||||
medical_record.subject = subject
|
||||
medical_record.status = 'Open'
|
||||
medical_record.communication_date = doc.encounter_date
|
||||
medical_record.reference_doctype = 'Patient Encounter'
|
||||
medical_record.reference_name = doc.name
|
||||
medical_record.reference_owner = doc.owner
|
||||
medical_record.save(ignore_permissions=True)
|
||||
|
||||
|
||||
def update_encounter_medical_record(encounter):
|
||||
medical_record_id = frappe.db.exists('Patient Medical Record', {'reference_name': encounter.name})
|
||||
|
||||
if medical_record_id and medical_record_id[0][0]:
|
||||
subject = set_subject_field(encounter)
|
||||
frappe.db.set_value('Patient Medical Record', medical_record_id[0][0], 'subject', subject)
|
||||
else:
|
||||
insert_encounter_to_medical_record(encounter)
|
||||
|
||||
|
||||
def delete_medical_record(encounter):
|
||||
record = frappe.db.exists('Patient Medical Record', {'reference_name', encounter.name})
|
||||
if record:
|
||||
frappe.delete_doc('Patient Medical Record', record, force=1)
|
||||
|
||||
def delete_ip_medication_order(encounter):
|
||||
record = frappe.db.exists('Inpatient Medication Order', {'patient_encounter': encounter.name})
|
||||
if record:
|
||||
frappe.delete_doc('Inpatient Medication Order', record, force=1)
|
||||
|
||||
|
||||
def set_subject_field(encounter):
|
||||
subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '<br>'
|
||||
if encounter.symptoms:
|
||||
subject += frappe.bold(_('Symptoms: ')) + '<br>'
|
||||
for entry in encounter.symptoms:
|
||||
subject += cstr(entry.complaint) + '<br>'
|
||||
else:
|
||||
subject += frappe.bold(_('No Symptoms')) + '<br>'
|
||||
|
||||
if encounter.diagnosis:
|
||||
subject += frappe.bold(_('Diagnosis: ')) + '<br>'
|
||||
for entry in encounter.diagnosis:
|
||||
subject += cstr(entry.diagnosis) + '<br>'
|
||||
else:
|
||||
subject += frappe.bold(_('No Diagnosis')) + '<br>'
|
||||
|
||||
if encounter.drug_prescription:
|
||||
subject += '<br>' + _('Drug(s) Prescribed.')
|
||||
if encounter.lab_test_prescription:
|
||||
subject += '<br>' + _('Test(s) Prescribed.')
|
||||
if encounter.procedure_prescription:
|
||||
subject += '<br>' + _('Procedure(s) Prescribed.')
|
||||
|
||||
return subject
|
||||
frappe.delete_doc('Inpatient Medication Order', record, force=1)
|
@ -0,0 +1,55 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-11-25 13:40:23.054469",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"document_type",
|
||||
"date_fieldname",
|
||||
"add_edit_fields",
|
||||
"selected_fields"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Document Type",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "selected_fields",
|
||||
"fieldtype": "Code",
|
||||
"label": "Selected Fields",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "add_edit_fields",
|
||||
"fieldtype": "Button",
|
||||
"in_list_view": 1,
|
||||
"label": "Add / Edit Fields"
|
||||
},
|
||||
{
|
||||
"fieldname": "date_fieldname",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Date Fieldname",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-30 13:54:37.474671",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Patient History Custom Document Type",
|
||||
"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 PatientHistoryCustomDocumentType(Document):
|
||||
pass
|
@ -0,0 +1,133 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Patient History Settings', {
|
||||
refresh: function(frm) {
|
||||
frm.set_query('document_type', 'custom_doctypes', () => {
|
||||
return {
|
||||
filters: {
|
||||
custom: 1,
|
||||
is_submittable: 1,
|
||||
module: 'Healthcare',
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
field_selector: function(frm, doc, standard=1) {
|
||||
let document_fields = [];
|
||||
if (doc.selected_fields)
|
||||
document_fields = (JSON.parse(doc.selected_fields)).map(f => f.fieldname);
|
||||
|
||||
frm.call({
|
||||
method: 'get_doctype_fields',
|
||||
doc: frm.doc,
|
||||
args: {
|
||||
document_type: doc.document_type,
|
||||
fields: document_fields
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
let doctype = 'Patient History Custom Document Type';
|
||||
if (standard)
|
||||
doctype = 'Patient History Standard Document Type';
|
||||
|
||||
frm.events.show_field_selector_dialog(frm, doc, doctype, r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
show_field_selector_dialog: function(frm, doc, doctype, doc_fields) {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __('{0} Fields', [__(doc.document_type)]),
|
||||
fields: [
|
||||
{
|
||||
label: __('Select Fields'),
|
||||
fieldtype: 'MultiCheck',
|
||||
fieldname: 'fields',
|
||||
options: doc_fields,
|
||||
columns: 2
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
d.$body.prepend(`
|
||||
<div class="columns-search">
|
||||
<input type="text" placeholder="${__('Search')}" data-element="search" class="form-control input-xs">
|
||||
</div>`
|
||||
);
|
||||
|
||||
frappe.utils.setup_search(d.$body, '.unit-checkbox', '.label-area');
|
||||
|
||||
d.set_primary_action(__('Save'), () => {
|
||||
let values = d.get_values().fields;
|
||||
|
||||
let selected_fields = [];
|
||||
|
||||
frappe.model.with_doctype(doc.document_type, function() {
|
||||
for (let idx in values) {
|
||||
let value = values[idx];
|
||||
|
||||
let field = frappe.get_meta(doc.document_type).fields.filter((df) => df.fieldname == value)[0];
|
||||
if (field) {
|
||||
selected_fields.push({
|
||||
label: field.label,
|
||||
fieldname: field.fieldname,
|
||||
fieldtype: field.fieldtype
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
d.refresh();
|
||||
frappe.model.set_value(doctype, doc.name, 'selected_fields', JSON.stringify(selected_fields));
|
||||
});
|
||||
|
||||
d.hide();
|
||||
});
|
||||
|
||||
d.show();
|
||||
},
|
||||
|
||||
get_date_field_for_dt: function(frm, row) {
|
||||
frm.call({
|
||||
method: 'get_date_field_for_dt',
|
||||
doc: frm.doc,
|
||||
args: {
|
||||
document_type: row.document_type
|
||||
},
|
||||
callback: function(data) {
|
||||
if (data.message) {
|
||||
frappe.model.set_value('Patient History Custom Document Type',
|
||||
row.name, 'date_fieldname', data.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Patient History Custom Document Type', {
|
||||
document_type: function(frm, cdt, cdn) {
|
||||
let row = locals[cdt][cdn];
|
||||
if (row.document_type) {
|
||||
frm.events.get_date_field_for_dt(frm, row);
|
||||
}
|
||||
},
|
||||
|
||||
add_edit_fields: function(frm, cdt, cdn) {
|
||||
let row = locals[cdt][cdn];
|
||||
if (row.document_type) {
|
||||
frm.events.field_selector(frm, row, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Patient History Standard Document Type', {
|
||||
add_edit_fields: function(frm, cdt, cdn) {
|
||||
let row = locals[cdt][cdn];
|
||||
if (row.document_type) {
|
||||
frm.events.field_selector(frm, row);
|
||||
}
|
||||
}
|
||||
});
|
@ -0,0 +1,55 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-11-25 13:41:37.675518",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"standard_doctypes",
|
||||
"section_break_2",
|
||||
"custom_doctypes"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "custom_doctypes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Custom Document Types",
|
||||
"options": "Patient History Custom Document Type"
|
||||
},
|
||||
{
|
||||
"fieldname": "standard_doctypes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Standard Document Types",
|
||||
"options": "Patient History Standard Document Type",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-25 13:43:38.511771",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Patient History Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
# -*- 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
|
||||
import json
|
||||
from frappe import _
|
||||
from frappe.utils import cstr, cint
|
||||
from frappe.model.document import Document
|
||||
from erpnext.healthcare.page.patient_history.patient_history import get_patient_history_doctypes
|
||||
|
||||
class PatientHistorySettings(Document):
|
||||
def validate(self):
|
||||
self.validate_submittable_doctypes()
|
||||
self.validate_date_fieldnames()
|
||||
|
||||
def validate_submittable_doctypes(self):
|
||||
for entry in self.custom_doctypes:
|
||||
if not cint(frappe.db.get_value('DocType', entry.document_type, 'is_submittable')):
|
||||
msg = _('Row #{0}: Document Type {1} is not submittable. ').format(
|
||||
entry.idx, frappe.bold(entry.document_type))
|
||||
msg += _('Patient Medical Record can only be created for submittable document types.')
|
||||
frappe.throw(msg)
|
||||
|
||||
def validate_date_fieldnames(self):
|
||||
for entry in self.custom_doctypes:
|
||||
field = frappe.get_meta(entry.document_type).get_field(entry.date_fieldname)
|
||||
if not field:
|
||||
frappe.throw(_('Row #{0}: No such Field named {1} found in the Document Type {2}.').format(
|
||||
entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type)))
|
||||
|
||||
if field.fieldtype not in ['Date', 'Datetime']:
|
||||
frappe.throw(_('Row #{0}: Field {1} in Document Type {2} is not a Date / Datetime field.').format(
|
||||
entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type)))
|
||||
|
||||
def get_doctype_fields(self, document_type, fields):
|
||||
multicheck_fields = []
|
||||
doc_fields = frappe.get_meta(document_type).fields
|
||||
|
||||
for field in doc_fields:
|
||||
if field.fieldtype not in frappe.model.no_value_fields or \
|
||||
field.fieldtype in frappe.model.table_fields and not field.hidden:
|
||||
multicheck_fields.append({
|
||||
'label': field.label,
|
||||
'value': field.fieldname,
|
||||
'checked': 1 if field.fieldname in fields else 0
|
||||
})
|
||||
|
||||
return multicheck_fields
|
||||
|
||||
def get_date_field_for_dt(self, document_type):
|
||||
meta = frappe.get_meta(document_type)
|
||||
date_fields = meta.get('fields', {
|
||||
'fieldtype': ['in', ['Date', 'Datetime']]
|
||||
})
|
||||
|
||||
if date_fields:
|
||||
return date_fields[0].get('fieldname')
|
||||
|
||||
def create_medical_record(doc, method=None):
|
||||
medical_record_required = validate_medical_record_required(doc)
|
||||
if not medical_record_required:
|
||||
return
|
||||
|
||||
if frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name }):
|
||||
return
|
||||
|
||||
subject = set_subject_field(doc)
|
||||
date_field = get_date_field(doc.doctype)
|
||||
medical_record = frappe.new_doc('Patient Medical Record')
|
||||
medical_record.patient = doc.patient
|
||||
medical_record.subject = subject
|
||||
medical_record.status = 'Open'
|
||||
medical_record.communication_date = doc.get(date_field)
|
||||
medical_record.reference_doctype = doc.doctype
|
||||
medical_record.reference_name = doc.name
|
||||
medical_record.reference_owner = doc.owner
|
||||
medical_record.save(ignore_permissions=True)
|
||||
|
||||
|
||||
def update_medical_record(doc, method=None):
|
||||
medical_record_required = validate_medical_record_required(doc)
|
||||
if not medical_record_required:
|
||||
return
|
||||
|
||||
medical_record_id = frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name })
|
||||
|
||||
if medical_record_id:
|
||||
subject = set_subject_field(doc)
|
||||
frappe.db.set_value('Patient Medical Record', medical_record_id[0][0], 'subject', subject)
|
||||
else:
|
||||
create_medical_record(doc)
|
||||
|
||||
|
||||
def delete_medical_record(doc, method=None):
|
||||
medical_record_required = validate_medical_record_required(doc)
|
||||
if not medical_record_required:
|
||||
return
|
||||
|
||||
record = frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name })
|
||||
if record:
|
||||
frappe.delete_doc('Patient Medical Record', record, force=1)
|
||||
|
||||
|
||||
def set_subject_field(doc):
|
||||
from frappe.utils.formatters import format_value
|
||||
|
||||
meta = frappe.get_meta(doc.doctype)
|
||||
subject = ''
|
||||
patient_history_fields = get_patient_history_fields(doc)
|
||||
|
||||
for entry in patient_history_fields:
|
||||
fieldname = entry.get('fieldname')
|
||||
if entry.get('fieldtype') == 'Table' and doc.get(fieldname):
|
||||
formatted_value = get_formatted_value_for_table_field(doc.get(fieldname), meta.get_field(fieldname))
|
||||
subject += frappe.bold(_(entry.get('label')) + ': ') + '<br>' + cstr(formatted_value) + '<br>'
|
||||
|
||||
else:
|
||||
if doc.get(fieldname):
|
||||
formatted_value = format_value(doc.get(fieldname), meta.get_field(fieldname), doc)
|
||||
subject += frappe.bold(_(entry.get('label')) + ': ') + cstr(formatted_value) + '<br>'
|
||||
|
||||
return subject
|
||||
|
||||
|
||||
def get_date_field(doctype):
|
||||
dt = get_patient_history_config_dt(doctype)
|
||||
|
||||
return frappe.db.get_value(dt, { 'document_type': doctype }, 'date_fieldname')
|
||||
|
||||
|
||||
def get_patient_history_fields(doc):
|
||||
dt = get_patient_history_config_dt(doc.doctype)
|
||||
patient_history_fields = frappe.db.get_value(dt, { 'document_type': doc.doctype }, 'selected_fields')
|
||||
|
||||
if patient_history_fields:
|
||||
return json.loads(patient_history_fields)
|
||||
|
||||
|
||||
def get_formatted_value_for_table_field(items, df):
|
||||
child_meta = frappe.get_meta(df.options)
|
||||
|
||||
table_head = ''
|
||||
table_row = ''
|
||||
html = ''
|
||||
create_head = True
|
||||
for item in items:
|
||||
table_row += '<tr>'
|
||||
for cdf in child_meta.fields:
|
||||
if cdf.in_list_view:
|
||||
if create_head:
|
||||
table_head += '<td>' + cdf.label + '</td>'
|
||||
if item.get(cdf.fieldname):
|
||||
table_row += '<td>' + str(item.get(cdf.fieldname)) + '</td>'
|
||||
else:
|
||||
table_row += '<td></td>'
|
||||
create_head = False
|
||||
table_row += '</tr>'
|
||||
|
||||
html += "<table class='table table-condensed table-bordered'>" + table_head + table_row + "</table>"
|
||||
|
||||
return html
|
||||
|
||||
|
||||
def get_patient_history_config_dt(doctype):
|
||||
if frappe.db.get_value('DocType', doctype, 'custom'):
|
||||
return 'Patient History Custom Document Type'
|
||||
else:
|
||||
return 'Patient History Standard Document Type'
|
||||
|
||||
|
||||
def validate_medical_record_required(doc):
|
||||
if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard \
|
||||
or get_module(doc) != 'Healthcare':
|
||||
return False
|
||||
|
||||
if doc.doctype not in get_patient_history_doctypes():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_module(doc):
|
||||
module = doc.meta.module
|
||||
if not module:
|
||||
module = frappe.db.get_value('DocType', doc.doctype, 'module')
|
||||
|
||||
return module
|
@ -0,0 +1,104 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
import json
|
||||
from frappe.utils import getdate
|
||||
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_patient
|
||||
|
||||
class TestPatientHistorySettings(unittest.TestCase):
|
||||
def setUp(self):
|
||||
dt = create_custom_doctype()
|
||||
settings = frappe.get_single("Patient History Settings")
|
||||
settings.append("custom_doctypes", {
|
||||
"document_type": dt.name,
|
||||
"date_fieldname": "date",
|
||||
"selected_fields": json.dumps([{
|
||||
"label": "Date",
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date"
|
||||
},
|
||||
{
|
||||
"label": "Rating",
|
||||
"fieldname": "rating",
|
||||
"fieldtype": "Rating"
|
||||
},
|
||||
{
|
||||
"label": "Feedback",
|
||||
"fieldname": "feedback",
|
||||
"fieldtype": "Small Text"
|
||||
}])
|
||||
})
|
||||
settings.save()
|
||||
|
||||
def test_custom_doctype_medical_record(self):
|
||||
# tests for medical record creation of standard doctypes in test_patient_medical_record.py
|
||||
patient = create_patient()
|
||||
doc = create_doc(patient)
|
||||
|
||||
# check for medical record
|
||||
medical_rec = frappe.db.exists("Patient Medical Record", {"status": "Open", "reference_name": doc.name})
|
||||
self.assertTrue(medical_rec)
|
||||
|
||||
medical_rec = frappe.get_doc("Patient Medical Record", medical_rec)
|
||||
expected_subject = "<b>Date: </b>{0}<br><b>Rating: </b>3<br><b>Feedback: </b>Test Patient History Settings<br>".format(
|
||||
frappe.utils.format_date(getdate()))
|
||||
self.assertEqual(medical_rec.subject, expected_subject)
|
||||
self.assertEqual(medical_rec.patient, patient)
|
||||
self.assertEqual(medical_rec.communication_date, getdate())
|
||||
|
||||
|
||||
def create_custom_doctype():
|
||||
if not frappe.db.exists("DocType", "Test Patient Feedback"):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "DocType",
|
||||
"module": "Healthcare",
|
||||
"custom": 1,
|
||||
"is_submittable": 1,
|
||||
"fields": [{
|
||||
"label": "Date",
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date"
|
||||
},
|
||||
{
|
||||
"label": "Patient",
|
||||
"fieldname": "patient",
|
||||
"fieldtype": "Link",
|
||||
"options": "Patient"
|
||||
},
|
||||
{
|
||||
"label": "Rating",
|
||||
"fieldname": "rating",
|
||||
"fieldtype": "Rating"
|
||||
},
|
||||
{
|
||||
"label": "Feedback",
|
||||
"fieldname": "feedback",
|
||||
"fieldtype": "Small Text"
|
||||
}],
|
||||
"permissions": [{
|
||||
"role": "System Manager",
|
||||
"read": 1
|
||||
}],
|
||||
"name": "Test Patient Feedback",
|
||||
})
|
||||
doc.insert()
|
||||
return doc
|
||||
else:
|
||||
return frappe.get_doc("DocType", "Test Patient Feedback")
|
||||
|
||||
|
||||
def create_doc(patient):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Test Patient Feedback",
|
||||
"patient": patient,
|
||||
"date": getdate(),
|
||||
"rating": 3,
|
||||
"feedback": "Test Patient History Settings"
|
||||
}).insert()
|
||||
doc.submit()
|
||||
|
||||
return doc
|
@ -0,0 +1,57 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-11-25 13:39:36.014814",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"document_type",
|
||||
"date_fieldname",
|
||||
"add_edit_fields",
|
||||
"selected_fields"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Document Type",
|
||||
"options": "DocType",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "selected_fields",
|
||||
"fieldtype": "Code",
|
||||
"label": "Selected Fields",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "add_edit_fields",
|
||||
"fieldtype": "Button",
|
||||
"in_list_view": 1,
|
||||
"label": "Add / Edit Fields"
|
||||
},
|
||||
{
|
||||
"fieldname": "date_fieldname",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Date Fieldname",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-30 13:54:56.773325",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Patient History Standard Document Type",
|
||||
"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 PatientHistoryStandardDocumentType(Document):
|
||||
pass
|
@ -18,6 +18,7 @@ class TestPatientMedicalRecord(unittest.TestCase):
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
appointment = create_appointment(patient, practitioner, nowdate(), invoice=1)
|
||||
encounter = create_encounter(appointment)
|
||||
|
||||
# check for encounter
|
||||
medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': encounter.name})
|
||||
self.assertTrue(medical_rec)
|
||||
|
@ -5,10 +5,10 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
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_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):
|
||||
def test_creation_on_encounter_submission(self):
|
||||
@ -28,6 +28,15 @@ class TestTherapyPlan(unittest.TestCase):
|
||||
frappe.get_doc(session).submit()
|
||||
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):
|
||||
patient = create_patient()
|
||||
template = create_therapy_plan_template()
|
||||
|
@ -47,7 +47,7 @@ class TherapyPlan(Document):
|
||||
|
||||
|
||||
@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_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.rate = therapy_type.rate
|
||||
therapy_session.exercises = therapy_type.exercises
|
||||
therapy_session.appointment = appointment
|
||||
|
||||
if frappe.flags.in_test:
|
||||
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) {
|
||||
|
@ -41,9 +41,15 @@ class TherapySession(Document):
|
||||
|
||||
def on_submit(self):
|
||||
self.update_sessions_count_in_therapy_plan()
|
||||
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):
|
||||
if self.appointment:
|
||||
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open')
|
||||
|
||||
self.update_sessions_count_in_therapy_plan(on_cancel=True)
|
||||
|
||||
def update_sessions_count_in_therapy_plan(self, on_cancel=False):
|
||||
@ -135,23 +141,3 @@ def get_therapy_item(therapy, item):
|
||||
item.reference_dt = 'Therapy Session'
|
||||
item.reference_dn = therapy.name
|
||||
return item
|
||||
|
||||
|
||||
def insert_session_medical_record(doc):
|
||||
subject = frappe.bold(_('Therapy: ')) + cstr(doc.therapy_type) + '<br>'
|
||||
if doc.therapy_plan:
|
||||
subject += frappe.bold(_('Therapy Plan: ')) + cstr(doc.therapy_plan) + '<br>'
|
||||
if doc.practitioner:
|
||||
subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
|
||||
subject += frappe.bold(_('Total Counts Targeted: ')) + cstr(doc.total_counts_targeted) + '<br>'
|
||||
subject += frappe.bold(_('Total Counts Completed: ')) + cstr(doc.total_counts_completed) + '<br>'
|
||||
|
||||
medical_record = frappe.new_doc('Patient Medical Record')
|
||||
medical_record.patient = doc.patient
|
||||
medical_record.subject = subject
|
||||
medical_record.status = 'Open'
|
||||
medical_record.communication_date = doc.start_date
|
||||
medical_record.reference_doctype = 'Therapy Session'
|
||||
medical_record.reference_name = doc.name
|
||||
medical_record.reference_owner = doc.owner
|
||||
medical_record.save(ignore_permissions=True)
|
@ -12,47 +12,7 @@ class VitalSigns(Document):
|
||||
def validate(self):
|
||||
self.set_title()
|
||||
|
||||
def on_submit(self):
|
||||
insert_vital_signs_to_medical_record(self)
|
||||
|
||||
def on_cancel(self):
|
||||
delete_vital_signs_from_medical_record(self)
|
||||
|
||||
def set_title(self):
|
||||
self.title = _('{0} on {1}').format(self.patient_name or self.patient,
|
||||
frappe.utils.format_date(self.signs_date))[:100]
|
||||
|
||||
def insert_vital_signs_to_medical_record(doc):
|
||||
subject = set_subject_field(doc)
|
||||
medical_record = frappe.new_doc('Patient Medical Record')
|
||||
medical_record.patient = doc.patient
|
||||
medical_record.subject = subject
|
||||
medical_record.status = 'Open'
|
||||
medical_record.communication_date = doc.signs_date
|
||||
medical_record.reference_doctype = 'Vital Signs'
|
||||
medical_record.reference_name = doc.name
|
||||
medical_record.reference_owner = doc.owner
|
||||
medical_record.flags.ignore_mandatory = True
|
||||
medical_record.save(ignore_permissions=True)
|
||||
|
||||
def delete_vital_signs_from_medical_record(doc):
|
||||
medical_record = frappe.db.get_value('Patient Medical Record', {'reference_name': doc.name})
|
||||
if medical_record:
|
||||
frappe.delete_doc('Patient Medical Record', medical_record)
|
||||
|
||||
def set_subject_field(doc):
|
||||
subject = ''
|
||||
if doc.temperature:
|
||||
subject += frappe.bold(_('Temperature: ')) + cstr(doc.temperature) + '<br>'
|
||||
if doc.pulse:
|
||||
subject += frappe.bold(_('Pulse: ')) + cstr(doc.pulse) + '<br>'
|
||||
if doc.respiratory_rate:
|
||||
subject += frappe.bold(_('Respiratory Rate: ')) + cstr(doc.respiratory_rate) + '<br>'
|
||||
if doc.bp:
|
||||
subject += frappe.bold(_('BP: ')) + cstr(doc.bp) + '<br>'
|
||||
if doc.bmi:
|
||||
subject += frappe.bold(_('BMI: ')) + cstr(doc.bmi) + '<br>'
|
||||
if doc.nutrition_note:
|
||||
subject += frappe.bold(_('Note: ')) + cstr(doc.nutrition_note) + '<br>'
|
||||
|
||||
return subject
|
||||
|
@ -109,6 +109,11 @@
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.patient-history-filter {
|
||||
margin-left: 35px;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
#page-medical_record .plot-wrapper {
|
||||
padding: 20px 15px;
|
||||
border-bottom: 1px solid #d1d8dd;
|
||||
|
@ -1,6 +1,5 @@
|
||||
<div class="col-sm-12">
|
||||
<div class="col-sm-3">
|
||||
<p class="text-center">{%= __("Select Patient") %}</p>
|
||||
<p class="patient" style="margin: auto; max-width: 300px; margin-bottom: 20px;"></p>
|
||||
<div class="patient_details" style="z-index=0"></div>
|
||||
</div>
|
||||
@ -11,6 +10,13 @@
|
||||
<div id="chart" class="col-sm-12 patient_vital_charts">
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-separator col-sm-12 d-flex border-bottom py-3" style="display:none"></div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 d-flex">
|
||||
<div class="patient-history-filter doctype-filter"></div>
|
||||
<div class="patient-history-filter date-filter"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 patient_documents_list">
|
||||
</div>
|
||||
<div class="col-sm-12 text-center py-3">
|
||||
|
@ -1,141 +1,225 @@
|
||||
frappe.provide("frappe.patient_history");
|
||||
frappe.provide('frappe.patient_history');
|
||||
frappe.pages['patient_history'].on_page_load = function(wrapper) {
|
||||
var me = this;
|
||||
var page = frappe.ui.make_app_page({
|
||||
let me = this;
|
||||
let page = frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: 'Patient History',
|
||||
single_column: true
|
||||
});
|
||||
|
||||
frappe.breadcrumbs.add("Healthcare");
|
||||
frappe.breadcrumbs.add('Healthcare');
|
||||
let pid = '';
|
||||
page.main.html(frappe.render_template("patient_history", {}));
|
||||
var patient = frappe.ui.form.make_control({
|
||||
parent: page.main.find(".patient"),
|
||||
page.main.html(frappe.render_template('patient_history', {}));
|
||||
page.main.find('.header-separator').hide();
|
||||
|
||||
let patient = frappe.ui.form.make_control({
|
||||
parent: page.main.find('.patient'),
|
||||
df: {
|
||||
fieldtype: "Link",
|
||||
options: "Patient",
|
||||
fieldname: "patient",
|
||||
change: function(){
|
||||
if(pid != patient.get_value() && patient.get_value()){
|
||||
fieldtype: 'Link',
|
||||
options: 'Patient',
|
||||
fieldname: 'patient',
|
||||
placeholder: __('Select Patient'),
|
||||
only_select: true,
|
||||
change: function() {
|
||||
let patient_id = patient.get_value();
|
||||
if (pid != patient_id && patient_id) {
|
||||
me.start = 0;
|
||||
me.page.main.find(".patient_documents_list").html("");
|
||||
get_documents(patient.get_value(), me);
|
||||
show_patient_info(patient.get_value(), me);
|
||||
show_patient_vital_charts(patient.get_value(), me, "bp", "mmHg", "Blood Pressure");
|
||||
me.page.main.find('.patient_documents_list').html('');
|
||||
setup_filters(patient_id, me);
|
||||
get_documents(patient_id, me);
|
||||
show_patient_info(patient_id, me);
|
||||
show_patient_vital_charts(patient_id, me, 'bp', 'mmHg', 'Blood Pressure');
|
||||
}
|
||||
pid = patient.get_value();
|
||||
pid = patient_id;
|
||||
}
|
||||
},
|
||||
only_input: true,
|
||||
});
|
||||
patient.refresh();
|
||||
|
||||
if (frappe.route_options){
|
||||
if (frappe.route_options) {
|
||||
patient.set_value(frappe.route_options.patient);
|
||||
}
|
||||
|
||||
this.page.main.on("click", ".btn-show-chart", function() {
|
||||
var btn_show_id = $(this).attr("data-show-chart-id"), pts = $(this).attr("data-pts");
|
||||
var title = $(this).attr("data-title");
|
||||
this.page.main.on('click', '.btn-show-chart', function() {
|
||||
let btn_show_id = $(this).attr('data-show-chart-id'), pts = $(this).attr('data-pts');
|
||||
let title = $(this).attr('data-title');
|
||||
show_patient_vital_charts(patient.get_value(), me, btn_show_id, pts, title);
|
||||
});
|
||||
|
||||
this.page.main.on("click", ".btn-more", function() {
|
||||
var doctype = $(this).attr("data-doctype"), docname = $(this).attr("data-docname");
|
||||
if(me.page.main.find("."+docname).parent().find('.document-html').attr('data-fetched') == "1"){
|
||||
me.page.main.find("."+docname).hide();
|
||||
me.page.main.find("."+docname).parent().find('.document-html').show();
|
||||
}else{
|
||||
if(doctype && docname){
|
||||
let exclude = ["patient", "patient_name", 'patient_sex', "encounter_date"];
|
||||
this.page.main.on('click', '.btn-more', function() {
|
||||
let doctype = $(this).attr('data-doctype'), docname = $(this).attr('data-docname');
|
||||
if (me.page.main.find('.'+docname).parent().find('.document-html').attr('data-fetched') == '1') {
|
||||
me.page.main.find('.'+docname).hide();
|
||||
me.page.main.find('.'+docname).parent().find('.document-html').show();
|
||||
} else {
|
||||
if (doctype && docname) {
|
||||
let exclude = ['patient', 'patient_name', 'patient_sex', 'encounter_date'];
|
||||
frappe.call({
|
||||
method: "erpnext.healthcare.utils.render_doc_as_html",
|
||||
method: 'erpnext.healthcare.utils.render_doc_as_html',
|
||||
args:{
|
||||
doctype: doctype,
|
||||
docname: docname,
|
||||
exclude_fields: exclude
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
if (r.message){
|
||||
me.page.main.find("."+docname).hide();
|
||||
me.page.main.find("."+docname).parent().find('.document-html').html(r.message.html+"\
|
||||
<div align='center'><a class='btn octicon octicon-chevron-up btn-default btn-xs\
|
||||
btn-less' data-doctype='"+doctype+"' data-docname='"+docname+"'></a></div>");
|
||||
me.page.main.find("."+docname).parent().find('.document-html').show();
|
||||
me.page.main.find("."+docname).parent().find('.document-html').attr('data-fetched', "1");
|
||||
if (r.message) {
|
||||
me.page.main.find('.' + docname).hide();
|
||||
|
||||
me.page.main.find('.' + docname).parent().find('.document-html').html(
|
||||
`${r.message.html}
|
||||
<div align='center'>
|
||||
<a class='btn octicon octicon-chevron-up btn-default btn-xs btn-less'
|
||||
data-doctype='${doctype}'
|
||||
data-docname='${docname}'>
|
||||
</a>
|
||||
</div>
|
||||
`);
|
||||
|
||||
me.page.main.find('.' + docname).parent().find('.document-html').show();
|
||||
me.page.main.find('.' + docname).parent().find('.document-html').attr('data-fetched', '1');
|
||||
}
|
||||
},
|
||||
freeze: true
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.page.main.on("click", ".btn-less", function() {
|
||||
var docname = $(this).attr("data-docname");
|
||||
me.page.main.find("."+docname).parent().find('.document-id').show();
|
||||
me.page.main.find("."+docname).parent().find('.document-html').hide();
|
||||
this.page.main.on('click', '.btn-less', function() {
|
||||
let docname = $(this).attr('data-docname');
|
||||
me.page.main.find('.' + docname).parent().find('.document-id').show();
|
||||
me.page.main.find('.' + docname).parent().find('.document-html').hide();
|
||||
});
|
||||
me.start = 0;
|
||||
me.page.main.on("click", ".btn-get-records", function(){
|
||||
me.page.main.on('click', '.btn-get-records', function() {
|
||||
get_documents(patient.get_value(), me);
|
||||
});
|
||||
};
|
||||
|
||||
var get_documents = function(patient, me){
|
||||
let setup_filters = function(patient, me) {
|
||||
$('.doctype-filter').empty();
|
||||
frappe.xcall(
|
||||
'erpnext.healthcare.page.patient_history.patient_history.get_patient_history_doctypes'
|
||||
).then(document_types => {
|
||||
let doctype_filter = frappe.ui.form.make_control({
|
||||
parent: $('.doctype-filter'),
|
||||
df: {
|
||||
fieldtype: 'MultiSelectList',
|
||||
fieldname: 'document_type',
|
||||
placeholder: __('Select Document Type'),
|
||||
input_class: 'input-xs',
|
||||
change: () => {
|
||||
me.start = 0;
|
||||
me.page.main.find('.patient_documents_list').html('');
|
||||
get_documents(patient, me, doctype_filter.get_value(), date_range_field.get_value());
|
||||
},
|
||||
get_data: () => {
|
||||
return document_types.map(document_type => {
|
||||
return {
|
||||
description: document_type,
|
||||
value: document_type
|
||||
};
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
doctype_filter.refresh();
|
||||
|
||||
$('.date-filter').empty();
|
||||
let date_range_field = frappe.ui.form.make_control({
|
||||
df: {
|
||||
fieldtype: 'DateRange',
|
||||
fieldname: 'date_range',
|
||||
placeholder: __('Date Range'),
|
||||
input_class: 'input-xs',
|
||||
change: () => {
|
||||
let selected_date_range = date_range_field.get_value();
|
||||
if (selected_date_range && selected_date_range.length === 2) {
|
||||
me.start = 0;
|
||||
me.page.main.find('.patient_documents_list').html('');
|
||||
get_documents(patient, me, doctype_filter.get_value(), selected_date_range);
|
||||
}
|
||||
}
|
||||
},
|
||||
parent: $('.date-filter')
|
||||
});
|
||||
date_range_field.refresh();
|
||||
});
|
||||
};
|
||||
|
||||
let get_documents = function(patient, me, document_types="", selected_date_range="") {
|
||||
let filters = {
|
||||
name: patient,
|
||||
start: me.start,
|
||||
page_length: 20
|
||||
};
|
||||
if (document_types)
|
||||
filters['document_types'] = document_types;
|
||||
if (selected_date_range)
|
||||
filters['date_range'] = selected_date_range;
|
||||
|
||||
frappe.call({
|
||||
"method": "erpnext.healthcare.page.patient_history.patient_history.get_feed",
|
||||
args: {
|
||||
name: patient,
|
||||
start: me.start,
|
||||
page_length: 20
|
||||
},
|
||||
callback: function (r) {
|
||||
var data = r.message;
|
||||
if(data.length){
|
||||
'method': 'erpnext.healthcare.page.patient_history.patient_history.get_feed',
|
||||
args: filters,
|
||||
callback: function(r) {
|
||||
let data = r.message;
|
||||
if (data.length) {
|
||||
add_to_records(me, data);
|
||||
}else{
|
||||
me.page.main.find(".patient_documents_list").append("<div class='text-muted' align='center'><br><br>No more records..<br><br></div>");
|
||||
me.page.main.find(".btn-get-records").hide();
|
||||
} else {
|
||||
me.page.main.find('.patient_documents_list').append(`
|
||||
<div class='text-muted' align='center'>
|
||||
<br><br>${__('No more records..')}<br><br>
|
||||
</div>`);
|
||||
me.page.main.find('.btn-get-records').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var add_to_records = function(me, data){
|
||||
var details = "<ul class='nav nav-pills nav-stacked'>";
|
||||
var i;
|
||||
for(i=0; i<data.length; i++){
|
||||
if(data[i].reference_doctype){
|
||||
let add_to_records = function(me, data) {
|
||||
let details = "<ul class='nav nav-pills nav-stacked'>";
|
||||
let i;
|
||||
for (i=0; i<data.length; i++) {
|
||||
if (data[i].reference_doctype) {
|
||||
let label = '';
|
||||
if(data[i].subject){
|
||||
label += "<br/>"+data[i].subject;
|
||||
if (data[i].subject) {
|
||||
label += "<br/>" + data[i].subject;
|
||||
}
|
||||
data[i] = add_date_separator(data[i]);
|
||||
if(frappe.user_info(data[i].owner).image){
|
||||
|
||||
if (frappe.user_info(data[i].owner).image) {
|
||||
data[i].imgsrc = frappe.utils.get_file_link(frappe.user_info(data[i].owner).image);
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
data[i].imgsrc = false;
|
||||
}
|
||||
var time_line_heading = data[i].practitioner ? `${data[i].practitioner} ` : ``;
|
||||
time_line_heading += data[i].reference_doctype + " - "+ data[i].reference_name;
|
||||
details += `<li data-toggle='pill' class='patient_doc_menu'
|
||||
data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'>
|
||||
<div class='col-sm-12 d-flex border-bottom py-3'>`;
|
||||
if (data[i].imgsrc){
|
||||
details += `<span class='mr-3'>
|
||||
<img class='avtar' src='${data[i].imgsrc}' width='32' height='32'>
|
||||
</img>
|
||||
</span>`;
|
||||
}else{
|
||||
details += `<span class='mr-3 avatar avatar-small' style='width:32px; height:32px;'><div align='center' class='standard-image'
|
||||
style='background-color: #fafbfc;'>${data[i].practitioner ? data[i].practitioner.charAt(0) : "U"}</div></span>`;
|
||||
|
||||
let time_line_heading = data[i].practitioner ? `${data[i].practitioner} ` : ``;
|
||||
time_line_heading += data[i].reference_doctype + " - " +
|
||||
`<a onclick="frappe.set_route('Form', '${data[i].reference_doctype}', '${data[i].reference_name}');">
|
||||
${data[i].reference_name}
|
||||
</a>`;
|
||||
|
||||
details += `
|
||||
<li data-toggle='pill' class='patient_doc_menu'
|
||||
data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'>
|
||||
<div class='col-sm-12 d-flex border-bottom py-3'>`;
|
||||
|
||||
if (data[i].imgsrc) {
|
||||
details += `
|
||||
<span class='mr-3'>
|
||||
<img class='avtar' src='${data[i].imgsrc}' width='32' height='32'></img>
|
||||
</span>`;
|
||||
} else {
|
||||
details += `<span class='mr-3 avatar avatar-small' style='width:32px; height:32px;'>
|
||||
<div align='center' class='standard-image' style='background-color: #fafbfc;'>
|
||||
${data[i].practitioner ? data[i].practitioner.charAt(0) : 'U'}
|
||||
</div>
|
||||
</span>`;
|
||||
}
|
||||
|
||||
details += `<div class='d-flex flex-column width-full'>
|
||||
<div>
|
||||
`+time_line_heading+` on
|
||||
`+time_line_heading+`
|
||||
<span>
|
||||
${data[i].date_sep}
|
||||
</span>
|
||||
@ -156,133 +240,150 @@ var add_to_records = function(me, data){
|
||||
</li>`;
|
||||
}
|
||||
}
|
||||
details += "</ul>";
|
||||
me.page.main.find(".patient_documents_list").append(details);
|
||||
|
||||
details += '</ul>';
|
||||
me.page.main.find('.patient_documents_list').append(details);
|
||||
me.start += data.length;
|
||||
if(data.length===20){
|
||||
|
||||
if (data.length === 20) {
|
||||
me.page.main.find(".btn-get-records").show();
|
||||
}else{
|
||||
} else {
|
||||
me.page.main.find(".btn-get-records").hide();
|
||||
me.page.main.find(".patient_documents_list").append("<div class='text-muted' align='center'><br><br>No more records..<br><br></div>");
|
||||
me.page.main.find(".patient_documents_list").append(`
|
||||
<div class='text-muted' align='center'>
|
||||
<br><br>${__('No more records..')}<br><br>
|
||||
</div>`);
|
||||
}
|
||||
};
|
||||
|
||||
var add_date_separator = function(data) {
|
||||
var date = frappe.datetime.str_to_obj(data.creation);
|
||||
let add_date_separator = function(data) {
|
||||
let date = frappe.datetime.str_to_obj(data.communication_date);
|
||||
let pdate = '';
|
||||
let diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date));
|
||||
|
||||
var diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date));
|
||||
if(diff < 1) {
|
||||
var pdate = 'Today';
|
||||
} else if(diff < 2) {
|
||||
pdate = 'Yesterday';
|
||||
if (diff < 1) {
|
||||
pdate = __('Today');
|
||||
} else if (diff < 2) {
|
||||
pdate = __('Yesterday');
|
||||
} else {
|
||||
pdate = frappe.datetime.global_date_format(date);
|
||||
pdate = __('on ') + frappe.datetime.global_date_format(date);
|
||||
}
|
||||
data.date_sep = pdate;
|
||||
return data;
|
||||
};
|
||||
|
||||
var show_patient_info = function(patient, me){
|
||||
let show_patient_info = function(patient, me) {
|
||||
frappe.call({
|
||||
"method": "erpnext.healthcare.doctype.patient.patient.get_patient_detail",
|
||||
'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
|
||||
args: {
|
||||
patient: patient
|
||||
},
|
||||
callback: function (r) {
|
||||
var data = r.message;
|
||||
var details = "";
|
||||
if(data.image){
|
||||
details += "<div><img class='thumbnail' width=75% src='"+data.image+"'></div>";
|
||||
callback: function(r) {
|
||||
let data = r.message;
|
||||
let details = '';
|
||||
if (data.image) {
|
||||
details += `<div><img class='thumbnail' width=75% src='${data.image}'></div>`;
|
||||
}
|
||||
details += "<b>" + data.patient_name +"</b><br>" + data.sex;
|
||||
if(data.email) details += "<br>" + data.email;
|
||||
if(data.mobile) details += "<br>" + data.mobile;
|
||||
if(data.occupation) details += "<br><br><b>Occupation :</b> " + data.occupation;
|
||||
if(data.blood_group) details += "<br><b>Blood group : </b> " + data.blood_group;
|
||||
if(data.allergies) details += "<br><br><b>Allergies : </b> "+ data.allergies.replace("\n", "<br>");
|
||||
if(data.medication) details += "<br><b>Medication : </b> "+ data.medication.replace("\n", "<br>");
|
||||
if(data.alcohol_current_use) details += "<br><br><b>Alcohol use : </b> "+ data.alcohol_current_use;
|
||||
if(data.alcohol_past_use) details += "<br><b>Alcohol past use : </b> "+ data.alcohol_past_use;
|
||||
if(data.tobacco_current_use) details += "<br><b>Tobacco use : </b> "+ data.tobacco_current_use;
|
||||
if(data.tobacco_past_use) details += "<br><b>Tobacco past use : </b> "+ data.tobacco_past_use;
|
||||
if(data.medical_history) details += "<br><br><b>Medical history : </b> "+ data.medical_history.replace("\n", "<br>");
|
||||
if(data.surgical_history) details += "<br><b>Surgical history : </b> "+ data.surgical_history.replace("\n", "<br>");
|
||||
if(data.surrounding_factors) details += "<br><br><b>Occupational hazards : </b> "+ data.surrounding_factors.replace("\n", "<br>");
|
||||
if(data.other_risk_factors) details += "<br><b>Other risk factors : </b> " + data.other_risk_factors.replace("\n", "<br>");
|
||||
if(data.patient_details) details += "<br><br><b>More info : </b> " + data.patient_details.replace("\n", "<br>");
|
||||
|
||||
if(details){
|
||||
details = "<div style='padding-left:10px; font-size:13px;' align='center'>" + details + "</div>";
|
||||
details += `<b> ${data.patient_name} </b><br> ${data.sex}`;
|
||||
if (data.email) details += `<br> ${data.email}`;
|
||||
if (data.mobile) details += `<br> ${data.mobile}`;
|
||||
if (data.occupation) details += `<br><br><b> ${__('Occupation')} : </b> ${data.occupation}`;
|
||||
if (data.blood_group) details += `<br><b> ${__('Blood Group')} : </b> ${data.blood_group}`;
|
||||
if (data.allergies) details += `<br><br><b> ${__('Allerigies')} : </b> ${data.allergies.replace("\n", ", ")}`;
|
||||
if (data.medication) details += `<br><b> ${__('Medication')} : </b> ${data.medication.replace("\n", ", ")}`;
|
||||
if (data.alcohol_current_use) details += `<br><br><b> ${__('Alcohol use')} : </b> ${data.alcohol_current_use}`;
|
||||
if (data.alcohol_past_use) details += `<br><b> ${__('Alcohol past use')} : </b> ${data.alcohol_past_use}`;
|
||||
if (data.tobacco_current_use) details += `<br><b> ${__('Tobacco use')} : </b> ${data.tobacco_current_use}`;
|
||||
if (data.tobacco_past_use) details += `<br><b> ${__('Tobacco past use')} : </b> ${data.tobacco_past_use}`;
|
||||
if (data.medical_history) details += `<br><br><b> ${__('Medical history')} : </b> ${data.medical_history.replace("\n", ", ")}`;
|
||||
if (data.surgical_history) details += `<br><b> ${__('Surgical history')} : </b> ${data.surgical_history.replace("\n", ", ")}`;
|
||||
if (data.surrounding_factors) details += `<br><br><b> ${__('Occupational hazards')} : </b> ${data.surrounding_factors.replace("\n", ", ")}`;
|
||||
if (data.other_risk_factors) details += `<br><b> ${__('Other risk factors')} : </b> ${data.other_risk_factors.replace("\n", ", ")}`;
|
||||
if (data.patient_details) details += `<br><br><b> ${__('More info')} : </b> ${data.patient_details.replace("\n", ", ")}`;
|
||||
|
||||
if (details) {
|
||||
details = `<div style='padding-left:10px; font-size:13px;' align='left'>` + details + `</div>`;
|
||||
}
|
||||
me.page.main.find(".patient_details").html(details);
|
||||
me.page.main.find('.patient_details').html(details);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
|
||||
let show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
|
||||
frappe.call({
|
||||
method: "erpnext.healthcare.utils.get_patient_vitals",
|
||||
method: 'erpnext.healthcare.utils.get_patient_vitals',
|
||||
args:{
|
||||
patient: patient
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message){
|
||||
var show_chart_btns_html = "<div style='padding-top:5px;'><a class='btn btn-default btn-xs btn-show-chart' \
|
||||
data-show-chart-id='bp' data-pts='mmHg' data-title='Blood Pressure'>Blood Pressure</a>\
|
||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='pulse_rate' \
|
||||
data-pts='per Minutes' data-title='Respiratory/Pulse Rate'>Respiratory/Pulse Rate</a>\
|
||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='temperature' \
|
||||
data-pts='°C or °F' data-title='Temperature'>Temperature</a>\
|
||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bmi' \
|
||||
data-pts='' data-title='BMI'>BMI</a></div>";
|
||||
me.page.main.find(".show_chart_btns").html(show_chart_btns_html);
|
||||
var data = r.message;
|
||||
if (r.message) {
|
||||
let show_chart_btns_html = `
|
||||
<div style='padding-top:10px;'>
|
||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bp' data-pts='mmHg' data-title='Blood Pressure'>
|
||||
${__('Blood Pressure')}
|
||||
</a>
|
||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='pulse_rate' data-pts='per Minutes' data-title='Respiratory/Pulse Rate'>
|
||||
${__('Respiratory/Pulse Rate')}
|
||||
</a>
|
||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='temperature' data-pts='°C or °F' data-title='Temperature'>
|
||||
${__('Temperature')}
|
||||
</a>
|
||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bmi' data-pts='' data-title='BMI'>
|
||||
${__('BMI')}
|
||||
</a>
|
||||
</div>`;
|
||||
|
||||
me.page.main.find('.show_chart_btns').html(show_chart_btns_html);
|
||||
let data = r.message;
|
||||
let labels = [], datasets = [];
|
||||
let bp_systolic = [], bp_diastolic = [], temperature = [];
|
||||
let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = [];
|
||||
for(var i=0; i<data.length; i++){
|
||||
labels.push(data[i].signs_date+"||"+data[i].signs_time);
|
||||
if(btn_show_id=="bp"){
|
||||
|
||||
for (let i=0; i<data.length; i++) {
|
||||
labels.push(data[i].signs_date+'||'+data[i].signs_time);
|
||||
|
||||
if (btn_show_id === 'bp') {
|
||||
bp_systolic.push(data[i].bp_systolic);
|
||||
bp_diastolic.push(data[i].bp_diastolic);
|
||||
}
|
||||
if(btn_show_id=="temperature"){
|
||||
if (btn_show_id === 'temperature') {
|
||||
temperature.push(data[i].temperature);
|
||||
}
|
||||
if(btn_show_id=="pulse_rate"){
|
||||
if (btn_show_id === 'pulse_rate') {
|
||||
pulse.push(data[i].pulse);
|
||||
respiratory_rate.push(data[i].respiratory_rate);
|
||||
}
|
||||
if(btn_show_id=="bmi"){
|
||||
if (btn_show_id === 'bmi') {
|
||||
bmi.push(data[i].bmi);
|
||||
height.push(data[i].height);
|
||||
weight.push(data[i].weight);
|
||||
}
|
||||
}
|
||||
if(btn_show_id=="temperature"){
|
||||
datasets.push({name: "Temperature", values: temperature, chartType:'line'});
|
||||
if (btn_show_id === 'temperature') {
|
||||
datasets.push({name: 'Temperature', values: temperature, chartType: 'line'});
|
||||
}
|
||||
if(btn_show_id=="bmi"){
|
||||
datasets.push({name: "BMI", values: bmi, chartType:'line'});
|
||||
datasets.push({name: "Height", values: height, chartType:'line'});
|
||||
datasets.push({name: "Weight", values: weight, chartType:'line'});
|
||||
if (btn_show_id === 'bmi') {
|
||||
datasets.push({name: 'BMI', values: bmi, chartType: 'line'});
|
||||
datasets.push({name: 'Height', values: height, chartType: 'line'});
|
||||
datasets.push({name: 'Weight', values: weight, chartType: 'line'});
|
||||
}
|
||||
if(btn_show_id=="bp"){
|
||||
datasets.push({name: "BP Systolic", values: bp_systolic, chartType:'line'});
|
||||
datasets.push({name: "BP Diastolic", values: bp_diastolic, chartType:'line'});
|
||||
if (btn_show_id === 'bp') {
|
||||
datasets.push({name: 'BP Systolic', values: bp_systolic, chartType: 'line'});
|
||||
datasets.push({name: 'BP Diastolic', values: bp_diastolic, chartType: 'line'});
|
||||
}
|
||||
if(btn_show_id=="pulse_rate"){
|
||||
datasets.push({name: "Heart Rate / Pulse", values: pulse, chartType:'line'});
|
||||
datasets.push({name: "Respiratory Rate", values: respiratory_rate, chartType:'line'});
|
||||
if (btn_show_id === 'pulse_rate') {
|
||||
datasets.push({name: 'Heart Rate / Pulse', values: pulse, chartType: 'line'});
|
||||
datasets.push({name: 'Respiratory Rate', values: respiratory_rate, chartType: 'line'});
|
||||
}
|
||||
new frappe.Chart( ".patient_vital_charts", {
|
||||
new frappe.Chart('.patient_vital_charts', {
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: datasets
|
||||
},
|
||||
|
||||
title: title,
|
||||
type: 'axis-mixed', // 'axis-mixed', 'bar', 'line', 'pie', 'percentage'
|
||||
type: 'axis-mixed',
|
||||
height: 200,
|
||||
colors: ['purple', '#ffa3ef', 'light-blue'],
|
||||
|
||||
@ -291,9 +392,11 @@ var show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
|
||||
formatTooltipY: d => d + ' ' + pts,
|
||||
}
|
||||
});
|
||||
}else{
|
||||
me.page.main.find(".patient_vital_charts").html("");
|
||||
me.page.main.find(".show_chart_btns").html("");
|
||||
me.page.main.find('.header-separator').show();
|
||||
} else {
|
||||
me.page.main.find('.patient_vital_charts').html('');
|
||||
me.page.main.find('.show_chart_btns').html('');
|
||||
me.page.main.find('.header-separator').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -4,36 +4,70 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from frappe.utils import cint
|
||||
from erpnext.healthcare.utils import render_docs_as_html
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_feed(name, start=0, page_length=20):
|
||||
def get_feed(name, document_types=None, date_range=None, start=0, page_length=20):
|
||||
"""get feed"""
|
||||
result = frappe.db.sql("""select name, owner, creation,
|
||||
reference_doctype, reference_name, subject
|
||||
from `tabPatient Medical Record`
|
||||
where patient=%(patient)s
|
||||
order by creation desc
|
||||
limit %(start)s, %(page_length)s""",
|
||||
{
|
||||
"patient": name,
|
||||
"start": cint(start),
|
||||
"page_length": cint(page_length)
|
||||
}, as_dict=True)
|
||||
filters = get_filters(name, document_types, date_range)
|
||||
|
||||
result = frappe.db.get_all('Patient Medical Record',
|
||||
fields=['name', 'owner', 'communication_date',
|
||||
'reference_doctype', 'reference_name', 'subject'],
|
||||
filters=filters,
|
||||
order_by='communication_date DESC',
|
||||
limit=cint(page_length),
|
||||
start=cint(start)
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_filters(name, document_types=None, date_range=None):
|
||||
filters = {'patient': name}
|
||||
if document_types:
|
||||
document_types = json.loads(document_types)
|
||||
if len(document_types):
|
||||
filters['reference_doctype'] = ['IN', document_types]
|
||||
|
||||
if date_range:
|
||||
try:
|
||||
date_range = json.loads(date_range)
|
||||
if date_range:
|
||||
filters['communication_date'] = ['between', [date_range[0], date_range[1]]]
|
||||
except json.decoder.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return filters
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_feed_for_dt(doctype, docname):
|
||||
"""get feed"""
|
||||
result = frappe.db.sql("""select name, owner, modified, creation,
|
||||
reference_doctype, reference_name, subject
|
||||
from `tabPatient Medical Record`
|
||||
where reference_name=%(docname)s and reference_doctype=%(doctype)s
|
||||
order by creation desc""",
|
||||
{
|
||||
"docname": docname,
|
||||
"doctype": doctype
|
||||
}, as_dict=True)
|
||||
result = frappe.db.get_all('Patient Medical Record',
|
||||
fields=['name', 'owner', 'communication_date',
|
||||
'reference_doctype', 'reference_name', 'subject'],
|
||||
filters={
|
||||
'reference_doctype': doctype,
|
||||
'reference_name': docname
|
||||
},
|
||||
order_by='communication_date DESC'
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_patient_history_doctypes():
|
||||
document_types = []
|
||||
settings = frappe.get_single("Patient History Settings")
|
||||
|
||||
for entry in settings.standard_doctypes:
|
||||
document_types.append(entry.document_type)
|
||||
|
||||
for entry in settings.custom_doctypes:
|
||||
document_types.append(entry.document_type)
|
||||
|
||||
return document_types
|
||||
|
@ -16,6 +16,7 @@ def setup_healthcare():
|
||||
create_healthcare_item_groups()
|
||||
create_sensitivity()
|
||||
add_healthcare_service_unit_tree_root()
|
||||
setup_patient_history_settings()
|
||||
|
||||
def create_medical_departments():
|
||||
departments = [
|
||||
@ -213,3 +214,82 @@ def get_company():
|
||||
if company:
|
||||
return company[0].name
|
||||
return None
|
||||
|
||||
def setup_patient_history_settings():
|
||||
import json
|
||||
|
||||
settings = frappe.get_single('Patient History Settings')
|
||||
configuration = get_patient_history_config()
|
||||
for dt, config in configuration.items():
|
||||
settings.append("standard_doctypes", {
|
||||
"document_type": dt,
|
||||
"date_fieldname": config[0],
|
||||
"selected_fields": json.dumps(config[1])
|
||||
})
|
||||
settings.save()
|
||||
|
||||
def get_patient_history_config():
|
||||
return {
|
||||
"Patient Encounter": ("encounter_date", [
|
||||
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
|
||||
{"label": "Symptoms", "fieldname": "symptoms", "fieldtype": "Table Multiselect"},
|
||||
{"label": "Diagnosis", "fieldname": "diagnosis", "fieldtype": "Table Multiselect"},
|
||||
{"label": "Drug Prescription", "fieldname": "drug_prescription", "fieldtype": "Table"},
|
||||
{"label": "Lab Tests", "fieldname": "lab_test_prescription", "fieldtype": "Table"},
|
||||
{"label": "Clinical Procedures", "fieldname": "procedure_prescription", "fieldtype": "Table"},
|
||||
{"label": "Therapies", "fieldname": "therapies", "fieldtype": "Table"},
|
||||
{"label": "Review Details", "fieldname": "encounter_comment", "fieldtype": "Small Text"}
|
||||
]),
|
||||
"Clinical Procedure": ("start_date", [
|
||||
{"label": "Procedure Template", "fieldname": "procedure_template", "fieldtype": "Link"},
|
||||
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
|
||||
{"label": "Notes", "fieldname": "notes", "fieldtype": "Small Text"},
|
||||
{"label": "Service Unit", "fieldname": "service_unit", "fieldtype": "Healthcare Service Unit"},
|
||||
{"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"},
|
||||
{"label": "Sample", "fieldname": "sample", "fieldtype": "Link"}
|
||||
]),
|
||||
"Lab Test": ("result_date", [
|
||||
{"label": "Test Template", "fieldname": "template", "fieldtype": "Link"},
|
||||
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
|
||||
{"label": "Test Name", "fieldname": "lab_test_name", "fieldtype": "Data"},
|
||||
{"label": "Lab Technician Name", "fieldname": "employee_name", "fieldtype": "Data"},
|
||||
{"label": "Sample ID", "fieldname": "sample", "fieldtype": "Link"},
|
||||
{"label": "Normal Test Result", "fieldname": "normal_test_items", "fieldtype": "Table"},
|
||||
{"label": "Descriptive Test Result", "fieldname": "descriptive_test_items", "fieldtype": "Table"},
|
||||
{"label": "Organism Test Result", "fieldname": "organism_test_items", "fieldtype": "Table"},
|
||||
{"label": "Sensitivity Test Result", "fieldname": "sensitivity_test_items", "fieldtype": "Table"},
|
||||
{"label": "Comments", "fieldname": "lab_test_comment", "fieldtype": "Table"}
|
||||
]),
|
||||
"Therapy Session": ("start_date", [
|
||||
{"label": "Therapy Type", "fieldname": "therapy_type", "fieldtype": "Link"},
|
||||
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
|
||||
{"label": "Therapy Plan", "fieldname": "therapy_plan", "fieldtype": "Link"},
|
||||
{"label": "Duration", "fieldname": "duration", "fieldtype": "Int"},
|
||||
{"label": "Location", "fieldname": "location", "fieldtype": "Link"},
|
||||
{"label": "Healthcare Service Unit", "fieldname": "service_unit", "fieldtype": "Link"},
|
||||
{"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"},
|
||||
{"label": "Exercises", "fieldname": "exercises", "fieldtype": "Table"},
|
||||
{"label": "Total Counts Targeted", "fieldname": "total_counts_targeted", "fieldtype": "Int"},
|
||||
{"label": "Total Counts Completed", "fieldname": "total_counts_completed", "fieldtype": "Int"}
|
||||
]),
|
||||
"Vital Signs": ("signs_date", [
|
||||
{"label": "Body Temperature", "fieldname": "temperature", "fieldtype": "Data"},
|
||||
{"label": "Heart Rate / Pulse", "fieldname": "pulse", "fieldtype": "Data"},
|
||||
{"label": "Respiratory rate", "fieldname": "respiratory_rate", "fieldtype": "Data"},
|
||||
{"label": "Tongue", "fieldname": "tongue", "fieldtype": "Select"},
|
||||
{"label": "Abdomen", "fieldname": "abdomen", "fieldtype": "Select"},
|
||||
{"label": "Reflexes", "fieldname": "reflexes", "fieldtype": "Select"},
|
||||
{"label": "Blood Pressure", "fieldname": "bp", "fieldtype": "Data"},
|
||||
{"label": "Notes", "fieldname": "vital_signs_note", "fieldtype": "Small Text"},
|
||||
{"label": "Height (In Meter)", "fieldname": "height", "fieldtype": "Float"},
|
||||
{"label": "Weight (In Kilogram)", "fieldname": "weight", "fieldtype": "Float"},
|
||||
{"label": "BMI", "fieldname": "bmi", "fieldtype": "Float"}
|
||||
]),
|
||||
"Inpatient Medication Order": ("start_date", [
|
||||
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
|
||||
{"label": "Start Date", "fieldname": "start_date", "fieldtype": "Date"},
|
||||
{"label": "End Date", "fieldname": "end_date", "fieldtype": "Date"},
|
||||
{"label": "Medication Orders", "fieldname": "medication_orders", "fieldtype": "Table"},
|
||||
{"label": "Total Orders", "fieldname": "total_orders", "fieldtype": "Float"}
|
||||
])
|
||||
}
|
@ -6,6 +6,7 @@ from __future__ import unicode_literals
|
||||
import math
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils.formatters import format_value
|
||||
from frappe.utils import time_diff_in_hours, rounded
|
||||
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account
|
||||
from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity
|
||||
@ -77,11 +78,13 @@ def get_appointments_to_invoice(patient, company):
|
||||
|
||||
|
||||
def get_encounters_to_invoice(patient, company):
|
||||
if not isinstance(patient, str):
|
||||
patient = patient.name
|
||||
encounters_to_invoice = []
|
||||
encounters = frappe.get_list(
|
||||
'Patient Encounter',
|
||||
fields=['*'],
|
||||
filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1}
|
||||
filters={'patient': patient, 'company': company, 'invoiced': False, 'docstatus': 1}
|
||||
)
|
||||
if encounters:
|
||||
for encounter in encounters:
|
||||
@ -90,6 +93,10 @@ def get_encounters_to_invoice(patient, company):
|
||||
income_account = None
|
||||
service_item = None
|
||||
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)
|
||||
income_account = get_income_account(encounter.practitioner, encounter.company)
|
||||
|
||||
@ -642,11 +649,15 @@ def render_doc_as_html(doctype, docname, exclude_fields = []):
|
||||
html += "<table class='table table-condensed table-bordered'>" \
|
||||
+ table_head + table_row + "</table>"
|
||||
continue
|
||||
|
||||
#on other field types add label and value to html
|
||||
if not df.hidden and not df.print_hide and doc.get(df.fieldname) and df.fieldname not in exclude_fields:
|
||||
html += '<br>{0} : {1}'.format(df.label or df.fieldname, \
|
||||
doc.get(df.fieldname))
|
||||
if doc.get(df.fieldname):
|
||||
formatted_value = format_value(doc.get(df.fieldname), meta.get_field(df.fieldname), doc)
|
||||
html += '<br>{0} : {1}'.format(df.label or df.fieldname, formatted_value)
|
||||
|
||||
if not has_data : has_data = True
|
||||
|
||||
if sec_on and col_on and has_data:
|
||||
doc_html += section_html + html + '</div></div>'
|
||||
elif sec_on and not col_on and has_data:
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user