ProcuremenTime.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <?php
  2. namespace app\common\library;
  3. use think\Db;
  4. /**
  5. * 协助采购时间:操作日志、完结时间统一为可读 YYYY-MM-DD HH:mm:ss(不再显示 Unix 时间戳)
  6. */
  7. class ProcuremenTime
  8. {
  9. public static function nowDateTime(): string
  10. {
  11. return date('Y-m-d H:i:s');
  12. }
  13. /** @deprecated 使用 {@see nowDateTime} */
  14. public static function todayYmd(): string
  15. {
  16. return self::nowDateTime();
  17. }
  18. /** 列表/详情展示:年月日 + 时分秒 */
  19. public static function formatDisplayDateTime($value): string
  20. {
  21. if ($value === null || $value === '') {
  22. return '';
  23. }
  24. if (is_numeric($value) && (int)$value > 946684800) {
  25. return date('Y-m-d H:i:s', (int)$value);
  26. }
  27. $s = trim((string)$value);
  28. if ($s === '' || preg_match('/^0000-00-00/i', $s)) {
  29. return '';
  30. }
  31. if (preg_match('/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/', $s, $m)) {
  32. return $m[1];
  33. }
  34. if (preg_match('/^(\d{4}-\d{2}-\d{2})$/', $s, $m)) {
  35. return $m[1] . ' 00:00:00';
  36. }
  37. if (preg_match('/^(\d{4}-\d{2}-\d{2})/', $s, $m)) {
  38. $ts = strtotime($s);
  39. return ($ts !== false && $ts > 0) ? date('Y-m-d H:i:s', $ts) : $m[1] . ' 00:00:00';
  40. }
  41. $ts = strtotime($s);
  42. return ($ts !== false && $ts > 0) ? date('Y-m-d H:i:s', $ts) : $s;
  43. }
  44. /** 兼容旧调用:与 formatDisplayDateTime 相同 */
  45. public static function formatDisplayDate($value): string
  46. {
  47. return self::formatDisplayDateTime($value);
  48. }
  49. /** 排序/月份统计用 Unix 时间 */
  50. public static function parseToTimestamp($value): int
  51. {
  52. if ($value === null || $value === '') {
  53. return 0;
  54. }
  55. if (is_numeric($value) && (int)$value > 946684800) {
  56. return (int)$value;
  57. }
  58. $s = trim((string)$value);
  59. if ($s === '' || preg_match('/^0000-00-00/i', $s)) {
  60. return 0;
  61. }
  62. if (preg_match('/^(\d{4}-\d{2}-\d{2})(?:\s+\d{2}:\d{2}:\d{2})?/', $s)) {
  63. $ts = strtotime($s);
  64. return ($ts !== false && $ts > 0) ? $ts : 0;
  65. }
  66. $ts = strtotime($s);
  67. return ($ts !== false && $ts > 0) ? $ts : 0;
  68. }
  69. /**
  70. * @param int[] $scydgyIds
  71. * @param array<int,string> $actions
  72. * @return array<int, int> scydgy_id => unix
  73. */
  74. public static function loadOperLogTimestampMap(array $scydgyIds, array $actions): array
  75. {
  76. $out = [];
  77. $scydgyIds = array_values(array_unique(array_filter(array_map('intval', $scydgyIds))));
  78. $actions = array_values(array_filter(array_map('strval', $actions)));
  79. if ($scydgyIds === [] || $actions === []) {
  80. return $out;
  81. }
  82. try {
  83. $logs = Db::table('purchase_order_oper_log')
  84. ->where(ProcuremenOperLog::COL_SCYDGY_ID, 'in', $scydgyIds)
  85. ->where(ProcuremenOperLog::COL_ACTION, 'in', ProcuremenOperLog::expandActionQueryValues($actions))
  86. ->field(ProcuremenOperLog::COL_SCYDGY_ID . ',' . ProcuremenOperLog::COL_TIME)
  87. ->order('id', 'asc')
  88. ->select();
  89. } catch (\Throwable $e) {
  90. return $out;
  91. }
  92. if (!is_array($logs)) {
  93. return $out;
  94. }
  95. foreach ($logs as $log) {
  96. if (!is_array($log)) {
  97. continue;
  98. }
  99. $log = ProcuremenOperLog::normalizeRow($log);
  100. $sid = (int)($log['scydgy_id'] ?? 0);
  101. $ct = self::parseToTimestamp($log['createtime'] ?? null);
  102. if ($sid > 0 && $ct > 946684800) {
  103. $out[$sid] = isset($out[$sid]) ? max($out[$sid], $ct) : $ct;
  104. }
  105. }
  106. return $out;
  107. }
  108. /**
  109. * 已完结时间:与详情进度一致,优先采购确认/直接完结操作日志
  110. *
  111. * @param array<string, mixed> $row
  112. * @param array<int, int> $completeTsMap
  113. * @return array{ts:int, text:string}
  114. */
  115. public static function resolveCompletedDone(array $row, array $completeTsMap = []): array
  116. {
  117. $sid = (int)($row['scydgy_id'] ?? 0);
  118. $ts = 0;
  119. $text = '';
  120. if ($sid > 0 && isset($completeTsMap[$sid]) && (int)$completeTsMap[$sid] > 946684800) {
  121. $ts = (int)$completeTsMap[$sid];
  122. }
  123. if ($ts <= 0) {
  124. foreach (['pick_time', 'createtime', 'dStamp'] as $key) {
  125. $t = self::parseToTimestamp($row[$key] ?? null);
  126. if ($t > $ts) {
  127. $ts = $t;
  128. }
  129. }
  130. }
  131. if ($ts > 0) {
  132. $text = date('Y-m-d H:i:s', $ts);
  133. }
  134. return ['ts' => $ts, 'text' => $text];
  135. }
  136. /**
  137. * 操作记录展示文案:统一步骤用语(下发 / 确认供应商 / 审核确认供应商)
  138. */
  139. public static function formatOperLogContent(string $content, string $action = ''): string
  140. {
  141. $content = trim($content);
  142. if ($content === '') {
  143. return '';
  144. }
  145. // 统一成中文标签后再匹配(兼容历史英文码)
  146. $action = ProcuremenOperLog::toActionLabel(trim($action));
  147. if ($action === '审核确认供应商') {
  148. if (preg_match('/[「『](.+?)[」』]/u', $content, $m)) {
  149. return '审核确认供应商:已选定「' . trim($m[1]) . '」';
  150. }
  151. }
  152. if ($action === '确认供应商') {
  153. if (preg_match('/选定[::]\s*(.+)$/u', $content, $m)) {
  154. $name = trim($m[1]);
  155. if (preg_match('/^确认供应商/u', $content) && preg_match('/订单([^),]+)/u', $content, $om)) {
  156. return '确认供应商(订单' . trim($om[1]) . '):已选定「' . $name . '」';
  157. }
  158. return '确认供应商:已选定「' . $name . '」';
  159. }
  160. }
  161. if ($action === '开标验证') {
  162. if (preg_match('/开标验证/u', $content)) {
  163. return $content;
  164. }
  165. return '开标验证:' . $content;
  166. }
  167. if ($action === '补加供应商') {
  168. if (preg_match('/^补加供应商/u', $content)) {
  169. return $content;
  170. }
  171. return '补加供应商:' . $content;
  172. }
  173. if ($action === '重发短信') {
  174. if (preg_match('/重新发送短信/u', $content)) {
  175. return $content;
  176. }
  177. return '确认页重新发送短信:' . $content;
  178. }
  179. if ($action === '重发邮件') {
  180. if (preg_match('/重新发送邮件/u', $content)) {
  181. return $content;
  182. }
  183. return '确认页重新发送邮件:' . $content;
  184. }
  185. if ($action === '下发通知' || $action === '下发') {
  186. if (preg_match('/通知供应商[((](\d+)\s*家[))].*?[::]\s*(.+)$/u', $content, $m)) {
  187. return '下发:通知供应商(' . $m[1] . ' 家):' . trim($m[2]);
  188. }
  189. }
  190. $content = preg_replace('/^采购确认:已选中供应商/u', '审核确认供应商:已选定', $content);
  191. $content = preg_replace('/^采购确认:/u', '审核确认供应商:', $content);
  192. $content = preg_replace('/已选中供应商/u', '已选定', $content);
  193. $content = preg_replace('/^(合并)?(协助|外发)初选/u', '下发', $content);
  194. $content = preg_replace('/^采购终审驳回/u', '审核驳回', $content);
  195. $content = preg_replace('/退回外发初选/u', '退回协助初选', $content);
  196. $content = preg_replace('/^重新下发(([^)]+)),/u', '重新下发($1):', $content);
  197. $content = preg_replace('/;+$/u', '', $content);
  198. return trim($content);
  199. }
  200. }