From 1f099752d2b601d11c6bf6f2d09877dd68e27e9a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 12 Oct 2024 18:32:30 +0530 Subject: [PATCH 01/22] fix: validation for corrective job card (backport #43555) (backport #43558) (#43628) fix: validation for corrective job card (backport #43555) (#43558) * fix: validation for corrective job card (#43555) (cherry picked from commit 7a0a893d08217b3131f2a1d38db7c0be51aed82b) # Conflicts: # erpnext/manufacturing/doctype/job_card/job_card.py * chore: fix conflicts * chore: fix linters issue --------- Co-authored-by: rohitwaghchaure (cherry picked from commit cf0fa0db7b7c2a9a5dbd3802ddec1db757afeb24) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- erpnext/manufacturing/doctype/job_card/job_card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 8c562eff83f..ccabf14cde4 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -521,7 +521,7 @@ class JobCard(Document): self.set_transferred_qty() def validate_transfer_qty(self): - if self.items and self.transferred_qty < self.for_quantity: + if not self.is_corrective_job_card and self.items and self.transferred_qty < self.for_quantity: frappe.throw( _( "Materials needs to be transferred to the work in progress warehouse for the job card {0}" From 01d6c1081b573d93ae2b382874b3064e9f616a9c Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhoda Date: Sun, 13 Oct 2024 19:58:20 +0530 Subject: [PATCH 02/22] fix: Use `ref_doc.get()` for `party_account_currency` (cherry picked from commit b79549422aff1c9b404e759a0f14efc01823c1b5) --- erpnext/accounts/doctype/payment_request/payment_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index f24b2891723..47cb3fd812f 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -538,7 +538,7 @@ def make_payment_request(**args): ) party_type = args.get("party_type") or "Customer" - party_account_currency = ref_doc.party_account_currency + party_account_currency = ref_doc.get("party_account_currency") if not party_account_currency: party_account = get_party_account(party_type, ref_doc.get(party_type.lower()), ref_doc.company) From ae47fb9617c1cfd284ab0ce2bf6ab90e254899a4 Mon Sep 17 00:00:00 2001 From: Ninad1306 Date: Fri, 11 Oct 2024 11:33:27 +0530 Subject: [PATCH 03/22] fix: ignore free item when qty is zero (cherry picked from commit 7ae98f77eeee9b44d0afb9899c826a5acd4eb126) --- erpnext/accounts/doctype/pricing_rule/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index b5d4af1ab90..0032e539e44 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -644,6 +644,9 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): if pricing_rule.round_free_qty: qty = math.floor(qty) + if not qty: + return + free_item_data_args = { "item_code": free_item, "qty": qty, From 3b1f0c191e4754e51b98f8fcccc441dc7948d75d Mon Sep 17 00:00:00 2001 From: Ninad1306 Date: Fri, 11 Oct 2024 14:02:50 +0530 Subject: [PATCH 04/22] test: test case to validate free item is ignored when qty is zero (cherry picked from commit a2b41a0c16c9d95c6ca609e1be100d8924d8028b) --- erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 93a91584ecd..90e606d7861 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -981,6 +981,12 @@ class TestPricingRule(FrappeTestCase): self.assertEqual(so.items[1].item_code, "_Test Item") self.assertEqual(so.items[1].qty, 3) + so = make_sales_order(item_code="_Test Item", qty=5, do_not_submit=1) + so.items[0].qty = 1 + del so.items[-1] + so.save() + self.assertEqual(len(so.items), 1) + def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self): frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1") frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2") From fc7207bcd37fbb2fc943c47b80841147461b2f76 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Thu, 10 Oct 2024 12:33:12 +0530 Subject: [PATCH 05/22] refactor: remove 'format:' based naming (cherry picked from commit e8e1ec0e851496a8096184a2dddebea8efcd6551) # Conflicts: # erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json --- .../doctype/unreconcile_payment/unreconcile_payment.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json index f906dc6cec6..8ba79581d50 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json @@ -1,7 +1,5 @@ { "actions": [], - "allow_rename": 1, - "autoname": "format:UNREC-{#####}", "creation": "2023-08-22 10:26:34.421423", "default_view": "List", "doctype": "DocType", @@ -58,11 +56,14 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2023-08-28 17:42:50.261377", +======= + "modified": "2024-10-10 12:03:50.022444", +>>>>>>> e8e1ec0e85 (refactor: remove 'format:' based naming) "modified_by": "Administrator", "module": "Accounts", "name": "Unreconcile Payment", - "naming_rule": "Expression", "owner": "Administrator", "permissions": [ { From 065465626ed9ae08930408e33faa9567329aa7fb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 15 Oct 2024 06:00:47 +0530 Subject: [PATCH 06/22] chore: resolve conflict --- .../doctype/unreconcile_payment/unreconcile_payment.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json index 8ba79581d50..f2c2f380fe9 100644 --- a/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json +++ b/erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json @@ -56,11 +56,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], -<<<<<<< HEAD - "modified": "2023-08-28 17:42:50.261377", -======= "modified": "2024-10-10 12:03:50.022444", ->>>>>>> e8e1ec0e85 (refactor: remove 'format:' based naming) "modified_by": "Administrator", "module": "Accounts", "name": "Unreconcile Payment", From c520a7c6c137f4e4e26d75f119cfb5047c76c3bf Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 8 Oct 2024 16:18:31 +0530 Subject: [PATCH 07/22] fix: update item details with actual quantity. (cherry picked from commit 9dbdfec9b7a4bcc38399a4d849dcc32b54bf9d4a) --- erpnext/selling/doctype/sales_order/sales_order.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index f201228c2b1..4cedebe2668 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -13,6 +13,7 @@ from frappe.model.mapper import get_mapped_doc from frappe.model.utils import get_fetch_values from frappe.query_builder.functions import Sum from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, nowdate, strip_html +from erpnext.stock.get_item_details import get_bin_details from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( unlink_inter_company_doc, @@ -607,6 +608,7 @@ def make_material_request(source_name, target_doc=None): target.project = source_parent.project target.qty = get_remaining_qty(source) target.stock_qty = flt(target.qty) * flt(target.conversion_factor) + target.actual_qty = get_bin_details(target.item_code, target.warehouse, source_parent.company, True).get("actual_qty", 0) args = target.as_dict().copy() args.update( From dc2483bb923e25b9ece910e4ffae781ebdbbf7bf Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 8 Oct 2024 16:24:28 +0530 Subject: [PATCH 08/22] test: Validate the actual quantity when creating a material request from the sales order (cherry picked from commit 17fdd426455aa3daab828444a2ecde837936faa2) --- erpnext/selling/doctype/sales_order/test_sales_order.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 74f781d8982..e075bf702f7 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -8,6 +8,8 @@ import frappe.permissions from frappe.core.doctype.user_permission.test_user_permission import create_user from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, flt, getdate, nowdate, today +from erpnext.stock.get_item_details import get_bin_details + from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.controllers.accounts_controller import update_child_qty_rate @@ -99,6 +101,10 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase): self.assertEqual(mr.material_request_type, "Purchase") self.assertEqual(len(mr.get("items")), len(so.get("items"))) + for item in mr.get("items"): + actual_qty = get_bin_details(item.item_code, item.warehouse, mr.company, True).get("actual_qty", 0) + self.assertEqual(flt(item.actual_qty), actual_qty) + def test_make_delivery_note(self): so = make_sales_order(do_not_submit=True) From 699b09846ac2bff4ec9a0ca2118202d31fb6ec2d Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 8 Oct 2024 17:49:12 +0530 Subject: [PATCH 09/22] fix: update formatings (cherry picked from commit 5f4a523340209b29ebd9932228b0715eb1035103) # Conflicts: # erpnext/selling/doctype/sales_order/sales_order.py --- erpnext/selling/doctype/sales_order/sales_order.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 4cedebe2668..86e2c7ea4de 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -13,7 +13,6 @@ from frappe.model.mapper import get_mapped_doc from frappe.model.utils import get_fetch_values from frappe.query_builder.functions import Sum from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, nowdate, strip_html -from erpnext.stock.get_item_details import get_bin_details from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( unlink_inter_company_doc, @@ -31,7 +30,15 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import ( from erpnext.selling.doctype.customer.customer import check_credit_limit from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.stock.doctype.item.item import get_item_defaults +<<<<<<< HEAD from erpnext.stock.get_item_details import get_default_bom, get_price_list_rate +======= +from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( + get_sre_reserved_qty_details_for_voucher, + has_reserved_stock, +) +from erpnext.stock.get_item_details import get_bin_details, get_default_bom, get_price_list_rate +>>>>>>> 5f4a523340 (fix: update formatings) from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty form_grid_templates = {"items": "templates/form_grid/item_grid.html"} @@ -608,7 +615,9 @@ def make_material_request(source_name, target_doc=None): target.project = source_parent.project target.qty = get_remaining_qty(source) target.stock_qty = flt(target.qty) * flt(target.conversion_factor) - target.actual_qty = get_bin_details(target.item_code, target.warehouse, source_parent.company, True).get("actual_qty", 0) + target.actual_qty = get_bin_details( + target.item_code, target.warehouse, source_parent.company, True + ).get("actual_qty", 0) args = target.as_dict().copy() args.update( From a8ad608fd8a2a40a1805c82cbebab8f440c76a5b Mon Sep 17 00:00:00 2001 From: Bhavan23 Date: Tue, 8 Oct 2024 18:14:00 +0530 Subject: [PATCH 10/22] fix: update formatings (cherry picked from commit 50442973219cd246f07d6178ca3465c59778808b) --- erpnext/selling/doctype/sales_order/test_sales_order.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index e075bf702f7..b1207ee97a1 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -8,8 +8,6 @@ import frappe.permissions from frappe.core.doctype.user_permission.test_user_permission import create_user from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, flt, getdate, nowdate, today -from erpnext.stock.get_item_details import get_bin_details - from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.controllers.accounts_controller import update_child_qty_rate @@ -32,6 +30,7 @@ from erpnext.selling.doctype.sales_order.sales_order import ( ) from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.stock.get_item_details import get_bin_details class TestSalesOrder(AccountsTestMixin, FrappeTestCase): @@ -102,7 +101,9 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase): self.assertEqual(len(mr.get("items")), len(so.get("items"))) for item in mr.get("items"): - actual_qty = get_bin_details(item.item_code, item.warehouse, mr.company, True).get("actual_qty", 0) + actual_qty = get_bin_details(item.item_code, item.warehouse, mr.company, True).get( + "actual_qty", 0 + ) self.assertEqual(flt(item.actual_qty), actual_qty) def test_make_delivery_note(self): From 7eac9cc1dbaacbee77d385c4f8310ebaa3f52b90 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 15 Oct 2024 11:57:02 +0530 Subject: [PATCH 11/22] fix: zero incoming rate for delivery note return (#43642) (cherry picked from commit 6087a57b0c84b4a35a4e0797a321f13f07fa8d43) # Conflicts: # erpnext/stock/doctype/delivery_note/test_delivery_note.py --- erpnext/controllers/selling_controller.py | 10 + .../delivery_note/test_delivery_note.py | 440 ++++++++++++++++++ erpnext/stock/stock_ledger.py | 9 + 3 files changed, 459 insertions(+) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 827f204e40e..3b4d32b7305 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -456,6 +456,16 @@ class SellingController(StockController): raise_error_if_no_rate=False, ) + if ( + not d.incoming_rate + and self.get("return_against") + and self.get("is_return") + and get_valuation_method(d.item_code) == "Moving Average" + ): + d.incoming_rate = get_rate_for_return( + self.doctype, self.name, d.item_code, self.return_against, item_row=d + ) + # For internal transfers use incoming rate as the valuation rate if self.is_internal_transfer(): if self.doctype == "Delivery Note" or self.get("update_stock"): diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index e9b07e76b2d..afd49c99033 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1449,6 +1449,446 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(so.items[0].rate, rate) self.assertEqual(dn.items[0].rate, so.items[0].rate) +<<<<<<< HEAD +======= + def test_use_serial_batch_fields_for_packed_items(self): + bundle_item = make_item("Test _Packed Product Bundle Item ", {"is_stock_item": 0}) + serial_item = make_item( + "Test _Packed Serial Item ", + {"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SN-TESTSERIAL-.#####"}, + ) + batch_item = make_item( + "Test _Packed Batch Item ", + { + "is_stock_item": 1, + "has_batch_no": 1, + "batch_number_series": "BATCH-TESTSERIAL-.#####", + "create_new_batch": 1, + }, + ) + make_product_bundle(parent=bundle_item.name, items=[serial_item.name, batch_item.name]) + + item_details = {} + for item in [serial_item, batch_item]: + se = make_stock_entry(item_code=item.name, target="_Test Warehouse - _TC", qty=5, basic_rate=100) + item_details[item.name] = se.items[0].serial_and_batch_bundle + + dn = create_delivery_note(item_code=bundle_item.name, qty=1, do_not_submit=True) + serial_no = "" + for row in dn.packed_items: + row.use_serial_batch_fields = 1 + + if row.item_code == serial_item.name: + serial_and_batch_bundle = item_details[serial_item.name] + row.serial_no = get_serial_nos_from_bundle(serial_and_batch_bundle)[3] + serial_no = row.serial_no + else: + serial_and_batch_bundle = item_details[batch_item.name] + row.batch_no = get_batch_from_bundle(serial_and_batch_bundle) + + dn.submit() + dn.load_from_db() + + for row in dn.packed_items: + self.assertTrue(row.serial_no or row.batch_no) + self.assertTrue(row.serial_and_batch_bundle) + + if row.serial_no: + self.assertEqual(row.serial_no, serial_no) + + def test_delivery_note_legacy_serial_no_valuation(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + + frappe.flags.ignore_serial_batch_bundle_validation = True + sn_item = "Old Serial NO Item Valuation Test - 2" + make_item( + sn_item, + { + "has_serial_no": 1, + "serial_no_series": "SN-SOVOSN-.####", + "is_stock_item": 1, + }, + ) + + serial_nos = [ + "SN-SOVOSN-1234", + "SN-SOVOSN-2234", + ] + + for sn in serial_nos: + if not frappe.db.exists("Serial No", sn): + sn_doc = frappe.get_doc( + { + "doctype": "Serial No", + "item_code": sn_item, + "serial_no": sn, + } + ) + sn_doc.insert() + + warehouse = "_Test Warehouse - _TC" + company = frappe.db.get_value("Warehouse", warehouse, "company") + se_doc = make_stock_entry( + item_code=sn_item, + company=company, + target="_Test Warehouse - _TC", + qty=2, + basic_rate=150, + do_not_submit=1, + use_serial_batch_fields=0, + ) + se_doc.submit() + + se_doc.items[0].db_set("serial_no", "\n".join(serial_nos)) + + sle_data = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_no": se_doc.name, "voucher_type": "Stock Entry"}, + )[0] + + sle_doc = frappe.get_doc("Stock Ledger Entry", sle_data.name) + self.assertFalse(sle_doc.serial_no) + sle_doc.db_set("serial_no", "\n".join(serial_nos)) + sle_doc.reload() + self.assertTrue(sle_doc.serial_no) + self.assertFalse(sle_doc.is_cancelled) + + for sn in serial_nos: + sn_doc = frappe.get_doc("Serial No", sn) + sn_doc.db_set( + { + "status": "Active", + "warehouse": warehouse, + } + ) + + self.assertEqual(sorted(get_serial_nos(se_doc.items[0].serial_no)), sorted(serial_nos)) + frappe.flags.ignore_serial_batch_bundle_validation = False + + se_doc = make_stock_entry( + item_code=sn_item, + company=company, + target="_Test Warehouse - _TC", + qty=2, + basic_rate=200, + ) + + serial_nos.extend(get_serial_nos_from_bundle(se_doc.items[0].serial_and_batch_bundle)) + + dn = create_delivery_note( + item_code=sn_item, + qty=3, + rate=500, + warehouse=warehouse, + company=company, + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + use_serial_batch_fields=1, + serial_no="\n".join(serial_nos[0:3]), + ) + + dn.reload() + + sle_data = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_no": dn.name, "voucher_type": "Delivery Note"}, + fields=["stock_value_difference", "actual_qty"], + )[0] + + self.assertEqual(sle_data.actual_qty, 3 * -1) + self.assertEqual(sle_data.stock_value_difference, 500.0 * -1) + + dn = create_delivery_note( + item_code=sn_item, + qty=1, + rate=500, + warehouse=warehouse, + company=company, + expense_account="Cost of Goods Sold - _TC", + cost_center="Main - _TC", + use_serial_batch_fields=1, + serial_no=serial_nos[-1], + ) + + dn.reload() + + sle_data = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_no": dn.name, "voucher_type": "Delivery Note"}, + fields=["stock_value_difference", "actual_qty"], + )[0] + + self.assertEqual(sle_data.actual_qty, 1 * -1) + self.assertEqual(sle_data.stock_value_difference, 200.0 * -1) + + def test_sales_return_batch_no_for_batched_item_in_dn(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + + item_code = make_item( + "Test Batched Item for Sales Return 11", + properties={ + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "B11-TESTBATCH.#####", + "is_stock_item": 1, + }, + ).name + + se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100) + + batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + dn = create_delivery_note( + item_code=item_code, + qty=5, + rate=500, + use_serial_batch_fields=0, + batch_no=batch_no, + ) + + dn_return = make_sales_return(dn.name) + dn_return.save().submit() + returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle) + self.assertEqual(batch_no, returned_batch_no) + + def test_partial_sales_return_batch_no_for_batched_item_in_dn(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + + item_code = make_item( + "Test Partial Batched Item for Sales Return 11", + properties={ + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "BPART11-TESTBATCH.#####", + "is_stock_item": 1, + }, + ).name + + se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100) + + batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + dn = create_delivery_note( + item_code=item_code, + qty=5, + rate=500, + use_serial_batch_fields=0, + batch_no=batch_no, + ) + + dn_return = make_sales_return(dn.name) + dn_return.items[0].qty = 3 * -1 + dn_return.save().submit() + + returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle) + self.assertEqual(batch_no, returned_batch_no) + sabb_qty = frappe.db.get_value( + "Serial and Batch Bundle", dn_return.items[0].serial_and_batch_bundle, "total_qty" + ) + self.assertEqual(sabb_qty, 3) + + dn_return = make_sales_return(dn.name) + dn_return.items[0].qty = 2 * -1 + dn_return.save().submit() + + returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle) + self.assertEqual(batch_no, returned_batch_no) + + sabb_qty = frappe.db.get_value( + "Serial and Batch Bundle", dn_return.items[0].serial_and_batch_bundle, "total_qty" + ) + self.assertEqual(sabb_qty, 2) + + def test_sales_return_serial_no_for_serial_item_in_dn(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + + item_code = make_item( + "Test Serial Item for Sales Return 11", + properties={ + "has_serial_no": 1, + "serial_no_series": "SNN11-TESTBATCH.#####", + "is_stock_item": 1, + }, + ).name + + se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100) + + serial_nos = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle) + dn = create_delivery_note( + item_code=item_code, + qty=5, + rate=500, + use_serial_batch_fields=0, + serial_no=serial_nos, + ) + + dn_return = make_sales_return(dn.name) + dn_return.save().submit() + returned_serial_nos = get_serial_nos_from_bundle(dn_return.items[0].serial_and_batch_bundle) + self.assertEqual(serial_nos, returned_serial_nos) + + def test_same_posting_date_and_posting_time(self): + item_code = make_item( + "Test Same Posting Datetime Item", + properties={ + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "SS-ART11-TESTBATCH.#####", + "is_stock_item": 1, + }, + ).name + + se = make_stock_entry( + item_code=item_code, + target="_Test Warehouse - _TC", + qty=100, + basic_rate=50, + posting_date=add_days(nowdate(), -1), + ) + + batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + + posting_date = today() + posting_time = nowtime() + dn1 = create_delivery_note( + posting_date=posting_date, + posting_time=posting_time, + item_code=item_code, + rate=300, + qty=25, + batch_no=batch_no, + use_serial_batch_fields=1, + ) + + dn2 = create_delivery_note( + posting_date=posting_date, + posting_time=posting_time, + item_code=item_code, + rate=300, + qty=25, + batch_no=batch_no, + use_serial_batch_fields=1, + ) + + dn3 = create_delivery_note( + posting_date=posting_date, + posting_time=posting_time, + item_code=item_code, + rate=300, + qty=25, + batch_no=batch_no, + use_serial_batch_fields=1, + ) + + dn4 = create_delivery_note( + posting_date=posting_date, + posting_time=posting_time, + item_code=item_code, + rate=300, + qty=25, + batch_no=batch_no, + use_serial_batch_fields=1, + ) + + for dn in [dn1, dn2, dn3, dn4]: + sles = frappe.get_all( + "Stock Ledger Entry", + fields=["stock_value_difference", "actual_qty"], + filters={"is_cancelled": 0, "voucher_no": dn.name, "docstatus": 1}, + ) + + for sle in sles: + self.assertEqual(sle.actual_qty, 25.0 * -1) + self.assertEqual(sle.stock_value_difference, 25.0 * 50 * -1) + + dn5 = create_delivery_note( + posting_date=posting_date, + posting_time=posting_time, + item_code=item_code, + rate=300, + qty=25, + batch_no=batch_no, + use_serial_batch_fields=1, + do_not_submit=True, + ) + + self.assertRaises(frappe.ValidationError, dn5.submit) + + def test_warranty_expiry_date_for_serial_item(self): + item_code = make_item( + "Test Warranty Expiry Date Item", + properties={ + "has_serial_no": 1, + "serial_no_series": "TWE.#####", + "is_stock_item": 1, + "warranty_period": 100, + }, + ).name + + se = make_stock_entry( + item_code=item_code, + target="_Test Warehouse - _TC", + qty=2, + basic_rate=50, + posting_date=nowdate(), + ) + + serial_nos = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle) + create_delivery_note( + item_code=item_code, + qty=2, + rate=300, + use_serial_batch_fields=0, + serial_no=serial_nos, + ) + + for row in serial_nos: + sn = frappe.get_doc("Serial No", row) + self.assertEqual(getdate(sn.warranty_expiry_date), getdate(add_days(nowdate(), 100))) + self.assertEqual(sn.status, "Delivered") + self.assertEqual(sn.warranty_period, 100) + + def test_batch_return_dn(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + + item_code = make_item( + "Test Batch Return DN Item 1", + properties={ + "has_batch_no": 1, + "valuation_method": "Moving Average", + "create_new_batch": 1, + "batch_number_series": "TBRDN1-.#####", + "is_stock_item": 1, + }, + ).name + + se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100) + + batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + dn = create_delivery_note( + item_code=item_code, + qty=5, + rate=500, + use_serial_batch_fields=1, + batch_no=batch_no, + ) + + dn_return = make_sales_return(dn.name) + dn_return.save().submit() + + self.assertEqual(dn_return.items[0].qty, 5 * -1) + + returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle) + self.assertEqual(batch_no, returned_batch_no) + + stock_value_difference = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_no": dn_return.name, "voucher_type": "Delivery Note"}, + "stock_value_difference", + ) + + self.assertEqual(stock_value_difference, 100.0 * 5) + +>>>>>>> 6087a57b0c (fix: zero incoming rate for delivery note return (#43642)) def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 8ebc2aa9c04..a81b57168de 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -775,6 +775,15 @@ class update_entries_after: } ) + if not rate and sle.voucher_type in ["Delivery Note", "Sales Invoice"]: + rate = get_rate_for_return( + sle.voucher_type, + sle.voucher_no, + sle.item_code, + voucher_detail_no=sle.voucher_detail_no, + sle=sle, + ) + else: rate = get_rate_for_return( sle.voucher_type, From a585a7029f341ad430616581d1baddf50fec1ab7 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 15 Oct 2024 12:08:24 +0530 Subject: [PATCH 12/22] chore: fix conflicts --- .../delivery_note/test_delivery_note.py | 404 +----------------- 1 file changed, 2 insertions(+), 402 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index afd49c99033..85fbda5a28e 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1449,404 +1449,6 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(so.items[0].rate, rate) self.assertEqual(dn.items[0].rate, so.items[0].rate) -<<<<<<< HEAD -======= - def test_use_serial_batch_fields_for_packed_items(self): - bundle_item = make_item("Test _Packed Product Bundle Item ", {"is_stock_item": 0}) - serial_item = make_item( - "Test _Packed Serial Item ", - {"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SN-TESTSERIAL-.#####"}, - ) - batch_item = make_item( - "Test _Packed Batch Item ", - { - "is_stock_item": 1, - "has_batch_no": 1, - "batch_number_series": "BATCH-TESTSERIAL-.#####", - "create_new_batch": 1, - }, - ) - make_product_bundle(parent=bundle_item.name, items=[serial_item.name, batch_item.name]) - - item_details = {} - for item in [serial_item, batch_item]: - se = make_stock_entry(item_code=item.name, target="_Test Warehouse - _TC", qty=5, basic_rate=100) - item_details[item.name] = se.items[0].serial_and_batch_bundle - - dn = create_delivery_note(item_code=bundle_item.name, qty=1, do_not_submit=True) - serial_no = "" - for row in dn.packed_items: - row.use_serial_batch_fields = 1 - - if row.item_code == serial_item.name: - serial_and_batch_bundle = item_details[serial_item.name] - row.serial_no = get_serial_nos_from_bundle(serial_and_batch_bundle)[3] - serial_no = row.serial_no - else: - serial_and_batch_bundle = item_details[batch_item.name] - row.batch_no = get_batch_from_bundle(serial_and_batch_bundle) - - dn.submit() - dn.load_from_db() - - for row in dn.packed_items: - self.assertTrue(row.serial_no or row.batch_no) - self.assertTrue(row.serial_and_batch_bundle) - - if row.serial_no: - self.assertEqual(row.serial_no, serial_no) - - def test_delivery_note_legacy_serial_no_valuation(self): - from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return - from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos - - frappe.flags.ignore_serial_batch_bundle_validation = True - sn_item = "Old Serial NO Item Valuation Test - 2" - make_item( - sn_item, - { - "has_serial_no": 1, - "serial_no_series": "SN-SOVOSN-.####", - "is_stock_item": 1, - }, - ) - - serial_nos = [ - "SN-SOVOSN-1234", - "SN-SOVOSN-2234", - ] - - for sn in serial_nos: - if not frappe.db.exists("Serial No", sn): - sn_doc = frappe.get_doc( - { - "doctype": "Serial No", - "item_code": sn_item, - "serial_no": sn, - } - ) - sn_doc.insert() - - warehouse = "_Test Warehouse - _TC" - company = frappe.db.get_value("Warehouse", warehouse, "company") - se_doc = make_stock_entry( - item_code=sn_item, - company=company, - target="_Test Warehouse - _TC", - qty=2, - basic_rate=150, - do_not_submit=1, - use_serial_batch_fields=0, - ) - se_doc.submit() - - se_doc.items[0].db_set("serial_no", "\n".join(serial_nos)) - - sle_data = frappe.get_all( - "Stock Ledger Entry", - filters={"voucher_no": se_doc.name, "voucher_type": "Stock Entry"}, - )[0] - - sle_doc = frappe.get_doc("Stock Ledger Entry", sle_data.name) - self.assertFalse(sle_doc.serial_no) - sle_doc.db_set("serial_no", "\n".join(serial_nos)) - sle_doc.reload() - self.assertTrue(sle_doc.serial_no) - self.assertFalse(sle_doc.is_cancelled) - - for sn in serial_nos: - sn_doc = frappe.get_doc("Serial No", sn) - sn_doc.db_set( - { - "status": "Active", - "warehouse": warehouse, - } - ) - - self.assertEqual(sorted(get_serial_nos(se_doc.items[0].serial_no)), sorted(serial_nos)) - frappe.flags.ignore_serial_batch_bundle_validation = False - - se_doc = make_stock_entry( - item_code=sn_item, - company=company, - target="_Test Warehouse - _TC", - qty=2, - basic_rate=200, - ) - - serial_nos.extend(get_serial_nos_from_bundle(se_doc.items[0].serial_and_batch_bundle)) - - dn = create_delivery_note( - item_code=sn_item, - qty=3, - rate=500, - warehouse=warehouse, - company=company, - expense_account="Cost of Goods Sold - _TC", - cost_center="Main - _TC", - use_serial_batch_fields=1, - serial_no="\n".join(serial_nos[0:3]), - ) - - dn.reload() - - sle_data = frappe.get_all( - "Stock Ledger Entry", - filters={"voucher_no": dn.name, "voucher_type": "Delivery Note"}, - fields=["stock_value_difference", "actual_qty"], - )[0] - - self.assertEqual(sle_data.actual_qty, 3 * -1) - self.assertEqual(sle_data.stock_value_difference, 500.0 * -1) - - dn = create_delivery_note( - item_code=sn_item, - qty=1, - rate=500, - warehouse=warehouse, - company=company, - expense_account="Cost of Goods Sold - _TC", - cost_center="Main - _TC", - use_serial_batch_fields=1, - serial_no=serial_nos[-1], - ) - - dn.reload() - - sle_data = frappe.get_all( - "Stock Ledger Entry", - filters={"voucher_no": dn.name, "voucher_type": "Delivery Note"}, - fields=["stock_value_difference", "actual_qty"], - )[0] - - self.assertEqual(sle_data.actual_qty, 1 * -1) - self.assertEqual(sle_data.stock_value_difference, 200.0 * -1) - - def test_sales_return_batch_no_for_batched_item_in_dn(self): - from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return - - item_code = make_item( - "Test Batched Item for Sales Return 11", - properties={ - "has_batch_no": 1, - "create_new_batch": 1, - "batch_number_series": "B11-TESTBATCH.#####", - "is_stock_item": 1, - }, - ).name - - se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100) - - batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) - dn = create_delivery_note( - item_code=item_code, - qty=5, - rate=500, - use_serial_batch_fields=0, - batch_no=batch_no, - ) - - dn_return = make_sales_return(dn.name) - dn_return.save().submit() - returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle) - self.assertEqual(batch_no, returned_batch_no) - - def test_partial_sales_return_batch_no_for_batched_item_in_dn(self): - from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return - - item_code = make_item( - "Test Partial Batched Item for Sales Return 11", - properties={ - "has_batch_no": 1, - "create_new_batch": 1, - "batch_number_series": "BPART11-TESTBATCH.#####", - "is_stock_item": 1, - }, - ).name - - se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100) - - batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) - dn = create_delivery_note( - item_code=item_code, - qty=5, - rate=500, - use_serial_batch_fields=0, - batch_no=batch_no, - ) - - dn_return = make_sales_return(dn.name) - dn_return.items[0].qty = 3 * -1 - dn_return.save().submit() - - returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle) - self.assertEqual(batch_no, returned_batch_no) - sabb_qty = frappe.db.get_value( - "Serial and Batch Bundle", dn_return.items[0].serial_and_batch_bundle, "total_qty" - ) - self.assertEqual(sabb_qty, 3) - - dn_return = make_sales_return(dn.name) - dn_return.items[0].qty = 2 * -1 - dn_return.save().submit() - - returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle) - self.assertEqual(batch_no, returned_batch_no) - - sabb_qty = frappe.db.get_value( - "Serial and Batch Bundle", dn_return.items[0].serial_and_batch_bundle, "total_qty" - ) - self.assertEqual(sabb_qty, 2) - - def test_sales_return_serial_no_for_serial_item_in_dn(self): - from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return - - item_code = make_item( - "Test Serial Item for Sales Return 11", - properties={ - "has_serial_no": 1, - "serial_no_series": "SNN11-TESTBATCH.#####", - "is_stock_item": 1, - }, - ).name - - se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100) - - serial_nos = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle) - dn = create_delivery_note( - item_code=item_code, - qty=5, - rate=500, - use_serial_batch_fields=0, - serial_no=serial_nos, - ) - - dn_return = make_sales_return(dn.name) - dn_return.save().submit() - returned_serial_nos = get_serial_nos_from_bundle(dn_return.items[0].serial_and_batch_bundle) - self.assertEqual(serial_nos, returned_serial_nos) - - def test_same_posting_date_and_posting_time(self): - item_code = make_item( - "Test Same Posting Datetime Item", - properties={ - "has_batch_no": 1, - "create_new_batch": 1, - "batch_number_series": "SS-ART11-TESTBATCH.#####", - "is_stock_item": 1, - }, - ).name - - se = make_stock_entry( - item_code=item_code, - target="_Test Warehouse - _TC", - qty=100, - basic_rate=50, - posting_date=add_days(nowdate(), -1), - ) - - batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) - - posting_date = today() - posting_time = nowtime() - dn1 = create_delivery_note( - posting_date=posting_date, - posting_time=posting_time, - item_code=item_code, - rate=300, - qty=25, - batch_no=batch_no, - use_serial_batch_fields=1, - ) - - dn2 = create_delivery_note( - posting_date=posting_date, - posting_time=posting_time, - item_code=item_code, - rate=300, - qty=25, - batch_no=batch_no, - use_serial_batch_fields=1, - ) - - dn3 = create_delivery_note( - posting_date=posting_date, - posting_time=posting_time, - item_code=item_code, - rate=300, - qty=25, - batch_no=batch_no, - use_serial_batch_fields=1, - ) - - dn4 = create_delivery_note( - posting_date=posting_date, - posting_time=posting_time, - item_code=item_code, - rate=300, - qty=25, - batch_no=batch_no, - use_serial_batch_fields=1, - ) - - for dn in [dn1, dn2, dn3, dn4]: - sles = frappe.get_all( - "Stock Ledger Entry", - fields=["stock_value_difference", "actual_qty"], - filters={"is_cancelled": 0, "voucher_no": dn.name, "docstatus": 1}, - ) - - for sle in sles: - self.assertEqual(sle.actual_qty, 25.0 * -1) - self.assertEqual(sle.stock_value_difference, 25.0 * 50 * -1) - - dn5 = create_delivery_note( - posting_date=posting_date, - posting_time=posting_time, - item_code=item_code, - rate=300, - qty=25, - batch_no=batch_no, - use_serial_batch_fields=1, - do_not_submit=True, - ) - - self.assertRaises(frappe.ValidationError, dn5.submit) - - def test_warranty_expiry_date_for_serial_item(self): - item_code = make_item( - "Test Warranty Expiry Date Item", - properties={ - "has_serial_no": 1, - "serial_no_series": "TWE.#####", - "is_stock_item": 1, - "warranty_period": 100, - }, - ).name - - se = make_stock_entry( - item_code=item_code, - target="_Test Warehouse - _TC", - qty=2, - basic_rate=50, - posting_date=nowdate(), - ) - - serial_nos = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle) - create_delivery_note( - item_code=item_code, - qty=2, - rate=300, - use_serial_batch_fields=0, - serial_no=serial_nos, - ) - - for row in serial_nos: - sn = frappe.get_doc("Serial No", row) - self.assertEqual(getdate(sn.warranty_expiry_date), getdate(add_days(nowdate(), 100))) - self.assertEqual(sn.status, "Delivered") - self.assertEqual(sn.warranty_period, 100) - def test_batch_return_dn(self): from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return @@ -1863,12 +1465,11 @@ class TestDeliveryNote(FrappeTestCase): se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100) - batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + batch_no = se.items[0].batch_no dn = create_delivery_note( item_code=item_code, qty=5, rate=500, - use_serial_batch_fields=1, batch_no=batch_no, ) @@ -1877,7 +1478,7 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(dn_return.items[0].qty, 5 * -1) - returned_batch_no = get_batch_from_bundle(dn_return.items[0].serial_and_batch_bundle) + returned_batch_no = dn_return.items[0].batch_no self.assertEqual(batch_no, returned_batch_no) stock_value_difference = frappe.db.get_value( @@ -1888,7 +1489,6 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(stock_value_difference, 100.0 * 5) ->>>>>>> 6087a57b0c (fix: zero incoming rate for delivery note return (#43642)) def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") From dcbfd4ae1d3704397ada434d4a652b20b705b714 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 15 Oct 2024 11:52:44 +0530 Subject: [PATCH 13/22] fix: removed unused query (cherry picked from commit 5f590ddfa2901d483e0ee73fa5e35ff7dd369b1e) # Conflicts: # erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py --- .../tax_withholding_category/tax_withholding_category.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 8b0a2557d9b..9e090e463c3 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -578,8 +578,11 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers): conditions.append(ple.voucher_no == ple.against_voucher_no) conditions.append(ple.company == inv.company) +<<<<<<< HEAD (qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run(as_list=1)) +======= +>>>>>>> 5f590ddfa2 (fix: removed unused query) advance_amt = ( qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run()[0][0] or 0.0 ) From be78e17e8c614456cb2207c1829f00d9b567d395 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 15 Oct 2024 13:25:14 +0530 Subject: [PATCH 14/22] chore: resolve conflict --- erpnext/selling/doctype/sales_order/sales_order.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 86e2c7ea4de..293066cde38 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -30,15 +30,7 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import ( from erpnext.selling.doctype.customer.customer import check_credit_limit from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.stock.doctype.item.item import get_item_defaults -<<<<<<< HEAD -from erpnext.stock.get_item_details import get_default_bom, get_price_list_rate -======= -from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( - get_sre_reserved_qty_details_for_voucher, - has_reserved_stock, -) from erpnext.stock.get_item_details import get_bin_details, get_default_bom, get_price_list_rate ->>>>>>> 5f4a523340 (fix: update formatings) from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty form_grid_templates = {"items": "templates/form_grid/item_grid.html"} From e7f630fc7457347e630827baca212eb2b54702fa Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 15 Oct 2024 13:34:37 +0530 Subject: [PATCH 15/22] chore: resolve conflict --- .../tax_withholding_category/tax_withholding_category.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 9e090e463c3..d1bb2461795 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -578,11 +578,6 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers): conditions.append(ple.voucher_no == ple.against_voucher_no) conditions.append(ple.company == inv.company) -<<<<<<< HEAD - (qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run(as_list=1)) - -======= ->>>>>>> 5f590ddfa2 (fix: removed unused query) advance_amt = ( qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run()[0][0] or 0.0 ) From c7c2bdd15984811585b82623c29f0c3673de0778 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 8 Oct 2024 12:54:28 +0530 Subject: [PATCH 16/22] fix: delete invalid pricing rule on change of applicable_for (cherry picked from commit 42746fc630eb3b4707ee62f16bf73268799deb2b) --- .../promotional_scheme/promotional_scheme.py | 52 +++++++++++++++---- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index 857ee0e7187..c9762282a7d 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -5,6 +5,8 @@ import frappe from frappe import _ from frappe.model.document import Document +from frappe.query_builder import Criterion +from frappe.query_builder.functions import IfNull pricing_rule_fields = [ "apply_on", @@ -91,22 +93,50 @@ class PromotionalScheme(Document): if self.is_new(): return - transaction_exists = False - docnames = [] + invalid_pricing_rule = self.get_invalid_pricing_rules() - # If user has changed applicable for - if self.get_doc_before_save() and self.get_doc_before_save().applicable_for == self.applicable_for: + if not invalid_pricing_rule: return - docnames = frappe.get_all("Pricing Rule", filters={"promotional_scheme": self.name}) + if frappe.db.exists( + "Pricing Rule Detail", + { + "pricing_rule": ["in", invalid_pricing_rule], + "docstatus": ["<", 2], + }, + ): + raise_for_transaction_exists(self.name) - for docname in docnames: - if frappe.db.exists("Pricing Rule Detail", {"pricing_rule": docname.name, "docstatus": ("<", 2)}): - raise_for_transaction_exists(self.name) + for doc in invalid_pricing_rule: + frappe.delete_doc("Pricing Rule", doc) - if docnames and not transaction_exists: - for docname in docnames: - frappe.delete_doc("Pricing Rule", docname.name) + frappe.msgprint( + _("The following invalid Pricing Rules are deleted:") + + "

  • " + + "
  • ".join(invalid_pricing_rule) + + "
" + ) + + def get_invalid_pricing_rules(self): + pr = frappe.qb.DocType("Pricing Rule") + conditions = [] + conditions.append(pr.promotional_scheme == self.name) + + if self.applicable_for: + applicable_for = frappe.scrub(self.applicable_for) + applicable_for_list = [d.get(applicable_for) for d in self.get(applicable_for)] + + conditions.append( + (IfNull(pr.applicable_for, "") != self.applicable_for) + | ( + (IfNull(pr.applicable_for, "") == self.applicable_for) + & IfNull(pr[applicable_for], "").notin(applicable_for_list) + ) + ) + else: + conditions.append(IfNull(pr.applicable_for, "") != "") + + return frappe.qb.from_(pr).select(pr.name).where(Criterion.all(conditions)).run(pluck=True) def on_update(self): self.validate() From 5d9474c98c2a19acb6d990e0d15a08aab405e155 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 8 Oct 2024 17:06:05 +0530 Subject: [PATCH 17/22] test: added test for change in applicable_for_value in promotional scheme (cherry picked from commit 2613bdd868ac22bb2165dda439a3d9d0d0a29cea) --- .../test_promotional_scheme.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py index 0d08fd98139..c4a5fcf4b0a 100644 --- a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py @@ -90,6 +90,31 @@ class TestPromotionalScheme(unittest.TestCase): price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name}) self.assertEqual(price_rules, []) + def test_change_applicable_for_values_in_promotional_scheme(self): + ps = make_promotional_scheme(applicable_for="Customer", customer="_Test Customer") + ps.append("customer", {"customer": "_Test Customer 2"}) + ps.save() + + price_rules = frappe.get_all( + "Pricing Rule", filters={"promotional_scheme": ps.name, "applicable_for": "Customer"} + ) + self.assertTrue(len(price_rules), 2) + + ps.set("customer", []) + ps.append("customer", {"customer": "_Test Customer 2"}) + ps.save() + + price_rules = frappe.get_all( + "Pricing Rule", + filters={ + "promotional_scheme": ps.name, + "applicable_for": "Customer", + "customer": "_Test Customer", + }, + ) + self.assertEqual(price_rules, []) + frappe.delete_doc("Promotional Scheme", ps.name) + def test_min_max_amount_configuration(self): ps = make_promotional_scheme() ps.price_discount_slabs[0].min_amount = 10 From 1262c322d146d301705cea4ba2c10db27a7122e0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:08:37 +0530 Subject: [PATCH 18/22] fix: conversion factor issue (backport #43645) (#43675) fix: conversion factor issue (#43645) (cherry picked from commit a52756f1d471ff313b0c932aeb5d0634be2113d4) Co-authored-by: rohitwaghchaure --- .../request_for_quotation.py | 1 + erpnext/controllers/stock_controller.py | 15 +++++++++++++++ .../purchase_receipt/test_purchase_receipt.py | 15 +++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 73882d39cec..a65fe30f050 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -392,6 +392,7 @@ def create_rfq_items(sq_doc, supplier, data): "material_request", "material_request_item", "stock_qty", + "uom", ]: args[field] = data.get(field) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index a4d8e3efee1..b37658b7784 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -50,6 +50,21 @@ class StockController(AccountsController): self.set_rate_of_stock_uom() self.validate_internal_transfer() self.validate_putaway_capacity() + self.reset_conversion_factor() + + def reset_conversion_factor(self): + for row in self.get("items"): + if row.uom != row.stock_uom: + continue + + if row.conversion_factor != 1.0: + row.conversion_factor = 1.0 + frappe.msgprint( + _( + "Conversion factor for item {0} has been reset to 1.0 as the uom {1} is same as stock uom {2}." + ).format(bold(row.item_code), bold(row.uom), bold(row.stock_uom)), + alert=True, + ) def make_gl_entries(self, gl_entries=None, from_repost=False, via_landed_cost_voucher=False): if self.docstatus == 2: diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index c87699f5f8c..1c4feedbcfb 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2668,6 +2668,21 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(data[0].get("bal_qty"), 50.0) + def test_same_stock_and_transaction_uom_conversion_factor(self): + item_code = "Test Item for Same Stock and Transaction UOM Conversion Factor" + create_item(item_code) + + pr = make_purchase_receipt( + item_code=item_code, + qty=10, + rate=100, + stock_uom="Nos", + transaction_uom="Nos", + conversion_factor=10, + ) + + self.assertEqual(pr.items[0].conversion_factor, 1.0) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 32f6eb7ee79e64d4ace840e7929f6f3693e481c2 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 10 Oct 2024 11:27:26 +0530 Subject: [PATCH 19/22] fix: added string for translation in bank reconciliation statement (cherry picked from commit c99d9f70373d3c833229e828eb9596f93140c3b2) --- .../bank_reconciliation_statement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js index 9335a8cd65a..2684c87a22a 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js @@ -47,7 +47,7 @@ frappe.query_reports["Bank Reconciliation Statement"] = { }, ], formatter: function (value, row, column, data, default_formatter, filter) { - if (column.fieldname == "payment_entry" && value == "Cheques and Deposits incorrectly cleared") { + if (column.fieldname == "payment_entry" && value == __("Cheques and Deposits incorrectly cleared")) { column.link_onclick = "frappe.query_reports['Bank Reconciliation Statement'].open_utility_report()"; } From 0f738de5e5fb86f37fd9eb39bf3a286d83fe2ac2 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Fri, 11 Oct 2024 11:07:19 +0530 Subject: [PATCH 20/22] fix: added parentheses for correct query formation for logical OR condition (cherry picked from commit c0da8f11f77c6d3bcd543e70aea6a9754427538e) --- .../period_closing_voucher/period_closing_voucher.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 6109185f0bd..909614d8965 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -323,9 +323,10 @@ class PeriodClosingVoucher(AccountsController): if get_opening_entries: query = query.where( - gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date) - | gl_entry.is_opening - == "Yes" + ( # noqa: UP034 + (gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date)) + | (gl_entry.is_opening == "Yes") + ) ) else: query = query.where( From 59c314c98ab1a4b8d4e717e564166cc965445680 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 15 Oct 2024 12:42:48 +0530 Subject: [PATCH 21/22] fix: run gl_entries and closing voucher processes in same function (cherry picked from commit af4daa5b0f68338fee20ac15c35b6f721d78a459) --- .../period_closing_voucher.py | 54 +++++++------------ 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 6109185f0bd..8e235ca4fd5 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -40,7 +40,7 @@ class PeriodClosingVoucher(AccountsController): ) if gle_count > 5000: frappe.enqueue( - make_reverse_gl_entries, + process_cancellation, voucher_type="Period Closing Voucher", voucher_no=self.name, queue="long", @@ -51,9 +51,7 @@ class PeriodClosingVoucher(AccountsController): alert=True, ) else: - make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name) - - self.delete_closing_entries() + process_cancellation(voucher_type="Period Closing Voucher", voucher_no=self.name) def validate_future_closing_vouchers(self): if frappe.db.exists( @@ -66,12 +64,6 @@ class PeriodClosingVoucher(AccountsController): ) ) - def delete_closing_entries(self): - closing_balance = frappe.qb.DocType("Account Closing Balance") - frappe.qb.from_(closing_balance).delete().where( - closing_balance.period_closing_voucher == self.name - ).run() - def validate_account_head(self): closing_account_type = frappe.get_cached_value("Account", self.closing_account_head, "root_type") @@ -136,14 +128,7 @@ class PeriodClosingVoucher(AccountsController): closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries) if len(gl_entries + closing_entries) > 3000: frappe.enqueue( - process_gl_entries, - gl_entries=gl_entries, - voucher_name=self.name, - timeout=3000, - ) - - frappe.enqueue( - process_closing_entries, + process_gl_and_closing_entries, gl_entries=gl_entries, closing_entries=closing_entries, voucher_name=self.name, @@ -157,8 +142,9 @@ class PeriodClosingVoucher(AccountsController): alert=True, ) else: - process_gl_entries(gl_entries, self.name) - process_closing_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date) + process_gl_and_closing_entries( + gl_entries, closing_entries, self.name, self.company, self.posting_date + ) def get_grouped_gl_entries(self, get_opening_entries=False): closing_entries = [] @@ -343,12 +329,16 @@ class PeriodClosingVoucher(AccountsController): return query.run(as_dict=1) -def process_gl_entries(gl_entries, voucher_name): +def process_gl_and_closing_entries(gl_entries, closing_entries, voucher_name, company, closing_date): + from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import ( + make_closing_entries, + ) from erpnext.accounts.general_ledger import make_gl_entries try: if gl_entries: make_gl_entries(gl_entries, merge_entries=False) + make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date) frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed") except Exception as e: frappe.db.rollback() @@ -356,25 +346,21 @@ def process_gl_entries(gl_entries, voucher_name): frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed") -def process_closing_entries(gl_entries, closing_entries, voucher_name, company, closing_date): - from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import ( - make_closing_entries, - ) - - try: - make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date) - except Exception as e: - frappe.db.rollback() - frappe.log_error(e) - - -def make_reverse_gl_entries(voucher_type, voucher_no): +def process_cancellation(voucher_type, voucher_no): from erpnext.accounts.general_ledger import make_reverse_gl_entries try: make_reverse_gl_entries(voucher_type=voucher_type, voucher_no=voucher_no) + delete_closing_entries(voucher_no) frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Completed") except Exception as e: frappe.db.rollback() frappe.log_error(e) frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Failed") + + +def delete_closing_entries(voucher_no): + closing_balance = frappe.qb.DocType("Account Closing Balance") + frappe.qb.from_(closing_balance).delete().where( + closing_balance.period_closing_voucher == voucher_no + ).run() From bb774cec585b4d90f755bea60c75ed55df08e822 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 15 Oct 2024 11:41:00 +0530 Subject: [PATCH 22/22] fix: missing child company accounts in consolidated balance sheet (cherry picked from commit 7fae9d57d24de1a075a4c6bcca73ed95888603d9) --- .../consolidated_financial_statement.py | 72 +++++++++---------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index a809edd33cb..388b7d8fe86 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -474,10 +474,13 @@ def update_parent_account_names(accounts): for d in accounts: if d.account_number: - account_name = d.account_number + " - " + d.account_name + account_key = d.account_number + " - " + d.account_name else: - account_name = d.account_name - name_to_account_map[d.name] = account_name + account_key = d.account_name + + d.account_key = account_key + + name_to_account_map[d.name] = account_key for account in accounts: if account.parent_account: @@ -510,33 +513,26 @@ def get_subsidiary_companies(company): def get_accounts(root_type, companies): accounts = [] - added_accounts = [] for company in companies: - for account in frappe.get_all( - "Account", - fields=[ - "name", - "is_group", - "company", - "parent_account", - "lft", - "rgt", - "root_type", - "report_type", - "account_name", - "account_number", - ], - filters={"company": company, "root_type": root_type}, - ): - if account.account_number: - account_key = account.account_number + "-" + account.account_name - else: - account_key = account.account_name - - if account_key not in added_accounts: - accounts.append(account) - added_accounts.append(account_key) + accounts.extend( + frappe.get_all( + "Account", + fields=[ + "name", + "is_group", + "company", + "parent_account", + "lft", + "rgt", + "root_type", + "report_type", + "account_name", + "account_number", + ], + filters={"company": company, "root_type": root_type}, + ) + ) return accounts @@ -775,15 +771,17 @@ def add_total_row(out, root_type, balance_must_be, companies, company_currency): def filter_accounts(accounts, depth=10): parent_children_map = {} accounts_by_name = {} - for d in accounts: - if d.account_number: - account_name = d.account_number + " - " + d.account_name - else: - account_name = d.account_name - d["company_wise_opening_bal"] = defaultdict(float) - accounts_by_name[account_name] = d + added_accounts = [] - parent_children_map.setdefault(d.parent_account or None, []).append(d) + for d in accounts: + if d.account_key in added_accounts: + continue + + added_accounts.append(d.account_key) + d["company_wise_opening_bal"] = defaultdict(float) + accounts_by_name[d.account_key] = d + + parent_children_map.setdefault(d.parent_account_name or None, []).append(d) filtered_accounts = [] @@ -795,7 +793,7 @@ def filter_accounts(accounts, depth=10): for child in children: child.indent = level filtered_accounts.append(child) - add_to_list(child.name, level + 1) + add_to_list(child.account_key, level + 1) add_to_list(None, 0)