Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: auto reserve stock for Sales Order on purchase #37603

Merged
merged 13 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -264,6 +264,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 @@ -829,6 +830,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 update_billed_amount_based_on_po(po_details, update_modified=True):
po_billed_amt_details = get_billed_amount_against_po(po_details)
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
Loading