Merge branch 'develop' into fix-payment-entry-wrong-bank-account-fetch-develop
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index f9db14b..9df8655 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -911,7 +911,7 @@
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 @@
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:
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 287e00f..e93ec95 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -140,9 +140,6 @@
})
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 @@
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")
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 639ef6c..df77dc8 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -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",
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 4e22b05..2563b66 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -223,9 +223,9 @@
# 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 @@
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 @@
`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`
diff --git a/erpnext/assets/assets_dashboard/asset/asset.json b/erpnext/assets/assets_dashboard/asset/asset.json
new file mode 100644
index 0000000..56b1e2a
--- /dev/null
+++ b/erpnext/assets/assets_dashboard/asset/asset.json
@@ -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"
+}
\ No newline at end of file
diff --git a/erpnext/assets/dashboard_chart/asset_value_analytics/asset_value_analytics.json b/erpnext/assets/dashboard_chart/asset_value_analytics/asset_value_analytics.json
new file mode 100644
index 0000000..bc2edc9
--- /dev/null
+++ b/erpnext/assets/dashboard_chart/asset_value_analytics/asset_value_analytics.json
@@ -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": []
+}
\ No newline at end of file
diff --git a/erpnext/assets/dashboard_chart/category_wise_asset_value/category_wise_asset_value.json b/erpnext/assets/dashboard_chart/category_wise_asset_value/category_wise_asset_value.json
new file mode 100644
index 0000000..e79d2d7
--- /dev/null
+++ b/erpnext/assets/dashboard_chart/category_wise_asset_value/category_wise_asset_value.json
@@ -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"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/assets/dashboard_chart/location_wise_asset_value/location_wise_asset_value.json b/erpnext/assets/dashboard_chart/location_wise_asset_value/location_wise_asset_value.json
new file mode 100644
index 0000000..481586e
--- /dev/null
+++ b/erpnext/assets/dashboard_chart/location_wise_asset_value/location_wise_asset_value.json
@@ -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"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/assets/number_card/asset_value/asset_value.json b/erpnext/assets/number_card/asset_value/asset_value.json
new file mode 100644
index 0000000..68e5f54
--- /dev/null
+++ b/erpnext/assets/number_card/asset_value/asset_value.json
@@ -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"
+}
\ No newline at end of file
diff --git "a/erpnext/assets/number_card/new_assets_\050this_year\051/new_assets_\050this_year\051.json" "b/erpnext/assets/number_card/new_assets_\050this_year\051/new_assets_\050this_year\051.json"
new file mode 100644
index 0000000..6c8fb35
--- /dev/null
+++ "b/erpnext/assets/number_card/new_assets_\050this_year\051/new_assets_\050this_year\051.json"
@@ -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"
+}
\ No newline at end of file
diff --git a/erpnext/assets/number_card/total_assets/total_assets.json b/erpnext/assets/number_card/total_assets/total_assets.json
new file mode 100644
index 0000000..d127de8
--- /dev/null
+++ b/erpnext/assets/number_card/total_assets/total_assets.json
@@ -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"
+}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.json b/erpnext/crm/doctype/email_campaign/email_campaign.json
index 736a9d6..0340364 100644
--- a/erpnext/crm/doctype/email_campaign/email_campaign.json
+++ b/erpnext/crm/doctype/email_campaign/email_campaign.json
@@ -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",
diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.py b/erpnext/crm/doctype/email_campaign/email_campaign.py
index 8f60ecf..71c93e8 100644
--- a/erpnext/crm/doctype/email_campaign/email_campaign.py
+++ b/erpnext/crm/doctype/email_campaign/email_campaign.py
@@ -70,10 +70,15 @@
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 @@
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,
diff --git a/erpnext/education/api.py b/erpnext/education/api.py
index fe033d4..bf9f221 100644
--- a/erpnext/education/api.py
+++ b/erpnext/education/api.py
@@ -152,7 +152,7 @@
: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
diff --git a/erpnext/education/doctype/fees/fees.js b/erpnext/education/doctype/fees/fees.js
index 867866f..aaf42b4 100644
--- a/erpnext/education/doctype/fees/fees.js
+++ b/erpnext/education/doctype/fees/fees.js
@@ -162,6 +162,7 @@
$.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;
});
}
diff --git a/erpnext/healthcare/desk_page/healthcare/healthcare.json b/erpnext/healthcare/desk_page/healthcare/healthcare.json
index 334b655..6546b08 100644
--- a/erpnext/healthcare/desk_page/healthcare/healthcare.json
+++ b/erpnext/healthcare/desk_page/healthcare/healthcare.json
@@ -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",
diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py
index 30a1e45..63dd8d4 100644
--- a/erpnext/healthcare/doctype/patient/patient.py
+++ b/erpnext/healthcare/doctype/patient/patient.py
@@ -172,3 +172,15 @@
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))
diff --git a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json
index 15c9434..eb0021f 100644
--- a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json
+++ b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json
@@ -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",
diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
index c19be17..e0f015f 100644
--- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
+++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
@@ -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 @@
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()
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.json b/erpnext/healthcare/doctype/therapy_session/therapy_session.json
index c75d934..dc0cafc 100644
--- a/erpnext/healthcare/doctype/therapy_session/therapy_session.json
+++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.json
@@ -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",
diff --git a/erpnext/healthcare/page/patient_progress/__init__.py b/erpnext/healthcare/page/patient_progress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/healthcare/page/patient_progress/__init__.py
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.css b/erpnext/healthcare/page/patient_progress/patient_progress.css
new file mode 100644
index 0000000..5d85a74
--- /dev/null
+++ b/erpnext/healthcare/page/patient_progress/patient_progress.css
@@ -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%;
+ }
+}
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.html b/erpnext/healthcare/page/patient_progress/patient_progress.html
new file mode 100644
index 0000000..c20537e
--- /dev/null
+++ b/erpnext/healthcare/page/patient_progress/patient_progress.html
@@ -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>
\ No newline at end of file
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.js b/erpnext/healthcare/page/patient_progress/patient_progress.js
new file mode 100644
index 0000000..2410b0c
--- /dev/null
+++ b/erpnext/healthcare/page/patient_progress/patient_progress.js
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.json b/erpnext/healthcare/page/patient_progress/patient_progress.json
new file mode 100644
index 0000000..0175cb9
--- /dev/null
+++ b/erpnext/healthcare/page/patient_progress/patient_progress.json
@@ -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"
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.py b/erpnext/healthcare/page/patient_progress/patient_progress.py
new file mode 100644
index 0000000..a04fb2b
--- /dev/null
+++ b/erpnext/healthcare/page/patient_progress/patient_progress.py
@@ -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())
+
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html b/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html
new file mode 100644
index 0000000..cd62dd3
--- /dev/null
+++ b/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html
@@ -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>
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 2fb9d7f..a24f5f7 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -713,6 +713,7 @@
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
diff --git a/erpnext/patches/v12_0/fix_percent_complete_for_projects.py b/erpnext/patches/v12_0/fix_percent_complete_for_projects.py
new file mode 100644
index 0000000..3622df6
--- /dev/null
+++ b/erpnext/patches/v12_0/fix_percent_complete_for_projects.py
@@ -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")
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index 1e2983e..4ccf564 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -869,10 +869,10 @@
# 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
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index 4bdda68..cf2fd26 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -175,6 +175,9 @@
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
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 42f9cabc..d9f6e1d 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -338,8 +338,8 @@
};
},
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 @@
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
},
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.js b/erpnext/stock/report/stock_ageing/stock_ageing.js
index ccde61a..8495142 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.js
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.js
@@ -37,6 +37,27 @@
"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"),
"fieldtype": "Check",
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index d5878cb..4af3c54 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -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 @@
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 @@
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 @@
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 @@
"fieldname": "average_age",
"fieldtype": "Float",
"width": 100
- },
+ }])
+ columns.extend(range_columns)
+ columns.extend([
{
"label": _("Earliest"),
"fieldname": "earliest",
@@ -263,3 +284,18 @@
},
"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
+ ))
\ No newline at end of file