771 lines
32 KiB
PHP
771 lines
32 KiB
PHP
<?php
|
|
|
|
namespace app\controller\admin;
|
|
|
|
use app\support\AppraisalEvidenceService;
|
|
use app\support\ContentService;
|
|
use app\support\EnterpriseWebhookService;
|
|
use app\support\FulfillmentFlowService;
|
|
use app\support\MaterialTagService;
|
|
use app\support\MessageDispatcher;
|
|
use support\Request;
|
|
use support\think\Db;
|
|
|
|
class ReportsController
|
|
{
|
|
public function index(Request $request)
|
|
{
|
|
$keyword = trim((string)$request->input('keyword', ''));
|
|
$status = trim((string)$request->input('status', ''));
|
|
$serviceProvider = trim((string)$request->input('service_provider', ''));
|
|
$paginationEnabled = $request->input('page', null) !== null || $request->input('page_size', null) !== null;
|
|
$page = max(1, (int)$request->input('page', 1));
|
|
$pageSize = max(1, min(100, (int)$request->input('page_size', 20)));
|
|
|
|
$query = Db::name('reports')
|
|
->alias('r')
|
|
->leftJoin('orders o', 'o.id = r.order_id')
|
|
->leftJoin('order_products p', 'p.order_id = r.order_id')
|
|
->leftJoin('material_tag_codes mt', 'mt.report_id = r.id')
|
|
->field([
|
|
'r.id',
|
|
'r.report_no',
|
|
'r.order_id',
|
|
'r.appraisal_no',
|
|
'r.report_type',
|
|
'r.report_title',
|
|
'r.report_status',
|
|
'r.service_provider',
|
|
'r.institution_name',
|
|
'r.publish_time',
|
|
'r.zhongjian_report_no',
|
|
'r.report_entry_admin_name',
|
|
'r.report_entered_at',
|
|
'o.order_no',
|
|
'p.product_name',
|
|
'p.category_name',
|
|
'p.brand_name',
|
|
'mt.id as material_tag_id',
|
|
'mt.verify_code as material_tag_verify_code',
|
|
'mt.bind_status as material_tag_bind_status',
|
|
])
|
|
->order('r.id', 'desc');
|
|
|
|
if ($status !== '') {
|
|
$query->where('r.report_status', $status);
|
|
}
|
|
|
|
if ($serviceProvider !== '') {
|
|
$query->where('r.service_provider', $serviceProvider);
|
|
}
|
|
|
|
$rows = $query->select()->toArray();
|
|
$contentMap = $this->loadReportContentMap(array_map(fn(array $item) => (int)$item['id'], $rows));
|
|
|
|
$list = [];
|
|
foreach ($rows as $item) {
|
|
$productSnapshot = $contentMap[(int)$item['id']]['product_snapshot'] ?? [];
|
|
$mapped = [
|
|
'id' => (int)$item['id'],
|
|
'order_id' => (int)($item['order_id'] ?? 0),
|
|
'order_no' => $item['order_no'] ?? '',
|
|
'appraisal_no' => $item['appraisal_no'] ?? '',
|
|
'report_no' => $item['report_no'],
|
|
'report_type' => $item['report_type'] ?: 'appraisal',
|
|
'report_type_text' => $this->reportTypeText($item['report_type'] ?: 'appraisal'),
|
|
'report_title' => $item['report_title'],
|
|
'report_status' => $item['report_status'],
|
|
'report_status_text' => $this->reportStatusText($item['report_status']),
|
|
'service_provider' => $item['service_provider'],
|
|
'service_provider_text' => $this->serviceProviderText($item['service_provider']),
|
|
'institution_name' => $this->defaultInstitutionName((string)$item['service_provider']),
|
|
'publish_time' => $item['publish_time'],
|
|
'zhongjian_report_no' => (string)($item['zhongjian_report_no'] ?? ''),
|
|
'report_entry_admin_name' => (string)($item['report_entry_admin_name'] ?? ''),
|
|
'report_entered_at' => (string)($item['report_entered_at'] ?? ''),
|
|
'product_name' => $item['product_name'] ?: (string)($productSnapshot['product_name'] ?? ''),
|
|
'category_name' => $item['category_name'] ?: (string)($productSnapshot['category_name'] ?? ''),
|
|
'brand_name' => $item['brand_name'] ?: (string)($productSnapshot['brand_name'] ?? ''),
|
|
'material_tag_bound' => (int)($item['material_tag_id'] ?? 0) > 0,
|
|
'material_tag_verify_code' => (string)($item['material_tag_verify_code'] ?? ''),
|
|
'material_tag_bind_status' => (string)($item['material_tag_bind_status'] ?? ''),
|
|
];
|
|
|
|
if ($keyword !== '' && !$this->matchKeyword($mapped, $keyword)) {
|
|
continue;
|
|
}
|
|
|
|
$list[] = $mapped;
|
|
}
|
|
|
|
$total = count($list);
|
|
if ($paginationEnabled) {
|
|
$offset = ($page - 1) * $pageSize;
|
|
$list = array_slice($list, $offset, $pageSize);
|
|
|
|
return api_success([
|
|
'list' => $list,
|
|
'total' => $total,
|
|
'page' => $page,
|
|
'page_size' => $pageSize,
|
|
]);
|
|
}
|
|
|
|
return api_success(['list' => $list]);
|
|
}
|
|
|
|
public function detail(Request $request)
|
|
{
|
|
$id = (int)$request->input('id', 0);
|
|
if (!$id) {
|
|
return api_error('报告 ID 不能为空', 422);
|
|
}
|
|
|
|
$report = Db::name('reports')->where('id', $id)->find();
|
|
if (!$report) {
|
|
return api_error('报告不存在', 404);
|
|
}
|
|
|
|
$content = Db::name('report_contents')->where('report_id', $id)->find();
|
|
$productSnapshot = $this->decodeJsonField($content['product_snapshot_json'] ?? null);
|
|
$resultSnapshot = $this->decodeJsonField($content['result_snapshot_json'] ?? null);
|
|
$appraisalSnapshot = $this->decodeJsonField($content['appraisal_snapshot_json'] ?? null);
|
|
$valuationSnapshot = $this->decodeJsonField($content['valuation_snapshot_json'] ?? null);
|
|
$zhongjianReportFiles = $this->evidenceService()->normalize($content['zhongjian_report_files_json'] ?? null, $request);
|
|
$appraisalSnapshot = $this->enrichAppraisalSnapshot($report, $appraisalSnapshot);
|
|
$evidenceAttachments = $this->evidenceService()->normalize($content['evidence_attachments_json'] ?? null, $request);
|
|
$materialTag = (new MaterialTagService())->findBoundTagForReport($id);
|
|
|
|
$verify = Db::name('report_verifies')->where('report_id', $id)->find() ?: [];
|
|
if (($report['report_status'] ?? '') === 'published') {
|
|
$verify = $this->createOrUpdateVerifyRecord($report, date('Y-m-d H:i:s'));
|
|
}
|
|
|
|
$reportPageUrl = $this->buildPublicPageUrl('/pages/report/detail', ['report_no' => $report['report_no']]);
|
|
$verifyUrl = $this->buildPublicPageUrl('/pages/verify/result', ['report_no' => $report['report_no']]);
|
|
if (!$verify) {
|
|
$verify = [];
|
|
}
|
|
$verify['report_page_url'] = $verify['report_page_url'] ?? $reportPageUrl;
|
|
$verify['verify_qrcode_url'] = $verify['verify_qrcode_url'] ?? $reportPageUrl;
|
|
$verify['verify_url'] = $verify['verify_url'] ?? $verifyUrl;
|
|
$defaultRiskNotice = (new ContentService())->getReportRiskNotice((string)($report['report_type'] ?? 'appraisal'));
|
|
|
|
return api_success([
|
|
'report_header' => [
|
|
'id' => (int)$report['id'],
|
|
'order_id' => (int)($report['order_id'] ?? 0),
|
|
'report_no' => $report['report_no'],
|
|
'report_type' => $report['report_type'] ?: 'appraisal',
|
|
'report_type_text' => $this->reportTypeText($report['report_type'] ?: 'appraisal'),
|
|
'report_title' => $report['report_title'],
|
|
'report_status' => $report['report_status'],
|
|
'report_status_text' => $this->reportStatusText($report['report_status']),
|
|
'service_provider' => $report['service_provider'],
|
|
'service_provider_text' => $this->serviceProviderText($report['service_provider']),
|
|
'institution_name' => $this->defaultInstitutionName((string)$report['service_provider']),
|
|
'publish_time' => $report['publish_time'],
|
|
'zhongjian_report_no' => (string)($report['zhongjian_report_no'] ?? ''),
|
|
'report_entry_admin_id' => (int)($report['report_entry_admin_id'] ?? 0),
|
|
'report_entry_admin_name' => (string)($report['report_entry_admin_name'] ?? ''),
|
|
'report_entered_at' => (string)($report['report_entered_at'] ?? ''),
|
|
],
|
|
'product_info' => $productSnapshot,
|
|
'result_info' => $resultSnapshot,
|
|
'appraisal_info' => $appraisalSnapshot,
|
|
'valuation_info' => $valuationSnapshot,
|
|
'evidence_attachments' => $evidenceAttachments,
|
|
'zhongjian_report_files' => $zhongjianReportFiles,
|
|
'material_tag' => $materialTag,
|
|
'risk_notice_text' => ($content['risk_notice_text'] ?? '') !== '' ? $content['risk_notice_text'] : $defaultRiskNotice,
|
|
'verify_info' => [
|
|
'verify_status' => $verify['verify_status'] ?? (($report['report_status'] ?? '') === 'published' ? 'valid' : 'pending'),
|
|
'verify_url' => $verify['verify_url'] ?? $verifyUrl,
|
|
'verify_qrcode_url' => $verify['verify_qrcode_url'] ?? $reportPageUrl,
|
|
'report_page_url' => $verify['report_page_url'] ?? $reportPageUrl,
|
|
'verify_count' => (int)($verify['verify_count'] ?? 0),
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function saveInspection(Request $request)
|
|
{
|
|
$id = (int)$request->input('id', 0);
|
|
$header = $request->input('report_header', []);
|
|
$productInfo = $request->input('product_info', []);
|
|
$resultInfo = $request->input('result_info', []);
|
|
$appraisalInfo = $request->input('appraisal_info', []);
|
|
$valuationInfo = $request->input('valuation_info', []);
|
|
$riskNoticeText = trim((string)$request->input('risk_notice_text', ''));
|
|
|
|
if (!is_array($header) || !is_array($productInfo) || !is_array($resultInfo) || !is_array($appraisalInfo) || !is_array($valuationInfo)) {
|
|
return api_error('检查单参数格式错误', 422);
|
|
}
|
|
|
|
$serviceProvider = trim((string)($header['service_provider'] ?? 'anxinyan'));
|
|
if (!in_array($serviceProvider, ['anxinyan', 'zhongjian'], true)) {
|
|
return api_error('服务类型不正确', 422);
|
|
}
|
|
|
|
$reportStatus = trim((string)($header['report_status'] ?? 'pending_publish'));
|
|
if (!in_array($reportStatus, ['draft', 'pending_publish', 'published'], true)) {
|
|
return api_error('报告状态不正确', 422);
|
|
}
|
|
|
|
$productName = trim((string)($productInfo['product_name'] ?? ''));
|
|
$resultText = trim((string)($resultInfo['result_text'] ?? ''));
|
|
if ($productName === '') {
|
|
return api_error('商品名称不能为空', 422);
|
|
}
|
|
if ($resultText === '') {
|
|
return api_error('鉴定结论不能为空', 422);
|
|
}
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
Db::startTrans();
|
|
try {
|
|
$existing = null;
|
|
if ($id > 0) {
|
|
$existing = Db::name('reports')->where('id', $id)->find();
|
|
if (!$existing || (($existing['report_type'] ?? 'appraisal') !== 'inspection')) {
|
|
Db::rollback();
|
|
return api_error('检查单不存在', 404);
|
|
}
|
|
if (($existing['report_status'] ?? '') === 'published') {
|
|
Db::rollback();
|
|
return api_error('已发布的检查单不支持直接编辑,请复制后重新补录', 422);
|
|
}
|
|
}
|
|
|
|
$reportNo = trim((string)($header['report_no'] ?? ($existing['report_no'] ?? '')));
|
|
if ($reportNo === '') {
|
|
$reportNo = $this->generateUniqueReportNo('inspection');
|
|
}
|
|
|
|
$conflict = Db::name('reports')
|
|
->where('report_no', $reportNo)
|
|
->when($id > 0, fn($query) => $query->where('id', '<>', $id))
|
|
->find();
|
|
if ($conflict) {
|
|
Db::rollback();
|
|
return api_error('检查单编号已存在,请更换后重试', 422);
|
|
}
|
|
|
|
$reportTitle = trim((string)($header['report_title'] ?? ''));
|
|
if ($reportTitle === '') {
|
|
$reportTitle = $this->defaultReportTitle($serviceProvider, 'inspection');
|
|
}
|
|
|
|
$institutionName = trim((string)($header['institution_name'] ?? ''));
|
|
if ($institutionName === '') {
|
|
$institutionName = $this->defaultInstitutionName($serviceProvider);
|
|
}
|
|
|
|
$publishTime = $reportStatus === 'published'
|
|
? trim((string)($header['publish_time'] ?? ($existing['publish_time'] ?? $now)))
|
|
: null;
|
|
|
|
$reportPayload = [
|
|
'report_no' => $reportNo,
|
|
'order_id' => 0,
|
|
'appraisal_no' => $existing['appraisal_no'] ?? $this->generateUniqueAppraisalNo('inspection'),
|
|
'report_type' => 'inspection',
|
|
'service_provider' => $serviceProvider,
|
|
'institution_name' => $institutionName,
|
|
'report_title' => $reportTitle,
|
|
'report_status' => $reportStatus,
|
|
'report_version' => $existing ? ((int)$existing['report_version'] + 1) : 1,
|
|
'publish_time' => $publishTime ?: null,
|
|
'invalid_reason' => '',
|
|
'updated_at' => $now,
|
|
];
|
|
|
|
if ($existing) {
|
|
Db::name('reports')->where('id', $id)->update($reportPayload);
|
|
$reportId = $id;
|
|
} else {
|
|
$reportPayload['created_at'] = $now;
|
|
$reportId = (int)Db::name('reports')->insertGetId($reportPayload);
|
|
}
|
|
|
|
$normalizedProductInfo = [
|
|
'product_name' => $productName,
|
|
'category_name' => trim((string)($productInfo['category_name'] ?? '')),
|
|
'brand_name' => trim((string)($productInfo['brand_name'] ?? '')),
|
|
'color' => trim((string)($productInfo['color'] ?? '')),
|
|
'size_spec' => trim((string)($productInfo['size_spec'] ?? '')),
|
|
'serial_no' => trim((string)($productInfo['serial_no'] ?? '')),
|
|
];
|
|
|
|
$normalizedResultInfo = [
|
|
'result_status' => trim((string)($resultInfo['result_status'] ?? 'authentic')),
|
|
'result_text' => $resultText,
|
|
'result_desc' => trim((string)($resultInfo['result_desc'] ?? '')),
|
|
];
|
|
|
|
$normalizedAppraisalInfo = [
|
|
'service_provider' => $serviceProvider,
|
|
'institution_name' => $institutionName,
|
|
'appraiser_name' => trim((string)($appraisalInfo['appraiser_name'] ?? '')),
|
|
'reviewer_name' => trim((string)($appraisalInfo['reviewer_name'] ?? '')),
|
|
'appraisal_time' => trim((string)($appraisalInfo['appraisal_time'] ?? ($publishTime ?: $now))),
|
|
];
|
|
|
|
$normalizedValuationInfo = [
|
|
'condition_grade' => trim((string)($valuationInfo['condition_grade'] ?? '')),
|
|
'condition_desc' => trim((string)($valuationInfo['condition_desc'] ?? '')),
|
|
'valuation_min' => (float)($valuationInfo['valuation_min'] ?? 0),
|
|
'valuation_max' => (float)($valuationInfo['valuation_max'] ?? 0),
|
|
'valuation_desc' => trim((string)($valuationInfo['valuation_desc'] ?? '')),
|
|
];
|
|
|
|
$contentPayload = [
|
|
'report_id' => $reportId,
|
|
'product_snapshot_json' => json_encode($normalizedProductInfo, JSON_UNESCAPED_UNICODE),
|
|
'result_snapshot_json' => json_encode($normalizedResultInfo, JSON_UNESCAPED_UNICODE),
|
|
'appraisal_snapshot_json' => json_encode($normalizedAppraisalInfo, JSON_UNESCAPED_UNICODE),
|
|
'valuation_snapshot_json' => json_encode($normalizedValuationInfo, JSON_UNESCAPED_UNICODE),
|
|
'risk_notice_text' => $riskNoticeText !== '' ? $riskNoticeText : (new ContentService())->getReportRiskNotice('inspection'),
|
|
'updated_at' => $now,
|
|
];
|
|
|
|
$content = Db::name('report_contents')->where('report_id', $reportId)->find();
|
|
if ($content) {
|
|
Db::name('report_contents')->where('report_id', $reportId)->update($contentPayload);
|
|
} else {
|
|
$contentPayload['created_at'] = $now;
|
|
Db::name('report_contents')->insert($contentPayload);
|
|
}
|
|
|
|
$reportRecord = Db::name('reports')->where('id', $reportId)->find();
|
|
$verifyInfo = [
|
|
'verify_url' => '',
|
|
'report_page_url' => '',
|
|
];
|
|
if ($reportStatus === 'published' && $reportRecord) {
|
|
$verifyInfo = $this->createOrUpdateVerifyRecord($reportRecord, $now);
|
|
} elseif ($reportStatus !== 'published') {
|
|
Db::name('report_verifies')->where('report_id', $reportId)->delete();
|
|
}
|
|
|
|
Db::commit();
|
|
|
|
return api_success([
|
|
'id' => $reportId,
|
|
'report_status' => $reportStatus,
|
|
'publish_time' => $publishTime ?: '',
|
|
'verify_url' => $verifyInfo['verify_url'] ?? '',
|
|
'report_page_url' => $verifyInfo['report_page_url'] ?? '',
|
|
], $existing ? '检查单已更新' : '检查单已补录');
|
|
} catch (\Throwable $e) {
|
|
Db::rollback();
|
|
return api_error('检查单保存失败', 500, [
|
|
'detail' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function publish(Request $request)
|
|
{
|
|
$id = (int)$request->input('id', 0);
|
|
$qrInput = trim((string)$request->input('qr_input', ''));
|
|
if (!$id) {
|
|
return api_error('报告 ID 不能为空', 422);
|
|
}
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
Db::startTrans();
|
|
try {
|
|
$report = Db::name('reports')->where('id', $id)->find();
|
|
if (!$report) {
|
|
Db::rollback();
|
|
return api_error('报告不存在', 404);
|
|
}
|
|
|
|
if (!in_array($report['report_status'], ['draft', 'pending_publish', 'updated', 'published'], true)) {
|
|
Db::rollback();
|
|
return api_error('当前报告状态不支持发布', 422);
|
|
}
|
|
|
|
$effectivePublishTime = $report['publish_time'] ?: $now;
|
|
$isOrderAppraisalReport = ($report['report_type'] ?? 'appraisal') === 'appraisal' && (int)($report['order_id'] ?? 0) > 0;
|
|
$materialTag = null;
|
|
if ($isOrderAppraisalReport) {
|
|
$materialTag = (new MaterialTagService())->findBoundTagForReport($id);
|
|
if (!$materialTag) {
|
|
if ($qrInput === '') {
|
|
Db::rollback();
|
|
return api_error('请扫描验真吊牌二维码后再发布报告', 422);
|
|
}
|
|
|
|
$task = Db::name('appraisal_tasks')
|
|
->where('order_id', (int)$report['order_id'])
|
|
->order('id', 'desc')
|
|
->find();
|
|
if (!$task) {
|
|
Db::rollback();
|
|
return api_error('报告未关联鉴定任务,不能绑定吊牌发布', 422);
|
|
}
|
|
|
|
$materialTag = (new MaterialTagService())->bindTagToReportByTask((int)$task['id'], $qrInput, $request);
|
|
}
|
|
}
|
|
|
|
if ($report['report_status'] !== 'published') {
|
|
Db::name('reports')->where('id', $id)->update([
|
|
'report_status' => 'published',
|
|
'publish_time' => $effectivePublishTime,
|
|
'updated_at' => $now,
|
|
]);
|
|
$report['report_status'] = 'published';
|
|
$report['publish_time'] = $effectivePublishTime;
|
|
}
|
|
|
|
if ($isOrderAppraisalReport) {
|
|
$this->refreshAppraisalSnapshot((int)$report['id'], (int)$report['order_id'], $report['service_provider'], $now);
|
|
}
|
|
|
|
$verify = $this->createOrUpdateVerifyRecord($report, $now);
|
|
|
|
if ($isOrderAppraisalReport) {
|
|
Db::name('orders')->where('id', $report['order_id'])->update([
|
|
'order_status' => 'report_published',
|
|
'display_status' => '报告已出具',
|
|
'updated_at' => $now,
|
|
]);
|
|
|
|
$order = Db::name('orders')->where('id', $report['order_id'])->find();
|
|
$product = Db::name('order_products')->where('order_id', $report['order_id'])->find();
|
|
|
|
$timelineExists = Db::name('order_timelines')
|
|
->where('order_id', $report['order_id'])
|
|
->where('node_code', 'report_published')
|
|
->where('node_text', '报告已出具')
|
|
->find();
|
|
|
|
if (!$timelineExists) {
|
|
Db::name('order_timelines')->insert([
|
|
'order_id' => $report['order_id'],
|
|
'node_code' => 'report_published',
|
|
'node_text' => '报告已出具',
|
|
'node_desc' => '正式报告已发布,用户可查看报告并进行验真。',
|
|
'operator_type' => 'admin',
|
|
'operator_id' => (int)$request->header('x-admin-id', 0) ?: null,
|
|
'occurred_at' => $now,
|
|
'created_at' => $now,
|
|
]);
|
|
}
|
|
|
|
(new MessageDispatcher())->sendInboxEvent('report_published', [
|
|
'user_id' => (int)($order['user_id'] ?? 0),
|
|
'biz_type' => 'report',
|
|
'biz_id' => (int)$report['id'],
|
|
'report_no' => $report['report_no'],
|
|
'report_title' => $report['report_title'],
|
|
'product_name' => $product['product_name'] ?? '',
|
|
'publish_time' => $report['publish_time'] ?: $now,
|
|
'verify_url' => (string)($verify['verify_url'] ?? ''),
|
|
'fallback_title' => '报告已出具',
|
|
'fallback_content' => '您的正式报告已生成,可前往报告中心查看并完成验真。',
|
|
]);
|
|
|
|
(new FulfillmentFlowService())->markReportPublished((int)$report['order_id'], $request);
|
|
}
|
|
|
|
Db::commit();
|
|
|
|
if ($isOrderAppraisalReport) {
|
|
(new EnterpriseWebhookService())->recordOrderEvent((int)$report['order_id'], 'report_published', [
|
|
'report_id' => $id,
|
|
'report_no' => (string)$report['report_no'],
|
|
'report_title' => (string)$report['report_title'],
|
|
'publish_time' => $effectivePublishTime,
|
|
'verify_url' => (string)($verify['verify_url'] ?? ''),
|
|
'report_page_url' => (string)($verify['report_page_url'] ?? ''),
|
|
]);
|
|
}
|
|
|
|
return api_success([
|
|
'id' => $id,
|
|
'report_status' => 'published',
|
|
'publish_time' => $effectivePublishTime,
|
|
'verify_url' => (string)($verify['verify_url'] ?? ''),
|
|
'report_page_url' => (string)($verify['report_page_url'] ?? ''),
|
|
'material_tag' => $materialTag,
|
|
], '报告已发布');
|
|
} catch (\Throwable $e) {
|
|
Db::rollback();
|
|
return api_error('报告发布失败', 500, [
|
|
'detail' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
private function reportStatusText(string $status): string
|
|
{
|
|
return match ($status) {
|
|
'draft' => '草稿中',
|
|
'pending_publish' => '待发布',
|
|
'published' => '已发布',
|
|
'updated' => '已更新',
|
|
'invalid' => '已作废',
|
|
default => $status,
|
|
};
|
|
}
|
|
|
|
private function reportTypeText(string $reportType): string
|
|
{
|
|
return match ($reportType) {
|
|
'inspection' => '补录检查单',
|
|
default => '订单报告',
|
|
};
|
|
}
|
|
|
|
private function serviceProviderText(string $serviceProvider): string
|
|
{
|
|
return $serviceProvider === 'zhongjian' ? '中检鉴定' : '实物鉴定';
|
|
}
|
|
|
|
private function decodeJsonField(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 loadReportContentMap(array $reportIds): array
|
|
{
|
|
if (!$reportIds) {
|
|
return [];
|
|
}
|
|
|
|
$rows = Db::name('report_contents')->whereIn('report_id', $reportIds)->select()->toArray();
|
|
$map = [];
|
|
foreach ($rows as $row) {
|
|
$map[(int)$row['report_id']] = [
|
|
'product_snapshot' => $this->decodeJsonField($row['product_snapshot_json'] ?? null),
|
|
'result_snapshot' => $this->decodeJsonField($row['result_snapshot_json'] ?? null),
|
|
];
|
|
}
|
|
return $map;
|
|
}
|
|
|
|
private function matchKeyword(array $item, string $keyword): bool
|
|
{
|
|
$needle = mb_strtolower($keyword);
|
|
foreach (['report_no', 'report_title', 'product_name', 'brand_name', 'institution_name', 'order_no', 'appraisal_no'] as $field) {
|
|
if (str_contains(mb_strtolower((string)($item[$field] ?? '')), $needle)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function createOrUpdateVerifyRecord(array $report, string $now): array
|
|
{
|
|
$reportNo = (string)$report['report_no'];
|
|
$verifyToken = 'verify_' . strtolower((string)preg_replace('/[^a-zA-Z0-9]/', '', $reportNo));
|
|
$verifyUrl = $this->buildPublicPageUrl('/pages/verify/result', ['report_no' => $reportNo]);
|
|
$reportPageUrl = $this->buildPublicPageUrl('/pages/report/detail', ['report_no' => $reportNo]);
|
|
|
|
$payload = [
|
|
'report_id' => (int)$report['id'],
|
|
'report_no' => $reportNo,
|
|
'verify_token' => $verifyToken,
|
|
'verify_qrcode_url' => $reportPageUrl,
|
|
'verify_url' => $verifyUrl,
|
|
'verify_status' => 'valid',
|
|
'updated_at' => $now,
|
|
];
|
|
|
|
$verify = Db::name('report_verifies')->where('report_id', $report['id'])->find();
|
|
if ($verify) {
|
|
Db::name('report_verifies')->where('id', $verify['id'])->update($payload);
|
|
} else {
|
|
$payload['last_verified_at'] = null;
|
|
$payload['verify_count'] = 0;
|
|
$payload['created_at'] = $now;
|
|
Db::name('report_verifies')->insert($payload);
|
|
}
|
|
|
|
$fresh = Db::name('report_verifies')->where('report_id', $report['id'])->find() ?: $payload;
|
|
$fresh['report_page_url'] = $reportPageUrl;
|
|
return $fresh;
|
|
}
|
|
|
|
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 enrichAppraisalSnapshot(array $report, array $snapshot): array
|
|
{
|
|
if (($report['report_type'] ?? 'appraisal') !== 'appraisal' || (int)($report['order_id'] ?? 0) <= 0) {
|
|
return $snapshot;
|
|
}
|
|
|
|
$tasks = Db::name('appraisal_tasks')
|
|
->where('order_id', (int)$report['order_id'])
|
|
->order('id', 'asc')
|
|
->select()
|
|
->toArray();
|
|
|
|
$firstReviewTask = null;
|
|
$finalReviewTask = null;
|
|
foreach ($tasks as $task) {
|
|
if (($task['task_stage'] ?? '') === 'first_review') {
|
|
$firstReviewTask = $task;
|
|
}
|
|
if (($task['task_stage'] ?? '') === 'final_review') {
|
|
$finalReviewTask = $task;
|
|
}
|
|
}
|
|
|
|
$institutionName = $snapshot['institution_name'] ?? ($report['institution_name'] ?: $this->defaultInstitutionName($report['service_provider']));
|
|
$appraiserName = $this->normalizeAssigneeName($snapshot['appraiser_name'] ?? '')
|
|
?: $this->normalizeAssigneeName($firstReviewTask['assignee_name'] ?? '')
|
|
?: $this->normalizeAssigneeName($finalReviewTask['assignee_name'] ?? '');
|
|
$reviewerName = $appraiserName;
|
|
$appraisalTime = $snapshot['appraisal_time']
|
|
?? ($firstReviewTask['submitted_at']
|
|
?? $firstReviewTask['started_at']
|
|
?? $finalReviewTask['submitted_at']
|
|
?? $finalReviewTask['started_at']
|
|
?? '');
|
|
|
|
$snapshot['service_provider'] = $snapshot['service_provider'] ?? $report['service_provider'];
|
|
$snapshot['institution_name'] = $institutionName;
|
|
$snapshot['appraiser_name'] = $appraiserName;
|
|
$snapshot['reviewer_name'] = $reviewerName;
|
|
$snapshot['appraisal_time'] = $appraisalTime;
|
|
|
|
return $snapshot;
|
|
}
|
|
|
|
private function refreshAppraisalSnapshot(int $reportId, int $orderId, string $serviceProvider, string $now): void
|
|
{
|
|
$content = Db::name('report_contents')->where('report_id', $reportId)->find();
|
|
if (!$content) {
|
|
return;
|
|
}
|
|
|
|
$snapshot = $this->enrichAppraisalSnapshot(
|
|
[
|
|
'report_type' => 'appraisal',
|
|
'order_id' => $orderId,
|
|
'service_provider' => $serviceProvider,
|
|
'institution_name' => '',
|
|
],
|
|
$this->decodeJsonField($content['appraisal_snapshot_json'] ?? null),
|
|
);
|
|
|
|
Db::name('report_contents')->where('report_id', $reportId)->update([
|
|
'appraisal_snapshot_json' => json_encode($snapshot, JSON_UNESCAPED_UNICODE),
|
|
'updated_at' => $now,
|
|
]);
|
|
}
|
|
|
|
private function normalizeAssigneeName(?string $value): string
|
|
{
|
|
$name = trim((string)$value);
|
|
if ($name === '' || $name === '未分配') {
|
|
return '';
|
|
}
|
|
|
|
return $name;
|
|
}
|
|
|
|
private function evidenceService(): AppraisalEvidenceService
|
|
{
|
|
return new AppraisalEvidenceService();
|
|
}
|
|
|
|
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 generateUniqueReportNo(string $reportType): string
|
|
{
|
|
$prefix = $reportType === 'inspection' ? 'AXY-CHK' : 'AXY-R';
|
|
for ($i = 0; $i < 20; $i++) {
|
|
$candidate = sprintf('%s-%s-%04d', $prefix, date('Ymd'), random_int(0, 9999));
|
|
if (!Db::name('reports')->where('report_no', $candidate)->find()) {
|
|
return $candidate;
|
|
}
|
|
}
|
|
|
|
return sprintf('%s-%s-%s', $prefix, date('YmdHis'), random_int(1000, 9999));
|
|
}
|
|
|
|
private function generateUniqueAppraisalNo(string $reportType): string
|
|
{
|
|
$prefix = $reportType === 'inspection' ? 'AXY-CHECK' : 'AXY-APP';
|
|
for ($i = 0; $i < 20; $i++) {
|
|
$candidate = sprintf('%s-%s-%04d', $prefix, date('Ymd'), random_int(0, 9999));
|
|
if (!Db::name('reports')->where('appraisal_no', $candidate)->find()) {
|
|
return $candidate;
|
|
}
|
|
}
|
|
|
|
return sprintf('%s-%s-%s', $prefix, date('YmdHis'), random_int(1000, 9999));
|
|
}
|
|
|
|
private function defaultReportTitle(string $serviceProvider, string $reportType): string
|
|
{
|
|
if ($reportType === 'inspection') {
|
|
return $serviceProvider === 'zhongjian' ? '中检检查单' : '安心验检查单';
|
|
}
|
|
|
|
return $serviceProvider === 'zhongjian' ? '中检鉴定报告' : '安心验鉴定报告';
|
|
}
|
|
|
|
private function defaultInstitutionName(string $serviceProvider): string
|
|
{
|
|
return $serviceProvider === 'zhongjian' ? '中检鉴定中心' : '安心检验';
|
|
}
|
|
}
|