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 App\Admin\Services\EmployeeSignService;
|
||||||
use Slowlyo\OwlAdmin\Renderers\Form;
|
use Slowlyo\OwlAdmin\Renderers\Form;
|
||||||
use Slowlyo\OwlAdmin\Renderers\Page;
|
use Slowlyo\OwlAdmin\Renderers\Page;
|
||||||
|
use App\Enums\{SignType, SignStatus};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 考勤打卡
|
* 考勤打卡
|
||||||
|
|
@ -16,6 +17,7 @@ class SignController extends AdminController
|
||||||
|
|
||||||
public function list(): Page
|
public function list(): Page
|
||||||
{
|
{
|
||||||
|
// $this->service->signResult();
|
||||||
$crud = $this->baseCRUD()
|
$crud = $this->baseCRUD()
|
||||||
->tableLayout('fixed')
|
->tableLayout('fixed')
|
||||||
->headerToolbar([
|
->headerToolbar([
|
||||||
|
|
@ -26,16 +28,25 @@ class SignController extends AdminController
|
||||||
amis()->GroupControl()->mode('horizontal')->body([
|
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()->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()->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(),
|
amisMake()->DateRangeControl()->name('date_range')->label(__('employee_sign.date'))->columnRatio(3)->clearable(),
|
||||||
]),
|
]),
|
||||||
]))
|
]))
|
||||||
->columns([
|
->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('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('first_time')->label(__('employee_sign.first_time')),
|
||||||
amisMake()->TableColumn()->name('last_time')->label(__('employee_sign.last_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->rowActions([
|
||||||
$this->rowShowButton(),
|
$this->rowShowButton(),
|
||||||
]),
|
]),
|
||||||
|
|
@ -47,6 +58,8 @@ class SignController extends AdminController
|
||||||
public function detail(): Form
|
public function detail(): Form
|
||||||
{
|
{
|
||||||
return $this->baseDetail()->title('')->body(amisMake()->Property()->items([
|
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();
|
$end = Carbon::createFromTimestamp(data_get($dates, 1, time()))->endOfDay();
|
||||||
$this->whereBetween('date', [$start, $end]);
|
$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\Admin\Filters\EmployeeSignFilter;
|
||||||
use App\Models\EmployeeSign;
|
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
|
class EmployeeSignService extends BaseService
|
||||||
{
|
{
|
||||||
|
|
@ -12,4 +17,63 @@ class EmployeeSignService extends BaseService
|
||||||
protected string $modelName = EmployeeSign::class;
|
protected string $modelName = EmployeeSign::class;
|
||||||
|
|
||||||
protected string $modelFilterName = EmployeeSignFilter::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
|
enum SignStatus: int
|
||||||
{
|
{
|
||||||
case Normal = 1;
|
case Normal = 1;
|
||||||
case Absent = 2;
|
case Lose = 2;
|
||||||
case Lose = 3;
|
case Absent = 3;
|
||||||
|
|
||||||
public static function options()
|
public static function options()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
self::Normal->value => '正常',
|
self::Normal->value => '正常',
|
||||||
self::Absent->value => '旷工',
|
|
||||||
self::Lose->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 Normal = 1;
|
||||||
case Outside = 2;
|
case Outside = 2;
|
||||||
|
case Absent = 3;
|
||||||
|
|
||||||
public static function options()
|
public static function options()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
self::Normal->value => '正常打卡',
|
self::Normal->value => '正常打卡',
|
||||||
self::Outside->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');
|
return $this->belongsTo(Store::class, 'store_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 打卡情况
|
||||||
|
public function signs()
|
||||||
|
{
|
||||||
|
return $this->hasMany(EmployeeSign::class, 'employee_id');
|
||||||
|
}
|
||||||
|
|
||||||
// 管理的门店(店长)
|
// 管理的门店(店长)
|
||||||
// public function masterStore()
|
// public function masterStore()
|
||||||
// {
|
// {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class EmployeeSign extends Model
|
||||||
|
|
||||||
protected $table = 'employee_sign_dates';
|
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 = [
|
protected $casts = [
|
||||||
'date' => 'date:Y-m-d',
|
'date' => 'date:Y-m-d',
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,24 @@ namespace App\Models;
|
||||||
|
|
||||||
use App\Enums\SignType;
|
use App\Enums\SignType;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use App\Traits\HasDateTimeFormatter;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 员工-打卡流水
|
* 员工-打卡流水
|
||||||
*/
|
*/
|
||||||
class EmployeeSignLog extends Model
|
class EmployeeSignLog extends Model
|
||||||
{
|
{
|
||||||
|
use HasDateTimeFormatter, HasFactory;
|
||||||
|
|
||||||
protected $table = 'employee_sign_logs';
|
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 = [
|
protected $casts = [
|
||||||
'sign_type' => SignType::class,
|
'sign_type' => SignType::class,
|
||||||
'position' => 'json',
|
'position' => 'json',
|
||||||
|
'time' => 'datetime',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function store()
|
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('first_time')->nullable()->comment('上班打卡时间');
|
||||||
$table->timestamp('last_time')->nullable()->comment('下班打卡时间');
|
$table->timestamp('last_time')->nullable()->comment('下班打卡时间');
|
||||||
$table->unsignedInteger('sign_status')->default(1)->comment('考勤状态(1: 正常, 2: 旷工, 3: 缺卡)');
|
$table->unsignedInteger('sign_status')->default(1)->comment('考勤状态(1: 正常, 2: 旷工, 3: 缺卡)');
|
||||||
|
$table->string('remarks')->nullable()->comment('备注');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
|
||||||
$table->comment('员工-考勤记录');
|
$table->comment('员工-考勤记录');
|
||||||
|
|
@ -32,6 +33,7 @@ return new class extends Migration
|
||||||
$table->unsignedInteger('sign_type')->default(1)->comment('类别(1: 正常打卡, 2: 外勤)');
|
$table->unsignedInteger('sign_type')->default(1)->comment('类别(1: 正常打卡, 2: 外勤)');
|
||||||
$table->string('remarks')->nullable()->comment('备注');
|
$table->string('remarks')->nullable()->comment('备注');
|
||||||
$table->json('position')->comment('打卡位置');
|
$table->json('position')->comment('打卡位置');
|
||||||
|
$table->timestamp('time')->comment('打卡时间');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
|
||||||
$table->comment('员工-打卡流水');
|
$table->comment('员工-打卡流水');
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ namespace Database\Seeders;
|
||||||
|
|
||||||
use App\Models\Employee;
|
use App\Models\Employee;
|
||||||
use App\Models\Store;
|
use App\Models\Store;
|
||||||
|
use App\Models\{EmployeeSign, EmployeeSignLog};
|
||||||
use Database\Factories\EmployeeFactory;
|
use Database\Factories\EmployeeFactory;
|
||||||
use Database\Factories\StoreFactory;
|
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
|
@ -16,11 +16,15 @@ class EmployeeSeeder extends Seeder
|
||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
DB::table('employee_jobs')->truncate();
|
// DB::table('employee_jobs')->truncate();
|
||||||
Employee::truncate();
|
// Employee::truncate();
|
||||||
(new EmployeeFactory)->count(100)->create(['admin_user_id' => 1]);
|
// (new EmployeeFactory)->count(100)->create(['admin_user_id' => 1]);
|
||||||
|
|
||||||
Store::truncate();
|
// Store::truncate();
|
||||||
Store::factory()->count(10)->create();
|
// Store::factory()->count(10)->create();
|
||||||
|
|
||||||
|
EmployeeSign::truncate();
|
||||||
|
EmployeeSignLog::truncate();
|
||||||
|
EmployeeSignLog::factory()->count(100)->create();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,4 +14,5 @@ return [
|
||||||
'first_time' => '上班时间',
|
'first_time' => '上班时间',
|
||||||
'last_time' => '下班时间',
|
'last_time' => '下班时间',
|
||||||
'sign_status' => '打卡状态',
|
'sign_status' => '打卡状态',
|
||||||
|
'remarks' => '事由',
|
||||||
];
|
];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue