diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index f39aa026b47..bc30118a0c6 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -486,6 +486,22 @@ def apply_price_discount_rule(pricing_rule, item_details, args): if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"): # Apply discount on discounted rate item_details[field] += (100 - item_details[field]) * (pricing_rule.get(field, 0) / 100) + elif args.price_list_rate: + value = pricing_rule.get(field, 0) + calculate_discount_percentage = False + if field == "discount_percentage": + field = "discount_amount" + value = args.price_list_rate * (value / 100) + calculate_discount_percentage = True + + if field not in item_details: + item_details.setdefault(field, 0) + + item_details[field] += value if pricing_rule else args.get(field, 0) + if calculate_discount_percentage and args.price_list_rate and item_details.discount_amount: + item_details.discount_percentage = flt( + (flt(item_details.discount_amount) / flt(args.price_list_rate)) * 100 + ) else: if field not in item_details: item_details.setdefault(field, 0) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index ab52d6c9729..cb0c223bffa 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -978,6 +978,59 @@ class TestPricingRule(unittest.TestCase): self.assertEqual(so.items[1].item_code, "_Test Item") self.assertEqual(so.items[1].qty, 4) + 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") + test_record = { + "doctype": "Pricing Rule", + "title": "_Test Pricing Rule 1", + "name": "_Test Pricing Rule 1", + "apply_on": "Item Code", + "currency": "USD", + "items": [ + { + "item_code": "_Test Item", + } + ], + "selling": 1, + "price_or_product_discount": "Price", + "rate_or_discount": "Discount Percentage", + "discount_percentage": 10, + "apply_multiple_pricing_rules": 1, + "company": "_Test Company", + } + + frappe.get_doc(test_record.copy()).insert() + + test_record = { + "doctype": "Pricing Rule", + "title": "_Test Pricing Rule 2", + "name": "_Test Pricing Rule 2", + "apply_on": "Item Code", + "currency": "USD", + "items": [ + { + "item_code": "_Test Item", + } + ], + "selling": 1, + "price_or_product_discount": "Price", + "rate_or_discount": "Discount Amount", + "discount_amount": 100, + "apply_multiple_pricing_rules": 1, + "company": "_Test Company", + } + + frappe.get_doc(test_record.copy()).insert() + + so = make_sales_order(item_code="_Test Item", qty=1, price_list_rate=1000, do_not_submit=True) + self.assertEqual(so.items[0].discount_amount, 200) + self.assertEqual(so.items[0].rate, 800) + + frappe.delete_doc_if_exists("Sales Order", so.name) + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1") + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2") + test_dependencies = ["Campaign"] diff --git a/erpnext/accounts/doctype/tax_withholding_account/tax_withholding_account.json b/erpnext/accounts/doctype/tax_withholding_account/tax_withholding_account.json index 06d6b088e5b..52a4f4c0eed 100644 --- a/erpnext/accounts/doctype/tax_withholding_account/tax_withholding_account.json +++ b/erpnext/accounts/doctype/tax_withholding_account/tax_withholding_account.json @@ -21,7 +21,7 @@ "fieldname": "company", "fieldtype": "Link", "hidden": 0, - "ignore_user_permissions": 0, + "ignore_user_permissions": 1, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, @@ -53,7 +53,7 @@ "fieldname": "account", "fieldtype": "Link", "hidden": 0, - "ignore_user_permissions": 0, + "ignore_user_permissions": 1, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, @@ -87,7 +87,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-04-13 18:44:25.055382", + "modified": "2024-04-30 10:26:48.21829", "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withholding Account", 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 58ce2840b4b..d8b2079e5ac 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -253,6 +253,14 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N if taxable_vouchers: tax_deducted = get_deducted_tax(taxable_vouchers, tax_details) + # If advance is outside the current tax withholding period (usually a fiscal year), `get_deducted_tax` won't fetch it. + # updating `tax_deducted` with correct advance tax value (from current and previous previous withholding periods), will allow the + # rest of the below logic to function properly + # ---FY 2023-------------||---------------------FY 2024-----------------------||-- + # ---Advance-------------||---------Inv_1--------Inv_2------------------------||-- + if tax_deducted_on_advances: + tax_deducted += get_advance_tax_across_fiscal_year(tax_deducted_on_advances, tax_details) + tax_amount = 0 if party_type == "Supplier": @@ -389,7 +397,7 @@ def get_taxes_deducted_on_advances_allocated(inv, tax_details): frappe.qb.from_(at) .inner_join(pe) .on(pe.name == at.parent) - .select(at.parent, at.name, at.tax_amount, at.allocated_amount) + .select(pe.posting_date, at.parent, at.name, at.tax_amount, at.allocated_amount) .where(pe.tax_withholding_category == tax_details.get("tax_withholding_category")) .where(at.parent.isin(advances)) .where(at.account_head == tax_details.account_head) @@ -414,6 +422,16 @@ def get_deducted_tax(taxable_vouchers, tax_details): return sum(entries) +def get_advance_tax_across_fiscal_year(tax_deducted_on_advances, tax_details): + """ + Only applies for Taxes deducted on Advance Payments + """ + advance_tax_from_across_fiscal_year = sum( + [adv.tax_amount for adv in tax_deducted_on_advances if adv.posting_date < tax_details.from_date] + ) + return advance_tax_from_across_fiscal_year + + def get_tds_amount(ldc, parties, inv, tax_details, vouchers): tds_amount = 0 invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1} diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index a5509619583..19e6f4d3188 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -1,19 +1,22 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt +import datetime import unittest import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -from frappe.tests.utils import change_settings -from frappe.utils import today +from frappe.tests.utils import FrappeTestCase, change_settings +from frappe.utils import add_days, today +from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.utils import get_fiscal_year +from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice test_dependencies = ["Supplier Group", "Customer Group"] -class TestTaxWithholdingCategory(unittest.TestCase): +class TestTaxWithholdingCategory(FrappeTestCase): @classmethod def setUpClass(self): # create relevant supplier, etc @@ -22,7 +25,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): make_pan_no_field() def tearDown(self): - cancel_invoices() + frappe.db.rollback() def test_cumulative_threshold_tds(self): frappe.db.set_value( @@ -322,8 +325,6 @@ class TestTaxWithholdingCategory(unittest.TestCase): d.cancel() def test_tds_deduction_for_po_via_payment_entry(self): - from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry - frappe.db.set_value( "Supplier", "Test TDS Supplier8", "tax_withholding_category", "Cumulative Threshold TDS" ) @@ -490,6 +491,133 @@ class TestTaxWithholdingCategory(unittest.TestCase): pi2.cancel() pi3.cancel() + def set_previous_fy_and_tax_category(self): + test_company = "_Test Company" + category = "Cumulative Threshold TDS" + + def add_company_to_fy(fy, company): + if not [x.company for x in fy.companies if x.company == company]: + fy.append("companies", {"company": company}) + fy.save() + + # setup previous fiscal year + fiscal_year = get_fiscal_year(today(), company=test_company) + if prev_fiscal_year := get_fiscal_year(add_days(fiscal_year[1], -10)): + self.prev_fy = frappe.get_doc("Fiscal Year", prev_fiscal_year[0]) + add_company_to_fy(self.prev_fy, test_company) + else: + # make previous fiscal year + start = datetime.date(fiscal_year[1].year - 1, fiscal_year[1].month, fiscal_year[1].day) + end = datetime.date(fiscal_year[2].year - 1, fiscal_year[2].month, fiscal_year[2].day) + self.prev_fy = frappe.get_doc( + { + "doctype": "Fiscal Year", + "year_start_date": start, + "year_end_date": end, + "companies": [{"company": test_company}], + } + ) + self.prev_fy.save() + + # setup tax withholding category for previous fiscal year + cat = frappe.get_doc("Tax Withholding Category", category) + cat.append( + "rates", + { + "from_date": self.prev_fy.year_start_date, + "to_date": self.prev_fy.year_end_date, + "tax_withholding_rate": 10, + "single_threshold": 0, + "cumulative_threshold": 30000, + }, + ) + cat.save() + + def test_tds_across_fiscal_year(self): + """ + Advance TDS on previous fiscal year should be properly allocated on Invoices in upcoming fiscal year + --||-----FY 2023-----||-----FY 2024-----||-- + --||-----Advance-----||---Inv1---Inv2---||-- + """ + self.set_previous_fy_and_tax_category() + supplier = "Test TDS Supplier" + # Cumulative threshold 30000 and tax rate 10% + category = "Cumulative Threshold TDS" + frappe.db.set_value( + "Supplier", + supplier, + { + "tax_withholding_category": category, + "pan": "ABCTY1234D", + }, + ) + po_and_advance_posting_date = add_days(self.prev_fy.year_end_date, -10) + po = create_purchase_order(supplier=supplier, qty=10, rate=10000) + po.transaction_date = po_and_advance_posting_date + po.taxes = [] + po.apply_tds = False + po.tax_withholding_category = None + po.save().submit() + + # Partial advance + payment = get_payment_entry(po.doctype, po.name) + payment.posting_date = po_and_advance_posting_date + payment.paid_amount = 60000 + payment.apply_tax_withholding_amount = 1 + payment.tax_withholding_category = category + payment.references = [] + payment.taxes = [] + payment.save().submit() + + self.assertEqual(len(payment.taxes), 1) + self.assertEqual(payment.taxes[0].tax_amount, 6000) + + # Multiple partial invoices + payment.reload() + pi1 = make_purchase_invoice(source_name=po.name) + pi1.apply_tds = True + pi1.tax_withholding_category = category + pi1.items[0].qty = 3 + pi1.items[0].rate = 10000 + advances = pi1.get_advance_entries() + pi1.append( + "advances", + { + "reference_type": advances[0].reference_type, + "reference_name": advances[0].reference_name, + "advance_amount": advances[0].amount, + "allocated_amount": 30000, + }, + ) + pi1.save().submit() + pi1.reload() + payment.reload() + self.assertEqual(pi1.taxes, []) + self.assertEqual(payment.taxes[0].tax_amount, 6000) + self.assertEqual(payment.taxes[0].allocated_amount, 3000) + + pi2 = make_purchase_invoice(source_name=po.name) + pi2.apply_tds = True + pi2.tax_withholding_category = category + pi2.items[0].qty = 3 + pi2.items[0].rate = 10000 + advances = pi2.get_advance_entries() + pi2.append( + "advances", + { + "reference_type": advances[0].reference_type, + "reference_name": advances[0].reference_name, + "advance_amount": advances[0].amount, + "allocated_amount": 30000, + }, + ) + pi2.save().submit() + pi2.reload() + payment.reload() + self.assertEqual(pi2.taxes, []) + self.assertEqual(payment.taxes[0].tax_amount, 6000) + self.assertEqual(payment.taxes[0].allocated_amount, 6000) + def cancel_invoices(): purchase_invoices = frappe.get_all( diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index dcfd85afddb..25143e555d3 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -501,8 +501,9 @@ class ReceivablePayableReport: # Deduct that from paid amount pre allocation row.paid -= flt(payment_terms_details[0].total_advance) - # If no or single payment terms, no need to split the row - if len(payment_terms_details) <= 1: + # If single payment terms, no need to split the row + if len(payment_terms_details) == 1 and payment_terms_details[0].payment_term: + self.append_payment_term(row, payment_terms_details[0], original_row) return for d in payment_terms_details: diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index d966074b2ae..8fc47b0b679 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -479,6 +479,11 @@ def reconcile_against_document( # re-submit advance entry doc = frappe.get_doc(entry.voucher_type, entry.voucher_no) gl_map = doc.build_gl_map() + from erpnext.accounts.general_ledger import process_debit_credit_difference + + # Make sure there is no overallocation + process_debit_credit_difference(gl_map) + create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1) # Only update outstanding for newly linked vouchers diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 15a79c8efb4..41085181720 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -1008,7 +1008,12 @@ def is_reposting_pending(): ) -def future_sle_exists(args, sl_entries=None): +def future_sle_exists(args, sl_entries=None, allow_force_reposting=True): + if allow_force_reposting and frappe.db.get_single_value( + "Stock Reposting Settings", "do_reposting_for_each_stock_transaction" + ): + return True + key = (args.voucher_type, args.voucher_no) if not hasattr(frappe.local, "future_sle"): frappe.local.future_sle = {} diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 75753c8b0f5..c7b36b6aa3f 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -202,7 +202,7 @@ def update_qty(bin_name, args): sle = frappe.qb.DocType("Stock Ledger Entry") # actual qty is not up to date in case of backdated transaction - if future_sle_exists(args): + if future_sle_exists(args, allow_force_reposting=False): last_sle_qty = ( frappe.qb.from_(sle) .select(sle.qty_after_transaction) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 2ad3f485d08..18adf6ab1f9 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -739,7 +739,7 @@ def make_sales_invoice(source_name, target_doc=None, args=None): @frappe.whitelist() -def make_delivery_trip(source_name, target_doc=None): +def make_delivery_trip(source_name, target_doc=None, kwargs=None): def update_stop_details(source_doc, target_doc, source_parent): target_doc.customer = source_parent.customer target_doc.address = source_parent.shipping_address_name diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.js b/erpnext/stock/doctype/delivery_trip/delivery_trip.js index 4f8649c0bfa..77eae534d17 100755 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.js +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.js @@ -51,6 +51,7 @@ frappe.ui.form.on("Delivery Trip", { frm.add_custom_button( __("Delivery Note"), () => { + frm.clear_table("delivery_stops"); erpnext.utils.map_current_doc({ method: "erpnext.stock.doctype.delivery_note.delivery_note.make_delivery_trip", source_doctype: "Delivery Note", diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json index 68afd996b49..cbbb0ce0990 100644 --- a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json +++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json @@ -13,6 +13,7 @@ "end_time", "limits_dont_apply_on", "item_based_reposting", + "do_reposting_for_each_stock_transaction", "errors_notification_section", "notify_reposting_error_to_role" ], @@ -65,12 +66,18 @@ "fieldname": "errors_notification_section", "fieldtype": "Section Break", "label": "Errors Notification" + }, + { + "default": "0", + "fieldname": "do_reposting_for_each_stock_transaction", + "fieldtype": "Check", + "label": "Do reposting for each Stock Transaction" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-11-01 16:14:29.080697", + "modified": "2024-04-24 12:19:40.204888", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reposting Settings", @@ -91,4 +98,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py index 51fb5ac4c40..10104fb9a60 100644 --- a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py +++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py @@ -11,6 +11,10 @@ class StockRepostingSettings(Document): def validate(self): self.set_minimum_reposting_time_slot() + def before_save(self): + if self.do_reposting_for_each_stock_transaction: + self.item_based_reposting = 1 + def set_minimum_reposting_time_slot(self): """Ensure that timeslot for reposting is at least 12 hours.""" if not self.limit_reposting_timeslot: diff --git a/erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py b/erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py index a6dc72d7a42..e53659c1735 100644 --- a/erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py +++ b/erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py @@ -38,3 +38,51 @@ class TestStockRepostingSettings(unittest.TestCase): users = get_recipients() self.assertTrue(user in users) + + def test_do_reposting_for_each_stock_transaction(self): + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry + + frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 1) + if frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"): + frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 0) + + item = make_item( + "_Test item for reposting check for each transaction", properties={"is_stock_item": 1} + ).name + + stock_entry = make_stock_entry( + item_code=item, + qty=1, + rate=100, + stock_entry_type="Material Receipt", + target="_Test Warehouse - _TC", + ) + + riv = frappe.get_all("Repost Item Valuation", filters={"voucher_no": stock_entry.name}, pluck="name") + self.assertTrue(riv) + + frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 0) + + def test_do_not_reposting_for_each_stock_transaction(self): + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry + + frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 0) + if frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"): + frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 0) + + item = make_item( + "_Test item for do not reposting check for each transaction", properties={"is_stock_item": 1} + ).name + + stock_entry = make_stock_entry( + item_code=item, + qty=1, + rate=100, + stock_entry_type="Material Receipt", + target="_Test Warehouse - _TC", + ) + + riv = frappe.get_all("Repost Item Valuation", filters={"voucher_no": stock_entry.name}, pluck="name") + self.assertFalse(riv) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index c8c26fd66cb..8e27b8c4f69 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -131,6 +131,7 @@ def get_stock_ledger_entries(filters): def get_item_warehouse_batch_map(filters, float_precision): + _system_settings = frappe.get_cached_doc("System Settings") with frappe.db.unbuffered_cursor(): sle = get_stock_ledger_entries(filters) sle = sle.run(as_dict=True, as_iterator=True) diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index 26bf99e1ed7..c09137e645b 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -229,11 +229,12 @@ class FIFOSlots: """ stock_ledger_entries = self.sle + _system_settings = frappe.get_cached_doc("System Settings") with frappe.db.unbuffered_cursor(): - if self.sle is None: - self.sle = self.__get_stock_ledger_entries() + if stock_ledger_entries is None: + stock_ledger_entries = self.__get_stock_ledger_entries() - for d in self.sle: + for d in stock_ledger_entries: key, fifo_queue, transferred_item_key = self.__init_key_stores(d) if d.voucher_type == "Stock Reconciliation": diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index f0fddac2c64..5a79cdf1827 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -141,6 +141,8 @@ class StockBalanceReport: if self.filters.get("show_stock_ageing_data"): self.sle_entries = self.sle_query.run(as_dict=True) + # HACK: This is required to avoid causing db query in flt + _system_settings = frappe.get_cached_doc("System Settings") with frappe.db.unbuffered_cursor(): if not self.filters.get("show_stock_ageing_data"): self.sle_entries = self.sle_query.run(as_dict=True, as_iterator=True) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index a9f74149d51..bde1434e600 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -156,13 +156,6 @@ def get_columns(filters): "width": 100, "convertible": "qty", }, - { - "label": _("Voucher #"), - "fieldname": "voucher_no", - "fieldtype": "Dynamic Link", - "options": "voucher_type", - "width": 150, - }, { "label": _("Warehouse"), "fieldname": "warehouse", diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 99930768ea5..4e624c9acb0 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1490,6 +1490,10 @@ def get_stock_reco_qty_shift(args): stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance) else: stock_reco_qty_shift = flt(args.actual_qty) + + elif args.get("serial_no") or args.get("batch_no"): + stock_reco_qty_shift = flt(args.actual_qty) + else: # reco is being submitted last_balance = get_previous_sle_of_current_voucher(args, "<=", exclude_current_voucher=True).get( @@ -1559,6 +1563,15 @@ def get_datetime_limit_condition(detail): def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): if allow_negative_stock or is_negative_stock_allowed(item_code=args.item_code): return + + if ( + args.voucher_type == "Stock Reconciliation" + and args.actual_qty < 0 + and args.get("batch_no") + and frappe.db.get_value("Stock Reconciliation Item", args.voucher_detail_no, "qty") > 0 + ): + return + if not (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation"): return diff --git a/erpnext/translations/en.csv b/erpnext/translations/en.csv deleted file mode 100644 index 7fac9e1785b..00000000000 --- a/erpnext/translations/en.csv +++ /dev/null @@ -1 +0,0 @@ -Married,既婚,