'设备编号', 'sczl_bzdh' => '班组代号', 'sczl_rq' => '日期', 'theoretical_speed' => '理论速度', 'scheduled_production' => '计划生产时间', 'scheduled_maintenance'=> '计划保养时间', 'scheduled_handover' => '计划早会交接班时间', 'fault' => '设备故障时间', 'changeover' => '换型时间', 'proofing' => '打样时间', 'abnormal' => '异常时间', 'planned_output' => '计划完成产量', 'actual_output' => '实际产量', 'defective_product' => '不良品数量', ]; /** @var array 按月汇总时求和的字段 */ protected $sumFields = [ 'scheduled_production', 'scheduled_maintenance', 'scheduled_handover', 'fault', 'changeover', 'proofing', 'abnormal', 'planned_output', 'actual_output', 'defective_product', ]; /** * 1. 左侧菜单:年 -> 月 -> 日 * @ApiMethod (GET) */ public function getMenu() { if (!$this->request->isGet()) { $this->error('请求错误'); } $dateList = Db::name($this->tableName) ->where('sczl_rq', 'not null') ->field("DISTINCT DATE_FORMAT(sczl_rq, '%Y-%m-%d') AS rq") ->order('rq DESC') ->select(); $tree = []; foreach ($dateList as $item) { $rq = $item['rq']; if (empty($rq)) { continue; } $year = date('Y', strtotime($rq)); $month = date('Y-m', strtotime($rq)); if (!isset($tree[$year])) { $tree[$year] = [ 'year' => $year, 'children' => [], ]; } if (!isset($tree[$year]['children'][$month])) { $tree[$year]['children'][$month] = [ 'month' => $month, 'children' => [], ]; } if (!in_array($rq, $tree[$year]['children'][$month]['children'], true)) { $tree[$year]['children'][$month]['children'][] = $rq; } } $result = array_values($tree); foreach ($result as &$yearItem) { $yearItem['children'] = array_values($yearItem['children']); } unset($yearItem); $this->success('成功', $result); } /** * 2. 表格数据查询(联表设备基本资料) * @ApiMethod (GET) * @param string date 时间参数:Y-m-d 查日明细,Y-m 查月汇总 */ public function getList() { if (!$this->request->isGet()) { $this->error('请求错误'); } $time = trim((string)$this->request->param('date', '')); if ($time === '') { $this->error('请传入 date 参数,格式 Y-m-d 或 Y-m'); } $query = $this->parseQueryTime($time); if ($query['type'] === 'day') { $list = $this->queryDailyList($query['start'], $query['end']); } else { $list = $this->queryMonthlyList($query['start'], $query['end'], $query['month']); } $this->success('成功', $list); } /** * 解析查询时间:Y-m-d 为日,Y-m 为月 */ protected function parseQueryTime($time) { if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $time)) { $timestamp = strtotime($time); if ($timestamp === false || date('Y-m-d', $timestamp) !== $time) { $this->error('日期格式错误,请传 Y-m-d,如 2026-07-23'); } return [ 'type' => 'day', 'start' => date('Y-m-d 00:00:00', $timestamp), 'end' => date('Y-m-d 23:59:59', $timestamp), ]; } if (preg_match('/^\d{4}-\d{2}$/', $time)) { $timestamp = strtotime($time . '-01'); if ($timestamp === false || date('Y-m', $timestamp) !== $time) { $this->error('月份格式错误,请传 Y-m,如 2026-07'); } return [ 'type' => 'month', 'month' => $time, 'start' => date('Y-m-01 00:00:00', $timestamp), 'end' => date('Y-m-t 23:59:59', $timestamp), ]; } $this->error('时间格式错误,请传 Y-m-d(具体日期)或 Y-m(月份)'); } /** * 3. 获取车间、机组、机台编号、机台名称、班组基础数据 * @ApiMethod (GET) */ public function getBaseData() { if (!$this->request->isGet()) { $this->error('请求错误'); } $equipments = Db::name($this->equipmentTable) ->where('设备编组', '<>', '') ->where('sys_sbID', '<>', '') ->where('使用部门', '<>', '研发中心') ->field([ 'rtrim(使用部门) as 车间', 'rtrim(设备编组) as 机组', 'rtrim(设备编号) as 机台编号', 'rtrim(设备名称) as 机台名称', '平均车速', ]) ->order('设备编组,设备编号') ->select(); if (empty($equipments)) { $this->success('未获取到机台数据', []); } $list = []; foreach ($equipments as $item) { $code = $this->trimText($item['机台编号']); $list[] = [ '车间' => $this->trimText($item['车间']), '机组' => $this->trimText($item['机组']), '机台编号' => $code, '机台名称' => $this->trimText($item['机台名称']), '设备编号' => $code, '理论速度' => (int)$this->formatNumber($item['平均车速']), '班组列表' => $this->defaultShifts, ]; } $this->success('成功', $this->sortEquipmentList($list)); } /** * 4. 批量新增(每个机台每个班组一条) * @ApiMethod (POST) * @param array list 数据列表 * @param string sys_id 操作人 */ public function batchAdd() { if (!$this->request->isPost()) { $this->error('请求错误'); } $params = $this->request->post(); $list = isset($params['list']) ? $params['list'] : []; $operator = isset($params['sys_id']) ? trim($params['sys_id']) : ''; if (empty($list) || !is_array($list)) { $this->error('参数 list 不能为空'); } if ($operator === '') { $this->error('参数 sys_id 不能为空'); } $now = date('Y-m-d H:i:s'); $insertRows = []; $logs = []; Db::startTrans(); try { foreach ($list as $index => $row) { if (!is_array($row)) { throw new Exception('第 ' . ($index + 1) . ' 条数据格式错误'); } $data = $this->normalizeRow($row); $this->validateRow($data, $index + 1); $exists = Db::name($this->tableName) ->where('sczl_jtbh', $data['sczl_jtbh']) ->where('sczl_bzdh', $data['sczl_bzdh']) ->where('sczl_rq', $data['sczl_rq']) ->find(); if ($exists) { throw new Exception(sprintf( '第 %d 条记录已存在:设备[%s] 班组[%s] 日期[%s]', $index + 1, $data['sczl_jtbh'], $data['sczl_bzdh'], date('Y-m-d', strtotime($data['sczl_rq'])) )); } $data['sys_id'] = $operator; $data['sys_rq'] = $now; $insertRows[] = $data; } foreach ($insertRows as $data) { $recordId = Db::name($this->tableName)->insertGetId($data); if (!$recordId) { throw new Exception('新增失败'); } $logs = array_merge($logs, $this->buildAddLogs($recordId, $data, $operator, $now)); } if (!empty($logs)) { Db::name($this->logTableName)->insertAll($logs); } Db::commit(); $this->success('批量新增成功', ['count' => count($insertRows)]); } catch (Exception $e) { Db::rollback(); $this->error($e->getMessage()); } } /** * 5. 批量修改 * @ApiMethod (POST) * @param array list 含 id 的数据列表 * @param string sys_id 操作人 */ public function batchUpdate() { if (!$this->request->isPost()) { $this->error('请求错误'); } $params = $this->request->post(); $list = isset($params['list']) ? $params['list'] : []; $operator = isset($params['sys_id']) ? trim($params['sys_id']) : ''; if (empty($list) || !is_array($list)) { $this->error('参数 list 不能为空'); } if ($operator === '') { $this->error('参数 sys_id 不能为空'); } $now = date('Y-m-d H:i:s'); $logs = []; $updateCount = 0; Db::startTrans(); try { foreach ($list as $index => $row) { if (!is_array($row) || empty($row['id'])) { throw new Exception('第 ' . ($index + 1) . ' 条数据缺少 id'); } $id = (int)$row['id']; $old = Db::name($this->tableName)->where('id', $id)->find(); if (empty($old)) { throw new Exception('第 ' . ($index + 1) . ' 条记录不存在'); } $data = $this->normalizeRow($row, false); unset($data['id']); if (array_key_exists('sczl_bzdh', $row)) { if ($data['sczl_bzdh'] === '' || !in_array($data['sczl_bzdh'], $this->defaultShifts, true)) { throw new Exception('第 ' . ($index + 1) . ' 条班组代号只能为 A班 或 B班'); } } $checkJtbh = isset($data['sczl_jtbh']) ? $data['sczl_jtbh'] : rtrim($old['sczl_jtbh']); $checkBzdh = isset($data['sczl_bzdh']) ? $data['sczl_bzdh'] : rtrim($old['sczl_bzdh']); $checkRq = isset($data['sczl_rq']) ? $data['sczl_rq'] : $old['sczl_rq']; $duplicate = Db::name($this->tableName) ->where('sczl_jtbh', $checkJtbh) ->where('sczl_bzdh', $checkBzdh) ->where('sczl_rq', $checkRq) ->where('id', '<>', $id) ->find(); if ($duplicate) { throw new Exception(sprintf( '第 %d 条修改后与已有记录冲突:设备[%s] 班组[%s] 日期[%s]', $index + 1, $checkJtbh, $checkBzdh, date('Y-m-d', strtotime($checkRq)) )); } $data['mod_id'] = $operator; $data['mod_rq'] = $now; $changed = []; foreach ($data as $field => $value) { if (!array_key_exists($field, $old)) { continue; } $oldValue = $old[$field]; if ((string)$oldValue !== (string)$value) { $changed[$field] = $value; } } if (empty($changed)) { continue; } $result = Db::name($this->tableName)->where('id', $id)->update($changed + [ 'mod_id' => $operator, 'mod_rq' => $now, ]); if ($result === false) { throw new Exception('第 ' . ($index + 1) . ' 条修改失败'); } $logs = array_merge($logs, $this->buildUpdateLogs($id, $old, $changed, $operator, $now)); $updateCount++; } if (!empty($logs)) { Db::name($this->logTableName)->insertAll($logs); } Db::commit(); $this->success('批量修改成功', ['count' => $updateCount]); } catch (Exception $e) { Db::rollback(); $this->error($e->getMessage()); } } /** * 6. 批量删除 * @ApiMethod (POST) * @param string ids 逗号分隔 id * @param string sys_id 操作人 */ public function batchDelete() { if (!$this->request->isPost()) { $this->error('请求错误'); } $params = $this->request->post(); $idsParam = isset($params['ids']) ? trim($params['ids']) : ''; $operator = isset($params['sys_id']) ? trim($params['sys_id']) : ''; if ($idsParam === '') { $this->error('参数 ids 不能为空'); } if ($operator === '') { $this->error('参数 sys_id 不能为空'); } $ids = array_values(array_filter(array_map('intval', explode(',', $idsParam)))); if (empty($ids)) { $this->error('ids 格式错误'); } $records = Db::name($this->tableName)->where('id', 'in', $ids)->select(); if (count($records) !== count($ids)) { $this->error('部分记录不存在,请刷新后重试'); } $now = date('Y-m-d H:i:s'); $logs = []; Db::startTrans(); try { foreach ($records as $record) { $logs = array_merge($logs, $this->buildDeleteLogs((int)$record['id'], $record, $operator, $now)); } $deleteResult = Db::name($this->tableName)->where('id', 'in', $ids)->delete(); if ($deleteResult === false) { throw new Exception('删除失败'); } if (!empty($logs)) { Db::name($this->logTableName)->insertAll($logs); } Db::commit(); $this->success('批量删除成功', ['count' => $deleteResult]); } catch (Exception $e) { Db::rollback(); $this->error($e->getMessage()); } } /** * 按日查询明细 */ protected function queryDailyList($start, $end) { $list = Db::name($this->tableName) ->alias('a') ->join($this->equipmentTable . ' b', 'a.sczl_jtbh = b.设备编号', 'LEFT') ->where('a.sczl_rq', '>=', $start) ->where('a.sczl_rq', '<=', $end) ->field($this->buildSelectFields(false)) ->select(); return $this->formatList($list); } /** * 按月汇总查询 */ protected function queryMonthlyList($start, $end, $month) { $sumExpr = []; foreach ($this->sumFields as $field) { $sumExpr[] = "SUM(a.{$field}) as {$field}"; } $list = Db::name($this->tableName) ->alias('a') ->join($this->equipmentTable . ' b', 'a.sczl_jtbh = b.设备编号', 'LEFT') ->where('a.sczl_rq', '>=', $start) ->where('a.sczl_rq', '<=', $end) ->field(array_merge([ 'MIN(a.id) as id', 'a.sczl_jtbh', 'a.sczl_bzdh', 'MAX(a.theoretical_speed) as theoretical_speed', 'rtrim(b.使用部门) as 车间', 'rtrim(b.设备编组) as 机组', 'rtrim(b.设备名称) as 机台名称', ], $sumExpr)) ->group('a.sczl_jtbh,a.sczl_bzdh,b.使用部门,b.设备编组,b.设备名称') ->select(); foreach ($list as &$row) { $row['sczl_rq'] = $month . '-01 00:00:00'; $row['query_type'] = 'month'; $row['query_month'] = $month; } unset($row); return $this->formatList($list); } /** * 查询字段 */ protected function buildSelectFields($isMonth = false) { $fields = [ 'a.id', 'a.sczl_jtbh', 'a.sczl_bzdh', 'a.sczl_rq', 'a.theoretical_speed', 'a.scheduled_production', 'a.scheduled_maintenance', 'a.scheduled_handover', 'a.fault', 'a.changeover', 'a.proofing', 'a.abnormal', 'a.planned_output', 'a.actual_output', 'a.defective_product', 'a.sys_id', 'a.sys_rq', 'a.mod_id', 'a.mod_rq', 'rtrim(b.使用部门) as 车间', 'rtrim(b.设备编组) as 机组', 'rtrim(b.设备名称) as 机台名称', ]; return $fields; } /** * 格式化返回列表 */ protected function formatList($list) { $result = []; foreach ($list as $row) { $result[] = [ 'id' => (int)$row['id'], '车间' => isset($row['车间']) ? rtrim($row['车间']) : '', '机组' => isset($row['机组']) ? rtrim($row['机组']) : '', '机台编号' => rtrim($row['sczl_jtbh']), '设备编号' => rtrim($row['sczl_jtbh']), '机台名称' => isset($row['机台名称']) ? rtrim($row['机台名称']) : '', '班组' => $this->normalizeShift($row['sczl_bzdh']), 'sczl_jtbh' => rtrim($row['sczl_jtbh']), 'sczl_bzdh' => $this->normalizeShift($row['sczl_bzdh']), 'sczl_rq' => $row['sczl_rq'], 'theoretical_speed' => $this->formatNumber($row['theoretical_speed']), 'scheduled_production' => $this->formatNumber($row['scheduled_production']), 'scheduled_maintenance'=> $this->formatNumber($row['scheduled_maintenance']), 'scheduled_handover' => $this->formatNumber($row['scheduled_handover']), 'fault' => $this->formatNumber($row['fault']), 'changeover' => $this->formatNumber($row['changeover']), 'proofing' => $this->formatNumber($row['proofing']), 'abnormal' => $this->formatNumber($row['abnormal']), 'planned_output' => $this->formatNumber($row['planned_output']), 'actual_output' => $this->formatNumber($row['actual_output']), 'defective_product' => $this->formatNumber($row['defective_product']), 'sys_id' => isset($row['sys_id']) ? rtrim($row['sys_id']) : '', 'sys_rq' => isset($row['sys_rq']) ? $row['sys_rq'] : '', 'mod_id' => isset($row['mod_id']) ? rtrim($row['mod_id']) : '', 'mod_rq' => isset($row['mod_rq']) ? $row['mod_rq'] : '', 'query_type' => isset($row['query_type']) ? $row['query_type'] : 'day', 'query_month' => isset($row['query_month']) ? $row['query_month'] : '', ]; } return $this->sortEquipmentList($result); } /** * 按设备编组、机台编号数字排序 */ protected function sortEquipmentList(array $list) { usort($list, function ($a, $b) { return $this->compareEquipmentRow($a, $b); }); return $list; } /** * 设备行排序比较 */ protected function compareEquipmentRow($a, $b) { $groupA = $this->trimText(isset($a['机组']) ? $a['机组'] : ''); $groupB = $this->trimText(isset($b['机组']) ? $b['机组'] : ''); $groupOrderA = $this->extractGroupOrder($groupA); $groupOrderB = $this->extractGroupOrder($groupB); if ($groupOrderA !== $groupOrderB) { return $groupOrderA <=> $groupOrderB; } if ($groupA !== $groupB) { return strcmp($groupA, $groupB); } $codeA = $this->getMachineCodeFromRow($a); $codeB = $this->getMachineCodeFromRow($b); $numA = $this->extractMachineNumber($codeA); $numB = $this->extractMachineNumber($codeB); if ($numA !== $numB) { return $numA <=> $numB; } if ($codeA !== $codeB) { return strcmp($codeA, $codeB); } $shiftA = isset($a['班组']) ? $a['班组'] : (isset($a['sczl_bzdh']) ? $a['sczl_bzdh'] : ''); $shiftB = isset($b['班组']) ? $b['班组'] : (isset($b['sczl_bzdh']) ? $b['sczl_bzdh'] : ''); return strcmp($shiftA, $shiftB); } /** * 提取设备编组前缀序号(如 03、卷凹机组 -> 3) */ protected function extractGroupOrder($group) { $group = $this->trimText($group); if (preg_match('/^(\d+)/', $group, $matches)) { return (int)$matches[1]; } return PHP_INT_MAX; } /** * 提取机台编号中的数字(如 YWY01# -> 1) */ protected function extractMachineNumber($code) { $code = $this->trimText($code); if (preg_match('/(\d+)/', $code, $matches)) { return (int)$matches[1]; } return PHP_INT_MAX; } /** * 从返回行中获取机台编号 */ protected function getMachineCodeFromRow($row) { if (!empty($row['机台编号'])) { return $this->trimText($row['机台编号']); } if (!empty($row['sczl_jtbh'])) { return $this->trimText($row['sczl_jtbh']); } if (!empty($row['设备编号'])) { return $this->trimText($row['设备编号']); } return ''; } /** * 去除字符串首尾空白(含全角空格) */ protected function trimText($value) { return preg_replace('/^[\s\x{3000}]+|[\s\x{3000}]+$/u', '', (string)$value); } /** * 班组名称规范化:仅支持 A班、B班 */ protected function normalizeShift($shift) { $shift = $this->trimText($shift); $shift = preg_replace('/\s+/u', '', $shift); if ($shift === '') { return ''; } if (preg_match('/^A(?:班)?$/iu', $shift)) { return 'A班'; } if (preg_match('/^B(?:班)?$/iu', $shift)) { return 'B班'; } return ''; } /** * 标准化单行数据 */ protected function normalizeRow(array $row, $requireAll = true) { $data = []; foreach ($this->dataFields as $field) { if (!array_key_exists($field, $row)) { if (!$requireAll) { continue; } $data[$field] = in_array($field, $this->sumFields, true) || $field === 'theoretical_speed' ? 0 : ''; continue; } if ($field === 'sczl_rq') { $data[$field] = date('Y-m-d 00:00:00', strtotime($row[$field])); continue; } if (in_array($field, ['sczl_jtbh', 'sczl_bzdh'], true)) { $value = trim((string)$row[$field]); $data[$field] = $field === 'sczl_bzdh' ? $this->normalizeShift($value) : $value; continue; } if ($field === 'theoretical_speed') { $data[$field] = (int)$row[$field]; continue; } $data[$field] = $row[$field] === '' || $row[$field] === null ? 0 : $row[$field]; } return $data; } /** * 校验单行数据 */ protected function validateRow(array $data, $lineNo) { if ($data['sczl_jtbh'] === '') { throw new Exception('第 ' . $lineNo . ' 条设备编号不能为空'); } if ($data['sczl_bzdh'] === '') { throw new Exception('第 ' . $lineNo . ' 条班组代号不能为空,且只能为 A班 或 B班'); } if (!in_array($data['sczl_bzdh'], $this->defaultShifts, true)) { throw new Exception('第 ' . $lineNo . ' 条班组代号只能为 A班 或 B班'); } if (empty($data['sczl_rq'])) { throw new Exception('第 ' . $lineNo . ' 条日期不能为空'); } $equipment = Db::name($this->equipmentTable) ->where('设备编号', $data['sczl_jtbh']) ->find(); if (empty($equipment)) { throw new Exception('第 ' . $lineNo . ' 条设备编号不存在:' . $data['sczl_jtbh']); } } /** * 构建新增日志 */ protected function buildAddLogs($recordId, array $data, $operator, $time) { $logs = []; foreach ($this->dataFields as $field) { if (!array_key_exists($field, $data)) { continue; } $logs[] = [ 'record_id' => $recordId, 'operation_type' => 'add', 'field_name' => isset($this->fieldLabels[$field]) ? $this->fieldLabels[$field] : $field, 'old_value' => null, 'new_value' => (string)$data[$field], 'operator_id' => $operator, 'operation_time' => $time, ]; } return $logs; } /** * 构建修改日志 */ protected function buildUpdateLogs($recordId, array $old, array $changed, $operator, $time) { $logs = []; foreach ($changed as $field => $newValue) { if ($field === 'mod_id' || $field === 'mod_rq') { continue; } $logs[] = [ 'record_id' => $recordId, 'operation_type' => 'update', 'field_name' => isset($this->fieldLabels[$field]) ? $this->fieldLabels[$field] : $field, 'old_value' => isset($old[$field]) ? (string)$old[$field] : '', 'new_value' => (string)$newValue, 'operator_id' => $operator, 'operation_time' => $time, ]; } return $logs; } /** * 构建删除日志 */ protected function buildDeleteLogs($recordId, array $record, $operator, $time) { $logs = []; foreach ($this->dataFields as $field) { if (!array_key_exists($field, $record)) { continue; } $logs[] = [ 'record_id' => $recordId, 'operation_type' => 'delete', 'field_name' => isset($this->fieldLabels[$field]) ? $this->fieldLabels[$field] : $field, 'old_value' => (string)$record[$field], 'new_value' => null, 'operator_id' => $operator, 'operation_time' => $time, ]; } return $logs; } /** * 数值格式化 */ protected function formatNumber($value) { if ($value === null || $value === '') { return 0; } return is_numeric($value) ? (float)$value : $value; } }