""" Inventory app admin configuration. """ from django.contrib import admin from django.utils.html import format_html from django.urls import reverse from django.utils.safestring import mark_safe from decimal import Decimal from .models import ( InventoryItem, InventoryStock, InventoryLocation, PurchaseOrder, PurchaseOrderItem, Supplier ) class InventoryStockInline(admin.TabularInline): """ Inline admin for inventory stock. """ model = InventoryStock extra = 0 fields = [ 'location', 'lot_number', 'quantity_on_hand', 'quantity_reserved', 'expiration_date', 'quality_status' ] readonly_fields = ['quantity_available'] class PurchaseOrderItemInline(admin.TabularInline): """ Inline admin for purchase order items. """ model = PurchaseOrderItem extra = 0 fields = [ 'line_number', 'inventory_item', 'quantity_ordered', 'quantity_received', 'unit_price', 'total_price' ] readonly_fields = ['total_price', 'quantity_remaining'] @admin.register(InventoryItem) class InventoryItemAdmin(admin.ModelAdmin): """ Admin interface for inventory items. """ list_display = [ 'item_code', 'item_name', 'category', 'item_type', 'manufacturer', 'unit_cost', 'current_stock_display', 'reorder_point', 'needs_reorder_display', 'is_active' ] list_filter = [ 'tenant', 'category', 'item_type', 'manufacturer', 'is_active', 'is_tracked', 'has_expiration', 'controlled_substance' ] search_fields = [ 'item_code', 'item_name', 'description', 'manufacturer', 'model_number', 'part_number', 'upc_code', 'ndc_code' ] readonly_fields = [ 'item_id', 'current_stock', 'total_value', 'needs_reorder', 'created_at', 'updated_at' ] fieldsets = [ ('Item Information', { 'fields': [ 'item_id', 'tenant', 'item_code', 'item_name', 'description' ] }), ('Classification', { 'fields': [ 'category', 'subcategory', 'item_type' ] }), ('Manufacturer Information', { 'fields': [ 'manufacturer', 'model_number', 'part_number' ] }), ('Identification Codes', { 'fields': [ 'upc_code', 'ndc_code', 'gtin_code' ], 'classes': ['collapse'] }), ('Unit and Packaging', { 'fields': [ 'unit_of_measure', 'package_size', 'package_type' ] }), ('Pricing', { 'fields': [ 'unit_cost', 'list_price' ] }), ('Storage Requirements', { 'fields': [ 'storage_temperature_min', 'storage_temperature_max', 'storage_humidity_min', 'storage_humidity_max', 'storage_requirements' ], 'classes': ['collapse'] }), ('Expiration Information', { 'fields': [ 'has_expiration', 'shelf_life_days' ] }), ('Regulatory Information', { 'fields': [ 'fda_approved', 'controlled_substance', 'dea_schedule' ], 'classes': ['collapse'] }), ('Inventory Management', { 'fields': [ 'is_active', 'is_tracked', 'is_serialized', 'is_lot_tracked' ] }), ('Reorder Information', { 'fields': [ 'reorder_point', 'reorder_quantity', 'min_stock_level', 'max_stock_level' ] }), ('Supplier Information', { 'fields': [ 'primary_supplier' ] }), ('Clinical Information', { 'fields': [ 'clinical_use', 'contraindications' ], 'classes': ['collapse'] }), ('Stock Information', { 'fields': [ 'current_stock', 'total_value', 'needs_reorder' ], 'classes': ['collapse'] }), ('Notes', { 'fields': [ 'notes' ], 'classes': ['collapse'] }), ('Metadata', { 'fields': [ 'created_at', 'updated_at', 'created_by' ], 'classes': ['collapse'] }) ] inlines = [InventoryStockInline] date_hierarchy = 'created_at' def current_stock_display(self, obj): """Display current stock with color coding.""" stock = obj.current_stock if stock <= obj.reorder_point: color = 'red' elif stock <= obj.reorder_point * 1.5: color = 'orange' else: color = 'green' return format_html( '{}', color, stock ) current_stock_display.short_description = 'Current Stock' def needs_reorder_display(self, obj): """Display reorder status with icon.""" if obj.needs_reorder: return format_html( '⚠️ Yes' ) return format_html( '✓ No' ) needs_reorder_display.short_description = 'Needs Reorder' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(tenant=request.user.tenant) return qs.select_related('primary_supplier') @admin.register(InventoryStock) class InventoryStockAdmin(admin.ModelAdmin): """ Admin interface for inventory stock. """ list_display = [ 'item_name', 'location_name', 'lot_number', 'quantity_on_hand', 'quantity_reserved', 'quantity_available', 'expiration_date', 'days_to_expiry_display', 'quality_status' ] list_filter = [ 'inventory_item__tenant', 'location', 'quality_status', 'expiration_date', 'received_date' ] search_fields = [ 'inventory_item__item_name', 'inventory_item__item_code', 'location__name', 'lot_number', 'serial_number' ] readonly_fields = [ 'stock_id', 'quantity_available', 'total_cost', 'tenant', 'is_expired', 'days_to_expiry', 'created_at', 'updated_at' ] fieldsets = [ ('Stock Information', { 'fields': [ 'stock_id', 'inventory_item', 'location' ] }), ('Lot Information', { 'fields': [ 'lot_number', 'serial_number' ] }), ('Quantity Information', { 'fields': [ 'quantity_on_hand', 'quantity_reserved', 'quantity_available' ] }), ('Dates', { 'fields': [ 'received_date', 'expiration_date' ] }), ('Cost Information', { 'fields': [ 'unit_cost', 'total_cost' ] }), ('Quality Information', { 'fields': [ 'quality_status' ] }), ('Supplier Information', { 'fields': [ 'supplier', 'purchase_order' ] }), ('Expiration Status', { 'fields': [ 'is_expired', 'days_to_expiry' ], 'classes': ['collapse'] }), ('Notes', { 'fields': [ 'notes' ], 'classes': ['collapse'] }), ('Related Information', { 'fields': [ 'tenant' ], 'classes': ['collapse'] }), ('Metadata', { 'fields': [ 'created_at', 'updated_at' ], 'classes': ['collapse'] }) ] date_hierarchy = 'expiration_date' def item_name(self, obj): """Display item name.""" return obj.inventory_item.item_name item_name.short_description = 'Item' def location_name(self, obj): """Display location name.""" return obj.location.name location_name.short_description = 'Location' def days_to_expiry_display(self, obj): """Display days to expiry with color coding.""" days = obj.days_to_expiry if days is None: return "-" if days < 0: color = 'red' text = f"Expired {abs(days)} days ago" elif days <= 30: color = 'red' text = f"{days} days" elif days <= 90: color = 'orange' text = f"{days} days" else: color = 'green' text = f"{days} days" return format_html( '{}', color, text ) days_to_expiry_display.short_description = 'Days to Expiry' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(inventory_item__tenant=request.user.tenant) return qs.select_related('inventory_item', 'location', 'supplier') @admin.register(InventoryLocation) class InventoryLocationAdmin(admin.ModelAdmin): """ Admin interface for inventory locations. """ list_display = [ 'location_code', 'name', 'location_type', 'building', 'floor', 'room', 'total_items_display', 'total_quantity_display', 'is_active' ] list_filter = [ 'tenant', 'location_type', 'building', 'floor', 'temperature_controlled', 'humidity_controlled', 'secure_location', 'is_active' ] search_fields = [ 'location_code', 'name', 'description', 'building', 'room', 'zone' ] readonly_fields = [ 'location_id', 'full_address', 'total_items', 'total_quantity', 'created_at', 'updated_at' ] fieldsets = [ ('Location Information', { 'fields': [ 'location_id', 'tenant', 'location_code', 'name', 'description' ] }), ('Location Type', { 'fields': [ 'location_type' ] }), ('Physical Information', { 'fields': [ 'building', 'floor', 'room', 'zone', 'aisle', 'shelf', 'bin' ] }), ('Capacity Information', { 'fields': [ 'capacity_cubic_feet', 'max_weight_pounds' ], 'classes': ['collapse'] }), ('Environmental Controls', { 'fields': [ 'temperature_controlled', 'temperature_min', 'temperature_max', 'humidity_controlled', 'humidity_min', 'humidity_max' ] }), ('Security and Access', { 'fields': [ 'secure_location', 'access_control' ] }), ('Location Status', { 'fields': [ 'is_active' ] }), ('Hierarchy', { 'fields': [ 'parent_location' ], 'classes': ['collapse'] }), ('Staff', { 'fields': [ 'location_manager' ] }), ('Summary Information', { 'fields': [ 'full_address', 'total_items', 'total_quantity' ], 'classes': ['collapse'] }), ('Notes', { 'fields': [ 'notes' ], 'classes': ['collapse'] }), ('Metadata', { 'fields': [ 'created_at', 'updated_at', 'created_by' ], 'classes': ['collapse'] }) ] def total_items_display(self, obj): """Display total items count.""" return obj.total_items total_items_display.short_description = 'Items' def total_quantity_display(self, obj): """Display total quantity.""" return obj.total_quantity total_quantity_display.short_description = 'Total Qty' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(tenant=request.user.tenant) return qs.select_related('location_manager') @admin.register(PurchaseOrder) class PurchaseOrderAdmin(admin.ModelAdmin): """ Admin interface for purchase orders. """ list_display = [ 'po_number', 'supplier_name', 'order_date', 'total_amount', 'status', 'requested_delivery_date', 'days_outstanding_display', 'is_overdue_display' ] list_filter = [ 'tenant', 'supplier', 'status', 'order_type', 'priority', 'order_date', 'requested_delivery_date' ] search_fields = [ 'po_number', 'supplier__name', 'supplier__supplier_code', 'notes' ] readonly_fields = [ 'po_id', 'po_number', 'total_amount', 'is_overdue', 'days_outstanding', 'created_at', 'updated_at' ] fieldsets = [ ('Purchase Order Information', { 'fields': [ 'po_id', 'po_number', 'tenant' ] }), ('Supplier Information', { 'fields': [ 'supplier' ] }), ('Order Dates', { 'fields': [ 'order_date', 'requested_delivery_date', 'promised_delivery_date', 'actual_delivery_date' ] }), ('Order Type and Priority', { 'fields': [ 'order_type', 'priority' ] }), ('Financial Information', { 'fields': [ 'subtotal', 'tax_amount', 'shipping_amount', 'total_amount' ] }), ('Order Status', { 'fields': [ 'status' ] }), ('Delivery Information', { 'fields': [ 'delivery_location', 'delivery_instructions' ] }), ('Payment Terms', { 'fields': [ 'payment_terms' ] }), ('Approval Information', { 'fields': [ 'requested_by', 'approved_by', 'approval_date' ] }), ('Status Information', { 'fields': [ 'is_overdue', 'days_outstanding' ], 'classes': ['collapse'] }), ('Notes', { 'fields': [ 'notes' ], 'classes': ['collapse'] }), ('Metadata', { 'fields': [ 'created_at', 'updated_at', 'created_by' ], 'classes': ['collapse'] }) ] inlines = [PurchaseOrderItemInline] date_hierarchy = 'order_date' def supplier_name(self, obj): """Display supplier name.""" return obj.supplier.name supplier_name.short_description = 'Supplier' def days_outstanding_display(self, obj): """Display days outstanding with color coding.""" days = obj.days_outstanding if days <= 7: color = 'green' elif days <= 30: color = 'orange' else: color = 'red' return format_html( '{} days', color, days ) days_outstanding_display.short_description = 'Days Outstanding' def is_overdue_display(self, obj): """Display overdue status with icon.""" if obj.is_overdue: return format_html( '⚠️ Yes' ) return format_html( '✓ No' ) is_overdue_display.short_description = 'Overdue' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(tenant=request.user.tenant) return qs.select_related('supplier', 'requested_by', 'approved_by') @admin.register(PurchaseOrderItem) class PurchaseOrderItemAdmin(admin.ModelAdmin): """ Admin interface for purchase order items. """ list_display = [ 'po_number', 'line_number', 'item_name', 'quantity_ordered', 'quantity_received', 'quantity_remaining', 'unit_price', 'total_price', 'status' ] list_filter = [ 'purchase_order__tenant', 'status', 'purchase_order__order_date', 'requested_delivery_date' ] search_fields = [ 'purchase_order__po_number', 'inventory_item__item_name', 'inventory_item__item_code' ] readonly_fields = [ 'item_id', 'total_price', 'quantity_remaining', 'tenant', 'is_fully_received', 'created_at', 'updated_at' ] fieldsets = [ ('Item Information', { 'fields': [ 'item_id', 'purchase_order', 'line_number' ] }), ('Inventory Item', { 'fields': [ 'inventory_item' ] }), ('Quantity Information', { 'fields': [ 'quantity_ordered', 'quantity_received', 'quantity_remaining' ] }), ('Pricing Information', { 'fields': [ 'unit_price', 'total_price' ] }), ('Delivery Information', { 'fields': [ 'requested_delivery_date' ] }), ('Item Status', { 'fields': [ 'status' ] }), ('Status Information', { 'fields': [ 'is_fully_received' ], 'classes': ['collapse'] }), ('Notes', { 'fields': [ 'notes' ], 'classes': ['collapse'] }), ('Related Information', { 'fields': [ 'tenant' ], 'classes': ['collapse'] }), ('Metadata', { 'fields': [ 'created_at', 'updated_at' ], 'classes': ['collapse'] }) ] def po_number(self, obj): """Display PO number.""" return obj.purchase_order.po_number po_number.short_description = 'PO Number' def item_name(self, obj): """Display item name.""" return obj.inventory_item.item_name item_name.short_description = 'Item' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(purchase_order__tenant=request.user.tenant) return qs.select_related('purchase_order', 'inventory_item') @admin.register(Supplier) class SupplierAdmin(admin.ModelAdmin): """ Admin interface for suppliers. """ list_display = [ 'supplier_code', 'name', 'supplier_type', 'contact_person', 'phone', 'email', 'performance_rating_display', 'on_time_delivery_rate', 'is_active', 'is_preferred' ] list_filter = [ 'tenant', 'supplier_type', 'is_active', 'is_preferred', 'payment_terms', 'performance_rating' ] search_fields = [ 'supplier_code', 'name', 'contact_person', 'phone', 'email', 'city', 'state' ] readonly_fields = [ 'supplier_id', 'full_address', 'total_orders', 'total_order_value', 'has_active_contract', 'created_at', 'updated_at' ] fieldsets = [ ('Supplier Information', { 'fields': [ 'supplier_id', 'tenant', 'supplier_code', 'name' ] }), ('Supplier Type', { 'fields': [ 'supplier_type' ] }), ('Contact Information', { 'fields': [ 'contact_person', 'phone', 'email', 'website' ] }), ('Address Information', { 'fields': [ 'address_line_1', 'address_line_2', 'city', 'state', 'postal_code', 'country' ] }), ('Business Information', { 'fields': [ 'tax_id', 'duns_number' ], 'classes': ['collapse'] }), ('Payment Information', { 'fields': [ 'payment_terms' ] }), ('Performance Information', { 'fields': [ 'performance_rating', 'on_time_delivery_rate', 'quality_rating' ] }), ('Supplier Status', { 'fields': [ 'is_active', 'is_preferred' ] }), ('Certification Information', { 'fields': [ 'certifications' ], 'classes': ['collapse'] }), ('Contract Information', { 'fields': [ 'contract_start_date', 'contract_end_date' ] }), ('Summary Information', { 'fields': [ 'full_address', 'total_orders', 'total_order_value', 'has_active_contract' ], 'classes': ['collapse'] }), ('Notes', { 'fields': [ 'notes' ], 'classes': ['collapse'] }), ('Metadata', { 'fields': [ 'created_at', 'updated_at', 'created_by' ], 'classes': ['collapse'] }) ] def performance_rating_display(self, obj): """Display performance rating with stars.""" rating = obj.performance_rating stars = '★' * int(rating) + '☆' * (5 - int(rating)) return format_html( '{}', rating, stars ) performance_rating_display.short_description = 'Performance' def get_queryset(self, request): """Filter by user's tenant.""" qs = super().get_queryset(request) if hasattr(request.user, 'tenant'): qs = qs.filter(tenant=request.user.tenant) return qs # Customize admin site admin.site.site_header = "Hospital Management System - Inventory" admin.site.site_title = "Inventory Admin" admin.site.index_title = "Inventory Administration"