Add custom fields + warranty plugin, rework settings into two-pane shell
Feature work from the 2026-07 session: Settings IA - Replace the flat 27-card settings hub with a persistent two-pane shell (SettingsLayout.vue): grouped, searchable left rail + content pane. - Nest all settings/* routes under the shell via router post-processing; shared nav catalog in settingsNav.js. Group by asset class (PCs, Printers, Equipment, Network) so per-type settings stop scattering. Custom fields (core) - customfields + customfieldvalues tables (migration 7d14), CRUD API at /api/customfields, per-asset value get/save. - Settings management page + reusable CustomFieldsSection (detail) and CustomFieldsInputs (form) wired into all four asset types. Warranty (new plugin) - plugins/warranty: warranties + warrantyassets (migration 7d15), derived coverage status, provider abstraction (manual now; Dell/Lenovo/HP stubs). - API CRUD + per-asset panel + report buckets; WarrantyPanel on all four detail pages; Warranties management page; Warranty report + Reports card. - Seed warranty.* permissions. Printer drivers - printerdrivers table (migration 7d13) linked to printer models; drivers now surface on the matching printer's detail page. Other - PCDetail rebalanced (Network + Status + Warranty + custom fields on the right). - Rename PCs list "Features" column to "Remote Access"; fix badge hover underline. - Drop equipment islocationonly field. - Centralize asset-type label/route maps into utils/assetTypes.js. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,8 @@ from shopdb.api import db, Asset, AssetType, Vendor, Model, AuditLog, success_re
|
||||
|
||||
from ..models import Equipment, EquipmentType
|
||||
|
||||
from shopdb.api import require_permission, require_role
|
||||
|
||||
equipment_bp = Blueprint('equipment', __name__)
|
||||
|
||||
|
||||
@@ -54,6 +56,7 @@ def get_equipment_type(type_id: int):
|
||||
|
||||
@equipment_bp.route('/types', methods=['POST'])
|
||||
@jwt_required()
|
||||
@require_permission('equipment.create')
|
||||
def create_equipment_type():
|
||||
"""Create a new equipment type."""
|
||||
data = request.get_json()
|
||||
@@ -61,7 +64,15 @@ def create_equipment_type():
|
||||
if not data or not data.get('equipmenttype'):
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'equipmenttype is required')
|
||||
|
||||
if EquipmentType.query.filter_by(equipmenttype=data['equipmenttype']).first():
|
||||
existing = EquipmentType.query.filter_by(equipmenttype=data['equipmenttype']).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"Equipment type '{data['equipmenttype']}' already exists",
|
||||
@@ -71,7 +82,7 @@ def create_equipment_type():
|
||||
t = EquipmentType(
|
||||
equipmenttype=data['equipmenttype'],
|
||||
description=data.get('description'),
|
||||
icon=data.get('icon')
|
||||
icon=data.get('icon'), color=data.get('color')
|
||||
)
|
||||
|
||||
db.session.add(t)
|
||||
@@ -82,6 +93,7 @@ def create_equipment_type():
|
||||
|
||||
@equipment_bp.route('/types/<int:type_id>', methods=['PUT'])
|
||||
@jwt_required()
|
||||
@require_permission('equipment.edit')
|
||||
def update_equipment_type(type_id: int):
|
||||
"""Update an equipment type."""
|
||||
t = EquipmentType.query.get(type_id)
|
||||
@@ -105,7 +117,7 @@ def update_equipment_type(type_id: int):
|
||||
http_code=409
|
||||
)
|
||||
|
||||
for key in ['equipmenttype', 'description', 'icon', 'isactive']:
|
||||
for key in ['equipmenttype', 'description', 'icon', 'color', 'isactive']:
|
||||
if key in data:
|
||||
setattr(t, key, data[key])
|
||||
|
||||
@@ -113,6 +125,23 @@ def update_equipment_type(type_id: int):
|
||||
return success_response(t.to_dict(), message='Equipment type updated')
|
||||
|
||||
|
||||
@equipment_bp.route('/types/<int:type_id>', methods=['DELETE'])
|
||||
@jwt_required()
|
||||
@require_permission('equipment.delete')
|
||||
def delete_equipment_type(type_id: int):
|
||||
"""Delete an equipment type. Refused if any asset still uses it."""
|
||||
t = EquipmentType.query.get(type_id)
|
||||
if not t:
|
||||
return error_response(ErrorCodes.NOT_FOUND, 'Equipment type not found', http_code=404)
|
||||
inuse = Equipment.query.filter_by(equipmenttypeid=type_id).count()
|
||||
if inuse:
|
||||
return error_response(ErrorCodes.CONFLICT,
|
||||
f"Cannot delete: {inuse} asset(s) still use this type", http_code=409)
|
||||
db.session.delete(t)
|
||||
db.session.commit()
|
||||
return success_response(message='Equipment type deleted')
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Equipment CRUD
|
||||
# =============================================================================
|
||||
@@ -232,6 +261,7 @@ def get_equipment_by_asset(asset_id: int):
|
||||
|
||||
@equipment_bp.route('', methods=['POST'])
|
||||
@jwt_required()
|
||||
@require_permission('equipment.create')
|
||||
def create_equipment():
|
||||
"""
|
||||
Create new equipment (creates both Asset and Equipment records).
|
||||
@@ -321,6 +351,7 @@ def create_equipment():
|
||||
|
||||
@equipment_bp.route('/<int:equipment_id>', methods=['PUT'])
|
||||
@jwt_required()
|
||||
@require_permission('equipment.edit')
|
||||
def update_equipment(equipment_id: int):
|
||||
"""Update equipment (both Asset and Equipment records)."""
|
||||
equip = Equipment.query.get(equipment_id)
|
||||
@@ -391,6 +422,7 @@ def update_equipment(equipment_id: int):
|
||||
|
||||
@equipment_bp.route('/<int:equipment_id>', methods=['DELETE'])
|
||||
@jwt_required()
|
||||
@require_permission('equipment.delete')
|
||||
def delete_equipment(equipment_id: int):
|
||||
"""Delete (soft delete) equipment."""
|
||||
equip = Equipment.query.get(equipment_id)
|
||||
|
||||
Reference in New Issue
Block a user