simedkom/app/Console/Commands/MigrateMediaCooperationCommand.php

331 lines
11 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Enums\ApprovalStatus;
use App\Enums\CooperationStatus;
use App\Models\Cooperation;
use App\Models\CooperationProposal;
use App\Models\PartnerMedia;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class MigrateMediaCooperationCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'migrate:media-cooperation';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Migrate media_cooperation from legacy database → cooperations and cooperation_proposals';
/**
* Cache for migrated cooperation titles to avoid redundant processing.
*/
private array $migratedTitles = [];
/**
* Cache for proposal media to avoid re-importing files.
*/
private array $migratedProposalIds = [];
/**
* Cache for User ID to Partner Media ID mapping.
*/
private array $userToMediaMap = [];
/**
* Execute the console command.
*/
public function handle(): void
{
$this->info('Starting migration: media_cooperation → cooperations...');
$legacyConn = DB::connection('mysql_second');
$query = $legacyConn->table('media_cooperation')->whereNull('deleted_at');
try {
$totalCount = $query->count();
} catch (\Exception $e) {
$this->error('Table media_cooperation not found or error accessing legacy database.');
$this->error($e->getMessage());
return;
}
if ($totalCount === 0) {
$this->warn('No media_cooperation found in legacy database.');
return;
}
$this->info("Found {$totalCount} legacy media_cooperation records.");
// Pre-cache migrated cooperation titles
$this->migratedTitles = DB::table('cooperations')
->pluck('title')
->map(fn ($t) => strtolower(trim($t)))
->flip()
->all();
// Pre-cache proposal media
$this->migratedProposalIds = DB::table('media')
->where('model_type', CooperationProposal::class)
->where('collection_name', 'proposals')
->pluck('model_id')
->flip()
->all();
// Pre-load User to Partner Media mapping
$this->loadUserToMediaMap();
// Load all records and group by name
$all = $query->orderBy('id')->get();
$groups = $all->groupBy(fn ($item) => strtolower(trim($item->name)));
$this->info("Grouped into {$groups->count()} unique cooperation group(s).");
foreach ($groups as $groupKey => $group) {
$this->processGroup($groupKey, $group);
}
$this->newLine();
$this->info('media_cooperation migration completed successfully!');
}
/**
* Load mapping from User ID to Partner Media ID.
* Path: User -> Company -> PartnerMedia
*/
private function loadUserToMediaMap(): void
{
$this->userToMediaMap = DB::table('partner_media')
->join('companies', 'partner_media.company_id', '=', 'companies.id')
->whereNotNull('companies.user_id')
->pluck('partner_media.id', 'companies.user_id')
->all();
}
/**
* Process a single group of media_cooperation records.
*/
private function processGroup(string $groupKey, Collection $group): void
{
$first = $group->first();
$label = Str::limit($first->name, 40);
$this->output->write("<info>[group]</info> Processing: <comment>{$label}</comment>... ");
try {
// Check if cooperation already exists by title
$existingCooperation = DB::table('cooperations')
->whereRaw('LOWER(TRIM(title)) = ?', [$groupKey])
->first();
if ($existingCooperation) {
$cooperationId = $existingCooperation->id;
$this->output->write('<info>JOINING EXISTING</info>... ');
} else {
$cooperationId = $this->insertCooperation(
name: $first->name,
description: $first->description,
initialDate: $group->min('begin'),
finalDate: $group->max('end'),
status: CooperationStatus::COMPLETED->value,
);
$this->output->write('<info>CREATED NEW</info>... ');
}
// Optional: insert cooperation_media if the legacy table has a 'media' column with IDs
if (isset($first->media)) {
$this->insertCooperationMedia($group, $cooperationId);
}
// Migrate proposals belonging to this group's media_cooperation
$this->migrateProposals(
group: $group,
cooperationId: $cooperationId
);
$this->output->writeln('<info>DONE</info>');
} catch (\Exception $e) {
$this->output->writeln('<error>FAILED: '.$e->getMessage().'</error>');
}
}
private function insertCooperation(
string $name,
?string $description,
?string $initialDate,
?string $finalDate,
string $status
): int {
DB::table('cooperations')->insert([
'title' => $name,
'initial_submission_date' => $initialDate,
'final_submission_date' => $finalDate,
'description' => $description ?? '',
'status' => $status,
'created_at' => now(),
'updated_at' => now(),
]);
return (int) DB::getPdo()->lastInsertId();
}
private function insertCooperationMedia(Collection $group, int $cooperationId): void
{
$validMediaIds = DB::table('partner_media')->pluck('id')->toArray();
$mediaStatusMap = [];
foreach ($group as $item) {
if (empty($item->media)) {
continue;
}
$ids = array_map('trim', explode(',', $item->media));
$statuses = array_map('trim', explode(',', $item->status ?? ''));
foreach ($ids as $index => $rawId) {
$mediaId = (int) $rawId;
if (! in_array($mediaId, $validMediaIds)) {
continue;
}
$legacyStatus = $statuses[$index] ?? '1';
$mediaStatusMap[$mediaId] = $this->mapApprovalStatus($legacyStatus);
}
}
foreach ($mediaStatusMap as $mediaId => $approvalStatus) {
DB::table('cooperation_media')->insert([
'cooperation_id' => $cooperationId,
'partner_media_id' => $mediaId,
'status' => $approvalStatus,
'created_at' => now(),
'updated_at' => now(),
]);
}
}
private function migrateProposals(Collection $group, int $cooperationId): void
{
$legacyConn = DB::connection('mysql_second');
$legacyIds = $group->pluck('id')->toArray();
$legacyProposals = $legacyConn->table('propose_media_cooperation')
->whereIn('media_cooperation_id', $legacyIds)
->whereNull('deleted_at')
->get();
foreach ($legacyProposals as $legacyProposal) {
$this->processProposal($legacyProposal, $cooperationId);
}
}
private function processProposal(object $legacy, int $cooperationId): void
{
$userId = $legacy->enhancer;
$partnerMediaId = $this->userToMediaMap[$userId] ?? null;
if (! $partnerMediaId) {
// Fallback or skip if media not found
return;
}
// Check if this proposal already exists to avoid duplication
$exists = DB::table('cooperation_proposals')
->where('cooperation_id', $cooperationId)
->where('partner_media_id', $partnerMediaId)
->exists();
if ($exists) {
return;
}
DB::table('cooperation_proposals')->insert([
'cooperation_id' => $cooperationId,
'partner_media_id' => $partnerMediaId,
'status' => $this->mapApprovalStatus((string) $legacy->status),
'submitted_at' => $legacy->created_at ?? now(),
'created_at' => $legacy->created_at ?? now(),
'updated_at' => $legacy->updated_at ?? now(),
]);
$proposalId = (int) DB::getPdo()->lastInsertId();
// Migrate offer_file if exists and not already migrated
if (! empty($legacy->offer_file) && ! isset($this->migratedProposalIds[$proposalId])) {
$this->importOfferFile($proposalId, $legacy);
}
}
private function importOfferFile(int $proposalId, object $legacy): void
{
$legacyPath = trim($legacy->offer_file);
if (empty($legacyPath) || $legacyPath === '-' || $legacyPath === 'no-image.png') {
return;
}
// Adjust path based on where these files are stored in S3
$sourcePath = 'old-data/proposals/'.$legacyPath;
$fileName = basename($sourcePath);
$legacyDir = dirname($sourcePath);
DB::table('media')->insert([
'model_type' => CooperationProposal::class,
'model_id' => $proposalId,
'uuid' => (string) Str::uuid(),
'collection_name' => 'proposals',
'name' => pathinfo($fileName, PATHINFO_FILENAME),
'file_name' => $fileName,
'mime_type' => $this->guessMime($fileName),
'disk' => 's3',
'conversions_disk' => 's3',
'size' => 0,
'manipulations' => '[]',
'custom_properties' => json_encode([
'feature' => 'proposals',
'date' => Carbon::parse($legacy->created_at ?? now())->toDateString(),
'legacy_dir' => $legacyDir,
]),
'generated_conversions' => '[]',
'responsive_images' => '[]',
'order_column' => 1,
'created_at' => now(),
'updated_at' => now(),
]);
}
private function guessMime(string $fileName): string
{
return match (strtolower(pathinfo($fileName, PATHINFO_EXTENSION))) {
'pdf' => 'application/pdf',
'jpg', 'jpeg' => 'image/jpeg',
'png' => 'image/png',
'doc' => 'application/msword',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
default => 'application/octet-stream',
};
}
private function mapApprovalStatus(string $legacyStatus): int
{
return match ($legacyStatus) {
'0' => ApprovalStatus::PENDING->value,
'1' => ApprovalStatus::ACCEPTED->value,
'2' => ApprovalStatus::REJECTED->value,
default => ApprovalStatus::ACCEPTED->value,
};
}
}