fix: throw when the transaction company has no sample retention warehouse

Item.retain_sample is validated against any company having one configured,
since Item is not company-scoped. The transaction company may still not be,
in which case get_batch_qty received warehouse=None and returned its batch
list, which was then compared numerically -> TypeError.

Resolve company -> retention warehouse through one helper that throws a
clear message instead, covering both the sample stock entry and the
whitelisted quantity validation.
This commit is contained in:
Mihir Kandoi
2026-07-28 19:41:29 +05:30
parent 26613d258e
commit 455251abe1
2 changed files with 38 additions and 2 deletions

View File

@@ -1178,7 +1178,7 @@ def ceil_qty_if_uom_has_whole_number(qty, stock_uom):
def move_sample_to_retention_warehouse(company: str, items: str | list):
items = frappe.parse_json(items)
retention_warehouse = frappe.get_cached_value("Company", company, "sample_retention_warehouse")
retention_warehouse = get_sample_retention_warehouse(company)
stock_entry = frappe.new_doc("Stock Entry")
stock_entry.company = company
stock_entry.purpose = "Material Transfer"
@@ -1281,10 +1281,22 @@ def validate_sample_quantity(
_("Sample quantity {0} cannot be more than received quantity {1}").format(sample_quantity, qty)
)
retention_warehouse = frappe.get_cached_value("Company", company, "sample_retention_warehouse")
retention_warehouse = get_sample_retention_warehouse(company)
return _adjust_sample_quantity(item_code, sample_quantity, batch_no, get_batch_qty, retention_warehouse)
def get_sample_retention_warehouse(company: str) -> str:
warehouse = frappe.get_cached_value("Company", company, "sample_retention_warehouse")
if not warehouse:
frappe.throw(
_("Please set {0} in Company {1} to retain samples.").format(
bold(_("Sample Retention Warehouse")), bold(company)
),
title=_("Sample Retention Warehouse Missing"),
)
return warehouse
def _adjust_sample_quantity(item_code, sample_quantity, batch_no, get_batch_qty, retention_warehouse):
retainted_qty = get_batch_qty(batch_no, retention_warehouse, item_code) if batch_no else 0
max_retain_qty = frappe.get_value("Item", item_code, "sample_quantity")

View File

@@ -3236,6 +3236,30 @@ class TestStockEntryCoverage(ERPNextTestSuite):
)
self.assertRaises(frappe.ValidationError, validate_sample_quantity, item.name, 10, 5, "_Test Company")
def test_validate_sample_quantity_raises_when_company_has_no_retention_warehouse(self):
"""Item.retain_sample only needs *some* company configured, so the transaction company may not be."""
from erpnext.stock.doctype.stock_entry.services.manufacturing import (
validate_sample_quantity,
)
frappe.db.set_value(
"Company", "_Test Company", "sample_retention_warehouse", "_Test Warehouse 1 - _TC"
)
frappe.db.set_value("Company", "_Test Company 1", "sample_retention_warehouse", None)
item = make_item(
"_Sample Qty No Retention Item",
{"is_stock_item": 1, "retain_sample": 1, "sample_quantity": 2, "has_batch_no": 1},
)
self.assertRaises(
frappe.ValidationError,
validate_sample_quantity,
item.name,
1,
5,
"_Test Company 1",
"_Sample Batch",
)
# ── get_expired_batches ────────────────────────────────────────────────────
def test_get_expired_batches_includes_expired_batch(self):