|
|
|
|
@@ -5,9 +5,9 @@ import logging
|
|
|
|
|
from flask import Blueprint, request
|
|
|
|
|
from flask_jwt_extended import jwt_required
|
|
|
|
|
|
|
|
|
|
from shopdb.api import db, cache, Asset, AssetType, Vendor, Model, Communication, CommunicationType, success_response, error_response, paginated_response, ErrorCodes, get_pagination_params, paginate_query
|
|
|
|
|
from shopdb.api import db, cache, Asset, AssetType, Vendor, Model, Communication, CommunicationType, AssetRelationship, RelationshipType, success_response, error_response, paginated_response, ErrorCodes, get_pagination_params, paginate_query
|
|
|
|
|
|
|
|
|
|
from ..models import Printer, PrinterType, ModelSupply
|
|
|
|
|
from ..models import Printer, PrinterType, ModelSupply, PrinterDriver
|
|
|
|
|
from ..models.model_supply import SUPPLY_TYPES, SUPPLY_COLORS, CAPACITY_TIERS
|
|
|
|
|
from ..services import (
|
|
|
|
|
ZabbixService,
|
|
|
|
|
@@ -19,6 +19,8 @@ from ..services import (
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
from shopdb.api import require_permission, require_role
|
|
|
|
|
|
|
|
|
|
printers_asset_bp = Blueprint('printers_asset', __name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -66,6 +68,7 @@ def get_printer_type(type_id: int):
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('/types', methods=['POST'])
|
|
|
|
|
@jwt_required()
|
|
|
|
|
@require_permission('printers.create')
|
|
|
|
|
def create_printer_type():
|
|
|
|
|
"""Create a new printer type."""
|
|
|
|
|
data = request.get_json()
|
|
|
|
|
@@ -73,7 +76,15 @@ def create_printer_type():
|
|
|
|
|
if not data or not data.get('printertype'):
|
|
|
|
|
return error_response(ErrorCodes.VALIDATION_ERROR, 'printertype is required')
|
|
|
|
|
|
|
|
|
|
if PrinterType.query.filter_by(printertype=data['printertype']).first():
|
|
|
|
|
existing = PrinterType.query.filter_by(printertype=data['printertype']).first()
|
|
|
|
|
if existing:
|
|
|
|
|
if not existing.isactive:
|
|
|
|
|
existing.isactive = True
|
|
|
|
|
for key in ('description', 'icon', 'color'):
|
|
|
|
|
if data.get(key) is not None:
|
|
|
|
|
setattr(existing, key, data[key])
|
|
|
|
|
db.session.commit()
|
|
|
|
|
return success_response(existing.to_dict(), message='Reactivated existing type')
|
|
|
|
|
return error_response(
|
|
|
|
|
ErrorCodes.CONFLICT,
|
|
|
|
|
f"Printer type '{data['printertype']}' already exists",
|
|
|
|
|
@@ -83,7 +94,7 @@ def create_printer_type():
|
|
|
|
|
t = PrinterType(
|
|
|
|
|
printertype=data['printertype'],
|
|
|
|
|
description=data.get('description'),
|
|
|
|
|
icon=data.get('icon')
|
|
|
|
|
icon=data.get('icon'), color=data.get('color')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
db.session.add(t)
|
|
|
|
|
@@ -92,6 +103,110 @@ def create_printer_type():
|
|
|
|
|
return success_response(t.to_dict(), message='Printer type created', http_code=201)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('/types/<int:type_id>', methods=['PUT'])
|
|
|
|
|
@jwt_required()
|
|
|
|
|
@require_permission('printers.edit')
|
|
|
|
|
def update_printer_type(type_id: int):
|
|
|
|
|
"""Update a printer type."""
|
|
|
|
|
t = PrinterType.query.get(type_id)
|
|
|
|
|
if not t:
|
|
|
|
|
return error_response(ErrorCodes.NOT_FOUND,
|
|
|
|
|
f'Printer type with ID {type_id} not found', http_code=404)
|
|
|
|
|
|
|
|
|
|
data = request.get_json() or {}
|
|
|
|
|
if 'printertype' in data and data['printertype'] != t.printertype:
|
|
|
|
|
if PrinterType.query.filter_by(printertype=data['printertype']).first():
|
|
|
|
|
return error_response(ErrorCodes.CONFLICT,
|
|
|
|
|
f"Printer type '{data['printertype']}' already exists", http_code=409)
|
|
|
|
|
|
|
|
|
|
for key in ['printertype', 'description', 'icon', 'color', 'isactive']:
|
|
|
|
|
if key in data:
|
|
|
|
|
setattr(t, key, data[key])
|
|
|
|
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
return success_response(t.to_dict(), message='Printer type updated')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('/types/<int:type_id>', methods=['DELETE'])
|
|
|
|
|
@jwt_required()
|
|
|
|
|
@require_permission('printers.delete')
|
|
|
|
|
def delete_printer_type(type_id: int):
|
|
|
|
|
"""Delete a printer type. Refused if any printer still uses it."""
|
|
|
|
|
t = PrinterType.query.get(type_id)
|
|
|
|
|
if not t:
|
|
|
|
|
return error_response(ErrorCodes.NOT_FOUND, 'Printer type not found', http_code=404)
|
|
|
|
|
inuse = Printer.query.filter_by(printertypeid=type_id).count()
|
|
|
|
|
if inuse:
|
|
|
|
|
return error_response(ErrorCodes.CONFLICT,
|
|
|
|
|
f"Cannot delete: {inuse} printer(s) still use this type", http_code=409)
|
|
|
|
|
db.session.delete(t)
|
|
|
|
|
db.session.commit()
|
|
|
|
|
return success_response(message='Printer type deleted')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Printer Drivers (named SMB / HTTP links to driver packages)
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('/drivers', methods=['GET'])
|
|
|
|
|
@jwt_required(optional=True)
|
|
|
|
|
def list_drivers():
|
|
|
|
|
"""List printer drivers. ?active=false includes inactive ones."""
|
|
|
|
|
query = PrinterDriver.query
|
|
|
|
|
if request.args.get('active', 'true').lower() != 'false':
|
|
|
|
|
query = query.filter_by(isactive=True)
|
|
|
|
|
drivers = query.order_by(PrinterDriver.name).all()
|
|
|
|
|
return success_response([d.to_dict() for d in drivers])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('/drivers', methods=['POST'])
|
|
|
|
|
@jwt_required()
|
|
|
|
|
@require_permission('printers.create')
|
|
|
|
|
def create_driver():
|
|
|
|
|
data = request.get_json() or {}
|
|
|
|
|
if not (data.get('name') and data.get('location')):
|
|
|
|
|
return error_response(ErrorCodes.VALIDATION_ERROR, 'name and location are required')
|
|
|
|
|
d = PrinterDriver(
|
|
|
|
|
name=data['name'],
|
|
|
|
|
location=data['location'],
|
|
|
|
|
description=data.get('description'),
|
|
|
|
|
modelnumberid=data.get('modelnumberid') or None,
|
|
|
|
|
isactive=data.get('isactive', True),
|
|
|
|
|
)
|
|
|
|
|
db.session.add(d)
|
|
|
|
|
db.session.commit()
|
|
|
|
|
return success_response(d.to_dict(), message='Driver created', http_code=201)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('/drivers/<int:driver_id>', methods=['PUT'])
|
|
|
|
|
@jwt_required()
|
|
|
|
|
@require_permission('printers.edit')
|
|
|
|
|
def update_driver(driver_id):
|
|
|
|
|
d = PrinterDriver.query.get(driver_id)
|
|
|
|
|
if not d:
|
|
|
|
|
return error_response(ErrorCodes.NOT_FOUND, 'Driver not found', http_code=404)
|
|
|
|
|
data = request.get_json() or {}
|
|
|
|
|
for key in ('name', 'location', 'description', 'isactive'):
|
|
|
|
|
if key in data:
|
|
|
|
|
setattr(d, key, data[key])
|
|
|
|
|
if 'modelnumberid' in data:
|
|
|
|
|
d.modelnumberid = data['modelnumberid'] or None
|
|
|
|
|
db.session.commit()
|
|
|
|
|
return success_response(d.to_dict(), message='Driver updated')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('/drivers/<int:driver_id>', methods=['DELETE'])
|
|
|
|
|
@jwt_required()
|
|
|
|
|
@require_permission('printers.delete')
|
|
|
|
|
def delete_driver(driver_id):
|
|
|
|
|
d = PrinterDriver.query.get(driver_id)
|
|
|
|
|
if not d:
|
|
|
|
|
return error_response(ErrorCodes.NOT_FOUND, 'Driver not found', http_code=404)
|
|
|
|
|
db.session.delete(d)
|
|
|
|
|
db.session.commit()
|
|
|
|
|
return success_response(message='Driver deleted')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Printers CRUD
|
|
|
|
|
# =============================================================================
|
|
|
|
|
@@ -235,6 +350,50 @@ def printer_install_list():
|
|
|
|
|
return success_response(rows)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('/pc-default', methods=['GET'])
|
|
|
|
|
@jwt_required(optional=True)
|
|
|
|
|
def pc_default_printer():
|
|
|
|
|
"""Default printer for a PC, by machine (asset) number.
|
|
|
|
|
|
|
|
|
|
Parity with classic apipcdefaultprinter.asp: the signed installer EXE
|
|
|
|
|
preselects a PC's default-printer hotspot on the site-map wizard using the
|
|
|
|
|
machine number persisted at PXE enrollment. The link is a `defaultprinter`
|
|
|
|
|
asset relationship (PC asset -> printer asset), so this stays inside the
|
|
|
|
|
contract surface (no cross-plugin model import).
|
|
|
|
|
|
|
|
|
|
Returns {printerid, windowsname}, or {} when the machine is unknown or has
|
|
|
|
|
no active default printer set.
|
|
|
|
|
"""
|
|
|
|
|
machine = (request.args.get('machine') or '').strip()
|
|
|
|
|
if not machine:
|
|
|
|
|
return success_response({})
|
|
|
|
|
|
|
|
|
|
pc = Asset.query.filter_by(assetnumber=machine, isactive=True).first()
|
|
|
|
|
dp_type = RelationshipType.query.filter_by(relationshiptype='defaultprinter').first()
|
|
|
|
|
if not pc or not dp_type:
|
|
|
|
|
return success_response({})
|
|
|
|
|
|
|
|
|
|
rel = AssetRelationship.query.filter_by(
|
|
|
|
|
sourceassetid=pc.assetid,
|
|
|
|
|
relationshiptypeid=dp_type.relationshiptypeid,
|
|
|
|
|
isactive=True,
|
|
|
|
|
).first()
|
|
|
|
|
if not rel:
|
|
|
|
|
return success_response({})
|
|
|
|
|
|
|
|
|
|
printer = db.session.query(Printer).join(Asset).filter(
|
|
|
|
|
Printer.assetid == rel.targetassetid,
|
|
|
|
|
Asset.isactive == True,
|
|
|
|
|
).first()
|
|
|
|
|
if not printer:
|
|
|
|
|
return success_response({})
|
|
|
|
|
|
|
|
|
|
return success_response({
|
|
|
|
|
'printerid': printer.printerid,
|
|
|
|
|
'windowsname': printer.windowsname,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('/<int:printer_id>', methods=['GET'])
|
|
|
|
|
@jwt_required(optional=True)
|
|
|
|
|
def get_printer(printer_id: int):
|
|
|
|
|
@@ -256,6 +415,15 @@ def get_printer(printer_id: int):
|
|
|
|
|
comms = Communication.query.filter_by(assetid=printer.asset.assetid).all()
|
|
|
|
|
result['communications'] = [c.to_dict() for c in comms]
|
|
|
|
|
|
|
|
|
|
# Attach active drivers that match this printer's model
|
|
|
|
|
if printer.modelnumberid:
|
|
|
|
|
drivers = PrinterDriver.query.filter_by(
|
|
|
|
|
modelnumberid=printer.modelnumberid, isactive=True
|
|
|
|
|
).order_by(PrinterDriver.name).all()
|
|
|
|
|
result['drivers'] = [d.to_dict() for d in drivers]
|
|
|
|
|
else:
|
|
|
|
|
result['drivers'] = []
|
|
|
|
|
|
|
|
|
|
return success_response(result)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -280,6 +448,7 @@ def get_printer_by_asset(asset_id: int):
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('', methods=['POST'])
|
|
|
|
|
@jwt_required()
|
|
|
|
|
@require_permission('printers.create')
|
|
|
|
|
def create_printer():
|
|
|
|
|
"""
|
|
|
|
|
Create new printer (creates both Asset and Printer records).
|
|
|
|
|
@@ -379,6 +548,7 @@ def create_printer():
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('/<int:printer_id>', methods=['PUT'])
|
|
|
|
|
@jwt_required()
|
|
|
|
|
@require_permission('printers.edit')
|
|
|
|
|
def update_printer(printer_id: int):
|
|
|
|
|
"""Update printer (both Asset and Printer records)."""
|
|
|
|
|
printer = Printer.query.get(printer_id)
|
|
|
|
|
@@ -453,6 +623,7 @@ def update_printer(printer_id: int):
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('/<int:printer_id>', methods=['DELETE'])
|
|
|
|
|
@jwt_required()
|
|
|
|
|
@require_permission('printers.delete')
|
|
|
|
|
def delete_printer(printer_id: int):
|
|
|
|
|
"""Delete (soft delete) printer."""
|
|
|
|
|
printer = Printer.query.get(printer_id)
|
|
|
|
|
@@ -697,6 +868,7 @@ def printer_lookup():
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('/supplies/refresh', methods=['POST'])
|
|
|
|
|
@jwt_required()
|
|
|
|
|
@require_permission('printers.create')
|
|
|
|
|
def refresh_supplies_cache():
|
|
|
|
|
"""Clear cached Zabbix supply data so the next read pulls fresh values.
|
|
|
|
|
|
|
|
|
|
@@ -878,6 +1050,7 @@ def list_model_supplies(modelnumberid: int):
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('/models/<int:modelnumberid>/supplies', methods=['POST'])
|
|
|
|
|
@jwt_required()
|
|
|
|
|
@require_permission('printers.create')
|
|
|
|
|
def create_model_supply(modelnumberid: int):
|
|
|
|
|
"""Add a supply to a model."""
|
|
|
|
|
model = Model.query.get(modelnumberid)
|
|
|
|
|
@@ -918,6 +1091,7 @@ def create_model_supply(modelnumberid: int):
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('/supplies/<int:modelsupplyid>', methods=['PUT'])
|
|
|
|
|
@jwt_required()
|
|
|
|
|
@require_permission('printers.edit')
|
|
|
|
|
def update_model_supply(modelsupplyid: int):
|
|
|
|
|
"""Update a model supply."""
|
|
|
|
|
supply = ModelSupply.query.get(modelsupplyid)
|
|
|
|
|
@@ -962,6 +1136,7 @@ def update_model_supply(modelsupplyid: int):
|
|
|
|
|
|
|
|
|
|
@printers_asset_bp.route('/supplies/<int:modelsupplyid>', methods=['DELETE'])
|
|
|
|
|
@jwt_required()
|
|
|
|
|
@require_permission('printers.delete')
|
|
|
|
|
def delete_model_supply(modelsupplyid: int):
|
|
|
|
|
"""Delete a model supply."""
|
|
|
|
|
supply = ModelSupply.query.get(modelsupplyid)
|
|
|
|
|
|