Compare commits
17 Commits
17584d0002
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 739415cf9d | |||
| d087fc0f22 | |||
| 6fe6d61779 | |||
| d4ed4b1d89 | |||
| 0997de940e | |||
| 313da27e56 | |||
| be06bca0cf | |||
| 3c11969c89 | |||
| a1704dc1bb | |||
| 375bf9f2b5 | |||
| 2fe1d76071 | |||
| 8ade73a877 | |||
| ee57b903d6 | |||
| 8f8cf5d78d | |||
| 7d8d1b1376 | |||
| f48e93f96a | |||
| 8be0cf57cf |
@@ -1 +1,3 @@
|
|||||||
*.pyc\n__pycache__/\n*.egg-info/
|
*.pyc\n__pycache__/\n*.egg-info/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Import print format HTML templates into ERPNext as Print Format records."""
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import os
|
||||||
|
|
||||||
|
TEMPLATE_DIR = "/home/frappe/erpnext-bench/apps/westech_r2/westech_r2/templates/print_format"
|
||||||
|
|
||||||
|
PRINT_FORMATS = [
|
||||||
|
{
|
||||||
|
"name": "Green Sheet",
|
||||||
|
"doc_type": "Pallet",
|
||||||
|
"file": "green-sheet.html",
|
||||||
|
"description": "R2 Data-Bearing Equipment Intake Sheet",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Purple Sheet",
|
||||||
|
"doc_type": "Load",
|
||||||
|
"file": "purple-sheet.html",
|
||||||
|
"description": "Load Tracking Worksheet",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pallet Label 4x6",
|
||||||
|
"doc_type": "Pallet",
|
||||||
|
"file": "pallet-label-4x6.html",
|
||||||
|
"description": "4x6 Pallet Shipping Label",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "R2 Special Handling Log",
|
||||||
|
"doc_type": "Pallet",
|
||||||
|
"file": "r2-special-handling-log.html",
|
||||||
|
"description": "R2 Special Handling Chain of Custody Log",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "R2 UW Label",
|
||||||
|
"doc_type": "Pallet",
|
||||||
|
"file": "r2-uw-label.html",
|
||||||
|
"description": "R2 Unwanted/Unknown Material Label",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Special Handling Log",
|
||||||
|
"doc_type": "Load",
|
||||||
|
"file": "special-handling-log.html",
|
||||||
|
"description": "Special Handling Chain of Custody Log",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def import_print_formats():
|
||||||
|
frappe.init(site="erpnext.local")
|
||||||
|
frappe.connect()
|
||||||
|
|
||||||
|
for pf in PRINT_FORMATS:
|
||||||
|
file_path = os.path.join(TEMPLATE_DIR, pf["file"])
|
||||||
|
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
print(f"⚠️ File not found: {file_path}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(file_path, "r") as f:
|
||||||
|
html_content = f.read()
|
||||||
|
|
||||||
|
# Check if Print Format already exists
|
||||||
|
existing = frappe.get_doc("Print Format", pf["name"]) if frappe.db.exists("Print Format", pf["name"]) else None
|
||||||
|
|
||||||
|
if existing:
|
||||||
|
# Update existing
|
||||||
|
existing.html = html_content
|
||||||
|
existing.description = pf["description"]
|
||||||
|
existing.save()
|
||||||
|
print(f"✓ Updated: {pf['name']}")
|
||||||
|
else:
|
||||||
|
# Create new
|
||||||
|
new_pf = frappe.get_doc({
|
||||||
|
"doctype": "Print Format",
|
||||||
|
"name": pf["name"],
|
||||||
|
"print_format_name": pf["name"],
|
||||||
|
"doc_type": pf["doc_type"],
|
||||||
|
"html": html_content,
|
||||||
|
"description": pf["description"],
|
||||||
|
"standard": "No",
|
||||||
|
"custom_format": 1,
|
||||||
|
})
|
||||||
|
new_pf.insert()
|
||||||
|
print(f"✓ Created: {pf['name']}")
|
||||||
|
|
||||||
|
frappe.db.commit()
|
||||||
|
print("\n✓ All print formats imported successfully")
|
||||||
|
|
||||||
|
frappe.destroy()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import_print_formats()
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Test render pallet label with real data."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, '/home/frappe/erpnext-bench/apps/frappe')
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
frappe.init(site='erpnext.local', sites_path='/home/frappe/erpnext-bench/sites')
|
||||||
|
frappe.connect()
|
||||||
|
frappe.set_user('Administrator')
|
||||||
|
|
||||||
|
# Get a real pallet
|
||||||
|
pallet = frappe.get_doc("Pallet", {"pallet_number": ("is", "set")}, limit=1)
|
||||||
|
if not pallet:
|
||||||
|
print("No pallets found")
|
||||||
|
frappe.destroy()
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
print(f"Testing with Pallet: {pallet.pallet_number} (name: {pallet.name})")
|
||||||
|
|
||||||
|
# Get the print format
|
||||||
|
pf = frappe.get_doc("Print Format", "Pallet Label 4x6")
|
||||||
|
print(f"Print Format found: {pf.name}")
|
||||||
|
print(f" DocType: {pf.doc_type}")
|
||||||
|
print(f" Custom: {pf.custom_format}")
|
||||||
|
print(f" HTML length: {len(pf.html) if pf.html else 0} chars")
|
||||||
|
|
||||||
|
# Try rendering via get_print
|
||||||
|
from frappe import get_print
|
||||||
|
html = get_print("Pallet", pallet.name, print_format="Pallet Label 4x6")
|
||||||
|
|
||||||
|
# Write to file for inspection
|
||||||
|
with open("/tmp/pallet_label_test.html", "w") as f:
|
||||||
|
f.write(html)
|
||||||
|
|
||||||
|
print(f"✓ Rendered to /tmp/pallet_label_test.html")
|
||||||
|
print(f" Output length: {len(html)} chars")
|
||||||
|
|
||||||
|
frappe.destroy()
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Verify print formats were imported."""
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
frappe.init(site="erpnext.local")
|
||||||
|
frappe.connect()
|
||||||
|
|
||||||
|
pfs = frappe.get_all("Print Format",
|
||||||
|
filters={"doc_type": ["in", ["Pallet", "Load"]]},
|
||||||
|
fields=["name", "doc_type", "custom_format"]
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Print Formats for Pallet/Load:")
|
||||||
|
for pf in pfs:
|
||||||
|
print(f" - {pf['name']} ({pf['doc_type']}, custom={pf['custom_format']})")
|
||||||
|
|
||||||
|
frappe.destroy()
|
||||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
from westech_r2.api import sales
|
||||||
|
from westech_r2.api import receiving_api
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
import frappe
|
||||||
|
from reportlab.lib.pagesizes import letter
|
||||||
|
from reportlab.lib.units import inch
|
||||||
|
from reportlab.lib.colors import HexColor, black, white, grey
|
||||||
|
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable, Image
|
||||||
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||||
|
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
|
||||||
|
from reportlab.lib import colors
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
|
||||||
|
DARK_BLUE = HexColor('#2F5496')
|
||||||
|
LIGHT_BLUE = HexColor('#D6E4F0')
|
||||||
|
GRAY = HexColor('#666666')
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def generate_cor(company_name=None, weights=None, received_date=None, red_r2=None, contact_name=None, contact_number=None, address_line=None, pallet_name=None):
|
||||||
|
"""Generate Certificate of Recycling PDF from form data."""
|
||||||
|
|
||||||
|
# Format date
|
||||||
|
date_str = ''
|
||||||
|
if received_date:
|
||||||
|
from frappe.utils import formatdate
|
||||||
|
date_str = formatdate(received_date, 'MMMM d, Y')
|
||||||
|
|
||||||
|
items_recycled = 'e-waste'
|
||||||
|
if red_r2:
|
||||||
|
items_recycled += ' (' + red_r2 + ')'
|
||||||
|
|
||||||
|
output = io.BytesIO()
|
||||||
|
doc = SimpleDocTemplate(
|
||||||
|
output,
|
||||||
|
pagesize=letter,
|
||||||
|
topMargin=0.5 * inch,
|
||||||
|
bottomMargin=0.5 * inch,
|
||||||
|
leftMargin=0.75 * inch,
|
||||||
|
rightMargin=0.75 * inch
|
||||||
|
)
|
||||||
|
|
||||||
|
styles = getSampleStyleSheet()
|
||||||
|
|
||||||
|
# Custom styles matching the Electron app
|
||||||
|
date_style = ParagraphStyle('DateBlock', parent=styles['Normal'], fontSize=14, fontName='Times-Bold', alignment=TA_LEFT)
|
||||||
|
title_style = ParagraphStyle('CertTitle', parent=styles['Title'], fontSize=16, fontName='Times-Bold', textColor=black, spaceAfter=6, alignment=TA_CENTER, letterSpacing=0.05)
|
||||||
|
cert_style = ParagraphStyle('CertBody', parent=styles['Normal'], fontName='Times-Roman', fontSize=12, spaceAfter=12, alignment=TA_JUSTIFY)
|
||||||
|
body_style = ParagraphStyle('BodyText2', parent=styles['Normal'], fontName='Times-Roman', fontSize=12, spaceAfter=10, alignment=TA_JUSTIFY)
|
||||||
|
bullet_style = ParagraphStyle('BulletText', parent=styles['Normal'], fontName='Times-Roman', fontSize=10, spaceAfter=4, leftIndent=24, bulletIndent=12, alignment=TA_JUSTIFY)
|
||||||
|
optin_style = ParagraphStyle('OptIn', parent=styles['Normal'], fontName='Times-Roman', fontSize=12, spaceAfter=10, alignment=TA_JUSTIFY)
|
||||||
|
sig_style = ParagraphStyle('Signature', parent=styles['Normal'], fontName='Times-Bold', fontSize=18, spaceBefore=18)
|
||||||
|
footer_style = ParagraphStyle('Footer', parent=styles['Normal'], fontName='Times-Roman', fontSize=10, textColor=GRAY)
|
||||||
|
|
||||||
|
elements = []
|
||||||
|
|
||||||
|
# Header row: Date | Logo | Title
|
||||||
|
logo_path = os.path.join(frappe.get_app_path('westech_r2'), 'public', 'images', 'cor_logo.png')
|
||||||
|
logo_img = None
|
||||||
|
if os.path.exists(logo_path):
|
||||||
|
logo_img = Image(logo_path, width=2.45 * inch, height=0.8 * inch)
|
||||||
|
|
||||||
|
header_data = [
|
||||||
|
[Paragraph(date_str, date_style), logo_img or Paragraph('', styles['Normal']), Paragraph('CERTIFICATE OF RECYCLING', title_style)]
|
||||||
|
]
|
||||||
|
header_table = Table(header_data, colWidths=[1.8 * inch, 2.45 * inch, 2.75 * inch])
|
||||||
|
header_table.setStyle(TableStyle([
|
||||||
|
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
||||||
|
('ALIGN', (0, 0), (0, 0), 'LEFT'),
|
||||||
|
('ALIGN', (1, 0), (1, 0), 'CENTER'),
|
||||||
|
('ALIGN', (2, 0), (2, 0), 'CENTER'),
|
||||||
|
]))
|
||||||
|
elements.append(header_table)
|
||||||
|
elements.append(Spacer(1, 18))
|
||||||
|
|
||||||
|
# Certification paragraph
|
||||||
|
elements.append(Paragraph(
|
||||||
|
'Full Circle Electronics AZ, LLC (dba Westech Recyclers) certifies that the '
|
||||||
|
'materials submitted for recycling are received and will be properly recycled '
|
||||||
|
'in accordance with all state and federal recycling regulations and in '
|
||||||
|
'accordance with the R2 Standard.',
|
||||||
|
cert_style
|
||||||
|
))
|
||||||
|
|
||||||
|
# Data table
|
||||||
|
data_rows = [
|
||||||
|
['Company:', company_name or 'N/A'],
|
||||||
|
['Weight:', weights or 'N/A'],
|
||||||
|
['Items Recycled:', items_recycled],
|
||||||
|
]
|
||||||
|
if contact_name:
|
||||||
|
data_rows.append(['Contact:', contact_name])
|
||||||
|
if contact_number:
|
||||||
|
data_rows.append(['Phone:', contact_number])
|
||||||
|
if address_line:
|
||||||
|
data_rows.append(['Address:', address_line])
|
||||||
|
|
||||||
|
data_table = Table(data_rows, colWidths=[3.36 * inch, 3.64 * inch])
|
||||||
|
data_table.setStyle(TableStyle([
|
||||||
|
('FONTNAME', (0, 0), (-1, -1), 'Times-Roman'),
|
||||||
|
('FONTSIZE', (0, 0), (-1, -1), 12),
|
||||||
|
('ALIGN', (0, 0), (0, -1), 'RIGHT'),
|
||||||
|
('ALIGN', (1, 0), (1, -1), 'LEFT'),
|
||||||
|
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||||
|
('GRID', (0, 0), (-1, -1), 0.5, HexColor('#bfbfbf')),
|
||||||
|
('TOPPADDING', (0, 0), (-1, -1), 4),
|
||||||
|
('BOTTOMPADDING', (0, 0), (-1, -1), 4),
|
||||||
|
('LEFTPADDING', (0, 0), (-1, -1), 6),
|
||||||
|
('RIGHTPADDING', (0, 0), (-1, -1), 6),
|
||||||
|
]))
|
||||||
|
elements.append(data_table)
|
||||||
|
elements.append(Spacer(1, 12))
|
||||||
|
|
||||||
|
# Body paragraphs
|
||||||
|
elements.append(Paragraph(
|
||||||
|
'Full Circle Electronics AZ, LLC further acknowledges the acceptance and '
|
||||||
|
'recycling of any material potentially containing data. Data containing '
|
||||||
|
'materials are stored in our secured facility ensuring the security of the '
|
||||||
|
'unit(s) prior to data sanitization.',
|
||||||
|
body_style
|
||||||
|
))
|
||||||
|
|
||||||
|
elements.append(Paragraph(
|
||||||
|
'Data containing materials are sanitized in compliance with NIST 800-88 '
|
||||||
|
'guidelines which is set forth by the U.S. government for a robust methodology '
|
||||||
|
'for erasing data from storage media. Depending upon the media received, the '
|
||||||
|
'data destruction methods used are as follows:',
|
||||||
|
body_style
|
||||||
|
))
|
||||||
|
|
||||||
|
# Bullet list
|
||||||
|
bullets = [
|
||||||
|
'Hard disk and solid-state drives will either be logically sanitized using professional software or physically destroyed via shredding or degaussing.',
|
||||||
|
'Media cards and small storage devices will either be degaussed / shredded at our facility or sent straight to a smelter.',
|
||||||
|
'Data tapes or reels will either be degaussed or shredded at a vetted and approved downstream service provider.',
|
||||||
|
'Electronics with embedded storage chips will either be destroyed by physical destruction at our facility or at a vetted and approved downstream service provider.',
|
||||||
|
'Small electronics containing data will either be logically sanitized using the manufacturer\'s application for destroying data or sent to a vetted and approved downstream service provider.',
|
||||||
|
]
|
||||||
|
for b in bullets:
|
||||||
|
elements.append(Paragraph('\u2022 ' + b, bullet_style))
|
||||||
|
|
||||||
|
elements.append(Spacer(1, 6))
|
||||||
|
|
||||||
|
# Opt-in
|
||||||
|
elements.append(Paragraph(
|
||||||
|
'Opt-in option. If you desire to be informed of our data destruction process '
|
||||||
|
'changes or be notified of any unlikely security breaches, please let us know.',
|
||||||
|
optin_style
|
||||||
|
))
|
||||||
|
|
||||||
|
# Signature
|
||||||
|
elements.append(Paragraph('Westech Recyclers', sig_style))
|
||||||
|
|
||||||
|
# Footer
|
||||||
|
elements.append(Spacer(1, 10))
|
||||||
|
elements.append(Paragraph(
|
||||||
|
'220 S 9th St Phoenix, AZ 85034 '
|
||||||
|
'<link href="http://www.westechrecyclers.com" color="#1155cc">www.westechrecyclers.com</link> '
|
||||||
|
'602.256.7626',
|
||||||
|
footer_style
|
||||||
|
))
|
||||||
|
|
||||||
|
doc.build(elements)
|
||||||
|
output.seek(0)
|
||||||
|
|
||||||
|
frappe.response.filename = 'COR_' + (company_name or 'document').replace(' ', '_') + '.pdf'
|
||||||
|
frappe.response.filecontent = output.getvalue()
|
||||||
|
frappe.response.type = 'download'
|
||||||
|
frappe.response.display_content_as = 'attachment'
|
||||||
@@ -107,3 +107,21 @@ def _calculate_slots(gaylord_sizes_text):
|
|||||||
slots = size_map.get(size, 1)
|
slots = size_map.get(size, 1)
|
||||||
total += int(count) * slots
|
total += int(count) * slots
|
||||||
return total or 1
|
return total or 1
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_scheduled_pickups(pickup_date=None):
|
||||||
|
"""Get scheduled pickups for a given date."""
|
||||||
|
if not pickup_date:
|
||||||
|
pickup_date = frappe.utils.today()
|
||||||
|
|
||||||
|
pickups = frappe.get_list("Scheduled Pickup",
|
||||||
|
filters={"pickup_date": pickup_date},
|
||||||
|
fields=["name", "customer_number", "company_name", "estimated_items",
|
||||||
|
"estimated_weight", "gaylord_count", "gaylord_sizes", "slots_needed",
|
||||||
|
"latitude", "longitude", "stop_order", "truck_profile", "status",
|
||||||
|
"contact_name", "contact_phone", "address_line", "city", "state", "zip_code"],
|
||||||
|
order_by="stop_order asc"
|
||||||
|
)
|
||||||
|
|
||||||
|
return pickups
|
||||||
@@ -0,0 +1,499 @@
|
|||||||
|
import json
|
||||||
|
import frappe
|
||||||
|
from frappe.utils import today, getdate, add_days
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_pickups(date=None):
|
||||||
|
"""Fetch scheduled pickups with optional date filter.
|
||||||
|
Returns pickups, calendar (next 30 days), and weekly chart data."""
|
||||||
|
filters = []
|
||||||
|
if date:
|
||||||
|
filters.append(["Scheduled Pickup", "pickup_date", "=", date])
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
"name", "pickup_date", "pickup_type", "status", "truck", "stop_order",
|
||||||
|
"customer_number", "company_name",
|
||||||
|
"contact_name", "contact_phone", "contact_email",
|
||||||
|
"address_line", "city", "state", "zip_code",
|
||||||
|
"latitude", "longitude",
|
||||||
|
"estimated_items", "estimated_weight", "load_contents",
|
||||||
|
"num_labels", "data_status", "red_r2",
|
||||||
|
"notes", "legacy_notes", "needs_aor", "needs_cod",
|
||||||
|
]
|
||||||
|
|
||||||
|
pickups = frappe.get_list("Scheduled Pickup",
|
||||||
|
fields=fields,
|
||||||
|
filters=filters if filters else None,
|
||||||
|
order_by="pickup_date asc, stop_order asc",
|
||||||
|
limit_page_length=500,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build calendar data (next 30 days)
|
||||||
|
from_date = getdate(today())
|
||||||
|
to_date = add_days(from_date, 30)
|
||||||
|
|
||||||
|
all_pickups = frappe.get_list("Scheduled Pickup",
|
||||||
|
fields=["pickup_date"],
|
||||||
|
filters=[["Scheduled Pickup", "pickup_date", ">=", str(from_date)],
|
||||||
|
["Scheduled Pickup", "pickup_date", "<=", str(to_date)]],
|
||||||
|
limit_page_length=500,
|
||||||
|
)
|
||||||
|
|
||||||
|
pickup_counts = {}
|
||||||
|
for p in all_pickups:
|
||||||
|
d = p.get("pickup_date", "")
|
||||||
|
if d:
|
||||||
|
pickup_counts[d] = pickup_counts.get(d, 0) + 1
|
||||||
|
|
||||||
|
calendar = []
|
||||||
|
for i in range(30):
|
||||||
|
d = add_days(from_date, i)
|
||||||
|
ds = str(d)
|
||||||
|
calendar.append({"date": ds, "count": pickup_counts.get(ds, 0)})
|
||||||
|
|
||||||
|
# Build weekly chart data (last 12 weeks)
|
||||||
|
weekly = []
|
||||||
|
for i in range(11, -1, -1):
|
||||||
|
week_start = add_days(from_date, -(from_date.weekday() + 7 * i))
|
||||||
|
week_end = add_days(week_start, 6)
|
||||||
|
count = 0
|
||||||
|
for d_str, c in pickup_counts.items():
|
||||||
|
try:
|
||||||
|
d = getdate(d_str)
|
||||||
|
if week_start <= d <= week_end:
|
||||||
|
count += c
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
weekly.append({"label": week_start.strftime("%m/%d"), "count": count})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"pickups": pickups,
|
||||||
|
"calendar": calendar,
|
||||||
|
"weekly": weekly,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def auto_route(date=None):
|
||||||
|
"""Auto-assign pickups to trucks based on capacity and proximity."""
|
||||||
|
if not date:
|
||||||
|
date = today()
|
||||||
|
|
||||||
|
pickups = frappe.get_list("Scheduled Pickup",
|
||||||
|
filters={"pickup_date": date},
|
||||||
|
fields=["name", "company_name", "estimated_items", "estimated_weight",
|
||||||
|
"latitude", "longitude", "pickup_type"],
|
||||||
|
limit_page_length=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not pickups:
|
||||||
|
return {"success": True, "assigned": 0}
|
||||||
|
|
||||||
|
trucks = ["Truck 1", "Truck 2", "Truck 3"]
|
||||||
|
sorted_p = sorted(pickups, key=lambda p: (float(p.get("latitude") or 0), float(p.get("longitude") or 0)))
|
||||||
|
n = len(sorted_p)
|
||||||
|
assigned = 0
|
||||||
|
|
||||||
|
for i, p in enumerate(sorted_p):
|
||||||
|
if p.get("pickup_type") == "Drop-off":
|
||||||
|
truck = ""
|
||||||
|
else:
|
||||||
|
truck = trucks[i % 3] if n <= 3 else trucks[min(i * 3 // n, 2)]
|
||||||
|
|
||||||
|
doc = frappe.get_doc("Scheduled Pickup", p["name"])
|
||||||
|
doc.truck = truck
|
||||||
|
doc.status = "Routed" if truck else "Scheduled"
|
||||||
|
doc.stop_order = i + 1
|
||||||
|
doc.save()
|
||||||
|
assigned += 1
|
||||||
|
|
||||||
|
frappe.db.commit()
|
||||||
|
return {"success": True, "assigned": assigned}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_checkins():
|
||||||
|
"""Fetch completed check-ins — returns Loads with their Pallets."""
|
||||||
|
loads = frappe.get_list("Load",
|
||||||
|
fields=["name", "load_number", "incoming_date", "customer", "customer_name",
|
||||||
|
"total_devices", "total_weight", "data_status", "red_r2"],
|
||||||
|
order_by="incoming_date desc",
|
||||||
|
limit_page_length=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
for load in loads:
|
||||||
|
pallets = frappe.get_list("Pallet",
|
||||||
|
filters={"load": load.name},
|
||||||
|
fields=["name", "pallet_number", "received_date", "inbound_weight",
|
||||||
|
"total_items", "data_status", "red_r2", "description", "status"],
|
||||||
|
limit_page_length=50,
|
||||||
|
)
|
||||||
|
load["pallets"] = pallets
|
||||||
|
load["pallet_count"] = len(pallets)
|
||||||
|
|
||||||
|
return {"checkins": loads}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def checkin_load(pickup_name, received_date, actual_pallets, total_weight, load_contents, data_status=None, red_r2=None):
|
||||||
|
"""Check in a load: create Load + Pallets, mark pickup Complete."""
|
||||||
|
# Get the pickup
|
||||||
|
pickup = frappe.get_doc("Scheduled Pickup", pickup_name)
|
||||||
|
|
||||||
|
if not actual_pallets or int(actual_pallets) < 1:
|
||||||
|
frappe.throw("Actual pallet count must be at least 1")
|
||||||
|
|
||||||
|
actual_pallets = int(actual_pallets)
|
||||||
|
|
||||||
|
# Resolve customer - customer_number on pickup is a Link to Customer
|
||||||
|
customer_id = pickup.customer_number
|
||||||
|
if not customer_id or not frappe.db.exists("Customer", customer_id):
|
||||||
|
frappe.throw("Customer {} not found. Please verify the customer on the pickup.".format(customer_id))
|
||||||
|
|
||||||
|
# Generate Load name: MMDDYYYY-CustomerNumber format
|
||||||
|
from datetime import datetime
|
||||||
|
try:
|
||||||
|
dt = datetime.strptime(received_date, "%Y-%m-%d")
|
||||||
|
date_part = dt.strftime("%m%d%Y")
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
date_part = received_date.replace("-", "")
|
||||||
|
|
||||||
|
cust_num = customer_id
|
||||||
|
load_name = "{}-{}".format(date_part, cust_num)
|
||||||
|
|
||||||
|
# Make unique if name already exists
|
||||||
|
base_name = load_name
|
||||||
|
counter = 1
|
||||||
|
while frappe.db.exists("Load", load_name):
|
||||||
|
load_name = "{}-{}".format(base_name, counter)
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
# Create Load
|
||||||
|
load = frappe.get_doc({
|
||||||
|
"doctype": "Load",
|
||||||
|
"name": load_name,
|
||||||
|
"load_number": load_name,
|
||||||
|
"incoming_date": received_date,
|
||||||
|
"customer": customer_id,
|
||||||
|
"customer_name": pickup.company_name or "",
|
||||||
|
"customer_number": customer_id,
|
||||||
|
"data_status": data_status or pickup.data_status or "",
|
||||||
|
"red_r2": red_r2 or pickup.red_r2 or "",
|
||||||
|
"total_weight": float(total_weight) if total_weight else 0,
|
||||||
|
"total_devices": actual_pallets,
|
||||||
|
"material_items": [{
|
||||||
|
"material_type": "# Of Pallets",
|
||||||
|
"total_count": actual_pallets,
|
||||||
|
"weight": float(total_weight) if total_weight else 0,
|
||||||
|
"initial_data_status": data_status or pickup.data_status or "D0",
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
load.insert()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
# Create Pallets — autoname=pallet_number, so set name=pallet_number
|
||||||
|
for i in range(actual_pallets):
|
||||||
|
pallet_num = "{}-P{}".format(load_name, i + 1)
|
||||||
|
pallet = frappe.get_doc({
|
||||||
|
"doctype": "Pallet",
|
||||||
|
"name": pallet_num,
|
||||||
|
"pallet_number": pallet_num,
|
||||||
|
"received_date": received_date,
|
||||||
|
"load": load.name,
|
||||||
|
"company_name": pickup.company_name or "",
|
||||||
|
"inbound_weight": str(round(float(total_weight) / actual_pallets, 1)) if total_weight and actual_pallets else "",
|
||||||
|
"description": load_contents or "",
|
||||||
|
"data_status": data_status or pickup.data_status or "",
|
||||||
|
"red_r2": red_r2 or pickup.red_r2 or "",
|
||||||
|
"contact_name": pickup.contact_name or "",
|
||||||
|
"contact_number": pickup.contact_phone or "",
|
||||||
|
"contact_email": pickup.contact_email or "",
|
||||||
|
"address_line": (pickup.address_line or "") + ((", " + pickup.city) if pickup.city else "") + ((", " + pickup.state) if pickup.state else ""),
|
||||||
|
"needs_aor": pickup.needs_aor or 0,
|
||||||
|
"needs_cod": pickup.needs_cod or 0,
|
||||||
|
"notes": pickup.notes or "",
|
||||||
|
"pickup": pickup.pickup_type or "",
|
||||||
|
"status": "Received",
|
||||||
|
})
|
||||||
|
pallet.insert()
|
||||||
|
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
# Update pickup status
|
||||||
|
pickup.status = "Complete"
|
||||||
|
pickup.save()
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"load": load.name,
|
||||||
|
"pallets_created": actual_pallets,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_pickup_details(pickup_name):
|
||||||
|
"""Get full details of a Scheduled Pickup for the check-in form."""
|
||||||
|
pickup = frappe.get_doc("Scheduled Pickup", pickup_name)
|
||||||
|
return {
|
||||||
|
"name": pickup.name,
|
||||||
|
"pickup_date": pickup.pickup_date,
|
||||||
|
"pickup_type": pickup.pickup_type,
|
||||||
|
"customer_number": pickup.customer_number,
|
||||||
|
"company_name": pickup.company_name,
|
||||||
|
"contact_name": pickup.contact_name,
|
||||||
|
"contact_phone": pickup.contact_phone,
|
||||||
|
"contact_email": pickup.contact_email,
|
||||||
|
"address_line": pickup.address_line,
|
||||||
|
"city": pickup.city,
|
||||||
|
"state": pickup.state,
|
||||||
|
"zip_code": pickup.zip_code,
|
||||||
|
"estimated_items": pickup.estimated_items,
|
||||||
|
"estimated_weight": pickup.estimated_weight,
|
||||||
|
"data_status": pickup.data_status,
|
||||||
|
"red_r2": pickup.red_r2,
|
||||||
|
"needs_aor": pickup.needs_aor,
|
||||||
|
"needs_cod": pickup.needs_cod,
|
||||||
|
"notes": pickup.notes,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def cor_report():
|
||||||
|
"""Generate Certificate of Recycling report."""
|
||||||
|
pickups = frappe.get_list("Scheduled Pickup",
|
||||||
|
filters={"status": "Complete"},
|
||||||
|
fields=["name", "pickup_date", "company_name", "customer_number",
|
||||||
|
"estimated_items", "estimated_weight", "load_contents",
|
||||||
|
"data_status", "red_r2", "needs_aor", "needs_cod"],
|
||||||
|
order_by="pickup_date desc",
|
||||||
|
limit_page_length=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
html = "<!DOCTYPE html><html><head><title>CoR Report</title>"
|
||||||
|
html += "<style>body{font-family:Arial,sans-serif;margin:40px;}"
|
||||||
|
html += "table{border-collapse:collapse;width:100%;}"
|
||||||
|
html += "th,td{border:1px solid #ddd;padding:8px;text-align:left;font-size:13px;}"
|
||||||
|
html += "th{background:#2F5496;color:#fff;}h1{color:#2F5496;}</style></head><body>"
|
||||||
|
html += "<h1>Certificate of Recycling (CoR) Report</h1>"
|
||||||
|
html += "<p>Generated: " + frappe.utils.now() + "</p>"
|
||||||
|
html += "<p>Total completed loads: " + str(len(pickups)) + "</p>"
|
||||||
|
|
||||||
|
if pickups:
|
||||||
|
html += "<table><tr><th>Date</th><th>Customer</th><th>Items</th><th>Weight</th>"
|
||||||
|
html += "<th>Contents</th><th>Data Status</th><th>RED/R2</th><th>AoR</th><th>CoD</th></tr>"
|
||||||
|
for p in pickups:
|
||||||
|
html += "<tr><td>" + str(p.get("pickup_date", "")) + "</td>"
|
||||||
|
html += "<td>" + str(p.get("company_name", "")) + "</td>"
|
||||||
|
html += "<td>" + str(p.get("estimated_items", "")) + "</td>"
|
||||||
|
html += "<td>" + str(p.get("estimated_weight", "")) + "</td>"
|
||||||
|
html += "<td>" + str(p.get("load_contents", "")) + "</td>"
|
||||||
|
html += "<td>" + str(p.get("data_status", "")) + "</td>"
|
||||||
|
html += "<td>" + str(p.get("red_r2", "")) + "</td>"
|
||||||
|
html += "<td>" + ("✓" if p.get("needs_aor") else "") + "</td>"
|
||||||
|
html += "<td>" + ("✓" if p.get("needs_cod") else "") + "</td></tr>"
|
||||||
|
html += "</table>"
|
||||||
|
else:
|
||||||
|
html += "<p>No completed loads found.</p>"
|
||||||
|
|
||||||
|
html += "</body></html>"
|
||||||
|
frappe.local.response["type"] = "html"
|
||||||
|
frappe.local.response["page_content"] = html
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def print_route_sheet(date=None):
|
||||||
|
"""Generate printable route sheet."""
|
||||||
|
if not date:
|
||||||
|
date = today()
|
||||||
|
|
||||||
|
filters = {"pickup_date": date} if date else {}
|
||||||
|
pickups = frappe.get_list("Scheduled Pickup",
|
||||||
|
filters=filters,
|
||||||
|
fields=["name", "pickup_date", "pickup_type", "status", "truck", "stop_order",
|
||||||
|
"company_name", "contact_name", "contact_phone", "contact_email",
|
||||||
|
"address_line", "city", "state", "zip_code",
|
||||||
|
"estimated_items", "estimated_weight", "load_contents",
|
||||||
|
"data_status", "red_r2", "needs_aor", "needs_cod", "notes"],
|
||||||
|
order_by="truck asc, stop_order asc",
|
||||||
|
limit_page_length=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
trucks = {}
|
||||||
|
unassigned = []
|
||||||
|
for p in pickups:
|
||||||
|
t = p.get("truck", "")
|
||||||
|
if t and t != "Unassigned":
|
||||||
|
trucks.setdefault(t, []).append(p)
|
||||||
|
else:
|
||||||
|
unassigned.append(p)
|
||||||
|
|
||||||
|
html = "<!DOCTYPE html><html><head><title>Route Sheet</title>"
|
||||||
|
html += "<style>body{font-family:Arial,sans-serif;margin:30px;}"
|
||||||
|
html += "table{border-collapse:collapse;width:100%;margin-bottom:12px;}"
|
||||||
|
html += "th,td{border:1px solid #999;padding:6px 10px;text-align:left;font-size:12px;}"
|
||||||
|
html += "th{background:#2F5496;color:#fff;}h1{color:#2F5496;font-size:20px;}"
|
||||||
|
html += ".truck-header{background:#f0f0f0;padding:8px;font-weight:700;font-size:14px;border:1px solid #ccc;}"
|
||||||
|
html += "@media print{body{margin:10px;}}</style></head><body>"
|
||||||
|
html += "<h1>Route Sheet — " + str(date or "Today") + "</h1>"
|
||||||
|
|
||||||
|
for truck_name, stops in sorted(trucks.items()):
|
||||||
|
html += '<div class="truck-header">🚛 ' + truck_name + " — " + str(len(stops)) + " stops</div>"
|
||||||
|
html += "<table><tr><th>#</th><th>Customer</th><th>Address</th><th>Contact</th>"
|
||||||
|
html += "<th>Items</th><th>Weight</th><th>Data</th><th>RED/R2</th><th>AoR</th><th>CoD</th><th>Notes</th></tr>"
|
||||||
|
for i, s in enumerate(stops, 1):
|
||||||
|
addr = str(s.get("address_line", "")) + ", " + str(s.get("city", "")) + ", " + str(s.get("state", "")) + " " + str(s.get("zip_code", ""))
|
||||||
|
html += "<tr><td>" + str(i) + "</td><td>" + str(s.get("company_name", "")) + "</td>"
|
||||||
|
html += "<td>" + addr + "</td><td>" + str(s.get("contact_name", "")) + "<br>" + str(s.get("contact_phone", "")) + "</td>"
|
||||||
|
html += "<td>" + str(s.get("estimated_items", "")) + "</td><td>" + str(s.get("estimated_weight", "")) + "</td>"
|
||||||
|
html += "<td>" + str(s.get("data_status", "")) + "</td><td>" + str(s.get("red_r2", "")) + "</td>"
|
||||||
|
html += "<td>" + ("✓" if s.get("needs_aor") else "") + "</td>"
|
||||||
|
html += "<td>" + ("✓" if s.get("needs_cod") else "") + "</td>"
|
||||||
|
html += "<td>" + str(s.get("notes", "")) + "</td></tr>"
|
||||||
|
html += "</table>"
|
||||||
|
|
||||||
|
if unassigned:
|
||||||
|
html += "<h2>Unassigned</h2><table><tr><th>Customer</th><th>Address</th><th>Notes</th></tr>"
|
||||||
|
for s in unassigned:
|
||||||
|
html += "<tr><td>" + str(s.get("company_name", "")) + "</td>"
|
||||||
|
html += "<td>" + str(s.get("address_line", "")) + "</td>"
|
||||||
|
html += "<td>" + str(s.get("notes", "")) + "</td></tr>"
|
||||||
|
html += "</table>"
|
||||||
|
|
||||||
|
html += "</body></html>"
|
||||||
|
frappe.local.response["type"] = "html"
|
||||||
|
frappe.local.response["page_content"] = html
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def print_green_sheet(date=None):
|
||||||
|
"""Generate Green Sheet printout for each pallet.
|
||||||
|
Shows customer info, service level banner, driver instructions, RED LINE instructions."""
|
||||||
|
if not date:
|
||||||
|
date = today()
|
||||||
|
|
||||||
|
# Get completed loads for this date
|
||||||
|
loads = frappe.get_list("Load",
|
||||||
|
filters={"incoming_date": date},
|
||||||
|
fields=["name", "load_number", "customer", "customer_name",
|
||||||
|
"incoming_date", "total_weight", "data_status", "red_r2"],
|
||||||
|
order_by="name asc",
|
||||||
|
limit_page_length=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
html = "<!DOCTYPE html><html><head><title>Green Sheets</title>"
|
||||||
|
html += "<style>body{font-family:Arial,sans-serif;margin:30px;}"
|
||||||
|
html += ".green-sheet{border:2px solid #2E7D32;border-radius:6px;padding:16px;margin:20px 0;page-break-after:always;}"
|
||||||
|
html += ".gs-title{color:#2E7D32;font-size:18px;font-weight:700;border-bottom:2px solid #2E7D32;padding-bottom:4px;}"
|
||||||
|
html += ".gs-customer{background:#E8F5E9;border:1px solid #66BB6A;border-radius:4px;padding:8px;margin:8px 0;}"
|
||||||
|
html += ".gs-service-banner{background:#C62828;color:#fff;font-size:16px;font-weight:700;text-align:center;padding:8px;border-radius:4px;margin:8px 0;}"
|
||||||
|
html += ".gs-driver{background:#F5F5F5;border:1px solid #999;border-radius:4px;padding:8px;margin:8px 0;}"
|
||||||
|
html += ".gs-redline{background:#FFCDD2;border:2px solid #C62828;border-radius:4px;padding:8px;margin:8px 0;}"
|
||||||
|
html += ".gs-r2warning{background:#FFF9C4;border:1px solid #F9A825;border-radius:4px;padding:8px;margin:8px 0;font-size:12px;}"
|
||||||
|
html += "table{border-collapse:collapse;width:100%;margin:8px 0;}"
|
||||||
|
html += "th,td{border:1px solid #999;padding:6px;text-align:left;font-size:12px;}"
|
||||||
|
html += "th{background:#2E7D32;color:#fff;}"
|
||||||
|
html += ".gs-footer{margin-top:12px;font-size:11px;color:#666;border-top:1px solid #ccc;padding-top:8px;}"
|
||||||
|
html += "@media print{body{margin:10px;}.green-sheet{page-break-after:always;}}</style></head><body>"
|
||||||
|
|
||||||
|
for load in loads:
|
||||||
|
pallets = frappe.get_list("Pallet",
|
||||||
|
filters={"load": load.name},
|
||||||
|
fields=["name", "pallet_number", "inbound_weight", "total_items",
|
||||||
|
"data_status", "red_r2", "description", "needs_aor", "needs_cod", "notes", "status"],
|
||||||
|
limit_page_length=50,
|
||||||
|
)
|
||||||
|
|
||||||
|
for pallet in pallets:
|
||||||
|
html += '<div class="green-sheet">'
|
||||||
|
html += '<div class="gs-title">🟢 GREEN SHEET — Data-Bearing Equipment Tracking</div>'
|
||||||
|
html += '<div style="text-align:right;font-size:12px;">Pallet # ' + str(pallet.get("pallet_number", "")) + ' | Load # ' + str(load.get("name", "")) + ' | ' + str(load.get("incoming_date", "")) + '</div>'
|
||||||
|
|
||||||
|
# Customer block
|
||||||
|
html += '<div class="gs-customer">'
|
||||||
|
service_level = ""
|
||||||
|
if pallet.get("red_r2"):
|
||||||
|
service_level = pallet.get("red_r2", "")
|
||||||
|
html += '<strong>(' + str(load.get("customer", "")) + ') — ' + str(service_level) + ' — ' + str(load.get("customer_name", "")) + '</strong>'
|
||||||
|
html += '</div>'
|
||||||
|
|
||||||
|
# Service level banner (only for RED/NIST)
|
||||||
|
rr = pallet.get("red_r2", "")
|
||||||
|
if rr and rr != "Neither":
|
||||||
|
html += '<div class="gs-service-banner">SERVICE LEVEL: ' + str(rr).upper() + '</div>'
|
||||||
|
|
||||||
|
# Driver instructions (notes)
|
||||||
|
if pallet.get("notes"):
|
||||||
|
html += '<div class="gs-driver"><strong>Driver Instructions:</strong><br>' + str(pallet.get("notes", "")) + '</div>'
|
||||||
|
|
||||||
|
# RED LINE instructions (for RED/NIST)
|
||||||
|
if rr and rr not in ("", "Neither", "R2"):
|
||||||
|
html += '<div class="gs-redline"><strong>⚠ RED LINE INSTRUCTIONS</strong><br>All data-bearing equipment must be tracked. Destruction method per customer specification.</div>'
|
||||||
|
|
||||||
|
# Pallet details table
|
||||||
|
html += '<table><tr><th>Pallet Designation</th><th>Data Status</th></tr>'
|
||||||
|
html += '<tr><td>' + str(pallet.get("status", "Received")) + '</td><td>' + str(pallet.get("data_status", "")) + '</td></tr>'
|
||||||
|
html += '<tr><th>Inbound Weight</th><th>Total Items</th></tr>'
|
||||||
|
html += '<tr><td>' + str(pallet.get("inbound_weight", "")) + '</td><td>' + str(pallet.get("total_items", "")) + '</td></tr>'
|
||||||
|
html += '<tr><th>AoR/CoR</th><th>Contents</th></tr>'
|
||||||
|
aor_cor = ""
|
||||||
|
if pallet.get("needs_aor"): aor_cor += "✓ AoR "
|
||||||
|
if pallet.get("needs_cod"): aor_cor += "✓ CoD"
|
||||||
|
html += '<tr><td>' + (aor_cor or "None") + '</td><td>' + str(pallet.get("description", "")) + '</td></tr>'
|
||||||
|
html += '</table>'
|
||||||
|
|
||||||
|
# Material tracking (hand-write on paper)
|
||||||
|
html += '<table><tr><th>Material</th><th>%</th><th>Weight</th><th>Sign Off</th><th>Date</th></tr>'
|
||||||
|
for _ in range(4):
|
||||||
|
html += '<tr><td> </td><td></td><td></td><td></td><td></td></tr>'
|
||||||
|
html += '</table>'
|
||||||
|
|
||||||
|
# R2 warning
|
||||||
|
html += '<div class="gs-r2warning">⚠ R2 REQUIREMENT: This pallet contains data-bearing equipment. All devices must be tracked through erasure with 5% verification audit.</div>'
|
||||||
|
|
||||||
|
# Signatures
|
||||||
|
html += '<table><tr><th>Received By</th><th>Inspected By</th><th>Verified By</th></tr>'
|
||||||
|
html += '<tr><td> </td><td> </td><td> </td></tr></table>'
|
||||||
|
|
||||||
|
# Footer
|
||||||
|
html += '<div class="gs-footer">Westech Electronics • Green Sheet — Pallet # ' + str(pallet.get("pallet_number", "")) + ' • Printed ' + str(frappe.utils.nowdate()) + ' • KEEP WITH PALLET AT ALL TIMES</div>'
|
||||||
|
html += '</div>'
|
||||||
|
|
||||||
|
html += "</body></html>"
|
||||||
|
frappe.local.response["type"] = "html"
|
||||||
|
frappe.local.response["page_content"] = html
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def print_labels(date=None):
|
||||||
|
"""Generate printable labels."""
|
||||||
|
if not date:
|
||||||
|
date = today()
|
||||||
|
|
||||||
|
filters = {"pickup_date": date} if date else {}
|
||||||
|
pickups = frappe.get_list("Scheduled Pickup",
|
||||||
|
filters=filters,
|
||||||
|
fields=["name", "company_name", "pickup_date", "num_labels", "data_status", "red_r2"],
|
||||||
|
limit_page_length=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
html = "<!DOCTYPE html><html><head><title>Labels</title>"
|
||||||
|
html += "<style>body{font-family:Arial,sans-serif;margin:10px;}"
|
||||||
|
html += ".label{border:2px solid #000;width:3in;height:1.5in;display:inline-block;margin:4px;padding:6px;font-size:11px;page-break-inside:avoid;}"
|
||||||
|
html += ".label-co{font-weight:700;font-size:14px;}.label-date{font-size:10px;color:#666;}"
|
||||||
|
html += ".label-status{font-size:10px;margin-top:2px;}"
|
||||||
|
html += "@media print{body{margin:0;}}</style></head><body>"
|
||||||
|
|
||||||
|
for p in pickups:
|
||||||
|
n = p.get("num_labels") or 1
|
||||||
|
for _ in range(n):
|
||||||
|
html += '<div class="label">'
|
||||||
|
html += '<div class="label-co">' + str(p.get("company_name", "")) + '</div>'
|
||||||
|
html += '<div class="label-date">' + str(p.get("pickup_date", "")) + '</div>'
|
||||||
|
html += '<div class="label-status">' + str(p.get("data_status", "")) + " | " + str(p.get("red_r2", "")) + '</div>'
|
||||||
|
html += '</div>'
|
||||||
|
|
||||||
|
html += "</body></html>"
|
||||||
|
frappe.local.response["type"] = "html"
|
||||||
|
frappe.local.response["page_content"] = html
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2026, Westech and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Customer Interaction", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "autoincrement",
|
||||||
|
"creation": "2026-05-22 11:58:31.649154",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"customer",
|
||||||
|
"customer_number",
|
||||||
|
"contact_name",
|
||||||
|
"phone_1",
|
||||||
|
"phone_2",
|
||||||
|
"email_1",
|
||||||
|
"email_2",
|
||||||
|
"address",
|
||||||
|
"city",
|
||||||
|
"zip",
|
||||||
|
"hours",
|
||||||
|
"notes",
|
||||||
|
"red_r2",
|
||||||
|
"dnc",
|
||||||
|
"raw_name",
|
||||||
|
"raw_phone1",
|
||||||
|
"raw_phone2",
|
||||||
|
"raw_email"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "customer",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Customer",
|
||||||
|
"options": "Customer",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Customer Number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "contact_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Contact Name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "phone_1",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Phone 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "phone_2",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Phone 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "email_1",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Email 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "email_2",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Email 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "address",
|
||||||
|
"fieldtype": "Text",
|
||||||
|
"label": "Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "city",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "City"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "zip",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Zip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "hours",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Hours"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "notes",
|
||||||
|
"fieldtype": "Text",
|
||||||
|
"label": "Notes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "red_r2",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Red R2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "dnc",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "DNC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "raw_name",
|
||||||
|
"fieldtype": "Text",
|
||||||
|
"label": "Raw Name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "raw_phone1",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Raw Phone 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "raw_phone2",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Raw Phone 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "raw_email",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Raw Email"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2026-05-22 11:58:31.649154",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Westech R2",
|
||||||
|
"name": "Customer Interaction",
|
||||||
|
"naming_rule": "Autoincrement",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"row_format": "Dynamic",
|
||||||
|
"rows_threshold_for_grid_search": 20,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"title_field": "contact_name",
|
||||||
|
"track_changes": 1,
|
||||||
|
"track_seen": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Westech and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class CustomerInteraction(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2026, Westech and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestCustomerInteraction(FrappeTestCase):
|
||||||
|
pass
|
||||||
+9
@@ -11,6 +11,15 @@ frappe.ui.form.on('Pallet', {
|
|||||||
'pallet': frm.doc.pallet_number || frm.doc.name
|
'pallet': frm.doc.pallet_number || frm.doc.name
|
||||||
});
|
});
|
||||||
}, __('View'));
|
}, __('View'));
|
||||||
|
|
||||||
|
frm.add_custom_button(__('Generate COR'), function() {
|
||||||
|
if (!frm.doc.pallet_number) {
|
||||||
|
frappe.msgprint('Please save the Pallet first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var url = '/api/method/westech_r2.api.cor_generator.generate_cor?pallet_number=' + encodeURIComponent(frm.doc.pallet_number);
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}, __('Actions'));
|
||||||
},
|
},
|
||||||
|
|
||||||
customer_number: function(frm) {
|
customer_number: function(frm) {
|
||||||
+2
-2
@@ -2,7 +2,7 @@ frappe.ui.form.on('Scheduled Pickup', {
|
|||||||
customer_number: function(frm) {
|
customer_number: function(frm) {
|
||||||
var customer = frm.doc.customer_number;
|
var customer = frm.doc.customer_number;
|
||||||
if (!customer) {
|
if (!customer) {
|
||||||
clear_supplier_fields(frm);
|
clear_customer_fields(frm);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
frappe.call({
|
frappe.call({
|
||||||
@@ -52,7 +52,7 @@ frappe.ui.form.on('Scheduled Pickup', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function clear_supplier_fields(frm) {
|
function clear_customer_fields(frm) {
|
||||||
frm.set_value('company_name', '');
|
frm.set_value('company_name', '');
|
||||||
frm.set_value('contact_name', '');
|
frm.set_value('contact_name', '');
|
||||||
frm.set_value('contact_phone', '');
|
frm.set_value('contact_phone', '');
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
app_name = "westech_r2"
|
||||||
|
app_title = "Westech R2"
|
||||||
|
app_publisher = "Westech"
|
||||||
|
app_description = "R2 Tracking for Westech Recyclers"
|
||||||
|
app_email = ""
|
||||||
|
app_license = "MIT"
|
||||||
|
|
||||||
|
# Fixtures - these will be exported/imported
|
||||||
|
fixtures = [
|
||||||
|
"DocType",
|
||||||
|
"Custom Field",
|
||||||
|
"Client Script",
|
||||||
|
"Workspace",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Required apps
|
||||||
|
required_apps = ["erpnext"]
|
||||||
|
|
||||||
|
# DocType event hooks
|
||||||
|
doc_events = {
|
||||||
|
"Pallet": {
|
||||||
|
"before_save": "westech_r2.doctype.pallet.pallet.update_serial_nos",
|
||||||
|
},
|
||||||
|
"Scheduled Pickup": {
|
||||||
|
"before_save": "westech_r2.doctype.scheduled_pickup.scheduled_pickup.set_title",
|
||||||
|
},
|
||||||
|
"Serial No": {
|
||||||
|
"validate": "westech_r2.westech_r2.api.serial_hooks.validate_hardware_tests",
|
||||||
|
},
|
||||||
|
"Load": {
|
||||||
|
"before_save": "westech_r2.doctype.load.load.calculate_totals",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
app_include_css = "/assets/westech_r2/css/westech_theme.css"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Westech R2
|
||||||
+5
-5
@@ -7,12 +7,12 @@ def search_customers(q=""):
|
|||||||
return []
|
return []
|
||||||
q = q.strip().lower()
|
q = q.strip().lower()
|
||||||
customers = frappe.db.sql("""
|
customers = frappe.db.sql("""
|
||||||
SELECT c.name, c.customer_name, c.customer_number, c.phone, c.email_id,
|
SELECT c.name, c.customer_name, c.customer_number, c.mobile_no,
|
||||||
a.address_line1, a.city, a.state, a.pincode
|
a.address_line1, a.city, a.state, a.pincode
|
||||||
FROM tabCustomer c
|
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 `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
|
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
|
WHERE LOWER(c.customer_name) LIKE %s OR LOWER(c.customer_number) LIKE %s OR LOWER(c.mobile_no) LIKE %s
|
||||||
ORDER BY c.customer_name
|
ORDER BY c.customer_name
|
||||||
LIMIT 20
|
LIMIT 20
|
||||||
""", ("%" + q + "%", "%" + q + "%", "%" + q + "%"), as_dict=True)
|
""", ("%" + q + "%", "%" + q + "%", "%" + q + "%"), as_dict=True)
|
||||||
@@ -27,7 +27,7 @@ def get_customer(name):
|
|||||||
addr = frappe.db.sql("""
|
addr = frappe.db.sql("""
|
||||||
SELECT a.address_line1, a.city, a.state, a.pincode, a.phone
|
SELECT a.address_line1, a.city, a.state, a.pincode, a.phone
|
||||||
FROM tabAddress a
|
FROM tabAddress a
|
||||||
JOIN tabDynamic Link dl ON dl.parent = a.name AND dl.link_doctype = 'Customer' AND dl.link_name = %s
|
JOIN `tabDynamic Link` dl ON dl.parent = a.name AND dl.link_doctype = 'Customer' AND dl.link_name = %s
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""", (name,), as_dict=True)
|
""", (name,), as_dict=True)
|
||||||
if addr:
|
if addr:
|
||||||
@@ -50,7 +50,7 @@ def create_customer_from_intake(data):
|
|||||||
customer.customer_group = data.get("customer_group", "IT Recycling")
|
customer.customer_group = data.get("customer_group", "IT Recycling")
|
||||||
customer.customer_type = "Company"
|
customer.customer_type = "Company"
|
||||||
customer.customer_number = data.get("customer_number")
|
customer.customer_number = data.get("customer_number")
|
||||||
customer.phone = data.get("phone")
|
customer.mobile_no = data.get("phone")
|
||||||
customer.email_id = data.get("email_id")
|
customer.email_id = data.get("email_id")
|
||||||
customer.legacy_notes = data.get("legacy_notes")
|
customer.legacy_notes = data.get("legacy_notes")
|
||||||
customer.hours_of_operation = data.get("hours_of_operation")
|
customer.hours_of_operation = data.get("hours_of_operation")
|
||||||
@@ -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 @@
|
|||||||
|
|
||||||
@@ -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,580 @@
|
|||||||
|
frappe.pages['intake'].on_page_load = function(wrapper) {
|
||||||
|
try {
|
||||||
|
var page = frappe.ui.make_app_page({
|
||||||
|
parent: wrapper,
|
||||||
|
title: 'Customer Management',
|
||||||
|
single_column: true
|
||||||
|
});
|
||||||
|
|
||||||
|
page.set_primary_action('New Customer', function() {
|
||||||
|
show_intake_form();
|
||||||
|
}, 'add');
|
||||||
|
|
||||||
|
page.add_inner_button('Refresh', function() {
|
||||||
|
load_customer_list();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(wrapper).find('.layout-main-section').html(`
|
||||||
|
<div class="intake-station" style="padding: 20px;">
|
||||||
|
<div id="intake-form-container">
|
||||||
|
<div class="card" style="margin-bottom: 20px;">
|
||||||
|
<div class="card-header" style="background: linear-gradient(135deg, #6f42c1, #28a745); color: white; padding: 15px;">
|
||||||
|
<h4 style="margin:0; color: white;">Customer Management</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;">Customer</h5>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Customer <span class="text-danger">*</span></label>
|
||||||
|
<div id="customer-number-control"></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Company Name</label>
|
||||||
|
<input type="text" id="company_name" class="form-control" readonly style="background:#f8f9fa;">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Driver</label>
|
||||||
|
<div id="driver-control"></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Contact Name</label>
|
||||||
|
<input type="text" id="contact_name" class="form-control">
|
||||||
|
</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>
|
||||||
|
<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>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>Hours of Operation</label>
|
||||||
|
<input type="text" id="hours_of_operation" class="form-control" readonly style="background:#f8f9fa;" placeholder="Auto-filled from Customer">
|
||||||
|
</div>
|
||||||
|
<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="Clear">Clear</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Notes</label>
|
||||||
|
<textarea id="notes" class="form-control" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Legacy Notes</label>
|
||||||
|
<textarea id="legacy_notes" class="form-control" rows="2" readonly style="background:#fafafa;" title="Auto-filled from Customer record"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4" id="load-info-section" style="display:none;">
|
||||||
|
<h5 style="color:#6f42c1;">Items & Weight</h5>
|
||||||
|
<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>
|
||||||
|
<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;">
|
||||||
|
Save Contact Info
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-lg" id="btn-print-labels" disabled>
|
||||||
|
Print Labels
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-lg" id="btn-generate-cor" disabled>
|
||||||
|
CoR/AoR
|
||||||
|
</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" style="display:none;">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header" style="background: #f8f9fa; display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<h5 style="margin:0;">Customers</h5>
|
||||||
|
<input type="text" id="customer-search" class="form-control" placeholder="Search..." style="width: 300px;">
|
||||||
|
</div>
|
||||||
|
<div class="card-body" style="padding: 0;">
|
||||||
|
<table class="table table-striped table-hover" id="customer-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Company</th>
|
||||||
|
<th>Contact</th>
|
||||||
|
<th>Phone</th>
|
||||||
|
<th>Address</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="customer-tbody">
|
||||||
|
<tr><td colspan="5" class="text-center">Loading...</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
setup_link_controls();
|
||||||
|
set_today_date();
|
||||||
|
|
||||||
|
// Trigger weekday calculation after date field is set
|
||||||
|
setTimeout(function() {
|
||||||
|
var dateVal = $('#received_date').val();
|
||||||
|
if (dateVal) {
|
||||||
|
var d = new Date(dateVal + 'T12:00:00');
|
||||||
|
var days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
|
||||||
|
$('#weekday').val(days[d.getDay()] || '');
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
|
||||||
|
// Show/hide load info based on pickup dropdown
|
||||||
|
.on('change', function() {
|
||||||
|
var val = .val();
|
||||||
|
if (val) {
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Initial state - hide if blank
|
||||||
|
.trigger('change');
|
||||||
|
|
||||||
|
load_customer_list();
|
||||||
|
|
||||||
|
$('#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_customer();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#btn-cancel').on('click', function() {
|
||||||
|
show_customer_list();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#btn-print-labels').on('click', function() {
|
||||||
|
frappe.msgprint('Label printing coming soon.');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#btn-generate-cor').on('click', function() {
|
||||||
|
generate_cor_report();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#customer-search').on('input', function() {
|
||||||
|
var q = $(this).val().toLowerCase();
|
||||||
|
$('#customer-tbody tr').each(function() {
|
||||||
|
var text = $(this).text().toLowerCase();
|
||||||
|
$(this).toggle(text.indexOf(q) > -1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch(e) {
|
||||||
|
console.error('[INTAKE] FATAL:', e.message, e.stack);
|
||||||
|
$(wrapper).find('.layout-main-section').html(
|
||||||
|
'<div style="padding:20px;"><h3>Error Loading Page</h3><pre>' + e.message + '</pre></div>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Link Controls ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
var customer_number_control = null;
|
||||||
|
var driver_control = null;
|
||||||
|
var selected_customer_name = null;
|
||||||
|
|
||||||
|
function setup_link_controls() {
|
||||||
|
customer_number_control = frappe.ui.form.make_control({
|
||||||
|
parent: $('#customer-number-control'),
|
||||||
|
df: {
|
||||||
|
fieldtype: 'Link',
|
||||||
|
fieldname: 'customer_number',
|
||||||
|
options: 'Customer',
|
||||||
|
label: 'Customer',
|
||||||
|
reqd: 1,
|
||||||
|
placeholder: 'Search customer...',
|
||||||
|
onchange: function() {
|
||||||
|
var val = customer_number_control.get_value();
|
||||||
|
if (val) {
|
||||||
|
selected_customer_name = val;
|
||||||
|
fetch_customer_details(val);
|
||||||
|
} else {
|
||||||
|
selected_customer_name = null;
|
||||||
|
clear_customer_fields();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
only_input: true,
|
||||||
|
});
|
||||||
|
customer_number_control.refresh();
|
||||||
|
$('#customer-number-control .control-input').css('margin', '0');
|
||||||
|
$('#customer-number-control .help-box').remove();
|
||||||
|
|
||||||
|
driver_control = frappe.ui.form.make_control({
|
||||||
|
parent: $('#driver-control'),
|
||||||
|
df: {
|
||||||
|
fieldtype: 'Link',
|
||||||
|
fieldname: 'driver',
|
||||||
|
options: 'Employee',
|
||||||
|
label: 'Driver',
|
||||||
|
placeholder: 'Search driver...',
|
||||||
|
onchange: function() {}
|
||||||
|
},
|
||||||
|
only_input: true,
|
||||||
|
});
|
||||||
|
driver_control.refresh();
|
||||||
|
$('#driver-control .control-input').css('margin', '0');
|
||||||
|
$('#driver-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;
|
||||||
|
$('#company_name').val(c.customer_name || '');
|
||||||
|
$('#contact_name').val(c.contact_persons || '');
|
||||||
|
$('#hours_of_operation').val(c.hours_of_operation || '');
|
||||||
|
$('#legacy_notes').val(c.legacy_notes || '');
|
||||||
|
|
||||||
|
// Parse phone and email from contact_persons if not in dedicated fields
|
||||||
|
var phone = c.mobile_no || '';
|
||||||
|
var email = c.email_id || '';
|
||||||
|
var addressLine = '';
|
||||||
|
|
||||||
|
// Extract phone from contact_persons text blob
|
||||||
|
if (!phone && c.contact_persons) {
|
||||||
|
var phoneMatch = c.contact_persons.match(/\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/);
|
||||||
|
if (phoneMatch) phone = phoneMatch[0];
|
||||||
|
}
|
||||||
|
// Extract email from contact_persons text blob
|
||||||
|
if (!email && c.contact_persons) {
|
||||||
|
var emailMatch = c.contact_persons.match(/[\w.+-]+@[\w.-]+\.\w+/);
|
||||||
|
if (emailMatch) email = emailMatch[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#contact_number').val(phone);
|
||||||
|
$('#contact_email').val(email);
|
||||||
|
|
||||||
|
// Enable CoR button once we have a customer with data
|
||||||
|
$('#btn-generate-cor').prop('disabled', false);
|
||||||
|
|
||||||
|
// Get address — always fetch from Address record for full street+city+state+zip
|
||||||
|
if (c.customer_primary_address) {
|
||||||
|
frappe.call({
|
||||||
|
method: 'frappe.client.get',
|
||||||
|
args: {doctype: 'Address', name: c.customer_primary_address},
|
||||||
|
callback: function(r3) {
|
||||||
|
if (r3.message) {
|
||||||
|
var addr = r3.message;
|
||||||
|
var line = (addr.address_line1 || '') + (addr.address_line2 ? ', ' + addr.address_line2 : '');
|
||||||
|
var full = line + (addr.city ? ', ' + addr.city : '') + (addr.state ? ', ' + addr.state : '') + (addr.pincode ? ' ' + addr.pincode : '');
|
||||||
|
$('#address_line').val(full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (c.primary_address) {
|
||||||
|
// Fallback: parse text blob (less reliable)
|
||||||
|
var lines = c.primary_address.split('\n');
|
||||||
|
addressLine = lines.join(', ');
|
||||||
|
$('#address_line').val(addressLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If still missing phone/email, try linked Contact
|
||||||
|
if ((!phone || !email) && c.customer_primary_contact) {
|
||||||
|
frappe.call({
|
||||||
|
method: 'frappe.client.get',
|
||||||
|
args: {doctype: 'Contact', name: c.customer_primary_contact},
|
||||||
|
callback: function(r2) {
|
||||||
|
if (r2.message) {
|
||||||
|
var ct = r2.message;
|
||||||
|
if (!phone) $('#contact_number').val(ct.phone || ct.mobile_no || '');
|
||||||
|
if (!email) $('#contact_email').val(ct.email_id || '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear_customer_fields() {
|
||||||
|
$('#company_name').val('');
|
||||||
|
$('#contact_name').val('');
|
||||||
|
$('#contact_number').val('');
|
||||||
|
$('#contact_email').val('');
|
||||||
|
$('#address_line').val('');
|
||||||
|
$('#hours_of_operation').val('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear_form() {
|
||||||
|
if (customer_number_control) customer_number_control.set_value('');
|
||||||
|
if (driver_control) driver_control.set_value('');
|
||||||
|
$('#company_name').val('');
|
||||||
|
$('#received_date').val('');
|
||||||
|
$('#weekday').val('');
|
||||||
|
$('#pickup').val('');
|
||||||
|
$('#data_status').val('');
|
||||||
|
$('#red_r2').val('');
|
||||||
|
$('#barcode').val('');
|
||||||
|
$('#total_items').val(0);
|
||||||
|
$('#num_labels').val(1);
|
||||||
|
$('#contact_name').val('');
|
||||||
|
$('#contact_number').val('');
|
||||||
|
$('#contact_email').val('');
|
||||||
|
$('#address_line').val('');
|
||||||
|
$('#hours_of_operation').val('');
|
||||||
|
$('#legacy_notes').val('');
|
||||||
|
$('#weights').val('');
|
||||||
|
$('#invoice_check_request').val('');
|
||||||
|
$('#amount').val(0);
|
||||||
|
$('#paid_received').val('');
|
||||||
|
$('#notes').val('');
|
||||||
|
$('#btn-print-labels').prop('disabled', true);
|
||||||
|
$('#btn-generate-cor').prop('disabled', true);
|
||||||
|
$('#save-status').html('');
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_customer_list() {
|
||||||
|
$('#intake-form-container').hide();
|
||||||
|
$('#recent-pallets').show();
|
||||||
|
clear_form();
|
||||||
|
load_customer_list();
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_customer_list() {
|
||||||
|
frappe.call({
|
||||||
|
method: 'frappe.client.get_list',
|
||||||
|
args: {
|
||||||
|
doctype: 'Customer',
|
||||||
|
fields: ['name', 'customer_name', 'customer_number', 'mobile_no', 'email_id', 'contact_persons', 'primary_address', 'customer_primary_address'],
|
||||||
|
limit_page_length: 50,
|
||||||
|
order_by: 'customer_name asc'
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
var tbody = $('#customer-tbody');
|
||||||
|
tbody.empty();
|
||||||
|
if (!r.message || r.message.length === 0) {
|
||||||
|
tbody.append('<tr><td colspan="5" class="text-center">No customers found.</td></tr>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
r.message.forEach(function(c) {
|
||||||
|
var contactName = '';
|
||||||
|
if (c.contact_persons) {
|
||||||
|
var parts = c.contact_persons.split('|');
|
||||||
|
if (parts.length > 0) contactName = parts[0].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody.append(
|
||||||
|
'<tr style="cursor:pointer;" onclick="select_customer_from_list(\'' + c.name.replace(/'/g, "\\'") + '\')">' +
|
||||||
|
'<td><strong>' + (c.customer_name || c.name) + '</strong>' + (c.customer_number ? ' <span class="text-muted">(#' + c.customer_number + ')</span>' : '') + '</td>' +
|
||||||
|
'<td>' + contactName + '</td>' +
|
||||||
|
'<td>' + (c.mobile_no || '') + '</td>' +
|
||||||
|
'<td>' + (c.primary_address ? c.primary_address.replace(/\n/g, ', ') : '') + '</td>' +
|
||||||
|
'<td><button class="btn btn-xs btn-default"><i class="fa fa-arrow-right"></i></button></td>' +
|
||||||
|
'</tr>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function select_customer_from_list(customer_name) {
|
||||||
|
if (customer_number_control) customer_number_control.set_value(customer_name);
|
||||||
|
fetch_customer_details(customer_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function edit_pallet(name) {
|
||||||
|
frappe.call({
|
||||||
|
method: 'frappe.client.get',
|
||||||
|
args: {doctype: 'Pallet', name: name},
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
show_intake_form();
|
||||||
|
var d = r.message;
|
||||||
|
$('#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 (driver_control) driver_control.set_value(d.driver || '');
|
||||||
|
$('#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 || '');
|
||||||
|
$('#hours_of_operation').val(d.hours_of_operation || '');
|
||||||
|
$('#legacy_notes').val(d.legacy_notes || '');
|
||||||
|
$('#weights').val(d.weights || '');
|
||||||
|
$('#invoice_check_request').val(d.invoice_check_request || '');
|
||||||
|
$('#amount').val(d.amount || 0);
|
||||||
|
$('#paid_received').val(d.paid_received || '');
|
||||||
|
$('#notes').val(d.notes || '');
|
||||||
|
$('#received_date').trigger('change');
|
||||||
|
$('#btn-print-labels').prop('disabled', false);
|
||||||
|
$('#btn-generate-cor').prop('disabled', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function save_customer() {
|
||||||
|
var customer_name = customer_number_control ? customer_number_control.get_value() : null;
|
||||||
|
if (!customer_name) {
|
||||||
|
frappe.msgprint("Please select a customer first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frappe.call({
|
||||||
|
method: "frappe.client.get",
|
||||||
|
args: { doctype: "Customer", name: customer_name },
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message) {
|
||||||
|
var doc = r.message;
|
||||||
|
doc.contact_persons = .val();
|
||||||
|
doc.mobile_no = .val();
|
||||||
|
doc.email_id = .val();
|
||||||
|
doc.hours_of_operation = .val();
|
||||||
|
doc.legacy_notes = .val();
|
||||||
|
frappe.call({
|
||||||
|
method: "frappe.client.save",
|
||||||
|
args: { doc: doc },
|
||||||
|
callback: function(r2) {
|
||||||
|
if (r2.message) {
|
||||||
|
frappe.msgprint("Customer updated!");
|
||||||
|
.html("<span style=\"color:green;\">Saved!</span>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function generate_cor_report() {
|
||||||
|
var companyName = $('#company_name').val();
|
||||||
|
if (!companyName) {
|
||||||
|
frappe.msgprint('Please select a customer first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var args = {
|
||||||
|
company_name: companyName,
|
||||||
|
weights: $('#weights').val() || '',
|
||||||
|
received_date: $('#received_date').val() || '',
|
||||||
|
red_r2: $('#red_r2').val() || '',
|
||||||
|
contact_name: $('#contact_name').val() || '',
|
||||||
|
contact_number: $('#contact_number').val() || '',
|
||||||
|
address_line: $('#address_line').val() || '',
|
||||||
|
pallet_name: $('#intake-form-container').data('pallet-name') || ''
|
||||||
|
};
|
||||||
|
window.open('/api/method/westech_r2.api.cor_generator.generate_cor?'
|
||||||
|
+ '&company_name=' + encodeURIComponent(args.company_name)
|
||||||
|
+ '&weights=' + encodeURIComponent(args.weights)
|
||||||
|
+ '&received_date=' + encodeURIComponent(args.received_date)
|
||||||
|
+ '&red_r2=' + encodeURIComponent(args.red_r2)
|
||||||
|
+ '&contact_name=' + encodeURIComponent(args.contact_name)
|
||||||
|
+ '&contact_number=' + encodeURIComponent(args.contact_number)
|
||||||
|
+ '&address_line=' + encodeURIComponent(args.address_line)
|
||||||
|
+ '&pallet_name=' + encodeURIComponent(args.pallet_name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.edit_pallet = edit_pallet;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"content": null,
|
||||||
|
"creation": "2026-05-09 12:05:32.403207",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Page",
|
||||||
|
"idx": 0,
|
||||||
|
"modified": "2026-05-23 01:31:28.579759",
|
||||||
|
"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": "Customer Management"
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from frappe import _
|
||||||
|
|
||||||
|
def get_context(context):
|
||||||
|
context.no_cache = 1
|
||||||
|
context.title = _("Intake Station")
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
no_cache = 1
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
no_cache = 1
|
||||||
+7
-7
@@ -1,6 +1,6 @@
|
|||||||
<style>
|
<style>
|
||||||
.pallet-list-page { font-family: Helvetica Neue, Arial, sans-serif; }
|
.pallet-list-page { font-family: Helvetica Neue, Arial, sans-serif; }
|
||||||
.pallet-list-page h2 { color: #2F5496; margin-bottom: 16px; font-size: 20px; }
|
.pallet-list-page h2 { color: #6f42c1; 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 { margin-bottom: 16px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||||
.pallet-search-box input, .pallet-search-box select {
|
.pallet-search-box input, .pallet-search-box select {
|
||||||
padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;
|
padding: 6px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px;
|
||||||
@@ -9,18 +9,18 @@
|
|||||||
padding: 6px 14px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;
|
padding: 6px 14px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer;
|
||||||
font-size: 13px; background: white;
|
font-size: 13px; background: white;
|
||||||
}
|
}
|
||||||
.pallet-search-box button.btn-primary { background: #2F5496; color: white; border-color: #2F5496; }
|
.pallet-search-box button.btn-primary { background: #6f42c1; color: white; border-color: #6f42c1; }
|
||||||
.pallet-search-box button.btn-success { background: #548235; color: white; border-color: #548235; }
|
.pallet-search-box button.btn-success { background: #28a745; color: white; border-color: #28a745; }
|
||||||
.pallet-table-container { padding: 0; overflow-x: auto; background: white; border-radius: 4px; }
|
.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 { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||||
.pallet-table th {
|
.pallet-table th {
|
||||||
background: #2F5496; color: white; padding: 8px 10px; text-align: left;
|
background: #6f42c1; color: white; padding: 8px 10px; text-align: left;
|
||||||
cursor: pointer; white-space: nowrap; font-weight: 500;
|
cursor: pointer; white-space: nowrap; font-weight: 500;
|
||||||
}
|
}
|
||||||
.pallet-table td { padding: 5px 10px; border-bottom: 1px solid #eee; }
|
.pallet-table td { padding: 5px 10px; border-bottom: 1px solid #eee; }
|
||||||
.pallet-table tr:hover { background: #f5f5f5; }
|
.pallet-table tr:hover { background: #f5f5f5; }
|
||||||
.pallet-table tr.new-row { background: #FFF9E6 !important; }
|
.pallet-table tr.new-row { background: #FFF9E6 !important; }
|
||||||
.pallet-table a { color: #2F5496; text-decoration: none; font-weight: 600; }
|
.pallet-table a { color: #6f42c1; text-decoration: none; font-weight: 600; }
|
||||||
.status-received { color: #2196F3; font-weight: 600; }
|
.status-received { color: #2196F3; font-weight: 600; }
|
||||||
.status-sorting { color: #FF9800; font-weight: 600; }
|
.status-sorting { color: #FF9800; font-weight: 600; }
|
||||||
.status-processing { color: #9C27B0; font-weight: 600; }
|
.status-processing { color: #9C27B0; font-weight: 600; }
|
||||||
@@ -29,9 +29,9 @@
|
|||||||
.pallet-pagination { margin-top: 12px; display: flex; gap: 5px; align-items: center; }
|
.pallet-pagination { margin-top: 12px; display: flex; gap: 5px; align-items: center; }
|
||||||
.pallet-pagination button {
|
.pallet-pagination button {
|
||||||
padding: 5px 12px; border: 1px solid #ddd; background: white; cursor: pointer;
|
padding: 5px 12px; border: 1px solid #ddd; background: white; cursor: pointer;
|
||||||
border-radius: 4px; font-size: 13px; color: #2F5496;
|
border-radius: 4px; font-size: 13px; color: #6f42c1;
|
||||||
}
|
}
|
||||||
.pallet-pagination button.active { background: #2F5496; color: white; border-color: #2F5496; }
|
.pallet-pagination button.active { background: #6f42c1; color: white; border-color: #6f42c1; }
|
||||||
.pallet-pagination button:disabled { opacity: 0.5; cursor: not-allowed; }
|
.pallet-pagination button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||||
.pallet-count { margin-left: auto; color: #666; font-size: 13px; }
|
.pallet-count { margin-left: auto; color: #666; font-size: 13px; }
|
||||||
</style>
|
</style>
|
||||||
+1
-1
@@ -17,7 +17,7 @@ frappe.pages["pallet-list"].on_page_load = function(wrapper) {
|
|||||||
|
|
||||||
function loadPallets() {
|
function loadPallets() {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "westech_r2.page.pallet-list.pallet-list.get_pallets",
|
method: "westech_r2.page.pallet_list.pallet_list.get_pallets",
|
||||||
args: {
|
args: {
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
page_size: pageSize,
|
page_size: pageSize,
|
||||||
@@ -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 @@
|
|||||||
|
|
||||||
@@ -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
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user