Merge pull request #62675 from rohitwaghchaure/fix-manual-adjustment-entry-message

fix: block manual selection of Adjustment Entry purpose in Stock Reconciliation
This commit is contained in:
mergify[bot]
2026-10-27 12:02:19 +00:00
committed by GitHub
4 changed files with 73 additions and 2 deletions
@@ -11,6 +11,7 @@
"naming_series",
"company",
"purpose",
"is_made_from_valuation_comparison",
"col1",
"posting_date",
"posting_time",
@@ -59,6 +60,15 @@
"options": "\nOpening Stock\nStock Reconciliation\nAdjustment Entry",
"reqd": 1
},
{
"default": "0",
"fieldname": "is_made_from_valuation_comparison",
"fieldtype": "Check",
"hidden": 1,
"label": "Made from Stock Valuation Comparison",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "col1",
"fieldtype": "Column Break"
@@ -193,7 +203,7 @@
"idx": 1,
"is_submittable": 1,
"links": [],
"modified": "2026-09-30 12:00:00.000000",
"modified": "2026-10-27 10:40:42.811876",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation",
@@ -61,6 +61,7 @@ class StockReconciliation(StockController):
cost_center: DF.Link | None
difference_amount: DF.Currency
expense_account: DF.Link | None
is_made_from_valuation_comparison: DF.Check
items: DF.Table[StockReconciliationItem]
naming_series: DF.Literal["MAT-RECO-.YYYY.-"]
posting_date: DF.Date
@@ -117,6 +118,8 @@ class StockReconciliation(StockController):
def validate_adjustment_entry(self):
"""An Adjustment Entry resets each item-warehouse to its rows (erpnext.stock.valuation_adjustment),
so none of the counting a reconciliation does applies to it."""
AdjustmentEntry(self).validate_made_from_report()
if not self.expense_account:
self.expense_account = frappe.get_cached_value(
"Company", self.company, "stock_adjustment_account"
@@ -48,7 +48,11 @@ from erpnext.stock.report.stock_valuation_comparison.stock_valuation_comparison
from erpnext.stock.report.stock_valuation_comparison.test_stock_valuation_comparison import (
make_batch_without_batch_wise_valuation,
)
from erpnext.stock.valuation_adjustment import ADJUSTMENT_ENTRY, BackdatedEntryBeforeAdjustmentError
from erpnext.stock.valuation_adjustment import (
ADJUSTMENT_ENTRY,
BackdatedEntryBeforeAdjustmentError,
ManualAdjustmentEntryError,
)
from erpnext.tests.utils import ERPNextTestSuite
COMPANY = "_Test Company"
@@ -142,6 +146,7 @@ class ValuationFixTestCase(ERPNextTestSuite):
self.assertEqual(result["not_adjusted"], [])
adjustment = frappe.get_doc("Stock Reconciliation", result["adjustment_entry"])
self.assertEqual(adjustment.purpose, ADJUSTMENT_ENTRY)
self.assertTrue(adjustment.is_made_from_valuation_comparison)
adjustment.submit()
# from its date on the ledger is right
@@ -711,6 +716,42 @@ class TestAdjustmentEntry(ValuationFixTestCase):
adjustment.items[0].valuation_rate = 90
self.assertRaises(frappe.ValidationError, adjustment.save)
def test_rows_are_checked_on_the_posting_date_set_on_save(self):
item = self.make_fifo_issue_at_wrong_value()
result = make_adjustment_entry(COMPANY, self.get_differences(item), get_adjustment_date(), "00:00:00")
adjustment = frappe.get_doc("Stock Reconciliation", result["adjustment_entry"])
adjustment.set_posting_time = 0
self.assertRaises(frappe.ValidationError, adjustment.save)
def test_adjustment_entry_cannot_be_selected_manually(self):
item = make_item(properties={"is_stock_item": 1, "valuation_method": "FIFO"}).name
make_stock_entry(item_code=item, target=WAREHOUSE, qty=10, rate=100)
reconciliation = frappe.get_doc(
{
"doctype": "Stock Reconciliation",
"company": COMPANY,
"purpose": ADJUSTMENT_ENTRY,
"items": [{"item_code": item, "warehouse": WAREHOUSE, "qty": 10, "valuation_rate": 100}],
}
)
self.assertRaises(ManualAdjustmentEntryError, reconciliation.insert)
reconciliation.purpose = "Stock Reconciliation"
reconciliation.items[0].qty = 12
reconciliation.insert()
reconciliation.purpose = ADJUSTMENT_ENTRY
self.assertRaises(ManualAdjustmentEntryError, reconciliation.save)
def test_duplicate_of_adjustment_entry_is_not_allowed(self):
item = self.make_fifo_issue_at_wrong_value()
result = make_adjustment_entry(COMPANY, self.get_differences(item), get_adjustment_date(), "00:00:00")
copy = frappe.copy_doc(
frappe.get_doc("Stock Reconciliation", result["adjustment_entry"]), ignore_no_copy=False
)
self.assertRaises(ManualAdjustmentEntryError, copy.insert)
def test_no_backdated_entry_before_an_adjustment_entry(self):
item = self.make_fifo_issue_at_wrong_value()
issue = frappe.get_last_doc("Stock Entry", filters={"posting_date": last_year(50)})
+17
View File
@@ -51,6 +51,10 @@ class BackdatedEntryBeforeAdjustmentError(frappe.ValidationError):
pass
class ManualAdjustmentEntryError(frappe.ValidationError):
pass
@dataclass
class AdjustmentRow:
"""One row of an Adjustment Entry: stock counted in at a rate, as a batch, serial nos, or
@@ -317,6 +321,7 @@ def make_adjustment_entry(
doc.update(
{
"purpose": ADJUSTMENT_ENTRY,
"is_made_from_valuation_comparison": 1,
"company": company,
"posting_date": posting_date,
"posting_time": posting_time,
@@ -429,6 +434,18 @@ class AdjustmentEntry:
sum(flt(row.amount_difference) for row in self.doc.items), self.doc.precision("difference_amount")
)
def validate_made_from_report(self) -> None:
if self.doc.is_made_from_valuation_comparison:
return
frappe.throw(
_(
"Adjustment Entry can only be used by the system from the Stock Valuation Comparison report. Users can only select the purpose Opening Stock to add opening stock, or Stock Reconciliation to update stock quantity or valuation rate."
),
title=_("Not Allowed Purpose"),
exc=ManualAdjustmentEntryError,
)
def set_row_amounts(self, plan: AdjustmentPlan, rows) -> None:
"""The first row of an item-warehouse shows what the ledger held, which the reset counts out."""
for index, row in enumerate(rows):