作者 ZhengBing He

合并分支 'workorder' 到 'master'

Workorder



查看合并请求 !2063
<?php
namespace App\Http\Controllers\Aside\WorkOrder;
use App\Enums\Common\Code;
use App\Http\Controllers\Aside\BaseController;
use App\Http\Requests\Aside\WorkOrder\WorkOrderListRequest;
use App\Http\Requests\Aside\WorkOrder\WorkOrderUpdateRequest;
use App\Models\WorkOrder\WorkOrder;
use App\Models\WorkOrder\WorkOrderLog;
use Illuminate\Support\Facades\DB;
class WorkOrderController extends BaseController
{
/**
* A端工单列表
* 显示有我参与的工单列表
*/
public function index(WorkOrderListRequest $request)
{
/*
* A端工程师工单列表, 查询我的工单
*/
$request->validated();
# manage_id 或 engineer_id 是我
$lists = WorkOrderLog::with([
'workOrder.logs.manager:id,name',
])
->where('manage_id', $this->manage['id'])
->when($request->has('status'), function ($query) use ($request) {
return $query->where('status', $request->input('status'));
})
->when($request->has('search'), function ($query) use ($request) {
return $query->whereHas('workOrder', function ($q) use ($request) {
$q->where('product', 'like', '%' . $request->input('search') . '%')
->orWhere('content', 'like', '%' . $request->input('search') . '%');
});
})
->orderBy('id', 'desc')
->paginate($this->row, ['*'], 'page', $this->page);
$this->response('success', Code::SUCCESS, $lists);
}
/**
* @param WorkOrderListRequest $request
* @return void
* A端管理员的工单列表
*/
public function manager(WorkOrderListRequest $request)
{
$request->validated();
// 管理员查看所有工单
$lists = WorkOrder::with([
'user:id,name',
'manager:id,name',
'engineer:id,name',
'logs.manager:id,name',
])
->when($request->has('status'), function ($query) use ($request) {
return $query->where('status', $request->input('status'));
})
->when($request->has('search'), function ($query) use ($request) {
return $query->where('product', 'like', '%' . $request->input('search') . '%')
->orWhere('content', 'like', '%' . $request->input('search') . '%');
})
->orderBy('id', 'desc')
->paginate($this->row, ['*'], 'page', $this->page);
$this->response('success', Code::SUCCESS, $lists);
}
/**
* A端工单详情
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$workOrder = WorkOrder::with([
'logs.manager:id,name',
'user:id,name',
'manager:id,name',
'engineer:id,name',
])->find($id);
if (!$workOrder)
$this->response('工单不存在', Code::USER_MODEL_NOTFOUND_ERROE);
// TODO 判断是否有查看工单详情权限,待添加
$this->response('success', Code::SUCCESS, $workOrder->toArray());
}
/**
* A端操作工单,工程师操作的是工单日志
* - 工程师:回复工单,自动将拆分给自己的子任务改为完成
* - 工单第一对接人:
* - 邀请工程师处理工单
* - 修改工单状态
* 若全部子任务完成,则将工单状态改为完成
*/
public function update(WorkOrderUpdateRequest $request, $id)
{
$request->validated();
$log = WorkOrderLog::find($id); // 拆分的子工单
if (!$log) {
$this->response('工单不存在', Code::USER_MODEL_NOTFOUND_ERROE);
}
if ($log->manage_id != $this->manage['id']) {
// 只能操作自己的工单
$this->response('没有权限操作该工单', Code::USER_PERMISSION_ERROE);
}
$workOrder = $log->workOrder;
$result = DB::transaction(function () use ($request, $workOrder, $log) {
if ($request->has('engineer_ids'))
{
// 有邀请工程师协同处理
foreach ($request->input('engineer_ids') as $engineer_id)
{
try {
// 利用唯一索引去重
$new_log = new WorkOrderLog();
$new_log->manage_id = $engineer_id;
$workOrder->logs()->save($new_log);
$workOrder->engineer_id = $engineer_id;
$workOrder->save();
}catch (\Exception $exception){}
}
}
if ($request->input('content'))
{
// 工程师在回复处理结果
$log->content = $request->input('content');
if ($request->input('files'))
$log->files = $request->input('files');
$log->status = WorkOrderLog::STATUS_COMPLETED;
$log->end_at = now();
$log->save();
// 如果所有的子任务都已完成, 则更新工单主表状态
$pending = $workOrder->logs()
->where('status', '<', WorkOrderLog::STATUS_COMPLETED)
->count();
if ($pending == 0)
{
$workOrder->status = WorkOrderLog::STATUS_COMPLETED;
$workOrder->save();
}
}
return $log;
});
$this->response('success', Code::SUCCESS, $result->toArray());
}
}
... ...
<?php
namespace App\Http\Controllers\Bside\WorkOrder;
use App\Enums\Common\Code;
use App\Http\Controllers\Bside\BaseController;
use App\Http\Requests\Aside\WorkOrder\WorkOrderListRequest;
use App\Http\Requests\Bside\WorkOrder\WorkOrderCreateRequest;
use App\Http\Requests\Bside\WorkOrder\WorkOrderUpdateRequest;
use App\Models\Project\Project;
use App\Models\WorkOrder\WorkOrder;
use App\Models\WorkOrder\WorkOrderLog;
use Illuminate\Support\Facades\DB;
class WorkOrderController extends BaseController
{
/**
* B端售后工单列表
*
* @return \Illuminate\Http\Response
*/
public function index(WorkOrder $workOrder, WorkOrderListRequest $request)
{
$request->validated();
$where = [
'project_id' => $this->user['project_id'],
];
// 分页查询
$lists = $workOrder->where($where)
->when($request->input('status'), function ($query) use ($request) {
return $query->where('status', $request->input('status'));
})
->when($request->input('search'), function ($query) use ($request) {
return $query->where('product', 'like', '%' . $request->input('search') . '%')
->orWhere('content', 'like', '%' . $request->input('search') . '%');
})
->orderBy('id', 'desc')->paginate($this->row, ['*'], 'page', $this->page);
$this->response('success', Code::SUCCESS, $lists);
}
/**
* B端用户提交工单
*
* @return \Illuminate\Http\Response
*/
public function store(WorkOrderCreateRequest $request)
{
$request->validated();
$result = DB::transaction(function () use ($request) {
$workOrder = new WorkOrder;
$workOrder->product = $request->input('product');
$workOrder->content = $request->input('content');
$workOrder->files = json_encode($request->input('files'));
$workOrder->project_user_id = $this->user['id'];
$workOrder->project_id = $this->user['project_id'];
$workOrder->engineer_id = 0;
if ($this->project['type'] == 3){
// 项目类型是优化推广,项目负责人找优化
$seo = Project::find($this->user['project_id'])->deploy_optimize;
$workOrder->manage_id = $seo->manager_mid ?? $seo->optimist_mid ?? 0;
}else{
// 非优化推广项目,项目负责人找技术组长
$build = Project::find($this->user['project_id'])->deploy_build;
$workOrder->manage_id = $build->leader_mid ?? 0;
}
$workOrder->save();
// 添加工单日志
$log = new WorkOrderLog();
$log->manage_id = $workOrder->manage_id;
$workOrder->logs()->save($log);
return $workOrder;
});
$this->response('success', Code::SUCCESS, $result->toArray());
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$workOrder = WorkOrder::with([
'logs.manager:id,name',
'user:id,name',
'manager:id,name',
'engineer:id,name',
])->find($id);
if (!$workOrder) {
$this->response('error', 404, '工单未找到');
}
if ($workOrder->project_id != $this->user['project_id'] && $workOrder->engineer_id != $this->user['id']) {
$this->response('error', 403, '无权限查看该工单');
}
$this->response('success', Code::SUCCESS, $workOrder->toArray());
}
/**
* B端用户修改工单,只能是修改工单状态
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(WorkOrderUpdateRequest $request, $id)
{
$request->validated();
$workOrder = WorkOrder::find($id);
if (!$workOrder) {
$this->response('error', 404, '工单未找到');
}
// b端只有自己项目下的账号可以更新工单
if ($workOrder->project_id != $this->user['project_id']) {
$this->response('error', 403, '无权限更新该工单');
}
// 更新工单状态
$workOrder->status = $request->input('status');
$workOrder->save();
$this->response('success', Code::SUCCESS);
}
}
... ...
<?php
namespace App\Http\Requests\Aside\WorkOrder;
use Illuminate\Foundation\Http\FormRequest;
class WorkOrderListRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'page' => 'nullable|integer',
'size' => 'nullable|integer',
'project_id' => 'nullable|integer', // 产品ID
'status' => 'nullable|in:0,1,2,3|integer',
'search' => 'nullable|string', // 搜索关键词
];
}
}
... ...
<?php
namespace App\Http\Requests\Aside\WorkOrder;
use Illuminate\Foundation\Http\FormRequest;
class WorkOrderUpdateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
* A端更新售后工单
*/
public function rules()
{
return [
'status' => 'nullable|in:0,1,2,3|integer',
'engineer_ids' => 'nullable|array',
'content' => 'nullable|string',
'files' => 'nullable|array',
];
}
}
... ...
<?php
namespace App\Http\Requests\Bside\WorkOrder;
use Illuminate\Foundation\Http\FormRequest;
class WorkOrderCreateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'product' => 'required|string',
'content' => 'required|string',
'files' => 'nullable|array',
];
}
}
... ...
<?php
namespace App\Http\Requests\Bside\WorkOrder;
use Illuminate\Foundation\Http\FormRequest;
class WorkOrderUpdateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'status' => 'required|in:0,1,2,3|integer',
];
}
}
... ...
<?php
namespace App\Models\WorkOrder;
use App\Models\Base;
use App\Models\Manage\Manage;
use App\Models\Project\Project;
use App\Models\User\User;
class WorkOrder extends Base
{
protected $table = 'gl_work_orders';
const STATUS_PEDDING = 0; // 待处理
const STATUS_PROCESSING = 1; // 处理中
const STATUS_COMPLETED = 2; // 已完成
const STATUS_CLOSED = 3; // 已关闭
/**
* 工单操作日志
*/
public function logs()
{
return $this->hasMany(WorkOrderLog::class, 'work_order_id', 'id');
}
/**
* 提交工单的用户
*/
public function user()
{
return $this->belongsTo(User::class, 'project_user_id', 'id');
}
/**
* 工单分配的管理员
*/
public function manager()
{
return $this->belongsTo(Manage::class, 'manage_id', 'id');
}
/**
* 工单分配的工程师
*/
public function engineer()
{
return $this->belongsTo(Manage::class, 'engineer_id', 'id');
}
/** 工单所属项目 */
public function project()
{
return $this->belongsTo(Project::class, 'project_id', 'id');
}
}
... ...
<?php
namespace App\Models\WorkOrder;
use App\Models\Base;
use App\Models\Manage\Manage;
class WorkOrderLog extends Base
{
protected $table = 'gl_work_order_logs';
const STATUS_PEDDING = 0; // 待处理
const STATUS_PROCESSING = 1; // 处理中
const STATUS_COMPLETED = 2; // 已完成
const STATUS_CLOSED = 3; // 已关闭
/**
* 所属工单
*/
public function workOrder()
{
return $this->belongsTo(WorkOrder::class, 'work_order_id');
}
/**
* 分配的工单负责人,工程师等
*/
public function manager()
{
return $this->belongsTo(Manage::class, 'manage_id', 'id');
}
}
... ...
... ... @@ -12,6 +12,7 @@
"beyondcode/laravel-websockets": "^1.14",
"doctrine/dbal": "^3.6",
"fruitcake/laravel-cors": "^2.0",
"g4t/swagger": "^4.0",
"guzzlehttp/guzzle": "^7.0.1",
"hashids/hashids": "^4.1",
"intervention/image": "^2.7",
... ...
<?php
return [
/*
|--------------------------------------------------------------------------
| API Title
|--------------------------------------------------------------------------
|
| The title of your API documentation.
|
*/
"title" => env("SWAGGER_TITLE", "Globalso V6 Api Documentation"),
/*
|--------------------------------------------------------------------------
| API Description
|--------------------------------------------------------------------------
|
| The description of your API.
|
*/
"description" => env("SWAGGER_DESCRIPTION", "Laravel autogenerate swagger"),
/*
|--------------------------------------------------------------------------
| API Email
|--------------------------------------------------------------------------
|
| The email associated with your API documentation.
|
*/
"email" => env("SWAGGER_EMAIL", "bill@ai.cc"),
/*
|--------------------------------------------------------------------------
| API Version
|--------------------------------------------------------------------------
|
| The version of your API.
|
*/
"version" => env("SWAGGER_VERSION", "3.0.7"),
/*
|--------------------------------------------------------------------------
| Documentation Auth
|--------------------------------------------------------------------------
|
| This options to enable documentation auth
|
*/
"enable_auth" => false,
"username" => "admin",
"password" => "pass",
"sesson_ttl" => 100000,
/*
|--------------------------------------------------------------------------
| Enable Response Schema
|--------------------------------------------------------------------------
|
| Whether to enable response schema or not.
|
*/
"enable_response_schema" => true,
"suggestions_select_input" => false,
"load_from_json" => false,
/*
|--------------------------------------------------------------------------
| Authentication Middlewares
|--------------------------------------------------------------------------
|
| List of middleware names used for authentication.
|
*/
"auth_middlewares" => [
"auth",
"auth:api",
"aloginauth",
"bloginauth",
],
/*
|--------------------------------------------------------------------------
| API URL
|--------------------------------------------------------------------------
|
| The URL path for accessing your API documentation.
|
*/
"url" => env("SWAGGER_URL", "swagger/documentation"),
/*
|--------------------------------------------------------------------------
| Issues URL
|--------------------------------------------------------------------------
|
| The URL path for accessing issues related to your API documentation.
|
*/
"issues_url" => env("SWAGGER_ISSUE_URL", "swagger/issues"),
/*
|--------------------------------------------------------------------------
| Enable Swagger
|--------------------------------------------------------------------------
|
| Whether Swagger is enabled or not.
|
*/
"enable" => env('SWAGGER_ENABLED', true),
/*
|--------------------------------------------------------------------------
| Show Prefix
|--------------------------------------------------------------------------
|
| List of prefixes to show in Swagger.
|
*/
"show_prefix" => [],
/*
|--------------------------------------------------------------------------
| Include Web Routes
|--------------------------------------------------------------------------
|
| If you want to includes web.php routes, then enable this
|
*/
"include_web_routes" => env('SWAGGER_INCLUDE_WEB_ROUTES', true),
/*
|--------------------------------------------------------------------------
| API Versions
|--------------------------------------------------------------------------
|
| List of versions to show in Swagger.
|
*/
"versions" => [
"all",
// "v1"
],
"default" => "all",
/*
|--------------------------------------------------------------------------
| Servers
|--------------------------------------------------------------------------
|
| List of servers associated with your API.
|
*/
"servers" => [
[
"url" => env("APP_URL"),
"description" => "localhost"
]
],
/*
|--------------------------------------------------------------------------
| Security Schemes
|--------------------------------------------------------------------------
|
| Security schemes used in your API.
|
*/
"security_schemes" => [
"APIKeyHeader" => [
"type" => "apiKey",
"name" => "token",
"in" => "header"
],
],
/*
|--------------------------------------------------------------------------
| Spatie Query Builder
|--------------------------------------------------------------------------
|
| Enable it if you using Spatie query builder package to add spatie filters in all GET routes.
|
*/
"spatie_query_builder" => false,
/*
|--------------------------------------------------------------------------
| Status
|--------------------------------------------------------------------------
|
| HTTP response statuses for various methods.
|
*/
"status" => [
"GET" => [
"200" => [
"description" => "Successful Operation",
],
"404" => [
"description" => "Not Found"
]
],
"POST" => [
"200" => [
"description" => "Successful Operation",
],
"422" => [
"description" => "Validation Issues"
]
],
"PUT" => [
"200" => [
"description" => "Successful Operation",
],
"404" => [
"description" => "Not Found"
],
"405" => [
"description" => "Validation exception"
]
],
"PATCH" => [
"200" => [
"description" => "Successful Operation",
],
"404" => [
"description" => "Not Found"
],
"405" => [
"description" => "Validation exception"
]
],
"DELETE" => [
"200" => [
"description" => "successful Operation",
],
"404" => [
"description" => "page Not Found"
]
],
],
];
... ...
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePasswordResetsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('password_resets', function (Blueprint $table) {
$table->string('email')->index();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('password_resets');
}
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateFailedJobsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('failed_jobs');
}
}
... ... @@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePersonalAccessTokensTable extends Migration
class CreateWorkOrdersTable extends Migration
{
/**
* Run the migrations.
... ... @@ -13,15 +13,21 @@ class CreatePersonalAccessTokensTable extends Migration
*/
public function up()
{
Schema::create('personal_access_tokens', function (Blueprint $table) {
Schema::create('gl_work_orders', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamps();
$table->string('product')->nullable()->comment('相关产品,工单类型');
$table->longText('content')->comment('工单图文描述');
$table->json('files')->nullable()->comment('附件');
$table->integer('status')->index()->default(0)->comment('工单状态,0:待处理, 1:处理中,2:已完成, 3:已关闭');
$table->integer('project_user_id')->index()->comment('客户ID');
$table->integer('project_id')->index()->comment('项目ID');
$table->integer('manage_id')->index()->comment('A端项目负责人ID, gl_manage 表ID');
$table->integer('engineer_id')->index()->comment('处理技术,工程师id, gl_manage 表ID');
$table->timestamp('end_at')->nullable()->comment('完成时间');
});
# 添加表注释
\Illuminate\Support\Facades\DB::statement("ALTER TABLE gl_work_orders comment '工单表'");
}
/**
... ... @@ -31,6 +37,6 @@ class CreatePersonalAccessTokensTable extends Migration
*/
public function down()
{
Schema::dropIfExists('personal_access_tokens');
Schema::dropIfExists('gl_work_orders');
}
}
... ...
... ... @@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
class CreateWorkOrderLogsTable extends Migration
{
/**
* Run the migrations.
... ... @@ -13,15 +13,19 @@ class CreateUsersTable extends Migration
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
Schema::create('gl_work_order_logs', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
$table->integer('work_order_id')->index();
$table->integer('manage_id')->comment('gl_manage 表ID,操作人ID');
$table->boolean('ding')->default(false)->comment('是否钉钉通知');
$table->longText('content')->nullable()->comment('处理结果,图文描述');
$table->json('files')->nullable()->comment('附件');
$table->integer('status')->index()->default(0)->comment('工单状态,0:待处理, 1:处理中, 2:已完成');
$table->timestamp('end_at')->nullable()->comment('完成时间');
$table->unique(['work_order_id', 'manage_id'], 'work_order_manage_unique'); # 唯一索引,防止同一工单被同一人多次操作,同一工单,可以多人协作
});
\Illuminate\Support\Facades\DB::statement('ALTER TABLE gl_work_order_logs comment "工单操作日志表,分配工程师时创建,工程师完成时修改状态"');
}
/**
... ... @@ -31,6 +35,6 @@ class CreateUsersTable extends Migration
*/
public function down()
{
Schema::dropIfExists('users');
Schema::dropIfExists('gl_work_order_logs');
}
}
... ...
不能预览此文件类型
... ... @@ -246,6 +246,13 @@ Route::middleware(['aloginauth'])->group(function () {
Route::post('/save_follow', [Aside\Task\TaskController::class, 'save_follow'])->name('admin.task_save_follow');
Route::any('/getUserTaskList', [Aside\Task\TaskController::class, 'getUserTaskList'])->name('admin.task_getUserTaskList');
});
// 售后工单
Route::prefix('workorder')->group(function () {
Route::get('/', [Aside\WorkOrder\WorkOrderController::class, 'index'])->name('admin.workorder.index')->summary('A端工程师的工单列表');
Route::get('/manager', [Aside\WorkOrder\WorkOrderController::class, 'manager'])->name('admin.workorder.manager')->summary('A端管理员的工单列表');
Route::get('/{id}', [Aside\WorkOrder\WorkOrderController::class, 'show'])->name('admin.workorder.show')->summary('A端工单详情');
Route::post('/{id}', [Aside\WorkOrder\WorkOrderController::class, 'update'])->name('admin.workorder.update')->summary('A端更新工单');
});
//服务器配置
Route::prefix('devops')->group(function () {
Route::any('/', [Aside\Devops\ServerConfigController::class, 'lists'])->name('admin.devops.lists');
... ...
... ... @@ -269,6 +269,15 @@ Route::middleware(['bloginauth'])->group(function () {
Route::any('/save',[\App\Http\Controllers\Bside\Setting\WebSettingAmpController::class, 'save'])->name('amp_save');
});
});
// 售后工单
Route::prefix('workorder')->group(function () {
Route::get('/', [\App\Http\Controllers\Bside\WorkOrder\WorkOrderController::class, 'index'])->name('workorder.index')->summary('B端售后工单列表');
Route::post('/', [\App\Http\Controllers\Bside\WorkOrder\WorkOrderController::class, 'store'])->name('workorder.store')->summary('B端创建工单');
Route::get('/{id}', [\App\Http\Controllers\Bside\WorkOrder\WorkOrderController::class, 'show'])->name('workorder.show')->summary('B端查看工单');
Route::post('/{id}', [\App\Http\Controllers\Bside\WorkOrder\WorkOrderController::class, 'update'])->name('workorder.update')->summary('B端完结工单');
});
//产品
Route::prefix('product')->group(function () {
//产品
... ...