3
0
mirror of https://github.com/snipe/snipe-it.git synced 2026-08-18 11:15:42 +00:00

Updated DB migrations

This commit is contained in:
snipe
2026-08-05 11:42:33 +01:00
parent 20e9d8dacb
commit ec7c1faada
4 changed files with 189 additions and 60 deletions

View File

@ -1,60 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Drop the now-orphaned `order_number` string columns from the five
* inventory tables. Runs after the `backfill_orders_from_inventory_tables`
* migration has moved every non-null value into a real Order + OrderItem
* pair; anything still pointing at the parent column is either null or
* legitimately stale.
*
* Deliberately does not touch Asset / License business logic — those
* models had already dropped `order_number` from their fillable /
* searchable / validation arrays in the same PR. The DB column is the
* last piece of the old shape.
*/
return new class extends Migration
{
private const TABLES = [
'accessories',
'consumables',
'components',
'assets',
'licenses',
];
public function up(): void
{
foreach (self::TABLES as $table) {
if (! Schema::hasColumn($table, 'order_number')) {
continue;
}
Schema::table($table, function (Blueprint $t) {
$t->dropColumn('order_number');
});
}
}
public function down(): void
{
// Restore as a plain nullable string column. No data recovery —
// callers that want the pre-Orders values back need to roll the
// backfill migration first and then rehydrate this column from
// the OrderItem rows themselves. Leaving that manual because
// the rehydration is inherently lossy (many-to-one order → one
// row here means we'd have to pick one Order per parent row).
foreach (self::TABLES as $table) {
if (Schema::hasColumn($table, 'order_number')) {
continue;
}
Schema::table($table, function (Blueprint $t) {
$t->string('order_number')->nullable();
});
}
}
};

View File

@ -0,0 +1,56 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Rename `order_number` to `legacy_order_number` on the aggregate
* inventory tables that moved to Orders / OrderItems. Preserves the
* historical row-level value as a fallback under a new name. The
* backfill migration has already copied non-null values into Order
* rows, but keeping the raw column lets reports and audits reach it
* if the polymorphic path ever needs to be cross-checked.
*
* Assets keep their own `order_number` column as the canonical
* single-value acquisition reference — assets are 1:1 with a
* transaction, and the column is still authoritative for that shape.
*
* Licenses are intentionally out of scope for this rename — the
* License model isn't participating in the Orders flow yet
* (per-seat product-key semantics need their own design pass).
*/
return new class extends Migration
{
private const TABLES = [
'accessories',
'consumables',
'components',
];
public function up(): void
{
foreach (self::TABLES as $table) {
if (! Schema::hasColumn($table, 'order_number')) {
continue;
}
Schema::table($table, function (Blueprint $t) {
$t->renameColumn('order_number', 'legacy_order_number');
});
}
}
public function down(): void
{
foreach (self::TABLES as $table) {
if (! Schema::hasColumn($table, 'legacy_order_number')) {
continue;
}
Schema::table($table, function (Blueprint $t) {
$t->renameColumn('legacy_order_number', 'order_number');
});
}
}
};

View File

@ -0,0 +1,82 @@
<?php
use App\Enums\ActionType;
use App\Models\Accessory;
use App\Models\Component;
use App\Models\Consumable;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/**
* One-shot reconciliation of parent `qty` against the action_logs
* ledger for the three inventory models with qty adjustments
* (Accessory, Consumable, Component). Runs the invariant:
*
* parent.qty = SUM(action_logs.quantity WHERE item = parent
* AND action_type IN ('create', 'qty_adjust'))
*
* Any row where the parent column disagrees with the ledger sum gets
* corrected to the ledger value. The AdjustsQuantity trait wraps its
* qty writes in a DB transaction so from this migration forward the
* two stay in sync. This migration catches historical drift that
* pre-dates the trait (direct `$model->qty = ...` writes that skipped
* the log).
*
* License is deliberately excluded. License.seats reconciliation
* would also need to create or destroy LicenseSeat pivot rows to keep
* the seat-tracking invariants, which is beyond a data-only fix.
*/
return new class extends Migration
{
public function up(): void
{
foreach ([Accessory::class, Consumable::class, Component::class] as $modelClass) {
$this->reconcileFor($modelClass);
}
}
public function down(): void
{
// No-op. We can't restore whatever drift existed before the
// reconciliation because the ledger IS the correct value.
}
private function reconcileFor(string $modelClass): void
{
$qtyAdjust = ActionType::QuantityAdjust->value;
$modelClass::query()->chunkById(500, function ($rows) use ($modelClass, $qtyAdjust) {
foreach ($rows as $model) {
$expected = (int) DB::table('action_logs')
->where('item_type', $modelClass)
->where('item_id', $model->id)
->whereIn('action_type', ['create', $qtyAdjust])
->whereNull('deleted_at')
->sum('quantity');
$actual = (int) $model->qty;
if ($expected === $actual) {
continue;
}
Log::info(sprintf(
'Reconciling %s#%d qty: %d -> %d (ledger sum)',
$modelClass,
$model->id,
$actual,
$expected,
));
// Direct DB update to bypass observers and events. The
// AdjustsQuantity trait would try to write another
// action_log entry, which would defeat the "sum equals
// the ledger" invariant we just calculated.
DB::table((new $modelClass)->getTable())
->where('id', $model->id)
->update(['qty' => $expected]);
}
});
}
};

View File

@ -0,0 +1,51 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Follow-up index coverage for the Orders / OrderItems tables that
* the initial create migration didn't include.
*
* `orders(order_number, supplier_id, company_id)` composite: matches
* the dedupe key used by every firstOrCreate on Orders (adjust-quantity
* flow, AssetObserver::created, ItemImporter). Without it MySQL /
* MariaDB pick one of the single-column indexes and filter the rest,
* which gets progressively slower as the orders table grows. The
* existing single-column indexes stay because supplier_id and
* company_id are also filtered on their own (e.g. FMCS scoping).
*
* `deleted_at` indexes on both tables: every Eloquent read appends an
* implicit `WHERE deleted_at IS NULL` from SoftDeletes. Cheap now,
* hot spot later on any install with a heavy history of adjustments.
*/
return new class extends Migration
{
public function up(): void
{
Schema::table('orders', function (Blueprint $table) {
$table->index(
['order_number', 'supplier_id', 'company_id'],
'orders_order_number_supplier_id_company_id_index',
);
$table->index('deleted_at');
});
Schema::table('order_items', function (Blueprint $table) {
$table->index('deleted_at');
});
}
public function down(): void
{
Schema::table('order_items', function (Blueprint $table) {
$table->dropIndex(['deleted_at']);
});
Schema::table('orders', function (Blueprint $table) {
$table->dropIndex('orders_order_number_supplier_id_company_id_index');
$table->dropIndex(['deleted_at']);
});
}
};