feat(生產/庫存): 實作生產管理模組與批號追溯功能
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* 新增批號追溯相關欄位至 inventories 資料表。
|
||||
* 批號格式:{商品代號}-{來源國家}-{入庫日期}-{批次流水號}
|
||||
* 完整格式(含箱號):{商品代號}-{來源國家}-{入庫日期}-{批次流水號}-{箱號}
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Step 1: 新增批號相關欄位
|
||||
Schema::table('inventories', function (Blueprint $table) {
|
||||
// 批號組成:{商品代號}-{來源國家}-{入庫日期}-{批次流水號}
|
||||
$table->string('batch_number', 50)->nullable()->after('location')
|
||||
->comment('批號 (格式: AB-VN-20260119-01)');
|
||||
$table->string('box_number', 10)->nullable()->after('batch_number')
|
||||
->comment('箱號 (如: 01, 02)');
|
||||
|
||||
// 批號解析欄位(方便查詢與排序)
|
||||
$table->string('origin_country', 10)->nullable()->after('box_number')
|
||||
->comment('來源國家代碼 (如: VN, TW)');
|
||||
$table->date('arrival_date')->nullable()->after('origin_country')
|
||||
->comment('入庫日期');
|
||||
$table->date('expiry_date')->nullable()->after('arrival_date')
|
||||
->comment('效期');
|
||||
|
||||
// 來源追溯
|
||||
$table->foreignId('source_purchase_order_id')->nullable()->after('expiry_date')
|
||||
->constrained('purchase_orders')->nullOnDelete()
|
||||
->comment('來源採購單');
|
||||
|
||||
// 品質狀態
|
||||
$table->enum('quality_status', ['normal', 'frozen', 'rejected'])
|
||||
->default('normal')->after('source_purchase_order_id')
|
||||
->comment('品質狀態:正常/凍結/退貨');
|
||||
$table->text('quality_remark')->nullable()->after('quality_status')
|
||||
->comment('品質異常備註');
|
||||
});
|
||||
|
||||
// Step 2: 為現有資料設定預設批號 (LEGACY-{id})
|
||||
DB::statement("UPDATE inventories SET batch_number = CONCAT('LEGACY-', id) WHERE batch_number IS NULL");
|
||||
|
||||
// Step 3: 將 batch_number 改為必填
|
||||
Schema::table('inventories', function (Blueprint $table) {
|
||||
$table->string('batch_number', 50)->nullable(false)->change();
|
||||
});
|
||||
|
||||
// Step 4: 新增批號相關索引 (不刪除舊索引,因為有外鍵依賴)
|
||||
// 舊的 warehouse_product_unique 保留,新增更精確的批號索引
|
||||
Schema::table('inventories', function (Blueprint $table) {
|
||||
$table->index(['warehouse_id', 'product_id', 'batch_number'], 'inventories_batch_lookup');
|
||||
$table->index(['arrival_date'], 'inventories_arrival_date');
|
||||
$table->index(['quality_status'], 'inventories_quality_status');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('inventories', function (Blueprint $table) {
|
||||
// 移除索引
|
||||
$table->dropIndex('inventories_batch_lookup');
|
||||
$table->dropIndex('inventories_arrival_date');
|
||||
$table->dropIndex('inventories_quality_status');
|
||||
|
||||
// 移除新增欄位
|
||||
$table->dropForeign(['source_purchase_order_id']);
|
||||
$table->dropColumn([
|
||||
'batch_number',
|
||||
'box_number',
|
||||
'origin_country',
|
||||
'arrival_date',
|
||||
'expiry_date',
|
||||
'source_purchase_order_id',
|
||||
'quality_status',
|
||||
'quality_remark',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* 生產工單主表,記錄每次生產的成品資訊。
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('production_orders', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('code', 50)->unique()->comment('生產單號 (如: PRO-20260121-001)');
|
||||
|
||||
// 成品資訊
|
||||
$table->foreignId('product_id')->constrained()->onDelete('restrict')
|
||||
->comment('成品商品');
|
||||
$table->string('output_batch_number', 50)->comment('成品批號');
|
||||
$table->string('output_box_count', 10)->nullable()->comment('成品箱數');
|
||||
$table->decimal('output_quantity', 10, 2)->comment('生產數量');
|
||||
|
||||
// 入庫倉庫
|
||||
$table->foreignId('warehouse_id')->constrained()->onDelete('restrict')
|
||||
->comment('入庫倉庫');
|
||||
|
||||
// 生產資訊
|
||||
$table->date('production_date')->comment('生產日期');
|
||||
$table->date('expiry_date')->nullable()->comment('成品效期');
|
||||
|
||||
// 操作人員
|
||||
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete()
|
||||
->comment('操作人員');
|
||||
|
||||
// 狀態與備註
|
||||
$table->enum('status', ['draft', 'completed', 'cancelled'])
|
||||
->default('completed')->comment('狀態:草稿/完成/取消');
|
||||
$table->text('remark')->nullable()->comment('備註');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
// 索引
|
||||
$table->index(['production_date', 'product_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('production_orders');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* 生產工單明細表 (BOM),記錄使用的原物料。
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('production_order_items', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// 所屬生產工單
|
||||
$table->foreignId('production_order_id')->constrained()->onDelete('cascade')
|
||||
->comment('所屬生產工單');
|
||||
|
||||
// 使用的庫存(含商品與批號)
|
||||
$table->foreignId('inventory_id')->constrained()->onDelete('restrict')
|
||||
->comment('使用的庫存紀錄 (含 product, batch)');
|
||||
|
||||
// 使用量
|
||||
$table->decimal('quantity_used', 10, 4)->comment('使用量');
|
||||
$table->foreignId('unit_id')->nullable()->constrained('units')->nullOnDelete()
|
||||
->comment('單位');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
// 索引
|
||||
$table->index(['production_order_id', 'inventory_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('production_order_items');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* 新增生產管理權限
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$guard = 'web';
|
||||
|
||||
// 建立生產管理權限
|
||||
$permissions = [
|
||||
'production_orders.view' => '檢視生產工單',
|
||||
'production_orders.create' => '建立生產工單',
|
||||
'production_orders.edit' => '編輯生產工單',
|
||||
'production_orders.delete' => '刪除生產工單',
|
||||
];
|
||||
|
||||
foreach ($permissions as $name => $description) {
|
||||
Permission::firstOrCreate(
|
||||
['name' => $name, 'guard_name' => $guard],
|
||||
['name' => $name, 'guard_name' => $guard]
|
||||
);
|
||||
}
|
||||
|
||||
// 授予 super-admin 所有新權限
|
||||
$superAdmin = Role::where('name', 'super-admin')->first();
|
||||
if ($superAdmin) {
|
||||
$superAdmin->givePermissionTo(array_keys($permissions));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$permissions = [
|
||||
'production_orders.view',
|
||||
'production_orders.create',
|
||||
'production_orders.edit',
|
||||
'production_orders.delete',
|
||||
];
|
||||
|
||||
foreach ($permissions as $name) {
|
||||
Permission::where('name', $name)->delete();
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user