resolve($token); if ($assignment === null) { return $this->unavailable(); } $snapshot = json_decode((string) $assignment->snapshot, true); $snapshot = is_array($snapshot) ? array_values($snapshot) : []; $latestPeriod = $this->workflow->latestPeriodSummary($snapshot); return view('business.pages.negative-alerts.dingtalk-task', [ 'assignment' => $assignment, 'suppliers' => $snapshot, 'latestPeriod' => $latestPeriod, 'processingDeadline' => $this->workflow->processingDeadline((string) $latestPeriod['month']), 'supplierResults' => $this->workflow->resultsForAssignment((int) $assignment->id), 'supplierAttachments' => $this->workflow->attachmentsForAssignment((int) $assignment->id), 'token' => $token, ]); } public function update(Request $request, string $token): RedirectResponse|Response { $assignment = $this->resolve($token); if ($assignment === null) { return $this->unavailable(); } $snapshot = json_decode((string) $assignment->snapshot, true); $supplierCount = is_array($snapshot) ? count($snapshot) : 0; $data = $request->validate([ 'results' => ['required', 'array', 'size:' . $supplierCount], 'results.*' => ['nullable', 'string', 'max:2000'], 'screenshots' => ['nullable', 'array', 'size:' . $supplierCount], 'screenshots.*' => ['nullable', 'array', 'max:5'], 'screenshots.*.*' => ['file', 'mimes:jpg,jpeg,png,webp', 'max:10240'], ], [ 'results.required' => '请为每个供应商填写处理结果。', 'results.size' => '请为每个供应商填写处理结果。', 'results.*.max' => '单个供应商的处理结果不能超过 2000 个字符。', 'screenshots.*.*.mimes' => '处理截图仅支持 JPG、PNG、WEBP 图片。', 'screenshots.*.*.max' => '单张处理截图不能超过 10MB。', ]); if (! collect((array) $data['results'])->contains( static fn (mixed $value): bool => trim((string) $value) !== '', )) { throw ValidationException::withMessages([ 'results' => '请至少填写一个供应商的处理结果。', ]); } $uploads = []; foreach ((array) $request->file('screenshots', []) as $index => $files) { $uploads[(int) $index] = array_values(array_filter( (array) $files, static fn (mixed $file): bool => $file !== null, )); } $existingAttachments = $this->workflow->attachmentsForAssignment((int) $assignment->id); foreach (is_array($snapshot) ? $snapshot : [] as $index => $supplier) { $supplierName = trim((string) ($supplier['supplier'] ?? '')); $existingCount = count($existingAttachments[$supplierName] ?? []); if ($existingCount + count($uploads[$index] ?? []) > 5) { throw ValidationException::withMessages([ 'screenshots' => $supplierName . ' 最多上传 5 张处理截图。', ]); } } try { $this->workflow->submit($token, array_values((array) $data['results'])); $this->workflow->storeAttachments($assignment, is_array($snapshot) ? $snapshot : [], $uploads); } catch (ValidationException $exception) { throw $exception; } return redirect() ->route('supplier-negative-alert.task', ['token' => $token]) ->with('negative_alert_success', '已保存本次填写的供应商处理结果,未填写的供应商会继续留在待处理任务中。'); } public function attachment(string $token, int $attachment): StreamedResponse|Response { $assignment = $this->resolve($token); if ($assignment === null) { return $this->unavailable(); } $file = $this->workflow->attachmentForAssignment((int) $assignment->id, $attachment); abort_if($file === null, 404); $disk = Storage::disk('uploads'); abort_unless($disk->exists((string) $file->path), 404, '处理截图不存在。'); return $disk->response((string) $file->path, (string) $file->original_name, [ 'Cache-Control' => 'private, no-store', 'Content-Type' => (string) $file->mime_type, 'X-Content-Type-Options' => 'nosniff', ]); } private function resolve(string $token): ?object { $assignment = $this->workflow->findByToken($token); if ($assignment === null) { return null; } Company::find($assignment->company_id)?->makeCurrent(); return $assignment; } private function unavailable(): Response { return response()->view('business.pages.negative-alerts.dingtalk-task-unavailable', [], 404); } }