fix: use serial batch fields for subcontracting receipt (#40311)
(cherry picked from commit cef6291311)
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
This commit is contained in:
@@ -434,9 +434,21 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
|
|
||||||
filtered_batches = get_filterd_batches(batches)
|
filtered_batches = get_filterd_batches(batches)
|
||||||
|
|
||||||
|
if filters.get("is_inward"):
|
||||||
|
filtered_batches.extend(get_empty_batches(filters))
|
||||||
|
|
||||||
return filtered_batches
|
return filtered_batches
|
||||||
|
|
||||||
|
|
||||||
|
def get_empty_batches(filters):
|
||||||
|
return frappe.get_all(
|
||||||
|
"Batch",
|
||||||
|
fields=["name", "batch_qty"],
|
||||||
|
filters={"item": filters.get("item_code"), "batch_qty": 0.0},
|
||||||
|
as_list=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_filterd_batches(data):
|
def get_filterd_batches(data):
|
||||||
batches = OrderedDict()
|
batches = OrderedDict()
|
||||||
|
|
||||||
|
|||||||
@@ -190,43 +190,23 @@ class StockController(AccountsController):
|
|||||||
if row.use_serial_batch_fields and (
|
if row.use_serial_batch_fields and (
|
||||||
not row.serial_and_batch_bundle and not row.get("rejected_serial_and_batch_bundle")
|
not row.serial_and_batch_bundle and not row.get("rejected_serial_and_batch_bundle")
|
||||||
):
|
):
|
||||||
if self.doctype == "Stock Reconciliation":
|
bundle_details = {
|
||||||
qty = row.qty
|
|
||||||
type_of_transaction = "Inward"
|
|
||||||
warehouse = row.warehouse
|
|
||||||
elif table_name == "packed_items":
|
|
||||||
qty = row.qty
|
|
||||||
warehouse = row.warehouse
|
|
||||||
type_of_transaction = "Outward"
|
|
||||||
if self.is_return:
|
|
||||||
type_of_transaction = "Inward"
|
|
||||||
else:
|
|
||||||
qty = row.stock_qty if self.doctype != "Stock Entry" else row.transfer_qty
|
|
||||||
type_of_transaction = get_type_of_transaction(self, row)
|
|
||||||
warehouse = (
|
|
||||||
row.warehouse if self.doctype != "Stock Entry" else row.s_warehouse or row.t_warehouse
|
|
||||||
)
|
|
||||||
|
|
||||||
sn_doc = SerialBatchCreation(
|
|
||||||
{
|
|
||||||
"item_code": row.item_code,
|
"item_code": row.item_code,
|
||||||
"warehouse": warehouse,
|
|
||||||
"posting_date": self.posting_date,
|
"posting_date": self.posting_date,
|
||||||
"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,
|
"voucher_detail_no": row.name,
|
||||||
"qty": qty,
|
|
||||||
"type_of_transaction": type_of_transaction,
|
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"is_rejected": 1 if row.get("rejected_warehouse") else 0,
|
"is_rejected": 1 if row.get("rejected_warehouse") else 0,
|
||||||
"serial_nos": get_serial_nos(row.serial_no) if row.serial_no else None,
|
"serial_nos": get_serial_nos(row.serial_no) if row.serial_no else None,
|
||||||
"batches": frappe._dict({row.batch_no: qty}) if row.batch_no else None,
|
|
||||||
"batch_no": row.batch_no,
|
"batch_no": row.batch_no,
|
||||||
"use_serial_batch_fields": row.use_serial_batch_fields,
|
"use_serial_batch_fields": row.use_serial_batch_fields,
|
||||||
"do_not_submit": True,
|
"do_not_submit": True,
|
||||||
}
|
}
|
||||||
).make_serial_and_batch_bundle()
|
|
||||||
|
self.update_bundle_details(bundle_details, table_name, row)
|
||||||
|
sn_doc = SerialBatchCreation(bundle_details).make_serial_and_batch_bundle()
|
||||||
|
|
||||||
if sn_doc.is_rejected:
|
if sn_doc.is_rejected:
|
||||||
row.rejected_serial_and_batch_bundle = sn_doc.name
|
row.rejected_serial_and_batch_bundle = sn_doc.name
|
||||||
@@ -243,6 +223,34 @@ class StockController(AccountsController):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def update_bundle_details(self, bundle_details, table_name, row):
|
||||||
|
# Since qty field is different for different doctypes
|
||||||
|
qty = row.get("qty")
|
||||||
|
warehouse = row.get("warehouse")
|
||||||
|
|
||||||
|
if table_name == "packed_items":
|
||||||
|
type_of_transaction = "Inward"
|
||||||
|
if not self.is_return:
|
||||||
|
type_of_transaction = "Outward"
|
||||||
|
else:
|
||||||
|
type_of_transaction = get_type_of_transaction(self, row)
|
||||||
|
|
||||||
|
if hasattr(row, "stock_qty"):
|
||||||
|
qty = row.stock_qty
|
||||||
|
|
||||||
|
if self.doctype == "Stock Entry":
|
||||||
|
qty = row.transfer_qty
|
||||||
|
warehouse = row.s_warehouse or row.t_warehouse
|
||||||
|
|
||||||
|
bundle_details.update(
|
||||||
|
{
|
||||||
|
"qty": qty,
|
||||||
|
"type_of_transaction": type_of_transaction,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
"batches": frappe._dict({row.batch_no: qty}) if row.batch_no else None,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def validate_serial_nos_and_batches_with_bundle(self, row):
|
def validate_serial_nos_and_batches_with_bundle(self, row):
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
|
|||||||
@@ -371,11 +371,18 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
label: __('Batch No'),
|
label: __('Batch No'),
|
||||||
in_list_view: 1,
|
in_list_view: 1,
|
||||||
get_query: () => {
|
get_query: () => {
|
||||||
|
let is_inward = false;
|
||||||
|
if ((["Purchase Receipt", "Purchase Invoice"].includes(this.frm.doc.doctype) && !this.frm.doc.is_return)
|
||||||
|
|| (this.frm.doc.doctype === 'Stock Entry' && this.frm.doc.purpose === 'Material Receipt')) {
|
||||||
|
is_inward = true;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
query : "erpnext.controllers.queries.get_batch_no",
|
query : "erpnext.controllers.queries.get_batch_no",
|
||||||
filters: {
|
filters: {
|
||||||
'item_code': this.item.item_code,
|
'item_code': this.item.item_code,
|
||||||
'warehouse': this.item.s_warehouse || this.item.t_warehouse,
|
'warehouse': this.item.s_warehouse || this.item.t_warehouse || this.item.warehouse,
|
||||||
|
'is_inward': is_inward
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1263,6 +1263,13 @@ def get_type_of_transaction(parent_doc, child_row):
|
|||||||
if parent_doc.get("is_return"):
|
if parent_doc.get("is_return"):
|
||||||
type_of_transaction = "Inward" if type_of_transaction == "Outward" else "Outward"
|
type_of_transaction = "Inward" if type_of_transaction == "Outward" else "Outward"
|
||||||
|
|
||||||
|
if parent_doc.get("doctype") == "Subcontracting Receipt":
|
||||||
|
type_of_transaction = "Outward"
|
||||||
|
if child_row.get("doctype") == "Subcontracting Receipt Item":
|
||||||
|
type_of_transaction = "Inward"
|
||||||
|
elif parent_doc.get("doctype") == "Stock Reconciliation":
|
||||||
|
type_of_transaction = "Inward"
|
||||||
|
|
||||||
return type_of_transaction
|
return type_of_transaction
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,17 +10,7 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import (
|
from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate
|
||||||
cint,
|
|
||||||
comma_or,
|
|
||||||
cstr,
|
|
||||||
flt,
|
|
||||||
format_time,
|
|
||||||
formatdate,
|
|
||||||
getdate,
|
|
||||||
month_diff,
|
|
||||||
nowdate,
|
|
||||||
)
|
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.general_ledger import process_gl_map
|
from erpnext.accounts.general_ledger import process_gl_map
|
||||||
@@ -237,41 +227,6 @@ class StockEntry(StockController):
|
|||||||
self.reset_default_field_value("from_warehouse", "items", "s_warehouse")
|
self.reset_default_field_value("from_warehouse", "items", "s_warehouse")
|
||||||
self.reset_default_field_value("to_warehouse", "items", "t_warehouse")
|
self.reset_default_field_value("to_warehouse", "items", "t_warehouse")
|
||||||
|
|
||||||
def submit(self):
|
|
||||||
if self.is_enqueue_action():
|
|
||||||
frappe.msgprint(
|
|
||||||
_(
|
|
||||||
"The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Entry and revert to the Draft stage"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.queue_action("submit", timeout=2000)
|
|
||||||
else:
|
|
||||||
self._submit()
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
if self.is_enqueue_action():
|
|
||||||
frappe.msgprint(
|
|
||||||
_(
|
|
||||||
"The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Entry and revert to the Submitted stage"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.queue_action("cancel", timeout=2000)
|
|
||||||
else:
|
|
||||||
self._cancel()
|
|
||||||
|
|
||||||
def is_enqueue_action(self, force=False) -> bool:
|
|
||||||
if force:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if frappe.flags.in_test:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# If line items are more than 100 or record is older than 6 months
|
|
||||||
if len(self.items) > 50 or month_diff(nowdate(), self.posting_date) > 6:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.validate_closed_subcontracting_order()
|
self.validate_closed_subcontracting_order()
|
||||||
self.make_bundle_using_old_serial_batch_fields()
|
self.make_bundle_using_old_serial_batch_fields()
|
||||||
|
|||||||
@@ -1633,36 +1633,6 @@ class TestStockEntry(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertRaises(frappe.ValidationError, sr_doc.submit)
|
self.assertRaises(frappe.ValidationError, sr_doc.submit)
|
||||||
|
|
||||||
def test_enqueue_action(self):
|
|
||||||
frappe.flags.in_test = False
|
|
||||||
item_code = "Test Enqueue Item - 001"
|
|
||||||
create_item(item_code=item_code, is_stock_item=1, valuation_rate=10)
|
|
||||||
|
|
||||||
doc = make_stock_entry(
|
|
||||||
item_code=item_code,
|
|
||||||
posting_date=add_to_date(today(), months=-7),
|
|
||||||
posting_time="00:00:00",
|
|
||||||
purpose="Material Receipt",
|
|
||||||
qty=10,
|
|
||||||
to_warehouse="_Test Warehouse - _TC",
|
|
||||||
do_not_submit=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertTrue(doc.is_enqueue_action())
|
|
||||||
|
|
||||||
doc = make_stock_entry(
|
|
||||||
item_code=item_code,
|
|
||||||
posting_date=today(),
|
|
||||||
posting_time="00:00:00",
|
|
||||||
purpose="Material Receipt",
|
|
||||||
qty=10,
|
|
||||||
to_warehouse="_Test Warehouse - _TC",
|
|
||||||
do_not_submit=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertFalse(doc.is_enqueue_action())
|
|
||||||
frappe.flags.in_test = True
|
|
||||||
|
|
||||||
def test_negative_batch(self):
|
def test_negative_batch(self):
|
||||||
item_code = "Test Negative Batch Item - 001"
|
item_code = "Test Negative Batch Item - 001"
|
||||||
make_item(
|
make_item(
|
||||||
|
|||||||
@@ -1061,6 +1061,77 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertTrue(frappe.db.get_value("Purchase Receipt", {"subcontracting_receipt": scr.name}))
|
self.assertTrue(frappe.db.get_value("Purchase Receipt", {"subcontracting_receipt": scr.name}))
|
||||||
|
|
||||||
|
def test_use_serial_batch_fields_for_subcontracting_receipt(self):
|
||||||
|
fg_item = make_item(
|
||||||
|
"Test Subcontracted Item With Batch No",
|
||||||
|
properties={
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "BATCH-BNGS-.####",
|
||||||
|
"is_sub_contracted_item": 1,
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
make_item(
|
||||||
|
"Test Subcontracted Item With Batch No Service Item 1",
|
||||||
|
properties={"is_stock_item": 0},
|
||||||
|
)
|
||||||
|
|
||||||
|
make_bom(
|
||||||
|
item=fg_item,
|
||||||
|
raw_materials=[
|
||||||
|
make_item(
|
||||||
|
"Test Subcontracted Item With Batch No RM Item 1",
|
||||||
|
properties={
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "BATCH-RM-BNGS-.####",
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
service_items = [
|
||||||
|
{
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"item_code": "Test Subcontracted Item With Batch No Service Item 1",
|
||||||
|
"qty": 1,
|
||||||
|
"rate": 100,
|
||||||
|
"fg_item": fg_item,
|
||||||
|
"fg_item_qty": 1,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
sco = get_subcontracting_order(service_items=service_items)
|
||||||
|
rm_items = get_rm_items(sco.supplied_items)
|
||||||
|
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||||
|
make_stock_transfer_entry(
|
||||||
|
sco_no=sco.name,
|
||||||
|
rm_items=rm_items,
|
||||||
|
itemwise_details=copy.deepcopy(itemwise_details),
|
||||||
|
)
|
||||||
|
|
||||||
|
batch_no = "BATCH-BNGS-0001"
|
||||||
|
if not frappe.db.exists("Batch", batch_no):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Batch",
|
||||||
|
"batch_id": batch_no,
|
||||||
|
"item": fg_item,
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
scr = make_subcontracting_receipt(sco.name)
|
||||||
|
self.assertFalse(scr.items[0].serial_and_batch_bundle)
|
||||||
|
scr.items[0].use_serial_batch_fields = 1
|
||||||
|
scr.items[0].batch_no = batch_no
|
||||||
|
|
||||||
|
scr.save()
|
||||||
|
scr.submit()
|
||||||
|
scr.reload()
|
||||||
|
self.assertTrue(scr.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
|
||||||
def make_return_subcontracting_receipt(**args):
|
def make_return_subcontracting_receipt(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
Reference in New Issue
Block a user