""" Django admin configuration for finance app. """ from django.contrib import admin from django.utils.translation import gettext_lazy as _ from simple_history.admin import SimpleHistoryAdmin from .models import ( Service, Package, Payer, Invoice, InvoiceLineItem, Payment, PackagePurchase, CSID, ) @admin.register(Service) class ServiceAdmin(admin.ModelAdmin): """Admin interface for Service model.""" list_display = ['code', 'name_en', 'clinic', 'base_price', 'duration_minutes', 'is_active', 'tenant'] list_filter = ['clinic', 'is_active', 'tenant'] search_fields = ['code', 'name_en', 'name_ar'] readonly_fields = ['id', 'created_at', 'updated_at'] fieldsets = ( (None, { 'fields': ('code', 'name_en', 'name_ar', 'clinic', 'tenant', 'is_active') }), (_('Pricing & Duration'), { 'fields': ('base_price', 'duration_minutes') }), (_('Description'), { 'fields': ('description',), 'classes': ('collapse',) }), (_('Metadata'), { 'fields': ('id', 'created_at', 'updated_at'), 'classes': ('collapse',) }), ) @admin.register(Package) class PackageAdmin(admin.ModelAdmin): """Admin interface for Package model.""" list_display = ['name_en', 'total_sessions', 'price', 'validity_days', 'is_active', 'tenant'] list_filter = ['is_active', 'tenant'] search_fields = ['name_en', 'name_ar'] readonly_fields = ['id', 'created_at', 'updated_at'] filter_horizontal = ['services'] fieldsets = ( (None, { 'fields': ('name_en', 'name_ar', 'tenant', 'is_active') }), (_('Package Details'), { 'fields': ('services', 'total_sessions', 'price', 'validity_days') }), (_('Description'), { 'fields': ('description',), 'classes': ('collapse',) }), (_('Metadata'), { 'fields': ('id', 'created_at', 'updated_at'), 'classes': ('collapse',) }), ) @admin.register(Payer) class PayerAdmin(admin.ModelAdmin): """Admin interface for Payer model.""" list_display = ['name', 'patient', 'payer_type', 'coverage_percentage', 'is_active', 'tenant'] list_filter = ['payer_type', 'is_active', 'tenant'] search_fields = ['name', 'policy_number', 'patient__mrn', 'patient__first_name_en', 'patient__last_name_en'] readonly_fields = ['id', 'created_at', 'updated_at'] fieldsets = ( (None, { 'fields': ('patient', 'name', 'payer_type', 'tenant', 'is_active') }), (_('Coverage Details'), { 'fields': ('policy_number', 'coverage_percentage') }), (_('Notes'), { 'fields': ('notes',), 'classes': ('collapse',) }), (_('Metadata'), { 'fields': ('id', 'created_at', 'updated_at'), 'classes': ('collapse',) }), ) class InvoiceLineItemInline(admin.TabularInline): """Inline admin for Invoice Line Items.""" model = InvoiceLineItem extra = 1 readonly_fields = ['id'] fields = ['service', 'package', 'description', 'quantity', 'unit_price', 'total'] @admin.register(Invoice) class InvoiceAdmin(SimpleHistoryAdmin): """Admin interface for Invoice model.""" list_display = ['invoice_number', 'invoice_counter', 'patient', 'invoice_type', 'issue_date', 'total', 'status', 'zatca_status', 'tenant'] list_filter = ['status', 'invoice_type', 'zatca_status', 'tenant', 'issue_date', 'due_date'] search_fields = ['invoice_number', 'patient__mrn', 'patient__first_name_en', 'patient__last_name_en'] readonly_fields = ['id', 'invoice_number', 'invoice_counter', 'invoice_hash', 'previous_invoice_hash', 'qr_code', 'cryptographic_stamp', 'amount_paid', 'amount_due', 'is_fully_paid', 'created_at', 'updated_at', 'zatca_submission_date', 'zatca_response'] date_hierarchy = 'issue_date' inlines = [InvoiceLineItemInline] fieldsets = ( (_('Identification'), { 'fields': ('invoice_number', 'invoice_counter', 'invoice_type', 'tenant') }), (_('Core Information'), { 'fields': ('patient', 'appointment', 'payer') }), (_('Credit/Debit Note Reference'), { 'fields': ('billing_reference_id', 'billing_reference_issue_date'), 'classes': ('collapse',) }), (_('Dates'), { 'fields': ('issue_date', 'issue_time', 'due_date') }), (_('Amounts'), { 'fields': ('subtotal', 'tax', 'discount', 'total', 'amount_paid', 'amount_due', 'is_fully_paid') }), (_('Status'), { 'fields': ('status',) }), (_('ZATCA E-Invoice'), { 'fields': ('invoice_hash', 'previous_invoice_hash', 'qr_code', 'cryptographic_stamp', 'zatca_status', 'zatca_submission_date', 'zatca_response', 'xml_content'), 'classes': ('collapse',) }), (_('Notes'), { 'fields': ('notes',), 'classes': ('collapse',) }), (_('Metadata'), { 'fields': ('id', 'created_at', 'updated_at'), 'classes': ('collapse',) }), ) @admin.register(InvoiceLineItem) class InvoiceLineItemAdmin(admin.ModelAdmin): """Admin interface for InvoiceLineItem model.""" list_display = ['invoice', 'description', 'quantity', 'unit_price', 'total'] list_filter = ['invoice__status', 'invoice__tenant'] search_fields = ['invoice__invoice_number', 'description'] readonly_fields = ['id'] fieldsets = ( (None, { 'fields': ('invoice', 'service', 'package') }), (_('Details'), { 'fields': ('description', 'quantity', 'unit_price', 'total') }), (_('Metadata'), { 'fields': ('id',), 'classes': ('collapse',) }), ) @admin.register(Payment) class PaymentAdmin(admin.ModelAdmin): """Admin interface for Payment model.""" list_display = ['invoice', 'payment_date', 'amount', 'method', 'status', 'processed_by', 'tenant'] list_filter = ['method', 'status', 'tenant', 'payment_date'] search_fields = ['invoice__invoice_number', 'transaction_id', 'reference'] readonly_fields = ['id', 'created_at', 'updated_at'] date_hierarchy = 'payment_date' fieldsets = ( (None, { 'fields': ('invoice', 'tenant', 'status') }), (_('Payment Details'), { 'fields': ('payment_date', 'amount', 'method', 'processed_by') }), (_('Transaction Information'), { 'fields': ('transaction_id', 'reference'), 'classes': ('collapse',) }), (_('Notes'), { 'fields': ('notes',), 'classes': ('collapse',) }), (_('Metadata'), { 'fields': ('id', 'created_at', 'updated_at'), 'classes': ('collapse',) }), ) @admin.register(PackagePurchase) class PackagePurchaseAdmin(admin.ModelAdmin): """Admin interface for PackagePurchase model.""" list_display = ['patient', 'package', 'purchase_date', 'expiry_date', 'total_sessions', 'sessions_used', 'sessions_remaining', 'status', 'tenant'] list_filter = ['status', 'tenant', 'purchase_date', 'expiry_date'] search_fields = ['patient__mrn', 'patient__first_name_en', 'patient__last_name_en', 'package__name_en'] readonly_fields = ['id', 'sessions_remaining', 'is_expired', 'is_completed', 'created_at', 'updated_at'] date_hierarchy = 'purchase_date' fieldsets = ( (None, { 'fields': ('patient', 'package', 'tenant', 'status') }), (_('Purchase Details'), { 'fields': ('invoice', 'purchase_date', 'expiry_date') }), (_('Session Tracking'), { 'fields': ('total_sessions', 'sessions_used', 'sessions_remaining', 'is_expired', 'is_completed') }), (_('Metadata'), { 'fields': ('id', 'created_at', 'updated_at'), 'classes': ('collapse',) }), ) @admin.register(CSID) class CSIDAdmin(admin.ModelAdmin): """Admin interface for CSID model.""" list_display = ['common_name', 'csid_type', 'status', 'issue_date', 'expiry_date', 'days_until_expiry', 'invoices_signed', 'tenant'] list_filter = ['csid_type', 'status', 'tenant', 'issue_date', 'expiry_date'] search_fields = ['common_name', 'egs_serial_number', 'organization_unit'] readonly_fields = ['id', 'is_valid', 'days_until_expiry', 'needs_renewal', 'invoices_signed', 'last_used', 'created_at', 'updated_at'] date_hierarchy = 'issue_date' fieldsets = ( (_('CSID Information'), { 'fields': ('csid_type', 'status', 'tenant') }), (_('Certificate Details'), { 'fields': ('certificate', 'secret', 'request_id') }), (_('EGS Unit Information'), { 'fields': ('egs_serial_number', 'common_name', 'organization_unit') }), (_('Validity'), { 'fields': ('issue_date', 'expiry_date', 'is_valid', 'days_until_expiry', 'needs_renewal') }), (_('Revocation'), { 'fields': ('revocation_date', 'revocation_reason'), 'classes': ('collapse',) }), (_('Usage Statistics'), { 'fields': ('invoices_signed', 'last_used') }), (_('Metadata'), { 'fields': ('id', 'created_at', 'updated_at'), 'classes': ('collapse',) }), ) actions = ['revoke_selected_csids'] def revoke_selected_csids(self, request, queryset): """Revoke selected CSIDs.""" count = 0 for csid in queryset.filter(status=CSID.Status.ACTIVE): csid.revoke(reason="Revoked via admin interface") count += 1 self.message_user(request, f"Successfully revoked {count} CSID(s).") revoke_selected_csids.short_description = "Revoke selected CSIDs"