This commit is contained in:
Simon Pocrnjič
2025-12-26 22:39:58 +01:00
parent f8623a6071
commit dea7432deb
55 changed files with 7977 additions and 1983 deletions
@@ -0,0 +1,156 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class FixImportMappingEntities extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'import:fix-mapping-entities {--dry-run : Show changes without applying them}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Fix entity names in import_mappings table to use canonical roots';
/**
* Entity name mappings from incorrect to correct canonical roots
*/
protected array $entityMapping = [
'contracts' => 'contract',
'contract' => 'contract',
'client_cases' => 'client_case',
'client_case' => 'client_case',
'person_addresses' => 'address',
'addresses' => 'address',
'address' => 'address',
'person_phones' => 'phone',
'phones' => 'phone',
'phone' => 'phone',
'emails' => 'email',
'email' => 'email',
'activities' => 'activity',
'activity' => 'activity',
'persons' => 'person',
'person' => 'person',
'accounts' => 'account',
'account' => 'account',
'payments' => 'payment',
'payment' => 'payment',
'bookings' => 'booking',
'booking' => 'booking',
];
/**
* Execute the console command.
*/
public function handle()
{
$dryRun = $this->option('dry-run');
if ($dryRun) {
$this->info('Running in DRY-RUN mode - no changes will be made');
}
$mappings = DB::table('import_mappings')
->whereNotNull('entity')
->where('entity', '!=', '')
->get();
if ($mappings->isEmpty()) {
$this->info('No mappings found to fix.');
return 0;
}
$this->info("Found {$mappings->count()} mappings to check");
$this->newLine();
$updates = [];
$unchanged = 0;
foreach ($mappings as $mapping) {
$currentEntity = trim($mapping->entity);
if (isset($this->entityMapping[$currentEntity])) {
$correctEntity = $this->entityMapping[$currentEntity];
if ($currentEntity !== $correctEntity) {
$updates[] = [
'id' => $mapping->id,
'current' => $currentEntity,
'correct' => $correctEntity,
'source' => $mapping->source_column,
'target' => $mapping->target_field,
];
} else {
$unchanged++;
}
} else {
$this->warn("Unknown entity type: {$currentEntity} (ID: {$mapping->id})");
}
}
if (empty($updates)) {
$this->info("✓ All {$unchanged} mappings already have correct entity names!");
return 0;
}
// Display changes
$this->info("Changes to be made:");
$this->newLine();
$table = [];
foreach ($updates as $update) {
$table[] = [
$update['id'],
$update['source'],
$update['target'],
$update['current'],
$update['correct'],
];
}
$this->table(
['ID', 'Source Column', 'Target Field', 'Current Entity', 'Correct Entity'],
$table
);
$this->newLine();
$this->info("Total changes: " . count($updates));
$this->info("Unchanged: {$unchanged}");
if ($dryRun) {
$this->newLine();
$this->warn('DRY-RUN mode: No changes were made. Run without --dry-run to apply changes.');
return 0;
}
// Confirm before proceeding
if (!$this->confirm('Do you want to apply these changes?', true)) {
$this->info('Operation cancelled.');
return 0;
}
// Apply updates
$updated = 0;
foreach ($updates as $update) {
DB::table('import_mappings')
->where('id', $update['id'])
->update(['entity' => $update['correct']]);
$updated++;
}
$this->newLine();
$this->info("✓ Successfully updated {$updated} mappings!");
return 0;
}
}
@@ -0,0 +1,113 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class PopulateImportMappingEntities extends Command
{
protected $signature = 'import:populate-mapping-entities {--dry-run : Show changes without applying them}';
protected $description = 'Populate entity column from target_field for mappings where entity is null';
protected array $entityMap = [
'contracts' => 'contract',
'client_cases' => 'client_case',
'person_addresses' => 'address',
'person_phones' => 'phone',
'emails' => 'email',
'activities' => 'activity',
'payments' => 'payment',
'accounts' => 'account',
'persons' => 'person',
'person' => 'person',
'contract' => 'contract',
'client_case' => 'client_case',
'address' => 'address',
'phone' => 'phone',
'email' => 'email',
'activity' => 'activity',
'payment' => 'payment',
'account' => 'account',
];
public function handle()
{
$dryRun = $this->option('dry-run');
$this->info('Populating entity column from target_field...');
if ($dryRun) {
$this->warn('DRY RUN MODE - No changes will be made');
}
// Get all mappings where entity is null
$mappings = DB::table('import_mappings')
->whereNull('entity')
->get();
if ($mappings->isEmpty()) {
$this->info('No mappings found with null entity.');
return 0;
}
$this->info("Found {$mappings->count()} mappings to process.");
$this->newLine();
$updated = 0;
$skipped = 0;
foreach ($mappings as $mapping) {
$targetField = $mapping->target_field;
// Parse the target_field to extract entity and field
if (str_contains($targetField, '.')) {
[$rawEntity, $field] = explode('.', $targetField, 2);
} elseif (str_contains($targetField, '->')) {
[$rawEntity, $field] = explode('->', $targetField, 2);
} else {
$this->warn("Skipping mapping ID {$mapping->id}: Cannot parse target_field '{$targetField}'");
$skipped++;
continue;
}
$rawEntity = trim($rawEntity);
$field = trim($field);
// Map to canonical entity name
$canonicalEntity = $this->entityMap[$rawEntity] ?? $rawEntity;
$this->line(sprintf(
"ID %d: '%s' -> '%s' => entity='%s', field='%s'",
$mapping->id,
$mapping->source_column,
$targetField,
$canonicalEntity,
$field
));
if (!$dryRun) {
DB::table('import_mappings')
->where('id', $mapping->id)
->update([
'entity' => $canonicalEntity,
'target_field' => $field,
]);
$updated++;
}
}
$this->newLine();
if ($dryRun) {
$this->info("Dry run complete. Would have updated {$mappings->count()} mappings.");
} else {
$this->info("Successfully updated {$updated} mappings.");
}
if ($skipped > 0) {
$this->warn("Skipped {$skipped} mappings that couldn't be parsed.");
}
return 0;
}
}
@@ -0,0 +1,145 @@
<?php
namespace App\Console\Commands;
use App\Models\Import;
use App\Services\Import\ImportSimulationServiceV2;
use Illuminate\Console\Command;
class SimulateImportV2Command extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'import:simulate-v2 {import_id} {--limit=100 : Number of rows to simulate} {--verbose : Include detailed information}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Simulate ImportServiceV2 without persisting data';
/**
* Execute the console command.
*/
public function handle(ImportSimulationServiceV2 $service): int
{
$importId = $this->argument('import_id');
$limit = (int) $this->option('limit');
$verbose = (bool) $this->option('verbose');
$import = Import::find($importId);
if (! $import) {
$this->error("Import #{$importId} not found.");
return 1;
}
$this->info("Simulating import #{$importId} - {$import->file_name}");
$this->info("Client: ".($import->client->name ?? 'N/A'));
$this->info("Limit: {$limit} rows");
$this->line('');
$result = $service->simulate($import, $limit, $verbose);
if (! $result['success']) {
$this->error('Simulation failed: '.$result['error']);
return 1;
}
$this->info("✓ Simulated {$result['total_simulated']} rows");
$this->line('');
// Display summaries
if (! empty($result['summaries'])) {
$this->info('=== Entity Summaries ===');
$summaryRows = [];
foreach ($result['summaries'] as $entity => $stats) {
$summaryRows[] = [
'entity' => $entity,
'create' => $stats['create'],
'update' => $stats['update'],
'skip' => $stats['skip'],
'invalid' => $stats['invalid'],
'total' => array_sum($stats),
];
}
$this->table(
['Entity', 'Create', 'Update', 'Skip', 'Invalid', 'Total'],
$summaryRows
);
$this->line('');
}
// Display row previews (first 5)
if (! empty($result['rows'])) {
$this->info('=== Row Previews (first 5) ===');
foreach (array_slice($result['rows'], 0, 5) as $row) {
$this->line("Row #{$row['row_number']}:");
if (! empty($row['entities'])) {
foreach ($row['entities'] as $entity => $data) {
$action = $data['action'];
$color = match ($action) {
'create' => 'green',
'update' => 'yellow',
'skip' => 'gray',
'invalid', 'error' => 'red',
default => 'white',
};
$line = " {$entity}: <fg={$color}>{$action}</>";
if (isset($data['reference'])) {
$line .= " ({$data['reference']})";
}
if (isset($data['existing_id'])) {
$line .= " [ID: {$data['existing_id']}]";
}
$this->line($line);
if ($verbose && ! empty($data['changes'])) {
foreach ($data['changes'] as $field => $change) {
$this->line(" {$field}: {$change['old']}{$change['new']}");
}
}
if (! empty($data['errors'])) {
foreach ($data['errors'] as $error) {
$this->error("{$error}");
}
}
}
}
if (! empty($row['warnings'])) {
foreach ($row['warnings'] as $warning) {
$this->warn("{$warning}");
}
}
if (! empty($row['errors'])) {
foreach ($row['errors'] as $error) {
$this->error("{$error}");
}
}
$this->line('');
}
}
$this->info('Simulation completed successfully.');
return 0;
}
}
@@ -0,0 +1,68 @@
<?php
namespace App\Console\Commands;
use App\Jobs\ProcessLargeImportJob;
use App\Models\Import;
use App\Services\Import\ImportServiceV2;
use Illuminate\Console\Command;
class TestImportV2Command extends Command
{
protected $signature = 'import:test-v2 {import_id : The import ID to process} {--queue : Process via queue}';
protected $description = 'Test ImportServiceV2 with an existing import';
public function handle()
{
$importId = $this->argument('import_id');
$useQueue = $this->option('queue');
$import = Import::find($importId);
if (! $import) {
$this->error("Import {$importId} not found.");
return 1;
}
$this->info("Processing import: {$import->id} ({$import->file_name})");
$this->info("Source: {$import->source_type}");
$this->info("Status: {$import->status}");
$this->newLine();
if ($useQueue) {
$this->info('Dispatching to queue...');
ProcessLargeImportJob::dispatch($import, auth()->id());
$this->info('Job dispatched successfully. Monitor queue for progress.');
return 0;
}
$this->info('Processing synchronously...');
$service = app(ImportServiceV2::class);
try {
$results = $service->process($import, auth()->user());
$this->newLine();
$this->info('Processing completed!');
$this->table(
['Metric', 'Count'],
[
['Total rows', $results['total']],
['Imported', $results['imported']],
['Skipped', $results['skipped']],
['Invalid', $results['invalid']],
]
);
return 0;
} catch (\Throwable $e) {
$this->error('Processing failed: '.$e->getMessage());
$this->error($e->getTraceAsString());
return 1;
}
}
}