Merge branch 'stock_reco_for_serialized_items' of https://github.com/rohitwaghchaure/erpnext_develop into rohitwaghchaure-stock_reco_for_serialized_items

This commit is contained in:
Nabin Hait 2019-07-15 18:12:49 +05:30
commit 6e264701cb
8 changed files with 553 additions and 597 deletions

View File

@ -222,7 +222,7 @@ def validate_serial_no(sle, item_det):
frappe.throw(_("Serial No {0} has already been received").format(serial_no), frappe.throw(_("Serial No {0} has already been received").format(serial_no),
SerialNoDuplicateError) SerialNoDuplicateError)
if (sr.delivery_document_no and sle.voucher_type != 'Stock Entry' if (sr.delivery_document_no and sle.voucher_type not in ['Stock Entry', 'Stock Reconciliation']
and sle.voucher_type == sr.delivery_document_type): and sle.voucher_type == sr.delivery_document_type):
return_against = frappe.db.get_value(sle.voucher_type, sle.voucher_no, 'return_against') return_against = frappe.db.get_value(sle.voucher_type, sle.voucher_no, 'return_against')
if return_against and return_against != sr.delivery_document_no: if return_against and return_against != sr.delivery_document_no:
@ -299,7 +299,7 @@ def validate_so_serial_no(sr, sales_order,):
be delivered""").format(sales_order, sr.item_code, sr.name)) be delivered""").format(sales_order, sr.item_code, sr.name))
def has_duplicate_serial_no(sn, sle): def has_duplicate_serial_no(sn, sle):
if sn.warehouse: if sn.warehouse and sle.voucher_type != 'Stock Reconciliation':
return True return True
if sn.company != sle.company: if sn.company != sle.company:
@ -418,12 +418,14 @@ def update_serial_nos_after_submit(controller, parentfield):
update_rejected_serial_nos = True if (controller.doctype in ("Purchase Receipt", "Purchase Invoice") update_rejected_serial_nos = True if (controller.doctype in ("Purchase Receipt", "Purchase Invoice")
and d.rejected_qty) else False and d.rejected_qty) else False
accepted_serial_nos_updated = False accepted_serial_nos_updated = False
if controller.doctype == "Stock Entry": if controller.doctype == "Stock Entry":
warehouse = d.t_warehouse warehouse = d.t_warehouse
qty = d.transfer_qty qty = d.transfer_qty
else: else:
warehouse = d.warehouse warehouse = d.warehouse
qty = d.stock_qty qty = (d.qty if controller.doctype == "Stock Reconciliation"
else d.stock_qty)
for sle in stock_ledger_entries: for sle in stock_ledger_entries:
if sle.voucher_detail_no==d.name: if sle.voucher_detail_no==d.name:

View File

@ -38,7 +38,7 @@ class StockLedgerEntry(Document):
self.check_stock_frozen_date() self.check_stock_frozen_date()
self.actual_amt_check() self.actual_amt_check()
if not self.get("via_landed_cost_voucher") and self.voucher_type != 'Stock Reconciliation': if not self.get("via_landed_cost_voucher"):
from erpnext.stock.doctype.serial_no.serial_no import process_serial_no from erpnext.stock.doctype.serial_no.serial_no import process_serial_no
process_serial_no(self) process_serial_no(self)

View File

@ -12,8 +12,7 @@ frappe.ui.form.on("Stock Reconciliation", {
return { return {
query: "erpnext.controllers.queries.item_query", query: "erpnext.controllers.queries.item_query",
filters:{ filters:{
"is_stock_item": 1, "is_stock_item": 1
"has_serial_no": 0
} }
} }
}); });
@ -77,6 +76,7 @@ frappe.ui.form.on("Stock Reconciliation", {
set_valuation_rate_and_qty: function(frm, cdt, cdn) { set_valuation_rate_and_qty: function(frm, cdt, cdn) {
var d = frappe.model.get_doc(cdt, cdn); var d = frappe.model.get_doc(cdt, cdn);
if(d.item_code && d.warehouse) { if(d.item_code && d.warehouse) {
frappe.call({ frappe.call({
method: "erpnext.stock.doctype.stock_reconciliation.stock_reconciliation.get_stock_balance_for", method: "erpnext.stock.doctype.stock_reconciliation.stock_reconciliation.get_stock_balance_for",
@ -84,7 +84,8 @@ frappe.ui.form.on("Stock Reconciliation", {
item_code: d.item_code, item_code: d.item_code,
warehouse: d.warehouse, warehouse: d.warehouse,
posting_date: frm.doc.posting_date, posting_date: frm.doc.posting_date,
posting_time: frm.doc.posting_time posting_time: frm.doc.posting_time,
batch_no: d.batch_no
}, },
callback: function(r) { callback: function(r) {
frappe.model.set_value(cdt, cdn, "qty", r.message.qty); frappe.model.set_value(cdt, cdn, "qty", r.message.qty);
@ -93,7 +94,7 @@ frappe.ui.form.on("Stock Reconciliation", {
frappe.model.set_value(cdt, cdn, "current_valuation_rate", r.message.rate); frappe.model.set_value(cdt, cdn, "current_valuation_rate", r.message.rate);
frappe.model.set_value(cdt, cdn, "current_amount", r.message.rate * r.message.qty); frappe.model.set_value(cdt, cdn, "current_amount", r.message.rate * r.message.qty);
frappe.model.set_value(cdt, cdn, "amount", r.message.rate * r.message.qty); frappe.model.set_value(cdt, cdn, "amount", r.message.rate * r.message.qty);
frappe.model.set_value(cdt, cdn, "current_serial_no", r.message.serial_nos);
} }
}); });
} }
@ -152,17 +153,44 @@ frappe.ui.form.on("Stock Reconciliation Item", {
barcode: function(frm, cdt, cdn) { barcode: function(frm, cdt, cdn) {
frm.events.set_item_code(frm, cdt, cdn); frm.events.set_item_code(frm, cdt, cdn);
}, },
warehouse: function(frm, cdt, cdn) { warehouse: function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
if (child.batch_no) {
frappe.model.set_value(child.cdt, child.cdn, "batch_no", "");
}
frm.events.set_valuation_rate_and_qty(frm, cdt, cdn); frm.events.set_valuation_rate_and_qty(frm, cdt, cdn);
}, },
item_code: function(frm, cdt, cdn) { item_code: function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
if (child.batch_no) {
frappe.model.set_value(cdt, cdn, "batch_no", "");
}
frm.events.set_valuation_rate_and_qty(frm, cdt, cdn); frm.events.set_valuation_rate_and_qty(frm, cdt, cdn);
}, },
batch_no: function(frm, cdt, cdn) {
frm.events.set_valuation_rate_and_qty(frm, cdt, cdn);
},
qty: function(frm, cdt, cdn) { qty: function(frm, cdt, cdn) {
frm.events.set_amount_quantity(frm, cdt, cdn); frm.events.set_amount_quantity(frm, cdt, cdn);
}, },
valuation_rate: function(frm, cdt, cdn) { valuation_rate: function(frm, cdt, cdn) {
frm.events.set_amount_quantity(frm, cdt, cdn); frm.events.set_amount_quantity(frm, cdt, cdn);
},
serial_no: function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
if (child.serial_no) {
const serial_nos = child.serial_no.trim().split('\n');
frappe.model.set_value(cdt, cdn, "qty", serial_nos.length);
}
} }
}); });

View File

@ -9,7 +9,9 @@ from frappe.utils import cstr, flt, cint
from erpnext.stock.stock_ledger import update_entries_after from erpnext.stock.stock_ledger import update_entries_after
from erpnext.controllers.stock_controller import StockController from erpnext.controllers.stock_controller import StockController
from erpnext.accounts.utils import get_company_default from erpnext.accounts.utils import get_company_default
from erpnext.stock.utils import get_stock_balance from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.utils import get_stock_balance, get_incoming_rate, get_available_serial_nos
from erpnext.stock.doctype.batch.batch import get_batch_qty
class OpeningEntryAccountError(frappe.ValidationError): pass class OpeningEntryAccountError(frappe.ValidationError): pass
class EmptyStockReconciliationItemsError(frappe.ValidationError): pass class EmptyStockReconciliationItemsError(frappe.ValidationError): pass
@ -30,10 +32,16 @@ class StockReconciliation(StockController):
self.validate_expense_account() self.validate_expense_account()
self.set_total_qty_and_amount() self.set_total_qty_and_amount()
if self._action=="submit":
self.make_batches('warehouse')
def on_submit(self): def on_submit(self):
self.update_stock_ledger() self.update_stock_ledger()
self.make_gl_entries() self.make_gl_entries()
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
update_serial_nos_after_submit(self, "items")
def on_cancel(self): def on_cancel(self):
self.delete_and_repost_sle() self.delete_and_repost_sle()
self.make_gl_entries_on_cancel() self.make_gl_entries_on_cancel()
@ -42,23 +50,29 @@ class StockReconciliation(StockController):
"""Remove items if qty or rate is not changed""" """Remove items if qty or rate is not changed"""
self.difference_amount = 0.0 self.difference_amount = 0.0
def _changed(item): def _changed(item):
qty, rate = get_stock_balance(item.item_code, item.warehouse, item_dict = get_stock_balance_for(item.item_code, item.warehouse,
self.posting_date, self.posting_time, with_valuation_rate=True) self.posting_date, self.posting_time, batch_no=item.batch_no)
if (item.qty==None or item.qty==qty) and (item.valuation_rate==None or item.valuation_rate==rate):
if (((item.qty is None or item.qty==item_dict.get("qty")) and
(item.valuation_rate is None or item.valuation_rate==item_dict.get("rate")) and not item.serial_no)
or (item.serial_no and item.serial_no == item_dict.get("serial_nos"))):
return False return False
else: else:
# set default as current rates # set default as current rates
if item.qty==None: if item.qty is None:
item.qty = qty item.qty = item_dict.get("qty")
if item.valuation_rate==None: if item.valuation_rate is None:
item.valuation_rate = rate item.valuation_rate = item_dict.get("rate")
item.current_qty = qty if item_dict.get("serial_nos"):
item.current_valuation_rate = rate item.current_serial_no = item_dict.get("serial_nos")
item.current_qty = item_dict.get("qty")
item.current_valuation_rate = item_dict.get("rate")
self.difference_amount += (flt(item.qty, item.precision("qty")) * \ self.difference_amount += (flt(item.qty, item.precision("qty")) * \
flt(item.valuation_rate or rate, item.precision("valuation_rate")) \ flt(item.valuation_rate or item_dict.get("rate"), item.precision("valuation_rate")) \
- flt(qty, item.precision("qty")) * flt(rate, item.precision("valuation_rate"))) - flt(item_dict.get("qty"), item.precision("qty")) * flt(item_dict.get("rate"), item.precision("valuation_rate")))
return True return True
items = list(filter(lambda d: _changed(d), self.items)) items = list(filter(lambda d: _changed(d), self.items))
@ -84,12 +98,17 @@ class StockReconciliation(StockController):
for row_num, row in enumerate(self.items): for row_num, row in enumerate(self.items):
# find duplicates # find duplicates
if [row.item_code, row.warehouse] in item_warehouse_combinations: key = [row.item_code, row.warehouse]
for field in ['serial_no', 'batch_no']:
if row.get(field):
key.append(row.get(field))
if key in item_warehouse_combinations:
self.validation_messages.append(_get_msg(row_num, _("Duplicate entry"))) self.validation_messages.append(_get_msg(row_num, _("Duplicate entry")))
else: else:
item_warehouse_combinations.append([row.item_code, row.warehouse]) item_warehouse_combinations.append(key)
self.validate_item(row.item_code, row_num+1) self.validate_item(row.item_code, row)
# validate warehouse # validate warehouse
if not frappe.db.get_value("Warehouse", row.warehouse): if not frappe.db.get_value("Warehouse", row.warehouse):
@ -131,7 +150,7 @@ class StockReconciliation(StockController):
raise frappe.ValidationError(self.validation_messages) raise frappe.ValidationError(self.validation_messages)
def validate_item(self, item_code, row_num): def validate_item(self, item_code, row):
from erpnext.stock.doctype.item.item import validate_end_of_life, \ from erpnext.stock.doctype.item.item import validate_end_of_life, \
validate_is_stock_item, validate_cancelled_item validate_is_stock_item, validate_cancelled_item
@ -145,51 +164,139 @@ class StockReconciliation(StockController):
validate_is_stock_item(item_code, item.is_stock_item, verbose=0) validate_is_stock_item(item_code, item.is_stock_item, verbose=0)
# item should not be serialized # item should not be serialized
if item.has_serial_no == 1: if item.has_serial_no and not row.serial_no and not item.serial_no_series:
raise frappe.ValidationError(_("Serialized Item {0} cannot be updated using Stock Reconciliation, please use Stock Entry").format(item_code)) raise frappe.ValidationError(_("Serial no(s) required for serialized item {0}").format(item_code))
# item managed batch-wise not allowed # item managed batch-wise not allowed
if item.has_batch_no == 1: if item.has_batch_no and not row.batch_no and not item.create_new_batch:
raise frappe.ValidationError(_("Batched Item {0} cannot be updated using Stock Reconciliation, instead use Stock Entry").format(item_code)) raise frappe.ValidationError(_("Batch no is required for batched item {0}").format(item_code))
# docstatus should be < 2 # docstatus should be < 2
validate_cancelled_item(item_code, item.docstatus, verbose=0) validate_cancelled_item(item_code, item.docstatus, verbose=0)
except Exception as e: except Exception as e:
self.validation_messages.append(_("Row # ") + ("%d: " % (row_num)) + cstr(e)) self.validation_messages.append(_("Row # ") + ("%d: " % (row.idx)) + cstr(e))
def update_stock_ledger(self): def update_stock_ledger(self):
""" find difference between current and expected entries """ find difference between current and expected entries
and create stock ledger entries based on the difference""" and create stock ledger entries based on the difference"""
from erpnext.stock.stock_ledger import get_previous_sle from erpnext.stock.stock_ledger import get_previous_sle
sl_entries = []
for row in self.items: for row in self.items:
item = frappe.get_doc("Item", row.item_code)
if item.has_serial_no or item.has_batch_no:
self.get_sle_for_serialized_items(row, sl_entries)
else:
previous_sle = get_previous_sle({
"item_code": row.item_code,
"warehouse": row.warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time
})
if previous_sle:
if row.qty in ("", None):
row.qty = previous_sle.get("qty_after_transaction", 0)
if row.valuation_rate in ("", None):
row.valuation_rate = previous_sle.get("valuation_rate", 0)
if row.qty and not row.valuation_rate:
frappe.throw(_("Valuation Rate required for Item {0} at row {1}").format(row.item_code, row.idx))
if ((previous_sle and row.qty == previous_sle.get("qty_after_transaction")
and (row.valuation_rate == previous_sle.get("valuation_rate") or row.qty == 0))
or (not previous_sle and not row.qty)):
continue
sl_entries.append(self.get_sle_for_items(row))
if sl_entries:
self.make_sl_entries(sl_entries)
def get_sle_for_serialized_items(self, row, sl_entries):
from erpnext.stock.stock_ledger import get_previous_sle
serial_nos = get_serial_nos(row.serial_no)
# To issue existing serial nos
if row.current_qty and (row.current_serial_no or row.batch_no):
args = self.get_sle_for_items(row)
args.update({
'actual_qty': -1 * row.current_qty,
'serial_no': row.current_serial_no,
'batch_no': row.batch_no,
'valuation_rate': row.current_valuation_rate
})
if row.current_serial_no:
args.update({
'qty_after_transaction': 0,
})
sl_entries.append(args)
for serial_no in serial_nos:
args = self.get_sle_for_items(row, [serial_no])
previous_sle = get_previous_sle({ previous_sle = get_previous_sle({
"item_code": row.item_code, "item_code": row.item_code,
"warehouse": row.warehouse,
"posting_date": self.posting_date, "posting_date": self.posting_date,
"posting_time": self.posting_time "posting_time": self.posting_time,
"serial_no": serial_no
}) })
if previous_sle:
if row.qty in ("", None):
row.qty = previous_sle.get("qty_after_transaction", 0)
if row.valuation_rate in ("", None): if previous_sle and row.warehouse != previous_sle.get("warehouse"):
row.valuation_rate = previous_sle.get("valuation_rate", 0) # If serial no exists in different warehouse
if row.qty and not row.valuation_rate: new_args = args.copy()
frappe.throw(_("Valuation Rate required for Item in row {0}").format(row.idx)) new_args.update({
'actual_qty': -1,
'qty_after_transaction': cint(previous_sle.get('qty_after_transaction')) - 1,
'warehouse': previous_sle.get("warehouse", '') or row.warehouse,
'valuation_rate': previous_sle.get("valuation_rate")
})
if ((previous_sle and row.qty == previous_sle.get("qty_after_transaction") sl_entries.append(new_args)
and (row.valuation_rate == previous_sle.get("valuation_rate") or row.qty == 0))
or (not previous_sle and not row.qty)):
continue
self.insert_entries(row) if self.docstatus == 2:
args.update({
'actual_qty': 1,
'incoming_rate': row.valuation_rate,
'valuation_rate': row.valuation_rate
})
def insert_entries(self, row): sl_entries.append(args)
if self.docstatus == 1 and row.qty:
args = self.get_sle_for_items(row)
args.update({
'actual_qty': row.qty,
'incoming_rate': row.valuation_rate,
'valuation_rate': row.valuation_rate
})
sl_entries.append(args)
if serial_nos == get_serial_nos(row.current_serial_no):
# update valuation rate
self.update_valuation_rate_for_serial_nos(row, serial_nos)
def update_valuation_rate_for_serial_nos(self, row, serial_nos):
valuation_rate = row.valuation_rate if self.docstatus == 1 else row.current_valuation_rate
for d in serial_nos:
frappe.db.set_value("Serial No", d, 'purchase_rate', valuation_rate)
def get_sle_for_items(self, row, serial_nos=None):
"""Insert Stock Ledger Entries""" """Insert Stock Ledger Entries"""
args = frappe._dict({
if not serial_nos and row.serial_no:
serial_nos = get_serial_nos(row.serial_no)
data = frappe._dict({
"doctype": "Stock Ledger Entry", "doctype": "Stock Ledger Entry",
"item_code": row.item_code, "item_code": row.item_code,
"warehouse": row.warehouse, "warehouse": row.warehouse,
@ -197,13 +304,19 @@ class StockReconciliation(StockController):
"posting_time": self.posting_time, "posting_time": self.posting_time,
"voucher_type": self.doctype, "voucher_type": self.doctype,
"voucher_no": self.name, "voucher_no": self.name,
"voucher_detail_no": row.name,
"company": self.company, "company": self.company,
"stock_uom": frappe.db.get_value("Item", row.item_code, "stock_uom"), "stock_uom": frappe.db.get_value("Item", row.item_code, "stock_uom"),
"is_cancelled": "No", "is_cancelled": "No" if self.docstatus != 2 else "Yes",
"qty_after_transaction": flt(row.qty, row.precision("qty")), "serial_no": '\n'.join(serial_nos) if serial_nos else '',
"batch_no": row.batch_no,
"valuation_rate": flt(row.valuation_rate, row.precision("valuation_rate")) "valuation_rate": flt(row.valuation_rate, row.precision("valuation_rate"))
}) })
self.make_sl_entries([args])
if not row.batch_no:
data.qty_after_transaction = flt(row.qty, row.precision("qty"))
return data
def delete_and_repost_sle(self): def delete_and_repost_sle(self):
""" Delete Stock Ledger Entries related to this voucher """ Delete Stock Ledger Entries related to this voucher
@ -217,6 +330,15 @@ class StockReconciliation(StockController):
frappe.db.sql("""delete from `tabStock Ledger Entry` frappe.db.sql("""delete from `tabStock Ledger Entry`
where voucher_type=%s and voucher_no=%s""", (self.doctype, self.name)) where voucher_type=%s and voucher_no=%s""", (self.doctype, self.name))
sl_entries = []
for row in self.items:
if row.serial_no or row.batch_no or row.current_serial_no:
self.get_sle_for_serialized_items(row, sl_entries)
if sl_entries:
sl_entries.reverse()
self.make_sl_entries(sl_entries)
# repost future entries for selected item_code, warehouse # repost future entries for selected item_code, warehouse
for entries in existing_entries: for entries in existing_entries:
update_entries_after({ update_entries_after({
@ -310,17 +432,52 @@ def get_items(warehouse, posting_date, posting_time, company):
return res return res
@frappe.whitelist() @frappe.whitelist()
def get_stock_balance_for(item_code, warehouse, posting_date, posting_time): def get_stock_balance_for(item_code, warehouse,
posting_date, posting_time, batch_no=None, with_valuation_rate= True):
frappe.has_permission("Stock Reconciliation", "write", throw = True) frappe.has_permission("Stock Reconciliation", "write", throw = True)
qty, rate = get_stock_balance(item_code, warehouse, item_dict = frappe.db.get_value("Item", item_code,
posting_date, posting_time, with_valuation_rate=True) ["has_serial_no", "has_batch_no"], as_dict=1)
serial_nos = ""
if item_dict.get("has_serial_no"):
qty, rate, serial_nos = get_qty_rate_for_serial_nos(item_code,
warehouse, posting_date, posting_time, item_dict)
else:
qty, rate = get_stock_balance(item_code, warehouse,
posting_date, posting_time, with_valuation_rate=with_valuation_rate)
if item_dict.get("has_batch_no"):
qty = get_batch_qty(batch_no, warehouse) or 0
return { return {
'qty': qty, 'qty': qty,
'rate': rate 'rate': rate,
'serial_nos': serial_nos
} }
def get_qty_rate_for_serial_nos(item_code, warehouse, posting_date, posting_time, item_dict):
args = {
"item_code": item_code,
"warehouse": warehouse,
"posting_date": posting_date,
"posting_time": posting_time,
}
serial_nos_list = [serial_no.get("name")
for serial_no in get_available_serial_nos(item_code, warehouse)]
qty = len(serial_nos_list)
serial_nos = '\n'.join(serial_nos_list)
args.update({
'qty': qty,
"serial_nos": serial_nos
})
rate = get_incoming_rate(args, raise_error_if_no_rate=False) or 0
return qty, rate, serial_nos
@frappe.whitelist() @frappe.whitelist()
def get_difference_account(purpose, company): def get_difference_account(purpose, company):
if purpose == 'Stock Reconciliation': if purpose == 'Stock Reconciliation':

View File

@ -13,9 +13,12 @@ from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.utils import get_stock_balance, get_incoming_rate, get_available_serial_nos, get_stock_value_on
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
class TestStockReconciliation(unittest.TestCase): class TestStockReconciliation(unittest.TestCase):
def setUp(self): def setUp(self):
create_batch_or_serial_no_items()
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
self.insert_existing_sle() self.insert_existing_sle()
@ -106,6 +109,135 @@ class TestStockReconciliation(unittest.TestCase):
make_stock_entry(posting_date="2013-01-05", posting_time="07:00", item_code="_Test Item", make_stock_entry(posting_date="2013-01-05", posting_time="07:00", item_code="_Test Item",
target="_Test Warehouse - _TC", qty=15, basic_rate=1200) target="_Test Warehouse - _TC", qty=15, basic_rate=1200)
def test_stock_reco_for_serialized_item(self):
set_perpetual_inventory()
to_delete_records = []
to_delete_serial_nos = []
# Add new serial nos
serial_item_code = "Stock-Reco-Serial-Item-1"
serial_warehouse = "_Test Warehouse for Stock Reco1 - _TC"
sr = create_stock_reconciliation(item_code=serial_item_code,
warehouse = serial_warehouse, qty=5, rate=200)
# print(sr.name)
serial_nos = get_serial_nos(sr.items[0].serial_no)
self.assertEqual(len(serial_nos), 5)
args = {
"item_code": serial_item_code,
"warehouse": serial_warehouse,
"posting_date": nowdate(),
"posting_time": nowtime(),
"serial_no": sr.items[0].serial_no
}
valuation_rate = get_incoming_rate(args)
self.assertEqual(valuation_rate, 200)
to_delete_records.append(sr.name)
sr = create_stock_reconciliation(item_code=serial_item_code,
warehouse = serial_warehouse, qty=5, rate=300, serial_no = '\n'.join(serial_nos))
# print(sr.name)
serial_nos1 = get_serial_nos(sr.items[0].serial_no)
self.assertEqual(len(serial_nos1), 5)
args = {
"item_code": serial_item_code,
"warehouse": serial_warehouse,
"posting_date": nowdate(),
"posting_time": nowtime(),
"serial_no": sr.items[0].serial_no
}
valuation_rate = get_incoming_rate(args)
self.assertEqual(valuation_rate, 300)
to_delete_records.append(sr.name)
to_delete_records.reverse()
for d in to_delete_records:
stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel()
frappe.delete_doc("Stock Reconciliation", stock_doc.name)
for d in serial_nos + serial_nos1:
if frappe.db.exists("Serial No", d):
frappe.delete_doc("Serial No", d)
def test_stock_reco_for_batch_item(self):
set_perpetual_inventory()
to_delete_records = []
to_delete_serial_nos = []
# Add new serial nos
item_code = "Stock-Reco-batch-Item-1"
warehouse = "_Test Warehouse for Stock Reco2 - _TC"
sr = create_stock_reconciliation(item_code=item_code,
warehouse = warehouse, qty=5, rate=200, do_not_submit=1)
sr.save(ignore_permissions=True)
sr.submit()
self.assertTrue(sr.items[0].batch_no)
to_delete_records.append(sr.name)
sr1 = create_stock_reconciliation(item_code=item_code,
warehouse = warehouse, qty=6, rate=300, batch_no=sr.items[0].batch_no)
args = {
"item_code": item_code,
"warehouse": warehouse,
"posting_date": nowdate(),
"posting_time": nowtime(),
}
valuation_rate = get_incoming_rate(args)
self.assertEqual(valuation_rate, 300)
to_delete_records.append(sr1.name)
sr2 = create_stock_reconciliation(item_code=item_code,
warehouse = warehouse, qty=0, rate=0, batch_no=sr.items[0].batch_no)
stock_value = get_stock_value_on(warehouse, nowdate(), item_code)
self.assertEqual(stock_value, 0)
to_delete_records.append(sr2.name)
to_delete_records.reverse()
for d in to_delete_records:
stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel()
frappe.delete_doc("Batch", sr.items[0].batch_no)
for d in to_delete_records:
frappe.delete_doc("Stock Reconciliation", d)
def create_batch_or_serial_no_items():
create_warehouse("_Test Warehouse for Stock Reco1",
{"is_group": 0, "parent_warehouse": "_Test Warehouse Group - _TC"})
create_warehouse("_Test Warehouse for Stock Reco2",
{"is_group": 0, "parent_warehouse": "_Test Warehouse Group - _TC"})
serial_item_doc = create_item("Stock-Reco-Serial-Item-1", is_stock_item=1)
if not serial_item_doc.has_serial_no:
serial_item_doc.has_serial_no = 1
serial_item_doc.serial_no_series = "SRSI.####"
serial_item_doc.save(ignore_permissions=True)
batch_item_doc = create_item("Stock-Reco-batch-Item-1", is_stock_item=1)
if not batch_item_doc.has_batch_no:
batch_item_doc.has_batch_no = 1
batch_item_doc.create_new_batch = 1
serial_item_doc.batch_number_series = "BASR.#####"
batch_item_doc.save(ignore_permissions=True)
def create_stock_reconciliation(**args): def create_stock_reconciliation(**args):
args = frappe._dict(args) args = frappe._dict(args)
sr = frappe.new_doc("Stock Reconciliation") sr = frappe.new_doc("Stock Reconciliation")
@ -120,11 +252,14 @@ def create_stock_reconciliation(**args):
"item_code": args.item_code or "_Test Item", "item_code": args.item_code or "_Test Item",
"warehouse": args.warehouse or "_Test Warehouse - _TC", "warehouse": args.warehouse or "_Test Warehouse - _TC",
"qty": args.qty, "qty": args.qty,
"valuation_rate": args.rate "valuation_rate": args.rate,
"serial_no": args.serial_no,
"batch_no": args.batch_no
}) })
try: try:
sr.submit() if not args.do_not_submit:
sr.submit()
except EmptyStockReconciliationItemsError: except EmptyStockReconciliationItemsError:
pass pass
return sr return sr
@ -140,3 +275,4 @@ def set_valuation_method(item_code, valuation_method):
}, allow_negative_stock=1) }, allow_negative_stock=1)
test_dependencies = ["Item", "Warehouse"] test_dependencies = ["Item", "Warehouse"]

View File

@ -1,560 +1,182 @@
{ {
"allow_copy": 0, "creation": "2015-02-17 01:06:05.072764",
"allow_guest_to_view": 0, "doctype": "DocType",
"allow_import": 0, "document_type": "Other",
"allow_rename": 0, "editable_grid": 1,
"beta": 0, "engine": "InnoDB",
"creation": "2015-02-17 01:06:05.072764", "field_order": [
"custom": 0, "barcode",
"docstatus": 0, "item_code",
"doctype": "DocType", "item_name",
"document_type": "Other", "warehouse",
"editable_grid": 1, "column_break_6",
"engine": "InnoDB", "qty",
"valuation_rate",
"amount",
"serial_no_and_batch_section",
"serial_no",
"column_break_11",
"batch_no",
"section_break_3",
"current_qty",
"current_serial_no",
"column_break_9",
"current_valuation_rate",
"current_amount",
"section_break_14",
"quantity_difference",
"column_break_16",
"amount_difference"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "barcode",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "label": "Barcode",
"collapsible": 0, "print_hide": 1
"columns": 0, },
"fieldname": "barcode",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Barcode",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 3,
"allow_on_submit": 0, "fieldname": "item_code",
"bold": 0, "fieldtype": "Link",
"collapsible": 0, "in_global_search": 1,
"columns": 3, "in_list_view": 1,
"fieldname": "item_code", "label": "Item Code",
"fieldtype": "Link", "options": "Item",
"hidden": 0, "reqd": 1
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Item Code",
"length": 0,
"no_copy": 0,
"options": "Item",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "item_name",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "in_global_search": 1,
"collapsible": 0, "label": "Item Name",
"columns": 0, "no_copy": 1,
"fieldname": "item_name", "print_hide": 1,
"fieldtype": "Data", "read_only": 1
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Item Name",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 3,
"allow_on_submit": 0, "fieldname": "warehouse",
"bold": 0, "fieldtype": "Link",
"collapsible": 0, "in_list_view": 1,
"columns": 3, "label": "Warehouse",
"fieldname": "warehouse", "options": "Warehouse",
"fieldtype": "Link", "reqd": 1
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Warehouse",
"length": 0,
"no_copy": 0,
"options": "Warehouse",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_6",
"allow_on_submit": 0, "fieldtype": "Column Break"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_6",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_on_submit": 0, "fieldname": "qty",
"bold": 0, "fieldtype": "Float",
"collapsible": 0, "in_list_view": 1,
"columns": 2, "label": "Quantity"
"description": "", },
"fieldname": "qty",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Quantity",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_on_submit": 0, "fieldname": "valuation_rate",
"bold": 0, "fieldtype": "Currency",
"collapsible": 0, "in_list_view": 1,
"columns": 2, "label": "Valuation Rate",
"description": "", "options": "Company:company:default_currency"
"fieldname": "valuation_rate", },
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Valuation Rate",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "amount",
"allow_on_submit": 0, "fieldtype": "Currency",
"bold": 0, "label": "Amount",
"collapsible": 0, "options": "Company:company:default_currency",
"columns": 0, "read_only": 1
"fieldname": "amount", },
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amount",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "serial_no_and_batch_section",
"allow_on_submit": 0, "fieldtype": "Section Break",
"bold": 0, "label": "Serial No and Batch"
"collapsible": 0, },
"columns": 0,
"fieldname": "section_break_3",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Before reconciliation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "serial_no",
"allow_on_submit": 0, "fieldtype": "Small Text",
"bold": 0, "label": "Serial No"
"collapsible": 0, },
"columns": 0,
"description": "",
"fieldname": "current_qty",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Current Qty",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_11",
"allow_on_submit": 0, "fieldtype": "Column Break"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "section_break_3",
"allow_on_submit": 0, "fieldtype": "Section Break",
"bold": 0, "label": "Before reconciliation"
"collapsible": 0, },
"columns": 0,
"description": "",
"fieldname": "current_valuation_rate",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Current Valuation Rate",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_on_submit": 0, "fieldname": "current_qty",
"bold": 0, "fieldtype": "Float",
"collapsible": 0, "label": "Current Qty",
"columns": 0, "read_only": 1
"description": "", },
"fieldname": "current_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Current Amount",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "current_serial_no",
"allow_on_submit": 0, "fieldtype": "Small Text",
"bold": 0, "label": "Current Serial No",
"collapsible": 0, "no_copy": 1,
"columns": 0, "print_hide": 1,
"fieldname": "section_break_14", "read_only": 1
"fieldtype": "Section Break", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_9",
"allow_on_submit": 0, "fieldtype": "Column Break"
"bold": 0, },
"collapsible": 0,
"columns": 0,
"fieldname": "quantity_difference",
"fieldtype": "Read Only",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Quantity Difference",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "current_valuation_rate",
"allow_on_submit": 0, "fieldtype": "Currency",
"bold": 0, "label": "Current Valuation Rate",
"collapsible": 0, "options": "Company:company:default_currency",
"columns": 0, "read_only": 1
"fieldname": "column_break_16", },
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "current_amount",
"allow_on_submit": 0, "fieldtype": "Currency",
"bold": 0, "label": "Current Amount",
"collapsible": 0, "options": "Company:company:default_currency",
"columns": 0, "read_only": 1
"fieldname": "amount_difference", },
"fieldtype": "Currency", {
"hidden": 0, "fieldname": "section_break_14",
"ignore_user_permissions": 0, "fieldtype": "Section Break"
"ignore_xss_filter": 0, },
"in_filter": 0, {
"in_global_search": 0, "fieldname": "quantity_difference",
"in_list_view": 0, "fieldtype": "Read Only",
"in_standard_filter": 0, "label": "Quantity Difference"
"label": "Amount Difference", },
"length": 0, {
"no_copy": 0, "fieldname": "column_break_16",
"options": "Company:company:default_currency", "fieldtype": "Column Break"
"permlevel": 0, },
"precision": "", {
"print_hide": 0, "fieldname": "amount_difference",
"print_hide_if_no_value": 0, "fieldtype": "Currency",
"read_only": 1, "label": "Amount Difference",
"remember_last_selected_value": 0, "options": "Company:company:default_currency",
"report_hide": 0, "read_only": 1
"reqd": 0, },
"search_index": 0, {
"set_only_once": 0, "fieldname": "batch_no",
"unique": 0 "fieldtype": "Link",
"label": "Batch No",
"options": "Batch"
} }
], ],
"has_web_view": 0, "istable": 1,
"hide_heading": 0, "modified": "2019-06-14 17:10:53.188305",
"hide_toolbar": 0, "modified_by": "Administrator",
"idx": 0, "module": "Stock",
"image_view": 0, "name": "Stock Reconciliation Item",
"in_create": 0, "owner": "Administrator",
"is_submittable": 0, "permissions": [],
"issingle": 0, "quick_entry": 1,
"istable": 1, "sort_field": "modified",
"max_attachments": 0, "sort_order": "DESC",
"menu_index": 0, "track_changes": 1
"modified": "2017-08-03 00:03:40.412071",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation Item",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
} }

View File

@ -157,9 +157,12 @@ class update_entries_after(object):
if sle.serial_no: if sle.serial_no:
self.get_serialized_values(sle) self.get_serialized_values(sle)
self.qty_after_transaction += flt(sle.actual_qty) self.qty_after_transaction += flt(sle.actual_qty)
if sle.voucher_type == "Stock Reconciliation":
self.qty_after_transaction = sle.qty_after_transaction
self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate) self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate)
else: else:
if sle.voucher_type=="Stock Reconciliation": if sle.voucher_type=="Stock Reconciliation" and not sle.batch_no:
# assert # assert
self.valuation_rate = sle.valuation_rate self.valuation_rate = sle.valuation_rate
self.qty_after_transaction = sle.qty_after_transaction self.qty_after_transaction = sle.qty_after_transaction
@ -371,7 +374,7 @@ class update_entries_after(object):
"""get Stock Ledger Entries after a particular datetime, for reposting""" """get Stock Ledger Entries after a particular datetime, for reposting"""
return get_stock_ledger_entries(self.previous_sle or frappe._dict({ return get_stock_ledger_entries(self.previous_sle or frappe._dict({
"item_code": self.args.get("item_code"), "warehouse": self.args.get("warehouse") }), "item_code": self.args.get("item_code"), "warehouse": self.args.get("warehouse") }),
">", "asc", for_update=True) ">", "asc", for_update=True, check_serial_no=False)
def raise_exceptions(self): def raise_exceptions(self):
deficiency = min(e["diff"] for e in self.exceptions) deficiency = min(e["diff"] for e in self.exceptions)
@ -412,7 +415,8 @@ def get_previous_sle(args, for_update=False):
sle = get_stock_ledger_entries(args, "<=", "desc", "limit 1", for_update=for_update) sle = get_stock_ledger_entries(args, "<=", "desc", "limit 1", for_update=for_update)
return sle and sle[0] or {} return sle and sle[0] or {}
def get_stock_ledger_entries(previous_sle, operator=None, order="desc", limit=None, for_update=False, debug=False): def get_stock_ledger_entries(previous_sle, operator=None,
order="desc", limit=None, for_update=False, debug=False, check_serial_no=True):
"""get stock ledger entries filtered by specific posting datetime conditions""" """get stock ledger entries filtered by specific posting datetime conditions"""
conditions = " and timestamp(posting_date, posting_time) {0} timestamp(%(posting_date)s, %(posting_time)s)".format(operator) conditions = " and timestamp(posting_date, posting_time) {0} timestamp(%(posting_date)s, %(posting_time)s)".format(operator)
if previous_sle.get("warehouse"): if previous_sle.get("warehouse"):
@ -420,6 +424,9 @@ def get_stock_ledger_entries(previous_sle, operator=None, order="desc", limit=No
elif previous_sle.get("warehouse_condition"): elif previous_sle.get("warehouse_condition"):
conditions += " and " + previous_sle.get("warehouse_condition") conditions += " and " + previous_sle.get("warehouse_condition")
if check_serial_no and previous_sle.get("serial_no"):
conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no"))))
if not previous_sle.get("posting_date"): if not previous_sle.get("posting_date"):
previous_sle["posting_date"] = "1900-01-01" previous_sle["posting_date"] = "1900-01-01"
if not previous_sle.get("posting_time"): if not previous_sle.get("posting_time"):

View File

@ -173,7 +173,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
in_rate = get_valuation_rate(args.get('item_code'), args.get('warehouse'), in_rate = get_valuation_rate(args.get('item_code'), args.get('warehouse'),
args.get('voucher_type'), voucher_no, args.get('allow_zero_valuation'), args.get('voucher_type'), voucher_no, args.get('allow_zero_valuation'),
currency=erpnext.get_company_currency(args.get('company')), company=args.get('company'), currency=erpnext.get_company_currency(args.get('company')), company=args.get('company'),
raise_error_if_no_rate=True) raise_error_if_no_rate=raise_error_if_no_rate)
return in_rate return in_rate
@ -277,3 +277,7 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto
new_row.append(None) new_row.append(None)
result[row_idx] = new_row result[row_idx] = new_row
def get_available_serial_nos(item_code, warehouse):
return frappe.get_all("Serial No", filters = {'item_code': item_code,
'warehouse': warehouse, 'delivery_document_no': ''}) or []