修复签署文件日期未生成 #225

Merged
limeng merged 2 commits from fix/limeng-signature-date-variable into main 2026-09-21 10:02:38 +08:00
12 changed files with 181 additions and 35 deletions

View File

@ -278,6 +278,7 @@ class CourierAgreementSignatures extends Controller
'quote_version_id' => ['required_if:document_type,quote-confirmation', 'nullable', 'integer', 'min:1'],
'signing_subject' => ['required', 'string', 'min:2', 'max:255'],
'company_full_name' => ['required', 'string', 'min:2', 'max:255'],
'signature_date' => ['required', 'date'],
'signing_deadline' => $date,
'esign_account' => ['required', 'string', 'max:255'],
'secondary_esign_account' => ['required_if:document_type,entity-change,cooperation-contract,authorization', 'nullable', 'string', 'max:255'],
@ -299,12 +300,10 @@ class CourierAgreementSignatures extends Controller
'entity_change_date' => ['required_if:document_type,entity-change', 'date'],
'changed_company_name' => ['required_if:document_type,entity-change', 'string', 'max:255'],
'changed_company_start_date' => ['required_if:document_type,entity-change', 'date'],
'entity_proof_date' => ['required_if:document_type,entity-change', 'date'],
'contract_partner_name' => ['required_if:document_type,shared-contract', 'string', 'max:255'],
'shared_courier_names' => ['nullable', 'array', 'min:1'],
'shared_courier_names.*' => ['required', 'string', 'max:255'],
'shared_courier_name' => ['nullable', 'string', 'max:255'],
'shared_proof_date' => ['nullable', 'date'],
'shared_note' => ['nullable', 'string', 'max:2000'],
'cooperation_party_a_name' => ['required_if:document_type,cooperation-contract', 'string', 'max:255'],
'cooperation_party_a_phone' => ['required_if:document_type,cooperation-contract', 'string', 'max:64'],
@ -342,7 +341,7 @@ class CourierAgreementSignatures extends Controller
->findOrFail((int) $data['quote_version_id']);
$this->ensureQuoteVersionMatchesCourier($version, $data['courier_name']);
$signature = $this->quoteSignatureService->prepareVersion($version, user_id());
$signature = $this->quoteSignatureService->prepareVersion($version, user_id(), $data['signature_date']);
$account = trim((string) $data['esign_account']);
$signature = $this->quoteSignatureService->send($signature, [
'organization_name' => $version->carrier_name,

View File

@ -19,11 +19,14 @@ class CourierQuoteSignatures extends Controller
{
}
public function prepare(CourierQuoteVersion $quoteVersion): RedirectResponse
public function prepare(Request $request, CourierQuoteVersion $quoteVersion): RedirectResponse
{
$this->ensureCanManage();
abort_unless($quoteVersion->status === 'approved', 422, '只有已审核报价版本可以生成签署文件。');
$signature = $this->service->prepareVersion($quoteVersion, user_id());
$data = $request->validate([
'signature_date' => ['required', 'date'],
]);
$signature = $this->service->prepareVersion($quoteVersion, user_id(), $data['signature_date']);
return redirect()->route('business.courier-quote-changes.show', $quoteVersion->source_change_id)
->with('success', "{$quoteVersion->version_code} 的签署 PDF 已生成,文件校验值已固定。")
@ -40,9 +43,15 @@ class CourierQuoteSignatures extends Controller
'operator_name' => ['required', 'string', 'min:2', 'max:100'],
'operator_account' => ['required', 'string', 'max:255'],
'notice_type' => ['required', 'in:1,2'],
'signature_date' => ['required', 'date'],
'expires_at' => ['required', 'date', 'after:now', 'before_or_equal:' . now()->addDays(90)->toDateTimeString()],
]);
$this->validateAccountForNotice($data['operator_account'], $data['notice_type']);
$quoteSignature = $this->service->prepareVersion(
$quoteSignature->version,
user_id(),
$data['signature_date'],
);
$signature = $this->service->send($quoteSignature, $data, user_id());
$environment = $signature->environment === 'sandbox' ? '沙箱' : '正式环境';

View File

@ -3,17 +3,26 @@
namespace App\Support;
use App\Models\Business\CourierAgreementSignature;
use Illuminate\Support\Carbon;
use RuntimeException;
final class CourierAgreementPdf
{
public function render(CourierAgreementSignature $signature): string
{
$payload = $signature->payload ?? [];
$signatureDate = (string) ($payload['signature_date'] ?? $payload['entity_proof_date'] ?? $payload['shared_proof_date'] ?? '');
try {
$signatureDate = $signatureDate !== '' ? Carbon::parse($signatureDate)->format('Y年n月j日') : '待填写';
} catch (\Throwable) {
$signatureDate = '待填写';
}
$html = view('business.pdf.courier-agreement', [
'signature' => $signature,
'type' => $signature->document_type,
'title' => $this->title($signature),
'payload' => $signature->payload ?? [],
'payload' => $payload,
'signatureDate' => $signatureDate,
'fontPath' => str_replace('\\', '/', resource_path('fonts/DroidSansFallback.ttf')),
])->render();

View File

@ -3,6 +3,7 @@
namespace App\Support;
use App\Models\Business\CourierQuoteVersion;
use Illuminate\Support\Carbon;
use RuntimeException;
final class CourierQuotePdf
@ -11,9 +12,10 @@ final class CourierQuotePdf
{
}
public function render(CourierQuoteVersion $version): string
public function render(CourierQuoteVersion $version, ?string $signatureDate = null): string
{
$data = $this->confirmation->data($version);
$signatureDate = Carbon::parse($signatureDate ?: now())->format('Y年n月j日');
$html = view('business.pdf.courier-quote', [
'version' => $version,
'change' => $version->change,
@ -23,6 +25,7 @@ final class CourierQuotePdf
'expiryDate' => $data['expiry_date'],
'faceFee' => $data['face_fee'],
'bubbleCoefficient' => $data['bubble_coefficient'],
'signatureDate' => $signatureDate,
'generatedAt' => now(),
'fontPath' => str_replace('\\', '/', resource_path('fonts/DroidSansFallback.ttf')),
])->render();

View File

@ -18,11 +18,11 @@ final class CourierQuoteSignatureService
{
}
public function prepareVersion(CourierQuoteVersion $version, int $userId): CourierQuoteSignature
public function prepareVersion(CourierQuoteVersion $version, int $userId, ?string $signatureDate = null): CourierQuoteSignature
{
$existing = $version->signature;
if ($existing && $this->sourcePdfIsIntact($existing)) {
if ($signatureDate === null && $existing && $this->sourcePdfIsIntact($existing)) {
return $existing;
}
@ -32,7 +32,7 @@ final class CourierQuoteSignatureService
]);
}
$bytes = $this->pdf->render($version);
$bytes = $this->pdf->render($version, $signatureDate);
$path = $this->path($version, 'source.pdf');
$sha256 = hash('sha256', $bytes);

View File

@ -1394,6 +1394,11 @@
<label for="agreement-deadline">签署截止日期 <span class="agreement-required" aria-hidden="true">*</span></label>
<input class="agreement-control" id="agreement-deadline" name="signing_deadline" type="date" min="{{ now()->toDateString() }}" value="{{ now()->addDays(7)->toDateString() }}" required>
</div>
<div class="agreement-field">
<label for="agreement-signature-date">签署日期 <span class="agreement-required" aria-hidden="true">*</span></label>
<input class="agreement-control" id="agreement-signature-date" name="signature_date" type="date" value="{{ old('signature_date', now()->toDateString()) }}" required>
<p class="agreement-help">该日期会直接写入待签文件,并随原文一起推送到 E 签宝。</p>
</div>
<div class="agreement-field">
<label for="agreement-esign-account"><span data-primary-esign-label>对方 E 签宝账号</span></label>
<input class="agreement-control" id="agreement-esign-account" name="esign_account" type="text" value="" data-esign-account placeholder="手机号或邮箱" autocomplete="email" required>
@ -1499,10 +1504,6 @@
<label for="agreement-changed-company-start-date">新公司启用日期 <span class="agreement-required" aria-hidden="true">*</span></label>
<input class="agreement-control" id="agreement-changed-company-start-date" name="changed_company_start_date" type="date" value="{{ now()->toDateString() }}" required>
</div>
<div class="agreement-field">
<label for="agreement-entity-proof-date">证明日期 <span class="agreement-required" aria-hidden="true">*</span></label>
<input class="agreement-control" id="agreement-entity-proof-date" name="entity_proof_date" type="date" value="{{ now()->toDateString() }}" required>
</div>
</div>
<div class="agreement-form-grid" data-template-fields="shared-contract" hidden>
@ -1649,7 +1650,7 @@
<p>以上收款账户的变更,以我司的盖章为有效。</p>
<div class="agreement-preview-signature">
<p><span data-preview-bind="proofSigner">待填写</span>:</p>
<p>日期:<span data-preview-bind="signatureDate">签署后生成</span></p>
<p>日期:<span data-preview-bind="signatureDate">{{ now()->format('Y年n月j日') }}</span></p>
</div>
<p class="agreement-preview-page-number">第 1 页 / 共 1 页</p>
</article>
@ -1667,7 +1668,7 @@
<p>以上收款账户的变更,以我司的盖章为有效。</p>
<div class="agreement-preview-signature">
<p><span data-preview-bind="changeSigner">待填写</span>:</p>
<p>日期:<span data-preview-bind="signatureDate">签署后生成</span></p>
<p>日期:<span data-preview-bind="signatureDate">{{ now()->format('Y年n月j日') }}</span></p>
</div>
<p class="agreement-preview-page-number">第 1 页 / 共 1 页</p>
</article>
@ -1681,7 +1682,7 @@
<div class="agreement-preview-signature">
<p>变更前公司法人签字盖公章:</p>
<p>变更后公司法人签字盖公章:</p>
<p><span data-preview-bind="entityProofDate">待填写</span></p>
<p>日期:<span data-preview-bind="signatureDate">{{ now()->format('Y年n月j日') }}</span></p>
</div>
<p class="agreement-preview-page-number">第 1 页 / 共 1 页</p>
</article>
@ -1693,7 +1694,7 @@
<p data-preview-shared-note hidden>备注:<span class="agreement-preview-value" data-preview-bind="sharedNote">待填写</span></p>
<div class="agreement-preview-signature">
<p>法人签字并公司盖章:</p>
<p>日期:签署后生成</p>
<p>日期:<span data-preview-bind="signatureDate">{{ now()->format('Y年n月j日') }}</span></p>
</div>
<p class="agreement-preview-page-number">第 1 页 / 共 1 页</p>
</article>
@ -1725,7 +1726,7 @@
<p>受托方:<span data-preview-bind="trusteeName">待填写</span></p>
</div>
<div class="agreement-preview-signature">
<p>日期:<span data-preview-bind="signatureDate">签署后生成</span></p>
<p>日期:<span data-preview-bind="signatureDate">{{ now()->format('Y年n月j日') }}</span></p>
</div>
<p class="agreement-preview-page-number">第 1 页 / 共 1 页</p>
</article>
@ -1745,7 +1746,7 @@
</div>
<p>双方确认以下报价版本作为快递服务结算依据。报价版本一经签署即形成独立留档,后续变价须生成新版本并重新签署。</p>
<div class="agreement-preview-quote-matrix" data-preview-bind="quoteMatrix">请选择报价版本后查看报价明细</div>
<div class="agreement-preview-signature"><p>乙方(快递方)签章:</p><p>日期:签署后生成</p></div>
<div class="agreement-preview-signature"><p>乙方(快递方)签章:</p><p>日期:<span data-preview-bind="signatureDate">{{ now()->format('Y年n月j日') }}</span></p></div>
<p class="agreement-preview-page-number">报价确认单 · 快递方单方签署</p>
</article>
</div>
@ -2421,7 +2422,7 @@
setText('[data-preview-template-code]', documentOption.dataset.code);
setPreviewValue('company', companyName);
setPreviewValue('courier', courier);
setPreviewValue('signatureDate', '签署后生成');
setPreviewValue('signatureDate', formatDateField('[name="signature_date"]', '待填写'));
if (templateType === 'account-proof') {
setPreviewValue('proofEffectiveDate', formatDateField('[name="account_proof_effective_date"]', '待填写'));
@ -2450,7 +2451,6 @@
setPreviewValue('entityChangeDate', formatDateField('[name="entity_change_date"]', '待填写'));
setPreviewValue('changedCompanyName', fieldValue('[name="changed_company_name"]', '待填写'));
setPreviewValue('changedCompanyStartDate', formatDateField('[name="changed_company_start_date"]', '待填写'));
setPreviewValue('entityProofDate', formatDateField('[name="entity_proof_date"]', '待填写'));
}
if (templateType === 'shared-contract') {

View File

@ -72,7 +72,7 @@
<div class="quote-review__body">
@if(!$quoteSignature)
<div class="quote-review__hint">该历史报价版本尚未生成签署原文。</div>
@if($canManageSignature)<form method="POST" action="{{ route('business.courier-quote-signatures.prepare',$change->version) }}" style="margin-top:12px">@csrf<button class="quote-review__button quote-review__button--primary" type="submit"><span class="material-icons-outlined" aria-hidden="true">picture_as_pdf</span><span>生成签署 PDF</span></button></form>@endif
@if($canManageSignature)<form class="quote-review__form" method="POST" action="{{ route('business.courier-quote-signatures.prepare',$change->version) }}">@csrf<div class="quote-review__form-grid"><div class="quote-review__field"><label for="prepare_signature_date">签署日期</label><input class="quote-review__control" id="prepare_signature_date" name="signature_date" type="date" value="{{ old('signature_date',now()->toDateString()) }}" required></div></div><div class="quote-review__form-actions"><span class="quote-review__hint">日期将直接写入待签原文。</span><button class="quote-review__button quote-review__button--primary" type="submit"><span class="material-icons-outlined" aria-hidden="true">picture_as_pdf</span><span>生成签署 PDF</span></button></div></form>@endif
@else
<dl class="quote-review__signature-meta">
<div><dt>签署状态</dt><dd><span class="quote-review__status">{{ $quoteSignature->statusLabel() }}</span></dd></div>
@ -97,6 +97,7 @@
<div class="quote-review__field"><label for="organization_name">签署企业全称</label><input class="quote-review__control" id="organization_name" name="organization_name" value="{{ old('organization_name',$quoteSignature->organization_name) }}" maxlength="255" required></div>
<div class="quote-review__field"><label for="operator_name">经办人姓名</label><input class="quote-review__control" id="operator_name" name="operator_name" value="{{ old('operator_name',$quoteSignature->operator_name) }}" maxlength="100" autocomplete="name" required></div>
<div class="quote-review__field"><label for="operator_account">手机号或邮箱</label><input class="quote-review__control" id="operator_account" name="operator_account" value="{{ old('operator_account',$quoteSignature->operator_account) }}" maxlength="255" autocomplete="email" required></div>
<div class="quote-review__field"><label for="signature_date">签署日期</label><input class="quote-review__control" id="signature_date" name="signature_date" type="date" value="{{ old('signature_date',now()->toDateString()) }}" required></div>
<div class="quote-review__field"><label for="expires_at">签署截止时间</label><input class="quote-review__control" id="expires_at" name="expires_at" type="datetime-local" value="{{ old('expires_at',now()->addDays(7)->format('Y-m-d\TH:i')) }}" required></div>
</div>
<div class="quote-review__form-actions">

View File

@ -128,10 +128,10 @@
<p>乙 方:<span class="agreement-preview-value" data-preview-bind="cooperationPartyBName">待填写</span></p>
<p>代表人:</p>
<p>代表人:</p>
<p>日 期:<span data-preview-bind="signatureDate">签署后生成</span></p>
<p>日 期:<span data-preview-bind="signatureDate">签署后生成</span></p>
<p>日 期:<span data-preview-bind="signatureDate">待填写</span></p>
<p>日 期:<span data-preview-bind="signatureDate">待填写</span></p>
<p>签约地:常州市</p>
</div>
@else
<div class="sign"><p>甲 方:{!! $mixedText($payload['cooperation_party_a_name'] ?? '') !!}</p><p>乙 方:{!! $mixedText($payload['cooperation_party_b_name'] ?? '') !!}</p><p>代表人:</p><p>代表人:</p><p>日 期:签署后生成</p><p>日 期:签署后生成</p><p>签约地:常州市</p></div>
<div class="sign"><p>甲 方:{!! $mixedText($payload['cooperation_party_a_name'] ?? '') !!}</p><p>乙 方:{!! $mixedText($payload['cooperation_party_b_name'] ?? '') !!}</p><p>代表人:</p><p>代表人:</p><p>日 期:{!! $mixedText($signatureDate) !!}</p><p>日 期:{!! $mixedText($signatureDate) !!}</p><p>签约地:常州市</p></div>
@endif

View File

@ -10,7 +10,7 @@
h1 { text-align: center; font-size: 20px; margin: 0 0 24px; }
h2 { font-size: 14px; margin: 18px 0 8px; }
h4 { font-size: 14px; margin: 18px 0 8px; }
p { margin: 0 0 12px; }
p { margin: 0 0 12px; overflow-wrap: anywhere; word-break: break-all; }
.meta { color: #595959; font-size: 11px; text-align: right; margin-bottom: 20px; }
.account { margin: 14px 0 20px 28px; }
.account div { margin: 6px 0; }
@ -57,7 +57,7 @@
</div>
<p>请贵公司按照上述汇款资料付款,如因我司提供收款银行帐户资料错误,导致相关款项支付不到位,则由此产生的一切经济纠纷和责任由我司承担。我司承诺若上述收款银行帐户资料发生变化及时书面通知贵公司。</p>
<p>以上收款账户的变更,以我司的盖章为有效。</p>
<div class="sign"><p>公司盖章:</p><p>日期:签署后生成</p></div>
<div class="sign"><p>公司盖章:</p><p>日期:{!! $mixedText($signatureDate) !!}</p></div>
@elseif ($type === 'account-change')
<p><span class="latin">TO</span> <strong>{!! $mixedText($payload['company_full_name']) !!}</strong>:</p>
<p>基于我公司正规化管理的需要,同时保证广大客户的汇款安全,我司收款账户将于 {!! $mixedText($payload['account_change_date']) !!} 变更账户,之前相应的账户(收款账户为:{!! $mixedText($payload['old_account_name']) !!},账号:{!! $mixedText($payload['old_account_number']) !!})于 {!! $mixedText($payload['old_account_stop_date']) !!} 停止使用,即日起我司收款的银行帐户进行变更,自 {!! $mixedText($payload['new_account_effective_date']) !!} 起我司与贵司往来款项请汇款至以下新账户,新汇款资料如下:</p>
@ -67,20 +67,20 @@
<div>银行账户:{!! $mixedText($payload['new_bank_account']) !!}</div>
</div>
<p>如因我司提供收款银行帐户资料错误导致相关款项支付不到位,由此产生的一切经济纠纷和责任由我司承担。以上收款账户的变更,以我司的盖章为有效。</p>
<div class="sign"><p>公司盖章:</p><p>日期:签署后生成</p></div>
<div class="sign"><p>公司盖章:</p><p>日期:{!! $mixedText($signatureDate) !!}</p></div>
@elseif ($type === 'entity-change')
<p>致 <strong>{!! $mixedText($payload['company_full_name']) !!}</strong>:</p>
<p>我司由于企业名称变更,原公司名称 {!! $mixedText($payload['former_company_name']) !!} 于 {!! $mixedText($payload['entity_change_date']) !!} 变更为 {!! $mixedText($payload['changed_company_name']) !!},自 {!! $mixedText($payload['changed_company_start_date']) !!} 开始,我司将启用变更后的公司与贵公司开展工作,原公司名称停止使用。</p>
<p>变更后,原公司的各项事宜由变更后的公司继续执行。原公司的结算未付款项,由变更之后的公司提供的收款账户收款。原公司的业务和款项若出现法律上的纠纷,由变更后的公司承担责任。</p>
<p>特此证明!</p>
<div class="sign"><p>变更前公司法人签字盖公章:{!! $mixedText($payload['former_company_name']) !!}</p><p>变更后公司法人签字盖公章:{!! $mixedText($payload['changed_company_name']) !!}</p><p>日期:{!! $mixedText($payload['entity_proof_date']) !!}</p></div>
<div class="sign"><p>变更前公司法人签字盖公章:{!! $mixedText($payload['former_company_name']) !!}</p><p>变更后公司法人签字盖公章:{!! $mixedText($payload['changed_company_name']) !!}</p><p>日期:{!! $mixedText($signatureDate) !!}</p></div>
@elseif ($type === 'shared-contract')
<p>与 {!! $mixedText($payload['contract_partner_name'] ?? '') !!} 合作的 {!! $mixedText($sharedCourierText) !!} 共同用一份合同,打款资料为同一份资料,同一个签字人以及同一个法人!</p>
<p>特此证明!</p>
@if (filled($payload['shared_note'] ?? null))
<p>备注:{!! $mixedText($payload['shared_note']) !!}</p>
@endif
<div class="sign"><p>法人签字并公司盖章:</p><p>日期:{!! $mixedText($payload['shared_proof_date'] ?? '签署后生成') !!}</p></div>
<div class="sign"><p>法人签字并公司盖章:</p><p>日期:{!! $mixedText($signatureDate) !!}</p></div>
@elseif ($type === 'cooperation-contract')
@include('business.partials.cooperation-contract', ['mode' => 'pdf', 'payload' => $payload])
@else
@ -98,7 +98,7 @@
<li>代表委托方代收 {!! $mixedText($payload['payment_counterparty']) !!} 的往来货款。</li>
</ol>
<p>委托有效期:从 {!! $mixedText($payload['authorization_start_date']) !!} 至 {!! $mixedText($payload['authorization_end_date']) !!} 止。</p>
<div class="sign"><p>委托方:{!! $mixedText($payload['principal_name']) !!}</p><p>受托方:{!! $mixedText($payload['trustee_name']) !!}</p><p>日期:签署后生成</p></div>
<div class="sign"><p>委托方:{!! $mixedText($payload['principal_name']) !!}</p><p>受托方:{!! $mixedText($payload['trustee_name']) !!}</p><p>日期:{!! $mixedText($signatureDate) !!}</p></div>
@endif
</body>
</html>

View File

@ -134,7 +134,7 @@
<section class="signature">
<h2>签署确认</h2>
<div class="signature__meta">报价版本:{!! $mixedText($version->version_code) !!} 快递公司:{!! $mixedText($version->carrier_name) !!} 执行日期:{!! $mixedText($version->effective_date?->toDateString()) !!}</div>
<div class="signature__box">乙方(快递方)签章:{!! $mixedText($version->carrier_name) !!}<br><br>签署日期:</div>
<div class="signature__box">乙方(快递方)签章:{!! $mixedText($version->carrier_name) !!}<br><br>签署日期:{!! $mixedText($signatureDate) !!}</div>
<div class="document-control">生成时间:{!! $mixedText($generatedAt->format('Y-m-d H:i:s')) !!} · 版本 {!! $mixedText($version->version_code) !!}</div>
</section>
</body>

View File

@ -49,6 +49,7 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase
$this->assertSame(CourierAgreementSignature::STATUS_SIGNING, $signature->status);
$this->assertSame('flow-agreement-001', $signature->esign_sign_flow_id);
$this->assertSame('新北中通快递有限公司', $signature->organization_name);
$this->assertSame('2026-09-21', $signature->payload['signature_date']);
Storage::disk('uploads')->assertExists($signature->source_pdf_path);
$this->assertSame('新北中通快递有限公司', $signature->payload['account_proof_account_name']);
@ -101,6 +102,7 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase
'document_type' => 'cooperation-contract',
'signing_subject' => $this->company->name,
'company_full_name' => $this->company->name,
'signature_date' => '2026-09-21',
'signing_deadline' => now()->addDays(7)->toDateString(),
'esign_account' => '13800138000',
'secondary_esign_account' => '13900139000',
@ -151,6 +153,7 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase
'document_type' => 'shared-contract',
'signing_subject' => $this->company->name,
'company_full_name' => $this->company->name,
'signature_date' => '2026-09-21',
'signing_deadline' => now()->addDays(7)->toDateString(),
'esign_account' => '13800138000',
'operator_name' => '张经理',
@ -232,6 +235,7 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase
'quote_version_id' => $version->id,
'signing_subject' => $this->company->name,
'company_full_name' => $this->company->name,
'signature_date' => '2026-09-21',
'signing_deadline' => now()->addDays(7)->toDateString(),
'esign_account' => '13800138000',
'operator_name' => '李经理',
@ -261,6 +265,79 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase
});
}
public function test_signature_date_is_required_for_every_signing_request(): void
{
$payload = $this->accountProofPayload('missing-signature-date');
unset($payload['signature_date']);
$this->withExceptionHandling()->loginAs()
->post(route('business.courier-agreement-signatures.store'), $payload)
->assertSessionHasErrors('signature_date');
$this->assertDatabaseCount('business_courier_agreement_signatures', 0);
}
public function test_all_agreement_templates_render_the_selected_signature_date(): void
{
$payload = [
'company_full_name' => $this->company->name,
'signature_date' => '2026-09-21',
'account_proof_effective_date' => '2026-09-01',
'account_proof_account_name' => '测试收款人',
'account_proof_bank_name' => '测试银行',
'account_proof_bank_account' => '6222000012345678',
'account_change_date' => '2026-09-01',
'old_account_name' => '旧账户',
'old_account_number' => '6222000011111111',
'old_account_stop_date' => '2026-09-01',
'new_account_effective_date' => '2026-09-02',
'new_account_name' => '新账户',
'new_bank_name' => '新银行',
'new_bank_account' => '6222000022222222',
'former_company_name' => '变更前公司',
'entity_change_date' => '2026-09-01',
'changed_company_name' => '变更后公司',
'changed_company_start_date' => '2026-09-02',
'contract_partner_name' => '快递公司甲',
'shared_courier_names' => ['快递公司乙'],
'cooperation_party_a_name' => '甲方公司',
'cooperation_party_a_phone' => '0519-88886666',
'cooperation_party_b_name' => '乙方公司',
'cooperation_party_b_phone' => '0519-88889999',
'cooperation_start_date' => '2026-09-01',
'cooperation_end_date' => '2027-08-31',
'principal_name' => '委托方',
'principal_id_number' => '91320400TEST',
'trustee_name' => '受托方',
'trustee_id_number' => '320400TEST',
'authorization_items' => ['1'],
'bill_counterparty' => '对账方',
'loan_counterparty' => '借支方',
'payment_counterparty' => '付款方',
'authorization_start_date' => '2026-09-01',
'authorization_end_date' => '2027-08-31',
];
foreach (['account-proof', 'account-change', 'entity-change', 'shared-contract', 'cooperation-contract', 'authorization'] as $type) {
$signature = new CourierAgreementSignature([
'business_no' => 'AGR-DATE-' . $type,
'template_code' => CourierAgreementSignature::DOCUMENTS[$type]['code'],
'document_type' => $type,
]);
$html = view('business.pdf.courier-agreement', [
'signature' => $signature,
'type' => $type,
'title' => CourierAgreementSignature::DOCUMENTS[$type]['name'],
'payload' => $payload,
'signatureDate' => '2026年9月21日',
'fontPath' => str_replace('\\', '/', resource_path('fonts/DroidSansFallback.ttf')),
])->render();
$this->assertStringContainsString('2026年9月21日', strip_tags($html), "{$type} 未输出签署日期变量");
$this->assertStringNotContainsString('签署后生成', $html, "{$type} 仍包含旧占位文案");
}
}
public function test_verified_agreement_callback_is_idempotent_and_queues_archive(): void
{
Queue::fake();
@ -541,6 +618,7 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase
'document_type' => 'account-proof',
'signing_subject' => $this->company->name,
'company_full_name' => $this->company->name,
'signature_date' => '2026-09-21',
'signing_deadline' => now()->addDays(7)->toDateString(),
'esign_account' => '13800138000',
'operator_name' => '张经理',

View File

@ -46,6 +46,26 @@ class CourierQuoteSignatureWorkflowTest extends FeatureTestCase
$again = app(CourierQuoteSignatureService::class)->prepareVersion($version->fresh(), $this->user->id);
$this->assertSame($signature->id, $again->id);
$this->assertSame($signature->source_pdf_sha256, $again->source_pdf_sha256);
$dated = app(CourierQuoteSignatureService::class)->prepareVersion($version->fresh(), $this->user->id, '2026-09-21');
$this->assertSame($signature->id, $dated->id);
$this->assertNotSame($signature->source_pdf_sha256, $dated->source_pdf_sha256);
$html = view('business.pdf.courier-quote', [
'version' => $version,
'change' => $version->change,
'values' => [['省份', '面单'], ['广东省', 6]],
'formulas' => [[], []],
'partyA' => $this->company->name,
'expiryDate' => '长期',
'faceFee' => '6',
'bubbleCoefficient' => '1.2',
'signatureDate' => '2026年9月21日',
'generatedAt' => now(),
'fontPath' => str_replace('\\', '/', resource_path('fonts/DroidSansFallback.ttf')),
])->render();
$this->assertStringContainsString('签署日期:', $html);
$this->assertStringContainsString('2026年9月21日', strip_tags($html));
}
public function test_admin_can_push_the_exact_pdf_to_esign_sandbox(): void
@ -75,6 +95,7 @@ class CourierQuoteSignatureWorkflowTest extends FeatureTestCase
'operator_name' => '张经理',
'operator_account' => '13800138000',
'notice_type' => '1',
'signature_date' => '2026-09-21',
'expires_at' => now()->addDays(7)->format('Y-m-d H:i:s'),
])->assertRedirect(route('business.courier-quote-changes.show', $signature->version->source_change_id));
@ -115,16 +136,41 @@ class CourierQuoteSignatureWorkflowTest extends FeatureTestCase
Storage::disk('uploads')->put($signature->source_pdf_path, '%PDF-tampered');
Http::fake();
try {
app(CourierQuoteSignatureService::class)->send($signature, [
'organization_name' => '新北中通快递有限公司',
'operator_name' => '张经理',
'operator_account' => '13800138000',
'notice_type' => '1',
'expires_at' => now()->addDays(7)->format('Y-m-d H:i:s'),
], $this->user->id);
$this->fail('A tampered source PDF must not be sent.');
} catch (\Illuminate\Validation\ValidationException $exception) {
$this->assertSame(
'待签报价 PDF 缺失或校验不一致,请重新生成后再推送。',
$exception->errors()['signature'][0],
);
}
Http::assertNothingSent();
$this->assertNull($signature->fresh()->esign_sign_flow_id);
}
public function test_quote_send_requires_a_signature_date(): void
{
$signature = $this->createPreparedSignature();
Http::fake();
$this->withExceptionHandling()->loginAs()->post(route('business.courier-quote-signatures.send', $signature), [
'organization_name' => '新北中通快递有限公司',
'operator_name' => '张经理',
'operator_account' => '13800138000',
'notice_type' => '1',
'expires_at' => now()->addDays(7)->format('Y-m-d H:i:s'),
])->assertSessionHasErrors('signature');
])->assertSessionHasErrors('signature_date');
Http::assertNothingSent();
$this->assertNull($signature->fresh()->esign_sign_flow_id);
$this->assertSame(CourierQuoteSignature::STATUS_PREPARED, $signature->fresh()->status);
}
public function test_source_pdf_cannot_be_regenerated_after_signing_has_started(): void
@ -175,6 +221,7 @@ class CourierQuoteSignatureWorkflowTest extends FeatureTestCase
'operator_name' => '张经理',
'operator_account' => '13800138000',
'notice_type' => '1',
'signature_date' => '2026-09-21',
'expires_at' => now()->addDays(7)->format('Y-m-d H:i:s'),
])
->assertForbidden();