diff --git a/app/Http/Controllers/Business/CourierAgreementSignatures.php b/app/Http/Controllers/Business/CourierAgreementSignatures.php index d4abc707..7268a088 100644 --- a/app/Http/Controllers/Business/CourierAgreementSignatures.php +++ b/app/Http/Controllers/Business/CourierAgreementSignatures.php @@ -283,11 +283,26 @@ class CourierAgreementSignatures extends Controller ->with('success', "{$signature->documentLabel()}状态已同步:{$signature->statusLabel()}。"); } - public function archive(CourierAgreementSignature $agreementSignature): RedirectResponse + public function archive(Request $request, CourierAgreementSignature $agreementSignature): RedirectResponse { $this->ensureCanManage(); abort_unless($agreementSignature->status === CourierAgreementSignature::STATUS_COMPLETED && $agreementSignature->remote_status === '2', 422, '只有签署完成的协议可以人工归档。'); - $signature = $this->service->archive($agreementSignature, null, user_id()); + $data = $request->validate([ + 'archive_reason_code' => ['required', 'in:completed,confirmed,backfill,other'], + 'archive_note' => ['nullable', 'string', 'max:200'], + ]); + $reasonLabel = match ($data['archive_reason_code']) { + 'completed' => '签署完成', + 'confirmed' => '财务已确认', + 'backfill' => '历史补录', + 'other' => '其他', + }; + $note = trim((string) ($data['archive_note'] ?? '')); + if ($data['archive_reason_code'] === 'other' && $note === '') { + throw ValidationException::withMessages(['archive_note' => '选择“其他”时请填写归档说明。']); + } + $reason = $note === '' ? $reasonLabel : $reasonLabel . ':' . $note; + $signature = $this->service->archive($agreementSignature, null, user_id(), $reason); return redirect() ->route('business.pages.show', ['section' => 'courier-payables', 'page' => 'agreement-signing', 'agreement_view' => 'tasks']) diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index 388c7f3a..730de895 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -2206,6 +2206,13 @@ class Pages extends Controller $query = CourierAgreementSignature::query(); $search = $this->agreementFilterSearch(); $status = $this->agreementFilterStatus(); + $scope = $this->agreementFilterScope(); + + if ($scope === 'archived') { + $query->where('status', CourierAgreementSignature::STATUS_ARCHIVED); + } elseif ($scope === 'active') { + $query->where('status', '<>', CourierAgreementSignature::STATUS_ARCHIVED); + } if ($search !== '') { $like = '%' . addcslashes($search, '%_\\') . '%'; @@ -2269,6 +2276,14 @@ class Pages extends Controller return in_array($type, $allowed, true) ? $type : null; } + protected function agreementFilterScope(): string + { + $default = request('agreement_status') === CourierAgreementSignature::STATUS_ARCHIVED ? 'archived' : 'active'; + $scope = trim((string) request('agreement_scope', $default)); + + return in_array($scope, ['active', 'archived', 'all'], true) ? $scope : 'active'; + } + protected function agreementSubjects(?string $page): Collection { if ($page !== 'agreement-signing') { @@ -2302,6 +2317,13 @@ class Pages extends Controller $query = CourierQuoteSignature::query()->with('version'); $search = $this->agreementFilterSearch(); $status = $this->agreementFilterStatus(); + $scope = $this->agreementFilterScope(); + + if ($scope === 'archived') { + $query->where('status', CourierQuoteSignature::STATUS_ARCHIVED); + } elseif ($scope === 'active') { + $query->where('status', '<>', CourierQuoteSignature::STATUS_ARCHIVED); + } if ($search !== '') { $like = '%' . addcslashes($search, '%_\\') . '%'; diff --git a/app/Models/Business/CourierAgreementSignature.php b/app/Models/Business/CourierAgreementSignature.php index 59190e8a..ce9b63a8 100644 --- a/app/Models/Business/CourierAgreementSignature.php +++ b/app/Models/Business/CourierAgreementSignature.php @@ -158,4 +158,13 @@ class CourierAgreementSignature extends Model return in_array($mode, ['auto', 'manual'], true) ? $mode : 'auto'; } + + public function archiveReason(): string + { + try { + return trim((string) data_get($this->payload, 'archive_reason', '')); + } catch (\Throwable) { + return ''; + } + } } diff --git a/app/Support/CourierAgreementSignatureService.php b/app/Support/CourierAgreementSignatureService.php index d6bb27e8..46dbcfca 100644 --- a/app/Support/CourierAgreementSignatureService.php +++ b/app/Support/CourierAgreementSignatureService.php @@ -211,7 +211,7 @@ final class CourierAgreementSignatureService }); } - public function archive(CourierAgreementSignature $signature, ?EsignClient $client = null, ?int $userId = null): CourierAgreementSignature + public function archive(CourierAgreementSignature $signature, ?EsignClient $client = null, ?int $userId = null, ?string $archiveReason = null): CourierAgreementSignature { if ($signature->status === CourierAgreementSignature::STATUS_ARCHIVED) { return $signature; @@ -246,6 +246,17 @@ final class CourierAgreementSignatureService 'last_error' => null, 'updated_by' => $userId ?: (int) $signature->updated_by, ]; + $archiveReason ??= '签署完成后自动归档'; + if (filled($archiveReason)) { + try { + $payload = $signature->payload ?? []; + $payload['archive_reason'] = mb_substr(trim($archiveReason), 0, 255); + $archive['payload'] = $payload; + } catch (Throwable) { + // Preserve unreadable historical payloads; retiring an APP_KEY + // must not prevent downloading and archiving a completed flow. + } + } if ($certificateBytes !== null) { $archive['certificate_path'] = $certificatePath; $archive['certificate_sha256'] = hash('sha256', $certificateBytes); diff --git a/resources/views/business/pages/courier-payables/agreement-signing.blade.php b/resources/views/business/pages/courier-payables/agreement-signing.blade.php index 97b0a171..61a7e6d5 100644 --- a/resources/views/business/pages/courier-payables/agreement-signing.blade.php +++ b/resources/views/business/pages/courier-payables/agreement-signing.blade.php @@ -12,6 +12,8 @@ @php($agreementSearch = trim((string) request('agreement_search', ''))) @php($agreementStatus = trim((string) request('agreement_status', ''))) @php($agreementType = trim((string) request('agreement_type', ''))) +@php($agreementScope = trim((string) request('agreement_scope', $agreementStatus === 'archived' ? 'archived' : 'active'))) +@php($agreementScope = in_array($agreementScope, ['active', 'archived', 'all'], true) ? $agreementScope : 'active') @php($agreementFiltersActive = $agreementSearch !== '' || $agreementStatus !== '' || $agreementType !== '')
@@ -198,6 +200,77 @@ border-bottom: 1px solid var(--agreement-border); } + .agreement-scope-bar { + display: flex; + flex-wrap: wrap; + gap: 1rem; + padding: 0 1rem; + border-bottom: 1px solid var(--agreement-border); + } + + .agreement-scope-link { + display: inline-flex; + align-items: center; + gap: 0.35rem; + min-height: 42px; + border-bottom: 3px solid transparent; + color: var(--agreement-muted); + font-size: 0.75rem; + font-weight: 600; + text-decoration: none; + } + + .agreement-scope-link:hover, + .agreement-scope-link[aria-current="page"] { + color: var(--agreement-accent); + } + + .agreement-scope-link[aria-current="page"] { + border-bottom-color: var(--agreement-gold); + } + + .agreement-scope-link:focus-visible { + outline: 2px solid var(--agreement-accent); + outline-offset: 2px; + } + + .agreement-scope-link .material-icons-outlined { + font-size: 17px; + } + + .agreement-archive-meta { + display: block; + max-width: 24rem; + margin-top: 0.35rem; + color: var(--agreement-muted); + font-size: 0.75rem; + line-height: 1.4; + white-space: normal; + overflow-wrap: anywhere; + } + + .agreement-archive-meta strong { + margin-right: 0.4rem; + color: var(--agreement-ink); + font-weight: 600; + } + + .agreement-button--compact { + min-height: 30px; + height: 30px; + padding: 0 0.6rem; + font-size: 0.75rem; + } + + .agreement-task-error:not(:empty) { + margin: 0.75rem 1rem; + } + + .agreement-archive-dialog .agreement-modal__body { + display: grid; + gap: 1rem; + } + .agreement-field { display: grid; gap: 0.35rem; @@ -1231,8 +1304,19 @@
+ @php($scopeQuery = request()->except(['agreement_scope', 'agreement_status', 'agreement_task_page', 'agreement_quote_page'])) + +
+
@@ -1302,6 +1390,10 @@ 报价确认单 {{ $quoteSignature->statusLabel() }} + @if ($quoteSignature->status === 'archived') + 归档时间{{ optional($quoteSignature->archived_at)->format('Y-m-d H:i') ?: '时间未知' }} + 归档说明签署完成后自动归档 + @endif @if ($quoteError) {{ $quoteError }} @endif @@ -1336,6 +1428,10 @@ {{ $task->documentLabel() }} {{ $task->statusLabel() }} + @if ($task->status === 'archived') + 归档时间{{ optional($task->archived_at)->format('Y-m-d H:i') ?: '时间未知' }} + 归档说明{{ $task->archiveReason() ?: '未记录说明' }} + @endif @if ($taskError) {{ $taskError }} @endif @@ -1349,10 +1445,12 @@ @endif - @if ($task->status === 'completed' && $task->archiveMode() === 'manual') -
+ @if ($task->status === 'completed' && $task->remote_status === '2' && $task->archiveMode() === 'manual') + @csrf - + + +
@endif @if ($task->source_pdf_path) @@ -1361,6 +1459,9 @@ @if ($task->signed_pdf_path) @endif + @if ($task->certificate_path) + + @endif @endforeach @@ -1375,8 +1476,8 @@ 没有匹配的签署任务

请调整筛选条件后重新查询。

@else - 还没有协议签署任务 -

完成E签宝接入后,发起、签署、拒签和归档记录会在这里统一追踪。

+ {{ $agreementScope === 'archived' ? '还没有已归档任务' : ($agreementScope === 'active' ? '暂无待处理任务' : '还没有协议签署任务') }} +

{{ $agreementScope === 'archived' ? '完成归档后可在这里检索和下载签署文件。' : '可新建签署任务,或在“全部任务”和“已归档”中查找历史文件。' }}

@endif
+ +