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); } }