admin 打卡记录

main
panliang 2024-03-28 11:51:50 +08:00
parent 308115f729
commit f7b2bec32a
12 changed files with 191 additions and 14 deletions

View File

@ -6,6 +6,7 @@ use App\Admin\Controllers\AdminController;
use App\Admin\Services\EmployeeSignService;
use Slowlyo\OwlAdmin\Renderers\Form;
use Slowlyo\OwlAdmin\Renderers\Page;
use App\Enums\{SignType, SignStatus};
/**
* 考勤打卡
@ -16,6 +17,7 @@ class SignController extends AdminController
public function list(): Page
{
// $this->service->signResult();
$crud = $this->baseCRUD()
->tableLayout('fixed')
->headerToolbar([
@ -26,16 +28,25 @@ class SignController extends AdminController
amis()->GroupControl()->mode('horizontal')->body([
amisMake()->SelectControl()->source(admin_url('api/stores?_all=1'))->labelField('title')->valueField('id')->searchable()->name('store_id')->label(__('employee_sign.store_id'))->columnRatio(3)->clearable(),
amisMake()->TextControl()->name('employee_name')->label(__('employee_sign.employee_id'))->placeholder(__('employee.name').'/'.__('employee.phone'))->columnRatio(3)->clearable(),
amisMake()->SelectControl()->options(SignType::options())->name('sign_type')->label(__('employee_sign.sign_type'))->columnRatio(3)->clearable(),
amisMake()->SelectControl()->options(SignStatus::options())->name('sign_status')->label(__('employee_sign.sign_status'))->columnRatio(3)->clearable(),
]),
amis()->GroupControl()->mode('horizontal')->body([
amisMake()->DateRangeControl()->name('date_range')->label(__('employee_sign.date'))->columnRatio(3)->clearable(),
]),
]))
->columns([
amisMake()->TableColumn()->name('store.name')->label(__('employee_sign.store_id')),
amisMake()->TableColumn()->name('store.title')->label(__('employee_sign.store_id')),
amisMake()->TableColumn()->name('employee.name')->label(__('employee.name')),
amisMake()->TableColumn()->name('sign_type')->label(__('employee_sign.sign_type')),
amisMake()->TableColumn()->name('sign_type')->label(__('employee_sign.sign_type'))
->set('type', 'status')
->source(SignType::source()),
amisMake()->TableColumn()->name('first_time')->label(__('employee_sign.first_time')),
amisMake()->TableColumn()->name('last_time')->label(__('employee_sign.last_time')),
amisMake()->TableColumn()->name('sign_status')->label(__('employee_sign.sign_status')),
amisMake()->TableColumn()->name('sign_status')->label(__('employee_sign.sign_status'))
->set('type', 'status')
->source(SignStatus::source()),
amisMake()->TableColumn()->name('remarks')->label(__('employee_sign.remarks')),
$this->rowActions([
$this->rowShowButton(),
]),
@ -47,6 +58,8 @@ class SignController extends AdminController
public function detail(): Form
{
return $this->baseDetail()->title('')->body(amisMake()->Property()->items([
['label' => __('employee_sign.store_id'), 'content' => '${store.title}'],
['label' => __('employee.name'), 'content' => '${employee.name}'],
]));
}
}

View File

@ -36,4 +36,14 @@ class EmployeeSignFilter extends ModelFilter
$end = Carbon::createFromTimestamp(data_get($dates, 1, time()))->endOfDay();
$this->whereBetween('date', [$start, $end]);
}
public function signType($key)
{
$this->whereIn('sign_type', is_array($key) ? $key : explode(',', $key));
}
public function signStatus($key)
{
$this->whereIn('sign_status', is_array($key) ? $key : explode(',', $key));
}
}

View File

@ -4,6 +4,11 @@ namespace App\Admin\Services;
use App\Admin\Filters\EmployeeSignFilter;
use App\Models\EmployeeSign;
use App\Models\EmployeeSignLog;
use App\Models\EmployeeRest;
use App\Models\Employee;
use App\Enums\SignType;
use App\Enums\SignStatus;
class EmployeeSignService extends BaseService
{
@ -12,4 +17,63 @@ class EmployeeSignService extends BaseService
protected string $modelName = EmployeeSign::class;
protected string $modelFilterName = EmployeeSignFilter::class;
/**
* 整理昨天的打卡流水, 生成对应的打卡记录
*/
public function signResult()
{
$date = now()->subDay();
$start = $date->copy()->startOfDay();
$end = $date->copy()->endOfDay();
$list = EmployeeSignLog::whereBetween('time', [$start, $end])->get();
// 休息的员工
$restEmployeeIds = EmployeeRest::whereBetWeen('date', [$start, $end])->pluck('employee_id');
// 需要打卡的员工
$employees = Employee::where('store_id', '>', 0)->whereNotIn('id', $restEmployeeIds)->get();
foreach ($employees as $employee) {
$logs = $list->where('employee_id', $employee->id);
// 状态: 两个打卡=正常, 一次打卡 = 缺卡, 两次未打=旷工
$status = 0;
// 外勤打卡-事由
$remarks = null;
// 上班时间: 12:00 前打卡的都算
$firstTime = null;
if ($item = $logs->where('time', '<=', $date->format('Y-m-d 12:00'))->sortBy('time')->first()) {
$firstTime = $item->time;
$status ++;
if ($item->sign_type == SignType::Outside) {
$remarks = $item->remarks;
}
}
// 下班时间: 24:00 前打卡的都算
$lastTime = null;
if ($item = $logs->where('time', '<=', $date->format('Y-m-d 24:00'))->sortByDesc('time')->first()) {
$lastTime = $item->time;
$status ++;
if ($item->sign_type == SignType::Outside) {
$remarks = $item->remarks;
}
}
// 打卡类型
$type = SignType::Absent;
if ($status > 0) {
$type = $logs->where('sign_type', SignType::Outside)->count() > 0 ? SignType::Outside : SignType::Normal;
}
$attributes = [
'date' => $date,
'store_id' => $employee->store_id,
'sign_type' => $type,
'first_time' => $firstTime,
'last_time' => $lastTime,
'sign_status' => match($status) {
0 => SignStatus::Absent,
1 => SignStatus::Lose,
2 => SignStatus::Normal,
},
'remarks' => $remarks,
];
$employee->signs()->create($attributes);
}
}
}

View File

@ -5,15 +5,33 @@ namespace App\Enums;
enum SignStatus: int
{
case Normal = 1;
case Absent = 2;
case Lose = 3;
case Lose = 2;
case Absent = 3;
public static function options()
{
return [
self::Normal->value => '正常',
self::Absent->value => '旷工',
self::Lose->value => '缺卡',
self::Absent->value => '旷工',
];
}
public static function source()
{
return [
self::Normal->value => [
'label' => self::Normal->text(),
'color' => '#1b9908',
],
self::Lose->value => [
'label' => self::Lose->text(),
'color' => '#d97116',
],
self::Absent->value => [
'label' => self::Absent->text(),
'color' => '#a61922',
],
];
}

View File

@ -6,12 +6,32 @@ enum SignType: int
{
case Normal = 1;
case Outside = 2;
case Absent = 3;
public static function options()
{
return [
self::Normal->value => '正常打卡',
self::Outside->value => '外勤打卡',
self::Absent->value => '旷工',
];
}
public static function source()
{
return [
self::Normal->value => [
'label' => self::Normal->text(),
'color' => '#1b9908',
],
self::Outside->value => [
'label' => self::Outside->text(),
'color' => '#d97116',
],
self::Absent->value => [
'label' => self::Absent->text(),
'color' => '#a61922',
],
];
}

View File

@ -59,6 +59,12 @@ class Employee extends Model
return $this->belongsTo(Store::class, 'store_id');
}
// 打卡情况
public function signs()
{
return $this->hasMany(EmployeeSign::class, 'employee_id');
}
// 管理的门店(店长)
// public function masterStore()
// {

View File

@ -17,7 +17,7 @@ class EmployeeSign extends Model
protected $table = 'employee_sign_dates';
protected $fillable = ['date', 'store_id', 'employee_id', 'sign_type', 'first_time', 'last_time', 'sign_status'];
protected $fillable = ['date', 'store_id', 'employee_id', 'sign_type', 'first_time', 'last_time', 'sign_status', 'remarks'];
protected $casts = [
'date' => 'date:Y-m-d',

View File

@ -4,19 +4,24 @@ namespace App\Models;
use App\Enums\SignType;
use Illuminate\Database\Eloquent\Model;
use App\Traits\HasDateTimeFormatter;
use Illuminate\Database\Eloquent\Factories\HasFactory;
/**
* 员工-打卡流水
*/
class EmployeeSignLog extends Model
{
use HasDateTimeFormatter, HasFactory;
protected $table = 'employee_sign_logs';
protected $fillable = ['store_id', 'employee_id', 'sign_type', 'remarks', 'position'];
protected $fillable = ['store_id', 'employee_id', 'sign_type', 'remarks', 'position', 'time'];
protected $casts = [
'sign_type' => SignType::class,
'position' => 'json',
'time' => 'datetime',
];
public function store()

View File

@ -0,0 +1,34 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Models\EmployeeSignLog;
use App\Models\Employee;
use App\Enums\SignType;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\EmployeeSign>
*/
class EmployeeSignLogFactory extends Factory
{
protected $model = EmployeeSignLog::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
$employee = Employee::where('store_id', '>', 0)->inRandomOrder()->first();
$type = $this->faker->randomElement(SignType::class);
return [
'store_id' => $employee->store_id,
'employee_id' => $employee->id,
'sign_type' => $type,
'remarks' => $type == SignType::Outside ? '我在外面的' : '',
'position' => ['province' => '重庆', 'city' => '重庆市', 'address' => '重庆市南川区东城街道办事处东环路三号'],
'time' => $this->faker->dateTimeBetween('-7 days', 'now')
];
}
}

View File

@ -20,6 +20,7 @@ return new class extends Migration
$table->timestamp('first_time')->nullable()->comment('上班打卡时间');
$table->timestamp('last_time')->nullable()->comment('下班打卡时间');
$table->unsignedInteger('sign_status')->default(1)->comment('考勤状态(1: 正常, 2: 旷工, 3: 缺卡)');
$table->string('remarks')->nullable()->comment('备注');
$table->timestamps();
$table->comment('员工-考勤记录');
@ -32,6 +33,7 @@ return new class extends Migration
$table->unsignedInteger('sign_type')->default(1)->comment('类别(1: 正常打卡, 2: 外勤)');
$table->string('remarks')->nullable()->comment('备注');
$table->json('position')->comment('打卡位置');
$table->timestamp('time')->comment('打卡时间');
$table->timestamps();
$table->comment('员工-打卡流水');

View File

@ -4,8 +4,8 @@ namespace Database\Seeders;
use App\Models\Employee;
use App\Models\Store;
use App\Models\{EmployeeSign, EmployeeSignLog};
use Database\Factories\EmployeeFactory;
use Database\Factories\StoreFactory;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
@ -16,11 +16,15 @@ class EmployeeSeeder extends Seeder
*/
public function run(): void
{
DB::table('employee_jobs')->truncate();
Employee::truncate();
(new EmployeeFactory)->count(100)->create(['admin_user_id' => 1]);
// DB::table('employee_jobs')->truncate();
// Employee::truncate();
// (new EmployeeFactory)->count(100)->create(['admin_user_id' => 1]);
Store::truncate();
Store::factory()->count(10)->create();
// Store::truncate();
// Store::factory()->count(10)->create();
EmployeeSign::truncate();
EmployeeSignLog::truncate();
EmployeeSignLog::factory()->count(100)->create();
}
}

View File

@ -14,4 +14,5 @@ return [
'first_time' => '上班时间',
'last_time' => '下班时间',
'sign_status' => '打卡状态',
'remarks' => '事由',
];