admin 审核流程

main
panliang 2024-04-01 14:17:06 +08:00
parent 56384c3f81
commit 965e591ad2
17 changed files with 267 additions and 117 deletions

View File

@ -19,6 +19,7 @@ class SignRepairController extends AdminController
public function list(): Page
{
$subjectType = (new EmployeeSignRepair)->getMorphClass();
$crud = $this->baseCRUD()
->tableLayout('fixed')
->headerToolbar([
@ -60,35 +61,17 @@ class SignRepairController extends AdminController
->label('发起审核')
->level('link')
->api(amisMake()->BaseApi()->url(admin_url('api/workflow/apply'))->method('post')->data([
'subject_type' => (new EmployeeSignRepair)->getMorphClass(),
'subject_type' => $subjectType,
'subject_id' => '${id}',
'user' => '${employee_id}'
]))
->confirmText(__('admin.confirm'))
->visibleOn('${OR(check_status == '.CheckStatus::None->value.', check_status == '.CheckStatus::Cancel->value.', check_status == '.CheckStatus::Fail->value.')}'),
amisMake()->AjaxAction()
->label('审核通过')
->level('link')
->api(amisMake()->BaseApi()->url(admin_url('api/workflow/cancel'))->method('post')->data([
'subject_type' => EmployeeSignRepair::class,
'subject_id' => '${id}',
]))
->confirmText(__('admin.confirm'))
->visibleOn('${check_status == '.CheckStatus::Processing->value.'}'),
amisMake()->AjaxAction()
->label('审核不通过')
->level('link')
->api(amisMake()->BaseApi()->url(admin_url('api/workflow/cancel'))->method('post')->data([
'subject_type' => EmployeeSignRepair::class,
'subject_id' => '${id}',
]))
->confirmText(__('admin.confirm'))
->visibleOn('${check_status == '.CheckStatus::Processing->value.'}'),
amisMake()->AjaxAction()
->label('取消审核')
->level('link')
->api(amisMake()->BaseApi()->url(admin_url('api/workflow/cancel'))->method('post')->data([
'subject_type' => EmployeeSignRepair::class,
'subject_type' => $subjectType,
'subject_id' => '${id}',
]))
->confirmText(__('admin.confirm'))
@ -118,8 +101,61 @@ class SignRepairController extends AdminController
public function detail(): Form
{
$subjectType = (new EmployeeSignRepair)->getMorphClass();
$detail = amisMake()->Property()->items([
['label' => __('employee_sign_repair.store_id'), 'content' => '${store.title}'],
['label' => __('employee_sign_repair.employee_id'), 'content' => '${employee.name}'],
['label' => __('employee_sign_repair.date'), 'content' => '${date}'],
['label' => __('employee_sign_repair.repair_type'), 'content' => amisMake()->Mapping()->name('repair_type')->map(SignTime::options())],
['label' => __('employee_sign_repair.reason'), 'content' => '${reason}'],
['label' => __('employee_sign_repair.created_at'), 'content' => '${created_at}'],
['label' => __('employee_sign_repair.check_status'), 'content' => amisMake()->Mapping()->name('check_status')->map(CheckStatus::options())],
['label' => __('employee_sign_repair.checked_at'), 'content' => '${checked_at}'],
['label' => __('employee_sign_repair.check_remarks'), 'content' => '${check_remarks}'],
]);
return $this->baseDetail()->title('')->body($detail);
$table = amisMake()->Service()
->id('sign-repair-checklog-table')
->initFetch(false)
->api(
amisMake()->BaseApi()->method('get')->url(admin_url('api/workflow/logs'))->data([
'subject_type' => $subjectType,
'subject_id' => '${id}',
])
)
->body(
amisMake()->Table()->columnsTogglable(false)->itemActions([
amisMake()->AjaxAction()
->label('审核通过')
->level('link')
->api('post:' . admin_url('api/workflow/success?id=${id}'))
->confirmText(__('admin.confirm'))
->visibleOn('${check_status == '.CheckStatus::Processing->value.'}')
->reload('sign-repair-detail'),
amisMake()->DialogAction()
->label('审核不通过')
->level('link')
->dialog(amisMake()->Dialog()->title('审核不通过')->body(
amisMake()->Form()->title('')->api('post:'.admin_url('api/workflow/fail'))->body([
amisMake()->HiddenControl()->name('id'),
amisMake()->TextControl()->name('remarks')->label(__('workflow_log.remarks'))->required(),
])->reload('sign-repair-detail')
))
->visibleOn('${check_status == '.CheckStatus::Processing->value.'}'),
])->columns([
amisMake()->TableColumn()->name('batch_id')->label(__('workflow_log.batch_id')),
amisMake()->TableColumn()->name('check_name')->label(__('workflow_log.check_name')),
amisMake()->TableColumn()->name('check_user.name')->label(__('workflow_log.check_user_id')),
amisMake()->TableColumn()->name('check_status')->label(__('workflow_log.check_status'))->set('type', 'mapping')->map(CheckStatus::options()),
amisMake()->TableColumn()->name('checked_at')->label(__('workflow_log.checked_at')),
amisMake()->TableColumn()->name('remarks')->label(__('workflow_log.remarks')),
])
);
return $this->baseDetail()->id('sign-repair-detail')->title('')->onEvent([
'inited' => [
'actions' => [
['actionType' => 'reload', 'componentId' => 'sign-repair-checklog-table'],
]
]
])->body([$detail, amisMake()->Divider(), $table]);
}
}

View File

@ -39,6 +39,7 @@ class EmployeeController extends AdminController
->columns([
amisMake()->TableColumn()->name('store.title')->label(__('employee.store_id')),
amisMake()->TableColumn()->name('name')->label(__('employee.name')),
amisMake()->TableColumn()->name('jobs')->label(__('employee.jobs'))->type('each')->items(amisMake()->Tag()->label('${name}')),
// amisMake()->TableColumn()->name('store.master_id')->label(__('store.master_id'))->set('type', 'tpl')->tpl('${store.master_id == id ? "店长" : "--"}'),
amisMake()->TableColumn()->name('phone')->label(__('employee.phone')),
$this->rowActions([
@ -56,6 +57,7 @@ class EmployeeController extends AdminController
->source(admin_url('api/stores?_all=1'))
->labelField('title')
->valueField('id')
->searchable()
->required(),
amisMake()->SelectControl()->name('employee_id')->label(__('store.employees'))
->source(admin_url('api/employees?_all=1&store_id=0&enable=1'))

View File

@ -5,11 +5,12 @@ namespace App\Admin\Controllers\System;
use App\Admin\Controllers\AdminController;
use App\Admin\Services\WorkFlowService;
use App\Enums\CheckType;
use App\Models\Employee;
use App\Models\{Employee, WorkflowLog};
use App\Models\Keyword;
use Slowlyo\OwlAdmin\Renderers\Form;
use Slowlyo\OwlAdmin\Renderers\Page;
use Illuminate\Http\Request;
use Slowlyo\OwlAdmin\Admin;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Relations\Relation;
@ -94,8 +95,8 @@ class WorkflowController extends AdminController
$type = $request->input('subject_type');
$id = $request->input('subject_id');
$subject = Relation::getMorphedModel($type);
$user = $request->input('user');
$model = (new $subject)->findOrFail($id);
$user = $request->input('user');
if ($request->filled('user')) {
$user = Employee::findOrFail($request->input('user'));
} else {
@ -119,7 +120,8 @@ class WorkflowController extends AdminController
{
$type = $request->input('subject_type');
$id = $request->input('subject_id');
$model = (new $type)->findOrFail($id);
$subject = Relation::getMorphedModel($type);
$model = (new $subject)->findOrFail($id);
try {
DB::beginTransaction();
@ -134,6 +136,59 @@ class WorkflowController extends AdminController
}
}
public function success(Request $request)
{
$user = Admin::user();
$employee = Employee::where('admin_user_id', $user->id)->first();
if (!$employee) {
return $this->response()->fail('当前登录账户未关联员工');
}
$log = WorkflowLog::findOrFail($request->input('id'));
try {
DB::beginTransaction();
if (!$this->service->check($employee, $log, true)) {
return $this->response()->fail($this->service->getError());
}
DB::commit();
return $this->response()->success();
} catch (\Exception $e) {
DB::rollBack();
return $this->response()->fail($e->getMessage());
}
}
public function fail(Request $request)
{
if (!$request->input('remarks')) {
return $this->response()->fail('请填写未通过原因');
}
$user = Admin::user();
$employee = Employee::where('admin_user_id', $user->id)->first();
if (!$employee) {
return $this->response()->fail('当前登录账户未关联员工');
}
$log = WorkflowLog::findOrFail($request->input('id'));
try {
DB::beginTransaction();
if (!$this->service->check($employee, $log, false, ['remarks' => $request->input('remarks')])) {
return $this->response()->fail($this->service->getError());
}
DB::commit();
return $this->response()->success();
} catch (\Exception $e) {
DB::rollBack();
return $this->response()->fail($e->getMessage());
}
}
public function logs(Request $request)
{
$type = $request->input('subject_type');
$id = $request->input('subject_id');
$list = WorkflowLog::with(['checkUser'])->where('subject_type', $type)->where('subject_id', $id)->sort()->get();
return $this->response()->success($list);
}
public function getJobOptions()
{
if (! $this->jobOptions) {

View File

@ -72,16 +72,7 @@ class EmployeeService extends BaseService
public function resloveData($data, $model = null)
{
$adminUserService = AdminUserService::make();
if ($model) {
// 修改管理员信息
if (Arr::hasAny($data, ['username', 'password', 'confirm_password'])) {
if (! $adminUserService->update($model->admin_user_id, Arr::only($data, ['username', 'password', 'confirm_password']))) {
$this->setError($adminUserService->getError());
return false;
}
}
} else {
if (!$model) {
// 添加管理员信息
if (! $adminUserService->store(Arr::only($data, ['username', 'password', 'confirm_password']))) {
$this->setError($adminUserService->getError());

View File

@ -9,7 +9,7 @@ use Illuminate\Validation\Rule;
class StoreEmployeeService extends BaseService
{
protected array $withRelationships = ['store'];
protected array $withRelationships = ['store', 'jobs'];
protected string $modelName = Employee::class;

View File

@ -34,10 +34,6 @@ class WorkFlowService extends BaseService
if ($subject->check_status === CheckStatus::Processing->value) {
return $this->setError('正在审核中');
}
$result = $subject->checkApplyPre();
if ($result !== true) {
return $this->setError($result);
}
$workflow = Workflow::where('key', $subject->getCheckKey())->first();
// 没有配置审核流程, 直接通过
@ -47,7 +43,7 @@ class WorkFlowService extends BaseService
}
$jobs = Keyword::where('parent_key', 'job')->get();
$config = collect($workflow->config)->sortBy('sort');
$batchId = WorkflowLog::max('id') + 1;
$batchId = WorkflowLog::where('subject_type', $subject->getMorphClass())->where('subject_id', $subject->id)->max('batch_id') + 1;
$subjectData = $subject->toArray();
foreach($config as $item) {
$checkValue = '';
@ -84,11 +80,16 @@ class WorkFlowService extends BaseService
]);
}
$subject->checkApply();
// 开启第一个审核流程
$this->next($subject);
return true;
}
/**
* 取消审核
* 1. 删除未审核的流程
* 2. 更新申请记录的状态
*
*/
public function cancel(Checkable $subject)
{
@ -97,6 +98,85 @@ class WorkFlowService extends BaseService
return true;
}
/**
* 审核单个流程
*
* @param Employee $user 审核人
* @param WorkflowLog $log 审核流水记录
* @param boolean $status 通过/不通过
* @param array $options {remarks: 不通过原因, time: 审核时间(默认当前时间)}
* @return boolean
*/
public function check(Employee $user, WorkflowLog $log, $status, $options = [])
{
if ($log->check_status != CheckStatus::Processing) {
return $this->setError('不可操作, 等待前面的审核完成');
}
if (!$this->authCheck($user, $log)) {
return $this->setError('没有权限');
}
$attributes = ['check_status' => $status ? CheckStatus::Success : CheckStatus::Fail];
$attributes['checked_at'] = data_get($options, 'time', now());
$attributes['remarks'] = data_get($options, 'remarks');
$attributes['check_user_id'] = $user->id;
$log->update($attributes);
$subject = $log->subject;
// 审核未通过, 删除剩下的流程
if (!$status) {
WorkflowLog::where('subject_type', $log->subject_type)->where('subject_id', $log->subject_id)->where('check_status', CheckStatus::None)->delete();
$subject->checkFail($options);
return true;
}
return $this->next($subject);
}
/**
* 开启下一个审核流程
*
* @param Checkable $subject
* @return boolean
*/
public function next(Checkable $subject)
{
$log = $subject->workflows()->where('check_status', CheckStatus::None)->orderBy('sort')->first();
if ($log) {
$log->update(['check_status' => CheckStatus::Processing]);
// 申请人自动审核通过
if ($this->authCheck($subject->employee, $log)) {
return $this->check($subject->employee, $log, true);
}
} else {
// 没有审核流程了
$subject->checkSuccess();
}
return true;
}
/**
* 员工是否有权限审核
*/
public function authCheck(Employee $user, WorkflowLog $log)
{
if ($user->adminUser?->isAdministrator()) {
return true;
}
if ($log->check_type == CheckType::User && $log->check_value == $user->id) {
return true;
} else if ($log->check_type == CheckType::Job) {
$jobs = $user->jobs;
foreach($jobs as $job) {
if ($log->check_value == $user->store_id . '-' . $job->key) {
return true;
}
}
}
return false;
}
public function resloveData($data, $model = null)
{
if (isset($data['config'])) {

View File

@ -157,5 +157,8 @@ Route::group([
$router->post('workflow/apply', [WorkflowController::class, 'apply']);
$router->post('workflow/cancel', [WorkflowController::class, 'cancel']);
$router->post('workflow/success', [WorkflowController::class, 'success']);
$router->post('workflow/fail', [WorkflowController::class, 'fail']);
$router->get('workflow/logs', [WorkflowController::class, 'logs']);
});
});

View File

@ -15,8 +15,4 @@ interface Checkable
public function getCheckKey();
public function workflows();
public function checkApplyPre();
public function checkSuccessPre();
}

View File

@ -8,7 +8,7 @@ namespace App\Enums;
enum CheckStatus: int
{
/**
* 审核
* 审核
*/
case None = 1;
/**
@ -31,7 +31,7 @@ enum CheckStatus: int
public static function options(): array
{
return [
self::None->value => '审核',
self::None->value => '审核',
self::Processing->value => '审核中',
self::Success->value => '审核通过',
self::Fail->value => '审核不通过',

View File

@ -18,7 +18,7 @@ class EmployeeSignRepair extends Model implements Checkable
protected $table = 'employee_sign_repairs';
protected $fillable = ['date', 'store_id', 'employee_id', 'reason', 'repair_type', 'check_status', 'checked_at'];
protected $fillable = ['date', 'store_id', 'employee_id', 'reason', 'repair_type', 'check_status', 'checked_at', 'check_remarks'];
protected $casts = [
'date' => 'date:Y-m-d',

View File

@ -11,20 +11,14 @@ use Illuminate\Database\Eloquent\Model;
*/
class WorkflowLog extends Model
{
protected $fillable = ['batch_id', 'check_type', 'check_value', 'check_name', 'user_id', 'subject_type', 'subject_id', 'subject_data', 'is_enable', 'check_user_id', 'checked_at', 'remarks', 'check_status', 'sort'];
protected $fillable = ['batch_id', 'check_type', 'check_value', 'check_name', 'subject_type', 'subject_id', 'subject_data', 'check_user_id', 'checked_at', 'remarks', 'check_status', 'sort'];
protected $casts = [
'check_type' => CheckType::class,
'check_status' => CheckStatus::class,
'is_enable' => 'boolean',
'subject_data' => 'json',
];
public function user()
{
return $this->belongsTo(Employee::class, 'user_id');
}
public function checkUser()
{
return $this->belongsTo(Employee::class, 'check_user_id');
@ -36,4 +30,9 @@ class WorkflowLog extends Model
// $this->morphMany(WorkflowLog::class, 'subject');
return $this->morphTo();
}
public function scopeSort($q)
{
return $q->orderBy('batch_id')->orderBy('sort');
}
}

View File

@ -3,7 +3,7 @@
namespace App\Traits;
use App\Enums\CheckStatus;
use App\Models\WorkflowLog;
use App\Models\{WorkflowLog, Employee};
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
@ -13,28 +13,42 @@ trait HasCheckable
{
$this->update([
'check_status' => CheckStatus::Processing,
'checked_at' => null,
'check_remarks' => '',
]);
}
public function checkSuccess()
/**
* 审核通过
*
* @param array $options {remarks: 不通过原因, time: 审核时间(默认当前时间)}
*/
public function checkSuccess(array $options = [])
{
$this->update([
'check_status' => CheckStatus::Success,
'checked_at' => now(),
]);
$attributes = ['check_status' => CheckStatus::Success];
$attributes['checked_at'] = data_get($options, 'time', now());
$attributes['check_remarks'] = data_get($options, 'remarks');
$this->update($attributes);
}
public function checkFail()
/**
* 审核未通过
*
* @param array $options {remarks: 不通过原因, time: 审核时间(默认当前时间)}
*/
public function checkFail(array $options = [])
{
$this->update([
'check_status' => CheckStatus::Fail,
]);
$attributes = ['check_status' => CheckStatus::Fail];
$attributes['checked_at'] = data_get($options, 'time', now());
$attributes['check_remarks'] = data_get($options, 'remarks');
$this->update($attributes);
}
public function checkCancel()
public function checkCancel(array $options = [])
{
$this->update([
'check_status' => CheckStatus::Cancel,
'checked_at' => data_get($options, 'time', now()),
]);
}
@ -43,19 +57,20 @@ trait HasCheckable
return Str::snake(class_basename(__CLASS__));
}
/**
* 关联审核流水
*/
public function workflows()
{
return $this->morphMany(WorkflowLog::class, 'subject');
}
/**
* 是否允许修改
*
* @return bool
* 关联申请人
*/
public function canEdit(): bool
public function employee()
{
return !($this->check_status === CheckStatus::Processing);
return $this->belongsTo(Employee::class, 'employee_id');
}
/**
@ -67,48 +82,4 @@ trait HasCheckable
{
return $q->where('check_status', CheckStatus::Success);
}
/**
* 审核成功前, 会调用该方法
* 返回错误信息 阻止审核通过
*/
public function checkSuccessPre()
{
return true;
}
/**
* 审核申请前, 会调用此方法
* 返回错误信息 阻止申请
*/
public function checkApplyPre()
{
return true;
}
public static function boot()
{
parent::boot();
static::updating(function ($model) {
// 修改审核通过的内容, 需要重新审核
if (isset($model->checkListen) && count($model->checkListen) > 0 && $model->isDirty($model->checkListen) && $model->check_status === CheckStatus::Success) {
$model->check_status = CheckStatus::None;
$model->checked_at = null;
}
});
static::deleting(function ($model) {
// 删除审核记录
$model->workflows()->delete();
});
}
/**
* 是否允许删除,已取消或已拒绝的
* @return bool
*/
public function canDel(): bool
{
return $this->check_status === CheckStatus::Cancel || $this->check_status === CheckStatus::Fail || $this->check_status === CheckStatus::None;
}
}

View File

@ -28,10 +28,8 @@ return new class extends Migration
$table->string('check_type')->comment('审核类型{job, user}');
$table->string('check_value')->comment('审核类型值');
$table->string('check_name')->comment('审核名称(展示用)');
$table->unsignedBigInteger('user_id')->comment('申请人(employees.id)');
$table->nullableMorphs('subject');
$table->json('subject_data')->nullable('审核内容');
$table->unsignedTinyInteger('is_enable')->default(0)->comment('操作状态(0: 不可操作, 1: 可操作)');
$table->unsignedBigInteger('check_user_id')->nullable()->comment('实际审核人(admin_users.id)');
$table->timestamp('checked_at')->nullable()->comment('审核时间');
$table->string('remarks')->nullable()->comment('审核备注');

View File

@ -59,6 +59,7 @@ return new class extends Migration
$table->unsignedInteger('check_status')->default(CheckStatus::None->value)->comment('审核状态');
$table->timestamp('checked_at')->nullable()->comment('审核通过时间');
$table->string('check_remarks')->nullable()->comment('审核未通过原因');
$table->timestamps();

View File

@ -18,7 +18,7 @@ class EmployeeSeeder extends Seeder
{
DB::table('employee_jobs')->truncate();
Employee::where('admin_user_id', '!=', 1)->delete();
(new EmployeeFactory)->count(100)->create(['admin_user_id' => 1]);
(new EmployeeFactory)->count(100)->create();
Store::truncate();
Store::factory()->count(10)->create();

View File

@ -12,4 +12,5 @@ return [
'repair_type' => '上班/下班',
'check_status' => '审核状态',
'checked_at' => '审核通过时间',
'check_remarks' => '审核未通过原因',
];

View File

@ -0,0 +1,17 @@
<?php
return [
'batch_id' => '批次号',
'check_type' => '审核类型',
'check_value' => '审核人',
'check_name' => '审核人',
'user_id' => '申请人',
'subject_type' => '申请记录',
'subject_id' => '申请记录',
'subject_data' => '基本信息',
'check_user_id' => '实际审核人',
'checked_at' => '审核时间',
'remarks' => '未通过原因',
'check_status' => '审核状态',
'sort' => '顺序号',
];