fix: travis issue
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index e14f9e6..bf393c0 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -128,6 +128,7 @@
doc = frappe.get_doc("Serial and Batch Bundle", item.serial_and_batch_bundle)
if doc.docstatus == 0:
+ doc.flags.ignore_voucher_validation = True
doc.submit()
def check_phone_payments(self):
diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py
index 8b4279b..b7c5d57 100644
--- a/erpnext/stock/deprecated_serial_batch.py
+++ b/erpnext/stock/deprecated_serial_batch.py
@@ -76,7 +76,6 @@
@deprecated
def get_sle_for_batches(self):
- batch_nos = list(self.batch_nos.keys())
sle = frappe.qb.DocType("Stock Ledger Entry")
timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime(
@@ -88,7 +87,11 @@
== CombineDatetime(self.sle.posting_date, self.sle.posting_time)
) & (sle.creation < self.sle.creation)
- return (
+ batch_nos = self.batch_nos
+ if isinstance(self.batch_nos, dict):
+ batch_nos = list(self.batch_nos.keys())
+
+ query = (
frappe.qb.from_(sle)
.select(
sle.batch_no,
@@ -97,11 +100,15 @@
)
.where(
(sle.item_code == self.sle.item_code)
- & (sle.name != self.sle.name)
& (sle.warehouse == self.sle.warehouse)
& (sle.batch_no.isin(batch_nos))
& (sle.is_cancelled == 0)
)
.where(timestamp_condition)
.groupby(sle.batch_no)
- ).run(as_dict=True)
+ )
+
+ if self.sle.name:
+ query = query.where(sle.name != self.sle.name)
+
+ return query.run(as_dict=True)
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 84ab74a..88a0372 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -8,8 +8,8 @@
from frappe import _
from frappe.model.document import Document
from frappe.model.naming import make_autoname, revert_series_if_last
-from frappe.query_builder.functions import CombineDatetime, CurDate, Sum
-from frappe.utils import cint, flt, get_link_to_form, nowtime
+from frappe.query_builder.functions import CurDate, Sum
+from frappe.utils import cint, flt, get_link_to_form
from frappe.utils.data import add_days
from frappe.utils.jinja import render_template
@@ -179,44 +179,28 @@
:param warehouse: Optional - give qty for this warehouse
:param item_code: Optional - give qty for this item"""
- sle = frappe.qb.DocType("Stock Ledger Entry")
+ from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
+ get_auto_batch_nos,
+ )
- out = 0
- if batch_no and warehouse:
- query = (
- frappe.qb.from_(sle)
- .select(Sum(sle.actual_qty))
- .where((sle.is_cancelled == 0) & (sle.warehouse == warehouse) & (sle.batch_no == batch_no))
- )
+ batchwise_qty = defaultdict(float)
+ kwargs = frappe._dict({
+ "item_code": item_code,
+ "warehouse": warehouse,
+ "posting_date": posting_date,
+ "posting_time": posting_time,
+ "batch_no": batch_no
+ })
- if posting_date:
- if posting_time is None:
- posting_time = nowtime()
+ batches = get_auto_batch_nos(kwargs)
- query = query.where(
- CombineDatetime(sle.posting_date, sle.posting_time)
- <= CombineDatetime(posting_date, posting_time)
- )
+ if not (batch_no and warehouse):
+ return batches
- out = query.run(as_list=True)[0][0] or 0
+ for batch in batches:
+ batchwise_qty[batch.get("batch_no")] += batch.get("qty")
- if batch_no and not warehouse:
- out = (
- frappe.qb.from_(sle)
- .select(sle.warehouse, Sum(sle.actual_qty).as_("qty"))
- .where((sle.is_cancelled == 0) & (sle.batch_no == batch_no))
- .groupby(sle.warehouse)
- ).run(as_dict=True)
-
- if not batch_no and item_code and warehouse:
- out = (
- frappe.qb.from_(sle)
- .select(sle.batch_no, Sum(sle.actual_qty).as_("qty"))
- .where((sle.is_cancelled == 0) & (sle.item_code == item_code) & (sle.warehouse == warehouse))
- .groupby(sle.batch_no)
- ).run(as_dict=True)
-
- return out
+ return batchwise_qty[batch_no]
@frappe.whitelist()
@@ -366,3 +350,14 @@
batchwise_qty[batch.get("batch_no")] += batch.get("qty")
return batchwise_qty
+
+
+def get_batch_no(bundle_id):
+ from erpnext.stock.serial_batch_bundle import get_batch_nos
+
+ batches = defaultdict(float)
+
+ for batch_id, d in get_batch_nos(bundle_id).items():
+ batches[batch_id] += abs(d.get("qty"))
+
+ return batches
diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py
index 271e2e0..cf0d3f2 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -10,15 +10,15 @@
from frappe.utils.data import add_to_date, getdate
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
-from erpnext.stock.doctype.batch.batch import UnableToSelectBatchError, get_batch_no, get_batch_qty
+from erpnext.stock.doctype.batch.batch import get_batch_no, get_batch_qty
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
-from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
-from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
- create_stock_reconciliation,
+from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
+ BatchNegativeStockError,
)
+from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.get_item_details import get_item_details
-from erpnext.stock.stock_ledger import get_valuation_rate
+from erpnext.stock.serial_batch_bundle import SerialBatchCreation
class TestBatch(FrappeTestCase):
@@ -49,8 +49,10 @@
).insert()
receipt.submit()
- self.assertTrue(receipt.items[0].batch_no)
- self.assertEqual(get_batch_qty(receipt.items[0].batch_no, receipt.items[0].warehouse), batch_qty)
+ receipt.load_from_db()
+ self.assertTrue(receipt.items[0].serial_and_batch_bundle)
+ batch_no = get_batch_from_bundle(receipt.items[0].serial_and_batch_bundle)
+ self.assertEqual(get_batch_qty(batch_no, receipt.items[0].warehouse), batch_qty)
return receipt
@@ -80,9 +82,12 @@
stock_entry.insert()
stock_entry.submit()
- self.assertTrue(stock_entry.items[0].batch_no)
+ stock_entry.load_from_db()
+
+ bundle = stock_entry.items[0].serial_and_batch_bundle
+ self.assertTrue(bundle)
self.assertEqual(
- get_batch_qty(stock_entry.items[0].batch_no, stock_entry.items[0].t_warehouse), 90
+ get_batch_qty(get_batch_from_bundle(bundle), stock_entry.items[0].t_warehouse), 90
)
def test_delivery_note(self):
@@ -103,25 +108,35 @@
).insert()
delivery_note.submit()
+ receipt.load_from_db()
+ delivery_note.load_from_db()
+
# shipped from FEFO batch
self.assertEqual(
- delivery_note.items[0].batch_no, get_batch_no(item_code, receipt.items[0].warehouse, batch_qty)
+ get_batch_no(delivery_note.items[0].serial_and_batch_bundle),
+ get_batch_no(receipt.items[0].serial_and_batch_bundle),
)
- def test_delivery_note_fail(self):
+ def test_batch_negative_stock_error(self):
"""Test automatic batch selection for outgoing items"""
receipt = self.test_purchase_receipt(100)
- delivery_note = frappe.get_doc(
- dict(
- doctype="Delivery Note",
- customer="_Test Customer",
- company=receipt.company,
- items=[
- dict(item_code="ITEM-BATCH-1", qty=5000, rate=10, warehouse=receipt.items[0].warehouse)
- ],
- )
+
+ receipt.load_from_db()
+ batch_no = get_batch_from_bundle(receipt.items[0].serial_and_batch_bundle)
+ sn_doc = SerialBatchCreation(
+ {
+ "item_code": "ITEM-BATCH-1",
+ "warehouse": receipt.items[0].warehouse,
+ "voucher_type": "Delivery Note",
+ "qty": 5000,
+ "avg_rate": 10,
+ "batches": frappe._dict({batch_no: 90}),
+ "type_of_transaction": "Outward",
+ "company": receipt.company,
+ }
)
- self.assertRaises(UnableToSelectBatchError, delivery_note.insert)
+
+ self.assertRaises(BatchNegativeStockError, sn_doc.make_serial_and_batch_bundle)
def test_stock_entry_outgoing(self):
"""Test automatic batch selection for outgoing stock entry"""
@@ -149,9 +164,9 @@
stock_entry.insert()
stock_entry.submit()
- # assert same batch is selected
self.assertEqual(
- stock_entry.items[0].batch_no, get_batch_no(item_code, receipt.items[0].warehouse, batch_qty)
+ get_batch_no(stock_entry.items[0].serial_and_batch_bundle),
+ get_batch_no(receipt.items[0].serial_and_batch_bundle),
)
def test_batch_split(self):
@@ -201,6 +216,19 @@
)
batch.save()
+ sn_doc = SerialBatchCreation(
+ {
+ "item_code": item_name,
+ "warehouse": warehouse,
+ "voucher_type": "Stock Entry",
+ "qty": 90,
+ "avg_rate": 10,
+ "batches": frappe._dict({batch_name: 90}),
+ "type_of_transaction": "Inward",
+ "company": "_Test Company",
+ }
+ ).make_serial_and_batch_bundle()
+
stock_entry = frappe.get_doc(
dict(
doctype="Stock Entry",
@@ -210,10 +238,10 @@
dict(
item_code=item_name,
qty=90,
+ serial_and_batch_bundle=sn_doc.name,
t_warehouse=warehouse,
cost_center="Main - _TC",
rate=10,
- batch_no=batch_name,
allow_zero_valuation_rate=1,
)
],
@@ -320,7 +348,8 @@
batches = {}
for rate in rates:
se = make_stock_entry(item_code=item_code, qty=10, rate=rate, target=warehouse)
- batches[se.items[0].batch_no] = rate
+ batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
+ batches[batch_no] = rate
LOW, HIGH = list(batches.keys())
@@ -341,7 +370,9 @@
sle = frappe.get_last_doc("Stock Ledger Entry", {"is_cancelled": 0, "voucher_no": se.name})
- stock_value_difference = sle.actual_qty * batches[sle.batch_no]
+ stock_value_difference = (
+ sle.actual_qty * batches[get_batch_from_bundle(sle.serial_and_batch_bundle)]
+ )
self.assertAlmostEqual(sle.stock_value_difference, stock_value_difference)
stock_value += stock_value_difference
@@ -353,45 +384,6 @@
self.assertEqual(json.loads(sle.stock_queue), []) # queues don't apply on batched items
- def test_moving_batch_valuation_rates(self):
- item_code = "_TestBatchWiseVal"
- warehouse = "_Test Warehouse - _TC"
- self.make_batch_item(item_code)
-
- def assertValuation(expected):
- actual = get_valuation_rate(
- item_code, warehouse, "voucher_type", "voucher_no", batch_no=batch_no
- )
- self.assertAlmostEqual(actual, expected)
-
- se = make_stock_entry(item_code=item_code, qty=100, rate=10, target=warehouse)
- batch_no = se.items[0].batch_no
- assertValuation(10)
-
- # consumption should never affect current valuation rate
- make_stock_entry(item_code=item_code, qty=20, source=warehouse)
- assertValuation(10)
-
- make_stock_entry(item_code=item_code, qty=30, source=warehouse)
- assertValuation(10)
-
- # 50 * 10 = 500 current value, add more item with higher valuation
- make_stock_entry(item_code=item_code, qty=50, rate=20, target=warehouse, batch_no=batch_no)
- assertValuation(15)
-
- # consuming again shouldn't do anything
- make_stock_entry(item_code=item_code, qty=20, source=warehouse)
- assertValuation(15)
-
- # reset rate with stock reconiliation
- create_stock_reconciliation(
- item_code=item_code, warehouse=warehouse, qty=10, rate=25, batch_no=batch_no
- )
- assertValuation(25)
-
- make_stock_entry(item_code=item_code, qty=20, rate=20, target=warehouse, batch_no=batch_no)
- assertValuation((20 * 20 + 10 * 25) / (10 + 20))
-
def test_update_batch_properties(self):
item_code = "_TestBatchWiseVal"
self.make_batch_item(item_code)
@@ -430,6 +422,12 @@
self.assertEqual("BATCHEXISTING002", pr_2.items[0].batch_no)
+def get_batch_from_bundle(bundle):
+ batches = get_batch_no(bundle)
+
+ return list(batches.keys())[0]
+
+
def create_batch(item_code, rate, create_item_price_for_batch):
pi = make_purchase_invoice(
company="_Test Company",
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index 0624ae9..6f15215 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -9,7 +9,7 @@
from frappe import _, bold
from frappe.model.document import Document
from frappe.query_builder.functions import CombineDatetime, Sum
-from frappe.utils import add_days, cint, flt, get_link_to_form, today
+from frappe.utils import add_days, cint, flt, get_link_to_form, nowtime, today
from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation
@@ -18,6 +18,10 @@
pass
+class BatchNegativeStockError(frappe.ValidationError):
+ pass
+
+
class SerialandBatchBundle(Document):
def validate(self):
self.validate_serial_and_batch_no()
@@ -81,7 +85,7 @@
def set_incoming_rate_for_outward_transaction(self, row=None, save=False):
sle = self.get_sle_for_outward_transaction(row)
- if not sle.actual_qty:
+ if not sle.actual_qty and sle.qty:
sle.actual_qty = sle.qty
if self.has_serial_no:
@@ -122,7 +126,7 @@
of quantity {bold(available_qty)} in the
warehouse {self.warehouse}"""
- frappe.throw(_(msg))
+ frappe.throw(_(msg), BatchNegativeStockError)
def get_sle_for_outward_transaction(self, row):
return frappe._dict(
@@ -228,7 +232,13 @@
if self.voucher_no and not frappe.db.exists(self.voucher_type, self.voucher_no):
self.throw_error_message(f"The {self.voucher_type} # {self.voucher_no} does not exist")
- if frappe.get_cached_value(self.voucher_type, self.voucher_no, "docstatus") != 1:
+ if self.flags.ignore_voucher_validation:
+ return
+
+ if (
+ self.docstatus == 1
+ and frappe.get_cached_value(self.voucher_type, self.voucher_no, "docstatus") != 1
+ ):
self.throw_error_message(f"The {self.voucher_type} # {self.voucher_no} should be submit first.")
def check_future_entries_exists(self):
@@ -750,6 +760,16 @@
.groupby(batch_ledger.batch_no)
)
+ if kwargs.get("posting_date"):
+ if kwargs.get("posting_time") is None:
+ kwargs.posting_time = nowtime()
+
+ timestamp_condition = CombineDatetime(
+ stock_ledger_entry.posting_date, stock_ledger_entry.posting_time
+ ) <= CombineDatetime(kwargs.posting_date, kwargs.posting_time)
+
+ query = query.where(timestamp_condition)
+
for field in ["warehouse", "item_code"]:
if not kwargs.get(field):
continue
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 8788e15..17e6d83 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -405,28 +405,6 @@
}
},
- set_serial_no: function(frm, cdt, cdn, callback) {
- var d = frappe.model.get_doc(cdt, cdn);
- if(!d.item_code && !d.s_warehouse && !d.qty) return;
- var args = {
- 'item_code' : d.item_code,
- 'warehouse' : cstr(d.s_warehouse),
- 'stock_qty' : d.transfer_qty
- };
- frappe.call({
- method: "erpnext.stock.get_item_details.get_serial_no",
- args: {"args": args},
- callback: function(r) {
- if (!r.exe && r.message){
- frappe.model.set_value(cdt, cdn, "serial_no", r.message);
- }
- if (callback) {
- callback();
- }
- }
- });
- },
-
make_retention_stock_entry: function(frm) {
frappe.call({
method: "erpnext.stock.doctype.stock_entry.stock_entry.move_sample_to_retention_warehouse",
@@ -682,9 +660,7 @@
frappe.ui.form.on('Stock Entry Detail', {
qty(frm, cdt, cdn) {
- frm.events.set_serial_no(frm, cdt, cdn, () => {
- frm.events.set_basic_rate(frm, cdt, cdn);
- });
+ frm.events.set_basic_rate(frm, cdt, cdn);
},
conversion_factor(frm, cdt, cdn) {
@@ -692,9 +668,7 @@
},
s_warehouse(frm, cdt, cdn) {
- frm.events.set_serial_no(frm, cdt, cdn, () => {
- frm.events.get_warehouse_details(frm, cdt, cdn);
- });
+ frm.events.get_warehouse_details(frm, cdt, cdn);
// set allow_zero_valuation_rate to 0 if s_warehouse is selected.
let item = frappe.get_doc(cdt, cdn);
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index fb5a93c..056a3ae 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -747,7 +747,7 @@
currency=erpnext.get_company_currency(self.company),
company=self.company,
raise_error_if_no_rate=raise_error_if_no_rate,
- batch_no=d.batch_no,
+ serial_and_batch_bundle=d.serial_and_batch_bundle,
)
# do not round off basic rate to avoid precision loss
@@ -904,6 +904,9 @@
return
for row in self.items:
+ if not row.s_warehouse:
+ continue
+
if row.serial_and_batch_bundle or row.item_code not in serial_or_batch_items:
continue
@@ -915,7 +918,7 @@
"posting_time": self.posting_time,
"voucher_type": self.doctype,
"voucher_detail_no": row.name,
- "total_qty": row.qty,
+ "qty": row.qty * -1,
"type_of_transaction": "Outward",
"company": self.company,
"do_not_submit": True,
@@ -1437,10 +1440,8 @@
"qty": args.get("qty"),
"transfer_qty": args.get("qty"),
"conversion_factor": 1,
- "batch_no": "",
"actual_qty": 0,
"basic_rate": 0,
- "serial_no": "",
"has_serial_no": item.has_serial_no,
"has_batch_no": item.has_batch_no,
"sample_quantity": item.sample_quantity,
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
index 0f90013..674a49b 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
@@ -52,6 +52,7 @@
:do_not_save: Optional flag
:do_not_submit: Optional flag
"""
+ from erpnext.stock.serial_batch_bundle import SerialBatchCreation
def process_serial_numbers(serial_nos_list):
serial_nos_list = [
@@ -131,16 +132,27 @@
# We can find out the serial number using the batch source document
serial_number = args.serial_no
+ bundle_id = None
if not args.serial_no and args.qty and args.batch_no:
- serial_number_list = frappe.get_list(
- doctype="Stock Ledger Entry",
- fields=["serial_no"],
- filters={"batch_no": args.batch_no, "warehouse": args.from_warehouse},
+ batches = frappe._dict({args.batch_no: args.qty})
+
+ bundle_id = (
+ SerialBatchCreation(
+ {
+ "item_code": args.item,
+ "warehouse": args.source or args.target,
+ "voucher_type": "Stock Entry",
+ "total_qty": args.qty * (-1 if args.source else 1),
+ "batches": batches,
+ "type_of_transaction": "Outward" if args.source else "Inward",
+ "company": s.company,
+ }
+ )
+ .make_serial_and_batch_bundle()
+ .name
)
- serial_number = process_serial_numbers(serial_number_list)
args.serial_no = serial_number
-
s.append(
"items",
{
@@ -148,6 +160,7 @@
"s_warehouse": args.source,
"t_warehouse": args.target,
"qty": args.qty,
+ "serial_and_batch_bundle": bundle_id,
"basic_rate": args.rate or args.basic_rate,
"conversion_factor": args.conversion_factor or 1.0,
"transfer_qty": flt(args.qty) * (flt(args.conversion_factor) or 1.0),
@@ -164,4 +177,7 @@
s.insert()
if not args.do_not_submit:
s.submit()
+
+ s.load_from_db()
+
return s
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index c14df3b..7a6190e 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -67,7 +67,7 @@
"voucher_type": self.sle.voucher_type,
"voucher_no": self.sle.voucher_no,
"voucher_detail_no": self.sle.voucher_detail_no,
- "total_qty": self.sle.actual_qty,
+ "qty": self.sle.actual_qty,
"avg_rate": self.sle.incoming_rate,
"total_amount": flt(self.sle.actual_qty) * flt(self.sle.incoming_rate),
"type_of_transaction": "Inward" if self.sle.actual_qty > 0 else "Outward",
@@ -136,7 +136,6 @@
and not self.sle.serial_and_batch_bundle
and self.item_details.has_batch_no == 1
and self.item_details.create_new_batch
- and self.item_details.batch_number_series
):
self.make_serial_batch_no_bundle()
elif not self.sle.is_cancelled:
@@ -393,7 +392,7 @@
self.calculate_valuation_rate()
def calculate_avg_rate(self):
- if self.sle.actual_qty > 0:
+ if flt(self.sle.actual_qty) > 0:
self.stock_value_change = frappe.get_cached_value(
"Serial and Batch Bundle", self.sle.serial_and_batch_bundle, "total_amount"
)
@@ -414,7 +413,9 @@
parent = frappe.qb.DocType("Serial and Batch Bundle")
child = frappe.qb.DocType("Serial and Batch Entry")
- batch_nos = list(self.batch_nos.keys())
+ batch_nos = self.batch_nos
+ if isinstance(self.batch_nos, dict):
+ batch_nos = list(self.batch_nos.keys())
timestamp_condition = ""
if self.sle.posting_date and self.sle.posting_time:
@@ -433,7 +434,6 @@
)
.where(
(child.batch_no.isin(batch_nos))
- & (child.parent != self.sle.serial_and_batch_bundle)
& (parent.warehouse == self.sle.warehouse)
& (parent.item_code == self.sle.item_code)
& (parent.docstatus == 1)
@@ -443,8 +443,11 @@
.groupby(child.batch_no)
)
+ if self.sle.serial_and_batch_bundle:
+ query = query.where(child.parent != self.sle.serial_and_batch_bundle)
+
if timestamp_condition:
- query.where(timestamp_condition)
+ query = query.where(timestamp_condition)
return query.run(as_dict=True)
@@ -455,6 +458,9 @@
return get_batch_nos(self.sle.serial_and_batch_bundle)
def set_stock_value_difference(self):
+ if not self.sle.serial_and_batch_bundle:
+ return
+
self.stock_value_change = 0
for batch_no, ledger in self.batch_nos.items():
stock_value_change = self.batch_avg_rate[batch_no] * ledger.qty
@@ -471,11 +477,10 @@
self.wh_data.stock_value + self.stock_value_change
)
+ self.wh_data.qty_after_transaction += self.sle.actual_qty
if self.wh_data.qty_after_transaction:
self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction
- self.wh_data.qty_after_transaction += self.sle.actual_qty
-
def get_incoming_rate(self):
return abs(flt(self.stock_value_change) / flt(self.sle.actual_qty))
@@ -484,7 +489,8 @@
entries = frappe.get_all(
"Serial and Batch Entry",
fields=["batch_no", "qty", "name"],
- filters={"parent": serial_and_batch_bundle, "is_outward": 1},
+ filters={"parent": serial_and_batch_bundle},
+ order_by="idx",
)
return {d.batch_no: d for d in entries}
@@ -591,6 +597,12 @@
setattr(self, "posting_date", today())
self.__dict__["posting_date"] = self.posting_date
+ if not self.get("actual_qty"):
+ qty = self.get("qty") or self.get("total_qty")
+
+ setattr(self, "actual_qty", qty)
+ self.__dict__["actual_qty"] = self.actual_qty
+
def duplicate_package(self):
if not self.serial_and_batch_bundle:
return
@@ -613,14 +625,14 @@
if self.type_of_transaction == "Outward":
self.set_auto_serial_batch_entries_for_outward()
- elif self.type_of_transaction == "Inward":
+ elif self.type_of_transaction == "Inward" and not self.get("batches"):
self.set_auto_serial_batch_entries_for_inward()
self.set_serial_batch_entries(doc)
- doc.set_incoming_rate()
doc.save()
if not hasattr(self, "do_not_submit") or not self.do_not_submit:
+ doc.flags.ignore_voucher_validation = True
doc.submit()
return doc
@@ -633,7 +645,7 @@
{
"item_code": self.item_code,
"warehouse": self.warehouse,
- "qty": abs(self.total_qty),
+ "qty": abs(self.actual_qty),
"based_on": frappe.db.get_single_value("Stock Settings", "pick_serial_and_batch_based_on"),
}
)
@@ -651,7 +663,7 @@
if self.has_serial_no:
self.serial_nos = self.get_auto_created_serial_nos()
else:
- self.batches = frappe._dict({self.batch_no: abs(self.total_qty)})
+ self.batches = frappe._dict({self.batch_no: abs(self.actual_qty)})
def set_serial_batch_entries(self, doc):
if self.get("serial_nos"):
@@ -698,9 +710,9 @@
return make_batch(
frappe._dict(
{
- "item": self.item_code,
- "reference_doctype": self.voucher_type,
- "reference_name": self.voucher_no,
+ "item": self.get("item_code"),
+ "reference_doctype": self.get("voucher_type"),
+ "reference_name": self.get("voucher_no"),
}
)
)
@@ -709,7 +721,7 @@
sr_nos = []
serial_nos_details = []
- for i in range(abs(cint(self.total_qty))):
+ for i in range(abs(cint(self.actual_qty))):
serial_no = make_autoname(self.serial_no_series, "Serial No")
sr_nos.append(serial_no)
serial_nos_details.append(
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index e616ed0..aefc692 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -732,6 +732,7 @@
self.wh_data.stock_value = flt(self.wh_data.stock_value, self.currency_precision)
if not self.wh_data.qty_after_transaction:
self.wh_data.stock_value = 0.0
+
stock_value_difference = self.wh_data.stock_value - self.wh_data.prev_stock_value
self.wh_data.prev_stock_value = self.wh_data.stock_value
@@ -1421,7 +1422,7 @@
currency=None,
company=None,
raise_error_if_no_rate=True,
- batch_no=None,
+ serial_and_batch_bundle=None,
):
if not company:
@@ -1430,21 +1431,20 @@
last_valuation_rate = None
# Get moving average rate of a specific batch number
- if warehouse and batch_no and frappe.db.get_value("Batch", batch_no, "use_batchwise_valuation"):
- last_valuation_rate = frappe.db.sql(
- """
- select sum(stock_value_difference) / sum(actual_qty)
- from `tabStock Ledger Entry`
- where
- item_code = %s
- AND warehouse = %s
- AND batch_no = %s
- AND is_cancelled = 0
- AND NOT (voucher_no = %s AND voucher_type = %s)
- """,
- (item_code, warehouse, batch_no, voucher_no, voucher_type),
+ if warehouse and serial_and_batch_bundle:
+ batch_obj = BatchNoValuation(
+ sle=frappe._dict(
+ {
+ "item_code": item_code,
+ "warehouse": warehouse,
+ "actual_qty": -1,
+ "serial_and_batch_bundle": serial_and_batch_bundle,
+ }
+ )
)
+ return batch_obj.get_incoming_rate()
+
# Get valuation rate from last sle for the same item and warehouse
if not last_valuation_rate or last_valuation_rate[0][0] is None:
last_valuation_rate = frappe.db.sql(