From 3441d123b68497a90e5c4b6a9669c76f01a63f6f Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 11:36:06 +0530 Subject: [PATCH 01/96] feat: additional customer fileds add customer_group, price list, currency etc. rearrange and group fields disable cusotmer edit after save (temp fix) --- erpnext/healthcare/doctype/patient/patient.js | 1 + .../healthcare/doctype/patient/patient.json | 81 +++++++++++++------ erpnext/healthcare/doctype/patient/patient.py | 10 ++- 3 files changed, 65 insertions(+), 27 deletions(-) diff --git a/erpnext/healthcare/doctype/patient/patient.js b/erpnext/healthcare/doctype/patient/patient.js index d5df9567ec..a11faf5806 100644 --- a/erpnext/healthcare/doctype/patient/patient.js +++ b/erpnext/healthcare/doctype/patient/patient.js @@ -40,6 +40,7 @@ frappe.ui.form.on('Patient', { frm.add_custom_button(__('Patient Encounter'), function () { create_encounter(frm); }, 'Create'); + frm.toggle_enable(['customer'], 0); // ToDo, allow change only if no transactions booked or better, add merge option } }, onload: function (frm) { diff --git a/erpnext/healthcare/doctype/patient/patient.json b/erpnext/healthcare/doctype/patient/patient.json index 4258e4011d..2c701fbf94 100644 --- a/erpnext/healthcare/doctype/patient/patient.json +++ b/erpnext/healthcare/doctype/patient/patient.json @@ -24,13 +24,20 @@ "image", "column_break_14", "status", - "inpatient_status", "inpatient_record", - "customer", + "inpatient_status", + "report_preference", "mobile", "email", "phone", - "report_preference", + "customer_details_section", + "customer", + "customer_group", + "territory", + "column_break_24", + "default_currency", + "default_price_list", + "language", "personal_and_social_history", "occupation", "column_break_25", @@ -52,9 +59,7 @@ "surrounding_factors", "other_risk_factors", "more_info", - "patient_details", - "ac_sb", - "default_currency" + "patient_details" ], "fields": [ { @@ -156,6 +161,7 @@ "fieldname": "customer", "fieldtype": "Link", "ignore_user_permissions": 1, + "in_preview": 1, "label": "Customer", "options": "Customer" }, @@ -171,7 +177,8 @@ "fieldtype": "Data", "in_list_view": 1, "in_standard_filter": 1, - "label": "Mobile" + "label": "Mobile", + "options": "Phone" }, { "bold": 1, @@ -186,7 +193,8 @@ "fieldname": "phone", "fieldtype": "Data", "in_filter": 1, - "label": "Phone" + "label": "Phone", + "options": "Phone" }, { "collapsible": 1, @@ -268,25 +276,25 @@ "fieldname": "tobacco_past_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Tobacco Consumption Habbits (Past)" + "label": "Tobacco Consumption (Past)" }, { "fieldname": "tobacco_current_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Tobacco Consumption Habbits (Present)" + "label": "Tobacco Consumption (Present)" }, { "fieldname": "alcohol_past_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Alcohol Consumption Habbits (Past)" + "label": "Alcohol Consumption (Past)" }, { "fieldname": "alcohol_current_use", "fieldtype": "Data", "ignore_user_permissions": 1, - "label": "Alcohol Consumption Habbits (Present)" + "label": "Alcohol Consumption (Present)" }, { "fieldname": "column_break_32", @@ -320,20 +328,11 @@ "ignore_xss_filter": 1, "label": "Patient Details" }, - { - "collapsible": 1, - "fieldname": "ac_sb", - "fieldtype": "Section Break", - "label": "Account Details" - }, { "fieldname": "default_currency", "fieldtype": "Link", - "hidden": 1, - "ignore_xss_filter": 1, - "label": "Default Currency", - "options": "Currency", - "print_hide": 1 + "label": "Billing Currency", + "options": "Currency" }, { "fieldname": "last_name", @@ -351,13 +350,47 @@ "fieldname": "middle_name", "fieldtype": "Data", "label": "Middle Name (optional)" + }, + { + "collapsible": 1, + "fieldname": "customer_details_section", + "fieldtype": "Section Break", + "label": "Customer Details" + }, + { + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "options": "Customer Group" + }, + { + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "options": "Territory" + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break" + }, + { + "fieldname": "default_price_list", + "fieldtype": "Link", + "label": "Default Price List", + "options": "Price List" + }, + { + "fieldname": "language", + "fieldtype": "Link", + "label": "Print Language", + "options": "Language" } ], "icon": "fa fa-user", "image_field": "image", "links": [], "max_attachments": 50, - "modified": "2020-04-06 12:55:30.807744", + "modified": "2020-04-11 14:53:48.767245", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient", diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py index e304a0bbc3..f83fac03a6 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -26,6 +26,7 @@ class Patient(Document): frappe.db.set_value('Patient', self.name, 'status', 'Disabled') else: send_registration_sms(self) + self.reload() # self.notify_update() def set_full_name(self): if self.last_name: @@ -86,8 +87,8 @@ class Patient(Document): return {'invoice': sales_invoice.name} def create_customer(doc): - customer_group = frappe.db.get_single_value('Selling Settings', 'customer_group') - territory = frappe.db.get_single_value('Selling Settings', 'territory') + customer_group = doc.customer_group or frappe.db.get_single_value('Selling Settings', 'customer_group') + territory = doc.territory or frappe.db.get_single_value('Selling Settings', 'territory') if not (customer_group and territory): customer_group = get_root_of('Customer Group') territory = get_root_of('Territory') @@ -98,7 +99,10 @@ def create_customer(doc): 'customer_name': doc.patient_name, 'customer_group': customer_group, 'territory' : territory, - 'customer_type': 'Individual' + 'customer_type': 'Individual', + 'default_currency': doc.default_currency, + 'default_price_ist': doc.default_price_list, + 'language': doc.language }).insert(ignore_permissions=True, ignore_mandatory=True) frappe.db.set_value('Patient', doc.name, 'customer', customer.name) From 9a6bf4e0c97c1b28ae663ab26f5363d25c6bedde Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 11:56:09 +0530 Subject: [PATCH 02/96] fix: vitals added naimg series, minor field rearrangements --- .../healthcare_practitioner.json | 10 +++++++--- .../sample_collection/sample_collection.json | 8 ++++---- .../doctype/vital_signs/vital_signs.json | 20 ++++++++++++++----- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json index fd5b6e12f6..cb747f95ef 100644 --- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json +++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json @@ -1,6 +1,5 @@ { "actions": [], - "allow_copy": 1, "allow_import": 1, "allow_rename": 1, "autoname": "naming_series:", @@ -51,17 +50,20 @@ "fieldname": "first_name", "fieldtype": "Data", "label": "First Name", + "no_copy": 1, "reqd": 1 }, { "fieldname": "middle_name", "fieldtype": "Data", - "label": "Middle Name (Optional)" + "label": "Middle Name (Optional)", + "no_copy": 1 }, { "fieldname": "last_name", "fieldtype": "Data", - "label": "Last Name" + "label": "Last Name", + "no_copy": 1 }, { "fieldname": "image", @@ -226,6 +228,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Full Name", + "no_copy": 1, "read_only": 1, "search_index": 1 }, @@ -233,6 +236,7 @@ "fieldname": "naming_series", "fieldtype": "Select", "label": "Series", + "no_copy": 1, "options": "HLC-PRAC-.YYYY.-", "report_hide": 1, "set_only_once": 1 diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.json b/erpnext/healthcare/doctype/sample_collection/sample_collection.json index 39cead8862..c352287faf 100644 --- a/erpnext/healthcare/doctype/sample_collection/sample_collection.json +++ b/erpnext/healthcare/doctype/sample_collection/sample_collection.json @@ -9,14 +9,14 @@ "document_type": "Document", "engine": "InnoDB", "field_order": [ - "inpatient_record", "naming_series", - "invoiced", "patient", - "column_break_4", "patient_age", "patient_sex", + "column_break_4", + "inpatient_record", "company", + "invoiced", "section_break_6", "sample", "sample_uom", @@ -167,7 +167,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-03-25 16:55:52.376834", + "modified": "2020-04-04 19:17:02.707203", "modified_by": "Administrator", "module": "Healthcare", "name": "Sample Collection", diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.json b/erpnext/healthcare/doctype/vital_signs/vital_signs.json index 75726dbe07..fdacda6277 100644 --- a/erpnext/healthcare/doctype/vital_signs/vital_signs.json +++ b/erpnext/healthcare/doctype/vital_signs/vital_signs.json @@ -8,11 +8,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "inpatient_record", "patient", "patient_name", - "appointment", - "encounter", "column_break_2", "signs_date", "signs_time", @@ -34,6 +31,11 @@ "bmi", "column_break_14", "nutrition_note", + "sb_references", + "inpatient_record", + "appointment", + "encounter", + "column_break_28", "company", "amended_from" ], @@ -217,7 +219,6 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, "label": "Company", "options": "Company" }, @@ -229,11 +230,20 @@ "options": "Vital Signs", "print_hide": 1, "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "sb_references", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_28", + "fieldtype": "Column Break" } ], "is_submittable": 1, "links": [], - "modified": "2020-03-04 17:19:29.549889", + "modified": "2020-04-03 23:06:29.786184", "modified_by": "Administrator", "module": "Healthcare", "name": "Vital Signs", From 174dcee155c033fcd01ffa5d93e8cf4f5a20e767 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 12:09:06 +0530 Subject: [PATCH 03/96] fix: set company while creating encounter, fields rearranged --- .../patient_appointment.js | 3 +- .../patient_appointment.json | 43 +++++++++++++------ .../patient_appointment.py | 3 +- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index efa6b249b3..de8dc0e90a 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -193,7 +193,6 @@ let check_and_set_availability = function(frm) { d.hide(); frm.enable_save(); frm.save(); - frm.enable_save(); d.get_primary_btn().attr('disabled', true); } }); @@ -400,6 +399,7 @@ let create_vital_signs = function(frm) { frappe.route_options = { 'patient': frm.doc.patient, 'appointment': frm.doc.name, + 'company': frm.doc.company }; frappe.new_doc('Vital Signs'); }; @@ -432,6 +432,7 @@ frappe.ui.form.on('Patient Appointment', 'practitioner', function(frm) { callback: function (data) { frappe.model.set_value(frm.doctype, frm.docname, 'department', data.message.department); frappe.model.set_value(frm.doctype, frm.docname, 'paid_amount', data.message.op_consulting_charge); + frappe.model.set_value(frm.doctype, frm.docname, 'billing_item', data.message.op_consulting_charge_item); } }); } diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index 7f9a671d47..81f7597563 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -14,13 +14,16 @@ "patient_name", "patient_sex", "patient_age", - "inpatient_record", "column_break_1", - "status", - "procedure_template", - "get_procedure_from_encounter", - "procedure_prescription", + "inpatient_record", + "company", "service_unit", + "status", + "section_break_11", + "get_procedure_from_encounter", + "column_break_13", + "procedure_template", + "procedure_prescription", "section_break_12", "practitioner", "department", @@ -32,9 +35,9 @@ "duration", "section_break_16", "mode_of_payment", - "paid_amount", - "company", + "billing_item", "column_break_2", + "paid_amount", "invoiced", "ref_sales_invoice", "section_break_3", @@ -114,7 +117,8 @@ "label": "Procedure Prescription", "no_copy": 1, "options": "Procedure Prescription", - "print_hide": 1 + "print_hide": 1, + "read_only": 1 }, { "fieldname": "service_unit", @@ -140,6 +144,7 @@ "set_only_once": 1 }, { + "fetch_from": "practitioner.department", "fieldname": "department", "fieldtype": "Link", "ignore_user_permissions": 1, @@ -234,12 +239,9 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, "label": "Company", "no_copy": 1, - "options": "Company", - "print_hide": 1, - "report_hide": 1 + "options": "Company" }, { "collapsible": 1, @@ -282,10 +284,25 @@ "label": "Series", "options": "HLC-APP-.YYYY.-", "set_only_once": 1 + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "fieldname": "billing_item", + "fieldtype": "Link", + "label": "Billing Item", + "options": "Item", + "read_only": 1 } ], "links": [], - "modified": "2020-03-27 11:27:33.773195", + "modified": "2020-04-07 11:16:34.981240", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Appointment", diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index a2d9d0240f..3786bf2861 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -340,7 +340,8 @@ def make_encounter(source_name, target_doc=None): ['medical_department', 'department'], ['patient_sex', 'patient_sex'], ['encounter_date', 'appointment_date'], - ['invoiced', 'invoiced'] + ['invoiced', 'invoiced'], + ['company', 'company'] ] } }, target_doc) From d2224626c7c94c5a579606091efbea1ab52b591b Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 12:11:59 +0530 Subject: [PATCH 04/96] fix: company when creatign procedure, practitioner name added --- .../patient_encounter/patient_encounter.js | 9 ++++--- .../patient_encounter/patient_encounter.json | 24 +++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js index 83c5d2be9c..0e34164d07 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js @@ -110,7 +110,8 @@ frappe.ui.form.on('Patient Encounter', { 'patient':data.message.patient, 'type': data.message.appointment_type, 'practitioner': data.message.practitioner, - 'invoiced': data.message.invoiced + 'invoiced': data.message.invoiced, + 'company': data.message.company }; frm.set_value(values); } @@ -209,7 +210,8 @@ let create_vital_signs = function (frm) { frappe.route_options = { 'patient': frm.doc.patient, 'appointment': frm.doc.appointment, - 'encounter': frm.doc.name + 'encounter': frm.doc.name, + 'company': frm.doc.company }; frappe.new_doc('Vital Signs'); }; @@ -220,7 +222,8 @@ let create_procedure = function (frm) { } frappe.route_options = { 'patient': frm.doc.patient, - 'medical_department': frm.doc.medical_department + 'medical_department': frm.doc.medical_department, + 'company': frm.doc.company }; frappe.new_doc('Clinical Procedure'); }; diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json index d00e7bc7dd..935935e0c0 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json @@ -17,9 +17,9 @@ "patient_name", "patient_sex", "patient_age", - "company", "column_break_6", "practitioner", + "practitioner_name", "medical_department", "encounter_date", "encounter_time", @@ -43,6 +43,8 @@ "sb_procedures", "procedure_prescription", "encounter_comment", + "sb_refs", + "company", "amended_from" ], "fields": [ @@ -80,7 +82,6 @@ "fieldname": "patient", "fieldtype": "Link", "ignore_user_permissions": 1, - "in_list_view": 1, "in_standard_filter": 1, "label": "Patient", "options": "Patient", @@ -110,7 +111,6 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, "label": "Company", "options": "Company" }, @@ -121,7 +121,6 @@ { "fieldname": "practitioner", "fieldtype": "Link", - "in_list_view": 1, "in_standard_filter": 1, "label": "Healthcare Practitioner", "options": "Healthcare Practitioner", @@ -272,7 +271,6 @@ "fieldname": "medical_department", "fieldtype": "Link", "ignore_user_permissions": 1, - "in_list_view": 1, "in_standard_filter": 1, "label": "Department", "options": "Medical Department", @@ -287,11 +285,23 @@ { "fieldname": "column_break_17", "fieldtype": "Column Break" + }, + { + "fieldname": "sb_refs", + "fieldtype": "Section Break" + }, + { + "fetch_from": "practitioner.practitioner_name", + "fieldname": "practitioner_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Practitioner Name", + "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-02-27 12:42:21.751964", + "modified": "2020-04-03 23:06:16.348846", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Encounter", @@ -318,7 +328,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "title_field": "patient", + "title_field": "patient_name", "track_changes": 1, "track_seen": 1 } \ No newline at end of file From 5f923fce2de89abfb3f517573c190e5ef071aaff Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:30:00 +0530 Subject: [PATCH 05/96] feat: multi company support --- .../healthcare/doctype/lab_test/lab_test.js | 9 ++++---- .../healthcare/doctype/lab_test/lab_test.json | 15 ++++++------- .../healthcare/doctype/lab_test/lab_test.py | 21 +++++++++++-------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.js b/erpnext/healthcare/doctype/lab_test/lab_test.js index 5b3f4c705a..e7e44dcb48 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.js +++ b/erpnext/healthcare/doctype/lab_test/lab_test.js @@ -137,7 +137,7 @@ var get_lab_test_prescribed = function(frm){ }); } else{ - frappe.msgprint(__("Please select Patient to get Lab Tests")); + frappe.msgprint(__("Please select a Patient to get Lab Orders")); } }; @@ -180,9 +180,10 @@ var show_lab_tests = function(frm, result){ return false; }); }); - if(!result){ - var msg = "There are no Lab Test prescribed for "+frm.doc.patient; - $(repl('
%(msg)s
', {msg: msg})).appendTo(html_field); + if(!result.length){ + var msg = "No Lab Orders found for patient " + frm.doc.patient_name + ""; + html_field.empty(); + $(repl('
%(msg)s
', {msg: msg})).appendTo(html_field); } d.show(); }; diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.json b/erpnext/healthcare/doctype/lab_test/lab_test.json index ccbc24b3fb..17dc1edd8c 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.json +++ b/erpnext/healthcare/doctype/lab_test/lab_test.json @@ -9,18 +9,18 @@ "document_type": "Document", "engine": "InnoDB", "field_order": [ - "inpatient_record", "naming_series", - "invoiced", "patient", "patient_name", "patient_age", "patient_sex", - "practitioner", + "report_preference", "email", "mobile", - "company", + "practitioner", "c_b", + "inpatient_record", + "company", "department", "status", "submitted_date", @@ -31,7 +31,7 @@ "employee_name", "employee_designation", "user", - "report_preference", + "invoiced", "sb_first", "lab_test_name", "column_break_26", @@ -153,7 +153,7 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, + "in_standard_filter": 1, "label": "Company", "options": "Company", "print_hide": 1, @@ -168,6 +168,7 @@ "fieldname": "department", "fieldtype": "Link", "ignore_user_permissions": 1, + "in_standard_filter": 1, "label": "Department", "options": "Medical Department", "search_index": 1 @@ -427,7 +428,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-03-23 19:37:06.617764", + "modified": "2020-04-04 19:16:29.131168", "modified_by": "Administrator", "module": "Healthcare", "name": "Lab Test", diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.py b/erpnext/healthcare/doctype/lab_test/lab_test.py index 4e4015d2f0..e49429ea62 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.py +++ b/erpnext/healthcare/doctype/lab_test/lab_test.py @@ -69,9 +69,9 @@ def create_multiple(doctype, docname): lab_test_created = create_lab_test_from_encounter(docname) if lab_test_created: - frappe.msgprint(_("Lab Test(s) "+lab_test_created+" created.")) + frappe.msgprint(_("Lab Test(s) " + lab_test_created + " created.")) else: - frappe.msgprint(_("No Lab Test created")) + frappe.msgprint(_("No Lab Tests created")) def create_lab_test_from_encounter(encounter_id): lab_test_created = False @@ -87,7 +87,7 @@ def create_lab_test_from_encounter(encounter_id): for lab_test_id in lab_test_ids: template = get_lab_test_template(lab_test_id[1]) if template: - lab_test = create_lab_test_doc(lab_test_id[2], encounter.practitioner, patient, template) + lab_test = create_lab_test_doc(lab_test_id[2], encounter.practitioner, patient, template, encounter.company) lab_test.save(ignore_permissions = True) frappe.db.set_value("Lab Prescription", lab_test_id[0], "lab_test_created", 1) if not lab_test_created: @@ -111,7 +111,7 @@ def create_lab_test_from_invoice(invoice_name): if lab_test_created != 1: template = get_lab_test_template(item.item_code) if template: - lab_test = create_lab_test_doc(True, invoice.ref_practitioner, patient, template) + lab_test = create_lab_test_doc(True, invoice.ref_practitioner, patient, template, company, invoice.company) if item.reference_dt == "Lab Prescription": lab_test.prescription = item.reference_dn lab_test.save(ignore_permissions = True) @@ -121,7 +121,7 @@ def create_lab_test_from_invoice(invoice_name): if not lab_tests_created: lab_tests_created = lab_test.name else: - lab_tests_created += ", "+lab_test.name + lab_tests_created += ", " + lab_test.name return lab_tests_created def get_lab_test_template(item): @@ -141,7 +141,7 @@ def check_template_exists(item): return template_exists return False -def create_lab_test_doc(invoiced, practitioner, patient, template): +def create_lab_test_doc(invoiced, practitioner, patient, template, company): lab_test = frappe.new_doc("Lab Test") lab_test.invoiced = invoiced lab_test.practitioner = practitioner @@ -150,11 +150,12 @@ def create_lab_test_doc(invoiced, practitioner, patient, template): lab_test.patient_sex = patient.sex lab_test.email = patient.email lab_test.mobile = patient.mobile + lab_test.report_preference = patient.report_preference lab_test.department = template.department lab_test.template = template.name lab_test.lab_test_group = template.lab_test_group lab_test.result_date = getdate() - lab_test.report_preference = patient.report_preference + lab_test.company = company return lab_test def create_normals(template, lab_test): @@ -190,7 +191,7 @@ def create_specials(template, lab_test): special.require_result_value = 1 special.template = template.name -def create_sample_doc(template, patient, invoice): +def create_sample_doc(template, patient, invoice, company = None): if template.sample: sample_exists = frappe.db.exists({ "doctype": "Sample Collection", @@ -221,6 +222,8 @@ def create_sample_doc(template, patient, invoice): sample_collection.sample = template.sample sample_collection.sample_uom = template.sample_uom sample_collection.sample_qty = template.sample_qty + sample_collection.company = company + if(template.sample_details): sample_collection.sample_details = "Test :" + (template.get("lab_test_name") or template.get("template")) +"\n"+"Collection Detials:\n\t"+template.sample_details sample_collection.save(ignore_permissions=True) @@ -229,7 +232,7 @@ def create_sample_doc(template, patient, invoice): def create_sample_collection(lab_test, template, patient, invoice): if(frappe.db.get_value("Healthcare Settings", None, "create_sample_collection_for_lab_test") == "1"): - sample_collection = create_sample_doc(template, patient, invoice) + sample_collection = create_sample_doc(template, patient, invoice, lab_test.company) if(sample_collection): lab_test.sample = sample_collection.name return lab_test From 0686c3d729ab8046c1a79b233bb6b7d9ece78ba7 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:32:35 +0530 Subject: [PATCH 06/96] feat: multi company support --- .../clinical_procedure/clinical_procedure.js | 35 ++++++++--- .../clinical_procedure.json | 58 ++++++++++++++++--- .../clinical_procedure/clinical_procedure.py | 2 +- 3 files changed, 79 insertions(+), 16 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js index 5f36bdd95c..213fa3de1f 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js @@ -175,16 +175,37 @@ frappe.ui.form.on('Clinical Procedure', { name: frm.doc.appointment }, callback: function(data) { - frm.set_value('patient', data.message.patient); - frm.set_value('procedure_template', data.message.procedure_template); - frm.set_value('medical_department', data.message.department); - frm.set_value('start_date', data.message.appointment_date); - frm.set_value('start_time', data.message.appointment_time); - frm.set_value('notes', data.message.notes); - frm.set_value('service_unit', data.message.service_unit); + let values = { + 'patient':data.message.patient, + 'procedure_template': data.message.procedure_template, + 'medical_department': data.message.department, + 'start_date': data.message.appointment_date, + 'start_time': data.message.appointment_time, + 'notes': data.message.notes, + 'service_unit': data.message.service_unit, + 'company': data.message.company + } + frm.set_value(values); } }); } + else{ + let values = { + 'patient': '', + 'patient_name': '', + 'patient_sex': '', + 'patient_age': '', + 'medical_department': '', + 'procedure_template': '', + 'start_date': '', + 'start_time': '', + 'notes': '', + 'service_unit': '', + 'inpatient_record': '' + // 'inpatient_status': '' + }; + frm.set_value(values); + } }, procedure_template: function(frm) { diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json index 3c936bbf27..59a87eccde 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json @@ -7,16 +7,18 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "inpatient_record", "naming_series", - "procedure_template", "appointment", + "column_break_30", + "procedure_template", + "section_break_6", "patient", + "patient_name", "patient_sex", "patient_age", - "prescription", "medical_department", "practitioner", + "practitioner_name", "column_break_7", "status", "service_unit", @@ -26,7 +28,6 @@ "sample", "invoiced", "notes", - "company", "consumables_section", "consume_stock", "items", @@ -36,6 +37,11 @@ "consumable_total_amount", "column_break_27", "consumption_details", + "sb_refs", + "inpatient_record", + "company", + "column_break_34", + "prescription", "amended_from" ], "fields": [ @@ -56,7 +62,7 @@ { "fieldname": "appointment", "fieldtype": "Link", - "in_list_view": 1, + "in_standard_filter": 1, "label": "Appointment", "options": "Patient Appointment" }, @@ -64,7 +70,7 @@ "fetch_from": "inpatient_record.patient", "fieldname": "patient", "fieldtype": "Link", - "in_list_view": 1, + "in_standard_filter": 1, "label": "Patient", "options": "Patient", "reqd": 1 @@ -88,17 +94,20 @@ "fieldtype": "Link", "hidden": 1, "label": "Procedure Prescription", - "options": "Procedure Prescription" + "options": "Procedure Prescription", + "read_only": 1 }, { "fieldname": "medical_department", "fieldtype": "Link", + "in_standard_filter": 1, "label": "Medical Department", "options": "Medical Department" }, { "fieldname": "practitioner", "fieldtype": "Link", + "in_standard_filter": 1, "label": "Healthcare Practitioner", "options": "Healthcare Practitioner" }, @@ -237,11 +246,43 @@ { "fieldname": "section_break_24", "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_30", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "collapsible": 1, + "fieldname": "sb_refs", + "fieldtype": "Section Break" + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "fetch_from": "practitioner.practitioner_name", + "fieldname": "practitioner_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Practitioner Name", + "read_only": 1 + }, + { + "fieldname": "column_break_34", + "fieldtype": "Column Break" } ], "is_submittable": 1, "links": [], - "modified": "2020-03-02 11:44:27.970651", + "modified": "2020-04-03 23:06:04.009856", "modified_by": "Administrator", "module": "Healthcare", "name": "Clinical Procedure", @@ -263,5 +304,6 @@ "restrict_to_domain": "Healthcare", "sort_field": "modified", "sort_order": "DESC", + "title_field": "patient_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py index db3afc8807..56617e5258 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py @@ -37,7 +37,7 @@ class ClinicalProcedure(Document): template = frappe.get_doc('Clinical Procedure Template', self.procedure_template) if template.sample: patient = frappe.get_doc('Patient', self.patient) - sample_collection = create_sample_doc(template, patient, None) + sample_collection = create_sample_doc(template, patient, None, self.company) frappe.db.set_value('Clinical Procedure', self.name, 'sample', sample_collection.name) self.reload() From f90ea8d622c6ffeed1b8d9c02d2cc166f0fc236e Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:34:28 +0530 Subject: [PATCH 07/96] company field added --- .../inpatient_record/inpatient_record.json | 1165 ++++------------- 1 file changed, 220 insertions(+), 945 deletions(-) diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json index 92c11fbb4d..c1b516d536 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json @@ -1,980 +1,255 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2018-07-11 17:48:51.404139", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "naming_series:", + "creation": "2018-07-11 17:48:51.404139", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "section_break_1", + "naming_series", + "patient", + "patient_name", + "gender", + "blood_group", + "dob", + "mobile", + "email", + "phone", + "column_break_8", + "company", + "status", + "scheduled_date", + "admitted_datetime", + "expected_discharge", + "discharge_date", + "references", + "cb_admission", + "admission_practitioner", + "admission_encounter", + "cb_discharge", + "discharge_practitioner", + "discharge_encounter", + "sb_inpatient_occupancy", + "inpatient_occupancies", + "btn_transfer", + "sb_discharge_note", + "discharge_note" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_1", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_1", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 0, - "options": "HLC-INP-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "hidden": 1, + "label": "Series", + "options": "HLC-INP-.YYYY.-" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "patient", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Patient", - "length": 0, - "no_copy": 0, - "options": "Patient", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "patient", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.patient_name", - "fieldname": "patient_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Patient Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.sex", - "fieldname": "gender", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Gender", - "length": 0, - "no_copy": 0, - "options": "Gender", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.sex", + "fieldname": "gender", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.blood_group", - "fieldname": "blood_group", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Blood Group", - "length": 0, - "no_copy": 0, - "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.blood_group", + "fieldname": "blood_group", + "fieldtype": "Select", + "label": "Blood Group", + "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "dob", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Date of birth", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.dob", + "fieldname": "dob", + "fieldtype": "Date", + "label": "Date of birth", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.mobile", - "fieldname": "mobile", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Mobile", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.mobile", + "fieldname": "mobile", + "fieldtype": "Data", + "label": "Mobile", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.email", - "fieldname": "email", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Email", - "length": 0, - "no_copy": 0, - "options": "Email", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.email", + "fieldname": "email", + "fieldtype": "Data", + "label": "Email", + "options": "Email", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.phone", - "fieldname": "phone", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Phone", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.phone", + "fieldname": "phone", + "fieldtype": "Data", + "label": "Phone", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_8", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 0, - "options": "Admission Scheduled\nAdmitted\nDischarge Scheduled\nDischarged", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Admission Scheduled\nAdmitted\nDischarge Scheduled\nDischarged", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "scheduled_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Admission Schedule Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Today", + "fieldname": "scheduled_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Admission Schedule Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "admitted_datetime", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Admitted Datetime", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Today", + "fieldname": "admitted_datetime", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Admitted Datetime" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "expected_discharge", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Expected Discharge", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "expected_discharge", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Expected Discharge" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Discharge Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "discharge_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Discharge Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "references", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "References", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "collapsible": 1, + "fieldname": "references", + "fieldtype": "Section Break", + "label": "References" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cb_admission", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Admission", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "cb_admission", + "fieldtype": "Column Break", + "label": "Admission" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "admission_practitioner", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Healthcare Practitioner", - "length": 0, - "no_copy": 0, - "options": "Healthcare Practitioner", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "admission_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "admission_encounter", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Patient Encounter", - "length": 0, - "no_copy": 0, - "options": "Patient Encounter", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "admission_encounter", + "fieldtype": "Link", + "label": "Patient Encounter", + "options": "Patient Encounter", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cb_discharge", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Discharge", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "cb_discharge", + "fieldtype": "Column Break", + "label": "Discharge" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_practitioner", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Healthcare Practitioner", - "length": 0, - "no_copy": 0, - "options": "Healthcare Practitioner", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "discharge_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_encounter", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Patient Encounter", - "length": 0, - "no_copy": 0, - "options": "Patient Encounter", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "discharge_encounter", + "fieldtype": "Link", + "label": "Patient Encounter", + "options": "Patient Encounter", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sb_inpatient_occupancy", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Inpatient Occupancy", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "sb_inpatient_occupancy", + "fieldtype": "Section Break", + "label": "Inpatient Occupancy" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "inpatient_occupancies", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "options": "Inpatient Occupancy", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "inpatient_occupancies", + "fieldtype": "Table", + "options": "Inpatient Occupancy", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "btn_transfer", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Transfer", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "btn_transfer", + "fieldtype": "Button", + "label": "Transfer" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.status != \"Admission Scheduled\"", - "fieldname": "sb_discharge_note", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Discharge Note", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.status != \"Admission Scheduled\"", + "fieldname": "sb_discharge_note", + "fieldtype": "Section Break", + "label": "Discharge Note" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_note", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "discharge_note", + "fieldtype": "Text Editor" + }, + { + "fetch_from": "admission_encounter.company", + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 14:44:43.168245", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Inpatient Record", - "name_case": "", - "owner": "Administrator", + ], + "links": [], + "modified": "2020-04-07 13:13:39.351977", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Inpatient Record", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "search_fields": "patient", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "patient", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} + ], + "restrict_to_domain": "Healthcare", + "search_fields": "patient", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "patient", + "track_changes": 1 +} \ No newline at end of file From c01fb2a4d8752b663828513fe722d4d62414b0da Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:42:03 +0530 Subject: [PATCH 08/96] feat: multi-company billing sales onvoice - filter get items based on company utils - company filters in all get item helper methods utils - refactor appointemnt items --- .../doctype/sales_invoice/sales_invoice.js | 7 +- erpnext/healthcare/utils.py | 96 ++++++++++--------- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 6f78db2ccc..7741842fca 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -919,7 +919,7 @@ var get_healthcare_services_to_invoice = function(frm) { if(patient && patient!=selected_patient){ selected_patient = patient; var method = "erpnext.healthcare.utils.get_healthcare_services_to_invoice"; - var args = {patient: patient}; + var args = {patient: patient, company: frm.doc.company}; var columns = (["service", "reference_name", "reference_type"]); get_healthcare_items(frm, true, $results, $placeholder, method, args, columns); } @@ -1063,7 +1063,10 @@ var get_drugs_to_invoice = function(frm) { description:'Quantity will be calculated only for items which has "Nos" as UoM. You may change as required for each invoice item.', get_query: function(doc) { return { - filters: { patient: dialog.get_value("patient"), docstatus: 1 } + filters: { patient: dialog.get_value("patient"), + company: frm.doc.company, + docstatus: 1 + } }; } }, diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py index 246242ad84..e9cc597e81 100644 --- a/erpnext/healthcare/utils.py +++ b/erpnext/healthcare/utils.py @@ -3,82 +3,84 @@ # For license information, please see license.txt from __future__ import unicode_literals +import math +import json import frappe from frappe import _ -import math 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 from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple @frappe.whitelist() -def get_healthcare_services_to_invoice(patient): +def get_healthcare_services_to_invoice(patient, company): patient = frappe.get_doc('Patient', patient) + items_to_invoice = [] if patient: validate_customer_created(patient) - items_to_invoice = [] - patient_appointments = frappe.get_list( - 'Patient Appointment', - fields='*', - filters={'patient': patient.name, 'invoiced': 0}, - order_by='appointment_date' - ) - if patient_appointments: - items_to_invoice = get_fee_validity(patient_appointments) + # Customer validated, build a list of billable services + items_to_invoice += get_appointments_to_invoice(patient, company) + items_to_invoice += get_encounters_to_invoice(patient, company) + items_to_invoice += get_lab_tests_to_invoice(patient, company) + items_to_invoice += get_clinical_procedures_to_invoice(patient, company) + items_to_invoice += get_inpatient_services_to_invoice(patient, company) - encounters = get_encounters_to_invoice(patient) - lab_tests = get_lab_tests_to_invoice(patient) - clinical_procedures = get_clinical_procedures_to_invoice(patient) - inpatient_services = get_inpatient_services_to_invoice(patient) - - items_to_invoice += encounters + lab_tests + clinical_procedures + inpatient_services return items_to_invoice + def validate_customer_created(patient): if not frappe.db.get_value('Patient', patient.name, 'customer'): msg = _("Please set a Customer linked to the Patient") msg += " {0}".format(patient.name) frappe.throw(msg, title=_('Customer Not Found')) -def get_fee_validity(patient_appointments): - if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'): - return - items_to_invoice = [] +def get_appointments_to_invoice(patient, company): + appointments_to_invoice = [] + patient_appointments = frappe.get_list( + 'Patient Appointment', + fields = '*', + filters = {'patient': patient.name, 'company': company, 'invoiced': 0}, + order_by = 'appointment_date' + ) + for appointment in patient_appointments: + # Procedure Appointments if appointment.procedure_template: if frappe.db.get_value('Clinical Procedure Template', appointment.procedure_template, 'is_billable'): - items_to_invoice.append({ + appointments_to_invoice.append({ 'reference_type': 'Patient Appointment', 'reference_name': appointment.name, 'service': appointment.procedure_template }) + # Consultation Appointments, should check fee validity else: - fee_validity = frappe.db.exists('Fee Validity Reference', {'appointment': appointment.name}) - if not fee_validity: - practitioner_charge = 0 - income_account = None - service_item = None - if appointment.practitioner: - service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment) - income_account = get_income_account(appointment.practitioner, appointment.company) - items_to_invoice.append({ - 'reference_type': 'Patient Appointment', - 'reference_name': appointment.name, - 'service': service_item, - 'rate': practitioner_charge, - 'income_account': income_account - }) + if frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups') and \ + frappe.db.exists('Fee Validity Reference', {'appointment': appointment.name}): + continue # Skip invoicing, fee validty present + practitioner_charge = 0 + income_account = None + service_item = None + if appointment.practitioner: + service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment) + income_account = get_income_account(appointment.practitioner, appointment.company) + appointments_to_invoice.append({ + 'reference_type': 'Patient Appointment', + 'reference_name': appointment.name, + 'service': service_item, + 'rate': practitioner_charge, + 'income_account': income_account + }) - return items_to_invoice + return appointments_to_invoice -def get_encounters_to_invoice(patient): +def get_encounters_to_invoice(patient, company): encounters_to_invoice = [] encounters = frappe.get_list( 'Patient Encounter', fields=['*'], - filters={'patient': patient.name, 'invoiced': False, 'docstatus': 1} + filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1} ) if encounters: for encounter in encounters: @@ -101,12 +103,12 @@ def get_encounters_to_invoice(patient): return encounters_to_invoice -def get_lab_tests_to_invoice(patient): +def get_lab_tests_to_invoice(patient, company): lab_tests_to_invoice = [] lab_tests = frappe.get_list( 'Lab Test', fields=['name', 'template'], - filters={'patient': patient.name, 'invoiced': False, 'docstatus': 1} + filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1} ) for lab_test in lab_tests: item, is_billable = frappe.get_cached_value('Lab Test Template', lab_test.lab_test_code, ['item', 'is_billable']) @@ -142,12 +144,12 @@ def get_lab_tests_to_invoice(patient): return lab_tests_to_invoice -def get_clinical_procedures_to_invoice(patient): +def get_clinical_procedures_to_invoice(patient, company): clinical_procedures_to_invoice = [] procedures = frappe.get_list( 'Clinical Procedure', fields='*', - filters={'patient': patient.name, 'invoiced': False} + filters={'patient': patient.name, 'company': company, 'invoiced': False} ) for procedure in procedures: if not procedure.appointment: @@ -203,7 +205,7 @@ def get_clinical_procedures_to_invoice(patient): return clinical_procedures_to_invoice -def get_inpatient_services_to_invoice(patient): +def get_inpatient_services_to_invoice(patient, company): services_to_invoice = [] inpatient_services = frappe.db.sql( ''' @@ -213,10 +215,11 @@ def get_inpatient_services_to_invoice(patient): `tabInpatient Record` ip, `tabInpatient Occupancy` io WHERE ip.patient=%s + and ip.company=%s and io.parent=ip.name and io.left=1 and io.invoiced=0 - ''', (patient.name), as_dict=1) + ''', (patient.name, company), as_dict=1) for inpatient_occupancy in inpatient_services: service_unit_type = frappe.db.get_value('Healthcare Service Unit', inpatient_occupancy.service_unit, 'service_unit_type') @@ -376,6 +379,7 @@ def check_fee_validity(appointment): def manage_fee_validity(appointment): fee_validity = check_fee_validity(appointment) + if fee_validity: if appointment.status == 'Cancelled' and fee_validity.visited > 0: fee_validity.visited -= 1 From 407b33d7e09be346d05ec938dbb3d10f8a46d93f Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 11:36:06 +0530 Subject: [PATCH 09/96] feat: additional customer fileds add customer_group, price list, currency etc. rearrange and group fields disable cusotmer edit after save (temp fix) --- erpnext/healthcare/doctype/patient/patient.js | 1 + .../healthcare/doctype/patient/patient.json | 81 +++++++++++++------ erpnext/healthcare/doctype/patient/patient.py | 10 ++- 3 files changed, 65 insertions(+), 27 deletions(-) diff --git a/erpnext/healthcare/doctype/patient/patient.js b/erpnext/healthcare/doctype/patient/patient.js index d5df9567ec..a11faf5806 100644 --- a/erpnext/healthcare/doctype/patient/patient.js +++ b/erpnext/healthcare/doctype/patient/patient.js @@ -40,6 +40,7 @@ frappe.ui.form.on('Patient', { frm.add_custom_button(__('Patient Encounter'), function () { create_encounter(frm); }, 'Create'); + frm.toggle_enable(['customer'], 0); // ToDo, allow change only if no transactions booked or better, add merge option } }, onload: function (frm) { diff --git a/erpnext/healthcare/doctype/patient/patient.json b/erpnext/healthcare/doctype/patient/patient.json index 4258e4011d..2c701fbf94 100644 --- a/erpnext/healthcare/doctype/patient/patient.json +++ b/erpnext/healthcare/doctype/patient/patient.json @@ -24,13 +24,20 @@ "image", "column_break_14", "status", - "inpatient_status", "inpatient_record", - "customer", + "inpatient_status", + "report_preference", "mobile", "email", "phone", - "report_preference", + "customer_details_section", + "customer", + "customer_group", + "territory", + "column_break_24", + "default_currency", + "default_price_list", + "language", "personal_and_social_history", "occupation", "column_break_25", @@ -52,9 +59,7 @@ "surrounding_factors", "other_risk_factors", "more_info", - "patient_details", - "ac_sb", - "default_currency" + "patient_details" ], "fields": [ { @@ -156,6 +161,7 @@ "fieldname": "customer", "fieldtype": "Link", "ignore_user_permissions": 1, + "in_preview": 1, "label": "Customer", "options": "Customer" }, @@ -171,7 +177,8 @@ "fieldtype": "Data", "in_list_view": 1, "in_standard_filter": 1, - "label": "Mobile" + "label": "Mobile", + "options": "Phone" }, { "bold": 1, @@ -186,7 +193,8 @@ "fieldname": "phone", "fieldtype": "Data", "in_filter": 1, - "label": "Phone" + "label": "Phone", + "options": "Phone" }, { "collapsible": 1, @@ -268,25 +276,25 @@ "fieldname": "tobacco_past_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Tobacco Consumption Habbits (Past)" + "label": "Tobacco Consumption (Past)" }, { "fieldname": "tobacco_current_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Tobacco Consumption Habbits (Present)" + "label": "Tobacco Consumption (Present)" }, { "fieldname": "alcohol_past_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Alcohol Consumption Habbits (Past)" + "label": "Alcohol Consumption (Past)" }, { "fieldname": "alcohol_current_use", "fieldtype": "Data", "ignore_user_permissions": 1, - "label": "Alcohol Consumption Habbits (Present)" + "label": "Alcohol Consumption (Present)" }, { "fieldname": "column_break_32", @@ -320,20 +328,11 @@ "ignore_xss_filter": 1, "label": "Patient Details" }, - { - "collapsible": 1, - "fieldname": "ac_sb", - "fieldtype": "Section Break", - "label": "Account Details" - }, { "fieldname": "default_currency", "fieldtype": "Link", - "hidden": 1, - "ignore_xss_filter": 1, - "label": "Default Currency", - "options": "Currency", - "print_hide": 1 + "label": "Billing Currency", + "options": "Currency" }, { "fieldname": "last_name", @@ -351,13 +350,47 @@ "fieldname": "middle_name", "fieldtype": "Data", "label": "Middle Name (optional)" + }, + { + "collapsible": 1, + "fieldname": "customer_details_section", + "fieldtype": "Section Break", + "label": "Customer Details" + }, + { + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "options": "Customer Group" + }, + { + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "options": "Territory" + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break" + }, + { + "fieldname": "default_price_list", + "fieldtype": "Link", + "label": "Default Price List", + "options": "Price List" + }, + { + "fieldname": "language", + "fieldtype": "Link", + "label": "Print Language", + "options": "Language" } ], "icon": "fa fa-user", "image_field": "image", "links": [], "max_attachments": 50, - "modified": "2020-04-06 12:55:30.807744", + "modified": "2020-04-11 14:53:48.767245", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient", diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py index e304a0bbc3..f83fac03a6 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -26,6 +26,7 @@ class Patient(Document): frappe.db.set_value('Patient', self.name, 'status', 'Disabled') else: send_registration_sms(self) + self.reload() # self.notify_update() def set_full_name(self): if self.last_name: @@ -86,8 +87,8 @@ class Patient(Document): return {'invoice': sales_invoice.name} def create_customer(doc): - customer_group = frappe.db.get_single_value('Selling Settings', 'customer_group') - territory = frappe.db.get_single_value('Selling Settings', 'territory') + customer_group = doc.customer_group or frappe.db.get_single_value('Selling Settings', 'customer_group') + territory = doc.territory or frappe.db.get_single_value('Selling Settings', 'territory') if not (customer_group and territory): customer_group = get_root_of('Customer Group') territory = get_root_of('Territory') @@ -98,7 +99,10 @@ def create_customer(doc): 'customer_name': doc.patient_name, 'customer_group': customer_group, 'territory' : territory, - 'customer_type': 'Individual' + 'customer_type': 'Individual', + 'default_currency': doc.default_currency, + 'default_price_ist': doc.default_price_list, + 'language': doc.language }).insert(ignore_permissions=True, ignore_mandatory=True) frappe.db.set_value('Patient', doc.name, 'customer', customer.name) From be2dc701c2c503aa06720376efc07c6841b0a2de Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 11:56:09 +0530 Subject: [PATCH 10/96] fix: vitals added naimg series, minor field rearrangements --- .../healthcare_practitioner.json | 10 +++++++--- .../sample_collection/sample_collection.json | 8 ++++---- .../doctype/vital_signs/vital_signs.json | 20 ++++++++++++++----- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json index fd5b6e12f6..cb747f95ef 100644 --- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json +++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json @@ -1,6 +1,5 @@ { "actions": [], - "allow_copy": 1, "allow_import": 1, "allow_rename": 1, "autoname": "naming_series:", @@ -51,17 +50,20 @@ "fieldname": "first_name", "fieldtype": "Data", "label": "First Name", + "no_copy": 1, "reqd": 1 }, { "fieldname": "middle_name", "fieldtype": "Data", - "label": "Middle Name (Optional)" + "label": "Middle Name (Optional)", + "no_copy": 1 }, { "fieldname": "last_name", "fieldtype": "Data", - "label": "Last Name" + "label": "Last Name", + "no_copy": 1 }, { "fieldname": "image", @@ -226,6 +228,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Full Name", + "no_copy": 1, "read_only": 1, "search_index": 1 }, @@ -233,6 +236,7 @@ "fieldname": "naming_series", "fieldtype": "Select", "label": "Series", + "no_copy": 1, "options": "HLC-PRAC-.YYYY.-", "report_hide": 1, "set_only_once": 1 diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.json b/erpnext/healthcare/doctype/sample_collection/sample_collection.json index 39cead8862..c352287faf 100644 --- a/erpnext/healthcare/doctype/sample_collection/sample_collection.json +++ b/erpnext/healthcare/doctype/sample_collection/sample_collection.json @@ -9,14 +9,14 @@ "document_type": "Document", "engine": "InnoDB", "field_order": [ - "inpatient_record", "naming_series", - "invoiced", "patient", - "column_break_4", "patient_age", "patient_sex", + "column_break_4", + "inpatient_record", "company", + "invoiced", "section_break_6", "sample", "sample_uom", @@ -167,7 +167,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-03-25 16:55:52.376834", + "modified": "2020-04-04 19:17:02.707203", "modified_by": "Administrator", "module": "Healthcare", "name": "Sample Collection", diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.json b/erpnext/healthcare/doctype/vital_signs/vital_signs.json index 75726dbe07..fdacda6277 100644 --- a/erpnext/healthcare/doctype/vital_signs/vital_signs.json +++ b/erpnext/healthcare/doctype/vital_signs/vital_signs.json @@ -8,11 +8,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "inpatient_record", "patient", "patient_name", - "appointment", - "encounter", "column_break_2", "signs_date", "signs_time", @@ -34,6 +31,11 @@ "bmi", "column_break_14", "nutrition_note", + "sb_references", + "inpatient_record", + "appointment", + "encounter", + "column_break_28", "company", "amended_from" ], @@ -217,7 +219,6 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, "label": "Company", "options": "Company" }, @@ -229,11 +230,20 @@ "options": "Vital Signs", "print_hide": 1, "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "sb_references", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_28", + "fieldtype": "Column Break" } ], "is_submittable": 1, "links": [], - "modified": "2020-03-04 17:19:29.549889", + "modified": "2020-04-03 23:06:29.786184", "modified_by": "Administrator", "module": "Healthcare", "name": "Vital Signs", From b514b26242c239533ddcebb24cbe63d218ec9a3e Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 12:09:06 +0530 Subject: [PATCH 11/96] fix: set company while creating encounter, fields rearranged --- .../patient_appointment.js | 3 +- .../patient_appointment.json | 43 +++++++++++++------ .../patient_appointment.py | 3 +- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index efa6b249b3..de8dc0e90a 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -193,7 +193,6 @@ let check_and_set_availability = function(frm) { d.hide(); frm.enable_save(); frm.save(); - frm.enable_save(); d.get_primary_btn().attr('disabled', true); } }); @@ -400,6 +399,7 @@ let create_vital_signs = function(frm) { frappe.route_options = { 'patient': frm.doc.patient, 'appointment': frm.doc.name, + 'company': frm.doc.company }; frappe.new_doc('Vital Signs'); }; @@ -432,6 +432,7 @@ frappe.ui.form.on('Patient Appointment', 'practitioner', function(frm) { callback: function (data) { frappe.model.set_value(frm.doctype, frm.docname, 'department', data.message.department); frappe.model.set_value(frm.doctype, frm.docname, 'paid_amount', data.message.op_consulting_charge); + frappe.model.set_value(frm.doctype, frm.docname, 'billing_item', data.message.op_consulting_charge_item); } }); } diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index 7f9a671d47..81f7597563 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -14,13 +14,16 @@ "patient_name", "patient_sex", "patient_age", - "inpatient_record", "column_break_1", - "status", - "procedure_template", - "get_procedure_from_encounter", - "procedure_prescription", + "inpatient_record", + "company", "service_unit", + "status", + "section_break_11", + "get_procedure_from_encounter", + "column_break_13", + "procedure_template", + "procedure_prescription", "section_break_12", "practitioner", "department", @@ -32,9 +35,9 @@ "duration", "section_break_16", "mode_of_payment", - "paid_amount", - "company", + "billing_item", "column_break_2", + "paid_amount", "invoiced", "ref_sales_invoice", "section_break_3", @@ -114,7 +117,8 @@ "label": "Procedure Prescription", "no_copy": 1, "options": "Procedure Prescription", - "print_hide": 1 + "print_hide": 1, + "read_only": 1 }, { "fieldname": "service_unit", @@ -140,6 +144,7 @@ "set_only_once": 1 }, { + "fetch_from": "practitioner.department", "fieldname": "department", "fieldtype": "Link", "ignore_user_permissions": 1, @@ -234,12 +239,9 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, "label": "Company", "no_copy": 1, - "options": "Company", - "print_hide": 1, - "report_hide": 1 + "options": "Company" }, { "collapsible": 1, @@ -282,10 +284,25 @@ "label": "Series", "options": "HLC-APP-.YYYY.-", "set_only_once": 1 + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "fieldname": "billing_item", + "fieldtype": "Link", + "label": "Billing Item", + "options": "Item", + "read_only": 1 } ], "links": [], - "modified": "2020-03-27 11:27:33.773195", + "modified": "2020-04-07 11:16:34.981240", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Appointment", diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index a2d9d0240f..3786bf2861 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -340,7 +340,8 @@ def make_encounter(source_name, target_doc=None): ['medical_department', 'department'], ['patient_sex', 'patient_sex'], ['encounter_date', 'appointment_date'], - ['invoiced', 'invoiced'] + ['invoiced', 'invoiced'], + ['company', 'company'] ] } }, target_doc) From db00dbf90e9550e31ea8036d672a7585b2e0ed87 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 12:11:59 +0530 Subject: [PATCH 12/96] fix: company when creatign procedure, practitioner name added --- .../patient_encounter/patient_encounter.js | 9 ++++--- .../patient_encounter/patient_encounter.json | 24 +++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js index 83c5d2be9c..0e34164d07 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js @@ -110,7 +110,8 @@ frappe.ui.form.on('Patient Encounter', { 'patient':data.message.patient, 'type': data.message.appointment_type, 'practitioner': data.message.practitioner, - 'invoiced': data.message.invoiced + 'invoiced': data.message.invoiced, + 'company': data.message.company }; frm.set_value(values); } @@ -209,7 +210,8 @@ let create_vital_signs = function (frm) { frappe.route_options = { 'patient': frm.doc.patient, 'appointment': frm.doc.appointment, - 'encounter': frm.doc.name + 'encounter': frm.doc.name, + 'company': frm.doc.company }; frappe.new_doc('Vital Signs'); }; @@ -220,7 +222,8 @@ let create_procedure = function (frm) { } frappe.route_options = { 'patient': frm.doc.patient, - 'medical_department': frm.doc.medical_department + 'medical_department': frm.doc.medical_department, + 'company': frm.doc.company }; frappe.new_doc('Clinical Procedure'); }; diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json index d00e7bc7dd..935935e0c0 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json @@ -17,9 +17,9 @@ "patient_name", "patient_sex", "patient_age", - "company", "column_break_6", "practitioner", + "practitioner_name", "medical_department", "encounter_date", "encounter_time", @@ -43,6 +43,8 @@ "sb_procedures", "procedure_prescription", "encounter_comment", + "sb_refs", + "company", "amended_from" ], "fields": [ @@ -80,7 +82,6 @@ "fieldname": "patient", "fieldtype": "Link", "ignore_user_permissions": 1, - "in_list_view": 1, "in_standard_filter": 1, "label": "Patient", "options": "Patient", @@ -110,7 +111,6 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, "label": "Company", "options": "Company" }, @@ -121,7 +121,6 @@ { "fieldname": "practitioner", "fieldtype": "Link", - "in_list_view": 1, "in_standard_filter": 1, "label": "Healthcare Practitioner", "options": "Healthcare Practitioner", @@ -272,7 +271,6 @@ "fieldname": "medical_department", "fieldtype": "Link", "ignore_user_permissions": 1, - "in_list_view": 1, "in_standard_filter": 1, "label": "Department", "options": "Medical Department", @@ -287,11 +285,23 @@ { "fieldname": "column_break_17", "fieldtype": "Column Break" + }, + { + "fieldname": "sb_refs", + "fieldtype": "Section Break" + }, + { + "fetch_from": "practitioner.practitioner_name", + "fieldname": "practitioner_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Practitioner Name", + "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-02-27 12:42:21.751964", + "modified": "2020-04-03 23:06:16.348846", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Encounter", @@ -318,7 +328,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "title_field": "patient", + "title_field": "patient_name", "track_changes": 1, "track_seen": 1 } \ No newline at end of file From 35932f24912a76b1fef3b57b892df51ff92d988d Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:30:00 +0530 Subject: [PATCH 13/96] feat: multi company support --- .../healthcare/doctype/lab_test/lab_test.js | 9 ++++---- .../healthcare/doctype/lab_test/lab_test.json | 15 ++++++------- .../healthcare/doctype/lab_test/lab_test.py | 21 +++++++++++-------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.js b/erpnext/healthcare/doctype/lab_test/lab_test.js index 5b3f4c705a..e7e44dcb48 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.js +++ b/erpnext/healthcare/doctype/lab_test/lab_test.js @@ -137,7 +137,7 @@ var get_lab_test_prescribed = function(frm){ }); } else{ - frappe.msgprint(__("Please select Patient to get Lab Tests")); + frappe.msgprint(__("Please select a Patient to get Lab Orders")); } }; @@ -180,9 +180,10 @@ var show_lab_tests = function(frm, result){ return false; }); }); - if(!result){ - var msg = "There are no Lab Test prescribed for "+frm.doc.patient; - $(repl('
%(msg)s
', {msg: msg})).appendTo(html_field); + if(!result.length){ + var msg = "No Lab Orders found for patient " + frm.doc.patient_name + ""; + html_field.empty(); + $(repl('
%(msg)s
', {msg: msg})).appendTo(html_field); } d.show(); }; diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.json b/erpnext/healthcare/doctype/lab_test/lab_test.json index ccbc24b3fb..17dc1edd8c 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.json +++ b/erpnext/healthcare/doctype/lab_test/lab_test.json @@ -9,18 +9,18 @@ "document_type": "Document", "engine": "InnoDB", "field_order": [ - "inpatient_record", "naming_series", - "invoiced", "patient", "patient_name", "patient_age", "patient_sex", - "practitioner", + "report_preference", "email", "mobile", - "company", + "practitioner", "c_b", + "inpatient_record", + "company", "department", "status", "submitted_date", @@ -31,7 +31,7 @@ "employee_name", "employee_designation", "user", - "report_preference", + "invoiced", "sb_first", "lab_test_name", "column_break_26", @@ -153,7 +153,7 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, + "in_standard_filter": 1, "label": "Company", "options": "Company", "print_hide": 1, @@ -168,6 +168,7 @@ "fieldname": "department", "fieldtype": "Link", "ignore_user_permissions": 1, + "in_standard_filter": 1, "label": "Department", "options": "Medical Department", "search_index": 1 @@ -427,7 +428,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-03-23 19:37:06.617764", + "modified": "2020-04-04 19:16:29.131168", "modified_by": "Administrator", "module": "Healthcare", "name": "Lab Test", diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.py b/erpnext/healthcare/doctype/lab_test/lab_test.py index 4e4015d2f0..e49429ea62 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.py +++ b/erpnext/healthcare/doctype/lab_test/lab_test.py @@ -69,9 +69,9 @@ def create_multiple(doctype, docname): lab_test_created = create_lab_test_from_encounter(docname) if lab_test_created: - frappe.msgprint(_("Lab Test(s) "+lab_test_created+" created.")) + frappe.msgprint(_("Lab Test(s) " + lab_test_created + " created.")) else: - frappe.msgprint(_("No Lab Test created")) + frappe.msgprint(_("No Lab Tests created")) def create_lab_test_from_encounter(encounter_id): lab_test_created = False @@ -87,7 +87,7 @@ def create_lab_test_from_encounter(encounter_id): for lab_test_id in lab_test_ids: template = get_lab_test_template(lab_test_id[1]) if template: - lab_test = create_lab_test_doc(lab_test_id[2], encounter.practitioner, patient, template) + lab_test = create_lab_test_doc(lab_test_id[2], encounter.practitioner, patient, template, encounter.company) lab_test.save(ignore_permissions = True) frappe.db.set_value("Lab Prescription", lab_test_id[0], "lab_test_created", 1) if not lab_test_created: @@ -111,7 +111,7 @@ def create_lab_test_from_invoice(invoice_name): if lab_test_created != 1: template = get_lab_test_template(item.item_code) if template: - lab_test = create_lab_test_doc(True, invoice.ref_practitioner, patient, template) + lab_test = create_lab_test_doc(True, invoice.ref_practitioner, patient, template, company, invoice.company) if item.reference_dt == "Lab Prescription": lab_test.prescription = item.reference_dn lab_test.save(ignore_permissions = True) @@ -121,7 +121,7 @@ def create_lab_test_from_invoice(invoice_name): if not lab_tests_created: lab_tests_created = lab_test.name else: - lab_tests_created += ", "+lab_test.name + lab_tests_created += ", " + lab_test.name return lab_tests_created def get_lab_test_template(item): @@ -141,7 +141,7 @@ def check_template_exists(item): return template_exists return False -def create_lab_test_doc(invoiced, practitioner, patient, template): +def create_lab_test_doc(invoiced, practitioner, patient, template, company): lab_test = frappe.new_doc("Lab Test") lab_test.invoiced = invoiced lab_test.practitioner = practitioner @@ -150,11 +150,12 @@ def create_lab_test_doc(invoiced, practitioner, patient, template): lab_test.patient_sex = patient.sex lab_test.email = patient.email lab_test.mobile = patient.mobile + lab_test.report_preference = patient.report_preference lab_test.department = template.department lab_test.template = template.name lab_test.lab_test_group = template.lab_test_group lab_test.result_date = getdate() - lab_test.report_preference = patient.report_preference + lab_test.company = company return lab_test def create_normals(template, lab_test): @@ -190,7 +191,7 @@ def create_specials(template, lab_test): special.require_result_value = 1 special.template = template.name -def create_sample_doc(template, patient, invoice): +def create_sample_doc(template, patient, invoice, company = None): if template.sample: sample_exists = frappe.db.exists({ "doctype": "Sample Collection", @@ -221,6 +222,8 @@ def create_sample_doc(template, patient, invoice): sample_collection.sample = template.sample sample_collection.sample_uom = template.sample_uom sample_collection.sample_qty = template.sample_qty + sample_collection.company = company + if(template.sample_details): sample_collection.sample_details = "Test :" + (template.get("lab_test_name") or template.get("template")) +"\n"+"Collection Detials:\n\t"+template.sample_details sample_collection.save(ignore_permissions=True) @@ -229,7 +232,7 @@ def create_sample_doc(template, patient, invoice): def create_sample_collection(lab_test, template, patient, invoice): if(frappe.db.get_value("Healthcare Settings", None, "create_sample_collection_for_lab_test") == "1"): - sample_collection = create_sample_doc(template, patient, invoice) + sample_collection = create_sample_doc(template, patient, invoice, lab_test.company) if(sample_collection): lab_test.sample = sample_collection.name return lab_test From 0bdb5ba0c5110b916510477fe3e357c9f0230406 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:32:35 +0530 Subject: [PATCH 14/96] feat: multi company support --- .../clinical_procedure/clinical_procedure.js | 35 ++++++++--- .../clinical_procedure.json | 58 ++++++++++++++++--- .../clinical_procedure/clinical_procedure.py | 2 +- 3 files changed, 79 insertions(+), 16 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js index 5f36bdd95c..213fa3de1f 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js @@ -175,16 +175,37 @@ frappe.ui.form.on('Clinical Procedure', { name: frm.doc.appointment }, callback: function(data) { - frm.set_value('patient', data.message.patient); - frm.set_value('procedure_template', data.message.procedure_template); - frm.set_value('medical_department', data.message.department); - frm.set_value('start_date', data.message.appointment_date); - frm.set_value('start_time', data.message.appointment_time); - frm.set_value('notes', data.message.notes); - frm.set_value('service_unit', data.message.service_unit); + let values = { + 'patient':data.message.patient, + 'procedure_template': data.message.procedure_template, + 'medical_department': data.message.department, + 'start_date': data.message.appointment_date, + 'start_time': data.message.appointment_time, + 'notes': data.message.notes, + 'service_unit': data.message.service_unit, + 'company': data.message.company + } + frm.set_value(values); } }); } + else{ + let values = { + 'patient': '', + 'patient_name': '', + 'patient_sex': '', + 'patient_age': '', + 'medical_department': '', + 'procedure_template': '', + 'start_date': '', + 'start_time': '', + 'notes': '', + 'service_unit': '', + 'inpatient_record': '' + // 'inpatient_status': '' + }; + frm.set_value(values); + } }, procedure_template: function(frm) { diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json index 3c936bbf27..59a87eccde 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json @@ -7,16 +7,18 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "inpatient_record", "naming_series", - "procedure_template", "appointment", + "column_break_30", + "procedure_template", + "section_break_6", "patient", + "patient_name", "patient_sex", "patient_age", - "prescription", "medical_department", "practitioner", + "practitioner_name", "column_break_7", "status", "service_unit", @@ -26,7 +28,6 @@ "sample", "invoiced", "notes", - "company", "consumables_section", "consume_stock", "items", @@ -36,6 +37,11 @@ "consumable_total_amount", "column_break_27", "consumption_details", + "sb_refs", + "inpatient_record", + "company", + "column_break_34", + "prescription", "amended_from" ], "fields": [ @@ -56,7 +62,7 @@ { "fieldname": "appointment", "fieldtype": "Link", - "in_list_view": 1, + "in_standard_filter": 1, "label": "Appointment", "options": "Patient Appointment" }, @@ -64,7 +70,7 @@ "fetch_from": "inpatient_record.patient", "fieldname": "patient", "fieldtype": "Link", - "in_list_view": 1, + "in_standard_filter": 1, "label": "Patient", "options": "Patient", "reqd": 1 @@ -88,17 +94,20 @@ "fieldtype": "Link", "hidden": 1, "label": "Procedure Prescription", - "options": "Procedure Prescription" + "options": "Procedure Prescription", + "read_only": 1 }, { "fieldname": "medical_department", "fieldtype": "Link", + "in_standard_filter": 1, "label": "Medical Department", "options": "Medical Department" }, { "fieldname": "practitioner", "fieldtype": "Link", + "in_standard_filter": 1, "label": "Healthcare Practitioner", "options": "Healthcare Practitioner" }, @@ -237,11 +246,43 @@ { "fieldname": "section_break_24", "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_30", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "collapsible": 1, + "fieldname": "sb_refs", + "fieldtype": "Section Break" + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "fetch_from": "practitioner.practitioner_name", + "fieldname": "practitioner_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Practitioner Name", + "read_only": 1 + }, + { + "fieldname": "column_break_34", + "fieldtype": "Column Break" } ], "is_submittable": 1, "links": [], - "modified": "2020-03-02 11:44:27.970651", + "modified": "2020-04-03 23:06:04.009856", "modified_by": "Administrator", "module": "Healthcare", "name": "Clinical Procedure", @@ -263,5 +304,6 @@ "restrict_to_domain": "Healthcare", "sort_field": "modified", "sort_order": "DESC", + "title_field": "patient_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py index db3afc8807..56617e5258 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py @@ -37,7 +37,7 @@ class ClinicalProcedure(Document): template = frappe.get_doc('Clinical Procedure Template', self.procedure_template) if template.sample: patient = frappe.get_doc('Patient', self.patient) - sample_collection = create_sample_doc(template, patient, None) + sample_collection = create_sample_doc(template, patient, None, self.company) frappe.db.set_value('Clinical Procedure', self.name, 'sample', sample_collection.name) self.reload() From 93d6794d286e211c60c38681ce6edf21c6438538 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:34:28 +0530 Subject: [PATCH 15/96] company field added --- .../inpatient_record/inpatient_record.json | 1165 ++++------------- 1 file changed, 220 insertions(+), 945 deletions(-) diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json index 92c11fbb4d..c1b516d536 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json @@ -1,980 +1,255 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2018-07-11 17:48:51.404139", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "naming_series:", + "creation": "2018-07-11 17:48:51.404139", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "section_break_1", + "naming_series", + "patient", + "patient_name", + "gender", + "blood_group", + "dob", + "mobile", + "email", + "phone", + "column_break_8", + "company", + "status", + "scheduled_date", + "admitted_datetime", + "expected_discharge", + "discharge_date", + "references", + "cb_admission", + "admission_practitioner", + "admission_encounter", + "cb_discharge", + "discharge_practitioner", + "discharge_encounter", + "sb_inpatient_occupancy", + "inpatient_occupancies", + "btn_transfer", + "sb_discharge_note", + "discharge_note" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_1", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_1", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 0, - "options": "HLC-INP-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "hidden": 1, + "label": "Series", + "options": "HLC-INP-.YYYY.-" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "patient", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Patient", - "length": 0, - "no_copy": 0, - "options": "Patient", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "patient", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.patient_name", - "fieldname": "patient_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Patient Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.sex", - "fieldname": "gender", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Gender", - "length": 0, - "no_copy": 0, - "options": "Gender", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.sex", + "fieldname": "gender", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.blood_group", - "fieldname": "blood_group", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Blood Group", - "length": 0, - "no_copy": 0, - "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.blood_group", + "fieldname": "blood_group", + "fieldtype": "Select", + "label": "Blood Group", + "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "dob", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Date of birth", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.dob", + "fieldname": "dob", + "fieldtype": "Date", + "label": "Date of birth", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.mobile", - "fieldname": "mobile", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Mobile", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.mobile", + "fieldname": "mobile", + "fieldtype": "Data", + "label": "Mobile", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.email", - "fieldname": "email", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Email", - "length": 0, - "no_copy": 0, - "options": "Email", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.email", + "fieldname": "email", + "fieldtype": "Data", + "label": "Email", + "options": "Email", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.phone", - "fieldname": "phone", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Phone", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.phone", + "fieldname": "phone", + "fieldtype": "Data", + "label": "Phone", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_8", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 0, - "options": "Admission Scheduled\nAdmitted\nDischarge Scheduled\nDischarged", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Admission Scheduled\nAdmitted\nDischarge Scheduled\nDischarged", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "scheduled_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Admission Schedule Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Today", + "fieldname": "scheduled_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Admission Schedule Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "admitted_datetime", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Admitted Datetime", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Today", + "fieldname": "admitted_datetime", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Admitted Datetime" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "expected_discharge", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Expected Discharge", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "expected_discharge", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Expected Discharge" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Discharge Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "discharge_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Discharge Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "references", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "References", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "collapsible": 1, + "fieldname": "references", + "fieldtype": "Section Break", + "label": "References" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cb_admission", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Admission", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "cb_admission", + "fieldtype": "Column Break", + "label": "Admission" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "admission_practitioner", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Healthcare Practitioner", - "length": 0, - "no_copy": 0, - "options": "Healthcare Practitioner", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "admission_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "admission_encounter", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Patient Encounter", - "length": 0, - "no_copy": 0, - "options": "Patient Encounter", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "admission_encounter", + "fieldtype": "Link", + "label": "Patient Encounter", + "options": "Patient Encounter", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cb_discharge", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Discharge", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "cb_discharge", + "fieldtype": "Column Break", + "label": "Discharge" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_practitioner", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Healthcare Practitioner", - "length": 0, - "no_copy": 0, - "options": "Healthcare Practitioner", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "discharge_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_encounter", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Patient Encounter", - "length": 0, - "no_copy": 0, - "options": "Patient Encounter", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "discharge_encounter", + "fieldtype": "Link", + "label": "Patient Encounter", + "options": "Patient Encounter", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sb_inpatient_occupancy", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Inpatient Occupancy", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "sb_inpatient_occupancy", + "fieldtype": "Section Break", + "label": "Inpatient Occupancy" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "inpatient_occupancies", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "options": "Inpatient Occupancy", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "inpatient_occupancies", + "fieldtype": "Table", + "options": "Inpatient Occupancy", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "btn_transfer", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Transfer", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "btn_transfer", + "fieldtype": "Button", + "label": "Transfer" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.status != \"Admission Scheduled\"", - "fieldname": "sb_discharge_note", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Discharge Note", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.status != \"Admission Scheduled\"", + "fieldname": "sb_discharge_note", + "fieldtype": "Section Break", + "label": "Discharge Note" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_note", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "discharge_note", + "fieldtype": "Text Editor" + }, + { + "fetch_from": "admission_encounter.company", + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 14:44:43.168245", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Inpatient Record", - "name_case": "", - "owner": "Administrator", + ], + "links": [], + "modified": "2020-04-07 13:13:39.351977", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Inpatient Record", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "search_fields": "patient", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "patient", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} + ], + "restrict_to_domain": "Healthcare", + "search_fields": "patient", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "patient", + "track_changes": 1 +} \ No newline at end of file From 2bdf21bd3062425a6d34fe6a505afac73edcf8b7 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:42:03 +0530 Subject: [PATCH 16/96] feat: multi-company billing sales onvoice - filter get items based on company utils - company filters in all get item helper methods utils - refactor appointemnt items --- .../doctype/sales_invoice/sales_invoice.js | 7 +- erpnext/healthcare/utils.py | 96 ++++++++++--------- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 6f78db2ccc..7741842fca 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -919,7 +919,7 @@ var get_healthcare_services_to_invoice = function(frm) { if(patient && patient!=selected_patient){ selected_patient = patient; var method = "erpnext.healthcare.utils.get_healthcare_services_to_invoice"; - var args = {patient: patient}; + var args = {patient: patient, company: frm.doc.company}; var columns = (["service", "reference_name", "reference_type"]); get_healthcare_items(frm, true, $results, $placeholder, method, args, columns); } @@ -1063,7 +1063,10 @@ var get_drugs_to_invoice = function(frm) { description:'Quantity will be calculated only for items which has "Nos" as UoM. You may change as required for each invoice item.', get_query: function(doc) { return { - filters: { patient: dialog.get_value("patient"), docstatus: 1 } + filters: { patient: dialog.get_value("patient"), + company: frm.doc.company, + docstatus: 1 + } }; } }, diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py index 246242ad84..e9cc597e81 100644 --- a/erpnext/healthcare/utils.py +++ b/erpnext/healthcare/utils.py @@ -3,82 +3,84 @@ # For license information, please see license.txt from __future__ import unicode_literals +import math +import json import frappe from frappe import _ -import math 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 from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple @frappe.whitelist() -def get_healthcare_services_to_invoice(patient): +def get_healthcare_services_to_invoice(patient, company): patient = frappe.get_doc('Patient', patient) + items_to_invoice = [] if patient: validate_customer_created(patient) - items_to_invoice = [] - patient_appointments = frappe.get_list( - 'Patient Appointment', - fields='*', - filters={'patient': patient.name, 'invoiced': 0}, - order_by='appointment_date' - ) - if patient_appointments: - items_to_invoice = get_fee_validity(patient_appointments) + # Customer validated, build a list of billable services + items_to_invoice += get_appointments_to_invoice(patient, company) + items_to_invoice += get_encounters_to_invoice(patient, company) + items_to_invoice += get_lab_tests_to_invoice(patient, company) + items_to_invoice += get_clinical_procedures_to_invoice(patient, company) + items_to_invoice += get_inpatient_services_to_invoice(patient, company) - encounters = get_encounters_to_invoice(patient) - lab_tests = get_lab_tests_to_invoice(patient) - clinical_procedures = get_clinical_procedures_to_invoice(patient) - inpatient_services = get_inpatient_services_to_invoice(patient) - - items_to_invoice += encounters + lab_tests + clinical_procedures + inpatient_services return items_to_invoice + def validate_customer_created(patient): if not frappe.db.get_value('Patient', patient.name, 'customer'): msg = _("Please set a Customer linked to the Patient") msg += " {0}".format(patient.name) frappe.throw(msg, title=_('Customer Not Found')) -def get_fee_validity(patient_appointments): - if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'): - return - items_to_invoice = [] +def get_appointments_to_invoice(patient, company): + appointments_to_invoice = [] + patient_appointments = frappe.get_list( + 'Patient Appointment', + fields = '*', + filters = {'patient': patient.name, 'company': company, 'invoiced': 0}, + order_by = 'appointment_date' + ) + for appointment in patient_appointments: + # Procedure Appointments if appointment.procedure_template: if frappe.db.get_value('Clinical Procedure Template', appointment.procedure_template, 'is_billable'): - items_to_invoice.append({ + appointments_to_invoice.append({ 'reference_type': 'Patient Appointment', 'reference_name': appointment.name, 'service': appointment.procedure_template }) + # Consultation Appointments, should check fee validity else: - fee_validity = frappe.db.exists('Fee Validity Reference', {'appointment': appointment.name}) - if not fee_validity: - practitioner_charge = 0 - income_account = None - service_item = None - if appointment.practitioner: - service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment) - income_account = get_income_account(appointment.practitioner, appointment.company) - items_to_invoice.append({ - 'reference_type': 'Patient Appointment', - 'reference_name': appointment.name, - 'service': service_item, - 'rate': practitioner_charge, - 'income_account': income_account - }) + if frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups') and \ + frappe.db.exists('Fee Validity Reference', {'appointment': appointment.name}): + continue # Skip invoicing, fee validty present + practitioner_charge = 0 + income_account = None + service_item = None + if appointment.practitioner: + service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment) + income_account = get_income_account(appointment.practitioner, appointment.company) + appointments_to_invoice.append({ + 'reference_type': 'Patient Appointment', + 'reference_name': appointment.name, + 'service': service_item, + 'rate': practitioner_charge, + 'income_account': income_account + }) - return items_to_invoice + return appointments_to_invoice -def get_encounters_to_invoice(patient): +def get_encounters_to_invoice(patient, company): encounters_to_invoice = [] encounters = frappe.get_list( 'Patient Encounter', fields=['*'], - filters={'patient': patient.name, 'invoiced': False, 'docstatus': 1} + filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1} ) if encounters: for encounter in encounters: @@ -101,12 +103,12 @@ def get_encounters_to_invoice(patient): return encounters_to_invoice -def get_lab_tests_to_invoice(patient): +def get_lab_tests_to_invoice(patient, company): lab_tests_to_invoice = [] lab_tests = frappe.get_list( 'Lab Test', fields=['name', 'template'], - filters={'patient': patient.name, 'invoiced': False, 'docstatus': 1} + filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1} ) for lab_test in lab_tests: item, is_billable = frappe.get_cached_value('Lab Test Template', lab_test.lab_test_code, ['item', 'is_billable']) @@ -142,12 +144,12 @@ def get_lab_tests_to_invoice(patient): return lab_tests_to_invoice -def get_clinical_procedures_to_invoice(patient): +def get_clinical_procedures_to_invoice(patient, company): clinical_procedures_to_invoice = [] procedures = frappe.get_list( 'Clinical Procedure', fields='*', - filters={'patient': patient.name, 'invoiced': False} + filters={'patient': patient.name, 'company': company, 'invoiced': False} ) for procedure in procedures: if not procedure.appointment: @@ -203,7 +205,7 @@ def get_clinical_procedures_to_invoice(patient): return clinical_procedures_to_invoice -def get_inpatient_services_to_invoice(patient): +def get_inpatient_services_to_invoice(patient, company): services_to_invoice = [] inpatient_services = frappe.db.sql( ''' @@ -213,10 +215,11 @@ def get_inpatient_services_to_invoice(patient): `tabInpatient Record` ip, `tabInpatient Occupancy` io WHERE ip.patient=%s + and ip.company=%s and io.parent=ip.name and io.left=1 and io.invoiced=0 - ''', (patient.name), as_dict=1) + ''', (patient.name, company), as_dict=1) for inpatient_occupancy in inpatient_services: service_unit_type = frappe.db.get_value('Healthcare Service Unit', inpatient_occupancy.service_unit, 'service_unit_type') @@ -376,6 +379,7 @@ def check_fee_validity(appointment): def manage_fee_validity(appointment): fee_validity = check_fee_validity(appointment) + if fee_validity: if appointment.status == 'Cancelled' and fee_validity.visited > 0: fee_validity.visited -= 1 From c1e25eabfd067ed927c68622906944be82e57839 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 11:36:06 +0530 Subject: [PATCH 17/96] feat: additional customer fileds add customer_group, price list, currency etc. rearrange and group fields disable cusotmer edit after save (temp fix) --- erpnext/healthcare/doctype/patient/patient.js | 1 + .../healthcare/doctype/patient/patient.json | 81 +++++++++++++------ erpnext/healthcare/doctype/patient/patient.py | 10 ++- 3 files changed, 65 insertions(+), 27 deletions(-) diff --git a/erpnext/healthcare/doctype/patient/patient.js b/erpnext/healthcare/doctype/patient/patient.js index d5df9567ec..a11faf5806 100644 --- a/erpnext/healthcare/doctype/patient/patient.js +++ b/erpnext/healthcare/doctype/patient/patient.js @@ -40,6 +40,7 @@ frappe.ui.form.on('Patient', { frm.add_custom_button(__('Patient Encounter'), function () { create_encounter(frm); }, 'Create'); + frm.toggle_enable(['customer'], 0); // ToDo, allow change only if no transactions booked or better, add merge option } }, onload: function (frm) { diff --git a/erpnext/healthcare/doctype/patient/patient.json b/erpnext/healthcare/doctype/patient/patient.json index 4258e4011d..2c701fbf94 100644 --- a/erpnext/healthcare/doctype/patient/patient.json +++ b/erpnext/healthcare/doctype/patient/patient.json @@ -24,13 +24,20 @@ "image", "column_break_14", "status", - "inpatient_status", "inpatient_record", - "customer", + "inpatient_status", + "report_preference", "mobile", "email", "phone", - "report_preference", + "customer_details_section", + "customer", + "customer_group", + "territory", + "column_break_24", + "default_currency", + "default_price_list", + "language", "personal_and_social_history", "occupation", "column_break_25", @@ -52,9 +59,7 @@ "surrounding_factors", "other_risk_factors", "more_info", - "patient_details", - "ac_sb", - "default_currency" + "patient_details" ], "fields": [ { @@ -156,6 +161,7 @@ "fieldname": "customer", "fieldtype": "Link", "ignore_user_permissions": 1, + "in_preview": 1, "label": "Customer", "options": "Customer" }, @@ -171,7 +177,8 @@ "fieldtype": "Data", "in_list_view": 1, "in_standard_filter": 1, - "label": "Mobile" + "label": "Mobile", + "options": "Phone" }, { "bold": 1, @@ -186,7 +193,8 @@ "fieldname": "phone", "fieldtype": "Data", "in_filter": 1, - "label": "Phone" + "label": "Phone", + "options": "Phone" }, { "collapsible": 1, @@ -268,25 +276,25 @@ "fieldname": "tobacco_past_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Tobacco Consumption Habbits (Past)" + "label": "Tobacco Consumption (Past)" }, { "fieldname": "tobacco_current_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Tobacco Consumption Habbits (Present)" + "label": "Tobacco Consumption (Present)" }, { "fieldname": "alcohol_past_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Alcohol Consumption Habbits (Past)" + "label": "Alcohol Consumption (Past)" }, { "fieldname": "alcohol_current_use", "fieldtype": "Data", "ignore_user_permissions": 1, - "label": "Alcohol Consumption Habbits (Present)" + "label": "Alcohol Consumption (Present)" }, { "fieldname": "column_break_32", @@ -320,20 +328,11 @@ "ignore_xss_filter": 1, "label": "Patient Details" }, - { - "collapsible": 1, - "fieldname": "ac_sb", - "fieldtype": "Section Break", - "label": "Account Details" - }, { "fieldname": "default_currency", "fieldtype": "Link", - "hidden": 1, - "ignore_xss_filter": 1, - "label": "Default Currency", - "options": "Currency", - "print_hide": 1 + "label": "Billing Currency", + "options": "Currency" }, { "fieldname": "last_name", @@ -351,13 +350,47 @@ "fieldname": "middle_name", "fieldtype": "Data", "label": "Middle Name (optional)" + }, + { + "collapsible": 1, + "fieldname": "customer_details_section", + "fieldtype": "Section Break", + "label": "Customer Details" + }, + { + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "options": "Customer Group" + }, + { + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "options": "Territory" + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break" + }, + { + "fieldname": "default_price_list", + "fieldtype": "Link", + "label": "Default Price List", + "options": "Price List" + }, + { + "fieldname": "language", + "fieldtype": "Link", + "label": "Print Language", + "options": "Language" } ], "icon": "fa fa-user", "image_field": "image", "links": [], "max_attachments": 50, - "modified": "2020-04-06 12:55:30.807744", + "modified": "2020-04-11 14:53:48.767245", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient", diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py index e304a0bbc3..f83fac03a6 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -26,6 +26,7 @@ class Patient(Document): frappe.db.set_value('Patient', self.name, 'status', 'Disabled') else: send_registration_sms(self) + self.reload() # self.notify_update() def set_full_name(self): if self.last_name: @@ -86,8 +87,8 @@ class Patient(Document): return {'invoice': sales_invoice.name} def create_customer(doc): - customer_group = frappe.db.get_single_value('Selling Settings', 'customer_group') - territory = frappe.db.get_single_value('Selling Settings', 'territory') + customer_group = doc.customer_group or frappe.db.get_single_value('Selling Settings', 'customer_group') + territory = doc.territory or frappe.db.get_single_value('Selling Settings', 'territory') if not (customer_group and territory): customer_group = get_root_of('Customer Group') territory = get_root_of('Territory') @@ -98,7 +99,10 @@ def create_customer(doc): 'customer_name': doc.patient_name, 'customer_group': customer_group, 'territory' : territory, - 'customer_type': 'Individual' + 'customer_type': 'Individual', + 'default_currency': doc.default_currency, + 'default_price_ist': doc.default_price_list, + 'language': doc.language }).insert(ignore_permissions=True, ignore_mandatory=True) frappe.db.set_value('Patient', doc.name, 'customer', customer.name) From e2a92246ea6ef72cfe266ba53136bc88c79838d2 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 11:56:09 +0530 Subject: [PATCH 18/96] fix: vitals added naimg series, minor field rearrangements --- .../healthcare_practitioner.json | 10 +++++++--- .../sample_collection/sample_collection.json | 8 ++++---- .../doctype/vital_signs/vital_signs.json | 20 ++++++++++++++----- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json index fd5b6e12f6..cb747f95ef 100644 --- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json +++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json @@ -1,6 +1,5 @@ { "actions": [], - "allow_copy": 1, "allow_import": 1, "allow_rename": 1, "autoname": "naming_series:", @@ -51,17 +50,20 @@ "fieldname": "first_name", "fieldtype": "Data", "label": "First Name", + "no_copy": 1, "reqd": 1 }, { "fieldname": "middle_name", "fieldtype": "Data", - "label": "Middle Name (Optional)" + "label": "Middle Name (Optional)", + "no_copy": 1 }, { "fieldname": "last_name", "fieldtype": "Data", - "label": "Last Name" + "label": "Last Name", + "no_copy": 1 }, { "fieldname": "image", @@ -226,6 +228,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Full Name", + "no_copy": 1, "read_only": 1, "search_index": 1 }, @@ -233,6 +236,7 @@ "fieldname": "naming_series", "fieldtype": "Select", "label": "Series", + "no_copy": 1, "options": "HLC-PRAC-.YYYY.-", "report_hide": 1, "set_only_once": 1 diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.json b/erpnext/healthcare/doctype/sample_collection/sample_collection.json index 39cead8862..c352287faf 100644 --- a/erpnext/healthcare/doctype/sample_collection/sample_collection.json +++ b/erpnext/healthcare/doctype/sample_collection/sample_collection.json @@ -9,14 +9,14 @@ "document_type": "Document", "engine": "InnoDB", "field_order": [ - "inpatient_record", "naming_series", - "invoiced", "patient", - "column_break_4", "patient_age", "patient_sex", + "column_break_4", + "inpatient_record", "company", + "invoiced", "section_break_6", "sample", "sample_uom", @@ -167,7 +167,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-03-25 16:55:52.376834", + "modified": "2020-04-04 19:17:02.707203", "modified_by": "Administrator", "module": "Healthcare", "name": "Sample Collection", diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.json b/erpnext/healthcare/doctype/vital_signs/vital_signs.json index 75726dbe07..fdacda6277 100644 --- a/erpnext/healthcare/doctype/vital_signs/vital_signs.json +++ b/erpnext/healthcare/doctype/vital_signs/vital_signs.json @@ -8,11 +8,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "inpatient_record", "patient", "patient_name", - "appointment", - "encounter", "column_break_2", "signs_date", "signs_time", @@ -34,6 +31,11 @@ "bmi", "column_break_14", "nutrition_note", + "sb_references", + "inpatient_record", + "appointment", + "encounter", + "column_break_28", "company", "amended_from" ], @@ -217,7 +219,6 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, "label": "Company", "options": "Company" }, @@ -229,11 +230,20 @@ "options": "Vital Signs", "print_hide": 1, "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "sb_references", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_28", + "fieldtype": "Column Break" } ], "is_submittable": 1, "links": [], - "modified": "2020-03-04 17:19:29.549889", + "modified": "2020-04-03 23:06:29.786184", "modified_by": "Administrator", "module": "Healthcare", "name": "Vital Signs", From b72d18c469dd9289e3ce0b69e6c6a8aa7fe151ae Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 12:09:06 +0530 Subject: [PATCH 19/96] fix: set company while creating encounter, fields rearranged --- .../patient_appointment.js | 3 +- .../patient_appointment.json | 43 +++++++++++++------ .../patient_appointment.py | 3 +- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index efa6b249b3..de8dc0e90a 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -193,7 +193,6 @@ let check_and_set_availability = function(frm) { d.hide(); frm.enable_save(); frm.save(); - frm.enable_save(); d.get_primary_btn().attr('disabled', true); } }); @@ -400,6 +399,7 @@ let create_vital_signs = function(frm) { frappe.route_options = { 'patient': frm.doc.patient, 'appointment': frm.doc.name, + 'company': frm.doc.company }; frappe.new_doc('Vital Signs'); }; @@ -432,6 +432,7 @@ frappe.ui.form.on('Patient Appointment', 'practitioner', function(frm) { callback: function (data) { frappe.model.set_value(frm.doctype, frm.docname, 'department', data.message.department); frappe.model.set_value(frm.doctype, frm.docname, 'paid_amount', data.message.op_consulting_charge); + frappe.model.set_value(frm.doctype, frm.docname, 'billing_item', data.message.op_consulting_charge_item); } }); } diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index 7f9a671d47..81f7597563 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -14,13 +14,16 @@ "patient_name", "patient_sex", "patient_age", - "inpatient_record", "column_break_1", - "status", - "procedure_template", - "get_procedure_from_encounter", - "procedure_prescription", + "inpatient_record", + "company", "service_unit", + "status", + "section_break_11", + "get_procedure_from_encounter", + "column_break_13", + "procedure_template", + "procedure_prescription", "section_break_12", "practitioner", "department", @@ -32,9 +35,9 @@ "duration", "section_break_16", "mode_of_payment", - "paid_amount", - "company", + "billing_item", "column_break_2", + "paid_amount", "invoiced", "ref_sales_invoice", "section_break_3", @@ -114,7 +117,8 @@ "label": "Procedure Prescription", "no_copy": 1, "options": "Procedure Prescription", - "print_hide": 1 + "print_hide": 1, + "read_only": 1 }, { "fieldname": "service_unit", @@ -140,6 +144,7 @@ "set_only_once": 1 }, { + "fetch_from": "practitioner.department", "fieldname": "department", "fieldtype": "Link", "ignore_user_permissions": 1, @@ -234,12 +239,9 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, "label": "Company", "no_copy": 1, - "options": "Company", - "print_hide": 1, - "report_hide": 1 + "options": "Company" }, { "collapsible": 1, @@ -282,10 +284,25 @@ "label": "Series", "options": "HLC-APP-.YYYY.-", "set_only_once": 1 + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "fieldname": "billing_item", + "fieldtype": "Link", + "label": "Billing Item", + "options": "Item", + "read_only": 1 } ], "links": [], - "modified": "2020-03-27 11:27:33.773195", + "modified": "2020-04-07 11:16:34.981240", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Appointment", diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index a2d9d0240f..3786bf2861 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -340,7 +340,8 @@ def make_encounter(source_name, target_doc=None): ['medical_department', 'department'], ['patient_sex', 'patient_sex'], ['encounter_date', 'appointment_date'], - ['invoiced', 'invoiced'] + ['invoiced', 'invoiced'], + ['company', 'company'] ] } }, target_doc) From b66833edfe2dd16499d37b50bce2d02f295e6d5a Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 12:11:59 +0530 Subject: [PATCH 20/96] fix: company when creatign procedure, practitioner name added --- .../patient_encounter/patient_encounter.js | 9 ++++--- .../patient_encounter/patient_encounter.json | 24 +++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js index 83c5d2be9c..0e34164d07 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js @@ -110,7 +110,8 @@ frappe.ui.form.on('Patient Encounter', { 'patient':data.message.patient, 'type': data.message.appointment_type, 'practitioner': data.message.practitioner, - 'invoiced': data.message.invoiced + 'invoiced': data.message.invoiced, + 'company': data.message.company }; frm.set_value(values); } @@ -209,7 +210,8 @@ let create_vital_signs = function (frm) { frappe.route_options = { 'patient': frm.doc.patient, 'appointment': frm.doc.appointment, - 'encounter': frm.doc.name + 'encounter': frm.doc.name, + 'company': frm.doc.company }; frappe.new_doc('Vital Signs'); }; @@ -220,7 +222,8 @@ let create_procedure = function (frm) { } frappe.route_options = { 'patient': frm.doc.patient, - 'medical_department': frm.doc.medical_department + 'medical_department': frm.doc.medical_department, + 'company': frm.doc.company }; frappe.new_doc('Clinical Procedure'); }; diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json index d00e7bc7dd..935935e0c0 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json @@ -17,9 +17,9 @@ "patient_name", "patient_sex", "patient_age", - "company", "column_break_6", "practitioner", + "practitioner_name", "medical_department", "encounter_date", "encounter_time", @@ -43,6 +43,8 @@ "sb_procedures", "procedure_prescription", "encounter_comment", + "sb_refs", + "company", "amended_from" ], "fields": [ @@ -80,7 +82,6 @@ "fieldname": "patient", "fieldtype": "Link", "ignore_user_permissions": 1, - "in_list_view": 1, "in_standard_filter": 1, "label": "Patient", "options": "Patient", @@ -110,7 +111,6 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, "label": "Company", "options": "Company" }, @@ -121,7 +121,6 @@ { "fieldname": "practitioner", "fieldtype": "Link", - "in_list_view": 1, "in_standard_filter": 1, "label": "Healthcare Practitioner", "options": "Healthcare Practitioner", @@ -272,7 +271,6 @@ "fieldname": "medical_department", "fieldtype": "Link", "ignore_user_permissions": 1, - "in_list_view": 1, "in_standard_filter": 1, "label": "Department", "options": "Medical Department", @@ -287,11 +285,23 @@ { "fieldname": "column_break_17", "fieldtype": "Column Break" + }, + { + "fieldname": "sb_refs", + "fieldtype": "Section Break" + }, + { + "fetch_from": "practitioner.practitioner_name", + "fieldname": "practitioner_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Practitioner Name", + "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-02-27 12:42:21.751964", + "modified": "2020-04-03 23:06:16.348846", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Encounter", @@ -318,7 +328,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "title_field": "patient", + "title_field": "patient_name", "track_changes": 1, "track_seen": 1 } \ No newline at end of file From aa1b9b57ac1a6bc3841685cdc3f01e24e1948784 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:30:00 +0530 Subject: [PATCH 21/96] feat: multi company support --- .../healthcare/doctype/lab_test/lab_test.js | 9 ++++---- .../healthcare/doctype/lab_test/lab_test.json | 15 ++++++------- .../healthcare/doctype/lab_test/lab_test.py | 21 +++++++++++-------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.js b/erpnext/healthcare/doctype/lab_test/lab_test.js index 5b3f4c705a..e7e44dcb48 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.js +++ b/erpnext/healthcare/doctype/lab_test/lab_test.js @@ -137,7 +137,7 @@ var get_lab_test_prescribed = function(frm){ }); } else{ - frappe.msgprint(__("Please select Patient to get Lab Tests")); + frappe.msgprint(__("Please select a Patient to get Lab Orders")); } }; @@ -180,9 +180,10 @@ var show_lab_tests = function(frm, result){ return false; }); }); - if(!result){ - var msg = "There are no Lab Test prescribed for "+frm.doc.patient; - $(repl('
%(msg)s
', {msg: msg})).appendTo(html_field); + if(!result.length){ + var msg = "No Lab Orders found for patient " + frm.doc.patient_name + ""; + html_field.empty(); + $(repl('
%(msg)s
', {msg: msg})).appendTo(html_field); } d.show(); }; diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.json b/erpnext/healthcare/doctype/lab_test/lab_test.json index ccbc24b3fb..17dc1edd8c 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.json +++ b/erpnext/healthcare/doctype/lab_test/lab_test.json @@ -9,18 +9,18 @@ "document_type": "Document", "engine": "InnoDB", "field_order": [ - "inpatient_record", "naming_series", - "invoiced", "patient", "patient_name", "patient_age", "patient_sex", - "practitioner", + "report_preference", "email", "mobile", - "company", + "practitioner", "c_b", + "inpatient_record", + "company", "department", "status", "submitted_date", @@ -31,7 +31,7 @@ "employee_name", "employee_designation", "user", - "report_preference", + "invoiced", "sb_first", "lab_test_name", "column_break_26", @@ -153,7 +153,7 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, + "in_standard_filter": 1, "label": "Company", "options": "Company", "print_hide": 1, @@ -168,6 +168,7 @@ "fieldname": "department", "fieldtype": "Link", "ignore_user_permissions": 1, + "in_standard_filter": 1, "label": "Department", "options": "Medical Department", "search_index": 1 @@ -427,7 +428,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-03-23 19:37:06.617764", + "modified": "2020-04-04 19:16:29.131168", "modified_by": "Administrator", "module": "Healthcare", "name": "Lab Test", diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.py b/erpnext/healthcare/doctype/lab_test/lab_test.py index 4e4015d2f0..e49429ea62 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.py +++ b/erpnext/healthcare/doctype/lab_test/lab_test.py @@ -69,9 +69,9 @@ def create_multiple(doctype, docname): lab_test_created = create_lab_test_from_encounter(docname) if lab_test_created: - frappe.msgprint(_("Lab Test(s) "+lab_test_created+" created.")) + frappe.msgprint(_("Lab Test(s) " + lab_test_created + " created.")) else: - frappe.msgprint(_("No Lab Test created")) + frappe.msgprint(_("No Lab Tests created")) def create_lab_test_from_encounter(encounter_id): lab_test_created = False @@ -87,7 +87,7 @@ def create_lab_test_from_encounter(encounter_id): for lab_test_id in lab_test_ids: template = get_lab_test_template(lab_test_id[1]) if template: - lab_test = create_lab_test_doc(lab_test_id[2], encounter.practitioner, patient, template) + lab_test = create_lab_test_doc(lab_test_id[2], encounter.practitioner, patient, template, encounter.company) lab_test.save(ignore_permissions = True) frappe.db.set_value("Lab Prescription", lab_test_id[0], "lab_test_created", 1) if not lab_test_created: @@ -111,7 +111,7 @@ def create_lab_test_from_invoice(invoice_name): if lab_test_created != 1: template = get_lab_test_template(item.item_code) if template: - lab_test = create_lab_test_doc(True, invoice.ref_practitioner, patient, template) + lab_test = create_lab_test_doc(True, invoice.ref_practitioner, patient, template, company, invoice.company) if item.reference_dt == "Lab Prescription": lab_test.prescription = item.reference_dn lab_test.save(ignore_permissions = True) @@ -121,7 +121,7 @@ def create_lab_test_from_invoice(invoice_name): if not lab_tests_created: lab_tests_created = lab_test.name else: - lab_tests_created += ", "+lab_test.name + lab_tests_created += ", " + lab_test.name return lab_tests_created def get_lab_test_template(item): @@ -141,7 +141,7 @@ def check_template_exists(item): return template_exists return False -def create_lab_test_doc(invoiced, practitioner, patient, template): +def create_lab_test_doc(invoiced, practitioner, patient, template, company): lab_test = frappe.new_doc("Lab Test") lab_test.invoiced = invoiced lab_test.practitioner = practitioner @@ -150,11 +150,12 @@ def create_lab_test_doc(invoiced, practitioner, patient, template): lab_test.patient_sex = patient.sex lab_test.email = patient.email lab_test.mobile = patient.mobile + lab_test.report_preference = patient.report_preference lab_test.department = template.department lab_test.template = template.name lab_test.lab_test_group = template.lab_test_group lab_test.result_date = getdate() - lab_test.report_preference = patient.report_preference + lab_test.company = company return lab_test def create_normals(template, lab_test): @@ -190,7 +191,7 @@ def create_specials(template, lab_test): special.require_result_value = 1 special.template = template.name -def create_sample_doc(template, patient, invoice): +def create_sample_doc(template, patient, invoice, company = None): if template.sample: sample_exists = frappe.db.exists({ "doctype": "Sample Collection", @@ -221,6 +222,8 @@ def create_sample_doc(template, patient, invoice): sample_collection.sample = template.sample sample_collection.sample_uom = template.sample_uom sample_collection.sample_qty = template.sample_qty + sample_collection.company = company + if(template.sample_details): sample_collection.sample_details = "Test :" + (template.get("lab_test_name") or template.get("template")) +"\n"+"Collection Detials:\n\t"+template.sample_details sample_collection.save(ignore_permissions=True) @@ -229,7 +232,7 @@ def create_sample_doc(template, patient, invoice): def create_sample_collection(lab_test, template, patient, invoice): if(frappe.db.get_value("Healthcare Settings", None, "create_sample_collection_for_lab_test") == "1"): - sample_collection = create_sample_doc(template, patient, invoice) + sample_collection = create_sample_doc(template, patient, invoice, lab_test.company) if(sample_collection): lab_test.sample = sample_collection.name return lab_test From d856a9aed3931274795006563a1daec11ebabd02 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:32:35 +0530 Subject: [PATCH 22/96] feat: multi company support --- .../clinical_procedure/clinical_procedure.js | 35 ++++++++--- .../clinical_procedure.json | 58 ++++++++++++++++--- .../clinical_procedure/clinical_procedure.py | 2 +- 3 files changed, 79 insertions(+), 16 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js index 5f36bdd95c..213fa3de1f 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js @@ -175,16 +175,37 @@ frappe.ui.form.on('Clinical Procedure', { name: frm.doc.appointment }, callback: function(data) { - frm.set_value('patient', data.message.patient); - frm.set_value('procedure_template', data.message.procedure_template); - frm.set_value('medical_department', data.message.department); - frm.set_value('start_date', data.message.appointment_date); - frm.set_value('start_time', data.message.appointment_time); - frm.set_value('notes', data.message.notes); - frm.set_value('service_unit', data.message.service_unit); + let values = { + 'patient':data.message.patient, + 'procedure_template': data.message.procedure_template, + 'medical_department': data.message.department, + 'start_date': data.message.appointment_date, + 'start_time': data.message.appointment_time, + 'notes': data.message.notes, + 'service_unit': data.message.service_unit, + 'company': data.message.company + } + frm.set_value(values); } }); } + else{ + let values = { + 'patient': '', + 'patient_name': '', + 'patient_sex': '', + 'patient_age': '', + 'medical_department': '', + 'procedure_template': '', + 'start_date': '', + 'start_time': '', + 'notes': '', + 'service_unit': '', + 'inpatient_record': '' + // 'inpatient_status': '' + }; + frm.set_value(values); + } }, procedure_template: function(frm) { diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json index 3c936bbf27..59a87eccde 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json @@ -7,16 +7,18 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "inpatient_record", "naming_series", - "procedure_template", "appointment", + "column_break_30", + "procedure_template", + "section_break_6", "patient", + "patient_name", "patient_sex", "patient_age", - "prescription", "medical_department", "practitioner", + "practitioner_name", "column_break_7", "status", "service_unit", @@ -26,7 +28,6 @@ "sample", "invoiced", "notes", - "company", "consumables_section", "consume_stock", "items", @@ -36,6 +37,11 @@ "consumable_total_amount", "column_break_27", "consumption_details", + "sb_refs", + "inpatient_record", + "company", + "column_break_34", + "prescription", "amended_from" ], "fields": [ @@ -56,7 +62,7 @@ { "fieldname": "appointment", "fieldtype": "Link", - "in_list_view": 1, + "in_standard_filter": 1, "label": "Appointment", "options": "Patient Appointment" }, @@ -64,7 +70,7 @@ "fetch_from": "inpatient_record.patient", "fieldname": "patient", "fieldtype": "Link", - "in_list_view": 1, + "in_standard_filter": 1, "label": "Patient", "options": "Patient", "reqd": 1 @@ -88,17 +94,20 @@ "fieldtype": "Link", "hidden": 1, "label": "Procedure Prescription", - "options": "Procedure Prescription" + "options": "Procedure Prescription", + "read_only": 1 }, { "fieldname": "medical_department", "fieldtype": "Link", + "in_standard_filter": 1, "label": "Medical Department", "options": "Medical Department" }, { "fieldname": "practitioner", "fieldtype": "Link", + "in_standard_filter": 1, "label": "Healthcare Practitioner", "options": "Healthcare Practitioner" }, @@ -237,11 +246,43 @@ { "fieldname": "section_break_24", "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_30", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "collapsible": 1, + "fieldname": "sb_refs", + "fieldtype": "Section Break" + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "fetch_from": "practitioner.practitioner_name", + "fieldname": "practitioner_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Practitioner Name", + "read_only": 1 + }, + { + "fieldname": "column_break_34", + "fieldtype": "Column Break" } ], "is_submittable": 1, "links": [], - "modified": "2020-03-02 11:44:27.970651", + "modified": "2020-04-03 23:06:04.009856", "modified_by": "Administrator", "module": "Healthcare", "name": "Clinical Procedure", @@ -263,5 +304,6 @@ "restrict_to_domain": "Healthcare", "sort_field": "modified", "sort_order": "DESC", + "title_field": "patient_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py index db3afc8807..56617e5258 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py @@ -37,7 +37,7 @@ class ClinicalProcedure(Document): template = frappe.get_doc('Clinical Procedure Template', self.procedure_template) if template.sample: patient = frappe.get_doc('Patient', self.patient) - sample_collection = create_sample_doc(template, patient, None) + sample_collection = create_sample_doc(template, patient, None, self.company) frappe.db.set_value('Clinical Procedure', self.name, 'sample', sample_collection.name) self.reload() From 05e34ee9fb1b1bbf4a9c49b83de63eb1ecd8776f Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:34:28 +0530 Subject: [PATCH 23/96] company field added --- .../inpatient_record/inpatient_record.json | 1165 ++++------------- 1 file changed, 220 insertions(+), 945 deletions(-) diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json index 92c11fbb4d..c1b516d536 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json @@ -1,980 +1,255 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2018-07-11 17:48:51.404139", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "naming_series:", + "creation": "2018-07-11 17:48:51.404139", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "section_break_1", + "naming_series", + "patient", + "patient_name", + "gender", + "blood_group", + "dob", + "mobile", + "email", + "phone", + "column_break_8", + "company", + "status", + "scheduled_date", + "admitted_datetime", + "expected_discharge", + "discharge_date", + "references", + "cb_admission", + "admission_practitioner", + "admission_encounter", + "cb_discharge", + "discharge_practitioner", + "discharge_encounter", + "sb_inpatient_occupancy", + "inpatient_occupancies", + "btn_transfer", + "sb_discharge_note", + "discharge_note" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_1", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_1", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 0, - "options": "HLC-INP-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "hidden": 1, + "label": "Series", + "options": "HLC-INP-.YYYY.-" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "patient", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Patient", - "length": 0, - "no_copy": 0, - "options": "Patient", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "patient", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.patient_name", - "fieldname": "patient_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Patient Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.sex", - "fieldname": "gender", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Gender", - "length": 0, - "no_copy": 0, - "options": "Gender", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.sex", + "fieldname": "gender", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.blood_group", - "fieldname": "blood_group", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Blood Group", - "length": 0, - "no_copy": 0, - "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.blood_group", + "fieldname": "blood_group", + "fieldtype": "Select", + "label": "Blood Group", + "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "dob", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Date of birth", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.dob", + "fieldname": "dob", + "fieldtype": "Date", + "label": "Date of birth", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.mobile", - "fieldname": "mobile", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Mobile", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.mobile", + "fieldname": "mobile", + "fieldtype": "Data", + "label": "Mobile", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.email", - "fieldname": "email", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Email", - "length": 0, - "no_copy": 0, - "options": "Email", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.email", + "fieldname": "email", + "fieldtype": "Data", + "label": "Email", + "options": "Email", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.phone", - "fieldname": "phone", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Phone", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.phone", + "fieldname": "phone", + "fieldtype": "Data", + "label": "Phone", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_8", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 0, - "options": "Admission Scheduled\nAdmitted\nDischarge Scheduled\nDischarged", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Admission Scheduled\nAdmitted\nDischarge Scheduled\nDischarged", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "scheduled_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Admission Schedule Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Today", + "fieldname": "scheduled_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Admission Schedule Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "admitted_datetime", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Admitted Datetime", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Today", + "fieldname": "admitted_datetime", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Admitted Datetime" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "expected_discharge", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Expected Discharge", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "expected_discharge", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Expected Discharge" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Discharge Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "discharge_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Discharge Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "references", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "References", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "collapsible": 1, + "fieldname": "references", + "fieldtype": "Section Break", + "label": "References" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cb_admission", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Admission", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "cb_admission", + "fieldtype": "Column Break", + "label": "Admission" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "admission_practitioner", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Healthcare Practitioner", - "length": 0, - "no_copy": 0, - "options": "Healthcare Practitioner", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "admission_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "admission_encounter", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Patient Encounter", - "length": 0, - "no_copy": 0, - "options": "Patient Encounter", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "admission_encounter", + "fieldtype": "Link", + "label": "Patient Encounter", + "options": "Patient Encounter", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cb_discharge", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Discharge", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "cb_discharge", + "fieldtype": "Column Break", + "label": "Discharge" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_practitioner", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Healthcare Practitioner", - "length": 0, - "no_copy": 0, - "options": "Healthcare Practitioner", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "discharge_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_encounter", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Patient Encounter", - "length": 0, - "no_copy": 0, - "options": "Patient Encounter", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "discharge_encounter", + "fieldtype": "Link", + "label": "Patient Encounter", + "options": "Patient Encounter", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sb_inpatient_occupancy", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Inpatient Occupancy", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "sb_inpatient_occupancy", + "fieldtype": "Section Break", + "label": "Inpatient Occupancy" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "inpatient_occupancies", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "options": "Inpatient Occupancy", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "inpatient_occupancies", + "fieldtype": "Table", + "options": "Inpatient Occupancy", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "btn_transfer", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Transfer", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "btn_transfer", + "fieldtype": "Button", + "label": "Transfer" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.status != \"Admission Scheduled\"", - "fieldname": "sb_discharge_note", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Discharge Note", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.status != \"Admission Scheduled\"", + "fieldname": "sb_discharge_note", + "fieldtype": "Section Break", + "label": "Discharge Note" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_note", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "discharge_note", + "fieldtype": "Text Editor" + }, + { + "fetch_from": "admission_encounter.company", + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 14:44:43.168245", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Inpatient Record", - "name_case": "", - "owner": "Administrator", + ], + "links": [], + "modified": "2020-04-07 13:13:39.351977", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Inpatient Record", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "search_fields": "patient", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "patient", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} + ], + "restrict_to_domain": "Healthcare", + "search_fields": "patient", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "patient", + "track_changes": 1 +} \ No newline at end of file From 4f3c46840f54a6c3399ab6f09a934503517c05d4 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:42:03 +0530 Subject: [PATCH 24/96] feat: multi-company billing sales onvoice - filter get items based on company utils - company filters in all get item helper methods utils - refactor appointemnt items --- .../doctype/sales_invoice/sales_invoice.js | 7 +- erpnext/healthcare/utils.py | 96 ++++++++++--------- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 6f78db2ccc..7741842fca 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -919,7 +919,7 @@ var get_healthcare_services_to_invoice = function(frm) { if(patient && patient!=selected_patient){ selected_patient = patient; var method = "erpnext.healthcare.utils.get_healthcare_services_to_invoice"; - var args = {patient: patient}; + var args = {patient: patient, company: frm.doc.company}; var columns = (["service", "reference_name", "reference_type"]); get_healthcare_items(frm, true, $results, $placeholder, method, args, columns); } @@ -1063,7 +1063,10 @@ var get_drugs_to_invoice = function(frm) { description:'Quantity will be calculated only for items which has "Nos" as UoM. You may change as required for each invoice item.', get_query: function(doc) { return { - filters: { patient: dialog.get_value("patient"), docstatus: 1 } + filters: { patient: dialog.get_value("patient"), + company: frm.doc.company, + docstatus: 1 + } }; } }, diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py index 246242ad84..e9cc597e81 100644 --- a/erpnext/healthcare/utils.py +++ b/erpnext/healthcare/utils.py @@ -3,82 +3,84 @@ # For license information, please see license.txt from __future__ import unicode_literals +import math +import json import frappe from frappe import _ -import math 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 from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple @frappe.whitelist() -def get_healthcare_services_to_invoice(patient): +def get_healthcare_services_to_invoice(patient, company): patient = frappe.get_doc('Patient', patient) + items_to_invoice = [] if patient: validate_customer_created(patient) - items_to_invoice = [] - patient_appointments = frappe.get_list( - 'Patient Appointment', - fields='*', - filters={'patient': patient.name, 'invoiced': 0}, - order_by='appointment_date' - ) - if patient_appointments: - items_to_invoice = get_fee_validity(patient_appointments) + # Customer validated, build a list of billable services + items_to_invoice += get_appointments_to_invoice(patient, company) + items_to_invoice += get_encounters_to_invoice(patient, company) + items_to_invoice += get_lab_tests_to_invoice(patient, company) + items_to_invoice += get_clinical_procedures_to_invoice(patient, company) + items_to_invoice += get_inpatient_services_to_invoice(patient, company) - encounters = get_encounters_to_invoice(patient) - lab_tests = get_lab_tests_to_invoice(patient) - clinical_procedures = get_clinical_procedures_to_invoice(patient) - inpatient_services = get_inpatient_services_to_invoice(patient) - - items_to_invoice += encounters + lab_tests + clinical_procedures + inpatient_services return items_to_invoice + def validate_customer_created(patient): if not frappe.db.get_value('Patient', patient.name, 'customer'): msg = _("Please set a Customer linked to the Patient") msg += " {0}".format(patient.name) frappe.throw(msg, title=_('Customer Not Found')) -def get_fee_validity(patient_appointments): - if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'): - return - items_to_invoice = [] +def get_appointments_to_invoice(patient, company): + appointments_to_invoice = [] + patient_appointments = frappe.get_list( + 'Patient Appointment', + fields = '*', + filters = {'patient': patient.name, 'company': company, 'invoiced': 0}, + order_by = 'appointment_date' + ) + for appointment in patient_appointments: + # Procedure Appointments if appointment.procedure_template: if frappe.db.get_value('Clinical Procedure Template', appointment.procedure_template, 'is_billable'): - items_to_invoice.append({ + appointments_to_invoice.append({ 'reference_type': 'Patient Appointment', 'reference_name': appointment.name, 'service': appointment.procedure_template }) + # Consultation Appointments, should check fee validity else: - fee_validity = frappe.db.exists('Fee Validity Reference', {'appointment': appointment.name}) - if not fee_validity: - practitioner_charge = 0 - income_account = None - service_item = None - if appointment.practitioner: - service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment) - income_account = get_income_account(appointment.practitioner, appointment.company) - items_to_invoice.append({ - 'reference_type': 'Patient Appointment', - 'reference_name': appointment.name, - 'service': service_item, - 'rate': practitioner_charge, - 'income_account': income_account - }) + if frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups') and \ + frappe.db.exists('Fee Validity Reference', {'appointment': appointment.name}): + continue # Skip invoicing, fee validty present + practitioner_charge = 0 + income_account = None + service_item = None + if appointment.practitioner: + service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment) + income_account = get_income_account(appointment.practitioner, appointment.company) + appointments_to_invoice.append({ + 'reference_type': 'Patient Appointment', + 'reference_name': appointment.name, + 'service': service_item, + 'rate': practitioner_charge, + 'income_account': income_account + }) - return items_to_invoice + return appointments_to_invoice -def get_encounters_to_invoice(patient): +def get_encounters_to_invoice(patient, company): encounters_to_invoice = [] encounters = frappe.get_list( 'Patient Encounter', fields=['*'], - filters={'patient': patient.name, 'invoiced': False, 'docstatus': 1} + filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1} ) if encounters: for encounter in encounters: @@ -101,12 +103,12 @@ def get_encounters_to_invoice(patient): return encounters_to_invoice -def get_lab_tests_to_invoice(patient): +def get_lab_tests_to_invoice(patient, company): lab_tests_to_invoice = [] lab_tests = frappe.get_list( 'Lab Test', fields=['name', 'template'], - filters={'patient': patient.name, 'invoiced': False, 'docstatus': 1} + filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1} ) for lab_test in lab_tests: item, is_billable = frappe.get_cached_value('Lab Test Template', lab_test.lab_test_code, ['item', 'is_billable']) @@ -142,12 +144,12 @@ def get_lab_tests_to_invoice(patient): return lab_tests_to_invoice -def get_clinical_procedures_to_invoice(patient): +def get_clinical_procedures_to_invoice(patient, company): clinical_procedures_to_invoice = [] procedures = frappe.get_list( 'Clinical Procedure', fields='*', - filters={'patient': patient.name, 'invoiced': False} + filters={'patient': patient.name, 'company': company, 'invoiced': False} ) for procedure in procedures: if not procedure.appointment: @@ -203,7 +205,7 @@ def get_clinical_procedures_to_invoice(patient): return clinical_procedures_to_invoice -def get_inpatient_services_to_invoice(patient): +def get_inpatient_services_to_invoice(patient, company): services_to_invoice = [] inpatient_services = frappe.db.sql( ''' @@ -213,10 +215,11 @@ def get_inpatient_services_to_invoice(patient): `tabInpatient Record` ip, `tabInpatient Occupancy` io WHERE ip.patient=%s + and ip.company=%s and io.parent=ip.name and io.left=1 and io.invoiced=0 - ''', (patient.name), as_dict=1) + ''', (patient.name, company), as_dict=1) for inpatient_occupancy in inpatient_services: service_unit_type = frappe.db.get_value('Healthcare Service Unit', inpatient_occupancy.service_unit, 'service_unit_type') @@ -376,6 +379,7 @@ def check_fee_validity(appointment): def manage_fee_validity(appointment): fee_validity = check_fee_validity(appointment) + if fee_validity: if appointment.status == 'Cancelled' and fee_validity.visited > 0: fee_validity.visited -= 1 From 8179804405f6955ba35487a08516ce9486f9be3c Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 14 Apr 2020 23:05:11 +0530 Subject: [PATCH 25/96] refactor: Quoted Item Comparison Report - Refactored Report View - Added Chart - Added 'valid_till' date in Supplier Quotation - Added patch to set valid_till and daily job to set expiry status --- .../supplier_quotation/supplier_quotation.js | 4 + .../supplier_quotation.json | 13 +- .../supplier_quotation/supplier_quotation.py | 16 +- .../quoted_item_comparison.js | 91 ++++++++-- .../quoted_item_comparison.py | 161 ++++++++++-------- erpnext/hooks.py | 3 +- erpnext/patches.txt | 1 + ...t_valid_till_date_in_supplier_quotation.py | 8 + 8 files changed, 207 insertions(+), 90 deletions(-) create mode 100644 erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js index 16061c61ba..1b8b40459f 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js @@ -18,6 +18,10 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext refresh: function() { var me = this; this._super(); + + if (this.frm.doc.__islocal && !this.frm.doc.valid_till) { + this.frm.set_value('valid_till', frappe.datetime.add_months(this.frm.doc.transaction_date, 1)); + } if (this.frm.doc.docstatus === 1) { cur_frm.add_custom_button(__("Purchase Order"), this.make_purchase_order, __('Create')); diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 82fc6285bc..6964e783ee 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -13,9 +13,10 @@ "supplier", "supplier_name", "column_break1", - "transaction_date", - "amended_from", "company", + "transaction_date", + "valid_till", + "amended_from", "address_section", "supplier_address", "contact_person", @@ -791,13 +792,19 @@ "options": "Opportunity", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "valid_till", + "fieldtype": "Date", + "label": "Valid Till", + "reqd": 1 } ], "icon": "fa fa-shopping-cart", "idx": 29, "is_submittable": 1, "links": [], - "modified": "2019-12-30 19:17:28.208693", + "modified": "2020-04-14 22:43:32.248415", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py index 5b4356a747..baf245735a 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt, nowdate, add_days +from frappe.utils import flt, nowdate, add_days, getdate from frappe.model.mapper import get_mapped_doc from erpnext.controllers.buying_controller import BuyingController @@ -28,6 +28,7 @@ class SupplierQuotation(BuyingController): validate_for_items(self) self.validate_with_previous_doc() self.validate_uom_is_integer("uom", "qty") + self.validate_valid_till() def on_submit(self): frappe.db.set(self, "status", "Submitted") @@ -52,6 +53,11 @@ class SupplierQuotation(BuyingController): "is_child_table": True } }) + + def validate_valid_till(self): + if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date): + frappe.throw(_("Valid till Date cannot be before Transaction Date")) + def update_rfq_supplier_status(self, include_me): rfq_list = set([]) for item in self.items: @@ -158,3 +164,11 @@ def make_quotation(source_name, target_doc=None): }, target_doc) return doclist + +def set_expired_status(): + frappe.db.sql(""" + UPDATE + `tabSupplier Quotation` SET `status` = 'Expired' + WHERE + `status` not in ('Cancelled', 'Stopped') AND `valid_till` < %s + """, (nowdate())) \ No newline at end of file diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js index 3d05612c9e..f331beb49e 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js @@ -5,13 +5,11 @@ frappe.query_reports["Quoted Item Comparison"] = { filters: [ { fieldtype: "Link", - label: __("Supplier Quotation"), - options: "Supplier Quotation", - fieldname: "supplier_quotation", - default: "", - get_query: () => { - return { filters: { "docstatus": ["<", 2] } } - } + label: __("Company"), + options: "Company", + fieldname: "company", + default: frappe.defaults.get_user_default("Company"), + "reqd": 1 }, { reqd: 1, @@ -37,8 +35,83 @@ frappe.query_reports["Quoted Item Comparison"] = { } } } + }, + { + fieldtype: "Link", + label: __("Supplier Quotation"), + options: "Supplier Quotation", + fieldname: "supplier_quotation", + default: "", + get_query: () => { + return { filters: { "docstatus": ["<", 2] } } + } + }, + { + fieldtype: "Link", + label: __("Request for Quotation"), + options: "Request for Quotation", + fieldname: "request_for_quotation", + default: "", + get_query: () => { + return { filters: { "docstatus": ["<", 2] } } + } } ], + + prepare_chart_data: (result) => { + let supplier_wise_map = {}, data_points_map = {}; + let qty_list = result.map(res=> res.qty); + qty_list = new Set(qty_list); + + // create supplier wise map like in Report + for(let res of result){ + if(!(res.supplier in supplier_wise_map)){ + supplier_wise_map[res.supplier]= {}; + } + supplier_wise_map[res.supplier][res.qty] = res.price; + } + + // create datapoints for each qty + for(let supplier of Object.keys(supplier_wise_map)) { + let row = supplier_wise_map[supplier]; + for(let qty of qty_list){ + if(!data_points_map[qty]){ + data_points_map[qty] = [] + } + if(row[qty]){ + data_points_map[qty].push(row[qty]); + } + else{ + data_points_map[qty].push(null); + } + } + } + + let dataset = []; + qty_list.forEach((qty) => { + let datapoints = { + 'name': 'Price for Qty ' + qty, + 'values': data_points_map[qty] + } + dataset.push(datapoints); + + }); + return dataset; + }, + + get_chart_data: function (columns, result) { + let suppliers = result.filter(d => d.supplier_name).map(res => res.supplier_name); + let dataset = frappe.query_reports["Quoted Item Comparison"].prepare_chart_data(result); + + return { + data: { + labels: suppliers, + datasets: dataset + }, + type: 'bar' + } + }, + onload: (report) => { // Create a button for setting the default supplier report.page.add_inner_button(__("Select Default Supplier"), () => { @@ -102,6 +175,4 @@ frappe.query_reports["Quoted Item Comparison"] = { }); dialog.show(); } -} - - +} \ No newline at end of file diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py index 5aff6bacae..bb1067a05a 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py @@ -2,103 +2,114 @@ # For license information, please see license.txt from __future__ import unicode_literals -from erpnext.setup.utils import get_exchange_rate -from frappe.utils import flt, cint import frappe +from frappe.utils import flt, cint +from collections import defaultdict +from erpnext.setup.utils import get_exchange_rate def execute(filters=None): - qty_list = get_quantity_list(filters.item) - data = get_quote_list(filters.item, qty_list) - columns = get_columns(qty_list) + conditions = get_conditions(filters) + data = get_data(filters, conditions) + columns = get_columns() return columns, data - -def get_quote_list(item, qty_list): - out = [] + +def get_data(filters, conditions): + out, suppliers = [], [] + item = filters.get("item") + if not item: return [] - suppliers = [] - price_data = [] company_currency = frappe.db.get_default("currency") - float_precision = cint(frappe.db.get_default("float_precision")) or 2 - # Get the list of suppliers - for root in frappe.db.sql("""select parent, qty, rate from `tabSupplier Quotation Item` - where item_code=%s and docstatus < 2""", item, as_dict=1): - for splr in frappe.db.sql("""select supplier from `tabSupplier Quotation` - where name =%s and docstatus < 2""", root.parent, as_dict=1): - ip = frappe._dict({ - "supplier": splr.supplier, - "qty": root.qty, - "parent": root.parent, - "rate": root.rate - }) - price_data.append(ip) - suppliers.append(splr.supplier) + float_precision = cint(frappe.db.get_default("float_precision")) or 2 - #Add a row for each supplier - for root in set(suppliers): - supplier_currency = frappe.db.get_value("Supplier", root, "default_currency") + supplier_quotation_data = frappe.db.sql("""SELECT + sqi.parent, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation, + sq.supplier + FROM + `tabSupplier Quotation Item` sqi, + `tabSupplier Quotation` sq + WHERE + sqi.item_code = '{0}' + AND sqi.parent = sq.name + AND sqi.docstatus < 2 + AND sq.company = '{1}' + AND sq.status != 'Expired' + {2}""".format(item, filters.get("company"), conditions), as_dict=1) + + supplier_wise_map = defaultdict(list) + + for data in supplier_quotation_data: + supplier_currency = frappe.db.get_value("Supplier", data.get("supplier"), "default_currency") if supplier_currency: exchange_rate = get_exchange_rate(supplier_currency, company_currency) else: exchange_rate = 1 - row = frappe._dict({ - "supplier_name": root - }) - for col in qty_list: - # Get the quantity for this row - for item_price in price_data: - if str(item_price.qty) == col.key and item_price.supplier == root: - row[col.key] = flt(item_price.rate * exchange_rate, float_precision) - row[col.key + "QUOTE"] = item_price.parent - break - else: - row[col.key] = "" - row[col.key + "QUOTE"] = "" - out.append(row) - - return out - -def get_quantity_list(item): - out = [] - - if item: - qty_list = frappe.db.sql("""select distinct qty from `tabSupplier Quotation Item` - where ifnull(item_code,'')=%s and docstatus < 2 order by qty""", item, as_dict=1) + row = { + "quotation": data.get("parent"), + "qty": data.get("qty"), + "price": flt(data.get("rate") * exchange_rate, float_precision), + "request_for_quotation": data.get("request_for_quotation"), + "supplier": data.get("supplier") # used for chart generation + } - for qt in qty_list: - col = frappe._dict({ - "key": str(qt.qty), - "label": "Qty: " + str(int(qt.qty)) - }) - out.append(col) + supplier_wise_map[data.supplier].append(row) + suppliers.append(data.supplier) + + suppliers = set(suppliers) + + for supplier in suppliers: + supplier_wise_map[supplier][0].update({"supplier_name": supplier}) + for entry in supplier_wise_map[supplier]: + out.append(entry) return out - -def get_columns(qty_list): + +def get_conditions(filters): + conditions = "" + + if filters.get("request_for_quotation"): + conditions += " AND sqi.request_for_quotation = '{0}' ".format(filters.get("request_for_quotation")) + + return conditions + + +def get_columns(): columns = [{ "fieldname": "supplier_name", "label": "Supplier", "fieldtype": "Link", "options": "Supplier", "width": 200 - }] - - for qty in qty_list: - columns.append({ - "fieldname": qty.key, - "label": qty.label, - "fieldtype": "Currency", - "options": "currency", - "width": 80 - }) - columns.append({ - "fieldname": qty.key + "QUOTE", - "label": "Quotation", - "fieldtype": "Link", - "options": "Supplier Quotation", - "width": 90 - }) + }, + { + "fieldname": "quotation", + "label": "Supplier Quotation", + "fieldtype": "Link", + "options": "Supplier Quotation", + "width": 200 + }, + { + "fieldname": "qty", + "label": "Quantity", + "fieldtype": "Float", + "width": 80 + }, + { + "fieldname": "price", + "label": "Price", + "fieldtype": "Currency", + "options": "Company:company:default_currency", + "width": 110 + }, + { + "fieldname": "request_for_quotation", + "label": "Request for Quotation", + "fieldtype": "Link", + "options": "Request for Quotation", + "width": 200 + } + ] return columns \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 6199cb2264..a1f47b8f9c 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -304,7 +304,8 @@ scheduler_events = { "erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status", "erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts", "erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status", - "erpnext.selling.doctype.quotation.quotation.set_expired_status" + "erpnext.selling.doctype.quotation.quotation.set_expired_status", + "erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status" ], "daily_long": [ "erpnext.setup.doctype.email_digest.email_digest.send", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 9b5e5d02fb..a2a393b96a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -666,3 +666,4 @@ erpnext.patches.v12_0.recalculate_requested_qty_in_bin erpnext.patches.v12_0.set_total_batch_quantity erpnext.patches.v12_0.rename_mws_settings_fields erpnext.patches.v12_0.set_updated_purpose_in_pick_list +erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation \ No newline at end of file diff --git a/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py b/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py new file mode 100644 index 0000000000..0f24ec6756 --- /dev/null +++ b/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py @@ -0,0 +1,8 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + reload_doc("buying", "doctype", "suppplier_quotation") + frappe.db.sql("""UPDATE `tabSupplier Quotation` + SET valid_till = DATE_ADD(transaction_date , INTERVAL 1 MONTH) + WHERE docstatus < 2""") \ No newline at end of file From 8890b6044d27381a0e9b83614fce6a7db53093f3 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 15 Apr 2020 12:18:10 +0530 Subject: [PATCH 26/96] fix: Patch fix, Travis fix and cleanup - Added UOM column in Report - Removed mandatory on `valid_till` - Added list view indicator for Expired status in Supplier Quotation - Sorted Labels in Chart and syntax cleanup - Made labels Translatable - Fixed patch --- .../supplier_quotation.json | 5 ++-- .../supplier_quotation_list.js | 2 ++ .../quoted_item_comparison.js | 24 +++++++++---------- .../quoted_item_comparison.py | 19 +++++++++++---- ...t_valid_till_date_in_supplier_quotation.py | 2 +- 5 files changed, 31 insertions(+), 21 deletions(-) diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 6964e783ee..3bc441af6d 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -796,15 +796,14 @@ { "fieldname": "valid_till", "fieldtype": "Date", - "label": "Valid Till", - "reqd": 1 + "label": "Valid Till" } ], "icon": "fa fa-shopping-cart", "idx": 29, "is_submittable": 1, "links": [], - "modified": "2020-04-14 22:43:32.248415", + "modified": "2020-04-15 11:44:52.958022", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js index 95554397bb..9f4fecea86 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js @@ -5,6 +5,8 @@ frappe.listview_settings['Supplier Quotation'] = { return [__("Ordered"), "green", "status,=,Ordered"]; } else if(doc.status==="Rejected") { return [__("Lost"), "darkgrey", "status,=,Lost"]; + } else if(doc.status==="Expired") { + return [__("Expired"), "darkgrey", "status,=,Expired"]; } } }; diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js index f331beb49e..fe4abd8c9c 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js @@ -60,28 +60,29 @@ frappe.query_reports["Quoted Item Comparison"] = { prepare_chart_data: (result) => { let supplier_wise_map = {}, data_points_map = {}; - let qty_list = result.map(res=> res.qty); + let qty_list = result.map(res => res.qty); + qty_list.sort(); qty_list = new Set(qty_list); // create supplier wise map like in Report - for(let res of result){ - if(!(res.supplier in supplier_wise_map)){ - supplier_wise_map[res.supplier]= {}; + for (let res of result) { + if (!(res.supplier in supplier_wise_map)) { + supplier_wise_map[res.supplier] = {}; } supplier_wise_map[res.supplier][res.qty] = res.price; } // create datapoints for each qty - for(let supplier of Object.keys(supplier_wise_map)) { + for (let supplier of Object.keys(supplier_wise_map)) { let row = supplier_wise_map[supplier]; - for(let qty of qty_list){ - if(!data_points_map[qty]){ - data_points_map[qty] = [] + for (let qty of qty_list) { + if (!data_points_map[qty]) { + data_points_map[qty] = []; } - if(row[qty]){ + if (row[qty]) { data_points_map[qty].push(row[qty]); } - else{ + else { data_points_map[qty].push(null); } } @@ -90,11 +91,10 @@ frappe.query_reports["Quoted Item Comparison"] = { let dataset = []; qty_list.forEach((qty) => { let datapoints = { - 'name': 'Price for Qty ' + qty, + 'name': __('Price for Qty ') + qty, 'values': data_points_map[qty] } dataset.push(datapoints); - }); return dataset; }, diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py index bb1067a05a..fd7a731198 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe from frappe.utils import flt, cint +from frappe import _ from collections import defaultdict from erpnext.setup.utils import get_exchange_rate @@ -50,6 +51,7 @@ def get_data(filters, conditions): "quotation": data.get("parent"), "qty": data.get("qty"), "price": flt(data.get("rate") * exchange_rate, float_precision), + "uom": data.get("uom"), "request_for_quotation": data.get("request_for_quotation"), "supplier": data.get("supplier") # used for chart generation } @@ -78,34 +80,41 @@ def get_conditions(filters): def get_columns(): columns = [{ "fieldname": "supplier_name", - "label": "Supplier", + "label": _("Supplier"), "fieldtype": "Link", "options": "Supplier", "width": 200 }, { "fieldname": "quotation", - "label": "Supplier Quotation", + "label": _("Supplier Quotation"), "fieldtype": "Link", "options": "Supplier Quotation", "width": 200 }, { "fieldname": "qty", - "label": "Quantity", + "label": _("Quantity"), "fieldtype": "Float", "width": 80 }, { "fieldname": "price", - "label": "Price", + "label": _("Price"), "fieldtype": "Currency", "options": "Company:company:default_currency", "width": 110 }, + { + "fieldname": "uom", + "label": _("UOM"), + "fieldtype": "Link", + "options": "UOM", + "width": 90 + }, { "fieldname": "request_for_quotation", - "label": "Request for Quotation", + "label": _("Request for Quotation"), "fieldtype": "Link", "options": "Request for Quotation", "width": 200 diff --git a/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py b/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py index 0f24ec6756..befa46c31d 100644 --- a/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py +++ b/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import frappe def execute(): - reload_doc("buying", "doctype", "suppplier_quotation") + frappe.reload_doc("buying", "doctype", "suppplier_quotation") frappe.db.sql("""UPDATE `tabSupplier Quotation` SET valid_till = DATE_ADD(transaction_date , INTERVAL 1 MONTH) WHERE docstatus < 2""") \ No newline at end of file From 79b01838c37f6fd6d1efa098b5fa353c2a842048 Mon Sep 17 00:00:00 2001 From: anoop Date: Thu, 16 Apr 2020 12:52:03 +0530 Subject: [PATCH 27/96] fix: set patient while billing, tests fixed --- .../patient_appointment.py | 2 +- .../test_patient_appointment.py | 29 ++++++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index 3786bf2861..cc3492a810 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -119,6 +119,7 @@ def invoice_appointment(appointment_doc): if automate_invoicing and not appointment_invoiced and not fee_validity: sales_invoice = frappe.new_doc('Sales Invoice') + sales_invoice.patient = appointment_doc.patient sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer') sales_invoice.appointment = appointment_doc.name sales_invoice.due_date = getdate() @@ -339,7 +340,6 @@ def make_encounter(source_name, target_doc=None): ['practitioner', 'practitioner'], ['medical_department', 'department'], ['patient_sex', 'patient_sex'], - ['encounter_date', 'appointment_date'], ['invoiced', 'invoiced'], ['company', 'company'] ] diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 7075af5d00..7cdb28e0da 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import unittest import frappe -from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status +from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter from frappe.utils import nowdate, add_days from frappe.utils.make_random import get_random @@ -23,6 +23,14 @@ class TestPatientAppointment(unittest.TestCase): create_encounter(appointment) self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') + def test_start_encounter(self): + patient, medical_department, practitioner = create_healthcare_docs() + appointment = create_appointment(patient, practitioner, add_days(nowdate(), 3)) + encounter = create_encounter(appointment) + self.assertEquals(frappe.db.get_value('Patient Encounter', encounter.name, 'company'), appointment.company) + self.assertEquals(frappe.db.get_value('Patient Encounter', encounter.name, 'practitioner'), appointment.practitioner) + self.assertEquals(frappe.db.get_value('Patient Encounter', encounter.name, 'patient'), appointment.patient) + def test_invoicing(self): patient, medical_department, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) @@ -33,7 +41,11 @@ class TestPatientAppointment(unittest.TestCase): frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2), invoice=1) self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'), 1) - self.assertTrue(frappe.db.get_value('Patient Appointment', appointment.name, 'ref_sales_invoice')) + sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent') + self.assertTrue(sales_invoice_name) + self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'company'), appointment.company) + self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'patient'), appointment.patient) + self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount) def test_appointment_cancel(self): patient, medical_department, practitioner = create_healthcare_docs() @@ -53,8 +65,8 @@ class TestPatientAppointment(unittest.TestCase): appointment = create_appointment(patient, practitioner, nowdate(), invoice=1) update_status(appointment.name, 'Cancelled') # check invoice cancelled - sales_invoice = frappe.db.get_value('Patient Appointment', appointment.name, 'ref_sales_invoice') - self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice, 'status'), 'Cancelled') + 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 create_healthcare_docs(): @@ -90,14 +102,9 @@ def create_patient(): patient = patient.name return patient -def create_encounter(appointment=None): - encounter = frappe.new_doc('Patient Encounter') +def create_encounter(appointment): if appointment: - encounter.appointment = appointment.name - encounter.patient = appointment.patient - encounter.practitioner = appointment.practitioner - encounter.encounter_date = appointment.appointment_date - encounter.encounter_time = appointment.appointment_time + encounter = make_encounter(appointment.name) encounter.save() encounter.submit() return encounter From d4f5a6928f37b6838de6bdcbea9094b595d9fb24 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 11:36:06 +0530 Subject: [PATCH 28/96] feat: additional customer fileds add customer_group, price list, currency etc. rearrange and group fields disable cusotmer edit after save (temp fix) --- erpnext/healthcare/doctype/patient/patient.js | 1 + .../healthcare/doctype/patient/patient.json | 81 +++++++++++++------ erpnext/healthcare/doctype/patient/patient.py | 10 ++- 3 files changed, 65 insertions(+), 27 deletions(-) diff --git a/erpnext/healthcare/doctype/patient/patient.js b/erpnext/healthcare/doctype/patient/patient.js index d5df9567ec..a11faf5806 100644 --- a/erpnext/healthcare/doctype/patient/patient.js +++ b/erpnext/healthcare/doctype/patient/patient.js @@ -40,6 +40,7 @@ frappe.ui.form.on('Patient', { frm.add_custom_button(__('Patient Encounter'), function () { create_encounter(frm); }, 'Create'); + frm.toggle_enable(['customer'], 0); // ToDo, allow change only if no transactions booked or better, add merge option } }, onload: function (frm) { diff --git a/erpnext/healthcare/doctype/patient/patient.json b/erpnext/healthcare/doctype/patient/patient.json index 4258e4011d..2c701fbf94 100644 --- a/erpnext/healthcare/doctype/patient/patient.json +++ b/erpnext/healthcare/doctype/patient/patient.json @@ -24,13 +24,20 @@ "image", "column_break_14", "status", - "inpatient_status", "inpatient_record", - "customer", + "inpatient_status", + "report_preference", "mobile", "email", "phone", - "report_preference", + "customer_details_section", + "customer", + "customer_group", + "territory", + "column_break_24", + "default_currency", + "default_price_list", + "language", "personal_and_social_history", "occupation", "column_break_25", @@ -52,9 +59,7 @@ "surrounding_factors", "other_risk_factors", "more_info", - "patient_details", - "ac_sb", - "default_currency" + "patient_details" ], "fields": [ { @@ -156,6 +161,7 @@ "fieldname": "customer", "fieldtype": "Link", "ignore_user_permissions": 1, + "in_preview": 1, "label": "Customer", "options": "Customer" }, @@ -171,7 +177,8 @@ "fieldtype": "Data", "in_list_view": 1, "in_standard_filter": 1, - "label": "Mobile" + "label": "Mobile", + "options": "Phone" }, { "bold": 1, @@ -186,7 +193,8 @@ "fieldname": "phone", "fieldtype": "Data", "in_filter": 1, - "label": "Phone" + "label": "Phone", + "options": "Phone" }, { "collapsible": 1, @@ -268,25 +276,25 @@ "fieldname": "tobacco_past_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Tobacco Consumption Habbits (Past)" + "label": "Tobacco Consumption (Past)" }, { "fieldname": "tobacco_current_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Tobacco Consumption Habbits (Present)" + "label": "Tobacco Consumption (Present)" }, { "fieldname": "alcohol_past_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Alcohol Consumption Habbits (Past)" + "label": "Alcohol Consumption (Past)" }, { "fieldname": "alcohol_current_use", "fieldtype": "Data", "ignore_user_permissions": 1, - "label": "Alcohol Consumption Habbits (Present)" + "label": "Alcohol Consumption (Present)" }, { "fieldname": "column_break_32", @@ -320,20 +328,11 @@ "ignore_xss_filter": 1, "label": "Patient Details" }, - { - "collapsible": 1, - "fieldname": "ac_sb", - "fieldtype": "Section Break", - "label": "Account Details" - }, { "fieldname": "default_currency", "fieldtype": "Link", - "hidden": 1, - "ignore_xss_filter": 1, - "label": "Default Currency", - "options": "Currency", - "print_hide": 1 + "label": "Billing Currency", + "options": "Currency" }, { "fieldname": "last_name", @@ -351,13 +350,47 @@ "fieldname": "middle_name", "fieldtype": "Data", "label": "Middle Name (optional)" + }, + { + "collapsible": 1, + "fieldname": "customer_details_section", + "fieldtype": "Section Break", + "label": "Customer Details" + }, + { + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "options": "Customer Group" + }, + { + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "options": "Territory" + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break" + }, + { + "fieldname": "default_price_list", + "fieldtype": "Link", + "label": "Default Price List", + "options": "Price List" + }, + { + "fieldname": "language", + "fieldtype": "Link", + "label": "Print Language", + "options": "Language" } ], "icon": "fa fa-user", "image_field": "image", "links": [], "max_attachments": 50, - "modified": "2020-04-06 12:55:30.807744", + "modified": "2020-04-11 14:53:48.767245", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient", diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py index e304a0bbc3..f83fac03a6 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -26,6 +26,7 @@ class Patient(Document): frappe.db.set_value('Patient', self.name, 'status', 'Disabled') else: send_registration_sms(self) + self.reload() # self.notify_update() def set_full_name(self): if self.last_name: @@ -86,8 +87,8 @@ class Patient(Document): return {'invoice': sales_invoice.name} def create_customer(doc): - customer_group = frappe.db.get_single_value('Selling Settings', 'customer_group') - territory = frappe.db.get_single_value('Selling Settings', 'territory') + customer_group = doc.customer_group or frappe.db.get_single_value('Selling Settings', 'customer_group') + territory = doc.territory or frappe.db.get_single_value('Selling Settings', 'territory') if not (customer_group and territory): customer_group = get_root_of('Customer Group') territory = get_root_of('Territory') @@ -98,7 +99,10 @@ def create_customer(doc): 'customer_name': doc.patient_name, 'customer_group': customer_group, 'territory' : territory, - 'customer_type': 'Individual' + 'customer_type': 'Individual', + 'default_currency': doc.default_currency, + 'default_price_ist': doc.default_price_list, + 'language': doc.language }).insert(ignore_permissions=True, ignore_mandatory=True) frappe.db.set_value('Patient', doc.name, 'customer', customer.name) From 82aeff0a3256c978be733896c00ad577df861c88 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 11:56:09 +0530 Subject: [PATCH 29/96] fix: vitals added naimg series, minor field rearrangements --- .../healthcare_practitioner.json | 10 +++++++--- .../sample_collection/sample_collection.json | 8 ++++---- .../doctype/vital_signs/vital_signs.json | 20 ++++++++++++++----- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json index fd5b6e12f6..cb747f95ef 100644 --- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json +++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json @@ -1,6 +1,5 @@ { "actions": [], - "allow_copy": 1, "allow_import": 1, "allow_rename": 1, "autoname": "naming_series:", @@ -51,17 +50,20 @@ "fieldname": "first_name", "fieldtype": "Data", "label": "First Name", + "no_copy": 1, "reqd": 1 }, { "fieldname": "middle_name", "fieldtype": "Data", - "label": "Middle Name (Optional)" + "label": "Middle Name (Optional)", + "no_copy": 1 }, { "fieldname": "last_name", "fieldtype": "Data", - "label": "Last Name" + "label": "Last Name", + "no_copy": 1 }, { "fieldname": "image", @@ -226,6 +228,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Full Name", + "no_copy": 1, "read_only": 1, "search_index": 1 }, @@ -233,6 +236,7 @@ "fieldname": "naming_series", "fieldtype": "Select", "label": "Series", + "no_copy": 1, "options": "HLC-PRAC-.YYYY.-", "report_hide": 1, "set_only_once": 1 diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.json b/erpnext/healthcare/doctype/sample_collection/sample_collection.json index 39cead8862..c352287faf 100644 --- a/erpnext/healthcare/doctype/sample_collection/sample_collection.json +++ b/erpnext/healthcare/doctype/sample_collection/sample_collection.json @@ -9,14 +9,14 @@ "document_type": "Document", "engine": "InnoDB", "field_order": [ - "inpatient_record", "naming_series", - "invoiced", "patient", - "column_break_4", "patient_age", "patient_sex", + "column_break_4", + "inpatient_record", "company", + "invoiced", "section_break_6", "sample", "sample_uom", @@ -167,7 +167,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-03-25 16:55:52.376834", + "modified": "2020-04-04 19:17:02.707203", "modified_by": "Administrator", "module": "Healthcare", "name": "Sample Collection", diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.json b/erpnext/healthcare/doctype/vital_signs/vital_signs.json index 75726dbe07..fdacda6277 100644 --- a/erpnext/healthcare/doctype/vital_signs/vital_signs.json +++ b/erpnext/healthcare/doctype/vital_signs/vital_signs.json @@ -8,11 +8,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "inpatient_record", "patient", "patient_name", - "appointment", - "encounter", "column_break_2", "signs_date", "signs_time", @@ -34,6 +31,11 @@ "bmi", "column_break_14", "nutrition_note", + "sb_references", + "inpatient_record", + "appointment", + "encounter", + "column_break_28", "company", "amended_from" ], @@ -217,7 +219,6 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, "label": "Company", "options": "Company" }, @@ -229,11 +230,20 @@ "options": "Vital Signs", "print_hide": 1, "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "sb_references", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_28", + "fieldtype": "Column Break" } ], "is_submittable": 1, "links": [], - "modified": "2020-03-04 17:19:29.549889", + "modified": "2020-04-03 23:06:29.786184", "modified_by": "Administrator", "module": "Healthcare", "name": "Vital Signs", From 9c7d45deae18e299a9fd1baf2b08a8086e02c19d Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 12:09:06 +0530 Subject: [PATCH 30/96] fix: set company while creating encounter, fields rearranged --- .../patient_appointment.js | 3 +- .../patient_appointment.json | 43 +++++++++++++------ .../patient_appointment.py | 3 +- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index efa6b249b3..de8dc0e90a 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -193,7 +193,6 @@ let check_and_set_availability = function(frm) { d.hide(); frm.enable_save(); frm.save(); - frm.enable_save(); d.get_primary_btn().attr('disabled', true); } }); @@ -400,6 +399,7 @@ let create_vital_signs = function(frm) { frappe.route_options = { 'patient': frm.doc.patient, 'appointment': frm.doc.name, + 'company': frm.doc.company }; frappe.new_doc('Vital Signs'); }; @@ -432,6 +432,7 @@ frappe.ui.form.on('Patient Appointment', 'practitioner', function(frm) { callback: function (data) { frappe.model.set_value(frm.doctype, frm.docname, 'department', data.message.department); frappe.model.set_value(frm.doctype, frm.docname, 'paid_amount', data.message.op_consulting_charge); + frappe.model.set_value(frm.doctype, frm.docname, 'billing_item', data.message.op_consulting_charge_item); } }); } diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index 7f9a671d47..81f7597563 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -14,13 +14,16 @@ "patient_name", "patient_sex", "patient_age", - "inpatient_record", "column_break_1", - "status", - "procedure_template", - "get_procedure_from_encounter", - "procedure_prescription", + "inpatient_record", + "company", "service_unit", + "status", + "section_break_11", + "get_procedure_from_encounter", + "column_break_13", + "procedure_template", + "procedure_prescription", "section_break_12", "practitioner", "department", @@ -32,9 +35,9 @@ "duration", "section_break_16", "mode_of_payment", - "paid_amount", - "company", + "billing_item", "column_break_2", + "paid_amount", "invoiced", "ref_sales_invoice", "section_break_3", @@ -114,7 +117,8 @@ "label": "Procedure Prescription", "no_copy": 1, "options": "Procedure Prescription", - "print_hide": 1 + "print_hide": 1, + "read_only": 1 }, { "fieldname": "service_unit", @@ -140,6 +144,7 @@ "set_only_once": 1 }, { + "fetch_from": "practitioner.department", "fieldname": "department", "fieldtype": "Link", "ignore_user_permissions": 1, @@ -234,12 +239,9 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, "label": "Company", "no_copy": 1, - "options": "Company", - "print_hide": 1, - "report_hide": 1 + "options": "Company" }, { "collapsible": 1, @@ -282,10 +284,25 @@ "label": "Series", "options": "HLC-APP-.YYYY.-", "set_only_once": 1 + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "fieldname": "billing_item", + "fieldtype": "Link", + "label": "Billing Item", + "options": "Item", + "read_only": 1 } ], "links": [], - "modified": "2020-03-27 11:27:33.773195", + "modified": "2020-04-07 11:16:34.981240", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Appointment", diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index a2d9d0240f..3786bf2861 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -340,7 +340,8 @@ def make_encounter(source_name, target_doc=None): ['medical_department', 'department'], ['patient_sex', 'patient_sex'], ['encounter_date', 'appointment_date'], - ['invoiced', 'invoiced'] + ['invoiced', 'invoiced'], + ['company', 'company'] ] } }, target_doc) From 80ab98bf3676bb65acd3081142ecf33a5026c93d Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 12:11:59 +0530 Subject: [PATCH 31/96] fix: company when creatign procedure, practitioner name added --- .../patient_encounter/patient_encounter.js | 9 ++++--- .../patient_encounter/patient_encounter.json | 24 +++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js index 83c5d2be9c..0e34164d07 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js @@ -110,7 +110,8 @@ frappe.ui.form.on('Patient Encounter', { 'patient':data.message.patient, 'type': data.message.appointment_type, 'practitioner': data.message.practitioner, - 'invoiced': data.message.invoiced + 'invoiced': data.message.invoiced, + 'company': data.message.company }; frm.set_value(values); } @@ -209,7 +210,8 @@ let create_vital_signs = function (frm) { frappe.route_options = { 'patient': frm.doc.patient, 'appointment': frm.doc.appointment, - 'encounter': frm.doc.name + 'encounter': frm.doc.name, + 'company': frm.doc.company }; frappe.new_doc('Vital Signs'); }; @@ -220,7 +222,8 @@ let create_procedure = function (frm) { } frappe.route_options = { 'patient': frm.doc.patient, - 'medical_department': frm.doc.medical_department + 'medical_department': frm.doc.medical_department, + 'company': frm.doc.company }; frappe.new_doc('Clinical Procedure'); }; diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json index d00e7bc7dd..935935e0c0 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json @@ -17,9 +17,9 @@ "patient_name", "patient_sex", "patient_age", - "company", "column_break_6", "practitioner", + "practitioner_name", "medical_department", "encounter_date", "encounter_time", @@ -43,6 +43,8 @@ "sb_procedures", "procedure_prescription", "encounter_comment", + "sb_refs", + "company", "amended_from" ], "fields": [ @@ -80,7 +82,6 @@ "fieldname": "patient", "fieldtype": "Link", "ignore_user_permissions": 1, - "in_list_view": 1, "in_standard_filter": 1, "label": "Patient", "options": "Patient", @@ -110,7 +111,6 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, "label": "Company", "options": "Company" }, @@ -121,7 +121,6 @@ { "fieldname": "practitioner", "fieldtype": "Link", - "in_list_view": 1, "in_standard_filter": 1, "label": "Healthcare Practitioner", "options": "Healthcare Practitioner", @@ -272,7 +271,6 @@ "fieldname": "medical_department", "fieldtype": "Link", "ignore_user_permissions": 1, - "in_list_view": 1, "in_standard_filter": 1, "label": "Department", "options": "Medical Department", @@ -287,11 +285,23 @@ { "fieldname": "column_break_17", "fieldtype": "Column Break" + }, + { + "fieldname": "sb_refs", + "fieldtype": "Section Break" + }, + { + "fetch_from": "practitioner.practitioner_name", + "fieldname": "practitioner_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Practitioner Name", + "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-02-27 12:42:21.751964", + "modified": "2020-04-03 23:06:16.348846", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Encounter", @@ -318,7 +328,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "title_field": "patient", + "title_field": "patient_name", "track_changes": 1, "track_seen": 1 } \ No newline at end of file From 2d2db60035e7afe7c83098085d66e999e53d7e4b Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:30:00 +0530 Subject: [PATCH 32/96] feat: multi company support --- .../healthcare/doctype/lab_test/lab_test.js | 9 ++++---- .../healthcare/doctype/lab_test/lab_test.json | 15 ++++++------- .../healthcare/doctype/lab_test/lab_test.py | 21 +++++++++++-------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.js b/erpnext/healthcare/doctype/lab_test/lab_test.js index 5b3f4c705a..e7e44dcb48 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.js +++ b/erpnext/healthcare/doctype/lab_test/lab_test.js @@ -137,7 +137,7 @@ var get_lab_test_prescribed = function(frm){ }); } else{ - frappe.msgprint(__("Please select Patient to get Lab Tests")); + frappe.msgprint(__("Please select a Patient to get Lab Orders")); } }; @@ -180,9 +180,10 @@ var show_lab_tests = function(frm, result){ return false; }); }); - if(!result){ - var msg = "There are no Lab Test prescribed for "+frm.doc.patient; - $(repl('
%(msg)s
', {msg: msg})).appendTo(html_field); + if(!result.length){ + var msg = "No Lab Orders found for patient " + frm.doc.patient_name + ""; + html_field.empty(); + $(repl('
%(msg)s
', {msg: msg})).appendTo(html_field); } d.show(); }; diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.json b/erpnext/healthcare/doctype/lab_test/lab_test.json index ccbc24b3fb..17dc1edd8c 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.json +++ b/erpnext/healthcare/doctype/lab_test/lab_test.json @@ -9,18 +9,18 @@ "document_type": "Document", "engine": "InnoDB", "field_order": [ - "inpatient_record", "naming_series", - "invoiced", "patient", "patient_name", "patient_age", "patient_sex", - "practitioner", + "report_preference", "email", "mobile", - "company", + "practitioner", "c_b", + "inpatient_record", + "company", "department", "status", "submitted_date", @@ -31,7 +31,7 @@ "employee_name", "employee_designation", "user", - "report_preference", + "invoiced", "sb_first", "lab_test_name", "column_break_26", @@ -153,7 +153,7 @@ { "fieldname": "company", "fieldtype": "Link", - "hidden": 1, + "in_standard_filter": 1, "label": "Company", "options": "Company", "print_hide": 1, @@ -168,6 +168,7 @@ "fieldname": "department", "fieldtype": "Link", "ignore_user_permissions": 1, + "in_standard_filter": 1, "label": "Department", "options": "Medical Department", "search_index": 1 @@ -427,7 +428,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-03-23 19:37:06.617764", + "modified": "2020-04-04 19:16:29.131168", "modified_by": "Administrator", "module": "Healthcare", "name": "Lab Test", diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.py b/erpnext/healthcare/doctype/lab_test/lab_test.py index 4e4015d2f0..e49429ea62 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.py +++ b/erpnext/healthcare/doctype/lab_test/lab_test.py @@ -69,9 +69,9 @@ def create_multiple(doctype, docname): lab_test_created = create_lab_test_from_encounter(docname) if lab_test_created: - frappe.msgprint(_("Lab Test(s) "+lab_test_created+" created.")) + frappe.msgprint(_("Lab Test(s) " + lab_test_created + " created.")) else: - frappe.msgprint(_("No Lab Test created")) + frappe.msgprint(_("No Lab Tests created")) def create_lab_test_from_encounter(encounter_id): lab_test_created = False @@ -87,7 +87,7 @@ def create_lab_test_from_encounter(encounter_id): for lab_test_id in lab_test_ids: template = get_lab_test_template(lab_test_id[1]) if template: - lab_test = create_lab_test_doc(lab_test_id[2], encounter.practitioner, patient, template) + lab_test = create_lab_test_doc(lab_test_id[2], encounter.practitioner, patient, template, encounter.company) lab_test.save(ignore_permissions = True) frappe.db.set_value("Lab Prescription", lab_test_id[0], "lab_test_created", 1) if not lab_test_created: @@ -111,7 +111,7 @@ def create_lab_test_from_invoice(invoice_name): if lab_test_created != 1: template = get_lab_test_template(item.item_code) if template: - lab_test = create_lab_test_doc(True, invoice.ref_practitioner, patient, template) + lab_test = create_lab_test_doc(True, invoice.ref_practitioner, patient, template, company, invoice.company) if item.reference_dt == "Lab Prescription": lab_test.prescription = item.reference_dn lab_test.save(ignore_permissions = True) @@ -121,7 +121,7 @@ def create_lab_test_from_invoice(invoice_name): if not lab_tests_created: lab_tests_created = lab_test.name else: - lab_tests_created += ", "+lab_test.name + lab_tests_created += ", " + lab_test.name return lab_tests_created def get_lab_test_template(item): @@ -141,7 +141,7 @@ def check_template_exists(item): return template_exists return False -def create_lab_test_doc(invoiced, practitioner, patient, template): +def create_lab_test_doc(invoiced, practitioner, patient, template, company): lab_test = frappe.new_doc("Lab Test") lab_test.invoiced = invoiced lab_test.practitioner = practitioner @@ -150,11 +150,12 @@ def create_lab_test_doc(invoiced, practitioner, patient, template): lab_test.patient_sex = patient.sex lab_test.email = patient.email lab_test.mobile = patient.mobile + lab_test.report_preference = patient.report_preference lab_test.department = template.department lab_test.template = template.name lab_test.lab_test_group = template.lab_test_group lab_test.result_date = getdate() - lab_test.report_preference = patient.report_preference + lab_test.company = company return lab_test def create_normals(template, lab_test): @@ -190,7 +191,7 @@ def create_specials(template, lab_test): special.require_result_value = 1 special.template = template.name -def create_sample_doc(template, patient, invoice): +def create_sample_doc(template, patient, invoice, company = None): if template.sample: sample_exists = frappe.db.exists({ "doctype": "Sample Collection", @@ -221,6 +222,8 @@ def create_sample_doc(template, patient, invoice): sample_collection.sample = template.sample sample_collection.sample_uom = template.sample_uom sample_collection.sample_qty = template.sample_qty + sample_collection.company = company + if(template.sample_details): sample_collection.sample_details = "Test :" + (template.get("lab_test_name") or template.get("template")) +"\n"+"Collection Detials:\n\t"+template.sample_details sample_collection.save(ignore_permissions=True) @@ -229,7 +232,7 @@ def create_sample_doc(template, patient, invoice): def create_sample_collection(lab_test, template, patient, invoice): if(frappe.db.get_value("Healthcare Settings", None, "create_sample_collection_for_lab_test") == "1"): - sample_collection = create_sample_doc(template, patient, invoice) + sample_collection = create_sample_doc(template, patient, invoice, lab_test.company) if(sample_collection): lab_test.sample = sample_collection.name return lab_test From 38f0258991fc4e1a82aac0fc13dbc09068d4241c Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:32:35 +0530 Subject: [PATCH 33/96] feat: multi company support --- .../clinical_procedure/clinical_procedure.js | 35 ++++++++--- .../clinical_procedure.json | 58 ++++++++++++++++--- .../clinical_procedure/clinical_procedure.py | 2 +- 3 files changed, 79 insertions(+), 16 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js index 5f36bdd95c..213fa3de1f 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js @@ -175,16 +175,37 @@ frappe.ui.form.on('Clinical Procedure', { name: frm.doc.appointment }, callback: function(data) { - frm.set_value('patient', data.message.patient); - frm.set_value('procedure_template', data.message.procedure_template); - frm.set_value('medical_department', data.message.department); - frm.set_value('start_date', data.message.appointment_date); - frm.set_value('start_time', data.message.appointment_time); - frm.set_value('notes', data.message.notes); - frm.set_value('service_unit', data.message.service_unit); + let values = { + 'patient':data.message.patient, + 'procedure_template': data.message.procedure_template, + 'medical_department': data.message.department, + 'start_date': data.message.appointment_date, + 'start_time': data.message.appointment_time, + 'notes': data.message.notes, + 'service_unit': data.message.service_unit, + 'company': data.message.company + } + frm.set_value(values); } }); } + else{ + let values = { + 'patient': '', + 'patient_name': '', + 'patient_sex': '', + 'patient_age': '', + 'medical_department': '', + 'procedure_template': '', + 'start_date': '', + 'start_time': '', + 'notes': '', + 'service_unit': '', + 'inpatient_record': '' + // 'inpatient_status': '' + }; + frm.set_value(values); + } }, procedure_template: function(frm) { diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json index 3c936bbf27..59a87eccde 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json @@ -7,16 +7,18 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "inpatient_record", "naming_series", - "procedure_template", "appointment", + "column_break_30", + "procedure_template", + "section_break_6", "patient", + "patient_name", "patient_sex", "patient_age", - "prescription", "medical_department", "practitioner", + "practitioner_name", "column_break_7", "status", "service_unit", @@ -26,7 +28,6 @@ "sample", "invoiced", "notes", - "company", "consumables_section", "consume_stock", "items", @@ -36,6 +37,11 @@ "consumable_total_amount", "column_break_27", "consumption_details", + "sb_refs", + "inpatient_record", + "company", + "column_break_34", + "prescription", "amended_from" ], "fields": [ @@ -56,7 +62,7 @@ { "fieldname": "appointment", "fieldtype": "Link", - "in_list_view": 1, + "in_standard_filter": 1, "label": "Appointment", "options": "Patient Appointment" }, @@ -64,7 +70,7 @@ "fetch_from": "inpatient_record.patient", "fieldname": "patient", "fieldtype": "Link", - "in_list_view": 1, + "in_standard_filter": 1, "label": "Patient", "options": "Patient", "reqd": 1 @@ -88,17 +94,20 @@ "fieldtype": "Link", "hidden": 1, "label": "Procedure Prescription", - "options": "Procedure Prescription" + "options": "Procedure Prescription", + "read_only": 1 }, { "fieldname": "medical_department", "fieldtype": "Link", + "in_standard_filter": 1, "label": "Medical Department", "options": "Medical Department" }, { "fieldname": "practitioner", "fieldtype": "Link", + "in_standard_filter": 1, "label": "Healthcare Practitioner", "options": "Healthcare Practitioner" }, @@ -237,11 +246,43 @@ { "fieldname": "section_break_24", "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_30", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "collapsible": 1, + "fieldname": "sb_refs", + "fieldtype": "Section Break" + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "fetch_from": "practitioner.practitioner_name", + "fieldname": "practitioner_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Practitioner Name", + "read_only": 1 + }, + { + "fieldname": "column_break_34", + "fieldtype": "Column Break" } ], "is_submittable": 1, "links": [], - "modified": "2020-03-02 11:44:27.970651", + "modified": "2020-04-03 23:06:04.009856", "modified_by": "Administrator", "module": "Healthcare", "name": "Clinical Procedure", @@ -263,5 +304,6 @@ "restrict_to_domain": "Healthcare", "sort_field": "modified", "sort_order": "DESC", + "title_field": "patient_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py index db3afc8807..56617e5258 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py @@ -37,7 +37,7 @@ class ClinicalProcedure(Document): template = frappe.get_doc('Clinical Procedure Template', self.procedure_template) if template.sample: patient = frappe.get_doc('Patient', self.patient) - sample_collection = create_sample_doc(template, patient, None) + sample_collection = create_sample_doc(template, patient, None, self.company) frappe.db.set_value('Clinical Procedure', self.name, 'sample', sample_collection.name) self.reload() From 7ad98c95b9af953b53c30bd641b74bd331f1bfc2 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:34:28 +0530 Subject: [PATCH 34/96] company field added --- .../inpatient_record/inpatient_record.json | 1165 ++++------------- 1 file changed, 220 insertions(+), 945 deletions(-) diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json index 92c11fbb4d..c1b516d536 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json @@ -1,980 +1,255 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2018-07-11 17:48:51.404139", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "naming_series:", + "creation": "2018-07-11 17:48:51.404139", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "section_break_1", + "naming_series", + "patient", + "patient_name", + "gender", + "blood_group", + "dob", + "mobile", + "email", + "phone", + "column_break_8", + "company", + "status", + "scheduled_date", + "admitted_datetime", + "expected_discharge", + "discharge_date", + "references", + "cb_admission", + "admission_practitioner", + "admission_encounter", + "cb_discharge", + "discharge_practitioner", + "discharge_encounter", + "sb_inpatient_occupancy", + "inpatient_occupancies", + "btn_transfer", + "sb_discharge_note", + "discharge_note" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_1", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_1", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 0, - "options": "HLC-INP-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "hidden": 1, + "label": "Series", + "options": "HLC-INP-.YYYY.-" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "patient", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Patient", - "length": 0, - "no_copy": 0, - "options": "Patient", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "patient", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.patient_name", - "fieldname": "patient_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Patient Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.sex", - "fieldname": "gender", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Gender", - "length": 0, - "no_copy": 0, - "options": "Gender", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.sex", + "fieldname": "gender", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.blood_group", - "fieldname": "blood_group", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Blood Group", - "length": 0, - "no_copy": 0, - "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.blood_group", + "fieldname": "blood_group", + "fieldtype": "Select", + "label": "Blood Group", + "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "dob", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Date of birth", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.dob", + "fieldname": "dob", + "fieldtype": "Date", + "label": "Date of birth", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.mobile", - "fieldname": "mobile", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Mobile", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.mobile", + "fieldname": "mobile", + "fieldtype": "Data", + "label": "Mobile", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.email", - "fieldname": "email", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Email", - "length": 0, - "no_copy": 0, - "options": "Email", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.email", + "fieldname": "email", + "fieldtype": "Data", + "label": "Email", + "options": "Email", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "patient.phone", - "fieldname": "phone", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Phone", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "patient.phone", + "fieldname": "phone", + "fieldtype": "Data", + "label": "Phone", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_8", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 0, - "options": "Admission Scheduled\nAdmitted\nDischarge Scheduled\nDischarged", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Admission Scheduled\nAdmitted\nDischarge Scheduled\nDischarged", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "scheduled_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Admission Schedule Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Today", + "fieldname": "scheduled_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Admission Schedule Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "admitted_datetime", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Admitted Datetime", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Today", + "fieldname": "admitted_datetime", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Admitted Datetime" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "expected_discharge", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Expected Discharge", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "expected_discharge", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Expected Discharge" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Discharge Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "discharge_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Discharge Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "references", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "References", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "collapsible": 1, + "fieldname": "references", + "fieldtype": "Section Break", + "label": "References" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cb_admission", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Admission", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "cb_admission", + "fieldtype": "Column Break", + "label": "Admission" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "admission_practitioner", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Healthcare Practitioner", - "length": 0, - "no_copy": 0, - "options": "Healthcare Practitioner", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "admission_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "admission_encounter", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Patient Encounter", - "length": 0, - "no_copy": 0, - "options": "Patient Encounter", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "admission_encounter", + "fieldtype": "Link", + "label": "Patient Encounter", + "options": "Patient Encounter", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cb_discharge", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Discharge", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "cb_discharge", + "fieldtype": "Column Break", + "label": "Discharge" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_practitioner", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Healthcare Practitioner", - "length": 0, - "no_copy": 0, - "options": "Healthcare Practitioner", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "discharge_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_encounter", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Patient Encounter", - "length": 0, - "no_copy": 0, - "options": "Patient Encounter", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "discharge_encounter", + "fieldtype": "Link", + "label": "Patient Encounter", + "options": "Patient Encounter", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sb_inpatient_occupancy", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Inpatient Occupancy", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "sb_inpatient_occupancy", + "fieldtype": "Section Break", + "label": "Inpatient Occupancy" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "inpatient_occupancies", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "options": "Inpatient Occupancy", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "inpatient_occupancies", + "fieldtype": "Table", + "options": "Inpatient Occupancy", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "btn_transfer", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Transfer", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "btn_transfer", + "fieldtype": "Button", + "label": "Transfer" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.status != \"Admission Scheduled\"", - "fieldname": "sb_discharge_note", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Discharge Note", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.status != \"Admission Scheduled\"", + "fieldname": "sb_discharge_note", + "fieldtype": "Section Break", + "label": "Discharge Note" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discharge_note", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "discharge_note", + "fieldtype": "Text Editor" + }, + { + "fetch_from": "admission_encounter.company", + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "options": "Company" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 14:44:43.168245", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Inpatient Record", - "name_case": "", - "owner": "Administrator", + ], + "links": [], + "modified": "2020-04-07 13:13:39.351977", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Inpatient Record", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "search_fields": "patient", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "patient", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} + ], + "restrict_to_domain": "Healthcare", + "search_fields": "patient", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "patient", + "track_changes": 1 +} \ No newline at end of file From 93d0c78ca5121eb6a0e23279cacacae07b18395e Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 16:42:03 +0530 Subject: [PATCH 35/96] feat: multi-company billing sales onvoice - filter get items based on company utils - company filters in all get item helper methods utils - refactor appointemnt items --- .../doctype/sales_invoice/sales_invoice.js | 7 +- erpnext/healthcare/utils.py | 96 ++++++++++--------- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 6f78db2ccc..7741842fca 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -919,7 +919,7 @@ var get_healthcare_services_to_invoice = function(frm) { if(patient && patient!=selected_patient){ selected_patient = patient; var method = "erpnext.healthcare.utils.get_healthcare_services_to_invoice"; - var args = {patient: patient}; + var args = {patient: patient, company: frm.doc.company}; var columns = (["service", "reference_name", "reference_type"]); get_healthcare_items(frm, true, $results, $placeholder, method, args, columns); } @@ -1063,7 +1063,10 @@ var get_drugs_to_invoice = function(frm) { description:'Quantity will be calculated only for items which has "Nos" as UoM. You may change as required for each invoice item.', get_query: function(doc) { return { - filters: { patient: dialog.get_value("patient"), docstatus: 1 } + filters: { patient: dialog.get_value("patient"), + company: frm.doc.company, + docstatus: 1 + } }; } }, diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py index 246242ad84..e9cc597e81 100644 --- a/erpnext/healthcare/utils.py +++ b/erpnext/healthcare/utils.py @@ -3,82 +3,84 @@ # For license information, please see license.txt from __future__ import unicode_literals +import math +import json import frappe from frappe import _ -import math 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 from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple @frappe.whitelist() -def get_healthcare_services_to_invoice(patient): +def get_healthcare_services_to_invoice(patient, company): patient = frappe.get_doc('Patient', patient) + items_to_invoice = [] if patient: validate_customer_created(patient) - items_to_invoice = [] - patient_appointments = frappe.get_list( - 'Patient Appointment', - fields='*', - filters={'patient': patient.name, 'invoiced': 0}, - order_by='appointment_date' - ) - if patient_appointments: - items_to_invoice = get_fee_validity(patient_appointments) + # Customer validated, build a list of billable services + items_to_invoice += get_appointments_to_invoice(patient, company) + items_to_invoice += get_encounters_to_invoice(patient, company) + items_to_invoice += get_lab_tests_to_invoice(patient, company) + items_to_invoice += get_clinical_procedures_to_invoice(patient, company) + items_to_invoice += get_inpatient_services_to_invoice(patient, company) - encounters = get_encounters_to_invoice(patient) - lab_tests = get_lab_tests_to_invoice(patient) - clinical_procedures = get_clinical_procedures_to_invoice(patient) - inpatient_services = get_inpatient_services_to_invoice(patient) - - items_to_invoice += encounters + lab_tests + clinical_procedures + inpatient_services return items_to_invoice + def validate_customer_created(patient): if not frappe.db.get_value('Patient', patient.name, 'customer'): msg = _("Please set a Customer linked to the Patient") msg += " {0}".format(patient.name) frappe.throw(msg, title=_('Customer Not Found')) -def get_fee_validity(patient_appointments): - if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'): - return - items_to_invoice = [] +def get_appointments_to_invoice(patient, company): + appointments_to_invoice = [] + patient_appointments = frappe.get_list( + 'Patient Appointment', + fields = '*', + filters = {'patient': patient.name, 'company': company, 'invoiced': 0}, + order_by = 'appointment_date' + ) + for appointment in patient_appointments: + # Procedure Appointments if appointment.procedure_template: if frappe.db.get_value('Clinical Procedure Template', appointment.procedure_template, 'is_billable'): - items_to_invoice.append({ + appointments_to_invoice.append({ 'reference_type': 'Patient Appointment', 'reference_name': appointment.name, 'service': appointment.procedure_template }) + # Consultation Appointments, should check fee validity else: - fee_validity = frappe.db.exists('Fee Validity Reference', {'appointment': appointment.name}) - if not fee_validity: - practitioner_charge = 0 - income_account = None - service_item = None - if appointment.practitioner: - service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment) - income_account = get_income_account(appointment.practitioner, appointment.company) - items_to_invoice.append({ - 'reference_type': 'Patient Appointment', - 'reference_name': appointment.name, - 'service': service_item, - 'rate': practitioner_charge, - 'income_account': income_account - }) + if frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups') and \ + frappe.db.exists('Fee Validity Reference', {'appointment': appointment.name}): + continue # Skip invoicing, fee validty present + practitioner_charge = 0 + income_account = None + service_item = None + if appointment.practitioner: + service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment) + income_account = get_income_account(appointment.practitioner, appointment.company) + appointments_to_invoice.append({ + 'reference_type': 'Patient Appointment', + 'reference_name': appointment.name, + 'service': service_item, + 'rate': practitioner_charge, + 'income_account': income_account + }) - return items_to_invoice + return appointments_to_invoice -def get_encounters_to_invoice(patient): +def get_encounters_to_invoice(patient, company): encounters_to_invoice = [] encounters = frappe.get_list( 'Patient Encounter', fields=['*'], - filters={'patient': patient.name, 'invoiced': False, 'docstatus': 1} + filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1} ) if encounters: for encounter in encounters: @@ -101,12 +103,12 @@ def get_encounters_to_invoice(patient): return encounters_to_invoice -def get_lab_tests_to_invoice(patient): +def get_lab_tests_to_invoice(patient, company): lab_tests_to_invoice = [] lab_tests = frappe.get_list( 'Lab Test', fields=['name', 'template'], - filters={'patient': patient.name, 'invoiced': False, 'docstatus': 1} + filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1} ) for lab_test in lab_tests: item, is_billable = frappe.get_cached_value('Lab Test Template', lab_test.lab_test_code, ['item', 'is_billable']) @@ -142,12 +144,12 @@ def get_lab_tests_to_invoice(patient): return lab_tests_to_invoice -def get_clinical_procedures_to_invoice(patient): +def get_clinical_procedures_to_invoice(patient, company): clinical_procedures_to_invoice = [] procedures = frappe.get_list( 'Clinical Procedure', fields='*', - filters={'patient': patient.name, 'invoiced': False} + filters={'patient': patient.name, 'company': company, 'invoiced': False} ) for procedure in procedures: if not procedure.appointment: @@ -203,7 +205,7 @@ def get_clinical_procedures_to_invoice(patient): return clinical_procedures_to_invoice -def get_inpatient_services_to_invoice(patient): +def get_inpatient_services_to_invoice(patient, company): services_to_invoice = [] inpatient_services = frappe.db.sql( ''' @@ -213,10 +215,11 @@ def get_inpatient_services_to_invoice(patient): `tabInpatient Record` ip, `tabInpatient Occupancy` io WHERE ip.patient=%s + and ip.company=%s and io.parent=ip.name and io.left=1 and io.invoiced=0 - ''', (patient.name), as_dict=1) + ''', (patient.name, company), as_dict=1) for inpatient_occupancy in inpatient_services: service_unit_type = frappe.db.get_value('Healthcare Service Unit', inpatient_occupancy.service_unit, 'service_unit_type') @@ -376,6 +379,7 @@ def check_fee_validity(appointment): def manage_fee_validity(appointment): fee_validity = check_fee_validity(appointment) + if fee_validity: if appointment.status == 'Cancelled' and fee_validity.visited > 0: fee_validity.visited -= 1 From b922b292eb31454ffd15c2efa135e534f52b9bee Mon Sep 17 00:00:00 2001 From: anoop Date: Thu, 16 Apr 2020 12:52:03 +0530 Subject: [PATCH 36/96] fix: set patient while billing, tests fixed --- .../patient_appointment.py | 2 +- .../test_patient_appointment.py | 29 ++++++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index 3786bf2861..cc3492a810 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -119,6 +119,7 @@ def invoice_appointment(appointment_doc): if automate_invoicing and not appointment_invoiced and not fee_validity: sales_invoice = frappe.new_doc('Sales Invoice') + sales_invoice.patient = appointment_doc.patient sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer') sales_invoice.appointment = appointment_doc.name sales_invoice.due_date = getdate() @@ -339,7 +340,6 @@ def make_encounter(source_name, target_doc=None): ['practitioner', 'practitioner'], ['medical_department', 'department'], ['patient_sex', 'patient_sex'], - ['encounter_date', 'appointment_date'], ['invoiced', 'invoiced'], ['company', 'company'] ] diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 7075af5d00..7cdb28e0da 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import unittest import frappe -from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status +from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter from frappe.utils import nowdate, add_days from frappe.utils.make_random import get_random @@ -23,6 +23,14 @@ class TestPatientAppointment(unittest.TestCase): create_encounter(appointment) self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') + def test_start_encounter(self): + patient, medical_department, practitioner = create_healthcare_docs() + appointment = create_appointment(patient, practitioner, add_days(nowdate(), 3)) + encounter = create_encounter(appointment) + self.assertEquals(frappe.db.get_value('Patient Encounter', encounter.name, 'company'), appointment.company) + self.assertEquals(frappe.db.get_value('Patient Encounter', encounter.name, 'practitioner'), appointment.practitioner) + self.assertEquals(frappe.db.get_value('Patient Encounter', encounter.name, 'patient'), appointment.patient) + def test_invoicing(self): patient, medical_department, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) @@ -33,7 +41,11 @@ class TestPatientAppointment(unittest.TestCase): frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2), invoice=1) self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'), 1) - self.assertTrue(frappe.db.get_value('Patient Appointment', appointment.name, 'ref_sales_invoice')) + sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent') + self.assertTrue(sales_invoice_name) + self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'company'), appointment.company) + self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'patient'), appointment.patient) + self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount) def test_appointment_cancel(self): patient, medical_department, practitioner = create_healthcare_docs() @@ -53,8 +65,8 @@ class TestPatientAppointment(unittest.TestCase): appointment = create_appointment(patient, practitioner, nowdate(), invoice=1) update_status(appointment.name, 'Cancelled') # check invoice cancelled - sales_invoice = frappe.db.get_value('Patient Appointment', appointment.name, 'ref_sales_invoice') - self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice, 'status'), 'Cancelled') + 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 create_healthcare_docs(): @@ -90,14 +102,9 @@ def create_patient(): patient = patient.name return patient -def create_encounter(appointment=None): - encounter = frappe.new_doc('Patient Encounter') +def create_encounter(appointment): if appointment: - encounter.appointment = appointment.name - encounter.patient = appointment.patient - encounter.practitioner = appointment.practitioner - encounter.encounter_date = appointment.appointment_date - encounter.encounter_time = appointment.appointment_time + encounter = make_encounter(appointment.name) encounter.save() encounter.submit() return encounter From 1c49a12ac29ef1bdf1fd95f6bc27710f7015eb63 Mon Sep 17 00:00:00 2001 From: anoop Date: Thu, 16 Apr 2020 17:00:47 +0530 Subject: [PATCH 37/96] fix: patient appointment auto invoicing: record payment if payment mode and amount available test: fixes --- .../patient_appointment.py | 12 +++++---- .../test_patient_appointment.py | 26 ++++++++++++++----- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index cc3492a810..fcd87d719b 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -123,22 +123,24 @@ def invoice_appointment(appointment_doc): sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer') sales_invoice.appointment = appointment_doc.name sales_invoice.due_date = getdate() - sales_invoice.is_pos = 1 sales_invoice.company = appointment_doc.company sales_invoice.debit_to = get_receivable_account(appointment_doc.company) item = sales_invoice.append('items', {}) item = get_appointment_item(appointment_doc, item) - payment = sales_invoice.append('payments', {}) - payment.mode_of_payment = appointment_doc.mode_of_payment - payment.amount = appointment_doc.paid_amount + # Add payments if payment details are supplied else proceed to create invoice as Unpaid + if appointment_doc.mode_of_payment and appointment_doc.paid_amount: + sales_invoice.is_pos = 1 + payment = sales_invoice.append('payments', {}) + payment.mode_of_payment = appointment_doc.mode_of_payment + payment.amount = appointment_doc.paid_amount sales_invoice.set_missing_values(for_validate=True) sales_invoice.flags.ignore_mandatory = True sales_invoice.save(ignore_permissions=True) sales_invoice.submit() - frappe.msgprint(_('Sales Invoice {0} created as paid'.format(sales_invoice.name)), alert=True) + frappe.msgprint(_('Sales Invoice {0} created'.format(sales_invoice.name)), alert=True) frappe.db.set_value('Patient Appointment', appointment_doc.name, 'invoiced', 1) frappe.db.set_value('Patient Appointment', appointment_doc.name, 'ref_sales_invoice', sales_invoice.name) diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 7cdb28e0da..eeed157291 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -11,7 +11,8 @@ from frappe.utils.make_random import get_random class TestPatientAppointment(unittest.TestCase): def setUp(self): frappe.db.sql("""delete from `tabPatient Appointment`""") - frappe.db.sql("""delete from `tabFee Validity""") + frappe.db.sql("""delete from `tabFee Validity`""") + frappe.db.sql("""delete from `tabPatient Encounter`""") def test_status(self): patient, medical_department, practitioner = create_healthcare_docs() @@ -25,11 +26,16 @@ class TestPatientAppointment(unittest.TestCase): def test_start_encounter(self): patient, medical_department, practitioner = create_healthcare_docs() - appointment = create_appointment(patient, practitioner, add_days(nowdate(), 3)) - encounter = create_encounter(appointment) - self.assertEquals(frappe.db.get_value('Patient Encounter', encounter.name, 'company'), appointment.company) - self.assertEquals(frappe.db.get_value('Patient Encounter', encounter.name, 'practitioner'), appointment.practitioner) - self.assertEquals(frappe.db.get_value('Patient Encounter', encounter.name, 'patient'), appointment.patient) + frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) + appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4), invoice = 1) + self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'), 1) + encounter = make_encounter(appointment.name) + self.assertTrue(encounter) + self.assertEqual(encounter.company, appointment.company) + self.assertEqual(encounter.practitioner, appointment.practitioner) + self.assertEqual(encounter.patient, appointment.patient) + # invoiced flag mapped from appointment + self.assertEqual(encounter.invoiced, frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced')) def test_invoicing(self): patient, medical_department, practitioner = create_healthcare_docs() @@ -104,7 +110,13 @@ def create_patient(): def create_encounter(appointment): if appointment: - encounter = make_encounter(appointment.name) + encounter = frappe.new_doc('Patient Encounter') + encounter.appointment = appointment.name + encounter.patient = appointment.patient + encounter.practitioner = appointment.practitioner + encounter.encounter_date = appointment.appointment_date + encounter.encounter_time = appointment.appointment_time + encounter.company = appointment.company encounter.save() encounter.submit() return encounter From fdc075220d6247089e3f108d5937a6f681580aa1 Mon Sep 17 00:00:00 2001 From: anoop Date: Thu, 16 Apr 2020 23:05:38 +0530 Subject: [PATCH 38/96] fix: minor, codacy suggestions --- .../doctype/clinical_procedure/clinical_procedure.js | 3 +-- erpnext/healthcare/utils.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js index 213fa3de1f..80157e1c25 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js @@ -184,7 +184,7 @@ frappe.ui.form.on('Clinical Procedure', { 'notes': data.message.notes, 'service_unit': data.message.service_unit, 'company': data.message.company - } + }; frm.set_value(values); } }); @@ -202,7 +202,6 @@ frappe.ui.form.on('Clinical Procedure', { 'notes': '', 'service_unit': '', 'inpatient_record': '' - // 'inpatient_status': '' }; frm.set_value(values); } diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py index e9cc597e81..17d8f64292 100644 --- a/erpnext/healthcare/utils.py +++ b/erpnext/healthcare/utils.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import math -import json import frappe from frappe import _ from frappe.utils import time_diff_in_hours, rounded From ec7db4f4679855890eccaec643be4d1f57bde9be Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 23 Apr 2020 03:10:11 +0530 Subject: [PATCH 39/96] fix: modified timestamp conflict --- .../doctype/patient_appointment/patient_appointment.json | 2 +- .../healthcare/doctype/patient_encounter/patient_encounter.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index 9297588509..6e889c2a0c 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -328,7 +328,7 @@ } ], "links": [], - "modified": "2020-04-07 11:16:34.981240", + "modified": "2020-04-23 11:16:34.981240", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Appointment", diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json index 12203fde8d..ff7ecc926f 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json @@ -328,7 +328,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-04-03 23:06:16.348846", + "modified": "2020-04-23 23:06:16.348846", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Encounter", From c02eceb989c5e4647684858ac753887094192f84 Mon Sep 17 00:00:00 2001 From: anoop Date: Thu, 23 Apr 2020 21:15:06 +0530 Subject: [PATCH 40/96] style: intendation --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 7741842fca..9aaee3cf62 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -1063,10 +1063,11 @@ var get_drugs_to_invoice = function(frm) { description:'Quantity will be calculated only for items which has "Nos" as UoM. You may change as required for each invoice item.', get_query: function(doc) { return { - filters: { patient: dialog.get_value("patient"), - company: frm.doc.company, - docstatus: 1 - } + filters: { + patient: dialog.get_value("patient"), + company: frm.doc.company, + docstatus: 1 + } }; } }, From e30cc8359146ad0bcf4cadce37a61bf6850b852a Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 27 Apr 2020 19:36:49 +0530 Subject: [PATCH 41/96] fix: field order, minor fixes - patient - update customer, releated fields patient appointment - send message --- .../clinical_procedure/clinical_procedure.js | 20 +++---- .../clinical_procedure.json | 44 +++++++++++----- .../healthcare/doctype/lab_test/lab_test.js | 8 +-- .../healthcare/doctype/lab_test/lab_test.py | 4 +- erpnext/healthcare/doctype/patient/patient.js | 2 + .../healthcare/doctype/patient/patient.json | 11 ++-- erpnext/healthcare/doctype/patient/patient.py | 49 +++++++++++++---- .../patient_appointment.js | 5 ++ .../patient_appointment.json | 52 ++++++++++--------- .../patient_appointment.py | 12 +++-- .../patient_encounter/patient_encounter.js | 31 +++++++---- .../patient_encounter/patient_encounter.json | 41 +++++---------- 12 files changed, 168 insertions(+), 111 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js index 80157e1c25..2d30e9003b 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js @@ -87,8 +87,8 @@ frappe.ui.form.on('Clinical Procedure', { ['' + r.message + '']), indicator: 'green' }); - frm.reload_doc(); } + frm.reload_doc(); } }); } @@ -156,11 +156,13 @@ frappe.ui.form.on('Clinical Procedure', { age = __('{0} as on {1}', [age, data.message.age_as_on]); } } + frm.set_value('patient_name', data.message.patient_name); frm.set_value('patient_age', age); frm.set_value('patient_sex', data.message.sex); } }); } else { + frm.set_value('patient_name', ''); frm.set_value('patient_age', ''); frm.set_value('patient_sex', ''); } @@ -179,6 +181,7 @@ frappe.ui.form.on('Clinical Procedure', { 'patient':data.message.patient, 'procedure_template': data.message.procedure_template, 'medical_department': data.message.department, + 'practitioner': data.message.practitioner, 'start_date': data.message.appointment_date, 'start_time': data.message.appointment_time, 'notes': data.message.notes, @@ -188,8 +191,7 @@ frappe.ui.form.on('Clinical Procedure', { frm.set_value(values); } }); - } - else{ + } else { let values = { 'patient': '', 'patient_name': '', @@ -252,9 +254,11 @@ frappe.ui.form.on('Clinical Procedure', { name: frm.doc.practitioner }, callback: function (data) { - frappe.model.set_value(frm.doctype,frm.docname, 'medical_department',data.message.department); + frappe.model.set_value(frm.doctype,frm.docname, 'practitioner_name', data.message.practitioner_name); } }); + } else { + frappe.model.set_value(frm.doctype,frm.docname, 'practitioner_name', ''); } }, @@ -302,14 +306,6 @@ frappe.ui.form.on('Clinical Procedure', { }); -cur_frm.set_query('procedure_template', function(doc) { - return { - filters: { - 'medical_department': doc.medical_department - } - }; -}); - frappe.ui.form.on('Clinical Procedure Item', { qty: function(frm, cdt, cdn) { let d = locals[cdt][cdn]; diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json index 59a87eccde..7c4b9a3ed3 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json @@ -9,27 +9,29 @@ "field_order": [ "naming_series", "appointment", - "column_break_30", "procedure_template", + "column_break_30", + "company", + "invoiced", "section_break_6", "patient", "patient_name", "patient_sex", "patient_age", - "medical_department", - "practitioner", - "practitioner_name", + "inpatient_record", + "notes", "column_break_7", "status", + "practitioner", + "practitioner_name", + "medical_department", "service_unit", - "warehouse", "start_date", "start_time", "sample", - "invoiced", - "notes", "consumables_section", "consume_stock", + "warehouse", "items", "section_break_24", "invoice_separately_as_consumables", @@ -38,8 +40,6 @@ "column_break_27", "consumption_details", "sb_refs", - "inpatient_record", - "company", "column_break_34", "prescription", "amended_from" @@ -64,10 +64,10 @@ "fieldtype": "Link", "in_standard_filter": 1, "label": "Appointment", - "options": "Patient Appointment" + "options": "Patient Appointment", + "set_only_once": 1 }, { - "fetch_from": "inpatient_record.patient", "fieldname": "patient", "fieldtype": "Link", "in_standard_filter": 1, @@ -217,6 +217,7 @@ "read_only": 1 }, { + "depends_on": "eval:!doc.__islocal", "fieldname": "status", "fieldtype": "Select", "in_list_view": 1, @@ -235,6 +236,8 @@ "read_only": 1 }, { + "collapsible": 1, + "collapsible_depends_on": "consume_stock", "fieldname": "consumables_section", "fieldtype": "Section Break", "label": "Consumables" @@ -261,14 +264,12 @@ "fieldtype": "Section Break" }, { - "fetch_from": "patient.patient_name", "fieldname": "patient_name", "fieldtype": "Data", "label": "Patient Name", "read_only": 1 }, { - "fetch_from": "practitioner.practitioner_name", "fieldname": "practitioner_name", "fieldtype": "Data", "in_list_view": 1, @@ -282,7 +283,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-04-03 23:06:04.009856", + "modified": "2020-04-24 22:53:13.156901", "modified_by": "Administrator", "module": "Healthcare", "name": "Clinical Procedure", @@ -298,6 +299,21 @@ "report": 1, "role": "Nursing User", "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "submit": 1, "write": 1 } ], diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.js b/erpnext/healthcare/doctype/lab_test/lab_test.js index e7e44dcb48..bf1ecc87e4 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.js +++ b/erpnext/healthcare/doctype/lab_test/lab_test.js @@ -137,13 +137,13 @@ var get_lab_test_prescribed = function(frm){ }); } else{ - frappe.msgprint(__("Please select a Patient to get Lab Orders")); + frappe.msgprint(__("Please select a Patient to get Lab Tests")); } }; var show_lab_tests = function(frm, result){ var d = new frappe.ui.Dialog({ - title: __("Lab Test Prescriptions"), + title: __("Lab Tests"), fields: [ { fieldtype: "HTML", fieldname: "lab_test" @@ -161,7 +161,7 @@ var show_lab_tests = function(frm, result){ ', {name:y[0], lab_test: y[1], encounter:y[2], invoiced:y[3], practitioner:y[4], date:y[5]})).appendTo(html_field); row.find("a").click(function() { frm.doc.template = $(this).attr("data-lab-test"); @@ -181,7 +181,7 @@ var show_lab_tests = function(frm, result){ }); }); if(!result.length){ - var msg = "No Lab Orders found for patient " + frm.doc.patient_name + ""; + var msg = __("No Lab Tests found for the Patient {0}", [frm.doc.patient_name.bold()]); html_field.empty(); $(repl('
%(msg)s
', {msg: msg})).appendTo(html_field); } diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.py b/erpnext/healthcare/doctype/lab_test/lab_test.py index e49429ea62..298d2939cf 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.py +++ b/erpnext/healthcare/doctype/lab_test/lab_test.py @@ -69,7 +69,7 @@ def create_multiple(doctype, docname): lab_test_created = create_lab_test_from_encounter(docname) if lab_test_created: - frappe.msgprint(_("Lab Test(s) " + lab_test_created + " created.")) + frappe.msgprint(_("Lab Test(s) {0} created".format(lab_test_created))) else: frappe.msgprint(_("No Lab Tests created")) @@ -111,7 +111,7 @@ def create_lab_test_from_invoice(invoice_name): if lab_test_created != 1: template = get_lab_test_template(item.item_code) if template: - lab_test = create_lab_test_doc(True, invoice.ref_practitioner, patient, template, company, invoice.company) + lab_test = create_lab_test_doc(True, invoice.ref_practitioner, patient, template, invoice.company) if item.reference_dt == "Lab Prescription": lab_test.prescription = item.reference_dn lab_test.save(ignore_permissions = True) diff --git a/erpnext/healthcare/doctype/patient/patient.js b/erpnext/healthcare/doctype/patient/patient.js index a11faf5806..490f247500 100644 --- a/erpnext/healthcare/doctype/patient/patient.js +++ b/erpnext/healthcare/doctype/patient/patient.js @@ -10,6 +10,8 @@ frappe.ui.form.on('Patient', { ] }; }); + frm.set_query('customer_group', {'is_group': 0}); + frm.set_query('default_price_list', { 'selling': 1}); if (frappe.defaults.get_default('patient_name_by') != 'Naming Series') { frm.toggle_display('naming_series', false); diff --git a/erpnext/healthcare/doctype/patient/patient.json b/erpnext/healthcare/doctype/patient/patient.json index 2c701fbf94..8af1a9ccd7 100644 --- a/erpnext/healthcare/doctype/patient/patient.json +++ b/erpnext/healthcare/doctype/patient/patient.json @@ -72,6 +72,7 @@ { "fieldname": "inpatient_status", "fieldtype": "Select", + "in_preview": 1, "label": "Inpatient Status", "options": "\nAdmission Scheduled\nAdmitted\nDischarge Scheduled", "read_only": 1 @@ -106,6 +107,7 @@ { "fieldname": "sex", "fieldtype": "Link", + "in_preview": 1, "label": "Gender", "options": "Gender", "reqd": 1 @@ -114,6 +116,7 @@ "bold": 1, "fieldname": "blood_group", "fieldtype": "Select", + "in_preview": 1, "label": "Blood Group", "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative" }, @@ -121,6 +124,7 @@ "bold": 1, "fieldname": "dob", "fieldtype": "Date", + "in_preview": 1, "label": "Date of birth" }, { @@ -147,6 +151,7 @@ "fieldname": "image", "fieldtype": "Attach Image", "hidden": 1, + "in_preview": 1, "label": "Image", "no_copy": 1, "print_hide": 1, @@ -161,9 +166,9 @@ "fieldname": "customer", "fieldtype": "Link", "ignore_user_permissions": 1, - "in_preview": 1, "label": "Customer", - "options": "Customer" + "options": "Customer", + "set_only_once": 1 }, { "fieldname": "report_preference", @@ -390,7 +395,7 @@ "image_field": "image", "links": [], "max_attachments": 50, - "modified": "2020-04-11 14:53:48.767245", + "modified": "2020-04-25 17:24:32.146415", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient", diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py index f83fac03a6..6349aece36 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -10,6 +10,7 @@ from frappe.utils import cint, cstr, getdate import dateutil from frappe.model.naming import set_name_by_naming_series from frappe.utils.nestedset import get_root_of +from erpnext import get_default_currency from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account, send_registration_sms class Patient(Document): @@ -17,6 +18,9 @@ class Patient(Document): self.set_full_name() self.add_as_website_user() + def before_insert(self): + self.set_missing_customer_details() + def after_insert(self): self.add_as_website_user() self.reload() @@ -28,12 +32,44 @@ class Patient(Document): send_registration_sms(self) self.reload() # self.notify_update() + def on_update(self): + if self.customer: + customer = frappe.get_doc('Customer', self.customer) + if self.customer_group: + customer.customer_group = self.customer_group + if self.territory: + customer.territory = self.territory + + customer.default_price_list = self.default_price_list + customer.default_currency = self.default_currency + customer.language = self.language + customer.save(ignore_permissions=True) + else: + if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient'): + create_customer(self) + def set_full_name(self): if self.last_name: self.patient_name = ' '.join(filter(None, [self.first_name, self.last_name])) else: self.patient_name = self.first_name + def set_missing_customer_details(self): + if not self.customer_group: + self.customer_group = frappe.db.get_single_value('Selling Settings', 'customer_group') or get_root_of('Customer Group') + if not self.territory: + self.territory = frappe.db.get_single_value('Selling Settings', 'territory') or get_root_of('Territory') + if not self.default_price_list: + self.default_price_list = frappe.db.get_single_value('Selling Settings', 'selling_price_list') + + if not self.customer_group or not self.territory or not self.default_price_list: + frappe.msgprint(_('Please set defaults for Customer Group, Territory and Selling Price List in Selling Settings'), alert=True) + + if not self.default_currency: + self.default_currency = get_default_currency() + if not self.language: + self.language = frappe.db.get_single_value('System Settings', 'language') + def add_as_website_user(self): if self.email: if not frappe.db.exists ('User', self.email): @@ -87,21 +123,14 @@ class Patient(Document): return {'invoice': sales_invoice.name} def create_customer(doc): - customer_group = doc.customer_group or frappe.db.get_single_value('Selling Settings', 'customer_group') - territory = doc.territory or frappe.db.get_single_value('Selling Settings', 'territory') - if not (customer_group and territory): - customer_group = get_root_of('Customer Group') - territory = get_root_of('Territory') - frappe.msgprint(_('Please set default customer group and territory in Selling Settings'), alert=True) - customer = frappe.get_doc({ 'doctype': 'Customer', 'customer_name': doc.patient_name, - 'customer_group': customer_group, - 'territory' : territory, + 'customer_group': doc.customer_group or frappe.db.get_single_value('Selling Settings', 'customer_group'), + 'territory' : doc.territory or frappe.db.get_single_value('Selling Settings', 'territory'), 'customer_type': 'Individual', 'default_currency': doc.default_currency, - 'default_price_ist': doc.default_price_list, + 'default_price_list': doc.default_price_list, 'language': doc.language }).insert(ignore_permissions=True, ignore_mandatory=True) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index 28db7ffea4..488abc2107 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -127,6 +127,11 @@ frappe.ui.form.on('Patient Appointment', { patient: function(frm) { if (frm.doc.patient) { frm.trigger('toggle_payment_fields'); + } else { + frm.set_value('patient_name', ''); + frm.set_value('patient_sex', ''); + frm.set_value('patient_age', ''); + frm.set_value('inpatient_record', ''); } }, diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index 6e889c2a0c..2b0bf216a9 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -10,33 +10,30 @@ "engine": "InnoDB", "field_order": [ "naming_series", + "status", "patient", "patient_name", "patient_sex", "patient_age", - "column_break_1", "inpatient_record", + "column_break_1", "company", "service_unit", - "status", - "section_break_11", - "get_procedure_from_encounter", - "column_break_13", "procedure_template", + "get_procedure_from_encounter", "procedure_prescription", "therapy_type", "get_prescribed_therapies", "therapy_plan", - "service_unit", - "section_break_12", "practitioner", "department", + "section_break_12", "appointment_type", + "duration", "column_break_17", "appointment_date", "appointment_time", "appointment_datetime", - "duration", "section_break_16", "mode_of_payment", "billing_item", @@ -45,9 +42,10 @@ "invoiced", "ref_sales_invoice", "section_break_3", - "notes", "referring_practitioner", - "reminded" + "reminded", + "column_break_36", + "notes" ], "fields": [ { @@ -59,7 +57,6 @@ "read_only": 1 }, { - "fetch_from": "inpatient_record.patient", "fieldname": "patient", "fieldtype": "Link", "ignore_user_permissions": 1, @@ -83,7 +80,8 @@ "fieldname": "duration", "fieldtype": "Int", "in_filter": 1, - "label": "Duration (In Minutes)" + "label": "Duration (In Minutes)", + "set_only_once": 1 }, { "fieldname": "column_break_1", @@ -102,6 +100,7 @@ "search_index": 1 }, { + "depends_on": "eval:doc.patient;", "fieldname": "procedure_template", "fieldtype": "Link", "label": "Clinical Procedure Template", @@ -133,7 +132,8 @@ }, { "fieldname": "section_break_12", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Appointment Details" }, { "fieldname": "practitioner", @@ -179,11 +179,13 @@ "fieldtype": "Time", "in_list_view": 1, "label": "Time", - "read_only": 1 + "read_only": 1, + "reqd": 1 }, { "fieldname": "section_break_16", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Payments" }, { "fetch_from": "patient.patient_name", @@ -212,6 +214,7 @@ { "fieldname": "appointment_datetime", "fieldtype": "Datetime", + "hidden": 1, "label": "Appointment Datetime", "print_hide": 1, "read_only": 1, @@ -243,9 +246,12 @@ { "fieldname": "company", "fieldtype": "Link", + "in_standard_filter": 1, "label": "Company", "no_copy": 1, - "options": "Company" + "options": "Company", + "reqd": 1, + "set_only_once": 1 }, { "collapsible": 1, @@ -311,24 +317,20 @@ "options": "HLC-APP-.YYYY.-", "set_only_once": 1 }, - { - "fieldname": "section_break_11", - "fieldtype": "Section Break" - }, - { - "fieldname": "column_break_13", - "fieldtype": "Column Break" - }, { "fieldname": "billing_item", "fieldtype": "Link", "label": "Billing Item", "options": "Item", "read_only": 1 + }, + { + "fieldname": "column_break_36", + "fieldtype": "Column Break" } ], "links": [], - "modified": "2020-04-23 11:16:34.981240", + "modified": "2020-04-25 17:23:49.841975", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Appointment", diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index 67fd82dbc2..a0e3de6b48 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -369,17 +369,19 @@ def send_appointment_reminder(): frappe.db.set_value('Patient Appointment', doc.name, 'reminded', 1) def send_message(doc, message): - patient = frappe.get_doc('Patient', doc.patient) - if patient.mobile: + patient_mobile = frappe.db.get_value("Patient", doc.patient, "mobile") + if patient_mobile: context = {'doc': doc, 'alert': doc, 'comments': None} if doc.get('_comments'): context['comments'] = json.loads(doc.get('_comments')) # jinja to string convertion happens here message = frappe.render_template(message, context) - number = [patient.mobile] - send_sms(number, message) - + number = [patient_mobile] + try: + send_sms(number, message) + except Exception as e: + frappe.msgprint(_("SMS not sent, please check SMS Settings"), alert=True) @frappe.whitelist() def get_events(start, end, filters=None): diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js index 7600900495..b2911d421e 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js @@ -25,15 +25,16 @@ frappe.ui.form.on('Patient Encounter', { refresh_field('lab_test_prescription'); if (!frm.doc.__islocal) { - - if (frm.doc.inpatient_status == 'Admission Scheduled' || frm.doc.inpatient_status == 'Admitted') { - frm.add_custom_button(__('Schedule Discharge'), function() { - schedule_discharge(frm); - }); - } else if (frm.doc.inpatient_status != 'Discharge Scheduled') { - frm.add_custom_button(__('Schedule Admission'), function() { - schedule_inpatient(frm); - }); + if (frm.doc.docstatus == 1) { + if (frm.doc.inpatient_status == 'Admission Scheduled' || frm.doc.inpatient_status == 'Admitted') { + frm.add_custom_button(__('Schedule Discharge'), function() { + schedule_discharge(frm); + }); + } else if (frm.doc.inpatient_status != 'Discharge Scheduled') { + frm.add_custom_button(__('Schedule Admission'), function() { + schedule_inpatient(frm); + }); + } } frm.add_custom_button(__('Patient History'), function() { @@ -101,6 +102,11 @@ frappe.ui.form.on('Patient Encounter', { frm.events.set_patient_info(frm); }, + practitioner: function(frm) { + if(!frm.doc.practitioner) { + frm.set_value('practitioner_name', ''); + } + }, set_appointment_fields: function(frm) { if (frm.doc.appointment) { frappe.call({ @@ -118,6 +124,7 @@ frappe.ui.form.on('Patient Encounter', { 'company': data.message.company }; frm.set_value(values); + frm.set_df_property('patient', 'read_only', 1); } }); } @@ -134,6 +141,7 @@ frappe.ui.form.on('Patient Encounter', { 'inpatient_status': '' }; frm.set_value(values); + frm.set_df_property('patient', 'read_only', 0); } }, @@ -149,15 +157,20 @@ frappe.ui.form.on('Patient Encounter', { if (data.message.dob) { age = calculate_age(data.message.dob); } + frappe.model.set_value(frm.doctype, frm.docname, 'patient_mame', data.message.patient_mame); frappe.model.set_value(frm.doctype, frm.docname, 'patient_age', age); frappe.model.set_value(frm.doctype, frm.docname, 'patient_sex', data.message.sex); if (data.message.inpatient_record) { frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_record', data.message.inpatient_record); frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_status', data.message.inpatient_status); + } else { + frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_record', ''); + frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_status', ''); } } }); } else { + frappe.model.set_value(frm.doctype, frm.docname, 'patient_mame', ''); frappe.model.set_value(frm.doctype, frm.docname, 'patient_sex', ''); frappe.model.set_value(frm.doctype, frm.docname, 'patient_age', ''); frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_record', ''); diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json index ff7ecc926f..f1831ad7a1 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json @@ -17,17 +17,16 @@ "patient_name", "patient_sex", "patient_age", + "inpatient_record", + "inpatient_status", "column_break_6", + "company", + "encounter_date", + "encounter_time", "practitioner", "practitioner_name", "medical_department", - "encounter_date", - "encounter_time", "invoiced", - "section_break_1", - "inpatient_record", - "column_break_17", - "inpatient_status", "sb_symptoms", "symptoms", "symptoms_in_print", @@ -48,7 +47,6 @@ "section_break_33", "encounter_comment", "sb_refs", - "company", "amended_from" ], "fields": [ @@ -59,12 +57,6 @@ "options": "Inpatient Record", "read_only": 1 }, - { - "collapsible": 1, - "fieldname": "section_break_1", - "fieldtype": "Section Break", - "label": "Inpatient Details" - }, { "fieldname": "naming_series", "fieldtype": "Select", @@ -79,10 +71,10 @@ "ignore_user_permissions": 1, "label": "Appointment", "options": "Patient Appointment", - "search_index": 1 + "search_index": 1, + "set_only_once": 1 }, { - "fetch_from": "inpatient_record.patient", "fieldname": "patient", "fieldtype": "Link", "ignore_user_permissions": 1, @@ -93,7 +85,6 @@ "search_index": 1 }, { - "fetch_from": "patient.patient_name", "fieldname": "patient_name", "fieldtype": "Data", "label": "Patient Name", @@ -206,29 +197,29 @@ { "fieldname": "codification_table", "fieldtype": "Table", - "label": "Medical Coding", + "label": "Medical Codes", "options": "Codification Table" }, { "fieldname": "sb_drug_prescription", "fieldtype": "Section Break", - "label": "Medication" + "label": "Medications" }, { "fieldname": "drug_prescription", "fieldtype": "Table", - "label": "Drug Prescription", + "label": "Items", "options": "Drug Prescription" }, { "fieldname": "sb_test_prescription", "fieldtype": "Section Break", - "label": "Investigation" + "label": "Investigations" }, { "fieldname": "lab_test_prescription", "fieldtype": "Table", - "label": "Lab Prescription", + "label": "Lab Tests", "options": "Lab Prescription" }, { @@ -239,7 +230,7 @@ { "fieldname": "procedure_prescription", "fieldtype": "Table", - "label": "Procedure Prescription", + "label": "Clinical Procedures", "no_copy": 1, "options": "Procedure Prescription" }, @@ -309,10 +300,6 @@ "label": "Inpatient Status", "read_only": 1 }, - { - "fieldname": "column_break_17", - "fieldtype": "Column Break" - }, { "fieldname": "sb_refs", "fieldtype": "Section Break" @@ -328,7 +315,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-04-23 23:06:16.348846", + "modified": "2020-04-27 18:59:25.713887", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Encounter", From 256b77b463f7bba7dece0d65dec4178ec89b9406 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 27 Apr 2020 20:00:27 +0530 Subject: [PATCH 42/96] fix: filter service unit by company --- .../doctype/clinical_procedure/clinical_procedure.js | 3 ++- .../doctype/patient_appointment/patient_appointment.js | 5 +++-- .../doctype/therapy_session/therapy_session.js | 10 ++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js index 2d30e9003b..7b992a80b7 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js @@ -43,7 +43,8 @@ frappe.ui.form.on('Clinical Procedure', { return { filters: { 'is_group': false, - 'allow_appointments': true + 'allow_appointments': true, + 'company': frm.doc.company } }; }); diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index 488abc2107..f7ed31bfea 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -32,8 +32,9 @@ frappe.ui.form.on('Patient Appointment', { frm.set_query('service_unit', function(){ return { filters: { - 'is_group': 0, - 'allow_appointments': 1 + 'is_group': false, + 'allow_appointments': true, + 'company': frm.doc.company } }; }); diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.js b/erpnext/healthcare/doctype/therapy_session/therapy_session.js index bb675752bb..10d657af2e 100644 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session.js +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.js @@ -9,6 +9,16 @@ frappe.ui.form.on('Therapy Session', { {fieldname: 'counts_completed', columns: 1}, {fieldname: 'assistance_level', columns: 1} ]; + + frm.set_query('service_unit', function() { + return { + filters: { + 'is_group': false, + 'allow_appointments': true, + 'company': frm.doc.company + } + }; + }); }, refresh: function(frm) { From ca6f3ec977e0a1500425e776fb0f37fc8981cff2 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 27 Apr 2020 22:27:13 +0530 Subject: [PATCH 43/96] fix: set customer name on patient update --- erpnext/healthcare/doctype/patient/patient.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py index 6349aece36..ea63ac7492 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -40,6 +40,7 @@ class Patient(Document): if self.territory: customer.territory = self.territory + customer.customer_name = self.patient_name customer.default_price_list = self.default_price_list customer.default_currency = self.default_currency customer.language = self.language From 0f541cb7ab6e8a93b21756c464e6d37dd4699e01 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 27 Apr 2020 23:42:22 +0530 Subject: [PATCH 44/96] feat: set title for appointment, encounter, procedure and vitals --- .../clinical_procedure.json | 15 ++++++- .../clinical_procedure/clinical_procedure.py | 4 ++ .../patient_appointment.json | 22 ++++++++++- .../patient_appointment.py | 6 +++ .../patient_encounter/patient_encounter.js | 32 +++++++-------- .../patient_encounter/patient_encounter.json | 15 ++++++- .../patient_encounter/patient_encounter.py | 7 ++++ .../doctype/vital_signs/vital_signs.json | 39 +++++++++++++------ .../doctype/vital_signs/vital_signs.py | 7 ++++ 9 files changed, 113 insertions(+), 34 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json index 7c4b9a3ed3..eaf8d80ba8 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json @@ -8,6 +8,7 @@ "engine": "InnoDB", "field_order": [ "naming_series", + "title", "appointment", "procedure_template", "column_break_30", @@ -279,11 +280,21 @@ { "fieldname": "column_break_34", "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-04-24 22:53:13.156901", + "modified": "2020-04-27 21:36:23.796924", "modified_by": "Administrator", "module": "Healthcare", "name": "Clinical Procedure", @@ -320,6 +331,6 @@ "restrict_to_domain": "Healthcare", "sort_field": "modified", "sort_order": "DESC", - "title_field": "patient_name", + "title_field": "title", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py index 56617e5258..168307b734 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py @@ -16,6 +16,7 @@ from frappe.model.mapper import get_mapped_doc class ClinicalProcedure(Document): def validate(self): self.set_status() + self.set_title() if self.consume_stock: self.set_actual_qty() @@ -50,6 +51,9 @@ class ClinicalProcedure(Document): elif self.docstatus == 2: self.status = 'Cancelled' + def set_title(self): + self.title = _('{0} - {1}').format(self.patient_name or self.patient, self.procedure_template)[:100] + def complete_procedure(self): if self.consume_stock and self.items: stock_entry = make_stock_entry(self) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index 2b0bf216a9..b8a400c6b7 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -10,6 +10,7 @@ "engine": "InnoDB", "field_order": [ "naming_series", + "title", "status", "patient", "patient_name", @@ -26,6 +27,7 @@ "get_prescribed_therapies", "therapy_plan", "practitioner", + "practitioner_name", "department", "section_break_12", "appointment_type", @@ -327,10 +329,26 @@ { "fieldname": "column_break_36", "fieldtype": "Column Break" + }, + { + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fetch_from": "practitioner.practitioner_name", + "fieldname": "practitioner_name", + "fieldtype": "Data", + "label": "Practitioner Name", + "read_only": 1 } ], "links": [], - "modified": "2020-04-25 17:23:49.841975", + "modified": "2020-04-27 21:36:06.404062", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Appointment", @@ -378,7 +396,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "title_field": "patient", + "title_field": "title", "track_changes": 1, "track_seen": 1 } \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index a0e3de6b48..c296065d4a 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -21,6 +21,7 @@ class PatientAppointment(Document): self.set_appointment_datetime() self.validate_customer_created() self.set_status() + self.set_title() def after_insert(self): self.update_prescription_details() @@ -28,6 +29,11 @@ class PatientAppointment(Document): self.update_fee_validity() send_confirmation_msg(self) + def set_title(self): + self.title = _('{0} with {1} on {2}').format(self.patient_name or self.patient, + self.practitioner_name or self.practitioner, + frappe.utils.format_datetime(self.appointment_datetime))[:100] + def set_status(self): today = getdate() appointment_date = getdate(self.appointment_date) diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js index b2911d421e..20ee9daede 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js @@ -157,24 +157,25 @@ frappe.ui.form.on('Patient Encounter', { if (data.message.dob) { age = calculate_age(data.message.dob); } - frappe.model.set_value(frm.doctype, frm.docname, 'patient_mame', data.message.patient_mame); - frappe.model.set_value(frm.doctype, frm.docname, 'patient_age', age); - frappe.model.set_value(frm.doctype, frm.docname, 'patient_sex', data.message.sex); - if (data.message.inpatient_record) { - frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_record', data.message.inpatient_record); - frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_status', data.message.inpatient_status); - } else { - frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_record', ''); - frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_status', ''); - } + let values = { + 'patient_age': age, + 'patient_name':data.message.patient_name, + 'patient_sex': data.message.sex, + 'inpatient_record': data.message.inpatient_record, + 'inpatient_status': data.message.inpatient_status + }; + frm.set_value(values); } }); } else { - frappe.model.set_value(frm.doctype, frm.docname, 'patient_mame', ''); - frappe.model.set_value(frm.doctype, frm.docname, 'patient_sex', ''); - frappe.model.set_value(frm.doctype, frm.docname, 'patient_age', ''); - frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_record', ''); - frappe.model.set_value(frm.doctype, frm.docname, 'inpatient_status', ''); + let values = { + 'patient_age': '', + 'patient_name':'', + 'patient_sex': '', + 'inpatient_record': '', + 'inpatient_status': '' + }; + frm.set_value(values); } } }); @@ -226,7 +227,6 @@ let create_vital_signs = function (frm) { } frappe.route_options = { 'patient': frm.doc.patient, - 'appointment': frm.doc.appointment, 'encounter': frm.doc.name, 'company': frm.doc.company }; diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json index f1831ad7a1..05eec87398 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json @@ -11,6 +11,7 @@ "engine": "InnoDB", "field_order": [ "naming_series", + "title", "appointment", "appointment_type", "patient", @@ -311,11 +312,21 @@ "in_list_view": 1, "label": "Practitioner Name", "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-04-27 18:59:25.713887", + "modified": "2020-04-27 21:58:29.789797", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Encounter", @@ -342,7 +353,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "title_field": "patient_name", + "title_field": "title", "track_changes": 1, "track_seen": 1 } \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py index 767643bc73..bab3d3e45f 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py @@ -10,6 +10,9 @@ from frappe.utils import cstr from frappe import _ class PatientEncounter(Document): + def validate(self): + self.set_title() + def on_update(self): if self.appointment: frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed') @@ -26,6 +29,10 @@ class PatientEncounter(Document): def on_submit(self): create_therapy_plan(self) + def set_title(self): + self.title = _('{0} with {1}').format(self.patient_name or self.patient, + self.practitioner_name or self.practitioner)[:100] + def create_therapy_plan(encounter): if len(encounter.therapies): doc = frappe.new_doc('Therapy Plan') diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.json b/erpnext/healthcare/doctype/vital_signs/vital_signs.json index fdacda6277..57fc2369e1 100644 --- a/erpnext/healthcare/doctype/vital_signs/vital_signs.json +++ b/erpnext/healthcare/doctype/vital_signs/vital_signs.json @@ -2,15 +2,22 @@ "actions": [], "allow_copy": 1, "allow_import": 1, + "autoname": "naming_series:", "beta": 1, "creation": "2017-02-02 11:00:24.853005", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "naming_series", + "title", "patient", "patient_name", + "inpatient_record", + "appointment", + "encounter", "column_break_2", + "company", "signs_date", "signs_time", "sb_vs", @@ -32,11 +39,6 @@ "column_break_14", "nutrition_note", "sb_references", - "inpatient_record", - "appointment", - "encounter", - "column_break_28", - "company", "amended_from" ], "fields": [ @@ -70,7 +72,8 @@ "fieldname": "appointment", "fieldtype": "Link", "in_filter": 1, - "label": "Appointment", + "label": "Patient Appointment", + "no_copy": 1, "options": "Patient Appointment", "print_hide": 1, "read_only": 1 @@ -83,8 +86,7 @@ "no_copy": 1, "options": "Patient Encounter", "print_hide": 1, - "read_only": 1, - "report_hide": 1 + "read_only": 1 }, { "fieldname": "column_break_2", @@ -237,13 +239,26 @@ "fieldtype": "Section Break" }, { - "fieldname": "column_break_28", - "fieldtype": "Column Break" + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "HLC-VTS-.YYYY.-", + "reqd": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-04-03 23:06:29.786184", + "modified": "2020-04-27 23:18:08.278064", "modified_by": "Administrator", "module": "Healthcare", "name": "Vital Signs", @@ -283,7 +298,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "title_field": "patient", + "title_field": "title", "track_changes": 1, "track_seen": 1 } \ No newline at end of file diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.py b/erpnext/healthcare/doctype/vital_signs/vital_signs.py index 959e8504c4..5c590cff46 100644 --- a/erpnext/healthcare/doctype/vital_signs/vital_signs.py +++ b/erpnext/healthcare/doctype/vital_signs/vital_signs.py @@ -9,12 +9,19 @@ from frappe.utils import cstr from frappe import _ 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} {2}').format(self.patient_name or self.patient, + frappe.utils.format_date(self.signs_date), frappe.utils.format_time(self.signs_time))[:100] + def insert_vital_signs_to_medical_record(doc): subject = set_subject_field(doc) medical_record = frappe.new_doc('Patient Medical Record') From 1696a2a4b957870f0ff187acdcc6021ca0737dc1 Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 27 Apr 2020 23:44:41 +0530 Subject: [PATCH 45/96] fix: default sms text in settings - corrected field names --- .../doctype/healthcare_settings/healthcare_settings.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json index de08620179..2f0115c36a 100644 --- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json +++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json @@ -240,7 +240,7 @@ "label": "Patient Registration" }, { - "default": "Hello {{doc.patient}}, Thank you for registering with {{doc.company}}. Your ID is {{doc.id}} . Please note this ID for future reference. \nThank You, Get well soon!", + "default": "Hello {{doc.patient}}, Thank you for registering with {{doc.company}}. Your ID is {{doc.name}} . Please note this ID for future reference. \nThank You!", "depends_on": "send_registration_msg", "fieldname": "registration_msg", "fieldtype": "Small Text", @@ -254,7 +254,7 @@ "label": "Appointment Confirmation" }, { - "default": "Hello {{doc.patient}}, You have scheduled an appointment with {{doc.practitioner}} by {{doc.start_dt}} at {{doc.company}}.\nThank you, Good day!", + "default": "Hello {{doc.patient}}, You have scheduled an appointment with {{doc.practitioner}} on {{doc.appointment_datetime}} at {{doc.company}}.\nThank you, Good day!", "depends_on": "send_appointment_confirmation", "fieldname": "appointment_confirmation_msg", "fieldtype": "Small Text", @@ -276,7 +276,7 @@ "label": "Appointment Reminder" }, { - "default": "Hello {{doc.patient}}, You have an appointment with {{doc.practitioner}} by {{doc.appointment_time}} at {{doc.company}}.\nThank you, Good day!\n", + "default": "Hello {{doc.patient}}, You have an appointment with {{doc.practitioner}} by {{doc.appointment_datetime}} at {{doc.company}}.\nThank you, Good day!\n", "depends_on": "send_appointment_reminder", "fieldname": "appointment_reminder_msg", "fieldtype": "Small Text", From d097eaef6304a247f7f7681e79acd38bc5ce3349 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Tue, 5 May 2020 15:57:49 +0530 Subject: [PATCH 46/96] Appending Email and Phone in Child Table --- erpnext/selling/doctype/customer/customer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 50e719f02e..d0db6d62a0 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -164,6 +164,8 @@ class Customer(TransactionBase): contact.phone = lead.phone contact.mobile_no = lead.mobile_no contact.is_primary_contact = 1 + contact.append('email_ids', dict(email_id=lead.email_id, is_primary=1)) + contact.append('phone_nos', dict(phone=lead.mobile_no, is_primary_mobile_no=1)) contact.append('links', dict(link_doctype='Customer', link_name=self.name)) contact.flags.ignore_permissions = self.flags.ignore_permissions contact.autoname() From 31a747b98ad4220d6659523f9a53b5bb81ba9252 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 7 May 2020 17:38:00 +0530 Subject: [PATCH 47/96] fix: Query enhancement, cleanup, added extra filter - Query changes as requested - Moved chart generation from js to py - Added Supplier Multiselect filter --- .../quoted_item_comparison.js | 64 ++--------- .../quoted_item_comparison.py | 101 ++++++++++++++---- 2 files changed, 88 insertions(+), 77 deletions(-) diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js index fe4abd8c9c..a76ffeec2e 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js @@ -16,7 +16,7 @@ frappe.query_reports["Quoted Item Comparison"] = { default: "", options: "Item", label: __("Item"), - fieldname: "item", + fieldname: "item_code", fieldtype: "Link", get_query: () => { let quote = frappe.query_report.get_filter_value('supplier_quotation'); @@ -36,6 +36,14 @@ frappe.query_reports["Quoted Item Comparison"] = { } } }, + { + fieldname: "supplier", + label: __("Supplier"), + fieldtype: "MultiSelectList", + get_data: function(txt) { + return frappe.db.get_link_options('Supplier', txt); + } + }, { fieldtype: "Link", label: __("Supplier Quotation"), @@ -58,60 +66,6 @@ frappe.query_reports["Quoted Item Comparison"] = { } ], - prepare_chart_data: (result) => { - let supplier_wise_map = {}, data_points_map = {}; - let qty_list = result.map(res => res.qty); - qty_list.sort(); - qty_list = new Set(qty_list); - - // create supplier wise map like in Report - for (let res of result) { - if (!(res.supplier in supplier_wise_map)) { - supplier_wise_map[res.supplier] = {}; - } - supplier_wise_map[res.supplier][res.qty] = res.price; - } - - // create datapoints for each qty - for (let supplier of Object.keys(supplier_wise_map)) { - let row = supplier_wise_map[supplier]; - for (let qty of qty_list) { - if (!data_points_map[qty]) { - data_points_map[qty] = []; - } - if (row[qty]) { - data_points_map[qty].push(row[qty]); - } - else { - data_points_map[qty].push(null); - } - } - } - - let dataset = []; - qty_list.forEach((qty) => { - let datapoints = { - 'name': __('Price for Qty ') + qty, - 'values': data_points_map[qty] - } - dataset.push(datapoints); - }); - return dataset; - }, - - get_chart_data: function (columns, result) { - let suppliers = result.filter(d => d.supplier_name).map(res => res.supplier_name); - let dataset = frappe.query_reports["Quoted Item Comparison"].prepare_chart_data(result); - - return { - data: { - labels: suppliers, - datasets: dataset - }, - type: 'bar' - } - }, - onload: (report) => { // Create a button for setting the default supplier report.page.add_inner_button(__("Select Default Supplier"), () => { diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py index fd7a731198..a33867a525 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py @@ -9,21 +9,33 @@ from collections import defaultdict from erpnext.setup.utils import get_exchange_rate def execute(filters=None): + if not filters: + return [], [] + conditions = get_conditions(filters) - data = get_data(filters, conditions) + supplier_quotation_data = get_data(filters, conditions) columns = get_columns() - return columns, data + + data, chart_data = prepare_data(supplier_quotation_data) + + return columns, data, None, chart_data + +def get_conditions(filters): + conditions = "" + if filters.get("supplier_quotation"): + conditions += " AND sqi.parent = %(supplier_quotation)s" + + if filters.get("request_for_quotation"): + conditions += " AND sqi.request_for_quotation = %(request_for_quotation)s" + + if filters.get("supplier"): + conditions += " AND sq.supplier in %(supplier)s" + return conditions def get_data(filters, conditions): - out, suppliers = [], [] - item = filters.get("item") - - if not item: + if not filters.get("item_code"): return [] - company_currency = frappe.db.get_default("currency") - float_precision = cint(frappe.db.get_default("float_precision")) or 2 - supplier_quotation_data = frappe.db.sql("""SELECT sqi.parent, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation, sq.supplier @@ -31,17 +43,27 @@ def get_data(filters, conditions): `tabSupplier Quotation Item` sqi, `tabSupplier Quotation` sq WHERE - sqi.item_code = '{0}' + sqi.item_code = %(item_code)s AND sqi.parent = sq.name AND sqi.docstatus < 2 - AND sq.company = '{1}' + AND sq.company = %(company)s AND sq.status != 'Expired' - {2}""".format(item, filters.get("company"), conditions), as_dict=1) + {0}""".format(conditions), filters, as_dict=1) + return supplier_quotation_data + +def prepare_data(supplier_quotation_data): + out, suppliers, qty_list = [], [], [] supplier_wise_map = defaultdict(list) + supplier_qty_price_map = {} + + company_currency = frappe.db.get_default("currency") + float_precision = cint(frappe.db.get_default("float_precision")) or 2 for data in supplier_quotation_data: + supplier = data.get("supplier") supplier_currency = frappe.db.get_value("Supplier", data.get("supplier"), "default_currency") + if supplier_currency: exchange_rate = get_exchange_rate(supplier_currency, company_currency) else: @@ -53,29 +75,64 @@ def get_data(filters, conditions): "price": flt(data.get("rate") * exchange_rate, float_precision), "uom": data.get("uom"), "request_for_quotation": data.get("request_for_quotation"), - "supplier": data.get("supplier") # used for chart generation } - supplier_wise_map[data.supplier].append(row) - suppliers.append(data.supplier) + # map for report view of form {'supplier1':[{},{},...]} + supplier_wise_map[supplier].append(row) - suppliers = set(suppliers) + # map for chart preparation of the form {'supplier1': {'qty': 'price'}} + if not supplier in supplier_qty_price_map: + supplier_qty_price_map[supplier] = {} + supplier_qty_price_map[supplier][row["qty"]] = row["price"] + suppliers.append(supplier) + qty_list.append(data.get("qty")) + + suppliers = list(set(suppliers)) + qty_list = list(set(qty_list)) + + # final data format for report view for supplier in suppliers: supplier_wise_map[supplier][0].update({"supplier_name": supplier}) for entry in supplier_wise_map[supplier]: out.append(entry) - return out + chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map) -def get_conditions(filters): - conditions = "" + return out, chart_data - if filters.get("request_for_quotation"): - conditions += " AND sqi.request_for_quotation = '{0}' ".format(filters.get("request_for_quotation")) +def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map): + data_points_map = {} + qty_list.sort() - return conditions + # create qty wise values map of the form {'qty1':[value1, value2]} + for supplier in suppliers: + entry = supplier_qty_price_map[supplier] + for qty in qty_list: + if not qty in data_points_map: + data_points_map[qty] = [] + if qty in entry: + data_points_map[qty].append(entry[qty]) + else: + data_points_map[qty].append(None) + dataset = [] + for qty in qty_list: + datapoints = { + "name": _("Price for Qty ") + str(qty), + "values": data_points_map[qty] + } + dataset.append(datapoints) + + chart_data = { + "data": { + "labels": suppliers, + "datasets": dataset + }, + "type": "bar" + } + + return chart_data def get_columns(): columns = [{ From e29283630b5b36e7d2eecbeff29726826d5eaba2 Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Fri, 8 May 2020 17:45:08 +0800 Subject: [PATCH 48/96] fix: Zero threshold in Tax Withholding Category This fixes a bug in Tax Withholding Category where if it has a threshold of 0, it doesn't apply to Purchase Invoices. The bug was fixed by updating the condition. --- .../tax_withholding_category/tax_withholding_category.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index dd6b4fdc60..818e56633a 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -162,8 +162,7 @@ def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_detai debit_note_amount = get_debit_note_amount(suppliers, year_start_date, year_end_date) supplier_credit_amount -= debit_note_amount - if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold) - or (tax_details.get('cumulative_threshold', 0) and supplier_credit_amount >= tax_details.cumulative_threshold)): + if supplier_credit_amount >= tax_details.get('threshold', 0) or supplier_credit_amount >= tax_details.get('cumulative_threshold', 0): if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, tds_deducted, net_total, ldc.certificate_limit): @@ -225,4 +224,4 @@ def is_valid_certificate(valid_from, valid_upto, posting_date, deducted_amount, certificate_limit > deducted_amount): valid = True - return valid \ No newline at end of file + return valid From 9d81f97fe677d0ac92064fc9a4e37bf4f5d4a6a0 Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Fri, 8 May 2020 18:01:18 +0800 Subject: [PATCH 49/96] fix: Tax Withholding Category Description default This commit fixes a bug that happens when a Purchase Invoice uses a Tax Withholding Category without a category_name. The category_name is used as the tax description which is a required field. The bug was fixed by using the Tax Withholding Category's name as the description if the category_name is empty. --- .../tax_withholding_category/tax_withholding_category.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index dd6b4fdc60..e904a681f6 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -58,7 +58,7 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company): "rate": tax_rate_detail.tax_withholding_rate, "threshold": tax_rate_detail.single_threshold, "cumulative_threshold": tax_rate_detail.cumulative_threshold, - "description": tax_withholding.category_name + "description": tax_withholding.category_name.strip() if tax_withholding.category_name.strip() else tax_withholding_category }) def get_tax_withholding_rates(tax_withholding, fiscal_year): @@ -225,4 +225,4 @@ def is_valid_certificate(valid_from, valid_upto, posting_date, deducted_amount, certificate_limit > deducted_amount): valid = True - return valid \ No newline at end of file + return valid From be6eb201b71150202f3c63fa2c632a5a9594cd95 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Fri, 8 May 2020 17:33:21 +0530 Subject: [PATCH 50/96] Appending Email and Phone in Child Table --- erpnext/selling/doctype/customer/customer.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index d0db6d62a0..3d172ac7a2 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -164,9 +164,11 @@ class Customer(TransactionBase): contact.phone = lead.phone contact.mobile_no = lead.mobile_no contact.is_primary_contact = 1 - contact.append('email_ids', dict(email_id=lead.email_id, is_primary=1)) - contact.append('phone_nos', dict(phone=lead.mobile_no, is_primary_mobile_no=1)) contact.append('links', dict(link_doctype='Customer', link_name=self.name)) + if lead.email_id: + contact.append('email_ids', dict(email_id=lead.email_id, is_primary=1)) + if lead.mobile_no: + contact.append('phone_nos', dict(phone=lead.mobile_no, is_primary_mobile_no=1)) contact.flags.ignore_permissions = self.flags.ignore_permissions contact.autoname() if not frappe.db.exists("Contact", contact.name): From d543830a59d1a87bf6de83244303347ac168e769 Mon Sep 17 00:00:00 2001 From: Abhishek Balam Date: Sat, 9 May 2020 21:56:53 +0530 Subject: [PATCH 51/96] feat(Selling): Added Territory wise treeview to 'Customer Acquistion and Loyalty' report --- .../customer_acquisition_and_loyalty.js | 20 +- .../customer_acquisition_and_loyalty.py | 239 ++++++++++++++---- erpnext/setup/doctype/territory/territory.py | 5 +- 3 files changed, 212 insertions(+), 52 deletions(-) diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js index a854fa9969..654614b4bb 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js @@ -3,6 +3,13 @@ frappe.query_reports["Customer Acquisition and Loyalty"] = { "filters": [ + { + "fieldname":"view_type", + "label": __("View Type"), + "fieldtype": "Select", + "default": "Time Series", + "options": ["Time Series", "Territory Tree"] + }, { "fieldname":"company", "label": __("Company"), @@ -24,6 +31,13 @@ frappe.query_reports["Customer Acquisition and Loyalty"] = { "fieldtype": "Date", "default": frappe.defaults.get_user_default("year_end_date"), "reqd": 1 - }, - ] -} + } + ], + 'formatter': function(value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + if (data && data.bold) { + value = value.bold(); + } + return value + } +} \ No newline at end of file diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py index aa57665a81..f2033f1fff 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py @@ -2,65 +2,210 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals +import calendar import frappe from frappe import _ -from frappe.utils import getdate, cint, cstr -import calendar +from frappe.utils import cint, cstr def execute(filters=None): - # key yyyy-mm - new_customers_in = {} - repeat_customers_in = {} - customers = [] - company_condition = "" + common_columns = [ + { + 'label': _('New Customers'), + 'fieldname': 'new_customers', + 'fieldtype': 'Int', + 'default': 0, + 'width': 100 + }, + { + 'label': _('Repeat Customers'), + 'fieldname': 'repeat_customers', + 'fieldtype': 'Int', + 'default': 0, + 'width': 100 + }, + { + 'label': _('Total'), + 'fieldname': 'total', + 'fieldtype': 'Int', + 'default': 0, + 'width': 100 + }, + { + 'label': _('New Customer Revenue'), + 'fieldname': 'new_customer_revenue', + 'fieldtype': 'Currency', + 'default': 0.0, + 'width': 150 + }, + { + 'label': _('Repeat Customer Revenue'), + 'fieldname': 'repeat_customer_revenue', + 'fieldtype': 'Currency', + 'default': 0.0, + 'width': 150 + }, + { + 'label': _('Total Revenue'), + 'fieldname': 'total_revenue', + 'fieldtype': 'Currency', + 'default': 0.0, + 'width': 150 + } + ] + if filters.get('view_type') == 'Territory Tree': + return get_data_by_territory(filters, common_columns) + else: + return get_data_by_time(filters, common_columns) - if filters.get("company"): - company_condition = ' and company=%(company)s' +def get_data_by_time(filters, common_columns): + # key yyyy-mm + columns = [ + { + 'label': 'Year', + 'fieldname': 'year', + 'fieldtype': 'Data', + 'width': 100 + }, + { + 'label': 'Month', + 'fieldname': 'month', + 'fieldtype': 'Data', + 'width': 100 + }, + ] + columns += common_columns - for si in frappe.db.sql("""select posting_date, customer, base_grand_total from `tabSales Invoice` - where docstatus=1 and posting_date <= %(to_date)s - {company_condition} order by posting_date""".format(company_condition=company_condition), - filters, as_dict=1): + customers_in = get_customer_stats(filters) - key = si.posting_date.strftime("%Y-%m") - if not si.customer in customers: - new_customers_in.setdefault(key, [0, 0.0]) - new_customers_in[key][0] += 1 - new_customers_in[key][1] += si.base_grand_total - customers.append(si.customer) - else: - repeat_customers_in.setdefault(key, [0, 0.0]) - repeat_customers_in[key][0] += 1 - repeat_customers_in[key][1] += si.base_grand_total + # time series + from_year, from_month, temp = filters.get('from_date').split('-') + to_year, to_month, temp = filters.get('to_date').split('-') - # time series - from_year, from_month, temp = filters.get("from_date").split("-") - to_year, to_month, temp = filters.get("to_date").split("-") + from_year, from_month, to_year, to_month = \ + cint(from_year), cint(from_month), cint(to_year), cint(to_month) - from_year, from_month, to_year, to_month = \ - cint(from_year), cint(from_month), cint(to_year), cint(to_month) + out = [] + for year in range(from_year, to_year+1): + for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13): + key = '{year}-{month:02d}'.format(year=year, month=month) + data = customers_in.get(key) + new = data['new'] if data else [0, 0.0] + repeat = data['repeat'] if data else [0, 0.0] + out.append({ + 'year': cstr(year), + 'month': calendar.month_name[month], + 'new_customers': new[0], + 'repeat_customers': repeat[0], + 'total': new[0] + repeat[0], + 'new_customer_revenue': new[1], + 'repeat_customer_revenue': repeat[1], + 'total_revenue': new[1] + repeat[1] + }) + return columns, out - out = [] - for year in range(from_year, to_year+1): - for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13): - key = "{year}-{month:02d}".format(year=year, month=month) +def get_data_by_territory(filters, common_columns): + columns = [{ + 'label': 'Territory', + 'fieldname': 'territory', + 'fieldtype': 'Link', + 'options': 'Territory', + 'width': 150 + }] + columns += common_columns - new = new_customers_in.get(key, [0,0.0]) - repeat = repeat_customers_in.get(key, [0,0.0]) + customers_in = get_customer_stats(filters, tree_view=True) - out.append([cstr(year), calendar.month_name[month], - new[0], repeat[0], new[0] + repeat[0], - new[1], repeat[1], new[1] + repeat[1]]) + territory_dict = {} + for t in frappe.db.sql('''SELECT name, lft, parent_territory, is_group FROM `tabTerritory` ORDER BY lft''', as_dict=1): + territory_dict.update({ + t.name: { + 'parent': t.parent_territory, + 'is_group': t.is_group + } + }) - return [ - _("Year") + "::100", - _("Month") + "::100", - _("New Customers") + ":Int:100", - _("Repeat Customers") + ":Int:100", - _("Total") + ":Int:100", - _("New Customer Revenue") + ":Currency:150", - _("Repeat Customer Revenue") + ":Currency:150", - _("Total Revenue") + ":Currency:150" - ], out + depth_map = frappe._dict() + for name, info in territory_dict.items(): + default = depth_map.get(info['parent']) + 1 if info['parent'] else 0 + depth_map.setdefault(name, default) + data = [] + for name, indent in depth_map.items(): + condition = customers_in.get(name) + new = customers_in[name]['new'] if condition else [0, 0.0] + repeat = customers_in[name]['repeat'] if condition else [0, 0.0] + temp = { + 'territory': name, + 'indent': indent, + 'new_customers': new[0], + 'repeat_customers': repeat[0], + 'total': new[0] + repeat[0], + 'new_customer_revenue': new[1], + 'repeat_customer_revenue': repeat[1], + 'total_revenue': new[1] + repeat[1], + 'bold': 0 if condition else 1 + } + data.append(temp) + node_list = [x for x in territory_dict.keys() if territory_dict[x]['is_group'] == 0] + root_node = [x for x in territory_dict.keys() if territory_dict[x]['parent'] is None][0] + for node in node_list: + data = update_groups(node, data, root_node, territory_dict) + + for group in [x for x in territory_dict.keys() if territory_dict[x]['parent'] == root_node]: + group_data = [x for x in data if x['territory'] == group][0] + root_data = [x for x in data if x['territory'] == root_node][0] + for key in group_data.keys(): + if key not in ['indent', 'territory', 'bold']: + root_data[key] += group_data[key] + + return columns, data, None, None, None, 1 + +def update_groups(node, data, root_node, territory_dict): + ''' Adds values of child territories to parent node except root ''' + parent_node = territory_dict[node]['parent'] + if parent_node != root_node and parent_node: + node_data = [x for x in data if x['territory'] == node][0] + parent_data = [x for x in data if x['territory'] == parent_node][0] + for key in parent_data.keys(): + if key not in ['indent', 'territory', 'bold']: + parent_data[key] += node_data[key] + return update_groups(parent_node, data, root_node, territory_dict) + else: + return data + +def get_customer_stats(filters, tree_view=False): + ''' Calculates number of new and repeated customers ''' + company_condition = '' + if filters.get('company'): + company_condition = ' and company=%(company)s' + + customers = [] + customers_in = {} + new_customers_in = {} + repeat_customers_in = {} + + for si in frappe.db.sql('''select territory, posting_date, customer, base_grand_total from `tabSales Invoice` + where docstatus=1 and posting_date <= %(to_date)s and posting_date >= %(from_date)s + {company_condition} order by posting_date'''.format(company_condition=company_condition), + filters, as_dict=1): + if tree_view: + key = si.territory + else: + key = si.posting_date.strftime('%Y-%m') + if not si.customer in customers: + new_customers_in.setdefault(key, [0, 0.0]) + new_customers_in[key][0] += 1 + new_customers_in[key][1] += si.base_grand_total + customers.append(si.customer) + else: + repeat_customers_in.setdefault(key, [0, 0.0]) + repeat_customers_in[key][0] += 1 + repeat_customers_in[key][1] += si.base_grand_total + customers_in.update({ + key: { + 'new': new_customers_in[key] if new_customers_in.get(key) else [0, 0.0], + 'repeat': repeat_customers_in[key] if repeat_customers_in.get(key) else [0, 0.0], + } + }) + return customers_in diff --git a/erpnext/setup/doctype/territory/territory.py b/erpnext/setup/doctype/territory/territory.py index 095bd1c179..4f2ab70b2c 100644 --- a/erpnext/setup/doctype/territory/territory.py +++ b/erpnext/setup/doctype/territory/territory.py @@ -3,8 +3,6 @@ from __future__ import unicode_literals import frappe - - from frappe.utils import flt from frappe import _ @@ -14,6 +12,9 @@ class Territory(NestedSet): nsm_parent_field = 'parent_territory' def validate(self): + if frappe.db.sql("SELECT COUNT(name) FROM `tabTerritory` WHERE parent IS NULL")[0][0] > 1: + frappe.throw('Only one Root Territory is allowed, please select a Parent Territory!') + for d in self.get('targets') or []: if not flt(d.target_qty) and not flt(d.target_amount): frappe.throw(_("Either target qty or target amount is mandatory")) From b59f2780ebf974779b4b0236e67e3f1bbee7781a Mon Sep 17 00:00:00 2001 From: Abhishek Balam Date: Sat, 9 May 2020 22:09:02 +0530 Subject: [PATCH 52/96] fix: renamed view types, added default --- .../customer_acquisition_and_loyalty.js | 7 ++++--- .../customer_acquisition_and_loyalty.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js index 654614b4bb..c24d2e2bdd 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js @@ -4,11 +4,12 @@ frappe.query_reports["Customer Acquisition and Loyalty"] = { "filters": [ { - "fieldname":"view_type", + "fieldname": "view_type", "label": __("View Type"), "fieldtype": "Select", - "default": "Time Series", - "options": ["Time Series", "Territory Tree"] + "options": ["Monthly", "Territory Wise"], + "default": "Monthly", + "reqd": 1 }, { "fieldname":"company", diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py index f2033f1fff..d8cc763ed9 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py @@ -52,7 +52,7 @@ def execute(filters=None): 'width': 150 } ] - if filters.get('view_type') == 'Territory Tree': + if filters.get('view_type') == 'Territory Wise': return get_data_by_territory(filters, common_columns) else: return get_data_by_time(filters, common_columns) From 7cbc902d90a3f43b985c9c12abbf0ab8caf82dc2 Mon Sep 17 00:00:00 2001 From: Abhishek Balam Date: Sat, 9 May 2020 22:35:28 +0530 Subject: [PATCH 53/96] removed validation for root node in territory, codacy recommended changed --- .../customer_acquisition_and_loyalty.js | 2 +- .../customer_acquisition_and_loyalty.py | 4 ++-- erpnext/setup/doctype/territory/territory.py | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js index c24d2e2bdd..d93ffb7266 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.js @@ -39,6 +39,6 @@ frappe.query_reports["Customer Acquisition and Loyalty"] = { if (data && data.bold) { value = value.bold(); } - return value + return value; } } \ No newline at end of file diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py index d8cc763ed9..0121a8267f 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py @@ -162,7 +162,7 @@ def get_data_by_territory(filters, common_columns): return columns, data, None, None, None, 1 def update_groups(node, data, root_node, territory_dict): - ''' Adds values of child territories to parent node except root ''' + ''' Adds values of child territories to parent node except root. ''' parent_node = territory_dict[node]['parent'] if parent_node != root_node and parent_node: node_data = [x for x in data if x['territory'] == node][0] @@ -175,7 +175,7 @@ def update_groups(node, data, root_node, territory_dict): return data def get_customer_stats(filters, tree_view=False): - ''' Calculates number of new and repeated customers ''' + ''' Calculates number of new and repeated customers. ''' company_condition = '' if filters.get('company'): company_condition = ' and company=%(company)s' diff --git a/erpnext/setup/doctype/territory/territory.py b/erpnext/setup/doctype/territory/territory.py index 4f2ab70b2c..808b5386ab 100644 --- a/erpnext/setup/doctype/territory/territory.py +++ b/erpnext/setup/doctype/territory/territory.py @@ -12,8 +12,6 @@ class Territory(NestedSet): nsm_parent_field = 'parent_territory' def validate(self): - if frappe.db.sql("SELECT COUNT(name) FROM `tabTerritory` WHERE parent IS NULL")[0][0] > 1: - frappe.throw('Only one Root Territory is allowed, please select a Parent Territory!') for d in self.get('targets') or []: if not flt(d.target_qty) and not flt(d.target_amount): From 5ec5584319e18341e839d354bd251b9b7a382ca7 Mon Sep 17 00:00:00 2001 From: Abhishek Balam Date: Sun, 10 May 2020 00:24:43 +0530 Subject: [PATCH 54/96] In treeview, bold only for root territory, looks cleaner --- .../customer_acquisition_and_loyalty.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py index 0121a8267f..b7bb021056 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py @@ -143,7 +143,7 @@ def get_data_by_territory(filters, common_columns): 'new_customer_revenue': new[1], 'repeat_customer_revenue': repeat[1], 'total_revenue': new[1] + repeat[1], - 'bold': 0 if condition else 1 + 'bold': 0 if indent else 1 } data.append(temp) node_list = [x for x in territory_dict.keys() if territory_dict[x]['is_group'] == 0] From 500dff63e764d7a679747546f15f1ee671a2fdc8 Mon Sep 17 00:00:00 2001 From: Abhishek Balam Date: Sun, 10 May 2020 14:53:59 +0530 Subject: [PATCH 55/96] fix: adjusted width of colums to see full column names, also fixes #21556 --- .../customer_acquisition_and_loyalty.py | 12 ++++---- .../territory_wise_sales.py | 30 +++++++++++-------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py index b7bb021056..6e3f397fd6 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py @@ -14,21 +14,21 @@ def execute(filters=None): 'fieldname': 'new_customers', 'fieldtype': 'Int', 'default': 0, - 'width': 100 + 'width': 150 }, { 'label': _('Repeat Customers'), 'fieldname': 'repeat_customers', 'fieldtype': 'Int', 'default': 0, - 'width': 100 + 'width': 150 }, { 'label': _('Total'), 'fieldname': 'total', 'fieldtype': 'Int', 'default': 0, - 'width': 100 + 'width': 150 }, { 'label': _('New Customer Revenue'), @@ -52,10 +52,10 @@ def execute(filters=None): 'width': 150 } ] - if filters.get('view_type') == 'Territory Wise': - return get_data_by_territory(filters, common_columns) - else: + if filters.get('view_type') == 'Monthly': return get_data_by_time(filters, common_columns) + else: + return get_data_by_territory(filters, common_columns) def get_data_by_time(filters, common_columns): # key yyyy-mm diff --git a/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py b/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py index f2db478686..e883500170 100644 --- a/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py +++ b/erpnext/selling/report/territory_wise_sales/territory_wise_sales.py @@ -20,31 +20,36 @@ def get_columns(): "label": _("Territory"), "fieldname": "territory", "fieldtype": "Link", - "options": "Territory" + "options": "Territory", + "width": 150 }, { "label": _("Opportunity Amount"), "fieldname": "opportunity_amount", "fieldtype": "Currency", - "options": currency + "options": currency, + "width": 150 }, { "label": _("Quotation Amount"), "fieldname": "quotation_amount", "fieldtype": "Currency", - "options": currency + "options": currency, + "width": 150 }, { "label": _("Order Amount"), "fieldname": "order_amount", "fieldtype": "Currency", - "options": currency + "options": currency, + "width": 150 }, { "label": _("Billing Amount"), "fieldname": "billing_amount", "fieldtype": "Currency", - "options": currency + "options": currency, + "width": 150 } ] @@ -62,8 +67,7 @@ def get_data(filters=None): territory_opportunities = list(filter(lambda x: x.territory == territory.name, opportunities)) t_opportunity_names = [] if territory_opportunities: - t_opportunity_names = [t.name for t in territory_opportunities] - + t_opportunity_names = [t.name for t in territory_opportunities] territory_quotations = [] if t_opportunity_names and quotations: territory_quotations = list(filter(lambda x: x.opportunity in t_opportunity_names, quotations)) @@ -76,7 +80,7 @@ def get_data(filters=None): list(filter(lambda x: x.quotation in t_quotation_names, sales_orders)) t_order_names = [] if territory_orders: - t_order_names = [t.name for t in territory_orders] + t_order_names = [t.name for t in territory_orders] territory_invoices = list(filter(lambda x: x.sales_order in t_order_names, sales_invoices)) if t_order_names and sales_invoices else [] @@ -96,12 +100,12 @@ def get_opportunities(filters): if filters.get('transaction_date'): conditions = " WHERE transaction_date between {0} and {1}".format( - frappe.db.escape(filters['transaction_date'][0]), + frappe.db.escape(filters['transaction_date'][0]), frappe.db.escape(filters['transaction_date'][1])) - + if filters.company: if conditions: - conditions += " AND" + conditions += " AND" else: conditions += " WHERE" conditions += " company = %(company)s" @@ -115,7 +119,7 @@ def get_opportunities(filters): def get_quotations(opportunities): if not opportunities: return [] - + opportunity_names = [o.name for o in opportunities] return frappe.db.sql(""" @@ -155,5 +159,5 @@ def _get_total(doclist, amount_field="base_grand_total"): total = 0 for doc in doclist: total += doc.get(amount_field, 0) - + return total From 0defefda92ee28158ceb54d8cd1b639e76df94fc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 May 2020 12:14:46 +0530 Subject: [PATCH 56/96] feat: Standard accounts dashboard --- erpnext/accounts/accounts | 0 .../account_balance_timeline.py | 2 +- erpnext/accounts/dashboard_fixtures.py | 153 ++++++++++++++++++ .../accounts_receivable.py | 2 +- .../setup_wizard/data/dashboard_charts.py | 97 ----------- 5 files changed, 155 insertions(+), 99 deletions(-) create mode 100644 erpnext/accounts/accounts create mode 100644 erpnext/accounts/dashboard_fixtures.py diff --git a/erpnext/accounts/accounts b/erpnext/accounts/accounts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py index c3e2f7db12..5decccb486 100644 --- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py +++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py @@ -6,7 +6,7 @@ import frappe, json from frappe import _ from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate, get_link_to_form from erpnext.accounts.report.general_ledger.general_ledger import execute -from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan +from frappe.utils.dashboard import cache_source, get_from_date_from_timespan from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending from frappe.utils.nestedset import get_descendants_of diff --git a/erpnext/accounts/dashboard_fixtures.py b/erpnext/accounts/dashboard_fixtures.py new file mode 100644 index 0000000000..746eb64943 --- /dev/null +++ b/erpnext/accounts/dashboard_fixtures.py @@ -0,0 +1,153 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +import json +from frappe.utils import nowdate, add_months + +def get_company_for_dashboards(): + company = frappe.defaults.get_defaults().company + if company: + return company + else: + company_list = frappe.get_list("Company") + if company_list: + return company_list[0].name + return None + +def get_data(): + return frappe._dict({ + "dashboards": get_dashboards(), + "charts": get_charts() + }) + +def get_dashboards(): + + return [{ + "name": "Accounts Dashboard", + "dashboard_name": "Accounts", + "doctype": "Dashboard", + "charts": [ + { "chart": "Profit and Loss" , "width": "Full"}, + { "chart": "Incoming Bills"}, + { "chart": "Outgoing Bills"}, + { "chart": "Accounts Receivable Ageing"}, + { "chart": "Accounts Payable Ageing"}, + { "chart": "Bank Balance", "width": "Full"}, + ] + }] + +def get_charts(): + company = frappe.get_doc("Company", get_company_for_dashboards()) + bank_account = company.default_bank_account or get_account("Bank", company.name) + + return [ + { + "doctype": "Dashboard Charts", + "name": "Profit and Loss", + "owner": "Administrator", + "report_name": "Profit and Loss Statement", + "filters_json": json.dumps({ + "company": company.name, + "filter_based_on": "Date Range", + "period_start_date": add_months(nowdate(), -4), + "period_end_date": nowdate(), + "periodicity": "Monthly", + "include_default_book_entries": 1 + }), + "type": "Bar", + 'timeseries': 0, + "chart_type": "Report", + "chart_name": "Profit and Loss", + "is_custom": 1 + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Monthly", + "name": "Incoming Bills", + "chart_name": "Incoming Bills (Purchase Invoice)", + "timespan": "Last Year", + "color": "#a83333", + "value_based_on": "base_grand_total", + "filters_json": json.dumps({}), + "chart_type": "Sum", + "timeseries": 1, + "based_on": "posting_date", + "owner": "Administrator", + "document_type": "Purchase Invoice", + "type": "Bar", + "width": "Half" + }, + { + "doctype": "Dashboard Chart", + "name": "Outgoing Bills", + "time_interval": "Monthly", + "chart_name": "Outgoing Bills (Sales Invoice)", + "timespan": "Last Year", + "color": "#7b933d", + "value_based_on": "base_grand_total", + "filters_json": json.dumps({}), + "chart_type": "Sum", + "timeseries": 1, + "based_on": "posting_date", + "owner": "Administrator", + "document_type": "Sales Invoice", + "type": "Bar", + "width": "Half" + }, + { + "doctype": "Dashboard Charts", + "name": "Accounts Receivable Ageing", + "owner": "Administrator", + "report_name": "Accounts Receivable", + "filters_json": json.dumps({ + "company": company.name, + "report_date": nowdate(), + "ageing_based_on": "Due Date", + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120 + }), + "type": "Donut", + 'timeseries': 0, + "chart_type": "Report", + "chart_name": "Accounts Receivable Ageing", + "is_custom": 1 + }, + { + "doctype": "Dashboard Charts", + "name": "Accounts Payable Ageing", + "owner": "Administrator", + "report_name": "Accounts Payable", + "filters_json": json.dumps({ + "company": company.name, + "report_date": nowdate(), + "ageing_based_on": "Due Date", + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120 + }), + "type": "Donut", + 'timeseries': 0, + "chart_type": "Report", + "chart_name": "Accounts Payable Ageing", + "is_custom": 1 + }, + { + "doctype": "Dashboard Charts", + "name": "Bank Balance", + "time_interval": "Quarterly", + "chart_name": "Bank Balance", + "timespan": "Last Year", + "filters_json": json.dumps({"company": company.name, "account": bank_account}), + "source": "Account Balance Timeline", + "chart_type": "Custom", + "timeseries": 1, + "owner": "Administrator", + "type": "Line", + "width": "Half" + }, + + ] \ No newline at end of file diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index e9c286fcf0..a0a1b9783a 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -546,7 +546,7 @@ class ReceivablePayableReport(object): self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4 = 30, 60, 90, 120 for i, days in enumerate([self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4]): - if row.age <= days: + if cint(row.age) <= cint(days): index = i break diff --git a/erpnext/setup/setup_wizard/data/dashboard_charts.py b/erpnext/setup/setup_wizard/data/dashboard_charts.py index b182dfc103..2828307204 100644 --- a/erpnext/setup/setup_wizard/data/dashboard_charts.py +++ b/erpnext/setup/setup_wizard/data/dashboard_charts.py @@ -3,21 +3,8 @@ from frappe import _ import frappe import json -def get_company_for_dashboards(): - company = frappe.defaults.get_defaults().company - if company: - return company - else: - company_list = frappe.get_list("Company") - if company_list: - return company_list[0].name - return None def get_default_dashboards(): - company = frappe.get_doc("Company", get_company_for_dashboards()) - income_account = company.default_income_account or get_account("Income Account", company.name) - expense_account = company.default_expense_account or get_account("Expense Account", company.name) - bank_account = company.default_bank_account or get_account("Bank", company.name) return { "Dashboards": [ @@ -25,90 +12,11 @@ def get_default_dashboards(): "doctype": "Dashboard", "dashboard_name": "Accounts", "charts": [ - { "chart": "Outgoing Bills (Sales Invoice)" }, - { "chart": "Incoming Bills (Purchase Invoice)" }, - { "chart": "Bank Balance" }, - { "chart": "Income" }, - { "chart": "Expenses" }, { "chart": "Patient Appointments" } ] } ], "Charts": [ - { - "doctype": "Dashboard Chart", - "time_interval": "Quarterly", - "chart_name": "Income", - "timespan": "Last Year", - "color": None, - "filters_json": json.dumps({"company": company.name, "account": income_account}), - "source": "Account Balance Timeline", - "chart_type": "Custom", - "timeseries": 1, - "owner": "Administrator", - "type": "Line", - "width": "Half" - }, - { - "doctype": "Dashboard Chart", - "time_interval": "Quarterly", - "chart_name": "Expenses", - "timespan": "Last Year", - "color": None, - "filters_json": json.dumps({"company": company.name, "account": expense_account}), - "source": "Account Balance Timeline", - "chart_type": "Custom", - "timeseries": 1, - "owner": "Administrator", - "type": "Line", - "width": "Half" - }, - { - "doctype": "Dashboard Chart", - "time_interval": "Quarterly", - "chart_name": "Bank Balance", - "timespan": "Last Year", - "color": "#ffb868", - "filters_json": json.dumps({"company": company.name, "account": bank_account}), - "source": "Account Balance Timeline", - "chart_type": "Custom", - "timeseries": 1, - "owner": "Administrator", - "type": "Line", - "width": "Half" - }, - { - "doctype": "Dashboard Chart", - "time_interval": "Monthly", - "chart_name": "Incoming Bills (Purchase Invoice)", - "timespan": "Last Year", - "color": "#a83333", - "value_based_on": "base_grand_total", - "filters_json": json.dumps({}), - "chart_type": "Sum", - "timeseries": 1, - "based_on": "posting_date", - "owner": "Administrator", - "document_type": "Purchase Invoice", - "type": "Bar", - "width": "Half" - }, - { - "doctype": "Dashboard Chart", - "time_interval": "Monthly", - "chart_name": "Outgoing Bills (Sales Invoice)", - "timespan": "Last Year", - "color": "#7b933d", - "value_based_on": "base_grand_total", - "filters_json": json.dumps({}), - "chart_type": "Sum", - "timeseries": 1, - "based_on": "posting_date", - "owner": "Administrator", - "document_type": "Sales Invoice", - "type": "Bar", - "width": "Half" - }, { "doctype": "Dashboard Chart", "time_interval": "Daily", @@ -126,8 +34,3 @@ def get_default_dashboards(): } ] } - -def get_account(account_type, company): - accounts = frappe.get_list("Account", filters={"account_type": account_type, "company": company}) - if accounts: - return accounts[0].name From 87776c335beb693c471608fa318a4997171f2dbd Mon Sep 17 00:00:00 2001 From: Abhishek Balam Date: Mon, 11 May 2020 15:18:40 +0530 Subject: [PATCH 57/96] code improvements --- .../customer_acquisition_and_loyalty.py | 45 ++++++++----------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py index 6e3f397fd6..38fbd60008 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py @@ -14,42 +14,42 @@ def execute(filters=None): 'fieldname': 'new_customers', 'fieldtype': 'Int', 'default': 0, - 'width': 150 + 'width': 125 }, { 'label': _('Repeat Customers'), 'fieldname': 'repeat_customers', 'fieldtype': 'Int', 'default': 0, - 'width': 150 + 'width': 125 }, { 'label': _('Total'), 'fieldname': 'total', 'fieldtype': 'Int', 'default': 0, - 'width': 150 + 'width': 100 }, { 'label': _('New Customer Revenue'), 'fieldname': 'new_customer_revenue', 'fieldtype': 'Currency', 'default': 0.0, - 'width': 150 + 'width': 175 }, { 'label': _('Repeat Customer Revenue'), 'fieldname': 'repeat_customer_revenue', 'fieldtype': 'Currency', 'default': 0.0, - 'width': 150 + 'width': 175 }, { 'label': _('Total Revenue'), 'fieldname': 'total_revenue', 'fieldtype': 'Currency', 'default': 0.0, - 'width': 150 + 'width': 175 } ] if filters.get('view_type') == 'Monthly': @@ -148,13 +148,13 @@ def get_data_by_territory(filters, common_columns): data.append(temp) node_list = [x for x in territory_dict.keys() if territory_dict[x]['is_group'] == 0] root_node = [x for x in territory_dict.keys() if territory_dict[x]['parent'] is None][0] + root_data = [x for x in data if x['territory'] == root_node][0] for node in node_list: data = update_groups(node, data, root_node, territory_dict) for group in [x for x in territory_dict.keys() if territory_dict[x]['parent'] == root_node]: group_data = [x for x in data if x['territory'] == group][0] - root_data = [x for x in data if x['territory'] == root_node][0] for key in group_data.keys(): if key not in ['indent', 'territory', 'bold']: root_data[key] += group_data[key] @@ -162,7 +162,7 @@ def get_data_by_territory(filters, common_columns): return columns, data, None, None, None, 1 def update_groups(node, data, root_node, territory_dict): - ''' Adds values of child territories to parent node except root. ''' + """ Adds values of child territories to parent node except root. """ parent_node = territory_dict[node]['parent'] if parent_node != root_node and parent_node: node_data = [x for x in data if x['territory'] == node][0] @@ -175,37 +175,28 @@ def update_groups(node, data, root_node, territory_dict): return data def get_customer_stats(filters, tree_view=False): - ''' Calculates number of new and repeated customers. ''' + """ Calculates number of new and repeated customers. """ company_condition = '' if filters.get('company'): company_condition = ' and company=%(company)s' customers = [] customers_in = {} - new_customers_in = {} - repeat_customers_in = {} for si in frappe.db.sql('''select territory, posting_date, customer, base_grand_total from `tabSales Invoice` where docstatus=1 and posting_date <= %(to_date)s and posting_date >= %(from_date)s {company_condition} order by posting_date'''.format(company_condition=company_condition), filters, as_dict=1): - if tree_view: - key = si.territory - else: - key = si.posting_date.strftime('%Y-%m') + + key = si.territory if tree_view else si.posting_date.strftime('%Y-%m') + customers_in.setdefault(key, {'new': [0, 0.0], 'repeat': [0, 0.0]}) + if not si.customer in customers: - new_customers_in.setdefault(key, [0, 0.0]) - new_customers_in[key][0] += 1 - new_customers_in[key][1] += si.base_grand_total + customers_in[key]['new'][0] += 1 + customers_in[key]['new'][1] += si.base_grand_total customers.append(si.customer) else: - repeat_customers_in.setdefault(key, [0, 0.0]) - repeat_customers_in[key][0] += 1 - repeat_customers_in[key][1] += si.base_grand_total - customers_in.update({ - key: { - 'new': new_customers_in[key] if new_customers_in.get(key) else [0, 0.0], - 'repeat': repeat_customers_in[key] if repeat_customers_in.get(key) else [0, 0.0], - } - }) + customers_in[key]['repeat'][0] += 1 + customers_in[key]['repeat'][1] += si.base_grand_total + return customers_in From 734bfcdfc2878ea9e69d90f83a02a30912cecb26 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 12 May 2020 14:11:33 +0530 Subject: [PATCH 58/96] fix: purchase inv shows overdue for fraction of outstanding --- .../purchase_invoice/purchase_invoice.py | 34 +++++++++++++++++++ erpnext/controllers/status_updater.py | 11 ------ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 3aa24df16d..5b16eb4640 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1021,6 +1021,40 @@ class PurchaseInvoice(BuyingController): # calculate totals again after applying TDS self.calculate_taxes_and_totals() + + def set_status(self, update=False, status=None, update_modified=True): + if self.is_new(): + if self.get('amended_from'): + self.status = 'Draft' + return + + precision = self.precision("outstanding_amount") + outstanding_amount = flt(self.outstanding_amount, precision) + due_date = getdate(self.due_date) + nowdate = getdate() + + if not status: + if self.docstatus == 2: + status = "Cancelled" + elif self.docstatus == 1: + elif outstanding_amount > 0 and due_date < nowdate: + self.status = "Overdue" + elif outstanding_amount > 0 and due_date >= nowdate: + self.status = "Unpaid" + #Check if outstanding amount is 0 due to debit note issued against invoice + elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): + self.status = "Debit Note Issued" + elif self.is_return == 1: + self.status = "Return" + elif outstanding_amount<=0: + self.status = "Paid" + else: + self.status = "Submitted" + else: + self.status = "Draft" + + if update: + self.db_set('status', self.status, update_modified = update_modified) def get_list_context(context=None): from erpnext.controllers.website_list_for_contact import get_list_context diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index de76e45cd1..b465a106f0 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -69,17 +69,6 @@ status_map = { ["Cancelled", "eval:self.docstatus==2"], ["Closed", "eval:self.status=='Closed'"], ], - "Purchase Invoice": [ - ["Draft", None], - ["Submitted", "eval:self.docstatus==1"], - ["Paid", "eval:self.outstanding_amount==0 and self.docstatus==1"], - ["Return", "eval:self.is_return==1 and self.docstatus==1"], - ["Debit Note Issued", - "eval:self.outstanding_amount <= 0 and self.docstatus==1 and self.is_return==0 and get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1})"], - ["Unpaid", "eval:self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.docstatus==1"], - ["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"], - ["Cancelled", "eval:self.docstatus==2"], - ], "Material Request": [ ["Draft", None], ["Stopped", "eval:self.status == 'Stopped'"], From 411b12590648e602565829d6e5f26b57ce56b62c Mon Sep 17 00:00:00 2001 From: Abhishek Balam Date: Tue, 12 May 2020 15:25:35 +0530 Subject: [PATCH 59/96] new parent updating logic, made requested changes --- .../customer_acquisition_and_loyalty.py | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py index 38fbd60008..88bd9c135d 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py @@ -61,13 +61,13 @@ def get_data_by_time(filters, common_columns): # key yyyy-mm columns = [ { - 'label': 'Year', + 'label': _('Year'), 'fieldname': 'year', 'fieldtype': 'Data', 'width': 100 }, { - 'label': 'Month', + 'label': _('Month'), 'fieldname': 'month', 'fieldtype': 'Data', 'width': 100 @@ -136,6 +136,7 @@ def get_data_by_territory(filters, common_columns): repeat = customers_in[name]['repeat'] if condition else [0, 0.0] temp = { 'territory': name, + 'parent_territory': territory_dict[name]['parent'], 'indent': indent, 'new_customers': new[0], 'repeat_customers': repeat[0], @@ -146,34 +147,18 @@ def get_data_by_territory(filters, common_columns): 'bold': 0 if indent else 1 } data.append(temp) - node_list = [x for x in territory_dict.keys() if territory_dict[x]['is_group'] == 0] - root_node = [x for x in territory_dict.keys() if territory_dict[x]['parent'] is None][0] - root_data = [x for x in data if x['territory'] == root_node][0] - for node in node_list: - data = update_groups(node, data, root_node, territory_dict) + loop_data = sorted(data, key=lambda k: k['indent'], reverse=True) - for group in [x for x in territory_dict.keys() if territory_dict[x]['parent'] == root_node]: - group_data = [x for x in data if x['territory'] == group][0] - for key in group_data.keys(): - if key not in ['indent', 'territory', 'bold']: - root_data[key] += group_data[key] + for ld in loop_data: + if ld['parent_territory']: + parent_data = [x for x in data if x['territory'] == ld['parent_territory']][0] + for key in parent_data.keys(): + if key not in ['indent', 'territory', 'parent_territory', 'bold']: + parent_data[key] += ld[key] return columns, data, None, None, None, 1 -def update_groups(node, data, root_node, territory_dict): - """ Adds values of child territories to parent node except root. """ - parent_node = territory_dict[node]['parent'] - if parent_node != root_node and parent_node: - node_data = [x for x in data if x['territory'] == node][0] - parent_data = [x for x in data if x['territory'] == parent_node][0] - for key in parent_data.keys(): - if key not in ['indent', 'territory', 'bold']: - parent_data[key] += node_data[key] - return update_groups(parent_node, data, root_node, territory_dict) - else: - return data - def get_customer_stats(filters, tree_view=False): """ Calculates number of new and repeated customers. """ company_condition = '' From cdeb897fff6613bc0dd103638e0f7fdb3a4f82bd Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 12 May 2020 17:23:15 +0530 Subject: [PATCH 60/96] fix: Account shortcut in desk page --- .../desk_page/accounting/accounting.json | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json index 0d6aca65b1..648ebe81a1 100644 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ b/erpnext/accounts/desk_page/accounting/accounting.json @@ -45,11 +45,6 @@ "label": "Bank Statement", "links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]" }, - { - "hidden": 0, - "links": "[\n {\n \"description\": \"Match non-linked Invoices and Payments.\",\n \"label\": \"Match Payments with Invoices\",\n \"name\": \"Payment Reconciliation\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Update bank payment dates with journals.\",\n \"label\": \"Update Bank Clearance Dates\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Invoice Discounting\",\n \"name\": \"Invoice Discounting\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Clearance Summary\",\n \"name\": \"Bank Clearance Summary\",\n \"type\": \"report\"\n },\n {\n \"label\": \"Bank Guarantee\",\n \"name\": \"Bank Guarantee\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup cheque dimensions for printing\",\n \"label\": \"Cheque Print Template\",\n \"name\": \"Cheque Print Template\",\n \"type\": \"doctype\"\n }\n]", - "title": "Banking and Payments" - }, { "hidden": 0, "label": "Subscription Management", @@ -99,11 +94,10 @@ "docstatus": 0, "doctype": "Desk Page", "extends_another_page": 0, - "icon": "", "idx": 0, "is_standard": 1, "label": "Accounting", - "modified": "2020-04-29 12:17:34.844397", + "modified": "2020-05-12 17:20:27.573449", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", @@ -112,10 +106,20 @@ "pin_to_top": 0, "shortcuts": [ { - "label": "Account", + "label": "Chart Of Accounts", "link_to": "Account", "type": "DocType" }, + { + "label": "Sales Invoice", + "link_to": "Sales Invoice", + "type": "DocType" + }, + { + "label": "Purchase Invoice", + "link_to": "Purchase Invoice", + "type": "DocType" + }, { "label": "Journal Entry", "link_to": "Journal Entry", From 673e704bb5daefbc3c128bafee2f7051eb60d7cb Mon Sep 17 00:00:00 2001 From: Abhishek Balam Date: Tue, 12 May 2020 19:09:27 +0530 Subject: [PATCH 61/96] typo in error message in loan_security_pledge.py --- .../doctype/loan_security_pledge/loan_security_pledge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py index f97e5965a5..961c05c9c1 100644 --- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py +++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py @@ -38,7 +38,7 @@ class LoanSecurityPledge(Document): for pledge in self.securities: if not pledge.qty and not pledge.amount: - frappe.throw(_("Qty or Amount is mandatroy for loan security")) + frappe.throw(_("Qty or Amount is mandatory for loan security!")) if not (self.loan_application and pledge.loan_security_price): pledge.loan_security_price = get_loan_security_price(pledge.loan_security) From 4a9fd9ef6d5e34eb6f04deb0423c93f33f0b3028 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 13 May 2020 16:11:22 +0530 Subject: [PATCH 62/96] fix: error log title for failing bank transactions --- .../doctype/plaid_settings/plaid_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index b4a5bd11a0..a7062239c3 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -209,7 +209,7 @@ def new_bank_transaction(transaction): result.append(new_transaction.name) except Exception: - frappe.throw(frappe.get_traceback()) + frappe.throw(title=_('Bank transaction creation error')) return result From 1aaedd68b9d7213804a6e244d4783674cc8865d2 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Wed, 13 May 2020 17:48:11 +0530 Subject: [PATCH 63/96] Twitter and LinkedIn Auth fix --- .../linkedin_settings/linkedin_settings.py | 4 +-- .../twitter_settings/twitter_settings.json | 34 +++++++++---------- .../twitter_settings/twitter_settings.py | 14 ++++---- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py index bdde9eed37..377e061fdf 100644 --- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py +++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py @@ -15,7 +15,7 @@ class LinkedInSettings(Document): params = urlencode({ "response_type":"code", "client_id": self.consumer_key, - "redirect_uri": get_site_url(frappe.local.site) + "/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?", + "redirect_uri": "{0}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(frappe.utils.get_url()), "scope": "r_emailaddress w_organization_social r_basicprofile r_liteprofile r_organization_social rw_organization_admin w_member_social" }) @@ -30,7 +30,7 @@ class LinkedInSettings(Document): "code": code, "client_id": self.consumer_key, "client_secret": self.get_password(fieldname="consumer_secret"), - "redirect_uri": get_site_url(frappe.local.site) + "/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?", + "redirect_uri": "{0}/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?".format(frappe.utils.get_url()), } headers = { "Content-Type": "application/x-www-form-urlencoded" diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.json b/erpnext/crm/doctype/twitter_settings/twitter_settings.json index f92e7f0495..36776e5c20 100644 --- a/erpnext/crm/doctype/twitter_settings/twitter_settings.json +++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.json @@ -11,8 +11,8 @@ "consumer_key", "column_break_5", "consumer_secret", - "oauth_token", - "oauth_secret", + "access_token", + "access_token_secret", "session_status" ], "fields": [ @@ -41,20 +41,6 @@ "label": "API Secret Key", "reqd": 1 }, - { - "fieldname": "oauth_token", - "fieldtype": "Data", - "hidden": 1, - "label": "OAuth Token", - "read_only": 1 - }, - { - "fieldname": "oauth_secret", - "fieldtype": "Password", - "hidden": 1, - "label": "OAuth Token Secret", - "read_only": 1 - }, { "fieldname": "column_break_5", "fieldtype": "Column Break" @@ -72,12 +58,26 @@ "label": "Session Status", "options": "Expired\nActive", "read_only": 1 + }, + { + "fieldname": "access_token", + "fieldtype": "Data", + "hidden": 1, + "label": "Access Token", + "read_only": 1 + }, + { + "fieldname": "access_token_secret", + "fieldtype": "Data", + "hidden": 1, + "label": "Access Token Secret", + "read_only": 1 } ], "image_field": "profile_pic", "issingle": 1, "links": [], - "modified": "2020-04-21 22:06:43.726798", + "modified": "2020-05-13 17:50:47.934776", "modified_by": "Administrator", "module": "CRM", "name": "Twitter Settings", diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.py b/erpnext/crm/doctype/twitter_settings/twitter_settings.py index 7616b4c027..976a23dfc7 100644 --- a/erpnext/crm/doctype/twitter_settings/twitter_settings.py +++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.py @@ -31,13 +31,13 @@ class TwitterSettings(Document): try: auth.get_access_token(oauth_verifier) - api = self.get_api() + api = self.get_api(auth.access_token, auth.access_token_secret) user = api.me() profile_pic = (user._json["profile_image_url"]).replace("_normal","") frappe.db.set_value(self.doctype, self.name, { - "oauth_token" : auth.access_token, - "oauth_secret" : auth.access_token_secret, + "access_token" : auth.access_token, + "access_token_secret" : auth.access_token_secret, "account_name" : user._json["screen_name"], "profile_pic" : profile_pic, "session_status" : "Active" @@ -49,11 +49,11 @@ class TwitterSettings(Document): frappe.msgprint(_("Error! Failed to get access token.")) frappe.throw(_('Invalid Consumer Key or Consumer Secret Key')) - def get_api(self): + def get_api(self, access_token, access_token_secret): # authentication of consumer key and secret auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret")) # authentication of access token and secret - auth.set_access_token(self.oauth_token, self.get_password(fieldname="oauth_secret")) + auth.set_access_token(access_token, access_token_secret) return tweepy.API(auth) @@ -67,13 +67,13 @@ class TwitterSettings(Document): def upload_image(self, media): media = get_file_path(media) - api = self.get_api() + api = self.get_api(self.access_token, self.access_token_secret) media = api.media_upload(media) return media.media_id def send_tweet(self, text, media_id=None): - api = self.get_api() + api = self.get_api(self.access_token, self.access_token_secret) try: if media_id: response = api.update_status(status = text, media_ids = [media_id]) From bd7e5358857b66f5025e38ab3cf82d589dec6506 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Wed, 13 May 2020 19:48:42 +0530 Subject: [PATCH 64/96] Fix: Set General Ledger 'Group By' filter as 'Group by Voucher(Consolidated)' when opened from Invoice (#21673) * fix for issue #21419 * changing group by filter to default Group by Voucher (Consolidated) --- erpnext/accounts/report/general_ledger/general_ledger.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 1188beaa0f..2aecd6b717 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -53,7 +53,7 @@ frappe.query_reports["General Ledger"] = { "label": __("Voucher No"), "fieldtype": "Data", on_change: function() { - frappe.query_report.set_filter_value('group_by', ""); + frappe.query_report.set_filter_value('group_by', "Group by Voucher (Consolidated)"); } }, { From 13096cdbfefd58f16f7e009a48343a5601f41221 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 13 May 2020 22:02:56 +0530 Subject: [PATCH 65/96] fix: invalid conditional statement --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 5b16eb4640..265969db1f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1037,7 +1037,7 @@ class PurchaseInvoice(BuyingController): if self.docstatus == 2: status = "Cancelled" elif self.docstatus == 1: - elif outstanding_amount > 0 and due_date < nowdate: + if outstanding_amount > 0 and due_date < nowdate: self.status = "Overdue" elif outstanding_amount > 0 and due_date >= nowdate: self.status = "Unpaid" From dde39c3d1aa323072de5703b8c30eabe07b3960c Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Thu, 14 May 2020 12:09:36 +0530 Subject: [PATCH 66/96] chore: Drop Python2 support (#21704) * chore: Drop Python2 support * test: Fix test redundancy by removing countries 3 countries seems ennough to test coa template feature --- .travis.yml | 17 ++--------------- erpnext/setup/doctype/company/test_company.py | 4 +--- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 213445b806..77d427e5a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ -dist: trusty - language: python +dist: trusty git: depth: 1 @@ -14,21 +13,10 @@ addons: jobs: include: - - name: "Python 2.7 Server Side Test" - python: 2.7 - script: bench --site test_site run-tests --app erpnext --coverage - - name: "Python 3.6 Server Side Test" python: 3.6 script: bench --site test_site run-tests --app erpnext --coverage - - name: "Python 2.7 Patch Test" - python: 2.7 - before_script: - - wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz - - bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz - script: bench --site test_site migrate - - name: "Python 3.6 Patch Test" python: 3.6 before_script: @@ -40,8 +28,7 @@ install: - cd ~ - nvm install 10 - - git clone https://github.com/frappe/bench --depth 1 - - pip install -e ./bench + - pip install frappe-bench - git clone https://github.com/frappe/frappe --branch $TRAVIS_BRANCH --depth 1 - bench init --skip-assets --frappe-path ~/frappe --python $(which python) frappe-bench diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py index b37cc17ba9..29f6c3731d 100644 --- a/erpnext/setup/doctype/company/test_company.py +++ b/erpnext/setup/doctype/company/test_company.py @@ -47,9 +47,7 @@ class TestCompany(unittest.TestCase): frappe.delete_doc("Company", "COA from Existing Company") def test_coa_based_on_country_template(self): - countries = ["India", "Brazil", "United Arab Emirates", "Canada", "Germany", "France", - "Guatemala", "Indonesia", "Italy", "Mexico", "Nicaragua", "Netherlands", "Singapore", - "Brazil", "Argentina", "Hungary", "Taiwan"] + countries = ["Canada", "Germany", "France"] for country in countries: templates = get_charts_for_country(country) From 10df3d5081c54d5ee70bdf69c81c3d70acb35169 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 14 May 2020 17:15:16 +0530 Subject: [PATCH 67/96] fix: Get basic and hra component from db, not from cache --- erpnext/regional/india/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 33098587c2..732780a0a3 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -251,8 +251,7 @@ def get_tax_template_for_sez(party_details, master_doctype, company, party_type) def calculate_annual_eligible_hra_exemption(doc): - basic_component = frappe.get_cached_value('Company', doc.company, "basic_component") - hra_component = frappe.get_cached_value('Company', doc.company, "hra_component") + basic_component, hra_component = frappe.db.get_value('Company', doc.company, ["basic_component", "hra_component"]) if not (basic_component and hra_component): frappe.throw(_("Please mention Basic and HRA component in Company")) annual_exemption, monthly_exemption, hra_amount = 0, 0, 0 From 200f80c3d368884000ebd8b5942e1e9ea28d0301 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 14 May 2020 17:18:29 +0530 Subject: [PATCH 68/96] fix: Added submit permission in employee other income --- .../employee_other_income.json | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/erpnext/hr/doctype/employee_other_income/employee_other_income.json b/erpnext/hr/doctype/employee_other_income/employee_other_income.json index 2dd6c10988..8abfe1e93a 100644 --- a/erpnext/hr/doctype/employee_other_income/employee_other_income.json +++ b/erpnext/hr/doctype/employee_other_income/employee_other_income.json @@ -76,25 +76,15 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-03-19 18:06:45.361830", + "modified": "2020-05-14 17:17:38.883126", "modified_by": "Administrator", "module": "HR", "name": "Employee Other Income", "owner": "Administrator", "permissions": [ { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { + "amend": 1, + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -104,9 +94,12 @@ "report": 1, "role": "HR Manager", "share": 1, + "submit": 1, "write": 1 }, { + "amend": 1, + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -116,9 +109,12 @@ "report": 1, "role": "HR User", "share": 1, + "submit": 1, "write": 1 }, { + "amend": 1, + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -128,6 +124,7 @@ "report": 1, "role": "Employee", "share": 1, + "submit": 1, "write": 1 } ], From 39cb749f955f0fe40dc2289cdbc32bcb7f9ba939 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 14 May 2020 18:25:58 +0530 Subject: [PATCH 69/96] fix: add heatmap_year parameter to get --- .../account_balance_timeline/account_balance_timeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py index 5decccb486..39bf4b053a 100644 --- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py +++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py @@ -14,7 +14,7 @@ from frappe.utils.nestedset import get_descendants_of @frappe.whitelist() @cache_source def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None, - to_date = None, timespan = None, time_interval = None): + to_date = None, timespan = None, time_interval = None, heatmap_year = None): if chart_name: chart = frappe.get_doc('Dashboard Chart', chart_name) else: From 8bbac6defc464dc6a7ad4b71675b3cefe4064225 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 14 May 2020 22:57:11 +0530 Subject: [PATCH 70/96] fix: Onboarding and Dashboard for accounts module --- erpnext/accounts/dashboard_fixtures.py | 60 ++++++++++-- .../desk_page/accounting/accounting.json | 20 ++-- .../module_onboarding/accounts/accounts.json | 51 ++++++++++ .../chart_of_accounts/chart_of_accounts.json | 20 ++++ .../configure_account_settings.json | 19 ++++ .../create_a_customer/create_a_customer.json | 19 ++++ .../create_a_product/create_a_product.json | 19 ++++ .../create_a_supplier/create_a_supplier.json | 19 ++++ .../create_your_first_purchase_invoice.json | 19 ++++ .../create_your_first_sales_invoice.json | 19 ++++ .../setup_taxes/setup_taxes.json | 19 ++++ .../budget_variance_report.py | 96 ++++++++++++++++--- 12 files changed, 347 insertions(+), 33 deletions(-) create mode 100644 erpnext/accounts/module_onboarding/accounts/accounts.json create mode 100644 erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json create mode 100644 erpnext/accounts/onboarding_step/configure_account_settings/configure_account_settings.json create mode 100644 erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json create mode 100644 erpnext/accounts/onboarding_step/create_a_product/create_a_product.json create mode 100644 erpnext/accounts/onboarding_step/create_a_supplier/create_a_supplier.json create mode 100644 erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json create mode 100644 erpnext/accounts/onboarding_step/create_your_first_sales_invoice/create_your_first_sales_invoice.json create mode 100644 erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json diff --git a/erpnext/accounts/dashboard_fixtures.py b/erpnext/accounts/dashboard_fixtures.py index 46d1d6a66a..8b9eca5b2d 100644 --- a/erpnext/accounts/dashboard_fixtures.py +++ b/erpnext/accounts/dashboard_fixtures.py @@ -4,6 +4,8 @@ import frappe import json from frappe.utils import nowdate, add_months +from frappe import _ +from erpnext.accounts.utils import get_fiscal_year def get_company_for_dashboards(): company = frappe.defaults.get_defaults().company @@ -18,11 +20,11 @@ def get_company_for_dashboards(): def get_data(): return frappe._dict({ "dashboards": get_dashboards(), - "charts": get_charts() + "charts": get_charts(), + "number_cards": get_number_cards() }) def get_dashboards(): - return [{ "name": "Accounts Dashboard", "dashboard_name": "Accounts", @@ -33,13 +35,19 @@ def get_dashboards(): { "chart": "Outgoing Bills"}, { "chart": "Accounts Receivable Ageing"}, { "chart": "Accounts Payable Ageing"}, + { "chart": "Budget Variance", "width": "Full"}, { "chart": "Bank Balance", "width": "Full"}, + ], + "cards": [ + {"card": "Total Payment Received"} ] }] def get_charts(): company = frappe.get_doc("Company", get_company_for_dashboards()) bank_account = company.default_bank_account or get_account("Bank", company.name) + fiscal_year = get_fiscal_year(date=nowdate()) + default_cost_center = company.cost_center return [ { @@ -58,14 +66,14 @@ def get_charts(): "type": "Bar", 'timeseries': 0, "chart_type": "Report", - "chart_name": "Profit and Loss", + "chart_name": _("Profit and Loss"), "is_custom": 1 }, { "doctype": "Dashboard Chart", "time_interval": "Monthly", "name": "Incoming Bills", - "chart_name": "Incoming Bills (Purchase Invoice)", + "chart_name": _("Incoming Bills (Purchase Invoice)"), "timespan": "Last Year", "color": "#a83333", "value_based_on": "base_grand_total", @@ -82,7 +90,7 @@ def get_charts(): "doctype": "Dashboard Chart", "name": "Outgoing Bills", "time_interval": "Monthly", - "chart_name": "Outgoing Bills (Sales Invoice)", + "chart_name": _("Outgoing Bills (Sales Invoice)"), "timespan": "Last Year", "color": "#7b933d", "value_based_on": "base_grand_total", @@ -112,7 +120,7 @@ def get_charts(): "type": "Donut", 'timeseries': 0, "chart_type": "Report", - "chart_name": "Accounts Receivable Ageing", + "chart_name": _("Accounts Receivable Ageing"), "is_custom": 1 }, { @@ -128,11 +136,29 @@ def get_charts(): "range2": 60, "range3": 90, "range4": 120 - }), + }), "type": "Donut", 'timeseries': 0, "chart_type": "Report", - "chart_name": "Accounts Payable Ageing", + "chart_name": _("Accounts Payable Ageing"), + "is_custom": 1 + }, + { + "doctype": "Dashboard Charts", + "name": "Budget Variance", + "owner": "Administrator", + "report_name": "Budget Variance Report", + "filters_json": json.dumps({ + "company": company.name, + "from_fiscal_year": fiscal_year[0], + "to_fiscal_year": fiscal_year[0], + "period": "Monthly", + "budget_against": "Cost Center" + }), + "type": "Bar", + "timeseries": 0, + "chart_type": "Report", + "chart_name": _("Budget Variance"), "is_custom": 1 }, { @@ -149,5 +175,21 @@ def get_charts(): "type": "Line", "width": "Half" }, - + ] + +def get_number_cards(): + return [ + { + "doctype": "Number Card", + "document_type": "Payment Entry", + "name": "Total Payment Received", + "filters_json": json.dumps([]), + "label": _("Total Payment Received"), + "function": "Sum", + "aggregate_function_based_on": "base_received_amount", + "is_public": 1, + "is_custom": 1, + "show_percentage_stats": 1, + "stats_time_interval": "Daily" + } ] diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json index 648ebe81a1..682eb8fced 100644 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ b/erpnext/accounts/desk_page/accounting/accounting.json @@ -82,12 +82,7 @@ } ], "category": "Modules", - "charts": [ - { - "chart_name": "Bank Balance", - "label": "Bank Balance" - } - ], + "charts": [], "creation": "2020-03-02 15:41:59.515192", "developer_mode_only": 0, "disable_user_customization": 0, @@ -97,10 +92,11 @@ "idx": 0, "is_standard": 1, "label": "Accounting", - "modified": "2020-05-12 17:20:27.573449", + "modified": "2020-05-14 22:28:25.262409", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", + "onboarding": "Accounts", "owner": "Administrator", "pin_to_bottom": 0, "pin_to_top": 0, @@ -120,6 +116,11 @@ "link_to": "Purchase Invoice", "type": "DocType" }, + { + "label": "Accounts Dashboard", + "link_to": "Accounts Dashboard", + "type": "Dashboard" + }, { "label": "Journal Entry", "link_to": "Journal Entry", @@ -140,11 +141,6 @@ "link_to": "General Ledger", "type": "Report" }, - { - "label": "Profit and Loss Statement", - "link_to": "Profit and Loss Statement", - "type": "Report" - }, { "label": "Trial Balance", "link_to": "Trial Balance", diff --git a/erpnext/accounts/module_onboarding/accounts/accounts.json b/erpnext/accounts/module_onboarding/accounts/accounts.json new file mode 100644 index 0000000000..12da440028 --- /dev/null +++ b/erpnext/accounts/module_onboarding/accounts/accounts.json @@ -0,0 +1,51 @@ +{ + "allow_roles": [ + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + } + ], + "creation": "2020-05-13 19:03:32.564049", + "docstatus": 0, + "doctype": "Module Onboarding", + "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts", + "idx": 0, + "is_complete": 0, + "modified": "2020-05-14 22:11:06.475938", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Accounts", + "owner": "Administrator", + "steps": [ + { + "step": "Chart Of Accounts" + }, + { + "step": "Setup Taxes" + }, + { + "step": "Create a Product" + }, + { + "step": "Create a Supplier" + }, + { + "step": "Create Your First Purchase Invoice" + }, + { + "step": "Create a Customer" + }, + { + "step": "Create Your First Sales Invoice" + }, + { + "step": "Configure Account Settings" + } + ], + "subtitle": "Accounts, invoices and taxation.", + "success_message": "The Accounts module is now set up!", + "title": "Let's Setup Your Accounts and Taxes.", + "user_can_dismiss": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json b/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json new file mode 100644 index 0000000000..cbd022bfdb --- /dev/null +++ b/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json @@ -0,0 +1,20 @@ +{ + "action": "Go to Page", + "creation": "2020-05-13 19:58:20.928127", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-14 17:40:28.410447", + "modified_by": "Administrator", + "name": "Chart Of Accounts", + "owner": "Administrator", + "path": "Tree/Account", + "reference_document": "Account", + "show_full_form": 0, + "title": "Review Chart Of Accounts", + "validate_action": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/onboarding_step/configure_account_settings/configure_account_settings.json b/erpnext/accounts/onboarding_step/configure_account_settings/configure_account_settings.json new file mode 100644 index 0000000000..c8be357de0 --- /dev/null +++ b/erpnext/accounts/onboarding_step/configure_account_settings/configure_account_settings.json @@ -0,0 +1,19 @@ +{ + "action": "Create Entry", + "creation": "2020-05-14 17:53:00.876946", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 1, + "is_skipped": 0, + "modified": "2020-05-14 18:06:25.212923", + "modified_by": "Administrator", + "name": "Configure Account Settings", + "owner": "Administrator", + "reference_document": "Accounts Settings", + "show_full_form": 1, + "title": "Configure Account Settings", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json b/erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json new file mode 100644 index 0000000000..bb396d268a --- /dev/null +++ b/erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json @@ -0,0 +1,19 @@ +{ + "action": "Create Entry", + "creation": "2020-05-14 17:46:41.831517", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-14 17:46:41.831517", + "modified_by": "Administrator", + "name": "Create a Customer", + "owner": "Administrator", + "reference_document": "Customer", + "show_full_form": 0, + "title": "Create a Customer", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/onboarding_step/create_a_product/create_a_product.json b/erpnext/accounts/onboarding_step/create_a_product/create_a_product.json new file mode 100644 index 0000000000..450bee1f40 --- /dev/null +++ b/erpnext/accounts/onboarding_step/create_a_product/create_a_product.json @@ -0,0 +1,19 @@ +{ + "action": "Create Entry", + "creation": "2020-05-14 17:45:28.554605", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-14 17:45:28.554605", + "modified_by": "Administrator", + "name": "Create a Product", + "owner": "Administrator", + "reference_document": "Item", + "show_full_form": 0, + "title": "Create a Product", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/accounts/onboarding_step/create_a_supplier/create_a_supplier.json new file mode 100644 index 0000000000..7a64224bd4 --- /dev/null +++ b/erpnext/accounts/onboarding_step/create_a_supplier/create_a_supplier.json @@ -0,0 +1,19 @@ +{ + "action": "Create Entry", + "creation": "2020-05-14 22:09:10.043554", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-14 22:09:10.043554", + "modified_by": "Administrator", + "name": "Create a Supplier", + "owner": "Administrator", + "reference_document": "Supplier", + "show_full_form": 0, + "title": "Create a Supplier", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json b/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json new file mode 100644 index 0000000000..3a2b8d3925 --- /dev/null +++ b/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json @@ -0,0 +1,19 @@ +{ + "action": "Create Entry", + "creation": "2020-05-14 22:10:07.049704", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-14 22:10:07.049704", + "modified_by": "Administrator", + "name": "Create Your First Purchase Invoice", + "owner": "Administrator", + "reference_document": "Purchase Invoice", + "show_full_form": 1, + "title": "Create Your First Purchase Invoice ", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/onboarding_step/create_your_first_sales_invoice/create_your_first_sales_invoice.json b/erpnext/accounts/onboarding_step/create_your_first_sales_invoice/create_your_first_sales_invoice.json new file mode 100644 index 0000000000..473de5079f --- /dev/null +++ b/erpnext/accounts/onboarding_step/create_your_first_sales_invoice/create_your_first_sales_invoice.json @@ -0,0 +1,19 @@ +{ + "action": "Create Entry", + "creation": "2020-05-14 17:48:21.019019", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-14 17:48:21.019019", + "modified_by": "Administrator", + "name": "Create Your First Sales Invoice", + "owner": "Administrator", + "reference_document": "Sales Invoice", + "show_full_form": 1, + "title": "Create Your First Sales Invoice ", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json b/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json new file mode 100644 index 0000000000..8e0006762d --- /dev/null +++ b/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json @@ -0,0 +1,19 @@ +{ + "action": "Create Entry", + "creation": "2020-05-13 19:29:43.844463", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-14 17:40:16.014413", + "modified_by": "Administrator", + "name": "Setup Taxes", + "owner": "Administrator", + "reference_document": "Sales Taxes and Charges Template", + "show_full_form": 1, + "title": "Lets create a Tax Template for Sales ", + "validate_action": 0 +} \ No newline at end of file diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index 49c1d0f2cc..05dc282661 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -56,14 +56,26 @@ def execute(filters=None): row += totals data.append(row) - return columns, data + chart = get_chart_data(filters, columns, data) + return columns, data, None, chart def get_columns(filters): columns = [ - _(filters.get("budget_against")) - + ":Link/%s:150" % (filters.get("budget_against")), - _("Account") + ":Link/Account:150" + { + 'label': _(filters.get("budget_against")), + 'fieldtype': 'Link', + 'fieldname': 'budget_against', + 'options': filters.get('budget_against'), + 'width': 150 + }, + { + 'label': _('Account'), + 'fieldname': 'Account', + 'fieldtype': 'Link', + 'options': 'Account', + 'width': 150 + } ] group_months = False if filters["period"] == "Monthly" else True @@ -79,7 +91,12 @@ def get_columns(filters): _("Variance ") + " " + str(year[0]) ] for label in labels: - columns.append(label + ":Float:150") + columns.append({ + 'label': label, + 'fieldtype': 'Float', + 'fieldname': frappe.scrub(label), + 'width': 150 + }) else: for label in [ _("Budget") + " (%s)" + " " + str(year[0]), @@ -95,14 +112,23 @@ def get_columns(filters): else: label = label % formatdate(from_date, format_string="MMM") - columns.append(label + ":Float:150") + columns.append({ + 'label': label, + 'fieldtype': 'Float', + 'fieldname': frappe.scrub(label), + 'width': 150 + }) if filters["period"] != "Yearly": - return columns + [ - _("Total Budget") + ":Float:150", - _("Total Actual") + ":Float:150", - _("Total Variance") + ":Float:150" - ] + for label in [_("Total Budget"), _("Total Actual"), _("Total Variance")]: + columns.append({ + 'label': label, + 'fieldtype': 'Float', + 'fieldname': frappe.scrub(label), + 'width': 150 + }) + + return columns else: return columns @@ -173,7 +199,7 @@ def get_dimension_target_details(filters): filters.budget_against, filters.company, ] - + filters.get("budget_against_filter") + + (filters.get("budget_against_filter") or []) ), as_dict=True) @@ -305,3 +331,49 @@ def get_fiscal_years(filters): }) return fiscal_year + +def get_chart_data(filters, columns, data): + + if not data: + return None + + labels = [] + + fiscal_year = get_fiscal_years(filters) + group_months = False if filters["period"] == "Monthly" else True + + for year in fiscal_year: + for from_date, to_date in get_period_date_ranges(filters["period"], year[0]): + if filters['period'] == 'Yearly': + labels.append(year[0]) + else: + if group_months: + label = formatdate(from_date, format_string="MMM") + "-" \ + + formatdate(to_date, format_string="MMM") + labels.append(label) + else: + label = formatdate(from_date, format_string="MMM") + labels.append(label) + + no_of_columns = len(labels) + + budget_values, actual_values = [0] * no_of_columns, [0] * no_of_columns + for d in data: + values = d[2:] + index = 0 + + for i in range(no_of_columns): + budget_values[i] += values[index] + actual_values[i] += values[index+1] + index += 3 + + return { + 'data': { + 'labels': labels, + 'datasets': [ + {'name': 'Budget', 'chartType': 'bar', 'values': budget_values}, + {'name': 'Actual Expense', 'chartType': 'bar', 'values': actual_values} + ] + } + } + From 41b47a68b36a377d040784b3125e6a2b62d931a9 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 15 May 2020 04:24:36 +0530 Subject: [PATCH 71/96] fix: item price not fetching when customer is unset in item price (#21488) * fix: item price not fetching when customer is unset in item price * fix: item price of selling type has hidden supplier value * fix: remove test variable * fix: test * patch: invalid customer/supplier based on item price type * fix: invalid query * fix: patch Co-authored-by: Marica --- erpnext/patches.txt | 1 + ...stomer_supplier_based_on_type_of_item_price.py | 15 +++++++++++++++ erpnext/stock/doctype/item_price/item_price.py | 7 +++++++ erpnext/stock/get_item_details.py | 2 +- 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f72172474c..0edadcc66d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -681,3 +681,4 @@ erpnext.patches.v12_0.retain_permission_rules_for_video_doctype erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive execute:frappe.delete_doc_if_exists("Page", "appointment-analytic") execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True) +erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price diff --git a/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py b/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py new file mode 100644 index 0000000000..60aec05466 --- /dev/null +++ b/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + invalid_selling_item_price = frappe.db.sql( + """SELECT name FROM `tabItem Price` WHERE selling = 1 and buying = 0 and (supplier IS NOT NULL or supplier = '')""" + ) + invalid_buying_item_price = frappe.db.sql( + """SELECT name FROM `tabItem Price` WHERE selling = 0 and buying = 1 and (customer IS NOT NULL or customer = '')""" + ) + docs_to_modify = invalid_buying_item_price + invalid_selling_item_price + for d in docs_to_modify: + # saving the doc will auto reset invalid customer/supplier field + doc = frappe.get_doc("Item Price", d[0]) + doc.save() \ No newline at end of file diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index 957c41546b..8e39eb5037 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -69,3 +69,10 @@ class ItemPrice(Document): self.reference = self.customer if self.buying: self.reference = self.supplier + + if self.selling and not self.buying: + # if only selling then remove supplier + self.supplier = None + if self.buying and not self.selling: + # if only buying then remove customer + self.customer = None diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index d50712aee7..11b6403419 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -630,7 +630,7 @@ def get_item_price(args, item_code, ignore_party=False): elif args.get("supplier"): conditions += " and supplier=%(supplier)s" else: - conditions += " and (customer is null or customer = '') and (supplier is null or supplier = '')" + conditions += "and (customer is null or customer = '') and (supplier is null or supplier = '')" if args.get('transaction_date'): conditions += """ and %(transaction_date)s between From c649468f37db8fb941f940eeae7c63807729840a Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 15 May 2020 11:35:41 +0530 Subject: [PATCH 72/96] Fixed typo --- .../v12_0/set_valid_till_date_in_supplier_quotation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py b/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py index befa46c31d..4a6e228856 100644 --- a/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py +++ b/erpnext/patches/v12_0/set_valid_till_date_in_supplier_quotation.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import frappe def execute(): - frappe.reload_doc("buying", "doctype", "suppplier_quotation") + frappe.reload_doc("buying", "doctype", "supplier_quotation") frappe.db.sql("""UPDATE `tabSupplier Quotation` SET valid_till = DATE_ADD(transaction_date , INTERVAL 1 MONTH) - WHERE docstatus < 2""") \ No newline at end of file + WHERE docstatus < 2""") From f984bee5f96723c0bd8d6370c042f7ae4f4f9d47 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 15 May 2020 11:55:42 +0530 Subject: [PATCH 73/96] fix: duplicate leave expiry creation (#21505) * fix: validate existing ledger entries to avoid duplicates * patch: remove duplicate ledger entries created * fix: consider only submitted ledger entries * fix: delete duplicate leaves from the ledger * fix: check if duplicate ledger entry exists * chore: formatting changes Co-authored-by: Nabin Hait --- .../leave_application/leave_application.py | 6 +-- .../leave_ledger_entry/leave_ledger_entry.py | 50 +++++++++++-------- erpnext/patches.txt | 1 + .../remove_duplicate_leave_ledger_entries.py | 44 ++++++++++++++++ 4 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 47b1bb7684..d2620bec91 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -549,7 +549,7 @@ def get_remaining_leaves(allocation, leaves_taken, date, expiry): return _get_remaining_leaves(total_leaves, allocation.to_date) -def get_leaves_for_period(employee, leave_type, from_date, to_date): +def get_leaves_for_period(employee, leave_type, from_date, to_date, do_not_skip_expired_leaves=False): leave_entries = get_leave_entries(employee, leave_type, from_date, to_date) leave_days = 0 @@ -559,8 +559,8 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date): if inclusive_period and leave_entry.transaction_type == 'Leave Encashment': leave_days += leave_entry.leaves - elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' \ - and leave_entry.is_expired and not skip_expiry_leaves(leave_entry, to_date): + elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' and leave_entry.is_expired \ + and (do_not_skip_expired_leaves or not skip_expiry_leaves(leave_entry, to_date)): leave_days += leave_entry.leaves elif leave_entry.transaction_type == 'Leave Application': diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 9ed58c9e59..63559c4f5a 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -88,32 +88,40 @@ def get_previous_expiry_ledger_entry(ledger): }, fieldname=['name']) def process_expired_allocation(): - ''' Check if a carry forwarded allocation has expired and create a expiry ledger entry ''' + ''' Check if a carry forwarded allocation has expired and create a expiry ledger entry + Case 1: carry forwarded expiry period is set for the leave type, + create a separate leave expiry entry against each entry of carry forwarded and non carry forwarded leaves + Case 2: leave type has no specific expiry period for carry forwarded leaves + and there is no carry forwarded leave allocation, create a single expiry against the remaining leaves. + ''' # fetch leave type records that has carry forwarded leaves expiry leave_type_records = frappe.db.get_values("Leave Type", filters={ 'expire_carry_forwarded_leaves_after_days': (">", 0) }, fieldname=['name']) - leave_type = [record[0] for record in leave_type_records] + leave_type = [record[0] for record in leave_type_records] or [''] - expired_allocation = frappe.db.sql_list("""SELECT name - FROM `tabLeave Ledger Entry` - WHERE - `transaction_type`='Leave Allocation' - AND `is_expired`=1""") - - expire_allocation = frappe.get_all("Leave Ledger Entry", - fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'], - filters={ - 'to_date': ("<", today()), - 'transaction_type': 'Leave Allocation', - 'transaction_name': ('not in', expired_allocation) - }, - or_filters={ - 'is_carry_forward': 0, - 'leave_type': ('in', leave_type) - }) + # fetch non expired leave ledger entry of transaction_type allocation + expire_allocation = frappe.db.sql(""" + SELECT + leaves, to_date, employee, leave_type, + is_carry_forward, transaction_name as name, transaction_type + FROM `tabLeave Ledger Entry` l + WHERE (NOT EXISTS + (SELECT name + FROM `tabLeave Ledger Entry` + WHERE + transaction_name = l.transaction_name + AND transaction_type = 'Leave Allocation' + AND name<>l.name + AND docstatus = 1 + AND ( + is_carry_forward=l.is_carry_forward + OR (is_carry_forward = 0 AND leave_type not in %s) + ))) + AND transaction_type = 'Leave Allocation' + AND to_date < %s""", (leave_type, today()), as_dict=1) if expire_allocation: create_expiry_ledger_entry(expire_allocation) @@ -133,6 +141,7 @@ def get_remaining_leaves(allocation): 'employee': allocation.employee, 'leave_type': allocation.leave_type, 'to_date': ('<=', allocation.to_date), + 'docstatus': 1 }, fieldname=['SUM(leaves)']) @frappe.whitelist() @@ -159,7 +168,8 @@ def expire_allocation(allocation, expiry_date=None): def expire_carried_forward_allocation(allocation): ''' Expires remaining leaves in the on carried forward allocation ''' from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period - leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type, allocation.from_date, allocation.to_date) + leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type, + allocation.from_date, allocation.to_date, do_not_skip_expired_leaves=True) leaves = flt(allocation.leaves) + flt(leaves_taken) # allow expired leaves entry to be created diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0edadcc66d..274728151a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -678,6 +678,7 @@ erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123 erpnext.patches.v12_0.fix_quotation_expired_status erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry erpnext.patches.v12_0.retain_permission_rules_for_video_doctype +erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive execute:frappe.delete_doc_if_exists("Page", "appointment-analytic") execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True) diff --git a/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py new file mode 100644 index 0000000000..98a2fcf27e --- /dev/null +++ b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py @@ -0,0 +1,44 @@ +# Copyright (c) 2018, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + """Delete duplicate leave ledger entries of type allocation created.""" + if not frappe.db.a_row_exists("Leave Ledger Entry"): + return + + duplicate_records_list = get_duplicate_records() + delete_duplicate_ledger_entries(duplicate_records_list) + +def get_duplicate_records(): + """Fetch all but one duplicate records from the list of expired leave allocation.""" + return frappe.db.sql_list(""" + WITH duplicate_records AS + (SELECT + name, transaction_name, is_carry_forward, + ROW_NUMBER() over(partition by transaction_name order by creation)as row + FROM `tabLeave Ledger Entry` l + WHERE (EXISTS + (SELECT name + FROM `tabLeave Ledger Entry` + WHERE + transaction_name = l.transaction_name + AND transaction_type = 'Leave Allocation' + AND name <> l.name + AND employee = l.employee + AND docstatus = 1 + AND leave_type = l.leave_type + AND is_carry_forward=l.is_carry_forward + AND to_date = l.to_date + AND from_date = l.from_date + AND is_expired = 1 + ))) + SELECT name FROM duplicate_records WHERE row > 1 + """) + +def delete_duplicate_ledger_entries(duplicate_records_list): + """Delete duplicate leave ledger entries.""" + if duplicate_records_list: + frappe.db.sql(''' DELETE FROM `tabLeave Ledger Entry` WHERE name in {0}'''.format(tuple(duplicate_records_list))) #nosec \ No newline at end of file From 200af04657edac47a96ef873347c66d93b7d7078 Mon Sep 17 00:00:00 2001 From: Rohan Date: Fri, 15 May 2020 12:10:34 +0530 Subject: [PATCH 74/96] format: better error messages for invalid coupon codes (develop) (#21599) * format: better error messages for invalid coupon codes * fix: remove unnecessary docstatus check --- .../accounts/doctype/pricing_rule/utils.py | 30 +++++++------ erpnext/shopping_cart/cart.py | 44 ++++++++++--------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index b358f56671..cb05481df5 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -4,13 +4,19 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, copy, json -from frappe import throw, _ + +import copy +import json + from six import string_types -from frappe.utils import flt, cint, get_datetime, get_link_to_form, today + +import frappe from erpnext.setup.doctype.item_group.item_group import get_child_item_groups from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses from erpnext.stock.get_item_details import get_conversion_factor +from frappe import _, throw +from frappe.utils import cint, flt, get_datetime, get_link_to_form, getdate, today + class MultiplePricingRuleConflict(frappe.ValidationError): pass @@ -502,18 +508,16 @@ def get_pricing_rule_items(pr_doc): return list(set(apply_on_data)) def validate_coupon_code(coupon_name): - from frappe.utils import today,getdate - coupon=frappe.get_doc("Coupon Code",coupon_name) + coupon = frappe.get_doc("Coupon Code", coupon_name) + if coupon.valid_from: - if coupon.valid_from > getdate(today()) : - frappe.throw(_("Sorry,coupon code validity has not started")) + if coupon.valid_from > getdate(today()): + frappe.throw(_("Sorry, this coupon code's validity has not started")) elif coupon.valid_upto: - if coupon.valid_upto < getdate(today()) : - frappe.throw(_("Sorry,coupon code validity has expired")) - elif coupon.used>=coupon.maximum_use: - frappe.throw(_("Sorry,coupon code are exhausted")) - else: - return + if coupon.valid_upto < getdate(today()): + frappe.throw(_("Sorry, this coupon code's validity has expired")) + elif coupon.used >= coupon.maximum_use: + frappe.throw(_("Sorry, this coupon code is no longer valid")) def update_coupon_code_count(coupon_name,transaction_type): coupon=frappe.get_doc("Coupon Code",coupon_name) diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index e11e1bb5dc..4ac546e82c 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -541,27 +541,31 @@ def show_terms(doc): return doc.tc_name @frappe.whitelist(allow_guest=True) -def apply_coupon_code(applied_code,applied_referral_sales_partner): +def apply_coupon_code(applied_code, applied_referral_sales_partner): quotation = True - if applied_code: - coupon_list=frappe.get_all('Coupon Code', filters={"docstatus": ("<", "2"), 'coupon_code':applied_code }, fields=['name']) - if coupon_list: - coupon_name=coupon_list[0].name - from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code - validate_coupon_code(coupon_name) - quotation = _get_cart_quotation() - quotation.coupon_code=coupon_name + + if not applied_code: + frappe.throw(_("Please enter a coupon code")) + + coupon_list = frappe.get_all('Coupon Code', filters={'coupon_code': applied_code}) + if not coupon_list: + frappe.throw(_("Please enter a valid coupon code")) + + coupon_name = coupon_list[0].name + + from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code + validate_coupon_code(coupon_name) + quotation = _get_cart_quotation() + quotation.coupon_code = coupon_name + quotation.flags.ignore_permissions = True + quotation.save() + + if applied_referral_sales_partner: + sales_partner_list = frappe.get_all('Sales Partner', filters={'referral_code': applied_referral_sales_partner}) + if sales_partner_list: + sales_partner_name = sales_partner_list[0].name + quotation.referral_sales_partner = sales_partner_name quotation.flags.ignore_permissions = True quotation.save() - if applied_referral_sales_partner: - sales_partner_list=frappe.get_all('Sales Partner', filters={'docstatus': 0, 'referral_code':applied_referral_sales_partner }, fields=['name']) - if sales_partner_list: - sales_partner_name=sales_partner_list[0].name - quotation.referral_sales_partner=sales_partner_name - quotation.flags.ignore_permissions = True - quotation.save() - else: - frappe.throw(_("Please enter valid coupon code !!")) - else: - frappe.throw(_("Please enter coupon code !!")) + return quotation From 7d61c03af41271082bd872c7edbfdb3ac3b478ae Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 15 May 2020 12:58:48 +0530 Subject: [PATCH 75/96] fix: Add missing dimensions in GL entries (#21689) * fix: Add misssing dimensions in GL entries * fix: Add project filter in trial balance report * fix: Use current dimensions instead of dimensions from asset --- erpnext/accounts/deferred_revenue.py | 12 ++++----- .../accounting_dimension.py | 6 ++--- .../invoice_discounting.py | 14 ++++++++--- .../doctype/payment_entry/payment_entry.py | 10 ++++---- .../purchase_invoice/purchase_invoice.py | 13 +++++----- .../doctype/sales_invoice/sales_invoice.py | 25 +++++++++---------- .../report/trial_balance/trial_balance.js | 12 ++++++--- .../report/trial_balance/trial_balance.py | 8 ++++++ erpnext/assets/doctype/asset/asset.py | 12 ++++----- erpnext/education/doctype/fees/fees.py | 6 +++-- .../hr/doctype/expense_claim/expense_claim.py | 13 +++++----- .../expense_claim_detail.json | 17 ++++++++++--- .../expense_taxes_and_charges.json | 17 ++++++++++--- erpnext/patches.txt | 2 +- ...counting_dimensions_in_missing_doctypes.py | 3 ++- 15 files changed, 107 insertions(+), 63 deletions(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index b0210e5fd4..b57e6783ce 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -185,7 +185,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): total_days, total_booking_days, account_currency) make_gl_entries(doc, credit_account, debit_account, against, - amount, base_amount, end_date, project, account_currency, item.cost_center, item.name, deferred_process) + amount, base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process) # Returned in case of any errors because it tries to submit the same record again and again in case of errors if frappe.flags.deferred_accounting_error: @@ -222,7 +222,7 @@ def process_deferred_accounting(posting_date=today()): doc.submit() def make_gl_entries(doc, credit_account, debit_account, against, - amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no, deferred_process=None): + amount, base_amount, posting_date, project, account_currency, cost_center, item, deferred_process=None): # GL Entry for crediting the amount in the deferred expense from erpnext.accounts.general_ledger import make_gl_entries @@ -236,12 +236,12 @@ def make_gl_entries(doc, credit_account, debit_account, against, "credit": base_amount, "credit_in_account_currency": amount, "cost_center": cost_center, - "voucher_detail_no": voucher_detail_no, + "voucher_detail_no": item.name, 'posting_date': posting_date, 'project': project, 'against_voucher_type': 'Process Deferred Accounting', 'against_voucher': deferred_process - }, account_currency) + }, account_currency, item=item) ) # GL Entry to debit the amount from the expense gl_entries.append( @@ -251,12 +251,12 @@ def make_gl_entries(doc, credit_account, debit_account, against, "debit": base_amount, "debit_in_account_currency": amount, "cost_center": cost_center, - "voucher_detail_no": voucher_detail_no, + "voucher_detail_no": item.name, 'posting_date': posting_date, 'project': project, 'against_voucher_type': 'Process Deferred Accounting', 'against_voucher': deferred_process - }, account_currency) + }, account_currency, item=item) ) if gl_entries: diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 7a85bfb26b..894ec5bdec 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -162,9 +162,9 @@ def toggle_disabling(doc): def get_doctypes_with_dimensions(): doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset", - "Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item", - "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", - "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule", + "Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", + "Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", + "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule", "Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription", "Subscription Plan"] diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py index 39fc203d53..594b4d4a22 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py @@ -8,6 +8,7 @@ from frappe import _ from frappe.utils import flt, getdate, nowdate, add_days from erpnext.controllers.accounts_controller import AccountsController from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions class InvoiceDiscounting(AccountsController): def validate(self): @@ -81,10 +82,15 @@ class InvoiceDiscounting(AccountsController): def make_gl_entries(self): company_currency = frappe.get_cached_value('Company', self.company, "default_currency") + gl_entries = [] + invoice_fields = ["debit_to", "party_account_currency", "conversion_rate", "cost_center"] + accounting_dimensions = get_accounting_dimensions() + + invoice_fields.extend(accounting_dimensions) + for d in self.invoices: - inv = frappe.db.get_value("Sales Invoice", d.sales_invoice, - ["debit_to", "party_account_currency", "conversion_rate", "cost_center"], as_dict=1) + inv = frappe.db.get_value("Sales Invoice", d.sales_invoice, invoice_fields, as_dict=1) if d.outstanding_amount: outstanding_in_company_currency = flt(d.outstanding_amount * inv.conversion_rate, @@ -102,7 +108,7 @@ class InvoiceDiscounting(AccountsController): "cost_center": inv.cost_center, "against_voucher": d.sales_invoice, "against_voucher_type": "Sales Invoice" - }, inv.party_account_currency)) + }, inv.party_account_currency, item=inv)) gl_entries.append(self.get_gl_dict({ "account": self.accounts_receivable_credit, @@ -115,7 +121,7 @@ class InvoiceDiscounting(AccountsController): "cost_center": inv.cost_center, "against_voucher": d.sales_invoice, "against_voucher_type": "Sales Invoice" - }, ar_credit_account_currency)) + }, ar_credit_account_currency, item=inv)) make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No') diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 83c670eace..22df5be1b9 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -86,7 +86,7 @@ class PaymentEntry(AccountsController): self.update_payment_schedule(cancel=1) self.set_payment_req_status() self.set_status() - + def set_payment_req_status(self): from erpnext.accounts.doctype.payment_request.payment_request import update_payment_req_status update_payment_req_status(self, None) @@ -280,7 +280,7 @@ class PaymentEntry(AccountsController): outstanding_amount, is_return = frappe.get_cached_value(d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"]) if outstanding_amount <= 0 and not is_return: no_oustanding_refs.setdefault(d.reference_doctype, []).append(d) - + for k, v in no_oustanding_refs.items(): frappe.msgprint(_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.

\ If this is undesirable please cancel the corresponding Payment Entry.") @@ -506,7 +506,7 @@ class PaymentEntry(AccountsController): "against": against_account, "account_currency": self.party_account_currency, "cost_center": self.cost_center - }) + }, item=self) dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit" @@ -550,7 +550,7 @@ class PaymentEntry(AccountsController): "credit_in_account_currency": self.paid_amount, "credit": self.base_paid_amount, "cost_center": self.cost_center - }) + }, item=self) ) if self.payment_type in ("Receive", "Internal Transfer"): gl_entries.append( @@ -561,7 +561,7 @@ class PaymentEntry(AccountsController): "debit_in_account_currency": self.received_amount, "debit": self.base_received_amount, "cost_center": self.cost_center - }) + }, item=self) ) def add_deductions_gl_entries(self, gl_entries): diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 3aa24df16d..cf4e158bba 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -460,7 +460,7 @@ class PurchaseInvoice(BuyingController): "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, "cost_center": self.cost_center - }, self.party_account_currency) + }, self.party_account_currency, item=self) ) def make_item_gl_entries(self, gl_entries): @@ -841,7 +841,7 @@ class PurchaseInvoice(BuyingController): "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, "cost_center": self.cost_center - }, self.party_account_currency) + }, self.party_account_currency, item=self) ) gl_entries.append( @@ -852,7 +852,7 @@ class PurchaseInvoice(BuyingController): "credit_in_account_currency": self.base_paid_amount \ if bank_account_currency==self.company_currency else self.paid_amount, "cost_center": self.cost_center - }, bank_account_currency) + }, bank_account_currency, item=self) ) def make_write_off_gl_entry(self, gl_entries): @@ -873,7 +873,7 @@ class PurchaseInvoice(BuyingController): "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, "cost_center": self.cost_center - }, self.party_account_currency) + }, self.party_account_currency, item=self) ) gl_entries.append( self.get_gl_dict({ @@ -883,7 +883,7 @@ class PurchaseInvoice(BuyingController): "credit_in_account_currency": self.base_write_off_amount \ if write_off_account_currency==self.company_currency else self.write_off_amount, "cost_center": self.cost_center or self.write_off_cost_center - }) + }, item=self) ) def make_gle_for_rounding_adjustment(self, gl_entries): @@ -902,8 +902,7 @@ class PurchaseInvoice(BuyingController): "debit_in_account_currency": self.rounding_adjustment, "debit": self.base_rounding_adjustment, "cost_center": self.cost_center or round_off_cost_center, - } - )) + }, item=self)) def on_cancel(self): super(PurchaseInvoice, self).on_cancel() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 3b0fade0e5..05b85dabd4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -791,7 +791,7 @@ class SalesInvoice(SellingController): "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, "cost_center": self.cost_center - }, self.party_account_currency) + }, self.party_account_currency, item=self) ) def make_tax_gl_entries(self, gl_entries): @@ -808,7 +808,7 @@ class SalesInvoice(SellingController): tax.precision("base_tax_amount_after_discount_amount")) if account_currency==self.company_currency else flt(tax.tax_amount_after_discount_amount, tax.precision("tax_amount_after_discount_amount"))), "cost_center": tax.cost_center - }, account_currency) + }, account_currency, item=tax) ) def make_item_gl_entries(self, gl_entries): @@ -828,7 +828,7 @@ class SalesInvoice(SellingController): for gle in fixed_asset_gl_entries: gle["against"] = self.customer - gl_entries.append(self.get_gl_dict(gle)) + gl_entries.append(self.get_gl_dict(gle, item=item)) asset.db_set("disposal_date", self.posting_date) asset.set_status("Sold" if self.docstatus==1 else None) @@ -866,7 +866,7 @@ class SalesInvoice(SellingController): "against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher_type": self.doctype, "cost_center": self.cost_center - }) + }, item=self) ) gl_entries.append( self.get_gl_dict({ @@ -875,7 +875,7 @@ class SalesInvoice(SellingController): "against": self.customer, "debit": self.loyalty_amount, "remark": "Loyalty Points redeemed by the customer" - }) + }, item=self) ) def make_pos_gl_entries(self, gl_entries): @@ -896,7 +896,7 @@ class SalesInvoice(SellingController): "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, "cost_center": self.cost_center - }, self.party_account_currency) + }, self.party_account_currency, item=self) ) payment_mode_account_currency = get_account_currency(payment_mode.account) @@ -909,7 +909,7 @@ class SalesInvoice(SellingController): if payment_mode_account_currency==self.company_currency \ else payment_mode.amount, "cost_center": self.cost_center - }, payment_mode_account_currency) + }, payment_mode_account_currency, item=self) ) def make_gle_for_change_amount(self, gl_entries): @@ -927,7 +927,7 @@ class SalesInvoice(SellingController): "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher_type": self.doctype, "cost_center": self.cost_center - }, self.party_account_currency) + }, self.party_account_currency, item=self) ) gl_entries.append( @@ -936,7 +936,7 @@ class SalesInvoice(SellingController): "against": self.customer, "credit": self.base_change_amount, "cost_center": self.cost_center - }) + }, item=self) ) else: frappe.throw(_("Select change amount account"), title="Mandatory Field") @@ -960,7 +960,7 @@ class SalesInvoice(SellingController): "against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher_type": self.doctype, "cost_center": self.cost_center - }, self.party_account_currency) + }, self.party_account_currency, item=self) ) gl_entries.append( self.get_gl_dict({ @@ -971,7 +971,7 @@ class SalesInvoice(SellingController): self.precision("base_write_off_amount")) if write_off_account_currency==self.company_currency else flt(self.write_off_amount, self.precision("write_off_amount"))), "cost_center": self.cost_center or self.write_off_cost_center or default_cost_center - }, write_off_account_currency) + }, write_off_account_currency, item=self) ) def make_gle_for_rounding_adjustment(self, gl_entries): @@ -988,8 +988,7 @@ class SalesInvoice(SellingController): "credit": flt(self.base_rounding_adjustment, self.precision("base_rounding_adjustment")), "cost_center": self.cost_center or round_off_cost_center, - } - )) + }, item=self)) def update_billing_status_in_dn(self, update_modified=True): updated_delivery_notes = [] diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js index 622bab6946..07752e1e62 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.js +++ b/erpnext/accounts/report/trial_balance/trial_balance.js @@ -46,7 +46,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "default": frappe.defaults.get_user_default("year_end_date"), }, { - "fieldname":"cost_center", + "fieldname": "cost_center", "label": __("Cost Center"), "fieldtype": "Link", "options": "Cost Center", @@ -61,7 +61,13 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { } }, { - "fieldname":"finance_book", + "fieldname": "project", + "label": __("Project"), + "fieldtype": "Link", + "options": "Project" + }, + { + "fieldname": "finance_book", "label": __("Finance Book"), "fieldtype": "Link", "options": "Finance Book", @@ -97,7 +103,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { } erpnext.dimension_filters.forEach((dimension) => { - frappe.query_reports["Trial Balance"].filters.splice(5, 0 ,{ + frappe.query_reports["Trial Balance"].filters.splice(6, 0 ,{ "fieldname": dimension["fieldname"], "label": __(dimension["label"]), "fieldtype": "Link", diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index d78324157a..8bd4399e60 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -69,6 +69,10 @@ def get_data(filters): gl_entries_by_account = {} opening_balances = get_opening_balances(filters) + + #add filter inside list so that the query in financial_statements.py doesn't break + filters.project = [filters.project] + set_gl_entries_by_account(filters.company, filters.from_date, filters.to_date, min_lft, max_rgt, filters, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry)) @@ -102,6 +106,9 @@ def get_rootwise_opening_balances(filters, report_type): additional_conditions += """ and cost_center in (select name from `tabCost Center` where lft >= %s and rgt <= %s)""" % (lft, rgt) + if filters.project: + additional_conditions += " and project = %(project)s" + if filters.finance_book: fb_conditions = " AND finance_book = %(finance_book)s" if filters.include_default_book_entries: @@ -116,6 +123,7 @@ def get_rootwise_opening_balances(filters, report_type): "from_date": filters.from_date, "report_type": report_type, "year_start_date": filters.year_start_date, + "project": filters.project, "finance_book": filters.finance_book, "company_fb": frappe.db.get_value("Company", filters.company, 'default_finance_book') } diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index a3200d5644..505ba4c6b6 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -125,7 +125,7 @@ class Asset(AccountsController): if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date): frappe.throw(_("Available-for-use Date should be after purchase date")) - + def validate_gross_and_purchase_amount(self): if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount: frappe.throw(_("Gross Purchase Amount should be {} to purchase amount of one single Asset. {}\ @@ -455,7 +455,7 @@ class Asset(AccountsController): for d in self.get('finance_books'): if d.finance_book == self.default_finance_book: return cint(d.idx) - 1 - + def validate_make_gl_entry(self): purchase_document = self.get_purchase_document() asset_bought_with_invoice = purchase_document == self.purchase_invoice @@ -487,14 +487,14 @@ class Asset(AccountsController): purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt return purchase_document - + def get_asset_accounts(self): fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name, asset_category = self.asset_category, company = self.company) cwip_account = get_asset_account("capital_work_in_progress_account", self.name, self.asset_category, self.company) - + return fixed_asset_account, cwip_account def make_gl_entries(self): @@ -513,7 +513,7 @@ class Asset(AccountsController): "credit": self.purchase_receipt_amount, "credit_in_account_currency": self.purchase_receipt_amount, "cost_center": self.cost_center - })) + }, item=self)) gl_entries.append(self.get_gl_dict({ "account": fixed_asset_account, @@ -523,7 +523,7 @@ class Asset(AccountsController): "debit": self.purchase_receipt_amount, "debit_in_account_currency": self.purchase_receipt_amount, "cost_center": self.cost_center - })) + }, item=self)) if gl_entries: from erpnext.accounts.general_ledger import make_gl_entries diff --git a/erpnext/education/doctype/fees/fees.py b/erpnext/education/doctype/fees/fees.py index f0d60faed6..25d67d2d5f 100644 --- a/erpnext/education/doctype/fees/fees.py +++ b/erpnext/education/doctype/fees/fees.py @@ -98,14 +98,16 @@ class Fees(AccountsController): "debit_in_account_currency": self.grand_total, "against_voucher": self.name, "against_voucher_type": self.doctype - }) + }, item=self) + fee_gl_entry = self.get_gl_dict({ "account": self.income_account, "against": self.student, "credit": self.grand_total, "credit_in_account_currency": self.grand_total, "cost_center": self.cost_center - }) + }, item=self) + from erpnext.accounts.general_ledger import make_gl_entries make_gl_entries([student_gl_entries, fee_gl_entry], cancel=(self.docstatus == 2), update_outstanding="Yes", merge_entries=False) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index ac1bfa1a39..ea469b82c9 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -116,8 +116,9 @@ class ExpenseClaim(AccountsController): "party_type": "Employee", "party": self.employee, "against_voucher_type": self.doctype, - "against_voucher": self.name - }) + "against_voucher": self.name, + "cost_center": self.cost_center + }, item=self) ) # expense entries @@ -129,7 +130,7 @@ class ExpenseClaim(AccountsController): "debit_in_account_currency": data.sanctioned_amount, "against": self.employee, "cost_center": data.cost_center - }) + }, item=data) ) for data in self.advances: @@ -157,7 +158,7 @@ class ExpenseClaim(AccountsController): "credit": self.grand_total, "credit_in_account_currency": self.grand_total, "against": self.employee - }) + }, item=self) ) gl_entry.append( @@ -170,7 +171,7 @@ class ExpenseClaim(AccountsController): "debit_in_account_currency": self.grand_total, "against_voucher": self.name, "against_voucher_type": self.doctype, - }) + }, item=self) ) return gl_entry @@ -187,7 +188,7 @@ class ExpenseClaim(AccountsController): "cost_center": self.cost_center, "against_voucher_type": self.doctype, "against_voucher": self.name - }) + }, item=tax) ) def validate_account_details(self): diff --git a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json index 16e9eef917..3cce50e090 100644 --- a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json +++ b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json @@ -13,9 +13,11 @@ "description", "section_break_6", "amount", - "cost_center", "column_break_8", - "sanctioned_amount" + "sanctioned_amount", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break" ], "fields": [ { @@ -104,12 +106,21 @@ "fieldtype": "Link", "label": "Cost Center", "options": "Cost Center" + }, + { + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2019-12-11 13:42:33.233432", + "modified": "2020-05-11 18:54:35.601592", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim Detail", diff --git a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json index d68caf1cc1..885e3eed97 100644 --- a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json +++ b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json @@ -8,14 +8,16 @@ "engine": "InnoDB", "field_order": [ "account_head", - "cost_center", "rate", "col_break1", "description", "section_break_6", "tax_amount", "column_break_8", - "total" + "total", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break" ], "fields": [ { @@ -91,11 +93,20 @@ { "fieldname": "column_break_8", "fieldtype": "Column Break" + }, + { + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" } ], "istable": 1, "links": [], - "modified": "2020-03-11 13:25:06.721917", + "modified": "2020-05-11 19:01:26.611758", "modified_by": "Administrator", "module": "HR", "name": "Expense Taxes and Charges", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 274728151a..e7df472272 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -623,7 +623,7 @@ erpnext.patches.v11_1.update_default_supplier_in_item_defaults erpnext.patches.v12_0.update_due_date_in_gle erpnext.patches.v12_0.add_default_buying_selling_terms_in_company erpnext.patches.v12_0.update_ewaybill_field_position -erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes +erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes #2020-05-11 erpnext.patches.v11_1.set_status_for_material_request_type_manufacture erpnext.patches.v12_0.move_plaid_settings_to_doctype execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart_link') diff --git a/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py b/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py index b71ea66594..657decfed2 100644 --- a/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py +++ b/erpnext/patches/v12_0/create_accounting_dimensions_in_missing_doctypes.py @@ -20,7 +20,8 @@ def execute(): else: insert_after_field = 'accounting_dimensions_section' - for doctype in ["Subscription Plan", "Subscription", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item"]: + for doctype in ["Subscription Plan", "Subscription", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", + "Expense Claim Detail", "Expense Taxes and Charges"]: field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname}) From 940856cc16bca544963473afea0d42644de2cf01 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 15 May 2020 14:30:24 +0530 Subject: [PATCH 76/96] fix: add tests for set_status --- .../doctype/purchase_invoice/purchase_invoice.py | 2 +- .../purchase_invoice/test_purchase_invoice.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 265969db1f..baf2ba9fd4 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1042,7 +1042,7 @@ class PurchaseInvoice(BuyingController): elif outstanding_amount > 0 and due_date >= nowdate: self.status = "Unpaid" #Check if outstanding amount is 0 due to debit note issued against invoice - elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): + elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): self.status = "Debit Note Issued" elif self.is_return == 1: self.status = "Return" diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index e41ad42846..6170005061 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -86,6 +86,8 @@ class TestPurchaseInvoice(unittest.TestCase): pe.submit() pi_doc = frappe.get_doc('Purchase Invoice', pi_doc.name) + pi_doc.load_from_db() + self.assertTrue(pi_doc.status, "Paid") self.assertRaises(frappe.LinkExistsError, pi_doc.cancel) unlink_payment_on_cancel_of_invoice() @@ -203,7 +205,9 @@ class TestPurchaseInvoice(unittest.TestCase): pi.insert() pi.submit() + pi.load_from_db() + self.assertTrue(pi.status, "Unpaid") self.check_gle_for_pi(pi.name) def check_gle_for_pi(self, pi): @@ -234,6 +238,9 @@ class TestPurchaseInvoice(unittest.TestCase): pi = frappe.copy_doc(test_records[0]) pi.insert() + pi.load_from_db() + + self.assertTrue(pi.status, "Draft") pi.naming_series = 'TEST-' self.assertRaises(frappe.CannotChangeConstantError, pi.save) @@ -248,6 +255,8 @@ class TestPurchaseInvoice(unittest.TestCase): pi.get("taxes").pop(1) pi.insert() pi.submit() + pi.load_from_db() + self.assertTrue(pi.status, "Unpaid") gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s @@ -599,6 +608,11 @@ class TestPurchaseInvoice(unittest.TestCase): # return entry pi1 = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2, rate=50, update_stock=1) + pi.load_from_db() + self.assertTrue(pi.status, "Debit Note Issued") + pi1.load_from_db() + self.assertTrue(pi1.status, "Return") + actual_qty_2 = get_qty_after_transaction() self.assertEqual(actual_qty_1 - 2, actual_qty_2) @@ -771,6 +785,8 @@ class TestPurchaseInvoice(unittest.TestCase): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import get_outstanding_amount pi = make_purchase_invoice(item_code = "_Test Item", qty = (5 * -1), rate=500, is_return = 1) + pi.load_from_db() + self.assertTrue(pi.status, "Return") outstanding_amount = get_outstanding_amount(pi.doctype, pi.name, "Creditors - _TC", pi.supplier, "Supplier") From 03d165ff79291a3b4c104f398277fbbd43d8cb84 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 15 May 2020 16:39:02 +0530 Subject: [PATCH 77/96] fix: update remark on submitting payment entry --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 83c670eace..7e3b1349f2 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -451,8 +451,6 @@ class PaymentEntry(AccountsController): frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction")) def set_remarks(self): - if self.remarks: return - if self.payment_type=="Internal Transfer": remarks = [_("Amount {0} {1} transferred from {2} to {3}") .format(self.paid_from_account_currency, self.paid_amount, self.paid_from, self.paid_to)] From 7be71c88f21b93f9058a1dbe96fa79509a05ccac Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 15 May 2020 19:23:41 +0530 Subject: [PATCH 78/96] fix: Better validation message for group accounts (#21725) --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index efab5801e8..291aff3f5a 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -112,8 +112,8 @@ class GLEntry(Document): from tabAccount where name=%s""", self.account, as_dict=1)[0] if ret.is_group==1: - frappe.throw(_("{0} {1}: Account {2} cannot be a Group") - .format(self.voucher_type, self.voucher_no, self.account)) + frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in + transactions''').format(self.voucher_type, self.voucher_no, self.account)) if ret.docstatus==2: frappe.throw(_("{0} {1}: Account {2} is inactive") From 5cfbdf4bcbb96ec891f114e891e42d6948dfb694 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 15 May 2020 19:36:23 +0530 Subject: [PATCH 79/96] fix: user not able to view product (#21740) --- erpnext/shopping_cart/product_info.py | 8 +++++--- erpnext/stock/doctype/item/item.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/shopping_cart/product_info.py b/erpnext/shopping_cart/product_info.py index a7da09cb80..21ee335125 100644 --- a/erpnext/shopping_cart/product_info.py +++ b/erpnext/shopping_cart/product_info.py @@ -10,14 +10,16 @@ from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings from erpnext.utilities.product import get_price, get_qty_in_stock, get_non_stock_item_status @frappe.whitelist(allow_guest=True) -def get_product_info_for_website(item_code): +def get_product_info_for_website(item_code, skip_quotation_creation=False): """get product price / stock info for website""" cart_settings = get_shopping_cart_settings() if not cart_settings.enabled: return frappe._dict() - cart_quotation = _get_cart_quotation() + cart_quotation = frappe._dict() + if not skip_quotation_creation: + cart_quotation = _get_cart_quotation() price = get_price( item_code, @@ -51,7 +53,7 @@ def get_product_info_for_website(item_code): def set_product_info_for_website(item): """set product price uom for website""" - product_info = get_product_info_for_website(item.item_code) + product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True) if product_info: item.update(product_info) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 4cc50bba9e..7a1c1279ea 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -467,7 +467,7 @@ class Item(WebsiteGenerator): def set_shopping_cart_data(self, context): from erpnext.shopping_cart.product_info import get_product_info_for_website - context.shopping_cart = get_product_info_for_website(self.name) + context.shopping_cart = get_product_info_for_website(self.name, skip_quotation_creation=True) def add_default_uom_in_conversion_factor_table(self): uom_conv_list = [d.uom for d in self.get("uoms")] From 49dbbdc4cb2a7b8c385cca7b92684c7ab6c5e606 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 16 May 2020 04:59:43 +0530 Subject: [PATCH 80/96] fix: promotional scheme not able to savce --- erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 19f571fb30..4d9053a55b 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -99,7 +99,7 @@ class PricingRule(Document): self.same_item = 1 def validate_max_discount(self): - if self.rate_or_discount == "Discount Percentage" and self.items: + if self.rate_or_discount == "Discount Percentage" and self.get("items"): for d in self.items: max_discount = frappe.get_cached_value("Item", d.item_code, "max_discount") if max_discount and flt(self.discount_percentage) > flt(max_discount): From 2a0e3e3515ed4b25cbbe771ec39d5472aa9d630e Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Sat, 16 May 2020 18:07:03 +0530 Subject: [PATCH 81/96] fix: Remove strip --- .../tax_withholding_category/tax_withholding_category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index e904a681f6..93bec686ce 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -58,7 +58,7 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company): "rate": tax_rate_detail.tax_withholding_rate, "threshold": tax_rate_detail.single_threshold, "cumulative_threshold": tax_rate_detail.cumulative_threshold, - "description": tax_withholding.category_name.strip() if tax_withholding.category_name.strip() else tax_withholding_category + "description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category }) def get_tax_withholding_rates(tax_withholding, fiscal_year): From 779fc898b187e1cd284d615a10556b014c4b0646 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Sun, 17 May 2020 20:18:30 +0530 Subject: [PATCH 82/96] fix: Future date half day validation (#21718) * fix: Future date half day validation * fix: Allow half day attendance only via leave application Co-authored-by: Nabin Hait --- erpnext/hr/doctype/attendance/attendance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index b6c80655c2..6c7ee002db 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -21,7 +21,7 @@ class Attendance(Document): date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining") # leaves can be marked for future dates - if self.status not in ('On Leave', 'Half Day') and getdate(self.attendance_date) > getdate(nowdate()): + if self.status != 'On Leave' and not self.leave_application and getdate(self.attendance_date) > getdate(nowdate()): frappe.throw(_("Attendance can not be marked for future dates")) elif date_of_joining and getdate(self.attendance_date) < getdate(date_of_joining): frappe.throw(_("Attendance date can not be less than employee's joining date")) @@ -41,7 +41,7 @@ class Attendance(Document): leave_record = frappe.db.sql(""" select leave_type, half_day, half_day_date from `tabLeave Application` - where employee = %s + where employee = %s and %s between from_date and to_date and status = 'Approved' and docstatus = 1 From 8e3fc5ee6eccb572a30794741de9dafe4a29b746 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sun, 17 May 2020 20:28:39 +0530 Subject: [PATCH 83/96] fix: incorrect stock valuation for repack entry (#21736) --- .../stock/doctype/stock_entry/stock_entry.py | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 62c9eb1eb2..be2dd526a6 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -363,6 +363,9 @@ class StockEntry(StockController): + self.work_order + ":" + ", ".join(other_ste), DuplicateEntryForWorkOrderError) def set_incoming_rate(self): + if self.purpose == "Repack": + self.set_basic_rate_for_finished_goods() + for d in self.items: if d.s_warehouse: args = self.get_args_for_incoming_rate(d) @@ -475,20 +478,31 @@ class StockEntry(StockController): "allow_zero_valuation": item.allow_zero_valuation_rate, }) - def set_basic_rate_for_finished_goods(self, raw_material_cost, scrap_material_cost): + def set_basic_rate_for_finished_goods(self, raw_material_cost=0, scrap_material_cost=0): + total_fg_qty = 0 + if not raw_material_cost and self.get("items"): + raw_material_cost = sum([flt(row.basic_amount) for row in self.items + if row.s_warehouse and not row.t_warehouse]) + + total_fg_qty = sum([flt(row.qty) for row in self.items + if row.t_warehouse and not row.s_warehouse]) + if self.purpose in ["Manufacture", "Repack"]: for d in self.get("items"): if (d.transfer_qty and (d.bom_no or d.t_warehouse) and (getattr(self, "pro_doc", frappe._dict()).scrap_warehouse != d.t_warehouse)): - if self.work_order \ - and frappe.db.get_single_value("Manufacturing Settings", "material_consumption"): + if (self.work_order and self.purpose == "Manufacture" + and frappe.db.get_single_value("Manufacturing Settings", "material_consumption")): bom_items = self.get_bom_raw_materials(d.transfer_qty) raw_material_cost = sum([flt(row.qty)*flt(row.rate) for row in bom_items.values()]) - if raw_material_cost: + if raw_material_cost and self.purpose == "Manufacture": d.basic_rate = flt((raw_material_cost - scrap_material_cost) / flt(d.transfer_qty), d.precision("basic_rate")) d.basic_amount = flt((raw_material_cost - scrap_material_cost), d.precision("basic_amount")) + elif self.purpose == "Repack" and total_fg_qty: + d.basic_rate = flt(raw_material_cost) / flt(total_fg_qty) + d.basic_amount = d.basic_rate * d.qty def distribute_additional_costs(self): if self.purpose == "Material Issue": From 3bce13eaa2d203fb5225f42acae504c782954202 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Sun, 17 May 2020 17:28:37 +0200 Subject: [PATCH 84/96] fix: remove guest access (#21693) --- erpnext/crm/doctype/lead/lead.json | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index 20ab51d44b..6fef0c4643 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_events_in_timeline": 1, "allow_import": 1, "autoname": "naming_series:", @@ -447,7 +448,7 @@ "idx": 5, "image_field": "image", "links": [], - "modified": "2020-04-08 22:26:11.687110", + "modified": "2020-05-11 20:27:45.868960", "modified_by": "Administrator", "module": "CRM", "name": "Lead", @@ -504,15 +505,6 @@ "read": 1, "report": 1, "role": "Sales User" - }, - { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Guest", - "share": 1 } ], "search_fields": "lead_name,lead_owner,status", From 40d49306d3b0e28e820995698bd85841049008cb Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 17 May 2020 21:46:42 +0530 Subject: [PATCH 85/96] fix: pass ignore_mandatory flag when updating customer from patient --- erpnext/healthcare/doctype/patient/patient.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py index ea63ac7492..30a1e45f0e 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -44,6 +44,7 @@ class Patient(Document): customer.default_price_list = self.default_price_list customer.default_currency = self.default_currency customer.language = self.language + customer.ignore_mandatory = True customer.save(ignore_permissions=True) else: if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient'): From 30314f18f76ce051857c0c6a33380306c2984e25 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 17 May 2020 22:06:02 +0530 Subject: [PATCH 86/96] fix: patient appointment title --- .../doctype/patient_appointment/patient_appointment.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index 94c6919b84..9eb6e77c85 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -30,9 +30,8 @@ class PatientAppointment(Document): send_confirmation_msg(self) def set_title(self): - self.title = _('{0} with {1} on {2}').format(self.patient_name or self.patient, - self.practitioner_name or self.practitioner, - frappe.utils.format_datetime(self.appointment_datetime))[:100] + self.title = _('{0} with {1}').format(self.patient_name or self.patient, + self.practitioner_name or self.practitioner) def set_status(self): today = getdate() @@ -379,7 +378,7 @@ def send_appointment_reminder(): frappe.db.set_value('Patient Appointment', doc.name, 'reminded', 1) def send_message(doc, message): - patient_mobile = frappe.db.get_value("Patient", doc.patient, "mobile") + patient_mobile = frappe.db.get_value('Patient', doc.patient, 'mobile') if patient_mobile: context = {'doc': doc, 'alert': doc, 'comments': None} if doc.get('_comments'): @@ -391,7 +390,7 @@ def send_message(doc, message): try: send_sms(number, message) except Exception as e: - frappe.msgprint(_("SMS not sent, please check SMS Settings"), alert=True) + frappe.msgprint(_('SMS not sent, please check SMS Settings'), alert=True) @frappe.whitelist() def get_events(start, end, filters=None): From 2888e12dfc9fb2e67379395cb177db06d85810dc Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 17 May 2020 22:23:53 +0530 Subject: [PATCH 87/96] fix: vital signs title field --- erpnext/healthcare/doctype/vital_signs/vital_signs.json | 3 ++- erpnext/healthcare/doctype/vital_signs/vital_signs.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.json b/erpnext/healthcare/doctype/vital_signs/vital_signs.json index 57fc2369e1..15ab5047bc 100644 --- a/erpnext/healthcare/doctype/vital_signs/vital_signs.json +++ b/erpnext/healthcare/doctype/vital_signs/vital_signs.json @@ -247,6 +247,7 @@ }, { "allow_on_submit": 1, + "columns": 5, "fieldname": "title", "fieldtype": "Data", "hidden": 1, @@ -258,7 +259,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-04-27 23:18:08.278064", + "modified": "2020-05-17 22:23:24.632286", "modified_by": "Administrator", "module": "Healthcare", "name": "Vital Signs", diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.py b/erpnext/healthcare/doctype/vital_signs/vital_signs.py index 43c6205b8e..69d81ff4b0 100644 --- a/erpnext/healthcare/doctype/vital_signs/vital_signs.py +++ b/erpnext/healthcare/doctype/vital_signs/vital_signs.py @@ -19,8 +19,8 @@ class VitalSigns(Document): delete_vital_signs_from_medical_record(self) def set_title(self): - self.title = _('{0} on {1} {2}').format(self.patient_name or self.patient, - frappe.utils.format_date(self.signs_date), frappe.utils.format_time(self.signs_time))[:100] + 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) From d3a5df2202c8ab50dcfba54eb4a51116ee9499e1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 17 May 2020 22:34:42 +0530 Subject: [PATCH 88/96] fix: codacy issues --- .../healthcare/doctype/patient_encounter/patient_encounter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js index 20ee9daede..2410f8e10d 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js @@ -25,7 +25,7 @@ frappe.ui.form.on('Patient Encounter', { refresh_field('lab_test_prescription'); if (!frm.doc.__islocal) { - if (frm.doc.docstatus == 1) { + if (frm.doc.docstatus === 1) { if (frm.doc.inpatient_status == 'Admission Scheduled' || frm.doc.inpatient_status == 'Admitted') { frm.add_custom_button(__('Schedule Discharge'), function() { schedule_discharge(frm); @@ -103,7 +103,7 @@ frappe.ui.form.on('Patient Encounter', { }, practitioner: function(frm) { - if(!frm.doc.practitioner) { + if (!frm.doc.practitioner) { frm.set_value('practitioner_name', ''); } }, From 53934b7e3b9725557188ec1eabc4f0536d8a1ae7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 17 May 2020 22:44:19 +0530 Subject: [PATCH 89/96] fix: failing test due to inpatient record --- .../doctype/inpatient_record/test_inpatient_record.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py index e15324c55b..4c2d3f692a 100644 --- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py @@ -8,7 +8,6 @@ import unittest from frappe.utils import now_datetime, today from frappe.utils.make_random import get_random from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge -from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_patient class TestInpatientRecord(unittest.TestCase): def test_admit_and_discharge(self): @@ -112,3 +111,13 @@ def get_service_unit_type(): service_unit_type.save(ignore_permissions = True) return service_unit_type.name return service_unit_type + +def create_patient(): + patient = frappe.db.exists('Patient', '_Test IPD Patient') + if not patient: + patient = frappe.new_doc('Patient') + patient.first_name = '_Test IPD Patient' + patient.sex = 'Female' + patient.save(ignore_permissions=True) + patient = patient.name + return patient From 1862f5f29a04b260940ddee7dc2f2c907a28ec26 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 18 May 2020 07:37:04 +0530 Subject: [PATCH 90/96] feat: Payroll based on employee cost center (#21609) --- erpnext/hr/doctype/department/department.json | 14 ++++- erpnext/hr/doctype/employee/employee.json | 16 ++++- erpnext/hr/doctype/employee/test_employee.py | 9 ++- .../hr/doctype/payroll_entry/payroll_entry.py | 39 +++++++----- .../payroll_entry/test_payroll_entry.py | 60 +++++++++++++++++-- .../hr/doctype/salary_slip/salary_slip.json | 12 +++- .../doctype/salary_slip/test_salary_slip.py | 26 +++++--- .../salary_structure/salary_structure.py | 6 +- .../salary_structure/test_salary_structure.py | 1 + 9 files changed, 147 insertions(+), 36 deletions(-) diff --git a/erpnext/hr/doctype/department/department.json b/erpnext/hr/doctype/department/department.json index 6469f4cbed..a54c1d18e7 100644 --- a/erpnext/hr/doctype/department/department.json +++ b/erpnext/hr/doctype/department/department.json @@ -14,6 +14,8 @@ "is_group", "disabled", "section_break_4", + "payroll_cost_center", + "column_break_9", "leave_block_list", "leave_section", "leave_approvers", @@ -125,13 +127,23 @@ { "fieldname": "column_break_3", "fieldtype": "Column Break" + }, + { + "fieldname": "payroll_cost_center", + "fieldtype": "Link", + "label": "Payroll Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" } ], "icon": "fa fa-sitemap", "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-03-18 18:03:27.784362", + "modified": "2020-05-05 18:49:28.503931", "modified_by": "Administrator", "module": "HR", "name": "Department", diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index 13c202c775..f575765f69 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -60,6 +60,8 @@ "default_shift", "salary_information", "salary_mode", + "payroll_cost_center", + "column_break_52", "bank_name", "bank_ac_no", "health_insurance_section", @@ -783,13 +785,25 @@ { "fieldname": "column_break_19", "fieldtype": "Column Break" + }, + { + "fetch_from": "department.payroll_cost_center", + "fetch_if_empty": 1, + "fieldname": "payroll_cost_center", + "fieldtype": "Link", + "label": "Payroll Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "column_break_52", + "fieldtype": "Column Break" } ], "icon": "fa fa-user", "idx": 24, "image_field": "image", "links": [], - "modified": "2020-04-08 12:25:34.306695", + "modified": "2020-05-05 18:51:03.152503", "modified_by": "Administrator", "module": "HR", "name": "Employee", diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index d3410de2eb..f4b214adc3 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -45,7 +45,7 @@ class TestEmployee(unittest.TestCase): employee1_doc.status = 'Left' self.assertRaises(EmployeeLeftValidationError, employee1_doc.save) -def make_employee(user, company=None): +def make_employee(user, company=None, **kwargs): if not frappe.db.get_value("User", user): frappe.get_doc({ "doctype": "User", @@ -55,7 +55,7 @@ def make_employee(user, company=None): "roles": [{"doctype": "Has Role", "role": "Employee"}] }).insert() - if not frappe.db.get_value("Employee", { "user_id": user, "company": company or erpnext.get_default_company() }): + if not frappe.db.get_value("Employee", {"user_id": user}): employee = frappe.get_doc({ "doctype": "Employee", "naming_series": "EMP-", @@ -71,7 +71,10 @@ def make_employee(user, company=None): "prefered_email": user, "status": "Active", "employment_type": "Intern" - }).insert() + }) + if kwargs: + employee.update(kwargs) + employee.insert() return employee.name else: return frappe.get_value("Employee", {"employee_name":user}, "name") diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index 9ef3a99930..656de0170d 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -55,6 +55,7 @@ class PayrollEntry(Document): ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s {condition}""".format(condition=condition), {"company": self.company, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet}) + if sal_struct: cond += "and t2.salary_structure IN %(sal_struct)s " cond += "and %(from_date)s >= t2.from_date" @@ -138,7 +139,7 @@ class PayrollEntry(Document): cond = self.get_filter_condition() ss_list = frappe.db.sql(""" - select t1.name, t1.salary_structure from `tabSalary Slip` t1 + select t1.name, t1.salary_structure, t1.payroll_cost_center from `tabSalary Slip` t1 where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s %s """ % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict) @@ -169,10 +170,14 @@ class PayrollEntry(Document): def get_salary_components(self, component_type): salary_slips = self.get_sal_slip_list(ss_status = 1, as_dict = True) - if salary_slips: - salary_components = frappe.db.sql("""select salary_component, amount, parentfield - from `tabSalary Detail` where parentfield = '%s' and parent in (%s)""" % - (component_type, ', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]), as_dict=True) + if salary_slips: + salary_components = frappe.db.sql(""" + select ssd.salary_component, ssd.amount, ssd.parentfield, ss.payroll_cost_center + from `tabSalary Slip` ss, `tabSalary Detail` ssd + where ss.name = ssd.parent and ssd.parentfield = '%s' and ss.name in (%s) + """ % (component_type, ', '.join(['%s']*len(salary_slips))), + tuple([d.name for d in salary_slips]), as_dict=True) + return salary_components def get_salary_component_total(self, component_type = None): @@ -186,15 +191,16 @@ class PayrollEntry(Document): if is_flexible_benefit == 1 and only_tax_impact ==1: add_component_to_accrual_jv_entry = False if add_component_to_accrual_jv_entry: - component_dict[item['salary_component']] = component_dict.get(item['salary_component'], 0) + item['amount'] + component_dict[(item.salary_component, item.payroll_cost_center)] \ + = component_dict.get((item.salary_component, item.payroll_cost_center), 0) + flt(item.amount) account_details = self.get_account(component_dict = component_dict) return account_details def get_account(self, component_dict = None): - account_dict = {} - for s, a in component_dict.items(): - account = self.get_salary_component_account(s) - account_dict[account] = account_dict.get(account, 0) + a + account_dict = {} + for key, amount in component_dict.items(): + account = self.get_salary_component_account(key[0]) + account_dict[(account, key[1])] = account_dict.get((account, key[1]), 0) + amount return account_dict def get_default_payroll_payable_account(self): @@ -227,23 +233,23 @@ class PayrollEntry(Document): payable_amount = 0 # Earnings - for acc, amount in earnings.items(): + for acc_cc, amount in earnings.items(): payable_amount += flt(amount, precision) accounts.append({ - "account": acc, + "account": acc_cc[0], "debit_in_account_currency": flt(amount, precision), "party_type": '', - "cost_center": self.cost_center, + "cost_center": acc_cc[1] or self.cost_center, "project": self.project }) # Deductions - for acc, amount in deductions.items(): + for acc_cc, amount in deductions.items(): payable_amount -= flt(amount, precision) accounts.append({ - "account": acc, + "account": acc_cc[0], "credit_in_account_currency": flt(amount, precision), - "cost_center": self.cost_center, + "cost_center": acc_cc[1] or self.cost_center, "party_type": '', "project": self.project }) @@ -253,6 +259,7 @@ class PayrollEntry(Document): "account": default_payroll_payable_account, "credit_in_account_currency": flt(payable_amount, precision), "party_type": '', + "cost_center": self.cost_center }) journal_entry.set("accounts", accounts) diff --git a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py index e43f744bd4..3c318e78a2 100644 --- a/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/test_payroll_entry.py @@ -10,15 +10,16 @@ from frappe.utils import add_months from erpnext.hr.doctype.payroll_entry.payroll_entry import get_start_end_dates, get_end_date from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.salary_slip.test_salary_slip import get_salary_component_account, \ - make_earning_salary_component, make_deduction_salary_component + make_earning_salary_component, make_deduction_salary_component, create_account from erpnext.hr.doctype.salary_structure.test_salary_structure import make_salary_structure from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans class TestPayrollEntry(unittest.TestCase): def setUp(self): - for dt in ["Salary Slip", "Salary Component", "Salary Component Account", "Payroll Entry", "Salary Structure"]: - frappe.db.sql("delete from `tab%s`" % dt) + for dt in ["Salary Slip", "Salary Component", "Salary Component Account", + "Payroll Entry", "Salary Structure", "Salary Structure Assignment", "Payroll Employee Detail", "Additional Salary"]: + frappe.db.sql("delete from `tab%s`" % dt) make_earning_salary_component(setup=True, company_list=["_Test Company"]) make_deduction_salary_component(setup=True, company_list=["_Test Company"]) @@ -33,11 +34,59 @@ class TestPayrollEntry(unittest.TestCase): get_salary_component_account(data.name) employee = frappe.db.get_value("Employee", {'company': company}) - make_salary_structure("_Test Salary Structure", "Monthly", employee) + make_salary_structure("_Test Salary Structure", "Monthly", employee, company=company) dates = get_start_end_dates('Monthly', nowdate()) if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}): make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date) + def test_payroll_entry_with_employee_cost_center(self): # pylint: disable=no-self-use + for data in frappe.get_all('Salary Component', fields = ["name"]): + if not frappe.db.get_value('Salary Component Account', + {'parent': data.name, 'company': "_Test Company"}, 'name'): + get_salary_component_account(data.name) + + if not frappe.db.exists('Department', "cc - _TC"): + frappe.get_doc({ + 'doctype': 'Department', + 'department_name': "cc", + "company": "_Test Company" + }).insert() + + employee1 = make_employee("test_employee1@example.com", payroll_cost_center="_Test Cost Center - _TC", + department="cc - _TC", company="_Test Company") + employee2 = make_employee("test_employee2@example.com", payroll_cost_center="_Test Cost Center 2 - _TC", + department="cc - _TC", company="_Test Company") + + make_salary_structure("_Test Salary Structure 1", "Monthly", employee1, company="_Test Company") + make_salary_structure("_Test Salary Structure 2", "Monthly", employee2, company="_Test Company") + + if not frappe.db.exists("Account", "_Test Payroll Payable - _TC"): + create_account(account_name="_Test Payroll Payable", + company="_Test Company", parent_account="Current Liabilities - _TC") + frappe.db.set_value("Company", "_Test Company", "default_payroll_payable_account", + "_Test Payroll Payable - _TC") + + dates = get_start_end_dates('Monthly', nowdate()) + if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}): + pe = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date, + department="cc - _TC", company="_Test Company", payment_account="Cash - _TC", cost_center="Main - _TC") + je = frappe.db.get_value("Salary Slip", {"payroll_entry": pe.name}, "journal_entry") + je_entries = frappe.db.sql(""" + select account, cost_center, debit, credit + from `tabJournal Entry Account` + where parent=%s + order by account, cost_center + """, je) + expected_je = ( + ('_Test Payroll Payable - _TC', 'Main - _TC', 0.0, 155600.0), + ('Salary - _TC', '_Test Cost Center - _TC', 78000.0, 0.0), + ('Salary - _TC', '_Test Cost Center 2 - _TC', 78000.0, 0.0), + ('Salary Deductions - _TC', '_Test Cost Center - _TC', 0.0, 200.0), + ('Salary Deductions - _TC', '_Test Cost Center 2 - _TC', 0.0, 200.0) + ) + + self.assertEqual(je_entries, expected_je) + def test_get_end_date(self): self.assertEqual(get_end_date('2017-01-01', 'monthly'), {'end_date': '2017-01-31'}) self.assertEqual(get_end_date('2017-02-01', 'monthly'), {'end_date': '2017-02-28'}) @@ -49,7 +98,6 @@ class TestPayrollEntry(unittest.TestCase): self.assertEqual(get_end_date('2017-02-15', 'daily'), {'end_date': '2017-02-15'}) def test_loan(self): - branch = "Test Employee Branch" applicant = make_employee("test_employee@loan.com", company="_Test Company") company = "_Test Company" @@ -116,6 +164,7 @@ def make_payroll_entry(**args): payroll_entry.posting_date = nowdate() payroll_entry.payroll_frequency = "Monthly" payroll_entry.branch = args.branch or None + payroll_entry.department = args.department or None if args.cost_center: payroll_entry.cost_center = args.cost_center @@ -123,6 +172,7 @@ def make_payroll_entry(**args): if args.payment_account: payroll_entry.payment_account = args.payment_account + payroll_entry.fill_employee_details() payroll_entry.save() payroll_entry.create_salary_slips() payroll_entry.submit_salary_slips() diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.json b/erpnext/hr/doctype/salary_slip/salary_slip.json index 54a8164587..cfd4d89731 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.json +++ b/erpnext/hr/doctype/salary_slip/salary_slip.json @@ -12,6 +12,7 @@ "department", "designation", "branch", + "payroll_cost_center", "column_break1", "status", "journal_entry", @@ -459,13 +460,22 @@ "options": "Salary Slip", "print_hide": 1, "read_only": 1 + }, + { + "fetch_from": "employee.payroll_cost_center", + "fetch_if_empty": 1, + "fieldname": "payroll_cost_center", + "fieldtype": "Link", + "label": "Payroll Cost Center", + "options": "Cost Center", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 9, "is_submittable": 1, "links": [], - "modified": "2020-04-14 20:02:53.159827", + "modified": "2020-05-05 18:55:26.173629", "modified_by": "Administrator", "module": "HR", "name": "Salary Slip", diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index a7dcb94167..3eff738ec8 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -422,22 +422,32 @@ def get_salary_component_account(sal_comp, company_list=None): sal_comp = frappe.get_doc("Salary Component", sal_comp) if not sal_comp.get("accounts"): for d in company_list: + company_abbr = frappe.get_cached_value('Company', d, 'abbr') + + if sal_comp.type == "Earning": + account_name = "Salary" + parent_account = "Indirect Expenses - " + company_abbr + else: + account_name = "Salary Deductions" + parent_account = "Current Liabilities - " + company_abbr + sal_comp.append("accounts", { "company": d, - "default_account": create_account(d) + "default_account": create_account(account_name, d, parent_account) }) sal_comp.save() -def create_account(company): - salary_account = frappe.db.get_value("Account", "Salary - " + frappe.get_cached_value('Company', company, 'abbr')) - if not salary_account: +def create_account(account_name, company, parent_account): + company_abbr = frappe.get_cached_value('Company', company, 'abbr') + account = frappe.db.get_value("Account", account_name + " - " + company_abbr) + if not account: frappe.get_doc({ "doctype": "Account", - "account_name": "Salary", - "parent_account": "Indirect Expenses - " + frappe.get_cached_value('Company', company, 'abbr'), + "account_name": account_name, + "parent_account": parent_account, "company": company }).insert() - return salary_account + return account def make_earning_salary_component(setup=False, test_tax=False, company_list=None): data = [ @@ -683,7 +693,7 @@ def setup_test(): make_earning_salary_component(setup=True, company_list=["_Test Company"]) make_deduction_salary_component(setup=True, company_list=["_Test Company"]) - for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Attendance"]: + for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Attendance", "Additional Salary"]: frappe.db.sql("delete from `tab%s`" % dt) make_holiday_list() diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.py b/erpnext/hr/doctype/salary_structure/salary_structure.py index 5ba7f1c432..ffc16d73c2 100644 --- a/erpnext/hr/doctype/salary_structure/salary_structure.py +++ b/erpnext/hr/doctype/salary_structure/salary_structure.py @@ -153,12 +153,16 @@ def make_salary_slip(source_name, target_doc = None, employee = None, as_print = def postprocess(source, target): if employee: employee_details = frappe.db.get_value("Employee", employee, - ["employee_name", "branch", "designation", "department"], as_dict=1) + ["employee_name", "branch", "designation", "department", "payroll_cost_center"], as_dict=1) target.employee = employee target.employee_name = employee_details.employee_name target.branch = employee_details.branch target.designation = employee_details.designation target.department = employee_details.department + target.payroll_cost_center = employee_details.payroll_cost_center + if not target.payroll_cost_center and target.department: + target.payroll_cost_center = frappe.db.get_value("Department", target.department, "payroll_cost_center") + target.run_method('process_salary_structure', for_preview=for_preview) doc = get_mapped_doc("Salary Structure", source_name, { diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py index c1869f05d7..eb5311e3b8 100644 --- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py @@ -128,6 +128,7 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, do salary_structure_doc.insert() if not dont_submit: salary_structure_doc.submit() + else: salary_structure_doc = frappe.get_doc("Salary Structure", salary_structure) From f4f307c36a925ae4eb25cc0be4d9f95d353ea3b9 Mon Sep 17 00:00:00 2001 From: Marica Date: Mon, 18 May 2020 11:18:44 +0530 Subject: [PATCH 91/96] fix: Patch to set status in old serial no data (#21720) * fix: Patch to set status in old serial no data * fix: Avoid get_doc in patch * fix: fetch all values and check status in one query --- erpnext/patches.txt | 1 + erpnext/patches/v12_0/set_serial_no_status.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 erpnext/patches/v12_0/set_serial_no_status.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ebbcccc710..4ae591b54b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -684,3 +684,4 @@ execute:frappe.delete_doc_if_exists("Page", "appointment-analytic") execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True) erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation +erpnext.patches.v12_0.set_serial_no_status diff --git a/erpnext/patches/v12_0/set_serial_no_status.py b/erpnext/patches/v12_0/set_serial_no_status.py new file mode 100644 index 0000000000..4ec84ef0f9 --- /dev/null +++ b/erpnext/patches/v12_0/set_serial_no_status.py @@ -0,0 +1,17 @@ +from __future__ import unicode_literals +import frappe +from frappe.utils import getdate, nowdate + +def execute(): + frappe.reload_doc('stock', 'doctype', 'serial_no') + + for serial_no in frappe.db.sql("""select name, delivery_document_type, warranty_expiry_date from `tabSerial No` + where (status is NULL OR status='')""", as_dict = 1): + if serial_no.get("delivery_document_type"): + status = "Delivered" + elif serial_no.get("warranty_expiry_date") and getdate(serial_no.get("warranty_expiry_date")) <= getdate(nowdate()): + status = "Expired" + else: + status = "Active" + + frappe.db.set_value("Serial No", serial_no.get("name"), "status", status) \ No newline at end of file From ec8c28e48796169258b2788f78fa4c6de37eae5f Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 18 May 2020 11:27:38 +0530 Subject: [PATCH 92/96] Revert "fix(accounting): Allow 0 threshold in Tax Withholding Category" --- .../tax_withholding_category/tax_withholding_category.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 87fe3715bf..4d43919f62 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -162,7 +162,8 @@ def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_detai debit_note_amount = get_debit_note_amount(suppliers, year_start_date, year_end_date) supplier_credit_amount -= debit_note_amount - if supplier_credit_amount >= tax_details.get('threshold', 0) or supplier_credit_amount >= tax_details.get('cumulative_threshold', 0): + if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold) + or (tax_details.get('cumulative_threshold', 0) and supplier_credit_amount >= tax_details.cumulative_threshold)): if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, tds_deducted, net_total, ldc.certificate_limit): @@ -224,4 +225,4 @@ def is_valid_certificate(valid_from, valid_upto, posting_date, deducted_amount, certificate_limit > deducted_amount): valid = True - return valid + return valid \ No newline at end of file From be750096ed782bd03108f3a35bd203b171bc516d Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 18 May 2020 14:23:30 +0530 Subject: [PATCH 93/96] fix: bom incorrect price list rate for raw material if price list currency is different from company currency (#21585) * fix: bom incorrect price list rate for raw material if price list currency is different from company currency * fixed test cases * fixed base_rate calculation and added plc_conversion_rate trigger --- erpnext/manufacturing/doctype/bom/bom.js | 21 ++++++++- erpnext/manufacturing/doctype/bom/bom.json | 44 ++++++++++++++----- erpnext/manufacturing/doctype/bom/bom.py | 20 +++++++-- erpnext/manufacturing/doctype/bom/test_bom.py | 8 ++-- erpnext/patches.txt | 1 + .../update_price_list_currency_in_bom.py | 31 +++++++++++++ 6 files changed, 105 insertions(+), 20 deletions(-) create mode 100644 erpnext/patches/v12_0/update_price_list_currency_in_bom.py diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index ebfb762640..44da9cae35 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -212,6 +212,12 @@ frappe.ui.form.on("BOM", { }); }, + rm_cost_as_per: function(frm) { + if (in_list(["Valuation Rate", "Last Purchase Rate"], frm.doc.rm_cost_as_per)) { + frm.set_value("plc_conversion_rate", 1.0); + } + }, + routing: function(frm) { if (frm.doc.routing) { frappe.call({ @@ -242,7 +248,7 @@ erpnext.bom.BomController = erpnext.TransactionController.extend({ item_code: function(doc, cdt, cdn){ var scrap_items = false; var child = locals[cdt][cdn]; - if(child.doctype == 'BOM Scrap Item') { + if (child.doctype == 'BOM Scrap Item') { scrap_items = true; } @@ -252,8 +258,19 @@ erpnext.bom.BomController = erpnext.TransactionController.extend({ get_bom_material_detail(doc, cdt, cdn, scrap_items); }, + + buying_price_list: function(doc) { + this.apply_price_list(); + }, + + plc_conversion_rate: function(doc) { + if (!this.in_apply_price_list) { + this.apply_price_list(); + } + }, + conversion_factor: function(doc, cdt, cdn) { - if(frappe.meta.get_docfield(cdt, "stock_qty", cdn)) { + if (frappe.meta.get_docfield(cdt, "stock_qty", cdn)) { var item = frappe.get_doc(cdt, cdn); frappe.model.round_floats_in(item, ["qty", "conversion_factor"]); item.stock_qty = flt(item.qty * item.conversion_factor, precision("stock_qty", item)); diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 63f4f977c5..4ce0ecf3f2 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "creation": "2013-01-22 15:11:38", "doctype": "DocType", @@ -6,23 +7,25 @@ "engine": "InnoDB", "field_order": [ "item", - "quantity", - "set_rate_of_sub_assembly_item_based_on_bom", + "company", + "item_name", + "uom", "cb0", "is_active", "is_default", "allow_alternative_item", - "image", - "item_name", - "uom", - "currency_detail", - "company", + "set_rate_of_sub_assembly_item_based_on_bom", "project", + "quantity", + "image", + "currency_detail", + "currency", "conversion_rate", "column_break_12", - "currency", "rm_cost_as_per", "buying_price_list", + "price_list_currency", + "plc_conversion_rate", "section_break_21", "with_operations", "column_break_23", @@ -176,7 +179,8 @@ }, { "fieldname": "currency_detail", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Currency and Price List" }, { "fieldname": "company", @@ -324,7 +328,7 @@ }, { "fieldname": "base_scrap_material_cost", - "fieldtype": "Data", + "fieldtype": "Currency", "label": "Scrap Material Cost(Company Currency)", "no_copy": 1, "options": "Company:company:default_currency", @@ -477,13 +481,31 @@ { "fieldname": "column_break_52", "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "depends_on": "eval:doc.rm_cost_as_per=='Price List'", + "fieldname": "plc_conversion_rate", + "fieldtype": "Float", + "label": "Price List Exchange Rate" + }, + { + "allow_on_submit": 1, + "depends_on": "eval:doc.rm_cost_as_per=='Price List'", + "fieldname": "price_list_currency", + "fieldtype": "Link", + "label": "Price List Currency", + "options": "Currency", + "print_hide": 1, + "read_only": 1 } ], "icon": "fa fa-sitemap", "idx": 1, "image_field": "image", "is_submittable": 1, - "modified": "2019-11-22 14:35:12.142150", + "links": [], + "modified": "2020-05-05 14:29:32.634952", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index b1fc4deae9..6ac653e37a 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -70,11 +70,13 @@ class BOM(WebsiteGenerator): self.validate_main_item() self.validate_currency() self.set_conversion_rate() + self.set_plc_conversion_rate() self.validate_uom_is_interger() self.set_bom_material_details() self.validate_materials() self.validate_operations() self.calculate_cost() + self.update_cost(update_parent=False, from_child_bom=True, save=False) def get_context(self, context): context.parents = [{'name': 'boms', 'title': _('All BOMs') }] @@ -165,7 +167,7 @@ class BOM(WebsiteGenerator): 'rate' : rate, 'qty' : args.get("qty") or args.get("stock_qty") or 1, 'stock_qty' : args.get("qty") or args.get("stock_qty") or 1, - 'base_rate' : rate, + 'base_rate' : flt(rate) * (flt(self.conversion_rate) or 1), 'include_item_in_manufacturing': cint(args['transfer_for_manufacture']) or 0 } @@ -226,7 +228,7 @@ class BOM(WebsiteGenerator): frappe.msgprint(_("{0} not found for item {1}") .format(self.rm_cost_as_per, arg["item_code"]), alert=True) - return flt(rate) / (self.conversion_rate or 1) + return flt(rate) * flt(self.plc_conversion_rate or 1) / (self.conversion_rate or 1) def update_cost(self, update_parent=True, from_child_bom=False, save=True): if self.docstatus == 2: @@ -243,10 +245,15 @@ class BOM(WebsiteGenerator): "stock_uom": d.stock_uom, "conversion_factor": d.conversion_factor }) + if rate: d.rate = rate d.amount = flt(d.rate) * flt(d.qty) - d.db_update() + d.base_rate = flt(d.rate) * flt(self.conversion_rate) + d.base_amount = flt(d.amount) * flt(self.conversion_rate) + + if save: + d.db_update() if self.docstatus == 1: self.flags.ignore_validate_update_after_submit = True @@ -372,6 +379,13 @@ class BOM(WebsiteGenerator): elif self.conversion_rate == 1 or flt(self.conversion_rate) <= 0: self.conversion_rate = get_exchange_rate(self.currency, self.company_currency(), args="for_buying") + def set_plc_conversion_rate(self): + if self.rm_cost_as_per in ["Valuation Rate", "Last Purchase Rate"]: + self.plc_conversion_rate = 1 + elif not self.plc_conversion_rate and self.price_list_currency: + self.plc_conversion_rate = get_exchange_rate(self.price_list_currency, + self.company_currency(), args="for_buying") + def validate_materials(self): """ Validate raw material entries """ diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 45a7b935d3..3dfd03b139 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -81,13 +81,13 @@ class TestBOM(unittest.TestCase): # test amounts in selected currency self.assertEqual(bom.operating_cost, 100) - self.assertEqual(bom.raw_material_cost, 8000) - self.assertEqual(bom.total_cost, 8100) + self.assertEqual(bom.raw_material_cost, 351.68) + self.assertEqual(bom.total_cost, 451.68) # test amounts in selected currency self.assertEqual(bom.base_operating_cost, 6000) - self.assertEqual(bom.base_raw_material_cost, 480000) - self.assertEqual(bom.base_total_cost, 486000) + self.assertEqual(bom.base_raw_material_cost, 21100.80) + self.assertEqual(bom.base_total_cost, 27100.80) def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self): frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4ae591b54b..28cd21536f 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -685,3 +685,4 @@ execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True) erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation erpnext.patches.v12_0.set_serial_no_status +erpnext.patches.v12_0.update_price_list_currency_in_bom diff --git a/erpnext/patches/v12_0/update_price_list_currency_in_bom.py b/erpnext/patches/v12_0/update_price_list_currency_in_bom.py new file mode 100644 index 0000000000..f5e7b947c2 --- /dev/null +++ b/erpnext/patches/v12_0/update_price_list_currency_in_bom.py @@ -0,0 +1,31 @@ +from __future__ import unicode_literals +import frappe +from frappe.utils import getdate, flt +from erpnext.setup.utils import get_exchange_rate + +def execute(): + frappe.reload_doc("manufacturing", "doctype", "bom") + frappe.reload_doc("manufacturing", "doctype", "bom_item") + + frappe.db.sql(""" UPDATE `tabBOM`, `tabPrice List` + SET + `tabBOM`.price_list_currency = `tabPrice List`.currency, + `tabBOM`.plc_conversion_rate = 1.0 + WHERE + `tabBOM`.buying_price_list = `tabPrice List`.name AND `tabBOM`.docstatus < 2 + AND `tabBOM`.rm_cost_as_per = 'Price List' + """) + + for d in frappe.db.sql(""" + SELECT + bom.creation, bom.name, bom.price_list_currency as currency, + company.default_currency as company_currency + FROM + `tabBOM` as bom, `tabCompany` as company + WHERE + bom.company = company.name AND bom.rm_cost_as_per = 'Price List' AND + bom.price_list_currency != company.default_currency AND bom.docstatus < 2""", as_dict=1): + plc_conversion_rate = get_exchange_rate(d.currency, + d.company_currency, getdate(d.creation), "for_buying") + + frappe.db.set_value("BOM", d.name, "plc_conversion_rate", plc_conversion_rate) \ No newline at end of file From d94a38eb51bdf9ec20f4c6b0fb7926e6ac052740 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Mon, 18 May 2020 14:26:26 +0530 Subject: [PATCH 94/96] fix: make queries show searchfields (#21685) * fix: make queries show searchfields * Update erpnext/controllers/queries.py Co-authored-by: Nabin Hait * Update queries.py * Update erpnext/controllers/queries.py Co-authored-by: Nabin Hait * Update erpnext/controllers/queries.py Co-authored-by: Nabin Hait * fix: make fields string for sql Co-authored-by: Nabin Hait --- erpnext/controllers/queries.py | 66 +++++++++++++++----- erpnext/selling/doctype/customer/customer.py | 21 ++++--- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 5febfd6bf2..5fbc460a97 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -8,11 +8,14 @@ from frappe.desk.reportview import get_match_cond, get_filters_cond from frappe.utils import nowdate, getdate from collections import defaultdict from erpnext.stock.get_item_details import _get_item_tax_template +from frappe.utils import unique # searches for active employees def employee_query(doctype, txt, searchfield, start, page_len, filters): conditions = [] - return frappe.db.sql("""select name, employee_name from `tabEmployee` + fields = get_fields("Employee", ["name", "employee_name"]) + + return frappe.db.sql("""select {fields} from `tabEmployee` where status = 'Active' and docstatus < 2 and ({key} like %(txt)s @@ -24,6 +27,7 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): idx desc, name, employee_name limit %(start)s, %(page_len)s""".format(**{ + 'fields': ", ".join(fields), 'key': searchfield, 'fcond': get_filters_cond(doctype, filters, conditions), 'mcond': get_match_cond(doctype) @@ -34,9 +38,12 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters): 'page_len': page_len }) - # searches for leads which are not converted + +# searches for leads which are not converted def lead_query(doctype, txt, searchfield, start, page_len, filters): - return frappe.db.sql("""select name, lead_name, company_name from `tabLead` + fields = get_fields("Lead", ["name", "lead_name", "company_name"]) + + return frappe.db.sql("""select {fields} from `tabLead` where docstatus < 2 and ifnull(status, '') != 'Converted' and ({key} like %(txt)s @@ -50,6 +57,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters): idx desc, name, lead_name limit %(start)s, %(page_len)s""".format(**{ + 'fields': ", ".join(fields), 'key': searchfield, 'mcond':get_match_cond(doctype) }), { @@ -59,6 +67,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters): 'page_len': page_len }) + # searches for customer def customer_query(doctype, txt, searchfield, start, page_len, filters): conditions = [] @@ -69,13 +78,9 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters): else: fields = ["name", "customer_name", "customer_group", "territory"] - meta = frappe.get_meta("Customer") - searchfields = meta.get_search_fields() - searchfields = searchfields + [f for f in [searchfield or "name", "customer_name"] \ - if not f in searchfields] - fields = fields + [f for f in searchfields if not f in fields] + fields = get_fields("Customer", fields) - fields = ", ".join(fields) + searchfields = frappe.get_meta("Customer").get_search_fields() searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) return frappe.db.sql("""select {fields} from `tabCustomer` @@ -88,7 +93,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters): idx desc, name, customer_name limit %(start)s, %(page_len)s""".format(**{ - "fields": fields, + "fields": ", ".join(fields), "scond": searchfields, "mcond": get_match_cond(doctype), "fcond": get_filters_cond(doctype, filters, conditions).replace('%', '%%'), @@ -99,6 +104,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters): 'page_len': page_len }) + # searches for supplier def supplier_query(doctype, txt, searchfield, start, page_len, filters): supp_master_name = frappe.defaults.get_user_default("supp_master_name") @@ -106,7 +112,8 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters): fields = ["name", "supplier_group"] else: fields = ["name", "supplier_name", "supplier_group"] - fields = ", ".join(fields) + + fields = get_fields("Supplier", fields) return frappe.db.sql("""select {field} from `tabSupplier` where docstatus < 2 @@ -119,7 +126,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters): idx desc, name, supplier_name limit %(start)s, %(page_len)s """.format(**{ - 'field': fields, + 'field': ', '.join(fields), 'key': searchfield, 'mcond':get_match_cond(doctype) }), { @@ -129,6 +136,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters): 'page_len': page_len }) + def tax_account_query(doctype, txt, searchfield, start, page_len, filters): company_currency = erpnext.get_company_currency(filters.get('company')) @@ -153,6 +161,7 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters): return tax_accounts + def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): conditions = [] @@ -221,10 +230,12 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals "page_len": page_len }, as_dict=as_dict) + def bom(doctype, txt, searchfield, start, page_len, filters): conditions = [] + fields = get_fields("BOM", ["name", "item"]) - return frappe.db.sql("""select tabBOM.name, tabBOM.item + return frappe.db.sql("""select {fields} from tabBOM where tabBOM.docstatus=1 and tabBOM.is_active=1 @@ -234,6 +245,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters): if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), idx desc, name limit %(start)s, %(page_len)s """.format( + fields=", ".join(fields), fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'), mcond=get_match_cond(doctype).replace('%', '%%'), key=searchfield), @@ -244,13 +256,16 @@ def bom(doctype, txt, searchfield, start, page_len, filters): 'page_len': page_len or 20 }) + def get_project_name(doctype, txt, searchfield, start, page_len, filters): cond = '' if filters.get('customer'): cond = """(`tabProject`.customer = %s or ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer"))) - return frappe.db.sql("""select `tabProject`.name from `tabProject` + fields = get_fields("Project", ["name"]) + + return frappe.db.sql("""select {fields} from `tabProject` where `tabProject`.status not in ("Completed", "Cancelled") and {cond} `tabProject`.name like %(txt)s {match_cond} order by @@ -258,6 +273,7 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): idx desc, `tabProject`.name asc limit {start}, {page_len}""".format( + fields=", ".join(['`tabProject`.{0}'.format(f) for f in fields]), cond=cond, match_cond=get_match_cond(doctype), start=start, @@ -268,8 +284,10 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters): def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict): + fields = get_fields("Delivery Note", ["name", "customer", "posting_date"]) + return frappe.db.sql(""" - select `tabDelivery Note`.name, `tabDelivery Note`.customer, `tabDelivery Note`.posting_date + select %(fields)s from `tabDelivery Note` where `tabDelivery Note`.`%(key)s` like %(txt)s and `tabDelivery Note`.docstatus = 1 @@ -284,6 +302,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, ) %(mcond)s order by `tabDelivery Note`.`%(key)s` asc limit %(start)s, %(page_len)s """ % { + "fields": ", ".join(["`tabDelivery Note`.{0}".format(f) for f in fields]), "key": searchfield, "fcond": get_filters_cond(doctype, filters, []), "mcond": get_match_cond(doctype), @@ -349,6 +368,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): order by expiry_date, name desc limit %(start)s, %(page_len)s""".format(cond, match_conditions=get_match_cond(doctype)), args) + def get_account_list(doctype, txt, searchfield, start, page_len, filters): filter_list = [] @@ -371,6 +391,7 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters): fields = ["name", "parent_account"], limit_start=start, limit_page_length=page_len, as_list=True) + def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date from `tabBlanket Order` bo, `tabBlanket Order Item` boi @@ -385,6 +406,7 @@ def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters): company = frappe.db.escape(filters.get("company")) )) + @frappe.whitelist() def get_income_account(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond @@ -490,6 +512,7 @@ def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(query, filters) + @frappe.whitelist() def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters): item_filters = [ @@ -507,6 +530,7 @@ def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters) ) return item_manufacturers + @frappe.whitelist() def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters): query = """ @@ -520,6 +544,7 @@ def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(query, filters) + @frappe.whitelist() def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): query = """ @@ -533,6 +558,7 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(query, filters) + @frappe.whitelist() def get_tax_template(doctype, txt, searchfield, start, page_len, filters): @@ -556,3 +582,13 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters): taxes = _get_item_tax_template(args, taxes, for_validate=True) return [(d,) for d in set(taxes)] + + +def get_fields(doctype, fields=[]): + meta = frappe.get_meta(doctype) + fields.extend(meta.get_search_fields()) + + if meta.title_field and not meta.title_field.strip() in fields: + fields.insert(1, meta.title_field.strip()) + + return unique(fields) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 3d172ac7a2..a6889e080d 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -337,11 +337,15 @@ def get_loyalty_programs(doc): return lp_details def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None): + from erpnext.controllers.queries import get_fields + if frappe.db.get_default("cust_master_name") == "Customer Name": fields = ["name", "customer_group", "territory"] else: fields = ["name", "customer_name", "customer_group", "territory"] + fields = get_fields("Customer", fields) + match_conditions = build_match_conditions("Customer") match_conditions = "and {}".format(match_conditions) if match_conditions else "" @@ -349,14 +353,17 @@ def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None): filter_conditions = get_filters_cond(doctype, filters, []) match_conditions += "{}".format(filter_conditions) - return frappe.db.sql("""select %s from `tabCustomer` where docstatus < 2 - and (%s like %s or customer_name like %s) - {match_conditions} + return frappe.db.sql(""" + select %s + from `tabCustomer` + where docstatus < 2 + and (%s like %s or customer_name like %s) + {match_conditions} order by - case when name like %s then 0 else 1 end, - case when customer_name like %s then 0 else 1 end, - name, customer_name limit %s, %s""".format(match_conditions=match_conditions) % - (", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"), + case when name like %s then 0 else 1 end, + case when customer_name like %s then 0 else 1 end, + name, customer_name limit %s, %s + """.format(match_conditions=match_conditions) % (", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"), ("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len)) From c734db5d4505fc08c2a55d1c5be53a10c669e8cb Mon Sep 17 00:00:00 2001 From: Marica Date: Mon, 18 May 2020 14:38:57 +0530 Subject: [PATCH 95/96] fix: Validate Filters in Sales Funnel. (#21761) * fix: Validate Filters in Sales Funnel. * fix: Style fixes --- .../selling/page/sales_funnel/sales_funnel.js | 4 ++++ .../selling/page/sales_funnel/sales_funnel.py | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.js b/erpnext/selling/page/sales_funnel/sales_funnel.js index 85c0cd8bf0..e3d0a55c3a 100644 --- a/erpnext/selling/page/sales_funnel/sales_funnel.js +++ b/erpnext/selling/page/sales_funnel/sales_funnel.js @@ -90,6 +90,10 @@ erpnext.SalesFunnel = class SalesFunnel { get_data(btn) { var me = this; + if (!this.company) { + frappe.throw(__("Please Select a Company.")); + } + const method_map = { "sales_funnel": "erpnext.selling.page.sales_funnel.sales_funnel.get_funnel_data", "opp_by_lead_source": "erpnext.selling.page.sales_funnel.sales_funnel.get_opp_by_lead_source", diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.py b/erpnext/selling/page/sales_funnel/sales_funnel.py index d62e2093c6..dba24ef5b0 100644 --- a/erpnext/selling/page/sales_funnel/sales_funnel.py +++ b/erpnext/selling/page/sales_funnel/sales_funnel.py @@ -8,14 +8,23 @@ from frappe import _ from erpnext.accounts.report.utils import convert import pandas as pd +def validate_filters(from_date, to_date, company): + if from_date and to_date and (from_date >= to_date): + frappe.throw(_("To Date must be greater than From Date")) + + if not company: + frappe.throw(_("Please Select a Company")) + @frappe.whitelist() def get_funnel_data(from_date, to_date, company): + validate_filters(from_date, to_date, company) + active_leads = frappe.db.sql("""select count(*) from `tabLead` where (date(`modified`) between %s and %s) and status != "Do Not Contact" and company=%s""", (from_date, to_date, company))[0][0] active_leads += frappe.db.sql("""select count(distinct contact.name) from `tabContact` contact - left join `tabDynamic Link` dl on (dl.parent=contact.name) where dl.link_doctype='Customer' + left join `tabDynamic Link` dl on (dl.parent=contact.name) where dl.link_doctype='Customer' and (date(contact.modified) between %s and %s) and status != "Passive" """, (from_date, to_date))[0][0] opportunities = frappe.db.sql("""select count(*) from `tabOpportunity` @@ -38,6 +47,8 @@ def get_funnel_data(from_date, to_date, company): @frappe.whitelist() def get_opp_by_lead_source(from_date, to_date, company): + validate_filters(from_date, to_date, company) + opportunities = frappe.get_all("Opportunity", filters=[['status', 'in', ['Open', 'Quotation', 'Replied']], ['company', '=', company], ['transaction_date', 'Between', [from_date, to_date]]], fields=['currency', 'sales_stage', 'opportunity_amount', 'probability', 'source']) if opportunities: @@ -68,11 +79,13 @@ def get_opp_by_lead_source(from_date, to_date, company): @frappe.whitelist() def get_pipeline_data(from_date, to_date, company): + validate_filters(from_date, to_date, company) + opportunities = frappe.get_all("Opportunity", filters=[['status', 'in', ['Open', 'Quotation', 'Replied']], ['company', '=', company], ['transaction_date', 'Between', [from_date, to_date]]], fields=['currency', 'sales_stage', 'opportunity_amount', 'probability']) if opportunities: default_currency = frappe.get_cached_value('Global Defaults', 'None', 'default_currency') - + cp_opportunities = [dict(x, **{'compound_amount': (convert(x['opportunity_amount'], x['currency'], default_currency, to_date) * x['probability']/100)}) for x in opportunities] df = pd.DataFrame(cp_opportunities).groupby(['sales_stage'], as_index=True).agg({'compound_amount': 'sum'}).to_dict() From 9aae6f479398847b18bb231cee4ce35dc036420d Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 18 May 2020 18:03:11 +0530 Subject: [PATCH 96/96] feat: accounting dashboard + desk --- erpnext/accounts/dashboard_fixtures.py | 131 +++++++++++++----- .../desk_page/accounting/accounting.json | 9 +- erpnext/patches.txt | 1 + .../selling/desk_page/selling/selling.json | 85 ------------ 4 files changed, 108 insertions(+), 118 deletions(-) delete mode 100644 erpnext/selling/desk_page/selling/selling.json diff --git a/erpnext/accounts/dashboard_fixtures.py b/erpnext/accounts/dashboard_fixtures.py index 8b9eca5b2d..214e467b5c 100644 --- a/erpnext/accounts/dashboard_fixtures.py +++ b/erpnext/accounts/dashboard_fixtures.py @@ -3,9 +3,9 @@ import frappe import json -from frappe.utils import nowdate, add_months +from frappe.utils import nowdate, add_months, get_date_str from frappe import _ -from erpnext.accounts.utils import get_fiscal_year +from erpnext.accounts.utils import get_fiscal_year, get_account_name def get_company_for_dashboards(): company = frappe.defaults.get_defaults().company @@ -27,25 +27,28 @@ def get_data(): def get_dashboards(): return [{ "name": "Accounts Dashboard", - "dashboard_name": "Accounts", + "dashboard_name": "Accounts Dashboard", "doctype": "Dashboard", "charts": [ { "chart": "Profit and Loss" , "width": "Full"}, - { "chart": "Incoming Bills"}, - { "chart": "Outgoing Bills"}, - { "chart": "Accounts Receivable Ageing"}, - { "chart": "Accounts Payable Ageing"}, + { "chart": "Incoming Bills (Purchase Invoice)", "width": "Half"}, + { "chart": "Outgoing Bills (Sales Invoice)", "width": "Half"}, + { "chart": "Accounts Receivable Ageing", "width": "Half"}, + { "chart": "Accounts Payable Ageing", "width": "Half"}, { "chart": "Budget Variance", "width": "Full"}, - { "chart": "Bank Balance", "width": "Full"}, + { "chart": "Bank Balance", "width": "Full"} ], "cards": [ - {"card": "Total Payment Received"} + {"card": "Total Outgoing Bills"}, + {"card": "Total Incoming Bills"}, + {"card": "Total Incoming Payment"}, + {"card": "Total Outgoing Payment"} ] }] def get_charts(): company = frappe.get_doc("Company", get_company_for_dashboards()) - bank_account = company.default_bank_account or get_account("Bank", company.name) + bank_account = company.default_bank_account or get_account_name("Bank", company=company.name) fiscal_year = get_fiscal_year(date=nowdate()) default_cost_center = company.cost_center @@ -58,50 +61,53 @@ def get_charts(): "filters_json": json.dumps({ "company": company.name, "filter_based_on": "Date Range", - "period_start_date": add_months(nowdate(), -4), - "period_end_date": nowdate(), + "period_start_date": get_date_str(fiscal_year[1]), + "period_end_date": get_date_str(fiscal_year[2]), "periodicity": "Monthly", "include_default_book_entries": 1 - }), + }), "type": "Bar", 'timeseries': 0, "chart_type": "Report", "chart_name": _("Profit and Loss"), - "is_custom": 1 + "is_custom": 1, + "is_public": 1 }, { "doctype": "Dashboard Chart", "time_interval": "Monthly", - "name": "Incoming Bills", + "name": "Incoming Bills (Purchase Invoice)", "chart_name": _("Incoming Bills (Purchase Invoice)"), "timespan": "Last Year", "color": "#a83333", - "value_based_on": "base_grand_total", - "filters_json": json.dumps({}), + "value_based_on": "base_net_total", + "filters_json": json.dumps({"docstatus": 1}), "chart_type": "Sum", "timeseries": 1, "based_on": "posting_date", "owner": "Administrator", "document_type": "Purchase Invoice", "type": "Bar", - "width": "Half" + "width": "Half", + "is_public": 1 }, { "doctype": "Dashboard Chart", - "name": "Outgoing Bills", + "name": "Outgoing Bills (Sales Invoice)", "time_interval": "Monthly", "chart_name": _("Outgoing Bills (Sales Invoice)"), "timespan": "Last Year", "color": "#7b933d", - "value_based_on": "base_grand_total", - "filters_json": json.dumps({}), + "value_based_on": "base_net_total", + "filters_json": json.dumps({"docstatus": 1}), "chart_type": "Sum", "timeseries": 1, "based_on": "posting_date", "owner": "Administrator", "document_type": "Sales Invoice", "type": "Bar", - "width": "Half" + "width": "Half", + "is_public": 1 }, { "doctype": "Dashboard Charts", @@ -121,7 +127,8 @@ def get_charts(): 'timeseries': 0, "chart_type": "Report", "chart_name": _("Accounts Receivable Ageing"), - "is_custom": 1 + "is_custom": 1, + "is_public": 1 }, { "doctype": "Dashboard Charts", @@ -141,7 +148,8 @@ def get_charts(): 'timeseries': 0, "chart_type": "Report", "chart_name": _("Accounts Payable Ageing"), - "is_custom": 1 + "is_custom": 1, + "is_public": 1 }, { "doctype": "Dashboard Charts", @@ -159,7 +167,8 @@ def get_charts(): "timeseries": 0, "chart_type": "Report", "chart_name": _("Budget Variance"), - "is_custom": 1 + "is_custom": 1, + "is_public": 1 }, { "doctype": "Dashboard Charts", @@ -167,29 +176,89 @@ def get_charts(): "time_interval": "Quarterly", "chart_name": "Bank Balance", "timespan": "Last Year", - "filters_json": json.dumps({"company": company.name, "account": bank_account}), + "filters_json": json.dumps({ + "company": company.name, + "account": bank_account + }), "source": "Account Balance Timeline", "chart_type": "Custom", "timeseries": 1, "owner": "Administrator", "type": "Line", - "width": "Half" + "width": "Half", + "is_public": 1 }, ] def get_number_cards(): + fiscal_year = get_fiscal_year(date=nowdate()) + year_start_date = get_date_str(fiscal_year[1]) + year_end_date = get_date_str(fiscal_year[2]) return [ { "doctype": "Number Card", "document_type": "Payment Entry", - "name": "Total Payment Received", - "filters_json": json.dumps([]), - "label": _("Total Payment Received"), + "name": "Total Incoming Payment", + "filters_json": json.dumps([ + ['Payment Entry', 'docstatus', '=', 1], + ['Payment Entry', 'posting_date', 'between', [year_start_date, year_end_date]], + ['Payment Entry', 'payment_type', '=', 'Receive'] + ]), + "label": _("Total Incoming Payment"), "function": "Sum", "aggregate_function_based_on": "base_received_amount", "is_public": 1, "is_custom": 1, "show_percentage_stats": 1, - "stats_time_interval": "Daily" + "stats_time_interval": "Monthly" + }, + { + "doctype": "Number Card", + "document_type": "Payment Entry", + "name": "Total Outgoing Payment", + "filters_json": json.dumps([ + ['Payment Entry', 'docstatus', '=', 1], + ['Payment Entry', 'posting_date', 'between', [year_start_date, year_end_date]], + ['Payment Entry', 'payment_type', '=', 'Pay'] + ]), + "label": _("Total Outgoing Payment"), + "function": "Sum", + "aggregate_function_based_on": "base_paid_amount", + "is_public": 1, + "is_custom": 1, + "show_percentage_stats": 1, + "stats_time_interval": "Monthly" + }, + { + "doctype": "Number Card", + "document_type": "Sales Invoice", + "name": "Total Outgoing Bills", + "filters_json": json.dumps([ + ['Sales Invoice', 'docstatus', '=', 1], + ['Sales Invoice', 'posting_date', 'between', [year_start_date, year_end_date]] + ]), + "label": _("Total Outgoing Bills"), + "function": "Sum", + "aggregate_function_based_on": "base_net_total", + "is_public": 1, + "is_custom": 1, + "show_percentage_stats": 1, + "stats_time_interval": "Monthly" + }, + { + "doctype": "Number Card", + "document_type": "Purchase Invoice", + "name": "Total Incoming Bills", + "filters_json": json.dumps([ + ['Purchase Invoice', 'docstatus', '=', 1], + ['Purchase Invoice', 'posting_date', 'between', [year_start_date, year_end_date]] + ]), + "label": _("Total Incoming Bills"), + "function": "Sum", + "aggregate_function_based_on": "base_net_total", + "is_public": 1, + "is_custom": 1, + "show_percentage_stats": 1, + "stats_time_interval": "Monthly" } ] diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json index 682eb8fced..a783b1d0db 100644 --- a/erpnext/accounts/desk_page/accounting/accounting.json +++ b/erpnext/accounts/desk_page/accounting/accounting.json @@ -82,7 +82,12 @@ } ], "category": "Modules", - "charts": [], + "charts": [ + { + "chart_name": "Profit and Loss", + "label": "Profit and Loss" + } + ], "creation": "2020-03-02 15:41:59.515192", "developer_mode_only": 0, "disable_user_customization": 0, @@ -92,7 +97,7 @@ "idx": 0, "is_standard": 1, "label": "Accounting", - "modified": "2020-05-14 22:28:25.262409", + "modified": "2020-05-18 17:27:26.882340", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4ae591b54b..e88e05dd30 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -685,3 +685,4 @@ execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True) erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation erpnext.patches.v12_0.set_serial_no_status +execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts') \ No newline at end of file diff --git a/erpnext/selling/desk_page/selling/selling.json b/erpnext/selling/desk_page/selling/selling.json deleted file mode 100644 index a20806b264..0000000000 --- a/erpnext/selling/desk_page/selling/selling.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Items and Pricing", - "links": "[\n {\n \"description\": \"All Products or Services.\",\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Price List\"\n ],\n \"description\": \"Multiple Item prices.\",\n \"label\": \"Item Price\",\n \"name\": \"Item Price\",\n \"onboard\": 1,\n \"route\": \"#Report/Item Price\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Price List master.\",\n \"label\": \"Price List\",\n \"name\": \"Price List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tree of Item Groups.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Item Group\",\n \"link\": \"Tree/Item Group\",\n \"name\": \"Item Group\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Bundle items at time of sale.\",\n \"label\": \"Product Bundle\",\n \"name\": \"Product Bundle\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for applying different promotional schemes.\",\n \"label\": \"Promotional Scheme\",\n \"name\": \"Promotional Scheme\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Rules for applying pricing and discount.\",\n \"label\": \"Pricing Rule\",\n \"name\": \"Pricing Rule\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Rules for adding shipping costs.\",\n \"label\": \"Shipping Rule\",\n \"name\": \"Shipping Rule\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Define coupon codes.\",\n \"label\": \"Coupon Code\",\n \"name\": \"Coupon Code\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Settings", - "links": "[\n {\n \"description\": \"Default settings for selling transactions.\",\n \"label\": \"Selling Settings\",\n \"name\": \"Selling Settings\",\n \"settings\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Template of terms or contract.\",\n \"label\": \"Terms and Conditions Template\",\n \"name\": \"Terms and Conditions\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Tax template for selling transactions.\",\n \"label\": \"Sales Taxes and Charges Template\",\n \"name\": \"Sales Taxes and Charges Template\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Track Leads by Lead Source.\",\n \"label\": \"Lead Source\",\n \"name\": \"Lead Source\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Customer Group Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Customer Group\",\n \"link\": \"Tree/Customer Group\",\n \"name\": \"Customer Group\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Contacts.\",\n \"label\": \"Contact\",\n \"name\": \"Contact\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"All Addresses.\",\n \"label\": \"Address\",\n \"name\": \"Address\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Manage Territory Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Territory\",\n \"link\": \"Tree/Territory\",\n \"name\": \"Territory\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Sales campaigns.\",\n \"label\": \"Campaign\",\n \"name\": \"Campaign\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Other Reports", - "links": "[\n {\n \"dependencies\": [\n \"Lead\"\n ],\n \"doctype\": \"Lead\",\n \"is_query_report\": true,\n \"label\": \"Lead Details\",\n \"name\": \"Lead Details\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Address\"\n ],\n \"doctype\": \"Address\",\n \"is_query_report\": true,\n \"label\": \"Customer Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"route_options\": {\n \"party_type\": \"Customer\"\n },\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Search\",\n \"name\": \"BOM Search\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Available Stock for Packing Items\",\n \"name\": \"Available Stock for Packing Items\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Pending SO Items For Purchase Request\",\n \"name\": \"Pending SO Items For Purchase Request\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customer Credit Balance\",\n \"name\": \"Customer Credit Balance\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Customers Without Any Sales Transactions\",\n \"name\": \"Customers Without Any Sales Transactions\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"is_query_report\": true,\n \"label\": \"Sales Partners Commission\",\n \"name\": \"Sales Partners Commission\",\n \"type\": \"report\"\n }\n]" - }, - { - "hidden": 0, - "label": "Sales", - "links": "[\n {\n \"description\": \"Customer Database.\",\n \"label\": \"Customer\",\n \"name\": \"Customer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Quotes to Leads or Customers.\",\n \"label\": \"Quotation\",\n \"name\": \"Quotation\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Confirmed orders from Customers.\",\n \"label\": \"Sales Order\",\n \"name\": \"Sales Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Invoices for Costumers.\",\n \"label\": \"Sales Invoice\",\n \"name\": \"Sales Invoice\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Blanket Orders from Costumers.\",\n \"label\": \"Blanket Order\",\n \"name\": \"Blanket Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"description\": \"Manage Sales Partners.\",\n \"label\": \"Sales Partner\",\n \"name\": \"Sales Partner\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"description\": \"Manage Sales Person Tree.\",\n \"icon\": \"fa fa-sitemap\",\n \"label\": \"Sales Person\",\n \"link\": \"Tree/Sales Person\",\n \"name\": \"Sales Person\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Key Reports", - "links": "[\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Analytics\",\n \"name\": \"Sales Analytics\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Sales Funnel\",\n \"name\": \"sales-funnel\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Customer\"\n ],\n \"doctype\": \"Customer\",\n \"icon\": \"fa fa-bar-chart\",\n \"is_query_report\": true,\n \"label\": \"Customer Acquisition and Loyalty\",\n \"name\": \"Customer Acquisition and Loyalty\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Inactive Customers\",\n \"name\": \"Inactive Customers\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Ordered Items To Be Delivered\",\n \"name\": \"Ordered Items To Be Delivered\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Person-wise Transaction Summary\",\n \"name\": \"Sales Person-wise Transaction Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Item-wise Sales History\",\n \"name\": \"Item-wise Sales History\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Quotation\"\n ],\n \"doctype\": \"Quotation\",\n \"is_query_report\": true,\n \"label\": \"Quotation Trends\",\n \"name\": \"Quotation Trends\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Sales Order\"\n ],\n \"doctype\": \"Sales Order\",\n \"is_query_report\": true,\n \"label\": \"Sales Order Trends\",\n \"name\": \"Sales Order Trends\",\n \"type\": \"report\"\n }\n]" - } - ], - "category": "Modules", - "charts": [ - { - "chart_name": "Income", - "label": "Income" - } - ], - "creation": "2020-01-28 11:49:12.092882", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "icon": "", - "idx": 0, - "is_standard": 1, - "label": "Selling", - "modified": "2020-04-01 11:28:51.047373", - "modified_by": "Administrator", - "module": "Selling", - "name": "Selling", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "shortcuts": [ - { - "label": "Sales Invoice", - "link_to": "Sales Invoice", - "type": "DocType" - }, - { - "label": "Sales Order", - "link_to": "Sales Order", - "type": "DocType" - }, - { - "label": "Quotation", - "link_to": "Quotation", - "type": "DocType" - }, - { - "label": "Delivery Note", - "link_to": "Delivery Note", - "type": "DocType" - }, - { - "label": "Accounts Receivable", - "link_to": "Accounts Receivable", - "type": "Report" - }, - { - "label": "Sales Register", - "link_to": "Sales Register", - "type": "Report" - } - ] -} \ No newline at end of file