From 77b1eedcf491e6cecd64613a243fb9bce661b760 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 20:11:19 +0530 Subject: [PATCH 01/31] chore: refetch item images on transaction save (backport #38095) (#38099) chore: refetch item images on transaction save (#38095) chore: re fetch item images on transaction save (cherry picked from commit e93a19ffb5d45a3bf0136ca3fc24a828b008aeee) Co-authored-by: Anand Baburajan --- .../doctype/pos_invoice_item/pos_invoice_item.json | 3 ++- .../purchase_invoice_item/purchase_invoice_item.json | 3 ++- .../doctype/sales_invoice_item/sales_invoice_item.json | 5 +++-- .../purchase_order_item/purchase_order_item.json | 3 ++- .../request_for_quotation_item.json | 7 +++++-- .../supplier_quotation_item.json | 7 +++++-- .../crm/doctype/opportunity_item/opportunity_item.json | 4 +++- .../doctype/bom_explosion_item/bom_explosion_item.json | 3 ++- erpnext/manufacturing/doctype/bom_item/bom_item.json | 3 ++- .../selling/doctype/quotation_item/quotation_item.json | 5 +++-- .../doctype/sales_order_item/sales_order_item.json | 10 ++-------- .../doctype/delivery_note_item/delivery_note_item.json | 3 ++- .../material_request_item/material_request_item.json | 3 ++- .../purchase_receipt_item/purchase_receipt_item.json | 3 ++- .../subcontracting_order_item.json | 3 ++- .../subcontracting_receipt_item.json | 3 ++- 16 files changed, 41 insertions(+), 27 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json index cb0ed3d6aa..5a281aaa4f 100644 --- a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json +++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json @@ -186,6 +186,7 @@ "label": "Image" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -833,7 +834,7 @@ ], "istable": 1, "links": [], - "modified": "2023-03-12 13:36:40.160468", + "modified": "2023-11-14 18:33:22.585715", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice Item", diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 424e942990..bcedb7c943 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -158,6 +158,7 @@ "width": "300px" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -915,7 +916,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-10-03 21:01:01.824892", + "modified": "2023-11-14 18:33:48.547297", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index 5d2764b669..a403b14c54 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -167,6 +167,7 @@ "print_hide": 1 }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -901,7 +902,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-07-26 12:53:22.404057", + "modified": "2023-11-14 18:34:10.479329", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", @@ -911,4 +912,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} +} \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 2b6ffb752f..2d706f41e5 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -189,6 +189,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -916,7 +917,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-06 11:00:53.596417", + "modified": "2023-11-14 18:34:27.267382", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json index 82fcfa2713..6cdd2bac0d 100644 --- a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json +++ b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json @@ -87,6 +87,7 @@ "width": "300px" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -260,13 +261,15 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-09-24 17:26:46.276934", + "modified": "2023-11-14 18:34:48.327224", "modified_by": "Administrator", "module": "Buying", "name": "Request for Quotation Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json index 8d491fbc84..4bbcacfef3 100644 --- a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json +++ b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json @@ -133,6 +133,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -559,13 +560,15 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-10-19 12:36:26.913211", + "modified": "2023-11-14 18:35:03.435817", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/crm/doctype/opportunity_item/opportunity_item.json b/erpnext/crm/doctype/opportunity_item/opportunity_item.json index 1b4973c1b2..732f80d01c 100644 --- a/erpnext/crm/doctype/opportunity_item/opportunity_item.json +++ b/erpnext/crm/doctype/opportunity_item/opportunity_item.json @@ -103,6 +103,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -165,7 +166,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-07-30 16:39:09.775720", + "modified": "2023-11-14 18:35:30.887278", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity Item", @@ -173,5 +174,6 @@ "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json index 9b1db63494..c75ac32cd1 100644 --- a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json +++ b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json @@ -85,6 +85,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -169,7 +170,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-05-27 13:42:23.305455", + "modified": "2023-11-14 18:35:40.856895", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Explosion Item", diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json index c5266119dc..cb58af1f29 100644 --- a/erpnext/manufacturing/doctype/bom_item/bom_item.json +++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json @@ -111,6 +111,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -289,7 +290,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-07-28 10:20:51.559010", + "modified": "2023-11-14 18:35:51.378513", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Item", diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index 5016f1f1fd..0e25313f76 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -135,6 +135,7 @@ "width": "300px" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -666,7 +667,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-09-26 13:42:11.410294", + "modified": "2023-11-14 18:24:24.619832", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Item", @@ -676,4 +677,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index f82047f511..b4f73003ae 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -68,7 +68,6 @@ "total_weight", "column_break_21", "weight_uom", - "accounting_dimensions_section", "warehouse_and_reference", "warehouse", "target_warehouse", @@ -177,6 +176,7 @@ "print_hide": 1 }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -890,18 +890,12 @@ "label": "Production Plan Qty", "no_copy": 1, "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "accounting_dimensions_section", - "fieldtype": "Section Break", - "label": "Accounting Dimensions" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2023-10-17 18:18:26.475259", + "modified": "2023-11-14 18:37:12.787893", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", 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 6148950462..a44b9ac44b 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -168,6 +168,7 @@ "width": "300px" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -893,7 +894,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-10-16 16:18:18.013379", + "modified": "2023-11-14 18:37:38.638144", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json index 9912be145f..5dc07c99f6 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -110,6 +110,7 @@ "width": "250px" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach Image", "label": "Image", @@ -478,7 +479,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-10-27 15:53:41.444236", + "modified": "2023-11-14 18:37:59.599115", "modified_by": "Administrator", "module": "Stock", "name": "Material Request Item", 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 718f007577..ce2e5d7f84 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -192,6 +192,7 @@ "width": "300px" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -1090,7 +1091,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-10-30 17:32:24.560337", + "modified": "2023-11-14 18:38:15.251994", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json index d77e77440e..46c229bfd3 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json @@ -112,6 +112,7 @@ "fieldtype": "Column Break" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -337,7 +338,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-01-20 23:25:45.363281", + "modified": "2023-11-14 18:38:37.640677", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Order Item", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json index 38432beb44..26a29dd811 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -109,6 +109,7 @@ "width": "300px" }, { + "fetch_from": "item_code.image", "fieldname": "image", "fieldtype": "Attach", "hidden": 1, @@ -521,7 +522,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2023-09-03 17:04:21.214316", + "modified": "2023-11-14 18:38:26.459669", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt Item", From 28e6e5d9106b78a9cba29946cd4a30b68ff2c0f2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:15:44 +0530 Subject: [PATCH 02/31] fix: valuation rate for the subcontracting receipt supplied items with Serial and Batch Bundle (backport #38094) (#38097) fix: valuation rate for the subcontracting receipt supplied items with Serial and Batch Bundle (#38094) fix: valuation rate for the subcontracting receipt supplied items with batch (cherry picked from commit 3e77c0b5644c28ed9b4eef22b228fdc4a1283020) Co-authored-by: rohitwaghchaure --- .../controllers/subcontracting_controller.py | 13 +++ .../stock_ledger_entry.json | 10 +- erpnext/stock/serial_batch_bundle.py | 10 +- erpnext/stock/stock_ledger.py | 4 +- .../subcontracting_receipt.js | 10 ++ .../test_subcontracting_receipt.py | 91 +++++++++++++++++++ 6 files changed, 135 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 5fa66b1a87..3d55a087bd 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -626,6 +626,18 @@ class SubcontractingController(StockController): (row.item_code, row.get(self.subcontract_data.order_field)) ] -= row.qty + def __set_rate_for_serial_and_batch_bundle(self): + if self.doctype != "Subcontracting Receipt": + return + + for row in self.get(self.raw_material_table): + if not row.get("serial_and_batch_bundle"): + continue + + row.rate = frappe.get_cached_value( + "Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate" + ) + def __modify_serial_and_batch_bundle(self): if self.is_new(): return @@ -681,6 +693,7 @@ class SubcontractingController(StockController): self.__remove_changed_rows() self.__set_supplied_items() self.__modify_serial_and_batch_bundle() + self.__set_rate_for_serial_and_batch_bundle() def __validate_batch_no(self, row, key): if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get( diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index dcbd9b2d06..be379940ca 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -12,6 +12,7 @@ "posting_date", "posting_time", "is_adjustment_entry", + "auto_created_serial_and_batch_bundle", "column_break_6", "voucher_type", "voucher_no", @@ -340,6 +341,13 @@ "fieldname": "is_adjustment_entry", "fieldtype": "Check", "label": "Is Adjustment Entry" + }, + { + "default": "0", + "depends_on": "serial_and_batch_bundle", + "fieldname": "auto_created_serial_and_batch_bundle", + "fieldtype": "Check", + "label": "Auto Created Serial and Batch Bundle" } ], "hide_toolbar": 1, @@ -348,7 +356,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-10-23 18:07:42.063615", + "modified": "2023-11-14 16:47:39.791967", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 5998274bb7..da98455b5c 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -129,7 +129,9 @@ class SerialBatchBundle: frappe.throw(_(error_msg)) def set_serial_and_batch_bundle(self, sn_doc): - self.sle.db_set("serial_and_batch_bundle", sn_doc.name) + self.sle.db_set( + {"serial_and_batch_bundle": sn_doc.name, "auto_created_serial_and_batch_bundle": 1} + ) if sn_doc.is_rejected: frappe.db.set_value( @@ -143,6 +145,12 @@ class SerialBatchBundle: @property def child_doctype(self): child_doctype = self.sle.voucher_type + " Item" + + if ( + self.sle.voucher_type == "Subcontracting Receipt" and self.sle.dependant_sle_voucher_detail_no + ): + child_doctype = "Subcontracting Receipt Supplied Item" + if self.sle.voucher_type == "Stock Entry": child_doctype = "Stock Entry Detail" diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 000b295039..a53180442f 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -759,7 +759,9 @@ class update_entries_after(object): sle.doctype = "Stock Ledger Entry" frappe.get_doc(sle).db_update() - if not self.args.get("sle_id"): + if not self.args.get("sle_id") or ( + sle.serial_and_batch_bundle and sle.auto_created_serial_and_batch_bundle + ): self.update_outgoing_rate_on_transaction(sle) def reset_actual_qty_for_stock_reco(self, sle): diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 19a1c939c3..36001eb78f 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -13,6 +13,16 @@ frappe.ui.form.on('Subcontracting Receipt', { frm.trigger('set_queries'); }, + on_submit(frm) { + frm.events.refresh_serial_batch_bundle_field(frm); + }, + + refresh_serial_batch_bundle_field(frm) { + frappe.route_hooks.after_submit = (frm_obj) => { + frm_obj.reload_doc(); + } + }, + refresh: (frm) => { if (frm.doc.docstatus > 0) { frm.add_custom_button(__('Stock Ledger'), () => { diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 96babf2b32..6191a8ca94 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -576,6 +576,97 @@ class TestSubcontractingReceipt(FrappeTestCase): self.assertEqual(scr.items[0].rm_cost_per_qty, 300) self.assertEqual(scr.items[0].service_cost_per_qty, 100) + def test_subcontracting_receipt_valuation_with_auto_created_serial_batch_bundle(self): + set_backflush_based_on("BOM") + + fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name + rm_item1 = make_item( + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "BNGS-.####", + } + ).name + + rm_item2 = make_item( + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "has_serial_no": 1, + "create_new_batch": 1, + "batch_number_series": "BNGS-.####", + "serial_no_series": "BNSS-.####", + } + ).name + + rm_item3 = make_item( + properties={ + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": "BSSSS-.####", + } + ).name + + bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2, rm_item3]) + + rm_batch_no = None + for row in bom.items: + make_stock_entry( + item_code=row.item_code, + qty=1, + target="_Test Warehouse 1 - _TC", + rate=300, + ) + + make_stock_entry( + item_code=row.item_code, + qty=1, + target="_Test Warehouse 1 - _TC", + rate=400, + ) + + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 1, + "rate": 100, + "fg_item": fg_item, + "fg_item_qty": 1, + }, + ] + sco = get_subcontracting_order(service_items=service_items) + + frappe.db.set_single_value( + "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1 + ) + scr = make_subcontracting_receipt(sco.name) + scr.save() + for row in scr.supplied_items: + self.assertNotEqual(row.rate, 300.00) + self.assertFalse(row.serial_and_batch_bundle) + + scr.submit() + scr.reload() + + for row in scr.supplied_items: + self.assertEqual(row.rate, 300.00) + self.assertTrue(row.serial_and_batch_bundle) + auto_created_serial_batch = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_no": scr.name, "voucher_detail_no": row.name}, + "auto_created_serial_and_batch_bundle", + ) + + self.assertTrue(auto_created_serial_batch) + + self.assertEqual(scr.items[0].rm_cost_per_qty, 900) + self.assertEqual(scr.items[0].service_cost_per_qty, 100) + frappe.db.set_single_value( + "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0 + ) + def test_subcontracting_receipt_raw_material_rate(self): # Step - 1: Set Backflush Based On as "BOM" set_backflush_based_on("BOM") From 8bc871a84290470529b239a2657db42b896fbb1c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 15 Nov 2023 18:01:33 +0530 Subject: [PATCH 03/31] chore: change read only condition of asset quantity field (backport #38111) (#38113) chore: change read only condition of asset quantity field (#38111) chore: change read only condition of asset quantity (cherry picked from commit 6e0362dee85811267edff53f23a24b48ebd91c11) Co-authored-by: Anand Baburajan --- erpnext/assets/doctype/asset/asset.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 40f51ab570..d6b9c461cb 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -481,11 +481,10 @@ "read_only": 1 }, { - "depends_on": "eval.doc.asset_quantity", "fieldname": "asset_quantity", "fieldtype": "Int", "label": "Asset Quantity", - "read_only": 1 + "read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset" }, { "fieldname": "depr_entry_posting_status", @@ -572,7 +571,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2023-10-27 17:03:46.629617", + "modified": "2023-11-15 17:40:17.315203", "modified_by": "Administrator", "module": "Assets", "name": "Asset", From e2dd414793d357272494f0a7f03d1d0c1e6452b0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 14 Nov 2023 06:44:49 +0530 Subject: [PATCH 04/31] refactor: use 'boolean' parameter while fetching FY year (cherry picked from commit c31ee8ea33afcc8128270f098c40cc0b0e3f1a49) --- erpnext/accounts/utils.py | 3 +++ erpnext/public/js/financial_statements.js | 10 ++++++++-- erpnext/public/js/utils.js | 5 +++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 8ae6c0e082..4abd04b6c2 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -53,6 +53,9 @@ GL_REPOSTING_CHUNK = 100 def get_fiscal_year( date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False ): + if isinstance(boolean, str): + boolean = frappe.json.loads(boolean) + fiscal_years = get_fiscal_years( date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean ) diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 907a775bfa..1b10d8ad3a 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -139,7 +139,6 @@ function get_filters() { "label": __("Start Year"), "fieldtype": "Link", "options": "Fiscal Year", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()), "reqd": 1, "depends_on": "eval:doc.filter_based_on == 'Fiscal Year'" }, @@ -148,7 +147,6 @@ function get_filters() { "label": __("End Year"), "fieldtype": "Link", "options": "Fiscal Year", - "default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()), "reqd": 1, "depends_on": "eval:doc.filter_based_on == 'Fiscal Year'" }, @@ -197,5 +195,13 @@ function get_filters() { } ] + // Dynamically set 'default' values for fiscal year filters + let fy_filters = filters.filter(x=>{return ["from_fiscal_year", "to_fiscal_year"].includes(x.fieldname);}) + let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), false, true); + if (fiscal_year) { + let fy = erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), false, false); + fy_filters.forEach(x=>{x.default = fy;}) + } + return filters; } diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index d435711cf5..25fc754b9a 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -404,7 +404,7 @@ $.extend(erpnext.utils, { }); }, - get_fiscal_year: function(date, with_dates=false) { + get_fiscal_year: function(date, with_dates=false, boolean=false) { if(!date) { date = frappe.datetime.get_today(); } @@ -413,7 +413,8 @@ $.extend(erpnext.utils, { frappe.call({ method: "erpnext.accounts.utils.get_fiscal_year", args: { - date: date + date: date, + boolean: boolean }, async: false, callback: function(r) { From 9a9d5775e88ac7cadd8279a1bf03d1cdb4957fb6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 15:26:52 +0530 Subject: [PATCH 05/31] chore: remove 'Bulk Transaction Log' doctype (cherry picked from commit 815c616f18ae1cd6ec1dbd315fa5f0376edef523) --- .../doctype/bulk_transaction_log/__init__.py | 0 .../bulk_transaction_log.js | 30 ------- .../bulk_transaction_log.json | 51 ------------ .../bulk_transaction_log.py | 67 ---------------- .../test_bulk_transaction_log.py | 79 ------------------- 5 files changed, 227 deletions(-) delete mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/__init__.py delete mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js delete mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json delete mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py delete mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/__init__.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js deleted file mode 100644 index 0073170a85..0000000000 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Bulk Transaction Log', { - - refresh: function(frm) { - frm.disable_save(); - frm.add_custom_button(__('Retry Failed Transactions'), ()=>{ - frappe.confirm(__("Retry Failing Transactions ?"), ()=>{ - query(frm, 1); - } - ); - }); - } -}); - -function query(frm) { - frappe.call({ - method: "erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction", - args: { - log_date: frm.doc.log_date - } - }).then((r) => { - if (r.message === "No Failed Records") { - frappe.show_alert(__(r.message), 5); - } else { - frappe.show_alert(__("Retrying Failed Transactions"), 5); - } - }); -} \ No newline at end of file diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json deleted file mode 100644 index da42cf1bd4..0000000000 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "actions": [], - "allow_rename": 1, - "creation": "2021-11-30 13:41:16.343827", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "log_date", - "logger_data" - ], - "fields": [ - { - "fieldname": "log_date", - "fieldtype": "Date", - "label": "Log Date", - "read_only": 1 - }, - { - "fieldname": "logger_data", - "fieldtype": "Table", - "label": "Logger Data", - "options": "Bulk Transaction Log Detail" - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2022-02-03 17:23:02.935325", - "modified_by": "Administrator", - "module": "Bulk Transaction", - "name": "Bulk Transaction Log", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py deleted file mode 100644 index 0596be4462..0000000000 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from datetime import date - -import frappe -from frappe.model.document import Document - -from erpnext.utilities.bulk_transaction import task, update_logger - - -class BulkTransactionLog(Document): - pass - - -@frappe.whitelist() -def retry_failing_transaction(log_date=None): - if not log_date: - log_date = str(date.today()) - btp = frappe.qb.DocType("Bulk Transaction Log Detail") - data = ( - frappe.qb.from_(btp) - .select(btp.transaction_name, btp.from_doctype, btp.to_doctype) - .distinct() - .where(btp.retried != 1) - .where(btp.transaction_status == "Failed") - .where(btp.date == log_date) - ).run(as_dict=True) - - if data: - if len(data) > 10: - frappe.enqueue(job, queue="long", job_name="bulk_retry", data=data, log_date=log_date) - else: - job(data, log_date) - else: - return "No Failed Records" - - -def job(data, log_date): - for d in data: - failed = [] - try: - frappe.db.savepoint("before_creation_of_record") - task(d.transaction_name, d.from_doctype, d.to_doctype) - except Exception as e: - frappe.db.rollback(save_point="before_creation_of_record") - failed.append(e) - update_logger( - d.transaction_name, - e, - d.from_doctype, - d.to_doctype, - status="Failed", - log_date=log_date, - restarted=1, - ) - - if not failed: - update_logger( - d.transaction_name, - None, - d.from_doctype, - d.to_doctype, - status="Success", - log_date=log_date, - restarted=1, - ) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py deleted file mode 100644 index c673be89b3..0000000000 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest -from datetime import date - -import frappe - -from erpnext.utilities.bulk_transaction import transaction_processing - - -class TestBulkTransactionLog(unittest.TestCase): - def setUp(self): - create_company() - create_customer() - create_item() - - def test_entry_in_log(self): - so_name = create_so() - transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice") - doc = frappe.get_doc("Bulk Transaction Log", str(date.today())) - for d in doc.get("logger_data"): - if d.transaction_name == so_name: - self.assertEqual(d.transaction_name, so_name) - self.assertEqual(d.transaction_status, "Success") - self.assertEqual(d.from_doctype, "Sales Order") - self.assertEqual(d.to_doctype, "Sales Invoice") - self.assertEqual(d.retried, 0) - - -def create_company(): - if not frappe.db.exists("Company", "_Test Company"): - frappe.get_doc( - { - "doctype": "Company", - "company_name": "_Test Company", - "country": "India", - "default_currency": "INR", - } - ).insert() - - -def create_customer(): - if not frappe.db.exists("Customer", "Bulk Customer"): - frappe.get_doc({"doctype": "Customer", "customer_name": "Bulk Customer"}).insert() - - -def create_item(): - if not frappe.db.exists("Item", "MK"): - frappe.get_doc( - { - "doctype": "Item", - "item_code": "MK", - "item_name": "Milk", - "description": "Milk", - "item_group": "Products", - } - ).insert() - - -def create_so(intent=None): - so = frappe.new_doc("Sales Order") - so.customer = "Bulk Customer" - so.company = "_Test Company" - so.transaction_date = date.today() - - so.set_warehouse = "Finished Goods - _TC" - so.append( - "items", - { - "item_code": "MK", - "delivery_date": date.today(), - "qty": 10, - "rate": 80, - }, - ) - so.insert() - so.submit() - return so.name From bf0d8c16ace6306c9ab543372c87087ad114acf3 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 15:29:57 +0530 Subject: [PATCH 06/31] chore: convert child to normal table (cherry picked from commit e5a8ad54e26d74b5713e7db057860c1262c1b93b) --- .../bulk_transaction_log_detail.js | 8 ++++++++ .../bulk_transaction_log_detail.json | 18 +++++++++++++++--- .../test_bulk_transaction_log_detail.py | 9 +++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.js create mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.js new file mode 100644 index 0000000000..5669601d11 --- /dev/null +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Bulk Transaction Log Detail", { +// refresh(frm) { + +// }, +// }); diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json index 8262caa020..64518de4c0 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json @@ -71,14 +71,26 @@ } ], "index_web_pages_for_search": 1, - "istable": 1, "links": [], - "modified": "2022-02-03 19:57:31.650359", + "modified": "2023-11-09 15:29:33.013547", "modified_by": "Administrator", "module": "Bulk Transaction", "name": "Bulk Transaction Log Detail", "owner": "Administrator", - "permissions": [], + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], "sort_field": "modified", "sort_order": "DESC", "states": [], diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py new file mode 100644 index 0000000000..5217b601f8 --- /dev/null +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestBulkTransactionLogDetail(FrappeTestCase): + pass From 3be8bfe9d81b0949088b66fddc4ccfbfd79d1dcd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 16:07:45 +0530 Subject: [PATCH 07/31] chore: rearrange fields (cherry picked from commit c4f8f3613f1173a54b17e3c6ca8ea52469f32e3b) --- .../bulk_transaction_log_detail.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json index 64518de4c0..051dc0af02 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json @@ -6,12 +6,12 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "from_doctype", "transaction_name", "date", "time", "transaction_status", "error_description", - "from_doctype", "to_doctype", "retried" ], @@ -72,7 +72,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-11-09 15:29:33.013547", + "modified": "2023-11-09 15:33:26.828998", "modified_by": "Administrator", "module": "Bulk Transaction", "name": "Bulk Transaction Log Detail", From feb49f23ed0651afa23774658826352bfaca18e4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 16:38:34 +0530 Subject: [PATCH 08/31] refactor: simplify logging logic bulk_transaction (cherry picked from commit ebd74a4e5b68534a4ceab2482618e16643f220d1) --- erpnext/utilities/bulk_transaction.py | 74 +++++---------------------- 1 file changed, 14 insertions(+), 60 deletions(-) diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index fcee265644..fd8e722648 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -3,6 +3,7 @@ from datetime import date, datetime import frappe from frappe import _ +from frappe.utils import today @frappe.whitelist() @@ -38,7 +39,7 @@ def job(deserialized_data, from_doctype, to_doctype): except Exception as e: frappe.db.rollback(save_point="before_creation_state") fail_count += 1 - update_logger( + create_log( doc_name, str(frappe.get_traceback()), from_doctype, @@ -47,7 +48,7 @@ def job(deserialized_data, from_doctype, to_doctype): log_date=str(date.today()), ) else: - update_logger( + create_log( doc_name, None, from_doctype, to_doctype, status="Success", log_date=str(date.today()) ) @@ -108,45 +109,18 @@ def task(doc_name, from_doctype, to_doctype): obj.insert(ignore_mandatory=True) -def check_logger_doc_exists(log_date): - return frappe.db.exists("Bulk Transaction Log", log_date) - - -def get_logger_doc(log_date): - return frappe.get_doc("Bulk Transaction Log", log_date) - - -def create_logger_doc(): - log_doc = frappe.new_doc("Bulk Transaction Log") - log_doc.set_new_name(set_name=str(date.today())) - log_doc.log_date = date.today() - - return log_doc - - -def append_data_to_logger(log_doc, doc_name, error, from_doctype, to_doctype, status, restarted): - row = log_doc.append("logger_data", {}) - row.transaction_name = doc_name - row.date = date.today() +def create_log(doc_name, e, from_doctype, to_doctype, status, log_date=None, restarted=0): + transaction_log = frappe.new_doc("Bulk Transaction Log Detail") + transaction_log.transaction_name = doc_name + transaction_log.date = today() now = datetime.now() - row.time = now.strftime("%H:%M:%S") - row.transaction_status = status - row.error_description = str(error) - row.from_doctype = from_doctype - row.to_doctype = to_doctype - row.retried = restarted - - -def update_logger(doc_name, e, from_doctype, to_doctype, status, log_date=None, restarted=0): - if not check_logger_doc_exists(log_date): - log_doc = create_logger_doc() - append_data_to_logger(log_doc, doc_name, e, from_doctype, to_doctype, status, restarted) - log_doc.insert() - else: - log_doc = get_logger_doc(log_date) - if record_exists(log_doc, doc_name, status): - append_data_to_logger(log_doc, doc_name, e, from_doctype, to_doctype, status, restarted) - log_doc.save() + transaction_log.time = now.strftime("%H:%M:%S") + transaction_log.transaction_status = status + transaction_log.error_description = str(e) + transaction_log.from_doctype = from_doctype + transaction_log.to_doctype = to_doctype + transaction_log.retried = restarted + transaction_log.save() def show_job_status(fail_count, deserialized_data_count, to_doctype): @@ -176,23 +150,3 @@ def show_job_status(fail_count, deserialized_data_count, to_doctype): title="Failed", indicator="red", ) - - -def record_exists(log_doc, doc_name, status): - record = mark_retrired_transaction(log_doc, doc_name) - if record and status == "Failed": - return False - elif record and status == "Success": - return True - else: - return True - - -def mark_retrired_transaction(log_doc, doc_name): - record = 0 - for d in log_doc.get("logger_data"): - if d.transaction_name == doc_name and d.transaction_status == "Failed": - frappe.db.set_value("Bulk Transaction Log Detail", d.name, "retried", 1) - record = record + 1 - - return record From f0a8c83fa8894def63e84673ccf23fe3bcb26986 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 17:16:58 +0530 Subject: [PATCH 09/31] chore: add list view filters (cherry picked from commit ade09bc709ab8bca047abdad5235461a4618bc17) --- .../bulk_transaction_log_detail.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json index 051dc0af02..3a5d434a04 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json @@ -20,6 +20,7 @@ "fieldname": "transaction_name", "fieldtype": "Dynamic Link", "in_list_view": 1, + "in_standard_filter": 1, "label": "Name", "options": "from_doctype" }, @@ -39,6 +40,7 @@ { "fieldname": "from_doctype", "fieldtype": "Link", + "in_standard_filter": 1, "label": "From Doctype", "options": "DocType", "read_only": 1 @@ -54,6 +56,7 @@ "fieldname": "date", "fieldtype": "Date", "in_list_view": 1, + "in_standard_filter": 1, "label": "Date ", "read_only": 1 }, @@ -70,9 +73,10 @@ "read_only": 1 } ], + "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-11-09 15:33:26.828998", + "modified": "2023-11-09 16:55:31.026064", "modified_by": "Administrator", "module": "Bulk Transaction", "name": "Bulk Transaction Log Detail", From 6eaf67e700558992f32c941c248fcf56f0dc3e4e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 17:40:04 +0530 Subject: [PATCH 10/31] chore: add indexes (cherry picked from commit 73090fa1306fd65ff8e290d57486697af5722081) --- .../bulk_transaction_log_detail.json | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json index 3a5d434a04..fe1a6d9b56 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json @@ -22,7 +22,8 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Name", - "options": "from_doctype" + "options": "from_doctype", + "search_index": 1 }, { "fieldname": "transaction_status", @@ -43,7 +44,8 @@ "in_standard_filter": 1, "label": "From Doctype", "options": "DocType", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "to_doctype", @@ -58,7 +60,8 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Date ", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "time", @@ -76,7 +79,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-11-09 16:55:31.026064", + "modified": "2023-11-09 17:44:14.804836", "modified_by": "Administrator", "module": "Bulk Transaction", "name": "Bulk Transaction Log Detail", @@ -99,4 +102,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From 461f7a1a1cf0226f26b82820c60169b5186a8f7b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 17:43:04 +0530 Subject: [PATCH 11/31] chore: make `from_doctype` readonly (cherry picked from commit b0dfc936a10aef4dfd743959a6cf5e47ea69632e) --- .../bulk_transaction_log_detail/bulk_transaction_log_detail.json | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json index fe1a6d9b56..5491f56a1a 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json @@ -23,6 +23,7 @@ "in_standard_filter": 1, "label": "Name", "options": "from_doctype", + "read_only": 1, "search_index": 1 }, { From e68d2e138a656022cfd6e88237b4f8b23adb1efb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 20:15:33 +0530 Subject: [PATCH 12/31] feat: virtual parent doctype (cherry picked from commit a248b13cc353fcd94b0009c74c24512ea629a376) --- .../doctype/bulk_transaction_log/__init__.py | 0 .../bulk_transaction_log.js | 8 ++ .../bulk_transaction_log.json | 80 +++++++++++++++++++ .../bulk_transaction_log.py | 28 +++++++ .../test_bulk_transaction_log.py | 9 +++ 5 files changed, 125 insertions(+) create mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/__init__.py create mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js create mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json create mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py create mode 100644 erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/__init__.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js new file mode 100644 index 0000000000..07a8009816 --- /dev/null +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Bulk Transaction Log", { +// refresh(frm) { + +// }, +// }); diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json new file mode 100644 index 0000000000..a874b791ca --- /dev/null +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json @@ -0,0 +1,80 @@ +{ + "actions": [], + "allow_copy": 1, + "creation": "2023-11-09 20:14:45.139593", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "date", + "column_break_bsan", + "log_entries", + "section_break_mdmv", + "succeeded", + "column_break_qryp", + "failed" + ], + "fields": [ + { + "fieldname": "date", + "fieldtype": "Date", + "label": "Date", + "read_only": 1 + }, + { + "fieldname": "log_entries", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Log Entries", + "read_only": 1 + }, + { + "fieldname": "column_break_bsan", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_mdmv", + "fieldtype": "Section Break" + }, + { + "fieldname": "succeeded", + "fieldtype": "Int", + "label": "Succeeded", + "read_only": 1 + }, + { + "fieldname": "column_break_qryp", + "fieldtype": "Column Break" + }, + { + "fieldname": "failed", + "fieldtype": "Int", + "label": "Failed", + "read_only": 1 + } + ], + "in_create": 1, + "is_virtual": 1, + "links": [], + "modified": "2023-11-10 11:13:52.733076", + "modified_by": "Administrator", + "module": "Bulk Transaction", + "name": "Bulk Transaction Log", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py new file mode 100644 index 0000000000..fb9fcf7c52 --- /dev/null +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py @@ -0,0 +1,28 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class BulkTransactionLog(Document): + def db_insert(self, *args, **kwargs): + pass + + def load_from_db(self): + pass + + def db_update(self): + pass + + @staticmethod + def get_list(args): + pass + + @staticmethod + def get_count(args): + pass + + @staticmethod + def get_stats(args): + pass diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py new file mode 100644 index 0000000000..01bb615a3e --- /dev/null +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestBulkTransactionLog(FrappeTestCase): + pass From 696f8b0ae01a5a9a7ceb2884dd07b9b072525e87 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 9 Nov 2023 21:07:01 +0530 Subject: [PATCH 13/31] refactor: add basic functionalities (cherry picked from commit af35590549987ab84eadbf08c10f3837242203dc) --- .../bulk_transaction_log.js | 15 ++-- .../bulk_transaction_log.py | 71 +++++++++++++++++-- 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js index 07a8009816..218424beca 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js @@ -1,8 +1,13 @@ // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -// frappe.ui.form.on("Bulk Transaction Log", { -// refresh(frm) { - -// }, -// }); +frappe.ui.form.on("Bulk Transaction Log", { + refresh(frm) { + frm.add_custom_button(__('Succeeded Entries'), function() { + frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Success"}); + }, __("View")); + frm.add_custom_button(__('Failed Entries'), function() { + frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Failed"}); + }, __("View")); + }, +}); diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py index fb9fcf7c52..4febb48a60 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py @@ -1,8 +1,14 @@ # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe +import frappe +from frappe import qb from frappe.model.document import Document +from frappe.query_builder.functions import Count +from frappe.utils import cint +from pypika import Order + +log_detail = qb.DocType("Bulk Transaction Log Detail") class BulkTransactionLog(Document): @@ -10,14 +16,51 @@ class BulkTransactionLog(Document): pass def load_from_db(self): - pass - - def db_update(self): - pass + succeeded_logs = ( + qb.from_(log_detail) + .select(Count(log_detail.date).as_("count")) + .where((log_detail.date == self.name) & (log_detail.transaction_status == "Success")) + .run() + )[0][0] or 0 + failed_logs = ( + qb.from_(log_detail) + .select(Count(log_detail.date).as_("count")) + .where((log_detail.date == self.name) & (log_detail.transaction_status == "Failed")) + .run() + )[0][0] or 0 + total_logs = succeeded_logs + failed_logs + transaction_log = frappe._dict( + { + "date": self.name, + "count": total_logs, + "succeeded": succeeded_logs, + "failed": failed_logs, + } + ) + super(Document, self).__init__(serialize_transaction_log(transaction_log)) @staticmethod def get_list(args): - pass + limit = cint(args.get("page_length")) or 20 + dates = ( + qb.from_(log_detail) + .select(log_detail.date) + .distinct() + .orderby(log_detail.date, order=Order.desc) + .limit(limit) + .run() + ) + + transaction_logs = ( + qb.from_(log_detail) + .select(log_detail.date.as_("date"), Count(log_detail.date).as_("count")) + .where(log_detail.date.isin(dates)) + .orderby(log_detail.date, order=Order.desc) + .groupby(log_detail.date) + .limit(limit) + .run(as_dict=True) + ) + return [serialize_transaction_log(x) for x in transaction_logs] @staticmethod def get_count(args): @@ -26,3 +69,19 @@ class BulkTransactionLog(Document): @staticmethod def get_stats(args): pass + + def db_update(self, *args, **kwargs): + pass + + def delete(self): + pass + + +def serialize_transaction_log(data): + return frappe._dict( + name=data.date, + date=data.date, + log_entries=data.count, + succeeded=data.succeeded, + failed=data.failed, + ) From 33f1e709f136eaadccec3cd83e93149ab518b95f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 10 Nov 2023 11:44:25 +0530 Subject: [PATCH 14/31] chore: show retried in list view (cherry picked from commit 194d70f8a0c93c8e2952cd58cdc5d994414198fb) --- .../bulk_transaction_log_detail.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json index 5491f56a1a..9590325a06 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json @@ -73,6 +73,7 @@ { "fieldname": "retried", "fieldtype": "Int", + "in_list_view": 1, "label": "Retried", "read_only": 1 } @@ -80,7 +81,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-11-09 17:44:14.804836", + "modified": "2023-11-10 11:44:10.758342", "modified_by": "Administrator", "module": "Bulk Transaction", "name": "Bulk Transaction Log Detail", @@ -103,4 +104,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} +} \ No newline at end of file From db60e147e0146bb4bbe8bf21f6181e5694091e68 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 10 Nov 2023 12:20:30 +0530 Subject: [PATCH 15/31] refactor: barebones retry functionality (cherry picked from commit 0aa1636d04bed9fef260aedde4ed6a42b6a1968b) --- .../bulk_transaction_log.js | 8 ++++++ erpnext/utilities/bulk_transaction.py | 28 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js index 218424beca..6e2250052d 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js @@ -9,5 +9,13 @@ frappe.ui.form.on("Bulk Transaction Log", { frm.add_custom_button(__('Failed Entries'), function() { frappe.set_route('List', 'Bulk Transaction Log Detail', {'date': frm.doc.date, 'transaction_status': "Failed"}); }, __("View")); + if (frm.doc.failed) { + frm.add_custom_button(__('Retry Failed Transactions'), function() { + frappe.call({ + method: "erpnext.utilities.bulk_transaction.retry_failed_transactions", + args: {date: frm.doc.date} + }).then(()=> { }); + }); + } }, }); diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index fd8e722648..a596001583 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -29,6 +29,34 @@ def transaction_processing(data, from_doctype, to_doctype): job(deserialized_data, from_doctype, to_doctype) +@frappe.whitelist() +def retry_failed_transactions(date: str | None): + if date: + failed_docs = frappe.db.get_all( + "Bulk Transaction Log Detail", + filters={"date": date, "transaction_status": "Failed", "retried": 0}, + fields=["name", "transaction_name", "from_doctype", "to_doctype"], + ) + if not failed_docs: + frappe.msgprint("There are no Failed transactions") + return + + for log in failed_docs: + try: + frappe.db.savepoint("before_creation_state") + task(log.transaction_name, log.from_doctype, log.to_doctype) + except Exception as e: + frappe.db.rollback(save_point="before_creation_state") + update_log(log.name, "Failed", 1) + else: + update_log(log.name, "Success", 1) + + +def update_log(log_name, status, retried): + frappe.db.set_value("Bulk Transaction Log Detail", log_name, "transaction_status", status) + frappe.db.set_value("Bulk Transaction Log Detail", log_name, "retried", retried) + + def job(deserialized_data, from_doctype, to_doctype): fail_count = 0 for d in deserialized_data: From 0ec2978ea0a6fea50f14d8ce1bd7b7fced407fa2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 10 Nov 2023 13:45:52 +0530 Subject: [PATCH 16/31] refactor: rollback for retries and UI alerts (cherry picked from commit c3202886901eaecc0ab8b9bf73d8816994e6dc1f) --- .../bulk_transaction_log.js | 2 +- erpnext/utilities/bulk_transaction.py | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js index 6e2250052d..3135d41cc1 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js @@ -12,7 +12,7 @@ frappe.ui.form.on("Bulk Transaction Log", { if (frm.doc.failed) { frm.add_custom_button(__('Retry Failed Transactions'), function() { frappe.call({ - method: "erpnext.utilities.bulk_transaction.retry_failed_transactions", + method: "erpnext.utilities.bulk_transaction.retry", args: {date: frm.doc.date} }).then(()=> { }); }); diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index a596001583..7e73c2fe0b 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -3,7 +3,7 @@ from datetime import date, datetime import frappe from frappe import _ -from frappe.utils import today +from frappe.utils import get_link_to_form, today @frappe.whitelist() @@ -30,7 +30,7 @@ def transaction_processing(data, from_doctype, to_doctype): @frappe.whitelist() -def retry_failed_transactions(date: str | None): +def retry(date: str | None): if date: failed_docs = frappe.db.get_all( "Bulk Transaction Log Detail", @@ -38,9 +38,21 @@ def retry_failed_transactions(date: str | None): fields=["name", "transaction_name", "from_doctype", "to_doctype"], ) if not failed_docs: - frappe.msgprint("There are no Failed transactions") - return + frappe.msgprint(_("There are no Failed transactions")) + else: + job = frappe.enqueue( + retry_failed_transactions, + failed_docs=failed_docs, + ) + frappe.msgprint( + _("Job: {0} has been triggered for processing failed transactions").format( + get_link_to_form("RQ Job", job.id) + ) + ) + +def retry_failed_transactions(failed_docs: list | None): + if failed_docs: for log in failed_docs: try: frappe.db.savepoint("before_creation_state") From 76f3d4a31c80c359666fa3f74c371078dc0fc75a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 10 Nov 2023 15:27:28 +0530 Subject: [PATCH 17/31] chore: resolve linting issue (cherry picked from commit 73639db9105260d0d1af3cea35caa03523f396e5) --- .../doctype/bulk_transaction_log/bulk_transaction_log.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py index 4febb48a60..1a078b53d5 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py @@ -8,14 +8,13 @@ from frappe.query_builder.functions import Count from frappe.utils import cint from pypika import Order -log_detail = qb.DocType("Bulk Transaction Log Detail") - class BulkTransactionLog(Document): def db_insert(self, *args, **kwargs): pass def load_from_db(self): + log_detail = qb.DocType("Bulk Transaction Log Detail") succeeded_logs = ( qb.from_(log_detail) .select(Count(log_detail.date).as_("count")) @@ -41,6 +40,7 @@ class BulkTransactionLog(Document): @staticmethod def get_list(args): + log_detail = qb.DocType("Bulk Transaction Log Detail") limit = cint(args.get("page_length")) or 20 dates = ( qb.from_(log_detail) From df5fcbee717ea88f04378b0378342f26a4802bf4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 11 Nov 2023 05:10:16 +0530 Subject: [PATCH 18/31] refactor: support list view filters (cherry picked from commit 93295bf25b67069936e23d03f5a1c559294bc25f) --- .../bulk_transaction_log.json | 8 +++- .../bulk_transaction_log.py | 42 +++++++++++++------ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json index a874b791ca..75cb358ff2 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json @@ -2,6 +2,7 @@ "actions": [], "allow_copy": 1, "creation": "2023-11-09 20:14:45.139593", + "default_view": "List", "doctype": "DocType", "engine": "InnoDB", "field_order": [ @@ -17,6 +18,8 @@ { "fieldname": "date", "fieldtype": "Date", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Date", "read_only": 1 }, @@ -55,7 +58,7 @@ "in_create": 1, "is_virtual": 1, "links": [], - "modified": "2023-11-10 11:13:52.733076", + "modified": "2023-11-11 04:52:49.347376", "modified_by": "Administrator", "module": "Bulk Transaction", "name": "Bulk Transaction Log", @@ -76,5 +79,6 @@ ], "sort_field": "modified", "sort_order": "DESC", - "states": [] + "states": [], + "title_field": "date" } \ No newline at end of file diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py index 1a078b53d5..8ae54ddab8 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py @@ -40,26 +40,33 @@ class BulkTransactionLog(Document): @staticmethod def get_list(args): - log_detail = qb.DocType("Bulk Transaction Log Detail") + filter_date = parse_list_filters(args) limit = cint(args.get("page_length")) or 20 - dates = ( + log_detail = qb.DocType("Bulk Transaction Log Detail") + + dates_query = ( qb.from_(log_detail) .select(log_detail.date) .distinct() .orderby(log_detail.date, order=Order.desc) .limit(limit) - .run() ) + if filter_date: + dates_query = dates_query.where(log_detail.date == filter_date) + dates = dates_query.run() + + transaction_logs = [] + if dates: + transaction_logs_query = ( + qb.from_(log_detail) + .select(log_detail.date.as_("date"), Count(log_detail.date).as_("count")) + .where(log_detail.date.isin(dates)) + .orderby(log_detail.date, order=Order.desc) + .groupby(log_detail.date) + .limit(limit) + ) + transaction_logs = transaction_logs_query.run(as_dict=True) - transaction_logs = ( - qb.from_(log_detail) - .select(log_detail.date.as_("date"), Count(log_detail.date).as_("count")) - .where(log_detail.date.isin(dates)) - .orderby(log_detail.date, order=Order.desc) - .groupby(log_detail.date) - .limit(limit) - .run(as_dict=True) - ) return [serialize_transaction_log(x) for x in transaction_logs] @staticmethod @@ -85,3 +92,14 @@ def serialize_transaction_log(data): succeeded=data.succeeded, failed=data.failed, ) + + +def parse_list_filters(args): + # parse date filter + filter_date = None + for fil in args.get("filters"): + if isinstance(fil, list): + for elem in fil: + if elem == "date": + filter_date = fil[3] + return filter_date From f28d7187325c97973f5f86a3ae3b5d6a9853f2b6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sat, 11 Nov 2023 05:20:27 +0530 Subject: [PATCH 19/31] refactor: update traceback on retry (cherry picked from commit a52a1b49af1c7b72ba85563057f60bb7cf54e81c) --- erpnext/utilities/bulk_transaction.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index 7e73c2fe0b..402c305ca9 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -59,14 +59,16 @@ def retry_failed_transactions(failed_docs: list | None): task(log.transaction_name, log.from_doctype, log.to_doctype) except Exception as e: frappe.db.rollback(save_point="before_creation_state") - update_log(log.name, "Failed", 1) + update_log(log.name, "Failed", 1, str(frappe.get_traceback())) else: update_log(log.name, "Success", 1) -def update_log(log_name, status, retried): +def update_log(log_name, status, retried, err=None): frappe.db.set_value("Bulk Transaction Log Detail", log_name, "transaction_status", status) frappe.db.set_value("Bulk Transaction Log Detail", log_name, "retried", retried) + if err: + frappe.db.set_value("Bulk Transaction Log Detail", log_name, "error_description", err) def job(deserialized_data, from_doctype, to_doctype): From 0134be491522ac1d6c6f72ecb4fe3fed3104de9e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 13 Nov 2023 10:39:40 +0530 Subject: [PATCH 20/31] refactor: raise exception on invalid date (cherry picked from commit a393a6b76c35d95e4422fbf239c468ab48e67b71) --- .../doctype/bulk_transaction_log/bulk_transaction_log.js | 2 +- .../doctype/bulk_transaction_log/bulk_transaction_log.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js index 3135d41cc1..dc54d606e7 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js @@ -14,7 +14,7 @@ frappe.ui.form.on("Bulk Transaction Log", { frappe.call({ method: "erpnext.utilities.bulk_transaction.retry", args: {date: frm.doc.date} - }).then(()=> { }); + }); }); } }, diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py index 8ae54ddab8..712caf1f91 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py @@ -15,6 +15,13 @@ class BulkTransactionLog(Document): def load_from_db(self): log_detail = qb.DocType("Bulk Transaction Log Detail") + + has_records = frappe.db.sql( + f"select exists (select * from `tabBulk Transaction Log Detail` where date = '{self.name}');" + )[0][0] + if not has_records: + raise frappe.DoesNotExistError + succeeded_logs = ( qb.from_(log_detail) .select(Count(log_detail.date).as_("count")) From ac61abb2e40e5ee09223e41c1f6fec154b290738 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 13 Nov 2023 15:05:34 +0530 Subject: [PATCH 21/31] fix: handle partial return against invoices (cherry picked from commit 5b446d45750c45c6a86bb420c689a06945ab4974) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index c0e3ab3ed4..a280852a97 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1074,9 +1074,7 @@ class PaymentEntry(AccountsController): reverse_dr_or_cr, standalone_note = 0, 0 if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]: - is_return, return_against = frappe.db.get_value( - d.reference_doctype, d.reference_name, ["is_return", "return_against"] - ) + is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return") payable_party_types = get_party_types_from_account_type("Payable") receivable_party_types = get_party_types_from_account_type("Receivable") if is_return and self.party_type in receivable_party_types and (self.payment_type == "Pay"): @@ -1086,7 +1084,7 @@ class PaymentEntry(AccountsController): ): reverse_dr_or_cr = 1 - if is_return and not return_against and not reverse_dr_or_cr: + if is_return and not reverse_dr_or_cr: dr_or_cr = "debit" if dr_or_cr == "credit" else "credit" gle.update( From 22b39ac4b4d46de38e5142ae2bba3b9ca00245df Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 13 Nov 2023 18:09:49 +0530 Subject: [PATCH 22/31] fix: reset dr_or_cr for every reference (cherry picked from commit a59c942cd487e0440625663034760eb89171e2ef) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index a280852a97..025277b40c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1054,9 +1054,9 @@ class PaymentEntry(AccountsController): item=self, ) - dr_or_cr = "credit" if self.payment_type == "Receive" else "debit" - for d in self.get("references"): + # re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse + dr_or_cr = "credit" if self.payment_type == "Receive" else "debit" cost_center = self.cost_center if d.reference_doctype == "Sales Invoice" and not cost_center: cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center") @@ -1072,7 +1072,7 @@ class PaymentEntry(AccountsController): against_voucher_type = d.reference_doctype against_voucher = d.reference_name - reverse_dr_or_cr, standalone_note = 0, 0 + reverse_dr_or_cr = 0 if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]: is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return") payable_party_types = get_party_types_from_account_type("Payable") @@ -1098,6 +1098,7 @@ class PaymentEntry(AccountsController): ) gl_entries.append(gle) + dr_or_cr = "credit" if self.payment_type == "Receive" else "debit" if self.unallocated_amount: exchange_rate = self.get_exchange_rate() base_unallocated_amount = self.unallocated_amount * exchange_rate From 376e09680ca035355a5f5fd7a1bcb7afe075c8cc Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 13 Nov 2023 18:11:00 +0530 Subject: [PATCH 23/31] test: payment against partial return invoices (cherry picked from commit 09f9764bbdec90d1b9f85ccc5a368bf85547bea1) --- .../payment_entry/test_payment_entry.py | 54 ++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index b6b93b6b11..8dcead67dd 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1250,6 +1250,9 @@ class TestPaymentEntry(FrappeTestCase): self.assertEqual(so.advance_paid, so.rounded_total) def test_receive_payment_from_payable_party_type(self): + """ + Checks GL entries generated while receiving payments from a Payable Party Type. + """ pe = create_payment_entry( party_type="Supplier", party="_Test Supplier", @@ -1261,8 +1264,55 @@ class TestPaymentEntry(FrappeTestCase): ) self.voucher_no = pe.name self.expected_gle = [ - {"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0}, {"account": "Creditors - _TC", "debit": 0.0, "credit": 1000.0}, + {"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0}, + ] + self.check_gl_entries() + + def test_payment_against_partial_return_invoice(self): + """ + Checks GL entries generated for partial return invoice payments. + """ + si = create_sales_invoice(qty=10, rate=10, customer="_Test Customer") + credit_note = create_sales_invoice( + qty=-4, rate=10, customer="_Test Customer", is_return=1, return_against=si.name + ) + pe = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Receive", + paid_from="Debtors - _TC", + paid_to="_Test Cash - _TC", + ) + pe.set( + "references", + [ + { + "reference_doctype": "Sales Invoice", + "reference_name": si.name, + "due_date": si.get("due_date"), + "total_amount": si.grand_total, + "outstanding_amount": si.outstanding_amount, + "allocated_amount": si.outstanding_amount, + }, + { + "reference_doctype": "Sales Invoice", + "reference_name": credit_note.name, + "due_date": credit_note.get("due_date"), + "total_amount": credit_note.grand_total, + "outstanding_amount": credit_note.outstanding_amount, + "allocated_amount": credit_note.outstanding_amount, + }, + ], + ) + pe.save() + pe.submit() + self.voucher_no = pe.name + self.expected_gle = [ + {"account": "Debtors - _TC", "debit": 40.0, "credit": 0.0}, + {"account": "Debtors - _TC", "debit": 0.0, "credit": 940.0}, + {"account": "Debtors - _TC", "debit": 0.0, "credit": 100.0}, + {"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0}, ] self.check_gl_entries() @@ -1276,7 +1326,7 @@ class TestPaymentEntry(FrappeTestCase): gle.credit, ) .where((gle.voucher_no == self.voucher_no) & (gle.is_cancelled == 0)) - .orderby(gle.account) + .orderby(gle.account, gle.debit, gle.credit, order=frappe.qb.desc) ).run(as_dict=True) for row in range(len(self.expected_gle)): for field in ["account", "debit", "credit"]: From cb4bb5b4ee126a24492775f627849ed5d5a3ff6f Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 15 Nov 2023 13:32:23 +0530 Subject: [PATCH 24/31] fix: test total unallocated amount in payment (cherry picked from commit 2499675ad121ab370f672e6f9ffdcda3c8c76b8e) --- erpnext/accounts/doctype/payment_entry/test_payment_entry.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 8dcead67dd..13c9f21805 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1307,6 +1307,8 @@ class TestPaymentEntry(FrappeTestCase): ) pe.save() pe.submit() + self.assertEqual(pe.total_allocated_amount, 60) + self.assertEqual(pe.unallocated_amount, 940) self.voucher_no = pe.name self.expected_gle = [ {"account": "Debtors - _TC", "debit": 40.0, "credit": 0.0}, From d0a74419b397ff5b7fb97954cbc3db3fad22b316 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 16 Nov 2023 12:18:47 +0530 Subject: [PATCH 25/31] ci: change release branch --- .github/workflows/release.yml | 2 +- .releaserc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ccd712065d..7b48babe33 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Generate Semantic Release on: push: branches: - - version-14 + - version-15 jobs: release: name: Release diff --git a/.releaserc b/.releaserc index 454adc7094..f8d353e0d7 100644 --- a/.releaserc +++ b/.releaserc @@ -1,5 +1,5 @@ { - "branches": ["version-14"], + "branches": ["version-15"], "plugins": [ "@semantic-release/commit-analyzer", { "preset": "angular", From ea43862fcd164f5a992f8fd69219f56e086c3589 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Tue, 14 Nov 2023 11:13:05 +0100 Subject: [PATCH 26/31] fix(dn): regression from bulk transaction fix (cherry picked from commit 426c245032bccc93b79d0505f853bc25ae51f37e) --- erpnext/selling/doctype/sales_order/sales_order.py | 7 +++++-- erpnext/utilities/bulk_transaction.py | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index e4f1a28316..a97198aa78 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -767,8 +767,11 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None): if target.company_address: target.update(get_fetch_values("Delivery Note", "company_address", target.company_address)) - # set target items names to ensure proper linking with packed_items - target.set_new_name() + # if invoked in bulk creation, validations are ignored and thus this method is nerver invoked + if frappe.flags.bulk_transaction: + # set target items names to ensure proper linking with packed_items + target.set_new_name() + make_packing_list(target) def condition(doc): diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py index 402c305ca9..57c2f9d787 100644 --- a/erpnext/utilities/bulk_transaction.py +++ b/erpnext/utilities/bulk_transaction.py @@ -141,6 +141,7 @@ def task(doc_name, from_doctype, to_doctype): }, "Purchase Receipt": {"Purchase Invoice": purchase_receipt.make_purchase_invoice}, } + frappe.flags.bulk_transaction = True if to_doctype in ["Payment Entry"]: obj = mapper[from_doctype][to_doctype](from_doctype, doc_name) else: @@ -149,6 +150,7 @@ def task(doc_name, from_doctype, to_doctype): obj.flags.ignore_validate = True obj.set_title_field() obj.insert(ignore_mandatory=True) + del frappe.flags.bulk_transaction def create_log(doc_name, e, from_doctype, to_doctype, status, log_date=None, restarted=0): From d51237195a6ab35ac29bba6dc15d4660de0932af Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 16 Nov 2023 13:38:10 +0530 Subject: [PATCH 27/31] fix: GL Entries for receiving non CWIP assets using Purchase Receipt (#38123) * fix: GL Entries for receiving non CWIP assets using Purchase Receipt * test: Update tests --- .../purchase_invoice/purchase_invoice.py | 239 +------ erpnext/assets/doctype/asset/test_asset.py | 11 +- erpnext/controllers/stock_controller.py | 14 +- .../purchase_receipt/purchase_receipt.py | 633 ++++++++---------- 4 files changed, 331 insertions(+), 566 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 48beb5e287..e1f0f1932e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -33,7 +33,7 @@ from erpnext.accounts.general_ledger import ( ) from erpnext.accounts.party import get_due_date, get_party_account from erpnext.accounts.utils import get_account_currency, get_fiscal_year -from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled +from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.controllers.accounts_controller import validate_account_head @@ -281,9 +281,6 @@ class PurchaseInvoice(BuyingController): # in case of auto inventory accounting, # expense account is always "Stock Received But Not Billed" for a stock item # except opening entry, drop-ship entry and fixed asset items - if item.item_code: - asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") - if ( auto_accounting_for_stock and item.item_code in stock_items @@ -350,22 +347,26 @@ class PurchaseInvoice(BuyingController): frappe.msgprint(msg, title=_("Expense Head Changed")) item.expense_account = stock_not_billed_account - - elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category): + elif item.is_fixed_asset and item.pr_detail: + if not asset_received_but_not_billed: + asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed") + item.expense_account = asset_received_but_not_billed + elif item.is_fixed_asset: + account_type = ( + "capital_work_in_progress_account" + if is_cwip_accounting_enabled(item.asset_category) + else "fixed_asset_account" + ) asset_category_account = get_asset_category_account( - "fixed_asset_account", item=item.item_code, company=self.company + account_type, item=item.item_code, company=self.company ) if not asset_category_account: - form_link = get_link_to_form("Asset Category", asset_category) + form_link = get_link_to_form("Asset Category", item.asset_category) throw( _("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company), title=_("Missing Account"), ) item.expense_account = asset_category_account - elif item.is_fixed_asset and item.pr_detail: - if not asset_received_but_not_billed: - asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed") - item.expense_account = asset_received_but_not_billed elif not item.expense_account and for_validate: throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name)) @@ -584,12 +585,11 @@ class PurchaseInvoice(BuyingController): def get_gl_entries(self, warehouse_account=None): self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) + if self.auto_accounting_for_stock: self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed") - self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") else: self.stock_received_but_not_billed = None - self.expenses_included_in_valuation = None self.negative_expense_to_be_booked = 0.0 gl_entries = [] @@ -598,9 +598,6 @@ class PurchaseInvoice(BuyingController): self.make_item_gl_entries(gl_entries) self.make_precision_loss_gl_entry(gl_entries) - if self.check_asset_cwip_enabled(): - self.get_asset_gl_entry(gl_entries) - self.make_tax_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) @@ -702,7 +699,11 @@ class PurchaseInvoice(BuyingController): if item.item_code: asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") - if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items: + if ( + self.update_stock + and self.auto_accounting_for_stock + and (item.item_code in stock_items or item.is_fixed_asset) + ): # warehouse account warehouse_debit_amount = self.make_stock_adjustment_entry( gl_entries, item, voucher_wise_stock_value, account_currency @@ -817,9 +818,7 @@ class PurchaseInvoice(BuyingController): ) ) - elif not item.is_fixed_asset or ( - item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category) - ): + else: expense_account = ( item.expense_account if (not item.enable_deferred_expense or self.is_return) @@ -912,40 +911,6 @@ class PurchaseInvoice(BuyingController): ) ) - # If asset is bought through this document and not linked to PR - if self.update_stock and item.landed_cost_voucher_amount: - expenses_included_in_asset_valuation = self.get_company_default( - "expenses_included_in_asset_valuation" - ) - # Amount added through landed-cost-voucher - gl_entries.append( - self.get_gl_dict( - { - "account": expenses_included_in_asset_valuation, - "against": expense_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(item.landed_cost_voucher_amount), - "project": item.project or self.project, - }, - item=item, - ) - ) - - gl_entries.append( - self.get_gl_dict( - { - "account": expense_account, - "against": expenses_included_in_asset_valuation, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": flt(item.landed_cost_voucher_amount), - "project": item.project or self.project, - }, - item=item, - ) - ) - # update gross amount of asset bought through this document assets = frappe.db.get_all( "Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code} @@ -970,11 +935,17 @@ class PurchaseInvoice(BuyingController): (item.purchase_receipt, valuation_tax_accounts), ) + stock_rbnb = ( + self.get_company_default("asset_received_but_not_billed") + if item.is_fixed_asset + else self.stock_received_but_not_billed + ) + if not negative_expense_booked_in_pr: gl_entries.append( self.get_gl_dict( { - "account": self.stock_received_but_not_billed, + "account": stock_rbnb, "against": self.supplier, "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), "remarks": self.remarks or _("Accounting Entry for Stock"), @@ -989,156 +960,12 @@ class PurchaseInvoice(BuyingController): item.item_tax_amount, item.precision("item_tax_amount") ) - def get_asset_gl_entry(self, gl_entries): - arbnb_account = None - eiiav_account = None - asset_eiiav_currency = None - - for item in self.get("items"): - if item.is_fixed_asset: - asset_amount = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate) - base_asset_amount = flt(item.base_net_amount + item.item_tax_amount) - - item_exp_acc_type = frappe.get_cached_value("Account", item.expense_account, "account_type") - if not item.expense_account or item_exp_acc_type not in [ - "Asset Received But Not Billed", - "Fixed Asset", - ]: - if not arbnb_account: - arbnb_account = self.get_company_default("asset_received_but_not_billed") - item.expense_account = arbnb_account - - if not self.update_stock: - arbnb_currency = get_account_currency(item.expense_account) - gl_entries.append( - self.get_gl_dict( - { - "account": item.expense_account, - "against": self.supplier, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "debit": base_asset_amount, - "debit_in_account_currency": ( - base_asset_amount if arbnb_currency == self.company_currency else asset_amount - ), - "cost_center": item.cost_center, - "project": item.project or self.project, - }, - item=item, - ) - ) - - if item.item_tax_amount: - if not eiiav_account or not asset_eiiav_currency: - eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") - asset_eiiav_currency = get_account_currency(eiiav_account) - - gl_entries.append( - self.get_gl_dict( - { - "account": eiiav_account, - "against": self.supplier, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "cost_center": item.cost_center, - "project": item.project or self.project, - "credit": item.item_tax_amount, - "credit_in_account_currency": ( - item.item_tax_amount - if asset_eiiav_currency == self.company_currency - else item.item_tax_amount / self.conversion_rate - ), - }, - item=item, - ) - ) - else: - cwip_account = get_asset_account( - "capital_work_in_progress_account", asset_category=item.asset_category, company=self.company - ) - - cwip_account_currency = get_account_currency(cwip_account) - gl_entries.append( - self.get_gl_dict( - { - "account": cwip_account, - "against": self.supplier, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "debit": base_asset_amount, - "debit_in_account_currency": ( - base_asset_amount if cwip_account_currency == self.company_currency else asset_amount - ), - "cost_center": item.cost_center or self.cost_center, - "project": item.project or self.project, - }, - item=item, - ) - ) - - if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)): - if not eiiav_account or not asset_eiiav_currency: - eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") - asset_eiiav_currency = get_account_currency(eiiav_account) - - gl_entries.append( - self.get_gl_dict( - { - "account": eiiav_account, - "against": self.supplier, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "cost_center": item.cost_center, - "credit": item.item_tax_amount, - "project": item.project or self.project, - "credit_in_account_currency": ( - item.item_tax_amount - if asset_eiiav_currency == self.company_currency - else item.item_tax_amount / self.conversion_rate - ), - }, - item=item, - ) - ) - - # Assets are bought through this document then it will be linked to this document - if flt(item.landed_cost_voucher_amount): - if not eiiav_account: - eiiav_account = self.get_company_default("expenses_included_in_asset_valuation") - - gl_entries.append( - self.get_gl_dict( - { - "account": eiiav_account, - "against": cwip_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "credit": flt(item.landed_cost_voucher_amount), - "project": item.project or self.project, - }, - item=item, - ) - ) - - gl_entries.append( - self.get_gl_dict( - { - "account": cwip_account, - "against": eiiav_account, - "cost_center": item.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": flt(item.landed_cost_voucher_amount), - "project": item.project or self.project, - }, - item=item, - ) - ) - - # update gross amount of assets bought through this document - assets = frappe.db.get_all( - "Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code} - ) - for asset in assets: - frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate)) - frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)) - - return gl_entries + assets = frappe.db.get_all( + "Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code} + ) + for asset in assets: + frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate)) + frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)) def make_stock_adjustment_entry( self, gl_entries, item, voucher_wise_stock_value, account_currency diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 04adca4595..9e3ec6faa8 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -19,7 +19,6 @@ from frappe.utils import ( from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.assets.doctype.asset.asset import ( - get_asset_value_after_depreciation, make_sales_invoice, split_asset, update_maintenance_status, @@ -194,6 +193,7 @@ class TestAsset(AssetSetup): def test_is_fixed_asset_set(self): asset = create_asset(is_existing_asset=1) doc = frappe.new_doc("Purchase Invoice") + doc.company = "_Test Company" doc.supplier = "_Test Supplier" doc.append("items", {"item_code": "Macbook Pro", "qty": 1, "asset": asset.name}) @@ -534,7 +534,7 @@ class TestAsset(AssetSetup): self.assertEqual("Asset Received But Not Billed - _TC", doc.items[0].expense_account) - # CWIP: Capital Work In Progress + # Capital Work In Progress def test_cwip_accounting(self): pr = make_purchase_receipt( item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location" @@ -567,7 +567,8 @@ class TestAsset(AssetSetup): pr.submit() expected_gle = ( - ("Asset Received But Not Billed - _TC", 0.0, 5250.0), + ("_Test Account Shipping Charges - _TC", 0.0, 250.0), + ("Asset Received But Not Billed - _TC", 0.0, 5000.0), ("CWIP Account - _TC", 5250.0, 0.0), ) @@ -586,9 +587,8 @@ class TestAsset(AssetSetup): expected_gle = ( ("_Test Account Service Tax - _TC", 250.0, 0.0), ("_Test Account Shipping Charges - _TC", 250.0, 0.0), - ("Asset Received But Not Billed - _TC", 5250.0, 0.0), + ("Asset Received But Not Billed - _TC", 5000.0, 0.0), ("Creditors - _TC", 0.0, 5500.0), - ("Expenses Included In Asset Valuation - _TC", 0.0, 250.0), ) pi_gle = frappe.db.sql( @@ -1791,6 +1791,7 @@ def create_asset_category(): "fixed_asset_account": "_Test Fixed Asset - _TC", "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC", "depreciation_expense_account": "_Test Depreciations - _TC", + "capital_work_in_progress_account": "CWIP Account - _TC", }, ) asset_category.append( diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index e48c01b77f..fc45c7ad52 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -62,9 +62,12 @@ class StockController(AccountsController): ) ) + is_asset_pr = any(d.get("is_fixed_asset") for d in self.get("items")) + if ( cint(erpnext.is_perpetual_inventory_enabled(self.company)) or provisional_accounting_for_non_stock_items + or is_asset_pr ): warehouse_account = get_warehouse_account_map(self.company) @@ -73,12 +76,6 @@ class StockController(AccountsController): gl_entries = self.get_gl_entries(warehouse_account) make_gl_entries(gl_entries, from_repost=from_repost) - elif self.doctype in ["Purchase Receipt", "Purchase Invoice"] and self.docstatus == 1: - gl_entries = [] - gl_entries = self.get_asset_gl_entry(gl_entries) - update_regional_gl_entries(gl_entries, self) - make_gl_entries(gl_entries, from_repost=from_repost) - def validate_serialized_batch(self): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -1225,8 +1222,3 @@ def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=Fa repost_entries.append(repost_entry) return repost_entries - - -@erpnext.allow_regional -def update_regional_gl_entries(gl_list, doc): - return diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index aedf29cccc..d905fe5ea6 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -13,7 +13,6 @@ from pypika import functions as fn import erpnext from erpnext.accounts.utils import get_account_currency from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled -from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.buying.utils import check_on_hold_or_closed_status from erpnext.controllers.buying_controller import BuyingController from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_transaction @@ -144,8 +143,8 @@ class PurchaseReceipt(BuyingController): if item.is_fixed_asset and is_cwip_accounting_enabled(item.asset_category): # check cwip accounts before making auto assets # Improves UX by not giving messages of "Assets Created" before throwing error of not finding arbnb account - arbnb_account = self.get_company_default("asset_received_but_not_billed") - cwip_account = get_asset_account( + self.get_company_default("asset_received_but_not_billed") + get_asset_account( "capital_work_in_progress_account", asset_category=item.asset_category, company=self.company ) break @@ -314,7 +313,6 @@ class PurchaseReceipt(BuyingController): self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account) self.make_tax_gl_entries(gl_entries) - self.get_asset_gl_entry(gl_entries) update_regional_gl_entries(gl_entries, self) return process_gl_map(gl_entries) @@ -324,14 +322,6 @@ class PurchaseReceipt(BuyingController): get_purchase_document_details, ) - stock_rbnb = None - if erpnext.is_perpetual_inventory_enabled(self.company): - stock_rbnb = self.get_company_default("stock_received_but_not_billed") - landed_cost_entries = get_item_account_wise_additional_cost(self.name) - expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") - - warehouse_with_no_account = [] - stock_items = self.get_stock_items() provisional_accounting_for_non_stock_items = cint( frappe.db.get_value( "Company", self.company, "enable_provisional_accounting_for_non_stock_items" @@ -340,28 +330,258 @@ class PurchaseReceipt(BuyingController): exchange_rate_map, net_rate_map = get_purchase_document_details(self) + def validate_account(account_type): + frappe.throw(_("{0} account not found while submitting purchase receipt").format(account_type)) + + def make_item_asset_inward_gl_entry(item, stock_value_diff, stock_asset_account_name): + account_currency = get_account_currency(stock_asset_account_name) + + if not stock_asset_account_name: + validate_account("Asset or warehouse account") + + self.add_gl_entry( + gl_entries=gl_entries, + account=stock_asset_account_name, + cost_center=d.cost_center, + debit=stock_value_diff, + credit=0.0, + remarks=remarks, + against_account=stock_asset_rbnb, + account_currency=account_currency, + item=item, + ) + + def make_stock_received_but_not_billed_entry(item): + account = ( + warehouse_account[item.from_warehouse]["account"] if item.from_warehouse else stock_asset_rbnb + ) + account_currency = get_account_currency(account) + + # GL Entry for from warehouse or Stock Received but not billed + # Intentionally passed negative debit amount to avoid incorrect GL Entry validation + credit_amount = ( + flt(item.base_net_amount, item.precision("base_net_amount")) + if account_currency == self.company_currency + else flt(item.net_amount, item.precision("net_amount")) + ) + + outgoing_amount = item.base_net_amount + if self.is_internal_transfer() and item.valuation_rate: + outgoing_amount = abs(get_stock_value_difference(self.name, item.name, item.from_warehouse)) + credit_amount = outgoing_amount + + if credit_amount: + if not account: + validate_account("Stock or Asset Received But Not Billed") + + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=item.cost_center, + debit=-1 * flt(outgoing_amount, item.precision("base_net_amount")), + credit=0.0, + remarks=remarks, + against_account=stock_asset_account_name, + debit_in_account_currency=-1 * flt(outgoing_amount, item.precision("base_net_amount")), + account_currency=account_currency, + item=item, + ) + + # check if the exchange rate has changed + if d.get("purchase_invoice"): + if ( + exchange_rate_map[item.purchase_invoice] + and self.conversion_rate != exchange_rate_map[item.purchase_invoice] + and item.net_rate == net_rate_map[item.purchase_invoice_item] + ): + + discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * ( + exchange_rate_map[item.purchase_invoice] - self.conversion_rate + ) + + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=item.cost_center, + debit=0.0, + credit=discrepancy_caused_by_exchange_rate_difference, + remarks=remarks, + against_account=self.supplier, + debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, + account_currency=account_currency, + item=item, + ) + + self.add_gl_entry( + gl_entries=gl_entries, + account=self.get_company_default("exchange_gain_loss_account"), + cost_center=d.cost_center, + debit=discrepancy_caused_by_exchange_rate_difference, + credit=0.0, + remarks=remarks, + against_account=self.supplier, + debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, + account_currency=account_currency, + item=item, + ) + + return outgoing_amount + + def make_landed_cost_gl_entries(item): + # Amount added through landed-cost-voucher + if item.landed_cost_voucher_amount and landed_cost_entries: + if (item.item_code, item.name) in landed_cost_entries: + for account, amount in landed_cost_entries[(item.item_code, item.name)].items(): + account_currency = get_account_currency(account) + credit_amount = ( + flt(amount["base_amount"]) + if (amount["base_amount"] or account_currency != self.company_currency) + else flt(amount["amount"]) + ) + + if not account: + validate_account("Landed Cost Account") + + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=item.cost_center, + debit=0.0, + credit=credit_amount, + remarks=remarks, + against_account=stock_asset_account_name, + credit_in_account_currency=flt(amount["amount"]), + account_currency=account_currency, + project=item.project, + item=item, + ) + + def make_rate_difference_entry(item): + if item.rate_difference_with_purchase_invoice and stock_asset_rbnb: + account_currency = get_account_currency(stock_asset_rbnb) + self.add_gl_entry( + gl_entries=gl_entries, + account=stock_asset_rbnb, + cost_center=item.cost_center, + debit=0.0, + credit=flt(item.rate_difference_with_purchase_invoice), + remarks=_("Adjustment based on Purchase Invoice rate"), + against_account=stock_asset_account_name, + account_currency=account_currency, + project=item.project, + item=item, + ) + + def make_sub_contracting_gl_entries(item): + # sub-contracting warehouse + if flt(item.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): + self.add_gl_entry( + gl_entries=gl_entries, + account=supplier_warehouse_account, + cost_center=item.cost_center, + debit=0.0, + credit=flt(item.rm_supp_cost), + remarks=remarks, + against_account=stock_asset_account_name, + account_currency=supplier_warehouse_account_currency, + item=item, + ) + + def make_divisional_loss_gl_entry(item, outgoing_amount): + if item.is_fixed_asset: + return + + # divisional loss adjustment + valuation_amount_as_per_doc = ( + flt(outgoing_amount, d.precision("base_net_amount")) + + flt(item.landed_cost_voucher_amount) + + flt(item.rm_supp_cost) + + flt(item.item_tax_amount) + + flt(item.rate_difference_with_purchase_invoice) + ) + + divisional_loss = flt( + valuation_amount_as_per_doc - flt(stock_value_diff), item.precision("base_net_amount") + ) + + if divisional_loss: + loss_account = ( + self.get_company_default("default_expense_account", ignore_validation=True) + or stock_asset_rbnb + ) + + cost_center = item.cost_center or frappe.get_cached_value( + "Company", self.company, "cost_center" + ) + account_currency = get_account_currency(loss_account) + self.add_gl_entry( + gl_entries=gl_entries, + account=loss_account, + cost_center=cost_center, + debit=divisional_loss, + credit=0.0, + remarks=remarks, + against_account=stock_asset_account_name, + account_currency=account_currency, + project=item.project, + item=item, + ) + + stock_items = self.get_stock_items() + warehouse_with_no_account = [] + for d in self.get("items"): - if d.item_code in stock_items and flt(d.qty) and (flt(d.valuation_rate) or self.is_return): - if warehouse_account.get(d.warehouse): - stock_value_diff = frappe.db.get_value( - "Stock Ledger Entry", - { - "voucher_type": "Purchase Receipt", - "voucher_no": self.name, - "voucher_detail_no": d.name, - "warehouse": d.warehouse, - "is_cancelled": 0, - }, - "stock_value_difference", + if ( + provisional_accounting_for_non_stock_items + and d.item_code not in stock_items + and flt(d.qty) + and d.get("provisional_expense_account") + and not d.is_fixed_asset + ): + self.add_provisional_gl_entry( + d, gl_entries, self.posting_date, d.get("provisional_expense_account") + ) + elif flt(d.qty) and (flt(d.valuation_rate) or self.is_return): + remarks = self.get("remarks") or _("Accounting Entry for {0}").format( + "Asset" if d.is_fixed_asset else "Stock" + ) + + if not ( + (erpnext.is_perpetual_inventory_enabled(self.company) and d.item_code in stock_items) + or d.is_fixed_asset + ): + continue + + stock_asset_rbnb = ( + self.get_company_default("asset_received_but_not_billed") + if d.is_fixed_asset + else self.get_company_default("stock_received_but_not_billed") + ) + landed_cost_entries = get_item_account_wise_additional_cost(self.name) + + if d.is_fixed_asset: + account_type = ( + "capital_work_in_progress_account" + if is_cwip_accounting_enabled(d.asset_category) + else "fixed_asset_account" ) - warehouse_account_name = warehouse_account[d.warehouse]["account"] - warehouse_account_currency = warehouse_account[d.warehouse]["account_currency"] + stock_asset_account_name = get_asset_account( + account_type, asset_category=d.asset_category, company=self.company + ) + + stock_value_diff = ( + flt(d.net_amount) + + flt(d.item_tax_amount / self.conversion_rate) + + flt(d.landed_cost_voucher_amount) + ) + elif warehouse_account.get(d.warehouse): + stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse) + stock_asset_account_name = warehouse_account[d.warehouse]["account"] supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account") supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get( "account_currency" ) - remarks = self.get("remarks") or _("Accounting Entry for Stock") # If PR is sub-contracted and fg item rate is zero # in that case if account for source and target warehouse are same, @@ -369,213 +589,24 @@ class PurchaseReceipt(BuyingController): if ( flt(stock_value_diff) == flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse) - and warehouse_account_name == supplier_warehouse_account + and stock_asset_account_name == supplier_warehouse_account ): continue - self.add_gl_entry( - gl_entries=gl_entries, - account=warehouse_account_name, - cost_center=d.cost_center, - debit=stock_value_diff, - credit=0.0, - remarks=remarks, - against_account=stock_rbnb, - account_currency=warehouse_account_currency, - item=d, - ) - - # GL Entry for from warehouse or Stock Received but not billed - # Intentionally passed negative debit amount to avoid incorrect GL Entry validation - credit_currency = ( - get_account_currency(warehouse_account[d.from_warehouse]["account"]) - if d.from_warehouse - else get_account_currency(stock_rbnb) - ) - - credit_amount = ( - flt(d.base_net_amount, d.precision("base_net_amount")) - if credit_currency == self.company_currency - else flt(d.net_amount, d.precision("net_amount")) - ) - - outgoing_amount = d.base_net_amount - if self.is_internal_transfer() and d.valuation_rate: - outgoing_amount = abs( - frappe.db.get_value( - "Stock Ledger Entry", - { - "voucher_type": "Purchase Receipt", - "voucher_no": self.name, - "voucher_detail_no": d.name, - "warehouse": d.from_warehouse, - "is_cancelled": 0, - }, - "stock_value_difference", - ) - ) - credit_amount = outgoing_amount - - if credit_amount: - account = warehouse_account[d.from_warehouse]["account"] if d.from_warehouse else stock_rbnb - - self.add_gl_entry( - gl_entries=gl_entries, - account=account, - cost_center=d.cost_center, - debit=-1 * flt(outgoing_amount, d.precision("base_net_amount")), - credit=0.0, - remarks=remarks, - against_account=warehouse_account_name, - debit_in_account_currency=-1 * credit_amount, - account_currency=credit_currency, - item=d, - ) - - # check if the exchange rate has changed - if d.get("purchase_invoice"): - if ( - exchange_rate_map[d.purchase_invoice] - and self.conversion_rate != exchange_rate_map[d.purchase_invoice] - and d.net_rate == net_rate_map[d.purchase_invoice_item] - ): - - discrepancy_caused_by_exchange_rate_difference = (d.qty * d.net_rate) * ( - exchange_rate_map[d.purchase_invoice] - self.conversion_rate - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=account, - cost_center=d.cost_center, - debit=0.0, - credit=discrepancy_caused_by_exchange_rate_difference, - remarks=remarks, - against_account=self.supplier, - debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, - account_currency=credit_currency, - item=d, - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=self.get_company_default("exchange_gain_loss_account"), - cost_center=d.cost_center, - debit=discrepancy_caused_by_exchange_rate_difference, - credit=0.0, - remarks=remarks, - against_account=self.supplier, - debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, - account_currency=credit_currency, - item=d, - ) - - # Amount added through landed-cos-voucher - if d.landed_cost_voucher_amount and landed_cost_entries: - if (d.item_code, d.name) in landed_cost_entries: - for account, amount in landed_cost_entries[(d.item_code, d.name)].items(): - account_currency = get_account_currency(account) - credit_amount = ( - flt(amount["base_amount"]) - if (amount["base_amount"] or account_currency != self.company_currency) - else flt(amount["amount"]) - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=account, - cost_center=d.cost_center, - debit=0.0, - credit=credit_amount, - remarks=remarks, - against_account=warehouse_account_name, - credit_in_account_currency=flt(amount["amount"]), - account_currency=account_currency, - project=d.project, - item=d, - ) - - if d.rate_difference_with_purchase_invoice and stock_rbnb: - account_currency = get_account_currency(stock_rbnb) - self.add_gl_entry( - gl_entries=gl_entries, - account=stock_rbnb, - cost_center=d.cost_center, - debit=0.0, - credit=flt(d.rate_difference_with_purchase_invoice), - remarks=_("Adjustment based on Purchase Invoice rate"), - against_account=warehouse_account_name, - account_currency=account_currency, - project=d.project, - item=d, - ) - - # sub-contracting warehouse - if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): - self.add_gl_entry( - gl_entries=gl_entries, - account=supplier_warehouse_account, - cost_center=d.cost_center, - debit=0.0, - credit=flt(d.rm_supp_cost), - remarks=remarks, - against_account=warehouse_account_name, - account_currency=supplier_warehouse_account_currency, - item=d, - ) - - # divisional loss adjustment - valuation_amount_as_per_doc = ( - flt(outgoing_amount, d.precision("base_net_amount")) - + flt(d.landed_cost_voucher_amount) - + flt(d.rm_supp_cost) - + flt(d.item_tax_amount) - + flt(d.rate_difference_with_purchase_invoice) - ) - - divisional_loss = flt( - valuation_amount_as_per_doc - flt(stock_value_diff), d.precision("base_net_amount") - ) - - if divisional_loss: - if self.is_return or flt(d.item_tax_amount): - loss_account = expenses_included_in_valuation - else: - loss_account = ( - self.get_company_default("default_expense_account", ignore_validation=True) or stock_rbnb - ) - - cost_center = d.cost_center or frappe.get_cached_value( - "Company", self.company, "cost_center" - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=loss_account, - cost_center=cost_center, - debit=divisional_loss, - credit=0.0, - remarks=remarks, - against_account=warehouse_account_name, - account_currency=credit_currency, - project=d.project, - item=d, - ) - - elif (d.warehouse and d.warehouse not in warehouse_with_no_account) or ( - d.rejected_warehouse and d.rejected_warehouse not in warehouse_with_no_account - ): - warehouse_with_no_account.append(d.warehouse or d.rejected_warehouse) - elif ( - d.item_code not in stock_items - and not d.is_fixed_asset - and flt(d.qty) - and provisional_accounting_for_non_stock_items - and d.get("provisional_expense_account") + if (flt(d.valuation_rate) or self.is_return or d.is_fixed_asset) and flt(d.qty): + make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name) + outgoing_amount = make_stock_received_but_not_billed_entry(d) + make_landed_cost_gl_entries(d) + make_rate_difference_entry(d) + make_sub_contracting_gl_entries(d) + make_divisional_loss_gl_entry(d, outgoing_amount) + elif (d.warehouse and d.warehouse not in warehouse_with_no_account) or ( + d.rejected_warehouse and d.rejected_warehouse not in warehouse_with_no_account ): - self.add_provisional_gl_entry( - d, gl_entries, self.posting_date, d.get("provisional_expense_account") - ) + warehouse_with_no_account.append(d.warehouse or d.rejected_warehouse) + + if d.is_fixed_asset: + self.update_assets(d, d.valuation_rate) if warehouse_with_no_account: frappe.msgprint( @@ -588,8 +619,8 @@ class PurchaseReceipt(BuyingController): self, item, gl_entries, posting_date, provisional_account, reverse=0 ): credit_currency = get_account_currency(provisional_account) - debit_currency = get_account_currency(item.expense_account) expense_account = item.expense_account + debit_currency = get_account_currency(item.expense_account) remarks = self.get("remarks") or _("Accounting Entry for Service") multiplication_factor = 1 @@ -630,11 +661,8 @@ class PurchaseReceipt(BuyingController): ) def make_tax_gl_entries(self, gl_entries): - - if erpnext.is_perpetual_inventory_enabled(self.company): - expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") - negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get("items")]) + is_asset_pr = any(d.is_fixed_asset for d in self.get("items")) # Cost center-wise amount breakup for other charges included for valuation valuation_tax = {} for tax in self.get("taxes"): @@ -654,26 +682,26 @@ class PurchaseReceipt(BuyingController): if negative_expense_to_be_booked and valuation_tax: # Backward compatibility: - # If expenses_included_in_valuation account has been credited in against PI # and charges added via Landed Cost Voucher, # post valuation related charges on "Stock Received But Not Billed" - # introduced in 2014 for backward compatibility of expenses already booked in expenses_included_in_valuation account - - negative_expense_booked_in_pi = frappe.db.sql( - """select name from `tabPurchase Invoice Item` pi - where docstatus = 1 and purchase_receipt=%s - and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice' - and voucher_no=pi.parent and account=%s)""", - (self.name, expenses_included_in_valuation), - ) - against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0]) total_valuation_amount = sum(valuation_tax.values()) amount_including_divisional_loss = negative_expense_to_be_booked - stock_rbnb = self.get_company_default("stock_received_but_not_billed") + stock_rbnb = ( + self.get("asset_received_but_not_billed") + if is_asset_pr + else self.get_company_default("stock_received_but_not_billed") + ) i = 1 for tax in self.get("taxes"): if valuation_tax.get(tax.name): + negative_expense_booked_in_pi = frappe.db.sql( + """select name from `tabPurchase Invoice Item` pi + where docstatus = 1 and purchase_receipt=%s + and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice' + and voucher_no=pi.parent and account=%s)""", + (self.name, tax.account_head), + ) if negative_expense_booked_in_pi: account = stock_rbnb @@ -701,103 +729,6 @@ class PurchaseReceipt(BuyingController): i += 1 - def get_asset_gl_entry(self, gl_entries): - for item in self.get("items"): - if item.is_fixed_asset: - if is_cwip_accounting_enabled(item.asset_category): - self.add_asset_gl_entries(item, gl_entries) - if flt(item.landed_cost_voucher_amount): - self.add_lcv_gl_entries(item, gl_entries) - # update assets gross amount by its valuation rate - # valuation rate is total of net rate, raw mat supp cost, tax amount, lcv amount per item - self.update_assets(item, item.valuation_rate) - return gl_entries - - def add_asset_gl_entries(self, item, gl_entries): - arbnb_account = self.get_company_default("asset_received_but_not_billed") - # This returns category's cwip account if not then fallback to company's default cwip account - cwip_account = get_asset_account( - "capital_work_in_progress_account", asset_category=item.asset_category, company=self.company - ) - - asset_amount = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate) - base_asset_amount = flt(item.base_net_amount + item.item_tax_amount) - remarks = self.get("remarks") or _("Accounting Entry for Asset") - - cwip_account_currency = get_account_currency(cwip_account) - # debit cwip account - debit_in_account_currency = ( - base_asset_amount if cwip_account_currency == self.company_currency else asset_amount - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=cwip_account, - cost_center=item.cost_center, - debit=base_asset_amount, - credit=0.0, - remarks=remarks, - against_account=arbnb_account, - debit_in_account_currency=debit_in_account_currency, - item=item, - ) - - asset_rbnb_currency = get_account_currency(arbnb_account) - # credit arbnb account - credit_in_account_currency = ( - base_asset_amount if asset_rbnb_currency == self.company_currency else asset_amount - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=arbnb_account, - cost_center=item.cost_center, - debit=0.0, - credit=base_asset_amount, - remarks=remarks, - against_account=cwip_account, - credit_in_account_currency=credit_in_account_currency, - item=item, - ) - - def add_lcv_gl_entries(self, item, gl_entries): - expenses_included_in_asset_valuation = self.get_company_default( - "expenses_included_in_asset_valuation" - ) - if not is_cwip_accounting_enabled(item.asset_category): - asset_account = get_asset_category_account( - asset_category=item.asset_category, fieldname="fixed_asset_account", company=self.company - ) - else: - # This returns company's default cwip account - asset_account = get_asset_account("capital_work_in_progress_account", company=self.company) - - remarks = self.get("remarks") or _("Accounting Entry for Stock") - - self.add_gl_entry( - gl_entries=gl_entries, - account=expenses_included_in_asset_valuation, - cost_center=item.cost_center, - debit=0.0, - credit=flt(item.landed_cost_voucher_amount), - remarks=remarks, - against_account=asset_account, - project=item.project, - item=item, - ) - - self.add_gl_entry( - gl_entries=gl_entries, - account=asset_account, - cost_center=item.cost_center, - debit=flt(item.landed_cost_voucher_amount), - credit=0.0, - remarks=remarks, - against_account=expenses_included_in_asset_valuation, - project=item.project, - item=item, - ) - def update_assets(self, item, valuation_rate): assets = frappe.db.get_all( "Asset", filters={"purchase_receipt": self.name, "item_code": item.item_code} @@ -869,6 +800,20 @@ class PurchaseReceipt(BuyingController): ) +def get_stock_value_difference(voucher_no, voucher_detail_no, warehouse): + return frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Purchase Receipt", + "voucher_no": voucher_no, + "voucher_detail_no": voucher_detail_no, + "warehouse": warehouse, + "is_cancelled": 0, + }, + "stock_value_difference", + ) + + def update_billed_amount_based_on_po(po_details, update_modified=True, pr_doc=None): po_billed_amt_details = get_billed_amount_against_po(po_details) From ed9cd7c92be709558d7534b398568dc684da9cb9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 18:53:08 +0530 Subject: [PATCH 28/31] fix: bom creator not able to amend / duplicate (backport #38128) (#38129) fix: bom creator not able to amend / duplicate (#38128) fix: bom creator not able to amend (cherry picked from commit 2df767f596417143ae3b855b2252585bf2bc96f9) Co-authored-by: rohitwaghchaure --- .../doctype/bom_creator/bom_creator.js | 2 +- .../doctype/bom_creator/bom_creator.py | 14 +++++++++++--- .../doctype/bom_creator_item/bom_creator_item.json | 3 +-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.js b/erpnext/manufacturing/doctype/bom_creator/bom_creator.js index 0cf2b51df2..243e52df5b 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.js +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.js @@ -15,7 +15,7 @@ frappe.ui.form.on("BOM Creator", { || frappe.bom_configurator.bom_configurator !== frm.doc.name)) { frm.trigger("build_tree"); } - } else { + } else if (!frm.doc.items?.length ) { let $parent = $(frm.fields_dict["bom_creator"].wrapper); $parent.empty(); frm.trigger("make_new_entry"); diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py index 058caa3686..49041a0929 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py @@ -6,7 +6,7 @@ from collections import OrderedDict import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import flt +from frappe.utils import cint, flt from erpnext.manufacturing.doctype.bom.bom import get_bom_item_rate @@ -91,11 +91,19 @@ class BOMCreator(Document): parent_reference = {row.idx: row.name for row in self.items} for row in self.items: - if row.fg_reference_id: + ref_id = "" + + if row.parent_row_no: + ref_id = parent_reference.get(cint(row.parent_row_no)) + + # Check whether the reference id of the FG Item has correct or not + if row.fg_reference_id and row.fg_reference_id == ref_id: continue if row.parent_row_no: - row.fg_reference_id = parent_reference.get(row.parent_row_no) + row.fg_reference_id = ref_id + elif row.fg_item == self.item_code: + row.fg_reference_id = self.name @frappe.whitelist() def add_boms(self): diff --git a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json index fdb5d3ad33..56acd8a1a6 100644 --- a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json +++ b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json @@ -215,7 +215,6 @@ "fieldname": "parent_row_no", "fieldtype": "Data", "label": "Parent Row No", - "no_copy": 1, "print_hide": 1 }, { @@ -231,7 +230,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-08-07 11:52:30.492233", + "modified": "2023-11-16 13:34:06.321061", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Creator Item", From 8276614c14d3143418119e8c31abe11a678c8951 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 20:27:35 +0530 Subject: [PATCH 29/31] fix: remove ESS role when not mapped to employee (backport #37867) (#38133) fix: remove ESS role when not mapped to employee (#37867) * fix: remove ESS role when not mapped to employee * fix: emp role removal on unlinking * fix: test case for user employee role mapping * fix: mapped employee and user on creation (cherry picked from commit 56b8d1b9277a1290b18c6745736bef8dfa4e6f90) Co-authored-by: Dany Robert --- erpnext/setup/doctype/employee/employee.js | 4 ++- erpnext/setup/doctype/employee/employee.py | 29 +++++++++++++++---- .../setup/doctype/employee/test_employee.py | 9 ++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/erpnext/setup/doctype/employee/employee.js b/erpnext/setup/doctype/employee/employee.js index 39a215f383..efc3fd1d33 100755 --- a/erpnext/setup/doctype/employee/employee.js +++ b/erpnext/setup/doctype/employee/employee.js @@ -81,8 +81,10 @@ frappe.ui.form.on("Employee", { employee: frm.doc.name, email: frm.doc.prefered_email }, + freeze: true, + freeze_message: __("Creating User..."), callback: function (r) { - frm.set_value("user_id", r.message); + frm.reload_doc(); } }); } diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py index 566392c327..ca78141e87 100755 --- a/erpnext/setup/doctype/employee/employee.py +++ b/erpnext/setup/doctype/employee/employee.py @@ -48,6 +48,9 @@ class Employee(NestedSet): else: existing_user_id = frappe.db.get_value("Employee", self.name, "user_id") if existing_user_id: + user = frappe.get_doc("User", existing_user_id) + validate_employee_role(user, ignore_emp_check=True) + user.save(ignore_permissions=True) remove_user_permission("Employee", self.name, existing_user_id) def after_rename(self, old, new, merge): @@ -230,12 +233,26 @@ class Employee(NestedSet): frappe.cache().hdel("employees_with_number", prev_number) -def validate_employee_role(doc, method): +def validate_employee_role(doc, method=None, ignore_emp_check=False): # called via User hook - if "Employee" in [d.role for d in doc.get("roles")]: - if not frappe.db.get_value("Employee", {"user_id": doc.name}): - frappe.msgprint(_("Please set User ID field in an Employee record to set Employee Role")) - doc.get("roles").remove(doc.get("roles", {"role": "Employee"})[0]) + if not ignore_emp_check: + if frappe.db.get_value("Employee", {"user_id": doc.name}): + return + + user_roles = [d.role for d in doc.get("roles")] + if "Employee" in user_roles: + frappe.msgprint( + _("User {0}: Removed Employee role as there is no mapped employee.").format(doc.name) + ) + doc.get("roles").remove(doc.get("roles", {"role": "Employee"})[0]) + + if "Employee Self Service" in user_roles: + frappe.msgprint( + _("User {0}: Removed Employee Self Service role as there is no mapped employee.").format( + doc.name + ) + ) + doc.get("roles").remove(doc.get("roles", {"role": "Employee Self Service"})[0]) def update_user_permissions(doc, method): @@ -347,6 +364,8 @@ def create_user(employee, user=None, email=None): } ) user.insert() + emp.user_id = user.name + emp.save() return user.name diff --git a/erpnext/setup/doctype/employee/test_employee.py b/erpnext/setup/doctype/employee/test_employee.py index 5a693c5eff..9b70683626 100644 --- a/erpnext/setup/doctype/employee/test_employee.py +++ b/erpnext/setup/doctype/employee/test_employee.py @@ -25,6 +25,15 @@ class TestEmployee(unittest.TestCase): employee1_doc.status = "Left" self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save) + def test_user_has_employee(self): + employee = make_employee("test_emp_user_creation@company.com") + employee_doc = frappe.get_doc("Employee", employee) + user = employee_doc.user_id + self.assertTrue("Employee" in frappe.get_roles(user)) + employee_doc.user_id = "" + employee_doc.save() + self.assertTrue("Employee" not in frappe.get_roles(user)) + def tearDown(self): frappe.db.rollback() From c9674683d1ee97d9cbe53acd80bb04ab3e572178 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 16 Nov 2023 10:04:50 +0530 Subject: [PATCH 30/31] fix: add revaluation journal filter in Payable report (cherry picked from commit 134201794ace2863c02458b3e1797da816802d06) --- erpnext/accounts/report/accounts_payable/accounts_payable.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index eff705dafa..0979cffbf3 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -144,6 +144,11 @@ frappe.query_reports["Accounts Payable"] = { "label": __("Show Future Payments"), "fieldtype": "Check", }, + { + "fieldname": "for_revaluation_journals", + "label": __("Revaluation Journals"), + "fieldtype": "Check", + }, { "fieldname": "ignore_accounts", "label": __("Group by Voucher"), From 20e15ebd2274c299f300d0e3d7aaf28e2c979b42 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 17 Nov 2023 12:40:18 +0530 Subject: [PATCH 31/31] fix: allow regional gl in payment entry for gl preview (#38136) fix: allow regional gl in payment entry for gl preview (#38136) (cherry picked from commit 7e43d7b1312fd665bdcde7db7c67d192f746e60a) Co-authored-by: Smit Vora --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 025277b40c..cad20abfd3 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1022,6 +1022,7 @@ class PaymentEntry(AccountsController): self.add_bank_gl_entries(gl_entries) self.add_deductions_gl_entries(gl_entries) self.add_tax_gl_entries(gl_entries) + add_regional_gl_entries(gl_entries, self) return gl_entries def make_gl_entries(self, cancel=0, adv_adj=0): @@ -2610,3 +2611,8 @@ def make_payment_order(source_name, target_doc=None): ) return doclist + + +@erpnext.allow_regional +def add_regional_gl_entries(gl_entries, doc): + return