Enterprise Data Handover & Ownership Transfer
This guide explains how to migrate document and resource ownership between users during offboarding, reorganizations, or system administration tasks.
1. Using the Administration CLI
Framework M provides a built-in administrative command to perform data handover directly from the terminal.
Usage Syntax
m admin transfer-ownership --from <current_owner_uuid> --to <new_owner_uuid>
Process Lifecycle
- The CLI command resolves both the source and target users.
- It queries the
MetaRegistryto find all SQL-mapped and document collections. - For each document type, it loads its registered controller and repository.
- Instead of performing a blanket SQL update (which could bypass validation logic or mess up system constraints), the handover process delegates the transfer to each document type's repository and controller lifecycle hooks.
- Emits transfer event updates and registers the completed actions in the audit log.
2. Programmatic Transfer via OwnershipTransferService
To trigger a handover within your business logic (for example, in a workflow action or custom REST endpoint), inject and use the OwnershipTransferService.
from framework_m_core.di import inject, Provide
from framework_m_standard.services.ownership_transfer import OwnershipTransferService
@inject
async def deactivate_employee_workflow(
departing_user_id: str,
replacement_user_id: str,
transfer_service: OwnershipTransferService = Provide[Container.ownership_transfer_service]
) -> None:
# Triggers the transfer across all registered doctypes
summary = await transfer_service.transfer_all_ownership(
from_user_id=departing_user_id,
to_user_id=replacement_user_id
)
print(f"Transferred {summary.total_records} records across {len(summary.doctypes)} doctypes.")
3. Implementing Custom Transfer Hooks
Downstream developers can customize or intercept the ownership transfer for any specific DocType by defining lifecycle hooks in the DocType's Controller.
Hook methods:
before_transfer_ownership(self, from_user_id: str, to_user_id: str, context: Any = None) -> None- Called before changes are persisted. Raise exceptions here to block transfer.
after_transfer_ownership(self, from_user_id: str, to_user_id: str, context: Any = None) -> None- Called after the transfer is successfully committed. Use this to trigger notifications, clean up external resources, or sync downstream systems.
Example Implementation:
from framework_m_core.domain.base_controller import BaseController
from framework_m_core.exceptions import ValidationError
class DocumentController(BaseController[Document]):
async def before_transfer_ownership(
self,
from_user_id: str,
to_user_id: str,
context: Any = None
) -> None:
# Prevent transferring documents that are currently locked
if self.doc.is_locked:
raise ValidationError(
f"Cannot transfer ownership of locked document {self.doc.id}"
)
async def after_transfer_ownership(
self,
from_user_id: str,
to_user_id: str,
context: Any = None
) -> None:
# Notify the new owner
await self.notify_owner_assigned(to_user_id)