307 lines
10 KiB
Python
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"
|