Procuremenarchive.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  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';
  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. }
  27. /**
  28. * 历史存证「重新下发」权限:与审批列表统一为 auditabandon
  29. */
  30. protected function canArchiveAbandon(): bool
  31. {
  32. if ($this->hasProcuremenSiblingPerm(['auditabandon', 'auditAbandon'])) {
  33. return true;
  34. }
  35. return $this->hasProcuremenSiblingPerm(['archiveabandon', 'archiveAbandon']);
  36. }
  37. /**
  38. * @param string[] $actions
  39. */
  40. protected function hasProcuremenSiblingPerm(array $actions): bool
  41. {
  42. foreach ($actions as $action) {
  43. $action = strtolower(trim((string)$action));
  44. if ($action === '') {
  45. continue;
  46. }
  47. if (strpos($action, 'procuremen/') !== 0) {
  48. $action = 'procuremen/' . $action;
  49. }
  50. if ($this->auth->check($action)) {
  51. return true;
  52. }
  53. }
  54. $suffixes = [];
  55. foreach ($actions as $action) {
  56. $action = strtolower(trim((string)$action));
  57. $action = preg_replace('#^procuremen/#', '', $action);
  58. if ($action !== '') {
  59. $suffixes[$action] = true;
  60. }
  61. }
  62. if ($suffixes === []) {
  63. return false;
  64. }
  65. try {
  66. foreach ($this->auth->getRuleList() as $rule) {
  67. $rule = strtolower((string)$rule);
  68. if (strpos($rule, 'procuremen/') !== 0) {
  69. continue;
  70. }
  71. foreach (array_keys($suffixes) as $suffix) {
  72. if ($rule === 'procuremen/' . $suffix || strpos($rule, 'procuremen/' . $suffix . '/') === 0) {
  73. return true;
  74. }
  75. }
  76. }
  77. } catch (\Throwable $e) {
  78. }
  79. return false;
  80. }
  81. /**
  82. * 历史存证范围:审批通过(wflow=已审核)即可显示,不要求入库合格/已完结
  83. * (兼容旧数据仅有 status=已完结)
  84. */
  85. protected function applyPurchaseOrderArchiveWhere($query): void
  86. {
  87. $quoted = [];
  88. foreach (ProcuremenStatus::wflowApprovedValues() as $v) {
  89. $quoted[] = "'" . str_replace("'", "''", (string)$v) . "'";
  90. }
  91. $wflowSql = 'TRIM(CAST(`wflow_status` AS CHAR)) IN (' . implode(',', $quoted) . ')';
  92. $query->whereRaw('(' . $wflowSql . ' OR ' . ProcuremenStatus::sqlPoCompleted('status') . ')');
  93. }
  94. public function index()
  95. {
  96. $this->relationSearch = false;
  97. $this->request->filter(['strip_tags', 'trim']);
  98. if ($this->request->isAjax()) {
  99. list($where, $sort, $order, $offset, $limit) = $this->buildparams();
  100. $limit = max(10, min(500, (int)$limit));
  101. $offset = max(0, (int)$offset);
  102. $applyFilters = function ($query) use ($where) {
  103. $this->applyPurchaseOrderArchiveWhere($query);
  104. if (!empty($where)) {
  105. $query->where($where);
  106. }
  107. };
  108. $sortField = preg_match('/^[a-zA-Z0-9_]+$/', (string)$sort) ? $sort : 'complete_time';
  109. $orderDir = strtoupper((string)$order) === 'ASC' ? 'ASC' : 'DESC';
  110. // 完结/审批时间为计算字段,库表排序仅用真实列
  111. $dbSortAllow = ['id', 'CCYDH', 'CYJMC', 'CGYMC', 'pick_company_name', 'createtime', 'pick_time', 'dStamp'];
  112. $dbSortField = in_array($sortField, $dbSortAllow, true) ? $sortField : 'id';
  113. $listQuery = Db::table('purchase_order');
  114. $applyFilters($listQuery);
  115. $rows = $listQuery
  116. ->field('id,scydgy_id,CCYDH,CYJMC,CGYMC,pick_company_name,createtime,dStamp,pick_time')
  117. ->order($dbSortField, $orderDir)
  118. ->select();
  119. if (!is_array($rows)) {
  120. $rows = [];
  121. }
  122. $sidList = [];
  123. foreach ($rows as $r) {
  124. if (!is_array($r)) {
  125. continue;
  126. }
  127. $sid = (int)($r['scydgy_id'] ?? 0);
  128. if ($sid !== 0) {
  129. $sidList[$sid] = true;
  130. }
  131. }
  132. // 审批时间 = 审核确认供应商;完结时间 = 直接完结 / 质量评分(与详情「已完结」一致)
  133. $approveTsMap = ProcuremenTime::loadOperLogTimestampMap(
  134. array_keys($sidList),
  135. ['purchase_confirm']
  136. );
  137. $completeTsMap = ProcuremenTime::loadOperLogTimestampMap(
  138. array_keys($sidList),
  139. ['mark_complete', 'inbound_score']
  140. );
  141. $inboundMap = [];
  142. $inboundTimeMap = [];
  143. if ($sidList !== []) {
  144. try {
  145. $inRows = Db::table('purchase_order_inbound_score')
  146. ->where('scydgy_id', 'in', array_keys($sidList))
  147. ->field('scydgy_id,result,updatetime,createtime')
  148. ->select();
  149. } catch (\Throwable $e) {
  150. $inRows = [];
  151. }
  152. if (is_array($inRows)) {
  153. foreach ($inRows as $ir) {
  154. if (!is_array($ir)) {
  155. continue;
  156. }
  157. $isid = (int)($ir['scydgy_id'] ?? 0);
  158. if ($isid === 0) {
  159. continue;
  160. }
  161. $inboundMap[$isid] = trim((string)($ir['result'] ?? ''));
  162. $its = ProcuremenTime::parseToTimestamp($ir['updatetime'] ?? $ir['createtime'] ?? null);
  163. if ($its > 946684800) {
  164. $inboundTimeMap[$isid] = $its;
  165. }
  166. }
  167. }
  168. }
  169. $out = [];
  170. foreach ($rows as $r) {
  171. if (!is_array($r)) {
  172. continue;
  173. }
  174. $sid = (int)($r['scydgy_id'] ?? 0);
  175. $approve = ProcuremenTime::resolveCompletedDone($r, $approveTsMap);
  176. // 完结:操作日志优先;无日志时用入库评分表时间兜底
  177. $completeMap = $completeTsMap;
  178. if ($sid > 0 && empty($completeMap[$sid]) && isset($inboundTimeMap[$sid])) {
  179. $completeMap[$sid] = $inboundTimeMap[$sid];
  180. }
  181. $complete = ProcuremenTime::resolveCompletedDone($r, $completeMap);
  182. // 未评分完结时勿用 pick_time 冒充完结:无完结日志则留空(审批时间仍单独展示)
  183. if ($sid > 0 && empty($completeTsMap[$sid]) && empty($inboundTimeMap[$sid])) {
  184. $complete = ['ts' => 0, 'text' => ''];
  185. }
  186. $out[] = [
  187. 'id' => (int)($r['id'] ?? 0),
  188. 'scydgy_id' => $sid,
  189. 'CCYDH' => trim((string)($r['CCYDH'] ?? '')),
  190. 'CYJMC' => trim((string)($r['CYJMC'] ?? '')),
  191. 'CGYMC' => trim((string)($r['CGYMC'] ?? '')),
  192. 'pick_company_name' => trim((string)($r['pick_company_name'] ?? '')),
  193. 'inbound_result' => $inboundMap[$sid] ?? '',
  194. 'createtime' => $approve['ts'],
  195. 'createtime_text' => $approve['text'],
  196. 'complete_time' => $complete['ts'],
  197. 'complete_time_text' => $complete['text'],
  198. ];
  199. }
  200. $merged = $this->collapseArchiveRowsByOrder($out);
  201. $nMerged = count($merged);
  202. if ($nMerged > 1) {
  203. if ($sortField === 'id') {
  204. usort($merged, function ($a, $b) use ($orderDir) {
  205. $cmp = ((int)($a['id'] ?? 0)) <=> ((int)($b['id'] ?? 0));
  206. if ($cmp === 0) {
  207. return strcmp((string)($a['CCYDH'] ?? ''), (string)($b['CCYDH'] ?? ''));
  208. }
  209. return $orderDir === 'ASC' ? $cmp : -$cmp;
  210. });
  211. } elseif ($sortField === 'createtime') {
  212. usort($merged, function ($a, $b) use ($orderDir) {
  213. $ta = (int)($a['createtime'] ?? 0);
  214. $tb = (int)($b['createtime'] ?? 0);
  215. if ($ta === $tb) {
  216. return ((int)($b['id'] ?? 0)) <=> ((int)($a['id'] ?? 0));
  217. }
  218. return $orderDir === 'ASC' ? ($ta <=> $tb) : ($tb <=> $ta);
  219. });
  220. } else {
  221. // 默认 / complete_time:按完结时间;无完结时间的排后面
  222. usort($merged, function ($a, $b) use ($orderDir) {
  223. $ta = (int)($a['complete_time'] ?? 0);
  224. $tb = (int)($b['complete_time'] ?? 0);
  225. if ($ta <= 0 && $tb <= 0) {
  226. return ((int)($b['id'] ?? 0)) <=> ((int)($a['id'] ?? 0));
  227. }
  228. if ($ta <= 0) {
  229. return 1;
  230. }
  231. if ($tb <= 0) {
  232. return -1;
  233. }
  234. if ($ta === $tb) {
  235. return ((int)($b['id'] ?? 0)) <=> ((int)($a['id'] ?? 0));
  236. }
  237. return $orderDir === 'ASC' ? ($ta <=> $tb) : ($tb <=> $ta);
  238. });
  239. }
  240. }
  241. $pageRows = array_slice($merged, $offset, $limit);
  242. return json(['total' => $nMerged, 'rows' => $pageRows]);
  243. }
  244. return $this->view->fetch();
  245. }
  246. /**
  247. * 历史存证:同一订单号 CCYDH 合并为一行(与审批/确认列表一致)
  248. *
  249. * @param array<int, array<string, mixed>> $rows
  250. * @return array<int, array<string, mixed>>
  251. */
  252. protected function collapseArchiveRowsByOrder(array $rows): array
  253. {
  254. if ($rows === []) {
  255. return [];
  256. }
  257. $groups = [];
  258. foreach ($rows as $row) {
  259. if (!is_array($row)) {
  260. continue;
  261. }
  262. $dh = trim((string)($row['CCYDH'] ?? ''));
  263. $key = $dh !== '' ? $dh : ('_id_' . (int)($row['id'] ?? 0));
  264. if (!isset($groups[$key])) {
  265. $groups[$key] = [];
  266. }
  267. $groups[$key][] = $row;
  268. }
  269. $out = [];
  270. foreach ($groups as $groupRows) {
  271. if ($groupRows === []) {
  272. continue;
  273. }
  274. usort($groupRows, function ($a, $b) {
  275. return ((int)($a['id'] ?? 0)) <=> ((int)($b['id'] ?? 0));
  276. });
  277. $head = $groupRows[0];
  278. $gymcList = [];
  279. $supplierList = [];
  280. $inboundList = [];
  281. foreach ($groupRows as $r) {
  282. $g = trim((string)($r['CGYMC'] ?? ''));
  283. if ($g !== '' && !in_array($g, $gymcList, true)) {
  284. $gymcList[] = $g;
  285. }
  286. $sn = trim((string)($r['pick_company_name'] ?? ''));
  287. if ($sn !== '' && !in_array($sn, $supplierList, true)) {
  288. $supplierList[] = $sn;
  289. }
  290. $ir = trim((string)($r['inbound_result'] ?? ''));
  291. if ($ir !== '' && !in_array($ir, $inboundList, true)) {
  292. $inboundList[] = $ir;
  293. }
  294. }
  295. $merged = $head;
  296. if (count($groupRows) > 1) {
  297. $merged['CGYMC'] = implode('、', $gymcList);
  298. }
  299. $merged['pick_company_name'] = implode('、', $supplierList);
  300. $merged['inbound_result'] = $inboundList !== [] ? implode('、', $inboundList) : '';
  301. $merged['process_count'] = count($groupRows);
  302. $latestApproveTs = 0;
  303. $latestApproveText = '';
  304. $latestCompleteTs = 0;
  305. $latestCompleteText = '';
  306. foreach ($groupRows as $r) {
  307. $ts = (int)($r['createtime'] ?? 0);
  308. if ($ts <= 0 && !empty($r['createtime_text'])) {
  309. $ts = (int)strtotime((string)$r['createtime_text']);
  310. }
  311. if ($ts > $latestApproveTs) {
  312. $latestApproveTs = $ts;
  313. $latestApproveText = trim((string)($r['createtime_text'] ?? ''));
  314. }
  315. $cts = (int)($r['complete_time'] ?? 0);
  316. if ($cts <= 0 && !empty($r['complete_time_text'])) {
  317. $cts = (int)strtotime((string)$r['complete_time_text']);
  318. }
  319. if ($cts > $latestCompleteTs) {
  320. $latestCompleteTs = $cts;
  321. $latestCompleteText = trim((string)($r['complete_time_text'] ?? ''));
  322. }
  323. }
  324. if ($latestApproveTs > 0) {
  325. $merged['createtime'] = $latestApproveTs;
  326. if ($latestApproveText === '') {
  327. $latestApproveText = date('Y-m-d H:i:s', $latestApproveTs);
  328. }
  329. $merged['createtime_text'] = ProcuremenTime::formatDisplayDateTime($latestApproveText);
  330. }
  331. if ($latestCompleteTs > 0) {
  332. $merged['complete_time'] = $latestCompleteTs;
  333. if ($latestCompleteText === '') {
  334. $latestCompleteText = date('Y-m-d H:i:s', $latestCompleteTs);
  335. }
  336. $merged['complete_time_text'] = ProcuremenTime::formatDisplayDateTime($latestCompleteText);
  337. } else {
  338. $merged['complete_time'] = 0;
  339. $merged['complete_time_text'] = '';
  340. }
  341. $maxId = (int)($head['id'] ?? 0);
  342. foreach ($groupRows as $r) {
  343. $maxId = max($maxId, (int)($r['id'] ?? 0));
  344. }
  345. $merged['id'] = $maxId;
  346. $out[] = $merged;
  347. }
  348. return $out;
  349. }
  350. }