Fix page module paths: move all pages to correct app-level directory, add missing .py files
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"content": null,
|
||||||
|
"creation": "2026-05-20 15:03:29.017530",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Page",
|
||||||
|
"idx": 0,
|
||||||
|
"modified": "2026-05-20 15:03:29.017530",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Westech R2",
|
||||||
|
"name": "customer-records",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"page_name": "customer-records",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "All"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"script": null,
|
||||||
|
"standard": "Yes",
|
||||||
|
"style": null,
|
||||||
|
"system_page": 0,
|
||||||
|
"title": "Customer Records"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
no_cache = 1
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
no_cache = 1
|
||||||
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -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.
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"creation":"2026-05-20 15:00:00.000000","docstatus":0,"doctype":"Page","idx":0,"module":"westech_r2","name":"customer-intake","page_name":"customer-intake","roles":[],"script":null,"standard":"Yes","style":null,"system_page":0,"title":"Customer Intake"}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<div class="container" style="padding:20px;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h3>Customer Intake</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label>Search Customer</label>
|
||||||
|
<input type="text" id="intake-search" class="form-control" placeholder="Type company name, number, or phone...">
|
||||||
|
<div id="search-results" class="list-group" style="margin-top:8px; max-height:250px; overflow-y:auto;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div id="no-match" style="display:none;">
|
||||||
|
<button id="btn-add-new" class="btn btn-primary">Add New Customer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label>Customer Name</label>
|
||||||
|
<input type="text" id="cust-name" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label>Customer Number</label>
|
||||||
|
<input type="text" id="cust-number" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label>Phone</label>
|
||||||
|
<input type="text" id="cust-phone" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row" style="margin-top:10px;">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label>Address Line 1</label>
|
||||||
|
<input type="text" id="cust-address" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label>City</label>
|
||||||
|
<input type="text" id="cust-city" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label>State</label>
|
||||||
|
<input type="text" id="cust-state" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label>Zip</label>
|
||||||
|
<input type="text" id="cust-zip" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row" style="margin-top:10px;">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label>Contact Persons</label>
|
||||||
|
<input type="text" id="cust-contacts" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label>Email</label>
|
||||||
|
<input type="text" id="cust-email" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label>Hours of Operation</label>
|
||||||
|
<input type="text" id="cust-hours" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row" style="margin-top:10px;">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label>Data Status</label>
|
||||||
|
<select id="pallet-data-status" class="form-control">
|
||||||
|
<option value="D0">D0 - Unknown</option>
|
||||||
|
<option value="D1">D1 - Contains Data</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label>Status</label>
|
||||||
|
<select id="pallet-status" class="form-control">
|
||||||
|
<option value="Received">Received</option>
|
||||||
|
<option value="Sorting">Sorting</option>
|
||||||
|
<option value="Processing">Processing</option>
|
||||||
|
<option value="Complete">Complete</option>
|
||||||
|
<option value="Shipped">Shipped</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label>Inbound Weight (lbs)</label>
|
||||||
|
<input type="text" id="pallet-weight" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row" style="margin-top:15px;">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<button id="btn-save-cust" class="btn btn-success">Save Customer</button>
|
||||||
|
<button id="btn-create-pallet" class="btn btn-warning" style="margin-left:10px;">Create Pallet</button>
|
||||||
|
<span id="cust-status" style="margin-left:15px;"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
frappe.pages["customer-intake"].on_page_load = function(wrapper) {
|
||||||
|
var page = frappe.ui.make_app_page({parent: wrapper, title: __("Customer Intake"), single_column: true});
|
||||||
|
var content = frappe.render_template("customer-intake", {});
|
||||||
|
$(page.body).append(content);
|
||||||
|
|
||||||
|
var currentCustomer = null;
|
||||||
|
var searchTimer = null;
|
||||||
|
|
||||||
|
function clearForm() {
|
||||||
|
currentCustomer = null;
|
||||||
|
$("#cust-name, #cust-number, #cust-phone, #cust-address, #cust-city, #cust-state, #cust-zip, #cust-contacts, #cust-email, #cust-hours").val("");
|
||||||
|
$("#search-results").empty();
|
||||||
|
$("#no-match").hide();
|
||||||
|
$("#cust-status").text("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillForm(c) {
|
||||||
|
currentCustomer = c;
|
||||||
|
$("#cust-name").val(c.customer_name || "");
|
||||||
|
$("#cust-number").val(c.customer_number || "");
|
||||||
|
$("#cust-phone").val(c.phone || c.address_phone || "");
|
||||||
|
$("#cust-address").val(c.address_line1 || "");
|
||||||
|
$("#cust-city").val(c.city || "");
|
||||||
|
$("#cust-state").val(c.state || "");
|
||||||
|
$("#cust-zip").val(c.pincode || "");
|
||||||
|
$("#cust-contacts").val(c.contact_persons || "");
|
||||||
|
$("#cust-email").val(c.email_id || "");
|
||||||
|
$("#cust-hours").val(c.hours_of_operation || "");
|
||||||
|
$("#cust-status").text("Selected: " + (c.customer_name || c.name));
|
||||||
|
$("#no-match").hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSearch() {
|
||||||
|
var q = $("#intake-search").val().trim();
|
||||||
|
if (q.length < 2) { $("#search-results").empty(); return; }
|
||||||
|
frappe.call({
|
||||||
|
method: "westech_r2.page.customer-intake.customer-intake.search_customers",
|
||||||
|
args: {q: q},
|
||||||
|
callback: function(r) {
|
||||||
|
var list = $("#search-results").empty();
|
||||||
|
if (r.message && r.message.length) {
|
||||||
|
r.message.forEach(function(c) {
|
||||||
|
var item = $('<div class="list-group-item" style="cursor:pointer;">')
|
||||||
|
.html('<b>' + frappe.utils.escape_html(c.customer_name || c.name) + '</b> ' +
|
||||||
|
(c.address_line1 ? '<br>' + frappe.utils.escape_html(c.address_line1) : '') +
|
||||||
|
(c.city ? ', ' + c.city : '') +
|
||||||
|
(c.phone ? ' <br><small>' + c.phone + '</small>' : ''));
|
||||||
|
item.on("click", function() { fillForm(c); });
|
||||||
|
list.append(item);
|
||||||
|
});
|
||||||
|
$("#no-match").hide();
|
||||||
|
} else {
|
||||||
|
$("#no-match").show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#intake-search").on("input", function() {
|
||||||
|
clearTimeout(searchTimer);
|
||||||
|
searchTimer = setTimeout(doSearch, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#btn-add-new").click(function() {
|
||||||
|
clearForm();
|
||||||
|
$("#cust-name").val($("#intake-search").val());
|
||||||
|
$("#cust-status").text("Adding new customer...");
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#btn-save-cust").click(function() {
|
||||||
|
var data = {
|
||||||
|
customer_name: $("#cust-name").val(),
|
||||||
|
customer_number: $("#cust-number").val(),
|
||||||
|
phone: $("#cust-phone").val(),
|
||||||
|
address_line1: $("#cust-address").val(),
|
||||||
|
city: $("#cust-city").val(),
|
||||||
|
state: $("#cust-state").val(),
|
||||||
|
pincode: $("#cust-zip").val(),
|
||||||
|
contact_persons: $("#cust-contacts").val(),
|
||||||
|
email_id: $("#cust-email").val(),
|
||||||
|
hours_of_operation: $("#cust-hours").val()
|
||||||
|
};
|
||||||
|
if (currentCustomer && currentCustomer.name) {
|
||||||
|
data.name = currentCustomer.name;
|
||||||
|
}
|
||||||
|
frappe.call({
|
||||||
|
method: "westech_r2.page.customer-intake.customer-intake.create_customer_from_intake",
|
||||||
|
args: {data: JSON.stringify(data)},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.status === "ok") {
|
||||||
|
currentCustomer = {name: r.message.customer, customer_name: data.customer_name};
|
||||||
|
$("#cust-status").text("Saved: " + r.message.customer);
|
||||||
|
frappe.show_alert("Customer saved", 3);
|
||||||
|
} else {
|
||||||
|
frappe.show_alert("Error saving customer", 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#btn-create-pallet").click(function() {
|
||||||
|
if (!currentCustomer || !currentCustomer.name) {
|
||||||
|
frappe.msgprint("Please select or save a customer first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frappe.call({
|
||||||
|
method: "westech_r2.page.customer-intake.customer-intake.create_pallet",
|
||||||
|
args: {
|
||||||
|
data: JSON.stringify({
|
||||||
|
customer: currentCustomer.name,
|
||||||
|
customer_number: $("#cust-number").val(),
|
||||||
|
data_status: $("#pallet-data-status").val(),
|
||||||
|
status: $("#pallet-status").val(),
|
||||||
|
inbound_weight: $("#pallet-weight").val()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.status === "ok") {
|
||||||
|
frappe.msgprint("Pallet created: " + r.message.pallet);
|
||||||
|
$("#cust-status").text("Pallet: " + r.message.pallet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def search_customers(q=""):
|
||||||
|
if not q or len(q) < 2:
|
||||||
|
return []
|
||||||
|
q = q.strip().lower()
|
||||||
|
customers = frappe.db.sql("""
|
||||||
|
SELECT c.name, c.customer_name, c.customer_number, c.phone, c.email_id,
|
||||||
|
a.address_line1, a.city, a.state, a.pincode
|
||||||
|
FROM tabCustomer c
|
||||||
|
LEFT JOIN tabDynamic Link dl ON dl.link_doctype = 'Customer' AND dl.link_name = c.name AND dl.parenttype = 'Address'
|
||||||
|
LEFT JOIN tabAddress a ON a.name = dl.parent
|
||||||
|
WHERE LOWER(c.customer_name) LIKE %s OR LOWER(c.customer_number) LIKE %s OR LOWER(c.phone) LIKE %s
|
||||||
|
ORDER BY c.customer_name
|
||||||
|
LIMIT 20
|
||||||
|
""", ("%" + q + "%", "%" + q + "%", "%" + q + "%"), as_dict=True)
|
||||||
|
return customers
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_customer(name):
|
||||||
|
if not name:
|
||||||
|
return {}
|
||||||
|
cust = frappe.get_doc("Customer", name)
|
||||||
|
result = cust.as_dict()
|
||||||
|
addr = frappe.db.sql("""
|
||||||
|
SELECT a.address_line1, a.city, a.state, a.pincode, a.phone
|
||||||
|
FROM tabAddress a
|
||||||
|
JOIN tabDynamic Link dl ON dl.parent = a.name AND dl.link_doctype = 'Customer' AND dl.link_name = %s
|
||||||
|
LIMIT 1
|
||||||
|
""", (name,), as_dict=True)
|
||||||
|
if addr:
|
||||||
|
result.update({
|
||||||
|
"address_line1": addr[0].address_line1,
|
||||||
|
"city": addr[0].city,
|
||||||
|
"state": addr[0].state,
|
||||||
|
"pincode": addr[0].pincode,
|
||||||
|
"address_phone": addr[0].phone
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_customer_from_intake(data):
|
||||||
|
data = frappe.parse_json(data)
|
||||||
|
if not data.get("customer_name"):
|
||||||
|
frappe.throw(_("Customer name required"))
|
||||||
|
customer = frappe.new_doc("Customer")
|
||||||
|
customer.customer_name = data.get("customer_name")
|
||||||
|
customer.customer_group = data.get("customer_group", "IT Recycling")
|
||||||
|
customer.customer_type = "Company"
|
||||||
|
customer.customer_number = data.get("customer_number")
|
||||||
|
customer.phone = data.get("phone")
|
||||||
|
customer.email_id = data.get("email_id")
|
||||||
|
customer.legacy_notes = data.get("legacy_notes")
|
||||||
|
customer.hours_of_operation = data.get("hours_of_operation")
|
||||||
|
customer.contact_persons = data.get("contact_persons")
|
||||||
|
customer.save()
|
||||||
|
if data.get("address_line1") or data.get("city"):
|
||||||
|
addr = frappe.new_doc("Address")
|
||||||
|
addr.address_title = customer.customer_name
|
||||||
|
addr.address_type = "Billing"
|
||||||
|
addr.address_line1 = data.get("address_line1", "Unknown")
|
||||||
|
addr.city = data.get("city", "Unknown")
|
||||||
|
addr.state = data.get("state", "")
|
||||||
|
addr.pincode = data.get("pincode", "")
|
||||||
|
addr.country = "United States"
|
||||||
|
addr.append("links", {"link_doctype": "Customer", "link_name": customer.name})
|
||||||
|
addr.save()
|
||||||
|
return {"status": "ok", "customer": customer.name}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_pallet(data):
|
||||||
|
data = frappe.parse_json(data)
|
||||||
|
if not data.get("customer"):
|
||||||
|
frappe.throw(_("Customer required"))
|
||||||
|
pallet = frappe.new_doc("Pallet")
|
||||||
|
pallet.customer = data.get("customer")
|
||||||
|
pallet.customer_number = data.get("customer_number")
|
||||||
|
pallet.data_status = data.get("data_status", "D0")
|
||||||
|
pallet.status = data.get("status", "Received")
|
||||||
|
pallet.inbound_weight = data.get("inbound_weight", "")
|
||||||
|
pallet.save()
|
||||||
|
return {"status": "ok", "pallet": pallet.name}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"content": null,"creation": "2026-05-20 22:00:00.000000","docstatus": 0,"doctype": "Page","idx": 0,"modified": "2026-05-20 22:00:00.000000","modified_by": "Administrator","module": "Westech R2","name": "customer-records","owner": "Administrator","page_name": "customer-records","roles": [{"doctype": "Has Role","idx": 1,"name": "a80mopj93i","parent": "customer-records","parentfield": "roles","parenttype": "Page","role": "All"}],"script": null,"standard": "Yes","style": null,"system_page": 0,"title": "Customer Records"}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
<style>
|
||||||
|
.customer-records-page {
|
||||||
|
font-family: Segoe UI, Arial, sans-serif;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
.customer-records-page h1 {
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: 900;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.toolbar button {
|
||||||
|
padding: 8px 20px;
|
||||||
|
border: 2px solid #333;
|
||||||
|
background: #f0f0f0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.toolbar button:hover { background: #e0e0e0; }
|
||||||
|
.toolbar .record-counter {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
.toolbar .nav-btn {
|
||||||
|
padding: 6px 14px;
|
||||||
|
font-size: 18px;
|
||||||
|
min-width: 40px;
|
||||||
|
}
|
||||||
|
.form-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 16px 40px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.form-group label {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 13px;
|
||||||
|
min-width: 140px;
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.form-group input,
|
||||||
|
.form-group textarea {
|
||||||
|
flex: 1;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
font-size: 13px;
|
||||||
|
background: #f8f8f8;
|
||||||
|
}
|
||||||
|
.form-group textarea {
|
||||||
|
min-height: 60px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
.form-group.full-width {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
.search-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 2px solid #333;
|
||||||
|
}
|
||||||
|
.search-bar label {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.search-bar select,
|
||||||
|
.search-bar input {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.search-bar .search-label {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
.search-bar button {
|
||||||
|
padding: 6px 20px;
|
||||||
|
border: 2px solid #333;
|
||||||
|
background: #f0f0f0;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.search-bar button:hover { background: #e0e0e0; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="customer-records-page">
|
||||||
|
<h1>Modify Records</h1>
|
||||||
|
|
||||||
|
<div class="toolbar">
|
||||||
|
<button id="btn-save">Save</button>
|
||||||
|
<span class="record-counter">
|
||||||
|
<span id="current-index">0</span> of <span id="total-count">0</span>
|
||||||
|
</span>
|
||||||
|
<button class="nav-btn" id="btn-prev"><</button>
|
||||||
|
<button class="nav-btn" id="btn-next">></button>
|
||||||
|
<button class="nav-btn" id="btn-first"><<</button>
|
||||||
|
<button class="nav-btn" id="btn-last">>></button>
|
||||||
|
<button id="btn-delete">Delete</button>
|
||||||
|
<button id="btn-print">Print</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-grid">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Record #</label>
|
||||||
|
<input type="text" id="record-number" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Additional Numbers:</label>
|
||||||
|
<input type="text" id="additional-numbers">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>*</label>
|
||||||
|
<input type="text" id="field-star">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Customer Address:</label>
|
||||||
|
<input type="text" id="customer-address">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Any Letter:</label>
|
||||||
|
<input type="text" id="any-letter">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>City:</label>
|
||||||
|
<input type="text" id="city">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>E</label>
|
||||||
|
<input type="text" id="field-e">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Zip:</label>
|
||||||
|
<input type="text" id="zip">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Company Name:</label>
|
||||||
|
<input type="text" id="company-name">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Contacted date:</label>
|
||||||
|
<input type="date" id="contacted-date">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Contact Person(s):</label>
|
||||||
|
<textarea id="contact-persons"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Follow up date:</label>
|
||||||
|
<input type="text" id="follow-up-date">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>E-Mail Address:</label>
|
||||||
|
<input type="email" id="email-address">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Last P/U date:</label>
|
||||||
|
<input type="text" id="last-pu-date">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Contact Numbers:</label>
|
||||||
|
<textarea id="contact-numbers"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Hours of Operation:</label>
|
||||||
|
<input type="text" id="hours-operation">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Comments:</label>
|
||||||
|
<textarea id="comments"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-bar">
|
||||||
|
<label>SEARCH:</label>
|
||||||
|
<select id="search-field">
|
||||||
|
<option value="address">Address</option>
|
||||||
|
<option value="company_name">Company Name</option>
|
||||||
|
<option value="contact_person">Contact Person</option>
|
||||||
|
<option value="phone">Phone</option>
|
||||||
|
<option value="email">Email</option>
|
||||||
|
<option value="city">City</option>
|
||||||
|
<option value="zip">Zip</option>
|
||||||
|
<option value="record_number">Record #</option>
|
||||||
|
</select>
|
||||||
|
<span class="search-label">FOR</span>
|
||||||
|
<input type="text" id="search-value" style="min-width:200px;">
|
||||||
|
<button id="btn-search">Search!</button>
|
||||||
|
<button id="btn-reset">Reset</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_records():
|
||||||
|
# For now return Lead records mapped to form fields
|
||||||
|
leads = frappe.db.sql("""
|
||||||
|
SELECT name, company_name, email_id, mobile_no, phone, address_line1, city, state, pincode,
|
||||||
|
title, status, industry, source
|
||||||
|
FROM tabLead
|
||||||
|
ORDER BY creation DESC
|
||||||
|
LIMIT 1000
|
||||||
|
""", as_dict=True)
|
||||||
|
result = []
|
||||||
|
for l in leads:
|
||||||
|
result.append({
|
||||||
|
"name": l.name,
|
||||||
|
"additional_numbers": "",
|
||||||
|
"field_star": l.status or "",
|
||||||
|
"customer_address": l.address_line1 or "",
|
||||||
|
"any_letter": l.title or "",
|
||||||
|
"city": l.city or "",
|
||||||
|
"field_e": l.email_id or "",
|
||||||
|
"zip": l.pincode or "",
|
||||||
|
"company_name": l.company_name or "",
|
||||||
|
"contacted_date": "",
|
||||||
|
"contact_persons": l.title or "",
|
||||||
|
"follow_up_date": "",
|
||||||
|
"email_address": l.email_id or "",
|
||||||
|
"last_pu_date": "",
|
||||||
|
"contact_numbers": (l.phone or "") + "\n" + (l.mobile_no or ""),
|
||||||
|
"hours_operation": l.industry or "",
|
||||||
|
"comments": l.source or ""
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def save_record(data):
|
||||||
|
data = frappe.parse_json(data)
|
||||||
|
# For now just return OK - Lead update can be wired later
|
||||||
|
return {"status": "ok", "message": "Saved " + (data.get("name") or "")}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def delete_record(name):
|
||||||
|
# For now return OK
|
||||||
|
return {"status": "ok", "message": "Deleted " + name}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def search_records(field, value):
|
||||||
|
leads = frappe.db.sql("""
|
||||||
|
SELECT name, company_name, email_id, mobile_no, phone, address_line1, city, state, pincode,
|
||||||
|
title, status, industry, source
|
||||||
|
FROM tabLead
|
||||||
|
WHERE LOWER(company_name) LIKE %s
|
||||||
|
OR LOWER(title) LIKE %s
|
||||||
|
OR LOWER(address_line1) LIKE %s
|
||||||
|
OR LOWER(city) LIKE %s
|
||||||
|
OR LOWER(pincode) LIKE %s
|
||||||
|
OR LOWER(email_id) LIKE %s
|
||||||
|
OR LOWER(phone) LIKE %s
|
||||||
|
OR LOWER(mobile_no) LIKE %s
|
||||||
|
ORDER BY creation DESC
|
||||||
|
LIMIT 100
|
||||||
|
""", tuple(["%" + value + "%"] * 8), as_dict=True)
|
||||||
|
result = []
|
||||||
|
for l in leads:
|
||||||
|
result.append({
|
||||||
|
"name": l.name,
|
||||||
|
"additional_numbers": "",
|
||||||
|
"field_star": l.status or "",
|
||||||
|
"customer_address": l.address_line1 or "",
|
||||||
|
"any_letter": l.title or "",
|
||||||
|
"city": l.city or "",
|
||||||
|
"field_e": l.email_id or "",
|
||||||
|
"zip": l.pincode or "",
|
||||||
|
"company_name": l.company_name or "",
|
||||||
|
"contacted_date": "",
|
||||||
|
"contact_persons": l.title or "",
|
||||||
|
"follow_up_date": "",
|
||||||
|
"email_address": l.email_id or "",
|
||||||
|
"last_pu_date": "",
|
||||||
|
"contact_numbers": (l.phone or "") + "\n" + (l.mobile_no or ""),
|
||||||
|
"hours_operation": l.industry or "",
|
||||||
|
"comments": l.source or ""
|
||||||
|
})
|
||||||
|
return result
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.badge-fresh { background-color: #28a745; }
|
||||||
|
.badge-aging { background-color: #ffc107; color: #212529; }
|
||||||
|
.badge-expired { background-color: #dc3545; }
|
||||||
|
.badge-needs { background-color: #fd7e14; }
|
||||||
|
.badge-error { background-color: #6c757d; }
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<style>
|
||||||
|
.badge.badge-fresh { background-color: #28a745; }
|
||||||
|
.badge.badge-aging { background-color: #ffc107; color: #212529; }
|
||||||
|
.badge.badge-expired { background-color: #dc3545; }
|
||||||
|
.badge.badge-needs { background-color: #fd7e14; }
|
||||||
|
.badge.badge-error { background-color: #6c757d; }
|
||||||
|
</style>
|
||||||
|
<div id="ebay-pricing-page"></div>
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
frappe.pages['ebay-pricing'].on_page_load = function(wrapper) {
|
||||||
|
var page = frappe.ui.make_app_page({
|
||||||
|
parent: wrapper,
|
||||||
|
title: __('eBay Pricing'),
|
||||||
|
single_column: true
|
||||||
|
});
|
||||||
|
|
||||||
|
let $container = $(`
|
||||||
|
<div style="padding: 1rem;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Search Model</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" id="ebay-search-input"
|
||||||
|
placeholder="Dell Latitude 5410..." autocomplete="off">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button class="btn btn-primary" id="ebay-search-btn">
|
||||||
|
<i class="fa fa-search"></i> Search
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 text-right">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Batch Size</label>
|
||||||
|
<select class="form-control" id="ebay-batch-size" style="display:inline-block; width:auto;">
|
||||||
|
<option value="10">10</option>
|
||||||
|
<option value="25">25</option>
|
||||||
|
<option value="50">50</option>
|
||||||
|
<option value="100">100</option>
|
||||||
|
<option value="all">All</option>
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-warning" id="ebay-batch-btn">
|
||||||
|
<i class="fa fa-play"></i> Price Batch
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h4>Apply Pricing to Inventory</h4>
|
||||||
|
<div class="form-group">
|
||||||
|
<select class="form-control" id="ebay-apply-item" style="width: 100%;">
|
||||||
|
<option value="">Select Item to apply pricing...</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-success" id="ebay-apply-btn">
|
||||||
|
<i class="fa fa-check"></i> Apply Pricing
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-info" id="ebay-apply-all-btn" style="margin-left: 0.5rem;">
|
||||||
|
<i class="fa fa-check-double"></i> Apply All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 text-right">
|
||||||
|
<div class="well" style="display: inline-block; text-align: left;">
|
||||||
|
<h5>Pricing Status</h5>
|
||||||
|
<div id="pricing-stats">
|
||||||
|
<p class="text-muted">Click Apply All to see stats</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div id="ebay-results-area">
|
||||||
|
<div class="text-muted text-center" style="padding: 4rem;">
|
||||||
|
<i class="fa fa-search" style="font-size: 3rem; opacity: 0.3;"></i>
|
||||||
|
<p>Search for a model or run batch pricing</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`).appendTo(page.main);
|
||||||
|
|
||||||
|
// Load item dropdown
|
||||||
|
load_item_dropdown();
|
||||||
|
|
||||||
|
// Event handlers
|
||||||
|
$container.find('#ebay-search-btn').on('click', function() {
|
||||||
|
let query = $container.find('#ebay-search-input').val().trim();
|
||||||
|
if (!query) {
|
||||||
|
frappe.msgprint(__('Enter a model to search'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
search_ebay(query);
|
||||||
|
});
|
||||||
|
|
||||||
|
$container.find('#ebay-search-input').on('keypress', function(e) {
|
||||||
|
if (e.which === 13) {
|
||||||
|
$container.find('#ebay-search-btn').trigger('click');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$container.find('#ebay-batch-btn').on('click', function() {
|
||||||
|
let size = $container.find('#ebay-batch-size').val();
|
||||||
|
run_batch(size);
|
||||||
|
});
|
||||||
|
|
||||||
|
$container.find('#ebay-apply-btn').on('click', function() {
|
||||||
|
let item = $container.find('#ebay-apply-item').val();
|
||||||
|
if (!item) {
|
||||||
|
frappe.msgprint(__('Select an Item first'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
apply_pricing(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
$container.find('#ebay-apply-all-btn').on('click', function() {
|
||||||
|
apply_pricing_all();
|
||||||
|
});
|
||||||
|
|
||||||
|
function load_item_dropdown() {
|
||||||
|
frappe.call({
|
||||||
|
method: 'frappe.client.get_list',
|
||||||
|
args: {
|
||||||
|
doctype: 'Item',
|
||||||
|
filters: {
|
||||||
|
'disabled': 0,
|
||||||
|
'item_group': ['in', ['Laptop', 'Desktop', 'Tablet', 'Phone', 'Workstation']]
|
||||||
|
},
|
||||||
|
fields: ['name', 'item_name'],
|
||||||
|
limit: 1000
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
let $select = $container.find('#ebay-apply-item');
|
||||||
|
r.message.forEach(item => {
|
||||||
|
$select.append(`<option value="${item.name}">${item.item_name || item.name}</option>`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function search_ebay(query) {
|
||||||
|
frappe.call({
|
||||||
|
method: 'westech_r2.api.ebay_pricing.search_model',
|
||||||
|
args: { query: query },
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __('Searching eBay sold listings...'),
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.results) {
|
||||||
|
render_results(r.message);
|
||||||
|
} else {
|
||||||
|
let msg = (r.message && r.message.message) || __('No results found');
|
||||||
|
frappe.msgprint(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function run_batch(size) {
|
||||||
|
frappe.call({
|
||||||
|
method: 'westech_r2.api.ebay_pricing.run_batch',
|
||||||
|
args: { batch_size: size },
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __('Running batch pricing...'),
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
frappe.msgprint(__('Batch complete: {0} priced, {1} failed, {2} skipped',
|
||||||
|
[r.message.priced, r.message.failed, r.message.skipped]));
|
||||||
|
load_recent_pricing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function apply_pricing(item_code) {
|
||||||
|
frappe.call({
|
||||||
|
method: 'westech_r2.api.ebay_pricing.batch_apply_pricing',
|
||||||
|
args: { item_code: item_code },
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __('Applying pricing to Serial Nos...'),
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
render_pricing_stats(r.message);
|
||||||
|
frappe.msgprint(__('Pricing applied: {0} priced, {1} commodity, {2} needs grading',
|
||||||
|
[r.message.priced, r.message.commodity, r.message.needs_grading]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function apply_pricing_all() {
|
||||||
|
frappe.call({
|
||||||
|
method: 'westech_r2.api.ebay_pricing.batch_apply_pricing',
|
||||||
|
args: { batch_size: 1000 },
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __('Applying pricing to all Serial Nos...'),
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
render_pricing_stats(r.message);
|
||||||
|
frappe.msgprint(__('Batch pricing applied: {0} priced, {1} commodity, {2} needs grading, {3} errors',
|
||||||
|
[r.message.priced, r.message.commodity, r.message.needs_grading, r.message.errors]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function render_pricing_stats(stats) {
|
||||||
|
let html = `
|
||||||
|
<table class="table table-condensed" style="margin-bottom: 0;">
|
||||||
|
<tr><td>Priced</td><td><span class="badge badge-success">${stats.priced || 0}</span></td></tr>
|
||||||
|
<tr><td>Commodity</td><td><span class="badge badge-warning">${stats.commodity || 0}</span></td></tr>
|
||||||
|
<tr><td>Needs Grading</td><td><span class="badge badge-info">${stats.needs_grading || 0}</span></td></tr>
|
||||||
|
<tr><td>Needs Price Point</td><td><span class="badge badge-primary">${stats.needs_price_point || 0}</span></td></tr>
|
||||||
|
<tr><td>Errors</td><td><span class="badge badge-danger">${stats.errors || 0}</span></td></tr>
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
$container.find('#pricing-stats').html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
function render_results(data) {
|
||||||
|
let $area = $container.find('#ebay-results-area').empty();
|
||||||
|
if (!data.results || !data.results.length) {
|
||||||
|
$area.html(`<div class="text-muted text-center" style="padding: 2rem;">No results</div>`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = `<table class="table table-bordered">
|
||||||
|
<thead><tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Price</th>
|
||||||
|
<th>Condition</th>
|
||||||
|
<th>Sold</th>
|
||||||
|
<th>Shipping</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>`;
|
||||||
|
data.results.forEach(item => {
|
||||||
|
html += `<tr>
|
||||||
|
<td>${frappe.utils.escape_html(item.title || '')}</td>
|
||||||
|
<td>$${(item.price || 0).toFixed(2)}</td>
|
||||||
|
<td>${frappe.utils.escape_html(item.condition || '')}</td>
|
||||||
|
<td>${item.sold || ''}</td>
|
||||||
|
<td>${item.shipping || ''}</td>
|
||||||
|
</tr>`;
|
||||||
|
});
|
||||||
|
html += `</tbody></table>`;
|
||||||
|
|
||||||
|
if (data.pricing) {
|
||||||
|
html += `<div class="well">
|
||||||
|
<h4>Pricing Summary</h4>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3"><strong>Low:</strong> $${data.pricing.price_low}</div>
|
||||||
|
<div class="col-md-3"><strong>High:</strong> $${data.pricing.price_high}</div>
|
||||||
|
<div class="col-md-3"><strong>Average:</strong> $${data.pricing.price_average}</div>
|
||||||
|
<div class="col-md-3"><strong>Median:</strong> $${data.pricing.price_auction}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row" style="margin-top: 1rem;">
|
||||||
|
<div class="col-md-6"><strong>Source:</strong> ${data.pricing.source}</div>
|
||||||
|
<div class="col-md-6"><strong>Samples:</strong> ${data.pricing.sample_count}</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
$area.html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_recent_pricing() {
|
||||||
|
frappe.call({
|
||||||
|
method: 'westech_r2.api.ebay_pricing.get_recent_pricing',
|
||||||
|
args: { limit: 50 },
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
render_pricing_grid(r.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function render_pricing_grid(items) {
|
||||||
|
let $area = $container.find('#ebay-results-area');
|
||||||
|
if (!items || !items.length) {
|
||||||
|
$area.html(`<div class="text-muted text-center" style="padding: 2rem;">No pricing data yet</div>`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = `<h4>Recent Pricing Results</h4>
|
||||||
|
<table class="table table-bordered table-hover">
|
||||||
|
<thead><tr>
|
||||||
|
<th>Manufacturer</th>
|
||||||
|
<th>Model</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Age</th>
|
||||||
|
<th>Low</th>
|
||||||
|
<th>High</th>
|
||||||
|
<th>Avg</th>
|
||||||
|
<th>Samples</th>
|
||||||
|
<th>Source</th>
|
||||||
|
<th>Last Priced</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>`;
|
||||||
|
|
||||||
|
items.forEach(row => {
|
||||||
|
let status_class = 'badge-needs';
|
||||||
|
if (row.pricing_status === 'Priced') status_class = 'badge-fresh';
|
||||||
|
else if (row.pricing_status === 'Manual Override') status_class = 'badge-fresh';
|
||||||
|
else if (row.pricing_status === 'Expired') status_class = 'badge-expired';
|
||||||
|
else if (row.pricing_status === 'Error') status_class = 'badge-error';
|
||||||
|
|
||||||
|
let age = row.days_since_pricing || 0;
|
||||||
|
let age_badge = age < 90 ? 'badge-fresh' : (age < 120 ? 'badge-aging' : 'badge-expired');
|
||||||
|
|
||||||
|
html += `<tr>
|
||||||
|
<td>${frappe.utils.escape_html(row.manufacturer || '')}</td>
|
||||||
|
<td>${frappe.utils.escape_html(row.model || '')}</td>
|
||||||
|
<td><span class="badge ${status_class}">${row.pricing_status}</span></td>
|
||||||
|
<td><span class="badge ${age_badge}">${age} days</span></td>
|
||||||
|
<td>$${row.price_low || ''}</td>
|
||||||
|
<td>$${row.price_high || ''}</td>
|
||||||
|
<td>$${row.price_average || ''}</td>
|
||||||
|
<td>${row.sample_count || ''}</td>
|
||||||
|
<td>${row.source || ''}</td>
|
||||||
|
<td>${frappe.datetime.str_to_user(row.scraped_at) || ''}</td>
|
||||||
|
</tr>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `</tbody></table>`;
|
||||||
|
$area.html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
load_recent_pricing();
|
||||||
|
};
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"creation": "2026-05-17 05:30:00.000000",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Page",
|
||||||
|
"icon": "fa fa-tags",
|
||||||
|
"modified": "2026-05-17 05:30:00.000000",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Westech R2",
|
||||||
|
"name": "ebay-pricing",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"page_name": "ebay-pricing",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "System Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Stock User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Sales User"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"standard": "Yes",
|
||||||
|
"system_page": 0,
|
||||||
|
"title": "eBay Pricing"
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# eBay Pricing desk page
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.badge-fresh { background-color: #28a745; }
|
||||||
|
.badge-aging { background-color: #ffc107; color: #212529; }
|
||||||
|
.badge-expired { background-color: #dc3545; }
|
||||||
|
.badge-needs { background-color: #fd7e14; }
|
||||||
|
.badge-error { background-color: #6c757d; }
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<style>
|
||||||
|
.badge.badge-fresh { background-color: #28a745; }
|
||||||
|
.badge.badge-aging { background-color: #ffc107; color: #212529; }
|
||||||
|
.badge.badge-expired { background-color: #dc3545; }
|
||||||
|
.badge.badge-needs { background-color: #fd7e14; }
|
||||||
|
.badge.badge-error { background-color: #6c757d; }
|
||||||
|
</style>
|
||||||
|
<div id="ebay-pricing-page"></div>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ebay-pricing.js
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ebay-pricing.json
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# eBay Pricing desk page
|
||||||
@@ -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 @@
|
|||||||
|
/* CSS */
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<div class="ld-container" style="padding: 20px;">
|
||||||
|
<style>
|
||||||
|
.ld-header { background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px; }
|
||||||
|
.ld-table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 13px; }
|
||||||
|
.ld-table th { background: #37474f; color: white; padding: 8px; text-align: left; }
|
||||||
|
.ld-table td { padding: 6px 8px; border-bottom: 1px solid #e0e0e0; }
|
||||||
|
.ld-table .num { text-align: right; font-family: monospace; }
|
||||||
|
.ld-btn { background: #455a64; color: white; border: none; padding: 8px 20px; border-radius: 4px; cursor: pointer; margin-bottom: 20px; margin-right: 10px; }
|
||||||
|
.ld-badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px; margin-right: 4px; }
|
||||||
|
.bg-rcv { background: #e3f2fd; color: #1565c0; }
|
||||||
|
.bg-hdr { background: #fff3e0; color: #e65100; }
|
||||||
|
.bg-tst { background: #e8f5e9; color: #2e7d32; }
|
||||||
|
.bg-r2 { background: #f3e5f5; color: #7b1fa2; }
|
||||||
|
.bg-des { background: #ffebee; color: #c62828; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="ld-header">
|
||||||
|
<h2>Load: <span id="ld-name"></span></h2>
|
||||||
|
<div>In Date: <span id="ld-date"></span> | Customer: <span id="ld-cust"></span> | Devices: <span id="ld-dev"></span> | Weight: <span id="ld-wt"></span> lbs</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 15px;">
|
||||||
|
<span class="ld-badge bg-rcv">Receiving</span>
|
||||||
|
<span class="ld-badge bg-hdr">HDR / Disassembly</span>
|
||||||
|
<span class="ld-badge bg-tst">Test</span>
|
||||||
|
<span class="ld-badge bg-r2">R2 Downstream</span>
|
||||||
|
<span class="ld-badge bg-des">Destruction</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="ld-btn" onclick="window.print()">Print Data Tracking Worksheet</button>
|
||||||
|
<button class="ld-btn" style="background: #1976d2;" onclick="window.location.href='/app/load-list'">Back to Loads</button>
|
||||||
|
|
||||||
|
<table class="ld-table" id="ld-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th rowspan="2">Material Type</th>
|
||||||
|
<th colspan="3" style="text-align:center; background:#1565c0;">Receiving</th>
|
||||||
|
<th colspan="3" style="text-align:center; background:#e65100;">HDR / Disassembly</th>
|
||||||
|
<th colspan="3" style="text-align:center; background:#2e7d32;">Test</th>
|
||||||
|
<th style="text-align:center; background:#7b1fa2;">R2</th>
|
||||||
|
<th colspan="4" style="text-align:center; background:#c62828;">Destruction</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Count</th><th>Status</th><th>Send To</th>
|
||||||
|
<th>Recv'd</th><th>HDD Out</th><th>Send To</th>
|
||||||
|
<th>Sanitized</th><th>Status</th><th>Send To</th>
|
||||||
|
<th>Sent</th>
|
||||||
|
<th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="ld-body"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
frappe.pages["load-detail"].on_page_load = function(wrapper) {
|
||||||
|
var page = frappe.ui.make_app_page({
|
||||||
|
parent: wrapper,
|
||||||
|
title: "Load Detail",
|
||||||
|
single_column: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var loadName = frappe.utils.get_url_arg("load");
|
||||||
|
if (!loadName) {
|
||||||
|
$(page.body).html("<div style='padding:40px;text-align:center;'><h2>No load specified</h2><p>Use ?load=MMDDYYYY-XXXX</p></div>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: "frappe.client.get",
|
||||||
|
args: { doctype: "Load", name: loadName },
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) { showLoad(page, r.message); }
|
||||||
|
else { $(page.body).html("<div style='padding:40px;text-align:center;'><h2>Load not found</h2></div>"); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function showLoad(page, load) {
|
||||||
|
var h = "<div style='padding:20px;'>";
|
||||||
|
h += "<div style='background:#f8f9fa;padding:15px;border-radius:8px;margin-bottom:20px;'>";
|
||||||
|
h += "<h2>Load: " + load.name + "</h2>";
|
||||||
|
h += "<div>In Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "</div>";
|
||||||
|
h += "<div>Devices: " + (load.total_devices || 0) + " | Weight: " + (load.total_weight || 0) + " lbs</div>";
|
||||||
|
h += "</div>";
|
||||||
|
h += "<button class='btn btn-primary' onclick='window.print()' style='margin-bottom:15px;'>Print Data Tracking Worksheet</button> ";
|
||||||
|
h += "<a href='/app/load/" + encodeURIComponent(load.name) + "' class='btn btn-default' style='margin-bottom:15px;'>Open Form View</a> ";
|
||||||
|
h += "<a href='/app/load-update?load=" + encodeURIComponent(load.name) + "' class='btn btn-default' style='margin-bottom:15px;'>Edit Load</a>";
|
||||||
|
h += "<table style='width:100%;border-collapse:collapse;font-size:13px;margin-top:20px;'>";
|
||||||
|
h += "<thead><tr style='background:#37474f;color:white;'>";
|
||||||
|
h += "<th>Material Type</th><th>Count</th><th>Rcv Status</th><th>Send To</th>";
|
||||||
|
h += "<th>Recv'd</th><th>HDD Out</th><th>Dis Send To</th>";
|
||||||
|
h += "<th>Sanitized</th><th>Test Status</th><th>Test Send</th>";
|
||||||
|
h += "<th>R2 Sent</th><th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>";
|
||||||
|
h += "</tr></thead><tbody>";
|
||||||
|
|
||||||
|
if (load.material_items && load.material_items.length > 0) {
|
||||||
|
load.material_items.forEach(function(item) {
|
||||||
|
h += "<tr style='border-bottom:1px solid #e0e0e0;'>";
|
||||||
|
h += "<td><strong>" + (item.material_type || "") + "</strong></td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.total_count || 0) + "</td>";
|
||||||
|
h += "<td>" + (item.initial_data_status || "") + "</td>";
|
||||||
|
h += "<td>" + (item.send_to || "") + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.devices_received || 0) + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.hdd_removed || 0) + "</td>";
|
||||||
|
h += "<td>" + (item.disassembly_send_to || "") + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.units_sanitized_software || 0) + "</td>";
|
||||||
|
h += "<td>" + (item.test_data_status || "") + "</td>";
|
||||||
|
h += "<td>" + (item.test_send_to || "") + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.r2_units_sent || 0) + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.units_physical_destruction || 0) + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.hdd_needs_sanitize || 0) + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.hdd_physical_destruction || 0) + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.hdd_logical_sanitization || 0) + "</td>";
|
||||||
|
h += "</tr>";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
h += "</tbody></table></div>";
|
||||||
|
$(page.body).html(h);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"doctype": "Page",
|
||||||
|
"name": "load-detail",
|
||||||
|
"page_name": "load-detail",
|
||||||
|
"title": "Load Detail",
|
||||||
|
"page_type": "Web Page",
|
||||||
|
"module": "Westech R2",
|
||||||
|
"standard": "Yes",
|
||||||
|
"system_page": 0,
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "All"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"content": "",
|
||||||
|
"script": null,
|
||||||
|
"style": null
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* CSS */
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<div class="ld-container" style="padding: 20px;">
|
||||||
|
<style>
|
||||||
|
.ld-header { background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px; }
|
||||||
|
.ld-table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 13px; }
|
||||||
|
.ld-table th { background: #37474f; color: white; padding: 8px; text-align: left; }
|
||||||
|
.ld-table td { padding: 6px 8px; border-bottom: 1px solid #e0e0e0; }
|
||||||
|
.ld-table .num { text-align: right; font-family: monospace; }
|
||||||
|
.ld-btn { background: #455a64; color: white; border: none; padding: 8px 20px; border-radius: 4px; cursor: pointer; margin-bottom: 20px; margin-right: 10px; }
|
||||||
|
.ld-badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px; margin-right: 4px; }
|
||||||
|
.bg-rcv { background: #e3f2fd; color: #1565c0; }
|
||||||
|
.bg-hdr { background: #fff3e0; color: #e65100; }
|
||||||
|
.bg-tst { background: #e8f5e9; color: #2e7d32; }
|
||||||
|
.bg-r2 { background: #f3e5f5; color: #7b1fa2; }
|
||||||
|
.bg-des { background: #ffebee; color: #c62828; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="ld-header">
|
||||||
|
<h2>Load: <span id="ld-name"></span></h2>
|
||||||
|
<div>In Date: <span id="ld-date"></span> | Customer: <span id="ld-cust"></span> | Devices: <span id="ld-dev"></span> | Weight: <span id="ld-wt"></span> lbs</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 15px;">
|
||||||
|
<span class="ld-badge bg-rcv">Receiving</span>
|
||||||
|
<span class="ld-badge bg-hdr">HDR / Disassembly</span>
|
||||||
|
<span class="ld-badge bg-tst">Test</span>
|
||||||
|
<span class="ld-badge bg-r2">R2 Downstream</span>
|
||||||
|
<span class="ld-badge bg-des">Destruction</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="ld-btn" onclick="window.print()">Print Data Tracking Worksheet</button>
|
||||||
|
<button class="ld-btn" style="background: #1976d2;" onclick="window.location.href='/app/load-list'">Back to Loads</button>
|
||||||
|
|
||||||
|
<table class="ld-table" id="ld-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th rowspan="2">Material Type</th>
|
||||||
|
<th colspan="3" style="text-align:center; background:#1565c0;">Receiving</th>
|
||||||
|
<th colspan="3" style="text-align:center; background:#e65100;">HDR / Disassembly</th>
|
||||||
|
<th colspan="3" style="text-align:center; background:#2e7d32;">Test</th>
|
||||||
|
<th style="text-align:center; background:#7b1fa2;">R2</th>
|
||||||
|
<th colspan="4" style="text-align:center; background:#c62828;">Destruction</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Count</th><th>Status</th><th>Send To</th>
|
||||||
|
<th>Recv'd</th><th>HDD Out</th><th>Send To</th>
|
||||||
|
<th>Sanitized</th><th>Status</th><th>Send To</th>
|
||||||
|
<th>Sent</th>
|
||||||
|
<th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="ld-body"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
frappe.pages["load-detail"].on_page_load = function(wrapper) {
|
||||||
|
var page = frappe.ui.make_app_page({
|
||||||
|
parent: wrapper,
|
||||||
|
title: "Load Detail",
|
||||||
|
single_column: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var loadName = frappe.utils.get_url_arg("load");
|
||||||
|
if (!loadName) {
|
||||||
|
$(page.body).html("<div style='padding:40px;text-align:center;'><h2>No load specified</h2><p>Use ?load=MMDDYYYY-XXXX</p></div>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: "frappe.client.get",
|
||||||
|
args: { doctype: "Load", name: loadName },
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) { showLoad(page, r.message); }
|
||||||
|
else { $(page.body).html("<div style='padding:40px;text-align:center;'><h2>Load not found</h2></div>"); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function showLoad(page, load) {
|
||||||
|
var h = "<div style='padding:20px;'>";
|
||||||
|
h += "<div style='background:#f8f9fa;padding:15px;border-radius:8px;margin-bottom:20px;'>";
|
||||||
|
h += "<h2>Load: " + load.name + "</h2>";
|
||||||
|
h += "<div>In Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "</div>";
|
||||||
|
h += "<div>Devices: " + (load.total_devices || 0) + " | Weight: " + (load.total_weight || 0) + " lbs</div>";
|
||||||
|
h += "</div>";
|
||||||
|
h += "<button class='btn btn-primary' onclick='window.print()' style='margin-bottom:15px;'>Print Data Tracking Worksheet</button> ";
|
||||||
|
h += "<a href='/app/load/" + encodeURIComponent(load.name) + "' class='btn btn-default' style='margin-bottom:15px;'>Open Form View</a> ";
|
||||||
|
h += "<a href='/app/load-update?load=" + encodeURIComponent(load.name) + "' class='btn btn-default' style='margin-bottom:15px;'>Edit Load</a>";
|
||||||
|
h += "<table style='width:100%;border-collapse:collapse;font-size:13px;margin-top:20px;'>";
|
||||||
|
h += "<thead><tr style='background:#37474f;color:white;'>";
|
||||||
|
h += "<th>Material Type</th><th>Count</th><th>Rcv Status</th><th>Send To</th>";
|
||||||
|
h += "<th>Recv'd</th><th>HDD Out</th><th>Dis Send To</th>";
|
||||||
|
h += "<th>Sanitized</th><th>Test Status</th><th>Test Send</th>";
|
||||||
|
h += "<th>R2 Sent</th><th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>";
|
||||||
|
h += "</tr></thead><tbody>";
|
||||||
|
|
||||||
|
if (load.material_items && load.material_items.length > 0) {
|
||||||
|
load.material_items.forEach(function(item) {
|
||||||
|
h += "<tr style='border-bottom:1px solid #e0e0e0;'>";
|
||||||
|
h += "<td><strong>" + (item.material_type || "") + "</strong></td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.total_count || 0) + "</td>";
|
||||||
|
h += "<td>" + (item.initial_data_status || "") + "</td>";
|
||||||
|
h += "<td>" + (item.send_to || "") + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.devices_received || 0) + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.hdd_removed || 0) + "</td>";
|
||||||
|
h += "<td>" + (item.disassembly_send_to || "") + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.units_sanitized_software || 0) + "</td>";
|
||||||
|
h += "<td>" + (item.test_data_status || "") + "</td>";
|
||||||
|
h += "<td>" + (item.test_send_to || "") + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.r2_units_sent || 0) + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.units_physical_destruction || 0) + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.hdd_needs_sanitize || 0) + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.hdd_physical_destruction || 0) + "</td>";
|
||||||
|
h += "<td style='text-align:right;'>" + (item.hdd_logical_sanitization || 0) + "</td>";
|
||||||
|
h += "</tr>";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
h += "</tbody></table></div>";
|
||||||
|
$(page.body).html(h);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"doctype": "Page",
|
||||||
|
"name": "load-detail",
|
||||||
|
"page_name": "load-detail",
|
||||||
|
"title": "Load Detail",
|
||||||
|
"page_type": "Web Page",
|
||||||
|
"module": "Westech R2",
|
||||||
|
"standard": "Yes",
|
||||||
|
"system_page": 0,
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "All"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"content": "",
|
||||||
|
"script": null,
|
||||||
|
"style": null
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* Load Update page CSS */
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<div style="padding:20px;">
|
||||||
|
<h2>Load Update</h2>
|
||||||
|
<p>Use the form below to update load material items.</p>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
frappe.pages["load-update"].on_page_load = function(wrapper) {
|
||||||
|
var page = frappe.ui.make_app_page({
|
||||||
|
parent: wrapper,
|
||||||
|
title: "Load Update",
|
||||||
|
single_column: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var loadName = frappe.utils.get_url_arg("load");
|
||||||
|
if (!loadName) {
|
||||||
|
$(page.body).html("<div style='padding:40px;text-align:center;'><h2>No load specified</h2><p>Use ?load=MMDDYYYY-XXXX</p></div>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadLoad() {
|
||||||
|
frappe.call({
|
||||||
|
method: "frappe.client.get",
|
||||||
|
args: { doctype: "Load", name: loadName },
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) { renderForm(page, r.message); }
|
||||||
|
else { $(page.body).html("<div style='padding:40px;text-align:center;'><h2>Load not found</h2></div>"); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderForm(page, load) {
|
||||||
|
var h = "<div style='padding:20px;'>";
|
||||||
|
h += "<h2>Update Load: " + load.name + "</h2>";
|
||||||
|
h += "<p>Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "</p>";
|
||||||
|
h += "<div style='margin-bottom:15px;'>";
|
||||||
|
h += "<button id='lu-save' class='btn btn-primary'>Save Changes</button> ";
|
||||||
|
h += "<button id='lu-print' class='btn btn-default'>Print Worksheet</button> ";
|
||||||
|
h += "<a href='/app/load/" + encodeURIComponent(load.name) + "' class='btn btn-default'>Open Form View</a>";
|
||||||
|
h += "</div>";
|
||||||
|
h += "<table style='width:100%;border-collapse:collapse;font-size:13px;'>";
|
||||||
|
h += "<thead><tr style='background:#37474f;color:white;'>";
|
||||||
|
h += "<th>Material Type</th><th>Count</th><th>Recv Status</th><th>Send To</th>";
|
||||||
|
h += "<th>Recv'd</th><th>HDD Out</th><th>Dis Send To</th>";
|
||||||
|
h += "<th>Sanitized</th><th>Test Status</th><th>Test Send</th>";
|
||||||
|
h += "<th>R2 Sent</th><th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>";
|
||||||
|
h += "</tr></thead><tbody>";
|
||||||
|
|
||||||
|
if (load.material_items && load.material_items.length > 0) {
|
||||||
|
load.material_items.forEach(function(item, idx) {
|
||||||
|
var prefix = "item_" + idx + "_";
|
||||||
|
h += "<tr style='border-bottom:1px solid #e0e0e0;'>";
|
||||||
|
h += "<td><strong>" + (item.material_type || "") + "</strong></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "total_count' value='" + (item.total_count || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='text' id='" + prefix + "initial_data_status' value='" + (item.initial_data_status || "") + "' style='width:60px;'></td>";
|
||||||
|
h += "<td><input type='text' id='" + prefix + "send_to' value='" + (item.send_to || "") + "' style='width:80px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "devices_received' value='" + (item.devices_received || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "hdd_removed' value='" + (item.hdd_removed || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='text' id='" + prefix + "disassembly_send_to' value='" + (item.disassembly_send_to || "") + "' style='width:80px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "units_sanitized_software' value='" + (item.units_sanitized_software || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='text' id='" + prefix + "test_data_status' value='" + (item.test_data_status || "") + "' style='width:60px;'></td>";
|
||||||
|
h += "<td><input type='text' id='" + prefix + "test_send_to' value='" + (item.test_send_to || "") + "' style='width:80px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "r2_units_sent' value='" + (item.r2_units_sent || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "units_physical_destruction' value='" + (item.units_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "hdd_needs_sanitize' value='" + (item.hdd_needs_sanitize || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "hdd_physical_destruction' value='" + (item.hdd_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "hdd_logical_sanitization' value='" + (item.hdd_logical_sanitization || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "</tr>";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
h += "</tbody></table></div>";
|
||||||
|
$(page.body).html(h);
|
||||||
|
|
||||||
|
$("#lu-save").on("click", function() {
|
||||||
|
var items = [];
|
||||||
|
load.material_items.forEach(function(item, idx) {
|
||||||
|
var prefix = "#item_" + idx + "_";
|
||||||
|
items.push({
|
||||||
|
name: item.name,
|
||||||
|
total_count: parseInt($(prefix + "total_count").val()) || 0,
|
||||||
|
initial_data_status: $(prefix + "initial_data_status").val(),
|
||||||
|
send_to: $(prefix + "send_to").val(),
|
||||||
|
devices_received: parseInt($(prefix + "devices_received").val()) || 0,
|
||||||
|
hdd_removed: parseInt($(prefix + "hdd_removed").val()) || 0,
|
||||||
|
disassembly_send_to: $(prefix + "disassembly_send_to").val(),
|
||||||
|
units_sanitized_software: parseInt($(prefix + "units_sanitized_software").val()) || 0,
|
||||||
|
test_data_status: $(prefix + "test_data_status").val(),
|
||||||
|
test_send_to: $(prefix + "test_send_to").val(),
|
||||||
|
r2_units_sent: parseInt($(prefix + "r2_units_sent").val()) || 0,
|
||||||
|
units_physical_destruction: parseInt($(prefix + "units_physical_destruction").val()) || 0,
|
||||||
|
hdd_needs_sanitize: parseInt($(prefix + "hdd_needs_sanitize").val()) || 0,
|
||||||
|
hdd_physical_destruction: parseInt($(prefix + "hdd_physical_destruction").val()) || 0,
|
||||||
|
hdd_logical_sanitization: parseInt($(prefix + "hdd_logical_sanitization").val()) || 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
frappe.call({
|
||||||
|
method: "westech_r2.page.load-update.load-update.save_load_items",
|
||||||
|
args: { load_name: loadName, items: JSON.stringify(items) },
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.status === "ok") {
|
||||||
|
frappe.show_alert({message: "Saved", indicator: "green"});
|
||||||
|
loadLoad();
|
||||||
|
} else {
|
||||||
|
frappe.show_alert({message: "Save failed", indicator: "red"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#lu-print").on("click", function() {
|
||||||
|
window.print();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadLoad();
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"doctype": "Page",
|
||||||
|
"name": "load-update",
|
||||||
|
"page_name": "load-update",
|
||||||
|
"title": "Load Update",
|
||||||
|
"page_type": "Web Page",
|
||||||
|
"module": "Westech R2",
|
||||||
|
"standard": "Yes",
|
||||||
|
"system_page": 0,
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "All"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"content": "",
|
||||||
|
"script": null,
|
||||||
|
"style": null
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import frappe
|
||||||
|
import json
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def save_load_items(load_name, items):
|
||||||
|
items = json.loads(items)
|
||||||
|
load_doc = frappe.get_doc("Load", load_name)
|
||||||
|
for item_data in items:
|
||||||
|
for row in load_doc.material_items:
|
||||||
|
if row.name == item_data["name"]:
|
||||||
|
for field, value in item_data.items():
|
||||||
|
if field != "name":
|
||||||
|
row.set(field, value)
|
||||||
|
break
|
||||||
|
load_doc.save(ignore_permissions=True)
|
||||||
|
frappe.db.commit()
|
||||||
|
return {"status": "ok", "message": "Saved " + str(len(items)) + " items"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* Load Update page CSS */
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<div style="padding:20px;">
|
||||||
|
<h2>Load Update</h2>
|
||||||
|
<p>Use the form below to update load material items.</p>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
frappe.pages["load-update"].on_page_load = function(wrapper) {
|
||||||
|
var page = frappe.ui.make_app_page({
|
||||||
|
parent: wrapper,
|
||||||
|
title: "Load Update",
|
||||||
|
single_column: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var loadName = frappe.utils.get_url_arg("load");
|
||||||
|
if (!loadName) {
|
||||||
|
$(page.body).html("<div style='padding:40px;text-align:center;'><h2>No load specified</h2><p>Use ?load=MMDDYYYY-XXXX</p></div>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadLoad() {
|
||||||
|
frappe.call({
|
||||||
|
method: "frappe.client.get",
|
||||||
|
args: { doctype: "Load", name: loadName },
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) { renderForm(page, r.message); }
|
||||||
|
else { $(page.body).html("<div style='padding:40px;text-align:center;'><h2>Load not found</h2></div>"); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderForm(page, load) {
|
||||||
|
var h = "<div style='padding:20px;'>";
|
||||||
|
h += "<h2>Update Load: " + load.name + "</h2>";
|
||||||
|
h += "<p>Date: " + (load.incoming_date || "N/A") + " | Customer: " + (load.customer_name || load.customer_number || "N/A") + "</p>";
|
||||||
|
h += "<div style='margin-bottom:15px;'>";
|
||||||
|
h += "<button id='lu-save' class='btn btn-primary'>Save Changes</button> ";
|
||||||
|
h += "<button id='lu-print' class='btn btn-default'>Print Worksheet</button> ";
|
||||||
|
h += "<a href='/app/load/" + encodeURIComponent(load.name) + "' class='btn btn-default'>Open Form View</a>";
|
||||||
|
h += "</div>";
|
||||||
|
h += "<table style='width:100%;border-collapse:collapse;font-size:13px;'>";
|
||||||
|
h += "<thead><tr style='background:#37474f;color:white;'>";
|
||||||
|
h += "<th>Material Type</th><th>Count</th><th>Recv Status</th><th>Send To</th>";
|
||||||
|
h += "<th>Recv'd</th><th>HDD Out</th><th>Dis Send To</th>";
|
||||||
|
h += "<th>Sanitized</th><th>Test Status</th><th>Test Send</th>";
|
||||||
|
h += "<th>R2 Sent</th><th>Phys</th><th>HDD Need</th><th>HDD Phys</th><th>HDD Logic</th>";
|
||||||
|
h += "</tr></thead><tbody>";
|
||||||
|
|
||||||
|
if (load.material_items && load.material_items.length > 0) {
|
||||||
|
load.material_items.forEach(function(item, idx) {
|
||||||
|
var prefix = "item_" + idx + "_";
|
||||||
|
h += "<tr style='border-bottom:1px solid #e0e0e0;'>";
|
||||||
|
h += "<td><strong>" + (item.material_type || "") + "</strong></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "total_count' value='" + (item.total_count || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='text' id='" + prefix + "initial_data_status' value='" + (item.initial_data_status || "") + "' style='width:60px;'></td>";
|
||||||
|
h += "<td><input type='text' id='" + prefix + "send_to' value='" + (item.send_to || "") + "' style='width:80px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "devices_received' value='" + (item.devices_received || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "hdd_removed' value='" + (item.hdd_removed || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='text' id='" + prefix + "disassembly_send_to' value='" + (item.disassembly_send_to || "") + "' style='width:80px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "units_sanitized_software' value='" + (item.units_sanitized_software || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='text' id='" + prefix + "test_data_status' value='" + (item.test_data_status || "") + "' style='width:60px;'></td>";
|
||||||
|
h += "<td><input type='text' id='" + prefix + "test_send_to' value='" + (item.test_send_to || "") + "' style='width:80px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "r2_units_sent' value='" + (item.r2_units_sent || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "units_physical_destruction' value='" + (item.units_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "hdd_needs_sanitize' value='" + (item.hdd_needs_sanitize || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "hdd_physical_destruction' value='" + (item.hdd_physical_destruction || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "<td><input type='number' id='" + prefix + "hdd_logical_sanitization' value='" + (item.hdd_logical_sanitization || 0) + "' style='width:50px;'></td>";
|
||||||
|
h += "</tr>";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
h += "</tbody></table></div>";
|
||||||
|
$(page.body).html(h);
|
||||||
|
|
||||||
|
$("#lu-save").on("click", function() {
|
||||||
|
var items = [];
|
||||||
|
load.material_items.forEach(function(item, idx) {
|
||||||
|
var prefix = "#item_" + idx + "_";
|
||||||
|
items.push({
|
||||||
|
name: item.name,
|
||||||
|
total_count: parseInt($(prefix + "total_count").val()) || 0,
|
||||||
|
initial_data_status: $(prefix + "initial_data_status").val(),
|
||||||
|
send_to: $(prefix + "send_to").val(),
|
||||||
|
devices_received: parseInt($(prefix + "devices_received").val()) || 0,
|
||||||
|
hdd_removed: parseInt($(prefix + "hdd_removed").val()) || 0,
|
||||||
|
disassembly_send_to: $(prefix + "disassembly_send_to").val(),
|
||||||
|
units_sanitized_software: parseInt($(prefix + "units_sanitized_software").val()) || 0,
|
||||||
|
test_data_status: $(prefix + "test_data_status").val(),
|
||||||
|
test_send_to: $(prefix + "test_send_to").val(),
|
||||||
|
r2_units_sent: parseInt($(prefix + "r2_units_sent").val()) || 0,
|
||||||
|
units_physical_destruction: parseInt($(prefix + "units_physical_destruction").val()) || 0,
|
||||||
|
hdd_needs_sanitize: parseInt($(prefix + "hdd_needs_sanitize").val()) || 0,
|
||||||
|
hdd_physical_destruction: parseInt($(prefix + "hdd_physical_destruction").val()) || 0,
|
||||||
|
hdd_logical_sanitization: parseInt($(prefix + "hdd_logical_sanitization").val()) || 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
frappe.call({
|
||||||
|
method: "westech_r2.page.load-update.load-update.save_load_items",
|
||||||
|
args: { load_name: loadName, items: JSON.stringify(items) },
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message && r.message.status === "ok") {
|
||||||
|
frappe.show_alert({message: "Saved", indicator: "green"});
|
||||||
|
loadLoad();
|
||||||
|
} else {
|
||||||
|
frappe.show_alert({message: "Save failed", indicator: "red"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#lu-print").on("click", function() {
|
||||||
|
window.print();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadLoad();
|
||||||
|
};
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"doctype": "Page",
|
||||||
|
"name": "load-update",
|
||||||
|
"page_name": "load-update",
|
||||||
|
"title": "Load Update",
|
||||||
|
"page_type": "Web Page",
|
||||||
|
"module": "Westech R2",
|
||||||
|
"standard": "Yes",
|
||||||
|
"system_page": 0,
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "All"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"content": "",
|
||||||
|
"script": null,
|
||||||
|
"style": null
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import frappe
|
||||||
|
import json
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def save_load_items(load_name, items):
|
||||||
|
items = json.loads(items)
|
||||||
|
load_doc = frappe.get_doc("Load", load_name)
|
||||||
|
for item_data in items:
|
||||||
|
for row in load_doc.material_items:
|
||||||
|
if row.name == item_data["name"]:
|
||||||
|
for field, value in item_data.items():
|
||||||
|
if field != "name":
|
||||||
|
row.set(field, value)
|
||||||
|
break
|
||||||
|
load_doc.save(ignore_permissions=True)
|
||||||
|
frappe.db.commit()
|
||||||
|
return {"status": "ok", "message": "Saved " + str(len(items)) + " items"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Pallet List page for Westech R2
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<style>
|
||||||
|
.pallet-list-page { font-family: Helvetica Neue, Arial, sans-serif; }
|
||||||
|
.pallet-list-page h2 { color: #2F5496; margin-bottom: 16px; font-size: 20px; }
|
||||||
|
.pallet-search-box { margin-bottom: 16px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||||
|
.pallet-search-box input, .pallet-search-box select {
|
||||||
|
padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;
|
||||||
|
}
|
||||||
|
.pallet-search-box button {
|
||||||
|
padding: 6px 14px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;
|
||||||
|
font-size: 13px; background: white;
|
||||||
|
}
|
||||||
|
.pallet-search-box button.btn-primary { background: #2F5496; color: white; border-color: #2F5496; }
|
||||||
|
.pallet-search-box button.btn-success { background: #548235; color: white; border-color: #548235; }
|
||||||
|
.pallet-table-container { padding: 0; overflow-x: auto; background: white; border-radius: 4px; }
|
||||||
|
.pallet-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||||
|
.pallet-table th {
|
||||||
|
background: #2F5496; color: white; padding: 8px 10px; text-align: left;
|
||||||
|
cursor: pointer; white-space: nowrap; font-weight: 500;
|
||||||
|
}
|
||||||
|
.pallet-table td { padding: 5px 10px; border-bottom: 1px solid #eee; }
|
||||||
|
.pallet-table tr:hover { background: #f5f5f5; }
|
||||||
|
.pallet-table tr.new-row { background: #FFF9E6 !important; }
|
||||||
|
.pallet-table a { color: #2F5496; text-decoration: none; font-weight: 600; }
|
||||||
|
.status-received { color: #2196F3; font-weight: 600; }
|
||||||
|
.status-sorting { color: #FF9800; font-weight: 600; }
|
||||||
|
.status-processing { color: #9C27B0; font-weight: 600; }
|
||||||
|
.status-complete { color: #4CAF50; font-weight: 600; }
|
||||||
|
.status-shipped { color: #607D8B; font-weight: 600; }
|
||||||
|
.pallet-pagination { margin-top: 12px; display: flex; gap: 5px; align-items: center; }
|
||||||
|
.pallet-pagination button {
|
||||||
|
padding: 5px 12px; border: 1px solid #ddd; background: white; cursor: pointer;
|
||||||
|
border-radius: 4px; font-size: 13px; color: #2F5496;
|
||||||
|
}
|
||||||
|
.pallet-pagination button.active { background: #2F5496; color: white; border-color: #2F5496; }
|
||||||
|
.pallet-pagination button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||||
|
.pallet-count { margin-left: auto; color: #666; font-size: 13px; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="pallet-list-page">
|
||||||
|
<h2>📦 Pallet List</h2>
|
||||||
|
|
||||||
|
<div class="pallet-search-box">
|
||||||
|
<input type="text" id="pallet-search" placeholder="Search pallet #..." style="max-width:200px;">
|
||||||
|
<select id="status-filter">
|
||||||
|
<option value="">All Statuses</option>
|
||||||
|
<option value="Received">Received</option>
|
||||||
|
<option value="Sorting">Sorting</option>
|
||||||
|
<option value="Processing">Processing</option>
|
||||||
|
<option value="Complete">Complete</option>
|
||||||
|
<option value="Shipped">Shipped</option>
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-default" id="btn-clear">✕ Clear</button>
|
||||||
|
<button class="btn btn-success" id="btn-export">⬇ Export CSV</button>
|
||||||
|
<span class="pallet-count" id="pallet-count"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pallet-table-container">
|
||||||
|
<table class="pallet-table" id="pallet-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-sort="pallet_number">Pallet # ⇅</th>
|
||||||
|
<th data-sort="date_reserved">Date Reserved ⇅</th>
|
||||||
|
<th data-sort="received_date">Rec. Date ⇅</th>
|
||||||
|
<th data-sort="customer_number">Customer # ⇅</th>
|
||||||
|
<th data-sort="inbound_weight">Lbs ⇅</th>
|
||||||
|
<th data-sort="tester">Who ⇅</th>
|
||||||
|
<th data-sort="description">Items Tested ⇅</th>
|
||||||
|
<th data-sort="qty_to_sales">QTY Sale ⇅</th>
|
||||||
|
<th data-sort="weight_to_sales">Lbs Sale ⇅</th>
|
||||||
|
<th data-sort="finish_date">Finish Date ⇅</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="pallet-tbody">
|
||||||
|
<tr><td colspan="12" style="text-align:center;padding:40px;color:#666;">Loading...</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pallet-pagination" id="pallet-pagination"></div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
frappe.pages["pallet-list"].on_page_load = function(wrapper) {
|
||||||
|
var page = frappe.ui.make_app_page({
|
||||||
|
parent: wrapper,
|
||||||
|
title: __("Pallet List"),
|
||||||
|
single_column: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var content = frappe.render_template("pallet-list", {});
|
||||||
|
$(page.body).append(content);
|
||||||
|
|
||||||
|
var currentPage = 1;
|
||||||
|
var pageSize = 100;
|
||||||
|
var currentSort = "pallet_number";
|
||||||
|
var sortDir = "desc";
|
||||||
|
var searchTerm = "";
|
||||||
|
var statusFilter = "";
|
||||||
|
|
||||||
|
function loadPallets() {
|
||||||
|
frappe.call({
|
||||||
|
method: "westech_r2.page.pallet-list.pallet-list.get_pallets",
|
||||||
|
args: {
|
||||||
|
page: currentPage,
|
||||||
|
page_size: pageSize,
|
||||||
|
sort_field: currentSort,
|
||||||
|
sort_dir: sortDir,
|
||||||
|
status_filter: statusFilter,
|
||||||
|
search: searchTerm
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
renderPallets(r.message.pallets, r.message.total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPallets(pallets, total) {
|
||||||
|
var tbody = $("#pallet-tbody");
|
||||||
|
tbody.empty();
|
||||||
|
|
||||||
|
if (!pallets || pallets.length === 0) {
|
||||||
|
tbody.append('<tr><td colspan="13" style="text-align:center;padding:40px;">No pallets found</td></tr>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pallets.forEach(function(p) {
|
||||||
|
var statusClass = "status-" + (p.status || "").toLowerCase().replace(/\s+/g, "-");
|
||||||
|
var link = "/app/pallet/" + encodeURIComponent(p.name);
|
||||||
|
var pn = (p.pallet_number || "").replace(/</g, "<").replace(/>/g, ">");
|
||||||
|
|
||||||
|
var row = '<tr>' +
|
||||||
|
'<td><a href="' + link + '" style="color:#3cc062;text-decoration:none;font-weight:600;">' + pn + '</a></td>' +
|
||||||
|
'<td>' + fmtDate(p.date_reserved) + '</td>' +
|
||||||
|
'<td>' + fmtDate(p.received_date) + '</td>' +
|
||||||
|
'<td>' + (p.customer_number || "") + '</td>' +
|
||||||
|
'<td>' + (p.company_name || "") + '</td>' +
|
||||||
|
'<td>' + (p.inbound_weight || "") + '</td>' +
|
||||||
|
'<td>' + (p.tester || "") + '</td>' +
|
||||||
|
'<td>' + (p.description || "") + '</td>' +
|
||||||
|
'<td>' + (p.qty_to_sales || "") + '</td>' +
|
||||||
|
'<td>' + (p.weight_to_sales || "") + '</td>' +
|
||||||
|
'<td>' + fmtDate(p.finish_date) + '</td>' +
|
||||||
|
'<td class="' + statusClass + '">' + (p.status || "") + '</td>' +
|
||||||
|
'<td>' + (p.notes || "").substring(0, 50) + '</td>' +
|
||||||
|
'</tr>';
|
||||||
|
tbody.append(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
renderPagination(total);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPagination(total) {
|
||||||
|
var totalPages = Math.ceil(total / pageSize);
|
||||||
|
var pagination = $("#pallet-pagination");
|
||||||
|
pagination.empty();
|
||||||
|
|
||||||
|
if (totalPages <= 1) return;
|
||||||
|
|
||||||
|
var prevBtn = $('<button>« Prev</button>').attr("disabled", currentPage === 1)
|
||||||
|
.css({padding: "5px 12px", border: "1px solid #ddd", background: "white", cursor: "pointer", marginRight: "5px"})
|
||||||
|
.on("click", function() {
|
||||||
|
if (currentPage > 1) { currentPage--; loadPallets(); }
|
||||||
|
});
|
||||||
|
pagination.append(prevBtn);
|
||||||
|
|
||||||
|
var startPage = Math.max(1, currentPage - 2);
|
||||||
|
var endPage = Math.min(totalPages, startPage + 4);
|
||||||
|
|
||||||
|
for (var i = startPage; i <= endPage; i++) {
|
||||||
|
var btn = $('<button>' + i + '</button>')
|
||||||
|
.css({padding: "5px 12px", border: "1px solid #ddd", background: i === currentPage ? "#3cc062" : "white",
|
||||||
|
color: i === currentPage ? "white" : "#333", cursor: "pointer", marginRight: "5px"})
|
||||||
|
.on("click", function(page) {
|
||||||
|
return function() { currentPage = page; loadPallets(); };
|
||||||
|
}(i));
|
||||||
|
pagination.append(btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextBtn = $('<button>Next »</button>').attr("disabled", currentPage === totalPages)
|
||||||
|
.css({padding: "5px 12px", border: "1px solid #ddd", background: "white", cursor: "pointer", marginRight: "5px"})
|
||||||
|
.on("click", function() {
|
||||||
|
if (currentPage < totalPages) { currentPage++; loadPallets(); }
|
||||||
|
});
|
||||||
|
pagination.append(nextBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fmtDate(v) {
|
||||||
|
if (!v) return "";
|
||||||
|
var s = String(v);
|
||||||
|
if (s.indexOf("T") > -1) s = s.split("T")[0];
|
||||||
|
if (s.indexOf(" ") > -1) s = s.split(" ")[0];
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#pallet-search").on("input", function() {
|
||||||
|
searchTerm = $(this).val();
|
||||||
|
currentPage = 1;
|
||||||
|
loadPallets();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#status-filter").on("change", function() {
|
||||||
|
statusFilter = $(this).val();
|
||||||
|
currentPage = 1;
|
||||||
|
loadPallets();
|
||||||
|
});
|
||||||
|
|
||||||
|
loadPallets();
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
content: null,
|
||||||
|
creation: 2026-05-19 13:00:00.000000,
|
||||||
|
docstatus: 0,
|
||||||
|
doctype: Page,
|
||||||
|
idx: 0,
|
||||||
|
modified: 2026-05-19 13:00:00.000000,
|
||||||
|
modified_by: Administrator,
|
||||||
|
module: Westech R2,
|
||||||
|
name: pallet-list,
|
||||||
|
owner: Administrator,
|
||||||
|
page_name: pallet-list,
|
||||||
|
roles: [],
|
||||||
|
script: null,
|
||||||
|
standard: Yes,
|
||||||
|
style: null,
|
||||||
|
system_page: 0,
|
||||||
|
title: Pallet List
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_pallets(page=1, page_size=100, sort_field="pallet_number", sort_dir="desc", status_filter="", search=""):
|
||||||
|
page = int(page)
|
||||||
|
page_size = int(page_size)
|
||||||
|
offset = (page - 1) * page_size
|
||||||
|
|
||||||
|
conditions = [
|
||||||
|
"pallet_number IS NOT NULL",
|
||||||
|
"pallet_number != ''",
|
||||||
|
"date_reserved IS NOT NULL",
|
||||||
|
"customer_number IS NOT NULL",
|
||||||
|
"customer_number != ''"
|
||||||
|
]
|
||||||
|
|
||||||
|
junk = ["", "0", "0000", "N/A", "TBD", "null", "999990", "999995"]
|
||||||
|
junk_list = "', '".join(junk)
|
||||||
|
conditions.append("pallet_number NOT IN ('" + junk_list + "')")
|
||||||
|
conditions.append("pallet_number NOT LIKE '999%'")
|
||||||
|
conditions.append("pallet_number REGEXP '^[0-9]'")
|
||||||
|
|
||||||
|
if status_filter:
|
||||||
|
conditions.append("status = '" + frappe.db.escape(status_filter) + "'")
|
||||||
|
if search:
|
||||||
|
conditions.append("pallet_number LIKE '%" + frappe.db.escape(search) + "%'")
|
||||||
|
|
||||||
|
where_clause = " AND ".join(conditions)
|
||||||
|
|
||||||
|
total = frappe.db.sql("SELECT COUNT(*) FROM tabPallet WHERE " + where_clause)[0][0]
|
||||||
|
|
||||||
|
pallets = frappe.db.sql("SELECT name, pallet_number, date_reserved, received_date, customer_number, company_name, inbound_weight, tester, description, qty_to_sales, weight_to_sales, finish_date, notes, status FROM tabPallet WHERE " + where_clause + " ORDER BY CAST(pallet_number AS UNSIGNED) " + sort_dir + " LIMIT " + str(page_size) + " OFFSET " + str(offset), as_dict=True)
|
||||||
|
|
||||||
|
empty_pallet = frappe.db.sql("SELECT name, pallet_number, date_reserved, received_date, customer_number, company_name, inbound_weight, tester, description, qty_to_sales, weight_to_sales, finish_date, notes, status FROM tabPallet WHERE date_reserved IS NULL AND (customer_number IS NULL OR customer_number = '') AND pallet_number NOT IN ('" + junk_list + "') AND pallet_number NOT LIKE '999%' AND pallet_number REGEXP '^[0-9]' ORDER BY CAST(pallet_number AS UNSIGNED) DESC LIMIT 1", as_dict=True)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
if empty_pallet:
|
||||||
|
empty_pallet[0]["_is_new"] = True
|
||||||
|
result.append(empty_pallet[0])
|
||||||
|
result.extend(pallets)
|
||||||
|
|
||||||
|
return {"pallets": result, "total": total, "page": page, "page_size": page_size}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def update_pallet(docname, field, value):
|
||||||
|
pallet = frappe.get_doc("Pallet", docname)
|
||||||
|
pallet.set(field, value)
|
||||||
|
pallet.save(ignore_permissions=True)
|
||||||
|
frappe.db.commit()
|
||||||
|
return {"status": "ok", "message": "Updated " + field}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_pallet(data):
|
||||||
|
data = frappe.parse_json(data)
|
||||||
|
pallet = frappe.new_doc("Pallet")
|
||||||
|
pallet.pallet_number = data.get("pallet_number")
|
||||||
|
pallet.status = data.get("status", "Received")
|
||||||
|
pallet.date_reserved = data.get("date_reserved")
|
||||||
|
for field, value in data.items():
|
||||||
|
if field not in ["pallet_number", "status", "date_reserved"] and value:
|
||||||
|
pallet.set(field, value)
|
||||||
|
pallet.insert(ignore_permissions=True)
|
||||||
|
frappe.db.commit()
|
||||||
|
return {"status": "ok", "name": pallet.name}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<style>
|
||||||
|
.pallet-list-page { font-family: Helvetica Neue, Arial, sans-serif; }
|
||||||
|
.pallet-list-page h2 { color: #2F5496; margin-bottom: 16px; font-size: 20px; }
|
||||||
|
.pallet-search-box { margin-bottom: 16px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||||
|
.pallet-search-box input, .pallet-search-box select {
|
||||||
|
padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;
|
||||||
|
}
|
||||||
|
.pallet-search-box button {
|
||||||
|
padding: 6px 14px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;
|
||||||
|
font-size: 13px; background: white;
|
||||||
|
}
|
||||||
|
.pallet-search-box button.btn-primary { background: #2F5496; color: white; border-color: #2F5496; }
|
||||||
|
.pallet-search-box button.btn-success { background: #548235; color: white; border-color: #548235; }
|
||||||
|
.pallet-table-container { padding: 0; overflow-x: auto; background: white; border-radius: 4px; }
|
||||||
|
.pallet-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||||
|
.pallet-table th {
|
||||||
|
background: #2F5496; color: white; padding: 8px 10px; text-align: left;
|
||||||
|
cursor: pointer; white-space: nowrap; font-weight: 500;
|
||||||
|
}
|
||||||
|
.pallet-table td { padding: 5px 10px; border-bottom: 1px solid #eee; }
|
||||||
|
.pallet-table tr:hover { background: #f5f5f5; }
|
||||||
|
.pallet-table tr.new-row { background: #FFF9E6 !important; }
|
||||||
|
.pallet-table a { color: #2F5496; text-decoration: none; font-weight: 600; }
|
||||||
|
.status-received { color: #2196F3; font-weight: 600; }
|
||||||
|
.status-sorting { color: #FF9800; font-weight: 600; }
|
||||||
|
.status-processing { color: #9C27B0; font-weight: 600; }
|
||||||
|
.status-complete { color: #4CAF50; font-weight: 600; }
|
||||||
|
.status-shipped { color: #607D8B; font-weight: 600; }
|
||||||
|
.pallet-pagination { margin-top: 12px; display: flex; gap: 5px; align-items: center; }
|
||||||
|
.pallet-pagination button {
|
||||||
|
padding: 5px 12px; border: 1px solid #ddd; background: white; cursor: pointer;
|
||||||
|
border-radius: 4px; font-size: 13px; color: #2F5496;
|
||||||
|
}
|
||||||
|
.pallet-pagination button.active { background: #2F5496; color: white; border-color: #2F5496; }
|
||||||
|
.pallet-pagination button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||||
|
.pallet-count { margin-left: auto; color: #666; font-size: 13px; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="pallet-list-page">
|
||||||
|
<h2>📦 Pallet List</h2>
|
||||||
|
|
||||||
|
<div class="pallet-search-box">
|
||||||
|
<input type="text" id="pallet-search" placeholder="Search pallet #..." style="max-width:200px;">
|
||||||
|
<select id="status-filter">
|
||||||
|
<option value="">All Statuses</option>
|
||||||
|
<option value="Received">Received</option>
|
||||||
|
<option value="Sorting">Sorting</option>
|
||||||
|
<option value="Processing">Processing</option>
|
||||||
|
<option value="Complete">Complete</option>
|
||||||
|
<option value="Shipped">Shipped</option>
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-default" id="btn-clear">✕ Clear</button>
|
||||||
|
<button class="btn btn-success" id="btn-export">⬇ Export CSV</button>
|
||||||
|
<span class="pallet-count" id="pallet-count"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pallet-table-container">
|
||||||
|
<table class="pallet-table" id="pallet-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-sort="pallet_number">Pallet # ⇅</th>
|
||||||
|
<th data-sort="date_reserved">Date Reserved ⇅</th>
|
||||||
|
<th data-sort="received_date">Rec. Date ⇅</th>
|
||||||
|
<th data-sort="customer_number">Customer # ⇅</th>
|
||||||
|
<th data-sort="inbound_weight">Lbs ⇅</th>
|
||||||
|
<th data-sort="tester">Who ⇅</th>
|
||||||
|
<th data-sort="description">Items Tested ⇅</th>
|
||||||
|
<th data-sort="qty_to_sales">QTY Sale ⇅</th>
|
||||||
|
<th data-sort="weight_to_sales">Lbs Sale ⇅</th>
|
||||||
|
<th data-sort="finish_date">Finish Date ⇅</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="pallet-tbody">
|
||||||
|
<tr><td colspan="12" style="text-align:center;padding:40px;color:#666;">Loading...</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pallet-pagination" id="pallet-pagination"></div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
frappe.pages["pallet-list"].on_page_load = function(wrapper) {
|
||||||
|
var page = frappe.ui.make_app_page({
|
||||||
|
parent: wrapper,
|
||||||
|
title: __("Pallet List"),
|
||||||
|
single_column: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var content = frappe.render_template("pallet-list", {});
|
||||||
|
$(page.body).append(content);
|
||||||
|
|
||||||
|
var currentPage = 1;
|
||||||
|
var pageSize = 100;
|
||||||
|
var currentSort = "pallet_number";
|
||||||
|
var sortDir = "desc";
|
||||||
|
var searchTerm = "";
|
||||||
|
var statusFilter = "";
|
||||||
|
|
||||||
|
function loadPallets() {
|
||||||
|
frappe.call({
|
||||||
|
method: "westech_r2.page.pallet-list.pallet-list.get_pallets",
|
||||||
|
args: {
|
||||||
|
page: currentPage,
|
||||||
|
page_size: pageSize,
|
||||||
|
sort_field: currentSort,
|
||||||
|
sort_dir: sortDir,
|
||||||
|
status_filter: statusFilter,
|
||||||
|
search: searchTerm
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
renderPallets(r.message.pallets, r.message.total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPallets(pallets, total) {
|
||||||
|
var tbody = $("#pallet-tbody");
|
||||||
|
tbody.empty();
|
||||||
|
|
||||||
|
if (!pallets || pallets.length === 0) {
|
||||||
|
tbody.append('<tr><td colspan="13" style="text-align:center;padding:40px;">No pallets found</td></tr>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pallets.forEach(function(p) {
|
||||||
|
var statusClass = "status-" + (p.status || "").toLowerCase().replace(/\s+/g, "-");
|
||||||
|
var link = "/app/pallet/" + encodeURIComponent(p.name);
|
||||||
|
var pn = (p.pallet_number || "").replace(/</g, "<").replace(/>/g, ">");
|
||||||
|
|
||||||
|
var row = '<tr>' +
|
||||||
|
'<td><a href="' + link + '" style="color:#3cc062;text-decoration:none;font-weight:600;">' + pn + '</a></td>' +
|
||||||
|
'<td>' + fmtDate(p.date_reserved) + '</td>' +
|
||||||
|
'<td>' + fmtDate(p.received_date) + '</td>' +
|
||||||
|
'<td>' + (p.customer_number || "") + '</td>' +
|
||||||
|
'<td>' + (p.company_name || "") + '</td>' +
|
||||||
|
'<td>' + (p.inbound_weight || "") + '</td>' +
|
||||||
|
'<td>' + (p.tester || "") + '</td>' +
|
||||||
|
'<td>' + (p.description || "") + '</td>' +
|
||||||
|
'<td>' + (p.qty_to_sales || "") + '</td>' +
|
||||||
|
'<td>' + (p.weight_to_sales || "") + '</td>' +
|
||||||
|
'<td>' + fmtDate(p.finish_date) + '</td>' +
|
||||||
|
'<td class="' + statusClass + '">' + (p.status || "") + '</td>' +
|
||||||
|
'<td>' + (p.notes || "").substring(0, 50) + '</td>' +
|
||||||
|
'</tr>';
|
||||||
|
tbody.append(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
renderPagination(total);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPagination(total) {
|
||||||
|
var totalPages = Math.ceil(total / pageSize);
|
||||||
|
var pagination = $("#pallet-pagination");
|
||||||
|
pagination.empty();
|
||||||
|
|
||||||
|
if (totalPages <= 1) return;
|
||||||
|
|
||||||
|
var prevBtn = $('<button>« Prev</button>').attr("disabled", currentPage === 1)
|
||||||
|
.css({padding: "5px 12px", border: "1px solid #ddd", background: "white", cursor: "pointer", marginRight: "5px"})
|
||||||
|
.on("click", function() {
|
||||||
|
if (currentPage > 1) { currentPage--; loadPallets(); }
|
||||||
|
});
|
||||||
|
pagination.append(prevBtn);
|
||||||
|
|
||||||
|
var startPage = Math.max(1, currentPage - 2);
|
||||||
|
var endPage = Math.min(totalPages, startPage + 4);
|
||||||
|
|
||||||
|
for (var i = startPage; i <= endPage; i++) {
|
||||||
|
var btn = $('<button>' + i + '</button>')
|
||||||
|
.css({padding: "5px 12px", border: "1px solid #ddd", background: i === currentPage ? "#3cc062" : "white",
|
||||||
|
color: i === currentPage ? "white" : "#333", cursor: "pointer", marginRight: "5px"})
|
||||||
|
.on("click", function(page) {
|
||||||
|
return function() { currentPage = page; loadPallets(); };
|
||||||
|
}(i));
|
||||||
|
pagination.append(btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextBtn = $('<button>Next »</button>').attr("disabled", currentPage === totalPages)
|
||||||
|
.css({padding: "5px 12px", border: "1px solid #ddd", background: "white", cursor: "pointer", marginRight: "5px"})
|
||||||
|
.on("click", function() {
|
||||||
|
if (currentPage < totalPages) { currentPage++; loadPallets(); }
|
||||||
|
});
|
||||||
|
pagination.append(nextBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fmtDate(v) {
|
||||||
|
if (!v) return "";
|
||||||
|
var s = String(v);
|
||||||
|
if (s.indexOf("T") > -1) s = s.split("T")[0];
|
||||||
|
if (s.indexOf(" ") > -1) s = s.split(" ")[0];
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#pallet-search").on("input", function() {
|
||||||
|
searchTerm = $(this).val();
|
||||||
|
currentPage = 1;
|
||||||
|
loadPallets();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#status-filter").on("change", function() {
|
||||||
|
statusFilter = $(this).val();
|
||||||
|
currentPage = 1;
|
||||||
|
loadPallets();
|
||||||
|
});
|
||||||
|
|
||||||
|
loadPallets();
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
content: null,
|
||||||
|
creation: 2026-05-19 13:00:00.000000,
|
||||||
|
docstatus: 0,
|
||||||
|
doctype: Page,
|
||||||
|
idx: 0,
|
||||||
|
modified: 2026-05-19 13:00:00.000000,
|
||||||
|
modified_by: Administrator,
|
||||||
|
module: Westech R2,
|
||||||
|
name: pallet-list,
|
||||||
|
owner: Administrator,
|
||||||
|
page_name: pallet-list,
|
||||||
|
roles: [],
|
||||||
|
script: null,
|
||||||
|
standard: Yes,
|
||||||
|
style: null,
|
||||||
|
system_page: 0,
|
||||||
|
title: Pallet List
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_pallets(page=1, page_size=100, sort_field="pallet_number", sort_dir="desc", status_filter="", search=""):
|
||||||
|
page = int(page)
|
||||||
|
page_size = int(page_size)
|
||||||
|
offset = (page - 1) * page_size
|
||||||
|
|
||||||
|
conditions = [
|
||||||
|
"pallet_number IS NOT NULL",
|
||||||
|
"pallet_number != ''",
|
||||||
|
"date_reserved IS NOT NULL",
|
||||||
|
"customer_number IS NOT NULL",
|
||||||
|
"customer_number != ''"
|
||||||
|
]
|
||||||
|
|
||||||
|
junk = ["", "0", "0000", "N/A", "TBD", "null", "999990", "999995"]
|
||||||
|
junk_list = "', '".join(junk)
|
||||||
|
conditions.append("pallet_number NOT IN ('" + junk_list + "')")
|
||||||
|
conditions.append("pallet_number NOT LIKE '999%'")
|
||||||
|
conditions.append("pallet_number REGEXP '^[0-9]'")
|
||||||
|
|
||||||
|
if status_filter:
|
||||||
|
conditions.append("status = '" + frappe.db.escape(status_filter) + "'")
|
||||||
|
if search:
|
||||||
|
conditions.append("pallet_number LIKE '%" + frappe.db.escape(search) + "%'")
|
||||||
|
|
||||||
|
where_clause = " AND ".join(conditions)
|
||||||
|
|
||||||
|
total = frappe.db.sql("SELECT COUNT(*) FROM tabPallet WHERE " + where_clause)[0][0]
|
||||||
|
|
||||||
|
pallets = frappe.db.sql("SELECT name, pallet_number, date_reserved, received_date, customer_number, company_name, inbound_weight, tester, description, qty_to_sales, weight_to_sales, finish_date, notes, status FROM tabPallet WHERE " + where_clause + " ORDER BY CAST(pallet_number AS UNSIGNED) " + sort_dir + " LIMIT " + str(page_size) + " OFFSET " + str(offset), as_dict=True)
|
||||||
|
|
||||||
|
empty_pallet = frappe.db.sql("SELECT name, pallet_number, date_reserved, received_date, customer_number, company_name, inbound_weight, tester, description, qty_to_sales, weight_to_sales, finish_date, notes, status FROM tabPallet WHERE date_reserved IS NULL AND (customer_number IS NULL OR customer_number = '') AND pallet_number NOT IN ('" + junk_list + "') AND pallet_number NOT LIKE '999%' AND pallet_number REGEXP '^[0-9]' ORDER BY CAST(pallet_number AS UNSIGNED) DESC LIMIT 1", as_dict=True)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
if empty_pallet:
|
||||||
|
empty_pallet[0]["_is_new"] = True
|
||||||
|
result.append(empty_pallet[0])
|
||||||
|
result.extend(pallets)
|
||||||
|
|
||||||
|
return {"pallets": result, "total": total, "page": page, "page_size": page_size}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def update_pallet(docname, field, value):
|
||||||
|
pallet = frappe.get_doc("Pallet", docname)
|
||||||
|
pallet.set(field, value)
|
||||||
|
pallet.save(ignore_permissions=True)
|
||||||
|
frappe.db.commit()
|
||||||
|
return {"status": "ok", "message": "Updated " + field}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_pallet(data):
|
||||||
|
data = frappe.parse_json(data)
|
||||||
|
pallet = frappe.new_doc("Pallet")
|
||||||
|
pallet.pallet_number = data.get("pallet_number")
|
||||||
|
pallet.status = data.get("status", "Received")
|
||||||
|
pallet.date_reserved = data.get("date_reserved")
|
||||||
|
for field, value in data.items():
|
||||||
|
if field not in ["pallet_number", "status", "date_reserved"] and value:
|
||||||
|
pallet.set(field, value)
|
||||||
|
pallet.insert(ignore_permissions=True)
|
||||||
|
frappe.db.commit()
|
||||||
|
return {"status": "ok", "name": pallet.name}
|
||||||
@@ -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,4 @@
|
|||||||
|
<div style="padding:20px;">
|
||||||
|
<h2>Route Planner</h2>
|
||||||
|
<p>Optimize pickup routes for scheduled pickups.</p>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
frappe.pages["route-planner"].on_page_load = function(wrapper) {
|
||||||
|
var page = frappe.ui.make_app_page({
|
||||||
|
parent: wrapper,
|
||||||
|
title: "Route Planner",
|
||||||
|
single_column: true
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: "westech_r2.api.optimize_routes.get_scheduled_pickups",
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
renderRoutePlanner(page, r.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderRoutePlanner(page, data) {
|
||||||
|
var h = "<div style='padding:20px;'>";
|
||||||
|
h += "<h2>Route Planner</h2>";
|
||||||
|
h += "<p>Schedule and optimize pickup routes.</p>";
|
||||||
|
h += "<div id='route-map' style='width:100%;height:400px;background:#f0f0f0;margin:20px 0;'></div>";
|
||||||
|
h += "<button class='btn btn-primary' id='optimize-btn'>Optimize Routes</button>";
|
||||||
|
h += "<div id='routes-container' style='margin-top:20px;'></div>";
|
||||||
|
h += "</div>";
|
||||||
|
$(page.body).html(h);
|
||||||
|
|
||||||
|
$("#optimize-btn").on("click", function() {
|
||||||
|
frappe.call({
|
||||||
|
method: "westech_r2.api.optimize_routes.optimize_routes",
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
frappe.show_alert({message: "Routes optimized", indicator: "green"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"doctype": "Page",
|
||||||
|
"name": "route-planner",
|
||||||
|
"page_name": "route-planner",
|
||||||
|
"title": "Route Planner",
|
||||||
|
"module": "Westech R2",
|
||||||
|
"standard": "Yes",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "All"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"script": "frappe.pages[\"route-planner\"].on_page_load = function(wrapper) {\n var page = frappe.ui.make_app_page({\n parent: wrapper,\n title: \"Route Planner\",\n single_column: true\n });\n \n frappe.call({\n method: \"westech_r2.api.optimize_routes.get_scheduled_pickups\",\n callback: function(r) {\n if (r.message) {\n renderRoutePlanner(page, r.message);\n }\n }\n });\n \n function renderRoutePlanner(page, data) {\n var h = \"<div style='padding:20px;'>\";\n h += \"<h2>Route Planner</h2>\";\n h += \"<p>Schedule and optimize pickup routes.</p>\";\n h += \"<div id='route-map' style='width:100%;height:400px;background:#f0f0f0;margin:20px 0;'></div>\";\n h += \"<button class='btn btn-primary' id='optimize-btn'>Optimize Routes</button>\";\n h += \"<div id='routes-container' style='margin-top:20px;'></div>\";\n h += \"</div>\";\n $(page.body).html(h);\n \n $(\"#optimize-btn\").on(\"click\", function() {\n frappe.call({\n method: \"westech_r2.api.optimize_routes.optimize_routes\",\n callback: function(r) {\n if (r.message) {\n frappe.show_alert({message: \"Routes optimized\", indicator: \"green\"});\n }\n }\n });\n });\n }\n};\n"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<div style="padding:20px;">
|
||||||
|
<h2>Route Planner</h2>
|
||||||
|
<p>Optimize pickup routes for scheduled pickups.</p>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
frappe.pages["route-planner"].on_page_load = function(wrapper) {
|
||||||
|
var page = frappe.ui.make_app_page({
|
||||||
|
parent: wrapper,
|
||||||
|
title: "Route Planner",
|
||||||
|
single_column: true
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: "westech_r2.api.optimize_routes.get_scheduled_pickups",
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
renderRoutePlanner(page, r.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderRoutePlanner(page, data) {
|
||||||
|
var h = "<div style='padding:20px;'>";
|
||||||
|
h += "<h2>Route Planner</h2>";
|
||||||
|
h += "<p>Schedule and optimize pickup routes.</p>";
|
||||||
|
h += "<div id='route-map' style='width:100%;height:400px;background:#f0f0f0;margin:20px 0;'></div>";
|
||||||
|
h += "<button class='btn btn-primary' id='optimize-btn'>Optimize Routes</button>";
|
||||||
|
h += "<div id='routes-container' style='margin-top:20px;'></div>";
|
||||||
|
h += "</div>";
|
||||||
|
$(page.body).html(h);
|
||||||
|
|
||||||
|
$("#optimize-btn").on("click", function() {
|
||||||
|
frappe.call({
|
||||||
|
method: "westech_r2.api.optimize_routes.optimize_routes",
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
frappe.show_alert({message: "Routes optimized", indicator: "green"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"doctype": "Page",
|
||||||
|
"name": "route-planner",
|
||||||
|
"page_name": "route-planner",
|
||||||
|
"title": "Route Planner",
|
||||||
|
"module": "Westech R2",
|
||||||
|
"standard": "Yes",
|
||||||
|
"roles": [{"role": "All"}]
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user