fix: travis issue
This commit is contained in:
parent
7290dd87be
commit
c2d7461d3c
@ -128,6 +128,7 @@ class POSInvoice(SalesInvoice):
|
||||
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):
|
||||
|
@ -76,7 +76,6 @@ class DeprecatedBatchNoValuation:
|
||||
|
||||
@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 @@ class DeprecatedBatchNoValuation:
|
||||
== 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 @@ class DeprecatedBatchNoValuation:
|
||||
)
|
||||
.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)
|
||||
|
@ -8,8 +8,8 @@ import frappe
|
||||
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 @@ def get_batch_qty(
|
||||
: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 @@ def get_available_batches(kwargs):
|
||||
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
|
||||
|
@ -10,15 +10,15 @@ from frappe.utils import cint, flt
|
||||
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 @@ class TestBatch(FrappeTestCase):
|
||||
).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 @@ class TestBatch(FrappeTestCase):
|
||||
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 @@ class TestBatch(FrappeTestCase):
|
||||
).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 @@ class TestBatch(FrappeTestCase):
|
||||
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 @@ class TestBatch(FrappeTestCase):
|
||||
)
|
||||
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 @@ class TestBatch(FrappeTestCase):
|
||||
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 @@ class TestBatch(FrappeTestCase):
|
||||
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 @@ class TestBatch(FrappeTestCase):
|
||||
|
||||
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 @@ class TestBatch(FrappeTestCase):
|
||||
|
||||
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 @@ class TestBatch(FrappeTestCase):
|
||||
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",
|
||||
|
@ -9,7 +9,7 @@ import frappe
|
||||
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 @@ class SerialNoExistsInFutureTransactionError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class BatchNegativeStockError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class SerialandBatchBundle(Document):
|
||||
def validate(self):
|
||||
self.validate_serial_and_batch_no()
|
||||
@ -81,7 +85,7 @@ class SerialandBatchBundle(Document):
|
||||
|
||||
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 @@ class SerialandBatchBundle(Document):
|
||||
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 @@ class SerialandBatchBundle(Document):
|
||||
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 @@ def get_available_batches(kwargs):
|
||||
.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
|
||||
|
@ -405,28 +405,6 @@ frappe.ui.form.on('Stock Entry', {
|
||||
}
|
||||
},
|
||||
|
||||
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', {
|
||||
|
||||
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 @@ frappe.ui.form.on('Stock Entry Detail', {
|
||||
},
|
||||
|
||||
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);
|
||||
|
@ -747,7 +747,7 @@ class StockEntry(StockController):
|
||||
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 @@ class StockEntry(StockController):
|
||||
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 @@ class StockEntry(StockController):
|
||||
"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 @@ class StockEntry(StockController):
|
||||
"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,
|
||||
|
@ -52,6 +52,7 @@ def make_stock_entry(**args):
|
||||
: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 @@ def make_stock_entry(**args):
|
||||
# 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 @@ def make_stock_entry(**args):
|
||||
"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 @@ def make_stock_entry(**args):
|
||||
s.insert()
|
||||
if not args.do_not_submit:
|
||||
s.submit()
|
||||
|
||||
s.load_from_db()
|
||||
|
||||
return s
|
||||
|
@ -67,7 +67,7 @@ class SerialBatchBundle:
|
||||
"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 @@ class SerialBatchBundle:
|
||||
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 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
||||
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 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
||||
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 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
||||
)
|
||||
.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 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
||||
.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 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
||||
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 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
||||
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 @@ def get_batch_nos(serial_and_batch_bundle):
|
||||
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 @@ class SerialBatchCreation:
|
||||
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 @@ class SerialBatchCreation:
|
||||
|
||||
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 @@ class SerialBatchCreation:
|
||||
{
|
||||
"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 @@ class SerialBatchCreation:
|
||||
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 @@ class SerialBatchCreation:
|
||||
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 @@ class SerialBatchCreation:
|
||||
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(
|
||||
|
@ -732,6 +732,7 @@ class update_entries_after(object):
|
||||
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 @@ def get_valuation_rate(
|
||||
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 @@ def get_valuation_rate(
|
||||
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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user