test: create stock test mixin for assertion/utils
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
index c5c0cef..41a3b89 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
@@ -2,11 +2,37 @@
# See license.txt
+from typing import TYPE_CHECKING, Optional, overload
+
import frappe
from frappe.utils import cint, flt
import erpnext
+if TYPE_CHECKING:
+ from erpnext.stock.doctype.stock_entry.stock_entry import StockEntry
+
+
+@overload
+def make_stock_entry(
+ *,
+ item_code: str,
+ qty: float,
+ company: Optional[str] = None,
+ from_warehouse: Optional[str] = None,
+ to_warehouse: Optional[str] = None,
+ rate: Optional[float] = None,
+ serial_no: Optional[str] = None,
+ batch_no: Optional[str] = None,
+ posting_date: Optional[str] = None,
+ posting_time: Optional[str] = None,
+ purpose: Optional[str] = None,
+ do_not_save: bool = False,
+ do_not_submit: bool = False,
+ inspection_required: bool = False,
+) -> "StockEntry":
+ ...
+
@frappe.whitelist()
def make_stock_entry(**args):
diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
index eb1e0fc..55a213c 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
@@ -24,9 +24,10 @@
create_stock_reconciliation,
)
from erpnext.stock.stock_ledger import get_previous_sle
+from erpnext.stock.tests.test_utils import StockTestMixin
-class TestStockLedgerEntry(FrappeTestCase):
+class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
def setUp(self):
items = create_items()
reset("Stock Entry")
@@ -541,30 +542,6 @@
"Incorrect 'Incoming Rate' values fetched for DN items",
)
- def assertSLEs(self, doc, expected_sles, sle_filters=None):
- """Compare sorted SLEs, useful for vouchers that create multiple SLEs for same line"""
-
- filters = {"voucher_no": doc.name, "voucher_type": doc.doctype, "is_cancelled": 0}
- if sle_filters:
- filters.update(sle_filters)
- sles = frappe.get_all(
- "Stock Ledger Entry",
- fields=["*"],
- filters=filters,
- order_by="timestamp(posting_date, posting_time), creation",
- )
-
- for exp_sle, act_sle in zip(expected_sles, sles):
- for k, v in exp_sle.items():
- act_value = act_sle[k]
- if k == "stock_queue":
- act_value = json.loads(act_value)
- if act_value and act_value[0][0] == 0:
- # ignore empty fifo bins
- continue
-
- self.assertEqual(v, act_value, msg=f"{k} doesn't match \n{exp_sle}\n{act_sle}")
-
def test_batchwise_item_valuation_stock_reco(self):
item, warehouses, batches = setup_item_valuation_test()
state = {"stock_value": 0.0, "qty": 0.0}
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 9088eb8..191c03f 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -10,7 +10,7 @@
from frappe.utils import add_days, cstr, flt, nowdate, nowtime, random_string
from erpnext.accounts.utils import get_stock_and_account_balance
-from erpnext.stock.doctype.item.test_item import create_item, make_item
+from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import (
@@ -19,10 +19,11 @@
)
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after
+from erpnext.stock.tests.test_utils import StockTestMixin
from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method
-class TestStockReconciliation(FrappeTestCase):
+class TestStockReconciliation(FrappeTestCase, StockTestMixin):
@classmethod
def setUpClass(cls):
create_batch_or_serial_no_items()
@@ -40,7 +41,7 @@
self._test_reco_sle_gle("Moving Average")
def _test_reco_sle_gle(self, valuation_method):
- item_code = make_item(properties={"valuation_method": valuation_method}).name
+ item_code = self.make_item(properties={"valuation_method": valuation_method}).name
se1, se2, se3 = insert_existing_sle(warehouse="Stores - TCP1", item_code=item_code)
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
@@ -392,7 +393,7 @@
SR4 | Reco | 0 | 6 (posting date: today-1) [backdated]
PR3 | PR | 1 | 7 (posting date: today) # can't post future PR
"""
- item_code = make_item().name
+ item_code = self.make_item().name
warehouse = "_Test Warehouse - _TC"
frappe.flags.dont_execute_stock_reposts = True
@@ -458,7 +459,7 @@
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.stock_ledger import NegativeStockError
- item_code = make_item().name
+ item_code = self.make_item().name
warehouse = "_Test Warehouse - _TC"
pr1 = make_purchase_receipt(
@@ -506,7 +507,7 @@
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.stock_ledger import NegativeStockError
- item_code = make_item().name
+ item_code = self.make_item().name
warehouse = "_Test Warehouse - _TC"
sr = create_stock_reconciliation(
@@ -549,7 +550,7 @@
# repost will make this test useless, qty should update in realtime without reposts
frappe.flags.dont_execute_stock_reposts = True
- item_code = make_item().name
+ item_code = self.make_item().name
warehouse = "_Test Warehouse - _TC"
sr = create_stock_reconciliation(
diff --git a/erpnext/stock/tests/test_utils.py b/erpnext/stock/tests/test_utils.py
index 9ee0c9f..e07e08c 100644
--- a/erpnext/stock/tests/test_utils.py
+++ b/erpnext/stock/tests/test_utils.py
@@ -1,16 +1,50 @@
+import json
+
import frappe
from frappe.tests.utils import FrappeTestCase
-from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.utils import scan_barcode
-class TestStockUtilities(FrappeTestCase):
+class StockTestMixin:
+ """Mixin to simplfy stock ledger tests, useful for all stock transactions."""
+
+ def make_item(self, item_code=None, properties=None, *args, **kwargs):
+ from erpnext.stock.doctype.item.test_item import make_item
+
+ return make_item(item_code, properties, *args, **kwargs)
+
+ def assertSLEs(self, doc, expected_sles, sle_filters=None):
+ """Compare sorted SLEs, useful for vouchers that create multiple SLEs for same line"""
+
+ filters = {"voucher_no": doc.name, "voucher_type": doc.doctype, "is_cancelled": 0}
+ if sle_filters:
+ filters.update(sle_filters)
+ sles = frappe.get_all(
+ "Stock Ledger Entry",
+ fields=["*"],
+ filters=filters,
+ order_by="timestamp(posting_date, posting_time), creation",
+ )
+
+ for exp_sle, act_sle in zip(expected_sles, sles):
+ for k, v in exp_sle.items():
+ act_value = act_sle[k]
+ if k == "stock_queue":
+ act_value = json.loads(act_value)
+ if act_value and act_value[0][0] == 0:
+ # ignore empty fifo bins
+ continue
+
+ self.assertEqual(v, act_value, msg=f"{k} doesn't match \n{exp_sle}\n{act_sle}")
+
+
+class TestStockUtilities(FrappeTestCase, StockTestMixin):
def test_barcode_scanning(self):
- simple_item = make_item(properties={"barcodes": [{"barcode": "12399"}]})
+ simple_item = self.make_item(properties={"barcodes": [{"barcode": "12399"}]})
self.assertEqual(scan_barcode("12399")["item_code"], simple_item.name)
- batch_item = make_item(properties={"has_batch_no": 1, "create_new_batch": 1})
+ batch_item = self.make_item(properties={"has_batch_no": 1, "create_new_batch": 1})
batch = frappe.get_doc(doctype="Batch", item=batch_item.name).insert()
batch_scan = scan_barcode(batch.name)
@@ -19,7 +53,7 @@
self.assertEqual(batch_scan["has_batch_no"], 1)
self.assertEqual(batch_scan["has_serial_no"], 0)
- serial_item = make_item(properties={"has_serial_no": 1})
+ serial_item = self.make_item(properties={"has_serial_no": 1})
serial = frappe.get_doc(
doctype="Serial No", item_code=serial_item.name, serial_no=frappe.generate_hash()
).insert()