增加签署任务查询筛选 #232
@ -623,9 +623,38 @@ class Pages extends Controller
|
||||
}
|
||||
|
||||
try {
|
||||
return CourierAgreementSignature::query()
|
||||
$type = $this->agreementFilterType();
|
||||
if ($type === 'quote-confirmation') {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$query = CourierAgreementSignature::query();
|
||||
$search = $this->agreementFilterSearch();
|
||||
$status = $this->agreementFilterStatus();
|
||||
|
||||
if ($search !== '') {
|
||||
$like = '%' . addcslashes($search, '%_\\') . '%';
|
||||
$query->where(function ($builder) use ($like): void {
|
||||
$builder
|
||||
->where('business_no', 'like', $like)
|
||||
->orWhere('courier_name', 'like', $like)
|
||||
->orWhere('organization_name', 'like', $like)
|
||||
->orWhere('operator_name', 'like', $like)
|
||||
->orWhere('company_full_name', 'like', $like);
|
||||
});
|
||||
}
|
||||
|
||||
if ($status !== null) {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
|
||||
if ($type !== null) {
|
||||
$query->where('document_type', $type);
|
||||
}
|
||||
|
||||
return $query
|
||||
->latest('id')
|
||||
->paginate(50, ['*'], 'agreement_page')->withQueryString();
|
||||
->paginate(50, ['*'], 'agreement_task_page')->withQueryString();
|
||||
} catch (QueryException $exception) {
|
||||
// 迁移尚未执行时仍允许打开页面;发送接口会明确提示数据库未就绪。
|
||||
report($exception);
|
||||
@ -633,6 +662,38 @@ class Pages extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
protected function agreementFilterSearch(): string
|
||||
{
|
||||
return mb_substr(trim((string) request('agreement_search', '')), 0, 191);
|
||||
}
|
||||
|
||||
protected function agreementFilterStatus(): ?string
|
||||
{
|
||||
$status = trim((string) request('agreement_status', ''));
|
||||
$allowed = [
|
||||
CourierAgreementSignature::STATUS_PREPARED,
|
||||
CourierAgreementSignature::STATUS_SENDING,
|
||||
CourierAgreementSignature::STATUS_SIGNING,
|
||||
CourierAgreementSignature::STATUS_COMPLETED,
|
||||
CourierAgreementSignature::STATUS_ARCHIVED,
|
||||
CourierAgreementSignature::STATUS_REVOKED,
|
||||
CourierAgreementSignature::STATUS_EXPIRED,
|
||||
CourierAgreementSignature::STATUS_REJECTED,
|
||||
CourierAgreementSignature::STATUS_FAILED,
|
||||
CourierAgreementSignature::STATUS_UNKNOWN,
|
||||
];
|
||||
|
||||
return in_array($status, $allowed, true) ? $status : null;
|
||||
}
|
||||
|
||||
protected function agreementFilterType(): ?string
|
||||
{
|
||||
$type = trim((string) request('agreement_type', ''));
|
||||
$allowed = array_keys(CourierAgreementSignature::DOCUMENTS);
|
||||
|
||||
return in_array($type, $allowed, true) ? $type : null;
|
||||
}
|
||||
|
||||
protected function agreementSubjects(?string $page): Collection
|
||||
{
|
||||
if ($page !== 'agreement-signing') {
|
||||
@ -658,10 +719,36 @@ class Pages extends Controller
|
||||
}
|
||||
|
||||
try {
|
||||
return CourierQuoteSignature::query()
|
||||
->with('version')
|
||||
$type = $this->agreementFilterType();
|
||||
if ($type !== null && $type !== 'quote-confirmation') {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$query = CourierQuoteSignature::query()->with('version');
|
||||
$search = $this->agreementFilterSearch();
|
||||
$status = $this->agreementFilterStatus();
|
||||
|
||||
if ($search !== '') {
|
||||
$like = '%' . addcslashes($search, '%_\\') . '%';
|
||||
$query->where(function ($builder) use ($like): void {
|
||||
$builder
|
||||
->where('organization_name', 'like', $like)
|
||||
->orWhere('operator_name', 'like', $like)
|
||||
->orWhereHas('version', function ($versionQuery) use ($like): void {
|
||||
$versionQuery
|
||||
->where('version_code', 'like', $like)
|
||||
->orWhere('carrier_name', 'like', $like);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if ($status !== null) {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
|
||||
return $query
|
||||
->latest('id')
|
||||
->paginate(50, ['*'], 'agreement_page')->withQueryString();
|
||||
->paginate(50, ['*'], 'agreement_quote_page')->withQueryString();
|
||||
} catch (QueryException $exception) {
|
||||
// 报价签署迁移尚未执行时仍允许打开协议签署页。
|
||||
report($exception);
|
||||
|
||||
@ -9,6 +9,10 @@
|
||||
@php($agreementCompanyName = $agreementDefaultSubject?->name ?: company()?->name ?: '当前公司')
|
||||
@php($agreementSubjectPayload = $agreementSubjects->map(fn ($subject) => ['id' => $subject->id, 'name' => $subject->name, 'unified_social_credit_code' => $subject->unified_social_credit_code, 'is_default' => (bool) $subject->is_default])->values())
|
||||
@php($esignConfigured = \App\Services\Esign\EsignClient::configured())
|
||||
@php($agreementSearch = trim((string) request('agreement_search', '')))
|
||||
@php($agreementStatus = trim((string) request('agreement_status', '')))
|
||||
@php($agreementType = trim((string) request('agreement_type', '')))
|
||||
@php($agreementFiltersActive = $agreementSearch !== '' || $agreementStatus !== '' || $agreementType !== '')
|
||||
|
||||
<section class="agreement-workspace" aria-label="协议签署" data-agreement-signing>
|
||||
<style>
|
||||
@ -1169,43 +1173,53 @@
|
||||
</div>
|
||||
|
||||
<section class="agreement-panel" id="agreement-panel-tasks" role="tabpanel" aria-labelledby="agreement-tab-tasks" data-agreement-panel="tasks">
|
||||
<div class="agreement-filter-bar">
|
||||
<form class="agreement-filter-bar" method="GET" action="{{ url()->current() }}" data-task-filter-form>
|
||||
<input type="hidden" name="agreement_view" value="tasks">
|
||||
<div class="agreement-field">
|
||||
<label for="agreement-task-search">搜索任务</label>
|
||||
<div class="agreement-search">
|
||||
<span class="material-icons-outlined" aria-hidden="true">search</span>
|
||||
<input class="agreement-control" id="agreement-task-search" type="search" placeholder="任务编号、快递公司或签署人" autocomplete="off" data-task-filter>
|
||||
<input class="agreement-control" id="agreement-task-search" name="agreement_search" type="search" value="{{ $agreementSearch }}" placeholder="任务编号、快递公司或签署人" autocomplete="off" data-task-filter>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agreement-field">
|
||||
<label for="agreement-task-status">签署状态</label>
|
||||
<select class="agreement-control" id="agreement-task-status" data-task-filter>
|
||||
<select class="agreement-control" id="agreement-task-status" name="agreement_status" data-task-filter>
|
||||
<option value="">全部状态</option>
|
||||
<option value="draft">草稿</option>
|
||||
<option value="signing">签署中</option>
|
||||
<option value="completed">已完成</option>
|
||||
<option value="rejected">已拒签</option>
|
||||
<option value="expired">已过期</option>
|
||||
<option value="prepared" @selected($agreementStatus === 'prepared')>待推送</option>
|
||||
<option value="sending" @selected($agreementStatus === 'sending')>推送中</option>
|
||||
<option value="signing" @selected($agreementStatus === 'signing')>待签署</option>
|
||||
<option value="completed" @selected($agreementStatus === 'completed')>签署完成,待归档</option>
|
||||
<option value="archived" @selected($agreementStatus === 'archived')>已签署并归档</option>
|
||||
<option value="revoked" @selected($agreementStatus === 'revoked')>已撤销</option>
|
||||
<option value="rejected" @selected($agreementStatus === 'rejected')>已拒签</option>
|
||||
<option value="expired" @selected($agreementStatus === 'expired')>已过期</option>
|
||||
<option value="failed" @selected($agreementStatus === 'failed')>处理失败</option>
|
||||
<option value="unknown" @selected($agreementStatus === 'unknown')>状态未知</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="agreement-field">
|
||||
<label for="agreement-task-type">业务类型</label>
|
||||
<select class="agreement-control" id="agreement-task-type" data-task-filter>
|
||||
<select class="agreement-control" id="agreement-task-type" name="agreement_type" data-task-filter>
|
||||
<option value="">全部类型</option>
|
||||
<option value="account-change">收款账户变更</option>
|
||||
<option value="account-proof">收款账户证明</option>
|
||||
<option value="entity-change">公司主体变更</option>
|
||||
<option value="shared-contract">共用合同证明</option>
|
||||
<option value="cooperation-contract">合作合同</option>
|
||||
<option value="authorization">授权委托书</option>
|
||||
<option value="quote-confirmation">报价确认单</option>
|
||||
<option value="account-change" @selected($agreementType === 'account-change')>收款账户变更</option>
|
||||
<option value="account-proof" @selected($agreementType === 'account-proof')>收款账户证明</option>
|
||||
<option value="entity-change" @selected($agreementType === 'entity-change')>公司主体变更</option>
|
||||
<option value="shared-contract" @selected($agreementType === 'shared-contract')>共用合同证明</option>
|
||||
<option value="cooperation-contract" @selected($agreementType === 'cooperation-contract')>合作合同</option>
|
||||
<option value="authorization" @selected($agreementType === 'authorization')>授权委托书</option>
|
||||
<option value="quote-confirmation" @selected($agreementType === 'quote-confirmation')>报价确认单</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="agreement-button agreement-button--primary" type="submit">
|
||||
<span class="material-icons-outlined" aria-hidden="true">search</span>
|
||||
查询
|
||||
</button>
|
||||
<button class="agreement-button" type="button" data-task-reset>
|
||||
<span class="material-icons-outlined" aria-hidden="true">restart_alt</span>
|
||||
重置
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="agreement-table-wrap">
|
||||
<table class="agreement-table">
|
||||
@ -1299,8 +1313,13 @@
|
||||
<span class="agreement-empty__icon">
|
||||
<span class="material-icons-outlined" aria-hidden="true">assignment_late</span>
|
||||
</span>
|
||||
<strong>还没有协议签署任务</strong>
|
||||
<p>完成E签宝接入后,发起、签署、拒签和归档记录会在这里统一追踪。</p>
|
||||
@if ($agreementFiltersActive)
|
||||
<strong>没有匹配的签署任务</strong>
|
||||
<p>请调整筛选条件后重新查询。</p>
|
||||
@else
|
||||
<strong>还没有协议签署任务</strong>
|
||||
<p>完成E签宝接入后,发起、签署、拒签和归档记录会在这里统一追踪。</p>
|
||||
@endif
|
||||
<button class="agreement-button agreement-button--primary" type="button" data-start-agreement>
|
||||
<span class="material-icons-outlined" aria-hidden="true">add</span>
|
||||
新建签署任务
|
||||
@ -1317,10 +1336,14 @@
|
||||
@php($quoteTotal = method_exists($agreementQuoteSignatures, 'total') ? $agreementQuoteSignatures->total() : $agreementQuoteSignatures->count())
|
||||
@php($taskPages = method_exists($agreementTasks, 'lastPage') ? $agreementTasks->lastPage() : 0)
|
||||
@php($quotePages = method_exists($agreementQuoteSignatures, 'lastPage') ? $agreementQuoteSignatures->lastPage() : 0)
|
||||
@php($pageSource = $taskPages >= $quotePages ? $agreementTasks : $agreementQuoteSignatures)
|
||||
<div class="agreement-panel-footnote">
|
||||
<span>共 {{ $taskTotal + $quoteTotal }} 条 · 共 {{ $taskTotal + $quoteTotal > 0 ? max($taskPages, $quotePages) : 0 }} 页(每页两类签署记录各最多 50 条)</span>
|
||||
@if (method_exists($pageSource, 'links')) {{ $pageSource->links() }} @endif
|
||||
<span>共 {{ $taskTotal + $quoteTotal }} 条 · 协议任务 {{ $taskTotal }} 条,报价确认单 {{ $quoteTotal }} 条</span>
|
||||
@if (method_exists($agreementTasks, 'links') && $taskPages > 1)
|
||||
<span>协议任务分页:</span>{{ $agreementTasks->links() }}
|
||||
@endif
|
||||
@if (method_exists($agreementQuoteSignatures, 'links') && $quotePages > 1)
|
||||
<span>报价确认单分页:</span>{{ $agreementQuoteSignatures->links() }}
|
||||
@endif
|
||||
</div>
|
||||
<div class="agreement-panel-footnote">
|
||||
<span class="material-icons-outlined" aria-hidden="true">shield</span>
|
||||
@ -2708,10 +2731,12 @@
|
||||
});
|
||||
|
||||
root.querySelector('[data-task-reset]').addEventListener('click', function () {
|
||||
root.querySelectorAll('[data-task-filter]').forEach(function (control) {
|
||||
control.value = '';
|
||||
var url = new URL(window.location.href);
|
||||
['agreement_search', 'agreement_status', 'agreement_type', 'agreement_task_page', 'agreement_quote_page', 'agreement_page'].forEach(function (key) {
|
||||
url.searchParams.delete(key);
|
||||
});
|
||||
announce('筛选条件已重置');
|
||||
url.searchParams.set('agreement_view', 'tasks');
|
||||
window.location.assign(url.toString());
|
||||
});
|
||||
|
||||
window.addEventListener('popstate', function () {
|
||||
|
||||
@ -4,6 +4,9 @@ namespace Tests\Feature\Business;
|
||||
|
||||
use App\Models\Auth\Permission;
|
||||
use App\Models\Business\CourierAgreementSignature;
|
||||
use App\Models\Business\CourierQuoteChange;
|
||||
use App\Models\Business\CourierQuoteSignature;
|
||||
use App\Models\Business\CourierQuoteVersion;
|
||||
use App\Services\Esign\EsignClient;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
@ -120,6 +123,63 @@ class AgreementSigningPageTest extends FeatureTestCase
|
||||
->assertDontSee('<td>张经理</td>', false);
|
||||
}
|
||||
|
||||
public function testAgreementSigningPageFiltersAgreementTasksBySearchStatusAndType(): void
|
||||
{
|
||||
$this->grantWorkspaceAccess();
|
||||
|
||||
$this->createAgreementTask([
|
||||
'business_no' => 'AGR-FILTER-MATCH',
|
||||
'document_type' => 'account-proof',
|
||||
'status' => CourierAgreementSignature::STATUS_ARCHIVED,
|
||||
'courier_name' => '筛选快递',
|
||||
'operator_name' => '筛选签署人',
|
||||
]);
|
||||
$this->createAgreementTask([
|
||||
'business_no' => 'AGR-FILTER-OTHER',
|
||||
'document_type' => 'account-change',
|
||||
'status' => CourierAgreementSignature::STATUS_SIGNING,
|
||||
'courier_name' => '另一家快递',
|
||||
'operator_name' => '另一位签署人',
|
||||
]);
|
||||
|
||||
$this->loginAs()
|
||||
->get(route('business.pages.show', [
|
||||
'section' => 'courier-payables',
|
||||
'page' => 'agreement-signing',
|
||||
'agreement_search' => 'AGR-FILTER-MATCH',
|
||||
'agreement_status' => CourierAgreementSignature::STATUS_ARCHIVED,
|
||||
'agreement_type' => 'account-proof',
|
||||
]))
|
||||
->assertOk()
|
||||
->assertSeeText('AGR-FILTER-MATCH')
|
||||
->assertDontSeeText('AGR-FILTER-OTHER')
|
||||
->assertSee('name="agreement_search"', false)
|
||||
->assertSee('value="AGR-FILTER-MATCH"', false)
|
||||
->assertSee('value="archived" selected', false)
|
||||
->assertSee('value="account-proof" selected', false);
|
||||
}
|
||||
|
||||
public function testAgreementSigningPageFiltersQuoteConfirmationsByVersionCarrierAndStatus(): void
|
||||
{
|
||||
$this->grantWorkspaceAccess();
|
||||
|
||||
$this->createQuoteSignature('QB-FILTER-MATCH', CourierQuoteSignature::STATUS_ARCHIVED, '筛选快递');
|
||||
$this->createQuoteSignature('QB-FILTER-OTHER', CourierQuoteSignature::STATUS_SIGNING, '另一家快递');
|
||||
|
||||
$this->loginAs()
|
||||
->get(route('business.pages.show', [
|
||||
'section' => 'courier-payables',
|
||||
'page' => 'agreement-signing',
|
||||
'agreement_search' => 'QB-FILTER-MATCH',
|
||||
'agreement_status' => CourierQuoteSignature::STATUS_ARCHIVED,
|
||||
'agreement_type' => 'quote-confirmation',
|
||||
]))
|
||||
->assertOk()
|
||||
->assertSeeText('QB-FILTER-MATCH')
|
||||
->assertDontSee('<td>QB-FILTER-OTHER</td>', false)
|
||||
->assertDontSeeText('没有匹配的签署任务');
|
||||
}
|
||||
|
||||
public function testAgreementSigningPageDistinguishesDefinitiveFailureFromUnknownResult(): void
|
||||
{
|
||||
$this->grantWorkspaceAccess();
|
||||
@ -293,4 +353,84 @@ class AgreementSigningPageTest extends FeatureTestCase
|
||||
$this->assertSame(404, $exception->getStatusCode());
|
||||
}
|
||||
}
|
||||
|
||||
private function createAgreementTask(array $overrides = []): CourierAgreementSignature
|
||||
{
|
||||
return CourierAgreementSignature::create(array_merge([
|
||||
'company_id' => $this->company->id,
|
||||
'business_no' => 'AGR-FILTER-' . uniqid(),
|
||||
'idempotency_key' => 'filter-' . uniqid(),
|
||||
'document_type' => 'account-proof',
|
||||
'template_code' => 'TPL-ACCOUNT-CERT-V2',
|
||||
'template_version' => '2',
|
||||
'source_file_name' => '收款账户证明文件.doc',
|
||||
'status' => CourierAgreementSignature::STATUS_SIGNING,
|
||||
'courier_name' => '新北中通',
|
||||
'company_full_name' => $this->company->name,
|
||||
'organization_name' => '新北中通',
|
||||
'operator_name' => '张经理',
|
||||
'operator_account' => '13800138000',
|
||||
'payload' => ['signing_subject' => $this->company->name],
|
||||
'environment' => 'sandbox',
|
||||
'source_pdf_path' => 'courier-agreements/filter/source.pdf',
|
||||
'source_pdf_sha256' => str_repeat('a', 64),
|
||||
'created_by' => $this->user->id,
|
||||
'updated_by' => $this->user->id,
|
||||
], $overrides));
|
||||
}
|
||||
|
||||
private function createQuoteSignature(string $versionCode, string $status, string $carrierName): CourierQuoteSignature
|
||||
{
|
||||
$change = CourierQuoteChange::create([
|
||||
'company_id' => $this->company->id,
|
||||
'number' => 'BG-' . $versionCode,
|
||||
'source_quote_id' => 'QB-SOURCE-' . $versionCode,
|
||||
'source_sheet_name' => $carrierName . '报价',
|
||||
'carrier_name' => $carrierName,
|
||||
'quote_type' => '标准报价',
|
||||
'change_type' => '面单调整',
|
||||
'status' => CourierQuoteChange::STATUS_APPROVED,
|
||||
'effective_date' => now()->toDateString(),
|
||||
'is_retrospective' => false,
|
||||
'recalculation_status' => CourierQuoteChange::RECALCULATION_NOT_REQUIRED,
|
||||
'summary' => '筛选测试报价。',
|
||||
'created_by' => $this->user->id,
|
||||
'approved_by' => $this->user->id,
|
||||
'approved_at' => now(),
|
||||
]);
|
||||
|
||||
$version = CourierQuoteVersion::create([
|
||||
'company_id' => $this->company->id,
|
||||
'version_code' => $versionCode,
|
||||
'source_quote_id' => 'QB-SOURCE-' . $versionCode,
|
||||
'source_change_id' => $change->id,
|
||||
'sheet_name' => $carrierName . '报价',
|
||||
'company_name' => $this->company->name,
|
||||
'carrier_name' => $carrierName,
|
||||
'quote_type' => '标准报价',
|
||||
'effective_date' => now()->toDateString(),
|
||||
'source_range' => 'A1:B2',
|
||||
'values_json' => json_encode([['地区', '面单'], ['广东省', 6.5]], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR),
|
||||
'formulas_json' => json_encode([[], []], JSON_THROW_ON_ERROR),
|
||||
'status' => 'approved',
|
||||
'signature_status' => '未签署',
|
||||
'created_by' => $this->user->id,
|
||||
'approved_by' => $this->user->id,
|
||||
]);
|
||||
|
||||
return CourierQuoteSignature::create([
|
||||
'company_id' => $this->company->id,
|
||||
'quote_version_id' => $version->id,
|
||||
'provider' => 'esign',
|
||||
'environment' => 'sandbox',
|
||||
'status' => $status,
|
||||
'organization_name' => $carrierName,
|
||||
'operator_name' => '筛选签署人',
|
||||
'source_pdf_path' => 'courier-quotes/filter/' . $versionCode . '.pdf',
|
||||
'source_pdf_sha256' => str_repeat('b', 64),
|
||||
'source_pdf_generated_at' => now(),
|
||||
'created_by' => $this->user->id,
|
||||
'updated_by' => $this->user->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user