Skip to content

Commit

Permalink
Merge pull request #37603 from s-aga-r/AUTO-RESERVATION
Browse files Browse the repository at this point in the history
feat: auto reserve stock for Sales Order on purchase
  • Loading branch information
s-aga-r authored Oct 24, 2023
2 parents 3f42128 + 6942ab1 commit cdbe1c8
Show file tree
Hide file tree
Showing 19 changed files with 400 additions and 167 deletions.
2 changes: 2 additions & 0 deletions erpnext/buying/doctype/purchase_order/purchase_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,8 @@ def update_item(obj, target, source_parent):
"bom": "bom",
"material_request": "material_request",
"material_request_item": "material_request_item",
"sales_order": "sales_order",
"sales_order_item": "sales_order_item",
},
"postprocess": update_item,
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
Expand Down
1 change: 1 addition & 0 deletions erpnext/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -340,5 +340,6 @@ erpnext.patches.v14_0.update_invoicing_period_in_subscription
execute:frappe.delete_doc("Page", "welcome-to-erpnext")
erpnext.patches.v15_0.delete_payment_gateway_doctypes
erpnext.patches.v14_0.create_accounting_dimensions_in_sales_order_item
erpnext.patches.v15_0.update_sre_from_voucher_details
# below migration patch should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
15 changes: 15 additions & 0 deletions erpnext/patches/v15_0/update_sre_from_voucher_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import frappe
from frappe.query_builder.functions import IfNull


def execute():
sre = frappe.qb.DocType("Stock Reservation Entry")
(
frappe.qb.update(sre)
.set(sre.from_voucher_type, "Pick List")
.set(sre.from_voucher_no, sre.against_pick_list)
.set(sre.from_voucher_detail_no, sre.against_pick_list_item)
.where(
(IfNull(sre.against_pick_list, "") != "") & (IfNull(sre.against_pick_list_item, "") != "")
)
).run()
12 changes: 4 additions & 8 deletions erpnext/selling/doctype/sales_order/sales_order.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,13 @@ frappe.ui.form.on("Sales Order", {
frm.events.get_items_from_internal_purchase_order(frm);
}

if (frm.is_new()) {
if (frm.doc.docstatus === 0) {
frappe.db.get_single_value("Stock Settings", "enable_stock_reservation").then((value) => {
if (value) {
frappe.db.get_single_value("Stock Settings", "auto_reserve_stock_for_sales_order").then((value) => {
// If `Reserve Stock on Sales Order Submission` is enabled in Stock Settings, set Reserve Stock to 1 else 0.
frm.set_value("reserve_stock", value ? 1 : 0);
})
} else {
// If `Stock Reservation` is disabled in Stock Settings, set Reserve Stock to 0 and read only.
if (!value) {
// If `Stock Reservation` is disabled in Stock Settings, set Reserve Stock to 0 and make the field read-only and hidden.
frm.set_value("reserve_stock", 0);
frm.set_df_property("reserve_stock", "read_only", 1);
frm.set_df_property("reserve_stock", "hidden", 1);
}
})
}
Expand Down
5 changes: 2 additions & 3 deletions erpnext/selling/doctype/sales_order/sales_order.json
Original file line number Diff line number Diff line change
Expand Up @@ -1631,10 +1631,9 @@
{
"default": "0",
"depends_on": "eval: (doc.docstatus == 0 || doc.reserve_stock)",
"description": "If checked, Stock Reservation Entries will be created on <b>Submit</b>",
"description": "If checked, Stock will be reserved on <b>Submit</b>",
"fieldname": "reserve_stock",
"fieldtype": "Check",
"hidden": 1,
"label": "Reserve Stock",
"no_copy": 1,
"print_hide": 1,
Expand All @@ -1645,7 +1644,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2023-07-24 08:59:11.599875",
"modified": "2023-10-18 12:41:54.813462",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
Expand Down
15 changes: 13 additions & 2 deletions erpnext/selling/doctype/sales_order/sales_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@


import json
from typing import Literal

import frappe
import frappe.utils
Expand Down Expand Up @@ -534,14 +535,24 @@ def has_unreserved_stock(self) -> bool:
return False

@frappe.whitelist()
def create_stock_reservation_entries(self, items_details=None, notify=True) -> None:
def create_stock_reservation_entries(
self,
items_details: list[dict] = None,
from_voucher_type: Literal["Pick List", "Purchase Receipt"] = None,
notify=True,
) -> None:
"""Creates Stock Reservation Entries for Sales Order Items."""

from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
create_stock_reservation_entries_for_so_items as create_stock_reservation_entries,
)

create_stock_reservation_entries(so=self, items_details=items_details, notify=notify)
create_stock_reservation_entries(
sales_order=self,
items_details=items_details,
from_voucher_type=from_voucher_type,
notify=notify,
)

@frappe.whitelist()
def cancel_stock_reservation_entries(self, sre_list=None, notify=True) -> None:
Expand Down
3 changes: 2 additions & 1 deletion erpnext/stock/doctype/pick_list/pick_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,8 @@ frappe.ui.form.on('Pick List', {
from_date: moment(frm.doc.creation).format('YYYY-MM-DD'),
to_date: to_date,
voucher_type: "Sales Order",
against_pick_list: frm.doc.name,
from_voucher_type: "Pick List",
from_voucher_no: frm.doc.name,
}
frappe.set_route("query-report", "Reserved Stock");
}
Expand Down
31 changes: 20 additions & 11 deletions erpnext/stock/doctype/pick_list/pick_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,20 +229,27 @@ def update_sales_order_picking_status(self) -> None:
def create_stock_reservation_entries(self, notify=True) -> None:
"""Creates Stock Reservation Entries for Sales Order Items against Pick List."""

from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
create_stock_reservation_entries_for_so_items,
)

so_details = {}
so_items_details_map = {}
for location in self.locations:
if location.warehouse and location.sales_order and location.sales_order_item:
so_details.setdefault(location.sales_order, []).append(location)
item_details = {
"name": location.sales_order_item,
"item_code": location.item_code,
"warehouse": location.warehouse,
"qty_to_reserve": (flt(location.picked_qty) - flt(location.stock_reserved_qty)),
"from_voucher_no": location.parent,
"from_voucher_detail_no": location.name,
"serial_and_batch_bundle": location.serial_and_batch_bundle,
}
so_items_details_map.setdefault(location.sales_order, []).append(item_details)

if so_details:
for so, locations in so_details.items():
if so_items_details_map:
for so, items_details in so_items_details_map.items():
so_doc = frappe.get_doc("Sales Order", so)
create_stock_reservation_entries_for_so_items(
so=so_doc, items_details=locations, against_pick_list=True, notify=notify
so_doc.create_stock_reservation_entries(
items_details=items_details,
from_voucher_type="Pick List",
notify=notify,
)

@frappe.whitelist()
Expand All @@ -253,7 +260,9 @@ def cancel_stock_reservation_entries(self, notify=True) -> None:
cancel_stock_reservation_entries,
)

cancel_stock_reservation_entries(against_pick_list=self.name, notify=notify)
cancel_stock_reservation_entries(
from_voucher_type="Pick List", from_voucher_no=self.name, notify=notify
)

def validate_picked_qty(self, data):
over_delivery_receipt_allowance = 100 + flt(
Expand Down
2 changes: 1 addition & 1 deletion erpnext/stock/doctype/pick_list/pick_list_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ def get_data():
return {
"fieldname": "pick_list",
"non_standard_fieldnames": {
"Stock Reservation Entry": "against_pick_list",
"Stock Reservation Entry": "from_voucher_no",
},
"internal_links": {
"Sales Order": ["locations", "sales_order"],
Expand Down
32 changes: 32 additions & 0 deletions erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ def on_submit(self):
self.make_gl_entries()
self.repost_future_sle_and_gle()
self.set_consumed_qty_in_subcontract_order()
self.reserve_stock_for_sales_order()

def check_next_docstatus(self):
submit_rv = frappe.db.sql(
Expand Down Expand Up @@ -759,6 +760,37 @@ def update_billing_status(self, update_modified=True):

self.load_from_db()

def reserve_stock_for_sales_order(self):
if self.is_return or not cint(
frappe.db.get_single_value("Stock Settings", "auto_reserve_stock_for_sales_order_on_purchase")
):
return

self.reload() # reload to get the Serial and Batch Bundle Details

so_items_details_map = {}
for item in self.items:
if item.sales_order and item.sales_order_item:
item_details = {
"name": item.sales_order_item,
"item_code": item.item_code,
"warehouse": item.warehouse,
"qty_to_reserve": item.stock_qty,
"from_voucher_no": item.parent,
"from_voucher_detail_no": item.name,
"serial_and_batch_bundle": item.serial_and_batch_bundle,
}
so_items_details_map.setdefault(item.sales_order, []).append(item_details)

if so_items_details_map:
for so, items_details in so_items_details_map.items():
so_doc = frappe.get_doc("Sales Order", so)
so_doc.create_stock_reservation_entries(
items_details=items_details,
from_voucher_type="Purchase Receipt",
notify=True,
)


def get_stock_value_difference(voucher_no, voucher_detail_no, warehouse):
return frappe.db.get_value(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def get_data():
"Landed Cost Voucher": "receipt_document",
"Auto Repeat": "reference_document",
"Purchase Receipt": "return_against",
"Stock Reservation Entry": "from_voucher_no",
},
"internal_links": {
"Material Request": ["items", "material_request"],
Expand All @@ -18,7 +19,10 @@ def get_data():
"Quality Inspection": ["items", "quality_inspection"],
},
"transactions": [
{"label": _("Related"), "items": ["Purchase Invoice", "Landed Cost Voucher", "Asset"]},
{
"label": _("Related"),
"items": ["Purchase Invoice", "Landed Cost Voucher", "Asset", "Stock Reservation Entry"],
},
{
"label": _("Reference"),
"items": ["Material Request", "Purchase Order", "Quality Inspection", "Project"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@
"dimension_col_break",
"cost_center",
"section_break_80",
"page_break"
"page_break",
"sales_order",
"sales_order_item"
],
"fields": [
{
Expand Down Expand Up @@ -1062,12 +1064,32 @@
"fieldtype": "Link",
"label": "WIP Composite Asset",
"options": "Asset"
},
{
"fieldname": "sales_order",
"fieldtype": "Link",
"label": "Sales Order",
"no_copy": 1,
"options": "Sales Order",
"print_hide": 1,
"read_only": 1,
"search_index": 1
},
{
"fieldname": "sales_order_item",
"fieldtype": "Data",
"hidden": 1,
"label": "Sales Order Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1,
"search_index": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2023-10-03 21:11:50.547261",
"modified": "2023-10-19 10:50:58.071735",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
Expand All @@ -1078,4 +1100,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ frappe.ui.form.on('Stock Reservation Entry', {
'qty', 'read_only', frm.doc.has_serial_no
);

frm.set_df_property('sb_entries', 'allow_on_submit', frm.doc.against_pick_list ? 0 : 1);
frm.set_df_property('sb_entries', 'allow_on_submit', frm.doc.from_voucher_type == "Pick List" ? 0 : 1);
},

hide_rate_related_fields(frm) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
"voucher_no",
"voucher_detail_no",
"column_break_7dxj",
"against_pick_list",
"against_pick_list_item",
"from_voucher_type",
"from_voucher_no",
"from_voucher_detail_no",
"section_break_xt4m",
"stock_uom",
"column_break_grdt",
Expand Down Expand Up @@ -158,7 +159,7 @@
"oldfieldname": "actual_qty",
"oldfieldtype": "Currency",
"print_width": "150px",
"read_only_depends_on": "eval: ((doc.reservation_based_on == \"Serial and Batch\") || (doc.against_pick_list) || (doc.delivered_qty > 0))",
"read_only_depends_on": "eval: ((doc.reservation_based_on == \"Serial and Batch\") || (doc.from_voucher_type == \"Pick List\") || (doc.delivered_qty > 0))",
"width": "150px"
},
{
Expand Down Expand Up @@ -268,43 +269,53 @@
"label": "Reservation Based On",
"no_copy": 1,
"options": "Qty\nSerial and Batch",
"read_only_depends_on": "eval: (doc.delivered_qty > 0 || doc.against_pick_list)"
"read_only_depends_on": "eval: (doc.delivered_qty > 0 || doc.from_voucher_type == \"Pick List\")"
},
{
"fieldname": "against_pick_list",
"fieldtype": "Link",
"label": "Against Pick List",
"fieldname": "column_break_7dxj",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_grdt",
"fieldtype": "Column Break"
},
{
"fieldname": "from_voucher_type",
"fieldtype": "Select",
"label": "From Voucher Type",
"no_copy": 1,
"options": "Pick List",
"options": "\nPick List\nPurchase Receipt",
"print_hide": 1,
"read_only": 1,
"report_hide": 1,
"search_index": 1
"report_hide": 1
},
{
"fieldname": "against_pick_list_item",
"fieldname": "from_voucher_detail_no",
"fieldtype": "Data",
"label": "Against Pick List Item",
"label": "From Voucher Detail No",
"no_copy": 1,
"print_hide": 1,
"read_only": 1,
"report_hide": 1
},
{
"fieldname": "column_break_7dxj",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_grdt",
"fieldtype": "Column Break"
"fieldname": "from_voucher_no",
"fieldtype": "Dynamic Link",
"label": "From Voucher No",
"no_copy": 1,
"options": "from_voucher_type",
"print_hide": 1,
"read_only": 1,
"report_hide": 1,
"search_index": 1
}
],
"hide_toolbar": 1,
"in_create": 1,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-08-08 17:15:13.317706",
"modified": "2023-10-19 16:41:16.545416",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reservation Entry",
Expand Down
Loading

0 comments on commit cdbe1c8

Please sign in to comment.