Files
Investidor-V2/app/Services/Mne/CompatibilityBackfill.php
T
nala 3dc7c5c649
tests / ci (push) Canceled after 0s
commit 30/9
2026-09-30 09:05:43 +02:00

74 lines
4.0 KiB
PHP

<?php
namespace App\Services\Mne;
use Illuminate\Support\Facades\DB;
use Ramsey\Uuid\Uuid;
final class CompatibilityBackfill
{
/** @return array<string, int> */
public function run(int $batchSize = 100): array
{
$counts = [];
foreach (['identity' => ['users', 'identity_links', 'user_id'], 'sector' => ['sectors', 'sector_links', 'sector_id'],
'business' => ['existing_businesses', 'business_links', 'legacy_business_id']] as $domain => [$source, $target, $key]) {
// Start again on each invocation to detect edits behind a previous checkpoint.
$last = null;
$processed = 0;
do {
$batch = DB::transaction(function () use ($domain, $source, $target, $key, $last, $processed, $batchSize) {
DB::select('SELECT pg_advisory_xact_lock(290926,170004)');
$query = DB::table('public.'.$source)->orderBy('id')->limit($batchSize)->lockForUpdate();
if ($last !== null) {
$query->where('id', '>', $last);
}
$rows = $query->get();
$checksums = [];
foreach ($rows as $row) {
$checksum = hash('sha256', json_encode((array) $row, JSON_THROW_ON_ERROR));
$checksums[] = $checksum;
$stableId = (string) Uuid::uuid5(Uuid::NAMESPACE_URL, 'investmoz:'.$domain.':'.$row->id);
$data = ['id' => $stableId, $key => $row->id, 'source_system' => 'public.'.$source,
'source_checksum' => $checksum, 'status' => 'pending'];
if ($domain === 'sector') {
$data['canonical_sector_id'] = $stableId;
$data['source_code'] = $row->slug;
$data['status'] = $row->is_active && $row->deleted_at === null ? 'active' : 'pending';
}
$existing = DB::table('mne.'.$target)->where($key, $row->id)->first();
if (! $existing) {
DB::table('mne.'.$target)->insert($data);
} elseif ($existing->source_checksum !== $checksum) {
// Changed sources invalidate previous reconciliation; never activate by inference.
DB::table('mne.'.$target)->where($key, $row->id)->update(['source_checksum' => $checksum, 'status' => 'pending']);
}
if ($domain !== 'sector' && (! $existing || $existing->status !== 'active' || $existing->source_checksum !== $checksum)) {
DB::table('mne.compatibility_rejections')->upsert([[
'id' => $stableId, 'domain' => $domain, 'source_key' => (string) $row->id,
'code' => 'CANONICAL_MAPPING_REQUIRED', 'cause' => 'Explicit canonical mapping and reconciliation required',
'resolved_at' => null, 'resolution' => null,
]], ['domain', 'source_key', 'code'], ['cause', 'resolved_at', 'resolution']);
}
}
if ($rows->isNotEmpty() || $processed === 0) {
DB::table('mne.compatibility_checkpoints')->updateOrInsert(['domain' => $domain], [
'last_key' => $rows->isEmpty() ? $last : (string) $rows->last()->id,
'processed' => $processed + $rows->count(), 'checksum' => hash('sha256', implode('', $checksums)), 'updated_at' => now(),
]);
}
return $rows;
});
$processed += $batch->count();
if ($batch->isNotEmpty()) {
$last = $batch->last()->id;
}
} while ($batch->count() === $batchSize);
$counts[$domain] = $processed;
}
return $counts;
}
}