Procuremenarchive.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. <?php
  2. namespace app\admin\controller;
  3. use app\common\controller\Backend;
  4. use app\common\library\ProcuremenStatus;
  5. use app\common\library\ProcuremenTime;
  6. use think\Db;
  7. /**
  8. * 协助采购 — 历史存证档案查询(审批通过即可查;含操作记录详情)
  9. *
  10. * @icon fa fa-archive
  11. */
  12. class Procuremenarchive extends Backend
  13. {
  14. protected $searchFields = 'CCYDH,CYJMC,CGYMC,pick_company_name,order_batch_no';
  15. public function _initialize()
  16. {
  17. parent::_initialize();
  18. $canArchiveAbandon = $this->canArchiveAbandon();
  19. $canDetails = $this->hasProcuremenSiblingPerm(['details']);
  20. $this->view->assign('canArchiveAbandon', $canArchiveAbandon);
  21. $this->assignconfig('procuremenAuth', [
  22. 'details' => $canDetails,
  23. 'auditabandon' => $canArchiveAbandon,
  24. 'archiveabandon' => $canArchiveAbandon,
  25. ]);
  26. if (session_status() === PHP_SESSION_ACTIVE) {
  27. @session_write_close();
  28. }
  29. }
  30. /**
  31. * 历史存证「重新下发」权限:与审批列表统一为 auditabandon
  32. */
  33. protected function canArchiveAbandon(): bool
  34. {
  35. if ($this->hasProcuremenSiblingPerm(['auditabandon', 'auditAbandon'])) {
  36. return true;
  37. }
  38. return $this->hasProcuremenSiblingPerm(['archiveabandon', 'archiveAbandon']);
  39. }
  40. /**
  41. * @param string[] $actions
  42. */
  43. protected function hasProcuremenSiblingPerm(array $actions): bool
  44. {
  45. foreach ($actions as $action) {
  46. $action = strtolower(trim((string)$action));
  47. if ($action === '') {
  48. continue;
  49. }
  50. if (strpos($action, 'procuremen/') !== 0) {
  51. $action = 'procuremen/' . $action;
  52. }
  53. if ($this->auth->check($action)) {
  54. return true;
  55. }
  56. }
  57. $suffixes = [];
  58. foreach ($actions as $action) {
  59. $action = strtolower(trim((string)$action));
  60. $action = preg_replace('#^procuremen/#', '', $action);
  61. if ($action !== '') {
  62. $suffixes[$action] = true;
  63. }
  64. }
  65. if ($suffixes === []) {
  66. return false;
  67. }
  68. try {
  69. foreach ($this->auth->getRuleList() as $rule) {
  70. $rule = strtolower((string)$rule);
  71. if (strpos($rule, 'procuremen/') !== 0) {
  72. continue;
  73. }
  74. foreach (array_keys($suffixes) as $suffix) {
  75. if ($rule === 'procuremen/' . $suffix || strpos($rule, 'procuremen/' . $suffix . '/') === 0) {
  76. return true;
  77. }
  78. }
  79. }
  80. } catch (\Throwable $e) {
  81. }
  82. return false;
  83. }
  84. /**
  85. * 历史存证范围:审批通过(wflow=已审核)即可显示,不要求入库合格/已完结
  86. * (兼容旧数据仅有 status=已完结)
  87. */
  88. protected function applyPurchaseOrderArchiveWhere($query): void
  89. {
  90. // 用 CAST 文案比较,避免数字/中文混存时 whereIn 漏数
  91. $wflow = [];
  92. foreach (ProcuremenStatus::wflowApprovedValues() as $v) {
  93. $wflow[] = "'" . str_replace("'", "''", (string)$v) . "'";
  94. }
  95. $poDone = [];
  96. foreach (ProcuremenStatus::poCompletedValues() as $v) {
  97. $poDone[] = "'" . str_replace("'", "''", (string)$v) . "'";
  98. }
  99. $query->whereRaw(
  100. '(TRIM(CAST(`wflow_status` AS CHAR)) IN (' . implode(',', $wflow) . ')'
  101. . ' OR TRIM(CAST(`status` AS CHAR)) IN (' . implode(',', $poDone) . '))'
  102. );
  103. }
  104. /**
  105. * 历史存证合并键:订单号 + 批次号 + 中标供应商
  106. * (同订单号不同批次/供应商必须分行,避免串单显示)
  107. */
  108. protected function archiveOrderGroupKeySql(): string
  109. {
  110. return "IF(TRIM(IFNULL(`CCYDH`,''))='', CONCAT('_id_', `id`), CONCAT("
  111. . "TRIM(`CCYDH`), CHAR(30), "
  112. . "IF(TRIM(IFNULL(`order_batch_no`,''))='','',TRIM(`order_batch_no`)), CHAR(30), "
  113. . "IF(TRIM(IFNULL(`pick_company_name`,''))='','',TRIM(`pick_company_name`))"
  114. . "))";
  115. }
  116. /**
  117. * @param array<string, mixed> $row
  118. */
  119. protected function buildArchiveRowGroupKey(array $row): string
  120. {
  121. $dh = trim((string)($row['CCYDH'] ?? ''));
  122. if ($dh === '') {
  123. return '_id_' . (int)($row['id'] ?? 0);
  124. }
  125. $batch = trim((string)($row['order_batch_no'] ?? ''));
  126. $company = trim((string)($row['pick_company_name'] ?? ''));
  127. return $dh . "\x1e" . $batch . "\x1e" . $company;
  128. }
  129. /**
  130. * @return array{type:string,id?:int,CCYDH?:string,order_batch_no?:string,pick_company_name?:string}
  131. */
  132. protected function parseArchiveGroupKey(string $gk): array
  133. {
  134. $gk = (string)$gk;
  135. if (strpos($gk, '_id_') === 0) {
  136. return ['type' => 'id', 'id' => (int)substr($gk, 4)];
  137. }
  138. $parts = explode("\x1e", $gk, 3);
  139. return [
  140. 'type' => 'scope',
  141. 'CCYDH' => (string)($parts[0] ?? ''),
  142. 'order_batch_no' => (string)($parts[1] ?? ''),
  143. 'pick_company_name' => (string)($parts[2] ?? ''),
  144. ];
  145. }
  146. public function index()
  147. {
  148. $this->relationSearch = false;
  149. $this->request->filter(['strip_tags', 'trim']);
  150. if ($this->request->isAjax()) {
  151. list($where, $sort, $order, $offset, $limit) = $this->buildparams();
  152. $limit = max(10, min(500, (int)$limit));
  153. $offset = max(0, (int)$offset);
  154. $applyFilters = function ($query) use ($where) {
  155. $this->applyPurchaseOrderArchiveWhere($query);
  156. if (!empty($where)) {
  157. $query->where($where);
  158. }
  159. };
  160. // 默认按 id(近似审批先后);前端默认 sortName=createtime
  161. $sortField = preg_match('/^[a-zA-Z0-9_]+$/', (string)$sort) ? $sort : 'createtime';
  162. $orderDir = strtoupper((string)$order) === 'ASC' ? 'ASC' : 'DESC';
  163. $groupKeySql = $this->archiveOrderGroupKeySql();
  164. $total = 0;
  165. try {
  166. $countQuery = Db::table('purchase_order');
  167. $applyFilters($countQuery);
  168. $subSql = $countQuery->fieldRaw($groupKeySql . ' AS gk')->group($groupKeySql)->buildSql();
  169. $cntRows = Db::query('SELECT COUNT(*) AS c FROM ' . $subSql . ' _archive_g');
  170. $total = (int)($cntRows[0]['c'] ?? 0);
  171. } catch (\Throwable $e) {
  172. // 兼容缺 order_batch_no 列时回退按订单号合并
  173. try {
  174. $groupKeySql = "IF(TRIM(IFNULL(`CCYDH`,''))='', CONCAT('_id_', `id`), TRIM(`CCYDH`))";
  175. $countQuery = Db::table('purchase_order');
  176. $applyFilters($countQuery);
  177. $subSql = $countQuery->fieldRaw($groupKeySql . ' AS gk')->group($groupKeySql)->buildSql();
  178. $cntRows = Db::query('SELECT COUNT(*) AS c FROM ' . $subSql . ' _archive_g');
  179. $total = (int)($cntRows[0]['c'] ?? 0);
  180. } catch (\Throwable $e2) {
  181. $total = 0;
  182. }
  183. }
  184. if ($total <= 0) {
  185. return json(['total' => 0, 'rows' => []]);
  186. }
  187. // 审批通过即可进历史:按最新主单 id 排,保证刚审的在最前
  188. $orderSql = 'MAX(`id`) ' . $orderDir;
  189. if ($sortField === 'CCYDH') {
  190. $orderSql = $groupKeySql . ' ' . $orderDir;
  191. } elseif (in_array($sortField, ['CYJMC', 'CGYMC', 'pick_company_name', 'order_batch_no'], true)) {
  192. $orderSql = 'MAX(`' . $sortField . '`) ' . $orderDir;
  193. } elseif (in_array($sortField, ['createtime', 'complete_time'], true)) {
  194. $orderSql = 'MAX(`id`) ' . $orderDir;
  195. }
  196. $pageQuery = Db::table('purchase_order');
  197. $applyFilters($pageQuery);
  198. try {
  199. $groups = $pageQuery
  200. ->fieldRaw($groupKeySql . ' AS gk, MAX(`id`) AS max_id')
  201. ->group($groupKeySql)
  202. ->orderRaw($orderSql)
  203. ->limit($offset, $limit)
  204. ->select();
  205. } catch (\Throwable $e) {
  206. $groups = [];
  207. }
  208. if (!is_array($groups) || $groups === []) {
  209. return json(['total' => $total, 'rows' => []]);
  210. }
  211. $scopes = [];
  212. $orphanIds = [];
  213. foreach ($groups as $g) {
  214. if (!is_array($g)) {
  215. continue;
  216. }
  217. $gk = (string)($g['gk'] ?? '');
  218. if ($gk === '') {
  219. continue;
  220. }
  221. $parsed = $this->parseArchiveGroupKey($gk);
  222. if (($parsed['type'] ?? '') === 'id') {
  223. $oid = (int)($parsed['id'] ?? 0);
  224. if ($oid > 0) {
  225. $orphanIds[$oid] = true;
  226. }
  227. } elseif (($parsed['CCYDH'] ?? '') !== '') {
  228. $scopes[] = $parsed;
  229. }
  230. }
  231. $orphanIdList = array_keys($orphanIds);
  232. if ($scopes === [] && $orphanIdList === []) {
  233. return json(['total' => $total, 'rows' => []]);
  234. }
  235. $detailQuery = Db::table('purchase_order');
  236. $applyFilters($detailQuery);
  237. try {
  238. $detailQuery->field('id,scydgy_id,CCYDH,CYJMC,CGYMC,pick_company_name,order_batch_no,createtime,dStamp,pick_time,status,wflow_status');
  239. } catch (\Throwable $e) {
  240. $detailQuery->field('id,scydgy_id,CCYDH,CYJMC,CGYMC,pick_company_name,createtime,dStamp,pick_time,status,wflow_status');
  241. }
  242. $detailQuery->where(function ($q) use ($scopes, $orphanIdList) {
  243. $first = true;
  244. foreach ($scopes as $scope) {
  245. if (!is_array($scope)) {
  246. continue;
  247. }
  248. $ccydh = trim((string)($scope['CCYDH'] ?? ''));
  249. if ($ccydh === '') {
  250. continue;
  251. }
  252. $batch = trim((string)($scope['order_batch_no'] ?? ''));
  253. $company = trim((string)($scope['pick_company_name'] ?? ''));
  254. $fn = function ($qq) use ($ccydh, $batch, $company) {
  255. $qq->whereRaw("TRIM(`CCYDH`) = '" . str_replace("'", "''", $ccydh) . "'");
  256. if ($batch === '') {
  257. $qq->whereRaw("TRIM(IFNULL(`order_batch_no`,'')) = ''");
  258. } else {
  259. $qq->whereRaw("TRIM(`order_batch_no`) = '" . str_replace("'", "''", $batch) . "'");
  260. }
  261. if ($company === '') {
  262. $qq->whereRaw("TRIM(IFNULL(`pick_company_name`,'')) = ''");
  263. } else {
  264. $qq->whereRaw("TRIM(`pick_company_name`) = '" . str_replace("'", "''", $company) . "'");
  265. }
  266. };
  267. if ($first) {
  268. $q->where($fn);
  269. $first = false;
  270. } else {
  271. $q->whereOr($fn);
  272. }
  273. }
  274. if ($orphanIdList !== []) {
  275. if ($first) {
  276. $q->where('id', 'in', $orphanIdList);
  277. } else {
  278. $q->whereOr('id', 'in', $orphanIdList);
  279. }
  280. }
  281. });
  282. try {
  283. $rows = $detailQuery->select();
  284. } catch (\Throwable $e) {
  285. // 缺 order_batch_no 时退回按订单号拉取
  286. $ccydhs = [];
  287. foreach ($scopes as $scope) {
  288. $dh = trim((string)($scope['CCYDH'] ?? ''));
  289. if ($dh !== '') {
  290. $ccydhs[$dh] = true;
  291. }
  292. }
  293. $detailQuery2 = Db::table('purchase_order');
  294. $applyFilters($detailQuery2);
  295. $detailQuery2->field('id,scydgy_id,CCYDH,CYJMC,CGYMC,pick_company_name,createtime,dStamp,pick_time,status,wflow_status');
  296. $detailQuery2->where(function ($q) use ($ccydhs, $orphanIdList) {
  297. if ($ccydhs !== []) {
  298. $quoted = [];
  299. foreach (array_keys($ccydhs) as $dh) {
  300. $quoted[] = "'" . str_replace("'", "''", (string)$dh) . "'";
  301. }
  302. $q->whereRaw('TRIM(`CCYDH`) IN (' . implode(',', $quoted) . ')');
  303. }
  304. if ($orphanIdList !== []) {
  305. if ($ccydhs !== []) {
  306. $q->whereOr('id', 'in', $orphanIdList);
  307. } else {
  308. $q->where('id', 'in', $orphanIdList);
  309. }
  310. }
  311. });
  312. $rows = $detailQuery2->select();
  313. }
  314. if (!is_array($rows)) {
  315. $rows = [];
  316. }
  317. $sidList = [];
  318. foreach ($rows as $r) {
  319. if (!is_array($r)) {
  320. continue;
  321. }
  322. $sid = (int)($r['scydgy_id'] ?? 0);
  323. if ($sid !== 0) {
  324. $sidList[$sid] = true;
  325. }
  326. }
  327. // 审批时间 = 审核确认供应商;完结时间 = 直接完结 / 质量评分(与详情「已完结」一致)
  328. $approveTsMap = ProcuremenTime::loadOperLogTimestampMap(
  329. array_keys($sidList),
  330. ['purchase_confirm']
  331. );
  332. $completeTsMap = ProcuremenTime::loadOperLogTimestampMap(
  333. array_keys($sidList),
  334. ['mark_complete', 'inbound_score']
  335. );
  336. $inboundMap = [];
  337. $deliveryMap = [];
  338. $inboundTimeMap = [];
  339. if ($sidList !== []) {
  340. try {
  341. $inRows = Db::table('purchase_order_inbound_score')
  342. ->where('scydgy_id', 'in', array_keys($sidList))
  343. ->field('scydgy_id,result,delivery_status,updatetime,createtime')
  344. ->select();
  345. } catch (\Throwable $e) {
  346. try {
  347. $inRows = Db::table('purchase_order_inbound_score')
  348. ->where('scydgy_id', 'in', array_keys($sidList))
  349. ->field('scydgy_id,result,updatetime,createtime')
  350. ->select();
  351. } catch (\Throwable $e2) {
  352. $inRows = [];
  353. }
  354. }
  355. if (is_array($inRows)) {
  356. foreach ($inRows as $ir) {
  357. if (!is_array($ir)) {
  358. continue;
  359. }
  360. $isid = (int)($ir['scydgy_id'] ?? 0);
  361. if ($isid === 0) {
  362. continue;
  363. }
  364. $inboundMap[$isid] = trim((string)($ir['result'] ?? ''));
  365. $ds = trim((string)($ir['delivery_status'] ?? ''));
  366. if ($ds === '准时' || $ds === '滞后') {
  367. $deliveryMap[$isid] = $ds;
  368. }
  369. $its = ProcuremenTime::parseToTimestamp($ir['updatetime'] ?? $ir['createtime'] ?? null);
  370. if ($its > 946684800) {
  371. $inboundTimeMap[$isid] = $its;
  372. }
  373. }
  374. }
  375. }
  376. $out = [];
  377. foreach ($rows as $r) {
  378. if (!is_array($r)) {
  379. continue;
  380. }
  381. $sid = (int)($r['scydgy_id'] ?? 0);
  382. $approve = ProcuremenTime::resolveCompletedDone($r, $approveTsMap);
  383. $completeMap = $completeTsMap;
  384. if ($sid > 0 && empty($completeMap[$sid]) && isset($inboundTimeMap[$sid])) {
  385. $completeMap[$sid] = $inboundTimeMap[$sid];
  386. }
  387. $complete = ProcuremenTime::resolveCompletedDone($r, $completeMap);
  388. if ($sid > 0 && empty($completeTsMap[$sid]) && empty($inboundTimeMap[$sid])) {
  389. $complete = ['ts' => 0, 'text' => ''];
  390. }
  391. $out[] = [
  392. 'id' => (int)($r['id'] ?? 0),
  393. 'scydgy_id' => $sid,
  394. 'CCYDH' => trim((string)($r['CCYDH'] ?? '')),
  395. 'CYJMC' => trim((string)($r['CYJMC'] ?? '')),
  396. 'CGYMC' => trim((string)($r['CGYMC'] ?? '')),
  397. 'pick_company_name' => trim((string)($r['pick_company_name'] ?? '')),
  398. 'order_batch_no' => trim((string)($r['order_batch_no'] ?? '')),
  399. 'inbound_result' => $inboundMap[$sid] ?? '',
  400. 'delivery_status' => $deliveryMap[$sid] ?? '',
  401. 'createtime' => $approve['ts'],
  402. 'createtime_text' => $approve['text'],
  403. 'complete_time' => $complete['ts'],
  404. 'complete_time_text' => $complete['text'],
  405. ];
  406. }
  407. $merged = $this->collapseArchiveRowsByOrder($out);
  408. $pageRows = $this->reorderArchiveRowsByGroupKeys($merged, $groups);
  409. return json(['total' => $total, 'rows' => $pageRows]);
  410. }
  411. return $this->view->fetch();
  412. }
  413. /**
  414. * @param array<int, array<string, mixed>> $merged
  415. * @param array<int, array<string, mixed>> $groups
  416. * @return array<int, array<string, mixed>>
  417. */
  418. protected function reorderArchiveRowsByGroupKeys(array $merged, array $groups): array
  419. {
  420. $pos = [];
  421. foreach ($groups as $i => $g) {
  422. if (!is_array($g)) {
  423. continue;
  424. }
  425. // 勿 trim 整段 gk:中间分隔符 CHAR(30) 需保留
  426. $gk = (string)($g['gk'] ?? '');
  427. if ($gk !== '') {
  428. $pos[$gk] = $i;
  429. }
  430. }
  431. if ($pos === []) {
  432. return $merged;
  433. }
  434. usort($merged, function ($a, $b) use ($pos) {
  435. $ka = $this->buildArchiveRowGroupKey(is_array($a) ? $a : []);
  436. $kb = $this->buildArchiveRowGroupKey(is_array($b) ? $b : []);
  437. $ia = $pos[$ka] ?? 999999;
  438. $ib = $pos[$kb] ?? 999999;
  439. if ($ia === $ib) {
  440. return ((int)($b['id'] ?? 0)) <=> ((int)($a['id'] ?? 0));
  441. }
  442. return $ia <=> $ib;
  443. });
  444. return array_values($merged);
  445. }
  446. /**
  447. * 历史存证:同一订单号 + 同一批次 + 同一中标供应商合并为一行
  448. *
  449. * @param array<int, array<string, mixed>> $rows
  450. * @return array<int, array<string, mixed>>
  451. */
  452. protected function collapseArchiveRowsByOrder(array $rows): array
  453. {
  454. if ($rows === []) {
  455. return [];
  456. }
  457. $groups = [];
  458. foreach ($rows as $row) {
  459. if (!is_array($row)) {
  460. continue;
  461. }
  462. $key = $this->buildArchiveRowGroupKey($row);
  463. if (!isset($groups[$key])) {
  464. $groups[$key] = [];
  465. }
  466. $groups[$key][] = $row;
  467. }
  468. $out = [];
  469. foreach ($groups as $groupRows) {
  470. if ($groupRows === []) {
  471. continue;
  472. }
  473. usort($groupRows, function ($a, $b) {
  474. return ((int)($a['id'] ?? 0)) <=> ((int)($b['id'] ?? 0));
  475. });
  476. $head = $groupRows[0];
  477. $gymcList = [];
  478. $supplierList = [];
  479. $inboundList = [];
  480. $deliveryList = [];
  481. foreach ($groupRows as $r) {
  482. $g = trim((string)($r['CGYMC'] ?? ''));
  483. if ($g !== '' && !in_array($g, $gymcList, true)) {
  484. $gymcList[] = $g;
  485. }
  486. $sn = trim((string)($r['pick_company_name'] ?? ''));
  487. if ($sn !== '' && !in_array($sn, $supplierList, true)) {
  488. $supplierList[] = $sn;
  489. }
  490. $ir = trim((string)($r['inbound_result'] ?? ''));
  491. if ($ir !== '' && !in_array($ir, $inboundList, true)) {
  492. $inboundList[] = $ir;
  493. }
  494. $ds = trim((string)($r['delivery_status'] ?? ''));
  495. if (($ds === '准时' || $ds === '滞后') && !in_array($ds, $deliveryList, true)) {
  496. $deliveryList[] = $ds;
  497. }
  498. }
  499. $merged = $head;
  500. if (count($groupRows) > 1) {
  501. $merged['CGYMC'] = implode('、', $gymcList);
  502. }
  503. $merged['pick_company_name'] = $supplierList !== []
  504. ? implode('、', $supplierList)
  505. : trim((string)($head['pick_company_name'] ?? ''));
  506. $merged['order_batch_no'] = trim((string)($head['order_batch_no'] ?? ''));
  507. $merged['inbound_result'] = $inboundList !== [] ? implode('、', $inboundList) : '';
  508. if (in_array('滞后', $deliveryList, true)) {
  509. $merged['delivery_status'] = '滞后';
  510. } elseif (in_array('准时', $deliveryList, true)) {
  511. $merged['delivery_status'] = '准时';
  512. } else {
  513. $merged['delivery_status'] = '';
  514. }
  515. $merged['process_count'] = count($groupRows);
  516. $latestApproveTs = 0;
  517. $latestApproveText = '';
  518. $latestCompleteTs = 0;
  519. $latestCompleteText = '';
  520. foreach ($groupRows as $r) {
  521. $ts = (int)($r['createtime'] ?? 0);
  522. if ($ts <= 0 && !empty($r['createtime_text'])) {
  523. $ts = (int)strtotime((string)$r['createtime_text']);
  524. }
  525. if ($ts > $latestApproveTs) {
  526. $latestApproveTs = $ts;
  527. $latestApproveText = trim((string)($r['createtime_text'] ?? ''));
  528. }
  529. $cts = (int)($r['complete_time'] ?? 0);
  530. if ($cts <= 0 && !empty($r['complete_time_text'])) {
  531. $cts = (int)strtotime((string)$r['complete_time_text']);
  532. }
  533. if ($cts > $latestCompleteTs) {
  534. $latestCompleteTs = $cts;
  535. $latestCompleteText = trim((string)($r['complete_time_text'] ?? ''));
  536. }
  537. }
  538. if ($latestApproveTs > 0) {
  539. $merged['createtime'] = $latestApproveTs;
  540. if ($latestApproveText === '') {
  541. $latestApproveText = date('Y-m-d H:i:s', $latestApproveTs);
  542. }
  543. $merged['createtime_text'] = ProcuremenTime::formatDisplayDateTime($latestApproveText);
  544. } else {
  545. $merged['createtime'] = 0;
  546. $merged['createtime_text'] = '';
  547. }
  548. if ($latestCompleteTs > 0) {
  549. $merged['complete_time'] = $latestCompleteTs;
  550. if ($latestCompleteText === '') {
  551. $latestCompleteText = date('Y-m-d H:i:s', $latestCompleteTs);
  552. }
  553. $merged['complete_time_text'] = ProcuremenTime::formatDisplayDateTime($latestCompleteText);
  554. } else {
  555. $merged['complete_time'] = 0;
  556. $merged['complete_time_text'] = '';
  557. }
  558. $maxId = (int)($head['id'] ?? 0);
  559. foreach ($groupRows as $r) {
  560. $maxId = max($maxId, (int)($r['id'] ?? 0));
  561. }
  562. $merged['id'] = $maxId;
  563. $out[] = $merged;
  564. }
  565. return $out;
  566. }
  567. }