Merge pull request #33759 from rohitwaghchaure/fix-incorrect-actual-qty-for-packed-item
fix: incorrect actual qty for the packed item
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 1ce780e..57feaa0 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -687,11 +687,21 @@
def apply_pricing_rule_for_free_items(doc, pricing_rule_args):
if pricing_rule_args:
- items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item)
+ args = {(d["item_code"], d["pricing_rules"]): d for d in pricing_rule_args}
- for args in pricing_rule_args:
- if not items or (args.get("item_code"), args.get("pricing_rules")) not in items:
- doc.append("items", args)
+ for item in doc.items:
+ if not item.is_free_item:
+ continue
+
+ free_item_data = args.get((item.item_code, item.pricing_rules))
+ if free_item_data:
+ free_item_data.pop("item_name")
+ free_item_data.pop("description")
+ item.update(free_item_data)
+ args.pop((item.item_code, item.pricing_rules))
+
+ for free_item in args.values():
+ doc.append("items", free_item)
def get_pricing_rule_items(pr_doc, other_items=False) -> list:
diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json
index ce8c0c3..46b430c 100644
--- a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json
+++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json
@@ -1,6 +1,6 @@
{
"actions": [],
- "autoname": "autoincrement",
+ "autoname": "hash",
"creation": "2022-09-13 16:18:59.404842",
"doctype": "DocType",
"editable_grid": 1,
@@ -36,11 +36,11 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-09-13 23:40:41.479208",
+ "modified": "2023-01-13 13:40:41.479208",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withheld Vouchers",
- "naming_rule": "Autoincrement",
+ "naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 572d9d3..f0360b2 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -889,6 +889,11 @@
self.assertEqual(po.status, "Completed")
self.assertEqual(mr.status, "Received")
+ def test_variant_item_po(self):
+ po = create_purchase_order(item_code="_Test Variant Item", qty=1, rate=100, do_not_save=1)
+
+ self.assertRaises(frappe.ValidationError, po.save)
+
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
@@ -994,8 +999,8 @@
},
)
- po.set_missing_values()
if not args.do_not_save:
+ po.set_missing_values()
po.insert()
if not args.do_not_submit:
if po.is_subcontracted:
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index d497297..dd2a670 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -58,7 +58,7 @@
"eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1",
],
["Cancelled", "eval:self.docstatus==2"],
- ["Closed", "eval:self.status=='Closed'"],
+ ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
["On Hold", "eval:self.status=='On Hold'"],
],
"Purchase Order": [
@@ -79,7 +79,7 @@
["Delivered", "eval:self.status=='Delivered'"],
["Cancelled", "eval:self.docstatus==2"],
["On Hold", "eval:self.status=='On Hold'"],
- ["Closed", "eval:self.status=='Closed'"],
+ ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
],
"Delivery Note": [
["Draft", None],
@@ -87,7 +87,7 @@
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
- ["Closed", "eval:self.status=='Closed'"],
+ ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
],
"Purchase Receipt": [
["Draft", None],
@@ -95,7 +95,7 @@
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
- ["Closed", "eval:self.status=='Closed'"],
+ ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
],
"Material Request": [
["Draft", None],
diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py
index 828c655..bbe04d5 100644
--- a/erpnext/e_commerce/doctype/website_item/test_website_item.py
+++ b/erpnext/e_commerce/doctype/website_item/test_website_item.py
@@ -174,7 +174,10 @@
# Website Item Portal Tests Begin
def test_website_item_breadcrumbs(self):
- "Check if breadcrumbs include homepage, product listing navigation page, parent item group(s) and item group."
+ """
+ Check if breadcrumbs include homepage, product listing navigation page,
+ parent item group(s) and item group
+ """
from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups
item_code = "Test Breadcrumb Item"
@@ -197,7 +200,7 @@
breadcrumbs = get_parent_item_groups(item.item_group)
self.assertEqual(breadcrumbs[0]["name"], "Home")
- self.assertEqual(breadcrumbs[1]["name"], "Shop by Category")
+ self.assertEqual(breadcrumbs[1]["name"], "All Products")
self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group
self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1")
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 4dd8205..4304193 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -65,7 +65,21 @@
});
},
- onload_post_render(frm) {
+ validate: function(frm) {
+ if (frm.doc.fg_based_operating_cost && frm.doc.with_operations) {
+ frappe.throw({message: __("Please check either with operations or FG Based Operating Cost."), title: __("Mandatory")});
+ }
+ },
+
+ with_operations: function(frm) {
+ frm.set_df_property("fg_based_operating_cost", "hidden", frm.doc.with_operations ? 1 : 0);
+ },
+
+ fg_based_operating_cost: function(frm) {
+ frm.set_df_property("with_operations", "hidden", frm.doc.fg_based_operating_cost ? 1 : 0);
+ },
+
+ onload_post_render: function(frm) {
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
},
@@ -532,18 +546,25 @@
};
erpnext.bom.calculate_op_cost = function(doc) {
- var op = doc.operations || [];
doc.operating_cost = 0.0;
doc.base_operating_cost = 0.0;
- for(var i=0;i<op.length;i++) {
- var operating_cost = flt(flt(op[i].hour_rate) * flt(op[i].time_in_mins) / 60, 2);
- var base_operating_cost = flt(operating_cost * doc.conversion_rate, 2);
- frappe.model.set_value('BOM Operation',op[i].name, "operating_cost", operating_cost);
- frappe.model.set_value('BOM Operation',op[i].name, "base_operating_cost", base_operating_cost);
+ if(doc.with_operations) {
+ doc.operations.forEach((item) => {
+ let operating_cost = flt(flt(item.hour_rate) * flt(item.time_in_mins) / 60, 2);
+ let base_operating_cost = flt(operating_cost * doc.conversion_rate, 2);
+ frappe.model.set_value('BOM Operation',item.name, {
+ "operating_cost": operating_cost,
+ "base_operating_cost": base_operating_cost
+ });
- doc.operating_cost += operating_cost;
- doc.base_operating_cost += base_operating_cost;
+ doc.operating_cost += operating_cost;
+ doc.base_operating_cost += base_operating_cost;
+ });
+ } else if(doc.fg_based_operating_cost) {
+ let total_operating_cost = doc.quantity * flt(doc.operating_cost_per_bom_quantity);
+ doc.operating_cost = total_operating_cost;
+ doc.base_operating_cost = flt(total_operating_cost * doc.conversion_rate, 2);
}
refresh_field(['operating_cost', 'base_operating_cost']);
};
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index c31b69f..c2b331f 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -33,6 +33,9 @@
"column_break_23",
"transfer_material_against",
"routing",
+ "fg_based_operating_cost",
+ "fg_based_section_section",
+ "operating_cost_per_bom_quantity",
"operations_section",
"operations",
"materials_section",
@@ -575,7 +578,26 @@
{
"fieldname": "scrap_items_section",
"fieldtype": "Section Break",
+ "hide_border": 1,
"label": "Scrap Items"
+ },
+ {
+ "default": "0",
+ "fieldname": "fg_based_operating_cost",
+ "fieldtype": "Check",
+ "label": "FG based Operating Cost"
+ },
+ {
+ "depends_on": "fg_based_operating_cost",
+ "fieldname": "fg_based_section_section",
+ "fieldtype": "Section Break",
+ "label": "FG Based Operating Cost Section"
+ },
+ {
+ "depends_on": "fg_based_operating_cost",
+ "fieldname": "operating_cost_per_bom_quantity",
+ "fieldtype": "Currency",
+ "label": "Operating Cost Per BOM Quantity"
}
],
"icon": "fa fa-sitemap",
@@ -583,7 +605,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2023-01-03 18:42:27.732107",
+ "modified": "2023-01-10 07:47:08.652616",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 53af28d..8ab79e6 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -614,18 +614,26 @@
"""Update workstation rate and calculates totals"""
self.operating_cost = 0
self.base_operating_cost = 0
- for d in self.get("operations"):
- if d.workstation:
- self.update_rate_and_time(d, update_hour_rate)
+ if self.get("with_operations"):
+ for d in self.get("operations"):
+ if d.workstation:
+ self.update_rate_and_time(d, update_hour_rate)
- operating_cost = d.operating_cost
- base_operating_cost = d.base_operating_cost
- if d.set_cost_based_on_bom_qty:
- operating_cost = flt(d.cost_per_unit) * flt(self.quantity)
- base_operating_cost = flt(d.base_cost_per_unit) * flt(self.quantity)
+ operating_cost = d.operating_cost
+ base_operating_cost = d.base_operating_cost
+ if d.set_cost_based_on_bom_qty:
+ operating_cost = flt(d.cost_per_unit) * flt(self.quantity)
+ base_operating_cost = flt(d.base_cost_per_unit) * flt(self.quantity)
- self.operating_cost += flt(operating_cost)
- self.base_operating_cost += flt(base_operating_cost)
+ self.operating_cost += flt(operating_cost)
+ self.base_operating_cost += flt(base_operating_cost)
+
+ elif self.get("fg_based_operating_cost"):
+ total_operating_cost = flt(self.get("quantity")) * flt(
+ self.get("operating_cost_per_bom_quantity")
+ )
+ self.operating_cost = total_operating_cost
+ self.base_operating_cost = flt(total_operating_cost * self.conversion_rate, 2)
def update_rate_and_time(self, row, update_hour_rate=False):
if not row.hour_rate or update_hour_rate:
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 16f5c79..d60feb2 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -202,6 +202,33 @@
self.assertEqual(bom.items[0].rate, 20)
+ def test_bom_cost_with_fg_based_operating_cost(self):
+ bom = frappe.copy_doc(test_records[4])
+ bom.insert()
+
+ raw_material_cost = 0.0
+ op_cost = 0.0
+
+ op_cost = bom.quantity * bom.operating_cost_per_bom_quantity
+
+ for row in bom.items:
+ raw_material_cost += row.amount
+
+ base_raw_material_cost = raw_material_cost * flt(
+ bom.conversion_rate, bom.precision("conversion_rate")
+ )
+ base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
+
+ # test amounts in selected currency, almostEqual checks for 7 digits by default
+ self.assertAlmostEqual(bom.operating_cost, op_cost)
+ self.assertAlmostEqual(bom.raw_material_cost, raw_material_cost)
+ self.assertAlmostEqual(bom.total_cost, raw_material_cost + op_cost)
+
+ # test amounts in selected currency
+ self.assertAlmostEqual(bom.base_operating_cost, base_op_cost)
+ self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost)
+ self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
+
def test_subcontractor_sourced_item(self):
item_code = "_Test Subcontracted FG Item 1"
set_backflush_based_on("Material Transferred for Subcontract")
diff --git a/erpnext/manufacturing/doctype/bom/test_records.json b/erpnext/manufacturing/doctype/bom/test_records.json
index 507d319..e9cbdfe 100644
--- a/erpnext/manufacturing/doctype/bom/test_records.json
+++ b/erpnext/manufacturing/doctype/bom/test_records.json
@@ -162,5 +162,31 @@
"item": "_Test Variant Item",
"quantity": 1.0,
"with_operations": 1
+ },
+ {
+ "items": [
+ {
+ "amount": 5000.0,
+ "doctype": "BOM Item",
+ "item_code": "_Test Item",
+ "parentfield": "items",
+ "qty": 2.0,
+ "rate": 3000.0,
+ "uom": "_Test UOM",
+ "stock_uom": "_Test UOM",
+ "source_warehouse": "_Test Warehouse - _TC",
+ "include_item_in_manufacturing": 1
+ }
+ ],
+ "docstatus": 1,
+ "doctype": "BOM",
+ "is_active": 1,
+ "is_default": 1,
+ "currency": "USD",
+ "item": "_Test Variant Item",
+ "quantity": 1.0,
+ "with_operations": 0,
+ "fg_based_operating_cost": 1,
+ "operating_cost_per_bom_quantity": 140
}
]
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 41067d8..6744d16 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -324,3 +324,4 @@
erpnext.patches.v14_0.setup_clear_repost_logs
erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request
erpnext.patches.v14_0.update_entry_type_for_journal_entry
+erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers
diff --git a/erpnext/patches/v14_0/change_autoname_for_tax_withheld_vouchers.py b/erpnext/patches/v14_0/change_autoname_for_tax_withheld_vouchers.py
new file mode 100644
index 0000000..e20ba73
--- /dev/null
+++ b/erpnext/patches/v14_0/change_autoname_for_tax_withheld_vouchers.py
@@ -0,0 +1,12 @@
+import frappe
+
+
+def execute():
+ if (
+ frappe.db.sql(
+ """select data_type FROM information_schema.columns
+ where column_name = 'name' and table_name = 'tabTax Withheld Vouchers'"""
+ )[0][0]
+ == "bigint"
+ ):
+ frappe.db.change_column_type("Tax Withheld Vouchers", "name", "varchar(140)")
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index 2dde542..ce3ae4f 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -80,7 +80,7 @@
if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"):
frappe.throw(
_(
- "Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled."
+ "Cannot complete task {0} as its dependant task {1} are not completed / cancelled."
).format(frappe.bold(self.name), frappe.bold(d.task))
)
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 1f8a5e3..271b563 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -122,24 +122,16 @@
calculate_item_values() {
var me = this;
if (!this.discount_amount_applied) {
- $.each(this.frm.doc["items"] || [], function(i, item) {
+ for (item of this.frm.doc.items || []) {
frappe.model.round_floats_in(item);
item.net_rate = item.rate;
-
- if ((!item.qty) && me.frm.doc.is_return) {
- item.amount = flt(item.rate * -1, precision("amount", item));
- } else if ((!item.qty) && me.frm.doc.is_debit_note) {
- item.amount = flt(item.rate, precision("amount", item));
- } else {
- item.amount = flt(item.rate * item.qty, precision("amount", item));
- }
-
- item.net_amount = item.amount;
+ item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty;
+ item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item));
item.item_tax_amount = 0.0;
item.total_weight = flt(item.weight_per_unit * item.stock_qty);
me.set_in_company_currency(item, ["price_list_rate", "rate", "amount", "net_rate", "net_amount"]);
- });
+ }
}
}
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 5c1c6d1..3a778fa 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1473,6 +1473,7 @@
"parenttype": d.parenttype,
"parent": d.parent,
"pricing_rules": d.pricing_rules,
+ "is_free_item": d.is_free_item,
"warehouse": d.warehouse,
"serial_no": d.serial_no,
"batch_no": d.batch_no,
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index 95bbf84..2fdfcf6 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -148,12 +148,12 @@
def get_parent_item_groups(item_group_name, from_item=False):
- base_nav_page = {"name": _("Shop by Category"), "route": "/shop-by-category"}
+ base_nav_page = {"name": _("All Products"), "route": "/all-products"}
if from_item and frappe.request.environ.get("HTTP_REFERER"):
# base page after 'Home' will vary on Item page
last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0]
- if last_page and last_page in ("shop-by-category", "all-products"):
+ if last_page and last_page == "shop-by-category":
base_nav_page_title = " ".join(last_page.split("-")).title()
base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page}
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 6847c78..903e2af 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -690,6 +690,11 @@
update_delivery_note_status(dn.name, "Closed")
self.assertEqual(frappe.db.get_value("Delivery Note", dn.name, "Status"), "Closed")
+ # Check cancelling closed delivery note
+ dn.load_from_db()
+ dn.cancel()
+ self.assertEqual(dn.status, "Cancelled")
+
def test_dn_billing_status_case1(self):
# SO -> DN -> SI
so = make_sales_order()
diff --git a/erpnext/stock/doctype/item_attribute/item_attribute.py b/erpnext/stock/doctype/item_attribute/item_attribute.py
index 391ff06..ac4c313 100644
--- a/erpnext/stock/doctype/item_attribute/item_attribute.py
+++ b/erpnext/stock/doctype/item_attribute/item_attribute.py
@@ -74,11 +74,10 @@
def validate_duplication(self):
values, abbrs = [], []
for d in self.item_attribute_values:
- d.abbr = d.abbr.upper()
- if d.attribute_value in values:
- frappe.throw(_("{0} must appear only once").format(d.attribute_value))
+ if d.attribute_value.lower() in map(str.lower, values):
+ frappe.throw(_("Attribute value: {0} must appear only once").format(d.attribute_value.title()))
values.append(d.attribute_value)
- if d.abbr in abbrs:
- frappe.throw(_("{0} must appear only once").format(d.abbr))
+ if d.abbr.lower() in map(str.lower, abbrs):
+ frappe.throw(_("Abbreviation: {0} must appear only once").format(d.abbr.title()))
abbrs.append(d.abbr)
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 9c6f4f4..808f19e 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -11,7 +11,7 @@
from frappe.model.document import Document
from frappe.model.mapper import map_child_doc
from frappe.query_builder import Case
-from frappe.query_builder.functions import Locate
+from frappe.query_builder.functions import IfNull, Locate, Sum
from frappe.utils import cint, floor, flt, today
from frappe.utils.nestedset import get_descendants_of
@@ -503,42 +503,30 @@
def get_available_item_locations_for_batched_item(
item_code, from_warehouses, required_qty, company
):
- warehouse_condition = "and warehouse in %(warehouses)s" if from_warehouses else ""
- batch_locations = frappe.db.sql(
- """
- SELECT
- sle.`warehouse`,
- sle.`batch_no`,
- SUM(sle.`actual_qty`) AS `qty`
- FROM
- `tabStock Ledger Entry` sle, `tabBatch` batch
- WHERE
- sle.batch_no = batch.name
- and sle.`item_code`=%(item_code)s
- and sle.`company` = %(company)s
- and batch.disabled = 0
- and sle.is_cancelled=0
- and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
- {warehouse_condition}
- GROUP BY
- sle.`warehouse`,
- sle.`batch_no`,
- sle.`item_code`
- HAVING `qty` > 0
- ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`, sle.`batch_no`, sle.`warehouse`
- """.format(
- warehouse_condition=warehouse_condition
- ),
- { # nosec
- "item_code": item_code,
- "company": company,
- "today": today(),
- "warehouses": from_warehouses,
- },
- as_dict=1,
+ sle = frappe.qb.DocType("Stock Ledger Entry")
+ batch = frappe.qb.DocType("Batch")
+
+ query = (
+ frappe.qb.from_(sle)
+ .from_(batch)
+ .select(sle.warehouse, sle.batch_no, Sum(sle.actual_qty).as_("qty"))
+ .where(
+ (sle.batch_no == batch.name)
+ & (sle.item_code == item_code)
+ & (sle.company == company)
+ & (batch.disabled == 0)
+ & (sle.is_cancelled == 0)
+ & (IfNull(batch.expiry_date, "2200-01-01") > today())
+ )
+ .groupby(sle.warehouse, sle.batch_no, sle.item_code)
+ .having(Sum(sle.actual_qty) > 0)
+ .orderby(IfNull(batch.expiry_date, "2200-01-01"), batch.creation, sle.batch_no, sle.warehouse)
)
- return batch_locations
+ if from_warehouses:
+ query = query.where(sle.warehouse.isin(from_warehouses))
+
+ return query.run(as_dict=True)
def get_available_item_locations_for_serial_and_batched_item(
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 363dc0a..5af1441 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -236,8 +236,10 @@
validate_end_of_life(item.name, item.end_of_life, item.disabled)
- if args.transaction_type == "selling" and cint(item.has_variants):
- throw(_("Item {0} is a template, please select one of its variants").format(item.name))
+ if cint(item.has_variants):
+ msg = f"Item {item.name} is a template, please select one of its variants"
+
+ throw(_(msg), title=_("Template Item Selected"))
elif args.transaction_type == "buying" and args.doctype != "Material Request":
if args.get("is_subcontracted"):
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index e8faa48..f4fd4de 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -262,15 +262,17 @@
def get_gl_entries(self, warehouse_account=None):
from erpnext.accounts.general_ledger import process_gl_map
+ if not erpnext.is_perpetual_inventory_enabled(self.company):
+ return []
+
gl_entries = []
self.make_item_gl_entries(gl_entries, warehouse_account)
return process_gl_map(gl_entries)
def make_item_gl_entries(self, gl_entries, warehouse_account=None):
- if erpnext.is_perpetual_inventory_enabled(self.company):
- stock_rbnb = self.get_company_default("stock_received_but_not_billed")
- expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
+ stock_rbnb = self.get_company_default("stock_received_but_not_billed")
+ expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
warehouse_with_no_account = []