fix: travis for work order, pos invoice and landed cost voucher
This commit is contained in:
parent
48fbf99e6d
commit
f8bf4aa7c8
@ -5,12 +5,18 @@ import copy
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
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.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
get_batch_from_bundle,
|
||||
get_serial_nos_from_bundle,
|
||||
make_serial_batch_bundle,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
|
||||
@ -249,7 +255,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
)
|
||||
|
||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)
|
||||
|
||||
pos = create_pos_invoice(
|
||||
company="_Test Company",
|
||||
@ -260,11 +266,11 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
serial_no=[serial_nos[0]],
|
||||
rate=1000,
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos.get("items")[0].serial_no = serial_nos[0]
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
|
||||
)
|
||||
@ -276,7 +282,9 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
|
||||
pos_return.insert()
|
||||
pos_return.submit()
|
||||
self.assertEqual(pos_return.get("items")[0].serial_no, serial_nos[0])
|
||||
self.assertEqual(
|
||||
get_serial_nos_from_bundle(pos_return.get("items")[0].serial_and_batch_bundle)[0], serial_nos[0]
|
||||
)
|
||||
|
||||
def test_partial_pos_returns(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
@ -289,7 +297,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
)
|
||||
|
||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)
|
||||
|
||||
pos = create_pos_invoice(
|
||||
company="_Test Company",
|
||||
@ -300,12 +308,12 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
serial_no=serial_nos,
|
||||
qty=2,
|
||||
rate=1000,
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos.get("items")[0].serial_no = serial_nos[0] + "\n" + serial_nos[1]
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
|
||||
)
|
||||
@ -317,14 +325,27 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
|
||||
# partial return 1
|
||||
pos_return1.get("items")[0].qty = -1
|
||||
pos_return1.get("items")[0].serial_no = serial_nos[0]
|
||||
|
||||
bundle_id = frappe.get_doc(
|
||||
"Serial and Batch Bundle", pos_return1.get("items")[0].serial_and_batch_bundle
|
||||
)
|
||||
|
||||
bundle_id.remove(bundle_id.entries[1])
|
||||
bundle_id.save()
|
||||
|
||||
bundle_id.load_from_db()
|
||||
|
||||
serial_no = bundle_id.entries[0].serial_no
|
||||
self.assertEqual(serial_no, serial_nos[0])
|
||||
|
||||
pos_return1.insert()
|
||||
pos_return1.submit()
|
||||
|
||||
# partial return 2
|
||||
pos_return2 = make_sales_return(pos.name)
|
||||
self.assertEqual(pos_return2.get("items")[0].qty, -1)
|
||||
self.assertEqual(pos_return2.get("items")[0].serial_no, serial_nos[1])
|
||||
serial_no = get_serial_nos_from_bundle(pos_return2.get("items")[0].serial_and_batch_bundle)[0]
|
||||
self.assertEqual(serial_no, serial_nos[1])
|
||||
|
||||
def test_pos_change_amount(self):
|
||||
pos = create_pos_invoice(
|
||||
@ -368,7 +389,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
)
|
||||
|
||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)
|
||||
|
||||
pos = create_pos_invoice(
|
||||
company="_Test Company",
|
||||
@ -380,10 +401,10 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
serial_no=[serial_nos[0]],
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos.get("items")[0].serial_no = serial_nos[0]
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 1000}
|
||||
)
|
||||
@ -401,10 +422,10 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
serial_no=[serial_nos[0]],
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos2.get("items")[0].serial_no = serial_nos[0]
|
||||
pos2.append(
|
||||
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 1000}
|
||||
)
|
||||
@ -423,7 +444,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
)
|
||||
|
||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)
|
||||
|
||||
si = create_sales_invoice(
|
||||
company="_Test Company",
|
||||
@ -435,11 +456,11 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
update_stock=1,
|
||||
serial_no=[serial_nos[0]],
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
si.get("items")[0].serial_no = serial_nos[0]
|
||||
si.update_stock = 1
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
@ -453,10 +474,10 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
serial_no=[serial_nos[0]],
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos2.get("items")[0].serial_no = serial_nos[0]
|
||||
pos2.append(
|
||||
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 1000}
|
||||
)
|
||||
@ -473,7 +494,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
)
|
||||
serial_nos = se.get("items")[0].serial_no + "wrong"
|
||||
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0] + "wrong"
|
||||
|
||||
pos = create_pos_invoice(
|
||||
company="_Test Company",
|
||||
@ -486,14 +507,13 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
qty=2,
|
||||
serial_nos=[serial_nos],
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos.get("items")[0].has_serial_no = 1
|
||||
pos.get("items")[0].serial_no = serial_nos
|
||||
pos.insert()
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos.submit)
|
||||
self.assertRaises(frappe.ValidationError, pos.insert)
|
||||
|
||||
def test_value_error_on_serial_no_validation(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
@ -504,7 +524,7 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
)
|
||||
serial_nos = se.get("items")[0].serial_no
|
||||
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)
|
||||
|
||||
# make a pos invoice
|
||||
pos = create_pos_invoice(
|
||||
@ -517,11 +537,11 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
serial_no=[serial_nos[0]],
|
||||
qty=1,
|
||||
do_not_save=1,
|
||||
)
|
||||
pos.get("items")[0].has_serial_no = 1
|
||||
pos.get("items")[0].serial_no = serial_nos.split("\n")[0]
|
||||
pos.set("payments", [])
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
|
||||
@ -547,12 +567,12 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
serial_no=[serial_nos[0]],
|
||||
qty=1,
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos2.get("items")[0].has_serial_no = 1
|
||||
pos2.get("items")[0].serial_no = serial_nos.split("\n")[0]
|
||||
# Value error should not be triggered on validation
|
||||
pos2.save()
|
||||
|
||||
@ -748,16 +768,16 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
self.assertEqual(rounded_total, 400)
|
||||
|
||||
def test_pos_batch_item_qty_validation(self):
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||
BatchNegativeStockError,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
|
||||
create_batch_item_with_batch,
|
||||
)
|
||||
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||
|
||||
create_batch_item_with_batch("_BATCH ITEM", "TestBatch 01")
|
||||
item = frappe.get_doc("Item", "_BATCH ITEM")
|
||||
batch = frappe.get_doc("Batch", "TestBatch 01")
|
||||
batch.submit()
|
||||
item.batch_no = "TestBatch 01"
|
||||
item.save()
|
||||
|
||||
se = make_stock_entry(
|
||||
target="_Test Warehouse - _TC",
|
||||
@ -767,16 +787,28 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
batch_no="TestBatch 01",
|
||||
)
|
||||
|
||||
pos_inv1 = create_pos_invoice(item=item.name, rate=300, qty=1, do_not_submit=1)
|
||||
pos_inv1.items[0].batch_no = "TestBatch 01"
|
||||
pos_inv1 = create_pos_invoice(
|
||||
item=item.name, rate=300, qty=1, do_not_submit=1, batch_no="TestBatch 01"
|
||||
)
|
||||
pos_inv1.save()
|
||||
pos_inv1.submit()
|
||||
|
||||
pos_inv2 = create_pos_invoice(item=item.name, rate=300, qty=2, do_not_submit=1)
|
||||
pos_inv2.items[0].batch_no = "TestBatch 01"
|
||||
pos_inv2.save()
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos_inv2.submit)
|
||||
sn_doc = SerialBatchCreation(
|
||||
{
|
||||
"item_code": item.name,
|
||||
"warehouse": pos_inv2.items[0].warehouse,
|
||||
"voucher_type": "Delivery Note",
|
||||
"qty": 2,
|
||||
"avg_rate": 300,
|
||||
"batches": frappe._dict({"TestBatch 01": 2}),
|
||||
"type_of_transaction": "Outward",
|
||||
"company": pos_inv2.company,
|
||||
}
|
||||
)
|
||||
|
||||
self.assertRaises(BatchNegativeStockError, sn_doc.make_serial_and_batch_bundle)
|
||||
|
||||
# teardown
|
||||
pos_inv1.reload()
|
||||
@ -785,9 +817,6 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
pos_inv2.reload()
|
||||
pos_inv2.delete()
|
||||
se.cancel()
|
||||
batch.reload()
|
||||
batch.cancel()
|
||||
batch.delete()
|
||||
|
||||
def test_ignore_pricing_rule(self):
|
||||
from erpnext.accounts.doctype.pricing_rule.test_pricing_rule import make_pricing_rule
|
||||
@ -838,18 +867,18 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
frappe.db.savepoint("before_test_delivered_serial_no_case")
|
||||
try:
|
||||
se = make_serialized_item()
|
||||
serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
|
||||
serial_no = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0]
|
||||
|
||||
dn = create_delivery_note(item_code="_Test Serialized Item With Series", serial_no=serial_no)
|
||||
dn = create_delivery_note(item_code="_Test Serialized Item With Series", serial_no=[serial_no])
|
||||
delivered_serial_no = get_serial_nos_from_bundle(dn.get("items")[0].serial_and_batch_bundle)[0]
|
||||
|
||||
delivery_document_no = frappe.db.get_value("Serial No", serial_no, "delivery_document_no")
|
||||
self.assertEquals(delivery_document_no, dn.name)
|
||||
self.assertEqual(serial_no, delivered_serial_no)
|
||||
|
||||
init_user_and_profile()
|
||||
|
||||
pos_inv = create_pos_invoice(
|
||||
item_code="_Test Serialized Item With Series",
|
||||
serial_no=serial_no,
|
||||
serial_no=[serial_no],
|
||||
qty=1,
|
||||
rate=100,
|
||||
do_not_submit=True,
|
||||
@ -861,42 +890,6 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
frappe.db.rollback(save_point="before_test_delivered_serial_no_case")
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_returned_serial_no_case(self):
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.test_pos_invoice_merge_log import (
|
||||
init_user_and_profile,
|
||||
)
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos
|
||||
from erpnext.stock.doctype.serial_no.test_serial_no import get_serial_nos
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
|
||||
frappe.db.savepoint("before_test_returned_serial_no_case")
|
||||
try:
|
||||
se = make_serialized_item()
|
||||
serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
|
||||
|
||||
init_user_and_profile()
|
||||
|
||||
pos_inv = create_pos_invoice(
|
||||
item_code="_Test Serialized Item With Series",
|
||||
serial_no=serial_no,
|
||||
qty=1,
|
||||
rate=100,
|
||||
)
|
||||
|
||||
pos_return = make_sales_return(pos_inv.name)
|
||||
pos_return.flags.ignore_validate = True
|
||||
pos_return.insert()
|
||||
pos_return.submit()
|
||||
|
||||
pos_reserved_serial_nos = get_pos_reserved_serial_nos(
|
||||
{"item_code": "_Test Serialized Item With Series", "warehouse": "_Test Warehouse - _TC"}
|
||||
)
|
||||
self.assertTrue(serial_no not in pos_reserved_serial_nos)
|
||||
|
||||
finally:
|
||||
frappe.db.rollback(save_point="before_test_returned_serial_no_case")
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
|
||||
def create_pos_invoice(**args):
|
||||
args = frappe._dict(args)
|
||||
@ -926,6 +919,40 @@ def create_pos_invoice(**args):
|
||||
|
||||
pos_inv.set_missing_values()
|
||||
|
||||
bundle_id = None
|
||||
if args.get("batch_no") or args.get("serial_no"):
|
||||
type_of_transaction = args.type_of_transaction or "Outward"
|
||||
|
||||
if pos_inv.is_return:
|
||||
type_of_transaction = "Inward"
|
||||
|
||||
qty = args.get("qty") or 1
|
||||
qty *= -1 if type_of_transaction == "Outward" else 1
|
||||
batches = {}
|
||||
if args.get("batch_no"):
|
||||
batches = frappe._dict({args.batch_no: qty})
|
||||
|
||||
bundle_id = make_serial_batch_bundle(
|
||||
frappe._dict(
|
||||
{
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": qty,
|
||||
"batches": batches,
|
||||
"voucher_type": "Delivery Note",
|
||||
"serial_nos": args.serial_no,
|
||||
"posting_date": pos_inv.posting_date,
|
||||
"posting_time": pos_inv.posting_time,
|
||||
"type_of_transaction": type_of_transaction,
|
||||
"do_not_submit": True,
|
||||
}
|
||||
)
|
||||
).name
|
||||
|
||||
if not bundle_id:
|
||||
msg = f"Serial No {args.serial_no} not available for Item {args.item}"
|
||||
frappe.throw(_(msg))
|
||||
|
||||
pos_inv.append(
|
||||
"items",
|
||||
{
|
||||
@ -936,8 +963,7 @@ def create_pos_invoice(**args):
|
||||
"income_account": args.income_account or "Sales - _TC",
|
||||
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"serial_no": args.serial_no,
|
||||
"batch_no": args.batch_no,
|
||||
"serial_and_batch_bundle": bundle_id,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -3402,11 +3402,12 @@ def create_sales_invoice(**args):
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": qty,
|
||||
"batches": batches,
|
||||
"voucher_type": "Purchase Invoice",
|
||||
"voucher_type": "Sales Invoice",
|
||||
"serial_nos": serial_nos,
|
||||
"type_of_transaction": "Outward" if not args.is_return else "Inward",
|
||||
"posting_date": si.posting_date or today(),
|
||||
"posting_time": si.posting_time,
|
||||
"do_not_submit": True,
|
||||
}
|
||||
)
|
||||
).name
|
||||
|
@ -393,8 +393,15 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
|
||||
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||
|
||||
target_doc.qty = -1 * source_doc.qty
|
||||
item_details = frappe.get_cached_value(
|
||||
"Item", source_doc.item_code, ["has_batch_no", "has_serial_no"], as_dict=1
|
||||
)
|
||||
|
||||
returned_serial_nos = []
|
||||
if source_doc.get("serial_and_batch_bundle"):
|
||||
if item_details.has_serial_no:
|
||||
returned_serial_nos = get_returned_serial_nos(source_doc, source_parent)
|
||||
|
||||
type_of_transaction = "Inward"
|
||||
if (
|
||||
frappe.db.get_value(
|
||||
@ -410,6 +417,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
|
||||
"serial_and_batch_bundle": source_doc.serial_and_batch_bundle,
|
||||
"returned_against": source_doc.name,
|
||||
"item_code": source_doc.item_code,
|
||||
"returned_serial_nos": returned_serial_nos,
|
||||
}
|
||||
)
|
||||
|
||||
@ -418,6 +426,11 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
|
||||
target_doc.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle
|
||||
|
||||
if source_doc.get("rejected_serial_and_batch_bundle"):
|
||||
if item_details.has_serial_no:
|
||||
returned_serial_nos = get_returned_serial_nos(
|
||||
source_doc, source_parent, serial_no_field="rejected_serial_and_batch_bundle"
|
||||
)
|
||||
|
||||
type_of_transaction = "Inward"
|
||||
if (
|
||||
frappe.db.get_value(
|
||||
@ -433,6 +446,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
|
||||
"serial_and_batch_bundle": source_doc.rejected_serial_and_batch_bundle,
|
||||
"returned_against": source_doc.name,
|
||||
"item_code": source_doc.item_code,
|
||||
"returned_serial_nos": returned_serial_nos,
|
||||
}
|
||||
)
|
||||
|
||||
@ -649,8 +663,11 @@ def get_filters(
|
||||
return filters
|
||||
|
||||
|
||||
def get_returned_serial_nos(child_doc, parent_doc, serial_no_field="serial_no"):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
def get_returned_serial_nos(child_doc, parent_doc, serial_no_field=None):
|
||||
from erpnext.stock.serial_batch_bundle import get_serial_nos
|
||||
|
||||
if not serial_no_field:
|
||||
serial_no_field = "serial_and_batch_bundle"
|
||||
|
||||
return_ref_field = frappe.scrub(child_doc.doctype)
|
||||
if child_doc.doctype == "Delivery Note Item":
|
||||
@ -667,7 +684,10 @@ def get_returned_serial_nos(child_doc, parent_doc, serial_no_field="serial_no"):
|
||||
[parent_doc.doctype, "docstatus", "=", 1],
|
||||
]
|
||||
|
||||
ids = []
|
||||
for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
|
||||
serial_nos.extend(get_serial_nos(row.get(serial_no_field)))
|
||||
ids.append(row.get("serial_and_batch_bundle"))
|
||||
|
||||
serial_nos.extend(get_serial_nos(ids))
|
||||
|
||||
return serial_nos
|
||||
|
@ -22,6 +22,11 @@ from erpnext.manufacturing.doctype.work_order.work_order import (
|
||||
)
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.stock.doctype.item.test_item import create_item, make_item
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
get_batch_from_bundle,
|
||||
get_serial_nos_from_bundle,
|
||||
make_serial_batch_bundle,
|
||||
)
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.doctype.stock_entry import test_stock_entry
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
@ -672,8 +677,11 @@ class TestWorkOrder(FrappeTestCase):
|
||||
if row.is_finished_item:
|
||||
self.assertEqual(row.item_code, fg_item)
|
||||
self.assertEqual(row.qty, 10)
|
||||
self.assertTrue(row.batch_no in batches)
|
||||
batches.remove(row.batch_no)
|
||||
|
||||
bundle_id = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle)
|
||||
for bundle_row in bundle_id.get("entries"):
|
||||
self.assertTrue(bundle_row.batch_no in batches)
|
||||
batches.remove(bundle_row.batch_no)
|
||||
|
||||
ste1.submit()
|
||||
|
||||
@ -682,8 +690,12 @@ class TestWorkOrder(FrappeTestCase):
|
||||
for row in ste1.get("items"):
|
||||
if row.is_finished_item:
|
||||
self.assertEqual(row.item_code, fg_item)
|
||||
self.assertEqual(row.qty, 10)
|
||||
remaining_batches.append(row.batch_no)
|
||||
self.assertEqual(row.qty, 20)
|
||||
|
||||
bundle_id = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle)
|
||||
for bundle_row in bundle_id.get("entries"):
|
||||
self.assertTrue(bundle_row.batch_no in batches)
|
||||
remaining_batches.append(bundle_row.batch_no)
|
||||
|
||||
self.assertEqual(sorted(remaining_batches), sorted(batches))
|
||||
|
||||
@ -1168,18 +1180,28 @@ class TestWorkOrder(FrappeTestCase):
|
||||
|
||||
try:
|
||||
wo_order = make_wo_order_test_record(item=fg_item, qty=2, skip_transfer=True)
|
||||
serial_nos = wo_order.serial_no
|
||||
serial_nos = self.get_serial_nos_for_fg(wo_order.name)
|
||||
|
||||
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
|
||||
stock_entry.set_work_order_details()
|
||||
stock_entry.set_serial_no_batch_for_finished_good()
|
||||
for row in stock_entry.items:
|
||||
if row.item_code == fg_item:
|
||||
self.assertTrue(row.serial_no)
|
||||
self.assertEqual(sorted(get_serial_nos(row.serial_no)), sorted(get_serial_nos(serial_nos)))
|
||||
self.assertTrue(row.serial_and_batch_bundle)
|
||||
self.assertEqual(
|
||||
sorted(get_serial_nos_from_bundle(row.serial_and_batch_bundle)), sorted(serial_nos)
|
||||
)
|
||||
|
||||
except frappe.MandatoryError:
|
||||
self.fail("Batch generation causing failing in Work Order")
|
||||
|
||||
def get_serial_nos_for_fg(self, work_order):
|
||||
serial_nos = []
|
||||
for row in frappe.get_all("Serial No", filters={"work_order": work_order}):
|
||||
serial_nos.append(row.name)
|
||||
|
||||
return serial_nos
|
||||
|
||||
@change_settings(
|
||||
"Manufacturing Settings",
|
||||
{"backflush_raw_materials_based_on": "Material Transferred for Manufacture"},
|
||||
@ -1272,63 +1294,66 @@ class TestWorkOrder(FrappeTestCase):
|
||||
fg_item = "Test FG Item with Batch Raw Materials"
|
||||
|
||||
ste_doc = test_stock_entry.make_stock_entry(
|
||||
item_code=batch_item, target="Stores - _TC", qty=2, basic_rate=100, do_not_save=True
|
||||
)
|
||||
|
||||
ste_doc.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": batch_item,
|
||||
"item_name": batch_item,
|
||||
"description": batch_item,
|
||||
"basic_rate": 100,
|
||||
"t_warehouse": "Stores - _TC",
|
||||
"qty": 2,
|
||||
"uom": "Nos",
|
||||
"stock_uom": "Nos",
|
||||
"conversion_factor": 1,
|
||||
},
|
||||
item_code=batch_item, target="Stores - _TC", qty=4, basic_rate=100, do_not_save=True
|
||||
)
|
||||
|
||||
# Inward raw materials in Stores warehouse
|
||||
ste_doc.insert()
|
||||
ste_doc.submit()
|
||||
ste_doc.load_from_db()
|
||||
|
||||
batch_list = sorted([row.batch_no for row in ste_doc.items])
|
||||
batch_no = get_batch_from_bundle(ste_doc.items[0].serial_and_batch_bundle)
|
||||
|
||||
wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4)
|
||||
transferred_ste_doc = frappe.get_doc(
|
||||
make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4)
|
||||
)
|
||||
|
||||
transferred_ste_doc.items[0].qty = 2
|
||||
transferred_ste_doc.items[0].batch_no = batch_list[0]
|
||||
transferred_ste_doc.items[0].qty = 4
|
||||
transferred_ste_doc.items[0].serial_and_batch_bundle = make_serial_batch_bundle(
|
||||
frappe._dict(
|
||||
{
|
||||
"item_code": batch_item,
|
||||
"warehouse": "Stores - _TC",
|
||||
"company": transferred_ste_doc.company,
|
||||
"qty": 4,
|
||||
"voucher_type": "Stock Entry",
|
||||
"batches": frappe._dict({batch_no: 4}),
|
||||
"posting_date": transferred_ste_doc.posting_date,
|
||||
"posting_time": transferred_ste_doc.posting_time,
|
||||
"type_of_transaction": "Outward",
|
||||
"do_not_submit": True,
|
||||
}
|
||||
)
|
||||
).name
|
||||
|
||||
new_row = copy.deepcopy(transferred_ste_doc.items[0])
|
||||
new_row.name = ""
|
||||
new_row.batch_no = batch_list[1]
|
||||
|
||||
# Transferred two batches from Stores to WIP Warehouse
|
||||
transferred_ste_doc.append("items", new_row)
|
||||
transferred_ste_doc.submit()
|
||||
transferred_ste_doc.load_from_db()
|
||||
|
||||
# First Manufacture stock entry
|
||||
manufacture_ste_doc1 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 1))
|
||||
manufacture_ste_doc1.submit()
|
||||
manufacture_ste_doc1.load_from_db()
|
||||
|
||||
# Batch no should be same as transferred Batch no
|
||||
self.assertEqual(manufacture_ste_doc1.items[0].batch_no, batch_list[0])
|
||||
self.assertEqual(
|
||||
get_batch_from_bundle(manufacture_ste_doc1.items[0].serial_and_batch_bundle), batch_no
|
||||
)
|
||||
self.assertEqual(manufacture_ste_doc1.items[0].qty, 1)
|
||||
|
||||
manufacture_ste_doc1.submit()
|
||||
|
||||
# Second Manufacture stock entry
|
||||
manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 2))
|
||||
manufacture_ste_doc2.submit()
|
||||
manufacture_ste_doc2.load_from_db()
|
||||
|
||||
# Batch no should be same as transferred Batch no
|
||||
self.assertEqual(manufacture_ste_doc2.items[0].batch_no, batch_list[0])
|
||||
self.assertEqual(manufacture_ste_doc2.items[0].qty, 1)
|
||||
self.assertEqual(manufacture_ste_doc2.items[1].batch_no, batch_list[1])
|
||||
self.assertEqual(manufacture_ste_doc2.items[1].qty, 1)
|
||||
self.assertTrue(manufacture_ste_doc2.items[0].serial_and_batch_bundle)
|
||||
bundle_doc = frappe.get_doc(
|
||||
"Serial and Batch Bundle", manufacture_ste_doc2.items[0].serial_and_batch_bundle
|
||||
)
|
||||
|
||||
for d in bundle_doc.entries:
|
||||
self.assertEqual(d.batch_no, batch_no)
|
||||
self.assertEqual(abs(d.qty), 2)
|
||||
|
||||
def test_backflushed_serial_no_raw_materials_based_on_transferred(self):
|
||||
frappe.db.set_value(
|
||||
@ -1386,76 +1411,79 @@ class TestWorkOrder(FrappeTestCase):
|
||||
fg_item = "Test FG Item with Serial & Batch No Raw Materials"
|
||||
|
||||
ste_doc = test_stock_entry.make_stock_entry(
|
||||
item_code=sn_batch_item, target="Stores - _TC", qty=2, basic_rate=100, do_not_save=True
|
||||
)
|
||||
|
||||
ste_doc.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": sn_batch_item,
|
||||
"item_name": sn_batch_item,
|
||||
"description": sn_batch_item,
|
||||
"basic_rate": 100,
|
||||
"t_warehouse": "Stores - _TC",
|
||||
"qty": 2,
|
||||
"uom": "Nos",
|
||||
"stock_uom": "Nos",
|
||||
"conversion_factor": 1,
|
||||
},
|
||||
item_code=sn_batch_item, target="Stores - _TC", qty=4, basic_rate=100, do_not_save=True
|
||||
)
|
||||
|
||||
# Inward raw materials in Stores warehouse
|
||||
ste_doc.insert()
|
||||
ste_doc.submit()
|
||||
ste_doc.load_from_db()
|
||||
|
||||
batch_dict = {row.batch_no: get_serial_nos(row.serial_no) for row in ste_doc.items}
|
||||
batches = list(batch_dict.keys())
|
||||
serial_nos = []
|
||||
for row in ste_doc.items:
|
||||
bundle_doc = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle)
|
||||
|
||||
for d in bundle_doc.entries:
|
||||
serial_nos.append(d.serial_no)
|
||||
|
||||
wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4)
|
||||
transferred_ste_doc = frappe.get_doc(
|
||||
make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4)
|
||||
)
|
||||
|
||||
transferred_ste_doc.items[0].qty = 2
|
||||
transferred_ste_doc.items[0].batch_no = batches[0]
|
||||
transferred_ste_doc.items[0].serial_no = "\n".join(batch_dict.get(batches[0]))
|
||||
transferred_ste_doc.items[0].qty = 4
|
||||
transferred_ste_doc.items[0].serial_and_batch_bundle = make_serial_batch_bundle(
|
||||
frappe._dict(
|
||||
{
|
||||
"item_code": transferred_ste_doc.get("items")[0].item_code,
|
||||
"warehouse": transferred_ste_doc.get("items")[0].s_warehouse,
|
||||
"company": transferred_ste_doc.company,
|
||||
"qty": 4,
|
||||
"type_of_transaction": "Outward",
|
||||
"voucher_type": "Stock Entry",
|
||||
"serial_nos": serial_nos,
|
||||
"posting_date": transferred_ste_doc.posting_date,
|
||||
"posting_time": transferred_ste_doc.posting_time,
|
||||
"do_not_submit": True,
|
||||
}
|
||||
)
|
||||
).name
|
||||
|
||||
new_row = copy.deepcopy(transferred_ste_doc.items[0])
|
||||
new_row.name = ""
|
||||
new_row.batch_no = batches[1]
|
||||
new_row.serial_no = "\n".join(batch_dict.get(batches[1]))
|
||||
|
||||
# Transferred two batches from Stores to WIP Warehouse
|
||||
transferred_ste_doc.append("items", new_row)
|
||||
transferred_ste_doc.submit()
|
||||
transferred_ste_doc.load_from_db()
|
||||
|
||||
# First Manufacture stock entry
|
||||
manufacture_ste_doc1 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 1))
|
||||
manufacture_ste_doc1.submit()
|
||||
manufacture_ste_doc1.load_from_db()
|
||||
|
||||
# Batch no & Serial Nos should be same as transferred Batch no & Serial Nos
|
||||
batch_no = manufacture_ste_doc1.items[0].batch_no
|
||||
self.assertEqual(
|
||||
get_serial_nos(manufacture_ste_doc1.items[0].serial_no)[0], batch_dict.get(batch_no)[0]
|
||||
)
|
||||
self.assertEqual(manufacture_ste_doc1.items[0].qty, 1)
|
||||
bundle = manufacture_ste_doc1.items[0].serial_and_batch_bundle
|
||||
self.assertTrue(bundle)
|
||||
|
||||
manufacture_ste_doc1.submit()
|
||||
bundle_doc = frappe.get_doc("Serial and Batch Bundle", bundle)
|
||||
for d in bundle_doc.entries:
|
||||
self.assertTrue(d.serial_no)
|
||||
self.assertTrue(d.batch_no)
|
||||
batch_no = frappe.get_cached_value("Serial No", d.serial_no, "batch_no")
|
||||
self.assertEqual(d.batch_no, batch_no)
|
||||
serial_nos.remove(d.serial_no)
|
||||
|
||||
# Second Manufacture stock entry
|
||||
manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 2))
|
||||
manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 3))
|
||||
manufacture_ste_doc2.submit()
|
||||
manufacture_ste_doc2.load_from_db()
|
||||
|
||||
# Batch no & Serial Nos should be same as transferred Batch no & Serial Nos
|
||||
batch_no = manufacture_ste_doc2.items[0].batch_no
|
||||
self.assertEqual(
|
||||
get_serial_nos(manufacture_ste_doc2.items[0].serial_no)[0], batch_dict.get(batch_no)[1]
|
||||
)
|
||||
self.assertEqual(manufacture_ste_doc2.items[0].qty, 1)
|
||||
bundle = manufacture_ste_doc2.items[0].serial_and_batch_bundle
|
||||
self.assertTrue(bundle)
|
||||
|
||||
batch_no = manufacture_ste_doc2.items[1].batch_no
|
||||
self.assertEqual(
|
||||
get_serial_nos(manufacture_ste_doc2.items[1].serial_no)[0], batch_dict.get(batch_no)[0]
|
||||
)
|
||||
self.assertEqual(manufacture_ste_doc2.items[1].qty, 1)
|
||||
bundle_doc = frappe.get_doc("Serial and Batch Bundle", bundle)
|
||||
for d in bundle_doc.entries:
|
||||
self.assertTrue(d.serial_no)
|
||||
self.assertTrue(d.batch_no)
|
||||
serial_nos.remove(d.serial_no)
|
||||
|
||||
self.assertFalse(serial_nos)
|
||||
|
||||
def test_non_consumed_material_return_against_work_order(self):
|
||||
frappe.db.set_value(
|
||||
@ -1490,27 +1518,35 @@ class TestWorkOrder(FrappeTestCase):
|
||||
for row in ste_doc.items:
|
||||
row.qty += 2
|
||||
row.transfer_qty += 2
|
||||
nste_doc = test_stock_entry.make_stock_entry(
|
||||
test_stock_entry.make_stock_entry(
|
||||
item_code=row.item_code, target="Stores - _TC", qty=row.qty, basic_rate=100
|
||||
)
|
||||
|
||||
row.batch_no = nste_doc.items[0].batch_no
|
||||
row.serial_no = nste_doc.items[0].serial_no
|
||||
|
||||
ste_doc.save()
|
||||
ste_doc.submit()
|
||||
ste_doc.load_from_db()
|
||||
|
||||
# Create a stock entry to manufacture the item
|
||||
print("remove 2 qty from each item")
|
||||
ste_doc = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 5))
|
||||
for row in ste_doc.items:
|
||||
if row.s_warehouse and not row.t_warehouse:
|
||||
row.qty -= 2
|
||||
row.transfer_qty -= 2
|
||||
|
||||
if row.serial_no:
|
||||
serial_nos = get_serial_nos(row.serial_no)
|
||||
row.serial_no = "\n".join(serial_nos[0:5])
|
||||
if not row.serial_and_batch_bundle:
|
||||
continue
|
||||
|
||||
bundle_id = row.serial_and_batch_bundle
|
||||
bundle_doc = frappe.get_doc("Serial and Batch Bundle", bundle_id)
|
||||
if bundle_doc.has_serial_no:
|
||||
bundle_doc.set("entries", bundle_doc.entries[0:5])
|
||||
else:
|
||||
for bundle_row in bundle_doc.entries:
|
||||
bundle_row.qty += 2
|
||||
|
||||
bundle_doc.save()
|
||||
bundle_doc.load_from_db()
|
||||
|
||||
ste_doc.save()
|
||||
ste_doc.submit()
|
||||
|
@ -1364,10 +1364,10 @@ def split_qty_based_on_batch_size(wo_doc, row, qty):
|
||||
|
||||
|
||||
def get_serial_nos_for_job_card(row, wo_doc):
|
||||
if not wo_doc.serial_no:
|
||||
if not wo_doc.has_serial_no:
|
||||
return
|
||||
|
||||
serial_nos = get_serial_nos(wo_doc.serial_no)
|
||||
serial_nos = get_serial_nos_for_work_order(wo_doc.name, wo_doc.production_item)
|
||||
used_serial_nos = []
|
||||
for d in frappe.get_all(
|
||||
"Job Card",
|
||||
@ -1380,6 +1380,21 @@ def get_serial_nos_for_job_card(row, wo_doc):
|
||||
row.serial_no = "\n".join(serial_nos[0 : cint(row.job_card_qty)])
|
||||
|
||||
|
||||
def get_serial_nos_for_work_order(work_order, production_item):
|
||||
serial_nos = []
|
||||
for d in frappe.get_all(
|
||||
"Serial No",
|
||||
fields=["name"],
|
||||
filters={
|
||||
"work_order": work_order,
|
||||
"item_code": production_item,
|
||||
},
|
||||
):
|
||||
serial_nos.append(d.name)
|
||||
|
||||
return serial_nos
|
||||
|
||||
|
||||
def validate_operation_data(row):
|
||||
if row.get("qty") <= 0:
|
||||
frappe.throw(
|
||||
|
@ -1254,112 +1254,6 @@ class TestSalesOrder(FrappeTestCase):
|
||||
)
|
||||
self.assertEqual(wo_qty[0][0], so_item_name.get(item))
|
||||
|
||||
def test_serial_no_based_delivery(self):
|
||||
frappe.set_value("Stock Settings", None, "automatically_set_serial_nos_based_on_fifo", 1)
|
||||
item = make_item(
|
||||
"_Reserved_Serialized_Item",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"maintain_stock": 1,
|
||||
"has_serial_no": 1,
|
||||
"serial_no_series": "SI.####",
|
||||
"valuation_rate": 500,
|
||||
"item_defaults": [{"default_warehouse": "_Test Warehouse - _TC", "company": "_Test Company"}],
|
||||
},
|
||||
)
|
||||
frappe.db.sql("""delete from `tabSerial No` where item_code=%s""", (item.item_code))
|
||||
make_item(
|
||||
"_Test Item A",
|
||||
{
|
||||
"maintain_stock": 1,
|
||||
"valuation_rate": 100,
|
||||
"item_defaults": [{"default_warehouse": "_Test Warehouse - _TC", "company": "_Test Company"}],
|
||||
},
|
||||
)
|
||||
make_item(
|
||||
"_Test Item B",
|
||||
{
|
||||
"maintain_stock": 1,
|
||||
"valuation_rate": 200,
|
||||
"item_defaults": [{"default_warehouse": "_Test Warehouse - _TC", "company": "_Test Company"}],
|
||||
},
|
||||
)
|
||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||
|
||||
make_bom(item=item.item_code, rate=1000, raw_materials=["_Test Item A", "_Test Item B"])
|
||||
|
||||
so = make_sales_order(
|
||||
**{
|
||||
"item_list": [
|
||||
{
|
||||
"item_code": item.item_code,
|
||||
"ensure_delivery_based_on_produced_serial_no": 1,
|
||||
"qty": 1,
|
||||
"rate": 1000,
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
so.submit()
|
||||
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
|
||||
|
||||
work_order = make_wo_order_test_record(item=item.item_code, qty=1, do_not_save=True)
|
||||
work_order.fg_warehouse = "_Test Warehouse - _TC"
|
||||
work_order.sales_order = so.name
|
||||
work_order.submit()
|
||||
make_stock_entry(item_code=item.item_code, target="_Test Warehouse - _TC", qty=1)
|
||||
item_serial_no = frappe.get_doc("Serial No", {"item_code": item.item_code})
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import (
|
||||
make_stock_entry as make_production_stock_entry,
|
||||
)
|
||||
|
||||
se = frappe.get_doc(make_production_stock_entry(work_order.name, "Manufacture", 1))
|
||||
se.submit()
|
||||
reserved_serial_no = se.get("items")[2].serial_no
|
||||
serial_no_so = frappe.get_value("Serial No", reserved_serial_no, "sales_order")
|
||||
self.assertEqual(serial_no_so, so.name)
|
||||
dn = make_delivery_note(so.name)
|
||||
dn.save()
|
||||
self.assertEqual(reserved_serial_no, dn.get("items")[0].serial_no)
|
||||
item_line = dn.get("items")[0]
|
||||
item_line.serial_no = item_serial_no.name
|
||||
item_line = dn.get("items")[0]
|
||||
item_line.serial_no = reserved_serial_no
|
||||
dn.submit()
|
||||
dn.load_from_db()
|
||||
dn.cancel()
|
||||
si = make_sales_invoice(so.name)
|
||||
si.update_stock = 1
|
||||
si.save()
|
||||
self.assertEqual(si.get("items")[0].serial_no, reserved_serial_no)
|
||||
item_line = si.get("items")[0]
|
||||
item_line.serial_no = item_serial_no.name
|
||||
self.assertRaises(frappe.ValidationError, dn.submit)
|
||||
item_line = si.get("items")[0]
|
||||
item_line.serial_no = reserved_serial_no
|
||||
self.assertTrue(si.submit)
|
||||
si.submit()
|
||||
si.load_from_db()
|
||||
si.cancel()
|
||||
si = make_sales_invoice(so.name)
|
||||
si.update_stock = 0
|
||||
si.submit()
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
make_delivery_note as make_delivery_note_from_invoice,
|
||||
)
|
||||
|
||||
dn = make_delivery_note_from_invoice(si.name)
|
||||
dn.save()
|
||||
dn.submit()
|
||||
self.assertEqual(dn.get("items")[0].serial_no, reserved_serial_no)
|
||||
dn.load_from_db()
|
||||
dn.cancel()
|
||||
si.load_from_db()
|
||||
si.cancel()
|
||||
se.load_from_db()
|
||||
se.cancel()
|
||||
self.assertFalse(frappe.db.exists("Serial No", {"sales_order": so.name}))
|
||||
|
||||
def test_advance_payment_entry_unlink_against_sales_order(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
|
||||
|
@ -211,6 +211,7 @@ class DeprecatedBatchNoValuation:
|
||||
& (bundle_child.batch_no.isnotnull())
|
||||
& (batch.use_batchwise_valuation == 0)
|
||||
& (bundle.is_cancelled == 0)
|
||||
& (bundle.type_of_transaction.isin(["Inward", "Outward"]))
|
||||
)
|
||||
.where(timestamp_condition)
|
||||
)
|
||||
|
@ -512,6 +512,7 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
|
||||
def test_return_for_serialized_items(self):
|
||||
se = make_serialized_item()
|
||||
|
||||
serial_no = [get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0]]
|
||||
|
||||
dn = create_delivery_note(
|
||||
@ -1215,6 +1216,9 @@ def create_delivery_note(**args):
|
||||
if args.get("batch_no") or args.get("serial_no"):
|
||||
type_of_transaction = args.type_of_transaction or "Outward"
|
||||
|
||||
if dn.is_return:
|
||||
type_of_transaction = "Inward"
|
||||
|
||||
qty = args.get("qty") or 1
|
||||
qty *= -1 if type_of_transaction == "Outward" else 1
|
||||
batches = {}
|
||||
@ -1233,6 +1237,7 @@ def create_delivery_note(**args):
|
||||
"posting_date": dn.posting_date,
|
||||
"posting_time": dn.posting_time,
|
||||
"type_of_transaction": type_of_transaction,
|
||||
"do_not_submit": True,
|
||||
}
|
||||
)
|
||||
).name
|
||||
@ -1257,6 +1262,9 @@ def create_delivery_note(**args):
|
||||
dn.insert()
|
||||
if not args.do_not_submit:
|
||||
dn.submit()
|
||||
|
||||
dn.load_from_db()
|
||||
|
||||
return dn
|
||||
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_days, add_to_date, flt, now
|
||||
from frappe.utils import add_days, add_to_date, flt, now, nowtime, today
|
||||
|
||||
from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
@ -15,6 +15,12 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
|
||||
get_gl_entries,
|
||||
make_purchase_receipt,
|
||||
)
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
get_batch_from_bundle,
|
||||
get_serial_nos_from_bundle,
|
||||
make_serial_batch_bundle,
|
||||
)
|
||||
from erpnext.stock.serial_batch_bundle import SerialNoValuation
|
||||
|
||||
|
||||
class TestLandedCostVoucher(FrappeTestCase):
|
||||
@ -297,9 +303,8 @@ class TestLandedCostVoucher(FrappeTestCase):
|
||||
self.assertEqual(expected_values[gle.account][1], gle.credit)
|
||||
|
||||
def test_landed_cost_voucher_for_serialized_item(self):
|
||||
frappe.db.sql(
|
||||
"delete from `tabSerial No` where name in ('SN001', 'SN002', 'SN003', 'SN004', 'SN005')"
|
||||
)
|
||||
frappe.db.set_value("Item", "_Test Serialized Item", "serial_no_series", "SNJJ.###")
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
company="_Test Company with perpetual inventory",
|
||||
warehouse="Stores - TCP1",
|
||||
@ -310,17 +315,42 @@ class TestLandedCostVoucher(FrappeTestCase):
|
||||
)
|
||||
|
||||
pr.items[0].item_code = "_Test Serialized Item"
|
||||
pr.items[0].serial_no = "SN001\nSN002\nSN003\nSN004\nSN005"
|
||||
pr.submit()
|
||||
pr.load_from_db()
|
||||
|
||||
serial_no_rate = frappe.db.get_value("Serial No", "SN001", "purchase_rate")
|
||||
serial_no = get_serial_nos_from_bundle(pr.items[0].serial_and_batch_bundle)[0]
|
||||
|
||||
sn_obj = SerialNoValuation(
|
||||
sle=frappe._dict(
|
||||
{
|
||||
"posting_date": today(),
|
||||
"posting_time": nowtime(),
|
||||
"item_code": "_Test Serialized Item",
|
||||
"warehouse": "Stores - TCP1",
|
||||
"serial_nos": [serial_no],
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
serial_no_rate = sn_obj.get_incoming_rate_of_serial_no(serial_no)
|
||||
|
||||
create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
|
||||
|
||||
serial_no = frappe.db.get_value("Serial No", "SN001", ["warehouse", "purchase_rate"], as_dict=1)
|
||||
sn_obj = SerialNoValuation(
|
||||
sle=frappe._dict(
|
||||
{
|
||||
"posting_date": today(),
|
||||
"posting_time": nowtime(),
|
||||
"item_code": "_Test Serialized Item",
|
||||
"warehouse": "Stores - TCP1",
|
||||
"serial_nos": [serial_no],
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(serial_no.purchase_rate - serial_no_rate, 5.0)
|
||||
self.assertEqual(serial_no.warehouse, "Stores - TCP1")
|
||||
new_serial_no_rate = sn_obj.get_incoming_rate_of_serial_no(serial_no)
|
||||
|
||||
self.assertEqual(new_serial_no_rate - serial_no_rate, 5.0)
|
||||
|
||||
def test_serialized_lcv_delivered(self):
|
||||
"""In some cases you'd want to deliver before you can know all the
|
||||
@ -337,6 +367,15 @@ class TestLandedCostVoucher(FrappeTestCase):
|
||||
item_code = "_Test Serialized Item"
|
||||
warehouse = "Stores - TCP1"
|
||||
|
||||
if not frappe.db.exists("Serial No", serial_no):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Serial No",
|
||||
"item_code": item_code,
|
||||
"serial_no": serial_no,
|
||||
}
|
||||
).insert()
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
company="_Test Company with perpetual inventory",
|
||||
warehouse=warehouse,
|
||||
@ -346,7 +385,19 @@ class TestLandedCostVoucher(FrappeTestCase):
|
||||
serial_no=[serial_no],
|
||||
)
|
||||
|
||||
serial_no_rate = frappe.db.get_value("Serial No", serial_no, "purchase_rate")
|
||||
sn_obj = SerialNoValuation(
|
||||
sle=frappe._dict(
|
||||
{
|
||||
"posting_date": today(),
|
||||
"posting_time": nowtime(),
|
||||
"item_code": "_Test Serialized Item",
|
||||
"warehouse": "Stores - TCP1",
|
||||
"serial_nos": [serial_no],
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
serial_no_rate = sn_obj.get_incoming_rate_of_serial_no(serial_no)
|
||||
|
||||
# deliver it before creating LCV
|
||||
dn = create_delivery_note(
|
||||
@ -362,14 +413,24 @@ class TestLandedCostVoucher(FrappeTestCase):
|
||||
|
||||
charges = 10
|
||||
create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=charges)
|
||||
|
||||
new_purchase_rate = serial_no_rate + charges
|
||||
|
||||
serial_no = frappe.db.get_value(
|
||||
"Serial No", serial_no, ["warehouse", "purchase_rate"], as_dict=1
|
||||
sn_obj = SerialNoValuation(
|
||||
sle=frappe._dict(
|
||||
{
|
||||
"posting_date": today(),
|
||||
"posting_time": nowtime(),
|
||||
"item_code": "_Test Serialized Item",
|
||||
"warehouse": "Stores - TCP1",
|
||||
"serial_nos": [serial_no],
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(serial_no.purchase_rate, new_purchase_rate)
|
||||
new_serial_no_rate = sn_obj.get_incoming_rate_of_serial_no(serial_no)
|
||||
|
||||
# Since the serial no is already delivered the rate must be zero
|
||||
self.assertFalse(new_serial_no_rate)
|
||||
|
||||
stock_value_difference = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
|
@ -52,9 +52,11 @@ class SerialandBatchBundle(Document):
|
||||
return
|
||||
|
||||
serial_nos = [d.serial_no for d in self.entries if d.serial_no]
|
||||
available_serial_nos = get_available_serial_nos(
|
||||
frappe._dict({"item_code": self.item_code, "warehouse": self.warehouse})
|
||||
)
|
||||
kwargs = {"item_code": self.item_code, "warehouse": self.warehouse}
|
||||
if self.voucher_type == "POS Invoice":
|
||||
kwargs["ignore_voucher_no"] = self.voucher_no
|
||||
|
||||
available_serial_nos = get_available_serial_nos(frappe._dict(kwargs))
|
||||
|
||||
serial_no_warehouse = {}
|
||||
for data in available_serial_nos:
|
||||
@ -149,12 +151,9 @@ class SerialandBatchBundle(Document):
|
||||
d.incoming_rate = abs(sn_obj.batch_avg_rate.get(d.batch_no))
|
||||
|
||||
available_qty = flt(sn_obj.available_qty.get(d.batch_no)) + flt(d.qty)
|
||||
|
||||
self.validate_negative_batch(d.batch_no, available_qty)
|
||||
|
||||
if self.has_batch_no:
|
||||
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)
|
||||
|
||||
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)
|
||||
if save:
|
||||
d.db_set(
|
||||
{"incoming_rate": d.incoming_rate, "stock_value_difference": d.stock_value_difference}
|
||||
@ -198,6 +197,9 @@ class SerialandBatchBundle(Document):
|
||||
if self.voucher_type in ["Sales Invoice", "Delivery Note"]:
|
||||
valuation_field = "incoming_rate"
|
||||
|
||||
if self.voucher_type == "POS Invoice":
|
||||
valuation_field = "rate"
|
||||
|
||||
rate = row.get(valuation_field) if row else 0.0
|
||||
precision = frappe.get_precision(self.child_table, valuation_field) or 2
|
||||
|
||||
@ -325,6 +327,7 @@ class SerialandBatchBundle(Document):
|
||||
& (parent.item_code == self.item_code)
|
||||
& (parent.docstatus == 1)
|
||||
& (parent.is_cancelled == 0)
|
||||
& (parent.type_of_transaction.isin(["Inward", "Outward"]))
|
||||
)
|
||||
.where(timestamp_condition)
|
||||
).run(as_dict=True)
|
||||
@ -830,6 +833,7 @@ def get_reserved_serial_nos_for_pos(kwargs):
|
||||
["POS Invoice", "consolidated_invoice", "is", "not set"],
|
||||
["POS Invoice", "docstatus", "=", 1],
|
||||
["POS Invoice Item", "item_code", "=", kwargs.item_code],
|
||||
["POS Invoice", "name", "!=", kwargs.ignore_voucher_no],
|
||||
],
|
||||
)
|
||||
|
||||
@ -839,7 +843,10 @@ def get_reserved_serial_nos_for_pos(kwargs):
|
||||
if pos_invoice.serial_and_batch_bundle
|
||||
]
|
||||
|
||||
for d in get_serial_batch_ledgers(ids, docstatus=1, name=ids):
|
||||
if not ids:
|
||||
return []
|
||||
|
||||
for d in get_serial_batch_ledgers(kwargs.item_code, docstatus=1, name=ids):
|
||||
ignore_serial_nos.append(d.serial_no)
|
||||
|
||||
# Will be deprecated in v16
|
||||
@ -1010,7 +1017,11 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> List[frappe._dict]:
|
||||
bundle_table.posting_date,
|
||||
bundle_table.posting_time,
|
||||
)
|
||||
.where((bundle_table.docstatus == 1) & (bundle_table.is_cancelled == 0))
|
||||
.where(
|
||||
(bundle_table.docstatus == 1)
|
||||
& (bundle_table.is_cancelled == 0)
|
||||
& (bundle_table.type_of_transaction.isin(["Inward", "Outward"]))
|
||||
)
|
||||
)
|
||||
|
||||
for key, val in kwargs.items():
|
||||
|
@ -39,9 +39,7 @@
|
||||
"more_info",
|
||||
"company",
|
||||
"column_break_2cmm",
|
||||
"work_order",
|
||||
"section_break_fgyk",
|
||||
"serial_no_details"
|
||||
"work_order"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -226,11 +224,6 @@
|
||||
"fieldtype": "Section Break",
|
||||
"label": "More Information"
|
||||
},
|
||||
{
|
||||
"fieldname": "serial_no_details",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Serial No Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
@ -282,10 +275,6 @@
|
||||
{
|
||||
"fieldname": "column_break_2cmm",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_fgyk",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-barcode",
|
||||
|
@ -2789,7 +2789,7 @@ def get_available_materials(work_order) -> dict:
|
||||
|
||||
if row.batch_nos:
|
||||
for batch_no, qty in row.batch_nos.items():
|
||||
item_data.batch_details[batch_no] -= qty
|
||||
item_data.batch_details[batch_no] += qty
|
||||
|
||||
if row.serial_no:
|
||||
for serial_no in get_serial_nos(row.serial_no):
|
||||
|
@ -1803,6 +1803,8 @@ def make_serialized_item(**args):
|
||||
se.set_stock_entry_type()
|
||||
se.insert()
|
||||
se.submit()
|
||||
|
||||
se.load_from_db()
|
||||
return se
|
||||
|
||||
|
||||
|
@ -257,6 +257,9 @@ class SerialBatchBundle:
|
||||
|
||||
def get_serial_nos(serial_and_batch_bundle):
|
||||
filters = {"parent": serial_and_batch_bundle}
|
||||
if isinstance(serial_and_batch_bundle, list):
|
||||
filters = {"parent": ("in", serial_and_batch_bundle)}
|
||||
|
||||
entries = frappe.get_all("Serial and Batch Entry", fields=["serial_no"], filters=filters)
|
||||
|
||||
return [d.serial_no for d in entries]
|
||||
@ -306,7 +309,7 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
|
||||
(bundle.is_cancelled == 0)
|
||||
& (bundle.docstatus == 1)
|
||||
& (bundle_child.serial_no.isin(serial_nos))
|
||||
& (bundle.type_of_transaction != "Maintenance")
|
||||
& (bundle.type_of_transaction.isin(["Inward", "Outward"]))
|
||||
& (bundle.item_code == self.sle.item_code)
|
||||
& (bundle_child.warehouse == self.sle.warehouse)
|
||||
)
|
||||
@ -314,7 +317,7 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
|
||||
)
|
||||
|
||||
# Important to exclude the current voucher
|
||||
if self.sle.voucher_type == "Stock Reconciliation" and self.sle.voucher_no:
|
||||
if self.sle.voucher_no:
|
||||
query = query.where(bundle.voucher_no != self.sle.voucher_no)
|
||||
|
||||
if self.sle.posting_date:
|
||||
@ -370,6 +373,9 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
|
||||
def get_incoming_rate(self):
|
||||
return abs(flt(self.stock_value_change) / flt(self.sle.actual_qty))
|
||||
|
||||
def get_incoming_rate_of_serial_no(self, serial_no):
|
||||
return self.serial_no_incoming_rate.get(serial_no, 0.0)
|
||||
|
||||
|
||||
def is_rejected(voucher_type, voucher_detail_no, warehouse):
|
||||
if voucher_type in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
@ -437,7 +443,7 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
||||
& (parent.item_code == self.sle.item_code)
|
||||
& (parent.docstatus == 1)
|
||||
& (parent.is_cancelled == 0)
|
||||
& (parent.type_of_transaction != "Maintenance")
|
||||
& (parent.type_of_transaction.isin(["Inward", "Outward"]))
|
||||
)
|
||||
.groupby(child.batch_no)
|
||||
)
|
||||
@ -644,6 +650,10 @@ class SerialBatchCreation:
|
||||
id = self.serial_and_batch_bundle
|
||||
package = frappe.get_doc("Serial and Batch Bundle", id)
|
||||
new_package = frappe.copy_doc(package)
|
||||
|
||||
if self.get("returned_serial_nos"):
|
||||
self.remove_returned_serial_nos(new_package)
|
||||
|
||||
new_package.docstatus = 0
|
||||
new_package.type_of_transaction = self.type_of_transaction
|
||||
new_package.returned_against = self.get("returned_against")
|
||||
@ -651,6 +661,15 @@ class SerialBatchCreation:
|
||||
|
||||
self.serial_and_batch_bundle = new_package.name
|
||||
|
||||
def remove_returned_serial_nos(self, package):
|
||||
remove_list = []
|
||||
for d in package.entries:
|
||||
if d.serial_no in self.returned_serial_nos:
|
||||
remove_list.append(d)
|
||||
|
||||
for d in remove_list:
|
||||
package.remove(d)
|
||||
|
||||
def make_serial_and_batch_bundle(self):
|
||||
doc = frappe.new_doc("Serial and Batch Bundle")
|
||||
valid_columns = doc.meta.get_valid_columns()
|
||||
@ -664,6 +683,9 @@ class SerialBatchCreation:
|
||||
self.set_auto_serial_batch_entries_for_inward()
|
||||
|
||||
self.set_serial_batch_entries(doc)
|
||||
if not doc.get("entries"):
|
||||
return frappe._dict({})
|
||||
|
||||
doc.save()
|
||||
|
||||
if not hasattr(self, "do_not_submit") or not self.do_not_submit:
|
||||
|
Loading…
x
Reference in New Issue
Block a user