Merge branch 'develop' into lcv_multicurrency
This commit is contained in:
commit
62a1caf6c4
@ -132,16 +132,10 @@ def allow_regional(fn):
|
|||||||
|
|
||||||
return caller
|
return caller
|
||||||
|
|
||||||
def get_last_membership():
|
def get_last_membership(member):
|
||||||
'''Returns last membership if exists'''
|
'''Returns last membership if exists'''
|
||||||
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
|
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
|
||||||
dict(member=frappe.session.user, paid=1), order_by='to_date desc', limit=1)
|
dict(member=member, paid=1), order_by='to_date desc', limit=1)
|
||||||
|
|
||||||
return last_membership and last_membership[0]
|
if last_membership:
|
||||||
|
return last_membership[0]
|
||||||
def is_member():
|
|
||||||
'''Returns true if the user is still a member'''
|
|
||||||
last_membership = get_last_membership()
|
|
||||||
if last_membership and getdate(last_membership.to_date) > getdate():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|||||||
@ -1861,23 +1861,6 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
def test_einvoice_json(self):
|
def test_einvoice_json(self):
|
||||||
from erpnext.regional.india.e_invoice.utils import make_einvoice
|
from erpnext.regional.india.e_invoice.utils import make_einvoice
|
||||||
|
|
||||||
customer_gstin = '27AACCM7806M1Z3'
|
|
||||||
customer_gstin_dtls = {
|
|
||||||
'LegalName': '_Test Customer', 'TradeName': '_Test Customer', 'AddrLoc': '_Test City',
|
|
||||||
'StateCode': '27', 'AddrPncd': '410038', 'AddrBno': '_Test Bldg',
|
|
||||||
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
|
|
||||||
}
|
|
||||||
company_gstin = '27AAECE4835E1ZR'
|
|
||||||
company_gstin_dtls = {
|
|
||||||
'LegalName': '_Test Company', 'TradeName': '_Test Company', 'AddrLoc': '_Test City',
|
|
||||||
'StateCode': '27', 'AddrPncd': '401108', 'AddrBno': '_Test Bldg',
|
|
||||||
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
|
|
||||||
}
|
|
||||||
# set cache gstin details to avoid fetching details which will require connection to GSP servers
|
|
||||||
frappe.local.gstin_cache = {}
|
|
||||||
frappe.local.gstin_cache[customer_gstin] = customer_gstin_dtls
|
|
||||||
frappe.local.gstin_cache[company_gstin] = company_gstin_dtls
|
|
||||||
|
|
||||||
si = make_sales_invoice_for_ewaybill()
|
si = make_sales_invoice_for_ewaybill()
|
||||||
si.naming_series = 'INV-2020-.#####'
|
si.naming_series = 'INV-2020-.#####'
|
||||||
si.items = []
|
si.items = []
|
||||||
@ -1930,12 +1913,12 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
|
self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
|
||||||
self.assertEqual(value_details['IgstVal'], total_item_igst_value)
|
self.assertEqual(value_details['IgstVal'], total_item_igst_value)
|
||||||
|
|
||||||
self.assertEqual(
|
calculated_invoice_value = \
|
||||||
value_details['TotInvVal'],
|
value_details['AssVal'] + value_details['CgstVal'] \
|
||||||
value_details['AssVal'] + value_details['CgstVal']
|
+ value_details['SgstVal'] + value_details['IgstVal'] \
|
||||||
+ value_details['SgstVal'] + value_details['IgstVal']
|
|
||||||
+ value_details['OthChrg'] - value_details['Discount']
|
+ value_details['OthChrg'] - value_details['Discount']
|
||||||
)
|
|
||||||
|
self.assertTrue(value_details['TotInvVal'] - calculated_invoice_value < 0.1)
|
||||||
|
|
||||||
self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
|
self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
|
||||||
self.assertTrue(einvoice['EwbDtls'])
|
self.assertTrue(einvoice['EwbDtls'])
|
||||||
|
|||||||
@ -53,8 +53,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
|||||||
|
|
||||||
row = {
|
row = {
|
||||||
'item_code': d.item_code,
|
'item_code': d.item_code,
|
||||||
'item_name': item_record.item_name,
|
'item_name': item_record.item_name if item_record else d.item_name,
|
||||||
'item_group': item_record.item_group,
|
'item_group': item_record.item_group if item_record else d.item_group,
|
||||||
'description': d.description,
|
'description': d.description,
|
||||||
'invoice': d.parent,
|
'invoice': d.parent,
|
||||||
'posting_date': d.posting_date,
|
'posting_date': d.posting_date,
|
||||||
@ -316,6 +316,7 @@ def get_items(filters, additional_query_columns):
|
|||||||
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
|
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
|
||||||
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
|
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
|
||||||
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
|
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
|
||||||
|
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`,
|
||||||
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
|
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
|
||||||
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
|
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
|
||||||
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
|
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
|
||||||
|
|||||||
@ -655,6 +655,34 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
return frappe.db.sql(query, filters)
|
return frappe.db.sql(query, filters)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
|
def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
query = """
|
||||||
|
select name
|
||||||
|
from `tabHealthcare Service Unit`
|
||||||
|
where
|
||||||
|
is_group = 0
|
||||||
|
and company = {company}
|
||||||
|
and name like {txt}""".format(
|
||||||
|
company = frappe.db.escape(filters.get('company')), txt = frappe.db.escape('%{0}%'.format(txt)))
|
||||||
|
|
||||||
|
if filters and filters.get('inpatient_record'):
|
||||||
|
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
|
||||||
|
service_unit = get_current_healthcare_service_unit(filters.get('inpatient_record'))
|
||||||
|
|
||||||
|
# if the patient is admitted, then appointments should be allowed against the admission service unit,
|
||||||
|
# inspite of it being an Inpatient Occupancy service unit
|
||||||
|
if service_unit:
|
||||||
|
query += " and (allow_appointments = 1 or name = {service_unit})".format(service_unit = frappe.db.escape(service_unit))
|
||||||
|
else:
|
||||||
|
query += " and allow_appointments = 1"
|
||||||
|
else:
|
||||||
|
query += " and allow_appointments = 1"
|
||||||
|
|
||||||
|
return frappe.db.sql(query, filters)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
|
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
|||||||
@ -8,12 +8,12 @@
|
|||||||
"is_mandatory": 0,
|
"is_mandatory": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2020-05-14 17:38:27.496696",
|
"modified": "2021-01-21 15:28:52.483839",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Create Opportunity",
|
"name": "Create Opportunity",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"reference_document": "Opportunity",
|
"reference_document": "Opportunity",
|
||||||
"show_full_form": 0,
|
"show_full_form": 1,
|
||||||
"title": "Create Opportunity",
|
"title": "Create Opportunity",
|
||||||
"validate_action": 1
|
"validate_action": 1
|
||||||
}
|
}
|
||||||
@ -100,7 +100,6 @@ class ClinicalProcedure(Document):
|
|||||||
allow_start = self.set_actual_qty()
|
allow_start = self.set_actual_qty()
|
||||||
if allow_start:
|
if allow_start:
|
||||||
self.db_set('status', 'In Progress')
|
self.db_set('status', 'In Progress')
|
||||||
insert_clinical_procedure_to_medical_record(self)
|
|
||||||
return 'success'
|
return 'success'
|
||||||
return 'insufficient stock'
|
return 'insufficient stock'
|
||||||
|
|
||||||
@ -247,21 +246,3 @@ def make_procedure(source_name, target_doc=None):
|
|||||||
}, target_doc, set_missing_values)
|
}, target_doc, set_missing_values)
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
||||||
def insert_clinical_procedure_to_medical_record(doc):
|
|
||||||
subject = frappe.bold(_("Clinical Procedure conducted: ")) + cstr(doc.procedure_template) + "<br>"
|
|
||||||
if doc.practitioner:
|
|
||||||
subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
|
|
||||||
if subject and doc.notes:
|
|
||||||
subject += '<br/>' + doc.notes
|
|
||||||
|
|
||||||
medical_record = frappe.new_doc('Patient Medical Record')
|
|
||||||
medical_record.patient = doc.patient
|
|
||||||
medical_record.subject = subject
|
|
||||||
medical_record.status = 'Open'
|
|
||||||
medical_record.communication_date = doc.start_date
|
|
||||||
medical_record.reference_doctype = 'Clinical Procedure'
|
|
||||||
medical_record.reference_name = doc.name
|
|
||||||
medical_record.reference_owner = doc.owner
|
|
||||||
medical_record.save(ignore_permissions=True)
|
|
||||||
|
|||||||
@ -264,7 +264,7 @@ def get_filters(entry):
|
|||||||
|
|
||||||
def get_current_healthcare_service_unit(inpatient_record):
|
def get_current_healthcare_service_unit(inpatient_record):
|
||||||
ip_record = frappe.get_doc('Inpatient Record', inpatient_record)
|
ip_record = frappe.get_doc('Inpatient Record', inpatient_record)
|
||||||
if ip_record.inpatient_occupancies:
|
if ip_record.status in ['Admitted', 'Discharge Scheduled'] and ip_record.inpatient_occupancies:
|
||||||
return ip_record.inpatient_occupancies[-1].service_unit
|
return ip_record.inpatient_occupancies[-1].service_unit
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@ -142,11 +142,15 @@ def create_inpatient(patient):
|
|||||||
return inpatient_record
|
return inpatient_record
|
||||||
|
|
||||||
|
|
||||||
def get_healthcare_service_unit():
|
def get_healthcare_service_unit(unit_name=None):
|
||||||
|
if not unit_name:
|
||||||
service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
|
service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
|
||||||
|
else:
|
||||||
|
service_unit = frappe.db.exists("Healthcare Service Unit", {"healthcare_service_unit_name": unit_name})
|
||||||
|
|
||||||
if not service_unit:
|
if not service_unit:
|
||||||
service_unit = frappe.new_doc("Healthcare Service Unit")
|
service_unit = frappe.new_doc("Healthcare Service Unit")
|
||||||
service_unit.healthcare_service_unit_name = "Test Service Unit Ip Occupancy"
|
service_unit.healthcare_service_unit_name = unit_name or "Test Service Unit Ip Occupancy"
|
||||||
service_unit.company = "_Test Company"
|
service_unit.company = "_Test Company"
|
||||||
service_unit.service_unit_type = get_service_unit_type()
|
service_unit.service_unit_type = get_service_unit_type()
|
||||||
service_unit.inpatient_occupancy = 1
|
service_unit.inpatient_occupancy = 1
|
||||||
|
|||||||
@ -359,6 +359,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "normal_test_items",
|
"fieldname": "normal_test_items",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
|
"label": "Normal Test Result",
|
||||||
"options": "Normal Test Result",
|
"options": "Normal Test Result",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
@ -380,6 +381,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "sensitivity_test_items",
|
"fieldname": "sensitivity_test_items",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
|
"label": "Sensitivity Test Result",
|
||||||
"options": "Sensitivity Test Result",
|
"options": "Sensitivity Test Result",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"report_hide": 1
|
"report_hide": 1
|
||||||
@ -529,6 +531,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "descriptive_test_items",
|
"fieldname": "descriptive_test_items",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
|
"label": "Descriptive Test Result",
|
||||||
"options": "Descriptive Test Result",
|
"options": "Descriptive Test Result",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"report_hide": 1
|
"report_hide": 1
|
||||||
@ -549,13 +552,14 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "organism_test_items",
|
"fieldname": "organism_test_items",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
|
"label": "Organism Test Result",
|
||||||
"options": "Organism Test Result",
|
"options": "Organism Test Result",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-07-30 18:18:38.516215",
|
"modified": "2020-11-30 11:04:17.195848",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Healthcare",
|
"module": "Healthcare",
|
||||||
"name": "Lab Test",
|
"name": "Lab Test",
|
||||||
|
|||||||
@ -17,11 +17,9 @@ class LabTest(Document):
|
|||||||
self.validate_result_values()
|
self.validate_result_values()
|
||||||
self.db_set('submitted_date', getdate())
|
self.db_set('submitted_date', getdate())
|
||||||
self.db_set('status', 'Completed')
|
self.db_set('status', 'Completed')
|
||||||
insert_lab_test_to_medical_record(self)
|
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.db_set('status', 'Cancelled')
|
self.db_set('status', 'Cancelled')
|
||||||
delete_lab_test_from_medical_record(self)
|
|
||||||
self.reload()
|
self.reload()
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
@ -330,60 +328,6 @@ def get_employee_by_user_id(user_id):
|
|||||||
return frappe.get_doc('Employee', emp_id)
|
return frappe.get_doc('Employee', emp_id)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def insert_lab_test_to_medical_record(doc):
|
|
||||||
table_row = False
|
|
||||||
subject = cstr(doc.lab_test_name)
|
|
||||||
if doc.practitioner:
|
|
||||||
subject += frappe.bold(_('Healthcare Practitioner: '))+ doc.practitioner + '<br>'
|
|
||||||
if doc.normal_test_items:
|
|
||||||
item = doc.normal_test_items[0]
|
|
||||||
comment = ''
|
|
||||||
if item.lab_test_comment:
|
|
||||||
comment = str(item.lab_test_comment)
|
|
||||||
table_row = frappe.bold(_('Lab Test Conducted: ')) + item.lab_test_name
|
|
||||||
|
|
||||||
if item.lab_test_event:
|
|
||||||
table_row += frappe.bold(_('Lab Test Event: ')) + item.lab_test_event
|
|
||||||
|
|
||||||
if item.result_value:
|
|
||||||
table_row += ' ' + frappe.bold(_('Lab Test Result: ')) + item.result_value
|
|
||||||
|
|
||||||
if item.normal_range:
|
|
||||||
table_row += ' ' + _('Normal Range: ') + item.normal_range
|
|
||||||
table_row += ' ' + comment
|
|
||||||
|
|
||||||
elif doc.descriptive_test_items:
|
|
||||||
item = doc.descriptive_test_items[0]
|
|
||||||
|
|
||||||
if item.lab_test_particulars and item.result_value:
|
|
||||||
table_row = item.lab_test_particulars + ' ' + item.result_value
|
|
||||||
|
|
||||||
elif doc.sensitivity_test_items:
|
|
||||||
item = doc.sensitivity_test_items[0]
|
|
||||||
|
|
||||||
if item.antibiotic and item.antibiotic_sensitivity:
|
|
||||||
table_row = item.antibiotic + ' ' + item.antibiotic_sensitivity
|
|
||||||
|
|
||||||
if table_row:
|
|
||||||
subject += '<br>' + table_row
|
|
||||||
if doc.lab_test_comment:
|
|
||||||
subject += '<br>' + cstr(doc.lab_test_comment)
|
|
||||||
|
|
||||||
medical_record = frappe.new_doc('Patient Medical Record')
|
|
||||||
medical_record.patient = doc.patient
|
|
||||||
medical_record.subject = subject
|
|
||||||
medical_record.status = 'Open'
|
|
||||||
medical_record.communication_date = doc.result_date
|
|
||||||
medical_record.reference_doctype = 'Lab Test'
|
|
||||||
medical_record.reference_name = doc.name
|
|
||||||
medical_record.reference_owner = doc.owner
|
|
||||||
medical_record.save(ignore_permissions = True)
|
|
||||||
|
|
||||||
def delete_lab_test_from_medical_record(self):
|
|
||||||
medical_record_id = frappe.db.sql('select name from `tabPatient Medical Record` where reference_name=%s', (self.name))
|
|
||||||
|
|
||||||
if medical_record_id and medical_record_id[0][0]:
|
|
||||||
frappe.delete_doc('Patient Medical Record', medical_record_id[0][0])
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_lab_test_prescribed(patient):
|
def get_lab_test_prescribed(patient):
|
||||||
|
|||||||
@ -33,10 +33,10 @@ frappe.ui.form.on('Patient Appointment', {
|
|||||||
|
|
||||||
frm.set_query('service_unit', function() {
|
frm.set_query('service_unit', function() {
|
||||||
return {
|
return {
|
||||||
|
query: 'erpnext.controllers.queries.get_healthcare_service_units',
|
||||||
filters: {
|
filters: {
|
||||||
'is_group': false,
|
company: frm.doc.company,
|
||||||
'allow_appointments': true,
|
inpatient_record: frm.doc.inpatient_record
|
||||||
'company': frm.doc.company
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -18,6 +18,7 @@ from erpnext.healthcare.utils import check_fee_validity, get_service_item_and_pr
|
|||||||
class PatientAppointment(Document):
|
class PatientAppointment(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_overlaps()
|
self.validate_overlaps()
|
||||||
|
self.validate_service_unit()
|
||||||
self.set_appointment_datetime()
|
self.set_appointment_datetime()
|
||||||
self.validate_customer_created()
|
self.validate_customer_created()
|
||||||
self.set_status()
|
self.set_status()
|
||||||
@ -68,6 +69,19 @@ class PatientAppointment(Document):
|
|||||||
overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4])
|
overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4])
|
||||||
frappe.throw(overlapping_details, title=_('Appointments Overlapping'))
|
frappe.throw(overlapping_details, title=_('Appointments Overlapping'))
|
||||||
|
|
||||||
|
def validate_service_unit(self):
|
||||||
|
if self.inpatient_record and self.service_unit:
|
||||||
|
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
|
||||||
|
|
||||||
|
is_inpatient_occupancy_unit = frappe.db.get_value('Healthcare Service Unit', self.service_unit,
|
||||||
|
'inpatient_occupancy')
|
||||||
|
service_unit = get_current_healthcare_service_unit(self.inpatient_record)
|
||||||
|
if is_inpatient_occupancy_unit and service_unit != self.service_unit:
|
||||||
|
msg = _('Patient {0} is not admitted in the service unit {1}').format(frappe.bold(self.patient), frappe.bold(self.service_unit)) + '<br>'
|
||||||
|
msg += _('Appointment for service units with Inpatient Occupancy can only be created against the unit where patient has been admitted.')
|
||||||
|
frappe.throw(msg, title=_('Invalid Healthcare Service Unit'))
|
||||||
|
|
||||||
|
|
||||||
def set_appointment_datetime(self):
|
def set_appointment_datetime(self):
|
||||||
self.appointment_datetime = "%s %s" % (self.appointment_date, self.appointment_time or "00:00:00")
|
self.appointment_datetime = "%s %s" % (self.appointment_date, self.appointment_time or "00:00:00")
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
|||||||
import unittest
|
import unittest
|
||||||
import frappe
|
import frappe
|
||||||
from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter
|
from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter
|
||||||
from frappe.utils import nowdate, add_days
|
from frappe.utils import nowdate, add_days, now_datetime
|
||||||
from frappe.utils.make_random import get_random
|
from frappe.utils.make_random import get_random
|
||||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||||
|
|
||||||
@ -78,6 +78,59 @@ class TestPatientAppointment(unittest.TestCase):
|
|||||||
sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
|
sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
|
||||||
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'status'), 'Cancelled')
|
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'status'), 'Cancelled')
|
||||||
|
|
||||||
|
def test_appointment_booking_for_admission_service_unit(self):
|
||||||
|
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
|
||||||
|
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \
|
||||||
|
create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
|
||||||
|
|
||||||
|
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||||
|
patient, medical_department, practitioner = create_healthcare_docs()
|
||||||
|
patient = create_patient()
|
||||||
|
# Schedule Admission
|
||||||
|
ip_record = create_inpatient(patient)
|
||||||
|
ip_record.expected_length_of_stay = 0
|
||||||
|
ip_record.save(ignore_permissions = True)
|
||||||
|
|
||||||
|
# Admit
|
||||||
|
service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
|
||||||
|
admit_patient(ip_record, service_unit, now_datetime())
|
||||||
|
|
||||||
|
appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit)
|
||||||
|
self.assertEqual(appointment.service_unit, service_unit)
|
||||||
|
|
||||||
|
# Discharge
|
||||||
|
schedule_discharge(frappe.as_json({'patient': patient}))
|
||||||
|
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
|
||||||
|
mark_invoiced_inpatient_occupancy(ip_record1)
|
||||||
|
discharge_patient(ip_record1)
|
||||||
|
|
||||||
|
def test_invalid_healthcare_service_unit_validation(self):
|
||||||
|
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
|
||||||
|
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \
|
||||||
|
create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
|
||||||
|
|
||||||
|
frappe.db.sql("""delete from `tabInpatient Record`""")
|
||||||
|
patient, medical_department, practitioner = create_healthcare_docs()
|
||||||
|
patient = create_patient()
|
||||||
|
# Schedule Admission
|
||||||
|
ip_record = create_inpatient(patient)
|
||||||
|
ip_record.expected_length_of_stay = 0
|
||||||
|
ip_record.save(ignore_permissions = True)
|
||||||
|
|
||||||
|
# Admit
|
||||||
|
service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
|
||||||
|
admit_patient(ip_record, service_unit, now_datetime())
|
||||||
|
|
||||||
|
appointment_service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy for Appointment')
|
||||||
|
appointment = create_appointment(patient, practitioner, nowdate(), service_unit=appointment_service_unit, save=0)
|
||||||
|
self.assertRaises(frappe.exceptions.ValidationError, appointment.save)
|
||||||
|
|
||||||
|
# Discharge
|
||||||
|
schedule_discharge(frappe.as_json({'patient': patient}))
|
||||||
|
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
|
||||||
|
mark_invoiced_inpatient_occupancy(ip_record1)
|
||||||
|
discharge_patient(ip_record1)
|
||||||
|
|
||||||
|
|
||||||
def create_healthcare_docs():
|
def create_healthcare_docs():
|
||||||
patient = create_patient()
|
patient = create_patient()
|
||||||
@ -125,7 +178,7 @@ def create_encounter(appointment):
|
|||||||
encounter.submit()
|
encounter.submit()
|
||||||
return encounter
|
return encounter
|
||||||
|
|
||||||
def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0):
|
def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0, service_unit=None, save=1):
|
||||||
item = create_healthcare_service_items()
|
item = create_healthcare_service_items()
|
||||||
frappe.db.set_value('Healthcare Settings', None, 'inpatient_visit_charge_item', item)
|
frappe.db.set_value('Healthcare Settings', None, 'inpatient_visit_charge_item', item)
|
||||||
frappe.db.set_value('Healthcare Settings', None, 'op_consulting_charge_item', item)
|
frappe.db.set_value('Healthcare Settings', None, 'op_consulting_charge_item', item)
|
||||||
@ -136,11 +189,14 @@ def create_appointment(patient, practitioner, appointment_date, invoice=0, proce
|
|||||||
appointment.appointment_date = appointment_date
|
appointment.appointment_date = appointment_date
|
||||||
appointment.company = '_Test Company'
|
appointment.company = '_Test Company'
|
||||||
appointment.duration = 15
|
appointment.duration = 15
|
||||||
|
if service_unit:
|
||||||
|
appointment.service_unit = service_unit
|
||||||
if invoice:
|
if invoice:
|
||||||
appointment.mode_of_payment = 'Cash'
|
appointment.mode_of_payment = 'Cash'
|
||||||
appointment.paid_amount = 500
|
appointment.paid_amount = 500
|
||||||
if procedure_template:
|
if procedure_template:
|
||||||
appointment.procedure_template = create_clinical_procedure_template().get('name')
|
appointment.procedure_template = create_clinical_procedure_template().get('name')
|
||||||
|
if save:
|
||||||
appointment.save(ignore_permissions=True)
|
appointment.save(ignore_permissions=True)
|
||||||
return appointment
|
return appointment
|
||||||
|
|
||||||
@ -152,6 +208,7 @@ def create_healthcare_service_items():
|
|||||||
item.item_name = 'Consulting Charges'
|
item.item_name = 'Consulting Charges'
|
||||||
item.item_group = 'Services'
|
item.item_group = 'Services'
|
||||||
item.is_stock_item = 0
|
item.is_stock_item = 0
|
||||||
|
item.stock_uom = 'Nos'
|
||||||
item.save()
|
item.save()
|
||||||
return item.name
|
return item.name
|
||||||
|
|
||||||
|
|||||||
@ -210,7 +210,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "drug_prescription",
|
"fieldname": "drug_prescription",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Items",
|
"label": "Drug Prescription",
|
||||||
"options": "Drug Prescription"
|
"options": "Drug Prescription"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -328,7 +328,7 @@
|
|||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-16 21:00:08.644531",
|
"modified": "2020-11-30 10:39:00.783119",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Healthcare",
|
"module": "Healthcare",
|
||||||
"name": "Patient Encounter",
|
"name": "Patient Encounter",
|
||||||
|
|||||||
@ -17,10 +17,6 @@ class PatientEncounter(Document):
|
|||||||
def on_update(self):
|
def on_update(self):
|
||||||
if self.appointment:
|
if self.appointment:
|
||||||
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed')
|
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed')
|
||||||
update_encounter_medical_record(self)
|
|
||||||
|
|
||||||
def after_insert(self):
|
|
||||||
insert_encounter_to_medical_record(self)
|
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if self.therapies:
|
if self.therapies:
|
||||||
@ -33,8 +29,6 @@ class PatientEncounter(Document):
|
|||||||
if self.inpatient_record and self.drug_prescription:
|
if self.inpatient_record and self.drug_prescription:
|
||||||
delete_ip_medication_order(self)
|
delete_ip_medication_order(self)
|
||||||
|
|
||||||
delete_medical_record(self)
|
|
||||||
|
|
||||||
def set_title(self):
|
def set_title(self):
|
||||||
self.title = _('{0} with {1}').format(self.patient_name or self.patient,
|
self.title = _('{0} with {1}').format(self.patient_name or self.patient,
|
||||||
self.practitioner_name or self.practitioner)[:100]
|
self.practitioner_name or self.practitioner)[:100]
|
||||||
@ -102,61 +96,7 @@ def create_therapy_plan(encounter):
|
|||||||
frappe.msgprint(_('Therapy Plan {0} created successfully.').format(frappe.bold(doc.name)), alert=True)
|
frappe.msgprint(_('Therapy Plan {0} created successfully.').format(frappe.bold(doc.name)), alert=True)
|
||||||
|
|
||||||
|
|
||||||
def insert_encounter_to_medical_record(doc):
|
|
||||||
subject = set_subject_field(doc)
|
|
||||||
medical_record = frappe.new_doc('Patient Medical Record')
|
|
||||||
medical_record.patient = doc.patient
|
|
||||||
medical_record.subject = subject
|
|
||||||
medical_record.status = 'Open'
|
|
||||||
medical_record.communication_date = doc.encounter_date
|
|
||||||
medical_record.reference_doctype = 'Patient Encounter'
|
|
||||||
medical_record.reference_name = doc.name
|
|
||||||
medical_record.reference_owner = doc.owner
|
|
||||||
medical_record.save(ignore_permissions=True)
|
|
||||||
|
|
||||||
|
|
||||||
def update_encounter_medical_record(encounter):
|
|
||||||
medical_record_id = frappe.db.exists('Patient Medical Record', {'reference_name': encounter.name})
|
|
||||||
|
|
||||||
if medical_record_id and medical_record_id[0][0]:
|
|
||||||
subject = set_subject_field(encounter)
|
|
||||||
frappe.db.set_value('Patient Medical Record', medical_record_id[0][0], 'subject', subject)
|
|
||||||
else:
|
|
||||||
insert_encounter_to_medical_record(encounter)
|
|
||||||
|
|
||||||
|
|
||||||
def delete_medical_record(encounter):
|
|
||||||
record = frappe.db.exists('Patient Medical Record', {'reference_name', encounter.name})
|
|
||||||
if record:
|
|
||||||
frappe.delete_doc('Patient Medical Record', record, force=1)
|
|
||||||
|
|
||||||
def delete_ip_medication_order(encounter):
|
def delete_ip_medication_order(encounter):
|
||||||
record = frappe.db.exists('Inpatient Medication Order', {'patient_encounter': encounter.name})
|
record = frappe.db.exists('Inpatient Medication Order', {'patient_encounter': encounter.name})
|
||||||
if record:
|
if record:
|
||||||
frappe.delete_doc('Inpatient Medication Order', record, force=1)
|
frappe.delete_doc('Inpatient Medication Order', record, force=1)
|
||||||
|
|
||||||
|
|
||||||
def set_subject_field(encounter):
|
|
||||||
subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '<br>'
|
|
||||||
if encounter.symptoms:
|
|
||||||
subject += frappe.bold(_('Symptoms: ')) + '<br>'
|
|
||||||
for entry in encounter.symptoms:
|
|
||||||
subject += cstr(entry.complaint) + '<br>'
|
|
||||||
else:
|
|
||||||
subject += frappe.bold(_('No Symptoms')) + '<br>'
|
|
||||||
|
|
||||||
if encounter.diagnosis:
|
|
||||||
subject += frappe.bold(_('Diagnosis: ')) + '<br>'
|
|
||||||
for entry in encounter.diagnosis:
|
|
||||||
subject += cstr(entry.diagnosis) + '<br>'
|
|
||||||
else:
|
|
||||||
subject += frappe.bold(_('No Diagnosis')) + '<br>'
|
|
||||||
|
|
||||||
if encounter.drug_prescription:
|
|
||||||
subject += '<br>' + _('Drug(s) Prescribed.')
|
|
||||||
if encounter.lab_test_prescription:
|
|
||||||
subject += '<br>' + _('Test(s) Prescribed.')
|
|
||||||
if encounter.procedure_prescription:
|
|
||||||
subject += '<br>' + _('Procedure(s) Prescribed.')
|
|
||||||
|
|
||||||
return subject
|
|
||||||
|
|||||||
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-11-25 13:40:23.054469",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"document_type",
|
||||||
|
"date_fieldname",
|
||||||
|
"add_edit_fields",
|
||||||
|
"selected_fields"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "document_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Document Type",
|
||||||
|
"options": "DocType",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "selected_fields",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Selected Fields",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "add_edit_fields",
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Add / Edit Fields"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "date_fieldname",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Date Fieldname",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-11-30 13:54:37.474671",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Healthcare",
|
||||||
|
"name": "Patient History Custom Document Type",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class PatientHistoryCustomDocumentType(Document):
|
||||||
|
pass
|
||||||
@ -0,0 +1,133 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Patient History Settings', {
|
||||||
|
refresh: function(frm) {
|
||||||
|
frm.set_query('document_type', 'custom_doctypes', () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
custom: 1,
|
||||||
|
is_submittable: 1,
|
||||||
|
module: 'Healthcare',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
field_selector: function(frm, doc, standard=1) {
|
||||||
|
let document_fields = [];
|
||||||
|
if (doc.selected_fields)
|
||||||
|
document_fields = (JSON.parse(doc.selected_fields)).map(f => f.fieldname);
|
||||||
|
|
||||||
|
frm.call({
|
||||||
|
method: 'get_doctype_fields',
|
||||||
|
doc: frm.doc,
|
||||||
|
args: {
|
||||||
|
document_type: doc.document_type,
|
||||||
|
fields: document_fields
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
let doctype = 'Patient History Custom Document Type';
|
||||||
|
if (standard)
|
||||||
|
doctype = 'Patient History Standard Document Type';
|
||||||
|
|
||||||
|
frm.events.show_field_selector_dialog(frm, doc, doctype, r.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
show_field_selector_dialog: function(frm, doc, doctype, doc_fields) {
|
||||||
|
let d = new frappe.ui.Dialog({
|
||||||
|
title: __('{0} Fields', [__(doc.document_type)]),
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: __('Select Fields'),
|
||||||
|
fieldtype: 'MultiCheck',
|
||||||
|
fieldname: 'fields',
|
||||||
|
options: doc_fields,
|
||||||
|
columns: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
d.$body.prepend(`
|
||||||
|
<div class="columns-search">
|
||||||
|
<input type="text" placeholder="${__('Search')}" data-element="search" class="form-control input-xs">
|
||||||
|
</div>`
|
||||||
|
);
|
||||||
|
|
||||||
|
frappe.utils.setup_search(d.$body, '.unit-checkbox', '.label-area');
|
||||||
|
|
||||||
|
d.set_primary_action(__('Save'), () => {
|
||||||
|
let values = d.get_values().fields;
|
||||||
|
|
||||||
|
let selected_fields = [];
|
||||||
|
|
||||||
|
frappe.model.with_doctype(doc.document_type, function() {
|
||||||
|
for (let idx in values) {
|
||||||
|
let value = values[idx];
|
||||||
|
|
||||||
|
let field = frappe.get_meta(doc.document_type).fields.filter((df) => df.fieldname == value)[0];
|
||||||
|
if (field) {
|
||||||
|
selected_fields.push({
|
||||||
|
label: field.label,
|
||||||
|
fieldname: field.fieldname,
|
||||||
|
fieldtype: field.fieldtype
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.refresh();
|
||||||
|
frappe.model.set_value(doctype, doc.name, 'selected_fields', JSON.stringify(selected_fields));
|
||||||
|
});
|
||||||
|
|
||||||
|
d.hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
d.show();
|
||||||
|
},
|
||||||
|
|
||||||
|
get_date_field_for_dt: function(frm, row) {
|
||||||
|
frm.call({
|
||||||
|
method: 'get_date_field_for_dt',
|
||||||
|
doc: frm.doc,
|
||||||
|
args: {
|
||||||
|
document_type: row.document_type
|
||||||
|
},
|
||||||
|
callback: function(data) {
|
||||||
|
if (data.message) {
|
||||||
|
frappe.model.set_value('Patient History Custom Document Type',
|
||||||
|
row.name, 'date_fieldname', data.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on('Patient History Custom Document Type', {
|
||||||
|
document_type: function(frm, cdt, cdn) {
|
||||||
|
let row = locals[cdt][cdn];
|
||||||
|
if (row.document_type) {
|
||||||
|
frm.events.get_date_field_for_dt(frm, row);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
add_edit_fields: function(frm, cdt, cdn) {
|
||||||
|
let row = locals[cdt][cdn];
|
||||||
|
if (row.document_type) {
|
||||||
|
frm.events.field_selector(frm, row, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on('Patient History Standard Document Type', {
|
||||||
|
add_edit_fields: function(frm, cdt, cdn) {
|
||||||
|
let row = locals[cdt][cdn];
|
||||||
|
if (row.document_type) {
|
||||||
|
frm.events.field_selector(frm, row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-11-25 13:41:37.675518",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"standard_doctypes",
|
||||||
|
"section_break_2",
|
||||||
|
"custom_doctypes"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_2",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "custom_doctypes",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Custom Document Types",
|
||||||
|
"options": "Patient History Custom Document Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "standard_doctypes",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Standard Document Types",
|
||||||
|
"options": "Patient History Standard Document Type",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"issingle": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-11-25 13:43:38.511771",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Healthcare",
|
||||||
|
"name": "Patient History Settings",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@ -0,0 +1,188 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
import json
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import cstr, cint
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from erpnext.healthcare.page.patient_history.patient_history import get_patient_history_doctypes
|
||||||
|
|
||||||
|
class PatientHistorySettings(Document):
|
||||||
|
def validate(self):
|
||||||
|
self.validate_submittable_doctypes()
|
||||||
|
self.validate_date_fieldnames()
|
||||||
|
|
||||||
|
def validate_submittable_doctypes(self):
|
||||||
|
for entry in self.custom_doctypes:
|
||||||
|
if not cint(frappe.db.get_value('DocType', entry.document_type, 'is_submittable')):
|
||||||
|
msg = _('Row #{0}: Document Type {1} is not submittable. ').format(
|
||||||
|
entry.idx, frappe.bold(entry.document_type))
|
||||||
|
msg += _('Patient Medical Record can only be created for submittable document types.')
|
||||||
|
frappe.throw(msg)
|
||||||
|
|
||||||
|
def validate_date_fieldnames(self):
|
||||||
|
for entry in self.custom_doctypes:
|
||||||
|
field = frappe.get_meta(entry.document_type).get_field(entry.date_fieldname)
|
||||||
|
if not field:
|
||||||
|
frappe.throw(_('Row #{0}: No such Field named {1} found in the Document Type {2}.').format(
|
||||||
|
entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type)))
|
||||||
|
|
||||||
|
if field.fieldtype not in ['Date', 'Datetime']:
|
||||||
|
frappe.throw(_('Row #{0}: Field {1} in Document Type {2} is not a Date / Datetime field.').format(
|
||||||
|
entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type)))
|
||||||
|
|
||||||
|
def get_doctype_fields(self, document_type, fields):
|
||||||
|
multicheck_fields = []
|
||||||
|
doc_fields = frappe.get_meta(document_type).fields
|
||||||
|
|
||||||
|
for field in doc_fields:
|
||||||
|
if field.fieldtype not in frappe.model.no_value_fields or \
|
||||||
|
field.fieldtype in frappe.model.table_fields and not field.hidden:
|
||||||
|
multicheck_fields.append({
|
||||||
|
'label': field.label,
|
||||||
|
'value': field.fieldname,
|
||||||
|
'checked': 1 if field.fieldname in fields else 0
|
||||||
|
})
|
||||||
|
|
||||||
|
return multicheck_fields
|
||||||
|
|
||||||
|
def get_date_field_for_dt(self, document_type):
|
||||||
|
meta = frappe.get_meta(document_type)
|
||||||
|
date_fields = meta.get('fields', {
|
||||||
|
'fieldtype': ['in', ['Date', 'Datetime']]
|
||||||
|
})
|
||||||
|
|
||||||
|
if date_fields:
|
||||||
|
return date_fields[0].get('fieldname')
|
||||||
|
|
||||||
|
def create_medical_record(doc, method=None):
|
||||||
|
medical_record_required = validate_medical_record_required(doc)
|
||||||
|
if not medical_record_required:
|
||||||
|
return
|
||||||
|
|
||||||
|
if frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name }):
|
||||||
|
return
|
||||||
|
|
||||||
|
subject = set_subject_field(doc)
|
||||||
|
date_field = get_date_field(doc.doctype)
|
||||||
|
medical_record = frappe.new_doc('Patient Medical Record')
|
||||||
|
medical_record.patient = doc.patient
|
||||||
|
medical_record.subject = subject
|
||||||
|
medical_record.status = 'Open'
|
||||||
|
medical_record.communication_date = doc.get(date_field)
|
||||||
|
medical_record.reference_doctype = doc.doctype
|
||||||
|
medical_record.reference_name = doc.name
|
||||||
|
medical_record.reference_owner = doc.owner
|
||||||
|
medical_record.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
|
def update_medical_record(doc, method=None):
|
||||||
|
medical_record_required = validate_medical_record_required(doc)
|
||||||
|
if not medical_record_required:
|
||||||
|
return
|
||||||
|
|
||||||
|
medical_record_id = frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name })
|
||||||
|
|
||||||
|
if medical_record_id:
|
||||||
|
subject = set_subject_field(doc)
|
||||||
|
frappe.db.set_value('Patient Medical Record', medical_record_id[0][0], 'subject', subject)
|
||||||
|
else:
|
||||||
|
create_medical_record(doc)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_medical_record(doc, method=None):
|
||||||
|
medical_record_required = validate_medical_record_required(doc)
|
||||||
|
if not medical_record_required:
|
||||||
|
return
|
||||||
|
|
||||||
|
record = frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name })
|
||||||
|
if record:
|
||||||
|
frappe.delete_doc('Patient Medical Record', record, force=1)
|
||||||
|
|
||||||
|
|
||||||
|
def set_subject_field(doc):
|
||||||
|
from frappe.utils.formatters import format_value
|
||||||
|
|
||||||
|
meta = frappe.get_meta(doc.doctype)
|
||||||
|
subject = ''
|
||||||
|
patient_history_fields = get_patient_history_fields(doc)
|
||||||
|
|
||||||
|
for entry in patient_history_fields:
|
||||||
|
fieldname = entry.get('fieldname')
|
||||||
|
if entry.get('fieldtype') == 'Table' and doc.get(fieldname):
|
||||||
|
formatted_value = get_formatted_value_for_table_field(doc.get(fieldname), meta.get_field(fieldname))
|
||||||
|
subject += frappe.bold(_(entry.get('label')) + ': ') + '<br>' + cstr(formatted_value) + '<br>'
|
||||||
|
|
||||||
|
else:
|
||||||
|
if doc.get(fieldname):
|
||||||
|
formatted_value = format_value(doc.get(fieldname), meta.get_field(fieldname), doc)
|
||||||
|
subject += frappe.bold(_(entry.get('label')) + ': ') + cstr(formatted_value) + '<br>'
|
||||||
|
|
||||||
|
return subject
|
||||||
|
|
||||||
|
|
||||||
|
def get_date_field(doctype):
|
||||||
|
dt = get_patient_history_config_dt(doctype)
|
||||||
|
|
||||||
|
return frappe.db.get_value(dt, { 'document_type': doctype }, 'date_fieldname')
|
||||||
|
|
||||||
|
|
||||||
|
def get_patient_history_fields(doc):
|
||||||
|
dt = get_patient_history_config_dt(doc.doctype)
|
||||||
|
patient_history_fields = frappe.db.get_value(dt, { 'document_type': doc.doctype }, 'selected_fields')
|
||||||
|
|
||||||
|
if patient_history_fields:
|
||||||
|
return json.loads(patient_history_fields)
|
||||||
|
|
||||||
|
|
||||||
|
def get_formatted_value_for_table_field(items, df):
|
||||||
|
child_meta = frappe.get_meta(df.options)
|
||||||
|
|
||||||
|
table_head = ''
|
||||||
|
table_row = ''
|
||||||
|
html = ''
|
||||||
|
create_head = True
|
||||||
|
for item in items:
|
||||||
|
table_row += '<tr>'
|
||||||
|
for cdf in child_meta.fields:
|
||||||
|
if cdf.in_list_view:
|
||||||
|
if create_head:
|
||||||
|
table_head += '<td>' + cdf.label + '</td>'
|
||||||
|
if item.get(cdf.fieldname):
|
||||||
|
table_row += '<td>' + str(item.get(cdf.fieldname)) + '</td>'
|
||||||
|
else:
|
||||||
|
table_row += '<td></td>'
|
||||||
|
create_head = False
|
||||||
|
table_row += '</tr>'
|
||||||
|
|
||||||
|
html += "<table class='table table-condensed table-bordered'>" + table_head + table_row + "</table>"
|
||||||
|
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
def get_patient_history_config_dt(doctype):
|
||||||
|
if frappe.db.get_value('DocType', doctype, 'custom'):
|
||||||
|
return 'Patient History Custom Document Type'
|
||||||
|
else:
|
||||||
|
return 'Patient History Standard Document Type'
|
||||||
|
|
||||||
|
|
||||||
|
def validate_medical_record_required(doc):
|
||||||
|
if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard \
|
||||||
|
or get_module(doc) != 'Healthcare':
|
||||||
|
return False
|
||||||
|
|
||||||
|
if doc.doctype not in get_patient_history_doctypes():
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_module(doc):
|
||||||
|
module = doc.meta.module
|
||||||
|
if not module:
|
||||||
|
module = frappe.db.get_value('DocType', doc.doctype, 'module')
|
||||||
|
|
||||||
|
return module
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
import json
|
||||||
|
from frappe.utils import getdate
|
||||||
|
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_patient
|
||||||
|
|
||||||
|
class TestPatientHistorySettings(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
dt = create_custom_doctype()
|
||||||
|
settings = frappe.get_single("Patient History Settings")
|
||||||
|
settings.append("custom_doctypes", {
|
||||||
|
"document_type": dt.name,
|
||||||
|
"date_fieldname": "date",
|
||||||
|
"selected_fields": json.dumps([{
|
||||||
|
"label": "Date",
|
||||||
|
"fieldname": "date",
|
||||||
|
"fieldtype": "Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Rating",
|
||||||
|
"fieldname": "rating",
|
||||||
|
"fieldtype": "Rating"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Feedback",
|
||||||
|
"fieldname": "feedback",
|
||||||
|
"fieldtype": "Small Text"
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
settings.save()
|
||||||
|
|
||||||
|
def test_custom_doctype_medical_record(self):
|
||||||
|
# tests for medical record creation of standard doctypes in test_patient_medical_record.py
|
||||||
|
patient = create_patient()
|
||||||
|
doc = create_doc(patient)
|
||||||
|
|
||||||
|
# check for medical record
|
||||||
|
medical_rec = frappe.db.exists("Patient Medical Record", {"status": "Open", "reference_name": doc.name})
|
||||||
|
self.assertTrue(medical_rec)
|
||||||
|
|
||||||
|
medical_rec = frappe.get_doc("Patient Medical Record", medical_rec)
|
||||||
|
expected_subject = "<b>Date: </b>{0}<br><b>Rating: </b>3<br><b>Feedback: </b>Test Patient History Settings<br>".format(
|
||||||
|
frappe.utils.format_date(getdate()))
|
||||||
|
self.assertEqual(medical_rec.subject, expected_subject)
|
||||||
|
self.assertEqual(medical_rec.patient, patient)
|
||||||
|
self.assertEqual(medical_rec.communication_date, getdate())
|
||||||
|
|
||||||
|
|
||||||
|
def create_custom_doctype():
|
||||||
|
if not frappe.db.exists("DocType", "Test Patient Feedback"):
|
||||||
|
doc = frappe.get_doc({
|
||||||
|
"doctype": "DocType",
|
||||||
|
"module": "Healthcare",
|
||||||
|
"custom": 1,
|
||||||
|
"is_submittable": 1,
|
||||||
|
"fields": [{
|
||||||
|
"label": "Date",
|
||||||
|
"fieldname": "date",
|
||||||
|
"fieldtype": "Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Patient",
|
||||||
|
"fieldname": "patient",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Patient"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Rating",
|
||||||
|
"fieldname": "rating",
|
||||||
|
"fieldtype": "Rating"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Feedback",
|
||||||
|
"fieldname": "feedback",
|
||||||
|
"fieldtype": "Small Text"
|
||||||
|
}],
|
||||||
|
"permissions": [{
|
||||||
|
"role": "System Manager",
|
||||||
|
"read": 1
|
||||||
|
}],
|
||||||
|
"name": "Test Patient Feedback",
|
||||||
|
})
|
||||||
|
doc.insert()
|
||||||
|
return doc
|
||||||
|
else:
|
||||||
|
return frappe.get_doc("DocType", "Test Patient Feedback")
|
||||||
|
|
||||||
|
|
||||||
|
def create_doc(patient):
|
||||||
|
doc = frappe.get_doc({
|
||||||
|
"doctype": "Test Patient Feedback",
|
||||||
|
"patient": patient,
|
||||||
|
"date": getdate(),
|
||||||
|
"rating": 3,
|
||||||
|
"feedback": "Test Patient History Settings"
|
||||||
|
}).insert()
|
||||||
|
doc.submit()
|
||||||
|
|
||||||
|
return doc
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-11-25 13:39:36.014814",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"document_type",
|
||||||
|
"date_fieldname",
|
||||||
|
"add_edit_fields",
|
||||||
|
"selected_fields"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "document_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Document Type",
|
||||||
|
"options": "DocType",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "selected_fields",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Selected Fields",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "add_edit_fields",
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Add / Edit Fields"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "date_fieldname",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Date Fieldname",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-11-30 13:54:56.773325",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Healthcare",
|
||||||
|
"name": "Patient History Standard Document Type",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class PatientHistoryStandardDocumentType(Document):
|
||||||
|
pass
|
||||||
@ -18,6 +18,7 @@ class TestPatientMedicalRecord(unittest.TestCase):
|
|||||||
patient, medical_department, practitioner = create_healthcare_docs()
|
patient, medical_department, practitioner = create_healthcare_docs()
|
||||||
appointment = create_appointment(patient, practitioner, nowdate(), invoice=1)
|
appointment = create_appointment(patient, practitioner, nowdate(), invoice=1)
|
||||||
encounter = create_encounter(appointment)
|
encounter = create_encounter(appointment)
|
||||||
|
|
||||||
# check for encounter
|
# check for encounter
|
||||||
medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': encounter.name})
|
medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': encounter.name})
|
||||||
self.assertTrue(medical_rec)
|
self.assertTrue(medical_rec)
|
||||||
|
|||||||
@ -41,7 +41,6 @@ class TherapySession(Document):
|
|||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.update_sessions_count_in_therapy_plan()
|
self.update_sessions_count_in_therapy_plan()
|
||||||
insert_session_medical_record(self)
|
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
if self.appointment:
|
if self.appointment:
|
||||||
@ -142,23 +141,3 @@ def get_therapy_item(therapy, item):
|
|||||||
item.reference_dt = 'Therapy Session'
|
item.reference_dt = 'Therapy Session'
|
||||||
item.reference_dn = therapy.name
|
item.reference_dn = therapy.name
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
def insert_session_medical_record(doc):
|
|
||||||
subject = frappe.bold(_('Therapy: ')) + cstr(doc.therapy_type) + '<br>'
|
|
||||||
if doc.therapy_plan:
|
|
||||||
subject += frappe.bold(_('Therapy Plan: ')) + cstr(doc.therapy_plan) + '<br>'
|
|
||||||
if doc.practitioner:
|
|
||||||
subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
|
|
||||||
subject += frappe.bold(_('Total Counts Targeted: ')) + cstr(doc.total_counts_targeted) + '<br>'
|
|
||||||
subject += frappe.bold(_('Total Counts Completed: ')) + cstr(doc.total_counts_completed) + '<br>'
|
|
||||||
|
|
||||||
medical_record = frappe.new_doc('Patient Medical Record')
|
|
||||||
medical_record.patient = doc.patient
|
|
||||||
medical_record.subject = subject
|
|
||||||
medical_record.status = 'Open'
|
|
||||||
medical_record.communication_date = doc.start_date
|
|
||||||
medical_record.reference_doctype = 'Therapy Session'
|
|
||||||
medical_record.reference_name = doc.name
|
|
||||||
medical_record.reference_owner = doc.owner
|
|
||||||
medical_record.save(ignore_permissions=True)
|
|
||||||
@ -12,47 +12,7 @@ class VitalSigns(Document):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
self.set_title()
|
self.set_title()
|
||||||
|
|
||||||
def on_submit(self):
|
|
||||||
insert_vital_signs_to_medical_record(self)
|
|
||||||
|
|
||||||
def on_cancel(self):
|
|
||||||
delete_vital_signs_from_medical_record(self)
|
|
||||||
|
|
||||||
def set_title(self):
|
def set_title(self):
|
||||||
self.title = _('{0} on {1}').format(self.patient_name or self.patient,
|
self.title = _('{0} on {1}').format(self.patient_name or self.patient,
|
||||||
frappe.utils.format_date(self.signs_date))[:100]
|
frappe.utils.format_date(self.signs_date))[:100]
|
||||||
|
|
||||||
def insert_vital_signs_to_medical_record(doc):
|
|
||||||
subject = set_subject_field(doc)
|
|
||||||
medical_record = frappe.new_doc('Patient Medical Record')
|
|
||||||
medical_record.patient = doc.patient
|
|
||||||
medical_record.subject = subject
|
|
||||||
medical_record.status = 'Open'
|
|
||||||
medical_record.communication_date = doc.signs_date
|
|
||||||
medical_record.reference_doctype = 'Vital Signs'
|
|
||||||
medical_record.reference_name = doc.name
|
|
||||||
medical_record.reference_owner = doc.owner
|
|
||||||
medical_record.flags.ignore_mandatory = True
|
|
||||||
medical_record.save(ignore_permissions=True)
|
|
||||||
|
|
||||||
def delete_vital_signs_from_medical_record(doc):
|
|
||||||
medical_record = frappe.db.get_value('Patient Medical Record', {'reference_name': doc.name})
|
|
||||||
if medical_record:
|
|
||||||
frappe.delete_doc('Patient Medical Record', medical_record)
|
|
||||||
|
|
||||||
def set_subject_field(doc):
|
|
||||||
subject = ''
|
|
||||||
if doc.temperature:
|
|
||||||
subject += frappe.bold(_('Temperature: ')) + cstr(doc.temperature) + '<br>'
|
|
||||||
if doc.pulse:
|
|
||||||
subject += frappe.bold(_('Pulse: ')) + cstr(doc.pulse) + '<br>'
|
|
||||||
if doc.respiratory_rate:
|
|
||||||
subject += frappe.bold(_('Respiratory Rate: ')) + cstr(doc.respiratory_rate) + '<br>'
|
|
||||||
if doc.bp:
|
|
||||||
subject += frappe.bold(_('BP: ')) + cstr(doc.bp) + '<br>'
|
|
||||||
if doc.bmi:
|
|
||||||
subject += frappe.bold(_('BMI: ')) + cstr(doc.bmi) + '<br>'
|
|
||||||
if doc.nutrition_note:
|
|
||||||
subject += frappe.bold(_('Note: ')) + cstr(doc.nutrition_note) + '<br>'
|
|
||||||
|
|
||||||
return subject
|
|
||||||
|
|||||||
@ -109,6 +109,11 @@
|
|||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.patient-history-filter {
|
||||||
|
margin-left: 35px;
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
#page-medical_record .plot-wrapper {
|
#page-medical_record .plot-wrapper {
|
||||||
padding: 20px 15px;
|
padding: 20px 15px;
|
||||||
border-bottom: 1px solid #d1d8dd;
|
border-bottom: 1px solid #d1d8dd;
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
<p class="text-center">{%= __("Select Patient") %}</p>
|
|
||||||
<p class="patient" style="margin: auto; max-width: 300px; margin-bottom: 20px;"></p>
|
<p class="patient" style="margin: auto; max-width: 300px; margin-bottom: 20px;"></p>
|
||||||
<div class="patient_details" style="z-index=0"></div>
|
<div class="patient_details" style="z-index=0"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -11,6 +10,13 @@
|
|||||||
<div id="chart" class="col-sm-12 patient_vital_charts">
|
<div id="chart" class="col-sm-12 patient_vital_charts">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="header-separator col-sm-12 d-flex border-bottom py-3" style="display:none"></div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 d-flex">
|
||||||
|
<div class="patient-history-filter doctype-filter"></div>
|
||||||
|
<div class="patient-history-filter date-filter"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="col-sm-12 patient_documents_list">
|
<div class="col-sm-12 patient_documents_list">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12 text-center py-3">
|
<div class="col-sm-12 text-center py-3">
|
||||||
|
|||||||
@ -1,33 +1,38 @@
|
|||||||
frappe.provide("frappe.patient_history");
|
frappe.provide('frappe.patient_history');
|
||||||
frappe.pages['patient_history'].on_page_load = function(wrapper) {
|
frappe.pages['patient_history'].on_page_load = function(wrapper) {
|
||||||
var me = this;
|
let me = this;
|
||||||
var page = frappe.ui.make_app_page({
|
let page = frappe.ui.make_app_page({
|
||||||
parent: wrapper,
|
parent: wrapper,
|
||||||
title: 'Patient History',
|
title: 'Patient History',
|
||||||
single_column: true
|
single_column: true
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.breadcrumbs.add("Healthcare");
|
frappe.breadcrumbs.add('Healthcare');
|
||||||
let pid = '';
|
let pid = '';
|
||||||
page.main.html(frappe.render_template("patient_history", {}));
|
page.main.html(frappe.render_template('patient_history', {}));
|
||||||
var patient = frappe.ui.form.make_control({
|
page.main.find('.header-separator').hide();
|
||||||
parent: page.main.find(".patient"),
|
|
||||||
|
let patient = frappe.ui.form.make_control({
|
||||||
|
parent: page.main.find('.patient'),
|
||||||
df: {
|
df: {
|
||||||
fieldtype: "Link",
|
fieldtype: 'Link',
|
||||||
options: "Patient",
|
options: 'Patient',
|
||||||
fieldname: "patient",
|
fieldname: 'patient',
|
||||||
|
placeholder: __('Select Patient'),
|
||||||
|
only_select: true,
|
||||||
change: function() {
|
change: function() {
|
||||||
if(pid != patient.get_value() && patient.get_value()){
|
let patient_id = patient.get_value();
|
||||||
|
if (pid != patient_id && patient_id) {
|
||||||
me.start = 0;
|
me.start = 0;
|
||||||
me.page.main.find(".patient_documents_list").html("");
|
me.page.main.find('.patient_documents_list').html('');
|
||||||
get_documents(patient.get_value(), me);
|
setup_filters(patient_id, me);
|
||||||
show_patient_info(patient.get_value(), me);
|
get_documents(patient_id, me);
|
||||||
show_patient_vital_charts(patient.get_value(), me, "bp", "mmHg", "Blood Pressure");
|
show_patient_info(patient_id, me);
|
||||||
|
show_patient_vital_charts(patient_id, me, 'bp', 'mmHg', 'Blood Pressure');
|
||||||
}
|
}
|
||||||
pid = patient.get_value();
|
pid = patient_id;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
only_input: true,
|
|
||||||
});
|
});
|
||||||
patient.refresh();
|
patient.refresh();
|
||||||
|
|
||||||
@ -35,77 +40,145 @@ frappe.pages['patient_history'].on_page_load = function(wrapper) {
|
|||||||
patient.set_value(frappe.route_options.patient);
|
patient.set_value(frappe.route_options.patient);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.page.main.on("click", ".btn-show-chart", function() {
|
this.page.main.on('click', '.btn-show-chart', function() {
|
||||||
var btn_show_id = $(this).attr("data-show-chart-id"), pts = $(this).attr("data-pts");
|
let btn_show_id = $(this).attr('data-show-chart-id'), pts = $(this).attr('data-pts');
|
||||||
var title = $(this).attr("data-title");
|
let title = $(this).attr('data-title');
|
||||||
show_patient_vital_charts(patient.get_value(), me, btn_show_id, pts, title);
|
show_patient_vital_charts(patient.get_value(), me, btn_show_id, pts, title);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.page.main.on("click", ".btn-more", function() {
|
this.page.main.on('click', '.btn-more', function() {
|
||||||
var doctype = $(this).attr("data-doctype"), docname = $(this).attr("data-docname");
|
let doctype = $(this).attr('data-doctype'), docname = $(this).attr('data-docname');
|
||||||
if(me.page.main.find("."+docname).parent().find('.document-html').attr('data-fetched') == "1"){
|
if (me.page.main.find('.'+docname).parent().find('.document-html').attr('data-fetched') == '1') {
|
||||||
me.page.main.find("."+docname).hide();
|
me.page.main.find('.'+docname).hide();
|
||||||
me.page.main.find("."+docname).parent().find('.document-html').show();
|
me.page.main.find('.'+docname).parent().find('.document-html').show();
|
||||||
} else {
|
} else {
|
||||||
if (doctype && docname) {
|
if (doctype && docname) {
|
||||||
let exclude = ["patient", "patient_name", 'patient_sex', "encounter_date"];
|
let exclude = ['patient', 'patient_name', 'patient_sex', 'encounter_date'];
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.healthcare.utils.render_doc_as_html",
|
method: 'erpnext.healthcare.utils.render_doc_as_html',
|
||||||
args:{
|
args:{
|
||||||
doctype: doctype,
|
doctype: doctype,
|
||||||
docname: docname,
|
docname: docname,
|
||||||
exclude_fields: exclude
|
exclude_fields: exclude
|
||||||
},
|
},
|
||||||
|
freeze: true,
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if (r.message) {
|
if (r.message) {
|
||||||
me.page.main.find("."+docname).hide();
|
me.page.main.find('.' + docname).hide();
|
||||||
me.page.main.find("."+docname).parent().find('.document-html').html(r.message.html+"\
|
|
||||||
<div align='center'><a class='btn octicon octicon-chevron-up btn-default btn-xs\
|
me.page.main.find('.' + docname).parent().find('.document-html').html(
|
||||||
btn-less' data-doctype='"+doctype+"' data-docname='"+docname+"'></a></div>");
|
`${r.message.html}
|
||||||
me.page.main.find("."+docname).parent().find('.document-html').show();
|
<div align='center'>
|
||||||
me.page.main.find("."+docname).parent().find('.document-html').attr('data-fetched', "1");
|
<a class='btn octicon octicon-chevron-up btn-default btn-xs btn-less'
|
||||||
|
data-doctype='${doctype}'
|
||||||
|
data-docname='${docname}'>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
me.page.main.find('.' + docname).parent().find('.document-html').show();
|
||||||
|
me.page.main.find('.' + docname).parent().find('.document-html').attr('data-fetched', '1');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
freeze: true
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.page.main.on("click", ".btn-less", function() {
|
this.page.main.on('click', '.btn-less', function() {
|
||||||
var docname = $(this).attr("data-docname");
|
let docname = $(this).attr('data-docname');
|
||||||
me.page.main.find("."+docname).parent().find('.document-id').show();
|
me.page.main.find('.' + docname).parent().find('.document-id').show();
|
||||||
me.page.main.find("."+docname).parent().find('.document-html').hide();
|
me.page.main.find('.' + docname).parent().find('.document-html').hide();
|
||||||
});
|
});
|
||||||
me.start = 0;
|
me.start = 0;
|
||||||
me.page.main.on("click", ".btn-get-records", function(){
|
me.page.main.on('click', '.btn-get-records', function() {
|
||||||
get_documents(patient.get_value(), me);
|
get_documents(patient.get_value(), me);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var get_documents = function(patient, me){
|
let setup_filters = function(patient, me) {
|
||||||
frappe.call({
|
$('.doctype-filter').empty();
|
||||||
"method": "erpnext.healthcare.page.patient_history.patient_history.get_feed",
|
frappe.xcall(
|
||||||
args: {
|
'erpnext.healthcare.page.patient_history.patient_history.get_patient_history_doctypes'
|
||||||
|
).then(document_types => {
|
||||||
|
let doctype_filter = frappe.ui.form.make_control({
|
||||||
|
parent: $('.doctype-filter'),
|
||||||
|
df: {
|
||||||
|
fieldtype: 'MultiSelectList',
|
||||||
|
fieldname: 'document_type',
|
||||||
|
placeholder: __('Select Document Type'),
|
||||||
|
input_class: 'input-xs',
|
||||||
|
change: () => {
|
||||||
|
me.start = 0;
|
||||||
|
me.page.main.find('.patient_documents_list').html('');
|
||||||
|
get_documents(patient, me, doctype_filter.get_value(), date_range_field.get_value());
|
||||||
|
},
|
||||||
|
get_data: () => {
|
||||||
|
return document_types.map(document_type => {
|
||||||
|
return {
|
||||||
|
description: document_type,
|
||||||
|
value: document_type
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
doctype_filter.refresh();
|
||||||
|
|
||||||
|
$('.date-filter').empty();
|
||||||
|
let date_range_field = frappe.ui.form.make_control({
|
||||||
|
df: {
|
||||||
|
fieldtype: 'DateRange',
|
||||||
|
fieldname: 'date_range',
|
||||||
|
placeholder: __('Date Range'),
|
||||||
|
input_class: 'input-xs',
|
||||||
|
change: () => {
|
||||||
|
let selected_date_range = date_range_field.get_value();
|
||||||
|
if (selected_date_range && selected_date_range.length === 2) {
|
||||||
|
me.start = 0;
|
||||||
|
me.page.main.find('.patient_documents_list').html('');
|
||||||
|
get_documents(patient, me, doctype_filter.get_value(), selected_date_range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parent: $('.date-filter')
|
||||||
|
});
|
||||||
|
date_range_field.refresh();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let get_documents = function(patient, me, document_types="", selected_date_range="") {
|
||||||
|
let filters = {
|
||||||
name: patient,
|
name: patient,
|
||||||
start: me.start,
|
start: me.start,
|
||||||
page_length: 20
|
page_length: 20
|
||||||
},
|
};
|
||||||
|
if (document_types)
|
||||||
|
filters['document_types'] = document_types;
|
||||||
|
if (selected_date_range)
|
||||||
|
filters['date_range'] = selected_date_range;
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
'method': 'erpnext.healthcare.page.patient_history.patient_history.get_feed',
|
||||||
|
args: filters,
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
var data = r.message;
|
let data = r.message;
|
||||||
if (data.length) {
|
if (data.length) {
|
||||||
add_to_records(me, data);
|
add_to_records(me, data);
|
||||||
} else {
|
} else {
|
||||||
me.page.main.find(".patient_documents_list").append("<div class='text-muted' align='center'><br><br>No more records..<br><br></div>");
|
me.page.main.find('.patient_documents_list').append(`
|
||||||
me.page.main.find(".btn-get-records").hide();
|
<div class='text-muted' align='center'>
|
||||||
|
<br><br>${__('No more records..')}<br><br>
|
||||||
|
</div>`);
|
||||||
|
me.page.main.find('.btn-get-records').hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var add_to_records = function(me, data){
|
let add_to_records = function(me, data) {
|
||||||
var details = "<ul class='nav nav-pills nav-stacked'>";
|
let details = "<ul class='nav nav-pills nav-stacked'>";
|
||||||
var i;
|
let i;
|
||||||
for (i=0; i<data.length; i++) {
|
for (i=0; i<data.length; i++) {
|
||||||
if (data[i].reference_doctype) {
|
if (data[i].reference_doctype) {
|
||||||
let label = '';
|
let label = '';
|
||||||
@ -113,29 +186,40 @@ var add_to_records = function(me, data){
|
|||||||
label += "<br/>" + data[i].subject;
|
label += "<br/>" + data[i].subject;
|
||||||
}
|
}
|
||||||
data[i] = add_date_separator(data[i]);
|
data[i] = add_date_separator(data[i]);
|
||||||
|
|
||||||
if (frappe.user_info(data[i].owner).image) {
|
if (frappe.user_info(data[i].owner).image) {
|
||||||
data[i].imgsrc = frappe.utils.get_file_link(frappe.user_info(data[i].owner).image);
|
data[i].imgsrc = frappe.utils.get_file_link(frappe.user_info(data[i].owner).image);
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
data[i].imgsrc = false;
|
data[i].imgsrc = false;
|
||||||
}
|
}
|
||||||
var time_line_heading = data[i].practitioner ? `${data[i].practitioner} ` : ``;
|
|
||||||
time_line_heading += data[i].reference_doctype + " - "+ data[i].reference_name;
|
let time_line_heading = data[i].practitioner ? `${data[i].practitioner} ` : ``;
|
||||||
details += `<li data-toggle='pill' class='patient_doc_menu'
|
time_line_heading += data[i].reference_doctype + " - " +
|
||||||
|
`<a onclick="frappe.set_route('Form', '${data[i].reference_doctype}', '${data[i].reference_name}');">
|
||||||
|
${data[i].reference_name}
|
||||||
|
</a>`;
|
||||||
|
|
||||||
|
details += `
|
||||||
|
<li data-toggle='pill' class='patient_doc_menu'
|
||||||
data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'>
|
data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'>
|
||||||
<div class='col-sm-12 d-flex border-bottom py-3'>`;
|
<div class='col-sm-12 d-flex border-bottom py-3'>`;
|
||||||
|
|
||||||
if (data[i].imgsrc) {
|
if (data[i].imgsrc) {
|
||||||
details += `<span class='mr-3'>
|
details += `
|
||||||
<img class='avtar' src='${data[i].imgsrc}' width='32' height='32'>
|
<span class='mr-3'>
|
||||||
</img>
|
<img class='avtar' src='${data[i].imgsrc}' width='32' height='32'></img>
|
||||||
</span>`;
|
</span>`;
|
||||||
} else {
|
} else {
|
||||||
details += `<span class='mr-3 avatar avatar-small' style='width:32px; height:32px;'><div align='center' class='standard-image'
|
details += `<span class='mr-3 avatar avatar-small' style='width:32px; height:32px;'>
|
||||||
style='background-color: #fafbfc;'>${data[i].practitioner ? data[i].practitioner.charAt(0) : "U"}</div></span>`;
|
<div align='center' class='standard-image' style='background-color: #fafbfc;'>
|
||||||
|
${data[i].practitioner ? data[i].practitioner.charAt(0) : 'U'}
|
||||||
|
</div>
|
||||||
|
</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
details += `<div class='d-flex flex-column width-full'>
|
details += `<div class='d-flex flex-column width-full'>
|
||||||
<div>
|
<div>
|
||||||
`+time_line_heading+` on
|
`+time_line_heading+`
|
||||||
<span>
|
<span>
|
||||||
${data[i].date_sep}
|
${data[i].date_sep}
|
||||||
</span>
|
</span>
|
||||||
@ -156,133 +240,150 @@ var add_to_records = function(me, data){
|
|||||||
</li>`;
|
</li>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
details += "</ul>";
|
|
||||||
me.page.main.find(".patient_documents_list").append(details);
|
details += '</ul>';
|
||||||
|
me.page.main.find('.patient_documents_list').append(details);
|
||||||
me.start += data.length;
|
me.start += data.length;
|
||||||
|
|
||||||
if (data.length === 20) {
|
if (data.length === 20) {
|
||||||
me.page.main.find(".btn-get-records").show();
|
me.page.main.find(".btn-get-records").show();
|
||||||
} else {
|
} else {
|
||||||
me.page.main.find(".btn-get-records").hide();
|
me.page.main.find(".btn-get-records").hide();
|
||||||
me.page.main.find(".patient_documents_list").append("<div class='text-muted' align='center'><br><br>No more records..<br><br></div>");
|
me.page.main.find(".patient_documents_list").append(`
|
||||||
|
<div class='text-muted' align='center'>
|
||||||
|
<br><br>${__('No more records..')}<br><br>
|
||||||
|
</div>`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var add_date_separator = function(data) {
|
let add_date_separator = function(data) {
|
||||||
var date = frappe.datetime.str_to_obj(data.creation);
|
let date = frappe.datetime.str_to_obj(data.communication_date);
|
||||||
|
let pdate = '';
|
||||||
|
let diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date));
|
||||||
|
|
||||||
var diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date));
|
|
||||||
if (diff < 1) {
|
if (diff < 1) {
|
||||||
var pdate = 'Today';
|
pdate = __('Today');
|
||||||
} else if (diff < 2) {
|
} else if (diff < 2) {
|
||||||
pdate = 'Yesterday';
|
pdate = __('Yesterday');
|
||||||
} else {
|
} else {
|
||||||
pdate = frappe.datetime.global_date_format(date);
|
pdate = __('on ') + frappe.datetime.global_date_format(date);
|
||||||
}
|
}
|
||||||
data.date_sep = pdate;
|
data.date_sep = pdate;
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
var show_patient_info = function(patient, me){
|
let show_patient_info = function(patient, me) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
"method": "erpnext.healthcare.doctype.patient.patient.get_patient_detail",
|
'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
|
||||||
args: {
|
args: {
|
||||||
patient: patient
|
patient: patient
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
var data = r.message;
|
let data = r.message;
|
||||||
var details = "";
|
let details = '';
|
||||||
if (data.image) {
|
if (data.image) {
|
||||||
details += "<div><img class='thumbnail' width=75% src='"+data.image+"'></div>";
|
details += `<div><img class='thumbnail' width=75% src='${data.image}'></div>`;
|
||||||
}
|
}
|
||||||
details += "<b>" + data.patient_name +"</b><br>" + data.sex;
|
|
||||||
if(data.email) details += "<br>" + data.email;
|
details += `<b> ${data.patient_name} </b><br> ${data.sex}`;
|
||||||
if(data.mobile) details += "<br>" + data.mobile;
|
if (data.email) details += `<br> ${data.email}`;
|
||||||
if(data.occupation) details += "<br><br><b>Occupation :</b> " + data.occupation;
|
if (data.mobile) details += `<br> ${data.mobile}`;
|
||||||
if(data.blood_group) details += "<br><b>Blood group : </b> " + data.blood_group;
|
if (data.occupation) details += `<br><br><b> ${__('Occupation')} : </b> ${data.occupation}`;
|
||||||
if(data.allergies) details += "<br><br><b>Allergies : </b> "+ data.allergies.replace("\n", "<br>");
|
if (data.blood_group) details += `<br><b> ${__('Blood Group')} : </b> ${data.blood_group}`;
|
||||||
if(data.medication) details += "<br><b>Medication : </b> "+ data.medication.replace("\n", "<br>");
|
if (data.allergies) details += `<br><br><b> ${__('Allerigies')} : </b> ${data.allergies.replace("\n", ", ")}`;
|
||||||
if(data.alcohol_current_use) details += "<br><br><b>Alcohol use : </b> "+ data.alcohol_current_use;
|
if (data.medication) details += `<br><b> ${__('Medication')} : </b> ${data.medication.replace("\n", ", ")}`;
|
||||||
if(data.alcohol_past_use) details += "<br><b>Alcohol past use : </b> "+ data.alcohol_past_use;
|
if (data.alcohol_current_use) details += `<br><br><b> ${__('Alcohol use')} : </b> ${data.alcohol_current_use}`;
|
||||||
if(data.tobacco_current_use) details += "<br><b>Tobacco use : </b> "+ data.tobacco_current_use;
|
if (data.alcohol_past_use) details += `<br><b> ${__('Alcohol past use')} : </b> ${data.alcohol_past_use}`;
|
||||||
if(data.tobacco_past_use) details += "<br><b>Tobacco past use : </b> "+ data.tobacco_past_use;
|
if (data.tobacco_current_use) details += `<br><b> ${__('Tobacco use')} : </b> ${data.tobacco_current_use}`;
|
||||||
if(data.medical_history) details += "<br><br><b>Medical history : </b> "+ data.medical_history.replace("\n", "<br>");
|
if (data.tobacco_past_use) details += `<br><b> ${__('Tobacco past use')} : </b> ${data.tobacco_past_use}`;
|
||||||
if(data.surgical_history) details += "<br><b>Surgical history : </b> "+ data.surgical_history.replace("\n", "<br>");
|
if (data.medical_history) details += `<br><br><b> ${__('Medical history')} : </b> ${data.medical_history.replace("\n", ", ")}`;
|
||||||
if(data.surrounding_factors) details += "<br><br><b>Occupational hazards : </b> "+ data.surrounding_factors.replace("\n", "<br>");
|
if (data.surgical_history) details += `<br><b> ${__('Surgical history')} : </b> ${data.surgical_history.replace("\n", ", ")}`;
|
||||||
if(data.other_risk_factors) details += "<br><b>Other risk factors : </b> " + data.other_risk_factors.replace("\n", "<br>");
|
if (data.surrounding_factors) details += `<br><br><b> ${__('Occupational hazards')} : </b> ${data.surrounding_factors.replace("\n", ", ")}`;
|
||||||
if(data.patient_details) details += "<br><br><b>More info : </b> " + data.patient_details.replace("\n", "<br>");
|
if (data.other_risk_factors) details += `<br><b> ${__('Other risk factors')} : </b> ${data.other_risk_factors.replace("\n", ", ")}`;
|
||||||
|
if (data.patient_details) details += `<br><br><b> ${__('More info')} : </b> ${data.patient_details.replace("\n", ", ")}`;
|
||||||
|
|
||||||
if (details) {
|
if (details) {
|
||||||
details = "<div style='padding-left:10px; font-size:13px;' align='center'>" + details + "</div>";
|
details = `<div style='padding-left:10px; font-size:13px;' align='left'>` + details + `</div>`;
|
||||||
}
|
}
|
||||||
me.page.main.find(".patient_details").html(details);
|
me.page.main.find('.patient_details').html(details);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
|
let show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.healthcare.utils.get_patient_vitals",
|
method: 'erpnext.healthcare.utils.get_patient_vitals',
|
||||||
args:{
|
args:{
|
||||||
patient: patient
|
patient: patient
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if (r.message) {
|
if (r.message) {
|
||||||
var show_chart_btns_html = "<div style='padding-top:5px;'><a class='btn btn-default btn-xs btn-show-chart' \
|
let show_chart_btns_html = `
|
||||||
data-show-chart-id='bp' data-pts='mmHg' data-title='Blood Pressure'>Blood Pressure</a>\
|
<div style='padding-top:10px;'>
|
||||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='pulse_rate' \
|
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bp' data-pts='mmHg' data-title='Blood Pressure'>
|
||||||
data-pts='per Minutes' data-title='Respiratory/Pulse Rate'>Respiratory/Pulse Rate</a>\
|
${__('Blood Pressure')}
|
||||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='temperature' \
|
</a>
|
||||||
data-pts='°C or °F' data-title='Temperature'>Temperature</a>\
|
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='pulse_rate' data-pts='per Minutes' data-title='Respiratory/Pulse Rate'>
|
||||||
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bmi' \
|
${__('Respiratory/Pulse Rate')}
|
||||||
data-pts='' data-title='BMI'>BMI</a></div>";
|
</a>
|
||||||
me.page.main.find(".show_chart_btns").html(show_chart_btns_html);
|
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='temperature' data-pts='°C or °F' data-title='Temperature'>
|
||||||
var data = r.message;
|
${__('Temperature')}
|
||||||
|
</a>
|
||||||
|
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bmi' data-pts='' data-title='BMI'>
|
||||||
|
${__('BMI')}
|
||||||
|
</a>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
me.page.main.find('.show_chart_btns').html(show_chart_btns_html);
|
||||||
|
let data = r.message;
|
||||||
let labels = [], datasets = [];
|
let labels = [], datasets = [];
|
||||||
let bp_systolic = [], bp_diastolic = [], temperature = [];
|
let bp_systolic = [], bp_diastolic = [], temperature = [];
|
||||||
let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = [];
|
let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = [];
|
||||||
for(var i=0; i<data.length; i++){
|
|
||||||
labels.push(data[i].signs_date+"||"+data[i].signs_time);
|
for (let i=0; i<data.length; i++) {
|
||||||
if(btn_show_id=="bp"){
|
labels.push(data[i].signs_date+'||'+data[i].signs_time);
|
||||||
|
|
||||||
|
if (btn_show_id === 'bp') {
|
||||||
bp_systolic.push(data[i].bp_systolic);
|
bp_systolic.push(data[i].bp_systolic);
|
||||||
bp_diastolic.push(data[i].bp_diastolic);
|
bp_diastolic.push(data[i].bp_diastolic);
|
||||||
}
|
}
|
||||||
if(btn_show_id=="temperature"){
|
if (btn_show_id === 'temperature') {
|
||||||
temperature.push(data[i].temperature);
|
temperature.push(data[i].temperature);
|
||||||
}
|
}
|
||||||
if(btn_show_id=="pulse_rate"){
|
if (btn_show_id === 'pulse_rate') {
|
||||||
pulse.push(data[i].pulse);
|
pulse.push(data[i].pulse);
|
||||||
respiratory_rate.push(data[i].respiratory_rate);
|
respiratory_rate.push(data[i].respiratory_rate);
|
||||||
}
|
}
|
||||||
if(btn_show_id=="bmi"){
|
if (btn_show_id === 'bmi') {
|
||||||
bmi.push(data[i].bmi);
|
bmi.push(data[i].bmi);
|
||||||
height.push(data[i].height);
|
height.push(data[i].height);
|
||||||
weight.push(data[i].weight);
|
weight.push(data[i].weight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(btn_show_id=="temperature"){
|
if (btn_show_id === 'temperature') {
|
||||||
datasets.push({name: "Temperature", values: temperature, chartType:'line'});
|
datasets.push({name: 'Temperature', values: temperature, chartType: 'line'});
|
||||||
}
|
}
|
||||||
if(btn_show_id=="bmi"){
|
if (btn_show_id === 'bmi') {
|
||||||
datasets.push({name: "BMI", values: bmi, chartType:'line'});
|
datasets.push({name: 'BMI', values: bmi, chartType: 'line'});
|
||||||
datasets.push({name: "Height", values: height, chartType:'line'});
|
datasets.push({name: 'Height', values: height, chartType: 'line'});
|
||||||
datasets.push({name: "Weight", values: weight, chartType:'line'});
|
datasets.push({name: 'Weight', values: weight, chartType: 'line'});
|
||||||
}
|
}
|
||||||
if(btn_show_id=="bp"){
|
if (btn_show_id === 'bp') {
|
||||||
datasets.push({name: "BP Systolic", values: bp_systolic, chartType:'line'});
|
datasets.push({name: 'BP Systolic', values: bp_systolic, chartType: 'line'});
|
||||||
datasets.push({name: "BP Diastolic", values: bp_diastolic, chartType:'line'});
|
datasets.push({name: 'BP Diastolic', values: bp_diastolic, chartType: 'line'});
|
||||||
}
|
}
|
||||||
if(btn_show_id=="pulse_rate"){
|
if (btn_show_id === 'pulse_rate') {
|
||||||
datasets.push({name: "Heart Rate / Pulse", values: pulse, chartType:'line'});
|
datasets.push({name: 'Heart Rate / Pulse', values: pulse, chartType: 'line'});
|
||||||
datasets.push({name: "Respiratory Rate", values: respiratory_rate, chartType:'line'});
|
datasets.push({name: 'Respiratory Rate', values: respiratory_rate, chartType: 'line'});
|
||||||
}
|
}
|
||||||
new frappe.Chart( ".patient_vital_charts", {
|
new frappe.Chart('.patient_vital_charts', {
|
||||||
data: {
|
data: {
|
||||||
labels: labels,
|
labels: labels,
|
||||||
datasets: datasets
|
datasets: datasets
|
||||||
},
|
},
|
||||||
|
|
||||||
title: title,
|
title: title,
|
||||||
type: 'axis-mixed', // 'axis-mixed', 'bar', 'line', 'pie', 'percentage'
|
type: 'axis-mixed',
|
||||||
height: 200,
|
height: 200,
|
||||||
colors: ['purple', '#ffa3ef', 'light-blue'],
|
colors: ['purple', '#ffa3ef', 'light-blue'],
|
||||||
|
|
||||||
@ -291,9 +392,11 @@ var show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
|
|||||||
formatTooltipY: d => d + ' ' + pts,
|
formatTooltipY: d => d + ' ' + pts,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
me.page.main.find('.header-separator').show();
|
||||||
} else {
|
} else {
|
||||||
me.page.main.find(".patient_vital_charts").html("");
|
me.page.main.find('.patient_vital_charts').html('');
|
||||||
me.page.main.find(".show_chart_btns").html("");
|
me.page.main.find('.show_chart_btns').html('');
|
||||||
|
me.page.main.find('.header-separator').hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,36 +4,70 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
|
import json
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint
|
||||||
from erpnext.healthcare.utils import render_docs_as_html
|
from erpnext.healthcare.utils import render_docs_as_html
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_feed(name, start=0, page_length=20):
|
def get_feed(name, document_types=None, date_range=None, start=0, page_length=20):
|
||||||
"""get feed"""
|
"""get feed"""
|
||||||
result = frappe.db.sql("""select name, owner, creation,
|
filters = get_filters(name, document_types, date_range)
|
||||||
reference_doctype, reference_name, subject
|
|
||||||
from `tabPatient Medical Record`
|
result = frappe.db.get_all('Patient Medical Record',
|
||||||
where patient=%(patient)s
|
fields=['name', 'owner', 'communication_date',
|
||||||
order by creation desc
|
'reference_doctype', 'reference_name', 'subject'],
|
||||||
limit %(start)s, %(page_length)s""",
|
filters=filters,
|
||||||
{
|
order_by='communication_date DESC',
|
||||||
"patient": name,
|
limit=cint(page_length),
|
||||||
"start": cint(start),
|
start=cint(start)
|
||||||
"page_length": cint(page_length)
|
)
|
||||||
}, as_dict=True)
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_filters(name, document_types=None, date_range=None):
|
||||||
|
filters = {'patient': name}
|
||||||
|
if document_types:
|
||||||
|
document_types = json.loads(document_types)
|
||||||
|
if len(document_types):
|
||||||
|
filters['reference_doctype'] = ['IN', document_types]
|
||||||
|
|
||||||
|
if date_range:
|
||||||
|
try:
|
||||||
|
date_range = json.loads(date_range)
|
||||||
|
if date_range:
|
||||||
|
filters['communication_date'] = ['between', [date_range[0], date_range[1]]]
|
||||||
|
except json.decoder.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return filters
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_feed_for_dt(doctype, docname):
|
def get_feed_for_dt(doctype, docname):
|
||||||
"""get feed"""
|
"""get feed"""
|
||||||
result = frappe.db.sql("""select name, owner, modified, creation,
|
result = frappe.db.get_all('Patient Medical Record',
|
||||||
reference_doctype, reference_name, subject
|
fields=['name', 'owner', 'communication_date',
|
||||||
from `tabPatient Medical Record`
|
'reference_doctype', 'reference_name', 'subject'],
|
||||||
where reference_name=%(docname)s and reference_doctype=%(doctype)s
|
filters={
|
||||||
order by creation desc""",
|
'reference_doctype': doctype,
|
||||||
{
|
'reference_name': docname
|
||||||
"docname": docname,
|
},
|
||||||
"doctype": doctype
|
order_by='communication_date DESC'
|
||||||
}, as_dict=True)
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_patient_history_doctypes():
|
||||||
|
document_types = []
|
||||||
|
settings = frappe.get_single("Patient History Settings")
|
||||||
|
|
||||||
|
for entry in settings.standard_doctypes:
|
||||||
|
document_types.append(entry.document_type)
|
||||||
|
|
||||||
|
for entry in settings.custom_doctypes:
|
||||||
|
document_types.append(entry.document_type)
|
||||||
|
|
||||||
|
return document_types
|
||||||
|
|||||||
@ -16,6 +16,7 @@ def setup_healthcare():
|
|||||||
create_healthcare_item_groups()
|
create_healthcare_item_groups()
|
||||||
create_sensitivity()
|
create_sensitivity()
|
||||||
add_healthcare_service_unit_tree_root()
|
add_healthcare_service_unit_tree_root()
|
||||||
|
setup_patient_history_settings()
|
||||||
|
|
||||||
def create_medical_departments():
|
def create_medical_departments():
|
||||||
departments = [
|
departments = [
|
||||||
@ -213,3 +214,82 @@ def get_company():
|
|||||||
if company:
|
if company:
|
||||||
return company[0].name
|
return company[0].name
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def setup_patient_history_settings():
|
||||||
|
import json
|
||||||
|
|
||||||
|
settings = frappe.get_single('Patient History Settings')
|
||||||
|
configuration = get_patient_history_config()
|
||||||
|
for dt, config in configuration.items():
|
||||||
|
settings.append("standard_doctypes", {
|
||||||
|
"document_type": dt,
|
||||||
|
"date_fieldname": config[0],
|
||||||
|
"selected_fields": json.dumps(config[1])
|
||||||
|
})
|
||||||
|
settings.save()
|
||||||
|
|
||||||
|
def get_patient_history_config():
|
||||||
|
return {
|
||||||
|
"Patient Encounter": ("encounter_date", [
|
||||||
|
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
|
||||||
|
{"label": "Symptoms", "fieldname": "symptoms", "fieldtype": "Table Multiselect"},
|
||||||
|
{"label": "Diagnosis", "fieldname": "diagnosis", "fieldtype": "Table Multiselect"},
|
||||||
|
{"label": "Drug Prescription", "fieldname": "drug_prescription", "fieldtype": "Table"},
|
||||||
|
{"label": "Lab Tests", "fieldname": "lab_test_prescription", "fieldtype": "Table"},
|
||||||
|
{"label": "Clinical Procedures", "fieldname": "procedure_prescription", "fieldtype": "Table"},
|
||||||
|
{"label": "Therapies", "fieldname": "therapies", "fieldtype": "Table"},
|
||||||
|
{"label": "Review Details", "fieldname": "encounter_comment", "fieldtype": "Small Text"}
|
||||||
|
]),
|
||||||
|
"Clinical Procedure": ("start_date", [
|
||||||
|
{"label": "Procedure Template", "fieldname": "procedure_template", "fieldtype": "Link"},
|
||||||
|
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
|
||||||
|
{"label": "Notes", "fieldname": "notes", "fieldtype": "Small Text"},
|
||||||
|
{"label": "Service Unit", "fieldname": "service_unit", "fieldtype": "Healthcare Service Unit"},
|
||||||
|
{"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"},
|
||||||
|
{"label": "Sample", "fieldname": "sample", "fieldtype": "Link"}
|
||||||
|
]),
|
||||||
|
"Lab Test": ("result_date", [
|
||||||
|
{"label": "Test Template", "fieldname": "template", "fieldtype": "Link"},
|
||||||
|
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
|
||||||
|
{"label": "Test Name", "fieldname": "lab_test_name", "fieldtype": "Data"},
|
||||||
|
{"label": "Lab Technician Name", "fieldname": "employee_name", "fieldtype": "Data"},
|
||||||
|
{"label": "Sample ID", "fieldname": "sample", "fieldtype": "Link"},
|
||||||
|
{"label": "Normal Test Result", "fieldname": "normal_test_items", "fieldtype": "Table"},
|
||||||
|
{"label": "Descriptive Test Result", "fieldname": "descriptive_test_items", "fieldtype": "Table"},
|
||||||
|
{"label": "Organism Test Result", "fieldname": "organism_test_items", "fieldtype": "Table"},
|
||||||
|
{"label": "Sensitivity Test Result", "fieldname": "sensitivity_test_items", "fieldtype": "Table"},
|
||||||
|
{"label": "Comments", "fieldname": "lab_test_comment", "fieldtype": "Table"}
|
||||||
|
]),
|
||||||
|
"Therapy Session": ("start_date", [
|
||||||
|
{"label": "Therapy Type", "fieldname": "therapy_type", "fieldtype": "Link"},
|
||||||
|
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
|
||||||
|
{"label": "Therapy Plan", "fieldname": "therapy_plan", "fieldtype": "Link"},
|
||||||
|
{"label": "Duration", "fieldname": "duration", "fieldtype": "Int"},
|
||||||
|
{"label": "Location", "fieldname": "location", "fieldtype": "Link"},
|
||||||
|
{"label": "Healthcare Service Unit", "fieldname": "service_unit", "fieldtype": "Link"},
|
||||||
|
{"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"},
|
||||||
|
{"label": "Exercises", "fieldname": "exercises", "fieldtype": "Table"},
|
||||||
|
{"label": "Total Counts Targeted", "fieldname": "total_counts_targeted", "fieldtype": "Int"},
|
||||||
|
{"label": "Total Counts Completed", "fieldname": "total_counts_completed", "fieldtype": "Int"}
|
||||||
|
]),
|
||||||
|
"Vital Signs": ("signs_date", [
|
||||||
|
{"label": "Body Temperature", "fieldname": "temperature", "fieldtype": "Data"},
|
||||||
|
{"label": "Heart Rate / Pulse", "fieldname": "pulse", "fieldtype": "Data"},
|
||||||
|
{"label": "Respiratory rate", "fieldname": "respiratory_rate", "fieldtype": "Data"},
|
||||||
|
{"label": "Tongue", "fieldname": "tongue", "fieldtype": "Select"},
|
||||||
|
{"label": "Abdomen", "fieldname": "abdomen", "fieldtype": "Select"},
|
||||||
|
{"label": "Reflexes", "fieldname": "reflexes", "fieldtype": "Select"},
|
||||||
|
{"label": "Blood Pressure", "fieldname": "bp", "fieldtype": "Data"},
|
||||||
|
{"label": "Notes", "fieldname": "vital_signs_note", "fieldtype": "Small Text"},
|
||||||
|
{"label": "Height (In Meter)", "fieldname": "height", "fieldtype": "Float"},
|
||||||
|
{"label": "Weight (In Kilogram)", "fieldname": "weight", "fieldtype": "Float"},
|
||||||
|
{"label": "BMI", "fieldname": "bmi", "fieldtype": "Float"}
|
||||||
|
]),
|
||||||
|
"Inpatient Medication Order": ("start_date", [
|
||||||
|
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
|
||||||
|
{"label": "Start Date", "fieldname": "start_date", "fieldtype": "Date"},
|
||||||
|
{"label": "End Date", "fieldname": "end_date", "fieldtype": "Date"},
|
||||||
|
{"label": "Medication Orders", "fieldname": "medication_orders", "fieldtype": "Table"},
|
||||||
|
{"label": "Total Orders", "fieldname": "total_orders", "fieldtype": "Float"}
|
||||||
|
])
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ from __future__ import unicode_literals
|
|||||||
import math
|
import math
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.utils.formatters import format_value
|
||||||
from frappe.utils import time_diff_in_hours, rounded
|
from frappe.utils import time_diff_in_hours, rounded
|
||||||
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account
|
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account
|
||||||
from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity
|
from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity
|
||||||
@ -648,11 +649,15 @@ def render_doc_as_html(doctype, docname, exclude_fields = []):
|
|||||||
html += "<table class='table table-condensed table-bordered'>" \
|
html += "<table class='table table-condensed table-bordered'>" \
|
||||||
+ table_head + table_row + "</table>"
|
+ table_head + table_row + "</table>"
|
||||||
continue
|
continue
|
||||||
|
|
||||||
#on other field types add label and value to html
|
#on other field types add label and value to html
|
||||||
if not df.hidden and not df.print_hide and doc.get(df.fieldname) and df.fieldname not in exclude_fields:
|
if not df.hidden and not df.print_hide and doc.get(df.fieldname) and df.fieldname not in exclude_fields:
|
||||||
html += '<br>{0} : {1}'.format(df.label or df.fieldname, \
|
if doc.get(df.fieldname):
|
||||||
doc.get(df.fieldname))
|
formatted_value = format_value(doc.get(df.fieldname), meta.get_field(df.fieldname), doc)
|
||||||
|
html += '<br>{0} : {1}'.format(df.label or df.fieldname, formatted_value)
|
||||||
|
|
||||||
if not has_data : has_data = True
|
if not has_data : has_data = True
|
||||||
|
|
||||||
if sec_on and col_on and has_data:
|
if sec_on and col_on and has_data:
|
||||||
doc_html += section_html + html + '</div></div>'
|
doc_html += section_html + html + '</div></div>'
|
||||||
elif sec_on and not col_on and has_data:
|
elif sec_on and not col_on and has_data:
|
||||||
|
|||||||
@ -221,6 +221,11 @@ standard_queries = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
doc_events = {
|
doc_events = {
|
||||||
|
"*": {
|
||||||
|
"on_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.create_medical_record",
|
||||||
|
"on_update_after_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record",
|
||||||
|
"on_cancel": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.delete_medical_record"
|
||||||
|
},
|
||||||
"Stock Entry": {
|
"Stock Entry": {
|
||||||
"on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty",
|
"on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty",
|
||||||
"on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty"
|
"on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty"
|
||||||
@ -341,7 +346,8 @@ scheduler_events = {
|
|||||||
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
|
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
|
||||||
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status",
|
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status",
|
||||||
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
|
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
|
||||||
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email"
|
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email",
|
||||||
|
"erpnext.non_profit.doctype.membership.membership.set_expired_status"
|
||||||
],
|
],
|
||||||
"daily_long": [
|
"daily_long": [
|
||||||
"erpnext.setup.doctype.email_digest.email_digest.send",
|
"erpnext.setup.doctype.email_digest.email_digest.send",
|
||||||
|
|||||||
@ -26,6 +26,7 @@ def get_columns(filters):
|
|||||||
{"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80},
|
{"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80},
|
||||||
{"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "width": 100},
|
{"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "width": 100},
|
||||||
{"label": _("Latest Price"), "fieldname": "latest_price", "fieldtype": "Currency", "options": "currency", "width": 100},
|
{"label": _("Latest Price"), "fieldname": "latest_price", "fieldtype": "Currency", "options": "currency", "width": 100},
|
||||||
|
{"label": _("Price Valid Upto"), "fieldname": "price_valid_upto", "fieldtype": "Datetime", "width": 100},
|
||||||
{"label": _("Current Value"), "fieldname": "current_value", "fieldtype": "Currency", "options": "currency", "width": 100},
|
{"label": _("Current Value"), "fieldname": "current_value", "fieldtype": "Currency", "options": "currency", "width": 100},
|
||||||
{"label": _("% Of Applicant Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "width": 100},
|
{"label": _("% Of Applicant Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "width": 100},
|
||||||
{"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
|
{"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
|
||||||
@ -43,13 +44,16 @@ def get_data(filters):
|
|||||||
|
|
||||||
for key, qty in iteritems(pledge_values):
|
for key, qty in iteritems(pledge_values):
|
||||||
row = {}
|
row = {}
|
||||||
current_value = flt(qty * loan_security_details.get(key[1])['latest_price'])
|
current_value = flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0))
|
||||||
|
valid_upto = loan_security_details.get(key[1], {}).get('valid_upto')
|
||||||
|
|
||||||
row.update(loan_security_details.get(key[1]))
|
row.update(loan_security_details.get(key[1]))
|
||||||
row.update({
|
row.update({
|
||||||
'applicant_type': applicant_type_map.get(key[0]),
|
'applicant_type': applicant_type_map.get(key[0]),
|
||||||
'applicant_name': key[0],
|
'applicant_name': key[0],
|
||||||
'total_qty': qty,
|
'total_qty': qty,
|
||||||
'current_value': current_value,
|
'current_value': current_value,
|
||||||
|
'price_valid_upto': valid_upto,
|
||||||
'portfolio_percent': flt(current_value * 100 / total_value_map.get(key[0]), 2),
|
'portfolio_percent': flt(current_value * 100 / total_value_map.get(key[0]), 2),
|
||||||
'currency': currency
|
'currency': currency
|
||||||
})
|
})
|
||||||
@ -60,20 +64,30 @@ def get_data(filters):
|
|||||||
|
|
||||||
def get_loan_security_details(filters):
|
def get_loan_security_details(filters):
|
||||||
security_detail_map = {}
|
security_detail_map = {}
|
||||||
|
loan_security_price_map = {}
|
||||||
|
lsp_validity_map = {}
|
||||||
|
|
||||||
loan_security_price_map = frappe._dict(frappe.db.sql("""
|
loan_security_prices = frappe.db.sql("""
|
||||||
SELECT loan_security, loan_security_price
|
SELECT loan_security, loan_security_price, valid_upto
|
||||||
FROM `tabLoan Security Price` t1
|
FROM `tabLoan Security Price` t1
|
||||||
WHERE valid_from >= (SELECT MAX(valid_from) FROM `tabLoan Security Price` t2
|
WHERE valid_from >= (SELECT MAX(valid_from) FROM `tabLoan Security Price` t2
|
||||||
WHERE t1.loan_security = t2.loan_security)
|
WHERE t1.loan_security = t2.loan_security)
|
||||||
""", as_list=1))
|
""", as_dict=1)
|
||||||
|
|
||||||
|
for security in loan_security_prices:
|
||||||
|
loan_security_price_map.setdefault(security.loan_security, security.loan_security_price)
|
||||||
|
lsp_validity_map.setdefault(security.loan_security, security.valid_upto)
|
||||||
|
|
||||||
loan_security_details = frappe.get_all('Loan Security', fields=['name as loan_security',
|
loan_security_details = frappe.get_all('Loan Security', fields=['name as loan_security',
|
||||||
'loan_security_code', 'loan_security_name', 'haircut', 'loan_security_type',
|
'loan_security_code', 'loan_security_name', 'haircut', 'loan_security_type',
|
||||||
'disabled'])
|
'disabled'])
|
||||||
|
|
||||||
for security in loan_security_details:
|
for security in loan_security_details:
|
||||||
security.update({'latest_price': flt(loan_security_price_map.get(security.loan_security))})
|
security.update({
|
||||||
|
'latest_price': flt(loan_security_price_map.get(security.loan_security)),
|
||||||
|
'valid_upto': lsp_validity_map.get(security.loan_security)
|
||||||
|
})
|
||||||
|
|
||||||
security_detail_map.setdefault(security.loan_security, security)
|
security_detail_map.setdefault(security.loan_security, security)
|
||||||
|
|
||||||
return security_detail_map
|
return security_detail_map
|
||||||
@ -118,6 +132,6 @@ def get_applicant_wise_total_loan_security_qty(filters, loan_security_details):
|
|||||||
applicant_wise_unpledges.get((security.applicant, security.loan_security), 0.0)
|
applicant_wise_unpledges.get((security.applicant, security.loan_security), 0.0)
|
||||||
|
|
||||||
total_value_map[security.applicant] += current_pledges.get((security.applicant, security.loan_security)) \
|
total_value_map[security.applicant] += current_pledges.get((security.applicant, security.loan_security)) \
|
||||||
* loan_security_details.get(security.loan_security)['latest_price']
|
* loan_security_details.get(security.loan_security, {}).get('latest_price', 0)
|
||||||
|
|
||||||
return current_pledges, total_value_map, applicant_type_map
|
return current_pledges, total_value_map, applicant_type_map
|
||||||
@ -6,6 +6,8 @@ import frappe
|
|||||||
import erpnext
|
import erpnext
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import flt, getdate, add_days
|
from frappe.utils import flt, getdate, add_days
|
||||||
|
from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applicant_wise_loan_security_exposure \
|
||||||
|
import get_loan_security_details
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
@ -31,6 +33,7 @@ def get_columns(filters):
|
|||||||
{"label": _("Undue Booked Interest"), "fieldname": "undue_interest", "fieldtype": "Currency", "options": "currency", "width": 120},
|
{"label": _("Undue Booked Interest"), "fieldname": "undue_interest", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||||
{"label": _("Interest %"), "fieldname": "rate_of_interest", "fieldtype": "Percent", "width": 100},
|
{"label": _("Interest %"), "fieldname": "rate_of_interest", "fieldtype": "Percent", "width": 100},
|
||||||
{"label": _("Penalty Interest %"), "fieldname": "penalty_interest", "fieldtype": "Percent", "width": 100},
|
{"label": _("Penalty Interest %"), "fieldname": "penalty_interest", "fieldtype": "Percent", "width": 100},
|
||||||
|
{"label": _("Loan To Value Ratio"), "fieldname": "loan_to_value", "fieldtype": "Percent", "width": 100},
|
||||||
{"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
|
{"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -50,6 +53,9 @@ def get_active_loan_details(filters):
|
|||||||
|
|
||||||
loan_list = [d.loan for d in loan_details]
|
loan_list = [d.loan for d in loan_details]
|
||||||
|
|
||||||
|
current_pledges = get_loan_wise_pledges(filters)
|
||||||
|
loan_wise_security_value = get_loan_wise_security_value(filters, current_pledges)
|
||||||
|
|
||||||
sanctioned_amount_map = get_sanctioned_amount_map()
|
sanctioned_amount_map = get_sanctioned_amount_map()
|
||||||
penal_interest_rate_map = get_penal_interest_rate_map()
|
penal_interest_rate_map = get_penal_interest_rate_map()
|
||||||
payments = get_payments(loan_list)
|
payments = get_payments(loan_list)
|
||||||
@ -67,12 +73,16 @@ def get_active_loan_details(filters):
|
|||||||
"penalty": flt(accrual_map.get(loan.loan, {}).get("penalty")),
|
"penalty": flt(accrual_map.get(loan.loan, {}).get("penalty")),
|
||||||
"penalty_interest": penal_interest_rate_map.get(loan.loan_type),
|
"penalty_interest": penal_interest_rate_map.get(loan.loan_type),
|
||||||
"undue_interest": flt(accrual_map.get(loan.loan, {}).get("undue_interest")),
|
"undue_interest": flt(accrual_map.get(loan.loan, {}).get("undue_interest")),
|
||||||
|
"loan_to_value": 0.0,
|
||||||
"currency": currency
|
"currency": currency
|
||||||
})
|
})
|
||||||
|
|
||||||
loan['total_outstanding'] = loan['principal_outstanding'] + loan['interest_outstanding'] \
|
loan['total_outstanding'] = loan['principal_outstanding'] + loan['interest_outstanding'] \
|
||||||
+ loan['penalty']
|
+ loan['penalty']
|
||||||
|
|
||||||
|
if loan_wise_security_value.get(loan.loan):
|
||||||
|
loan['loan_to_value'] = (loan['principal_outstanding'] * 100) / loan_wise_security_value.get(loan.loan)
|
||||||
|
|
||||||
return loan_details
|
return loan_details
|
||||||
|
|
||||||
def get_sanctioned_amount_map():
|
def get_sanctioned_amount_map():
|
||||||
@ -122,3 +132,52 @@ def get_interest_accruals(loans):
|
|||||||
|
|
||||||
def get_penal_interest_rate_map():
|
def get_penal_interest_rate_map():
|
||||||
return frappe._dict(frappe.get_all("Loan Type", fields=["name", "penalty_interest_rate"], as_list=1))
|
return frappe._dict(frappe.get_all("Loan Type", fields=["name", "penalty_interest_rate"], as_list=1))
|
||||||
|
|
||||||
|
def get_loan_wise_pledges(filters):
|
||||||
|
loan_wise_unpledges = {}
|
||||||
|
current_pledges = {}
|
||||||
|
|
||||||
|
conditions = ""
|
||||||
|
|
||||||
|
if filters.get('company'):
|
||||||
|
conditions = "AND company = %(company)s"
|
||||||
|
|
||||||
|
unpledges = frappe.db.sql("""
|
||||||
|
SELECT up.loan, u.loan_security, sum(u.qty) as qty
|
||||||
|
FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
|
||||||
|
WHERE u.parent = up.name
|
||||||
|
AND up.status = 'Approved'
|
||||||
|
{conditions}
|
||||||
|
GROUP BY up.loan
|
||||||
|
""".format(conditions=conditions), filters, as_dict=1)
|
||||||
|
|
||||||
|
for unpledge in unpledges:
|
||||||
|
loan_wise_unpledges.setdefault((unpledge.loan, unpledge.loan_security), unpledge.qty)
|
||||||
|
|
||||||
|
pledges = frappe.db.sql("""
|
||||||
|
SELECT lp.loan, p.loan_security, sum(p.qty) as qty
|
||||||
|
FROM `tabLoan Security Pledge` lp, `tabPledge`p
|
||||||
|
WHERE p.parent = lp.name
|
||||||
|
AND lp.status = 'Pledged'
|
||||||
|
{conditions}
|
||||||
|
GROUP BY lp.loan
|
||||||
|
""".format(conditions=conditions), filters, as_dict=1)
|
||||||
|
|
||||||
|
for security in pledges:
|
||||||
|
current_pledges.setdefault((security.loan, security.loan_security), security.qty)
|
||||||
|
current_pledges[(security.loan, security.loan_security)] -= \
|
||||||
|
loan_wise_unpledges.get((security.loan, security.loan_security), 0.0)
|
||||||
|
|
||||||
|
return current_pledges
|
||||||
|
|
||||||
|
def get_loan_wise_security_value(filters, current_pledges):
|
||||||
|
loan_security_details = get_loan_security_details(filters)
|
||||||
|
loan_wise_security_value = {}
|
||||||
|
|
||||||
|
for key in current_pledges:
|
||||||
|
qty = current_pledges.get(key)
|
||||||
|
loan_wise_security_value.setdefault(key[0], 0.0)
|
||||||
|
loan_wise_security_value[key[0]] += \
|
||||||
|
flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0))
|
||||||
|
|
||||||
|
return loan_wise_security_value
|
||||||
@ -24,6 +24,7 @@ def get_columns(filters):
|
|||||||
{"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80},
|
{"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80},
|
||||||
{"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "width": 100},
|
{"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "width": 100},
|
||||||
{"label": _("Latest Price"), "fieldname": "latest_price", "fieldtype": "Currency", "options": "currency", "width": 100},
|
{"label": _("Latest Price"), "fieldname": "latest_price", "fieldtype": "Currency", "options": "currency", "width": 100},
|
||||||
|
{"label": _("Price Valid Upto"), "fieldname": "price_valid_upto", "fieldtype": "Datetime", "width": 100},
|
||||||
{"label": _("Current Value"), "fieldname": "current_value", "fieldtype": "Currency", "options": "currency", "width": 100},
|
{"label": _("Current Value"), "fieldname": "current_value", "fieldtype": "Currency", "options": "currency", "width": 100},
|
||||||
{"label": _("% Of Total Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "width": 100},
|
{"label": _("% Of Total Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "width": 100},
|
||||||
{"label": _("Pledged Applicant Count"), "fieldname": "pledged_applicant_count", "fieldtype": "Percentage", "width": 100},
|
{"label": _("Pledged Applicant Count"), "fieldname": "pledged_applicant_count", "fieldtype": "Percentage", "width": 100},
|
||||||
@ -40,13 +41,16 @@ def get_data(filters):
|
|||||||
|
|
||||||
for security, value in iteritems(current_pledges):
|
for security, value in iteritems(current_pledges):
|
||||||
row = {}
|
row = {}
|
||||||
current_value = flt(value['qty'] * loan_security_details.get(security)['latest_price'])
|
current_value = flt(value.get('qty', 0) * loan_security_details.get(security, {}).get('latest_price', 0))
|
||||||
|
valid_upto = loan_security_details.get(security, {}).get('valid_upto')
|
||||||
|
|
||||||
row.update(loan_security_details.get(security))
|
row.update(loan_security_details.get(security))
|
||||||
row.update({
|
row.update({
|
||||||
'total_qty': value['qty'],
|
'total_qty': value.get('qty'),
|
||||||
'current_value': current_value,
|
'current_value': current_value,
|
||||||
|
'price_valid_upto': valid_upto,
|
||||||
'portfolio_percent': flt(current_value * 100 / total_portfolio_value, 2),
|
'portfolio_percent': flt(current_value * 100 / total_portfolio_value, 2),
|
||||||
'pledged_applicant_count': value['applicant_count'],
|
'pledged_applicant_count': value.get('applicant_count'),
|
||||||
'currency': currency
|
'currency': currency
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,6 @@
|
|||||||
"membership_expiry_date",
|
"membership_expiry_date",
|
||||||
"column_break_5",
|
"column_break_5",
|
||||||
"membership_type",
|
"membership_type",
|
||||||
"email",
|
|
||||||
"email_id",
|
"email_id",
|
||||||
"image",
|
"image",
|
||||||
"customer_section",
|
"customer_section",
|
||||||
@ -64,13 +63,6 @@
|
|||||||
"options": "Membership Type",
|
"options": "Membership Type",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "email",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "User",
|
|
||||||
"options": "User"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "image",
|
"fieldname": "image",
|
||||||
"fieldtype": "Attach Image",
|
"fieldtype": "Attach Image",
|
||||||
@ -178,7 +170,7 @@
|
|||||||
],
|
],
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-09-16 23:44:13.596948",
|
"modified": "2020-11-09 12:12:10.174647",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Non Profit",
|
"module": "Non Profit",
|
||||||
"name": "Member",
|
"name": "Member",
|
||||||
|
|||||||
@ -18,8 +18,6 @@ class Member(Document):
|
|||||||
|
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if self.email:
|
|
||||||
self.validate_email_type(self.email)
|
|
||||||
if self.email_id:
|
if self.email_id:
|
||||||
self.validate_email_type(self.email_id)
|
self.validate_email_type(self.email_id)
|
||||||
|
|
||||||
@ -57,14 +55,16 @@ class Member(Document):
|
|||||||
def make_customer_and_link(self):
|
def make_customer_and_link(self):
|
||||||
if self.customer:
|
if self.customer:
|
||||||
frappe.msgprint(_("A customer is already linked to this Member"))
|
frappe.msgprint(_("A customer is already linked to this Member"))
|
||||||
cust = create_customer(frappe._dict({
|
|
||||||
|
customer = create_customer(frappe._dict({
|
||||||
'fullname': self.member_name,
|
'fullname': self.member_name,
|
||||||
'email': self.email_id or self.email,
|
'email': self.email_id,
|
||||||
'phone': None
|
'phone': None
|
||||||
}))
|
}))
|
||||||
|
|
||||||
self.customer = cust
|
self.customer = customer
|
||||||
self.save()
|
self.save()
|
||||||
|
frappe.msgprint(_("Customer {0} has been created succesfully.").format(self.customer))
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_member(user_details):
|
def get_or_create_member(user_details):
|
||||||
|
|||||||
@ -4,16 +4,25 @@
|
|||||||
frappe.ui.form.on('Membership', {
|
frappe.ui.form.on('Membership', {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => {
|
frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => {
|
||||||
if (val) frm.set_df_property('razorpay_details_section', 'hidden', false);
|
if (val) frm.set_df_property("razorpay_details_section", "hidden", false);
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
|
if (frm.doc.__islocal)
|
||||||
|
return;
|
||||||
|
|
||||||
!frm.doc.invoice && frm.add_custom_button("Generate Invoice", () => {
|
!frm.doc.invoice && frm.add_custom_button("Generate Invoice", () => {
|
||||||
frm.call("generate_invoice", {
|
frm.call({
|
||||||
save: true
|
doc: frm.doc,
|
||||||
}).then(() => {
|
method: "generate_invoice",
|
||||||
|
args: {save: true},
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __("Creating Membership Invoice"),
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.invoice)
|
||||||
frm.reload_doc();
|
frm.reload_doc();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -27,6 +36,6 @@ frappe.ui.form.on('Membership', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
frm.add_fetch('membership_type', 'amount', 'amount');
|
frm.add_fetch("membership_type", "amount", "amount");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"member",
|
"member",
|
||||||
|
"member_name",
|
||||||
"membership_type",
|
"membership_type",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"membership_status",
|
"membership_status",
|
||||||
@ -46,6 +47,8 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "membership_status",
|
"fieldname": "membership_status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
"label": "Membership Status",
|
"label": "Membership Status",
|
||||||
"options": "New\nCurrent\nExpired\nPending\nCancelled"
|
"options": "New\nCurrent\nExpired\nPending\nCancelled"
|
||||||
},
|
},
|
||||||
@ -122,11 +125,18 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Invoice",
|
"label": "Invoice",
|
||||||
"options": "Sales Invoice"
|
"options": "Sales Invoice"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "member.member_name",
|
||||||
|
"fieldname": "member_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Member Name",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-09-19 14:28:11.532696",
|
"modified": "2021-01-21 16:31:20.032656",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Non Profit",
|
"module": "Non Profit",
|
||||||
"name": "Membership",
|
"name": "Membership",
|
||||||
@ -158,7 +168,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"restrict_to_domain": "Non Profit",
|
"restrict_to_domain": "Non Profit",
|
||||||
|
"search_fields": "member, member_name",
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"title_field": "member_name",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@ -14,17 +14,26 @@ from erpnext.non_profit.doctype.member.member import create_member
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
import erpnext
|
import erpnext
|
||||||
|
|
||||||
|
|
||||||
class Membership(Document):
|
class Membership(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if not self.member or not frappe.db.exists("Member", self.member):
|
if not self.member or not frappe.db.exists("Member", self.member):
|
||||||
member_name = frappe.get_value('Member', dict(email=frappe.session.user))
|
# for web forms
|
||||||
|
user_type = frappe.db.get_value("User", frappe.session.user, "user_type")
|
||||||
|
if user_type == "Website User":
|
||||||
|
self.create_member_from_website_user()
|
||||||
|
else:
|
||||||
|
frappe.throw(_("Please select a Member"))
|
||||||
|
|
||||||
|
self.validate_membership_period()
|
||||||
|
|
||||||
|
def create_member_from_website_user(self):
|
||||||
|
member_name = frappe.get_value("Member", dict(email_id=frappe.session.user))
|
||||||
|
|
||||||
if not member_name:
|
if not member_name:
|
||||||
user = frappe.get_doc('User', frappe.session.user)
|
user = frappe.get_doc("User", frappe.session.user)
|
||||||
member = frappe.get_doc(dict(
|
member = frappe.get_doc(dict(
|
||||||
doctype='Member',
|
doctype="Member",
|
||||||
email=frappe.session.user,
|
email_id=frappe.session.user,
|
||||||
membership_type=self.membership_type,
|
membership_type=self.membership_type,
|
||||||
member_name=user.get_fullname()
|
member_name=user.get_fullname()
|
||||||
)).insert(ignore_permissions=True)
|
)).insert(ignore_permissions=True)
|
||||||
@ -33,14 +42,15 @@ class Membership(Document):
|
|||||||
if self.get("__islocal"):
|
if self.get("__islocal"):
|
||||||
self.member = member_name
|
self.member = member_name
|
||||||
|
|
||||||
|
def validate_membership_period(self):
|
||||||
# get last membership (if active)
|
# get last membership (if active)
|
||||||
last_membership = erpnext.get_last_membership()
|
last_membership = erpnext.get_last_membership(self.member)
|
||||||
|
|
||||||
# if person applied for offline membership
|
# if person applied for offline membership
|
||||||
if last_membership and not frappe.session.user == "Administrator":
|
if last_membership and not frappe.session.user == "Administrator":
|
||||||
# if last membership does not expire in 30 days, then do not allow to renew
|
# if last membership does not expire in 30 days, then do not allow to renew
|
||||||
if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) :
|
if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) :
|
||||||
frappe.throw(_('You can only renew if your membership expires within 30 days'))
|
frappe.throw(_("You can only renew if your membership expires within 30 days"))
|
||||||
|
|
||||||
self.from_date = add_days(last_membership.to_date, 1)
|
self.from_date = add_days(last_membership.to_date, 1)
|
||||||
elif frappe.session.user == "Administrator":
|
elif frappe.session.user == "Administrator":
|
||||||
@ -54,11 +64,16 @@ class Membership(Document):
|
|||||||
self.to_date = add_months(self.from_date, 1)
|
self.to_date = add_months(self.from_date, 1)
|
||||||
|
|
||||||
def on_payment_authorized(self, status_changed_to=None):
|
def on_payment_authorized(self, status_changed_to=None):
|
||||||
if status_changed_to in ("Completed", "Authorized"):
|
if status_changed_to not in ("Completed", "Authorized"):
|
||||||
|
return
|
||||||
self.load_from_db()
|
self.load_from_db()
|
||||||
self.db_set('paid', 1)
|
self.db_set("paid", 1)
|
||||||
|
settings = frappe.get_doc("Membership Settings")
|
||||||
|
if settings.enable_invoicing and settings.create_for_web_forms:
|
||||||
|
self.generate_invoice(with_payment_entry=settings.make_payment_entry, save=True)
|
||||||
|
|
||||||
def generate_invoice(self, save=True):
|
|
||||||
|
def generate_invoice(self, save=True, with_payment_entry=False):
|
||||||
if not (self.paid or self.currency or self.amount):
|
if not (self.paid or self.currency or self.amount):
|
||||||
frappe.throw(_("The payment for this membership is not paid. To generate invoice fill the payment details"))
|
frappe.throw(_("The payment for this membership is not paid. To generate invoice fill the payment details"))
|
||||||
|
|
||||||
@ -66,34 +81,64 @@ class Membership(Document):
|
|||||||
frappe.throw(_("An invoice is already linked to this document"))
|
frappe.throw(_("An invoice is already linked to this document"))
|
||||||
|
|
||||||
member = frappe.get_doc("Member", self.member)
|
member = frappe.get_doc("Member", self.member)
|
||||||
plan = frappe.get_doc("Membership Type", self.membership_type)
|
|
||||||
settings = frappe.get_doc("Membership Settings")
|
|
||||||
|
|
||||||
if not member.customer:
|
if not member.customer:
|
||||||
frappe.throw(_("No customer linked to member {0}").format(frappe.bold(self.member)))
|
frappe.throw(_("No customer linked to member {0}").format(frappe.bold(self.member)))
|
||||||
|
|
||||||
if not settings.debit_account:
|
plan = frappe.get_doc("Membership Type", self.membership_type)
|
||||||
frappe.throw(_("You need to set <b>Debit Account</b> in Membership Settings"))
|
settings = frappe.get_doc("Membership Settings")
|
||||||
|
self.validate_membership_type_and_settings(plan, settings)
|
||||||
if not settings.company:
|
|
||||||
frappe.throw(_("You need to set <b>Default Company</b> for invoicing in Membership Settings"))
|
|
||||||
|
|
||||||
invoice = make_invoice(self, member, plan, settings)
|
invoice = make_invoice(self, member, plan, settings)
|
||||||
self.invoice = invoice.name
|
self.invoice = invoice.name
|
||||||
|
|
||||||
|
if with_payment_entry:
|
||||||
|
self.make_payment_entry(settings, invoice)
|
||||||
|
|
||||||
if save:
|
if save:
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
return invoice
|
return invoice
|
||||||
|
|
||||||
|
def validate_membership_type_and_settings(self, plan, settings):
|
||||||
|
settings_link = get_link_to_form("Membership Type", self.membership_type)
|
||||||
|
|
||||||
|
if not settings.debit_account:
|
||||||
|
frappe.throw(_("You need to set <b>Debit Account</b> in {0}").format(settings_link))
|
||||||
|
|
||||||
|
if not settings.company:
|
||||||
|
frappe.throw(_("You need to set <b>Default Company</b> for invoicing in {0}").format(settings_link))
|
||||||
|
|
||||||
|
if not plan.linked_item:
|
||||||
|
frappe.throw(_("Please set a Linked Item for the Membership Type {0}").format(
|
||||||
|
get_link_to_form("Membership Type", self.membership_type)))
|
||||||
|
|
||||||
|
def make_payment_entry(self, settings, invoice):
|
||||||
|
if not settings.payment_account:
|
||||||
|
frappe.throw(_("You need to set <b>Payment Account</b> in {0}").format(
|
||||||
|
get_link_to_form("Membership Type", self.membership_type)))
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
|
frappe.flags.ignore_account_permission = True
|
||||||
|
pe = get_payment_entry(dt="Sales Invoice", dn=invoice.name, bank_amount=invoice.grand_total)
|
||||||
|
frappe.flags.ignore_account_permission=False
|
||||||
|
pe.paid_to = settings.payment_account
|
||||||
|
pe.reference_no = self.name
|
||||||
|
pe.reference_date = getdate()
|
||||||
|
pe.save(ignore_permissions=True)
|
||||||
|
pe.submit()
|
||||||
|
|
||||||
def send_acknowlement(self):
|
def send_acknowlement(self):
|
||||||
settings = frappe.get_doc("Membership Settings")
|
settings = frappe.get_doc("Membership Settings")
|
||||||
if not settings.send_email:
|
if not settings.send_email:
|
||||||
frappe.throw(_("You need to enable <b>Send Acknowledge Email</b> in Membership Settings"))
|
frappe.throw(_("You need to enable <b>Send Acknowledge Email</b> in {0}").format(
|
||||||
|
get_link_to_form("Membership Settings", "Membership Settings")))
|
||||||
|
|
||||||
member = frappe.get_doc("Member", self.member)
|
member = frappe.get_doc("Member", self.member)
|
||||||
|
if not member.email_id:
|
||||||
|
frappe.throw(_("Email address of member {0} is missing").format(frappe.utils.get_link_to_form("Member", self.member)))
|
||||||
|
|
||||||
plan = frappe.get_doc("Membership Type", self.membership_type)
|
plan = frappe.get_doc("Membership Type", self.membership_type)
|
||||||
email = member.email_id if member.email_id else member.email
|
email = member.email_id
|
||||||
attachments = [frappe.attach_print("Membership", self.name, print_format=settings.membership_print_format)]
|
attachments = [frappe.attach_print("Membership", self.name, print_format=settings.membership_print_format)]
|
||||||
|
|
||||||
if self.invoice and settings.send_invoice:
|
if self.invoice and settings.send_invoice:
|
||||||
@ -112,48 +157,56 @@ class Membership(Document):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if not frappe.flags.in_test:
|
if not frappe.flags.in_test:
|
||||||
frappe.enqueue(method=frappe.sendmail, queue='short', timeout=300, is_async=True, **email_args)
|
frappe.enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args)
|
||||||
else:
|
else:
|
||||||
frappe.sendmail(**email_args)
|
frappe.sendmail(**email_args)
|
||||||
|
|
||||||
def generate_and_send_invoice(self):
|
def generate_and_send_invoice(self):
|
||||||
invoice = self.generate_invoice(False)
|
self.generate_invoice(save=False)
|
||||||
self.send_acknowlement()
|
self.send_acknowlement()
|
||||||
|
|
||||||
|
|
||||||
def make_invoice(membership, member, plan, settings):
|
def make_invoice(membership, member, plan, settings):
|
||||||
invoice = frappe.get_doc({
|
invoice = frappe.get_doc({
|
||||||
'doctype': 'Sales Invoice',
|
"doctype": "Sales Invoice",
|
||||||
'customer': member.customer,
|
"customer": member.customer,
|
||||||
'debit_to': settings.debit_account,
|
"debit_to": settings.debit_account,
|
||||||
'currency': membership.currency,
|
"currency": membership.currency,
|
||||||
'is_pos': 0,
|
"company": settings.company,
|
||||||
'items': [
|
"is_pos": 0,
|
||||||
|
"items": [
|
||||||
{
|
{
|
||||||
'item_code': plan.linked_item,
|
"item_code": plan.linked_item,
|
||||||
'rate': membership.amount,
|
"rate": membership.amount,
|
||||||
'qty': 1
|
"qty": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
invoice.set_missing_values()
|
||||||
invoice.insert(ignore_permissions=True)
|
invoice.insert(ignore_permissions=True)
|
||||||
invoice.submit()
|
invoice.submit()
|
||||||
|
|
||||||
|
frappe.msgprint(_("Sales Invoice created successfully"))
|
||||||
|
|
||||||
return invoice
|
return invoice
|
||||||
|
|
||||||
|
|
||||||
def get_member_based_on_subscription(subscription_id, email):
|
def get_member_based_on_subscription(subscription_id, email):
|
||||||
members = frappe.get_all("Member", filters={
|
members = frappe.get_all("Member", filters={
|
||||||
'subscription_id': subscription_id,
|
"subscription_id": subscription_id,
|
||||||
'email_id': email
|
"email_id": email
|
||||||
}, order_by="creation desc")
|
}, order_by="creation desc")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return frappe.get_doc("Member", members[0]['name'])
|
return frappe.get_doc("Member", members[0]["name"])
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def verify_signature(data):
|
def verify_signature(data):
|
||||||
signature = frappe.request.headers.get('X-Razorpay-Signature')
|
if frappe.flags.in_test:
|
||||||
|
return True
|
||||||
|
signature = frappe.request.headers.get("X-Razorpay-Signature")
|
||||||
|
|
||||||
settings = frappe.get_doc("Membership Settings")
|
settings = frappe.get_doc("Membership Settings")
|
||||||
key = settings.get_webhook_secret()
|
key = settings.get_webhook_secret()
|
||||||
@ -162,6 +215,7 @@ def verify_signature(data):
|
|||||||
|
|
||||||
controller.verify_signature(data, signature, key)
|
controller.verify_signature(data, signature, key)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
def trigger_razorpay_subscription(*args, **kwargs):
|
def trigger_razorpay_subscription(*args, **kwargs):
|
||||||
data = frappe.request.get_data(as_text=True)
|
data = frappe.request.get_data(as_text=True)
|
||||||
@ -170,16 +224,16 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
log = frappe.log_error(e, "Webhook Verification Error")
|
log = frappe.log_error(e, "Webhook Verification Error")
|
||||||
notify_failure(log)
|
notify_failure(log)
|
||||||
return { 'status': 'Failed', 'reason': e}
|
return { "status": "Failed", "reason": e}
|
||||||
|
|
||||||
if isinstance(data, six.string_types):
|
if isinstance(data, six.string_types):
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
data = frappe._dict(data)
|
data = frappe._dict(data)
|
||||||
|
|
||||||
subscription = data.payload.get("subscription", {}).get('entity', {})
|
subscription = data.payload.get("subscription", {}).get("entity", {})
|
||||||
subscription = frappe._dict(subscription)
|
subscription = frappe._dict(subscription)
|
||||||
|
|
||||||
payment = data.payload.get("payment", {}).get('entity', {})
|
payment = data.payload.get("payment", {}).get("entity", {})
|
||||||
payment = frappe._dict(payment)
|
payment = frappe._dict(payment)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -189,15 +243,15 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
|||||||
member = get_member_based_on_subscription(subscription.id, payment.email)
|
member = get_member_based_on_subscription(subscription.id, payment.email)
|
||||||
if not member:
|
if not member:
|
||||||
member = create_member(frappe._dict({
|
member = create_member(frappe._dict({
|
||||||
'fullname': payment.email,
|
"fullname": payment.email,
|
||||||
'email': payment.email,
|
"email": payment.email,
|
||||||
'plan_id': get_plan_from_razorpay_id(subscription.plan_id)
|
"plan_id": get_plan_from_razorpay_id(subscription.plan_id)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
member.subscription_id = subscription.id
|
member.subscription_id = subscription.id
|
||||||
member.customer_id = payment.customer_id
|
member.customer_id = payment.customer_id
|
||||||
if subscription.notes and type(subscription.notes) == dict:
|
if subscription.notes and type(subscription.notes) == dict:
|
||||||
notes = '\n'.join("{}: {}".format(k, v) for k, v in subscription.notes.items())
|
notes = "\n".join("{}: {}".format(k, v) for k, v in subscription.notes.items())
|
||||||
member.add_comment("Comment", notes)
|
member.add_comment("Comment", notes)
|
||||||
elif subscription.notes and type(subscription.notes) == str:
|
elif subscription.notes and type(subscription.notes) == str:
|
||||||
member.add_comment("Comment", subscription.notes)
|
member.add_comment("Comment", subscription.notes)
|
||||||
@ -227,28 +281,39 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
|||||||
message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id)
|
message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id)
|
||||||
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
|
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
|
||||||
notify_failure(log)
|
notify_failure(log)
|
||||||
return { 'status': 'Failed', 'reason': e}
|
return { "status": "Failed", "reason": e}
|
||||||
|
|
||||||
return { 'status': 'Success' }
|
return { "status": "Success" }
|
||||||
|
|
||||||
|
|
||||||
def notify_failure(log):
|
def notify_failure(log):
|
||||||
try:
|
try:
|
||||||
content = """Dear System Manager,
|
content = """
|
||||||
Razorpay webhook for creating renewing membership subscription failed due to some reason. Please check the following error log linked below
|
Dear System Manager,
|
||||||
|
Razorpay webhook for creating renewing membership subscription failed due to some reason.
|
||||||
|
Please check the following error log linked below
|
||||||
Error Log: {0}
|
Error Log: {0}
|
||||||
|
Regards, Administrator
|
||||||
|
""".format(get_link_to_form("Error Log", log.name))
|
||||||
|
|
||||||
Regards,
|
|
||||||
Administrator""".format(get_link_to_form("Error Log", log.name))
|
|
||||||
sendmail_to_system_managers("[Important] [ERPNext] Razorpay membership webhook failed , please check.", content)
|
sendmail_to_system_managers("[Important] [ERPNext] Razorpay membership webhook failed , please check.", content)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_plan_from_razorpay_id(plan_id):
|
def get_plan_from_razorpay_id(plan_id):
|
||||||
plan = frappe.get_all("Membership Type", filters={'razorpay_plan_id': plan_id}, order_by="creation desc")
|
plan = frappe.get_all("Membership Type", filters={"razorpay_plan_id": plan_id}, order_by="creation desc")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return plan[0]['name']
|
return plan[0]["name"]
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def set_expired_status():
|
||||||
|
frappe.db.sql("""
|
||||||
|
UPDATE
|
||||||
|
`tabMembership` SET `status` = 'Expired'
|
||||||
|
WHERE
|
||||||
|
`status` not in ('Cancelled') AND `to_date` < %s
|
||||||
|
""", (nowdate()))
|
||||||
15
erpnext/non_profit/doctype/membership/membership_list.js
Normal file
15
erpnext/non_profit/doctype/membership/membership_list.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
frappe.listview_settings['Membership'] = {
|
||||||
|
get_indicator: function(doc) {
|
||||||
|
if (doc.membership_status == 'New') {
|
||||||
|
return [__('New'), 'blue', 'membership_status,=,New'];
|
||||||
|
} else if (doc.membership_status === 'Current') {
|
||||||
|
return [__('Current'), 'green', 'membership_status,=,Current'];
|
||||||
|
} else if (doc.membership_status === 'Pending') {
|
||||||
|
return [__('Pending'), 'yellow', 'membership_status,=,Pending'];
|
||||||
|
} else if (doc.membership_status === 'Expired') {
|
||||||
|
return [__('Expired'), 'grey', 'membership_status,=,Expired'];
|
||||||
|
} else {
|
||||||
|
return [__('Cancelled'), 'red', 'membership_status,=,Cancelled'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -2,8 +2,110 @@
|
|||||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
import frappe
|
||||||
|
import erpnext
|
||||||
|
from erpnext.non_profit.doctype.member.member import create_member
|
||||||
|
from frappe.utils import nowdate, add_months
|
||||||
|
|
||||||
class TestMembership(unittest.TestCase):
|
class TestMembership(unittest.TestCase):
|
||||||
pass
|
def setUp(self):
|
||||||
|
# Get default company
|
||||||
|
company = frappe.get_doc("Company", erpnext.get_default_company())
|
||||||
|
|
||||||
|
# update membership settings
|
||||||
|
settings = frappe.get_doc("Membership Settings")
|
||||||
|
# Enable razorpay
|
||||||
|
settings.enable_razorpay = 1
|
||||||
|
settings.billing_cycle = "Monthly"
|
||||||
|
settings.billing_frequency = 24
|
||||||
|
# Enable invoicing
|
||||||
|
settings.enable_invoicing = 1
|
||||||
|
settings.make_payment_entry = 1
|
||||||
|
settings.company = company.name
|
||||||
|
settings.payment_account = company.default_cash_account
|
||||||
|
settings.debit_account = company.default_receivable_account
|
||||||
|
settings.save()
|
||||||
|
|
||||||
|
# make test plan
|
||||||
|
if not frappe.db.exists("Membership Type", "_rzpy_test_milythm"):
|
||||||
|
plan = frappe.new_doc("Membership Type")
|
||||||
|
plan.membership_type = "_rzpy_test_milythm"
|
||||||
|
plan.amount = 100
|
||||||
|
plan.razorpay_plan_id = "_rzpy_test_milythm"
|
||||||
|
plan.linked_item = create_item("_Test Item for Non Profit Membership").name
|
||||||
|
plan.insert()
|
||||||
|
else:
|
||||||
|
plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm")
|
||||||
|
|
||||||
|
# make test member
|
||||||
|
self.member_doc = create_member(frappe._dict({
|
||||||
|
'fullname': "_Test_Member",
|
||||||
|
'email': "_test_member_erpnext@example.com",
|
||||||
|
'plan_id': plan.name
|
||||||
|
}))
|
||||||
|
self.member_doc.make_customer_and_link()
|
||||||
|
self.member = self.member_doc.name
|
||||||
|
|
||||||
|
def test_auto_generate_invoice_and_payment_entry(self):
|
||||||
|
entry = make_membership(self.member)
|
||||||
|
|
||||||
|
# Naive test to see if at all invoice was generated and attached to member
|
||||||
|
# In any case if details were missing, the invoicing would throw an error
|
||||||
|
invoice = entry.generate_invoice(save=True)
|
||||||
|
self.assertEqual(invoice.name, entry.invoice)
|
||||||
|
|
||||||
|
def test_renew_within_30_days(self):
|
||||||
|
# create a membership for two months
|
||||||
|
# Should work fine
|
||||||
|
make_membership(self.member, { "from_date": nowdate() })
|
||||||
|
make_membership(self.member, { "from_date": add_months(nowdate(), 1) })
|
||||||
|
|
||||||
|
from frappe.utils.user import add_role
|
||||||
|
add_role("test@example.com", "Non Profit Manager")
|
||||||
|
frappe.set_user("test@example.com")
|
||||||
|
|
||||||
|
# create next membership with expiry not within 30 days
|
||||||
|
self.assertRaises(frappe.ValidationError, make_membership, self.member, {
|
||||||
|
"from_date": add_months(nowdate(), 2),
|
||||||
|
})
|
||||||
|
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
# create the same membership but as administrator
|
||||||
|
make_membership(self.member, {
|
||||||
|
"from_date": add_months(nowdate(), 2),
|
||||||
|
"to_date": add_months(nowdate(), 3),
|
||||||
|
})
|
||||||
|
|
||||||
|
def set_config(key, value):
|
||||||
|
frappe.db.set_value("Membership Settings", None, key, value)
|
||||||
|
|
||||||
|
def make_membership(member, payload={}):
|
||||||
|
data = {
|
||||||
|
"doctype": "Membership",
|
||||||
|
"member": member,
|
||||||
|
"membership_status": "Current",
|
||||||
|
"membership_type": "_rzpy_test_milythm",
|
||||||
|
"currency": "INR",
|
||||||
|
"paid": 1,
|
||||||
|
"from_date": nowdate(),
|
||||||
|
"amount": 100
|
||||||
|
}
|
||||||
|
data.update(payload)
|
||||||
|
membership = frappe.get_doc(data)
|
||||||
|
membership.insert(ignore_permissions=True, ignore_if_duplicate=True)
|
||||||
|
return membership
|
||||||
|
|
||||||
|
def create_item(item_code):
|
||||||
|
if not frappe.db.exists("Item", item_code):
|
||||||
|
item = frappe.new_doc("Item")
|
||||||
|
item.item_code = item_code
|
||||||
|
item.item_name = item_code
|
||||||
|
item.stock_uom = "Nos"
|
||||||
|
item.description = item_code
|
||||||
|
item.item_group = "All Item Groups"
|
||||||
|
item.is_stock_item = 0
|
||||||
|
item.save()
|
||||||
|
else:
|
||||||
|
item = frappe.get_doc("Item", item_code)
|
||||||
|
return item
|
||||||
|
|||||||
@ -11,7 +11,7 @@ frappe.ui.form.on("Membership Settings", {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.set_query('inv_print_format', function(doc) {
|
frm.set_query("inv_print_format", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
"doc_type": "Sales Invoice"
|
"doc_type": "Sales Invoice"
|
||||||
@ -19,7 +19,7 @@ frappe.ui.form.on("Membership Settings", {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query('membership_print_format', function(doc) {
|
frm.set_query("membership_print_format", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
"doc_type": "Membership"
|
"doc_type": "Membership"
|
||||||
@ -27,12 +27,23 @@ frappe.ui.form.on("Membership Settings", {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query('debit_account', function(doc) {
|
frm.set_query("debit_account", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
'account_type': 'Receivable',
|
"account_type": "Receivable",
|
||||||
'is_group': 0,
|
"is_group": 0,
|
||||||
'company': frm.doc.company
|
"company": frm.doc.company
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("payment_account", function () {
|
||||||
|
var account_types = ["Bank", "Cash"];
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"account_type": ["in", account_types],
|
||||||
|
"is_group": 0,
|
||||||
|
"company": frm.doc.company
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -11,9 +11,12 @@
|
|||||||
"billing_frequency",
|
"billing_frequency",
|
||||||
"webhook_secret",
|
"webhook_secret",
|
||||||
"column_break_6",
|
"column_break_6",
|
||||||
"enable_auto_invoicing",
|
"enable_invoicing",
|
||||||
|
"create_for_web_forms",
|
||||||
|
"make_payment_entry",
|
||||||
"company",
|
"company",
|
||||||
"debit_account",
|
"debit_account",
|
||||||
|
"payment_account",
|
||||||
"column_break_9",
|
"column_break_9",
|
||||||
"send_email",
|
"send_email",
|
||||||
"send_invoice",
|
"send_invoice",
|
||||||
@ -58,14 +61,7 @@
|
|||||||
"label": "Invoicing"
|
"label": "Invoicing"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"depends_on": "eval:doc.enable_invoicing",
|
||||||
"fieldname": "enable_auto_invoicing",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Enable Auto Invoicing",
|
|
||||||
"mandatory_depends_on": "eval:doc.send_invoice"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "eval:doc.enable_auto_invoicing",
|
|
||||||
"fieldname": "debit_account",
|
"fieldname": "debit_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Debit Account",
|
"label": "Debit Account",
|
||||||
@ -77,7 +73,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.enable_auto_invoicing",
|
"depends_on": "eval:doc.enable_invoicing",
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
@ -86,7 +82,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:doc.enable_auto_invoicing && doc.send_email",
|
"depends_on": "eval:doc.enable_invoicing && doc.send_email",
|
||||||
"fieldname": "send_invoice",
|
"fieldname": "send_invoice",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Send Invoice with Email"
|
"label": "Send Invoice with Email"
|
||||||
@ -119,11 +115,43 @@
|
|||||||
"label": "Email Template",
|
"label": "Email Template",
|
||||||
"mandatory_depends_on": "eval:doc.send_email",
|
"mandatory_depends_on": "eval:doc.send_email",
|
||||||
"options": "Email Template"
|
"options": "Email Template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "enable_invoicing",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Enable Invoicing",
|
||||||
|
"mandatory_depends_on": "eval:doc.send_invoice || doc.make_payment_entry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.enable_invoicing",
|
||||||
|
"description": "Auto creates Payment Entry for Sales Invoices created for Membership from web forms.",
|
||||||
|
"fieldname": "make_payment_entry",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Make Payment Entry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.make_payment_entry",
|
||||||
|
"fieldname": "payment_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Payment To",
|
||||||
|
"mandatory_depends_on": "eval:doc.make_payment_entry",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.enable_invoicing",
|
||||||
|
"description": "Automatically create an invoice when payment is authorized from a web form entry",
|
||||||
|
"fieldname": "create_for_web_forms",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Auto Create Invoice for Web Forms"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-08-05 17:26:37.287395",
|
"modified": "2021-01-21 19:57:53.213286",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Non Profit",
|
"module": "Non Profit",
|
||||||
"name": "Membership Settings",
|
"name": "Membership Settings",
|
||||||
|
|||||||
@ -3,12 +3,20 @@
|
|||||||
|
|
||||||
frappe.ui.form.on('Membership Type', {
|
frappe.ui.form.on('Membership Type', {
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => {
|
frappe.db.get_single_value('Membership Settings', 'enable_razorpay').then(val => {
|
||||||
if (val) frm.set_df_property('razorpay_plan_id', 'hidden', false);
|
if (val) frm.set_df_property('razorpay_plan_id', 'hidden', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.db.get_single_value("Membership Settings", "enable_auto_invoicing").then(val => {
|
frappe.db.get_single_value('Membership Settings', 'enable_invoicing').then(val => {
|
||||||
if (val) frm.set_df_property('linked_item', 'hidden', false);
|
if (val) frm.set_df_property('linked_item', 'hidden', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query('linked_item', () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
is_stock_item: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,9 +5,14 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
class MembershipType(Document):
|
class MembershipType(Document):
|
||||||
pass
|
def validate(self):
|
||||||
|
if self.linked_item:
|
||||||
|
is_stock_item = frappe.db.get_value("Item", self.linked_item, "is_stock_item")
|
||||||
|
if is_stock_item:
|
||||||
|
frappe.throw(_("The Linked Item should be a service item"))
|
||||||
|
|
||||||
def get_membership_type(razorpay_id):
|
def get_membership_type(razorpay_id):
|
||||||
return frappe.db.exists("Membership Type", {"razorpay_plan_id": razorpay_id})
|
return frappe.db.exists("Membership Type", {"razorpay_plan_id": razorpay_id})
|
||||||
@ -736,8 +736,9 @@ erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail
|
|||||||
erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02
|
erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02
|
||||||
erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
||||||
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
|
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
|
||||||
erpnext.patches.v13_0.update_custom_fields_for_shopify
|
|
||||||
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
|
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
|
||||||
|
erpnext.patches.v13_0.update_member_email_address
|
||||||
|
erpnext.patches.v13_0.update_custom_fields_for_shopify
|
||||||
erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
||||||
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
|
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
|
||||||
erpnext.patches.v13_0.add_po_to_global_search
|
erpnext.patches.v13_0.add_po_to_global_search
|
||||||
@ -745,3 +746,4 @@ erpnext.patches.v13_0.update_returned_qty_in_pr_dn
|
|||||||
erpnext.patches.v13_0.update_project_template_tasks
|
erpnext.patches.v13_0.update_project_template_tasks
|
||||||
erpnext.patches.v13_0.set_company_in_leave_ledger_entry
|
erpnext.patches.v13_0.set_company_in_leave_ledger_entry
|
||||||
erpnext.patches.v13_0.convert_qi_parameter_to_link_field
|
erpnext.patches.v13_0.convert_qi_parameter_to_link_field
|
||||||
|
erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from erpnext.healthcare.setup import setup_patient_history_settings
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
if "Healthcare" not in frappe.get_active_domains():
|
||||||
|
return
|
||||||
|
|
||||||
|
frappe.reload_doc("healthcare", "doctype", "Patient History Settings")
|
||||||
|
frappe.reload_doc("healthcare", "doctype", "Patient History Standard Document Type")
|
||||||
|
frappe.reload_doc("healthcare", "doctype", "Patient History Custom Document Type")
|
||||||
|
|
||||||
|
setup_patient_history_settings()
|
||||||
23
erpnext/patches/v13_0/update_member_email_address.py
Normal file
23
erpnext/patches/v13_0/update_member_email_address.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# MIT License. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.model.utils.rename_field import rename_field
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
"""add value to email_id column from email"""
|
||||||
|
|
||||||
|
if frappe.db.has_column("Member", "email"):
|
||||||
|
# Get all members
|
||||||
|
for member in frappe.db.get_all("Member", pluck="name"):
|
||||||
|
# Check if email_id already exists
|
||||||
|
if not frappe.db.get_value("Member", member, "email_id"):
|
||||||
|
# fetch email id from the user linked field email
|
||||||
|
email = frappe.db.get_value("Member", member, "email")
|
||||||
|
|
||||||
|
# Set the value for it
|
||||||
|
frappe.db.set_value("Member", member, "email_id", email)
|
||||||
|
|
||||||
|
if frappe.db.exists("DocType", "Membership Settings"):
|
||||||
|
rename_field("Membership Settings", "enable_auto_invoicing", "enable_invoicing")
|
||||||
@ -7,16 +7,19 @@ import frappe
|
|||||||
def execute():
|
def execute():
|
||||||
frappe.reload_doc("projects", "doctype", "project_template")
|
frappe.reload_doc("projects", "doctype", "project_template")
|
||||||
frappe.reload_doc("projects", "doctype", "project_template_task")
|
frappe.reload_doc("projects", "doctype", "project_template_task")
|
||||||
frappe.reload_doc("projects", "doctype", "project_template")
|
|
||||||
frappe.reload_doc("projects", "doctype", "task")
|
frappe.reload_doc("projects", "doctype", "task")
|
||||||
|
|
||||||
for template_name in frappe.db.sql("""
|
# Update property setter status if any
|
||||||
select
|
property_setter = frappe.db.get_value('Property Setter', {'doc_type': 'Task',
|
||||||
name
|
'field_name': 'status', 'property': 'options'})
|
||||||
from
|
|
||||||
`tabProject Template` """,
|
|
||||||
as_dict=1):
|
|
||||||
|
|
||||||
|
if property_setter:
|
||||||
|
property_setter_doc = frappe.get_doc('Property Setter', {'doc_type': 'Task',
|
||||||
|
'field_name': 'status', 'property': 'options'})
|
||||||
|
property_setter_doc.value += "\nTemplate"
|
||||||
|
property_setter_doc.save()
|
||||||
|
|
||||||
|
for template_name in frappe.get_all('Project Template'):
|
||||||
template = frappe.get_doc("Project Template", template_name.name)
|
template = frappe.get_doc("Project Template", template_name.name)
|
||||||
replace_tasks = False
|
replace_tasks = False
|
||||||
new_tasks = []
|
new_tasks = []
|
||||||
|
|||||||
@ -12,6 +12,8 @@ frappe.ui.form.on('Additional Salary', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.trigger('set_earning_component');
|
||||||
},
|
},
|
||||||
|
|
||||||
employee: function(frm) {
|
employee: function(frm) {
|
||||||
@ -43,6 +45,20 @@ frappe.ui.form.on('Additional Salary', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
company: function(frm) {
|
||||||
|
frm.trigger('set_earning_component');
|
||||||
|
},
|
||||||
|
|
||||||
|
set_earning_component: function(frm) {
|
||||||
|
if (!frm.doc.company) return;
|
||||||
|
frm.set_query("salary_component", function() {
|
||||||
|
return {
|
||||||
|
query: "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
|
||||||
|
filters: {type: "earning", company: frm.doc.company}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
get_employee_currency: function(frm) {
|
get_employee_currency: function(frm) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
|
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
|
||||||
|
|||||||
@ -70,6 +70,9 @@ frappe.ui.form.on('Salary Structure', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
company: function(frm) {
|
||||||
|
frm.trigger('set_earning_deduction_component');
|
||||||
|
},
|
||||||
|
|
||||||
currency: function(frm) {
|
currency: function(frm) {
|
||||||
calculate_totals(frm.doc);
|
calculate_totals(frm.doc);
|
||||||
@ -117,6 +120,7 @@ frappe.ui.form.on('Salary Structure', {
|
|||||||
fields_read_only.forEach(function(field) {
|
fields_read_only.forEach(function(field) {
|
||||||
frappe.meta.get_docfield("Salary Detail", field, frm.doc.name).read_only = 1;
|
frappe.meta.get_docfield("Salary Detail", field, frm.doc.name).read_only = 1;
|
||||||
});
|
});
|
||||||
|
frm.trigger('set_earning_deduction_component');
|
||||||
},
|
},
|
||||||
|
|
||||||
assign_to_employees:function (frm) {
|
assign_to_employees:function (frm) {
|
||||||
|
|||||||
@ -216,8 +216,13 @@ def get_earning_deduction_components(doctype, txt, searchfield, start, page_len,
|
|||||||
return frappe.db.sql("""
|
return frappe.db.sql("""
|
||||||
select t1.salary_component
|
select t1.salary_component
|
||||||
from `tabSalary Component` t1, `tabSalary Component Account` t2
|
from `tabSalary Component` t1, `tabSalary Component Account` t2
|
||||||
where t1.salary_component = t2.parent
|
where (t1.name = t2.parent
|
||||||
and t1.type = %s
|
and t1.type = %(type)s
|
||||||
and t2.company = %s
|
and t2.company = %(company)s)
|
||||||
|
or (t1.type = %(type)s
|
||||||
|
and t1.statistical_component = 1)
|
||||||
order by salary_component
|
order by salary_component
|
||||||
""", (filters['type'], filters['company']) )
|
""",{
|
||||||
|
"type": filters['type'],
|
||||||
|
"company": filters['company']
|
||||||
|
})
|
||||||
|
|||||||
@ -18,6 +18,9 @@ erpnext.setup_einvoice_actions = (doctype) => {
|
|||||||
|
|
||||||
if (!irn && !__unsaved) {
|
if (!irn && !__unsaved) {
|
||||||
const action = () => {
|
const action = () => {
|
||||||
|
if (frm.doc.__unsaved) {
|
||||||
|
frappe.throw(__('Please save the document to generate IRN.'));
|
||||||
|
}
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: 'erpnext.regional.india.e_invoice.utils.get_einvoice',
|
method: 'erpnext.regional.india.e_invoice.utils.get_einvoice',
|
||||||
args: { doctype, docname: name },
|
args: { doctype, docname: name },
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import json
|
|||||||
import base64
|
import base64
|
||||||
import frappe
|
import frappe
|
||||||
import traceback
|
import traceback
|
||||||
|
import io
|
||||||
from frappe import _, bold
|
from frappe import _, bold
|
||||||
from pyqrcode import create as qrcreate
|
from pyqrcode import create as qrcreate
|
||||||
from frappe.integrations.utils import make_post_request, make_get_request
|
from frappe.integrations.utils import make_post_request, make_get_request
|
||||||
@ -161,9 +162,9 @@ def get_item_list(invoice):
|
|||||||
|
|
||||||
item.qty = abs(item.qty)
|
item.qty = abs(item.qty)
|
||||||
item.discount_amount = abs(item.discount_amount * item.qty)
|
item.discount_amount = abs(item.discount_amount * item.qty)
|
||||||
item.unit_rate = abs(item.base_amount / item.qty)
|
item.unit_rate = abs(item.base_net_amount / item.qty)
|
||||||
item.gross_amount = abs(item.base_amount)
|
item.gross_amount = abs(item.base_net_amount)
|
||||||
item.taxable_value = abs(item.base_amount)
|
item.taxable_value = abs(item.base_net_amount)
|
||||||
|
|
||||||
item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
|
item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
|
||||||
item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
|
item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
|
||||||
@ -198,7 +199,7 @@ def update_item_taxes(invoice, item):
|
|||||||
if t.account_head in gst_accounts_list:
|
if t.account_head in gst_accounts_list:
|
||||||
item_tax_rate = item_tax_detail[0]
|
item_tax_rate = item_tax_detail[0]
|
||||||
# item tax amount excluding discount amount
|
# item tax amount excluding discount amount
|
||||||
item_tax_amount = (item_tax_rate / 100) * item.base_amount
|
item_tax_amount = (item_tax_rate / 100) * item.base_net_amount
|
||||||
|
|
||||||
if t.account_head in gst_accounts.cess_account:
|
if t.account_head in gst_accounts.cess_account:
|
||||||
item_tax_amount_after_discount = item_tax_detail[1]
|
item_tax_amount_after_discount = item_tax_detail[1]
|
||||||
@ -217,8 +218,14 @@ def update_item_taxes(invoice, item):
|
|||||||
|
|
||||||
def get_invoice_value_details(invoice):
|
def get_invoice_value_details(invoice):
|
||||||
invoice_value_details = frappe._dict(dict())
|
invoice_value_details = frappe._dict(dict())
|
||||||
|
|
||||||
|
if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount:
|
||||||
invoice_value_details.base_total = abs(invoice.base_total)
|
invoice_value_details.base_total = abs(invoice.base_total)
|
||||||
invoice_value_details.invoice_discount_amt = invoice.base_discount_amount
|
else:
|
||||||
|
invoice_value_details.base_total = abs(invoice.base_net_total)
|
||||||
|
|
||||||
|
# since tax already considers discount amount
|
||||||
|
invoice_value_details.invoice_discount_amt = 0 # invoice.base_discount_amount
|
||||||
invoice_value_details.round_off = invoice.base_rounding_adjustment
|
invoice_value_details.round_off = invoice.base_rounding_adjustment
|
||||||
invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total)
|
invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total)
|
||||||
invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total)
|
invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total)
|
||||||
@ -244,9 +251,9 @@ def update_invoice_taxes(invoice, invoice_value_details):
|
|||||||
|
|
||||||
for tax_type in ['igst', 'cgst', 'sgst']:
|
for tax_type in ['igst', 'cgst', 'sgst']:
|
||||||
if t.account_head in gst_accounts[f'{tax_type}_account']:
|
if t.account_head in gst_accounts[f'{tax_type}_account']:
|
||||||
invoice_value_details[f'total_{tax_type}_amt'] += abs(t.base_tax_amount)
|
invoice_value_details[f'total_{tax_type}_amt'] += abs(t.base_tax_amount_after_discount_amount)
|
||||||
else:
|
else:
|
||||||
invoice_value_details.total_other_charges += abs(t.base_tax_amount)
|
invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount)
|
||||||
|
|
||||||
return invoice_value_details
|
return invoice_value_details
|
||||||
|
|
||||||
@ -430,7 +437,7 @@ class GSPConnector():
|
|||||||
self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
|
self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
|
||||||
self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
|
self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
|
||||||
self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin'
|
self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin'
|
||||||
self.cancel_ewaybill_url = self.base_url + '/enriched/ei/api/ewayapi'
|
self.cancel_ewaybill_url = self.base_url + '/enriched/ewb/ewayapi?action=CANEWB'
|
||||||
self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill'
|
self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill'
|
||||||
|
|
||||||
def get_credentials(self):
|
def get_credentials(self):
|
||||||
@ -473,7 +480,7 @@ class GSPConnector():
|
|||||||
"data": json.dumps(data, indent=4) if isinstance(data, dict) else data,
|
"data": json.dumps(data, indent=4) if isinstance(data, dict) else data,
|
||||||
"response": json.dumps(res, indent=4) if res else None
|
"response": json.dumps(res, indent=4) if res else None
|
||||||
})
|
})
|
||||||
request_log.insert(ignore_permissions=True)
|
request_log.save(ignore_permissions=True)
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
||||||
def fetch_auth_token(self):
|
def fetch_auth_token(self):
|
||||||
@ -486,7 +493,8 @@ class GSPConnector():
|
|||||||
res = self.make_request('post', self.authenticate_url, headers)
|
res = self.make_request('post', self.authenticate_url, headers)
|
||||||
self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token'))
|
self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token'))
|
||||||
self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in'))
|
self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in'))
|
||||||
self.e_invoice_settings.save()
|
self.e_invoice_settings.save(ignore_permissions=True)
|
||||||
|
self.e_invoice_settings.reload()
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log_error(res)
|
self.log_error(res)
|
||||||
@ -664,7 +672,8 @@ class GSPConnector():
|
|||||||
'cancelRsnCode': reason,
|
'cancelRsnCode': reason,
|
||||||
'cancelRmrk': remark
|
'cancelRmrk': remark
|
||||||
}, indent=4)
|
}, indent=4)
|
||||||
|
headers["username"] = headers["user_name"]
|
||||||
|
del headers["user_name"]
|
||||||
try:
|
try:
|
||||||
res = self.make_request('post', self.cancel_ewaybill_url, headers, data)
|
res = self.make_request('post', self.cancel_ewaybill_url, headers, data)
|
||||||
if res.get('success'):
|
if res.get('success'):
|
||||||
@ -762,21 +771,21 @@ class GSPConnector():
|
|||||||
qrcode = self.invoice.signed_qr_code
|
qrcode = self.invoice.signed_qr_code
|
||||||
doctype = self.invoice.doctype
|
doctype = self.invoice.doctype
|
||||||
docname = self.invoice.name
|
docname = self.invoice.name
|
||||||
|
filename = 'QRCode_{}.png'.format(docname).replace(os.path.sep, "__")
|
||||||
|
|
||||||
_file = frappe.new_doc('File')
|
qr_image = io.BytesIO()
|
||||||
_file.update({
|
|
||||||
'file_name': 'QRCode_{}.png'.format(docname.replace('/', '-')),
|
|
||||||
'attached_to_doctype': doctype,
|
|
||||||
'attached_to_name': docname,
|
|
||||||
'content': 'qrcode',
|
|
||||||
'is_private': 1
|
|
||||||
})
|
|
||||||
_file.insert()
|
|
||||||
frappe.db.commit()
|
|
||||||
url = qrcreate(qrcode, error='L')
|
url = qrcreate(qrcode, error='L')
|
||||||
abs_file_path = os.path.abspath(_file.get_full_path())
|
url.png(qr_image, scale=2, quiet_zone=1)
|
||||||
url.png(abs_file_path, scale=2, quiet_zone=1)
|
_file = frappe.get_doc({
|
||||||
|
"doctype": "File",
|
||||||
|
"file_name": filename,
|
||||||
|
"attached_to_doctype": doctype,
|
||||||
|
"attached_to_name": docname,
|
||||||
|
"attached_to_field": "qrcode_image",
|
||||||
|
"is_private": 1,
|
||||||
|
"content": qr_image.getvalue()})
|
||||||
|
_file.save()
|
||||||
|
frappe.db.commit()
|
||||||
self.invoice.qrcode_image = _file.file_url
|
self.invoice.qrcode_image = _file.file_url
|
||||||
|
|
||||||
def update_invoice(self):
|
def update_invoice(self):
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1,27 +1,32 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
import frappe
|
|
||||||
import json
|
import json
|
||||||
from frappe import _, _dict
|
|
||||||
from frappe.utils import nowdate
|
|
||||||
from frappe.utils.data import fmt_money
|
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
|
||||||
from PyPDF2 import PdfFileWriter
|
from PyPDF2 import PdfFileWriter
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import cstr, nowdate
|
||||||
|
from frappe.utils.data import fmt_money
|
||||||
|
from frappe.utils.jinja import render_template
|
||||||
from frappe.utils.pdf import get_pdf
|
from frappe.utils.pdf import get_pdf
|
||||||
from frappe.utils.print_format import read_multi_pdf
|
from frappe.utils.print_format import read_multi_pdf
|
||||||
from frappe.utils.jinja import render_template
|
|
||||||
|
IRS_1099_FORMS_FILE_EXTENSION = ".pdf"
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
filters = filters if isinstance(filters, _dict) else _dict(filters)
|
filters = filters if isinstance(filters, frappe._dict) else frappe._dict(filters)
|
||||||
|
|
||||||
if not filters:
|
if not filters:
|
||||||
filters.setdefault('fiscal_year', get_fiscal_year(nowdate())[0])
|
filters.setdefault('fiscal_year', get_fiscal_year(nowdate())[0])
|
||||||
filters.setdefault('company', frappe.db.get_default("company"))
|
filters.setdefault('company', frappe.db.get_default("company"))
|
||||||
|
|
||||||
region = frappe.db.get_value("Company", fieldname = ["country"], filters = { "name": filters.company })
|
region = frappe.db.get_value("Company",
|
||||||
|
filters={"name": filters.company},
|
||||||
|
fieldname=["country"])
|
||||||
|
|
||||||
if region != 'United States':
|
if region != 'United States':
|
||||||
return [], []
|
return [], []
|
||||||
|
|
||||||
@ -34,20 +39,23 @@ def execute(filters=None):
|
|||||||
s.tax_id as "tax_id",
|
s.tax_id as "tax_id",
|
||||||
SUM(gl.debit_in_account_currency) AS "payments"
|
SUM(gl.debit_in_account_currency) AS "payments"
|
||||||
FROM
|
FROM
|
||||||
`tabGL Entry` gl INNER JOIN `tabSupplier` s
|
`tabGL Entry` gl
|
||||||
|
INNER JOIN `tabSupplier` s
|
||||||
WHERE
|
WHERE
|
||||||
s.name = gl.party
|
s.name = gl.party
|
||||||
AND s.irs_1099 = 1
|
AND s.irs_1099 = 1
|
||||||
AND gl.fiscal_year = %(fiscal_year)s
|
AND gl.fiscal_year = %(fiscal_year)s
|
||||||
AND gl.party_type = "Supplier"
|
AND gl.party_type = "Supplier"
|
||||||
|
|
||||||
GROUP BY
|
GROUP BY
|
||||||
gl.party
|
gl.party
|
||||||
|
|
||||||
ORDER BY
|
ORDER BY
|
||||||
gl.party DESC""", {"fiscal_year": filters.fiscal_year,
|
gl.party DESC
|
||||||
|
""", {
|
||||||
|
"fiscal_year": filters.fiscal_year,
|
||||||
"supplier_group": filters.supplier_group,
|
"supplier_group": filters.supplier_group,
|
||||||
"company": filters.company}, as_dict=True)
|
"company": filters.company
|
||||||
|
}, as_dict=True)
|
||||||
|
|
||||||
return columns, data
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
@ -74,7 +82,6 @@ def get_columns():
|
|||||||
"width": 120
|
"width": 120
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
||||||
"fieldname": "payments",
|
"fieldname": "payments",
|
||||||
"label": _("Total Payments"),
|
"label": _("Total Payments"),
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
@ -88,23 +95,32 @@ def irs_1099_print(filters):
|
|||||||
if not filters:
|
if not filters:
|
||||||
frappe._dict({
|
frappe._dict({
|
||||||
"company": frappe.db.get_default("Company"),
|
"company": frappe.db.get_default("Company"),
|
||||||
"fiscal_year": frappe.db.get_default("fiscal_year")})
|
"fiscal_year": frappe.db.get_default("Fiscal Year")
|
||||||
|
})
|
||||||
else:
|
else:
|
||||||
filters = frappe._dict(json.loads(filters))
|
filters = frappe._dict(json.loads(filters))
|
||||||
|
|
||||||
|
fiscal_year_doc = get_fiscal_year(fiscal_year=filters.fiscal_year, as_dict=True)
|
||||||
|
fiscal_year = cstr(fiscal_year_doc.year_start_date.year)
|
||||||
|
|
||||||
company_address = get_payer_address_html(filters.company)
|
company_address = get_payer_address_html(filters.company)
|
||||||
company_tin = frappe.db.get_value("Company", filters.company, "tax_id")
|
company_tin = frappe.db.get_value("Company", filters.company, "tax_id")
|
||||||
|
|
||||||
columns, data = execute(filters)
|
columns, data = execute(filters)
|
||||||
template = frappe.get_doc("Print Format", "IRS 1099 Form").html
|
template = frappe.get_doc("Print Format", "IRS 1099 Form").html
|
||||||
output = PdfFileWriter()
|
output = PdfFileWriter()
|
||||||
|
|
||||||
for row in data:
|
for row in data:
|
||||||
|
row["fiscal_year"] = fiscal_year
|
||||||
row["company"] = filters.company
|
row["company"] = filters.company
|
||||||
row["company_tin"] = company_tin
|
row["company_tin"] = company_tin
|
||||||
row["payer_street_address"] = company_address
|
row["payer_street_address"] = company_address
|
||||||
row["recipient_street_address"], row["recipient_city_state"] = get_street_address_html("Supplier", row.supplier)
|
row["recipient_street_address"], row["recipient_city_state"] = get_street_address_html(
|
||||||
|
"Supplier", row.supplier)
|
||||||
row["payments"] = fmt_money(row["payments"], precision=0, currency="USD")
|
row["payments"] = fmt_money(row["payments"], precision=0, currency="USD")
|
||||||
frappe._dict(row)
|
|
||||||
pdf = get_pdf(render_template(template, row), output=output if output else None)
|
pdf = get_pdf(render_template(template, row), output=output if output else None)
|
||||||
frappe.local.response.filename = filters.fiscal_year + " " + filters.company + " IRS 1099 Forms"
|
|
||||||
|
frappe.local.response.filename = f"{filters.fiscal_year} {filters.company} IRS 1099 Forms{IRS_1099_FORMS_FILE_EXTENSION}"
|
||||||
frappe.local.response.filecontent = read_multi_pdf(output)
|
frappe.local.response.filecontent = read_multi_pdf(output)
|
||||||
frappe.local.response.type = "download"
|
frappe.local.response.type = "download"
|
||||||
|
|
||||||
@ -121,35 +137,44 @@ def get_payer_address_html(company):
|
|||||||
address_type="Postal" DESC, address_type="Billing" DESC
|
address_type="Postal" DESC, address_type="Billing" DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""", {"company": company}, as_dict=True)
|
""", {"company": company}, as_dict=True)
|
||||||
|
|
||||||
|
address_display = ""
|
||||||
if address_list:
|
if address_list:
|
||||||
company_address = address_list[0]["name"]
|
company_address = address_list[0]["name"]
|
||||||
return frappe.get_doc("Address", company_address).get_display()
|
address_display = frappe.get_doc("Address", company_address).get_display()
|
||||||
else:
|
|
||||||
return ""
|
return address_display
|
||||||
|
|
||||||
|
|
||||||
def get_street_address_html(party_type, party):
|
def get_street_address_html(party_type, party):
|
||||||
address_list = frappe.db.sql("""
|
address_list = frappe.db.sql("""
|
||||||
SELECT
|
SELECT
|
||||||
link.parent
|
link.parent
|
||||||
FROM `tabDynamic Link` link, `tabAddress` address
|
FROM
|
||||||
WHERE link.parenttype = "Address"
|
`tabDynamic Link` link,
|
||||||
|
`tabAddress` address
|
||||||
|
WHERE
|
||||||
|
link.parenttype = "Address"
|
||||||
AND link.link_name = %(party)s
|
AND link.link_name = %(party)s
|
||||||
ORDER BY address.address_type="Postal" DESC,
|
ORDER BY
|
||||||
|
address.address_type="Postal" DESC,
|
||||||
address.address_type="Billing" DESC
|
address.address_type="Billing" DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""", {"party": party}, as_dict=True)
|
""", {"party": party}, as_dict=True)
|
||||||
|
|
||||||
|
street_address = city_state = ""
|
||||||
if address_list:
|
if address_list:
|
||||||
supplier_address = address_list[0]["parent"]
|
supplier_address = address_list[0]["parent"]
|
||||||
doc = frappe.get_doc("Address", supplier_address)
|
doc = frappe.get_doc("Address", supplier_address)
|
||||||
|
|
||||||
if doc.address_line2:
|
if doc.address_line2:
|
||||||
street = doc.address_line1 + "<br>\n" + doc.address_line2 + "<br>\n"
|
street_address = doc.address_line1 + "<br>\n" + doc.address_line2 + "<br>\n"
|
||||||
else:
|
else:
|
||||||
street = doc.address_line1 + "<br>\n"
|
street_address = doc.address_line1 + "<br>\n"
|
||||||
city = doc.city + ", " if doc.city else ""
|
|
||||||
city = city + doc.state + " " if doc.state else city
|
city_state = doc.city + ", " if doc.city else ""
|
||||||
city = city + doc.pincode if doc.pincode else city
|
city_state = city_state + doc.state + " " if doc.state else city_state
|
||||||
city += "<br>\n"
|
city_state = city_state + doc.pincode if doc.pincode else city_state
|
||||||
return street, city
|
city_state += "<br>\n"
|
||||||
else:
|
|
||||||
return "", ""
|
return street_address, city_state
|
||||||
|
|||||||
@ -233,7 +233,8 @@ def get_stock_ledger_entries(filters):
|
|||||||
from `tabItem` {item_conditions}) item
|
from `tabItem` {item_conditions}) item
|
||||||
where item_code = item.name and
|
where item_code = item.name and
|
||||||
company = %(company)s and
|
company = %(company)s and
|
||||||
posting_date <= %(to_date)s
|
posting_date <= %(to_date)s and
|
||||||
|
is_cancelled != 1
|
||||||
{sle_conditions}
|
{sle_conditions}
|
||||||
order by posting_date, posting_time, sle.creation, actual_qty""" #nosec
|
order by posting_date, posting_time, sle.creation, actual_qty""" #nosec
|
||||||
.format(item_conditions=get_item_conditions(filters),
|
.format(item_conditions=get_item_conditions(filters),
|
||||||
|
|||||||
@ -214,7 +214,10 @@ class Issue(Document):
|
|||||||
|
|
||||||
def before_insert(self):
|
def before_insert(self):
|
||||||
if frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
|
if frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
|
||||||
|
if frappe.flags.in_test:
|
||||||
self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
|
self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
|
||||||
|
else:
|
||||||
|
self.set_response_and_resolution_time()
|
||||||
|
|
||||||
def set_response_and_resolution_time(self, priority=None, service_level_agreement=None):
|
def set_response_and_resolution_time(self, priority=None, service_level_agreement=None):
|
||||||
service_level_agreement = get_active_service_level_agreement_for(priority=priority,
|
service_level_agreement = get_active_service_level_agreement_for(priority=priority,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user