This repository has been archived on 2026-07-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
DataMigration/files/custom/Espo/Modules/DataMigration/Api/Status.php
T
chaim 6d6f5d571a feat: add admin UI for export/import data migration
- Admin panel page at #Admin/dataMigration with two-column layout
- Export: filter by case status, skip attachments option, progress + log
- Import: select backup, preserve IDs / skip duplicates, dry run support
- List/delete existing backups with full manifest details
- 5 API endpoints (status, export, import, listBackups, deleteBackup)
- BufferedIO adapter to capture CLI output for API responses
- Admin-only access on all endpoints
- i18n: English + Hebrew (fa_IR) translations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:45:20 +00:00

123 lines
3.0 KiB
PHP

<?php
namespace Espo\Modules\DataMigration\Api;
use Espo\Core\Api\Action;
use Espo\Core\Api\Request;
use Espo\Core\Api\Response;
use Espo\Core\Api\ResponseComposer;
use Espo\Core\Exceptions\Forbidden;
use Espo\Entities\User;
use Espo\ORM\EntityManager;
use Espo\Modules\DataMigration\Services\ManifestBuilder;
class Status implements Action
{
private const ENTITY_TYPES = [
'Case',
'Account',
'Contact',
'CaseActivity',
'Charge',
'Invoice',
'Document',
'NhMeeting',
'NhDecision',
'NhProcess',
'NhPlea',
'NhActivity',
'NhDocument',
'NhSyncLog',
'CaseMemory',
'SignatureRequest',
'SignatureRequestSigner',
'SmsLog',
'PricingAgreement',
'PricingAgreementRate',
'FixedActivityRate',
'ClientRate',
];
public function __construct(
private EntityManager $entityManager,
private ManifestBuilder $manifestBuilder,
private User $user
) {}
public function process(Request $request): Response
{
if (!$this->user->isAdmin()) {
throw new Forbidden();
}
$entityCounts = [];
$total = 0;
foreach (self::ENTITY_TYPES as $entityType) {
try {
$count = $this->entityManager
->getRDBRepository($entityType)
->count();
$entityCounts[$entityType] = $count;
$total += $count;
} catch (\Exception $e) {
// Entity type doesn't exist
}
}
$attachmentInfo = $this->getAttachmentInfo();
return ResponseComposer::json([
'entityCounts' => $entityCounts,
'totalRecords' => $total,
'attachmentFiles' => $attachmentInfo['count'],
'attachmentSize' => $attachmentInfo['size'],
'attachmentSizeFormatted' => $this->formatBytes($attachmentInfo['size']),
]);
}
private function getAttachmentInfo(): array
{
$uploadDir = 'data/upload/';
$count = 0;
$size = 0;
if (is_dir($uploadDir)) {
$files = scandir($uploadDir);
foreach ($files as $file) {
if ($file === '.' || $file === '..') {
continue;
}
$path = $uploadDir . $file;
if (is_file($path)) {
$count++;
$size += filesize($path);
}
}
}
return ['count' => $count, 'size' => $size];
}
private function formatBytes(int $bytes): string
{
if ($bytes >= 1073741824) {
return round($bytes / 1073741824, 2) . ' GB';
}
if ($bytes >= 1048576) {
return round($bytes / 1048576, 2) . ' MB';
}
if ($bytes >= 1024) {
return round($bytes / 1024, 2) . ' KB';
}
return $bytes . ' B';
}
}