Serial No: warehousing pricing + grading + hardware failure routing
- Grade options: High/Med/Low/Flagged (replaces A/B/C/D/F) - assigned_price label → Recommended Price - Client script: grade→price mapping (High→market_high, Med→market_median, Low→market_low) - Flagged grade shows FLAGGED, no price, Dismantle status - New fields: cpu_test (Pass/Fail), ram_test (Pass/Fail) - CPU/RAM Fail → auto Flagged grade, Dismantle status, route to Dismantle warehouse - Server-side validation prevents pricing on failed hardware - pricing_status options updated: Needs Pricing/Priced/Flagged/Dismantle/Manual Override/Expired/Error
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,37 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
def validate_hardware_tests(doc, method):
|
||||||
|
"""Before save: if CPU or RAM test failed, route to Dismantle."""
|
||||||
|
|
||||||
|
# Check if this is a device-type serial (has item_code)
|
||||||
|
if not doc.item_code:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check CPU and RAM test results
|
||||||
|
cpu_fail = doc.get("cpu_test") == "Fail"
|
||||||
|
ram_fail = doc.get("ram_test") == "Fail"
|
||||||
|
|
||||||
|
if cpu_fail or ram_fail:
|
||||||
|
# Set grade to Flagged
|
||||||
|
doc.grade = "Flagged"
|
||||||
|
doc.pricing_status = "Dismantle"
|
||||||
|
doc.assigned_price = None
|
||||||
|
|
||||||
|
# Route to Dismantle warehouse if it exists
|
||||||
|
dismantle_wh = frappe.db.exists("Warehouse", "Dismantle - WR")
|
||||||
|
if dismantle_wh:
|
||||||
|
doc.warehouse = "Dismantle - WR"
|
||||||
|
|
||||||
|
# Log the failure reason
|
||||||
|
reasons = []
|
||||||
|
if cpu_fail:
|
||||||
|
reasons.append("CPU test failed")
|
||||||
|
if ram_fail:
|
||||||
|
reasons.append("RAM test failed")
|
||||||
|
|
||||||
|
frappe.msgprint(
|
||||||
|
_("Hardware failure detected: {0}. Device routed to Dismantle.").format(", ".join(reasons)),
|
||||||
|
indicator="red",
|
||||||
|
alert=True
|
||||||
|
)
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
"doctype": "Client Script",
|
"doctype": "Client Script",
|
||||||
"dt": "Serial No",
|
"dt": "Serial No",
|
||||||
"enabled": 1,
|
"enabled": 1,
|
||||||
"modified": "2026-05-17 06:30:57.598334",
|
"modified": "2026-05-19 08:15:00.000000",
|
||||||
"module": null,
|
"module": null,
|
||||||
"name": "Serial No - Price Calculator",
|
"name": "Serial No - Price Calculator",
|
||||||
"script": "frappe.ui.form.on('Serial No', {\n grade: function(frm) {\n calculate_price(frm);\n },\n price_point: function(frm) {\n calculate_price(frm);\n },\n manual_price: function(frm) {\n if (frm.doc.price_point === 'Manual') {\n frm.set_value('assigned_price', frm.doc.manual_price);\n frm.set_value('pricing_status', 'Manual Override');\n }\n }\n});\n\nfunction calculate_price(frm) {\n var grade = frm.doc.grade;\n var price_point = frm.doc.price_point;\n \n if (!grade || !price_point) {\n return;\n }\n \n // C, D, F grades = commodity\n if (['C', 'D', 'F'].includes(grade)) {\n // Will be handled by server side\n frm.set_value('pricing_status', 'Commodity');\n return;\n }\n \n // Need item reference to get market prices\n if (!frm.doc.item_code) {\n return;\n }\n \n frappe.call({\n method: 'frappe.client.get',\n args: {\n doctype: 'Item',\n name: frm.doc.item_code\n },\n callback: function(r) {\n if (!r.message) return;\n var item = r.message;\n var base_price = 0;\n \n switch(price_point) {\n case 'Low':\n base_price = item.market_low || item.base_market_price || 0;\n break;\n case 'Median':\n base_price = item.market_median || item.base_market_price || 0;\n break;\n case 'Average':\n base_price = item.market_average || item.base_market_price || 0;\n break;\n case 'High':\n base_price = item.market_high || item.base_market_price || 0;\n break;\n }\n \n var multiplier = 1.0;\n if (grade === 'A') multiplier = item.grade_a_multiplier || 1.0;\n else if (grade === 'B') multiplier = item.grade_b_multiplier || 0.8;\n \n var final_price = base_price * multiplier;\n frm.set_value('assigned_price', Math.round(final_price * 100) / 100);\n frm.set_value('pricing_status', 'Priced');\n }\n });\n}",
|
"script": "frappe.ui.form.on('Serial No', {\n grade: function(frm) {\n calculate_recommended_price(frm);\n },\n cpu_test: function(frm) {\n check_hardware_failures(frm);\n },\n ram_test: function(frm) {\n check_hardware_failures(frm);\n },\n refresh: function(frm) {\n // Label change via JS (since fixture label updates need migrate)\n var price_field = frm.get_field('assigned_price');\n if (price_field && price_field.$wrapper) {\n price_field.$wrapper.find('.control-label').text('Recommended Price');\n }\n check_hardware_failures(frm);\n }\n});\n\nfunction check_hardware_failures(frm) {\n var cpu_fail = frm.doc.cpu_test === 'Fail';\n var ram_fail = frm.doc.ram_test === 'Fail';\n \n if (cpu_fail || ram_fail) {\n // Force Flagged grade and dismantle routing\n if (frm.doc.grade !== 'Flagged') {\n frm.set_value('grade', 'Flagged');\n }\n frm.set_value('assigned_price', null);\n frm.set_value('pricing_status', 'Dismantle');\n frm.set_df_property('grade', 'read_only', 1);\n \n var reason = [];\n if (cpu_fail) reason.push('CPU test failed');\n if (ram_fail) reason.push('RAM test failed');\n \n frappe.show_alert({\n message: __('Hardware failure: ' + reason.join(', ') + ' \u2014 routed to Dismantle'),\n indicator: 'red'\n }, 5);\n } else {\n frm.set_df_property('grade', 'read_only', 0);\n }\n}\n\nfunction calculate_recommended_price(frm) {\n var grade = frm.doc.grade;\n \n // Flagged = no price, show FLAGGED text\n if (!grade || grade === 'Flagged') {\n frm.set_value('assigned_price', null);\n frm.set_value('pricing_status', grade === 'Flagged' ? 'Flagged' : 'Needs Pricing');\n return;\n }\n \n // Need item reference to get market prices\n if (!frm.doc.item_code) {\n return;\n }\n \n frappe.call({\n method: 'frappe.client.get',\n args: {\n doctype: 'Item',\n name: frm.doc.item_code\n },\n callback: function(r) {\n if (!r.message) return;\n var item = r.message;\n var base_price = 0;\n var price_source = '';\n \n switch(grade) {\n case 'High':\n base_price = item.market_high || item.base_market_price || 0;\n price_source = 'market_high';\n break;\n case 'Med':\n base_price = item.market_median || item.base_market_price || 0;\n price_source = 'market_median';\n break;\n case 'Low':\n base_price = item.market_low || item.base_market_price || 0;\n price_source = 'market_low';\n break;\n }\n \n if (base_price > 0) {\n frm.set_value('assigned_price', Math.round(base_price * 100) / 100);\n frm.set_value('pricing_status', 'Priced');\n frm.set_value('pricing_source', price_source);\n } else {\n frm.set_value('assigned_price', null);\n frm.set_value('pricing_status', 'Needs Pricing');\n }\n }\n });\n}\n",
|
||||||
"view": "Form"
|
"view": "Form"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2081,7 +2081,7 @@
|
|||||||
"insert_after": "cosmetic_grade",
|
"insert_after": "cosmetic_grade",
|
||||||
"is_system_generated": 0,
|
"is_system_generated": 0,
|
||||||
"is_virtual": 0,
|
"is_virtual": 0,
|
||||||
"label": "Grade",
|
"label": "Grade (High/Med/Low/Flagged)",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"link_filters": null,
|
"link_filters": null,
|
||||||
"mandatory_depends_on": null,
|
"mandatory_depends_on": null,
|
||||||
@@ -2090,7 +2090,7 @@
|
|||||||
"name": "Serial No-grade",
|
"name": "Serial No-grade",
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"non_negative": 0,
|
"non_negative": 0,
|
||||||
"options": "A\nB\nC\nD\nF",
|
"options": "High\nMed\nLow\nFlagged",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"placeholder": null,
|
"placeholder": null,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
@@ -2309,7 +2309,7 @@
|
|||||||
"insert_after": "grade",
|
"insert_after": "grade",
|
||||||
"is_system_generated": 0,
|
"is_system_generated": 0,
|
||||||
"is_virtual": 0,
|
"is_virtual": 0,
|
||||||
"label": "Assigned Price",
|
"label": "Recommended Price",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"link_filters": null,
|
"link_filters": null,
|
||||||
"mandatory_depends_on": null,
|
"mandatory_depends_on": null,
|
||||||
@@ -2831,7 +2831,7 @@
|
|||||||
"name": "Serial No-pricing_status",
|
"name": "Serial No-pricing_status",
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
"non_negative": 0,
|
"non_negative": 0,
|
||||||
"options": "Needs Pricing\nPriced\nManual Override\nExpired\nError",
|
"options": "Needs Pricing\nPriced\nFlagged\nDismantle\nManual Override\nExpired\nError",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"placeholder": null,
|
"placeholder": null,
|
||||||
"precision": "",
|
"precision": "",
|
||||||
@@ -4330,5 +4330,83 @@
|
|||||||
"translatable": 0,
|
"translatable": 0,
|
||||||
"unique": 0,
|
"unique": 0,
|
||||||
"width": null
|
"width": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"default": null,
|
||||||
|
"depends_on": null,
|
||||||
|
"description": "CPU stress test result",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Custom Field",
|
||||||
|
"dt": "Serial No",
|
||||||
|
"fieldname": "cpu_test",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_preview": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"insert_after": "functional_grade",
|
||||||
|
"is_system_generated": 0,
|
||||||
|
"is_virtual": 0,
|
||||||
|
"label": "CPU Test",
|
||||||
|
"module": "Westech R2",
|
||||||
|
"name": "Serial No-cpu_test",
|
||||||
|
"no_copy": 0,
|
||||||
|
"non_negative": 0,
|
||||||
|
"options": "Pass\nFail",
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"show_dashboard": 0,
|
||||||
|
"sort_options": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_in_quick_entry": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"default": null,
|
||||||
|
"depends_on": null,
|
||||||
|
"description": "RAM test result",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Custom Field",
|
||||||
|
"dt": "Serial No",
|
||||||
|
"fieldname": "ram_test",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_preview": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"insert_after": "cpu_test",
|
||||||
|
"is_system_generated": 0,
|
||||||
|
"is_virtual": 0,
|
||||||
|
"label": "RAM Test",
|
||||||
|
"module": "Westech R2",
|
||||||
|
"name": "Serial No-ram_test",
|
||||||
|
"no_copy": 0,
|
||||||
|
"non_negative": 0,
|
||||||
|
"options": "Pass\nFail",
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"show_dashboard": 0,
|
||||||
|
"sort_options": 0,
|
||||||
|
"translatable": 0,
|
||||||
|
"unique": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -24,6 +24,9 @@ doc_events = {
|
|||||||
"Scheduled Pickup": {
|
"Scheduled Pickup": {
|
||||||
"before_save": "westech_r2.doctype.scheduled_pickup.scheduled_pickup.set_title",
|
"before_save": "westech_r2.doctype.scheduled_pickup.scheduled_pickup.set_title",
|
||||||
},
|
},
|
||||||
|
"Serial No": {
|
||||||
|
"validate": "westech_r2.api.serial_hooks.validate_hardware_tests",
|
||||||
|
},
|
||||||
"Load": {
|
"Load": {
|
||||||
"before_save": "westech_r2.doctype.load.load.calculate_totals",
|
"before_save": "westech_r2.doctype.load.load.calculate_totals",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -190,7 +190,7 @@
|
|||||||
"type": "Link"
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2026-05-17 17:58:07.846989",
|
"modified": "2026-05-19 06:21:55.636514",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Westech R2",
|
"module": "Westech R2",
|
||||||
"name": "Westech",
|
"name": "Westech",
|
||||||
|
|||||||
Reference in New Issue
Block a user