Skip to content

Commit 7362d78

Browse files
authored
Merge branch 'develop' into holiday-list-assignment
2 parents 3ce6da3 + 60f8654 commit 7362d78

File tree

82 files changed

+1700
-600
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+1700
-600
lines changed

erpnext/buying/doctype/purchase_order/purchase_order.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,9 @@ def on_cancel(self):
523523
if self.is_against_so():
524524
self.update_status_updater()
525525

526+
if self.is_against_pp():
527+
self.update_status_updater_if_from_pp()
528+
526529
if self.has_drop_ship_item():
527530
self.update_delivered_qty_in_sales_order()
528531

@@ -1007,6 +1010,13 @@ def post_process(source_doc, target_doc):
10071010
"Job Card", item.job_card, "wip_warehouse"
10081011
)
10091012

1013+
production_plan = set([item.production_plan for item in source_doc.items if item.production_plan])
1014+
if production_plan:
1015+
target_doc.production_plan = production_plan.pop()
1016+
target_doc.reserve_stock = frappe.get_single_value(
1017+
"Stock Settings", "auto_reserve_stock"
1018+
) or frappe.get_value("Production Plan", target_doc.production_plan, "reserve_stock")
1019+
10101020
if target_doc and isinstance(target_doc, str):
10111021
target_doc = json.loads(target_doc)
10121022
for key in ["service_items", "items", "supplied_items"]:

erpnext/buying/doctype/purchase_order_item/purchase_order_item.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,7 @@
830830
"fieldname": "production_plan",
831831
"fieldtype": "Link",
832832
"label": "Production Plan",
833+
"no_copy": 1,
833834
"options": "Production Plan",
834835
"print_hide": 1,
835836
"read_only": 1
@@ -948,7 +949,7 @@
948949
"index_web_pages_for_search": 1,
949950
"istable": 1,
950951
"links": [],
951-
"modified": "2025-10-12 10:57:31.552812",
952+
"modified": "2025-10-30 16:51:56.761673",
952953
"modified_by": "Administrator",
953954
"module": "Buying",
954955
"name": "Purchase Order Item",

erpnext/controllers/sales_and_purchase_return.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import erpnext
1414
from erpnext.stock.serial_batch_bundle import get_batches_from_bundle
15-
from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle
1615
from erpnext.stock.utils import get_combine_datetime, get_incoming_rate, get_valuation_method
1716

1817

@@ -145,7 +144,7 @@ def validate_returned_items(doc):
145144
ref.rate
146145
and flt(d.rate) > ref.rate
147146
and doc.doctype in ("Delivery Note", "Sales Invoice")
148-
and get_valuation_method(ref.item_code) != "Moving Average"
147+
and get_valuation_method(ref.item_code, doc.company) != "Moving Average"
149148
):
150149
frappe.throw(
151150
_("Row # {0}: Rate cannot be greater than the rate used in {1} {2}").format(

erpnext/controllers/selling_controller.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ def set_incoming_rate(self):
524524
)
525525

526526
if not self.get("return_against") or (
527-
get_valuation_method(d.item_code) == "Moving Average"
527+
get_valuation_method(d.item_code, self.company) == "Moving Average"
528528
and self.get("is_return")
529529
and not item_details.has_serial_no
530530
and not item_details.has_batch_no
@@ -535,7 +535,10 @@ def set_incoming_rate(self):
535535
if (
536536
not d.incoming_rate
537537
or self.is_internal_transfer()
538-
or (get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return"))
538+
or (
539+
get_valuation_method(d.item_code, self.company) == "Moving Average"
540+
and self.get("is_return")
541+
)
539542
):
540543
d.incoming_rate = get_incoming_rate(
541544
{
@@ -560,7 +563,7 @@ def set_incoming_rate(self):
560563
not d.incoming_rate
561564
and self.get("return_against")
562565
and self.get("is_return")
563-
and get_valuation_method(d.item_code) == "Moving Average"
566+
and get_valuation_method(d.item_code, self.company) == "Moving Average"
564567
):
565568
d.incoming_rate = get_rate_for_return(
566569
self.doctype, self.name, d.item_code, self.return_against, item_row=d

erpnext/controllers/stock_controller.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,128 @@ def add_gl_entry(
16451645

16461646
gl_entries.append(self.get_gl_dict(gl_entry, item=item))
16471647

1648+
def update_stock_reservation_entries(self):
1649+
def get_sre_list():
1650+
table = frappe.qb.DocType("Stock Reservation Entry")
1651+
query = (
1652+
frappe.qb.from_(table)
1653+
.select(table.name)
1654+
.where(
1655+
(table.docstatus == 1)
1656+
& (table.voucher_type == data_map[purpose or self.doctype]["voucher_type"])
1657+
& (
1658+
table.voucher_no
1659+
== data_map[purpose or self.doctype].get(
1660+
"voucher_no", item.get("subcontracting_order")
1661+
)
1662+
)
1663+
)
1664+
.orderby(table.creation)
1665+
)
1666+
if reference_field := data_map[purpose or self.doctype].get("voucher_detail_no_field"):
1667+
query = query.where(table.voucher_detail_no == item.get(reference_field))
1668+
else:
1669+
query = query.where(
1670+
(table.item_code == item.rm_item_code) & (table.warehouse == self.supplier_warehouse)
1671+
)
1672+
1673+
return query.run(pluck="name")
1674+
1675+
def get_data_map():
1676+
return {
1677+
"Subcontracting Delivery": {
1678+
"table_name": "items",
1679+
"voucher_type": "Subcontracting Inward Order",
1680+
"voucher_no": self.get("subcontracting_inward_order"),
1681+
"voucher_detail_no_field": "scio_detail",
1682+
"field": "delivered_qty",
1683+
},
1684+
"Send to Subcontractor": {
1685+
"table_name": "items",
1686+
"voucher_type": "Subcontracting Order",
1687+
"voucher_no": self.get("subcontracting_order"),
1688+
"voucher_detail_no_field": "sco_rm_detail",
1689+
"field": "transferred_qty",
1690+
},
1691+
"Subcontracting Receipt": {
1692+
"table_name": "supplied_items",
1693+
"voucher_type": "Subcontracting Order",
1694+
"field": "consumed_qty",
1695+
},
1696+
}
1697+
1698+
purpose = self.get("purpose")
1699+
if (
1700+
purpose == "Subcontracting Delivery"
1701+
or (
1702+
purpose == "Send to Subcontractor"
1703+
and frappe.get_value("Subcontracting Order", self.subcontracting_order, "reserve_stock")
1704+
)
1705+
or (self.doctype == "Subcontracting Receipt" and self.has_reserved_stock() and not self.is_return)
1706+
):
1707+
data_map = get_data_map()
1708+
1709+
field = data_map[purpose or self.doctype]["field"]
1710+
for item in self.get(data_map[purpose or self.doctype]["table_name"]):
1711+
sre_list = get_sre_list()
1712+
1713+
if not sre_list:
1714+
continue
1715+
1716+
qty = item.get("transfer_qty", item.get("consumed_qty"))
1717+
for sre in sre_list:
1718+
if qty <= 0:
1719+
break
1720+
1721+
sre_doc = frappe.get_doc("Stock Reservation Entry", sre)
1722+
1723+
working_qty = 0
1724+
if sre_doc.reservation_based_on == "Serial and Batch":
1725+
sbb = frappe.get_doc("Serial and Batch Bundle", item.serial_and_batch_bundle)
1726+
if sre_doc.has_serial_no:
1727+
serial_nos = [d.serial_no for d in sbb.entries]
1728+
for entry in sre_doc.sb_entries:
1729+
if entry.serial_no in serial_nos:
1730+
entry.delivered_qty = 1 if self._action == "submit" else 0
1731+
entry.db_update()
1732+
working_qty += 1
1733+
serial_nos.remove(entry.serial_no)
1734+
else:
1735+
batch_qty = {d.batch_no: -1 * d.qty for d in sbb.entries}
1736+
for entry in sre_doc.sb_entries:
1737+
if entry.batch_no in batch_qty:
1738+
delivered_qty = min(
1739+
(entry.qty - entry.delivered_qty)
1740+
if self._action == "submit"
1741+
else entry.delivered_qty,
1742+
batch_qty[entry.batch_no],
1743+
)
1744+
entry.delivered_qty += (
1745+
delivered_qty if self._action == "submit" else (-1 * delivered_qty)
1746+
)
1747+
entry.db_update()
1748+
working_qty += delivered_qty
1749+
batch_qty[entry.batch_no] -= delivered_qty
1750+
else:
1751+
working_qty = min(
1752+
(sre_doc.reserved_qty - sre_doc.get(field))
1753+
if self._action == "submit"
1754+
else sre_doc.get(field),
1755+
qty,
1756+
)
1757+
1758+
sre_doc.set(
1759+
field,
1760+
sre_doc.get(field)
1761+
+ (working_qty if self._action == "submit" else (-1 * working_qty)),
1762+
)
1763+
sre_doc.db_update()
1764+
sre_doc.update_reserved_qty_in_voucher()
1765+
sre_doc.update_status()
1766+
sre_doc.update_reserved_stock_in_bin()
1767+
1768+
qty -= working_qty
1769+
16481770

16491771
@frappe.whitelist()
16501772
def show_accounting_ledger_preview(company, doctype, docname):

erpnext/controllers/subcontracting_controller.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -497,11 +497,10 @@ def get_available_materials(self):
497497

498498
if row.serial_no:
499499
details.serial_no.extend(get_serial_nos(row.serial_no))
500-
501-
elif row.batch_no:
500+
if row.batch_no:
502501
details.batch_no[row.batch_no] += row.qty
503502

504-
elif voucher_bundle_data:
503+
if not row.serial_no and not row.batch_no and voucher_bundle_data:
505504
bundle_key = (row.rm_item_code, row.main_item_code, row.t_warehouse, row.voucher_no)
506505

507506
bundle_data = voucher_bundle_data.get(bundle_key, frappe._dict())
@@ -551,6 +550,8 @@ def __remove_serial_and_batch_bundle(self, item):
551550
frappe.delete_doc("Serial and Batch Bundle", item.serial_and_batch_bundle, force=True)
552551

553552
def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0):
553+
data = []
554+
554555
doctype = "BOM Item" if not exploded_item else "BOM Explosion Item"
555556
fields = [f"`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit"]
556557

@@ -559,7 +560,7 @@ def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0):
559560
"name": "bom_detail_no",
560561
"source_warehouse": "reserve_warehouse",
561562
}
562-
for field in [
563+
fields_list = [
563564
"item_code",
564565
"name",
565566
"rate",
@@ -568,7 +569,12 @@ def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0):
568569
"description",
569570
"item_name",
570571
"stock_uom",
571-
]:
572+
]
573+
574+
if doctype == "BOM Item":
575+
fields_list.extend(["is_phantom_item", "bom_no"])
576+
577+
for field in fields_list:
572578
fields.append(f"`tab{doctype}`.`{field}` As {alias_dict.get(field, field)}")
573579

574580
filters = [
@@ -578,7 +584,19 @@ def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0):
578584
[doctype, "sourced_by_supplier", "=", 0],
579585
]
580586

581-
return frappe.get_all("BOM", fields=fields, filters=filters, order_by=f"`tab{doctype}`.`idx`") or []
587+
data = frappe.get_all("BOM", fields=fields, filters=filters, order_by=f"`tab{doctype}`.`idx`") or []
588+
to_remove = []
589+
for item in data:
590+
if item.is_phantom_item:
591+
data += self.__get_materials_from_bom(
592+
item.rm_item_code, item.bom_no, exploded_item=exploded_item
593+
)
594+
to_remove.append(item)
595+
596+
for item in to_remove:
597+
data.remove(item)
598+
599+
return data
582600

583601
def __update_reserve_warehouse(self, row, item):
584602
if (

0 commit comments

Comments
 (0)