ProductionInformationStatistics.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  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. ->order('设备编组,设备编号')
  186. ->select();
  187. if (empty($equipments)) {
  188. $this->success('未获取到机台数据', []);
  189. }
  190. $list = [];
  191. foreach ($equipments as $item) {
  192. $code = $this->trimText($item['机台编号']);
  193. $list[] = [
  194. '车间' => $this->trimText($item['车间']),
  195. '机组' => $this->trimText($item['机组']),
  196. '机台编号' => $code,
  197. '机台名称' => $this->trimText($item['机台名称']),
  198. '设备编号' => $code,
  199. '班组列表' => $this->defaultShifts,
  200. ];
  201. }
  202. $this->success('成功', $this->sortEquipmentList($list));
  203. }
  204. /**
  205. * 4. 批量新增(每个机台每个班组一条)
  206. * @ApiMethod (POST)
  207. * @param array list 数据列表
  208. * @param string sys_id 操作人
  209. */
  210. public function batchAdd()
  211. {
  212. if (!$this->request->isPost()) {
  213. $this->error('请求错误');
  214. }
  215. $params = $this->request->post();
  216. $list = isset($params['list']) ? $params['list'] : [];
  217. $operator = isset($params['sys_id']) ? trim($params['sys_id']) : '';
  218. if (empty($list) || !is_array($list)) {
  219. $this->error('参数 list 不能为空');
  220. }
  221. if ($operator === '') {
  222. $this->error('参数 sys_id 不能为空');
  223. }
  224. $now = date('Y-m-d H:i:s');
  225. $insertRows = [];
  226. $logs = [];
  227. Db::startTrans();
  228. try {
  229. foreach ($list as $index => $row) {
  230. if (!is_array($row)) {
  231. throw new Exception('第 ' . ($index + 1) . ' 条数据格式错误');
  232. }
  233. $data = $this->normalizeRow($row);
  234. $this->validateRow($data, $index + 1);
  235. $exists = Db::name($this->tableName)
  236. ->where('sczl_jtbh', $data['sczl_jtbh'])
  237. ->where('sczl_bzdh', $data['sczl_bzdh'])
  238. ->where('sczl_rq', $data['sczl_rq'])
  239. ->find();
  240. if ($exists) {
  241. throw new Exception(sprintf(
  242. '第 %d 条记录已存在:设备[%s] 班组[%s] 日期[%s]',
  243. $index + 1,
  244. $data['sczl_jtbh'],
  245. $data['sczl_bzdh'],
  246. date('Y-m-d', strtotime($data['sczl_rq']))
  247. ));
  248. }
  249. $data['sys_id'] = $operator;
  250. $data['sys_rq'] = $now;
  251. $insertRows[] = $data;
  252. }
  253. foreach ($insertRows as $data) {
  254. $recordId = Db::name($this->tableName)->insertGetId($data);
  255. if (!$recordId) {
  256. throw new Exception('新增失败');
  257. }
  258. $logs = array_merge($logs, $this->buildAddLogs($recordId, $data, $operator, $now));
  259. }
  260. if (!empty($logs)) {
  261. Db::name($this->logTableName)->insertAll($logs);
  262. }
  263. Db::commit();
  264. $this->success('批量新增成功', ['count' => count($insertRows)]);
  265. } catch (Exception $e) {
  266. Db::rollback();
  267. $this->error($e->getMessage());
  268. }
  269. }
  270. /**
  271. * 5. 批量修改
  272. * @ApiMethod (POST)
  273. * @param array list 含 id 的数据列表
  274. * @param string sys_id 操作人
  275. */
  276. public function batchUpdate()
  277. {
  278. if (!$this->request->isPost()) {
  279. $this->error('请求错误');
  280. }
  281. $params = $this->request->post();
  282. $list = isset($params['list']) ? $params['list'] : [];
  283. $operator = isset($params['sys_id']) ? trim($params['sys_id']) : '';
  284. if (empty($list) || !is_array($list)) {
  285. $this->error('参数 list 不能为空');
  286. }
  287. if ($operator === '') {
  288. $this->error('参数 sys_id 不能为空');
  289. }
  290. $now = date('Y-m-d H:i:s');
  291. $logs = [];
  292. $updateCount = 0;
  293. Db::startTrans();
  294. try {
  295. foreach ($list as $index => $row) {
  296. if (!is_array($row) || empty($row['id'])) {
  297. throw new Exception('第 ' . ($index + 1) . ' 条数据缺少 id');
  298. }
  299. $id = (int)$row['id'];
  300. $old = Db::name($this->tableName)->where('id', $id)->find();
  301. if (empty($old)) {
  302. throw new Exception('第 ' . ($index + 1) . ' 条记录不存在');
  303. }
  304. $data = $this->normalizeRow($row, false);
  305. unset($data['id']);
  306. if (array_key_exists('sczl_bzdh', $row)) {
  307. if ($data['sczl_bzdh'] === '' || !in_array($data['sczl_bzdh'], $this->defaultShifts, true)) {
  308. throw new Exception('第 ' . ($index + 1) . ' 条班组代号只能为 A班 或 B班');
  309. }
  310. }
  311. $checkJtbh = isset($data['sczl_jtbh']) ? $data['sczl_jtbh'] : rtrim($old['sczl_jtbh']);
  312. $checkBzdh = isset($data['sczl_bzdh']) ? $data['sczl_bzdh'] : rtrim($old['sczl_bzdh']);
  313. $checkRq = isset($data['sczl_rq']) ? $data['sczl_rq'] : $old['sczl_rq'];
  314. $duplicate = Db::name($this->tableName)
  315. ->where('sczl_jtbh', $checkJtbh)
  316. ->where('sczl_bzdh', $checkBzdh)
  317. ->where('sczl_rq', $checkRq)
  318. ->where('id', '<>', $id)
  319. ->find();
  320. if ($duplicate) {
  321. throw new Exception(sprintf(
  322. '第 %d 条修改后与已有记录冲突:设备[%s] 班组[%s] 日期[%s]',
  323. $index + 1,
  324. $checkJtbh,
  325. $checkBzdh,
  326. date('Y-m-d', strtotime($checkRq))
  327. ));
  328. }
  329. $data['mod_id'] = $operator;
  330. $data['mod_rq'] = $now;
  331. $changed = [];
  332. foreach ($data as $field => $value) {
  333. if (!array_key_exists($field, $old)) {
  334. continue;
  335. }
  336. $oldValue = $old[$field];
  337. if ((string)$oldValue !== (string)$value) {
  338. $changed[$field] = $value;
  339. }
  340. }
  341. if (empty($changed)) {
  342. continue;
  343. }
  344. $result = Db::name($this->tableName)->where('id', $id)->update($changed + [
  345. 'mod_id' => $operator,
  346. 'mod_rq' => $now,
  347. ]);
  348. if ($result === false) {
  349. throw new Exception('第 ' . ($index + 1) . ' 条修改失败');
  350. }
  351. $logs = array_merge($logs, $this->buildUpdateLogs($id, $old, $changed, $operator, $now));
  352. $updateCount++;
  353. }
  354. if (!empty($logs)) {
  355. Db::name($this->logTableName)->insertAll($logs);
  356. }
  357. Db::commit();
  358. $this->success('批量修改成功', ['count' => $updateCount]);
  359. } catch (Exception $e) {
  360. Db::rollback();
  361. $this->error($e->getMessage());
  362. }
  363. }
  364. /**
  365. * 6. 批量删除
  366. * @ApiMethod (POST)
  367. * @param string ids 逗号分隔 id
  368. * @param string sys_id 操作人
  369. */
  370. public function batchDelete()
  371. {
  372. if (!$this->request->isPost()) {
  373. $this->error('请求错误');
  374. }
  375. $params = $this->request->post();
  376. $idsParam = isset($params['ids']) ? trim($params['ids']) : '';
  377. $operator = isset($params['sys_id']) ? trim($params['sys_id']) : '';
  378. if ($idsParam === '') {
  379. $this->error('参数 ids 不能为空');
  380. }
  381. if ($operator === '') {
  382. $this->error('参数 sys_id 不能为空');
  383. }
  384. $ids = array_values(array_filter(array_map('intval', explode(',', $idsParam))));
  385. if (empty($ids)) {
  386. $this->error('ids 格式错误');
  387. }
  388. $records = Db::name($this->tableName)->where('id', 'in', $ids)->select();
  389. if (count($records) !== count($ids)) {
  390. $this->error('部分记录不存在,请刷新后重试');
  391. }
  392. $now = date('Y-m-d H:i:s');
  393. $logs = [];
  394. Db::startTrans();
  395. try {
  396. foreach ($records as $record) {
  397. $logs = array_merge($logs, $this->buildDeleteLogs((int)$record['id'], $record, $operator, $now));
  398. }
  399. $deleteResult = Db::name($this->tableName)->where('id', 'in', $ids)->delete();
  400. if ($deleteResult === false) {
  401. throw new Exception('删除失败');
  402. }
  403. if (!empty($logs)) {
  404. Db::name($this->logTableName)->insertAll($logs);
  405. }
  406. Db::commit();
  407. $this->success('批量删除成功', ['count' => $deleteResult]);
  408. } catch (Exception $e) {
  409. Db::rollback();
  410. $this->error($e->getMessage());
  411. }
  412. }
  413. /**
  414. * 按日查询明细
  415. */
  416. protected function queryDailyList($start, $end)
  417. {
  418. $list = Db::name($this->tableName)
  419. ->alias('a')
  420. ->join($this->equipmentTable . ' b', 'a.sczl_jtbh = b.设备编号', 'LEFT')
  421. ->where('a.sczl_rq', '>=', $start)
  422. ->where('a.sczl_rq', '<=', $end)
  423. ->field($this->buildSelectFields(false))
  424. ->select();
  425. return $this->formatList($list);
  426. }
  427. /**
  428. * 按月汇总查询
  429. */
  430. protected function queryMonthlyList($start, $end, $month)
  431. {
  432. $sumExpr = [];
  433. foreach ($this->sumFields as $field) {
  434. $sumExpr[] = "SUM(a.{$field}) as {$field}";
  435. }
  436. $list = Db::name($this->tableName)
  437. ->alias('a')
  438. ->join($this->equipmentTable . ' b', 'a.sczl_jtbh = b.设备编号', 'LEFT')
  439. ->where('a.sczl_rq', '>=', $start)
  440. ->where('a.sczl_rq', '<=', $end)
  441. ->field(array_merge([
  442. 'MIN(a.id) as id',
  443. 'a.sczl_jtbh',
  444. 'a.sczl_bzdh',
  445. 'MAX(a.theoretical_speed) as theoretical_speed',
  446. 'rtrim(b.使用部门) as 车间',
  447. 'rtrim(b.设备编组) as 机组',
  448. 'rtrim(b.设备名称) as 机台名称',
  449. ], $sumExpr))
  450. ->group('a.sczl_jtbh,a.sczl_bzdh,b.使用部门,b.设备编组,b.设备名称')
  451. ->select();
  452. foreach ($list as &$row) {
  453. $row['sczl_rq'] = $month . '-01 00:00:00';
  454. $row['query_type'] = 'month';
  455. $row['query_month'] = $month;
  456. }
  457. unset($row);
  458. return $this->formatList($list);
  459. }
  460. /**
  461. * 查询字段
  462. */
  463. protected function buildSelectFields($isMonth = false)
  464. {
  465. $fields = [
  466. 'a.id',
  467. 'a.sczl_jtbh',
  468. 'a.sczl_bzdh',
  469. 'a.sczl_rq',
  470. 'a.theoretical_speed',
  471. 'a.scheduled_production',
  472. 'a.scheduled_maintenance',
  473. 'a.scheduled_handover',
  474. 'a.fault',
  475. 'a.changeover',
  476. 'a.proofing',
  477. 'a.abnormal',
  478. 'a.planned_output',
  479. 'a.actual_output',
  480. 'a.defective_product',
  481. 'a.sys_id',
  482. 'a.sys_rq',
  483. 'a.mod_id',
  484. 'a.mod_rq',
  485. 'rtrim(b.使用部门) as 车间',
  486. 'rtrim(b.设备编组) as 机组',
  487. 'rtrim(b.设备名称) as 机台名称',
  488. ];
  489. return $fields;
  490. }
  491. /**
  492. * 格式化返回列表
  493. */
  494. protected function formatList($list)
  495. {
  496. $result = [];
  497. foreach ($list as $row) {
  498. $result[] = [
  499. 'id' => (int)$row['id'],
  500. '车间' => isset($row['车间']) ? rtrim($row['车间']) : '',
  501. '机组' => isset($row['机组']) ? rtrim($row['机组']) : '',
  502. '机台编号' => rtrim($row['sczl_jtbh']),
  503. '设备编号' => rtrim($row['sczl_jtbh']),
  504. '机台名称' => isset($row['机台名称']) ? rtrim($row['机台名称']) : '',
  505. '班组' => $this->normalizeShift($row['sczl_bzdh']),
  506. 'sczl_jtbh' => rtrim($row['sczl_jtbh']),
  507. 'sczl_bzdh' => $this->normalizeShift($row['sczl_bzdh']),
  508. 'sczl_rq' => $row['sczl_rq'],
  509. 'theoretical_speed' => $this->formatNumber($row['theoretical_speed']),
  510. 'scheduled_production' => $this->formatNumber($row['scheduled_production']),
  511. 'scheduled_maintenance'=> $this->formatNumber($row['scheduled_maintenance']),
  512. 'scheduled_handover' => $this->formatNumber($row['scheduled_handover']),
  513. 'fault' => $this->formatNumber($row['fault']),
  514. 'changeover' => $this->formatNumber($row['changeover']),
  515. 'proofing' => $this->formatNumber($row['proofing']),
  516. 'abnormal' => $this->formatNumber($row['abnormal']),
  517. 'planned_output' => $this->formatNumber($row['planned_output']),
  518. 'actual_output' => $this->formatNumber($row['actual_output']),
  519. 'defective_product' => $this->formatNumber($row['defective_product']),
  520. 'sys_id' => isset($row['sys_id']) ? rtrim($row['sys_id']) : '',
  521. 'sys_rq' => isset($row['sys_rq']) ? $row['sys_rq'] : '',
  522. 'mod_id' => isset($row['mod_id']) ? rtrim($row['mod_id']) : '',
  523. 'mod_rq' => isset($row['mod_rq']) ? $row['mod_rq'] : '',
  524. 'query_type' => isset($row['query_type']) ? $row['query_type'] : 'day',
  525. 'query_month' => isset($row['query_month']) ? $row['query_month'] : '',
  526. ];
  527. }
  528. return $this->sortEquipmentList($result);
  529. }
  530. /**
  531. * 按设备编组、机台编号数字排序
  532. */
  533. protected function sortEquipmentList(array $list)
  534. {
  535. usort($list, function ($a, $b) {
  536. return $this->compareEquipmentRow($a, $b);
  537. });
  538. return $list;
  539. }
  540. /**
  541. * 设备行排序比较
  542. */
  543. protected function compareEquipmentRow($a, $b)
  544. {
  545. $groupA = $this->trimText(isset($a['机组']) ? $a['机组'] : '');
  546. $groupB = $this->trimText(isset($b['机组']) ? $b['机组'] : '');
  547. $groupOrderA = $this->extractGroupOrder($groupA);
  548. $groupOrderB = $this->extractGroupOrder($groupB);
  549. if ($groupOrderA !== $groupOrderB) {
  550. return $groupOrderA <=> $groupOrderB;
  551. }
  552. if ($groupA !== $groupB) {
  553. return strcmp($groupA, $groupB);
  554. }
  555. $codeA = $this->getMachineCodeFromRow($a);
  556. $codeB = $this->getMachineCodeFromRow($b);
  557. $numA = $this->extractMachineNumber($codeA);
  558. $numB = $this->extractMachineNumber($codeB);
  559. if ($numA !== $numB) {
  560. return $numA <=> $numB;
  561. }
  562. if ($codeA !== $codeB) {
  563. return strcmp($codeA, $codeB);
  564. }
  565. $shiftA = isset($a['班组']) ? $a['班组'] : (isset($a['sczl_bzdh']) ? $a['sczl_bzdh'] : '');
  566. $shiftB = isset($b['班组']) ? $b['班组'] : (isset($b['sczl_bzdh']) ? $b['sczl_bzdh'] : '');
  567. return strcmp($shiftA, $shiftB);
  568. }
  569. /**
  570. * 提取设备编组前缀序号(如 03、卷凹机组 -> 3)
  571. */
  572. protected function extractGroupOrder($group)
  573. {
  574. $group = $this->trimText($group);
  575. if (preg_match('/^(\d+)/', $group, $matches)) {
  576. return (int)$matches[1];
  577. }
  578. return PHP_INT_MAX;
  579. }
  580. /**
  581. * 提取机台编号中的数字(如 YWY01# -> 1)
  582. */
  583. protected function extractMachineNumber($code)
  584. {
  585. $code = $this->trimText($code);
  586. if (preg_match('/(\d+)/', $code, $matches)) {
  587. return (int)$matches[1];
  588. }
  589. return PHP_INT_MAX;
  590. }
  591. /**
  592. * 从返回行中获取机台编号
  593. */
  594. protected function getMachineCodeFromRow($row)
  595. {
  596. if (!empty($row['机台编号'])) {
  597. return $this->trimText($row['机台编号']);
  598. }
  599. if (!empty($row['sczl_jtbh'])) {
  600. return $this->trimText($row['sczl_jtbh']);
  601. }
  602. if (!empty($row['设备编号'])) {
  603. return $this->trimText($row['设备编号']);
  604. }
  605. return '';
  606. }
  607. /**
  608. * 去除字符串首尾空白(含全角空格)
  609. */
  610. protected function trimText($value)
  611. {
  612. return preg_replace('/^[\s\x{3000}]+|[\s\x{3000}]+$/u', '', (string)$value);
  613. }
  614. /**
  615. * 班组名称规范化:仅支持 A班、B班
  616. */
  617. protected function normalizeShift($shift)
  618. {
  619. $shift = $this->trimText($shift);
  620. $shift = preg_replace('/\s+/u', '', $shift);
  621. if ($shift === '') {
  622. return '';
  623. }
  624. if (preg_match('/^A(?:班)?$/iu', $shift)) {
  625. return 'A班';
  626. }
  627. if (preg_match('/^B(?:班)?$/iu', $shift)) {
  628. return 'B班';
  629. }
  630. return '';
  631. }
  632. /**
  633. * 标准化单行数据
  634. */
  635. protected function normalizeRow(array $row, $requireAll = true)
  636. {
  637. $data = [];
  638. foreach ($this->dataFields as $field) {
  639. if (!array_key_exists($field, $row)) {
  640. if (!$requireAll) {
  641. continue;
  642. }
  643. $data[$field] = in_array($field, $this->sumFields, true) || $field === 'theoretical_speed' ? 0 : '';
  644. continue;
  645. }
  646. if ($field === 'sczl_rq') {
  647. $data[$field] = date('Y-m-d 00:00:00', strtotime($row[$field]));
  648. continue;
  649. }
  650. if (in_array($field, ['sczl_jtbh', 'sczl_bzdh'], true)) {
  651. $value = trim((string)$row[$field]);
  652. $data[$field] = $field === 'sczl_bzdh' ? $this->normalizeShift($value) : $value;
  653. continue;
  654. }
  655. if ($field === 'theoretical_speed') {
  656. $data[$field] = (int)$row[$field];
  657. continue;
  658. }
  659. $data[$field] = $row[$field] === '' || $row[$field] === null ? 0 : $row[$field];
  660. }
  661. return $data;
  662. }
  663. /**
  664. * 校验单行数据
  665. */
  666. protected function validateRow(array $data, $lineNo)
  667. {
  668. if ($data['sczl_jtbh'] === '') {
  669. throw new Exception('第 ' . $lineNo . ' 条设备编号不能为空');
  670. }
  671. if ($data['sczl_bzdh'] === '') {
  672. throw new Exception('第 ' . $lineNo . ' 条班组代号不能为空,且只能为 A班 或 B班');
  673. }
  674. if (!in_array($data['sczl_bzdh'], $this->defaultShifts, true)) {
  675. throw new Exception('第 ' . $lineNo . ' 条班组代号只能为 A班 或 B班');
  676. }
  677. if (empty($data['sczl_rq'])) {
  678. throw new Exception('第 ' . $lineNo . ' 条日期不能为空');
  679. }
  680. $equipment = Db::name($this->equipmentTable)
  681. ->where('设备编号', $data['sczl_jtbh'])
  682. ->find();
  683. if (empty($equipment)) {
  684. throw new Exception('第 ' . $lineNo . ' 条设备编号不存在:' . $data['sczl_jtbh']);
  685. }
  686. }
  687. /**
  688. * 构建新增日志
  689. */
  690. protected function buildAddLogs($recordId, array $data, $operator, $time)
  691. {
  692. $logs = [];
  693. foreach ($this->dataFields as $field) {
  694. if (!array_key_exists($field, $data)) {
  695. continue;
  696. }
  697. $logs[] = [
  698. 'record_id' => $recordId,
  699. 'operation_type' => 'add',
  700. 'field_name' => isset($this->fieldLabels[$field]) ? $this->fieldLabels[$field] : $field,
  701. 'old_value' => null,
  702. 'new_value' => (string)$data[$field],
  703. 'operator_id' => $operator,
  704. 'operation_time' => $time,
  705. ];
  706. }
  707. return $logs;
  708. }
  709. /**
  710. * 构建修改日志
  711. */
  712. protected function buildUpdateLogs($recordId, array $old, array $changed, $operator, $time)
  713. {
  714. $logs = [];
  715. foreach ($changed as $field => $newValue) {
  716. if ($field === 'mod_id' || $field === 'mod_rq') {
  717. continue;
  718. }
  719. $logs[] = [
  720. 'record_id' => $recordId,
  721. 'operation_type' => 'update',
  722. 'field_name' => isset($this->fieldLabels[$field]) ? $this->fieldLabels[$field] : $field,
  723. 'old_value' => isset($old[$field]) ? (string)$old[$field] : '',
  724. 'new_value' => (string)$newValue,
  725. 'operator_id' => $operator,
  726. 'operation_time' => $time,
  727. ];
  728. }
  729. return $logs;
  730. }
  731. /**
  732. * 构建删除日志
  733. */
  734. protected function buildDeleteLogs($recordId, array $record, $operator, $time)
  735. {
  736. $logs = [];
  737. foreach ($this->dataFields as $field) {
  738. if (!array_key_exists($field, $record)) {
  739. continue;
  740. }
  741. $logs[] = [
  742. 'record_id' => $recordId,
  743. 'operation_type' => 'delete',
  744. 'field_name' => isset($this->fieldLabels[$field]) ? $this->fieldLabels[$field] : $field,
  745. 'old_value' => (string)$record[$field],
  746. 'new_value' => null,
  747. 'operator_id' => $operator,
  748. 'operation_time' => $time,
  749. ];
  750. }
  751. return $logs;
  752. }
  753. /**
  754. * 数值格式化
  755. */
  756. protected function formatNumber($value)
  757. {
  758. if ($value === null || $value === '') {
  759. return 0;
  760. }
  761. return is_numeric($value) ? (float)$value : $value;
  762. }
  763. }