agdar/finance/admin.py
Marwan Alwali d912313a27 update
2025-11-02 16:03:03 +03:00

307 lines
10 KiB
Python

"""
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,
PackageService,
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',)
}),
)
class PackageServiceInline(admin.TabularInline):
"""Inline admin for Package Services."""
model = PackageService
extra = 1
readonly_fields = ['id']
fields = ['service', 'sessions']
autocomplete_fields = ['service']
@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', 'total_sessions', 'created_at', 'updated_at']
inlines = [PackageServiceInline]
fieldsets = (
(None, {
'fields': ('name_en', 'name_ar', 'tenant', 'is_active')
}),
(_('Package Details'), {
'fields': ('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"