"""Contacts plugin — Core CRM entity lifecycle management. Registers Contact entity models, permissions, restore config, and history hooks via the same plugin lifecycle as all other business plugins. No Core special case. """ from __future__ import annotations import logging from app.plugins.base import BasePlugin from app.plugins.manifest import ( FrontendDashboardWidget, PluginManifest, PluginRouteDef, ) logger = logging.getLogger(__name__) class ContactsPlugin(BasePlugin): """Contacts plugin — owns the full Contact domain (Block B1). Routes (contacts, companies, contact folders, folder permissions) live in this plugin and are mounted via manifest.routes with require_active_plugin("contacts") protection. Entity lifecycle (permissions, entity models, restore, history) is managed through on_activate/on_deactivate like every other business plugin. """ manifest = PluginManifest( name="contacts", version="1.1.0", display_name="Contacts", description="Core CRM contacts — persons and companies.", dependencies=[], routes=[ PluginRouteDef( path="/api/v1/contacts", module="app.plugins.builtins.contacts.routes", router_attr="router", ), PluginRouteDef( path="/api/v1/companies", module="app.plugins.builtins.contacts.company_routes", router_attr="router", ), PluginRouteDef( path="/api/v1/contact-folders", module="app.plugins.builtins.contacts.folder_routes", router_attr="router", ), PluginRouteDef( path="/api/v1/contact-folders", module="app.plugins.builtins.contacts.folder_permission_routes", router_attr="router", ), ], events=[], migrations=[], dashboard_widgets=[ FrontendDashboardWidget( id="recent_contacts", label_key="dashboard.recentContacts", label="Recent Contacts", component="@/components/dashboard/RecentContactsWidget", icon="Users", order=10, col_span=2, permission="contacts:read", ), ], permissions=[ "contacts:read", "contacts:write", "contacts:delete", ], is_core=True, author="LeoCRM Team", min_app_version="1.0.0", contract_version="1.0.0", ) def get_entity_models(self) -> dict[str, type]: from app.models.contact import Contact return { "contact": Contact, "contacts": Contact, "company": Contact, } async def on_activate(self, db, service_container, event_bus) -> None: """Activate: register restore config + history hooks for Contact.""" await super().on_activate(db, service_container, event_bus) # Register restore config for Contact entities from app.core.restore_registry import RestoreConfig, get_restore_registry from app.models.contact import Contact get_restore_registry().register(RestoreConfig( entity_type="contact", model_class=Contact, restore_permission="contacts:write", excluded_fields=frozenset({ "search_tsv", "embedding", "default_person_id", "admin_contactperson_id", }), )) # Register history hooks for Contact entities from app.core.history_hooks import register_history_hooks from app.core.hooks import get_hook_registry register_history_hooks( get_hook_registry(), "contact", "contact.after_create", "contact.after_update", "contact.after_delete", owner_tag="contacts", ) logger.info("Contacts plugin activated: restore + history registered") async def on_deactivate(self, db, service_container, event_bus) -> None: """Deactivate: unregister restore config + history hooks for Contact.""" from app.core.restore_registry import get_restore_registry get_restore_registry().unregister("contact") # Unregister history hooks — use owner_tag to remove only this plugin's hooks from app.core.hooks import get_hook_registry hook_reg = get_hook_registry() hook_reg.unregister_actions_by_owner("contact.after_create", "contacts") hook_reg.unregister_actions_by_owner("contact.after_update", "contacts") hook_reg.unregister_actions_by_owner("contact.after_delete", "contacts") from app.plugins.builtins.contracts import get_contract_registry get_contract_registry().unregister(self.manifest.name) await super().on_deactivate(db, service_container, event_bus) logger.info("Contacts plugin deactivated: restore + history unregistered")