You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

282 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Http\Controllers\Admin;
use App\Models\Admin;
use App\Models\Blacklist;
use App\Models\GateLog;
use App\Models\Role;
use App\Models\Visit;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use App\Helpers\ResponseCode;
use Illuminate\Support\Carbon;
use Rap2hpoutre\FastExcel\FastExcel;
/**
* 门岗
*/
class GateController extends CommonController
{
/**
* @OA\Get(
* path="/api/admin/gate/user-list",
* tags={"门岗-门岗人列表"},
* summary="列表",
* description="",
* @OA\Response(
* response="200",
* description="暂无"
* )
* )
*/
public function uerList()
{
$admin = Admin::whereHas('role', function ($query) {
$query->where('name', 'like', '%门岗%');
})->get();
return $this->success($admin);
}
/**
* @OA\Get(
* path="/api/admin/gate/visit-list",
* tags={"门岗-拜访记录"},
* summary="拜访记录",
* description="",
* @OA\Parameter(name="keyword", in="query", @OA\Schema(type="string"), required=false, description="关键词"),
* @OA\Parameter(name="audit_status", in="query", @OA\Schema(type="string"), required=false, description="审核状态-1待学习0待审核1通过(待进厂)2驳回3已进厂4已离厂"),
* @OA\Parameter(name="start_date", in="query", @OA\Schema(type="string"), required=false, description="开始日期"),
* @OA\Parameter(name="end_date", in="query", @OA\Schema(type="string"), required=false, description="结束日期"),
* @OA\Parameter(name="page_size", in="query", @OA\Schema(type="string"), required=false, description="每页显示的条数"),
* @OA\Parameter(name="page", in="query", @OA\Schema(type="string"), required=false, description="页码"),
* @OA\Parameter(name="sort_name", in="query", @OA\Schema(type="string"), required=false, description="排序字段名字"),
* @OA\Parameter(name="sort_type", in="query", @OA\Schema(type="string"), required=false, description="排序类型"),
* @OA\Parameter(name="code", in="query", @OA\Schema(type="string"), required=false, description="编码"),
* @OA\Parameter(name="idcard", in="query", @OA\Schema(type="string"), required=false, description="身份证号码"),
* @OA\Parameter(name="car_no", in="query", @OA\Schema(type="string"), required=false, description="停车牌"),
* @OA\Parameter(name="person_no", in="query", @OA\Schema(type="string"), required=false, description="人牌"),
* @OA\Parameter(name="is_export", in="query", @OA\Schema(type="string"), required=false, description="是否导出0否1是默认0"),
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"),
* @OA\Response(
* response="200",
* description="暂无"
* )
* )
*/
public function visitList()
{
$all = request()->all();
$list = Visit::with('logs.admin', 'logs.user', 'visitTime', 'admin', 'visitArea', 'acceptAdmin.department', 'acceptAdminSignFile', 'acceptGoodsAdmin.department')->where(function ($query) use ($all) {
if (isset($all['keyword'])) {
$query->where('name', 'like', '%' . $all['keyword'] . '%');
}
if (isset($all['audit_status'])) {
$query->where('audit_status', $all['audit_status']);
}
if (isset($all['code'])) {
$query->where('code', $all['code']);
}
if (isset($all['idcard'])) {
$query->where('idcard', $all['idcard']);
}
// 普通访客:按预约到访日 date 落在查询区间内;长期访客:查询区间与长期有效区间 [start_date,end_date] 有交集即可出现在「今日」等列表中
if (!empty($all['start_date']) && !empty($all['end_date'])) {
$qs = $all['start_date'];
$qe = $all['end_date'];
$query->where(function ($sub) use ($qs, $qe) {
$sub->where(function ($q1) use ($qs, $qe) {
$q1->where(function ($q2) {
$q2->whereNull('long_time')->orWhere('long_time', 0);
})->whereBetween('date', [$qs, $qe]);
})->orWhere(function ($q1) use ($qs, $qe) {
$q1->where('long_time', 1)
->whereNotNull('start_date')
->whereNotNull('end_date')
->where('start_date', '<=', $qe)
->where('end_date', '>=', $qs);
});
});
}
})->orderBy($all['sort_name'] ?? 'id', $all['sort_type'] ?? 'desc');
if (isset($all['is_export']) && !empty($all['is_export'])) {
return (new FastExcel($list->limit(10000)->get()->toArray()))->download('访客记录' . date('Ymd') . '.csv', function ($info) {
return [
'编码' => $info['code'] ?? '',
'姓名' => $info['name'] ?? '',
'类型' => $info['type_text'] ?? '',
'审核状态' => $info['audit_status_text'] ?? '',
'被访人' => ($info['accept_admin']['name']) ?? '',
'预约日期' => $info['date'] ?? '',
'证件号' => $info['idcard'] ?? '',
'手机号' => $info['mobile'] ?? '',
'单位名称' => $info['company_name'] ?? '',
'到访时段开始' => ($info['visit_time']['start_time']) ?? '',
'到访时段结束' => ($info['visit_time']['end_time']) ?? '',
'创建时间' => $info['created_at'] ?? '',
];
});
}
$list = $list->paginate($all['page_size'] ?? 20);
$today = Carbon::now()->format('Y-m-d');
$list->getCollection()->transform(function ($visit) use ($today) {
if ((int) $visit->long_time === 1) {
$visit->setAttribute('gate_verify_type', $this->computeLongTermGateVerifyType($visit, $today));
}
return $visit;
});
return $this->success($list);
}
/**
* 长期访客:单条门岗流水的业务日期(与自然日核销口径一致,缺 biz_date 时用创建时间换算应用时区)
*/
protected function gateLogBizDateForLongTerm(GateLog $log): string
{
if (!empty($log->biz_date)) {
return Carbon::parse((string) $log->biz_date)->format('Y-m-d');
}
return Carbon::parse((string) $log->created_at)->timezone(config('app.timezone'))->format('Y-m-d');
}
/**
* 长期访客:门岗建议本次核销 1 进厂 / 2 离厂(与 useCode 长期规则对齐)
*/
protected function computeLongTermGateVerifyType(Visit $visit, string $today): int
{
$latestGateLog = GateLog::where('visit_id', $visit->id)->orderByDesc('id')->first();
if (!$latestGateLog && !empty($visit->code)) {
$latestGateLog = GateLog::where('code', $visit->code)->orderByDesc('id')->first();
}
if (!$latestGateLog) {
return 1;
}
if ((int) $latestGateLog->action === 2) {
return 1;
}
$biz = $this->gateLogBizDateForLongTerm($latestGateLog);
return $biz < $today ? 1 : 2;
}
/**
* @OA\Get(
* path="/api/admin/gate/use-code",
* tags={"门岗-核销"},
* summary="核销",
* description="",
* @OA\Parameter(name="admin_id", in="query", @OA\Schema(type="string"), required=false, description="管理员id"),
* @OA\Parameter(name="code", in="query", @OA\Schema(type="string"), required=false, description="编码"),
* @OA\Parameter(name="car_no", in="query", @OA\Schema(type="string"), required=false, description="停车牌"),
* @OA\Parameter(name="person_no", in="query", @OA\Schema(type="string"), required=false, description="人牌"),
* @OA\Parameter(name="type", in="query", @OA\Schema(type="string"), required=false, description="1进厂2离厂"),
* @OA\Response(
* response="200",
* description="暂无"
* )
* )
*/
public function useCode()
{
$all = \request()->all();
$messages = [
'code.required' => '编号必填',
'type.required' => '类型必填',
'admin_id.required' => '操作人必填'
];
$validator = Validator::make($all, [
'code' => 'required',
'type' => 'required',
'admin_id' => 'required'
], $messages);
if ($validator->fails()) {
return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]);
}
$check = Visit::where('code', $all['code'])->first();
if (empty($check)) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '拜访记录不存在']);
}
if ($check->audit_status == 2 || $check->audit_status == 5) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '当前记录不可核销']);
}
// 物流访客:每次进/离厂都必须先上传当次车辆照片
if ((int)$check->type === 3) {
$latestGateLog = GateLog::where('visit_id', $check->id)->orderByDesc('id')->first();
$fileIds = [];
if (is_array($check->file)) {
$fileIds = $check->file;
} elseif (is_string($check->file) && !empty($check->file)) {
$decoded = json_decode($check->file, true);
if (is_array($decoded)) {
$fileIds = $decoded;
}
}
$hasPhoto = count($fileIds) > 0;
if (!$hasPhoto) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '物流访客需先上传车辆照片后再核销']);
}
// 要求本次核销前有一次新的更新动作,避免重复使用上次照片直接核销
if (!empty($latestGateLog) && strtotime((string)$check->updated_at) <= strtotime((string)$latestGateLog->created_at)) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '物流访客本次进/离厂请先上传当次照片']);
}
}
// 长期访客:校验当前日期在有效周期内
$today = Carbon::now()->format('Y-m-d');
if ((int)$check->long_time === 1) {
if (empty($check->start_date) || empty($check->end_date)) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '长期访客未配置有效周期']);
}
if ($today < $check->start_date || $today > $check->end_date) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '当前不在长期访客有效周期内']);
}
// 长期访客:以「全局最新流水」判断是否仍在厂未离;但以「本条进厂的 biz_date」决定能否核销离厂
// ——昨日进今日第一次操作须先核销进厂(记入今日),不能直接核销离厂;当日先离仍可再核进。
$latestGateLog = GateLog::where('visit_id', $check->id)->orderByDesc('id')->first();
if (!$latestGateLog && !empty($check->code)) {
$latestGateLog = GateLog::where('code', $check->code)->orderByDesc('id')->first();
}
if ((int)$all['type'] === 1 && $latestGateLog && (int)$latestGateLog->action === 1) {
$enterBizDate = $this->gateLogBizDateForLongTerm($latestGateLog);
if ($enterBizDate >= $today) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '今日已进厂尚未离厂,请先核销离厂后再进厂']);
}
// 未离开的进厂业务日早于今日:视为跨日未完成场次,允许再核销一笔「当日进厂」
}
if ((int)$all['type'] === 2) {
if (!$latestGateLog || (int)$latestGateLog->action !== 1) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '无有效未离厂的进厂记录,无法核销离厂。请先核销进厂后再离厂']);
}
$enterBizDate = $this->gateLogBizDateForLongTerm($latestGateLog);
if ($enterBizDate !== $today) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '当前未离厂的进厂记录不属于今日,请先核销当日进厂后再离厂']);
}
}
}
$remark = '进厂';
if ($all['type'] == 2) {
$remark = '离厂';
}
$gateLog = GateLog::add($all['admin_id'], $all['code'], $all['person_no'] ?? [], $all['car_no'] ?? [], $remark, $check->id, (int)$all['type'], $today);
if ($all['type'] == 1) {
// 入场
Visit::where('code', $all['code'])->update(['audit_status' => 3, 'person_no' => $all['person_no'] ?? '', 'car_no' => $all['car_no'] ?? '']);
// 通知被访人
$vars = ['date' => $check->date, 'name' => $check->name, 'phone_number' => $check->mobile];
$template_id = 'zPtka4';
$acceptAdmin = Admin::find($check->accept_admin_id);
sms($acceptAdmin->mobile, $vars, $template_id);
}
if ($all['type'] == 2) {
Visit::where('code', $all['code'])->update(['audit_status' => 4]);
}
return $this->success($gateLog);
}
}