[ 'code' => 'TPL-ACCOUNT-CERT-V2', 'version' => '2', 'name' => '收款账户证明', 'file' => '收款账户证明文件.doc', ], 'account-change' => [ 'code' => 'TPL-ACCOUNT-CHANGE-V1', 'version' => '1', 'name' => '收款账户变更', 'file' => '收款账户变更证明.doc', ], 'entity-change' => [ 'code' => 'TPL-ENTITY-CHANGE-V1', 'version' => '1', 'name' => '公司主体变更', 'file' => '公司主体变更证明.docx', ], 'shared-contract' => [ 'code' => 'TPL-SHARED-CONTRACT-V1', 'version' => '1', 'name' => '共用合同证明', 'file' => '共用合同证明.docx', ], 'cooperation-contract' => [ 'code' => 'TPL-COOPERATION-CONTRACT-V1', 'version' => '1', 'name' => '合作合同', 'file' => '保丟不保损-合同发快递2024.8.doc', ], 'authorization' => [ 'code' => 'TPL-AUTHORIZATION-V1', 'version' => '1', 'name' => '授权委托书', 'file' => '授权委托书(快递).docx', ], 'quote-confirmation' => [ 'code' => 'TPL-QUOTE-CONFIRMATION-V1', 'version' => '1', 'name' => '报价确认单', 'file' => '报价确认单(报价版本快照).pdf', ], ]; protected $table = 'business_courier_agreement_signatures'; protected $fillable = [ 'company_id', 'business_no', 'idempotency_key', 'document_type', 'template_code', 'template_version', 'source_file_name', 'status', 'courier_name', 'company_full_name', 'organization_name', 'operator_name', 'operator_account', 'payload', 'environment', 'expires_at', 'source_pdf_path', 'source_pdf_sha256', 'esign_file_id', 'esign_sign_flow_id', 'remote_status', 'signed_pdf_path', 'signed_pdf_sha256', 'certificate_path', 'certificate_sha256', 'sent_at', 'completed_at', 'archived_at', 'last_synced_at', 'last_error', 'callback_payload_hash', 'created_by', 'updated_by', ]; protected $casts = [ 'company_id' => 'integer', 'expires_at' => 'datetime', 'sent_at' => 'datetime', 'completed_at' => 'datetime', 'archived_at' => 'datetime', 'last_synced_at' => 'datetime', 'payload' => 'encrypted:array', 'operator_account' => 'encrypted', ]; public function partnerTypeLabel(): string { // Records created before partner types were introduced are courier tasks. try { $partnerType = $this->payload['partner_type'] ?? 'courier'; } catch (DecryptException) { // Keep legacy rows with unreadable encrypted payloads renderable. $partnerType = 'courier'; } return $partnerType === 'supplier' ? '供应商' : '快递公司'; } /** Only completed archives with a readable courier snapshot belong to contract management. */ public function archivedCourierContractPayload(): ?array { if ($this->status !== self::STATUS_ARCHIVED || $this->document_type !== 'cooperation-contract') { return null; } try { $payload = $this->payload; } catch (\Throwable) { return null; } // Before partner_type was introduced all signing tasks were courier tasks. return is_array($payload) && ($payload['partner_type'] ?? 'courier') === 'courier' ? $payload : null; } public function credentialDocuments(): array { try { return $this->payload['credential_documents'] ?? []; } catch (\Throwable) { return []; } } 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 => '处理失败', self::STATUS_UNKNOWN => $this->hasDefinitiveFailure() ? '处理失败' : '状态未知,请人工核对', default => $this->status, }; } public function hasDefinitiveFailure(): bool { return EsignErrorMessage::isDefinitiveFailure($this->last_error); } public function lastErrorSummary(): ?string { return EsignErrorMessage::sanitize($this->last_error); } public function canSend(): bool { return in_array($this->status, [self::STATUS_PREPARED, self::STATUS_FAILED], true) && blank($this->esign_sign_flow_id); } public function documentLabel(): string { foreach (\App\Support\SupplierCooperationTemplates::TEMPLATES as $template) { if ($this->template_code === $template['code']) { return $template['name']; } } return self::DOCUMENTS[$this->document_type]['name'] ?? $this->document_type; } /** * The company that initiated the request is kept in the encrypted request * snapshot. It is separate from organization_name, which is the E-sign * signing organization for the counterparty. */ public function initiatorName(): string { try { $payloadName = trim((string) data_get($this->payload, 'signing_subject', '')); } catch (\Throwable) { // 历史记录可能由已轮换的 APP_KEY 加密。列表仍应可读, // 使用未加密的公司快照作为发起人回退值。 $payloadName = ''; } return $payloadName !== '' ? $payloadName : (trim((string) $this->company_full_name) ?: '—'); } /** * Return the archive mode without allowing an unreadable historical * payload to turn the signing task list into a 500 response. */ public function archiveMode(): string { try { $mode = (string) data_get($this->payload, 'archive_mode', 'auto'); } catch (\Throwable) { $mode = 'auto'; } return in_array($mode, ['auto', 'manual'], true) ? $mode : 'auto'; } public function archiveReason(): string { try { return trim((string) data_get($this->payload, 'archive_reason', '')); } catch (\Throwable) { return ''; } } }