Merge pull request #38962 from s-aga-r/FIX-38781
fix: use `Stock Qty` while getting `POS Reserved Qty`
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json
index c4492be..09912e9 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.json
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json
@@ -23,6 +23,7 @@
"against_voucher_type",
"against_voucher",
"voucher_type",
+ "voucher_subtype",
"voucher_no",
"voucher_detail_no",
"project",
@@ -138,19 +139,19 @@
"options": "DocType"
},
{
- "fieldname": "against",
- "fieldtype": "Text",
- "in_filter": 1,
- "label": "Against",
- "oldfieldname": "against",
- "oldfieldtype": "Text"
+ "fieldname": "against",
+ "fieldtype": "Text",
+ "in_filter": 1,
+ "label": "Against",
+ "oldfieldname": "against",
+ "oldfieldtype": "Text"
},
{
- "fieldname": "against_link",
- "fieldtype": "Dynamic Link",
- "in_filter": 1,
- "label": "Against",
- "options": "against_type"
+ "fieldname": "against_link",
+ "fieldtype": "Dynamic Link",
+ "in_filter": 1,
+ "label": "Against",
+ "options": "against_type"
},
{
"fieldname": "against_voucher_type",
@@ -294,13 +295,18 @@
"fieldtype": "Currency",
"label": "Credit Amount in Transaction Currency",
"options": "transaction_currency"
+ },
+ {
+ "fieldname": "voucher_subtype",
+ "fieldtype": "Small Text",
+ "label": "Voucher Subtype"
}
],
"icon": "fa fa-list",
"idx": 1,
"in_create": 1,
"links": [],
- "modified": "2023-11-08 12:20:23.031733",
+ "modified": "2023-12-18 15:38:14.006208",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GL Entry",
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index f7dd29a..139f526 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -39,6 +39,8 @@
account: DF.Link | None
account_currency: DF.Link | None
against: DF.Text | None
+ against_link: DF.DynamicLink | None
+ against_type: DF.Link | None
against_voucher: DF.DynamicLink | None
against_voucher_type: DF.Link | None
company: DF.Link | None
@@ -66,6 +68,7 @@
transaction_exchange_rate: DF.Float
voucher_detail_no: DF.Data | None
voucher_no: DF.DynamicLink | None
+ voucher_subtype: DF.SmallText | None
voucher_type: DF.Link | None
# end: auto-generated types
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index cebd61a..215d8ec 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -163,6 +163,18 @@
}
})
}, __("Get Items From"));
+
+ if (!this.frm.doc.is_return) {
+ frappe.db.get_single_value("Buying Settings", "maintain_same_rate").then((value) => {
+ if (value) {
+ this.frm.doc.items.forEach((item) => {
+ this.frm.fields_dict.items.grid.update_docfield_property(
+ "rate", "read_only", (item.purchase_receipt && item.pr_detail)
+ );
+ });
+ }
+ });
+ }
}
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted);
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 7cad3ae..9cf4e4f 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -288,7 +288,6 @@
"oldfieldname": "import_rate",
"oldfieldtype": "Currency",
"options": "currency",
- "read_only_depends_on": "eval: (!parent.is_return && doc.purchase_receipt && doc.pr_detail)",
"reqd": 1
},
{
@@ -919,7 +918,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-11-30 16:26:05.629780",
+ "modified": "2023-12-25 22:00:28.043555",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index b45ff60..4054dca 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -200,7 +200,7 @@
"""
select
name as gl_entry, posting_date, account, party_type, party,
- voucher_type, voucher_no, {dimension_fields}
+ voucher_type, voucher_subtype, voucher_no, {dimension_fields}
cost_center, project, {transaction_currency_fields}
against_voucher_type, against_voucher, account_currency,
against_link, against, is_opening, creation {select_fields}
@@ -610,6 +610,12 @@
columns += [
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 120},
{
+ "label": _("Voucher Subtype"),
+ "fieldname": "voucher_subtype",
+ "fieldtype": "Data",
+ "width": 180,
+ },
+ {
"label": _("Voucher No"),
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index d88424b..febad18 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -874,6 +874,7 @@
"project": self.get("project"),
"post_net_value": args.get("post_net_value"),
"voucher_detail_no": args.get("voucher_detail_no"),
+ "voucher_subtype": self.get_voucher_subtype(),
}
)
@@ -929,6 +930,25 @@
return gl_dict
+ def get_voucher_subtype(self):
+ voucher_subtypes = {
+ "Journal Entry": "voucher_type",
+ "Payment Entry": "payment_type",
+ "Stock Entry": "stock_entry_type",
+ "Asset Capitalization": "entry_type",
+ }
+ if self.doctype in voucher_subtypes:
+ return self.get(voucher_subtypes[self.doctype])
+ elif self.doctype == "Purchase Receipt" and self.is_return:
+ return "Purchase Return"
+ elif self.doctype == "Delivery Note" and self.is_return:
+ return "Sales Return"
+ elif (self.doctype == "Sales Invoice" and self.is_return) or self.doctype == "Purchase Invoice":
+ return "Credit Note"
+ elif (self.doctype == "Purchase Invoice" and self.is_return) or self.doctype == "Sales Invoice":
+ return "Debit Note"
+ return self.doctype
+
def get_value_in_transaction_currency(self, account_currency, args, field):
if account_currency == self.get("currency"):
return args.get(field + "_in_account_currency")
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index e8d3542..5083873 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -218,6 +218,7 @@
"options": "\nWork Order\nJob Card"
},
{
+ "default": "1",
"fieldname": "conversion_rate",
"fieldtype": "Float",
"label": "Conversion Rate",
@@ -636,7 +637,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2023-08-07 11:38:08.152294",
+ "modified": "2023-12-26 19:34:08.159312",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index dd102b0..cd92263 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -305,6 +305,8 @@
frappe.throw(__("Select the Warehouse"));
}
+ frm.set_value("consider_minimum_order_qty", 0);
+
if (frm.doc.ignore_existing_ordered_qty) {
frm.events.get_items_for_material_requests(frm);
} else {
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 49386c4..257b60c 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -48,6 +48,7 @@
"material_request_planning",
"include_non_stock_items",
"include_subcontracted_items",
+ "consider_minimum_order_qty",
"include_safety_stock",
"ignore_existing_ordered_qty",
"column_break_25",
@@ -423,13 +424,19 @@
"fieldtype": "Link",
"label": "Sub Assembly Warehouse",
"options": "Warehouse"
+ },
+ {
+ "default": "0",
+ "fieldname": "consider_minimum_order_qty",
+ "fieldtype": "Check",
+ "label": "Consider Minimum Order Qty"
}
],
"icon": "fa fa-calendar",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-11-03 14:08:11.928027",
+ "modified": "2023-12-26 16:31:13.740777",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 4b72a83..2bfd4be 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -67,6 +67,7 @@
combine_items: DF.Check
combine_sub_items: DF.Check
company: DF.Link
+ consider_minimum_order_qty: DF.Check
customer: DF.Link | None
for_warehouse: DF.Link | None
from_date: DF.Date | None
@@ -1211,7 +1212,14 @@
def get_material_request_items(
- row, sales_order, company, ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict
+ doc,
+ row,
+ sales_order,
+ company,
+ ignore_existing_ordered_qty,
+ include_safety_stock,
+ warehouse,
+ bin_dict,
):
total_qty = row["qty"]
@@ -1220,8 +1228,14 @@
required_qty = total_qty
elif total_qty > bin_dict.get("projected_qty", 0):
required_qty = total_qty - bin_dict.get("projected_qty", 0)
- if required_qty > 0 and required_qty < row["min_order_qty"]:
+
+ if (
+ doc.get("consider_minimum_order_qty")
+ and required_qty > 0
+ and required_qty < row["min_order_qty"]
+ ):
required_qty = row["min_order_qty"]
+
item_group_defaults = get_item_group_defaults(row.item_code, company)
if not row["purchase_uom"]:
@@ -1559,6 +1573,7 @@
if details.qty > 0:
items = get_material_request_items(
+ doc,
details,
sales_order,
company,
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index f86725d..cb99b88 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -1499,6 +1499,29 @@
after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
self.assertAlmostEqual(after_qty, before_qty)
+ def test_min_order_qty_in_pp(self):
+ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+ from erpnext.stock.utils import get_or_make_bin
+
+ fg_item = make_item(properties={"is_stock_item": 1}).name
+ rm_item = make_item(properties={"is_stock_item": 1, "min_order_qty": 1000}).name
+
+ rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company")
+
+ make_bom(item=fg_item, raw_materials=[rm_item], source_warehouse="_Test Warehouse - _TC")
+
+ pln = create_production_plan(item_code=fg_item, planned_qty=10, do_not_submit=1)
+
+ pln.for_warehouse = rm_warehouse
+ mr_items = get_items_for_material_requests(pln.as_dict())
+ for d in mr_items:
+ self.assertEqual(d.get("quantity"), 10.0)
+
+ pln.consider_minimum_order_qty = 1
+ mr_items = get_items_for_material_requests(pln.as_dict())
+ for d in mr_items:
+ self.assertEqual(d.get("quantity"), 1000.0)
+
def create_production_plan(**args):
"""
diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js
index 084cca7..b92b02e 100644
--- a/erpnext/public/js/utils/sales_common.js
+++ b/erpnext/public/js/utils/sales_common.js
@@ -184,6 +184,12 @@
refresh_field("incentives",row.name,row.parentfield);
}
+ warehouse(doc, cdt, cdn) {
+ if (doc.docstatus === 0 && doc.is_return && !doc.return_against) {
+ frappe.model.set_value(cdt, cdn, "incoming_rate", 0.0);
+ }
+ }
+
toggle_editable_price_list_rate() {
var df = frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "price_list_rate", this.frm.doc.name);
var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate"));
diff --git a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py
index a58f403..40aa9ac 100644
--- a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py
+++ b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py
@@ -3,11 +3,11 @@
import frappe
-from frappe import _
+from frappe import _, qb
+from frappe.query_builder import Criterion
from erpnext import get_default_company
from erpnext.accounts.party import get_party_details
-from erpnext.stock.get_item_details import get_price_list_rate_for
def execute(filters=None):
@@ -50,6 +50,42 @@
]
+def fetch_item_prices(
+ customer: str = None, price_list: str = None, selling_price_list: str = None, items: list = None
+):
+ price_list_map = frappe._dict()
+ ip = qb.DocType("Item Price")
+ and_conditions = []
+ or_conditions = []
+ if items:
+ and_conditions.append(ip.item_code.isin([x.item_code for x in items]))
+ and_conditions.append(ip.selling == True)
+
+ or_conditions.append(ip.customer == None)
+ or_conditions.append(ip.price_list == None)
+
+ if customer:
+ or_conditions.append(ip.customer == customer)
+
+ if price_list:
+ or_conditions.append(ip.price_list == price_list)
+
+ if selling_price_list:
+ or_conditions.append(ip.price_list == selling_price_list)
+
+ res = (
+ qb.from_(ip)
+ .select(ip.item_code, ip.price_list, ip.price_list_rate)
+ .where(Criterion.all(and_conditions))
+ .where(Criterion.any(or_conditions))
+ .run(as_dict=True)
+ )
+ for x in res:
+ price_list_map.update({(x.item_code, x.price_list): x.price_list_rate})
+
+ return price_list_map
+
+
def get_data(filters=None):
data = []
customer_details = get_customer_details(filters)
@@ -59,9 +95,17 @@
"Bin", fields=["item_code", "sum(actual_qty) AS available"], group_by="item_code"
)
item_stock_map = {item.item_code: item.available for item in item_stock_map}
+ price_list_map = fetch_item_prices(
+ customer_details.customer,
+ customer_details.price_list,
+ customer_details.selling_price_list,
+ items,
+ )
for item in items:
- price_list_rate = get_price_list_rate_for(customer_details, item.item_code) or 0.0
+ price_list_rate = price_list_map.get(
+ (item.item_code, customer_details.price_list or customer_details.selling_price_list), 0.0
+ )
available_stock = item_stock_map.get(item.item_code)
data.append(
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index 6c9d339..2cbccb0 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -88,6 +88,20 @@
}, __('Create'));
}
+ if (frm.doc.docstatus === 0) {
+ if (!frm.doc.is_return) {
+ frappe.db.get_single_value("Buying Settings", "maintain_same_rate").then((value) => {
+ if (value) {
+ frm.doc.items.forEach((item) => {
+ frm.fields_dict.items.grid.update_docfield_property(
+ "rate", "read_only", (item.purchase_order && item.purchase_order_item)
+ );
+ });
+ }
+ });
+ }
+ }
+
frm.events.add_custom_buttons(frm);
},
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 7344d2a..9bd692a 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -359,7 +359,6 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_width": "100px",
- "read_only_depends_on": "eval: (!parent.is_return && doc.purchase_order && doc.purchase_order_item)",
"width": "100px"
},
{
@@ -1104,7 +1103,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-11-30 16:12:02.364608",
+ "modified": "2023-12-25 22:32:09.801965",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index 37916e2..afb53fb 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -85,6 +85,7 @@
# end: auto-generated types
def validate(self):
+ self.set_batch_no()
self.validate_serial_and_batch_no()
self.validate_duplicate_serial_and_batch_no()
self.validate_voucher_no()
@@ -99,6 +100,26 @@
self.set_incoming_rate()
self.calculate_qty_and_amount()
+ def set_batch_no(self):
+ if self.has_serial_no and self.has_batch_no:
+ serial_nos = [d.serial_no for d in self.entries if d.serial_no]
+ has_no_batch = any(not d.batch_no for d in self.entries)
+ if not has_no_batch:
+ return
+
+ serial_no_batch = frappe._dict(
+ frappe.get_all(
+ "Serial No",
+ filters={"name": ("in", serial_nos)},
+ fields=["name", "batch_no"],
+ as_list=True,
+ )
+ )
+
+ for row in self.entries:
+ if not row.batch_no:
+ row.batch_no = serial_no_batch.get(row.serial_no)
+
def validate_serial_nos_inventory(self):
if not (self.has_serial_no and self.type_of_transaction == "Outward"):
return
@@ -1164,7 +1185,7 @@
doc.append(
"entries",
{
- "qty": (row.qty or 1.0) * (1 if type_of_transaction == "Inward" else -1),
+ "qty": (flt(row.qty) or 1.0) * (1 if type_of_transaction == "Inward" else -1),
"warehouse": warehouse,
"batch_no": row.batch_no,
"serial_no": row.serial_no,
@@ -1192,7 +1213,7 @@
doc.append(
"entries",
{
- "qty": (d.get("qty") or 1.0) * (1 if doc.type_of_transaction == "Inward" else -1),
+ "qty": (flt(d.get("qty")) or 1.0) * (1 if doc.type_of_transaction == "Inward" else -1),
"warehouse": warehouse or d.get("warehouse"),
"batch_no": d.get("batch_no"),
"serial_no": d.get("serial_no"),
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index e8d652e..6819968 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -171,7 +171,7 @@
},
)
- if item_details.has_batch_no:
+ elif item_details.has_batch_no:
batch_nos_details = get_available_batches(
frappe._dict(
{
@@ -228,6 +228,9 @@
def set_new_serial_and_batch_bundle(self):
for item in self.items:
+ if not item.qty:
+ continue
+
if item.current_serial_and_batch_bundle and not item.serial_and_batch_bundle:
current_doc = frappe.get_doc("Serial and Batch Bundle", item.current_serial_and_batch_bundle)
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 1ec99bf..70e9fb2 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -865,6 +865,66 @@
sr1.load_from_db()
self.assertEqual(sr1.difference_amount, 10000)
+ def test_make_stock_zero_for_serial_batch_item(self):
+ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
+ serial_item = self.make_item(
+ properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "DJJ.####"}
+ ).name
+ batch_item = self.make_item(
+ properties={
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "batch_number_series": "BDJJ.####",
+ "create_new_batch": 1,
+ }
+ ).name
+
+ serial_batch_item = self.make_item(
+ properties={
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "batch_number_series": "ADJJ.####",
+ "create_new_batch": 1,
+ "has_serial_no": 1,
+ "serial_no_series": "SN-ADJJ.####",
+ }
+ ).name
+
+ warehouse = "_Test Warehouse - _TC"
+
+ for item_code in [serial_item, batch_item, serial_batch_item]:
+ make_stock_entry(
+ item_code=item_code,
+ target=warehouse,
+ qty=10,
+ basic_rate=100,
+ )
+
+ _reco = create_stock_reconciliation(
+ item_code=item_code,
+ warehouse=warehouse,
+ qty=0.0,
+ )
+
+ serial_batch_bundle = frappe.get_all(
+ "Stock Ledger Entry",
+ {"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0, "voucher_no": _reco.name},
+ "serial_and_batch_bundle",
+ )
+
+ self.assertEqual(len(serial_batch_bundle), 1)
+
+ _reco.cancel()
+
+ serial_batch_bundle = frappe.get_all(
+ "Stock Ledger Entry",
+ {"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0, "voucher_no": _reco.name},
+ "serial_and_batch_bundle",
+ )
+
+ self.assertEqual(len(serial_batch_bundle), 0)
+
def create_batch_item_with_batch(item_name, batch_id):
batch_item_doc = create_item(item_name, is_stock_item=1)
diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
index 24650fd..7e03ac3 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
+++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
@@ -9,7 +9,7 @@
from frappe.query_builder.functions import Sum
from frappe.utils import cint, flt
-from erpnext.stock.utils import get_or_make_bin
+from erpnext.stock.utils import get_or_make_bin, get_stock_balance
class StockReservationEntry(Document):
@@ -151,7 +151,7 @@
"""Validates `Reserved Qty` when `Reservation Based On` is `Qty`."""
if self.reservation_based_on == "Qty":
- self.validate_with_max_reserved_qty(self.reserved_qty)
+ self.validate_with_allowed_qty(self.reserved_qty)
def auto_reserve_serial_and_batch(self, based_on: str = None) -> None:
"""Auto pick Serial and Batch Nos to reserve when `Reservation Based On` is `Serial and Batch`."""
@@ -324,7 +324,7 @@
frappe.throw(msg)
# Should be called after validating Serial and Batch Nos.
- self.validate_with_max_reserved_qty(qty_to_be_reserved)
+ self.validate_with_allowed_qty(qty_to_be_reserved)
self.db_set("reserved_qty", qty_to_be_reserved)
def update_reserved_qty_in_voucher(
@@ -429,7 +429,7 @@
msg = _("Stock Reservation Entry cannot be updated as it has been delivered.")
frappe.throw(msg)
- def validate_with_max_reserved_qty(self, qty_to_be_reserved: float) -> None:
+ def validate_with_allowed_qty(self, qty_to_be_reserved: float) -> None:
"""Validates `Reserved Qty` with `Max Reserved Qty`."""
self.db_set(
@@ -448,12 +448,12 @@
)
voucher_delivered_qty = flt(delivered_qty) * flt(conversion_factor)
- max_reserved_qty = min(
+ allowed_qty = min(
self.available_qty, (self.voucher_qty - voucher_delivered_qty - total_reserved_qty)
)
- if max_reserved_qty <= 0 and self.voucher_type == "Sales Order":
- msg = _("Item {0} is already delivered for Sales Order {1}.").format(
+ if self.get("_action") != "submit" and self.voucher_type == "Sales Order" and allowed_qty <= 0:
+ msg = _("Item {0} is already reserved/delivered against Sales Order {1}.").format(
frappe.bold(self.item_code), frappe.bold(self.voucher_no)
)
@@ -463,19 +463,33 @@
else:
frappe.throw(msg)
- if qty_to_be_reserved > max_reserved_qty:
+ if qty_to_be_reserved > allowed_qty:
+ actual_qty = get_stock_balance(self.item_code, self.warehouse)
msg = """
- Cannot reserve more than Max Reserved Qty {0} {1}.<br /><br />
- The <b>Max Reserved Qty</b> is calculated as follows:<br />
+ Cannot reserve more than Allowed Qty {0} {1} for Item {2} against {3} {4}.<br /><br />
+ The <b>Allowed Qty</b> is calculated as follows:<br />
<ul>
- <li><b>Available Qty To Reserve</b> = (Actual Stock Qty - Reserved Stock Qty)</li>
- <li><b>Voucher Qty</b> = Voucher Item Qty</li>
- <li><b>Delivered Qty</b> = Qty delivered against the Voucher Item</li>
- <li><b>Total Reserved Qty</b> = Qty reserved against the Voucher Item</li>
- <li><b>Max Reserved Qty</b> = Minimum of (Available Qty To Reserve, (Voucher Qty - Delivered Qty - Total Reserved Qty))</li>
+ <li>Actual Qty [Available Qty at Warehouse] = {5}</li>
+ <li>Reserved Stock [Ignore current SRE] = {6}</li>
+ <li>Available Qty To Reserve [Actual Qty - Reserved Stock] = {7}</li>
+ <li>Voucher Qty [Voucher Item Qty] = {8}</li>
+ <li>Delivered Qty [Qty delivered against the Voucher Item] = {9}</li>
+ <li>Total Reserved Qty [Qty reserved against the Voucher Item] = {10}</li>
+ <li>Allowed Qty [Minimum of (Available Qty To Reserve, (Voucher Qty - Delivered Qty - Total Reserved Qty))] = {11}</li>
</ul>
""".format(
- frappe.bold(max_reserved_qty), self.stock_uom
+ frappe.bold(allowed_qty),
+ self.stock_uom,
+ frappe.bold(self.item_code),
+ self.voucher_type,
+ frappe.bold(self.voucher_no),
+ actual_qty,
+ actual_qty - self.available_qty,
+ self.available_qty,
+ self.voucher_qty,
+ voucher_delivered_qty,
+ total_reserved_qty,
+ allowed_qty,
)
frappe.throw(msg)
@@ -509,7 +523,6 @@
"""Returns `Available Qty to Reserve (Actual Qty - Reserved Qty)` for Item, Warehouse and Batch combination."""
from erpnext.stock.doctype.batch.batch import get_batch_qty
- from erpnext.stock.utils import get_stock_balance
if batch_no:
return get_batch_qty(
diff --git a/erpnext/stock/report/reserved_stock/reserved_stock.js b/erpnext/stock/report/reserved_stock/reserved_stock.js
index 6872741..2b075e2 100644
--- a/erpnext/stock/report/reserved_stock/reserved_stock.js
+++ b/erpnext/stock/report/reserved_stock/reserved_stock.js
@@ -149,34 +149,36 @@
formatter: (value, row, column, data, default_formatter) => {
value = default_formatter(value, row, column, data);
- if (column.fieldname == "status") {
- switch (data.status) {
- case "Partially Reserved":
- value = "<span style='color:orange'>" + value + "</span>";
- break;
- case "Reserved":
- value = "<span style='color:blue'>" + value + "</span>";
- break;
- case "Partially Delivered":
- value = "<span style='color:purple'>" + value + "</span>";
- break;
- case "Delivered":
- value = "<span style='color:green'>" + value + "</span>";
- break;
+ if (data) {
+ if (column.fieldname == "status") {
+ switch (data.status) {
+ case "Partially Reserved":
+ value = "<span style='color:orange'>" + value + "</span>";
+ break;
+ case "Reserved":
+ value = "<span style='color:blue'>" + value + "</span>";
+ break;
+ case "Partially Delivered":
+ value = "<span style='color:purple'>" + value + "</span>";
+ break;
+ case "Delivered":
+ value = "<span style='color:green'>" + value + "</span>";
+ break;
+ }
}
- }
- else if (column.fieldname == "delivered_qty") {
- if (data.delivered_qty > 0) {
- if (data.reserved_qty > data.delivered_qty) {
- value = "<span style='color:blue'>" + value + "</span>";
+ else if (column.fieldname == "delivered_qty") {
+ if (data.delivered_qty > 0) {
+ if (data.reserved_qty > data.delivered_qty) {
+ value = "<span style='color:blue'>" + value + "</span>";
+ }
+ else {
+ value = "<span style='color:green'>" + value + "</span>";
+ }
}
else {
- value = "<span style='color:green'>" + value + "</span>";
+ value = "<span style='color:red'>" + value + "</span>";
}
}
- else {
- value = "<span style='color:red'>" + value + "</span>";
- }
}
return value;
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index 2745d4d..d05d0d9 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -103,6 +103,7 @@
Actual Qty {0} / Waiting Qty {1},Tatsächliche Menge {0} / Wartezeit {1},
Actual Qty: Quantity available in the warehouse.,Tatsächliche Menge: Menge verfügbar im Lager.,
Actual qty in stock,Tatsächliche Menge auf Lager,
+Actual Time (in Hours via Time Sheet), IST Zeit (in Stunden aus Zeiterfassung),
Actual type tax cannot be included in Item rate in row {0},Tatsächliche Steuerart kann nicht im Artikelpreis in Zeile {0} beinhaltet sein,
Add,Hinzufügen,
Add / Edit Prices,Preise hinzufügen / bearbeiten,