canArchiveAbandon(); $canDetails = $this->hasProcuremenSiblingPerm(['details']); $this->view->assign('canArchiveAbandon', $canArchiveAbandon); $this->assignconfig('procuremenAuth', [ 'details' => $canDetails, 'auditabandon' => $canArchiveAbandon, 'archiveabandon' => $canArchiveAbandon, ]); if (session_status() === PHP_SESSION_ACTIVE) { @session_write_close(); } } /** * 历史存证「重新下发」权限:与审批列表统一为 auditabandon */ protected function canArchiveAbandon(): bool { if ($this->hasProcuremenSiblingPerm(['auditabandon', 'auditAbandon'])) { return true; } return $this->hasProcuremenSiblingPerm(['archiveabandon', 'archiveAbandon']); } /** * @param string[] $actions */ protected function hasProcuremenSiblingPerm(array $actions): bool { foreach ($actions as $action) { $action = strtolower(trim((string)$action)); if ($action === '') { continue; } if (strpos($action, 'procuremen/') !== 0) { $action = 'procuremen/' . $action; } if ($this->auth->check($action)) { return true; } } $suffixes = []; foreach ($actions as $action) { $action = strtolower(trim((string)$action)); $action = preg_replace('#^procuremen/#', '', $action); if ($action !== '') { $suffixes[$action] = true; } } if ($suffixes === []) { return false; } try { foreach ($this->auth->getRuleList() as $rule) { $rule = strtolower((string)$rule); if (strpos($rule, 'procuremen/') !== 0) { continue; } foreach (array_keys($suffixes) as $suffix) { if ($rule === 'procuremen/' . $suffix || strpos($rule, 'procuremen/' . $suffix . '/') === 0) { return true; } } } } catch (\Throwable $e) { } return false; } /** * 历史存证范围:审批通过(wflow=已审核)即可显示,不要求入库合格/已完结 * (兼容旧数据仅有 status=已完结) */ protected function applyPurchaseOrderArchiveWhere($query): void { // 用 CAST 文案比较,避免数字/中文混存时 whereIn 漏数 $wflow = []; foreach (ProcuremenStatus::wflowApprovedValues() as $v) { $wflow[] = "'" . str_replace("'", "''", (string)$v) . "'"; } $poDone = []; foreach (ProcuremenStatus::poCompletedValues() as $v) { $poDone[] = "'" . str_replace("'", "''", (string)$v) . "'"; } $query->whereRaw( '(TRIM(CAST(`wflow_status` AS CHAR)) IN (' . implode(',', $wflow) . ')' . ' OR TRIM(CAST(`status` AS CHAR)) IN (' . implode(',', $poDone) . '))' ); } /** * 历史存证合并键:订单号 + 批次号 + 中标供应商 * (同订单号不同批次/供应商必须分行,避免串单显示) */ protected function archiveOrderGroupKeySql(): string { return "IF(TRIM(IFNULL(`CCYDH`,''))='', CONCAT('_id_', `id`), CONCAT(" . "TRIM(`CCYDH`), CHAR(30), " . "IF(TRIM(IFNULL(`order_batch_no`,''))='','',TRIM(`order_batch_no`)), CHAR(30), " . "IF(TRIM(IFNULL(`pick_company_name`,''))='','',TRIM(`pick_company_name`))" . "))"; } /** * @param array $row */ protected function buildArchiveRowGroupKey(array $row): string { $dh = trim((string)($row['CCYDH'] ?? '')); if ($dh === '') { return '_id_' . (int)($row['id'] ?? 0); } $batch = trim((string)($row['order_batch_no'] ?? '')); $company = trim((string)($row['pick_company_name'] ?? '')); return $dh . "\x1e" . $batch . "\x1e" . $company; } /** * @return array{type:string,id?:int,CCYDH?:string,order_batch_no?:string,pick_company_name?:string} */ protected function parseArchiveGroupKey(string $gk): array { $gk = (string)$gk; if (strpos($gk, '_id_') === 0) { return ['type' => 'id', 'id' => (int)substr($gk, 4)]; } $parts = explode("\x1e", $gk, 3); return [ 'type' => 'scope', 'CCYDH' => (string)($parts[0] ?? ''), 'order_batch_no' => (string)($parts[1] ?? ''), 'pick_company_name' => (string)($parts[2] ?? ''), ]; } public function index() { $this->relationSearch = false; $this->request->filter(['strip_tags', 'trim']); if ($this->request->isAjax()) { list($where, $sort, $order, $offset, $limit) = $this->buildparams(); $limit = max(10, min(500, (int)$limit)); $offset = max(0, (int)$offset); $applyFilters = function ($query) use ($where) { $this->applyPurchaseOrderArchiveWhere($query); if (!empty($where)) { $query->where($where); } }; // 默认按 id(近似审批先后);前端默认 sortName=createtime $sortField = preg_match('/^[a-zA-Z0-9_]+$/', (string)$sort) ? $sort : 'createtime'; $orderDir = strtoupper((string)$order) === 'ASC' ? 'ASC' : 'DESC'; $groupKeySql = $this->archiveOrderGroupKeySql(); $total = 0; try { $countQuery = Db::table('purchase_order'); $applyFilters($countQuery); $subSql = $countQuery->fieldRaw($groupKeySql . ' AS gk')->group($groupKeySql)->buildSql(); $cntRows = Db::query('SELECT COUNT(*) AS c FROM ' . $subSql . ' _archive_g'); $total = (int)($cntRows[0]['c'] ?? 0); } catch (\Throwable $e) { // 兼容缺 order_batch_no 列时回退按订单号合并 try { $groupKeySql = "IF(TRIM(IFNULL(`CCYDH`,''))='', CONCAT('_id_', `id`), TRIM(`CCYDH`))"; $countQuery = Db::table('purchase_order'); $applyFilters($countQuery); $subSql = $countQuery->fieldRaw($groupKeySql . ' AS gk')->group($groupKeySql)->buildSql(); $cntRows = Db::query('SELECT COUNT(*) AS c FROM ' . $subSql . ' _archive_g'); $total = (int)($cntRows[0]['c'] ?? 0); } catch (\Throwable $e2) { $total = 0; } } if ($total <= 0) { return json(['total' => 0, 'rows' => []]); } // 审批通过即可进历史:按最新主单 id 排,保证刚审的在最前 $orderSql = 'MAX(`id`) ' . $orderDir; if ($sortField === 'CCYDH') { $orderSql = $groupKeySql . ' ' . $orderDir; } elseif (in_array($sortField, ['CYJMC', 'CGYMC', 'pick_company_name', 'order_batch_no'], true)) { $orderSql = 'MAX(`' . $sortField . '`) ' . $orderDir; } elseif (in_array($sortField, ['createtime', 'complete_time'], true)) { $orderSql = 'MAX(`id`) ' . $orderDir; } $pageQuery = Db::table('purchase_order'); $applyFilters($pageQuery); try { $groups = $pageQuery ->fieldRaw($groupKeySql . ' AS gk, MAX(`id`) AS max_id') ->group($groupKeySql) ->orderRaw($orderSql) ->limit($offset, $limit) ->select(); } catch (\Throwable $e) { $groups = []; } if (!is_array($groups) || $groups === []) { return json(['total' => $total, 'rows' => []]); } $scopes = []; $orphanIds = []; foreach ($groups as $g) { if (!is_array($g)) { continue; } $gk = (string)($g['gk'] ?? ''); if ($gk === '') { continue; } $parsed = $this->parseArchiveGroupKey($gk); if (($parsed['type'] ?? '') === 'id') { $oid = (int)($parsed['id'] ?? 0); if ($oid > 0) { $orphanIds[$oid] = true; } } elseif (($parsed['CCYDH'] ?? '') !== '') { $scopes[] = $parsed; } } $orphanIdList = array_keys($orphanIds); if ($scopes === [] && $orphanIdList === []) { return json(['total' => $total, 'rows' => []]); } $detailQuery = Db::table('purchase_order'); $applyFilters($detailQuery); try { $detailQuery->field('id,scydgy_id,CCYDH,CYJMC,CGYMC,pick_company_name,order_batch_no,createtime,dStamp,pick_time,status,wflow_status'); } catch (\Throwable $e) { $detailQuery->field('id,scydgy_id,CCYDH,CYJMC,CGYMC,pick_company_name,createtime,dStamp,pick_time,status,wflow_status'); } $detailQuery->where(function ($q) use ($scopes, $orphanIdList) { $first = true; foreach ($scopes as $scope) { if (!is_array($scope)) { continue; } $ccydh = trim((string)($scope['CCYDH'] ?? '')); if ($ccydh === '') { continue; } $batch = trim((string)($scope['order_batch_no'] ?? '')); $company = trim((string)($scope['pick_company_name'] ?? '')); $fn = function ($qq) use ($ccydh, $batch, $company) { $qq->whereRaw("TRIM(`CCYDH`) = '" . str_replace("'", "''", $ccydh) . "'"); if ($batch === '') { $qq->whereRaw("TRIM(IFNULL(`order_batch_no`,'')) = ''"); } else { $qq->whereRaw("TRIM(`order_batch_no`) = '" . str_replace("'", "''", $batch) . "'"); } if ($company === '') { $qq->whereRaw("TRIM(IFNULL(`pick_company_name`,'')) = ''"); } else { $qq->whereRaw("TRIM(`pick_company_name`) = '" . str_replace("'", "''", $company) . "'"); } }; if ($first) { $q->where($fn); $first = false; } else { $q->whereOr($fn); } } if ($orphanIdList !== []) { if ($first) { $q->where('id', 'in', $orphanIdList); } else { $q->whereOr('id', 'in', $orphanIdList); } } }); try { $rows = $detailQuery->select(); } catch (\Throwable $e) { // 缺 order_batch_no 时退回按订单号拉取 $ccydhs = []; foreach ($scopes as $scope) { $dh = trim((string)($scope['CCYDH'] ?? '')); if ($dh !== '') { $ccydhs[$dh] = true; } } $detailQuery2 = Db::table('purchase_order'); $applyFilters($detailQuery2); $detailQuery2->field('id,scydgy_id,CCYDH,CYJMC,CGYMC,pick_company_name,createtime,dStamp,pick_time,status,wflow_status'); $detailQuery2->where(function ($q) use ($ccydhs, $orphanIdList) { if ($ccydhs !== []) { $quoted = []; foreach (array_keys($ccydhs) as $dh) { $quoted[] = "'" . str_replace("'", "''", (string)$dh) . "'"; } $q->whereRaw('TRIM(`CCYDH`) IN (' . implode(',', $quoted) . ')'); } if ($orphanIdList !== []) { if ($ccydhs !== []) { $q->whereOr('id', 'in', $orphanIdList); } else { $q->where('id', 'in', $orphanIdList); } } }); $rows = $detailQuery2->select(); } if (!is_array($rows)) { $rows = []; } $sidList = []; foreach ($rows as $r) { if (!is_array($r)) { continue; } $sid = (int)($r['scydgy_id'] ?? 0); if ($sid !== 0) { $sidList[$sid] = true; } } // 审批时间 = 审核确认供应商;完结时间 = 直接完结 / 质量评分(与详情「已完结」一致) $approveTsMap = ProcuremenTime::loadOperLogTimestampMap( array_keys($sidList), ['purchase_confirm'] ); $completeTsMap = ProcuremenTime::loadOperLogTimestampMap( array_keys($sidList), ['mark_complete', 'inbound_score'] ); $inboundMap = []; $deliveryMap = []; $inboundTimeMap = []; if ($sidList !== []) { try { $inRows = Db::table('purchase_order_inbound_score') ->where('scydgy_id', 'in', array_keys($sidList)) ->field('scydgy_id,result,delivery_status,updatetime,createtime') ->select(); } catch (\Throwable $e) { try { $inRows = Db::table('purchase_order_inbound_score') ->where('scydgy_id', 'in', array_keys($sidList)) ->field('scydgy_id,result,updatetime,createtime') ->select(); } catch (\Throwable $e2) { $inRows = []; } } if (is_array($inRows)) { foreach ($inRows as $ir) { if (!is_array($ir)) { continue; } $isid = (int)($ir['scydgy_id'] ?? 0); if ($isid === 0) { continue; } $inboundMap[$isid] = trim((string)($ir['result'] ?? '')); $ds = trim((string)($ir['delivery_status'] ?? '')); if ($ds === '准时' || $ds === '滞后') { $deliveryMap[$isid] = $ds; } $its = ProcuremenTime::parseToTimestamp($ir['updatetime'] ?? $ir['createtime'] ?? null); if ($its > 946684800) { $inboundTimeMap[$isid] = $its; } } } } $out = []; foreach ($rows as $r) { if (!is_array($r)) { continue; } $sid = (int)($r['scydgy_id'] ?? 0); $approve = ProcuremenTime::resolveCompletedDone($r, $approveTsMap); $completeMap = $completeTsMap; if ($sid > 0 && empty($completeMap[$sid]) && isset($inboundTimeMap[$sid])) { $completeMap[$sid] = $inboundTimeMap[$sid]; } $complete = ProcuremenTime::resolveCompletedDone($r, $completeMap); if ($sid > 0 && empty($completeTsMap[$sid]) && empty($inboundTimeMap[$sid])) { $complete = ['ts' => 0, 'text' => '']; } $out[] = [ 'id' => (int)($r['id'] ?? 0), 'scydgy_id' => $sid, 'CCYDH' => trim((string)($r['CCYDH'] ?? '')), 'CYJMC' => trim((string)($r['CYJMC'] ?? '')), 'CGYMC' => trim((string)($r['CGYMC'] ?? '')), 'pick_company_name' => trim((string)($r['pick_company_name'] ?? '')), 'order_batch_no' => trim((string)($r['order_batch_no'] ?? '')), 'inbound_result' => $inboundMap[$sid] ?? '', 'delivery_status' => $deliveryMap[$sid] ?? '', 'createtime' => $approve['ts'], 'createtime_text' => $approve['text'], 'complete_time' => $complete['ts'], 'complete_time_text' => $complete['text'], ]; } $merged = $this->collapseArchiveRowsByOrder($out); $pageRows = $this->reorderArchiveRowsByGroupKeys($merged, $groups); return json(['total' => $total, 'rows' => $pageRows]); } return $this->view->fetch(); } /** * @param array> $merged * @param array> $groups * @return array> */ protected function reorderArchiveRowsByGroupKeys(array $merged, array $groups): array { $pos = []; foreach ($groups as $i => $g) { if (!is_array($g)) { continue; } // 勿 trim 整段 gk:中间分隔符 CHAR(30) 需保留 $gk = (string)($g['gk'] ?? ''); if ($gk !== '') { $pos[$gk] = $i; } } if ($pos === []) { return $merged; } usort($merged, function ($a, $b) use ($pos) { $ka = $this->buildArchiveRowGroupKey(is_array($a) ? $a : []); $kb = $this->buildArchiveRowGroupKey(is_array($b) ? $b : []); $ia = $pos[$ka] ?? 999999; $ib = $pos[$kb] ?? 999999; if ($ia === $ib) { return ((int)($b['id'] ?? 0)) <=> ((int)($a['id'] ?? 0)); } return $ia <=> $ib; }); return array_values($merged); } /** * 历史存证:同一订单号 + 同一批次 + 同一中标供应商合并为一行 * * @param array> $rows * @return array> */ protected function collapseArchiveRowsByOrder(array $rows): array { if ($rows === []) { return []; } $groups = []; foreach ($rows as $row) { if (!is_array($row)) { continue; } $key = $this->buildArchiveRowGroupKey($row); if (!isset($groups[$key])) { $groups[$key] = []; } $groups[$key][] = $row; } $out = []; foreach ($groups as $groupRows) { if ($groupRows === []) { continue; } usort($groupRows, function ($a, $b) { return ((int)($a['id'] ?? 0)) <=> ((int)($b['id'] ?? 0)); }); $head = $groupRows[0]; $gymcList = []; $supplierList = []; $inboundList = []; $deliveryList = []; foreach ($groupRows as $r) { $g = trim((string)($r['CGYMC'] ?? '')); if ($g !== '' && !in_array($g, $gymcList, true)) { $gymcList[] = $g; } $sn = trim((string)($r['pick_company_name'] ?? '')); if ($sn !== '' && !in_array($sn, $supplierList, true)) { $supplierList[] = $sn; } $ir = trim((string)($r['inbound_result'] ?? '')); if ($ir !== '' && !in_array($ir, $inboundList, true)) { $inboundList[] = $ir; } $ds = trim((string)($r['delivery_status'] ?? '')); if (($ds === '准时' || $ds === '滞后') && !in_array($ds, $deliveryList, true)) { $deliveryList[] = $ds; } } $merged = $head; if (count($groupRows) > 1) { $merged['CGYMC'] = implode('、', $gymcList); } $merged['pick_company_name'] = $supplierList !== [] ? implode('、', $supplierList) : trim((string)($head['pick_company_name'] ?? '')); $merged['order_batch_no'] = trim((string)($head['order_batch_no'] ?? '')); $merged['inbound_result'] = $inboundList !== [] ? implode('、', $inboundList) : ''; if (in_array('滞后', $deliveryList, true)) { $merged['delivery_status'] = '滞后'; } elseif (in_array('准时', $deliveryList, true)) { $merged['delivery_status'] = '准时'; } else { $merged['delivery_status'] = ''; } $merged['process_count'] = count($groupRows); $latestApproveTs = 0; $latestApproveText = ''; $latestCompleteTs = 0; $latestCompleteText = ''; foreach ($groupRows as $r) { $ts = (int)($r['createtime'] ?? 0); if ($ts <= 0 && !empty($r['createtime_text'])) { $ts = (int)strtotime((string)$r['createtime_text']); } if ($ts > $latestApproveTs) { $latestApproveTs = $ts; $latestApproveText = trim((string)($r['createtime_text'] ?? '')); } $cts = (int)($r['complete_time'] ?? 0); if ($cts <= 0 && !empty($r['complete_time_text'])) { $cts = (int)strtotime((string)$r['complete_time_text']); } if ($cts > $latestCompleteTs) { $latestCompleteTs = $cts; $latestCompleteText = trim((string)($r['complete_time_text'] ?? '')); } } if ($latestApproveTs > 0) { $merged['createtime'] = $latestApproveTs; if ($latestApproveText === '') { $latestApproveText = date('Y-m-d H:i:s', $latestApproveTs); } $merged['createtime_text'] = ProcuremenTime::formatDisplayDateTime($latestApproveText); } else { $merged['createtime'] = 0; $merged['createtime_text'] = ''; } if ($latestCompleteTs > 0) { $merged['complete_time'] = $latestCompleteTs; if ($latestCompleteText === '') { $latestCompleteText = date('Y-m-d H:i:s', $latestCompleteTs); } $merged['complete_time_text'] = ProcuremenTime::formatDisplayDateTime($latestCompleteText); } else { $merged['complete_time'] = 0; $merged['complete_time_text'] = ''; } $maxId = (int)($head['id'] ?? 0); foreach ($groupRows as $r) { $maxId = max($maxId, (int)($r['id'] ?? 0)); } $merged['id'] = $maxId; $out[] = $merged; } return $out; } }