main
panliang 2024-04-12 17:12:37 +08:00
parent e6058fbdc7
commit 3c24c55b43
18 changed files with 306 additions and 79 deletions

View File

@ -34,6 +34,7 @@ class EmployeeController extends AdminController
->columns([
amisMake()->TableColumn()->name('id')->label(__('employee.id')),
amisMake()->TableColumn()->name('name')->label(__('employee.name')),
amisMake()->TableColumn()->name('avatar')->label(__('employee.avatar'))->type('avatar')->src('${avatar}'),
amisMake()->TableColumn()->name('jobs')->label(__('employee.jobs'))->type('each')->items(amisMake()->Tag()->label('${name}')),
amisMake()->TableColumn()->name('phone')->label(__('employee.phone')),
amisMake()->TableColumn()->name('employee_status_text')->label(__('employee.employee_status'))->set('type', 'tag')->set('color', '${employee_status_color}'),
@ -68,10 +69,10 @@ class EmployeeController extends AdminController
public function form($edit): Form
{
return $this->baseForm()->title('')->body([
amisMake()->ImageControl()->name('avatar')->label(__('employee.avatar'))->receiver($this->uploadImagePath().'?full-url=1'),
amisMake()->TextControl()->name('name')->label(__('employee.name'))->required(),
amisMake()->TextControl()->name('phone')->label(__('employee.phone'))->required(),
// amisMake()->SelectControl()->name('employee_status')->label(__('employee.employee_status'))->options(EmployeeStatus::options())->required(),
amisMake()->TagControl()->name('jobs')->label(__('employee.jobs'))
->source(admin_url('api/keywords/tree-list').'?parent_key='.Employee::JOB_KEY)
->labelField('name')
@ -98,6 +99,7 @@ class EmployeeController extends AdminController
public function detail(): Form
{
return $this->baseDetail()->title('')->body(amisMake()->Property()->items([
['label' => __('employee.avatar'), 'content' => amisMake()->Avatar()->src('${avatar}')],
['label' => __('employee.name'), 'content' => '${name}'],
['label' => __('employee.phone'), 'content' => '${phone}'],
['label' => __('employee.jobs'), 'content' => amisMake()->Each()->name('jobs')->items(amisMake()->Tag()->label('${name}'))],
@ -116,19 +118,10 @@ class EmployeeController extends AdminController
public function leave($id, Request $request)
{
$user = Employee::findOrFail($id);
if ($user->employee_status == EmployeeStatus::Online) {
$user->update([
'leave_at' => $request->input('leave_at', now()),
'employee_status' => EmployeeStatus::Offline,
]);
} else {
$user->update([
'leave_at' => null,
'employee_status' => EmployeeStatus::Online,
]);
if ($this->service->leave($user, $request->only(['leave_at']))) {
return $this->response()->success(null, '操作成功');
}
return $this->response()->success(null, '操作成功');
return $this->response()->fail($this->service->getError());
}
public function shareList()

View File

@ -25,7 +25,7 @@ class HolidayController extends AdminController
$crud = $this->baseCRUD()
->tableLayout('fixed')
->headerToolbar([
$this->createTypeButton('drawer', 'xl')->visible(Admin::user()->can('admin.hr.holiday.create')),
// $this->createTypeButton('drawer', 'xl')->visible(Admin::user()->can('admin.hr.holiday.create')),
...$this->baseHeaderToolBar(),
])
->bulkActions([])
@ -65,9 +65,9 @@ class HolidayController extends AdminController
amisMake()->TableColumn()->name('created_at')->label(__('holiday_apply.created_at')),
$this->rowActions([
$this->rowShowButton()->visible(Admin::user()->can('admin.hr.holiday.view')),
$this->rowEditTypeButton('drawer', 'xl')
->visible(Admin::user()->can('admin.hr.holiday.update'))
->visibleOn('${OR(workflow.check_status == '.CheckStatus::None->value.', workflow.check_status == '.CheckStatus::Cancel->value.', workflow.check_status == '.CheckStatus::Fail->value.')}'),
// $this->rowEditTypeButton('drawer', 'xl')
// ->visible(Admin::user()->can('admin.hr.holiday.update'))
// ->visibleOn('${OR(workflow.check_status == '.CheckStatus::None->value.', workflow.check_status == '.CheckStatus::Cancel->value.', workflow.check_status == '.CheckStatus::Fail->value.')}'),
$this->rowDeleteButton()
->visible(Admin::user()->can('admin.hr.holiday.delete'))
->visibleOn('${OR(workflow.check_status == '.CheckStatus::None->value.', workflow.check_status == '.CheckStatus::Cancel->value.', workflow.check_status == '.CheckStatus::Fail->value.')}'),

View File

@ -25,7 +25,7 @@ class OfficalBusinessController extends AdminController
$crud = $this->baseCRUD()
->tableLayout('fixed')
->headerToolbar([
$this->createTypeButton('drawer', 'xl')->visible(Admin::user()->can('admin.hr.business.create')),
// $this->createTypeButton('drawer', 'xl')->visible(Admin::user()->can('admin.hr.business.create')),
...$this->baseHeaderToolBar(),
])
->bulkActions([])
@ -59,9 +59,9 @@ class OfficalBusinessController extends AdminController
amisMake()->TableColumn()->name('created_at')->label(__('offical_business.created_at')),
$this->rowActions([
$this->rowShowButton()->visible(Admin::user()->can('admin.hr.business.view')),
$this->rowEditTypeButton('drawer', 'xl')
->visible(Admin::user()->can('admin.hr.business.update'))
->visibleOn('${OR(workflow.check_status == '.CheckStatus::None->value.', workflow.check_status == '.CheckStatus::Cancel->value.', workflow.check_status == '.CheckStatus::Fail->value.')}'),
// $this->rowEditTypeButton('drawer', 'xl')
// ->visible(Admin::user()->can('admin.hr.business.update'))
// ->visibleOn('${OR(workflow.check_status == '.CheckStatus::None->value.', workflow.check_status == '.CheckStatus::Cancel->value.', workflow.check_status == '.CheckStatus::Fail->value.')}'),
$this->rowDeleteButton()
->visible(Admin::user()->can('admin.hr.business.delete'))
->visibleOn('${OR(workflow.check_status == '.CheckStatus::None->value.', workflow.check_status == '.CheckStatus::Cancel->value.', workflow.check_status == '.CheckStatus::Fail->value.')}'),

View File

@ -25,7 +25,7 @@ class OvertimeController extends AdminController
$crud = $this->baseCRUD()
->tableLayout('fixed')
->headerToolbar([
$this->createTypeButton('drawer', 'xl')->visible(Admin::user()->can('admin.hr.overtime.create')),
// $this->createTypeButton('drawer', 'xl')->visible(Admin::user()->can('admin.hr.overtime.create')),
...$this->baseHeaderToolBar(),
])
->bulkActions([])
@ -60,9 +60,9 @@ class OvertimeController extends AdminController
amisMake()->TableColumn()->name('created_at')->label(__('overtime_apply.created_at')),
$this->rowActions([
$this->rowShowButton()->visible(Admin::user()->can('admin.hr.overtime.view')),
$this->rowEditTypeButton('drawer', 'xl')
->visible(Admin::user()->can('admin.hr.overtime.update'))
->visibleOn('${OR(workflow.check_status == '.CheckStatus::None->value.', workflow.check_status == '.CheckStatus::Cancel->value.', workflow.check_status == '.CheckStatus::Fail->value.')}'),
// $this->rowEditTypeButton('drawer', 'xl')
// ->visible(Admin::user()->can('admin.hr.overtime.update'))
// ->visibleOn('${OR(workflow.check_status == '.CheckStatus::None->value.', workflow.check_status == '.CheckStatus::Cancel->value.', workflow.check_status == '.CheckStatus::Fail->value.')}'),
$this->rowDeleteButton()
->visible(Admin::user()->can('admin.hr.overtime.delete'))
->visibleOn('${OR(workflow.check_status == '.CheckStatus::None->value.', workflow.check_status == '.CheckStatus::Cancel->value.', workflow.check_status == '.CheckStatus::Fail->value.')}'),

View File

@ -26,7 +26,7 @@ class SignRepairController extends AdminController
$crud = $this->baseCRUD()
->tableLayout('fixed')
->headerToolbar([
$this->createTypeButton('drawer', 'xl')->visible(Admin::user()->can('admin.hr.repairs.create')),
// $this->createTypeButton('drawer', 'xl')->visible(Admin::user()->can('admin.hr.repairs.create')),
...$this->baseHeaderToolBar(),
])
->bulkActions([])
@ -65,9 +65,9 @@ class SignRepairController extends AdminController
->map(CheckStatus::options()),
$this->rowActions([
$this->rowShowButton()->visible(Admin::user()->can('admin.hr.repairs.view')),
$this->rowEditTypeButton('drawer', 'xl')
->visible(Admin::user()->can('admin.hr.repairs.update'))
->visibleOn('${OR(workflow.check_status == '.CheckStatus::None->value.', workflow.check_status == '.CheckStatus::Cancel->value.', workflow.check_status == '.CheckStatus::Fail->value.')}'),
// $this->rowEditTypeButton('drawer', 'xl')
// ->visible(Admin::user()->can('admin.hr.repairs.update'))
// ->visibleOn('${OR(workflow.check_status == '.CheckStatus::None->value.', workflow.check_status == '.CheckStatus::Cancel->value.', workflow.check_status == '.CheckStatus::Fail->value.')}'),
$this->rowDeleteButton()
->visible(Admin::user()->can('admin.hr.repairs.delete'))
->visibleOn('${OR(workflow.check_status == '.CheckStatus::None->value.', workflow.check_status == '.CheckStatus::Cancel->value.', workflow.check_status == '.CheckStatus::Fail->value.')}'),

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('avatar')->label(__('employee.avatar'))->type('avatar')->src('${avatar}'),
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')),

View File

@ -6,6 +6,17 @@ use EloquentFilter\ModelFilter;
class EmployeeFilter extends ModelFilter
{
public $relations = [
'store' => [
'region' => 'region',
'province_code' => 'province_code',
'province_code' => 'province_code',
'city_code' => 'city_code',
]
];
protected $drop_id = false;
public function search($key)
{
$condition = '%'.$key.'%';
@ -22,9 +33,9 @@ class EmployeeFilter extends ModelFilter
$this->whereLike('phone', $key);
}
public function store($key)
public function storeId($key)
{
$this->where('store_id', $key);
$this->whereIn('store_id', is_array($key) ? $key : explode(',', $key));
}
public function storeIdGt($key)
@ -32,7 +43,7 @@ class EmployeeFilter extends ModelFilter
$this->where('store_id', '>', 0);
}
public function masterStore($key)
public function masterStoreId($key)
{
$this->where('master_store_id', $key);
}

View File

@ -46,4 +46,14 @@ class StoreFilter extends ModelFilter
$this->where('region->cityCode', $cityCode);
}
}
public function provinceCode($provinceCode)
{
$this->where('region->provinceCode', $provinceCode);
}
public function cityCode($cityCode)
{
$this->where('region->cityCode', $cityCode);
}
}

View File

@ -3,14 +3,15 @@
namespace App\Admin\Services;
use App\Admin\Filters\EmployeeFilter;
use App\Models\Employee;
use App\Models\{Employee, EmployeeSignLog, HolidayApply, OvertimeApply, OfficalBusiness};
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Slowlyo\OwlAdmin\Models\AdminUser;
use Slowlyo\OwlAdmin\Services\AdminUserService;
use App\Admin\Services\System\AdminUserService;
use App\Enums\EmployeeStatus;
class EmployeeService extends BaseService
{
@ -23,13 +24,21 @@ class EmployeeService extends BaseService
public function store($data): bool
{
$data = $this->resloveData($data);
$validate = $this->validate($data);
if ($validate !== true) {
$this->setError($validate);
return false;
}
// 添加管理员信息
$adminUserService = AdminUserService::make();
if (! $adminUserService->store(Arr::only($data, ['username', 'avatar', 'password', 'confirm_password', 'name']))) {
$this->setError($adminUserService->getError());
return false;
}
$adminUser = AdminUser::where('username', $data['username'])->first();
$data['admin_user_id'] = $adminUser->id;
$model = $this->modelName::create($data);
@ -51,6 +60,12 @@ class EmployeeService extends BaseService
return false;
}
// 修改管理员
$adminUserService = AdminUserService::make();
if (!$adminUserService->update($model->admin_user_id, Arr::only($data, ['password', 'confirm_password', 'name', 'avatar']))) {
return $this->setError($adminUserService->getError());
}
if (isset($data['jobs']) && is_string($data['jobs'])) {
$this->resloveJob($model, explode(',', $data['jobs']));
}
@ -58,32 +73,29 @@ class EmployeeService extends BaseService
return $model->update($data);
}
public function getEditData($id): Model|\Illuminate\Database\Eloquent\Collection|Builder|array|null
/**
* 员工离职/还原
*
* @param Employee $user
* @param array $options {leave_at: 离职时间}
*
* @return boolean
*/
public function leave(Employee $user, $options = [])
{
$model = $this->getModel();
$hidden = collect([$model->getCreatedAtColumn(), $model->getUpdatedAtColumn()])
->filter(fn ($item) => $item !== null)
->toArray();
return $this->query()->with(['adminUser', 'jobs'])->find($id)->makeHidden($hidden);
}
public function resloveData($data, $model = null)
{
$adminUserService = AdminUserService::make();
if (! $model) {
// 添加管理员信息
if (! $adminUserService->store(Arr::only($data, ['username', 'password', 'confirm_password', 'name']))) {
$this->setError($adminUserService->getError());
return false;
}
$adminUser = AdminUser::where('username', $data['username'])->first();
$data['admin_user_id'] = $adminUser->id;
if ($user->employee_status == EmployeeStatus::Online) {
$user->update([
'leave_at' => data_get($options, 'leave_at', now()),
'employee_status' => EmployeeStatus::Offline,
]);
} else {
$user->update([
'leave_at' => null,
'employee_status' => EmployeeStatus::Online,
]);
}
return $data;
return true;
}
/**
@ -99,6 +111,26 @@ class EmployeeService extends BaseService
public function preDelete(array $ids): void
{
if (in_array(1, $ids)) {
admin_abort('超级管理员不能删除');
}
// 无打卡记录
if (EmployeeSignLog::whereIn('employee_id', $ids)->exists()) {
admin_abort('请先删除员工打卡记录');
}
// 无请假申请
if (HolidayApply::whereIn('employee_id', $ids)->exists()) {
admin_abort('请先删除员工打卡记录');
}
// 无出差申请
if (OfficalBusiness::whereIn('employee_id', $ids)->exists()) {
admin_abort('请先删除员工打卡记录');
}
// 加班申请
if (OvertimeApply::whereIn('employee_id', $ids)->exists()) {
admin_abort('请先删除员工打卡记录');
}
// 店员关联
if (DB::table('store_employees')->whereIn('employee_id', $ids)->exists()) {
admin_abort('员工已关联门店, 请先从门店中删除');
@ -118,6 +150,8 @@ class EmployeeService extends BaseService
$createRules = [
'name' => ['required'],
'phone' => ['required'],
'username' => ['required'],
'password' => ['required'],
];
$updateRules = [];
$validator = Validator::make($data, $model ? $updateRules : $createRules, [

View File

@ -9,6 +9,9 @@ use Illuminate\Http\{Request, Response};
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
use App\Enums\UserRole;
use App\Http\Resources\KeywordResource;
use App\Admin\Services\EmployeeService;
use Illuminate\Support\Facades\DB;
/**
* 个人中心
@ -20,16 +23,14 @@ class UserController extends Controller
{
$user = $this->guard()->user();
$admin = $user->adminUser;
return [
'id' => $user->id,
'avatar' => $admin->avatar,
'name' => $user->name,
'name' => $user->name,
'phone' => $user->phone,
'jobs' => $user->jobs->pluck('name'),
'avatar' => $user->avatar,
'jobs' => KeywordResource::collection($user->jobs),
'unread_notifications' => 0,
// 身份: user-普通员工, store-店长, admin-管理员
'role' => $user->userRole(),
];
@ -37,20 +38,19 @@ class UserController extends Controller
// 修改账户信息
public function update(Request $request)
{
$request->validate([
'password' => ['nullable', 'confirmed'],
]);
$user = $this->guard()->user();
$admin = $user->adminUser;
$data = $request->only(['avatar', 'name']);
if ($request->filled('password')) {
$data['password'] = Hash::make($request->input('password'));
try {
DB::beginTransaction();
$service = EmployeeService::make();
$data = $request->only(['name', 'avatar', 'password', 'confirm_password', 'phone']);
if (!$service->update($user->id, $data)) {
throw new RuntimeException($service->getError());
}
DB::commit();
} catch (\Exception $e) {
DB::rollback();
throw new RuntimeException($e->getMessage());
}
$admin->update($data);
$user->update($data);
return response('', Response::HTTP_OK);
}

View File

@ -6,6 +6,11 @@ use App\Http\Controllers\Api\Controller;
use App\Models\{Employee, Store, AdminUser};
use Illuminate\Http\{Request, Response};
use App\Enums\UserRole;
use App\Http\Resources\EmployeeResource;
use App\Admin\Services\EmployeeService;
use App\Exceptions\RuntimeException;
use Illuminate\Support\Facades\DB;
use App\Enums\EmployeeStatus;
/**
* 员工管理
@ -16,12 +21,91 @@ class EmployeeController extends Controller
{
$user = $this->guard()->user();
$role = $user->userRole();
$query = Employee::filter($request->all());
$filter = $request->all();
$query = Employee::with(['jobs', 'store', 'adminUser'])->filter($filter)->enable();
if ($role == UserRole::User || $role == UserRole::Store) {
$query->whereIn('store_id', [$user->store_id]);
}
$list = $query->paginate($request->input('per_page'));
return $query->get();
return EmployeeResource::collection($list);
}
public function show($id)
{
$info = Employee::with(['jobs', 'store'])->findOrFail($id);
return EmployeeResource::make($info);
}
public function store(Request $request)
{
try {
DB::beginTransaction();
$service = EmployeeService::make();
$data = $request->all();
if (!$service->store($data)) {
throw new RuntimeException($service->getError());
}
DB::commit();
} catch (\Exception $e) {
DB::rollback();
throw new RuntimeException($e->getMessage());
}
return response('', Response::HTTP_CREATED);
}
public function update($id, Request $request)
{
try {
DB::beginTransaction();
$service = EmployeeService::make();
$data = $request->only(['name', 'avatar', 'password', 'confirm_password', 'phone', 'store_id']);
if (!$service->update($id, $data)) {
throw new RuntimeException($service->getError());
}
DB::commit();
} catch (\Exception $e) {
DB::rollback();
throw new RuntimeException($e->getMessage());
}
return response('', Response::HTTP_OK);
}
public function destroy($id)
{
try {
DB::beginTransaction();
$service = EmployeeService::make();
if (!$service->delete($id)) {
throw new RuntimeException($service->getError());
}
DB::commit();
} catch (\Exception $e) {
DB::rollback();
throw new RuntimeException($e->getMessage());
}
return response('', Response::HTTP_OK);
}
public function leave($id)
{
$info = Employee::findOrFail($id);
try {
DB::beginTransaction();
if ($info->employee_status != EmployeeStatus::Online) {
throw new RuntimeException('未入职');
}
$service = EmployeeService::make();
if (!$service->leave($info)) {
throw new RuntimeException($service->getError());
}
DB::commit();
} catch (\Exception $e) {
DB::rollback();
throw new RuntimeException($e->getMessage());
}
return response('', Response::HTTP_OK);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class EmployeeResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'store_id' => $this->store_id,
'name' => $this->name,
'avatar' => $this->avatar,
'phone' => $this->phone,
'prize_images' => $this->prize_images,
'skill_images' => $this->skill_images,
'employee_status' => $this->employee_status,
'admin_user_id' => $this->admin_user_id,
'leave_at' => $this->leave_at?->timestamp,
'join_at' => $this->join_at?->timestamp,
'jobs' => KeywordResource::collection($this->whenLoaded('jobs')),
'store' => StoreResource::make($this->whenLoaded('store')),
];
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class KeywordResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
];
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class StoreResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'region' => $this->region,
'lon' => $this->lon,
'lat' => $this->lat,
'address' => $this->address,
];
}
}

View File

@ -13,6 +13,7 @@ use Illuminate\Database\Eloquent\Model;
use Laravel\Sanctum\HasApiTokens;
use Slowlyo\OwlAdmin\Models\AdminUser;
use App\Enums\UserRole;
use Illuminate\Support\Str;
/**
* 员工
@ -23,7 +24,7 @@ class Employee extends Model implements AuthenticatableContract
const JOB_KEY = 'job';
protected $fillable = ['store_id', 'master_store_id', 'name', 'phone', 'prize_images', 'skill_images', 'employee_status', 'admin_user_id', 'leave_at', 'join_at', 'remarks'];
protected $fillable = ['store_id', 'name', 'avatar', 'phone', 'prize_images', 'skill_images', 'employee_status', 'admin_user_id', 'leave_at', 'join_at', 'remarks'];
protected $casts = [
'employee_status' => EmployeeStatus::class,
@ -44,6 +45,13 @@ class Employee extends Model implements AuthenticatableContract
return EmployeeFilter::class;
}
public function avatar(): Attribute
{
return Attribute::make(
get: fn($value) => $value ? $value : url(config('admin.default_avatar')),
);
}
// 拥有的职位
public function jobs()
{

View File

@ -16,6 +16,7 @@ return new class extends Migration
$table->foreignId('store_id')->default(0)->comment('所属门店, stores.id');
// $table->foreignId('master_store_id')->default(0)->comment('管理的门店, stores.id');
$table->string('name')->comment('姓名');
$table->string('avatar')->nullable()->comment('头像');
$table->string('phone')->comment('电话');
$table->json('prize_images')->nullable()->comment('荣誉证书');
$table->json('skill_images')->nullable()->comment('专业证书');

View File

@ -6,6 +6,7 @@ return [
'updated_at' => '更新时间',
'name' => '姓名',
'avatar' => '头像',
'phone' => '电话',
'prize_images' => '荣誉证书',
'skill_images' => '专业证书',

View File

@ -30,5 +30,8 @@ Route::group([
Route::post('feedback', [FeedbackController::class, 'store']);
// 员工管理
Route::get('hr/employee', [\App\Http\Controllers\Api\Hr\EmployeeController::class, 'index'])->middleware(['user_role:admin,store']);
Route::group(['middleware' => ['user_role:admin,store']], function () {
Route::post('hr/employee/{id}/leave', [\App\Http\Controllers\Api\Hr\EmployeeController::class, 'leave']);
Route::resource('hr/employee', \App\Http\Controllers\Api\Hr\EmployeeController::class);
});
});