新的 工单支持 / 权限管理

This commit is contained in:
iVampireSP.com 2023-01-01 21:00:21 +08:00
parent e5ee68a4ef
commit 1d6b89f9d0
No known key found for this signature in database
GPG Key ID: 2F7B001CA27A8132
17 changed files with 354 additions and 146 deletions

View File

@ -91,7 +91,6 @@ public function update(Request $request, WorkOrder $workOrder): RedirectResponse
*/
public function destroy(WorkOrder $workOrder): RedirectResponse
{
//
try {
$workOrder->safeDelete();
} catch (CommonException $e) {

View File

@ -7,7 +7,6 @@
use App\Models\WorkOrder\WorkOrder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use function auth;
class ReplyController extends Controller
@ -19,11 +18,7 @@ class ReplyController extends Controller
*/
public function index(WorkOrder $workOrder)
{
if (auth()->id() !== $workOrder->user_id) {
return $this->notFound('无法找到对应的工单。');
}
$replies = Reply::workOrderId($workOrder->id)->simplePaginate(100);
$replies = Reply::workOrderId($workOrder->id)->with(['module', 'user'])->simplePaginate(100);
return $this->success($replies);
}
@ -34,25 +29,34 @@ public function index(WorkOrder $workOrder)
* @param Request $request
* @param WorkOrder $workOrder
*
* @return JsonResponse|Response
* @return JsonResponse
*/
public function store(Request $request, WorkOrder $workOrder)
{
if (auth()->id() !== $workOrder->user_id) {
return $this->notFound('无法找到对应的工单。');
}
// add reply
$this->validate($request, [
'content' => 'string|required|min:1|max:1000',
]);
if ($workOrder->isFailure()) {
return $this->error('工单状态异常,无法进行回复。请尝试重新建立工单。');
}
$reply = Reply::create([
$create = [
'content' => $request->input('content'),
'work_order_id' => $workOrder->id,
]);
];
if (auth('sanctum')->check()) {
$create['user_id'] = auth('sanctum')->id();
} else {
$this->validate($request, [
'name' => 'string|required|min:1|max:255',
]);
$create['name'] = $request->input('name');
}
$reply = Reply::create($create);
return $this->success($reply);
}

View File

@ -7,7 +7,6 @@
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use function auth;
class WorkOrderController extends Controller
{
@ -31,15 +30,20 @@ public function store(Request $request)
]);
// module_id 和 host_id 必须有个要填写
if (isset($request->module_id) && isset($request->host_id)) {
return $this->error('module_id 和 host_id 至少要填写一个');
if ($request->input('module_id') === null && $request->input('host_id') === null) {
$message = 'module_id 和 host_id 必须有个要填写';
throw ValidationException::withMessages([
'module_id' => $message,
'host_id' => $message,
]);
}
$workOrder = WorkOrder::create([
'title' => $request->title,
'title' => $request->input('title'),
'content' => $request->input('content'),
'module_id' => $request->module_id,
'host_id' => $request->host_id,
'module_id' => $request->input('module_id'),
'host_id' => $request->input('host_id'),
'status' => 'pending',
]);
@ -48,11 +52,8 @@ public function store(Request $request)
public function show(WorkOrder $workOrder): JsonResponse
{
if (auth()->id() !== $workOrder->user_id) {
return $this->notFound('无法找到对应的工单。');
}
$workOrder->load(['module', 'host']);
return $this->success($workOrder);
}
@ -61,15 +62,12 @@ public function show(WorkOrder $workOrder): JsonResponse
*/
public function update(Request $request, WorkOrder $workOrder)
{
if (auth()->id() !== $workOrder->user_id) {
return $this->notFound('无法找到对应的工单。');
}
$this->validate($request, [
'status' => 'nullable|sometimes|string|in:closed',
]);
$workOrder->update($request->only('status'));
return $this->success($workOrder);
}
}

View File

@ -19,8 +19,8 @@ class ReplyController extends Controller
*/
public function index(Request $request): JsonResponse
{
//
$replies = Reply::workOrderId($request->route('work_order'))->simplePaginate(10);
return $this->success($replies);
}
@ -33,19 +33,14 @@ public function store(Request $request, WorkOrder $work_order): JsonResponse
{
$this->validate($request, [
'content' => 'required|string|max:255',
'work_order_id' => 'required|integer|exists:work_orders,id',
'name' => 'required|string|max:255',
]);
if ($work_order->module_id !== auth('module')->id()) {
return $this->error('您没有权限回复此工单。');
}
// 需要转换成数组
$request_array = $request->all();
$reply = Reply::create([
'content' => $request_array['content'],
'work_order_id' => $request_array['work_order_id'],
'content' => $request->input('content'),
'work_order_id' => $work_order->id,
'module_id' => $work_order->module_id,
'name' => $request->input('name'),
]);
return $this->success($reply);

View File

@ -6,7 +6,6 @@
use App\Http\Requests\Remote\WorkOrderRequest;
use App\Models\WorkOrder\WorkOrder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
class WorkOrderController extends Controller
@ -18,12 +17,8 @@ public function index(WorkOrder $workOrder): JsonResponse
return $this->success($workOrder);
}
public function show(Request $request, WorkOrder $workOrder): JsonResponse
public function show(WorkOrder $workOrder): JsonResponse
{
if ($workOrder->module_id !== $request->user('module')->id) {
return $this->error('您没有权限查看此工单。');
}
return $this->success($workOrder);
}
@ -33,10 +28,11 @@ public function show(Request $request, WorkOrder $workOrder): JsonResponse
public function update(WorkOrderRequest $request, WorkOrder $workOrder): JsonResponse
{
$this->validate($request, [
'status' => 'nullable|sometimes|string|in:open,closed,on_hold,in_progress',
'status' => 'nullable|sometimes|string|in:open,closed,on_hold,in_progress,read',
]);
$workOrder->update($request->only('status'));
return $this->success($workOrder);
}
}

View File

@ -4,6 +4,7 @@
use App\Events\UserEvent;
use App\Exceptions\CommonException;
use App\Models\Module;
use App\Models\User;
use Eloquent;
use GeneaLabs\LaravelModelCaching\CachedBuilder;
@ -66,13 +67,15 @@ class Reply extends Model
'content',
'work_order_id',
'user_id',
'name',
'module_id',
'is_pending',
];
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
static::creating(function (self $model) {
$model->is_pending = 1;
// load work order
@ -82,10 +85,11 @@ protected static function boot()
if (is_null($model->workOrder)) {
throw new CommonException('Work order not found');
}
throw_if($model->workOrder->status == 'pending' || $model->workOrder->status == 'error', CommonException::class, '工单状态不正确');
throw_if($model->workOrder->isFailure(), CommonException::class, '工单还没有就绪。');
// change work order status
if (auth()->check()) {
if (auth('sanctum')->check()) {
$model->user_id = auth()->id();
$model->workOrder->status = 'user_replied';
}
@ -116,6 +120,11 @@ public function workOrder(): BelongsTo
return $this->belongsTo(WorkOrder::class, 'work_order_id', 'id');
}
public function module(): BelongsTo
{
return $this->belongsTo(Module::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
@ -123,7 +132,6 @@ public function user(): BelongsTo
// before create
public function scopeWorkOrderId($query, $work_order_id)
{
return $query->where('work_order_id', $work_order_id);

View File

@ -15,6 +15,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
/**
* App\Models\WorkOrder\WorkOrder
@ -87,32 +88,33 @@ protected static function boot()
{
parent::boot();
static::creating(function ($model) {
static::creating(function (self $model) {
// 生成 uuid
$model->uuid = Str::uuid()->toString();
if ($model->host_id) {
$model->load(['host']);
$model->module_id = $model->host->module_id;
}
// if logged
if (auth()->check()) {
if (auth('sanctum')->check()) {
$model->user_id = auth()->id();
if ($model->host_id) {
if (!$model->user_id === $model->host->user_id) {
if (!$model->user_id == $model->host->user_id) {
throw new CommonException('user_id not match host user_id');
}
}
} else {
throw new CommonException('user_id is required');
if (!$model->user_id) {
throw new CommonException('user_id is required');
}
}
if ($model->host_id) {
$model->host->load('module');
$module = $model->host->module;
if ($module === null) {
$model->status = 'open';
} else {
@ -127,6 +129,41 @@ protected static function boot()
});
}
public function scopeThisModule($query)
{
return $query->where('module_id', auth('module')->id());
}
public function scopeThisUser($query)
{
return $query->where('user_id', auth()->id());
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function replies(): HasMany
{
return $this->hasMany(Reply::class);
}
public function host(): BelongsTo
{
return $this->belongsTo(Host::class);
}
public function module(): BelongsTo
{
return $this->belongsTo(Module::class);
}
public function isFailure(): bool
{
return $this->status === 'pending' || $this->status === 'error';
}
/**
* @throws CommonException
*/
@ -140,43 +177,4 @@ public function safeDelete(): bool
return true;
}
// replies
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
// host
public function replies(): HasMany
{
return $this->hasMany(Reply::class);
}
public function host(): BelongsTo
{
return $this->belongsTo(Host::class);
}
// scope
public function module(): BelongsTo
{
return $this->belongsTo(Module::class);
}
public function scopeThisModule($query)
{
return $query->where('module_id', auth('module')->id());
}
// on create
public function scopeThisUser($query)
{
return $query->where('user_id', auth()->id());
}
}

View File

@ -28,7 +28,7 @@ public function created(Reply $reply)
*
* @return void
*/
public function updated(Reply $reply)
public function updated(Reply $reply): void
{
//
}
@ -40,7 +40,7 @@ public function updated(Reply $reply)
*
* @return void
*/
public function deleted(Reply $reply)
public function deleted(Reply $reply): void
{
//
}
@ -52,20 +52,9 @@ public function deleted(Reply $reply)
*
* @return void
*/
public function restored(Reply $reply)
public function restored(Reply $reply): void
{
//
}
/**
* Handle the Reply "force deleted" event.
*
* @param Reply $reply
*
* @return void
*/
public function forceDeleted(Reply $reply)
{
//
}
}

View File

@ -4,7 +4,6 @@
use App\Models\WorkOrder\WorkOrder;
use App\Notifications\WorkOrderNotification;
use Illuminate\Support\Facades\Log;
class WorkOrderObserver
{
@ -31,11 +30,9 @@ public function created(WorkOrder $workOrder)
*/
public function updated(WorkOrder $workOrder)
{
Log::debug('workOrder updated', ['workOrder' => $workOrder]);
//
return (new WorkOrderNotification())
->toGroup($workOrder);
return;
// return (new WorkOrderNotification())
// ->toGroup($workOrder);
}
/**
@ -45,7 +42,7 @@ public function updated(WorkOrder $workOrder)
*
* @return void
*/
public function deleted(WorkOrder $workOrder)
public function deleted(WorkOrder $workOrder): void
{
//
}
@ -57,20 +54,9 @@ public function deleted(WorkOrder $workOrder)
*
* @return void
*/
public function restored(WorkOrder $workOrder)
public function restored(WorkOrder $workOrder): void
{
//
}
/**
* Handle the WorkOrder "force deleted" event.
*
* @param WorkOrder $workOrder
*
* @return void
*/
public function forceDeleted(WorkOrder $workOrder)
{
//
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies\WorkOrder;
use App\Models\User;
use App\Models\WorkOrder\Reply;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Auth\Access\Response;
class ReplyPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*
* @param User $user
*
* @return Response|bool
*/
public function viewAny(User $user): Response|bool
{
//
return true;
}
/**
* Determine whether the user can create models.
*
* @param User $user
*
* @return Response|bool
*/
public function create(User $user): Response|bool
{
return true;
}
/**
* Determine whether the user can update the model.
*
* @param User $user
* @param Reply $reply
*
* @return Response|bool
*/
public function update(User $user, Reply $reply): Response|bool
{
return false;
}
/**
* Determine whether the user can delete the model.
*
* @param User $user
* @param Reply $reply
*
* @return Response|bool
*/
public function delete(User $user, Reply $reply): Response|bool
{
//
return false;
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Policies\WorkOrder;
use App\Models\Module;
use App\Models\User;
use App\Models\WorkOrder\WorkOrder;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Auth\Access\Response;
class WorkOrderPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view the model.
*
* @param User|Module $user
* @param WorkOrder $workOrder
*
* @return Response|bool
*/
public function view(User|Module $user, WorkOrder $workOrder): Response|bool
{
if ($user instanceof Module) {
return $user->id === $workOrder->module_id;
}
return true;
}
/**
* Determine whether the user can update the model.
*
* @param User|Module $user
* @param WorkOrder $workOrder
*
* @return Response|bool
*/
public function update(User|Module $user, WorkOrder $workOrder): Response|bool
{
if ($user instanceof Module) {
return $user->id === $workOrder->module_id;
}
return true;
}
/**
* Determine whether the user can delete the model.
*
* @param User|Module $user
* @param WorkOrder $workOrder
*
* @return Response|bool
*/
public function delete(User|Module $user, WorkOrder $workOrder): Response|bool
{
if ($user instanceof Module) {
return $user->id === $workOrder->module_id;
}
return $user->id === $workOrder->user_id
? Response::allow()
: Response::deny('You do not own this work order.');
}
}

View File

@ -3,25 +3,30 @@
namespace App\Providers;
// use Illuminate\Support\Facades\Gate;
use App\Models\WorkOrder\Reply;
use App\Models\WorkOrder\WorkOrder;
use App\Policies\WorkOrder\ReplyPolicy;
use App\Policies\WorkOrder\WorkOrderPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
* 应用程序的策略映射。
*
* @var array<class-string, class-string>
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
WorkOrder::class => WorkOrderPolicy::class,
Reply::class => ReplyPolicy::class,
];
/**
* Register any authentication / authorization services.
* 注册任何应用程序 身份验证 / 授权服务。
*
* @return void
*/
public function boot()
public function boot(): void
{
$this->registerPolicies();

View File

@ -2,14 +2,12 @@
namespace App\View\Components;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class WorkOrderStatus extends Component
{
public $status = null;
public string|null $status = null;
/**
* Create a new component instance.
@ -18,17 +16,15 @@ class WorkOrderStatus extends Component
*/
public function __construct($status)
{
//
$this->status = $status;
}
/**
* Get the view / contents that represent the component.
*
* @return Application|Factory|View
* @return View
*/
public function render()
public function render(): View
{
$this->status = match ($this->status) {
'pending' => '推送中',

View File

@ -0,0 +1,39 @@
<?php
use App\Models\WorkOrder\WorkOrder;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('work_orders', function (Blueprint $table) {
// uuid
$table->uuid('uuid')->nullable()->after('id')->index()->unique();
});
// 为每个工单生成一个 uuid 安静更改
WorkOrder::query()->chunk(100, function ($workOrders) {
foreach ($workOrders as $workOrder) {
$workOrder->uuid = Str::uuid();
$workOrder->saveQuietly();
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
return;
}
};

View File

@ -0,0 +1,48 @@
<?php
use App\Models\WorkOrder\Reply;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('work_order_replies', function (Blueprint $table) {
// name
$table->string('name')->nullable()->after('user_id');
// module_id
$table->string('module_id')->nullable()->after('name')->index();
$table->foreign('module_id')->references('id')->on('modules')->cascadeOnDelete();
});
// 为每个工单回复生成一个 module_id 安静更改
Reply::whereNull('module_id')->with('workOrder')->chunk(100, function ($replies) {
foreach ($replies as $reply) {
$reply->module_id = $reply->workOrder->module_id;
$reply->saveQuietly();
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('work_order_replies', function (Blueprint $table) {
Schema::hasColumn('work_order_replies', 'name') && $table->dropColumn('name');
$table->dropForeign(['module_id']);
$table->dropColumn('module_id');
});
}
};

View File

@ -14,11 +14,19 @@
@foreach($replies as $reply)
<div class="card border-light mb-3 shadow">
<div class="card-header">
<div class="card-header d-flex w-100 justify-content-between">
@if ($reply->user_id)
<a href="{{ route('admin.users.edit', $reply->user) }}">{{ $workOrder->user->name }}</a>
@elseif($reply->module_id)
<a href="{{ route('admin.modules.edit', $workOrder->module_id) }}">{{ $workOrder->module->name }}
@if ($reply->name)
{{ $reply->name }}
@endif
</a>
@elseif ($reply->name === null && $reply->user_id === null && $reply->module_id === null)
<span class="text-primary">{{ config('app.display_name') }}</span>
@else
<a href="{{ route('admin.modules.edit', $workOrder->module_id) }}">{{ $workOrder->module->name }}</a>
{{ $reply->name }}
@endif
<span class="text-end">{{ $reply->created_at }}</span>
@ -37,7 +45,8 @@
{{-- label --}}
<div class="form-group">
<label for="content">内容</label>
<textarea class="form-control" id="content" name="content" rows="10" placeholder="代替模块的回复。"></textarea>
<textarea class="form-control" id="content" name="content" rows="10"
placeholder="代替模块的回复。"></textarea>
</div>
<button type="submit" class="btn btn-primary mt-3 mb-3">提交</button>

View File

@ -29,9 +29,14 @@
Route::get('hosts/usages', [HostController::class, 'usages']);
Route::apiResource('hosts', HostController::class);
Route::apiResource('work-orders', WorkOrderController::class)->only(['index', 'store', 'show', 'update']);
Route::apiResource('work-orders', WorkOrderController::class)->only(['index', 'store', 'update']);
Route::apiResource('work-orders.replies', ReplyController::class)->only(['index', 'store']);
Route::withoutMiddleware('auth:sanctum')->prefix('work-orders')->group(function () {
Route::get('{workOrder:uuid}', [WorkOrderController::class, 'show']);
Route::get('{workOrder:uuid}/replies', [ReplyController::class, 'index']);
Route::post('{workOrder:uuid}/replies', [ReplyController::class, 'store']);
});
Route::any('modules/{module}/{path?}', [ModuleController::class, 'call'])
->where('path', '.*');