Label print API
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
from fastapi import FastAPI, Depends, HTTPException, APIRouter, status
|
from fastapi import FastAPI, Depends, HTTPException, APIRouter, status
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from typing import List, Optional
|
from typing import List, Optional, Dict, Any
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from .database import engine, get_db, Base
|
from .database import engine, get_db, Base
|
||||||
from .models import Drug, DrugVariant, Dispensing, User
|
from .models import Drug, DrugVariant, Dispensing, User
|
||||||
from .auth import hash_password, verify_password, create_access_token, get_current_user, get_current_admin_user, ACCESS_TOKEN_EXPIRE_MINUTES
|
from .auth import hash_password, verify_password, create_access_token, get_current_user, get_current_admin_user, ACCESS_TOKEN_EXPIRE_MINUTES
|
||||||
|
from .mqtt_service import publish_label_print
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
# Create tables
|
# Create tables
|
||||||
@@ -117,6 +118,21 @@ class DispensingResponse(BaseModel):
|
|||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
|
||||||
|
class LabelVariables(BaseModel):
|
||||||
|
practice_name: str
|
||||||
|
animal_name: str
|
||||||
|
drug_name: str
|
||||||
|
dosage: str
|
||||||
|
quantity: str
|
||||||
|
expiry_date: str
|
||||||
|
|
||||||
|
class LabelPrintRequest(BaseModel):
|
||||||
|
variables: LabelVariables
|
||||||
|
|
||||||
|
class LabelPrintResponse(BaseModel):
|
||||||
|
success: bool
|
||||||
|
message: str
|
||||||
|
|
||||||
# Authentication Routes
|
# Authentication Routes
|
||||||
@router.post("/auth/register", response_model=TokenResponse)
|
@router.post("/auth/register", response_model=TokenResponse)
|
||||||
def register(user_data: UserCreate, db: Session = Depends(get_db)):
|
def register(user_data: UserCreate, db: Session = Depends(get_db)):
|
||||||
@@ -475,5 +491,49 @@ def get_variant_dispensings(variant_id: int, db: Session = Depends(get_db), curr
|
|||||||
|
|
||||||
return db.query(Dispensing).filter(Dispensing.drug_variant_id == variant_id).order_by(Dispensing.dispensed_at.desc()).all()
|
return db.query(Dispensing).filter(Dispensing.drug_variant_id == variant_id).order_by(Dispensing.dispensed_at.desc()).all()
|
||||||
|
|
||||||
|
# Label printing endpoint
|
||||||
|
@router.post("/labels/print", response_model=LabelPrintResponse)
|
||||||
|
def print_label(label_request: LabelPrintRequest, current_user: User = Depends(get_current_user)):
|
||||||
|
"""
|
||||||
|
Print a drug label by publishing an MQTT message
|
||||||
|
|
||||||
|
This endpoint publishes a label print request to the MQTT broker,
|
||||||
|
which will be picked up by the label printing service.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get label configuration from environment
|
||||||
|
import os
|
||||||
|
template_id = os.getenv("LABEL_TEMPLATE_ID", "vet_label")
|
||||||
|
label_size = os.getenv("LABEL_SIZE", "29x90")
|
||||||
|
test_mode = os.getenv("LABEL_TEST", "false").lower() == "true"
|
||||||
|
|
||||||
|
# Convert the request to the MQTT message format
|
||||||
|
mqtt_message = {
|
||||||
|
"template_id": template_id,
|
||||||
|
"label_size": label_size,
|
||||||
|
"variables": label_request.variables.dict(),
|
||||||
|
"test": test_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
# Publish to MQTT
|
||||||
|
success = publish_label_print(mqtt_message)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
return LabelPrintResponse(
|
||||||
|
success=True,
|
||||||
|
message="Label print request sent successfully"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail="Failed to send label print request to MQTT broker"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Error sending label print request: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
# Include router with /api prefix
|
# Include router with /api prefix
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
|
|||||||
57
backend/app/mqtt_service.py
Normal file
57
backend/app/mqtt_service.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# MQTT Configuration from environment
|
||||||
|
MQTT_BROKER_HOST = os.getenv("MQTT_BROKER_HOST", "localhost")
|
||||||
|
MQTT_BROKER_PORT = int(os.getenv("MQTT_BROKER_PORT", "1883"))
|
||||||
|
MQTT_USERNAME = os.getenv("MQTT_USERNAME", "")
|
||||||
|
MQTT_PASSWORD = os.getenv("MQTT_PASSWORD", "")
|
||||||
|
MQTT_LABEL_TOPIC = os.getenv("MQTT_LABEL_TOPIC", "vet/labels/print")
|
||||||
|
|
||||||
|
def publish_label_print(label_data: dict) -> bool:
|
||||||
|
"""
|
||||||
|
Publish a label print request to MQTT broker
|
||||||
|
|
||||||
|
Args:
|
||||||
|
label_data: Dictionary containing label print information
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Create MQTT client
|
||||||
|
client = mqtt.Client()
|
||||||
|
|
||||||
|
# Set username and password if provided
|
||||||
|
if MQTT_USERNAME and MQTT_PASSWORD:
|
||||||
|
client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
|
||||||
|
|
||||||
|
# Connect to broker
|
||||||
|
client.connect(MQTT_BROKER_HOST, MQTT_BROKER_PORT, 60)
|
||||||
|
|
||||||
|
# Start network loop to process connection
|
||||||
|
client.loop_start()
|
||||||
|
|
||||||
|
# Publish message with QoS 0 (fire and forget)
|
||||||
|
message = json.dumps(label_data)
|
||||||
|
result = client.publish(MQTT_LABEL_TOPIC, message, qos=0)
|
||||||
|
|
||||||
|
# Stop loop and disconnect
|
||||||
|
client.loop_stop()
|
||||||
|
client.disconnect()
|
||||||
|
|
||||||
|
if result.rc == mqtt.MQTT_ERR_SUCCESS:
|
||||||
|
logger.info(f"Successfully published label print request to {MQTT_LABEL_TOPIC}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.error(f"Failed to publish label print request: {result.rc}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error publishing MQTT message: {str(e)}")
|
||||||
|
return False
|
||||||
@@ -5,3 +5,4 @@ pydantic==2.5.0
|
|||||||
python-multipart==0.0.6
|
python-multipart==0.0.6
|
||||||
python-jose[cryptography]==3.3.0
|
python-jose[cryptography]==3.3.0
|
||||||
passlib[argon2]==1.7.4
|
passlib[argon2]==1.7.4
|
||||||
|
paho-mqtt==1.6.1
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ services:
|
|||||||
- DATABASE_URL=sqlite:///./data/drugs.db
|
- DATABASE_URL=sqlite:///./data/drugs.db
|
||||||
- PUID=1001
|
- PUID=1001
|
||||||
- PGID=1001
|
- PGID=1001
|
||||||
|
- MQTT_BROKER_HOST=${MQTT_BROKER_HOST:-localhost}
|
||||||
|
- MQTT_BROKER_PORT=${MQTT_BROKER_PORT:-1883}
|
||||||
|
- MQTT_USERNAME=${MQTT_USERNAME:-}
|
||||||
|
- MQTT_PASSWORD=${MQTT_PASSWORD:-}
|
||||||
|
- MQTT_LABEL_TOPIC=${MQTT_LABEL_TOPIC:-vet/labels/print}
|
||||||
|
- LABEL_TEMPLATE_ID=${LABEL_TEMPLATE_ID:-vet_label}
|
||||||
|
- LABEL_SIZE=${LABEL_SIZE:-29x90}
|
||||||
|
- LABEL_TEST=${LABEL_TEST:-false}
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: nginx:alpine
|
image: nginx:alpine
|
||||||
|
|||||||
@@ -324,6 +324,11 @@ function renderDrugs() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort alphabetically by drug name
|
||||||
|
drugsToShow = drugsToShow.sort((a, b) =>
|
||||||
|
a.name.toLowerCase().localeCompare(b.name.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
if (drugsToShow.length === 0) {
|
if (drugsToShow.length === 0) {
|
||||||
drugsList.innerHTML = '<p class="empty">No drugs found matching your criteria</p>';
|
drugsList.innerHTML = '<p class="empty">No drugs found matching your criteria</p>';
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user