87 lines
3.1 KiB
Python
87 lines
3.1 KiB
Python
import json
|
|
import secrets
|
|
import frappe
|
|
from datetime import datetime, timedelta
|
|
from frappe import _
|
|
from frappe.utils import now_datetime
|
|
from frappe.utils.password import check_password, get_decrypted_password
|
|
|
|
TOKEN_TTL_HOURS = 24
|
|
TOKEN_CACHE_PREFIX = "worker_token"
|
|
|
|
ACTIVITY_LABEL_TO_DOCTYPE = {
|
|
"Scouting": "Scouting Report", "Order Patching": "Order Patch",
|
|
"Order Dispatching": "Dispatch Record", "Mixing Media": "Media Mix Batch",
|
|
"Planting": "Planting Record", "Covering Seedlings": "Cover Log",
|
|
"Watering Greenhouse": "Greenhouse Watering Log", "Watering Seedlings": "Seedling Watering Log",
|
|
"Spraying Morning": "Spray Log Morning", "Spraying Evening": "Spray Log Evening",
|
|
"Customer Engagement": "Customer Interaction", "Soaking": "Soaking Record",
|
|
"Recycling Trays": "Tray Recycling Record",
|
|
}
|
|
|
|
def issue_token(worker_id):
|
|
token = secrets.token_urlsafe(32)
|
|
expires_at = now_datetime() + timedelta(hours=TOKEN_TTL_HOURS)
|
|
frappe.cache().set_value(
|
|
f"{TOKEN_CACHE_PREFIX}:{token}",
|
|
json.dumps({"worker_id": worker_id, "expires_at": expires_at.isoformat()}),
|
|
expires_in_sec=TOKEN_TTL_HOURS * 3600,
|
|
)
|
|
return token, expires_at
|
|
|
|
@frappe.whitelist(allow_guest=True, methods=["POST"])
|
|
def field_worker_login(worker_id, pin):
|
|
worker_id = (worker_id or "").strip()
|
|
pin = (pin or "").strip()
|
|
|
|
if not frappe.db.exists("Worker Profile", worker_id):
|
|
frappe.throw(f"Worker ID '{worker_id}' not found in system.", frappe.AuthenticationError)
|
|
|
|
# --- UNIVERSAL PIN CHECK ---
|
|
authenticated = False
|
|
|
|
# Method 1: Standard Frappe Check
|
|
try:
|
|
check_password(worker_id, pin, "Worker Profile", "pin_hash")
|
|
authenticated = True
|
|
except:
|
|
pass
|
|
|
|
# Method 2: Decryption Check (for Desk-saved PINs)
|
|
if not authenticated:
|
|
try:
|
|
stored = get_decrypted_password("Worker Profile", worker_id, fieldname="pin_hash")
|
|
if stored and str(stored) == str(pin):
|
|
authenticated = True
|
|
except:
|
|
pass
|
|
|
|
# Method 3: Direct DB Match (Fallback)
|
|
if not authenticated:
|
|
db_val = frappe.db.get_value("Worker Profile", worker_id, "pin_hash")
|
|
if db_val == pin: # Sometimes saved as plain text in dev
|
|
authenticated = True
|
|
|
|
if not authenticated:
|
|
frappe.throw(f"The PIN you entered for {worker_id} is incorrect.", frappe.AuthenticationError)
|
|
|
|
profile = frappe.get_doc("Worker Profile", worker_id)
|
|
if not profile.active:
|
|
frappe.throw("This worker account is deactivated.", frappe.AuthenticationError)
|
|
|
|
assigned_activities = [
|
|
ACTIVITY_LABEL_TO_DOCTYPE.get(row.activity_type, row.activity_type)
|
|
for row in (profile.get("assigned_activity_types") or []) if row.get("activity_type")
|
|
]
|
|
|
|
token, expires_at = issue_token(worker_id)
|
|
return {
|
|
"token": token,
|
|
"expires_at": expires_at.isoformat(),
|
|
"worker_id": profile.name,
|
|
"full_name": profile.full_name,
|
|
"assigned_zones": [row.zone for row in (profile.get("assigned_zones") or []) if row.get("zone")],
|
|
"assigned_activity_types": assigned_activities
|
|
}
|
|
|