646 lines
20 KiB
Python
646 lines
20 KiB
Python
from django.contrib import admin
|
|
from core.models import (
|
|
Customer,
|
|
CustomerAddress,
|
|
CustomerContact,
|
|
Account,
|
|
AccountAddress,
|
|
AccountContact,
|
|
Service,
|
|
Project,
|
|
Report,
|
|
Revenue,
|
|
Labor,
|
|
Schedule,
|
|
Invoice,
|
|
AccountPunchlist,
|
|
ProjectPunchlist,
|
|
CustomerProfile,
|
|
TeamProfile,
|
|
Scope,
|
|
Area,
|
|
Task,
|
|
TaskCompletion,
|
|
ScopeTemplate,
|
|
AreaTemplate,
|
|
TaskTemplate,
|
|
ProjectScope,
|
|
ProjectScopeCategory,
|
|
ProjectScopeTask,
|
|
ProjectScopeTaskCompletion,
|
|
ProjectScopeTemplate,
|
|
ProjectAreaTemplate,
|
|
ProjectTaskTemplate,
|
|
ServiceSession,
|
|
ProjectSession,
|
|
ServiceSessionNote,
|
|
ProjectSessionNote,
|
|
# Events & Notifications
|
|
Event,
|
|
NotificationRule,
|
|
Notification,
|
|
NotificationDelivery,
|
|
# Messaging
|
|
Conversation,
|
|
ConversationParticipant,
|
|
Message,
|
|
MessageReadReceipt,
|
|
# Session Media
|
|
ServiceSessionImage,
|
|
ProjectSessionImage,
|
|
ServiceSessionVideo,
|
|
ProjectSessionVideo,
|
|
# Chat
|
|
ChatConversation,
|
|
ChatMessage,
|
|
)
|
|
|
|
|
|
@admin.register(Customer)
|
|
class CustomerAdmin(admin.ModelAdmin):
|
|
list_display = ("name", "status", "start_date", "end_date")
|
|
list_filter = ("status",)
|
|
search_fields = ("name",)
|
|
|
|
|
|
@admin.register(Account)
|
|
class AccountAdmin(admin.ModelAdmin):
|
|
list_display = ("name", "customer", "status", "start_date", "end_date")
|
|
list_filter = ("status", "customer")
|
|
search_fields = ("name", "customer__name")
|
|
|
|
|
|
@admin.register(CustomerAddress)
|
|
class CustomerAddressAdmin(admin.ModelAdmin):
|
|
list_display = ("customer", "address_type", "is_primary", "is_active")
|
|
list_filter = ("address_type", "is_primary", "is_active")
|
|
search_fields = ("customer__name", "street_address", "city")
|
|
|
|
|
|
@admin.register(CustomerContact)
|
|
class CustomerContactAdmin(admin.ModelAdmin):
|
|
list_display = ("full_name", "customer", "email", "phone", "is_primary", "is_active")
|
|
list_filter = ("is_primary", "is_active")
|
|
search_fields = ("first_name", "last_name", "customer__name", "email", "phone")
|
|
|
|
|
|
@admin.register(AccountAddress)
|
|
class AccountAddressAdmin(admin.ModelAdmin):
|
|
list_display = ("account", "street_address", "city", "is_primary", "is_active")
|
|
list_filter = ("is_primary", "is_active")
|
|
search_fields = ("account__name", "street_address", "city")
|
|
|
|
|
|
@admin.register(AccountContact)
|
|
class AccountContactAdmin(admin.ModelAdmin):
|
|
list_display = ("full_name", "account", "email", "phone", "is_primary", "is_active")
|
|
list_filter = ("is_primary", "is_active")
|
|
search_fields = ("first_name", "last_name", "account__name", "email", "phone")
|
|
|
|
|
|
@admin.register(Service)
|
|
class ServiceAdmin(admin.ModelAdmin):
|
|
list_display = ("account_address", "date", "status")
|
|
list_filter = ("status", "date")
|
|
search_fields = ("account_address__account__name",)
|
|
|
|
|
|
@admin.register(Project)
|
|
class ProjectAdmin(admin.ModelAdmin):
|
|
list_display = ("customer", "account_address", "date", "status", "labor", "amount")
|
|
list_filter = ("status", "date", "customer")
|
|
search_fields = (
|
|
"customer__name",
|
|
"account_address__account__name",
|
|
"street_address",
|
|
"city",
|
|
"state",
|
|
"zip_code",
|
|
)
|
|
|
|
|
|
@admin.register(Report)
|
|
class ReportAdmin(admin.ModelAdmin):
|
|
list_display = ("team_member", "date")
|
|
list_filter = ("date",)
|
|
search_fields = ("team_member__first_name", "team_member__last_name")
|
|
|
|
|
|
@admin.register(Revenue)
|
|
class RevenueAdmin(admin.ModelAdmin):
|
|
list_display = ("account", "amount", "start_date", "end_date")
|
|
list_filter = ("start_date",)
|
|
search_fields = ("account__name",)
|
|
|
|
|
|
@admin.register(Labor)
|
|
class LaborAdmin(admin.ModelAdmin):
|
|
list_display = ("account_address", "amount", "start_date", "end_date")
|
|
list_filter = ("start_date",)
|
|
search_fields = ("account_address__account__name",)
|
|
|
|
|
|
@admin.register(Schedule)
|
|
class ScheduleAdmin(admin.ModelAdmin):
|
|
list_display = ("account_address", "start_date", "end_date", "weekend_service")
|
|
list_filter = ("weekend_service",)
|
|
search_fields = ("account_address__account__name",)
|
|
|
|
|
|
@admin.register(Invoice)
|
|
class InvoiceAdmin(admin.ModelAdmin):
|
|
list_display = ("customer", "date", "status", "date_paid", "payment_type")
|
|
list_filter = ("status", "date")
|
|
search_fields = ("customer__name",)
|
|
|
|
|
|
@admin.register(AccountPunchlist)
|
|
class AccountPunchlistAdmin(admin.ModelAdmin):
|
|
list_display = ("account", "date")
|
|
list_filter = ("date",)
|
|
search_fields = ("account__name",)
|
|
|
|
|
|
@admin.register(ProjectPunchlist)
|
|
class ProjectPunchlistAdmin(admin.ModelAdmin):
|
|
list_display = ("project", "date")
|
|
list_filter = ("date",)
|
|
search_fields = (
|
|
"project__account_address__account__name",
|
|
"project__street_address",
|
|
"project__city",
|
|
"project__state",
|
|
"project__zip_code",
|
|
"project__customer__name",
|
|
)
|
|
|
|
|
|
@admin.register(CustomerProfile)
|
|
class CustomerProfileAdmin(admin.ModelAdmin):
|
|
list_display = ("user", "get_customers", "status")
|
|
list_filter = ("status",)
|
|
search_fields = ("user__username", "first_name", "last_name", "email")
|
|
filter_horizontal = ("customers",)
|
|
|
|
def get_customers(self, obj):
|
|
"""Display comma-separated list of customers"""
|
|
return ", ".join([c.name for c in obj.customers.all()])
|
|
get_customers.short_description = "Customers"
|
|
|
|
|
|
@admin.register(TeamProfile)
|
|
class TeamProfileAdmin(admin.ModelAdmin):
|
|
list_display = ("user", "first_name", "last_name", "status")
|
|
list_filter = ("status",)
|
|
search_fields = ("user__username", "first_name", "last_name")
|
|
|
|
|
|
@admin.register(Scope)
|
|
class ScopeAdmin(admin.ModelAdmin):
|
|
list_display = ("name", "account", "account_address", "is_active")
|
|
list_filter = ("is_active", "account")
|
|
search_fields = ("name", "account__name", "account_address__street_address")
|
|
|
|
|
|
class TaskTemplateInline(admin.TabularInline):
|
|
model = TaskTemplate
|
|
extra = 1
|
|
fields = ("description", "frequency", "order", "is_conditional", "estimated_minutes")
|
|
ordering = ("order",)
|
|
show_change_link = True
|
|
|
|
|
|
class AreaTemplateInline(admin.TabularInline):
|
|
model = AreaTemplate
|
|
extra = 1
|
|
fields = ("name", "order")
|
|
ordering = ("order",)
|
|
show_change_link = True
|
|
|
|
|
|
@admin.register(ScopeTemplate)
|
|
class ScopeTemplateAdmin(admin.ModelAdmin):
|
|
list_display = ("name", "is_active")
|
|
list_filter = ("is_active",)
|
|
search_fields = ("name", "description")
|
|
inlines = (AreaTemplateInline,)
|
|
ordering = ("name",)
|
|
|
|
|
|
@admin.register(AreaTemplate)
|
|
class AreaTemplateAdmin(admin.ModelAdmin):
|
|
list_display = ("name", "scope_template", "order")
|
|
list_filter = ("scope_template",)
|
|
search_fields = ("name", "scope_template__name")
|
|
inlines = (TaskTemplateInline,)
|
|
ordering = ("scope_template", "order", "name")
|
|
|
|
|
|
@admin.register(TaskTemplate)
|
|
class TaskTemplateAdmin(admin.ModelAdmin):
|
|
list_display = ("short_description", "area_template", "frequency", "order", "is_conditional")
|
|
list_filter = ("frequency", "is_conditional", "area_template__scope_template")
|
|
search_fields = ("description", "area_template__name", "area_template__scope_template__name")
|
|
ordering = ("area_template", "order")
|
|
|
|
def short_description(self, obj):
|
|
return (obj.description or "")[:60]
|
|
|
|
short_description.short_description = "Description"
|
|
|
|
|
|
@admin.register(ServiceSession)
|
|
class ServiceSessionAdmin(admin.ModelAdmin):
|
|
list_display = (
|
|
"service",
|
|
"account",
|
|
"account_address",
|
|
"scope",
|
|
"start",
|
|
"end",
|
|
"created_by",
|
|
"closed_by",
|
|
"is_active",
|
|
)
|
|
list_filter = ("start", "end", "account", "scope")
|
|
search_fields = (
|
|
"service__account_address__account__name",
|
|
"account_address__street_address",
|
|
"account_address__city",
|
|
"created_by__first_name",
|
|
"created_by__last_name",
|
|
)
|
|
ordering = ("-start",)
|
|
readonly_fields = ("duration_seconds",)
|
|
filter_horizontal = ("completed_tasks",)
|
|
|
|
|
|
@admin.register(ProjectScope)
|
|
class ProjectScopeAdmin(admin.ModelAdmin):
|
|
list_display = ("name", "project", "account", "account_address", "is_active")
|
|
list_filter = ("is_active", "project", "account")
|
|
search_fields = (
|
|
"name",
|
|
"project__customer__name",
|
|
"project__account_address__account__name",
|
|
"account__name",
|
|
"account_address__street_address",
|
|
)
|
|
ordering = ("name",)
|
|
|
|
|
|
@admin.register(ProjectScopeCategory)
|
|
class ProjectScopeCategoryAdmin(admin.ModelAdmin):
|
|
list_display = ("name", "scope", "order")
|
|
list_filter = ("scope",)
|
|
search_fields = ("name", "scope__name")
|
|
ordering = ("scope", "order", "name")
|
|
|
|
|
|
@admin.register(ProjectScopeTask)
|
|
class ProjectScopeTaskAdmin(admin.ModelAdmin):
|
|
list_display = ("short_description", "category", "order", "estimated_minutes")
|
|
list_filter = ("category__scope",)
|
|
search_fields = ("description", "category__name", "category__scope__name")
|
|
ordering = ("category", "order")
|
|
|
|
def short_description(self, obj):
|
|
return (obj.description or "")[:60]
|
|
|
|
short_description.short_description = "Description"
|
|
|
|
|
|
class ProjectTaskTemplateInline(admin.TabularInline):
|
|
model = ProjectTaskTemplate
|
|
extra = 1
|
|
fields = ("description", "order", "estimated_minutes")
|
|
ordering = ("order",)
|
|
show_change_link = True
|
|
|
|
|
|
class ProjectAreaTemplateInline(admin.TabularInline):
|
|
model = ProjectAreaTemplate
|
|
extra = 1
|
|
fields = ("name", "order")
|
|
ordering = ("order",)
|
|
show_change_link = True
|
|
|
|
|
|
@admin.register(ProjectScopeTemplate)
|
|
class ProjectScopeTemplateAdmin(admin.ModelAdmin):
|
|
list_display = ("name", "is_active")
|
|
list_filter = ("is_active",)
|
|
search_fields = ("name", "description")
|
|
inlines = (ProjectAreaTemplateInline,)
|
|
ordering = ("name",)
|
|
|
|
|
|
@admin.register(ProjectAreaTemplate)
|
|
class ProjectAreaTemplateAdmin(admin.ModelAdmin):
|
|
list_display = ("name", "scope_template", "order")
|
|
list_filter = ("scope_template",)
|
|
search_fields = ("name", "scope_template__name")
|
|
inlines = (ProjectTaskTemplateInline,)
|
|
ordering = ("scope_template", "order", "name")
|
|
|
|
|
|
@admin.register(ProjectTaskTemplate)
|
|
class ProjectTaskTemplateAdmin(admin.ModelAdmin):
|
|
list_display = ("short_description", "area_template", "order", "estimated_minutes")
|
|
list_filter = ("area_template__scope_template",)
|
|
search_fields = ("description", "area_template__name", "area_template__scope_template__name")
|
|
ordering = ("area_template", "order")
|
|
|
|
def short_description(self, obj):
|
|
return (obj.description or "")[:60]
|
|
|
|
short_description.short_description = "Description"
|
|
|
|
|
|
@admin.register(ProjectSession)
|
|
class ProjectSessionAdmin(admin.ModelAdmin):
|
|
list_display = (
|
|
"project",
|
|
"account",
|
|
"account_address",
|
|
"scope",
|
|
"start",
|
|
"end",
|
|
"created_by",
|
|
"closed_by",
|
|
"is_active",
|
|
)
|
|
list_filter = ("start", "end", "account", "scope")
|
|
search_fields = (
|
|
"project__account_address__account__name",
|
|
"account_address__street_address",
|
|
"account_address__city",
|
|
"created_by__first_name",
|
|
"created_by__last_name",
|
|
)
|
|
ordering = ("-start",)
|
|
readonly_fields = ("duration_seconds",)
|
|
|
|
|
|
# Admin registrations for Area, Task, TaskCompletion, and ProjectScopeTaskCompletion
|
|
class TaskInline(admin.TabularInline):
|
|
model = Task
|
|
extra = 1
|
|
fields = ("description", "frequency", "order", "is_conditional", "estimated_minutes")
|
|
ordering = ("order",)
|
|
show_change_link = True
|
|
|
|
|
|
@admin.register(Area)
|
|
class AreaAdmin(admin.ModelAdmin):
|
|
list_display = ("name", "scope", "order")
|
|
list_filter = ("scope",)
|
|
search_fields = ("name", "scope__name")
|
|
ordering = ("scope", "order", "name")
|
|
inlines = (TaskInline,)
|
|
|
|
|
|
@admin.register(Task)
|
|
class TaskAdmin(admin.ModelAdmin):
|
|
list_display = ("short_description", "area", "frequency", "order", "is_conditional")
|
|
list_filter = ("frequency", "is_conditional", "area__scope")
|
|
search_fields = ("description", "area__name", "area__scope__name")
|
|
ordering = ("area", "order")
|
|
|
|
def short_description(self, obj):
|
|
return (obj.description or "")[:60]
|
|
|
|
short_description.short_description = "Description"
|
|
|
|
|
|
@admin.register(TaskCompletion)
|
|
class TaskCompletionAdmin(admin.ModelAdmin):
|
|
list_display = ("task", "service", "account_address", "completed_by", "completed_at", "year", "month")
|
|
list_filter = ("completed_at", "completed_by", "task__area__scope")
|
|
search_fields = (
|
|
"task__description",
|
|
"task__area__name",
|
|
"task__area__scope__name",
|
|
"service__account_address__account__name",
|
|
"service__account_address__street_address",
|
|
)
|
|
ordering = ("-completed_at",)
|
|
|
|
|
|
@admin.register(ProjectScopeTaskCompletion)
|
|
class ProjectScopeTaskCompletionAdmin(admin.ModelAdmin):
|
|
list_display = (
|
|
"task",
|
|
"project",
|
|
"account",
|
|
"account_address",
|
|
"completed_by",
|
|
"completed_at",
|
|
)
|
|
list_filter = ("completed_at", "completed_by", "task__category__scope", "project", "account")
|
|
search_fields = (
|
|
"task__description",
|
|
"task__category__name",
|
|
"task__category__scope__name",
|
|
"project__customer__name",
|
|
"project__account_address__account__name",
|
|
"account__name",
|
|
"account_address__street_address",
|
|
)
|
|
ordering = ("-completed_at",)
|
|
|
|
|
|
@admin.register(ServiceSessionNote)
|
|
class ServiceSessionNoteAdmin(admin.ModelAdmin):
|
|
list_display = ("session", "short_content", "author", "internal", "created_at")
|
|
list_filter = ("internal", "created_at", "author")
|
|
search_fields = (
|
|
"content",
|
|
"session__service__account_address__account__name",
|
|
"author__first_name",
|
|
"author__last_name",
|
|
)
|
|
ordering = ("-created_at",)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
def short_content(self, obj):
|
|
return (obj.content or "")[:60]
|
|
|
|
short_content.short_description = "Content"
|
|
|
|
|
|
@admin.register(ProjectSessionNote)
|
|
class ProjectSessionNoteAdmin(admin.ModelAdmin):
|
|
list_display = ("session", "short_content", "author", "internal", "created_at")
|
|
list_filter = ("internal", "created_at", "author")
|
|
search_fields = (
|
|
"content",
|
|
"session__project__customer__name",
|
|
"author__first_name",
|
|
"author__last_name",
|
|
)
|
|
ordering = ("-created_at",)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
def short_content(self, obj):
|
|
return (obj.content or "")[:60]
|
|
|
|
short_content.short_description = "Content"
|
|
|
|
|
|
# =============================================================================
|
|
# Events & Notifications
|
|
# =============================================================================
|
|
|
|
|
|
@admin.register(Event)
|
|
class EventAdmin(admin.ModelAdmin):
|
|
list_display = ("event_type", "entity_type", "entity_id", "created_at")
|
|
list_filter = ("event_type", "entity_type", "created_at")
|
|
search_fields = ("entity_type", "entity_id")
|
|
ordering = ("-created_at",)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
|
|
@admin.register(NotificationRule)
|
|
class NotificationRuleAdmin(admin.ModelAdmin):
|
|
list_display = ("name", "is_active", "get_channels", "created_at")
|
|
list_filter = ("is_active",)
|
|
search_fields = ("name", "description")
|
|
filter_horizontal = ("target_team_profiles", "target_customer_profiles")
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
def get_channels(self, obj):
|
|
return ", ".join(obj.channels) if obj.channels else ""
|
|
|
|
get_channels.short_description = "Channels"
|
|
|
|
|
|
@admin.register(Notification)
|
|
class NotificationAdmin(admin.ModelAdmin):
|
|
list_display = ("subject", "event", "status", "read_at", "created_at")
|
|
list_filter = ("status", "created_at")
|
|
search_fields = ("subject", "body")
|
|
ordering = ("-created_at",)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
|
|
@admin.register(NotificationDelivery)
|
|
class NotificationDeliveryAdmin(admin.ModelAdmin):
|
|
list_display = ("notification", "channel", "status", "attempts", "sent_at")
|
|
list_filter = ("channel", "status")
|
|
ordering = ("-created_at",)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
|
|
# =============================================================================
|
|
# Messaging
|
|
# =============================================================================
|
|
|
|
|
|
@admin.register(Conversation)
|
|
class ConversationAdmin(admin.ModelAdmin):
|
|
list_display = ("subject", "conversation_type", "last_message_at", "is_archived")
|
|
list_filter = ("conversation_type", "is_archived")
|
|
search_fields = ("subject",)
|
|
ordering = ("-last_message_at",)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
|
|
@admin.register(ConversationParticipant)
|
|
class ConversationParticipantAdmin(admin.ModelAdmin):
|
|
list_display = ("conversation", "unread_count", "is_muted", "is_archived", "joined_at")
|
|
list_filter = ("is_muted", "is_archived")
|
|
ordering = ("-joined_at",)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
|
|
@admin.register(Message)
|
|
class MessageAdmin(admin.ModelAdmin):
|
|
list_display = ("conversation", "short_body", "is_system_message", "created_at")
|
|
list_filter = ("is_system_message", "created_at")
|
|
search_fields = ("body",)
|
|
ordering = ("-created_at",)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
def short_body(self, obj):
|
|
return (obj.body or "")[:60]
|
|
|
|
short_body.short_description = "Body"
|
|
|
|
|
|
@admin.register(MessageReadReceipt)
|
|
class MessageReadReceiptAdmin(admin.ModelAdmin):
|
|
list_display = ("message", "read_at")
|
|
ordering = ("-read_at",)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
|
|
# =============================================================================
|
|
# Session Media
|
|
# =============================================================================
|
|
|
|
|
|
@admin.register(ServiceSessionImage)
|
|
class ServiceSessionImageAdmin(admin.ModelAdmin):
|
|
list_display = ("service_session", "title", "created_at")
|
|
list_filter = ("created_at",)
|
|
search_fields = ("title",)
|
|
ordering = ("-created_at",)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
|
|
@admin.register(ProjectSessionImage)
|
|
class ProjectSessionImageAdmin(admin.ModelAdmin):
|
|
list_display = ("project_session", "title", "created_at")
|
|
list_filter = ("created_at",)
|
|
search_fields = ("title",)
|
|
ordering = ("-created_at",)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
|
|
@admin.register(ServiceSessionVideo)
|
|
class ServiceSessionVideoAdmin(admin.ModelAdmin):
|
|
list_display = ("service_session", "title", "duration_seconds", "created_at")
|
|
list_filter = ("created_at",)
|
|
search_fields = ("title",)
|
|
ordering = ("-created_at",)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
|
|
@admin.register(ProjectSessionVideo)
|
|
class ProjectSessionVideoAdmin(admin.ModelAdmin):
|
|
list_display = ("project_session", "title", "duration_seconds", "created_at")
|
|
list_filter = ("created_at",)
|
|
search_fields = ("title",)
|
|
ordering = ("-created_at",)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
|
|
# =============================================================================
|
|
# Chat
|
|
# =============================================================================
|
|
|
|
|
|
@admin.register(ChatConversation)
|
|
class ChatConversationAdmin(admin.ModelAdmin):
|
|
list_display = ("team_profile", "title", "is_active", "created_at", "updated_at")
|
|
list_filter = ("is_active", "created_at")
|
|
search_fields = ("title", "team_profile__first_name", "team_profile__last_name")
|
|
ordering = ("-updated_at",)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
|
|
@admin.register(ChatMessage)
|
|
class ChatMessageAdmin(admin.ModelAdmin):
|
|
list_display = ("conversation", "role", "short_content", "created_at")
|
|
list_filter = ("role", "created_at")
|
|
search_fields = ("content",)
|
|
ordering = ("-created_at",)
|
|
readonly_fields = ("created_at", "updated_at")
|
|
|
|
def short_content(self, obj):
|
|
return (obj.content or "")[:60]
|
|
|
|
short_content.short_description = "Content"
|