746 lines
29 KiB
PHP
746 lines
29 KiB
PHP
<?php
|
|
|
|
namespace app\controller\app;
|
|
|
|
use app\model\Report;
|
|
use app\support\AppraisalEvidenceService;
|
|
use app\support\AppAuthService;
|
|
use app\support\ContentService;
|
|
use app\support\FileStorageService;
|
|
use app\support\ReportPdfGenerator;
|
|
use support\Request;
|
|
use support\think\Db;
|
|
|
|
class ReportsController
|
|
{
|
|
public function index(Request $request)
|
|
{
|
|
$userId = app_user_id($request);
|
|
$rows = Db::name('orders')
|
|
->alias('o')
|
|
->leftJoin('reports r', 'r.order_id = o.id AND r.report_status = "published"')
|
|
->leftJoin('order_products p', 'p.order_id = o.id')
|
|
->field([
|
|
'o.id AS order_id',
|
|
'o.order_status',
|
|
'o.display_status',
|
|
'o.service_provider',
|
|
'p.product_name',
|
|
'p.product_cover',
|
|
'r.id AS report_id',
|
|
'r.report_no',
|
|
'r.institution_name',
|
|
'r.publish_time',
|
|
])
|
|
->where('o.user_id', $userId)
|
|
->whereIn('o.order_status', ['in_first_review', 'in_final_review', 'generating_report', 'report_published', 'completed'])
|
|
->whereRaw('r.id IS NOT NULL')
|
|
->order('o.id', 'desc')
|
|
->select()
|
|
->toArray();
|
|
|
|
$list = array_map(function (array $item) {
|
|
$published = !empty($item['report_id']);
|
|
return [
|
|
'report_id' => $published ? (int)$item['report_id'] : null,
|
|
'order_id' => (int)$item['order_id'],
|
|
'report_no' => $item['report_no'] ?: '',
|
|
'product_name' => $item['product_name'] ?: '',
|
|
'product_cover' => $item['product_cover'] ?: '',
|
|
'service_provider' => $item['service_provider'],
|
|
'status' => $published ? '已出报告' : '待出报告',
|
|
'result_text' => $published ? '正品' : '待出报告',
|
|
'institution_name' => $this->displayInstitutionName((string)$item['service_provider']),
|
|
'publish_time' => $item['publish_time'],
|
|
];
|
|
}, $rows);
|
|
|
|
return api_success(['list' => $list]);
|
|
}
|
|
|
|
public function detail(Request $request)
|
|
{
|
|
$id = (int)$request->input('id', 0);
|
|
$reportNo = trim((string)$request->input('report_no', ''));
|
|
if (!$id && $reportNo === '') {
|
|
return api_error('报告标识不能为空', 422);
|
|
}
|
|
|
|
$report = null;
|
|
if ($reportNo !== '') {
|
|
$report = Report::where('report_status', 'published')
|
|
->where('report_no', $reportNo)
|
|
->find();
|
|
} elseif ($id > 0) {
|
|
$userInfo = app_user($request) ?: (new AppAuthService())->current($request);
|
|
if (!$userInfo) {
|
|
return api_error('未登录或登录已过期', 401);
|
|
}
|
|
|
|
$report = Db::name('reports')
|
|
->alias('r')
|
|
->join('orders o', 'o.id = r.order_id')
|
|
->where('r.id', $id)
|
|
->where('r.report_status', 'published')
|
|
->where('o.user_id', (int)$userInfo['id'])
|
|
->field('r.*')
|
|
->find();
|
|
}
|
|
|
|
if (!$report) {
|
|
return api_error('报告不存在', 404);
|
|
}
|
|
|
|
$reportData = is_array($report) ? $report : $report->toArray();
|
|
$content = Db::name('report_contents')->where('report_id', $reportData['id'])->find();
|
|
$verify = Db::name('report_verifies')->where('report_id', $reportData['id'])->find() ?: [];
|
|
$verify = $this->normalizeVerifyInfo($reportData, $verify);
|
|
$evidenceAttachments = $this->evidenceService()->normalize($content['evidence_attachments_json'] ?? null, $request);
|
|
$zhongjianReportFiles = $this->evidenceService()->normalize($content['zhongjian_report_files_json'] ?? null, $request);
|
|
$defaultRiskNotice = (new ContentService())->getReportRiskNotice((string)($reportData['report_type'] ?? 'appraisal'));
|
|
$payload = [
|
|
'product_snapshot' => $this->decodeJsonField($content['product_snapshot_json'] ?? null),
|
|
'result_snapshot' => $this->decodeJsonField($content['result_snapshot_json'] ?? null),
|
|
'appraisal_snapshot' => $this->decodeJsonField($content['appraisal_snapshot_json'] ?? null),
|
|
'valuation_snapshot' => $this->decodeJsonField($content['valuation_snapshot_json'] ?? null),
|
|
'risk_notice_text' => ($content['risk_notice_text'] ?? '') !== '' ? $content['risk_notice_text'] : $defaultRiskNotice,
|
|
];
|
|
$productDisplay = $this->buildProductDisplay(
|
|
$reportData,
|
|
$payload['product_snapshot'],
|
|
$payload['result_snapshot'],
|
|
$payload['valuation_snapshot'],
|
|
$payload['appraisal_snapshot']
|
|
);
|
|
$reportMedia = [
|
|
'images' => $this->filterAssetsByType($evidenceAttachments, 'image'),
|
|
];
|
|
$traceInfoVisible = (int)($reportData['trace_info_visible'] ?? 0) === 1;
|
|
$traceInfo = $traceInfoVisible
|
|
? $this->buildTraceInfo(
|
|
(int)($reportData['order_id'] ?? 0),
|
|
$payload['appraisal_snapshot'],
|
|
$evidenceAttachments,
|
|
$request
|
|
)
|
|
: ['visible' => false, 'nodes' => []];
|
|
$traceInfo['visible'] = $traceInfoVisible;
|
|
$pdfProductDisplay = $productDisplay;
|
|
$pdfProductDisplay['items'] = array_values(array_filter(
|
|
$productDisplay['items'] ?? [],
|
|
fn (array $item) => ($item['label'] ?? '') !== '服务类型'
|
|
));
|
|
$pdfUrl = $this->ensurePdfFile($request, $reportData, $content ?: [], $verify ?: [], $pdfProductDisplay, $reportMedia);
|
|
|
|
return api_success([
|
|
'report_header' => [
|
|
'report_id' => (int)$reportData['id'],
|
|
'report_no' => $reportData['report_no'],
|
|
'report_type' => $reportData['report_type'] ?? 'appraisal',
|
|
'report_title' => $reportData['report_title'],
|
|
'report_status' => $reportData['report_status'],
|
|
'service_provider' => $reportData['service_provider'],
|
|
'service_provider_text' => $this->serviceProviderText((string)$reportData['service_provider']),
|
|
'institution_name' => $this->displayInstitutionName((string)$reportData['service_provider']),
|
|
'publish_time' => $reportData['publish_time'],
|
|
'zhongjian_report_no' => (string)($reportData['zhongjian_report_no'] ?? ''),
|
|
'report_entry_admin_name' => (string)($reportData['report_entry_admin_name'] ?? ''),
|
|
'report_entered_at' => (string)($reportData['report_entered_at'] ?? ''),
|
|
'trace_info_visible' => $traceInfoVisible,
|
|
],
|
|
'result_info' => $payload['result_snapshot'],
|
|
'product_info' => $payload['product_snapshot'],
|
|
'appraisal_info' => $payload['appraisal_snapshot'],
|
|
'valuation_info' => $payload['valuation_snapshot'],
|
|
'evidence_attachments' => $evidenceAttachments,
|
|
'zhongjian_report_files' => $zhongjianReportFiles,
|
|
'report_media' => $reportMedia,
|
|
'product_display' => $productDisplay,
|
|
'trace_info' => $traceInfo,
|
|
'risk_notice_text' => $payload['risk_notice_text'],
|
|
'verify_info' => [
|
|
'report_no' => $reportData['report_no'],
|
|
'verify_status' => $verify['verify_status'] ?? 'valid',
|
|
'verify_url' => $verify['verify_url'] ?? '',
|
|
'verify_qrcode_url' => $verify['verify_qrcode_url'] ?? '',
|
|
],
|
|
'file_info' => [
|
|
'pdf_url' => $pdfUrl,
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function antiCounterfeitVerify(Request $request)
|
|
{
|
|
$reportNo = trim((string)$request->input('report_no', ''));
|
|
$verifyCode = trim((string)$request->input('verify_code', ''));
|
|
if ($reportNo === '' || $verifyCode === '') {
|
|
return api_error('报告编号和防伪查询码不能为空', 422);
|
|
}
|
|
if (!preg_match('/^\d{6}$/', $verifyCode)) {
|
|
return api_error('防伪查询码应为 6 位数字', 422);
|
|
}
|
|
|
|
$report = Db::name('reports')
|
|
->where('report_no', $reportNo)
|
|
->where('report_status', 'published')
|
|
->find();
|
|
if (!$report) {
|
|
return api_success([
|
|
'verify_passed' => false,
|
|
'verify_message' => '未查询到有效报告,请核对报告编号后重试。',
|
|
'verify_count' => 0,
|
|
]);
|
|
}
|
|
|
|
$tag = Db::name('material_tag_codes')
|
|
->where('report_id', (int)$report['id'])
|
|
->where('bind_status', 'bound')
|
|
->find();
|
|
if (!$tag) {
|
|
return api_success([
|
|
'verify_passed' => false,
|
|
'verify_message' => '当前报告尚未绑定防伪吊牌,暂不能完成防伪查询。',
|
|
'verify_count' => 0,
|
|
]);
|
|
}
|
|
|
|
$batch = Db::name('material_batches')->where('id', (int)$tag['batch_id'])->find();
|
|
$active = ($tag['status'] ?? 'active') !== 'invalid'
|
|
&& (!$batch || ($batch['status'] ?? 'active') !== 'invalid');
|
|
$passed = $active
|
|
&& hash_equals((string)$tag['verify_code'], $verifyCode)
|
|
&& (string)($tag['report_no'] ?: $report['report_no']) === $reportNo;
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
if ($passed) {
|
|
Db::name('material_tag_codes')->where('id', (int)$tag['id'])->update([
|
|
'verify_count' => (int)$tag['verify_count'] + 1,
|
|
'last_verified_at' => $now,
|
|
'updated_at' => $now,
|
|
]);
|
|
}
|
|
$this->insertMaterialTagVerifyLog($tag, $reportNo, $verifyCode, $passed, $request, $now);
|
|
|
|
if (!$active) {
|
|
return api_success([
|
|
'verify_passed' => false,
|
|
'verify_message' => '该防伪吊牌已失效,不能进行防伪查询。',
|
|
'verify_count' => (int)$tag['verify_count'],
|
|
]);
|
|
}
|
|
|
|
return api_success([
|
|
'verify_passed' => $passed,
|
|
'verify_message' => $passed
|
|
? '防伪查询通过,该报告编号与吊牌防伪查询码匹配。'
|
|
: '防伪查询码与当前报告不匹配,请核对吊牌后重试。',
|
|
'verify_count' => (int)$tag['verify_count'] + ($passed ? 1 : 0),
|
|
]);
|
|
}
|
|
|
|
private function decodeJsonField(mixed $value): array
|
|
{
|
|
if (is_array($value)) {
|
|
return $value;
|
|
}
|
|
if (is_string($value) && $value !== '') {
|
|
return json_decode($value, true) ?: [];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
private function normalizeVerifyInfo(array $report, array $verify): array
|
|
{
|
|
$reportNo = (string)($report['report_no'] ?? '');
|
|
$verifyPageUrl = $this->buildPublicPageUrl('/pages/verify/result', ['report_no' => $reportNo]);
|
|
|
|
$verify['report_no'] = $verify['report_no'] ?? $reportNo;
|
|
$verify['verify_status'] = $verify['verify_status'] ?? 'valid';
|
|
|
|
$rawVerifyUrl = trim((string)($verify['verify_url'] ?? ''));
|
|
if ($rawVerifyUrl === '' || str_starts_with($rawVerifyUrl, '/api/app/verify')) {
|
|
$verify['verify_url'] = $verifyPageUrl;
|
|
}
|
|
|
|
$rawQrValue = trim((string)($verify['verify_qrcode_url'] ?? ''));
|
|
if ($rawQrValue === '' || str_starts_with($rawQrValue, '/api/app/verify') || str_contains($rawQrValue, '/pages/report/detail')) {
|
|
$verify['verify_qrcode_url'] = $verify['verify_url'];
|
|
}
|
|
|
|
return $verify;
|
|
}
|
|
|
|
private function ensurePdfFile(Request $request, array $report, array $content, array $verify, array $productDisplay, array $reportMedia): string
|
|
{
|
|
$existingFile = Db::name('report_files')
|
|
->where('report_id', (int)$report['id'])
|
|
->where('file_type', 'pdf')
|
|
->find();
|
|
$publishTime = (string)($report['publish_time'] ?: date('Y-m-d H:i:s'));
|
|
$relativeDir = 'uploads/reports/' . date('Ymd', strtotime($publishTime));
|
|
$filename = $report['report_no'] . '-v3.pdf';
|
|
$relativePath = $relativeDir . '/' . $filename;
|
|
|
|
if ($existingFile && !empty($existingFile['file_url'])) {
|
|
$relativeUrl = ltrim((string)$existingFile['file_url'], '/');
|
|
if ($relativeUrl === $relativePath && !$this->shouldRebuildPdf($existingFile, $report, $content) && $this->storage()->exists($relativeUrl)) {
|
|
return $this->storage()->publicUrl($request, $relativeUrl);
|
|
}
|
|
}
|
|
|
|
$productInfo = $this->decodeJsonField($content['product_snapshot_json'] ?? null);
|
|
$resultInfo = $this->decodeJsonField($content['result_snapshot_json'] ?? null);
|
|
|
|
$defaultRiskNotice = (new ContentService())->getReportRiskNotice((string)($report['report_type'] ?? 'appraisal'));
|
|
$generator = new ReportPdfGenerator();
|
|
$pdfBinary = $generator->generate([
|
|
'report_title' => $report['report_title'] ?? '鉴定报告',
|
|
'service_provider_text' => $this->serviceProviderText((string)($report['service_provider'] ?? 'anxinyan')),
|
|
'institution_name' => $this->displayInstitutionName((string)($report['service_provider'] ?? 'anxinyan')),
|
|
'report_no' => $report['report_no'] ?? '',
|
|
'publish_time' => $publishTime,
|
|
'result_text' => $resultInfo['result_text'] ?? '-',
|
|
'result_desc' => $resultInfo['result_desc'] ?? '-',
|
|
'product_name' => $productInfo['product_name'] ?? '-',
|
|
'product_items' => $productDisplay['items'] ?? [],
|
|
'hero_image' => $this->firstPdfHeroImage($reportMedia['images'] ?? []),
|
|
'hero_image_labels' => $this->assetNameList($reportMedia['images'] ?? []),
|
|
'verify_info' => sprintf(
|
|
'%s / %s',
|
|
$verify['report_no'] ?? ($report['report_no'] ?? '-'),
|
|
(($verify['verify_status'] ?? 'valid') === 'valid' ? '有效' : ($verify['verify_status'] ?? '-'))
|
|
),
|
|
'risk_notice_text' => ($content['risk_notice_text'] ?? '') !== '' ? $content['risk_notice_text'] : ($defaultRiskNotice !== '' ? $defaultRiskNotice : '-'),
|
|
]);
|
|
|
|
$this->storage()->putContentsWithMimeType($relativePath, $pdfBinary, 'application/pdf');
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
$filePayload = [
|
|
'report_id' => (int)$report['id'],
|
|
'file_type' => 'pdf',
|
|
'file_url' => '/' . $relativePath,
|
|
'file_status' => 'ready',
|
|
'updated_at' => $now,
|
|
];
|
|
|
|
if ($existingFile) {
|
|
Db::name('report_files')->where('id', $existingFile['id'])->update($filePayload);
|
|
} else {
|
|
$filePayload['created_at'] = $now;
|
|
Db::name('report_files')->insert($filePayload);
|
|
}
|
|
|
|
return $this->storage()->publicUrl($request, $relativePath);
|
|
}
|
|
|
|
private function buildProductDisplay(array $report, array $productInfo, array $resultInfo, array $valuationInfo = [], array $appraisalInfo = []): array
|
|
{
|
|
$items = [];
|
|
$this->appendDisplayItem(
|
|
$items,
|
|
'检测结论',
|
|
$this->textValue($resultInfo['result_text'] ?? '') ?: '-',
|
|
$this->textValue($resultInfo['result_desc'] ?? ''),
|
|
true
|
|
);
|
|
|
|
foreach ([
|
|
'品类' => $productInfo['category_name'] ?? '',
|
|
'品牌' => $productInfo['brand_name'] ?? '',
|
|
'颜色' => $productInfo['color'] ?? '',
|
|
'规格/尺寸' => $productInfo['size_spec'] ?? '',
|
|
'序列号/编码' => $productInfo['serial_no'] ?? '',
|
|
] as $label => $value) {
|
|
$this->appendDisplayItem($items, $label, $value);
|
|
}
|
|
|
|
foreach (($resultInfo['key_points'] ?? []) as $point) {
|
|
if (!is_array($point)) {
|
|
continue;
|
|
}
|
|
$label = $this->textValue($point['point_name'] ?? '');
|
|
if ($label === '') {
|
|
continue;
|
|
}
|
|
if ($this->displayItemIndex($items, $label) !== null) {
|
|
continue;
|
|
}
|
|
$this->appendDisplayItem(
|
|
$items,
|
|
$label,
|
|
$this->textValue($point['point_value'] ?? '') ?: '-',
|
|
'',
|
|
true
|
|
);
|
|
}
|
|
|
|
$this->appendDisplayItem(
|
|
$items,
|
|
'服务类型',
|
|
$this->serviceProviderText((string)($report['service_provider'] ?? 'anxinyan'))
|
|
);
|
|
$appraiserName = $this->textValue($appraisalInfo['appraiser_name'] ?? '')
|
|
?: $this->textValue($appraisalInfo['reviewer_name'] ?? '')
|
|
?: $this->textValue($report['report_entry_admin_name'] ?? '');
|
|
$this->appendDisplayItem($items, '鉴定师', $appraiserName);
|
|
|
|
$conditionGrade = $this->textValue($valuationInfo['condition_grade'] ?? '');
|
|
$conditionDesc = $this->textValue($valuationInfo['condition_desc'] ?? '');
|
|
if ($conditionGrade !== '' || $conditionDesc !== '') {
|
|
$this->appendDisplayItem($items, '成色评级', $conditionGrade ?: '-', $conditionDesc, true);
|
|
}
|
|
|
|
$valuationRange = $this->formatValuationRange($valuationInfo['valuation_min'] ?? 0, $valuationInfo['valuation_max'] ?? 0);
|
|
$valuationDesc = $this->textValue($valuationInfo['valuation_desc'] ?? '');
|
|
if ($valuationRange !== '' || $valuationDesc !== '') {
|
|
$this->appendDisplayItem($items, '估值区间', $valuationRange ?: '-', $valuationDesc, true);
|
|
}
|
|
|
|
$externalRemark = $this->textValue($resultInfo['external_remark'] ?? '');
|
|
if ($externalRemark !== '') {
|
|
$this->appendDisplayItem($items, '备注', $externalRemark);
|
|
}
|
|
|
|
return [
|
|
'product_name' => $this->textValue($productInfo['product_name'] ?? '') ?: '-',
|
|
'institution_name' => $this->displayInstitutionName((string)($report['service_provider'] ?? 'anxinyan')),
|
|
'items' => $items,
|
|
];
|
|
}
|
|
|
|
private function displayItemIndex(array $items, string $label): ?int
|
|
{
|
|
foreach ($items as $index => $item) {
|
|
if (($item['label'] ?? '') === $label) {
|
|
return $index;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function appendDisplayItem(array &$items, string $label, mixed $value, mixed $remark = '', bool $keepEmpty = false): void
|
|
{
|
|
$valueText = $this->textValue($value);
|
|
$remarkText = $this->textValue($remark);
|
|
if (!$keepEmpty && $valueText === '' && $remarkText === '') {
|
|
return;
|
|
}
|
|
|
|
$items[] = [
|
|
'label' => $label,
|
|
'value' => $valueText !== '' ? $valueText : '-',
|
|
'remark' => $remarkText,
|
|
];
|
|
}
|
|
|
|
private function formatValuationRange(mixed $min, mixed $max): string
|
|
{
|
|
$minValue = (float)($min ?? 0);
|
|
$maxValue = (float)($max ?? 0);
|
|
if ($minValue <= 0 && $maxValue <= 0) {
|
|
return '';
|
|
}
|
|
if ($minValue > 0 && $maxValue > 0) {
|
|
return '¥' . $this->formatMoney($minValue) . ' - ¥' . $this->formatMoney($maxValue);
|
|
}
|
|
if ($minValue > 0) {
|
|
return '¥' . $this->formatMoney($minValue) . ' 起';
|
|
}
|
|
return '¥' . $this->formatMoney($maxValue) . ' 内';
|
|
}
|
|
|
|
private function formatMoney(float $value): string
|
|
{
|
|
return rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.');
|
|
}
|
|
|
|
private function buildTraceInfo(int $orderId, array $appraisalInfo, array $evidenceAttachments, Request $request): array
|
|
{
|
|
$logs = $orderId > 0
|
|
? Db::name('order_transfer_flow_logs')
|
|
->where('order_id', $orderId)
|
|
->whereIn('action_code', ['inbound_received', 'return_shipped'])
|
|
->order('id', 'asc')
|
|
->select()
|
|
->toArray()
|
|
: [];
|
|
|
|
$inboundLog = $this->findFlowLog($logs, 'inbound_received');
|
|
$returnLog = $this->findFlowLog($logs, 'return_shipped');
|
|
$inboundPayload = $this->decodeJsonObject($inboundLog['payload_json'] ?? null);
|
|
$returnPayload = $this->decodeJsonObject($returnLog['payload_json'] ?? null);
|
|
$appraisalFinishedAt = $this->appraisalFinishedAt($orderId, $appraisalInfo);
|
|
$appraisalVideoAssets = $this->filterAssetsByType($evidenceAttachments, 'video');
|
|
|
|
$nodes = [
|
|
[
|
|
'code' => 'inbound',
|
|
'title' => '入仓',
|
|
'occurred_at' => (string)($inboundLog['created_at'] ?? ''),
|
|
'status' => $inboundLog ? 'completed' : 'pending',
|
|
'assets' => $this->evidenceService()->normalize($inboundPayload['inbound_attachments'] ?? [], $request),
|
|
],
|
|
[
|
|
'code' => 'appraisal',
|
|
'title' => '鉴定',
|
|
'occurred_at' => $appraisalFinishedAt,
|
|
'status' => ($appraisalFinishedAt !== '' || $appraisalVideoAssets) ? 'completed' : 'pending',
|
|
'assets' => $appraisalVideoAssets,
|
|
],
|
|
[
|
|
'code' => 'return',
|
|
'title' => '寄回',
|
|
'occurred_at' => (string)($returnLog['created_at'] ?? ''),
|
|
'status' => $returnLog ? 'completed' : 'pending',
|
|
'assets' => $this->evidenceService()->normalize($returnPayload['packing_attachments'] ?? [], $request),
|
|
],
|
|
];
|
|
|
|
return ['nodes' => $nodes];
|
|
}
|
|
|
|
private function findFlowLog(array $logs, string $actionCode): ?array
|
|
{
|
|
$matched = null;
|
|
foreach ($logs as $log) {
|
|
if (($log['action_code'] ?? '') === $actionCode) {
|
|
$matched = $log;
|
|
}
|
|
}
|
|
return $matched;
|
|
}
|
|
|
|
private function appraisalFinishedAt(int $orderId, array $appraisalInfo): string
|
|
{
|
|
if ($orderId > 0) {
|
|
$submittedAt = Db::name('appraisal_tasks')
|
|
->where('order_id', $orderId)
|
|
->whereRaw('submitted_at IS NOT NULL')
|
|
->order('submitted_at', 'desc')
|
|
->value('submitted_at');
|
|
if ($submittedAt) {
|
|
return (string)$submittedAt;
|
|
}
|
|
}
|
|
|
|
return $this->textValue($appraisalInfo['appraisal_time'] ?? '');
|
|
}
|
|
|
|
private function filterAssetsByType(array $assets, string $fileType): array
|
|
{
|
|
return array_values(array_filter($assets, fn (array $asset) => (string)($asset['file_type'] ?? '') === $fileType));
|
|
}
|
|
|
|
private function shouldRebuildPdf(array $existingFile, array $report, array $content): bool
|
|
{
|
|
$fileUpdatedAt = strtotime((string)($existingFile['updated_at'] ?? '')) ?: 0;
|
|
if ($fileUpdatedAt <= 0) {
|
|
return true;
|
|
}
|
|
|
|
foreach ([$report['updated_at'] ?? '', $content['updated_at'] ?? ''] as $timestamp) {
|
|
if ((strtotime((string)$timestamp) ?: 0) > $fileUpdatedAt) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function firstPdfHeroImage(array $images): ?array
|
|
{
|
|
foreach ($images as $image) {
|
|
$binary = $this->readAssetBinary((string)($image['file_url'] ?? ''));
|
|
if ($binary === '' || strlen($binary) > 5 * 1024 * 1024) {
|
|
continue;
|
|
}
|
|
|
|
$info = @getimagesizefromstring($binary);
|
|
if (!$info) {
|
|
continue;
|
|
}
|
|
|
|
if ((int)($info[2] ?? 0) !== IMAGETYPE_JPEG) {
|
|
$converted = $this->convertImageBinaryToJpeg($binary);
|
|
if ($converted === '') {
|
|
continue;
|
|
}
|
|
$binary = $converted;
|
|
}
|
|
|
|
return [
|
|
'data' => $binary,
|
|
'width' => (int)$info[0],
|
|
'height' => (int)$info[1],
|
|
];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function convertImageBinaryToJpeg(string $binary): string
|
|
{
|
|
if (!function_exists('imagecreatefromstring')) {
|
|
return '';
|
|
}
|
|
|
|
$image = @imagecreatefromstring($binary);
|
|
if (!$image) {
|
|
return '';
|
|
}
|
|
|
|
$width = imagesx($image);
|
|
$height = imagesy($image);
|
|
$canvas = imagecreatetruecolor($width, $height);
|
|
if (!$canvas) {
|
|
imagedestroy($image);
|
|
return '';
|
|
}
|
|
|
|
$background = imagecolorallocate($canvas, 255, 255, 255);
|
|
imagefill($canvas, 0, 0, $background);
|
|
imagecopy($canvas, $image, 0, 0, 0, 0, $width, $height);
|
|
ob_start();
|
|
imagejpeg($canvas, null, 86);
|
|
$jpeg = (string)ob_get_clean();
|
|
imagedestroy($canvas);
|
|
imagedestroy($image);
|
|
|
|
return $jpeg;
|
|
}
|
|
|
|
private function readAssetBinary(string $fileUrl): string
|
|
{
|
|
$fileUrl = trim($fileUrl);
|
|
if ($fileUrl === '') {
|
|
return '';
|
|
}
|
|
|
|
$relativePath = $this->storage()->storagePath($fileUrl);
|
|
$localPath = public_path() . '/' . $relativePath;
|
|
if (is_file($localPath)) {
|
|
return (string)file_get_contents($localPath);
|
|
}
|
|
|
|
if (preg_match('/^https?:\/\//i', $fileUrl)) {
|
|
$context = stream_context_create([
|
|
'http' => ['timeout' => 3],
|
|
'ssl' => ['verify_peer' => false, 'verify_peer_name' => false],
|
|
]);
|
|
return (string)(@file_get_contents($fileUrl, false, $context) ?: '');
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
private function assetNameList(array $assets): array
|
|
{
|
|
return array_values(array_filter(array_map(function (array $asset) {
|
|
return $this->textValue($asset['name'] ?? '') ?: $this->textValue($asset['file_id'] ?? '');
|
|
}, $assets)));
|
|
}
|
|
|
|
private function insertMaterialTagVerifyLog(array $tag, string $reportNo, string $verifyCode, bool $passed, Request $request, string $now): void
|
|
{
|
|
Db::name('material_tag_scan_logs')->insert([
|
|
'tag_code_id' => (int)$tag['id'],
|
|
'batch_id' => (int)$tag['batch_id'],
|
|
'report_id' => (int)($tag['report_id'] ?? 0) ?: null,
|
|
'report_no' => $reportNo,
|
|
'verify_type' => 'report_page_code',
|
|
'verify_code_input' => mb_substr($verifyCode, 0, 16),
|
|
'verify_passed' => $passed ? 1 : 0,
|
|
'ip' => (string)$request->getRealIp(),
|
|
'user_agent' => mb_substr((string)$request->header('user-agent', ''), 0, 500),
|
|
'scanned_at' => $now,
|
|
'created_at' => $now,
|
|
]);
|
|
}
|
|
|
|
private function decodeJsonObject(mixed $value): array
|
|
{
|
|
if (is_array($value)) {
|
|
return $value;
|
|
}
|
|
if (is_string($value) && $value !== '') {
|
|
$decoded = json_decode($value, true);
|
|
return is_array($decoded) ? $decoded : [];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
private function textValue(mixed $value): string
|
|
{
|
|
return trim((string)($value ?? ''));
|
|
}
|
|
|
|
private function serviceProviderText(string $serviceProvider): string
|
|
{
|
|
return $serviceProvider === 'zhongjian' ? '中检鉴定' : '实物鉴定';
|
|
}
|
|
|
|
private function displayInstitutionName(string $serviceProvider): string
|
|
{
|
|
return $serviceProvider === 'zhongjian' ? '中检鉴定中心' : '安心检验';
|
|
}
|
|
|
|
private function buildPublicPageUrl(string $pagePath, array $query = []): string
|
|
{
|
|
$baseUrl = $this->normalizeH5BaseUrl($this->getSystemConfigValue('h5', 'page_base_url'));
|
|
$page = ltrim($pagePath, '/');
|
|
$queryString = http_build_query($query);
|
|
$hashPath = '/#/' . $page;
|
|
if ($queryString !== '') {
|
|
$hashPath .= '?' . $queryString;
|
|
}
|
|
|
|
if ($baseUrl === '') {
|
|
return $hashPath;
|
|
}
|
|
|
|
return $baseUrl . $hashPath;
|
|
}
|
|
|
|
private function normalizeH5BaseUrl(string $value): string
|
|
{
|
|
$baseUrl = trim($value);
|
|
if ($baseUrl === '') {
|
|
return '';
|
|
}
|
|
|
|
$hashPos = strpos($baseUrl, '#');
|
|
if ($hashPos !== false) {
|
|
$baseUrl = substr($baseUrl, 0, $hashPos);
|
|
}
|
|
|
|
if (!preg_match('/^https?:\/\//i', $baseUrl)) {
|
|
$baseUrl = 'https://' . ltrim($baseUrl, '/');
|
|
}
|
|
|
|
return rtrim($baseUrl, '/');
|
|
}
|
|
|
|
private function getSystemConfigValue(string $groupCode, string $configKey): string
|
|
{
|
|
$row = Db::name('system_configs')
|
|
->where('config_group', $groupCode)
|
|
->where('config_key', $configKey)
|
|
->find();
|
|
|
|
return trim((string)($row['config_value'] ?? ''));
|
|
}
|
|
|
|
private function evidenceService(): AppraisalEvidenceService
|
|
{
|
|
return new AppraisalEvidenceService();
|
|
}
|
|
|
|
private function storage(): FileStorageService
|
|
{
|
|
return new FileStorageService();
|
|
}
|
|
}
|