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

Use ITEM_TYPES constant

This commit is contained in:
snipe
2026-08-07 11:57:58 +01:00
parent a9b6cdec40
commit ff10b8139a
4 changed files with 61 additions and 31 deletions

View File

@ -25,7 +25,21 @@ class OrderItem extends Model
protected $table = 'order_items';
// NOTE: `created_by` is deliberately NOT fillable — accepting it via
/**
* The inventory model classes that can legally appear as the
* polymorphic `item` on an OrderItem. Referenced from the item_type
* validation rule and from the backfill / reconcile migrations,
* which each iterate a filtered subset of this list.
*/
public const ITEM_TYPES = [
Accessory::class,
Consumable::class,
Component::class,
Asset::class,
License::class,
];
// NOTE: `created_by` is deliberately NOT fillable, accepting it via
// mass-assignment would let a request attribute a line to any
// user_id the caller writes. All internal writers set it via
// explicit `$line->created_by = auth()->id()`.
@ -42,6 +56,18 @@ class OrderItem extends Model
'price' => 'decimal:4',
];
/**
* Static rules that don't depend on runtime data. The `item_type`
* rule is composed from ITEM_TYPES in getRules() below so the
* polymorphic class list has one source of truth.
*/
public $rules = [
'order_id' => 'required|integer|exists:orders,id',
'item_id' => 'required|integer|min:1',
'qty' => 'required|integer|min:1',
'price' => 'nullable|numeric|gte:0|max:99999999999999999.99',
];
/**
* Validation for the polymorphic pivot. item_id can't be validated
* exists-style without a custom rule (the target table is a runtime
@ -50,13 +76,12 @@ class OrderItem extends Model
* item_id to caller sanity. order_id blocks reference to a deleted
* Order via the exists rule (ignoring soft-deleted rows).
*/
public $rules = [
'order_id' => 'required|integer|exists:orders,id',
'item_type' => 'required|string|in:App\\Models\\Accessory,App\\Models\\Consumable,App\\Models\\Component,App\\Models\\Asset,App\\Models\\License',
'item_id' => 'required|integer|min:1',
'qty' => 'required|integer|min:1',
'price' => 'nullable|numeric|gte:0|max:99999999999999999.99',
];
public function getRules(): array
{
return $this->rules + [
'item_type' => 'required|string|in:'.implode(',', self::ITEM_TYPES),
];
}
public function order(): BelongsTo
{

View File

@ -7,7 +7,6 @@ use App\Models\Actionlog;
class AccessoryObserver
{
/**
* Listen to the User created event.
*

View File

@ -1,5 +1,7 @@
<?php
use App\Models\License;
use App\Models\OrderItem;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
@ -20,7 +22,7 @@ use Illuminate\Support\Facades\Schema;
* recur across different suppliers or across companies in FMCS installs,
* and collapsing them would rewrite history.
*
* Rows with a null / empty order_number are skipped they represent
* Rows with a null / empty order_number are skipped: they represent
* inventory rows never associated with any order, and there's nothing
* for the Orders table to record.
*
@ -29,19 +31,6 @@ use Illuminate\Support\Facades\Schema;
*/
return new class extends Migration
{
/**
* Model class => source table name. Order determines the sequence
* OrderItem rows are created in (purely cosmetic — a chronological
* report would sort by Order.purchase_date or Order.created_at
* anyway).
*/
private const SOURCES = [
\App\Models\Accessory::class => 'accessories',
\App\Models\Consumable::class => 'consumables',
\App\Models\Component::class => 'components',
\App\Models\Asset::class => 'assets',
];
public function up(): void
{
// Order dedupe cache: (order_number|supplier_id|company_id) => order_id.
@ -52,13 +41,23 @@ return new class extends Migration
// inventory tables didn't have their own currency column, and
// stamping every historical Order with today's system
// default_currency would fabricate information we don't
// actually have orders placed years ago may have been in a
// actually have, orders placed years ago may have been in a
// different currency than the install's current setting.
// Downstream display code can fall back to
// $snipeSettings->default_currency at render time (matching how
// the pre-Orders info panels rendered purchase_cost anyway).
//
// Order iteration is purely cosmetic: a chronological report
// would sort by Order.purchase_date or Order.created_at.
//
// License is filtered out because per-seat product-key
// semantics need their own design pass before License can join
// the Orders flow cleanly.
$sourceClasses = array_diff(OrderItem::ITEM_TYPES, [License::class]);
foreach ($sourceClasses as $modelClass) {
$table = (new $modelClass)->getTable();
foreach (self::SOURCES as $modelClass => $table) {
// Defensive skip: if a source table has already had its
// order_number column dropped (partial rerun, hand rollback,
// schema drift), there's nothing to backfill from and the
@ -129,10 +128,11 @@ return new class extends Migration
public function down(): void
{
// Reversible only in a coarse sense the drop wipes every row
// Reversible only in a coarse sense: the delete wipes every row
// in both tables rather than trying to reconstruct the original
// per-source-column state. Combined with the follow-up
// drop-columns migration (which does have a real down()), a full
// rename-columns migration (143000, which does have a real
// down() that restores the original column names), a full
// rollback restores the pre-Orders layout.
DB::table('order_items')->delete();
DB::table('orders')->delete();

View File

@ -1,9 +1,9 @@
<?php
use App\Enums\ActionType;
use App\Models\Accessory;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\Asset;
use App\Models\License;
use App\Models\OrderItem;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
@ -31,7 +31,13 @@ return new class extends Migration
{
public function up(): void
{
foreach ([Accessory::class, Consumable::class, Component::class] as $modelClass) {
// Filter OrderItem::ITEM_TYPES down to the models with a scalar
// `qty` column that this reconciliation actually operates on.
// Asset is 1:1 (no qty column, one row per unit), and License
// is excluded per the note above.
$qtyModels = array_diff(OrderItem::ITEM_TYPES, [Asset::class, License::class]);
foreach ($qtyModels as $modelClass) {
$this->reconcileFor($modelClass);
}
}