ProductionInformationStatistics.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862
  1. <?php
  2. namespace app\api\controller;
  3. use app\common\controller\Api;
  4. use think\Db;
  5. use think\Exception;
  6. use think\Log;
  7. /**
  8. * 设备生产信息统计
  9. */
  10. class ProductionInformationStatistics extends Api
  11. {
  12. protected $noNeedLogin = ['*'];
  13. protected $noNeedRight = ['*'];
  14. /** @var string 主表 */
  15. protected $tableName = '设备_生产信息统计表';
  16. /** @var string 日志表 */
  17. protected $logTableName = '设备_生产信息统计操作日志';
  18. /** @var array 固定班组 */
  19. protected $defaultShifts = ['A班', 'B班'];
  20. /** @var string 设备基本资料表 */
  21. protected $equipmentTable = '设备_基本资料';
  22. /** @var array 可编辑业务字段 */
  23. protected $dataFields = [
  24. 'sczl_jtbh',
  25. 'sczl_bzdh',
  26. 'sczl_rq',
  27. 'theoretical_speed',
  28. 'scheduled_production',
  29. 'scheduled_maintenance',
  30. 'scheduled_handover',
  31. 'fault',
  32. 'changeover',
  33. 'proofing',
  34. 'abnormal',
  35. 'planned_output',
  36. 'actual_output',
  37. 'defective_product',
  38. ];
  39. /** @var array 字段中文名 */
  40. protected $fieldLabels = [
  41. 'sczl_jtbh' => '设备编号',
  42. 'sczl_bzdh' => '班组代号',
  43. 'sczl_rq' => '日期',
  44. 'theoretical_speed' => '理论速度',
  45. 'scheduled_production' => '计划生产时间',
  46. 'scheduled_maintenance'=> '计划保养时间',
  47. 'scheduled_handover' => '计划早会交接班时间',
  48. 'fault' => '设备故障时间',
  49. 'changeover' => '换型时间',
  50. 'proofing' => '打样时间',
  51. 'abnormal' => '异常时间',
  52. 'planned_output' => '计划完成产量',
  53. 'actual_output' => '实际产量',
  54. 'defective_product' => '不良品数量',
  55. ];
  56. /** @var array 按月汇总时求和的字段 */
  57. protected $sumFields = [
  58. 'scheduled_production',
  59. 'scheduled_maintenance',
  60. 'scheduled_handover',
  61. 'fault',
  62. 'changeover',
  63. 'proofing',
  64. 'abnormal',
  65. 'planned_output',
  66. 'actual_output',
  67. 'defective_product',
  68. ];
  69. /**
  70. * 1. 左侧菜单:年 -> 月 -> 日
  71. * @ApiMethod (GET)
  72. */
  73. public function getMenu()
  74. {
  75. if (!$this->request->isGet()) {
  76. $this->error('请求错误');
  77. }
  78. $dateList = Db::name($this->tableName)
  79. ->where('sczl_rq', 'not null')
  80. ->field("DISTINCT DATE_FORMAT(sczl_rq, '%Y-%m-%d') AS rq")
  81. ->order('rq DESC')
  82. ->select();
  83. $tree = [];
  84. foreach ($dateList as $item) {
  85. $rq = $item['rq'];
  86. if (empty($rq)) {
  87. continue;
  88. }
  89. $year = date('Y', strtotime($rq));
  90. $month = date('Y-m', strtotime($rq));
  91. if (!isset($tree[$year])) {
  92. $tree[$year] = [
  93. 'year' => $year,
  94. 'children' => [],
  95. ];
  96. }
  97. if (!isset($tree[$year]['children'][$month])) {
  98. $tree[$year]['children'][$month] = [
  99. 'month' => $month,
  100. 'children' => [],
  101. ];
  102. }
  103. if (!in_array($rq, $tree[$year]['children'][$month]['children'], true)) {
  104. $tree[$year]['children'][$month]['children'][] = $rq;
  105. }
  106. }
  107. $result = array_values($tree);
  108. foreach ($result as &$yearItem) {
  109. $yearItem['children'] = array_values($yearItem['children']);
  110. }
  111. unset($yearItem);
  112. $this->success('成功', $result);
  113. }
  114. /**
  115. * 2. 表格数据查询(联表设备基本资料)
  116. * @ApiMethod (GET)
  117. * @param string date 时间参数:Y-m-d 查日明细,Y-m 查月汇总
  118. */
  119. public function getList()
  120. {
  121. if (!$this->request->isGet()) {
  122. $this->error('请求错误');
  123. }
  124. $time = trim((string)$this->request->param('date', ''));
  125. if ($time === '') {
  126. $this->error('请传入 date 参数,格式 Y-m-d 或 Y-m');
  127. }
  128. $query = $this->parseQueryTime($time);
  129. if ($query['type'] === 'day') {
  130. $list = $this->queryDailyList($query['start'], $query['end']);
  131. } else {
  132. $list = $this->queryMonthlyList($query['start'], $query['end'], $query['month']);
  133. }
  134. $this->success('成功', $list);
  135. }
  136. /**
  137. * 解析查询时间:Y-m-d 为日,Y-m 为月
  138. */
  139. protected function parseQueryTime($time)
  140. {
  141. if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $time)) {
  142. $timestamp = strtotime($time);
  143. if ($timestamp === false || date('Y-m-d', $timestamp) !== $time) {
  144. $this->error('日期格式错误,请传 Y-m-d,如 2026-07-23');
  145. }
  146. return [
  147. 'type' => 'day',
  148. 'start' => date('Y-m-d 00:00:00', $timestamp),
  149. 'end' => date('Y-m-d 23:59:59', $timestamp),
  150. ];
  151. }
  152. if (preg_match('/^\d{4}-\d{2}$/', $time)) {
  153. $timestamp = strtotime($time . '-01');
  154. if ($timestamp === false || date('Y-m', $timestamp) !== $time) {
  155. $this->error('月份格式错误,请传 Y-m,如 2026-07');
  156. }
  157. return [
  158. 'type' => 'month',
  159. 'month' => $time,
  160. 'start' => date('Y-m-01 00:00:00', $timestamp),
  161. 'end' => date('Y-m-t 23:59:59', $timestamp),
  162. ];
  163. }
  164. $this->error('时间格式错误,请传 Y-m-d(具体日期)或 Y-m(月份)');
  165. }
  166. /**
  167. * 3. 获取车间、机组、机台编号、机台名称、班组基础数据
  168. * @ApiMethod (GET)
  169. */
  170. public function getBaseData()
  171. {
  172. if (!$this->request->isGet()) {
  173. $this->error('请求错误');
  174. }
  175. $equipments = Db::name($this->equipmentTable)
  176. ->where('设备编组', '<>', '')
  177. ->where('sys_sbID', '<>', '')
  178. ->where('使用部门', '<>', '研发中心')
  179. ->field([
  180. 'rtrim(使用部门) as 车间',
  181. 'rtrim(设备编组) as 机组',
  182. 'rtrim(设备编号) as 机台编号',
  183. 'rtrim(设备名称) as 机台名称',
  184. '平均车速',
  185. ])
  186. ->order('设备编组,设备编号')
  187. ->select();
  188. if (empty($equipments)) {
  189. $this->success('未获取到机台数据', []);
  190. }
  191. $list = [];
  192. foreach ($equipments as $item) {
  193. $code = $this->trimText($item['机台编号']);
  194. $list[] = [
  195. '车间' => $this->trimText($item['车间']),
  196. '机组' => $this->trimText($item['机组']),
  197. '机台编号' => $code,
  198. '机台名称' => $this->trimText($item['机台名称']),
  199. '设备编号' => $code,
  200. '理论速度' => (int)$this->formatNumber($item['平均车速']),
  201. '班组列表' => $this->defaultShifts,
  202. ];
  203. }
  204. $this->success('成功', $this->sortEquipmentList($list));
  205. }
  206. /**
  207. * 4. 批量新增(每个机台每个班组一条)
  208. * @ApiMethod (POST)
  209. * @param array list 数据列表
  210. * @param string sys_id 操作人
  211. */
  212. public function batchAdd()
  213. {
  214. if (!$this->request->isPost()) {
  215. $this->error('请求错误');
  216. }
  217. $params = $this->request->post();
  218. $list = isset($params['list']) ? $params['list'] : [];
  219. $operator = isset($params['sys_id']) ? trim($params['sys_id']) : '';
  220. if (empty($list) || !is_array($list)) {
  221. $this->error('参数 list 不能为空');
  222. }
  223. if ($operator === '') {
  224. $this->error('参数 sys_id 不能为空');
  225. }
  226. $now = date('Y-m-d H:i:s');
  227. $insertRows = [];
  228. $logs = [];
  229. Db::startTrans();
  230. try {
  231. foreach ($list as $index => $row) {
  232. if (!is_array($row)) {
  233. throw new Exception('第 ' . ($index + 1) . ' 条数据格式错误');
  234. }
  235. $data = $this->normalizeRow($row);
  236. $this->validateRow($data, $index + 1);
  237. $exists = Db::name($this->tableName)
  238. ->where('sczl_jtbh', $data['sczl_jtbh'])
  239. ->where('sczl_bzdh', $data['sczl_bzdh'])
  240. ->where('sczl_rq', $data['sczl_rq'])
  241. ->find();
  242. if ($exists) {
  243. throw new Exception(sprintf(
  244. '第 %d 条记录已存在:设备[%s] 班组[%s] 日期[%s]',
  245. $index + 1,
  246. $data['sczl_jtbh'],
  247. $data['sczl_bzdh'],
  248. date('Y-m-d', strtotime($data['sczl_rq']))
  249. ));
  250. }
  251. $data['sys_id'] = $operator;
  252. $data['sys_rq'] = $now;
  253. $insertRows[] = $data;
  254. }
  255. foreach ($insertRows as $data) {
  256. $recordId = Db::name($this->tableName)->insertGetId($data);
  257. if (!$recordId) {
  258. throw new Exception('新增失败');
  259. }
  260. $logs = array_merge($logs, $this->buildAddLogs($recordId, $data, $operator, $now));
  261. }
  262. if (!empty($logs)) {
  263. Db::name($this->logTableName)->insertAll($logs);
  264. }
  265. Db::commit();
  266. $this->success('批量新增成功', ['count' => count($insertRows)]);
  267. } catch (Exception $e) {
  268. Db::rollback();
  269. $this->error($e->getMessage());
  270. }
  271. }
  272. /**
  273. * 5. 批量修改
  274. * @ApiMethod (POST)
  275. * @param array list 含 id 的数据列表
  276. * @param string sys_id 操作人
  277. */
  278. public function batchUpdate()
  279. {
  280. if (!$this->request->isPost()) {
  281. $this->error('请求错误');
  282. }
  283. $params = $this->request->post();
  284. $list = isset($params['list']) ? $params['list'] : [];
  285. $operator = isset($params['sys_id']) ? trim($params['sys_id']) : '';
  286. if (empty($list) || !is_array($list)) {
  287. $this->error('参数 list 不能为空');
  288. }
  289. if ($operator === '') {
  290. $this->error('参数 sys_id 不能为空');
  291. }
  292. $now = date('Y-m-d H:i:s');
  293. $logs = [];
  294. $updateCount = 0;
  295. Db::startTrans();
  296. try {
  297. foreach ($list as $index => $row) {
  298. if (!is_array($row) || empty($row['id'])) {
  299. throw new Exception('第 ' . ($index + 1) . ' 条数据缺少 id');
  300. }
  301. $id = (int)$row['id'];
  302. $old = Db::name($this->tableName)->where('id', $id)->find();
  303. if (empty($old)) {
  304. throw new Exception('第 ' . ($index + 1) . ' 条记录不存在');
  305. }
  306. $data = $this->normalizeRow($row, false);
  307. unset($data['id']);
  308. if (array_key_exists('sczl_bzdh', $row)) {
  309. if ($data['sczl_bzdh'] === '' || !in_array($data['sczl_bzdh'], $this->defaultShifts, true)) {
  310. throw new Exception('第 ' . ($index + 1) . ' 条班组代号只能为 A班 或 B班');
  311. }
  312. }
  313. $checkJtbh = isset($data['sczl_jtbh']) ? $data['sczl_jtbh'] : rtrim($old['sczl_jtbh']);
  314. $checkBzdh = isset($data['sczl_bzdh']) ? $data['sczl_bzdh'] : rtrim($old['sczl_bzdh']);
  315. $checkRq = isset($data['sczl_rq']) ? $data['sczl_rq'] : $old['sczl_rq'];
  316. $duplicate = Db::name($this->tableName)
  317. ->where('sczl_jtbh', $checkJtbh)
  318. ->where('sczl_bzdh', $checkBzdh)
  319. ->where('sczl_rq', $checkRq)
  320. ->where('id', '<>', $id)
  321. ->find();
  322. if ($duplicate) {
  323. throw new Exception(sprintf(
  324. '第 %d 条修改后与已有记录冲突:设备[%s] 班组[%s] 日期[%s]',
  325. $index + 1,
  326. $checkJtbh,
  327. $checkBzdh,
  328. date('Y-m-d', strtotime($checkRq))
  329. ));
  330. }
  331. $data['mod_id'] = $operator;
  332. $data['mod_rq'] = $now;
  333. $changed = [];
  334. foreach ($data as $field => $value) {
  335. if (!array_key_exists($field, $old)) {
  336. continue;
  337. }
  338. $oldValue = $old[$field];
  339. if ((string)$oldValue !== (string)$value) {
  340. $changed[$field] = $value;
  341. }
  342. }
  343. if (empty($changed)) {
  344. continue;
  345. }
  346. $result = Db::name($this->tableName)->where('id', $id)->update($changed + [
  347. 'mod_id' => $operator,
  348. 'mod_rq' => $now,
  349. ]);
  350. if ($result === false) {
  351. throw new Exception('第 ' . ($index + 1) . ' 条修改失败');
  352. }
  353. $logs = array_merge($logs, $this->buildUpdateLogs($id, $old, $changed, $operator, $now));
  354. $updateCount++;
  355. }
  356. if (!empty($logs)) {
  357. Db::name($this->logTableName)->insertAll($logs);
  358. }
  359. Db::commit();
  360. $this->success('批量修改成功', ['count' => $updateCount]);
  361. } catch (Exception $e) {
  362. Db::rollback();
  363. $this->error($e->getMessage());
  364. }
  365. }
  366. /**
  367. * 6. 批量删除
  368. * @ApiMethod (POST)
  369. * @param string ids 逗号分隔 id
  370. * @param string sys_id 操作人
  371. */
  372. public function batchDelete()
  373. {
  374. if (!$this->request->isPost()) {
  375. $this->error('请求错误');
  376. }
  377. $params = $this->request->post();
  378. $idsParam = isset($params['ids']) ? trim($params['ids']) : '';
  379. $operator = isset($params['sys_id']) ? trim($params['sys_id']) : '';
  380. if ($idsParam === '') {
  381. $this->error('参数 ids 不能为空');
  382. }
  383. if ($operator === '') {
  384. $this->error('参数 sys_id 不能为空');
  385. }
  386. $ids = array_values(array_filter(array_map('intval', explode(',', $idsParam))));
  387. if (empty($ids)) {
  388. $this->error('ids 格式错误');
  389. }
  390. $records = Db::name($this->tableName)->where('id', 'in', $ids)->select();
  391. if (count($records) !== count($ids)) {
  392. $this->error('部分记录不存在,请刷新后重试');
  393. }
  394. $now = date('Y-m-d H:i:s');
  395. $logs = [];
  396. Db::startTrans();
  397. try {
  398. foreach ($records as $record) {
  399. $logs = array_merge($logs, $this->buildDeleteLogs((int)$record['id'], $record, $operator, $now));
  400. }
  401. $deleteResult = Db::name($this->tableName)->where('id', 'in', $ids)->delete();
  402. if ($deleteResult === false) {
  403. throw new Exception('删除失败');
  404. }
  405. if (!empty($logs)) {
  406. Db::name($this->logTableName)->insertAll($logs);
  407. }
  408. Db::commit();
  409. $this->success('批量删除成功', ['count' => $deleteResult]);
  410. } catch (Exception $e) {
  411. Db::rollback();
  412. $this->error($e->getMessage());
  413. }
  414. }
  415. /**
  416. * 按日查询明细
  417. */
  418. protected function queryDailyList($start, $end)
  419. {
  420. $list = Db::name($this->tableName)
  421. ->alias('a')
  422. ->join($this->equipmentTable . ' b', 'a.sczl_jtbh = b.设备编号', 'LEFT')
  423. ->where('a.sczl_rq', '>=', $start)
  424. ->where('a.sczl_rq', '<=', $end)
  425. ->field($this->buildSelectFields(false))
  426. ->select();
  427. return $this->formatList($list);
  428. }
  429. /**
  430. * 按月汇总查询
  431. */
  432. protected function queryMonthlyList($start, $end, $month)
  433. {
  434. $sumExpr = [];
  435. foreach ($this->sumFields as $field) {
  436. $sumExpr[] = "SUM(a.{$field}) as {$field}";
  437. }
  438. $list = Db::name($this->tableName)
  439. ->alias('a')
  440. ->join($this->equipmentTable . ' b', 'a.sczl_jtbh = b.设备编号', 'LEFT')
  441. ->where('a.sczl_rq', '>=', $start)
  442. ->where('a.sczl_rq', '<=', $end)
  443. ->field(array_merge([
  444. 'MIN(a.id) as id',
  445. 'a.sczl_jtbh',
  446. 'a.sczl_bzdh',
  447. 'MAX(a.theoretical_speed) as theoretical_speed',
  448. 'rtrim(b.使用部门) as 车间',
  449. 'rtrim(b.设备编组) as 机组',
  450. 'rtrim(b.设备名称) as 机台名称',
  451. ], $sumExpr))
  452. ->group('a.sczl_jtbh,a.sczl_bzdh,b.使用部门,b.设备编组,b.设备名称')
  453. ->select();
  454. foreach ($list as &$row) {
  455. $row['sczl_rq'] = $month . '-01 00:00:00';
  456. $row['query_type'] = 'month';
  457. $row['query_month'] = $month;
  458. }
  459. unset($row);
  460. return $this->formatList($list);
  461. }
  462. /**
  463. * 查询字段
  464. */
  465. protected function buildSelectFields($isMonth = false)
  466. {
  467. $fields = [
  468. 'a.id',
  469. 'a.sczl_jtbh',
  470. 'a.sczl_bzdh',
  471. 'a.sczl_rq',
  472. 'a.theoretical_speed',
  473. 'a.scheduled_production',
  474. 'a.scheduled_maintenance',
  475. 'a.scheduled_handover',
  476. 'a.fault',
  477. 'a.changeover',
  478. 'a.proofing',
  479. 'a.abnormal',
  480. 'a.planned_output',
  481. 'a.actual_output',
  482. 'a.defective_product',
  483. 'a.sys_id',
  484. 'a.sys_rq',
  485. 'a.mod_id',
  486. 'a.mod_rq',
  487. 'rtrim(b.使用部门) as 车间',
  488. 'rtrim(b.设备编组) as 机组',
  489. 'rtrim(b.设备名称) as 机台名称',
  490. ];
  491. return $fields;
  492. }
  493. /**
  494. * 格式化返回列表
  495. */
  496. protected function formatList($list)
  497. {
  498. $result = [];
  499. foreach ($list as $row) {
  500. $result[] = [
  501. 'id' => (int)$row['id'],
  502. '车间' => isset($row['车间']) ? rtrim($row['车间']) : '',
  503. '机组' => isset($row['机组']) ? rtrim($row['机组']) : '',
  504. '机台编号' => rtrim($row['sczl_jtbh']),
  505. '设备编号' => rtrim($row['sczl_jtbh']),
  506. '机台名称' => isset($row['机台名称']) ? rtrim($row['机台名称']) : '',
  507. '班组' => $this->normalizeShift($row['sczl_bzdh']),
  508. 'sczl_jtbh' => rtrim($row['sczl_jtbh']),
  509. 'sczl_bzdh' => $this->normalizeShift($row['sczl_bzdh']),
  510. 'sczl_rq' => $row['sczl_rq'],
  511. 'theoretical_speed' => $this->formatNumber($row['theoretical_speed']),
  512. 'scheduled_production' => $this->formatNumber($row['scheduled_production']),
  513. 'scheduled_maintenance'=> $this->formatNumber($row['scheduled_maintenance']),
  514. 'scheduled_handover' => $this->formatNumber($row['scheduled_handover']),
  515. 'fault' => $this->formatNumber($row['fault']),
  516. 'changeover' => $this->formatNumber($row['changeover']),
  517. 'proofing' => $this->formatNumber($row['proofing']),
  518. 'abnormal' => $this->formatNumber($row['abnormal']),
  519. 'planned_output' => $this->formatNumber($row['planned_output']),
  520. 'actual_output' => $this->formatNumber($row['actual_output']),
  521. 'defective_product' => $this->formatNumber($row['defective_product']),
  522. 'sys_id' => isset($row['sys_id']) ? rtrim($row['sys_id']) : '',
  523. 'sys_rq' => isset($row['sys_rq']) ? $row['sys_rq'] : '',
  524. 'mod_id' => isset($row['mod_id']) ? rtrim($row['mod_id']) : '',
  525. 'mod_rq' => isset($row['mod_rq']) ? $row['mod_rq'] : '',
  526. 'query_type' => isset($row['query_type']) ? $row['query_type'] : 'day',
  527. 'query_month' => isset($row['query_month']) ? $row['query_month'] : '',
  528. ];
  529. }
  530. return $this->sortEquipmentList($result);
  531. }
  532. /**
  533. * 按设备编组、机台编号数字排序
  534. */
  535. protected function sortEquipmentList(array $list)
  536. {
  537. usort($list, function ($a, $b) {
  538. return $this->compareEquipmentRow($a, $b);
  539. });
  540. return $list;
  541. }
  542. /**
  543. * 设备行排序比较
  544. */
  545. protected function compareEquipmentRow($a, $b)
  546. {
  547. $groupA = $this->trimText(isset($a['机组']) ? $a['机组'] : '');
  548. $groupB = $this->trimText(isset($b['机组']) ? $b['机组'] : '');
  549. $groupOrderA = $this->extractGroupOrder($groupA);
  550. $groupOrderB = $this->extractGroupOrder($groupB);
  551. if ($groupOrderA !== $groupOrderB) {
  552. return $groupOrderA <=> $groupOrderB;
  553. }
  554. if ($groupA !== $groupB) {
  555. return strcmp($groupA, $groupB);
  556. }
  557. $codeA = $this->getMachineCodeFromRow($a);
  558. $codeB = $this->getMachineCodeFromRow($b);
  559. $numA = $this->extractMachineNumber($codeA);
  560. $numB = $this->extractMachineNumber($codeB);
  561. if ($numA !== $numB) {
  562. return $numA <=> $numB;
  563. }
  564. if ($codeA !== $codeB) {
  565. return strcmp($codeA, $codeB);
  566. }
  567. $shiftA = isset($a['班组']) ? $a['班组'] : (isset($a['sczl_bzdh']) ? $a['sczl_bzdh'] : '');
  568. $shiftB = isset($b['班组']) ? $b['班组'] : (isset($b['sczl_bzdh']) ? $b['sczl_bzdh'] : '');
  569. return strcmp($shiftA, $shiftB);
  570. }
  571. /**
  572. * 提取设备编组前缀序号(如 03、卷凹机组 -> 3)
  573. */
  574. protected function extractGroupOrder($group)
  575. {
  576. $group = $this->trimText($group);
  577. if (preg_match('/^(\d+)/', $group, $matches)) {
  578. return (int)$matches[1];
  579. }
  580. return PHP_INT_MAX;
  581. }
  582. /**
  583. * 提取机台编号中的数字(如 YWY01# -> 1)
  584. */
  585. protected function extractMachineNumber($code)
  586. {
  587. $code = $this->trimText($code);
  588. if (preg_match('/(\d+)/', $code, $matches)) {
  589. return (int)$matches[1];
  590. }
  591. return PHP_INT_MAX;
  592. }
  593. /**
  594. * 从返回行中获取机台编号
  595. */
  596. protected function getMachineCodeFromRow($row)
  597. {
  598. if (!empty($row['机台编号'])) {
  599. return $this->trimText($row['机台编号']);
  600. }
  601. if (!empty($row['sczl_jtbh'])) {
  602. return $this->trimText($row['sczl_jtbh']);
  603. }
  604. if (!empty($row['设备编号'])) {
  605. return $this->trimText($row['设备编号']);
  606. }
  607. return '';
  608. }
  609. /**
  610. * 去除字符串首尾空白(含全角空格)
  611. */
  612. protected function trimText($value)
  613. {
  614. return preg_replace('/^[\s\x{3000}]+|[\s\x{3000}]+$/u', '', (string)$value);
  615. }
  616. /**
  617. * 班组名称规范化:仅支持 A班、B班
  618. */
  619. protected function normalizeShift($shift)
  620. {
  621. $shift = $this->trimText($shift);
  622. $shift = preg_replace('/\s+/u', '', $shift);
  623. if ($shift === '') {
  624. return '';
  625. }
  626. if (preg_match('/^A(?:班)?$/iu', $shift)) {
  627. return 'A班';
  628. }
  629. if (preg_match('/^B(?:班)?$/iu', $shift)) {
  630. return 'B班';
  631. }
  632. return '';
  633. }
  634. /**
  635. * 标准化单行数据
  636. */
  637. protected function normalizeRow(array $row, $requireAll = true)
  638. {
  639. $data = [];
  640. foreach ($this->dataFields as $field) {
  641. if (!array_key_exists($field, $row)) {
  642. if (!$requireAll) {
  643. continue;
  644. }
  645. $data[$field] = in_array($field, $this->sumFields, true) || $field === 'theoretical_speed' ? 0 : '';
  646. continue;
  647. }
  648. if ($field === 'sczl_rq') {
  649. $data[$field] = date('Y-m-d 00:00:00', strtotime($row[$field]));
  650. continue;
  651. }
  652. if (in_array($field, ['sczl_jtbh', 'sczl_bzdh'], true)) {
  653. $value = trim((string)$row[$field]);
  654. $data[$field] = $field === 'sczl_bzdh' ? $this->normalizeShift($value) : $value;
  655. continue;
  656. }
  657. if ($field === 'theoretical_speed') {
  658. $data[$field] = (int)$row[$field];
  659. continue;
  660. }
  661. $data[$field] = $row[$field] === '' || $row[$field] === null ? 0 : $row[$field];
  662. }
  663. return $data;
  664. }
  665. /**
  666. * 校验单行数据
  667. */
  668. protected function validateRow(array $data, $lineNo)
  669. {
  670. if ($data['sczl_jtbh'] === '') {
  671. throw new Exception('第 ' . $lineNo . ' 条设备编号不能为空');
  672. }
  673. if ($data['sczl_bzdh'] === '') {
  674. throw new Exception('第 ' . $lineNo . ' 条班组代号不能为空,且只能为 A班 或 B班');
  675. }
  676. if (!in_array($data['sczl_bzdh'], $this->defaultShifts, true)) {
  677. throw new Exception('第 ' . $lineNo . ' 条班组代号只能为 A班 或 B班');
  678. }
  679. if (empty($data['sczl_rq'])) {
  680. throw new Exception('第 ' . $lineNo . ' 条日期不能为空');
  681. }
  682. $equipment = Db::name($this->equipmentTable)
  683. ->where('设备编号', $data['sczl_jtbh'])
  684. ->find();
  685. if (empty($equipment)) {
  686. throw new Exception('第 ' . $lineNo . ' 条设备编号不存在:' . $data['sczl_jtbh']);
  687. }
  688. }
  689. /**
  690. * 构建新增日志
  691. */
  692. protected function buildAddLogs($recordId, array $data, $operator, $time)
  693. {
  694. $logs = [];
  695. foreach ($this->dataFields as $field) {
  696. if (!array_key_exists($field, $data)) {
  697. continue;
  698. }
  699. $logs[] = [
  700. 'record_id' => $recordId,
  701. 'operation_type' => 'add',
  702. 'field_name' => isset($this->fieldLabels[$field]) ? $this->fieldLabels[$field] : $field,
  703. 'old_value' => null,
  704. 'new_value' => (string)$data[$field],
  705. 'operator_id' => $operator,
  706. 'operation_time' => $time,
  707. ];
  708. }
  709. return $logs;
  710. }
  711. /**
  712. * 构建修改日志
  713. */
  714. protected function buildUpdateLogs($recordId, array $old, array $changed, $operator, $time)
  715. {
  716. $logs = [];
  717. foreach ($changed as $field => $newValue) {
  718. if ($field === 'mod_id' || $field === 'mod_rq') {
  719. continue;
  720. }
  721. $logs[] = [
  722. 'record_id' => $recordId,
  723. 'operation_type' => 'update',
  724. 'field_name' => isset($this->fieldLabels[$field]) ? $this->fieldLabels[$field] : $field,
  725. 'old_value' => isset($old[$field]) ? (string)$old[$field] : '',
  726. 'new_value' => (string)$newValue,
  727. 'operator_id' => $operator,
  728. 'operation_time' => $time,
  729. ];
  730. }
  731. return $logs;
  732. }
  733. /**
  734. * 构建删除日志
  735. */
  736. protected function buildDeleteLogs($recordId, array $record, $operator, $time)
  737. {
  738. $logs = [];
  739. foreach ($this->dataFields as $field) {
  740. if (!array_key_exists($field, $record)) {
  741. continue;
  742. }
  743. $logs[] = [
  744. 'record_id' => $recordId,
  745. 'operation_type' => 'delete',
  746. 'field_name' => isset($this->fieldLabels[$field]) ? $this->fieldLabels[$field] : $field,
  747. 'old_value' => (string)$record[$field],
  748. 'new_value' => null,
  749. 'operator_id' => $operator,
  750. 'operation_time' => $time,
  751. ];
  752. }
  753. return $logs;
  754. }
  755. /**
  756. * 数值格式化
  757. */
  758. protected function formatNumber($value)
  759. {
  760. if ($value === null || $value === '') {
  761. return 0;
  762. }
  763. return is_numeric($value) ? (float)$value : $value;
  764. }
  765. }