Test cases for serial no
This commit is contained in:
parent
66aa37f1e2
commit
87c4b06437
@ -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:
|
||||||
@ -413,14 +413,17 @@ 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:
|
||||||
|
print(accepted_serial_nos_updated, qty, sle.actual_qty)
|
||||||
if sle.voucher_detail_no==d.name:
|
if sle.voucher_detail_no==d.name:
|
||||||
if not accepted_serial_nos_updated and qty and abs(sle.actual_qty)==qty \
|
if not accepted_serial_nos_updated and qty and abs(sle.actual_qty)==qty \
|
||||||
and sle.warehouse == warehouse and sle.serial_no != d.serial_no:
|
and sle.warehouse == warehouse and sle.serial_no != d.serial_no:
|
||||||
|
|||||||
@ -36,6 +36,9 @@ class StockReconciliation(StockController):
|
|||||||
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()
|
||||||
@ -48,7 +51,8 @@ class StockReconciliation(StockController):
|
|||||||
self.posting_date, self.posting_time, batch_no=item.batch_no)
|
self.posting_date, self.posting_time, batch_no=item.batch_no)
|
||||||
|
|
||||||
if ((item.qty==None or item.qty==item_dict.get("qty"))
|
if ((item.qty==None or item.qty==item_dict.get("qty"))
|
||||||
and (item.valuation_rate==None or item.valuation_rate==item_dict.get("rate"))):
|
and (item.valuation_rate==None or item.valuation_rate==item_dict.get("rate"))
|
||||||
|
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
|
||||||
@ -64,7 +68,7 @@ class StockReconciliation(StockController):
|
|||||||
item.current_qty = item_dict.get("qty")
|
item.current_qty = item_dict.get("qty")
|
||||||
item.current_valuation_rate = item_dict.get("rate")
|
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(item_dict.get("qty"), item.precision("qty")) * flt(item_dict.get("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
|
||||||
|
|
||||||
@ -157,7 +161,7 @@ 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 and not row.serial_no:
|
if item.has_serial_no and not row.serial_no and not item.serial_no_series:
|
||||||
raise frappe.ValidationError(_("Serial nos are required for serialized item {0}").format(item_code))
|
raise frappe.ValidationError(_("Serial nos are required for serialized item {0}").format(item_code))
|
||||||
|
|
||||||
# item managed batch-wise not allowed
|
# item managed batch-wise not allowed
|
||||||
@ -180,7 +184,8 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
sl_entries = []
|
sl_entries = []
|
||||||
for row in self.items:
|
for row in self.items:
|
||||||
if row.serial_no or row.batch_no:
|
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)
|
self.get_sle_for_serialized_items(row, sl_entries)
|
||||||
else:
|
else:
|
||||||
previous_sle = get_previous_sle({
|
previous_sle = get_previous_sle({
|
||||||
@ -213,6 +218,9 @@ class StockReconciliation(StockController):
|
|||||||
def get_sle_for_serialized_items(self, row, sl_entries):
|
def get_sle_for_serialized_items(self, row, sl_entries):
|
||||||
from erpnext.stock.stock_ledger import get_previous_sle
|
from erpnext.stock.stock_ledger import get_previous_sle
|
||||||
|
|
||||||
|
serial_nos = get_serial_nos(row.serial_no)
|
||||||
|
|
||||||
|
|
||||||
# To issue existing serial nos
|
# To issue existing serial nos
|
||||||
if row.current_qty and (row.current_serial_no or row.batch_no):
|
if row.current_qty and (row.current_serial_no or row.batch_no):
|
||||||
args = self.get_sle_for_items(row)
|
args = self.get_sle_for_items(row)
|
||||||
@ -230,7 +238,7 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
sl_entries.append(args)
|
sl_entries.append(args)
|
||||||
|
|
||||||
for serial_no in get_serial_nos(row.serial_no):
|
for serial_no in serial_nos:
|
||||||
args = self.get_sle_for_items(row, [serial_no])
|
args = self.get_sle_for_items(row, [serial_no])
|
||||||
|
|
||||||
previous_sle = get_previous_sle({
|
previous_sle = get_previous_sle({
|
||||||
@ -262,7 +270,7 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
sl_entries.append(args)
|
sl_entries.append(args)
|
||||||
|
|
||||||
if self.docstatus == 1:
|
if self.docstatus == 1 and not row.remove_serial_no_from_stock:
|
||||||
args = self.get_sle_for_items(row)
|
args = self.get_sle_for_items(row)
|
||||||
|
|
||||||
args.update({
|
args.update({
|
||||||
@ -273,6 +281,15 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
sl_entries.append(args)
|
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):
|
def get_sle_for_items(self, row, serial_nos=None):
|
||||||
"""Insert Stock Ledger Entries"""
|
"""Insert Stock Ledger Entries"""
|
||||||
|
|
||||||
@ -287,6 +304,7 @@ 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" if self.docstatus != 2 else "Yes",
|
"is_cancelled": "No" if self.docstatus != 2 else "Yes",
|
||||||
@ -432,7 +450,6 @@ def get_stock_balance_for(item_code, warehouse,
|
|||||||
if item_dict.get("has_batch_no"):
|
if item_dict.get("has_batch_no"):
|
||||||
qty = get_batch_qty(batch_no, warehouse) or 0
|
qty = get_batch_qty(batch_no, warehouse) or 0
|
||||||
|
|
||||||
print(qty, rate, batch_no, warehouse)
|
|
||||||
return {
|
return {
|
||||||
'qty': qty,
|
'qty': qty,
|
||||||
'rate': rate,
|
'rate': rate,
|
||||||
@ -457,7 +474,7 @@ def get_qty_rate_for_serial_nos(item_code, warehouse, posting_date, posting_time
|
|||||||
"serial_nos": serial_nos
|
"serial_nos": serial_nos
|
||||||
})
|
})
|
||||||
|
|
||||||
rate = get_incoming_rate(args)
|
rate = get_incoming_rate(args, raise_error_if_no_rate=False) or 0
|
||||||
|
|
||||||
return qty, rate, serial_nos
|
return qty, rate, serial_nos
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
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,83 @@ 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 create_batch_or_serial_no_items():
|
||||||
|
create_warehouse("_Test Warehouse for Stock Reco1",
|
||||||
|
{"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,7 +200,10 @@ 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,
|
||||||
|
"remove_serial_no_from_stock": args.remove_serial_no_from_stock or 0
|
||||||
})
|
})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -140,3 +223,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"]
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
"amount",
|
"amount",
|
||||||
"serial_no_and_batch_section",
|
"serial_no_and_batch_section",
|
||||||
"serial_no",
|
"serial_no",
|
||||||
|
"remove_serial_no_from_stock",
|
||||||
"column_break_11",
|
"column_break_11",
|
||||||
"batch_no",
|
"batch_no",
|
||||||
"section_break_3",
|
"section_break_3",
|
||||||
@ -165,10 +166,16 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Batch No",
|
"label": "Batch No",
|
||||||
"options": "Batch"
|
"options": "Batch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "remove_serial_no_from_stock",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Remove Serial No from Stock"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-05-24 12:34:50.018491",
|
"modified": "2019-06-01 03:16:38.459307",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Reconciliation Item",
|
"name": "Stock Reconciliation Item",
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user