Squashed commit of the following:
commit 6ae625d4fdc11989950ef0678db62fdb52be7c8c Author: ncoronel <ncoronel@quo.ar> Date: Wed Sep 2 09:46:18 2026 -0300 refactor(tickets): enhance search functionality for tickets by client names and formatted amounts commit 098e525d32be9cb6487bc01a3d120aa0f2386a15 Author: ncoronel <ncoronel@quo.ar> Date: Wed Sep 2 09:09:30 2026 -0300 fix(tickets): protect referenced purchase items commit 210db17f2f6a6a4a509297b773a2b5d3e3149a3d Author: ncoronel <ncoronel@quo.ar> Date: Wed Sep 2 08:59:24 2026 -0300 test(tickets): cover purchase item references commit 6a004713c4bad622dfa5e26394458925db1cb5a7 Author: ncoronel <ncoronel@quo.ar> Date: Wed Sep 2 08:59:02 2026 -0300 refactor(tickets): reference purchase items directly
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('tickets', function (Blueprint $table): void {
|
||||
$table->foreignId('source_purchase_item_id')
|
||||
->nullable()
|
||||
->after('source_purchase_id')
|
||||
->constrained('compra_items')
|
||||
->cascadeOnUpdate()
|
||||
->nullOnDelete();
|
||||
});
|
||||
|
||||
DB::table('tickets')
|
||||
->whereNotNull('source_purchase_id')
|
||||
->whereNull('source_purchase_item_id')
|
||||
->orderBy('id')
|
||||
->chunkById(500, function (Collection $tickets): void {
|
||||
$purchaseIds = $tickets->pluck('source_purchase_id')->unique()->values();
|
||||
$itemsByPurchase = DB::table('compra_items')
|
||||
->whereIn('compra_id', $purchaseIds)
|
||||
->get(['id', 'compra_id', 'source_catalog_item_id', 'source_variant_id'])
|
||||
->groupBy('compra_id');
|
||||
$bundleIds = $itemsByPurchase->flatten(1)
|
||||
->pluck('source_catalog_item_id')
|
||||
->filter()
|
||||
->unique()
|
||||
->values();
|
||||
$componentsByBundle = DB::table('bundle_components')
|
||||
->whereIn('bundle_catalog_item_id', $bundleIds)
|
||||
->get([
|
||||
'bundle_catalog_item_id',
|
||||
'component_catalog_item_id',
|
||||
'component_variant_id',
|
||||
])
|
||||
->groupBy('bundle_catalog_item_id');
|
||||
|
||||
foreach ($tickets as $ticket) {
|
||||
$candidates = collect($itemsByPurchase->get($ticket->source_purchase_id, []))
|
||||
->filter(function (object $item) use ($ticket, $componentsByBundle): bool {
|
||||
if ($this->sameCatalogTarget($item, $ticket)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return collect($componentsByBundle->get($item->source_catalog_item_id, []))
|
||||
->contains(fn (object $component): bool => $this->sameBundleTarget($component, $ticket));
|
||||
})
|
||||
->pluck('id')
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
if ($candidates->count() === 1) {
|
||||
DB::table('tickets')->where('id', $ticket->id)->update([
|
||||
'source_purchase_item_id' => $candidates->sole(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('tickets', function (Blueprint $table): void {
|
||||
$table->dropConstrainedForeignId('source_purchase_item_id');
|
||||
});
|
||||
}
|
||||
|
||||
private function sameCatalogTarget(object $item, object $ticket): bool
|
||||
{
|
||||
return (int) $item->source_catalog_item_id === (int) $ticket->source_catalog_item_id
|
||||
&& $this->sameNullableId($item->source_variant_id, $ticket->source_variant_id);
|
||||
}
|
||||
|
||||
private function sameBundleTarget(object $component, object $ticket): bool
|
||||
{
|
||||
return (int) $component->component_catalog_item_id === (int) $ticket->source_catalog_item_id
|
||||
&& $this->sameNullableId($component->component_variant_id, $ticket->source_variant_id);
|
||||
}
|
||||
|
||||
private function sameNullableId(mixed $left, mixed $right): bool
|
||||
{
|
||||
return $left === null || $right === null
|
||||
? $left === null && $right === null
|
||||
: (int) $left === (int) $right;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$unresolved = DB::table('tickets')
|
||||
->whereNotNull('source_purchase_id')
|
||||
->whereNull('source_purchase_item_id')
|
||||
->orderBy('id')
|
||||
->limit(20)
|
||||
->pluck('id');
|
||||
|
||||
if ($unresolved->isNotEmpty()) {
|
||||
throw new RuntimeException(
|
||||
'No se puede eliminar tickets.source_purchase_id: hay tickets sin un compra_item inequívoco. '
|
||||
.'IDs: '.$unresolved->implode(', ')
|
||||
);
|
||||
}
|
||||
|
||||
$inconsistent = DB::table('tickets')
|
||||
->join('compra_items', 'compra_items.id', '=', 'tickets.source_purchase_item_id')
|
||||
->whereNotNull('tickets.source_purchase_id')
|
||||
->whereColumn('tickets.source_purchase_id', '!=', 'compra_items.compra_id')
|
||||
->orderBy('tickets.id')
|
||||
->limit(20)
|
||||
->pluck('tickets.id');
|
||||
|
||||
if ($inconsistent->isNotEmpty()) {
|
||||
throw new RuntimeException(
|
||||
'No se puede eliminar tickets.source_purchase_id: hay referencias de compra inconsistentes. '
|
||||
.'IDs: '.$inconsistent->implode(', ')
|
||||
);
|
||||
}
|
||||
|
||||
Schema::table('tickets', function (Blueprint $table): void {
|
||||
$table->dropConstrainedForeignId('source_purchase_id');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('tickets', function (Blueprint $table): void {
|
||||
$table->foreignId('source_purchase_id')
|
||||
->nullable()
|
||||
->after('description')
|
||||
->constrained('compras')
|
||||
->cascadeOnUpdate()
|
||||
->nullOnDelete();
|
||||
});
|
||||
|
||||
DB::table('tickets')
|
||||
->whereNotNull('source_purchase_item_id')
|
||||
->orderBy('id')
|
||||
->chunkById(500, function (Collection $tickets): void {
|
||||
$purchaseIdsByItem = DB::table('compra_items')
|
||||
->whereIn('id', $tickets->pluck('source_purchase_item_id'))
|
||||
->pluck('compra_id', 'id');
|
||||
|
||||
foreach ($tickets as $ticket) {
|
||||
DB::table('tickets')->where('id', $ticket->id)->update([
|
||||
'source_purchase_id' => $purchaseIdsByItem->get($ticket->source_purchase_item_id),
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('tickets', function (Blueprint $table): void {
|
||||
$table->dropForeign(['source_purchase_item_id']);
|
||||
});
|
||||
|
||||
Schema::table('tickets', function (Blueprint $table): void {
|
||||
$table->foreign('source_purchase_item_id')
|
||||
->references('id')
|
||||
->on('compra_items')
|
||||
->cascadeOnUpdate()
|
||||
->restrictOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('tickets', function (Blueprint $table): void {
|
||||
$table->dropForeign(['source_purchase_item_id']);
|
||||
});
|
||||
|
||||
Schema::table('tickets', function (Blueprint $table): void {
|
||||
$table->foreign('source_purchase_item_id')
|
||||
->references('id')
|
||||
->on('compra_items')
|
||||
->cascadeOnUpdate()
|
||||
->nullOnDelete();
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user