generated from liutk/owl-admin-base
api signDay
parent
28382facf7
commit
afa6924680
|
|
@ -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],
|
||||
]));
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
])
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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文件内配置;
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 验证申请时间是否重复
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
// 关联登录账户
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -34,16 +34,25 @@ trait HasCheckable
|
|||
return Str::snake(class_basename(__CLASS__));
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核通过
|
||||
*/
|
||||
public function checkSuccess()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核未通过
|
||||
*/
|
||||
public function checkFail()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消申请
|
||||
*/
|
||||
public function checkCancel()
|
||||
{
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class DatabaseSeeder extends Seeder
|
|||
KeywordSeeder::class,
|
||||
AdminPermissionSeeder::class,
|
||||
WorkflowSeeder::class,
|
||||
SettingSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class KeywordSeeder extends Seeder
|
|||
[
|
||||
'key' => 'job',
|
||||
'name' => '职位',
|
||||
'children' => ['普通员工', '小组长', '主管'],
|
||||
'children' => ['普通员工', '店长', '区域经理'],
|
||||
],
|
||||
[
|
||||
'key' => 'store_category',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Slowlyo\OwlAdmin\Services\AdminSettingService;
|
||||
|
||||
class SettingSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
DB::table('admin_settings')->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);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,4 +11,5 @@ return [
|
|||
'time' => '打卡时间',
|
||||
'remarks' => '事由',
|
||||
'position' => '位置',
|
||||
'is_repair' => '是否补卡',
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in New Issue