diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 6b0115d3f56..d420bcca342 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -89,6 +89,7 @@ "custom_remarks", "remarks", "base_in_words", + "is_opening", "column_break_16", "letter_head", "print_heading", @@ -777,6 +778,16 @@ "label": "Reconcile on Advance Payment Date", "no_copy": 1, "read_only": 1 + }, + { + "default": "No", + "depends_on": "eval: doc.book_advance_payments_in_separate_party_account == 1", + "fieldname": "is_opening", + "fieldtype": "Select", + "label": "Is Opening", + "options": "No\nYes", + "print_hide": 1, + "search_index": 1 } ], "index_web_pages_for_search": 1, @@ -790,7 +801,7 @@ "table_fieldname": "payment_entries" } ], - "modified": "2024-05-17 10:21:11.199445", + "modified": "2024-05-31 17:07:06.197249", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e542ff68176..69075e5cf6c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -116,11 +116,13 @@ class PaymentEntry(AccountsController): self.book_advance_payments_in_separate_party_account = False if self.party_type not in ("Customer", "Supplier"): + self.is_opening = "No" return if not frappe.db.get_value( "Company", self.company, "book_advance_payments_in_separate_party_account" ): + self.is_opening = "No" return # Important to set this flag for the gl building logic to work properly @@ -132,6 +134,7 @@ class PaymentEntry(AccountsController): if (account_type == "Payable" and self.party_type == "Customer") or ( account_type == "Receivable" and self.party_type == "Supplier" ): + self.is_opening = "No" return if self.references: @@ -141,6 +144,7 @@ class PaymentEntry(AccountsController): # If there are referencers other than `allowed_types`, treat this as a normal payment entry if reference_types - allowed_types: self.book_advance_payments_in_separate_party_account = False + self.is_opening = "No" return liability_account = get_party_account( diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index d22350d20fc..4631f8905e6 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1729,6 +1729,68 @@ class TestPaymentEntry(FrappeTestCase): self.check_gl_entries() self.check_pl_entries() + def test_opening_flag_for_advance_as_liability(self): + company = "_Test Company" + + advance_account = create_account( + parent_account="Current Assets - _TC", + account_name="Advances Received", + company=company, + account_type="Receivable", + ) + + # Enable Advance in separate party account + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_in_separate_party_account": 1, + "default_advance_received_account": advance_account, + }, + ) + # Advance Payment + adv = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Receive", + paid_from="Debtors - _TC", + paid_to="_Test Cash - _TC", + ) + adv.is_opening = "Yes" + adv.save() # use save() to trigger set_liability_account() + adv.submit() + + gl_with_opening_set = frappe.db.get_all( + "GL Entry", filters={"voucher_no": adv.name, "is_opening": "Yes"} + ) + # 'Is Opening' can be 'Yes' for Advances in separate party account + self.assertNotEqual(gl_with_opening_set, []) + + # Disable Advance in separate party account + frappe.db.set_value( + "Company", + company, + { + "book_advance_payments_in_separate_party_account": 0, + "default_advance_received_account": None, + }, + ) + payment = create_payment_entry( + party_type="Customer", + party="_Test Customer", + payment_type="Receive", + paid_from="Debtors - _TC", + paid_to="_Test Cash - _TC", + ) + payment.is_opening = "Yes" + payment.save() + payment.submit() + gl_with_opening_set = frappe.db.get_all( + "GL Entry", filters={"voucher_no": payment.name, "is_opening": "Yes"} + ) + # 'Is Opening' should always be 'No' for normal advance payments + self.assertEqual(gl_with_opening_set, []) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index c56a083808d..c0a43395216 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -677,7 +677,7 @@ frappe.ui.form.on("Purchase Invoice", { if (frm.doc.supplier) { frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0; } - if (!frm.doc.__onload.supplier_tds) { + if (!frm.doc.__onload.enable_apply_tds) { frm.set_df_property("apply_tds", "read_only", 1); } } diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index afccc21c313..df54a63b661 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -3,7 +3,7 @@ import frappe -from frappe import _, throw +from frappe import _, qb, throw from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate @@ -347,6 +347,22 @@ class PurchaseInvoice(BuyingController): self.tax_withholding_category = tds_category self.set_onload("supplier_tds", tds_category) + # If Linked Purchase Order has TDS applied, enable 'apply_tds' checkbox + if purchase_orders := [x.purchase_order for x in self.items if x.purchase_order]: + po = qb.DocType("Purchase Order") + po_with_tds = ( + qb.from_(po) + .select(po.name) + .where( + po.docstatus.eq(1) + & (po.name.isin(purchase_orders)) + & (po.apply_tds.eq(1)) + & (po.tax_withholding_category.notnull()) + ) + .run() + ) + self.set_onload("enable_apply_tds", True if po_with_tds else False) + super().set_missing_values(for_validate) def validate_credit_to_acc(self): diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json index 8aa0a840c7e..dd314ca6601 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json +++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json @@ -17,7 +17,7 @@ "in_create": 1, "issingle": 1, "links": [], - "modified": "2023-11-07 14:24:13.321522", + "modified": "2024-06-06 13:56:37.908879", "modified_by": "Administrator", "module": "Accounts", "name": "Repost Accounting Ledger Settings", @@ -30,13 +30,17 @@ "print": 1, "read": 1, "role": "Administrator", + "select": 1, "share": 1, "write": 1 }, { + "create": 1, + "delete": 1, "read": 1, "role": "System Manager", - "select": 1 + "select": 1, + "write": 1 } ], "sort_field": "modified", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 1b36bf50f86..90b6e768092 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -787,6 +787,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Time Sheets", + "no_copy": 1, "options": "Sales Invoice Timesheet", "print_hide": 1 }, @@ -2187,7 +2188,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2024-05-08 18:02:28.549041", + "modified": "2024-06-07 16:49:32.458402", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py index 4fa485f54b3..f9a008ade7f 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py @@ -69,48 +69,50 @@ def get_asset_categories_for_grouped_by_category(filters): condition = "" if filters.get("asset_category"): condition += " and asset_category = %(asset_category)s" + # nosemgrep return frappe.db.sql( f""" - SELECT asset_category, - ifnull(sum(case when purchase_date < %(from_date)s then - case when ifnull(disposal_date, 0) = 0 or disposal_date >= %(from_date)s then - gross_purchase_amount + SELECT a.asset_category, + ifnull(sum(case when a.purchase_date < %(from_date)s then + case when ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s then + a.gross_purchase_amount else 0 end else 0 end), 0) as cost_as_on_from_date, - ifnull(sum(case when purchase_date >= %(from_date)s then - gross_purchase_amount + ifnull(sum(case when a.purchase_date >= %(from_date)s then + a.gross_purchase_amount else 0 end), 0) as cost_of_new_purchase, - ifnull(sum(case when ifnull(disposal_date, 0) != 0 - and disposal_date >= %(from_date)s - and disposal_date <= %(to_date)s then - case when status = "Sold" then - gross_purchase_amount + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 + and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s then + case when a.status = "Sold" then + a.gross_purchase_amount else 0 end else 0 end), 0) as cost_of_sold_asset, - ifnull(sum(case when ifnull(disposal_date, 0) != 0 - and disposal_date >= %(from_date)s - and disposal_date <= %(to_date)s then - case when status = "Scrapped" then - gross_purchase_amount + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 + and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s then + case when a.status = "Scrapped" then + a.gross_purchase_amount else 0 end else 0 end), 0) as cost_of_scrapped_asset - from `tabAsset` + from `tabAsset` a where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s {condition} - group by asset_category + and not exists(select name from `tabAsset Capitalization Asset Item` where asset = a.name) + group by a.asset_category """, { "to_date": filters.to_date, diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index bde2f6bd099..cad9178366e 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -715,6 +715,9 @@ class StockController(AccountsController): row.db_set("rejected_serial_and_batch_bundle", None) + if row.get("current_serial_and_batch_bundle"): + row.db_set("current_serial_and_batch_bundle", None) + def set_serial_and_batch_bundle(self, table_name=None, ignore_validate=False): if not table_name: table_name = "items" diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a378d8ae606..7d3aa000c87 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -452,6 +452,10 @@ class ProductionPlan(Document): {"sales_order": data.parent, "sales_order_item": data.name, "qty": data.pending_qty} ) + bom_no = data.bom_no or item_details and item_details.bom_no or "" + if not bom_no: + continue + pi = self.append( "po_items", { @@ -459,7 +463,7 @@ class ProductionPlan(Document): "item_code": data.item_code, "description": data.description or item_details.description, "stock_uom": item_details and item_details.stock_uom or "", - "bom_no": data.bom_no or item_details and item_details.bom_no or "", + "bom_no": bom_no, "planned_qty": data.pending_qty, "pending_qty": data.pending_qty, "planned_start_date": now_datetime(), diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 8957c11d3f0..71d9b59c677 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -328,6 +328,28 @@ class TestProductionPlan(FrappeTestCase): self.assertEqual(pln2.po_items[0].bom_no, bom2.name) + def test_production_plan_with_non_active_bom_item(self): + item = make_item("Test Production Item 1 for Non Active BOM", {"is_stock_item": 1}).name + + so1 = make_sales_order(item_code=item, qty=1) + + pln = frappe.new_doc("Production Plan") + pln.company = so1.company + pln.get_items_from = "Sales Order" + pln.append( + "sales_orders", + { + "sales_order": so1.name, + "sales_order_date": so1.transaction_date, + "customer": so1.customer, + "grand_total": so1.grand_total, + }, + ) + + pln.get_items() + + self.assertFalse(pln.po_items) + def test_production_plan_combine_items(self): "Test combining FG items in Production Plan." item = "Test Production Item 1" diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index cf6b34c9604..adf05ffb154 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -83,7 +83,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.frm.doc.paid_amount = flt(this.frm.doc.grand_total, precision("grand_total")); } - this.frm.refresh_field("taxes"); + this.frm.refresh_fields(); } calculate_discount_amount() { @@ -841,7 +841,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { }); } - this.frm.refresh_field("taxes"); + this.frm.refresh_fields(); } set_default_payment(total_amount_to_pay, update_paid_amount) { diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 937b19d9abb..139ab4bff0f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1246,8 +1246,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } qty(doc, cdt, cdn) { - if (!this.frm.doc.__onload?.load_after_mapping) { - let item = frappe.get_doc(cdt, cdn); + let item = frappe.get_doc(cdt, cdn); + if (!this.is_a_mapped_document(item)) { // item.pricing_rules = '' frappe.run_serially([ () => this.remove_pricing_rule_for_item(item), diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index d6c08d957fe..e5e67ed38b7 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -794,6 +794,11 @@ def get_requested_item_qty(sales_order): def make_material_request(source_name, target_doc=None): requested_item_qty = get_requested_item_qty(source_name) + def postprocess(source, target): + if source.tc_name and frappe.db.get_value("Terms and Conditions", source.tc_name, "buying") != 1: + target.tc_name = None + target.terms = None + def get_remaining_qty(so_item): return flt( flt(so_item.qty) @@ -849,6 +854,7 @@ def make_material_request(source_name, target_doc=None): }, }, target_doc, + postprocess, ) return doc diff --git a/erpnext/setup/doctype/holiday_list/holiday_list.py b/erpnext/setup/doctype/holiday_list/holiday_list.py index 9d854c7c412..6932f7be825 100644 --- a/erpnext/setup/doctype/holiday_list/holiday_list.py +++ b/erpnext/setup/doctype/holiday_list/holiday_list.py @@ -42,7 +42,7 @@ class HolidayList(Document): def validate(self): self.validate_days() self.total_holidays = len(self.holidays) - self.validate_dupliacte_date() + self.validate_duplicate_date() @frappe.whitelist() def get_weekly_off_dates(self): @@ -148,7 +148,7 @@ class HolidayList(Document): def clear_table(self): self.set("holidays", []) - def validate_dupliacte_date(self): + def validate_duplicate_date(self): unique_dates = [] for row in self.holidays: if row.holiday_date in unique_dates: diff --git a/erpnext/setup/doctype/vehicle/test_vehicle.py b/erpnext/setup/doctype/vehicle/test_vehicle.py index 97fe651122c..3cd52386284 100644 --- a/erpnext/setup/doctype/vehicle/test_vehicle.py +++ b/erpnext/setup/doctype/vehicle/test_vehicle.py @@ -26,3 +26,29 @@ class TestVehicle(unittest.TestCase): } ) vehicle.insert() + + def test_renaming_vehicle(self): + license_plate = random_string(10).upper() + + vehicle = frappe.get_doc( + { + "doctype": "Vehicle", + "license_plate": license_plate, + "make": "Skoda", + "model": "Slavia", + "last_odometer": 5000, + "acquisition_date": frappe.utils.nowdate(), + "location": "Mumbai", + "chassis_no": "1234EFGH", + "uom": "Litre", + "vehicle_value": frappe.utils.flt(500000), + } + ) + vehicle.insert() + + new_license_plate = random_string(10).upper() + frappe.rename_doc("Vehicle", license_plate, new_license_plate) + + self.assertEqual( + new_license_plate, frappe.db.get_value("Vehicle", new_license_plate, "license_plate") + ) diff --git a/erpnext/setup/doctype/vehicle/vehicle.json b/erpnext/setup/doctype/vehicle/vehicle.json index b19d45924fb..c76719e4b92 100644 --- a/erpnext/setup/doctype/vehicle/vehicle.json +++ b/erpnext/setup/doctype/vehicle/vehicle.json @@ -2,7 +2,8 @@ "allow_copy": 0, "allow_guest_to_view": 0, "allow_import": 0, - "allow_rename": 0, + "actions": [], + "allow_rename": 1, "autoname": "field:license_plate", "beta": 0, "creation": "2016-09-03 03:33:27.680331", @@ -834,7 +835,8 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2022-06-27 14:48:30.813359", + "links": [], + "modified": "2024-05-31 06:38:15.399283", "modified_by": "Administrator", "module": "Setup", "name": "Vehicle", diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 89e69c153d4..8301a706183 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -164,7 +164,11 @@ class StockReconciliation(StockController): for item in self.items: if not item.reconcile_all_serial_batch and item.serial_and_batch_bundle: bundle = self.get_bundle_for_specific_serial_batch(item) - item.current_serial_and_batch_bundle = bundle + item.current_serial_and_batch_bundle = bundle.name + item.current_valuation_rate = abs(bundle.avg_rate) + + if not item.valuation_rate: + item.valuation_rate = item.current_valuation_rate continue if not save and item.use_serial_batch_fields: @@ -282,7 +286,12 @@ class StockReconciliation(StockController): from erpnext.stock.serial_batch_bundle import SerialBatchCreation if row.current_serial_and_batch_bundle and not self.has_change_in_serial_batch(row): - return row.current_serial_and_batch_bundle + return frappe._dict( + { + "name": row.current_serial_and_batch_bundle, + "avg_rate": row.current_valuation_rate, + } + ) cls_obj = SerialBatchCreation( { @@ -316,12 +325,11 @@ class StockReconciliation(StockController): total_current_qty += current_qty entry.qty = current_qty * -1 - reco_obj.flags.ignore_validate = True reco_obj.save() row.current_qty = total_current_qty - return reco_obj.name + return reco_obj def has_change_in_serial_batch(self, row) -> bool: bundles = {row.serial_and_batch_bundle: [], row.current_serial_and_batch_bundle: []} @@ -721,7 +729,7 @@ class StockReconciliation(StockController): for d in serial_nos: frappe.db.set_value("Serial No", d, "purchase_rate", valuation_rate) - def get_sle_for_items(self, row, serial_nos=None): + def get_sle_for_items(self, row, serial_nos=None, current_bundle=True): """Insert Stock Ledger Entries""" if not serial_nos and row.serial_no: @@ -755,7 +763,7 @@ class StockReconciliation(StockController): has_dimensions = True if self.docstatus == 2 and (not row.batch_no or not row.serial_and_batch_bundle): - if row.current_qty: + if row.current_qty and current_bundle: data.actual_qty = -1 * row.current_qty data.qty_after_transaction = flt(row.current_qty) data.previous_qty_after_transaction = flt(row.qty) @@ -785,6 +793,8 @@ class StockReconciliation(StockController): has_serial_no = False for row in self.items: sl_entries.append(self.get_sle_for_items(row)) + if row.serial_and_batch_bundle and row.current_serial_and_batch_bundle: + sl_entries.append(self.get_sle_for_items(row, current_bundle=False)) if sl_entries: if has_serial_no: diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 4397616e30c..8845bdbb753 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -1109,6 +1109,8 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): ) sr.reload() + + self.assertTrue(sr.items[0].current_valuation_rate) current_sabb = sr.items[0].current_serial_and_batch_bundle doc = frappe.get_doc("Serial and Batch Bundle", current_sabb) for row in doc.entries: @@ -1118,6 +1120,18 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): batch_qty = get_batch_qty(batches[0].batch_no, warehouse, item.name) self.assertEqual(batch_qty, 100) + for row in frappe.get_all("Repost Item Valuation", filters={"voucher_no": sr.name}): + rdoc = frappe.get_doc("Repost Item Valuation", row.name) + rdoc.cancel() + rdoc.delete() + + sr.cancel() + + for row in frappe.get_all( + "Serial and Batch Bundle", fields=["docstatus"], filters={"voucher_no": sr.name} + ): + self.assertEqual(row.docstatus, 2) + def test_not_reconcile_all_serial_nos(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.utils import get_incoming_rate diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index abbd48853cc..e804ae18016 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -36,6 +36,7 @@ from erpnext.stock.utils import ( get_incoming_outgoing_rate_for_cancel, get_incoming_rate, get_or_make_bin, + get_serial_nos_data, get_stock_balance, get_valuation_method, ) @@ -811,9 +812,10 @@ class update_entries_after: self.update_outgoing_rate_on_transaction(sle) def get_serialized_values(self, sle): + from erpnext.stock.serial_batch_bundle import SerialNoValuation + incoming_rate = flt(sle.incoming_rate) actual_qty = flt(sle.actual_qty) - serial_nos = cstr(sle.serial_no).split("\n") if incoming_rate < 0: # wrong incoming rate @@ -826,8 +828,16 @@ class update_entries_after: # In case of delivery/stock issue, get average purchase rate # of serial nos of current entry if not sle.is_cancelled: - outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos) - stock_value_change = -1 * outgoing_value + new_sle = copy.deepcopy(sle) + new_sle.qty = new_sle.actual_qty + new_sle.serial_nos = get_serial_nos_data(new_sle.get("serial_no")) + + sn_obj = SerialNoValuation( + sle=new_sle, warehouse=new_sle.get("warehouse"), item_code=new_sle.get("item_code") + ) + + outgoing_value = sn_obj.get_incoming_rate() + stock_value_change = actual_qty * outgoing_value else: stock_value_change = actual_qty * sle.outgoing_rate @@ -1272,6 +1282,8 @@ class update_entries_after: self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction def update_batched_values(self, sle): + from erpnext.stock.serial_batch_bundle import BatchNoValuation + incoming_rate = flt(sle.incoming_rate) actual_qty = flt(sle.actual_qty) @@ -1282,21 +1294,25 @@ class update_entries_after: if actual_qty > 0: stock_value_difference = incoming_rate * actual_qty else: - outgoing_rate = get_batch_incoming_rate( - item_code=sle.item_code, - warehouse=sle.warehouse, - batch_no=sle.batch_no, - posting_date=sle.posting_date, - posting_time=sle.posting_time, - creation=sle.creation, + new_sle = copy.deepcopy(sle) + + new_sle.qty = new_sle.actual_qty + new_sle.batch_nos = frappe._dict({new_sle.batch_no: new_sle}) + batch_obj = BatchNoValuation( + sle=new_sle, + warehouse=new_sle.get("warehouse"), + item_code=new_sle.get("item_code"), ) + outgoing_rate = batch_obj.get_incoming_rate() + if outgoing_rate is None: # This can *only* happen if qty available for the batch is zero. # in such case fall back various other rates. # future entries will correct the overall accounting as each # batch individually uses moving average rates. outgoing_rate = self.get_fallback_rate(sle) + stock_value_difference = outgoing_rate * actual_qty self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + stock_value_difference)