diff --git a/app/Admin/Controllers/Hr/SignController.php b/app/Admin/Controllers/Hr/SignController.php index 84c407d..a8aad77 100644 --- a/app/Admin/Controllers/Hr/SignController.php +++ b/app/Admin/Controllers/Hr/SignController.php @@ -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}'], ])); } } diff --git a/app/Admin/Filters/EmployeeSignFilter.php b/app/Admin/Filters/EmployeeSignFilter.php index 19205ac..ce8f049 100644 --- a/app/Admin/Filters/EmployeeSignFilter.php +++ b/app/Admin/Filters/EmployeeSignFilter.php @@ -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)); + } } diff --git a/app/Admin/Services/EmployeeSignService.php b/app/Admin/Services/EmployeeSignService.php index 41253ab..03aa0f9 100644 --- a/app/Admin/Services/EmployeeSignService.php +++ b/app/Admin/Services/EmployeeSignService.php @@ -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); + } + } } diff --git a/app/Enums/SignStatus.php b/app/Enums/SignStatus.php index 738df57..6703bbd 100644 --- a/app/Enums/SignStatus.php +++ b/app/Enums/SignStatus.php @@ -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', + ], ]; } diff --git a/app/Enums/SignType.php b/app/Enums/SignType.php index 67eb7ce..426af95 100644 --- a/app/Enums/SignType.php +++ b/app/Enums/SignType.php @@ -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', + ], ]; } diff --git a/app/Models/Employee.php b/app/Models/Employee.php index 454eefe..ea04693 100644 --- a/app/Models/Employee.php +++ b/app/Models/Employee.php @@ -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() // { diff --git a/app/Models/EmployeeSign.php b/app/Models/EmployeeSign.php index d2d01ea..b934f67 100644 --- a/app/Models/EmployeeSign.php +++ b/app/Models/EmployeeSign.php @@ -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', diff --git a/app/Models/EmployeeSignLog.php b/app/Models/EmployeeSignLog.php index 55c7680..63c907d 100644 --- a/app/Models/EmployeeSignLog.php +++ b/app/Models/EmployeeSignLog.php @@ -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() diff --git a/database/factories/EmployeeSignLogFactory.php b/database/factories/EmployeeSignLogFactory.php new file mode 100644 index 0000000..c08d1d9 --- /dev/null +++ b/database/factories/EmployeeSignLogFactory.php @@ -0,0 +1,34 @@ + + */ +class EmployeeSignLogFactory extends Factory +{ + protected $model = EmployeeSignLog::class; + /** + * Define the model's default state. + * + * @return array + */ + 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') + ]; + } +} diff --git a/database/migrations/2024_03_27_140744_create_employee_sign_table.php b/database/migrations/2024_03_27_140744_create_employee_sign_table.php index e0db072..a590437 100644 --- a/database/migrations/2024_03_27_140744_create_employee_sign_table.php +++ b/database/migrations/2024_03_27_140744_create_employee_sign_table.php @@ -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('员工-打卡流水'); diff --git a/database/seeders/EmployeeSeeder.php b/database/seeders/EmployeeSeeder.php index b0a7728..d6b1782 100644 --- a/database/seeders/EmployeeSeeder.php +++ b/database/seeders/EmployeeSeeder.php @@ -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(); } } diff --git a/lang/zh_CN/employee_sign.php b/lang/zh_CN/employee_sign.php index 9f54fe1..6a518ad 100644 --- a/lang/zh_CN/employee_sign.php +++ b/lang/zh_CN/employee_sign.php @@ -14,4 +14,5 @@ return [ 'first_time' => '上班时间', 'last_time' => '下班时间', 'sign_status' => '打卡状态', + 'remarks' => '事由', ];