chore: 完善模組化架構遷移與修復前端顯示錯誤
- 修正所有模組 Controller 的 Model 引用路徑 (App\Modules\...) - 更新 ProductionOrder 與 ProductionOrderItem 模型結構以符合新版邏輯 - 修復 resources/js/utils/format.ts 在處理空值時導致 toLocaleString 崩潰的問題 - 清除全域路徑與 Controller 遷移殘留檔案
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use App\Modules\Core\Models\Tenant;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace App\Http\Controllers\Landlord;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Tenant;
|
||||
use App\Modules\Core\Models\Tenant;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class DashboardController extends Controller
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace App\Http\Controllers\Landlord;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Tenant;
|
||||
use App\Modules\Core\Models\Tenant;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Inertia\Inertia;
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
use Spatie\Activitylog\LogOptions;
|
||||
|
||||
class ProductionOrder extends Model
|
||||
{
|
||||
use HasFactory, LogsActivity;
|
||||
|
||||
protected $fillable = [
|
||||
'code',
|
||||
'product_id',
|
||||
'output_batch_number',
|
||||
'output_box_count',
|
||||
'output_quantity',
|
||||
'warehouse_id',
|
||||
'production_date',
|
||||
'expiry_date',
|
||||
'user_id',
|
||||
'status',
|
||||
'remark',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'production_date' => 'date:Y-m-d',
|
||||
'expiry_date' => 'date:Y-m-d',
|
||||
'output_quantity' => 'decimal:2',
|
||||
];
|
||||
|
||||
/**
|
||||
* 成品商品
|
||||
*/
|
||||
public function product(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 入庫倉庫
|
||||
*/
|
||||
public function warehouse(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Warehouse::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作人員
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生產工單明細 (BOM)
|
||||
*/
|
||||
public function items(): HasMany
|
||||
{
|
||||
return $this->hasMany(ProductionOrderItem::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 活動日誌設定
|
||||
*/
|
||||
public function getActivitylogOptions(): LogOptions
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
->logAll()
|
||||
->logOnlyDirty()
|
||||
->dontSubmitEmptyLogs();
|
||||
}
|
||||
|
||||
/**
|
||||
* 活動日誌快照
|
||||
*/
|
||||
public function tapActivity(\Spatie\Activitylog\Contracts\Activity $activity, string $eventName)
|
||||
{
|
||||
$properties = $activity->properties;
|
||||
$attributes = $properties['attributes'] ?? [];
|
||||
$snapshot = $properties['snapshot'] ?? [];
|
||||
|
||||
// 快照關鍵名稱
|
||||
$snapshot['production_code'] = $this->code;
|
||||
$snapshot['product_name'] = $this->product ? $this->product->name : null;
|
||||
$snapshot['warehouse_name'] = $this->warehouse ? $this->warehouse->name : null;
|
||||
$snapshot['user_name'] = $this->user ? $this->user->name : null;
|
||||
|
||||
$properties['attributes'] = $attributes;
|
||||
$properties['snapshot'] = $snapshot;
|
||||
$activity->properties = $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 產生生產單號
|
||||
*/
|
||||
public static function generateCode(): string
|
||||
{
|
||||
$date = now()->format('Ymd');
|
||||
$prefix = "PRO-{$date}-";
|
||||
|
||||
$lastOrder = static::where('code', 'like', "{$prefix}%")
|
||||
->orderByDesc('code')
|
||||
->first();
|
||||
|
||||
if ($lastOrder) {
|
||||
$lastNumber = (int) substr($lastOrder->code, -3);
|
||||
$nextNumber = str_pad($lastNumber + 1, 3, '0', STR_PAD_LEFT);
|
||||
} else {
|
||||
$nextNumber = '001';
|
||||
}
|
||||
|
||||
return $prefix . $nextNumber;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ProductionOrderItem extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'production_order_id',
|
||||
'inventory_id',
|
||||
'quantity_used',
|
||||
'unit_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'quantity_used' => 'decimal:4',
|
||||
];
|
||||
|
||||
/**
|
||||
* 所屬生產工單
|
||||
*/
|
||||
public function productionOrder(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ProductionOrder::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用的庫存紀錄
|
||||
*/
|
||||
public function inventory(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Inventory::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 單位
|
||||
*/
|
||||
public function unit(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Unit::class);
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
use Spatie\Activitylog\LogOptions;
|
||||
|
||||
class PurchaseOrder extends Model
|
||||
{
|
||||
use HasFactory, LogsActivity;
|
||||
|
||||
protected $fillable = [
|
||||
'code',
|
||||
'vendor_id',
|
||||
'warehouse_id',
|
||||
'user_id',
|
||||
'status',
|
||||
'expected_delivery_date',
|
||||
'total_amount',
|
||||
'tax_amount',
|
||||
'grand_total',
|
||||
'remark',
|
||||
'invoice_number',
|
||||
'invoice_date',
|
||||
'invoice_amount',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'expected_delivery_date' => 'date:Y-m-d',
|
||||
'invoice_date' => 'date:Y-m-d',
|
||||
'total_amount' => 'decimal:2',
|
||||
'tax_amount' => 'decimal:2',
|
||||
'grand_total' => 'decimal:2',
|
||||
'invoice_amount' => 'decimal:2',
|
||||
];
|
||||
|
||||
protected $appends = [
|
||||
'poNumber',
|
||||
'supplierId',
|
||||
'supplierName',
|
||||
'expectedDate',
|
||||
'totalAmount',
|
||||
'taxAmount', // Add this
|
||||
'grandTotal', // Add this
|
||||
'createdBy',
|
||||
'warehouse_name',
|
||||
'createdAt',
|
||||
'invoiceNumber',
|
||||
'invoiceDate',
|
||||
'invoiceAmount',
|
||||
];
|
||||
|
||||
public function getCreatedAtAttribute()
|
||||
{
|
||||
return $this->attributes['created_at'];
|
||||
}
|
||||
|
||||
public function getPoNumberAttribute(): string
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function getSupplierIdAttribute(): string
|
||||
{
|
||||
return (string) $this->vendor_id;
|
||||
}
|
||||
|
||||
public function getSupplierNameAttribute(): string
|
||||
{
|
||||
return $this->vendor ? $this->vendor->name : '';
|
||||
}
|
||||
|
||||
public function getExpectedDateAttribute(): ?string
|
||||
{
|
||||
return $this->attributes['expected_delivery_date'] ?? null;
|
||||
}
|
||||
|
||||
public function getTotalAmountAttribute(): float
|
||||
{
|
||||
return (float) ($this->attributes['total_amount'] ?? 0);
|
||||
}
|
||||
|
||||
public function getTaxAmountAttribute(): float
|
||||
{
|
||||
return (float) ($this->attributes['tax_amount'] ?? 0);
|
||||
}
|
||||
|
||||
public function getGrandTotalAttribute(): float
|
||||
{
|
||||
return (float) ($this->attributes['grand_total'] ?? 0);
|
||||
}
|
||||
|
||||
public function getCreatedByAttribute(): string
|
||||
{
|
||||
return $this->user ? $this->user->name : '系統';
|
||||
}
|
||||
|
||||
public function getWarehouseNameAttribute(): string
|
||||
{
|
||||
return $this->warehouse ? $this->warehouse->name : '';
|
||||
}
|
||||
|
||||
public function getInvoiceNumberAttribute(): ?string
|
||||
{
|
||||
return $this->attributes['invoice_number'] ?? null;
|
||||
}
|
||||
|
||||
public function getInvoiceDateAttribute(): ?string
|
||||
{
|
||||
return $this->attributes['invoice_date'] ?? null;
|
||||
}
|
||||
|
||||
public function getInvoiceAmountAttribute(): ?float
|
||||
{
|
||||
return isset($this->attributes['invoice_amount']) ? (float) $this->attributes['invoice_amount'] : null;
|
||||
}
|
||||
|
||||
public function vendor(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Vendor::class);
|
||||
}
|
||||
|
||||
public function warehouse(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Warehouse::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function items(): HasMany
|
||||
{
|
||||
return $this->hasMany(PurchaseOrderItem::class);
|
||||
}
|
||||
|
||||
public function getActivitylogOptions(): LogOptions
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
->logAll()
|
||||
->logOnlyDirty()
|
||||
->dontSubmitEmptyLogs();
|
||||
}
|
||||
|
||||
public function tapActivity(\Spatie\Activitylog\Contracts\Activity $activity, string $eventName)
|
||||
{
|
||||
$properties = $activity->properties;
|
||||
$attributes = $properties['attributes'] ?? [];
|
||||
$snapshot = $properties['snapshot'] ?? [];
|
||||
|
||||
// Snapshot key names
|
||||
$snapshot['po_number'] = $this->code;
|
||||
$snapshot['vendor_name'] = $this->vendor ? $this->vendor->name : null;
|
||||
$snapshot['warehouse_name'] = $this->warehouse ? $this->warehouse->name : null;
|
||||
$snapshot['user_name'] = $this->user ? $this->user->name : null;
|
||||
|
||||
$properties['attributes'] = $attributes;
|
||||
$properties['snapshot'] = $snapshot;
|
||||
$activity->properties = $properties;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class PurchaseOrderItem extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'purchase_order_id',
|
||||
'product_id',
|
||||
'quantity',
|
||||
'unit_id', // 新增單位ID欄位
|
||||
'unit_price',
|
||||
'subtotal',
|
||||
'received_quantity',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'quantity' => 'decimal:2',
|
||||
'unit_price' => 'decimal:2',
|
||||
'subtotal' => 'decimal:2',
|
||||
'received_quantity' => 'decimal:2',
|
||||
];
|
||||
|
||||
public function getProductNameAttribute(): string
|
||||
{
|
||||
return $this->product?->name ?? '';
|
||||
}
|
||||
|
||||
// 關聯單位
|
||||
public function unit(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Unit::class);
|
||||
}
|
||||
|
||||
public function getUnitNameAttribute(): string
|
||||
{
|
||||
// 優先使用關聯的 unit
|
||||
if ($this->unit) {
|
||||
return $this->unit->name;
|
||||
}
|
||||
|
||||
if (!$this->product) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Fallback: 嘗試從 Product 的關聯單位獲取
|
||||
return $this->product->purchaseUnit?->name
|
||||
?? $this->product->largeUnit?->name
|
||||
?? $this->product->baseUnit?->name
|
||||
?? '';
|
||||
}
|
||||
|
||||
public function purchaseOrder(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PurchaseOrder::class);
|
||||
}
|
||||
|
||||
public function product(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UtilityFee extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'transaction_date',
|
||||
'category',
|
||||
'amount',
|
||||
'invoice_number',
|
||||
'description',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'transaction_date' => 'date:Y-m-d',
|
||||
'amount' => 'decimal:2',
|
||||
];
|
||||
}
|
||||
0
app/Modules/.gitkeep
Normal file
0
app/Modules/.gitkeep
Normal file
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
namespace App\Modules\Core\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
@@ -12,16 +13,16 @@ class ActivityLogController extends Controller
|
||||
private function getSubjectMap()
|
||||
{
|
||||
return [
|
||||
'App\Models\User' => '使用者',
|
||||
'App\Models\Role' => '角色',
|
||||
'App\Models\Product' => '商品',
|
||||
'App\Models\Vendor' => '廠商',
|
||||
'App\Models\Category' => '商品分類',
|
||||
'App\Models\Unit' => '單位',
|
||||
'App\Models\PurchaseOrder' => '採購單',
|
||||
'App\Models\Warehouse' => '倉庫',
|
||||
'App\Models\Inventory' => '庫存',
|
||||
'App\Models\UtilityFee' => '公共事業費',
|
||||
'App\Modules\Core\Models\User' => '使用者',
|
||||
'App\Modules\Core\Models\Role' => '角色',
|
||||
'App\Modules\Inventory\Models\Product' => '商品',
|
||||
'App\Modules\Procurement\Models\Vendor' => '廠商',
|
||||
'App\Modules\Inventory\Models\Category' => '商品分類',
|
||||
'App\Modules\Inventory\Models\Unit' => '單位',
|
||||
'App\Modules\Procurement\Models\PurchaseOrder' => '採購單',
|
||||
'App\Modules\Inventory\Models\Warehouse' => '倉庫',
|
||||
'App\Modules\Inventory\Models\Inventory' => '庫存',
|
||||
'App\Modules\Finance\Models\UtilityFee' => '公共事業費',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -101,7 +102,7 @@ class ActivityLogController extends Controller
|
||||
})->values();
|
||||
|
||||
// Get users for causer filter
|
||||
$users = \App\Models\User::select('id', 'name')->orderBy('name')->get()
|
||||
$users = \App\Modules\Core\Models\User::select('id', 'name')->orderBy('name')->get()
|
||||
->map(function ($user) {
|
||||
return ['label' => $user->name, 'value' => (string) $user->id];
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
namespace App\Modules\Core\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -1,13 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Modules\Core\Controllers;
|
||||
|
||||
use App\Models\Product;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Warehouse;
|
||||
use App\Models\Inventory;
|
||||
use App\Models\WarehouseProductSafetyStock;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use App\Modules\Inventory\Models\Product;
|
||||
use App\Modules\Procurement\Models\Vendor;
|
||||
use App\Modules\Procurement\Models\PurchaseOrder;
|
||||
use App\Modules\Inventory\Models\Warehouse;
|
||||
use App\Modules\Inventory\Models\Inventory;
|
||||
use App\Modules\Inventory\Models\WarehouseProductSafetyStock;
|
||||
use Inertia\Inertia;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Modules\Core\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Inertia\Inertia;
|
||||
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
namespace App\Modules\Core\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
@@ -1,9 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
namespace App\Modules\Core\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
|
||||
use App\Modules\Core\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Inertia\Inertia;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Modules\Core\Models;
|
||||
|
||||
use Spatie\Permission\Models\Role as SpatieRole;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Modules\Core\Models;
|
||||
|
||||
use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
|
||||
use Stancl\Tenancy\Contracts\TenantWithDatabase;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Modules\Core\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
54
app/Modules/Core/Routes/web.php
Normal file
54
app/Modules/Core/Routes/web.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Modules\Core\Controllers\Auth\LoginController;
|
||||
use App\Modules\Core\Controllers\DashboardController;
|
||||
use App\Modules\Core\Controllers\ProfileController;
|
||||
use App\Modules\Core\Controllers\RoleController;
|
||||
use App\Modules\Core\Controllers\UserController;
|
||||
use App\Modules\Core\Controllers\ActivityLogController;
|
||||
|
||||
// 登入/登出路由
|
||||
Route::get('/login', [LoginController::class, 'show'])->name('login');
|
||||
Route::post('/login', [LoginController::class, 'store']);
|
||||
Route::post('/logout', [LoginController::class, 'destroy'])->name('logout');
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
// 儀表板 - 所有登入使用者皆可存取
|
||||
Route::get('/', [DashboardController::class, 'index'])->name('dashboard');
|
||||
|
||||
// 使用者帳號設定
|
||||
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
|
||||
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
|
||||
Route::put('/profile/password', [ProfileController::class, 'updatePassword'])->name('profile.password');
|
||||
|
||||
// 系統管理
|
||||
Route::prefix('admin')->group(function () {
|
||||
Route::middleware('permission:roles.view')->group(function () {
|
||||
Route::get('/roles', [RoleController::class, 'index'])->name('roles.index');
|
||||
Route::middleware('permission:roles.create')->group(function () {
|
||||
Route::get('/roles/create', [RoleController::class, 'create'])->name('roles.create');
|
||||
Route::post('/roles', [RoleController::class, 'store'])->name('roles.store');
|
||||
});
|
||||
Route::get('/roles/{role}/edit', [RoleController::class, 'edit'])->middleware('permission:roles.edit')->name('roles.edit');
|
||||
Route::put('/roles/{role}', [RoleController::class, 'update'])->middleware('permission:roles.edit')->name('roles.update');
|
||||
Route::delete('/roles/{role}', [RoleController::class, 'destroy'])->middleware('permission:roles.delete')->name('roles.destroy');
|
||||
});
|
||||
|
||||
Route::middleware('permission:users.view')->group(function () {
|
||||
Route::get('/users', [UserController::class, 'index'])->name('users.index');
|
||||
Route::middleware('permission:users.create')->group(function () {
|
||||
Route::get('/users/create', [UserController::class, 'create'])->name('users.create');
|
||||
Route::post('/users', [UserController::class, 'store'])->name('users.store');
|
||||
});
|
||||
Route::get('/users/{user}/edit', [UserController::class, 'edit'])->middleware('permission:users.edit')->name('users.edit');
|
||||
Route::put('/users/{user}', [UserController::class, 'update'])->middleware('permission:users.edit')->name('users.update');
|
||||
Route::delete('/users/{user}', [UserController::class, 'destroy'])->middleware('permission:users.delete')->name('users.destroy');
|
||||
});
|
||||
|
||||
Route::middleware('permission:system.view_logs')->group(function () {
|
||||
Route::get('/activity-logs', [ActivityLogController::class, 'index'])->name('activity-logs.index');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Modules\Finance\Controllers;
|
||||
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\UtilityFee;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use App\Modules\Finance\Models\UtilityFee;
|
||||
use App\Modules\Procurement\Models\PurchaseOrder;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Illuminate\Support\Carbon;
|
||||
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Modules\Finance\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Modules\Finance\Models\UtilityFee;
|
||||
|
||||
use App\Models\UtilityFee;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
36
app/Modules/Finance/Models/UtilityFee.php
Normal file
36
app/Modules/Finance/Models/UtilityFee.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Finance\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UtilityFee extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UtilityFeeFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'type', // 'electricity', 'water', 'gas', etc.
|
||||
'billing_period_start',
|
||||
'billing_period_end',
|
||||
'due_date',
|
||||
'amount',
|
||||
'usage_amount', // kWh, m3, etc.
|
||||
'unit', // 度, 立方米
|
||||
'status', // 'pending', 'paid', 'overdue'
|
||||
'paid_at',
|
||||
'payment_method',
|
||||
'notes',
|
||||
'receipt_image_path',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'billing_period_start' => 'date',
|
||||
'billing_period_end' => 'date',
|
||||
'due_date' => 'date',
|
||||
'paid_at' => 'datetime',
|
||||
'amount' => 'decimal:2',
|
||||
'usage_amount' => 'decimal:2',
|
||||
];
|
||||
}
|
||||
29
app/Modules/Finance/Routes/web.php
Normal file
29
app/Modules/Finance/Routes/web.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Modules\Finance\Controllers\UtilityFeeController;
|
||||
use App\Modules\Finance\Controllers\AccountingReportController;
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
// 公共事業費管理
|
||||
Route::middleware('permission:utility_fees.view')->group(function () {
|
||||
Route::get('/utility-fees', [UtilityFeeController::class, 'index'])->name('utility-fees.index');
|
||||
});
|
||||
Route::middleware('permission:utility_fees.create')->group(function () {
|
||||
Route::post('/utility-fees', [UtilityFeeController::class, 'store'])->name('utility-fees.store');
|
||||
});
|
||||
Route::middleware('permission:utility_fees.edit')->group(function () {
|
||||
Route::put('/utility-fees/{utility_fee}', [UtilityFeeController::class, 'update'])->name('utility-fees.update');
|
||||
});
|
||||
Route::middleware('permission:utility_fees.delete')->group(function () {
|
||||
Route::delete('/utility-fees/{utility_fee}', [UtilityFeeController::class, 'destroy'])->name('utility-fees.destroy');
|
||||
});
|
||||
|
||||
// 會計報表
|
||||
Route::middleware('permission:accounting.view')->prefix('accounting-report')->group(function () {
|
||||
Route::get('/', [AccountingReportController::class, 'index'])->name('accounting.report');
|
||||
Route::get('/export', [AccountingReportController::class, 'export'])
|
||||
->middleware('permission:accounting.export')
|
||||
->name('accounting.export');
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Modules\Inventory\Controllers;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use App\Modules\Inventory\Models\Category;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CategoryController extends Controller
|
||||
@@ -1,13 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Modules\Inventory\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\WarehouseProductSafetyStock;
|
||||
use App\Modules\Inventory\Models\WarehouseProductSafetyStock;
|
||||
|
||||
class InventoryController extends Controller
|
||||
{
|
||||
public function index(\Illuminate\Http\Request $request, \App\Models\Warehouse $warehouse)
|
||||
public function index(\Illuminate\Http\Request $request, \App\Modules\Inventory\Models\Warehouse $warehouse)
|
||||
{
|
||||
$warehouse->load([
|
||||
'inventories.product.category',
|
||||
@@ -15,7 +17,7 @@ class InventoryController extends Controller
|
||||
'inventories.lastIncomingTransaction',
|
||||
'inventories.lastOutgoingTransaction'
|
||||
]);
|
||||
$allProducts = \App\Models\Product::with('category')->get();
|
||||
$allProducts = \App\Modules\Inventory\Models\Product::with('category')->get();
|
||||
|
||||
// 1. 準備 availableProducts
|
||||
$availableProducts = $allProducts->map(function ($product) {
|
||||
@@ -104,10 +106,10 @@ class InventoryController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(\App\Models\Warehouse $warehouse)
|
||||
public function create(\App\Modules\Inventory\Models\Warehouse $warehouse)
|
||||
{
|
||||
// 取得所有商品供前端選單使用
|
||||
$products = \App\Models\Product::with(['baseUnit', 'largeUnit'])
|
||||
$products = \App\Modules\Inventory\Models\Product::with(['baseUnit', 'largeUnit'])
|
||||
->select('id', 'name', 'code', 'base_unit_id', 'large_unit_id', 'conversion_rate')
|
||||
->get()
|
||||
->map(function ($product) {
|
||||
@@ -127,7 +129,7 @@ class InventoryController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(\Illuminate\Http\Request $request, \App\Models\Warehouse $warehouse)
|
||||
public function store(\Illuminate\Http\Request $request, \App\Modules\Inventory\Models\Warehouse $warehouse)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'inboundDate' => 'required|date',
|
||||
@@ -148,16 +150,16 @@ class InventoryController extends Controller
|
||||
|
||||
if ($item['batchMode'] === 'existing') {
|
||||
// 模式 A:選擇現有批號 (包含已刪除的也要能找回來累加)
|
||||
$inventory = \App\Models\Inventory::withTrashed()->findOrFail($item['inventoryId']);
|
||||
$inventory = \App\Modules\Inventory\Models\Inventory::withTrashed()->findOrFail($item['inventoryId']);
|
||||
if ($inventory->trashed()) {
|
||||
$inventory->restore();
|
||||
}
|
||||
} else {
|
||||
// 模式 B:建立新批號
|
||||
$originCountry = $item['originCountry'] ?? 'TW';
|
||||
$product = \App\Models\Product::find($item['productId']);
|
||||
$product = \App\Modules\Inventory\Models\Product::find($item['productId']);
|
||||
|
||||
$batchNumber = \App\Models\Inventory::generateBatchNumber(
|
||||
$batchNumber = \App\Modules\Inventory\Models\Inventory::generateBatchNumber(
|
||||
$product->code ?? 'UNK',
|
||||
$originCountry,
|
||||
$validated['inboundDate']
|
||||
@@ -208,12 +210,12 @@ class InventoryController extends Controller
|
||||
/**
|
||||
* API: 取得商品在特定倉庫的所有批號,並回傳當前日期/產地下的一個流水號
|
||||
*/
|
||||
public function getBatches(\App\Models\Warehouse $warehouse, $productId, \Illuminate\Http\Request $request)
|
||||
public function getBatches(\App\Modules\Inventory\Models\Warehouse $warehouse, $productId, \Illuminate\Http\Request $request)
|
||||
{
|
||||
$originCountry = $request->query('originCountry', 'TW');
|
||||
$arrivalDate = $request->query('arrivalDate', now()->format('Y-m-d'));
|
||||
|
||||
$batches = \App\Models\Inventory::where('warehouse_id', $warehouse->id)
|
||||
$batches = \App\Modules\Inventory\Models\Inventory::where('warehouse_id', $warehouse->id)
|
||||
->where('product_id', $productId)
|
||||
->get()
|
||||
->map(function ($inventory) {
|
||||
@@ -227,10 +229,10 @@ class InventoryController extends Controller
|
||||
});
|
||||
|
||||
// 計算下一個流水號
|
||||
$product = \App\Models\Product::find($productId);
|
||||
$product = \App\Modules\Inventory\Models\Product::find($productId);
|
||||
$nextSequence = '01';
|
||||
if ($product) {
|
||||
$batchNumber = \App\Models\Inventory::generateBatchNumber(
|
||||
$batchNumber = \App\Modules\Inventory\Models\Inventory::generateBatchNumber(
|
||||
$product->code ?? 'UNK',
|
||||
$originCountry,
|
||||
$arrivalDate
|
||||
@@ -244,7 +246,7 @@ class InventoryController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(\Illuminate\Http\Request $request, \App\Models\Warehouse $warehouse, $inventoryId)
|
||||
public function edit(\Illuminate\Http\Request $request, \App\Modules\Inventory\Models\Warehouse $warehouse, $inventoryId)
|
||||
{
|
||||
// 取得庫存紀錄,包含商品資訊與異動紀錄 (含經手人)
|
||||
// 這裡如果是 Mock 的 ID (mock-inv-1),需要特殊處理
|
||||
@@ -252,7 +254,7 @@ class InventoryController extends Controller
|
||||
return redirect()->back()->with('error', '無法編輯範例資料');
|
||||
}
|
||||
|
||||
$inventory = \App\Models\Inventory::with(['product', 'transactions' => function($query) {
|
||||
$inventory = \App\Modules\Inventory\Models\Inventory::with(['product', 'transactions' => function($query) {
|
||||
$query->orderBy('actual_time', 'desc')->orderBy('id', 'desc');
|
||||
}, 'transactions.user'])->findOrFail($inventoryId);
|
||||
|
||||
@@ -289,13 +291,13 @@ class InventoryController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(\Illuminate\Http\Request $request, \App\Models\Warehouse $warehouse, $inventoryId)
|
||||
public function update(\Illuminate\Http\Request $request, \App\Modules\Inventory\Models\Warehouse $warehouse, $inventoryId)
|
||||
{
|
||||
// 若是 product ID (舊邏輯),先轉為 inventory
|
||||
// 但新路由我們傳的是 inventory ID
|
||||
// 為了相容,我們先判斷 $inventoryId 是 inventory ID
|
||||
|
||||
$inventory = \App\Models\Inventory::find($inventoryId);
|
||||
$inventory = \App\Modules\Inventory\Models\Inventory::find($inventoryId);
|
||||
|
||||
// 如果找不到 (可能是舊路由傳 product ID)
|
||||
if (!$inventory) {
|
||||
@@ -393,9 +395,9 @@ class InventoryController extends Controller
|
||||
});
|
||||
}
|
||||
|
||||
public function destroy(\App\Models\Warehouse $warehouse, $inventoryId)
|
||||
public function destroy(\App\Modules\Inventory\Models\Warehouse $warehouse, $inventoryId)
|
||||
{
|
||||
$inventory = \App\Models\Inventory::findOrFail($inventoryId);
|
||||
$inventory = \App\Modules\Inventory\Models\Inventory::findOrFail($inventoryId);
|
||||
|
||||
// 庫存 > 0 不允許刪除 (哪怕是軟刪除)
|
||||
if ($inventory->quantity > 0) {
|
||||
@@ -421,14 +423,14 @@ class InventoryController extends Controller
|
||||
->with('success', '庫存品項已刪除');
|
||||
}
|
||||
|
||||
public function history(Request $request, \App\Models\Warehouse $warehouse)
|
||||
public function history(Request $request, \App\Modules\Inventory\Models\Warehouse $warehouse)
|
||||
{
|
||||
$inventoryId = $request->query('inventoryId');
|
||||
$productId = $request->query('productId');
|
||||
|
||||
if ($productId) {
|
||||
// 商品層級查詢
|
||||
$inventories = \App\Models\Inventory::where('warehouse_id', $warehouse->id)
|
||||
$inventories = \App\Modules\Inventory\Models\Inventory::where('warehouse_id', $warehouse->id)
|
||||
->where('product_id', $productId)
|
||||
->with(['product', 'transactions' => function($query) {
|
||||
$query->orderBy('actual_time', 'desc')->orderBy('id', 'desc');
|
||||
@@ -503,7 +505,7 @@ class InventoryController extends Controller
|
||||
|
||||
if ($inventoryId) {
|
||||
// 單一批號查詢
|
||||
$inventory = \App\Models\Inventory::with(['product', 'transactions' => function($query) {
|
||||
$inventory = \App\Modules\Inventory\Models\Inventory::with(['product', 'transactions' => function($query) {
|
||||
$query->orderBy('actual_time', 'desc')->orderBy('id', 'desc');
|
||||
}, 'transactions.user'])->findOrFail($inventoryId);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Modules\Inventory\Controllers;
|
||||
|
||||
use App\Models\Product;
|
||||
use App\Models\Unit;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use App\Modules\Inventory\Models\Product;
|
||||
use App\Modules\Inventory\Models\Unit;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
@@ -59,7 +61,7 @@ class ProductController extends Controller
|
||||
|
||||
$products = $query->paginate($perPage)->withQueryString();
|
||||
|
||||
$categories = \App\Models\Category::where('is_active', true)->get();
|
||||
$categories = \App\Modules\Inventory\Models\Category::where('is_active', true)->get();
|
||||
|
||||
return Inertia::render('Product/Index', [
|
||||
'products' => $products,
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Modules\Inventory\Controllers;
|
||||
|
||||
use App\Models\Warehouse;
|
||||
use App\Models\WarehouseProductSafetyStock;
|
||||
use App\Models\Product;
|
||||
use App\Models\Inventory;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use App\Modules\Inventory\Models\Warehouse;
|
||||
use App\Modules\Inventory\Models\WarehouseProductSafetyStock;
|
||||
use App\Modules\Inventory\Models\Product;
|
||||
use App\Modules\Inventory\Models\Inventory;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Modules\Inventory\Controllers;
|
||||
|
||||
use App\Models\Inventory;
|
||||
use App\Models\Warehouse;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use App\Modules\Inventory\Models\Inventory;
|
||||
use App\Modules\Inventory\Models\Warehouse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Modules\Inventory\Controllers;
|
||||
|
||||
use App\Models\Unit;
|
||||
use App\Models\Product; // Import Product to check for usage
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use App\Modules\Inventory\Models\Unit;
|
||||
use App\Modules\Inventory\Models\Product; // Import Product to check for usage
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UnitController extends Controller
|
||||
@@ -1,10 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Modules\Inventory\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use App\Models\Warehouse;
|
||||
use App\Modules\Inventory\Models\Warehouse;
|
||||
|
||||
use Inertia\Inertia;
|
||||
|
||||
@@ -1,37 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Modules\Inventory\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
use Spatie\Activitylog\LogOptions;
|
||||
|
||||
class Category extends Model
|
||||
{
|
||||
use HasFactory, LogsActivity;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'is_active',
|
||||
];
|
||||
protected $fillable = ['name', 'description'];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the products for the category.
|
||||
*/
|
||||
public function products(): HasMany
|
||||
{
|
||||
return $this->hasMany(Product::class);
|
||||
}
|
||||
|
||||
public function getActivitylogOptions(): \Spatie\Activitylog\LogOptions
|
||||
public function getActivitylogOptions(): LogOptions
|
||||
{
|
||||
return \Spatie\Activitylog\LogOptions::defaults()
|
||||
return LogOptions::defaults()
|
||||
->logAll()
|
||||
->logOnlyDirty()
|
||||
->dontSubmitEmptyLogs();
|
||||
@@ -1,9 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Modules\Inventory\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Modules\Procurement\Models\PurchaseOrder; // Cross-module dependency
|
||||
|
||||
class Inventory extends Model
|
||||
{
|
||||
@@ -1,11 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Modules\Inventory\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Models\Inventory;
|
||||
use App\Models\User;
|
||||
use App\Modules\Core\Models\User; // Cross-module Core dependency
|
||||
|
||||
class InventoryTransaction extends Model
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Modules\Inventory\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
use Spatie\Activitylog\LogOptions;
|
||||
use App\Modules\Procurement\Models\Vendor; // Cross-module dependency (Procurement)
|
||||
|
||||
class Product extends Model
|
||||
{
|
||||
@@ -1,24 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Modules\Inventory\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
use Spatie\Activitylog\LogOptions;
|
||||
|
||||
class Unit extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UnitFactory> */
|
||||
use HasFactory, LogsActivity;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'code',
|
||||
];
|
||||
protected $fillable = ['name', 'abbreviation'];
|
||||
|
||||
public function getActivitylogOptions(): \Spatie\Activitylog\LogOptions
|
||||
public function productsAsBase(): HasMany
|
||||
{
|
||||
return \Spatie\Activitylog\LogOptions::defaults()
|
||||
return $this->hasMany(Product::class, 'base_unit_id');
|
||||
}
|
||||
|
||||
public function productsAsLarge(): HasMany
|
||||
{
|
||||
return $this->hasMany(Product::class, 'large_unit_id');
|
||||
}
|
||||
|
||||
public function getActivitylogOptions(): LogOptions
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
->logAll()
|
||||
->logOnlyDirty()
|
||||
->dontSubmitEmptyLogs();
|
||||
@@ -1,9 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Modules\Inventory\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Modules\Procurement\Models\PurchaseOrder; // Cross-module dependency (Procurement)
|
||||
|
||||
class Warehouse extends Model
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Modules\Inventory\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
80
app/Modules/Inventory/Routes/web.php
Normal file
80
app/Modules/Inventory/Routes/web.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Modules\Inventory\Controllers\CategoryController;
|
||||
use App\Modules\Inventory\Controllers\UnitController;
|
||||
use App\Modules\Inventory\Controllers\ProductController;
|
||||
use App\Modules\Inventory\Controllers\WarehouseController;
|
||||
use App\Modules\Inventory\Controllers\InventoryController;
|
||||
use App\Modules\Inventory\Controllers\SafetyStockController;
|
||||
use App\Modules\Inventory\Controllers\TransferOrderController;
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
|
||||
// 類別管理 (用於商品對話框) - 需要商品權限
|
||||
Route::middleware('permission:products.view')->group(function () {
|
||||
Route::get('/categories', [CategoryController::class, 'index'])->name('categories.index');
|
||||
Route::post('/categories', [CategoryController::class, 'store'])->middleware('permission:products.create')->name('categories.store');
|
||||
Route::put('/categories/{category}', [CategoryController::class, 'update'])->middleware('permission:products.edit')->name('categories.update');
|
||||
Route::delete('/categories/{category}', [CategoryController::class, 'destroy'])->middleware('permission:products.delete')->name('categories.destroy');
|
||||
});
|
||||
|
||||
// 單位管理 - 需要商品權限
|
||||
Route::middleware('permission:products.create|products.edit')->group(function () {
|
||||
Route::post('/units', [UnitController::class, 'store'])->name('units.store');
|
||||
Route::put('/units/{unit}', [UnitController::class, 'update'])->name('units.update');
|
||||
Route::delete('/units/{unit}', [UnitController::class, 'destroy'])->name('units.destroy');
|
||||
});
|
||||
|
||||
// 商品管理
|
||||
Route::middleware('permission:products.view')->group(function () {
|
||||
Route::get('/products', [ProductController::class, 'index'])->name('products.index');
|
||||
Route::post('/products', [ProductController::class, 'store'])->middleware('permission:products.create')->name('products.store');
|
||||
Route::put('/products/{product}', [ProductController::class, 'update'])->middleware('permission:products.edit')->name('products.update');
|
||||
Route::delete('/products/{product}', [ProductController::class, 'destroy'])->middleware('permission:products.delete')->name('products.destroy');
|
||||
});
|
||||
|
||||
// 倉庫管理
|
||||
Route::middleware('permission:warehouses.view')->group(function () {
|
||||
Route::get('/warehouses', [WarehouseController::class, 'index'])->name('warehouses.index');
|
||||
Route::post('/warehouses', [WarehouseController::class, 'store'])->middleware('permission:warehouses.create')->name('warehouses.store');
|
||||
Route::put('/warehouses/{warehouse}', [WarehouseController::class, 'update'])->middleware('permission:warehouses.edit')->name('warehouses.update');
|
||||
Route::delete('/warehouses/{warehouse}', [WarehouseController::class, 'destroy'])->middleware('permission:warehouses.delete')->name('warehouses.destroy');
|
||||
|
||||
// 倉庫庫存管理 - 需要庫存權限
|
||||
Route::middleware('permission:inventory.view')->group(function () {
|
||||
Route::get('/warehouses/{warehouse}/inventory', [InventoryController::class, 'index'])->name('warehouses.inventory.index');
|
||||
Route::get('/warehouses/{warehouse}/inventory-history', [InventoryController::class, 'history'])->name('warehouses.inventory.history');
|
||||
|
||||
Route::middleware('permission:inventory.adjust')->group(function () {
|
||||
Route::get('/warehouses/{warehouse}/inventory/create', [InventoryController::class, 'create'])->name('warehouses.inventory.create');
|
||||
Route::post('/warehouses/{warehouse}/inventory', [InventoryController::class, 'store'])->name('warehouses.inventory.store');
|
||||
Route::get('/warehouses/{warehouse}/inventory/{inventoryId}/edit', [InventoryController::class, 'edit'])->name('warehouses.inventory.edit');
|
||||
Route::put('/warehouses/{warehouse}/inventory/{inventoryId}', [InventoryController::class, 'update'])->name('warehouses.inventory.update');
|
||||
Route::delete('/warehouses/{warehouse}/inventory/{inventoryId}', [InventoryController::class, 'destroy'])->name('warehouses.inventory.destroy');
|
||||
});
|
||||
|
||||
// API: 取得商品在特定倉庫的所有批號
|
||||
Route::get('/api/warehouses/{warehouse}/inventory/batches/{productId}', [InventoryController::class, 'getBatches'])
|
||||
->name('api.warehouses.inventory.batches');
|
||||
});
|
||||
|
||||
// 安全庫存設定
|
||||
Route::middleware('permission:inventory.view')->group(function () {
|
||||
Route::get('/warehouses/{warehouse}/safety-stock', [SafetyStockController::class, 'index'])->name('warehouses.safety-stock.index');
|
||||
Route::middleware('permission:inventory.safety_stock')->group(function () {
|
||||
Route::post('/warehouses/{warehouse}/safety-stock', [SafetyStockController::class, 'store'])->name('warehouses.safety-stock.store');
|
||||
Route::put('/warehouses/{warehouse}/safety-stock/{safetyStock}', [SafetyStockController::class, 'update'])->name('warehouses.safety-stock.update');
|
||||
Route::delete('/warehouses/{warehouse}/safety-stock/{safetyStock}', [SafetyStockController::class, 'destroy'])->name('warehouses.safety-stock.destroy');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 撥補單 (在庫存調撥時使用)
|
||||
Route::middleware('permission:inventory.transfer')->group(function () {
|
||||
Route::post('/transfer-orders', [TransferOrderController::class, 'store'])->name('transfer-orders.store');
|
||||
});
|
||||
Route::get('/api/warehouses/{warehouse}/inventories', [TransferOrderController::class, 'getWarehouseInventories'])
|
||||
->middleware('permission:inventory.view')
|
||||
->name('api.warehouses.inventories');
|
||||
});
|
||||
@@ -1,10 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Modules\Procurement\Controllers;
|
||||
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Warehouse;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use App\Modules\Procurement\Models\PurchaseOrder;
|
||||
use App\Modules\Procurement\Models\Vendor;
|
||||
use App\Modules\Inventory\Models\Warehouse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -146,9 +148,9 @@ class PurchaseOrderController extends Controller
|
||||
// 確保有一個有效的使用者 ID
|
||||
$userId = auth()->id();
|
||||
if (!$userId) {
|
||||
$user = \App\Models\User::first();
|
||||
$user = \App\Modules\Core\Models\User::first();
|
||||
if (!$user) {
|
||||
$user = \App\Models\User::create([
|
||||
$user = \App\Modules\Core\Models\User::create([
|
||||
'name' => '系統管理員',
|
||||
'email' => 'admin@example.com',
|
||||
'password' => bcrypt('password'),
|
||||
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Modules\Procurement\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Modules\Procurement\Models\Vendor;
|
||||
|
||||
use App\Models\Vendor;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class VendorController extends Controller
|
||||
@@ -56,7 +58,7 @@ class VendorController extends Controller
|
||||
$vendor->load(['products.baseUnit', 'products.largeUnit']);
|
||||
return \Inertia\Inertia::render('Vendor/Show', [
|
||||
'vendor' => $vendor,
|
||||
'products' => \App\Models\Product::with('baseUnit')->get(),
|
||||
'products' => \App\Modules\Inventory\Models\Product::with('baseUnit')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Modules\Procurement\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Modules\Procurement\Models\Vendor;
|
||||
|
||||
use App\Models\Vendor;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -28,7 +30,7 @@ class VendorProductController extends Controller
|
||||
]);
|
||||
|
||||
// 記錄操作
|
||||
$product = \App\Models\Product::find($validated['product_id']);
|
||||
$product = \App\Modules\Inventory\Models\Product::find($validated['product_id']);
|
||||
activity()
|
||||
->performedOn($vendor)
|
||||
->withProperties([
|
||||
@@ -66,7 +68,7 @@ class VendorProductController extends Controller
|
||||
]);
|
||||
|
||||
// 記錄操作
|
||||
$product = \App\Models\Product::find($productId);
|
||||
$product = \App\Modules\Inventory\Models\Product::find($productId);
|
||||
activity()
|
||||
->performedOn($vendor)
|
||||
->withProperties([
|
||||
@@ -95,7 +97,7 @@ class VendorProductController extends Controller
|
||||
public function destroy(Vendor $vendor, $productId)
|
||||
{
|
||||
// 記錄操作 (需在 detach 前獲取資訊)
|
||||
$product = \App\Models\Product::find($productId);
|
||||
$product = \App\Modules\Inventory\Models\Product::find($productId);
|
||||
$old_price = $vendor->products()->where('product_id', $productId)->first()?->pivot?->last_price;
|
||||
|
||||
$vendor->products()->detach($productId);
|
||||
79
app/Modules/Procurement/Models/PurchaseOrder.php
Normal file
79
app/Modules/Procurement/Models/PurchaseOrder.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Procurement\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Modules\Inventory\Models\Warehouse;
|
||||
use App\Modules\Core\Models\User;
|
||||
|
||||
class PurchaseOrder extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\PurchaseOrderFactory> */
|
||||
use HasFactory;
|
||||
use \Spatie\Activitylog\Traits\LogsActivity;
|
||||
|
||||
protected $fillable = [
|
||||
'po_number',
|
||||
'vendor_id',
|
||||
'warehouse_id',
|
||||
'user_id',
|
||||
'order_date',
|
||||
'expected_delivery_date',
|
||||
'status',
|
||||
'total_amount',
|
||||
'notes',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'order_date' => 'date',
|
||||
'expected_delivery_date' => 'date',
|
||||
'total_amount' => 'decimal:2',
|
||||
];
|
||||
|
||||
public function getActivitylogOptions(): \Spatie\Activitylog\LogOptions
|
||||
{
|
||||
return \Spatie\Activitylog\LogOptions::defaults()
|
||||
->logAll()
|
||||
->logOnlyDirty()
|
||||
->dontSubmitEmptyLogs();
|
||||
}
|
||||
|
||||
public function tapActivity(\Spatie\Activitylog\Contracts\Activity $activity, string $eventName)
|
||||
{
|
||||
$snapshot = $activity->properties['snapshot'] ?? [];
|
||||
|
||||
$snapshot['po_number'] = $this->po_number;
|
||||
|
||||
if ($this->vendor) {
|
||||
$snapshot['vendor_name'] = $this->vendor->name;
|
||||
}
|
||||
if ($this->warehouse) {
|
||||
$snapshot['warehouse_name'] = $this->warehouse->name;
|
||||
}
|
||||
|
||||
$activity->properties = $activity->properties->merge([
|
||||
'snapshot' => $snapshot
|
||||
]);
|
||||
}
|
||||
|
||||
public function vendor(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Vendor::class);
|
||||
}
|
||||
|
||||
public function warehouse(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Warehouse::class);
|
||||
}
|
||||
|
||||
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function items(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(PurchaseOrderItem::class);
|
||||
}
|
||||
}
|
||||
44
app/Modules/Procurement/Models/PurchaseOrderItem.php
Normal file
44
app/Modules/Procurement/Models/PurchaseOrderItem.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Procurement\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Modules\Inventory\Models\Product;
|
||||
|
||||
class PurchaseOrderItem extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\PurchaseOrderItemFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'purchase_order_id',
|
||||
'product_id',
|
||||
'quantity',
|
||||
'unit_price',
|
||||
'subtotal',
|
||||
// 驗收欄位
|
||||
'received_quantity',
|
||||
// 批號與效期 (驗收時填寫)
|
||||
'batch_number',
|
||||
'expiry_date',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'quantity' => 'decimal:2',
|
||||
'unit_price' => 'decimal:4',
|
||||
'subtotal' => 'decimal:2',
|
||||
'received_quantity' => 'decimal:2',
|
||||
'expiry_date' => 'date',
|
||||
];
|
||||
|
||||
public function purchaseOrder(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PurchaseOrder::class);
|
||||
}
|
||||
|
||||
public function product(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Modules\Procurement\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
use Spatie\Activitylog\LogOptions;
|
||||
use App\Modules\Inventory\Models\Product;
|
||||
|
||||
class Vendor extends Model
|
||||
{
|
||||
use LogsActivity;
|
||||
/** @use HasFactory<\Database\Factories\VendorFactory> */
|
||||
use HasFactory, LogsActivity;
|
||||
|
||||
protected $fillable = [
|
||||
'code',
|
||||
'name',
|
||||
'short_name',
|
||||
'tax_id',
|
||||
'owner',
|
||||
'contact_name',
|
||||
'tel',
|
||||
'phone',
|
||||
'contact_person',
|
||||
'email',
|
||||
'phone',
|
||||
'address',
|
||||
'remark'
|
||||
'tax_id',
|
||||
'payment_terms',
|
||||
];
|
||||
public function products(): BelongsToMany
|
||||
|
||||
public function products(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Product::class, 'product_vendor')
|
||||
->withPivot('last_price')
|
||||
->withTimestamps();
|
||||
return $this->belongsToMany(Product::class)->withPivot('last_price')->withTimestamps();
|
||||
}
|
||||
|
||||
public function purchaseOrders(): HasMany
|
||||
public function purchaseOrders(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(PurchaseOrder::class);
|
||||
}
|
||||
@@ -49,12 +46,8 @@ class Vendor extends Model
|
||||
{
|
||||
$properties = $activity->properties;
|
||||
|
||||
// Store name in 'snapshot' for context, keeping 'attributes' clean
|
||||
$snapshot = $properties['snapshot'] ?? [];
|
||||
// Only set name if it's not already set (e.g. by controller for specific context like supply product)
|
||||
if (!isset($snapshot['name'])) {
|
||||
$snapshot['name'] = $this->name;
|
||||
}
|
||||
$snapshot['name'] = $this->name;
|
||||
$properties['snapshot'] = $snapshot;
|
||||
|
||||
$activity->properties = $properties;
|
||||
38
app/Modules/Procurement/Routes/web.php
Normal file
38
app/Modules/Procurement/Routes/web.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Modules\Procurement\Controllers\VendorController;
|
||||
use App\Modules\Procurement\Controllers\VendorProductController;
|
||||
use App\Modules\Procurement\Controllers\PurchaseOrderController;
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
// 廠商管理
|
||||
Route::middleware('permission:vendors.view')->group(function () {
|
||||
Route::get('/vendors', [VendorController::class, 'index'])->name('vendors.index');
|
||||
Route::get('/vendors/{vendor}', [VendorController::class, 'show'])->name('vendors.show');
|
||||
Route::post('/vendors', [VendorController::class, 'store'])->middleware('permission:vendors.create')->name('vendors.store');
|
||||
Route::put('/vendors/{vendor}', [VendorController::class, 'update'])->middleware('permission:vendors.edit')->name('vendors.update');
|
||||
Route::delete('/vendors/{vendor}', [VendorController::class, 'destroy'])->middleware('permission:vendors.delete')->name('vendors.destroy');
|
||||
|
||||
// 供貨商品相關路由
|
||||
Route::post('/vendors/{vendor}/products', [VendorProductController::class, 'store'])->middleware('permission:vendors.edit')->name('vendors.products.store');
|
||||
Route::put('/vendors/{vendor}/products/{product}', [VendorProductController::class, 'update'])->middleware('permission:vendors.edit')->name('vendors.products.update');
|
||||
Route::delete('/vendors/{vendor}/products/{product}', [VendorProductController::class, 'destroy'])->middleware('permission:vendors.edit')->name('vendors.products.destroy');
|
||||
});
|
||||
|
||||
// 採購單管理
|
||||
Route::middleware('permission:purchase_orders.view')->group(function () {
|
||||
Route::get('/purchase-orders', [PurchaseOrderController::class, 'index'])->name('purchase-orders.index');
|
||||
|
||||
Route::middleware('permission:purchase_orders.create')->group(function () {
|
||||
Route::get('/purchase-orders/create', [PurchaseOrderController::class, 'create'])->name('purchase-orders.create');
|
||||
Route::post('/purchase-orders', [PurchaseOrderController::class, 'store'])->name('purchase-orders.store');
|
||||
});
|
||||
|
||||
Route::get('/purchase-orders/{id}', [PurchaseOrderController::class, 'show'])->name('purchase-orders.show');
|
||||
|
||||
Route::get('/purchase-orders/{id}/edit', [PurchaseOrderController::class, 'edit'])->middleware('permission:purchase_orders.edit')->name('purchase-orders.edit');
|
||||
Route::put('/purchase-orders/{id}', [PurchaseOrderController::class, 'update'])->middleware('permission:purchase_orders.edit')->name('purchase-orders.update');
|
||||
Route::delete('/purchase-orders/{id}', [PurchaseOrderController::class, 'destroy'])->middleware('permission:purchase_orders.delete')->name('purchase-orders.destroy');
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Modules\Production\Controllers;
|
||||
|
||||
use App\Models\Inventory;
|
||||
use App\Models\Product;
|
||||
use App\Models\ProductionOrder;
|
||||
use App\Models\ProductionOrderItem;
|
||||
use App\Models\Unit;
|
||||
use App\Models\Warehouse;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use App\Modules\Inventory\Models\Product;
|
||||
use App\Modules\Production\Models\ProductionOrder;
|
||||
use App\Modules\Production\Models\ProductionOrderItem;
|
||||
use App\Modules\Inventory\Models\Unit;
|
||||
use App\Modules\Inventory\Models\Warehouse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Inertia;
|
||||
70
app/Modules/Production/Models/ProductionOrder.php
Normal file
70
app/Modules/Production/Models/ProductionOrder.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Production\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Modules\Inventory\Models\Product;
|
||||
use App\Modules\Inventory\Models\Warehouse;
|
||||
use App\Modules\Core\Models\User;
|
||||
|
||||
class ProductionOrder extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\ProductionOrderFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'code',
|
||||
'product_id',
|
||||
'warehouse_id',
|
||||
'output_quantity',
|
||||
'output_batch_number',
|
||||
'output_box_count',
|
||||
'production_date',
|
||||
'expiry_date',
|
||||
'user_id',
|
||||
'status',
|
||||
'remark',
|
||||
];
|
||||
|
||||
public static function generateCode()
|
||||
{
|
||||
$prefix = 'PO' . now()->format('Ymd');
|
||||
$lastOrder = self::where('code', 'like', $prefix . '%')->latest()->first();
|
||||
if ($lastOrder) {
|
||||
$lastSequence = intval(substr($lastOrder->code, -3));
|
||||
$sequence = str_pad($lastSequence + 1, 3, '0', STR_PAD_LEFT);
|
||||
} else {
|
||||
$sequence = '001';
|
||||
}
|
||||
return $prefix . $sequence;
|
||||
}
|
||||
|
||||
protected $casts = [
|
||||
'order_date' => 'date',
|
||||
'start_date' => 'datetime',
|
||||
'completion_date' => 'datetime',
|
||||
'quantity' => 'decimal:2',
|
||||
'produced_quantity' => 'decimal:2',
|
||||
];
|
||||
|
||||
public function product(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
|
||||
public function warehouse(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Warehouse::class);
|
||||
}
|
||||
|
||||
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function items(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(ProductionOrderItem::class);
|
||||
}
|
||||
}
|
||||
44
app/Modules/Production/Models/ProductionOrderItem.php
Normal file
44
app/Modules/Production/Models/ProductionOrderItem.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Production\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Modules\Inventory\Models\Product;
|
||||
|
||||
class ProductionOrderItem extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\ProductionOrderItemFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'production_order_id',
|
||||
'inventory_id',
|
||||
'quantity_used',
|
||||
'unit_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'quantity_used' => 'decimal:4',
|
||||
];
|
||||
|
||||
public function inventory()
|
||||
{
|
||||
return $this->belongsTo(\App\Modules\Inventory\Models\Inventory::class);
|
||||
}
|
||||
|
||||
public function unit()
|
||||
{
|
||||
return $this->belongsTo(\App\Modules\Inventory\Models\Unit::class);
|
||||
}
|
||||
|
||||
public function productionOrder(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ProductionOrder::class);
|
||||
}
|
||||
|
||||
public function product(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
}
|
||||
28
app/Modules/Production/Routes/web.php
Normal file
28
app/Modules/Production/Routes/web.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Modules\Production\Controllers\ProductionOrderController;
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
// 生產管理
|
||||
Route::middleware('permission:production_orders.view')->group(function () {
|
||||
Route::get('/production-orders', [ProductionOrderController::class, 'index'])->name('production-orders.index');
|
||||
|
||||
Route::middleware('permission:production_orders.create')->group(function () {
|
||||
Route::get('/production-orders/create', [ProductionOrderController::class, 'create'])->name('production-orders.create');
|
||||
Route::post('/production-orders', [ProductionOrderController::class, 'store'])->name('production-orders.store');
|
||||
});
|
||||
|
||||
Route::get('/production-orders/{productionOrder}', [ProductionOrderController::class, 'show'])->name('production-orders.show');
|
||||
|
||||
Route::middleware('permission:production_orders.edit')->group(function () {
|
||||
Route::get('/production-orders/{productionOrder}/edit', [ProductionOrderController::class, 'edit'])->name('production-orders.edit');
|
||||
Route::put('/production-orders/{productionOrder}', [ProductionOrderController::class, 'update'])->name('production-orders.update');
|
||||
});
|
||||
});
|
||||
|
||||
// 生產管理 API
|
||||
Route::get('/api/production/warehouses/{warehouse}/inventories', [ProductionOrderController::class, 'getWarehouseInventories'])
|
||||
->middleware('permission:production_orders.create')
|
||||
->name('api.production.warehouses.inventories');
|
||||
});
|
||||
40
app/Providers/ModuleServiceProvider.php
Normal file
40
app/Providers/ModuleServiceProvider.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class ModuleServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$modulesPath = app_path('Modules');
|
||||
|
||||
if (File::exists($modulesPath)) {
|
||||
$modules = File::directories($modulesPath);
|
||||
|
||||
foreach ($modules as $module) {
|
||||
// $moduleName = basename($module);
|
||||
$routesPath = $module . '/Routes/web.php';
|
||||
|
||||
if (File::exists($routesPath)) {
|
||||
Route::middleware('web')
|
||||
->group($routesPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user