"""Calendar plugin — appointments, tasks, kanban, resources, ICS feed/import, recurrence.""" from __future__ import annotations from app.plugins.base import BasePlugin from app.plugins.manifest import ( FrontendDashboardWidget, FrontendMenuItem, FrontendPageRoute, PluginManifest, PluginRouteDef, ) class CalendarPlugin(BasePlugin): """Calendar plugin for appointment/task management with recurrence, ICS, and resource booking.""" manifest = PluginManifest( name="calendar", version="1.0.0", display_name="Calendar", description="Calendar with appointments, tasks, kanban board, recurrence, ICS export/import, resource booking, sharing.", dependencies=[], routes=[ PluginRouteDef( path="/api/v1/calendars", module="app.plugins.builtins.calendar.routes", router_attr="calendar_router", ), PluginRouteDef( path="/api/v1/resources", module="app.plugins.builtins.calendar.routes", router_attr="resource_router", ), PluginRouteDef( path="/api/v1", module="app.plugins.builtins.calendar.routes", router_attr="router", ), ], events=[], migrations=["0001_initial.sql", "0002_add_deleted_at.sql"], dashboard_widgets=[ FrontendDashboardWidget( id="calendar_upcoming", label_key="dashboard.calendarUpcoming", label="Upcoming Appointments", component="@/components/dashboard/CalendarUpcomingWidget", icon="Calendar", order=30, col_span=1, permission="calendar:read", ), ], permissions=[ "calendar:read", "calendar:write", "calendar:delete", "calendar:share", "calendar:admin", ], menu_items=[ FrontendMenuItem(label_key='nav.calendar', label='Kalender', path='/calendar', icon='Calendar', order=20, permission='calendar:read'), ], page_routes=[ FrontendPageRoute(path='/calendar', component='@/pages/Calendar', protected=True, permission='calendar:read'), # Q1: kanban view was static-only before - now manifest-declared. FrontendPageRoute(path='/calendar/kanban', component='@/pages/CalendarKanban', protected=True, permission='calendar:read'), ], # BUG (ghost component): ContactCalendarTab does not exist in the # frontend — tab removed until implemented (Block I-D). detail_tabs=[], author="LeoCRM Team", min_app_version="1.0.0", hooks=["calendar.before_appointment", "calendar.after_appointment"], contract_version="1.0.0", ) async def on_activate(self, db, service_container, event_bus) -> None: """Activate plugin: register restore config + history hooks.""" await super().on_activate(db, service_container, event_bus) # Register restore config for CalendarEntry entities (P0-7 fix) from app.core.restore_registry import RestoreConfig, get_restore_registry from app.plugins.builtins.calendar.models import CalendarEntry get_restore_registry().register(RestoreConfig( entity_type="calendar_entry", model_class=CalendarEntry, restore_permission="calendar:write", excluded_fields=frozenset({"calendar_id", "created_by", "assigned_to", "source_mail_id"}), )) # Register history hooks for CalendarEntry entities (P0-8 fix) from app.core.history_hooks import register_history_hooks from app.core.hooks import get_hook_registry register_history_hooks( get_hook_registry(), "calendar_entry", "calendar_entry.after_create", "calendar_entry.after_update", "calendar_entry.after_delete", owner_tag="calendar", ) async def on_deactivate( self, db, service_container, event_bus ) -> None: """Deactivate plugin: unregister contract, restore, history, events.""" # Contract abmelden from app.plugins.builtins.contracts import get_contract_registry get_contract_registry().unregister(self.manifest.name) # Unregister restore config (P0-7 fix) from app.core.restore_registry import get_restore_registry get_restore_registry().unregister("calendar_entry") # Unregister history hooks (free functions, not bound methods) from app.core.hooks import get_hook_registry get_hook_registry().unregister_actions_by_owner("calendar_entry.after_create", "calendar") get_hook_registry().unregister_actions_by_owner("calendar_entry.after_update", "calendar") get_hook_registry().unregister_actions_by_owner("calendar_entry.after_delete", "calendar") await super().on_deactivate(db, service_container, event_bus)