feat: Inpatient Medication Order and Entry (#23473)
* feat: Inpatient Medication Order * feat: Inpatient Medication Entry * feat: update medication orders on medication entry submission * feat: added custom fields for medication references in Stock Entry * feat: make stock entry if update stock is checked in IPMOE * fix: handle cancel event for Inpatient Medication Entry * fix(ux): add link and progress bar to dashboard * refactor(ux): Adding Medication Orders without linking to Patient Encounter * fix: make medication entry child table read only * fix: filter stock items during manual medication order creation * fix: codacy * chore: tests for Inpatient Medication Order * chore: tests for Inpatient Medication Entry * fix: code clean-up * fix: filter for inpatients in IPMO * fix: add datetime validations for IPME filters * fix: do not hardcode stock entry type Co-authored-by: Nabin Hait <nabinhait@gmail.com>
This commit is contained in:
parent
3ad9393ff8
commit
4887b69946
@ -49,6 +49,22 @@ data = {
|
||||
'fieldname': 'reference_dn', 'label': 'Reference Name', 'fieldtype': 'Dynamic Link', 'options': 'reference_dt',
|
||||
'insert_after': 'reference_dt'
|
||||
}
|
||||
],
|
||||
'Stock Entry': [
|
||||
{
|
||||
'fieldname': 'inpatient_medication_entry', 'label': 'Inpatient Medication Entry', 'fieldtype': 'Link', 'options': 'Inpatient Medication Entry',
|
||||
'insert_after': 'credit_note', 'read_only': True
|
||||
}
|
||||
],
|
||||
'Stock Entry Detail': [
|
||||
{
|
||||
'fieldname': 'patient', 'label': 'Patient', 'fieldtype': 'Link', 'options': 'Patient',
|
||||
'insert_after': 'po_detail', 'read_only': True
|
||||
},
|
||||
{
|
||||
'fieldname': 'inpatient_medication_entry_child', 'label': 'Inpatient Medication Entry Child', 'fieldtype': 'Data',
|
||||
'insert_after': 'patient', 'read_only': True
|
||||
}
|
||||
]
|
||||
},
|
||||
'on_setup': 'erpnext.healthcare.setup.setup_healthcare'
|
||||
|
@ -43,7 +43,8 @@
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Dosage",
|
||||
"options": "Prescription Dosage"
|
||||
"options": "Prescription Dosage",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "period",
|
||||
@ -51,14 +52,16 @@
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Period",
|
||||
"options": "Prescription Duration"
|
||||
"options": "Prescription Duration",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "dosage_form",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Dosage Form",
|
||||
"options": "Dosage Form"
|
||||
"options": "Dosage Form",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_7",
|
||||
@ -72,7 +75,7 @@
|
||||
"label": "Comment"
|
||||
},
|
||||
{
|
||||
"depends_on": "use_interval",
|
||||
"depends_on": "usage_interval",
|
||||
"fieldname": "interval",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
@ -80,6 +83,7 @@
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "usage_interval",
|
||||
"fieldname": "update_schedule",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
@ -99,12 +103,13 @@
|
||||
"default": "0",
|
||||
"fieldname": "usage_interval",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Dosage by Time Interval"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-02-26 17:02:42.741338",
|
||||
"modified": "2020-09-30 23:32:09.495288",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Drug Prescription",
|
||||
|
@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Inpatient Medication Entry', {
|
||||
refresh: function(frm) {
|
||||
// Ignore cancellation of doctype on cancel all
|
||||
frm.ignore_doctypes_on_cancel_all = ['Stock Entry'];
|
||||
|
||||
frm.set_query('item_code', () => {
|
||||
return {
|
||||
filters: {
|
||||
is_stock_item: 1
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('drug_code', 'medication_orders', () => {
|
||||
return {
|
||||
filters: {
|
||||
is_stock_item: 1
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
get_medication_orders: function(frm) {
|
||||
frappe.call({
|
||||
method: 'get_medication_orders',
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
freeze_message: __('Fetching Pending Medication Orders'),
|
||||
callback: function() {
|
||||
refresh_field('medication_orders');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
@ -0,0 +1,203 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2020-09-25 14:13:20.111906",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"naming_series",
|
||||
"company",
|
||||
"column_break_3",
|
||||
"posting_date",
|
||||
"status",
|
||||
"filters_section",
|
||||
"item_code",
|
||||
"assigned_to_practitioner",
|
||||
"patient",
|
||||
"practitioner",
|
||||
"service_unit",
|
||||
"column_break_11",
|
||||
"from_date",
|
||||
"to_date",
|
||||
"from_time",
|
||||
"to_time",
|
||||
"select_medication_orders_section",
|
||||
"get_medication_orders",
|
||||
"medication_orders",
|
||||
"section_break_18",
|
||||
"update_stock",
|
||||
"warehouse",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"label": "Naming Series",
|
||||
"options": "HLC-IME-.YYYY.-"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Posting Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"options": "\nDraft\nSubmitted\nPending\nIn Process\nCompleted\nCancelled",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "filters_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Filters"
|
||||
},
|
||||
{
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"label": "Item Code (Drug)",
|
||||
"options": "Item"
|
||||
},
|
||||
{
|
||||
"depends_on": "update_stock",
|
||||
"description": "Warehouse from where medication stock should be consumed",
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Medication Warehouse",
|
||||
"mandatory_depends_on": "update_stock",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "patient",
|
||||
"fieldtype": "Link",
|
||||
"label": "Patient",
|
||||
"options": "Patient"
|
||||
},
|
||||
{
|
||||
"fieldname": "service_unit",
|
||||
"fieldtype": "Link",
|
||||
"label": "Healthcare Service Unit",
|
||||
"options": "Healthcare Service Unit"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "From Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "To Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Inpatient Medication Entry",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "practitioner",
|
||||
"fieldtype": "Link",
|
||||
"label": "Healthcare Practitioner",
|
||||
"options": "Healthcare Practitioner"
|
||||
},
|
||||
{
|
||||
"fieldname": "select_medication_orders_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Medication Orders"
|
||||
},
|
||||
{
|
||||
"fieldname": "medication_orders",
|
||||
"fieldtype": "Table",
|
||||
"label": "Inpatient Medication Orders",
|
||||
"options": "Inpatient Medication Entry Detail",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus!==1",
|
||||
"fieldname": "get_medication_orders",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Pending Medication Orders",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "assigned_to_practitioner",
|
||||
"fieldtype": "Link",
|
||||
"label": "Assigned To",
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_18",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Stock Details"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "update_stock",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Stock"
|
||||
},
|
||||
{
|
||||
"fieldname": "from_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "From Time"
|
||||
},
|
||||
{
|
||||
"fieldname": "to_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "To Time"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-30 23:40:45.528715",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Inpatient Medication Entry",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,273 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt, get_link_to_form, getdate, nowtime
|
||||
from erpnext.stock.utils import get_latest_stock_qty
|
||||
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account
|
||||
|
||||
class InpatientMedicationEntry(Document):
|
||||
def validate(self):
|
||||
self.validate_medication_orders()
|
||||
|
||||
def get_medication_orders(self):
|
||||
self.validate_datetime_filters()
|
||||
|
||||
# pull inpatient medication orders based on selected filters
|
||||
orders = get_pending_medication_orders(self)
|
||||
|
||||
if orders:
|
||||
self.add_mo_to_table(orders)
|
||||
return self
|
||||
else:
|
||||
self.set('medication_orders', [])
|
||||
frappe.msgprint(_('No pending medication orders found for selected criteria'))
|
||||
|
||||
def validate_datetime_filters(self):
|
||||
if self.from_date and self.to_date:
|
||||
self.validate_from_to_dates('from_date', 'to_date')
|
||||
|
||||
if self.from_date and getdate(self.from_date) > getdate():
|
||||
frappe.throw(_('From Date cannot be after the current date.'))
|
||||
|
||||
if self.to_date and getdate(self.to_date) > getdate():
|
||||
frappe.throw(_('To Date cannot be after the current date.'))
|
||||
|
||||
if self.from_time and self.from_time > nowtime():
|
||||
frappe.throw(_('From Time cannot be after the current time.'))
|
||||
|
||||
if self.to_time and self.to_time > nowtime():
|
||||
frappe.throw(_('To Time cannot be after the current time.'))
|
||||
|
||||
def add_mo_to_table(self, orders):
|
||||
# Add medication orders in the child table
|
||||
self.set('medication_orders', [])
|
||||
|
||||
for data in orders:
|
||||
self.append('medication_orders', {
|
||||
'patient': data.patient,
|
||||
'patient_name': data.patient_name,
|
||||
'inpatient_record': data.inpatient_record,
|
||||
'service_unit': data.service_unit,
|
||||
'datetime': "%s %s" % (data.date, data.time or "00:00:00"),
|
||||
'drug_code': data.drug,
|
||||
'drug_name': data.drug_name,
|
||||
'dosage': data.dosage,
|
||||
'dosage_form': data.dosage_form,
|
||||
'against_imo': data.parent,
|
||||
'against_imoe': data.name
|
||||
})
|
||||
|
||||
def on_submit(self):
|
||||
self.validate_medication_orders()
|
||||
success_msg = ""
|
||||
if self.update_stock:
|
||||
stock_entry = self.process_stock()
|
||||
success_msg += _('Stock Entry {0} created and ').format(
|
||||
frappe.bold(get_link_to_form('Stock Entry', stock_entry)))
|
||||
|
||||
self.update_medication_orders()
|
||||
success_msg += _('Inpatient Medication Orders updated successfully')
|
||||
frappe.msgprint(success_msg, title=_('Success'), indicator='green')
|
||||
|
||||
def validate_medication_orders(self):
|
||||
for entry in self.medication_orders:
|
||||
docstatus, is_completed = frappe.db.get_value('Inpatient Medication Order Entry', entry.against_imoe,
|
||||
['docstatus', 'is_completed'])
|
||||
|
||||
if docstatus == 2:
|
||||
frappe.throw(_('Row {0}: Cannot create Inpatient Medication Entry against cancelled Inpatient Medication Order {1}').format(
|
||||
entry.idx, get_link_to_form(entry.against_imo)))
|
||||
|
||||
if is_completed:
|
||||
frappe.throw(_('Row {0}: This Medication Order is already marked as completed').format(
|
||||
entry.idx))
|
||||
|
||||
def on_cancel(self):
|
||||
self.cancel_stock_entries()
|
||||
self.update_medication_orders(on_cancel=True)
|
||||
|
||||
def process_stock(self):
|
||||
allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
|
||||
if not allow_negative_stock:
|
||||
self.check_stock_qty()
|
||||
|
||||
return self.make_stock_entry()
|
||||
|
||||
def update_medication_orders(self, on_cancel=False):
|
||||
orders, order_entry_map = self.get_order_entry_map()
|
||||
# mark completion status
|
||||
is_completed = 1
|
||||
if on_cancel:
|
||||
is_completed = 0
|
||||
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabInpatient Medication Order Entry`
|
||||
SET is_completed = %(is_completed)s
|
||||
WHERE name IN %(orders)s
|
||||
""", {'orders': orders, 'is_completed': is_completed})
|
||||
|
||||
# update status and completed orders count
|
||||
for order, count in order_entry_map.items():
|
||||
medication_order = frappe.get_doc('Inpatient Medication Order', order)
|
||||
completed_orders = flt(count)
|
||||
current_value = frappe.db.get_value('Inpatient Medication Order', order, 'completed_orders')
|
||||
|
||||
if on_cancel:
|
||||
completed_orders = flt(current_value) - flt(count)
|
||||
else:
|
||||
completed_orders = flt(current_value) + flt(count)
|
||||
|
||||
medication_order.db_set('completed_orders', completed_orders)
|
||||
medication_order.set_status()
|
||||
|
||||
def get_order_entry_map(self):
|
||||
# for marking order completion status
|
||||
orders = []
|
||||
# orders mapped
|
||||
order_entry_map = dict()
|
||||
|
||||
for entry in self.medication_orders:
|
||||
orders.append(entry.against_imoe)
|
||||
parent = entry.against_imo
|
||||
if not order_entry_map.get(parent):
|
||||
order_entry_map[parent] = 0
|
||||
|
||||
order_entry_map[parent] += 1
|
||||
|
||||
return orders, order_entry_map
|
||||
|
||||
def check_stock_qty(self):
|
||||
from erpnext.stock.stock_ledger import NegativeStockError
|
||||
|
||||
drug_availability = dict()
|
||||
for d in self.medication_orders:
|
||||
if not drug_availability.get(d.drug_code):
|
||||
drug_availability[d.drug_code] = 0
|
||||
drug_availability[d.drug_code] += flt(d.dosage)
|
||||
|
||||
for drug, dosage in drug_availability.items():
|
||||
available_qty = get_latest_stock_qty(drug, self.warehouse)
|
||||
|
||||
# validate qty
|
||||
if flt(available_qty) < flt(dosage):
|
||||
frappe.throw(_('Quantity not available for {0} in warehouse {1}').format(
|
||||
frappe.bold(drug), frappe.bold(self.warehouse))
|
||||
+ '<br><br>' + _('Available quantity is {0}, you need {1}').format(
|
||||
frappe.bold(available_qty), frappe.bold(dosage))
|
||||
+ '<br><br>' + _('Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.'),
|
||||
NegativeStockError, title=_('Insufficient Stock'))
|
||||
|
||||
def make_stock_entry(self):
|
||||
stock_entry = frappe.new_doc('Stock Entry')
|
||||
stock_entry.purpose = 'Material Issue'
|
||||
stock_entry.set_stock_entry_type()
|
||||
stock_entry.from_warehouse = self.warehouse
|
||||
stock_entry.company = self.company
|
||||
stock_entry.inpatient_medication_entry = self.name
|
||||
cost_center = frappe.get_cached_value('Company', self.company, 'cost_center')
|
||||
expense_account = get_account(None, 'expense_account', 'Healthcare Settings', self.company)
|
||||
|
||||
for entry in self.medication_orders:
|
||||
se_child = stock_entry.append('items')
|
||||
se_child.item_code = entry.drug_code
|
||||
se_child.item_name = entry.drug_name
|
||||
se_child.uom = frappe.db.get_value('Item', entry.drug_code, 'stock_uom')
|
||||
se_child.stock_uom = se_child.uom
|
||||
se_child.qty = flt(entry.dosage)
|
||||
# in stock uom
|
||||
se_child.conversion_factor = 1
|
||||
se_child.cost_center = cost_center
|
||||
se_child.expense_account = expense_account
|
||||
# references
|
||||
se_child.patient = entry.patient
|
||||
se_child.inpatient_medication_entry_child = entry.name
|
||||
|
||||
stock_entry.submit()
|
||||
return stock_entry.name
|
||||
|
||||
def cancel_stock_entries(self):
|
||||
stock_entries = frappe.get_all('Stock Entry', {'inpatient_medication_entry': self.name})
|
||||
for entry in stock_entries:
|
||||
doc = frappe.get_doc('Stock Entry', entry.name)
|
||||
doc.cancel()
|
||||
|
||||
|
||||
def get_pending_medication_orders(entry):
|
||||
filters, values = get_filters(entry)
|
||||
|
||||
data = frappe.db.sql("""
|
||||
SELECT
|
||||
ip.inpatient_record, ip.patient, ip.patient_name,
|
||||
entry.name, entry.parent, entry.drug, entry.drug_name,
|
||||
entry.dosage, entry.dosage_form, entry.date, entry.time, entry.instructions
|
||||
FROM
|
||||
`tabInpatient Medication Order` ip
|
||||
INNER JOIN
|
||||
`tabInpatient Medication Order Entry` entry
|
||||
ON
|
||||
ip.name = entry.parent
|
||||
WHERE
|
||||
ip.docstatus = 1 and
|
||||
ip.company = %(company)s and
|
||||
entry.is_completed = 0
|
||||
{0}
|
||||
ORDER BY
|
||||
entry.date, entry.time
|
||||
""".format(filters), values, as_dict=1)
|
||||
|
||||
for doc in data:
|
||||
inpatient_record = doc.inpatient_record
|
||||
doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record)
|
||||
|
||||
if entry.service_unit and doc.service_unit != entry.service_unit:
|
||||
data.remove(doc)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_filters(entry):
|
||||
filters = ''
|
||||
values = dict(company=entry.company)
|
||||
if entry.from_date:
|
||||
filters += ' and entry.date >= %(from_date)s'
|
||||
values['from_date'] = entry.from_date
|
||||
|
||||
if entry.to_date:
|
||||
filters += ' and entry.date <= %(to_date)s'
|
||||
values['to_date'] = entry.to_date
|
||||
|
||||
if entry.from_time:
|
||||
filters += ' and entry.time >= %(from_time)s'
|
||||
values['from_time'] = entry.from_time
|
||||
|
||||
if entry.to_time:
|
||||
filters += ' and entry.time <= %(to_time)s'
|
||||
values['to_time'] = entry.to_time
|
||||
|
||||
if entry.patient:
|
||||
filters += ' and ip.patient = %(patient)s'
|
||||
values['patient'] = entry.patient
|
||||
|
||||
if entry.practitioner:
|
||||
filters += ' and ip.practitioner = %(practitioner)s'
|
||||
values['practitioner'] = entry.practitioner
|
||||
|
||||
if entry.item_code:
|
||||
filters += ' and entry.drug = %(item_code)s'
|
||||
values['item_code'] = entry.item_code
|
||||
|
||||
if entry.assigned_to_practitioner:
|
||||
filters += ' and ip._assign LIKE %(assigned_to)s'
|
||||
values['assigned_to'] = '%' + entry.assigned_to_practitioner + '%'
|
||||
|
||||
return filters, values
|
||||
|
||||
|
||||
def get_current_healthcare_service_unit(inpatient_record):
|
||||
ip_record = frappe.get_doc('Inpatient Record', inpatient_record)
|
||||
return ip_record.inpatient_occupancies[-1].service_unit
|
@ -0,0 +1,16 @@
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
'fieldname': 'against_imoe',
|
||||
'internal_links': {
|
||||
'Inpatient Medication Order': ['medication_orders', 'against_imo']
|
||||
},
|
||||
'transactions': [
|
||||
{
|
||||
'label': _('Reference'),
|
||||
'items': ['Inpatient Medication Order']
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import add_days, getdate, now_datetime
|
||||
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
|
||||
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
|
||||
from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import create_ipmo, create_ipme
|
||||
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account
|
||||
|
||||
class TestInpatientMedicationEntry(unittest.TestCase):
|
||||
def setUp(self):
|
||||
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||
frappe.db.sql("""delete from `tabInpatient Medication Order`""")
|
||||
frappe.db.sql("""delete from `tabInpatient Medication Entry`""")
|
||||
self.patient = create_patient()
|
||||
|
||||
# Admit
|
||||
ip_record = create_inpatient(self.patient)
|
||||
ip_record.expected_length_of_stay = 0
|
||||
ip_record.save()
|
||||
ip_record.reload()
|
||||
service_unit = get_healthcare_service_unit()
|
||||
admit_patient(ip_record, service_unit, now_datetime())
|
||||
self.ip_record = ip_record
|
||||
|
||||
def test_filters_for_fetching_pending_mo(self):
|
||||
ipmo = create_ipmo(self.patient)
|
||||
ipmo.submit()
|
||||
ipmo.reload()
|
||||
|
||||
date = add_days(getdate(), -1)
|
||||
filters = frappe._dict(
|
||||
from_date=date,
|
||||
to_date=date,
|
||||
from_time='',
|
||||
to_time='',
|
||||
item_code='Dextromethorphan',
|
||||
patient=self.patient
|
||||
)
|
||||
|
||||
ipme = create_ipme(filters, update_stock=0)
|
||||
|
||||
# 3 dosages per day
|
||||
self.assertEqual(len(ipme.medication_orders), 3)
|
||||
self.assertEqual(getdate(ipme.medication_orders[0].datetime), date)
|
||||
|
||||
def test_ipme_with_stock_update(self):
|
||||
ipmo = create_ipmo(self.patient)
|
||||
ipmo.submit()
|
||||
ipmo.reload()
|
||||
|
||||
date = add_days(getdate(), -1)
|
||||
filters = frappe._dict(
|
||||
from_date=date,
|
||||
to_date=date,
|
||||
from_time='',
|
||||
to_time='',
|
||||
item_code='Dextromethorphan',
|
||||
patient=self.patient
|
||||
)
|
||||
|
||||
make_stock_entry()
|
||||
ipme = create_ipme(filters, update_stock=1)
|
||||
ipme.submit()
|
||||
ipme.reload()
|
||||
|
||||
# test order completed
|
||||
is_order_completed = frappe.db.get_value('Inpatient Medication Order Entry',
|
||||
ipme.medication_orders[0].against_imoe, 'is_completed')
|
||||
self.assertEqual(is_order_completed, 1)
|
||||
|
||||
# test stock entry
|
||||
stock_entry = frappe.db.exists('Stock Entry', {'inpatient_medication_entry': ipme.name})
|
||||
self.assertTrue(stock_entry)
|
||||
|
||||
# check references
|
||||
stock_entry = frappe.get_doc('Stock Entry', stock_entry)
|
||||
self.assertEqual(stock_entry.items[0].patient, self.patient)
|
||||
self.assertEqual(stock_entry.items[0].inpatient_medication_entry_child, ipme.medication_orders[0].name)
|
||||
|
||||
def tearDown(self):
|
||||
# cleanup - Discharge
|
||||
schedule_discharge(frappe.as_json({'patient': self.patient}))
|
||||
self.ip_record.reload()
|
||||
mark_invoiced_inpatient_occupancy(self.ip_record)
|
||||
|
||||
self.ip_record.reload()
|
||||
discharge_patient(self.ip_record)
|
||||
|
||||
for entry in frappe.get_all('Inpatient Medication Entry'):
|
||||
doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
|
||||
doc.cancel()
|
||||
frappe.db.delete('Stock Entry', {'inpatient_medication_entry': doc.name})
|
||||
doc.delete()
|
||||
|
||||
for entry in frappe.get_all('Inpatient Medication Order'):
|
||||
doc = frappe.get_doc('Inpatient Medication Order', entry.name)
|
||||
doc.cancel()
|
||||
doc.delete()
|
||||
|
||||
def make_stock_entry():
|
||||
frappe.db.set_value('Company', '_Test Company', {
|
||||
'stock_adjustment_account': 'Stock Adjustment - _TC',
|
||||
'default_inventory_account': 'Stock In Hand - _TC'
|
||||
})
|
||||
stock_entry = frappe.new_doc('Stock Entry')
|
||||
stock_entry.stock_entry_type = 'Material Receipt'
|
||||
stock_entry.company = '_Test Company'
|
||||
stock_entry.to_warehouse = 'Stores - _TC'
|
||||
expense_account = get_account(None, 'expense_account', 'Healthcare Settings', '_Test Company')
|
||||
se_child = stock_entry.append('items')
|
||||
se_child.item_code = 'Dextromethorphan'
|
||||
se_child.item_name = 'Dextromethorphan'
|
||||
se_child.uom = 'Nos'
|
||||
se_child.stock_uom = 'Nos'
|
||||
se_child.qty = 6
|
||||
se_child.t_warehouse = 'Stores - _TC'
|
||||
# in stock uom
|
||||
se_child.conversion_factor = 1.0
|
||||
se_child.expense_account = expense_account
|
||||
stock_entry.submit()
|
@ -0,0 +1,163 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-09-25 14:56:32.636569",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"patient",
|
||||
"patient_name",
|
||||
"inpatient_record",
|
||||
"column_break_4",
|
||||
"service_unit",
|
||||
"datetime",
|
||||
"medication_details_section",
|
||||
"drug_code",
|
||||
"drug_name",
|
||||
"dosage",
|
||||
"available_qty",
|
||||
"dosage_form",
|
||||
"column_break_10",
|
||||
"instructions",
|
||||
"references_section",
|
||||
"against_imo",
|
||||
"against_imoe"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "patient",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Patient",
|
||||
"options": "Patient",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "patient.patient_name",
|
||||
"fieldname": "patient_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Patient Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "drug_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Drug Code",
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "drug_code.item_name",
|
||||
"fieldname": "drug_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Drug Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"fieldname": "dosage",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Dosage",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "dosage_form",
|
||||
"fieldtype": "Link",
|
||||
"label": "Dosage Form",
|
||||
"options": "Dosage Form"
|
||||
},
|
||||
{
|
||||
"fetch_from": "patient.inpatient_record",
|
||||
"fieldname": "inpatient_record",
|
||||
"fieldtype": "Link",
|
||||
"label": "Inpatient Record",
|
||||
"options": "Inpatient Record",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "references_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "References"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "medication_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Medication Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_10",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"columns": 3,
|
||||
"fieldname": "datetime",
|
||||
"fieldtype": "Datetime",
|
||||
"in_list_view": 1,
|
||||
"label": "Datetime",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "instructions",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Instructions"
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "service_unit",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Service Unit",
|
||||
"options": "Healthcare Service Unit",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "against_imo",
|
||||
"fieldtype": "Link",
|
||||
"label": "Against Inpatient Medication Order",
|
||||
"no_copy": 1,
|
||||
"options": "Inpatient Medication Order",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "against_imoe",
|
||||
"fieldtype": "Data",
|
||||
"label": "Against Inpatient Medication Order Entry",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "available_qty",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Available Qty",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-30 14:48:23.648223",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Inpatient Medication Entry Detail",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class InpatientMedicationEntryDetail(Document):
|
||||
pass
|
@ -0,0 +1,106 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Inpatient Medication Order', {
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.docstatus === 1) {
|
||||
frm.trigger("show_progress");
|
||||
}
|
||||
|
||||
frm.events.show_medication_order_button(frm);
|
||||
|
||||
frm.set_query('patient', () => {
|
||||
return {
|
||||
filters: {
|
||||
'inpatient_record': ['!=', '']
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
show_medication_order_button: function(frm) {
|
||||
frm.fields_dict['medication_orders'].grid.wrapper.find('.grid-add-row').hide();
|
||||
frm.fields_dict['medication_orders'].grid.add_custom_button(__('Add Medication Orders'), () => {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __('Add Medication Orders'),
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'drug_code',
|
||||
label: __('Drug'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Item',
|
||||
reqd: 1,
|
||||
"get_query": function () {
|
||||
return {
|
||||
filters: {'is_stock_item': 1}
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'dosage',
|
||||
label: __('Dosage'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Prescription Dosage',
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'period',
|
||||
label: __('Period'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Prescription Duration',
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'dosage_form',
|
||||
label: __('Dosage Form'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Dosage Form',
|
||||
reqd: 1
|
||||
}
|
||||
],
|
||||
primary_action_label: __('Add'),
|
||||
primary_action: () => {
|
||||
let values = d.get_values();
|
||||
if (values) {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
method: 'add_order_entries',
|
||||
args: {
|
||||
order: values
|
||||
},
|
||||
freeze: true,
|
||||
freeze_message: __('Adding Order Entries'),
|
||||
callback: function() {
|
||||
frm.refresh_field('medication_orders');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
d.show();
|
||||
});
|
||||
},
|
||||
|
||||
show_progress: function(frm) {
|
||||
let bars = [];
|
||||
let message = '';
|
||||
|
||||
// completed sessions
|
||||
let title = __('{0} medication orders completed', [frm.doc.completed_orders]);
|
||||
if (frm.doc.completed_orders === 1) {
|
||||
title = __('{0} medication order completed', [frm.doc.completed_orders]);
|
||||
}
|
||||
title += __(' out of {0}', [frm.doc.total_orders]);
|
||||
|
||||
bars.push({
|
||||
'title': title,
|
||||
'width': (frm.doc.completed_orders / frm.doc.total_orders * 100) + '%',
|
||||
'progress_class': 'progress-bar-success'
|
||||
});
|
||||
if (bars[0].width == '0%') {
|
||||
bars[0].width = '0.5%';
|
||||
}
|
||||
message = title;
|
||||
frm.dashboard.add_progress(__('Status'), bars, message);
|
||||
}
|
||||
});
|
@ -0,0 +1,196 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2020-09-14 18:33:56.715736",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"patient_details_section",
|
||||
"naming_series",
|
||||
"patient_encounter",
|
||||
"patient",
|
||||
"patient_name",
|
||||
"patient_age",
|
||||
"inpatient_record",
|
||||
"column_break_6",
|
||||
"company",
|
||||
"status",
|
||||
"practitioner",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"medication_orders_section",
|
||||
"medication_orders",
|
||||
"section_break_16",
|
||||
"total_orders",
|
||||
"column_break_18",
|
||||
"completed_orders",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "patient_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Patient Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"label": "Naming Series",
|
||||
"options": "HLC-IMO-.YYYY.-"
|
||||
},
|
||||
{
|
||||
"fieldname": "patient_encounter",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Patient Encounter",
|
||||
"options": "Patient Encounter"
|
||||
},
|
||||
{
|
||||
"fetch_from": "patient_encounter.patient",
|
||||
"fieldname": "patient",
|
||||
"fieldtype": "Link",
|
||||
"label": "Patient",
|
||||
"options": "Patient",
|
||||
"read_only_depends_on": "patient_encounter",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "patient.patient_name",
|
||||
"fieldname": "patient_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Patient Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "patient_age",
|
||||
"fieldtype": "Data",
|
||||
"label": "Patient Age",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "patient.inpatient_record",
|
||||
"fieldname": "inpatient_record",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Inpatient Record",
|
||||
"options": "Inpatient Record",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "patient_encounter.practitioner",
|
||||
"fieldname": "practitioner",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Healthcare Practitioner",
|
||||
"options": "Healthcare Practitioner",
|
||||
"read_only_depends_on": "patient_encounter"
|
||||
},
|
||||
{
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Start Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "End Date",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.patient && doc.start_date",
|
||||
"fieldname": "medication_orders_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Medication Orders"
|
||||
},
|
||||
{
|
||||
"fieldname": "medication_orders",
|
||||
"fieldtype": "Table",
|
||||
"label": "Medication Orders",
|
||||
"options": "Inpatient Medication Order Entry"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Inpatient Medication Order",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"options": "\nDraft\nSubmitted\nPending\nIn Process\nCompleted\nCancelled",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_16",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Other Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "total_orders",
|
||||
"fieldtype": "Float",
|
||||
"label": "Total Orders",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "completed_orders",
|
||||
"fieldtype": "Float",
|
||||
"label": "Completed Orders",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-30 21:53:27.128591",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Inpatient Medication Order",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"search_fields": "patient_encounter, patient",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "patient",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cstr
|
||||
from erpnext.healthcare.doctype.patient_encounter.patient_encounter import get_prescription_dates
|
||||
|
||||
class InpatientMedicationOrder(Document):
|
||||
def validate(self):
|
||||
self.validate_inpatient()
|
||||
self.validate_duplicate()
|
||||
self.set_total_orders()
|
||||
self.set_status()
|
||||
|
||||
def on_submit(self):
|
||||
self.validate_inpatient()
|
||||
self.set_status()
|
||||
|
||||
def on_cancel(self):
|
||||
self.set_status()
|
||||
|
||||
def validate_inpatient(self):
|
||||
if not self.inpatient_record:
|
||||
frappe.throw(_('No Inpatient Record found against patient {0}').format(self.patient))
|
||||
|
||||
def validate_duplicate(self):
|
||||
existing_mo = frappe.db.exists('Inpatient Medication Order', {
|
||||
'patient_encounter': self.patient_encounter,
|
||||
'docstatus': ('!=', 2),
|
||||
'name': ('!=', self.name)
|
||||
})
|
||||
if existing_mo:
|
||||
frappe.throw(_('An Inpatient Medication Order {0} against Patient Encounter {1} already exists.').format(
|
||||
existing_mo, self.patient_encounter), frappe.DuplicateEntryError)
|
||||
|
||||
def set_total_orders(self):
|
||||
self.db_set('total_orders', len(self.medication_orders))
|
||||
|
||||
def set_status(self):
|
||||
status = {
|
||||
"0": "Draft",
|
||||
"1": "Submitted",
|
||||
"2": "Cancelled"
|
||||
}[cstr(self.docstatus or 0)]
|
||||
|
||||
if self.docstatus == 1:
|
||||
if not self.completed_orders:
|
||||
status = 'Pending'
|
||||
elif self.completed_orders < self.total_orders:
|
||||
status = 'In Process'
|
||||
else:
|
||||
status = 'Completed'
|
||||
|
||||
self.db_set('status', status)
|
||||
|
||||
def add_order_entries(self, order):
|
||||
if order.get('drug_code'):
|
||||
dosage = frappe.get_doc('Prescription Dosage', order.get('dosage'))
|
||||
dates = get_prescription_dates(order.get('period'), self.start_date)
|
||||
for date in dates:
|
||||
for dose in dosage.dosage_strength:
|
||||
entry = self.append('medication_orders')
|
||||
entry.drug = order.get('drug_code')
|
||||
entry.drug_name = frappe.db.get_value('Item', order.get('drug_code'), 'item_name')
|
||||
entry.dosage = dose.strength
|
||||
entry.dosage_form = order.get('dosage_form')
|
||||
entry.date = date
|
||||
entry.time = dose.strength_time
|
||||
self.end_date = dates[-1]
|
||||
return
|
@ -0,0 +1,16 @@
|
||||
frappe.listview_settings['Inpatient Medication Order'] = {
|
||||
add_fields: ["status"],
|
||||
filters: [["status", "!=", "Cancelled"]],
|
||||
get_indicator: function(doc) {
|
||||
if (doc.status === "Pending") {
|
||||
return [__("Pending"), "orange", "status,=,Pending"];
|
||||
|
||||
} else if (doc.status === "In Process") {
|
||||
return [__("In Process"), "blue", "status,=,In Process"];
|
||||
|
||||
} else if (doc.status === "Completed") {
|
||||
return [__("Completed"), "green", "status,=,Completed"];
|
||||
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,150 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import add_days, getdate, now_datetime
|
||||
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
|
||||
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
|
||||
|
||||
class TestInpatientMedicationOrder(unittest.TestCase):
|
||||
def setUp(self):
|
||||
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||
self.patient = create_patient()
|
||||
|
||||
# Admit
|
||||
ip_record = create_inpatient(self.patient)
|
||||
ip_record.expected_length_of_stay = 0
|
||||
ip_record.save()
|
||||
ip_record.reload()
|
||||
service_unit = get_healthcare_service_unit()
|
||||
admit_patient(ip_record, service_unit, now_datetime())
|
||||
self.ip_record = ip_record
|
||||
|
||||
def test_order_creation(self):
|
||||
ipmo = create_ipmo(self.patient)
|
||||
ipmo.submit()
|
||||
ipmo.reload()
|
||||
|
||||
# 3 dosages per day for 2 days
|
||||
self.assertEqual(len(ipmo.medication_orders), 6)
|
||||
self.assertEqual(ipmo.medication_orders[0].date, add_days(getdate(), -1))
|
||||
|
||||
prescription_dosage = frappe.get_doc('Prescription Dosage', '1-1-1')
|
||||
for i in range(len(prescription_dosage.dosage_strength)):
|
||||
self.assertEqual(ipmo.medication_orders[i].time, prescription_dosage.dosage_strength[i].strength_time)
|
||||
|
||||
self.assertEqual(ipmo.medication_orders[3].date, getdate())
|
||||
|
||||
def test_inpatient_validation(self):
|
||||
# Discharge
|
||||
schedule_discharge(frappe.as_json({'patient': self.patient}))
|
||||
|
||||
self.ip_record.reload()
|
||||
mark_invoiced_inpatient_occupancy(self.ip_record)
|
||||
|
||||
self.ip_record.reload()
|
||||
discharge_patient(self.ip_record)
|
||||
|
||||
ipmo = create_ipmo(self.patient)
|
||||
# inpatient validation
|
||||
self.assertRaises(frappe.ValidationError, ipmo.insert)
|
||||
|
||||
def test_status(self):
|
||||
ipmo = create_ipmo(self.patient)
|
||||
ipmo.submit()
|
||||
ipmo.reload()
|
||||
|
||||
self.assertEqual(ipmo.status, 'Pending')
|
||||
|
||||
filters = frappe._dict(from_date=add_days(getdate(), -1), to_date=add_days(getdate(), -1), from_time='', to_time='')
|
||||
ipme = create_ipme(filters)
|
||||
ipme.submit()
|
||||
ipmo.reload()
|
||||
self.assertEqual(ipmo.status, 'In Process')
|
||||
|
||||
filters = frappe._dict(from_date=getdate(), to_date=getdate(), from_time='', to_time='')
|
||||
ipme = create_ipme(filters)
|
||||
ipme.submit()
|
||||
ipmo.reload()
|
||||
self.assertEqual(ipmo.status, 'Completed')
|
||||
|
||||
def tearDown(self):
|
||||
if frappe.db.get_value('Patient', self.patient, 'inpatient_record'):
|
||||
# cleanup - Discharge
|
||||
schedule_discharge(frappe.as_json({'patient': self.patient}))
|
||||
self.ip_record.reload()
|
||||
mark_invoiced_inpatient_occupancy(self.ip_record)
|
||||
|
||||
self.ip_record.reload()
|
||||
discharge_patient(self.ip_record)
|
||||
|
||||
for entry in frappe.get_all('Inpatient Medication Entry'):
|
||||
doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
|
||||
doc.cancel()
|
||||
doc.delete()
|
||||
|
||||
for entry in frappe.get_all('Inpatient Medication Order'):
|
||||
doc = frappe.get_doc('Inpatient Medication Order', entry.name)
|
||||
doc.cancel()
|
||||
doc.delete()
|
||||
|
||||
def create_dosage_form():
|
||||
if not frappe.db.exists('Dosage Form', 'Tablet'):
|
||||
frappe.get_doc({
|
||||
'doctype': 'Dosage Form',
|
||||
'dosage_form': 'Tablet'
|
||||
}).insert()
|
||||
|
||||
def create_drug(item=None):
|
||||
if not item:
|
||||
item = 'Dextromethorphan'
|
||||
drug = frappe.db.exists('Item', {'item_code': 'Dextromethorphan'})
|
||||
if not drug:
|
||||
drug = frappe.get_doc({
|
||||
'doctype': 'Item',
|
||||
'item_code': 'Dextromethorphan',
|
||||
'item_name': 'Dextromethorphan',
|
||||
'item_group': 'Products',
|
||||
'stock_uom': 'Nos',
|
||||
'is_stock_item': 1,
|
||||
'valuation_rate': 50,
|
||||
'opening_stock': 20
|
||||
}).insert()
|
||||
|
||||
def get_orders():
|
||||
create_dosage_form()
|
||||
create_drug()
|
||||
return {
|
||||
'drug_code': 'Dextromethorphan',
|
||||
'drug_name': 'Dextromethorphan',
|
||||
'dosage': '1-1-1',
|
||||
'dosage_form': 'Tablet',
|
||||
'period': '2 Day'
|
||||
}
|
||||
|
||||
def create_ipmo(patient):
|
||||
orders = get_orders()
|
||||
ipmo = frappe.new_doc('Inpatient Medication Order')
|
||||
ipmo.patient = patient
|
||||
ipmo.company = '_Test Company'
|
||||
ipmo.start_date = add_days(getdate(), -1)
|
||||
ipmo.add_order_entries(orders)
|
||||
|
||||
return ipmo
|
||||
|
||||
def create_ipme(filters, update_stock=0):
|
||||
ipme = frappe.new_doc('Inpatient Medication Entry')
|
||||
ipme.company = '_Test Company'
|
||||
ipme.posting_date = getdate()
|
||||
ipme.update_stock = update_stock
|
||||
if update_stock:
|
||||
ipme.warehouse = 'Stores - _TC'
|
||||
for key, value in filters.items():
|
||||
ipme.set(key, value)
|
||||
ipme = ipme.get_medication_orders()
|
||||
|
||||
return ipme
|
||||
|
@ -0,0 +1,94 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-09-14 21:51:30.259164",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"drug",
|
||||
"drug_name",
|
||||
"dosage",
|
||||
"dosage_form",
|
||||
"instructions",
|
||||
"column_break_4",
|
||||
"date",
|
||||
"time",
|
||||
"is_completed"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "drug",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Drug",
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "drug.item_name",
|
||||
"fieldname": "drug_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Drug Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "dosage",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Dosage",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "dosage_form",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Dosage Form",
|
||||
"options": "Dosage Form",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "time",
|
||||
"fieldtype": "Time",
|
||||
"in_list_view": 1,
|
||||
"label": "Time",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_completed",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Order Completed",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "instructions",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Instructions"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-30 14:03:26.755925",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Inpatient Medication Order Entry",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class InpatientMedicationOrderEntry(Document):
|
||||
pass
|
@ -83,6 +83,7 @@ def get_healthcare_service_unit():
|
||||
if not service_unit:
|
||||
service_unit = frappe.new_doc("Healthcare Service Unit")
|
||||
service_unit.healthcare_service_unit_name = "Test Service Unit Ip Occupancy"
|
||||
service_unit.company = "_Test Company"
|
||||
service_unit.service_unit_type = get_service_unit_type()
|
||||
service_unit.inpatient_occupancy = 1
|
||||
service_unit.occupancy_status = "Vacant"
|
||||
|
@ -58,6 +58,14 @@ frappe.ui.form.on('Patient Encounter', {
|
||||
create_procedure(frm);
|
||||
},'Create');
|
||||
|
||||
if (frm.doc.drug_prescription && frm.doc.inpatient_record && frm.doc.inpatient_status === "Admitted") {
|
||||
frm.add_custom_button(__('Inpatient Medication Order'), function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: 'erpnext.healthcare.doctype.patient_encounter.patient_encounter.make_ip_medication_order',
|
||||
frm: frm
|
||||
});
|
||||
}, 'Create');
|
||||
}
|
||||
}
|
||||
|
||||
frm.set_query('patient', function() {
|
||||
|
@ -6,8 +6,9 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cstr
|
||||
from frappe.utils import cstr, getdate, add_days
|
||||
from frappe import _
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
class PatientEncounter(Document):
|
||||
def validate(self):
|
||||
@ -22,20 +23,69 @@ class PatientEncounter(Document):
|
||||
insert_encounter_to_medical_record(self)
|
||||
|
||||
def on_submit(self):
|
||||
update_encounter_medical_record(self)
|
||||
if self.therapies:
|
||||
create_therapy_plan(self)
|
||||
|
||||
def on_cancel(self):
|
||||
if self.appointment:
|
||||
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open')
|
||||
delete_medical_record(self)
|
||||
|
||||
def on_submit(self):
|
||||
create_therapy_plan(self)
|
||||
if self.inpatient_record and self.drug_prescription:
|
||||
delete_ip_medication_order(self)
|
||||
|
||||
delete_medical_record(self)
|
||||
|
||||
def set_title(self):
|
||||
self.title = _('{0} with {1}').format(self.patient_name or self.patient,
|
||||
self.practitioner_name or self.practitioner)[:100]
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_ip_medication_order(source_name, target_doc=None):
|
||||
def set_missing_values(source, target):
|
||||
target.start_date = source.encounter_date
|
||||
for entry in source.drug_prescription:
|
||||
if entry.drug_code:
|
||||
dosage = frappe.get_doc('Prescription Dosage', entry.dosage)
|
||||
dates = get_prescription_dates(entry.period, target.start_date)
|
||||
for date in dates:
|
||||
for dose in dosage.dosage_strength:
|
||||
order = target.append('medication_orders')
|
||||
order.drug = entry.drug_code
|
||||
order.drug_name = entry.drug_name
|
||||
order.dosage = dose.strength
|
||||
order.instructions = entry.comment
|
||||
order.dosage_form = entry.dosage_form
|
||||
order.date = date
|
||||
order.time = dose.strength_time
|
||||
target.end_date = dates[-1]
|
||||
|
||||
doc = get_mapped_doc('Patient Encounter', source_name, {
|
||||
'Patient Encounter': {
|
||||
'doctype': 'Inpatient Medication Order',
|
||||
'field_map': {
|
||||
'name': 'patient_encounter',
|
||||
'patient': 'patient',
|
||||
'patient_name': 'patient_name',
|
||||
'patient_age': 'patient_age',
|
||||
'inpatient_record': 'inpatient_record',
|
||||
'practitioner': 'practitioner',
|
||||
'start_date': 'encounter_date'
|
||||
},
|
||||
}
|
||||
}, target_doc, set_missing_values)
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
def get_prescription_dates(period, start_date):
|
||||
prescription_duration = frappe.get_doc('Prescription Duration', period)
|
||||
days = prescription_duration.get_days()
|
||||
dates = [start_date]
|
||||
for i in range(1, days):
|
||||
dates.append(add_days(getdate(start_date), i))
|
||||
return dates
|
||||
|
||||
|
||||
def create_therapy_plan(encounter):
|
||||
if len(encounter.therapies):
|
||||
doc = frappe.new_doc('Therapy Plan')
|
||||
@ -51,6 +101,7 @@ def create_therapy_plan(encounter):
|
||||
encounter.db_set('therapy_plan', doc.name)
|
||||
frappe.msgprint(_('Therapy Plan {0} created successfully.').format(frappe.bold(doc.name)), alert=True)
|
||||
|
||||
|
||||
def insert_encounter_to_medical_record(doc):
|
||||
subject = set_subject_field(doc)
|
||||
medical_record = frappe.new_doc('Patient Medical Record')
|
||||
@ -63,6 +114,7 @@ def insert_encounter_to_medical_record(doc):
|
||||
medical_record.reference_owner = doc.owner
|
||||
medical_record.save(ignore_permissions=True)
|
||||
|
||||
|
||||
def update_encounter_medical_record(encounter):
|
||||
medical_record_id = frappe.db.exists('Patient Medical Record', {'reference_name': encounter.name})
|
||||
|
||||
@ -72,8 +124,17 @@ def update_encounter_medical_record(encounter):
|
||||
else:
|
||||
insert_encounter_to_medical_record(encounter)
|
||||
|
||||
|
||||
def delete_medical_record(encounter):
|
||||
frappe.delete_doc_if_exists('Patient Medical Record', 'reference_name', encounter.name)
|
||||
record = frappe.db.exists('Patient Medical Record', {'reference_name', encounter.name})
|
||||
if record:
|
||||
frappe.delete_doc('Patient Medical Record', record, force=1)
|
||||
|
||||
def delete_ip_medication_order(encounter):
|
||||
record = frappe.db.exists('Inpatient Medication Order', {'patient_encounter': encounter.name})
|
||||
if record:
|
||||
frappe.delete_doc('Inpatient Medication Order', record, force=1)
|
||||
|
||||
|
||||
def set_subject_field(encounter):
|
||||
subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '<br>'
|
||||
|
@ -5,12 +5,18 @@ def get_data():
|
||||
return {
|
||||
'fieldname': 'encounter',
|
||||
'non_standard_fieldnames': {
|
||||
'Patient Medical Record': 'reference_name'
|
||||
'Patient Medical Record': 'reference_name',
|
||||
'Inpatient Medication Order': 'patient_encounter'
|
||||
},
|
||||
'transactions': [
|
||||
{
|
||||
'label': _('Records'),
|
||||
'items': ['Vital Signs', 'Patient Medical Record']
|
||||
},
|
||||
]
|
||||
{
|
||||
'label': _('Orders'),
|
||||
'items': ['Inpatient Medication Order']
|
||||
}
|
||||
],
|
||||
'disable_create_buttons': ['Inpatient Medication Order']
|
||||
}
|
||||
|
@ -282,7 +282,8 @@ doc_events = {
|
||||
# to maintain data integrity we exempted payment entry. it will un-link when sales invoice get cancelled.
|
||||
# if payment entry not in auto cancel exempted doctypes it will cancel payment entry.
|
||||
auto_cancel_exempted_doctypes= [
|
||||
"Payment Entry"
|
||||
"Payment Entry",
|
||||
"Inpatient Medication Entry"
|
||||
]
|
||||
|
||||
scheduler_events = {
|
||||
|
@ -731,3 +731,4 @@ erpnext.patches.v13_0.change_default_pos_print_format
|
||||
erpnext.patches.v13_0.set_youtube_video_id
|
||||
erpnext.patches.v13_0.print_uom_after_quantity_patch
|
||||
erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
|
||||
erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail
|
||||
|
@ -0,0 +1,10 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from erpnext.domains.healthcare import data
|
||||
|
||||
def execute():
|
||||
if 'Healthcare' not in frappe.get_active_domains():
|
||||
return
|
||||
|
||||
if data['custom_fields']:
|
||||
create_custom_fields(data['custom_fields'])
|
Loading…
x
Reference in New Issue
Block a user