From 66069df02064ee19af9b8dc429b56f1a86d01956 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 31 Jul 2020 15:54:05 +0530 Subject: [PATCH 01/99] feat: Return tracking in PR/DN --- .../purchase_order/purchase_order.json | 4 +- .../controllers/sales_and_purchase_return.py | 1 + erpnext/controllers/status_updater.py | 21 ++++++--- .../purchase_receipt/purchase_receipt.json | 14 +++++- .../purchase_receipt/purchase_receipt.py | 43 ++++++++++++------- .../purchase_receipt/purchase_receipt_list.js | 2 + .../purchase_receipt_item.json | 25 +++++++++-- 7 files changed, 81 insertions(+), 29 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 502dbba571..858dec0b1e 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1084,7 +1084,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2020-07-18 05:09:33.800633", + "modified": "2020-07-31 14:39:44.599294", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", @@ -1135,5 +1135,5 @@ "sort_field": "modified", "sort_order": "DESC", "timeline_field": "supplier", - "title_field": "title" + "title_field": "supplier" } \ No newline at end of file diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 3f127a201e..1085486f94 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -207,6 +207,7 @@ def make_return_doc(doctype, source_name, target_doc=None): from frappe.model.mapper import get_mapped_doc company = frappe.db.get_value("Delivery Note", source_name, "company") default_warehouse_for_sales_return = frappe.db.get_value("Company", company, "default_warehouse_for_sales_return") + def set_missing_values(source, target): doc = frappe.get_doc(target) doc.is_return = 1 diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 0dc9878afd..bf69130e4a 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -65,6 +65,7 @@ status_map = { "Purchase Receipt": [ ["Draft", None], ["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"], + ["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"], ["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"], ["Cancelled", "eval:self.docstatus==2"], ["Closed", "eval:self.status=='Closed'"], @@ -232,7 +233,7 @@ class StatusUpdater(Document): self._update_children(args, update_modified) - if "percent_join_field" in args: + if "percent_join_field" in args or "percent_join_field_parent" in args: self._update_percent_field_in_targets(args, update_modified) def _update_children(self, args, update_modified): @@ -272,13 +273,19 @@ class StatusUpdater(Document): def _update_percent_field_in_targets(self, args, update_modified=True): """Update percent field in parent transaction""" - distinct_transactions = set([d.get(args['percent_join_field']) - for d in self.get_all_children(args['source_dt'])]) + if args.get('percent_join_field_parent'): + # if reference to target doc where % is to be updated, is + # in source doc's parent form, consider percent_join_field_parent + args['name'] = self.get(args['percent_join_field_parent']) + self._update_percent_field(args, update_modified) + else: + distinct_transactions = set([d.get(args['percent_join_field']) + for d in self.get_all_children(args['source_dt'])]) - for name in distinct_transactions: - if name: - args['name'] = name - self._update_percent_field(args, update_modified) + for name in distinct_transactions: + if name: + args['name'] = name + self._update_percent_field(args, update_modified) def _update_percent_field(self, args, update_modified=True): """Update percent field in parent transaction""" diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 92e33ca64e..fac8909e53 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -1,7 +1,6 @@ { "actions": [], "allow_import": 1, - "allow_workflow": 1, "autoname": "naming_series:", "creation": "2013-05-21 16:16:39", "doctype": "DocType", @@ -111,6 +110,7 @@ "range", "column_break4", "per_billed", + "per_returned", "is_internal_supplier", "inter_company_reference", "subscription_detail", @@ -1104,13 +1104,23 @@ "fieldtype": "Small Text", "label": "Billing Address", "read_only": 1 + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "per_returned", + "fieldtype": "Percent", + "in_list_view": 1, + "label": "% Returned", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "icon": "fa fa-truck", "idx": 261, "is_submittable": 1, "links": [], - "modified": "2020-07-18 05:19:12.148115", + "modified": "2020-07-31 15:16:26.811384", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d0ba001d7e..dafaae28c5 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -55,20 +55,33 @@ class PurchaseReceipt(BuyingController): 'percent_join_field': 'material_request' }] if cint(self.is_return): - self.status_updater.append({ - 'source_dt': 'Purchase Receipt Item', - 'target_dt': 'Purchase Order Item', - 'join_field': 'purchase_order_item', - 'target_field': 'returned_qty', - 'source_field': '-1 * qty', - 'second_source_dt': 'Purchase Invoice Item', - 'second_source_field': '-1 * qty', - 'second_join_field': 'po_detail', - 'extra_cond': """ and exists (select name from `tabPurchase Receipt` - where name=`tabPurchase Receipt Item`.parent and is_return=1)""", - 'second_source_extra_cond': """ and exists (select name from `tabPurchase Invoice` - where name=`tabPurchase Invoice Item`.parent and is_return=1 and update_stock=1)""" - }) + self.status_updater.extend([ + { + 'source_dt': 'Purchase Receipt Item', + 'target_dt': 'Purchase Order Item', + 'join_field': 'purchase_order_item', + 'target_field': 'returned_qty', + 'source_field': '-1 * qty', + 'second_source_dt': 'Purchase Invoice Item', + 'second_source_field': '-1 * qty', + 'second_join_field': 'po_detail', + 'extra_cond': """ and exists (select name from `tabPurchase Receipt` + where name=`tabPurchase Receipt Item`.parent and is_return=1)""", + 'second_source_extra_cond': """ and exists (select name from `tabPurchase Invoice` + where name=`tabPurchase Invoice Item`.parent and is_return=1 and update_stock=1)""" + }, + { + 'source_dt': 'Purchase Receipt Item', + 'target_dt': 'Purchase Receipt Item', + 'join_field': 'purchase_receipt_item', + 'target_field': 'returned_qty', + 'target_parent_dt': 'Purchase Receipt', + 'target_parent_field': 'per_returned', + 'target_ref_field': 'stock_qty', + 'source_field': '-1 * stock_qty', + 'percent_join_field_parent': 'return_against' + } + ]) def validate(self): self.validate_posting_time() @@ -470,7 +483,7 @@ class PurchaseReceipt(BuyingController): frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate)) def update_status(self, status): - self.set_status(update=True, status = status) + self.set_status(update=True, status=status) self.notify_update() clear_doctype_notifications(self) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js index e81f323a46..7bf5a1cb94 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js @@ -6,6 +6,8 @@ frappe.listview_settings['Purchase Receipt'] = { return [__("Return"), "darkgrey", "is_return,=,Yes"]; } else if (doc.status === "Closed") { return [__("Closed"), "green", "status,=,Closed"]; + } else if (flt(doc.per_returned, 2) == 100) { + return [__("Return Issued"), "grey", "per_returned,=,100"]; } else if (flt(doc.grand_total) !== 0 && flt(doc.per_billed, 2) < 100) { return [__("To Bill"), "orange", "per_billed,<,100"]; } else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) == 100) { diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index c1e1f901ba..a9f31aa74b 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -28,9 +28,12 @@ "uom", "stock_uom", "conversion_factor", - "stock_qty", "retain_sample", "sample_quantity", + "tracking_section", + "stock_qty", + "col_break_tracking_section", + "returned_qty", "rate_and_amount", "price_list_rate", "discount_percentage", @@ -526,7 +529,7 @@ { "fieldname": "stock_qty", "fieldtype": "Float", - "label": "Accepted Qty as per Stock UOM", + "label": "Accepted Qty in Stock UOM", "oldfieldname": "stock_qty", "oldfieldtype": "Currency", "print_hide": 1, @@ -834,12 +837,28 @@ "collapsible": 1, "fieldname": "image_column", "fieldtype": "Column Break" + }, + { + "fieldname": "tracking_section", + "fieldtype": "Section Break" + }, + { + "fieldname": "col_break_tracking_section", + "fieldtype": "Column Break" + }, + { + "depends_on": "returned_qty", + "fieldname": "returned_qty", + "fieldtype": "Float", + "label": "Returned Qty in Stock UOM", + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-28 19:01:21.154963", + "modified": "2020-07-30 21:02:17.912628", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From ba37fe796cbcd825ff44dba42b1acfb883e546c6 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 31 Jul 2020 20:01:06 +0530 Subject: [PATCH 02/99] chore: Delivery Note Return status --- erpnext/controllers/status_updater.py | 1 + .../doctype/delivery_note/delivery_note.json | 13 +++++++++- .../doctype/delivery_note/delivery_note.py | 16 ++++++++++-- .../delivery_note/delivery_note_list.js | 2 ++ .../delivery_note_item.json | 25 +++++++++++++++++-- 5 files changed, 52 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index bf69130e4a..e776dc96ac 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -58,6 +58,7 @@ status_map = { "Delivery Note": [ ["Draft", None], ["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"], + ["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"], ["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"], ["Cancelled", "eval:self.docstatus==2"], ["Closed", "eval:self.status=='Closed'"], diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 66efcf8cd8..2b47d8c248 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -132,6 +132,7 @@ "per_installed", "installation_status", "column_break_89", + "per_returned", "excise_page", "instructions", "subscription_section", @@ -1249,13 +1250,23 @@ "fieldtype": "Link", "label": "Inter Company Reference", "options": "Purchase Receipt" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "per_returned", + "fieldtype": "Percent", + "in_list_view": 1, + "label": "% Returned", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "icon": "fa fa-truck", "idx": 146, "is_submittable": 1, "links": [], - "modified": "2020-07-18 05:13:55.580420", + "modified": "2020-07-31 19:56:19.800171", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index d04cf785ab..895295bbfe 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -55,7 +55,7 @@ class DeliveryNote(SellingController): 'no_allowance': 1 }] if cint(self.is_return): - self.status_updater.append({ + self.status_updater.extend([{ 'source_dt': 'Delivery Note Item', 'target_dt': 'Sales Order Item', 'join_field': 'so_detail', @@ -69,7 +69,19 @@ class DeliveryNote(SellingController): where name=`tabDelivery Note Item`.parent and is_return=1)""", 'second_source_extra_cond': """ and exists (select name from `tabSales Invoice` where name=`tabSales Invoice Item`.parent and is_return=1 and update_stock=1)""" - }) + }, + { + 'source_dt': 'Delivery Note Item', + 'target_dt': 'Delivery Note Item', + 'join_field': 'dn_detail', + 'target_field': 'returned_qty', + 'target_parent_dt': 'Delivery Note', + 'target_parent_field': 'per_returned', + 'target_ref_field': 'stock_qty', + 'source_field': '-1 * stock_qty', + 'percent_join_field_parent': 'return_against' + } + ]) def before_print(self): def toggle_print_hide(meta, fieldname): diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js index 0ae7c37b3f..a0579446d0 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js @@ -6,6 +6,8 @@ frappe.listview_settings['Delivery Note'] = { return [__("Return"), "darkgrey", "is_return,=,Yes"]; } else if (doc.status === "Closed") { return [__("Closed"), "green", "status,=,Closed"]; + } else if (flt(doc.per_returned, 2) == 100) { + return [__("Return Issued"), "grey", "per_returned,=,100"]; } else if (flt(doc.per_billed, 2) < 100) { return [__("To Bill"), "orange", "per_billed,<,100"]; } else if (flt(doc.per_billed, 2) == 100) { diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index 3d57f47601..eb99fed986 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "hash", "creation": "2013-04-22 13:15:44", "doctype": "DocType", @@ -24,8 +25,11 @@ "col_break2", "uom", "conversion_factor", + "stock_qty_sec_break", "stock_qty", + "stock_qty_col_break", "section_break_17", + "returned_qty", "price_list_rate", "base_price_list_rate", "discount_and_margin", @@ -211,7 +215,7 @@ { "fieldname": "stock_qty", "fieldtype": "Float", - "label": "Qty as per Stock UOM", + "label": "Qty in Stock UOM", "no_copy": 1, "print_hide": 1, "read_only": 1 @@ -715,12 +719,29 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "stock_qty_sec_break", + "fieldtype": "Section Break" + }, + { + "fieldname": "stock_qty_col_break", + "fieldtype": "Column Break" + }, + { + "depends_on": "returned_qty", + "fieldname": "returned_qty", + "fieldtype": "Float", + "label": "Returned Qty in Stock UOM", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-07-20 12:25:06.177894", + "modified": "2020-07-31 19:43:46.152260", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", From d36df9275040a5b891efbdfe44a554b4ca1e15ab Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 31 Jul 2020 20:22:55 +0530 Subject: [PATCH 03/99] fix: Codacy and repositioned Returned Qty in DN Item --- erpnext/stock/doctype/delivery_note/delivery_note_list.js | 4 ++-- .../stock/doctype/delivery_note_item/delivery_note_item.json | 4 ++-- .../stock/doctype/purchase_receipt/purchase_receipt_list.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js index a0579446d0..4a6500cfd8 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js @@ -6,11 +6,11 @@ frappe.listview_settings['Delivery Note'] = { return [__("Return"), "darkgrey", "is_return,=,Yes"]; } else if (doc.status === "Closed") { return [__("Closed"), "green", "status,=,Closed"]; - } else if (flt(doc.per_returned, 2) == 100) { + } else if (flt(doc.per_returned, 2) === 100) { return [__("Return Issued"), "grey", "per_returned,=,100"]; } else if (flt(doc.per_billed, 2) < 100) { return [__("To Bill"), "orange", "per_billed,<,100"]; - } else if (flt(doc.per_billed, 2) == 100) { + } else if (flt(doc.per_billed, 2) === 100) { return [__("Completed"), "green", "per_billed,=,100"]; } }, diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index eb99fed986..7b471874af 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -28,8 +28,8 @@ "stock_qty_sec_break", "stock_qty", "stock_qty_col_break", - "section_break_17", "returned_qty", + "section_break_17", "price_list_rate", "base_price_list_rate", "discount_and_margin", @@ -741,7 +741,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-07-31 19:43:46.152260", + "modified": "2020-07-31 20:12:43.054342", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js index 7bf5a1cb94..c9501a409a 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js @@ -6,11 +6,11 @@ frappe.listview_settings['Purchase Receipt'] = { return [__("Return"), "darkgrey", "is_return,=,Yes"]; } else if (doc.status === "Closed") { return [__("Closed"), "green", "status,=,Closed"]; - } else if (flt(doc.per_returned, 2) == 100) { + } else if (flt(doc.per_returned, 2) === 100) { return [__("Return Issued"), "grey", "per_returned,=,100"]; } else if (flt(doc.grand_total) !== 0 && flt(doc.per_billed, 2) < 100) { return [__("To Bill"), "orange", "per_billed,<,100"]; - } else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) == 100) { + } else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) === 100) { return [__("Completed"), "green", "per_billed,=,100"]; } } From e35fd5e3051b1ce84417c068d8e5bedf738c8620 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 8 Sep 2020 16:24:10 +0530 Subject: [PATCH 04/99] chore: Tests for Purchase Receipt --- .../doctype/delivery_note/delivery_note.json | 4 +-- .../purchase_receipt/purchase_receipt.json | 4 +-- .../purchase_receipt/test_purchase_receipt.py | 31 +++++++++++++++++-- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index b7f080f983..32fe760e05 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -1098,7 +1098,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "\nDraft\nTo Bill\nCompleted\nCancelled\nClosed", + "options": "\nDraft\nTo Bill\nCompleted\nReturn Issued\nCancelled\nClosed", "print_hide": 1, "print_width": "150px", "read_only": 1, @@ -1266,7 +1266,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2020-08-03 23:18:47.739997", + "modified": "2020-09-08 11:22:09.056684", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 723908854d..bbfaeabaf7 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -895,7 +895,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "\nDraft\nTo Bill\nCompleted\nCancelled\nClosed", + "options": "\nDraft\nTo Bill\nCompleted\nReturn Issued\nCancelled\nClosed", "print_hide": 1, "print_width": "150px", "read_only": 1, @@ -1120,7 +1120,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2020-08-03 23:20:26.381024", + "modified": "2020-09-08 11:21:25.465966", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 67161aa6dd..bdc6c3a82a 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -255,11 +255,13 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(frappe.db.get_value("Serial No", serial_no, "warehouse"), pr.get("items")[0].rejected_warehouse) - def test_purchase_return(self): + def test_purchase_return_partial(self): pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") - return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-2) + return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-2, do_not_submit=1) + return_pr.items[0].purchase_receipt_item = pr.items[0].name + return_pr.submit() # check sle outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", @@ -283,6 +285,31 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(expected_values[gle.account][0], gle.debit) self.assertEqual(expected_values[gle.account][1], gle.credit) + # hack because new_doc isn't considering is_return portion of status_updater + returned = frappe.get_doc("Purchase Receipt", return_pr.name) + returned.update_prevdoc_status() + pr.load_from_db() + + # Check if Original PR updated + self.assertEqual(pr.items[0].returned_qty, 2) + self.assertEqual(pr.per_returned, 40) + + def test_purchase_return_full(self): + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") + + return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-5, do_not_submit=1) + return_pr.items[0].purchase_receipt_item = pr.items[0].name + return_pr.submit() + + # hack because new_doc isn't considering is_return portion of status_updater + returned = frappe.get_doc("Purchase Receipt", return_pr.name) + returned.update_prevdoc_status() + pr.load_from_db() + + # Check if Original PR updated + self.assertEqual(pr.items[0].returned_qty, 5) + self.assertEqual(pr.per_returned, 100) + self.assertEqual(pr.status, 'Return Issued') def test_purchase_return_for_rejected_qty(self): from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse From 315bf3051c5f4e1221b89129d264e0d5af522a05 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 8 Sep 2020 19:27:36 +0530 Subject: [PATCH 05/99] chore: Tests for Delivery Note --- .../delivery_note/test_delivery_note.py | 43 ++++++++++++++++++- 1 file changed, 41 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 4b04a0a8c3..ecd2d693a0 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -206,7 +206,7 @@ class TestDeliveryNote(unittest.TestCase): for field, value in field_values.items(): self.assertEqual(cstr(serial_no.get(field)), value) - def test_sales_return_for_non_bundled_items(self): + def test_sales_return_for_non_bundled_items_partial(self): company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100) @@ -225,7 +225,10 @@ class TestDeliveryNote(unittest.TestCase): # return entry dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-2, rate=500, - company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1") + company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", + cost_center="Main - TCP1", do_not_submit=1) + dn1.items[0].dn_detail = dn.items[0].name + dn1.submit() actual_qty_2 = get_qty_after_transaction(warehouse="Stores - TCP1") @@ -243,6 +246,42 @@ class TestDeliveryNote(unittest.TestCase): self.assertEqual(gle_warehouse_amount, stock_value_difference) + # hack because new_doc isn't considering is_return portion of status_updater + returned = frappe.get_doc("Delivery Note", dn1.name) + returned.update_prevdoc_status() + dn.load_from_db() + + # Check if Original DN updated + self.assertEqual(dn.items[0].returned_qty, 2) + self.assertEqual(dn.per_returned, 40) + + def test_sales_return_for_non_bundled_items_full(self): + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') + + make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100) + + actual_qty_0 = get_qty_after_transaction(warehouse="Stores - TCP1") + + dn = create_delivery_note(qty=5, rate=500, warehouse="Stores - TCP1", company=company, + expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1") + + #return entry + dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-5, rate=500, + company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", + cost_center="Main - TCP1", do_not_submit=1) + dn1.items[0].dn_detail = dn.items[0].name + dn1.submit() + + # hack because new_doc isn't considering is_return portion of status_updater + returned = frappe.get_doc("Delivery Note", dn1.name) + returned.update_prevdoc_status() + dn.load_from_db() + + # Check if Original DN updated + self.assertEqual(dn.items[0].returned_qty, 5) + self.assertEqual(dn.per_returned, 100) + self.assertEqual(dn.status, 'Return Issued') + def test_return_single_item_from_bundled_items(self): company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') From 8484d1cd957421c9b038e521484bbc0f3df8a69c Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 8 Sep 2020 20:32:25 +0530 Subject: [PATCH 06/99] fix: Patch and test codacy --- .../patches/v7_0/po_status_issue_for_pr_return.py | 12 ++++++++---- .../doctype/delivery_note/test_delivery_note.py | 2 -- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/patches/v7_0/po_status_issue_for_pr_return.py b/erpnext/patches/v7_0/po_status_issue_for_pr_return.py index 6e92ffb8a0..910814fd22 100644 --- a/erpnext/patches/v7_0/po_status_issue_for_pr_return.py +++ b/erpnext/patches/v7_0/po_status_issue_for_pr_return.py @@ -7,19 +7,23 @@ import frappe def execute(): parent_list = [] count = 0 - for data in frappe.db.sql(""" - select + + frappe.reload_doc('stock', 'doctype', 'purchase_receipt') + frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item') + + for data in frappe.db.sql(""" + select `tabPurchase Receipt Item`.purchase_order, `tabPurchase Receipt Item`.name, `tabPurchase Receipt Item`.item_code, `tabPurchase Receipt Item`.idx, `tabPurchase Receipt Item`.parent - from + from `tabPurchase Receipt Item`, `tabPurchase Receipt` where `tabPurchase Receipt Item`.parent = `tabPurchase Receipt`.name and `tabPurchase Receipt Item`.purchase_order_item is null and `tabPurchase Receipt Item`.purchase_order is not null and `tabPurchase Receipt`.is_return = 1""", as_dict=1): - name = frappe.db.get_value('Purchase Order Item', + name = frappe.db.get_value('Purchase Order Item', {'item_code': data.item_code, 'parent': data.purchase_order, 'idx': data.idx}, 'name') if name: diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index ecd2d693a0..339ea57dd9 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -260,8 +260,6 @@ class TestDeliveryNote(unittest.TestCase): make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100) - actual_qty_0 = get_qty_after_transaction(warehouse="Stores - TCP1") - dn = create_delivery_note(qty=5, rate=500, warehouse="Stores - TCP1", company=company, expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1") From efb211af6dc1b47e44118086503209c8097c10bf Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 9 Sep 2020 16:24:11 +0530 Subject: [PATCH 07/99] chore: Fix Received/Delivered Items to Billed Logic --- .../delivered_items_to_be_billed.py | 18 ++++++++++++----- erpnext/accounts/report/non_billed_report.py | 20 +++++++++++++------ .../received_items_to_be_billed.py | 18 ++++++++++++----- .../purchase_receipt_item.json | 3 ++- 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py index 3ffb3ac1df..2aea3f6423 100644 --- a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py +++ b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py @@ -14,11 +14,19 @@ def execute(filters=None): def get_column(): return [ - _("Delivery Note") + ":Link/Delivery Note:120", _("Status") + "::120", _("Date") + ":Date:100", - _("Suplier") + ":Link/Customer:120", _("Customer Name") + "::120", - _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120", - _("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Pending Amount") + ":Currency:100", - _("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120", + _("Delivery Note") + ":Link/Delivery Note:160", + _("Date") + ":Date:100", + _("Customer") + ":Link/Customer:120", + _("Customer Name") + "::120", + _("Item Code") + ":Link/Item:120", + _("Amount") + ":Currency:100", + _("Billed Amount") + ":Currency:100", + _("Returned Amount") + ":Currency:120", + _("Pending Amount") + ":Currency:100", + _("Item Name") + "::120", + _("Description") + "::120", + _("Project") + ":Link/Project:120", + _("Company") + ":Link/Company:120", ] def get_args(): diff --git a/erpnext/accounts/report/non_billed_report.py b/erpnext/accounts/report/non_billed_report.py index a9e25bc25b..2e18ce11dd 100644 --- a/erpnext/accounts/report/non_billed_report.py +++ b/erpnext/accounts/report/non_billed_report.py @@ -17,18 +17,26 @@ def get_ordered_to_be_billed_data(args): return frappe.db.sql(""" Select - `{parent_tab}`.name, `{parent_tab}`.status, `{parent_tab}`.{date_field}, `{parent_tab}`.{party}, `{parent_tab}`.{party}_name, - {project_field}, `{child_tab}`.item_code, `{child_tab}`.base_amount, + `{parent_tab}`.name, `{parent_tab}`.{date_field}, + `{parent_tab}`.{party}, `{parent_tab}`.{party}_name, + `{child_tab}`.item_code, + `{child_tab}`.base_amount, (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)), - (`{child_tab}`.base_amount - (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1))), - `{child_tab}`.item_name, `{child_tab}`.description, `{parent_tab}`.company + (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0)), + (`{child_tab}`.base_amount - + (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)) - + (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))), + `{child_tab}`.item_name, `{child_tab}`.description, + {project_field}, `{parent_tab}`.company from `{parent_tab}`, `{child_tab}` where `{parent_tab}`.name = `{child_tab}`.parent and `{parent_tab}`.docstatus = 1 and `{parent_tab}`.status not in ('Closed', 'Completed') - and `{child_tab}`.amount > 0 and round(`{child_tab}`.billed_amt * - ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) < `{child_tab}`.base_amount + and `{child_tab}`.amount > 0 + and (`{child_tab}`.base_amount - + round(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) - + (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))) > 0 order by `{parent_tab}`.{order} {order_by} """.format(parent_tab = 'tab' + doctype, child_tab = 'tab' + child_tab, precision= precision, party = party, diff --git a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py index 5e8d7730b7..c7d4384a73 100644 --- a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py +++ b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py @@ -14,11 +14,19 @@ def execute(filters=None): def get_column(): return [ - _("Purchase Receipt") + ":Link/Purchase Receipt:120", _("Status") + "::120", _("Date") + ":Date:100", - _("Supplier") + ":Link/Supplier:120", _("Supplier Name") + "::120", - _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120", - _("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100", - _("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120", + _("Purchase Receipt") + ":Link/Purchase Receipt:160", + _("Date") + ":Date:100", + _("Supplier") + ":Link/Supplier:120", + _("Supplier Name") + "::120", + _("Item Code") + ":Link/Item:120", + _("Amount") + ":Currency:100", + _("Billed Amount") + ":Currency:100", + _("Returned Amount") + ":Currency:120", + _("Pending Amount") + ":Currency:120", + _("Item Name") + "::120", + _("Description") + "::120", + _("Project") + ":Link/Project:120", + _("Company") + ":Link/Company:120", ] def get_args(): diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index a9f31aa74b..20ae56feeb 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -851,6 +851,7 @@ "fieldname": "returned_qty", "fieldtype": "Float", "label": "Returned Qty in Stock UOM", + "no_copy": 1, "print_hide": 1, "read_only": 1 } @@ -858,7 +859,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-07-30 21:02:17.912628", + "modified": "2020-09-09 13:39:46.452817", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From dc5f2aa8b8dae91a59b43d8403023f0085ca1aa6 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 9 Sep 2020 17:56:20 +0530 Subject: [PATCH 08/99] chore: Patch to set returned qty in PR and DN --- erpnext/patches.txt | 1 + .../v13_0/update_returned_qty_in_pr_dn.py | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 6c58f2f452..a0a49b4e2a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -725,3 +725,4 @@ erpnext.patches.v12_0.rename_lost_reason_detail erpnext.patches.v13_0.drop_razorpay_payload_column erpnext.patches.v13_0.update_start_end_date_for_old_shift_assignment erpnext.patches.v13_0.setting_custom_roles_for_some_regional_reports +erpnext.patches.v13_0.update_returned_qty_in_pr_dn \ No newline at end of file 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 new file mode 100644 index 0000000000..a13640e1b0 --- /dev/null +++ b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py @@ -0,0 +1,20 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('stock', 'doctype', 'purchase_receipt') + frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item') + frappe.reload_doc('stock', 'doctype', 'delivery_note') + frappe.reload_doc('stock', 'doctype', 'delivery_note_item') + + def update_from_return_docs(doctype): + for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1}): + # Update original receipt/delivery document from return + return_doc = frappe.get_cached_doc(doctype, return_doc.name) + return_doc.update_prevdoc_status() + + for doctype in ('Purchase Receipt', 'Delivery Note'): + update_from_return_docs(doctype) \ No newline at end of file From 83a03689a1145505b107d89919360e1ca2ec19f0 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 9 Sep 2020 20:51:01 +0530 Subject: [PATCH 09/99] fix: Use independent item for DN Test --- .../stock/doctype/delivery_note/test_delivery_note.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 339ea57dd9..5d180eaab0 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -256,15 +256,19 @@ class TestDeliveryNote(unittest.TestCase): self.assertEqual(dn.per_returned, 40) def test_sales_return_for_non_bundled_items_full(self): + from erpnext.stock.doctype.item.test_item import make_item + company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company') - make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100) + make_item("Box", {'is_stock_item': 1}) - dn = create_delivery_note(qty=5, rate=500, warehouse="Stores - TCP1", company=company, + make_stock_entry(item_code="Box", target="Stores - TCP1", qty=10, basic_rate=100) + + dn = create_delivery_note(item_code="Box", qty=5, rate=500, warehouse="Stores - TCP1", company=company, expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1") #return entry - dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-5, rate=500, + dn1 = create_delivery_note(item_code="Box", is_return=1, return_against=dn.name, qty=-5, rate=500, company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1", do_not_submit=1) dn1.items[0].dn_detail = dn.items[0].name From a119688e32f0a56ae546d75cdd047b2190308fa7 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 24 Sep 2020 15:22:46 +0530 Subject: [PATCH 10/99] fix: (RFQ/SQ) Link to Material Requests in Tools section --- .../request_for_quotation.js | 5 + .../request_for_quotation.json | 9 +- .../supplier_quotation/supplier_quotation.js | 6 + .../supplier_quotation.json | 10 +- erpnext/public/js/controllers/buying.js | 125 +++++++++--------- 5 files changed, 76 insertions(+), 79 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 4a937f7f0d..660af96505 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -326,6 +326,11 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e d.show(); }, __("Get items from")); + //Link Material Requests + this.frm.add_custom_button(__('Link to Material Requests'), + function() { + erpnext.buying.link_to_mrs(me.frm); + }, __("Tools")); } }, diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json index 5cd8e6f4fa..6994ef72f0 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json @@ -17,7 +17,6 @@ "get_suppliers_button", "items_section", "items", - "link_to_mrs", "supplier_response_section", "email_template", "message_for_supplier", @@ -119,12 +118,6 @@ "options": "Request for Quotation Item", "reqd": 1 }, - { - "depends_on": "eval:doc.docstatus===0 && (doc.items && doc.items.length)", - "fieldname": "link_to_mrs", - "fieldtype": "Button", - "label": "Link to Material Requests" - }, { "fieldname": "supplier_response_section", "fieldtype": "Section Break" @@ -235,7 +228,7 @@ "icon": "fa fa-shopping-cart", "is_submittable": 1, "links": [], - "modified": "2020-06-25 14:37:21.140194", + "modified": "2020-09-24 13:53:56.066616", "modified_by": "Administrator", "module": "Buying", "name": "Request for Quotation", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js index 1b8b40459f..c146f13dfe 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js @@ -54,6 +54,12 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext } }) }, __("Get items from")); + + //Link Material Requests + this.frm.add_custom_button(__('Link to Material Requests'), + function() { + erpnext.buying.link_to_mrs(me.frm); + }, __("Tools")); } }, diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 660dcff34b..0ffaa44a0d 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -34,7 +34,6 @@ "ignore_pricing_rule", "items_section", "items", - "link_to_mrs", "pricing_rule_details", "pricing_rules", "section_break_22", @@ -320,12 +319,6 @@ "options": "Supplier Quotation Item", "reqd": 1 }, - { - "depends_on": "eval:doc.docstatus===0 && (doc.items && doc.items.length)", - "fieldname": "link_to_mrs", - "fieldtype": "Button", - "label": "Link to material requests" - }, { "fieldname": "pricing_rule_details", "fieldtype": "Section Break", @@ -803,9 +796,10 @@ ], "icon": "fa fa-shopping-cart", "idx": 29, + "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-07-18 05:10:45.556792", + "modified": "2020-09-24 15:18:29.073368", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index cb76c87b62..fb904e7d66 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -294,69 +294,6 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ this.get_terms(); }, - link_to_mrs: function() { - var my_items = []; - for (var i in cur_frm.doc.items) { - if(!cur_frm.doc.items[i].material_request){ - my_items.push(cur_frm.doc.items[i].item_code); - } - } - frappe.call({ - method: "erpnext.buying.utils.get_linked_material_requests", - args:{ - items: my_items - }, - callback: function(r) { - if(!r.message || r.message.length == 0) { - frappe.throw(__("No pending Material Requests found to link for the given items.")) - } - else { - var i = 0; - var item_length = cur_frm.doc.items.length; - while (i < item_length) { - var qty = cur_frm.doc.items[i].qty; - (r.message[0] || []).forEach(function(d) { - if (d.qty > 0 && qty > 0 && cur_frm.doc.items[i].item_code == d.item_code && !cur_frm.doc.items[i].material_request_item) - { - cur_frm.doc.items[i].material_request = d.mr_name; - cur_frm.doc.items[i].material_request_item = d.mr_item; - var my_qty = Math.min(qty, d.qty); - qty = qty - my_qty; - d.qty = d.qty - my_qty; - cur_frm.doc.items[i].stock_qty = my_qty*cur_frm.doc.items[i].conversion_factor; - cur_frm.doc.items[i].qty = my_qty; - - frappe.msgprint("Assigning " + d.mr_name + " to " + d.item_code + " (row " + cur_frm.doc.items[i].idx + ")"); - if (qty > 0) - { - frappe.msgprint("Splitting " + qty + " units of " + d.item_code); - var newrow = frappe.model.add_child(cur_frm.doc, cur_frm.doc.items[i].doctype, "items"); - item_length++; - - for (var key in cur_frm.doc.items[i]) - { - newrow[key] = cur_frm.doc.items[i][key]; - } - - newrow.idx = item_length; - newrow["stock_qty"] = newrow.conversion_factor*qty; - newrow["qty"] = qty; - - newrow["material_request"] = ""; - newrow["material_request_item"] = ""; - - } - } - }); - i++; - } - refresh_field("items"); - //cur_frm.save(); - } - } - }); - }, - update_auto_repeat_reference: function(doc) { if (doc.auto_repeat) { frappe.call({ @@ -422,6 +359,68 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ cur_frm.add_fetch('project', 'cost_center', 'cost_center'); +erpnext.buying.link_to_mrs = function(frm) { + var my_items = []; + for (var i in frm.doc.items) { + if(!frm.doc.items[i].material_request){ + my_items.push(frm.doc.items[i].item_code); + } + } + frappe.call({ + method: "erpnext.buying.utils.get_linked_material_requests", + args:{ + items: my_items + }, + callback: function(r) { + if(!r.message || r.message.length == 0) { + frappe.throw(__("No pending Material Requests found to link for the given items.")) + } + else { + var i = 0; + var item_length = frm.doc.items.length; + while (i < item_length) { + var qty = frm.doc.items[i].qty; + (r.message[0] || []).forEach(function(d) { + if (d.qty > 0 && qty > 0 && frm.doc.items[i].item_code == d.item_code && !frm.doc.items[i].material_request_item) + { + frm.doc.items[i].material_request = d.mr_name; + frm.doc.items[i].material_request_item = d.mr_item; + var my_qty = Math.min(qty, d.qty); + qty = qty - my_qty; + d.qty = d.qty - my_qty; + frm.doc.items[i].stock_qty = my_qty*frm.doc.items[i].conversion_factor; + frm.doc.items[i].qty = my_qty; + + frappe.msgprint("Assigning " + d.mr_name + " to " + d.item_code + " (row " + frm.doc.items[i].idx + ")"); + if (qty > 0) + { + frappe.msgprint("Splitting " + qty + " units of " + d.item_code); + var newrow = frappe.model.add_child(frm.doc, frm.doc.items[i].doctype, "items"); + item_length++; + + for (var key in frm.doc.items[i]) + { + newrow[key] = frm.doc.items[i][key]; + } + + newrow.idx = item_length; + newrow["stock_qty"] = newrow.conversion_factor*qty; + newrow["qty"] = qty; + + newrow["material_request"] = ""; + newrow["material_request_item"] = ""; + + } + } + }); + i++; + } + refresh_field("items"); + } + } + }); +} + erpnext.buying.get_default_bom = function(frm) { $.each(frm.doc["items"] || [], function(i, d) { if (d.item_code && d.bom === "") { From be4dbad823ecba34fbedccca84f351fb08ccf2fa Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 24 Sep 2020 16:43:25 +0530 Subject: [PATCH 11/99] style: (minor) Reduce loc --- erpnext/public/js/controllers/buying.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index fb904e7d66..62da7f5c5b 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -360,20 +360,14 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ cur_frm.add_fetch('project', 'cost_center', 'cost_center'); erpnext.buying.link_to_mrs = function(frm) { - var my_items = []; - for (var i in frm.doc.items) { - if(!frm.doc.items[i].material_request){ - my_items.push(frm.doc.items[i].item_code); - } - } frappe.call({ method: "erpnext.buying.utils.get_linked_material_requests", args:{ - items: my_items + items: frm.doc.items.map((item) => {return item.item_code;}) }, callback: function(r) { if(!r.message || r.message.length == 0) { - frappe.throw(__("No pending Material Requests found to link for the given items.")) + frappe.throw({message: __("No pending Material Requests found to link for the given items."), title: __("Note")}); } else { var i = 0; From 4be5b5c891066e469c1d4458e04368e218d8ccd4 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 8 Oct 2020 19:08:27 +0530 Subject: [PATCH 12/99] fix: Handle missing Account and Item in Opening Invoice Creation Tool --- .../opening_invoice_creation_tool.py | 3 ++- .../doctype/purchase_invoice/purchase_invoice.py | 5 +++++ .../doctype/sales_invoice/sales_invoice.py | 5 +++++ erpnext/controllers/accounts_controller.py | 15 +++++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py index a53417eedf..3653a88167 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py @@ -181,7 +181,8 @@ class OpeningInvoiceCreationTool(Document): "due_date": row.due_date, "posting_date": row.posting_date, frappe.scrub(party_type): row.party, - "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice" + "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice", + "update_stock": 0 }) accounting_dimension = get_accounting_dimensions() diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 079f599706..3d67a8dc35 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -147,6 +147,11 @@ class PurchaseInvoice(BuyingController): throw(_("Conversion rate cannot be 0 or 1")) def validate_credit_to_acc(self): + if not self.credit_to: + self.credit_to = get_party_account("Supplier", self.supplier, self.company) + if not self.credit_to: + self.raise_missing_debit_credit_account_error("Supplier", self.supplier) + account = frappe.db.get_value("Account", self.credit_to, ["account_type", "report_type", "account_currency"], as_dict=True) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 92e49d59da..c280184d62 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -472,6 +472,11 @@ class SalesInvoice(SellingController): return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0] def validate_debit_to_acc(self): + if not self.debit_to: + self.debit_to = get_party_account("Customer", self.customer, self.company) + if not self.debit_to: + self.raise_missing_debit_credit_account_error("Customer", self.customer) + account = frappe.get_cached_value("Account", self.debit_to, ["account_type", "report_type", "account_currency"], as_dict=True) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index bb288c5551..7163e02122 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -720,6 +720,21 @@ class AccountsController(TransactionBase): return self._abbr + def raise_missing_debit_credit_account_error(self, party_type, party): + """Raise an error if debit to/credit to account does not exist""" + db_or_cr = frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To") + rec_or_pay = "Receivable" if self.doctype == "Sales Invoice" else "Payable" + + link_to_party = frappe.utils.get_link_to_form(party_type, party) + link_to_company = frappe.utils.get_link_to_form("Company", self.company) + + message = _("{0} Account not found against Customer {1}.").format(db_or_cr, frappe.bold(party) or '') + message += "
" + _("Please set one of the following:") + "
" + message += "
  • " + _("'Account' in the Accounting section of Customer {0}").format(link_to_party) + "
  • " + message += "
  • " + _("'Default {0} Account' in Company {1}").format(rec_or_pay, link_to_company) + "
" + + frappe.throw(message, title=_("Account Missing")) + def validate_party(self): party_type, party = self.get_party() validate_party_frozen_disabled(party_type, party) From 359778e2357997aaeac126c37bdcb34e8efa7ed3 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 16 Oct 2020 16:47:23 +0530 Subject: [PATCH 13/99] chore: Code cleanup, reduce redundancy --- .../request_for_quotation.js | 2 +- .../supplier_quotation/supplier_quotation.js | 2 +- erpnext/public/js/controllers/buying.js | 78 +++++++++---------- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 660af96505..1ebd21a17b 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -326,7 +326,7 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e d.show(); }, __("Get items from")); - //Link Material Requests + // Link Material Requests this.frm.add_custom_button(__('Link to Material Requests'), function() { erpnext.buying.link_to_mrs(me.frm); diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js index c146f13dfe..934d71c3b3 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js @@ -55,7 +55,7 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext }) }, __("Get items from")); - //Link Material Requests + // Link Material Requests this.frm.add_custom_button(__('Link to Material Requests'), function() { erpnext.buying.link_to_mrs(me.frm); diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 62da7f5c5b..8cae7a5b3d 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -363,54 +363,54 @@ erpnext.buying.link_to_mrs = function(frm) { frappe.call({ method: "erpnext.buying.utils.get_linked_material_requests", args:{ - items: frm.doc.items.map((item) => {return item.item_code;}) + items: frm.doc.items.map((item) => item.item_code) }, callback: function(r) { - if(!r.message || r.message.length == 0) { - frappe.throw({message: __("No pending Material Requests found to link for the given items."), title: __("Note")}); + if (!r.message || r.message.length == 0) { + frappe.throw({ + message: __("No pending Material Requests found to link for the given items."), + title: __("Note") + }); } - else { - var i = 0; - var item_length = frm.doc.items.length; - while (i < item_length) { - var qty = frm.doc.items[i].qty; - (r.message[0] || []).forEach(function(d) { - if (d.qty > 0 && qty > 0 && frm.doc.items[i].item_code == d.item_code && !frm.doc.items[i].material_request_item) + + var item_length = frm.doc.items.length; + for (let item of frm.doc.items) { + var qty = item.qty; + (r.message[0] || []).forEach(function(d) { + if (d.qty > 0 && qty > 0 && item.item_code == d.item_code && !item.material_request_item) + { + item.material_request = d.mr_name; + item.material_request_item = d.mr_item; + var my_qty = Math.min(qty, d.qty); + qty = qty - my_qty; + d.qty = d.qty - my_qty; + item.stock_qty = my_qty*item.conversion_factor; + item.qty = my_qty; + + frappe.msgprint("Assigning " + d.mr_name + " to " + d.item_code + " (row " + item.idx + ")"); + if (qty > 0) { - frm.doc.items[i].material_request = d.mr_name; - frm.doc.items[i].material_request_item = d.mr_item; - var my_qty = Math.min(qty, d.qty); - qty = qty - my_qty; - d.qty = d.qty - my_qty; - frm.doc.items[i].stock_qty = my_qty*frm.doc.items[i].conversion_factor; - frm.doc.items[i].qty = my_qty; + frappe.msgprint("Splitting " + qty + " units of " + d.item_code); + var newrow = frappe.model.add_child(frm.doc, item.doctype, "items"); + item_length++; - frappe.msgprint("Assigning " + d.mr_name + " to " + d.item_code + " (row " + frm.doc.items[i].idx + ")"); - if (qty > 0) + for (var key in item) { - frappe.msgprint("Splitting " + qty + " units of " + d.item_code); - var newrow = frappe.model.add_child(frm.doc, frm.doc.items[i].doctype, "items"); - item_length++; - - for (var key in frm.doc.items[i]) - { - newrow[key] = frm.doc.items[i][key]; - } - - newrow.idx = item_length; - newrow["stock_qty"] = newrow.conversion_factor*qty; - newrow["qty"] = qty; - - newrow["material_request"] = ""; - newrow["material_request_item"] = ""; - + newrow[key] = item[key]; } + + newrow.idx = item_length; + newrow["stock_qty"] = newrow.conversion_factor*qty; + newrow["qty"] = qty; + + newrow["material_request"] = ""; + newrow["material_request_item"] = ""; + } - }); - i++; - } - refresh_field("items"); + } + }); } + refresh_field("items"); } }); } From d6596a169cd2de4a4dfc74cf68d454cec290a30d Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 2 Nov 2020 15:07:48 +0530 Subject: [PATCH 14/99] fix: Billing % Logic and Map Pending Qty only in PR and DN - Billing % should consider unreturned amount as total - While mapping to return doc, map unreturned amount - Added field Received Qty in Stock UOM, to tally against Returned Qty in PR - PR billing percentage updation custom function - In patch set received qty in stock uom first, then update returned qty and billing --- .../purchase_invoice/purchase_invoice.py | 4 +- erpnext/controllers/buying_controller.py | 4 ++ .../controllers/sales_and_purchase_return.py | 58 ++++++++++++++++--- erpnext/controllers/stock_controller.py | 6 +- erpnext/patches.txt | 2 +- .../v13_0/update_returned_qty_in_pr_dn.py | 7 +++ erpnext/public/js/controllers/buying.js | 1 + .../purchase_receipt/purchase_receipt.py | 40 ++++++++++++- .../purchase_receipt_item.json | 9 ++- 9 files changed, 117 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 91c4dfb587..014f05c4c1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1032,7 +1032,9 @@ class PurchaseInvoice(BuyingController): updated_pr += update_billed_amount_based_on_po(d.po_detail, update_modified) for pr in set(updated_pr): - frappe.get_doc("Purchase Receipt", pr).update_billing_percentage(update_modified=update_modified) + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage + pr_doc = frappe.get_doc("Purchase Receipt", pr) + update_billing_percentage(pr_doc, update_modified=update_modified) def on_recurring(self, reference_doc, auto_repeat_doc): self.due_date = None diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index f376836f7b..af2474d3de 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -497,6 +497,10 @@ class BuyingController(StockController): frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx)) d.stock_qty = flt(d.qty) * flt(d.conversion_factor) + if self.doctype=="Purchase Receipt" and d.meta.get_field("received_stock_qty"): + # Set Received Qty in Stock UOM + d.received_stock_qty = flt(d.received_qty) * flt(d.conversion_factor, d.precision("conversion_factor")) + def validate_purchase_return(self): for d in self.get("items"): if self.is_return and flt(d.rejected_qty) != 0: diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index b4da5fa3e7..e11289d79e 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -203,6 +203,41 @@ def get_already_returned_items(doc): return items +def get_returned_qty_map_for_row(row_name, doctype): + child_doctype = doctype + " Item" + reference_field = frappe.scrub(child_doctype) if doctype == "Purchase Receipt" else "dn_detail" + reference_field = "child." + reference_field + columns = "" + + if doctype == "Purchase Receipt": + columns += ", sum(abs(child.rejected_qty)) as rejected_qty, \ + sum(abs(child.received_qty)) as received_qty, \ + sum(abs(child.received_stock_qty)) as received_stock_qty" + + data = frappe.db.sql(""" + select + sum(abs(child.qty)) as qty, + sum(abs(child.stock_qty)) as stock_qty, + %(columns)s + from + `tab{0}` child, `tab{1}` parent + where + child.parent = parent.name + and parent.docstatus = 1 + and parent.is_return = 1 + and {2} = %(row_name)s + """.format(child_doctype, doctype, reference_field), + { + "row_name": row_name, + "columns": columns, + "child_doctype": child_doctype, + "doctype": doctype, + "reference_field": reference_field + }, + as_dict=1) + + return data[0] + def make_return_doc(doctype, source_name, target_doc=None): from frappe.model.mapper import get_mapped_doc company = frappe.db.get_value("Delivery Note", source_name, "company") @@ -262,20 +297,25 @@ def make_return_doc(doctype, source_name, target_doc=None): doc.run_method("calculate_taxes_and_totals") def update_item(source_doc, target_doc, source_parent): - target_doc.qty = -1* source_doc.qty + target_doc.qty = -1 * source_doc.qty + if doctype == "Purchase Receipt": - target_doc.received_qty = -1* source_doc.received_qty - target_doc.rejected_qty = -1* source_doc.rejected_qty - target_doc.qty = -1* source_doc.qty - target_doc.stock_qty = -1 * source_doc.stock_qty + returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype) + target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0)) + target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0)) + target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0)) + + target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0)) + target_doc.received_stock_qty = -1 * flt(source_doc.received_stock_qty - (returned_qty_map.get('received_stock_qty') or 0)) + target_doc.purchase_order = source_doc.purchase_order target_doc.purchase_order_item = source_doc.purchase_order_item target_doc.rejected_warehouse = source_doc.rejected_warehouse target_doc.purchase_receipt_item = source_doc.name elif doctype == "Purchase Invoice": - target_doc.received_qty = -1* source_doc.received_qty - target_doc.rejected_qty = -1* source_doc.rejected_qty + target_doc.received_qty = -1 * source_doc.received_qty + target_doc.rejected_qty = -1 * source_doc.rejected_qty target_doc.qty = -1* source_doc.qty target_doc.stock_qty = -1 * source_doc.stock_qty target_doc.purchase_order = source_doc.purchase_order @@ -286,6 +326,10 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.purchase_invoice_item = source_doc.name elif doctype == "Delivery Note": + returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype) + target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0)) + target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0)) + target_doc.against_sales_order = source_doc.against_sales_order target_doc.against_sales_invoice = source_doc.against_sales_invoice target_doc.so_detail = source_doc.so_detail diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index f743d707f7..196279fa5c 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -338,11 +338,15 @@ class StockController(AccountsController): validate_warehouse_company(w, self.company) def update_billing_percentage(self, update_modified=True): + target_ref_field = "amount" + if self.doctype == "Delivery Note": + target_ref_field = "amount - (returned_qty * rate)" + self._update_percent_field({ "target_dt": self.doctype + " Item", "target_parent_dt": self.doctype, "target_parent_field": "per_billed", - "target_ref_field": "amount", + "target_ref_field": target_ref_field, "target_field": "billed_amt", "name": self.name, }, update_modified) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 6dfa085b58..dc7e99bcde 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -732,4 +732,4 @@ erpnext.patches.v13_0.set_youtube_video_id erpnext.patches.v13_0.print_uom_after_quantity_patch erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail -erpnext.patches.v13_0.update_returned_qty_in_pr_dn +erpnext.patches.v13_0.update_returned_qty_in_pr_dn #12am \ No newline at end of file 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 a13640e1b0..7f42cd92e3 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 @@ -15,6 +15,13 @@ def execute(): # Update original receipt/delivery document from return return_doc = frappe.get_cached_doc(doctype, return_doc.name) return_doc.update_prevdoc_status() + return_against = frappe.get_doc(doctype, return_doc.return_against) + return_against.update_billing_status() + + # Set received qty in stock uom in PR, as returned qty is checked against it + frappe.db.sql(""" update `tabPurchase Receipt Item` + set received_stock_qty = received_qty * conversion_factor + where docstatus = 1 """) for doctype in ('Purchase Receipt', 'Delivery Note'): update_from_return_docs(doctype) \ No newline at end of file diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index cb76c87b62..11f70f7f59 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -189,6 +189,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ frappe.model.round_floats_in(item, ["qty", "received_qty"]); item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item)); + item.received_stock_qty = flt(item.conversion_factor, precision("conversion_factor", item)) * flt(item.received_qty); } this._super(doc, cdt, cdn); diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 551f3777a5..be3ff5e5c2 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -77,8 +77,8 @@ class PurchaseReceipt(BuyingController): 'target_field': 'returned_qty', 'target_parent_dt': 'Purchase Receipt', 'target_parent_field': 'per_returned', - 'target_ref_field': 'stock_qty', - 'source_field': '-1 * stock_qty', + 'target_ref_field': 'received_stock_qty', + 'source_field': '-1 * received_stock_qty', 'percent_join_field_parent': 'return_against' } ]) @@ -503,7 +503,7 @@ class PurchaseReceipt(BuyingController): for pr in set(updated_pr): pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr) - pr_doc.update_billing_percentage(update_modified=update_modified) + update_billing_percentage(pr_doc, update_modified=update_modified) self.load_from_db() @@ -543,6 +543,39 @@ def update_billed_amount_based_on_po(po_detail, update_modified=True): return updated_pr +def update_billing_percentage(pr_doc, update_modified=True): + # Update Billing % based on pending accepted qty + total_amount, total_billed_amount = 0, 0 + for item in pr_doc.items: + returned_qty = frappe.db.sql(""" + select sum(abs(child.qty)) as qty + from + `tabPurchase Receipt Item` child, + `tabPurchase Receipt` parent + where + child.parent = parent.name + and parent.docstatus = 1 + and parent.is_return = 1 + and child.purchase_receipt_item = %(row_name)s + """, {"row_name": item.name}) + returned_qty = returned_qty[0][0] if returned_qty else 0 + + returned_amount = flt(returned_qty) * flt(item.rate) + pending_amount = flt(item.amount) - returned_amount + total_billable_amount = pending_amount if item.billed_amt <= pending_amount else item.billed_amt + + total_amount += total_billable_amount + total_billed_amount += flt(item.billed_amt) + + print(total_billed_amount, total_amount) + percent_billed = round(100 * (total_billed_amount / total_amount), 6) + pr_doc.db_set("per_billed", percent_billed) + pr_doc.load_from_db() + + if update_modified: + pr_doc.set_status(update=True) + pr_doc.notify_update() + @frappe.whitelist() def make_purchase_invoice(source_name, target_doc=None): from frappe.model.mapper import get_mapped_doc @@ -562,6 +595,7 @@ def make_purchase_invoice(source_name, target_doc=None): def update_item(source_doc, target_doc, source_parent): target_doc.qty, returned_qty = get_pending_qty(source_doc) + target_doc.stock_qty = flt(target_doc.qty) * flt(target_doc.conversion_factor, target_doc.precision("conversion_factor")) returned_qty_map[source_doc.name] = returned_qty def get_pending_qty(item_row): diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 20ae56feeb..84c64aa8f8 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -31,6 +31,7 @@ "retain_sample", "sample_quantity", "tracking_section", + "received_stock_qty", "stock_qty", "col_break_tracking_section", "returned_qty", @@ -854,12 +855,18 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "received_stock_qty", + "fieldtype": "Float", + "label": "Received Qty in Stock UOM", + "print_hide": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-09-09 13:39:46.452817", + "modified": "2020-11-02 10:00:38.204294", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From 3991b84b2bf26d30402d33ef479ba8af5843c220 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 2 Nov 2020 15:23:41 +0530 Subject: [PATCH 15/99] chore: Avoid multiline string in Translation & remove print statement --- erpnext/controllers/stock_controller.py | 4 ++-- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 196279fa5c..4436ab07e5 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -229,8 +229,8 @@ class StockController(AccountsController): def check_expense_account(self, item): if not item.get("expense_account"): - frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense \ - Account in the Items table").format(item.idx, frappe.bold(item.item_code)), + frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense Account in the Items table") + .format(item.idx, frappe.bold(item.item_code)), title=_("Expense Account Missing")) else: diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index be3ff5e5c2..c37740cc7d 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -567,7 +567,6 @@ def update_billing_percentage(pr_doc, update_modified=True): total_amount += total_billable_amount total_billed_amount += flt(item.billed_amt) - print(total_billed_amount, total_amount) percent_billed = round(100 * (total_billed_amount / total_amount), 6) pr_doc.db_set("per_billed", percent_billed) pr_doc.load_from_db() From f21e3fbf04c157ff74d40b88cb7bd4bc7d7578ca Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 3 Nov 2020 12:01:56 +0530 Subject: [PATCH 16/99] chore: Tests - Added test for mapping secnd return doc - Added test for billing % of partially returned doc - Handled PR with 0 billing amount --- .../delivery_note/test_delivery_note.py | 26 ++++++++++++ .../purchase_receipt/purchase_receipt.py | 7 +++- .../purchase_receipt/test_purchase_receipt.py | 41 +++++++++++++++++-- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 9f273d7959..fa07a2510c 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -255,6 +255,32 @@ class TestDeliveryNote(unittest.TestCase): self.assertEqual(dn.items[0].returned_qty, 2) self.assertEqual(dn.per_returned, 40) + from erpnext.controllers.sales_and_purchase_return import make_return_doc + return_dn_2 = make_return_doc("Delivery Note", dn.name) + + # Check if unreturned amount is mapped in 2nd return + self.assertEqual(return_dn_2.items[0].qty, -3) + + si = make_sales_invoice(dn.name) + si.submit() + + self.assertEqual(si.items[0].qty, 3) + + dn.load_from_db() + # DN should be completed on billing all unreturned amount + self.assertEqual(dn.items[0].billed_amt, 1500) + self.assertEqual(dn.per_billed, 100) + self.assertEqual(dn.status, 'Completed') + + si.load_from_db() + si.cancel() + + dn.load_from_db() + self.assertEqual(dn.per_billed, 0) + + dn1.cancel() + dn.cancel() + def test_sales_return_for_non_bundled_items_full(self): from erpnext.stock.doctype.item.test_item import make_item diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index c37740cc7d..1852985b1d 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -513,7 +513,7 @@ def update_billed_amount_based_on_po(po_detail, update_modified=True): where po_detail=%s and (pr_detail is null or pr_detail = '') and docstatus=1""", po_detail) billed_against_po = billed_against_po and billed_against_po[0][0] or 0 - # Get all Delivery Note Item rows against the Sales Order Item row + # Get all Purchase Receipt Item rows against the Purchase Order Item row pr_details = frappe.db.sql("""select pr_item.name, pr_item.amount, pr_item.parent from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr where pr.name=pr_item.parent and pr_item.purchase_order_item=%s @@ -544,6 +544,9 @@ def update_billed_amount_based_on_po(po_detail, update_modified=True): return updated_pr def update_billing_percentage(pr_doc, update_modified=True): + # Reload as billed amount was set in db directly + pr_doc.load_from_db() + # Update Billing % based on pending accepted qty total_amount, total_billed_amount = 0, 0 for item in pr_doc.items: @@ -567,7 +570,7 @@ def update_billing_percentage(pr_doc, update_modified=True): total_amount += total_billable_amount total_billed_amount += flt(item.billed_amt) - percent_billed = round(100 * (total_billed_amount / total_amount), 6) + percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6) pr_doc.db_set("per_billed", percent_billed) pr_doc.load_from_db() diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index aef5bf3959..c23d6c2b53 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -100,7 +100,10 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no})) def test_purchase_receipt_gl_entry(self): - pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", get_multiple_items = True, get_taxes_and_charges = True) + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", + get_multiple_items = True, get_taxes_and_charges = True) + self.assertEqual(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1) gl_entries = get_gl_entries("Purchase Receipt", pr.name) @@ -245,10 +248,12 @@ class TestPurchaseReceipt(unittest.TestCase): pr.get("items")[0].rejected_warehouse) def test_purchase_return_partial(self): + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") - pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") - - return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-2, do_not_submit=1) + return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", + warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", + is_return=1, return_against=pr.name, qty=-2, do_not_submit=1) return_pr.items[0].purchase_receipt_item = pr.items[0].name return_pr.submit() @@ -283,6 +288,33 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(pr.items[0].returned_qty, 2) self.assertEqual(pr.per_returned, 40) + from erpnext.controllers.sales_and_purchase_return import make_return_doc + return_pr_2 = make_return_doc("Purchase Receipt", pr.name) + + # Check if unreturned amount is mapped in 2nd return + self.assertEqual(return_pr_2.items[0].qty, -3) + + # Make PI against unreturned amount + pi = make_purchase_invoice(pr.name) + pi.submit() + + self.assertEqual(pi.items[0].qty, 3) + + pr.load_from_db() + # PR should be completed on billing all unreturned amount + self.assertEqual(pr.items[0].billed_amt, 150) + self.assertEqual(pr.per_billed, 100) + self.assertEqual(pr.status, 'Completed') + + pi.load_from_db() + pi.cancel() + + pr.load_from_db() + self.assertEqual(pr.per_billed, 0) + + return_pr.cancel() + pr.cancel() + def test_purchase_return_full(self): pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") @@ -406,6 +438,7 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(pr1.per_billed, 100) self.assertEqual(pr1.status, "Completed") + pr2.load_from_db() self.assertEqual(pr2.get("items")[0].billed_amt, 2000) self.assertEqual(pr2.per_billed, 80) self.assertEqual(pr2.status, "To Bill") From 53b1a9a40bb792614324316f8a933c72a92beac3 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 3 Nov 2020 15:45:25 +0530 Subject: [PATCH 17/99] chore: Add Test for missing debit account --- .../test_opening_invoice_creation_tool.py | 53 +++++++++++++++++-- erpnext/controllers/accounts_controller.py | 6 ++- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py index 54229f5247..329d84bdb7 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py @@ -7,16 +7,18 @@ import frappe import unittest test_dependencies = ["Customer", "Supplier"] +from frappe.custom.doctype.property_setter.property_setter import make_property_setter from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account class TestOpeningInvoiceCreationTool(unittest.TestCase): - def make_invoices(self, invoice_type="Sales"): + def make_invoices(self, invoice_type="Sales", company=None): doc = frappe.get_single("Opening Invoice Creation Tool") - args = get_opening_invoice_creation_dict(invoice_type=invoice_type) + args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company) doc.update(args) return doc.make_invoices() def test_opening_sales_invoice_creation(self): + property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check") invoices = self.make_invoices() self.assertEqual(len(invoices), 2) @@ -27,6 +29,13 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase): } self.check_expected_values(invoices, expected_value) + si = frappe.get_doc("Sales Invoice", invoices[0]) + + # Check if update stock is not enabled + self.assertEqual(si.update_stock, 0) + + property_setter.delete() + def check_expected_values(self, invoices, expected_value, invoice_type="Sales"): doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice" @@ -46,6 +55,32 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase): } self.check_expected_values(invoices, expected_value, "Purchase") + def test_opening_sales_invoice_creation_with_missing_debit_account(self): + company = make_company() + old_default_receivable_account = frappe.db.get_value("Company", company.name, "default_receivable_account") + frappe.db.set_value("Company", company.name, "default_receivable_account", "") + + if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"): + cc = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "_Test Opening Invoice Company", + "is_group": 1, "company": "_Test Opening Invoice Company"}) + cc.insert(ignore_mandatory=True) + cc2 = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "Main", "is_group": 0, + "company": "_Test Opening Invoice Company", "parent_cost_center": cc.name}) + cc2.insert() + + frappe.db.set_value("Company", company.name, "cost_center", "Main - _TOIC") + + self.make_invoices(company="_Test Opening Invoice Company") + + # Check if missing debit account error raised + error_log = frappe.db.exists("Error Log", {"error": ["like", "%erpnext.controllers.accounts_controller.AccountMissingError%"]}) + self.assertTrue(error_log) + + # teardown + frappe.db.set_value("Company", company.name, "default_receivable_account", old_default_receivable_account) + company.delete() + frappe.get_doc("Error Log", error_log).delete() + def get_opening_invoice_creation_dict(**args): party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier" company = args.get("company", "_Test Company") @@ -76,4 +111,16 @@ def get_opening_invoice_creation_dict(**args): }) invoice_dict.update(args) - return invoice_dict \ No newline at end of file + return invoice_dict + +def make_company(): + if frappe.db.exists("Company", "_Test Opening Invoice Company"): + return frappe.get_doc("Company", "_Test Opening Invoice Company") + + company = frappe.new_doc("Company") + company.company_name = "_Test Opening Invoice Company" + company.abbr = "_TOIC" + company.default_currency = "INR" + company.country = "India" + company.insert() + return company \ No newline at end of file diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 28c73a39e9..93a79ec934 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -23,6 +23,8 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g from erpnext.stock.get_item_details import get_item_warehouse, _get_item_tax_template, get_item_tax_map from erpnext.stock.doctype.packed_item.packed_item import make_packing_list +class AccountMissingError(frappe.ValidationError): pass + force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules") class AccountsController(TransactionBase): @@ -736,7 +738,7 @@ class AccountsController(TransactionBase): return self._abbr def raise_missing_debit_credit_account_error(self, party_type, party): - """Raise an error if debit to/credit to account does not exist""" + """Raise an error if debit to/credit to account does not exist.""" db_or_cr = frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To") rec_or_pay = "Receivable" if self.doctype == "Sales Invoice" else "Payable" @@ -748,7 +750,7 @@ class AccountsController(TransactionBase): message += "
  • " + _("'Account' in the Accounting section of Customer {0}").format(link_to_party) + "
  • " message += "
  • " + _("'Default {0} Account' in Company {1}").format(rec_or_pay, link_to_company) + "
" - frappe.throw(message, title=_("Account Missing")) + frappe.throw(message, title=_("Account Missing"), exc=AccountMissingError) def validate_party(self): party_type, party = self.get_party() From 7837161a3fbf52d384896b146ae1491447045078 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 3 Nov 2020 22:09:42 +0530 Subject: [PATCH 18/99] fix: Sider --- .../stock/doctype/purchase_receipt/test_purchase_receipt.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index c23d6c2b53..722b2c9aea 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -316,9 +316,11 @@ class TestPurchaseReceipt(unittest.TestCase): pr.cancel() def test_purchase_return_full(self): - pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1") + pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", + supplier_warehouse = "Work in Progress - TCP1") - return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-5, do_not_submit=1) + return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", + supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-5, do_not_submit=1) return_pr.items[0].purchase_receipt_item = pr.items[0].name return_pr.submit() From 4f2a64479dfcaf6a4bc164315abb4865723f75fe Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 9 Nov 2020 17:00:09 +0530 Subject: [PATCH 19/99] fix: Patch for old loans --- erpnext/patches/v13_0/update_old_loans.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py index 77239429c5..eaeda093f5 100644 --- a/erpnext/patches/v13_0/update_old_loans.py +++ b/erpnext/patches/v13_0/update_old_loans.py @@ -70,7 +70,7 @@ def execute(): payments = frappe.db.sql(''' SELECT j.name, a.debit, a.debit_in_account_currency, j.posting_date FROM `tabJournal Entry` j, `tabJournal Entry Account` a WHERE a.parent = j.name and a.reference_type='Loan' and a.reference_name = %s - and account = %s + and a.account = %s and j.docstatus = 1 ''', (loan.name, loan.loan_account), as_dict=1) for payment in payments: From 928dc432aba2cde284072b958c2ba3d801e4aeee Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 9 Nov 2020 17:17:12 +0530 Subject: [PATCH 20/99] fix: Reload journal entry account doc --- erpnext/patches/v13_0/update_old_loans.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py index eaeda093f5..dd15f10e09 100644 --- a/erpnext/patches/v13_0/update_old_loans.py +++ b/erpnext/patches/v13_0/update_old_loans.py @@ -18,6 +18,7 @@ def execute(): frappe.reload_doc('loan_management', 'doctype', 'loan_repayment_detail') frappe.reload_doc('loan_management', 'doctype', 'loan_interest_accrual') frappe.reload_doc('accounts', 'doctype', 'gl_entry') + frappe.reload_doc('accounts', 'doctype', 'journal_entry_account') updated_loan_types = [] From 1c969d64a2f928f78470c4dd42899d2393c6291f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 10 Nov 2020 20:25:35 +0530 Subject: [PATCH 21/99] fix: Handle cases where same loan type is used for multiple companies --- erpnext/patches/v13_0/update_old_loans.py | 35 +++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py index dd15f10e09..2925f0a5bc 100644 --- a/erpnext/patches/v13_0/update_old_loans.py +++ b/erpnext/patches/v13_0/update_old_loans.py @@ -23,7 +23,8 @@ def execute(): updated_loan_types = [] loans = frappe.get_all('Loan', fields=['name', 'loan_type', 'company', 'status', 'mode_of_payment', - 'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account']) + 'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account'], + filters={'docstatus': 1}) for loan in loans: # Update details in Loan Types and Loan @@ -39,7 +40,26 @@ def execute(): penalty_account = create_account(company=loan.company, account_type='Income Account', account_name='Penalty Account', parent_account=group_income_account) - if not loan_type_company: + # Same loan type used for multiple companies + if loan_type_company and loan_type_company != loan.company: + # get loan type for appropriate company + loan_type_name = frappe.get_value('Loan Type', {'company': loan.company, + 'mode_of_payment': loan.mode_of_payment, 'loan_account': loan.loan_account, + 'payment_account': loan.payment_account, 'interest_income_account': loan.interest_income_account, + 'penalty_income_account': loan.penalty_income_account}, 'name') + + if not loan_type_name: + loan_type_name = loan.loan_type + " - " + ''.join([c[0] for c in loan.company.split()]).upper() + create_loan_type(loan, loan_type_name, penalty_account) + + # update loan type in loan + frappe.db.sql("UPDATE `tabLoan` set loan_type = %s where name = %s", (loan_type_name, + loan.name)) + + if loan_type_name not in updated_loan_types: + updated_loan_types.append(loan_type_name) + + elif not loan_type_company: loan_type_doc = frappe.get_doc('Loan Type', loan.loan_type) loan_type_doc.is_term_loan = 1 loan_type_doc.company = loan.company @@ -87,3 +107,14 @@ def execute(): jv.flags.ignore_links = True jv.cancel() +def create_loan_type(loan, loan_type_name, penalty_account): + loan_type_doc = frappe.new_doc('Loan Type') + loan_type_doc.loan_name = loan_type_name + loan_type_doc.is_term_loan = 1 + loan_type_doc.company = loan.company + loan_type_doc.mode_of_payment = loan.mode_of_payment + loan_type_doc.payment_account = loan.payment_account + loan_type_doc.loan_account = loan.loan_account + loan_type_doc.interest_income_account = loan.interest_income_account + loan_type_doc.penalty_income_account = penalty_account + loan_type_doc.submit() From 73bde45bc5f488906f911cef57e6035712b38cb0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 10 Nov 2020 22:08:02 +0530 Subject: [PATCH 22/99] fix: Pass updated loan type --- erpnext/patches/v13_0/update_old_loans.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py index 2925f0a5bc..c16c2c81b8 100644 --- a/erpnext/patches/v13_0/update_old_loans.py +++ b/erpnext/patches/v13_0/update_old_loans.py @@ -29,6 +29,7 @@ def execute(): for loan in loans: # Update details in Loan Types and Loan loan_type_company = frappe.db.get_value('Loan Type', loan.loan_type, 'company') + loan_type = loan.loan_type group_income_account = frappe.get_value('Account', {'company': loan.company, 'is_group': 1, 'root_type': 'Income', 'account_name': _('Indirect Income')}) @@ -56,6 +57,7 @@ def execute(): frappe.db.sql("UPDATE `tabLoan` set loan_type = %s where name = %s", (loan_type_name, loan.name)) + loan_type = loan_type_name if loan_type_name not in updated_loan_types: updated_loan_types.append(loan_type_name) @@ -70,8 +72,9 @@ def execute(): loan_type_doc.penalty_income_account = penalty_account loan_type_doc.submit() updated_loan_types.append(loan.loan_type) + loan_type = loan.loan_type - if loan.loan_type in updated_loan_types: + if loan_type in updated_loan_types: if loan.status == 'Fully Disbursed': status = 'Disbursed' elif loan.status == 'Repaid/Closed': @@ -85,7 +88,7 @@ def execute(): 'status': status }) - process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan.loan_type, + process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan_type, loan=loan.name) payments = frappe.db.sql(''' SELECT j.name, a.debit, a.debit_in_account_currency, j.posting_date @@ -96,7 +99,7 @@ def execute(): for payment in payments: repayment_entry = make_repayment_entry(loan.name, loan.loan_applicant_type, loan.applicant, - loan.loan_type, loan.company) + loan_type, loan.company) repayment_entry.amount_paid = payment.debit_in_account_currency repayment_entry.posting_date = payment.posting_date From 13d1dda74b88c8c46d0e3adf618433e687c6cda2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 11 Nov 2020 11:07:17 +0530 Subject: [PATCH 23/99] fix: Handle loan type naming collisions --- erpnext/patches/v13_0/update_old_loans.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py index c16c2c81b8..70c1b7eb39 100644 --- a/erpnext/patches/v13_0/update_old_loans.py +++ b/erpnext/patches/v13_0/update_old_loans.py @@ -51,7 +51,7 @@ def execute(): if not loan_type_name: loan_type_name = loan.loan_type + " - " + ''.join([c[0] for c in loan.company.split()]).upper() - create_loan_type(loan, loan_type_name, penalty_account) + loan_type_name = create_loan_type(loan, loan_type_name, penalty_account) # update loan type in loan frappe.db.sql("UPDATE `tabLoan` set loan_type = %s where name = %s", (loan_type_name, @@ -111,6 +111,10 @@ def execute(): jv.cancel() def create_loan_type(loan, loan_type_name, penalty_account): + + if frappe.db.get_value('Loan Type', loan_type_name): + loan_type_name = loan_type_name + '-1' + loan_type_doc = frappe.new_doc('Loan Type') loan_type_doc.loan_name = loan_type_name loan_type_doc.is_term_loan = 1 From 0dc052e635d5b9846265807af29f704105c9afc4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 11 Nov 2020 12:57:16 +0530 Subject: [PATCH 24/99] fix: Return loan type name --- erpnext/patches/v13_0/update_old_loans.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py index 70c1b7eb39..fcadc6273e 100644 --- a/erpnext/patches/v13_0/update_old_loans.py +++ b/erpnext/patches/v13_0/update_old_loans.py @@ -125,3 +125,5 @@ def create_loan_type(loan, loan_type_name, penalty_account): loan_type_doc.interest_income_account = loan.interest_income_account loan_type_doc.penalty_income_account = penalty_account loan_type_doc.submit() + + return loan_type_name From a2dc1740df6d4dea70d76d19fabadbd2dc885c2e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 11 Nov 2020 13:57:10 +0530 Subject: [PATCH 25/99] fix: Use autoname for loan creation --- erpnext/patches/v13_0/update_old_loans.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py index fcadc6273e..23e4803029 100644 --- a/erpnext/patches/v13_0/update_old_loans.py +++ b/erpnext/patches/v13_0/update_old_loans.py @@ -5,6 +5,7 @@ from frappe.utils import nowdate from erpnext.accounts.doctype.account.test_account import create_account from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans from erpnext.loan_management.doctype.loan.loan import make_repayment_entry +from frappe.model.naming import make_autoname def execute(): @@ -50,7 +51,6 @@ def execute(): 'penalty_income_account': loan.penalty_income_account}, 'name') if not loan_type_name: - loan_type_name = loan.loan_type + " - " + ''.join([c[0] for c in loan.company.split()]).upper() loan_type_name = create_loan_type(loan, loan_type_name, penalty_account) # update loan type in loan @@ -111,12 +111,8 @@ def execute(): jv.cancel() def create_loan_type(loan, loan_type_name, penalty_account): - - if frappe.db.get_value('Loan Type', loan_type_name): - loan_type_name = loan_type_name + '-1' - loan_type_doc = frappe.new_doc('Loan Type') - loan_type_doc.loan_name = loan_type_name + loan_type_doc.loan_name = make_autoname("Loan Type-.####") loan_type_doc.is_term_loan = 1 loan_type_doc.company = loan.company loan_type_doc.mode_of_payment = loan.mode_of_payment @@ -126,4 +122,4 @@ def create_loan_type(loan, loan_type_name, penalty_account): loan_type_doc.penalty_income_account = penalty_account loan_type_doc.submit() - return loan_type_name + return loan_type_doc.name From b58dca8d942c18bc3ab0e1afa7ca7e744967c5c8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 12 Nov 2020 13:37:11 +0530 Subject: [PATCH 26/99] fix: Only update open loans --- erpnext/patches/v13_0/update_old_loans.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py index 23e4803029..8ed789cf45 100644 --- a/erpnext/patches/v13_0/update_old_loans.py +++ b/erpnext/patches/v13_0/update_old_loans.py @@ -25,7 +25,7 @@ def execute(): loans = frappe.get_all('Loan', fields=['name', 'loan_type', 'company', 'status', 'mode_of_payment', 'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account'], - filters={'docstatus': 1}) + filters={'docstatus': 1, 'status': ('!=', 'Closed')}) for loan in loans: # Update details in Loan Types and Loan From c51b340ddf46486cc98d92af03a9332c3e899517 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 12 Nov 2020 18:43:43 +0530 Subject: [PATCH 27/99] fix: Update closed loans --- erpnext/patches/v13_0/update_old_loans.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py index 8ed789cf45..3042db331a 100644 --- a/erpnext/patches/v13_0/update_old_loans.py +++ b/erpnext/patches/v13_0/update_old_loans.py @@ -23,6 +23,14 @@ def execute(): updated_loan_types = [] + # Update old loan status as closed + loans_list = frappe.db.sql("""SELECT distinct parent from `tabRepayment Schedule` + where paid = 0 and docstatus = 1""", as_dict=1) + + loans_to_close = [d.parent for d in loans_list] + + frappe.db.sql("UPDATE `tabLoan` set status = 'Closed' where name not in (%s)" % (', '.join(['%s'] * len(loans_to_close))), tuple(loans_to_close)) + loans = frappe.get_all('Loan', fields=['name', 'loan_type', 'company', 'status', 'mode_of_payment', 'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account'], filters={'docstatus': 1, 'status': ('!=', 'Closed')}) @@ -91,7 +99,7 @@ def execute(): process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan_type, loan=loan.name) - payments = frappe.db.sql(''' SELECT j.name, a.debit, a.debit_in_account_currency, j.posting_date + payments = frappe.db.sql(''' SELECT j.name, a.credit, a.credit_in_account_currency, j.posting_date FROM `tabJournal Entry` j, `tabJournal Entry Account` a WHERE a.parent = j.name and a.reference_type='Loan' and a.reference_name = %s and a.account = %s and j.docstatus = 1 From 78690af440ca67b3dd9de60b585522f172dfc423 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 12 Nov 2020 18:47:34 +0530 Subject: [PATCH 28/99] fix: Update only if loans to close --- erpnext/patches/v13_0/update_old_loans.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py index 3042db331a..c7f372e26f 100644 --- a/erpnext/patches/v13_0/update_old_loans.py +++ b/erpnext/patches/v13_0/update_old_loans.py @@ -29,7 +29,8 @@ def execute(): loans_to_close = [d.parent for d in loans_list] - frappe.db.sql("UPDATE `tabLoan` set status = 'Closed' where name not in (%s)" % (', '.join(['%s'] * len(loans_to_close))), tuple(loans_to_close)) + if loans_to_close: + frappe.db.sql("UPDATE `tabLoan` set status = 'Closed' where name not in (%s)" % (', '.join(['%s'] * len(loans_to_close))), tuple(loans_to_close)) loans = frappe.get_all('Loan', fields=['name', 'loan_type', 'company', 'status', 'mode_of_payment', 'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account'], From a862eb25e6c51b31eafe96f0bbfdb5f20d9d3cf2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 13 Nov 2020 17:57:57 +0530 Subject: [PATCH 29/99] fix: Make repayment entry only if amount exists --- erpnext/patches/v13_0/update_old_loans.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py index c7f372e26f..c4d9bdb7af 100644 --- a/erpnext/patches/v13_0/update_old_loans.py +++ b/erpnext/patches/v13_0/update_old_loans.py @@ -107,17 +107,18 @@ def execute(): ''', (loan.name, loan.loan_account), as_dict=1) for payment in payments: - repayment_entry = make_repayment_entry(loan.name, loan.loan_applicant_type, loan.applicant, - loan_type, loan.company) + if payment.credit_in_account_currency: + repayment_entry = make_repayment_entry(loan.name, loan.loan_applicant_type, loan.applicant, + loan_type, loan.company) - repayment_entry.amount_paid = payment.debit_in_account_currency - repayment_entry.posting_date = payment.posting_date - repayment_entry.save() - repayment_entry.submit() + repayment_entry.amount_paid = payment.credit_in_account_currency + repayment_entry.posting_date = payment.posting_date + repayment_entry.save() + repayment_entry.submit() - jv = frappe.get_doc('Journal Entry', payment.name) - jv.flags.ignore_links = True - jv.cancel() + jv = frappe.get_doc('Journal Entry', payment.name) + jv.flags.ignore_links = True + jv.cancel() def create_loan_type(loan, loan_type_name, penalty_account): loan_type_doc = frappe.new_doc('Loan Type') From 54228691e7e64ac181cb308520708e9f97a2694c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 26 Nov 2020 16:38:28 +0530 Subject: [PATCH 30/99] feat(IPME): Button to create Stock Entry for Drug Shortage --- .../inpatient_medication_entry.js | 23 +++++ .../inpatient_medication_entry.py | 92 +++++++++++++++---- 2 files changed, 98 insertions(+), 17 deletions(-) diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js index f523cf21bd..3980370370 100644 --- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js +++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js @@ -29,6 +29,29 @@ frappe.ui.form.on('Inpatient Medication Entry', { } }; }); + + if (frm.doc.__islocal || frm.doc.docstatus !== 0) + return; + + frm.add_custom_button(__('Make Stock Entry'), function() { + frappe.call({ + method: 'erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry.make_difference_stock_entry', + args: { docname: frm.doc.name }, + freeze: true, + callback: function(r) { + if (r.message) { + var doclist = frappe.model.sync(r.message); + frappe.set_route('Form', doclist[0].doctype, doclist[0].name); + } else { + frappe.msgprint({ + title: __('No Drug Shortage'), + message: __('All the drugs are available with sufficient qty to process this Inpatient Medication Entry.'), + indicator: 'green' + }); + } + } + }) + }); }, patient: function(frm) { diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py index 5dac23abd9..5a2a0e54aa 100644 --- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py +++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py @@ -142,25 +142,32 @@ class InpatientMedicationEntry(Document): return orders, order_entry_map def check_stock_qty(self): - from erpnext.stock.stock_ledger import NegativeStockError + drug_shortage = get_drug_shortage_map(self.medication_orders, self.warehouse) - drug_availability = dict() - for d in self.medication_orders: - if not drug_availability.get(d.drug_code): - drug_availability[d.drug_code] = 0 - drug_availability[d.drug_code] += flt(d.dosage) + if drug_shortage: + message = _('Quantity not available for the following items in warehouse {0}. ').format(frappe.bold(self.warehouse)) + message += _('Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.') - for drug, dosage in drug_availability.items(): - available_qty = get_latest_stock_qty(drug, self.warehouse) + formatted_item_rows = '' - # validate qty - if flt(available_qty) < flt(dosage): - frappe.throw(_('Quantity not available for {0} in warehouse {1}').format( - frappe.bold(drug), frappe.bold(self.warehouse)) - + '

' + _('Available quantity is {0}, you need {1}').format( - frappe.bold(available_qty), frappe.bold(dosage)) - + '

' + _('Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.'), - NegativeStockError, title=_('Insufficient Stock')) + for drug, shortage_qty in drug_shortage.items(): + item_link = get_link_to_form('Item', drug) + formatted_item_rows += """ + {0} + {1} + """.format(item_link, frappe.bold(shortage_qty)) + + message += """ + + + + + + {2} +
{0}{1}
+ """.format(_('Drug Code'), _('Shortage Qty'), formatted_item_rows) + + frappe.throw(message, title=_('Insufficient Stock'), is_minimizable=True, wide=True) def make_stock_entry(self): stock_entry = frappe.new_doc('Stock Entry') @@ -276,4 +283,55 @@ def get_current_healthcare_service_unit(inpatient_record): ip_record = frappe.get_doc('Inpatient Record', inpatient_record) if ip_record.inpatient_occupancies: return ip_record.inpatient_occupancies[-1].service_unit - return \ No newline at end of file + return + + +def get_drug_shortage_map(medication_orders, warehouse): + """ + Returns a dict like { drug_code: shortage_qty } + """ + drug_requirement = dict() + for d in medication_orders: + if not drug_requirement.get(d.drug_code): + drug_requirement[d.drug_code] = 0 + drug_requirement[d.drug_code] += flt(d.dosage) + + drug_shortage = dict() + for drug, required_qty in drug_requirement.items(): + available_qty = get_latest_stock_qty(drug, warehouse) + if flt(required_qty) > flt(available_qty): + drug_shortage[drug] = flt(flt(required_qty) - flt(available_qty)) + + return drug_shortage + + +@frappe.whitelist() +def make_difference_stock_entry(docname): + doc = frappe.get_doc('Inpatient Medication Entry', docname) + drug_shortage = get_drug_shortage_map(doc.medication_orders, doc.warehouse) + + if not drug_shortage: + return None + + stock_entry = frappe.new_doc('Stock Entry') + stock_entry.purpose = 'Material Transfer' + stock_entry.set_stock_entry_type() + stock_entry.to_warehouse = doc.warehouse + stock_entry.company = doc.company + cost_center = frappe.get_cached_value('Company', doc.company, 'cost_center') + expense_account = get_account(None, 'expense_account', 'Healthcare Settings', doc.company) + + for drug, shortage_qty in drug_shortage.items(): + se_child = stock_entry.append('items') + se_child.item_code = drug + se_child.item_name = frappe.db.get_value('Item', drug, 'stock_uom') + se_child.uom = frappe.db.get_value('Item', drug, 'stock_uom') + se_child.stock_uom = se_child.uom + se_child.qty = flt(shortage_qty) + se_child.t_warehouse = doc.warehouse + # in stock uom + se_child.conversion_factor = 1 + se_child.cost_center = cost_center + se_child.expense_account = expense_account + + return stock_entry From ac8ee249d544952b204e62fa40351652404ed9fd Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 26 Nov 2020 22:11:20 +0530 Subject: [PATCH 31/99] feat: Medication doctypes added to Desk page and Patient dashboard --- erpnext/healthcare/desk_page/healthcare/healthcare.json | 9 +++++++-- .../inpatient_medication_entry.py | 3 ++- erpnext/healthcare/doctype/patient/patient_dashboard.py | 4 ++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/erpnext/healthcare/desk_page/healthcare/healthcare.json b/erpnext/healthcare/desk_page/healthcare/healthcare.json index 81d60481ce..af601f3eb2 100644 --- a/erpnext/healthcare/desk_page/healthcare/healthcare.json +++ b/erpnext/healthcare/desk_page/healthcare/healthcare.json @@ -30,6 +30,11 @@ "label": "Laboratory", "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test\",\n\t\t\"label\": \"Lab Test\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sample Collection\",\n\t\t\"label\": \"Sample Collection\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Dosage Form\",\n\t\t\"label\": \"Dosage Form\"\n\t}\n]" }, + { + "hidden": 0, + "label": "Inpatient", + "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Medication Order\",\n\t\t\"label\": \"Inpatient Medication Order\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Medication Entry\",\n\t\t\"label\": \"Inpatient Medication Entry\"\n\t}\n]" + }, { "hidden": 0, "label": "Rehabilitation and Physiotherapy", @@ -38,7 +43,7 @@ { "hidden": 0, "label": "Records and History", - "links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]" + "links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t}\n]" }, { "hidden": 0, @@ -64,7 +69,7 @@ "idx": 0, "is_standard": 1, "label": "Healthcare", - "modified": "2020-11-23 23:00:48.764377", + "modified": "2020-11-26 22:09:09.164584", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare", diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py index 5a2a0e54aa..70ae713866 100644 --- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py +++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py @@ -230,7 +230,8 @@ def get_pending_medication_orders(entry): for doc in data: inpatient_record = doc.inpatient_record - doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record) + if inpatient_record: + doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record) if entry.service_unit and doc.service_unit != entry.service_unit: to_remove.append(doc) diff --git a/erpnext/healthcare/doctype/patient/patient_dashboard.py b/erpnext/healthcare/doctype/patient/patient_dashboard.py index e3def72334..39603f77a0 100644 --- a/erpnext/healthcare/doctype/patient/patient_dashboard.py +++ b/erpnext/healthcare/doctype/patient/patient_dashboard.py @@ -18,6 +18,10 @@ def get_data(): { 'label': _('Billing'), 'items': ['Sales Invoice'] + }, + { + 'label': _('Orders'), + 'items': ['Inpatient Medication Order'] } ] } From f5eddce407e46979aa279d709bae05d23417ade7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 27 Nov 2020 12:27:17 +0530 Subject: [PATCH 32/99] test: Inpatient Medication Entry Drug Shortage --- .../inpatient_medication_entry.js | 2 +- .../test_inpatient_medication_entry.py | 41 ++++++++++++++++--- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js index 3980370370..57af9eb848 100644 --- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js +++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js @@ -50,7 +50,7 @@ frappe.ui.form.on('Inpatient Medication Entry', { }); } } - }) + }); }); }, diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py b/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py index 2f1bb6b56f..7cb5a4814e 100644 --- a/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py +++ b/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py @@ -9,6 +9,7 @@ from frappe.utils import add_days, getdate, now_datetime from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import create_ipmo, create_ipme +from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_drug_shortage_map, make_difference_stock_entry from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account class TestInpatientMedicationEntry(unittest.TestCase): @@ -82,6 +83,39 @@ class TestInpatientMedicationEntry(unittest.TestCase): self.assertEqual(stock_entry.items[0].patient, self.patient) self.assertEqual(stock_entry.items[0].inpatient_medication_entry_child, ipme.medication_orders[0].name) + def test_drug_shortage_stock_entry(self): + ipmo = create_ipmo(self.patient) + ipmo.submit() + ipmo.reload() + + date = add_days(getdate(), -1) + filters = frappe._dict( + from_date=date, + to_date=date, + from_time='', + to_time='', + item_code='Dextromethorphan', + patient=self.patient + ) + + # check drug shortage + ipme = create_ipme(filters, update_stock=1) + ipme.warehouse = 'Finished Goods - _TC' + ipme.save() + drug_shortage = get_drug_shortage_map(ipme.medication_orders, ipme.warehouse) + self.assertEqual(drug_shortage.get('Dextromethorphan'), 3) + + # check material transfer for drug shortage + make_stock_entry() + stock_entry = make_difference_stock_entry(ipme.name) + self.assertEqual(stock_entry.items[0].item_code, 'Dextromethorphan') + self.assertEqual(stock_entry.items[0].qty, 3) + stock_entry.from_warehouse = 'Stores - _TC' + stock_entry.submit() + + ipme.reload() + ipme.submit() + def tearDown(self): # cleanup - Discharge schedule_discharge(frappe.as_json({'patient': self.patient})) @@ -94,15 +128,12 @@ class TestInpatientMedicationEntry(unittest.TestCase): for entry in frappe.get_all('Inpatient Medication Entry'): doc = frappe.get_doc('Inpatient Medication Entry', entry.name) doc.cancel() - frappe.db.delete('Stock Entry', {'inpatient_medication_entry': doc.name}) - doc.delete() for entry in frappe.get_all('Inpatient Medication Order'): doc = frappe.get_doc('Inpatient Medication Order', entry.name) doc.cancel() - doc.delete() -def make_stock_entry(): +def make_stock_entry(warehouse=None): frappe.db.set_value('Company', '_Test Company', { 'stock_adjustment_account': 'Stock Adjustment - _TC', 'default_inventory_account': 'Stock In Hand - _TC' @@ -110,7 +141,7 @@ def make_stock_entry(): stock_entry = frappe.new_doc('Stock Entry') stock_entry.stock_entry_type = 'Material Receipt' stock_entry.company = '_Test Company' - stock_entry.to_warehouse = 'Stores - _TC' + stock_entry.to_warehouse = warehouse or 'Stores - _TC' expense_account = get_account(None, 'expense_account', 'Healthcare Settings', '_Test Company') se_child = stock_entry.append('items') se_child.item_code = 'Dextromethorphan' From cf012ca9c3734acfe53d952bf55e61cf7223e413 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 27 Nov 2020 12:39:33 +0530 Subject: [PATCH 33/99] fix: show stock entry button only if update stock is enabled --- .../inpatient_medication_entry/inpatient_medication_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js index 57af9eb848..ca97489b8d 100644 --- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js +++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js @@ -30,7 +30,7 @@ frappe.ui.form.on('Inpatient Medication Entry', { }; }); - if (frm.doc.__islocal || frm.doc.docstatus !== 0) + if (frm.doc.__islocal || frm.doc.docstatus !== 0 || !frm.doc.update_stock) return; frm.add_custom_button(__('Make Stock Entry'), function() { From 452cbcd6eaa1d63538261ab91296b3cf2116ff79 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 30 Nov 2020 10:55:12 +0530 Subject: [PATCH 34/99] fix: Update payments directly in Loan Interest Accrual --- erpnext/patches/v13_0/update_old_loans.py | 40 ++++++++++++++--------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py index c4d9bdb7af..de29d329d1 100644 --- a/erpnext/patches/v13_0/update_old_loans.py +++ b/erpnext/patches/v13_0/update_old_loans.py @@ -5,6 +5,7 @@ from frappe.utils import nowdate from erpnext.accounts.doctype.account.test_account import create_account from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans from erpnext.loan_management.doctype.loan.loan import make_repayment_entry +from erpnext.loan_management.doctype.loan_repayment.loan_repayment import get_accrued_interest_entries from frappe.model.naming import make_autoname def execute(): @@ -100,25 +101,32 @@ def execute(): process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan_type, loan=loan.name) - payments = frappe.db.sql(''' SELECT j.name, a.credit, a.credit_in_account_currency, j.posting_date - FROM `tabJournal Entry` j, `tabJournal Entry Account` a - WHERE a.parent = j.name and a.reference_type='Loan' and a.reference_name = %s - and a.account = %s and j.docstatus = 1 - ''', (loan.name, loan.loan_account), as_dict=1) + accrued_entries = get_accrued_interest_entries(loan.name) + total_principal, total_interest = frappe.db.get_value('Repayment Schedule', fields=['sum(principal_amount) as total_principal', + 'sum(interest_amount) as total_interest'], filters={'is_paid': 1, 'parent': loan.name}) - for payment in payments: - if payment.credit_in_account_currency: - repayment_entry = make_repayment_entry(loan.name, loan.loan_applicant_type, loan.applicant, - loan_type, loan.company) + for entry in accrued_entries: + interest_paid = 0 + principal_paid = 0 - repayment_entry.amount_paid = payment.credit_in_account_currency - repayment_entry.posting_date = payment.posting_date - repayment_entry.save() - repayment_entry.submit() + if total_interest > entry.interest_amount: + interest_paid = entry.interest_amount + else: + interest_paid = total_interest - jv = frappe.get_doc('Journal Entry', payment.name) - jv.flags.ignore_links = True - jv.cancel() + if total_principal > entry.payable_principal_amount: + principal_paid = entry.payable_principal_amount + else: + principal_paid = total_principal + + frappe.db.sql(""" UPDATE `tabLoan Interest Accrual` + SET paid_principal_amount = `paid_principal_amount` + %s, + paid_interest_amount = `paid_interest_amount` + %s + WHERE name = %s""", + (principal_paid, interest_paid, entry.name)) + + total_principal -= principal_paid + total_interest -= interest_paid def create_loan_type(loan, loan_type_name, penalty_account): loan_type_doc = frappe.new_doc('Loan Type') From 724e16bca120954049ba4a9532ef25b6446ddf19 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 30 Nov 2020 12:42:25 +0530 Subject: [PATCH 35/99] chore: Use JSON style response and use ORM - Use JSON style response for report columns - Use ORM instead of frappe.db.sql - Remove returned % from list view --- .../delivered_items_to_be_billed.py | 100 +++++++++++++++--- .../received_items_to_be_billed.py | 100 +++++++++++++++--- .../controllers/sales_and_purchase_return.py | 43 +++----- .../doctype/delivery_note/delivery_note.json | 3 +- .../purchase_receipt/purchase_receipt.json | 3 +- .../purchase_receipt/purchase_receipt.py | 22 ++-- 6 files changed, 203 insertions(+), 68 deletions(-) diff --git a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py index 2aea3f6423..515fd995e6 100644 --- a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py +++ b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py @@ -14,19 +14,93 @@ def execute(filters=None): def get_column(): return [ - _("Delivery Note") + ":Link/Delivery Note:160", - _("Date") + ":Date:100", - _("Customer") + ":Link/Customer:120", - _("Customer Name") + "::120", - _("Item Code") + ":Link/Item:120", - _("Amount") + ":Currency:100", - _("Billed Amount") + ":Currency:100", - _("Returned Amount") + ":Currency:120", - _("Pending Amount") + ":Currency:100", - _("Item Name") + "::120", - _("Description") + "::120", - _("Project") + ":Link/Project:120", - _("Company") + ":Link/Company:120", + { + "label": _("Delivery Note"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Delivery Note", + "width": 160 + }, + { + "label": _("Date"), + "fieldname": "date", + "fieldtype": "Date", + "width": 100 + }, + { + "label": _("Customer"), + "fieldname": "customer", + "fieldtype": "Link", + "options": "Customer", + "width": 120 + }, + { + "label": _("Customer Name"), + "fieldname": "customer_name", + "fieldtype": "Data", + "width": 120 + }, + { + "label": _("Item Code"), + "fieldname": "item_code", + "fieldtype": "Link", + "options": "Item", + "width": 120 + }, + { + "label": _("Amount"), + "fieldname": "amount", + "fieldtype": "Currency", + "width": 100, + "options": "Company:company:default_currency" + }, + { + "label": _("Billed Amount"), + "fieldname": "billed_amount", + "fieldtype": "Currency", + "width": 100, + "options": "Company:company:default_currency" + }, + { + "label": _("Returned Amount"), + "fieldname": "returned_amount", + "fieldtype": "Currency", + "width": 120, + "options": "Company:company:default_currency" + }, + { + "label": _("Pending Amount"), + "fieldname": "pending_amount", + "fieldtype": "Currency", + "width": 120, + "options": "Company:company:default_currency" + }, + { + "label": _("Item Name"), + "fieldname": "item_name", + "fieldtype": "Data", + "width": 120 + }, + { + "label": _("Description"), + "fieldname": "description", + "fieldtype": "Data", + "width": 120 + }, + { + "label": _("Project"), + "fieldname": "project", + "fieldtype": "Link", + "options": "Project", + "width": 120 + }, + { + "label": _("Company"), + "fieldname": "company", + "fieldtype": "Link", + "options": "Company", + "width": 120 + } ] def get_args(): diff --git a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py index c7d4384a73..e9e9c9c4e6 100644 --- a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py +++ b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py @@ -14,19 +14,93 @@ def execute(filters=None): def get_column(): return [ - _("Purchase Receipt") + ":Link/Purchase Receipt:160", - _("Date") + ":Date:100", - _("Supplier") + ":Link/Supplier:120", - _("Supplier Name") + "::120", - _("Item Code") + ":Link/Item:120", - _("Amount") + ":Currency:100", - _("Billed Amount") + ":Currency:100", - _("Returned Amount") + ":Currency:120", - _("Pending Amount") + ":Currency:120", - _("Item Name") + "::120", - _("Description") + "::120", - _("Project") + ":Link/Project:120", - _("Company") + ":Link/Company:120", + { + "label": _("Purchase Receipt"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Purchase Receipt", + "width": 160 + }, + { + "label": _("Date"), + "fieldname": "date", + "fieldtype": "Date", + "width": 100 + }, + { + "label": _("Supplier"), + "fieldname": "supplier", + "fieldtype": "Link", + "options": "Supplier", + "width": 120 + }, + { + "label": _("Supplier Name"), + "fieldname": "supplier_name", + "fieldtype": "Data", + "width": 120 + }, + { + "label": _("Item Code"), + "fieldname": "item_code", + "fieldtype": "Link", + "options": "Item", + "width": 120 + }, + { + "label": _("Amount"), + "fieldname": "amount", + "fieldtype": "Currency", + "width": 100, + "options": "Company:company:default_currency" + }, + { + "label": _("Billed Amount"), + "fieldname": "billed_amount", + "fieldtype": "Currency", + "width": 100, + "options": "Company:company:default_currency" + }, + { + "label": _("Returned Amount"), + "fieldname": "returned_amount", + "fieldtype": "Currency", + "width": 120, + "options": "Company:company:default_currency" + }, + { + "label": _("Pending Amount"), + "fieldname": "pending_amount", + "fieldtype": "Currency", + "width": 120, + "options": "Company:company:default_currency" + }, + { + "label": _("Item Name"), + "fieldname": "item_name", + "fieldtype": "Data", + "width": 120 + }, + { + "label": _("Description"), + "fieldname": "description", + "fieldtype": "Data", + "width": 120 + }, + { + "label": _("Project"), + "fieldname": "project", + "fieldtype": "Link", + "options": "Project", + "width": 120 + }, + { + "label": _("Company"), + "fieldname": "company", + "fieldtype": "Link", + "options": "Company", + "width": 120 + } ] def get_args(): diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index e11289d79e..5299b25601 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -206,35 +206,26 @@ def get_already_returned_items(doc): def get_returned_qty_map_for_row(row_name, doctype): child_doctype = doctype + " Item" reference_field = frappe.scrub(child_doctype) if doctype == "Purchase Receipt" else "dn_detail" - reference_field = "child." + reference_field - columns = "" + + fields = [ + "sum(abs(`tab{0}`.qty)) as qty".format(child_doctype), + "sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype) + ] if doctype == "Purchase Receipt": - columns += ", sum(abs(child.rejected_qty)) as rejected_qty, \ - sum(abs(child.received_qty)) as received_qty, \ - sum(abs(child.received_stock_qty)) as received_stock_qty" + fields += [ + "sum(abs(`tab{0}`.rejected_qty)) as rejected_qty".format(child_doctype), + "sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype), + "sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype) + ] - data = frappe.db.sql(""" - select - sum(abs(child.qty)) as qty, - sum(abs(child.stock_qty)) as stock_qty, - %(columns)s - from - `tab{0}` child, `tab{1}` parent - where - child.parent = parent.name - and parent.docstatus = 1 - and parent.is_return = 1 - and {2} = %(row_name)s - """.format(child_doctype, doctype, reference_field), - { - "row_name": row_name, - "columns": columns, - "child_doctype": child_doctype, - "doctype": doctype, - "reference_field": reference_field - }, - as_dict=1) + data = frappe.db.get_list(doctype, + fields = fields, + filters = [ + [doctype, "docstatus", "=", 1], + [doctype, "is_return", "=", 1], + [child_doctype, reference_field, "=", row_name] + ]) return data[0] diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 111e3940b3..c9f8d0810e 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -1257,7 +1257,6 @@ "depends_on": "eval:!doc.__islocal", "fieldname": "per_returned", "fieldtype": "Percent", - "in_list_view": 1, "label": "% Returned", "no_copy": 1, "print_hide": 1, @@ -1268,7 +1267,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2020-11-19 11:22:09.056684", + "modified": "2020-11-30 12:54:45.407289", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 749b13121d..5bb3095708 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -1110,7 +1110,6 @@ "depends_on": "eval:!doc.__islocal", "fieldname": "per_returned", "fieldtype": "Percent", - "in_list_view": 1, "label": "% Returned", "no_copy": 1, "print_hide": 1, @@ -1121,7 +1120,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2020-11-19 11:21:25.465966", + "modified": "2020-11-30 12:54:23.278500", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 7e619bd59a..97e0fa738c 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -550,19 +550,17 @@ def update_billing_percentage(pr_doc, update_modified=True): # Update Billing % based on pending accepted qty total_amount, total_billed_amount = 0, 0 for item in pr_doc.items: - returned_qty = frappe.db.sql(""" - select sum(abs(child.qty)) as qty - from - `tabPurchase Receipt Item` child, - `tabPurchase Receipt` parent - where - child.parent = parent.name - and parent.docstatus = 1 - and parent.is_return = 1 - and child.purchase_receipt_item = %(row_name)s - """, {"row_name": item.name}) - returned_qty = returned_qty[0][0] if returned_qty else 0 + return_data = frappe.db.get_list("Purchase Receipt", + fields = [ + "sum(abs(`tabPurchase Receipt Item`.qty)) as qty" + ], + filters = [ + ["Purchase Receipt", "docstatus", "=", 1], + ["Purchase Receipt", "is_return", "=", 1], + ["Purchase Receipt Item", "purchase_receipt_item", "=", item.name] + ]) + returned_qty = return_data[0].qty if return_data else 0 returned_amount = flt(returned_qty) * flt(item.rate) pending_amount = flt(item.amount) - returned_amount total_billable_amount = pending_amount if item.billed_amt <= pending_amount else item.billed_amt From 19d5074c25646de3b0239eeadf56b50bb3eca667 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 30 Nov 2020 15:49:00 +0530 Subject: [PATCH 36/99] fix: get formatted value in 'taxes' print template --- erpnext/templates/print_formats/includes/taxes.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/templates/print_formats/includes/taxes.html b/erpnext/templates/print_formats/includes/taxes.html index 6e984f3901..304e845287 100644 --- a/erpnext/templates/print_formats/includes/taxes.html +++ b/erpnext/templates/print_formats/includes/taxes.html @@ -20,10 +20,10 @@ {%- if (charge.tax_amount or doc.flags.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
-
+ +
- {{ frappe.format_value(frappe.utils.flt(charge.tax_amount), - table_meta.get_field("tax_amount"), doc, currency=doc.currency) }} + {{ charge.get_formatted('tax_amount', doc) }}
{%- endif -%} From 2e27f074c3806f63f2ea4f9f70a43ba846c11346 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 1 Dec 2020 18:07:52 +0530 Subject: [PATCH 37/99] feat: reload doctype number card link --- erpnext/patches.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 61aa2eec59..29a7035b83 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -691,6 +691,7 @@ erpnext.patches.v13_0.update_old_loans erpnext.patches.v12_0.set_serial_no_status #2020-05-21 erpnext.patches.v12_0.update_price_list_currency_in_bom execute:frappe.reload_doctype('Dashboard') +execute:frappe.reload_doc('desk', 'doctype', 'number_card_link') execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts') erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25 From 9abc685504cc14a949fe922d343ff42d5f0f9a2a Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira Date: Wed, 2 Dec 2020 21:13:50 +0530 Subject: [PATCH 38/99] feat: add jinja templating in contract template --- erpnext/crm/doctype/contract/contract.js | 32 +++++++++++-------- erpnext/crm/doctype/contract/contract.json | 4 ++- .../contract_template/contract_template.py | 23 +++++++++++++ 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/erpnext/crm/doctype/contract/contract.js b/erpnext/crm/doctype/contract/contract.js index ee9e895130..6c0d739c89 100644 --- a/erpnext/crm/doctype/contract/contract.js +++ b/erpnext/crm/doctype/contract/contract.js @@ -1,23 +1,27 @@ // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -cur_frm.add_fetch("contract_template", "contract_terms", "contract_terms"); -cur_frm.add_fetch("contract_template", "requires_fulfilment", "requires_fulfilment"); - -// Add fulfilment terms from contract template into contract frappe.ui.form.on("Contract", { contract_template: function (frm) { - // Populate the fulfilment terms table from a contract template, if any if (frm.doc.contract_template) { - frappe.model.with_doc("Contract Template", frm.doc.contract_template, function () { - var tabletransfer = frappe.model.get_doc("Contract Template", frm.doc.contract_template); - - frm.doc.fulfilment_terms = []; - $.each(tabletransfer.fulfilment_terms, function (index, row) { - var d = frm.add_child("fulfilment_terms"); - d.requirement = row.requirement; - frm.refresh_field("fulfilment_terms"); - }); + frappe.call({ + method: 'erpnext.crm.doctype.contract_template.contract_template.get_contract_template', + args: { + template_name: frm.doc.contract_template, + doc: frm.doc + }, + callback: function(r) { + if(r && r.message){ + frm.set_value("contract_terms", r.message.contract_terms); + + // Populate the fulfilment terms table from a contract template, if any + r.message.contract_template.fulfilment_terms.forEach(element => { + let d = frm.add_child("fulfilment_terms"); + d.requirement = element.requirement; + }); + frm.refresh_field("fulfilment_terms"); + } + } }); } } diff --git a/erpnext/crm/doctype/contract/contract.json b/erpnext/crm/doctype/contract/contract.json index 0026e4a02e..fbc9f1c8d3 100755 --- a/erpnext/crm/doctype/contract/contract.json +++ b/erpnext/crm/doctype/contract/contract.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "allow_rename": 1, "creation": "2018-04-12 06:32:04.582486", @@ -175,6 +176,7 @@ }, { "default": "0", + "fetch_from": "contract_template.requires_fulfilment", "fieldname": "requires_fulfilment", "fieldtype": "Check", "label": "Requires Fulfilment" @@ -247,7 +249,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-03-30 06:56:07.257932", + "modified": "2020-12-02 21:12:44.118155", "modified_by": "Administrator", "module": "CRM", "name": "Contract", diff --git a/erpnext/crm/doctype/contract_template/contract_template.py b/erpnext/crm/doctype/contract_template/contract_template.py index 601ee9a28b..48ab8aad47 100644 --- a/erpnext/crm/doctype/contract_template/contract_template.py +++ b/erpnext/crm/doctype/contract_template/contract_template.py @@ -5,6 +5,29 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from frappe.utils.jinja import validate_template +from six import string_types +import json class ContractTemplate(Document): pass + + def validate(self): + if self.contract_terms: + validate_template(self.contract_terms) + +@frappe.whitelist() +def get_contract_template(template_name, doc): + if isinstance(doc, string_types): + doc = json.loads(doc) + + contract_template = frappe.get_doc("Contract Template", template_name) + contract_terms = None + + if contract_template.contract_terms: + contract_terms = frappe.render_template(contract_template.contract_terms, doc) + + return { + 'contract_template': contract_template, + 'contract_terms': contract_terms + } \ No newline at end of file From 131e46bab79c6f275fd0358ffbb0510a2bc2eef4 Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira Date: Wed, 2 Dec 2020 21:37:55 +0530 Subject: [PATCH 39/99] chore: add help section for Jinja --- .../contract_template/contract_template.json | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/contract_template/contract_template.json b/erpnext/crm/doctype/contract_template/contract_template.json index 5e4582f8d3..9fc24798cd 100644 --- a/erpnext/crm/doctype/contract_template/contract_template.json +++ b/erpnext/crm/doctype/contract_template/contract_template.json @@ -11,7 +11,9 @@ "contract_terms", "sb_fulfilment", "requires_fulfilment", - "fulfilment_terms" + "fulfilment_terms", + "section_break_6", + "contract_template_help" ], "fields": [ { @@ -41,10 +43,20 @@ "fieldtype": "Table", "label": "Fulfilment Terms and Conditions", "options": "Contract Template Fulfilment Terms" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "fieldname": "contract_template_help", + "fieldtype": "HTML", + "label": "Contract Template Help", + "options": "

Contract Template Example

\n\n
Contract for Customer {{ party_name }}\n\n-Valid From : {{ start_date }} \n-Valid To : {{ end_date }}\n
\n\n

How to get fieldnames

\n\n

The fieldnames you can use in your email template are the fields in the document from which you are sending the email. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Contract)

\n\n

Templating

\n\n

Templates are compiled using the Jinja Templating Language. To learn more about Jinja, read this documentation.

" } ], "links": [], - "modified": "2020-11-11 17:49:44.879363", + "modified": "2020-12-02 21:36:53.097074", "modified_by": "Administrator", "module": "CRM", "name": "Contract Template", From 8474476316095bc94286dd8dd8f6eb2f4decf651 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 24 Nov 2020 19:04:03 +0530 Subject: [PATCH 40/99] fix: incorrect balance value in stock balance report --- erpnext/stock/report/stock_balance/stock_balance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 1339d9b682..ccd01001bb 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -164,7 +164,7 @@ def get_stock_ledger_entries(filters, items): select sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate, sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference, - sle.item_code as name, sle.voucher_no + sle.item_code as name, sle.voucher_no, sle.stock_value from `tabStock Ledger Entry` sle force index (posting_sort_index) where sle.docstatus < 2 %s %s @@ -197,7 +197,7 @@ def get_item_warehouse_map(filters, sle): else: qty_diff = flt(d.actual_qty) - value_diff = flt(d.stock_value_difference) + value_diff = flt(d.stock_value) - flt(qty_dict.bal_val) if d.posting_date < from_date: qty_dict.opening_qty += qty_diff From 69c5d49b25002dffd8248df3f3916a9a3d1763ab Mon Sep 17 00:00:00 2001 From: Anuja P Date: Thu, 3 Dec 2020 15:14:54 +0530 Subject: [PATCH 41/99] refactor: to avoid using SQL query to get fiscal year filters --- .../accounts/doctype/fiscal_year/fiscal_year.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py index d80bc7fad1..8faf24d375 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py @@ -116,12 +116,8 @@ def auto_create_fiscal_year(): pass def get_from_and_to_date(fiscal_year): - from_and_to_date_tuple = frappe.db.sql("""select year_start_date, year_end_date - from `tabFiscal Year` where name=%s""", (fiscal_year))[0] - - from_and_to_date = { - "from_date": from_and_to_date_tuple[0], - "to_date": from_and_to_date_tuple[1] - } - - return from_and_to_date + fields = [ + "year_start_date as from_date", + "year_end_date as to_date" + ] + return frappe.db.get_value("Fiscal Year", fiscal_year, fields, as_dict=1) From 26cc662a7879ad1efdcd1d12e2a6c3ea27a93916 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Thu, 3 Dec 2020 17:45:34 +0530 Subject: [PATCH 42/99] feat: added filter for customer field --- erpnext/support/doctype/issue/issue.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index fe01d4b983..33191bedde 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -1,6 +1,13 @@ frappe.ui.form.on("Issue", { onload: function(frm) { frm.email_field = "raised_by"; + frm.set_query('customer', function () { + return { + filters: { + "disabled": 0 + } + } + }); frappe.db.get_value("Support Settings", {name: "Support Settings"}, ["allow_resetting_service_level_agreement", "track_service_level_agreement"], (r) => { @@ -145,6 +152,7 @@ frappe.ui.form.on("Issue", { reset_sla.show(); }, + timeline_refresh: function(frm) { // create button for "Help Article" if(frappe.model.can_create('Help Article')) { From 25e058833a7dfec25812d4fa5828d94ef61713a5 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Thu, 3 Dec 2020 18:13:24 +0530 Subject: [PATCH 43/99] fix: sider changes --- erpnext/support/doctype/issue/issue.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index 33191bedde..c20ad5c2fd 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -6,8 +6,8 @@ frappe.ui.form.on("Issue", { filters: { "disabled": 0 } - } - }); + }; + }); frappe.db.get_value("Support Settings", {name: "Support Settings"}, ["allow_resetting_service_level_agreement", "track_service_level_agreement"], (r) => { From 0139109de2ae31b2283712ee5ea555ed62afae77 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Fri, 4 Dec 2020 11:28:13 +0530 Subject: [PATCH 44/99] fix: consistency check --- erpnext/support/doctype/issue/issue.js | 32 +++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index c20ad5c2fd..521e671b0d 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -1,7 +1,7 @@ frappe.ui.form.on("Issue", { onload: function(frm) { frm.email_field = "raised_by"; - frm.set_query('customer', function () { + frm.set_query("customer", function () { return { filters: { "disabled": 0 @@ -28,14 +28,14 @@ frappe.ui.form.on("Issue", { }, callback: function (r) { if (r && r.message) { - frm.set_query('priority', function() { + frm.set_query("priority", function() { return { filters: { "name": ["in", r.message.priority], } }; }); - frm.set_query('service_level_agreement', function() { + frm.set_query("service_level_agreement", function() { return { filters: { "name": ["in", r.message.service_level_agreements], @@ -52,9 +52,9 @@ frappe.ui.form.on("Issue", { if (frm.doc.status !== "Closed" && frm.doc.agreement_status === "Ongoing") { if (frm.doc.service_level_agreement) { frappe.call({ - 'method': 'frappe.client.get', + "method": "frappe.client.get", args: { - doctype: 'Service Level Agreement', + doctype: "Service Level Agreement", name: frm.doc.service_level_agreement }, callback: function(data) { @@ -134,8 +134,8 @@ frappe.ui.form.on("Issue", { reset_sla.clear(); frappe.show_alert({ - indicator: 'green', - message: __('Resetting Service Level Agreement.') + indicator: "green", + message: __("Resetting Service Level Agreement.") }); frm.call("reset_service_level_agreement", { @@ -155,33 +155,33 @@ frappe.ui.form.on("Issue", { timeline_refresh: function(frm) { // create button for "Help Article" - if(frappe.model.can_create('Help Article')) { + if(frappe.model.can_create("Help Article")) { // Removing Help Article button if exists to avoid multiple occurance frm.timeline.wrapper.find('.comment-header .asset-details .btn-add-to-kb').remove(); $('') .appendTo(frm.timeline.wrapper.find('.comment-header .asset-details:not([data-communication-type="Comment"])')) - .on('click', function() { - var content = $(this).parents('.timeline-item:first').find('.timeline-item-content').html(); - var doc = frappe.model.get_new_doc('Help Article'); + .on("click", function() { + var content = $(this).parents(".timeline-item:first").find(".timeline-item-content").html(); + var doc = frappe.model.get_new_doc("Help Article"); doc.title = frm.doc.subject; doc.content = content; - frappe.set_route('Form', 'Help Article', doc.name); + frappe.set_route("Form", "Help Article", doc.name); }); } - if (!frm.timeline.wrapper.find('.btn-split-issue').length) { + if (!frm.timeline.wrapper.find(".btn-split-issue").length) { let split_issue = __("Split Issue") $(``) .appendTo(frm.timeline.wrapper.find('.comment-header .asset-details:not([data-communication-type="Comment"])')) if (!frm.timeline.wrapper.data("split-issue-event-attached")){ - frm.timeline.wrapper.on('click', '.btn-split-issue', (e) => { + frm.timeline.wrapper.on("click", ".btn-split-issue", (e) => { var dialog = new frappe.ui.Dialog({ title: __("Split Issue"), fields: [ - {fieldname: 'subject', fieldtype: 'Data', reqd:1, label: __('Subject'), description: __('All communications including and above this shall be moved into the new Issue')} + {fieldname: "subject", fieldtype: "Data", reqd:1, label: __("Subject"), description: __("All communications including and above this shall be moved into the new Issue")} ], primary_action_label: __("Split"), primary_action: function() { @@ -234,7 +234,7 @@ function set_time_to_resolve_and_response(frm) { function get_time_left(timestamp, agreement_status) { const diff = moment(timestamp).diff(moment()); const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : "Failed"; - let indicator = (diff_display == 'Failed' && agreement_status != "Fulfilled") ? "red" : "green"; + let indicator = (diff_display == "Failed" && agreement_status != "Fulfilled") ? "red" : "green"; return {"diff_display": diff_display, "indicator": indicator}; } From 28e86cf18312155ed020065b10152f77e8747d8f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 4 Dec 2020 00:47:46 +0530 Subject: [PATCH 45/99] fix: pricing rule with transaction not working for additional product --- .../doctype/pricing_rule/pricing_rule.json | 4 ++- .../doctype/pricing_rule/test_pricing_rule.py | 31 +++++++++++++++---- .../accounts/doctype/pricing_rule/utils.py | 11 ++++++- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index cc8ed4bc49..d08a854142 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -406,6 +406,7 @@ "fieldtype": "Column Break" }, { + "default": "0", "depends_on": "eval:doc.rate_or_discount==\"Rate\"", "fieldname": "rate", "fieldtype": "Currency", @@ -469,6 +470,7 @@ "options": "UOM" }, { + "description": "If rate is zero them item will be treated as \"Free Item\"", "fieldname": "free_item_rate", "fieldtype": "Currency", "label": "Rate" @@ -563,7 +565,7 @@ "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2020-10-28 16:53:14.416172", + "modified": "2020-12-04 00:36:24.698219", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index ec0a485bfc..af8d21d9ce 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -521,6 +521,22 @@ class TestPricingRule(unittest.TestCase): frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete() item.delete() + def test_pricing_rule_for_transaction(self): + make_item("Water Flask 1") + frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule') + make_pricing_rule(selling=1, min_qty=5, price_or_product_discount="Product", + apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10) + + si = create_sales_invoice(qty=5, do_not_submit=True) + self.assertEquals(len(si.items), 2) + self.assertEquals(si.items[1].rate, 10) + + si1 = create_sales_invoice(qty=2, do_not_submit=True) + self.assertEquals(len(si1.items), 1) + + for doc in [si, si1]: + doc.delete() + def make_pricing_rule(**args): args = frappe._dict(args) @@ -539,20 +555,23 @@ def make_pricing_rule(**args): "rate_or_discount": args.rate_or_discount or "Discount Percentage", "discount_percentage": args.discount_percentage or 0.0, "rate": args.rate or 0.0, - "margin_type": args.margin_type, "margin_rate_or_amount": args.margin_rate_or_amount or 0.0, "condition": args.condition or '', "apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0 }) - if args.get("priority"): - doc.priority = args.get("priority") + for field in ["free_item", "free_qty", "free_item_rate", "priority", + "margin_type", "price_or_product_discount"]: + if args.get(field): + doc.set(field, args.get(field)) apply_on = doc.apply_on.replace(' ', '_').lower() child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'} - doc.append(child_table.get(doc.apply_on), { - apply_on: args.get(apply_on) or "_Test Item" - }) + + if doc.apply_on != "Transaction": + doc.append(child_table.get(doc.apply_on), { + apply_on: args.get(apply_on) or "_Test Item" + }) doc.insert(ignore_permissions=True) if args.get(apply_on) and apply_on != "item_code": diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index b003328cc4..2c7cd14451 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -457,6 +457,9 @@ def apply_pricing_rule_on_transaction(doc): pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty, doc.total, pricing_rules) + if not pricing_rules: + remove_free_item(doc) + for d in pricing_rules: if d.price_or_product_discount == 'Price': if d.apply_discount_on: @@ -480,6 +483,12 @@ def apply_pricing_rule_on_transaction(doc): get_product_discount_rule(d, item_details, doc=doc) apply_pricing_rule_for_free_items(doc, item_details.free_item_data) doc.set_missing_values() + doc.calculate_taxes_and_totals() + +def remove_free_item(doc): + for d in doc.items: + if d.is_free_item: + doc.remove(d) def get_applied_pricing_rules(pricing_rules): if pricing_rules: @@ -492,7 +501,7 @@ def get_applied_pricing_rules(pricing_rules): def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): free_item = pricing_rule.free_item - if pricing_rule.same_item: + if pricing_rule.same_item and pricing_rule.get("apply_on") != 'Transaction': free_item = item_details.item_code or args.item_code if not free_item: From edee530d4cba4d6395e5a6f680fa5c33d962b175 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 4 Dec 2020 12:13:26 +0530 Subject: [PATCH 46/99] fix: paid amount in Sales Invoice POS return resets to 0 --- erpnext/controllers/taxes_and_totals.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 81d07c1327..ad58f137ee 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -641,7 +641,8 @@ class calculate_taxes_and_totals(object): if default_mode_of_payment: self.doc.append('payments', { 'mode_of_payment': default_mode_of_payment.mode_of_payment, - 'amount': total_amount_to_pay + 'amount': total_amount_to_pay, + 'default': 1 }) else: self.doc.is_pos = 0 From 6a2431586ec82290ffad6c24d30cd4e5f777a7a4 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 4 Dec 2020 13:31:36 +0530 Subject: [PATCH 47/99] fix: Make new Customers for account missing test and set company --- .../test_opening_invoice_creation_tool.py | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py index 329d84bdb7..bdfe532b9f 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py @@ -11,15 +11,20 @@ from frappe.custom.doctype.property_setter.property_setter import make_property_ from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account class TestOpeningInvoiceCreationTool(unittest.TestCase): - def make_invoices(self, invoice_type="Sales", company=None): + def setUp(self): + if not frappe.db.exists("Company", "_Test Opening Invoice Company"): + make_company() + + def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None): doc = frappe.get_single("Opening Invoice Creation Tool") - args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company) + args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company, + party_1=party_1, party_2=party_2) doc.update(args) return doc.make_invoices() def test_opening_sales_invoice_creation(self): property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check") - invoices = self.make_invoices() + invoices = self.make_invoices(company="_Test Opening Invoice Company") self.assertEqual(len(invoices), 2) expected_value = { @@ -45,7 +50,7 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase): self.assertEqual(si.get(field, ""), expected_value[invoice_idx][field_idx]) def test_opening_purchase_invoice_creation(self): - invoices = self.make_invoices(invoice_type="Purchase") + invoices = self.make_invoices(invoice_type="Purchase", company="_Test Opening Invoice Company") self.assertEqual(len(invoices), 2) expected_value = { @@ -56,9 +61,11 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase): self.check_expected_values(invoices, expected_value, "Purchase") def test_opening_sales_invoice_creation_with_missing_debit_account(self): - company = make_company() - old_default_receivable_account = frappe.db.get_value("Company", company.name, "default_receivable_account") - frappe.db.set_value("Company", company.name, "default_receivable_account", "") + company = "_Test Opening Invoice Company" + party_1, party_2 = make_customer("Customer A"), make_customer("Customer B") + + old_default_receivable_account = frappe.db.get_value("Company", company, "default_receivable_account") + frappe.db.set_value("Company", company, "default_receivable_account", "") if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"): cc = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "_Test Opening Invoice Company", @@ -68,18 +75,16 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase): "company": "_Test Opening Invoice Company", "parent_cost_center": cc.name}) cc2.insert() - frappe.db.set_value("Company", company.name, "cost_center", "Main - _TOIC") + frappe.db.set_value("Company", company, "cost_center", "Main - _TOIC") - self.make_invoices(company="_Test Opening Invoice Company") + self.make_invoices(company="_Test Opening Invoice Company", party_1=party_1, party_2=party_2) # Check if missing debit account error raised error_log = frappe.db.exists("Error Log", {"error": ["like", "%erpnext.controllers.accounts_controller.AccountMissingError%"]}) self.assertTrue(error_log) # teardown - frappe.db.set_value("Company", company.name, "default_receivable_account", old_default_receivable_account) - company.delete() - frappe.get_doc("Error Log", error_log).delete() + frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account) def get_opening_invoice_creation_dict(**args): party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier" @@ -92,7 +97,7 @@ def get_opening_invoice_creation_dict(**args): { "qty": 1.0, "outstanding_amount": 300, - "party": "_Test {0}".format(party), + "party": args.get("party_1") or "_Test {0}".format(party), "item_name": "Opening Item", "due_date": "2016-09-10", "posting_date": "2016-09-05", @@ -101,7 +106,7 @@ def get_opening_invoice_creation_dict(**args): { "qty": 2.0, "outstanding_amount": 250, - "party": "_Test {0} 1".format(party), + "party": args.get("party_2") or "_Test {0} 1".format(party), "item_name": "Opening Item", "due_date": "2016-09-10", "posting_date": "2016-09-05", @@ -123,4 +128,19 @@ def make_company(): company.default_currency = "INR" company.country = "India" company.insert() - return company \ No newline at end of file + return company + +def make_customer(customer=None): + customer_name = customer or "Opening Customer" + customer = frappe.get_doc({ + "doctype": "Customer", + "customer_name": customer_name, + "customer_group": "All Customer Groups", + "customer_type": "Company", + "territory": "All Territories" + }) + if not frappe.db.exists("Customer", customer_name): + customer.insert(ignore_permissions=True) + return customer.name + else: + return frappe.db.exists("Customer", customer_name) \ No newline at end of file From 931f2e73a7a741a695072fe598f284d4b22b435b Mon Sep 17 00:00:00 2001 From: Anuja P Date: Fri, 4 Dec 2020 14:57:23 +0530 Subject: [PATCH 48/99] fix: sider changes --- erpnext/support/doctype/issue/issue.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index 521e671b0d..086755be51 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -155,7 +155,7 @@ frappe.ui.form.on("Issue", { timeline_refresh: function(frm) { // create button for "Help Article" - if(frappe.model.can_create("Help Article")) { + if (frappe.model.can_create("Help Article")) { // Removing Help Article button if exists to avoid multiple occurance frm.timeline.wrapper.find('.comment-header .asset-details .btn-add-to-kb').remove(); $('