@foreach($change->events as $event)
{{ $event->eventLabel() }}
{{ $event->note }}
操作人:{{ $event->actor?->name ?? ('用户 #' . $event->created_by) }}
@endforeach
@if($canReview && $change->status==='pending_review')
@endif
diff --git a/tests/Feature/Business/CourierQuoteChangeWorkflowTest.php b/tests/Feature/Business/CourierQuoteChangeWorkflowTest.php
index f281ade6..5165d764 100644
--- a/tests/Feature/Business/CourierQuoteChangeWorkflowTest.php
+++ b/tests/Feature/Business/CourierQuoteChangeWorkflowTest.php
@@ -55,6 +55,8 @@ class CourierQuoteChangeWorkflowTest extends FeatureTestCase
$response->assertRedirect(route('business.courier-quote-changes.edit', $change));
$this->assertSame('BG-' . now()->format('Ymd') . '-001', $change->number);
$this->assertSame(CourierQuoteChange::STATUS_DRAFT, $change->status);
+ $this->assertFalse($change->isRetrospective());
+ $this->assertSame(CourierQuoteChange::RECALCULATION_NOT_REQUIRED, $change->recalculation_status);
$this->assertSame('新北中通', $change->carrier_name);
$this->assertCount(1, $change->items);
$this->assertSame('5.0000', $change->items->first()->original_amount);
@@ -79,9 +81,40 @@ class CourierQuoteChangeWorkflowTest extends FeatureTestCase
->assertSeeText('新价格')
->assertSee('data-price-row', false)
->assertSee('name="items[0][new_amount]"', false)
+ ->assertSee('data-effective-date', false)
+ ->assertDontSee('type="date" min=', false)
+ ->assertSeeText('生效日期可以早于今天')
->assertSeeText('保存草稿');
}
+ public function test_historical_effective_date_can_be_saved_and_submitted_for_retrospective_recalculation(): void
+ {
+ $payload = $this->validPayload();
+ $payload['effective_date'] = now()->subMonths(2)->startOfMonth()->toDateString();
+
+ $this->loginAs()->post(route('business.courier-quote-changes.store'), $payload)
+ ->assertRedirect();
+
+ $change = CourierQuoteChange::firstOrFail();
+ $this->assertSame($payload['effective_date'], $change->effective_date->toDateString());
+ $this->assertTrue($change->isRetrospective());
+ $this->assertSame(CourierQuoteChange::RECALCULATION_PENDING, $change->recalculation_status);
+ $this->assertNull($change->recalculation_note);
+
+ $this->loginAs()->post(route('business.courier-quote-changes.submit', $change))
+ ->assertRedirect(route('business.courier-quote-changes.show', $change));
+
+ $change->refresh();
+ $this->assertSame(CourierQuoteChange::STATUS_PENDING_REVIEW, $change->status);
+ $this->assertTrue($change->isRetrospective());
+ $this->assertSame(CourierQuoteChange::RECALCULATION_PENDING, $change->recalculation_status);
+
+ $this->loginAs()->get(route('business.courier-quote-changes.show', $change))
+ ->assertOk()
+ ->assertSeeText('追溯变价 · 待追溯核算')
+ ->assertSeeText('原运费和原始 Excel 保持不变');
+ }
+
public function test_invalid_or_unchanged_amount_is_rejected_without_saving_partial_data(): void
{
$payload = $this->validPayload();
@@ -142,18 +175,7 @@ class CourierQuoteChangeWorkflowTest extends FeatureTestCase
$change = CourierQuoteChange::firstOrFail();
$this->loginAs()->post(route('business.courier-quote-changes.submit', $change));
- $reviewer = user_model_class()::create([
- 'name' => 'Quote Reviewer',
- 'email' => 'quote-reviewer@example.com',
- 'password' => 'password',
- 'locale' => 'zh-CN',
- 'enabled' => 1,
- 'landing_page' => 'dashboard',
- 'created_from' => 'core::test',
- ]);
- $reviewer->companies()->attach($this->company->id);
- $reviewer->roles()->attach(Role::where('name', 'admin')->firstOrFail()->id);
- $reviewer->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
+ $reviewer = $this->createReviewer();
$this->loginAs($reviewer, $this->company)
->post(route('business.courier-quote-changes.approve', $change))
@@ -171,6 +193,49 @@ class CourierQuoteChangeWorkflowTest extends FeatureTestCase
$this->assertSame('QB-002', app(QuoteManagementSnapshot::class)->load()['quotes'][1]['id']);
}
+ public function test_approving_historical_change_blocks_automatic_compensation_and_records_reason(): void
+ {
+ $payload = $this->validPayload();
+ $payload['effective_date'] = now()->subMonths(2)->startOfMonth()->toDateString();
+
+ $this->loginAs()->post(route('business.courier-quote-changes.store'), $payload);
+ $change = CourierQuoteChange::firstOrFail();
+ $this->loginAs()->post(route('business.courier-quote-changes.submit', $change));
+
+ $this->loginAs($this->createReviewer(), $this->company)
+ ->post(route('business.courier-quote-changes.approve', $change))
+ ->assertRedirect(route('business.courier-quote-changes.show', $change))
+ ->assertSessionHas('success', function (string $message): bool {
+ return str_contains($message, '历史生效已登记') && str_contains($message, '补差核算尚未执行');
+ });
+
+ $change->refresh();
+ $this->assertSame(CourierQuoteChange::STATUS_APPROVED, $change->status);
+ $this->assertSame(CourierQuoteChange::RECALCULATION_BLOCKED, $change->recalculation_status);
+ $this->assertStringContainsString('当前报价未提供可执行的运费计算方式', $change->recalculation_note);
+ $this->assertStringContainsString('历史运费也未记录所用报价版本', $change->recalculation_note);
+ $this->assertDatabaseCount('business_courier_quote_versions', 1);
+
+ $approvalEvent = $change->events()->where('event_type', 'approved')->firstOrFail();
+ $this->assertStringContainsString('补差明细尚未生成', $approvalEvent->note);
+ $this->assertStringContainsString('原运费保持不变', $approvalEvent->note);
+ $this->assertSame(
+ CourierQuoteChange::RECALCULATION_BLOCKED,
+ json_decode($approvalEvent->details_json, true, 512, JSON_THROW_ON_ERROR)['recalculation_status']
+ );
+
+ $this->loginAs()->get(route('business.courier-quote-changes.show', $change))
+ ->assertOk()
+ ->assertSeeText('追溯变价 · 核算受阻')
+ ->assertSeeText('未生成补差明细')
+ ->assertSeeText('原运费保持不变');
+
+ $this->loginAs()->get(route('business.courier-quote-changes.index'))
+ ->assertOk()
+ ->assertSeeText('追溯变价')
+ ->assertSeeText('核算受阻');
+ }
+
public function test_other_company_cannot_open_or_modify_quote_change(): void
{
$this->loginAs()->post(route('business.courier-quote-changes.store'), $this->validPayload());
@@ -219,4 +284,22 @@ class CourierQuoteChangeWorkflowTest extends FeatureTestCase
]],
];
}
+
+ private function createReviewer()
+ {
+ $reviewer = user_model_class()::create([
+ 'name' => 'Quote Reviewer',
+ 'email' => 'quote-reviewer-' . uniqid('', true) . '@example.com',
+ 'password' => 'password',
+ 'locale' => 'zh-CN',
+ 'enabled' => 1,
+ 'landing_page' => 'dashboard',
+ 'created_from' => 'core::test',
+ ]);
+ $reviewer->companies()->attach($this->company->id);
+ $reviewer->roles()->attach(Role::where('name', 'admin')->firstOrFail()->id);
+ $reviewer->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
+
+ return $reviewer;
+ }
}
--
2.47.2
From c74a272d53de05871267419fb72bc9a2b46e83e8 Mon Sep 17 00:00:00 2001
From: limeng <1131177064@qq.com>
Date: Sat, 15 Aug 2026 16:39:33 +0800
Subject: [PATCH 4/4] feat: add e-sign quote archive workflow
---
.../Business/CourierQuoteChanges.php | 16 +-
.../Business/CourierQuoteSignatures.php | 100 +++++
.../Integrations/EsignCallbacks.php | 95 +++++
app/Http/Middleware/VerifyCsrfToken.php | 4 +-
.../Business/ArchiveCourierQuoteSignature.php | 28 ++
app/Models/Business/CourierQuoteSignature.php | 68 +++
app/Models/Business/CourierQuoteVersion.php | 6 +
app/Services/Esign/EsignClient.php | 201 +++++++++
app/Services/Esign/EsignRequestSigner.php | 75 ++++
app/Support/CourierQuotePdf.php | 46 ++
app/Support/CourierQuoteSignatureService.php | 298 +++++++++++++
config/services.php | 10 +
...usiness_courier_quote_signatures_table.php | 54 +++
resources/fonts/DroidSansFallback-LICENSE.txt | 189 +++++++++
resources/fonts/DroidSansFallback.ttf | Bin 0 -> 3451900 bytes
.../quote-change-show.blade.php | 62 ++-
.../business/pdf/courier-quote.blade.php | 117 +++++
routes/admin.php | 11 +
routes/web.php | 5 +
.../CourierQuoteSignatureWorkflowTest.php | 399 ++++++++++++++++++
tests/Unit/EsignRequestSignerTest.php | 43 ++
21 files changed, 1822 insertions(+), 5 deletions(-)
create mode 100644 app/Http/Controllers/Business/CourierQuoteSignatures.php
create mode 100644 app/Http/Controllers/Integrations/EsignCallbacks.php
create mode 100644 app/Jobs/Business/ArchiveCourierQuoteSignature.php
create mode 100644 app/Models/Business/CourierQuoteSignature.php
create mode 100644 app/Services/Esign/EsignClient.php
create mode 100644 app/Services/Esign/EsignRequestSigner.php
create mode 100644 app/Support/CourierQuotePdf.php
create mode 100644 app/Support/CourierQuoteSignatureService.php
create mode 100644 database/migrations/2026_08_14_210000_create_business_courier_quote_signatures_table.php
create mode 100644 resources/fonts/DroidSansFallback-LICENSE.txt
create mode 100644 resources/fonts/DroidSansFallback.ttf
create mode 100644 resources/views/business/pdf/courier-quote.blade.php
create mode 100644 tests/Feature/Business/CourierQuoteSignatureWorkflowTest.php
create mode 100644 tests/Unit/EsignRequestSignerTest.php
diff --git a/app/Http/Controllers/Business/CourierQuoteChanges.php b/app/Http/Controllers/Business/CourierQuoteChanges.php
index b2cb170e..8467806a 100644
--- a/app/Http/Controllers/Business/CourierQuoteChanges.php
+++ b/app/Http/Controllers/Business/CourierQuoteChanges.php
@@ -3,7 +3,9 @@
namespace App\Http\Controllers\Business;
use App\Models\Business\CourierQuoteChange;
+use App\Services\Esign\EsignClient;
use App\Support\CourierQuoteChangeService;
+use App\Support\CourierQuoteSignatureService;
use App\Support\QuoteManagementSnapshot;
use App\Utilities\SystemAccess;
use Illuminate\Http\RedirectResponse;
@@ -16,6 +18,7 @@ class CourierQuoteChanges extends Controller
public function __construct(
private readonly CourierQuoteChangeService $service,
private readonly QuoteManagementSnapshot $snapshot,
+ private readonly CourierQuoteSignatureService $signatureService,
) {
}
@@ -53,11 +56,14 @@ class CourierQuoteChanges extends Controller
public function show(CourierQuoteChange $quoteChange): View
{
- $quoteChange->load(['items', 'events.actor', 'version']);
+ $quoteChange->load(['items', 'events.actor', 'version.signature']);
return view('business.pages.courier-payables.quote-change-show', [
'change' => $quoteChange,
'canReview' => $this->canReview() && $quoteChange->created_by !== user_id(),
+ 'canManageSignature' => $this->canReview(),
+ 'esignConfigured' => EsignClient::configured(),
+ 'esignEnvironment' => (string) config('services.esign.environment', 'sandbox'),
]);
}
@@ -94,6 +100,14 @@ class CourierQuoteChanges extends Controller
$message = "审核完成,已生成新报价版本 {$change->version->version_code}。";
+ try {
+ $this->signatureService->prepareVersion($change->version, user_id());
+ $message .= ' 签署 PDF 已生成并固定校验值。';
+ } catch (\Throwable $exception) {
+ report($exception);
+ $message .= ' 签署 PDF 生成失败,可在详情页重试。';
+ }
+
if ($change->isRetrospective()) {
$message .= ' 历史生效已登记,但补差核算尚未执行,请查看核算状态和阻断原因。';
}
diff --git a/app/Http/Controllers/Business/CourierQuoteSignatures.php b/app/Http/Controllers/Business/CourierQuoteSignatures.php
new file mode 100644
index 00000000..98fb9a7d
--- /dev/null
+++ b/app/Http/Controllers/Business/CourierQuoteSignatures.php
@@ -0,0 +1,100 @@
+ensureCanManage();
+ abort_unless($quoteVersion->status === 'approved', 422, '只有已审核报价版本可以生成签署文件。');
+ $signature = $this->service->prepareVersion($quoteVersion, user_id());
+
+ return redirect()->route('business.courier-quote-changes.show', $quoteVersion->source_change_id)
+ ->with('success', "{$quoteVersion->version_code} 的签署 PDF 已生成,文件校验值已固定。")
+ ->with('signature_id', $signature->id);
+ }
+
+ public function send(Request $request, CourierQuoteSignature $quoteSignature): RedirectResponse
+ {
+ $this->ensureCanManage();
+ abort_unless($quoteSignature->version?->status === 'approved', 422, '只有已审核报价版本可以发起签署。');
+ abort_unless(EsignClient::configured(), 422, 'E 签宝沙箱尚未配置 App ID 或 AppSecret。');
+ $data = $request->validate([
+ 'organization_name' => ['required', 'string', 'min:2', 'max:255'],
+ 'operator_name' => ['required', 'string', 'min:2', 'max:100'],
+ 'operator_account' => ['required', 'string', 'max:255'],
+ 'notice_type' => ['required', 'in:1,2'],
+ 'expires_at' => ['required', 'date', 'after:now', 'before_or_equal:' . now()->addDays(90)->toDateTimeString()],
+ ]);
+ $this->validateAccountForNotice($data['operator_account'], $data['notice_type']);
+ $signature = $this->service->send($quoteSignature, $data, user_id());
+ $environment = $signature->environment === 'sandbox' ? '沙箱' : '正式环境';
+
+ return redirect()->route('business.courier-quote-changes.show', $signature->version->source_change_id)
+ ->with('success', "{$signature->version->version_code} 已推送到 E 签宝{$environment},等待快递公司签署。");
+ }
+
+ public function synchronize(CourierQuoteSignature $quoteSignature): RedirectResponse
+ {
+ $this->ensureCanManage();
+ $signature = $this->service->synchronize($quoteSignature, user_id());
+ $message = $signature->status === CourierQuoteSignature::STATUS_ARCHIVED
+ ? '签署完成,签署 PDF 和完成证书已私有归档。'
+ : "已同步 E 签宝状态:{$signature->statusLabel()}。";
+
+ return redirect()->route('business.courier-quote-changes.show', $signature->version->source_change_id)
+ ->with('success', $message);
+ }
+
+ public function download(CourierQuoteSignature $quoteSignature, string $kind): StreamedResponse
+ {
+ [$path, $suffix] = match ($kind) {
+ 'source' => [$quoteSignature->source_pdf_path, '报价签署原文'],
+ 'signed' => [$quoteSignature->signed_pdf_path, '已签署报价'],
+ 'certificate' => [$quoteSignature->certificate_path, '签署完成证书'],
+ default => abort(404),
+ };
+
+ abort_if(blank($path) || ! \Storage::disk('uploads')->exists($path), 404, '归档文件不存在。');
+ $fileName = $quoteSignature->version->version_code . '-' . $suffix . '.pdf';
+
+ return \Storage::disk('uploads')->download($path, $fileName, ['Content-Type' => 'application/pdf']);
+ }
+
+ private function validateAccountForNotice(string $account, string $noticeType): void
+ {
+ $valid = $noticeType === '1'
+ ? preg_match('/^1\d{10}$/', $account) === 1
+ : filter_var($account, FILTER_VALIDATE_EMAIL) !== false;
+
+ if (! $valid) {
+ $message = $noticeType === '1' ? '短信通知必须填写 11 位手机号。' : '邮件通知必须填写有效邮箱。';
+ throw ValidationException::withMessages(['operator_account' => $message]);
+ }
+ }
+
+ private function ensureCanManage(): void
+ {
+ abort_unless(
+ in_array(SystemAccess::roleName(user()), [SystemAccess::SUPER_ADMIN, SystemAccess::ADMIN], true),
+ 403,
+ '当前账号没有报价签署管理权限。',
+ );
+ }
+}
diff --git a/app/Http/Controllers/Integrations/EsignCallbacks.php b/app/Http/Controllers/Integrations/EsignCallbacks.php
new file mode 100644
index 00000000..2bf8778e
--- /dev/null
+++ b/app/Http/Controllers/Integrations/EsignCallbacks.php
@@ -0,0 +1,95 @@
+json(['code' => '503', 'msg' => 'not configured'], 503);
+ }
+
+ $timestamp = trim((string) $request->header('X-Tsign-Open-TIMESTAMP'));
+ $algorithm = trim((string) $request->header('X-Tsign-Open-SIGNATURE-ALGORITHM'));
+ $callbackAppId = trim((string) $request->header('X-Tsign-Open-App-Id'));
+ $signature = strtolower(trim((string) $request->header('X-Tsign-Open-SIGNATURE')));
+ $rawBody = $request->getContent();
+
+ if (! ctype_digit($timestamp)
+ || strcasecmp($algorithm, 'HmacSHA256') !== 0
+ || ! hash_equals($appId, $callbackAppId)
+ || $signature === ''
+ || ! $this->timestampIsFresh($timestamp)) {
+ return response()->json(['code' => '401', 'msg' => 'invalid callback'], 401);
+ }
+
+ $queryValues = $request->query();
+ ksort($queryValues, SORT_STRING);
+ $data = $timestamp . collect($queryValues)->map(fn ($value): string => $this->queryValue($value))->implode('') . $rawBody;
+ $expected = strtolower(hash_hmac('sha256', $data, $appSecret));
+
+ if (! hash_equals($expected, $signature)) {
+ return response()->json(['code' => '401', 'msg' => 'invalid callback'], 401);
+ }
+
+ $payload = json_decode($rawBody, true);
+
+ if (! is_array($payload)) {
+ return response()->json(['code' => '400', 'msg' => 'invalid json'], 400);
+ }
+
+ if (($payload['action'] ?? null) !== 'SIGN_FLOW_COMPLETE') {
+ return $this->success();
+ }
+
+ $signFlowId = trim((string) ($payload['signFlowId'] ?? ''));
+ $remoteStatus = trim((string) ($payload['signFlowStatus'] ?? ''));
+
+ if ($signFlowId === '' || $remoteStatus === '') {
+ return $this->success();
+ }
+
+ $signatureRecord = $service->acceptCallback($signFlowId, $remoteStatus, hash('sha256', $rawBody));
+
+ if ($signatureRecord
+ && $remoteStatus === '2'
+ && $signatureRecord->status !== CourierQuoteSignature::STATUS_ARCHIVED) {
+ ArchiveCourierQuoteSignature::dispatchAfterResponse($signatureRecord->id);
+ }
+
+ return $this->success();
+ }
+
+ private function timestampIsFresh(string $timestamp): bool
+ {
+ $seconds = strlen($timestamp) >= 13 ? intdiv((int) $timestamp, 1000) : (int) $timestamp;
+ $tolerance = max(60, (int) config('services.esign.callback_tolerance_seconds', 900));
+
+ return abs(now()->timestamp - $seconds) <= $tolerance;
+ }
+
+ private function queryValue(mixed $value): string
+ {
+ if (is_array($value)) {
+ return collect($value)->map(fn ($item): string => $this->queryValue($item))->implode('');
+ }
+
+ return is_scalar($value) ? (string) $value : '';
+ }
+
+ private function success(): JsonResponse
+ {
+ return response()->json(['code' => '200', 'msg' => 'success']);
+ }
+}
diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php
index e686b035..c27aa552 100644
--- a/app/Http/Middleware/VerifyCsrfToken.php
+++ b/app/Http/Middleware/VerifyCsrfToken.php
@@ -19,6 +19,6 @@ class VerifyCsrfToken extends Middleware
* @var array
*/
protected $except = [
- //
+ 'integrations/esign/callback',
];
-}
\ No newline at end of file
+}
diff --git a/app/Jobs/Business/ArchiveCourierQuoteSignature.php b/app/Jobs/Business/ArchiveCourierQuoteSignature.php
new file mode 100644
index 00000000..f387dfd7
--- /dev/null
+++ b/app/Jobs/Business/ArchiveCourierQuoteSignature.php
@@ -0,0 +1,28 @@
+synchronizeById($this->signatureId);
+ }
+}
diff --git a/app/Models/Business/CourierQuoteSignature.php b/app/Models/Business/CourierQuoteSignature.php
new file mode 100644
index 00000000..09474c94
--- /dev/null
+++ b/app/Models/Business/CourierQuoteSignature.php
@@ -0,0 +1,68 @@
+ 'integer',
+ 'quote_version_id' => 'integer',
+ 'expires_at' => 'datetime',
+ 'source_pdf_generated_at' => 'datetime',
+ 'sent_at' => 'datetime',
+ 'completed_at' => 'datetime',
+ 'archived_at' => 'datetime',
+ 'last_synced_at' => 'datetime',
+ ];
+
+ public function version(): BelongsTo
+ {
+ return $this->belongsTo(CourierQuoteVersion::class, 'quote_version_id');
+ }
+
+ public function statusLabel(): string
+ {
+ return match ($this->status) {
+ self::STATUS_PREPARED => '待推送',
+ self::STATUS_SENDING => '推送中',
+ self::STATUS_SIGNING => '待签署',
+ self::STATUS_COMPLETED => '签署完成,待归档',
+ self::STATUS_ARCHIVED => '已签署并归档',
+ self::STATUS_REVOKED => '已撤销',
+ self::STATUS_EXPIRED => '已过期',
+ self::STATUS_REJECTED => '已拒签',
+ self::STATUS_FAILED => '处理失败',
+ default => $this->status,
+ };
+ }
+
+ public function canSend(): bool
+ {
+ return in_array($this->status, [self::STATUS_PREPARED, self::STATUS_FAILED], true)
+ && blank($this->esign_sign_flow_id);
+ }
+}
diff --git a/app/Models/Business/CourierQuoteVersion.php b/app/Models/Business/CourierQuoteVersion.php
index bebce677..33f6497e 100644
--- a/app/Models/Business/CourierQuoteVersion.php
+++ b/app/Models/Business/CourierQuoteVersion.php
@@ -4,6 +4,7 @@ namespace App\Models\Business;
use App\Abstracts\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\HasOne;
class CourierQuoteVersion extends Model
{
@@ -24,4 +25,9 @@ class CourierQuoteVersion extends Model
{
return $this->belongsTo(CourierQuoteChange::class, 'source_change_id');
}
+
+ public function signature(): HasOne
+ {
+ return $this->hasOne(CourierQuoteSignature::class, 'quote_version_id');
+ }
}
diff --git a/app/Services/Esign/EsignClient.php b/app/Services/Esign/EsignClient.php
new file mode 100644
index 00000000..ab9d06ca
--- /dev/null
+++ b/app/Services/Esign/EsignClient.php
@@ -0,0 +1,201 @@
+baseUrl = rtrim((string) config('services.esign.base_url'), '/');
+ $this->signer = new EsignRequestSigner($appId, $appSecret);
+ }
+
+ public static function configured(): bool
+ {
+ return filled(config('services.esign.app_id')) && filled(config('services.esign.app_secret'));
+ }
+
+ public function uploadPdf(string $bytes, string $fileName): string
+ {
+ $contentMd5 = base64_encode(md5($bytes, true));
+ $data = $this->request('POST', '/v3/files/file-upload-url', [
+ 'contentMd5' => $contentMd5,
+ 'contentType' => 'application/pdf',
+ 'fileName' => $fileName,
+ 'fileSize' => strlen($bytes),
+ 'convertToPDF' => false,
+ ]);
+ $fileId = (string) ($data['fileId'] ?? '');
+ $uploadUrl = (string) ($data['fileUploadUrl'] ?? '');
+
+ if ($fileId === '' || $uploadUrl === '') {
+ throw new RuntimeException('E 签宝未返回文件 ID 或上传地址。');
+ }
+
+ $uploadResponse = Http::timeout($this->timeout())
+ ->withHeaders(['Content-MD5' => $contentMd5])
+ ->withBody($bytes, 'application/pdf')
+ ->send('PUT', $uploadUrl);
+
+ if (! $uploadResponse->successful()) {
+ throw new RuntimeException('报价 PDF 上传到 E 签宝文件服务失败。');
+ }
+
+ $this->waitUntilFileReady($fileId);
+
+ return $fileId;
+ }
+
+ public function createSignFlow(string $fileId, string $fileName, array $signer, int $expiresAtMs): string
+ {
+ $flowConfig = [
+ 'signFlowTitle' => $signer['flow_title'],
+ 'signFlowExpireTime' => $expiresAtMs,
+ 'autoStart' => true,
+ 'autoFinish' => true,
+ ];
+ $notifyUrl = trim((string) config('services.esign.notify_url'));
+
+ if ($notifyUrl !== '') {
+ $flowConfig['notifyUrl'] = $notifyUrl;
+ }
+
+ $data = $this->request('POST', '/v3/sign-flow/create-by-file', [
+ 'docs' => [[
+ 'fileId' => $fileId,
+ 'fileName' => $fileName,
+ ]],
+ 'signFlowConfig' => $flowConfig,
+ 'signers' => [[
+ 'signerType' => 1,
+ 'signConfig' => ['signOrder' => 1],
+ 'noticeConfig' => ['noticeTypes' => $signer['notice_types']],
+ 'orgSignerInfo' => [
+ 'orgName' => $signer['organization_name'],
+ 'transactorInfo' => [
+ 'psnAccount' => $signer['operator_account'],
+ 'psnInfo' => ['psnName' => $signer['operator_name']],
+ ],
+ ],
+ 'signFields' => [[
+ 'fileId' => $fileId,
+ 'normalSignFieldConfig' => [
+ 'freeMode' => true,
+ 'movableSignField' => true,
+ 'signFieldSize' => '159',
+ ],
+ ]],
+ ]],
+ ]);
+ $signFlowId = (string) ($data['signFlowId'] ?? '');
+
+ if ($signFlowId === '') {
+ throw new RuntimeException('E 签宝未返回签署流程 ID。');
+ }
+
+ return $signFlowId;
+ }
+
+ public function signFlowDetail(string $signFlowId): array
+ {
+ return $this->request('GET', "/v3/sign-flow/{$signFlowId}/detail");
+ }
+
+ public function downloadUrls(string $signFlowId): array
+ {
+ return $this->request('POST', "/v3/sign-flow/{$signFlowId}/file-download-url", [
+ 'urlAvailableDate' => '3600',
+ ]);
+ }
+
+ public function download(string $url): string
+ {
+ $response = Http::timeout($this->timeout())->get($url);
+
+ if (! $response->successful()) {
+ throw new RuntimeException('无法从 E 签宝下载签署归档文件。');
+ }
+
+ return $response->body();
+ }
+
+ private function waitUntilFileReady(string $fileId): void
+ {
+ for ($attempt = 0; $attempt < 8; $attempt++) {
+ $data = $this->request('GET', "/v3/files/{$fileId}");
+ $status = (string) ($data['fileStatus'] ?? $data['status'] ?? '');
+
+ if (in_array($status, ['2', '5'], true)) {
+ return;
+ }
+
+ if ($attempt < 7) {
+ usleep(400000);
+ }
+ }
+
+ throw new RuntimeException('E 签宝文件处理超时,请稍后重试。');
+ }
+
+ private function request(string $method, string $path, array $payload = []): array
+ {
+ $method = strtoupper($method);
+ $body = $method === 'GET' ? '' : $this->encodeJson($payload);
+ $request = Http::timeout($this->timeout())
+ ->withHeaders($this->signer->headers($method, $path, $body));
+
+ if ($body !== '') {
+ $request = $request->withBody($body, EsignRequestSigner::CONTENT_TYPE);
+ }
+
+ $response = $request->send($method, $this->baseUrl . $path);
+
+ if (! $response->successful()) {
+ throw new RuntimeException("E 签宝接口请求失败(HTTP {$response->status()})。");
+ }
+
+ $decoded = $response->json();
+
+ if (! is_array($decoded)) {
+ throw new RuntimeException('E 签宝接口返回了无法识别的数据。');
+ }
+
+ if ((int) ($decoded['code'] ?? -1) !== 0) {
+ $code = (string) ($decoded['code'] ?? 'unknown');
+ $message = mb_substr((string) ($decoded['message'] ?? '未知错误'), 0, 300);
+
+ throw new RuntimeException("E 签宝接口返回业务错误({$code}):{$message}");
+ }
+
+ return is_array($decoded['data'] ?? null) ? $decoded['data'] : [];
+ }
+
+ private function encodeJson(array $payload): string
+ {
+ try {
+ return json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
+ } catch (JsonException $exception) {
+ throw new RuntimeException('E 签宝请求数据无法编码。', previous: $exception);
+ }
+ }
+
+ private function timeout(): int
+ {
+ return max(5, (int) config('services.esign.timeout', 20));
+ }
+}
diff --git a/app/Services/Esign/EsignRequestSigner.php b/app/Services/Esign/EsignRequestSigner.php
new file mode 100644
index 00000000..3644bb6b
--- /dev/null
+++ b/app/Services/Esign/EsignRequestSigner.php
@@ -0,0 +1,75 @@
+appId === '' || $this->appSecret === '') {
+ throw new InvalidArgumentException('E 签宝 App ID 和 AppSecret 不能为空。');
+ }
+ }
+
+ public function headers(string $method, string $pathAndParameters, string $body = ''): array
+ {
+ $method = strtoupper($method);
+ $pathAndParameters = $this->canonicalPath($pathAndParameters);
+ $contentMd5 = $body === '' ? '' : base64_encode(md5($body, true));
+ $stringToSign = implode("\n", [
+ $method,
+ self::ACCEPT,
+ $contentMd5,
+ self::CONTENT_TYPE,
+ '',
+ ]) . "\n" . $pathAndParameters;
+
+ return [
+ 'X-Tsign-Open-App-Id' => $this->appId,
+ 'X-Tsign-Open-Auth-Mode' => 'Signature',
+ 'X-Tsign-Open-Ca-Timestamp' => (string) floor(microtime(true) * 1000),
+ 'X-Tsign-Open-Ca-Signature' => base64_encode(hash_hmac('sha256', $stringToSign, $this->appSecret, true)),
+ 'Accept' => self::ACCEPT,
+ 'Content-Type' => self::CONTENT_TYPE,
+ 'Content-MD5' => $contentMd5,
+ ];
+ }
+
+ public function canonicalPath(string $pathAndParameters): string
+ {
+ $parts = parse_url($pathAndParameters);
+ $path = (string) ($parts['path'] ?? '');
+
+ if ($path === '' || ! str_starts_with($path, '/')) {
+ throw new InvalidArgumentException('E 签宝 API 路径必须以 / 开头。');
+ }
+
+ if (! isset($parts['query']) || $parts['query'] === '') {
+ return $path;
+ }
+
+ $parameters = [];
+
+ foreach (explode('&', $parts['query']) as $pair) {
+ [$key, $value] = array_pad(explode('=', $pair, 2), 2, null);
+
+ if (! array_key_exists($key, $parameters)) {
+ $parameters[$key] = $value;
+ }
+ }
+
+ ksort($parameters, SORT_STRING);
+ $query = collect($parameters)->map(function (?string $value, string $key): string {
+ return $value === null || $value === '' ? $key : $key . '=' . $value;
+ })->implode('&');
+
+ return $path . '?' . $query;
+ }
+}
diff --git a/app/Support/CourierQuotePdf.php b/app/Support/CourierQuotePdf.php
new file mode 100644
index 00000000..2b5ced85
--- /dev/null
+++ b/app/Support/CourierQuotePdf.php
@@ -0,0 +1,46 @@
+loadMissing(['change.items']);
+ $values = $this->decodeMatrix($version->values_json);
+ $formulas = $this->decodeMatrix($version->formulas_json);
+ $html = view('business.pdf.courier-quote', [
+ 'version' => $version,
+ 'change' => $version->change,
+ 'values' => $values,
+ 'formulas' => $formulas,
+ 'generatedAt' => now(),
+ 'fontPath' => str_replace('\\', '/', resource_path('fonts/DroidSansFallback.ttf')),
+ ])->render();
+
+ $pdf = app('dompdf.wrapper');
+ $pdf->setPaper('a4', 'landscape');
+ $pdf->loadHTML($html, 'UTF-8');
+ $bytes = $pdf->output();
+
+ if (! is_string($bytes) || ! str_starts_with($bytes, '%PDF-')) {
+ throw new RuntimeException('报价 PDF 生成失败。');
+ }
+
+ return $bytes;
+ }
+
+ private function decodeMatrix(?string $json): array
+ {
+ if (blank($json)) {
+ return [];
+ }
+
+ $decoded = json_decode($json, true);
+
+ return is_array($decoded) ? $decoded : [];
+ }
+}
diff --git a/app/Support/CourierQuoteSignatureService.php b/app/Support/CourierQuoteSignatureService.php
new file mode 100644
index 00000000..dc573f54
--- /dev/null
+++ b/app/Support/CourierQuoteSignatureService.php
@@ -0,0 +1,298 @@
+signature;
+
+ if ($existing && $this->sourcePdfIsIntact($existing)) {
+ return $existing;
+ }
+
+ if ($existing && ! $existing->canSend()) {
+ throw ValidationException::withMessages([
+ 'signature' => '该报价已经进入签署流程,签署原文不可重新生成。',
+ ]);
+ }
+
+ $bytes = $this->pdf->render($version);
+ $path = $this->path($version, 'source.pdf');
+ $sha256 = hash('sha256', $bytes);
+
+ if (! Storage::disk('uploads')->put($path, $bytes)) {
+ throw new RuntimeException('报价 PDF 无法写入私有业务存储。');
+ }
+
+ $signature = CourierQuoteSignature::updateOrCreate(
+ ['company_id' => $version->company_id, 'quote_version_id' => $version->id],
+ [
+ 'provider' => 'esign',
+ 'environment' => (string) config('services.esign.environment', 'sandbox'),
+ 'status' => CourierQuoteSignature::STATUS_PREPARED,
+ 'organization_name' => $version->carrier_name,
+ 'source_pdf_path' => $path,
+ 'source_pdf_sha256' => $sha256,
+ 'source_pdf_generated_at' => now(),
+ 'last_error' => null,
+ 'created_by' => $existing?->created_by ?? $userId,
+ 'updated_by' => $userId,
+ ],
+ );
+ $this->updateVersionStatus($signature, $signature->statusLabel());
+
+ return $signature->fresh('version');
+ }
+
+ public function send(CourierQuoteSignature $signature, array $data, int $userId): CourierQuoteSignature
+ {
+ $signature = DB::transaction(function () use ($signature, $data, $userId): CourierQuoteSignature {
+ $locked = CourierQuoteSignature::query()->lockForUpdate()->findOrFail($signature->id);
+
+ if (! $locked->canSend()) {
+ throw ValidationException::withMessages(['signature' => '当前报价版本已经进入签署流程,不能重复推送。']);
+ }
+
+ if (! $this->sourcePdfIsIntact($locked)) {
+ throw ValidationException::withMessages(['signature' => '待签报价 PDF 缺失或校验不一致,请重新生成后再推送。']);
+ }
+
+ $locked->update([
+ 'status' => CourierQuoteSignature::STATUS_SENDING,
+ 'organization_name' => $data['organization_name'],
+ 'operator_name' => $data['operator_name'],
+ 'operator_account' => $data['operator_account'],
+ 'notice_types' => (string) $data['notice_type'],
+ 'expires_at' => Carbon::parse($data['expires_at']),
+ 'last_error' => null,
+ 'updated_by' => $userId,
+ ]);
+
+ return $locked->fresh('version');
+ });
+ $this->updateVersionStatus($signature, '推送中');
+
+ try {
+ $client = app(EsignClient::class);
+ $bytes = Storage::disk('uploads')->get($signature->source_pdf_path);
+ $fileName = 'quote-' . $signature->version->version_code . '.pdf';
+ $fileId = $client->uploadPdf($bytes, $fileName);
+ $signFlowId = $client->createSignFlow($fileId, $fileName, [
+ 'flow_title' => $signature->version->carrier_name . ' ' . $signature->version->version_code . ' 报价确认',
+ 'organization_name' => $signature->organization_name,
+ 'operator_name' => $signature->operator_name,
+ 'operator_account' => $signature->operator_account,
+ 'notice_types' => $signature->notice_types,
+ ], $signature->expires_at->getTimestampMs());
+
+ $signature->update([
+ 'status' => CourierQuoteSignature::STATUS_SIGNING,
+ 'esign_file_id' => $fileId,
+ 'esign_sign_flow_id' => $signFlowId,
+ 'remote_status' => '1',
+ 'sent_at' => now(),
+ 'last_synced_at' => now(),
+ 'last_error' => null,
+ 'updated_by' => $userId,
+ ]);
+ $this->updateVersionStatus($signature, '待签署');
+
+ return $signature->fresh('version');
+ } catch (Throwable $exception) {
+ $signature->update([
+ 'status' => CourierQuoteSignature::STATUS_FAILED,
+ 'last_error' => $this->safeError($exception),
+ 'updated_by' => $userId,
+ ]);
+ $this->updateVersionStatus($signature, '推送失败');
+
+ throw ValidationException::withMessages(['signature' => $signature->last_error]);
+ }
+ }
+
+ public function synchronize(CourierQuoteSignature $signature, int $userId): CourierQuoteSignature
+ {
+ if (blank($signature->esign_sign_flow_id)) {
+ throw ValidationException::withMessages(['signature' => '该报价版本尚未创建 E 签宝签署流程。']);
+ }
+
+ if ($signature->status === CourierQuoteSignature::STATUS_ARCHIVED) {
+ return $signature;
+ }
+
+ try {
+ $client = app(EsignClient::class);
+ $detail = $client->signFlowDetail($signature->esign_sign_flow_id);
+ $remoteStatus = (string) ($detail['signFlowStatus'] ?? $detail['status'] ?? '');
+
+ if ($remoteStatus === '') {
+ throw new RuntimeException('E 签宝未返回签署流程状态。');
+ }
+
+ $signature = $this->applyRemoteStatus($signature, $remoteStatus, $userId);
+
+ return $remoteStatus === '2' ? $this->archive($signature, $client, $userId) : $signature;
+ } catch (Throwable $exception) {
+ $signature->update([
+ 'last_error' => $this->safeError($exception),
+ 'last_synced_at' => now(),
+ 'updated_by' => $userId,
+ ]);
+
+ throw ValidationException::withMessages(['signature' => $signature->last_error]);
+ }
+ }
+
+ public function synchronizeById(int $signatureId): void
+ {
+ $signature = CourierQuoteSignature::allCompanies()->find($signatureId);
+
+ if (! $signature) {
+ return;
+ }
+
+ $this->synchronize($signature, $signature->updated_by);
+ }
+
+ public function acceptCallback(string $signFlowId, string $remoteStatus, string $payloadHash): ?CourierQuoteSignature
+ {
+ return DB::transaction(function () use ($signFlowId, $remoteStatus, $payloadHash): ?CourierQuoteSignature {
+ $signature = CourierQuoteSignature::allCompanies()
+ ->where('esign_sign_flow_id', $signFlowId)
+ ->lockForUpdate()
+ ->first();
+
+ if (! $signature) {
+ return null;
+ }
+
+ if ($signature->callback_payload_hash === $payloadHash) {
+ return null;
+ }
+
+ $signature->callback_payload_hash = $payloadHash;
+
+ return $this->applyRemoteStatus($signature, $remoteStatus, $signature->updated_by);
+ });
+ }
+
+ public function sourcePdfIsIntact(CourierQuoteSignature $signature): bool
+ {
+ if (blank($signature->source_pdf_path) || blank($signature->source_pdf_sha256)) {
+ return false;
+ }
+
+ $disk = Storage::disk('uploads');
+
+ return $disk->exists($signature->source_pdf_path)
+ && hash_equals($signature->source_pdf_sha256, hash('sha256', $disk->get($signature->source_pdf_path)));
+ }
+
+ private function applyRemoteStatus(CourierQuoteSignature $signature, string $remoteStatus, int $userId): CourierQuoteSignature
+ {
+ [$status, $label] = match ($remoteStatus) {
+ '1' => [CourierQuoteSignature::STATUS_SIGNING, '待签署'],
+ '2' => [CourierQuoteSignature::STATUS_COMPLETED, '签署完成,待归档'],
+ '3' => [CourierQuoteSignature::STATUS_REVOKED, '已撤销'],
+ '5' => [CourierQuoteSignature::STATUS_EXPIRED, '已过期'],
+ '7' => [CourierQuoteSignature::STATUS_REJECTED, '已拒签'],
+ default => [$signature->status, $signature->statusLabel()],
+ };
+ $signature->forceFill([
+ 'status' => $status,
+ 'remote_status' => $remoteStatus,
+ 'completed_at' => $remoteStatus === '2' ? ($signature->completed_at ?? now()) : $signature->completed_at,
+ 'last_synced_at' => now(),
+ 'last_error' => null,
+ 'updated_by' => $userId,
+ ])->save();
+ $this->updateVersionStatus($signature, $label);
+
+ $signature->refresh();
+
+ return $signature;
+ }
+
+ private function archive(CourierQuoteSignature $signature, EsignClient $client, int $userId): CourierQuoteSignature
+ {
+ if ($signature->status === CourierQuoteSignature::STATUS_ARCHIVED) {
+ return $signature;
+ }
+
+ $urls = $client->downloadUrls($signature->esign_sign_flow_id);
+ $signedUrl = (string) data_get($urls, 'files.0.downloadUrl', '');
+ $certificateUrl = (string) ($urls['certificateDownloadUrl'] ?? '');
+
+ if ($signedUrl === '' || $certificateUrl === '') {
+ throw new RuntimeException('E 签宝未返回签署文件或完成证书下载地址。');
+ }
+
+ $signedBytes = $client->download($signedUrl);
+ $certificateBytes = $client->download($certificateUrl);
+
+ if (! str_starts_with($signedBytes, '%PDF-') || ! str_starts_with($certificateBytes, '%PDF-')) {
+ throw new RuntimeException('E 签宝返回的签署文件或完成证书不是有效 PDF。');
+ }
+
+ $version = CourierQuoteVersion::allCompanies()->findOrFail($signature->quote_version_id);
+ $signedPath = $this->path($version, 'signed.pdf');
+ $certificatePath = $this->path($version, 'completion-certificate.pdf');
+ $disk = Storage::disk('uploads');
+
+ if (! $disk->put($signedPath, $signedBytes) || ! $disk->put($certificatePath, $certificateBytes)) {
+ throw new RuntimeException('签署归档文件无法写入私有业务存储。');
+ }
+
+ $signature->update([
+ 'status' => CourierQuoteSignature::STATUS_ARCHIVED,
+ 'signed_pdf_path' => $signedPath,
+ 'signed_pdf_sha256' => hash('sha256', $signedBytes),
+ 'certificate_path' => $certificatePath,
+ 'certificate_sha256' => hash('sha256', $certificateBytes),
+ 'archived_at' => now(),
+ 'last_synced_at' => now(),
+ 'last_error' => null,
+ 'updated_by' => $userId,
+ ]);
+ $this->updateVersionStatus($signature, '已签署并归档');
+
+ return $signature->fresh('version');
+ }
+
+ private function path(CourierQuoteVersion $version, string $fileName): string
+ {
+ $versionCode = preg_replace('/[^A-Za-z0-9_-]/', '-', $version->version_code) ?: (string) $version->id;
+
+ return "courier-quotes/{$version->company_id}/{$versionCode}/{$fileName}";
+ }
+
+ private function updateVersionStatus(CourierQuoteSignature $signature, string $label): void
+ {
+ CourierQuoteVersion::allCompanies()
+ ->whereKey($signature->quote_version_id)
+ ->update(['signature_status' => $label]);
+ }
+
+ private function safeError(Throwable $exception): string
+ {
+ $message = preg_replace('/[[:cntrl:]]+/u', ' ', $exception->getMessage()) ?: 'E 签宝处理失败。';
+
+ return mb_substr($message, 0, 1000);
+ }
+}
diff --git a/config/services.php b/config/services.php
index f88a8933..e7f3c686 100644
--- a/config/services.php
+++ b/config/services.php
@@ -34,4 +34,14 @@ return [
'api_key' => env('SENDGRID_API_KEY'),
],
+ 'esign' => [
+ 'environment' => env('ESIGN_ENVIRONMENT', 'sandbox'),
+ 'base_url' => env('ESIGN_BASE_URL', 'https://smlopenapi.esign.cn'),
+ 'app_id' => env('ESIGN_APP_ID'),
+ 'app_secret' => env('ESIGN_APP_SECRET'),
+ 'notify_url' => env('ESIGN_NOTIFY_URL'),
+ 'callback_tolerance_seconds' => (int) env('ESIGN_CALLBACK_TOLERANCE_SECONDS', 900),
+ 'timeout' => (int) env('ESIGN_TIMEOUT', 20),
+ ],
+
];
diff --git a/database/migrations/2026_08_14_210000_create_business_courier_quote_signatures_table.php b/database/migrations/2026_08_14_210000_create_business_courier_quote_signatures_table.php
new file mode 100644
index 00000000..2c918b55
--- /dev/null
+++ b/database/migrations/2026_08_14_210000_create_business_courier_quote_signatures_table.php
@@ -0,0 +1,54 @@
+increments('id');
+ $table->unsignedInteger('company_id');
+ $table->unsignedInteger('quote_version_id');
+ $table->string('provider', 32)->default('esign');
+ $table->string('environment', 32)->default('sandbox');
+ $table->string('status', 32)->default('prepared');
+ $table->string('organization_name');
+ $table->string('operator_name')->nullable();
+ $table->string('operator_account')->nullable();
+ $table->string('notice_types', 16)->nullable();
+ $table->timestamp('expires_at')->nullable();
+ $table->string('source_pdf_path', 1024);
+ $table->string('source_pdf_sha256', 64);
+ $table->timestamp('source_pdf_generated_at');
+ $table->string('esign_file_id', 128)->nullable();
+ $table->string('esign_sign_flow_id', 128)->nullable();
+ $table->string('remote_status', 32)->nullable();
+ $table->string('signed_pdf_path', 1024)->nullable();
+ $table->string('signed_pdf_sha256', 64)->nullable();
+ $table->string('certificate_path', 1024)->nullable();
+ $table->string('certificate_sha256', 64)->nullable();
+ $table->timestamp('sent_at')->nullable();
+ $table->timestamp('completed_at')->nullable();
+ $table->timestamp('archived_at')->nullable();
+ $table->timestamp('last_synced_at')->nullable();
+ $table->text('last_error')->nullable();
+ $table->string('callback_payload_hash', 64)->nullable();
+ $table->unsignedInteger('created_by');
+ $table->unsignedInteger('updated_by');
+ $table->timestamps();
+ $table->softDeletes();
+
+ $table->unique(['company_id', 'quote_version_id'], 'courier_quote_signatures_company_version_unique');
+ $table->unique('esign_sign_flow_id', 'courier_quote_signatures_flow_unique');
+ $table->index(['company_id', 'status'], 'courier_quote_signatures_company_status_index');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('business_courier_quote_signatures');
+ }
+};
diff --git a/resources/fonts/DroidSansFallback-LICENSE.txt b/resources/fonts/DroidSansFallback-LICENSE.txt
new file mode 100644
index 00000000..f7bd78d2
--- /dev/null
+++ b/resources/fonts/DroidSansFallback-LICENSE.txt
@@ -0,0 +1,189 @@
+
+ Copyright (c) 2005-2008, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/resources/fonts/DroidSansFallback.ttf b/resources/fonts/DroidSansFallback.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..1099b177c881c96bf298989dcdca5fda7841133d
GIT binary patch
literal 3451900
zcmeFaS+HH#ncsKBmP7&<7b6Felt_rAL~#@+aTW;zAVAFH#Xw>pCW12|ilRhGElTPZ
z)lzp`a$9P(Tkcld-Elj19Cw#p&fwV9QTxF!sY)t#rBaou
;ALB{h#_L
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