From b6dc0efa27bd3196f01e2bb1a5ade2422073ac00 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 22 Jun 2021 16:53:35 +0530 Subject: [PATCH 01/98] feat: add provision for process loss in manufac --- erpnext/manufacturing/doctype/bom/bom.py | 7 +- .../bom_scrap_item/bom_scrap_item.json | 429 ++++-------------- .../stock/doctype/stock_entry/stock_entry.py | 23 +- .../stock_entry_detail.json | 9 +- 4 files changed, 133 insertions(+), 335 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 0ba85078ea..6bd2a985e2 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -808,8 +808,11 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True) elif fetch_scrap_items: - query = query.format(table="BOM Scrap Item", where_conditions="", - select_columns=", bom_item.idx, item.description", is_stock_item=is_stock_item, qty_field="stock_qty") + query = query.format( + table="BOM Scrap Item", where_conditions="", + select_columns=", bom_item.idx, item.description, is_process_loss", + is_stock_item=is_stock_item, qty_field="stock_qty" + ) items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True) else: diff --git a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json index 9f7091dd8d..7018082e40 100644 --- a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json +++ b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json @@ -1,345 +1,112 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-09-26 02:19:21.642081", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "actions": [], + "creation": "2016-09-26 02:19:21.642081", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "column_break_2", + "item_name", + "is_process_loss", + "quantity_and_rate", + "stock_qty", + "rate", + "amount", + "column_break_6", + "stock_uom", + "base_rate", + "base_amount" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "item_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Item Name" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "quantity_and_rate", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Quantity and Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "quantity_and_rate", + "fieldtype": "Section Break", + "label": "Quantity and Rate" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "stock_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Qty", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "stock_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Qty", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Rate", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate", + "options": "currency" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amount", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "options": "currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_6", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "stock_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Stock UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Basic Rate (Company Currency)", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "base_rate", + "fieldtype": "Currency", + "label": "Basic Rate (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Basic Amount (Company Currency)", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "base_amount", + "fieldtype": "Currency", + "label": "Basic Amount (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "is_process_loss", + "fieldtype": "Check", + "label": "Is Process Loss" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-07-04 16:04:32.442287", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "BOM Scrap Item", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "istable": 1, + "links": [], + "modified": "2021-06-22 16:46:12.153311", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "BOM Scrap Item", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 7b31d2fdf2..478d5b2e0f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -463,7 +463,7 @@ class StockEntry(StockController): """ # Set rate for outgoing items outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate, raise_error_if_no_rate) - finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item) + finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item or d.is_process_loss) # Set basic rate for incoming items for d in self.get('items'): @@ -484,6 +484,8 @@ class StockEntry(StockController): raise_error_if_no_rate=raise_error_if_no_rate) d.basic_rate = flt(d.basic_rate, d.precision("basic_rate")) + if d.is_process_loss: + d.basic_rate = flt(0.) d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount")) def set_rate_for_outgoing_items(self, reset_outgoing_rate=True, raise_error_if_no_rate=True): @@ -1041,6 +1043,7 @@ class StockEntry(StockController): self.set_scrap_items() self.set_actual_qty() + self.adjust_qty_for_process_loss() self.validate_customer_provided_item() self.calculate_rate_and_amount() @@ -1398,6 +1401,7 @@ class StockEntry(StockController): get_default_cost_center(item_dict[d], company = self.company)) se_child.is_finished_item = item_dict[d].get("is_finished_item", 0) se_child.is_scrap_item = item_dict[d].get("is_scrap_item", 0) + se_child.is_process_loss = item_dict[d].get("is_process_loss", 0) for field in ["idx", "po_detail", "original_item", "expense_account", "description", "item_name", "serial_no", "batch_no"]: @@ -1576,6 +1580,23 @@ class StockEntry(StockController): if material_request and material_request not in material_requests: material_requests.append(material_request) frappe.db.set_value('Material Request', material_request, 'transfer_status', status) + + def adjust_qty_for_process_loss(self): + process_loss_dict = {} + for d in self.get("items"): + if not d.is_process_loss: + continue + if d.item_code not in process_loss_dict: + process_loss_dict[d.item_code] = [flt(0), flt(0)] + process_loss_dict[d.item_code][0] += flt(d.transfer_qty) + process_loss_dict[d.item_code][1] += flt(d.qty) + + for d in self.get("items"): + if not d.is_finished_item or d.item_code not in process_loss_dict: + continue + # Assumption: 1 FG has 1 row. + d.transfer_qty -= process_loss_dict[d.item_code][0] + d.qty -= process_loss_dict[d.item_code][1] def set_serial_no_batch_for_finished_good(self): args = {} diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index 22f412a298..df65706c39 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -19,6 +19,7 @@ "is_finished_item", "is_scrap_item", "quality_inspection", + "is_process_loss", "subcontracted_item", "section_break_8", "description", @@ -543,13 +544,19 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "default": "0", + "fieldname": "is_process_loss", + "fieldtype": "Check", + "label": "Is Process Loss" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-06-21 16:03:18.834880", + "modified": "2021-06-22 16:47:11.268975", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Detail", From 984c97ed4ee80c72edf8f12776ab3a8d99605424 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Wed, 23 Jun 2021 15:06:00 +0530 Subject: [PATCH 02/98] feat: add is process loss autoset and validation --- erpnext/manufacturing/doctype/bom/bom.js | 15 +++++++++++++++ erpnext/manufacturing/doctype/bom/bom.py | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 3f50b41be1..a5ce8c6195 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -379,6 +379,9 @@ erpnext.bom.BomController = class BomController extends erpnext.TransactionContr child.bom_no = ''; } + if (scrap_items) { + set_is_process_loss(doc, cdt, cdn) + } get_bom_material_detail(doc, cdt, cdn, scrap_items); } @@ -446,6 +449,10 @@ var get_bom_material_detail = function(doc, cdt, cdn, scrap_items) { }, callback: function(r) { d = locals[cdt][cdn]; + if (d.is_process_loss) { + r.message.rate = 0 + r.message.base_rate = 0 + } $.extend(d, r.message); refresh_field("items"); refresh_field("scrap_items"); @@ -655,3 +662,11 @@ frappe.ui.form.on("BOM", "with_operations", function(frm) { frm.set_value("operations", []); } }); + +function set_is_process_loss(doc, cdt, cdn) { + const row = locals[cdt][cdn] + if (row.item_code === doc.item) { + row.is_process_loss = 1 + frappe.msgprint(__("Item:") + ` ${row.item_code} ` + __("set as process loss.")) + } +} diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 6bd2a985e2..de0c521cf5 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -155,6 +155,7 @@ class BOM(WebsiteGenerator): self.update_stock_qty() self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False) self.set_bom_level() + self.validate_scrap_items() def get_context(self, context): context.parents = [{'name': 'boms', 'title': _('All BOMs') }] @@ -691,6 +692,15 @@ class BOM(WebsiteGenerator): if update: self.db_set("bom_level", self.bom_level) + def validate_scrap_items(self): + for item in self.scrap_items: + if item.item_code == self.item and not item.is_process_loss: + frappe.throw(_('Item:') + f' {item.item_code} ' +\ + _('in Scrap/Loss Items table should have Is Process Loss checked.')) + elif item.item_code != self.item and item.is_process_loss: + frappe.throw(_('Item:') + f' {item.item_code} ' +\ + _('in Scrap/Loss Items table should not have Is Process Loss checked.')) + def get_bom_item_rate(args, bom_doc): if bom_doc.rm_cost_as_per == 'Valuation Rate': rate = get_valuation_rate(args) * (args.get("conversion_factor") or 1) From 3df8d0cdf0f00e78c64ac3044f13e6e50f0010d5 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Wed, 23 Jun 2021 15:30:48 +0530 Subject: [PATCH 03/98] fix: add warehouse and unset is scrap for process loss items --- erpnext/stock/doctype/stock_entry/stock_entry.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 478d5b2e0f..4f724ec637 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1043,7 +1043,7 @@ class StockEntry(StockController): self.set_scrap_items() self.set_actual_qty() - self.adjust_qty_for_process_loss() + self.update_items_for_process_loss() self.validate_customer_provided_item() self.calculate_rate_and_amount() @@ -1581,11 +1581,17 @@ class StockEntry(StockController): material_requests.append(material_request) frappe.db.set_value('Material Request', material_request, 'transfer_status', status) - def adjust_qty_for_process_loss(self): + def update_items_for_process_loss(self): process_loss_dict = {} for d in self.get("items"): if not d.is_process_loss: continue + + scrap_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_scrap_warehouse") + if scrap_warehouse is not None: + d.t_warehouse = scrap_warehouse + d.is_scrap_item = 0 + if d.item_code not in process_loss_dict: process_loss_dict[d.item_code] = [flt(0), flt(0)] process_loss_dict[d.item_code][0] += flt(d.transfer_qty) @@ -1594,7 +1600,7 @@ class StockEntry(StockController): for d in self.get("items"): if not d.is_finished_item or d.item_code not in process_loss_dict: continue - # Assumption: 1 FG has 1 row. + # Assumption: 1 finished item has 1 row. d.transfer_qty -= process_loss_dict[d.item_code][0] d.qty -= process_loss_dict[d.item_code][1] From 7433b971060a671971af0c23e64efd4dab951799 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Thu, 24 Jun 2021 15:05:52 +0530 Subject: [PATCH 04/98] refactor: shift auto entry of is process loss check, update validations --- erpnext/manufacturing/doctype/bom/bom.js | 45 ++++++++++++++++++------ erpnext/manufacturing/doctype/bom/bom.py | 22 +++++++++--- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index a5ce8c6195..dd437dd555 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -379,9 +379,6 @@ erpnext.bom.BomController = class BomController extends erpnext.TransactionContr child.bom_no = ''; } - if (scrap_items) { - set_is_process_loss(doc, cdt, cdn) - } get_bom_material_detail(doc, cdt, cdn, scrap_items); } @@ -450,9 +447,10 @@ var get_bom_material_detail = function(doc, cdt, cdn, scrap_items) { callback: function(r) { d = locals[cdt][cdn]; if (d.is_process_loss) { - r.message.rate = 0 - r.message.base_rate = 0 + r.message.rate = 0; + r.message.base_rate = 0; } + $.extend(d, r.message); refresh_field("items"); refresh_field("scrap_items"); @@ -661,12 +659,37 @@ frappe.ui.form.on("BOM", "with_operations", function(frm) { if(!cint(frm.doc.with_operations)) { frm.set_value("operations", []); } + toggle_operations(frm); }); -function set_is_process_loss(doc, cdt, cdn) { - const row = locals[cdt][cdn] - if (row.item_code === doc.item) { - row.is_process_loss = 1 - frappe.msgprint(__("Item:") + ` ${row.item_code} ` + __("set as process loss.")) - } +frappe.ui.form.on("BOM Scrap Item", { + item_code(frm, cdt, cdn) { + const { item_code } = locals[cdt][cdn]; + if (item_code === frm.doc.item) { + locals[cdt][cdn].is_process_loss = 1; + trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code) + } + }, +}); + +function trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code) { + frappe.prompt( + { + fieldname: "percent", + fieldtype: "Percent", + label: __("% Finished Item Quantity"), + description: + __("Set quantity of process loss item:") + + ` ${item_code} ` + + __("as a percentage of finished item quantity"), + }, + (data) => { + const row = locals[cdt][cdn]; + row.stock_qty = (frm.doc.quantity * data.percent) / 100; + row.qty = row.stock_qty / (row.conversion_factor ?? 1); + refresh_field("scrap_items"); + }, + __("Set Process Loss Item Quantity"), + __("Set Quantity") + ); } diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index de0c521cf5..b90d54dea5 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -695,11 +695,25 @@ class BOM(WebsiteGenerator): def validate_scrap_items(self): for item in self.scrap_items: if item.item_code == self.item and not item.is_process_loss: - frappe.throw(_('Item:') + f' {item.item_code} ' +\ - _('in Scrap/Loss Items table should have Is Process Loss checked.')) + frappe.throw(_('Scrap/Loss Item:') + f' {frappe.bold(item.item_code)} ' +\ + _('should have') + ' ' + frappe.bold(_('Is Process Loss')) + ' ' + ('checked.')) elif item.item_code != self.item and item.is_process_loss: - frappe.throw(_('Item:') + f' {item.item_code} ' +\ - _('in Scrap/Loss Items table should not have Is Process Loss checked.')) + frappe.throw(_('Scrap/Loss Item:') + f' {frappe.bold(item.item_code)} ' +\ + _('should not have') + ' ' + frappe.bold(_('Is Process Loss')) + ' ' + ('checked.')) + + stock_uom = item.stock_uom + must_be_whole_number = frappe.get_value("UOM", stock_uom, "must_be_whole_number") + if item.is_process_loss and must_be_whole_number: + frappe.throw(_('Item:') + f' {frappe.bold(item.item_code)} ' +\ + _('with Stock UOM:') + f' {frappe.bold(stock_uom)} '+\ + _('cannot be a Scrap/Loss Item.')) + + if item.is_process_loss and (item.stock_qty >= self.quantity): + frappe.throw(_('Scrap/Loss Item:') + f' {item.item_code} ' +\ + _('should have') +' '+frappe.bold(_('Qty')) +\ + ' ' + _('less than finished goods') + ' ' +\ + frappe.bold(_('Quantity.'))) + def get_bom_item_rate(args, bom_doc): if bom_doc.rm_cost_as_per == 'Valuation Rate': From 8ecb14623175e8a789bd82a9bbeb1738787046f2 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Fri, 25 Jun 2021 14:20:22 +0530 Subject: [PATCH 05/98] test: add bom tests for process loss val, add se test for qty calc --- erpnext/manufacturing/doctype/bom/test_bom.py | 73 +++++++++++++++++++ .../tests/test_stock_entry_for_manufacture.js | 27 +++++++ 2 files changed, 100 insertions(+) create mode 100644 erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_manufacture.js diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index c89f7d66fd..e61bb52592 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -280,6 +280,38 @@ class TestBOM(unittest.TestCase): self.assertEqual(reqd_item.qty, created_item.qty) self.assertEqual(reqd_item.exploded_qty, created_item.exploded_qty) + def test_bom_with_process_loss_item(self): + fg_item_non_whole, fg_item_whole, bom_item = create_process_loss_bom_items() + + if frappe.db.exists("BOM", f"BOM-{fg_item_non_whole.item_code}-001") is None: + bom_doc = create_bom_with_process_loss_item( + fg_item_non_whole, bom_item, 0.25, 0, 1 + ) + bom_doc.submit() + + bom_doc = create_bom_with_process_loss_item( + fg_item_non_whole, bom_item, 2, 0 + ) + # PL Item qty can't be >= FG Item qty + self.assertRaises(frappe.ValidationError, bom_doc.submit) + + bom_doc = create_bom_with_process_loss_item( + fg_item_non_whole, bom_item, 1, 100 + ) + # PL Item rate has to be 0 + self.assertRaises(frappe.ValidationError, bom_doc.submit) + + bom_doc = create_bom_with_process_loss_item( + fg_item_whole, bom_item, 0.25, 0 + ) + # Items with whole UOMs can't be PL Items + self.assertRaises(frappe.ValidationError, bom_doc.submit) + + bom_doc = create_bom_with_process_loss_item( + fg_item_non_whole, bom_item, 0.25, 0, is_process_loss=0 + ) + # FG Items in Scrap/Loss Table should have Is Process Loss set + self.assertRaises(frappe.ValidationError, bom_doc.submit) def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) @@ -353,3 +385,44 @@ def reset_item_valuation_rate(item_code, warehouse_list=None, qty=None, rate=Non for warehouse in warehouse_list: create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=qty, rate=rate) + +def create_bom_with_process_loss_item( + fg_item, bom_item, scrap_qty, scrap_rate, fg_qty=2, is_process_loss=1): + bom_doc = frappe.new_doc("BOM") + bom_doc.item = fg_item.item_code + bom_doc.quantity = fg_qty + bom_doc.append("items", { + "item_code": bom_item.item_code, + "qty": 1, + "uom": bom_item.stock_uom, + "stock_uom": bom_item.stock_uom, + "rate": 100.0 + }) + bom_doc.append("scrap_items", { + "item_code": fg_item.item_code, + "qty": scrap_qty, + "stock_qty": scrap_qty, + "uom": fg_item.stock_uom, + "stock_uom": fg_item.stock_uom, + "rate": scrap_rate, + "is_process_loss": is_process_loss + }) + return bom_doc + +def create_process_loss_bom_items(): + item_list = [ + ("_Test Item - Non Whole UOM", "Kg"), + ("_Test Item - Whole UOM", "Unit"), + ( "_Test PL BOM Item", "Unit") + ] + return [create_process_loss_bom_item(it) for it in item_list] + +def create_process_loss_bom_item(item_tuple): + item_code, stock_uom = item_tuple + if frappe.db.exists("Item", item_code) is None: + return make_item( + item_code, + {'stock_uom':stock_uom, 'valuation_rate':100} + ) + else: + return frappe.get_doc("Item", item_code) diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_manufacture.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_manufacture.js new file mode 100644 index 0000000000..d74f31672d --- /dev/null +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_manufacture.js @@ -0,0 +1,27 @@ +QUnit.module('Stock'); + +QUnit.test("test manufacture from bom", function(assert) { + assert.expect(2); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make("Stock Entry", [ + {purpose:"Manufacture"}, + {from_bom:1}, + {bom_no:"BOM-_Test Item - Non Whole UOM-001"}, + {fg_completed_qty:2} + ]); + }, + () => cur_frm.save(), + () => frappe.click_button("Update Rate and Availability"), + () => { + assert.ok(cur_frm.doc.items[1] === 0.75, " Finished Item Qty correct"); + assert.ok(cur_frm.doc.items[2] === 0.25, " Process Loss Item Qty correct"); + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); + From cdf253aeb4b248846c2979ed41f2f239ab75c804 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Fri, 25 Jun 2021 14:21:12 +0530 Subject: [PATCH 06/98] fix: add more validations, remove source wh req for pl item --- erpnext/manufacturing/doctype/bom/bom.py | 29 ++++++++++--------- .../stock/doctype/stock_entry/stock_entry.py | 2 +- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index b90d54dea5..8f01edd0e2 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -695,25 +695,28 @@ class BOM(WebsiteGenerator): def validate_scrap_items(self): for item in self.scrap_items: if item.item_code == self.item and not item.is_process_loss: - frappe.throw(_('Scrap/Loss Item:') + f' {frappe.bold(item.item_code)} ' +\ - _('should have') + ' ' + frappe.bold(_('Is Process Loss')) + ' ' + ('checked.')) + frappe.throw(_('Scrap/Loss Item:') + f' {frappe.bold(item.item_code)} ' + + _('should have') + ' ' + frappe.bold(_('Is Process Loss')) + ' ' + ('checked')) elif item.item_code != self.item and item.is_process_loss: - frappe.throw(_('Scrap/Loss Item:') + f' {frappe.bold(item.item_code)} ' +\ - _('should not have') + ' ' + frappe.bold(_('Is Process Loss')) + ' ' + ('checked.')) + frappe.throw(_('Scrap/Loss Item:') + f' {frappe.bold(item.item_code)} ' + + _('should not have') + ' ' + frappe.bold(_('Is Process Loss')) + ' ' + ('checked')) - stock_uom = item.stock_uom - must_be_whole_number = frappe.get_value("UOM", stock_uom, "must_be_whole_number") + must_be_whole_number = frappe.get_value("UOM", item.stock_uom, "must_be_whole_number") if item.is_process_loss and must_be_whole_number: - frappe.throw(_('Item:') + f' {frappe.bold(item.item_code)} ' +\ - _('with Stock UOM:') + f' {frappe.bold(stock_uom)} '+\ - _('cannot be a Scrap/Loss Item.')) + frappe.throw(_('Item:') + f' {frappe.bold(item.item_code)} ' + + _('with Stock UOM:') + f' {frappe.bold(item.stock_uom)} ' + + _('cannot be a Scrap/Loss Item')) if item.is_process_loss and (item.stock_qty >= self.quantity): - frappe.throw(_('Scrap/Loss Item:') + f' {item.item_code} ' +\ - _('should have') +' '+frappe.bold(_('Qty')) +\ - ' ' + _('less than finished goods') + ' ' +\ - frappe.bold(_('Quantity.'))) + frappe.throw(_('Scrap/Loss Item:') + f' {item.item_code} ' + + _('should have') +' '+frappe.bold(_('Qty')) + ' ' + + _('less than finished goods') + ' ' + frappe.bold(_('Quantity'))) + if item.is_process_loss and (item.rate > 0): + frappe.throw(_('Scrap/Loss Item:') + f' {item.item_code} ' + + _('should have') + ' ' + frappe.bold(_('Rate')) + + ' ' + _('set to 0 because') + ' ' + + frappe.bold(_('Is Process Loss')) + ' ' + _('is checked')) def get_bom_item_rate(args, bom_doc): if bom_doc.rm_cost_as_per == 'Valuation Rate': diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 4f724ec637..21c0e75393 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -334,7 +334,7 @@ class StockEntry(StockController): if self.purpose == "Manufacture": if validate_for_manufacture: - if d.is_finished_item or d.is_scrap_item: + if d.is_finished_item or d.is_scrap_item or d.is_process_loss: d.s_warehouse = None if not d.t_warehouse: frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) From 55acb2e8434747658ea6cfd319840a55e11c0380 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Fri, 25 Jun 2021 14:46:08 +0530 Subject: [PATCH 07/98] fix: sider --- erpnext/manufacturing/doctype/bom/test_bom.py | 2 +- .../stock_entry/tests/test_stock_entry_for_manufacture.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index e61bb52592..fe7a8f151b 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -413,7 +413,7 @@ def create_process_loss_bom_items(): item_list = [ ("_Test Item - Non Whole UOM", "Kg"), ("_Test Item - Whole UOM", "Unit"), - ( "_Test PL BOM Item", "Unit") + ("_Test PL BOM Item", "Unit") ] return [create_process_loss_bom_item(it) for it in item_list] diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_manufacture.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_manufacture.js index d74f31672d..285ae4f59e 100644 --- a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_manufacture.js +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_manufacture.js @@ -6,10 +6,10 @@ QUnit.test("test manufacture from bom", function(assert) { frappe.run_serially([ () => { return frappe.tests.make("Stock Entry", [ - {purpose:"Manufacture"}, - {from_bom:1}, - {bom_no:"BOM-_Test Item - Non Whole UOM-001"}, - {fg_completed_qty:2} + { purpose: "Manufacture" }, + { from_bom: 1 }, + { bom_no: "BOM-_Test Item - Non Whole UOM-001" }, + { fg_completed_qty: 2 } ]); }, () => cur_frm.save(), From 47a4a3d88898fc13800d90ce9e9b7f62f0b7e272 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Fri, 25 Jun 2021 15:14:55 +0530 Subject: [PATCH 08/98] refactor: polyfill ?? --- erpnext/manufacturing/doctype/bom/bom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index dd437dd555..7e755d424c 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -686,7 +686,7 @@ function trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code) { (data) => { const row = locals[cdt][cdn]; row.stock_qty = (frm.doc.quantity * data.percent) / 100; - row.qty = row.stock_qty / (row.conversion_factor ?? 1); + row.qty = row.stock_qty / (row.conversion_factor || 1); refresh_field("scrap_items"); }, __("Set Process Loss Item Quantity"), From 23ef51a9819eda14c89b963b1e64a4555b2f95a7 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Fri, 25 Jun 2021 15:48:28 +0530 Subject: [PATCH 09/98] fix: sider --- erpnext/manufacturing/doctype/bom/bom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 7e755d424c..7de7e17abc 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -667,7 +667,7 @@ frappe.ui.form.on("BOM Scrap Item", { const { item_code } = locals[cdt][cdn]; if (item_code === frm.doc.item) { locals[cdt][cdn].is_process_loss = 1; - trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code) + trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code); } }, }); From ad73d3fbfb7894c74a259a489da5cba72d95f15f Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 9 Aug 2021 17:37:17 +0530 Subject: [PATCH 10/98] refactor: validation error message formatting --- erpnext/manufacturing/doctype/bom/bom.py | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 8f01edd0e2..8d9b10ddc1 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -694,29 +694,29 @@ class BOM(WebsiteGenerator): def validate_scrap_items(self): for item in self.scrap_items: + msg = "" if item.item_code == self.item and not item.is_process_loss: - frappe.throw(_('Scrap/Loss Item:') + f' {frappe.bold(item.item_code)} ' + - _('should have') + ' ' + frappe.bold(_('Is Process Loss')) + ' ' + ('checked')) + msg = _('Scrap/Loss Item: {0} should have Is Process Loss checked') \ + .format(frappe.bold(item.item_code)) elif item.item_code != self.item and item.is_process_loss: - frappe.throw(_('Scrap/Loss Item:') + f' {frappe.bold(item.item_code)} ' + - _('should not have') + ' ' + frappe.bold(_('Is Process Loss')) + ' ' + ('checked')) + msg = _('Scrap/Loss Item: {0} should not have Is Process Loss checked') \ + .format(frappe.bold(item.item_code)) must_be_whole_number = frappe.get_value("UOM", item.stock_uom, "must_be_whole_number") if item.is_process_loss and must_be_whole_number: - frappe.throw(_('Item:') + f' {frappe.bold(item.item_code)} ' + - _('with Stock UOM:') + f' {frappe.bold(item.stock_uom)} ' + - _('cannot be a Scrap/Loss Item')) + msg = _("Item: {0} with Stock UOM: {1} cannot be a Scrap/Loss Item") \ + .format(frappe.bold(item.item_code), frappe.bold(item.stock_uom)) if item.is_process_loss and (item.stock_qty >= self.quantity): - frappe.throw(_('Scrap/Loss Item:') + f' {item.item_code} ' + - _('should have') +' '+frappe.bold(_('Qty')) + ' ' + - _('less than finished goods') + ' ' + frappe.bold(_('Quantity'))) + msg = _("Scrap/Loss Item: {0} should have Qty less than finished goods Quantity") \ + .format(frappe.bold(item.item_code)) if item.is_process_loss and (item.rate > 0): - frappe.throw(_('Scrap/Loss Item:') + f' {item.item_code} ' + - _('should have') + ' ' + frappe.bold(_('Rate')) + - ' ' + _('set to 0 because') + ' ' + - frappe.bold(_('Is Process Loss')) + ' ' + _('is checked')) + msg = _("Scrap/Loss Item: {0} should have Rate set to 0 because Is Process Loss is checked") \ + .format(frappe.bold(item.item_code)) + + if msg: + frappe.throw(msg, title=_("Note")) def get_bom_item_rate(args, bom_doc): if bom_doc.rm_cost_as_per == 'Valuation Rate': From 2670adc0c00a1ea48e19285156f65a0c23b4a42c Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 10 Aug 2021 12:23:19 +0530 Subject: [PATCH 11/98] test: check manufacture completion qty in se and wo --- erpnext/manufacturing/doctype/bom/test_bom.py | 2 +- .../doctype/work_order/test_work_order.py | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index fe7a8f151b..6e17f2a831 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -283,7 +283,7 @@ class TestBOM(unittest.TestCase): def test_bom_with_process_loss_item(self): fg_item_non_whole, fg_item_whole, bom_item = create_process_loss_bom_items() - if frappe.db.exists("BOM", f"BOM-{fg_item_non_whole.item_code}-001") is None: + if not frappe.db.exists("BOM", f"BOM-{fg_item_non_whole.item_code}-001"): bom_doc = create_bom_with_process_loss_item( fg_item_non_whole, bom_item, 0.25, 0, 1 ) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index bf1ccb7159..7f943d9cbb 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -690,6 +690,64 @@ class TestWorkOrder(unittest.TestCase): self.assertRaises(frappe.ValidationError, make_stock_entry, wo.name, 'Material Transfer for Manufacture') + def test_wo_completion_with_pl_bom(self): + from erpnext.manufacturing.doctype.bom.test_bom import create_process_loss_bom_items + from erpnext.manufacturing.doctype.bom.test_bom import create_bom_with_process_loss_item + + qty = fg_qty = 4 + scrap_qty = 0.25 # bom item qty = 1, consider as 25% of FG + source_warehouse = "Stores - _TC" + wip_warehouse = "_Test Warehouse - _TC" + fg_item_non_whole, _, bom_item = create_process_loss_bom_items() + + test_stock_entry.make_stock_entry(item_code=bom_item.item_code, + target=source_warehouse, qty=4, basic_rate=100) + + bom_no = f"BOM-{fg_item_non_whole.item_code}-001" + if not frappe.db.exists("BOM", bom_no): + bom_doc = create_bom_with_process_loss_item( + fg_item_non_whole, bom_item, scrap_qty=scrap_qty, + scrap_rate=0, fg_qty=fg_qty, is_process_loss=1 + ) + bom_doc.submit() + + wo = make_wo_order_test_record( + production_item=fg_item_non_whole.item_code, + bom_no=bom_no, + wip_warehouse=wip_warehouse, + qty=qty, + skip_transfer=1, + stock_uom=fg_item_non_whole.stock_uom, + ) + + se = frappe.get_doc( + make_stock_entry(wo.name, "Material Transfer for Manufacture", 4) + ) + se.get("items")[0].s_warehouse = "Stores - _TC" + se.insert() + se.submit() + + se = frappe.get_doc( + make_stock_entry(wo.name, "Manufacture", 4) + ) + se.insert() + se.submit() + + # Testing stock entry values + items = se.get("items") + self.assertEqual(len(items), 3, "There should be 3 items including process loss.") + + source_item, fg_item, pl_item = items + + total_pl_qty = scrap_qty * fg_qty + actual_fg_qty = fg_qty - total_pl_qty + + self.assertEqual(pl_item.qty, total_pl_qty) + self.assertEqual(fg_item.qty, actual_fg_qty) + + # Testing Work Order values + self.assertEqual( frappe.db.get_value("Work Order", wo.name, "produced_qty"), actual_fg_qty) + def get_scrap_item_details(bom_no): scrap_items = {} for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item` From cc177f34158d3ffabdbfbb2ace500fa17a7b7c9d Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 10 Aug 2021 14:42:39 +0530 Subject: [PATCH 12/98] fix: wo tests, sider, account for pl in se validation --- .../doctype/work_order/test_work_order.py | 16 ++++++++-------- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 7f943d9cbb..d6a20df0c8 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -694,7 +694,7 @@ class TestWorkOrder(unittest.TestCase): from erpnext.manufacturing.doctype.bom.test_bom import create_process_loss_bom_items from erpnext.manufacturing.doctype.bom.test_bom import create_bom_with_process_loss_item - qty = fg_qty = 4 + qty = 4 scrap_qty = 0.25 # bom item qty = 1, consider as 25% of FG source_warehouse = "Stores - _TC" wip_warehouse = "_Test Warehouse - _TC" @@ -707,7 +707,7 @@ class TestWorkOrder(unittest.TestCase): if not frappe.db.exists("BOM", bom_no): bom_doc = create_bom_with_process_loss_item( fg_item_non_whole, bom_item, scrap_qty=scrap_qty, - scrap_rate=0, fg_qty=fg_qty, is_process_loss=1 + scrap_rate=0, fg_qty=1, is_process_loss=1 ) bom_doc.submit() @@ -721,32 +721,32 @@ class TestWorkOrder(unittest.TestCase): ) se = frappe.get_doc( - make_stock_entry(wo.name, "Material Transfer for Manufacture", 4) + make_stock_entry(wo.name, "Material Transfer for Manufacture", qty) ) se.get("items")[0].s_warehouse = "Stores - _TC" se.insert() se.submit() se = frappe.get_doc( - make_stock_entry(wo.name, "Manufacture", 4) + make_stock_entry(wo.name, "Manufacture", qty) ) se.insert() se.submit() # Testing stock entry values items = se.get("items") - self.assertEqual(len(items), 3, "There should be 3 items including process loss.") + self.assertEqual(len(items), 4, "There should be 3 items including process loss.") source_item, fg_item, pl_item = items - total_pl_qty = scrap_qty * fg_qty - actual_fg_qty = fg_qty - total_pl_qty + total_pl_qty = qty * scrap_qty + actual_fg_qty = qty - total_pl_qty self.assertEqual(pl_item.qty, total_pl_qty) self.assertEqual(fg_item.qty, actual_fg_qty) # Testing Work Order values - self.assertEqual( frappe.db.get_value("Work Order", wo.name, "produced_qty"), actual_fg_qty) + self.assertEqual(frappe.db.get_value("Work Order", wo.name, "produced_qty"), actual_fg_qty) def get_scrap_item_details(bom_no): scrap_items = {} diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 21c0e75393..8ea1275283 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -271,7 +271,7 @@ class StockEntry(StockController): item_wise_qty = {} if self.purpose == "Manufacture" and self.work_order: for d in self.items: - if d.is_finished_item: + if d.is_finished_item or d.is_process_loss: item_wise_qty.setdefault(d.item_code, []).append(d.qty) for item_code, qty_list in iteritems(item_wise_qty): From dddc29fdc1a6bb634ed12cc9ea6b364784ebb7c6 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 19 Aug 2021 17:55:24 +0530 Subject: [PATCH 13/98] feat: initialize party link for customer & suppliers --- .../accounts/doctype/party_link/__init__.py | 0 .../accounts/doctype/party_link/party_link.js | 41 ++++++++++ .../doctype/party_link/party_link.json | 78 +++++++++++++++++++ .../accounts/doctype/party_link/party_link.py | 12 +++ .../doctype/party_link/test_party_link.py | 8 ++ 5 files changed, 139 insertions(+) create mode 100644 erpnext/accounts/doctype/party_link/__init__.py create mode 100644 erpnext/accounts/doctype/party_link/party_link.js create mode 100644 erpnext/accounts/doctype/party_link/party_link.json create mode 100644 erpnext/accounts/doctype/party_link/party_link.py create mode 100644 erpnext/accounts/doctype/party_link/test_party_link.py diff --git a/erpnext/accounts/doctype/party_link/__init__.py b/erpnext/accounts/doctype/party_link/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/party_link/party_link.js b/erpnext/accounts/doctype/party_link/party_link.js new file mode 100644 index 0000000000..966a5f5d30 --- /dev/null +++ b/erpnext/accounts/doctype/party_link/party_link.js @@ -0,0 +1,41 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Party Link', { + refresh: function(frm) { + frm.set_query('party_type', 'links', () => { + return { + filters: { + name: ['in', party_types] + } + }; + }); + + frm.set_query('primary_role', () => { + return { + filters: { + name: ['in', ['Customer', 'Supplier']] + } + }; + }); + + frm.set_query('secondary_role', () => { + let party_types = Object.keys(frappe.boot.party_account_types) + .filter(p => p != frm.doc.primary_role); + return { + filters: { + name: ['in', party_types] + } + }; + }); + }, + + primary_role(frm) { + frm.set_value('primary_party', ''); + frm.set_value('secondary_role', ''); + }, + + secondary_role(frm) { + frm.set_value('secondary_party', ''); + } +}); diff --git a/erpnext/accounts/doctype/party_link/party_link.json b/erpnext/accounts/doctype/party_link/party_link.json new file mode 100644 index 0000000000..2053dc0f00 --- /dev/null +++ b/erpnext/accounts/doctype/party_link/party_link.json @@ -0,0 +1,78 @@ +{ + "actions": [], + "autoname": "ACC-PT-LNK-.###.", + "creation": "2021-08-18 21:06:53.027695", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "primary_role", + "secondary_role", + "column_break_2", + "primary_party", + "secondary_party" + ], + "fields": [ + { + "fieldname": "primary_role", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Primary Role", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "depends_on": "primary_role", + "fieldname": "secondary_role", + "fieldtype": "Link", + "label": "Secondary Role", + "mandatory_depends_on": "primary_role", + "options": "DocType" + }, + { + "depends_on": "primary_role", + "fieldname": "primary_party", + "fieldtype": "Dynamic Link", + "label": "Primary Party", + "mandatory_depends_on": "primary_role", + "options": "primary_role" + }, + { + "depends_on": "secondary_role", + "fieldname": "secondary_party", + "fieldtype": "Dynamic Link", + "label": "Secondary Party", + "mandatory_depends_on": "secondary_role", + "options": "secondary_role" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-08-19 17:53:43.456752", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Party Link", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "primary_party", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/party_link/party_link.py b/erpnext/accounts/doctype/party_link/party_link.py new file mode 100644 index 0000000000..80f86e75a0 --- /dev/null +++ b/erpnext/accounts/doctype/party_link/party_link.py @@ -0,0 +1,12 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document + +class PartyLink(Document): + def validate(self): + if self.primary_role not in ['Customer', 'Supplier']: + frappe.throw(_("Allowed primary roles are 'Customer' and 'Supplier'. Please select one of these roles only."), + title=_("Invalid Primary Role")) diff --git a/erpnext/accounts/doctype/party_link/test_party_link.py b/erpnext/accounts/doctype/party_link/test_party_link.py new file mode 100644 index 0000000000..a3ea3959ba --- /dev/null +++ b/erpnext/accounts/doctype/party_link/test_party_link.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + +class TestPartyLink(unittest.TestCase): + pass From cad08bc428346f1f09c0f6b8eaf59a9bf95ca3df Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 19 Aug 2021 17:56:04 +0530 Subject: [PATCH 14/98] feat: toggle to enable common party accounting --- .../doctype/accounts_settings/accounts_settings.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index a246ae51a4..7d0ecfbafd 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -19,6 +19,7 @@ "delete_linked_ledger_entries", "book_asset_depreciation_entry_automatically", "unlink_advance_payment_on_cancelation_of_order", + "enable_common_party_accounting", "post_change_gl_entries", "enable_discount_accounting", "tax_settings_section", @@ -268,6 +269,12 @@ "fieldname": "enable_discount_accounting", "fieldtype": "Check", "label": "Enable Discount Accounting" + }, + { + "default": "0", + "fieldname": "enable_common_party_accounting", + "fieldtype": "Check", + "label": "Enable Common Party Accounting" } ], "icon": "icon-cog", @@ -275,7 +282,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-08-09 13:08:04.335416", + "modified": "2021-08-19 11:17:38.788054", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", From 977b09b6ba35eeb0295c8397bfb982824355c699 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 19 Aug 2021 17:57:30 +0530 Subject: [PATCH 15/98] feat: auto create advance entry on invoice submission --- .../purchase_invoice/purchase_invoice.py | 2 + .../doctype/sales_invoice/sales_invoice.py | 2 + erpnext/controllers/accounts_controller.py | 62 ++++++++++++++++++- 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index a16795e628..e2f02f37ee 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -415,6 +415,8 @@ class PurchaseInvoice(BuyingController): self.update_project() update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) + self.process_common_party_accounting() + def make_gl_entries(self, gl_entries=None, from_repost=False): if not gl_entries: gl_entries = self.get_gl_entries() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 5fa622856b..f29f7bef22 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -253,6 +253,8 @@ class SalesInvoice(SellingController): if "Healthcare" in active_domains: manage_invoice_submit_cancel(self, "on_submit") + self.process_common_party_accounting() + def validate_pos_return(self): if self.is_pos and self.is_return: diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 4c243d0cc4..1b23289062 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -14,7 +14,7 @@ from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_a from erpnext.utilities.transaction_base import TransactionBase from erpnext.buying.utils import update_last_purchase_rate from erpnext.controllers.sales_and_purchase_return import validate_return -from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled +from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled, get_party_account from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_transaction, apply_pricing_rule_for_free_items, get_applied_pricing_rules) from erpnext.exceptions import InvalidCurrency @@ -1367,6 +1367,66 @@ class AccountsController(TransactionBase): return False + def process_common_party_accounting(self): + is_invoice = self.doctype in ['Sales Invoice', 'Purchase Invoice'] + if not is_invoice: + return + + if frappe.db.get_single_value('Accounts Settings', 'enable_common_party_accounting'): + party_link = self.get_common_party_link() + if party_link and self.outstanding_amount: + self.create_advance_and_reconcile(party_link) + + def get_common_party_link(self): + party_type, party = self.get_party() + party_link = frappe.db.exists('Party Link', {'secondary_role': party_type, 'secondary_party': party}) + if party_link: + return frappe.db.get_value('Party Link', party_link, ['primary_role', 'primary_party'], as_dict=True) + + def create_advance_and_reconcile(self, party_link): + secondary_party_type, secondary_party = self.get_party() + primary_party_type, primary_party = party_link.primary_role, party_link.primary_party + + primary_account = get_party_account(primary_party_type, primary_party, self.company) + secondary_account = get_party_account(secondary_party_type, secondary_party, self.company) + + jv = frappe.new_doc('Journal Entry') + jv.voucher_type = 'Journal Entry' + jv.naming_series = 'ACC-JV-.YYYY.-' + jv.posting_date = self.posting_date + jv.company = self.company + jv.remark = 'Adjustment for {} {}'.format(self.doctype, self.name) + + reconcilation_entry = frappe._dict() + advance_entry = frappe._dict() + cost_center = erpnext.get_default_cost_center(self.company) + + reconcilation_entry.account = secondary_account + reconcilation_entry.party_type = secondary_party_type + reconcilation_entry.party = secondary_party + reconcilation_entry.reference_type = self.doctype + reconcilation_entry.reference_name = self.name + reconcilation_entry.cost_center = cost_center + + advance_entry.account = primary_account + advance_entry.party_type = primary_party_type + advance_entry.party = primary_party + advance_entry.cost_center = cost_center + advance_entry.is_advance = 'Yes' + + if self.doctype == 'Sales Invoice': + reconcilation_entry.credit_in_account_currency = self.outstanding_amount + advance_entry.debit_in_account_currency = self.outstanding_amount + else: + advance_entry.credit_in_account_currency = self.outstanding_amount + reconcilation_entry.debit_in_account_currency = self.outstanding_amount + + jv.append('accounts', reconcilation_entry) + jv.append('accounts', advance_entry) + + jv.save() + jv.submit() + @frappe.whitelist() def get_tax_rate(account_head): return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True) From 71e72541846f6294bd0ccee3f76fb2448d8cfa9b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 19 Aug 2021 17:57:54 +0530 Subject: [PATCH 16/98] test: creation of advance entry on invoice submission --- .../sales_invoice/test_sales_invoice.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 4d1e0c3e06..8bb25dc663 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2174,6 +2174,50 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) self.assertTrue(schedule.journal_entry) + def test_sales_invoice_against_supplier(self): + from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import make_customer + from erpnext.buying.doctype.supplier.test_supplier import create_supplier + + # create a customer + customer = make_customer(customer="_Test Common Supplier") + # create a supplier + supplier = create_supplier(supplier_name="_Test Common Supplier").name + + # create a party link between customer & supplier + # set primary role as supplier + party_link = frappe.new_doc("Party Link") + party_link.primary_role = "Supplier" + party_link.primary_party = supplier + party_link.secondary_role = "Customer" + party_link.secondary_party = customer + party_link.save() + + # enable common party accounting + frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1) + + # create a sales invoice + si = create_sales_invoice(customer=customer) + + # check outstanding of sales invoice + si.reload() + self.assertEqual(si.status, 'Paid') + self.assertEqual(flt(si.outstanding_amount), 0.0) + + # check creation of journal entry + jv = frappe.get_all('Journal Entry Account', { + 'account': si.debit_to, + 'party_type': 'Customer', + 'party': si.customer, + 'reference_type': si.doctype, + 'reference_name': si.name + }, pluck='credit_in_account_currency') + + self.assertTrue(jv) + self.assertEqual(jv[0], si.grand_total) + + party_link.delete() + frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 0) + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = 'INV-2020-.#####' From 8e79c48db87da4bb180f4e67333d73501e88b9dd Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 19 Aug 2021 18:10:02 +0530 Subject: [PATCH 17/98] fix: remove unwanted filter query --- erpnext/accounts/doctype/party_link/party_link.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/accounts/doctype/party_link/party_link.js b/erpnext/accounts/doctype/party_link/party_link.js index 966a5f5d30..6da9291d64 100644 --- a/erpnext/accounts/doctype/party_link/party_link.js +++ b/erpnext/accounts/doctype/party_link/party_link.js @@ -3,14 +3,6 @@ frappe.ui.form.on('Party Link', { refresh: function(frm) { - frm.set_query('party_type', 'links', () => { - return { - filters: { - name: ['in', party_types] - } - }; - }); - frm.set_query('primary_role', () => { return { filters: { From 4a529f8039ccf594628494e15f18744b785cba68 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 24 Aug 2021 09:35:59 +0530 Subject: [PATCH 18/98] feat: Prospect --- erpnext/crm/doctype/lead/lead.js | 2 +- erpnext/crm/doctype/lead/lead.py | 10 ++ erpnext/crm/doctype/lead/lead_list.js | 36 ++++ .../crm/doctype/opportunity/opportunity.js | 10 +- .../crm/doctype/opportunity/opportunity.json | 6 +- erpnext/crm/doctype/prospect/__init__.py | 0 erpnext/crm/doctype/prospect/prospect.js | 39 +++++ erpnext/crm/doctype/prospect/prospect.json | 165 ++++++++++++++++++ erpnext/crm/doctype/prospect/prospect.py | 45 +++++ erpnext/crm/doctype/prospect/test_prospect.py | 8 + erpnext/crm/doctype/prospect_lead/__init__.py | 0 .../doctype/prospect_lead/prospect_lead.json | 71 ++++++++ .../doctype/prospect_lead/prospect_lead.py | 8 + .../selling/doctype/customer/customer.json | 12 +- 14 files changed, 396 insertions(+), 16 deletions(-) create mode 100644 erpnext/crm/doctype/lead/lead_list.js create mode 100644 erpnext/crm/doctype/prospect/__init__.py create mode 100644 erpnext/crm/doctype/prospect/prospect.js create mode 100644 erpnext/crm/doctype/prospect/prospect.json create mode 100644 erpnext/crm/doctype/prospect/prospect.py create mode 100644 erpnext/crm/doctype/prospect/test_prospect.py create mode 100644 erpnext/crm/doctype/prospect_lead/__init__.py create mode 100644 erpnext/crm/doctype/prospect_lead/prospect_lead.json create mode 100644 erpnext/crm/doctype/prospect_lead/prospect_lead.py diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 75af937990..9c21099088 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -51,7 +51,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller make_customer () { frappe.model.open_mapped_doc({ - method: "erpnext.crm.doctype.lead.lead.make_customer", + method: "erpnext.crm.doctype.lead.lead.make_prospect", frm: cur_frm }) } diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 7f028cb316..ecf6a41e6c 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -190,6 +190,16 @@ class Lead(SellingController): return contact +@frappe.whitelist() +def make_prospect(source_name, target_doc=None, leads=None): + print("``````````````````````````") + print(source_name) + print("``````````````````````````") + print(target_doc) + print("``````````````````````````") + print(leads) + print("``````````````````````````") + @frappe.whitelist() def make_customer(source_name, target_doc=None): return _make_customer(source_name, target_doc) diff --git a/erpnext/crm/doctype/lead/lead_list.js b/erpnext/crm/doctype/lead/lead_list.js new file mode 100644 index 0000000000..5cbbf76480 --- /dev/null +++ b/erpnext/crm/doctype/lead/lead_list.js @@ -0,0 +1,36 @@ +frappe.listview_settings['Lead'] = { + onload: function(listview) { + if (frappe.boot.user.can_create.includes("Prospect")) { + listview.page.add_action_item(__("Create Prospect"), function() { + let leads = listview.get_checked_items(); + console.log(listview.get_checked_items()); + frappe.model.open_mapped_doc({ + method: "erpnext.crm.doctype.lead.lead.make_prospect", + frm: cur_frm, + leads: leads + }) + + // listview.call_for_selected_items(method, {"status": "Open"}); + // let prospect_lead = [] + // leads.forEach(lead => { + // prospect_lead.push({ + // "lead": lead.name + // }); + // }); + // console.log("check"); + // console.log(prospect_lead); + // frappe.new_doc("Prospect", { + // "company_name": leads[0].company_name, + // "industry": leads[0].industry, + // "market_segment": leads[0].market_segment, + // "territory": leads[0].territory, + // "no_of_employees": leads[0].no_of_employees, + // "fax": leads[0].fax, + // "website": leads[0].website, + // "prospect_owner": leads[0].lead_owner, + // "prospect_lead": prospect_lead + // }); + }); + } + } +}; diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index e9a7a95fc7..bcfae11a05 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -10,15 +10,7 @@ frappe.ui.form.on("Opportunity", { frm.custom_make_buttons = { 'Quotation': 'Quotation', 'Supplier Quotation': 'Supplier Quotation' - }, - - frm.set_query("opportunity_from", function() { - return{ - "filters": { - "name": ["in", ["Customer", "Lead"]], - } - } - }); + }; if (frm.doc.opportunity_from && frm.doc.party_name){ frm.trigger('set_contact_link'); diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index 4ba4140244..e4b0e47309 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -78,13 +78,13 @@ }, { "fieldname": "opportunity_from", - "fieldtype": "Link", + "fieldtype": "Select", "in_list_view": 1, "in_standard_filter": 1, "label": "Opportunity From", "oldfieldname": "enquiry_from", "oldfieldtype": "Select", - "options": "DocType", + "options": "\nLead\nProspect\nCustomer", "print_hide": 1, "reqd": 1 }, @@ -430,7 +430,7 @@ "icon": "fa fa-info-sign", "idx": 195, "links": [], - "modified": "2021-06-04 10:11:22.831139", + "modified": "2021-08-23 14:43:09.484227", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", diff --git a/erpnext/crm/doctype/prospect/__init__.py b/erpnext/crm/doctype/prospect/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/crm/doctype/prospect/prospect.js b/erpnext/crm/doctype/prospect/prospect.js new file mode 100644 index 0000000000..793afccf3d --- /dev/null +++ b/erpnext/crm/doctype/prospect/prospect.js @@ -0,0 +1,39 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Prospect', { + refresh () { + if (!cur_frm.is_new() && frappe.boot.user.can_create.includes("Customer")) { + cur_frm.add_custom_button(__("Customer"), function() { + frappe.model.open_mapped_doc({ + method: "erpnext.crm.doctype.prospect.prospect.make_customer", + frm: cur_frm + }) + }, __("Create")); + } + if (!cur_frm.is_new() && frappe.boot.user.can_create.includes("Opportunity")) { + cur_frm.add_custom_button(__("Opportunity"), function() { + frappe.model.open_mapped_doc({ + method: "erpnext.crm.doctype.prospect.prospect.make_opportunity", + frm: cur_frm + }) + }, __("Create")); + } + }, + + make_customer () { + console.log("Make Customer"); + frappe.model.open_mapped_doc({ + method: "erpnext.crm.doctype.prospect.prospect.make_customer", + frm: cur_frm + }) + }, + + make_opportunity () { + console.log("Make Opportunity"); + // frappe.model.open_mapped_doc({ + // method: "erpnext.crm.doctype.lead.lead.make_opportunity", + // frm: cur_frm + // }) + } +}); diff --git a/erpnext/crm/doctype/prospect/prospect.json b/erpnext/crm/doctype/prospect/prospect.json new file mode 100644 index 0000000000..6f476a6b65 --- /dev/null +++ b/erpnext/crm/doctype/prospect/prospect.json @@ -0,0 +1,165 @@ +{ + "actions": [], + "autoname": "field:company_name", + "creation": "2021-08-19 00:21:06.995448", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company_name", + "industry", + "market_segment", + "customer_group", + "territory", + "column_break_6", + "no_of_employees", + "currency", + "annual_revenue", + "fax", + "website", + "prospect_owner", + "leads_section", + "prospect_lead", + "addresses_and_contacts_section", + "address_html", + "column_break_17", + "contact_html", + "notes_section", + "notes" + ], + "fields": [ + { + "fieldname": "company_name", + "fieldtype": "Data", + "label": "Company Name", + "unique": 1 + }, + { + "fieldname": "industry", + "fieldtype": "Link", + "label": "Industry", + "options": "Industry Type" + }, + { + "fieldname": "market_segment", + "fieldtype": "Link", + "label": "Market Segment", + "options": "Market Segment" + }, + { + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "options": "Customer Group" + }, + { + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "options": "Territory" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "no_of_employees", + "fieldtype": "Int", + "label": "No. of Employees" + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "options": "Currency" + }, + { + "fieldname": "annual_revenue", + "fieldtype": "Currency", + "label": "Annual Revenue", + "options": "currency" + }, + { + "fieldname": "fax", + "fieldtype": "Data", + "label": "Fax", + "options": "Phone" + }, + { + "fieldname": "website", + "fieldtype": "Data", + "label": "Website", + "options": "URL" + }, + { + "fieldname": "prospect_owner", + "fieldtype": "Link", + "label": "Prospect Owner", + "options": "User" + }, + { + "fieldname": "leads_section", + "fieldtype": "Section Break", + "label": "Leads" + }, + { + "fieldname": "prospect_lead", + "fieldtype": "Table", + "options": "Prospect Lead" + }, + { + "depends_on": "eval: !doc.__islocal", + "fieldname": "addresses_and_contacts_section", + "fieldtype": "Section Break", + "label": "Addresses and Contacts" + }, + { + "fieldname": "address_html", + "fieldtype": "HTML", + "label": "Address HTML" + }, + { + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, + { + "fieldname": "contact_html", + "fieldtype": "HTML", + "label": "Contact HTML" + }, + { + "collapsible": 1, + "fieldname": "notes_section", + "fieldtype": "Section Break", + "label": "Notes" + }, + { + "fieldname": "notes", + "fieldtype": "Text Editor" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-08-19 00:29:00.767038", + "modified_by": "Administrator", + "module": "CRM", + "name": "Prospect", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/crm/doctype/prospect/prospect.py b/erpnext/crm/doctype/prospect/prospect.py new file mode 100644 index 0000000000..a0175cd310 --- /dev/null +++ b/erpnext/crm/doctype/prospect/prospect.py @@ -0,0 +1,45 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc + +class Prospect(Document): + pass + +@frappe.whitelist() +def make_customer(source_name, target_doc=None): + def set_missing_values(source, target): + target.customer_type = "Company" + target.company_name = source.name + target.customer_group = source.customer_group or frappe.db.get_default("Customer Group") + + doclist = get_mapped_doc("Prospect", source_name, + {"Prospect": { + "doctype": "Customer", + "field_map": { + "company_name": "customer_name", + "currency": "default_currency", + "fax": "fax" + } + }}, target_doc, set_missing_values, ignore_permissions=False) + + return doclist + +@frappe.whitelist() +def make_opportunity(source_name, target_doc=None): + def set_missing_values(source, target): + target.opportunity_from = "Prospect" + target.customer_name = source.company_name + target.customer_group = source.customer_group or frappe.db.get_default("Customer Group") + + doclist = get_mapped_doc("Prospect", source_name, + {"Prospect": { + "doctype": "Opportunity", + "field_map": { + "name": "party_name", + } + }}, target_doc, set_missing_values, ignore_permissions=False) + + return doclist diff --git a/erpnext/crm/doctype/prospect/test_prospect.py b/erpnext/crm/doctype/prospect/test_prospect.py new file mode 100644 index 0000000000..f266a50593 --- /dev/null +++ b/erpnext/crm/doctype/prospect/test_prospect.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + +class TestProspect(unittest.TestCase): + pass diff --git a/erpnext/crm/doctype/prospect_lead/__init__.py b/erpnext/crm/doctype/prospect_lead/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/crm/doctype/prospect_lead/prospect_lead.json b/erpnext/crm/doctype/prospect_lead/prospect_lead.json new file mode 100644 index 0000000000..1797712a55 --- /dev/null +++ b/erpnext/crm/doctype/prospect_lead/prospect_lead.json @@ -0,0 +1,71 @@ +{ + "actions": [], + "creation": "2021-08-19 00:14:14.857421", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "lead", + "lead_name", + "status", + "email", + "mobile_no" + ], + "fields": [ + { + "fieldname": "lead", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Lead", + "options": "Lead", + "reqd": 1 + }, + { + "fetch_from": "lead.lead_name", + "fetch_if_empty": 1, + "fieldname": "lead_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Lead Name" + }, + { + "fetch_from": "lead.status", + "fetch_if_empty": 1, + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Lead\nOpen\nReplied\nOpportunity\nQuotation\nLost Quotation\nInterested\nConverted\nDo Not Contact" + }, + { + "fetch_from": "lead.email_id", + "fetch_if_empty": 1, + "fieldname": "email", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Email", + "options": "Email" + }, + { + "fetch_from": "lead.mobile_no", + "fetch_if_empty": 1, + "fieldname": "mobile_no", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Mobile No", + "options": "Phone" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-20 01:58:39.387874", + "modified_by": "Administrator", + "module": "CRM", + "name": "Prospect Lead", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/crm/doctype/prospect_lead/prospect_lead.py b/erpnext/crm/doctype/prospect_lead/prospect_lead.py new file mode 100644 index 0000000000..2be5a5f39a --- /dev/null +++ b/erpnext/crm/doctype/prospect_lead/prospect_lead.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class ProspectLead(Document): + pass diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index cd94ee101a..608b703862 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -20,6 +20,7 @@ "tax_withholding_category", "default_bank_account", "lead_name", + "prospect", "image", "column_break0", "account_manager", @@ -212,8 +213,7 @@ "fieldtype": "Link", "ignore_user_permissions": 1, "label": "Represents Company", - "options": "Company", - "unique": 1 + "options": "Company" }, { "depends_on": "represents_company", @@ -493,6 +493,12 @@ "fieldtype": "Link", "label": "Tax Withholding Category", "options": "Tax Withholding Category" + }, + { + "fieldname": "prospect", + "fieldtype": "Link", + "label": "Prospect", + "options": "Prospect" } ], "icon": "fa fa-user", @@ -500,7 +506,7 @@ "image_field": "image", "index_web_pages_for_search": 1, "links": [], - "modified": "2021-01-28 12:54:57.258959", + "modified": "2021-08-23 14:40:15.214350", "modified_by": "Administrator", "module": "Selling", "name": "Customer", From 7fb08173b54b67a67aac03102a6327cc3c9b475b Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 17 Aug 2021 16:03:04 +0530 Subject: [PATCH 19/98] fix: reword error messages, fix test values --- erpnext/manufacturing/doctype/bom/bom.py | 10 +++++----- .../doctype/work_order/test_work_order.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 8d9b10ddc1..8d105789d0 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -696,23 +696,23 @@ class BOM(WebsiteGenerator): for item in self.scrap_items: msg = "" if item.item_code == self.item and not item.is_process_loss: - msg = _('Scrap/Loss Item: {0} should have Is Process Loss checked') \ + msg = _('Scrap/Loss Item: {0} should have Is Process Loss checked as it is the same as the item to be manufactured or repacked.') \ .format(frappe.bold(item.item_code)) elif item.item_code != self.item and item.is_process_loss: - msg = _('Scrap/Loss Item: {0} should not have Is Process Loss checked') \ + msg = _('Scrap/Loss Item: {0} should not have Is Process Loss checked as it is different from the item to be manufactured or repacked') \ .format(frappe.bold(item.item_code)) must_be_whole_number = frappe.get_value("UOM", item.stock_uom, "must_be_whole_number") if item.is_process_loss and must_be_whole_number: - msg = _("Item: {0} with Stock UOM: {1} cannot be a Scrap/Loss Item") \ + msg = _("Item: {0} with Stock UOM: {1} cannot be a Scrap/Loss Item as {1} is a whole UOM.") \ .format(frappe.bold(item.item_code), frappe.bold(item.stock_uom)) if item.is_process_loss and (item.stock_qty >= self.quantity): - msg = _("Scrap/Loss Item: {0} should have Qty less than finished goods Quantity") \ + msg = _("Scrap/Loss Item: {0} should have Qty less than finished goods Quantity.") \ .format(frappe.bold(item.item_code)) if item.is_process_loss and (item.rate > 0): - msg = _("Scrap/Loss Item: {0} should have Rate set to 0 because Is Process Loss is checked") \ + msg = _("Scrap/Loss Item: {0} should have Rate set to 0 because Is Process Loss is checked.") \ .format(frappe.bold(item.item_code)) if msg: diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index d6a20df0c8..0569092f88 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -735,7 +735,7 @@ class TestWorkOrder(unittest.TestCase): # Testing stock entry values items = se.get("items") - self.assertEqual(len(items), 4, "There should be 3 items including process loss.") + self.assertEqual(len(items), 3, "There should be 3 items including process loss.") source_item, fg_item, pl_item = items @@ -746,7 +746,7 @@ class TestWorkOrder(unittest.TestCase): self.assertEqual(fg_item.qty, actual_fg_qty) # Testing Work Order values - self.assertEqual(frappe.db.get_value("Work Order", wo.name, "produced_qty"), actual_fg_qty) + self.assertEqual(frappe.db.get_value("Work Order", wo.name, "produced_qty"), qty) def get_scrap_item_details(bom_no): scrap_items = {} From 797b19a14a4b201a2b03dca6a17978793225a22a Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 24 Aug 2021 15:50:15 +0530 Subject: [PATCH 20/98] feat: creation of prospect from lead --- erpnext/crm/doctype/lead/lead.js | 8 ++++ erpnext/crm/doctype/lead/lead.py | 19 +++++---- erpnext/crm/doctype/lead/lead_dashboard.py | 2 +- erpnext/crm/doctype/lead/lead_list.js | 46 +++++++++------------- erpnext/crm/doctype/prospect/prospect.py | 21 +++++++++- 5 files changed, 57 insertions(+), 39 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 9c21099088..f6eb3f4836 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -39,6 +39,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller this.frm.add_custom_button(__("Customer"), this.make_customer, __("Create")); this.frm.add_custom_button(__("Opportunity"), this.make_opportunity, __("Create")); this.frm.add_custom_button(__("Quotation"), this.make_quotation, __("Create")); + this.frm.add_custom_button(__("Prospect"), this.make_prospect, __("Create")); } if (!this.frm.is_new()) { @@ -70,6 +71,13 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller }) } + make_prospect () { + frappe.model.open_mapped_doc({ + method: "erpnext.crm.doctype.lead.lead.make_prospect", + frm: cur_frm + }) + } + company_name () { if (!this.frm.doc.lead_name) { this.frm.set_value("lead_name", this.frm.doc.company_name); diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index ecf6a41e6c..f76595e978 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -190,16 +190,6 @@ class Lead(SellingController): return contact -@frappe.whitelist() -def make_prospect(source_name, target_doc=None, leads=None): - print("``````````````````````````") - print(source_name) - print("``````````````````````````") - print(target_doc) - print("``````````````````````````") - print(leads) - print("``````````````````````````") - @frappe.whitelist() def make_customer(source_name, target_doc=None): return _make_customer(source_name, target_doc) @@ -272,6 +262,15 @@ def make_quotation(source_name, target_doc=None): return target_doc +@frappe.whitelist() +def make_prospect(source_name, target_doc=None): + target_doc = get_mapped_doc("Lead", source_name, + {"Lead": { + "doctype": "Prospect", + }}, target_doc) + + return target_doc + def _set_missing_values(source, target): address = frappe.get_all('Dynamic Link', { 'link_doctype': source.doctype, diff --git a/erpnext/crm/doctype/lead/lead_dashboard.py b/erpnext/crm/doctype/lead/lead_dashboard.py index 69d8ca7092..6038811a66 100644 --- a/erpnext/crm/doctype/lead/lead_dashboard.py +++ b/erpnext/crm/doctype/lead/lead_dashboard.py @@ -13,7 +13,7 @@ def get_data(): }, 'transactions': [ { - 'items': ['Opportunity', 'Quotation'] + 'items': ['Opportunity', 'Quotation', 'Prospect'] }, ] } \ No newline at end of file diff --git a/erpnext/crm/doctype/lead/lead_list.js b/erpnext/crm/doctype/lead/lead_list.js index 5cbbf76480..75208fa64b 100644 --- a/erpnext/crm/doctype/lead/lead_list.js +++ b/erpnext/crm/doctype/lead/lead_list.js @@ -2,34 +2,26 @@ frappe.listview_settings['Lead'] = { onload: function(listview) { if (frappe.boot.user.can_create.includes("Prospect")) { listview.page.add_action_item(__("Create Prospect"), function() { - let leads = listview.get_checked_items(); - console.log(listview.get_checked_items()); - frappe.model.open_mapped_doc({ - method: "erpnext.crm.doctype.lead.lead.make_prospect", - frm: cur_frm, - leads: leads - }) + frappe.model.with_doctype("Prospect", function() { + let prospect = frappe.model.get_new_doc("Prospect"); + let leads = listview.get_checked_items(); + frappe.db.get_value("Lead", leads[0].name, ["company_name", "no_of_employees", "industry", "market_segment", "territory", "fax", "website", "lead_owner"], (r) => { + prospect.company_name = r.company_name; + prospect.no_of_employees = r.no_of_employees; + prospect.industry = r.industry; + prospect.market_segment = r.market_segment; + prospect.territory = r.territory; + prospect.fax = r.fax; + prospect.website = r.website; + prospect.prospect_owner = r.lead_owner; - // listview.call_for_selected_items(method, {"status": "Open"}); - // let prospect_lead = [] - // leads.forEach(lead => { - // prospect_lead.push({ - // "lead": lead.name - // }); - // }); - // console.log("check"); - // console.log(prospect_lead); - // frappe.new_doc("Prospect", { - // "company_name": leads[0].company_name, - // "industry": leads[0].industry, - // "market_segment": leads[0].market_segment, - // "territory": leads[0].territory, - // "no_of_employees": leads[0].no_of_employees, - // "fax": leads[0].fax, - // "website": leads[0].website, - // "prospect_owner": leads[0].lead_owner, - // "prospect_lead": prospect_lead - // }); + leads.forEach(function(lead) { + let lead_prospect_row = frappe.model.add_child(prospect, 'prospect_lead'); + lead_prospect_row.lead = lead.name; + }); + frappe.set_route("Form", "Prospect", prospect.name); + }); + }); }); } } diff --git a/erpnext/crm/doctype/prospect/prospect.py b/erpnext/crm/doctype/prospect/prospect.py index a0175cd310..80e2459a90 100644 --- a/erpnext/crm/doctype/prospect/prospect.py +++ b/erpnext/crm/doctype/prospect/prospect.py @@ -6,7 +6,26 @@ from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc class Prospect(Document): - pass + def validate(self): + self.link_with_lead_contact_and_address() + + def link_with_lead_contact_and_address(self): + for row in self.prospect_lead: + links = frappe.get_all('Dynamic Link', filters={'link_doctype': 'Lead', 'link_name': row.lead}, fields=['parent', 'parenttype']) + for link in links: + linked_doc = frappe.get_doc(link['parenttype'], link['parent']) + exists = False + + for d in linked_doc.get('links'): + if d.link_doctype == self.doctype and d.link_name == self.name: + exists = True + + if not exists: + linked_doc.append('links', { + 'link_doctype': self.doctype, + 'link_name': self.name + }) + linked_doc.save(ignore_permissions=True) @frappe.whitelist() def make_customer(source_name, target_doc=None): From 3610882077d5108a0fbc402254026c8f60685359 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 24 Aug 2021 15:51:57 +0530 Subject: [PATCH 21/98] fix: reverting local chnages --- erpnext/crm/doctype/lead/lead.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index f6eb3f4836..0bf2ab9615 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -52,7 +52,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller make_customer () { frappe.model.open_mapped_doc({ - method: "erpnext.crm.doctype.lead.lead.make_prospect", + method: "erpnext.crm.doctype.lead.lead.make_customer", frm: cur_frm }) } From b58853e89d9a7a149972cac7de32e5577ac0c16b Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 24 Aug 2021 16:11:29 +0530 Subject: [PATCH 22/98] feat: add procss_loss_qty field in work order --- .../doctype/work_order/test_work_order.py | 9 +++++- .../doctype/work_order/work_order.json | 30 +++++++++++-------- .../doctype/work_order/work_order.py | 16 ++++++++++ 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 0569092f88..a00520f6a1 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -746,7 +746,14 @@ class TestWorkOrder(unittest.TestCase): self.assertEqual(fg_item.qty, actual_fg_qty) # Testing Work Order values - self.assertEqual(frappe.db.get_value("Work Order", wo.name, "produced_qty"), qty) + self.assertEqual( + frappe.db.get_value("Work Order", wo.name, "produced_qty"), + qty + ) + self.assertEqual( + frappe.db.get_value("Work Order", wo.name, "process_loss_qty"), + actual_fg_qty + ) def get_scrap_item_details(bom_no): scrap_items = {} diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 3b56854aaf..913fc85af6 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -19,6 +19,7 @@ "qty", "material_transferred_for_manufacturing", "produced_qty", + "process_loss_qty", "sales_order", "project", "serial_no_and_batch_for_finished_good_section", @@ -64,16 +65,12 @@ "description", "stock_uom", "column_break2", - "references_section", "material_request", "material_request_item", "sales_order_item", - "column_break_61", "production_plan", "production_plan_item", "production_plan_sub_assembly_item", - "parent_work_order", - "bom_level", "product_bundle_item", "amended_from" ], @@ -553,20 +550,29 @@ "read_only": 1 }, { - "fieldname": "production_plan_sub_assembly_item", - "fieldtype": "Data", - "label": "Production Plan Sub-assembly Item", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - } + "fieldname": "production_plan_sub_assembly_item", + "fieldtype": "Data", + "label": "Production Plan Sub-assembly Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "eval: doc.process_loss_qty", + "fieldname": "process_loss_qty", + "fieldtype": "Float", + "label": "Process Loss Qty", + "no_copy": 1, + "non_negative": 1, + "read_only": 1 + } ], "icon": "fa fa-cogs", "idx": 1, "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-06-28 16:19:14.902699", + "modified": "2021-08-24 15:14:03.844937", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 282b5d0afe..c37a1c9e5a 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -214,6 +214,7 @@ class WorkOrder(Document): self.meta.get_label(fieldname), qty, completed_qty, self.name), StockOverProductionError) self.db_set(fieldname, qty) + self.set_process_loss_qty() from erpnext.selling.doctype.sales_order.sales_order import update_produced_qty_in_so_item @@ -223,6 +224,21 @@ class WorkOrder(Document): if self.production_plan: self.update_production_plan_status() + def set_process_loss_qty(self): + process_loss_qty = flt(frappe.db.sql(""" + SELECT sum(qty) FROM `tabStock Entry Detail` + WHERE + is_process_loss=1 + AND parent IN ( + SELECT name FROM `tabStock Entry` + WHERE + work_order=%s + AND docstatus=1 + ) + """, (self.name, ))[0][0]) + if process_loss_qty is not None: + self.db_set('process_loss_qty', process_loss_qty) + def update_production_plan_status(self): production_plan = frappe.get_doc('Production Plan', self.production_plan) produced_qty = 0 From 0c6212189ebe7ff08e7d30b05ff904fb11fc4002 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 24 Aug 2021 16:49:53 +0530 Subject: [PATCH 23/98] feat: link lead communication to prospect --- erpnext/crm/doctype/lead/lead.py | 10 ++++++++++ erpnext/crm/doctype/prospect/prospect.py | 22 +++++++++++++++++++++- erpnext/hooks.py | 3 +++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index f76595e978..693fc6a160 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -362,3 +362,13 @@ def daily_open_lead(): leads = frappe.get_all("Lead", filters = [["contact_date", "Between", [nowdate(), nowdate()]]]) for lead in leads: frappe.db.set_value("Lead", lead.name, "status", "Open") + +def add_prospect_link_in_communication(communication, method): + if communication.get('reference_doctype') == "Lead": + links = frappe.get_all('Prospect Lead', filters={'lead': communication.get('reference_name')}, fields=['parent', 'parenttype']) + + for link in links: + communication.append('timeline_links', { + 'link_doctype': link['parenttype'], + 'link_name': link['parent'] + }) diff --git a/erpnext/crm/doctype/prospect/prospect.py b/erpnext/crm/doctype/prospect/prospect.py index 80e2459a90..bd278dca35 100644 --- a/erpnext/crm/doctype/prospect/prospect.py +++ b/erpnext/crm/doctype/prospect/prospect.py @@ -6,9 +6,12 @@ from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc class Prospect(Document): - def validate(self): + def after_save(self): self.link_with_lead_contact_and_address() + def on_trash(self): + self.unlink_dynamic_links() + def link_with_lead_contact_and_address(self): for row in self.prospect_lead: links = frappe.get_all('Dynamic Link', filters={'link_doctype': 'Lead', 'link_name': row.lead}, fields=['parent', 'parenttype']) @@ -27,6 +30,23 @@ class Prospect(Document): }) linked_doc.save(ignore_permissions=True) + def unlink_dynamic_links(self): + links = frappe.get_all('Dynamic Link', filters={'link_doctype': self.doctype, 'link_name': self.name}, fields=['parent', 'parenttype']) + + for link in links: + linked_doc = frappe.get_doc(link['parenttype'], link['parent']) + + if len(linked_doc.get('links')) == 1: + linked_doc.delete(ignore_permissions=True) + else: + to_remove = None + for d in linked_doc.get('links'): + if d.link_doctype == self.doctype and d.link_name == self.name: + to_remove = d + if to_remove: + linked_doc.remove(to_remove) + linked_doc.save(ignore_permissions=True) + @frappe.whitelist() def make_customer(source_name, target_doc=None): def set_missing_values(source, target): diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 8f7c7db208..8069a1566f 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -249,6 +249,9 @@ doc_events = { "on_update": [ "erpnext.support.doctype.service_level_agreement.service_level_agreement.update_hold_time", "erpnext.support.doctype.issue.issue.set_first_response_time" + ], + "after_insert": [ + "erpnext.crm.doctype.lead.lead.add_prospect_link_in_communication" ] }, ("Sales Taxes and Charges Template", 'Price List'): { From 8f73d587f92473419301fa6b88b2c1562e5ff31a Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 24 Aug 2021 18:23:49 +0530 Subject: [PATCH 24/98] feat: process loss report, fix set pl query condition --- .../doctype/work_order/work_order.py | 1 + .../report/process_loss_report/__init__.py | 0 .../process_loss_report.js | 37 +++++ .../process_loss_report.json | 29 ++++ .../process_loss_report.py | 132 ++++++++++++++++++ 5 files changed, 199 insertions(+) create mode 100644 erpnext/stock/report/process_loss_report/__init__.py create mode 100644 erpnext/stock/report/process_loss_report/process_loss_report.js create mode 100644 erpnext/stock/report/process_loss_report/process_loss_report.json create mode 100644 erpnext/stock/report/process_loss_report/process_loss_report.py diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index c37a1c9e5a..1cdbc5f0e1 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -233,6 +233,7 @@ class WorkOrder(Document): SELECT name FROM `tabStock Entry` WHERE work_order=%s + AND purpose='Manufacture' AND docstatus=1 ) """, (self.name, ))[0][0]) diff --git a/erpnext/stock/report/process_loss_report/__init__.py b/erpnext/stock/report/process_loss_report/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.js b/erpnext/stock/report/process_loss_report/process_loss_report.js new file mode 100644 index 0000000000..078b9e11ce --- /dev/null +++ b/erpnext/stock/report/process_loss_report/process_loss_report.js @@ -0,0 +1,37 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Process Loss Report"] = { + filters: [ + { + label: __("Company"), + fieldname: "company", + fieldtype: "Link", + options: "Company", + mandatory: true, + default: frappe.defaults.get_user_default("Company"), + }, + { + label: __("Item"), + fieldname: "item", + fieldtype: "Link", + options: "Item", + mandatory: false, + }, + { + label: __("From Date"), + fieldname: "from_date", + fieldtype: "Date", + mandatory: true, + default: frappe.datetime.year_start(), + }, + { + label: __("To Date"), + fieldname: "to_date", + fieldtype: "Date", + mandatory: true, + default: frappe.datetime.get_today(), + }, + ] +}; diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.json b/erpnext/stock/report/process_loss_report/process_loss_report.json new file mode 100644 index 0000000000..afe4aff7f1 --- /dev/null +++ b/erpnext/stock/report/process_loss_report/process_loss_report.json @@ -0,0 +1,29 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-08-24 16:38:15.233395", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-08-24 16:38:15.233395", + "modified_by": "Administrator", + "module": "Stock", + "name": "Process Loss Report", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Work Order", + "report_name": "Process Loss Report", + "report_type": "Script Report", + "roles": [ + { + "role": "Manufacturing User" + }, + { + "role": "Stock User" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.py b/erpnext/stock/report/process_loss_report/process_loss_report.py new file mode 100644 index 0000000000..be0f0151d4 --- /dev/null +++ b/erpnext/stock/report/process_loss_report/process_loss_report.py @@ -0,0 +1,132 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from typing import Dict, List, Tuple + +Filters = frappe._dict +Row = frappe._dict +Data = List[Row] +Columns = List[Dict[str, str]] +QueryArgs = Dict[str, str] + +def execute(filters: Filters) -> Tuple[Columns, Data]: + columns = get_columns() + data = get_data(filters) + return columns, data + +def get_data(filters: Filters) -> Data: + query_args = get_query_args(filters) + data = run_query(query_args) + update_data_with_total_pl_value(data) + return data + +def get_columns() -> Columns: + return [ + { + 'label': 'Work Order', + 'fieldname': 'name', + 'fieldtype': 'Link', + 'options': 'Work Order', + 'width': '200' + }, + { + 'label': 'Item', + 'fieldname': 'production_item', + 'fieldtype': 'Link', + 'options': 'Item', + 'width': '100' + }, + { + 'label': 'Status', + 'fieldname': 'status', + 'fieldtype': 'Data', + 'width': '100' + }, + { + 'label': 'Qty To Manufacture', + 'fieldname': 'qty', + 'fieldtype': 'Float', + 'width': '150' + }, + { + 'label': 'Manufactured Qty', + 'fieldname': 'produced_qty', + 'fieldtype': 'Float', + 'width': '150' + }, + { + 'label': 'Process Loss Qty', + 'fieldname': 'process_loss_qty', + 'fieldtype': 'Float', + 'width': '150' + }, + { + 'label': 'Actual Manufactured Qty', + 'fieldname': 'actual_produced_qty', + 'fieldtype': 'Float', + 'width': '150' + }, + { + 'label': 'Total FG Value', + 'fieldname': 'total_fg_value', + 'fieldtype': 'Float', + 'width': '150' + }, + { + 'label': 'Total Raw Material Value', + 'fieldname': 'total_rm_value', + 'fieldtype': 'Float', + 'width': '150' + }, + { + 'label': 'Total Process Loss Value', + 'fieldname': 'total_pl_value', + 'fieldtype': 'Float', + 'width': '150' + }, + ] + +def get_query_args(filters: Filters) -> QueryArgs: + query_args = {} + query_args.update(filters) + query_args.update( + get_filter_conditions(filters) + ) + return query_args + +def run_query(query_args: QueryArgs) -> Data: + return frappe.db.sql(""" + SELECT + wo.name, wo.status, wo.production_item, wo.qty, + wo.produced_qty, wo.process_loss_qty, + (wo.produced_qty - wo.process_loss_qty) as actual_produced_qty, + sum(se.total_incoming_value) as total_fg_value, + sum(se.total_outgoing_value) as total_rm_value + FROM + `tabWork Order` wo INNER JOIN `tabStock Entry` se + ON wo.name=se.work_order + WHERE + process_loss_qty > 0 + AND wo.company = %(company)s + AND se.docstatus = 1 + AND se.posting_date BETWEEN %(from_date)s AND %(to_date)s + %(item_filter)s + GROUP BY + se.work_order + """, query_args, as_dict=1) + +def update_data_with_total_pl_value(data: Data) -> None: + for row in data: + value_per_unit_fg = row['total_fg_value'] / row['actual_produced_qty'] + row['total_pl_value'] = row['process_loss_qty'] * value_per_unit_fg + +def get_filter_conditions(filters: Filters) -> QueryArgs: + filter_conditions = dict(item_filter="") + if "item" in filters: + production_item = filters.get("item") + filter_conditions.update( + {"item_filter": f"wo.production_item='{production_item}'"} + ) + return filter_conditions + From c3ce3f918dbe3d871244750de05aa0a59afcfce3 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 24 Aug 2021 20:15:19 +0530 Subject: [PATCH 25/98] fix: remove spurious function 'toggle_operations' --- erpnext/manufacturing/doctype/bom/bom.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 7de7e17abc..5afda7028a 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -659,7 +659,6 @@ frappe.ui.form.on("BOM", "with_operations", function(frm) { if(!cint(frm.doc.with_operations)) { frm.set_value("operations", []); } - toggle_operations(frm); }); frappe.ui.form.on("BOM Scrap Item", { From 95a2565d86f76858f1e436a871f50862e9874a61 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 24 Aug 2021 20:18:53 +0530 Subject: [PATCH 26/98] fix: correct value in test --- erpnext/manufacturing/doctype/work_order/test_work_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index a00520f6a1..3a334a530c 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -752,7 +752,7 @@ class TestWorkOrder(unittest.TestCase): ) self.assertEqual( frappe.db.get_value("Work Order", wo.name, "process_loss_qty"), - actual_fg_qty + total_pl_qty ) def get_scrap_item_details(bom_no): From dfcac64b442b60fd329586f01b60b262d1be40de Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 25 Aug 2021 16:24:46 +0530 Subject: [PATCH 27/98] fix: review chnages --- erpnext/crm/doctype/lead/lead.js | 51 +++++++++++++++++-- erpnext/crm/doctype/lead/lead.py | 35 +++++++------ .../crm/doctype/opportunity/opportunity.js | 29 +++++++++-- .../crm/doctype/opportunity/opportunity.json | 6 +-- .../crm/doctype/opportunity/opportunity.py | 20 ++++++++ erpnext/crm/doctype/prospect/prospect.js | 21 +++----- erpnext/crm/doctype/prospect/prospect.py | 13 ++++- .../doctype/prospect_lead/prospect_lead.json | 22 ++++---- erpnext/hooks.py | 3 -- 9 files changed, 140 insertions(+), 60 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 0bf2ab9615..7f5f1a4cb4 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -40,6 +40,9 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller this.frm.add_custom_button(__("Opportunity"), this.make_opportunity, __("Create")); this.frm.add_custom_button(__("Quotation"), this.make_quotation, __("Create")); this.frm.add_custom_button(__("Prospect"), this.make_prospect, __("Create")); + this.frm.add_custom_button(__('Add to Prospect'), function() { + cur_frm.trigger('add_lead_to_prospect') + }, __('Action')); } if (!this.frm.is_new()) { @@ -50,6 +53,34 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller } } + add_lead_to_prospect () { + frappe.prompt([ + { + fieldname: 'prospect', + label: __('Prospect'), + fieldtype: 'Link', + options: 'Prospect', + reqd: 1 + } + ], + function(data) { + frappe.call({ + method: 'erpnext.crm.doctype.lead.lead.add_lead_to_prospect', + args: { + 'lead': cur_frm.doc.name, + 'prospect': data.prospect + }, + callback: function(r) { + if (!r.exc) { + cur_frm.reload_doc(); + } + }, + freeze: true, + freeze_message: __('...Adding Lead to Prospect') + }) + }, __('Add Lead to Prospect'), __('Add')); + } + make_customer () { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.lead.lead.make_customer", @@ -72,10 +103,22 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller } make_prospect () { - frappe.model.open_mapped_doc({ - method: "erpnext.crm.doctype.lead.lead.make_prospect", - frm: cur_frm - }) + frappe.model.with_doctype("Prospect", function() { + let prospect = frappe.model.get_new_doc("Prospect"); + prospect.company_name = cur_frm.doc.company_name; + prospect.no_of_employees = cur_frm.doc.no_of_employees; + prospect.industry = cur_frm.doc.industry; + prospect.market_segment = cur_frm.doc.market_segment; + prospect.territory = cur_frm.doc.territory; + prospect.fax = cur_frm.doc.fax; + prospect.website = cur_frm.doc.website; + prospect.prospect_owner = cur_frm.doc.lead_owner; + + let lead_prospect_row = frappe.model.add_child(prospect, 'prospect_lead'); + lead_prospect_row.lead = cur_frm.doc.name; + + frappe.set_route("Form", "Prospect", prospect.name); + }); } company_name () { diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 693fc6a160..c6115ee438 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -62,6 +62,7 @@ class Lead(SellingController): def on_update(self): self.add_calendar_event() + self.update_prospects() def before_insert(self): self.contact_doc = self.create_contact() @@ -88,6 +89,12 @@ class Lead(SellingController): "description": ('Contact ' + cstr(self.lead_name)) + (self.contact_by and ('. By : ' + cstr(self.contact_by)) or '') }, force) + def update_prospects(self): + prospects = frappe.get_all('Prospect Lead', filters={'lead': self.name}, fields=['parent']) + for row in prospects: + prospect = frappe.get_doc('Prospect', row.parent) + prospect.save(ignore_permissions=True) + def check_email_id_is_unique(self): if self.email_id: # validate email is unique @@ -262,15 +269,6 @@ def make_quotation(source_name, target_doc=None): return target_doc -@frappe.whitelist() -def make_prospect(source_name, target_doc=None): - target_doc = get_mapped_doc("Lead", source_name, - {"Lead": { - "doctype": "Prospect", - }}, target_doc) - - return target_doc - def _set_missing_values(source, target): address = frappe.get_all('Dynamic Link', { 'link_doctype': source.doctype, @@ -363,12 +361,13 @@ def daily_open_lead(): for lead in leads: frappe.db.set_value("Lead", lead.name, "status", "Open") -def add_prospect_link_in_communication(communication, method): - if communication.get('reference_doctype') == "Lead": - links = frappe.get_all('Prospect Lead', filters={'lead': communication.get('reference_name')}, fields=['parent', 'parenttype']) - - for link in links: - communication.append('timeline_links', { - 'link_doctype': link['parenttype'], - 'link_name': link['parent'] - }) +@frappe.whitelist() +def add_lead_to_prospect(lead, prospect): + prospect = frappe.get_doc('Prospect', prospect) + prospect.append('prospect_lead', { + 'lead': lead + }) + prospect.save(ignore_permissions=True) + frappe.msgprint(_('Lead {0} has been added to prospect {1}.').format(frappe.bold(lead), frappe.bold(prospect.name)), + title=_('Lead Added'), indicator='green') + \ No newline at end of file diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index bcfae11a05..80588ee002 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -12,6 +12,14 @@ frappe.ui.form.on("Opportunity", { 'Supplier Quotation': 'Supplier Quotation' }; + frm.set_query("opportunity_from", function() { + return{ + "filters": { + "name": ["in", ["Customer", "Lead", "Prospect"]], + } + } + }); + if (frm.doc.opportunity_from && frm.doc.party_name){ frm.trigger('set_contact_link'); } @@ -87,10 +95,18 @@ frappe.ui.form.on("Opportunity", { }, __('Create')); } - frm.add_custom_button(__('Quotation'), - cur_frm.cscript.create_quotation, __('Create')); + if (frm.doc.opportunity_from != "Customer") { + frm.add_custom_button(__('Customer'), + function() { + frm.trigger("make_customer") + }, __('Create')); + } - } + frm.add_custom_button(__('Quotation'), + function() { + frm.trigger("create_quotation") + }, __('Create')); + } if(!frm.doc.__islocal && frm.perm[0].write && frm.doc.docstatus==0) { if(frm.doc.status==="Open") { @@ -187,6 +203,13 @@ erpnext.crm.Opportunity = class Opportunity extends frappe.ui.form.Controller { frm: cur_frm }) } + + make_customer() { + frappe.model.open_mapped_doc({ + method: "erpnext.crm.doctype.opportunity.opportunity.make_customer", + frm: cur_frm + }) + } }; extend_cscript(cur_frm.cscript, new erpnext.crm.Opportunity({frm: cur_frm})); diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index e4b0e47309..12a564a9cb 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -78,13 +78,13 @@ }, { "fieldname": "opportunity_from", - "fieldtype": "Select", + "fieldtype": "Link", "in_list_view": 1, "in_standard_filter": 1, "label": "Opportunity From", "oldfieldname": "enquiry_from", "oldfieldtype": "Select", - "options": "\nLead\nProspect\nCustomer", + "options": "DocType", "print_hide": 1, "reqd": 1 }, @@ -430,7 +430,7 @@ "icon": "fa fa-info-sign", "idx": 195, "links": [], - "modified": "2021-08-23 14:43:09.484227", + "modified": "2021-08-25 10:28:24.923543", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 23ad98a282..9e620312b9 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -265,6 +265,26 @@ def make_quotation(source_name, target_doc=None): return doclist +@frappe.whitelist() +def make_customer(source_name, target_doc=None): + def set_missing_values(source, target): + if source.opportunity_from == "Lead": + target.lead_name = source.party_name + if source.opportunity_from == "Prospect": + target.prospect = source.party_name + + doclist = get_mapped_doc("Opportunity", source_name, { + "Opportunity": { + "doctype": "Customer", + "field_map": { + "currency": "default_currency", + "customer_name": "customer_name" + } + } + }, target_doc, set_missing_values) + + return doclist + @frappe.whitelist() def make_request_for_quotation(source_name, target_doc=None): def update_item(obj, target, source_parent): diff --git a/erpnext/crm/doctype/prospect/prospect.js b/erpnext/crm/doctype/prospect/prospect.js index 793afccf3d..64256cf31f 100644 --- a/erpnext/crm/doctype/prospect/prospect.js +++ b/erpnext/crm/doctype/prospect/prospect.js @@ -19,21 +19,12 @@ frappe.ui.form.on('Prospect', { }) }, __("Create")); } - }, - make_customer () { - console.log("Make Customer"); - frappe.model.open_mapped_doc({ - method: "erpnext.crm.doctype.prospect.prospect.make_customer", - frm: cur_frm - }) - }, - - make_opportunity () { - console.log("Make Opportunity"); - // frappe.model.open_mapped_doc({ - // method: "erpnext.crm.doctype.lead.lead.make_opportunity", - // frm: cur_frm - // }) + if (!cur_frm.is_new()) { + frappe.contacts.render_address_and_contact(cur_frm); + cur_frm.trigger('render_contact_day_html'); + } else { + frappe.contacts.clear_address_and_contact(cur_frm); + } } }); diff --git a/erpnext/crm/doctype/prospect/prospect.py b/erpnext/crm/doctype/prospect/prospect.py index bd278dca35..dc165af825 100644 --- a/erpnext/crm/doctype/prospect/prospect.py +++ b/erpnext/crm/doctype/prospect/prospect.py @@ -6,12 +6,23 @@ from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc class Prospect(Document): - def after_save(self): + def validate(self): + self.update_lead_details() + + def on_update(self): self.link_with_lead_contact_and_address() def on_trash(self): self.unlink_dynamic_links() + def update_lead_details(self): + for row in self.get('prospect_lead'): + lead = frappe.get_value('Lead', row.lead, ['lead_name', 'status', 'email_id', 'mobile_no'], as_dict=True) + row.lead_name = lead.lead_name + row.status = lead.status + row.email = lead.email_id + row.mobile_no = lead.mobile_no + def link_with_lead_contact_and_address(self): for row in self.prospect_lead: links = frappe.get_all('Dynamic Link', filters={'link_doctype': 'Lead', 'link_name': row.lead}, fields=['parent', 'parenttype']) diff --git a/erpnext/crm/doctype/prospect_lead/prospect_lead.json b/erpnext/crm/doctype/prospect_lead/prospect_lead.json index 1797712a55..3c160d9e80 100644 --- a/erpnext/crm/doctype/prospect_lead/prospect_lead.json +++ b/erpnext/crm/doctype/prospect_lead/prospect_lead.json @@ -21,45 +21,41 @@ "reqd": 1 }, { - "fetch_from": "lead.lead_name", - "fetch_if_empty": 1, "fieldname": "lead_name", "fieldtype": "Data", "in_list_view": 1, - "label": "Lead Name" + "label": "Lead Name", + "read_only": 1 }, { - "fetch_from": "lead.status", - "fetch_if_empty": 1, "fieldname": "status", "fieldtype": "Select", "in_list_view": 1, "label": "Status", - "options": "Lead\nOpen\nReplied\nOpportunity\nQuotation\nLost Quotation\nInterested\nConverted\nDo Not Contact" + "options": "Lead\nOpen\nReplied\nOpportunity\nQuotation\nLost Quotation\nInterested\nConverted\nDo Not Contact", + "read_only": 1 }, { - "fetch_from": "lead.email_id", - "fetch_if_empty": 1, "fieldname": "email", "fieldtype": "Data", "in_list_view": 1, "label": "Email", - "options": "Email" + "options": "Email", + "read_only": 1 }, { - "fetch_from": "lead.mobile_no", - "fetch_if_empty": 1, "fieldname": "mobile_no", "fieldtype": "Data", "in_list_view": 1, "label": "Mobile No", - "options": "Phone" + "options": "Phone", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-08-20 01:58:39.387874", + "modified": "2021-08-25 12:58:24.638054", "modified_by": "Administrator", "module": "CRM", "name": "Prospect Lead", diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 8069a1566f..8f7c7db208 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -249,9 +249,6 @@ doc_events = { "on_update": [ "erpnext.support.doctype.service_level_agreement.service_level_agreement.update_hold_time", "erpnext.support.doctype.issue.issue.set_first_response_time" - ], - "after_insert": [ - "erpnext.crm.doctype.lead.lead.add_prospect_link_in_communication" ] }, ("Sales Taxes and Charges Template", 'Price List'): { From c644fbb1023029edd1a30003e14f3dfb5ef4e1fa Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 25 Aug 2021 16:35:34 +0530 Subject: [PATCH 28/98] fix: sider issues --- erpnext/crm/doctype/lead/lead.js | 6 ++--- erpnext/crm/doctype/prospect/prospect.js | 4 ++-- erpnext/crm/doctype/prospect/prospect.json | 27 +++++++++++++++------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 7f5f1a4cb4..dfb3b094dc 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -40,9 +40,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller this.frm.add_custom_button(__("Opportunity"), this.make_opportunity, __("Create")); this.frm.add_custom_button(__("Quotation"), this.make_quotation, __("Create")); this.frm.add_custom_button(__("Prospect"), this.make_prospect, __("Create")); - this.frm.add_custom_button(__('Add to Prospect'), function() { - cur_frm.trigger('add_lead_to_prospect') - }, __('Action')); + this.frm.add_custom_button(__('Add to Prospect'), this.add_lead_to_prospect, __('Action')); } if (!this.frm.is_new()) { @@ -77,7 +75,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller }, freeze: true, freeze_message: __('...Adding Lead to Prospect') - }) + }); }, __('Add Lead to Prospect'), __('Add')); } diff --git a/erpnext/crm/doctype/prospect/prospect.js b/erpnext/crm/doctype/prospect/prospect.js index 64256cf31f..814fc2b6db 100644 --- a/erpnext/crm/doctype/prospect/prospect.js +++ b/erpnext/crm/doctype/prospect/prospect.js @@ -8,7 +8,7 @@ frappe.ui.form.on('Prospect', { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.prospect.prospect.make_customer", frm: cur_frm - }) + }); }, __("Create")); } if (!cur_frm.is_new() && frappe.boot.user.can_create.includes("Opportunity")) { @@ -16,7 +16,7 @@ frappe.ui.form.on('Prospect', { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.prospect.prospect.make_opportunity", frm: cur_frm - }) + }); }, __("Create")); } diff --git a/erpnext/crm/doctype/prospect/prospect.json b/erpnext/crm/doctype/prospect/prospect.json index 6f476a6b65..2eb7cde02d 100644 --- a/erpnext/crm/doctype/prospect/prospect.json +++ b/erpnext/crm/doctype/prospect/prospect.json @@ -15,12 +15,14 @@ "no_of_employees", "currency", "annual_revenue", + "more_details_section", "fax", "website", + "column_break_13", "prospect_owner", "leads_section", "prospect_lead", - "addresses_and_contacts_section", + "address_and_contact_section", "address_html", "column_break_17", "contact_html", @@ -107,12 +109,6 @@ "fieldtype": "Table", "options": "Prospect Lead" }, - { - "depends_on": "eval: !doc.__islocal", - "fieldname": "addresses_and_contacts_section", - "fieldtype": "Section Break", - "label": "Addresses and Contacts" - }, { "fieldname": "address_html", "fieldtype": "HTML", @@ -136,11 +132,26 @@ { "fieldname": "notes", "fieldtype": "Text Editor" + }, + { + "fieldname": "more_details_section", + "fieldtype": "Section Break", + "label": "More Details" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval: !doc.__islocal", + "fieldname": "address_and_contact_section", + "fieldtype": "Section Break", + "label": "Address and Contact" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-08-19 00:29:00.767038", + "modified": "2021-08-25 12:35:00.759909", "modified_by": "Administrator", "module": "CRM", "name": "Prospect", From 5ad8afcc8d6f06c62685f3cf24155cca4e172bf4 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 25 Aug 2021 17:36:53 +0530 Subject: [PATCH 29/98] revert: creation of customer from opportunity --- .../crm/doctype/opportunity/opportunity.js | 14 ------------- .../crm/doctype/opportunity/opportunity.py | 20 ------------------- 2 files changed, 34 deletions(-) diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index 80588ee002..6ebb0dab57 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -95,13 +95,6 @@ frappe.ui.form.on("Opportunity", { }, __('Create')); } - if (frm.doc.opportunity_from != "Customer") { - frm.add_custom_button(__('Customer'), - function() { - frm.trigger("make_customer") - }, __('Create')); - } - frm.add_custom_button(__('Quotation'), function() { frm.trigger("create_quotation") @@ -203,13 +196,6 @@ erpnext.crm.Opportunity = class Opportunity extends frappe.ui.form.Controller { frm: cur_frm }) } - - make_customer() { - frappe.model.open_mapped_doc({ - method: "erpnext.crm.doctype.opportunity.opportunity.make_customer", - frm: cur_frm - }) - } }; extend_cscript(cur_frm.cscript, new erpnext.crm.Opportunity({frm: cur_frm})); diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 9e620312b9..23ad98a282 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -265,26 +265,6 @@ def make_quotation(source_name, target_doc=None): return doclist -@frappe.whitelist() -def make_customer(source_name, target_doc=None): - def set_missing_values(source, target): - if source.opportunity_from == "Lead": - target.lead_name = source.party_name - if source.opportunity_from == "Prospect": - target.prospect = source.party_name - - doclist = get_mapped_doc("Opportunity", source_name, { - "Opportunity": { - "doctype": "Customer", - "field_map": { - "currency": "default_currency", - "customer_name": "customer_name" - } - } - }, target_doc, set_missing_values) - - return doclist - @frappe.whitelist() def make_request_for_quotation(source_name, target_doc=None): def update_item(obj, target, source_parent): From c7e11c89ff6636ec9c8aa4a3c3ed1ba5f71e3206 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Wed, 25 Aug 2021 18:58:56 +0530 Subject: [PATCH 30/98] fix: get filters to work - reorder and rename columns - add work order filter --- .../process_loss_report.js | 7 ++++ .../process_loss_report.py | 40 +++++++++---------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.js b/erpnext/stock/report/process_loss_report/process_loss_report.js index 078b9e11ce..b0c2b94a25 100644 --- a/erpnext/stock/report/process_loss_report/process_loss_report.js +++ b/erpnext/stock/report/process_loss_report/process_loss_report.js @@ -17,6 +17,13 @@ frappe.query_reports["Process Loss Report"] = { fieldname: "item", fieldtype: "Link", options: "Item", + mandatory: false, + }, + { + label: __("Work Order"), + fieldname: "work_order", + fieldtype: "Link", + options: "Work Order", mandatory: false, }, { diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.py b/erpnext/stock/report/process_loss_report/process_loss_report.py index be0f0151d4..7494328ab4 100644 --- a/erpnext/stock/report/process_loss_report/process_loss_report.py +++ b/erpnext/stock/report/process_loss_report/process_loss_report.py @@ -43,12 +43,6 @@ def get_columns() -> Columns: 'fieldtype': 'Data', 'width': '100' }, - { - 'label': 'Qty To Manufacture', - 'fieldname': 'qty', - 'fieldtype': 'Float', - 'width': '150' - }, { 'label': 'Manufactured Qty', 'fieldname': 'produced_qty', @@ -56,7 +50,7 @@ def get_columns() -> Columns: 'width': '150' }, { - 'label': 'Process Loss Qty', + 'label': 'Loss Qty', 'fieldname': 'process_loss_qty', 'fieldtype': 'Float', 'width': '150' @@ -68,23 +62,23 @@ def get_columns() -> Columns: 'width': '150' }, { - 'label': 'Total FG Value', + 'label': 'Loss Value', + 'fieldname': 'total_pl_value', + 'fieldtype': 'Float', + 'width': '150' + }, + { + 'label': 'FG Value', 'fieldname': 'total_fg_value', 'fieldtype': 'Float', 'width': '150' }, { - 'label': 'Total Raw Material Value', + 'label': 'Raw Material Value', 'fieldname': 'total_rm_value', 'fieldtype': 'Float', 'width': '150' - }, - { - 'label': 'Total Process Loss Value', - 'fieldname': 'total_pl_value', - 'fieldtype': 'Float', - 'width': '150' - }, + } ] def get_query_args(filters: Filters) -> QueryArgs: @@ -111,10 +105,11 @@ def run_query(query_args: QueryArgs) -> Data: AND wo.company = %(company)s AND se.docstatus = 1 AND se.posting_date BETWEEN %(from_date)s AND %(to_date)s - %(item_filter)s + {item_filter} + {work_order_filter} GROUP BY se.work_order - """, query_args, as_dict=1) + """.format(**query_args), query_args, as_dict=1, debug=1) def update_data_with_total_pl_value(data: Data) -> None: for row in data: @@ -122,11 +117,16 @@ def update_data_with_total_pl_value(data: Data) -> None: row['total_pl_value'] = row['process_loss_qty'] * value_per_unit_fg def get_filter_conditions(filters: Filters) -> QueryArgs: - filter_conditions = dict(item_filter="") + filter_conditions = dict(item_filter="", work_order_filter="") if "item" in filters: production_item = filters.get("item") filter_conditions.update( - {"item_filter": f"wo.production_item='{production_item}'"} + {"item_filter": f"AND wo.production_item='{production_item}'"} + ) + if "work_order" in filters: + work_order_name = filters.get("work_order") + filter_conditions.update( + {"work_order_filter": f"AND wo.name='{work_order_name}'"} ) return filter_conditions From c101b42d4a086c1e328fcdd442d4af33bde1f9c0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 25 Aug 2021 20:08:28 +0530 Subject: [PATCH 31/98] feat: validate multiple links --- erpnext/accounts/doctype/party_link/party_link.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/accounts/doctype/party_link/party_link.py b/erpnext/accounts/doctype/party_link/party_link.py index 80f86e75a0..7d58506ce7 100644 --- a/erpnext/accounts/doctype/party_link/party_link.py +++ b/erpnext/accounts/doctype/party_link/party_link.py @@ -10,3 +10,17 @@ class PartyLink(Document): if self.primary_role not in ['Customer', 'Supplier']: frappe.throw(_("Allowed primary roles are 'Customer' and 'Supplier'. Please select one of these roles only."), title=_("Invalid Primary Role")) + + existing_party_link = frappe.get_all('Party Link', { + 'primary_party': self.secondary_party + }, pluck="primary_role") + if existing_party_link: + frappe.throw(_('{} {} is already linked with another {}') + .format(self.secondary_role, self.secondary_party, existing_party_link[0])) + + existing_party_link = frappe.get_all('Party Link', { + 'secondary_party': self.primary_party + }, pluck="primary_role") + if existing_party_link: + frappe.throw(_('{} {} is already linked with another {}') + .format(self.primary_role, self.primary_party, existing_party_link[0])) From a2f8d6c31acbfeac4cc3abfedbaf59375f6fe962 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 25 Aug 2021 20:10:19 +0530 Subject: [PATCH 32/98] fix: party link permissions --- .../doctype/party_link/party_link.json | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/party_link/party_link.json b/erpnext/accounts/doctype/party_link/party_link.json index 2053dc0f00..a1bb15f0d6 100644 --- a/erpnext/accounts/doctype/party_link/party_link.json +++ b/erpnext/accounts/doctype/party_link/party_link.json @@ -52,7 +52,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-08-19 17:53:43.456752", + "modified": "2021-08-25 20:08:56.761150", "modified_by": "Administrator", "module": "Accounts", "name": "Party Link", @@ -69,6 +69,30 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "write": 1 } ], "sort_field": "modified", From 7254368fc52dd9f828dfe3c3d9570ba3274f0ade Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 25 Aug 2021 20:15:23 +0530 Subject: [PATCH 33/98] perf: reduce number of queries to get party link --- erpnext/controllers/accounts_controller.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 55e6f0482c..798b994920 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1375,9 +1375,12 @@ class AccountsController(TransactionBase): def get_common_party_link(self): party_type, party = self.get_party() - party_link = frappe.db.exists('Party Link', {'secondary_role': party_type, 'secondary_party': party}) - if party_link: - return frappe.db.get_value('Party Link', party_link, ['primary_role', 'primary_party'], as_dict=True) + return frappe.db.get_value( + doctype='Party Link', + filters={'secondary_role': party_type, 'secondary_party': party}, + fieldname=['primary_role', 'primary_party'], + as_dict=True + ) def create_advance_and_reconcile(self, party_link): secondary_party_type, secondary_party = self.get_party() From c6c7a8b5cf182b9314aa726296a1e34767450939 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 25 Aug 2021 20:17:04 +0530 Subject: [PATCH 34/98] fix: cost center & naming series --- erpnext/controllers/accounts_controller.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 798b994920..f4af8932b6 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1391,26 +1391,24 @@ class AccountsController(TransactionBase): jv = frappe.new_doc('Journal Entry') jv.voucher_type = 'Journal Entry' - jv.naming_series = 'ACC-JV-.YYYY.-' jv.posting_date = self.posting_date jv.company = self.company jv.remark = 'Adjustment for {} {}'.format(self.doctype, self.name) reconcilation_entry = frappe._dict() advance_entry = frappe._dict() - cost_center = erpnext.get_default_cost_center(self.company) reconcilation_entry.account = secondary_account reconcilation_entry.party_type = secondary_party_type reconcilation_entry.party = secondary_party reconcilation_entry.reference_type = self.doctype reconcilation_entry.reference_name = self.name - reconcilation_entry.cost_center = cost_center + reconcilation_entry.cost_center = self.cost_center advance_entry.account = primary_account advance_entry.party_type = primary_party_type advance_entry.party = primary_party - advance_entry.cost_center = cost_center + advance_entry.cost_center = self.cost_center advance_entry.is_advance = 'Yes' if self.doctype == 'Sales Invoice': From be7a38b662b82aac11a6a085f02eeffecd10872f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 25 Aug 2021 21:35:32 +0530 Subject: [PATCH 35/98] fix: cost center in test_sales_invoice_against_supplier --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index b48dc924f7..83f0222026 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2185,7 +2185,7 @@ class TestSalesInvoice(unittest.TestCase): frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1) # create a sales invoice - si = create_sales_invoice(customer=customer) + si = create_sales_invoice(customer=customer, parent_cost_center="_Test Cost Center - _TC") # check outstanding of sales invoice si.reload() From 1a919773d795f607142992fc4a373d5ab9d2e725 Mon Sep 17 00:00:00 2001 From: Brian Pond Date: Wed, 25 Aug 2021 23:27:28 -0700 Subject: [PATCH 36/98] fix: Customers 'primary_address' not updated automatically (#26798) (#26799) * Fix for Issue #26798 This PR is a fix for GitHub Issue 26798: https://github.com/frappe/erpnext/issues/26798 TLDR: When an Address is updated, and that Address is a Customer's Primary Address, update the Read Only field `customer.primary_address` * Update address.py Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- erpnext/accounts/custom/address.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/custom/address.py b/erpnext/accounts/custom/address.py index c417a493c6..834227bb58 100644 --- a/erpnext/accounts/custom/address.py +++ b/erpnext/accounts/custom/address.py @@ -1,7 +1,7 @@ import frappe from frappe import _ from frappe.contacts.doctype.address.address import Address -from frappe.contacts.doctype.address.address import get_address_templates +from frappe.contacts.doctype.address.address import get_address_templates, get_address_display class ERPNextAddress(Address): def validate(self): @@ -22,6 +22,16 @@ class ERPNextAddress(Address): frappe.throw(_("Address needs to be linked to a Company. Please add a row for Company in the Links table."), title=_("Company Not Linked")) + def on_update(self): + """ + After Address is updated, update the related 'Primary Address' on Customer. + """ + address_display = get_address_display(self.as_dict()) + filters = { "customer_primary_address": self.name } + customers = frappe.db.get_all("Customer", filters=filters, as_list=True) + for customer_name in customers: + frappe.db.set_value("Customer", customer_name[0], "primary_address", address_display) + @frappe.whitelist() def get_shipping_address(company, address = None): filters = [ From fbac5149298332b9ea893080e9ffbc895c4edea8 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 26 Aug 2021 12:14:29 +0530 Subject: [PATCH 37/98] feat: prospect permission --- erpnext/crm/doctype/prospect/prospect.json | 25 ++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/prospect/prospect.json b/erpnext/crm/doctype/prospect/prospect.json index 2eb7cde02d..237c4255c7 100644 --- a/erpnext/crm/doctype/prospect/prospect.json +++ b/erpnext/crm/doctype/prospect/prospect.json @@ -151,7 +151,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-08-25 12:35:00.759909", + "modified": "2021-08-26 12:10:49.191839", "modified_by": "Administrator", "module": "CRM", "name": "Prospect", @@ -159,7 +159,6 @@ "permissions": [ { "create": 1, - "delete": 1, "email": 1, "export": 1, "print": 1, @@ -168,9 +167,31 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1 } ], "sort_field": "modified", "sort_order": "DESC", + "title_field": "company_name", "track_changes": 1 } \ No newline at end of file From 642b4c805cdf912fdc07de5b998df70091a8c8ac Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 26 Aug 2021 12:15:49 +0530 Subject: [PATCH 38/98] fix: Fee Validity fixes (#27156) * chore: update Fee Validity form labels * fix: first appointment should not be considered for Fee Validity * fix: Fee Validity test cases * fix: appointment test case --- .../doctype/fee_validity/fee_validity.json | 6 +++--- .../doctype/fee_validity/fee_validity.py | 14 ++------------ .../doctype/fee_validity/test_fee_validity.py | 4 ++-- .../patient_appointment/patient_appointment.py | 6 +++++- .../test_patient_appointment.py | 9 ++++++--- 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.json b/erpnext/healthcare/doctype/fee_validity/fee_validity.json index b001bf024c..d76b42e683 100644 --- a/erpnext/healthcare/doctype/fee_validity/fee_validity.json +++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.json @@ -46,13 +46,13 @@ { "fieldname": "visited", "fieldtype": "Int", - "label": "Visited yet", + "label": "Visits Completed", "read_only": 1 }, { "fieldname": "valid_till", "fieldtype": "Date", - "label": "Valid till", + "label": "Valid Till", "read_only": 1 }, { @@ -106,7 +106,7 @@ ], "in_create": 1, "links": [], - "modified": "2020-03-17 20:25:06.487418", + "modified": "2021-08-26 10:51:05.609349", "modified_by": "Administrator", "module": "Healthcare", "name": "Fee Validity", diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.py b/erpnext/healthcare/doctype/fee_validity/fee_validity.py index 5b9c17934f..59586e0c31 100644 --- a/erpnext/healthcare/doctype/fee_validity/fee_validity.py +++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.py @@ -11,7 +11,6 @@ import datetime class FeeValidity(Document): def validate(self): self.update_status() - self.set_start_date() def update_status(self): if self.visited >= self.max_visits: @@ -19,13 +18,6 @@ class FeeValidity(Document): else: self.status = 'Pending' - def set_start_date(self): - self.start_date = getdate() - for appointment in self.ref_appointments: - appointment_date = frappe.db.get_value('Patient Appointment', appointment.appointment, 'appointment_date') - if getdate(appointment_date) < self.start_date: - self.start_date = getdate(appointment_date) - def create_fee_validity(appointment): if not check_is_new_patient(appointment): @@ -36,11 +28,9 @@ def create_fee_validity(appointment): fee_validity.patient = appointment.patient fee_validity.max_visits = frappe.db.get_single_value('Healthcare Settings', 'max_visits') or 1 valid_days = frappe.db.get_single_value('Healthcare Settings', 'valid_days') or 1 - fee_validity.visited = 1 + fee_validity.visited = 0 + fee_validity.start_date = getdate(appointment.appointment_date) fee_validity.valid_till = getdate(appointment.appointment_date) + datetime.timedelta(days=int(valid_days)) - fee_validity.append('ref_appointments', { - 'appointment': appointment.name - }) fee_validity.save(ignore_permissions=True) return fee_validity diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py index 82e7136d6b..4a178723a0 100644 --- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py +++ b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py @@ -22,14 +22,14 @@ class TestFeeValidity(unittest.TestCase): item = create_healthcare_service_items() healthcare_settings = frappe.get_single("Healthcare Settings") healthcare_settings.enable_free_follow_ups = 1 - healthcare_settings.max_visits = 2 + healthcare_settings.max_visits = 1 healthcare_settings.valid_days = 7 healthcare_settings.automate_appointment_invoicing = 1 healthcare_settings.op_consulting_charge_item = item healthcare_settings.save(ignore_permissions=True) patient, medical_department, practitioner = create_healthcare_docs() - # For first appointment, invoice is generated + # For first appointment, invoice is generated. First appointment not considered in fee validity appointment = create_appointment(patient, practitioner, nowdate()) invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced") self.assertEqual(invoiced, 1) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index 05e2cd30df..7db4fa616a 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -109,9 +109,13 @@ class PatientAppointment(Document): frappe.db.set_value('Patient Appointment', self.name, 'notes', comments) def update_fee_validity(self): + if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'): + return + fee_validity = manage_fee_validity(self) if fee_validity: - frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till)) + frappe.msgprint(_('{0}: {1} has fee validity till {2}').format(self.patient, + frappe.bold(self.patient_name), fee_validity.valid_till)) @frappe.whitelist() def get_therapy_types(self): diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 9c3392cd5b..157b3e1162 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -107,14 +107,17 @@ class TestPatientAppointment(unittest.TestCase): patient, medical_department, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1) appointment = create_appointment(patient, practitioner, nowdate()) - fee_validity = frappe.db.get_value('Fee Validity Reference', {'appointment': appointment.name}, 'parent') + fee_validity = frappe.db.get_value('Fee Validity', {'patient': patient, 'practitioner': practitioner}) # fee validity created self.assertTrue(fee_validity) - visited = frappe.db.get_value('Fee Validity', fee_validity, 'visited') + # first follow up appointment + appointment = create_appointment(patient, practitioner, nowdate()) + self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), 1) + update_status(appointment.name, 'Cancelled') # check fee validity updated - self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), visited - 1) + self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), 0) frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) From baa548a3dfec3167555c7a20252d6fc72b78a213 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Thu, 26 Aug 2021 12:48:43 +0530 Subject: [PATCH 39/98] fix(regional): minor fixes and test for South Africa VAT report (#26933) * fix: added test and minor fixes * fix: sider fixes * fix: sider * fix: test fixes Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> Co-authored-by: Saqib --- .../vat_audit_report/test_vat_audit_report.py | 193 ++++++++++++++++++ .../vat_audit_report/vat_audit_report.py | 11 +- 2 files changed, 199 insertions(+), 5 deletions(-) create mode 100644 erpnext/regional/report/vat_audit_report/test_vat_audit_report.py diff --git a/erpnext/regional/report/vat_audit_report/test_vat_audit_report.py b/erpnext/regional/report/vat_audit_report/test_vat_audit_report.py new file mode 100644 index 0000000000..dea17a66fd --- /dev/null +++ b/erpnext/regional/report/vat_audit_report/test_vat_audit_report.py @@ -0,0 +1,193 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from unittest import TestCase +from frappe.utils import today + +from erpnext.accounts.doctype.account.test_account import create_account +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice + +from erpnext.regional.report.vat_audit_report.vat_audit_report import execute + +class TestVATAuditReport(TestCase): + def setUp(self): + frappe.set_user("Administrator") + make_company("_Test Company SA VAT", "_TCSV") + + create_account(account_name="VAT - 0%", account_type="Tax", + parent_account="Duties and Taxes - _TCSV", company="_Test Company SA VAT") + create_account(account_name="VAT - 15%", account_type="Tax", + parent_account="Duties and Taxes - _TCSV", company="_Test Company SA VAT") + set_sa_vat_accounts() + + make_item("_Test SA VAT Item") + make_item("_Test SA VAT Zero Rated Item", properties = {"is_zero_rated": 1}) + + make_customer() + make_supplier() + + make_sales_invoices() + create_purchase_invoices() + + def tearDown(self): + frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company SA VAT'") + frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company SA VAT'") + + def test_vat_audit_report(self): + filters = { + "company": "_Test Company SA VAT", + "from_date": today(), + "to_date": today() + } + columns, data = execute(filters) + total_tax_amount = 0 + total_row_tax = 0 + for row in data: + keys = row.keys() + # skips total row tax_amount in if.. and skips section header in elif.. + if 'voucher_no' in keys: + total_tax_amount = total_tax_amount + row['tax_amount'] + elif 'tax_amount' in keys: + total_row_tax = total_row_tax + row['tax_amount'] + + self.assertEqual(total_tax_amount, total_row_tax) + +def make_company(company_name, abbr): + if not frappe.db.exists("Company", company_name): + company = frappe.get_doc({ + "doctype": "Company", + "company_name": company_name, + "abbr": abbr, + "default_currency": "ZAR", + "country": "South Africa", + "create_chart_of_accounts_based_on": "Standard Template" + }) + company.insert() + else: + company = frappe.get_doc("Company", company_name) + + company.create_default_warehouses() + + if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": company.name}): + company.create_default_cost_center() + + company.save() + + return company + +def set_sa_vat_accounts(): + if not frappe.db.exists("South Africa VAT Settings", "_Test Company SA VAT"): + vat_accounts = frappe.get_all( + "Account", + fields=["name"], + filters = { + "company": "_Test Company SA VAT", + "is_group": 0, + "account_type": "Tax" + } + ) + + sa_vat_accounts = [] + for account in vat_accounts: + sa_vat_accounts.append({ + "doctype": "South Africa VAT Account", + "account": account.name + }) + + frappe.get_doc({ + "company": "_Test Company SA VAT", + "vat_accounts": sa_vat_accounts, + "doctype": "South Africa VAT Settings", + }).insert() + +def make_customer(): + if not frappe.db.exists("Customer", "_Test SA Customer"): + frappe.get_doc({ + "doctype": "Customer", + "customer_name": "_Test SA Customer", + "customer_type": "Company", + }).insert() + +def make_supplier(): + if not frappe.db.exists("Supplier", "_Test SA Supplier"): + frappe.get_doc({ + "doctype": "Supplier", + "supplier_name": "_Test SA Supplier", + "supplier_type": "Company", + "supplier_group":"All Supplier Groups" + }).insert() + +def make_item(item_code, properties=None): + if not frappe.db.exists("Item", item_code): + item = frappe.get_doc({ + "doctype": "Item", + "item_code": item_code, + "item_name": item_code, + "description": item_code, + "item_group": "Products" + }) + + if properties: + item.update(properties) + + item.insert() + +def make_sales_invoices(): + def make_sales_invoices_wrapper(item, rate, tax_account, tax_rate, tax=True): + si = create_sales_invoice( + company="_Test Company SA VAT", + customer = "_Test SA Customer", + currency = "ZAR", + item=item, + rate=rate, + warehouse = "Finished Goods - _TCSV", + debit_to = "Debtors - _TCSV", + income_account = "Sales - _TCSV", + expense_account = "Cost of Goods Sold - _TCSV", + cost_center = "Main - _TCSV", + do_not_save=1 + ) + if tax: + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": tax_account, + "cost_center": "Main - _TCSV", + "description": "VAT 15% @ 15.0", + "rate": tax_rate + }) + + si.submit() + + test_item = "_Test SA VAT Item" + test_zero_rated_item = "_Test SA VAT Zero Rated Item" + + make_sales_invoices_wrapper(test_item, 100.0, "VAT - 15% - _TCSV", 15.0) + make_sales_invoices_wrapper(test_zero_rated_item, 100.0, "VAT - 0% - _TCSV", 0.0) + +def create_purchase_invoices(): + pi = make_purchase_invoice( + company = "_Test Company SA VAT", + supplier = "_Test SA Supplier", + supplier_warehouse = "Finished Goods - _TCSV", + warehouse = "Finished Goods - _TCSV", + currency = "ZAR", + cost_center = "Main - _TCSV", + expense_account = "Cost of Goods Sold - _TCSV", + item = "_Test SA VAT Item", + qty = 1, + rate = 100, + uom = "Nos", + do_not_save = 1 + ) + pi.append("taxes", { + "charge_type": "On Net Total", + "account_head": "VAT - 15% - _TCSV", + "cost_center": "Main - _TCSV", + "description": "VAT 15% @ 15.0", + "rate": 15.0 + }) + + pi.submit() diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py index 17aca17afd..88f6b923e6 100644 --- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py @@ -1,11 +1,11 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt from __future__ import unicode_literals import frappe import json from frappe import _ -from frappe.utils import formatdate +from frappe.utils import formatdate, get_link_to_form def execute(filters=None): return VATAuditReport(filters).run() @@ -42,7 +42,8 @@ class VATAuditReport(object): self.sa_vat_accounts = frappe.get_list("South Africa VAT Account", filters = {"parent": self.filters.company}, pluck="account") if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate: - frappe.throw(_("Please set VAT Accounts in South Africa VAT Settings")) + link_to_settings = get_link_to_form("South Africa VAT Settings", "", label="South Africa VAT Settings") + frappe.throw(_("Please set VAT Accounts in {0}").format(link_to_settings)) def get_invoice_data(self, doctype): conditions = self.get_conditions() @@ -69,7 +70,7 @@ class VATAuditReport(object): items = frappe.db.sql(""" SELECT - item_code, parent, taxable_value, base_net_amount, is_zero_rated + item_code, parent, base_net_amount, is_zero_rated FROM `tab%s Item` WHERE @@ -79,7 +80,7 @@ class VATAuditReport(object): if d.item_code not in self.invoice_items.get(d.parent, {}): self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, { 'net_amount': 0.0}) - self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) + self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('base_net_amount', 0) self.invoice_items[d.parent][d.item_code]['is_zero_rated'] = d.is_zero_rated def get_items_based_on_tax_rate(self, doctype): From fe4540d74d0dfda170c2a781347d745fb9f86fb6 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 26 Aug 2021 12:52:36 +0530 Subject: [PATCH 40/98] fix: allow to change rate manually in case of stand-alone credit note (#27036) Co-authored-by: Marica --- .../sales_invoice/test_sales_invoice.py | 15 ++++++++- .../sales_invoice_item.json | 10 +++--- .../controllers/sales_and_purchase_return.py | 32 +++++++++++-------- erpnext/controllers/selling_controller.py | 27 ++++++++-------- erpnext/stock/stock_ledger.py | 1 + 5 files changed, 52 insertions(+), 33 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index c3d83c7d74..053d42ee53 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1140,6 +1140,18 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(loss_for_si['credit'], loss_for_return_si['debit']) self.assertEqual(loss_for_si['debit'], loss_for_return_si['credit']) + def test_incoming_rate_for_stand_alone_credit_note(self): + return_si = create_sales_invoice(is_return=1, update_stock=1, qty=-1, rate=90000, incoming_rate=10, + company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', debit_to='Debtors - TCP1', + income_account='Sales - TCP1', expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1') + + incoming_rate = frappe.db.get_value('Stock Ledger Entry', {'voucher_no': return_si.name}, 'incoming_rate') + debit_amount = frappe.db.get_value('GL Entry', + {'voucher_no': return_si.name, 'account': 'Stock In Hand - TCP1'}, 'debit') + + self.assertEqual(debit_amount, 10.0) + self.assertEqual(incoming_rate, 10.0) + def test_discount_on_net_total(self): si = frappe.copy_doc(test_records[2]) si.apply_discount_on = "Net Total" @@ -2375,7 +2387,8 @@ def create_sales_invoice(**args): "asset": args.asset or None, "cost_center": args.cost_center or "_Test Cost Center - _TC", "serial_no": args.serial_no, - "conversion_factor": 1 + "conversion_factor": 1, + "incoming_rate": args.incoming_rate or 0 }) if not args.do_not_save: diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index c77076cb90..b90f3f0904 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -53,7 +53,6 @@ "column_break_24", "base_net_rate", "base_net_amount", - "incoming_rate", "drop_ship", "delivered_by_supplier", "accounting", @@ -81,6 +80,7 @@ "target_warehouse", "quality_inspection", "batch_no", + "incoming_rate", "col_break5", "allow_zero_valuation_rate", "serial_no", @@ -807,12 +807,12 @@ "read_only": 1 }, { + "depends_on": "eval:parent.is_return && parent.update_stock && !parent.return_against", "fieldname": "incoming_rate", "fieldtype": "Currency", - "label": "Incoming Rate", + "label": "Incoming Rate (Costing)", "no_copy": 1, - "print_hide": 1, - "read_only": 1 + "print_hide": 1 }, { "depends_on": "eval: doc.uom != doc.stock_uom", @@ -833,7 +833,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-08-12 20:15:47.668399", + "modified": "2021-08-19 13:41:53.435827", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 5ee1f2f7fb..01486fcd65 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -394,19 +394,6 @@ def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None if not return_against: return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against") - if not return_against and voucher_type == 'Sales Invoice' and sle: - return get_incoming_rate({ - "item_code": sle.item_code, - "warehouse": sle.warehouse, - "posting_date": sle.get('posting_date'), - "posting_time": sle.get('posting_time'), - "qty": sle.actual_qty, - "serial_no": sle.get('serial_no'), - "company": sle.company, - "voucher_type": sle.voucher_type, - "voucher_no": sle.voucher_no - }, raise_error_if_no_rate=False) - return_against_item_field = get_return_against_item_fields(voucher_type) filters = get_filters(voucher_type, voucher_no, voucher_detail_no, @@ -417,7 +404,24 @@ def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None else: select_field = "abs(stock_value_difference / actual_qty)" - return flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field)) + rate = flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field)) + if not (rate and return_against) and voucher_type in ['Sales Invoice', 'Delivery Note']: + rate = frappe.db.get_value(f'{voucher_type} Item', voucher_detail_no, 'incoming_rate') + + if not rate and sle: + rate = get_incoming_rate({ + "item_code": sle.item_code, + "warehouse": sle.warehouse, + "posting_date": sle.get('posting_date'), + "posting_time": sle.get('posting_time'), + "qty": sle.actual_qty, + "serial_no": sle.get('serial_no'), + "company": sle.company, + "voucher_type": sle.voucher_type, + "voucher_no": sle.voucher_no + }, raise_error_if_no_rate=False) + + return rate def get_return_against_item_fields(voucher_type): return_against_item_fields = { diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index fc2cc97e0a..4ea0e114b4 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -362,7 +362,7 @@ class SellingController(StockController): sales_order.update_reserved_qty(so_item_rows) def set_incoming_rate(self): - if self.doctype not in ("Delivery Note", "Sales Invoice", "Sales Order"): + if self.doctype not in ("Delivery Note", "Sales Invoice"): return items = self.get("items") + (self.get("packed_items") or []) @@ -371,18 +371,19 @@ class SellingController(StockController): # Get incoming rate based on original item cost based on valuation method qty = flt(d.get('stock_qty') or d.get('actual_qty')) - d.incoming_rate = get_incoming_rate({ - "item_code": d.item_code, - "warehouse": d.warehouse, - "posting_date": self.get('posting_date') or self.get('transaction_date'), - "posting_time": self.get('posting_time') or nowtime(), - "qty": qty if cint(self.get("is_return")) else (-1 * qty), - "serial_no": d.get('serial_no'), - "company": self.company, - "voucher_type": self.doctype, - "voucher_no": self.name, - "allow_zero_valuation": d.get("allow_zero_valuation") - }, raise_error_if_no_rate=False) + if not d.incoming_rate: + d.incoming_rate = get_incoming_rate({ + "item_code": d.item_code, + "warehouse": d.warehouse, + "posting_date": self.get('posting_date') or self.get('transaction_date'), + "posting_time": self.get('posting_time') or nowtime(), + "qty": qty if cint(self.get("is_return")) else (-1 * qty), + "serial_no": d.get('serial_no'), + "company": self.company, + "voucher_type": self.doctype, + "voucher_no": self.name, + "allow_zero_valuation": d.get("allow_zero_valuation") + }, raise_error_if_no_rate=False) # For internal transfers use incoming rate as the valuation rate if self.is_internal_transfer(): diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index eddd048c74..27feec1d15 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -332,6 +332,7 @@ class update_entries_after(object): where item_code = %(item_code)s and warehouse = %(warehouse)s + and is_cancelled = 0 and timestamp(posting_date, time_format(posting_time, %(time_format)s)) = timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s)) order by From f4dc9ee2aa57d82a0be747a89e1ca573940da959 Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 26 Aug 2021 13:12:51 +0530 Subject: [PATCH 41/98] fix: Don't create inward SLE against SI unless is internal customer enabled (#27086) * fix: Dont create inward SLE against SI unless is internal customer enabled - Check if is internal customer enabled apart from target warehouse - Test to check if inward SLE is made if target warehouse is accidentally set but customer is not internal * test: Use internal customer for delivery of bundle items to target warehouse - created `create_internal_customer` util - reused it in delivery note and sales invoice tests - use internal customer for target warehouse test in delivery note --- .../sales_invoice/test_sales_invoice.py | 56 +++++++++++++------ erpnext/controllers/selling_controller.py | 2 +- .../selling/doctype/customer/test_customer.py | 25 +++++++++ .../delivery_note/test_delivery_note.py | 25 +++++++-- 4 files changed, 85 insertions(+), 23 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 5a19426eb0..43a5f5e2f6 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -151,7 +151,7 @@ class TestSalesInvoice(unittest.TestCase): si1 = create_sales_invoice(rate=1000) si2 = create_sales_invoice(rate=300) si3 = create_sales_invoice(qty=-1, rate=300, is_return=1) - + pe = get_payment_entry("Sales Invoice", si1.name, bank_account="_Test Bank - _TC") pe.append('references', { @@ -1828,23 +1828,12 @@ class TestSalesInvoice(unittest.TestCase): acc_settings.save() def test_inter_company_transaction(self): + from erpnext.selling.doctype.customer.test_customer import create_internal_customer - if not frappe.db.exists("Customer", "_Test Internal Customer"): - customer = frappe.get_doc({ - "customer_group": "_Test Customer Group", - "customer_name": "_Test Internal Customer", - "customer_type": "Individual", - "doctype": "Customer", - "territory": "_Test Territory", - "is_internal_customer": 1, - "represents_company": "_Test Company 1" - }) - - customer.append("companies", { - "company": "Wind Power LLC" - }) - - customer.insert() + create_internal_customer( + customer_name="_Test Internal Customer", + represents_company="_Test Company 1" + ) if not frappe.db.exists("Supplier", "_Test Internal Supplier"): supplier = frappe.get_doc({ @@ -1970,6 +1959,39 @@ class TestSalesInvoice(unittest.TestCase): frappe.local.enable_perpetual_inventory['_Test Company 1'] = old_perpetual_inventory frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock) + def test_sle_if_target_warehouse_exists_accidentally(self): + """ + Check if inward entry exists if Target Warehouse accidentally exists + but Customer is not an internal customer. + """ + se = make_stock_entry( + item_code="138-CMS Shoe", + target="Finished Goods - _TC", + company = "_Test Company", + qty=1, + basic_rate=500 + ) + + si = frappe.copy_doc(test_records[0]) + si.update_stock = 1 + si.set_warehouse = "Finished Goods - _TC" + si.set_target_warehouse = "Stores - _TC" + si.get("items")[0].warehouse = "Finished Goods - _TC" + si.get("items")[0].target_warehouse = "Stores - _TC" + si.insert() + si.submit() + + sles = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": si.name}, + fields=["name", "actual_qty"]) + + # check if only one SLE for outward entry is created + self.assertEqual(len(sles), 1) + self.assertEqual(sles[0].actual_qty, -1) + + # tear down + si.cancel() + se.cancel() + def test_internal_transfer_gl_entry(self): ## Create internal transfer account account = create_account(account_name="Unrealized Profit", diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 4ea0e114b4..844c40c8a6 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -423,7 +423,7 @@ class SellingController(StockController): or (cint(self.is_return) and self.docstatus==2)): sl_entries.append(self.get_sle_for_source_warehouse(d)) - if d.target_warehouse: + if d.target_warehouse and self.get("is_internal_customer"): sl_entries.append(self.get_sle_for_target_warehouse(d)) if d.warehouse and ((not cint(self.is_return) and self.docstatus==2) diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index b1a5b52f96..908ba270b0 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -352,3 +352,28 @@ def set_credit_limit(customer, company, credit_limit): 'credit_limit': credit_limit }) customer.credit_limits[-1].db_insert() + +def create_internal_customer(**args): + args = frappe._dict(args) + + customer_name = args.get("customer_name") or "_Test Internal Customer" + + if not frappe.db.exists("Customer", customer_name): + customer = frappe.get_doc({ + "doctype": "Customer", + "customer_group": args.customer_group or "_Test Customer Group", + "customer_name": customer_name, + "customer_type": args.customer_type or "Individual", + "territory": args.territory or "_Test Territory", + "is_internal_customer": 1, + "represents_company": args.represents_company or "_Test Company with perpetual inventory" + }) + + customer.append("companies", { + "company": args.allowed_company or "Wind Power LLC" + }) + customer.insert() + + return customer + else: + return frappe.get_cached_doc("Customer", customer_name) \ No newline at end of file diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 91e7c006ee..dffc73020d 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -430,12 +430,18 @@ class TestDeliveryNote(unittest.TestCase): }) def test_delivery_of_bundled_items_to_target_warehouse(self): + from erpnext.selling.doctype.customer.test_customer import create_internal_customer + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') + customer = create_internal_customer( + customer_name="_Test Internal Customer 2", + allowed_company="_Test Company with perpetual inventory" + ) set_valuation_method("_Test Item", "FIFO") set_valuation_method("_Test Item Home Desktop 100", "FIFO") - target_warehouse=get_warehouse(company=company, abbr="TCP1", + target_warehouse = get_warehouse(company=company, abbr="TCP1", warehouse_name="_Test Customer Warehouse").name for warehouse in ("Stores - TCP1", target_warehouse): @@ -444,10 +450,16 @@ class TestDeliveryNote(unittest.TestCase): create_stock_reconciliation(item_code="_Test Item Home Desktop 100", company = company, expense_account = "Stock Adjustment - TCP1", warehouse=warehouse, qty=500, rate=100) - dn = create_delivery_note(item_code="_Test Product Bundle Item", - company='_Test Company with perpetual inventory', cost_center = 'Main - TCP1', - expense_account = "Cost of Goods Sold - TCP1", do_not_submit=True, qty=5, rate=500, - warehouse="Stores - TCP1", target_warehouse=target_warehouse) + dn = create_delivery_note( + item_code="_Test Product Bundle Item", + company="_Test Company with perpetual inventory", + customer=customer.name, + cost_center = 'Main - TCP1', + expense_account = "Cost of Goods Sold - TCP1", + do_not_submit=True, + qty=5, rate=500, + warehouse="Stores - TCP1", + target_warehouse=target_warehouse) dn.submit() @@ -487,6 +499,9 @@ class TestDeliveryNote(unittest.TestCase): for i, gle in enumerate(gl_entries): self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account)) + # tear down + frappe.db.rollback() + def test_closed_delivery_note(self): from erpnext.stock.doctype.delivery_note.delivery_note import update_delivery_note_status From b389b8c3ad945f0192c58151e525d9c077ceb1d0 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Thu, 26 Aug 2021 13:15:57 +0530 Subject: [PATCH 42/98] fix: prevent over riding scrap table values, name kwargs, set currency --- erpnext/manufacturing/doctype/bom/bom.py | 2 +- erpnext/manufacturing/doctype/bom/test_bom.py | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index ed1e259c3c..24f84e63b3 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -231,7 +231,7 @@ class BOM(WebsiteGenerator): } ret = self.get_bom_material_detail(args) for key, value in ret.items(): - if not item.get(key): + if item.get(key) is None: item.set(key, value) @frappe.whitelist() diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 6e17f2a831..b8f0db0de2 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -285,30 +285,30 @@ class TestBOM(unittest.TestCase): if not frappe.db.exists("BOM", f"BOM-{fg_item_non_whole.item_code}-001"): bom_doc = create_bom_with_process_loss_item( - fg_item_non_whole, bom_item, 0.25, 0, 1 + fg_item_non_whole, bom_item, scrap_qty=0.25, scrap_rate=0, fg_qty=1 ) bom_doc.submit() bom_doc = create_bom_with_process_loss_item( - fg_item_non_whole, bom_item, 2, 0 + fg_item_non_whole, bom_item, scrap_qty=2, scrap_rate=0 ) # PL Item qty can't be >= FG Item qty self.assertRaises(frappe.ValidationError, bom_doc.submit) bom_doc = create_bom_with_process_loss_item( - fg_item_non_whole, bom_item, 1, 100 + fg_item_non_whole, bom_item, scrap_qty=1, scrap_rate=100 ) # PL Item rate has to be 0 self.assertRaises(frappe.ValidationError, bom_doc.submit) bom_doc = create_bom_with_process_loss_item( - fg_item_whole, bom_item, 0.25, 0 + fg_item_whole, bom_item, scrap_qty=0.25, scrap_rate=0 ) # Items with whole UOMs can't be PL Items self.assertRaises(frappe.ValidationError, bom_doc.submit) bom_doc = create_bom_with_process_loss_item( - fg_item_non_whole, bom_item, 0.25, 0, is_process_loss=0 + fg_item_non_whole, bom_item, scrap_qty=0.25, scrap_rate=0, is_process_loss=0 ) # FG Items in Scrap/Loss Table should have Is Process Loss set self.assertRaises(frappe.ValidationError, bom_doc.submit) @@ -316,9 +316,6 @@ class TestBOM(unittest.TestCase): def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) - - - def level_order_traversal(node): traversal = [] q = deque() @@ -364,6 +361,7 @@ def create_nested_bom(tree, prefix="_Test bom "): bom = frappe.get_doc(doctype="BOM", item=bom_item_code) for child_item in child_items.keys(): bom.append("items", {"item_code": prefix + child_item}) + bom.currency = "INR" bom.insert() bom.submit() @@ -407,6 +405,7 @@ def create_bom_with_process_loss_item( "rate": scrap_rate, "is_process_loss": is_process_loss }) + bom_doc.currency = "INR" return bom_doc def create_process_loss_bom_items(): From 8ed63383366728ae7650f6858feaba7ee1f832cd Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 26 Aug 2021 13:27:16 +0530 Subject: [PATCH 43/98] refactor!: remove e_invoice utils (#27167) --- erpnext/regional/india/e_invoice/utils.py | 1131 --------------------- 1 file changed, 1131 deletions(-) delete mode 100644 erpnext/regional/india/e_invoice/utils.py diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py deleted file mode 100644 index 765b51f435..0000000000 --- a/erpnext/regional/india/e_invoice/utils.py +++ /dev/null @@ -1,1131 +0,0 @@ -# -*- 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 os -import re -import jwt -import sys -import json -import base64 -import frappe -import six -import traceback -import io -from frappe import _, bold -from pyqrcode import create as qrcreate -from frappe.utils.background_jobs import enqueue -from frappe.utils.scheduler import is_scheduler_inactive -from frappe.core.page.background_jobs.background_jobs import get_info -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, get_link_to_form, getdate, time_diff_in_hours - -@frappe.whitelist() -def validate_eligibility(doc): - if isinstance(doc, six.string_types): - doc = json.loads(doc) - - invalid_doctype = doc.get('doctype') != 'Sales Invoice' - if invalid_doctype: - return False - - einvoicing_enabled = cint(frappe.db.get_single_value('E Invoice Settings', 'enable')) - if not einvoicing_enabled: - return False - - einvoicing_eligible_from = frappe.db.get_single_value('E Invoice Settings', 'applicable_from') or '2021-04-01' - if getdate(doc.get('posting_date')) < getdate(einvoicing_eligible_from): - return False - - invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') }) - invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] - company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') - - # if export invoice, then taxes can be empty - # invoice can only be ineligible if no taxes applied and is not an export invoice - no_taxes_applied = not doc.get('taxes') and not doc.get('gst_category') == 'Overseas' - has_non_gst_item = any(d for d in doc.get('items', []) if d.get('is_non_gst')) - - if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item: - return False - - return True - -def validate_einvoice_fields(doc): - invoice_eligible = validate_eligibility(doc) - - if not invoice_eligible: - return - - if doc.docstatus == 0 and doc._action == 'save': - if doc.irn: - frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed')) - if len(doc.name) > 16: - raise_document_name_too_long_error() - - doc.einvoice_status = 'Pending' - - elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: - frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN')) - - elif doc.irn and doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled: - frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed')) - -def raise_document_name_too_long_error(): - title = _('Document ID Too Long') - msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice') - msg += ', ' - msg += _('document id {} exceed 16 letters.').format(bold(_('should not'))) - msg += '

' - msg += _('You must {} your {} in order to have document id of {} length 16.').format( - bold(_('modify')), bold(_('naming series')), bold(_('maximum')) - ) - msg += _('Please account for ammended documents too.') - frappe.throw(msg, title=title) - -def read_json(name): - file_path = os.path.join(os.path.dirname(__file__), '{name}.json'.format(name=name)) - with open(file_path, 'r') as f: - return cstr(f.read()) - -def get_transaction_details(invoice): - supply_type = '' - if invoice.gst_category == 'Registered Regular': supply_type = 'B2B' - elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP' - elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP' - elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP' - - if not supply_type: - rr, sez, overseas, export = bold('Registered Regular'), bold('SEZ'), bold('Overseas'), bold('Deemed Export') - frappe.throw(_('GST category should be one of {}, {}, {}, {}').format(rr, sez, overseas, export), - title=_('Invalid Supply Type')) - - return frappe._dict(dict( - tax_scheme='GST', - supply_type=supply_type, - reverse_charge=invoice.reverse_charge - )) - -def get_doc_details(invoice): - if getdate(invoice.posting_date) < getdate('2021-01-01'): - frappe.throw(_('IRN generation is not allowed for invoices dated before 1st Jan 2021'), title=_('Not Allowed')) - - invoice_type = 'CRN' if invoice.is_return else 'INV' - - invoice_name = invoice.name - invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy') - - return frappe._dict(dict( - invoice_type=invoice_type, - invoice_name=invoice_name, - invoice_date=invoice_date - )) - -def validate_address_fields(address, is_shipping_address): - if ((not address.gstin and not is_shipping_address) - or not address.city - or not address.pincode - or not address.address_title - or not address.address_line1 - or not address.gst_state_number): - - frappe.throw( - msg=_('Address Lines, City, Pincode, GSTIN are mandatory for address {}. Please set them and try again.').format(address.name), - title=_('Missing Address Fields') - ) - -def get_party_details(address_name, is_shipping_address=False): - addr = frappe.get_doc('Address', address_name) - - validate_address_fields(addr, is_shipping_address) - - if addr.gst_state_number == 97: - # according to einvoice standard - addr.pincode = 999999 - - party_address_details = frappe._dict(dict( - legal_name=sanitize_for_json(addr.address_title), - location=sanitize_for_json(addr.city), - pincode=addr.pincode, gstin=addr.gstin, - state_code=addr.gst_state_number, - address_line1=sanitize_for_json(addr.address_line1), - address_line2=sanitize_for_json(addr.address_line2) - )) - - return party_address_details - -def get_overseas_address_details(address_name): - 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=sanitize_for_json(address_title), - location=city, - address_line1=sanitize_for_json(address_line1), - address_line2=sanitize_for_json(address_line2), - pincode=999999, state_code=96, place_of_supply=96 - )) - -def get_item_list(invoice): - item_list = [] - - for d in invoice.items: - einvoice_item_schema = read_json('einv_item_template') - item = frappe._dict({}) - item.update(d.as_dict()) - - item.sr_no = d.idx - item.description = sanitize_for_json(d.item_name) - - item.qty = abs(item.qty) - if flt(item.qty) != 0.0: - item.unit_rate = abs(item.taxable_value / item.qty) - else: - item.unit_rate = abs(item.taxable_value) - item.gross_amount = abs(item.taxable_value) - item.taxable_value = abs(item.taxable_value) - item.discount_amount = 0 - - 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 = 'Y' if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else 'N' - item.serial_no = "" - - item = update_item_taxes(invoice, item) - - item.total_value = abs( - item.taxable_value + item.igst_amount + item.sgst_amount + - item.cgst_amount + item.cess_amount + item.cess_nadv_amount + item.other_charges - ) - einv_item = einvoice_item_schema.format(item=item) - item_list.append(einv_item) - - return ', '.join(item_list) - -def update_item_taxes(invoice, item): - gst_accounts = get_gst_accounts(invoice.company) - gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] - - for attr in [ - 'tax_rate', 'cess_rate', 'cess_nadv_amount', - 'cgst_amount', 'sgst_amount', 'igst_amount', - 'cess_amount', 'cess_nadv_amount', 'other_charges' - ]: - item[attr] = 0 - - for t in invoice.taxes: - is_applicable = t.tax_amount and t.account_head in gst_accounts_list - if is_applicable: - # this contains item wise tax rate & tax amount (incl. discount) - item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code or item.item_name) - - item_tax_rate = item_tax_detail[0] - # item tax amount excluding discount amount - item_tax_amount = (item_tax_rate / 100) * item.taxable_value - - if t.account_head in gst_accounts.cess_account: - item_tax_amount_after_discount = item_tax_detail[1] - if t.charge_type == 'On Item Quantity': - item.cess_nadv_amount += abs(item_tax_amount_after_discount) - else: - item.cess_rate += item_tax_rate - item.cess_amount += abs(item_tax_amount_after_discount) - - for tax_type in ['igst', 'cgst', 'sgst']: - if t.account_head in gst_accounts[f'{tax_type}_account']: - item.tax_rate += item_tax_rate - item[f'{tax_type}_amount'] += abs(item_tax_amount) - else: - # TODO: other charges per item - pass - - return item - -def get_invoice_value_details(invoice): - invoice_value_details = frappe._dict(dict()) - invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')])) - invoice_value_details.invoice_discount_amt = 0 - - invoice_value_details.round_off = invoice.base_rounding_adjustment - invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total) - invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total) - - invoice_value_details = update_invoice_taxes(invoice, invoice_value_details) - - return invoice_value_details - -def update_invoice_taxes(invoice, invoice_value_details): - gst_accounts = get_gst_accounts(invoice.company) - gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] - - invoice_value_details.total_cgst_amt = 0 - invoice_value_details.total_sgst_amt = 0 - invoice_value_details.total_igst_amt = 0 - invoice_value_details.total_cess_amt = 0 - invoice_value_details.total_other_charges = 0 - considered_rows = [] - - for t in invoice.taxes: - tax_amount = t.base_tax_amount_after_discount_amount - if t.account_head in gst_accounts_list: - if t.account_head in gst_accounts.cess_account: - # using after discount amt since item also uses after discount amt for cess calc - invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount) - - for tax_type in ['igst', 'cgst', 'sgst']: - if t.account_head in gst_accounts[f'{tax_type}_account']: - - invoice_value_details[f'total_{tax_type}_amt'] += abs(tax_amount) - update_other_charges(t, invoice_value_details, gst_accounts_list, invoice, considered_rows) - else: - invoice_value_details.total_other_charges += abs(tax_amount) - - return invoice_value_details - -def update_other_charges(tax_row, invoice_value_details, gst_accounts_list, invoice, considered_rows): - prev_row_id = cint(tax_row.row_id) - 1 - if tax_row.account_head in gst_accounts_list and prev_row_id not in considered_rows: - if tax_row.charge_type == 'On Previous Row Amount': - amount = invoice.get('taxes')[prev_row_id].tax_amount_after_discount_amount - invoice_value_details.total_other_charges -= abs(amount) - considered_rows.append(prev_row_id) - if tax_row.charge_type == 'On Previous Row Total': - amount = invoice.get('taxes')[prev_row_id].base_total - invoice.base_net_total - invoice_value_details.total_other_charges -= abs(amount) - considered_rows.append(prev_row_id) - -def get_payment_details(invoice): - payee_name = invoice.company - mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments]) - paid_amount = invoice.base_paid_amount - outstanding_amount = invoice.outstanding_amount - - return frappe._dict(dict( - payee_name=payee_name, mode_of_payment=mode_of_payment, - paid_amount=paid_amount, outstanding_amount=outstanding_amount - )) - -def get_return_doc_reference(invoice): - invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date') - return frappe._dict(dict( - invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy') - )) - -def get_eway_bill_details(invoice): - if invoice.is_return: - frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'), - title=_('Invalid Fields')) - - - mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' } - vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' } - - return frappe._dict(dict( - gstin=invoice.gst_transporter_id, - name=invoice.transporter_name, - mode_of_transport=mode_of_transport[invoice.mode_of_transport], - distance=invoice.distance or 0, - document_name=invoice.lr_no, - document_date=format_date(invoice.lr_date, 'dd/mm/yyyy'), - vehicle_no=invoice.vehicle_no, - 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. Please set Company Address and try again.'), - title=_('Missing Fields') - ) - if not invoice.customer_address: - frappe.throw( - _('Customer Address is mandatory to fetch customer GSTIN details. Please set Company Address and try again.'), - 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 invoice.gst_category != 'Overseas' and 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 validate_totals(einvoice): - item_list = einvoice['ItemList'] - value_details = einvoice['ValDtls'] - - total_item_ass_value = 0 - total_item_cgst_value = 0 - total_item_sgst_value = 0 - total_item_igst_value = 0 - total_item_value = 0 - for item in item_list: - total_item_ass_value += flt(item['AssAmt']) - total_item_cgst_value += flt(item['CgstAmt']) - total_item_sgst_value += flt(item['SgstAmt']) - total_item_igst_value += flt(item['IgstAmt']) - total_item_value += flt(item['TotItemVal']) - - if abs(flt(item['AssAmt']) * flt(item['GstRt']) / 100) - (flt(item['CgstAmt']) + flt(item['SgstAmt']) + flt(item['IgstAmt'])) > 1: - frappe.throw(_('Row #{}: GST rate is invalid. Please remove tax rows with zero tax amount from taxes table.').format(item.idx)) - - if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1: - frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.')) - - if abs( - flt(value_details['TotInvVal']) + flt(value_details['Discount']) - - flt(value_details['OthChrg']) - flt(value_details['RndOffAmt']) - - total_item_value) > 1: - frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.')) - - calculated_invoice_value = \ - flt(value_details['AssVal']) + flt(value_details['CgstVal']) \ - + flt(value_details['SgstVal']) + flt(value_details['IgstVal']) \ - + flt(value_details['OthChrg']) + flt(value_details['RndOffAmt']) - flt(value_details['Discount']) - - if abs(flt(value_details['TotInvVal']) - calculated_invoice_value) > 1: - frappe.throw(_('Total Item Value + Taxes - Discount is not equal to the Invoice Grand Total. Please check taxes / discounts for any correction.')) - -def make_einvoice(invoice): - validate_mandatory_fields(invoice) - - schema = read_json('einv_template') - - transaction_details = get_transaction_details(invoice) - item_list = get_item_list(invoice) - doc_details = get_doc_details(invoice) - invoice_value_details = get_invoice_value_details(invoice) - seller_details = get_party_details(invoice.company_address) - - if invoice.gst_category == 'Overseas': - buyer_details = get_overseas_address_details(invoice.customer_address) - else: - buyer_details = get_party_details(invoice.customer_address) - place_of_supply = get_place_of_supply(invoice, invoice.doctype) - if place_of_supply: - place_of_supply = place_of_supply.split('-')[0] - else: - place_of_supply = sanitize_for_json(invoice.billing_address_gstin)[:2] - buyer_details.update(dict(place_of_supply=place_of_supply)) - - seller_details.update(dict(legal_name=invoice.company)) - buyer_details.update(dict(legal_name=invoice.customer_name or invoice.customer)) - - shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({}) - if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name: - if invoice.gst_category == 'Overseas': - shipping_details = get_overseas_address_details(invoice.shipping_address_name) - else: - shipping_details = get_party_details(invoice.shipping_address_name, is_shipping_address=True) - - if invoice.is_pos and invoice.base_paid_amount: - payment_details = get_payment_details(invoice) - - if invoice.is_return and invoice.return_against: - prev_doc_details = get_return_doc_reference(invoice) - - if invoice.transporter and not invoice.is_return: - eway_bill_details = get_eway_bill_details(invoice) - - # not yet implemented - dispatch_details = period_details = export_details = frappe._dict({}) - - einvoice = schema.format( - transaction_details=transaction_details, doc_details=doc_details, dispatch_details=dispatch_details, - seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details, - item_list=item_list, invoice_value_details=invoice_value_details, payment_details=payment_details, - period_details=period_details, prev_doc_details=prev_doc_details, - export_details=export_details, eway_bill_details=eway_bill_details - ) - - try: - einvoice = safe_json_load(einvoice) - einvoice = santize_einvoice_fields(einvoice) - except Exception: - show_link_to_error_log(invoice, einvoice) - - validate_totals(einvoice) - - return einvoice - -def show_link_to_error_log(invoice, einvoice): - err_log = log_error(einvoice) - link_to_error_log = get_link_to_form('Error Log', err_log.name, 'Error Log') - frappe.throw( - _('An error occurred while creating e-invoice for {}. Please check {} for more information.').format( - invoice.name, link_to_error_log), - title=_('E Invoice Creation Failed') - ) - -def log_error(data=None): - if isinstance(data, six.string_types): - data = json.loads(data) - - seperator = "--" * 50 - err_tb = traceback.format_exc() - err_msg = str(sys.exc_info()[1]) - data = json.dumps(data, indent=4) - - message = "\n".join([ - "Error", err_msg, seperator, - "Data:", data, seperator, - "Exception:", err_tb - ]) - frappe.log_error(title=_('E Invoice Request Failed'), message=message) - -def santize_einvoice_fields(einvoice): - int_fields = ["Pin","Distance","CrDay"] - float_fields = ["Qty","FreeQty","UnitPrice","TotAmt","Discount","PreTaxVal","AssAmt","GstRt","IgstAmt","CgstAmt","SgstAmt","CesRt","CesAmt","CesNonAdvlAmt","StateCesRt","StateCesAmt","StateCesNonAdvlAmt","OthChrg","TotItemVal","AssVal","CgstVal","SgstVal","IgstVal","CesVal","StCesVal","Discount","OthChrg","RndOffAmt","TotInvVal","TotInvValFc","PaidAmt","PaymtDue","ExpDuty",] - copy = einvoice.copy() - for key, value in copy.items(): - if isinstance(value, list): - for idx, d in enumerate(value): - santized_dict = santize_einvoice_fields(d) - if santized_dict: - einvoice[key][idx] = santized_dict - else: - einvoice[key].pop(idx) - - if not einvoice[key]: - einvoice.pop(key, None) - - elif isinstance(value, dict): - santized_dict = santize_einvoice_fields(value) - if santized_dict: - einvoice[key] = santized_dict - else: - einvoice.pop(key, None) - - elif not value or value == "None": - einvoice.pop(key, None) - - elif key in float_fields: - einvoice[key] = flt(value, 2) - - elif key in int_fields: - einvoice[key] = cint(value) - - return einvoice - -def safe_json_load(json_string): - try: - return json.loads(json_string) - except json.JSONDecodeError as e: - # print a snippet of 40 characters around the location where error occured - pos = e.pos - start, end = max(0, pos-20), min(len(json_string)-1, pos+20) - snippet = json_string[start:end] - frappe.throw(_("Error in input data. Please check for any special characters near following input:
{}").format(snippet)) - -class RequestFailed(Exception): - pass -class CancellationNotAllowed(Exception): - pass - -class GSPConnector(): - def __init__(self, doctype=None, docname=None): - self.doctype = doctype - self.docname = docname - - self.set_invoice() - self.set_credentials() - - # authenticate url is same for sandbox & live - self.authenticate_url = 'https://gsp.adaequare.com/gsp/authenticate?grant_type=token' - self.base_url = 'https://gsp.adaequare.com' if not self.e_invoice_settings.sandbox_mode else 'https://gsp.adaequare.com/test' - - self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel' - self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn' - self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice' - self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin' - self.cancel_ewaybill_url = self.base_url + '/enriched/ewb/ewayapi?action=CANEWB' - self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill' - - def set_invoice(self): - self.invoice = None - if self.doctype and self.docname: - self.invoice = frappe.get_cached_doc(self.doctype, self.docname) - - def set_credentials(self): - self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings') - - if not self.e_invoice_settings.enable: - frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings"))) - - if self.invoice: - gstin = self.get_seller_gstin() - credentials_for_gstin = [d for d in self.e_invoice_settings.credentials if d.gstin == gstin] - if credentials_for_gstin: - self.credentials = credentials_for_gstin[0] - else: - frappe.throw(_('Cannot find e-invoicing credentials for selected Company GSTIN. Please check E-Invoice Settings')) - else: - self.credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None - - def get_seller_gstin(self): - gstin = frappe.db.get_value('Address', self.invoice.company_address, 'gstin') - if not gstin: - frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.')) - return gstin - - def get_auth_token(self): - if time_diff_in_seconds(self.e_invoice_settings.token_expiry, now_datetime()) < 150.0: - self.fetch_auth_token() - - return self.e_invoice_settings.auth_token - - def make_request(self, request_type, url, headers=None, data=None): - if request_type == 'post': - res = make_post_request(url, headers=headers, data=data) - else: - res = make_get_request(url, headers=headers, data=data) - - self.log_request(url, headers, data, res) - return res - - def log_request(self, url, headers, data, res): - headers.update({ 'password': self.credentials.password }) - request_log = frappe.get_doc({ - "doctype": "E Invoice Request Log", - "user": frappe.session.user, - "reference_invoice": self.invoice.name if self.invoice else None, - "url": url, - "headers": json.dumps(headers, indent=4) if headers else None, - "data": json.dumps(data, indent=4) if isinstance(data, dict) else data, - "response": json.dumps(res, indent=4) if res else None - }) - request_log.save(ignore_permissions=True) - frappe.db.commit() - - def fetch_auth_token(self): - headers = { - 'gspappid': frappe.conf.einvoice_client_id, - 'gspappsecret': frappe.conf.einvoice_client_secret - } - res = {} - try: - res = self.make_request('post', self.authenticate_url, headers) - self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token')) - self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in')) - self.e_invoice_settings.save(ignore_permissions=True) - self.e_invoice_settings.reload() - - except Exception: - log_error(res) - self.raise_error(True) - - def get_headers(self): - return { - 'content-type': 'application/json', - 'user_name': self.credentials.username, - 'password': self.credentials.get_password(), - 'gstin': self.credentials.gstin, - 'authorization': self.get_auth_token(), - 'requestid': str(base64.b64encode(os.urandom(18))), - } - - def fetch_gstin_details(self, gstin): - headers = self.get_headers() - - try: - params = '?gstin={gstin}'.format(gstin=gstin) - res = self.make_request('get', self.gstin_details_url + params, headers) - if res.get('success'): - return res.get('result') - else: - log_error(res) - raise RequestFailed - - except RequestFailed: - self.raise_error() - - except Exception: - log_error() - self.raise_error(True) - @staticmethod - def get_gstin_details(gstin): - '''fetch and cache GSTIN details''' - if not hasattr(frappe.local, 'gstin_cache'): - frappe.local.gstin_cache = {} - - key = gstin - gsp_connector = GSPConnector() - details = gsp_connector.fetch_gstin_details(gstin) - - frappe.local.gstin_cache[key] = details - frappe.cache().hset('gstin_cache', key, details) - return details - - def generate_irn(self): - data = {} - try: - headers = self.get_headers() - einvoice = make_einvoice(self.invoice) - data = json.dumps(einvoice, indent=4) - res = self.make_request('post', self.generate_irn_url, headers, data) - - if res.get('success'): - self.set_einvoice_data(res.get('result')) - - elif '2150' in res.get('message'): - # IRN already generated but not updated in invoice - # Extract the IRN from the response description and fetch irn details - irn = res.get('result')[0].get('Desc').get('Irn') - irn_details = self.get_irn_details(irn) - if irn_details: - self.set_einvoice_data(irn_details) - else: - raise RequestFailed('IRN has already been generated for the invoice but cannot fetch details for the it. \ - Contact ERPNext support to resolve the issue.') - - else: - raise RequestFailed - - except RequestFailed: - errors = self.sanitize_error_message(res.get('message')) - self.set_failed_status(errors=errors) - self.raise_error(errors=errors) - - except Exception as e: - self.set_failed_status(errors=str(e)) - log_error(data) - self.raise_error(True) - - @staticmethod - def bulk_generate_irn(invoices): - gsp_connector = GSPConnector() - gsp_connector.doctype = 'Sales Invoice' - - failed = [] - - for invoice in invoices: - try: - gsp_connector.docname = invoice - gsp_connector.set_invoice() - gsp_connector.set_credentials() - gsp_connector.generate_irn() - - except Exception as e: - failed.append({ - 'docname': invoice, - 'message': str(e) - }) - - return failed - - def get_irn_details(self, irn): - headers = self.get_headers() - - try: - params = '?irn={irn}'.format(irn=irn) - res = self.make_request('get', self.irn_details_url + params, headers) - if res.get('success'): - return res.get('result') - else: - raise RequestFailed - - except RequestFailed: - errors = self.sanitize_error_message(res.get('message')) - self.raise_error(errors=errors) - - except Exception: - log_error() - self.raise_error(True) - - def cancel_irn(self, irn, reason, remark): - data, res = {}, {} - try: - # validate cancellation - if time_diff_in_hours(now_datetime(), self.invoice.ack_date) > 24: - frappe.throw(_('E-Invoice cannot be cancelled after 24 hours of IRN generation.'), title=_('Not Allowed'), exc=CancellationNotAllowed) - if not irn: - frappe.throw(_('IRN not found. You must generate IRN before cancelling.'), title=_('Not Allowed'), exc=CancellationNotAllowed) - - headers = self.get_headers() - data = json.dumps({ - 'Irn': irn, - 'Cnlrsn': reason, - 'Cnlrem': remark - }, indent=4) - - res = self.make_request('post', self.cancel_irn_url, headers, data) - if res.get('success') or '9999' in res.get('message'): - self.invoice.irn_cancelled = 1 - self.invoice.irn_cancel_date = res.get('result')['CancelDate'] if res.get('result') else "" - self.invoice.einvoice_status = 'Cancelled' - self.invoice.flags.updater_reference = { - 'doctype': self.invoice.doctype, - 'docname': self.invoice.name, - 'label': _('IRN Cancelled - {}').format(remark) - } - self.update_invoice() - - else: - raise RequestFailed - - except RequestFailed: - errors = self.sanitize_error_message(res.get('message')) - self.set_failed_status(errors=errors) - self.raise_error(errors=errors) - - except CancellationNotAllowed as e: - self.set_failed_status(errors=str(e)) - self.raise_error(errors=str(e)) - - except Exception as e: - self.set_failed_status(errors=str(e)) - log_error(data) - self.raise_error(True) - - @staticmethod - def bulk_cancel_irn(invoices, reason, remark): - gsp_connector = GSPConnector() - gsp_connector.doctype = 'Sales Invoice' - - failed = [] - - for invoice in invoices: - try: - gsp_connector.docname = invoice - gsp_connector.set_invoice() - gsp_connector.set_credentials() - irn = gsp_connector.invoice.irn - gsp_connector.cancel_irn(irn, reason, remark) - - except Exception as e: - failed.append({ - 'docname': invoice, - 'message': str(e) - }) - - return failed - - def generate_eway_bill(self, **kwargs): - args = frappe._dict(kwargs) - - headers = self.get_headers() - eway_bill_details = get_eway_bill_details(args) - data = json.dumps({ - 'Irn': args.irn, - 'Distance': cint(eway_bill_details.distance), - 'TransMode': eway_bill_details.mode_of_transport, - 'TransId': eway_bill_details.gstin, - 'TransName': eway_bill_details.transporter, - 'TrnDocDt': eway_bill_details.document_date, - 'TrnDocNo': eway_bill_details.document_name, - 'VehNo': eway_bill_details.vehicle_no, - 'VehType': eway_bill_details.vehicle_type - }, indent=4) - - try: - res = self.make_request('post', self.generate_ewaybill_url, headers, data) - if res.get('success'): - self.invoice.ewaybill = res.get('result').get('EwbNo') - self.invoice.eway_bill_validity = res.get('result').get('EwbValidTill') - self.invoice.eway_bill_cancelled = 0 - self.invoice.update(args) - self.invoice.flags.updater_reference = { - 'doctype': self.invoice.doctype, - 'docname': self.invoice.name, - 'label': _('E-Way Bill Generated') - } - self.update_invoice() - - else: - raise RequestFailed - - except RequestFailed: - errors = self.sanitize_error_message(res.get('message')) - self.raise_error(errors=errors) - - except Exception: - log_error(data) - self.raise_error(True) - - def cancel_eway_bill(self, eway_bill, reason, remark): - headers = self.get_headers() - data = json.dumps({ - 'ewbNo': eway_bill, - 'cancelRsnCode': reason, - 'cancelRmrk': remark - }, indent=4) - headers["username"] = headers["user_name"] - del headers["user_name"] - try: - res = self.make_request('post', self.cancel_ewaybill_url, headers, data) - if res.get('success'): - self.invoice.ewaybill = '' - self.invoice.eway_bill_cancelled = 1 - self.invoice.flags.updater_reference = { - 'doctype': self.invoice.doctype, - 'docname': self.invoice.name, - 'label': _('E-Way Bill Cancelled - {}').format(remark) - } - self.update_invoice() - - else: - raise RequestFailed - - except RequestFailed: - errors = self.sanitize_error_message(res.get('message')) - self.raise_error(errors=errors) - - except Exception: - log_error(data) - self.raise_error(True) - - def sanitize_error_message(self, message): - ''' - On validation errors, response message looks something like this: - message = '2174 : For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, - 3095 : Supplier GSTIN is inactive' - we search for string between ':' to extract the error messages - errors = [ - ': For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, 3095 ', - ': Test' - ] - then we trim down the message by looping over errors - ''' - if not message: - return [] - - errors = re.findall(': [^:]+', message) - for idx, e in enumerate(errors): - # remove colons - errors[idx] = errors[idx].replace(':', '').strip() - # if not last - if idx != len(errors) - 1: - # remove last 7 chars eg: ', 3095 ' - errors[idx] = errors[idx][:-6] - - return errors - - def raise_error(self, raise_exception=False, errors=[]): - title = _('E Invoice Request Failed') - if errors: - frappe.throw(errors, title=title, as_list=1) - else: - link_to_error_list = 'Error Log' - frappe.msgprint( - _('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list), - title=title, - raise_exception=raise_exception, - indicator='red' - ) - - def set_einvoice_data(self, res): - enc_signed_invoice = res.get('SignedInvoice') - dec_signed_invoice = jwt.decode(enc_signed_invoice, options={"verify_signature": False})['data'] - - self.invoice.irn = res.get('Irn') - self.invoice.ewaybill = res.get('EwbNo') - self.invoice.eway_bill_validity = res.get('EwbValidTill') - self.invoice.ack_no = res.get('AckNo') - self.invoice.ack_date = res.get('AckDt') - self.invoice.signed_einvoice = dec_signed_invoice - self.invoice.ack_no = res.get('AckNo') - self.invoice.ack_date = res.get('AckDt') - self.invoice.signed_qr_code = res.get('SignedQRCode') - self.invoice.einvoice_status = 'Generated' - - self.attach_qrcode_image() - - self.invoice.flags.updater_reference = { - 'doctype': self.invoice.doctype, - 'docname': self.invoice.name, - 'label': _('IRN Generated') - } - self.update_invoice() - - def attach_qrcode_image(self): - qrcode = self.invoice.signed_qr_code - doctype = self.invoice.doctype - docname = self.invoice.name - filename = 'QRCode_{}.png'.format(docname).replace(os.path.sep, "__") - - qr_image = io.BytesIO() - url = qrcreate(qrcode, error='L') - url.png(qr_image, scale=2, quiet_zone=1) - _file = frappe.get_doc({ - "doctype": "File", - "file_name": filename, - "attached_to_doctype": doctype, - "attached_to_name": docname, - "attached_to_field": "qrcode_image", - "is_private": 0, - "content": qr_image.getvalue()}) - _file.save() - frappe.db.commit() - self.invoice.qrcode_image = _file.file_url - - def update_invoice(self): - self.invoice.flags.ignore_validate_update_after_submit = True - self.invoice.flags.ignore_validate = True - self.invoice.save() - - def set_failed_status(self, errors=None): - frappe.db.rollback() - self.invoice.einvoice_status = 'Failed' - self.invoice.failure_description = self.get_failure_message(errors) if errors else "" - self.update_invoice() - frappe.db.commit() - - def get_failure_message(self, errors): - if isinstance(errors, list): - errors = ', '.join(errors) - return errors - -def sanitize_for_json(string): - """Escape JSON specific characters from a string.""" - - # json.dumps adds double-quotes to the string. Indexing to remove them. - return json.dumps(string)[1:-1] - -@frappe.whitelist() -def get_einvoice(doctype, docname): - invoice = frappe.get_doc(doctype, docname) - return make_einvoice(invoice) - -@frappe.whitelist() -def generate_irn(doctype, docname): - gsp_connector = GSPConnector(doctype, docname) - gsp_connector.generate_irn() - -@frappe.whitelist() -def cancel_irn(doctype, docname, irn, reason, remark): - gsp_connector = GSPConnector(doctype, docname) - gsp_connector.cancel_irn(irn, reason, remark) - -@frappe.whitelist() -def generate_eway_bill(doctype, docname, **kwargs): - gsp_connector = GSPConnector(doctype, docname) - gsp_connector.generate_eway_bill(**kwargs) - -@frappe.whitelist() -def cancel_eway_bill(doctype, docname): - # TODO: uncomment when eway_bill api from Adequare is enabled - # gsp_connector = GSPConnector(doctype, docname) - # gsp_connector.cancel_eway_bill(eway_bill, reason, remark) - - frappe.db.set_value(doctype, docname, 'ewaybill', '') - frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1) - -@frappe.whitelist() -def generate_einvoices(docnames): - docnames = json.loads(docnames) or [] - - if len(docnames) < 10: - failures = GSPConnector.bulk_generate_irn(docnames) - frappe.local.message_log = [] - - if failures: - show_bulk_action_failure_message(failures) - - success = len(docnames) - len(failures) - frappe.msgprint( - _('{} e-invoices generated successfully').format(success), - title=_('Bulk E-Invoice Generation Complete') - ) - - else: - enqueue_bulk_action(schedule_bulk_generate_irn, docnames=docnames) - -def schedule_bulk_generate_irn(docnames): - failures = GSPConnector.bulk_generate_irn(docnames) - frappe.local.message_log = [] - - frappe.publish_realtime("bulk_einvoice_generation_complete", { - "user": frappe.session.user, - "failures": failures, - "invoices": docnames - }) - -def show_bulk_action_failure_message(failures): - for doc in failures: - docname = '{0}'.format(doc.get('docname')) - message = doc.get('message').replace("'", '"') - if message[0] == '[': - errors = json.loads(message) - error_list = ''.join(['
  • {}
  • '.format(err) for err in errors]) - message = '''{} has following errors:
    -
      {}
    '''.format(docname, error_list) - else: - message = '{} - {}'.format(docname, message) - - frappe.msgprint( - message, - title=_('Bulk E-Invoice Generation Complete'), - indicator='red' - ) - -@frappe.whitelist() -def cancel_irns(docnames, reason, remark): - docnames = json.loads(docnames) or [] - - if len(docnames) < 10: - failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark) - frappe.local.message_log = [] - - if failures: - show_bulk_action_failure_message(failures) - - success = len(docnames) - len(failures) - frappe.msgprint( - _('{} e-invoices cancelled successfully').format(success), - title=_('Bulk E-Invoice Cancellation Complete') - ) - else: - enqueue_bulk_action(schedule_bulk_cancel_irn, docnames=docnames, reason=reason, remark=remark) - -def schedule_bulk_cancel_irn(docnames, reason, remark): - failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark) - frappe.local.message_log = [] - - frappe.publish_realtime("bulk_einvoice_cancellation_complete", { - "user": frappe.session.user, - "failures": failures, - "invoices": docnames - }) - -def enqueue_bulk_action(job, **kwargs): - check_scheduler_status() - - enqueue( - job, - **kwargs, - queue="long", - timeout=10000, - event="processing_bulk_einvoice_action", - now=frappe.conf.developer_mode or frappe.flags.in_test, - ) - - if job == schedule_bulk_generate_irn: - msg = _('E-Invoices will be generated in a background process.') - else: - msg = _('E-Invoices will be cancelled in a background process.') - - frappe.msgprint(msg, alert=1) - -def check_scheduler_status(): - if is_scheduler_inactive() and not frappe.flags.in_test: - frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive")) - -def job_already_enqueued(job_name): - enqueued_jobs = [d.get("job_name") for d in get_info()] - if job_name in enqueued_jobs: - return True From de488f68c02654cdd29d2148b1be9919c8d07274 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 26 Aug 2021 14:23:44 +0530 Subject: [PATCH 44/98] adding test cases --- erpnext/crm/doctype/prospect/test_prospect.py | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/prospect/test_prospect.py b/erpnext/crm/doctype/prospect/test_prospect.py index f266a50593..0fffad1939 100644 --- a/erpnext/crm/doctype/prospect/test_prospect.py +++ b/erpnext/crm/doctype/prospect/test_prospect.py @@ -1,8 +1,54 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe +import frappe import unittest +from frappe.utils import random_string +from erpnext.crm.doctype.lead.test_lead import make_lead +from erpnext.crm.doctype.lead.lead import add_lead_to_prospect + class TestProspect(unittest.TestCase): - pass + def test_add_lead_to_prospect_and_address_linking(self): + lead_doc = make_lead() + address_doc = make_address(address_title=lead_doc.name) + address_doc.append('links', { + "link_doctype": lead_doc.doctype, + "link_name": lead_doc.name + }) + address_doc.save() + prospect_doc = make_prospect() + add_lead_to_prospect(lead_doc.name, prospect_doc.name) + prospect_doc.reload() + lead_exists_in_prosoect = False + for rec in prospect_doc.get('prospect_lead'): + if rec.lead == lead_doc.name: + lead_exists_in_prosoect = True + self.assertEqual(lead_exists_in_prosoect, True) + address_doc.reload() + self.assertEqual(address_doc.has_link('Prospect', prospect_doc.name), True) + + +def make_prospect(**args): + args = frappe._dict(args) + + prospect_doc = frappe.get_doc({ + "doctype": "Prospect", + "company_name": args.company_name or "_Test Company {}".format(random_string(3)), + }).insert() + + return prospect_doc + +def make_address(**args): + args = frappe._dict(args) + + address_doc = frappe.get_doc({ + "doctype": "Address", + "address_title": args.address_title or "Address Title", + "address_type": args.address_type or "Billing", + "city": args.city or "Mumbai", + "address_line1": args.address_line1 or "Vidya Vihar West", + "country": args.country or "India" + }).insert() + + return address_doc From 45edfd9d50f6d2f86d268b94cad86abbfa815d2a Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 26 Aug 2021 15:53:52 +0530 Subject: [PATCH 45/98] fix: Remove duplicate `create_internal_customer` util - Introduced via https://github.com/frappe/erpnext/pull/27086 --- .../sales_invoice/test_sales_invoice.py | 28 +++---------------- .../selling/doctype/customer/test_customer.py | 24 ++++++++-------- .../delivery_note/test_delivery_note.py | 3 +- 3 files changed, 17 insertions(+), 38 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 43a5f5e2f6..e06a3bb5b1 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1832,7 +1832,8 @@ class TestSalesInvoice(unittest.TestCase): create_internal_customer( customer_name="_Test Internal Customer", - represents_company="_Test Company 1" + represents_company="_Test Company 1", + allowed_to_interact_with="Wind Power LLC" ) if not frappe.db.exists("Supplier", "_Test Internal Supplier"): @@ -1994,6 +1995,8 @@ class TestSalesInvoice(unittest.TestCase): def test_internal_transfer_gl_entry(self): ## Create internal transfer account + from erpnext.selling.doctype.customer.test_customer import create_internal_customer + account = create_account(account_name="Unrealized Profit", parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory") @@ -2551,29 +2554,6 @@ def get_taxes_and_charges(): "row_id": 1 }] -def create_internal_customer(customer_name, represents_company, allowed_to_interact_with): - if not frappe.db.exists("Customer", customer_name): - customer = frappe.get_doc({ - "customer_group": "_Test Customer Group", - "customer_name": customer_name, - "customer_type": "Individual", - "doctype": "Customer", - "territory": "_Test Territory", - "is_internal_customer": 1, - "represents_company": represents_company - }) - - customer.append("companies", { - "company": allowed_to_interact_with - }) - - customer.insert() - customer_name = customer.name - else: - customer_name = frappe.db.get_value("Customer", customer_name) - - return customer_name - def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with): if not frappe.db.exists("Supplier", supplier_name): supplier = frappe.get_doc({ diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index 908ba270b0..5b337313d3 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -353,27 +353,25 @@ def set_credit_limit(customer, company, credit_limit): }) customer.credit_limits[-1].db_insert() -def create_internal_customer(**args): - args = frappe._dict(args) - - customer_name = args.get("customer_name") or "_Test Internal Customer" - +def create_internal_customer(customer_name, represents_company, allowed_to_interact_with): if not frappe.db.exists("Customer", customer_name): customer = frappe.get_doc({ "doctype": "Customer", - "customer_group": args.customer_group or "_Test Customer Group", + "customer_group": "_Test Customer Group", "customer_name": customer_name, - "customer_type": args.customer_type or "Individual", - "territory": args.territory or "_Test Territory", + "customer_type": "Individual", + "territory": "_Test Territory", "is_internal_customer": 1, - "represents_company": args.represents_company or "_Test Company with perpetual inventory" + "represents_company": represents_company }) customer.append("companies", { - "company": args.allowed_company or "Wind Power LLC" + "company": allowed_to_interact_with }) - customer.insert() - return customer + customer.insert() + customer_name = customer.name else: - return frappe.get_cached_doc("Customer", customer_name) \ No newline at end of file + customer_name = frappe.db.get_value("Customer", customer_name) + + return customer_name \ No newline at end of file diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index dffc73020d..e6736b2bab 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -435,7 +435,8 @@ class TestDeliveryNote(unittest.TestCase): company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') customer = create_internal_customer( customer_name="_Test Internal Customer 2", - allowed_company="_Test Company with perpetual inventory" + represents_company="_Test Company with perpetual inventory", + allowed_to_interact_with="_Test Company with perpetual inventory" ) set_valuation_method("_Test Item", "FIFO") From 501b91a608a5da4f03c43751d5af3d05aa9f1352 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 26 Aug 2021 16:18:23 +0530 Subject: [PATCH 46/98] fix: internal customer util returns str not doc object --- erpnext/stock/doctype/delivery_note/test_delivery_note.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index e6736b2bab..b333a6b57e 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -433,7 +433,7 @@ class TestDeliveryNote(unittest.TestCase): from erpnext.selling.doctype.customer.test_customer import create_internal_customer company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') - customer = create_internal_customer( + customer_name = create_internal_customer( customer_name="_Test Internal Customer 2", represents_company="_Test Company with perpetual inventory", allowed_to_interact_with="_Test Company with perpetual inventory" @@ -454,7 +454,7 @@ class TestDeliveryNote(unittest.TestCase): dn = create_delivery_note( item_code="_Test Product Bundle Item", company="_Test Company with perpetual inventory", - customer=customer.name, + customer=customer_name, cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1", do_not_submit=True, From e7109c18db6df4ffce80c936911b6c98327cdd0f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 26 Aug 2021 16:40:45 +0530 Subject: [PATCH 47/98] fix: negative qty validation on stock reco cancellation (#27170) * test: negative stock validation on SR cancel * fix: negative stock setting ignored in stock reco In stock reconcilation cancellation negative stock setting is ignored as `db.get_value` is returning string `'0'` which is not casted to int/bool for further logic. This causes negative qty, which evantually gets caught by reposting but by design this should stop cancellation. * test: typo and minor refactor --- .../stock_reconciliation.py | 2 +- .../test_stock_reconciliation.py | 45 +++++++++++++++++-- erpnext/stock/stock_ledger.py | 6 +-- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index cda7c1d31a..24b7b9aeb6 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -390,7 +390,7 @@ class StockReconciliation(StockController): sl_entries = self.merge_similar_item_serial_nos(sl_entries) sl_entries.reverse() - allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock") + allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 94b006c894..e4381271ed 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -15,6 +15,7 @@ from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt +from erpnext.tests.utils import change_settings class TestStockReconciliation(unittest.TestCase): @@ -310,6 +311,7 @@ class TestStockReconciliation(unittest.TestCase): pr2.cancel() pr1.cancel() + @change_settings("Stock Settings", {"allow_negative_stock": 0}) def test_backdated_stock_reco_future_negative_stock(self): """ Test if a backdated stock reco causes future negative stock and is blocked. @@ -327,8 +329,6 @@ class TestStockReconciliation(unittest.TestCase): warehouse = "_Test Warehouse - _TC" create_item(item_code) - negative_stock_setting = frappe.db.get_single_value("Stock Settings", "allow_negative_stock") - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 0) pr1 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), -2)) @@ -348,11 +348,50 @@ class TestStockReconciliation(unittest.TestCase): self.assertRaises(NegativeStockError, sr3.submit) # teardown - frappe.db.set_value("Stock Settings", None, "allow_negative_stock", negative_stock_setting) sr3.cancel() dn2.cancel() pr1.cancel() + + @change_settings("Stock Settings", {"allow_negative_stock": 0}) + def test_backdated_stock_reco_cancellation_future_negative_stock(self): + """ + Test if a backdated stock reco cancellation that causes future negative stock is blocked. + ------------------------------------------- + Var | Doc | Qty | Balance + ------------------------------------------- + SR | Reco | 100 | 100 (posting date: today-1) (shouldn't be cancelled after DN) + DN | DN | 100 | 0 (posting date: today) + """ + from erpnext.stock.stock_ledger import NegativeStockError + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + frappe.db.commit() + + item_code = "Backdated-Reco-Cancellation-Item" + warehouse = "_Test Warehouse - _TC" + create_item(item_code) + + + sr = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=100, rate=100, + posting_date=add_days(nowdate(), -1)) + + dn = create_delivery_note(item_code=item_code, warehouse=warehouse, qty=100, rate=120, + posting_date=nowdate()) + + dn_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": dn.name, "is_cancelled": 0}, + "qty_after_transaction") + self.assertEqual(dn_balance, 0) + + # check if cancellation of stock reco is blocked + self.assertRaises(NegativeStockError, sr.cancel) + + repost_exists = bool(frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name})) + self.assertFalse(repost_exists, msg="Negative stock validation not working on reco cancellation") + + # teardown + frappe.db.rollback() + + def test_valid_batch(self): create_batch_item_with_batch("Testing Batch Item 1", "001") create_batch_item_with_batch("Testing Batch Item 2", "002") diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 27feec1d15..48fd7d3d26 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -955,7 +955,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, return valuation_rate -def update_qty_in_future_sle(args, allow_negative_stock=None): +def update_qty_in_future_sle(args, allow_negative_stock=False): """Recalculate Qty after Transaction in future SLEs based on current SLE.""" datetime_limit_condition = "" qty_shift = args.actual_qty @@ -1044,8 +1044,8 @@ def get_datetime_limit_condition(detail): ) )""" -def validate_negative_qty_in_future_sle(args, allow_negative_stock=None): - allow_negative_stock = allow_negative_stock \ +def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): + allow_negative_stock = cint(allow_negative_stock) \ or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) if (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation") and not allow_negative_stock: From c07dce940e31f84fb38ea6fd8d2d41bef619f904 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 26 Aug 2021 18:33:42 +0530 Subject: [PATCH 48/98] fix: don't allow BOM's item code at any level of child items (#27157) * refactor: bom recursion checking * fix: dont allow bom recursion if same item_code is added in child items at any level, it shouldn't be allowed. * test: add test for bom recursion * test: fix broken prodplan test using recursive bom * test: fix recursive bom in tests --- erpnext/manufacturing/doctype/bom/bom.py | 30 ++++++++------ erpnext/manufacturing/doctype/bom/test_bom.py | 40 +++++++++++++++++++ .../production_plan/test_production_plan.py | 9 +++-- .../doctype/work_order/test_work_order.py | 7 +++- 4 files changed, 68 insertions(+), 18 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 24f84e63b3..1d7d451cc8 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -447,25 +447,29 @@ class BOM(WebsiteGenerator): frappe.throw(_("Quantity required for Item {0} in row {1}").format(m.item_code, m.idx)) check_list.append(m) - def check_recursion(self, bom_list=[]): + def check_recursion(self, bom_list=None): """ Check whether recursion occurs in any bom""" + def _throw_error(bom_name): + frappe.throw(_("BOM recursion: {0} cannot be parent or child of {0}").format(bom_name)) + bom_list = self.traverse_tree() - bom_nos = frappe.get_all('BOM Item', fields=["bom_no"], - filters={'parent': ('in', bom_list), 'parenttype': 'BOM'}) + child_items = frappe.get_all('BOM Item', fields=["bom_no", "item_code"], + filters={'parent': ('in', bom_list), 'parenttype': 'BOM'}) or [] - raise_exception = False - if bom_nos and self.name in [d.bom_no for d in bom_nos]: - raise_exception = True + child_bom = {d.bom_no for d in child_items} + child_items_codes = {d.item_code for d in child_items} - if not raise_exception: - bom_nos = frappe.get_all('BOM Item', fields=["parent"], - filters={'bom_no': self.name, 'parenttype': 'BOM'}) + if self.name in child_bom: + _throw_error(self.name) - if self.name in [d.parent for d in bom_nos]: - raise_exception = True + if self.item in child_items_codes: + _throw_error(self.item) - if raise_exception: - frappe.throw(_("BOM recursion: {0} cannot be parent or child of {1}").format(self.name, self.name)) + bom_nos = frappe.get_all('BOM Item', fields=["parent"], + filters={'bom_no': self.name, 'parenttype': 'BOM'}) or [] + + if self.name in {d.parent for d in bom_nos}: + _throw_error(self.name) def traverse_tree(self, bom_list=None): def _get_children(bom_no): diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index b8f0db0de2..8408f10b18 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -280,6 +280,46 @@ class TestBOM(unittest.TestCase): self.assertEqual(reqd_item.qty, created_item.qty) self.assertEqual(reqd_item.exploded_qty, created_item.exploded_qty) + def test_bom_recursion_1st_level(self): + """BOM should not allow BOM item again in child""" + item_code = "_Test BOM Recursion" + make_item(item_code, {'is_stock_item': 1}) + + bom = frappe.new_doc("BOM") + bom.item = item_code + bom.append("items", frappe._dict(item_code=item_code)) + with self.assertRaises(frappe.ValidationError) as err: + bom.save() + + self.assertTrue("recursion" in str(err.exception).lower()) + frappe.delete_doc("BOM", bom.name, ignore_missing=True) + + def test_bom_recursion_transitive(self): + item1 = "_Test BOM Recursion" + item2 = "_Test BOM Recursion 2" + make_item(item1, {'is_stock_item': 1}) + make_item(item2, {'is_stock_item': 1}) + + bom1 = frappe.new_doc("BOM") + bom1.item = item1 + bom1.append("items", frappe._dict(item_code=item2)) + bom1.save() + bom1.submit() + + bom2 = frappe.new_doc("BOM") + bom2.item = item2 + bom2.append("items", frappe._dict(item_code=item1)) + + with self.assertRaises(frappe.ValidationError) as err: + bom2.save() + bom2.submit() + + self.assertTrue("recursion" in str(err.exception).lower()) + + bom1.cancel() + frappe.delete_doc("BOM", bom1.name, ignore_missing=True, force=True) + frappe.delete_doc("BOM", bom2.name, ignore_missing=True, force=True) + def test_bom_with_process_loss_item(self): fg_item_non_whole, fg_item_whole, bom_item = create_process_loss_bom_items() diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index a5b9ff845f..78028039c4 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -288,6 +288,7 @@ class TestProductionPlan(unittest.TestCase): self.assertEqual(warehouses, expected_warehouses) def test_get_sales_order_with_variant(self): + rm_item = create_item('PIV_RM', valuation_rate = 100) if not frappe.db.exists('Item', {"item_code": 'PIV'}): item = create_item('PIV', valuation_rate = 100) variant_settings = { @@ -300,20 +301,20 @@ class TestProductionPlan(unittest.TestCase): } item.update(variant_settings) item.save() - parent_bom = make_bom(item = 'PIV', raw_materials = ['PIV']) + parent_bom = make_bom(item = 'PIV', raw_materials = [rm_item.item_code]) if not frappe.db.exists('BOM', {"item": 'PIV'}): - parent_bom = make_bom(item = 'PIV', raw_materials = ['PIV']) + parent_bom = make_bom(item = 'PIV', raw_materials = [rm_item.item_code]) else: parent_bom = frappe.get_doc('BOM', {"item": 'PIV'}) if not frappe.db.exists('Item', {"item_code": 'PIV-RED'}): variant = create_variant("PIV", {"Colour": "Red"}) variant.save() - variant_bom = make_bom(item = variant.item_code, raw_materials = [variant.item_code]) + variant_bom = make_bom(item = variant.item_code, raw_materials = [rm_item.item_code]) else: variant = frappe.get_doc('Item', 'PIV-RED') if not frappe.db.exists('BOM', {"item": 'PIV-RED'}): - variant_bom = make_bom(item = variant.item_code, raw_materials = [variant.item_code]) + variant_bom = make_bom(item = variant.item_code, raw_materials = [rm_item.item_code]) """Testing when item variant has a BOM""" so = make_sales_order(item_code="PIV-RED", qty=5) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 3a334a530c..c0ed611534 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -675,13 +675,18 @@ class TestWorkOrder(unittest.TestCase): def test_valuation_rate_missing_on_make_stock_entry(self): item_name = 'Test Valuation Rate Missing' + rm_item = '_Test raw material item' make_item(item_name, { "is_stock_item": 1, "include_item_in_manufacturing": 1, }) + make_item('_Test raw material item', { + "is_stock_item": 1, + "include_item_in_manufacturing": 1, + }) if not frappe.db.get_value('BOM', {'item': item_name}): - make_bom(item=item_name, raw_materials=[item_name], rm_qty=1) + make_bom(item=item_name, raw_materials=[rm_item], rm_qty=1) company = "_Test Company with perpetual inventory" source_warehouse = create_warehouse("Test Valuation Rate Missing Warehouse", company=company) From 4b1f16542952563f13f9eb716604d763a35bf54c Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 26 Aug 2021 18:34:15 +0530 Subject: [PATCH 49/98] fix: Use reverse debit and credit to get net amount in GL Entries (#27174) * fix: Use reverse debit and credit to get net amount in GL Entries * fix: Remove unused import --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index abacee985c..5a183e2e51 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -755,9 +755,11 @@ class PaymentEntry(AccountsController): if self.payment_type in ('Pay', 'Internal Transfer'): dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit" + rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit" against = self.party or self.paid_from elif self.payment_type == 'Receive': dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit" + rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit" against = self.party or self.paid_to payment_or_advance_account = self.get_party_account_for_taxes() @@ -779,14 +781,13 @@ class PaymentEntry(AccountsController): "cost_center": d.cost_center }, account_currency, item=d)) - #Intentionally use -1 to get net values in party account if not d.included_in_paid_amount or self.advance_tax_account: gl_entries.append( self.get_gl_dict({ "account": payment_or_advance_account, "against": against, - dr_or_cr: -1 * tax_amount, - dr_or_cr + "_in_account_currency": -1 * base_tax_amount + rev_dr_or_cr: tax_amount, + rev_dr_or_cr + "_in_account_currency": -1 * base_tax_amount if account_currency==self.company_currency else d.tax_amount, "cost_center": self.cost_center, From a9852a54830a68f388956b972933dfac8aec78f9 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 26 Aug 2021 20:02:51 +0530 Subject: [PATCH 50/98] fix(ux): hide irrelevant fields for asset items (#26274) --- erpnext/stock/doctype/item/item.js | 3 +-- erpnext/stock/doctype/item/item.json | 25 ++++++++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index c587dd5c7e..3de35e6062 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -141,9 +141,8 @@ frappe.ui.form.on("Item", { is_fixed_asset: function(frm) { // set serial no to false & toggles its visibility frm.set_value('has_serial_no', 0); + frm.set_value('has_batch_no', 0); frm.toggle_enable(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset); - frm.toggle_reqd(['asset_category'], frm.doc.is_fixed_asset); - frm.toggle_display(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset); frm.call({ method: "set_asset_naming_series", diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index f662bbd1c7..db5caf9164 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -204,6 +204,7 @@ }, { "default": "0", + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "is_item_from_hub", "fieldtype": "Check", "label": "Is Item from Hub", @@ -238,6 +239,7 @@ { "bold": 1, "default": "1", + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "is_stock_item", "fieldtype": "Check", "label": "Maintain Stock", @@ -246,6 +248,7 @@ }, { "default": "1", + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "include_item_in_manufacturing", "fieldtype": "Check", "label": "Include Item In Manufacturing" @@ -282,6 +285,7 @@ "fieldname": "asset_category", "fieldtype": "Link", "label": "Asset Category", + "mandatory_depends_on": "is_fixed_asset", "options": "Asset Category" }, { @@ -434,8 +438,8 @@ }, { "collapsible": 1, - "collapsible_depends_on": "eval:doc.has_batch_no || doc.has_serial_no || doc.is_fixed_asset", - "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", + "collapsible_depends_on": "eval:doc.has_batch_no || doc.has_serial_no", + "depends_on": "eval:doc.is_stock_item", "fieldname": "serial_nos_and_batches", "fieldtype": "Section Break", "label": "Serial Nos and Batches" @@ -492,7 +496,7 @@ }, { "default": "0", - "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", + "depends_on": "eval:doc.is_stock_item", "fieldname": "has_serial_no", "fieldtype": "Check", "label": "Has Serial No", @@ -510,6 +514,7 @@ { "collapsible": 1, "collapsible_depends_on": "attributes", + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "variants_section", "fieldtype": "Section Break", "label": "Variants" @@ -540,6 +545,7 @@ "options": "Item Variant Attribute" }, { + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "defaults", "fieldtype": "Section Break", "label": "Sales, Purchase, Accounting Defaults" @@ -621,6 +627,7 @@ }, { "collapsible": 1, + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "supplier_details", "fieldtype": "Section Break", "label": "Supplier Details" @@ -668,6 +675,7 @@ }, { "collapsible": 1, + "default": "eval:!doc.is_fixed_asset", "fieldname": "sales_details", "fieldtype": "Section Break", "label": "Sales Details", @@ -761,6 +769,7 @@ }, { "collapsible": 1, + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "customer_details", "fieldtype": "Section Break", "label": "Customer Details" @@ -791,6 +800,7 @@ }, { "collapsible": 1, + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "inspection_criteria", "fieldtype": "Section Break", "label": "Inspection Criteria", @@ -861,6 +871,7 @@ }, { "collapsible": 1, + "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "website_section", "fieldtype": "Section Break", "label": "Website", @@ -987,7 +998,7 @@ }, { "collapsible": 1, - "depends_on": "eval:(!doc.is_item_from_hub)", + "depends_on": "eval:(!doc.is_item_from_hub && !doc.is_fixed_asset)", "fieldname": "hub_publishing_sb", "fieldtype": "Section Break", "label": "Hub Publishing Details" @@ -1021,7 +1032,7 @@ "read_only": 1 }, { - "depends_on": "eval:!doc.__islocal", + "depends_on": "eval:!doc.__islocal && !doc.is_fixed_asset", "fieldname": "over_delivery_receipt_allowance", "fieldtype": "Float", "label": "Over Delivery/Receipt Allowance (%)", @@ -1029,7 +1040,7 @@ "oldfieldtype": "Currency" }, { - "depends_on": "eval:!doc.__islocal", + "depends_on": "eval:!doc.__islocal && !doc.is_fixed_asset", "fieldname": "over_billing_allowance", "fieldtype": "Float", "label": "Over Billing Allowance (%)" @@ -1067,7 +1078,7 @@ "index_web_pages_for_search": 1, "links": [], "max_attachments": 1, - "modified": "2021-07-13 01:29:06.071827", + "modified": "2021-08-26 12:23:07.277077", "modified_by": "Administrator", "module": "Stock", "name": "Item", From ae55eab599094ff45f7d495f4a3ef3f00bd9e00e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 26 Aug 2021 20:43:06 +0530 Subject: [PATCH 51/98] chore: remove deprecated and empty QUnit tests (#27179) * chore: remove deprecated and empty QUnit tests * ci: fix UI test config Testing library was added on Frappe, in order to reuse command testing library has to be installed during setup process. --- .github/workflows/ui-tests.yml | 2 +- .../test_accounting_period.js | 23 ------------------- erpnext/accounts/doctype/bank/test_bank.js | 23 ------------------- .../doctype/bank_account/test_bank_account.js | 23 ------------------- .../test_bank_account_subtype.js | 23 ------------------- .../bank_guarantee/test_bank_guarantee.js | 23 ------------------- .../bank_transaction/test_bank_transaction.js | 23 ------------------- .../cash_flow_mapper/test_cash_flow_mapper.js | 23 ------------------- .../test_cash_flow_mapping.js | 23 ------------------- .../test_cash_flow_mapping_template.js | 23 ------------------- ...test_cash_flow_mapping_template_details.js | 23 ------------------- .../cashier_closing/test_cashier_closing.js | 23 ------------------- .../test_chart_of_accounts_importer.js | 23 ------------------- .../doctype/coupon_code/test_coupon_code.js | 23 ------------------- .../test_exchange_rate_revaluation.js | 23 ------------------- .../doctype/finance_book/test_finance_book.js | 23 ------------------- .../doctype/gl_entry/test_gl_entry.js | 23 ------------------- .../test_item_tax_template.js | 23 ------------------- .../test_loyalty_point_entry.js | 23 ------------------- .../loyalty_program/test_loyalty_program.js | 23 ------------------- .../test_opening_invoice_creation_tool.js | 23 ------------------- .../payment_order/test_payment_order.js | 23 ------------------- .../payment_request/test_payment_request.js | 23 ------------------- .../doctype/payment_term/test_payment_term.js | 23 ------------------- .../test_payment_terms_template.js | 23 ------------------- .../test_pos_closing_entry.js | 23 ------------------- .../doctype/pos_profile/test_pos_profile.js | 23 ------------------- .../pos_profile_user/test_pos_profile_user.js | 23 ------------------- .../doctype/pos_settings/test_pos_settings.js | 23 ------------------- .../share_transfer/test_share_transfer.js | 23 ------------------- .../doctype/share_type/test_share_type.js | 23 ------------------- .../doctype/shareholder/test_shareholder.js | 23 ------------------- .../test_subscription_invoice.js | 23 ------------------- .../test_subscription_plan.js | 23 ------------------- .../test_subscription_settings.js | 23 ------------------- .../doctype/tax_category/test_tax_category.js | 23 ------------------- .../doctype/tax_rule/test_tax_rule.js | 23 ------------------- .../test_tax_withholding_category.js | 23 ------------------- .../test_agriculture_analysis_criteria.js | 23 ------------------- .../agriculture_task/test_agriculture_task.js | 23 ------------------- .../plant_analysis/test_plant_analysis.js | 23 ------------------- .../soil_analysis/test_soil_analysis.js | 23 ------------------- .../doctype/weather/test_weather.js | 23 ------------------- erpnext/assets/doctype/asset/test_asset.js | 23 ------------------- .../asset_category/test_asset_category.js | 23 ------------------- .../test_asset_maintenance.js | 23 ------------------- .../test_asset_maintenance_log.js | 23 ------------------- .../test_asset_maintenance_team.js | 23 ------------------- .../asset_movement/test_asset_movement.js | 23 ------------------- .../doctype/asset_repair/test_asset_repair.js | 23 ------------------- .../test_asset_value_adjustment.js | 23 ------------------- .../assets/doctype/location/test_location.js | 23 ------------------- .../test_maintenance_team_member.js | 23 ------------------- erpnext/crm/doctype/contract/test_contract.js | 23 ------------------- .../test_contract_fulfilment_checklist.js | 23 ------------------- .../test_contract_template.js | 23 ------------------- .../market_segment/test_market_segment.js | 23 ------------------- .../opportunity_type/test_opportunity_type.js | 23 ------------------- .../doctype/sales_stage/test_sales_stage.js | 23 ------------------- .../academic_year/test_academic_year.js | 23 ------------------- .../education/doctype/article/test_article.js | 23 ------------------- .../content_question/test_content_question.js | 23 ------------------- .../course_activity/test_course_activity.js | 23 ------------------- .../course_content/test_course_content.js | 23 ------------------- .../test_course_enrollment.js | 23 ------------------- .../course_schedule/test_course_schedule.js | 23 ------------------- .../test_course_scheduling_tool.js | 23 ------------------- .../doctype/course_topic/test_course_topic.js | 23 ------------------- .../doctype/fee_category/test_fee_category.js | 23 ------------------- .../doctype/fee_schedule/test_fee_schedule.js | 23 ------------------- .../fee_structure/test_fee_structure.js | 23 ------------------- .../test_program_enrollment.js | 23 ------------------- .../test_program_enrollment_tool.js | 23 ------------------- .../doctype/question/test_question.js | 23 ------------------- erpnext/education/doctype/quiz/test_quiz.js | 23 ------------------- .../quiz_activity/test_quiz_activity.js | 23 ------------------- .../doctype/quiz_result/test_quiz_result.js | 23 ------------------- .../doctype/school_house/test_school_house.js | 23 ------------------- .../education/doctype/student/test_student.js | 23 ------------------- .../student_language/test_student_language.js | 23 ------------------- .../test_student_report_generation_tool.js | 23 ------------------- erpnext/education/doctype/topic/test_topic.js | 23 ------------------- .../topic_content/test_topic_content.js | 23 ------------------- .../test_amazon_mws_settings.js | 23 ------------------- .../test_gocardless_mandate.js | 23 ------------------- .../test_gocardless_settings.js | 23 ------------------- .../plaid_settings/test_plaid_settings.js | 23 ------------------- .../test_quickbooks_migrator.js | 23 ------------------- .../tally_migration/test_tally_migration.js | 23 ------------------- .../test_woocommerce_settings.js | 23 ------------------- .../doctype/antibiotic/test_antibiotic.js | 23 ------------------- .../appointment_type/test_appointment_type.js | 23 ------------------- .../test_clinical_procedure.js | 23 ------------------- .../test_clinical_procedure_template.js | 23 ------------------- .../doctype/complaint/test_complaint.js | 23 ------------------- .../doctype/diagnosis/test_diagnosis.js | 23 ------------------- .../doctype/dosage_form/test_dosage_form.js | 23 ------------------- .../doctype/fee_validity/test_fee_validity.js | 23 ------------------- .../test_healthcare_practitioner.js | 23 ------------------- .../test_healthcare_service_unit.js | 23 ------------------- .../test_healthcare_service_unit_type.js | 23 ------------------- .../test_healthcare_settings.js | 23 ------------------- .../inpatient_record/test_inpatient_record.js | 23 ------------------- .../doctype/lab_test/test_lab_test.js | 23 ------------------- .../lab_test_sample/test_lab_test_sample.js | 23 ------------------- .../test_lab_test_template.js | 23 ------------------- .../doctype/lab_test_uom/test_lab_test_uom.js | 23 ------------------- .../doctype/medical_code/test_medical_code.js | 23 ------------------- .../test_medical_code_standard.js | 23 ------------------- .../test_medical_department.js | 23 ------------------- .../doctype/organism/test_organism.js | 23 ------------------- .../doctype/patient/test_patient.js | 23 ------------------- .../test_patient_appointment.js | 23 ------------------- .../test_patient_encounter.js | 23 ------------------- .../test_patient_medical_record.js | 23 ------------------- .../test_practitioner_schedule.js | 23 ------------------- .../test_prescription_dosage.js | 23 ------------------- .../test_prescription_duration.js | 23 ------------------- .../test_sample_collection.js | 23 ------------------- .../doctype/sensitivity/test_sensitivity.js | 23 ------------------- .../doctype/vital_signs/test_vital_signs.js | 23 ------------------- .../doctype/hotel_room/test_hotel_room.js | 23 ------------------- .../test_hotel_room_package.js | 23 ------------------- .../test_hotel_room_pricing.js | 23 ------------------- .../test_hotel_room_pricing_package.js | 23 ------------------- .../test_hotel_room_reservation.js | 23 ------------------- .../hotel_room_type/test_hotel_room_type.js | 23 ------------------- .../hotel_settings/test_hotel_settings.js | 23 ------------------- .../test_attendance_request.js | 23 ------------------- erpnext/hr/doctype/branch/test_branch.js | 23 ------------------- .../test_compensatory_leave_request.js | 23 ------------------- .../test_daily_work_summary.js | 23 ------------------- .../hr/doctype/department/test_department.js | 23 ------------------- .../doctype/designation/test_designation.js | 23 ------------------- erpnext/hr/doctype/driver/test_driver.js | 23 ------------------- .../employee_advance/test_employee_advance.js | 23 ------------------- .../employee_grade/test_employee_grade.js | 23 ------------------- .../test_employee_health_insurance.js | 23 ------------------- .../test_employee_onboarding.js | 23 ------------------- .../test_employee_onboarding_template.js | 23 ------------------- .../test_employee_promotion.js | 23 ------------------- .../test_employee_separation.js | 23 ------------------- .../test_employee_separation_template.js | 23 ------------------- .../test_employee_transfer.js | 23 ------------------- .../test_employee_transfer_property.js | 23 ------------------- .../doctype/hr_settings/test_hr_settings.js | 23 ------------------- .../test_identification_document_type.js | 23 ------------------- .../test_job_applicant_source.js | 23 ------------------- .../leave_encashment/test_leave_encashment.js | 23 ------------------- .../doctype/leave_period/test_leave_period.js | 23 ------------------- .../doctype/leave_policy/test_leave_policy.js | 23 ------------------- .../test_leave_policy_detail.js | 23 ------------------- .../test_purpose_of_travel.js | 23 ------------------- .../shift_assignment/test_shift_assignment.js | 23 ------------------- .../shift_request/test_shift_request.js | 23 ------------------- .../hr/doctype/shift_type/test_shift_type.js | 23 ------------------- .../staffing_plan/test_staffing_plan.js | 23 ------------------- .../training_program/test_training_program.js | 23 ------------------- .../training_result/test_training_result.js | 23 ------------------- .../travel_request/test_travel_request.js | 23 ------------------- erpnext/hr/doctype/vehicle/test_vehicle.js | 23 ------------------- .../test_marketplace_settings.js | 23 ------------------- .../test_maintenance_schedule.js | 23 ------------------- .../blanket_order/test_blanket_order.js | 23 ------------------- .../bom_update_tool/test_bom_update_tool.js | 23 ------------------- .../doctype/job_card/test_job_card.js | 23 ------------------- .../test_manufacturing_settings.js | 23 ------------------- .../test_material_request_plan_item.js | 23 ------------------- .../production_plan/test_production_plan.js | 23 ------------------- .../doctype/routing/test_routing.js | 23 ------------------- .../test_certification_application.js | 23 ------------------- .../test_certified_consultant.js | 23 ------------------- .../doctype/chapter/test_chapter.js | 23 ------------------- .../doctype/donor_type/test_donor_type.js | 23 ------------------- .../doctype/membership/test_membership.js | 23 ------------------- .../test_additional_salary.js | 23 ------------------- .../test_employee_benefit_application.js | 23 ------------------- .../test_employee_benefit_claim.js | 23 ------------------- .../test_employee_incentive.js | 23 ------------------- .../test_employee_tax_exemption_category.js | 23 ------------------- ...test_employee_tax_exemption_declaration.js | 23 ------------------- ...employee_tax_exemption_proof_submission.js | 23 ------------------- ...est_employee_tax_exemption_sub_category.js | 23 ------------------- .../payroll_period/test_payroll_period.js | 23 ------------------- .../retention_bonus/test_retention_bonus.js | 23 ------------------- .../salary_component/test_salary_component.js | 23 ------------------- .../test_salary_structure_assignment.js | 23 ------------------- .../test_products_settings.js | 23 ------------------- .../projects/doctype/project/test_project.js | 23 ------------------- .../doctype/project_type/test_project_type.js | 23 ------------------- .../project_update/test_project_update.js | 23 ------------------- .../test_projects_settings.js | 23 ------------------- .../doctype/timesheet/test_timesheet.js | 23 ------------------- .../doctype/gst_hsn_code/test_gst_hsn_code.js | 23 ------------------- .../doctype/gst_settings/test_gst_settings.js | 23 ------------------- .../selling/doctype/customer/test_customer.js | 23 ------------------- .../doctype/sales_order/test_sales_order.js | 23 ------------------- .../test_sales_partner_type.js | 23 ------------------- .../test_currency_exchange.js | 23 ------------------- .../global_defaults/test_global_defaults.js | 23 ------------------- .../doctype/item_group/test_item_group.js | 23 ------------------- .../naming_series/test_naming_series.js | 23 ------------------- .../doctype/party_type/test_party_type.js | 23 ------------------- .../supplier_group/test_supplier_group.js | 23 ------------------- .../test_uom_conversion_factor.js | 23 ------------------- .../test_shopping_cart_settings.js | 23 ------------------- .../test_customs_tariff_number.js | 23 ------------------- .../test_delivery_settings.js | 23 ------------------- .../delivery_trip/test_delivery_trip.js | 23 ------------------- erpnext/stock/doctype/item/test_item.js | 23 ------------------- .../item_alternative/test_item_alternative.js | 23 ------------------- .../test_item_variant_settings.js | 23 ------------------- .../doctype/manufacturer/test_manufacturer.js | 23 ------------------- .../material_request/test_material_request.js | 23 ------------------- .../doctype/price_list/test_price_list.js | 23 ------------------- .../test_quality_inspection.js | 23 ------------------- .../test_quality_inspection_template.js | 23 ------------------- .../stock/doctype/serial_no/test_serial_no.js | 23 ------------------- .../stock_settings/test_stock_settings.js | 23 ------------------- .../doctype/uom_category/test_uom_category.js | 23 ------------------- .../variant_field/test_variant_field.js | 23 ------------------- .../support_settings/test_support_settings.js | 23 ------------------- 222 files changed, 1 insertion(+), 5084 deletions(-) delete mode 100644 erpnext/accounts/doctype/accounting_period/test_accounting_period.js delete mode 100644 erpnext/accounts/doctype/bank/test_bank.js delete mode 100644 erpnext/accounts/doctype/bank_account/test_bank_account.js delete mode 100644 erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.js delete mode 100644 erpnext/accounts/doctype/bank_guarantee/test_bank_guarantee.js delete mode 100644 erpnext/accounts/doctype/bank_transaction/test_bank_transaction.js delete mode 100644 erpnext/accounts/doctype/cash_flow_mapper/test_cash_flow_mapper.js delete mode 100644 erpnext/accounts/doctype/cash_flow_mapping/test_cash_flow_mapping.js delete mode 100644 erpnext/accounts/doctype/cash_flow_mapping_template/test_cash_flow_mapping_template.js delete mode 100644 erpnext/accounts/doctype/cash_flow_mapping_template_details/test_cash_flow_mapping_template_details.js delete mode 100644 erpnext/accounts/doctype/cashier_closing/test_cashier_closing.js delete mode 100644 erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.js delete mode 100644 erpnext/accounts/doctype/coupon_code/test_coupon_code.js delete mode 100644 erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.js delete mode 100644 erpnext/accounts/doctype/finance_book/test_finance_book.js delete mode 100644 erpnext/accounts/doctype/gl_entry/test_gl_entry.js delete mode 100644 erpnext/accounts/doctype/item_tax_template/test_item_tax_template.js delete mode 100644 erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.js delete mode 100644 erpnext/accounts/doctype/loyalty_program/test_loyalty_program.js delete mode 100644 erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.js delete mode 100644 erpnext/accounts/doctype/payment_order/test_payment_order.js delete mode 100644 erpnext/accounts/doctype/payment_request/test_payment_request.js delete mode 100644 erpnext/accounts/doctype/payment_term/test_payment_term.js delete mode 100644 erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.js delete mode 100644 erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js delete mode 100644 erpnext/accounts/doctype/pos_profile/test_pos_profile.js delete mode 100644 erpnext/accounts/doctype/pos_profile_user/test_pos_profile_user.js delete mode 100644 erpnext/accounts/doctype/pos_settings/test_pos_settings.js delete mode 100644 erpnext/accounts/doctype/share_transfer/test_share_transfer.js delete mode 100644 erpnext/accounts/doctype/share_type/test_share_type.js delete mode 100644 erpnext/accounts/doctype/shareholder/test_shareholder.js delete mode 100644 erpnext/accounts/doctype/subscription_invoice/test_subscription_invoice.js delete mode 100644 erpnext/accounts/doctype/subscription_plan/test_subscription_plan.js delete mode 100644 erpnext/accounts/doctype/subscription_settings/test_subscription_settings.js delete mode 100644 erpnext/accounts/doctype/tax_category/test_tax_category.js delete mode 100644 erpnext/accounts/doctype/tax_rule/test_tax_rule.js delete mode 100644 erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.js delete mode 100644 erpnext/agriculture/doctype/agriculture_analysis_criteria/test_agriculture_analysis_criteria.js delete mode 100644 erpnext/agriculture/doctype/agriculture_task/test_agriculture_task.js delete mode 100644 erpnext/agriculture/doctype/plant_analysis/test_plant_analysis.js delete mode 100644 erpnext/agriculture/doctype/soil_analysis/test_soil_analysis.js delete mode 100644 erpnext/agriculture/doctype/weather/test_weather.js delete mode 100644 erpnext/assets/doctype/asset/test_asset.js delete mode 100644 erpnext/assets/doctype/asset_category/test_asset_category.js delete mode 100644 erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.js delete mode 100644 erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.js delete mode 100644 erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.js delete mode 100644 erpnext/assets/doctype/asset_movement/test_asset_movement.js delete mode 100644 erpnext/assets/doctype/asset_repair/test_asset_repair.js delete mode 100644 erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.js delete mode 100644 erpnext/assets/doctype/location/test_location.js delete mode 100644 erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.js delete mode 100644 erpnext/crm/doctype/contract/test_contract.js delete mode 100644 erpnext/crm/doctype/contract_fulfilment_checklist/test_contract_fulfilment_checklist.js delete mode 100644 erpnext/crm/doctype/contract_template/test_contract_template.js delete mode 100644 erpnext/crm/doctype/market_segment/test_market_segment.js delete mode 100644 erpnext/crm/doctype/opportunity_type/test_opportunity_type.js delete mode 100644 erpnext/crm/doctype/sales_stage/test_sales_stage.js delete mode 100644 erpnext/education/doctype/academic_year/test_academic_year.js delete mode 100644 erpnext/education/doctype/article/test_article.js delete mode 100644 erpnext/education/doctype/content_question/test_content_question.js delete mode 100644 erpnext/education/doctype/course_activity/test_course_activity.js delete mode 100644 erpnext/education/doctype/course_content/test_course_content.js delete mode 100644 erpnext/education/doctype/course_enrollment/test_course_enrollment.js delete mode 100644 erpnext/education/doctype/course_schedule/test_course_schedule.js delete mode 100644 erpnext/education/doctype/course_scheduling_tool/test_course_scheduling_tool.js delete mode 100644 erpnext/education/doctype/course_topic/test_course_topic.js delete mode 100644 erpnext/education/doctype/fee_category/test_fee_category.js delete mode 100644 erpnext/education/doctype/fee_schedule/test_fee_schedule.js delete mode 100644 erpnext/education/doctype/fee_structure/test_fee_structure.js delete mode 100644 erpnext/education/doctype/program_enrollment/test_program_enrollment.js delete mode 100644 erpnext/education/doctype/program_enrollment_tool/test_program_enrollment_tool.js delete mode 100644 erpnext/education/doctype/question/test_question.js delete mode 100644 erpnext/education/doctype/quiz/test_quiz.js delete mode 100644 erpnext/education/doctype/quiz_activity/test_quiz_activity.js delete mode 100644 erpnext/education/doctype/quiz_result/test_quiz_result.js delete mode 100644 erpnext/education/doctype/school_house/test_school_house.js delete mode 100644 erpnext/education/doctype/student/test_student.js delete mode 100644 erpnext/education/doctype/student_language/test_student_language.js delete mode 100644 erpnext/education/doctype/student_report_generation_tool/test_student_report_generation_tool.js delete mode 100644 erpnext/education/doctype/topic/test_topic.js delete mode 100644 erpnext/education/doctype/topic_content/test_topic_content.js delete mode 100644 erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.js delete mode 100644 erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.js delete mode 100644 erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.js delete mode 100644 erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.js delete mode 100644 erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.js delete mode 100644 erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.js delete mode 100644 erpnext/erpnext_integrations/doctype/woocommerce_settings/test_woocommerce_settings.js delete mode 100644 erpnext/healthcare/doctype/antibiotic/test_antibiotic.js delete mode 100644 erpnext/healthcare/doctype/appointment_type/test_appointment_type.js delete mode 100644 erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.js delete mode 100644 erpnext/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.js delete mode 100644 erpnext/healthcare/doctype/complaint/test_complaint.js delete mode 100644 erpnext/healthcare/doctype/diagnosis/test_diagnosis.js delete mode 100644 erpnext/healthcare/doctype/dosage_form/test_dosage_form.js delete mode 100644 erpnext/healthcare/doctype/fee_validity/test_fee_validity.js delete mode 100644 erpnext/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.js delete mode 100644 erpnext/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.js delete mode 100644 erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.js delete mode 100644 erpnext/healthcare/doctype/healthcare_settings/test_healthcare_settings.js delete mode 100644 erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.js delete mode 100644 erpnext/healthcare/doctype/lab_test/test_lab_test.js delete mode 100644 erpnext/healthcare/doctype/lab_test_sample/test_lab_test_sample.js delete mode 100644 erpnext/healthcare/doctype/lab_test_template/test_lab_test_template.js delete mode 100644 erpnext/healthcare/doctype/lab_test_uom/test_lab_test_uom.js delete mode 100644 erpnext/healthcare/doctype/medical_code/test_medical_code.js delete mode 100644 erpnext/healthcare/doctype/medical_code_standard/test_medical_code_standard.js delete mode 100644 erpnext/healthcare/doctype/medical_department/test_medical_department.js delete mode 100644 erpnext/healthcare/doctype/organism/test_organism.js delete mode 100644 erpnext/healthcare/doctype/patient/test_patient.js delete mode 100644 erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.js delete mode 100644 erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.js delete mode 100644 erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.js delete mode 100644 erpnext/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.js delete mode 100644 erpnext/healthcare/doctype/prescription_dosage/test_prescription_dosage.js delete mode 100644 erpnext/healthcare/doctype/prescription_duration/test_prescription_duration.js delete mode 100644 erpnext/healthcare/doctype/sample_collection/test_sample_collection.js delete mode 100644 erpnext/healthcare/doctype/sensitivity/test_sensitivity.js delete mode 100644 erpnext/healthcare/doctype/vital_signs/test_vital_signs.js delete mode 100644 erpnext/hotels/doctype/hotel_room/test_hotel_room.js delete mode 100644 erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js delete mode 100644 erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js delete mode 100644 erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js delete mode 100644 erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js delete mode 100644 erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js delete mode 100644 erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js delete mode 100644 erpnext/hr/doctype/attendance_request/test_attendance_request.js delete mode 100644 erpnext/hr/doctype/branch/test_branch.js delete mode 100644 erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.js delete mode 100644 erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js delete mode 100644 erpnext/hr/doctype/department/test_department.js delete mode 100644 erpnext/hr/doctype/designation/test_designation.js delete mode 100644 erpnext/hr/doctype/driver/test_driver.js delete mode 100644 erpnext/hr/doctype/employee_advance/test_employee_advance.js delete mode 100644 erpnext/hr/doctype/employee_grade/test_employee_grade.js delete mode 100644 erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.js delete mode 100644 erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js delete mode 100644 erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js delete mode 100644 erpnext/hr/doctype/employee_promotion/test_employee_promotion.js delete mode 100644 erpnext/hr/doctype/employee_separation/test_employee_separation.js delete mode 100644 erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js delete mode 100644 erpnext/hr/doctype/employee_transfer/test_employee_transfer.js delete mode 100644 erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.js delete mode 100644 erpnext/hr/doctype/hr_settings/test_hr_settings.js delete mode 100644 erpnext/hr/doctype/identification_document_type/test_identification_document_type.js delete mode 100644 erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.js delete mode 100644 erpnext/hr/doctype/leave_encashment/test_leave_encashment.js delete mode 100644 erpnext/hr/doctype/leave_period/test_leave_period.js delete mode 100644 erpnext/hr/doctype/leave_policy/test_leave_policy.js delete mode 100644 erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.js delete mode 100644 erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.js delete mode 100644 erpnext/hr/doctype/shift_assignment/test_shift_assignment.js delete mode 100644 erpnext/hr/doctype/shift_request/test_shift_request.js delete mode 100644 erpnext/hr/doctype/shift_type/test_shift_type.js delete mode 100644 erpnext/hr/doctype/staffing_plan/test_staffing_plan.js delete mode 100644 erpnext/hr/doctype/training_program/test_training_program.js delete mode 100644 erpnext/hr/doctype/training_result/test_training_result.js delete mode 100644 erpnext/hr/doctype/travel_request/test_travel_request.js delete mode 100644 erpnext/hr/doctype/vehicle/test_vehicle.js delete mode 100644 erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.js delete mode 100644 erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.js delete mode 100644 erpnext/manufacturing/doctype/blanket_order/test_blanket_order.js delete mode 100644 erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js delete mode 100644 erpnext/manufacturing/doctype/job_card/test_job_card.js delete mode 100644 erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js delete mode 100644 erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.js delete mode 100644 erpnext/manufacturing/doctype/production_plan/test_production_plan.js delete mode 100644 erpnext/manufacturing/doctype/routing/test_routing.js delete mode 100644 erpnext/non_profit/doctype/certification_application/test_certification_application.js delete mode 100644 erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.js delete mode 100644 erpnext/non_profit/doctype/chapter/test_chapter.js delete mode 100644 erpnext/non_profit/doctype/donor_type/test_donor_type.js delete mode 100644 erpnext/non_profit/doctype/membership/test_membership.js delete mode 100644 erpnext/payroll/doctype/additional_salary/test_additional_salary.js delete mode 100644 erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.js delete mode 100644 erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.js delete mode 100644 erpnext/payroll/doctype/employee_incentive/test_employee_incentive.js delete mode 100644 erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.js delete mode 100644 erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.js delete mode 100644 erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.js delete mode 100644 erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js delete mode 100644 erpnext/payroll/doctype/payroll_period/test_payroll_period.js delete mode 100644 erpnext/payroll/doctype/retention_bonus/test_retention_bonus.js delete mode 100644 erpnext/payroll/doctype/salary_component/test_salary_component.js delete mode 100644 erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.js delete mode 100644 erpnext/portal/doctype/products_settings/test_products_settings.js delete mode 100644 erpnext/projects/doctype/project/test_project.js delete mode 100644 erpnext/projects/doctype/project_type/test_project_type.js delete mode 100644 erpnext/projects/doctype/project_update/test_project_update.js delete mode 100644 erpnext/projects/doctype/projects_settings/test_projects_settings.js delete mode 100644 erpnext/projects/doctype/timesheet/test_timesheet.js delete mode 100644 erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js delete mode 100644 erpnext/regional/doctype/gst_settings/test_gst_settings.js delete mode 100644 erpnext/selling/doctype/customer/test_customer.js delete mode 100644 erpnext/selling/doctype/sales_order/test_sales_order.js delete mode 100644 erpnext/selling/doctype/sales_partner_type/test_sales_partner_type.js delete mode 100644 erpnext/setup/doctype/currency_exchange/test_currency_exchange.js delete mode 100644 erpnext/setup/doctype/global_defaults/test_global_defaults.js delete mode 100644 erpnext/setup/doctype/item_group/test_item_group.js delete mode 100644 erpnext/setup/doctype/naming_series/test_naming_series.js delete mode 100644 erpnext/setup/doctype/party_type/test_party_type.js delete mode 100644 erpnext/setup/doctype/supplier_group/test_supplier_group.js delete mode 100644 erpnext/setup/doctype/uom_conversion_factor/test_uom_conversion_factor.js delete mode 100644 erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.js delete mode 100644 erpnext/stock/doctype/customs_tariff_number/test_customs_tariff_number.js delete mode 100644 erpnext/stock/doctype/delivery_settings/test_delivery_settings.js delete mode 100644 erpnext/stock/doctype/delivery_trip/test_delivery_trip.js delete mode 100644 erpnext/stock/doctype/item/test_item.js delete mode 100644 erpnext/stock/doctype/item_alternative/test_item_alternative.js delete mode 100644 erpnext/stock/doctype/item_variant_settings/test_item_variant_settings.js delete mode 100644 erpnext/stock/doctype/manufacturer/test_manufacturer.js delete mode 100644 erpnext/stock/doctype/material_request/test_material_request.js delete mode 100644 erpnext/stock/doctype/price_list/test_price_list.js delete mode 100644 erpnext/stock/doctype/quality_inspection/test_quality_inspection.js delete mode 100644 erpnext/stock/doctype/quality_inspection_template/test_quality_inspection_template.js delete mode 100644 erpnext/stock/doctype/serial_no/test_serial_no.js delete mode 100644 erpnext/stock/doctype/stock_settings/test_stock_settings.js delete mode 100644 erpnext/stock/doctype/uom_category/test_uom_category.js delete mode 100644 erpnext/stock/doctype/variant_field/test_variant_field.js delete mode 100644 erpnext/support/doctype/support_settings/test_support_settings.js diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 0be9bd8f87..0ece0d8ee6 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -95,7 +95,7 @@ jobs: run: cd ~/frappe-bench/ && bench --site test_site execute erpnext.setup.utils.before_tests - name: cypress pre-requisites - run: cd ~/frappe-bench/apps/frappe && yarn add cypress-file-upload@^5 --no-lockfile + run: cd ~/frappe-bench/apps/frappe && yarn add cypress-file-upload@^5 @testing-library/cypress@^8 --no-lockfile - name: Build Assets diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.js b/erpnext/accounts/doctype/accounting_period/test_accounting_period.js deleted file mode 100644 index 71ce5b8d04..0000000000 --- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Accounting Period", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Accounting Period - () => frappe.tests.make('Accounting Period', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/bank/test_bank.js b/erpnext/accounts/doctype/bank/test_bank.js deleted file mode 100644 index 9ec264415a..0000000000 --- a/erpnext/accounts/doctype/bank/test_bank.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Bank", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Bank - () => frappe.tests.make('Bank', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/bank_account/test_bank_account.js b/erpnext/accounts/doctype/bank_account/test_bank_account.js deleted file mode 100644 index c20a7990e8..0000000000 --- a/erpnext/accounts/doctype/bank_account/test_bank_account.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Bank Account", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Bank Account - () => frappe.tests.make('Bank Account', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.js b/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.js deleted file mode 100644 index f59999845a..0000000000 --- a/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Bank Account Subtype", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Bank Account Subtype - () => frappe.tests.make('Bank Account Subtype', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/bank_guarantee/test_bank_guarantee.js b/erpnext/accounts/doctype/bank_guarantee/test_bank_guarantee.js deleted file mode 100644 index 0c60920cf9..0000000000 --- a/erpnext/accounts/doctype/bank_guarantee/test_bank_guarantee.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Bank Guarantee", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Bank Guarantee - () => frappe.tests.make('Bank Guarantee', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.js deleted file mode 100644 index 305119e137..0000000000 --- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Bank Transaction", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Bank Transaction - () => frappe.tests.make('Bank Transaction', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/cash_flow_mapper/test_cash_flow_mapper.js b/erpnext/accounts/doctype/cash_flow_mapper/test_cash_flow_mapper.js deleted file mode 100644 index 12ca254c5a..0000000000 --- a/erpnext/accounts/doctype/cash_flow_mapper/test_cash_flow_mapper.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Cash Flow Mapper", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Cash Flow Mapper - () => frappe.tests.make('Cash Flow Mapper', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/cash_flow_mapping/test_cash_flow_mapping.js b/erpnext/accounts/doctype/cash_flow_mapping/test_cash_flow_mapping.js deleted file mode 100644 index 1970ca806d..0000000000 --- a/erpnext/accounts/doctype/cash_flow_mapping/test_cash_flow_mapping.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Cash Flow Mapping", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Cash Flow Mapping - () => frappe.tests.make('Cash Flow Mapping', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/cash_flow_mapping_template/test_cash_flow_mapping_template.js b/erpnext/accounts/doctype/cash_flow_mapping_template/test_cash_flow_mapping_template.js deleted file mode 100644 index 12546ce2e3..0000000000 --- a/erpnext/accounts/doctype/cash_flow_mapping_template/test_cash_flow_mapping_template.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Cash Flow Mapping Template", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Cash Flow Mapping Template - () => frappe.tests.make('Cash Flow Mapping Template', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/cash_flow_mapping_template_details/test_cash_flow_mapping_template_details.js b/erpnext/accounts/doctype/cash_flow_mapping_template_details/test_cash_flow_mapping_template_details.js deleted file mode 100644 index eecabda751..0000000000 --- a/erpnext/accounts/doctype/cash_flow_mapping_template_details/test_cash_flow_mapping_template_details.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Cash Flow Mapping Template Details", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Cash Flow Mapping Template Details - () => frappe.tests.make('Cash Flow Mapping Template Details', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.js b/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.js deleted file mode 100644 index a7fcc8d842..0000000000 --- a/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Cashier Closing", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Cashier Closing - () => frappe.tests.make('Cashier Closing', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.js b/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.js deleted file mode 100644 index b075a015d6..0000000000 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Chart of Accounts Importer", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Chart of Accounts Importer - () => frappe.tests.make('Chart of Accounts Importer', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/coupon_code/test_coupon_code.js b/erpnext/accounts/doctype/coupon_code/test_coupon_code.js deleted file mode 100644 index 460fedc97f..0000000000 --- a/erpnext/accounts/doctype/coupon_code/test_coupon_code.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Coupon Code", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Coupon Code - () => frappe.tests.make('Coupon Code', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.js deleted file mode 100644 index 57c6a7871d..0000000000 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Exchange Rate Revaluation", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Exchange Rate Revaluation - () => frappe.tests.make('Exchange Rate Revaluation', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/finance_book/test_finance_book.js b/erpnext/accounts/doctype/finance_book/test_finance_book.js deleted file mode 100644 index 9fb7d4fcc8..0000000000 --- a/erpnext/accounts/doctype/finance_book/test_finance_book.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Finance Book", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Finance Book - () => frappe.tests.make('Finance Book', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/gl_entry/test_gl_entry.js b/erpnext/accounts/doctype/gl_entry/test_gl_entry.js deleted file mode 100644 index 2986e5e4e3..0000000000 --- a/erpnext/accounts/doctype/gl_entry/test_gl_entry.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: GL Entry", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially('GL Entry', [ - // insert a new GL Entry - () => frappe.tests.make([ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/item_tax_template/test_item_tax_template.js b/erpnext/accounts/doctype/item_tax_template/test_item_tax_template.js deleted file mode 100644 index 6893499391..0000000000 --- a/erpnext/accounts/doctype/item_tax_template/test_item_tax_template.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Item Tax Template", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Item Tax Template - () => frappe.tests.make('Item Tax Template', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.js b/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.js deleted file mode 100644 index a916b67522..0000000000 --- a/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Loyalty Point Entry", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Loyalty Point Entry - () => frappe.tests.make('Loyalty Point Entry', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.js b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.js deleted file mode 100644 index 9321c14e1f..0000000000 --- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Loyalty Program", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Loyalty Program - () => frappe.tests.make('Loyalty Program', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.js deleted file mode 100644 index f95d0d8213..0000000000 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Opening Invoice Creation Tool", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Opening Invoice Creation Tool - () => frappe.tests.make('Opening Invoice Creation Tool', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/payment_order/test_payment_order.js b/erpnext/accounts/doctype/payment_order/test_payment_order.js deleted file mode 100644 index f63fc54521..0000000000 --- a/erpnext/accounts/doctype/payment_order/test_payment_order.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Payment Order", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Payment Order - () => frappe.tests.make('Payment Order', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.js b/erpnext/accounts/doctype/payment_request/test_payment_request.js deleted file mode 100644 index 070b595fc6..0000000000 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Payment Request", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Payment Request - () => frappe.tests.make('Payment Request', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/payment_term/test_payment_term.js b/erpnext/accounts/doctype/payment_term/test_payment_term.js deleted file mode 100644 index b26e42aa37..0000000000 --- a/erpnext/accounts/doctype/payment_term/test_payment_term.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Payment Term", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Payment Term - () => frappe.tests.make('Payment Term', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.js b/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.js deleted file mode 100644 index 494a0ed21f..0000000000 --- a/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Payment Terms Template", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Payment Terms Template - () => frappe.tests.make('Payment Terms Template', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js deleted file mode 100644 index 48109b159c..0000000000 --- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: POS Closing Entry", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new POS Closing Entry - () => frappe.tests.make('POS Closing Entry', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.js b/erpnext/accounts/doctype/pos_profile/test_pos_profile.js deleted file mode 100644 index 42e5b7f92f..0000000000 --- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: POS Profile", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially('POS Profile', [ - // insert a new POS Profile - () => frappe.tests.make([ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/pos_profile_user/test_pos_profile_user.js b/erpnext/accounts/doctype/pos_profile_user/test_pos_profile_user.js deleted file mode 100644 index 5449ab76a3..0000000000 --- a/erpnext/accounts/doctype/pos_profile_user/test_pos_profile_user.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: POS Profile User", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new POS Profile User - () => frappe.tests.make('POS Profile User', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/pos_settings/test_pos_settings.js b/erpnext/accounts/doctype/pos_settings/test_pos_settings.js deleted file mode 100644 index 639c94ed10..0000000000 --- a/erpnext/accounts/doctype/pos_settings/test_pos_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: POS Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new POS Settings - () => frappe.tests.make('POS Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/share_transfer/test_share_transfer.js b/erpnext/accounts/doctype/share_transfer/test_share_transfer.js deleted file mode 100644 index e5530fa0aa..0000000000 --- a/erpnext/accounts/doctype/share_transfer/test_share_transfer.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Share Transfer", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Share Transfer - () => frappe.tests.make('Share Transfer', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/share_type/test_share_type.js b/erpnext/accounts/doctype/share_type/test_share_type.js deleted file mode 100644 index 620afa2ba8..0000000000 --- a/erpnext/accounts/doctype/share_type/test_share_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Share Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Share Type - () => frappe.tests.make('Share Type', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/shareholder/test_shareholder.js b/erpnext/accounts/doctype/shareholder/test_shareholder.js deleted file mode 100644 index 61c53120ea..0000000000 --- a/erpnext/accounts/doctype/shareholder/test_shareholder.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Shareholder", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Shareholder - () => frappe.tests.make('Shareholder', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/subscription_invoice/test_subscription_invoice.js b/erpnext/accounts/doctype/subscription_invoice/test_subscription_invoice.js deleted file mode 100644 index 15d3df2a63..0000000000 --- a/erpnext/accounts/doctype/subscription_invoice/test_subscription_invoice.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Subscription Invoice", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Subscription Invoice - () => frappe.tests.make('Subscription Invoice', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/subscription_plan/test_subscription_plan.js b/erpnext/accounts/doctype/subscription_plan/test_subscription_plan.js deleted file mode 100644 index 3ceb9a6050..0000000000 --- a/erpnext/accounts/doctype/subscription_plan/test_subscription_plan.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Subscription Plan", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Subscription Plan - () => frappe.tests.make('Subscription Plan', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/subscription_settings/test_subscription_settings.js b/erpnext/accounts/doctype/subscription_settings/test_subscription_settings.js deleted file mode 100644 index 5a751ea99c..0000000000 --- a/erpnext/accounts/doctype/subscription_settings/test_subscription_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Subscription Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Subscription Settings - () => frappe.tests.make('Subscription Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/tax_category/test_tax_category.js b/erpnext/accounts/doctype/tax_category/test_tax_category.js deleted file mode 100644 index 5142456d76..0000000000 --- a/erpnext/accounts/doctype/tax_category/test_tax_category.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Tax Category", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Tax Category - () => frappe.tests.make('Tax Category', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/tax_rule/test_tax_rule.js b/erpnext/accounts/doctype/tax_rule/test_tax_rule.js deleted file mode 100644 index 72d177deff..0000000000 --- a/erpnext/accounts/doctype/tax_rule/test_tax_rule.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Tax Rule", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Tax Rule - () => frappe.tests.make('Tax Rule', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.js b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.js deleted file mode 100644 index eab98d4389..0000000000 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Tax Withholding Category", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Tax Withholding Category - () => frappe.tests.make('Tax Withholding Category', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/agriculture/doctype/agriculture_analysis_criteria/test_agriculture_analysis_criteria.js b/erpnext/agriculture/doctype/agriculture_analysis_criteria/test_agriculture_analysis_criteria.js deleted file mode 100644 index f70dcd2f32..0000000000 --- a/erpnext/agriculture/doctype/agriculture_analysis_criteria/test_agriculture_analysis_criteria.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Agriculture Analysis Criteria", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Agriculture Analysis Criteria - () => frappe.tests.make('Agriculture Analysis Criteria', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/agriculture/doctype/agriculture_task/test_agriculture_task.js b/erpnext/agriculture/doctype/agriculture_task/test_agriculture_task.js deleted file mode 100644 index a012c4b1ad..0000000000 --- a/erpnext/agriculture/doctype/agriculture_task/test_agriculture_task.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Agriculture Task", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Agriculture Task - () => frappe.tests.make('Agriculture Task', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/agriculture/doctype/plant_analysis/test_plant_analysis.js b/erpnext/agriculture/doctype/plant_analysis/test_plant_analysis.js deleted file mode 100644 index 786c0471a4..0000000000 --- a/erpnext/agriculture/doctype/plant_analysis/test_plant_analysis.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Plant Analysis", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Plant Analysis - () => frappe.tests.make('Plant Analysis', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/agriculture/doctype/soil_analysis/test_soil_analysis.js b/erpnext/agriculture/doctype/soil_analysis/test_soil_analysis.js deleted file mode 100644 index 29128eba27..0000000000 --- a/erpnext/agriculture/doctype/soil_analysis/test_soil_analysis.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Soil Analysis", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Soil Analysis - () => frappe.tests.make('Soil Analysis', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/agriculture/doctype/weather/test_weather.js b/erpnext/agriculture/doctype/weather/test_weather.js deleted file mode 100644 index b5009a4ccd..0000000000 --- a/erpnext/agriculture/doctype/weather/test_weather.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Weather", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Weather - () => frappe.tests.make('Weather', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/assets/doctype/asset/test_asset.js b/erpnext/assets/doctype/asset/test_asset.js deleted file mode 100644 index 6119e38217..0000000000 --- a/erpnext/assets/doctype/asset/test_asset.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Asset", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Asset - () => frappe.tests.make('Asset', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/assets/doctype/asset_category/test_asset_category.js b/erpnext/assets/doctype/asset_category/test_asset_category.js deleted file mode 100644 index 7e343b7519..0000000000 --- a/erpnext/assets/doctype/asset_category/test_asset_category.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Asset Category", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Asset Category - () => frappe.tests.make('Asset Category', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.js b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.js deleted file mode 100644 index f9b38a1020..0000000000 --- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Asset Maintenance", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Asset Maintenance - () => frappe.tests.make('Asset Maintenance', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.js b/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.js deleted file mode 100644 index 4e80184ea7..0000000000 --- a/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Asset Maintenance Log", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Asset Maintenance Log - () => frappe.tests.make('Asset Maintenance Log', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.js b/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.js deleted file mode 100644 index 41bf69623e..0000000000 --- a/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Asset Maintenance Team", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Asset Maintenance Team - () => frappe.tests.make('Asset Maintenance Team', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.js b/erpnext/assets/doctype/asset_movement/test_asset_movement.js deleted file mode 100644 index b9515763c4..0000000000 --- a/erpnext/assets/doctype/asset_movement/test_asset_movement.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Asset Movement", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Asset Movement - () => frappe.tests.make('Asset Movement', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.js b/erpnext/assets/doctype/asset_repair/test_asset_repair.js deleted file mode 100644 index 7424ffe2b8..0000000000 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Asset Repair", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Asset Repair - () => frappe.tests.make('Asset Repair', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.js b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.js deleted file mode 100644 index 32831c61d2..0000000000 --- a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Asset Value Adjustment", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Asset Value Adjustment - () => frappe.tests.make('Asset Value Adjustment', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/assets/doctype/location/test_location.js b/erpnext/assets/doctype/location/test_location.js deleted file mode 100644 index 3c06b63e82..0000000000 --- a/erpnext/assets/doctype/location/test_location.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Location", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Location - () => frappe.tests.make('Location', [ - // values to be set - { location_name: 'Basil Farm' } - ]), - () => { - assert.equal(cur_frm.doc.name, 'Basil Farm'); - }, - () => done() - ]); - -}); diff --git a/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.js b/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.js deleted file mode 100644 index d942e2a156..0000000000 --- a/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Maintenance Team Member", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Maintenance Team Member - () => frappe.tests.make('Maintenance Team Member', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/crm/doctype/contract/test_contract.js b/erpnext/crm/doctype/contract/test_contract.js deleted file mode 100644 index 4c77c3d649..0000000000 --- a/erpnext/crm/doctype/contract/test_contract.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Contract", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Contract - () => frappe.tests.make('Contract', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/crm/doctype/contract_fulfilment_checklist/test_contract_fulfilment_checklist.js b/erpnext/crm/doctype/contract_fulfilment_checklist/test_contract_fulfilment_checklist.js deleted file mode 100644 index 2a2d5e1bfc..0000000000 --- a/erpnext/crm/doctype/contract_fulfilment_checklist/test_contract_fulfilment_checklist.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Contract Fulfilment Checklist", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Contract Fulfilment Checklist - () => frappe.tests.make('Contract Fulfilment Checklist', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/crm/doctype/contract_template/test_contract_template.js b/erpnext/crm/doctype/contract_template/test_contract_template.js deleted file mode 100644 index 6aaddd7df4..0000000000 --- a/erpnext/crm/doctype/contract_template/test_contract_template.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Contract Template", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Contract Template - () => frappe.tests.make('Contract Template', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/crm/doctype/market_segment/test_market_segment.js b/erpnext/crm/doctype/market_segment/test_market_segment.js deleted file mode 100644 index aa4b868f93..0000000000 --- a/erpnext/crm/doctype/market_segment/test_market_segment.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Market Segment", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Market Segment - () => frappe.tests.make('Market Segment', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/crm/doctype/opportunity_type/test_opportunity_type.js b/erpnext/crm/doctype/opportunity_type/test_opportunity_type.js deleted file mode 100644 index 3a1ede94db..0000000000 --- a/erpnext/crm/doctype/opportunity_type/test_opportunity_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Opportunity Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Opportunity Type - () => frappe.tests.make('Opportunity Type', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/crm/doctype/sales_stage/test_sales_stage.js b/erpnext/crm/doctype/sales_stage/test_sales_stage.js deleted file mode 100644 index 807af1fd98..0000000000 --- a/erpnext/crm/doctype/sales_stage/test_sales_stage.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Sales Stage", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Sales Stage - () => frappe.tests.make('Sales Stage', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/academic_year/test_academic_year.js b/erpnext/education/doctype/academic_year/test_academic_year.js deleted file mode 100644 index 51e9cf307d..0000000000 --- a/erpnext/education/doctype/academic_year/test_academic_year.js +++ /dev/null @@ -1,23 +0,0 @@ -// Testing Setup Module in Education -QUnit.module('education'); - -QUnit.test('Test: Academic Year', function(assert){ - assert.expect(3); - let done = assert.async(); - frappe.run_serially([ - () => { - return frappe.tests.make('Academic Year', [ - {academic_year_name: '2016-17'}, - {year_start_date: '2016-07-20'}, - {year_end_date:'2017-06-20'}, - ]); - }, - - () => { - assert.ok(cur_frm.doc.academic_year_name=='2016-17'); - assert.ok(cur_frm.doc.year_start_date=='2016-07-20'); - assert.ok(cur_frm.doc.year_end_date=='2017-06-20'); - }, - () => done() - ]); -}); diff --git a/erpnext/education/doctype/article/test_article.js b/erpnext/education/doctype/article/test_article.js deleted file mode 100644 index 9dbf063e84..0000000000 --- a/erpnext/education/doctype/article/test_article.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Article", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Article - () => frappe.tests.make('Article', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/content_question/test_content_question.js b/erpnext/education/doctype/content_question/test_content_question.js deleted file mode 100644 index cc869a87fc..0000000000 --- a/erpnext/education/doctype/content_question/test_content_question.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Content Question", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Content Question - () => frappe.tests.make('Content Question', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/course_activity/test_course_activity.js b/erpnext/education/doctype/course_activity/test_course_activity.js deleted file mode 100644 index c89c89e5d3..0000000000 --- a/erpnext/education/doctype/course_activity/test_course_activity.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Course Activity", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Course Activity - () => frappe.tests.make('Course Activity', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/course_content/test_course_content.js b/erpnext/education/doctype/course_content/test_course_content.js deleted file mode 100644 index 786e67e9a3..0000000000 --- a/erpnext/education/doctype/course_content/test_course_content.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Course Content", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Course Content - () => frappe.tests.make('Course Content', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/course_enrollment/test_course_enrollment.js b/erpnext/education/doctype/course_enrollment/test_course_enrollment.js deleted file mode 100644 index 216cc30799..0000000000 --- a/erpnext/education/doctype/course_enrollment/test_course_enrollment.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Course Enrollment", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Course Enrollment - () => frappe.tests.make('Course Enrollment', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/course_schedule/test_course_schedule.js b/erpnext/education/doctype/course_schedule/test_course_schedule.js deleted file mode 100644 index 5cdb67be48..0000000000 --- a/erpnext/education/doctype/course_schedule/test_course_schedule.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Course Schedule", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Course Schedule - () => frappe.tests.make('Course Schedule', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/course_scheduling_tool/test_course_scheduling_tool.js b/erpnext/education/doctype/course_scheduling_tool/test_course_scheduling_tool.js deleted file mode 100644 index 4419d18116..0000000000 --- a/erpnext/education/doctype/course_scheduling_tool/test_course_scheduling_tool.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Course Scheduling Tool", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Course Scheduling Tool - () => frappe.tests.make('Course Scheduling Tool', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/course_topic/test_course_topic.js b/erpnext/education/doctype/course_topic/test_course_topic.js deleted file mode 100644 index d8d154fb9c..0000000000 --- a/erpnext/education/doctype/course_topic/test_course_topic.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Course Topic", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Course Topic - () => frappe.tests.make('Course Topic', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/fee_category/test_fee_category.js b/erpnext/education/doctype/fee_category/test_fee_category.js deleted file mode 100644 index a08ed33e8b..0000000000 --- a/erpnext/education/doctype/fee_category/test_fee_category.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Fee Category", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Fee Category - () => frappe.tests.make('Fee Category', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/fee_schedule/test_fee_schedule.js b/erpnext/education/doctype/fee_schedule/test_fee_schedule.js deleted file mode 100644 index d495b4ce7b..0000000000 --- a/erpnext/education/doctype/fee_schedule/test_fee_schedule.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Fee Schedule", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially('Fee Schedule', [ - // insert a new Fee Schedule - () => frappe.tests.make([ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/fee_structure/test_fee_structure.js b/erpnext/education/doctype/fee_structure/test_fee_structure.js deleted file mode 100644 index 61f41354c3..0000000000 --- a/erpnext/education/doctype/fee_structure/test_fee_structure.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Fee Structure", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Fee Structure - () => frappe.tests.make('Fee Structure', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/program_enrollment/test_program_enrollment.js b/erpnext/education/doctype/program_enrollment/test_program_enrollment.js deleted file mode 100644 index aea81a0714..0000000000 --- a/erpnext/education/doctype/program_enrollment/test_program_enrollment.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Program Enrollment", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Program Enrollment - () => frappe.tests.make('Program Enrollment', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/program_enrollment_tool/test_program_enrollment_tool.js b/erpnext/education/doctype/program_enrollment_tool/test_program_enrollment_tool.js deleted file mode 100644 index 8d55104a0f..0000000000 --- a/erpnext/education/doctype/program_enrollment_tool/test_program_enrollment_tool.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Program Enrollment Tool", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Program Enrollment Tool - () => frappe.tests.make('Program Enrollment Tool', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/question/test_question.js b/erpnext/education/doctype/question/test_question.js deleted file mode 100644 index 509939c6b5..0000000000 --- a/erpnext/education/doctype/question/test_question.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Question", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Question - () => frappe.tests.make('Question', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/quiz/test_quiz.js b/erpnext/education/doctype/quiz/test_quiz.js deleted file mode 100644 index 147d13952a..0000000000 --- a/erpnext/education/doctype/quiz/test_quiz.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Quiz", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Quiz - () => frappe.tests.make('Quiz', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/quiz_activity/test_quiz_activity.js b/erpnext/education/doctype/quiz_activity/test_quiz_activity.js deleted file mode 100644 index 94b5ab796a..0000000000 --- a/erpnext/education/doctype/quiz_activity/test_quiz_activity.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Quiz Activity", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Quiz Activity - () => frappe.tests.make('Quiz Activity', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/quiz_result/test_quiz_result.js b/erpnext/education/doctype/quiz_result/test_quiz_result.js deleted file mode 100644 index 43f53a1dc7..0000000000 --- a/erpnext/education/doctype/quiz_result/test_quiz_result.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Quiz Result", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Quiz Result - () => frappe.tests.make('Quiz Result', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/school_house/test_school_house.js b/erpnext/education/doctype/school_house/test_school_house.js deleted file mode 100644 index dde63ecc4c..0000000000 --- a/erpnext/education/doctype/school_house/test_school_house.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: School House", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new School House - () => frappe.tests.make('School House', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/student/test_student.js b/erpnext/education/doctype/student/test_student.js deleted file mode 100644 index e18d39aee0..0000000000 --- a/erpnext/education/doctype/student/test_student.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Student", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Student - () => frappe.tests.make('Student', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/student_language/test_student_language.js b/erpnext/education/doctype/student_language/test_student_language.js deleted file mode 100644 index 9b25569961..0000000000 --- a/erpnext/education/doctype/student_language/test_student_language.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Student Language", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Student Language - () => frappe.tests.make('Student Language', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/student_report_generation_tool/test_student_report_generation_tool.js b/erpnext/education/doctype/student_report_generation_tool/test_student_report_generation_tool.js deleted file mode 100644 index 10be092bb9..0000000000 --- a/erpnext/education/doctype/student_report_generation_tool/test_student_report_generation_tool.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Student Report Generation Tool", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Student Report Generation Tool - () => frappe.tests.make('Student Report Generation Tool', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/topic/test_topic.js b/erpnext/education/doctype/topic/test_topic.js deleted file mode 100644 index 4460b79478..0000000000 --- a/erpnext/education/doctype/topic/test_topic.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Topic", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Topic - () => frappe.tests.make('Topic', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/education/doctype/topic_content/test_topic_content.js b/erpnext/education/doctype/topic_content/test_topic_content.js deleted file mode 100644 index bf9a62d037..0000000000 --- a/erpnext/education/doctype/topic_content/test_topic_content.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Topic Content", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Topic Content - () => frappe.tests.make('Topic Content', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.js b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.js deleted file mode 100644 index 9c8990986e..0000000000 --- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Amazon MWS Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Amazon MWS Settings - () => frappe.tests.make('Amazon MWS Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.js b/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.js deleted file mode 100644 index caa9399eb6..0000000000 --- a/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: GoCardless Mandate", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new GoCardless Mandate - () => frappe.tests.make('GoCardless Mandate', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.js b/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.js deleted file mode 100644 index b6daad8de4..0000000000 --- a/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: GoCardless Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new GoCardless Settings - () => frappe.tests.make('GoCardless Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.js deleted file mode 100644 index dc91347336..0000000000 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Plaid Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Plaid Settings - () => frappe.tests.make('Plaid Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.js b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.js deleted file mode 100644 index b71d704807..0000000000 --- a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: QuickBooks Migrator", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new QuickBooks Migrator - () => frappe.tests.make('QuickBooks Migrator', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.js deleted file mode 100644 index 433c5e2cda..0000000000 --- a/erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Tally Migration", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Tally Migration - () => frappe.tests.make('Tally Migration', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/erpnext_integrations/doctype/woocommerce_settings/test_woocommerce_settings.js b/erpnext/erpnext_integrations/doctype/woocommerce_settings/test_woocommerce_settings.js deleted file mode 100644 index ea06ab2dc4..0000000000 --- a/erpnext/erpnext_integrations/doctype/woocommerce_settings/test_woocommerce_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Woocommerce Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Woocommerce Settings - () => frappe.tests.make('Woocommerce Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/antibiotic/test_antibiotic.js b/erpnext/healthcare/doctype/antibiotic/test_antibiotic.js deleted file mode 100644 index b92103d750..0000000000 --- a/erpnext/healthcare/doctype/antibiotic/test_antibiotic.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Antibiotic", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Antibiotic - () => frappe.tests.make('Antibiotic', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/appointment_type/test_appointment_type.js b/erpnext/healthcare/doctype/appointment_type/test_appointment_type.js deleted file mode 100644 index 93274e55c7..0000000000 --- a/erpnext/healthcare/doctype/appointment_type/test_appointment_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Appointment Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Appointment Type - () => frappe.tests.make('Appointment Type', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.js deleted file mode 100644 index 80ef3d55f2..0000000000 --- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Clinical Procedure", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Clinical Procedure - () => frappe.tests.make('Clinical Procedure', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.js b/erpnext/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.js deleted file mode 100644 index 1dde8b5d86..0000000000 --- a/erpnext/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Clinical Procedure Template", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Clinical Procedure Template - () => frappe.tests.make('Clinical Procedure Template', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/complaint/test_complaint.js b/erpnext/healthcare/doctype/complaint/test_complaint.js deleted file mode 100644 index 9ff44d8da4..0000000000 --- a/erpnext/healthcare/doctype/complaint/test_complaint.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Complaint", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Complaint - () => frappe.tests.make('Complaint', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/diagnosis/test_diagnosis.js b/erpnext/healthcare/doctype/diagnosis/test_diagnosis.js deleted file mode 100644 index cacfef5b17..0000000000 --- a/erpnext/healthcare/doctype/diagnosis/test_diagnosis.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Diagnosis", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Diagnosis - () => frappe.tests.make('Diagnosis', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/dosage_form/test_dosage_form.js b/erpnext/healthcare/doctype/dosage_form/test_dosage_form.js deleted file mode 100644 index ba54ab16fa..0000000000 --- a/erpnext/healthcare/doctype/dosage_form/test_dosage_form.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Dosage Form", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Dosage Form - () => frappe.tests.make('Dosage Form', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.js b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.js deleted file mode 100644 index 0ebb97438c..0000000000 --- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Fee Validity", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Fee Validity - () => frappe.tests.make('Fee Validity', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.js b/erpnext/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.js deleted file mode 100644 index 75aa208ec1..0000000000 --- a/erpnext/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Healthcare Practitioner", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Healthcare Practitioner - () => frappe.tests.make('Healthcare Practitioner', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.js b/erpnext/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.js deleted file mode 100644 index a67a411707..0000000000 --- a/erpnext/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Healthcare Service Unit", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Healthcare Service Unit - () => frappe.tests.make('Healthcare Service Unit', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.js b/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.js deleted file mode 100644 index 6db8f9e9c1..0000000000 --- a/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Healthcare Service Unit Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Healthcare Service Unit Type - () => frappe.tests.make('Healthcare Service Unit Type', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/healthcare_settings/test_healthcare_settings.js b/erpnext/healthcare/doctype/healthcare_settings/test_healthcare_settings.js deleted file mode 100644 index ca10925e59..0000000000 --- a/erpnext/healthcare/doctype/healthcare_settings/test_healthcare_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Healthcare Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Healthcare Settings - () => frappe.tests.make('Healthcare Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.js b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.js deleted file mode 100644 index 1ce9afa96d..0000000000 --- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Inpatient Record", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Inpatient Record - () => frappe.tests.make('Inpatient Record', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/lab_test/test_lab_test.js b/erpnext/healthcare/doctype/lab_test/test_lab_test.js deleted file mode 100644 index 57cb22b269..0000000000 --- a/erpnext/healthcare/doctype/lab_test/test_lab_test.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Lab Test", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Lab Test - () => frappe.tests.make('Lab Test', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/lab_test_sample/test_lab_test_sample.js b/erpnext/healthcare/doctype/lab_test_sample/test_lab_test_sample.js deleted file mode 100644 index ace60de752..0000000000 --- a/erpnext/healthcare/doctype/lab_test_sample/test_lab_test_sample.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Lab Test Sample", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Lab Test Sample - () => frappe.tests.make('Lab Test Sample', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/lab_test_template/test_lab_test_template.js b/erpnext/healthcare/doctype/lab_test_template/test_lab_test_template.js deleted file mode 100644 index 7c2ec8c348..0000000000 --- a/erpnext/healthcare/doctype/lab_test_template/test_lab_test_template.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Lab Test Template", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Lab Test Template - () => frappe.tests.make('Lab Test Template', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/lab_test_uom/test_lab_test_uom.js b/erpnext/healthcare/doctype/lab_test_uom/test_lab_test_uom.js deleted file mode 100644 index 1328dda282..0000000000 --- a/erpnext/healthcare/doctype/lab_test_uom/test_lab_test_uom.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Lab Test UOM", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Lab Test UOM - () => frappe.tests.make('Lab Test UOM', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/medical_code/test_medical_code.js b/erpnext/healthcare/doctype/medical_code/test_medical_code.js deleted file mode 100644 index 8cc7c40025..0000000000 --- a/erpnext/healthcare/doctype/medical_code/test_medical_code.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Medical Code", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Medical Code - () => frappe.tests.make('Medical Code', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/medical_code_standard/test_medical_code_standard.js b/erpnext/healthcare/doctype/medical_code_standard/test_medical_code_standard.js deleted file mode 100644 index 6ab6d531df..0000000000 --- a/erpnext/healthcare/doctype/medical_code_standard/test_medical_code_standard.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Medical Code Standard", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Medical Code Standard - () => frappe.tests.make('Medical Code Standard', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/medical_department/test_medical_department.js b/erpnext/healthcare/doctype/medical_department/test_medical_department.js deleted file mode 100644 index fdf49718dc..0000000000 --- a/erpnext/healthcare/doctype/medical_department/test_medical_department.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Medical Department", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Medical Department - () => frappe.tests.make('Medical Department', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/organism/test_organism.js b/erpnext/healthcare/doctype/organism/test_organism.js deleted file mode 100644 index d57e5536c6..0000000000 --- a/erpnext/healthcare/doctype/organism/test_organism.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Organism", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Organism - () => frappe.tests.make('Organism', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/patient/test_patient.js b/erpnext/healthcare/doctype/patient/test_patient.js deleted file mode 100644 index e1d9ecbd24..0000000000 --- a/erpnext/healthcare/doctype/patient/test_patient.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Patient", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially('Patient', [ - // insert a new Patient - () => frappe.tests.make([ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.js deleted file mode 100644 index 71fc177845..0000000000 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Patient Appointment", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Patient Appointment - () => frappe.tests.make('Patient Appointment', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.js deleted file mode 100644 index 1baabf7eef..0000000000 --- a/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Patient Encounter", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Patient Encounter - () => frappe.tests.make('Patient Encounter', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.js b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.js deleted file mode 100644 index 66dda09e25..0000000000 --- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Patient Medical Record", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Patient Medical Record - () => frappe.tests.make('Patient Medical Record', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.js b/erpnext/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.js deleted file mode 100644 index 32dac2c652..0000000000 --- a/erpnext/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Practitioner Schedule", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Practitioner Schedule - () => frappe.tests.make('Practitioner Schedule', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/prescription_dosage/test_prescription_dosage.js b/erpnext/healthcare/doctype/prescription_dosage/test_prescription_dosage.js deleted file mode 100644 index 009614ff5d..0000000000 --- a/erpnext/healthcare/doctype/prescription_dosage/test_prescription_dosage.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Prescription Dosage", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Prescription Dosage - () => frappe.tests.make('Prescription Dosage', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/prescription_duration/test_prescription_duration.js b/erpnext/healthcare/doctype/prescription_duration/test_prescription_duration.js deleted file mode 100644 index 4971e79198..0000000000 --- a/erpnext/healthcare/doctype/prescription_duration/test_prescription_duration.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Prescription Duration", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Prescription Duration - () => frappe.tests.make('Prescription Duration', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/sample_collection/test_sample_collection.js b/erpnext/healthcare/doctype/sample_collection/test_sample_collection.js deleted file mode 100644 index 2b4aed756b..0000000000 --- a/erpnext/healthcare/doctype/sample_collection/test_sample_collection.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Sample Collection", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Sample Collection - () => frappe.tests.make('Sample Collection', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/sensitivity/test_sensitivity.js b/erpnext/healthcare/doctype/sensitivity/test_sensitivity.js deleted file mode 100644 index c2cf406f96..0000000000 --- a/erpnext/healthcare/doctype/sensitivity/test_sensitivity.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Sensitivity", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Sensitivity - () => frappe.tests.make('Sensitivity', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/healthcare/doctype/vital_signs/test_vital_signs.js b/erpnext/healthcare/doctype/vital_signs/test_vital_signs.js deleted file mode 100644 index f4ab4466be..0000000000 --- a/erpnext/healthcare/doctype/vital_signs/test_vital_signs.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Vital Signs", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Vital Signs - () => frappe.tests.make('Vital Signs', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hotels/doctype/hotel_room/test_hotel_room.js b/erpnext/hotels/doctype/hotel_room/test_hotel_room.js deleted file mode 100644 index 8b2b83330f..0000000000 --- a/erpnext/hotels/doctype/hotel_room/test_hotel_room.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Hotel Room", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Hotel Room - () => frappe.tests.make('Hotel Room', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js b/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js deleted file mode 100644 index f1ebad41d4..0000000000 --- a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Hotel Room Package", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Hotel Room Package - () => frappe.tests.make('Hotel Room Package', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js b/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js deleted file mode 100644 index ba0d1fd3e9..0000000000 --- a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Hotel Room Pricing", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Hotel Room Pricing - () => frappe.tests.make('Hotel Room Pricing', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js b/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js deleted file mode 100644 index 73a561c408..0000000000 --- a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Hotel Room Pricing Package", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Hotel Room Pricing Package - () => frappe.tests.make('Hotel Room Pricing Package', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js b/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js deleted file mode 100644 index 2897139359..0000000000 --- a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Hotel Room Reservation", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Hotel Room Reservation - () => frappe.tests.make('Hotel Room Reservation', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js b/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js deleted file mode 100644 index e2dd5780e3..0000000000 --- a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Hotel Room Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Hotel Room Type - () => frappe.tests.make('Hotel Room Type', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js b/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js deleted file mode 100644 index bc0b7f8341..0000000000 --- a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Hotel Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Hotel Settings - () => frappe.tests.make('Hotel Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.js b/erpnext/hr/doctype/attendance_request/test_attendance_request.js deleted file mode 100644 index d40ec61b08..0000000000 --- a/erpnext/hr/doctype/attendance_request/test_attendance_request.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Attendance Request", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Attendance Request - () => frappe.tests.make('Attendance Request', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/branch/test_branch.js b/erpnext/hr/doctype/branch/test_branch.js deleted file mode 100644 index 82a6ae103e..0000000000 --- a/erpnext/hr/doctype/branch/test_branch.js +++ /dev/null @@ -1,23 +0,0 @@ -QUnit.module('hr'); - -QUnit.test("Test: Branch [HR]", function (assert) { - assert.expect(1); - let done = assert.async(); - - frappe.run_serially([ - // test branch creation - () => frappe.set_route("List", "Branch", "List"), - () => frappe.new_doc("Branch"), - () => frappe.timeout(1), - () => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(), - () => frappe.timeout(1), - () => cur_frm.set_value("branch", "Test Branch"), - - // save form - () => cur_frm.save(), - () => frappe.timeout(1), - () => assert.equal("Test Branch", cur_frm.doc.branch, - 'name of branch correctly saved'), - () => done() - ]); -}); diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.js b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.js deleted file mode 100644 index bebcaac400..0000000000 --- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Compensatory Leave Request", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Compensatory Leave Request - () => frappe.tests.make('Compensatory Leave Request', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js deleted file mode 100644 index 1533517147..0000000000 --- a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Daily Work Summary", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Daily Work Summary - () => frappe.tests.make('Daily Work Summary', [ - // values to be set - { key: 'value' } - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/department/test_department.js b/erpnext/hr/doctype/department/test_department.js deleted file mode 100644 index e73779c97c..0000000000 --- a/erpnext/hr/doctype/department/test_department.js +++ /dev/null @@ -1,23 +0,0 @@ -QUnit.module('hr'); - -QUnit.test("Test: Department [HR]", function (assert) { - assert.expect(1); - let done = assert.async(); - - frappe.run_serially([ - // test department creation - () => frappe.set_route("List", "Department", "List"), - () => frappe.new_doc("Department"), - () => frappe.timeout(1), - () => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(), - () => frappe.timeout(1), - () => cur_frm.set_value("department_name", "Test Department"), - () => cur_frm.set_value("leave_block_list", "Test Leave block list"), - // save form - () => cur_frm.save(), - () => frappe.timeout(1), - () => assert.equal("Test Department", cur_frm.doc.department_name, - 'name of department correctly saved'), - () => done() - ]); -}); diff --git a/erpnext/hr/doctype/designation/test_designation.js b/erpnext/hr/doctype/designation/test_designation.js deleted file mode 100644 index 00adf8293f..0000000000 --- a/erpnext/hr/doctype/designation/test_designation.js +++ /dev/null @@ -1,23 +0,0 @@ -QUnit.module('hr'); - -QUnit.test("Test: Designation [HR]", function (assert) { - assert.expect(1); - let done = assert.async(); - - frappe.run_serially([ - // test designation creation - () => frappe.set_route("List", "Designation", "List"), - () => frappe.new_doc("Designation"), - () => frappe.timeout(1), - () => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(), - () => frappe.timeout(1), - () => cur_frm.set_value("designation_name", "Test Designation"), - () => cur_frm.set_value("description", "This designation is just for testing."), - // save form - () => cur_frm.save(), - () => frappe.timeout(1), - () => assert.equal("Test Designation", cur_frm.doc.designation_name, - 'name of designation correctly saved'), - () => done() - ]); -}); diff --git a/erpnext/hr/doctype/driver/test_driver.js b/erpnext/hr/doctype/driver/test_driver.js deleted file mode 100644 index ff9f61e66a..0000000000 --- a/erpnext/hr/doctype/driver/test_driver.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Driver", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Driver - () => frappe.tests.make('Driver', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.js b/erpnext/hr/doctype/employee_advance/test_employee_advance.js deleted file mode 100644 index 1b9ec6f6d0..0000000000 --- a/erpnext/hr/doctype/employee_advance/test_employee_advance.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Advance", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Advance - () => frappe.tests.make('Employee Advance', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_grade/test_employee_grade.js b/erpnext/hr/doctype/employee_grade/test_employee_grade.js deleted file mode 100644 index d684fb2ad1..0000000000 --- a/erpnext/hr/doctype/employee_grade/test_employee_grade.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Grade", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Grade - () => frappe.tests.make('Employee Grade', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.js b/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.js deleted file mode 100644 index 245cb32971..0000000000 --- a/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Health Insurance", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Health Insurance - () => frappe.tests.make('Employee Health Insurance', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js deleted file mode 100644 index d15cef77dc..0000000000 --- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Onboarding", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Onboarding - () => frappe.tests.make('Employee Onboarding', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js b/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js deleted file mode 100644 index 10912edb6a..0000000000 --- a/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Onboarding Template", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Onboarding Template - () => frappe.tests.make('Employee Onboarding Template', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_promotion/test_employee_promotion.js b/erpnext/hr/doctype/employee_promotion/test_employee_promotion.js deleted file mode 100644 index 5f0a5baf81..0000000000 --- a/erpnext/hr/doctype/employee_promotion/test_employee_promotion.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Promotion", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Promotion - () => frappe.tests.make('Employee Promotion', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.js b/erpnext/hr/doctype/employee_separation/test_employee_separation.js deleted file mode 100644 index d6c635951f..0000000000 --- a/erpnext/hr/doctype/employee_separation/test_employee_separation.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Separation", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Separation - () => frappe.tests.make('Employee Separation', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js b/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js deleted file mode 100644 index 66fd450804..0000000000 --- a/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Separation Template", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Separation Template - () => frappe.tests.make('Employee Separation Template', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.js b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.js deleted file mode 100644 index 05a3e1a573..0000000000 --- a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Transfer", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Transfer - () => frappe.tests.make('Employee Transfer', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.js b/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.js deleted file mode 100644 index 00a334a63d..0000000000 --- a/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Transfer Property", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Transfer Property - () => frappe.tests.make('Employee Transfer Property', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/hr_settings/test_hr_settings.js b/erpnext/hr/doctype/hr_settings/test_hr_settings.js deleted file mode 100644 index f32640ba5c..0000000000 --- a/erpnext/hr/doctype/hr_settings/test_hr_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: HR Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new HR Settings - () => frappe.tests.make('HR Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/identification_document_type/test_identification_document_type.js b/erpnext/hr/doctype/identification_document_type/test_identification_document_type.js deleted file mode 100644 index 65879098e8..0000000000 --- a/erpnext/hr/doctype/identification_document_type/test_identification_document_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Identification Document Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Identification Document Type - () => frappe.tests.make('Identification Document Type', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.js b/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.js deleted file mode 100644 index c093928f32..0000000000 --- a/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Job Applicant Source", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Job Applicant Source - () => frappe.tests.make('Job Applicant Source', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.js b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.js deleted file mode 100644 index cafd9602cb..0000000000 --- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Leave Encashment", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Leave Encashment - () => frappe.tests.make('Leave Encashment', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.js b/erpnext/hr/doctype/leave_period/test_leave_period.js deleted file mode 100644 index ec0a809689..0000000000 --- a/erpnext/hr/doctype/leave_period/test_leave_period.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Leave Period", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Leave Period - () => frappe.tests.make('Leave Period', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/leave_policy/test_leave_policy.js b/erpnext/hr/doctype/leave_policy/test_leave_policy.js deleted file mode 100644 index 5404a63bed..0000000000 --- a/erpnext/hr/doctype/leave_policy/test_leave_policy.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Leave Policy", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Leave Policy - () => frappe.tests.make('Leave Policy', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.js b/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.js deleted file mode 100644 index 1c8995b796..0000000000 --- a/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Leave Policy Detail", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Leave Policy Detail - () => frappe.tests.make('Leave Policy Detail', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.js b/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.js deleted file mode 100644 index 936c21ccf0..0000000000 --- a/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Purpose of Travel", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Purpose of Travel - () => frappe.tests.make('Purpose of Travel', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.js b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.js deleted file mode 100644 index 7727287742..0000000000 --- a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Shift Assignment", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Shift Assignment - () => frappe.tests.make('Shift Assignment', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.js b/erpnext/hr/doctype/shift_request/test_shift_request.js deleted file mode 100644 index 9c8cd70020..0000000000 --- a/erpnext/hr/doctype/shift_request/test_shift_request.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Shift Request", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Shift Request - () => frappe.tests.make('Shift Request', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/shift_type/test_shift_type.js b/erpnext/hr/doctype/shift_type/test_shift_type.js deleted file mode 100644 index 846f9316f5..0000000000 --- a/erpnext/hr/doctype/shift_type/test_shift_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Shift Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Shift Type - () => frappe.tests.make('Shift Type', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.js b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.js deleted file mode 100644 index 64320bcd92..0000000000 --- a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Staffing Plan", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Staffing Plan - () => frappe.tests.make('Staffing Plan', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/training_program/test_training_program.js b/erpnext/hr/doctype/training_program/test_training_program.js deleted file mode 100644 index 3a62b2fa22..0000000000 --- a/erpnext/hr/doctype/training_program/test_training_program.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Training Program", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Training Program - () => frappe.tests.make('Training Program', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/training_result/test_training_result.js b/erpnext/hr/doctype/training_result/test_training_result.js deleted file mode 100644 index cb1d7fb27a..0000000000 --- a/erpnext/hr/doctype/training_result/test_training_result.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Training Result", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Training Result - () => frappe.tests.make('Training Result', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/travel_request/test_travel_request.js b/erpnext/hr/doctype/travel_request/test_travel_request.js deleted file mode 100644 index 7e64591823..0000000000 --- a/erpnext/hr/doctype/travel_request/test_travel_request.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Travel Request", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Travel Request - () => frappe.tests.make('Travel Request', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hr/doctype/vehicle/test_vehicle.js b/erpnext/hr/doctype/vehicle/test_vehicle.js deleted file mode 100644 index 4d40cce086..0000000000 --- a/erpnext/hr/doctype/vehicle/test_vehicle.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Vehicle", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Vehicle - () => frappe.tests.make('Vehicle', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.js b/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.js deleted file mode 100644 index fba3e098d4..0000000000 --- a/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Marketplace Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Marketplace Settings - () => frappe.tests.make('Marketplace Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.js deleted file mode 100644 index e0f05b1abb..0000000000 --- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Maintenance Schedule", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Maintenance Schedule - () => frappe.tests.make('Maintenance Schedule', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.js deleted file mode 100644 index 51a0d94841..0000000000 --- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Blanket Order", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Blanket Order - () => frappe.tests.make('Blanket Order', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js deleted file mode 100644 index d220df2824..0000000000 --- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: BOM Update Tool", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially('BOM Update Tool', [ - // insert a new BOM Update Tool - () => frappe.tests.make([ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.js b/erpnext/manufacturing/doctype/job_card/test_job_card.js deleted file mode 100644 index 5dc7805d22..0000000000 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Job Card", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Job Card - () => frappe.tests.make('Job Card', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js b/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js deleted file mode 100644 index 2b2589eddd..0000000000 --- a/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Manufacturing Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially('Manufacturing Settings', [ - // insert a new Manufacturing Settings - () => frappe.tests.make([ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.js b/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.js deleted file mode 100644 index 14c6e39384..0000000000 --- a/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Material Request Plan Item", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Material Request Plan Item - () => frappe.tests.make('Material Request Plan Item', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.js b/erpnext/manufacturing/doctype/production_plan/test_production_plan.js deleted file mode 100644 index ef7d64c92d..0000000000 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Production Plan", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Production Plan - () => frappe.tests.make('Production Plan', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/manufacturing/doctype/routing/test_routing.js b/erpnext/manufacturing/doctype/routing/test_routing.js deleted file mode 100644 index 6cb65494af..0000000000 --- a/erpnext/manufacturing/doctype/routing/test_routing.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Routing", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Routing - () => frappe.tests.make('Routing', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/non_profit/doctype/certification_application/test_certification_application.js b/erpnext/non_profit/doctype/certification_application/test_certification_application.js deleted file mode 100644 index 40e94864d4..0000000000 --- a/erpnext/non_profit/doctype/certification_application/test_certification_application.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Certification Application", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Certification Application - () => frappe.tests.make('Certification Application', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.js b/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.js deleted file mode 100644 index f6a72a4327..0000000000 --- a/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Certified Consultant", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Certified Consultant - () => frappe.tests.make('Certified Consultant', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/non_profit/doctype/chapter/test_chapter.js b/erpnext/non_profit/doctype/chapter/test_chapter.js deleted file mode 100644 index e30d6a5bf9..0000000000 --- a/erpnext/non_profit/doctype/chapter/test_chapter.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Chapter", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Chapter - () => frappe.tests.make('Chapter', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/non_profit/doctype/donor_type/test_donor_type.js b/erpnext/non_profit/doctype/donor_type/test_donor_type.js deleted file mode 100644 index 22dc18ed76..0000000000 --- a/erpnext/non_profit/doctype/donor_type/test_donor_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Donor Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Member - () => frappe.tests.make('Donor Type', [ - // values to be set - {donor_type: 'Test Organization'}, - ]), - () => { - assert.equal(cur_frm.doc.donor_type, 'Test Organization'); - }, - () => done() - ]); - -}); diff --git a/erpnext/non_profit/doctype/membership/test_membership.js b/erpnext/non_profit/doctype/membership/test_membership.js deleted file mode 100644 index 24c85c6157..0000000000 --- a/erpnext/non_profit/doctype/membership/test_membership.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Membership", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Membership - () => frappe.tests.make('Membership', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/additional_salary/test_additional_salary.js b/erpnext/payroll/doctype/additional_salary/test_additional_salary.js deleted file mode 100644 index c18e187585..0000000000 --- a/erpnext/payroll/doctype/additional_salary/test_additional_salary.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Additional Salary", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Additional Salary - () => frappe.tests.make('Additional Salary', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.js b/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.js deleted file mode 100644 index b355e1c436..0000000000 --- a/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Benefit Application", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Benefit Application - () => frappe.tests.make('Employee Benefit Application', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.js b/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.js deleted file mode 100644 index 3c808c0a56..0000000000 --- a/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Benefit Claim", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Benefit Claim - () => frappe.tests.make('Employee Benefit Claim', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.js b/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.js deleted file mode 100644 index 10bc03701f..0000000000 --- a/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Incentive", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Incentive - () => frappe.tests.make('Employee Incentive', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.js b/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.js deleted file mode 100644 index e0e43c32e3..0000000000 --- a/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Tax Exemption Category", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Tax Exemption Category - () => frappe.tests.make('Employee Tax Exemption Category', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.js b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.js deleted file mode 100644 index 274a3a3860..0000000000 --- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Tax Exemption Declaration", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Tax Exemption Declaration - () => frappe.tests.make('Employee Tax Exemption Declaration', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.js b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.js deleted file mode 100644 index cec7508728..0000000000 --- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Tax Exemption Proof Submission", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Tax Exemption Proof Submission - () => frappe.tests.make('Employee Tax Exemption Proof Submission', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js b/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js deleted file mode 100644 index 8a1a6d151d..0000000000 --- a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Employee Tax Exemption Sub Category", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Employee Tax Exemption Sub Category - () => frappe.tests.make('Employee Tax Exemption Sub Category', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/payroll_period/test_payroll_period.js b/erpnext/payroll/doctype/payroll_period/test_payroll_period.js deleted file mode 100644 index 8c4ded96f3..0000000000 --- a/erpnext/payroll/doctype/payroll_period/test_payroll_period.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Payroll Period", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Payroll Period - () => frappe.tests.make('Payroll Period', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.js b/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.js deleted file mode 100644 index a4b95d3cb1..0000000000 --- a/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Retention Bonus", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Retention Bonus - () => frappe.tests.make('Retention Bonus', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/salary_component/test_salary_component.js b/erpnext/payroll/doctype/salary_component/test_salary_component.js deleted file mode 100644 index c47d32d996..0000000000 --- a/erpnext/payroll/doctype/salary_component/test_salary_component.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Salary Component", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Salary Component - () => frappe.tests.make('Salary Component', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.js b/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.js deleted file mode 100644 index 2f52576c7a..0000000000 --- a/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Salary Structure Assignment", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Salary Structure Assignment - () => frappe.tests.make('Salary Structure Assignment', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/portal/doctype/products_settings/test_products_settings.js b/erpnext/portal/doctype/products_settings/test_products_settings.js deleted file mode 100644 index b7049b37e1..0000000000 --- a/erpnext/portal/doctype/products_settings/test_products_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Products Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Products Settings - () => frappe.tests.make('Products Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/projects/doctype/project/test_project.js b/erpnext/projects/doctype/project/test_project.js deleted file mode 100644 index 16494f62b6..0000000000 --- a/erpnext/projects/doctype/project/test_project.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Project", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Project - () => frappe.tests.make('Project', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/projects/doctype/project_type/test_project_type.js b/erpnext/projects/doctype/project_type/test_project_type.js deleted file mode 100644 index c2198c452c..0000000000 --- a/erpnext/projects/doctype/project_type/test_project_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Project Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially('Project Type', [ - // insert a new Project Type - () => frappe.tests.make([ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/projects/doctype/project_update/test_project_update.js b/erpnext/projects/doctype/project_update/test_project_update.js deleted file mode 100644 index bda510b921..0000000000 --- a/erpnext/projects/doctype/project_update/test_project_update.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Project Update", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Project Update - () => frappe.tests.make('Project Update', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/projects/doctype/projects_settings/test_projects_settings.js b/erpnext/projects/doctype/projects_settings/test_projects_settings.js deleted file mode 100644 index f6feaa4804..0000000000 --- a/erpnext/projects/doctype/projects_settings/test_projects_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Projects Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Projects Settings - () => frappe.tests.make('Projects Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.js b/erpnext/projects/doctype/timesheet/test_timesheet.js deleted file mode 100644 index c081d6f8ea..0000000000 --- a/erpnext/projects/doctype/timesheet/test_timesheet.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Timesheet", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Timesheet - () => frappe.tests.make('Timesheet', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js b/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js deleted file mode 100644 index 24c5fd355f..0000000000 --- a/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: GST HSN Code", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new GST HSN Code - () => frappe.tests.make('GST HSN Code', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/regional/doctype/gst_settings/test_gst_settings.js b/erpnext/regional/doctype/gst_settings/test_gst_settings.js deleted file mode 100644 index 00fcca6f32..0000000000 --- a/erpnext/regional/doctype/gst_settings/test_gst_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: GST Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new GST Settings - () => frappe.tests.make('GST Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/selling/doctype/customer/test_customer.js b/erpnext/selling/doctype/customer/test_customer.js deleted file mode 100644 index 65b81af32c..0000000000 --- a/erpnext/selling/doctype/customer/test_customer.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Customer", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Customer - () => frappe.tests.make('Customer', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.js b/erpnext/selling/doctype/sales_order/test_sales_order.js deleted file mode 100644 index 57ed19b696..0000000000 --- a/erpnext/selling/doctype/sales_order/test_sales_order.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Sales Order", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially('Sales Order', [ - // insert a new Sales Order - () => frappe.tests.make([ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/selling/doctype/sales_partner_type/test_sales_partner_type.js b/erpnext/selling/doctype/sales_partner_type/test_sales_partner_type.js deleted file mode 100644 index 3ed7b46e1d..0000000000 --- a/erpnext/selling/doctype/sales_partner_type/test_sales_partner_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Sales Partner Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Sales Partner Type - () => frappe.tests.make('Sales Partner Type', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.js b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.js deleted file mode 100644 index 19fde2e148..0000000000 --- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Currency Exchange", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Currency Exchange - () => frappe.tests.make('Currency Exchange', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/setup/doctype/global_defaults/test_global_defaults.js b/erpnext/setup/doctype/global_defaults/test_global_defaults.js deleted file mode 100644 index 33634eb0f8..0000000000 --- a/erpnext/setup/doctype/global_defaults/test_global_defaults.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Global Defaults", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Global Defaults - () => frappe.tests.make('Global Defaults', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/setup/doctype/item_group/test_item_group.js b/erpnext/setup/doctype/item_group/test_item_group.js deleted file mode 100644 index ea322e23d6..0000000000 --- a/erpnext/setup/doctype/item_group/test_item_group.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Item Group", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Item Group - () => frappe.tests.make('Item Group', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/setup/doctype/naming_series/test_naming_series.js b/erpnext/setup/doctype/naming_series/test_naming_series.js deleted file mode 100644 index 22b664b2e6..0000000000 --- a/erpnext/setup/doctype/naming_series/test_naming_series.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Naming Series", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Naming Series - () => frappe.tests.make('Naming Series', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/setup/doctype/party_type/test_party_type.js b/erpnext/setup/doctype/party_type/test_party_type.js deleted file mode 100644 index c97dbc58c8..0000000000 --- a/erpnext/setup/doctype/party_type/test_party_type.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Party Type", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Party Type - () => frappe.tests.make('Party Type', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/setup/doctype/supplier_group/test_supplier_group.js b/erpnext/setup/doctype/supplier_group/test_supplier_group.js deleted file mode 100644 index 976dd2cc4f..0000000000 --- a/erpnext/setup/doctype/supplier_group/test_supplier_group.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Supplier Group", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Supplier Group - () => frappe.tests.make('Supplier Group', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/setup/doctype/uom_conversion_factor/test_uom_conversion_factor.js b/erpnext/setup/doctype/uom_conversion_factor/test_uom_conversion_factor.js deleted file mode 100644 index afcf74ccb4..0000000000 --- a/erpnext/setup/doctype/uom_conversion_factor/test_uom_conversion_factor.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: UOM Conversion Factor", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new UOM Conversion Factor - () => frappe.tests.make('UOM Conversion Factor', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.js b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.js deleted file mode 100644 index c8485e73fa..0000000000 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Shopping Cart Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Shopping Cart Settings - () => frappe.tests.make('Shopping Cart Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/stock/doctype/customs_tariff_number/test_customs_tariff_number.js b/erpnext/stock/doctype/customs_tariff_number/test_customs_tariff_number.js deleted file mode 100644 index 85812d6973..0000000000 --- a/erpnext/stock/doctype/customs_tariff_number/test_customs_tariff_number.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Customs Tariff Number", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Customs Tariff Number - () => frappe.tests.make('Customs Tariff Number', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/stock/doctype/delivery_settings/test_delivery_settings.js b/erpnext/stock/doctype/delivery_settings/test_delivery_settings.js deleted file mode 100644 index 22977c08f7..0000000000 --- a/erpnext/stock/doctype/delivery_settings/test_delivery_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Delivery Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Delivery Settings - () => frappe.tests.make('Delivery Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.js b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.js deleted file mode 100644 index b6d6d1af64..0000000000 --- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Delivery Trip", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Delivery Trip - () => frappe.tests.make('Delivery Trip', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/stock/doctype/item/test_item.js b/erpnext/stock/doctype/item/test_item.js deleted file mode 100644 index af44278a59..0000000000 --- a/erpnext/stock/doctype/item/test_item.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Item", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Item - () => frappe.tests.make('Item', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.js b/erpnext/stock/doctype/item_alternative/test_item_alternative.js deleted file mode 100644 index 87318499fe..0000000000 --- a/erpnext/stock/doctype/item_alternative/test_item_alternative.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Item Alternative", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Item Alternative - () => frappe.tests.make('Item Alternative', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/stock/doctype/item_variant_settings/test_item_variant_settings.js b/erpnext/stock/doctype/item_variant_settings/test_item_variant_settings.js deleted file mode 100644 index 3b3bf94f37..0000000000 --- a/erpnext/stock/doctype/item_variant_settings/test_item_variant_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Item Variant Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Item Variant Settings - () => frappe.tests.make('Item Variant Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/stock/doctype/manufacturer/test_manufacturer.js b/erpnext/stock/doctype/manufacturer/test_manufacturer.js deleted file mode 100644 index 0254a367cc..0000000000 --- a/erpnext/stock/doctype/manufacturer/test_manufacturer.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Manufacturer", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially('Manufacturer', [ - // insert a new Manufacturer - () => frappe.tests.make([ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/stock/doctype/material_request/test_material_request.js b/erpnext/stock/doctype/material_request/test_material_request.js deleted file mode 100644 index 793cad0f3b..0000000000 --- a/erpnext/stock/doctype/material_request/test_material_request.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Material Request", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially('Material Request', [ - // insert a new Material Request - () => frappe.tests.make([ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/stock/doctype/price_list/test_price_list.js b/erpnext/stock/doctype/price_list/test_price_list.js deleted file mode 100644 index fe4e07b3bd..0000000000 --- a/erpnext/stock/doctype/price_list/test_price_list.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Price List", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Price List - () => frappe.tests.make('Price List', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.js b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.js deleted file mode 100644 index 327484e6cc..0000000000 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Quality Inspection", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Quality Inspection - () => frappe.tests.make('Quality Inspection', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/stock/doctype/quality_inspection_template/test_quality_inspection_template.js b/erpnext/stock/doctype/quality_inspection_template/test_quality_inspection_template.js deleted file mode 100644 index 879c262ed2..0000000000 --- a/erpnext/stock/doctype/quality_inspection_template/test_quality_inspection_template.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Quality Inspection Template", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Quality Inspection Template - () => frappe.tests.make('Quality Inspection Template', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.js b/erpnext/stock/doctype/serial_no/test_serial_no.js deleted file mode 100644 index bf8293257c..0000000000 --- a/erpnext/stock/doctype/serial_no/test_serial_no.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Serial No", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Serial No - () => frappe.tests.make('Serial No', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/stock/doctype/stock_settings/test_stock_settings.js b/erpnext/stock/doctype/stock_settings/test_stock_settings.js deleted file mode 100644 index 57d9fc61aa..0000000000 --- a/erpnext/stock/doctype/stock_settings/test_stock_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Stock Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Stock Settings - () => frappe.tests.make('Stock Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/stock/doctype/uom_category/test_uom_category.js b/erpnext/stock/doctype/uom_category/test_uom_category.js deleted file mode 100644 index 4b5972ea71..0000000000 --- a/erpnext/stock/doctype/uom_category/test_uom_category.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: UOM Category", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new UOM Category - () => frappe.tests.make('UOM Category', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/stock/doctype/variant_field/test_variant_field.js b/erpnext/stock/doctype/variant_field/test_variant_field.js deleted file mode 100644 index 2600a10fe0..0000000000 --- a/erpnext/stock/doctype/variant_field/test_variant_field.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Variant Field", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Variant Field - () => frappe.tests.make('Variant Field', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/support/doctype/support_settings/test_support_settings.js b/erpnext/support/doctype/support_settings/test_support_settings.js deleted file mode 100644 index 0787306c3e..0000000000 --- a/erpnext/support/doctype/support_settings/test_support_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Support Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Support Settings - () => frappe.tests.make('Support Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); From f91faac7cd6f2c885729681d947b7a8bb9e43f0e Mon Sep 17 00:00:00 2001 From: Lovin Maxwell Date: Thu, 26 Aug 2021 18:24:02 +0300 Subject: [PATCH 52/98] fix: Pricing Rule on Transaction Based on Coupon (#26949) --- erpnext/accounts/doctype/pricing_rule/utils.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 94abf3b3c0..5467cb0bc5 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -475,7 +475,20 @@ def apply_pricing_rule_on_transaction(doc): frappe.msgprint(_("User has not applied rule on the invoice {0}") .format(doc.name)) else: - doc.set(field, d.get(pr_field)) + if not d.coupon_code_based: + doc.set(field, d.get(pr_field)) + elif doc.get('coupon_code'): + # coupon code based pricing rule + coupon_code_pricing_rule = frappe.db.get_value('Coupon Code', doc.get('coupon_code'), 'pricing_rule') + if coupon_code_pricing_rule == d.name: + # if selected coupon code is linked with pricing rule + doc.set(field, d.get(pr_field)) + else: + # reset discount if not linked + doc.set(field, 0) + else: + # if coupon code based but no coupon code selected + doc.set(field, 0) doc.calculate_taxes_and_totals() elif d.price_or_product_discount == 'Product': From 422f67a4c9d5b7ce6de42c0ca6e68382d930dabb Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 26 Aug 2021 21:40:48 +0530 Subject: [PATCH 53/98] refactor: update manufacturing module onboarding (#26945) * refactor: updated manufacturing onboarding cards * refactor: added form tours * refactor: updated form tours and card descriptions * Fix: Case of description Co-authored-by: Nabin Hait --- erpnext/manufacturing/doctype/bom/bom.js | 25 +++++++- .../doctype/operation/operation.js | 18 ++++++ .../production_plan/production_plan.js | 33 ++++++++++ .../manufacturing/doctype/routing/routing.js | 14 +++++ .../doctype/work_order/work_order.js | 60 +++++++++++++++++++ .../doctype/workstation/workstation.js | 27 ++++++++- .../manufacturing/manufacturing.json | 14 ++--- .../create_bom/create_bom.json | 8 ++- .../create_product/create_product.json | 10 ++-- .../create_raw_materials.json | 2 +- .../explore_manufacturing_settings.json | 8 ++- .../onboarding_step/operation/operation.json | 10 ++-- .../production_planning.json | 21 +++++++ .../onboarding_step/routing/routing.json | 21 +++++++ .../work_order/work_order.json | 8 ++- .../workstation/workstation.json | 10 ++-- erpnext/stock/doctype/item/item.js | 45 ++++++++++++++ 17 files changed, 303 insertions(+), 31 deletions(-) create mode 100644 erpnext/manufacturing/onboarding_step/production_planning/production_planning.json create mode 100644 erpnext/manufacturing/onboarding_step/routing/routing.json diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 1481f1c54f..05123d5f85 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -661,6 +661,29 @@ frappe.ui.form.on("BOM", "with_operations", function(frm) { } }); +frappe.tour['BOM'] = [ + { + fieldname: "item", + title: "Item", + description: __("Select the Item to be manufactured. The Item name, UoM, Company, and Currency will be fetched automatically.") + }, + { + fieldname: "quantity", + title: "Quantity", + description: __("Enter the quantity of the Item that will be manufactured from this Bill of Materials.") + }, + { + fieldname: "with_operations", + title: "With Operations", + description: __("To add Operations tick the 'With Operations' checkbox.") + }, + { + fieldname: "items", + title: "Raw Materials", + description: __("Select the raw materials (Items) required to manufacture the Item") + } +]; + frappe.ui.form.on("BOM Scrap Item", { item_code(frm, cdt, cdn) { const { item_code } = locals[cdt][cdn]; @@ -691,4 +714,4 @@ function trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code) { __("Set Process Loss Item Quantity"), __("Set Quantity") ); -} +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/operation/operation.js b/erpnext/manufacturing/doctype/operation/operation.js index 2936e33b11..95643bf49f 100644 --- a/erpnext/manufacturing/doctype/operation/operation.js +++ b/erpnext/manufacturing/doctype/operation/operation.js @@ -12,3 +12,21 @@ frappe.ui.form.on('Operation', { }); } }); + +frappe.tour['Operation'] = [ + { + fieldname: "__newname", + title: "Operation Name", + description: __("Enter a name for the Operation, for example, Cutting.") + }, + { + fieldname: "workstation", + title: "Default Workstation", + description: __("Select the Default Workstation where the Operation will be performed. This will be fetched in BOMs and Work Orders.") + }, + { + fieldname: "sub_operations", + title: "Sub Operations", + description: __("If an operation is divided into sub operations, they can be added here.") + } +]; \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index d198a6962a..847004fc47 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -428,3 +428,36 @@ cur_frm.fields_dict['sales_orders'].grid.get_field("sales_order").get_query = fu ] } }; + +frappe.tour['Production Plan'] = [ + { + fieldname: "get_items_from", + title: "Get Items From", + description: __("Select whether to get items from a Sales Order or a Material Request. For now select Sales Order.\n A Production Plan can also be created manually where you can select the Items to manufacture.") + }, + { + fieldname: "get_sales_orders", + title: "Get Sales Orders", + description: __("Click on Get Sales Orders to fetch sales orders based on the above filters.") + }, + { + fieldname: "get_items", + title: "Get Finished Goods for Manufacture", + description: __("Click on 'Get Finished Goods for Manufacture' to fetch the items from the above Sales Orders. Items only for which a BOM is present will be fetched.") + }, + { + fieldname: "po_items", + title: "Finished Goods", + description: __("On expanding a row in the Items to Manufacture table, you'll see an option to 'Include Exploded Items'. Ticking this includes raw materials of the sub-assembly items in the production process.") + }, + { + fieldname: "include_non_stock_items", + title: "Include Non Stock Items", + description: __("To include non-stock items in the material request planning. i.e. Items for which 'Maintain Stock' checkbox is unticked.") + }, + { + fieldname: "include_subcontracted_items", + title: "Include Subcontracted Items", + description: __("To add subcontracted Item's raw materials if include exploded items is disabled.") + } +]; diff --git a/erpnext/manufacturing/doctype/routing/routing.js b/erpnext/manufacturing/doctype/routing/routing.js index 032c9cd9a2..c17a8e959d 100644 --- a/erpnext/manufacturing/doctype/routing/routing.js +++ b/erpnext/manufacturing/doctype/routing/routing.js @@ -69,3 +69,17 @@ frappe.ui.form.on('BOM Operation', { frm.events.calculate_operating_cost(frm, d); } }); + +frappe.tour['Routing'] = [ + { + fieldname: "routing_name", + title: "Routing Name", + description: __("Enter a name for Routing.") + }, + { + fieldname: "operations", + title: "BOM Operations", + description: __("Enter the Operation, the table will fetch the Operation details like Hourly Rate, Workstation automatically.\n\n After that, set the Operation Time in minutes and the table will calculate the Operation Costs based on the Hourly Rate and Operation Time.") + } +]; + diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 512048512e..51c46f63ab 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -731,3 +731,63 @@ erpnext.work_order = { }); } }; + +frappe.tour['Work Order'] = [ + { + fieldname: "production_item", + title: "Item to Manufacture", + description: __("Select the Item to be manufactured.") + }, + { + fieldname: "bom_no", + title: "BOM No", + description: __("The default BOM for that item will be fetched by the system. You can also change the BOM.") + }, + { + fieldname: "qty", + title: "Qty to Manufacture", + description: __("Enter the quantity to manufacture. Raw material Items will be fetched only when this is set.") + }, + { + fieldname: "use_multi_level_bom", + title: "Use Multi-Level BOM", + description: __("This is enabled by default. If you want to plan materials for sub-assemblies of the Item you're manufacturing leave this enabled. If you plan and manufacture the sub-assemblies separately, you can disable this checkbox.") + }, + { + fieldname: "source_warehouse", + title: "Source Warehouse", + description: __("The warehouse where you store your raw materials. Each required item can have a separate source warehouse. Group warehouse also can be selected as source warehouse. On submission of the Work Order, the raw materials will be reserved in these warehouses for production usage.") + }, + { + fieldname: "fg_warehouse", + title: "Target Warehouse", + description: __("The warehouse where you store finished Items before they are shipped.") + }, + { + fieldname: "wip_warehouse", + title: "Work-in-Progress Warehouse", + description: __("The warehouse where your Items will be transferred when you begin production. Group Warehouse can also be selected as a Work in Progress warehouse.") + }, + { + fieldname: "scrap_warehouse", + title: "Scrap Warehouse", + description: __("If the BOM results in Scrap material, the Scrap Warehouse needs to be selected.") + }, + { + fieldname: "required_items", + title: "Required Items", + description: __("All the required items (raw materials) will be fetched from BOM and populated in this table. Here you can also change the Source Warehouse for any item. And during the production, you can track transferred raw materials from this table.") + }, + { + fieldname: "planned_start_date", + title: "Planned Start Date", + description: __("Set the Planned Start Date (an Estimated Date at which you want the Production to begin)") + }, + { + fieldname: "operations", + title: "Operations", + description: __("If the selected BOM has Operations mentioned in it, the system will fetch all Operations from BOM, these values can be changed.") + }, + + +]; diff --git a/erpnext/manufacturing/doctype/workstation/workstation.js b/erpnext/manufacturing/doctype/workstation/workstation.js index d8d25fc6f8..4f5231e449 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.js +++ b/erpnext/manufacturing/doctype/workstation/workstation.js @@ -16,4 +16,29 @@ frappe.ui.form.on("Workstation", { }) } } -}) +}); + +frappe.tour['Workstation'] = [ + { + fieldname: "workstation_name", + title: "Workstation Name", + description: __("You can set it as a machine name or operation type. For example, stiching machine 12") + }, + { + fieldname: "production_capacity", + title: "Production Capacity", + description: __("No. of parallel job cards which can be allowed on this workstation. Example: 2 would mean this workstation can process production for two Work Orders at a time.") + }, + { + fieldname: "holiday_list", + title: "Holiday List", + description: __("A Holiday List can be added to exclude counting these days for the Workstation.") + }, + { + fieldname: "working_hours", + title: "Working Hours", + description: __("Under Working Hours table, you can add start and end times for a Workstation. For example, a Workstation may be active from 9 am to 1 pm, then 2 pm to 5 pm. You can also specify the working hours based on shifts. While scheduling a Work Order, the system will check for the availability of the Workstation based on the working hours specified.") + }, + + +]; \ No newline at end of file diff --git a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json index 7317152565..032091f67e 100644 --- a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json @@ -19,14 +19,14 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing", "idx": 0, "is_complete": 0, - "modified": "2020-06-29 20:25:36.899106", + "modified": "2021-08-13 16:04:34.333812", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", "owner": "Administrator", "steps": [ { - "step": "Warehouse" + "step": "Explore Manufacturing Settings" }, { "step": "Workstation" @@ -35,22 +35,22 @@ "step": "Operation" }, { - "step": "Create Product" + "step": "Routing" }, { - "step": "Create Raw Materials" + "step": "Create Product" }, { "step": "Create BOM" }, { - "step": "Work Order" + "step": "Production Planning" }, { - "step": "Explore Manufacturing Settings" + "step": "Work Order" } ], "subtitle": "Products, Raw Materials, BOM, Work Order, and more.", "success_message": "Manufacturing module is all set up!", "title": "Let's Set Up the Manufacturing Module." -} +} \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json index 84b4088f23..9d7a58c121 100644 --- a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json +++ b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json @@ -1,19 +1,21 @@ { "action": "Create Entry", + "action_label": "Create your first Bill of Materials", "creation": "2020-05-05 16:41:20.239696", + "description": "# Create a Bill of Materials\n\nA Bill of Materials (BOM) is a list of items and sub-assemblies with quantities required to manufacture an Item.\n\nBOM also provides cost estimation for the production of the item. It takes raw-materials cost based on valuation and operations to cost based on routing, which gives total costing for a BOM.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 12:51:31.315686", + "modified": "2021-08-13 16:00:51.092671", "modified_by": "Administrator", "name": "Create BOM", "owner": "Administrator", "reference_document": "BOM", + "show_form_tour": 1, "show_full_form": 1, - "title": "Create a BOM (Bill of Material)", + "title": "Bill of Materials", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/create_product/create_product.json b/erpnext/manufacturing/onboarding_step/create_product/create_product.json index 0ffa30158b..ad7345234d 100644 --- a/erpnext/manufacturing/onboarding_step/create_product/create_product.json +++ b/erpnext/manufacturing/onboarding_step/create_product/create_product.json @@ -1,19 +1,21 @@ { "action": "Create Entry", + "action_label": "Create an Item", "creation": "2020-05-05 16:42:31.476275", + "description": "# Create Items for Bill of Materials\n\nOne of the prerequisites of a BOM is the creation of raw materials, sub-assembly, and finished items. Once these items are created, you will be able to proceed to the Bill of Materials master, which is composed of items and routing.\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 12:50:59.010439", + "modified": "2021-08-13 16:00:22.407811", "modified_by": "Administrator", "name": "Create Product", "owner": "Administrator", "reference_document": "Item", - "show_full_form": 0, - "title": "Create a Finished Good", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Finished Items", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json index 0764f2e44a..3f94764f08 100644 --- a/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json +++ b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json @@ -5,7 +5,6 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, "modified": "2020-05-19 11:53:25.147837", @@ -13,6 +12,7 @@ "name": "Create Raw Materials", "owner": "Administrator", "reference_document": "Item", + "show_form_tour": 0, "show_full_form": 0, "title": "Create Raw Materials", "validate_action": 1 diff --git a/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json index 7ef202ee4e..1d2c27e379 100644 --- a/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json +++ b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json @@ -1,20 +1,22 @@ { "action": "Show Form Tour", + "action_label": "Take a walk-through of Manufacturing Settings", "creation": "2020-05-19 11:55:11.378374", + "description": "# Review Manufacturing Settings\n\nIn ERPNext, the Manufacturing module\u2019s features are configurable as per your business needs. Manufacturing Settings is the place where you can set your preferences for:\n\n- Capacity planning for allocating jobs to workstations\n- Raw-material consumption based on BOM or actual\n- Default values and over-production allowance\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 1, "is_skipped": 0, - "modified": "2020-05-26 20:28:03.558199", + "modified": "2021-08-13 15:59:32.145655", "modified_by": "Administrator", "name": "Explore Manufacturing Settings", "owner": "Administrator", "reference_document": "Manufacturing Settings", + "show_form_tour": 0, "show_full_form": 0, - "title": "Explore Manufacturing Settings", + "title": "Manufacturing Settings", "validate_action": 0, "video_url": "https://www.youtube.com/watch?v=UVGfzwOOZC4" } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/operation/operation.json b/erpnext/manufacturing/onboarding_step/operation/operation.json index b532e6778c..2e9592185e 100644 --- a/erpnext/manufacturing/onboarding_step/operation/operation.json +++ b/erpnext/manufacturing/onboarding_step/operation/operation.json @@ -1,19 +1,21 @@ { "action": "Create Entry", + "action_label": "Let\u2019s create an Operation", "creation": "2020-05-12 16:15:31.706756", + "description": "# Create Operations\n\nAn Operation refers to any manufacturing operation performed on the raw materials to process it further in the manufacturing path. As an example, if you are into garments manufacturing, you will create Operations like fabric cutting, stitching, and washing as some of the operations.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 12:50:41.642754", + "modified": "2021-08-13 15:59:53.313532", "modified_by": "Administrator", "name": "Operation", "owner": "Administrator", "reference_document": "Operation", - "show_full_form": 0, - "title": "Create a Operation", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Operation", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/production_planning/production_planning.json b/erpnext/manufacturing/onboarding_step/production_planning/production_planning.json new file mode 100644 index 0000000000..ff9fdb9fd0 --- /dev/null +++ b/erpnext/manufacturing/onboarding_step/production_planning/production_planning.json @@ -0,0 +1,21 @@ +{ + "action": "Create Entry", + "action_label": "Learn more about Production Planning", + "creation": "2021-08-04 17:33:06.551077", + "description": "# How Production Planning Works\n\nProduction Plan helps in production and material planning for the Items planned for manufacturing. These production items can be committed via Sales Order (to Customers) or Material Requests (internally).\n", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-13 16:04:15.491414", + "modified_by": "Administrator", + "name": "Production Planning", + "owner": "Administrator", + "reference_document": "Production Plan", + "show_form_tour": 0, + "show_full_form": 1, + "title": "Production Planning", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/routing/routing.json b/erpnext/manufacturing/onboarding_step/routing/routing.json new file mode 100644 index 0000000000..be4fbff171 --- /dev/null +++ b/erpnext/manufacturing/onboarding_step/routing/routing.json @@ -0,0 +1,21 @@ +{ + "action": "Create Entry", + "action_label": "Check help to setup Routing", + "creation": "2021-08-04 11:56:42.455758", + "description": "# Setup Routing\n\nA Routing stores all Operations along with the description, hourly rate, operation time, batch size, etc. Click below to learn how the Routing template can be created, for quick selection in the BOM.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-13 16:00:07.391563", + "modified_by": "Administrator", + "name": "Routing", + "owner": "Administrator", + "reference_document": "Routing", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Routing", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/work_order/work_order.json b/erpnext/manufacturing/onboarding_step/work_order/work_order.json index c63363e7cb..d2756c9275 100644 --- a/erpnext/manufacturing/onboarding_step/work_order/work_order.json +++ b/erpnext/manufacturing/onboarding_step/work_order/work_order.json @@ -1,19 +1,21 @@ { "action": "Create Entry", + "action_label": "Create your first Work Order", "creation": "2020-05-12 16:15:56.084682", + "description": "# Create a Work Order\n\nA Work Order or a Job order is given to the manufacturing shop floor by the Production Manager to initiate the manufacturing of a certain quantity of an item. Work Order carriers details of production Item, its BOM, quantities to be manufactured, and operations.\n\nThrough Work Order, you can track various production status like:\n\n- Issue of raw-material to shop material\n- Progress on each Workstation via Job Card\n- Manufactured Quantity against Work Order\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 12:51:38.133150", + "modified": "2021-08-13 16:02:04.223536", "modified_by": "Administrator", "name": "Work Order", "owner": "Administrator", "reference_document": "Work Order", + "show_form_tour": 1, "show_full_form": 1, - "title": "Create a Work Order", + "title": "Work Order", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/onboarding_step/workstation/workstation.json b/erpnext/manufacturing/onboarding_step/workstation/workstation.json index df244bb494..67dd52eba2 100644 --- a/erpnext/manufacturing/onboarding_step/workstation/workstation.json +++ b/erpnext/manufacturing/onboarding_step/workstation/workstation.json @@ -1,19 +1,21 @@ { "action": "Create Entry", + "action_label": "Let\u2019s create a Workstation", "creation": "2020-05-12 16:14:14.930214", + "description": "# Create Workstations\n\nA Workstation stores information regarding the place where the workstation operations are performed. As an example, if you have ten sewing machines doing stitching jobs, each machine will be added as a workstation.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 12:50:33.938176", + "modified": "2021-08-13 15:59:59.634802", "modified_by": "Administrator", "name": "Workstation", "owner": "Administrator", "reference_document": "Workstation", - "show_full_form": 0, - "title": "Create a Workstation / Machine", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Workstation", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 3de35e6062..752a1fe732 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -792,3 +792,48 @@ frappe.ui.form.on("UOM Conversion Detail", { } } }); + +frappe.tour['Item'] = [ + { + fieldname: "item_code", + title: "Item Code", + description: __("Enter an Item Code, the name will be auto-filled the same as Item Code on clicking inside the Item Name field.") + }, + { + fieldname: "item_group", + title: "Item Group", + description: __("Select an Item Group.") + }, + { + fieldname: "is_stock_item", + title: "Maintain Stock", + description: __("If you are maintaining stock of this Item in your Inventory, ERPNext will make a stock ledger entry for each transaction of this item.") + }, + { + fieldname: "include_item_in_manufacturing", + title: "Include Item in Manufacturing", + description: __("This is for raw material Items that'll be used to create finished goods. If the Item is an additional service like 'washing' that'll be used in the BOM, keep this unchecked.") + }, + { + fieldname: "opening_stock", + title: "Opening Stock", + description: __("Enter the opening stock units.") + }, + { + fieldname: "valuation_rate", + title: "Valuation Rate", + description: __("There are two options to maintain valuation of stock. FIFO (first in - first out) and Moving Average. To understand this topic in detail please visit Item Valuation, FIFO and Moving Average.") + }, + { + fieldname: "standard_rate", + title: "Standard Selling Rate", + description: __("When creating an Item, entering a value for this field will automatically create an Item Price at the backend.") + }, + { + fieldname: "item_defaults", + title: "Item Defaults", + description: __("In this section, you can define Company-wide transaction-related defaults for this Item. Eg. Default Warehouse, Default Price List, Supplier, etc.") + } + + +]; From 16eed07a0fb34b8d0ad042ed18d79a1123cc6076 Mon Sep 17 00:00:00 2001 From: Rohan Date: Thu, 26 Aug 2021 21:47:00 +0530 Subject: [PATCH 54/98] fix: use Stripe's Price API for plan-price information (#26107) * fix: use Stripe's new Plan API for price information * patch: use inbuilt function to rename field * fix: patch call Co-authored-by: Ankush Menat Co-authored-by: Nabin Hait --- .../subscription_plan/subscription_plan.json | 14 +++--- .../stripe_integration.py | 46 +++++++++++-------- erpnext/patches.txt | 1 + erpnext/patches/v13_0/migrate_stripe_api.py | 7 +++ 4 files changed, 42 insertions(+), 26 deletions(-) create mode 100644 erpnext/patches/v13_0/migrate_stripe_api.py diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json index 771611a786..878ae09889 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json @@ -21,7 +21,7 @@ "column_break_13", "billing_interval_count", "payment_plan_section", - "payment_plan_id", + "product_price_id", "column_break_16", "payment_gateway", "accounting_dimensions_section", @@ -114,11 +114,6 @@ "fieldtype": "Section Break", "label": "Payment Plan" }, - { - "fieldname": "payment_plan_id", - "fieldtype": "Data", - "label": "Payment Plan" - }, { "fieldname": "column_break_16", "fieldtype": "Column Break" @@ -144,10 +139,15 @@ "fieldtype": "Link", "label": "Cost Center", "options": "Cost Center" + }, + { + "fieldname": "product_price_id", + "fieldtype": "Data", + "label": "Product Price ID" } ], "links": [], - "modified": "2021-08-09 10:53:44.205774", + "modified": "2021-08-13 10:53:44.205774", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription Plan", diff --git a/erpnext/erpnext_integrations/stripe_integration.py b/erpnext/erpnext_integrations/stripe_integration.py index 108b4c0dd8..820c740532 100644 --- a/erpnext/erpnext_integrations/stripe_integration.py +++ b/erpnext/erpnext_integrations/stripe_integration.py @@ -2,11 +2,12 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals +import stripe + import frappe from frappe import _ from frappe.integrations.utils import create_request_log -import stripe + def create_stripe_subscription(gateway_controller, data): stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller) @@ -23,31 +24,38 @@ def create_stripe_subscription(gateway_controller, data): except Exception: frappe.log_error(frappe.get_traceback()) return{ - "redirect_to": frappe.redirect_to_message(_('Server Error'), _("It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.")), + "redirect_to": frappe.redirect_to_message( + _('Server Error'), + _("It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.") + ), "status": 401 } def create_subscription_on_stripe(stripe_settings): - items = [] - for payment_plan in stripe_settings.payment_plans: - plan = frappe.db.get_value("Subscription Plan", payment_plan.plan, "payment_plan_id") - items.append({"plan": plan, "quantity": payment_plan.qty}) + items = [] + for payment_plan in stripe_settings.payment_plans: + plan = frappe.db.get_value("Subscription Plan", payment_plan.plan, "product_price_id") + items.append({"price": plan, "quantity": payment_plan.qty}) - try: - customer = stripe.Customer.create(description=stripe_settings.data.payer_name, email=stripe_settings.data.payer_email, source=stripe_settings.data.stripe_token_id) - subscription = stripe.Subscription.create(customer=customer, items=items) + try: + customer = stripe.Customer.create( + source=stripe_settings.data.stripe_token_id, + description=stripe_settings.data.payer_name, + email=stripe_settings.data.payer_email + ) - if subscription.status == "active": - stripe_settings.integration_request.db_set('status', 'Completed', update_modified=False) - stripe_settings.flags.status_changed_to = "Completed" + subscription = stripe.Subscription.create(customer=customer, items=items) - else: - stripe_settings.integration_request.db_set('status', 'Failed', update_modified=False) - frappe.log_error('Subscription N°: ' + subscription.id, 'Stripe Payment not completed') + if subscription.status == "active": + stripe_settings.integration_request.db_set('status', 'Completed', update_modified=False) + stripe_settings.flags.status_changed_to = "Completed" - except Exception: + else: stripe_settings.integration_request.db_set('status', 'Failed', update_modified=False) - frappe.log_error(frappe.get_traceback()) + frappe.log_error('Subscription N°: ' + subscription.id, 'Stripe Payment not completed') + except Exception: + stripe_settings.integration_request.db_set('status', 'Failed', update_modified=False) + frappe.log_error(frappe.get_traceback()) - return stripe_settings.finalize_request() + return stripe_settings.finalize_request() diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0a6a8bdbdc..311e785f8c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -295,6 +295,7 @@ erpnext.patches.v13_0.update_tds_check_field #3 erpnext.patches.v13_0.add_custom_field_for_south_africa #2 erpnext.patches.v13_0.update_recipient_email_digest erpnext.patches.v13_0.shopify_deprecation_warning +erpnext.patches.v13_0.migrate_stripe_api erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries erpnext.patches.v13_0.einvoicing_deprecation_warning erpnext.patches.v14_0.delete_einvoicing_doctypes diff --git a/erpnext/patches/v13_0/migrate_stripe_api.py b/erpnext/patches/v13_0/migrate_stripe_api.py new file mode 100644 index 0000000000..355421a1f4 --- /dev/null +++ b/erpnext/patches/v13_0/migrate_stripe_api.py @@ -0,0 +1,7 @@ +import frappe +from frappe.model.utils.rename_field import rename_field + + +def execute(): + frappe.reload_doc("accounts", "doctype", "subscription_plan") + rename_field("Subscription Plan", "payment_plan_id", "product_price_id") From f1b77360ee3b5cc418f074aefa83a1871964da99 Mon Sep 17 00:00:00 2001 From: Chillar Anand Date: Thu, 26 Aug 2021 22:15:30 +0530 Subject: [PATCH 55/98] feat(healthcare): Added Treatment Plan Template feature (#26557) * feat: Added doctypes for treatment plan template * feat: Added child doctype * feat: Added validations for patient age * chore: Clean up treatment plan template * fix: Limit plan items to templates * Added multiselectdialogbox for treatment plan template * Add template name as autoname * Clean up code * Cleanup lint issues * Clean up code * Added tests for tpt filters * Added test records * Fix order of fields * Added tests for care plan template * Added age property for patient * Clean up code * Clean up list view * Clean up code * Fix lint issues * Clean up test records * Code cleanup no-docs * chore: Code cleanup * chore: Use ORM instead of raw sql * fix: Make treatment plan item fields mandatory * fix: Added healthcare roles in permissions rules * fix: Added filters for symptoms, diagnosis * fix: Show applicable treatment plans button only if patient is present * fix: Fix key error issues * fix: Fix issues with filters in plan templates * chore: Fix age filters * refactor: appending treatment plan items * fix: treatment plan test Co-authored-by: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Co-authored-by: Rucha Mahabal --- .../drug_prescription/drug_prescription.json | 3 +- erpnext/healthcare/doctype/patient/patient.py | 17 +- .../patient_encounter/patient_encounter.js | 37 +++- .../patient_encounter/patient_encounter.json | 11 +- .../patient_encounter/patient_encounter.py | 80 ++++++++ .../test_patient_encounter.py | 79 +++++++- .../treatment_plan_template/__init__.py | 0 .../treatment_plan_template/test_records.json | 7 + .../test_treatment_plan_template.py | 8 + .../treatment_plan_template.js | 14 ++ .../treatment_plan_template.json | 189 ++++++++++++++++++ .../treatment_plan_template.py | 19 ++ .../treatment_plan_template_list.js | 10 + .../treatment_plan_template_item/__init__.py | 0 .../treatment_plan_template_item.json | 55 +++++ .../treatment_plan_template_item.py | 8 + .../__init__.py | 0 .../treatment_plan_template_practitioner.json | 32 +++ .../treatment_plan_template_practitioner.py | 8 + 19 files changed, 567 insertions(+), 10 deletions(-) create mode 100644 erpnext/healthcare/doctype/treatment_plan_template/__init__.py create mode 100644 erpnext/healthcare/doctype/treatment_plan_template/test_records.json create mode 100644 erpnext/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py create mode 100644 erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.js create mode 100644 erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.json create mode 100644 erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.py create mode 100644 erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js create mode 100644 erpnext/healthcare/doctype/treatment_plan_template_item/__init__.py create mode 100644 erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json create mode 100644 erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py create mode 100644 erpnext/healthcare/doctype/treatment_plan_template_practitioner/__init__.py create mode 100644 erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json create mode 100644 erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py diff --git a/erpnext/healthcare/doctype/drug_prescription/drug_prescription.json b/erpnext/healthcare/doctype/drug_prescription/drug_prescription.json index d91e6bf9dc..a65c56694e 100644 --- a/erpnext/healthcare/doctype/drug_prescription/drug_prescription.json +++ b/erpnext/healthcare/doctype/drug_prescription/drug_prescription.json @@ -56,6 +56,7 @@ "reqd": 1 }, { + "allow_in_quick_entry": 1, "fieldname": "dosage_form", "fieldtype": "Link", "ignore_user_permissions": 1, @@ -109,7 +110,7 @@ ], "istable": 1, "links": [], - "modified": "2020-09-30 23:32:09.495288", + "modified": "2021-06-11 11:53:06.343704", "modified_by": "Administrator", "module": "Healthcare", "name": "Drug Prescription", diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py index 56a34007ff..f77ad70633 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -102,12 +102,19 @@ class Patient(Document): return name + @property + def age(self): + if not self.dob: + return + dob = getdate(self.dob) + age = dateutil.relativedelta.relativedelta(getdate(), dob) + return age + def get_age(self): - age_str = '' - if self.dob: - dob = getdate(self.dob) - age = dateutil.relativedelta.relativedelta(getdate(), dob) - age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)") + age = self.age + if not age: + return + age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)") return age_str @frappe.whitelist() diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js index aaeaa692e6..c3466260d2 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js @@ -185,7 +185,42 @@ frappe.ui.form.on('Patient Encounter', { }; frm.set_value(values); } - } + }, + + get_applicable_treatment_plans: function(frm) { + frappe.call({ + method: 'get_applicable_treatment_plans', + doc: frm.doc, + args: {'encounter': frm.doc}, + freeze: true, + freeze_message: __('Fetching Treatment Plans'), + callback: function() { + new frappe.ui.form.MultiSelectDialog({ + doctype: "Treatment Plan Template", + target: this.cur_frm, + setters: { + medical_department: "", + }, + action(selections) { + frappe.call({ + method: 'set_treatment_plans', + doc: frm.doc, + args: selections, + }).then(() => { + frm.refresh_field('drug_prescription'); + frm.refresh_field('procedure_prescription'); + frm.refresh_field('lab_test_prescription'); + frm.refresh_field('therapies'); + }); + cur_dialog.hide(); + } + }); + + + } + }); + }, + }); var schedule_inpatient = function(frm) { diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json index b646ff9ebe..994597dca7 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json @@ -31,6 +31,7 @@ "sb_symptoms", "symptoms", "symptoms_in_print", + "get_applicable_treatment_plans", "physical_examination", "diagnosis", "diagnosis_in_print", @@ -324,11 +325,17 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "depends_on": "eval:doc.patient", + "fieldname": "get_applicable_treatment_plans", + "fieldtype": "Button", + "label": "Get Applicable Treatment Plans" } ], "is_submittable": 1, "links": [], - "modified": "2020-11-30 10:39:00.783119", + "modified": "2021-07-27 11:39:12.347704", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Encounter", @@ -358,4 +365,4 @@ "title_field": "title", "track_changes": 1, "track_seen": 1 -} \ No newline at end of file +} diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py index 2b3029efde..7a745ae468 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py @@ -10,6 +10,7 @@ from frappe.utils import cstr, getdate, add_days from frappe import _ from frappe.model.mapper import get_mapped_doc + class PatientEncounter(Document): def validate(self): self.set_title() @@ -33,6 +34,85 @@ class PatientEncounter(Document): self.title = _('{0} with {1}').format(self.patient_name or self.patient, self.practitioner_name or self.practitioner)[:100] + @frappe.whitelist() + @staticmethod + def get_applicable_treatment_plans(encounter): + patient = frappe.get_doc('Patient', encounter['patient']) + + plan_filters = {} + plan_filters['name'] = ['in', []] + + age = patient.age + if age: + plan_filters['patient_age_from'] = ['<=', age.years] + plan_filters['patient_age_to'] = ['>=', age.years] + + gender = patient.sex + if gender: + plan_filters['gender'] = ['in', [gender, None]] + + diagnosis = encounter.get('diagnosis') + if diagnosis: + diagnosis = [_diagnosis['diagnosis'] for _diagnosis in encounter['diagnosis']] + filters = [ + ['diagnosis', 'in', diagnosis], + ['parenttype', '=', 'Treatment Plan Template'], + ] + diagnosis = frappe.get_list('Patient Encounter Diagnosis', filters=filters, fields='*') + plan_names = [_diagnosis['parent'] for _diagnosis in diagnosis] + plan_filters['name'][1].extend(plan_names) + + symptoms = encounter.get('symptoms') + if symptoms: + symptoms = [symptom['complaint'] for symptom in encounter['symptoms']] + filters = [ + ['complaint', 'in', symptoms], + ['parenttype', '=', 'Treatment Plan Template'], + ] + symptoms = frappe.get_list('Patient Encounter Symptom', filters=filters, fields='*') + plan_names = [symptom['parent'] for symptom in symptoms] + plan_filters['name'][1].extend(plan_names) + + if not plan_filters['name'][1]: + plan_filters.pop('name') + + plans = frappe.get_list('Treatment Plan Template', fields='*', filters=plan_filters) + + return plans + + @frappe.whitelist() + def set_treatment_plans(self, treatment_plans=None): + for treatment_plan in treatment_plans: + self.set_treatment_plan(treatment_plan) + + def set_treatment_plan(self, plan): + plan_items = frappe.get_list('Treatment Plan Template Item', filters={'parent': plan}, fields='*') + for plan_item in plan_items: + self.set_treatment_plan_item(plan_item) + + drugs = frappe.get_list('Drug Prescription', filters={'parent': plan}, fields='*') + for drug in drugs: + self.append('drug_prescription', drug) + + self.save() + + def set_treatment_plan_item(self, plan_item): + if plan_item.type == 'Clinical Procedure Template': + self.append('procedure_prescription', { + 'procedure': plan_item.template + }) + + if plan_item.type == 'Lab Test Template': + self.append('lab_test_prescription', { + 'lab_test_code': plan_item.template + }) + + if plan_item.type == 'Therapy Type': + self.append('therapies', { + 'therapy_type': plan_item.template + }) + + @frappe.whitelist() def make_ip_medication_order(source_name, target_doc=None): def set_missing_values(source, target): diff --git a/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py index f5df152050..96976821a7 100644 --- a/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py +++ b/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py @@ -4,5 +4,82 @@ from __future__ import unicode_literals import unittest +import frappe +from erpnext.healthcare.doctype.patient_encounter.patient_encounter import PatientEncounter + + class TestPatientEncounter(unittest.TestCase): - pass + def setUp(self): + try: + gender_m = frappe.get_doc({ + 'doctype': 'Gender', + 'gender': 'MALE' + }).insert() + gender_f = frappe.get_doc({ + 'doctype': 'Gender', + 'gender': 'FEMALE' + }).insert() + except frappe.exceptions.DuplicateEntryError: + gender_m = frappe.get_doc({ + 'doctype': 'Gender', + 'gender': 'MALE' + }) + gender_f = frappe.get_doc({ + 'doctype': 'Gender', + 'gender': 'FEMALE' + }) + + self.patient_male = frappe.get_doc({ + 'doctype': 'Patient', + 'first_name': 'John', + 'sex': gender_m.gender, + }).insert() + self.patient_female = frappe.get_doc({ + 'doctype': 'Patient', + 'first_name': 'Curie', + 'sex': gender_f.gender, + }).insert() + self.practitioner = frappe.get_doc({ + 'doctype': 'Healthcare Practitioner', + 'first_name': 'Doc', + 'sex': 'MALE', + }).insert() + try: + self.care_plan_male = frappe.get_doc({ + 'doctype': 'Treatment Plan Template', + 'template_name': 'test plan - m', + 'gender': gender_m.gender, + }).insert() + self.care_plan_female = frappe.get_doc({ + 'doctype': 'Treatment Plan Template', + 'template_name': 'test plan - f', + 'gender': gender_f.gender, + }).insert() + except frappe.exceptions.DuplicateEntryError: + self.care_plan_male = frappe.get_doc({ + 'doctype': 'Treatment Plan Template', + 'template_name': 'test plan - m', + 'gender': gender_m.gender, + }) + self.care_plan_female = frappe.get_doc({ + 'doctype': 'Treatment Plan Template', + 'template_name': 'test plan - f', + 'gender': gender_f.gender, + }) + + def test_treatment_plan_template_filter(self): + encounter = frappe.get_doc({ + 'doctype': 'Patient Encounter', + 'patient': self.patient_male.name, + 'practitioner': self.practitioner.name, + }).insert() + plans = PatientEncounter.get_applicable_treatment_plans(encounter.as_dict()) + self.assertEqual(plans[0]['name'], self.care_plan_male.template_name) + + encounter = frappe.get_doc({ + 'doctype': 'Patient Encounter', + 'patient': self.patient_female.name, + 'practitioner': self.practitioner.name, + }).insert() + plans = PatientEncounter.get_applicable_treatment_plans(encounter.as_dict()) + self.assertEqual(plans[0]['name'], self.care_plan_female.template_name) diff --git a/erpnext/healthcare/doctype/treatment_plan_template/__init__.py b/erpnext/healthcare/doctype/treatment_plan_template/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/treatment_plan_template/test_records.json b/erpnext/healthcare/doctype/treatment_plan_template/test_records.json new file mode 100644 index 0000000000..d661b4304f --- /dev/null +++ b/erpnext/healthcare/doctype/treatment_plan_template/test_records.json @@ -0,0 +1,7 @@ +[ + { + "doctype": "Treatment Plan Template", + "template_name": "Chemo", + "patient_age_from": 21 + } +] diff --git a/erpnext/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py b/erpnext/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py new file mode 100644 index 0000000000..21ede7129f --- /dev/null +++ b/erpnext/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + +class TestTreatmentPlanTemplate(unittest.TestCase): + pass diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.js b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.js new file mode 100644 index 0000000000..986c3cb6e4 --- /dev/null +++ b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.js @@ -0,0 +1,14 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Treatment Plan Template', { + refresh: function (frm) { + frm.set_query('type', 'items', function () { + return { + filters: { + 'name': ['in', ['Lab Test Template', 'Clinical Procedure Template', 'Therapy Type']], + } + }; + }); + }, +}); diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.json b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.json new file mode 100644 index 0000000000..85a312fb17 --- /dev/null +++ b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.json @@ -0,0 +1,189 @@ +{ + "actions": [], + "autoname": "field:template_name", + "creation": "2021-06-10 10:14:17.901273", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "section_break_1", + "template_name", + "description", + "practitioners", + "disabled", + "column_break_1", + "medical_department", + "goal", + "order_group", + "section_break_8", + "patient_age_from", + "complaints", + "gender", + "column_break_12", + "patient_age_to", + "diagnosis", + "plan_items_section", + "items", + "drugs" + ], + "fields": [ + { + "fieldname": "section_break_1", + "fieldtype": "Section Break", + "label": "Plan Details" + }, + { + "fieldname": "medical_department", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Medical Department", + "options": "Medical Department" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + }, + { + "fieldname": "goal", + "fieldtype": "Small Text", + "label": "Goal" + }, + { + "fieldname": "practitioners", + "fieldtype": "Table MultiSelect", + "label": "Practitioners", + "options": "Treatment Plan Template Practitioner" + }, + { + "fieldname": "order_group", + "fieldtype": "Link", + "label": "Order Group", + "options": "Patient Encounter", + "read_only": 1 + }, + { + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "label": "Plan Conditions" + }, + { + "fieldname": "template_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Template Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "patient_age_from", + "fieldtype": "Int", + "label": "Patient Age From", + "non_negative": 1 + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fieldname": "patient_age_to", + "fieldtype": "Int", + "label": "Patient Age To", + "non_negative": 1 + }, + { + "fieldname": "gender", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender" + }, + { + "fieldname": "complaints", + "fieldtype": "Table MultiSelect", + "label": "Complaints", + "options": "Patient Encounter Symptom" + }, + { + "fieldname": "diagnosis", + "fieldtype": "Table MultiSelect", + "label": "Diagnosis", + "options": "Patient Encounter Diagnosis" + }, + { + "fieldname": "plan_items_section", + "fieldtype": "Section Break", + "label": "Plan Items" + }, + { + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "options": "Treatment Plan Template Item" + }, + { + "fieldname": "drugs", + "fieldtype": "Table", + "label": "Drugs", + "options": "Drug Prescription" + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, + { + "fieldname": "column_break_1", + "fieldtype": "Column Break" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-08-18 02:41:58.354296", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Treatment Plan Template", + "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": "Physician", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "template_name", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.py b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.py new file mode 100644 index 0000000000..a92e2668fe --- /dev/null +++ b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.py @@ -0,0 +1,19 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document + +class TreatmentPlanTemplate(Document): + def validate(self): + self.validate_age() + + def validate_age(self): + if self.patient_age_from and self.patient_age_from < 0: + frappe.throw(_('Patient Age From cannot be less than 0')) + if self.patient_age_to and self.patient_age_to < 0: + frappe.throw(_('Patient Age To cannot be less than 0')) + if self.patient_age_to and self.patient_age_from and \ + self.patient_age_to < self.patient_age_from: + frappe.throw(_('Patient Age To cannot be less than Patient Age From')) diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js new file mode 100644 index 0000000000..7ab31dff79 --- /dev/null +++ b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js @@ -0,0 +1,10 @@ +frappe.listview_settings['Treatment Plan Template'] = { + get_indicator: function(doc) { + var colors = { + 1: 'gray', + 0: 'blue', + }; + let label = doc.disabled == 1 ? 'Disabled' : 'Enabled'; + return [__(label), colors[doc.disabled], 'disable,=,' + doc.disabled]; + } +}; diff --git a/erpnext/healthcare/doctype/treatment_plan_template_item/__init__.py b/erpnext/healthcare/doctype/treatment_plan_template_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json b/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json new file mode 100644 index 0000000000..20a9d6793a --- /dev/null +++ b/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json @@ -0,0 +1,55 @@ +{ + "actions": [], + "creation": "2021-06-10 11:47:29.194795", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "type", + "template", + "qty", + "instructions" + ], + "fields": [ + { + "fieldname": "type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Type", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "template", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Template", + "options": "type", + "reqd": 1 + }, + { + "default": "1", + "fieldname": "qty", + "fieldtype": "Int", + "label": "Qty" + }, + { + "fieldname": "instructions", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Instructions" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-17 11:19:03.515441", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Treatment Plan Template Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py b/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py new file mode 100644 index 0000000000..5f58b06af6 --- /dev/null +++ b/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class TreatmentPlanTemplateItem(Document): + pass diff --git a/erpnext/healthcare/doctype/treatment_plan_template_practitioner/__init__.py b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json new file mode 100644 index 0000000000..04da387f7b --- /dev/null +++ b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "creation": "2021-06-10 10:37:56.669416", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "practitioner" + ], + "fields": [ + { + "fieldname": "practitioner", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Practitioner", + "options": "Healthcare Practitioner", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-06-11 16:05:06.733299", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Treatment Plan Template Practitioner", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py new file mode 100644 index 0000000000..6d34568e15 --- /dev/null +++ b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class TreatmentPlanTemplatePractitioner(Document): + pass From db69d1dc002dceac9029895d5037452a573c8cb6 Mon Sep 17 00:00:00 2001 From: Pawan Mehta Date: Thu, 26 Aug 2021 22:54:24 +0530 Subject: [PATCH 56/98] fix: fetch from more than one sales order in Maintenance Visit (#26924) * [fix] #26336 * fix(ux): make customer field reqd for fetching SO Co-authored-by: Ankush Menat --- .../doctype/maintenance_visit/maintenance_visit.js | 12 ++++++++++-- erpnext/selling/doctype/sales_order/sales_order.py | 6 ++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index 53ecdf5a61..6b3f18484a 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -73,12 +73,16 @@ erpnext.maintenance.MaintenanceVisit = class MaintenanceVisit extends frappe.ui. if (this.frm.doc.docstatus === 0) { this.frm.add_custom_button(__('Maintenance Schedule'), function () { + if (!me.frm.doc.customer) { + frappe.msgprint(__('Please select Customer first')); + return; + } erpnext.utils.map_current_doc({ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit", source_doctype: "Maintenance Schedule", target: me.frm, setters: { - customer: me.frm.doc.customer || undefined, + customer: me.frm.doc.customer, }, get_query_filters: { docstatus: 1, @@ -104,12 +108,16 @@ erpnext.maintenance.MaintenanceVisit = class MaintenanceVisit extends frappe.ui. }, __("Get Items From")); this.frm.add_custom_button(__('Sales Order'), function () { + if (!me.frm.doc.customer) { + frappe.msgprint(__('Please select Customer first')); + return; + } erpnext.utils.map_current_doc({ method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_visit", source_doctype: "Sales Order", target: me.frm, setters: { - customer: me.frm.doc.customer || undefined, + customer: me.frm.doc.customer, }, get_query_filters: { docstatus: 1, diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index bba54018ae..5d44582aab 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -718,8 +718,7 @@ def make_maintenance_schedule(source_name, target_doc=None): "doctype": "Maintenance Schedule Item", "field_map": { "parent": "sales_order" - }, - "add_if_empty": True + } } }, target_doc) @@ -745,8 +744,7 @@ def make_maintenance_visit(source_name, target_doc=None): "field_map": { "parent": "prevdoc_docname", "parenttype": "prevdoc_doctype" - }, - "add_if_empty": True + } } }, target_doc) From a1737c9a0c892aaaa2f22b863aa0dc2566b4a35b Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Thu, 26 Aug 2021 23:04:54 +0530 Subject: [PATCH 57/98] fix: Production Plan: load document defaults for plan items & remove name column from listview (#26584) (#27184) (cherry picked from commit 69d88a921273c9e0ea29d7131232686a0be7bb78) Co-authored-by: Richard Case <64409021+casesolved-co-uk@users.noreply.github.com> --- .../manufacturing/doctype/production_plan/production_plan.py | 2 +- .../doctype/production_plan/production_plan_list.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index b4c663507c..6b61c6d330 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -213,7 +213,6 @@ class ProductionPlan(Document): }) pi = self.append('po_items', { - 'include_exploded_items': 1, 'warehouse': data.warehouse, 'item_code': data.item_code, 'description': data.description or item_details.description, @@ -224,6 +223,7 @@ class ProductionPlan(Document): 'planned_start_date': now_datetime(), 'product_bundle_item': data.parent_item }) + pi._set_defaults() if self.get_items_from == "Sales Order": pi.sales_order = data.parent diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan_list.js b/erpnext/manufacturing/doctype/production_plan/production_plan_list.js index c2e3e6d712..8f94686624 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan_list.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan_list.js @@ -1,4 +1,5 @@ frappe.listview_settings['Production Plan'] = { + hide_name_column: true, add_fields: ["status"], filters: [["status", "!=", "Closed"]], get_indicator: function (doc) { From ae7ec8e44d3059a31e9e0b9a60821ac0e8adb74f Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 26 Aug 2021 23:51:09 +0530 Subject: [PATCH 58/98] fix: Base amount in tax gl entry --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 5a183e2e51..b259b11c63 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -787,7 +787,7 @@ class PaymentEntry(AccountsController): "account": payment_or_advance_account, "against": against, rev_dr_or_cr: tax_amount, - rev_dr_or_cr + "_in_account_currency": -1 * base_tax_amount + rev_dr_or_cr + "_in_account_currency": base_tax_amount if account_currency==self.company_currency else d.tax_amount, "cost_center": self.cost_center, From e6799d78efb031538dd69bb27f9f41494f81cf90 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 27 Aug 2021 10:46:45 +0530 Subject: [PATCH 59/98] fix: operation time auto set to zero (#27188) --- erpnext/manufacturing/doctype/bom/bom.py | 12 ++++++++---- erpnext/patches.txt | 1 + .../set_operation_time_based_on_operating_cost.py | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 1d7d451cc8..6e1c7dd81b 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -517,17 +517,21 @@ class BOM(WebsiteGenerator): def update_rate_and_time(self, row, update_hour_rate = False): if not row.hour_rate or update_hour_rate: hour_rate = flt(frappe.get_cached_value("Workstation", row.workstation, "hour_rate")) - row.hour_rate = (hour_rate / flt(self.conversion_rate) - if self.conversion_rate and hour_rate else hour_rate) + + if hour_rate: + row.hour_rate = (hour_rate / flt(self.conversion_rate) + if self.conversion_rate and hour_rate else hour_rate) if self.routing: - row.time_in_mins = flt(frappe.db.get_value("BOM Operation", { + time_in_mins = flt(frappe.db.get_value("BOM Operation", { "workstation": row.workstation, "operation": row.operation, - "sequence_id": row.sequence_id, "parent": self.routing }, ["time_in_mins"])) + if time_in_mins: + row.time_in_mins = time_in_mins + if row.hour_rate and row.time_in_mins: row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate) row.operating_cost = flt(row.hour_rate) * flt(row.time_in_mins) / 60.0 diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 311e785f8c..0f6a606cb1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -299,3 +299,4 @@ erpnext.patches.v13_0.migrate_stripe_api erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries erpnext.patches.v13_0.einvoicing_deprecation_warning erpnext.patches.v14_0.delete_einvoicing_doctypes +erpnext.patches.v13_0.set_operation_time_based_on_operating_cost \ No newline at end of file diff --git a/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py b/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py new file mode 100644 index 0000000000..4acbdd63a0 --- /dev/null +++ b/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py @@ -0,0 +1,15 @@ +import frappe + +def execute(): + frappe.reload_doc('manufacturing', 'doctype', 'bom') + frappe.reload_doc('manufacturing', 'doctype', 'bom_operation') + + frappe.db.sql(''' + UPDATE + `tabBOM Operation` + SET + time_in_mins = (operating_cost * 60) / hour_rate + WHERE + time_in_mins = 0 AND operating_cost > 0 + AND hour_rate > 0 AND docstatus = 1 AND parenttype = "BOM" + ''') \ No newline at end of file From 71b7c63ec052d5ca182e2eba33ca8693fe517256 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 27 Aug 2021 11:12:24 +0530 Subject: [PATCH 60/98] feat: Ability to schedule onboarding and separation activities (#26738) * refactor: employee onboarding form clean-up * feat: ability to schedule onboarding / separation tasks * feat: skip holidays while setting boarding activity dates * chore: remove unused child table - Employee Onboarding Activity * fix: tests * fix: employee separation test --- .../employee_boarding_controller.py | 45 ++- .../employee_boarding_activity.json | 29 +- .../employee_onboarding.json | 50 ++- .../test_employee_onboarding.py | 8 +- .../employee_onboarding_activity/__init__.py | 0 .../employee_onboarding_activity.json | 290 ------------------ .../employee_onboarding_activity.py | 10 - .../employee_separation.json | 9 +- .../test_employee_separation.py | 4 +- .../doctype/salary_slip/test_salary_slip.py | 6 +- 10 files changed, 129 insertions(+), 322 deletions(-) delete mode 100644 erpnext/hr/doctype/employee_onboarding_activity/__init__.py delete mode 100644 erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json delete mode 100644 erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py diff --git a/erpnext/controllers/employee_boarding_controller.py b/erpnext/controllers/employee_boarding_controller.py index 1898222916..f43c80416f 100644 --- a/erpnext/controllers/employee_boarding_controller.py +++ b/erpnext/controllers/employee_boarding_controller.py @@ -5,7 +5,9 @@ import frappe from frappe import _ from frappe.desk.form import assign_to from frappe.model.document import Document -from frappe.utils import flt, unique +from frappe.utils import flt, unique, add_days +from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday +from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee class EmployeeBoardingController(Document): ''' @@ -41,10 +43,14 @@ class EmployeeBoardingController(Document): def create_task_and_notify_user(self): # create the task for the given project and assign to the concerned person + holiday_list = self.get_holiday_list() + for activity in self.activities: if activity.task: continue + dates = self.get_task_dates(activity, holiday_list) + task = frappe.get_doc({ 'doctype': 'Task', 'project': self.project, @@ -52,7 +58,9 @@ class EmployeeBoardingController(Document): 'description': activity.description, 'department': self.department, 'company': self.company, - 'task_weight': activity.task_weight + 'task_weight': activity.task_weight, + 'exp_start_date': dates[0], + 'exp_end_date': dates[1] }).insert(ignore_permissions=True) activity.db_set('task', task.name) @@ -79,6 +87,36 @@ class EmployeeBoardingController(Document): if users: self.assign_task_to_users(task, users) + def get_holiday_list(self): + if self.doctype == 'Employee Separation': + return get_holiday_list_for_employee(self.employee) + else: + if self.employee: + return get_holiday_list_for_employee(self.employee) + else: + if not self.holiday_list: + frappe.throw(_('Please set the Holiday List.'), frappe.MandatoryError) + else: + return self.holiday_list + + def get_task_dates(self, activity, holiday_list): + start_date = end_date = None + + if activity.begin_on: + start_date = add_days(self.boarding_begins_on, activity.begin_on) + start_date = self.update_if_holiday(start_date, holiday_list) + + if activity.duration: + end_date = add_days(self.boarding_begins_on, activity.begin_on + activity.duration) + end_date = self.update_if_holiday(end_date, holiday_list) + + return [start_date, end_date] + + def update_if_holiday(self, date, holiday_list): + while is_holiday(holiday_list, date): + date = add_days(date, 1) + return date + def assign_task_to_users(self, task, users): for user in users: args = { @@ -103,7 +141,8 @@ class EmployeeBoardingController(Document): @frappe.whitelist() def get_onboarding_details(parent, parenttype): return frappe.get_all('Employee Boarding Activity', - fields=['activity_name', 'role', 'user', 'required_for_employee_creation', 'description', 'task_weight'], + fields=['activity_name', 'role', 'user', 'required_for_employee_creation', + 'description', 'task_weight', 'begin_on', 'duration'], filters={'parent': parent, 'parenttype': parenttype}, order_by= 'idx') diff --git a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json index 65792b42fb..044a5a9886 100644 --- a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json +++ b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2018-05-09 05:37:18.439763", "doctype": "DocType", "editable_grid": 1, @@ -7,6 +8,8 @@ "activity_name", "user", "role", + "begin_on", + "duration", "column_break_3", "task", "task_weight", @@ -16,12 +19,16 @@ ], "fields": [ { + "columns": 3, "fieldname": "activity_name", "fieldtype": "Data", "in_list_view": 1, - "label": "Activity Name" + "label": "Activity Name", + "reqd": 1 }, { + "columns": 2, + "depends_on": "eval:!doc.role", "fieldname": "user", "fieldtype": "Link", "in_list_view": 1, @@ -29,9 +36,10 @@ "options": "User" }, { + "columns": 1, + "depends_on": "eval:!doc.user", "fieldname": "role", "fieldtype": "Link", - "in_list_view": 1, "label": "Role", "options": "Role" }, @@ -67,10 +75,25 @@ "fieldname": "description", "fieldtype": "Text Editor", "label": "Description" + }, + { + "columns": 2, + "fieldname": "duration", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Duration (Days)" + }, + { + "columns": 2, + "fieldname": "begin_on", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Begin On (Days)" } ], "istable": 1, - "modified": "2019-06-03 19:22:42.965762", + "links": [], + "modified": "2021-07-30 15:55:22.470102", "modified_by": "Administrator", "module": "HR", "name": "Employee Boarding Activity", diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json index 673e228395..fd877a68d8 100644 --- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json @@ -8,20 +8,24 @@ "field_order": [ "job_applicant", "job_offer", - "employee_name", - "employee", - "date_of_joining", - "boarding_status", - "notify_users_by_email", - "column_break_7", "employee_onboarding_template", + "column_break_7", "company", + "boarding_status", + "project", + "details_section", + "employee", + "employee_name", "department", "designation", "employee_grade", - "project", + "holiday_list", + "column_break_13", + "date_of_joining", + "boarding_begins_on", "table_for_activity", "activities", + "notify_users_by_email", "amended_from" ], "fields": [ @@ -58,7 +62,8 @@ "fieldname": "date_of_joining", "fieldtype": "Date", "in_list_view": 1, - "label": "Date of Joining" + "label": "Date of Joining", + "reqd": 1 }, { "allow_on_submit": 1, @@ -90,7 +95,8 @@ "fieldname": "company", "fieldtype": "Link", "label": "Company", - "options": "Company" + "options": "Company", + "reqd": 1 }, { "fieldname": "department", @@ -121,7 +127,8 @@ }, { "fieldname": "table_for_activity", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Onboarding Activities" }, { "allow_on_submit": 1, @@ -138,11 +145,32 @@ "options": "Employee Onboarding", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "details_section", + "fieldtype": "Section Break", + "label": "Employee Details" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "fieldname": "boarding_begins_on", + "fieldtype": "Date", + "label": "Onboarding Begins On", + "reqd": 1 + }, + { + "fieldname": "holiday_list", + "fieldtype": "Link", + "label": "Holiday List", + "options": "Holiday List" } ], "is_submittable": 1, "links": [], - "modified": "2021-06-03 18:01:51.097927", + "modified": "2021-07-30 14:55:04.560683", "modified_by": "Administrator", "module": "HR", "name": "Employee Onboarding", diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py index 0445270b9f..ea46aa24a6 100644 --- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py +++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py @@ -5,8 +5,9 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import nowdate +from frappe.utils import getdate from erpnext.hr.doctype.employee_onboarding.employee_onboarding import make_employee +from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list from erpnext.hr.doctype.employee_onboarding.employee_onboarding import IncompleteTaskError from erpnext.hr.doctype.job_offer.test_job_offer import create_job_offer @@ -46,7 +47,7 @@ class TestEmployeeOnboarding(unittest.TestCase): onboarding.reload() employee = make_employee(onboarding.name) employee.first_name = employee.employee_name - employee.date_of_joining = nowdate() + employee.date_of_joining = getdate() employee.date_of_birth = '1990-05-08' employee.gender = 'Female' employee.insert() @@ -82,11 +83,14 @@ def get_job_offer(applicant_name): def create_employee_onboarding(): applicant = get_job_applicant() job_offer = get_job_offer(applicant.name) + holiday_list = make_holiday_list() onboarding = frappe.new_doc('Employee Onboarding') onboarding.job_applicant = applicant.name onboarding.job_offer = job_offer.name + onboarding.date_of_joining = onboarding.boarding_begins_on = getdate() onboarding.company = '_Test Company' + onboarding.holiday_list = holiday_list onboarding.designation = 'Researcher' onboarding.append('activities', { 'activity_name': 'Assign ID Card', diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/hr/doctype/employee_onboarding_activity/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json b/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json deleted file mode 100644 index 4e91b72384..0000000000 --- a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json +++ /dev/null @@ -1,290 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-05-09 05:37:18.439763", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "activity_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Activity Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "user", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "User", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "role", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Role", - "length": 0, - "no_copy": 0, - "options": "Role", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval: doc.parenttype == \"Employee Onboarding\"", - "fieldname": "completed", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Completed", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "required_for_employee_creation", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Required for Employee Creation", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-05-09 06:15:41.768236", - "modified_by": "Administrator", - "module": "HR", - "name": "Employee Onboarding Activity", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py b/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py deleted file mode 100644 index d170631819..0000000000 --- a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, 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 EmployeeOnboardingActivity(Document): - pass diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.json b/erpnext/hr/doctype/employee_separation/employee_separation.json index c10da5c35e..c240493e82 100644 --- a/erpnext/hr/doctype/employee_separation/employee_separation.json +++ b/erpnext/hr/doctype/employee_separation/employee_separation.json @@ -15,6 +15,7 @@ "company", "boarding_status", "resignation_letter_date", + "boarding_begins_on", "project", "table_for_activity", "employee_separation_template", @@ -144,11 +145,17 @@ "options": "Employee Separation", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "boarding_begins_on", + "fieldtype": "Date", + "label": "Separation Begins On", + "reqd": 1 } ], "is_submittable": 1, "links": [], - "modified": "2021-06-03 18:02:54.007313", + "modified": "2021-07-30 14:03:51.218791", "modified_by": "Administrator", "module": "HR", "name": "Employee Separation", diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py index d63501a931..2c11cbbe2c 100644 --- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py +++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +from frappe.utils import getdate import unittest test_dependencies = ['Employee Onboarding'] @@ -34,9 +35,10 @@ class TestEmployeeSeparation(unittest.TestCase): doc.delete() def create_employee_separation(): - employee = frappe.db.get_value('Employee', {'status': 'Active'}) + employee = frappe.db.get_value('Employee', {'status': 'Active', 'company': '_Test Company'}) separation = frappe.new_doc('Employee Separation') separation.employee = employee + separation.boarding_begins_on = getdate() separation.company = '_Test Company' separation.append('activities', { 'activity_name': 'Deactivate Employee', diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index d730fcf1fa..636ec0b5a6 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -828,7 +828,8 @@ def setup_test(): def make_holiday_list(): fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company()) - if not frappe.db.get_value("Holiday List", "Salary Slip Test Holiday List"): + holiday_list = frappe.db.exists("Holiday List", "Salary Slip Test Holiday List") + if not holiday_list: holiday_list = frappe.get_doc({ "doctype": "Holiday List", "holiday_list_name": "Salary Slip Test Holiday List", @@ -838,3 +839,6 @@ def make_holiday_list(): }).insert() holiday_list.get_weekly_off_dates() holiday_list.save() + holiday_list = holiday_list.name + + return holiday_list From d701ad5313bf2f33a9e8125f24bbf4c9b49a8525 Mon Sep 17 00:00:00 2001 From: Anupam Date: Fri, 27 Aug 2021 13:09:52 +0530 Subject: [PATCH 61/98] fix: using frm instead of cur_frm --- erpnext/crm/doctype/lead/lead.js | 38 ++++++++++++------------ erpnext/crm/doctype/prospect/prospect.js | 21 +++++++------ erpnext/crm/doctype/prospect/prospect.py | 4 +++ 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index dfb3b094dc..95cf03241b 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -51,7 +51,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller } } - add_lead_to_prospect () { + add_lead_to_prospect (frm) { frappe.prompt([ { fieldname: 'prospect', @@ -65,12 +65,12 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller frappe.call({ method: 'erpnext.crm.doctype.lead.lead.add_lead_to_prospect', args: { - 'lead': cur_frm.doc.name, + 'lead': frm.doc.name, 'prospect': data.prospect }, callback: function(r) { if (!r.exc) { - cur_frm.reload_doc(); + frm.reload_doc(); } }, freeze: true, @@ -79,41 +79,41 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller }, __('Add Lead to Prospect'), __('Add')); } - make_customer () { + make_customer (frm) { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.lead.lead.make_customer", - frm: cur_frm + frm: frm }) } - make_opportunity () { + make_opportunity (frm) { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.lead.lead.make_opportunity", - frm: cur_frm + frm: frm }) } - make_quotation () { + make_quotation (frm) { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.lead.lead.make_quotation", - frm: cur_frm + frm: frm }) } - make_prospect () { + make_prospect (frm) { frappe.model.with_doctype("Prospect", function() { let prospect = frappe.model.get_new_doc("Prospect"); - prospect.company_name = cur_frm.doc.company_name; - prospect.no_of_employees = cur_frm.doc.no_of_employees; - prospect.industry = cur_frm.doc.industry; - prospect.market_segment = cur_frm.doc.market_segment; - prospect.territory = cur_frm.doc.territory; - prospect.fax = cur_frm.doc.fax; - prospect.website = cur_frm.doc.website; - prospect.prospect_owner = cur_frm.doc.lead_owner; + prospect.company_name = frm.doc.company_name; + prospect.no_of_employees = frm.doc.no_of_employees; + prospect.industry = frm.doc.industry; + prospect.market_segment = frm.doc.market_segment; + prospect.territory = frm.doc.territory; + prospect.fax = frm.doc.fax; + prospect.website = frm.doc.website; + prospect.prospect_owner = frm.doc.lead_owner; let lead_prospect_row = frappe.model.add_child(prospect, 'prospect_lead'); - lead_prospect_row.lead = cur_frm.doc.name; + lead_prospect_row.lead = frm.doc.name; frappe.set_route("Form", "Prospect", prospect.name); }); diff --git a/erpnext/crm/doctype/prospect/prospect.js b/erpnext/crm/doctype/prospect/prospect.js index 814fc2b6db..67018e1ef9 100644 --- a/erpnext/crm/doctype/prospect/prospect.js +++ b/erpnext/crm/doctype/prospect/prospect.js @@ -2,29 +2,28 @@ // For license information, please see license.txt frappe.ui.form.on('Prospect', { - refresh () { - if (!cur_frm.is_new() && frappe.boot.user.can_create.includes("Customer")) { - cur_frm.add_custom_button(__("Customer"), function() { + refresh (frm) { + if (!frm.is_new() && frappe.boot.user.can_create.includes("Customer")) { + frm.add_custom_button(__("Customer"), function() { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.prospect.prospect.make_customer", - frm: cur_frm + frm: frm }); }, __("Create")); } - if (!cur_frm.is_new() && frappe.boot.user.can_create.includes("Opportunity")) { - cur_frm.add_custom_button(__("Opportunity"), function() { + if (!frm.is_new() && frappe.boot.user.can_create.includes("Opportunity")) { + frm.add_custom_button(__("Opportunity"), function() { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.prospect.prospect.make_opportunity", - frm: cur_frm + frm: frm }); }, __("Create")); } - if (!cur_frm.is_new()) { - frappe.contacts.render_address_and_contact(cur_frm); - cur_frm.trigger('render_contact_day_html'); + if (!frm.is_new()) { + frappe.contacts.render_address_and_contact(frm); } else { - frappe.contacts.clear_address_and_contact(cur_frm); + frappe.contacts.clear_address_and_contact(frm); } } }); diff --git a/erpnext/crm/doctype/prospect/prospect.py b/erpnext/crm/doctype/prospect/prospect.py index dc165af825..5f5815de5e 100644 --- a/erpnext/crm/doctype/prospect/prospect.py +++ b/erpnext/crm/doctype/prospect/prospect.py @@ -4,8 +4,12 @@ import frappe from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc +from frappe.contacts.address_and_contact import load_address_and_contact class Prospect(Document): + def onload(self): + load_address_and_contact(self) + def validate(self): self.update_lead_details() From 987746592c73b7c163df11e8f97e4a94c984ec6c Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 27 Aug 2021 15:41:26 +0530 Subject: [PATCH 62/98] fix: period closing voucher tests (#27198) --- .../test_period_closing_voucher.py | 138 ++++++++---------- 1 file changed, 58 insertions(+), 80 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 2d1939131c..2a636bb338 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -13,59 +13,49 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal class TestPeriodClosingVoucher(unittest.TestCase): def test_closing_entry(self): - year_start_date = get_fiscal_year(today(), company="_Test Company")[1] + frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") - make_journal_entry("_Test Bank - _TC", "Sales - _TC", 400, - "_Test Cost Center - _TC", posting_date=now(), submit=True) + company = create_company() + cost_center = create_cost_center('Test Cost Center 1') - make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", 600, "_Test Cost Center - _TC", posting_date=now(), submit=True) + jv1 = make_journal_entry( + amount=400, + account1="Cash - TPC", + account2="Sales - TPC", + cost_center=cost_center, + posting_date=now(), + save=False + ) + jv1.company = company + jv1.save() + jv1.submit() - random_expense_account = frappe.db.sql(""" - select t1.account, - sum(t1.debit) - sum(t1.credit) as balance, - sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) \ - as balance_in_account_currency - from `tabGL Entry` t1, `tabAccount` t2 - where t1.account = t2.name and t2.root_type = 'Expense' - and t2.docstatus < 2 and t2.company = '_Test Company' - and t1.posting_date between %s and %s - group by t1.account - having sum(t1.debit) > sum(t1.credit) - limit 1""", (year_start_date, today()), as_dict=True) - - profit_or_loss = frappe.db.sql("""select sum(t1.debit) - sum(t1.credit) as balance - from `tabGL Entry` t1, `tabAccount` t2 - where t1.account = t2.name and t2.report_type = 'Profit and Loss' - and t2.docstatus < 2 and t2.company = '_Test Company' - and t1.posting_date between %s and %s""", (year_start_date, today())) - - profit_or_loss = flt(profit_or_loss[0][0]) if profit_or_loss else 0 + jv2 = make_journal_entry( + amount=600, + account1="Cost of Goods Sold - TPC", + account2="Cash - TPC", + cost_center=cost_center, + posting_date=now(), + save=False + ) + jv2.company = company + jv2.save() + jv2.submit() pcv = self.make_period_closing_voucher() + surplus_account = pcv.closing_account_head - # Check value for closing account - gle_amount_for_closing_account = frappe.db.sql("""select debit - credit - from `tabGL Entry` where voucher_type='Period Closing Voucher' and voucher_no=%s - and account = '_Test Account Reserves and Surplus - _TC'""", pcv.name) + expected_gle = ( + ('Cost of Goods Sold - TPC', 0.0, 600.0), + (surplus_account, 600.0, 400.0), + ('Sales - TPC', 400.0, 0.0) + ) - gle_amount_for_closing_account = flt(gle_amount_for_closing_account[0][0]) \ - if gle_amount_for_closing_account else 0 + pcv_gle = frappe.db.sql(""" + select account, debit, credit from `tabGL Entry` where voucher_no=%s order by account + """, (pcv.name)) - self.assertEqual(gle_amount_for_closing_account, profit_or_loss) - - if random_expense_account: - # Check posted value for teh above random_expense_account - gle_for_random_expense_account = frappe.db.sql(""" - select sum(debit - credit) as amount, - sum(debit_in_account_currency - credit_in_account_currency) as amount_in_account_currency - from `tabGL Entry` - where voucher_type='Period Closing Voucher' and voucher_no=%s and account =%s""", - (pcv.name, random_expense_account[0].account), as_dict=True) - - self.assertEqual(gle_for_random_expense_account[0].amount, -1*random_expense_account[0].balance) - self.assertEqual(gle_for_random_expense_account[0].amount_in_account_currency, - -1*random_expense_account[0].balance_in_account_currency) + self.assertEqual(pcv_gle, expected_gle) def test_cost_center_wise_posting(self): frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") @@ -93,31 +83,23 @@ class TestPeriodClosingVoucher(unittest.TestCase): debit_to="Debtors - TPC" ) - pcv = frappe.get_doc({ - "transaction_date": today(), - "posting_date": today(), - "fiscal_year": get_fiscal_year(today())[0], - "company": "Test PCV Company", - "cost_center_wise_pnl": 1, - "closing_account_head": surplus_account, - "remarks": "Test", - "doctype": "Period Closing Voucher" - }) - pcv.insert() - pcv.submit() + pcv = self.make_period_closing_voucher() + surplus_account = pcv.closing_account_head expected_gle = ( - ('Sales - TPC', 200.0, 0.0, cost_center2), + (surplus_account, 0.0, 400.0, cost_center1), (surplus_account, 0.0, 200.0, cost_center2), ('Sales - TPC', 400.0, 0.0, cost_center1), - (surplus_account, 0.0, 400.0, cost_center1) + ('Sales - TPC', 200.0, 0.0, cost_center2), ) pcv_gle = frappe.db.sql(""" - select account, debit, credit, cost_center from `tabGL Entry` where voucher_no=%s + select account, debit, credit, cost_center + from `tabGL Entry` where voucher_no=%s + order by account, cost_center """, (pcv.name)) - self.assertTrue(pcv_gle, expected_gle) + self.assertEqual(pcv_gle, expected_gle) def test_period_closing_with_finance_book_entries(self): frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") @@ -146,39 +128,35 @@ class TestPeriodClosingVoucher(unittest.TestCase): jv.save() jv.submit() - pcv = frappe.get_doc({ - "transaction_date": today(), - "posting_date": today(), - "fiscal_year": get_fiscal_year(today())[0], - "company": company, - "closing_account_head": surplus_account, - "remarks": "Test", - "doctype": "Period Closing Voucher" - }) - pcv.insert() - pcv.submit() + pcv = self.make_period_closing_voucher() + surplus_account = pcv.closing_account_head expected_gle = ( - (surplus_account, 0.0, 400.0, ''), + (surplus_account, 0.0, 400.0, None), (surplus_account, 0.0, 400.0, jv.finance_book), - ('Sales - TPC', 400.0, 0.0, ''), + ('Sales - TPC', 400.0, 0.0, None), ('Sales - TPC', 400.0, 0.0, jv.finance_book) ) pcv_gle = frappe.db.sql(""" - select account, debit, credit, finance_book from `tabGL Entry` where voucher_no=%s + select account, debit, credit, finance_book + from `tabGL Entry` where voucher_no=%s + order by account, finance_book """, (pcv.name)) - self.assertTrue(pcv_gle, expected_gle) + self.assertEqual(pcv_gle, expected_gle) def make_period_closing_voucher(self): + surplus_account = create_account() + cost_center = create_cost_center("Test Cost Center 1") pcv = frappe.get_doc({ "doctype": "Period Closing Voucher", - "closing_account_head": "_Test Account Reserves and Surplus - _TC", - "company": "_Test Company", - "fiscal_year": get_fiscal_year(today(), company="_Test Company")[0], + "transaction_date": today(), "posting_date": today(), - "cost_center": "_Test Cost Center - _TC", + "company": "Test PCV Company", + "fiscal_year": get_fiscal_year(today(), company="Test PCV Company")[0], + "cost_center": cost_center, + "closing_account_head": surplus_account, "remarks": "test" }) pcv.insert() From a285b5b78b8080cda0a917cd45cc06e7de4e6ff9 Mon Sep 17 00:00:00 2001 From: Kamal Johnson Date: Fri, 27 Aug 2021 15:47:11 +0530 Subject: [PATCH 63/98] feat: Accounts, Selling & Assets Onboarding cleanup (#27112) * refactor: company form fields arrangement * refactor: accounts onboarding steps * refactor: re-arrange fields & modify labels in company form * feat: onbording steps for accounts module * fix: updated onboarding steps for selling module * fix: remove unwanted steps from accounts * feat: add more onboarding steps * feat: Refactor onboarding-module for accounting workspace * feat: Add basic asset onboarding steps * feat: Add basic asset item form tour * feat: Add basic selling onboarding-steps * feat: Add basic form tour for purchase recipt * feat: Add basic form tour for existing asset * feat: Adding asset category form tour * feat: Add form tour for asset category * feat: Refactor sales taxes and charges template form tour with new method * refactor: Remove old form tour for accounts settings * refactor: Change asset category form tour with new tour method * refactor: Change asset item form tour with new tour method * refactor: Change purchase receipt form tour with new tour method * refactor: Change asset form tour with new tour method * refactor: Remove old form tour for selling settings * refactor: general fix/typo in onboarding steps * Apply suggestions from code review Co-authored-by: Nabin Hait * refactor: Remove old form tour method for sales order. * fix: removed duplicate section break * fix: Removed duplicate fields Co-authored-by: Saqib Ansari Co-authored-by: Anuja Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> Co-authored-by: Nabin Hait --- .../accounts_settings/accounts_settings.js | 43 ------ .../sales_taxes_and_charges_template.js | 22 --- .../accounts_settings/accounts_settings.json | 113 ++++++++++++++++ .../purchase_invoice/purchase_invoice.json | 96 ++++++++++++++ .../sales_taxes_and_charges_template.json | 65 +++++++++ .../module_onboarding/accounts/accounts.json | 16 +-- .../accounts_settings/accounts_settings.json | 21 +++ .../chart_of_accounts/chart_of_accounts.json | 11 +- .../onboarding_step/company/company.json | 22 +++ ...cost_centers_for_report_and_budgeting.json | 21 +++ .../create_your_first_purchase_invoice.json | 7 +- .../financial_statements.json | 23 ++++ .../setup_taxes/setup_taxes.json | 8 +- .../updating_opening_balances.json | 22 +++ erpnext/assets/form_tour/asset/asset.json | 125 ++++++++++++++++++ .../asset_category/asset_category.json | 65 +++++++++ .../module_onboarding/assets/assets.json | 12 +- .../asset_category/asset_category.json | 21 +++ .../asset_item/asset_item.json | 21 +++ .../asset_purchase/asset_purchase.json | 21 +++ .../existing_asset/existing_asset.json | 21 +++ .../fixed_asset_accounts.json | 21 +++ .../selling_settings/selling_settings.js | 23 ---- .../form_tour/sales_order/sales_order.json | 97 ++++++++++++++ .../selling_settings/selling_settings.json | 65 +++++++++ .../module_onboarding/selling/selling.json | 23 +--- .../create_a_customer/create_a_customer.json | 2 +- .../create_a_product/create_a_product.json | 4 +- .../create_a_quotation.json | 2 +- .../create_a_sales_order.json | 21 +++ .../introduction_to_selling.json | 2 +- .../sales_order/sales_order.json | 21 +++ .../selling_settings/selling_settings.json | 8 +- .../setup_your_warehouse.json | 4 +- erpnext/setup/doctype/company/company.json | 20 +-- erpnext/stock/form_tour/item/item.json | 89 +++++++++++++ .../purchase_receipt/purchase_receipt.json | 41 ++++++ 37 files changed, 1066 insertions(+), 153 deletions(-) create mode 100644 erpnext/accounts/form_tour/accounts_settings/accounts_settings.json create mode 100644 erpnext/accounts/form_tour/purchase_invoice/purchase_invoice.json create mode 100644 erpnext/accounts/form_tour/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json create mode 100644 erpnext/accounts/onboarding_step/accounts_settings/accounts_settings.json create mode 100644 erpnext/accounts/onboarding_step/company/company.json create mode 100644 erpnext/accounts/onboarding_step/cost_centers_for_report_and_budgeting/cost_centers_for_report_and_budgeting.json create mode 100644 erpnext/accounts/onboarding_step/financial_statements/financial_statements.json create mode 100644 erpnext/accounts/onboarding_step/updating_opening_balances/updating_opening_balances.json create mode 100644 erpnext/assets/form_tour/asset/asset.json create mode 100644 erpnext/assets/form_tour/asset_category/asset_category.json create mode 100644 erpnext/assets/onboarding_step/asset_category/asset_category.json create mode 100644 erpnext/assets/onboarding_step/asset_item/asset_item.json create mode 100644 erpnext/assets/onboarding_step/asset_purchase/asset_purchase.json create mode 100644 erpnext/assets/onboarding_step/existing_asset/existing_asset.json create mode 100644 erpnext/assets/onboarding_step/fixed_asset_accounts/fixed_asset_accounts.json create mode 100644 erpnext/selling/form_tour/sales_order/sales_order.json create mode 100644 erpnext/selling/form_tour/selling_settings/selling_settings.json create mode 100644 erpnext/selling/onboarding_step/create_a_sales_order/create_a_sales_order.json create mode 100644 erpnext/selling/onboarding_step/sales_order/sales_order.json create mode 100644 erpnext/stock/form_tour/item/item.json create mode 100644 erpnext/stock/form_tour/purchase_receipt/purchase_receipt.json diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.js b/erpnext/accounts/doctype/accounts_settings/accounts_settings.js index e44af3a916..0627675de7 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.js +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.js @@ -6,46 +6,3 @@ frappe.ui.form.on('Accounts Settings', { } }); - -frappe.tour['Accounts Settings'] = [ - { - fieldname: "acc_frozen_upto", - title: "Accounts Frozen Upto", - description: __("Freeze accounting transactions up to specified date, nobody can make/modify entry except the specified Role."), - }, - { - fieldname: "frozen_accounts_modifier", - title: "Role Allowed to Set Frozen Accounts & Edit Frozen Entries", - description: __("Users with this Role are allowed to set frozen accounts and create/modify accounting entries against frozen accounts.") - }, - { - fieldname: "determine_address_tax_category_from", - title: "Determine Address Tax Category From", - description: __("Tax category can be set on Addresses. An address can be Shipping or Billing address. Set which addres to select when applying Tax Category.") - }, - { - fieldname: "over_billing_allowance", - title: "Over Billing Allowance Percentage", - description: __("The percentage by which you can overbill transactions. For example, if the order value is $100 for an Item and percentage here is set as 10% then you are allowed to bill for $110.") - }, - { - fieldname: "credit_controller", - title: "Credit Controller", - description: __("Select the role that is allowed to submit transactions that exceed credit limits set. The credit limit can be set in the Customer form.") - }, - { - fieldname: "make_payment_via_journal_entry", - title: "Make Payment via Journal Entry", - description: __("When checked, if user proceeds to make payment from an invoice, the system will open a Journal Entry instead of a Payment Entry.") - }, - { - fieldname: "unlink_payment_on_cancellation_of_invoice", - title: "Unlink Payment on Cancellation of Invoice", - description: __("If checked, system will unlink the payment against the respective invoice.") - }, - { - fieldname: "unlink_advance_payment_on_cancelation_of_order", - title: "Unlink Advance Payment on Cancellation of Order", - description: __("Similar to the previous option, this unlinks any advance payments made against Purchase/Sales Orders.") - } -]; diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js index 0e011883b1..97a6fdd336 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js @@ -5,25 +5,3 @@ cur_frm.cscript.tax_table = "Sales Taxes and Charges"; {% include "erpnext/public/js/controllers/accounts.js" %} -frappe.tour['Sales Taxes and Charges Template'] = [ - { - fieldname: "title", - title: __("Title"), - description: __("A name by which you will identify this template. You can change this later."), - }, - { - fieldname: "company", - title: __("Company"), - description: __("Company for which this tax template will be applicable"), - }, - { - fieldname: "is_default", - title: __("Is this Default?"), - description: __("Set this template as the default for all sales transactions"), - }, - { - fieldname: "taxes", - title: __("Taxes Table"), - description: __("You can add a row for a tax rule here. These rules can be applied on the net total, or can be a flat amount."), - } -]; diff --git a/erpnext/accounts/form_tour/accounts_settings/accounts_settings.json b/erpnext/accounts/form_tour/accounts_settings/accounts_settings.json new file mode 100644 index 0000000000..e2bf50d20a --- /dev/null +++ b/erpnext/accounts/form_tour/accounts_settings/accounts_settings.json @@ -0,0 +1,113 @@ +{ + "creation": "2021-06-29 17:00:18.273054", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-06-29 17:00:26.145996", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Accounts Settings", + "owner": "Administrator", + "reference_doctype": "Accounts Settings", + "save_on_complete": 0, + "steps": [ + { + "description": "The percentage by which you can overbill transactions. For example, if the order value is $100 for an Item and percentage here is set as 10% then you are allowed to bill for $110.", + "field": "", + "fieldname": "over_billing_allowance", + "fieldtype": "Currency", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Over Billing Allowance (%)", + "parent_field": "", + "position": "Right", + "title": "Over Billing Allowance Percentage" + }, + { + "description": "Select the role that is allowed to overbill a transactions.", + "field": "", + "fieldname": "role_allowed_to_over_bill", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Role Allowed to Over Bill ", + "parent_field": "", + "position": "Right", + "title": "Role Allowed to Over Bill" + }, + { + "description": "If checked, system will unlink the payment against the respective invoice.", + "field": "", + "fieldname": "unlink_payment_on_cancellation_of_invoice", + "fieldtype": "Check", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Unlink Payment on Cancellation of Invoice", + "parent_field": "", + "position": "Bottom", + "title": "Unlink Payment on Cancellation of Invoice" + }, + { + "description": "Similar to the previous option, this unlinks any advance payments made against Purchase/Sales Orders.", + "field": "", + "fieldname": "unlink_advance_payment_on_cancelation_of_order", + "fieldtype": "Check", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Unlink Advance Payment on Cancellation of Order", + "parent_field": "", + "position": "Bottom", + "title": "Unlink Advance Payment on Cancellation of Order" + }, + { + "description": "Tax category can be set on Addresses. An address can be Shipping or Billing address. Set which addres to select when applying Tax Category.", + "field": "", + "fieldname": "determine_address_tax_category_from", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Determine Address Tax Category From", + "parent_field": "", + "position": "Right", + "title": "Determine Address Tax Category From" + }, + { + "description": "Freeze accounting transactions up to specified date, nobody can make/modify entry except the specified Role.", + "field": "", + "fieldname": "acc_frozen_upto", + "fieldtype": "Date", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Accounts Frozen Till Date", + "parent_field": "", + "position": "Right", + "title": "Accounts Frozen Upto" + }, + { + "description": "Users with this Role are allowed to set frozen accounts and create/modify accounting entries against frozen accounts.", + "field": "", + "fieldname": "frozen_accounts_modifier", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Role Allowed to Set Frozen Accounts and Edit Frozen Entries", + "parent_field": "", + "position": "Right", + "title": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries" + }, + { + "description": "Select the role that is allowed to submit transactions that exceed credit limits set. The credit limit can be set in the Customer form.", + "field": "", + "fieldname": "credit_controller", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Credit Controller", + "parent_field": "", + "position": "Left", + "title": "Credit Controller" + } + ], + "title": "Accounts Settings" +} \ No newline at end of file diff --git a/erpnext/accounts/form_tour/purchase_invoice/purchase_invoice.json b/erpnext/accounts/form_tour/purchase_invoice/purchase_invoice.json new file mode 100644 index 0000000000..2dffcd1c0e --- /dev/null +++ b/erpnext/accounts/form_tour/purchase_invoice/purchase_invoice.json @@ -0,0 +1,96 @@ +{ + "creation": "2021-06-29 16:31:48.558826", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-06-29 16:31:48.558826", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Purchase Invoice", + "owner": "Administrator", + "reference_doctype": "Purchase Invoice", + "save_on_complete": 1, + "steps": [ + { + "description": "Select Supplier", + "field": "", + "fieldname": "supplier", + "fieldtype": "Link", + "has_next_condition": 1, + "is_table_field": 0, + "label": "Supplier", + "next_step_condition": "supplier", + "parent_field": "", + "position": "Right", + "title": "Select Supplier" + }, + { + "description": "Add items in the table", + "field": "", + "fieldname": "items", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Items", + "parent_field": "", + "position": "Bottom", + "title": "List of Items" + }, + { + "child_doctype": "Purchase Invoice Item", + "description": "Select an item", + "field": "", + "fieldname": "item_code", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 1, + "label": "Item", + "parent_field": "", + "parent_fieldname": "items", + "position": "Right", + "title": "Select Item" + }, + { + "child_doctype": "Purchase Invoice Item", + "description": "Enter the quantity", + "field": "", + "fieldname": "qty", + "fieldtype": "Float", + "has_next_condition": 0, + "is_table_field": 1, + "label": "Accepted Qty", + "parent_field": "", + "parent_fieldname": "items", + "position": "Right", + "title": "Enter Quantity" + }, + { + "child_doctype": "Purchase Invoice Item", + "description": "Enter rate of the item", + "field": "", + "fieldname": "rate", + "fieldtype": "Currency", + "has_next_condition": 0, + "is_table_field": 1, + "label": "Rate", + "parent_field": "", + "parent_fieldname": "items", + "position": "Right", + "title": "Enter Rate" + }, + { + "description": "You can add taxes here", + "field": "", + "fieldname": "taxes", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Purchase Taxes and Charges", + "parent_field": "", + "position": "Bottom", + "title": "Select taxes" + } + ], + "title": "Purchase Invoice" +} \ No newline at end of file diff --git a/erpnext/accounts/form_tour/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json b/erpnext/accounts/form_tour/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json new file mode 100644 index 0000000000..7de9ae1539 --- /dev/null +++ b/erpnext/accounts/form_tour/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json @@ -0,0 +1,65 @@ +{ + "creation": "2021-08-24 12:28:18.044902", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-08-24 12:28:18.044902", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Sales Taxes and Charges Template", + "owner": "Administrator", + "reference_doctype": "Sales Taxes and Charges Template", + "save_on_complete": 0, + "steps": [ + { + "description": "A name by which you will identify this template. You can change this later.", + "field": "", + "fieldname": "title", + "fieldtype": "Data", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Title", + "parent_field": "", + "position": "Bottom", + "title": "Title" + }, + { + "description": "Company for which this tax template will be applicable", + "field": "", + "fieldname": "company", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Company", + "parent_field": "", + "position": "Bottom", + "title": "Company" + }, + { + "description": "Set this template as the default for all sales transactions", + "field": "", + "fieldname": "is_default", + "fieldtype": "Check", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Default", + "parent_field": "", + "position": "Bottom", + "title": "Is this Default Tax Template?" + }, + { + "description": "You can add a row for a tax rule here. These rules can be applied on the net total, or can be a flat amount.", + "field": "", + "fieldname": "taxes", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Sales Taxes and Charges", + "parent_field": "", + "position": "Bottom", + "title": "Taxes Table" + } + ], + "title": "Sales Taxes and Charges Template" +} \ No newline at end of file diff --git a/erpnext/accounts/module_onboarding/accounts/accounts.json b/erpnext/accounts/module_onboarding/accounts/accounts.json index 6b5c5a1db8..2e0ab4305d 100644 --- a/erpnext/accounts/module_onboarding/accounts/accounts.json +++ b/erpnext/accounts/module_onboarding/accounts/accounts.json @@ -13,12 +13,15 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts", "idx": 0, "is_complete": 0, - "modified": "2020-10-30 15:41:15.547225", + "modified": "2021-08-13 11:59:35.690443", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts", "owner": "Administrator", "steps": [ + { + "step": "Company" + }, { "step": "Chart of Accounts" }, @@ -26,22 +29,19 @@ "step": "Setup Taxes" }, { - "step": "Create a Product" + "step": "Accounts Settings" }, { - "step": "Create a Supplier" + "step": "Cost Centers for Report and Budgeting" }, { "step": "Create Your First Purchase Invoice" }, { - "step": "Create a Customer" + "step": "Updating Opening Balances" }, { - "step": "Create Your First Sales Invoice" - }, - { - "step": "Configure Account Settings" + "step": "Financial Statements" } ], "subtitle": "Accounts, Invoices, Taxation, and more.", diff --git a/erpnext/accounts/onboarding_step/accounts_settings/accounts_settings.json b/erpnext/accounts/onboarding_step/accounts_settings/accounts_settings.json new file mode 100644 index 0000000000..3f44a73685 --- /dev/null +++ b/erpnext/accounts/onboarding_step/accounts_settings/accounts_settings.json @@ -0,0 +1,21 @@ +{ + "action": "Show Form Tour", + "action_label": "Take a quick walk-through of Accounts Settings", + "creation": "2021-06-29 16:42:03.400731", + "description": "# Account Settings\n\nIn ERPNext, Accounting features are configurable as per your business needs. Accounts Settings is the place to define some of your accounting preferences like:\n\n - Credit Limit and over billing settings\n - Taxation preferences\n - Deferred accounting preferences\n", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 1, + "is_skipped": 0, + "modified": "2021-08-13 11:50:06.227835", + "modified_by": "Administrator", + "name": "Accounts Settings", + "owner": "Administrator", + "reference_document": "Accounts Settings", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Accounts Settings", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json b/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json index fc49bd652b..67553baec7 100644 --- a/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json +++ b/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json @@ -1,10 +1,10 @@ { - "action": "Go to Page", - "action_label": "View Chart of Accounts", + "action": "Watch Video", + "action_label": "Learn more about Chart of Accounts", "callback_message": "You can continue with the onboarding after exploring this page", "callback_title": "Awesome Work", "creation": "2020-05-13 19:58:20.928127", - "description": "# Chart Of Accounts\n\nThe Chart of Accounts is the blueprint of the accounts in your organization.\nIt is a tree view of the names of the Accounts (Ledgers and Groups) that a Company requires to manage its books of accounts. ERPNext sets up a simple chart of accounts for each Company you create, but you can modify it according to your needs and legal requirements.\n\nFor each company, Chart of Accounts signifies the way to classify the accounting entries, mostly\nbased on statutory (tax, compliance to government regulations) requirements.\n\nThere's a brief video tutorial about chart of accounts in the next step.", + "description": "# Chart Of Accounts\n\nERPNext sets up a simple chart of accounts for each Company you create, but you can modify it according to business and legal requirements.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, @@ -12,7 +12,7 @@ "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-10-30 14:35:59.474920", + "modified": "2021-08-13 11:46:25.878506", "modified_by": "Administrator", "name": "Chart of Accounts", "owner": "Administrator", @@ -21,5 +21,6 @@ "show_form_tour": 0, "show_full_form": 0, "title": "Review Chart of Accounts", - "validate_action": 0 + "validate_action": 0, + "video_url": "https://www.youtube.com/embed/AcfMCT7wLLo" } \ No newline at end of file diff --git a/erpnext/accounts/onboarding_step/company/company.json b/erpnext/accounts/onboarding_step/company/company.json new file mode 100644 index 0000000000..4992e4d018 --- /dev/null +++ b/erpnext/accounts/onboarding_step/company/company.json @@ -0,0 +1,22 @@ +{ + "action": "Go to Page", + "action_label": "Let's Review your Company", + "creation": "2021-06-29 14:47:42.497318", + "description": "# Company\n\nIn ERPNext, you can also create multiple companies, and establish relationships (group/subsidiary) among them.\n\nWithin the company master, you can capture various default accounts for that Company and set crucial settings related to the accounting methodology followed for a company. \n", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-13 11:43:35.767341", + "modified_by": "Administrator", + "name": "Company", + "owner": "Administrator", + "path": "app/company", + "reference_document": "Company", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Review Company", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/onboarding_step/cost_centers_for_report_and_budgeting/cost_centers_for_report_and_budgeting.json b/erpnext/accounts/onboarding_step/cost_centers_for_report_and_budgeting/cost_centers_for_report_and_budgeting.json new file mode 100644 index 0000000000..252b075697 --- /dev/null +++ b/erpnext/accounts/onboarding_step/cost_centers_for_report_and_budgeting/cost_centers_for_report_and_budgeting.json @@ -0,0 +1,21 @@ +{ + "action": "Go to Page", + "action_label": "View Cost Center Tree", + "creation": "2021-07-12 12:02:05.726608", + "description": "# Cost Centers for Budgeting and Analysis\n\nWhile your Books of Accounts are framed to fulfill statutory requirements, you can set up Cost Center and Accounting Dimensions to address your companies reporting and budgeting requirements.\n\nClick here to learn more about how [Cost Center](https://docs.erpnext.com/docs/v13/user/manual/en/accounts/cost-center) and [Dimensions](https://docs.erpnext.com/docs/v13/user/manual/en/accounts/accounting-dimensions) allow you to get advanced financial analytics reports from ERPNext.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-13 11:55:08.510366", + "modified_by": "Administrator", + "name": "Cost Centers for Report and Budgeting", + "owner": "Administrator", + "path": "cost-center/view/tree", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Cost Centers for Budgeting and Analysis", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json b/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json index ddbc89ec0a..f4e298e701 100644 --- a/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json +++ b/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json @@ -1,14 +1,15 @@ { - "action": "Create Entry", + "action": "Show Form Tour", + "action_label": "Let\u2019s create your first Purchase Invoice", "creation": "2020-05-14 22:10:07.049704", - "description": "# What's a Purchase Invoice?\n\nA Purchase Invoice is a bill you receive from your Suppliers against which you need to make the payment.\nPurchase Invoice is the exact opposite of your Sales Invoice. Here you accrue expenses to your Supplier. \n\nThe following is what a typical purchase cycle looks like, however you can create a purchase invoice directly as well.\n\n![Purchase Flow](https://docs.erpnext.com/docs/assets/img/accounts/pi-flow.png)\n\n", + "description": "# Create your first Purchase Invoice\n\nA Purchase Invoice is a bill received from a Supplier for a product(s) or service(s) delivery to your company. You can track payables through Purchase Invoice and process Payment Entries against it.\n\nPurchase Invoices can also be created against a Purchase Order or Purchase Receipt.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-10-30 15:30:26.337773", + "modified": "2021-08-13 11:56:11.677253", "modified_by": "Administrator", "name": "Create Your First Purchase Invoice", "owner": "Administrator", diff --git a/erpnext/accounts/onboarding_step/financial_statements/financial_statements.json b/erpnext/accounts/onboarding_step/financial_statements/financial_statements.json new file mode 100644 index 0000000000..85cf9cdd7c --- /dev/null +++ b/erpnext/accounts/onboarding_step/financial_statements/financial_statements.json @@ -0,0 +1,23 @@ +{ + "action": "View Report", + "creation": "2021-07-12 12:08:47.026115", + "description": "# Financial Statements\n\nIn ERPNext, you can get crucial financial reports like [Balance Sheet] and [Profit and Loss] statements with a click of a button. You can run in the report for a different period and plot analytics charts premised on statement data. For more reports, check sections like Financial Statements, General Ledger, and Profitability reports.\n\n[Check Accounting reports](https://docs.erpnext.com/docs/v13/user/manual/en/accounts/accounting-reports)", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-13 11:59:18.767407", + "modified_by": "Administrator", + "name": "Financial Statements", + "owner": "Administrator", + "reference_report": "General Ledger", + "report_description": "General Ledger", + "report_reference_doctype": "GL Entry", + "report_type": "Script Report", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Financial Statements", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json b/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json index a4922013da..9f4c873e34 100644 --- a/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json +++ b/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json @@ -1,21 +1,21 @@ { "action": "Create Entry", - "action_label": "Make a Sales Tax Template", + "action_label": "Manage Sales Tax Templates", "creation": "2020-05-13 19:29:43.844463", - "description": "# Setting up Taxes\n\nAny sophisticated accounting system, including ERPNext will have automatic tax calculations for your transactions. These calculations are based on user defined rules in compliance to local rules and regulations.\n\nERPNext allows this via *Tax Templates*. These templates can be used in Sales Orders and Sales Invoices. Other types of charges that may apply to your invoices (like shipping, insurance etc.) can also be configured as taxes.\n\nFor Tax Accounts that you want to use in the tax templates, go to:\n\n`> Accounting > Taxes > Sales Taxes and Charges Template`\n\nYou can read more about these templates in our documentation [here](https://docs.erpnext.com/docs/user/manual/en/selling/sales-taxes-and-charges-template)\n", + "description": "# Setting up Taxes\n\nERPNext lets you configure your taxes so that they are automatically applied in your buying and selling transactions. You can configure them globally or even on Items. ERPNext taxes are pre-configured for most regions.\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-10-30 14:54:18.087383", + "modified": "2021-08-13 11:48:37.238610", "modified_by": "Administrator", "name": "Setup Taxes", "owner": "Administrator", "reference_document": "Sales Taxes and Charges Template", "show_form_tour": 1, "show_full_form": 1, - "title": "Lets create a Tax Template for Sales ", + "title": "Setting up Taxes", "validate_action": 0 } \ No newline at end of file diff --git a/erpnext/accounts/onboarding_step/updating_opening_balances/updating_opening_balances.json b/erpnext/accounts/onboarding_step/updating_opening_balances/updating_opening_balances.json new file mode 100644 index 0000000000..c0849a4ef5 --- /dev/null +++ b/erpnext/accounts/onboarding_step/updating_opening_balances/updating_opening_balances.json @@ -0,0 +1,22 @@ +{ + "action": "Watch Video", + "action_label": "Learn how to update opening balances", + "creation": "2021-07-12 11:53:50.525030", + "description": "# Updating Opening Balances\n\nOnce you close the financial statement in previous accounting software, you can update the same as opening in your ERPNext's Balance Sheet accounts. This will allow you to get complete financial statements from ERPNext in the coming years, and discontinue the parallel accounting system right away.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "intro_video_url": "https://www.youtube.com/embed/U5wPIvEn-0c", + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-13 11:56:45.483418", + "modified_by": "Administrator", + "name": "Updating Opening Balances", + "owner": "Administrator", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Updating Opening Balances", + "validate_action": 1, + "video_url": "https://www.youtube.com/embed/U5wPIvEn-0c" +} \ No newline at end of file diff --git a/erpnext/assets/form_tour/asset/asset.json b/erpnext/assets/form_tour/asset/asset.json new file mode 100644 index 0000000000..7c47a38bd1 --- /dev/null +++ b/erpnext/assets/form_tour/asset/asset.json @@ -0,0 +1,125 @@ +{ + "creation": "2021-08-24 16:55:10.923434", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-08-24 16:55:10.923434", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset", + "owner": "Administrator", + "reference_doctype": "Asset", + "save_on_complete": 0, + "steps": [ + { + "description": "Select Naming Series based on which Asset ID will be generated", + "field": "", + "fieldname": "naming_series", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Naming Series", + "parent_field": "", + "position": "Bottom", + "title": "Naming Series" + }, + { + "description": "Select an Asset Item", + "field": "", + "fieldname": "item_code", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Item Code", + "parent_field": "", + "position": "Bottom", + "title": "Item Code" + }, + { + "description": "Select a Location", + "field": "", + "fieldname": "location", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Location", + "parent_field": "", + "position": "Bottom", + "title": "Location" + }, + { + "description": "Check Is Existing Asset", + "field": "", + "fieldname": "is_existing_asset", + "fieldtype": "Check", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Is Existing Asset", + "parent_field": "", + "position": "Bottom", + "title": "Is Existing Asset?" + }, + { + "description": "Set Available for use date", + "field": "", + "fieldname": "available_for_use_date", + "fieldtype": "Date", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Available-for-use Date", + "parent_field": "", + "position": "Bottom", + "title": "Available For Use Date" + }, + { + "description": "Set Gross purchase amount", + "field": "", + "fieldname": "gross_purchase_amount", + "fieldtype": "Currency", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Gross Purchase Amount", + "parent_field": "", + "position": "Bottom", + "title": "Gross Purchase Amount" + }, + { + "description": "Set Purchase Date", + "field": "", + "fieldname": "purchase_date", + "fieldtype": "Date", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Purchase Date", + "parent_field": "", + "position": "Bottom", + "title": "Purchase Date" + }, + { + "description": "Check Calculate Depreciation", + "field": "", + "fieldname": "calculate_depreciation", + "fieldtype": "Check", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Calculate Depreciation", + "parent_field": "", + "position": "Bottom", + "title": "Calculate Depreciation" + }, + { + "description": "Enter depreciation which has already been booked for this asset", + "field": "", + "fieldname": "opening_accumulated_depreciation", + "fieldtype": "Currency", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Opening Accumulated Depreciation", + "parent_field": "", + "position": "Bottom", + "title": "Accumulated Depreciation" + } + ], + "title": "Asset" +} \ No newline at end of file diff --git a/erpnext/assets/form_tour/asset_category/asset_category.json b/erpnext/assets/form_tour/asset_category/asset_category.json new file mode 100644 index 0000000000..02834447a5 --- /dev/null +++ b/erpnext/assets/form_tour/asset_category/asset_category.json @@ -0,0 +1,65 @@ +{ + "creation": "2021-08-24 12:48:20.763173", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-08-24 12:48:20.763173", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Category", + "owner": "Administrator", + "reference_doctype": "Asset Category", + "save_on_complete": 0, + "steps": [ + { + "description": "Name Asset category. You can create categories based on Asset Types like Furniture, Property, Electronics etc.", + "field": "", + "fieldname": "asset_category_name", + "fieldtype": "Data", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Asset Category Name", + "parent_field": "", + "position": "Bottom", + "title": "Asset Category Name" + }, + { + "description": "Check to enable Capital Work in Progress accounting", + "field": "", + "fieldname": "enable_cwip_accounting", + "fieldtype": "Check", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Enable Capital Work in Progress Accounting", + "parent_field": "", + "position": "Bottom", + "title": "Enable CWIP Accounting" + }, + { + "description": "Add a row to define Depreciation Method and other details. Note that you can leave Finance Book blank to have it's accounting done in the primary books of accounts.", + "field": "", + "fieldname": "finance_books", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Finance Books", + "parent_field": "", + "position": "Bottom", + "title": "Finance Book Detail" + }, + { + "description": "Select the Fixed Asset and Depreciation accounts applicable for this Asset Category type", + "field": "", + "fieldname": "accounts", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Accounts", + "parent_field": "", + "position": "Bottom", + "title": "Accounts" + } + ], + "title": "Asset Category" +} \ No newline at end of file diff --git a/erpnext/assets/module_onboarding/assets/assets.json b/erpnext/assets/module_onboarding/assets/assets.json index 1086ab4bcd..e6df88b000 100644 --- a/erpnext/assets/module_onboarding/assets/assets.json +++ b/erpnext/assets/module_onboarding/assets/assets.json @@ -13,26 +13,26 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/asset", "idx": 0, "is_complete": 0, - "modified": "2020-07-08 14:05:51.828497", + "modified": "2021-08-24 17:50:41.573281", "modified_by": "Administrator", "module": "Assets", "name": "Assets", "owner": "Administrator", "steps": [ { - "step": "Introduction to Assets" + "step": "Fixed Asset Accounts" }, { - "step": "Create a Fixed Asset Item" + "step": "Asset Category" }, { - "step": "Create an Asset Category" + "step": "Asset Item" }, { - "step": "Purchase an Asset Item" + "step": "Asset Purchase" }, { - "step": "Create an Asset" + "step": "Existing Asset" } ], "subtitle": "Assets, Depreciations, Repairs, and more.", diff --git a/erpnext/assets/onboarding_step/asset_category/asset_category.json b/erpnext/assets/onboarding_step/asset_category/asset_category.json new file mode 100644 index 0000000000..033e86669c --- /dev/null +++ b/erpnext/assets/onboarding_step/asset_category/asset_category.json @@ -0,0 +1,21 @@ +{ + "action": "Show Form Tour", + "action_label": "Let's review existing Asset Category", + "creation": "2021-08-13 14:26:18.656303", + "description": "# Asset Category\n\nAn Asset Category classifies different assets of a Company.\n\nYou can create an Asset Category based on the type of assets. For example, all your desktops and laptops can be part of an Asset Category named \"Electronic Equipments\". Create a separate category for furniture. Also, you can update default properties for each category, like:\n - Depreciation type and duration\n - Fixed asset account\n - Depreciation account\n", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-24 12:49:37.665239", + "modified_by": "Administrator", + "name": "Asset Category", + "owner": "Administrator", + "reference_document": "Asset Category", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Define Asset Category", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/assets/onboarding_step/asset_item/asset_item.json b/erpnext/assets/onboarding_step/asset_item/asset_item.json new file mode 100644 index 0000000000..8a174c5b77 --- /dev/null +++ b/erpnext/assets/onboarding_step/asset_item/asset_item.json @@ -0,0 +1,21 @@ +{ + "action": "Show Form Tour", + "action_label": "Let's create a new Asset item", + "creation": "2021-08-13 14:27:07.277167", + "description": "# Asset Item\n\nAsset items are created based on Asset Category. You can create one or multiple items against once Asset Category. The sales and purchase transaction for Asset is done via Asset Item. ", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-16 13:59:18.362233", + "modified_by": "Administrator", + "name": "Asset Item", + "owner": "Administrator", + "reference_document": "Item", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Create an Asset Item", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/assets/onboarding_step/asset_purchase/asset_purchase.json b/erpnext/assets/onboarding_step/asset_purchase/asset_purchase.json new file mode 100644 index 0000000000..54611edc29 --- /dev/null +++ b/erpnext/assets/onboarding_step/asset_purchase/asset_purchase.json @@ -0,0 +1,21 @@ +{ + "action": "Show Form Tour", + "action_label": "Let's create a Purchase Receipt", + "creation": "2021-08-13 14:27:53.678621", + "description": "# Purchase an Asset\n\nAssets purchases process if done following the standard Purchase cycle. If capital work in progress is enabled in Asset Category, Asset will be created as soon as Purchase Receipt is created for it. You can quickly create a Purchase Receipt for Asset and see its impact on books of accounts.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-24 17:26:57.180637", + "modified_by": "Administrator", + "name": "Asset Purchase", + "owner": "Administrator", + "reference_document": "Purchase Receipt", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Purchase an Asset", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/assets/onboarding_step/existing_asset/existing_asset.json b/erpnext/assets/onboarding_step/existing_asset/existing_asset.json new file mode 100644 index 0000000000..052d5e8d77 --- /dev/null +++ b/erpnext/assets/onboarding_step/existing_asset/existing_asset.json @@ -0,0 +1,21 @@ +{ + "action": "Show Form Tour", + "action_label": "Add an existing Asset", + "creation": "2021-08-13 14:28:30.650459", + "description": "# Add an Existing Asset\n\nIf you are just starting with ERPNext, you will need to enter Assets you already possess. You can add them as existing fixed assets in ERPNext. Please note that you will have to make a Journal Entry separately updating the opening balance in the fixed asset account.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-16 14:03:48.850471", + "modified_by": "Administrator", + "name": "Existing Asset", + "owner": "Administrator", + "reference_document": "Asset", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Add an Existing Asset", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/assets/onboarding_step/fixed_asset_accounts/fixed_asset_accounts.json b/erpnext/assets/onboarding_step/fixed_asset_accounts/fixed_asset_accounts.json new file mode 100644 index 0000000000..cebee7a7ea --- /dev/null +++ b/erpnext/assets/onboarding_step/fixed_asset_accounts/fixed_asset_accounts.json @@ -0,0 +1,21 @@ +{ + "action": "Go to Page", + "action_label": "Let's walk-through Chart of Accounts to review setup", + "creation": "2021-08-13 14:23:09.297765", + "description": "# Fixed Asset Accounts\n\nWith the company, a host of fixed asset accounts are pre-configured. To ensure your asset transactions are leading to correct accounting entries, you can review and set up following asset accounts as per your business requirements.\n - Fixed asset accounts (Asset account)\n - Accumulated depreciation\n - Capital Work in progress (CWIP) account\n - Asset Depreciation account (Expense account)", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-24 17:46:37.646174", + "modified_by": "Administrator", + "name": "Fixed Asset Accounts", + "owner": "Administrator", + "path": "app/account/view/tree", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Review Fixed Asset Accounts", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.js b/erpnext/selling/doctype/selling_settings/selling_settings.js index d8d30515f8..cf6fb2806e 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.js +++ b/erpnext/selling/doctype/selling_settings/selling_settings.js @@ -6,26 +6,3 @@ frappe.ui.form.on('Selling Settings', { } }); - -frappe.tour['Selling Settings'] = [ - { - fieldname: "cust_master_name", - title: "Customer Naming By", - description: __("By default, the Customer Name is set as per the Full Name entered. If you want Customers to be named by a ") + "Naming Series" + __(" choose the 'Naming Series' option."), - }, - { - fieldname: "selling_price_list", - title: "Default Selling Price List", - description: __("Configure the default Price List when creating a new Sales transaction. Item prices will be fetched from this Price List.") - }, - { - fieldname: "so_required", - title: "Sales Order Required for Sales Invoice & Delivery Note Creation", - description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice or Delivery Note without creating a Sales Order first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Sales Order' checkbox in the Customer master.") - }, - { - fieldname: "dn_required", - title: "Delivery Note Required for Sales Invoice Creation", - description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice without creating a Delivery Note first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Delivery Note' checkbox in the Customer master.") - } -]; diff --git a/erpnext/selling/form_tour/sales_order/sales_order.json b/erpnext/selling/form_tour/sales_order/sales_order.json new file mode 100644 index 0000000000..a81eb4a043 --- /dev/null +++ b/erpnext/selling/form_tour/sales_order/sales_order.json @@ -0,0 +1,97 @@ +{ + "creation": "2021-06-29 21:13:36.089054", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-06-29 21:13:36.089054", + "modified_by": "Administrator", + "module": "Selling", + "name": "Sales Order", + "owner": "Administrator", + "reference_doctype": "Sales Order", + "save_on_complete": 1, + "steps": [ + { + "description": "Select a customer.", + "field": "", + "fieldname": "customer", + "fieldtype": "Link", + "has_next_condition": 1, + "is_table_field": 0, + "label": "Customer", + "next_step_condition": "customer", + "parent_field": "", + "position": "Right", + "title": "Select Customer" + }, + { + "description": "You can add items here.", + "field": "", + "fieldname": "items", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Items", + "parent_field": "", + "position": "Bottom", + "title": "List of items" + }, + { + "child_doctype": "Sales Order Item", + "description": "Select an item.", + "field": "", + "fieldname": "item_code", + "fieldtype": "Link", + "has_next_condition": 1, + "is_table_field": 1, + "label": "Item Code", + "next_step_condition": "eval: doc.item_code", + "parent_field": "", + "parent_fieldname": "items", + "position": "Right", + "title": "Select Item" + }, + { + "child_doctype": "Sales Order Item", + "description": "Enter quantity.", + "field": "", + "fieldname": "qty", + "fieldtype": "Float", + "has_next_condition": 0, + "is_table_field": 1, + "label": "Quantity", + "parent_field": "", + "parent_fieldname": "items", + "position": "Right", + "title": "Enter Quantity" + }, + { + "child_doctype": "Sales Order Item", + "description": "Enter rate of the item.", + "field": "", + "fieldname": "rate", + "fieldtype": "Currency", + "has_next_condition": 0, + "is_table_field": 1, + "label": "Rate", + "parent_field": "", + "parent_fieldname": "items", + "position": "Right", + "title": "Enter Rate" + }, + { + "description": "You can add sales taxes and charges here.", + "field": "", + "fieldname": "taxes", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Sales Taxes and Charges", + "parent_field": "", + "position": "Bottom", + "title": "Add Sales Taxes and Charges" + } + ], + "title": "Sales Order" +} \ No newline at end of file diff --git a/erpnext/selling/form_tour/selling_settings/selling_settings.json b/erpnext/selling/form_tour/selling_settings/selling_settings.json new file mode 100644 index 0000000000..20c718f8c0 --- /dev/null +++ b/erpnext/selling/form_tour/selling_settings/selling_settings.json @@ -0,0 +1,65 @@ +{ + "creation": "2021-06-29 20:39:19.408763", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-06-29 20:49:01.359489", + "modified_by": "Administrator", + "module": "Selling", + "name": "Selling Settings", + "owner": "Administrator", + "reference_doctype": "Selling Settings", + "save_on_complete": 0, + "steps": [ + { + "description": "By default, the Customer Name is set as per the Full Name entered. If you want Customers to be named by a Naming Series. Choose the 'Naming Series' option.", + "field": "", + "fieldname": "cust_master_name", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Customer Naming By", + "parent_field": "", + "position": "Right", + "title": "Customer Naming By" + }, + { + "description": "If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice or Delivery Note without creating a Sales Order first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Sales Order' checkbox in the Customer master.", + "field": "", + "fieldname": "so_required", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Is Sales Order Required for Sales Invoice & Delivery Note Creation?", + "parent_field": "", + "position": "Left", + "title": "Sales Order Required for Sales Invoice & Delivery Note Creation" + }, + { + "description": "If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice without creating a Delivery Note first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Delivery Note' checkbox in the Customer master.", + "field": "", + "fieldname": "dn_required", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Is Delivery Note Required for Sales Invoice Creation?", + "parent_field": "", + "position": "Left", + "title": "Delivery Note Required for Sales Invoice Creation" + }, + { + "description": "Configure the default Price List when creating a new Sales transaction. Item prices will be fetched from this Price List.", + "field": "", + "fieldname": "selling_price_list", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Default Price List", + "parent_field": "", + "position": "Right", + "title": "Default Selling Price List" + } + ], + "title": "Selling Settings" +} \ No newline at end of file diff --git a/erpnext/selling/module_onboarding/selling/selling.json b/erpnext/selling/module_onboarding/selling/selling.json index 160208ff68..c02f444b05 100644 --- a/erpnext/selling/module_onboarding/selling/selling.json +++ b/erpnext/selling/module_onboarding/selling/selling.json @@ -19,32 +19,17 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/selling", "idx": 0, "is_complete": 0, - "modified": "2020-07-08 14:05:37.669753", + "modified": "2021-08-24 18:12:38.052434", "modified_by": "Administrator", "module": "Selling", "name": "Selling", "owner": "Administrator", "steps": [ - { - "step": "Introduction to Selling" - }, - { - "step": "Create a Customer" - }, - { - "step": "Setup your Warehouse" - }, - { - "step": "Create a Product" - }, - { - "step": "Create a Quotation" - }, - { - "step": "Create your first Sales Order" - }, { "step": "Selling Settings" + }, + { + "step": "Sales Order" } ], "subtitle": "Products, Sales, Analysis, and more.", diff --git a/erpnext/selling/onboarding_step/create_a_customer/create_a_customer.json b/erpnext/selling/onboarding_step/create_a_customer/create_a_customer.json index 5a403b06cf..64defbfe3d 100644 --- a/erpnext/selling/onboarding_step/create_a_customer/create_a_customer.json +++ b/erpnext/selling/onboarding_step/create_a_customer/create_a_customer.json @@ -5,7 +5,6 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, "modified": "2020-06-01 13:16:19.731719", @@ -13,6 +12,7 @@ "name": "Create a Customer", "owner": "Administrator", "reference_document": "Customer", + "show_form_tour": 0, "show_full_form": 0, "title": "Create a Customer", "validate_action": 1 diff --git a/erpnext/selling/onboarding_step/create_a_product/create_a_product.json b/erpnext/selling/onboarding_step/create_a_product/create_a_product.json index d2068e167b..52a5861551 100644 --- a/erpnext/selling/onboarding_step/create_a_product/create_a_product.json +++ b/erpnext/selling/onboarding_step/create_a_product/create_a_product.json @@ -5,14 +5,14 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-12 18:30:02.489949", + "modified": "2020-10-14 14:53:00.133574", "modified_by": "Administrator", "name": "Create a Product", "owner": "Administrator", "reference_document": "Item", + "show_form_tour": 0, "show_full_form": 0, "title": "Create a Product", "validate_action": 1 diff --git a/erpnext/selling/onboarding_step/create_a_quotation/create_a_quotation.json b/erpnext/selling/onboarding_step/create_a_quotation/create_a_quotation.json index 27253d15b6..6f1837e24c 100644 --- a/erpnext/selling/onboarding_step/create_a_quotation/create_a_quotation.json +++ b/erpnext/selling/onboarding_step/create_a_quotation/create_a_quotation.json @@ -5,7 +5,6 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, "modified": "2020-06-01 13:34:58.958641", @@ -13,6 +12,7 @@ "name": "Create a Quotation", "owner": "Administrator", "reference_document": "Quotation", + "show_form_tour": 0, "show_full_form": 1, "title": "Create a Quotation", "validate_action": 1 diff --git a/erpnext/selling/onboarding_step/create_a_sales_order/create_a_sales_order.json b/erpnext/selling/onboarding_step/create_a_sales_order/create_a_sales_order.json new file mode 100644 index 0000000000..378f295f0c --- /dev/null +++ b/erpnext/selling/onboarding_step/create_a_sales_order/create_a_sales_order.json @@ -0,0 +1,21 @@ +{ + "action": "Create Entry", + "action_label": "Create Sales Order", + "creation": "2021-06-29 21:22:54.204880", + "description": "A Sales Order is a confirmation of an order from your customer. It is also referred to as Proforma Invoice.\n\nSales Order at the heart of your sales and purchase transactions. Sales Orders are linked in Delivery Note, Sales Invoices, Material Request, and Maintenance transactions. Through Sales Order, you can track fulfillment of the overall deal towards the customer.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-06-29 21:22:54.204880", + "modified_by": "Administrator", + "name": "Create a Sales Order", + "owner": "Administrator", + "reference_document": "Sales Order", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Create a Sales Order", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/selling/onboarding_step/introduction_to_selling/introduction_to_selling.json b/erpnext/selling/onboarding_step/introduction_to_selling/introduction_to_selling.json index d21c1f4954..636453363b 100644 --- a/erpnext/selling/onboarding_step/introduction_to_selling/introduction_to_selling.json +++ b/erpnext/selling/onboarding_step/introduction_to_selling/introduction_to_selling.json @@ -5,13 +5,13 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, "modified": "2020-06-01 13:29:13.703177", "modified_by": "Administrator", "name": "Introduction to Selling", "owner": "Administrator", + "show_form_tour": 0, "show_full_form": 0, "title": "Introduction to Selling", "validate_action": 1, diff --git a/erpnext/selling/onboarding_step/sales_order/sales_order.json b/erpnext/selling/onboarding_step/sales_order/sales_order.json new file mode 100644 index 0000000000..d616fae066 --- /dev/null +++ b/erpnext/selling/onboarding_step/sales_order/sales_order.json @@ -0,0 +1,21 @@ +{ + "action": "Show Form Tour", + "action_label": "Let\u2019s convert your first Sales Order against a Quotation", + "creation": "2021-08-13 14:03:49.217866", + "description": "# Sales Order\n\nA Sales Order is a confirmation of an order from your customer. It is also referred to as Proforma Invoice.\n\nSales Order at the heart of your sales and purchase transactions. Sales Orders are linked in Delivery Note, Sales Invoices, Material Request, and Maintenance transactions. Through Sales Order, you can track fulfillment of the overall deal towards the customer.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-08-24 18:12:32.394992", + "modified_by": "Administrator", + "name": "Sales Order", + "owner": "Administrator", + "reference_document": "Sales Order", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Sales Order", + "validate_action": 1 +} \ No newline at end of file diff --git a/erpnext/selling/onboarding_step/selling_settings/selling_settings.json b/erpnext/selling/onboarding_step/selling_settings/selling_settings.json index 7996d7b159..ec30fd3033 100644 --- a/erpnext/selling/onboarding_step/selling_settings/selling_settings.json +++ b/erpnext/selling/onboarding_step/selling_settings/selling_settings.json @@ -1,19 +1,21 @@ { "action": "Show Form Tour", + "action_label": "Let\u2019s walk-through Selling Settings", "creation": "2020-06-01 13:01:45.615189", + "description": "# Selling Settings\n\nCRM and Selling module\u2019s features are configurable as per your business needs. Selling Settings is the place where you can set your preferences for:\n - Customer naming and default values\n - Billing and shipping preference in sales transactions\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 1, "is_skipped": 0, - "modified": "2020-06-01 13:04:14.980743", + "modified": "2021-08-24 18:11:21.264335", "modified_by": "Administrator", "name": "Selling Settings", "owner": "Administrator", "reference_document": "Selling Settings", + "show_form_tour": 0, "show_full_form": 0, - "title": "Configure Selling Settings.", + "title": "Selling Settings", "validate_action": 0 } \ No newline at end of file diff --git a/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json b/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json index 9457deee26..1e20eb0eba 100644 --- a/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json +++ b/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json @@ -5,15 +5,15 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-07-04 12:33:16.970031", + "modified": "2020-10-14 14:53:25.538900", "modified_by": "Administrator", "name": "Setup your Warehouse", "owner": "Administrator", "path": "Tree/Warehouse", "reference_document": "Warehouse", + "show_form_tour": 0, "show_full_form": 0, "title": "Set up your Warehouse", "validate_action": 1 diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index e4ee3ecea7..58cb52c04d 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -715,6 +715,15 @@ "label": "Default Payment Discount Account", "options": "Account" }, + { + "fieldname": "hr_settings_section", + "fieldtype": "Section Break", + "label": "HR & Payroll Settings" + }, + { + "fieldname": "column_break_26", + "fieldtype": "Column Break" + }, { "collapsible": 1, "fieldname": "fixed_asset_defaults", @@ -731,15 +740,6 @@ "fieldname": "section_break_28", "fieldtype": "Section Break", "label": "Chart of Accounts" - }, - { - "fieldname": "hr_settings_section", - "fieldtype": "Section Break", - "label": "HR & Payroll Settings" - }, - { - "fieldname": "column_break_26", - "fieldtype": "Column Break" } ], "icon": "fa fa-building", @@ -808,4 +808,4 @@ "sort_field": "modified", "sort_order": "ASC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/form_tour/item/item.json b/erpnext/stock/form_tour/item/item.json new file mode 100644 index 0000000000..821e91b28d --- /dev/null +++ b/erpnext/stock/form_tour/item/item.json @@ -0,0 +1,89 @@ +{ + "creation": "2021-08-24 17:56:40.754909", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-08-24 18:04:50.928431", + "modified_by": "Administrator", + "module": "Stock", + "name": "Item", + "owner": "Administrator", + "reference_doctype": "Item", + "save_on_complete": 0, + "steps": [ + { + "description": "Enter code for Asset Item", + "field": "", + "fieldname": "item_code", + "fieldtype": "Data", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Item Code", + "parent_field": "", + "position": "Bottom", + "title": "Asset Item Code" + }, + { + "description": "Enter name for Asset Item", + "field": "", + "fieldname": "item_name", + "fieldtype": "Data", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Item Name", + "parent_field": "", + "position": "Bottom", + "title": "Asset Item Name" + }, + { + "description": "Check this field to make this an Asset Item", + "field": "", + "fieldname": "is_fixed_asset", + "fieldtype": "Check", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Is Fixed Asset", + "parent_field": "", + "position": "Bottom", + "title": "Is this a Fixed Asset?" + }, + { + "description": "On checking it, the system will create an Asset automatically on purchase", + "field": "", + "fieldname": "auto_create_assets", + "fieldtype": "Check", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Auto Create Assets on Purchase", + "parent_field": "", + "position": "Bottom", + "title": "Auto Create Asset on Purchase" + }, + { + "description": "Select an Asset Category for this Asset Item", + "field": "", + "fieldname": "asset_category", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Asset Category", + "parent_field": "", + "position": "Bottom", + "title": "Asset Category" + }, + { + "description": "Select a naming series which will be used to create an Asset automatically", + "field": "", + "fieldname": "asset_naming_series", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Asset Naming Series", + "parent_field": "", + "position": "Bottom", + "title": "Asset Naming Series" + } + ], + "title": "Item" +} diff --git a/erpnext/stock/form_tour/purchase_receipt/purchase_receipt.json b/erpnext/stock/form_tour/purchase_receipt/purchase_receipt.json new file mode 100644 index 0000000000..6fba3f4d68 --- /dev/null +++ b/erpnext/stock/form_tour/purchase_receipt/purchase_receipt.json @@ -0,0 +1,41 @@ +{ + "creation": "2021-08-24 13:03:21.333088", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-08-24 13:03:21.333088", + "modified_by": "Administrator", + "module": "Stock", + "name": "Purchase Receipt", + "owner": "Administrator", + "reference_doctype": "Purchase Receipt", + "save_on_complete": 0, + "steps": [ + { + "description": "Select Asset Supplier", + "field": "", + "fieldname": "supplier", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Supplier", + "parent_field": "", + "position": "Bottom", + "title": "Supplier" + }, + { + "description": "Select an Asset Item, Enter rate and quantity", + "field": "", + "fieldname": "items", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Items", + "parent_field": "", + "position": "Bottom", + "title": "Items" + } + ], + "title": "Purchase Receipt" +} \ No newline at end of file From bf72ec0598de4688af03d80c34c0241c5b5fb194 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 27 Aug 2021 14:57:42 +0530 Subject: [PATCH 64/98] feat: (consistency) Add Primary Address and Contact section in Supplier - The same is present in customer and is inconsistent with supplier - Helps quickly create primary address and contact via quick entry --- erpnext/buying/doctype/supplier/supplier.js | 43 +++++++++++ erpnext/buying/doctype/supplier/supplier.json | 51 ++++++++++++- erpnext/buying/doctype/supplier/supplier.py | 45 +++++++++++ erpnext/public/build.json | 1 + .../public/js/utils/supplier_quick_entry.js | 75 +++++++++++++++++++ 5 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 erpnext/public/js/utils/supplier_quick_entry.js diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js index 1766c2c80c..7ee91961ca 100644 --- a/erpnext/buying/doctype/supplier/supplier.js +++ b/erpnext/buying/doctype/supplier/supplier.js @@ -24,7 +24,26 @@ frappe.ui.form.on("Supplier", { } } }); + + frm.set_query("supplier_primary_contact", function(doc) { + return { + query: "erpnext.buying.doctype.supplier.supplier.get_supplier_primary_contact", + filters: { + "supplier": doc.name + } + }; + }); + + frm.set_query("supplier_primary_address", function(doc) { + return { + filters: { + "link_doctype": "Supplier", + "link_name": doc.name + } + }; + }); }, + refresh: function (frm) { frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Supplier' } @@ -78,6 +97,30 @@ frappe.ui.form.on("Supplier", { }); }, + supplier_primary_address: function(frm) { + if (frm.doc.supplier_primary_address) { + frappe.call({ + method: 'frappe.contacts.doctype.address.address.get_address_display', + args: { + "address_dict": frm.doc.supplier_primary_address + }, + callback: function(r) { + frm.set_value("primary_address", r.message); + } + }); + } + if (!frm.doc.supplier_primary_address) { + frm.set_value("primary_address", ""); + } + }, + + supplier_primary_contact: function(frm) { + if (!frm.doc.supplier_primary_contact) { + frm.set_value("mobile_no", ""); + frm.set_value("email_id", ""); + } + }, + is_internal_supplier: function(frm) { if (frm.doc.is_internal_supplier == 1) { frm.toggle_reqd("represents_company", true); diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 38b8dfdf48..22e689c101 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -49,6 +49,13 @@ "address_html", "column_break1", "contact_html", + "primary_address_and_contact_detail_section", + "supplier_primary_contact", + "mobile_no", + "email_id", + "column_break_44", + "supplier_primary_address", + "primary_address", "default_payable_accounts", "accounts", "default_tax_withholding_config", @@ -378,6 +385,48 @@ "fieldname": "allow_purchase_invoice_creation_without_purchase_receipt", "fieldtype": "Check", "label": "Allow Purchase Invoice Creation Without Purchase Receipt" + }, + { + "fieldname": "primary_address_and_contact_detail_section", + "fieldtype": "Section Break", + "label": "Primary Address and Contact Detail" + }, + { + "description": "Reselect, if the chosen contact is edited after save", + "fieldname": "supplier_primary_contact", + "fieldtype": "Link", + "label": "Supplier Primary Contact", + "options": "Contact" + }, + { + "depends_on": "mobile_no", + "fetch_from": "supplier_primary_contact.mobile_no", + "fieldname": "mobile_no", + "fieldtype": "Read Only", + "label": "Mobile No" + }, + { + "fetch_from": "supplier_primary_contact.email_id", + "fieldname": "email_id", + "fieldtype": "Read Only", + "label": "Email Id" + }, + { + "fieldname": "column_break_44", + "fieldtype": "Column Break" + }, + { + "fieldname": "primary_address", + "fieldtype": "Text", + "label": "Primary Address", + "read_only": 1 + }, + { + "description": "Reselect, if the chosen address is edited after save", + "fieldname": "supplier_primary_address", + "fieldtype": "Link", + "label": "Supplier Primary Address", + "options": "Address" } ], "icon": "fa fa-user", @@ -390,7 +439,7 @@ "link_fieldname": "supplier" } ], - "modified": "2021-05-18 15:10:11.087191", + "modified": "2021-08-27 13:46:18.212802", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index fd16b23c22..8252b40566 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -42,6 +42,8 @@ class Supplier(TransactionBase): if not self.naming_series: self.naming_series = '' + self.create_primary_contact() + def validate(self): # validation for Naming Series mandatory field... if frappe.defaults.get_global_default('supp_master_name') == 'Naming Series': @@ -76,7 +78,32 @@ class Supplier(TransactionBase): frappe.throw(_("Internal Supplier for company {0} already exists").format( frappe.bold(self.represents_company))) + def create_primary_contact(self): + from erpnext.selling.doctype.customer.customer import make_contact + + if not self.supplier_primary_contact: + if self.mobile_no or self.email_id: + contact = make_contact(self) + self.db_set('supplier_primary_contact', contact.name) + self.db_set('mobile_no', self.mobile_no) + self.db_set('email_id', self.email_id) + + def create_primary_address(self): + from erpnext.selling.doctype.customer.customer import make_address + + if self.flags.is_new_doc and self.get('address_line1'): + make_address(self) + def on_trash(self): + if self.supplier_primary_contact: + frappe.db.sql(f""" + UPDATE `tabSupplier` + SET + supplier_primary_contact=null, + mobile_no=null, + email_id=null + WHERE name='{self.name}'""") + delete_contact_and_address('Supplier', self.name) def after_rename(self, olddn, newdn, merge=False): @@ -104,3 +131,21 @@ class Supplier(TransactionBase): doc.name, args.get('supplier_email_' + str(i))) except frappe.NameError: pass + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_supplier_primary_contact(doctype, txt, searchfield, start, page_len, filters): + supplier = filters.get("supplier") + return frappe.db.sql(""" + SELECT + `tabContact`.name from `tabContact`, + `tabDynamic Link` + WHERE + `tabContact`.name = `tabDynamic Link`.parent + and `tabDynamic Link`.link_name = %(supplier)s + and `tabDynamic Link`.link_doctype = 'Supplier' + and `tabContact`.name like %(txt)s + """, { + 'supplier': supplier, + 'txt': '%%%s%%' % txt + }) diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 3c60e3ee50..6b70dab803 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -38,6 +38,7 @@ "public/js/templates/item_quick_entry.html", "public/js/utils/item_quick_entry.js", "public/js/utils/customer_quick_entry.js", + "public/js/utils/supplier_quick_entry.js", "public/js/education/student_button.html", "public/js/education/assessment_result_tool.html", "public/js/hub/hub_factory.js", diff --git a/erpnext/public/js/utils/supplier_quick_entry.js b/erpnext/public/js/utils/supplier_quick_entry.js new file mode 100644 index 0000000000..f650d1f101 --- /dev/null +++ b/erpnext/public/js/utils/supplier_quick_entry.js @@ -0,0 +1,75 @@ +frappe.provide('frappe.ui.form'); + +frappe.ui.form.SupplierQuickEntryForm = class SupplierQuickEntryForm extends frappe.ui.form.QuickEntryForm { + constructor(doctype, after_insert, init_callback, doc, force) { + super(doctype, after_insert, init_callback, doc, force); + this.skip_redirect_on_error = true; + } + + render_dialog() { + this.mandatory = this.mandatory.concat(this.get_variant_fields()); + super.render_dialog(); + } + + get_variant_fields() { + var variant_fields = [{ + fieldtype: "Section Break", + label: __("Primary Contact Details"), + collapsible: 1 + }, + { + label: __("Email Id"), + fieldname: "email_id", + fieldtype: "Data" + }, + { + fieldtype: "Column Break" + }, + { + label: __("Mobile Number"), + fieldname: "mobile_no", + fieldtype: "Data" + }, + { + fieldtype: "Section Break", + label: __("Primary Address Details"), + collapsible: 1 + }, + { + label: __("Address Line 1"), + fieldname: "address_line1", + fieldtype: "Data" + }, + { + label: __("Address Line 2"), + fieldname: "address_line2", + fieldtype: "Data" + }, + { + label: __("ZIP Code"), + fieldname: "pincode", + fieldtype: "Data" + }, + { + fieldtype: "Column Break" + }, + { + label: __("City"), + fieldname: "city", + fieldtype: "Data" + }, + { + label: __("State"), + fieldname: "state", + fieldtype: "Data" + }, + { + label: __("Country"), + fieldname: "country", + fieldtype: "Link", + options: "Country" + }]; + + return variant_fields; + } +}; From e170ad2df362f2b7fe086e5f0062896c3371fda3 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 27 Aug 2021 16:26:12 +0530 Subject: [PATCH 65/98] chore: add more fields to Prospect list view --- erpnext/crm/doctype/prospect/prospect.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/prospect/prospect.json b/erpnext/crm/doctype/prospect/prospect.json index 237c4255c7..3d6fba5123 100644 --- a/erpnext/crm/doctype/prospect/prospect.json +++ b/erpnext/crm/doctype/prospect/prospect.json @@ -39,6 +39,8 @@ { "fieldname": "industry", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Industry", "options": "Industry Type" }, @@ -57,6 +59,8 @@ { "fieldname": "territory", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Territory", "options": "Territory" }, @@ -72,12 +76,14 @@ { "fieldname": "currency", "fieldtype": "Link", + "in_list_view": 1, "label": "Currency", "options": "Currency" }, { "fieldname": "annual_revenue", "fieldtype": "Currency", + "in_list_view": 1, "label": "Annual Revenue", "options": "currency" }, @@ -151,7 +157,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-08-26 12:10:49.191839", + "modified": "2021-08-27 16:24:42.961967", "modified_by": "Administrator", "module": "CRM", "name": "Prospect", From 17e0fa7a8b8c7d6471a13dc50b4556d82d6c8592 Mon Sep 17 00:00:00 2001 From: Shadrak Gurupnor <30501401+shadrak98@users.noreply.github.com> Date: Fri, 27 Aug 2021 16:42:25 +0530 Subject: [PATCH 66/98] fix: patches were breaking while migrating (#27195) * fix: patches were breaking while migrating * fix: Removed duplicate function Co-authored-by: Nabin Hait --- erpnext/patches.txt | 2 +- .../patches/v13_0/check_is_income_tax_component.py | 4 ++-- erpnext/patches/v13_0/delete_old_purchase_reports.py | 11 +++++++++++ erpnext/patches/v13_0/delete_old_sales_reports.py | 2 ++ erpnext/patches/v13_0/rename_issue_doctype_fields.py | 3 +++ erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py | 1 + 6 files changed, 20 insertions(+), 3 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0f6a606cb1..ade19ff1d5 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -214,6 +214,7 @@ erpnext.patches.v13_0.delete_old_sales_reports execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation") erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020 erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020 +erpnext.patches.v12_0.create_itc_reversal_custom_fields erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020 erpnext.patches.v13_0.loyalty_points_entry_for_pos_invoice #22-07-2020 erpnext.patches.v12_0.add_taxjar_integration_field @@ -266,7 +267,6 @@ erpnext.patches.v13_0.rename_discharge_date_in_ip_record erpnext.patches.v12_0.create_taxable_value_field erpnext.patches.v12_0.add_gst_category_in_delivery_note erpnext.patches.v12_0.purchase_receipt_status -erpnext.patches.v12_0.create_itc_reversal_custom_fields erpnext.patches.v13_0.fix_non_unique_represents_company erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 diff --git a/erpnext/patches/v13_0/check_is_income_tax_component.py b/erpnext/patches/v13_0/check_is_income_tax_component.py index ebae3ad715..7a52dc88d2 100644 --- a/erpnext/patches/v13_0/check_is_income_tax_component.py +++ b/erpnext/patches/v13_0/check_is_income_tax_component.py @@ -19,10 +19,10 @@ def execute(): ] for doctype in doctypes: - frappe.reload_doc('Payroll', 'doctype', doctype) + frappe.reload_doc('Payroll', 'doctype', doctype, force=True) - reports = ['Professional Tax Deductions', 'Provident Fund Deductions'] + reports = ['Professional Tax Deductions', 'Provident Fund Deductions', 'E-Invoice Summary'] for report in reports: frappe.reload_doc('Regional', 'Report', report) frappe.reload_doc('Regional', 'Report', report) diff --git a/erpnext/patches/v13_0/delete_old_purchase_reports.py b/erpnext/patches/v13_0/delete_old_purchase_reports.py index c17aad06c7..360a82e5c0 100644 --- a/erpnext/patches/v13_0/delete_old_purchase_reports.py +++ b/erpnext/patches/v13_0/delete_old_purchase_reports.py @@ -13,6 +13,7 @@ def execute(): for report in reports_to_delete: if frappe.db.exists("Report", report): delete_auto_email_reports(report) + check_linked_reports(report) frappe.delete_doc("Report", report) @@ -21,3 +22,13 @@ def delete_auto_email_reports(report): auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"]) for auto_email_report in auto_email_reports: frappe.delete_doc("Auto Email Report", auto_email_report[0]) + +def check_linked_reports(report): + """ Check if reports are referenced in Desktop Icon """ + icons = frappe.get_all("Desktop Icon", + fields = ['name'], + filters = { + "_report": report + }) + if icons: + frappe.delete_doc("Desktop Icon", icons) \ No newline at end of file diff --git a/erpnext/patches/v13_0/delete_old_sales_reports.py b/erpnext/patches/v13_0/delete_old_sales_reports.py index 671c012c8a..69493e2c00 100644 --- a/erpnext/patches/v13_0/delete_old_sales_reports.py +++ b/erpnext/patches/v13_0/delete_old_sales_reports.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +from erpnext.patches.v13_0.delete_old_purchase_reports import check_linked_reports def execute(): reports_to_delete = ["Ordered Items To Be Delivered", "Ordered Items To Be Billed"] @@ -11,6 +12,7 @@ def execute(): for report in reports_to_delete: if frappe.db.exists("Report", report): delete_auto_email_reports(report) + check_linked_reports(report) frappe.delete_doc("Report", report) diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py index 41c51c36dc..4885c0b7af 100644 --- a/erpnext/patches/v13_0/rename_issue_doctype_fields.py +++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py @@ -41,6 +41,7 @@ def execute(): rename_field('Opportunity', 'mins_to_first_response', 'first_response_time') # change fieldtype to duration + frappe.reload_doc('crm', 'doctype', 'opportunity', force=True) count = 0 for entry in opportunities: mins_to_first_response = convert_to_seconds(entry.mins_to_first_response, 'Minutes') @@ -58,6 +59,8 @@ def execute(): def convert_to_seconds(value, unit): seconds = 0 + if value == 0: + return seconds if unit == 'Hours': seconds = value * 3600 if unit == 'Minutes': diff --git a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py index e642547ef8..a5769d2957 100644 --- a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py +++ b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py @@ -8,6 +8,7 @@ def execute(): frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item') frappe.reload_doc('stock', 'doctype', 'delivery_note') frappe.reload_doc('stock', 'doctype', 'delivery_note_item') + frappe.reload_doc('stock', 'doctype', 'stock_settings') def update_from_return_docs(doctype): for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1}): From 743375748980426b1b7fe82141ce4f0de1ecc8c6 Mon Sep 17 00:00:00 2001 From: Shadrak Gurupnor <30501401+shadrak98@users.noreply.github.com> Date: Fri, 27 Aug 2021 18:00:16 +0530 Subject: [PATCH 67/98] fix: patches were breaking during migration (#27200) * fix: patches were breaking during migrating * fix: patches were breaking during migration --- erpnext/accounts/utils.py | 11 ++++++++ erpnext/patches.txt | 3 ++- .../move_item_tax_to_item_tax_template.py | 3 ++- .../v13_0/delete_old_purchase_reports.py | 13 ++-------- .../patches/v13_0/delete_old_sales_reports.py | 4 +-- .../v13_0/validate_options_for_data_field.py | 25 +++++++++++++++++++ 6 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 erpnext/patches/v13_0/validate_options_for_data_field.py diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 118f628abe..5ed4a1947c 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1086,3 +1086,14 @@ def get_journal_entry(account, stock_adjustment_account, amount): db_or_cr_stock_adjustment_account : abs(amount) }] } + +def check_and_delete_linked_reports(report): + """ Check if reports are referenced in Desktop Icon """ + icons = frappe.get_all("Desktop Icon", + fields = ['name'], + filters = { + "_report": report + }) + if icons: + for icon in icons: + frappe.delete_doc("Desktop Icon", icon) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ade19ff1d5..05c385b6ba 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -299,4 +299,5 @@ erpnext.patches.v13_0.migrate_stripe_api erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries erpnext.patches.v13_0.einvoicing_deprecation_warning erpnext.patches.v14_0.delete_einvoicing_doctypes -erpnext.patches.v13_0.set_operation_time_based_on_operating_cost \ No newline at end of file +erpnext.patches.v13_0.set_operation_time_based_on_operating_cost +erpnext.patches.v13_0.validate_options_for_data_field \ No newline at end of file diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py index a6471eb53c..5c3fa5991c 100644 --- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py +++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py @@ -91,8 +91,9 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttyp item_tax_template.title = make_autoname("Item Tax Template-.####") for tax_type, tax_rate in iteritems(item_tax_map): - account_details = frappe.db.get_value("Account", tax_type, ['name', 'account_type'], as_dict=1) + account_details = frappe.db.get_value("Account", tax_type, ['name', 'account_type', 'company'], as_dict=1) if account_details: + item_tax_template.company = account_details.company if account_details.account_type not in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'): frappe.db.set_value('Account', account_details.name, 'account_type', 'Chargeable') else: diff --git a/erpnext/patches/v13_0/delete_old_purchase_reports.py b/erpnext/patches/v13_0/delete_old_purchase_reports.py index 360a82e5c0..57620d3e98 100644 --- a/erpnext/patches/v13_0/delete_old_purchase_reports.py +++ b/erpnext/patches/v13_0/delete_old_purchase_reports.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +from erpnext.accounts.utils import check_and_delete_linked_reports def execute(): reports_to_delete = ["Requested Items To Be Ordered", @@ -13,7 +14,7 @@ def execute(): for report in reports_to_delete: if frappe.db.exists("Report", report): delete_auto_email_reports(report) - check_linked_reports(report) + check_and_delete_linked_reports(report) frappe.delete_doc("Report", report) @@ -22,13 +23,3 @@ def delete_auto_email_reports(report): auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"]) for auto_email_report in auto_email_reports: frappe.delete_doc("Auto Email Report", auto_email_report[0]) - -def check_linked_reports(report): - """ Check if reports are referenced in Desktop Icon """ - icons = frappe.get_all("Desktop Icon", - fields = ['name'], - filters = { - "_report": report - }) - if icons: - frappe.delete_doc("Desktop Icon", icons) \ No newline at end of file diff --git a/erpnext/patches/v13_0/delete_old_sales_reports.py b/erpnext/patches/v13_0/delete_old_sales_reports.py index 69493e2c00..905a42c0c4 100644 --- a/erpnext/patches/v13_0/delete_old_sales_reports.py +++ b/erpnext/patches/v13_0/delete_old_sales_reports.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from erpnext.patches.v13_0.delete_old_purchase_reports import check_linked_reports +from erpnext.accounts.utils import check_and_delete_linked_reports def execute(): reports_to_delete = ["Ordered Items To Be Delivered", "Ordered Items To Be Billed"] @@ -12,7 +12,7 @@ def execute(): for report in reports_to_delete: if frappe.db.exists("Report", report): delete_auto_email_reports(report) - check_linked_reports(report) + check_and_delete_linked_reports(report) frappe.delete_doc("Report", report) diff --git a/erpnext/patches/v13_0/validate_options_for_data_field.py b/erpnext/patches/v13_0/validate_options_for_data_field.py new file mode 100644 index 0000000000..568d1a4b0c --- /dev/null +++ b/erpnext/patches/v13_0/validate_options_for_data_field.py @@ -0,0 +1,25 @@ +# Copyright (c) 2021, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model import data_field_options + +def execute(): + + for field in frappe.get_all('Custom Field', + fields = ['name'], + filters = { + 'fieldtype': 'Data', + 'options': ['!=', None] + }): + + if field not in data_field_options: + frappe.db.sql(""" + UPDATE + `tabCustom Field` + SET + options=NULL + WHERE + name=%s + """, (field)) From 2a3ef03388c4581550a247e2dbe99d8ca496883c Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 27 Aug 2021 18:06:51 +0530 Subject: [PATCH 68/98] fix: Popup stale build and data consistency - Include `supplier_quick_entry.js` in erpnext.bundle.js - Create primary supplier address on update - Set newly created address (quick entry) in Supplier and Customer - Clear address set in supplier and customer on delete (dependency) --- erpnext/buying/doctype/supplier/supplier.json | 3 +-- erpnext/buying/doctype/supplier/supplier.py | 14 +++++++++++-- erpnext/public/js/erpnext.bundle.js | 1 + .../public/js/utils/supplier_quick_entry.js | 3 ++- erpnext/selling/doctype/customer/customer.py | 20 +++++++++++++++---- 5 files changed, 32 insertions(+), 9 deletions(-) diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 22e689c101..c7a5db5994 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -399,7 +399,6 @@ "options": "Contact" }, { - "depends_on": "mobile_no", "fetch_from": "supplier_primary_contact.mobile_no", "fieldname": "mobile_no", "fieldtype": "Read Only", @@ -439,7 +438,7 @@ "link_fieldname": "supplier" } ], - "modified": "2021-08-27 13:46:18.212802", + "modified": "2021-08-27 18:02:44.314077", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index 8252b40566..207485e1bc 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -43,8 +43,11 @@ class Supplier(TransactionBase): self.naming_series = '' self.create_primary_contact() + self.create_primary_address() def validate(self): + self.flags.is_new_doc = self.is_new() + # validation for Naming Series mandatory field... if frappe.defaults.get_global_default('supp_master_name') == 'Naming Series': if not self.naming_series: @@ -90,9 +93,14 @@ class Supplier(TransactionBase): def create_primary_address(self): from erpnext.selling.doctype.customer.customer import make_address + from frappe.contacts.doctype.address.address import get_address_display if self.flags.is_new_doc and self.get('address_line1'): - make_address(self) + address = make_address(self) + address_display = get_address_display(address.name) + + self.db_set("supplier_primary_address", address.name) + self.db_set("primary_address", address_display) def on_trash(self): if self.supplier_primary_contact: @@ -100,8 +108,10 @@ class Supplier(TransactionBase): UPDATE `tabSupplier` SET supplier_primary_contact=null, + supplier_primary_address=null, mobile_no=null, - email_id=null + email_id=null, + primary_address=null WHERE name='{self.name}'""") delete_contact_and_address('Supplier', self.name) diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js index 9f7f29ad72..febdb24da3 100644 --- a/erpnext/public/js/erpnext.bundle.js +++ b/erpnext/public/js/erpnext.bundle.js @@ -15,6 +15,7 @@ import "./agriculture/ternary_plot"; import "./templates/item_quick_entry.html"; import "./utils/item_quick_entry"; import "./utils/customer_quick_entry"; +import "./utils/supplier_quick_entry"; import "./education/student_button.html"; import "./education/assessment_result_tool.html"; import "./hub/hub_factory"; diff --git a/erpnext/public/js/utils/supplier_quick_entry.js b/erpnext/public/js/utils/supplier_quick_entry.js index f650d1f101..e4a3812cf4 100644 --- a/erpnext/public/js/utils/supplier_quick_entry.js +++ b/erpnext/public/js/utils/supplier_quick_entry.js @@ -12,7 +12,8 @@ frappe.ui.form.SupplierQuickEntryForm = class SupplierQuickEntryForm extends fra } get_variant_fields() { - var variant_fields = [{ + var variant_fields = [ + { fieldtype: "Section Break", label: __("Primary Contact Details"), collapsible: 1 diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index abf146c43f..05cabcbbe2 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -150,8 +150,14 @@ class Customer(TransactionBase): self.db_set('email_id', self.email_id) def create_primary_address(self): + from frappe.contacts.doctype.address.address import get_address_display + if self.flags.is_new_doc and self.get('address_line1'): - make_address(self) + address = make_address(self) + address_display = get_address_display(address.name) + + self.db_set("customer_primary_address", address.name) + self.db_set("primary_address", address_display) def update_lead_status(self): '''If Customer created from Lead, update lead status to "Converted" @@ -246,9 +252,15 @@ class Customer(TransactionBase): def on_trash(self): if self.customer_primary_contact: - frappe.db.sql("""update `tabCustomer` - set customer_primary_contact=null, mobile_no=null, email_id=null - where name=%s""", self.name) + frappe.db.sql(f""" + UPDATE `tabCustomer` + SET + customer_primary_contact=null, + customer_primary_address=null, + mobile_no=null, + email_id=null, + primary_address=null + WHERE name='{self.name}'""") delete_contact_and_address('Customer', self.name) if self.lead_name: From 072151e16d6c0003d71c9a889ac8f00c10a7869c Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 27 Aug 2021 10:14:57 -0400 Subject: [PATCH 69/98] feat: Payment reconciliation on accounts module workspace (#25925) * feat: add payment reconciliation to accounting workspace * fix: update modified and duplicate the link Co-authored-by: Ankush Menat --- .../workspace/accounting/accounting.json | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index b5bd14d137..2b26ac5090 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -233,6 +233,15 @@ "onboard": 0, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Payment Reconciliation", + "link_to": "Payment Reconciliation", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "dependencies": "Sales Invoice", "hidden": 0, @@ -340,6 +349,15 @@ "onboard": 0, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Payment Reconciliation", + "link_to": "Payment Reconciliation", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "dependencies": "Purchase Invoice", "hidden": 0, @@ -1188,7 +1206,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:52.872470", + "modified": "2021-08-27 12:15:52.872470", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", @@ -1249,4 +1267,4 @@ } ], "title": "Accounting" -} \ No newline at end of file +} From 2caa09099101a304c3152f814a9b3e9df8fd25ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Aug 2021 19:59:36 +0530 Subject: [PATCH 70/98] chore(deps): bump tar from 6.1.0 to 6.1.11 (#27203) Bumps [tar](https://github.com/npm/node-tar) from 6.1.0 to 6.1.11. - [Release notes](https://github.com/npm/node-tar/releases) - [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/npm/node-tar/compare/v6.1.0...v6.1.11) --- updated-dependencies: - dependency-name: tar dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 82e98215d1..0a49c52f0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3244,9 +3244,9 @@ tar-stream@^2.1.0: readable-stream "^3.1.1" tar@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" - integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" From 327071cb9078840aa463fa3c2d80ac2ef8a6ab8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Ryckel?= Date: Sat, 28 Aug 2021 12:25:11 +0300 Subject: [PATCH 71/98] replace cur_frm with frm (#27210) --- erpnext/accounts/doctype/account/account.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/account.js b/erpnext/accounts/doctype/account/account.js index f7f1a5fb15..7a1d735948 100644 --- a/erpnext/accounts/doctype/account/account.js +++ b/erpnext/accounts/doctype/account/account.js @@ -74,7 +74,7 @@ frappe.ui.form.on('Account', { }); } else if (cint(frm.doc.is_group) == 0 && frappe.boot.user.can_read.indexOf("GL Entry") !== -1) { - cur_frm.add_custom_button(__('Ledger'), function () { + frm.add_custom_button(__('Ledger'), function () { frappe.route_options = { "account": frm.doc.name, "from_date": frappe.sys_defaults.year_start_date, From f5cdbf161d2b03e088ac6882553d381c39ed5ba7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Sat, 28 Aug 2021 14:55:51 +0530 Subject: [PATCH 72/98] fix: LTV ratio comparison (#27207) --- .../doctype/loan_repayment/loan_repayment.py | 13 +++++++------ .../loan_security_shortfall.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 57aec2e5c9..b67b4c4842 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -107,12 +107,13 @@ class LoanRepayment(AccountsController): lia = frappe.db.get_value('Loan Interest Accrual', {'process_loan_interest_accrual': process}, ['name', 'interest_amount', 'payable_principal_amount'], as_dict=1) - self.append('repayment_details', { - 'loan_interest_accrual': lia.name, - 'paid_interest_amount': flt(self.total_interest_paid - self.interest_payable, precision), - 'paid_principal_amount': 0.0, - 'accrual_type': 'Repayment' - }) + if lia: + self.append('repayment_details', { + 'loan_interest_accrual': lia.name, + 'paid_interest_amount': flt(self.total_interest_paid - self.interest_payable, precision), + 'paid_principal_amount': 0.0, + 'accrual_type': 'Repayment' + }) def update_paid_amount(self): loan = frappe.get_doc("Loan", self.against_loan) diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py index cd7694b7b1..61f1778048 100644 --- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py +++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py @@ -71,7 +71,7 @@ def check_for_ltv_shortfall(process_loan_security_shortfall): - flt(loan.total_principal_paid) pledged_securities = get_pledged_security_qty(loan.name) - ltv_ratio = '' + ltv_ratio = 0.0 security_value = 0.0 for security, qty in pledged_securities.items(): From e5e00700e5415d9abc91ef5e119649a17c347d9a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 30 Aug 2021 12:07:11 +0530 Subject: [PATCH 73/98] ci: use node action instead of apt (#27220) --- .github/helper/install.sh | 7 +------ .github/workflows/patch.yml | 8 +++++++- .github/workflows/server-tests.yml | 6 ++++++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index 455ab861f9..ac623e947d 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -4,11 +4,7 @@ set -e cd ~ || exit -sudo apt-get install redis-server - -sudo apt install nodejs - -sudo apt install npm +sudo apt-get install redis-server libcups2-dev pip install frappe-bench @@ -32,7 +28,6 @@ wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/w tar -xf /tmp/wkhtmltox.tar.xz -C /tmp sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf sudo chmod o+x /usr/local/bin/wkhtmltopdf -sudo apt-get install libcups2-dev cd ~/frappe-bench || exit diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index 72d4028ce6..f65b0001fe 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -31,7 +31,13 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: 3.7 + + - name: Setup Node + uses: actions/setup-node@v2 + with: + node-version: 14 + check-latest: true - name: Add to Hosts run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 3a1ecd399c..0a73d74d6b 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -43,6 +43,12 @@ jobs: with: python-version: 3.7 + - name: Setup Node + uses: actions/setup-node@v2 + with: + node-version: 14 + check-latest: true + - name: Add to Hosts run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts From 7b78473da35fa66476d93c5212f30fd07b63336d Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 30 Aug 2021 12:50:24 +0530 Subject: [PATCH 74/98] fix: Indentation and removed f-strings - Sider: fixed indentation in js - Dont use f-strings in queries --- erpnext/buying/doctype/supplier/supplier.py | 4 +- .../public/js/utils/supplier_quick_entry.js | 115 +++++++++--------- erpnext/selling/doctype/customer/customer.py | 4 +- 3 files changed, 62 insertions(+), 61 deletions(-) diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index 207485e1bc..c9750caa65 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -104,7 +104,7 @@ class Supplier(TransactionBase): def on_trash(self): if self.supplier_primary_contact: - frappe.db.sql(f""" + frappe.db.sql(""" UPDATE `tabSupplier` SET supplier_primary_contact=null, @@ -112,7 +112,7 @@ class Supplier(TransactionBase): mobile_no=null, email_id=null, primary_address=null - WHERE name='{self.name}'""") + WHERE name=%(name)s""", {"name": self.name}) delete_contact_and_address('Supplier', self.name) diff --git a/erpnext/public/js/utils/supplier_quick_entry.js b/erpnext/public/js/utils/supplier_quick_entry.js index e4a3812cf4..8d591a9651 100644 --- a/erpnext/public/js/utils/supplier_quick_entry.js +++ b/erpnext/public/js/utils/supplier_quick_entry.js @@ -13,63 +13,64 @@ frappe.ui.form.SupplierQuickEntryForm = class SupplierQuickEntryForm extends fra get_variant_fields() { var variant_fields = [ - { - fieldtype: "Section Break", - label: __("Primary Contact Details"), - collapsible: 1 - }, - { - label: __("Email Id"), - fieldname: "email_id", - fieldtype: "Data" - }, - { - fieldtype: "Column Break" - }, - { - label: __("Mobile Number"), - fieldname: "mobile_no", - fieldtype: "Data" - }, - { - fieldtype: "Section Break", - label: __("Primary Address Details"), - collapsible: 1 - }, - { - label: __("Address Line 1"), - fieldname: "address_line1", - fieldtype: "Data" - }, - { - label: __("Address Line 2"), - fieldname: "address_line2", - fieldtype: "Data" - }, - { - label: __("ZIP Code"), - fieldname: "pincode", - fieldtype: "Data" - }, - { - fieldtype: "Column Break" - }, - { - label: __("City"), - fieldname: "city", - fieldtype: "Data" - }, - { - label: __("State"), - fieldname: "state", - fieldtype: "Data" - }, - { - label: __("Country"), - fieldname: "country", - fieldtype: "Link", - options: "Country" - }]; + { + fieldtype: "Section Break", + label: __("Primary Contact Details"), + collapsible: 1 + }, + { + label: __("Email Id"), + fieldname: "email_id", + fieldtype: "Data" + }, + { + fieldtype: "Column Break" + }, + { + label: __("Mobile Number"), + fieldname: "mobile_no", + fieldtype: "Data" + }, + { + fieldtype: "Section Break", + label: __("Primary Address Details"), + collapsible: 1 + }, + { + label: __("Address Line 1"), + fieldname: "address_line1", + fieldtype: "Data" + }, + { + label: __("Address Line 2"), + fieldname: "address_line2", + fieldtype: "Data" + }, + { + label: __("ZIP Code"), + fieldname: "pincode", + fieldtype: "Data" + }, + { + fieldtype: "Column Break" + }, + { + label: __("City"), + fieldname: "city", + fieldtype: "Data" + }, + { + label: __("State"), + fieldname: "state", + fieldtype: "Data" + }, + { + label: __("Country"), + fieldname: "country", + fieldtype: "Link", + options: "Country" + } + ]; return variant_fields; } diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 05cabcbbe2..1164f40237 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -252,7 +252,7 @@ class Customer(TransactionBase): def on_trash(self): if self.customer_primary_contact: - frappe.db.sql(f""" + frappe.db.sql(""" UPDATE `tabCustomer` SET customer_primary_contact=null, @@ -260,7 +260,7 @@ class Customer(TransactionBase): mobile_no=null, email_id=null, primary_address=null - WHERE name='{self.name}'""") + WHERE name=%(name)s""", {"name": self.name}) delete_contact_and_address('Customer', self.name) if self.lead_name: From 212eb4bc1a3e94ce02079e6b27f1a94a9362c1a1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Aug 2021 13:10:18 +0530 Subject: [PATCH 75/98] feat(Healthcare): Capacity for Service Unit, concurrent appointments based on capacity, Patient Appointments (#27219) * feat(Healthcare): Capacity for Service Unit, concurrent appointments based on Capacity, Patient enhancements * fix: appointment test Co-authored-by: Anoop <3326959+akurungadam@users.noreply.github.com> --- .../test_clinical_procedure.py | 4 +- .../doctype/fee_validity/test_fee_validity.py | 2 +- .../healthcare_service_unit.js | 17 +- .../healthcare_service_unit.json | 45 +++- .../healthcare_service_unit.py | 95 ++++++++- .../healthcare_service_unit_tree.js | 194 +++++++++++++++-- .../healthcare_service_unit_type.js | 4 +- .../healthcare_service_unit_type.json | 36 +++- .../inpatient_record/test_inpatient_record.py | 8 +- erpnext/healthcare/doctype/patient/patient.js | 46 ++-- .../healthcare/doctype/patient/patient.json | 118 +++++++++-- erpnext/healthcare/doctype/patient/patient.py | 126 +++++++++-- .../doctype/patient/patient_dashboard.py | 25 ++- .../patient_appointment.js | 196 +++++++++++------- .../patient_appointment.json | 4 +- .../patient_appointment.py | 76 +++++-- .../test_patient_appointment.py | 196 +++++++++++++----- .../test_patient_medical_record.py | 5 +- .../doctype/therapy_plan/test_therapy_plan.py | 11 +- .../doctype/therapy_type/test_therapy_type.py | 6 +- .../test_inpatient_medication_orders.py | 12 +- erpnext/healthcare/utils.py | 110 ++++++---- erpnext/hooks.py | 9 +- 23 files changed, 1027 insertions(+), 318 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py index 81a3982c4b..0326e5e9da 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py @@ -11,7 +11,7 @@ test_dependencies = ['Item'] class TestClinicalProcedure(unittest.TestCase): def test_procedure_template_item(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() procedure_template = create_clinical_procedure_template() self.assertTrue(frappe.db.exists('Item', procedure_template.item)) @@ -20,7 +20,7 @@ class TestClinicalProcedure(unittest.TestCase): self.assertEqual(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1) def test_consumables(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() procedure_template = create_clinical_procedure_template() procedure_template.allow_stock_consumption = 1 consumable = create_consumable() diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py index 4a178723a0..957f85211d 100644 --- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py +++ b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py @@ -27,7 +27,7 @@ class TestFeeValidity(unittest.TestCase): healthcare_settings.automate_appointment_invoicing = 1 healthcare_settings.op_consulting_charge_item = item healthcare_settings.save(ignore_permissions=True) - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() # For first appointment, invoice is generated. First appointment not considered in fee validity appointment = create_appointment(patient, practitioner, nowdate()) diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js index 2cdd550656..2d1caf7efc 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js +++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js @@ -7,8 +7,8 @@ frappe.ui.form.on('Healthcare Service Unit', { // get query select healthcare service unit frm.fields_dict['parent_healthcare_service_unit'].get_query = function(doc) { - return{ - filters:[ + return { + filters: [ ['Healthcare Service Unit', 'is_group', '=', 1], ['Healthcare Service Unit', 'name', '!=', doc.healthcare_service_unit_name] ] @@ -21,6 +21,14 @@ frappe.ui.form.on('Healthcare Service Unit', { frm.add_custom_button(__('Healthcare Service Unit Tree'), function() { frappe.set_route('Tree', 'Healthcare Service Unit'); }); + + frm.set_query('warehouse', function() { + return { + filters: { + 'company': frm.doc.company + } + }; + }); }, set_root_readonly: function(frm) { // read-only for root healthcare service unit @@ -43,5 +51,10 @@ frappe.ui.form.on('Healthcare Service Unit', { else { frm.set_df_property('service_unit_type', 'reqd', 1); } + }, + overlap_appointments: function(frm) { + if (frm.doc.overlap_appointments == 0) { + frm.set_value('service_unit_capacity', ''); + } } }); diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json index 9ee865a62a..8935ec7d3c 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json +++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json @@ -16,6 +16,7 @@ "service_unit_type", "allow_appointments", "overlap_appointments", + "service_unit_capacity", "inpatient_occupancy", "occupancy_status", "column_break_9", @@ -31,6 +32,8 @@ { "fieldname": "healthcare_service_unit_name", "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, "in_global_search": 1, "in_list_view": 1, "label": "Service Unit", @@ -41,6 +44,8 @@ "bold": 1, "fieldname": "parent_healthcare_service_unit", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "ignore_user_permissions": 1, "in_list_view": 1, "label": "Parent Service Unit", @@ -52,6 +57,8 @@ "depends_on": "eval:doc.inpatient_occupancy != 1 && doc.allow_appointments != 1", "fieldname": "is_group", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Is Group" }, { @@ -59,6 +66,8 @@ "depends_on": "eval:doc.is_group != 1", "fieldname": "service_unit_type", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "label": "Service Unit Type", "options": "Healthcare Service Unit Type" }, @@ -68,6 +77,8 @@ "fetch_from": "service_unit_type.allow_appointments", "fieldname": "allow_appointments", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "in_list_view": 1, "label": "Allow Appointments", "no_copy": 1, @@ -79,6 +90,8 @@ "fetch_from": "service_unit_type.overlap_appointments", "fieldname": "overlap_appointments", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Allow Overlap", "no_copy": 1, "read_only": 1 @@ -90,6 +103,8 @@ "fetch_from": "service_unit_type.inpatient_occupancy", "fieldname": "inpatient_occupancy", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "in_list_view": 1, "label": "Inpatient Occupancy", "no_copy": 1, @@ -100,6 +115,8 @@ "depends_on": "eval:doc.inpatient_occupancy == 1", "fieldname": "occupancy_status", "fieldtype": "Select", + "hide_days": 1, + "hide_seconds": 1, "label": "Occupancy Status", "no_copy": 1, "options": "Vacant\nOccupied", @@ -107,13 +124,17 @@ }, { "fieldname": "column_break_9", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "hide_days": 1, + "hide_seconds": 1 }, { "bold": 1, "depends_on": "eval:doc.is_group != 1", "fieldname": "warehouse", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "label": "Warehouse", "no_copy": 1, "options": "Warehouse" @@ -121,6 +142,8 @@ { "fieldname": "company", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "ignore_user_permissions": 1, "in_list_view": 1, "in_standard_filter": 1, @@ -134,6 +157,8 @@ "fieldname": "lft", "fieldtype": "Int", "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, "label": "lft", "no_copy": 1, "print_hide": 1, @@ -143,6 +168,8 @@ "fieldname": "rgt", "fieldtype": "Int", "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, "label": "rgt", "no_copy": 1, "print_hide": 1, @@ -152,6 +179,8 @@ "fieldname": "old_parent", "fieldtype": "Link", "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, "ignore_user_permissions": 1, "label": "Old Parent", "no_copy": 1, @@ -163,14 +192,26 @@ "collapsible": 1, "fieldname": "tree_details_section", "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1, "label": "Tree Details" + }, + { + "depends_on": "eval:doc.overlap_appointments == 1", + "fieldname": "service_unit_capacity", + "fieldtype": "Int", + "label": "Service Unit Capacity", + "mandatory_depends_on": "eval:doc.overlap_appointments == 1", + "non_negative": 1 } ], + "is_tree": 1, "links": [], - "modified": "2020-05-20 18:26:56.065543", + "modified": "2021-08-19 14:09:11.643464", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare Service Unit", + "nsm_parent_field": "parent_healthcare_service_unit", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py index 9e0417a2be..989d426789 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py +++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py @@ -5,14 +5,21 @@ from __future__ import unicode_literals from frappe.utils.nestedset import NestedSet +from frappe.utils import cint, cstr import frappe +from frappe import _ +import json + class HealthcareServiceUnit(NestedSet): nsm_parent_field = 'parent_healthcare_service_unit' + def validate(self): + self.set_service_unit_properties() + def autoname(self): if self.company: - suffix = " - " + frappe.get_cached_value('Company', self.company, "abbr") + suffix = " - " + frappe.get_cached_value('Company', self.company, 'abbr') if not self.healthcare_service_unit_name.endswith(suffix): self.name = self.healthcare_service_unit_name + suffix else: @@ -22,16 +29,86 @@ class HealthcareServiceUnit(NestedSet): super(HealthcareServiceUnit, self).on_update() self.validate_one_root() - def after_insert(self): + def set_service_unit_properties(self): if self.is_group: - self.allow_appointments = 0 - self.overlap_appointments = 0 - self.inpatient_occupancy = 0 - elif self.service_unit_type: + self.allow_appointments = False + self.overlap_appointments = False + self.inpatient_occupancy = False + self.service_unit_capacity = 0 + self.occupancy_status = '' + self.service_unit_type = '' + elif self.service_unit_type != '': service_unit_type = frappe.get_doc('Healthcare Service Unit Type', self.service_unit_type) self.allow_appointments = service_unit_type.allow_appointments - self.overlap_appointments = service_unit_type.overlap_appointments self.inpatient_occupancy = service_unit_type.inpatient_occupancy - if self.inpatient_occupancy: + + if self.inpatient_occupancy and self.occupancy_status != '': self.occupancy_status = 'Vacant' - self.overlap_appointments = 0 + + if service_unit_type.overlap_appointments: + self.overlap_appointments = True + else: + self.overlap_appointments = False + self.service_unit_capacity = 0 + + if self.overlap_appointments: + if not self.service_unit_capacity: + frappe.throw(_('Please set a valid Service Unit Capacity to enable Overlapping Appointments'), + title=_('Mandatory')) + + +@frappe.whitelist() +def add_multiple_service_units(parent, data): + ''' + parent - parent service unit under which the service units are to be created + data (dict) - company, healthcare_service_unit_name, count, service_unit_type, warehouse, service_unit_capacity + ''' + if not parent or not data: + return + + data = json.loads(data) + company = data.get('company') or \ + frappe.defaults.get_defaults().get('company') or \ + frappe.db.get_single_value('Global Defaults', 'default_company') + + if not data.get('healthcare_service_unit_name') or not company: + frappe.throw(_('Service Unit Name and Company are mandatory to create Healthcare Service Units'), + title=_('Missing Required Fields')) + + count = cint(data.get('count') or 0) + if count <= 0: + frappe.throw(_('Number of Service Units to be created should at least be 1'), + title=_('Invalid Number of Service Units')) + + capacity = cint(data.get('service_unit_capacity') or 1) + + service_unit = { + 'doctype': 'Healthcare Service Unit', + 'parent_healthcare_service_unit': parent, + 'service_unit_type': data.get('service_unit_type') or None, + 'service_unit_capacity': capacity if capacity > 0 else 1, + 'warehouse': data.get('warehouse') or None, + 'company': company + } + + service_unit_name = '{}'.format(data.get('healthcare_service_unit_name').strip(' -')) + + last_suffix = frappe.db.sql("""SELECT + IFNULL(MAX(CAST(SUBSTRING(name FROM %(start)s FOR 4) AS UNSIGNED)), 0) + FROM `tabHealthcare Service Unit` + WHERE name like %(prefix)s AND company=%(company)s""", + {'start': len(service_unit_name)+2, 'prefix': '{}-%'.format(service_unit_name), 'company': company}, + as_list=1)[0][0] + start_suffix = cint(last_suffix) + 1 + + failed_list = [] + for i in range(start_suffix, count + start_suffix): + # name to be in the form WARD-#### + service_unit['healthcare_service_unit_name'] = '{}-{}'.format(service_unit_name, cstr('%0*d' % (4, i))) + service_unit_doc = frappe.get_doc(service_unit) + try: + service_unit_doc.insert() + except Exception: + failed_list.append(service_unit['healthcare_service_unit_name']) + + return failed_list diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js index b75f271827..ea3fea6b7a 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js +++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js @@ -1,35 +1,185 @@ -frappe.treeview_settings["Healthcare Service Unit"] = { - breadcrumbs: "Healthcare Service Unit", - title: __("Healthcare Service Unit"), +frappe.provide("frappe.treeview_settings"); + +frappe.treeview_settings['Healthcare Service Unit'] = { + breadcrumbs: 'Healthcare Service Unit', + title: __('Service Unit Tree'), get_tree_root: false, - filters: [{ - fieldname: "company", - fieldtype: "Select", - options: erpnext.utils.get_tree_options("company"), - label: __("Company"), - default: erpnext.utils.get_tree_default("company") - }], get_tree_nodes: 'erpnext.healthcare.utils.get_children', - ignore_fields:["parent_healthcare_service_unit"], - onrender: function(node) { - if (node.data.occupied_out_of_vacant!==undefined) { - $('' - + " " + node.data.occupied_out_of_vacant + filters: [{ + fieldname: 'company', + fieldtype: 'Select', + options: erpnext.utils.get_tree_options('company'), + label: __('Company'), + default: erpnext.utils.get_tree_default('company') + }], + fields: [ + { + fieldtype: 'Data', fieldname: 'healthcare_service_unit_name', label: __('New Service Unit Name'), + reqd: true + }, + { + fieldtype: 'Check', fieldname: 'is_group', label: __('Is Group'), + description: __("Child nodes can be only created under 'Group' type nodes") + }, + { + fieldtype: 'Link', fieldname: 'service_unit_type', label: __('Service Unit Type'), + options: 'Healthcare Service Unit Type', description: __('Type of the new Service Unit'), + depends_on: 'eval:!doc.is_group', default: '', + onchange: () => { + if (cur_dialog) { + if (cur_dialog.fields_dict.service_unit_type.value) { + frappe.db.get_value('Healthcare Service Unit Type', + cur_dialog.fields_dict.service_unit_type.value, 'overlap_appointments') + .then(r => { + if (r.message.overlap_appointments) { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', false); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', true); + } else { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); + } + }); + } else { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); + } + } + } + }, + { + fieldtype: 'Int', fieldname: 'service_unit_capacity', label: __('Service Unit Capacity'), + description: __('Sets the number of concurrent appointments allowed'), reqd: false, + depends_on: "eval:!doc.is_group && doc.service_unit_type != ''", hidden: true + }, + { + fieldtype: 'Link', fieldname: 'warehouse', label: __('Warehouse'), options: 'Warehouse', + description: __('Optional, if you want to manage stock separately for this Service Unit'), + depends_on: 'eval:!doc.is_group' + }, + { + fieldtype: 'Link', fieldname: 'company', label: __('Company'), options: 'Company', reqd: true, + default: () => { + return cur_page.page.page.fields_dict.company.value; + } + } + ], + ignore_fields: ['parent_healthcare_service_unit'], + onrender: function (node) { + if (node.data.occupied_of_available !== undefined) { + $("" + + ' ' + node.data.occupied_of_available + '').insertBefore(node.$ul); } - if (node.data && node.data.inpatient_occupancy!==undefined) { + if (node.data && node.data.inpatient_occupancy !== undefined) { if (node.data.inpatient_occupancy == 1) { - if (node.data.occupancy_status == "Occupied") { - $('' - + " " + node.data.occupancy_status + if (node.data.occupancy_status == 'Occupied') { + $("" + + ' ' + node.data.occupancy_status + '').insertBefore(node.$ul); } - if (node.data.occupancy_status == "Vacant") { - $('' - + " " + node.data.occupancy_status + if (node.data.occupancy_status == 'Vacant') { + $("" + + ' ' + node.data.occupancy_status + '').insertBefore(node.$ul); } } } }, + post_render: function (treeview) { + frappe.treeview_settings['Healthcare Service Unit'].treeview = {}; + $.extend(frappe.treeview_settings['Healthcare Service Unit'].treeview, treeview); + }, + toolbar: [ + { + label: __('Add Multiple'), + condition: function (node) { + return node.expandable; + }, + click: function (node) { + const dialog = new frappe.ui.Dialog({ + title: __('Add Multiple Service Units'), + fields: [ + { + fieldtype: 'Data', fieldname: 'healthcare_service_unit_name', label: __('Service Unit Name'), + reqd: true, description: __("Will be serially suffixed to maintain uniquness. Example: 'Ward' will be named as 'Ward-####'"), + }, + { + fieldtype: 'Int', fieldname: 'count', label: __('Number of Service Units'), + reqd: true + }, + { + fieldtype: 'Link', fieldname: 'service_unit_type', label: __('Service Unit Type'), + options: 'Healthcare Service Unit Type', description: __('Type of the new Service Unit'), + depends_on: 'eval:!doc.is_group', default: '', reqd: true, + onchange: () => { + if (cur_dialog) { + if (cur_dialog.fields_dict.service_unit_type.value) { + frappe.db.get_value('Healthcare Service Unit Type', + cur_dialog.fields_dict.service_unit_type.value, 'overlap_appointments') + .then(r => { + if (r.message.overlap_appointments) { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', false); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', true); + } else { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); + } + }); + } else { + cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); + cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); + } + } + } + }, + { + fieldtype: 'Int', fieldname: 'service_unit_capacity', label: __('Service Unit Capacity'), + description: __('Sets the number of concurrent appointments allowed'), reqd: false, + depends_on: "eval:!doc.is_group && doc.service_unit_type != ''", hidden: true + }, + { + fieldtype: 'Link', fieldname: 'warehouse', label: __('Warehouse'), options: 'Warehouse', + description: __('Optional, if you want to manage stock separately for this Service Unit'), + }, + { + fieldtype: 'Link', fieldname: 'company', label: __('Company'), options: 'Company', reqd: true, + default: () => { + return cur_page.page.page.fields_dict.company.get_value(); + } + } + ], + primary_action: () => { + dialog.hide(); + let vals = dialog.get_values(); + if (!vals) return; + + return frappe.call({ + method: 'erpnext.healthcare.doctype.healthcare_service_unit.healthcare_service_unit.add_multiple_service_units', + args: { + parent: node.data.value, + data: vals + }, + callback: function (r) { + if (!r.exc && r.message) { + frappe.treeview_settings['Healthcare Service Unit'].treeview.tree.load_children(node, true); + + frappe.show_alert({ + message: __('{0} Service Units created', [vals.count - r.message.length]), + indicator: 'green' + }); + } else { + frappe.msgprint(__('Could not create Service Units')); + } + }, + freeze: true, + freeze_message: __('Creating {0} Service Units', [vals.count]) + }); + }, + primary_action_label: __('Create') + }); + dialog.show(); + } + } + ], + extend_toolbar: true }; diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js index eb33ab68c0..ecf4aa1a4b 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js +++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js @@ -68,8 +68,8 @@ let change_item_code = function(frm, doc) { if (values) { frappe.call({ "method": "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.change_item_code", - "args": {item: doc.item, item_code: values['item_code'], doc_name: doc.name}, - callback: function () { + "args": { item: doc.item, item_code: values['item_code'], doc_name: doc.name }, + callback: function() { frm.reload_doc(); } }); diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json index 4b8503d028..9c81c65f6b 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json +++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json @@ -29,6 +29,8 @@ { "fieldname": "service_unit_type", "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, "in_list_view": 1, "label": "Service Unit Type", "no_copy": 1, @@ -41,6 +43,8 @@ "depends_on": "eval:doc.inpatient_occupancy != 1", "fieldname": "allow_appointments", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Allow Appointments" }, { @@ -49,6 +53,8 @@ "depends_on": "eval:doc.allow_appointments == 1 && doc.inpatient_occupany != 1", "fieldname": "overlap_appointments", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Allow Overlap" }, { @@ -57,6 +63,8 @@ "depends_on": "eval:doc.allow_appointments != 1", "fieldname": "inpatient_occupancy", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Inpatient Occupancy" }, { @@ -65,17 +73,23 @@ "depends_on": "eval:doc.inpatient_occupancy == 1 && doc.allow_appointments != 1", "fieldname": "is_billable", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Is Billable" }, { "depends_on": "is_billable", "fieldname": "item_details", "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1, "label": "Item Details" }, { "fieldname": "item", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "label": "Item", "no_copy": 1, "options": "Item", @@ -84,6 +98,8 @@ { "fieldname": "item_code", "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, "label": "Item Code", "mandatory_depends_on": "eval: doc.is_billable == 1", "no_copy": 1 @@ -91,6 +107,8 @@ { "fieldname": "item_group", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "label": "Item Group", "mandatory_depends_on": "eval: doc.is_billable == 1", "options": "Item Group" @@ -98,6 +116,8 @@ { "fieldname": "uom", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "label": "UOM", "mandatory_depends_on": "eval: doc.is_billable == 1", "options": "UOM" @@ -105,28 +125,38 @@ { "fieldname": "no_of_hours", "fieldtype": "Int", + "hide_days": 1, + "hide_seconds": 1, "label": "UOM Conversion in Hours", "mandatory_depends_on": "eval: doc.is_billable == 1" }, { "fieldname": "column_break_11", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "hide_days": 1, + "hide_seconds": 1 }, { "fieldname": "rate", "fieldtype": "Currency", + "hide_days": 1, + "hide_seconds": 1, "label": "Rate / UOM" }, { "default": "0", "fieldname": "disabled", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Disabled", "no_copy": 1 }, { "fieldname": "description", "fieldtype": "Small Text", + "hide_days": 1, + "hide_seconds": 1, "label": "Description" }, { @@ -134,11 +164,13 @@ "fieldname": "change_in_item", "fieldtype": "Check", "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, "label": "Change in Item" } ], "links": [], - "modified": "2020-05-20 15:31:09.627516", + "modified": "2021-08-19 17:52:30.266667", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare Service Unit Type", diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py index a8c7720a0a..b4a961264f 100644 --- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py @@ -151,7 +151,7 @@ def get_healthcare_service_unit(unit_name=None): if not service_unit: service_unit = frappe.new_doc("Healthcare Service Unit") - service_unit.healthcare_service_unit_name = unit_name or "Test Service Unit Ip Occupancy" + service_unit.healthcare_service_unit_name = unit_name or "_Test Service Unit Ip Occupancy" service_unit.company = "_Test Company" service_unit.service_unit_type = get_service_unit_type() service_unit.inpatient_occupancy = 1 @@ -159,12 +159,12 @@ def get_healthcare_service_unit(unit_name=None): service_unit.is_group = 0 service_unit_parent_name = frappe.db.exists({ "doctype": "Healthcare Service Unit", - "healthcare_service_unit_name": "All Healthcare Service Units", + "healthcare_service_unit_name": "_Test All Healthcare Service Units", "is_group": 1 }) if not service_unit_parent_name: parent_service_unit = frappe.new_doc("Healthcare Service Unit") - parent_service_unit.healthcare_service_unit_name = "All Healthcare Service Units" + parent_service_unit.healthcare_service_unit_name = "_Test All Healthcare Service Units" parent_service_unit.is_group = 1 parent_service_unit.save(ignore_permissions = True) service_unit.parent_healthcare_service_unit = parent_service_unit.name @@ -180,7 +180,7 @@ def get_service_unit_type(): if not service_unit_type: service_unit_type = frappe.new_doc("Healthcare Service Unit Type") - service_unit_type.service_unit_type = "Test Service Unit Type Ip Occupancy" + service_unit_type.service_unit_type = "_Test Service Unit Type Ip Occupancy" service_unit_type.inpatient_occupancy = 1 service_unit_type.save(ignore_permissions = True) return service_unit_type.name diff --git a/erpnext/healthcare/doctype/patient/patient.js b/erpnext/healthcare/doctype/patient/patient.js index bce42e51d0..9266467155 100644 --- a/erpnext/healthcare/doctype/patient/patient.js +++ b/erpnext/healthcare/doctype/patient/patient.js @@ -26,31 +26,39 @@ frappe.ui.form.on('Patient', { } if (frm.doc.patient_name && frappe.user.has_role('Physician')) { + frm.add_custom_button(__('Patient Progress'), function() { + frappe.route_options = {'patient': frm.doc.name}; + frappe.set_route('patient-progress'); + }, __('View')); + frm.add_custom_button(__('Patient History'), function() { frappe.route_options = {'patient': frm.doc.name}; frappe.set_route('patient_history'); - },'View'); + }, __('View')); } - if (!frm.doc.__islocal && (frappe.user.has_role('Nursing User') || frappe.user.has_role('Physician'))) { - frm.add_custom_button(__('Vital Signs'), function () { - create_vital_signs(frm); - }, 'Create'); - frm.add_custom_button(__('Medical Record'), function () { - create_medical_record(frm); - }, 'Create'); - frm.add_custom_button(__('Patient Encounter'), function () { - create_encounter(frm); - }, 'Create'); - frm.toggle_enable(['customer'], 0); // ToDo, allow change only if no transactions booked or better, add merge option + frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Patient'}; + frm.toggle_display(['address_html', 'contact_html'], !frm.is_new()); + + if (!frm.is_new()) { + if ((frappe.user.has_role('Nursing User') || frappe.user.has_role('Physician'))) { + frm.add_custom_button(__('Medical Record'), function () { + create_medical_record(frm); + }, 'Create'); + frm.toggle_enable(['customer'], 0); + } + frappe.contacts.render_address_and_contact(frm); + erpnext.utils.set_party_dashboard_indicators(frm); + } else { + frappe.contacts.clear_address_and_contact(frm); } }, + onload: function (frm) { - if (!frm.doc.dob) { - $(frm.fields_dict['age_html'].wrapper).html(''); - } if (frm.doc.dob) { $(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${get_age(frm.doc.dob)}`); + } else { + $(frm.fields_dict['age_html'].wrapper).html(''); } } }); @@ -59,16 +67,14 @@ frappe.ui.form.on('Patient', 'dob', function(frm) { if (frm.doc.dob) { let today = new Date(); let birthDate = new Date(frm.doc.dob); - if (today < birthDate){ + if (today < birthDate) { frappe.msgprint(__('Please select a valid Date')); frappe.model.set_value(frm.doctype,frm.docname, 'dob', ''); - } - else { + } else { let age_str = get_age(frm.doc.dob); $(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${age_str}`); } - } - else { + } else { $(frm.fields_dict['age_html'].wrapper).html(''); } }); diff --git a/erpnext/healthcare/doctype/patient/patient.json b/erpnext/healthcare/doctype/patient/patient.json index 8af1a9ccd7..4092a6a768 100644 --- a/erpnext/healthcare/doctype/patient/patient.json +++ b/erpnext/healthcare/doctype/patient/patient.json @@ -1,6 +1,6 @@ { "actions": [], - "allow_copy": 1, + "allow_events_in_timeline": 1, "allow_import": 1, "allow_rename": 1, "autoname": "naming_series:", @@ -24,12 +24,19 @@ "image", "column_break_14", "status", + "uid", "inpatient_record", "inpatient_status", "report_preference", "mobile", - "email", "phone", + "email", + "invite_user", + "user_id", + "address_contacts", + "address_html", + "column_break_22", + "contact_html", "customer_details_section", "customer", "customer_group", @@ -74,6 +81,7 @@ "fieldtype": "Select", "in_preview": 1, "label": "Inpatient Status", + "no_copy": 1, "options": "\nAdmission Scheduled\nAdmitted\nDischarge Scheduled", "read_only": 1 }, @@ -81,6 +89,7 @@ "fieldname": "inpatient_record", "fieldtype": "Link", "label": "Inpatient Record", + "no_copy": 1, "options": "Inpatient Record", "read_only": 1 }, @@ -101,6 +110,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Full Name", + "no_copy": 1, "read_only": 1, "search_index": 1 }, @@ -118,6 +128,7 @@ "fieldtype": "Select", "in_preview": 1, "label": "Blood Group", + "no_copy": 1, "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative" }, { @@ -125,7 +136,8 @@ "fieldname": "dob", "fieldtype": "Date", "in_preview": 1, - "label": "Date of birth" + "label": "Date of birth", + "no_copy": 1 }, { "fieldname": "age_html", @@ -167,6 +179,7 @@ "fieldtype": "Link", "ignore_user_permissions": 1, "label": "Customer", + "no_copy": 1, "options": "Customer", "set_only_once": 1 }, @@ -183,6 +196,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Mobile", + "no_copy": 1, "options": "Phone" }, { @@ -192,6 +206,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Email", + "no_copy": 1, "options": "Email" }, { @@ -199,6 +214,7 @@ "fieldtype": "Data", "in_filter": 1, "label": "Phone", + "no_copy": 1, "options": "Phone" }, { @@ -230,7 +246,8 @@ "fieldname": "medication", "fieldtype": "Small Text", "ignore_xss_filter": 1, - "label": "Medication" + "label": "Medication", + "no_copy": 1 }, { "fieldname": "column_break_20", @@ -240,13 +257,15 @@ "fieldname": "medical_history", "fieldtype": "Small Text", "ignore_xss_filter": 1, - "label": "Medical History" + "label": "Medical History", + "no_copy": 1 }, { "fieldname": "surgical_history", "fieldtype": "Small Text", "ignore_xss_filter": 1, - "label": "Surgical History" + "label": "Surgical History", + "no_copy": 1 }, { "collapsible": 1, @@ -258,8 +277,8 @@ "fieldname": "occupation", "fieldtype": "Data", "ignore_xss_filter": 1, - "in_standard_filter": 1, - "label": "Occupation" + "label": "Occupation", + "no_copy": 1 }, { "fieldname": "column_break_25", @@ -269,6 +288,7 @@ "fieldname": "marital_status", "fieldtype": "Select", "label": "Marital Status", + "no_copy": 1, "options": "\nSingle\nMarried\nDivorced\nWidow" }, { @@ -281,25 +301,29 @@ "fieldname": "tobacco_past_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Tobacco Consumption (Past)" + "label": "Tobacco Consumption (Past)", + "no_copy": 1 }, { "fieldname": "tobacco_current_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Tobacco Consumption (Present)" + "label": "Tobacco Consumption (Present)", + "no_copy": 1 }, { "fieldname": "alcohol_past_use", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Alcohol Consumption (Past)" + "label": "Alcohol Consumption (Past)", + "no_copy": 1 }, { "fieldname": "alcohol_current_use", "fieldtype": "Data", "ignore_user_permissions": 1, - "label": "Alcohol Consumption (Present)" + "label": "Alcohol Consumption (Present)", + "no_copy": 1 }, { "fieldname": "column_break_32", @@ -309,13 +333,15 @@ "fieldname": "surrounding_factors", "fieldtype": "Small Text", "ignore_xss_filter": 1, - "label": "Occupational Hazards and Environmental Factors" + "label": "Occupational Hazards and Environmental Factors", + "no_copy": 1 }, { "fieldname": "other_risk_factors", "fieldtype": "Small Text", "ignore_xss_filter": 1, - "label": "Other Risk Factors" + "label": "Other Risk Factors", + "no_copy": 1 }, { "collapsible": 1, @@ -331,7 +357,8 @@ "fieldname": "patient_details", "fieldtype": "Text", "ignore_xss_filter": 1, - "label": "Patient Details" + "label": "Patient Details", + "no_copy": 1 }, { "fieldname": "default_currency", @@ -342,19 +369,22 @@ { "fieldname": "last_name", "fieldtype": "Data", - "label": "Last Name" + "label": "Last Name", + "no_copy": 1 }, { "fieldname": "first_name", "fieldtype": "Data", "label": "First Name", + "no_copy": 1, "oldfieldtype": "Data", "reqd": 1 }, { "fieldname": "middle_name", "fieldtype": "Data", - "label": "Middle Name (optional)" + "label": "Middle Name (optional)", + "no_copy": 1 }, { "collapsible": 1, @@ -389,13 +419,63 @@ "fieldtype": "Link", "label": "Print Language", "options": "Language" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "address_contacts", + "fieldtype": "Section Break", + "label": "Address and Contact", + "options": "fa fa-map-marker" + }, + { + "fieldname": "address_html", + "fieldtype": "HTML", + "label": "Address HTML", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break" + }, + { + "fieldname": "contact_html", + "fieldtype": "HTML", + "label": "Contact HTML", + "no_copy": 1, + "read_only": 1 + }, + { + "allow_in_quick_entry": 1, + "default": "1", + "fieldname": "invite_user", + "fieldtype": "Check", + "label": "Invite as User", + "no_copy": 1, + "read_only_depends_on": "eval: doc.user_id" + }, + { + "fieldname": "user_id", + "fieldtype": "Read Only", + "label": "User ID", + "no_copy": 1, + "options": "User" + }, + { + "allow_in_quick_entry": 1, + "bold": 1, + "fieldname": "uid", + "fieldtype": "Data", + "in_standard_filter": 1, + "label": "Identification Number (UID)", + "unique": 1 } ], "icon": "fa fa-user", "image_field": "image", "links": [], "max_attachments": 50, - "modified": "2020-04-25 17:24:32.146415", + "modified": "2021-03-14 13:21:09.759906", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient", @@ -453,7 +533,7 @@ ], "quick_entry": 1, "restrict_to_domain": "Healthcare", - "search_fields": "patient_name,mobile,email,phone", + "search_fields": "patient_name,mobile,email,phone,uid", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "ASC", diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py index f77ad70633..9dae1f68b0 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -8,24 +8,27 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import cint, cstr, getdate import dateutil +from frappe.contacts.address_and_contact import load_address_and_contact +from frappe.contacts.doctype.contact.contact import get_default_contact from frappe.model.naming import set_name_by_naming_series from frappe.utils.nestedset import get_root_of from erpnext import get_default_currency from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account, send_registration_sms +from erpnext.accounts.party import get_dashboard_info class Patient(Document): + def onload(self): + '''Load address and contacts in `__onload`''' + load_address_and_contact(self) + self.load_dashboard_info() + def validate(self): self.set_full_name() - self.add_as_website_user() def before_insert(self): self.set_missing_customer_details() def after_insert(self): - self.add_as_website_user() - self.reload() - if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient') and not self.customer: - create_customer(self) if frappe.db.get_single_value('Healthcare Settings', 'collect_registration_fee'): frappe.db.set_value('Patient', self.name, 'status', 'Disabled') else: @@ -49,6 +52,16 @@ class Patient(Document): else: create_customer(self) + self.set_contact() # add or update contact + + if not self.user_id and self.email and self.invite_user: + self.create_website_user() + + def load_dashboard_info(self): + if self.customer: + info = get_dashboard_info('Customer', self.customer, None) + self.set_onload('dashboard_info', info) + def set_full_name(self): if self.last_name: self.patient_name = ' '.join(filter(None, [self.first_name, self.last_name])) @@ -71,18 +84,24 @@ class Patient(Document): if not self.language: self.language = frappe.db.get_single_value('System Settings', 'language') - def add_as_website_user(self): - if self.email: - if not frappe.db.exists ('User', self.email): - user = frappe.get_doc({ - 'doctype': 'User', - 'first_name': self.first_name, - 'last_name': self.last_name, - 'email': self.email, - 'user_type': 'Website User' - }) - user.flags.ignore_permissions = True - user.add_roles('Patient') + def create_website_user(self): + if self.email and not frappe.db.exists('User', self.email): + user = frappe.get_doc({ + 'doctype': 'User', + 'first_name': self.first_name, + 'last_name': self.last_name, + 'email': self.email, + 'user_type': 'Website User', + 'gender': self.sex, + 'phone': self.phone, + 'mobile_no': self.mobile, + 'birth_date': self.dob + }) + user.flags.ignore_permissions = True + user.enabled = True + user.send_welcome_email = True + user.add_roles('Patient') + frappe.db.set_value(self.doctype, self.name, 'user_id', user.name) def autoname(self): patient_name_by = frappe.db.get_single_value('Healthcare Settings', 'patient_name_by') @@ -114,7 +133,7 @@ class Patient(Document): age = self.age if not age: return - age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)") + age_str = f'{str(age.years)} {_("Years(s)")} {str(age.months)} {_("Month(s)")} {str(age.days)} {_("Day(s)")}' return age_str @frappe.whitelist() @@ -131,6 +150,58 @@ class Patient(Document): return {'invoice': sales_invoice.name} + def set_contact(self): + if frappe.db.exists('Dynamic Link', {'parenttype':'Contact', 'link_doctype':'Patient', 'link_name':self.name}): + old_doc = self.get_doc_before_save() + if old_doc.email != self.email or old_doc.mobile != self.mobile or old_doc.phone != self.phone: + self.update_contact() + else: + self.reload() + if self.email or self.mobile or self.phone: + contact = frappe.get_doc({ + 'doctype': 'Contact', + 'first_name': self.first_name, + 'middle_name': self.middle_name, + 'last_name': self.last_name, + 'gender': self.sex, + 'is_primary_contact': 1 + }) + contact.append('links', dict(link_doctype='Patient', link_name=self.name)) + if self.customer: + contact.append('links', dict(link_doctype='Customer', link_name=self.customer)) + + contact.insert(ignore_permissions=True) + self.update_contact(contact) # update email, mobile and phone + + def update_contact(self, contact=None): + if not contact: + contact_name = get_default_contact(self.doctype, self.name) + if contact_name: + contact = frappe.get_doc('Contact', contact_name) + + if contact: + if self.email and self.email != contact.email_id: + for email in contact.email_ids: + email.is_primary = True if email.email_id == self.email else False + contact.add_email(self.email, is_primary=True) + contact.set_primary_email() + + if self.mobile and self.mobile != contact.mobile_no: + for mobile in contact.phone_nos: + mobile.is_primary_mobile_no = True if mobile.phone == self.mobile else False + contact.add_phone(self.mobile, is_primary_mobile_no=True) + contact.set_primary('mobile_no') + + if self.phone and self.phone != contact.phone: + for phone in contact.phone_nos: + phone.is_primary_phone = True if phone.phone == self.phone else False + contact.add_phone(self.phone, is_primary_phone=True) + contact.set_primary('phone') + + contact.flags.ignore_validate = True # disable hook TODO: safe? + contact.save(ignore_permissions=True) + + def create_customer(doc): customer = frappe.get_doc({ 'doctype': 'Customer', @@ -156,8 +227,8 @@ def make_invoice(patient, company): sales_invoice.debit_to = get_receivable_account(company) item_line = sales_invoice.append('items') - item_line.item_name = 'Registeration Fee' - item_line.description = 'Registeration Fee' + item_line.item_name = 'Registration Fee' + item_line.description = 'Registration Fee' item_line.qty = 1 item_line.uom = uom item_line.conversion_factor = 1 @@ -181,8 +252,11 @@ def get_patient_detail(patient): return details def get_timeline_data(doctype, name): - """Return timeline data from medical records""" - return dict(frappe.db.sql(''' + ''' + Return Patient's timeline data from medical records + Also include the associated Customer timeline data + ''' + patient_timeline_data = dict(frappe.db.sql(''' SELECT unix_timestamp(communication_date), count(*) FROM @@ -191,3 +265,11 @@ def get_timeline_data(doctype, name): patient=%s and `communication_date` > date_sub(curdate(), interval 1 year) GROUP BY communication_date''', name)) + + customer = frappe.db.get_value(doctype, name, 'customer') + if customer: + from erpnext.accounts.party import get_timeline_data + customer_timeline_data = get_timeline_data('Customer', customer) + patient_timeline_data.update(customer_timeline_data) + + return patient_timeline_data diff --git a/erpnext/healthcare/doctype/patient/patient_dashboard.py b/erpnext/healthcare/doctype/patient/patient_dashboard.py index 39603f77a0..7f7cfa8e5b 100644 --- a/erpnext/healthcare/doctype/patient/patient_dashboard.py +++ b/erpnext/healthcare/doctype/patient/patient_dashboard.py @@ -6,22 +6,33 @@ def get_data(): 'heatmap': True, 'heatmap_message': _('This is based on transactions against this Patient. See timeline below for details'), 'fieldname': 'patient', + 'non_standard_fieldnames': { + 'Payment Entry': 'party' + }, 'transactions': [ { - 'label': _('Appointments and Patient Encounters'), - 'items': ['Patient Appointment', 'Patient Encounter'] + 'label': _('Appointments and Encounters'), + 'items': ['Patient Appointment', 'Vital Signs', 'Patient Encounter'] }, { 'label': _('Lab Tests and Vital Signs'), - 'items': ['Lab Test', 'Sample Collection', 'Vital Signs'] + 'items': ['Lab Test', 'Sample Collection'] }, { - 'label': _('Billing'), - 'items': ['Sales Invoice'] + 'label': _('Rehab and Physiotherapy'), + 'items': ['Patient Assessment', 'Therapy Session', 'Therapy Plan'] }, { - 'label': _('Orders'), - 'items': ['Inpatient Medication Order'] + 'label': _('Surgery'), + 'items': ['Clinical Procedure'] + }, + { + 'label': _('Admissions'), + 'items': ['Inpatient Record', 'Inpatient Medication Order'] + }, + { + 'label': _('Billing and Payments'), + 'items': ['Sales Invoice', 'Payment Entry'] } ] } diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index c6e489ec17..49847d5bc8 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -17,9 +17,9 @@ frappe.ui.form.on('Patient Appointment', { }, refresh: function(frm) { - frm.set_query('patient', function () { + frm.set_query('patient', function() { return { - filters: {'status': 'Active'} + filters: { 'status': 'Active' } }; }); @@ -64,7 +64,7 @@ frappe.ui.form.on('Patient Appointment', { } else { frappe.call({ method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd', - args: {'patient': frm.doc.patient}, + args: { 'patient': frm.doc.patient }, callback: function(data) { if (data.message == true) { if (frm.doc.mode_of_payment && frm.doc.paid_amount) { @@ -97,7 +97,7 @@ frappe.ui.form.on('Patient Appointment', { if (frm.doc.patient) { frm.add_custom_button(__('Patient History'), function() { - frappe.route_options = {'patient': frm.doc.patient}; + frappe.route_options = { 'patient': frm.doc.patient }; frappe.set_route('patient_history'); }, __('View')); } @@ -111,14 +111,14 @@ frappe.ui.form.on('Patient Appointment', { }); if (frm.doc.procedure_template) { - frm.add_custom_button(__('Clinical Procedure'), function(){ + frm.add_custom_button(__('Clinical Procedure'), function() { frappe.model.open_mapped_doc({ method: 'erpnext.healthcare.doctype.clinical_procedure.clinical_procedure.make_procedure', frm: frm, }); }, __('Create')); } else if (frm.doc.therapy_type) { - frm.add_custom_button(__('Therapy Session'),function(){ + frm.add_custom_button(__('Therapy Session'), function() { frappe.model.open_mapped_doc({ method: 'erpnext.healthcare.doctype.therapy_session.therapy_session.create_therapy_session', frm: frm, @@ -148,7 +148,7 @@ frappe.ui.form.on('Patient Appointment', { doctype: 'Patient', name: frm.doc.patient }, - callback: function (data) { + callback: function(data) { let age = null; if (data.message.dob) { age = calculate_age(data.message.dob); @@ -165,7 +165,7 @@ frappe.ui.form.on('Patient Appointment', { }, practitioner: function(frm) { - if (frm.doc.practitioner ) { + if (frm.doc.practitioner) { frm.events.set_payment_details(frm); } }, @@ -230,7 +230,7 @@ frappe.ui.form.on('Patient Appointment', { toggle_payment_fields: function(frm) { frappe.call({ method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd', - args: {'patient': frm.doc.patient}, + args: { 'patient': frm.doc.patient }, callback: function(data) { if (data.message.fee_validity) { // if fee validity exists and automated appointment invoicing is enabled, @@ -254,7 +254,7 @@ frappe.ui.form.on('Patient Appointment', { frm.toggle_display('paid_amount', data.message ? 1 : 0); frm.toggle_display('billing_item', data.message ? 1 : 0); frm.toggle_reqd('mode_of_payment', data.message ? 1 : 0); - frm.toggle_reqd('paid_amount', data.message ? 1 :0); + frm.toggle_reqd('paid_amount', data.message ? 1 : 0); frm.toggle_reqd('billing_item', data.message ? 1 : 0); } } @@ -265,7 +265,7 @@ frappe.ui.form.on('Patient Appointment', { if (frm.doc.patient) { frappe.call({ method: "erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_prescribed_therapies", - args: {patient: frm.doc.patient}, + args: { patient: frm.doc.patient }, callback: function(r) { if (r.message) { show_therapy_types(frm, r.message); @@ -302,13 +302,13 @@ let check_and_set_availability = function(frm) { let d = new frappe.ui.Dialog({ title: __('Available slots'), fields: [ - { fieldtype: 'Link', options: 'Medical Department', reqd: 1, fieldname: 'department', label: 'Medical Department'}, - { fieldtype: 'Column Break'}, - { fieldtype: 'Link', options: 'Healthcare Practitioner', reqd: 1, fieldname: 'practitioner', label: 'Healthcare Practitioner'}, - { fieldtype: 'Column Break'}, - { fieldtype: 'Date', reqd: 1, fieldname: 'appointment_date', label: 'Date'}, - { fieldtype: 'Section Break'}, - { fieldtype: 'HTML', fieldname: 'available_slots'} + { fieldtype: 'Link', options: 'Medical Department', reqd: 1, fieldname: 'department', label: 'Medical Department' }, + { fieldtype: 'Column Break' }, + { fieldtype: 'Link', options: 'Healthcare Practitioner', reqd: 1, fieldname: 'practitioner', label: 'Healthcare Practitioner' }, + { fieldtype: 'Column Break' }, + { fieldtype: 'Date', reqd: 1, fieldname: 'appointment_date', label: 'Date' }, + { fieldtype: 'Section Break' }, + { fieldtype: 'HTML', fieldname: 'available_slots' } ], primary_action_label: __('Book'), @@ -386,59 +386,22 @@ let check_and_set_availability = function(frm) { let $wrapper = d.fields_dict.available_slots.$wrapper; // make buttons for each slot - let slot_details = data.slot_details; - let slot_html = ''; - for (let i = 0; i < slot_details.length; i++) { - slot_html = slot_html + ``; - slot_html = slot_html + `
    ` + slot_details[i].avail_slot.map(slot => { - let disabled = ''; - let start_str = slot.from_time; - let slot_start_time = moment(slot.from_time, 'HH:mm:ss'); - let slot_to_time = moment(slot.to_time, 'HH:mm:ss'); - let interval = (slot_to_time - slot_start_time)/60000 | 0; - // iterate in all booked appointments, update the start time and duration - slot_details[i].appointments.forEach(function(booked) { - let booked_moment = moment(booked.appointment_time, 'HH:mm:ss'); - let end_time = booked_moment.clone().add(booked.duration, 'minutes'); - // Deal with 0 duration appointments - if (booked_moment.isSame(slot_start_time) || booked_moment.isBetween(slot_start_time, slot_to_time)) { - if(booked.duration == 0){ - disabled = 'disabled="disabled"'; - return false; - } - } - // Check for overlaps considering appointment duration - if (slot_start_time.isBefore(end_time) && slot_to_time.isAfter(booked_moment)) { - // There is an overlap - disabled = 'disabled="disabled"'; - return false; - } - }); - return ``; - }).join(""); - slot_html = slot_html + `
    `; - } + let slot_html = get_slots(data.slot_details); $wrapper .css('margin-bottom', 0) .addClass('text-center') .html(slot_html); - // blue button when clicked + // highlight button when clicked $wrapper.on('click', 'button', function() { let $btn = $(this); - $wrapper.find('button').removeClass('btn-primary'); - $btn.addClass('btn-primary'); + $wrapper.find('button').removeClass('btn-outline-primary'); + $btn.addClass('btn-outline-primary'); selected_slot = $btn.attr('data-name'); service_unit = $btn.attr('data-service-unit'); duration = $btn.attr('data-duration'); - // enable dialog action + // enable primary action 'Book' d.get_primary_btn().attr('disabled', null); }); @@ -448,19 +411,102 @@ let check_and_set_availability = function(frm) { } }, freeze: true, - freeze_message: __('Fetching records......') + freeze_message: __('Fetching Schedule...') }); } else { fd.available_slots.html(__('Appointment date and Healthcare Practitioner are Mandatory').bold()); } } + + function get_slots(slot_details) { + let slot_html = ''; + let appointment_count = 0; + let disabled = false; + let start_str, slot_start_time, slot_end_time, interval, count, count_class, tool_tip, available_slots; + + slot_details.forEach((slot_info) => { + slot_html += `
    + ${__('Practitioner Schedule:')} ${slot_info.slot_name}
    + ${__('Service Unit:')} ${slot_info.service_unit} `; + + if (slot_info.service_unit_capacity) { + slot_html += `
    ${__('Maximum Capacity:')} ${slot_info.service_unit_capacity} `; + } + + slot_html += '


    '; + + slot_html += slot_info.avail_slot.map(slot => { + appointment_count = 0; + disabled = false; + start_str = slot.from_time; + slot_start_time = moment(slot.from_time, 'HH:mm:ss'); + slot_end_time = moment(slot.to_time, 'HH:mm:ss'); + interval = (slot_end_time - slot_start_time) / 60000 | 0; + + // iterate in all booked appointments, update the start time and duration + slot_info.appointments.forEach((booked) => { + let booked_moment = moment(booked.appointment_time, 'HH:mm:ss'); + let end_time = booked_moment.clone().add(booked.duration, 'minutes'); + + // Deal with 0 duration appointments + if (booked_moment.isSame(slot_start_time) || booked_moment.isBetween(slot_start_time, slot_end_time)) { + if (booked.duration == 0) { + disabled = true; + return false; + } + } + + // Check for overlaps considering appointment duration + if (slot_info.allow_overlap != 1) { + if (slot_start_time.isBefore(end_time) && slot_end_time.isAfter(booked_moment)) { + // There is an overlap + disabled = true; + return false; + } + } else { + if (slot_start_time.isBefore(end_time) && slot_end_time.isAfter(booked_moment)) { + appointment_count++; + } + if (appointment_count >= slot_info.service_unit_capacity) { + // There is an overlap + disabled = true; + return false; + } + } + }); + + if (slot_info.allow_overlap == 1 && slot_info.service_unit_capacity > 1) { + available_slots = slot_info.service_unit_capacity - appointment_count; + count = `${(available_slots > 0 ? available_slots : __('Full'))}`; + count_class = `${(available_slots > 0 ? 'badge-success' : 'badge-danger')}`; + tool_tip =`${available_slots} ${__('slots available for booking')}`; + } + return ` + `; + }).join(""); + + if (slot_info.service_unit_capacity) { + slot_html += `
    ${__('Each slot indicates the capacity currently available for booking')}`; + } + slot_html += `

    `; + }); + + return slot_html; + } }; let get_prescribed_procedure = function(frm) { if (frm.doc.patient) { frappe.call({ method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_procedure_prescribed', - args: {patient: frm.doc.patient}, + args: { patient: frm.doc.patient }, callback: function(r) { if (r.message && r.message.length) { show_procedure_templates(frm, r.message); @@ -480,7 +526,7 @@ let get_prescribed_procedure = function(frm) { } }; -let show_procedure_templates = function(frm, result){ +let show_procedure_templates = function(frm, result) { let d = new frappe.ui.Dialog({ title: __('Prescribed Procedures'), fields: [ @@ -500,9 +546,11 @@ let show_procedure_templates = function(frm, result){ data-encounter="%(encounter)s" data-practitioner="%(practitioner)s"\ data-date="%(date)s" data-department="%(department)s">\

    ', {name:y[0], procedure_template: y[1], - encounter:y[2], consulting_practitioner:y[3], encounter_date:y[4], - practitioner:y[5]? y[5]:'', date: y[6]? y[6]:'', department: y[7]? y[7]:''})).appendTo(html_field); +

    ', { + name: y[0], procedure_template: y[1], + encounter: y[2], consulting_practitioner: y[3], encounter_date: y[4], + practitioner: y[5] ? y[5] : '', date: y[6] ? y[6] : '', department: y[7] ? y[7] : '' + })).appendTo(html_field); row.find("a").click(function() { frm.doc.procedure_template = $(this).attr('data-procedure-template'); frm.doc.procedure_prescription = $(this).attr('data-name'); @@ -520,7 +568,7 @@ let show_procedure_templates = function(frm, result){ }); if (!result) { let msg = __('There are no procedure prescribed for ') + frm.doc.patient; - $(repl('
    %(msg)s
    ', {msg: msg})).appendTo(html_field); + $(repl('
    %(msg)s
    ', { msg: msg })).appendTo(html_field); } d.show(); }; @@ -535,7 +583,7 @@ let show_therapy_types = function(frm, result) { ] }); var html_field = d.fields_dict.therapy_type.$wrapper; - $.each(result, function(x, y){ + $.each(result, function(x, y) { var row = $(repl('
    \
    %(encounter)s
    %(practitioner)s
    %(date)s
    \
    %(therapy)s
    \ @@ -544,9 +592,11 @@ let show_therapy_types = function(frm, result) { data-encounter="%(encounter)s" data-practitioner="%(practitioner)s"\ data-date="%(date)s" data-department="%(department)s">\

    ', {therapy:y[0], - name: y[1], encounter:y[2], practitioner:y[3], date:y[4], - department:y[6]? y[6]:'', therapy_plan:y[5]})).appendTo(html_field); +

    ', { + therapy: y[0], + name: y[1], encounter: y[2], practitioner: y[3], date: y[4], + department: y[6] ? y[6] : '', therapy_plan: y[5] + })).appendTo(html_field); row.find("a").click(function() { frm.doc.therapy_type = $(this).attr("data-therapy"); @@ -581,13 +631,13 @@ let create_vital_signs = function(frm) { frappe.new_doc('Vital Signs'); }; -let update_status = function(frm, status){ +let update_status = function(frm, status) { let doc = frm.doc; frappe.confirm(__('Are you sure you want to cancel this appointment?'), function() { frappe.call({ method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_status', - args: {appointment_id: doc.name, status:status}, + args: { appointment_id: doc.name, status: status }, callback: function(data) { if (!data.exc) { frm.reload_doc(); diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index 73ec3bc325..28d3a6dadf 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -131,7 +131,7 @@ "fieldtype": "Link", "label": "Service Unit", "options": "Healthcare Service Unit", - "set_only_once": 1 + "read_only": 1 }, { "depends_on": "eval:doc.practitioner;", @@ -349,7 +349,7 @@ } ], "links": [], - "modified": "2021-06-16 00:40:26.841794", + "modified": "2021-08-30 09:00:41.329387", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Appointment", diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index 7db4fa616a..36047c4838 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -15,6 +15,11 @@ from erpnext.hr.doctype.employee.employee import is_holiday from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account from erpnext.healthcare.utils import check_fee_validity, get_service_item_and_practitioner_charge, manage_fee_validity +class MaximumCapacityError(frappe.ValidationError): + pass +class OverlapError(frappe.ValidationError): + pass + class PatientAppointment(Document): def validate(self): self.validate_overlaps() @@ -49,26 +54,49 @@ class PatientAppointment(Document): end_time = datetime.datetime.combine(getdate(self.appointment_date), get_time(self.appointment_time)) \ + datetime.timedelta(minutes=flt(self.duration)) - overlaps = frappe.db.sql(""" - select - name, practitioner, patient, appointment_time, duration - from - `tabPatient Appointment` - where - appointment_date=%s and name!=%s and status NOT IN ("Closed", "Cancelled") - and (practitioner=%s or patient=%s) and - ((appointment_time<%s and appointment_time + INTERVAL duration MINUTE>%s) or - (appointment_time>%s and appointment_time<%s) or - (appointment_time=%s)) - """, (self.appointment_date, self.name, self.practitioner, self.patient, - self.appointment_time, end_time.time(), self.appointment_time, end_time.time(), self.appointment_time)) + # all appointments for both patient and practitioner overlapping the duration of this appointment + overlapping_appointments = frappe.db.sql(""" + SELECT + name, practitioner, patient, appointment_time, duration, service_unit + FROM + `tabPatient Appointment` + WHERE + appointment_date=%(appointment_date)s AND name!=%(name)s AND status NOT IN ("Closed", "Cancelled") AND + (practitioner=%(practitioner)s OR patient=%(patient)s) AND + ((appointment_time<%(appointment_time)s AND appointment_time + INTERVAL duration MINUTE>%(appointment_time)s) OR + (appointment_time>%(appointment_time)s AND appointment_time<%(end_time)s) OR + (appointment_time=%(appointment_time)s)) + """, + { + 'appointment_date': self.appointment_date, + 'name': self.name, + 'practitioner': self.practitioner, + 'patient': self.patient, + 'appointment_time': self.appointment_time, + 'end_time':end_time.time() + }, + as_dict = True + ) + + if not overlapping_appointments: + return # No overlaps, nothing to validate! + + if self.service_unit: # validate service unit capacity if overlap enabled + allow_overlap, service_unit_capacity = frappe.get_value('Healthcare Service Unit', self.service_unit, + ['overlap_appointments', 'service_unit_capacity']) + if allow_overlap: + service_unit_appointments = list(filter(lambda appointment: appointment['service_unit'] == self.service_unit and + appointment['patient'] != self.patient, overlapping_appointments)) # if same patient already booked, it should be an overlap + if len(service_unit_appointments) >= (service_unit_capacity or 1): + frappe.throw(_("Not allowed, {} cannot exceed maximum capacity {}") + .format(frappe.bold(self.service_unit), frappe.bold(service_unit_capacity or 1)), MaximumCapacityError) + else: # service_unit_appointments within capacity, remove from overlapping_appointments + overlapping_appointments = [appointment for appointment in overlapping_appointments if appointment not in service_unit_appointments] + + if overlapping_appointments: + frappe.throw(_("Not allowed, cannot overlap appointment {}") + .format(frappe.bold(', '.join([appointment['name'] for appointment in overlapping_appointments]))), OverlapError) - if overlaps: - overlapping_details = _('Appointment overlaps with ') - overlapping_details += "{0}
    ".format(overlaps[0][0]) - overlapping_details += _('{0} has appointment scheduled with {1} at {2} having {3} minute(s) duration.').format( - overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4]) - frappe.throw(overlapping_details, title=_('Appointments Overlapping')) def validate_service_unit(self): if self.inpatient_record and self.service_unit: @@ -325,6 +353,8 @@ def get_available_slots(practitioner_doc, date): if available_slots: appointments = [] + allow_overlap = 0 + service_unit_capacity = 0 # fetch all appointments to practitioner by service unit filters = { 'practitioner': practitioner, @@ -334,8 +364,8 @@ def get_available_slots(practitioner_doc, date): } if schedule_entry.service_unit: - slot_name = schedule_entry.schedule + ' - ' + schedule_entry.service_unit - allow_overlap = frappe.get_value('Healthcare Service Unit', schedule_entry.service_unit, 'overlap_appointments') + slot_name = f'{schedule_entry.schedule}' + allow_overlap, service_unit_capacity = frappe.get_value('Healthcare Service Unit', schedule_entry.service_unit, ['overlap_appointments', 'service_unit_capacity']) if not allow_overlap: # fetch all appointments to service unit filters.pop('practitioner') @@ -350,8 +380,8 @@ def get_available_slots(practitioner_doc, date): filters=filters, fields=['name', 'appointment_time', 'duration', 'status']) - slot_details.append({'slot_name':slot_name, 'service_unit':schedule_entry.service_unit, - 'avail_slot':available_slots, 'appointments': appointments}) + slot_details.append({'slot_name': slot_name, 'service_unit': schedule_entry.service_unit, 'avail_slot': available_slots, + 'appointments': appointments, 'allow_overlap': allow_overlap, 'service_unit_capacity': service_unit_capacity}) return slot_details diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 157b3e1162..f5477c096a 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -16,9 +16,11 @@ class TestPatientAppointment(unittest.TestCase): frappe.db.sql("""delete from `tabFee Validity`""") frappe.db.sql("""delete from `tabPatient Encounter`""") make_pos_profile() + frappe.db.sql("""delete from `tabHealthcare Service Unit` where name like '_Test %'""") + frappe.db.sql("""delete from `tabHealthcare Service Unit` where name like '_Test Service Unit Type%'""") def test_status(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0) appointment = create_appointment(patient, practitioner, nowdate()) self.assertEqual(appointment.status, 'Open') @@ -30,7 +32,7 @@ class TestPatientAppointment(unittest.TestCase): self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') def test_start_encounter(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4), invoice = 1) appointment.reload() @@ -44,7 +46,7 @@ class TestPatientAppointment(unittest.TestCase): self.assertEqual(encounter.invoiced, frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced')) def test_auto_invoicing(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0) appointment = create_appointment(patient, practitioner, nowdate()) @@ -60,13 +62,14 @@ class TestPatientAppointment(unittest.TestCase): self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount) def test_auto_invoicing_based_on_department(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() + medical_department = create_medical_department() frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) appointment_type = create_appointment_type() appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2), - invoice=1, appointment_type=appointment_type.name, department='_Test Medical Department') + invoice=1, appointment_type=appointment_type.name, department=medical_department) appointment.reload() self.assertEqual(appointment.invoiced, 1) @@ -78,7 +81,7 @@ class TestPatientAppointment(unittest.TestCase): self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount) def test_auto_invoicing_according_to_appointment_type_charge(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) @@ -104,7 +107,7 @@ class TestPatientAppointment(unittest.TestCase): self.assertTrue(sales_invoice_name) def test_appointment_cancel(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1) appointment = create_appointment(patient, practitioner, nowdate()) fee_validity = frappe.db.get_value('Fee Validity', {'patient': patient, 'practitioner': practitioner}) @@ -112,7 +115,7 @@ class TestPatientAppointment(unittest.TestCase): self.assertTrue(fee_validity) # first follow up appointment - appointment = create_appointment(patient, practitioner, nowdate()) + appointment = create_appointment(patient, practitioner, add_days(nowdate(), 1)) self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), 1) update_status(appointment.name, 'Cancelled') @@ -121,7 +124,7 @@ class TestPatientAppointment(unittest.TestCase): frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) - appointment = create_appointment(patient, practitioner, nowdate(), invoice=1) + appointment = create_appointment(patient, practitioner, add_days(nowdate(), 1), invoice=1) update_status(appointment.name, 'Cancelled') # check invoice cancelled sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent') @@ -133,7 +136,7 @@ class TestPatientAppointment(unittest.TestCase): create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy frappe.db.sql("""delete from `tabInpatient Record`""") - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() patient = create_patient() # Schedule Admission ip_record = create_inpatient(patient) @@ -141,7 +144,7 @@ class TestPatientAppointment(unittest.TestCase): ip_record.save(ignore_permissions = True) # Admit - service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy') + service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy') admit_patient(ip_record, service_unit, now_datetime()) appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit) @@ -159,7 +162,7 @@ class TestPatientAppointment(unittest.TestCase): create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy frappe.db.sql("""delete from `tabInpatient Record`""") - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() patient = create_patient() # Schedule Admission ip_record = create_inpatient(patient) @@ -167,10 +170,10 @@ class TestPatientAppointment(unittest.TestCase): ip_record.save(ignore_permissions = True) # Admit - service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy') + service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy') admit_patient(ip_record, service_unit, now_datetime()) - appointment_service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy for Appointment') + appointment_service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy for Appointment') appointment = create_appointment(patient, practitioner, nowdate(), service_unit=appointment_service_unit, save=0) self.assertRaises(frappe.exceptions.ValidationError, appointment.save) @@ -192,7 +195,7 @@ class TestPatientAppointment(unittest.TestCase): assert payment_required is True def test_sales_invoice_should_be_generated_for_new_patient_appointment(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) invoice_count = frappe.db.count('Sales Invoice') @@ -203,10 +206,10 @@ class TestPatientAppointment(unittest.TestCase): assert new_invoice_count == invoice_count + 1 def test_patient_appointment_should_consider_permissions_while_fetching_appointments(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() create_appointment(patient, practitioner, nowdate()) - patient, medical_department, new_practitioner = create_healthcare_docs(practitioner_name='Dr. John') + patient, new_practitioner = create_healthcare_docs(id=5) create_appointment(patient, new_practitioner, nowdate()) roles = [{"doctype": "Has Role", "role": "Physician"}] @@ -223,41 +226,102 @@ class TestPatientAppointment(unittest.TestCase): appointments = frappe.get_list('Patient Appointment') assert len(appointments) == 2 -def create_healthcare_docs(practitioner_name=None): - if not practitioner_name: - practitioner_name = '_Test Healthcare Practitioner' + def test_overlap_appointment(self): + from erpnext.healthcare.doctype.patient_appointment.patient_appointment import OverlapError + patient, practitioner = create_healthcare_docs(id=1) + patient_1, practitioner_1 = create_healthcare_docs(id=2) + service_unit = create_service_unit(id=0) + service_unit_1 = create_service_unit(id=1) + appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit) # valid - patient = create_patient() - practitioner = frappe.db.exists('Healthcare Practitioner', practitioner_name) - medical_department = frappe.db.exists('Medical Department', '_Test Medical Department') + # patient and practitioner cannot have overlapping appointments + appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit, save=0) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit_1, save=0) # diff service unit + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient, practitioner, nowdate(), save=0) # with no service unit link + self.assertRaises(OverlapError, appointment.save) - if not medical_department: - medical_department = frappe.new_doc('Medical Department') - medical_department.department = '_Test Medical Department' - medical_department.save(ignore_permissions=True) - medical_department = medical_department.name + # patient cannot have overlapping appointments with other practitioners + appointment = create_appointment(patient, practitioner_1, nowdate(), service_unit=service_unit, save=0) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient, practitioner_1, nowdate(), service_unit=service_unit_1, save=0) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient, practitioner_1, nowdate(), save=0) + self.assertRaises(OverlapError, appointment.save) - if not practitioner: - practitioner = frappe.new_doc('Healthcare Practitioner') - practitioner.first_name = practitioner_name - practitioner.gender = 'Female' - practitioner.department = medical_department - practitioner.op_consulting_charge = 500 - practitioner.inpatient_visit_charge = 500 - practitioner.save(ignore_permissions=True) - practitioner = practitioner.name + # practitioner cannot have overlapping appointments with other patients + appointment = create_appointment(patient_1, practitioner, nowdate(), service_unit=service_unit, save=0) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient_1, practitioner, nowdate(), service_unit=service_unit_1, save=0) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient_1, practitioner, nowdate(), save=0) + self.assertRaises(OverlapError, appointment.save) - return patient, medical_department, practitioner + def test_service_unit_capacity(self): + from erpnext.healthcare.doctype.patient_appointment.patient_appointment import MaximumCapacityError, OverlapError + practitioner = create_practitioner() + capacity = 3 + overlap_service_unit_type = create_service_unit_type(id=10, allow_appointments=1, overlap_appointments=1) + overlap_service_unit = create_service_unit(id=100, service_unit_type=overlap_service_unit_type, service_unit_capacity=capacity) + + for i in range(0, capacity): + patient = create_patient(id=i) + create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit) # valid + appointment = create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0) # overlap + self.assertRaises(OverlapError, appointment.save) + + patient = create_patient(id=capacity) + appointment = create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0) + self.assertRaises(MaximumCapacityError, appointment.save) + + +def create_healthcare_docs(id=0): + patient = create_patient(id) + practitioner = create_practitioner(id) + + return patient, practitioner + + +def create_patient(id=0): + if frappe.db.exists('Patient', {'firstname':f'_Test Patient {str(id)}'}): + patient = frappe.db.get_value('Patient', {'first_name': f'_Test Patient {str(id)}'}, ['name']) + return patient + + patient = frappe.new_doc('Patient') + patient.first_name = f'_Test Patient {str(id)}' + patient.sex = 'Female' + patient.save(ignore_permissions=True) + + return patient.name + + +def create_medical_department(id=0): + if frappe.db.exists('Medical Department', f'_Test Medical Department {str(id)}'): + return f'_Test Medical Department {str(id)}' + + medical_department = frappe.new_doc('Medical Department') + medical_department.department = f'_Test Medical Department {str(id)}' + medical_department.save(ignore_permissions=True) + + return medical_department.name + + +def create_practitioner(id=0, medical_department=None): + if frappe.db.exists('Healthcare Practitioner', {'firstname':f'_Test Healthcare Practitioner {str(id)}'}): + practitioner = frappe.db.get_value('Healthcare Practitioner', {'firstname':f'_Test Healthcare Practitioner {str(id)}'}, ['name']) + return practitioner + + practitioner = frappe.new_doc('Healthcare Practitioner') + practitioner.first_name = f'_Test Healthcare Practitioner {str(id)}' + practitioner.gender = 'Female' + practitioner.department = medical_department or create_medical_department(id) + practitioner.op_consulting_charge = 500 + practitioner.inpatient_visit_charge = 500 + practitioner.save(ignore_permissions=True) + + return practitioner.name -def create_patient(): - patient = frappe.db.exists('Patient', '_Test Patient') - if not patient: - patient = frappe.new_doc('Patient') - patient.first_name = '_Test Patient' - patient.sex = 'Female' - patient.save(ignore_permissions=True) - patient = patient.name - return patient def create_encounter(appointment): if appointment: @@ -270,8 +334,10 @@ def create_encounter(appointment): encounter.company = appointment.company encounter.save() encounter.submit() + return encounter + def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0, service_unit=None, appointment_type=None, save=1, department=None): item = create_healthcare_service_items() @@ -284,6 +350,7 @@ def create_appointment(patient, practitioner, appointment_date, invoice=0, proce appointment.appointment_date = appointment_date appointment.company = '_Test Company' appointment.duration = 15 + if service_unit: appointment.service_unit = service_unit if invoice: @@ -294,11 +361,14 @@ def create_appointment(patient, practitioner, appointment_date, invoice=0, proce appointment.procedure_template = create_clinical_procedure_template().get('name') if save: appointment.save(ignore_permissions=True) + return appointment + def create_healthcare_service_items(): if frappe.db.exists('Item', 'HLC-SI-001'): return 'HLC-SI-001' + item = frappe.new_doc('Item') item.item_code = 'HLC-SI-001' item.item_name = 'Consulting Charges' @@ -306,11 +376,14 @@ def create_healthcare_service_items(): item.is_stock_item = 0 item.stock_uom = 'Nos' item.save() + return item.name + def create_clinical_procedure_template(): if frappe.db.exists('Clinical Procedure Template', 'Knee Surgery and Rehab'): return frappe.get_doc('Clinical Procedure Template', 'Knee Surgery and Rehab') + template = frappe.new_doc('Clinical Procedure Template') template.template = 'Knee Surgery and Rehab' template.item_code = 'Knee Surgery and Rehab' @@ -319,8 +392,10 @@ def create_clinical_procedure_template(): template.description = 'Knee Surgery and Rehab' template.rate = 50000 template.save() + return template + def create_appointment_type(args=None): if not args: args = frappe.local.form_dict @@ -359,3 +434,30 @@ def create_user(email=None, roles=None): "roles": roles, }).insert() return user + + +def create_service_unit_type(id=0, allow_appointments=1, overlap_appointments=0): + if frappe.db.exists('Healthcare Service Unit Type', f'_Test Service Unit Type {str(id)}'): + return f'_Test Service Unit Type {str(id)}' + + service_unit_type = frappe.new_doc('Healthcare Service Unit Type') + service_unit_type.service_unit_type = f'_Test Service Unit Type {str(id)}' + service_unit_type.allow_appointments = allow_appointments + service_unit_type.overlap_appointments = overlap_appointments + service_unit_type.save(ignore_permissions=True) + + return service_unit_type.name + + +def create_service_unit(id=0, service_unit_type=None, service_unit_capacity=0): + if frappe.db.exists('Healthcare Service Unit', f'_Test Service Unit {str(id)}'): + return f'_Test service_unit {str(id)}' + + service_unit = frappe.new_doc('Healthcare Service Unit') + service_unit.is_group = 0 + service_unit.healthcare_service_unit_name= f'_Test Service Unit {str(id)}' + service_unit.service_unit_type = service_unit_type or create_service_unit_type(id) + service_unit.service_unit_capacity = service_unit_capacity + service_unit.save(ignore_permissions=True) + + return service_unit.name diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py index f8ccc8a002..5b7d8d62c8 100644 --- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py +++ b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import unittest import frappe from frappe.utils import nowdate -from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_encounter, create_healthcare_docs, create_appointment +from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_encounter, create_healthcare_docs, create_appointment, create_medical_department from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile class TestPatientMedicalRecord(unittest.TestCase): @@ -15,7 +15,8 @@ class TestPatientMedicalRecord(unittest.TestCase): make_pos_profile() def test_medical_record(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() + medical_department = create_medical_department() appointment = create_appointment(patient, practitioner, nowdate(), invoice=1) encounter = create_encounter(appointment) diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py index 113fa513f9..983fba9f5f 100644 --- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py @@ -8,11 +8,13 @@ import unittest 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, create_appointment +from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import \ + create_healthcare_docs, create_patient, create_appointment, create_medical_department class TestTherapyPlan(unittest.TestCase): def test_creation_on_encounter_submission(self): - patient, medical_department, practitioner = create_healthcare_docs() + patient, practitioner = create_healthcare_docs() + medical_department = create_medical_department() encounter = create_encounter(patient, medical_department, practitioner) self.assertTrue(frappe.db.exists('Therapy Plan', encounter.therapy_plan)) @@ -28,8 +30,9 @@ class TestTherapyPlan(unittest.TestCase): frappe.get_doc(session).submit() self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed') - patient, medical_department, practitioner = create_healthcare_docs() - appointment = create_appointment(patient, practitioner, nowdate()) + patient, 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() diff --git a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py index a5dad293e3..80fc83fd6c 100644 --- a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py +++ b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py @@ -34,7 +34,8 @@ def create_therapy_type(): }) therapy_type.save() else: - therapy_type = frappe.get_doc('Therapy Type', 'Basic Rehab') + therapy_type = frappe.get_doc('Therapy Type', therapy_type) + return therapy_type def create_exercise_type(): @@ -47,4 +48,7 @@ def create_exercise_type(): 'description': 'Squat and Rise' }) exercise_type.save() + else: + exercise_type = frappe.get_doc('Exercise Type', exercise_type) + return exercise_type diff --git a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py index 4b461f1a97..fae5ecef84 100644 --- a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py +++ b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py @@ -25,7 +25,7 @@ class TestInpatientMedicationOrders(unittest.TestCase): 'from_date': getdate(), 'to_date': getdate(), 'patient': '_Test IPD Patient', - 'service_unit': 'Test Service Unit Ip Occupancy - _TC' + 'service_unit': '_Test Service Unit Ip Occupancy - _TC' } report = execute(filters) @@ -42,7 +42,7 @@ class TestInpatientMedicationOrders(unittest.TestCase): 'date': getdate(), 'time': datetime.timedelta(seconds=32400), 'is_completed': 0, - 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC' + 'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC' }, { 'patient': '_Test IPD Patient', @@ -55,7 +55,7 @@ class TestInpatientMedicationOrders(unittest.TestCase): 'date': getdate(), 'time': datetime.timedelta(seconds=50400), 'is_completed': 0, - 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC' + 'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC' }, { 'patient': '_Test IPD Patient', @@ -68,7 +68,7 @@ class TestInpatientMedicationOrders(unittest.TestCase): 'date': getdate(), 'time': datetime.timedelta(seconds=75600), 'is_completed': 0, - 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC' + 'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC' } ] @@ -83,7 +83,7 @@ class TestInpatientMedicationOrders(unittest.TestCase): 'from_date': getdate(), 'to_date': getdate(), 'patient': '_Test IPD Patient', - 'service_unit': 'Test Service Unit Ip Occupancy - _TC', + 'service_unit': '_Test Service Unit Ip Occupancy - _TC', 'show_completed_orders': 0 } @@ -119,7 +119,7 @@ def create_records(patient): ip_record.expected_length_of_stay = 0 ip_record.save() ip_record.reload() - service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy') + service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy') admit_patient(ip_record, service_unit, now_datetime()) ipmo = create_ipmo(patient) diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py index d3d22c80b6..ffecf4dce2 100644 --- a/erpnext/healthcare/utils.py +++ b/erpnext/healthcare/utils.py @@ -543,58 +543,43 @@ def get_drugs_to_invoice(encounter): @frappe.whitelist() -def get_children(doctype, parent, company, is_root=False): - parent_fieldname = "parent_" + doctype.lower().replace(" ", "_") +def get_children(doctype, parent=None, company=None, is_root=False): + parent_fieldname = 'parent_' + doctype.lower().replace(' ', '_') fields = [ - "name as value", - "is_group as expandable", - "lft", - "rgt" + 'name as value', + 'is_group as expandable', + 'lft', + 'rgt' ] - # fields = [ "name", "is_group", "lft", "rgt" ] - filters = [["ifnull(`{0}`,'')".format(parent_fieldname), "=", "" if is_root else parent]] + + filters = [["ifnull(`{0}`,'')".format(parent_fieldname), + '=', '' if is_root else parent]] if is_root: - fields += ["service_unit_type"] if doctype == "Healthcare Service Unit" else [] - filters.append(["company", "=", company]) - + fields += ['service_unit_type'] if doctype == 'Healthcare Service Unit' else [] + filters.append(['company', '=', company]) else: - fields += ["service_unit_type", "allow_appointments", "inpatient_occupancy", "occupancy_status"] if doctype == "Healthcare Service Unit" else [] - fields += [parent_fieldname + " as parent"] + fields += ['service_unit_type', 'allow_appointments', 'inpatient_occupancy', + 'occupancy_status'] if doctype == 'Healthcare Service Unit' else [] + fields += [parent_fieldname + ' as parent'] - hc_service_units = frappe.get_list(doctype, fields=fields, filters=filters) + service_units = frappe.get_list(doctype, fields=fields, filters=filters) + for each in service_units: + if each['expandable'] == 1: # group node + available_count = frappe.db.count('Healthcare Service Unit', filters={ + 'parent_healthcare_service_unit': each['value'], + 'inpatient_occupancy': 1}) - if doctype == "Healthcare Service Unit": - for each in hc_service_units: - occupancy_msg = "" - if each["expandable"] == 1: - occupied = False - vacant = False - child_list = frappe.db.sql( - ''' - SELECT - name, occupancy_status - FROM - `tabHealthcare Service Unit` - WHERE - inpatient_occupancy = 1 - and lft > %s and rgt < %s - ''', (each['lft'], each['rgt'])) + if available_count > 0: + occupied_count = frappe.db.count('Healthcare Service Unit', { + 'parent_healthcare_service_unit': each['value'], + 'inpatient_occupancy': 1, + 'occupancy_status': 'Occupied'}) + # set occupancy status of group node + each['occupied_of_available'] = str( + occupied_count) + ' Occupied of ' + str(available_count) - for child in child_list: - if not occupied: - occupied = 0 - if child[1] == "Occupied": - occupied += 1 - if not vacant: - vacant = 0 - if child[1] == "Vacant": - vacant += 1 - if vacant and occupied: - occupancy_total = vacant + occupied - occupancy_msg = str(occupied) + " Occupied out of " + str(occupancy_total) - each["occupied_out_of_vacant"] = occupancy_msg - return hc_service_units + return service_units @frappe.whitelist() @@ -717,3 +702,40 @@ def render_doc_as_html(doctype, docname, exclude_fields = []): doc_html = "
    " %(doctype, docname) + doc_html + '
    ' return {'html': doc_html} + + +def update_address_links(address, method): + ''' + Hook validate Address + If Patient is linked in Address, also link the associated Customer + ''' + if 'Healthcare' not in frappe.get_active_domains(): + return + + patient_links = list(filter(lambda link: link.get('link_doctype') == 'Patient', address.links)) + + for link in patient_links: + customer = frappe.db.get_value('Patient', link.get('link_name'), 'customer') + if customer and not address.has_link('Customer', customer): + address.append('links', dict(link_doctype = 'Customer', link_name = customer)) + + +def update_patient_email_and_phone_numbers(contact, method): + ''' + Hook validate Contact + Update linked Patients' primary mobile and phone numbers + ''' + if 'Healthcare' not in frappe.get_active_domains(): + return + + if contact.is_primary_contact and (contact.email_id or contact.mobile_no or contact.phone): + patient_links = list(filter(lambda link: link.get('link_doctype') == 'Patient', contact.links)) + + for link in patient_links: + contact_details = frappe.db.get_value('Patient', link.get('link_name'), ['email', 'mobile', 'phone'], as_dict=1) + if contact.email_id and contact.email_id != contact_details.get('email'): + frappe.db.set_value('Patient', link.get('link_name'), 'email', contact.email_id) + if contact.mobile_no and contact.mobile_no != contact_details.get('mobile'): + frappe.db.set_value('Patient', link.get('link_name'), 'mobile', contact.mobile_no) + if contact.phone and contact.phone != contact_details.get('phone'): + frappe.db.set_value('Patient', link.get('link_name'), 'phone', contact.phone) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 4854bfd1e1..b1a64f95bf 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -290,7 +290,12 @@ doc_events = { "on_trash": "erpnext.regional.check_deletion_permission" }, 'Address': { - 'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code', 'erpnext.regional.india.utils.update_gst_category'] + 'validate': [ + 'erpnext.regional.india.utils.validate_gstin_for_india', + 'erpnext.regional.italy.utils.set_state_code', + 'erpnext.regional.india.utils.update_gst_category', + 'erpnext.healthcare.utils.update_address_links' + ], }, 'Supplier': { 'validate': 'erpnext.regional.india.utils.validate_pan_for_india' @@ -301,7 +306,7 @@ doc_events = { "Contact": { "on_trash": "erpnext.support.doctype.issue.issue.update_issue", "after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations", - "validate": "erpnext.crm.utils.update_lead_phone_numbers" + "validate": ["erpnext.crm.utils.update_lead_phone_numbers", "erpnext.healthcare.utils.update_patient_email_and_phone_numbers"] }, "Email Unsubscribe": { "after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient" From 9cb642238f6c9477487f0e6dd3f48ff1d1b27145 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Mon, 30 Aug 2021 13:17:22 +0530 Subject: [PATCH 76/98] fix: expense claim reimbursed amount update (#27204) --- .../doctype/journal_entry/journal_entry.py | 5 +- .../doctype/payment_entry/payment_entry.py | 9 ++-- .../hr/doctype/expense_claim/expense_claim.py | 20 ++------ .../expense_claim/test_expense_claim.py | 47 ++++++++++++++++++- 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 937597bc55..72648753a7 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -643,7 +643,10 @@ class JournalEntry(AccountsController): for d in self.accounts: if d.reference_type=="Expense Claim" and d.reference_name: doc = frappe.get_doc("Expense Claim", d.reference_name) - update_reimbursed_amount(doc, jv=self.name) + if self.docstatus == 2: + update_reimbursed_amount(doc, -1 * d.debit) + else: + update_reimbursed_amount(doc, d.debit) def validate_expense_claim(self): diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index b259b11c63..a5fcad4996 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -75,9 +75,9 @@ class PaymentEntry(AccountsController): if self.difference_amount: frappe.throw(_("Difference Amount must be zero")) self.make_gl_entries() + self.update_expense_claim() self.update_outstanding_amounts() self.update_advance_paid() - self.update_expense_claim() self.update_donation() self.update_payment_schedule() self.set_status() @@ -85,9 +85,9 @@ class PaymentEntry(AccountsController): def on_cancel(self): self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry') self.make_gl_entries(cancel=1) + self.update_expense_claim() self.update_outstanding_amounts() self.update_advance_paid() - self.update_expense_claim() self.update_donation(cancel=1) self.delink_advance_entry_references() self.update_payment_schedule(cancel=1) @@ -831,7 +831,10 @@ class PaymentEntry(AccountsController): for d in self.get("references"): if d.reference_doctype=="Expense Claim" and d.reference_name: doc = frappe.get_doc("Expense Claim", d.reference_name) - update_reimbursed_amount(doc, self.name) + if self.docstatus == 2: + update_reimbursed_amount(doc, -1 * d.allocated_amount) + else: + update_reimbursed_amount(doc, d.allocated_amount) def update_donation(self, cancel=0): if self.payment_type == "Receive" and self.party_type == "Donor" and self.party: diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 95e2806aed..4dc089cb1d 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -77,7 +77,7 @@ class ExpenseClaim(AccountsController): self.make_gl_entries() if self.is_paid: - update_reimbursed_amount(self) + update_reimbursed_amount(self, self.grand_total) self.set_status(update=True) self.update_claimed_amount_in_employee_advance() @@ -89,7 +89,7 @@ class ExpenseClaim(AccountsController): self.make_gl_entries(cancel=True) if self.is_paid: - update_reimbursed_amount(self) + update_reimbursed_amount(self, -1 * self.grand_total) self.update_claimed_amount_in_employee_advance() @@ -270,20 +270,10 @@ class ExpenseClaim(AccountsController): if not expense.default_account or not validate: expense.default_account = get_expense_claim_account(expense.expense_type, self.company)["account"] -def update_reimbursed_amount(doc, jv=None): +def update_reimbursed_amount(doc, amount): - condition = "" - - if jv: - condition += "and voucher_no = '{0}'".format(jv) - - amt = frappe.db.sql("""select ifnull(sum(debit_in_account_currency), 0) - ifnull(sum(credit_in_account_currency), 0)as amt - from `tabGL Entry` where against_voucher_type = 'Expense Claim' and against_voucher = %s - and party = %s {condition}""".format(condition=condition), #nosec - (doc.name, doc.employee) ,as_dict=1)[0].amt - - doc.total_amount_reimbursed = amt - frappe.db.set_value("Expense Claim", doc.name , "total_amount_reimbursed", amt) + doc.total_amount_reimbursed += amount + frappe.db.set_value("Expense Claim", doc.name , "total_amount_reimbursed", doc.total_amount_reimbursed) doc.set_status() frappe.db.set_value("Expense Claim", doc.name , "status", doc.status) diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index c2bd1e9f9f..b5fc1fbe75 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import random_string, nowdate +from frappe.utils import random_string, nowdate, flt from erpnext.hr.doctype.expense_claim.expense_claim import make_bank_entry from erpnext.accounts.doctype.account.test_account import create_account from erpnext.hr.doctype.employee.test_employee import make_employee @@ -138,6 +138,31 @@ class TestExpenseClaim(unittest.TestCase): expense_claim.submit() frappe.set_user("Administrator") + def test_multiple_payment_entries_against_expense(self): + # Creating expense claim + payable_account = get_payable_account("_Test Company") + expense_claim = make_expense_claim(payable_account, 5500, 5500, "_Test Company", "Travel Expenses - _TC") + expense_claim.save() + expense_claim.submit() + + # Payment entry 1: paying 500 + make_payment_entry(expense_claim, payable_account,500) + outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(expense_claim) + self.assertEqual(outstanding_amount, 5000) + self.assertEqual(total_amount_reimbursed, 500) + + # Payment entry 1: paying 2000 + make_payment_entry(expense_claim, payable_account,2000) + outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(expense_claim) + self.assertEqual(outstanding_amount, 3000) + self.assertEqual(total_amount_reimbursed, 2500) + + # Payment entry 1: paying 3000 + make_payment_entry(expense_claim, payable_account,3000) + outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(expense_claim) + self.assertEqual(outstanding_amount, 0) + self.assertEqual(total_amount_reimbursed, 5500) + def get_payable_account(company): return frappe.get_cached_value('Company', company, 'default_payable_account') @@ -191,3 +216,23 @@ def make_expense_claim(payable_account, amount, sanctioned_amount, company, acco return expense_claim expense_claim.submit() return expense_claim + +def get_outstanding_and_total_reimbursed_amounts(expense_claim): + outstanding_amount = flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_sanctioned_amount")) - \ + flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_amount_reimbursed")) + total_amount_reimbursed = flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_amount_reimbursed")) + + return outstanding_amount,total_amount_reimbursed + +def make_payment_entry(expense_claim, payable_account, amt): + from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + + pe = get_payment_entry("Expense Claim", expense_claim.name, bank_account="_Test Bank USD - _TC", bank_amount=amt) + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.source_exchange_rate = 1 + pe.paid_to = payable_account + pe.references[0].allocated_amount = amt + pe.insert() + pe.submit() + From 9de0f7538f87d95dc1af4cbba2d9560145014efd Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 30 Aug 2021 14:07:13 +0530 Subject: [PATCH 77/98] ci: concurrency control for CI jobs (#27230) similar to https://github.com/frappe/frappe/pull/14061 This will ensure that only one instance of the following tests run per PR and cancel previous running/queued jobs when new commits are pushed. - Server / unit tests - UI tests - Patch test --- .github/workflows/patch.yml | 3 +++ .github/workflows/server-tests.yml | 4 ++++ .github/workflows/ui-tests.yml | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index f65b0001fe..7bba4b2e9c 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -7,6 +7,9 @@ on: - '**.md' workflow_dispatch: +concurrency: + group: patch-develop-${{ github.event.number }} + cancel-in-progress: true jobs: test: diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 0a73d74d6b..511a292ee9 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -12,6 +12,10 @@ on: - '**.js' - '**.md' +concurrency: + group: server-develop-${{ github.event.number }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-18.04 diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 0ece0d8ee6..06067ff416 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -6,6 +6,10 @@ on: - '**.md' workflow_dispatch: +concurrency: + group: ui-develop-${{ github.event.number }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-18.04 From fd467e6d326fc6abb7820efc458e6106c38e307f Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 30 Aug 2021 17:53:59 +0530 Subject: [PATCH 78/98] fix: Correct company address not getting copied from Purchase Order to Invoice (#27217) * fix: Correct company adderess not getting copied from Purchase Order to Invoice * fix: Linting issues --- erpnext/public/js/controllers/transaction.js | 26 +++++++++++--------- erpnext/public/js/utils/party.js | 4 +-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 2538852bfa..5f8966f88a 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -866,21 +866,25 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") && in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) { - erpnext.utils.get_shipping_address(this.frm, function(){ + erpnext.utils.get_shipping_address(this.frm, function() { set_party_account(set_pricing); }); // Get default company billing address in Purchase Invoice, Order and Receipt - frappe.call({ - 'method': 'frappe.contacts.doctype.address.address.get_default_address', - 'args': { - 'doctype': 'Company', - 'name': this.frm.doc.company - }, - 'callback': function(r) { - me.frm.set_value('billing_address', r.message); - } - }); + if (this.frm.doc.company && frappe.meta.get_docfield(this.frm.doctype, "billing_address")) { + frappe.call({ + method: "erpnext.setup.doctype.company.company.get_default_company_address", + args: {name: this.frm.doc.company, existing_address: this.frm.doc.billing_address || ""}, + debounce: 2000, + callback: function(r) { + if (r.message) { + me.frm.set_value("billing_address", r.message); + } else { + me.frm.set_value("company_address", ""); + } + } + }); + } } else { set_party_account(set_pricing); diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 4d432e3d5c..a492b32a9f 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -289,8 +289,8 @@ erpnext.utils.get_shipping_address = function(frm, callback) { company: frm.doc.company, address: frm.doc.shipping_address }, - callback: function(r){ - if (r.message){ + callback: function(r) { + if (r.message) { frm.set_value("shipping_address", r.message[0]) //Address title or name frm.set_value("shipping_address_display", r.message[1]) //Address to be displayed on the page } From 5fd04101d4e79dead5e023e68ba24f4149d90426 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 30 Aug 2021 18:11:33 +0530 Subject: [PATCH 79/98] fix: Stock Ageing report issues for serialized items (#27228) * fix: incorrect calculation in get_range_age * fix: remove serial numbers not in stock from fifo_queue * refactor: make serial_no condition explicit * refactor: reduce code duplication Co-authored-by: Ankush Menat --- .../stock/report/stock_ageing/stock_ageing.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index 623dc2ffd9..8a9f0a5e58 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -26,7 +26,7 @@ def execute(filters=None): average_age = get_average_age(fifo_queue, to_date) earliest_age = date_diff(to_date, fifo_queue[0][1]) latest_age = date_diff(to_date, fifo_queue[-1][1]) - range1, range2, range3, above_range3 = get_range_age(filters, fifo_queue, to_date) + range1, range2, range3, above_range3 = get_range_age(filters, fifo_queue, to_date, item_dict) row = [details.name, details.item_name, details.description, details.item_group, details.brand] @@ -58,19 +58,21 @@ def get_average_age(fifo_queue, to_date): return flt(age_qty / total_qty, 2) if total_qty else 0.0 -def get_range_age(filters, fifo_queue, to_date): +def get_range_age(filters, fifo_queue, to_date, item_dict): range1 = range2 = range3 = above_range3 = 0.0 + for item in fifo_queue: age = date_diff(to_date, item[1]) + qty = flt(item[0]) if not item_dict["has_serial_no"] else 1.0 if age <= filters.range1: - range1 += flt(item[0]) + range1 += qty elif age <= filters.range2: - range2 += flt(item[0]) + range2 += qty elif age <= filters.range3: - range3 += flt(item[0]) + range3 += qty else: - above_range3 += flt(item[0]) + above_range3 += qty return range1, range2, range3, above_range3 @@ -197,9 +199,7 @@ def get_fifo_queue(filters, sle=None): fifo_queue.append([d.actual_qty, d.posting_date]) else: if serial_no_list: - for serial_no in fifo_queue: - if serial_no[0] in serial_no_list: - fifo_queue.remove(serial_no) + fifo_queue[:] = [serial_no for serial_no in fifo_queue if serial_no[0] not in serial_no_list] else: qty_to_pop = abs(d.actual_qty) while qty_to_pop: @@ -222,14 +222,16 @@ def get_fifo_queue(filters, sle=None): else: item_details[key]["total_qty"] += d.actual_qty + item_details[key]["has_serial_no"] = d.has_serial_no + return item_details def get_stock_ledger_entries(filters): return frappe.db.sql("""select - item.name, item.item_name, item_group, brand, description, item.stock_uom, + item.name, item.item_name, item_group, brand, description, item.stock_uom, item.has_serial_no, actual_qty, posting_date, voucher_type, voucher_no, serial_no, batch_no, qty_after_transaction, warehouse from `tabStock Ledger Entry` sle, - (select name, item_name, description, stock_uom, brand, item_group + (select name, item_name, description, stock_uom, brand, item_group, has_serial_no from `tabItem` {item_conditions}) item where item_code = item.name and company = %(company)s and From 1682402a9ff157cf31b8871bc4addddefbf4ae12 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Aug 2021 18:26:56 +0530 Subject: [PATCH 80/98] refactor: Healthcare Redesign Changes (#27236) --- .../clinical_procedures.json | 6 +- .../clinical_procedures_status.json | 6 +- .../dashboard_chart/diagnoses/diagnoses.json | 7 +- .../dashboard_chart/lab_tests/lab_tests.json | 6 +- .../dashboard_chart/symptoms/symptoms.json | 6 +- .../healthcare/doctype/lab_test/lab_test.py | 8 +- .../test_patient_appointment.py | 14 +- .../patient_history_settings.py | 6 +- .../test_patient_history_settings.py | 3 +- .../healthcare/healthcare.json | 2 +- .../create_healthcare_practitioner.json | 4 +- .../create_patient/create_patient.json | 6 +- .../create_practitioner_schedule.json | 6 +- .../explore_clinical_procedure_templates.json | 4 +- .../explore_healthcare_settings.json | 4 +- ...troduction_to_healthcare_practitioner.json | 6 +- .../page/patient_history/patient_history.css | 42 +- .../page/patient_history/patient_history.html | 38 +- .../page/patient_history/patient_history.js | 774 ++++++++++-------- .../patient_history_sidebar.html | 21 + .../patient_progress/patient_progress.css | 8 +- .../patient_progress/patient_progress.html | 13 +- .../page/patient_progress/patient_progress.js | 65 +- .../report/lab_test_report/lab_test_report.py | 2 +- erpnext/healthcare/utils.py | 165 ++-- .../workspace/healthcare/healthcare.json | 254 +++--- 26 files changed, 825 insertions(+), 651 deletions(-) create mode 100644 erpnext/healthcare/page/patient_history/patient_history_sidebar.html diff --git a/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json b/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json index a59f149ee5..6803528156 100644 --- a/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json +++ b/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json @@ -12,15 +12,15 @@ "idx": 0, "is_public": 1, "is_standard": 1, - "last_synced_on": "2020-07-22 13:22:47.008622", - "modified": "2020-07-22 13:36:48.114479", + "last_synced_on": "2021-01-30 21:03:30.086891", + "modified": "2021-02-01 13:36:04.469863", "modified_by": "Administrator", "module": "Healthcare", "name": "Clinical Procedures", "number_of_groups": 0, "owner": "Administrator", "timeseries": 0, - "type": "Percentage", + "type": "Bar", "use_report_chart": 0, "y_axis": [] } \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json b/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json index 6d560f74bf..dae9db19b8 100644 --- a/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json +++ b/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json @@ -12,15 +12,15 @@ "idx": 0, "is_public": 1, "is_standard": 1, - "last_synced_on": "2020-07-22 13:22:46.691764", - "modified": "2020-07-22 13:40:17.215775", + "last_synced_on": "2021-02-01 13:36:38.787783", + "modified": "2021-02-01 13:37:18.718275", "modified_by": "Administrator", "module": "Healthcare", "name": "Clinical Procedures Status", "number_of_groups": 0, "owner": "Administrator", "timeseries": 0, - "type": "Pie", + "type": "Bar", "use_report_chart": 0, "y_axis": [] } \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json b/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json index 0195aac8b7..82145d6024 100644 --- a/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json +++ b/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json @@ -5,21 +5,22 @@ "docstatus": 0, "doctype": "Dashboard Chart", "document_type": "Patient Encounter Diagnosis", + "dynamic_filters_json": "", "filters_json": "[]", "group_by_based_on": "diagnosis", "group_by_type": "Count", "idx": 0, "is_public": 1, "is_standard": 1, - "last_synced_on": "2020-07-22 13:22:47.895521", - "modified": "2020-07-22 13:43:32.369481", + "last_synced_on": "2021-01-30 21:03:33.729487", + "modified": "2021-02-01 13:34:57.385335", "modified_by": "Administrator", "module": "Healthcare", "name": "Diagnoses", "number_of_groups": 0, "owner": "Administrator", "timeseries": 0, - "type": "Percentage", + "type": "Bar", "use_report_chart": 0, "y_axis": [] } \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json b/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json index 052483533e..70293b158e 100644 --- a/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json +++ b/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json @@ -12,15 +12,15 @@ "idx": 0, "is_public": 1, "is_standard": 1, - "last_synced_on": "2020-07-22 13:22:47.344055", - "modified": "2020-07-22 13:37:34.490129", + "last_synced_on": "2021-01-30 21:03:28.272914", + "modified": "2021-02-01 13:36:08.391433", "modified_by": "Administrator", "module": "Healthcare", "name": "Lab Tests", "number_of_groups": 0, "owner": "Administrator", "timeseries": 0, - "type": "Percentage", + "type": "Bar", "use_report_chart": 0, "y_axis": [] } \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json b/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json index 8fc86a1c59..65e5472aa1 100644 --- a/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json +++ b/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json @@ -12,15 +12,15 @@ "idx": 0, "is_public": 1, "is_standard": 1, - "last_synced_on": "2020-07-22 13:22:47.296748", - "modified": "2020-07-22 13:40:59.655129", + "last_synced_on": "2021-01-30 21:03:32.067473", + "modified": "2021-02-01 13:35:30.953718", "modified_by": "Administrator", "module": "Healthcare", "name": "Symptoms", "number_of_groups": 0, "owner": "Administrator", "timeseries": 0, - "type": "Percentage", + "type": "Bar", "use_report_chart": 0, "y_axis": [] } \ No newline at end of file diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.py b/erpnext/healthcare/doctype/lab_test/lab_test.py index 4b57cd073d..74495a8591 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.py +++ b/erpnext/healthcare/doctype/lab_test/lab_test.py @@ -34,7 +34,7 @@ class LabTest(Document): frappe.db.set_value('Lab Prescription', self.prescription, 'lab_test_created', 1) if frappe.db.get_value('Lab Prescription', self.prescription, 'invoiced'): self.invoiced = True - if not self.lab_test_name and self.template: + if self.template: self.load_test_from_template() self.reload() @@ -50,7 +50,7 @@ class LabTest(Document): item.secondary_uom_result = float(item.result_value) * float(item.conversion_factor) except: item.secondary_uom_result = '' - frappe.msgprint(_('Row #{0}: Result for Secondary UOM not calculated'.format(item.idx)), title = _('Warning')) + frappe.msgprint(_('Row #{0}: Result for Secondary UOM not calculated').format(item.idx), title = _('Warning')) def validate_result_values(self): if self.normal_test_items: @@ -229,9 +229,9 @@ def create_sample_doc(template, patient, invoice, company = None): sample_collection = frappe.get_doc('Sample Collection', sample_exists[0][0]) quantity = int(sample_collection.sample_qty) + int(template.sample_qty) if template.sample_details: - sample_details = sample_collection.sample_details + '\n-\n' + _('Test: ') + sample_details = sample_collection.sample_details + '\n-\n' + _('Test :') sample_details += (template.get('lab_test_name') or template.get('template')) + '\n' - sample_details += _('Collection Details: ') + '\n\t' + template.sample_details + sample_details += _('Collection Details:') + '\n\t' + template.sample_details frappe.db.set_value('Sample Collection', sample_collection.name, 'sample_details', sample_details) frappe.db.set_value('Sample Collection', sample_collection.name, 'sample_qty', quantity) diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index f5477c096a..36ef2d1623 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -66,7 +66,7 @@ class TestPatientAppointment(unittest.TestCase): medical_department = create_medical_department() frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) - appointment_type = create_appointment_type() + appointment_type = create_appointment_type({'medical_department': medical_department}) appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2), invoice=1, appointment_type=appointment_type.name, department=medical_department) @@ -91,9 +91,9 @@ class TestPatientAppointment(unittest.TestCase): 'op_consulting_charge': 300 }] appointment_type = create_appointment_type(args={ - 'name': 'Generic Appointment Type charge', - 'items': items - }) + 'name': 'Generic Appointment Type charge', + 'items': items + }) appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2), invoice=1, appointment_type=appointment_type.name) @@ -408,9 +408,9 @@ def create_appointment_type(args=None): else: item = create_healthcare_service_items() items = [{ - 'medical_department': '_Test Medical Department', - 'op_consulting_charge_item': item, - 'op_consulting_charge': 200 + 'medical_department': args.get('medical_department') or '_Test Medical Department', + 'op_consulting_charge_item': item, + 'op_consulting_charge': 200 }] return frappe.get_doc({ 'doctype': 'Appointment Type', diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py index 63b00859d7..9e0d3c3e27 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py @@ -18,7 +18,7 @@ class PatientHistorySettings(Document): def validate_submittable_doctypes(self): for entry in self.custom_doctypes: if not cint(frappe.db.get_value('DocType', entry.document_type, 'is_submittable')): - msg = _('Row #{0}: Document Type {1} is not submittable. ').format( + msg = _('Row #{0}: Document Type {1} is not submittable.').format( entry.idx, frappe.bold(entry.document_type)) msg += _('Patient Medical Record can only be created for submittable document types.') frappe.throw(msg) @@ -116,12 +116,12 @@ def set_subject_field(doc): fieldname = entry.get('fieldname') if entry.get('fieldtype') == 'Table' and doc.get(fieldname): formatted_value = get_formatted_value_for_table_field(doc.get(fieldname), meta.get_field(fieldname)) - subject += frappe.bold(_(entry.get('label')) + ': ') + '
    ' + cstr(formatted_value) + '
    ' + subject += frappe.bold(_(entry.get('label')) + ':') + '
    ' + cstr(formatted_value) + '
    ' else: if doc.get(fieldname): formatted_value = format_value(doc.get(fieldname), meta.get_field(fieldname), doc) - subject += frappe.bold(_(entry.get('label')) + ': ') + cstr(formatted_value) + '
    ' + subject += frappe.bold(_(entry.get('label')) + ':') + cstr(formatted_value) + '
    ' return subject diff --git a/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py index 33119d8185..9169ea642b 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py @@ -38,13 +38,12 @@ class TestPatientHistorySettings(unittest.TestCase): # tests for medical record creation of standard doctypes in test_patient_medical_record.py patient = create_patient() doc = create_doc(patient) - # check for medical record medical_rec = frappe.db.exists("Patient Medical Record", {"status": "Open", "reference_name": doc.name}) self.assertTrue(medical_rec) medical_rec = frappe.get_doc("Patient Medical Record", medical_rec) - expected_subject = "Date: {0}Rating: 3Feedback: Test Patient History Settings".format( + expected_subject = "Date:{0}Rating:3Feedback:Test Patient History Settings".format( frappe.utils.format_date(getdate())) self.assertEqual(strip_html(medical_rec.subject), expected_subject) self.assertEqual(medical_rec.patient, patient) diff --git a/erpnext/healthcare/module_onboarding/healthcare/healthcare.json b/erpnext/healthcare/module_onboarding/healthcare/healthcare.json index 56c3c13559..0aa8f9a027 100644 --- a/erpnext/healthcare/module_onboarding/healthcare/healthcare.json +++ b/erpnext/healthcare/module_onboarding/healthcare/healthcare.json @@ -10,7 +10,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/healthcare", "idx": 0, "is_complete": 0, - "modified": "2020-07-08 14:06:19.512946", + "modified": "2021-01-30 19:22:20.273766", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare", diff --git a/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json b/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json index c45a347080..3f25a9d676 100644 --- a/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json +++ b/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json @@ -5,14 +5,14 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-26 23:16:31.965521", + "modified": "2021-01-30 12:02:22.849260", "modified_by": "Administrator", "name": "Create Healthcare Practitioner", "owner": "Administrator", "reference_document": "Healthcare Practitioner", + "show_form_tour": 0, "show_full_form": 1, "title": "Create Healthcare Practitioner", "validate_action": 1 diff --git a/erpnext/healthcare/onboarding_step/create_patient/create_patient.json b/erpnext/healthcare/onboarding_step/create_patient/create_patient.json index 77bc5bd7ad..b46bb15b48 100644 --- a/erpnext/healthcare/onboarding_step/create_patient/create_patient.json +++ b/erpnext/healthcare/onboarding_step/create_patient/create_patient.json @@ -5,14 +5,14 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 12:26:24.023418", - "modified_by": "Administrator", + "modified": "2021-01-30 00:09:28.786428", + "modified_by": "ruchamahabal2@gmail.com", "name": "Create Patient", "owner": "Administrator", "reference_document": "Patient", + "show_form_tour": 0, "show_full_form": 1, "title": "Create Patient", "validate_action": 1 diff --git a/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json b/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json index 65980ef668..7ce122d5c0 100644 --- a/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json +++ b/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json @@ -5,14 +5,14 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-19 12:27:09.437825", - "modified_by": "Administrator", + "modified": "2021-01-30 00:09:28.794602", + "modified_by": "ruchamahabal2@gmail.com", "name": "Create Practitioner Schedule", "owner": "Administrator", "reference_document": "Practitioner Schedule", + "show_form_tour": 0, "show_full_form": 1, "title": "Create Practitioner Schedule", "validate_action": 1 diff --git a/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json b/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json index 697b761e52..dfe9f71a76 100644 --- a/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json +++ b/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json @@ -5,14 +5,14 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-26 23:10:24.504030", + "modified": "2021-01-30 19:22:08.257160", "modified_by": "Administrator", "name": "Explore Clinical Procedure Templates", "owner": "Administrator", "reference_document": "Clinical Procedure Template", + "show_form_tour": 0, "show_full_form": 0, "title": "Explore Clinical Procedure Templates", "validate_action": 1 diff --git a/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json b/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json index b2d5aef431..2d952f3093 100644 --- a/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json +++ b/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json @@ -5,14 +5,14 @@ "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 1, "is_skipped": 0, - "modified": "2020-05-26 23:10:24.507648", + "modified": "2021-01-30 19:22:07.275735", "modified_by": "Administrator", "name": "Explore Healthcare Settings", "owner": "Administrator", "reference_document": "Healthcare Settings", + "show_form_tour": 0, "show_full_form": 0, "title": "Explore Healthcare Settings", "validate_action": 1 diff --git a/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json b/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json index fa4c9036d7..baa8358c06 100644 --- a/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json +++ b/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json @@ -6,14 +6,14 @@ "field": "schedule", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-26 22:07:07.482530", - "modified_by": "Administrator", + "modified": "2021-01-30 00:09:28.807129", + "modified_by": "ruchamahabal2@gmail.com", "name": "Introduction to Healthcare Practitioner", "owner": "Administrator", "reference_document": "Healthcare Practitioner", + "show_form_tour": 0, "show_full_form": 0, "title": "Introduction to Healthcare Practitioner", "validate_action": 0 diff --git a/erpnext/healthcare/page/patient_history/patient_history.css b/erpnext/healthcare/page/patient_history/patient_history.css index 1bb589164e..74b5e7eb91 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.css +++ b/erpnext/healthcare/page/patient_history/patient_history.css @@ -9,6 +9,26 @@ cursor: pointer; } +.patient-image-container { + margin-top: 17px; + } + +.patient-image { + display: inline-block; + width: 100%; + height: 0; + padding: 50% 0px; + background-size: cover; + background-repeat: no-repeat; + background-position: center center; + border-radius: 4px; +} + +.patient-name { + font-size: 20px; + margin-top: 25px; +} + .medical_record-label { max-width: 100px; margin-bottom: -4px; @@ -19,19 +39,19 @@ } .date-indicator { - background:none; - font-size:12px; - vertical-align:middle; - font-weight:bold; - color:#6c7680; + background:none; + font-size:12px; + vertical-align:middle; + font-weight:bold; + color:#6c7680; } .date-indicator::after { - margin:0 -4px 0 12px; - content:''; - display:inline-block; - height:8px; - width:8px; - border-radius:8px; + margin:0 -4px 0 12px; + content:''; + display:inline-block; + height:8px; + width:8px; + border-radius:8px; background: #d1d8dd; } diff --git a/erpnext/healthcare/page/patient_history/patient_history.html b/erpnext/healthcare/page/patient_history/patient_history.html index f1706557f4..d16b38637c 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.html +++ b/erpnext/healthcare/page/patient_history/patient_history.html @@ -1,26 +1,18 @@ -
    -
    -

    -
    +
    +
    +
    +
    +
    +
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    + +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/erpnext/healthcare/page/patient_history/patient_history.js b/erpnext/healthcare/page/patient_history/patient_history.js index 54343aae44..bf947cac21 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.js +++ b/erpnext/healthcare/page/patient_history/patient_history.js @@ -1,403 +1,455 @@ frappe.provide('frappe.patient_history'); frappe.pages['patient_history'].on_page_load = function(wrapper) { - let me = this; - let page = frappe.ui.make_app_page({ + frappe.ui.make_app_page({ parent: wrapper, - title: 'Patient History', - single_column: true + title: __('Patient History') }); - frappe.breadcrumbs.add('Healthcare'); - let pid = ''; - page.main.html(frappe.render_template('patient_history', {})); - page.main.find('.header-separator').hide(); - - let patient = frappe.ui.form.make_control({ - parent: page.main.find('.patient'), - df: { - fieldtype: 'Link', - options: 'Patient', - fieldname: 'patient', - placeholder: __('Select Patient'), - only_select: true, - change: function() { - let patient_id = patient.get_value(); - if (pid != patient_id && patient_id) { - me.start = 0; - me.page.main.find('.patient_documents_list').html(''); - setup_filters(patient_id, me); - get_documents(patient_id, me); - show_patient_info(patient_id, me); - show_patient_vital_charts(patient_id, me, 'bp', 'mmHg', 'Blood Pressure'); - } - pid = patient_id; - } - }, - }); - patient.refresh(); - - if (frappe.route_options) { - patient.set_value(frappe.route_options.patient); - } - - this.page.main.on('click', '.btn-show-chart', function() { - let btn_show_id = $(this).attr('data-show-chart-id'), pts = $(this).attr('data-pts'); - let title = $(this).attr('data-title'); - show_patient_vital_charts(patient.get_value(), me, btn_show_id, pts, title); - }); - - this.page.main.on('click', '.btn-more', function() { - let doctype = $(this).attr('data-doctype'), docname = $(this).attr('data-docname'); - if (me.page.main.find('.'+docname).parent().find('.document-html').attr('data-fetched') == '1') { - me.page.main.find('.'+docname).hide(); - me.page.main.find('.'+docname).parent().find('.document-html').show(); - } else { - if (doctype && docname) { - let exclude = ['patient', 'patient_name', 'patient_sex', 'encounter_date']; - frappe.call({ - method: 'erpnext.healthcare.utils.render_doc_as_html', - args:{ - doctype: doctype, - docname: docname, - exclude_fields: exclude - }, - freeze: true, - callback: function(r) { - if (r.message) { - me.page.main.find('.' + docname).hide(); - - me.page.main.find('.' + docname).parent().find('.document-html').html( - `${r.message.html} -
    - - -
    - `); - - me.page.main.find('.' + docname).parent().find('.document-html').show(); - me.page.main.find('.' + docname).parent().find('.document-html').attr('data-fetched', '1'); - } - } - }); - } - } - }); - - this.page.main.on('click', '.btn-less', function() { - let docname = $(this).attr('data-docname'); - me.page.main.find('.' + docname).parent().find('.document-id').show(); - me.page.main.find('.' + docname).parent().find('.document-html').hide(); - }); - me.start = 0; - me.page.main.on('click', '.btn-get-records', function() { - get_documents(patient.get_value(), me); + let patient_history = new PatientHistory(wrapper); + $(wrapper).bind('show', ()=> { + patient_history.show(); }); }; -let setup_filters = function(patient, me) { - $('.doctype-filter').empty(); - frappe.xcall( - 'erpnext.healthcare.page.patient_history.patient_history.get_patient_history_doctypes' - ).then(document_types => { - let doctype_filter = frappe.ui.form.make_control({ - parent: $('.doctype-filter'), +class PatientHistory { + constructor(wrapper) { + this.wrapper = $(wrapper); + this.page = wrapper.page; + this.sidebar = this.wrapper.find('.layout-side-section'); + this.main_section = this.wrapper.find('.layout-main-section'); + this.start = 0; + } + + show() { + frappe.breadcrumbs.add('Healthcare'); + this.sidebar.empty(); + + let me = this; + let patient = frappe.ui.form.make_control({ + parent: me.sidebar, df: { - fieldtype: 'MultiSelectList', - fieldname: 'document_type', - placeholder: __('Select Document Type'), - input_class: 'input-xs', + fieldtype: 'Link', + options: 'Patient', + fieldname: 'patient', + placeholder: __('Select Patient'), + only_select: true, change: () => { - me.start = 0; - me.page.main.find('.patient_documents_list').html(''); - get_documents(patient, me, doctype_filter.get_value(), date_range_field.get_value()); - }, - get_data: () => { - return document_types.map(document_type => { - return { - description: document_type, - value: document_type - }; - }); - }, + me.patient_id = ''; + if (me.patient_id != patient.get_value() && patient.get_value()) { + me.start = 0; + me.patient_id = patient.get_value(); + me.make_patient_profile(); + } + } } }); - doctype_filter.refresh(); + patient.refresh(); - $('.date-filter').empty(); - let date_range_field = frappe.ui.form.make_control({ - df: { - fieldtype: 'DateRange', - fieldname: 'date_range', - placeholder: __('Date Range'), - input_class: 'input-xs', - change: () => { - let selected_date_range = date_range_field.get_value(); - if (selected_date_range && selected_date_range.length === 2) { + if (frappe.route_options && !this.patient_id) { + patient.set_value(frappe.route_options.patient); + this.patient_id = frappe.route_options.patient; + } + + this.sidebar.find('[data-fieldname="patient"]').append('
    '); + } + + make_patient_profile() { + this.page.set_title(__('Patient History')); + this.main_section.empty().append(frappe.render_template('patient_history')); + this.setup_filters(); + this.setup_documents(); + this.show_patient_info(); + this.setup_buttons(); + this.show_patient_vital_charts('bp', 'mmHg', 'Blood Pressure'); + } + + setup_filters() { + $('.doctype-filter').empty(); + let me = this; + + frappe.xcall( + 'erpnext.healthcare.page.patient_history.patient_history.get_patient_history_doctypes' + ).then(document_types => { + let doctype_filter = frappe.ui.form.make_control({ + parent: $('.doctype-filter'), + df: { + fieldtype: 'MultiSelectList', + fieldname: 'document_type', + placeholder: __('Select Document Type'), + change: () => { me.start = 0; me.page.main.find('.patient_documents_list').html(''); - get_documents(patient, me, doctype_filter.get_value(), selected_date_range); - } + this.setup_documents(doctype_filter.get_value(), date_range_field.get_value()); + }, + get_data: () => { + return document_types.map(document_type => { + return { + description: document_type, + value: document_type + }; + }); + }, } - }, - parent: $('.date-filter') + }); + doctype_filter.refresh(); + + $('.date-filter').empty(); + let date_range_field = frappe.ui.form.make_control({ + df: { + fieldtype: 'DateRange', + fieldname: 'date_range', + placeholder: __('Date Range'), + input_class: 'input-xs', + change: () => { + let selected_date_range = date_range_field.get_value(); + if (selected_date_range && selected_date_range.length === 2) { + me.start = 0; + me.page.main.find('.patient_documents_list').html(''); + this.setup_documents(doctype_filter.get_value(), date_range_field.get_value()); + } + } + }, + parent: $('.date-filter') + }); + date_range_field.refresh(); }); - date_range_field.refresh(); - }); -}; + } -let get_documents = function(patient, me, document_types="", selected_date_range="") { - let filters = { - name: patient, - start: me.start, - page_length: 20 - }; - if (document_types) - filters['document_types'] = document_types; - if (selected_date_range) - filters['date_range'] = selected_date_range; + setup_documents(document_types="", selected_date_range="") { + let filters = { + name: this.patient_id, + start: this.start, + page_length: 20 + }; + if (document_types) + filters['document_types'] = document_types; + if (selected_date_range) + filters['date_range'] = selected_date_range; - frappe.call({ - 'method': 'erpnext.healthcare.page.patient_history.patient_history.get_feed', - args: filters, - callback: function(r) { - let data = r.message; - if (data.length) { - add_to_records(me, data); - } else { - me.page.main.find('.patient_documents_list').append(` -
    -

    ${__('No more records..')}

    -
    `); - me.page.main.find('.btn-get-records').hide(); + let me = this; + frappe.call({ + 'method': 'erpnext.healthcare.page.patient_history.patient_history.get_feed', + args: filters, + callback: function(r) { + let data = r.message; + if (data.length) { + me.add_to_records(data); + } else { + me.page.main.find('.patient_documents_list').append(` +
    +

    ${__('No more records..')}

    +
    `); + me.page.main.find('.btn-get-records').hide(); + } } - } - }); -}; + }); + } -let add_to_records = function(me, data) { - let details = "
    `; + } + } + + this.page.main.find('.patient_documents_list').append(details); + this.start += data.length; + + if (data.length === 20) { + this.page.main.find(".btn-get-records").show(); + } else { + this.page.main.find(".btn-get-records").hide(); + this.page.main.find(".patient_documents_list").append(` +
    +

    ${__('No more records..')}

    +
    `); } } - details += ''; - me.page.main.find('.patient_documents_list').append(details); - me.start += data.length; + add_date_separator(data) { + let date = frappe.datetime.str_to_obj(data.communication_date); + let pdate = ''; + let diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), + frappe.datetime.obj_to_str(date)); - if (data.length === 20) { - me.page.main.find(".btn-get-records").show(); - } else { - me.page.main.find(".btn-get-records").hide(); - me.page.main.find(".patient_documents_list").append(` -
    -

    ${__('No more records..')}

    -
    `); - } -}; - -let add_date_separator = function(data) { - let date = frappe.datetime.str_to_obj(data.communication_date); - let pdate = ''; - let diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date)); - - if (diff < 1) { - pdate = __('Today'); - } else if (diff < 2) { - pdate = __('Yesterday'); - } else { - pdate = __('on ') + frappe.datetime.global_date_format(date); - } - data.date_sep = pdate; - return data; -}; - -let show_patient_info = function(patient, me) { - frappe.call({ - 'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail', - args: { - patient: patient - }, - callback: function(r) { - let data = r.message; - let details = ''; - if (data.image) { - details += `
    `; - } - - details += ` ${data.patient_name}
    ${data.sex}`; - if (data.email) details += `
    ${data.email}`; - if (data.mobile) details += `
    ${data.mobile}`; - if (data.occupation) details += `

    ${__('Occupation')} : ${data.occupation}`; - if (data.blood_group) details += `
    ${__('Blood Group')} : ${data.blood_group}`; - if (data.allergies) details += `

    ${__('Allerigies')} : ${data.allergies.replace("\n", ", ")}`; - if (data.medication) details += `
    ${__('Medication')} : ${data.medication.replace("\n", ", ")}`; - if (data.alcohol_current_use) details += `

    ${__('Alcohol use')} : ${data.alcohol_current_use}`; - if (data.alcohol_past_use) details += `
    ${__('Alcohol past use')} : ${data.alcohol_past_use}`; - if (data.tobacco_current_use) details += `
    ${__('Tobacco use')} : ${data.tobacco_current_use}`; - if (data.tobacco_past_use) details += `
    ${__('Tobacco past use')} : ${data.tobacco_past_use}`; - if (data.medical_history) details += `

    ${__('Medical history')} : ${data.medical_history.replace("\n", ", ")}`; - if (data.surgical_history) details += `
    ${__('Surgical history')} : ${data.surgical_history.replace("\n", ", ")}`; - if (data.surrounding_factors) details += `

    ${__('Occupational hazards')} : ${data.surrounding_factors.replace("\n", ", ")}`; - if (data.other_risk_factors) details += `
    ${__('Other risk factors')} : ${data.other_risk_factors.replace("\n", ", ")}`; - if (data.patient_details) details += `

    ${__('More info')} : ${data.patient_details.replace("\n", ", ")}`; - - if (details) { - details = `
    ` + details + `
    `; - } - me.page.main.find('.patient_details').html(details); + if (diff < 1) { + pdate = __('Today'); + } else if (diff < 2) { + pdate = __('Yesterday'); + } else { + pdate = __('on {0}', [frappe.datetime.global_date_format(date)]); } - }); -}; + data.date_sep = pdate; + return data; + } -let show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) { - frappe.call({ - method: 'erpnext.healthcare.utils.get_patient_vitals', - args:{ - patient: patient - }, - callback: function(r) { - if (r.message) { - let show_chart_btns_html = ` - `; + show_patient_info() { + this.get_patient_info().then(() => { + $('.patient-info').empty().append(frappe.render_template('patient_history_sidebar', { + patient_image: this.patient.image, + patient_name: this.patient.patient_name, + patient_gender: this.patient.sex, + patient_mobile: this.patient.mobile + })); + this.show_patient_details(); + }); + } - me.page.main.find('.show_chart_btns').html(show_chart_btns_html); + show_patient_details() { + let me = this; + frappe.call({ + 'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail', + args: { + patient: me.patient_id + }, + callback: function(r) { let data = r.message; - let labels = [], datasets = []; - let bp_systolic = [], bp_diastolic = [], temperature = []; - let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = []; + let details = ``; - for (let i=0; i
    ${__('Occupation')} : ${data.occupation}`; + if (data.blood_group) details += `
    ${__('Blood Group')} : ${data.blood_group}`; + if (data.allergies) details += `

    ${__('Allerigies')} : ${data.allergies.replace("\n", ", ")}`; + if (data.medication) details += `
    ${__('Medication')} : ${data.medication.replace("\n", ", ")}`; + if (data.alcohol_current_use) details += `

    ${__('Alcohol use')} : ${data.alcohol_current_use}`; + if (data.alcohol_past_use) details += `
    ${__('Alcohol past use')} : ${data.alcohol_past_use}`; + if (data.tobacco_current_use) details += `
    ${__('Tobacco use')} : ${data.tobacco_current_use}`; + if (data.tobacco_past_use) details += `
    ${__('Tobacco past use')} : ${data.tobacco_past_use}`; + if (data.medical_history) details += `

    ${__('Medical history')} : ${data.medical_history.replace("\n", ", ")}`; + if (data.surgical_history) details += `
    ${__('Surgical history')} : ${data.surgical_history.replace("\n", ", ")}`; + if (data.surrounding_factors) details += `

    ${__('Occupational hazards')} : ${data.surrounding_factors.replace("\n", ", ")}`; + if (data.other_risk_factors) details += `
    ${__('Other risk factors')} : ${data.other_risk_factors.replace("\n", ", ")}`; + if (data.patient_details) details += `

    ${__('More info')} : ${data.patient_details.replace("\n", ", ")}`; - if (btn_show_id === 'bp') { - bp_systolic.push(data[i].bp_systolic); - bp_diastolic.push(data[i].bp_diastolic); - } - if (btn_show_id === 'temperature') { - temperature.push(data[i].temperature); - } - if (btn_show_id === 'pulse_rate') { - pulse.push(data[i].pulse); - respiratory_rate.push(data[i].respiratory_rate); - } - if (btn_show_id === 'bmi') { - bmi.push(data[i].bmi); - height.push(data[i].height); - weight.push(data[i].weight); - } + if (details) { + details = `
    ` + details + `
    `; } - if (btn_show_id === 'temperature') { - datasets.push({name: 'Temperature', values: temperature, chartType: 'line'}); - } - if (btn_show_id === 'bmi') { - datasets.push({name: 'BMI', values: bmi, chartType: 'line'}); - datasets.push({name: 'Height', values: height, chartType: 'line'}); - datasets.push({name: 'Weight', values: weight, chartType: 'line'}); - } - if (btn_show_id === 'bp') { - datasets.push({name: 'BP Systolic', values: bp_systolic, chartType: 'line'}); - datasets.push({name: 'BP Diastolic', values: bp_diastolic, chartType: 'line'}); - } - if (btn_show_id === 'pulse_rate') { - datasets.push({name: 'Heart Rate / Pulse', values: pulse, chartType: 'line'}); - datasets.push({name: 'Respiratory Rate', values: respiratory_rate, chartType: 'line'}); - } - new frappe.Chart('.patient_vital_charts', { - data: { - labels: labels, - datasets: datasets - }, - title: title, - type: 'axis-mixed', - height: 200, - colors: ['purple', '#ffa3ef', 'light-blue'], - - tooltipOptions: { - formatTooltipX: d => (d + '').toUpperCase(), - formatTooltipY: d => d + ' ' + pts, - } - }); - me.page.main.find('.header-separator').show(); - } else { - me.page.main.find('.patient_vital_charts').html(''); - me.page.main.find('.show_chart_btns').html(''); - me.page.main.find('.header-separator').hide(); + me.sidebar.find('.patient-details').html(details); } - } - }); -}; + }); + } + + get_patient_info() { + return frappe.xcall('frappe.client.get', { + doctype: 'Patient', + name: this.patient_id, + }).then((patient) => { + if (patient) { + this.patient = patient; + } + }); + } + + setup_buttons() { + let me = this; + this.page.main.on("click", ".btn-show-chart", function() { + let btn_id = $(this).attr("data-show-chart-id"), scale_unit = $(this).attr("data-pts"); + let title = $(this).attr("data-title"); + me.show_patient_vital_charts(btn_id, scale_unit, title); + }); + + this.page.main.on('click', '.btn-more', function() { + let doctype = $(this).attr('data-doctype'), docname = $(this).attr('data-docname'); + if (me.page.main.find('.'+docname).parent().find('.document-html').attr('data-fetched') == '1') { + me.page.main.find('.'+docname).hide(); + me.page.main.find('.'+docname).parent().find('.document-html').show(); + } else { + if (doctype && docname) { + let exclude = ['patient', 'patient_name', 'patient_sex', 'encounter_date', 'naming_series']; + frappe.call({ + method: 'erpnext.healthcare.utils.render_doc_as_html', + args: { + doctype: doctype, + docname: docname, + exclude_fields: exclude + }, + freeze: true, + callback: function(r) { + if (r.message) { + me.page.main.find('.' + docname).hide(); + + me.page.main.find('.' + docname).parent().find('.document-html').html( + `${r.message.html} +
    +
    + + +
    + `); + + me.page.main.find('.' + docname).parent().find('.document-html').attr('hidden', false); + me.page.main.find('.' + docname).parent().find('.document-html').attr('data-fetched', '1'); + } + } + }); + } + } + }); + + this.page.main.on('click', '.btn-less', function() { + let docname = $(this).attr('data-docname'); + me.page.main.find('.' + docname).parent().find('.document-id').show(); + me.page.main.find('.' + docname).parent().find('.document-html').hide(); + }); + + me.page.main.on('click', '.btn-get-records', function() { + this.setup_documents(); + }); + } + + show_patient_vital_charts(btn_id, scale_unit, title) { + let me = this; + + frappe.call({ + method: 'erpnext.healthcare.utils.get_patient_vitals', + args: { + patient: me.patient_id + }, + callback: function(r) { + if (r.message) { + let show_chart_btns_html = ` + `; + + me.page.main.find('.show_chart_btns').html(show_chart_btns_html); + let data = r.message; + let labels = [], datasets = []; + let bp_systolic = [], bp_diastolic = [], temperature = []; + let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = []; + + for (let i=0; i (d + '').toUpperCase(), + formatTooltipY: d => d + ' ' + scale_unit, + } + }); + me.page.main.find('.header-separator').show(); + } else { + me.page.main.find('.patient_vital_charts').html(''); + me.page.main.find('.show_chart_btns').html(''); + me.page.main.find('.header-separator').hide(); + } + } + }); + } +} \ No newline at end of file diff --git a/erpnext/healthcare/page/patient_history/patient_history_sidebar.html b/erpnext/healthcare/page/patient_history/patient_history_sidebar.html new file mode 100644 index 0000000000..4560e7e125 --- /dev/null +++ b/erpnext/healthcare/page/patient_history/patient_history_sidebar.html @@ -0,0 +1,21 @@ +
    +
    + {% if patient_image %} +
    + {% endif %} +
    +
    + {% if patient_name %} +

    {{patient_name}}

    + {% endif %} + {% if patient_gender %} +

    {%=__("Gender: ") %} {{patient_gender}}

    + {% endif %} + {% if patient_mobile %} +

    {%=__("Contact: ") %} {{patient_mobile}}

    + {% endif %} +
    +
    +
    +
    + diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.css b/erpnext/healthcare/page/patient_progress/patient_progress.css index 5d85a7487f..737b2e0ea2 100644 --- a/erpnext/healthcare/page/patient_progress/patient_progress.css +++ b/erpnext/healthcare/page/patient_progress/patient_progress.css @@ -29,6 +29,7 @@ .patient-name { font-size: 20px; + margin-top: 25px; } /* heatmap */ @@ -55,6 +56,7 @@ } .heatmap-container .chart-filter { + z-index: 1; position: relative; top: 5px; margin-right: 10px; @@ -111,10 +113,13 @@ text.title { } .chart-column-container { - border-bottom: 1px solid #d1d8dd; margin: 5px 0; } +.progress-graphs .progress-container { + margin-bottom: var(--margin-xl); +} + .line-chart-container .frappe-chart { margin-top: -20px; } @@ -146,6 +151,7 @@ text.title { } .percentage-chart-container .chart-filter { + z-index: 1; position: relative; top: 12px; margin-right: 10px; diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.html b/erpnext/healthcare/page/patient_progress/patient_progress.html index 30064bd165..ee60065618 100644 --- a/erpnext/healthcare/page/patient_progress/patient_progress.html +++ b/erpnext/healthcare/page/patient_progress/patient_progress.html @@ -1,14 +1,15 @@
    -