Merge branch 'develop' into fix-payment-entry-wrong-bank-account-fetch-develop
This commit is contained in:
commit
c591b77613
@ -911,7 +911,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
elif reference_doctype != "Journal Entry":
|
||||
if party_account_currency == company_currency:
|
||||
if ref_doc.doctype == "Expense Claim":
|
||||
total_amount = ref_doc.total_sanctioned_amount
|
||||
total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
|
||||
elif ref_doc.doctype == "Employee Advance":
|
||||
total_amount = ref_doc.advance_amount
|
||||
else:
|
||||
@ -929,8 +929,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||
bill_no = ref_doc.get("bill_no")
|
||||
elif reference_doctype == "Expense Claim":
|
||||
outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) \
|
||||
- flt(ref_doc.get("total_amount+reimbursed")) - flt(ref_doc.get("total_advance_amount"))
|
||||
outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) + flt(ref_doc.get("total_taxes_and_charges"))\
|
||||
- flt(ref_doc.get("total_amount_reimbursed")) - flt(ref_doc.get("total_advance_amount"))
|
||||
elif reference_doctype == "Employee Advance":
|
||||
outstanding_amount = ref_doc.advance_amount - flt(ref_doc.paid_amount)
|
||||
else:
|
||||
|
@ -140,9 +140,6 @@ class PaymentRequest(Document):
|
||||
})
|
||||
|
||||
def set_as_paid(self):
|
||||
if frappe.session.user == "Guest":
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
payment_entry = self.create_payment_entry()
|
||||
self.make_invoice()
|
||||
|
||||
@ -254,7 +251,7 @@ class PaymentRequest(Document):
|
||||
|
||||
if status in ["Authorized", "Completed"]:
|
||||
redirect_to = None
|
||||
self.run_method("set_as_paid")
|
||||
self.set_as_paid()
|
||||
|
||||
# if shopping cart enabled and in session
|
||||
if (shopping_cart_settings.enabled and hasattr(frappe.local, "session")
|
||||
|
@ -26,6 +26,7 @@
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"supplier_invoice_details",
|
||||
"bill_no",
|
||||
"column_break_15",
|
||||
@ -1319,13 +1320,19 @@
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Billing Address",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-18 05:06:08.488761",
|
||||
"modified": "2020-07-24 09:46:40.405463",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -223,9 +223,9 @@ class GrossProfitGenerator(object):
|
||||
# IMP NOTE
|
||||
# stock_ledger_entries should already be filtered by item_code and warehouse and
|
||||
# sorted by posting_date desc, posting_time desc
|
||||
if item_code in self.non_stock_items:
|
||||
if item_code in self.non_stock_items and (row.project or row.cost_center):
|
||||
#Issue 6089-Get last purchasing rate for non-stock item
|
||||
item_rate = self.get_last_purchase_rate(item_code)
|
||||
item_rate = self.get_last_purchase_rate(item_code, row)
|
||||
return flt(row.qty) * item_rate
|
||||
|
||||
else:
|
||||
@ -253,38 +253,34 @@ class GrossProfitGenerator(object):
|
||||
def get_average_buying_rate(self, row, item_code):
|
||||
args = row
|
||||
if not item_code in self.average_buying_rate:
|
||||
if item_code in self.non_stock_items:
|
||||
self.average_buying_rate[item_code] = flt(frappe.db.sql("""
|
||||
select sum(base_net_amount) / sum(qty * conversion_factor)
|
||||
from `tabPurchase Invoice Item`
|
||||
where item_code = %s and docstatus=1""", item_code)[0][0])
|
||||
else:
|
||||
args.update({
|
||||
'voucher_type': row.parenttype,
|
||||
'voucher_no': row.parent,
|
||||
'allow_zero_valuation': True,
|
||||
'company': self.filters.company
|
||||
})
|
||||
args.update({
|
||||
'voucher_type': row.parenttype,
|
||||
'voucher_no': row.parent,
|
||||
'allow_zero_valuation': True,
|
||||
'company': self.filters.company
|
||||
})
|
||||
|
||||
average_buying_rate = get_incoming_rate(args)
|
||||
self.average_buying_rate[item_code] = flt(average_buying_rate)
|
||||
average_buying_rate = get_incoming_rate(args)
|
||||
self.average_buying_rate[item_code] = flt(average_buying_rate)
|
||||
|
||||
return self.average_buying_rate[item_code]
|
||||
|
||||
def get_last_purchase_rate(self, item_code):
|
||||
def get_last_purchase_rate(self, item_code, row):
|
||||
condition = ''
|
||||
if row.project:
|
||||
condition += " AND a.project='%s'" % (row.project)
|
||||
elif row.cost_center:
|
||||
condition += " AND a.cost_center='%s'" % (row.cost_center)
|
||||
if self.filters.to_date:
|
||||
last_purchase_rate = frappe.db.sql("""
|
||||
select (a.base_rate / a.conversion_factor)
|
||||
from `tabPurchase Invoice Item` a
|
||||
where a.item_code = %s and a.docstatus=1
|
||||
and modified <= %s
|
||||
order by a.modified desc limit 1""", (item_code, self.filters.to_date))
|
||||
else:
|
||||
last_purchase_rate = frappe.db.sql("""
|
||||
select (a.base_rate / a.conversion_factor)
|
||||
from `tabPurchase Invoice Item` a
|
||||
where a.item_code = %s and a.docstatus=1
|
||||
order by a.modified desc limit 1""", item_code)
|
||||
condition += " AND modified='%s'" % (self.filters.to_date)
|
||||
|
||||
last_purchase_rate = frappe.db.sql("""
|
||||
select (a.base_rate / a.conversion_factor)
|
||||
from `tabPurchase Invoice Item` a
|
||||
where a.item_code = %s and a.docstatus=1
|
||||
{0}
|
||||
order by a.modified desc limit 1""".format(condition), item_code)
|
||||
|
||||
return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0
|
||||
|
||||
def load_invoice_items(self):
|
||||
@ -321,7 +317,8 @@ class GrossProfitGenerator(object):
|
||||
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.dn_detail,
|
||||
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
|
||||
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
|
||||
`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return
|
||||
`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return,
|
||||
`tabSales Invoice Item`.cost_center
|
||||
{sales_person_cols}
|
||||
from
|
||||
`tabSales Invoice` inner join `tabSales Invoice Item`
|
||||
|
39
erpnext/assets/assets_dashboard/asset/asset.json
Normal file
39
erpnext/assets/assets_dashboard/asset/asset.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"cards": [
|
||||
{
|
||||
"card": "Total Assets"
|
||||
},
|
||||
{
|
||||
"card": "New Assets (This Year)"
|
||||
},
|
||||
{
|
||||
"card": "Asset Value"
|
||||
}
|
||||
],
|
||||
"charts": [
|
||||
{
|
||||
"chart": "Asset Value Analytics",
|
||||
"width": "Full"
|
||||
},
|
||||
{
|
||||
"chart": "Category-wise Asset Value",
|
||||
"width": "Half"
|
||||
},
|
||||
{
|
||||
"chart": "Location-wise Asset Value",
|
||||
"width": "Half"
|
||||
}
|
||||
],
|
||||
"creation": "2020-07-14 18:23:53.343082",
|
||||
"dashboard_name": "Asset",
|
||||
"docstatus": 0,
|
||||
"doctype": "Dashboard",
|
||||
"idx": 0,
|
||||
"is_default": 0,
|
||||
"is_standard": 1,
|
||||
"modified": "2020-07-21 18:14:25.078929",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
"owner": "Administrator"
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
{
|
||||
"chart_name": "Asset Value Analytics",
|
||||
"chart_type": "Report",
|
||||
"creation": "2020-07-14 18:23:53.091233",
|
||||
"custom_options": "{\"type\": \"bar\", \"barOptions\": {\"stacked\": 1}, \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}}",
|
||||
"docstatus": 0,
|
||||
"doctype": "Dashboard Chart",
|
||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
|
||||
"filters_json": "{\"status\":\"In Location\",\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"date_based_on\":\"Purchase Date\",\"group_by\":\"--Select a group--\"}",
|
||||
"group_by_type": "Count",
|
||||
"idx": 0,
|
||||
"is_public": 0,
|
||||
"is_standard": 1,
|
||||
"modified": "2020-07-23 13:53:33.211371",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Value Analytics",
|
||||
"number_of_groups": 0,
|
||||
"owner": "Administrator",
|
||||
"report_name": "Fixed Asset Register",
|
||||
"time_interval": "Yearly",
|
||||
"timeseries": 0,
|
||||
"timespan": "Last Year",
|
||||
"type": "Bar",
|
||||
"use_report_chart": 1,
|
||||
"y_axis": []
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
{
|
||||
"chart_name": "Category-wise Asset Value",
|
||||
"chart_type": "Report",
|
||||
"creation": "2020-07-14 18:23:53.146304",
|
||||
"custom_options": "{\"type\": \"donut\", \"height\": 300, \"axisOptions\": {\"shortenYAxisNumbers\": 1}}",
|
||||
"docstatus": 0,
|
||||
"doctype": "Dashboard Chart",
|
||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
|
||||
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Asset Category\",\"is_existing_asset\":0}",
|
||||
"idx": 0,
|
||||
"is_public": 0,
|
||||
"is_standard": 1,
|
||||
"modified": "2020-07-23 13:39:32.429240",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Category-wise Asset Value",
|
||||
"number_of_groups": 0,
|
||||
"owner": "Administrator",
|
||||
"report_name": "Fixed Asset Register",
|
||||
"timeseries": 0,
|
||||
"type": "Donut",
|
||||
"use_report_chart": 0,
|
||||
"x_field": "asset_category",
|
||||
"y_axis": [
|
||||
{
|
||||
"y_field": "asset_value"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
{
|
||||
"chart_name": "Location-wise Asset Value",
|
||||
"chart_type": "Report",
|
||||
"creation": "2020-07-14 18:23:53.195389",
|
||||
"custom_options": "{\"type\": \"donut\", \"height\": 300, \"axisOptions\": {\"shortenYAxisNumbers\": 1}}",
|
||||
"docstatus": 0,
|
||||
"doctype": "Dashboard Chart",
|
||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
|
||||
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Location\",\"is_existing_asset\":0}",
|
||||
"idx": 0,
|
||||
"is_public": 0,
|
||||
"is_standard": 1,
|
||||
"modified": "2020-07-23 13:42:44.912551",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Location-wise Asset Value",
|
||||
"number_of_groups": 0,
|
||||
"owner": "Administrator",
|
||||
"report_name": "Fixed Asset Register",
|
||||
"timeseries": 0,
|
||||
"type": "Donut",
|
||||
"use_report_chart": 0,
|
||||
"x_field": "location",
|
||||
"y_axis": [
|
||||
{
|
||||
"y_field": "asset_value"
|
||||
}
|
||||
]
|
||||
}
|
21
erpnext/assets/number_card/asset_value/asset_value.json
Normal file
21
erpnext/assets/number_card/asset_value/asset_value.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"aggregate_function_based_on": "value_after_depreciation",
|
||||
"creation": "2020-07-14 18:23:53.302457",
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Asset",
|
||||
"filters_json": "[]",
|
||||
"function": "Sum",
|
||||
"idx": 0,
|
||||
"is_public": 1,
|
||||
"is_standard": 1,
|
||||
"label": "Asset Value",
|
||||
"modified": "2020-07-21 18:13:47.647997",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Value",
|
||||
"owner": "Administrator",
|
||||
"show_percentage_stats": 1,
|
||||
"stats_time_interval": "Monthly",
|
||||
"type": "Document Type"
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"creation": "2020-07-14 18:23:53.267919",
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Asset",
|
||||
"filters_json": "[[\"Asset\",\"creation\",\"Timespan\",\"this year\",false]]",
|
||||
"function": "Count",
|
||||
"idx": 0,
|
||||
"is_public": 1,
|
||||
"is_standard": 1,
|
||||
"label": "New Assets (This Year)",
|
||||
"modified": "2020-07-23 13:45:20.418766",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "New Assets (This Year)",
|
||||
"owner": "Administrator",
|
||||
"show_percentage_stats": 1,
|
||||
"stats_time_interval": "Monthly",
|
||||
"type": "Document Type"
|
||||
}
|
20
erpnext/assets/number_card/total_assets/total_assets.json
Normal file
20
erpnext/assets/number_card/total_assets/total_assets.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"creation": "2020-07-14 18:23:53.233328",
|
||||
"docstatus": 0,
|
||||
"doctype": "Number Card",
|
||||
"document_type": "Asset",
|
||||
"filters_json": "[]",
|
||||
"function": "Count",
|
||||
"idx": 0,
|
||||
"is_public": 1,
|
||||
"is_standard": 1,
|
||||
"label": "Total Assets",
|
||||
"modified": "2020-07-21 18:12:51.664292",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Total Assets",
|
||||
"owner": "Administrator",
|
||||
"show_percentage_stats": 1,
|
||||
"stats_time_interval": "Monthly",
|
||||
"type": "Document Type"
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "format:MAIL-CAMP-{YYYY}-{#####}",
|
||||
"creation": "2019-06-30 16:05:30.015615",
|
||||
"doctype": "DocType",
|
||||
@ -52,7 +53,7 @@
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Email Campaign For ",
|
||||
"options": "\nLead\nContact",
|
||||
"options": "\nLead\nContact\nEmail Group",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@ -70,7 +71,8 @@
|
||||
"options": "User"
|
||||
}
|
||||
],
|
||||
"modified": "2019-11-11 17:18:47.342839",
|
||||
"links": [],
|
||||
"modified": "2020-07-15 12:43:25.548682",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Email Campaign",
|
||||
|
@ -70,10 +70,15 @@ def send_email_to_leads_or_contacts():
|
||||
send_mail(entry, email_campaign)
|
||||
|
||||
def send_mail(entry, email_campaign):
|
||||
recipient = frappe.db.get_value(email_campaign.email_campaign_for, email_campaign.get("recipient"), 'email_id')
|
||||
recipient_list = []
|
||||
if email_campaign.email_campaign_for == "Email Group":
|
||||
for member in frappe.db.get_list("Email Group Member", filters={"email_group": email_campaign.get("recipient")}, fields=["email"]):
|
||||
recipient_list.append(member['email'])
|
||||
else:
|
||||
recipient_list.append(frappe.db.get_value(email_campaign.email_campaign_for, email_campaign.get("recipient"), "email_id"))
|
||||
|
||||
email_template = frappe.get_doc("Email Template", entry.get("email_template"))
|
||||
sender = frappe.db.get_value("User", email_campaign.get("sender"), 'email')
|
||||
sender = frappe.db.get_value("User", email_campaign.get("sender"), "email")
|
||||
context = {"doc": frappe.get_doc(email_campaign.email_campaign_for, email_campaign.recipient)}
|
||||
# send mail and link communication to document
|
||||
comm = make(
|
||||
@ -82,7 +87,7 @@ def send_mail(entry, email_campaign):
|
||||
subject = frappe.render_template(email_template.get("subject"), context),
|
||||
content = frappe.render_template(email_template.get("response"), context),
|
||||
sender = sender,
|
||||
recipients = recipient,
|
||||
recipients = recipient_list,
|
||||
communication_medium = "Email",
|
||||
sent_or_received = "Sent",
|
||||
send_email = True,
|
||||
|
@ -152,7 +152,7 @@ def get_fee_components(fee_structure):
|
||||
:param fee_structure: Fee Structure.
|
||||
"""
|
||||
if fee_structure:
|
||||
fs = frappe.get_list("Fee Component", fields=["fees_category", "amount"] , filters={"parent": fee_structure}, order_by= "idx")
|
||||
fs = frappe.get_list("Fee Component", fields=["fees_category", "description", "amount"] , filters={"parent": fee_structure}, order_by= "idx")
|
||||
return fs
|
||||
|
||||
|
||||
|
@ -162,6 +162,7 @@ frappe.ui.form.on("Fees", {
|
||||
$.each(r.message, function(i, d) {
|
||||
var row = frappe.model.add_child(frm.doc, "Fee Component", "components");
|
||||
row.fees_category = d.fees_category;
|
||||
row.description = d.description;
|
||||
row.amount = d.amount;
|
||||
});
|
||||
}
|
||||
|
@ -38,7 +38,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Records and History",
|
||||
"links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]"
|
||||
"links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
@ -64,7 +64,7 @@
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Healthcare",
|
||||
"modified": "2020-05-28 19:02:28.824995",
|
||||
"modified": "2020-06-25 23:50:56.951698",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Healthcare",
|
||||
|
@ -172,3 +172,15 @@ def get_patient_detail(patient):
|
||||
if vital_sign:
|
||||
details.update(vital_sign[0])
|
||||
return details
|
||||
|
||||
def get_timeline_data(doctype, name):
|
||||
"""Return timeline data from medical records"""
|
||||
return dict(frappe.db.sql('''
|
||||
SELECT
|
||||
unix_timestamp(communication_date), count(*)
|
||||
FROM
|
||||
`tabPatient Medical Record`
|
||||
WHERE
|
||||
patient=%s
|
||||
and `communication_date` > date_sub(curdate(), interval 1 year)
|
||||
GROUP BY communication_date''', name))
|
||||
|
@ -63,7 +63,8 @@
|
||||
{
|
||||
"fieldname": "assessment_datetime",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Assessment Datetime"
|
||||
"label": "Assessment Datetime",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_7",
|
||||
@ -139,7 +140,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-25 14:38:38.302399",
|
||||
"modified": "2020-06-25 00:25:13.208400",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Patient Assessment",
|
||||
|
@ -5,6 +5,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import today
|
||||
|
||||
class TherapyPlan(Document):
|
||||
def validate(self):
|
||||
@ -45,4 +46,6 @@ def make_therapy_session(therapy_plan, patient, therapy_type):
|
||||
therapy_session.rate = therapy_type.rate
|
||||
therapy_session.exercises = therapy_type.exercises
|
||||
|
||||
if frappe.flags.in_test:
|
||||
therapy_session.start_date = today()
|
||||
return therapy_session.as_dict()
|
@ -154,7 +154,8 @@
|
||||
{
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Start Date"
|
||||
"label": "Start Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "start_time",
|
||||
@ -219,7 +220,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-29 14:33:34.836594",
|
||||
"modified": "2020-06-30 10:56:10.354268",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Therapy Session",
|
||||
|
165
erpnext/healthcare/page/patient_progress/patient_progress.css
Normal file
165
erpnext/healthcare/page/patient_progress/patient_progress.css
Normal file
@ -0,0 +1,165 @@
|
||||
/* sidebar */
|
||||
|
||||
.layout-side-section .frappe-control[data-fieldname='patient'] {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.patient-image-container {
|
||||
margin-top: 17px;
|
||||
}
|
||||
|
||||
.patient-image {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
padding: 50% 0px;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.patient-details {
|
||||
margin: -5px 5px;
|
||||
}
|
||||
|
||||
.important-links {
|
||||
margin: 30px 5px;
|
||||
}
|
||||
|
||||
.patient-name {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* heatmap */
|
||||
|
||||
.heatmap-container {
|
||||
height: 170px;
|
||||
}
|
||||
|
||||
.patient-heatmap {
|
||||
width: 80%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.patient-heatmap .chart-container {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.patient-heatmap .frappe-chart {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.patient-heatmap .frappe-chart .chart-legend {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.heatmap-container .chart-filter {
|
||||
position: relative;
|
||||
top: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* percentage chart */
|
||||
|
||||
.percentage-chart-container {
|
||||
height: 130px;
|
||||
}
|
||||
|
||||
.percentage-chart-container .chart-filter {
|
||||
position: relative;
|
||||
top: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.therapy-session-percentage-chart .frappe-chart {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
/* line charts */
|
||||
|
||||
.date-field .clearfix {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.date-field .help-box {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.date-field .frappe-control {
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
.date-field .form-group {
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
/* common */
|
||||
|
||||
text.title {
|
||||
text-transform: uppercase;
|
||||
font-size: 11px;
|
||||
margin-left: 20px;
|
||||
margin-top: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.chart-filter-search {
|
||||
margin-left: 35px;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.chart-column-container {
|
||||
border-bottom: 1px solid #d1d8dd;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.line-chart-container .frappe-chart {
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
.line-chart-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-control {
|
||||
align-self: center;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
margin-top: -25px;
|
||||
}
|
||||
|
||||
.chart-control > * {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* mobile */
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.patient-progress-sidebar {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.percentage-chart-container {
|
||||
border-top: 1px solid #d1d8dd;
|
||||
}
|
||||
|
||||
.percentage-chart-container .chart-filter {
|
||||
position: relative;
|
||||
top: 12px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.patient-progress-sidebar .important-links {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.patient-progress-sidebar .patient-details {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.chart-filter-search {
|
||||
width: 40%;
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
<div class="row patient-progress">
|
||||
<div class="col-md-12">
|
||||
<div class="progress-graphs">
|
||||
<div class="chart-column-container heatmap-container hidden-xs hidden-sm">
|
||||
<div class="patient-heatmap"></div>
|
||||
</div>
|
||||
<div class="chart-column-container percentage-chart-container">
|
||||
<div class="therapy-session-percentage-chart"></div>
|
||||
</div>
|
||||
|
||||
<div class="therapy-progress">
|
||||
<div class="chart-head">
|
||||
<text class="title" text-anchor="start">Therapy Progress</text>
|
||||
<div class="chart-control pull-right"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="chart-filter-search therapy-type-search"></div>
|
||||
</div>
|
||||
<div class="col-md-12 chart-column-container line-chart-container">
|
||||
<div class="therapy-progress-line-chart">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="assessment-results">
|
||||
<div class="chart-head">
|
||||
<text class="title" text-anchor="start">Assessment Results</text>
|
||||
<div class="chart-control pull-right"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="chart-filter-search assessment-template-search"></div>
|
||||
</div>
|
||||
<div class="col-md-12 chart-column-container line-chart-container">
|
||||
<div class="assessment-results-line-chart">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="therapy-assessment-correlation progress-line-chart">
|
||||
<div class="chart-head">
|
||||
<text class="title" text-anchor="start">Therapy Type and Assessment Correlation</text>
|
||||
<div class="chart-control pull-right"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="chart-filter-search assessment-correlation-template-search"></div>
|
||||
</div>
|
||||
<div class="col-md-12 chart-column-container line-chart-container">
|
||||
<div class="therapy-assessment-correlation-chart">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="assessment-parameter-progress progress-line-chart">
|
||||
<div class="chart-head">
|
||||
<text class="title" text-anchor="start">Assessment Parameter Wise Progress</text>
|
||||
<div class="chart-control pull-right"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="chart-filter-search assessment-parameter-search"></div>
|
||||
</div>
|
||||
<div class="col-md-12 line-chart-container">
|
||||
<div class="assessment-parameter-progress-chart">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
531
erpnext/healthcare/page/patient_progress/patient_progress.js
Normal file
531
erpnext/healthcare/page/patient_progress/patient_progress.js
Normal file
@ -0,0 +1,531 @@
|
||||
frappe.pages['patient-progress'].on_page_load = function(wrapper) {
|
||||
|
||||
frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: __('Patient Progress')
|
||||
});
|
||||
|
||||
let patient_progress = new PatientProgress(wrapper);
|
||||
$(wrapper).bind('show', ()=> {
|
||||
patient_progress.show();
|
||||
});
|
||||
};
|
||||
|
||||
class PatientProgress {
|
||||
|
||||
constructor(wrapper) {
|
||||
this.wrapper = $(wrapper);
|
||||
this.page = wrapper.page;
|
||||
this.sidebar = this.wrapper.find('.layout-side-section');
|
||||
this.main_section = this.wrapper.find('.layout-main-section');
|
||||
}
|
||||
|
||||
show() {
|
||||
frappe.breadcrumbs.add('Healthcare');
|
||||
this.sidebar.empty();
|
||||
|
||||
let me = this;
|
||||
let patient = frappe.ui.form.make_control({
|
||||
parent: me.sidebar,
|
||||
df: {
|
||||
fieldtype: 'Link',
|
||||
options: 'Patient',
|
||||
fieldname: 'patient',
|
||||
placeholder: __('Select Patient'),
|
||||
only_select: true,
|
||||
change: () => {
|
||||
me.patient_id = '';
|
||||
if (me.patient_id != patient.get_value() && patient.get_value()) {
|
||||
me.start = 0;
|
||||
me.patient_id = patient.get_value();
|
||||
me.make_patient_profile();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
patient.refresh();
|
||||
|
||||
if (frappe.route_options && !this.patient) {
|
||||
patient.set_value(frappe.route_options.patient);
|
||||
this.patient_id = frappe.route_options.patient;
|
||||
}
|
||||
|
||||
this.sidebar.find('[data-fieldname="patient"]').append('<div class="patient-info"></div>');
|
||||
}
|
||||
|
||||
make_patient_profile() {
|
||||
this.page.set_title(__('Patient Progress'));
|
||||
this.main_section.empty().append(frappe.render_template('patient_progress'));
|
||||
this.render_patient_details();
|
||||
this.render_heatmap();
|
||||
this.render_percentage_chart('therapy_type', 'Therapy Type Distribution');
|
||||
this.create_percentage_chart_filters();
|
||||
this.show_therapy_progress();
|
||||
this.show_assessment_results();
|
||||
this.show_therapy_assessment_correlation();
|
||||
this.show_assessment_parameter_progress();
|
||||
}
|
||||
|
||||
get_patient_info() {
|
||||
return frappe.xcall('frappe.client.get', {
|
||||
doctype: 'Patient',
|
||||
name: this.patient_id
|
||||
}).then((patient) => {
|
||||
if (patient) {
|
||||
this.patient = patient;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get_therapy_sessions_count() {
|
||||
return frappe.xcall(
|
||||
'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_sessions_count', {
|
||||
patient: this.patient_id,
|
||||
}
|
||||
).then(data => {
|
||||
if (data) {
|
||||
this.total_therapy_sessions = data.total_therapy_sessions;
|
||||
this.therapy_sessions_this_month = data.therapy_sessions_this_month;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render_patient_details() {
|
||||
this.get_patient_info().then(() => {
|
||||
this.get_therapy_sessions_count().then(() => {
|
||||
$('.patient-info').empty().append(frappe.render_template('patient_progress_sidebar', {
|
||||
patient_image: this.patient.image,
|
||||
patient_name: this.patient.patient_name,
|
||||
patient_gender: this.patient.sex,
|
||||
patient_mobile: this.patient.mobile,
|
||||
total_therapy_sessions: this.total_therapy_sessions,
|
||||
therapy_sessions_this_month: this.therapy_sessions_this_month
|
||||
}));
|
||||
|
||||
this.setup_patient_profile_links();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setup_patient_profile_links() {
|
||||
this.wrapper.find('.patient-profile-link').on('click', () => {
|
||||
frappe.set_route('Form', 'Patient', this.patient_id);
|
||||
});
|
||||
|
||||
this.wrapper.find('.therapy-plan-link').on('click', () => {
|
||||
frappe.route_options = {
|
||||
'patient': this.patient_id,
|
||||
'docstatus': 1
|
||||
};
|
||||
frappe.set_route('List', 'Therapy Plan');
|
||||
});
|
||||
|
||||
this.wrapper.find('.patient-history').on('click', () => {
|
||||
frappe.route_options = {
|
||||
'patient': this.patient_id
|
||||
};
|
||||
frappe.set_route('patient_history');
|
||||
});
|
||||
}
|
||||
|
||||
render_heatmap() {
|
||||
this.heatmap = new frappe.Chart('.patient-heatmap', {
|
||||
type: 'heatmap',
|
||||
countLabel: 'Interactions',
|
||||
data: {},
|
||||
discreteDomains: 0
|
||||
});
|
||||
this.update_heatmap_data();
|
||||
this.create_heatmap_chart_filters();
|
||||
}
|
||||
|
||||
update_heatmap_data(date_from) {
|
||||
frappe.xcall('erpnext.healthcare.page.patient_progress.patient_progress.get_patient_heatmap_data', {
|
||||
patient: this.patient_id,
|
||||
date: date_from || frappe.datetime.year_start(),
|
||||
}).then((data) => {
|
||||
this.heatmap.update( {dataPoints: data} );
|
||||
});
|
||||
}
|
||||
|
||||
create_heatmap_chart_filters() {
|
||||
this.get_patient_info().then(() => {
|
||||
let filters = [
|
||||
{
|
||||
label: frappe.dashboard_utils.get_year(frappe.datetime.now_date()),
|
||||
options: frappe.dashboard_utils.get_years_since_creation(this.patient.creation),
|
||||
action: (selected_item) => {
|
||||
this.update_heatmap_data(frappe.datetime.obj_to_str(selected_item));
|
||||
}
|
||||
},
|
||||
];
|
||||
frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.heatmap-container');
|
||||
});
|
||||
}
|
||||
|
||||
render_percentage_chart(field, title) {
|
||||
frappe.xcall(
|
||||
'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_sessions_distribution_data', {
|
||||
patient: this.patient_id,
|
||||
field: field
|
||||
}
|
||||
).then(chart => {
|
||||
if (chart.labels.length) {
|
||||
this.percentage_chart = new frappe.Chart('.therapy-session-percentage-chart', {
|
||||
title: title,
|
||||
type: 'percentage',
|
||||
data: {
|
||||
labels: chart.labels,
|
||||
datasets: chart.datasets
|
||||
},
|
||||
truncateLegends: 1,
|
||||
barOptions: {
|
||||
height: 11,
|
||||
depth: 1
|
||||
},
|
||||
height: 160,
|
||||
maxSlices: 8,
|
||||
colors: ['#5e64ff', '#743ee2', '#ff5858', '#ffa00a', '#feef72', '#28a745', '#98d85b', '#a9a7ac'],
|
||||
});
|
||||
} else {
|
||||
this.wrapper.find('.percentage-chart-container').hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
create_percentage_chart_filters() {
|
||||
let filters = [
|
||||
{
|
||||
label: 'Therapy Type',
|
||||
options: ['Therapy Type', 'Exercise Type'],
|
||||
fieldnames: ['therapy_type', 'exercise_type'],
|
||||
action: (selected_item, fieldname) => {
|
||||
let title = selected_item + ' Distribution';
|
||||
this.render_percentage_chart(fieldname, title);
|
||||
}
|
||||
},
|
||||
];
|
||||
frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.percentage-chart-container');
|
||||
}
|
||||
|
||||
create_time_span_filters(action_method, parent) {
|
||||
let chart_control = $(parent).find('.chart-control');
|
||||
let filters = [
|
||||
{
|
||||
label: 'Last Month',
|
||||
options: ['Select Date Range', 'Last Week', 'Last Month', 'Last Quarter', 'Last Year'],
|
||||
action: (selected_item) => {
|
||||
if (selected_item === 'Select Date Range') {
|
||||
this.render_date_range_fields(action_method, chart_control);
|
||||
} else {
|
||||
// hide date range field if visible
|
||||
let date_field = $(parent).find('.date-field');
|
||||
if (date_field.is(':visible')) {
|
||||
date_field.hide();
|
||||
}
|
||||
this[action_method](selected_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', chart_control, 1);
|
||||
}
|
||||
|
||||
render_date_range_fields(action_method, parent) {
|
||||
let date_field = $(parent).find('.date-field');
|
||||
|
||||
if (!date_field.length) {
|
||||
let date_field_wrapper = $(
|
||||
`<div class="date-field pull-right"></div>`
|
||||
).appendTo(parent);
|
||||
|
||||
let date_range_field = frappe.ui.form.make_control({
|
||||
df: {
|
||||
fieldtype: 'DateRange',
|
||||
fieldname: 'from_date',
|
||||
placeholder: 'Date Range',
|
||||
input_class: 'input-xs',
|
||||
reqd: 1,
|
||||
change: () => {
|
||||
let selected_date_range = date_range_field.get_value();
|
||||
if (selected_date_range && selected_date_range.length === 2) {
|
||||
this[action_method](selected_date_range);
|
||||
}
|
||||
}
|
||||
},
|
||||
parent: date_field_wrapper,
|
||||
render_input: 1
|
||||
});
|
||||
} else if (!date_field.is(':visible')) {
|
||||
date_field.show();
|
||||
}
|
||||
}
|
||||
|
||||
show_therapy_progress() {
|
||||
let me = this;
|
||||
let therapy_type = frappe.ui.form.make_control({
|
||||
parent: $('.therapy-type-search'),
|
||||
df: {
|
||||
fieldtype: 'Link',
|
||||
options: 'Therapy Type',
|
||||
fieldname: 'therapy_type',
|
||||
placeholder: __('Select Therapy Type'),
|
||||
only_select: true,
|
||||
change: () => {
|
||||
if (me.therapy_type != therapy_type.get_value() && therapy_type.get_value()) {
|
||||
me.therapy_type = therapy_type.get_value();
|
||||
me.render_therapy_progress_chart();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
therapy_type.refresh();
|
||||
this.create_time_span_filters('render_therapy_progress_chart', '.therapy-progress');
|
||||
}
|
||||
|
||||
render_therapy_progress_chart(time_span='Last Month') {
|
||||
if (!this.therapy_type) return;
|
||||
|
||||
frappe.xcall(
|
||||
'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_progress_data', {
|
||||
patient: this.patient_id,
|
||||
therapy_type: this.therapy_type,
|
||||
time_span: time_span
|
||||
}
|
||||
).then(chart => {
|
||||
let data = {
|
||||
labels: chart.labels,
|
||||
datasets: chart.datasets
|
||||
}
|
||||
let parent = '.therapy-progress-line-chart';
|
||||
if (!chart.labels.length) {
|
||||
this.show_null_state(parent);
|
||||
} else {
|
||||
if (!this.therapy_line_chart) {
|
||||
this.therapy_line_chart = new frappe.Chart(parent, {
|
||||
type: 'axis-mixed',
|
||||
height: 250,
|
||||
data: data,
|
||||
lineOptions: {
|
||||
regionFill: 1
|
||||
},
|
||||
axisOptions: {
|
||||
xIsSeries: 1
|
||||
},
|
||||
});
|
||||
} else {
|
||||
$(parent).find('.chart-container').show();
|
||||
$(parent).find('.chart-empty-state').hide();
|
||||
this.therapy_line_chart.update(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
show_assessment_results() {
|
||||
let me = this;
|
||||
let assessment_template = frappe.ui.form.make_control({
|
||||
parent: $('.assessment-template-search'),
|
||||
df: {
|
||||
fieldtype: 'Link',
|
||||
options: 'Patient Assessment Template',
|
||||
fieldname: 'assessment_template',
|
||||
placeholder: __('Select Assessment Template'),
|
||||
only_select: true,
|
||||
change: () => {
|
||||
if (me.assessment_template != assessment_template.get_value() && assessment_template.get_value()) {
|
||||
me.assessment_template = assessment_template.get_value();
|
||||
me.render_assessment_result_chart();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
assessment_template.refresh();
|
||||
this.create_time_span_filters('render_assessment_result_chart', '.assessment-results');
|
||||
}
|
||||
|
||||
render_assessment_result_chart(time_span='Last Month') {
|
||||
if (!this.assessment_template) return;
|
||||
|
||||
frappe.xcall(
|
||||
'erpnext.healthcare.page.patient_progress.patient_progress.get_patient_assessment_data', {
|
||||
patient: this.patient_id,
|
||||
assessment_template: this.assessment_template,
|
||||
time_span: time_span
|
||||
}
|
||||
).then(chart => {
|
||||
let data = {
|
||||
labels: chart.labels,
|
||||
datasets: chart.datasets,
|
||||
yMarkers: [
|
||||
{ label: 'Max Score', value: chart.max_score }
|
||||
],
|
||||
}
|
||||
let parent = '.assessment-results-line-chart';
|
||||
if (!chart.labels.length) {
|
||||
this.show_null_state(parent);
|
||||
} else {
|
||||
if (!this.assessment_line_chart) {
|
||||
this.assessment_line_chart = new frappe.Chart(parent, {
|
||||
type: 'axis-mixed',
|
||||
height: 250,
|
||||
data: data,
|
||||
lineOptions: {
|
||||
regionFill: 1
|
||||
},
|
||||
axisOptions: {
|
||||
xIsSeries: 1
|
||||
},
|
||||
tooltipOptions: {
|
||||
formatTooltipY: d => d + __(' out of ') + chart.max_score
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$(parent).find('.chart-container').show();
|
||||
$(parent).find('.chart-empty-state').hide();
|
||||
this.assessment_line_chart.update(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
show_therapy_assessment_correlation() {
|
||||
let me = this;
|
||||
let assessment = frappe.ui.form.make_control({
|
||||
parent: $('.assessment-correlation-template-search'),
|
||||
df: {
|
||||
fieldtype: 'Link',
|
||||
options: 'Patient Assessment Template',
|
||||
fieldname: 'assessment',
|
||||
placeholder: __('Select Assessment Template'),
|
||||
only_select: true,
|
||||
change: () => {
|
||||
if (me.assessment != assessment.get_value() && assessment.get_value()) {
|
||||
me.assessment = assessment.get_value();
|
||||
me.render_therapy_assessment_correlation_chart();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
assessment.refresh();
|
||||
this.create_time_span_filters('render_therapy_assessment_correlation_chart', '.therapy-assessment-correlation');
|
||||
}
|
||||
|
||||
render_therapy_assessment_correlation_chart(time_span='Last Month') {
|
||||
if (!this.assessment) return;
|
||||
|
||||
frappe.xcall(
|
||||
'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_assessment_correlation_data', {
|
||||
patient: this.patient_id,
|
||||
assessment_template: this.assessment,
|
||||
time_span: time_span
|
||||
}
|
||||
).then(chart => {
|
||||
let data = {
|
||||
labels: chart.labels,
|
||||
datasets: chart.datasets,
|
||||
yMarkers: [
|
||||
{ label: 'Max Score', value: chart.max_score }
|
||||
],
|
||||
}
|
||||
let parent = '.therapy-assessment-correlation-chart';
|
||||
if (!chart.labels.length) {
|
||||
this.show_null_state(parent);
|
||||
} else {
|
||||
if (!this.correlation_chart) {
|
||||
this.correlation_chart = new frappe.Chart(parent, {
|
||||
type: 'axis-mixed',
|
||||
height: 300,
|
||||
data: data,
|
||||
axisOptions: {
|
||||
xIsSeries: 1
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$(parent).find('.chart-container').show();
|
||||
$(parent).find('.chart-empty-state').hide();
|
||||
this.correlation_chart.update(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
show_assessment_parameter_progress() {
|
||||
let me = this;
|
||||
let parameter = frappe.ui.form.make_control({
|
||||
parent: $('.assessment-parameter-search'),
|
||||
df: {
|
||||
fieldtype: 'Link',
|
||||
options: 'Patient Assessment Parameter',
|
||||
fieldname: 'assessment',
|
||||
placeholder: __('Select Assessment Parameter'),
|
||||
only_select: true,
|
||||
change: () => {
|
||||
if (me.parameter != parameter.get_value() && parameter.get_value()) {
|
||||
me.parameter = parameter.get_value();
|
||||
me.render_assessment_parameter_progress_chart();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
parameter.refresh();
|
||||
this.create_time_span_filters('render_assessment_parameter_progress_chart', '.assessment-parameter-progress');
|
||||
}
|
||||
|
||||
render_assessment_parameter_progress_chart(time_span='Last Month') {
|
||||
if (!this.parameter) return;
|
||||
|
||||
frappe.xcall(
|
||||
'erpnext.healthcare.page.patient_progress.patient_progress.get_assessment_parameter_data', {
|
||||
patient: this.patient_id,
|
||||
parameter: this.parameter,
|
||||
time_span: time_span
|
||||
}
|
||||
).then(chart => {
|
||||
let data = {
|
||||
labels: chart.labels,
|
||||
datasets: chart.datasets
|
||||
}
|
||||
let parent = '.assessment-parameter-progress-chart';
|
||||
if (!chart.labels.length) {
|
||||
this.show_null_state(parent);
|
||||
} else {
|
||||
if (!this.parameter_chart) {
|
||||
this.parameter_chart = new frappe.Chart(parent, {
|
||||
type: 'line',
|
||||
height: 250,
|
||||
data: data,
|
||||
lineOptions: {
|
||||
regionFill: 1
|
||||
},
|
||||
axisOptions: {
|
||||
xIsSeries: 1
|
||||
},
|
||||
tooltipOptions: {
|
||||
formatTooltipY: d => d + '%'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$(parent).find('.chart-container').show();
|
||||
$(parent).find('.chart-empty-state').hide();
|
||||
this.parameter_chart.update(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
show_null_state(parent) {
|
||||
let null_state = $(parent).find('.chart-empty-state');
|
||||
if (null_state.length) {
|
||||
$(null_state).show();
|
||||
} else {
|
||||
null_state = $(
|
||||
`<div class="chart-empty-state text-muted text-center" style="margin-bottom: 20px;">${__(
|
||||
"No Data..."
|
||||
)}</div>`
|
||||
);
|
||||
$(parent).append(null_state);
|
||||
}
|
||||
$(parent).find('.chart-container').hide();
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
{
|
||||
"content": null,
|
||||
"creation": "2020-06-12 15:46:23.111928",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2020-07-23 21:45:45.540055",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "patient-progress",
|
||||
"owner": "Administrator",
|
||||
"page_name": "patient-progress",
|
||||
"restrict_to_domain": "Healthcare",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Healthcare Administrator"
|
||||
},
|
||||
{
|
||||
"role": "Physician"
|
||||
},
|
||||
{
|
||||
"role": "Patient"
|
||||
},
|
||||
{
|
||||
"role": "System Manager"
|
||||
}
|
||||
],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0,
|
||||
"title": "Patient Progress"
|
||||
}
|
197
erpnext/healthcare/page/patient_progress/patient_progress.py
Normal file
197
erpnext/healthcare/page/patient_progress/patient_progress.py
Normal file
@ -0,0 +1,197 @@
|
||||
import frappe
|
||||
from datetime import datetime
|
||||
from frappe import _
|
||||
from frappe.utils import getdate, get_timespan_date_range
|
||||
import json
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_therapy_sessions_count(patient):
|
||||
total = frappe.db.count('Therapy Session', filters={
|
||||
'docstatus': 1,
|
||||
'patient': patient
|
||||
})
|
||||
|
||||
month_start = datetime.today().replace(day=1)
|
||||
this_month = frappe.db.count('Therapy Session', filters={
|
||||
'creation': ['>', month_start],
|
||||
'docstatus': 1,
|
||||
'patient': patient
|
||||
})
|
||||
|
||||
return {
|
||||
'total_therapy_sessions': total,
|
||||
'therapy_sessions_this_month': this_month
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_patient_heatmap_data(patient, date):
|
||||
return dict(frappe.db.sql("""
|
||||
SELECT
|
||||
unix_timestamp(communication_date), count(*)
|
||||
FROM
|
||||
`tabPatient Medical Record`
|
||||
WHERE
|
||||
communication_date > subdate(%(date)s, interval 1 year) and
|
||||
communication_date < subdate(%(date)s, interval -1 year) and
|
||||
patient = %(patient)s
|
||||
GROUP BY communication_date
|
||||
ORDER BY communication_date asc""", {'date': date, 'patient': patient}))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_therapy_sessions_distribution_data(patient, field):
|
||||
if field == 'therapy_type':
|
||||
result = frappe.db.get_all('Therapy Session',
|
||||
filters = {'patient': patient, 'docstatus': 1},
|
||||
group_by = field,
|
||||
order_by = field,
|
||||
fields = [field, 'count(*)'],
|
||||
as_list = True)
|
||||
|
||||
elif field == 'exercise_type':
|
||||
data = frappe.db.get_all('Therapy Session', filters={
|
||||
'docstatus': 1,
|
||||
'patient': patient
|
||||
}, as_list=True)
|
||||
therapy_sessions = [entry[0] for entry in data]
|
||||
|
||||
result = frappe.db.get_all('Exercise',
|
||||
filters = {
|
||||
'parenttype': 'Therapy Session',
|
||||
'parent': ['in', therapy_sessions],
|
||||
'docstatus': 1
|
||||
},
|
||||
group_by = field,
|
||||
order_by = field,
|
||||
fields = [field, 'count(*)'],
|
||||
as_list = True)
|
||||
|
||||
return {
|
||||
'labels': [r[0] for r in result if r[0] != None],
|
||||
'datasets': [{
|
||||
'values': [r[1] for r in result]
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_therapy_progress_data(patient, therapy_type, time_span):
|
||||
date_range = get_date_range(time_span)
|
||||
query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'therapy_type': therapy_type, 'patient': patient}
|
||||
result = frappe.db.sql("""
|
||||
SELECT
|
||||
start_date, total_counts_targeted, total_counts_completed
|
||||
FROM
|
||||
`tabTherapy Session`
|
||||
WHERE
|
||||
start_date BETWEEN %(from_date)s AND %(to_date)s and
|
||||
docstatus = 1 and
|
||||
therapy_type = %(therapy_type)s and
|
||||
patient = %(patient)s
|
||||
ORDER BY start_date""", query_values, as_list=1)
|
||||
|
||||
return {
|
||||
'labels': [r[0] for r in result if r[0] != None],
|
||||
'datasets': [
|
||||
{ 'name': _('Targetted'), 'values': [r[1] for r in result if r[0] != None] },
|
||||
{ 'name': _('Completed'), 'values': [r[2] for r in result if r[0] != None] }
|
||||
]
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_patient_assessment_data(patient, assessment_template, time_span):
|
||||
date_range = get_date_range(time_span)
|
||||
query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'assessment_template': assessment_template, 'patient': patient}
|
||||
result = frappe.db.sql("""
|
||||
SELECT
|
||||
assessment_datetime, total_score, total_score_obtained
|
||||
FROM
|
||||
`tabPatient Assessment`
|
||||
WHERE
|
||||
DATE(assessment_datetime) BETWEEN %(from_date)s AND %(to_date)s and
|
||||
docstatus = 1 and
|
||||
assessment_template = %(assessment_template)s and
|
||||
patient = %(patient)s
|
||||
ORDER BY assessment_datetime""", query_values, as_list=1)
|
||||
|
||||
return {
|
||||
'labels': [getdate(r[0]) for r in result if r[0] != None],
|
||||
'datasets': [
|
||||
{ 'name': _('Score Obtained'), 'values': [r[2] for r in result if r[0] != None] }
|
||||
],
|
||||
'max_score': result[0][1] if result else None
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_therapy_assessment_correlation_data(patient, assessment_template, time_span):
|
||||
date_range = get_date_range(time_span)
|
||||
query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'assessment': assessment_template, 'patient': patient}
|
||||
result = frappe.db.sql("""
|
||||
SELECT
|
||||
therapy.therapy_type, count(*), avg(assessment.total_score_obtained), total_score
|
||||
FROM
|
||||
`tabPatient Assessment` assessment INNER JOIN `tabTherapy Session` therapy
|
||||
ON
|
||||
assessment.therapy_session = therapy.name
|
||||
WHERE
|
||||
DATE(assessment.assessment_datetime) BETWEEN %(from_date)s AND %(to_date)s and
|
||||
assessment.docstatus = 1 and
|
||||
assessment.patient = %(patient)s and
|
||||
assessment.assessment_template = %(assessment)s
|
||||
GROUP BY therapy.therapy_type
|
||||
""", query_values, as_list=1)
|
||||
|
||||
return {
|
||||
'labels': [r[0] for r in result if r[0] != None],
|
||||
'datasets': [
|
||||
{ 'name': _('Sessions'), 'chartType': 'bar', 'values': [r[1] for r in result if r[0] != None] },
|
||||
{ 'name': _('Average Score'), 'chartType': 'line', 'values': [round(r[2], 2) for r in result if r[0] != None] }
|
||||
],
|
||||
'max_score': result[0][1] if result else None
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_assessment_parameter_data(patient, parameter, time_span):
|
||||
date_range = get_date_range(time_span)
|
||||
query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'parameter': parameter, 'patient': patient}
|
||||
results = frappe.db.sql("""
|
||||
SELECT
|
||||
assessment.assessment_datetime,
|
||||
sheet.score,
|
||||
template.scale_max
|
||||
FROM
|
||||
`tabPatient Assessment Sheet` sheet
|
||||
INNER JOIN `tabPatient Assessment` assessment
|
||||
ON sheet.parent = assessment.name
|
||||
INNER JOIN `tabPatient Assessment Template` template
|
||||
ON template.name = assessment.assessment_template
|
||||
WHERE
|
||||
DATE(assessment.assessment_datetime) BETWEEN %(from_date)s AND %(to_date)s and
|
||||
assessment.docstatus = 1 and
|
||||
sheet.parameter = %(parameter)s and
|
||||
assessment.patient = %(patient)s
|
||||
ORDER BY
|
||||
assessment.assessment_datetime asc
|
||||
""", query_values, as_list=1)
|
||||
|
||||
score_percentages = []
|
||||
for r in results:
|
||||
if r[2] != 0 and r[0] != None:
|
||||
score = round((int(r[1]) / int(r[2])) * 100, 2)
|
||||
score_percentages.append(score)
|
||||
|
||||
return {
|
||||
'labels': [getdate(r[0]) for r in results if r[0] != None],
|
||||
'datasets': [
|
||||
{ 'name': _('Score'), 'values': score_percentages }
|
||||
]
|
||||
}
|
||||
|
||||
def get_date_range(time_span):
|
||||
try:
|
||||
time_span = json.loads(time_span)
|
||||
return time_span
|
||||
except json.decoder.JSONDecodeError:
|
||||
return get_timespan_date_range(time_span.lower())
|
||||
|
@ -0,0 +1,29 @@
|
||||
<div class="patient-progress-sidebar">
|
||||
<div class="patient-image-container">
|
||||
{% if patient_image %}
|
||||
<div class="patient-image" src={{patient_image}} style="background-image: url(\'{%= patient_image %}\')"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="patient-details">
|
||||
{% if patient_name %}
|
||||
<p class="patient-name bold">{{patient_name}}</p>
|
||||
{% endif %}
|
||||
{% if patient_gender %}
|
||||
<p class="patient-gender text-muted">{%=__("Gender: ") %} {{patient_gender}}</p>
|
||||
{% endif %}
|
||||
{% if patient_mobile %}
|
||||
<p class="patient-mobile text-muted">{%=__("Contact: ") %} {{patient_mobile}}</p>
|
||||
{% endif %}
|
||||
{% if total_therapy_sessions %}
|
||||
<p class="patient-sessions text-muted">{%=__("Total Therapy Sessions: ") %} {{total_therapy_sessions}}</p>
|
||||
{% endif %}
|
||||
{% if therapy_sessions_this_month %}
|
||||
<p class="patient-sessions text-muted">{%=__("Monthly Therapy Sessions: ") %} {{therapy_sessions_this_month}}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="important-links">
|
||||
<p><a class="patient-profile-link">{%=__("Patient Profile") %}</a></p>
|
||||
<p><a class="therapy-plan-link">{%=__("Therapy Plan") %}</a></p>
|
||||
<p><a class="patient-history">{%=__("Patient History") %}</a></p>
|
||||
</div>
|
||||
</div>
|
@ -713,6 +713,7 @@ erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-20
|
||||
erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020
|
||||
erpnext.patches.v13_0.loyalty_points_entry_for_pos_invoice #22-07-2020
|
||||
erpnext.patches.v12_0.add_taxjar_integration_field
|
||||
erpnext.patches.v12_0.fix_percent_complete_for_projects
|
||||
erpnext.patches.v13_0.delete_report_requested_items_to_order
|
||||
erpnext.patches.v12_0.update_item_tax_template_company
|
||||
erpnext.patches.v13_0.move_branch_code_to_bank_account
|
||||
|
14
erpnext/patches/v12_0/fix_percent_complete_for_projects.py
Normal file
14
erpnext/patches/v12_0/fix_percent_complete_for_projects.py
Normal file
@ -0,0 +1,14 @@
|
||||
import frappe
|
||||
from frappe.utils import flt
|
||||
|
||||
def execute():
|
||||
for project in frappe.get_all("Project", fields=["name", "percent_complete_method"]):
|
||||
total = frappe.db.count('Task', dict(project=project.name))
|
||||
if project.percent_complete_method == "Task Completion" and total > 0:
|
||||
completed = frappe.db.sql("""select count(name) from tabTask where
|
||||
project=%s and status in ('Cancelled', 'Completed')""", project.name)[0][0]
|
||||
percent_complete = flt(flt(completed) / total * 100, 2)
|
||||
if project.percent_complete != percent_complete:
|
||||
frappe.db.set_value("Project", project.name, "percent_complete", percent_complete)
|
||||
if percent_complete == 100:
|
||||
frappe.db.set_value("Project", project.name, "status", "Completed")
|
@ -869,10 +869,10 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
# other taxes and charges on income tax
|
||||
for d in tax_slab.other_taxes_and_charges:
|
||||
if flt(d.min_taxable_income) and flt(d.min_taxable_income) > tax_amount:
|
||||
if flt(d.min_taxable_income) and flt(d.min_taxable_income) > annual_taxable_earning:
|
||||
continue
|
||||
|
||||
if flt(d.max_taxable_income) and flt(d.max_taxable_income) < tax_amount:
|
||||
if flt(d.max_taxable_income) and flt(d.max_taxable_income) < annual_taxable_earning:
|
||||
continue
|
||||
|
||||
tax_amount += tax_amount * flt(d.percent) / 100
|
||||
|
@ -175,6 +175,9 @@ class Task(NestedSet):
|
||||
|
||||
self.update_nsm_model()
|
||||
|
||||
def after_delete(self):
|
||||
self.update_project()
|
||||
|
||||
def update_status(self):
|
||||
if self.status not in ('Cancelled', 'Completed') and self.exp_end_date:
|
||||
from datetime import datetime
|
||||
|
@ -338,8 +338,8 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
||||
};
|
||||
},
|
||||
change: function () {
|
||||
let val = this.get_value();
|
||||
if (val.length === 0) {
|
||||
const batch_no = this.get_value();
|
||||
if (!batch_no) {
|
||||
this.grid_row.on_grid_fields_dict
|
||||
.available_qty.set_value(0);
|
||||
return;
|
||||
@ -359,14 +359,11 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
let batch_number = me.item.batch_no ||
|
||||
this.grid_row.on_grid_fields_dict.batch_no.get_value();
|
||||
|
||||
if (me.warehouse_details.name) {
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.doctype.batch.batch.get_batch_qty',
|
||||
args: {
|
||||
batch_no: batch_number,
|
||||
batch_no,
|
||||
warehouse: me.warehouse_details.name,
|
||||
item_code: me.item_code
|
||||
},
|
||||
|
@ -36,6 +36,27 @@ frappe.query_reports["Stock Ageing"] = {
|
||||
"fieldtype": "Link",
|
||||
"options": "Brand"
|
||||
},
|
||||
{
|
||||
"fieldname":"range1",
|
||||
"label": __("Ageing Range 1"),
|
||||
"fieldtype": "Int",
|
||||
"default": "30",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"range2",
|
||||
"label": __("Ageing Range 2"),
|
||||
"fieldtype": "Int",
|
||||
"default": "60",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"range3",
|
||||
"label": __("Ageing Range 3"),
|
||||
"fieldtype": "Int",
|
||||
"default": "90",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"show_warehouse_wise_stock",
|
||||
"label": __("Show Warehouse-wise Stock"),
|
||||
|
@ -4,12 +4,11 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import date_diff, flt
|
||||
from frappe.utils import date_diff, flt, cint
|
||||
from six import iteritems
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
def execute(filters=None):
|
||||
|
||||
columns = get_columns(filters)
|
||||
item_details = get_fifo_queue(filters)
|
||||
to_date = filters["to_date"]
|
||||
@ -25,6 +24,7 @@ def execute(filters=None):
|
||||
average_age = get_average_age(fifo_queue, to_date)
|
||||
earliest_age = date_diff(to_date, fifo_queue[0][1])
|
||||
latest_age = date_diff(to_date, fifo_queue[-1][1])
|
||||
range1, range2, range3, above_range3 = get_range_age(filters, fifo_queue, to_date)
|
||||
|
||||
row = [details.name, details.item_name,
|
||||
details.description, details.item_group, details.brand]
|
||||
@ -33,6 +33,7 @@ def execute(filters=None):
|
||||
row.append(details.warehouse)
|
||||
|
||||
row.extend([item_dict.get("total_qty"), average_age,
|
||||
range1, range2, range3, above_range3,
|
||||
earliest_age, latest_age, details.stock_uom])
|
||||
|
||||
data.append(row)
|
||||
@ -55,7 +56,25 @@ def get_average_age(fifo_queue, to_date):
|
||||
|
||||
return flt(age_qty / total_qty, 2) if total_qty else 0.0
|
||||
|
||||
def get_range_age(filters, fifo_queue, to_date):
|
||||
range1 = range2 = range3 = above_range3 = 0.0
|
||||
for item in fifo_queue:
|
||||
age = date_diff(to_date, item[1])
|
||||
|
||||
if age <= filters.range1:
|
||||
range1 += flt(item[0])
|
||||
elif age <= filters.range2:
|
||||
range2 += flt(item[0])
|
||||
elif age <= filters.range3:
|
||||
range3 += flt(item[0])
|
||||
else:
|
||||
above_range3 += flt(item[0])
|
||||
|
||||
return range1, range2, range3, above_range3
|
||||
|
||||
def get_columns(filters):
|
||||
range_columns = []
|
||||
setup_ageing_columns(filters, range_columns)
|
||||
columns = [
|
||||
{
|
||||
"label": _("Item Code"),
|
||||
@ -112,7 +131,9 @@ def get_columns(filters):
|
||||
"fieldname": "average_age",
|
||||
"fieldtype": "Float",
|
||||
"width": 100
|
||||
},
|
||||
}])
|
||||
columns.extend(range_columns)
|
||||
columns.extend([
|
||||
{
|
||||
"label": _("Earliest"),
|
||||
"fieldname": "earliest",
|
||||
@ -263,3 +284,18 @@ def get_chart_data(data, filters):
|
||||
},
|
||||
"type" : "bar"
|
||||
}
|
||||
|
||||
def setup_ageing_columns(filters, range_columns):
|
||||
for i, label in enumerate(["0-{range1}".format(range1=filters["range1"]),
|
||||
"{range1}-{range2}".format(range1=cint(filters["range1"])+ 1, range2=filters["range2"]),
|
||||
"{range2}-{range3}".format(range2=cint(filters["range2"])+ 1, range3=filters["range3"]),
|
||||
"{range3}-{above}".format(range3=cint(filters["range3"])+ 1, above=_("Above"))]):
|
||||
add_column(range_columns, label="Age ("+ label +")", fieldname='range' + str(i+1))
|
||||
|
||||
def add_column(range_columns, label, fieldname, fieldtype='Float', width=140):
|
||||
range_columns.append(dict(
|
||||
label=label,
|
||||
fieldname=fieldname,
|
||||
fieldtype=fieldtype,
|
||||
width=width
|
||||
))
|
Loading…
Reference in New Issue
Block a user