diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index fef3b569ed2..5e17881b6c1 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -94,10 +94,13 @@ class BankTransaction(Document):
pe.append(reference)
def update_allocated_amount(self):
- self.allocated_amount = (
+ allocated_amount = (
sum(p.allocated_amount for p in self.payment_entries) if self.payment_entries else 0.0
)
- self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - self.allocated_amount
+ unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - allocated_amount
+
+ self.allocated_amount = flt(allocated_amount, self.precision("allocated_amount"))
+ self.unallocated_amount = flt(unallocated_amount, self.precision("unallocated_amount"))
def before_submit(self):
self.allocate_payment_entries()
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 7cda11addc2..da3c2a4825f 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -14,23 +14,8 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
super.setup(doc);
}
company() {
+ super.company();
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
-
- let me = this;
- if (this.frm.doc.company) {
- frappe.call({
- method:
- "erpnext.accounts.party.get_party_account",
- args: {
- party_type: 'Customer',
- party: this.frm.doc.customer,
- company: this.frm.doc.company
- },
- callback: (response) => {
- if (response) me.frm.set_value("debit_to", response.message);
- },
- });
- }
}
onload() {
var me = this;
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index eafce6d3266..92d9755897c 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -83,7 +83,10 @@ class ReceivablePayableReport(object):
self.skip_total_row = 1
if self.filters.get("in_party_currency"):
- self.skip_total_row = 1
+ if self.filters.get("party") and len(self.filters.get("party")) == 1:
+ self.skip_total_row = 0
+ else:
+ self.skip_total_row = 1
def get_data(self):
self.get_ple_entries()
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
index ce22d7566c1..94457d59981 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
@@ -350,7 +350,13 @@ def get_conditions(filters, additional_conditions=None):
and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
if filters.get("warehouse"):
- conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
+ if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"):
+ lft, rgt = frappe.db.get_all(
+ "Warehouse", filters={"name": filters.get("warehouse")}, fields=["lft", "rgt"], as_list=True
+ )[0]
+ conditions += f"and ifnull(`tabSales Invoice Item`.warehouse, '') in (select name from `tabWarehouse` where lft > {lft} and rgt < {rgt}) "
+ else:
+ conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
if filters.get("brand"):
conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s"""
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 0f087d48541..28cca976f86 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -216,6 +216,18 @@ class AccountsController(TransactionBase):
)
)
+ if self.get("is_return") and self.get("return_against"):
+ document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
+ frappe.msgprint(
+ _(
+ "{0} will be treated as a standalone {0}. Post creation use {1} tool to reconcile against {2}."
+ ).format(
+ document_type,
+ get_link_to_form("Payment Reconciliation", "Payment Reconciliation"),
+ get_link_to_form(self.doctype, self.get("return_against")),
+ )
+ )
+
pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
self.set_advances()
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 0e039b9d1b7..c337f0dcf69 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -7,7 +7,7 @@ from typing import List, Tuple
import frappe
from frappe import _, bold
-from frappe.utils import cint, flt, get_link_to_form, getdate
+from frappe.utils import cint, cstr, flt, get_link_to_form, getdate
import erpnext
from erpnext.accounts.general_ledger import (
@@ -174,13 +174,16 @@ class StockController(AccountsController):
table_name = "stock_items"
for row in self.get(table_name):
+ if row.serial_and_batch_bundle and (row.serial_no or row.batch_no):
+ self.validate_serial_nos_and_batches_with_bundle(row)
+
if not row.serial_no and not row.batch_no and not row.get("rejected_serial_no"):
continue
if not row.use_serial_batch_fields and (
row.serial_no or row.batch_no or row.get("rejected_serial_no")
):
- frappe.throw(_("Please enable Use Old Serial / Batch Fields to make_bundle"))
+ row.use_serial_batch_fields = 1
if row.use_serial_batch_fields and (
not row.serial_and_batch_bundle and not row.get("rejected_serial_and_batch_bundle")
@@ -232,6 +235,41 @@ class StockController(AccountsController):
}
)
+ def validate_serial_nos_and_batches_with_bundle(self, row):
+ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+ throw_error = False
+ if row.serial_no:
+ serial_nos = frappe.get_all(
+ "Serial and Batch Entry", fields=["serial_no"], filters={"parent": row.serial_and_batch_bundle}
+ )
+ serial_nos = sorted([cstr(d.serial_no) for d in serial_nos])
+ parsed_serial_nos = get_serial_nos(row.serial_no)
+
+ if len(serial_nos) != len(parsed_serial_nos):
+ throw_error = True
+ elif serial_nos != parsed_serial_nos:
+ for serial_no in serial_nos:
+ if serial_no not in parsed_serial_nos:
+ throw_error = True
+ break
+
+ elif row.batch_no:
+ batches = frappe.get_all(
+ "Serial and Batch Entry", fields=["batch_no"], filters={"parent": row.serial_and_batch_bundle}
+ )
+ batches = sorted([d.batch_no for d in batches])
+
+ if batches != [row.batch_no]:
+ throw_error = True
+
+ if throw_error:
+ frappe.throw(
+ _(
+ "At row {0}: Serial and Batch Bundle {1} has already created. Please remove the values from the serial no or batch no fields."
+ ).format(row.idx, row.serial_and_batch_bundle)
+ )
+
def set_use_serial_batch_fields(self):
if frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields"):
for row in self.items:
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index 17a2b07daa3..eac35b0d39f 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -539,6 +539,10 @@ class SubcontractingController(StockController):
def __add_supplied_item(self, item_row, bom_item, qty):
bom_item.conversion_factor = item_row.conversion_factor
rm_obj = self.append(self.raw_material_table, bom_item)
+ if rm_obj.get("qty"):
+ # Qty field not exists
+ rm_obj.qty = 0.0
+
rm_obj.reference_name = item_row.name
if self.doctype == self.subcontract_data.order_doctype:
diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py
index 47762ac4cfd..95a7bcb3982 100644
--- a/erpnext/controllers/tests/test_subcontracting_controller.py
+++ b/erpnext/controllers/tests/test_subcontracting_controller.py
@@ -401,7 +401,7 @@ class TestSubcontractingController(FrappeTestCase):
{
"main_item_code": "Subcontracted Item SA4",
"item_code": "Subcontracted SRM Item 3",
- "qty": 1.0,
+ "qty": 3.0,
"rate": 100.0,
"stock_uom": "Nos",
"warehouse": "_Test Warehouse - _TC",
@@ -914,12 +914,6 @@ def update_item_details(child_row, details):
else child_row.get("consumed_qty")
)
- if child_row.serial_no:
- details.serial_no.extend(get_serial_nos(child_row.serial_no))
-
- if child_row.batch_no:
- details.batch_no[child_row.batch_no] += child_row.get("qty") or child_row.get("consumed_qty")
-
if child_row.serial_and_batch_bundle:
doc = frappe.get_doc("Serial and Batch Bundle", child_row.serial_and_batch_bundle)
for row in doc.get("entries"):
@@ -928,6 +922,12 @@ def update_item_details(child_row, details):
if row.batch_no:
details.batch_no[row.batch_no] += row.qty * (-1 if doc.type_of_transaction == "Outward" else 1)
+ else:
+ if child_row.serial_no:
+ details.serial_no.extend(get_serial_nos(child_row.serial_no))
+
+ if child_row.batch_no:
+ details.batch_no[child_row.batch_no] += child_row.get("qty") or child_row.get("consumed_qty")
def make_stock_transfer_entry(**args):
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
index f8f336a6e80..e406b357b75 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
@@ -41,11 +41,49 @@ class BlanketOrder(Document):
def validate(self):
self.validate_dates()
self.validate_duplicate_items()
+ self.set_party_item_code()
def validate_dates(self):
if getdate(self.from_date) > getdate(self.to_date):
frappe.throw(_("From date cannot be greater than To date"))
+ def set_party_item_code(self):
+ item_ref = {}
+ if self.blanket_order_type == "Selling":
+ item_ref = self.get_customer_items_ref()
+ else:
+ item_ref = self.get_supplier_items_ref()
+
+ if not item_ref:
+ return
+
+ for row in self.items:
+ row.party_item_code = item_ref.get(row.item_code)
+
+ def get_customer_items_ref(self):
+ items = [d.item_code for d in self.items]
+
+ return frappe._dict(
+ frappe.get_all(
+ "Item Customer Detail",
+ filters={"parent": ("in", items), "customer_name": self.customer},
+ fields=["parent", "ref_code"],
+ as_list=True,
+ )
+ )
+
+ def get_supplier_items_ref(self):
+ items = [d.item_code for d in self.items]
+
+ return frappe._dict(
+ frappe.get_all(
+ "Item Supplier",
+ filters={"parent": ("in", items), "supplier": self.supplier},
+ fields=["parent", "supplier_part_no"],
+ as_list=True,
+ )
+ )
+
def validate_duplicate_items(self):
item_list = []
for item in self.items:
diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
index e9fc25b5bcb..3f3b6f092c0 100644
--- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
@@ -5,6 +5,7 @@ from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_months, today
from erpnext import get_company_currency
+from erpnext.stock.doctype.item.test_item import make_item
from .blanket_order import make_order
@@ -90,6 +91,30 @@ class TestBlanketOrder(FrappeTestCase):
frappe.db.set_single_value("Buying Settings", "blanket_order_allowance", 10)
po.submit()
+ def test_party_item_code(self):
+ item_doc = make_item("_Test Item 1 for Blanket Order")
+ item_code = item_doc.name
+
+ customer = "_Test Customer"
+ supplier = "_Test Supplier"
+
+ if not frappe.db.exists(
+ "Item Customer Detail", {"customer_name": customer, "parent": item_code}
+ ):
+ item_doc.append("customer_items", {"customer_name": customer, "ref_code": "CUST-REF-1"})
+ item_doc.save()
+
+ if not frappe.db.exists("Item Supplier", {"supplier": supplier, "parent": item_code}):
+ item_doc.append("supplier_items", {"supplier": supplier, "supplier_part_no": "SUPP-PART-1"})
+ item_doc.save()
+
+ # Blanket Order for Selling
+ bo = make_blanket_order(blanket_order_type="Selling", customer=customer, item_code=item_code)
+ self.assertEqual(bo.items[0].party_item_code, "CUST-REF-1")
+
+ bo = make_blanket_order(blanket_order_type="Purchasing", supplier=supplier, item_code=item_code)
+ self.assertEqual(bo.items[0].party_item_code, "SUPP-PART-1")
+
def make_blanket_order(**args):
args = frappe._dict(args)
diff --git a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json
index 977ad547f55..aa7831fd6b8 100644
--- a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json
+++ b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"creation": "2018-05-24 07:20:04.255236",
"doctype": "DocType",
"editable_grid": 1,
@@ -6,6 +7,7 @@
"field_order": [
"item_code",
"item_name",
+ "party_item_code",
"column_break_3",
"qty",
"rate",
@@ -62,10 +64,17 @@
"fieldname": "terms_and_conditions",
"fieldtype": "Text",
"label": "Terms and Conditions"
+ },
+ {
+ "fieldname": "party_item_code",
+ "fieldtype": "Data",
+ "label": "Party Item Code",
+ "read_only": 1
}
],
"istable": 1,
- "modified": "2019-11-18 19:37:46.245878",
+ "links": [],
+ "modified": "2024-02-14 18:25:26.479672",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Blanket Order Item",
@@ -74,5 +83,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py
index 068c2e91182..316d294eaf7 100644
--- a/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py
+++ b/erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.py
@@ -20,6 +20,7 @@ class BlanketOrderItem(Document):
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
+ party_item_code: DF.Data | None
qty: DF.Float
rate: DF.Currency
terms_and_conditions: DF.Text | None
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index b3d301d988a..1d0d47ec3d3 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -368,6 +368,7 @@ erpnext.buying = {
let update_values = {
"serial_and_batch_bundle": r.name,
+ "use_serial_batch_fields": 0,
"qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
}
@@ -408,6 +409,7 @@ erpnext.buying = {
let update_values = {
"serial_and_batch_bundle": r.name,
+ "use_serial_batch_fields": 0,
"rejected_qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
}
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index d08c36c1d0d..c0298a4f14e 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -145,6 +145,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
});
}
+ if(this.frm.fields_dict['items'].grid.get_field('batch_no')) {
+ this.frm.set_query('batch_no', 'items', function(doc, cdt, cdn) {
+ return me.set_query_for_batch(doc, cdt, cdn);
+ });
+ }
+
if(
this.frm.docstatus < 2
&& this.frm.fields_dict["payment_terms_template"]
@@ -1633,18 +1639,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
return item_list;
}
- items_delete() {
- this.update_localstorage_scanned_data();
- }
-
- update_localstorage_scanned_data() {
- let doctypes = ["Sales Invoice", "Purchase Invoice", "Delivery Note", "Purchase Receipt"];
- if (this.frm.is_new() && doctypes.includes(this.frm.doc.doctype)) {
- const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
- barcode_scanner.update_localstorage_scanned_data();
- }
- }
-
_set_values_for_item_list(children) {
const items_rule_dict = {};
diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js
index 4bb78433ae6..a957530ec8b 100644
--- a/erpnext/public/js/utils/sales_common.js
+++ b/erpnext/public/js/utils/sales_common.js
@@ -339,6 +339,7 @@ erpnext.sales_common = {
frappe.model.set_value(item.doctype, item.name, {
"serial_and_batch_bundle": r.name,
+ "use_serial_batch_fields": 0,
"qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
});
}
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 80ade7086c0..fccaf88c719 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -71,6 +71,10 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
let warehouse = this.item?.type_of_transaction === "Outward" ?
(this.item.warehouse || this.item.s_warehouse) : "";
+ if (this.frm.doc.doctype === 'Stock Entry') {
+ warehouse = this.item.s_warehouse || this.item.t_warehouse;
+ }
+
if (!warehouse && this.frm.doc.doctype === 'Stock Reconciliation') {
warehouse = this.get_warehouse();
}
@@ -367,19 +371,11 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
label: __('Batch No'),
in_list_view: 1,
get_query: () => {
- if (this.item.type_of_transaction !== "Outward") {
- return {
- filters: {
- 'item': this.item.item_code,
- }
- }
- } else {
- return {
- query : "erpnext.controllers.queries.get_batch_no",
- filters: {
- 'item_code': this.item.item_code,
- 'warehouse': this.get_warehouse()
- }
+ return {
+ query : "erpnext.controllers.queries.get_batch_no",
+ filters: {
+ 'item_code': this.item.item_code,
+ 'warehouse': this.item.s_warehouse || this.item.t_warehouse,
}
}
},
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 77a3d6d97f0..1b40f2b4899 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -250,7 +250,7 @@ frappe.ui.form.on('Material Request', {
fields: [
{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
options:"BOM", reqd: 1, get_query: function() {
- return {filters: { docstatus:1 }};
+ return {filters: { docstatus:1, "is_active": 1 }};
}},
{"fieldname":"warehouse", "fieldtype":"Link", "label":__("For Warehouse"),
options:"Warehouse", reqd: 1},
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
index 056cd5cc99a..3a5daa1bb73 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.js
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -330,6 +330,7 @@ frappe.ui.form.on('Pick List Item', {
let qty = Math.abs(r.total_qty);
frappe.model.set_value(item.doctype, item.name, {
"serial_and_batch_bundle": r.name,
+ "use_serial_batch_fields": 0,
"qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
});
}
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 0e1f8d78b84..8a1f79d4a27 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -73,6 +73,10 @@ class PickList(Document):
self.update_status()
self.set_item_locations()
+ if self.get("locations"):
+ self.validate_sales_order_percentage()
+
+ def validate_sales_order_percentage(self):
# set percentage picked in SO
for location in self.get("locations"):
if (
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 2b18507a5f8..e906d4986bd 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -2280,6 +2280,30 @@ class TestPurchaseReceipt(FrappeTestCase):
serial_no_status = frappe.db.get_value("Serial No", sn, "status")
self.assertTrue(serial_no_status != "Active")
+ def test_auto_set_batch_based_on_bundle(self):
+ item_code = make_item(
+ "_Test Auto Set Batch Based on Bundle",
+ properties={
+ "has_batch_no": 1,
+ "batch_number_series": "BATCH-BNU-TASBBB-.#####",
+ "create_new_batch": 1,
+ },
+ ).name
+
+ frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
+
+ pr = make_purchase_receipt(
+ item_code=item_code,
+ qty=5,
+ rate=100,
+ )
+
+ self.assertTrue(pr.items[0].batch_no)
+ batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)
+ self.assertEqual(pr.items[0].batch_no, batch_no)
+
+ frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
+
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index 2715324ad0b..4660fb0f169 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -299,9 +299,20 @@ def repost(doc):
doc.log_error("Unable to repost item valuation")
message = frappe.message_log.pop() if frappe.message_log else ""
+ if isinstance(message, dict):
+ message = message.get("message")
+
if traceback:
- message += "
" + "Traceback:
" + traceback
- frappe.db.set_value(doc.doctype, doc.name, "error_log", message)
+ message += "
" + "Traceback:
" + traceback
+
+ frappe.db.set_value(
+ doc.doctype,
+ doc.name,
+ {
+ "error_log": message,
+ "status": "Failed",
+ },
+ )
outgoing_email_account = frappe.get_cached_value(
"Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name"
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
index 91b743016bd..1f7bb4d245a 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
@@ -207,13 +207,24 @@ frappe.ui.form.on('Serial and Batch Bundle', {
};
});
- frm.set_query('batch_no', 'entries', () => {
- return {
- filters: {
- item: frm.doc.item_code,
- disabled: 0,
+ frm.set_query('batch_no', 'entries', (doc) => {
+
+ if (doc.type_of_transaction ==="Outward") {
+ return {
+ query : "erpnext.controllers.queries.get_batch_no",
+ filters: {
+ item_code: doc.item_code,
+ warehouse: doc.warehouse,
+ }
}
- };
+ } else {
+ return {
+ filters: {
+ item: doc.item_code,
+ disabled: 0,
+ }
+ };
+ }
});
frm.set_query('warehouse', 'entries', () => {
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 8c762915d40..7f79f04aae9 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -543,7 +543,9 @@ frappe.ui.form.on('Stock Entry', {
let fields = [
{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
- options:"BOM", reqd: 1, get_query: filters()},
+ options:"BOM", reqd: 1, get_query: () => {
+ return {filters: { docstatus:1, "is_active": 1 }};
+ }},
{"fieldname":"source_warehouse", "fieldtype":"Link", "label":__("Source Warehouse"),
options:"Warehouse"},
{"fieldname":"target_warehouse", "fieldtype":"Link", "label":__("Target Warehouse"),
@@ -1178,6 +1180,7 @@ erpnext.stock.select_batch_and_serial_no = (frm, item) => {
if (r) {
frappe.model.set_value(item.doctype, item.name, {
"serial_and_batch_bundle": r.name,
+ "use_serial_batch_fields": 0,
"qty": Math.abs(r.total_qty) / flt(item.conversion_factor || 1, precision("conversion_factor", item))
});
}
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 81be3d1fc11..cbad4723770 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -834,6 +834,7 @@ class StockEntry(StockController):
currency=erpnext.get_company_currency(self.company),
company=self.company,
raise_error_if_no_rate=raise_error_if_no_rate,
+ batch_no=d.batch_no,
serial_and_batch_bundle=d.serial_and_batch_bundle,
)
@@ -862,7 +863,7 @@ class StockEntry(StockController):
if reset_outgoing_rate:
args = self.get_args_for_incoming_rate(d)
rate = get_incoming_rate(args, raise_error_if_no_rate)
- if rate > 0:
+ if rate >= 0:
d.basic_rate = rate
d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
@@ -885,6 +886,8 @@ class StockEntry(StockController):
"allow_zero_valuation": item.allow_zero_valuation_rate,
"serial_and_batch_bundle": item.serial_and_batch_bundle,
"voucher_detail_no": item.name,
+ "batch_no": item.batch_no,
+ "serial_no": item.serial_no,
}
)
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index ce08615ed5c..c69b20b7d97 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -834,6 +834,7 @@ class StockReconciliation(StockController):
if voucher_detail_no != row.name:
continue
+ val_rate = 0.0
current_qty = 0.0
if row.current_serial_and_batch_bundle:
current_qty = self.get_current_qty_for_serial_or_batch(row)
@@ -843,7 +844,6 @@ class StockReconciliation(StockController):
row.warehouse,
self.posting_date,
self.posting_time,
- voucher_no=self.name,
)
current_qty = item_dict.get("qty")
@@ -885,7 +885,7 @@ class StockReconciliation(StockController):
{"voucher_detail_no": row.name, "actual_qty": ("<", 0), "is_cancelled": 0},
"name",
)
- and (not row.current_serial_and_batch_bundle and not row.batch_no)
+ and (not row.current_serial_and_batch_bundle)
):
self.set_current_serial_and_batch_bundle(voucher_detail_no, save=True)
row.reload()
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js
index b00b422a67a..2ec757b2050 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.js
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.js
@@ -91,6 +91,12 @@ frappe.query_reports["Stock Ledger"] = {
"options": "Currency\nFloat",
"default": "Currency"
},
+ {
+ "fieldname": "segregate_serial_batch_bundle",
+ "label": __("Segregate Serial / Batch Bundle"),
+ "fieldtype": "Check",
+ "default": 0
+ }
],
"formatter": function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index 86af9e9a06d..50764357fe6 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -2,6 +2,8 @@
# License: GNU General Public License v3. See license.txt
+import copy
+
import frappe
from frappe import _
from frappe.query_builder.functions import CombineDatetime
@@ -26,6 +28,10 @@ def execute(filters=None):
item_details = get_item_details(items, sl_entries, include_uom)
opening_row = get_opening_balance(filters, columns, sl_entries)
precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
+ bundle_details = {}
+
+ if filters.get("segregate_serial_batch_bundle"):
+ bundle_details = get_serial_batch_bundle_details(sl_entries)
data = []
conversion_factors = []
@@ -45,6 +51,9 @@ def execute(filters=None):
item_detail = item_details[sle.item_code]
sle.update(item_detail)
+ if bundle_info := bundle_details.get(sle.serial_and_batch_bundle):
+ data.extend(get_segregated_bundle_entries(sle, bundle_info))
+ continue
if filters.get("batch_no") or inventory_dimension_filters_applied:
actual_qty += flt(sle.actual_qty, precision)
@@ -76,6 +85,60 @@ def execute(filters=None):
return columns, data
+def get_segregated_bundle_entries(sle, bundle_details):
+ segregated_entries = []
+ qty_before_transaction = sle.qty_after_transaction - sle.actual_qty
+ stock_value_before_transaction = sle.stock_value - sle.stock_value_difference
+
+ for row in bundle_details:
+ new_sle = copy.deepcopy(sle)
+ new_sle.update(row)
+
+ new_sle.update(
+ {
+ "in_out_rate": flt(new_sle.stock_value_difference / row.qty) if row.qty else 0,
+ "in_qty": row.qty if row.qty > 0 else 0,
+ "out_qty": row.qty if row.qty < 0 else 0,
+ "qty_after_transaction": qty_before_transaction + row.qty,
+ "stock_value": stock_value_before_transaction + new_sle.stock_value_difference,
+ "incoming_rate": row.incoming_rate if row.qty > 0 else 0,
+ }
+ )
+
+ qty_before_transaction += row.qty
+ stock_value_before_transaction += new_sle.stock_value_difference
+
+ new_sle.valuation_rate = (
+ stock_value_before_transaction / qty_before_transaction if qty_before_transaction else 0
+ )
+
+ segregated_entries.append(new_sle)
+
+ return segregated_entries
+
+
+def get_serial_batch_bundle_details(sl_entries):
+ bundle_details = []
+ for sle in sl_entries:
+ if sle.serial_and_batch_bundle:
+ bundle_details.append(sle.serial_and_batch_bundle)
+
+ if not bundle_details:
+ return frappe._dict({})
+
+ _bundle_details = frappe._dict({})
+ batch_entries = frappe.get_all(
+ "Serial and Batch Entry",
+ filters={"parent": ("in", bundle_details)},
+ fields=["parent", "qty", "incoming_rate", "stock_value_difference", "batch_no", "serial_no"],
+ order_by="parent, idx",
+ )
+ for entry in batch_entries:
+ _bundle_details.setdefault(entry.parent, []).append(entry)
+
+ return _bundle_details
+
+
def update_available_serial_nos(available_serial_nos, sle):
serial_nos = get_serial_nos(sle.serial_no)
key = (sle.item_code, sle.warehouse)
@@ -256,7 +319,6 @@ def get_columns(filters):
"options": "Serial and Batch Bundle",
"width": 100,
},
- {"label": _("Balance Serial No"), "fieldname": "balance_serial_no", "width": 100},
{
"label": _("Project"),
"fieldname": "project",
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index d8b5b34d449..fc1cca46f66 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -5,7 +5,7 @@ import frappe
from frappe import _, bold
from frappe.model.naming import make_autoname
from frappe.query_builder.functions import CombineDatetime, Sum
-from frappe.utils import cint, flt, get_link_to_form, now, nowtime, today
+from frappe.utils import cint, cstr, flt, get_link_to_form, now, nowtime, today
from erpnext.stock.deprecated_serial_batch import (
DeprecatedBatchNoValuation,
@@ -138,9 +138,17 @@ class SerialBatchBundle:
self.child_doctype, self.sle.voucher_detail_no, "rejected_serial_and_batch_bundle", sn_doc.name
)
else:
- frappe.db.set_value(
- self.child_doctype, self.sle.voucher_detail_no, "serial_and_batch_bundle", sn_doc.name
- )
+ values_to_update = {
+ "serial_and_batch_bundle": sn_doc.name,
+ }
+
+ if frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields"):
+ if sn_doc.has_serial_no:
+ values_to_update["serial_no"] = "\n".join(cstr(d.serial_no) for d in sn_doc.entries)
+ elif sn_doc.has_batch_no and len(sn_doc.entries) == 1:
+ values_to_update["batch_no"] = sn_doc.entries[0].batch_no
+
+ frappe.db.set_value(self.child_doctype, self.sle.voucher_detail_no, values_to_update)
@property
def child_doctype(self):
@@ -905,8 +913,6 @@ class SerialBatchCreation:
self.batches = get_available_batches(kwargs)
def set_auto_serial_batch_entries_for_inward(self):
- print(self.get("serial_nos"))
-
if (self.get("batches") and self.has_batch_no) or (
self.get("serial_nos") and self.has_serial_no
):
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 379320237e6..215b853e6a4 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -899,7 +899,7 @@ class update_entries_after(object):
precision = doc.precision("total_qty")
self.wh_data.qty_after_transaction += flt(doc.total_qty, precision)
- if self.wh_data.qty_after_transaction:
+ if flt(self.wh_data.qty_after_transaction, precision):
self.wh_data.valuation_rate = flt(self.wh_data.stock_value, precision) / flt(
self.wh_data.qty_after_transaction, precision
)
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 54e0ab5acf8..5410e692c41 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -262,7 +262,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
item_code=args.get("item_code"),
)
- in_rate = sn_obj.get_incoming_rate()
+ return sn_obj.get_incoming_rate()
elif item_details and item_details.has_batch_no and args.get("serial_and_batch_bundle"):
args.actual_qty = args.qty
@@ -272,23 +272,33 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
item_code=args.get("item_code"),
)
- in_rate = batch_obj.get_incoming_rate()
+ return batch_obj.get_incoming_rate()
elif (args.get("serial_no") or "").strip() and not args.get("serial_and_batch_bundle"):
- in_rate = get_avg_purchase_rate(args.get("serial_no"))
+ args.actual_qty = args.qty
+ args.serial_nos = get_serial_nos_data(args.get("serial_no"))
+
+ sn_obj = SerialNoValuation(
+ sle=args, warehouse=args.get("warehouse"), item_code=args.get("item_code")
+ )
+
+ return sn_obj.get_incoming_rate()
elif (
args.get("batch_no")
and frappe.db.get_value("Batch", args.get("batch_no"), "use_batchwise_valuation", cache=True)
and not args.get("serial_and_batch_bundle")
):
- in_rate = get_batch_incoming_rate(
- item_code=args.get("item_code"),
+
+ args.actual_qty = args.qty
+ args.batch_nos = frappe._dict({args.batch_no: args})
+
+ batch_obj = BatchNoValuation(
+ sle=args,
warehouse=args.get("warehouse"),
- batch_no=args.get("batch_no"),
- posting_date=args.get("posting_date"),
- posting_time=args.get("posting_time"),
+ item_code=args.get("item_code"),
)
+ return batch_obj.get_incoming_rate()
else:
valuation_method = get_valuation_method(args.get("item_code"))
previous_sle = get_previous_sle(args)
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index c03fb3ea1ff..cb18579c049 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -113,8 +113,8 @@ class Issue(Document):
"reference_name": self.name,
}
)
- communication.ignore_permissions = True
- communication.ignore_mandatory = True
+ communication.flags.ignore_permissions = True
+ communication.flags.ignore_mandatory = True
communication.save()
@frappe.whitelist()