From 3441d123b68497a90e5c4b6a9669c76f01a63f6f Mon Sep 17 00:00:00 2001 From: anoop Date: Mon, 13 Apr 2020 11:36:06 +0530 Subject: [PATCH 01/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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 79b01838c37f6fd6d1efa098b5fa353c2a842048 Mon Sep 17 00:00:00 2001 From: anoop Date: Thu, 16 Apr 2020 12:52:03 +0530 Subject: [PATCH 25/78] 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 26/78] 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 27/78] 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 28/78] 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 29/78] 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 30/78] 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 31/78] 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 32/78] 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 33/78] 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 34/78] 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 35/78] 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 36/78] 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 37/78] 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 38/78] 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 39/78] 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 40/78] 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 41/78] 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 42/78] 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 43/78] 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 da41724d9da94acf82c44912b3352535ca304fb9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 8 May 2020 15:12:08 +0530 Subject: [PATCH 44/78] feat: save shipping address to woocommerce customer --- .../connectors/woocommerce_connection.py | 57 +++++++++++-------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py index 618865200c..54fa6085d0 100644 --- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py +++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py @@ -49,12 +49,13 @@ def _order(*args, **kwargs): if event == "created": sys_lang = frappe.get_single("System Settings").language or 'en' raw_billing_data = order.get("billing") + raw_shipping_data = order.get("shipping") customer_name = raw_billing_data.get("first_name") + " " + raw_billing_data.get("last_name") - link_customer_and_address(raw_billing_data, customer_name) + link_customer_and_address(raw_billing_data, raw_shipping_data, customer_name) link_items(order.get("line_items"), woocommerce_settings, sys_lang) create_sales_order(order, woocommerce_settings, customer_name, sys_lang) -def link_customer_and_address(raw_billing_data, customer_name): +def link_customer_and_address(raw_billing_data, raw_shipping_data, customer_name): customer_woo_com_email = raw_billing_data.get("email") customer_exists = frappe.get_value("Customer", {"woocommerce_email": customer_woo_com_email}) if not customer_exists: @@ -68,38 +69,48 @@ def link_customer_and_address(raw_billing_data, customer_name): customer.customer_name = customer_name customer.woocommerce_email = customer_woo_com_email customer.flags.ignore_mandatory = True - customer.save() + customer.save() if customer_exists: frappe.rename_doc("Customer", old_name, customer_name) - address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email}) + billing_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Billing"}) + shipping_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Shipping"}) else: - address = frappe.new_doc("Address") + billing_address = create_address(raw_billing_data, customer, "Billing") + shipping_address = create_address(raw_shipping_data, customer, "Shipping") - address.address_line1 = raw_billing_data.get("address_1", "Not Provided") - address.address_line2 = raw_billing_data.get("address_2", "Not Provided") - address.city = raw_billing_data.get("city", "Not Provided") - address.woocommerce_email = customer_woo_com_email - address.address_type = "Billing" - address.country = frappe.get_value("Country", {"code": raw_billing_data.get("country", "IN").lower()}) - address.state = raw_billing_data.get("state") - address.pincode = raw_billing_data.get("postcode") - address.phone = raw_billing_data.get("phone") - address.email_id = customer_woo_com_email + if customer_exists: + rename_address(billing_address, customer) + rename_address(shipping_address, customer) + +def create_address(raw_data, customer, address_type): + address = frappe.new_doc("Address") + + address.address_line1 = raw_data.get("address_1", "Not Provided") + address.address_line2 = raw_data.get("address_2", "Not Provided") + address.city = raw_data.get("city", "Not Provided") + address.woocommerce_email = customer.woocommerce_email + address.address_type = address_type + address.country = frappe.get_value("Country", {"code": raw_data.get("country", "IN").lower()}) + address.state = raw_data.get("state") + address.pincode = raw_data.get("postcode") + address.phone = raw_data.get("phone") + address.email_id = customer.woocommerce_email address.append("links", { "link_doctype": "Customer", "link_name": customer.customer_name }) + address.flags.ignore_mandatory = True address = address.save() - if customer_exists: - old_address_title = address.name - new_address_title = customer.customer_name + "-billing" - address.address_title = customer.customer_name - address.save() +def rename_address(address, customer): + old_address_title = address.name + new_address_title = customer.customer_name + "-" + address.address_type + address.address_title = customer.customer_name + address.save() - frappe.rename_doc("Address", old_address_title, new_address_title) + frappe.rename_doc("Address", old_address_title, new_address_title) def link_items(items_list, woocommerce_settings, sys_lang): for item_data in items_list: @@ -111,7 +122,7 @@ def link_items(items_list, woocommerce_settings, sys_lang): else: #Create Item item = frappe.new_doc("Item") - + item.item_name = item_data.get("name") item.item_code = _("woocommerce - {0}", sys_lang).format(item_data.get("product_id")) item.woocommerce_id = item_data.get("product_id") @@ -171,7 +182,7 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l add_tax_details(new_sales_order, order.get("shipping_tax"), "Shipping Tax", woocommerce_settings.f_n_f_account) add_tax_details(new_sales_order, order.get("shipping_total"), "Shipping Total", woocommerce_settings.f_n_f_account) - + def add_tax_details(sales_order, price, desc, tax_account_head): sales_order.append("taxes", { "charge_type":"Actual", From a788ab281e8daf24d57ede10325b0f88966d086d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 8 May 2020 15:13:36 +0530 Subject: [PATCH 45/78] fix: typo in function name --- erpnext/tests/test_woocommerce.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/tests/test_woocommerce.py b/erpnext/tests/test_woocommerce.py index ce0f47d685..df715ab202 100644 --- a/erpnext/tests/test_woocommerce.py +++ b/erpnext/tests/test_woocommerce.py @@ -24,7 +24,7 @@ class TestWoocommerce(unittest.TestCase): woo_settings.creation_user = "Administrator" woo_settings.save(ignore_permissions=True) - def test_sales_order_for_woocommerece(self): + def test_sales_order_for_woocommerce(self): frappe.flags.woocomm_test_order_data = {"id":75,"parent_id":0,"number":"74","order_key":"wc_order_5aa1281c2dacb","created_via":"checkout","version":"3.3.3","status":"processing","currency":"INR","date_created":"2018-03-08T12:10:04","date_created_gmt":"2018-03-08T12:10:04","date_modified":"2018-03-08T12:10:04","date_modified_gmt":"2018-03-08T12:10:04","discount_total":"0.00","discount_tax":"0.00","shipping_total":"150.00","shipping_tax":"0.00","cart_tax":"0.00","total":"649.00","total_tax":"0.00","prices_include_tax":False,"customer_id":12,"customer_ip_address":"103.54.99.5","customer_user_agent":"mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36","customer_note":"","billing":{"first_name":"Tony","last_name":"Stark","company":"Woocommerce","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN","email":"tony@gmail.com","phone":"123457890"},"shipping":{"first_name":"Tony","last_name":"Stark","company":"","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN"},"payment_method":"cod","payment_method_title":"Cash on delivery","transaction_id":"","date_paid":"","date_paid_gmt":"","date_completed":"","date_completed_gmt":"","cart_hash":"8e76b020d5790066496f244860c4703f","meta_data":[],"line_items":[{"id":80,"name":"Marvel","product_id":56,"variation_id":0,"quantity":1,"tax_class":"","subtotal":"499.00","subtotal_tax":"0.00","total":"499.00","total_tax":"0.00","taxes":[],"meta_data":[],"sku":"","price":499}],"tax_lines":[],"shipping_lines":[{"id":81,"method_title":"Flat rate","method_id":"flat_rate:1","total":"150.00","total_tax":"0.00","taxes":[],"meta_data":[{"id":623,"key":"Items","value":"Marvel × 1"}]}],"fee_lines":[],"coupon_lines":[],"refunds":[]} order() From e29283630b5b36e7d2eecbeff29726826d5eaba2 Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Fri, 8 May 2020 17:45:08 +0800 Subject: [PATCH 46/78] 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 47/78] 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 0defefda92ee28158ceb54d8cd1b639e76df94fc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 May 2020 12:14:46 +0530 Subject: [PATCH 48/78] 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 cdeb897fff6613bc0dd103638e0f7fdb3a4f82bd Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 12 May 2020 17:23:15 +0530 Subject: [PATCH 49/78] 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 8bbac6defc464dc6a7ad4b71675b3cefe4064225 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 14 May 2020 22:57:11 +0530 Subject: [PATCH 50/78] 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 49dbbdc4cb2a7b8c385cca7b92684c7ab6c5e606 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 16 May 2020 04:59:43 +0530 Subject: [PATCH 51/78] 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 52/78] 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 fc514ba940f4017fdf569ee558f076571143a9de Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Sat, 16 May 2020 19:00:00 +0530 Subject: [PATCH 53/78] feat: save contact to woocommerce --- .../connectors/woocommerce_connection.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py index 54fa6085d0..e4593a02b9 100644 --- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py +++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py @@ -78,11 +78,39 @@ def link_customer_and_address(raw_billing_data, raw_shipping_data, customer_name else: billing_address = create_address(raw_billing_data, customer, "Billing") shipping_address = create_address(raw_shipping_data, customer, "Shipping") + contact = create_contact(raw_billing_data, customer) if customer_exists: rename_address(billing_address, customer) rename_address(shipping_address, customer) +def create_contact(data, customer): + email = data.get("email", None) + phone = data.get("phone", None) + + if not email and not phone: + return + + contact = frappe.new_doc("Contact") + contact.first_name = data.get("first_name") + contact.last_name = data.get("last_name") + contact.is_primary_contact = 1 + contact.is_billing_contact = 1 + + if phone: + contact.add_phone(phone, is_primary_mobile_no=1, is_primary_phone=1) + + if email: + contact.add_email(email, is_primary=1) + + contact.append("links", { + "link_doctype": "Customer", + "link_name": customer.customer_name + }) + + contact.flags.ignore_mandatory = True + contact.save() + def create_address(raw_data, customer, address_type): address = frappe.new_doc("Address") @@ -102,7 +130,7 @@ def create_address(raw_data, customer, address_type): }) address.flags.ignore_mandatory = True - address = address.save() + address.save() def rename_address(address, customer): old_address_title = address.name From 180bda76186a65ab68c70abd06d66d1f586d0f6a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Sat, 16 May 2020 19:20:55 +0530 Subject: [PATCH 54/78] refactor: return address and contact objects --- .../erpnext_integrations/connectors/woocommerce_connection.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py index e4593a02b9..1e422db828 100644 --- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py +++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py @@ -111,6 +111,8 @@ def create_contact(data, customer): contact.flags.ignore_mandatory = True contact.save() + return contact + def create_address(raw_data, customer, address_type): address = frappe.new_doc("Address") @@ -132,6 +134,8 @@ def create_address(raw_data, customer, address_type): address.flags.ignore_mandatory = True address.save() + return address + def rename_address(address, customer): old_address_title = address.name new_address_title = customer.customer_name + "-" + address.address_type From d69e3eb5d79bd3276c83713c25d88e35c38f038f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Sat, 16 May 2020 19:37:03 +0530 Subject: [PATCH 55/78] feat: use name instead of customer name to link address and contact --- .../connectors/woocommerce_connection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py index 1e422db828..6d379f6ed5 100644 --- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py +++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py @@ -105,7 +105,7 @@ def create_contact(data, customer): contact.append("links", { "link_doctype": "Customer", - "link_name": customer.customer_name + "link_name": customer.name }) contact.flags.ignore_mandatory = True @@ -128,7 +128,7 @@ def create_address(raw_data, customer, address_type): address.email_id = customer.woocommerce_email address.append("links", { "link_doctype": "Customer", - "link_name": customer.customer_name + "link_name": customer.name }) address.flags.ignore_mandatory = True @@ -138,7 +138,7 @@ def create_address(raw_data, customer, address_type): def rename_address(address, customer): old_address_title = address.name - new_address_title = customer.customer_name + "-" + address.address_type + new_address_title = customer.name + "-" + address.address_type address.address_title = customer.customer_name address.save() 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 56/78] 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 57/78] 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 58/78] 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 59/78] 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 60/78] 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 61/78] 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 62/78] 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 63/78] 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 64/78] 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 65/78] 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 66/78] 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 34f4a2398b7734fac40f8290ade81d6c914c38b1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 18 Apr 2020 18:59:32 +0530 Subject: [PATCH 67/78] feat: manufacturing dashboards --- erpnext/config/manufacturing.py | 14 +- .../manufacturing/manufacturing.json | 31 ++- .../doctype/downtime_entry/__init__.py | 0 .../doctype/downtime_entry/downtime_entry.js | 8 + .../downtime_entry/downtime_entry.json | 120 +++++++++ .../doctype/downtime_entry/downtime_entry.py | 13 + .../downtime_entry/test_downtime_entry.py | 10 + .../doctype/job_card/job_card.json | 13 +- .../doctype/work_order/work_order.json | 25 +- .../doctype/work_order/work_order.py | 27 +- .../report/downtime_analysis/__init__.py | 0 .../downtime_analysis/downtime_analysis.js | 28 ++ .../downtime_analysis/downtime_analysis.json | 31 +++ .../downtime_analysis/downtime_analysis.py | 96 +++++++ .../report/job_card_summary/__init__.py | 0 .../job_card_summary/job_card_summary.js | 52 ++++ .../job_card_summary/job_card_summary.json | 34 +++ .../job_card_summary/job_card_summary.py | 197 ++++++++++++++ .../production_analytics.py | 13 +- .../quality_inspection_summary/__init__.py | 0 .../quality_inspection_summary.js | 40 +++ .../quality_inspection_summary.json | 32 +++ .../quality_inspection_summary.py | 133 ++++++++++ .../report/work_order_summary/__init__.py | 0 .../work_order_summary/work_order_summary.js | 58 ++++ .../work_order_summary.json | 31 +++ .../work_order_summary/work_order_summary.py | 173 ++++++++++++ erpnext/patches.txt | 1 + erpnext/patches/v13_0/__init__.py | 1 + .../update_actual_start_and_end_date_in_wo.py | 43 +++ .../setup_wizard/data/dashboard_charts.py | 247 ++++++++++++++++++ .../quality_inspection.json | 16 +- .../stock/doctype/stock_entry/stock_entry.py | 4 + 33 files changed, 1444 insertions(+), 47 deletions(-) create mode 100644 erpnext/manufacturing/doctype/downtime_entry/__init__.py create mode 100644 erpnext/manufacturing/doctype/downtime_entry/downtime_entry.js create mode 100644 erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json create mode 100644 erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py create mode 100644 erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py create mode 100644 erpnext/manufacturing/report/downtime_analysis/__init__.py create mode 100644 erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js create mode 100644 erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json create mode 100644 erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py create mode 100644 erpnext/manufacturing/report/job_card_summary/__init__.py create mode 100644 erpnext/manufacturing/report/job_card_summary/job_card_summary.js create mode 100644 erpnext/manufacturing/report/job_card_summary/job_card_summary.json create mode 100644 erpnext/manufacturing/report/job_card_summary/job_card_summary.py create mode 100644 erpnext/manufacturing/report/quality_inspection_summary/__init__.py create mode 100644 erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js create mode 100644 erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json create mode 100644 erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py create mode 100644 erpnext/manufacturing/report/work_order_summary/__init__.py create mode 100644 erpnext/manufacturing/report/work_order_summary/work_order_summary.js create mode 100644 erpnext/manufacturing/report/work_order_summary/work_order_summary.json create mode 100644 erpnext/manufacturing/report/work_order_summary/work_order_summary.py create mode 100644 erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py create mode 100644 erpnext/setup/setup_wizard/data/dashboard_charts.py diff --git a/erpnext/config/manufacturing.py b/erpnext/config/manufacturing.py index 2c18eeb83a..012f1cad0a 100644 --- a/erpnext/config/manufacturing.py +++ b/erpnext/config/manufacturing.py @@ -120,13 +120,7 @@ def get_data(): { "type": "report", "is_query_report": True, - "name": "Open Work Orders", - "doctype": "Work Order" - }, - { - "type": "report", - "is_query_report": True, - "name": "Work Orders in Progress", + "name": "Work Order Summary", "doctype": "Work Order" }, { @@ -135,12 +129,6 @@ def get_data(): "name": "Issued Items Against Work Order", "doctype": "Work Order" }, - { - "type": "report", - "is_query_report": True, - "name": "Completed Work Orders", - "doctype": "Work Order" - }, { "type": "report", "is_query_report": True, diff --git a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json index 18604e283a..0464b763c0 100644 --- a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json @@ -3,7 +3,7 @@ { "hidden": 0, "label": "Production", - "links": "[\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Orders released for production.\",\n \"label\": \"Work Order\",\n \"name\": \"Work Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Generate Material Requests (MRP) and Work Orders.\",\n \"label\": \"Production Plan\",\n \"name\": \"Production Plan\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Activity Type\"\n ],\n \"description\": \"Time Sheet for manufacturing.\",\n \"label\": \"Timesheet\",\n \"name\": \"Timesheet\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Card\",\n \"name\": \"Job Card\",\n \"type\": \"doctype\"\n }\n]" + "links": "[\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Orders released for production.\",\n \"label\": \"Work Order\",\n \"name\": \"Work Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Generate Material Requests (MRP) and Work Orders.\",\n \"label\": \"Production Plan\",\n \"name\": \"Production Plan\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Card\",\n \"name\": \"Job Card\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Downtime Entry\",\n \"name\": \"Downtime Entry\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, @@ -13,7 +13,7 @@ { "hidden": 0, "label": "Reports", - "links": "[\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Open Work Orders\",\n \"name\": \"Open Work Orders\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Work Orders in Progress\",\n \"name\": \"Work Orders in Progress\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Issued Items Against Work Order\",\n \"name\": \"Issued Items Against Work Order\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Completed Work Orders\",\n \"name\": \"Completed Work Orders\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Production Analytics\",\n \"name\": \"Production Analytics\",\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 \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Stock Report\",\n \"name\": \"BOM Stock Report\",\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Work Order Summary\",\n \"name\": \"Work Order Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Issued Items Against Work Order\",\n \"name\": \"Issued Items Against Work Order\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Production Analytics\",\n \"name\": \"Production Analytics\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Quality Inspection\"\n ],\n \"doctype\": \"Quality Inspection\",\n \"is_query_report\": true,\n \"label\": \"Quality Inspection Summary\",\n \"name\": \"Quality Inspection Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Downtime Entry\"\n ],\n \"doctype\": \"Downtime Entry\",\n \"is_query_report\": true,\n \"label\": \"Downtime Analysis\",\n \"name\": \"Downtime Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Job Card\"\n ],\n \"doctype\": \"Job Card\",\n \"is_query_report\": true,\n \"label\": \"Job Card Summary\",\n \"name\": \"Job Card Summary\",\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 \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Stock Report\",\n \"name\": \"BOM Stock Report\",\n \"type\": \"report\"\n }\n]" }, { "hidden": 0, @@ -32,7 +32,12 @@ } ], "category": "Domains", - "charts": [], + "charts": [ + { + "chart_name": "Production Analysis", + "label": "Production Analysis" + } + ], "creation": "2020-03-02 17:11:37.032604", "developer_mode_only": 0, "disable_user_customization": 0, @@ -42,7 +47,7 @@ "idx": 0, "is_standard": 1, "label": "Manufacturing", - "modified": "2020-04-01 11:28:50.979358", + "modified": "2020-04-27 00:17:26.323677", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", @@ -50,5 +55,21 @@ "pin_to_bottom": 0, "pin_to_top": 0, "restrict_to_domain": "Manufacturing", - "shortcuts": [] + "shortcuts": [ + { + "label": "Item", + "link_to": "Item", + "type": "DocType" + }, + { + "label": "BOM", + "link_to": "BOM", + "type": "DocType" + }, + { + "label": "Work Order", + "link_to": "Work Order", + "type": "DocType" + } + ] } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/downtime_entry/__init__.py b/erpnext/manufacturing/doctype/downtime_entry/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.js b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.js new file mode 100644 index 0000000000..3b7f5ba8d7 --- /dev/null +++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Downtime Entry', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json new file mode 100644 index 0000000000..6ec088ad9e --- /dev/null +++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json @@ -0,0 +1,120 @@ +{ + "actions": [], + "allow_import": 1, + "creation": "2020-04-18 04:50:46.187638", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "workstation", + "operator", + "column_break_4", + "from_time", + "to_time", + "downtime", + "downtime_reason_section", + "reason" + ], + "fields": [ + { + "fieldname": "workstation", + "fieldtype": "Link", + "label": "Workstation / Machine", + "options": "Workstation", + "reqd": 1 + }, + { + "fieldname": "from_time", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "From Time", + "reqd": 1 + }, + { + "fieldname": "to_time", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "To Time", + "reqd": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "reason", + "fieldtype": "Text", + "label": "Reason", + "reqd": 1 + }, + { + "fieldname": "operator", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Operator", + "options": "Employee", + "reqd": 1 + }, + { + "fieldname": "downtime_reason_section", + "fieldtype": "Section Break", + "label": "Downtime Reason" + }, + { + "description": "In Mins", + "fieldname": "downtime", + "fieldtype": "Float", + "label": "Downtime", + "read_only": 1 + } + ], + "links": [], + "modified": "2020-04-20 17:34:51.299607", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Downtime Entry", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "workstation", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py new file mode 100644 index 0000000000..56ec4356af --- /dev/null +++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils import time_diff_in_hours +from frappe.model.document import Document + +class DowntimeEntry(Document): + def validate(self): + if self.from_time and self.to_time: + self.downtime = time_diff_in_hours(self.to_time, self.from_time) * 60 diff --git a/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py b/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py new file mode 100644 index 0000000000..8b2a8d36c1 --- /dev/null +++ b/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestDowntimeEntry(unittest.TestCase): + pass diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 7661fffa86..fba670c1c1 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "naming_series:", "creation": "2018-07-09 17:23:29.518745", "doctype": "DocType", @@ -264,8 +265,10 @@ { "fetch_from": "work_order.production_item", "fieldname": "production_item", - "fieldtype": "Read Only", - "label": "Production Item" + "fieldtype": "Link", + "label": "Production Item", + "options": "Item", + "read_only": 1 }, { "fieldname": "barcode", @@ -274,7 +277,8 @@ "read_only": 1 }, { - "fetch_from": "work_order.item_name", + "fetch_from": "production_item.item_name", + "fetch_if_empty": 1, "fieldname": "item_name", "fieldtype": "Read Only", "label": "Item Name" @@ -290,7 +294,8 @@ } ], "is_submittable": 1, - "modified": "2020-03-27 13:36:35.417502", + "links": [], + "modified": "2020-04-20 15:14:00.273441", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 00a67a03d6..8be22875a3 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -38,11 +38,12 @@ "required_items", "time", "planned_start_date", - "actual_start_date", - "column_break_13", "planned_end_date", - "actual_end_date", "expected_delivery_date", + "column_break_13", + "actual_start_date", + "actual_end_date", + "lead_time", "operations_section", "transfer_material_against", "operations", @@ -108,6 +109,8 @@ }, { "depends_on": "eval:doc.production_item", + "fetch_from": "production_item.item_name", + "fetch_if_empty": 1, "fieldname": "item_name", "fieldtype": "Data", "label": "Item Name", @@ -281,27 +284,30 @@ "reqd": 1 }, { + "allow_on_submit": 1, "fieldname": "actual_start_date", "fieldtype": "Datetime", "label": "Actual Start Date", - "read_only": 1 + "read_only_depends_on": "eval:doc.operations && doc.operations.length > 0" }, { "fieldname": "column_break_13", "fieldtype": "Column Break" }, { + "allow_on_submit": 1, "fieldname": "planned_end_date", "fieldtype": "Datetime", "label": "Planned End Date", "no_copy": 1, - "read_only": 1 + "read_only_depends_on": "eval:doc.operations && doc.operations.length > 0" }, { + "allow_on_submit": 1, "fieldname": "actual_end_date", "fieldtype": "Datetime", "label": "Actual End Date", - "read_only": 1 + "read_only_depends_on": "eval:doc.operations && doc.operations.length > 0" }, { "allow_on_submit": 1, @@ -476,6 +482,13 @@ "fieldtype": "Link", "label": "Source Warehouse", "options": "Warehouse" + }, + { + "description": "In Mins", + "fieldname": "lead_time", + "fieldtype": "Float", + "label": "Lead Time", + "read_only": 1 } ], "icon": "fa fa-cogs", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 8301f30d83..c2789559b0 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -6,7 +6,7 @@ import frappe import json import math from frappe import _ -from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate, get_link_to_form +from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate, get_link_to_form, time_diff_in_hours from frappe.model.document import Document from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict from dateutil.relativedelta import relativedelta @@ -279,7 +279,7 @@ class WorkOrder(Document): if enable_capacity_planning and job_card_doc: row.planned_start_time = job_card_doc.time_logs[-1].from_time row.planned_end_time = job_card_doc.time_logs[-1].to_time - print(row.planned_start_time, original_start_time, plan_days) + if date_diff(row.planned_start_time, original_start_time) > plan_days: frappe.message_log.pop() frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.") @@ -437,8 +437,6 @@ class WorkOrder(Document): frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'")) def set_actual_dates(self): - self.actual_start_date = None - self.actual_end_date = None if self.get("operations"): actual_start_dates = [d.actual_start_time for d in self.get("operations") if d.actual_start_time] if actual_start_dates: @@ -447,6 +445,27 @@ class WorkOrder(Document): actual_end_dates = [d.actual_end_time for d in self.get("operations") if d.actual_end_time] if actual_end_dates: self.actual_end_date = max(actual_end_dates) + else: + data = frappe.get_all("Stock Entry", + fields = ["timestamp(posting_date, posting_time) as posting_datetime"], + filters = { + "work_order": self.name, + "purpose": ("in", ["Material Transfer for Manufacture", "Manufacture"]) + } + ) + + if data and len(data): + dates = [d.posting_datetime for d in data] + self.actual_start_date = min(dates) + + if self.status == "Completed": + self.actual_end_date = max(dates) + + self.set_lead_time() + + def set_lead_time(self): + if self.actual_start_date and self.actual_end_date: + self.lead_time = flt(time_diff_in_hours(self.actual_end_date, self.actual_start_date) * 60) def delete_job_card(self): for d in frappe.get_all("Job Card", ["name"], {"work_order": self.name}): diff --git a/erpnext/manufacturing/report/downtime_analysis/__init__.py b/erpnext/manufacturing/report/downtime_analysis/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js new file mode 100644 index 0000000000..e20342792f --- /dev/null +++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js @@ -0,0 +1,28 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Downtime Analysis"] = { + "filters": [ + { + label: __("From Date"), + fieldname:"from_date", + fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), + reqd: 1 + }, + { + label: __("To Date"), + fieldname:"to_date", + fieldtype: "Date", + default: frappe.datetime.get_today(), + reqd: 1, + }, + { + label: __("Machine"), + fieldname: "workstation", + fieldtype: "Link", + options: "Workstation" + } + ] +}; diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json new file mode 100644 index 0000000000..5edc7781a2 --- /dev/null +++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json @@ -0,0 +1,31 @@ +{ + "add_total_row": 1, + "creation": "2020-04-20 18:26:04.345289", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "letter_head": "Gadgets International", + "modified": "2020-04-20 18:26:04.345289", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Downtime Analysis", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Downtime Entry", + "report_name": "Downtime Analysis", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Manufacturing User" + }, + { + "role": "Manufacturing Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py new file mode 100644 index 0000000000..d69ec189ba --- /dev/null +++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py @@ -0,0 +1,96 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils import flt +from frappe import _ + +def execute(filters=None): + columns, data = [], [] + data = get_data(filters) + columns = get_columns(filters) + chart_data = get_chart_data(data, filters) + return columns, data, None, chart_data + +def get_data(filters): + query_filters = {} + + fields = ["workstation", "operator", "from_time", "to_time", "downtime", "reason"] + + query_filters["from_time"] = (">=", filters.get("from_date")) + query_filters["to_time"] = ("<=", filters.get("to_date")) + + if filters.get("workstation"): + query_filters["workstation"] = filters.get("workstation") + + return frappe.get_all("Downtime Entry", fields= fields, filters=query_filters) + +def get_chart_data(data, columns): + labels = sorted(list(set([d.workstation for d in data]))) + + workstation_wise_data = {} + for d in data: + if d.workstation not in workstation_wise_data: + workstation_wise_data[d.workstation] = 0 + + workstation_wise_data[d.workstation] += flt(d.downtime, 2) + + datasets = [] + for label in labels: + datasets.append(workstation_wise_data.get(label, 0)) + + chart = { + "data": { + "labels": labels, + "datasets": [ + {"name": "Dataset 1", "values": datasets} + ] + }, + "type": "bar", + "colors": ["#ff5858"] + } + + return chart + +def get_columns(filters): + return [ + { + "label": _("Machine"), + "fieldname": "workstation", + "fieldtype": "Link", + "options": "Workstation", + "width": 100 + }, + { + "label": _("Operator"), + "fieldname": "operator", + "fieldtype": "Link", + "options": "Employee", + "width": 130 + }, + { + "label": _("From Time"), + "fieldname": "from_time", + "fieldtype": "Datetime", + "width": 160 + }, + { + "label": _("To Time"), + "fieldname": "to_time", + "fieldtype": "Datetime", + "width": 160 + }, + { + "label": _("Downtime (In Mins)"), + "fieldname": "downtime", + "fieldtype": "Float", + "width": 150 + }, + { + "label": _("Reason"), + "fieldname": "reason", + "fieldtype": "Text", + "width": 180 + } + ] \ No newline at end of file diff --git a/erpnext/manufacturing/report/job_card_summary/__init__.py b/erpnext/manufacturing/report/job_card_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js new file mode 100644 index 0000000000..b7e307183f --- /dev/null +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js @@ -0,0 +1,52 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Job Card Summary"] = { + "filters": [ + { + label: __("Company"), + fieldname: "company", + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1 + }, + { + label: __("From Date"), + fieldname:"from_date", + fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -12), + reqd: 1 + }, + { + label: __("To Date"), + fieldname:"to_date", + fieldtype: "Date", + default: frappe.datetime.get_today(), + reqd: 1, + }, + { + label: __("Status"), + fieldname: "status", + fieldtype: "Select", + options: ["", "Open", "Work In Progress", "Completed", "On Hold"] + }, + { + label: __("Sales Orders"), + fieldname: "sales_order", + fieldtype: "MultiSelectList", + get_data: function(txt) { + return frappe.db.get_link_options('Sales Order', txt); + } + }, + { + label: __("Production Item"), + fieldname: "production_item", + fieldtype: "MultiSelectList", + get_data: function(txt) { + return frappe.db.get_link_options('Item', txt); + } + } + ] +}; diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.json b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json new file mode 100644 index 0000000000..9f08fc34cb --- /dev/null +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json @@ -0,0 +1,34 @@ +{ + "add_total_row": 0, + "creation": "2020-04-20 12:00:21.436619", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "letter_head": "Gadgets International", + "modified": "2020-04-20 12:00:21.436619", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Job Card Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Job Card", + "report_name": "Job Card Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Manufacturing User" + }, + { + "role": "Stock User" + }, + { + "role": "Manufacturing Manager" + }, + { + "role": "Stock Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py new file mode 100644 index 0000000000..0f60c132ec --- /dev/null +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py @@ -0,0 +1,197 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import getdate, flt +from erpnext.stock.report.stock_analytics.stock_analytics import (get_period_date_ranges, get_period) + +def execute(filters=None): + columns, data = [], [] + data = get_data(filters) + columns = get_columns(filters) + chart_data = get_chart_data(data, filters) + return columns, data, None, chart_data + +def get_data(filters): + query_filters = {"docstatus": ("<", 2)} + + fields = ["name", "status", "work_order", "production_item", "item_name", + "total_completed_qty", "workstation", "operation", "employee_name", "total_time_in_mins"] + + for field in ["work_order", "workstation", "operation", "company"]: + if filters.get(field): + query_filters[field] = ("in", filters.get(field)) + + data = frappe.get_all("Job Card", + fields= fields, filters=query_filters) + + if not data: return [] + + job_cards = [d.name for d in data] + job_card_time_details = {} + for job_card_data in frappe.get_all("Job Card Time Log", + fields=["min(from_time) as from_time", "max(to_time) as to_time", "parent"], + filters={"docstatus": ("<", 2), "parent": ("in", job_cards)}, group_by="parent"): + job_card_time_details[job_card_data.parent] = job_card_data + + for d in data: + if d.status == "Material Transferred": + d.status = "Open" + + if job_card_time_details.get(d.name): + d.from_time = job_card_time_details.get(d.name).from_time + d.to_time = job_card_time_details.get(d.name).to_time + + return data + +def get_chart_data(job_card_details, filters): + labels, periodic_data = prepare_chart_data(job_card_details, filters) + + not_start, in_progress, on_hold, completed = [], [], [] , [] + datasets = [] + + for d in labels: + not_start.append(periodic_data.get("Open").get(d)) + in_progress.append(periodic_data.get("Work In Progress").get(d)) + on_hold.append(periodic_data.get("On Hold").get(d)) + completed.append(periodic_data.get("Completed").get(d)) + + datasets.append({'name':'Open', 'values': not_start}) + datasets.append({'name':'Work In Progress', 'values': in_progress}) + datasets.append({'name':'On Hold', 'values': on_hold}) + datasets.append({'name':'Completed', 'values': completed}) + + chart = { + "data": { + 'labels': labels, + 'datasets': datasets + }, + "type": "bar", + "colors": ["#ff5858", "#ffa00a", "#5e64ff", "#98d85b"], + "axisOptions": { + "xAxisMode": "tick" + }, + "barOptions": { + "stacked": 1 + } + } + + return chart + +def prepare_chart_data(job_card_details, filters): + labels = [] + + periodic_data = { + "Open": {}, + "Work In Progress": {}, + "On Hold": {}, + "Completed": {} + } + + filters.range = "Monthly" + + ranges = get_period_date_ranges(filters) + for from_date, end_date in ranges: + period = get_period(end_date, filters) + if period not in labels: + labels.append(period) + + for d in job_card_details: + if getdate(d.from_time) >= from_date and getdate(d.to_time) <= end_date: + if periodic_data.get(d.status) and periodic_data.get(d.status).get(period): + periodic_data[d.status][period] += 1 + else: + periodic_data[d.status][period] = 1 + + return labels, periodic_data + +def get_columns(filters): + columns = [ + { + "label": _("Id"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Job Card", + "width": 100 + }, + ] + + if not filters.get("status"): + columns.append( + { + "label": _("Status"), + "fieldname": "status", + "width": 100 + }, + ) + + columns.extend([ + { + "label": _("Work Order"), + "fieldname": "work_order", + "fieldtype": "Link", + "options": "Work Order", + "width": 100 + }, + { + "label": _("Production Item"), + "fieldname": "production_item", + "fieldtype": "Link", + "options": "Item", + "width": 110 + }, + { + "label": _("Item Name"), + "fieldname": "item_name", + "fieldtype": "Data", + "width": 100 + }, + { + "label": _("Workstation"), + "fieldname": "workstation", + "fieldtype": "Link", + "options": "Workstation", + "width": 110 + }, + { + "label": _("Operation"), + "fieldname": "operation", + "fieldtype": "Link", + "options": "Operation", + "width": 110 + }, + { + "label": _("Employee Name"), + "fieldname": "employee_name", + "fieldtype": "Data", + "width": 110 + }, + { + "label": _("Total Completed Qty"), + "fieldname": "total_completed_qty", + "fieldtype": "Float", + "width": 120 + }, + { + "label": _("From Time"), + "fieldname": "from_time", + "fieldtype": "Datetime", + "width": 120 + }, + { + "label": _("To Time"), + "fieldname": "to_time", + "fieldtype": "Datetime", + "width": 120 + }, + { + "label": _("Time Required (In Mins)"), + "fieldname": "total_time_in_mins", + "fieldtype": "Float", + "width": 100 + } + ]) + + return columns \ No newline at end of file diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py index 7447a1f670..f0bdfeda21 100644 --- a/erpnext/manufacturing/report/production_analytics/production_analytics.py +++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py @@ -38,7 +38,6 @@ def get_columns(filters): def get_periodic_data(filters, entry): periodic_data = { - "All Work Orders": {}, "Not Started": {}, "Overdue": {}, "Pending": {}, @@ -51,7 +50,6 @@ def get_periodic_data(filters, entry): period = get_period(end_date, filters) for d in entry: if getdate(d.creation) <= getdate(from_date) or getdate(d.creation) <= getdate(end_date) : - periodic_data = update_periodic_data(periodic_data, "All Work Orders", period) if d.status == 'Completed': if getdate(d.actual_end_date) < getdate(from_date) or getdate(d.modified) < getdate(from_date): periodic_data = update_periodic_data(periodic_data, "Completed", period) @@ -61,7 +59,7 @@ def get_periodic_data(filters, entry): elif getdate(d.planned_start_date) < getdate(from_date) : periodic_data = update_periodic_data(periodic_data, "Overdue", period) - + else: periodic_data = update_periodic_data(periodic_data, "Not Started", period) @@ -99,7 +97,7 @@ def get_data(filters, columns): periodic_data = get_periodic_data(filters,entry) - labels = ["All Work Orders", "Not Started", "Overdue", "Pending", "Completed"] + labels = ["Not Started", "Overdue", "Pending", "Completed"] chart_data = get_chart_data(periodic_data,columns) ranges = get_period_date_ranges(filters) @@ -123,13 +121,11 @@ def get_chart_data(periodic_data, columns): datasets = [] for d in labels: - all_data.append(periodic_data.get("All Work Orders").get(d)) not_start.append(periodic_data.get("Not Started").get(d)) overdue.append(periodic_data.get("Overdue").get(d)) pending.append(periodic_data.get("Pending").get(d)) completed.append(periodic_data.get("Completed").get(d)) - datasets.append({'name':'All Work Orders', 'values': all_data}) datasets.append({'name':'Not Started', 'values': not_start}) datasets.append({'name':'Overdue', 'values': overdue}) datasets.append({'name':'Pending', 'values': pending}) @@ -139,9 +135,10 @@ def get_chart_data(periodic_data, columns): "data": { 'labels': labels, 'datasets': datasets - } + }, + "type": "bar", + "colors": ["#5e64ff", "#ff5858", "#ffa00a", "#98d85b"] } - chart["type"] = "line" return chart diff --git a/erpnext/manufacturing/report/quality_inspection_summary/__init__.py b/erpnext/manufacturing/report/quality_inspection_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js new file mode 100644 index 0000000000..d4587aa661 --- /dev/null +++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js @@ -0,0 +1,40 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Quality Inspection Summary"] = { + "filters": [ + { + label: __("From Date"), + fieldname:"from_date", + fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -12), + reqd: 1 + }, + { + label: __("To Date"), + fieldname:"to_date", + fieldtype: "Date", + default: frappe.datetime.get_today(), + reqd: 1, + }, + { + label: __("Status"), + fieldname: "status", + fieldtype: "Select", + options: ["", "Accepted", "Rejected"] + }, + { + label: __("Item Code"), + fieldname: "item_code", + fieldtype: "Link", + options: "Item" + }, + { + label: __("Inspected By"), + fieldname: "inspected_by", + fieldtype: "Link", + options: "User" + } + ] +}; diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json new file mode 100644 index 0000000000..48226e6b21 --- /dev/null +++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "creation": "2020-04-26 18:23:53.475110", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "letter_head": "Gadgets International", + "modified": "2020-04-26 18:24:50.529940", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Quality Inspection Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Quality Inspection", + "report_name": "Quality Inspection Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Quality Manager" + }, + { + "role": "Stock User" + }, + { + "role": "Stock Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py new file mode 100644 index 0000000000..9c1bfa4630 --- /dev/null +++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py @@ -0,0 +1,133 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ + +def execute(filters=None): + columns, data = [], [] + data = get_data(filters) + columns = get_columns(filters) + chart_data = get_chart_data(data, filters) + return columns, data , None, chart_data + +def get_data(filters): + query_filters = {"docstatus": ("<", 2)} + + fields = ["name", "status", "report_date", "item_code", "item_name", "sample_size", + "inspection_type", "reference_type", "reference_name", "inspected_by"] + + for field in ["status", "item_code", "status", "inspected_by"]: + if filters.get(field): + query_filters[field] = ("in", filters.get(field)) + + query_filters["report_date"] = (">=", filters.get("from_date")) + query_filters["report_date"] = ("<=", filters.get("to_date")) + + return frappe.get_all("Quality Inspection", + fields= fields, filters=query_filters, order_by="report_date asc") + +def get_chart_data(periodic_data, columns): + labels = ["Rejected", "Accepted"] + + status_wise_data = { + "Accepted": 0, + "Rejected": 0 + } + + datasets = [] + + for d in periodic_data: + status_wise_data[d.status] += 1 + + datasets.append({'name':'Qty Wise Chart', + 'values': [status_wise_data.get("Rejected"), status_wise_data.get("Accepted")]}) + + chart = { + "data": { + 'labels': labels, + 'datasets': datasets + }, + "type": "donut", + "height": 300, + "colors": ["#ff5858", "#98d85b"] + } + + return chart + +def get_columns(filters): + columns = [ + { + "label": _("Id"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Work Order", + "width": 100 + }, + { + "label": _("Report Date"), + "fieldname": "report_date", + "fieldtype": "Date", + "width": 150 + } + ] + + if not filters.get("status"): + columns.append( + { + "label": _("Status"), + "fieldname": "status", + "width": 100 + }, + ) + + columns.extend([ + { + "label": _("Item Code"), + "fieldname": "item_code", + "fieldtype": "Link", + "options": "Item", + "width": 130 + }, + { + "label": _("Item Name"), + "fieldname": "item_name", + "fieldtype": "Data", + "width": 130 + }, + { + "label": _("Sample Size"), + "fieldname": "sample_size", + "fieldtype": "Float", + "width": 110 + }, + { + "label": _("Inspection Type"), + "fieldname": "inspection_type", + "fieldtype": "Data", + "width": 110 + }, + { + "label": _("Document Type"), + "fieldname": "reference_type", + "fieldtype": "Data", + "width": 90 + }, + { + "label": _("Document Name"), + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "options": "reference_type", + "width": 150 + }, + { + "label": _("Inspected By"), + "fieldname": "inspected_by", + "fieldtype": "Link", + "options": "User", + "width": 150 + } + ]) + + return columns \ No newline at end of file diff --git a/erpnext/manufacturing/report/work_order_summary/__init__.py b/erpnext/manufacturing/report/work_order_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js new file mode 100644 index 0000000000..33b147df49 --- /dev/null +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js @@ -0,0 +1,58 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Work Order Summary"] = { + "filters": [ + { + label: __("Company"), + fieldname: "company", + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + reqd: 1 + }, + { + label: __("From Date"), + fieldname:"from_date", + fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -12), + reqd: 1 + }, + { + label: __("To Date"), + fieldname:"to_date", + fieldtype: "Date", + default: frappe.datetime.get_today(), + reqd: 1, + }, + { + label: __("Status"), + fieldname: "status", + fieldtype: "Select", + options: ["", "Not Started", "In Process", "Completed", "Stopped"] + }, + { + label: __("Sales Orders"), + fieldname: "sales_order", + fieldtype: "MultiSelectList", + get_data: function(txt) { + return frappe.db.get_link_options('Sales Order', txt); + } + }, + { + label: __("Production Item"), + fieldname: "production_item", + fieldtype: "MultiSelectList", + get_data: function(txt) { + return frappe.db.get_link_options('Item', txt); + } + }, + { + label: __("Age"), + fieldname:"age", + fieldtype: "Int", + default: "0" + }, + ] +}; diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.json b/erpnext/manufacturing/report/work_order_summary/work_order_summary.json new file mode 100644 index 0000000000..0d093e22e9 --- /dev/null +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.json @@ -0,0 +1,31 @@ +{ + "add_total_row": 0, + "creation": "2020-04-17 17:07:56.830358", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "letter_head": "Gadgets International", + "modified": "2020-04-19 16:59:47.979278", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Work Order Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Work Order", + "report_name": "Work Order Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Manufacturing User" + }, + { + "role": "Stock User" + }, + { + "role": "Manufacturing Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py new file mode 100644 index 0000000000..92c31ab382 --- /dev/null +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py @@ -0,0 +1,173 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils import date_diff, today +from frappe import _ + +def execute(filters=None): + columns, data = [], [] + + if not filters.get("age"): + filters["age"] = 0 + + data = get_data(filters) + columns = get_columns(filters) + chart_data = get_chart_data(data, filters) + return columns, data, None, chart_data + +def get_data(filters): + query_filters = {"docstatus": 1} + + fields = ["name", "status", "sales_order", "production_item", "qty", "produced_qty", + "planned_start_date", "planned_end_date", "actual_start_date", "actual_end_date", "lead_time"] + + for field in ["sales_order", "production_item", "status", "company"]: + if filters.get(field): + query_filters[field] = ("in", filters.get(field)) + + query_filters["planned_start_date"] = (">=", filters.get("from_date")) + query_filters["planned_end_date"] = ("<=", filters.get("to_date")) + + data = frappe.get_all("Work Order", + fields= fields, filters=query_filters, order_by="planned_start_date asc") + + res = [] + for d in data: + start_date = d.actual_start_date or d.planned_start_date + d.age = 0 + + if d.status != 'Completed': + d.age = date_diff(today(), start_date) + + if filters.get("age") <= d.age: + res.append(d) + + return res + +def get_chart_data(periodic_data, columns): + labels = ["Not Started", "In Process", "Stopped", "Completed"] + + status_wise_data = { + "Not Started": 0, + "In Process": 0, + "Stopped": 0, + "Completed": 0 + } + + for d in periodic_data: + if d.status == "In Process" and d.produced_qty: + status_wise_data["Completed"] += d.produced_qty + + status_wise_data[d.status] += d.qty + + values = [status_wise_data["Not Started"], status_wise_data["In Process"], + status_wise_data["Stopped"], status_wise_data["Completed"]] + + chart = { + "data": { + 'labels': labels, + 'datasets': [{'name':'Qty Wise Chart', 'values': values}] + }, + "type": "donut", + "height": 300, + "colors": ["#ff5858", "#ffa00a", "#5e64ff", "#98d85b"] + } + + return chart + +def get_columns(filters): + columns = [ + { + "label": _("Id"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Work Order", + "width": 100 + }, + ] + + if not filters.get("status"): + columns.append( + { + "label": _("Status"), + "fieldname": "status", + "width": 100 + }, + ) + + columns.extend([ + { + "label": _("Production Item"), + "fieldname": "production_item", + "fieldtype": "Link", + "options": "Item", + "width": 130 + }, + { + "label": _("Produce Qty"), + "fieldname": "qty", + "fieldtype": "Float", + "width": 110 + }, + { + "label": _("Produced Qty"), + "fieldname": "produced_qty", + "fieldtype": "Float", + "width": 110 + }, + { + "label": _("Sales Order"), + "fieldname": "sales_order", + "fieldtype": "Link", + "options": "Sales Order", + "width": 90 + }, + { + "label": _("Planned Start Date"), + "fieldname": "planned_start_date", + "fieldtype": "Date", + "width": 150 + }, + { + "label": _("Planned End Date"), + "fieldname": "planned_end_date", + "fieldtype": "Date", + "width": 150 + } + ]) + + if filters.get("status") != 'Not Started': + columns.extend([ + { + "label": _("Actual Start Date"), + "fieldname": "actual_start_date", + "fieldtype": "Date", + "width": 100 + }, + { + "label": _("Actual End Date"), + "fieldname": "actual_end_date", + "fieldtype": "Date", + "width": 100 + }, + { + "label": _("Age"), + "fieldname": "age", + "fieldtype": "Float", + "width": 110 + }, + ]) + + if filters.get("status") == 'Completed': + columns.extend([ + { + "label": _("Lead Time (in mins)"), + "fieldname": "lead_time", + "fieldtype": "Float", + "width": 110 + }, + ]) + + return columns \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4ae591b54b..b920d0334f 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.v13_0.update_actual_start_and_end_date_in_wo diff --git a/erpnext/patches/v13_0/__init__.py b/erpnext/patches/v13_0/__init__.py index e69de29bb2..baffc48825 100644 --- a/erpnext/patches/v13_0/__init__.py +++ b/erpnext/patches/v13_0/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py new file mode 100644 index 0000000000..3fab0040fb --- /dev/null +++ b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py @@ -0,0 +1,43 @@ + +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe +from frappe.utils import add_to_date + +def execute(): + frappe.reload_doc("manufacturing", "doctype", "work_order") + frappe.reload_doc("manufacturing", "doctype", "work_order_item") + frappe.reload_doc("manufacturing", "doctype", "job_card") + + data = frappe.get_all("Work Order", + filters = { + "docstatus": 1, + "status": ("in", ["In Process", "Completed"]) + }) + + for d in data: + doc = frappe.get_doc("Work Order", d.name) + doc.set_actual_dates() + doc.db_set("actual_start_date", doc.actual_start_date, update_modified=False) + + if doc.status == "Completed": + frappe.db.set_value("Work Order", d.name, { + "actual_end_date": doc.actual_end_date, + "lead_time": doc.lead_time + }, update_modified=False) + + if not doc.planned_end_date: + planned_end_date = add_to_date(doc.planned_start_date, minutes=doc.lead_time) + doc.db_set("planned_end_date", doc.actual_start_date, update_modified=False) + + + frappe.db.sql(""" UPDATE `tabJob Card` as jc, `tabWork Order` as wo + SET + jc.production_item = wo.production_item, jc.item_name = wo.item_name + WHERE + jc.work_order = wo.name and IFNULL(jc.production_item, "") = "" + """) + diff --git a/erpnext/setup/setup_wizard/data/dashboard_charts.py b/erpnext/setup/setup_wizard/data/dashboard_charts.py new file mode 100644 index 0000000000..f5b6681767 --- /dev/null +++ b/erpnext/setup/setup_wizard/data/dashboard_charts.py @@ -0,0 +1,247 @@ +from __future__ import unicode_literals +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": [ + { + "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" } + ] + }, + { + "doctype": "Dashboard", + "dashboard_name": "Manufacturing", + "charts": [ + { "chart": "Work Order Analysis", "width": "Half" }, + { "chart": "Quality Inspection Analysis", "width": "Half" }, + { "chart": "Long Time Pending Work Orders", "width": "Half" }, + { "chart": "Downtime Analysis", "width": "Half" }, + { "chart": "Production Analysis", "width": "Full" }, + { "chart": "Job Card Analysis", "width": "Full" } + ] + } + ], + "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", + "chart_name": "Patient Appointments", + "timespan": "Last Month", + "color": "#77ecca", + "filters_json": json.dumps({}), + "chart_type": "Count", + "timeseries": 1, + "based_on": "appointment_datetime", + "owner": "Administrator", + "document_type": "Patient Appointment", + "type": "Line", + "width": "Half" + } + { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Work Order Analysis", + "timespan": "Last Year", + "report_name": "Work Order Summary", + "owner": "Administrator", + "filters_json": json.dumps({"company": company.name}), + "bar": "Donut", + "custom_options": json.dumps({ + "axisOptions": { + "shortenYAxisNumbers": 1 + }, + "height": 300, + "colors": ["#ff5858", "#ffa00a", "#5e64ff", "#98d85b"] + }), + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Quality Inspection Analysis", + "timespan": "Last Year", + "report_name": "Quality Inspection Summary", + "owner": "Administrator", + "filters_json": json.dumps({}), + "bar": "Donut", + "custom_options": json.dumps({ + "axisOptions": { + "shortenYAxisNumbers": 1 + }, + "height": 300, + "colors": ["#ff5858", "#98d85b"] + }), + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Long Time Pending Work Orders", + "timespan": "Last Year", + "report_name": "Work Order Summary", + "filters_json": json.dumps({"company": company.name, "age":180}), + "owner": "Administrator", + "bar": "Bar", + "custom_options": json.dumps({ + "colors": ["#ff5858"], + "x_field": "name", + "y_fields": ["age"], + "y_axis_fields": [{"__islocal": "true", "idx": 1, "y_field": "age"}], + "chart_type": "Bar" + }), + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Downtime Analysis", + "timespan": "Last Year", + "filters_json": json.dumps({}), + "report_name": "Downtime Analysis", + "owner": "Administrator", + "bar": "Bar", + "custom_options": json.dumps({ + "colors": ["#ff5858"] + }), + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Production Analysis", + "timespan": "Last Year", + "report_name": "Production Analytics", + "owner": "Administrator", + "filters_json": json.dumps({"company": company.name, "range":"Monthly"}), + "bar": "Bar", + "custom_options": json.dumps({ + "colors": ["#7cd6fd", "#ff5858", "#ffa00a", "#98d85b"] + }), + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Job Card Analysis", + "timespan": "Last Year", + "report_name": "Job Card Summary", + "owner": "Administrator", + "filters_json": json.dumps({"company": company.name, "range":"Monthly"}), + "bar": "Bar", + "custom_options": json.dumps({ + "axisOptions": { + "xAxisMode": "tick" + }, + "barOptions": { + "stacked": 1 + }, + "colors": ["#ff5858", "#ffa00a", "#5e64ff", "#98d85b"] + }), + }, + ] + } + +def get_account(account_type, company): + accounts = frappe.get_list("Account", filters={"account_type": account_type, "company": company}) + if accounts: + return accounts[0].name diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json index a9f3cd09ef..c951066aa8 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "naming_series:", "creation": "2013-04-30 13:13:03", "doctype": "DocType", @@ -8,6 +9,7 @@ "field_order": [ "naming_series", "report_date", + "status", "column_break_4", "inspection_type", "reference_type", @@ -20,17 +22,16 @@ "column_break1", "item_name", "description", - "status", + "bom_no", + "specification_details", + "quality_inspection_template", + "readings", "section_break_14", "inspected_by", "verified_by", - "bom_no", "column_break_17", "remarks", - "amended_from", - "specification_details", - "quality_inspection_template", - "readings" + "amended_from" ], "fields": [ { @@ -231,7 +232,8 @@ "icon": "fa fa-search", "idx": 1, "is_submittable": 1, - "modified": "2019-07-12 12:07:23.153698", + "links": [], + "modified": "2020-04-26 17:50:25.068222", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index be2dd526a6..18d68539da 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -732,11 +732,15 @@ class StockEntry(StockController): pro_doc = frappe.get_doc("Work Order", self.work_order) _validate_work_order(pro_doc) pro_doc.run_method("update_status") + if self.fg_completed_qty: pro_doc.run_method("update_work_order_qty") if self.purpose == "Manufacture": pro_doc.run_method("update_planned_qty") + if not pro_doc.operations: + pro_doc.set_actual_dates() + def get_item_details(self, args=None, for_update=False): item = frappe.db.sql("""select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group, i.has_batch_no, i.sample_quantity, i.has_serial_no, From 366bce678f0b3754dfa00202530f135178fc78a9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 6 May 2020 02:38:27 +0530 Subject: [PATCH 68/78] added onboarding for manufacturing --- erpnext/manufacturing/dashboard_fixtures.py | 216 +++++++++++++++ .../manufacturing/manufacturing.json | 43 ++- .../doctype/work_order/work_order.json | 2 +- .../manufacturing/manufacturing.json | 54 ++++ .../create_bom/create_bom.json | 16 ++ .../create_product/create_product.json | 16 ++ .../introduction_to_manufacturing.json | 17 ++ .../onboarding_step/operation/operation.json | 15 ++ .../onboarding_step/warehouse/warehouse.json | 15 ++ .../work_order/work_order.json | 15 ++ .../workstation/workstation.json | 15 ++ .../downtime_analysis/downtime_analysis.py | 3 +- .../job_card_summary/job_card_summary.py | 33 +-- .../production_analytics.py | 12 +- .../quality_inspection_summary.py | 3 +- .../work_order_summary/work_order_summary.js | 7 + .../work_order_summary/work_order_summary.py | 107 +++++++- .../update_actual_start_and_end_date_in_wo.py | 5 +- .../setup_wizard/data/dashboard_charts.py | 247 ------------------ 19 files changed, 545 insertions(+), 296 deletions(-) create mode 100644 erpnext/manufacturing/dashboard_fixtures.py create mode 100644 erpnext/manufacturing/onboarding/manufacturing/manufacturing.json create mode 100644 erpnext/manufacturing/onboarding_step/create_bom/create_bom.json create mode 100644 erpnext/manufacturing/onboarding_step/create_product/create_product.json create mode 100644 erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json create mode 100644 erpnext/manufacturing/onboarding_step/operation/operation.json create mode 100644 erpnext/manufacturing/onboarding_step/warehouse/warehouse.json create mode 100644 erpnext/manufacturing/onboarding_step/work_order/work_order.json create mode 100644 erpnext/manufacturing/onboarding_step/workstation/workstation.json delete mode 100644 erpnext/setup/setup_wizard/data/dashboard_charts.py diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py new file mode 100644 index 0000000000..de3775f52c --- /dev/null +++ b/erpnext/manufacturing/dashboard_fixtures.py @@ -0,0 +1,216 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe, erpnext, json +from frappe import _ + +def get_data(): + return frappe._dict({ + "dashboards": get_dashboards(), + "charts": get_charts(), + "number_cards": get_number_cards(), + }) + +def get_dashboards(): + return [{ + "name": "Manufacturing Dashboard", + "dashboard_name": "Manufacturing Dashboard", + "charts": [ + { "chart": "Produced Quantity", "width": "Half" }, + { "chart": "Completed Operation", "width": "Half" }, + { "chart": "Work Order Analysis", "width": "Half" }, + { "chart": "Quality Inspection Analysis", "width": "Half" }, + { "chart": "Pending Work Order", "width": "Half" }, + { "chart": "Last Month Downtime Analysis", "width": "Half" }, + { "chart": "Work Order Qty Analysis", "width": "Full" }, + { "chart": "Job Card Analysis", "width": "Full" } + ], + "cards": [ + { "card": "Total Work Order" }, + { "card": "Completed Work Order" }, + { "card": "Ongoing Job Card" }, + { "card": "Total Quality Inspection"} + ] + }] + +def get_charts(): + company = erpnext.get_default_company() + + if not company: + company = frappe.db.get_value("Company", {"is_group": 0}, "name") + + return [{ + "doctype": "Dashboard Chart", + "based_on": "creation", + "time_interval": "Yearly", + "chart_type": "Sum", + "chart_name": "Produced Quantity", + "document_type": "Work Order", + "filters_json": json.dumps({}), + "group_by_type": "Count", + "time_interval": "Quarterly", + "timespan": "Last Year", + "owner": "Administrator", + "type": "Line", + "value_based_on": "qty", + "is_public": 1, + "timeseries": 1 + }, { + "doctype": "Dashboard Chart", + "based_on": "creation", + "time_interval": "Yearly", + "chart_type": "Sum", + "chart_name": "Completed Operation", + "document_type": "Work Order Operation", + "filters_json": json.dumps({}), + "group_by_type": "Count", + "time_interval": "Quarterly", + "timespan": "Last Year", + "owner": "Administrator", + "type": "Line", + "value_based_on": "completed_qty", + "is_public": 1, + "timeseries": 1 + }, { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Work Order Analysis", + "timespan": "Last Year", + "report_name": "Work Order Summary", + "owner": "Administrator", + "filters_json": json.dumps({"company": company, "charts_based_on": "Status"}), + "type": "Donut", + "is_public": 1, + "is_custom": 1, + "custom_options": json.dumps({ + "axisOptions": { + "shortenYAxisNumbers": 1 + }, + "height": 300 + }), + }, { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Quality Inspection Analysis", + "timespan": "Last Year", + "report_name": "Quality Inspection Summary", + "owner": "Administrator", + "filters_json": json.dumps({}), + "type": "Donut", + "is_public": 1, + "is_custom": 1, + "custom_options": json.dumps({ + "axisOptions": { + "shortenYAxisNumbers": 1 + }, + "height": 300 + }), + }, { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Pending Work Order", + "timespan": "Last Year", + "report_name": "Work Order Summary", + "filters_json": json.dumps({"company": company, "charts_based_on": "Age"}), + "owner": "Administrator", + "type": "Donut", + "is_public": 1, + "is_custom": 1, + "custom_options": json.dumps({ + "axisOptions": { + "shortenYAxisNumbers": 1 + }, + "height": 300 + }), + }, { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Last Month Downtime Analysis", + "timespan": "Last Year", + "filters_json": json.dumps({}), + "report_name": "Downtime Analysis", + "owner": "Administrator", + "is_public": 1, + "is_custom": 1, + "type": "Bar" + }, { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": _("Work Order Qty Analysis"), + "timespan": "Last Year", + "report_name": "Work Order Summary", + "filters_json": json.dumps({"company": company, "charts_based_on": "Quantity"}), + "owner": "Administrator", + "type": "Bar", + "is_public": 1, + "is_custom": 1, + "custom_options": json.dumps({ + "barOptions": { "stacked": 1 } + }), + }, { + "doctype": "Dashboard Chart", + "time_interval": "Yearly", + "chart_type": "Report", + "chart_name": "Job Card Analysis", + "timespan": "Last Year", + "report_name": "Job Card Summary", + "owner": "Administrator", + "is_public": 1, + "is_custom": 1, + "filters_json": json.dumps({"company": company, "range":"Monthly"}), + "custom_options": json.dumps({ + "barOptions": { "stacked": 1 } + }), + "type": "Bar" + }] + +def get_number_cards(): + return [{ + "doctype": "Number Card", + "document_type": "Work Order", + "name": "Total Work Order", + "filters_json": json.dumps([]), + "function": "Count", + "is_public": 1, + "label": _("Total Work Order"), + "show_percentage_stats": 1, + "stats_time_interval": "Daily" + }, + { + "doctype": "Number Card", + "document_type": "Work Order", + "name": "Completed Work Order", + "filters_json": json.dumps([['Work Order', 'status','=','Completed', False]]), + "function": "Count", + "is_public": 1, + "label": _("Completed Work Order"), + "show_percentage_stats": 1, + "stats_time_interval": "Daily" + }, + { + "doctype": "Number Card", + "document_type": "Job Card", + "name": "Ongoing Job Card", + "filters_json": json.dumps([['Job Card', 'status','!=','Completed', False]]), + "function": "Count", + "is_public": 1, + "label": _("Ongoing Job Card"), + "show_percentage_stats": 1, + "stats_time_interval": "Daily" + }, + { + "doctype": "Number Card", + "document_type": "Quality Inspection", + "name": "Total Quality Inspection", + "filters_json": json.dumps([]), + "function": "Count", + "is_public": 1, + "label": _("Total Quality Inspection"), + "show_percentage_stats": 1, + "stats_time_interval": "Daily" + }] \ No newline at end of file diff --git a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json index 0464b763c0..f6bbf3db85 100644 --- a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json @@ -3,7 +3,7 @@ { "hidden": 0, "label": "Production", - "links": "[\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Orders released for production.\",\n \"label\": \"Work Order\",\n \"name\": \"Work Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Generate Material Requests (MRP) and Work Orders.\",\n \"label\": \"Production Plan\",\n \"name\": \"Production Plan\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Card\",\n \"name\": \"Job Card\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Downtime Entry\",\n \"name\": \"Downtime Entry\",\n \"type\": \"doctype\"\n }\n]" + "links": "[\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Orders released for production.\",\n \"label\": \"Work Order\",\n \"name\": \"Work Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Generate Material Requests (MRP) and Work Orders.\",\n \"label\": \"Production Plan\",\n \"name\": \"Production Plan\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Activity Type\"\n ],\n \"description\": \"Time Sheet for manufacturing.\",\n \"label\": \"Timesheet\",\n \"name\": \"Timesheet\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Card\",\n \"name\": \"Job Card\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, @@ -13,7 +13,7 @@ { "hidden": 0, "label": "Reports", - "links": "[\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Work Order Summary\",\n \"name\": \"Work Order Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Issued Items Against Work Order\",\n \"name\": \"Issued Items Against Work Order\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Production Analytics\",\n \"name\": \"Production Analytics\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Quality Inspection\"\n ],\n \"doctype\": \"Quality Inspection\",\n \"is_query_report\": true,\n \"label\": \"Quality Inspection Summary\",\n \"name\": \"Quality Inspection Summary\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Downtime Entry\"\n ],\n \"doctype\": \"Downtime Entry\",\n \"is_query_report\": true,\n \"label\": \"Downtime Analysis\",\n \"name\": \"Downtime Analysis\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Job Card\"\n ],\n \"doctype\": \"Job Card\",\n \"is_query_report\": true,\n \"label\": \"Job Card Summary\",\n \"name\": \"Job Card Summary\",\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 \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Stock Report\",\n \"name\": \"BOM Stock Report\",\n \"type\": \"report\"\n }\n]" + "links": "[{\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Work Order Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Work Order Summary\"\n}, {\n\t\"dependencies\": [\"Work Order\"],\n\t\"name\": \"Production Analytics\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Work Order\",\n\t\"label\": \"Production Analytics\"\n}, {\n\t\"dependencies\": [\"Quality Inspection\"],\n\t\"name\": \"Quality Inspection Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Quality Inspection\",\n\t\"label\": \"Quality Inspection Summary\"\n}, {\n\t\"dependencies\": [\"Downtime Entry\"],\n\t\"name\": \"Downtime Analysis\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Downtime Entry\",\n\t\"label\": \"Downtime Analysis\"\n}, {\n\t\"dependencies\": [\"Job Card\"],\n\t\"name\": \"Job Card Summary\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"Job Card\",\n\t\"label\": \"Job Card Summary\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Search\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Search\"\n}, {\n\t\"dependencies\": [\"BOM\"],\n\t\"name\": \"BOM Stock Report\",\n\t\"is_query_report\": true,\n\t\"type\": \"report\",\n\t\"doctype\": \"BOM\",\n\t\"label\": \"BOM Stock Report\"\n}]" }, { "hidden": 0, @@ -32,12 +32,7 @@ } ], "category": "Domains", - "charts": [ - { - "chart_name": "Production Analysis", - "label": "Production Analysis" - } - ], + "charts": [], "creation": "2020-03-02 17:11:37.032604", "developer_mode_only": 0, "disable_user_customization": 0, @@ -47,29 +42,59 @@ "idx": 0, "is_standard": 1, "label": "Manufacturing", - "modified": "2020-04-27 00:17:26.323677", + "modified": "2020-05-14 15:06:45.963942", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", + "onboarding": "Manufacturing", "owner": "Administrator", "pin_to_bottom": 0, "pin_to_top": 0, "restrict_to_domain": "Manufacturing", "shortcuts": [ { + "format": "{} Active", "label": "Item", "link_to": "Item", + "restrict_to_domain": "Manufacturing", + "stats_filter": "{\n \"disabled\": 0\n}", "type": "DocType" }, { + "format": "{} Active", "label": "BOM", "link_to": "BOM", + "restrict_to_domain": "Manufacturing", + "stats_filter": "{\n \"is_active\": 1\n}", "type": "DocType" }, { + "format": "{} Open", "label": "Work Order", "link_to": "Work Order", + "restrict_to_domain": "Manufacturing", + "stats_filter": "{ \"status\": \n (\n \"in\", [\"Draft\", \"Not Started\", \"In Process\"]\n )\n}", "type": "DocType" + }, + { + "format": "{} Open", + "label": "Production Plan", + "link_to": "Production Plan", + "restrict_to_domain": "Manufacturing", + "stats_filter": "{ \"status\": \n (\n \"!=\", \"Completed\"\n )\n}", + "type": "DocType" + }, + { + "label": "Work Order Summary", + "link_to": "Work Order Summary", + "restrict_to_domain": "Manufacturing", + "type": "Report" + }, + { + "label": "BOM Stock Report", + "link_to": "BOM Stock Report", + "restrict_to_domain": "Manufacturing", + "type": "Report" } ] } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 8be22875a3..585a09db2b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -496,7 +496,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2020-04-24 19:32:43.323054", + "modified": "2020-05-05 19:32:43.323054", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", diff --git a/erpnext/manufacturing/onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/onboarding/manufacturing/manufacturing.json new file mode 100644 index 0000000000..50584e19ca --- /dev/null +++ b/erpnext/manufacturing/onboarding/manufacturing/manufacturing.json @@ -0,0 +1,54 @@ +{ + "allow_roles": [ + { + "role": "Manufacturing User" + }, + { + "role": "Manufacturing Manager" + }, + { + "role": "Item Manager" + }, + { + "role": "Stock User" + } + ], + "creation": "2020-05-05 16:37:08.238935", + "docstatus": 0, + "doctype": "Onboarding", + "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing", + "idx": 0, + "is_complete": 0, + "modified": "2020-05-12 16:22:07.050224", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Manufacturing", + "owner": "Administrator", + "steps": [ + { + "step": "Introduction to Manufacturing" + }, + { + "step": "Warehouse" + }, + { + "step": "Workstation" + }, + { + "step": "Operation" + }, + { + "step": "Create Product" + }, + { + "step": "Create BOM" + }, + { + "step": "Work Order" + } + ], + "subtitle": "Products, Raw Materials, BOM, Work Order and more.", + "success_message": "Manufacturing module is all setup!", + "title": "Let's Setup Manufacturing Module", + "user_can_dismiss": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json new file mode 100644 index 0000000000..969c2fa5fe --- /dev/null +++ b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json @@ -0,0 +1,16 @@ +{ + "action": "Create Entry", + "creation": "2020-05-05 16:41:20.239696", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 1, + "is_skipped": 0, + "modified": "2020-05-05 16:41:20.239696", + "modified_by": "Administrator", + "name": "Create BOM", + "owner": "Administrator", + "reference_document": "BOM", + "title": "Create BOM" +} \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/create_product/create_product.json b/erpnext/manufacturing/onboarding_step/create_product/create_product.json new file mode 100644 index 0000000000..082f31ed6e --- /dev/null +++ b/erpnext/manufacturing/onboarding_step/create_product/create_product.json @@ -0,0 +1,16 @@ +{ + "action": "Create Entry", + "creation": "2020-05-05 16:42:31.476275", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 1, + "is_skipped": 0, + "modified": "2020-05-05 16:42:31.476275", + "modified_by": "Administrator", + "name": "Create Product", + "owner": "Administrator", + "reference_document": "Item", + "title": "Create Product" +} \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json b/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json new file mode 100644 index 0000000000..6e5c3a9f45 --- /dev/null +++ b/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json @@ -0,0 +1,17 @@ +{ + "action": "Update Settings", + "creation": "2020-05-05 16:40:23.676406", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_skipped": 0, + "modified": "2020-05-05 16:40:23.676406", + "modified_by": "Administrator", + "name": "Introduction to Manufacturing", + "owner": "Administrator", + "reference_document": "Manufacturing Settings", + "title": "Manufacturing Settings", + "video_url": "https://www.youtube.com/watch?v=UVGfzwOOZC4" +} \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/operation/operation.json b/erpnext/manufacturing/onboarding_step/operation/operation.json new file mode 100644 index 0000000000..6f84db13ec --- /dev/null +++ b/erpnext/manufacturing/onboarding_step/operation/operation.json @@ -0,0 +1,15 @@ +{ + "action": "Create Entry", + "creation": "2020-05-12 16:15:31.706756", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_skipped": 0, + "modified": "2020-05-12 16:17:06.943067", + "modified_by": "Administrator", + "name": "Operation", + "owner": "Administrator", + "title": "Create Operation" +} \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json b/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json new file mode 100644 index 0000000000..36f5204e0b --- /dev/null +++ b/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json @@ -0,0 +1,15 @@ +{ + "action": "Create Entry", + "creation": "2020-05-12 16:13:34.014554", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_skipped": 0, + "modified": "2020-05-12 16:16:48.345846", + "modified_by": "Administrator", + "name": "Warehouse", + "owner": "Administrator", + "title": "Create Warehouse" +} \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/work_order/work_order.json b/erpnext/manufacturing/onboarding_step/work_order/work_order.json new file mode 100644 index 0000000000..57e4e7b3bc --- /dev/null +++ b/erpnext/manufacturing/onboarding_step/work_order/work_order.json @@ -0,0 +1,15 @@ +{ + "action": "Create Entry", + "creation": "2020-05-12 16:15:56.084682", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_skipped": 0, + "modified": "2020-05-12 16:17:33.675304", + "modified_by": "Administrator", + "name": "Work Order", + "owner": "Administrator", + "title": "Create Work Order" +} \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/workstation/workstation.json b/erpnext/manufacturing/onboarding_step/workstation/workstation.json new file mode 100644 index 0000000000..6c1a06938b --- /dev/null +++ b/erpnext/manufacturing/onboarding_step/workstation/workstation.json @@ -0,0 +1,15 @@ +{ + "action": "Create Entry", + "creation": "2020-05-12 16:14:14.930214", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_skipped": 0, + "modified": "2020-05-12 16:16:58.808906", + "modified_by": "Administrator", + "name": "Workstation", + "owner": "Administrator", + "title": "Create Workstation" +} \ No newline at end of file diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py index d69ec189ba..dfc6b02b84 100644 --- a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py +++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py @@ -47,8 +47,7 @@ def get_chart_data(data, columns): {"name": "Dataset 1", "values": datasets} ] }, - "type": "bar", - "colors": ["#ff5858"] + "type": "bar" } return chart diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py index 0f60c132ec..ae1e4f3046 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py @@ -49,33 +49,22 @@ def get_data(filters): def get_chart_data(job_card_details, filters): labels, periodic_data = prepare_chart_data(job_card_details, filters) - not_start, in_progress, on_hold, completed = [], [], [] , [] + pending, completed = [], [] datasets = [] for d in labels: - not_start.append(periodic_data.get("Open").get(d)) - in_progress.append(periodic_data.get("Work In Progress").get(d)) - on_hold.append(periodic_data.get("On Hold").get(d)) + pending.append(periodic_data.get("Pending").get(d)) completed.append(periodic_data.get("Completed").get(d)) - datasets.append({'name':'Open', 'values': not_start}) - datasets.append({'name':'Work In Progress', 'values': in_progress}) - datasets.append({'name':'On Hold', 'values': on_hold}) - datasets.append({'name':'Completed', 'values': completed}) + datasets.append({"name": "Pending", "values": pending}) + datasets.append({"name": "Completed", "values": completed}) chart = { "data": { 'labels': labels, 'datasets': datasets }, - "type": "bar", - "colors": ["#ff5858", "#ffa00a", "#5e64ff", "#98d85b"], - "axisOptions": { - "xAxisMode": "tick" - }, - "barOptions": { - "stacked": 1 - } + "type": "bar" } return chart @@ -84,9 +73,7 @@ def prepare_chart_data(job_card_details, filters): labels = [] periodic_data = { - "Open": {}, - "Work In Progress": {}, - "On Hold": {}, + "Pending": {}, "Completed": {} } @@ -100,10 +87,12 @@ def prepare_chart_data(job_card_details, filters): for d in job_card_details: if getdate(d.from_time) >= from_date and getdate(d.to_time) <= end_date: - if periodic_data.get(d.status) and periodic_data.get(d.status).get(period): - periodic_data[d.status][period] += 1 + status = "Completed" if d.status == "Completed" else "Pending" + + if periodic_data.get(status) and periodic_data.get(status).get(period): + periodic_data[status][period] += 1 else: - periodic_data[d.status][period] = 1 + periodic_data[status][period] = 1 return labels, periodic_data diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py index f0bdfeda21..f62cd255b5 100644 --- a/erpnext/manufacturing/report/production_analytics/production_analytics.py +++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py @@ -38,6 +38,7 @@ def get_columns(filters): def get_periodic_data(filters, entry): periodic_data = { + "All Work Orders": {}, "Not Started": {}, "Overdue": {}, "Pending": {}, @@ -50,6 +51,7 @@ def get_periodic_data(filters, entry): period = get_period(end_date, filters) for d in entry: if getdate(d.creation) <= getdate(from_date) or getdate(d.creation) <= getdate(end_date) : + periodic_data = update_periodic_data(periodic_data, "All Work Orders", period) if d.status == 'Completed': if getdate(d.actual_end_date) < getdate(from_date) or getdate(d.modified) < getdate(from_date): periodic_data = update_periodic_data(periodic_data, "Completed", period) @@ -97,7 +99,7 @@ def get_data(filters, columns): periodic_data = get_periodic_data(filters,entry) - labels = ["Not Started", "Overdue", "Pending", "Completed"] + labels = ["All Work Orders", "Not Started", "Overdue", "Pending", "Completed"] chart_data = get_chart_data(periodic_data,columns) ranges = get_period_date_ranges(filters) @@ -121,11 +123,13 @@ def get_chart_data(periodic_data, columns): datasets = [] for d in labels: + all_data.append(periodic_data.get("All Work Orders").get(d)) not_start.append(periodic_data.get("Not Started").get(d)) overdue.append(periodic_data.get("Overdue").get(d)) pending.append(periodic_data.get("Pending").get(d)) completed.append(periodic_data.get("Completed").get(d)) + datasets.append({'name':'All Work Orders', 'values': all_data}) datasets.append({'name':'Not Started', 'values': not_start}) datasets.append({'name':'Overdue', 'values': overdue}) datasets.append({'name':'Pending', 'values': pending}) @@ -135,14 +139,12 @@ def get_chart_data(periodic_data, columns): "data": { 'labels': labels, 'datasets': datasets - }, - "type": "bar", - "colors": ["#5e64ff", "#ff5858", "#ffa00a", "#98d85b"] + } } + chart["type"] = "line" return chart - diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py index 9c1bfa4630..6192632bda 100644 --- a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py +++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py @@ -50,8 +50,7 @@ def get_chart_data(periodic_data, columns): 'datasets': datasets }, "type": "donut", - "height": 300, - "colors": ["#ff5858", "#98d85b"] + "height": 300 } return chart diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js index 33b147df49..ec9fe35d63 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js @@ -54,5 +54,12 @@ frappe.query_reports["Work Order Summary"] = { fieldtype: "Int", default: "0" }, + { + label: __("Charts Based On"), + fieldname:"charts_based_on", + fieldtype: "Select", + options: ["Status", "Age", "Quantity"], + default: "Status" + }, ] }; diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py index 92c31ab382..bc09ed4335 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py @@ -3,8 +3,9 @@ from __future__ import unicode_literals import frappe -from frappe.utils import date_diff, today +from frappe.utils import date_diff, today, getdate, flt from frappe import _ +from erpnext.stock.report.stock_analytics.stock_analytics import (get_period_date_ranges, get_period) def execute(filters=None): columns, data = [], [] @@ -46,7 +47,15 @@ def get_data(filters): return res -def get_chart_data(periodic_data, columns): +def get_chart_data(data, filters): + if filters.get("charts_based_on") == "Status": + return get_chart_based_on_status(data) + elif filters.get("charts_based_on") == "Age": + return get_chart_based_on_age(data) + else: + return get_chart_based_on_qty(data, filters) + +def get_chart_based_on_status(data): labels = ["Not Started", "In Process", "Stopped", "Completed"] status_wise_data = { @@ -56,7 +65,7 @@ def get_chart_data(periodic_data, columns): "Completed": 0 } - for d in periodic_data: + for d in data: if d.status == "In Process" and d.produced_qty: status_wise_data["Completed"] += d.produced_qty @@ -71,12 +80,100 @@ def get_chart_data(periodic_data, columns): 'datasets': [{'name':'Qty Wise Chart', 'values': values}] }, "type": "donut", - "height": 300, - "colors": ["#ff5858", "#ffa00a", "#5e64ff", "#98d85b"] + "height": 300 } return chart +def get_chart_based_on_age(data): + labels = ["0-30 Days", "30-60 Days", "60-90 Days", "90 Above"] + + age_wise_data = { + "0-30 Days": 0, + "30-60 Days": 0, + "60-90 Days": 0, + "90 Above": 0 + } + + for d in data: + if d.age > 0 and d.age <= 30: + age_wise_data["0-30 Days"] += 1 + elif d.age > 30 and d.age <= 60: + age_wise_data["30-60 Days"] += 1 + elif d.age > 60 and d.age <= 90: + age_wise_data["60-90 Days"] += 1 + else: + age_wise_data["90 Above"] += 1 + + values = [age_wise_data["0-30 Days"], age_wise_data["30-60 Days"], + age_wise_data["60-90 Days"], age_wise_data["90 Above"]] + + chart = { + "data": { + 'labels': labels, + 'datasets': [{'name':'Qty Wise Chart', 'values': values}] + }, + "type": "donut", + "height": 300 + } + + return chart + +def get_chart_based_on_qty(data, filters): + labels, periodic_data = prepare_chart_data(data, filters) + + pending, completed = [], [] + datasets = [] + + for d in labels: + pending.append(periodic_data.get("Pending").get(d)) + completed.append(periodic_data.get("Completed").get(d)) + + datasets.append({"name": "Pending", "values": pending}) + datasets.append({"name": "Completed", "values": completed}) + + chart = { + "data": { + 'labels': labels, + 'datasets': datasets + }, + "type": "bar", + "barOptions": { + "stacked": 1 + } + } + + return chart + +def prepare_chart_data(data, filters): + labels = [] + + periodic_data = { + "Pending": {}, + "Completed": {} + } + + filters.range = "Monthly" + + ranges = get_period_date_ranges(filters) + for from_date, end_date in ranges: + period = get_period(end_date, filters) + if period not in labels: + labels.append(period) + + if period not in periodic_data["Pending"]: + periodic_data["Pending"][period] = 0 + + if period not in periodic_data["Completed"]: + periodic_data["Completed"][period] = 0 + + for d in data: + if getdate(d.planned_start_date) >= from_date and getdate(d.planned_start_date) <= end_date: + periodic_data["Pending"][period] += (flt(d.qty) - flt(d.produced_qty)) + periodic_data["Completed"][period] += flt(d.produced_qty) + + return labels, periodic_data + def get_columns(filters): columns = [ { diff --git a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py index 3fab0040fb..331c5590e5 100644 --- a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py +++ b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe from frappe.utils import add_to_date +from frappe.utils.dashboard import get_config, make_records def execute(): frappe.reload_doc("manufacturing", "doctype", "work_order") @@ -33,11 +34,9 @@ def execute(): planned_end_date = add_to_date(doc.planned_start_date, minutes=doc.lead_time) doc.db_set("planned_end_date", doc.actual_start_date, update_modified=False) - frappe.db.sql(""" UPDATE `tabJob Card` as jc, `tabWork Order` as wo SET jc.production_item = wo.production_item, jc.item_name = wo.item_name WHERE jc.work_order = wo.name and IFNULL(jc.production_item, "") = "" - """) - + """) \ No newline at end of file diff --git a/erpnext/setup/setup_wizard/data/dashboard_charts.py b/erpnext/setup/setup_wizard/data/dashboard_charts.py deleted file mode 100644 index f5b6681767..0000000000 --- a/erpnext/setup/setup_wizard/data/dashboard_charts.py +++ /dev/null @@ -1,247 +0,0 @@ -from __future__ import unicode_literals -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": [ - { - "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" } - ] - }, - { - "doctype": "Dashboard", - "dashboard_name": "Manufacturing", - "charts": [ - { "chart": "Work Order Analysis", "width": "Half" }, - { "chart": "Quality Inspection Analysis", "width": "Half" }, - { "chart": "Long Time Pending Work Orders", "width": "Half" }, - { "chart": "Downtime Analysis", "width": "Half" }, - { "chart": "Production Analysis", "width": "Full" }, - { "chart": "Job Card Analysis", "width": "Full" } - ] - } - ], - "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", - "chart_name": "Patient Appointments", - "timespan": "Last Month", - "color": "#77ecca", - "filters_json": json.dumps({}), - "chart_type": "Count", - "timeseries": 1, - "based_on": "appointment_datetime", - "owner": "Administrator", - "document_type": "Patient Appointment", - "type": "Line", - "width": "Half" - } - { - "doctype": "Dashboard Chart", - "time_interval": "Yearly", - "chart_type": "Report", - "chart_name": "Work Order Analysis", - "timespan": "Last Year", - "report_name": "Work Order Summary", - "owner": "Administrator", - "filters_json": json.dumps({"company": company.name}), - "bar": "Donut", - "custom_options": json.dumps({ - "axisOptions": { - "shortenYAxisNumbers": 1 - }, - "height": 300, - "colors": ["#ff5858", "#ffa00a", "#5e64ff", "#98d85b"] - }), - }, - { - "doctype": "Dashboard Chart", - "time_interval": "Yearly", - "chart_type": "Report", - "chart_name": "Quality Inspection Analysis", - "timespan": "Last Year", - "report_name": "Quality Inspection Summary", - "owner": "Administrator", - "filters_json": json.dumps({}), - "bar": "Donut", - "custom_options": json.dumps({ - "axisOptions": { - "shortenYAxisNumbers": 1 - }, - "height": 300, - "colors": ["#ff5858", "#98d85b"] - }), - }, - { - "doctype": "Dashboard Chart", - "time_interval": "Yearly", - "chart_type": "Report", - "chart_name": "Long Time Pending Work Orders", - "timespan": "Last Year", - "report_name": "Work Order Summary", - "filters_json": json.dumps({"company": company.name, "age":180}), - "owner": "Administrator", - "bar": "Bar", - "custom_options": json.dumps({ - "colors": ["#ff5858"], - "x_field": "name", - "y_fields": ["age"], - "y_axis_fields": [{"__islocal": "true", "idx": 1, "y_field": "age"}], - "chart_type": "Bar" - }), - }, - { - "doctype": "Dashboard Chart", - "time_interval": "Yearly", - "chart_type": "Report", - "chart_name": "Downtime Analysis", - "timespan": "Last Year", - "filters_json": json.dumps({}), - "report_name": "Downtime Analysis", - "owner": "Administrator", - "bar": "Bar", - "custom_options": json.dumps({ - "colors": ["#ff5858"] - }), - }, - { - "doctype": "Dashboard Chart", - "time_interval": "Yearly", - "chart_type": "Report", - "chart_name": "Production Analysis", - "timespan": "Last Year", - "report_name": "Production Analytics", - "owner": "Administrator", - "filters_json": json.dumps({"company": company.name, "range":"Monthly"}), - "bar": "Bar", - "custom_options": json.dumps({ - "colors": ["#7cd6fd", "#ff5858", "#ffa00a", "#98d85b"] - }), - }, - { - "doctype": "Dashboard Chart", - "time_interval": "Yearly", - "chart_type": "Report", - "chart_name": "Job Card Analysis", - "timespan": "Last Year", - "report_name": "Job Card Summary", - "owner": "Administrator", - "filters_json": json.dumps({"company": company.name, "range":"Monthly"}), - "bar": "Bar", - "custom_options": json.dumps({ - "axisOptions": { - "xAxisMode": "tick" - }, - "barOptions": { - "stacked": 1 - }, - "colors": ["#ff5858", "#ffa00a", "#5e64ff", "#98d85b"] - }), - }, - ] - } - -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 1c9210b303df927e0b214c17d9fe1423176e682a Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 14 May 2020 18:58:25 +0530 Subject: [PATCH 69/78] made changes in the dashboards, renamed number cards --- erpnext/manufacturing/dashboard_fixtures.py | 8 +++ .../manufacturing/manufacturing.json | 8 ++- .../manufacturing/manufacturing.json | 54 +++++++++++++++++++ .../create_bom/create_bom.json | 7 ++- .../create_product/create_product.json | 7 ++- .../introduction_to_manufacturing.json | 5 +- .../onboarding_step/operation/operation.json | 8 ++- .../onboarding_step/warehouse/warehouse.json | 8 ++- .../work_order/work_order.json | 8 ++- .../workstation/workstation.json | 8 ++- 10 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py index de3775f52c..3029f488dc 100644 --- a/erpnext/manufacturing/dashboard_fixtures.py +++ b/erpnext/manufacturing/dashboard_fixtures.py @@ -45,6 +45,7 @@ def get_charts(): "time_interval": "Yearly", "chart_type": "Sum", "chart_name": "Produced Quantity", + "name": "Produced Quantity", "document_type": "Work Order", "filters_json": json.dumps({}), "group_by_type": "Count", @@ -61,6 +62,7 @@ def get_charts(): "time_interval": "Yearly", "chart_type": "Sum", "chart_name": "Completed Operation", + "name": "Completed Operation", "document_type": "Work Order Operation", "filters_json": json.dumps({}), "group_by_type": "Count", @@ -76,6 +78,7 @@ def get_charts(): "time_interval": "Yearly", "chart_type": "Report", "chart_name": "Work Order Analysis", + "name": "Work Order Analysis", "timespan": "Last Year", "report_name": "Work Order Summary", "owner": "Administrator", @@ -94,6 +97,7 @@ def get_charts(): "time_interval": "Yearly", "chart_type": "Report", "chart_name": "Quality Inspection Analysis", + "name": "Quality Inspection Analysis", "timespan": "Last Year", "report_name": "Quality Inspection Summary", "owner": "Administrator", @@ -112,6 +116,7 @@ def get_charts(): "time_interval": "Yearly", "chart_type": "Report", "chart_name": "Pending Work Order", + "name": "Pending Work Order", "timespan": "Last Year", "report_name": "Work Order Summary", "filters_json": json.dumps({"company": company, "charts_based_on": "Age"}), @@ -130,6 +135,7 @@ def get_charts(): "time_interval": "Yearly", "chart_type": "Report", "chart_name": "Last Month Downtime Analysis", + "name": "Last Month Downtime Analysis", "timespan": "Last Year", "filters_json": json.dumps({}), "report_name": "Downtime Analysis", @@ -142,6 +148,7 @@ def get_charts(): "time_interval": "Yearly", "chart_type": "Report", "chart_name": _("Work Order Qty Analysis"), + "name": _("Work Order Qty Analysis"), "timespan": "Last Year", "report_name": "Work Order Summary", "filters_json": json.dumps({"company": company, "charts_based_on": "Quantity"}), @@ -157,6 +164,7 @@ def get_charts(): "time_interval": "Yearly", "chart_type": "Report", "chart_name": "Job Card Analysis", + "name": "Job Card Analysis", "timespan": "Last Year", "report_name": "Job Card Summary", "owner": "Administrator", diff --git a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json index f6bbf3db85..6288169ee2 100644 --- a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json @@ -32,7 +32,11 @@ } ], "category": "Domains", - "charts": [], + "charts": [ + { + "chart_name": "Produced Quantity" + } + ], "creation": "2020-03-02 17:11:37.032604", "developer_mode_only": 0, "disable_user_customization": 0, @@ -42,7 +46,7 @@ "idx": 0, "is_standard": 1, "label": "Manufacturing", - "modified": "2020-05-14 15:06:45.963942", + "modified": "2020-05-14 18:57:26.810652", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", diff --git a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json new file mode 100644 index 0000000000..4092e9d87c --- /dev/null +++ b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json @@ -0,0 +1,54 @@ +{ + "allow_roles": [ + { + "role": "Manufacturing User" + }, + { + "role": "Manufacturing Manager" + }, + { + "role": "Item Manager" + }, + { + "role": "Stock User" + } + ], + "creation": "2020-05-05 16:37:08.238935", + "docstatus": 0, + "doctype": "Module Onboarding", + "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing", + "idx": 0, + "is_complete": 0, + "modified": "2020-05-14 19:12:17.289867", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Manufacturing", + "owner": "Administrator", + "steps": [ + { + "step": "Warehouse" + }, + { + "step": "Workstation" + }, + { + "step": "Operation" + }, + { + "step": "Create Product" + }, + { + "step": "Create BOM" + }, + { + "step": "Work Order" + }, + { + "step": "Introduction to Manufacturing" + } + ], + "subtitle": "Products, Raw Materials, BOM, Work Order and more.", + "success_message": "Manufacturing module is all setup!", + "title": "Let's Setup Manufacturing Module", + "user_can_dismiss": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json index 969c2fa5fe..866b871f2b 100644 --- a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json +++ b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json @@ -6,11 +6,14 @@ "idx": 0, "is_complete": 0, "is_mandatory": 1, + "is_single": 0, "is_skipped": 0, - "modified": "2020-05-05 16:41:20.239696", + "modified": "2020-05-14 19:11:57.153679", "modified_by": "Administrator", "name": "Create BOM", "owner": "Administrator", "reference_document": "BOM", - "title": "Create BOM" + "show_full_form": 1, + "title": "Create BOM (Bill of Material)", + "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/create_product/create_product.json b/erpnext/manufacturing/onboarding_step/create_product/create_product.json index 082f31ed6e..dd2fcb4ce7 100644 --- a/erpnext/manufacturing/onboarding_step/create_product/create_product.json +++ b/erpnext/manufacturing/onboarding_step/create_product/create_product.json @@ -6,11 +6,14 @@ "idx": 0, "is_complete": 0, "is_mandatory": 1, + "is_single": 0, "is_skipped": 0, - "modified": "2020-05-05 16:42:31.476275", + "modified": "2020-05-14 19:10:39.974370", "modified_by": "Administrator", "name": "Create Product", "owner": "Administrator", "reference_document": "Item", - "title": "Create Product" + "show_full_form": 0, + "title": "Create Product (Raw Materials / Finished Good)", + "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json b/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json index 6e5c3a9f45..eb7ab3a175 100644 --- a/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json +++ b/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json @@ -6,12 +6,15 @@ "idx": 0, "is_complete": 0, "is_mandatory": 0, + "is_single": 0, "is_skipped": 0, - "modified": "2020-05-05 16:40:23.676406", + "modified": "2020-05-14 19:11:57.152883", "modified_by": "Administrator", "name": "Introduction to Manufacturing", "owner": "Administrator", "reference_document": "Manufacturing Settings", + "show_full_form": 0, "title": "Manufacturing Settings", + "validate_action": 1, "video_url": "https://www.youtube.com/watch?v=UVGfzwOOZC4" } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/operation/operation.json b/erpnext/manufacturing/onboarding_step/operation/operation.json index 6f84db13ec..86e62e7411 100644 --- a/erpnext/manufacturing/onboarding_step/operation/operation.json +++ b/erpnext/manufacturing/onboarding_step/operation/operation.json @@ -6,10 +6,14 @@ "idx": 0, "is_complete": 0, "is_mandatory": 0, + "is_single": 0, "is_skipped": 0, - "modified": "2020-05-12 16:17:06.943067", + "modified": "2020-05-14 19:10:27.258157", "modified_by": "Administrator", "name": "Operation", "owner": "Administrator", - "title": "Create Operation" + "reference_document": "Operation", + "show_full_form": 0, + "title": "Create Operation", + "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json b/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json index 36f5204e0b..f187f57414 100644 --- a/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json +++ b/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json @@ -6,10 +6,14 @@ "idx": 0, "is_complete": 0, "is_mandatory": 0, + "is_single": 0, "is_skipped": 0, - "modified": "2020-05-12 16:16:48.345846", + "modified": "2020-05-14 19:10:07.439037", "modified_by": "Administrator", "name": "Warehouse", "owner": "Administrator", - "title": "Create Warehouse" + "reference_document": "Warehouse", + "show_full_form": 0, + "title": "Create Warehouse", + "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/work_order/work_order.json b/erpnext/manufacturing/onboarding_step/work_order/work_order.json index 57e4e7b3bc..e757c23d12 100644 --- a/erpnext/manufacturing/onboarding_step/work_order/work_order.json +++ b/erpnext/manufacturing/onboarding_step/work_order/work_order.json @@ -6,10 +6,14 @@ "idx": 0, "is_complete": 0, "is_mandatory": 0, + "is_single": 0, "is_skipped": 0, - "modified": "2020-05-12 16:17:33.675304", + "modified": "2020-05-14 19:11:57.145924", "modified_by": "Administrator", "name": "Work Order", "owner": "Administrator", - "title": "Create Work Order" + "reference_document": "Work Order", + "show_full_form": 1, + "title": "Create Work Order", + "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/workstation/workstation.json b/erpnext/manufacturing/onboarding_step/workstation/workstation.json index 6c1a06938b..b786443cbc 100644 --- a/erpnext/manufacturing/onboarding_step/workstation/workstation.json +++ b/erpnext/manufacturing/onboarding_step/workstation/workstation.json @@ -6,10 +6,14 @@ "idx": 0, "is_complete": 0, "is_mandatory": 0, + "is_single": 0, "is_skipped": 0, - "modified": "2020-05-12 16:16:58.808906", + "modified": "2020-05-14 19:10:20.930139", "modified_by": "Administrator", "name": "Workstation", "owner": "Administrator", - "title": "Create Workstation" + "reference_document": "Workstation", + "show_full_form": 0, + "title": "Create Workstation / Machine", + "validate_action": 1 } \ 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 70/78] 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 71/78] 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 72/78] 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 73/78] 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 From d3d3836943304137dafc792a1cb842f0934f5779 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 19 May 2020 11:53:28 +0530 Subject: [PATCH 74/78] style: cleaner conditions and returns --- .../connectors/woocommerce_connection.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py index 6d379f6ed5..44f87e0462 100644 --- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py +++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py @@ -75,14 +75,12 @@ def link_customer_and_address(raw_billing_data, raw_shipping_data, customer_name frappe.rename_doc("Customer", old_name, customer_name) billing_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Billing"}) shipping_address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": "Shipping"}) - else: - billing_address = create_address(raw_billing_data, customer, "Billing") - shipping_address = create_address(raw_shipping_data, customer, "Shipping") - contact = create_contact(raw_billing_data, customer) - - if customer_exists: rename_address(billing_address, customer) rename_address(shipping_address, customer) + else: + create_address(raw_billing_data, customer, "Billing") + create_address(raw_shipping_data, customer, "Shipping") + create_contact(raw_billing_data, customer) def create_contact(data, customer): email = data.get("email", None) @@ -111,8 +109,6 @@ def create_contact(data, customer): contact.flags.ignore_mandatory = True contact.save() - return contact - def create_address(raw_data, customer, address_type): address = frappe.new_doc("Address") @@ -134,8 +130,6 @@ def create_address(raw_data, customer, address_type): address.flags.ignore_mandatory = True address.save() - return address - def rename_address(address, customer): old_address_title = address.name new_address_title = customer.name + "-" + address.address_type From 84011b79d1c04d1bca4ac971e273f891af892edf Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 19 May 2020 13:00:54 +0530 Subject: [PATCH 75/78] fix: updated manufacturing onboarding and desk --- .../manufacturing/manufacturing.json | 9 +++--- .../downtime_entry/downtime_entry.json | 28 +++++++++++++------ .../manufacturing/manufacturing.json | 7 +++-- .../create_bom/create_bom.json | 4 +-- .../create_product/create_product.json | 4 +-- .../create_raw_materials.json | 19 +++++++++++++ .../explore_manufacturing_settings.json | 20 +++++++++++++ .../onboarding_step/operation/operation.json | 4 +-- .../onboarding_step/warehouse/warehouse.json | 4 +-- .../work_order/work_order.json | 4 +-- .../workstation/workstation.json | 4 +-- .../downtime_analysis/downtime_analysis.js | 8 +++--- .../downtime_analysis/downtime_analysis.py | 21 +++++++++++--- 13 files changed, 101 insertions(+), 35 deletions(-) create mode 100644 erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json create mode 100644 erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json diff --git a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json index c181d5d536..e35f1fb4ea 100644 --- a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json @@ -3,7 +3,7 @@ { "hidden": 0, "label": "Production", - "links": "[\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Orders released for production.\",\n \"label\": \"Work Order\",\n \"name\": \"Work Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Generate Material Requests (MRP) and Work Orders.\",\n \"label\": \"Production Plan\",\n \"name\": \"Production Plan\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Activity Type\"\n ],\n \"description\": \"Time Sheet for manufacturing.\",\n \"label\": \"Timesheet\",\n \"name\": \"Timesheet\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Card\",\n \"name\": \"Job Card\",\n \"type\": \"doctype\"\n }\n]" + "links": "[\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Orders released for production.\",\n \"label\": \"Work Order\",\n \"name\": \"Work Order\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"BOM\"\n ],\n \"description\": \"Generate Material Requests (MRP) and Work Orders.\",\n \"label\": \"Production Plan\",\n \"name\": \"Production Plan\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Card\",\n \"name\": \"Job Card\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Downtime Entry\",\n \"name\": \"Downtime Entry\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, @@ -43,11 +43,10 @@ "docstatus": 0, "doctype": "Desk Page", "extends_another_page": 0, - "hide_custom": 0, "idx": 0, "is_standard": 1, "label": "Manufacturing", - "modified": "2020-05-19 12:06:25.047481", + "modified": "2020-05-19 12:54:04.104444", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", @@ -78,7 +77,7 @@ "label": "Work Order", "link_to": "Work Order", "restrict_to_domain": "Manufacturing", - "stats_filter": "{ \"status\": \n (\n \"in\", [\"Draft\", \"Not Started\", \"In Process\"]\n )\n}", + "stats_filter": "{ \n \"status\": [\"in\", \n [\"Draft\", \"Not Started\", \"In Process\"]\n ]\n}", "type": "DocType" }, { @@ -86,7 +85,7 @@ "label": "Production Plan", "link_to": "Production Plan", "restrict_to_domain": "Manufacturing", - "stats_filter": "{ \"status\": \n (\n \"!=\", \"Completed\"\n )\n}", + "stats_filter": "{ \n \"status\": [\"not in\", [\"Completed\"]]\n}", "type": "DocType" }, { diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json index 6ec088ad9e..9acb4f0513 100644 --- a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json +++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json @@ -13,7 +13,9 @@ "to_time", "downtime", "downtime_reason_section", - "reason" + "stop_reason", + "column_break_9", + "remarks" ], "fields": [ { @@ -41,12 +43,6 @@ "fieldname": "column_break_4", "fieldtype": "Column Break" }, - { - "fieldname": "reason", - "fieldtype": "Text", - "label": "Reason", - "reqd": 1 - }, { "fieldname": "operator", "fieldtype": "Link", @@ -66,10 +62,26 @@ "fieldtype": "Float", "label": "Downtime", "read_only": 1 + }, + { + "fieldname": "stop_reason", + "fieldtype": "Select", + "label": "Stop Reason", + "options": "\nExcessive machine set up time\nUnplanned machine maintenance\nOn-machine press checks\nMachine operator errors\nMachine malfunction\nElectricity down\nOther", + "reqd": 1 + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "fieldname": "remarks", + "fieldtype": "Text", + "label": "Remarks" } ], "links": [], - "modified": "2020-04-20 17:34:51.299607", + "modified": "2020-05-19 12:59:37.358483", "modified_by": "Administrator", "module": "Manufacturing", "name": "Downtime Entry", diff --git a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json index 4092e9d87c..952d1f0e07 100644 --- a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json @@ -19,7 +19,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing", "idx": 0, "is_complete": 0, - "modified": "2020-05-14 19:12:17.289867", + "modified": "2020-05-19 12:51:42.744570", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", @@ -37,6 +37,9 @@ { "step": "Create Product" }, + { + "step": "Create Raw Materials" + }, { "step": "Create BOM" }, @@ -44,7 +47,7 @@ "step": "Work Order" }, { - "step": "Introduction to Manufacturing" + "step": "Explore Manufacturing Settings" } ], "subtitle": "Products, Raw Materials, BOM, Work Order and more.", diff --git a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json index 866b871f2b..84b4088f23 100644 --- a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json +++ b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json @@ -8,12 +8,12 @@ "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 19:11:57.153679", + "modified": "2020-05-19 12:51:31.315686", "modified_by": "Administrator", "name": "Create BOM", "owner": "Administrator", "reference_document": "BOM", "show_full_form": 1, - "title": "Create BOM (Bill of Material)", + "title": "Create a BOM (Bill of Material)", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/create_product/create_product.json b/erpnext/manufacturing/onboarding_step/create_product/create_product.json index dd2fcb4ce7..0ffa30158b 100644 --- a/erpnext/manufacturing/onboarding_step/create_product/create_product.json +++ b/erpnext/manufacturing/onboarding_step/create_product/create_product.json @@ -8,12 +8,12 @@ "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 19:10:39.974370", + "modified": "2020-05-19 12:50:59.010439", "modified_by": "Administrator", "name": "Create Product", "owner": "Administrator", "reference_document": "Item", "show_full_form": 0, - "title": "Create Product (Raw Materials / Finished Good)", + "title": "Create a Finished Good", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json new file mode 100644 index 0000000000..0764f2e44a --- /dev/null +++ b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json @@ -0,0 +1,19 @@ +{ + "action": "Create Entry", + "creation": "2020-05-19 11:53:17.295372", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-19 11:53:25.147837", + "modified_by": "Administrator", + "name": "Create Raw Materials", + "owner": "Administrator", + "reference_document": "Item", + "show_full_form": 0, + "title": "Create Raw Materials", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json new file mode 100644 index 0000000000..582aba40d6 --- /dev/null +++ b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json @@ -0,0 +1,20 @@ +{ + "action": "Update Settings", + "creation": "2020-05-19 11:55:11.378374", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 1, + "is_skipped": 0, + "modified": "2020-05-19 12:12:28.145366", + "modified_by": "Administrator", + "name": "Explore Manufacturing Settings", + "owner": "Administrator", + "reference_document": "Manufacturing Settings", + "show_full_form": 0, + "title": "Explore Manufacturing Settings", + "validate_action": 0, + "video_url": "https://www.youtube.com/watch?v=UVGfzwOOZC4" +} \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/operation/operation.json b/erpnext/manufacturing/onboarding_step/operation/operation.json index 86e62e7411..b532e6778c 100644 --- a/erpnext/manufacturing/onboarding_step/operation/operation.json +++ b/erpnext/manufacturing/onboarding_step/operation/operation.json @@ -8,12 +8,12 @@ "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 19:10:27.258157", + "modified": "2020-05-19 12:50:41.642754", "modified_by": "Administrator", "name": "Operation", "owner": "Administrator", "reference_document": "Operation", "show_full_form": 0, - "title": "Create Operation", + "title": "Create a Operation", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json b/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json index f187f57414..e23bd33b78 100644 --- a/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json +++ b/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json @@ -8,12 +8,12 @@ "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 19:10:07.439037", + "modified": "2020-05-19 12:50:13.766712", "modified_by": "Administrator", "name": "Warehouse", "owner": "Administrator", "reference_document": "Warehouse", "show_full_form": 0, - "title": "Create Warehouse", + "title": "Create a Warehouse", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/work_order/work_order.json b/erpnext/manufacturing/onboarding_step/work_order/work_order.json index e757c23d12..c63363e7cb 100644 --- a/erpnext/manufacturing/onboarding_step/work_order/work_order.json +++ b/erpnext/manufacturing/onboarding_step/work_order/work_order.json @@ -8,12 +8,12 @@ "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 19:11:57.145924", + "modified": "2020-05-19 12:51:38.133150", "modified_by": "Administrator", "name": "Work Order", "owner": "Administrator", "reference_document": "Work Order", "show_full_form": 1, - "title": "Create Work Order", + "title": "Create a Work Order", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/workstation/workstation.json b/erpnext/manufacturing/onboarding_step/workstation/workstation.json index b786443cbc..df244bb494 100644 --- a/erpnext/manufacturing/onboarding_step/workstation/workstation.json +++ b/erpnext/manufacturing/onboarding_step/workstation/workstation.json @@ -8,12 +8,12 @@ "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 19:10:20.930139", + "modified": "2020-05-19 12:50:33.938176", "modified_by": "Administrator", "name": "Workstation", "owner": "Administrator", "reference_document": "Workstation", "show_full_form": 0, - "title": "Create Workstation / Machine", + "title": "Create a Workstation / Machine", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js index e20342792f..ff32dbed98 100644 --- a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js +++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js @@ -7,15 +7,15 @@ frappe.query_reports["Downtime Analysis"] = { { label: __("From Date"), fieldname:"from_date", - fieldtype: "Date", - default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), + fieldtype: "Datetime", + default: frappe.datetime.add_months(frappe.datetime.now_datetime(), -1), reqd: 1 }, { label: __("To Date"), fieldname:"to_date", - fieldtype: "Date", - default: frappe.datetime.get_today(), + fieldtype: "Datetime", + default: frappe.datetime.now_datetime(), reqd: 1, }, { diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py index dfc6b02b84..2b2be4faa3 100644 --- a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py +++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py @@ -16,7 +16,7 @@ def execute(filters=None): def get_data(filters): query_filters = {} - fields = ["workstation", "operator", "from_time", "to_time", "downtime", "reason"] + fields = ["name", "workstation", "operator", "from_time", "to_time", "downtime", "stop_reason", "remarks"] query_filters["from_time"] = (">=", filters.get("from_date")) query_filters["to_time"] = ("<=", filters.get("to_date")) @@ -54,6 +54,13 @@ def get_chart_data(data, columns): def get_columns(filters): return [ + { + "label": _("ID"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Downtime Entry", + "width": 100 + }, { "label": _("Machine"), "fieldname": "workstation", @@ -87,9 +94,15 @@ def get_columns(filters): "width": 150 }, { - "label": _("Reason"), - "fieldname": "reason", + "label": _("Stop Reason"), + "fieldname": "stop_reason", + "fieldtype": "Data", + "width": 220 + }, + { + "label": _("Remarks"), + "fieldname": "remarks", "fieldtype": "Text", - "width": 180 + "width": 100 } ] \ No newline at end of file From 19ecde21979b7503f937c40d3a069de56a0b21af Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 19 May 2020 14:30:42 +0530 Subject: [PATCH 76/78] fix: add naming series (#21769) --- .../doctype/quality_meeting/quality_meeting.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json index 0849fd7aeb..7691fe3587 100644 --- a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json +++ b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json @@ -1,10 +1,12 @@ { - "autoname": "format:MTNG-{date}", + "actions": [], + "autoname": "naming_series:", "creation": "2018-10-15 16:25:41.548432", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "naming_series", "date", "cb_00", "status", @@ -53,9 +55,16 @@ "fieldname": "sb_01", "fieldtype": "Section Break", "label": "Minutes" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "MTNG-.YYYY.-.MM.-.DD.-" } ], - "modified": "2019-07-13 19:57:40.500541", + "links": [], + "modified": "2020-05-19 13:18:59.821740", "modified_by": "Administrator", "module": "Quality Management", "name": "Quality Meeting", From 65c90959d1b45ae712e15c94ebda7c9684c390a3 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 19 May 2020 15:54:24 +0530 Subject: [PATCH 77/78] fix: renamed manufacturing dashboards to manufacturing (#21776) --- erpnext/accounts/dashboard_fixtures.py | 4 ++-- erpnext/manufacturing/dashboard_fixtures.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/dashboard_fixtures.py b/erpnext/accounts/dashboard_fixtures.py index 214e467b5c..cdd375802d 100644 --- a/erpnext/accounts/dashboard_fixtures.py +++ b/erpnext/accounts/dashboard_fixtures.py @@ -26,8 +26,8 @@ def get_data(): def get_dashboards(): return [{ - "name": "Accounts Dashboard", - "dashboard_name": "Accounts Dashboard", + "name": "Accounts", + "dashboard_name": "Accounts", "doctype": "Dashboard", "charts": [ { "chart": "Profit and Loss" , "width": "Full"}, diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py index 587a032241..ef61f230ac 100644 --- a/erpnext/manufacturing/dashboard_fixtures.py +++ b/erpnext/manufacturing/dashboard_fixtures.py @@ -15,8 +15,8 @@ def get_data(): def get_dashboards(): return [{ - "name": "Manufacturing Dashboard", - "dashboard_name": "Manufacturing Dashboard", + "name": "Manufacturing", + "dashboard_name": "Manufacturing", "charts": [ { "chart": "Produced Quantity", "width": "Half" }, { "chart": "Completed Operation", "width": "Half" }, From 8caffe2c712bb7b2d7ab75e10538b9f22cf7d958 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 19 May 2020 16:37:34 +0530 Subject: [PATCH 78/78] refactor: use text editor in issue web form (#21767) --- erpnext/support/web_form/issues/issues.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/support/web_form/issues/issues.json b/erpnext/support/web_form/issues/issues.json index 0f15e4737f..1df9fb7969 100644 --- a/erpnext/support/web_form/issues/issues.json +++ b/erpnext/support/web_form/issues/issues.json @@ -18,7 +18,7 @@ "is_standard": 1, "login_required": 1, "max_attachment_size": 0, - "modified": "2020-03-06 05:24:05.749664", + "modified": "2020-05-19 13:01:10.729088", "modified_by": "Administrator", "module": "Support", "name": "issues", @@ -76,7 +76,7 @@ { "allow_read_on_all_link_options": 0, "fieldname": "description", - "fieldtype": "Text", + "fieldtype": "Text Editor", "hidden": 0, "label": "Description", "max_length": 0,