CaiWuXiTong/app/Http/Controllers/Business/SupplierNegativeAlertDingTalkForms.php
苏木 79fad431df feat: 自动推送负数预警并支持处理截图 (#742)
每日自动推送未处理供应商,增加处理截止时间和截图上传。
Co-authored-by: 苏木 <594326199@qq.com>
Co-committed-by: 苏木 <594326199@qq.com>
2026-10-28 16:32:21 +08:00

140 lines
5.6 KiB
PHP

<?php
namespace App\Http\Controllers\Business;
use App\Models\Common\Company;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
use Modules\EcommerceFinance\Services\SupplierNegativeAlertDingTalkWorkflow;
use Symfony\Component\HttpFoundation\StreamedResponse;
class SupplierNegativeAlertDingTalkForms extends Controller
{
public function __construct(
private readonly SupplierNegativeAlertDingTalkWorkflow $workflow,
) {
}
public function show(string $token): View|Response
{
$assignment = $this->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);
}
}