api signDay

main
panliang 2024-04-18 12:34:13 +08:00
parent 28382facf7
commit afa6924680
20 changed files with 152 additions and 28 deletions

View File

@ -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],
]));

View File

@ -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'),
])
);

View File

@ -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文件内配置

View File

@ -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(),
]);
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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 验证申请时间是否重复

View File

@ -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;
}
}

View File

@ -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)

View File

@ -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();

View File

@ -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');
}
// 关联登录账户

View File

@ -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()

View File

@ -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,

View File

@ -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;

View File

@ -34,16 +34,25 @@ trait HasCheckable
return Str::snake(class_basename(__CLASS__));
}
/**
* 审核通过
*/
public function checkSuccess()
{
}
/**
* 审核未通过
*/
public function checkFail()
{
}
/**
* 取消申请
*/
public function checkCancel()
{

View File

@ -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');
});
}

View File

@ -18,6 +18,7 @@ class DatabaseSeeder extends Seeder
KeywordSeeder::class,
AdminPermissionSeeder::class,
WorkflowSeeder::class,
SettingSeeder::class,
]);
}
}

View File

@ -34,7 +34,7 @@ class KeywordSeeder extends Seeder
[
'key' => 'job',
'name' => '职位',
'children' => ['普通员工', '小组长', '主管'],
'children' => ['普通员工', '店长', '区域经理'],
],
[
'key' => 'store_category',

View File

@ -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);
}
}

View File

@ -11,4 +11,5 @@ return [
'time' => '打卡时间',
'remarks' => '事由',
'position' => '位置',
'is_repair' => '是否补卡',
];