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:
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -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]);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -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']);
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user