增加了手机操作端

This commit is contained in:
wushumin
2026-05-15 14:01:36 +08:00
parent 9aac78b8da
commit dd56e0861b
107 changed files with 23547 additions and 346 deletions

View File

@@ -5,6 +5,7 @@ namespace app\controller\admin;
use app\support\AppraisalEvidenceService;
use app\support\ContentService;
use app\support\EnterpriseWebhookService;
use app\support\FulfillmentFlowService;
use app\support\MessageDispatcher;
use app\support\MaterialTagService;
use app\support\PublicAssetUrlService;
@@ -19,9 +20,14 @@ class AppraisalTasksController
$taskStage = trim((string)$request->input('task_stage', ''));
$status = trim((string)$request->input('status', ''));
$serviceProvider = trim((string)$request->input('service_provider', ''));
$scope = trim((string)$request->input('scope', ''));
$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 = $this->buildTaskBaseQuery()
->whereRaw($this->workbenchVisibleOrderStatusSql());
$this->applyTaskScopeFilter($query, $request, $scope);
if ($keyword !== '') {
$query->where(function ($builder) use ($keyword) {
@@ -49,7 +55,12 @@ class AppraisalTasksController
$matchedRows = $query->select()->toArray();
if (!$matchedRows) {
return api_success(['list' => []]);
return api_success($paginationEnabled ? [
'list' => [],
'total' => 0,
'page' => $page,
'page_size' => $pageSize,
] : ['list' => []]);
}
$orderIds = array_values(array_unique(array_map(fn (array $item) => (int)$item['order_id'], $matchedRows)));
@@ -58,12 +69,26 @@ class AppraisalTasksController
$allRows = $this->buildTaskBaseQuery()
->whereRaw($this->workbenchVisibleOrderStatusSql())
->whereIn('t.order_id', $orderIds)
->group('t.id')
->order('t.order_id', 'desc')
->order('t.id', 'desc')
->select()
->toArray();
$this->applyTaskScopeFilterRows($allRows, $request, $scope);
$list = $this->buildGroupedTaskList($allRows, $reportMap);
$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]);
}
@@ -81,7 +106,10 @@ class AppraisalTasksController
->leftJoin('order_products p', 'p.order_id = t.order_id')
->leftJoin('order_extras e', 'e.order_id = t.order_id')
->leftJoin('appraisal_task_results r', 'r.task_id = t.id')
->leftJoin('reports rp', 'rp.order_id = t.order_id AND rp.report_type = "appraisal"')
->leftJoin('report_contents rc', 'rc.report_id = rp.id')
->leftJoin('enterprise_customer_order_refs ecr', 'ecr.order_id = t.order_id')
->order('rp.id', 'desc')
->field([
't.id',
't.order_id',
@@ -123,6 +151,11 @@ class AppraisalTasksController
'r.attachments_json as result_attachments_json',
'r.external_remark',
'r.internal_remark',
'rp.zhongjian_report_no',
'rp.report_entry_admin_id',
'rp.report_entry_admin_name',
'rp.report_entered_at',
'rc.zhongjian_report_files_json',
])
->where('t.id', $id)
->find();
@@ -195,6 +228,7 @@ class AppraisalTasksController
$stageTaskRows = $this->buildTaskBaseQuery()
->where('t.order_id', (int)$task['order_id'])
->group('t.id')
->order('t.id', 'asc')
->select()
->toArray();
@@ -279,6 +313,13 @@ class AppraisalTasksController
'report_status_text' => $this->reportStatusText($report['report_status']),
] : null,
'material_tag' => $materialTag,
'zhongjian_report' => [
'report_no' => (string)($task['zhongjian_report_no'] ?? ''),
'report_entry_admin_id' => (int)($task['report_entry_admin_id'] ?? 0),
'report_entry_admin_name' => (string)($task['report_entry_admin_name'] ?? ''),
'report_entered_at' => (string)($task['report_entered_at'] ?? ''),
'files' => $this->evidenceService()->normalize($task['zhongjian_report_files_json'] ?? null, $request),
],
'product_info' => [
'product_name' => $task['product_name'] ?: '',
'category_id' => (int)($task['category_id'] ?? 0),
@@ -332,6 +373,9 @@ class AppraisalTasksController
if (!$task) {
return api_error('任务不存在', 404);
}
if (($task['service_provider'] ?? '') === 'zhongjian') {
return api_error('中检订单不使用平台验真吊牌', 422);
}
$operatorGuard = $this->guardTaskOperator($request, $task);
if ($operatorGuard['error']) {
@@ -354,6 +398,193 @@ class AppraisalTasksController
], '吊牌已绑定');
}
public function scanTransferTag(Request $request)
{
try {
return api_success((new FulfillmentFlowService())->scanTransferForAppraisal(
(string)$request->input('internal_tag_no', ''),
$request
));
} catch (\InvalidArgumentException $e) {
return api_error($e->getMessage(), 422);
} catch (\RuntimeException $e) {
return api_error($e->getMessage(), $e->getCode() ?: 404);
} catch (\Throwable $e) {
return api_error('内部流转码识别失败', 500, ['detail' => $e->getMessage()]);
}
}
public function publishWithMaterialTag(Request $request)
{
$id = (int)$request->input('id', 0);
$qrInput = trim((string)$request->input('qr_input', ''));
if ($id <= 0 || $qrInput === '') {
return api_error('任务 ID 和验真吊牌不能为空', 422);
}
$task = Db::name('appraisal_tasks')->where('id', $id)->find();
if (!$task) {
return api_error('任务不存在', 404);
}
if (($task['service_provider'] ?? '') === 'zhongjian') {
return api_error('中检订单不使用平台验真吊牌', 422);
}
try {
$tag = (new MaterialTagService())->bindTagToReportByTask($id, $qrInput, $request);
$report = $this->findLatestAppraisalReport((int)$task['order_id']);
if (!$report) {
return api_error('请先提交鉴定结论生成报告草稿', 422);
}
$publish = $this->publishReportRecord($report, $request);
(new FulfillmentFlowService())->markReportPublished((int)$task['order_id'], $request);
} catch (\InvalidArgumentException $e) {
return api_error($e->getMessage(), 422);
} catch (\RuntimeException $e) {
return api_error($e->getMessage(), $e->getCode() ?: 404);
} catch (\Throwable $e) {
return api_error('验真吊牌绑定或报告发布失败', 500, ['detail' => $e->getMessage()]);
}
return api_success([
'id' => $id,
'material_tag' => $tag,
'report' => $publish,
], '验真吊牌已绑定,报告已发布');
}
public function saveZhongjianReport(Request $request)
{
$id = (int)$request->input('id', 0);
$reportNo = trim((string)$request->input('zhongjian_report_no', ''));
$files = $this->evidenceService()->normalize($request->input('report_files', []), $request, true);
if ($id <= 0) {
return api_error('任务 ID 不能为空', 422);
}
if ($reportNo === '') {
return api_error('中检报告编号不能为空', 422);
}
if (!$files) {
return api_error('请至少上传 1 个中检报告文件', 422);
}
$task = Db::name('appraisal_tasks')->where('id', $id)->find();
if (!$task) {
return api_error('任务不存在', 404);
}
if (($task['service_provider'] ?? '') !== 'zhongjian') {
return api_error('非中检订单不能录入中检报告', 422);
}
$operatorGuard = $this->guardTaskOperator($request, $task);
if ($operatorGuard['error']) {
return $operatorGuard['error'];
}
$operatorId = (int)$request->header('x-admin-id', 0);
$operatorName = trim((string)$request->header('x-admin-name', ''));
$now = date('Y-m-d H:i:s');
Db::startTrans();
try {
if ($operatorGuard['task_update']) {
Db::name('appraisal_tasks')->where('id', $id)->update(array_merge($operatorGuard['task_update'], [
'updated_at' => $now,
]));
$task = array_merge($task, $operatorGuard['task_update']);
}
Db::name('appraisal_tasks')->where('id', $id)->update([
'status' => 'completed',
'started_at' => $task['started_at'] ?: $now,
'submitted_at' => $now,
'updated_at' => $now,
]);
Db::name('orders')->where('id', (int)$task['order_id'])->update([
'order_status' => 'generating_report',
'display_status' => '正在生成报告',
'updated_at' => $now,
]);
$resultPayload = [
'task_id' => $id,
'order_id' => (int)$task['order_id'],
'result_status' => 'zhongjian_report',
'result_text' => '以中检报告为准',
'result_desc' => '中检报告已回传并由平台录入。',
'condition_grade' => '',
'condition_desc' => '',
'valuation_min' => 0,
'valuation_max' => 0,
'valuation_desc' => '',
'attachments_json' => json_encode($files, JSON_UNESCAPED_UNICODE),
'external_remark' => '',
'internal_remark' => '中检报告编号:' . $reportNo,
'updated_at' => $now,
];
$resultId = Db::name('appraisal_task_results')->where('task_id', $id)->value('id');
if ($resultId) {
Db::name('appraisal_task_results')->where('id', (int)$resultId)->update($resultPayload);
} else {
$resultPayload['created_at'] = $now;
Db::name('appraisal_task_results')->insert($resultPayload);
}
$this->createOrUpdateReportDraft((int)$task['order_id'], $task, $resultPayload, $now);
$report = $this->findLatestAppraisalReport((int)$task['order_id']);
if (!$report) {
Db::rollback();
return api_error('中检报告草稿生成失败', 500);
}
Db::name('reports')->where('id', (int)$report['id'])->update([
'zhongjian_report_no' => $reportNo,
'report_entry_admin_id' => $operatorId,
'report_entry_admin_name' => $operatorName,
'report_entered_at' => $now,
'updated_at' => $now,
]);
$content = Db::name('report_contents')->where('report_id', (int)$report['id'])->find();
if ($content) {
Db::name('report_contents')->where('id', (int)$content['id'])->update([
'zhongjian_report_files_json' => json_encode($files, JSON_UNESCAPED_UNICODE),
'updated_at' => $now,
]);
}
Db::name('order_timelines')->insert([
'order_id' => (int)$task['order_id'],
'node_code' => 'zhongjian_report_entered',
'node_text' => '中检报告已录入',
'node_desc' => '报告录入人已录入中检报告编号并上传报告文件。',
'operator_type' => 'admin',
'operator_id' => $operatorId,
'occurred_at' => $now,
'created_at' => $now,
]);
Db::commit();
$freshReport = $this->findLatestAppraisalReport((int)$task['order_id']);
$publish = $this->publishReportRecord($freshReport, $request);
(new FulfillmentFlowService())->markReportPublished((int)$task['order_id'], $request);
return api_success([
'id' => $id,
'report' => $publish,
], '中检报告已录入并发布');
} catch (\Throwable $e) {
try {
Db::rollback();
} catch (\Throwable $rollbackError) {
// Transaction may already be committed before publishing.
}
return api_error('中检报告录入失败', 500, ['detail' => $e->getMessage()]);
}
}
public function assignableAdmins(Request $request)
{
$id = (int)$request->input('id', 0);
@@ -537,7 +768,7 @@ class AppraisalTasksController
'node_text' => '正在生成报告',
'node_desc' => '鉴定已完成,系统正在生成正式报告草稿',
'operator_type' => 'admin',
'operator_id' => 1,
'operator_id' => (int)$request->header('x-admin-id', 0),
'occurred_at' => $now,
'created_at' => $now,
]);
@@ -654,7 +885,7 @@ class AppraisalTasksController
'reason' => $reason,
'deadline' => $deadline !== '' ? $deadline : null,
'status' => 'pending',
'created_by' => 1,
'created_by' => (int)$request->header('x-admin-id', 0),
'submitted_at' => null,
'approved_at' => null,
'created_at' => $now,
@@ -704,7 +935,7 @@ class AppraisalTasksController
'node_text' => '待补资料',
'node_desc' => $reason,
'operator_type' => 'admin',
'operator_id' => 1,
'operator_id' => (int)$request->header('x-admin-id', 0),
'occurred_at' => $now,
'created_at' => $now,
]);
@@ -797,10 +1028,43 @@ class AppraisalTasksController
'p.category_name',
'p.brand_id',
'p.brand_name',
'r.result_text',
'r.result_text',
]);
}
private function applyTaskScopeFilter($query, Request $request, string $scope): void
{
if ($scope !== 'my') {
return;
}
$adminId = (int)$request->header('x-admin-id', 0);
if ($adminId <= 0) {
return;
}
$query->whereRaw('(t.assignee_id = :scope_admin_id OR t.assignee_id IS NULL OR t.assignee_id = 0)', [
'scope_admin_id' => $adminId,
]);
}
private function applyTaskScopeFilterRows(array &$rows, Request $request, string $scope): void
{
if ($scope !== 'my') {
return;
}
$adminId = (int)$request->header('x-admin-id', 0);
if ($adminId <= 0) {
return;
}
$rows = array_values(array_filter($rows, function (array $row) use ($adminId) {
$assigneeId = (int)($row['assignee_id'] ?? 0);
return $assigneeId <= 0 || $assigneeId === $adminId;
}));
}
private function normalizeTaskListRow(array $item, ?array $report = null): array
{
$effectiveStatus = $this->effectiveTaskStatus($item, $report);
@@ -1602,6 +1866,176 @@ class AppraisalTasksController
return $admin;
}
private function publishReportRecord(array $report, Request $request): array
{
if (!$report) {
throw new \RuntimeException('报告不存在', 404);
}
if (!in_array((string)$report['report_status'], ['draft', 'pending_publish', 'updated', 'published'], true)) {
throw new \InvalidArgumentException('当前报告状态不支持发布');
}
$operatorId = (int)$request->header('x-admin-id', 0);
$now = date('Y-m-d H:i:s');
$effectivePublishTime = $report['publish_time'] ?: $now;
$usesPlatformVerify = (string)($report['service_provider'] ?? '') !== 'zhongjian';
$verify = [];
Db::startTrans();
try {
if (($report['report_status'] ?? '') !== 'published') {
Db::name('reports')->where('id', (int)$report['id'])->update([
'report_status' => 'published',
'publish_time' => $effectivePublishTime,
'updated_at' => $now,
]);
$report['report_status'] = 'published';
$report['publish_time'] = $effectivePublishTime;
}
if ($usesPlatformVerify) {
$verify = $this->createOrUpdateVerifyRecord($report, $now);
}
if (($report['report_type'] ?? 'appraisal') === 'appraisal' && (int)($report['order_id'] ?? 0) > 0) {
Db::name('orders')->where('id', (int)$report['order_id'])->update([
'order_status' => 'report_published',
'display_status' => '报告已出具',
'updated_at' => $now,
]);
$order = Db::name('orders')->where('id', (int)$report['order_id'])->find();
$product = Db::name('order_products')->where('order_id', (int)$report['order_id'])->find();
$timelineExists = Db::name('order_timelines')
->where('order_id', (int)$report['order_id'])
->where('node_code', 'report_published')
->where('node_text', '报告已出具')
->find();
if (!$timelineExists) {
Db::name('order_timelines')->insert([
'order_id' => (int)$report['order_id'],
'node_code' => 'report_published',
'node_text' => '报告已出具',
'node_desc' => '正式报告已发布,用户可查看报告。',
'operator_type' => 'admin',
'operator_id' => $operatorId ?: 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' => (string)$report['report_no'],
'report_title' => (string)$report['report_title'],
'product_name' => $product['product_name'] ?? '',
'publish_time' => $effectivePublishTime,
'verify_url' => $usesPlatformVerify ? (string)($verify['verify_url'] ?? '') : '',
'fallback_title' => '报告已出具',
'fallback_content' => '您的正式报告已生成,可前往报告中心查看。',
]);
}
Db::commit();
} catch (\Throwable $e) {
Db::rollback();
throw $e;
}
if (($report['report_type'] ?? 'appraisal') === 'appraisal' && (int)($report['order_id'] ?? 0) > 0) {
(new EnterpriseWebhookService())->recordOrderEvent((int)$report['order_id'], 'report_published', [
'report_id' => (int)$report['id'],
'report_no' => (string)$report['report_no'],
'report_title' => (string)$report['report_title'],
'publish_time' => $effectivePublishTime,
'verify_url' => $usesPlatformVerify ? (string)($verify['verify_url'] ?? '') : '',
'report_page_url' => $usesPlatformVerify ? (string)($verify['report_page_url'] ?? '') : '',
]);
}
return [
'id' => (int)$report['id'],
'report_status' => 'published',
'publish_time' => $effectivePublishTime,
'verify_url' => $usesPlatformVerify ? (string)($verify['verify_url'] ?? '') : '',
'report_page_url' => $usesPlatformVerify ? (string)($verify['report_page_url'] ?? '') : '',
];
}
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', (int)$report['id'])->find();
if ($verify) {
Db::name('report_verifies')->where('id', (int)$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', (int)$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;
}
return $baseUrl === '' ? $hashPath : $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();