Merge branch 'develop' into lcv_multicurrency

This commit is contained in:
Marica 2021-01-25 12:39:16 +05:30 committed by GitHub
commit 62a1caf6c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 1863 additions and 688 deletions

View File

@ -132,16 +132,10 @@ def allow_regional(fn):
return caller
def get_last_membership():
def get_last_membership(member):
'''Returns last membership if exists'''
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
dict(member=frappe.session.user, paid=1), order_by='to_date desc', limit=1)
dict(member=member, paid=1), order_by='to_date desc', limit=1)
return last_membership and last_membership[0]
def is_member():
'''Returns true if the user is still a member'''
last_membership = get_last_membership()
if last_membership and getdate(last_membership.to_date) > getdate():
return True
return False
if last_membership:
return last_membership[0]

View File

@ -1861,23 +1861,6 @@ class TestSalesInvoice(unittest.TestCase):
def test_einvoice_json(self):
from erpnext.regional.india.e_invoice.utils import make_einvoice
customer_gstin = '27AACCM7806M1Z3'
customer_gstin_dtls = {
'LegalName': '_Test Customer', 'TradeName': '_Test Customer', 'AddrLoc': '_Test City',
'StateCode': '27', 'AddrPncd': '410038', 'AddrBno': '_Test Bldg',
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
}
company_gstin = '27AAECE4835E1ZR'
company_gstin_dtls = {
'LegalName': '_Test Company', 'TradeName': '_Test Company', 'AddrLoc': '_Test City',
'StateCode': '27', 'AddrPncd': '401108', 'AddrBno': '_Test Bldg',
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
}
# set cache gstin details to avoid fetching details which will require connection to GSP servers
frappe.local.gstin_cache = {}
frappe.local.gstin_cache[customer_gstin] = customer_gstin_dtls
frappe.local.gstin_cache[company_gstin] = company_gstin_dtls
si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####'
si.items = []
@ -1930,12 +1913,12 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
self.assertEqual(value_details['IgstVal'], total_item_igst_value)
self.assertEqual(
value_details['TotInvVal'],
value_details['AssVal'] + value_details['CgstVal']
+ value_details['SgstVal'] + value_details['IgstVal']
calculated_invoice_value = \
value_details['AssVal'] + value_details['CgstVal'] \
+ value_details['SgstVal'] + value_details['IgstVal'] \
+ value_details['OthChrg'] - value_details['Discount']
)
self.assertTrue(value_details['TotInvVal'] - calculated_invoice_value < 0.1)
self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
self.assertTrue(einvoice['EwbDtls'])

View File

@ -53,8 +53,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row = {
'item_code': d.item_code,
'item_name': item_record.item_name,
'item_group': item_record.item_group,
'item_name': item_record.item_name if item_record else d.item_name,
'item_group': item_record.item_group if item_record else d.item_group,
'description': d.description,
'invoice': d.parent,
'posting_date': d.posting_date,
@ -316,6 +316,7 @@ def get_items(filters, additional_query_columns):
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,

View File

@ -655,6 +655,34 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(query, filters)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, filters):
query = """
select name
from `tabHealthcare Service Unit`
where
is_group = 0
and company = {company}
and name like {txt}""".format(
company = frappe.db.escape(filters.get('company')), txt = frappe.db.escape('%{0}%'.format(txt)))
if filters and filters.get('inpatient_record'):
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
service_unit = get_current_healthcare_service_unit(filters.get('inpatient_record'))
# if the patient is admitted, then appointments should be allowed against the admission service unit,
# inspite of it being an Inpatient Occupancy service unit
if service_unit:
query += " and (allow_appointments = 1 or name = {service_unit})".format(service_unit = frappe.db.escape(service_unit))
else:
query += " and allow_appointments = 1"
else:
query += " and allow_appointments = 1"
return frappe.db.sql(query, filters)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):

View File

@ -8,12 +8,12 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-14 17:38:27.496696",
"modified": "2021-01-21 15:28:52.483839",
"modified_by": "Administrator",
"name": "Create Opportunity",
"owner": "Administrator",
"reference_document": "Opportunity",
"show_full_form": 0,
"show_full_form": 1,
"title": "Create Opportunity",
"validate_action": 1
}

View File

@ -100,7 +100,6 @@ class ClinicalProcedure(Document):
allow_start = self.set_actual_qty()
if allow_start:
self.db_set('status', 'In Progress')
insert_clinical_procedure_to_medical_record(self)
return 'success'
return 'insufficient stock'
@ -247,21 +246,3 @@ def make_procedure(source_name, target_doc=None):
}, target_doc, set_missing_values)
return doc
def insert_clinical_procedure_to_medical_record(doc):
subject = frappe.bold(_("Clinical Procedure conducted: ")) + cstr(doc.procedure_template) + "<br>"
if doc.practitioner:
subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
if subject and doc.notes:
subject += '<br/>' + doc.notes
medical_record = frappe.new_doc('Patient Medical Record')
medical_record.patient = doc.patient
medical_record.subject = subject
medical_record.status = 'Open'
medical_record.communication_date = doc.start_date
medical_record.reference_doctype = 'Clinical Procedure'
medical_record.reference_name = doc.name
medical_record.reference_owner = doc.owner
medical_record.save(ignore_permissions=True)

View File

@ -264,7 +264,7 @@ def get_filters(entry):
def get_current_healthcare_service_unit(inpatient_record):
ip_record = frappe.get_doc('Inpatient Record', inpatient_record)
if ip_record.inpatient_occupancies:
if ip_record.status in ['Admitted', 'Discharge Scheduled'] and ip_record.inpatient_occupancies:
return ip_record.inpatient_occupancies[-1].service_unit
return

View File

@ -142,11 +142,15 @@ def create_inpatient(patient):
return inpatient_record
def get_healthcare_service_unit():
service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
def get_healthcare_service_unit(unit_name=None):
if not unit_name:
service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
else:
service_unit = frappe.db.exists("Healthcare Service Unit", {"healthcare_service_unit_name": unit_name})
if not service_unit:
service_unit = frappe.new_doc("Healthcare Service Unit")
service_unit.healthcare_service_unit_name = "Test Service Unit Ip Occupancy"
service_unit.healthcare_service_unit_name = unit_name or "Test Service Unit Ip Occupancy"
service_unit.company = "_Test Company"
service_unit.service_unit_type = get_service_unit_type()
service_unit.inpatient_occupancy = 1

View File

@ -359,6 +359,7 @@
{
"fieldname": "normal_test_items",
"fieldtype": "Table",
"label": "Normal Test Result",
"options": "Normal Test Result",
"print_hide": 1
},
@ -380,6 +381,7 @@
{
"fieldname": "sensitivity_test_items",
"fieldtype": "Table",
"label": "Sensitivity Test Result",
"options": "Sensitivity Test Result",
"print_hide": 1,
"report_hide": 1
@ -529,6 +531,7 @@
{
"fieldname": "descriptive_test_items",
"fieldtype": "Table",
"label": "Descriptive Test Result",
"options": "Descriptive Test Result",
"print_hide": 1,
"report_hide": 1
@ -549,13 +552,14 @@
{
"fieldname": "organism_test_items",
"fieldtype": "Table",
"label": "Organism Test Result",
"options": "Organism Test Result",
"print_hide": 1
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-07-30 18:18:38.516215",
"modified": "2020-11-30 11:04:17.195848",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Lab Test",

View File

@ -17,11 +17,9 @@ class LabTest(Document):
self.validate_result_values()
self.db_set('submitted_date', getdate())
self.db_set('status', 'Completed')
insert_lab_test_to_medical_record(self)
def on_cancel(self):
self.db_set('status', 'Cancelled')
delete_lab_test_from_medical_record(self)
self.reload()
def on_update(self):
@ -330,60 +328,6 @@ def get_employee_by_user_id(user_id):
return frappe.get_doc('Employee', emp_id)
return None
def insert_lab_test_to_medical_record(doc):
table_row = False
subject = cstr(doc.lab_test_name)
if doc.practitioner:
subject += frappe.bold(_('Healthcare Practitioner: '))+ doc.practitioner + '<br>'
if doc.normal_test_items:
item = doc.normal_test_items[0]
comment = ''
if item.lab_test_comment:
comment = str(item.lab_test_comment)
table_row = frappe.bold(_('Lab Test Conducted: ')) + item.lab_test_name
if item.lab_test_event:
table_row += frappe.bold(_('Lab Test Event: ')) + item.lab_test_event
if item.result_value:
table_row += ' ' + frappe.bold(_('Lab Test Result: ')) + item.result_value
if item.normal_range:
table_row += ' ' + _('Normal Range: ') + item.normal_range
table_row += ' ' + comment
elif doc.descriptive_test_items:
item = doc.descriptive_test_items[0]
if item.lab_test_particulars and item.result_value:
table_row = item.lab_test_particulars + ' ' + item.result_value
elif doc.sensitivity_test_items:
item = doc.sensitivity_test_items[0]
if item.antibiotic and item.antibiotic_sensitivity:
table_row = item.antibiotic + ' ' + item.antibiotic_sensitivity
if table_row:
subject += '<br>' + table_row
if doc.lab_test_comment:
subject += '<br>' + cstr(doc.lab_test_comment)
medical_record = frappe.new_doc('Patient Medical Record')
medical_record.patient = doc.patient
medical_record.subject = subject
medical_record.status = 'Open'
medical_record.communication_date = doc.result_date
medical_record.reference_doctype = 'Lab Test'
medical_record.reference_name = doc.name
medical_record.reference_owner = doc.owner
medical_record.save(ignore_permissions = True)
def delete_lab_test_from_medical_record(self):
medical_record_id = frappe.db.sql('select name from `tabPatient Medical Record` where reference_name=%s', (self.name))
if medical_record_id and medical_record_id[0][0]:
frappe.delete_doc('Patient Medical Record', medical_record_id[0][0])
@frappe.whitelist()
def get_lab_test_prescribed(patient):

View File

@ -31,12 +31,12 @@ frappe.ui.form.on('Patient Appointment', {
};
});
frm.set_query('service_unit', function(){
frm.set_query('service_unit', function() {
return {
query: 'erpnext.controllers.queries.get_healthcare_service_units',
filters: {
'is_group': false,
'allow_appointments': true,
'company': frm.doc.company
company: frm.doc.company,
inpatient_record: frm.doc.inpatient_record
}
};
});

View File

@ -18,6 +18,7 @@ from erpnext.healthcare.utils import check_fee_validity, get_service_item_and_pr
class PatientAppointment(Document):
def validate(self):
self.validate_overlaps()
self.validate_service_unit()
self.set_appointment_datetime()
self.validate_customer_created()
self.set_status()
@ -68,6 +69,19 @@ class PatientAppointment(Document):
overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4])
frappe.throw(overlapping_details, title=_('Appointments Overlapping'))
def validate_service_unit(self):
if self.inpatient_record and self.service_unit:
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
is_inpatient_occupancy_unit = frappe.db.get_value('Healthcare Service Unit', self.service_unit,
'inpatient_occupancy')
service_unit = get_current_healthcare_service_unit(self.inpatient_record)
if is_inpatient_occupancy_unit and service_unit != self.service_unit:
msg = _('Patient {0} is not admitted in the service unit {1}').format(frappe.bold(self.patient), frappe.bold(self.service_unit)) + '<br>'
msg += _('Appointment for service units with Inpatient Occupancy can only be created against the unit where patient has been admitted.')
frappe.throw(msg, title=_('Invalid Healthcare Service Unit'))
def set_appointment_datetime(self):
self.appointment_datetime = "%s %s" % (self.appointment_date, self.appointment_time or "00:00:00")

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import unittest
import frappe
from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter
from frappe.utils import nowdate, add_days
from frappe.utils import nowdate, add_days, now_datetime
from frappe.utils.make_random import get_random
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
@ -78,6 +78,59 @@ class TestPatientAppointment(unittest.TestCase):
sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'status'), 'Cancelled')
def test_appointment_booking_for_admission_service_unit(self):
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \
create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
frappe.db.sql("""delete from `tabInpatient Record`""")
patient, medical_department, practitioner = create_healthcare_docs()
patient = create_patient()
# Schedule Admission
ip_record = create_inpatient(patient)
ip_record.expected_length_of_stay = 0
ip_record.save(ignore_permissions = True)
# Admit
service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
admit_patient(ip_record, service_unit, now_datetime())
appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit)
self.assertEqual(appointment.service_unit, service_unit)
# Discharge
schedule_discharge(frappe.as_json({'patient': patient}))
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
mark_invoiced_inpatient_occupancy(ip_record1)
discharge_patient(ip_record1)
def test_invalid_healthcare_service_unit_validation(self):
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \
create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
frappe.db.sql("""delete from `tabInpatient Record`""")
patient, medical_department, practitioner = create_healthcare_docs()
patient = create_patient()
# Schedule Admission
ip_record = create_inpatient(patient)
ip_record.expected_length_of_stay = 0
ip_record.save(ignore_permissions = True)
# Admit
service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
admit_patient(ip_record, service_unit, now_datetime())
appointment_service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy for Appointment')
appointment = create_appointment(patient, practitioner, nowdate(), service_unit=appointment_service_unit, save=0)
self.assertRaises(frappe.exceptions.ValidationError, appointment.save)
# Discharge
schedule_discharge(frappe.as_json({'patient': patient}))
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
mark_invoiced_inpatient_occupancy(ip_record1)
discharge_patient(ip_record1)
def create_healthcare_docs():
patient = create_patient()
@ -125,7 +178,7 @@ def create_encounter(appointment):
encounter.submit()
return encounter
def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0):
def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0, service_unit=None, save=1):
item = create_healthcare_service_items()
frappe.db.set_value('Healthcare Settings', None, 'inpatient_visit_charge_item', item)
frappe.db.set_value('Healthcare Settings', None, 'op_consulting_charge_item', item)
@ -136,12 +189,15 @@ def create_appointment(patient, practitioner, appointment_date, invoice=0, proce
appointment.appointment_date = appointment_date
appointment.company = '_Test Company'
appointment.duration = 15
if service_unit:
appointment.service_unit = service_unit
if invoice:
appointment.mode_of_payment = 'Cash'
appointment.paid_amount = 500
if procedure_template:
appointment.procedure_template = create_clinical_procedure_template().get('name')
appointment.save(ignore_permissions=True)
if save:
appointment.save(ignore_permissions=True)
return appointment
def create_healthcare_service_items():
@ -152,6 +208,7 @@ def create_healthcare_service_items():
item.item_name = 'Consulting Charges'
item.item_group = 'Services'
item.is_stock_item = 0
item.stock_uom = 'Nos'
item.save()
return item.name

View File

@ -210,7 +210,7 @@
{
"fieldname": "drug_prescription",
"fieldtype": "Table",
"label": "Items",
"label": "Drug Prescription",
"options": "Drug Prescription"
},
{
@ -328,7 +328,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-05-16 21:00:08.644531",
"modified": "2020-11-30 10:39:00.783119",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Encounter",

View File

@ -17,10 +17,6 @@ class PatientEncounter(Document):
def on_update(self):
if self.appointment:
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed')
update_encounter_medical_record(self)
def after_insert(self):
insert_encounter_to_medical_record(self)
def on_submit(self):
if self.therapies:
@ -33,8 +29,6 @@ class PatientEncounter(Document):
if self.inpatient_record and self.drug_prescription:
delete_ip_medication_order(self)
delete_medical_record(self)
def set_title(self):
self.title = _('{0} with {1}').format(self.patient_name or self.patient,
self.practitioner_name or self.practitioner)[:100]
@ -102,61 +96,7 @@ def create_therapy_plan(encounter):
frappe.msgprint(_('Therapy Plan {0} created successfully.').format(frappe.bold(doc.name)), alert=True)
def insert_encounter_to_medical_record(doc):
subject = set_subject_field(doc)
medical_record = frappe.new_doc('Patient Medical Record')
medical_record.patient = doc.patient
medical_record.subject = subject
medical_record.status = 'Open'
medical_record.communication_date = doc.encounter_date
medical_record.reference_doctype = 'Patient Encounter'
medical_record.reference_name = doc.name
medical_record.reference_owner = doc.owner
medical_record.save(ignore_permissions=True)
def update_encounter_medical_record(encounter):
medical_record_id = frappe.db.exists('Patient Medical Record', {'reference_name': encounter.name})
if medical_record_id and medical_record_id[0][0]:
subject = set_subject_field(encounter)
frappe.db.set_value('Patient Medical Record', medical_record_id[0][0], 'subject', subject)
else:
insert_encounter_to_medical_record(encounter)
def delete_medical_record(encounter):
record = frappe.db.exists('Patient Medical Record', {'reference_name', encounter.name})
if record:
frappe.delete_doc('Patient Medical Record', record, force=1)
def delete_ip_medication_order(encounter):
record = frappe.db.exists('Inpatient Medication Order', {'patient_encounter': encounter.name})
if record:
frappe.delete_doc('Inpatient Medication Order', record, force=1)
def set_subject_field(encounter):
subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '<br>'
if encounter.symptoms:
subject += frappe.bold(_('Symptoms: ')) + '<br>'
for entry in encounter.symptoms:
subject += cstr(entry.complaint) + '<br>'
else:
subject += frappe.bold(_('No Symptoms')) + '<br>'
if encounter.diagnosis:
subject += frappe.bold(_('Diagnosis: ')) + '<br>'
for entry in encounter.diagnosis:
subject += cstr(entry.diagnosis) + '<br>'
else:
subject += frappe.bold(_('No Diagnosis')) + '<br>'
if encounter.drug_prescription:
subject += '<br>' + _('Drug(s) Prescribed.')
if encounter.lab_test_prescription:
subject += '<br>' + _('Test(s) Prescribed.')
if encounter.procedure_prescription:
subject += '<br>' + _('Procedure(s) Prescribed.')
return subject
frappe.delete_doc('Inpatient Medication Order', record, force=1)

View File

@ -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
}

View File

@ -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

View File

@ -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);
}
}
});

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -18,6 +18,7 @@ class TestPatientMedicalRecord(unittest.TestCase):
patient, medical_department, practitioner = create_healthcare_docs()
appointment = create_appointment(patient, practitioner, nowdate(), invoice=1)
encounter = create_encounter(appointment)
# check for encounter
medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': encounter.name})
self.assertTrue(medical_rec)

View File

@ -41,7 +41,6 @@ class TherapySession(Document):
def on_submit(self):
self.update_sessions_count_in_therapy_plan()
insert_session_medical_record(self)
def on_update(self):
if self.appointment:
@ -142,23 +141,3 @@ def get_therapy_item(therapy, item):
item.reference_dt = 'Therapy Session'
item.reference_dn = therapy.name
return item
def insert_session_medical_record(doc):
subject = frappe.bold(_('Therapy: ')) + cstr(doc.therapy_type) + '<br>'
if doc.therapy_plan:
subject += frappe.bold(_('Therapy Plan: ')) + cstr(doc.therapy_plan) + '<br>'
if doc.practitioner:
subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
subject += frappe.bold(_('Total Counts Targeted: ')) + cstr(doc.total_counts_targeted) + '<br>'
subject += frappe.bold(_('Total Counts Completed: ')) + cstr(doc.total_counts_completed) + '<br>'
medical_record = frappe.new_doc('Patient Medical Record')
medical_record.patient = doc.patient
medical_record.subject = subject
medical_record.status = 'Open'
medical_record.communication_date = doc.start_date
medical_record.reference_doctype = 'Therapy Session'
medical_record.reference_name = doc.name
medical_record.reference_owner = doc.owner
medical_record.save(ignore_permissions=True)

View File

@ -12,47 +12,7 @@ class VitalSigns(Document):
def validate(self):
self.set_title()
def on_submit(self):
insert_vital_signs_to_medical_record(self)
def on_cancel(self):
delete_vital_signs_from_medical_record(self)
def set_title(self):
self.title = _('{0} on {1}').format(self.patient_name or self.patient,
frappe.utils.format_date(self.signs_date))[:100]
def insert_vital_signs_to_medical_record(doc):
subject = set_subject_field(doc)
medical_record = frappe.new_doc('Patient Medical Record')
medical_record.patient = doc.patient
medical_record.subject = subject
medical_record.status = 'Open'
medical_record.communication_date = doc.signs_date
medical_record.reference_doctype = 'Vital Signs'
medical_record.reference_name = doc.name
medical_record.reference_owner = doc.owner
medical_record.flags.ignore_mandatory = True
medical_record.save(ignore_permissions=True)
def delete_vital_signs_from_medical_record(doc):
medical_record = frappe.db.get_value('Patient Medical Record', {'reference_name': doc.name})
if medical_record:
frappe.delete_doc('Patient Medical Record', medical_record)
def set_subject_field(doc):
subject = ''
if doc.temperature:
subject += frappe.bold(_('Temperature: ')) + cstr(doc.temperature) + '<br>'
if doc.pulse:
subject += frappe.bold(_('Pulse: ')) + cstr(doc.pulse) + '<br>'
if doc.respiratory_rate:
subject += frappe.bold(_('Respiratory Rate: ')) + cstr(doc.respiratory_rate) + '<br>'
if doc.bp:
subject += frappe.bold(_('BP: ')) + cstr(doc.bp) + '<br>'
if doc.bmi:
subject += frappe.bold(_('BMI: ')) + cstr(doc.bmi) + '<br>'
if doc.nutrition_note:
subject += frappe.bold(_('Note: ')) + cstr(doc.nutrition_note) + '<br>'
return subject

View File

@ -109,6 +109,11 @@
padding-right: 0px;
}
.patient-history-filter {
margin-left: 35px;
width: 25%;
}
#page-medical_record .plot-wrapper {
padding: 20px 15px;
border-bottom: 1px solid #d1d8dd;

View File

@ -1,6 +1,5 @@
<div class="col-sm-12">
<div class="col-sm-3">
<p class="text-center">{%= __("Select Patient") %}</p>
<p class="patient" style="margin: auto; max-width: 300px; margin-bottom: 20px;"></p>
<div class="patient_details" style="z-index=0"></div>
</div>
@ -11,6 +10,13 @@
<div id="chart" class="col-sm-12 patient_vital_charts">
</div>
</div>
<div class="header-separator col-sm-12 d-flex border-bottom py-3" style="display:none"></div>
<div class="row">
<div class="col-sm-12 d-flex">
<div class="patient-history-filter doctype-filter"></div>
<div class="patient-history-filter date-filter"></div>
</div>
</div>
<div class="col-sm-12 patient_documents_list">
</div>
<div class="col-sm-12 text-center py-3">

View File

@ -1,141 +1,225 @@
frappe.provide("frappe.patient_history");
frappe.provide('frappe.patient_history');
frappe.pages['patient_history'].on_page_load = function(wrapper) {
var me = this;
var page = frappe.ui.make_app_page({
let me = this;
let page = frappe.ui.make_app_page({
parent: wrapper,
title: 'Patient History',
single_column: true
});
frappe.breadcrumbs.add("Healthcare");
frappe.breadcrumbs.add('Healthcare');
let pid = '';
page.main.html(frappe.render_template("patient_history", {}));
var patient = frappe.ui.form.make_control({
parent: page.main.find(".patient"),
page.main.html(frappe.render_template('patient_history', {}));
page.main.find('.header-separator').hide();
let patient = frappe.ui.form.make_control({
parent: page.main.find('.patient'),
df: {
fieldtype: "Link",
options: "Patient",
fieldname: "patient",
change: function(){
if(pid != patient.get_value() && patient.get_value()){
fieldtype: 'Link',
options: 'Patient',
fieldname: 'patient',
placeholder: __('Select Patient'),
only_select: true,
change: function() {
let patient_id = patient.get_value();
if (pid != patient_id && patient_id) {
me.start = 0;
me.page.main.find(".patient_documents_list").html("");
get_documents(patient.get_value(), me);
show_patient_info(patient.get_value(), me);
show_patient_vital_charts(patient.get_value(), me, "bp", "mmHg", "Blood Pressure");
me.page.main.find('.patient_documents_list').html('');
setup_filters(patient_id, me);
get_documents(patient_id, me);
show_patient_info(patient_id, me);
show_patient_vital_charts(patient_id, me, 'bp', 'mmHg', 'Blood Pressure');
}
pid = patient.get_value();
pid = patient_id;
}
},
only_input: true,
});
patient.refresh();
if (frappe.route_options){
if (frappe.route_options) {
patient.set_value(frappe.route_options.patient);
}
this.page.main.on("click", ".btn-show-chart", function() {
var btn_show_id = $(this).attr("data-show-chart-id"), pts = $(this).attr("data-pts");
var title = $(this).attr("data-title");
this.page.main.on('click', '.btn-show-chart', function() {
let btn_show_id = $(this).attr('data-show-chart-id'), pts = $(this).attr('data-pts');
let title = $(this).attr('data-title');
show_patient_vital_charts(patient.get_value(), me, btn_show_id, pts, title);
});
this.page.main.on("click", ".btn-more", function() {
var doctype = $(this).attr("data-doctype"), docname = $(this).attr("data-docname");
if(me.page.main.find("."+docname).parent().find('.document-html').attr('data-fetched') == "1"){
me.page.main.find("."+docname).hide();
me.page.main.find("."+docname).parent().find('.document-html').show();
}else{
if(doctype && docname){
let exclude = ["patient", "patient_name", 'patient_sex', "encounter_date"];
this.page.main.on('click', '.btn-more', function() {
let doctype = $(this).attr('data-doctype'), docname = $(this).attr('data-docname');
if (me.page.main.find('.'+docname).parent().find('.document-html').attr('data-fetched') == '1') {
me.page.main.find('.'+docname).hide();
me.page.main.find('.'+docname).parent().find('.document-html').show();
} else {
if (doctype && docname) {
let exclude = ['patient', 'patient_name', 'patient_sex', 'encounter_date'];
frappe.call({
method: "erpnext.healthcare.utils.render_doc_as_html",
method: 'erpnext.healthcare.utils.render_doc_as_html',
args:{
doctype: doctype,
docname: docname,
exclude_fields: exclude
},
freeze: true,
callback: function(r) {
if (r.message){
me.page.main.find("."+docname).hide();
me.page.main.find("."+docname).parent().find('.document-html').html(r.message.html+"\
<div align='center'><a class='btn octicon octicon-chevron-up btn-default btn-xs\
btn-less' data-doctype='"+doctype+"' data-docname='"+docname+"'></a></div>");
me.page.main.find("."+docname).parent().find('.document-html').show();
me.page.main.find("."+docname).parent().find('.document-html').attr('data-fetched', "1");
if (r.message) {
me.page.main.find('.' + docname).hide();
me.page.main.find('.' + docname).parent().find('.document-html').html(
`${r.message.html}
<div align='center'>
<a class='btn octicon octicon-chevron-up btn-default btn-xs btn-less'
data-doctype='${doctype}'
data-docname='${docname}'>
</a>
</div>
`);
me.page.main.find('.' + docname).parent().find('.document-html').show();
me.page.main.find('.' + docname).parent().find('.document-html').attr('data-fetched', '1');
}
},
freeze: true
}
});
}
}
});
this.page.main.on("click", ".btn-less", function() {
var docname = $(this).attr("data-docname");
me.page.main.find("."+docname).parent().find('.document-id').show();
me.page.main.find("."+docname).parent().find('.document-html').hide();
this.page.main.on('click', '.btn-less', function() {
let docname = $(this).attr('data-docname');
me.page.main.find('.' + docname).parent().find('.document-id').show();
me.page.main.find('.' + docname).parent().find('.document-html').hide();
});
me.start = 0;
me.page.main.on("click", ".btn-get-records", function(){
me.page.main.on('click', '.btn-get-records', function() {
get_documents(patient.get_value(), me);
});
};
var get_documents = function(patient, me){
let setup_filters = function(patient, me) {
$('.doctype-filter').empty();
frappe.xcall(
'erpnext.healthcare.page.patient_history.patient_history.get_patient_history_doctypes'
).then(document_types => {
let doctype_filter = frappe.ui.form.make_control({
parent: $('.doctype-filter'),
df: {
fieldtype: 'MultiSelectList',
fieldname: 'document_type',
placeholder: __('Select Document Type'),
input_class: 'input-xs',
change: () => {
me.start = 0;
me.page.main.find('.patient_documents_list').html('');
get_documents(patient, me, doctype_filter.get_value(), date_range_field.get_value());
},
get_data: () => {
return document_types.map(document_type => {
return {
description: document_type,
value: document_type
};
});
},
}
});
doctype_filter.refresh();
$('.date-filter').empty();
let date_range_field = frappe.ui.form.make_control({
df: {
fieldtype: 'DateRange',
fieldname: 'date_range',
placeholder: __('Date Range'),
input_class: 'input-xs',
change: () => {
let selected_date_range = date_range_field.get_value();
if (selected_date_range && selected_date_range.length === 2) {
me.start = 0;
me.page.main.find('.patient_documents_list').html('');
get_documents(patient, me, doctype_filter.get_value(), selected_date_range);
}
}
},
parent: $('.date-filter')
});
date_range_field.refresh();
});
};
let get_documents = function(patient, me, document_types="", selected_date_range="") {
let filters = {
name: patient,
start: me.start,
page_length: 20
};
if (document_types)
filters['document_types'] = document_types;
if (selected_date_range)
filters['date_range'] = selected_date_range;
frappe.call({
"method": "erpnext.healthcare.page.patient_history.patient_history.get_feed",
args: {
name: patient,
start: me.start,
page_length: 20
},
callback: function (r) {
var data = r.message;
if(data.length){
'method': 'erpnext.healthcare.page.patient_history.patient_history.get_feed',
args: filters,
callback: function(r) {
let data = r.message;
if (data.length) {
add_to_records(me, data);
}else{
me.page.main.find(".patient_documents_list").append("<div class='text-muted' align='center'><br><br>No more records..<br><br></div>");
me.page.main.find(".btn-get-records").hide();
} else {
me.page.main.find('.patient_documents_list').append(`
<div class='text-muted' align='center'>
<br><br>${__('No more records..')}<br><br>
</div>`);
me.page.main.find('.btn-get-records').hide();
}
}
});
};
var add_to_records = function(me, data){
var details = "<ul class='nav nav-pills nav-stacked'>";
var i;
for(i=0; i<data.length; i++){
if(data[i].reference_doctype){
let add_to_records = function(me, data) {
let details = "<ul class='nav nav-pills nav-stacked'>";
let i;
for (i=0; i<data.length; i++) {
if (data[i].reference_doctype) {
let label = '';
if(data[i].subject){
label += "<br/>"+data[i].subject;
if (data[i].subject) {
label += "<br/>" + data[i].subject;
}
data[i] = add_date_separator(data[i]);
if(frappe.user_info(data[i].owner).image){
if (frappe.user_info(data[i].owner).image) {
data[i].imgsrc = frappe.utils.get_file_link(frappe.user_info(data[i].owner).image);
}
else{
} else {
data[i].imgsrc = false;
}
var time_line_heading = data[i].practitioner ? `${data[i].practitioner} ` : ``;
time_line_heading += data[i].reference_doctype + " - "+ data[i].reference_name;
details += `<li data-toggle='pill' class='patient_doc_menu'
data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'>
<div class='col-sm-12 d-flex border-bottom py-3'>`;
if (data[i].imgsrc){
details += `<span class='mr-3'>
<img class='avtar' src='${data[i].imgsrc}' width='32' height='32'>
</img>
</span>`;
}else{
details += `<span class='mr-3 avatar avatar-small' style='width:32px; height:32px;'><div align='center' class='standard-image'
style='background-color: #fafbfc;'>${data[i].practitioner ? data[i].practitioner.charAt(0) : "U"}</div></span>`;
let time_line_heading = data[i].practitioner ? `${data[i].practitioner} ` : ``;
time_line_heading += data[i].reference_doctype + " - " +
`<a onclick="frappe.set_route('Form', '${data[i].reference_doctype}', '${data[i].reference_name}');">
${data[i].reference_name}
</a>`;
details += `
<li data-toggle='pill' class='patient_doc_menu'
data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'>
<div class='col-sm-12 d-flex border-bottom py-3'>`;
if (data[i].imgsrc) {
details += `
<span class='mr-3'>
<img class='avtar' src='${data[i].imgsrc}' width='32' height='32'></img>
</span>`;
} else {
details += `<span class='mr-3 avatar avatar-small' style='width:32px; height:32px;'>
<div align='center' class='standard-image' style='background-color: #fafbfc;'>
${data[i].practitioner ? data[i].practitioner.charAt(0) : 'U'}
</div>
</span>`;
}
details += `<div class='d-flex flex-column width-full'>
<div>
`+time_line_heading+` on
`+time_line_heading+`
<span>
${data[i].date_sep}
</span>
@ -156,133 +240,150 @@ var add_to_records = function(me, data){
</li>`;
}
}
details += "</ul>";
me.page.main.find(".patient_documents_list").append(details);
details += '</ul>';
me.page.main.find('.patient_documents_list').append(details);
me.start += data.length;
if(data.length===20){
if (data.length === 20) {
me.page.main.find(".btn-get-records").show();
}else{
} else {
me.page.main.find(".btn-get-records").hide();
me.page.main.find(".patient_documents_list").append("<div class='text-muted' align='center'><br><br>No more records..<br><br></div>");
me.page.main.find(".patient_documents_list").append(`
<div class='text-muted' align='center'>
<br><br>${__('No more records..')}<br><br>
</div>`);
}
};
var add_date_separator = function(data) {
var date = frappe.datetime.str_to_obj(data.creation);
let add_date_separator = function(data) {
let date = frappe.datetime.str_to_obj(data.communication_date);
let pdate = '';
let diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date));
var diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date));
if(diff < 1) {
var pdate = 'Today';
} else if(diff < 2) {
pdate = 'Yesterday';
if (diff < 1) {
pdate = __('Today');
} else if (diff < 2) {
pdate = __('Yesterday');
} else {
pdate = frappe.datetime.global_date_format(date);
pdate = __('on ') + frappe.datetime.global_date_format(date);
}
data.date_sep = pdate;
return data;
};
var show_patient_info = function(patient, me){
let show_patient_info = function(patient, me) {
frappe.call({
"method": "erpnext.healthcare.doctype.patient.patient.get_patient_detail",
'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
args: {
patient: patient
},
callback: function (r) {
var data = r.message;
var details = "";
if(data.image){
details += "<div><img class='thumbnail' width=75% src='"+data.image+"'></div>";
callback: function(r) {
let data = r.message;
let details = '';
if (data.image) {
details += `<div><img class='thumbnail' width=75% src='${data.image}'></div>`;
}
details += "<b>" + data.patient_name +"</b><br>" + data.sex;
if(data.email) details += "<br>" + data.email;
if(data.mobile) details += "<br>" + data.mobile;
if(data.occupation) details += "<br><br><b>Occupation :</b> " + data.occupation;
if(data.blood_group) details += "<br><b>Blood group : </b> " + data.blood_group;
if(data.allergies) details += "<br><br><b>Allergies : </b> "+ data.allergies.replace("\n", "<br>");
if(data.medication) details += "<br><b>Medication : </b> "+ data.medication.replace("\n", "<br>");
if(data.alcohol_current_use) details += "<br><br><b>Alcohol use : </b> "+ data.alcohol_current_use;
if(data.alcohol_past_use) details += "<br><b>Alcohol past use : </b> "+ data.alcohol_past_use;
if(data.tobacco_current_use) details += "<br><b>Tobacco use : </b> "+ data.tobacco_current_use;
if(data.tobacco_past_use) details += "<br><b>Tobacco past use : </b> "+ data.tobacco_past_use;
if(data.medical_history) details += "<br><br><b>Medical history : </b> "+ data.medical_history.replace("\n", "<br>");
if(data.surgical_history) details += "<br><b>Surgical history : </b> "+ data.surgical_history.replace("\n", "<br>");
if(data.surrounding_factors) details += "<br><br><b>Occupational hazards : </b> "+ data.surrounding_factors.replace("\n", "<br>");
if(data.other_risk_factors) details += "<br><b>Other risk factors : </b> " + data.other_risk_factors.replace("\n", "<br>");
if(data.patient_details) details += "<br><br><b>More info : </b> " + data.patient_details.replace("\n", "<br>");
if(details){
details = "<div style='padding-left:10px; font-size:13px;' align='center'>" + details + "</div>";
details += `<b> ${data.patient_name} </b><br> ${data.sex}`;
if (data.email) details += `<br> ${data.email}`;
if (data.mobile) details += `<br> ${data.mobile}`;
if (data.occupation) details += `<br><br><b> ${__('Occupation')} : </b> ${data.occupation}`;
if (data.blood_group) details += `<br><b> ${__('Blood Group')} : </b> ${data.blood_group}`;
if (data.allergies) details += `<br><br><b> ${__('Allerigies')} : </b> ${data.allergies.replace("\n", ", ")}`;
if (data.medication) details += `<br><b> ${__('Medication')} : </b> ${data.medication.replace("\n", ", ")}`;
if (data.alcohol_current_use) details += `<br><br><b> ${__('Alcohol use')} : </b> ${data.alcohol_current_use}`;
if (data.alcohol_past_use) details += `<br><b> ${__('Alcohol past use')} : </b> ${data.alcohol_past_use}`;
if (data.tobacco_current_use) details += `<br><b> ${__('Tobacco use')} : </b> ${data.tobacco_current_use}`;
if (data.tobacco_past_use) details += `<br><b> ${__('Tobacco past use')} : </b> ${data.tobacco_past_use}`;
if (data.medical_history) details += `<br><br><b> ${__('Medical history')} : </b> ${data.medical_history.replace("\n", ", ")}`;
if (data.surgical_history) details += `<br><b> ${__('Surgical history')} : </b> ${data.surgical_history.replace("\n", ", ")}`;
if (data.surrounding_factors) details += `<br><br><b> ${__('Occupational hazards')} : </b> ${data.surrounding_factors.replace("\n", ", ")}`;
if (data.other_risk_factors) details += `<br><b> ${__('Other risk factors')} : </b> ${data.other_risk_factors.replace("\n", ", ")}`;
if (data.patient_details) details += `<br><br><b> ${__('More info')} : </b> ${data.patient_details.replace("\n", ", ")}`;
if (details) {
details = `<div style='padding-left:10px; font-size:13px;' align='left'>` + details + `</div>`;
}
me.page.main.find(".patient_details").html(details);
me.page.main.find('.patient_details').html(details);
}
});
};
var show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
let show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
frappe.call({
method: "erpnext.healthcare.utils.get_patient_vitals",
method: 'erpnext.healthcare.utils.get_patient_vitals',
args:{
patient: patient
},
callback: function(r) {
if (r.message){
var show_chart_btns_html = "<div style='padding-top:5px;'><a class='btn btn-default btn-xs btn-show-chart' \
data-show-chart-id='bp' data-pts='mmHg' data-title='Blood Pressure'>Blood Pressure</a>\
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='pulse_rate' \
data-pts='per Minutes' data-title='Respiratory/Pulse Rate'>Respiratory/Pulse Rate</a>\
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='temperature' \
data-pts='°C or °F' data-title='Temperature'>Temperature</a>\
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bmi' \
data-pts='' data-title='BMI'>BMI</a></div>";
me.page.main.find(".show_chart_btns").html(show_chart_btns_html);
var data = r.message;
if (r.message) {
let show_chart_btns_html = `
<div style='padding-top:10px;'>
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bp' data-pts='mmHg' data-title='Blood Pressure'>
${__('Blood Pressure')}
</a>
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='pulse_rate' data-pts='per Minutes' data-title='Respiratory/Pulse Rate'>
${__('Respiratory/Pulse Rate')}
</a>
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='temperature' data-pts='°C or °F' data-title='Temperature'>
${__('Temperature')}
</a>
<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bmi' data-pts='' data-title='BMI'>
${__('BMI')}
</a>
</div>`;
me.page.main.find('.show_chart_btns').html(show_chart_btns_html);
let data = r.message;
let labels = [], datasets = [];
let bp_systolic = [], bp_diastolic = [], temperature = [];
let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = [];
for(var i=0; i<data.length; i++){
labels.push(data[i].signs_date+"||"+data[i].signs_time);
if(btn_show_id=="bp"){
for (let i=0; i<data.length; i++) {
labels.push(data[i].signs_date+'||'+data[i].signs_time);
if (btn_show_id === 'bp') {
bp_systolic.push(data[i].bp_systolic);
bp_diastolic.push(data[i].bp_diastolic);
}
if(btn_show_id=="temperature"){
if (btn_show_id === 'temperature') {
temperature.push(data[i].temperature);
}
if(btn_show_id=="pulse_rate"){
if (btn_show_id === 'pulse_rate') {
pulse.push(data[i].pulse);
respiratory_rate.push(data[i].respiratory_rate);
}
if(btn_show_id=="bmi"){
if (btn_show_id === 'bmi') {
bmi.push(data[i].bmi);
height.push(data[i].height);
weight.push(data[i].weight);
}
}
if(btn_show_id=="temperature"){
datasets.push({name: "Temperature", values: temperature, chartType:'line'});
if (btn_show_id === 'temperature') {
datasets.push({name: 'Temperature', values: temperature, chartType: 'line'});
}
if(btn_show_id=="bmi"){
datasets.push({name: "BMI", values: bmi, chartType:'line'});
datasets.push({name: "Height", values: height, chartType:'line'});
datasets.push({name: "Weight", values: weight, chartType:'line'});
if (btn_show_id === 'bmi') {
datasets.push({name: 'BMI', values: bmi, chartType: 'line'});
datasets.push({name: 'Height', values: height, chartType: 'line'});
datasets.push({name: 'Weight', values: weight, chartType: 'line'});
}
if(btn_show_id=="bp"){
datasets.push({name: "BP Systolic", values: bp_systolic, chartType:'line'});
datasets.push({name: "BP Diastolic", values: bp_diastolic, chartType:'line'});
if (btn_show_id === 'bp') {
datasets.push({name: 'BP Systolic', values: bp_systolic, chartType: 'line'});
datasets.push({name: 'BP Diastolic', values: bp_diastolic, chartType: 'line'});
}
if(btn_show_id=="pulse_rate"){
datasets.push({name: "Heart Rate / Pulse", values: pulse, chartType:'line'});
datasets.push({name: "Respiratory Rate", values: respiratory_rate, chartType:'line'});
if (btn_show_id === 'pulse_rate') {
datasets.push({name: 'Heart Rate / Pulse', values: pulse, chartType: 'line'});
datasets.push({name: 'Respiratory Rate', values: respiratory_rate, chartType: 'line'});
}
new frappe.Chart( ".patient_vital_charts", {
new frappe.Chart('.patient_vital_charts', {
data: {
labels: labels,
datasets: datasets
},
title: title,
type: 'axis-mixed', // 'axis-mixed', 'bar', 'line', 'pie', 'percentage'
type: 'axis-mixed',
height: 200,
colors: ['purple', '#ffa3ef', 'light-blue'],
@ -291,9 +392,11 @@ var show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
formatTooltipY: d => d + ' ' + pts,
}
});
}else{
me.page.main.find(".patient_vital_charts").html("");
me.page.main.find(".show_chart_btns").html("");
me.page.main.find('.header-separator').show();
} else {
me.page.main.find('.patient_vital_charts').html('');
me.page.main.find('.show_chart_btns').html('');
me.page.main.find('.header-separator').hide();
}
}
});

View File

@ -4,36 +4,70 @@
from __future__ import unicode_literals
import frappe
import json
from frappe.utils import cint
from erpnext.healthcare.utils import render_docs_as_html
@frappe.whitelist()
def get_feed(name, start=0, page_length=20):
def get_feed(name, document_types=None, date_range=None, start=0, page_length=20):
"""get feed"""
result = frappe.db.sql("""select name, owner, creation,
reference_doctype, reference_name, subject
from `tabPatient Medical Record`
where patient=%(patient)s
order by creation desc
limit %(start)s, %(page_length)s""",
{
"patient": name,
"start": cint(start),
"page_length": cint(page_length)
}, as_dict=True)
filters = get_filters(name, document_types, date_range)
result = frappe.db.get_all('Patient Medical Record',
fields=['name', 'owner', 'communication_date',
'reference_doctype', 'reference_name', 'subject'],
filters=filters,
order_by='communication_date DESC',
limit=cint(page_length),
start=cint(start)
)
return result
def get_filters(name, document_types=None, date_range=None):
filters = {'patient': name}
if document_types:
document_types = json.loads(document_types)
if len(document_types):
filters['reference_doctype'] = ['IN', document_types]
if date_range:
try:
date_range = json.loads(date_range)
if date_range:
filters['communication_date'] = ['between', [date_range[0], date_range[1]]]
except json.decoder.JSONDecodeError:
pass
return filters
@frappe.whitelist()
def get_feed_for_dt(doctype, docname):
"""get feed"""
result = frappe.db.sql("""select name, owner, modified, creation,
reference_doctype, reference_name, subject
from `tabPatient Medical Record`
where reference_name=%(docname)s and reference_doctype=%(doctype)s
order by creation desc""",
{
"docname": docname,
"doctype": doctype
}, as_dict=True)
result = frappe.db.get_all('Patient Medical Record',
fields=['name', 'owner', 'communication_date',
'reference_doctype', 'reference_name', 'subject'],
filters={
'reference_doctype': doctype,
'reference_name': docname
},
order_by='communication_date DESC'
)
return result
@frappe.whitelist()
def get_patient_history_doctypes():
document_types = []
settings = frappe.get_single("Patient History Settings")
for entry in settings.standard_doctypes:
document_types.append(entry.document_type)
for entry in settings.custom_doctypes:
document_types.append(entry.document_type)
return document_types

View File

@ -16,6 +16,7 @@ def setup_healthcare():
create_healthcare_item_groups()
create_sensitivity()
add_healthcare_service_unit_tree_root()
setup_patient_history_settings()
def create_medical_departments():
departments = [
@ -213,3 +214,82 @@ def get_company():
if company:
return company[0].name
return None
def setup_patient_history_settings():
import json
settings = frappe.get_single('Patient History Settings')
configuration = get_patient_history_config()
for dt, config in configuration.items():
settings.append("standard_doctypes", {
"document_type": dt,
"date_fieldname": config[0],
"selected_fields": json.dumps(config[1])
})
settings.save()
def get_patient_history_config():
return {
"Patient Encounter": ("encounter_date", [
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
{"label": "Symptoms", "fieldname": "symptoms", "fieldtype": "Table Multiselect"},
{"label": "Diagnosis", "fieldname": "diagnosis", "fieldtype": "Table Multiselect"},
{"label": "Drug Prescription", "fieldname": "drug_prescription", "fieldtype": "Table"},
{"label": "Lab Tests", "fieldname": "lab_test_prescription", "fieldtype": "Table"},
{"label": "Clinical Procedures", "fieldname": "procedure_prescription", "fieldtype": "Table"},
{"label": "Therapies", "fieldname": "therapies", "fieldtype": "Table"},
{"label": "Review Details", "fieldname": "encounter_comment", "fieldtype": "Small Text"}
]),
"Clinical Procedure": ("start_date", [
{"label": "Procedure Template", "fieldname": "procedure_template", "fieldtype": "Link"},
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
{"label": "Notes", "fieldname": "notes", "fieldtype": "Small Text"},
{"label": "Service Unit", "fieldname": "service_unit", "fieldtype": "Healthcare Service Unit"},
{"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"},
{"label": "Sample", "fieldname": "sample", "fieldtype": "Link"}
]),
"Lab Test": ("result_date", [
{"label": "Test Template", "fieldname": "template", "fieldtype": "Link"},
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
{"label": "Test Name", "fieldname": "lab_test_name", "fieldtype": "Data"},
{"label": "Lab Technician Name", "fieldname": "employee_name", "fieldtype": "Data"},
{"label": "Sample ID", "fieldname": "sample", "fieldtype": "Link"},
{"label": "Normal Test Result", "fieldname": "normal_test_items", "fieldtype": "Table"},
{"label": "Descriptive Test Result", "fieldname": "descriptive_test_items", "fieldtype": "Table"},
{"label": "Organism Test Result", "fieldname": "organism_test_items", "fieldtype": "Table"},
{"label": "Sensitivity Test Result", "fieldname": "sensitivity_test_items", "fieldtype": "Table"},
{"label": "Comments", "fieldname": "lab_test_comment", "fieldtype": "Table"}
]),
"Therapy Session": ("start_date", [
{"label": "Therapy Type", "fieldname": "therapy_type", "fieldtype": "Link"},
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
{"label": "Therapy Plan", "fieldname": "therapy_plan", "fieldtype": "Link"},
{"label": "Duration", "fieldname": "duration", "fieldtype": "Int"},
{"label": "Location", "fieldname": "location", "fieldtype": "Link"},
{"label": "Healthcare Service Unit", "fieldname": "service_unit", "fieldtype": "Link"},
{"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"},
{"label": "Exercises", "fieldname": "exercises", "fieldtype": "Table"},
{"label": "Total Counts Targeted", "fieldname": "total_counts_targeted", "fieldtype": "Int"},
{"label": "Total Counts Completed", "fieldname": "total_counts_completed", "fieldtype": "Int"}
]),
"Vital Signs": ("signs_date", [
{"label": "Body Temperature", "fieldname": "temperature", "fieldtype": "Data"},
{"label": "Heart Rate / Pulse", "fieldname": "pulse", "fieldtype": "Data"},
{"label": "Respiratory rate", "fieldname": "respiratory_rate", "fieldtype": "Data"},
{"label": "Tongue", "fieldname": "tongue", "fieldtype": "Select"},
{"label": "Abdomen", "fieldname": "abdomen", "fieldtype": "Select"},
{"label": "Reflexes", "fieldname": "reflexes", "fieldtype": "Select"},
{"label": "Blood Pressure", "fieldname": "bp", "fieldtype": "Data"},
{"label": "Notes", "fieldname": "vital_signs_note", "fieldtype": "Small Text"},
{"label": "Height (In Meter)", "fieldname": "height", "fieldtype": "Float"},
{"label": "Weight (In Kilogram)", "fieldname": "weight", "fieldtype": "Float"},
{"label": "BMI", "fieldname": "bmi", "fieldtype": "Float"}
]),
"Inpatient Medication Order": ("start_date", [
{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
{"label": "Start Date", "fieldname": "start_date", "fieldtype": "Date"},
{"label": "End Date", "fieldname": "end_date", "fieldtype": "Date"},
{"label": "Medication Orders", "fieldname": "medication_orders", "fieldtype": "Table"},
{"label": "Total Orders", "fieldname": "total_orders", "fieldtype": "Float"}
])
}

View File

@ -6,6 +6,7 @@ from __future__ import unicode_literals
import math
import frappe
from frappe import _
from frappe.utils.formatters import format_value
from frappe.utils import time_diff_in_hours, rounded
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account
from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity
@ -648,11 +649,15 @@ def render_doc_as_html(doctype, docname, exclude_fields = []):
html += "<table class='table table-condensed table-bordered'>" \
+ table_head + table_row + "</table>"
continue
#on other field types add label and value to html
if not df.hidden and not df.print_hide and doc.get(df.fieldname) and df.fieldname not in exclude_fields:
html += '<br>{0} : {1}'.format(df.label or df.fieldname, \
doc.get(df.fieldname))
if doc.get(df.fieldname):
formatted_value = format_value(doc.get(df.fieldname), meta.get_field(df.fieldname), doc)
html += '<br>{0} : {1}'.format(df.label or df.fieldname, formatted_value)
if not has_data : has_data = True
if sec_on and col_on and has_data:
doc_html += section_html + html + '</div></div>'
elif sec_on and not col_on and has_data:

View File

@ -221,6 +221,11 @@ standard_queries = {
}
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": {
"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"
@ -341,7 +346,8 @@ scheduler_events = {
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_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": [
"erpnext.setup.doctype.email_digest.email_digest.send",

View File

@ -26,6 +26,7 @@ def get_columns(filters):
{"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80},
{"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "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": _("% Of Applicant Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "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):
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({
'applicant_type': applicant_type_map.get(key[0]),
'applicant_name': key[0],
'total_qty': qty,
'current_value': current_value,
'price_valid_upto': valid_upto,
'portfolio_percent': flt(current_value * 100 / total_value_map.get(key[0]), 2),
'currency': currency
})
@ -60,20 +64,30 @@ def get_data(filters):
def get_loan_security_details(filters):
security_detail_map = {}
loan_security_price_map = {}
lsp_validity_map = {}
loan_security_price_map = frappe._dict(frappe.db.sql("""
SELECT loan_security, loan_security_price
loan_security_prices = frappe.db.sql("""
SELECT loan_security, loan_security_price, valid_upto
FROM `tabLoan Security Price` t1
WHERE valid_from >= (SELECT MAX(valid_from) FROM `tabLoan Security Price` t2
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_code', 'loan_security_name', 'haircut', 'loan_security_type',
'disabled'])
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)
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)
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

View File

@ -6,6 +6,8 @@ import frappe
import erpnext
from frappe import _
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):
@ -31,6 +33,7 @@ def get_columns(filters):
{"label": _("Undue Booked Interest"), "fieldname": "undue_interest", "fieldtype": "Currency", "options": "currency", "width": 120},
{"label": _("Interest %"), "fieldname": "rate_of_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},
]
@ -50,6 +53,9 @@ def get_active_loan_details(filters):
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()
penal_interest_rate_map = get_penal_interest_rate_map()
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_interest": penal_interest_rate_map.get(loan.loan_type),
"undue_interest": flt(accrual_map.get(loan.loan, {}).get("undue_interest")),
"loan_to_value": 0.0,
"currency": currency
})
loan['total_outstanding'] = loan['principal_outstanding'] + loan['interest_outstanding'] \
+ 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
def get_sanctioned_amount_map():
@ -121,4 +131,53 @@ def get_interest_accruals(loans):
return accrual_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

View File

@ -24,6 +24,7 @@ def get_columns(filters):
{"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80},
{"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "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": _("% Of Total Portfolio"), "fieldname": "portfolio_percent", "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):
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({
'total_qty': value['qty'],
'total_qty': value.get('qty'),
'current_value': current_value,
'price_valid_upto': valid_upto,
'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
})

View File

@ -12,7 +12,6 @@
"membership_expiry_date",
"column_break_5",
"membership_type",
"email",
"email_id",
"image",
"customer_section",
@ -64,13 +63,6 @@
"options": "Membership Type",
"reqd": 1
},
{
"fieldname": "email",
"fieldtype": "Link",
"in_list_view": 1,
"label": "User",
"options": "User"
},
{
"fieldname": "image",
"fieldtype": "Attach Image",
@ -178,7 +170,7 @@
],
"image_field": "image",
"links": [],
"modified": "2020-09-16 23:44:13.596948",
"modified": "2020-11-09 12:12:10.174647",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Member",

View File

@ -18,8 +18,6 @@ class Member(Document):
def validate(self):
if self.email:
self.validate_email_type(self.email)
if self.email_id:
self.validate_email_type(self.email_id)
@ -57,14 +55,16 @@ class Member(Document):
def make_customer_and_link(self):
if self.customer:
frappe.msgprint(_("A customer is already linked to this Member"))
cust = create_customer(frappe._dict({
customer = create_customer(frappe._dict({
'fullname': self.member_name,
'email': self.email_id or self.email,
'email': self.email_id,
'phone': None
}))
self.customer = cust
self.customer = customer
self.save()
frappe.msgprint(_("Customer {0} has been created succesfully.").format(self.customer))
def get_or_create_member(user_details):

View File

@ -4,16 +4,25 @@
frappe.ui.form.on('Membership', {
setup: function(frm) {
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) {
if (frm.doc.__islocal)
return;
!frm.doc.invoice && frm.add_custom_button("Generate Invoice", () => {
frm.call("generate_invoice", {
save: true
}).then(() => {
frm.reload_doc();
frm.call({
doc: frm.doc,
method: "generate_invoice",
args: {save: true},
freeze: true,
freeze_message: __("Creating Membership Invoice"),
callback: function(r) {
if (r.invoice)
frm.reload_doc();
}
});
});
@ -27,6 +36,6 @@ frappe.ui.form.on('Membership', {
},
onload: function(frm) {
frm.add_fetch('membership_type', 'amount', 'amount');
frm.add_fetch("membership_type", "amount", "amount");
}
});

View File

@ -7,6 +7,7 @@
"engine": "InnoDB",
"field_order": [
"member",
"member_name",
"membership_type",
"column_break_3",
"membership_status",
@ -46,6 +47,8 @@
{
"fieldname": "membership_status",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Membership Status",
"options": "New\nCurrent\nExpired\nPending\nCancelled"
},
@ -122,11 +125,18 @@
"fieldtype": "Link",
"label": "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,
"links": [],
"modified": "2020-09-19 14:28:11.532696",
"modified": "2021-01-21 16:31:20.032656",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Membership",
@ -158,7 +168,9 @@
}
],
"restrict_to_domain": "Non Profit",
"search_fields": "member, member_name",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "member_name",
"track_changes": 1
}
}

View File

@ -14,33 +14,43 @@ from erpnext.non_profit.doctype.member.member import create_member
from frappe import _
import erpnext
class Membership(Document):
def validate(self):
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"))
if not member_name:
user = frappe.get_doc('User', frappe.session.user)
member = frappe.get_doc(dict(
doctype='Member',
email=frappe.session.user,
membership_type=self.membership_type,
member_name=user.get_fullname()
)).insert(ignore_permissions=True)
member_name = member.name
self.validate_membership_period()
if self.get("__islocal"):
self.member = member_name
def create_member_from_website_user(self):
member_name = frappe.get_value("Member", dict(email_id=frappe.session.user))
if not member_name:
user = frappe.get_doc("User", frappe.session.user)
member = frappe.get_doc(dict(
doctype="Member",
email_id=frappe.session.user,
membership_type=self.membership_type,
member_name=user.get_fullname()
)).insert(ignore_permissions=True)
member_name = member.name
if self.get("__islocal"):
self.member = member_name
def validate_membership_period(self):
# 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 last_membership and not frappe.session.user == "Administrator":
# 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()) :
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)
elif frappe.session.user == "Administrator":
@ -54,11 +64,16 @@ class Membership(Document):
self.to_date = add_months(self.from_date, 1)
def on_payment_authorized(self, status_changed_to=None):
if status_changed_to in ("Completed", "Authorized"):
self.load_from_db()
self.db_set('paid', 1)
if status_changed_to not in ("Completed", "Authorized"):
return
self.load_from_db()
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):
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"))
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:
frappe.throw(_("No customer linked to member {0}").format(frappe.bold(self.member)))
if not settings.debit_account:
frappe.throw(_("You need to set <b>Debit Account</b> in Membership Settings"))
if not settings.company:
frappe.throw(_("You need to set <b>Default Company</b> for invoicing in Membership Settings"))
plan = frappe.get_doc("Membership Type", self.membership_type)
settings = frappe.get_doc("Membership Settings")
self.validate_membership_type_and_settings(plan, settings)
invoice = make_invoice(self, member, plan, settings)
self.invoice = invoice.name
if with_payment_entry:
self.make_payment_entry(settings, invoice)
if save:
self.save()
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):
settings = frappe.get_doc("Membership Settings")
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)
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)
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)]
if self.invoice and settings.send_invoice:
@ -112,48 +157,56 @@ class Membership(Document):
}
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:
frappe.sendmail(**email_args)
def generate_and_send_invoice(self):
invoice = self.generate_invoice(False)
self.generate_invoice(save=False)
self.send_acknowlement()
def make_invoice(membership, member, plan, settings):
invoice = frappe.get_doc({
'doctype': 'Sales Invoice',
'customer': member.customer,
'debit_to': settings.debit_account,
'currency': membership.currency,
'is_pos': 0,
'items': [
"doctype": "Sales Invoice",
"customer": member.customer,
"debit_to": settings.debit_account,
"currency": membership.currency,
"company": settings.company,
"is_pos": 0,
"items": [
{
'item_code': plan.linked_item,
'rate': membership.amount,
'qty': 1
"item_code": plan.linked_item,
"rate": membership.amount,
"qty": 1
}
]
})
invoice.set_missing_values()
invoice.insert(ignore_permissions=True)
invoice.submit()
frappe.msgprint(_("Sales Invoice created successfully"))
return invoice
def get_member_based_on_subscription(subscription_id, email):
members = frappe.get_all("Member", filters={
'subscription_id': subscription_id,
'email_id': email
"subscription_id": subscription_id,
"email_id": email
}, order_by="creation desc")
try:
return frappe.get_doc("Member", members[0]['name'])
return frappe.get_doc("Member", members[0]["name"])
except:
return None
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")
key = settings.get_webhook_secret()
@ -162,6 +215,7 @@ def verify_signature(data):
controller.verify_signature(data, signature, key)
@frappe.whitelist(allow_guest=True)
def trigger_razorpay_subscription(*args, **kwargs):
data = frappe.request.get_data(as_text=True)
@ -170,16 +224,16 @@ def trigger_razorpay_subscription(*args, **kwargs):
except Exception as e:
log = frappe.log_error(e, "Webhook Verification Error")
notify_failure(log)
return { 'status': 'Failed', 'reason': e}
return { "status": "Failed", "reason": e}
if isinstance(data, six.string_types):
data = json.loads(data)
data = frappe._dict(data)
subscription = data.payload.get("subscription", {}).get('entity', {})
subscription = data.payload.get("subscription", {}).get("entity", {})
subscription = frappe._dict(subscription)
payment = data.payload.get("payment", {}).get('entity', {})
payment = data.payload.get("payment", {}).get("entity", {})
payment = frappe._dict(payment)
try:
@ -189,15 +243,15 @@ def trigger_razorpay_subscription(*args, **kwargs):
member = get_member_based_on_subscription(subscription.id, payment.email)
if not member:
member = create_member(frappe._dict({
'fullname': payment.email,
'email': payment.email,
'plan_id': get_plan_from_razorpay_id(subscription.plan_id)
"fullname": payment.email,
"email": payment.email,
"plan_id": get_plan_from_razorpay_id(subscription.plan_id)
}))
member.subscription_id = subscription.id
member.customer_id = payment.customer_id
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)
elif subscription.notes and type(subscription.notes) == str:
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)
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
notify_failure(log)
return { 'status': 'Failed', 'reason': e}
return { "status": "Failed", "reason": e}
return { 'status': 'Success' }
return { "status": "Success" }
def notify_failure(log):
try:
content = """Dear System Manager,
Razorpay webhook for creating renewing membership subscription failed due to some reason. Please check the following error log linked below
content = """
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}
Regards, Administrator
""".format(get_link_to_form("Error Log", log.name))
Error Log: {0}
Regards,
Administrator""".format(get_link_to_form("Error Log", log.name))
sendmail_to_system_managers("[Important] [ERPNext] Razorpay membership webhook failed , please check.", content)
except:
pass
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:
return plan[0]['name']
return plan[0]["name"]
except:
return None
def set_expired_status():
frappe.db.sql("""
UPDATE
`tabMembership` SET `status` = 'Expired'
WHERE
`status` not in ('Cancelled') AND `to_date` < %s
""", (nowdate()))

View 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'];
}
}
};

View File

@ -2,8 +2,110 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
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):
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

View File

@ -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 {
filters: {
"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 {
filters: {
"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 {
filters: {
'account_type': 'Receivable',
'is_group': 0,
'company': frm.doc.company
"account_type": "Receivable",
"is_group": 0,
"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
}
};
});

View File

@ -11,9 +11,12 @@
"billing_frequency",
"webhook_secret",
"column_break_6",
"enable_auto_invoicing",
"enable_invoicing",
"create_for_web_forms",
"make_payment_entry",
"company",
"debit_account",
"payment_account",
"column_break_9",
"send_email",
"send_invoice",
@ -58,14 +61,7 @@
"label": "Invoicing"
},
{
"default": "0",
"fieldname": "enable_auto_invoicing",
"fieldtype": "Check",
"label": "Enable Auto Invoicing",
"mandatory_depends_on": "eval:doc.send_invoice"
},
{
"depends_on": "eval:doc.enable_auto_invoicing",
"depends_on": "eval:doc.enable_invoicing",
"fieldname": "debit_account",
"fieldtype": "Link",
"label": "Debit Account",
@ -77,7 +73,7 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.enable_auto_invoicing",
"depends_on": "eval:doc.enable_invoicing",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
@ -86,7 +82,7 @@
},
{
"default": "0",
"depends_on": "eval:doc.enable_auto_invoicing && doc.send_email",
"depends_on": "eval:doc.enable_invoicing && doc.send_email",
"fieldname": "send_invoice",
"fieldtype": "Check",
"label": "Send Invoice with Email"
@ -119,11 +115,43 @@
"label": "Email Template",
"mandatory_depends_on": "eval:doc.send_email",
"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,
"links": [],
"modified": "2020-08-05 17:26:37.287395",
"modified": "2021-01-21 19:57:53.213286",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Membership Settings",

View File

@ -2,13 +2,21 @@
// For license information, please see license.txt
frappe.ui.form.on('Membership Type', {
refresh: function(frm) {
frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => {
refresh: function (frm) {
frappe.db.get_single_value('Membership Settings', 'enable_razorpay').then(val => {
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);
});
frm.set_query('linked_item', () => {
return {
filters: {
is_stock_item: 0
}
};
});
}
});

View File

@ -5,9 +5,14 @@
from __future__ import unicode_literals
from frappe.model.document import Document
import frappe
from frappe import _
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):
return frappe.db.exists("Membership Type", {"razorpay_plan_id": razorpay_id})

View File

@ -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.v13_0.updates_for_multi_currency_payroll
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")
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.create_leave_policy_assignment_based_on_employee_current_leave_policy
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.set_company_in_leave_ledger_entry
erpnext.patches.v13_0.convert_qi_parameter_to_link_field
erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes

View File

@ -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()

View 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")

View File

@ -5,40 +5,43 @@ from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc("projects", "doctype", "project_template")
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", "project_template")
frappe.reload_doc("projects", "doctype", "project_template_task")
frappe.reload_doc("projects", "doctype", "task")
for template_name in frappe.db.sql("""
select
name
from
`tabProject Template` """,
as_dict=1):
template = frappe.get_doc("Project Template", template_name.name)
replace_tasks = False
new_tasks = []
for task in template.tasks:
if task.subject:
replace_tasks = True
new_task = frappe.get_doc(dict(
doctype = "Task",
subject = task.subject,
start = task.start,
duration = task.duration,
task_weight = task.task_weight,
description = task.description,
is_template = 1
)).insert()
new_tasks.append(new_task)
# Update property setter status if any
property_setter = frappe.db.get_value('Property Setter', {'doc_type': 'Task',
'field_name': 'status', 'property': 'options'})
if replace_tasks:
template.tasks = []
for tsk in new_tasks:
template.append("tasks", {
"task": tsk.name,
"subject": tsk.subject
})
template.save()
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)
replace_tasks = False
new_tasks = []
for task in template.tasks:
if task.subject:
replace_tasks = True
new_task = frappe.get_doc(dict(
doctype = "Task",
subject = task.subject,
start = task.start,
duration = task.duration,
task_weight = task.task_weight,
description = task.description,
is_template = 1
)).insert()
new_tasks.append(new_task)
if replace_tasks:
template.tasks = []
for tsk in new_tasks:
template.append("tasks", {
"task": tsk.name,
"subject": tsk.subject
})
template.save()

View File

@ -12,6 +12,8 @@ frappe.ui.form.on('Additional Salary', {
}
};
});
frm.trigger('set_earning_component');
},
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) {
frappe.call({
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",

View File

@ -70,6 +70,9 @@ frappe.ui.form.on('Salary Structure', {
});
},
company: function(frm) {
frm.trigger('set_earning_deduction_component');
},
currency: function(frm) {
calculate_totals(frm.doc);
@ -117,6 +120,7 @@ frappe.ui.form.on('Salary Structure', {
fields_read_only.forEach(function(field) {
frappe.meta.get_docfield("Salary Detail", field, frm.doc.name).read_only = 1;
});
frm.trigger('set_earning_deduction_component');
},
assign_to_employees:function (frm) {

View File

@ -216,8 +216,13 @@ def get_earning_deduction_components(doctype, txt, searchfield, start, page_len,
return frappe.db.sql("""
select t1.salary_component
from `tabSalary Component` t1, `tabSalary Component Account` t2
where t1.salary_component = t2.parent
and t1.type = %s
and t2.company = %s
where (t1.name = t2.parent
and t1.type = %(type)s
and t2.company = %(company)s)
or (t1.type = %(type)s
and t1.statistical_component = 1)
order by salary_component
""", (filters['type'], filters['company']) )
""",{
"type": filters['type'],
"company": filters['company']
})

View File

@ -18,6 +18,9 @@ erpnext.setup_einvoice_actions = (doctype) => {
if (!irn && !__unsaved) {
const action = () => {
if (frm.doc.__unsaved) {
frappe.throw(__('Please save the document to generate IRN.'));
}
frappe.call({
method: 'erpnext.regional.india.e_invoice.utils.get_einvoice',
args: { doctype, docname: name },

View File

@ -11,6 +11,7 @@ import json
import base64
import frappe
import traceback
import io
from frappe import _, bold
from pyqrcode import create as qrcreate
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.discount_amount = abs(item.discount_amount * item.qty)
item.unit_rate = abs(item.base_amount / item.qty)
item.gross_amount = abs(item.base_amount)
item.taxable_value = abs(item.base_amount)
item.unit_rate = abs(item.base_net_amount / item.qty)
item.gross_amount = abs(item.base_net_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 = 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:
item_tax_rate = item_tax_detail[0]
# 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:
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):
invoice_value_details = frappe._dict(dict())
invoice_value_details.base_total = abs(invoice.base_total)
invoice_value_details.invoice_discount_amt = invoice.base_discount_amount
if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount:
invoice_value_details.base_total = abs(invoice.base_total)
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.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)
@ -244,9 +251,9 @@ def update_invoice_taxes(invoice, invoice_value_details):
for tax_type in ['igst', 'cgst', 'sgst']:
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:
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
@ -430,7 +437,7 @@ class GSPConnector():
self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
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'
def get_credentials(self):
@ -473,7 +480,7 @@ class GSPConnector():
"data": json.dumps(data, indent=4) if isinstance(data, dict) else data,
"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()
def fetch_auth_token(self):
@ -486,7 +493,8 @@ class GSPConnector():
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.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:
self.log_error(res)
@ -520,7 +528,7 @@ class GSPConnector():
except Exception:
self.log_error()
self.raise_error(True)
@staticmethod
def get_gstin_details(gstin):
'''fetch and cache GSTIN details'''
@ -615,7 +623,7 @@ class GSPConnector():
except Exception:
self.log_error(data)
self.raise_error(True)
def generate_eway_bill(self, **kwargs):
args = frappe._dict(kwargs)
@ -664,7 +672,8 @@ class GSPConnector():
'cancelRsnCode': reason,
'cancelRmrk': remark
}, indent=4)
headers["username"] = headers["user_name"]
del headers["user_name"]
try:
res = self.make_request('post', self.cancel_ewaybill_url, headers, data)
if res.get('success'):
@ -757,26 +766,26 @@ class GSPConnector():
'label': _('IRN Generated')
}
self.update_invoice()
def attach_qrcode_image(self):
qrcode = self.invoice.signed_qr_code
doctype = self.invoice.doctype
docname = self.invoice.name
filename = 'QRCode_{}.png'.format(docname).replace(os.path.sep, "__")
_file = frappe.new_doc('File')
_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()
qr_image = io.BytesIO()
url = qrcreate(qrcode, error='L')
abs_file_path = os.path.abspath(_file.get_full_path())
url.png(abs_file_path, scale=2, quiet_zone=1)
url.png(qr_image, 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
def update_invoice(self):

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@
frappe.query_reports["IRS 1099"] = {
"filters": [
{
"fieldname":"company",
"fieldname": "company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
@ -13,7 +13,7 @@ frappe.query_reports["IRS 1099"] = {
"width": 80,
},
{
"fieldname":"fiscal_year",
"fieldname": "fiscal_year",
"label": __("Fiscal Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
@ -22,7 +22,7 @@ frappe.query_reports["IRS 1099"] = {
"width": 80,
},
{
"fieldname":"supplier_group",
"fieldname": "supplier_group",
"label": __("Supplier Group"),
"fieldtype": "Link",
"options": "Supplier Group",
@ -32,16 +32,16 @@ frappe.query_reports["IRS 1099"] = {
},
],
onload: function(query_report) {
onload: function (query_report) {
query_report.page.add_inner_button(__("Print IRS 1099 Forms"), () => {
build_1099_print(query_report);
});
}
};
function build_1099_print(query_report){
function build_1099_print(query_report) {
let filters = JSON.stringify(query_report.get_values());
let w = window.open('/api/method/erpnext.regional.report.irs_1099.irs_1099.irs_1099_print?' +
'&filters=' + encodeURIComponent(filters));
'&filters=' + encodeURIComponent(filters));
// w.print();
}

View File

@ -1,29 +1,34 @@
# Copyright (c) 2018, 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 _, _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
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.print_format import read_multi_pdf
from frappe.utils.jinja import render_template
IRS_1099_FORMS_FILE_EXTENSION = ".pdf"
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:
filters.setdefault('fiscal_year', get_fiscal_year(nowdate())[0])
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':
return [],[]
return [], []
data = []
columns = get_columns()
@ -34,20 +39,23 @@ def execute(filters=None):
s.tax_id as "tax_id",
SUM(gl.debit_in_account_currency) AS "payments"
FROM
`tabGL Entry` gl INNER JOIN `tabSupplier` s
`tabGL Entry` gl
INNER JOIN `tabSupplier` s
WHERE
s.name = gl.party
AND s.irs_1099 = 1
AND gl.fiscal_year = %(fiscal_year)s
AND gl.party_type = "Supplier"
AND s.irs_1099 = 1
AND gl.fiscal_year = %(fiscal_year)s
AND gl.party_type = "Supplier"
GROUP BY
gl.party
ORDER BY
gl.party DESC""", {"fiscal_year": filters.fiscal_year,
gl.party DESC
""", {
"fiscal_year": filters.fiscal_year,
"supplier_group": filters.supplier_group,
"company": filters.company}, as_dict=True)
"company": filters.company
}, as_dict=True)
return columns, data
@ -74,7 +82,6 @@ def get_columns():
"width": 120
},
{
"fieldname": "payments",
"label": _("Total Payments"),
"fieldtype": "Currency",
@ -88,23 +95,32 @@ def irs_1099_print(filters):
if not filters:
frappe._dict({
"company": frappe.db.get_default("Company"),
"fiscal_year": frappe.db.get_default("fiscal_year")})
"fiscal_year": frappe.db.get_default("Fiscal Year")
})
else:
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_tin = frappe.db.get_value("Company", filters.company, "tax_id")
columns, data = execute(filters)
template = frappe.get_doc("Print Format", "IRS 1099 Form").html
output = PdfFileWriter()
for row in data:
row["fiscal_year"] = fiscal_year
row["company"] = filters.company
row["company_tin"] = company_tin
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")
frappe._dict(row)
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.type = "download"
@ -120,36 +136,45 @@ def get_payer_address_html(company):
ORDER BY
address_type="Postal" DESC, address_type="Billing" DESC
LIMIT 1
""", {"company": company}, as_dict=True)
""", {"company": company}, as_dict=True)
address_display = ""
if address_list:
company_address = address_list[0]["name"]
return frappe.get_doc("Address", company_address).get_display()
else:
return ""
address_display = frappe.get_doc("Address", company_address).get_display()
return address_display
def get_street_address_html(party_type, party):
address_list = frappe.db.sql("""
SELECT
link.parent
FROM `tabDynamic Link` link, `tabAddress` address
WHERE link.parenttype = "Address"
AND link.link_name = %(party)s
ORDER BY address.address_type="Postal" DESC,
FROM
`tabDynamic Link` link,
`tabAddress` address
WHERE
link.parenttype = "Address"
AND link.link_name = %(party)s
ORDER BY
address.address_type="Postal" DESC,
address.address_type="Billing" DESC
LIMIT 1
""", {"party": party}, as_dict=True)
""", {"party": party}, as_dict=True)
street_address = city_state = ""
if address_list:
supplier_address = address_list[0]["parent"]
doc = frappe.get_doc("Address", supplier_address)
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:
street = doc.address_line1 + "<br>\n"
city = doc.city + ", " if doc.city else ""
city = city + doc.state + " " if doc.state else city
city = city + doc.pincode if doc.pincode else city
city += "<br>\n"
return street, city
else:
return "", ""
street_address = doc.address_line1 + "<br>\n"
city_state = doc.city + ", " if doc.city else ""
city_state = city_state + doc.state + " " if doc.state else city_state
city_state = city_state + doc.pincode if doc.pincode else city_state
city_state += "<br>\n"
return street_address, city_state

View File

@ -233,7 +233,8 @@ def get_stock_ledger_entries(filters):
from `tabItem` {item_conditions}) item
where item_code = item.name and
company = %(company)s and
posting_date <= %(to_date)s
posting_date <= %(to_date)s and
is_cancelled != 1
{sle_conditions}
order by posting_date, posting_time, sle.creation, actual_qty""" #nosec
.format(item_conditions=get_item_conditions(filters),

View File

@ -214,7 +214,10 @@ class Issue(Document):
def before_insert(self):
if frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
if frappe.flags.in_test:
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):
service_level_agreement = get_active_service_level_agreement_for(priority=priority,