From 9962ba86d0db913d53bf87736e1fdd2194436f09 Mon Sep 17 00:00:00 2001 From: "hasnain2808@gmail.com" Date: Wed, 16 Dec 2020 14:41:04 +0530 Subject: [PATCH 01/34] fix: charts not displaying when tree_type changed --- .../purchase_analytics/purchase_analytics.js | 72 +++++++++-------- .../report/sales_analytics/sales_analytics.js | 79 ++++++++++--------- 2 files changed, 81 insertions(+), 70 deletions(-) diff --git a/erpnext/buying/report/purchase_analytics/purchase_analytics.js b/erpnext/buying/report/purchase_analytics/purchase_analytics.js index e17973c337..7ee9f2c372 100644 --- a/erpnext/buying/report/purchase_analytics/purchase_analytics.js +++ b/erpnext/buying/report/purchase_analytics/purchase_analytics.js @@ -75,62 +75,66 @@ frappe.query_reports["Purchase Analytics"] = { return Object.assign(options, { checkboxColumn: true, events: { - onCheckRow: function(data) { + onCheckRow: function (data) { + if (!data) return; + + const data_doctype = $( + data[2].html + )[0].attributes.getNamedItem("data-doctype").value; + const tree_type = frappe.query_report.filters[0].value; + if (data_doctype != tree_type) return; + row_name = data[2].content; length = data.length; - var tree_type = frappe.query_report.filters[0].value; - - if(tree_type == "Supplier" || tree_type == "Item") { - row_values = data.slice(4,length-1).map(function (column) { - return column.content; - }) - } - else { - row_values = data.slice(3,length-1).map(function (column) { - return column.content; - }) + if (tree_type == "Supplier" || tree_type == "Item") { + row_values = data + .slice(4, length - 1) + .map(function (column) { + return column.content; + }); + } else { + row_values = data + .slice(3, length - 1) + .map(function (column) { + return column.content; + }); } - entry = { - 'name':row_name, - 'values':row_values - } + entry = { + name: row_name, + values: row_values, + }; let raw_data = frappe.query_report.chart.data; let new_datasets = raw_data.datasets; - var found = false; + let found = false; - for(var i=0; i < new_datasets.length;i++){ - if(new_datasets[i].name == row_name){ + for (let i = 0; i < new_datasets.length; i++) { + if (new_datasets[i].name == row_name) { found = true; - new_datasets.splice(i,1); + new_datasets.splice(i, 1); break; } } - if(!found){ + if (!found) { new_datasets.push(entry); } - let new_data = { labels: raw_data.labels, - datasets: new_datasets - } - - setTimeout(() => { - frappe.query_report.chart.update(new_data) - },500) - - - setTimeout(() => { - frappe.query_report.chart.draw(true); - }, 1000) + datasets: new_datasets, + }; + chart_options = { + data: new_data, + type: "line", + }; + frappe.query_report.render_chart(chart_options); frappe.query_report.raw_chart_data = new_data; }, - } + }, }); } } diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.js b/erpnext/selling/report/sales_analytics/sales_analytics.js index 0e565a3fb6..aad6bfd5ef 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.js +++ b/erpnext/selling/report/sales_analytics/sales_analytics.js @@ -74,67 +74,74 @@ frappe.query_reports["Sales Analytics"] = { return Object.assign(options, { checkboxColumn: true, events: { - onCheckRow: function(data) { + onCheckRow: function (data) { + if (!data) return; + + const data_doctype = $( + data[2].html + )[0].attributes.getNamedItem("data-doctype").value; + const tree_type = frappe.query_report.filters[0].value; + if (data_doctype != tree_type) return; + row_name = data[2].content; length = data.length; - var tree_type = frappe.query_report.filters[0].value; - - if(tree_type == "Customer") { - row_values = data.slice(4,length-1).map(function (column) { - return column.content; - }) + if (tree_type == "Customer") { + row_values = data + .slice(4, length - 1) + .map(function (column) { + return column.content; + }); } else if (tree_type == "Item") { - row_values = data.slice(5,length-1).map(function (column) { - return column.content; - }) - } - else { - row_values = data.slice(3,length-1).map(function (column) { - return column.content; - }) + row_values = data + .slice(5, length - 1) + .map(function (column) { + return column.content; + }); + } else { + row_values = data + .slice(3, length - 1) + .map(function (column) { + return column.content; + }); } entry = { - 'name':row_name, - 'values':row_values - } + name: row_name, + values: row_values, + }; let raw_data = frappe.query_report.chart.data; let new_datasets = raw_data.datasets; - var found = false; + let found = false; - for(var i=0; i < new_datasets.length;i++){ - if(new_datasets[i].name == row_name){ + for (let i = 0; i < new_datasets.length; i++) { + if (new_datasets[i].name == row_name) { found = true; - new_datasets.splice(i,1); + new_datasets.splice(i, 1); break; } } - if(!found){ + if (!found) { new_datasets.push(entry); } let new_data = { labels: raw_data.labels, - datasets: new_datasets - } - - setTimeout(() => { - frappe.query_report.chart.update(new_data) - }, 500) - - - setTimeout(() => { - frappe.query_report.chart.draw(true); - }, 1000) + datasets: new_datasets, + }; + chart_options = { + data: new_data, + type: "line", + }; + frappe.query_report.render_chart(chart_options); frappe.query_report.raw_chart_data = new_data; }, - } - }) + }, + }); }, } From d6277cdc7f08f14081b7e425f8a901472c4a73cb Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 18 Dec 2020 21:37:19 +0530 Subject: [PATCH 02/34] feat: Value Based and Numeric Quality Inspection - Acceptance Formula is optional - Choose between Value based and Numeric QI - If numeric, select single or multiple readings - Added Min, Max and Mean Values for numeric inspection to avoid formula usage - Deprecated code cleanup in js file --- .../item_quality_inspection_parameter.json | 54 +++++++++- .../quality_inspection/quality_inspection.js | 102 +++++++++--------- .../quality_inspection.json | 4 +- .../quality_inspection/quality_inspection.py | 98 +++++++++++++---- .../quality_inspection_reading.json | 93 ++++++++++++++-- .../quality_inspection_template.py | 4 +- 6 files changed, 268 insertions(+), 87 deletions(-) diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json index 888bc2de47..f450128157 100644 --- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json +++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json @@ -8,8 +8,14 @@ "field_order": [ "specification", "value", + "value_based", + "single_reading", "column_break_3", - "acceptance_formula" + "formula_based_criteria", + "acceptance_formula", + "min_value", + "max_value", + "mean_value" ], "fields": [ { @@ -24,10 +30,11 @@ "width": "200px" }, { + "depends_on": "eval:(!doc.formula_based_criteria && doc.value_based)", "fieldname": "value", "fieldtype": "Data", "in_list_view": 1, - "label": "Acceptance Criteria", + "label": "Acceptance Criteria Value", "oldfieldname": "value", "oldfieldtype": "Data" }, @@ -36,17 +43,56 @@ "fieldtype": "Column Break" }, { - "description": "Simple Python formula based on numeric Readings.
Example 1: reading_1 > 0.2 and reading_1 < 0.5
\nExample 2: (reading_1 + reading_2) / 2 < 10", + "depends_on": "formula_based_criteria", + "description": "Simple Python formula applied on Reading fields.
Numeric eg.: reading_1 > 0.2 and reading_1 < 0.5
\nValue based eg.: reading_value in (\"A\", \"B\", \"C)", "fieldname": "acceptance_formula", "fieldtype": "Code", "in_list_view": 1, "label": "Acceptance Criteria Formula" + }, + { + "default": "0", + "fieldname": "formula_based_criteria", + "fieldtype": "Check", + "label": "Formula Based Criteria" + }, + { + "default": "0", + "depends_on": "eval:!doc.value_based", + "fieldname": "single_reading", + "fieldtype": "Check", + "label": "Single Reading" + }, + { + "depends_on": "eval:(!doc.formula_based_criteria && !doc.single_reading && !doc.value_based)", + "fieldname": "mean_value", + "fieldtype": "Float", + "label": "Mean Value" + }, + { + "depends_on": "eval:(!doc.formula_based_criteria && !doc.value_based)", + "fieldname": "min_value", + "fieldtype": "Float", + "label": "Minimum Value" + }, + { + "depends_on": "eval:(!doc.formula_based_criteria && !doc.value_based)", + "fieldname": "max_value", + "fieldtype": "Float", + "label": "Maximum Value" + }, + { + "default": "0", + "description": "Non-numeric Inspection.", + "fieldname": "value_based", + "fieldtype": "Check", + "label": "Value Based" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-11-16 16:33:42.421842", + "modified": "2020-12-18 21:03:29.828723", "modified_by": "Administrator", "module": "Stock", "name": "Item Quality Inspection Parameter", diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js index 376848afaa..f0bf9aed80 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js @@ -4,6 +4,54 @@ cur_frm.cscript.refresh = cur_frm.cscript.inspection_type; frappe.ui.form.on("Quality Inspection", { + setup: function(frm) { + frm.set_query("batch_no", function() { + return { + filters: { + "item": frm.doc.item_code + } + } + }); + + // Serial No based on item_code + frm.set_query("item_serial_no", function() { + var filters = {}; + if (frm.doc.item_code) { + filters = { + 'item_code': frm.doc.item_code + } + } + return { filters: filters } + }); + + // item code based on GRN/DN + frm.set_query("item_code", function(doc) { + let doctype = doc.reference_type; + + if (doc.reference_type !== "Job Card") { + doctype = (doc.reference_type == "Stock Entry") ? + "Stock Entry Detail" : doc.reference_type + " Item"; + } + + if (doc.reference_type && doc.reference_name) { + let filters = { + "from": doctype, + "inspection_type": doc.inspection_type + }; + + if (doc.reference_type == doctype) + filters["reference_name"] = doc.reference_name; + else + filters["parent"] = doc.reference_name; + + return { + query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query", + filters: filters + }; + } + }); + }, + item_code: function(frm) { if (frm.doc.item_code) { return frm.call({ @@ -26,55 +74,5 @@ frappe.ui.form.on("Quality Inspection", { } }); } - } -}) - -// item code based on GRN/DN -cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) { - let doctype = doc.reference_type; - - if (doc.reference_type !== "Job Card") { - doctype = (doc.reference_type == "Stock Entry") ? - "Stock Entry Detail" : doc.reference_type + " Item"; - } - - if (doc.reference_type && doc.reference_name) { - let filters = { - "from": doctype, - "inspection_type": doc.inspection_type - }; - - if (doc.reference_type == doctype) - filters["reference_name"] = doc.reference_name; - else - filters["parent"] = doc.reference_name; - - return { - query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query", - filters: filters - }; - } -}, - -// Serial No based on item_code -cur_frm.fields_dict['item_serial_no'].get_query = function(doc, cdt, cdn) { - var filters = {}; - if (doc.item_code) { - filters = { - 'item_code': doc.item_code - } - } - return { filters: filters } -} - -cur_frm.set_query("batch_no", function(doc) { - return { - filters: { - "item": doc.item_code - } - } -}) - -cur_frm.add_fetch('item_code', 'item_name', 'item_name'); -cur_frm.add_fetch('item_code', 'description', 'description'); - + }, +}) \ No newline at end of file diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json index f6d76194d9..edfe7e98b2 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json @@ -136,6 +136,7 @@ "width": "50%" }, { + "fetch_from": "item_code.item_name", "fieldname": "item_name", "fieldtype": "Data", "in_global_search": 1, @@ -143,6 +144,7 @@ "read_only": 1 }, { + "fetch_from": "item_code.description", "fieldname": "description", "fieldtype": "Small Text", "label": "Description", @@ -236,7 +238,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-19 17:06:05.409963", + "modified": "2020-12-18 19:59:55.710300", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection", diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index ae4eb9b995..a7a023bcbf 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -6,7 +6,7 @@ import frappe from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc from frappe import _ -from frappe.utils import flt +from frappe.utils import flt, cint from erpnext.stock.doctype.quality_inspection_template.quality_inspection_template \ import get_template_details @@ -16,7 +16,7 @@ class QualityInspection(Document): self.get_item_specification_details() if self.readings: - self.set_status_based_on_acceptance_formula() + self.inspect_and_set_status() def get_item_specification_details(self): if not self.quality_inspection_template: @@ -29,9 +29,7 @@ class QualityInspection(Document): parameters = get_template_details(self.quality_inspection_template) for d in parameters: child = self.append('readings', {}) - child.specification = d.specification - child.value = d.value - child.acceptance_formula = d.acceptance_formula + child.update(d) child.status = "Accepted" def get_quality_inspection_template(self): @@ -76,28 +74,84 @@ class QualityInspection(Document): """.format(parent_doc=self.reference_type, child_doc=doctype), (quality_inspection, self.modified, self.reference_name, self.item_code)) - def set_status_based_on_acceptance_formula(self): + def inspect_and_set_status(self): for reading in self.readings: - if not reading.acceptance_formula: continue + if reading.formula_based_criteria: + self.set_status_based_on_acceptance_formula(reading) + else: + self.set_status_based_on_acceptance_values(reading) + + def set_status_based_on_acceptance_values(self, reading): + if cint(reading.value_based): + result = reading.get("reading_value") == reading.get("value") + else: + # numeric readings + if cint(reading.single_reading): + reading_1 = flt(reading.get("reading_1")) + result = flt(reading.get("min_value")) <= reading_1 <= flt(reading.get("max_value")) + else: + result = self.min_max_criteria_passed(reading) and self.mean_criteria_passed(reading) + + reading.status = "Accepted" if result else "Rejected" + + def min_max_criteria_passed(self, reading): + """Determine whether all readings fall in the acceptable range.""" + for i in range(1, 11): + reading_field = reading.get("reading_" + str(i)) + if reading_field is not None: + result = flt(reading.get("min_value")) <= flt(reading_field) <= flt(reading.get("max_value")) + if not result: return False + return True + + def mean_criteria_passed(self, reading): + """Determine whether mean of all readings is acceptable.""" + if reading.get("mean_value"): + from statistics import mean + readings_list = [] - condition = reading.acceptance_formula - data = {} for i in range(1, 11): - field = "reading_" + str(i) - data[field] = flt(reading.get(field)) or 0 + reading_value = reading.get("reading_" + str(i)) + if reading_value is not None: + readings_list.append(flt(reading_value)) - try: - result = frappe.safe_eval(condition, None, data) - reading.status = "Accepted" if result else "Rejected" - except SyntaxError: - frappe.throw(_("Row #{0}: Acceptance Criteria Formula is incorrect.").format(reading.idx), - title=_("Invalid Formula")) - except NameError as e: - field = frappe.bold(e.args[0].split()[1]) - frappe.throw(_("Row #{0}: {1} is not a valid reading field. Please refer to the field description.") - .format(reading.idx, field), - title=_("Invalid Formula")) + actual_mean = mean(readings_list) if readings_list else 0 + return True if actual_mean == reading.get("mean_value") else False + return True # no mean value, nothing to check + + def set_status_based_on_acceptance_formula(self, reading): + if not reading.acceptance_formula: + frappe.throw(_("Row #{0}: Acceptance Criteria Formula is required.").format(reading.idx), + title=_("Missing Formula")) + + condition = reading.acceptance_formula + data = self.get_formula_evaluation_data(reading) + + try: + result = frappe.safe_eval(condition, None, data) + reading.status = "Accepted" if result else "Rejected" + except NameError as e: + field = frappe.bold(e.args[0].split()[1]) + frappe.throw(_("Row #{0}: {1} is not a valid reading field. Please refer to the field description.") + .format(reading.idx, field), + title=_("Invalid Formula")) + except Exception: + frappe.throw(_("Row #{0}: Acceptance Criteria Formula is incorrect.").format(reading.idx), + title=_("Invalid Formula")) + + def get_formula_evaluation_data(self, reading): + data = {} + if cint(reading.value_based): + data = {"reading_value": reading.get("reading_value")} + else: + # numeric readings + data = {"reading_1": flt(reading.get("reading_1"))} + if not cint(reading.single_reading): + # if multiple numeric readings add all readings to data + for i in range(2, 11): + field = "reading_" + str(i) + data[field] = flt(reading.get(field)) + return data @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs diff --git a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json index c1976dd1fb..db95fabee0 100644 --- a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json +++ b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json @@ -7,21 +7,30 @@ "engine": "InnoDB", "field_order": [ "specification", - "value", "status", + "value", + "value_based", "column_break_4", + "formula_based_criteria", "acceptance_formula", + "min_value", + "max_value", + "mean_value", "section_break_3", + "reading_value", + "section_break_14", + "single_reading", + "section_break_12", "reading_1", "reading_2", "reading_3", - "column_break_10", "reading_4", + "column_break_10", "reading_5", "reading_6", - "column_break_14", "reading_7", "reading_8", + "column_break_14", "reading_9", "reading_10" ], @@ -38,10 +47,11 @@ }, { "columns": 2, + "depends_on": "eval:(!doc.formula_based_criteria && doc.value_based)", "fieldname": "value", "fieldtype": "Data", "in_list_view": 1, - "label": "Acceptance Criteria", + "label": "Acceptance Criteria Value", "oldfieldname": "value", "oldfieldtype": "Data" }, @@ -56,6 +66,7 @@ }, { "columns": 1, + "depends_on": "eval:!doc.single_reading", "fieldname": "reading_2", "fieldtype": "Data", "in_list_view": 1, @@ -65,6 +76,7 @@ }, { "columns": 1, + "depends_on": "eval:!doc.single_reading", "fieldname": "reading_3", "fieldtype": "Data", "in_list_view": 1, @@ -73,6 +85,7 @@ "oldfieldtype": "Data" }, { + "depends_on": "eval:!doc.single_reading", "fieldname": "reading_4", "fieldtype": "Data", "label": "Reading 4", @@ -80,6 +93,7 @@ "oldfieldtype": "Data" }, { + "depends_on": "eval:!doc.single_reading", "fieldname": "reading_5", "fieldtype": "Data", "label": "Reading 5", @@ -87,6 +101,7 @@ "oldfieldtype": "Data" }, { + "depends_on": "eval:!doc.single_reading", "fieldname": "reading_6", "fieldtype": "Data", "label": "Reading 6", @@ -94,6 +109,7 @@ "oldfieldtype": "Data" }, { + "depends_on": "eval:!doc.single_reading", "fieldname": "reading_7", "fieldtype": "Data", "label": "Reading 7", @@ -101,6 +117,7 @@ "oldfieldtype": "Data" }, { + "depends_on": "eval:!doc.single_reading", "fieldname": "reading_8", "fieldtype": "Data", "label": "Reading 8", @@ -108,6 +125,7 @@ "oldfieldtype": "Data" }, { + "depends_on": "eval:!doc.single_reading", "fieldname": "reading_9", "fieldtype": "Data", "label": "Reading 9", @@ -115,6 +133,7 @@ "oldfieldtype": "Data" }, { + "depends_on": "eval:!doc.single_reading", "fieldname": "reading_10", "fieldtype": "Data", "label": "Reading 10", @@ -133,15 +152,18 @@ "options": "Accepted\nRejected" }, { + "depends_on": "value_based", "fieldname": "section_break_3", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Value Based Inspection" }, { "fieldname": "column_break_4", "fieldtype": "Column Break" }, { - "description": "Simple Python formula based on numeric Readings.
Example 1: reading_1 > 0.2 and reading_1 < 0.5
\nExample 2: (reading_1 + reading_2) / 2 < 10", + "depends_on": "formula_based_criteria", + "description": "Simple Python formula applied on Reading fields.
Numeric eg.: reading_1 > 0.2 and reading_1 < 0.5
\nValue based eg.: reading_value in (\"A\", \"B\", \"C)", "fieldname": "acceptance_formula", "fieldtype": "Code", "label": "Acceptance Criteria Formula" @@ -153,12 +175,69 @@ { "fieldname": "column_break_14", "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "formula_based_criteria", + "fieldtype": "Check", + "label": "Formula Based Criteria" + }, + { + "depends_on": "eval:(!doc.formula_based_criteria && !doc.single_reading && !doc.value_based)", + "fieldname": "mean_value", + "fieldtype": "Float", + "label": "Mean Value" + }, + { + "default": "0", + "fieldname": "single_reading", + "fieldtype": "Check", + "label": "Single Reading" + }, + { + "depends_on": "eval:!doc.value_based", + "fieldname": "section_break_12", + "fieldtype": "Section Break", + "hide_border": 1 + }, + { + "depends_on": "eval:(!doc.formula_based_criteria && !doc.value_based)", + "description": "Applied on each reading.", + "fieldname": "min_value", + "fieldtype": "Float", + "label": "Minimum Value" + }, + { + "depends_on": "eval:(!doc.formula_based_criteria && !doc.value_based)", + "description": "Applied on each reading.", + "fieldname": "max_value", + "fieldtype": "Float", + "label": "Maximum Value" + }, + { + "default": "0", + "description": "Non-numeric Inspection.", + "fieldname": "value_based", + "fieldtype": "Check", + "label": "Value Based" + }, + { + "depends_on": "value_based", + "fieldname": "reading_value", + "fieldtype": "Data", + "label": "Reading Value" + }, + { + "depends_on": "eval:!doc.value_based", + "fieldname": "section_break_14", + "fieldtype": "Section Break", + "label": "Numeric Inspection" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-11-16 16:34:29.947856", + "modified": "2020-12-18 21:02:04.865777", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection Reading", diff --git a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py index e2848469b8..7dd0febc20 100644 --- a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py +++ b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py @@ -13,6 +13,8 @@ def get_template_details(template): if not template: return [] return frappe.get_all('Item Quality Inspection Parameter', - fields=["specification", "value", "acceptance_formula"], + fields=["specification", "value", "acceptance_formula", + "value_based", "formula_based_criteria", "single_reading", + "min_value", "max_value", "mean_value"], filters={'parenttype': 'Quality Inspection Template', 'parent': template}, order_by="idx") \ No newline at end of file From 0c4f97368d05f2277ccca54b583be0a104acf7a9 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 21 Dec 2020 11:44:48 +0530 Subject: [PATCH 03/34] chore: UX improvement - Removed 'single reading' checkbox, unnecessary - Removed 'Mean' field and added computed mean to formula data - Changed 'Value Based' to 'Non-Numeric' - Re-arranged fields --- .../item_quality_inspection_parameter.json | 43 +++++------- .../quality_inspection/quality_inspection.py | 57 +++++++--------- .../quality_inspection_reading.json | 65 +++++-------------- .../quality_inspection_template.py | 3 +- 4 files changed, 58 insertions(+), 110 deletions(-) diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json index f450128157..9b980a1e01 100644 --- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json +++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json @@ -8,14 +8,12 @@ "field_order": [ "specification", "value", - "value_based", - "single_reading", + "non_numeric", "column_break_3", - "formula_based_criteria", - "acceptance_formula", "min_value", "max_value", - "mean_value" + "formula_based_criteria", + "acceptance_formula" ], "fields": [ { @@ -27,10 +25,10 @@ "oldfieldtype": "Data", "print_width": "200px", "reqd": 1, - "width": "200px" + "width": "100px" }, { - "depends_on": "eval:(!doc.formula_based_criteria && doc.value_based)", + "depends_on": "eval:(!doc.formula_based_criteria && doc.non_numeric)", "fieldname": "value", "fieldtype": "Data", "in_list_view": 1, @@ -44,10 +42,9 @@ }, { "depends_on": "formula_based_criteria", - "description": "Simple Python formula applied on Reading fields.
Numeric eg.: reading_1 > 0.2 and reading_1 < 0.5
\nValue based eg.: reading_value in (\"A\", \"B\", \"C)", + "description": "Simple Python formula applied on Reading fields.
Numeric eg. 1: reading_1 > 0.2 and reading_1 < 0.5
\nNumeric eg. 2: mean > 3.5 (mean of populated fields)
\nValue based eg.: reading_value in (\"A\", \"B\", \"C)", "fieldname": "acceptance_formula", "fieldtype": "Code", - "in_list_view": 1, "label": "Acceptance Criteria Formula" }, { @@ -57,42 +54,32 @@ "label": "Formula Based Criteria" }, { - "default": "0", - "depends_on": "eval:!doc.value_based", - "fieldname": "single_reading", - "fieldtype": "Check", - "label": "Single Reading" - }, - { - "depends_on": "eval:(!doc.formula_based_criteria && !doc.single_reading && !doc.value_based)", - "fieldname": "mean_value", - "fieldtype": "Float", - "label": "Mean Value" - }, - { - "depends_on": "eval:(!doc.formula_based_criteria && !doc.value_based)", + "depends_on": "eval:(!doc.formula_based_criteria && !doc.non_numeric)", "fieldname": "min_value", "fieldtype": "Float", + "in_list_view": 1, "label": "Minimum Value" }, { - "depends_on": "eval:(!doc.formula_based_criteria && !doc.value_based)", + "depends_on": "eval:(!doc.formula_based_criteria && !doc.non_numeric)", "fieldname": "max_value", "fieldtype": "Float", + "in_list_view": 1, "label": "Maximum Value" }, { "default": "0", - "description": "Non-numeric Inspection.", - "fieldname": "value_based", + "fieldname": "non_numeric", "fieldtype": "Check", - "label": "Value Based" + "in_list_view": 1, + "label": "Non-Numeric", + "width": "80px" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-12-18 21:03:29.828723", + "modified": "2020-12-21 11:37:55.387677", "modified_by": "Administrator", "module": "Stock", "name": "Item Quality Inspection Parameter", diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index a7a023bcbf..f582658d87 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -79,46 +79,27 @@ class QualityInspection(Document): if reading.formula_based_criteria: self.set_status_based_on_acceptance_formula(reading) else: + # if not formula based check acceptance values set self.set_status_based_on_acceptance_values(reading) def set_status_based_on_acceptance_values(self, reading): - if cint(reading.value_based): + if cint(reading.non_numeric): result = reading.get("reading_value") == reading.get("value") else: # numeric readings - if cint(reading.single_reading): - reading_1 = flt(reading.get("reading_1")) - result = flt(reading.get("min_value")) <= reading_1 <= flt(reading.get("max_value")) - else: - result = self.min_max_criteria_passed(reading) and self.mean_criteria_passed(reading) + result = self.min_max_criteria_passed(reading) reading.status = "Accepted" if result else "Rejected" def min_max_criteria_passed(self, reading): """Determine whether all readings fall in the acceptable range.""" for i in range(1, 11): - reading_field = reading.get("reading_" + str(i)) - if reading_field is not None: - result = flt(reading.get("min_value")) <= flt(reading_field) <= flt(reading.get("max_value")) + reading_value = reading.get("reading_" + str(i)) + if reading_value is not None and reading_value.strip(): + result = flt(reading.get("min_value")) <= flt(reading_value) <= flt(reading.get("max_value")) if not result: return False return True - def mean_criteria_passed(self, reading): - """Determine whether mean of all readings is acceptable.""" - if reading.get("mean_value"): - from statistics import mean - readings_list = [] - - for i in range(1, 11): - reading_value = reading.get("reading_" + str(i)) - if reading_value is not None: - readings_list.append(flt(reading_value)) - - actual_mean = mean(readings_list) if readings_list else 0 - return True if actual_mean == reading.get("mean_value") else False - - return True # no mean value, nothing to check - def set_status_based_on_acceptance_formula(self, reading): if not reading.acceptance_formula: frappe.throw(_("Row #{0}: Acceptance Criteria Formula is required.").format(reading.idx), @@ -141,18 +122,30 @@ class QualityInspection(Document): def get_formula_evaluation_data(self, reading): data = {} - if cint(reading.value_based): + if cint(reading.non_numeric): data = {"reading_value": reading.get("reading_value")} else: # numeric readings - data = {"reading_1": flt(reading.get("reading_1"))} - if not cint(reading.single_reading): - # if multiple numeric readings add all readings to data - for i in range(2, 11): - field = "reading_" + str(i) - data[field] = flt(reading.get(field)) + for i in range(1, 11): + field = "reading_" + str(i) + data[field] = flt(reading.get(field)) + data["mean"] = self.calculate_mean(reading) + return data + def calculate_mean(self, reading): + """Calculate mean of all non-empty readings.""" + from statistics import mean + readings_list = [] + + for i in range(1, 11): + reading_value = reading.get("reading_" + str(i)) + if reading_value is not None and reading_value.strip(): + readings_list.append(flt(reading_value)) + + actual_mean = mean(readings_list) if readings_list else 0 + return actual_mean + @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def item_query(doctype, txt, searchfield, start, page_len, filters): diff --git a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json index db95fabee0..0792f26d2a 100644 --- a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json +++ b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json @@ -9,18 +9,15 @@ "specification", "status", "value", - "value_based", + "non_numeric", "column_break_4", - "formula_based_criteria", - "acceptance_formula", "min_value", "max_value", - "mean_value", + "formula_based_criteria", + "acceptance_formula", "section_break_3", "reading_value", "section_break_14", - "single_reading", - "section_break_12", "reading_1", "reading_2", "reading_3", @@ -47,7 +44,7 @@ }, { "columns": 2, - "depends_on": "eval:(!doc.formula_based_criteria && doc.value_based)", + "depends_on": "eval:(!doc.formula_based_criteria && doc.non_numeric)", "fieldname": "value", "fieldtype": "Data", "in_list_view": 1, @@ -66,7 +63,6 @@ }, { "columns": 1, - "depends_on": "eval:!doc.single_reading", "fieldname": "reading_2", "fieldtype": "Data", "in_list_view": 1, @@ -76,7 +72,6 @@ }, { "columns": 1, - "depends_on": "eval:!doc.single_reading", "fieldname": "reading_3", "fieldtype": "Data", "in_list_view": 1, @@ -85,7 +80,6 @@ "oldfieldtype": "Data" }, { - "depends_on": "eval:!doc.single_reading", "fieldname": "reading_4", "fieldtype": "Data", "label": "Reading 4", @@ -93,7 +87,6 @@ "oldfieldtype": "Data" }, { - "depends_on": "eval:!doc.single_reading", "fieldname": "reading_5", "fieldtype": "Data", "label": "Reading 5", @@ -101,7 +94,6 @@ "oldfieldtype": "Data" }, { - "depends_on": "eval:!doc.single_reading", "fieldname": "reading_6", "fieldtype": "Data", "label": "Reading 6", @@ -109,7 +101,6 @@ "oldfieldtype": "Data" }, { - "depends_on": "eval:!doc.single_reading", "fieldname": "reading_7", "fieldtype": "Data", "label": "Reading 7", @@ -117,7 +108,6 @@ "oldfieldtype": "Data" }, { - "depends_on": "eval:!doc.single_reading", "fieldname": "reading_8", "fieldtype": "Data", "label": "Reading 8", @@ -125,7 +115,6 @@ "oldfieldtype": "Data" }, { - "depends_on": "eval:!doc.single_reading", "fieldname": "reading_9", "fieldtype": "Data", "label": "Reading 9", @@ -133,7 +122,6 @@ "oldfieldtype": "Data" }, { - "depends_on": "eval:!doc.single_reading", "fieldname": "reading_10", "fieldtype": "Data", "label": "Reading 10", @@ -152,7 +140,7 @@ "options": "Accepted\nRejected" }, { - "depends_on": "value_based", + "depends_on": "non_numeric", "fieldname": "section_break_3", "fieldtype": "Section Break", "label": "Value Based Inspection" @@ -163,7 +151,7 @@ }, { "depends_on": "formula_based_criteria", - "description": "Simple Python formula applied on Reading fields.
Numeric eg.: reading_1 > 0.2 and reading_1 < 0.5
\nValue based eg.: reading_value in (\"A\", \"B\", \"C)", + "description": "Simple Python formula applied on Reading fields.
Numeric eg. 1: reading_1 > 0.2 and reading_1 < 0.5
\nNumeric eg. 2: mean > 3.5 (mean of populated fields)
\nValue based eg.: reading_value in (\"A\", \"B\", \"C)", "fieldname": "acceptance_formula", "fieldtype": "Code", "label": "Acceptance Criteria Formula" @@ -183,61 +171,42 @@ "label": "Formula Based Criteria" }, { - "depends_on": "eval:(!doc.formula_based_criteria && !doc.single_reading && !doc.value_based)", - "fieldname": "mean_value", - "fieldtype": "Float", - "label": "Mean Value" - }, - { - "default": "0", - "fieldname": "single_reading", - "fieldtype": "Check", - "label": "Single Reading" - }, - { - "depends_on": "eval:!doc.value_based", - "fieldname": "section_break_12", - "fieldtype": "Section Break", - "hide_border": 1 - }, - { - "depends_on": "eval:(!doc.formula_based_criteria && !doc.value_based)", + "depends_on": "eval:(!doc.formula_based_criteria && !doc.non_numeric)", "description": "Applied on each reading.", "fieldname": "min_value", "fieldtype": "Float", "label": "Minimum Value" }, { - "depends_on": "eval:(!doc.formula_based_criteria && !doc.value_based)", + "depends_on": "eval:(!doc.formula_based_criteria && !doc.non_numeric)", "description": "Applied on each reading.", "fieldname": "max_value", "fieldtype": "Float", "label": "Maximum Value" }, { - "default": "0", - "description": "Non-numeric Inspection.", - "fieldname": "value_based", - "fieldtype": "Check", - "label": "Value Based" - }, - { - "depends_on": "value_based", + "depends_on": "non_numeric", "fieldname": "reading_value", "fieldtype": "Data", "label": "Reading Value" }, { - "depends_on": "eval:!doc.value_based", + "depends_on": "eval:!doc.non_numeric", "fieldname": "section_break_14", "fieldtype": "Section Break", "label": "Numeric Inspection" + }, + { + "default": "0", + "fieldname": "non_numeric", + "fieldtype": "Check", + "label": "Non-Numeric" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-12-18 21:02:04.865777", + "modified": "2020-12-21 11:36:24.885019", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection Reading", diff --git a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py index 7dd0febc20..c5a7974a73 100644 --- a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py +++ b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py @@ -14,7 +14,6 @@ def get_template_details(template): return frappe.get_all('Item Quality Inspection Parameter', fields=["specification", "value", "acceptance_formula", - "value_based", "formula_based_criteria", "single_reading", - "min_value", "max_value", "mean_value"], + "non_numeric", "formula_based_criteria", "min_value", "max_value"], filters={'parenttype': 'Quality Inspection Template', 'parent': template}, order_by="idx") \ No newline at end of file From 68f91c96400226254875016d0e0b95bdc3816580 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 21 Dec 2020 12:24:45 +0530 Subject: [PATCH 04/34] chore: Added tests for new ux - Test for value based inspection - tweaks in test for formula based inspection - tweaks in create_quality_inspection as status in child row is auto set now --- .../test_quality_inspection.py | 59 ++++++++++++++++--- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index 2c40009426..d0bfb466e0 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -44,24 +44,61 @@ class TestQualityInspection(unittest.TestCase): qa.delete() dn.delete() + def test_value_based_qi_readings(self): + # Test QI based on acceptance values (Non formula) + dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) + readings = [{ + "specification": "Iron Content", # numeric reading + "min_value": 0.1, + "max_value": 0.9, + "reading_1": "0.4" + }, + { + "specification": "Particle Inspection Needed", # non-numeric reading + "non_numeric": 1, + "value": "Yes", + "reading_value": "Yes" + }] + + qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, + readings=readings, do_not_save=True) + qa.save() + + # status must be auto set as per formula + self.assertEqual(qa.readings[0].status, "Accepted") + self.assertEqual(qa.readings[1].status, "Accepted") + + qa.delete() + dn.delete() + def test_formula_based_qi_readings(self): dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) readings = [{ - "specification": "Iron Content", + "specification": "Iron Content", # numeric reading + "formula_based_criteria": 1, "acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50", - "reading_1": 0.4 + "reading_1": "0.4" }, { - "specification": "Calcium Content", + "specification": "Calcium Content", # numeric reading + "formula_based_criteria": 1, "acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50", - "reading_1": 0.7 + "reading_1": "0.7" }, { - "specification": "Mg Content", - "acceptance_formula": "(reading_1 + reading_2 + reading_3) / 3 < 0.9", - "reading_1": 0.5, - "reading_2": 0.7, + "specification": "Mg Content", # numeric reading + "formula_based_criteria": 1, + "acceptance_formula": "mean < 0.9", + "reading_1": "0.5", + "reading_2": "0.7", "reading_3": "random text" # check if random string input causes issues + }, + { + "specification": "Calcium Content", # non-numeric reading + "formula_based_criteria": 1, + "non_numeric": 1, + "acceptance_formula": "reading_value in ('Grade A', 'Grade B', 'Grade C')", + "reading_value": "Grade B" }] qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, @@ -72,6 +109,7 @@ class TestQualityInspection(unittest.TestCase): self.assertEqual(qa.readings[0].status, "Accepted") self.assertEqual(qa.readings[1].status, "Rejected") self.assertEqual(qa.readings[2].status, "Accepted") + self.assertEqual(qa.readings[3].status, "Accepted") qa.delete() dn.delete() @@ -86,8 +124,11 @@ def create_quality_inspection(**args): qa.item_code = args.item_code or "_Test Item with QA" qa.sample_size = 1 qa.inspected_by = frappe.session.user + qa.status = args.status or "Accepted" - readings = args.readings or {"specification": "Size", "status": args.status} + readings = args.readings or {"specification": "Size", "min_value": 0, "max_value": 10} + if args.status == "Rejected": + readings["reading_1"] = "12" # status is auto set in child on save if isinstance(readings, list): for entry in readings: From 0e222173ea431b46344f7b73866390c15e80106e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 21 Dec 2020 13:44:03 +0530 Subject: [PATCH 05/34] fix: don't set primary action if workflow is set --- erpnext/payroll/doctype/payroll_entry/payroll_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js index cb48abbc36..31abaf40bf 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js @@ -39,7 +39,7 @@ frappe.ui.form.on('Payroll Entry', { } ).toggleClass('btn-primary', !(frm.doc.employees || []).length); } - if ((frm.doc.employees || []).length) { + if ((frm.doc.employees || []).length && !frappe.model.has_workflow(frm.doctype)) { frm.page.clear_primary_action(); frm.page.set_primary_action(__('Create Salary Slips'), () => { frm.save('Submit').then(()=>{ From eae31f02cc1a5254292b7c621513e70b91d10b22 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 21 Dec 2020 13:58:44 +0530 Subject: [PATCH 06/34] fix: Sider (missing semi-colons) --- .../doctype/quality_inspection/quality_inspection.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js index f0bf9aed80..2ec8a07005 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js @@ -10,18 +10,18 @@ frappe.ui.form.on("Quality Inspection", { filters: { "item": frm.doc.item_code } - } + }; }); // Serial No based on item_code frm.set_query("item_serial_no", function() { - var filters = {}; + let filters = {}; if (frm.doc.item_code) { filters = { 'item_code': frm.doc.item_code - } + }; } - return { filters: filters } + return { filters: filters }; }); // item code based on GRN/DN @@ -75,4 +75,4 @@ frappe.ui.form.on("Quality Inspection", { }); } }, -}) \ No newline at end of file +}); \ No newline at end of file From 6900a79421b141e9d86d7e111ba9eac06e7cf75d Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 22 Dec 2020 11:37:13 +0100 Subject: [PATCH 07/34] fix: fail silently --- erpnext/regional/germany/accounts_controller.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/germany/accounts_controller.py b/erpnext/regional/germany/accounts_controller.py index 5b2b31f204..0ab027b4d6 100644 --- a/erpnext/regional/germany/accounts_controller.py +++ b/erpnext/regional/germany/accounts_controller.py @@ -37,7 +37,14 @@ def validate_regional(doc): for field in required_fields: condition = field.get("condition") - if condition and not frappe.safe_eval(condition, doc.as_dict()): + condition_true = True + try: + condition_true = frappe.safe_eval(condition, doc.as_dict()) + except: + # invalid condition should not result in an error + pass + + if condition and not condition_true: continue field_name = field.get("field_name") From 5adbe49ca65b9230531341e0d2d906670e39002e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 22 Dec 2020 11:37:43 +0100 Subject: [PATCH 08/34] refactor: translation syntax --- erpnext/regional/germany/accounts_controller.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/germany/accounts_controller.py b/erpnext/regional/germany/accounts_controller.py index 0ab027b4d6..63da96bdda 100644 --- a/erpnext/regional/germany/accounts_controller.py +++ b/erpnext/regional/germany/accounts_controller.py @@ -55,9 +55,6 @@ def validate_regional(doc): def missing(field_label, regulation): """Notify the user that a required field is missing.""" - context = 'Specific for Germany. Example: Remember to set Company Tax ID. It is required by § 14 Abs. 4 Nr. 2 UStG.' - msgprint(_('Remember to set {field_label}. It is required by {regulation}.', context=context).format( - field_label=frappe.bold(_(field_label)), - regulation=regulation - ) - ) + translated_msg = _('Remember to set {field_label}. It is required by {regulation}.', context='Specific for Germany. Example: Remember to set Company Tax ID. It is required by § 14 Abs. 4 Nr. 2 UStG.') + formatted_msg = translated_msg.format(field_label=frappe.bold(_(field_label)), regulation=regulation) + msgprint(formatted_msg) From a69021018aea2b2e51f4cccb999dad97bcdc5752 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 22 Dec 2020 11:38:09 +0100 Subject: [PATCH 09/34] test: add test for accounts controller --- erpnext/regional/germany/test_accounts_controller.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 erpnext/regional/germany/test_accounts_controller.py diff --git a/erpnext/regional/germany/test_accounts_controller.py b/erpnext/regional/germany/test_accounts_controller.py new file mode 100644 index 0000000000..63bb843d30 --- /dev/null +++ b/erpnext/regional/germany/test_accounts_controller.py @@ -0,0 +1,12 @@ +import frappe +import unittest +from erpnext.regional.germany.accounts_controller import validate_regional + + +class TestAccountsController(unittest.TestCase): + + def setUp(self): + self.sales_invoice = frappe.get_last_doc('Sales Invoice') + + def test_validate_regional(self): + validate_regional(self.sales_invoice) From 511be6466df429acde392aa458c9215cbde48238 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 22 Dec 2020 11:43:33 +0100 Subject: [PATCH 10/34] Revert "fix: fail silently" This reverts commit 6900a79421b141e9d86d7e111ba9eac06e7cf75d. --- erpnext/regional/germany/accounts_controller.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/erpnext/regional/germany/accounts_controller.py b/erpnext/regional/germany/accounts_controller.py index 63da96bdda..b789960ca0 100644 --- a/erpnext/regional/germany/accounts_controller.py +++ b/erpnext/regional/germany/accounts_controller.py @@ -37,14 +37,7 @@ def validate_regional(doc): for field in required_fields: condition = field.get("condition") - condition_true = True - try: - condition_true = frappe.safe_eval(condition, doc.as_dict()) - except: - # invalid condition should not result in an error - pass - - if condition and not condition_true: + if condition and not frappe.safe_eval(condition, doc.as_dict()): continue field_name = field.get("field_name") From 2acd8cbc02aca5904e35ece8dfb4b1608e23891e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 22 Dec 2020 17:34:22 +0100 Subject: [PATCH 11/34] fix: sider --- erpnext/regional/germany/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/germany/accounts_controller.py b/erpnext/regional/germany/accounts_controller.py index b789960ca0..7f76493608 100644 --- a/erpnext/regional/germany/accounts_controller.py +++ b/erpnext/regional/germany/accounts_controller.py @@ -48,6 +48,6 @@ def validate_regional(doc): def missing(field_label, regulation): """Notify the user that a required field is missing.""" - translated_msg = _('Remember to set {field_label}. It is required by {regulation}.', context='Specific for Germany. Example: Remember to set Company Tax ID. It is required by § 14 Abs. 4 Nr. 2 UStG.') + translated_msg = _('Remember to set {field_label}. It is required by {regulation}.', context='Specific for Germany. Example: Remember to set Company Tax ID. It is required by § 14 Abs. 4 Nr. 2 UStG.') # noqa: E501 formatted_msg = translated_msg.format(field_label=frappe.bold(_(field_label)), regulation=regulation) msgprint(formatted_msg) From df8ea194064d5d7abcdf1a696324af55d679baa3 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 22 Dec 2020 17:34:31 +0100 Subject: [PATCH 12/34] fix: whitespace --- erpnext/regional/germany/test_accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/germany/test_accounts_controller.py b/erpnext/regional/germany/test_accounts_controller.py index 63bb843d30..8bd378c971 100644 --- a/erpnext/regional/germany/test_accounts_controller.py +++ b/erpnext/regional/germany/test_accounts_controller.py @@ -7,6 +7,6 @@ class TestAccountsController(unittest.TestCase): def setUp(self): self.sales_invoice = frappe.get_last_doc('Sales Invoice') - + def test_validate_regional(self): validate_regional(self.sales_invoice) From 1fb412e3f6b0099082601b6539b0ce62f0345438 Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Wed, 23 Dec 2020 11:39:37 +1100 Subject: [PATCH 13/34] docs: fix simple typo, udpate -> update There is a small typo in erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py. Should read `update` rather than `udpate`. --- erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py b/erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py index ad043dd99d..97e217aa05 100644 --- a/erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py +++ b/erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py @@ -5,11 +5,11 @@ from __future__ import unicode_literals import frappe def execute(): - # udpate sales cycle + # update sales cycle for d in ['Sales Invoice', 'Sales Order', 'Quotation', 'Delivery Note']: frappe.db.sql("""update `tab%s` set taxes_and_charges=charge""" % d) - # udpate purchase cycle + # update purchase cycle for d in ['Purchase Invoice', 'Purchase Order', 'Supplier Quotation', 'Purchase Receipt']: frappe.db.sql("""update `tab%s` set taxes_and_charges=purchase_other_charges""" % d) From 46d5f4c7f14f9cdbf046f2afc06ce93ff751852d Mon Sep 17 00:00:00 2001 From: "hasnain2808@gmail.com" Date: Fri, 25 Dec 2020 16:34:43 +0530 Subject: [PATCH 14/34] refactor(analytics report): linting --- .../purchase_analytics/purchase_analytics.js | 24 +++++++++++-------- .../report/sales_analytics/sales_analytics.js | 17 ++++++------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/erpnext/buying/report/purchase_analytics/purchase_analytics.js b/erpnext/buying/report/purchase_analytics/purchase_analytics.js index 7ee9f2c372..ba8535a3ae 100644 --- a/erpnext/buying/report/purchase_analytics/purchase_analytics.js +++ b/erpnext/buying/report/purchase_analytics/purchase_analytics.js @@ -87,12 +87,18 @@ frappe.query_reports["Purchase Analytics"] = { row_name = data[2].content; length = data.length; - if (tree_type == "Supplier" || tree_type == "Item") { + if (tree_type == "Supplier") { row_values = data .slice(4, length - 1) .map(function (column) { return column.content; }); + } else if (tree_type == "Item") { + row_values = data + .slice(5, length - 1) + .map(function (column) { + return column.content; + }); } else { row_values = data .slice(3, length - 1) @@ -109,17 +115,15 @@ frappe.query_reports["Purchase Analytics"] = { let raw_data = frappe.query_report.chart.data; let new_datasets = raw_data.datasets; - let found = false; - - for (let i = 0; i < new_datasets.length; i++) { - if (new_datasets[i].name == row_name) { - found = true; - new_datasets.splice(i, 1); - break; + let element_found = new_datasets.some((element, index, array)=>{ + if(element.name == row_name){ + array.splice(index, 1) + return true } - } + return false + }) - if (!found) { + if (!element_found) { new_datasets.push(entry); } let new_data = { diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.js b/erpnext/selling/report/sales_analytics/sales_analytics.js index aad6bfd5ef..9089b53fb0 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.js +++ b/erpnext/selling/report/sales_analytics/sales_analytics.js @@ -76,7 +76,6 @@ frappe.query_reports["Sales Analytics"] = { events: { onCheckRow: function (data) { if (!data) return; - const data_doctype = $( data[2].html )[0].attributes.getNamedItem("data-doctype").value; @@ -114,17 +113,15 @@ frappe.query_reports["Sales Analytics"] = { let raw_data = frappe.query_report.chart.data; let new_datasets = raw_data.datasets; - let found = false; - - for (let i = 0; i < new_datasets.length; i++) { - if (new_datasets[i].name == row_name) { - found = true; - new_datasets.splice(i, 1); - break; + let element_found = new_datasets.some((element, index, array)=>{ + if(element.name == row_name){ + array.splice(index, 1) + return true } - } + return false + }) - if (!found) { + if (!element_found) { new_datasets.push(entry); } From 29a03bd5a1d250479369f8539450414bfbef080c Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 28 Dec 2020 16:59:13 +0530 Subject: [PATCH 15/34] feat: Add 'Manual Inspection' checkbox - fix merge conflict in js file - Dont auto set status if manual inspection is checked - Added 'Manual Inspection' checkbox in QI readings table --- .../doctype/quality_inspection/quality_inspection.js | 1 + .../doctype/quality_inspection/quality_inspection.py | 11 ++++++----- .../quality_inspection_reading.json | 10 +++++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js index 544bc2c307..f7565fd505 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js @@ -51,6 +51,7 @@ frappe.ui.form.on("Quality Inspection", { }; } }); + }, refresh: function(frm) { // Ignore cancellation of reference doctype on cancel all. diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index f582658d87..9672b62394 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -76,11 +76,12 @@ class QualityInspection(Document): def inspect_and_set_status(self): for reading in self.readings: - if reading.formula_based_criteria: - self.set_status_based_on_acceptance_formula(reading) - else: - # if not formula based check acceptance values set - self.set_status_based_on_acceptance_values(reading) + if not reading.manual_inspection: # dont auto set status if manual + if reading.formula_based_criteria: + self.set_status_based_on_acceptance_formula(reading) + else: + # if not formula based check acceptance values set + self.set_status_based_on_acceptance_values(reading) def set_status_based_on_acceptance_values(self, reading): if cint(reading.non_numeric): diff --git a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json index 0792f26d2a..264a6ea634 100644 --- a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json +++ b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json @@ -10,6 +10,7 @@ "status", "value", "non_numeric", + "manual_inspection", "column_break_4", "min_value", "max_value", @@ -201,12 +202,19 @@ "fieldname": "non_numeric", "fieldtype": "Check", "label": "Non-Numeric" + }, + { + "default": "0", + "description": "Set the status manually.", + "fieldname": "manual_inspection", + "fieldtype": "Check", + "label": "Manual Inspection" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-12-21 11:36:24.885019", + "modified": "2020-12-28 16:40:47.586382", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection Reading", From a69e81a1510d3dc4d3ece2744224023ed3f14a23 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 28 Dec 2020 18:07:22 +0530 Subject: [PATCH 16/34] chore: Made 'Parameter' a link field in QI and QI Template - Added doctype Quality Inspection Parameter - Made 'Parameter' a link field in QI and QI Template - Added patch to create Quality Inspection Parameter records for every parameter in the system --- erpnext/patches.txt | 1 + .../convert_qi_parameter_to_link_field.py | 23 +++++ .../item_quality_inspection_parameter.json | 5 +- .../quality_inspection_parameter/__init__.py | 0 .../quality_inspection_parameter.js | 8 ++ .../quality_inspection_parameter.json | 86 +++++++++++++++++++ .../quality_inspection_parameter.py | 10 +++ .../test_quality_inspection_parameter.py | 10 +++ .../quality_inspection_reading.json | 5 +- 9 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py create mode 100644 erpnext/stock/doctype/quality_inspection_parameter/__init__.py create mode 100644 erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.js create mode 100644 erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.json create mode 100644 erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.py create mode 100644 erpnext/stock/doctype/quality_inspection_parameter/test_quality_inspection_parameter.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d69dabf15c..621f4173ad 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -741,3 +741,4 @@ erpnext.patches.v13_0.updates_for_multi_currency_payroll erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy erpnext.patches.v13_0.add_po_to_global_search erpnext.patches.v13_0.update_returned_qty_in_pr_dn +erpnext.patches.v13_0.convert_qi_parameter_to_link_field #2345 diff --git a/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py new file mode 100644 index 0000000000..289b6a761e --- /dev/null +++ b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py @@ -0,0 +1,23 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('stock', 'doctype', 'quality_inspection_parameter') + + # get all distinct parameters from QI readigs table + reading_params = frappe.db.get_all("Quality Inspection Reading", fields=["distinct specification"]) + reading_params = [d.specification for d in reading_params] + + # get all distinct parameters from QI Template as some may be unused in QI + template_params = frappe.db.get_all("Item Quality Inspection Parameter", fields=["distinct specification"]) + template_params = [d.specification for d in template_params] + + params = list(set(reading_params + template_params)) + + for parameter in params: + if not frappe.db.exists("Quality Inspection Parameter", parameter): + frappe.get_doc({ + "doctype": "Quality Inspection Parameter", + "parameter": parameter, + "description": parameter + }).insert(ignore_permissions=True) \ No newline at end of file diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json index 9b980a1e01..fc06e89f2f 100644 --- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json +++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json @@ -18,11 +18,12 @@ "fields": [ { "fieldname": "specification", - "fieldtype": "Data", + "fieldtype": "Link", "in_list_view": 1, "label": "Parameter", "oldfieldname": "specification", "oldfieldtype": "Data", + "options": "Quality Inspection Parameter", "print_width": "200px", "reqd": 1, "width": "100px" @@ -79,7 +80,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-12-21 11:37:55.387677", + "modified": "2020-12-28 17:41:04.350225", "modified_by": "Administrator", "module": "Stock", "name": "Item Quality Inspection Parameter", diff --git a/erpnext/stock/doctype/quality_inspection_parameter/__init__.py b/erpnext/stock/doctype/quality_inspection_parameter/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.js b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.js new file mode 100644 index 0000000000..47c7e11d23 --- /dev/null +++ b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.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('Quality Inspection Parameter', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.json b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.json new file mode 100644 index 0000000000..0b5a9b5b3c --- /dev/null +++ b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.json @@ -0,0 +1,86 @@ +{ + "actions": [], + "autoname": "field:parameter", + "creation": "2020-12-28 17:06:00.254129", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "parameter", + "description" + ], + "fields": [ + { + "fieldname": "parameter", + "fieldtype": "Data", + "label": "Parameter", + "unique": 1 + }, + { + "fieldname": "description", + "fieldtype": "Text Editor", + "label": "Description" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-12-28 18:06:54.897317", + "modified_by": "Administrator", + "module": "Stock", + "name": "Quality Inspection Parameter", + "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": "Stock User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Quality 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 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.py b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.py new file mode 100644 index 0000000000..86784221a0 --- /dev/null +++ b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class QualityInspectionParameter(Document): + pass diff --git a/erpnext/stock/doctype/quality_inspection_parameter/test_quality_inspection_parameter.py b/erpnext/stock/doctype/quality_inspection_parameter/test_quality_inspection_parameter.py new file mode 100644 index 0000000000..cefdc0867b --- /dev/null +++ b/erpnext/stock/doctype/quality_inspection_parameter/test_quality_inspection_parameter.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 TestQualityInspectionParameter(unittest.TestCase): + pass diff --git a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json index 264a6ea634..739845bcda 100644 --- a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json +++ b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json @@ -36,11 +36,12 @@ { "columns": 3, "fieldname": "specification", - "fieldtype": "Data", + "fieldtype": "Link", "in_list_view": 1, "label": "Parameter", "oldfieldname": "specification", "oldfieldtype": "Data", + "options": "Quality Inspection Parameter", "reqd": 1 }, { @@ -214,7 +215,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-12-28 16:40:47.586382", + "modified": "2020-12-28 17:40:47.407210", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection Reading", From 7877d5a7c243b0e98be9e1f1362b8dfbd18acd2e Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 4 Jan 2021 11:10:04 +0530 Subject: [PATCH 17/34] fix: Create QI Parameters (links) in test cases --- erpnext/controllers/tests/test_item_variant.py | 3 +++ .../test_quality_inspection.py | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/tests/test_item_variant.py b/erpnext/controllers/tests/test_item_variant.py index c257215e71..813f0a0075 100644 --- a/erpnext/controllers/tests/test_item_variant.py +++ b/erpnext/controllers/tests/test_item_variant.py @@ -6,6 +6,7 @@ import unittest from erpnext.stock.doctype.item.test_item import set_item_variant_settings from erpnext.controllers.item_variant import copy_attributes_to_variant, make_variant_item_code +from erpnext.stock.doctype.quality_inspection.test_quality_inspection import create_quality_inspection_parameter from six import string_types @@ -56,6 +57,8 @@ def make_quality_inspection_template(): qc = frappe.new_doc("Quality Inspection Template") qc.quality_inspection_template_name = qc_template + + create_quality_inspection_parameter("Moisture") qc.append('item_quality_inspection_parameter', { "specification": "Moisture", "value": "< 5%", diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index d0bfb466e0..8c5a04b3f0 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -126,12 +126,18 @@ def create_quality_inspection(**args): qa.inspected_by = frappe.session.user qa.status = args.status or "Accepted" - readings = args.readings or {"specification": "Size", "min_value": 0, "max_value": 10} + if not args.readings: + create_quality_inspection_parameter("Size") + readings = {"specification": "Size", "min_value": 0, "max_value": 10} + else: + readings = args.readings + if args.status == "Rejected": readings["reading_1"] = "12" # status is auto set in child on save if isinstance(readings, list): for entry in readings: + create_quality_inspection_parameter(entry["specification"]) qa.append("readings", entry) else: qa.append("readings", readings) @@ -142,3 +148,11 @@ def create_quality_inspection(**args): qa.submit() return qa + +def create_quality_inspection_parameter(parameter): + if not frappe.db.exists("Quality Inspection Parameter", parameter): + frappe.get_doc({ + "doctype": "Quality Inspection Parameter", + "parameter": parameter, + "description": parameter + }).insert() \ No newline at end of file From 03b25be9e9cd6080ddece0b330ea2e1442049da7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 Jan 2021 11:16:59 +0530 Subject: [PATCH 18/34] feat: Allow Discharge despite Unbilled Healthcare Services --- .../healthcare_settings.json | 15 +++- .../healthcare_settings.py | 2 +- .../inpatient_record/inpatient_record.py | 69 +++++++++++++++---- 3 files changed, 70 insertions(+), 16 deletions(-) diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json index 0104386714..b33c326313 100644 --- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json +++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json @@ -17,6 +17,8 @@ "enable_free_follow_ups", "max_visits", "valid_days", + "inpatient_settings_section", + "allow_discharge_despite_unbilled_services", "healthcare_service_items", "inpatient_visit_charge_item", "op_consulting_charge_item", @@ -302,11 +304,22 @@ "fieldname": "enable_free_follow_ups", "fieldtype": "Check", "label": "Enable Free Follow-ups" + }, + { + "fieldname": "inpatient_settings_section", + "fieldtype": "Section Break", + "label": "Inpatient Settings" + }, + { + "default": "0", + "fieldname": "allow_discharge_despite_unbilled_services", + "fieldtype": "Check", + "label": "Allow Discharge Despite Unbilled Healthcare Services" } ], "issingle": 1, "links": [], - "modified": "2020-07-08 15:17:21.543218", + "modified": "2021-01-04 10:19:22.329272", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare Settings", diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py index a16fceb74d..e2ccc34a74 100644 --- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py +++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py @@ -11,7 +11,7 @@ import json class HealthcareSettings(Document): def validate(self): - for key in ['collect_registration_fee', 'link_customer_to_patient', 'patient_name_by', + for key in ['collect_registration_fee', 'link_customer_to_patient', 'patient_name_by', 'allow_discharge_despite_unbilled_services', 'lab_test_approval_required', 'create_sample_collection_for_lab_test', 'default_medical_code_standard']: frappe.db.set_default(key, self.get(key, "")) diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py index bc76970601..6a32aca9d0 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, json from frappe import _ -from frappe.utils import today, now_datetime, getdate, get_datetime +from frappe.utils import today, now_datetime, getdate, get_datetime, get_link_to_form from frappe.model.document import Document from frappe.desk.reportview import get_match_cond @@ -113,6 +113,7 @@ def schedule_inpatient(args): inpatient_record.status = 'Admission Scheduled' inpatient_record.save(ignore_permissions = True) + @frappe.whitelist() def schedule_discharge(args): discharge_order = json.loads(args) @@ -126,16 +127,19 @@ def schedule_discharge(args): frappe.db.set_value('Patient', discharge_order['patient'], 'inpatient_status', inpatient_record.status) frappe.db.set_value('Patient Encounter', inpatient_record.discharge_encounter, 'inpatient_status', inpatient_record.status) + def set_details_from_ip_order(inpatient_record, ip_order): for key in ip_order: inpatient_record.set(key, ip_order[key]) + def set_ip_child_records(inpatient_record, inpatient_record_child, encounter_child): for item in encounter_child: table = inpatient_record.append(inpatient_record_child) for df in table.meta.get('fields'): table.set(df.fieldname, item.get(df.fieldname)) + def check_out_inpatient(inpatient_record): if inpatient_record.inpatient_occupancies: for inpatient_occupancy in inpatient_record.inpatient_occupancies: @@ -144,54 +148,88 @@ def check_out_inpatient(inpatient_record): inpatient_occupancy.check_out = now_datetime() frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant") + def discharge_patient(inpatient_record): - validate_invoiced_inpatient(inpatient_record) + validate_inpatient_invoicing(inpatient_record) inpatient_record.discharge_date = today() inpatient_record.status = "Discharged" inpatient_record.save(ignore_permissions = True) -def validate_invoiced_inpatient(inpatient_record): - pending_invoices = [] + +def validate_inpatient_invoicing(inpatient_record): + if frappe.db.get_default("allow_discharge_despite_unbilled_services"): + return + + pending_invoices = get_pending_invoices(inpatient_record) + + if pending_invoices: + message = _("Cannot mark Inpatient Record as Discharged since there are unbilled services. ") + + formatted_doc_rows = '' + + for doctype, docnames in pending_invoices.items(): + formatted_doc_rows += """ + {0} + {1} + """.format(doctype, docnames) + + message += """ + + + + + + {2} +
{0}{1}
+ """.format(_("Healthcare Service"), _("Documents"), formatted_doc_rows) + + frappe.throw(message, title=_("Unbilled Services"), is_minimizable=True, wide=True) + + +def get_pending_invoices(inpatient_record): + pending_invoices = {} if inpatient_record.inpatient_occupancies: service_unit_names = False for inpatient_occupancy in inpatient_record.inpatient_occupancies: - if inpatient_occupancy.invoiced != 1: + if not inpatient_occupancy.invoiced: if service_unit_names: service_unit_names += ", " + inpatient_occupancy.service_unit else: service_unit_names = inpatient_occupancy.service_unit if service_unit_names: - pending_invoices.append("Inpatient Occupancy (" + service_unit_names + ")") + pending_invoices["Inpatient Occupancy"] = service_unit_names docs = ["Patient Appointment", "Patient Encounter", "Lab Test", "Clinical Procedure"] for doc in docs: - doc_name_list = get_inpatient_docs_not_invoiced(doc, inpatient_record) + doc_name_list = get_unbilled_inpatient_docs(doc, inpatient_record) if doc_name_list: pending_invoices = get_pending_doc(doc, doc_name_list, pending_invoices) - if pending_invoices: - frappe.throw(_("Can not mark Inpatient Record Discharged, there are Unbilled Invoices {0}").format(", " - .join(pending_invoices)), title=_('Unbilled Invoices')) + return pending_invoices + def get_pending_doc(doc, doc_name_list, pending_invoices): if doc_name_list: doc_ids = False for doc_name in doc_name_list: + doc_link = get_link_to_form(doc, doc_name.name) if doc_ids: - doc_ids += ", "+doc_name.name + doc_ids += ", " + doc_link else: - doc_ids = doc_name.name + doc_ids = doc_link if doc_ids: - pending_invoices.append(doc + " (" + doc_ids + ")") + pending_invoices[doc] = doc_ids return pending_invoices -def get_inpatient_docs_not_invoiced(doc, inpatient_record): + +def get_unbilled_inpatient_docs(doc, inpatient_record): return frappe.db.get_list(doc, filters = {'patient': inpatient_record.patient, 'inpatient_record': inpatient_record.name, 'docstatus': 1, 'invoiced': 0}) + def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None): inpatient_record.admitted_datetime = check_in inpatient_record.status = 'Admitted' @@ -203,6 +241,7 @@ def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=N frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_status', 'Admitted') frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_record', inpatient_record.name) + def transfer_patient(inpatient_record, service_unit, check_in): item_line = inpatient_record.append('inpatient_occupancies', {}) item_line.service_unit = service_unit @@ -212,6 +251,7 @@ def transfer_patient(inpatient_record, service_unit, check_in): frappe.db.set_value("Healthcare Service Unit", service_unit, "occupancy_status", "Occupied") + def patient_leave_service_unit(inpatient_record, check_out, leave_from): if inpatient_record.inpatient_occupancies: for inpatient_occupancy in inpatient_record.inpatient_occupancies: @@ -221,6 +261,7 @@ def patient_leave_service_unit(inpatient_record, check_out, leave_from): frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant") inpatient_record.save(ignore_permissions = True) + @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_leave_from(doctype, txt, searchfield, start, page_len, filters): From 7206e12c2f2d9ed46d11748d9ebb098e034bba28 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 Jan 2021 12:11:00 +0530 Subject: [PATCH 19/34] test: Allow Discharge despite Unbilled Services --- .../healthcare_settings.py | 2 +- .../inpatient_record/inpatient_record.py | 2 +- .../inpatient_record/test_inpatient_record.py | 35 +++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py index e2ccc34a74..a16fceb74d 100644 --- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py +++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py @@ -11,7 +11,7 @@ import json class HealthcareSettings(Document): def validate(self): - for key in ['collect_registration_fee', 'link_customer_to_patient', 'patient_name_by', 'allow_discharge_despite_unbilled_services', + for key in ['collect_registration_fee', 'link_customer_to_patient', 'patient_name_by', 'lab_test_approval_required', 'create_sample_collection_for_lab_test', 'default_medical_code_standard']: frappe.db.set_default(key, self.get(key, "")) diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py index 6a32aca9d0..dc549a65db 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py @@ -158,7 +158,7 @@ def discharge_patient(inpatient_record): def validate_inpatient_invoicing(inpatient_record): - if frappe.db.get_default("allow_discharge_despite_unbilled_services"): + if frappe.db.get_single_value("Healthcare Settings", "allow_discharge_despite_unbilled_services"): return pending_invoices = get_pending_invoices(inpatient_record) diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py index 70706adb2e..e8a9444fec 100644 --- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py @@ -40,6 +40,31 @@ class TestInpatientRecord(unittest.TestCase): self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record")) self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status")) + def test_allow_discharge_despite_unbilled_services(self): + frappe.db.sql("""delete from `tabInpatient Record`""") + setup_inpatient_settings() + patient = create_patient() + # Schedule Admission + ip_record = create_inpatient(patient) + ip_record.expected_length_of_stay = 0 + ip_record.save(ignore_permissions = True) + + # Admit + service_unit = get_healthcare_service_unit() + admit_patient(ip_record, service_unit, now_datetime()) + + # Discharge + schedule_discharge(frappe.as_json({"patient": patient})) + self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status")) + + ip_record = frappe.get_doc("Inpatient Record", ip_record.name) + # Should not validate Pending Invoices + ip_record.discharge() + + self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record")) + self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status")) + + def test_validate_overlap_admission(self): frappe.db.sql("""delete from `tabInpatient Record`""") patient = create_patient() @@ -63,6 +88,13 @@ def mark_invoiced_inpatient_occupancy(ip_record): inpatient_occupancy.invoiced = 1 ip_record.save(ignore_permissions = True) + +def setup_inpatient_settings(): + settings = frappe.get_single("Healthcare Settings") + settings.allow_discharge_despite_unbilled_services = 1 + settings.save() + + def create_inpatient(patient): patient_obj = frappe.get_doc('Patient', patient) inpatient_record = frappe.new_doc('Inpatient Record') @@ -78,6 +110,7 @@ def create_inpatient(patient): inpatient_record.scheduled_date = today() return inpatient_record + def get_healthcare_service_unit(): service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1}) if not service_unit: @@ -105,6 +138,7 @@ def get_healthcare_service_unit(): return service_unit.name return service_unit + def get_service_unit_type(): service_unit_type = get_random("Healthcare Service Unit Type", filters={"inpatient_occupancy": 1}) @@ -116,6 +150,7 @@ def get_service_unit_type(): return service_unit_type.name return service_unit_type + def create_patient(): patient = frappe.db.exists('Patient', '_Test IPD Patient') if not patient: From 0da201c6a5d6467d3e7a392ae1bb4c616681d364 Mon Sep 17 00:00:00 2001 From: Afshan Date: Mon, 4 Jan 2021 19:04:05 +0530 Subject: [PATCH 20/34] fix: set company in leave allocation and leave ledger entry --- .../doctype/leave_allocation/leave_allocation.json | 12 +++++++++++- .../leave_ledger_entry/leave_ledger_entry.json | 14 +++++++++++++- erpnext/patches.txt | 1 + .../v13_0/set_company_in_leave_ledger_entry.py | 7 +++++++ 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 4b315014da..3a300c0d63 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -11,6 +11,7 @@ "employee", "employee_name", "department", + "company", "column_break1", "leave_type", "from_date", @@ -219,6 +220,15 @@ "label": "Leave Policy Assignment", "options": "Leave Policy Assignment", "read_only": 1 + }, + { + "fetch_from": "employee.company", + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "read_only": 1, + "reqd": 1 } ], "icon": "fa fa-ok", @@ -226,7 +236,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-08-20 14:25:10.314323", + "modified": "2021-01-04 18:46:13.184104", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json index 4abba5f2d4..d74760a5cf 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2019-05-09 15:47:39.760406", "doctype": "DocType", "engine": "InnoDB", @@ -8,6 +9,7 @@ "leave_type", "transaction_type", "transaction_name", + "company", "leaves", "column_break_7", "from_date", @@ -106,12 +108,22 @@ "fieldtype": "Link", "label": "Holiday List", "options": "Holiday List" + }, + { + "fetch_from": "employee.company", + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "read_only": 1, + "reqd": 1 } ], "in_create": 1, "index_web_pages_for_search": 1, "is_submittable": 1, - "modified": "2020-09-04 12:16:36.569066", + "links": [], + "modified": "2021-01-04 18:47:45.146652", "modified_by": "Administrator", "module": "HR", "name": "Leave Ledger Entry", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f2e4f72d67..923ed2f339 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -742,3 +742,4 @@ erpnext.patches.v13_0.updates_for_multi_currency_payroll erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy erpnext.patches.v13_0.add_po_to_global_search erpnext.patches.v13_0.update_returned_qty_in_pr_dn +erpnext.patches.v13_0.set_company_in_leave_ledger_entry \ No newline at end of file diff --git a/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py b/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py new file mode 100644 index 0000000000..66857c4e65 --- /dev/null +++ b/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py @@ -0,0 +1,7 @@ +import frappe + +def execute(): + frappe.reload_doc('HR', 'doctype', 'Leave Allocation') + frappe.reload_doc('HR', 'doctype', 'Leave Ledger Entry') + frappe.db.sql("""update `tabLeave Ledger Entry` as lle set company = (select company from `tabEmployee` where employee = lle.employee)""") + frappe.db.sql("""update `tabLeave Allocation` as la set company = (select company from `tabEmployee` where employee = la.employee)""") \ No newline at end of file From 06a401ffbfa812452c9aafc4fca5b2b5d3702554 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Tue, 5 Jan 2021 11:54:34 +0530 Subject: [PATCH 21/34] fix: incoming rate attribute error (#24287) Co-authored-by: pateljannat --- erpnext/controllers/selling_controller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 85cfb951fc..812021f5c8 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -233,7 +233,7 @@ class SellingController(StockController): 'allow_zero_valuation': d.allow_zero_valuation_rate, 'sales_invoice_item': d.get("sales_invoice_item"), 'dn_detail': d.get("dn_detail"), - 'incoming_rate': p.incoming_rate + 'incoming_rate': p.get("incoming_rate") })) else: il.append(frappe._dict({ @@ -252,7 +252,7 @@ class SellingController(StockController): 'allow_zero_valuation': d.allow_zero_valuation_rate, 'sales_invoice_item': d.get("sales_invoice_item"), 'dn_detail': d.get("dn_detail"), - 'incoming_rate': d.incoming_rate + 'incoming_rate': d.get("incoming_rate") })) return il From 1f591ab02e40ab884899059ae95f7d86315da83b Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 5 Jan 2021 13:53:51 +0530 Subject: [PATCH 22/34] fix(e-invoicing): minor calculation fixes (#24285) --- .../controllers/sales_and_purchase_return.py | 2 + erpnext/public/js/controllers/transaction.js | 1 + erpnext/regional/india/e_invoice/utils.py | 41 +++++++++++++------ erpnext/stock/get_item_details.py | 4 +- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 79792262c0..a048d6e2df 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -328,6 +328,7 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.po_detail = source_doc.po_detail target_doc.pr_detail = source_doc.pr_detail target_doc.purchase_invoice_item = source_doc.name + target_doc.price_list_rate = 0 elif doctype == "Delivery Note": returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype) @@ -353,6 +354,7 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.dn_detail = source_doc.dn_detail target_doc.expense_account = source_doc.expense_account target_doc.sales_invoice_item = source_doc.name + target_doc.price_list_rate = 0 if default_warehouse_for_sales_return: target_doc.warehouse = default_warehouse_for_sales_return diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 3bc20f8733..bed9c14141 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -543,6 +543,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ company: me.frm.doc.company, order_type: me.frm.doc.order_type, is_pos: cint(me.frm.doc.is_pos), + is_return: cint(me.frm.doc.is_return), is_subcontracted: me.frm.doc.is_subcontracted, transaction_date: me.frm.doc.transaction_date || me.frm.doc.posting_date, ignore_pricing_rule: me.frm.doc.ignore_pricing_rule, diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 02ce6c14c9..e5f7d2d78c 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -92,21 +92,18 @@ def get_party_details(address_name): location = gstin_details.get('AddrLoc') or address.get('city') state_code = gstin_details.get('StateCode') pincode = gstin_details.get('AddrPncd') - address_line1 = '{} {}'.format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno')) - address_line2 = '{} {}'.format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt')) - email_id = address.get('email_id') - phone = address.get('phone') - # get last 10 digit - phone = phone.replace(" ", "")[-10:] if phone else '' + address_line1 = '{} {}'.format(gstin_details.get('AddrBno') or "", gstin_details.get('AddrFlno') or "") + address_line2 = '{} {}'.format(gstin_details.get('AddrBnm') or "", gstin_details.get('AddrSt') or "") if state_code == 97: # according to einvoice standard pincode = 999999 return frappe._dict(dict( - gstin=gstin, legal_name=legal_name, location=location, - pincode=pincode, state_code=state_code, address_line1=address_line1, - address_line2=address_line2, email=email_id, phone=phone + gstin=gstin, legal_name=legal_name, + location=location, pincode=pincode, + state_code=state_code, address_line1=address_line1, + address_line2=address_line2 )) def get_gstin_details(gstin): @@ -146,9 +143,10 @@ def get_item_list(invoice): item.update(d.as_dict()) item.sr_no = d.idx - item.discount_amount = abs(item.discount_amount * item.qty) - item.description = d.item_name + item.description = d.item_name.replace('"', '\\"') + item.qty = abs(item.qty) + item.discount_amount = abs(item.discount_amount * item.qty) item.unit_rate = abs(item.base_amount / item.qty) item.gross_amount = abs(item.base_amount) item.taxable_value = abs(item.base_amount) @@ -156,6 +154,7 @@ def get_item_list(invoice): item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y' + item.serial_no = "" item = update_item_taxes(invoice, item) @@ -272,7 +271,25 @@ def get_eway_bill_details(invoice): vehicle_type=vehicle_type[invoice.gst_vehicle_type] )) +def validate_mandatory_fields(invoice): + if not invoice.company_address: + frappe.throw(_('Company Address is mandatory to fetch company GSTIN details.'), title=_('Missing Fields')) + if not invoice.customer_address: + frappe.throw(_('Customer Address is mandatory to fetch customer GSTIN details.'), title=_('Missing Fields')) + if not frappe.db.get_value('Address', invoice.company_address, 'gstin'): + frappe.throw( + _('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'), + title=_('Missing Fields') + ) + if not frappe.db.get_value('Address', invoice.customer_address, 'gstin'): + frappe.throw( + _('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'), + title=_('Missing Fields') + ) + def make_einvoice(invoice): + validate_mandatory_fields(invoice) + schema = read_json('einv_template') transaction_details = get_transaction_details(invoice) @@ -351,7 +368,7 @@ def validate_einvoice(validations, einvoice, errors=[]): # remove empty dicts einvoice.pop(fieldname, None) continue - + # convert to int or str if value_type == 'string': einvoice[fieldname] = str(value) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 08f7a83b89..bf45251c9d 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -74,7 +74,9 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru update_party_blanket_order(args, out) - get_price_list_rate(args, item, out) + if not doc or cint(doc.get('is_return')) == 0: + # get price list rate only if the invoice is not a credit or debit note + get_price_list_rate(args, item, out) if args.customer and cint(args.is_pos): out.update(get_pos_profile_item_details(args.company, args)) From a56a5ccefa48e4764d058e3e0b46b99f22ea7282 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 5 Jan 2021 15:59:17 +0530 Subject: [PATCH 23/34] refactor: fetch & validate address from erpnext rather than gst portal (#24297) --- erpnext/regional/india/e_invoice/utils.py | 54 ++++++++++++++--------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index e5f7d2d78c..abe15043af 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -15,7 +15,7 @@ from frappe import _, bold from pyqrcode import create as qrcreate from frappe.integrations.utils import make_post_request, make_get_request from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply -from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date +from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form def validate_einvoice_fields(doc): einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable')) @@ -84,26 +84,32 @@ def get_doc_details(invoice): )) def get_party_details(address_name): - address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] - gstin = address.get('gstin') + d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] - gstin_details = get_gstin_details(gstin) - legal_name = gstin_details.get('LegalName') or gstin_details.get('TradeName') - location = gstin_details.get('AddrLoc') or address.get('city') - state_code = gstin_details.get('StateCode') - pincode = gstin_details.get('AddrPncd') - address_line1 = '{} {}'.format(gstin_details.get('AddrBno') or "", gstin_details.get('AddrFlno') or "") - address_line2 = '{} {}'.format(gstin_details.get('AddrBnm') or "", gstin_details.get('AddrSt') or "") + if (not d.gstin + or not d.city + or not d.pincode + or not d.address_title + or not d.address_line1 + or not d.gst_state_number): - if state_code == 97: + frappe.throw( + msg=_('Address lines, city, pincode, gstin is mandatory for address {}. Please set them and try again.').format( + get_link_to_form('Address', address_name) + ), + title=_('Missing Address Fields') + ) + + if d.gst_state_number == 97: # according to einvoice standard pincode = 999999 return frappe._dict(dict( - gstin=gstin, legal_name=legal_name, - location=location, pincode=pincode, - state_code=state_code, address_line1=address_line1, - address_line2=address_line2 + gstin=d.gstin, legal_name=d.address_title, + location=d.city, pincode=d.pincode, + state_code=d.gst_state_number, + address_line1=d.address_line1, + address_line2=d.address_line2 )) def get_gstin_details(gstin): @@ -124,14 +130,22 @@ def get_gstin_details(gstin): return GSPConnector.get_gstin_details(gstin) def get_overseas_address_details(address_name): - address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value( - 'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city', 'phone', 'email_id'] + address_title, address_line1, address_line2, city = frappe.db.get_value( + 'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city'] ) + if not address_title or not address_line1 or not city: + frappe.throw( + msg=_('Address lines and city is mandatory for address {}. Please set them and try again.').format( + get_link_to_form('Address', address_name) + ), + title=_('Missing Address Fields') + ) + return frappe._dict(dict( - gstin='URP', legal_name=address_title, address_line1=address_line1, - address_line2=address_line2, email=email_id, phone=phone, - pincode=999999, state_code=96, place_of_supply=96, location=city + gstin='URP', legal_name=address_title, location=city, + address_line1=address_line1, address_line2=address_line2, + pincode=999999, state_code=96, place_of_supply=96 )) def get_item_list(invoice): From b01b108dfa7baf53562f361a69657fe2ec1fc981 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 5 Jan 2021 17:34:16 +0530 Subject: [PATCH 24/34] fix: do not consider current salary slip in sum --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 99d8a8317c..3bb1f62b08 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -1145,7 +1145,9 @@ class SalarySlip(TransactionBase): fields = ['sum(net_pay) as sum'], filters = {'employee_name' : self.employee_name, 'start_date' : ['>=', period_start_date], - 'end_date' : ['<', period_end_date]}) + 'end_date' : ['<', period_end_date], + 'name': ['!=', self.name] + }) year_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0 @@ -1160,7 +1162,8 @@ class SalarySlip(TransactionBase): fields = ['sum(net_pay) as sum'], filters = {'employee_name' : self.employee_name, 'start_date' : ['>=', first_day_of_the_month], - 'end_date' : ['<', self.start_date] + 'end_date' : ['<', self.start_date], + 'name': ['!=', self.name] }) month_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0 From f7b9b0687ead28f180839c19fe04a02f6829be35 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 5 Jan 2021 20:43:11 +0530 Subject: [PATCH 25/34] fix: tax calculation on salary slip for the first month (#24272) * fix: tax calculation on salary slip for the first month * fix: net pay precision issue * fix: net pay precision issue Co-authored-by: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> --- erpnext/hr/doctype/employee/employee.json | 3 +- .../doctype/salary_slip/salary_slip.js | 1 - .../doctype/salary_slip/salary_slip.py | 46 +++++++++++-------- .../doctype/salary_slip/test_salary_slip.py | 2 +- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index 4f1c04ff5d..dc2aaa4a06 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -813,7 +813,7 @@ "idx": 24, "image_field": "image", "links": [], - "modified": "2020-10-16 15:02:04.283657", + "modified": "2021-01-01 16:54:33.477439", "modified_by": "Administrator", "module": "HR", "name": "Employee", @@ -855,7 +855,6 @@ "write": 1 } ], - "quick_entry": 1, "search_fields": "employee_name", "show_name_in_global_search": 1, "sort_field": "modified", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js index 8e05bb2057..51fb3596e9 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.js +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js @@ -151,7 +151,6 @@ frappe.ui.form.on("Salary Slip", { var salary_detail_fields = ["formula", "abbr", "statistical_component", "variable_based_on_taxable_salary"]; frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields, false); frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields, false); - calculate_totals(frm); frm.trigger("set_dynamic_labels"); }, diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 3bb1f62b08..d725f68a6b 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -143,8 +143,8 @@ class SalarySlip(TransactionBase): self.salary_slip_based_on_timesheet = self._salary_structure_doc.salary_slip_based_on_timesheet or 0 self.set_time_sheet() self.pull_sal_struct() - payroll_based_on, consider_unmarked_attendance_as = frappe.db.get_value("Payroll Settings", None, ["payroll_based_on","consider_unmarked_attendance_as"]) - return [payroll_based_on, consider_unmarked_attendance_as] + ps = frappe.db.get_value("Payroll Settings", None, ["payroll_based_on","consider_unmarked_attendance_as"], as_dict=1) + return [ps.payroll_based_on, ps.consider_unmarked_attendance_as] def set_time_sheet(self): if self.salary_slip_based_on_timesheet: @@ -424,16 +424,19 @@ class SalarySlip(TransactionBase): def calculate_net_pay(self): if self.salary_structure: self.calculate_component_amounts("earnings") - self.gross_pay = self.get_component_totals("earnings") + self.gross_pay = self.get_component_totals("earnings", depends_on_payment_days=1) self.base_gross_pay = flt(flt(self.gross_pay) * flt(self.exchange_rate), self.precision('base_gross_pay')) if self.salary_structure: self.calculate_component_amounts("deductions") + + self.set_loan_repayment() + self.set_component_amounts_based_on_payment_days() + self.set_net_pay() + + def set_net_pay(self): self.total_deduction = self.get_component_totals("deductions") self.base_total_deduction = flt(flt(self.total_deduction) * flt(self.exchange_rate), self.precision('base_total_deduction')) - - self.set_loan_repayment() - self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment)) self.rounded_total = rounded(self.net_pay) self.base_net_pay = flt(flt(self.net_pay) * flt(self.exchange_rate), self.precision('base_net_pay')) @@ -455,8 +458,6 @@ class SalarySlip(TransactionBase): else: self.add_tax_components(payroll_period) - self.set_component_amounts_based_on_payment_days(component_type) - def add_structure_components(self, component_type): data = self.get_data_for_eval() for struct_row in self._salary_structure_doc.get(component_type): @@ -813,7 +814,7 @@ class SalarySlip(TransactionBase): cint(row.depends_on_payment_days) and cint(self.total_working_days) and (not self.salary_slip_based_on_timesheet or getdate(self.start_date) < joining_date or - getdate(self.end_date) > relieving_date + (relieving_date and getdate(self.end_date) > relieving_date) )): additional_amount = flt((flt(row.additional_amount) * flt(self.payment_days) / cint(self.total_working_days)), row.precision("additional_amount")) @@ -946,15 +947,21 @@ class SalarySlip(TransactionBase): struct_row['variable_based_on_taxable_salary'] = component.variable_based_on_taxable_salary return struct_row - def get_component_totals(self, component_type): + def get_component_totals(self, component_type, depends_on_payment_days=0): + joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, + ["date_of_joining", "relieving_date"]) + total = 0.0 for d in self.get(component_type): if not d.do_not_include_in_total: - d.amount = flt(d.amount, d.precision("amount")) - total += d.amount + if depends_on_payment_days: + amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0] + else: + amount = flt(d.amount, d.precision("amount")) + total += amount return total - def set_component_amounts_based_on_payment_days(self, component_type): + def set_component_amounts_based_on_payment_days(self): joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, ["date_of_joining", "relieving_date"]) @@ -964,8 +971,9 @@ class SalarySlip(TransactionBase): if not joining_date: frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name))) - for d in self.get(component_type): - d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0] + for component_type in ("earnings", "deductions"): + for d in self.get(component_type): + d.amount = flt(self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0], d.precision("amount")) def set_loan_repayment(self): self.total_loan_repayment = 0 @@ -1089,17 +1097,17 @@ class SalarySlip(TransactionBase): self.calculate_net_pay() def set_totals(self): - self.gross_pay = 0 + self.gross_pay = 0.0 if self.salary_slip_based_on_timesheet == 1: self.calculate_total_for_salary_slip_based_on_timesheet() else: - self.total_deduction = 0 + self.total_deduction = 0.0 if self.earnings: for earning in self.earnings: - self.gross_pay += flt(earning.amount) + self.gross_pay += flt(earning.amount, earning.precision("amount")) if self.deductions: for deduction in self.deductions: - self.total_deduction += flt(deduction.amount) + self.total_deduction += flt(deduction.amount, deduction.precision("amount")) self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) - flt(self.total_loan_repayment) self.set_base_totals() diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index d6fb419598..4368c03c2a 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -318,7 +318,7 @@ class TestSalarySlip(unittest.TestCase): year_to_date = 0 for slip in salary_slips: - year_to_date += slip.net_pay + year_to_date += flt(slip.net_pay) self.assertEqual(slip.year_to_date, year_to_date) def test_tax_for_payroll_period(self): From dd768a07c5ff7d4efa243351f2a1fb1be23b044e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 5 Jan 2021 23:55:00 +0530 Subject: [PATCH 26/34] fix: Sanctioned loan security unpledge --- .../loan_management/doctype/loan/test_loan.py | 21 +++++++++++++++++++ .../loan_security_unpledge.py | 12 ++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 8b1f9a2266..2abd7d84d9 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -362,6 +362,27 @@ class TestLoan(unittest.TestCase): unpledge_request.load_from_db() self.assertEqual(unpledge_request.docstatus, 1) + def test_santined_loan_security_unpledge(self): + pledge = [{ + "loan_security": "Test Security 1", + "qty": 4000.00 + }] + + loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge) + create_pledge(loan_application) + + loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01') + loan.submit() + + self.assertEquals(loan.loan_amount, 1000000) + + unpledge_map = {'Test Security 1': 4000} + unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1) + unpledge_request.submit() + unpledge_request.status = 'Approved' + unpledge_request.save() + unpledge_request.submit() + def test_disbursal_check_with_shortfall(self): pledges = [{ "loan_security": "Test Security 2", diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py index 61c418d3d3..ae88a07e25 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py @@ -44,10 +44,16 @@ class LoanSecurityUnpledge(Document): "valid_upto": (">=", get_datetime()) }, as_list=1)) - total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid', - 'total_interest_payable', 'written_off_amount']) + loan_details = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid', + 'total_interest_payable', 'written_off_amount', 'disbursed_amount', 'status'], as_dict=1) + + if loan_details.status == 'Disbursed': + pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.interest_payable) \ + - flt(loan_details.principal_paid) - flt(loan_details.written_off_amount) + else: + pending_principal_amount = flt(loan_details.disbursed_amount) - flt(loan_details.total_interest_payable) \ + - flt(loan_details.total_principal_paid) - flt(loan_details.written_off_amount) - pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount) security_value = 0 unpledge_qty_map = {} ltv_ratio = 0 From 05fe7ac29cde423616f15b05643705d4bab026f0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 6 Jan 2021 09:10:28 +0530 Subject: [PATCH 27/34] fix: fieldname --- .../doctype/loan_security_unpledge/loan_security_unpledge.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py index ae88a07e25..c4c2d68378 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py @@ -48,8 +48,8 @@ class LoanSecurityUnpledge(Document): 'total_interest_payable', 'written_off_amount', 'disbursed_amount', 'status'], as_dict=1) if loan_details.status == 'Disbursed': - pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.interest_payable) \ - - flt(loan_details.principal_paid) - flt(loan_details.written_off_amount) + pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \ + - flt(loan_details.total_principal_paid) - flt(loan_details.written_off_amount) else: pending_principal_amount = flt(loan_details.disbursed_amount) - flt(loan_details.total_interest_payable) \ - flt(loan_details.total_principal_paid) - flt(loan_details.written_off_amount) From ad8be7c1fedd6bc41afaef23f86b44bffa3a9a1f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 6 Jan 2021 09:29:03 +0530 Subject: [PATCH 28/34] fix: Consider only submitted salary slips --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index d725f68a6b..47c9d31bf4 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -429,11 +429,11 @@ class SalarySlip(TransactionBase): if self.salary_structure: self.calculate_component_amounts("deductions") - + self.set_loan_repayment() self.set_component_amounts_based_on_payment_days() self.set_net_pay() - + def set_net_pay(self): self.total_deduction = self.get_component_totals("deductions") self.base_total_deduction = flt(flt(self.total_deduction) * flt(self.exchange_rate), self.precision('base_total_deduction')) @@ -1154,10 +1154,10 @@ class SalarySlip(TransactionBase): filters = {'employee_name' : self.employee_name, 'start_date' : ['>=', period_start_date], 'end_date' : ['<', period_end_date], - 'name': ['!=', self.name] + 'name': ['!=', self.name], + 'docstatus': 1 }) - year_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0 year_to_date += self.net_pay @@ -1171,7 +1171,8 @@ class SalarySlip(TransactionBase): filters = {'employee_name' : self.employee_name, 'start_date' : ['>=', first_day_of_the_month], 'end_date' : ['<', self.start_date], - 'name': ['!=', self.name] + 'name': ['!=', self.name], + 'docstatus': 1 }) month_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0 From e7fa6f6a1cb571544708f30d6456891132c27115 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Wed, 6 Jan 2021 13:15:30 +0530 Subject: [PATCH 29/34] fix: edditable employee grid --- .../doctype/payroll_entry/payroll_entry.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index a25a6e7a32..6bcd4e0c00 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -21,6 +21,9 @@ class PayrollEntry(Document): if cint(entries) == len(self.employees): self.set_onload("submitted_ss", True) + def validate(self): + self.number_of_employees = len(self.employees) + def on_submit(self): self.create_salary_slips() @@ -113,7 +116,7 @@ class PayrollEntry(Document): for d in employees: self.append('employees', d) - self.number_of_employees = len(employees) + self.number_of_employees = len(self.employees) if self.validate_attendance: return self.validate_employee_attendance() @@ -145,8 +148,8 @@ class PayrollEntry(Document): """ self.check_permission('write') self.created = 1 - emp_list = [d.employee for d in self.get_emp_list()] - if emp_list: + employees = [emp.employee for emp in self.employees] + if employees: args = frappe._dict({ "salary_slip_based_on_timesheet": self.salary_slip_based_on_timesheet, "payroll_frequency": self.payroll_frequency, @@ -160,10 +163,10 @@ class PayrollEntry(Document): "exchange_rate": self.exchange_rate, "currency": self.currency }) - if len(emp_list) > 30: - frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=emp_list, args=args) + if len(employees) > 30: + frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=employees, args=args) else: - create_salary_slips_for_employees(emp_list, args, publish_progress=False) + create_salary_slips_for_employees(employees, args, publish_progress=False) # since this method is called via frm.call this doc needs to be updated manually self.reload() From ff6ee9d4e712ac1d573e010c5a36193abacbcf50 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 8 Jan 2021 09:14:43 +0530 Subject: [PATCH 30/34] fix: Formula field description and Rearrange grid view - Missing closing quote in Formula field description - In grid view of child table in QI, show only input fields --- .../item_quality_inspection_parameter.json | 4 ++-- .../quality_inspection_reading.json | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json index fc06e89f2f..3e81619cfd 100644 --- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json +++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json @@ -43,7 +43,7 @@ }, { "depends_on": "formula_based_criteria", - "description": "Simple Python formula applied on Reading fields.
Numeric eg. 1: reading_1 > 0.2 and reading_1 < 0.5
\nNumeric eg. 2: mean > 3.5 (mean of populated fields)
\nValue based eg.: reading_value in (\"A\", \"B\", \"C)", + "description": "Simple Python formula applied on Reading fields.
Numeric eg. 1: reading_1 > 0.2 and reading_1 < 0.5
\nNumeric eg. 2: mean > 3.5 (mean of populated fields)
\nValue based eg.: reading_value in (\"A\", \"B\", \"C\")", "fieldname": "acceptance_formula", "fieldtype": "Code", "label": "Acceptance Criteria Formula" @@ -80,7 +80,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-12-28 17:41:04.350225", + "modified": "2021-01-07 21:32:49.866439", "modified_by": "Administrator", "module": "Stock", "name": "Item Quality Inspection Parameter", diff --git a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json index 739845bcda..dddb3d517d 100644 --- a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json +++ b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json @@ -49,7 +49,6 @@ "depends_on": "eval:(!doc.formula_based_criteria && doc.non_numeric)", "fieldname": "value", "fieldtype": "Data", - "in_list_view": 1, "label": "Acceptance Criteria Value", "oldfieldname": "value", "oldfieldtype": "Data" @@ -76,7 +75,6 @@ "columns": 1, "fieldname": "reading_3", "fieldtype": "Data", - "in_list_view": 1, "label": "Reading 3", "oldfieldname": "reading_3", "oldfieldtype": "Data" @@ -153,7 +151,7 @@ }, { "depends_on": "formula_based_criteria", - "description": "Simple Python formula applied on Reading fields.
Numeric eg. 1: reading_1 > 0.2 and reading_1 < 0.5
\nNumeric eg. 2: mean > 3.5 (mean of populated fields)
\nValue based eg.: reading_value in (\"A\", \"B\", \"C)", + "description": "Simple Python formula applied on Reading fields.
Numeric eg. 1: reading_1 > 0.2 and reading_1 < 0.5
\nNumeric eg. 2: mean > 3.5 (mean of populated fields)
\nValue based eg.: reading_value in (\"A\", \"B\", \"C\")", "fieldname": "acceptance_formula", "fieldtype": "Code", "label": "Acceptance Criteria Formula" @@ -190,6 +188,7 @@ "depends_on": "non_numeric", "fieldname": "reading_value", "fieldtype": "Data", + "in_list_view": 1, "label": "Reading Value" }, { @@ -202,6 +201,7 @@ "default": "0", "fieldname": "non_numeric", "fieldtype": "Check", + "in_list_view": 1, "label": "Non-Numeric" }, { @@ -215,7 +215,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-12-28 17:40:47.407210", + "modified": "2021-01-07 21:56:40.235579", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection Reading", From c4963bfdb24819e2edd4a28c11551190965d9cd1 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 8 Jan 2021 09:56:04 +0530 Subject: [PATCH 31/34] fix: Back Update from QC based on Batch No --- .../quality_inspection/quality_inspection.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index ae4eb9b995..b30d48d547 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -69,11 +69,21 @@ class QualityInspection(Document): doctype = 'Stock Entry Detail' if self.reference_type and self.reference_name: + conditions = "" + if self.batch_no: + conditions += " and t1.batch_no = '%s'"%(self.batch_no) + frappe.db.sql(""" - UPDATE `tab{child_doc}` t1, `tab{parent_doc}` t2 - SET t1.quality_inspection = %s, t2.modified = %s - WHERE t1.parent = %s and t1.item_code = %s and t1.parent = t2.name - """.format(parent_doc=self.reference_type, child_doc=doctype), + UPDATE + `tab{child_doc}` t1, `tab{parent_doc}` t2 + SET + t1.quality_inspection = %s, t2.modified = %s + WHERE + t1.parent = %s + and t1.item_code = %s + and t1.parent = t2.name + {conditions} + """.format(parent_doc=self.reference_type, child_doc=doctype, conditions=conditions), (quality_inspection, self.modified, self.reference_name, self.item_code)) def set_status_based_on_acceptance_formula(self): From a93151502c5f27b8492449d83d95eba25ff2887e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 8 Jan 2021 12:10:26 +0530 Subject: [PATCH 32/34] fix: Components formulated from additional salary not being fetched in Payroll Entry --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 47c9d31bf4..183ad13411 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -577,7 +577,7 @@ class SalarySlip(TransactionBase): 'default_amount': amount if not struct_row.get("is_additional_component") else 0, 'depends_on_payment_days' : struct_row.depends_on_payment_days, 'salary_component' : struct_row.salary_component, - 'abbr' : struct_row.abbr, + 'abbr' : struct_row.abbr or struct_row.get("salary_component_abbr"), 'additional_salary': additional_salary, 'do_not_include_in_total' : struct_row.do_not_include_in_total, 'is_tax_applicable': struct_row.is_tax_applicable, From b7637f49cd543e8a4fdbfc0d840323583d5c3651 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 8 Jan 2021 18:35:49 +0530 Subject: [PATCH 33/34] fix: Remove QI link on cancel wherever same QI name exists --- .../stock/doctype/quality_inspection/quality_inspection.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index b30d48d547..2084e3fa54 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -70,9 +70,12 @@ class QualityInspection(Document): if self.reference_type and self.reference_name: conditions = "" - if self.batch_no: + if self.batch_no and self.docstatus == 1: conditions += " and t1.batch_no = '%s'"%(self.batch_no) + if self.docstatus == 2: # if cancel, then remove qi link wherever same name + conditions += " and t1.quality_inspection = '%s'"%(self.name) + frappe.db.sql(""" UPDATE `tab{child_doc}` t1, `tab{parent_doc}` t2 From dcda8b9e8cfaac31056792ff3ab9a70975c2727a Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 11 Jan 2021 12:50:39 +0530 Subject: [PATCH 34/34] feat: Patient appointment status changes (#24201) * feat: patient appointment status changes * fix: sider * fix: sider * fix: test status on cancel of docs and test refactor Co-authored-by: pateljannat Co-authored-by: Rucha Mahabal --- .../patient_appointment/test_patient_appointment.py | 4 +++- .../doctype/therapy_plan/test_therapy_plan.py | 13 +++++++++++-- .../healthcare/doctype/therapy_plan/therapy_plan.py | 3 ++- .../doctype/therapy_session/therapy_session.js | 9 +++++++++ .../doctype/therapy_session/therapy_session.py | 7 +++++++ 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 3df7ba1531..b681ed1a22 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -23,8 +23,10 @@ class TestPatientAppointment(unittest.TestCase): self.assertEquals(appointment.status, 'Open') appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2)) self.assertEquals(appointment.status, 'Scheduled') - create_encounter(appointment) + encounter = create_encounter(appointment) self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') + encounter.cancel() + self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') def test_start_encounter(self): patient, medical_department, practitioner = create_healthcare_docs() diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py index a061c66a54..7fb159d6b5 100644 --- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py @@ -5,10 +5,10 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import getdate, flt +from frappe.utils import getdate, flt, nowdate from erpnext.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type from erpnext.healthcare.doctype.therapy_plan.therapy_plan import make_therapy_session, make_sales_invoice -from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient +from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient, create_appointment class TestTherapyPlan(unittest.TestCase): def test_creation_on_encounter_submission(self): @@ -28,6 +28,15 @@ class TestTherapyPlan(unittest.TestCase): frappe.get_doc(session).submit() self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed') + patient, medical_department, practitioner = create_healthcare_docs() + appointment = create_appointment(patient, practitioner, nowdate()) + session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name) + session = frappe.get_doc(session) + session.submit() + self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') + session.cancel() + self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') + def test_therapy_plan_from_template(self): patient = create_patient() template = create_therapy_plan_template() diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py index bc0ff1a505..ac01c604dd 100644 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py @@ -47,7 +47,7 @@ class TherapyPlan(Document): @frappe.whitelist() -def make_therapy_session(therapy_plan, patient, therapy_type, company): +def make_therapy_session(therapy_plan, patient, therapy_type, company, appointment=None): therapy_type = frappe.get_doc('Therapy Type', therapy_type) therapy_session = frappe.new_doc('Therapy Session') @@ -58,6 +58,7 @@ def make_therapy_session(therapy_plan, patient, therapy_type, company): therapy_session.duration = therapy_type.default_duration therapy_session.rate = therapy_type.rate therapy_session.exercises = therapy_type.exercises + therapy_session.appointment = appointment if frappe.flags.in_test: therapy_session.start_date = today() diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.js b/erpnext/healthcare/doctype/therapy_session/therapy_session.js index a2b01c9c18..fd20003693 100644 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session.js +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.js @@ -19,6 +19,15 @@ frappe.ui.form.on('Therapy Session', { } }; }); + + frm.set_query('appointment', function() { + + return { + filters: { + 'status': ['in', ['Open', 'Scheduled']] + } + }; + }); }, refresh: function(frm) { diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.py b/erpnext/healthcare/doctype/therapy_session/therapy_session.py index 85d0970177..c00054421d 100644 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session.py +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.py @@ -43,7 +43,14 @@ class TherapySession(Document): self.update_sessions_count_in_therapy_plan() insert_session_medical_record(self) + def on_update(self): + if self.appointment: + frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed') + def on_cancel(self): + if self.appointment: + frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open') + self.update_sessions_count_in_therapy_plan(on_cancel=True) def update_sessions_count_in_therapy_plan(self, on_cancel=False):