diff --git a/app/Admin/Controllers/Hr/EmployeeController.php b/app/Admin/Controllers/Hr/EmployeeController.php index 927b584..049ef54 100644 --- a/app/Admin/Controllers/Hr/EmployeeController.php +++ b/app/Admin/Controllers/Hr/EmployeeController.php @@ -73,11 +73,11 @@ class EmployeeController extends AdminController amisMake()->TextControl()->name('name')->label(__('employee.name'))->required(), amisMake()->TextControl()->name('phone')->label(__('employee.phone'))->required(), - amisMake()->TagControl()->name('jobs')->label(__('employee.jobs')) - ->source(admin_url('api/keywords/tree-list').'?parent_key='.Employee::JOB_KEY) - ->labelField('name') - ->valueField('key') - ->joinValues(), + // amisMake()->TagControl()->name('jobs')->label(__('employee.jobs')) + // ->source(admin_url('api/keywords/tree-list').'?parent_key='.Employee::JOB_KEY) + // ->labelField('name') + // ->valueField('key') + // ->joinValues(), amisMake()->DateControl()->name('join_at')->label(__('employee.join_at'))->format('YYYY-MM-DD'), amisMake()->TextControl()->name('remarks')->label(__('employee.remarks')), amisMake()->ImageControl()->name('prize_images')->label(__('employee.prize_images')) @@ -101,14 +101,14 @@ class EmployeeController extends AdminController return $this->baseDetail()->title('')->body(amisMake()->Property()->items([ ['label' => __('employee.avatar'), 'content' => amisMake()->Avatar()->src('${avatar}')], ['label' => __('employee.name'), 'content' => '${name}'], + ['label' => __('admin.username'), 'content' => '${admin_user.username}'], ['label' => __('employee.phone'), 'content' => '${phone}'], ['label' => __('employee.jobs'), 'content' => amisMake()->Each()->name('jobs')->items(amisMake()->Tag()->label('${name}'))], ['label' => __('employee.employee_status'), 'content' => amisMake()->Tag()->label('${employee_status_text}')->color('${employee_status_color}')], ['label' => __('employee.join_at'), 'content' => '${join_at}'], ['label' => __('employee.leave_at'), 'content' => '${leave_at}'], - ['label' => __('employee.remarks'), 'content' => '${remarks}', 'span' => 3], - ['label' => __('admin.username'), 'content' => '${admin_user.username}', 'span' => 3], + ['label' => __('employee.remarks'), 'content' => '${remarks}'], ['label' => __('employee.prize_images'), 'content' => amisMake()->Images()->source('${prize_images}')->enlargeAble(), 'span' => 3], ['label' => __('employee.skill_images'), 'content' => amisMake()->Images()->source('${skill_images}')->enlargeAble(), 'span' => 3], ])); diff --git a/app/Admin/Controllers/Hr/SignController.php b/app/Admin/Controllers/Hr/SignController.php index fe6ea30..4907ac9 100644 --- a/app/Admin/Controllers/Hr/SignController.php +++ b/app/Admin/Controllers/Hr/SignController.php @@ -86,6 +86,7 @@ class SignController extends AdminController amisMake()->TableColumn()->name('sign_type')->label(__('employee_sign_log.sign_type'))->set('type', 'mapping')->map(SignType::options()), amisMake()->TableColumn()->name('remarks')->label(__('employee_sign_log.remarks')), amisMake()->TableColumn()->name('position.address')->label(__('employee_sign_log.position')), + amisMake()->TableColumn()->name('is_repair')->label(__('employee_sign_log.is_repair'))->set('type', 'status'), ]) ); diff --git a/app/Admin/Controllers/SettingController.php b/app/Admin/Controllers/SettingController.php index aae262f..e6293d7 100644 --- a/app/Admin/Controllers/SettingController.php +++ b/app/Admin/Controllers/SettingController.php @@ -39,6 +39,9 @@ class SettingController extends AdminController amis()->TextControl('oss_config.domain', '自有域名')->size('lg')->visibleOn('${upload_disk == "oss"}')->remark('填写即启用 示例: my-domain.com'), amis()->SwitchControl('oss_config.use_ssl', '开启SSL')->value(false)->visibleOn('${upload_disk == "oss"}'), ]), + Tab::make()->title('打卡设置')->body([ + amisMake()->NumberControl()->name('sign.distance')->label('允许打卡的距离(公里)') + ]), ]) ); } @@ -48,6 +51,7 @@ class SettingController extends AdminController $data = $request->only([ 'upload_disk', 'oss_config', + 'sign' ]); //上传设置-修改env文件内配置; diff --git a/app/Admin/Controllers/Store/StoreController.php b/app/Admin/Controllers/Store/StoreController.php index e5a011f..04079d5 100644 --- a/app/Admin/Controllers/Store/StoreController.php +++ b/app/Admin/Controllers/Store/StoreController.php @@ -103,7 +103,7 @@ class StoreController extends AdminController ->valueField('key') ->required(), amisMake()->InputCityControl()->name('region')->label(__('store.region'))->allowDistrict(false)->extractValue(false)->required(), - amisMake()->LocationControl()->name('location')->label(__('store.location'))->ak('Qa9sxstHlyBIDfb3SjrR3Uli39yRjB6X')->autoSelectCurrentLoc(), + amisMake()->LocationControl()->name('location')->label(__('store.location'))->ak('xDTLJ15QG6zt3f6VQcaNBfN8q3MsWBsE')->autoSelectCurrentLoc(), ]); } diff --git a/app/Admin/Filters/EmployeeSignLogFilter.php b/app/Admin/Filters/EmployeeSignLogFilter.php index ac3d4dd..e5d999f 100644 --- a/app/Admin/Filters/EmployeeSignLogFilter.php +++ b/app/Admin/Filters/EmployeeSignLogFilter.php @@ -21,7 +21,12 @@ class EmployeeSignLogFilter extends ModelFilter public function date($key) { - $date = Carbon::createFromFormat('Y-m-d', $key); + $date = Carbon::parse($key); $this->whereBetween('time', [$date->copy()->startOfDay(), $date->copy()->endOfDay()]); } + + public function signTime($key) + { + $this->where('sign_time', $key); + } } diff --git a/app/Admin/Services/EmployeePromotionService.php b/app/Admin/Services/EmployeePromotionService.php index a7de825..6020808 100644 --- a/app/Admin/Services/EmployeePromotionService.php +++ b/app/Admin/Services/EmployeePromotionService.php @@ -9,6 +9,7 @@ use App\Models\Employee; use App\Models\EmployeePromotion; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Validator; +use Illuminate\Support\Arr; class EmployeePromotionService extends BaseService { @@ -86,7 +87,6 @@ class EmployeePromotionService extends BaseService public function validate($data, $model = null) { - // todo 验证申请人的职位是否重复 $createRules = [ 'store_id' => ['required'], 'employee_id' => ['required'], @@ -99,6 +99,10 @@ class EmployeePromotionService extends BaseService return $validator->errors()->first(); } + if (DB::table('employee_jobs')->where(Arr::only($data, ['employee_id', 'job_id']))->exists()) { + return '已经拥有该职位了'; + } + return true; } diff --git a/app/Admin/Services/EmployeeSignRepairService.php b/app/Admin/Services/EmployeeSignRepairService.php index 5986554..e5d62b4 100644 --- a/app/Admin/Services/EmployeeSignRepairService.php +++ b/app/Admin/Services/EmployeeSignRepairService.php @@ -4,7 +4,7 @@ namespace App\Admin\Services; use App\Admin\Filters\EmployeeSignRepairFilter; use App\Models\Employee; -use App\Models\{EmployeeSignRepair, EmployeeSign}; +use App\Models\{EmployeeSignRepair, EmployeeSign, EmployeeSignLog}; use App\Models\WorkflowCheck; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; @@ -50,6 +50,13 @@ class EmployeeSignRepairService extends BaseService if ($validator->fails()) { return $validator->errors()->first(); } + if (EmployeeSignLog::filter([ + 'date' => $data['date'], + 'employee_id' => $data['employee_id'], + 'sign_time' => $data['repair_type'] + ])->exists()) { + return '已经打过卡了'; + } // todo 已经打卡不能申请 // todo 验证申请时间是否重复 diff --git a/app/Admin/Services/EmployeeSignService.php b/app/Admin/Services/EmployeeSignService.php index 3bc9d88..3088aab 100644 --- a/app/Admin/Services/EmployeeSignService.php +++ b/app/Admin/Services/EmployeeSignService.php @@ -101,6 +101,9 @@ class EmployeeSignService extends BaseService 'sign_type' => data_get($options, 'type'), 'remarks' => data_get($options, 'remarks'), 'position' => data_get($options, 'position'), + 'is_repair' => data_get($options, 'is_repair') ? 1 : 0, + 'outside_remarks' => data_get($options, 'outside_remarks'), + 'repair_id' => data_get($options, 'repair_id'), ]); // 更新打卡情况 @@ -126,8 +129,38 @@ class EmployeeSignService extends BaseService return true; } - public function hasRest(Employee $user, $date) + /** + * 判断员工某天是否休息 + */ + public function hasRest(Employee $user, $date = '') { + $date = $date ?: now(); return EmployeeRest::where('employee_id', $user->id)->where('date', $date)->exists(); } + + /** + * 计算两点之间的距离(千米) + */ + public function haversineDistance($lat1, $lon1, $lat2, $lon2) + { + // 地球半径(千米) + $R = 6371; + + // 将角度转换为弧度 + $lat1 = deg2rad($lat1); + $lon1 = deg2rad($lon1); + $lat2 = deg2rad($lat2); + $lon2 = deg2rad($lon2); + + // 经纬度差值 + $dlon = $lon2 - $lon1; + $dlat = $lat2 - $lat1; + + // Calculate the Haversine formula + $a = pow(sin($dlat/2), 2) + cos($lat1) * cos($lat2) * pow(sin($dlon/2), 2); + $c = 2 * atan2(sqrt($a), sqrt(1-$a)); + $distance = $R * $c; + + return floor($distance * 1000) / 1000; + } } diff --git a/app/Http/Controllers/Api/Hr/SignController.php b/app/Http/Controllers/Api/Hr/SignController.php index 728c532..fa125c1 100644 --- a/app/Http/Controllers/Api/Hr/SignController.php +++ b/app/Http/Controllers/Api/Hr/SignController.php @@ -3,13 +3,14 @@ namespace App\Http\Controllers\Api\Hr; use App\Http\Controllers\Api\Controller; -use App\Models\{EmployeeSign}; +use App\Models\{EmployeeSign, EmployeeSignLog}; use Illuminate\Http\{Request, Response}; use App\Exceptions\RuntimeException; use Illuminate\Support\Facades\DB; use App\Admin\Services\EmployeeSignService; use App\Enums\{SignTime, SignType, SignStatus}; use Carbon\Carbon; +use Slowlyo\OwlAdmin\Services\AdminSettingService; /** * 考勤打卡 @@ -29,8 +30,8 @@ class SignController extends Controller array_push($data, [ 'date' => $start->format('Y-m-d'), 'sign_status' => $info ? $info->sign_status : null, - 'first_time' => $info?->first_time->format('H:i'), - 'last_time' => $info?->last_time->format('H:i'), + 'first_time' => $info && $info->first_time ? $info->first_time->format('H:i') : '', + 'last_time' => $info && $info->last_time ? $info->last_time->format('H:i') : '', ]); $start->addDay(); } while(!$end->isSameDay($start)); @@ -41,15 +42,27 @@ class SignController extends Controller public function info(Request $request, EmployeeSignService $service) { $user = $this->guard()->user(); + $store = $user->store; $date = now(); - // 上午: 上班打卡, 下午: 下班打卡 - $time = $date->format('H') <= 12 ? SignTime::Morning : SignTime::Afternoon; - // 根据定位的距离判断, 是否外勤 - $type = SignType::Normal; - // 当前位置不在考勤范围内,请选择外勤打卡 - $description = '已进入考勤范围xx店'; + // 是否允许打卡 + $enable = true; + // 上班/下班 打卡, 今天是否打卡 + $time = EmployeeSignLog::filter(['date' => $date->format('Y-m-d')])->exists() ? SignTime::Morning : SignTime::Afternoon; - return compact('time', 'type', 'description'); + // 根据定位的距离判断, 是否外勤 + $maxDistance = AdminSettingService::make()->arrayGet('sign', 'distance'); + $type = SignType::Outside; + $distance = ''; + $description = '当前位置不在考勤范围内,请选择外勤打卡'; + if ($request->filled(['lon', 'lat']) && $store && $maxDistance) { + $distance = $service->haversineDistance($request->input('lat'), $request->input('lon'), $store->lat, $store->lon); + if ($distance <= $maxDistance) { + $description = '已进入考勤范围' . $store->title; + $type = SignType::Normal; + } + } + + return compact('enable', 'time', 'type', 'description', 'distance'); } public function store(Request $request, EmployeeSignService $service) diff --git a/app/Http/Controllers/Api/Hr/SignRepairController.php b/app/Http/Controllers/Api/Hr/SignRepairController.php index 913b791..78ea893 100644 --- a/app/Http/Controllers/Api/Hr/SignRepairController.php +++ b/app/Http/Controllers/Api/Hr/SignRepairController.php @@ -36,7 +36,7 @@ class SignRepairController extends Controller try { DB::beginTransaction(); if (!$service->store($data)) { - throw new RuntimeException($result); + throw new RuntimeException($service->getError()); } $model = $service->getCurrentModel(); $workflow = WorkFlowService::make(); diff --git a/app/Models/Employee.php b/app/Models/Employee.php index f738f56..fe02d8e 100644 --- a/app/Models/Employee.php +++ b/app/Models/Employee.php @@ -55,7 +55,7 @@ class Employee extends Model implements AuthenticatableContract public function jobs() { // $related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, $relatedKey = null, $relation = null - return $this->belongsToMany(Keyword::class, 'employee_jobs', 'employee_id', 'job', null, 'key'); + return $this->belongsToMany(Keyword::class, 'employee_jobs', 'employee_id', 'job_id', null, 'key'); } // 关联登录账户 diff --git a/app/Models/EmployeePromotion.php b/app/Models/EmployeePromotion.php index fb981c6..ada2aed 100644 --- a/app/Models/EmployeePromotion.php +++ b/app/Models/EmployeePromotion.php @@ -33,6 +33,8 @@ class EmployeePromotion extends Model public function checkSuccess() { $this->update(['promotion_status' => PromotionStatus::Success]); + // 添加职位 + $this->employee->jobs()->attach($this->job_id); } public function checkFail() diff --git a/app/Models/EmployeeSignLog.php b/app/Models/EmployeeSignLog.php index 12589bf..a286440 100644 --- a/app/Models/EmployeeSignLog.php +++ b/app/Models/EmployeeSignLog.php @@ -18,7 +18,7 @@ class EmployeeSignLog extends Model protected $table = 'employee_sign_logs'; - protected $guarded = []; + protected $fillable = ['store_id', 'employee_id', 'sign_type', 'sign_time', 'remarks', 'position', 'time', 'is_repair', 'outside_remarks', 'repair_id']; protected $casts = [ 'sign_type' => SignType::class, diff --git a/app/Models/EmployeeSignRepair.php b/app/Models/EmployeeSignRepair.php index 5e6700b..e926a64 100644 --- a/app/Models/EmployeeSignRepair.php +++ b/app/Models/EmployeeSignRepair.php @@ -7,6 +7,8 @@ use App\Traits\HasCheckable; use App\Traits\HasDateTimeFormatter; use EloquentFilter\Filterable; use Illuminate\Database\Eloquent\Model; +use App\Models\EmployeeSignLog; +use App\Admin\Services\EmployeeSignService; /** * 补卡申请 @@ -17,7 +19,7 @@ class EmployeeSignRepair extends Model protected $table = 'employee_sign_repairs'; - protected $guarded = []; + protected $fillable = ['date', 'store_id', 'employee_id', 'reason', 'repair_type', 'sign_type', 'outside_remarks']; protected $casts = [ 'date' => 'datetime', @@ -30,6 +32,18 @@ class EmployeeSignRepair extends Model return in_array($this->workflow?->check_status, [CheckStatus::None, CheckStatus::Fail, CheckStatus::Cancel]); } + public function checkSuccess() + { + EmployeeSignService::make()->signDay($this->employee, $this->repair_type, $this->date, [ + 'type' => $this->sign_type, + 'remarks' => $this->reason, + 'position' => ['address' => '无'], + 'is_repair' => 1, + 'outside_remarks' => $this->outside_remarks, + 'repair_id' => $this->id, + ]); + } + public function modelFilter() { return \App\Admin\Filters\EmployeeSignRepairFilter::class; diff --git a/app/Traits/HasCheckable.php b/app/Traits/HasCheckable.php index 090efae..51f7c69 100644 --- a/app/Traits/HasCheckable.php +++ b/app/Traits/HasCheckable.php @@ -34,16 +34,25 @@ trait HasCheckable return Str::snake(class_basename(__CLASS__)); } + /** + * 审核通过 + */ public function checkSuccess() { } + /** + * 审核未通过 + */ public function checkFail() { } + /** + * 取消申请 + */ public function checkCancel() { diff --git a/database/migrations/2024_03_22_091409_create_employees_table.php b/database/migrations/2024_03_22_091409_create_employees_table.php index 451e8e5..4e898c4 100644 --- a/database/migrations/2024_03_22_091409_create_employees_table.php +++ b/database/migrations/2024_03_22_091409_create_employees_table.php @@ -33,7 +33,7 @@ return new class extends Migration Schema::create('employee_jobs', function (Blueprint $table) { $table->foreignId('employee_id'); - $table->string('job')->comment('职位, 字典表: keywords.job'); + $table->string('job_id')->comment('职位, 字典表: keywords.job'); }); } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index ba7fab1..5ad8323 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -18,6 +18,7 @@ class DatabaseSeeder extends Seeder KeywordSeeder::class, AdminPermissionSeeder::class, WorkflowSeeder::class, + SettingSeeder::class, ]); } } diff --git a/database/seeders/KeywordSeeder.php b/database/seeders/KeywordSeeder.php index 7b11133..2847ff9 100644 --- a/database/seeders/KeywordSeeder.php +++ b/database/seeders/KeywordSeeder.php @@ -34,7 +34,7 @@ class KeywordSeeder extends Seeder [ 'key' => 'job', 'name' => '职位', - 'children' => ['普通员工', '小组长', '主管'], + 'children' => ['普通员工', '店长', '区域经理'], ], [ 'key' => 'store_category', diff --git a/database/seeders/SettingSeeder.php b/database/seeders/SettingSeeder.php new file mode 100644 index 0000000..a55572f --- /dev/null +++ b/database/seeders/SettingSeeder.php @@ -0,0 +1,30 @@ +truncate(); + $now = now(); + $list = [ + ['key' => 'upload_disk', 'values' => 'public', 'created_at' => $now, 'updated_at' => $now], + ['key' => 'sign', 'values' => json_encode(['distance' => '10']), 'created_at' => $now, 'updated_at' => $now], + ['key' => 'baidu', 'values' => json_encode(['js_key' => '62038330xDTLJ15QG6zt3f6VQcaNBfN8q3MsWBsE'])] + ]; + // 清空缓存 + foreach ($list as $item) { + AdminSettingService::make()->clearCache($item['key']); + } + DB::table('admin_settings')->insert($list); + } +} diff --git a/lang/zh_CN/employee_sign_log.php b/lang/zh_CN/employee_sign_log.php index 7fddd64..2817660 100644 --- a/lang/zh_CN/employee_sign_log.php +++ b/lang/zh_CN/employee_sign_log.php @@ -11,4 +11,5 @@ return [ 'time' => '打卡时间', 'remarks' => '事由', 'position' => '位置', + 'is_repair' => '是否补卡', ];