Compare commits
5 Commits
17584d0002
...
ee57b903d6
| Author | SHA1 | Date | |
|---|---|---|---|
| ee57b903d6 | |||
| 8f8cf5d78d | |||
| 7d8d1b1376 | |||
| f48e93f96a | |||
| 8be0cf57cf |
Binary file not shown.
Binary file not shown.
@@ -1,2 +1,3 @@
|
||||
|
||||
from westech_r2.api import sales
|
||||
from westech_r2.api import receiving_api
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -107,3 +107,21 @@ def _calculate_slots(gaylord_sizes_text):
|
||||
slots = size_map.get(size, 1)
|
||||
total += int(count) * slots
|
||||
return total or 1
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_scheduled_pickups(pickup_date=None):
|
||||
"""Get scheduled pickups for a given date."""
|
||||
if not pickup_date:
|
||||
pickup_date = frappe.utils.today()
|
||||
|
||||
pickups = frappe.get_list("Scheduled Pickup",
|
||||
filters={"pickup_date": pickup_date},
|
||||
fields=["name", "customer_number", "company_name", "estimated_items",
|
||||
"estimated_weight", "gaylord_count", "gaylord_sizes", "slots_needed",
|
||||
"latitude", "longitude", "stop_order", "truck_profile", "status",
|
||||
"contact_name", "contact_phone", "address_line", "city", "state", "zip_code"],
|
||||
order_by="stop_order asc"
|
||||
)
|
||||
|
||||
return pickups
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
import json
|
||||
import frappe
|
||||
from frappe.utils import today, getdate, add_days
|
||||
from datetime import timedelta
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_pickups(date=None):
|
||||
"""Fetch scheduled pickups with optional date filter.
|
||||
Returns pickups, calendar (next 30 days), and weekly chart data."""
|
||||
filters = []
|
||||
if date:
|
||||
filters.append(["Scheduled Pickup", "pickup_date", "=", date])
|
||||
|
||||
fields = [
|
||||
"name", "pickup_date", "pickup_type", "status", "truck", "stop_order",
|
||||
"customer_number", "company_name",
|
||||
"contact_name", "contact_phone", "contact_email",
|
||||
"address_line", "city", "state", "zip_code",
|
||||
"latitude", "longitude",
|
||||
"estimated_items", "estimated_weight", "load_contents",
|
||||
"num_labels", "data_status", "red_r2",
|
||||
"notes", "legacy_notes", "needs_aor", "needs_cod",
|
||||
]
|
||||
|
||||
pickups = frappe.get_list("Scheduled Pickup",
|
||||
fields=fields,
|
||||
filters=filters if filters else None,
|
||||
order_by="pickup_date asc, stop_order asc",
|
||||
limit_page_length=500,
|
||||
)
|
||||
|
||||
# Build calendar data (next 30 days)
|
||||
from_date = getdate(today())
|
||||
to_date = add_days(from_date, 30)
|
||||
|
||||
all_pickups = frappe.get_list("Scheduled Pickup",
|
||||
fields=["pickup_date"],
|
||||
filters=[["Scheduled Pickup", "pickup_date", ">=", str(from_date)],
|
||||
["Scheduled Pickup", "pickup_date", "<=", str(to_date)]],
|
||||
limit_page_length=500,
|
||||
)
|
||||
|
||||
pickup_counts = {}
|
||||
for p in all_pickups:
|
||||
d = p.get("pickup_date", "")
|
||||
if d:
|
||||
pickup_counts[d] = pickup_counts.get(d, 0) + 1
|
||||
|
||||
calendar = []
|
||||
for i in range(30):
|
||||
d = add_days(from_date, i)
|
||||
ds = str(d)
|
||||
calendar.append({"date": ds, "count": pickup_counts.get(ds, 0)})
|
||||
|
||||
# Build weekly chart data (last 12 weeks)
|
||||
weekly = []
|
||||
for i in range(11, -1, -1):
|
||||
week_start = add_days(from_date, -(from_date.weekday() + 7 * i))
|
||||
week_end = add_days(week_start, 6)
|
||||
count = 0
|
||||
for d_str, c in pickup_counts.items():
|
||||
try:
|
||||
d = getdate(d_str)
|
||||
if week_start <= d <= week_end:
|
||||
count += c
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
weekly.append({"label": week_start.strftime("%m/%d"), "count": count})
|
||||
|
||||
return {
|
||||
"pickups": pickups,
|
||||
"calendar": calendar,
|
||||
"weekly": weekly,
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def auto_route(date=None):
|
||||
"""Auto-assign pickups to trucks based on capacity and proximity."""
|
||||
if not date:
|
||||
date = today()
|
||||
|
||||
pickups = frappe.get_list("Scheduled Pickup",
|
||||
filters={"pickup_date": date},
|
||||
fields=["name", "company_name", "estimated_items", "estimated_weight",
|
||||
"latitude", "longitude", "pickup_type"],
|
||||
limit_page_length=200,
|
||||
)
|
||||
|
||||
if not pickups:
|
||||
return {"success": True, "assigned": 0}
|
||||
|
||||
trucks = ["Truck 1", "Truck 2", "Truck 3"]
|
||||
sorted_p = sorted(pickups, key=lambda p: (float(p.get("latitude") or 0), float(p.get("longitude") or 0)))
|
||||
n = len(sorted_p)
|
||||
assigned = 0
|
||||
|
||||
for i, p in enumerate(sorted_p):
|
||||
if p.get("pickup_type") == "Drop-off":
|
||||
truck = ""
|
||||
else:
|
||||
truck = trucks[i % 3] if n <= 3 else trucks[min(i * 3 // n, 2)]
|
||||
|
||||
doc = frappe.get_doc("Scheduled Pickup", p["name"])
|
||||
doc.truck = truck
|
||||
doc.status = "Routed" if truck else "Scheduled"
|
||||
doc.stop_order = i + 1
|
||||
doc.save()
|
||||
assigned += 1
|
||||
|
||||
frappe.db.commit()
|
||||
return {"success": True, "assigned": assigned}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_checkins():
|
||||
"""Fetch completed check-ins."""
|
||||
checkins = frappe.get_list("Scheduled Pickup",
|
||||
filters={"status": ["in", ["Complete", "In Progress"]]},
|
||||
fields=["name", "pickup_date", "pickup_type", "status",
|
||||
"company_name", "customer_number",
|
||||
"estimated_items", "estimated_weight", "load_contents",
|
||||
"data_status", "red_r2", "notes"],
|
||||
order_by="pickup_date desc",
|
||||
limit_page_length=100,
|
||||
)
|
||||
return {"checkins": checkins}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def cor_report():
|
||||
"""Generate Certificate of Recycling report."""
|
||||
pickups = frappe.get_list("Scheduled Pickup",
|
||||
filters={"status": "Complete"},
|
||||
fields=["name", "pickup_date", "company_name", "customer_number",
|
||||
"estimated_items", "estimated_weight", "load_contents",
|
||||
"data_status", "red_r2", "needs_aor", "needs_cod"],
|
||||
order_by="pickup_date desc",
|
||||
limit_page_length=200,
|
||||
)
|
||||
|
||||
html = "<!DOCTYPE html><html><head><title>CoR Report</title>"
|
||||
html += "<style>body{font-family:Arial,sans-serif;margin:40px;}"
|
||||
html += "table{border-collapse:collapse;width:100%;}"
|
||||
html += "th,td{border:1px solid #ddd;padding:8px;text-align:left;font-size:13px;}"
|
||||
html += "th{background:#2F5496;color:#fff;}h1{color:#2F5496;}</style></head><body>"
|
||||
html += "<h1>Certificate of Recycling (CoR) Report</h1>"
|
||||
html += "<p>Generated: " + frappe.utils.now() + "</p>"
|
||||
html += "<p>Total completed loads: " + str(len(pickups)) + "</p>"
|
||||
|
||||
if pickups:
|
||||
html += "<table><tr><th>Date</th><th>Customer</th><th>Items</th><th>Weight</th>"
|
||||
html += "<th>Contents</th><th>Data Status</th><th>RED/R2</th><th>AoR</th><th>CoD</th></tr>"
|
||||
for p in pickups:
|
||||
html += "<tr><td>" + str(p.get("pickup_date", "")) + "</td>"
|
||||
html += "<td>" + str(p.get("company_name", "")) + "</td>"
|
||||
html += "<td>" + str(p.get("estimated_items", "")) + "</td>"
|
||||
html += "<td>" + str(p.get("estimated_weight", "")) + "</td>"
|
||||
html += "<td>" + str(p.get("load_contents", "")) + "</td>"
|
||||
html += "<td>" + str(p.get("data_status", "")) + "</td>"
|
||||
html += "<td>" + str(p.get("red_r2", "")) + "</td>"
|
||||
html += "<td>" + ("✓" if p.get("needs_aor") else "") + "</td>"
|
||||
html += "<td>" + ("✓" if p.get("needs_cod") else "") + "</td></tr>"
|
||||
html += "</table>"
|
||||
else:
|
||||
html += "<p>No completed loads found.</p>"
|
||||
|
||||
html += "</body></html>"
|
||||
frappe.local.response["type"] = "html"
|
||||
frappe.local.response["page_content"] = html
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def print_route_sheet(date=None):
|
||||
"""Generate printable route sheet."""
|
||||
if not date:
|
||||
date = today()
|
||||
|
||||
filters = {"pickup_date": date} if date else {}
|
||||
pickups = frappe.get_list("Scheduled Pickup",
|
||||
filters=filters,
|
||||
fields=["name", "pickup_date", "pickup_type", "status", "truck", "stop_order",
|
||||
"company_name", "contact_name", "contact_phone", "contact_email",
|
||||
"address_line", "city", "state", "zip_code",
|
||||
"estimated_items", "estimated_weight", "load_contents",
|
||||
"data_status", "red_r2", "needs_aor", "needs_cod", "notes"],
|
||||
order_by="truck asc, stop_order asc",
|
||||
limit_page_length=200,
|
||||
)
|
||||
|
||||
trucks = {}
|
||||
unassigned = []
|
||||
for p in pickups:
|
||||
t = p.get("truck", "")
|
||||
if t and t != "Unassigned":
|
||||
trucks.setdefault(t, []).append(p)
|
||||
else:
|
||||
unassigned.append(p)
|
||||
|
||||
html = "<!DOCTYPE html><html><head><title>Route Sheet</title>"
|
||||
html += "<style>body{font-family:Arial,sans-serif;margin:30px;}"
|
||||
html += "table{border-collapse:collapse;width:100%;margin-bottom:12px;}"
|
||||
html += "th,td{border:1px solid #999;padding:6px 10px;text-align:left;font-size:12px;}"
|
||||
html += "th{background:#2F5496;color:#fff;}h1{color:#2F5496;font-size:20px;}"
|
||||
html += ".truck-header{background:#f0f0f0;padding:8px;font-weight:700;font-size:14px;border:1px solid #ccc;}"
|
||||
html += "@media print{body{margin:10px;}}</style></head><body>"
|
||||
html += "<h1>Route Sheet — " + str(date or "Today") + "</h1>"
|
||||
|
||||
for truck_name, stops in sorted(trucks.items()):
|
||||
html += '<div class="truck-header">🚛 ' + truck_name + " — " + str(len(stops)) + " stops</div>"
|
||||
html += "<table><tr><th>#</th><th>Customer</th><th>Address</th><th>Contact</th>"
|
||||
html += "<th>Items</th><th>Weight</th><th>Data</th><th>RED/R2</th><th>AoR</th><th>CoD</th><th>Notes</th></tr>"
|
||||
for i, s in enumerate(stops, 1):
|
||||
addr = str(s.get("address_line", "")) + ", " + str(s.get("city", "")) + ", " + str(s.get("state", "")) + " " + str(s.get("zip_code", ""))
|
||||
html += "<tr><td>" + str(i) + "</td><td>" + str(s.get("company_name", "")) + "</td>"
|
||||
html += "<td>" + addr + "</td><td>" + str(s.get("contact_name", "")) + "<br>" + str(s.get("contact_phone", "")) + "</td>"
|
||||
html += "<td>" + str(s.get("estimated_items", "")) + "</td><td>" + str(s.get("estimated_weight", "")) + "</td>"
|
||||
html += "<td>" + str(s.get("data_status", "")) + "</td><td>" + str(s.get("red_r2", "")) + "</td>"
|
||||
html += "<td>" + ("✓" if s.get("needs_aor") else "") + "</td>"
|
||||
html += "<td>" + ("✓" if s.get("needs_cod") else "") + "</td>"
|
||||
html += "<td>" + str(s.get("notes", "")) + "</td></tr>"
|
||||
html += "</table>"
|
||||
|
||||
if unassigned:
|
||||
html += "<h2>Unassigned</h2><table><tr><th>Customer</th><th>Address</th><th>Notes</th></tr>"
|
||||
for s in unassigned:
|
||||
html += "<tr><td>" + str(s.get("company_name", "")) + "</td>"
|
||||
html += "<td>" + str(s.get("address_line", "")) + "</td>"
|
||||
html += "<td>" + str(s.get("notes", "")) + "</td></tr>"
|
||||
html += "</table>"
|
||||
|
||||
html += "</body></html>"
|
||||
frappe.local.response["type"] = "html"
|
||||
frappe.local.response["page_content"] = html
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def print_green_sheet(date=None):
|
||||
"""Generate Green Sheet printout.
|
||||
⚠️ TODO: Green Sheet template needs to be filled in with the actual form layout."""
|
||||
if not date:
|
||||
date = today()
|
||||
|
||||
filters = {"pickup_date": date} if date else {}
|
||||
pickups = frappe.get_list("Scheduled Pickup",
|
||||
filters=filters,
|
||||
fields=["name", "pickup_date", "pickup_type", "company_name",
|
||||
"contact_name", "contact_phone", "address_line", "city", "state", "zip_code",
|
||||
"estimated_items", "estimated_weight", "load_contents",
|
||||
"data_status", "red_r2", "needs_aor", "needs_cod", "notes"],
|
||||
order_by="truck asc, stop_order asc",
|
||||
limit_page_length=200,
|
||||
)
|
||||
|
||||
html = "<!DOCTYPE html><html><head><title>Green Sheet</title>"
|
||||
html += "<style>body{font-family:Arial,sans-serif;margin:30px;}"
|
||||
html += "h1{color:#2E7D32;font-size:20px;border-bottom:2px solid #2E7D32;padding-bottom:4px;}"
|
||||
html += ".field-row{display:flex;margin:4px 0;}.field-label{font-weight:700;width:180px;}"
|
||||
html += ".field-value{flex:1;border-bottom:1px dotted #ccc;min-height:20px;}"
|
||||
html += ".pickup-block{border:1px solid #2E7D32;border-radius:6px;padding:12px;margin:12px 0;}"
|
||||
html += ".placeholder{color:#999;font-style:italic;font-size:11px;}"
|
||||
html += "@media print{body{margin:10px;}}</style></head><body>"
|
||||
html += "<h1>🟢 Green Sheet — " + str(date or "Today") + "</h1>"
|
||||
html += '<p class="placeholder">⚠️ This is a PLACEHOLDER template. Replace with actual Green Sheet layout once the form spec is provided.</p>'
|
||||
|
||||
for p in pickups:
|
||||
html += '<div class="pickup-block">'
|
||||
fields = [
|
||||
("Customer", p.get("company_name", "")),
|
||||
("Contact", p.get("contact_name", "")),
|
||||
("Phone", p.get("contact_phone", "")),
|
||||
("Address", str(p.get("address_line", "")) + ", " + str(p.get("city", "")) + ", " + str(p.get("state", "")) + " " + str(p.get("zip_code", ""))),
|
||||
("Est. Items", p.get("estimated_items", "")),
|
||||
("Est. Weight", p.get("estimated_weight", "")),
|
||||
("Load Contents", p.get("load_contents", "")),
|
||||
("Data Status", p.get("data_status", "")),
|
||||
("RED/R2", p.get("red_r2", "")),
|
||||
("Needs AoR", "✓" if p.get("needs_aor") else ""),
|
||||
("Needs CoD", "✓" if p.get("needs_cod") else ""),
|
||||
("Notes", p.get("notes", "")),
|
||||
]
|
||||
for label, val in fields:
|
||||
html += '<div class="field-row"><div class="field-label">' + label + ':</div><div class="field-value">' + str(val) + '</div></div>'
|
||||
html += '</div>'
|
||||
|
||||
html += "</body></html>"
|
||||
frappe.local.response["type"] = "html"
|
||||
frappe.local.response["page_content"] = html
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def print_labels(date=None):
|
||||
"""Generate printable labels."""
|
||||
if not date:
|
||||
date = today()
|
||||
|
||||
filters = {"pickup_date": date} if date else {}
|
||||
pickups = frappe.get_list("Scheduled Pickup",
|
||||
filters=filters,
|
||||
fields=["name", "company_name", "pickup_date", "num_labels", "data_status", "red_r2"],
|
||||
limit_page_length=200,
|
||||
)
|
||||
|
||||
html = "<!DOCTYPE html><html><head><title>Labels</title>"
|
||||
html += "<style>body{font-family:Arial,sans-serif;margin:10px;}"
|
||||
html += ".label{border:2px solid #000;width:3in;height:1.5in;display:inline-block;margin:4px;padding:6px;font-size:11px;page-break-inside:avoid;}"
|
||||
html += ".label-co{font-weight:700;font-size:14px;}.label-date{font-size:10px;color:#666;}"
|
||||
html += ".label-status{font-size:10px;margin-top:2px;}"
|
||||
html += "@media print{body{margin:0;}}</style></head><body>"
|
||||
|
||||
for p in pickups:
|
||||
n = p.get("num_labels") or 1
|
||||
for _ in range(n):
|
||||
html += '<div class="label">'
|
||||
html += '<div class="label-co">' + str(p.get("company_name", "")) + '</div>'
|
||||
html += '<div class="label-date">' + str(p.get("pickup_date", "")) + '</div>'
|
||||
html += '<div class="label-status">' + str(p.get("data_status", "")) + " | " + str(p.get("red_r2", "")) + '</div>'
|
||||
html += '</div>'
|
||||
|
||||
html += "</body></html>"
|
||||
frappe.local.response["type"] = "html"
|
||||
frappe.local.response["page_content"] = html
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
frappe.pages["eim-portal"].on_page_load = function(wrapper) {
|
||||
wrapper.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:60vh;font-family:sans-serif;"><div style="text-align:center;"><i class="fa fa-spinner fa-spin" style="font-size:24px;color:#2d7d46;"></i><p style="margin-top:12px;color:#555;">Redirecting to EIM Device Portal...</p></div></div>';
|
||||
setTimeout(function() { window.location.href = "https://eim.diagalon.com"; }, 500);
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"creation": "2026-05-09 14:00:00",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2026-05-09 14:00:00",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Westech R2",
|
||||
"name": "eim-portal",
|
||||
"owner": "Administrator",
|
||||
"standard": "Yes",
|
||||
"title": "EIM Device Portal"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import frappe
|
||||
|
||||
def get_context(context):
|
||||
frappe.local.flags.redirect_location = "https://eim.diagalon.com"
|
||||
raise frappe.Redirect
|
||||
@@ -0,0 +1,7 @@
|
||||
frappe.pages['eim-portal'].on_page_load = function(wrapper) {
|
||||
var page = frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: 'EIM Device Portal',
|
||||
single_column: true
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"content": null,
|
||||
"creation": "2026-05-09 14:00:00",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2026-05-09 15:09:48.653878",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Westech R2",
|
||||
"name": "eim-portal",
|
||||
"owner": "Administrator",
|
||||
"page_name": "eim-portal",
|
||||
"roles": [
|
||||
{
|
||||
"role": "All"
|
||||
}
|
||||
],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0,
|
||||
"title": "EIM Device Portal"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
.intake-station .card { border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; }
|
||||
.intake-station .card-header { padding: 15px; }
|
||||
.intake-station .card-body { padding: 20px; }
|
||||
.intake-station .form-group { margin-bottom: 15px; }
|
||||
.intake-station .form-control { border-radius: 4px; padding: 8px 12px; font-size: 16px; }
|
||||
.intake-station .form-control:focus { border-color: #6f42c1; box-shadow: 0 0 0 0.2rem rgba(111,66,193,0.25); }
|
||||
.intake-station label { font-weight: 600; margin-bottom: 4px; }
|
||||
.intake-station h5 { margin-bottom: 15px; padding-bottom: 8px; border-bottom: 2px solid #e0e0e0; }
|
||||
.intake-station .table th { background: #f8f9fa; }
|
||||
.intake-station .btn-primary { background: linear-gradient(135deg, #6f42c1, #28a745) !important; border: none !important; }
|
||||
.intake-station .label { font-size: 0.85em; }
|
||||
@@ -0,0 +1,772 @@
|
||||
frappe.pages['intake'].on_page_load = function(wrapper) {
|
||||
var page = frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: 'Intake Station',
|
||||
single_column: true
|
||||
});
|
||||
|
||||
page.set_primary_action('New Intake', function() {
|
||||
show_intake_form();
|
||||
}, 'add');
|
||||
|
||||
page.add_inner_button('Refresh', function() {
|
||||
load_recent_pallets();
|
||||
});
|
||||
|
||||
$(wrapper).find('.layout-main-section').html(`
|
||||
<div class="intake-station" style="padding: 20px;">
|
||||
<div id="intake-form-container" style="display:none;">
|
||||
<div class="card" style="margin-bottom: 20px;">
|
||||
<div class="card-header" style="background: linear-gradient(135deg, #6f42c1, #28a745); color: white; padding: 15px;">
|
||||
<img src="/files/COR_html_952bb51d.png" style="max-height: 32px; vertical-align: middle; margin-right: 10px;"><h4 style="margin:0; color: white; display: inline; vertical-align: middle;">New Intake</h4>
|
||||
</div>
|
||||
<div class="card-body" style="padding: 20px;">
|
||||
<form id="intake-form">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<h5 style="color:#6f42c1;">📅 Dates & Source</h5>
|
||||
<div class="form-group">
|
||||
<label>Received Date <span class="text-danger">*</span></label>
|
||||
<input type="date" id="received_date" class="form-control" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Weekday</label>
|
||||
<input type="text" id="weekday" class="form-control" readonly style="background:#f8f9fa;">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Customer Number <span class="text-danger">*</span></label>
|
||||
<div id="customer-number-control"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Driver</label>
|
||||
<div id="supplier-control"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Company Name</label>
|
||||
<input type="text" id="company_name" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Pickup / Drop-off</label>
|
||||
<select id="pickup" class="form-control">
|
||||
<option value="">—</option>
|
||||
<option value="Pickup">Pickup</option>
|
||||
<option value="Drop-off">Drop-off</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Notes</label>
|
||||
<textarea id="notes" class="form-control" rows="3" placeholder="Any additional notes..."></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Legacy Notes</label>
|
||||
<textarea id="legacy_notes" class="form-control" rows="2" style="background:#fafafa;" readonly title="Auto-filled from Customer record"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h5 style="color:#6f42c1;">📦 Data & Labels</h5>
|
||||
<div class="form-group">
|
||||
<label>Data Status</label>
|
||||
<select id="data_status" class="form-control">
|
||||
<option value="">—</option>
|
||||
<option value="D0">D0 (Unknown)</option>
|
||||
<option value="D1">D1 (Contains Data)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>RED / R2</label>
|
||||
<select id="red_r2" class="form-control">
|
||||
<option value="">—</option>
|
||||
<option value="RED">RED</option>
|
||||
<option value="R2">R2</option>
|
||||
<option value="Both">Both</option>
|
||||
<option value="Neither">Neither</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" id="needs_aor"> <strong>Needs AoR</strong> <small class="text-muted">(Agreement of Recycling)</small></label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" id="needs_cod"> <strong>Needs COD</strong> <small class="text-muted">(Certificate of Destruction)</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Barcode</label>
|
||||
<input type="text" id="barcode" class="form-control" placeholder="Scan barcode...">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Total Items</label>
|
||||
<input type="number" id="total_items" class="form-control" value="0" min="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Number of Labels</label>
|
||||
<input type="number" id="num_labels" class="form-control" value="1" min="1" max="20">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h5 style="color:#6f42c1;">👤 Contact & Financial</h5>
|
||||
<div class="form-group">
|
||||
<label>Contact Name</label>
|
||||
<input type="text" id="contact_name" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Contact Persons</label>
|
||||
<input type="text" id="contact_persons" class="form-control" placeholder="e.g. John Doe, Jane Smith">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Contact #</label>
|
||||
<input type="tel" id="contact_number" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Contact Email</label>
|
||||
<input type="email" id="contact_email" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Address</label>
|
||||
<input type="text" id="address_line" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Hours of Operation</label>
|
||||
<input type="text" id="hours_of_operation" class="form-control" placeholder="e.g. Mon-Fri 8am-5pm">
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-group">
|
||||
<label>Weight <span class="text-danger">*</span></label>
|
||||
<input type="text" id="weights" class="form-control" placeholder="e.g. 340 lbs" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Invoice / Check Request</label>
|
||||
<input type="text" id="invoice_check_request" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Amount</label>
|
||||
<input type="number" id="amount" class="form-control" step="0.01" value="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Paid / Received</label>
|
||||
<select id="paid_received" class="form-control">
|
||||
<option value="">—</option>
|
||||
<option value="Paid">Paid</option>
|
||||
<option value="Received">Received</option>
|
||||
<option value="Pending">Pending</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="margin-top: 20px;">
|
||||
<div class="col-md-12">
|
||||
<button type="submit" class="btn btn-primary btn-lg" style="background: linear-gradient(135deg, #6f42c1, #28a745); border: none;">
|
||||
<i class="fa fa-save"></i> Save Intake
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-lg" id="btn-print-labels" disabled>
|
||||
<i class="fa fa-print"></i> Print Labels
|
||||
</button>
|
||||
<button type="button" class="btn btn-default btn-lg" id="btn-cancel">
|
||||
Cancel
|
||||
</button>
|
||||
<span id="save-status" class="ml-3" style="font-size: 1.2em;"></span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="recent-pallets">
|
||||
<div class="card">
|
||||
<div class="card-header" style="background: #f8f9fa;">
|
||||
<h5 style="margin:0;">Recent Pallets</h5>
|
||||
</div>
|
||||
<div class="card-body" style="padding: 0;">
|
||||
<table class="table table-striped table-hover" id="pallet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Customer #</th>
|
||||
<th>Driver</th>
|
||||
<th>Received</th>
|
||||
<th>RED/R2</th>
|
||||
<th>Items</th>
|
||||
<th>Weight</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="pallet-tbody">
|
||||
<tr><td colspan="8" class="text-center">Loading...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
// Build ERPNext Link controls for Customer Number and Supplier
|
||||
setup_link_controls();
|
||||
|
||||
set_today_date();
|
||||
load_recent_pallets();
|
||||
|
||||
$('#received_date').on('change', function() {
|
||||
var d = new Date($(this).val() + 'T12:00:00');
|
||||
var days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
|
||||
$('#weekday').val(days[d.getDay()] || '');
|
||||
});
|
||||
|
||||
$('#intake-form').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
save_pallet();
|
||||
});
|
||||
|
||||
$('#btn-cancel').on('click', function() {
|
||||
$('#intake-form-container').hide();
|
||||
$('#recent-pallets').show();
|
||||
clear_form();
|
||||
});
|
||||
|
||||
$('#btn-print-labels').on('click', function() {
|
||||
print_labels();
|
||||
});
|
||||
};
|
||||
|
||||
// ── ERPNext Link Controls ──────────────────────────────────────────────
|
||||
|
||||
var customer_number_control = null;
|
||||
var supplier_control = null;
|
||||
|
||||
function setup_link_controls() {
|
||||
// Customer Number — Link to Supplier, searching by name (which IS the customer number)
|
||||
customer_number_control = frappe.ui.form.make_control({
|
||||
parent: $('#customer-number-control'),
|
||||
df: {
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'customer_number',
|
||||
options: 'Customer',
|
||||
label: 'Customer Number',
|
||||
reqd: 1,
|
||||
placeholder: 'Search customer number...',
|
||||
onchange: function() {
|
||||
var val = customer_number_control.get_value();
|
||||
if (val) {
|
||||
fetch_customer_details(val);
|
||||
} else {
|
||||
clear_customer_fields();
|
||||
}
|
||||
}
|
||||
},
|
||||
only_input: true,
|
||||
});
|
||||
customer_number_control.refresh();
|
||||
// Style to match form
|
||||
$('#customer-number-control .control-input').css('margin', '0');
|
||||
$('#customer-number-control .help-box').remove();
|
||||
|
||||
// Supplier (Driver) — Link to Supplier for driver name
|
||||
supplier_control = frappe.ui.form.make_control({
|
||||
parent: $('#supplier-control'),
|
||||
df: {
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'supplier',
|
||||
options: 'Customer',
|
||||
label: 'Driver',
|
||||
placeholder: 'Search driver...',
|
||||
onchange: function() {
|
||||
var val = supplier_control.get_value();
|
||||
if (val && val !== customer_number_control.get_value()) {
|
||||
// Driver selected separately — don't override customer_number
|
||||
}
|
||||
}
|
||||
},
|
||||
only_input: true,
|
||||
});
|
||||
supplier_control.refresh();
|
||||
$('#supplier-control .control-input').css('margin', '0');
|
||||
$('#supplier-control .help-box').remove();
|
||||
}
|
||||
|
||||
function fetch_customer_details(customer_name) {
|
||||
frappe.call({
|
||||
method: 'frappe.client.get',
|
||||
args: {doctype: 'Customer', name: customer_name},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
var c = r.message;
|
||||
// Auto-fill company name
|
||||
if (c.customer_name && !$('#company_name').val()) {
|
||||
$('#company_name').val(c.customer_name);
|
||||
}
|
||||
// Auto-fill custom CRM fields
|
||||
if (c.contact_persons && !$('#contact_persons').val()) {
|
||||
$('#contact_persons').val(c.contact_persons);
|
||||
}
|
||||
if (c.hours_of_operation && !$('#hours_of_operation').val()) {
|
||||
$('#hours_of_operation').val(c.hours_of_operation);
|
||||
}
|
||||
if (c.legacy_notes && !$('#legacy_notes').val()) {
|
||||
$('#legacy_notes').val(c.legacy_notes);
|
||||
}
|
||||
// Driver is independent — don't auto-populate from customer number
|
||||
|
||||
// Fill contact fields from Supplier doc's native fields first
|
||||
// ERPNext auto-populates these from the primary Contact/Address
|
||||
if (c.customer_primary_contact) {
|
||||
// Fetch the Contact doc for name/phone/email
|
||||
frappe.call({
|
||||
method: 'frappe.client.get',
|
||||
args: {doctype: 'Contact', name: c.customer_primary_contact},
|
||||
callback: function(cr) {
|
||||
if (cr.message) {
|
||||
var ct = cr.message;
|
||||
var full_name = [ct.first_name, ct.last_name].filter(Boolean).join(' ');
|
||||
if (full_name && !$('#contact_name').val()) {
|
||||
$('#contact_name').val(full_name);
|
||||
}
|
||||
if (ct.email_id && !$('#contact_email').val()) {
|
||||
$('#contact_email').val(ct.email_id);
|
||||
}
|
||||
if (ct.phone && !$('#contact_number').val()) {
|
||||
$('#contact_number').val(ct.phone);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Fallback: use Supplier-level fields (mobile_no, email_id)
|
||||
if (c.mobile_no && !$('#contact_number').val()) {
|
||||
$('#contact_number').val(c.mobile_no);
|
||||
}
|
||||
if (c.email_id && !$('#contact_email').val()) {
|
||||
$('#contact_email').val(c.email_id);
|
||||
}
|
||||
|
||||
// Last resort: search for any linked Contact
|
||||
frappe.call({
|
||||
method: 'frappe.client.get_list',
|
||||
args: {
|
||||
doctype: 'Contact',
|
||||
filters: [['Dynamic Link', 'link_name', '=', customer_name]],
|
||||
fields: ['name', 'first_name', 'last_name', 'email_id', 'phone'],
|
||||
limit_page_length: 1
|
||||
},
|
||||
callback: function(cr) {
|
||||
if (cr.message && cr.message.length > 0) {
|
||||
var c = cr.message[0];
|
||||
var full_name = [c.first_name, c.last_name].filter(Boolean).join(' ');
|
||||
if (full_name && !$('#contact_name').val()) {
|
||||
$('#contact_name').val(full_name);
|
||||
}
|
||||
if (c.email_id && !$('#contact_email').val()) {
|
||||
$('#contact_email').val(c.email_id);
|
||||
}
|
||||
if (c.phone && !$('#contact_number').val()) {
|
||||
$('#contact_number').val(c.phone);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fill address from Supplier doc's primary_address or linked Address
|
||||
if (c.primary_address && !$('#address_line').val()) {
|
||||
$('#address_line').val(c.primary_address);
|
||||
} else if (c.customer_primary_address) {
|
||||
frappe.call({
|
||||
method: 'frappe.client.get',
|
||||
args: {doctype: 'Address', name: c.customer_primary_address},
|
||||
callback: function(ar) {
|
||||
if (ar.message) {
|
||||
var a = ar.message;
|
||||
var addr = [a.address_line1, a.address_line2, a.city, a.state].filter(Boolean).join(', ');
|
||||
if (addr && !$('#address_line').val()) {
|
||||
$('#address_line').val(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Fallback: search for any linked Address
|
||||
frappe.call({
|
||||
method: 'frappe.client.get_list',
|
||||
args: {
|
||||
doctype: 'Address',
|
||||
filters: [['Dynamic Link', 'link_name', '=', customer_name]],
|
||||
fields: ['name', 'address_line1', 'city', 'state'],
|
||||
limit_page_length: 1
|
||||
},
|
||||
callback: function(ar) {
|
||||
if (ar.message && ar.message.length > 0) {
|
||||
var a = ar.message[0];
|
||||
var addr = [a.address_line1, a.city, a.state].filter(Boolean).join(', ');
|
||||
if (addr && !$('#address_line').val()) {
|
||||
$('#address_line').val(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clear_customer_fields() {
|
||||
$('#company_name').val('');
|
||||
$('#contact_name').val('');
|
||||
$('#contact_number').val('');
|
||||
$('#contact_email').val('');
|
||||
$('#address_line').val('');
|
||||
if (supplier_control) supplier_control.set_value('');
|
||||
}
|
||||
|
||||
// ── Form Helpers ──────────────────────────────────────────────────────
|
||||
|
||||
function set_today_date() {
|
||||
var today = new Date().toISOString().split('T')[0];
|
||||
$('#received_date').val(today);
|
||||
$('#received_date').trigger('change');
|
||||
}
|
||||
|
||||
function show_intake_form() {
|
||||
$('#intake-form-container').show();
|
||||
$('#recent-pallets').hide();
|
||||
set_today_date();
|
||||
// Focus the customer number control
|
||||
setTimeout(function() {
|
||||
if (customer_number_control) {
|
||||
customer_number_control.$input.focus();
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function clear_form() {
|
||||
$('#intake-form input[type="text"], #intake-form input[type="tel"], #intake-form input[type="email"], #intake-form input[type="number"], #intake-form input[type="date"], #intake-form textarea').val('');
|
||||
$('#legacy_notes').val('');
|
||||
$('#intake-form select').val('');
|
||||
$('#total_items').val('0');
|
||||
$('#num_labels').val('1');
|
||||
$('#amount').val('0');
|
||||
$('#needs_aor').prop('checked', false);
|
||||
$('#needs_cod').prop('checked', false);
|
||||
$('#btn-print-labels').prop('disabled', true);
|
||||
$('#save-status').text('');
|
||||
$('#intake-form-container').data('pallet-name', '');
|
||||
if (customer_number_control) customer_number_control.set_value('');
|
||||
if (supplier_control) supplier_control.set_value('');
|
||||
}
|
||||
|
||||
function save_pallet() {
|
||||
var pallet_name = $('#intake-form-container').data('pallet-name');
|
||||
var is_new = !pallet_name;
|
||||
|
||||
var data = {
|
||||
doctype: 'Pallet',
|
||||
received_date: $('#received_date').val(),
|
||||
customer_number: customer_number_control ? customer_number_control.get_value() : '',
|
||||
customer: customer_number_control ? customer_number_control.get_value() : '',
|
||||
supplier: supplier_control ? supplier_control.get_value() : '',
|
||||
company_name: $('#company_name').val(),
|
||||
pickup: $('#pickup').val(),
|
||||
data_status: $('#data_status').val(),
|
||||
red_r2: $('#red_r2').val(),
|
||||
barcode: $('#barcode').val(),
|
||||
total_items: parseInt($('#total_items').val()) || 0,
|
||||
num_labels: parseInt($('#num_labels').val()) || 1,
|
||||
contact_name: $('#contact_name').val(),
|
||||
contact_persons: $('#contact_persons').val(),
|
||||
contact_number: $('#contact_number').val(),
|
||||
contact_email: $('#contact_email').val(),
|
||||
address_line: $('#address_line').val(),
|
||||
hours_of_operation: $('#hours_of_operation').val(),
|
||||
legacy_notes: $('#legacy_notes').val(),
|
||||
weights: $('#weights').val(),
|
||||
invoice_check_request: $('#invoice_check_request').val(),
|
||||
amount: parseFloat($('#amount').val()) || 0,
|
||||
paid_received: $('#paid_received').val(),
|
||||
needs_aor: $('#needs_aor').is(':checked') ? 1 : 0,
|
||||
needs_cod: $('#needs_cod').is(':checked') ? 1 : 0,
|
||||
notes: $('#notes').val(),
|
||||
status: 'Received'
|
||||
};
|
||||
|
||||
if (!is_new) {
|
||||
data.name = pallet_name;
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: is_new ? 'frappe.client.insert' : 'frappe.client.update',
|
||||
args: { doc: data },
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
var name = r.message.name;
|
||||
$('#save-status').html('<span class="text-success"><i class="fa fa-check"></i> Saved as Intake ' + name + '</span>');
|
||||
$('#btn-print-labels').prop('disabled', false);
|
||||
if (is_new) {
|
||||
$('#intake-form-container').data('pallet-name', name);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function(r) {
|
||||
var msg = 'Unknown error';
|
||||
if (r && r._server_messages) {
|
||||
try {
|
||||
var msgs = JSON.parse(r._server_messages);
|
||||
if (msgs && msgs.length) {
|
||||
var errObj = JSON.parse(msgs[0]);
|
||||
msg = errObj.message || msg;
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
$('#save-status').html('<span class="text-danger"><i class="fa fa-exclamation"></i> Error: ' + msg + '</span>');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function load_recent_pallets() {
|
||||
frappe.call({
|
||||
method: 'frappe.client.get_list',
|
||||
args: {
|
||||
doctype: 'Pallet',
|
||||
fields: ['name', 'pallet_number', 'status', 'customer_number', 'company_name', 'supplier', 'received_date', 'red_r2', 'total_items', 'weights', 'notes', 'needs_aor', 'needs_cod'],
|
||||
limit_page_length: 20,
|
||||
order_by: 'creation desc'
|
||||
},
|
||||
callback: function(r) {
|
||||
var tbody = $('#pallet-tbody');
|
||||
tbody.empty();
|
||||
if (!r.message || r.message.length === 0) {
|
||||
tbody.append('<tr><td colspan="8" class="text-center">No pallets yet</td></tr>');
|
||||
return;
|
||||
}
|
||||
r.message.forEach(function(p) {
|
||||
var status_class = {
|
||||
'Received': 'info',
|
||||
'Sorting': 'warning',
|
||||
'Processing': 'primary',
|
||||
'Complete': 'success',
|
||||
'Shipped': 'default'
|
||||
}[p.status] || 'default';
|
||||
|
||||
tbody.append(
|
||||
'<tr>' +
|
||||
'<td><span class="label label-' + status_class + '">' + (p.status || 'Received') + '</span></td>' +
|
||||
'<td><strong>' + (p.customer_number || '') + '</strong></td>' +
|
||||
'<td>' + (p.supplier || '') + '</td>' +
|
||||
'<td>' + (p.received_date || '') + '</td>' +
|
||||
'<td>' + (p.red_r2 || '') +
|
||||
(p.needs_aor ? ' <span class="label label-warning">AoR</span>' : '') +
|
||||
(p.needs_cod ? ' <span class="label label-danger">COD</span>' : '') +
|
||||
'</td>' +
|
||||
'<td>' + (p.total_items || 0) + '</td>' +
|
||||
'<td>' + (p.weights || '') + '</td>' +
|
||||
'<td>' +
|
||||
'<button class="btn btn-xs btn-default" onclick="edit_pallet(\'' + p.name + '\')"><i class="fa fa-edit"></i></button> ' +
|
||||
'<button class="btn btn-xs btn-default" onclick="print_pallet_labels(\'' + p.name + '\')"><i class="fa fa-print"></i></button>' +
|
||||
'</td>' +
|
||||
'</tr>'
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function edit_pallet(name) {
|
||||
frappe.call({
|
||||
method: 'frappe.client.get',
|
||||
args: {doctype: 'Pallet', name: name},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
var d = r.message;
|
||||
$('#intake-form-container').show();
|
||||
$('#recent-pallets').hide();
|
||||
$('#intake-form-container').data('pallet-name', name);
|
||||
$('#received_date').val(d.received_date || '');
|
||||
if (customer_number_control) customer_number_control.set_value(d.customer_number || '');
|
||||
if (supplier_control) supplier_control.set_value(d.supplier || '');
|
||||
$('#company_name').val(d.company_name || '');
|
||||
$('#pickup').val(d.pickup || '');
|
||||
$('#data_status').val(d.data_status || '');
|
||||
$('#red_r2').val(d.red_r2 || '');
|
||||
$('#barcode').val(d.barcode || '');
|
||||
$('#total_items').val(d.total_items || 0);
|
||||
$('#num_labels').val(d.num_labels || 1);
|
||||
$('#contact_name').val(d.contact_name || '');
|
||||
$('#contact_number').val(d.contact_number || '');
|
||||
$('#contact_email').val(d.contact_email || '');
|
||||
$('#address_line').val(d.address_line || '');
|
||||
$('#weights').val(d.weights || '');
|
||||
$('#invoice_check_request').val(d.invoice_check_request || '');
|
||||
$('#amount').val(d.amount || 0);
|
||||
$('#paid_received').val(d.paid_received || '');
|
||||
$('#needs_aor').prop('checked', d.needs_aor === 1);
|
||||
$('#needs_cod').prop('checked', d.needs_cod === 1);
|
||||
$('#notes').val(d.notes || '');
|
||||
$('#received_date').trigger('change');
|
||||
$('#btn-print-labels').prop('disabled', false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function print_labels() {
|
||||
var pallet_name = $('#intake-form-container').data('pallet-name');
|
||||
if (!pallet_name) return;
|
||||
print_pallet_labels(pallet_name);
|
||||
}
|
||||
|
||||
function print_pallet_labels(name) {
|
||||
frappe.call({
|
||||
method: 'frappe.client.get',
|
||||
args: {doctype: 'Pallet', name: name},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
generate_zpl_label(r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function generate_zpl_label(d) {
|
||||
var label_count = d.num_labels || 1;
|
||||
var date_str = d.received_date || '';
|
||||
var driver = d.supplier || '';
|
||||
var customer_num = d.customer_number || '';
|
||||
var red_r2 = d.red_r2 || '';
|
||||
var weight = d.weights || '';
|
||||
var pallet_num = d.pallet_number || d.name || '';
|
||||
var qr_url = window.location.origin + '/app/pallet/' + (d.name || pallet_num);
|
||||
|
||||
var logo_url = window.location.origin + '/files/COR_html_952bb51d.png';
|
||||
|
||||
var labels_html = '';
|
||||
for (var i = 1; i <= label_count; i++) {
|
||||
labels_html += '<div class="label-card">';
|
||||
labels_html += '<div class="label-header"><img src="' + logo_url + '" style="max-height: 36px; vertical-align: middle;"> <span style="vertical-align: middle;">WESTECH RECYCLERS</span></div>';
|
||||
labels_html += '<div class="label-body">';
|
||||
labels_html += '<div class="label-top">';
|
||||
labels_html += '<div class="label-info">';
|
||||
labels_html += '<div class="label-row"><span class="label-field">Pallet #:</span> <span class="label-value">' + pallet_num + '</span></div>';
|
||||
if (customer_num) {
|
||||
labels_html += '<div class="label-row"><span class="label-field">Customer #:</span> <span class="label-value">' + customer_num + '</span></div>';
|
||||
}
|
||||
labels_html += '<div class="label-row"><span class="label-field">Received:</span> <span class="label-value">' + date_str + '</span></div>';
|
||||
labels_html += '<div class="label-row"><span class="label-field">Driver:</span> <span class="label-value">' + driver + '</span></div>';
|
||||
labels_html += '<div class="label-row"><span class="label-field">Weight:</span> <span class="label-value">' + weight + '</span></div>';
|
||||
labels_html += '<div class="label-row"><span class="label-field">Items:</span> <span class="label-value">' + (d.total_items || 0) + '</span></div>';
|
||||
labels_html += '</div>';
|
||||
labels_html += '<div class="label-qr" id="qr-' + i + '"></div>';
|
||||
labels_html += '</div>';
|
||||
if (red_r2) {
|
||||
labels_html += '<div class="label-badge">' + red_r2 + '</div>';
|
||||
}
|
||||
labels_html += '<div class="label-footer">Label ' + i + ' of ' + label_count + '</div>';
|
||||
labels_html += '</div></div>';
|
||||
}
|
||||
|
||||
var existing = document.getElementById('label-preview-overlay');
|
||||
if (existing) existing.remove();
|
||||
|
||||
var overlay = document.createElement('div');
|
||||
overlay.id = 'label-preview-overlay';
|
||||
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);z-index:9999;display:flex;align-items:center;justify-content:center;';
|
||||
|
||||
var modal = document.createElement('div');
|
||||
modal.style.cssText = 'background:#fff;border-radius:8px;padding:20px;max-height:90vh;overflow-y:auto;box-shadow:0 4px 20px rgba(0,0,0,0.3);max-width:95vw;';
|
||||
|
||||
var title = document.createElement('h3');
|
||||
title.textContent = 'Label Preview — Pallet ' + pallet_num + ' (' + label_count + ' label' + (label_count > 1 ? 's' : '') + ')';
|
||||
title.style.cssText = 'margin:0 0 15px 0;font-size:16pt;';
|
||||
|
||||
var btnGroup = document.createElement('div');
|
||||
btnGroup.style.cssText = 'margin-bottom:15px;display:flex;gap:10px;';
|
||||
|
||||
var printBtn = document.createElement('button');
|
||||
printBtn.className = 'btn btn-primary';
|
||||
printBtn.innerHTML = '<i class="fa fa-print"></i> Print Labels';
|
||||
printBtn.onclick = function() {
|
||||
var pw = window.open('', '_blank');
|
||||
pw.document.write('<html><head><title>Labels — Pallet ' + pallet_num + '</title>');
|
||||
pw.document.write('<script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"><\/script>');
|
||||
pw.document.write('<style>');
|
||||
pw.document.write('@page { size: 4in 6in; margin: 0; }');
|
||||
pw.document.write('body { font-family: Arial, Helvetica, sans-serif; margin: 0; padding: 5mm; }');
|
||||
pw.document.write('.label-card { width: 100%; max-width: 3.5in; border: 2px solid #000; margin-bottom: 10px; page-break-inside: avoid; }');
|
||||
pw.document.write('.label-header { background: #000; color: #fff; font-size: 18pt; font-weight: bold; text-align: center; padding: 6px 0; letter-spacing: 2px; }');
|
||||
pw.document.write('.label-body { padding: 8px 12px; }');
|
||||
pw.document.write('.label-top { display: flex; justify-content: space-between; align-items: flex-start; }');
|
||||
pw.document.write('.label-info { flex: 1; }');
|
||||
pw.document.write('.label-qr { flex-shrink: 0; margin-left: 10px; }');
|
||||
pw.document.write('.label-qr table { border-collapse: collapse; }');
|
||||
pw.document.write('.label-qr td { width: 4px; height: 4px; }');
|
||||
pw.document.write('.label-row { font-size: 13pt; padding: 2px 0; }');
|
||||
pw.document.write('.label-field { font-weight: bold; display: inline-block; min-width: 100px; }');
|
||||
pw.document.write('.label-value { }');
|
||||
pw.document.write('.label-badge { margin-top: 6px; padding: 4px 12px; background: #d9534f; color: #fff; font-size: 14pt; font-weight: bold; text-align: center; border-radius: 3px; display: inline-block; }');
|
||||
pw.document.write('.label-footer { font-size: 10pt; color: #666; text-align: center; margin-top: 4px; }');
|
||||
pw.document.write('@media print { body { padding: 0; } .label-card { margin-bottom: 0; page-break-after: always; } .label-card:last-child { page-break-after: auto; } }');
|
||||
pw.document.write('</style></head><body>');
|
||||
pw.document.write(labels_html);
|
||||
pw.document.write('<script>');
|
||||
pw.document.write('var qr_url = ' + JSON.stringify(qr_url) + ';');
|
||||
pw.document.write('var qr = qrcode(0, "M"); qr.addData(qr_url); qr.make();');
|
||||
pw.document.write('for (var i = 1; i <= ' + label_count + '; i++) { var el = document.getElementById("qr-" + i); if (el) { el.innerHTML = qr.createImgTag(3, 0); } }');
|
||||
pw.document.write('setTimeout(function() { window.print(); }, 500);');
|
||||
pw.document.write('<\/script>');
|
||||
pw.document.write('</body></html>');
|
||||
pw.document.close();
|
||||
};
|
||||
|
||||
var closeBtn = document.createElement('button');
|
||||
closeBtn.className = 'btn btn-default';
|
||||
closeBtn.textContent = 'Cancel';
|
||||
closeBtn.onclick = function() {
|
||||
overlay.remove();
|
||||
};
|
||||
|
||||
btnGroup.appendChild(printBtn);
|
||||
btnGroup.appendChild(closeBtn);
|
||||
|
||||
var previewArea = document.createElement('div');
|
||||
previewArea.style.cssText = 'display:flex;flex-wrap:wrap;gap:15px;justify-content:center;';
|
||||
previewArea.innerHTML = labels_html;
|
||||
|
||||
var previewStyles = document.createElement('style');
|
||||
previewStyles.textContent =
|
||||
'#label-preview-overlay .label-card { width: 4in; border: 2px solid #000; background: #fff; box-shadow: 1px 1px 6px rgba(0,0,0,0.15); }' +
|
||||
'#label-preview-overlay .label-header { background: #000; color: #fff; font-size: 18pt; font-weight: bold; text-align: center; padding: 6px 0; letter-spacing: 2px; }' +
|
||||
'#label-preview-overlay .label-body { padding: 8px 12px; }' +
|
||||
'#label-preview-overlay .label-top { display: flex; justify-content: space-between; align-items: flex-start; }' +
|
||||
'#label-preview-overlay .label-info { flex: 1; }' +
|
||||
'#label-preview-overlay .label-qr { flex-shrink: 0; margin-left: 10px; }' +
|
||||
'#label-preview-overlay .label-row { font-size: 13pt; padding: 2px 0; }' +
|
||||
'#label-preview-overlay .label-field { font-weight: bold; display: inline-block; min-width: 100px; }' +
|
||||
'#label-preview-overlay .label-badge { margin-top: 6px; padding: 4px 12px; background: #d9534f; color: #fff; font-size: 14pt; font-weight: bold; text-align: center; border-radius: 3px; display: inline-block; }' +
|
||||
'#label-preview-overlay .label-footer { font-size: 10pt; color: #666; text-align: center; margin-top: 4px; }';
|
||||
|
||||
modal.appendChild(title);
|
||||
modal.appendChild(btnGroup);
|
||||
modal.appendChild(previewStyles);
|
||||
modal.appendChild(previewArea);
|
||||
overlay.appendChild(modal);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
// Generate QR codes in preview
|
||||
var script = document.createElement('script');
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js';
|
||||
script.onload = function() {
|
||||
var qr = qrcode(0, 'M');
|
||||
qr.addData(qr_url);
|
||||
qr.make();
|
||||
for (var i = 1; i <= label_count; i++) {
|
||||
var el = document.getElementById('qr-' + i);
|
||||
if (el) el.innerHTML = qr.createImgTag(3, 0);
|
||||
}
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
|
||||
overlay.addEventListener('click', function(e) {
|
||||
if (e.target === overlay) overlay.remove();
|
||||
});
|
||||
}
|
||||
|
||||
window.edit_pallet = edit_pallet;
|
||||
window.print_pallet_labels = print_pallet_labels;
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"content": null,
|
||||
"creation": "2026-05-09 12:05:32.403207",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2026-05-09 15:09:48.807066",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Westech R2",
|
||||
"name": "intake",
|
||||
"owner": "Administrator",
|
||||
"page_name": "intake",
|
||||
"roles": [
|
||||
{
|
||||
"role": "All"
|
||||
}
|
||||
],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0,
|
||||
"title": "Intake Station"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
from frappe import _
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
context.title = _("Intake Station")
|
||||
@@ -0,0 +1,3 @@
|
||||
import frappe
|
||||
|
||||
no_cache = 1
|
||||
@@ -0,0 +1,3 @@
|
||||
import frappe
|
||||
|
||||
no_cache = 1
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
frappe.pages["r2-tracking"].on_page_load = function(wrapper) {
|
||||
wrapper.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:60vh;font-family:sans-serif;"><div style="text-align:center;"><i class="fa fa-spinner fa-spin" style="font-size:24px;color:#1a6b8a;"></i><p style="margin-top:12px;color:#555;">Redirecting to R2 Data Tracking...</p></div></div>';
|
||||
setTimeout(function() { window.location.href = "https://eim.diagalon.com/report/data-tracking-form"; }, 500);
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"creation": "2026-05-09 14:00:00",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2026-05-09 14:00:00",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Westech R2",
|
||||
"name": "r2-tracking",
|
||||
"owner": "Administrator",
|
||||
"standard": "Yes",
|
||||
"title": "R2 Data Tracking"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import frappe
|
||||
|
||||
def get_context(context):
|
||||
frappe.local.flags.redirect_location = "https://eim.diagalon.com/report/data-tracking-form"
|
||||
raise frappe.Redirect
|
||||
@@ -0,0 +1,7 @@
|
||||
frappe.pages['r2-tracking'].on_page_load = function(wrapper) {
|
||||
var page = frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: 'R2 Data Tracking',
|
||||
single_column: true
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"content": null,
|
||||
"creation": "2026-05-09 14:00:00",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2026-05-09 15:09:48.707863",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Westech R2",
|
||||
"name": "r2-tracking",
|
||||
"owner": "Administrator",
|
||||
"page_name": "r2-tracking",
|
||||
"roles": [
|
||||
{
|
||||
"role": "All"
|
||||
}
|
||||
],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0,
|
||||
"title": "R2 Data Tracking"
|
||||
}
|
||||
@@ -0,0 +1,598 @@
|
||||
frappe.pages['receiving'].on_page_load = function(wrapper) {
|
||||
var page = frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: 'Receiving',
|
||||
single_column: true
|
||||
});
|
||||
|
||||
// Inline HTML — same pattern as intake.js
|
||||
$(wrapper).find('.layout-main-section').html(`
|
||||
<div class="receiving-station" style="padding: 20px;">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h3 style="margin-top: 0; color: #2F5496;">🚛 Receiving</h3>
|
||||
<p class="text-muted">Schedule pickups, manage routes, and check in loads.</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="nav nav-tabs" role="tablist" id="receiving-tabs">
|
||||
<li role="presentation" class="active"><a href="#stage-a" role="tab" data-toggle="tab">📋 Stage A — Schedule Pickup</a></li>
|
||||
<li role="presentation"><a href="#stage-b" role="tab" data-toggle="tab">🗺️ Stage B — Route & Dispatch</a></li>
|
||||
<li role="presentation"><a href="#stage-c" role="tab" data-toggle="tab">⚖️ Stage C — Load Check-in</a></li>
|
||||
</ul>
|
||||
<div class="tab-content" style="padding-top: 20px;">
|
||||
<div role="tabpanel" class="tab-pane active" id="stage-a">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">📅 Pickup Calendar — Next 30 Days</div>
|
||||
<div class="panel-body" id="pickup-calendar"><div class="text-muted text-center">Loading...</div></div>
|
||||
</div>
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">📊 Weekly Pickup Volume</div>
|
||||
<div class="panel-body" style="padding: 5px;"><canvas id="weekly-chart" height="180"></canvas></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-6"><strong>Scheduled Pickups</strong><span id="pickup-count-label" class="text-muted" style="margin-left: 8px;"></span></div>
|
||||
<div class="col-md-6 text-right">
|
||||
<button class="btn btn-primary btn-sm" id="btn-new-pickup">+ New Pickup</button>
|
||||
<input type="date" id="pickup-date-filter" class="form-control input-sm" style="display:inline-block;width:auto;vertical-align:middle;margin-left:8px;">
|
||||
<button class="btn btn-default btn-sm" id="btn-clear-date" style="margin-left:4px;">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body" style="padding: 0; overflow-x: auto;">
|
||||
<table class="table table-striped table-hover" id="pickup-table" style="font-size: 13px; margin-bottom: 0;">
|
||||
<thead><tr><th>Date</th><th>Weekday</th><th>Type</th><th>Customer</th><th>Contact</th><th>Address</th><th>Est. Items</th><th>Data</th><th>RED/R2</th><th>Status</th><th>Notes</th><th>Truck</th><th>AoR</th><th>CoD</th></tr></thead>
|
||||
<tbody id="pickup-tbody"><tr><td colspan="14" class="text-center text-muted">Loading...</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="new-pickup-form" style="display:none; margin-top: 16px;">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">+ New Scheduled Pickup</div>
|
||||
<div class="panel-body">
|
||||
<form id="pickup-form">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<h5 style="color:#6f42c1;">📅 Pickup Info</h5>
|
||||
<div class="form-group"><label>Pickup Date <span class="text-danger">*</span></label><input type="date" id="sp-pickup_date" class="form-control" required></div>
|
||||
<div class="form-group"><label>Type <span class="text-danger">*</span></label><select id="sp-pickup_type" class="form-control" required><option value="Pickup">Pickup</option><option value="Drop-off">Drop-off</option></select></div>
|
||||
<div class="form-group"><label>Customer <span class="text-danger">*</span></label><div id="sp-customer-control"></div></div>
|
||||
<div class="form-group"><label>Company Name</label><input type="text" id="sp-company_name" class="form-control" readonly style="background:#f8f9fa;"></div>
|
||||
<div class="form-group"><label>Contact Name</label><input type="text" id="sp-contact_name" class="form-control"></div>
|
||||
<div class="form-group"><label>Contact Phone</label><input type="text" id="sp-contact_phone" class="form-control"></div>
|
||||
<div class="form-group"><label>Contact Email</label><input type="email" id="sp-contact_email" class="form-control"></div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h5 style="color:#6f42c1;">📍 Address</h5>
|
||||
<div class="form-group"><label>Street Address</label><input type="text" id="sp-address_line" class="form-control"></div>
|
||||
<div class="form-group"><label>City</label><input type="text" id="sp-city" class="form-control"></div>
|
||||
<div class="form-group"><label>State</label><input type="text" id="sp-state" class="form-control" value="AZ"></div>
|
||||
<div class="form-group"><label>ZIP</label><input type="text" id="sp-zip_code" class="form-control"></div>
|
||||
<div class="form-group"><label>Hours of Operation</label><input type="text" id="sp-hours_of_operation" class="form-control" placeholder="e.g. Mon-Fri 8am-5pm"></div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h5 style="color:#6f42c1;">📦 Load Info</h5>
|
||||
<div class="form-group"><label>Estimated Items</label><input type="number" id="sp-estimated_items" class="form-control"></div>
|
||||
<div class="form-group"><label>Estimated Weight</label><input type="text" id="sp-estimated_weight" class="form-control"></div>
|
||||
<div class="form-group"><label>Load Contents</label><input type="text" id="sp-load_contents" class="form-control" placeholder="Wire, Monitors, Laptops..."></div>
|
||||
<div class="form-group"><label>Data Status</label><select id="sp-data_status" class="form-control"><option value="">—</option><option value="D0">D0</option><option value="D1">D1</option><option value="ND1">ND1</option><option value="ND2">ND2</option><option value="ND3">ND3</option><option value="ND4">ND4</option></select></div>
|
||||
<div class="form-group"><label>RED / R2</label><select id="sp-red_r2" class="form-control"><option value="">—</option><option value="RED">RED</option><option value="R2">R2</option><option value="Both">Both</option><option value="Neither">Neither</option></select></div>
|
||||
<div class="form-group"><div class="checkbox"><label><input type="checkbox" id="sp-needs_aor"> <strong>Needs AoR</strong></label></div><div class="checkbox"><label><input type="checkbox" id="sp-needs_cod"> <strong>Needs CoD</strong></label></div></div>
|
||||
<div class="form-group"><label>Notes</label><textarea id="sp-notes" class="form-control" rows="2"></textarea></div>
|
||||
<div class="form-group"><label>Legacy Notes</label><textarea id="sp-legacy_notes" class="form-control" rows="2" style="background:#fafafa;" readonly></textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="margin-top: 16px;"><div class="col-md-12"><button type="submit" class="btn btn-primary btn-lg">Save Pickup</button><button type="button" class="btn btn-default btn-lg" id="btn-cancel-pickup">Cancel</button></div></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="stage-b">
|
||||
<div class="row" style="margin-bottom: 16px;"><div class="col-md-12"><div class="btn-group"><input type="date" id="route-date" class="form-control" style="display:inline-block;width:auto;"><button class="btn btn-primary" id="btn-load-routes">Load Pickups</button><button class="btn btn-primary" id="btn-auto-route">🧮 Auto-Route</button><button class="btn btn-success" id="btn-route-sheet">🖨️ Route Sheet</button><button class="btn btn-success" id="btn-green-sheet">📄 Green Sheet</button><button class="btn btn-success" id="btn-labels">🏷️ Labels</button></div></div></div>
|
||||
<div class="row" id="route-columns">
|
||||
<div class="col-md-4"><div class="panel panel-default truck-column" data-truck="Truck 1"><div class="panel-heading">🚛 Truck 1 <span id="truck1-count" class="text-muted"></span></div><div class="panel-body truck-stops" id="truck1-stops"></div></div></div>
|
||||
<div class="col-md-4"><div class="panel panel-default truck-column" data-truck="Truck 2"><div class="panel-heading">🚛 Truck 2 <span id="truck2-count" class="text-muted"></span></div><div class="panel-body truck-stops" id="truck2-stops"></div></div></div>
|
||||
<div class="col-md-4"><div class="panel panel-default truck-column" data-truck="Truck 3"><div class="panel-heading">🚛 Truck 3 <span id="truck3-count" class="text-muted"></span></div><div class="panel-body truck-stops" id="truck3-stops"></div></div></div>
|
||||
</div>
|
||||
<div class="row" style="margin-top: 16px;"><div class="col-md-12"><div class="panel panel-default truck-column" data-truck=""><div class="panel-heading">📋 Unassigned <span id="unassigned-count" class="text-muted"></span></div><div class="panel-body truck-stops" id="unassigned-stops"></div></div></div></div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="stage-c">
|
||||
<div class="row" style="margin-bottom: 16px;"><div class="col-md-12"><button class="btn btn-primary" id="btn-new-checkin">+ Check In Load</button><button class="btn btn-success" id="btn-cor-report">📋 CoR Report</button></div></div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Recent Check-ins</div>
|
||||
<div class="panel-body" style="padding: 0; overflow-x: auto;">
|
||||
<table class="table table-striped table-hover" id="checkin-table" style="font-size: 13px; margin-bottom: 0;">
|
||||
<thead><tr><th>Date</th><th>Customer</th><th>Type</th><th class="text-right">Actual Pallets</th><th class="text-right">Actual Weight</th><th>Load Contents</th><th>Data Status</th><th>RED/R2</th><th>Status</th></tr></thead>
|
||||
<tbody id="checkin-tbody"><tr><td colspan="9" class="text-center text-muted">Loading...</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div id="checkin-form" style="display:none; margin-top: 16px;">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">+ Load Check-in</div>
|
||||
<div class="panel-body">
|
||||
<form id="checkin-form-inner">
|
||||
<div class="row">
|
||||
<div class="col-md-4"><div class="form-group"><label>Scheduled Pickup <span class="text-danger">*</span></label><div id="ci-pickup-control"></div></div></div>
|
||||
<div class="col-md-4"><div class="form-group"><label>Received Date <span class="text-danger">*</span></label><input type="date" id="ci-received_date" class="form-control" required></div></div>
|
||||
<div class="col-md-4"><div class="form-group"><label>Actual # of Pallets/Gaylords</label><input type="number" id="ci-actual_pallets" class="form-control"></div></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4"><div class="form-group"><label>Actual Weight (lbs)</label><input type="text" id="ci-actual_weight" class="form-control"></div></div>
|
||||
<div class="col-md-8"><div class="form-group"><label>Load Contents</label><textarea id="ci-load_contents" class="form-control" rows="2" placeholder="Wire, Monitors, Laptops, MRI machine..."></textarea></div></div>
|
||||
</div>
|
||||
<div class="row" style="margin-top: 8px;"><div class="col-md-12"><button type="submit" class="btn btn-primary btn-lg">Check In</button><button type="button" class="btn btn-default btn-lg" id="btn-cancel-checkin">Cancel</button></div></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.truck-stops { min-height: 60px; }
|
||||
.stop-card { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 6px; padding: 10px 12px; margin: 6px 0; cursor: grab; font-size: 13px; }
|
||||
.stop-card:hover { border-color: #2F5496; }
|
||||
.stop-card .stop-co { font-weight: 700; color: #2F5496; }
|
||||
.stop-card .stop-addr { color: #666; font-size: 12px; margin-top: 2px; }
|
||||
.stop-card .stop-meta { display: flex; gap: 8px; margin-top: 4px; font-size: 11px; }
|
||||
.stop-card .stop-meta span { background: #D6E4F0; color: #2F5496; padding: 1px 6px; border-radius: 3px; }
|
||||
.stop-card.dragging { opacity: 0.5; }
|
||||
.truck-column { min-height: 200px; }
|
||||
#pickup-calendar { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; font-size: 12px; }
|
||||
.cal-day { text-align: center; padding: 6px 4px; border-radius: 6px; }
|
||||
.cal-day.has-pickups { background: #D6E4F0; cursor: pointer; }
|
||||
.cal-day.today { background: #2F5496; color: #fff; }
|
||||
.cal-day .day-num { font-weight: 600; }
|
||||
.cal-day .day-count { font-size: 10px; font-weight: 700; }
|
||||
</style>
|
||||
`);
|
||||
|
||||
|
||||
// ── Stage Tabs ──
|
||||
$("#receiving-tabs a").on("click", function(e) {
|
||||
e.preventDefault();
|
||||
$(this).tab("show");
|
||||
var stage = $(this).attr("href").replace("#stage-", "");
|
||||
if (stage === "a") loadPickups();
|
||||
if (stage === "b") loadRoutes();
|
||||
if (stage === "c") loadCheckins();
|
||||
});
|
||||
|
||||
// ── Stage A: Link Controls ──
|
||||
var customer_control = null;
|
||||
|
||||
function setupCustomerLink() {
|
||||
customer_control = frappe.ui.form.make_control({
|
||||
parent: $("#sp-customer-control"),
|
||||
df: {
|
||||
fieldtype: "Link",
|
||||
fieldname: "customer_number",
|
||||
options: "Customer",
|
||||
label: "Customer",
|
||||
reqd: 1,
|
||||
placeholder: "Search customer...",
|
||||
onchange: function() {
|
||||
var val = customer_control.get_value();
|
||||
if (val) fetchCustomerDetails(val);
|
||||
else clearCustomerFields();
|
||||
}
|
||||
},
|
||||
only_input: true,
|
||||
});
|
||||
customer_control.refresh();
|
||||
$("#sp-customer-control .control-input").css("margin", "0");
|
||||
$("#sp-customer-control .help-box").remove();
|
||||
}
|
||||
|
||||
function fetchCustomerDetails(customer_name) {
|
||||
frappe.call({
|
||||
method: "frappe.client.get",
|
||||
args: { doctype: "Customer", name: customer_name },
|
||||
callback: function(r) {
|
||||
if (!r.message) return;
|
||||
var c = r.message;
|
||||
$("#sp-company_name").val(c.customer_name || "");
|
||||
$("#sp-contact_name").val(c.contact_name || "");
|
||||
$("#sp-contact_phone").val(c.contact_phone || "");
|
||||
$("#sp-contact_email").val(c.contact_email || "");
|
||||
$("#sp-legacy_notes").val(c.legacy_notes || "");
|
||||
$("#sp-hours_of_operation").val(c.hours_of_operation || "");
|
||||
|
||||
frappe.call({
|
||||
method: "frappe.client.get_list",
|
||||
args: {
|
||||
doctype: "Address",
|
||||
filters: [["Dynamic Link", "link_name", "=", customer_name]],
|
||||
fields: ["address_line1", "city", "state", "pincode"],
|
||||
limit_page_length: 1
|
||||
},
|
||||
callback: function(ra) {
|
||||
if (ra.message && ra.message.length) {
|
||||
var a = ra.message[0];
|
||||
$("#sp-address_line").val(a.address_line1 || "");
|
||||
$("#sp-city").val(a.city || "");
|
||||
$("#sp-state").val(a.state || "AZ");
|
||||
$("#sp-zip_code").val(a.pincode || "");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.call({
|
||||
method: "frappe.client.get_list",
|
||||
args: {
|
||||
doctype: "Contact",
|
||||
filters: [["Dynamic Link", "link_name", "=", customer_name]],
|
||||
fields: ["first_name", "last_name", "email_id", "phone", "mobile_no"],
|
||||
limit_page_length: 1
|
||||
},
|
||||
callback: function(rc) {
|
||||
if (rc.message && rc.message.length) {
|
||||
var ct = rc.message[0];
|
||||
if (!$("#sp-contact_name").val()) {
|
||||
$("#sp-contact_name").val((ct.first_name || "") + " " + (ct.last_name || ""));
|
||||
}
|
||||
if (!$("#sp-contact_phone").val()) {
|
||||
$("#sp-contact_phone").val(ct.phone || ct.mobile_no || "");
|
||||
}
|
||||
if (!$("#sp-contact_email").val()) {
|
||||
$("#sp-contact_email").val(ct.email_id || "");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearCustomerFields() {
|
||||
$("#sp-company_name, #sp-contact_name, #sp-contact_phone, #sp-contact_email, #sp-address_line, #sp-city, #sp-state, #sp-zip_code, #sp-legacy_notes, #sp-hours_of_operation").val("");
|
||||
$("#sp-state").val("AZ");
|
||||
}
|
||||
|
||||
// ── Stage A: Load Pickups ──
|
||||
function loadPickups() {
|
||||
var dateFilter = $("#pickup-date-filter").val();
|
||||
frappe.call({
|
||||
method: "westech_r2.api.receiving_api.get_pickups",
|
||||
args: { date: dateFilter },
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
renderPickupTable(r.message.pickups || []);
|
||||
renderCalendar(r.message.calendar || []);
|
||||
renderWeeklyChart(r.message.weekly || []);
|
||||
$("#pickup-count-label").text((r.message.pickups || []).length + " pickups");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderPickupTable(pickups) {
|
||||
var tbody = $("#pickup-tbody");
|
||||
if (!pickups.length) {
|
||||
tbody.html('<tr><td colspan="14" class="text-center text-muted">No scheduled pickups</td></tr>');
|
||||
return;
|
||||
}
|
||||
var statusColors = { "Scheduled": "#2196F3", "Routed": "#009688", "In Progress": "#FF9800", "Complete": "#4CAF50", "Cancelled": "#F44336" };
|
||||
var h = "";
|
||||
pickups.forEach(function(p) {
|
||||
var st = p.status || "Scheduled";
|
||||
var sc = statusColors[st] || "#999";
|
||||
var weekday = p.pickup_date ? dayName(new Date(p.pickup_date + "T12:00:00")) : "";
|
||||
var typeBadge = p.pickup_type === "Drop-off"
|
||||
? '<span class="badge" style="background:#E3F2FD;color:#1565C0">Drop-off</span>'
|
||||
: '<span class="badge" style="background:#FFF3E0;color:#E65100">Pickup</span>';
|
||||
h += '<tr style="cursor:pointer" onclick=\"window.open(\'/app/scheduled-pickup/\' + encodeURIComponent(p.name) + \'\', \'_blank\')\">';
|
||||
h += '<td>' + esc(p.pickup_date || "") + '</td>';
|
||||
h += '<td class="text-muted">' + weekday + '</td>';
|
||||
h += '<td>' + typeBadge + '</td>';
|
||||
h += '<td><strong>' + esc(p.company_name || p.customer_number || "") + '</strong></td>';
|
||||
h += '<td style="font-size:12px">' + esc((p.contact_name || "") + (p.contact_phone ? " • " + p.contact_phone : "")) + '</td>';
|
||||
h += '<td style="font-size:12px">' + esc((p.address_line || "") + (p.city ? ", " + p.city : "")) + '</td>';
|
||||
h += '<td class="text-right">' + (p.estimated_items || "—") + '</td>';
|
||||
h += '<td>' + esc(p.data_status || "—") + '</td>';
|
||||
h += '<td>' + esc(p.red_r2 || "—") + '</td>';
|
||||
h += '<td><span class="badge" style="background:' + sc + '22;color:' + sc + '">' + esc(st) + '</span></td>';
|
||||
h += '<td style="max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + esc(p.notes || "") + '</td>';
|
||||
h += '<td>' + esc(p.truck || "—") + '</td>';
|
||||
h += '<td class="text-center">' + (p.needs_aor ? "✓" : "") + '</td>';
|
||||
h += '<td class="text-center">' + (p.needs_cod ? "✓" : "") + '</td>';
|
||||
h += '</tr>';
|
||||
});
|
||||
tbody.html(h);
|
||||
}
|
||||
|
||||
function renderCalendar(days) {
|
||||
var el = $("#pickup-calendar");
|
||||
if (!days || !days.length) { el.html('<div class="text-muted text-center">No upcoming pickups</div>'); return; }
|
||||
var today = frappe.datetime.nowdate();
|
||||
var h = '<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:4px;font-size:12px">';
|
||||
["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].forEach(function(d) {
|
||||
h += '<div style="text-align:center;font-weight:600;color:#666;padding:4px">' + d + '</div>';
|
||||
});
|
||||
var first = new Date(days[0].date + "T12:00:00");
|
||||
for (var i = 0; i < first.getDay(); i++) h += '<div></div>';
|
||||
days.forEach(function(d) {
|
||||
var isToday = d.date === today;
|
||||
var hasCount = d.count > 0;
|
||||
var bg = isToday ? "cal-day today" : (hasCount ? "cal-day has-pickups" : "cal-day");
|
||||
var onclick = hasCount ? "onclick=$('#pickup-date-filter').val('" + d.date + "');loadPickups();" : "";
|
||||
h += '<div class="' + bg + '" ' + onclick + '>';
|
||||
h += '<div style="font-weight:' + (isToday ? "700" : "400") + '">' + d.date.split("-")[2] + '</div>';
|
||||
if (hasCount) h += '<div class="day-count">' + d.count + '</div>';
|
||||
h += '</div>';
|
||||
});
|
||||
h += '</div>';
|
||||
el.html(h);
|
||||
}
|
||||
|
||||
function renderWeeklyChart(weekly) {
|
||||
var canvas = document.getElementById("weekly-chart");
|
||||
if (!canvas || !weekly || !weekly.length) return;
|
||||
var ctx = canvas.getContext("2d");
|
||||
var W = canvas.width = canvas.parentElement.clientWidth - 20;
|
||||
var H = canvas.height = 180;
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
var maxVal = Math.max.apply(null, weekly.map(function(w) { return w.count; }));
|
||||
if (maxVal < 1) maxVal = 1;
|
||||
var barW = Math.min(50, (W - 40) / weekly.length - 8);
|
||||
var startX = 40;
|
||||
var barArea = H - 40;
|
||||
weekly.forEach(function(w, i) {
|
||||
var x = startX + i * ((W - 60) / weekly.length);
|
||||
var barH = (w.count / maxVal) * barArea;
|
||||
var y = H - 30 - barH;
|
||||
ctx.fillStyle = "#2F5496";
|
||||
ctx.fillRect(x, y, barW, barH);
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.font = "11px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(w.count, x + barW / 2, y - 4);
|
||||
ctx.fillStyle = "#999";
|
||||
ctx.font = "10px sans-serif";
|
||||
ctx.fillText(w.label, x + barW / 2, H - 10);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Stage A: New Pickup ──
|
||||
$("#btn-new-pickup").on("click", function() {
|
||||
$("#new-pickup-form").show();
|
||||
$("#sp-pickup_date").val(frappe.datetime.nowdate());
|
||||
setupCustomerLink();
|
||||
});
|
||||
|
||||
$("#btn-cancel-pickup").on("click", function() {
|
||||
$("#new-pickup-form").hide();
|
||||
if (customer_control) customer_control.set_value("");
|
||||
});
|
||||
|
||||
$("#pickup-form").on("submit", function(e) {
|
||||
e.preventDefault();
|
||||
var doc = {
|
||||
doctype: "Scheduled Pickup",
|
||||
pickup_date: $("#sp-pickup_date").val(),
|
||||
pickup_type: $("#sp-pickup_type").val(),
|
||||
customer_number: customer_control ? customer_control.get_value() : "",
|
||||
company_name: $("#sp-company_name").val(),
|
||||
contact_name: $("#sp-contact_name").val(),
|
||||
contact_phone: $("#sp-contact_phone").val(),
|
||||
contact_email: $("#sp-contact_email").val(),
|
||||
address_line: $("#sp-address_line").val(),
|
||||
city: $("#sp-city").val(),
|
||||
state: $("#sp-state").val(),
|
||||
zip_code: $("#sp-zip_code").val(),
|
||||
estimated_items: parseInt($("#sp-estimated_items").val()) || 0,
|
||||
estimated_weight: $("#sp-estimated_weight").val(),
|
||||
load_contents: $("#sp-load_contents").val(),
|
||||
data_status: $("#sp-data_status").val(),
|
||||
red_r2: $("#sp-red_r2").val(),
|
||||
needs_aor: $("#sp-needs_aor").is(":checked") ? 1 : 0,
|
||||
needs_cod: $("#sp-needs_cod").is(":checked") ? 1 : 0,
|
||||
notes: $("#sp-notes").val(),
|
||||
legacy_notes: $("#sp-legacy_notes").val(),
|
||||
status: "Scheduled"
|
||||
};
|
||||
frappe.call({
|
||||
method: "frappe.client.insert",
|
||||
args: { doc: doc },
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frappe.show_alert({ message: "Pickup scheduled", indicator: "green" });
|
||||
$("#new-pickup-form").hide();
|
||||
loadPickups();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#pickup-date-filter").on("change", loadPickups);
|
||||
$("#btn-clear-date").on("click", function() {
|
||||
$("#pickup-date-filter").val("");
|
||||
loadPickups();
|
||||
});
|
||||
|
||||
// ── Stage B: Routing ──
|
||||
function loadRoutes() {
|
||||
var date = $("#route-date").val() || frappe.datetime.nowdate();
|
||||
$("#route-date").val(date);
|
||||
frappe.call({
|
||||
method: "westech_r2.api.receiving_api.get_pickups",
|
||||
args: { date: date },
|
||||
callback: function(r) {
|
||||
if (r.message) renderRouteColumns(r.message.pickups || []);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderRouteColumns(pickups) {
|
||||
var trucks = { "Truck 1": [], "Truck 2": [], "Truck 3": [], "Unassigned": [] };
|
||||
pickups.forEach(function(p) {
|
||||
var t = p.truck || "";
|
||||
if (t && trucks[t]) trucks[t].push(p);
|
||||
else trucks["Unassigned"].push(p);
|
||||
});
|
||||
["Truck 1", "Truck 2", "Truck 3"].forEach(function(t) {
|
||||
var key = t.toLowerCase().replace(/ /g, "");
|
||||
$("#" + key + "-count").text("(" + trucks[t].length + " stops)");
|
||||
$("#" + key + "-stops").html(trucks[t].map(function(p, i) { return stopCard(p, i + 1); }).join(""));
|
||||
});
|
||||
$("#unassigned-count").text("(" + trucks["Unassigned"].length + " stops)");
|
||||
$("#unassigned-stops").html(trucks["Unassigned"].map(function(p) { return stopCard(p, 0); }).join(""));
|
||||
}
|
||||
|
||||
function stopCard(p, order) {
|
||||
var h = '<div class="stop-card" data-pickup="' + esc(p.name) + '">';
|
||||
if (order) h += '<div style="font-size:11px;color:#666;margin-bottom:2px">Stop #' + order + '</div>';
|
||||
h += '<div class="stop-co">' + esc(p.company_name || p.customer_number || "Unknown") + '</div>';
|
||||
h += '<div class="stop-addr">' + esc((p.address_line || "") + (p.city ? ", " + p.city : "")) + '</div>';
|
||||
h += '<div class="stop-meta">';
|
||||
if (p.estimated_items) h += '<span>' + p.estimated_items + ' items</span>';
|
||||
if (p.data_status) h += '<span>' + esc(p.data_status) + '</span>';
|
||||
if (p.red_r2) h += '<span>' + esc(p.red_r2) + '</span>';
|
||||
if (p.needs_aor) h += '<span>AoR</span>';
|
||||
if (p.needs_cod) h += '<span>CoD</span>';
|
||||
h += '</div></div>';
|
||||
return h;
|
||||
}
|
||||
|
||||
$("#btn-load-routes").on("click", loadRoutes);
|
||||
|
||||
$("#btn-auto-route").on("click", function() {
|
||||
var date = $("#route-date").val();
|
||||
if (!date) { frappe.msgprint("Select a date first"); return; }
|
||||
frappe.call({
|
||||
method: "westech_r2.api.receiving_api.auto_route",
|
||||
args: { date: date },
|
||||
callback: function(r) {
|
||||
if (r.message && r.message.success) {
|
||||
frappe.show_alert({ message: "Routes optimized", indicator: "green" });
|
||||
loadRoutes();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#btn-route-sheet").on("click", function() {
|
||||
var date = $("#route-date").val() || frappe.datetime.nowdate();
|
||||
window.open("/api/method/westech_r2.api.receiving_api.print_route_sheet?date=" + date, "_blank");
|
||||
});
|
||||
|
||||
$("#btn-green-sheet").on("click", function() {
|
||||
var date = $("#route-date").val() || frappe.datetime.nowdate();
|
||||
window.open("/api/method/westech_r2.api.receiving_api.print_green_sheet?date=" + date, "_blank");
|
||||
});
|
||||
|
||||
$("#btn-labels").on("click", function() {
|
||||
var date = $("#route-date").val() || frappe.datetime.nowdate();
|
||||
window.open("/api/method/westech_r2.api.receiving_api.print_labels?date=" + date, "_blank");
|
||||
});
|
||||
|
||||
// ── Stage C: Check-in ──
|
||||
var checkin_pickup_control = null;
|
||||
|
||||
function loadCheckins() {
|
||||
frappe.call({
|
||||
method: "westech_r2.api.receiving_api.get_checkins",
|
||||
callback: function(r) {
|
||||
if (r.message) renderCheckinTable(r.message.checkins || []);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderCheckinTable(checkins) {
|
||||
var tbody = $("#checkin-tbody");
|
||||
if (!checkins.length) {
|
||||
tbody.html('<tr><td colspan="9" class="text-center text-muted">No check-ins yet</td></tr>');
|
||||
return;
|
||||
}
|
||||
tbody.html(checkins.map(function(c) {
|
||||
return '<tr><td>' + esc(c.pickup_date || "") + '</td>' +
|
||||
'<td><strong>' + esc(c.company_name || "") + '</strong></td>' +
|
||||
'<td>' + esc(c.pickup_type || "") + '</td>' +
|
||||
'<td class="text-right">' + (c.estimated_items || "—") + '</td>' +
|
||||
'<td class="text-right">' + (c.estimated_weight || "—") + '</td>' +
|
||||
'<td style="max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + esc(c.load_contents || "") + '</td>' +
|
||||
'<td>' + esc(c.data_status || "—") + '</td>' +
|
||||
'<td>' + esc(c.red_r2 || "—") + '</td>' +
|
||||
'<td>' + esc(c.status || "") + '</td></tr>';
|
||||
}).join(""));
|
||||
}
|
||||
|
||||
$("#btn-new-checkin").on("click", function() {
|
||||
$("#checkin-form").show();
|
||||
$("#ci-received_date").val(frappe.datetime.nowdate());
|
||||
checkin_pickup_control = frappe.ui.form.make_control({
|
||||
parent: $("#ci-pickup-control"),
|
||||
df: {
|
||||
fieldtype: "Link",
|
||||
fieldname: "pickup_ref",
|
||||
options: "Scheduled Pickup",
|
||||
label: "Scheduled Pickup",
|
||||
reqd: 1,
|
||||
placeholder: "Search pickup...",
|
||||
get_query: function() {
|
||||
return {
|
||||
filters: [
|
||||
["Scheduled Pickup", "status", "in", ["Scheduled", "Routed", "In Progress"]]
|
||||
]
|
||||
};
|
||||
}
|
||||
},
|
||||
only_input: true,
|
||||
});
|
||||
checkin_pickup_control.refresh();
|
||||
$("#ci-pickup-control .control-input").css("margin", "0");
|
||||
$("#ci-pickup-control .help-box").remove();
|
||||
});
|
||||
|
||||
$("#btn-cancel-checkin").on("click", function() {
|
||||
$("#checkin-form").hide();
|
||||
});
|
||||
|
||||
$("#checkin-form-inner").on("submit", function(e) {
|
||||
e.preventDefault();
|
||||
var pickupName = checkin_pickup_control ? checkin_pickup_control.get_value() : "";
|
||||
if (!pickupName) { frappe.msgprint("Select a pickup"); return; }
|
||||
|
||||
var update = {};
|
||||
update.status = "Complete";
|
||||
if ($("#ci-actual_pallets").val()) update.estimated_items = parseInt($("#ci-actual_pallets").val());
|
||||
if ($("#ci-actual_weight").val()) update.estimated_weight = $("#ci-actual_weight").val();
|
||||
if ($("#ci-load_contents").val()) update.load_contents = $("#ci-load_contents").val();
|
||||
|
||||
frappe.call({
|
||||
method: "frappe.client.set_value",
|
||||
args: {
|
||||
doctype: "Scheduled Pickup",
|
||||
name: pickupName,
|
||||
fieldname: update
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frappe.show_alert({ message: "Load checked in", indicator: "green" });
|
||||
$("#checkin-form").hide();
|
||||
loadCheckins();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#btn-cor-report").on("click", function() {
|
||||
window.open("/api/method/westech_r2.api.receiving_api.cor_report", "_blank");
|
||||
});
|
||||
|
||||
// ── Helpers ──
|
||||
function esc(s) { return s ? String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """) : ""; }
|
||||
function dayName(d) { return ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][d.getDay()]; }
|
||||
|
||||
// ── Init ──
|
||||
loadPickups();
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"content": null,
|
||||
"creation": "2026-05-20 21:24:08.575605",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"icon": "stock",
|
||||
"idx": 0,
|
||||
"modified": "2026-05-20 21:27:35.049633",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Westech R2",
|
||||
"name": "receiving",
|
||||
"owner": "Administrator",
|
||||
"page_name": "receiving",
|
||||
"roles": [
|
||||
{
|
||||
"role": "All"
|
||||
}
|
||||
],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0,
|
||||
"title": "Receiving"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
from frappe import _
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
context.title = _("Receiving")
|
||||
@@ -0,0 +1,3 @@
|
||||
import frappe
|
||||
|
||||
no_cache = 1
|
||||
@@ -0,0 +1,3 @@
|
||||
import frappe
|
||||
|
||||
no_cache = 1
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
frappe.pages["wes-ai"].on_page_load = function(wrapper) {
|
||||
wrapper.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:60vh;font-family:sans-serif;"><div style="text-align:center;"><i class="fa fa-spinner fa-spin" style="font-size:24px;color:#5b3a8c;"></i><p style="margin-top:12px;color:#555;">Redirecting to Wes AI Assistant...</p></div></div>';
|
||||
setTimeout(function() { window.location.href = "https://wes.advante.ch"; }, 500);
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"creation": "2026-05-09 14:00:00",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2026-05-09 14:00:00",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Westech R2",
|
||||
"name": "wes-ai",
|
||||
"owner": "Administrator",
|
||||
"standard": "Yes",
|
||||
"title": "Wes AI Assistant"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import frappe
|
||||
|
||||
def get_context(context):
|
||||
frappe.local.flags.redirect_location = "https://wes.advante.ch"
|
||||
raise frappe.Redirect
|
||||
@@ -0,0 +1,30 @@
|
||||
frappe.pages["wes-ai"].on_page_load = function(wrapper) {
|
||||
var page = frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: "Wes AI Assistant",
|
||||
single_column: true
|
||||
});
|
||||
|
||||
// Create iframe that embeds Wes AI
|
||||
var $container = $(wrapper).find(".layout-main-section");
|
||||
$container.css({
|
||||
"position": "relative",
|
||||
"overflow": "hidden"
|
||||
});
|
||||
|
||||
// Determine Wes URL based on environment
|
||||
var wes_url = "https://wes.advante.ch";
|
||||
|
||||
// On production VM, Wes runs locally
|
||||
if (window.location.hostname === "erpnext.local" || window.location.hostname === "localhost") {
|
||||
wes_url = "http://localhost:8082";
|
||||
}
|
||||
|
||||
var $iframe = $('<iframe>', {
|
||||
src: wes_url,
|
||||
style: "width: 100%; height: calc(100vh - 120px); border: none; border-radius: 4px;",
|
||||
id: "wes-ai-iframe"
|
||||
});
|
||||
|
||||
$container.empty().append($iframe);
|
||||
};
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2026, Westech and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Customer Record", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -1,158 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:record_number",
|
||||
"creation": "2026-05-20 15:10:58.374067",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"record_number",
|
||||
"company_name",
|
||||
"supplier",
|
||||
"customer_number",
|
||||
"contact_persons",
|
||||
"email_address",
|
||||
"phone_numbers",
|
||||
"customer_address",
|
||||
"city",
|
||||
"state",
|
||||
"zip",
|
||||
"date_created",
|
||||
"contacted_date",
|
||||
"follow_up_date",
|
||||
"last_pu_date",
|
||||
"notes",
|
||||
"comments",
|
||||
"hours_operation"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "record_number",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Record Number",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Company Name"
|
||||
},
|
||||
{
|
||||
"fieldname": "supplier",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Supplier",
|
||||
"options": "Supplier"
|
||||
},
|
||||
{
|
||||
"fieldname": "customer_number",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Customer Number"
|
||||
},
|
||||
{
|
||||
"fieldname": "contact_persons",
|
||||
"fieldtype": "Text",
|
||||
"label": "Contact Persons"
|
||||
},
|
||||
{
|
||||
"fieldname": "email_address",
|
||||
"fieldtype": "Text",
|
||||
"label": "Email Addresses"
|
||||
},
|
||||
{
|
||||
"fieldname": "phone_numbers",
|
||||
"fieldtype": "Text",
|
||||
"label": "Phone Numbers"
|
||||
},
|
||||
{
|
||||
"fieldname": "customer_address",
|
||||
"fieldtype": "Data",
|
||||
"label": "Address"
|
||||
},
|
||||
{
|
||||
"fieldname": "city",
|
||||
"fieldtype": "Data",
|
||||
"label": "City"
|
||||
},
|
||||
{
|
||||
"fieldname": "state",
|
||||
"fieldtype": "Data",
|
||||
"label": "State"
|
||||
},
|
||||
{
|
||||
"fieldname": "zip",
|
||||
"fieldtype": "Data",
|
||||
"label": "Zip"
|
||||
},
|
||||
{
|
||||
"fieldname": "date_created",
|
||||
"fieldtype": "Date",
|
||||
"label": "Date Created"
|
||||
},
|
||||
{
|
||||
"fieldname": "contacted_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Contacted Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "follow_up_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Follow Up Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "last_pu_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Last PU Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "notes",
|
||||
"fieldtype": "Text",
|
||||
"label": "Notes"
|
||||
},
|
||||
{
|
||||
"fieldname": "comments",
|
||||
"fieldtype": "Text",
|
||||
"label": "Comments"
|
||||
},
|
||||
{
|
||||
"fieldname": "hours_operation",
|
||||
"fieldtype": "Data",
|
||||
"label": "Hours of Operation"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2026-05-20 15:10:58.374067",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Westech R2",
|
||||
"name": "Customer Record",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"rows_threshold_for_grid_search": 20,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2026, Westech and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class CustomerRecord(Document):
|
||||
pass
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2026, Westech and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestCustomerRecord(FrappeTestCase):
|
||||
pass
|
||||
@@ -1,7 +0,0 @@
|
||||
frappe.pages['customer-records'].on_page_load = function(wrapper) {
|
||||
var page = frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: 'Customer Records',
|
||||
single_column: true
|
||||
});
|
||||
}
|
||||
@@ -1,363 +0,0 @@
|
||||
{
|
||||
"charts": [],
|
||||
"content": "[{\"type\": \"header\", \"data\": {\"text\": \"<span class=\\\"h4\\\"><b>Westech Recyclers</b></span>\"}}, {\"type\": \"spacer\", \"data\": {}}, {\"type\": \"header\", \"data\": {\"text\": \"<span class=\\\"h4\\\"><b>Operations</b></span>\"}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"New Intake\"}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Pallets\"}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Pallet List\"}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Scheduled Pickups\"}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Route Planner\"}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"EIM Device Portal\"}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"R2 Data Tracking\"}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Schema Diagram\"}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Wes AI Assistant\"}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Customers\"}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Suppliers\"}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Serial No\"}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"R2 Device Inspection\"}}]",
|
||||
"creation": "2026-05-17 05:39:20.222818",
|
||||
"custom_blocks": [],
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
"hide_custom": 0,
|
||||
"icon": "stock",
|
||||
"idx": 0,
|
||||
"indicator_color": "green",
|
||||
"is_hidden": 0,
|
||||
"label": "Westech",
|
||||
"links": [
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Tools",
|
||||
"link_count": 0,
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "New Intake",
|
||||
"link_count": 0,
|
||||
"link_to": "intake",
|
||||
"link_type": "Page",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Pallets",
|
||||
"link_count": 0,
|
||||
"link_to": "Pallet",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Pallet List",
|
||||
"link_count": 0,
|
||||
"link_to": "pallet-list",
|
||||
"link_type": "Page",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Scheduled Pickups",
|
||||
"link_count": 0,
|
||||
"link_to": "Scheduled Pickup",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Route Planner",
|
||||
"link_count": 0,
|
||||
"link_to": "route-planner",
|
||||
"link_type": "Page",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "EIM Device Portal",
|
||||
"link_count": 0,
|
||||
"link_to": "eim-portal",
|
||||
"link_type": "Page",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "R2 Data Tracking",
|
||||
"link_count": 0,
|
||||
"link_to": "r2-tracking",
|
||||
"link_type": "Page",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Schema Diagram",
|
||||
"link_count": 0,
|
||||
"link_to": null,
|
||||
"link_type": null,
|
||||
"onboard": 0,
|
||||
"type": "Link",
|
||||
"url": "https://eim.diagalon.com/schema-diagram"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Records",
|
||||
"link_count": 0,
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Customers",
|
||||
"link_count": 0,
|
||||
"link_to": "Customer",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Suppliers",
|
||||
"link_count": 0,
|
||||
"link_to": "Supplier",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Stock",
|
||||
"link_count": 0,
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Warehouses",
|
||||
"link_count": 0,
|
||||
"link_to": "Warehouse",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Serial Nos",
|
||||
"link_count": 0,
|
||||
"link_to": "Serial No",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Stock Entry",
|
||||
"link_count": 0,
|
||||
"link_to": "Stock Entry",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Stock Balance",
|
||||
"link_count": 0,
|
||||
"link_to": "Stock Balance",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"report_ref_doctype": "Stock Ledger Entry",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "R2 Tracking",
|
||||
"link_count": 0,
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Loads",
|
||||
"link_count": 0,
|
||||
"link_to": "Load",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2026-05-19 06:21:55.636514",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Westech R2",
|
||||
"name": "Westech",
|
||||
"number_cards": [],
|
||||
"owner": "Administrator",
|
||||
"public": 1,
|
||||
"quick_lists": [],
|
||||
"roles": [
|
||||
{
|
||||
"role": "System Manager"
|
||||
},
|
||||
{
|
||||
"role": "Westech User"
|
||||
},
|
||||
{
|
||||
"role": "All"
|
||||
}
|
||||
],
|
||||
"sequence_id": 0.0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "add",
|
||||
"label": "New Intake",
|
||||
"link_to": "intake",
|
||||
"type": "Page"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "table",
|
||||
"label": "Pallets",
|
||||
"link_to": "Pallet",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "list",
|
||||
"label": "Pallet List",
|
||||
"link_to": "pallet-list",
|
||||
"type": "Page"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "calendar",
|
||||
"label": "Scheduled Pickups",
|
||||
"link_to": "Scheduled Pickup",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "map-pin",
|
||||
"label": "Route Planner",
|
||||
"link_to": "route-planner",
|
||||
"type": "Page"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "monitor",
|
||||
"label": "EIM Device Portal",
|
||||
"link_to": "eim-portal",
|
||||
"type": "Page"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "shield",
|
||||
"label": "R2 Data Tracking",
|
||||
"link_to": "r2-tracking",
|
||||
"type": "Page"
|
||||
},
|
||||
{
|
||||
"color": null,
|
||||
"doc_view": "",
|
||||
"format": null,
|
||||
"icon": "table",
|
||||
"kanban_board": null,
|
||||
"label": "Schema Diagram",
|
||||
"link_to": null,
|
||||
"parent": "Westech",
|
||||
"parentfield": "shortcuts",
|
||||
"parenttype": "Workspace",
|
||||
"report_ref_doctype": null,
|
||||
"restrict_to_domain": null,
|
||||
"stats_filter": null,
|
||||
"type": "URL",
|
||||
"url": "https://eim.diagalon.com/schema-diagram"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "customer",
|
||||
"label": "Customers",
|
||||
"link_to": "Customer",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "user",
|
||||
"label": "Suppliers",
|
||||
"link_to": "Supplier",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "hash",
|
||||
"label": "Serial No",
|
||||
"link_to": "Serial No",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "search",
|
||||
"label": "R2 Device Inspection",
|
||||
"link_to": "R2 Device Inspection",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "dashboard",
|
||||
"label": "R2 Pipeline",
|
||||
"link_to": "R2 Pipeline",
|
||||
"type": "Dashboard"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "filter",
|
||||
"label": "R2 Sort",
|
||||
"link_to": "R2 Sort",
|
||||
"type": "Dashboard"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "check",
|
||||
"label": "R2 HDR",
|
||||
"link_to": "R2 HDR",
|
||||
"type": "Dashboard"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "play",
|
||||
"label": "R2 Testing",
|
||||
"link_to": "R2 Testing",
|
||||
"type": "Dashboard"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "delete",
|
||||
"label": "R2 Wipe",
|
||||
"link_to": "R2 Wipe",
|
||||
"type": "Dashboard"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"icon": "home",
|
||||
"label": "R2 Warehouse",
|
||||
"link_to": "R2 Warehouse",
|
||||
"type": "Dashboard"
|
||||
}
|
||||
],
|
||||
"title": "Westech"
|
||||
}
|
||||
Reference in New Issue
Block a user