feat: 實作操作紀錄與商品分類單位異動紀錄 (Operation Logs for System, Products, Categories, Units)
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 58s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped

This commit is contained in:
2026-01-16 17:36:37 +08:00
parent 19c2eeba7b
commit 0d7bb2758d
21 changed files with 904 additions and 8 deletions

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Spatie\Activitylog\Models\Activity;
class ActivityLogController extends Controller
{
public function index(Request $request)
{
$activities = Activity::with('causer')
->latest()
->paginate($request->input('per_page', 10))
->through(function ($activity) {
$subjectMap = [
'App\Models\User' => '使用者',
'App\Models\Role' => '角色',
'App\Models\Product' => '商品',
'App\Models\Vendor' => '廠商',
'App\Models\Category' => '商品分類',
'App\Models\Unit' => '單位',
'App\Models\PurchaseOrder' => '採購單',
];
$eventMap = [
'created' => '新增',
'updated' => '更新',
'deleted' => '刪除',
];
return [
'id' => $activity->id,
'description' => $eventMap[$activity->event] ?? $activity->event,
'subject_type' => $subjectMap[$activity->subject_type] ?? class_basename($activity->subject_type),
'event' => $activity->event,
'causer' => $activity->causer ? $activity->causer->name : 'System',
'created_at' => $activity->created_at->format('Y-m-d H:i:s'),
'properties' => $activity->properties,
];
});
return Inertia::render('Admin/ActivityLog/Index', [
'activities' => $activities,
'filters' => [
'per_page' => $request->input('per_page', '10'),
],
]);
}
}

View File

@@ -5,10 +5,11 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Spatie\Activitylog\Traits\LogsActivity;
class Category extends Model
{
use HasFactory;
use HasFactory, LogsActivity;
protected $fillable = [
'name',
@@ -27,4 +28,12 @@ class Category extends Model
{
return $this->hasMany(Product::class);
}
public function getActivitylogOptions(): \Spatie\Activitylog\LogOptions
{
return \Spatie\Activitylog\LogOptions::defaults()
->logAll()
->logOnlyDirty()
->dontSubmitEmptyLogs();
}
}

View File

@@ -6,10 +6,13 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Product extends Model
{
use HasFactory, SoftDeletes;
use HasFactory, LogsActivity, SoftDeletes;
protected $fillable = [
'code',
@@ -60,6 +63,19 @@ class Product extends Model
return $this->hasMany(Inventory::class);
}
public function transactions(): HasMany
{
return $this->hasMany(InventoryTransaction::class);
}
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logAll()
->logOnlyDirty()
->dontSubmitEmptyLogs();
}
public function warehouses(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Warehouse::class, 'inventories')

View File

@@ -6,10 +6,12 @@ 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;
use HasFactory, LogsActivity;
protected $fillable = [
'code',
@@ -125,4 +127,12 @@ class PurchaseOrder extends Model
{
return $this->hasMany(PurchaseOrderItem::class);
}
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logAll()
->logOnlyDirty()
->dontSubmitEmptyLogs();
}
}

20
app/Models/Role.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Spatie\Permission\Models\Role as SpatieRole;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Role extends SpatieRole
{
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logAll()
->logOnlyDirty()
->dontSubmitEmptyLogs();
}
}

View File

@@ -4,14 +4,23 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
class Unit extends Model
{
/** @use HasFactory<\Database\Factories\UnitFactory> */
use HasFactory;
use HasFactory, LogsActivity;
protected $fillable = [
'name',
'code',
];
public function getActivitylogOptions(): \Spatie\Activitylog\LogOptions
{
return \Spatie\Activitylog\LogOptions::defaults()
->logAll()
->logOnlyDirty()
->dontSubmitEmptyLogs();
}
}

View File

@@ -7,11 +7,13 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable, HasRoles;
use HasFactory, Notifiable, HasRoles, LogsActivity;
/**
* The attributes that are mass assignable.
@@ -47,4 +49,12 @@ class User extends Authenticatable
'password' => 'hashed',
];
}
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logAll()
->logOnlyDirty()
->dontSubmitEmptyLogs();
}
}

View File

@@ -5,9 +5,13 @@ namespace App\Models;
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;
class Vendor extends Model
{
use LogsActivity;
protected $fillable = [
'code',
'name',
@@ -32,4 +36,12 @@ class Vendor extends Model
{
return $this->hasMany(PurchaseOrder::class);
}
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logAll()
->logOnlyDirty()
->dontSubmitEmptyLogs();
}
}