Skip to main content

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

  1. The CLI command resolves both the source and target users.
  2. It queries the MetaRegistry to find all SQL-mapped and document collections.
  3. For each document type, it loads its registered controller and repository.
  4. 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.
  5. 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)