ProductionInformationStatistics.php 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332
  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 (!$this->hasStatsInPeriod($query)) {
  130. if ($query['type'] === 'day') {
  131. $this->error(date('Y-m-d', strtotime($query['start'])) . '没有数据,请添加后查询');
  132. }
  133. $this->error($query['month'] . '没有数据,请添加后查询');
  134. }
  135. $list = $this->buildDisplayList($query);
  136. $this->success('成功', $list);
  137. }
  138. /**
  139. * 判断指定时间段内是否存在统计数据
  140. */
  141. protected function hasStatsInPeriod(array $query)
  142. {
  143. $count = Db::name($this->tableName)
  144. ->where('sczl_rq', '>=', $query['start'])
  145. ->where('sczl_rq', '<=', $query['end'])
  146. ->count();
  147. return $count > 0;
  148. }
  149. /**
  150. * 解析查询时间:Y-m-d 为日,Y-m 为月
  151. */
  152. protected function parseQueryTime($time)
  153. {
  154. if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $time)) {
  155. $timestamp = strtotime($time);
  156. if ($timestamp === false || date('Y-m-d', $timestamp) !== $time) {
  157. $this->error('日期格式错误,请传 Y-m-d,如 2026-07-23');
  158. }
  159. return [
  160. 'type' => 'day',
  161. 'start' => date('Y-m-d 00:00:00', $timestamp),
  162. 'end' => date('Y-m-d 23:59:59', $timestamp),
  163. ];
  164. }
  165. if (preg_match('/^\d{4}-\d{2}$/', $time)) {
  166. $timestamp = strtotime($time . '-01');
  167. if ($timestamp === false || date('Y-m', $timestamp) !== $time) {
  168. $this->error('月份格式错误,请传 Y-m,如 2026-07');
  169. }
  170. return [
  171. 'type' => 'month',
  172. 'month' => $time,
  173. 'start' => date('Y-m-01 00:00:00', $timestamp),
  174. 'end' => date('Y-m-t 23:59:59', $timestamp),
  175. ];
  176. }
  177. $this->error('时间格式错误,请传 Y-m-d(具体日期)或 Y-m(月份)');
  178. }
  179. /**
  180. * 供 Excel 导出使用的表格数据
  181. * @param string $time Y-m-d 或 Y-m
  182. * @return array
  183. */
  184. public function getExportData($time)
  185. {
  186. $time = trim((string)$time);
  187. if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $time)) {
  188. $timestamp = strtotime($time);
  189. if ($timestamp === false) {
  190. return [];
  191. }
  192. $query = [
  193. 'type' => 'day',
  194. 'start' => date('Y-m-d 00:00:00', $timestamp),
  195. 'end' => date('Y-m-d 23:59:59', $timestamp),
  196. ];
  197. } elseif (preg_match('/^\d{4}-\d{2}$/', $time)) {
  198. $timestamp = strtotime($time . '-01');
  199. if ($timestamp === false) {
  200. return [];
  201. }
  202. $query = [
  203. 'type' => 'month',
  204. 'month' => $time,
  205. 'start' => date('Y-m-01 00:00:00', $timestamp),
  206. 'end' => date('Y-m-t 23:59:59', $timestamp),
  207. ];
  208. } else {
  209. return [];
  210. }
  211. return $this->buildDisplayList($query);
  212. }
  213. /**
  214. * 3. 获取车间、机组、机台编号、机台名称、班组基础数据
  215. * @ApiMethod (GET)
  216. */
  217. public function getBaseData()
  218. {
  219. if (!$this->request->isGet()) {
  220. $this->error('请求错误');
  221. }
  222. $equipmentMap = $this->fetchEquipmentMap(true);
  223. if (empty($equipmentMap)) {
  224. $this->success('未获取到机台数据', []);
  225. }
  226. $list = [];
  227. foreach ($equipmentMap as $code => $equipment) {
  228. $list[] = [
  229. '车间' => $equipment['车间'],
  230. '机组' => $equipment['机组'],
  231. '编组' => $equipment['编组'],
  232. '机台编号' => $code,
  233. '机台名称' => $equipment['机台名称'],
  234. '设备编号' => $code,
  235. '理论速度' => $equipment['理论速度'],
  236. '班组列表' => $this->defaultShifts,
  237. ];
  238. }
  239. $this->success('成功', $this->sortEquipmentList($list));
  240. }
  241. /**
  242. * 4. 批量新增(每个机台每个班组一条)
  243. * @ApiMethod (POST)
  244. * @param array list 数据列表
  245. * @param string sys_id 操作人
  246. */
  247. public function batchAdd()
  248. {
  249. if (!$this->request->isPost()) {
  250. $this->error('请求错误');
  251. }
  252. $params = $this->request->post();
  253. $list = isset($params['list']) ? $params['list'] : [];
  254. $operator = isset($params['sys_id']) ? trim($params['sys_id']) : '';
  255. if (empty($list) || !is_array($list)) {
  256. $this->error('参数 list 不能为空');
  257. }
  258. if ($operator === '') {
  259. $this->error('参数 sys_id 不能为空');
  260. }
  261. $now = date('Y-m-d H:i:s');
  262. $insertRows = [];
  263. $logs = [];
  264. Db::startTrans();
  265. try {
  266. foreach ($list as $index => $row) {
  267. if (!is_array($row)) {
  268. throw new Exception('第 ' . ($index + 1) . ' 条数据格式错误');
  269. }
  270. $data = $this->normalizeRow($row);
  271. $this->validateRow($data, $index + 1);
  272. $exists = Db::name($this->tableName)
  273. ->where('sczl_jtbh', $data['sczl_jtbh'])
  274. ->where('sczl_bzdh', $data['sczl_bzdh'])
  275. ->where('sczl_rq', $data['sczl_rq'])
  276. ->find();
  277. if ($exists) {
  278. throw new Exception(sprintf(
  279. '第 %d 条记录已存在:设备[%s] 班组[%s] 日期[%s]',
  280. $index + 1,
  281. $data['sczl_jtbh'],
  282. $data['sczl_bzdh'],
  283. date('Y-m-d', strtotime($data['sczl_rq']))
  284. ));
  285. }
  286. $data['sys_id'] = $operator;
  287. $data['sys_rq'] = $now;
  288. $insertRows[] = $data;
  289. }
  290. foreach ($insertRows as $data) {
  291. $recordId = Db::name($this->tableName)->insertGetId($data);
  292. if (!$recordId) {
  293. throw new Exception('新增失败');
  294. }
  295. $logs = array_merge($logs, $this->buildAddLogs($recordId, $data, $operator, $now));
  296. }
  297. if (!empty($logs)) {
  298. Db::name($this->logTableName)->insertAll($logs);
  299. }
  300. Db::commit();
  301. $this->success('批量新增成功', ['count' => count($insertRows)]);
  302. } catch (Exception $e) {
  303. Db::rollback();
  304. $this->error($e->getMessage());
  305. }
  306. }
  307. /**
  308. * 5. 批量修改
  309. * @ApiMethod (POST)
  310. * @param array list 含 id 的数据列表
  311. * @param string sys_id 操作人
  312. */
  313. public function batchUpdate()
  314. {
  315. if (!$this->request->isPost()) {
  316. $this->error('请求错误');
  317. }
  318. $params = $this->request->post();
  319. $list = isset($params['list']) ? $params['list'] : [];
  320. $operator = isset($params['sys_id']) ? trim($params['sys_id']) : '';
  321. if (empty($list) || !is_array($list)) {
  322. $this->error('参数 list 不能为空');
  323. }
  324. if ($operator === '') {
  325. $this->error('参数 sys_id 不能为空');
  326. }
  327. $now = date('Y-m-d H:i:s');
  328. $logs = [];
  329. $updateCount = 0;
  330. Db::startTrans();
  331. try {
  332. foreach ($list as $index => $row) {
  333. if (!is_array($row) || empty($row['id'])) {
  334. throw new Exception('第 ' . ($index + 1) . ' 条数据缺少 id');
  335. }
  336. $id = (int)$row['id'];
  337. $old = Db::name($this->tableName)->where('id', $id)->find();
  338. if (empty($old)) {
  339. throw new Exception('第 ' . ($index + 1) . ' 条记录不存在');
  340. }
  341. $data = $this->normalizeRow($row, false);
  342. unset($data['id']);
  343. if (array_key_exists('sczl_bzdh', $row)) {
  344. if ($data['sczl_bzdh'] === '' || !in_array($data['sczl_bzdh'], $this->defaultShifts, true)) {
  345. throw new Exception('第 ' . ($index + 1) . ' 条班组代号只能为 A班 或 B班');
  346. }
  347. }
  348. $checkJtbh = isset($data['sczl_jtbh']) ? $data['sczl_jtbh'] : rtrim($old['sczl_jtbh']);
  349. $checkBzdh = isset($data['sczl_bzdh']) ? $data['sczl_bzdh'] : rtrim($old['sczl_bzdh']);
  350. $checkRq = isset($data['sczl_rq']) ? $data['sczl_rq'] : $old['sczl_rq'];
  351. $duplicate = Db::name($this->tableName)
  352. ->where('sczl_jtbh', $checkJtbh)
  353. ->where('sczl_bzdh', $checkBzdh)
  354. ->where('sczl_rq', $checkRq)
  355. ->where('id', '<>', $id)
  356. ->find();
  357. if ($duplicate) {
  358. throw new Exception(sprintf(
  359. '第 %d 条修改后与已有记录冲突:设备[%s] 班组[%s] 日期[%s]',
  360. $index + 1,
  361. $checkJtbh,
  362. $checkBzdh,
  363. date('Y-m-d', strtotime($checkRq))
  364. ));
  365. }
  366. $data['mod_id'] = $operator;
  367. $data['mod_rq'] = $now;
  368. $changed = [];
  369. foreach ($data as $field => $value) {
  370. if (!array_key_exists($field, $old)) {
  371. continue;
  372. }
  373. $oldValue = $old[$field];
  374. if ((string)$oldValue !== (string)$value) {
  375. $changed[$field] = $value;
  376. }
  377. }
  378. if (empty($changed)) {
  379. continue;
  380. }
  381. $result = Db::name($this->tableName)->where('id', $id)->update($changed + [
  382. 'mod_id' => $operator,
  383. 'mod_rq' => $now,
  384. ]);
  385. if ($result === false) {
  386. throw new Exception('第 ' . ($index + 1) . ' 条修改失败');
  387. }
  388. $logs = array_merge($logs, $this->buildUpdateLogs($id, $old, $changed, $operator, $now));
  389. $updateCount++;
  390. }
  391. if (!empty($logs)) {
  392. Db::name($this->logTableName)->insertAll($logs);
  393. }
  394. Db::commit();
  395. $this->success('批量修改成功', ['count' => $updateCount]);
  396. } catch (Exception $e) {
  397. Db::rollback();
  398. $this->error($e->getMessage());
  399. }
  400. }
  401. /**
  402. * 6. 批量删除
  403. * @ApiMethod (POST)
  404. * @param string ids 逗号分隔 id
  405. * @param string sys_id 操作人
  406. */
  407. public function batchDelete()
  408. {
  409. if (!$this->request->isPost()) {
  410. $this->error('请求错误');
  411. }
  412. $params = $this->request->post();
  413. $idsParam = isset($params['ids']) ? trim($params['ids']) : '';
  414. $operator = isset($params['sys_id']) ? trim($params['sys_id']) : '';
  415. if ($idsParam === '') {
  416. $this->error('参数 ids 不能为空');
  417. }
  418. if ($operator === '') {
  419. $this->error('参数 sys_id 不能为空');
  420. }
  421. $ids = array_values(array_filter(array_map('intval', explode(',', $idsParam))));
  422. if (empty($ids)) {
  423. $this->error('ids 格式错误');
  424. }
  425. $records = Db::name($this->tableName)->where('id', 'in', $ids)->select();
  426. if (count($records) !== count($ids)) {
  427. $this->error('部分记录不存在,请刷新后重试');
  428. }
  429. $now = date('Y-m-d H:i:s');
  430. $logs = [];
  431. Db::startTrans();
  432. try {
  433. foreach ($records as $record) {
  434. $logs = array_merge($logs, $this->buildDeleteLogs((int)$record['id'], $record, $operator, $now));
  435. }
  436. $deleteResult = Db::name($this->tableName)->where('id', 'in', $ids)->delete();
  437. if ($deleteResult === false) {
  438. throw new Exception('删除失败');
  439. }
  440. if (!empty($logs)) {
  441. Db::name($this->logTableName)->insertAll($logs);
  442. }
  443. Db::commit();
  444. $this->success('批量删除成功', ['count' => $deleteResult]);
  445. } catch (Exception $e) {
  446. Db::rollback();
  447. $this->error($e->getMessage());
  448. }
  449. }
  450. /**
  451. * 构建带计算字段和汇总行的表格数据
  452. */
  453. protected function buildDisplayList(array $query)
  454. {
  455. if ($query['type'] === 'day') {
  456. $statsRows = $this->fetchStatsRows($query['start'], $query['end']);
  457. $dateMeta = [
  458. 'query_type' => 'day',
  459. 'sczl_rq' => date('Y-m-d 00:00:00', strtotime($query['start'])),
  460. 'query_month' => '',
  461. ];
  462. } else {
  463. $statsRows = $this->fetchMonthlyStatsRows($query['start'], $query['end']);
  464. $dateMeta = [
  465. 'query_type' => 'month',
  466. 'sczl_rq' => $query['month'] . '-01 00:00:00',
  467. 'query_month' => $query['month'],
  468. ];
  469. }
  470. $equipmentMap = $this->fetchEquipmentMap(true);
  471. $statsRows = $this->enrichStatsWithEquipment($statsRows, $equipmentMap);
  472. $statsMap = [];
  473. foreach ($statsRows as $row) {
  474. $key = $this->trimText($row['sczl_jtbh']) . '|' . $this->normalizeShift($row['sczl_bzdh']);
  475. $statsMap[$key] = $row;
  476. }
  477. $detailRows = [];
  478. $processedKeys = [];
  479. foreach ($equipmentMap as $code => $equipment) {
  480. foreach ($this->defaultShifts as $shift) {
  481. $key = $code . '|' . $shift;
  482. $processedKeys[$key] = true;
  483. $stat = isset($statsMap[$key]) ? $statsMap[$key] : [];
  484. $detailRows[] = $this->buildDetailRow($equipment, $shift, $stat, $dateMeta);
  485. }
  486. }
  487. // 补充统计表中有数据但未纳入骨架的行(按设备基本资料补全车间编组)
  488. foreach ($statsMap as $key => $stat) {
  489. if (isset($processedKeys[$key])) {
  490. continue;
  491. }
  492. $code = $this->trimText($stat['sczl_jtbh']);
  493. $shift = $this->normalizeShift($stat['sczl_bzdh']);
  494. if ($code === '' || $shift === '' || !isset($equipmentMap[$code])) {
  495. continue;
  496. }
  497. $detailRows[] = $this->buildDetailRow($equipmentMap[$code], $shift, $stat, $dateMeta);
  498. }
  499. return $this->assembleListWithSummaries($detailRows);
  500. }
  501. /**
  502. * 获取设备基本资料映射(key=机台编号)
  503. */
  504. protected function fetchEquipmentMap($displayOnly = true)
  505. {
  506. $query = Db::name($this->equipmentTable)
  507. ->where('使用部门', '<>', '研发中心');
  508. if ($displayOnly) {
  509. $query->where('设备编组', '<>', '')->where('sys_sbID', '<>', '');
  510. }
  511. $equipments = $query
  512. ->field([
  513. 'rtrim(使用部门) as 车间',
  514. 'rtrim(设备编组) as 机组',
  515. 'rtrim(设备编号) as 机台编号',
  516. 'rtrim(设备名称) as 机台名称',
  517. '平均车速',
  518. ])
  519. ->order('设备编组,设备编号')
  520. ->select();
  521. $map = [];
  522. foreach ($equipments as $item) {
  523. $code = $this->trimText($item['机台编号']);
  524. if ($code === '') {
  525. continue;
  526. }
  527. $map[$code] = $this->formatEquipmentItem($item, $code);
  528. }
  529. return $map;
  530. }
  531. /**
  532. * 格式化设备基本资料项
  533. */
  534. protected function formatEquipmentItem(array $item, $code = null)
  535. {
  536. $code = $code ?: $this->trimText($item['机台编号']);
  537. $group = $this->trimText($item['机组']);
  538. return [
  539. '车间' => $this->trimText($item['车间']),
  540. '机组' => $group,
  541. '编组' => $group,
  542. '机台编号' => $code,
  543. '机台名称' => $this->trimText($item['机台名称']),
  544. '理论速度' => (int)$this->formatNumber($item['平均车速']),
  545. ];
  546. }
  547. /**
  548. * 按机台编号用设备基本资料补全统计数据
  549. */
  550. protected function enrichStatsWithEquipment(array $statsRows, array $equipmentMap)
  551. {
  552. foreach ($statsRows as &$row) {
  553. $code = $this->trimText($row['sczl_jtbh']);
  554. if ($code === '' || !isset($equipmentMap[$code])) {
  555. continue;
  556. }
  557. $equipment = $equipmentMap[$code];
  558. $row['车间'] = $equipment['车间'];
  559. $row['机组'] = $equipment['机组'];
  560. $row['编组'] = $equipment['编组'];
  561. $row['机台名称'] = $equipment['机台名称'];
  562. }
  563. unset($row);
  564. return $statsRows;
  565. }
  566. /**
  567. * 查询原始统计数据(按日)
  568. */
  569. protected function fetchStatsRows($start, $end)
  570. {
  571. return Db::name($this->tableName)
  572. ->alias('a')
  573. ->where('a.sczl_rq', '>=', $start)
  574. ->where('a.sczl_rq', '<=', $end)
  575. ->field([
  576. 'a.id',
  577. 'a.sczl_jtbh',
  578. 'a.sczl_bzdh',
  579. 'a.sczl_rq',
  580. 'a.theoretical_speed',
  581. 'a.scheduled_production',
  582. 'a.scheduled_maintenance',
  583. 'a.scheduled_handover',
  584. 'a.fault',
  585. 'a.changeover',
  586. 'a.proofing',
  587. 'a.abnormal',
  588. 'a.planned_output',
  589. 'a.actual_output',
  590. 'a.defective_product',
  591. 'a.sys_id',
  592. 'a.sys_rq',
  593. 'a.mod_id',
  594. 'a.mod_rq',
  595. ])
  596. ->select();
  597. }
  598. /**
  599. * 查询原始统计数据(按月汇总)
  600. */
  601. protected function fetchMonthlyStatsRows($start, $end)
  602. {
  603. $sumExpr = [];
  604. foreach ($this->sumFields as $field) {
  605. $sumExpr[] = "SUM(a.{$field}) as {$field}";
  606. }
  607. return Db::name($this->tableName)
  608. ->alias('a')
  609. ->where('a.sczl_rq', '>=', $start)
  610. ->where('a.sczl_rq', '<=', $end)
  611. ->field(array_merge([
  612. 'MIN(a.id) as id',
  613. 'a.sczl_jtbh',
  614. 'a.sczl_bzdh',
  615. 'MAX(a.theoretical_speed) as theoretical_speed',
  616. ], $sumExpr))
  617. ->group('a.sczl_jtbh,a.sczl_bzdh')
  618. ->select();
  619. }
  620. /**
  621. * 构建明细行
  622. */
  623. protected function buildDetailRow(array $equipment, $shift, array $stat, array $dateMeta)
  624. {
  625. $row = [
  626. 'row_type' => 'detail',
  627. 'id' => isset($stat['id']) ? (int)$stat['id'] : 0,
  628. '车间' => $equipment['车间'],
  629. '机组' => $equipment['机组'],
  630. '编组' => $equipment['编组'],
  631. '机台编号' => $equipment['机台编号'],
  632. '设备编号' => $equipment['机台编号'],
  633. '机台名称' => $equipment['机台名称'],
  634. '班组' => $shift,
  635. 'sczl_jtbh' => $equipment['机台编号'],
  636. 'sczl_bzdh' => $shift,
  637. 'sczl_rq' => $dateMeta['sczl_rq'],
  638. 'query_type' => $dateMeta['query_type'],
  639. 'query_month' => $dateMeta['query_month'],
  640. 'theoretical_speed' => !empty($stat['id'])
  641. ? (int)$this->formatNumber($stat['theoretical_speed'])
  642. : $equipment['理论速度'],
  643. 'scheduled_production' => $this->formatNumber(isset($stat['scheduled_production']) ? $stat['scheduled_production'] : 0),
  644. 'scheduled_maintenance'=> $this->formatNumber(isset($stat['scheduled_maintenance']) ? $stat['scheduled_maintenance'] : 0),
  645. 'scheduled_handover' => $this->formatNumber(isset($stat['scheduled_handover']) ? $stat['scheduled_handover'] : 0),
  646. 'fault' => $this->formatNumber(isset($stat['fault']) ? $stat['fault'] : 0),
  647. 'changeover' => $this->formatNumber(isset($stat['changeover']) ? $stat['changeover'] : 0),
  648. 'proofing' => $this->formatNumber(isset($stat['proofing']) ? $stat['proofing'] : 0),
  649. 'abnormal' => $this->formatNumber(isset($stat['abnormal']) ? $stat['abnormal'] : 0),
  650. 'planned_output' => $this->formatNumber(isset($stat['planned_output']) ? $stat['planned_output'] : 0),
  651. 'actual_output' => $this->formatNumber(isset($stat['actual_output']) ? $stat['actual_output'] : 0),
  652. 'defective_product' => $this->formatNumber(isset($stat['defective_product']) ? $stat['defective_product'] : 0),
  653. 'sys_id' => isset($stat['sys_id']) ? $this->trimText($stat['sys_id']) : '',
  654. 'sys_rq' => isset($stat['sys_rq']) ? $stat['sys_rq'] : '',
  655. 'mod_id' => isset($stat['mod_id']) ? $this->trimText($stat['mod_id']) : '',
  656. 'mod_rq' => isset($stat['mod_rq']) ? $stat['mod_rq'] : '',
  657. ];
  658. return $this->appendCalculatedFields($row);
  659. }
  660. /**
  661. * 组装明细 + 汇总行
  662. */
  663. protected function assembleListWithSummaries(array $detailRows)
  664. {
  665. $result = [];
  666. $workshopGroups = [];
  667. foreach ($detailRows as $row) {
  668. $workshop = $row['车间'];
  669. $group = $row['机组'];
  670. if (!isset($workshopGroups[$workshop])) {
  671. $workshopGroups[$workshop] = [];
  672. }
  673. if (!isset($workshopGroups[$workshop][$group])) {
  674. $workshopGroups[$workshop][$group] = [];
  675. }
  676. $workshopGroups[$workshop][$group][] = $row;
  677. }
  678. uksort($workshopGroups, function ($a, $b) {
  679. return strcmp($a, $b);
  680. });
  681. foreach ($workshopGroups as $workshop => $groups) {
  682. uksort($groups, function ($a, $b) {
  683. $orderA = $this->extractGroupOrder($a);
  684. $orderB = $this->extractGroupOrder($b);
  685. if ($orderA !== $orderB) {
  686. return $orderA <=> $orderB;
  687. }
  688. return strcmp($a, $b);
  689. });
  690. $workshopRows = [];
  691. $workshopBlock = [];
  692. foreach ($groups as $group => $groupRows) {
  693. $groupRows = $this->sortEquipmentList($groupRows);
  694. $workshopRows = array_merge($workshopRows, $groupRows);
  695. $workshopBlock[] = $this->buildSummaryRow(
  696. 'group',
  697. $workshop,
  698. $group,
  699. $this->buildGroupShortName($group) . '汇总',
  700. '/',
  701. $groupRows
  702. );
  703. foreach ($this->defaultShifts as $shift) {
  704. $shiftRows = array_values(array_filter($groupRows, function ($item) use ($shift) {
  705. return $item['班组'] === $shift;
  706. }));
  707. if (!empty($shiftRows)) {
  708. $workshopBlock[] = $this->buildSummaryRow(
  709. 'group_shift',
  710. $workshop,
  711. $group,
  712. $this->buildGroupShortName($group) . $shift,
  713. $shift,
  714. $shiftRows
  715. );
  716. }
  717. }
  718. $workshopBlock = array_merge($workshopBlock, $groupRows);
  719. }
  720. if (!empty($workshopRows)) {
  721. array_unshift(
  722. $workshopBlock,
  723. $this->buildSummaryRow(
  724. 'workshop',
  725. $workshop,
  726. '全部机组',
  727. $workshop . '全部工序合计',
  728. '/',
  729. $workshopRows
  730. )
  731. );
  732. }
  733. $result = array_merge($result, $workshopBlock);
  734. }
  735. return $result;
  736. }
  737. /**
  738. * 提取机组简称(03、卷凹机组 -> 卷凹)
  739. */
  740. protected function buildGroupShortName($group)
  741. {
  742. $group = $this->trimText($group);
  743. if (preg_match('/、(.+?)机组/u', $group, $matches)) {
  744. return $matches[1];
  745. }
  746. if (preg_match('/、(.+)/u', $group, $matches)) {
  747. return $matches[1];
  748. }
  749. return $group;
  750. }
  751. /**
  752. * 构建汇总行
  753. */
  754. protected function buildSummaryRow($rowType, $workshop, $group, $machineName, $shift, array $sourceRows)
  755. {
  756. $aggregated = $this->aggregateBaseFields($sourceRows);
  757. $row = [
  758. 'row_type' => $rowType,
  759. 'id' => 0,
  760. '车间' => $workshop,
  761. '机组' => $group,
  762. '编组' => $group,
  763. '机台编号' => '',
  764. '设备编号' => '',
  765. '机台名称' => $machineName,
  766. '班组' => $shift,
  767. 'sczl_jtbh' => '',
  768. 'sczl_bzdh' => $shift === '/' ? '' : $shift,
  769. 'sczl_rq' => $sourceRows[0]['sczl_rq'],
  770. 'query_type' => $sourceRows[0]['query_type'],
  771. 'query_month' => $sourceRows[0]['query_month'],
  772. 'theoretical_speed' => 0,
  773. 'scheduled_production' => $aggregated['scheduled_production'],
  774. 'scheduled_maintenance'=> $aggregated['scheduled_maintenance'],
  775. 'scheduled_handover' => $aggregated['scheduled_handover'],
  776. 'fault' => $aggregated['fault'],
  777. 'changeover' => $aggregated['changeover'],
  778. 'proofing' => $aggregated['proofing'],
  779. 'abnormal' => $aggregated['abnormal'],
  780. 'planned_output' => $aggregated['planned_output'],
  781. 'actual_output' => $aggregated['actual_output'],
  782. 'defective_product' => $aggregated['defective_product'],
  783. 'sys_id' => '',
  784. 'sys_rq' => '',
  785. 'mod_id' => '',
  786. 'mod_rq' => '',
  787. ];
  788. return $this->appendCalculatedFields($row, $aggregated['standard_output']);
  789. }
  790. /**
  791. * 汇总基础字段
  792. */
  793. protected function aggregateBaseFields(array $rows)
  794. {
  795. $totals = [
  796. 'scheduled_production' => 0,
  797. 'scheduled_maintenance'=> 0,
  798. 'scheduled_handover' => 0,
  799. 'fault' => 0,
  800. 'changeover' => 0,
  801. 'proofing' => 0,
  802. 'abnormal' => 0,
  803. 'planned_output' => 0,
  804. 'actual_output' => 0,
  805. 'defective_product' => 0,
  806. 'standard_output' => 0,
  807. ];
  808. foreach ($rows as $row) {
  809. foreach ($this->sumFields as $field) {
  810. $totals[$field] += $this->formatNumber($row[$field]);
  811. }
  812. $totals['standard_output'] += $this->formatNumber($row['标准产量']);
  813. }
  814. return $totals;
  815. }
  816. /**
  817. * 计算并追加衍生指标
  818. */
  819. protected function appendCalculatedFields(array $row, $standardOutputOverride = null)
  820. {
  821. $scheduledProduction = $this->formatNumber($row['scheduled_production']);
  822. $scheduledMaintenance = $this->formatNumber($row['scheduled_maintenance']);
  823. $scheduledHandover = $this->formatNumber($row['scheduled_handover']);
  824. $fault = $this->formatNumber($row['fault']);
  825. $changeover = $this->formatNumber($row['changeover']);
  826. $proofing = $this->formatNumber($row['proofing']);
  827. $abnormal = $this->formatNumber($row['abnormal']);
  828. $plannedOutput = $this->formatNumber($row['planned_output']);
  829. $actualOutput = $this->formatNumber($row['actual_output']);
  830. $defectiveProduct = $this->formatNumber($row['defective_product']);
  831. $theoreticalSpeed = $this->formatNumber($row['theoretical_speed']);
  832. $loadTime = $scheduledProduction - ($scheduledMaintenance + $scheduledHandover);
  833. if ($loadTime < 0) {
  834. $loadTime = 0;
  835. }
  836. $actualRunTime = $loadTime - ($fault + $changeover + $proofing + $abnormal);
  837. if ($actualRunTime < 0) {
  838. $actualRunTime = 0;
  839. }
  840. $standardOutput = $standardOutputOverride !== null
  841. ? $this->formatNumber($standardOutputOverride)
  842. : ($theoreticalSpeed * $actualRunTime);
  843. $planAchievementRate = $plannedOutput > 0
  844. ? (($actualOutput - $defectiveProduct) / $plannedOutput)
  845. : 0;
  846. $timeUtilization = $loadTime > 0 ? ($actualRunTime / $loadTime) : 0;
  847. $speedUtilization = $standardOutput > 0 ? ($actualOutput / $standardOutput) : 0;
  848. $qualityRate = $actualOutput > 0
  849. ? (($actualOutput - $defectiveProduct) / $actualOutput)
  850. : 0;
  851. $oee = $timeUtilization * $speedUtilization * $qualityRate;
  852. $row['负荷时间'] = round($loadTime, 2);
  853. $row['实际运行时间'] = round($actualRunTime, 2);
  854. $row['生产计划达成率'] = $this->formatRate($planAchievementRate);
  855. $row['标准产量'] = round($standardOutput, 2);
  856. $row['时间利用率'] = $this->formatRate($timeUtilization);
  857. $row['速度利用率'] = $this->formatRate($speedUtilization);
  858. $row['质量合格率'] = $this->formatRate($qualityRate);
  859. $row['设备综合效率'] = $this->formatRate($oee);
  860. $row['load_time'] = $row['负荷时间'];
  861. $row['actual_run_time'] = $row['实际运行时间'];
  862. $row['plan_achievement_rate'] = $row['生产计划达成率'];
  863. $row['standard_output'] = $row['标准产量'];
  864. $row['time_utilization'] = $row['时间利用率'];
  865. $row['speed_utilization'] = $row['速度利用率'];
  866. $row['quality_rate'] = $row['质量合格率'];
  867. $row['oee'] = $row['设备综合效率'];
  868. $row['理论速度'] = (int)$theoreticalSpeed;
  869. $row['计划生产时间'] = round($scheduledProduction, 2);
  870. $row['计划保养时间'] = round($scheduledMaintenance, 2);
  871. $row['计划早会交接班时间'] = round($scheduledHandover, 2);
  872. $row['设备故障时间'] = round($fault, 2);
  873. $row['换型时间'] = round($changeover, 2);
  874. $row['打样时间'] = round($proofing, 2);
  875. $row['异常时间'] = round($abnormal, 2);
  876. $row['计划完成产量'] = round($plannedOutput, 2);
  877. $row['实际产量'] = round($actualOutput, 2);
  878. $row['不良品数量'] = round($defectiveProduct, 2);
  879. return $row;
  880. }
  881. /**
  882. * 格式化为百分比(0-100,保留2位)
  883. */
  884. protected function formatRate($value)
  885. {
  886. return round((float)$value * 100, 2);
  887. }
  888. /**
  889. * 查询字段
  890. */
  891. protected function buildSelectFields($isMonth = false)
  892. {
  893. $fields = [
  894. 'a.id',
  895. 'a.sczl_jtbh',
  896. 'a.sczl_bzdh',
  897. 'a.sczl_rq',
  898. 'a.theoretical_speed',
  899. 'a.scheduled_production',
  900. 'a.scheduled_maintenance',
  901. 'a.scheduled_handover',
  902. 'a.fault',
  903. 'a.changeover',
  904. 'a.proofing',
  905. 'a.abnormal',
  906. 'a.planned_output',
  907. 'a.actual_output',
  908. 'a.defective_product',
  909. 'a.sys_id',
  910. 'a.sys_rq',
  911. 'a.mod_id',
  912. 'a.mod_rq',
  913. 'rtrim(b.使用部门) as 车间',
  914. 'rtrim(b.设备编组) as 机组',
  915. 'rtrim(b.设备名称) as 机台名称',
  916. ];
  917. return $fields;
  918. }
  919. /**
  920. * 格式化返回列表
  921. */
  922. protected function formatList($list)
  923. {
  924. $result = [];
  925. foreach ($list as $row) {
  926. $result[] = [
  927. 'id' => (int)$row['id'],
  928. '车间' => isset($row['车间']) ? rtrim($row['车间']) : '',
  929. '机组' => isset($row['机组']) ? rtrim($row['机组']) : '',
  930. '机台编号' => rtrim($row['sczl_jtbh']),
  931. '设备编号' => rtrim($row['sczl_jtbh']),
  932. '机台名称' => isset($row['机台名称']) ? rtrim($row['机台名称']) : '',
  933. '班组' => $this->normalizeShift($row['sczl_bzdh']),
  934. 'sczl_jtbh' => rtrim($row['sczl_jtbh']),
  935. 'sczl_bzdh' => $this->normalizeShift($row['sczl_bzdh']),
  936. 'sczl_rq' => $row['sczl_rq'],
  937. 'theoretical_speed' => $this->formatNumber($row['theoretical_speed']),
  938. 'scheduled_production' => $this->formatNumber($row['scheduled_production']),
  939. 'scheduled_maintenance'=> $this->formatNumber($row['scheduled_maintenance']),
  940. 'scheduled_handover' => $this->formatNumber($row['scheduled_handover']),
  941. 'fault' => $this->formatNumber($row['fault']),
  942. 'changeover' => $this->formatNumber($row['changeover']),
  943. 'proofing' => $this->formatNumber($row['proofing']),
  944. 'abnormal' => $this->formatNumber($row['abnormal']),
  945. 'planned_output' => $this->formatNumber($row['planned_output']),
  946. 'actual_output' => $this->formatNumber($row['actual_output']),
  947. 'defective_product' => $this->formatNumber($row['defective_product']),
  948. 'sys_id' => isset($row['sys_id']) ? rtrim($row['sys_id']) : '',
  949. 'sys_rq' => isset($row['sys_rq']) ? $row['sys_rq'] : '',
  950. 'mod_id' => isset($row['mod_id']) ? rtrim($row['mod_id']) : '',
  951. 'mod_rq' => isset($row['mod_rq']) ? $row['mod_rq'] : '',
  952. 'query_type' => isset($row['query_type']) ? $row['query_type'] : 'day',
  953. 'query_month' => isset($row['query_month']) ? $row['query_month'] : '',
  954. ];
  955. }
  956. return $this->sortEquipmentList($result);
  957. }
  958. /**
  959. * 按设备编组、机台编号数字排序
  960. */
  961. protected function sortEquipmentList(array $list)
  962. {
  963. usort($list, function ($a, $b) {
  964. return $this->compareEquipmentRow($a, $b);
  965. });
  966. return $list;
  967. }
  968. /**
  969. * 设备行排序比较
  970. */
  971. protected function compareEquipmentRow($a, $b)
  972. {
  973. $groupA = $this->trimText(isset($a['机组']) ? $a['机组'] : '');
  974. $groupB = $this->trimText(isset($b['机组']) ? $b['机组'] : '');
  975. $groupOrderA = $this->extractGroupOrder($groupA);
  976. $groupOrderB = $this->extractGroupOrder($groupB);
  977. if ($groupOrderA !== $groupOrderB) {
  978. return $groupOrderA <=> $groupOrderB;
  979. }
  980. if ($groupA !== $groupB) {
  981. return strcmp($groupA, $groupB);
  982. }
  983. $codeA = $this->getMachineCodeFromRow($a);
  984. $codeB = $this->getMachineCodeFromRow($b);
  985. $numA = $this->extractMachineNumber($codeA);
  986. $numB = $this->extractMachineNumber($codeB);
  987. if ($numA !== $numB) {
  988. return $numA <=> $numB;
  989. }
  990. if ($codeA !== $codeB) {
  991. return strcmp($codeA, $codeB);
  992. }
  993. $shiftA = isset($a['班组']) ? $a['班组'] : (isset($a['sczl_bzdh']) ? $a['sczl_bzdh'] : '');
  994. $shiftB = isset($b['班组']) ? $b['班组'] : (isset($b['sczl_bzdh']) ? $b['sczl_bzdh'] : '');
  995. return strcmp($shiftA, $shiftB);
  996. }
  997. /**
  998. * 提取设备编组前缀序号(如 03、卷凹机组 -> 3)
  999. */
  1000. protected function extractGroupOrder($group)
  1001. {
  1002. $group = $this->trimText($group);
  1003. if (preg_match('/^(\d+)/', $group, $matches)) {
  1004. return (int)$matches[1];
  1005. }
  1006. return PHP_INT_MAX;
  1007. }
  1008. /**
  1009. * 提取机台编号中的数字(如 YWY01# -> 1)
  1010. */
  1011. protected function extractMachineNumber($code)
  1012. {
  1013. $code = $this->trimText($code);
  1014. if (preg_match('/(\d+)/', $code, $matches)) {
  1015. return (int)$matches[1];
  1016. }
  1017. return PHP_INT_MAX;
  1018. }
  1019. /**
  1020. * 从返回行中获取机台编号
  1021. */
  1022. protected function getMachineCodeFromRow($row)
  1023. {
  1024. if (!empty($row['机台编号'])) {
  1025. return $this->trimText($row['机台编号']);
  1026. }
  1027. if (!empty($row['sczl_jtbh'])) {
  1028. return $this->trimText($row['sczl_jtbh']);
  1029. }
  1030. if (!empty($row['设备编号'])) {
  1031. return $this->trimText($row['设备编号']);
  1032. }
  1033. return '';
  1034. }
  1035. /**
  1036. * 去除字符串首尾空白(含全角空格)
  1037. */
  1038. protected function trimText($value)
  1039. {
  1040. return preg_replace('/^[\s\x{3000}]+|[\s\x{3000}]+$/u', '', (string)$value);
  1041. }
  1042. /**
  1043. * 班组名称规范化:仅支持 A班、B班
  1044. */
  1045. protected function normalizeShift($shift)
  1046. {
  1047. $shift = $this->trimText($shift);
  1048. $shift = preg_replace('/\s+/u', '', $shift);
  1049. if ($shift === '') {
  1050. return '';
  1051. }
  1052. if (preg_match('/^A(?:班)?$/iu', $shift)) {
  1053. return 'A班';
  1054. }
  1055. if (preg_match('/^B(?:班)?$/iu', $shift)) {
  1056. return 'B班';
  1057. }
  1058. return '';
  1059. }
  1060. /**
  1061. * 标准化单行数据
  1062. */
  1063. protected function normalizeRow(array $row, $requireAll = true)
  1064. {
  1065. $data = [];
  1066. foreach ($this->dataFields as $field) {
  1067. if (!array_key_exists($field, $row)) {
  1068. if (!$requireAll) {
  1069. continue;
  1070. }
  1071. $data[$field] = in_array($field, $this->sumFields, true) || $field === 'theoretical_speed' ? 0 : '';
  1072. continue;
  1073. }
  1074. if ($field === 'sczl_rq') {
  1075. $data[$field] = date('Y-m-d 00:00:00', strtotime($row[$field]));
  1076. continue;
  1077. }
  1078. if (in_array($field, ['sczl_jtbh', 'sczl_bzdh'], true)) {
  1079. $value = trim((string)$row[$field]);
  1080. $data[$field] = $field === 'sczl_bzdh' ? $this->normalizeShift($value) : $value;
  1081. continue;
  1082. }
  1083. if ($field === 'theoretical_speed') {
  1084. $data[$field] = (int)$row[$field];
  1085. continue;
  1086. }
  1087. $data[$field] = $row[$field] === '' || $row[$field] === null ? 0 : $row[$field];
  1088. }
  1089. return $data;
  1090. }
  1091. /**
  1092. * 校验单行数据
  1093. */
  1094. protected function validateRow(array $data, $lineNo)
  1095. {
  1096. if ($data['sczl_jtbh'] === '') {
  1097. throw new Exception('第 ' . $lineNo . ' 条设备编号不能为空');
  1098. }
  1099. if ($data['sczl_bzdh'] === '') {
  1100. throw new Exception('第 ' . $lineNo . ' 条班组代号不能为空,且只能为 A班 或 B班');
  1101. }
  1102. if (!in_array($data['sczl_bzdh'], $this->defaultShifts, true)) {
  1103. throw new Exception('第 ' . $lineNo . ' 条班组代号只能为 A班 或 B班');
  1104. }
  1105. if (empty($data['sczl_rq'])) {
  1106. throw new Exception('第 ' . $lineNo . ' 条日期不能为空');
  1107. }
  1108. $equipment = Db::name($this->equipmentTable)
  1109. ->where('设备编号', $data['sczl_jtbh'])
  1110. ->find();
  1111. if (empty($equipment)) {
  1112. throw new Exception('第 ' . $lineNo . ' 条设备编号不存在:' . $data['sczl_jtbh']);
  1113. }
  1114. }
  1115. /**
  1116. * 构建新增日志
  1117. */
  1118. protected function buildAddLogs($recordId, array $data, $operator, $time)
  1119. {
  1120. $logs = [];
  1121. foreach ($this->dataFields as $field) {
  1122. if (!array_key_exists($field, $data)) {
  1123. continue;
  1124. }
  1125. $logs[] = [
  1126. 'record_id' => $recordId,
  1127. 'operation_type' => 'add',
  1128. 'field_name' => isset($this->fieldLabels[$field]) ? $this->fieldLabels[$field] : $field,
  1129. 'old_value' => null,
  1130. 'new_value' => (string)$data[$field],
  1131. 'operator_id' => $operator,
  1132. 'operation_time' => $time,
  1133. ];
  1134. }
  1135. return $logs;
  1136. }
  1137. /**
  1138. * 构建修改日志
  1139. */
  1140. protected function buildUpdateLogs($recordId, array $old, array $changed, $operator, $time)
  1141. {
  1142. $logs = [];
  1143. foreach ($changed as $field => $newValue) {
  1144. if ($field === 'mod_id' || $field === 'mod_rq') {
  1145. continue;
  1146. }
  1147. $logs[] = [
  1148. 'record_id' => $recordId,
  1149. 'operation_type' => 'update',
  1150. 'field_name' => isset($this->fieldLabels[$field]) ? $this->fieldLabels[$field] : $field,
  1151. 'old_value' => isset($old[$field]) ? (string)$old[$field] : '',
  1152. 'new_value' => (string)$newValue,
  1153. 'operator_id' => $operator,
  1154. 'operation_time' => $time,
  1155. ];
  1156. }
  1157. return $logs;
  1158. }
  1159. /**
  1160. * 构建删除日志
  1161. */
  1162. protected function buildDeleteLogs($recordId, array $record, $operator, $time)
  1163. {
  1164. $logs = [];
  1165. foreach ($this->dataFields as $field) {
  1166. if (!array_key_exists($field, $record)) {
  1167. continue;
  1168. }
  1169. $logs[] = [
  1170. 'record_id' => $recordId,
  1171. 'operation_type' => 'delete',
  1172. 'field_name' => isset($this->fieldLabels[$field]) ? $this->fieldLabels[$field] : $field,
  1173. 'old_value' => (string)$record[$field],
  1174. 'new_value' => null,
  1175. 'operator_id' => $operator,
  1176. 'operation_time' => $time,
  1177. ];
  1178. }
  1179. return $logs;
  1180. }
  1181. /**
  1182. * 数值格式化
  1183. */
  1184. protected function formatNumber($value)
  1185. {
  1186. if ($value === null || $value === '') {
  1187. return 0;
  1188. }
  1189. return is_numeric($value) ? (float)$value : $value;
  1190. }
  1191. }