Complete USB self-hosted mode (app tables, no external cmmc_usb)
"Create the tables here" for USB now actually works. Add selfhosted.py backing every USB endpoint with the app-owned usbdevices/usbcheckouts tables and returning the same response shape as the external cmmc_usb path. Each route in routes.py delegates to it when usb_directory_mode=selfhosted (the default). - Device status derives from ischeckedout + isactive (retired); check-in/out history is synthesized from usbcheckouts rows (check-out + check-in events); holder names resolve from the self-hosted employee directory. - External cmmc_usb mode unchanged, for sites already running that solution. Verified E2E: create -> list -> checkout -> checkin -> history all correct against the app tables. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
"""USB plugin API endpoints.
|
||||
|
||||
Check-in/out state lives in a separate MySQL database (cmmc_usb), reached with
|
||||
raw parameterized pymysql via cmmc_usb_connection(), mirroring the read-only
|
||||
employee-directory helper pattern. The SQLAlchemy USBDevice/USBCheckout/
|
||||
USBDeviceType models in ../models are NOT used by these routes anymore; they
|
||||
are left in place for any legacy importers.
|
||||
Two modes, chosen by the usb_directory_mode setting:
|
||||
- 'selfhosted' (default): the app-owned tables (usbdevices/usbcheckouts/
|
||||
usbdevicetypes) via SQLAlchemy - see selfhosted.py. Each route below
|
||||
delegates when _usb_selfhosted() is true.
|
||||
- 'external': a separate MySQL database (cmmc_usb) reached with raw
|
||||
parameterized pymysql via cmmc_usb_connection(), for a site that already
|
||||
runs the cmmc_usb solution. That is the raw-SQL code in this file.
|
||||
|
||||
Both modes return the same response shape so the frontend is identical.
|
||||
|
||||
cmmc_usb schema used here:
|
||||
devices(device_id PK, device_desc, device_owner, status, locker_location)
|
||||
@@ -33,11 +37,20 @@ from shopdb.api import (
|
||||
get_pagination_params,
|
||||
require_permission,
|
||||
)
|
||||
from shopdb.core.models import Setting
|
||||
|
||||
from . import selfhosted
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
usb_bp = Blueprint('usb', __name__)
|
||||
|
||||
|
||||
def _usb_selfhosted():
|
||||
"""True when USB uses the app-owned tables, not the external cmmc_usb DB."""
|
||||
row = Setting.query.filter_by(key='usb_directory_mode').first()
|
||||
return (row.value if row and row.value else 'external').lower() == 'selfhosted'
|
||||
|
||||
# public status strings on the devices row
|
||||
_STATUS_CHECKED_IN = 'checked-in'
|
||||
_STATUS_CHECKED_OUT = 'checked-out'
|
||||
@@ -253,6 +266,8 @@ def list_usb_devices():
|
||||
- status: available | checkedout | retired
|
||||
- search: match on device_id or device_desc
|
||||
"""
|
||||
if _usb_selfhosted():
|
||||
return selfhosted.list_devices(request)
|
||||
page, per_page = get_pagination_params(request)
|
||||
|
||||
where = []
|
||||
@@ -294,6 +309,8 @@ def list_usb_devices():
|
||||
@jwt_required(optional=True)
|
||||
def get_usb_device(device_id):
|
||||
"""Get a single device plus its last 20 check-in/out log rows."""
|
||||
if _usb_selfhosted():
|
||||
return selfhosted.get_device(device_id)
|
||||
conn = cmmc_usb_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
@@ -325,6 +342,8 @@ def get_usb_device(device_id):
|
||||
@require_permission('usb.create')
|
||||
def create_usb_device():
|
||||
"""Add a device (starts checked-in)."""
|
||||
if _usb_selfhosted():
|
||||
return selfhosted.create_device(request.get_json() or {})
|
||||
data = request.get_json() or {}
|
||||
|
||||
device_id = (data.get('device_id') or '').strip()
|
||||
@@ -370,6 +389,8 @@ def create_usb_device():
|
||||
@require_permission('usb.edit')
|
||||
def update_usb_device(device_id):
|
||||
"""Edit device_desc / device_owner / locker_location / status."""
|
||||
if _usb_selfhosted():
|
||||
return selfhosted.update_device(device_id, request.get_json() or {})
|
||||
data = request.get_json() or {}
|
||||
|
||||
conn = cmmc_usb_connection()
|
||||
@@ -411,6 +432,8 @@ def update_usb_device(device_id):
|
||||
@require_permission('usb.edit')
|
||||
def retire_usb_device(device_id):
|
||||
"""Retire a device (status -> retired)."""
|
||||
if _usb_selfhosted():
|
||||
return selfhosted.retire_device(device_id)
|
||||
conn = cmmc_usb_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
@@ -444,6 +467,8 @@ def retire_usb_device(device_id):
|
||||
@require_permission('usb.create')
|
||||
def checkout_device(device_id):
|
||||
"""Check a device out to a badge."""
|
||||
if _usb_selfhosted():
|
||||
return selfhosted.checkout_device(device_id, request.get_json() or {})
|
||||
data = request.get_json() or {}
|
||||
|
||||
badge = (data.get('badge') or '').strip()
|
||||
@@ -507,6 +532,8 @@ def checkout_device(device_id):
|
||||
@require_permission('usb.create')
|
||||
def checkin_device(device_id):
|
||||
"""Check a device back in from a badge."""
|
||||
if _usb_selfhosted():
|
||||
return selfhosted.checkin_device(device_id, request.get_json() or {})
|
||||
data = request.get_json() or {}
|
||||
|
||||
badge = (data.get('badge') or '').strip()
|
||||
@@ -576,6 +603,8 @@ def checkin_device(device_id):
|
||||
@jwt_required(optional=True)
|
||||
def get_device_history(device_id):
|
||||
"""Paginated check-in/out log for one device."""
|
||||
if _usb_selfhosted():
|
||||
return selfhosted.device_history(device_id, request)
|
||||
page, per_page = get_pagination_params(request)
|
||||
|
||||
conn = cmmc_usb_connection()
|
||||
@@ -609,6 +638,8 @@ def list_all_checkouts():
|
||||
- active: true -> only rows whose device is currently checked out
|
||||
- badge: filter by badge_number
|
||||
"""
|
||||
if _usb_selfhosted():
|
||||
return selfhosted.list_checkouts(request, active_only=False)
|
||||
page, per_page = get_pagination_params(request)
|
||||
|
||||
where = ["l.action = 'check-out'"]
|
||||
@@ -653,6 +684,8 @@ def list_all_checkouts():
|
||||
@jwt_required(optional=True)
|
||||
def list_active_checkouts():
|
||||
"""List the latest check-out log row for every currently checked-out device."""
|
||||
if _usb_selfhosted():
|
||||
return selfhosted.list_checkouts(request, active_only=True)
|
||||
conn = cmmc_usb_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
|
||||
Reference in New Issue
Block a user