Skip to content
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
61 changes: 61 additions & 0 deletions rmax_custom/api/material_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import frappe
from frappe.utils import nowdate, flt, add_days
from frappe import _

@frappe.whitelist()
def create_material_request(item_code, from_warehouse, to_warehouse, qty, schedule_date, material_request_type, company):
"""Create a Material Request for Material Transfer."""

if not item_code:
frappe.throw(_("Item Code is required"))
if not from_warehouse:
frappe.throw(_("From Warehouse is required"))
if not to_warehouse:
frappe.throw(_("To Warehouse is required"))
if from_warehouse == to_warehouse:
frappe.throw(_("From Warehouse and To Warehouse cannot be the same"))
if not qty or flt(qty) <= 0:
frappe.throw(_("Quantity must be greater than 0"))
if not company:
frappe.throw(_("Company is required"))

# Validate item exists
if not frappe.db.exists("Item", item_code):
frappe.throw(_("Item {0} does not exist").format(item_code))

# Validate warehouses exist
if not frappe.db.exists("Warehouse", from_warehouse):
frappe.throw(_("From Warehouse {0} does not exist").format(from_warehouse))
if not frappe.db.exists("Warehouse", to_warehouse):
frappe.throw(_("To Warehouse {0} does not exist").format(to_warehouse))

# Get item details
item_doc = frappe.get_cached_doc("Item", item_code)

# Create Material Request
material_request = frappe.new_doc("Material Request")
material_request.transaction_date = nowdate()
material_request.company = company
material_request.material_request_type = "Material Transfer"

# Add item row
material_request.append("items", {
"item_code": item_code,
"item_name": item_doc.item_name,
"description": item_doc.description,
"qty": flt(qty),
"uom": item_doc.stock_uom,
"stock_uom": item_doc.stock_uom,
"schedule_date": schedule_date or add_days(nowdate(), 7),
"warehouse": to_warehouse,
"from_warehouse": from_warehouse,
"item_group": item_doc.item_group,
"brand": item_doc.brand
})

# Set missing values and insert
material_request.set_missing_values()
material_request.insert(ignore_permissions=True)
material_request.submit()

return material_request.name
88 changes: 88 additions & 0 deletions rmax_custom/api/warehouse_stock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import frappe
from frappe import _
from erpnext.stock.utils import get_stock_balance


@frappe.whitelist()
def get_item_warehouse_stock(item_code, company=None, limit=None, target_warehouse=None):
"""
Get stock balance for an item across all warehouses in a company.
Optimized to use Bin table for faster bulk queries.

Args:
item_code: Item code to get stock for
company: Company name (optional, will use default if not provided)
limit: Limit number of results (optional, for pagination)
target_warehouse: Target warehouse to prioritize (optional)

Returns:
List of dictionaries with warehouse name and stock balance
"""
if not item_code:
frappe.throw(_("Item Code is required"))

# Get company from context or use default
if not company:
company = frappe.defaults.get_user_default("company")

if not company:
frappe.throw(_("Please set a default company"))

# Get all warehouses for the company (non-group warehouses only)
warehouses = frappe.get_all(
"Warehouse",
filters={
"company": company,
"is_group": 0,
"disabled": 0
},
fields=["name", "warehouse_name"],
order_by="name"
)

if not warehouses:
return []

# Optimize: Use Bin table for faster stock queries (bulk query)
warehouse_names = [w.name for w in warehouses]

# Use Bin table for faster bulk query
bin_data = frappe.db.sql("""
SELECT warehouse, actual_qty
FROM `tabBin`
WHERE item_code = %s AND warehouse IN %s
""", (item_code, warehouse_names), as_dict=True)

# Create a dict for quick lookup
bin_dict = {d.warehouse: (d.actual_qty or 0.0) for d in bin_data}

# Build stock data list
stock_data = []
for warehouse in warehouses:
# Get stock from bin_dict (faster than individual get_stock_balance calls)
stock_qty = bin_dict.get(warehouse.name, 0.0)

stock_data.append({
"warehouse": warehouse.name,
"warehouse_name": warehouse.warehouse_name or warehouse.name,
"stock_qty": stock_qty
})

# Filter: Only show warehouses with stock > 0 (or target warehouse even if 0)
if target_warehouse:
filtered_stock_data = [item for item in stock_data if item["stock_qty"] > 0 or item["warehouse"] == target_warehouse]
else:
filtered_stock_data = [item for item in stock_data if item["stock_qty"] > 0]

# Sort: target warehouse first, then by stock quantity descending
if target_warehouse:
filtered_stock_data.sort(key=lambda x: (-1 if x["warehouse"] == target_warehouse else 0, -x["stock_qty"]))
else:
filtered_stock_data.sort(key=lambda x: x["stock_qty"], reverse=True)

# Apply limit if specified
if limit:
limit = int(limit)
return filtered_stock_data[:limit]

return filtered_stock_data
116 changes: 116 additions & 0 deletions rmax_custom/fixtures/custom_field.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
[
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Quotation",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_payment_mode",
"fieldtype": "Select",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "valid_till",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Payment Mode",
"length": 0,
"link_filters": null,
"mandatory_depends_on": null,
"modified": "2026-02-28 11:11:52.476754",
"module": null,
"name": "Quotation-custom_payment_mode",
"no_copy": 0,
"non_negative": 0,
"options": "Cash\nCredit",
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Sales Invoice",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_payment_mode",
"fieldtype": "Select",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "due_date",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Payment Mode",
"length": 0,
"link_filters": null,
"mandatory_depends_on": null,
"modified": "2026-02-26 16:23:30.463431",
"module": null,
"name": "Sales Invoice-custom_payment_mode",
"no_copy": 0,
"non_negative": 0,
"options": "Cash\nCredit",
"permlevel": 0,
"placeholder": null,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
}
]
38 changes: 36 additions & 2 deletions rmax_custom/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@

# include js, css files in header of desk.html
# app_include_css = "/assets/rmax_custom/css/rmax_custom.css"
# app_include_js = "/assets/rmax_custom/js/rmax_custom.js"
app_include_js = [
"/assets/rmax_custom/js/warehouse_stock_popup.js",
"/assets/rmax_custom/js/sales_invoice_pos_total_popup.js",
]



# include js, css files in header of web template
# web_include_css = "/assets/rmax_custom/css/rmax_custom.css"
Expand All @@ -43,7 +48,10 @@
# page_js = {"page" : "public/js/file.js"}

# include js in doctype views
# doctype_js = {"doctype" : "public/js/doctype.js"}
doctype_js = {
"Sales Invoice": "rmax_custom/custom_scripts/sales_invoice/sales_invoice.js",
"Quotation": "rmax_custom/custom_scripts/quotation/quotation.js",
}
# doctype_list_js = {"doctype" : "public/js/doctype_list.js"}
# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"}
# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"}
Expand Down Expand Up @@ -242,3 +250,29 @@
# "Logging DocType Name": 30 # days to retain logs
# }

fixtures = [
{
"dt": "Custom Field",
"filters": [
[
"name",
"in",
[
# Sales Invoice
"Sales Invoice-custom_payment_mode",

# Sales Invoice Item
"Sales Invoice Item-total_vat_linewise",


# Quotation
"Quotation-custom_payment_mode",

# Quotation Item
"Quotation Item-total_vat_linewise",

]
]
]
}
]
Loading
Loading