generated from liutk/owl-admin-base
admin 打卡记录
parent
308115f729
commit
f7b2bec32a
|
|
@ -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}'],
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
// {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -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('员工-打卡流水');
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,4 +14,5 @@ return [
|
|||
'first_time' => '上班时间',
|
||||
'last_time' => '下班时间',
|
||||
'sign_status' => '打卡状态',
|
||||
'remarks' => '事由',
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in New Issue