m0_70156489 2 days ago
parent
commit
eefc74df92

+ 8 - 5
application/api/controller/Manufacture.php

@@ -1206,17 +1206,20 @@ class Manufacture extends Api
 
         $orderIds = array_column($list, '订单编号');
 
-        // ========== 1. 批量查询附件数据 ==========
+        // ========== 1. 批量查询附件数据 修复column key覆盖bug ==========
         $relatedOrders = \db('工单_相关附件')
             ->whereIn('关联编号', $orderIds)
             ->whereIn('附件备注', ['技术附件', '订单资料附件'])
-            ->column('关联编号,附件备注');
+            ->field('关联编号,附件备注')
+            ->select();
 
         $groupedRelatedOrders = [
             'technical' => [],
             'order'     => []
         ];
-        foreach ($relatedOrders as $orderNumber => $attachmentRemark) {
+        foreach ($relatedOrders as $row) {
+            $orderNumber = $row['关联编号'];
+            $attachmentRemark = $row['附件备注'];
             if ($attachmentRemark === '技术附件') {
                 $groupedRelatedOrders['technical'][] = $orderNumber;
             } elseif ($attachmentRemark === '订单资料附件') {
@@ -1224,7 +1227,7 @@ class Manufacture extends Api
             }
         }
 
-        // ========== 2. 【优化】批量查询工艺资料,只查当前页所有工单 ==========
+        // ========== 2. 批量查询工艺资料,只查当前页所有工单 ==========
         $gongyiList = \db('工单_基础工艺资料')
             ->whereIn('work_order', $orderIds)
             ->column('work_order'); // 只取出已上传工艺的订单编号
@@ -1247,7 +1250,7 @@ class Manufacture extends Api
             $value['status'] = in_array($orderNo, $groupedRelatedOrders['technical']) ? '' : '*';
             // 订单资料附件标记
             $value['orderstatus'] = in_array($orderNo, $groupedRelatedOrders['order']) ? '' : '*';
-            // 【新增】工艺资料标记:有数据为空,无数据标 *
+            // 工艺资料标记:有数据为空,无数据标 *
             $value['gongyistatus'] = in_array($orderNo, $gongyiList) ? '' : '*';
 
             // 工序产量统计

+ 86 - 45
application/api/controller/ReportingWork.php

@@ -113,6 +113,7 @@ class ReportingWork extends Api
      * @ApiMethod (GET)
      * @param string $part_code 部件编号
      * @param string $workorder 工单编号
+     * @param string $sys_id 小组名称(可选,传了则返回本组完成数)
      * @return array
      * @throws \think\db\exception\DataNotFoundException
      * @throws \think\db\exception\ModelNotFoundException
@@ -130,6 +131,7 @@ class ReportingWork extends Api
         if (empty($params['part_code'])) {
             $this->error('部件编号不能为空');
         }
+        $sysId = isset($params['sys_id']) ? trim((string)$params['sys_id']) : '';
         $orderInfo = db('工单_基本资料')
             ->where('订单编号', $params['workorder'])
             ->whereNull('Mod_rq')
@@ -140,13 +142,15 @@ class ReportingWork extends Api
             ->where('work_order', $params['workorder'])
             ->where('part_code', $params['part_code'])
             ->whereNull('del_rq')
-            // ->where('status', 0)
+            // 已拆分母工序 status=1 不展示,仅返回可报工工序 status=0
+            ->where('status', 0)
             ->field('process_code as 工艺编号,process_name as 工艺名称,standard_hour as 标准工时,standard_minutes as 标准分钟,
-            standard_score as 标准工分,coefficient as 系数,remark as 备注,money as 金额')
+            standard_score as 标准工分,coefficient as 系数,remark as 备注,money as 金额,status')
             ->select();
         usort($data, function ($a, $b) {
             return strnatcmp((string)$a['工艺编号'], (string)$b['工艺编号']);
         });
+        // 累计完成数:全部小组
         $reportList = db('设备_工分计酬')
             ->where('work_order', $params['workorder'])
             ->where('part_code', $params['part_code'])
@@ -158,11 +162,30 @@ class ReportingWork extends Api
         foreach ($reportList as $report) {
             $completedMap[trim((string)$report['process_code'])] = floatval($report['completed_qty']);
         }
+
+        // 本组完成数:按 sys_id(小组名称)过滤
+        $groupCompletedMap = [];
+        if ($sysId !== '') {
+            $groupReportList = db('设备_工分计酬')
+                ->where('work_order', $params['workorder'])
+                ->where('part_code', $params['part_code'])
+                ->where('sys_id', $sysId)
+                ->whereNull('del_rq')
+                ->field('process_code, SUM(number) as completed_qty')
+                ->group('process_code')
+                ->select();
+            foreach ($groupReportList as $report) {
+                $groupCompletedMap[trim((string)$report['process_code'])] = floatval($report['completed_qty']);
+            }
+        }
+
         foreach ($data as $key => $row) {
             $processCode = trim((string)$row['工艺编号']);
             $completedQty = $completedMap[$processCode] ?? 0;
+            $groupCompletedQty = $groupCompletedMap[$processCode] ?? 0;
             $data[$key]['制造数量'] = $manufactureQty;
             $data[$key]['累计完成数'] = $completedQty;
+            $data[$key]['本组完成数'] = $groupCompletedQty;
             $data[$key]['未完成数'] = floatval($manufactureQty) - $completedQty;
         }
         $this->success('成功', $data);
@@ -212,6 +235,7 @@ class ReportingWork extends Api
         $insertData = [];
         $logList = [];
         $now = date('Y-m-d H:i:s');
+
         foreach ($reportList as $idx => $item) {
             $rowNo = $idx + 1;
             if (empty($item['staff_no'])) {
@@ -233,77 +257,91 @@ class ReportingWork extends Api
             if (empty($item['process_code'])) {
                 $this->error('第' . $rowNo . '条工艺编号不能为空');
             }
-            if (empty($item['process_name'])) {
-                $this->error('第' . $rowNo . '条工艺名称不能为空');
-            }
-            // if (!isset($item['standard_hour']) || $item['standard_hour'] === '') {
-            //     $this->error('第' . $rowNo . '条标准工时不能为空');
-            // }
-            // if (!isset($item['money']) || $item['money'] === '') {
-            //     $this->error('第' . $rowNo . '条金额不能为空');
-            // }
-            // if (empty($item['standard_minutes'])) {
-            //     $this->error('第' . $rowNo . '条标准分钟不能为空');
-            // }
-            if (!isset($item['standard_score']) || $item['standard_score'] === '') {
-                $this->error('第' . $rowNo . '条标准工分不能为空');
-            }
             if (!isset($item['number']) || $item['number'] === '') {
                 $this->error('第' . $rowNo . '条产量不能为空');
             }
 
-            $standardHour = floatval($item['standard_hour']);
-            $standardScore = floatval($item['standard_score']);
-            $money = floatval($item['money']);
-            $coefficient = isset($item['coefficient']) && $item['coefficient'] !== '' ? floatval($item['coefficient']) : 'C';
-            $number = floatval($item['number']);
-            $standardMinutes = floatval($item['standard_minutes']);
-            $productionHour = $standardHour * $number;
-            $productionScore = $standardScore * $number;
-            $salary = $this->calcReportSalary($number, $standardScore);
+            $workOrder = !empty($item['work_order']) ? trim((string)$item['work_order']) : trim((string)$item['workorder']);
+            $processCode = trim((string)$item['process_code']);
             $partCode = null;
             if (isset($item['part_code']) && $item['part_code'] !== '') {
                 $partCode = intval($item['part_code']);
             }
+            $number = floatval($item['number']);
+            if ($number <= 0) {
+                $this->error('第' . $rowNo . '条产量必须大于0');
+            }
+
+            // 校验工序在工艺资料中仍存在,并以工艺表为准写入名称/工分(避免前端缓存旧数据)
+            $processQuery = db('工单_基础工艺资料')
+                ->where('work_order', $workOrder)
+                ->where('process_code', $processCode)
+                ->whereNull('del_rq');
+            if ($partCode !== null) {
+                $processQuery->where('part_code', $partCode);
+            }
+            $processInfo = $processQuery
+                ->field('process_code,process_name,part_code,standard_hour,standard_minutes,standard_score,money,coefficient,big_process')
+                ->find();
+            if (empty($processInfo)) {
+                $this->error('第' . $rowNo . '条工序不存在或已删除(订单' . $workOrder . ' 工序号' . $processCode
+                    . ($partCode !== null ? ' 部件' . $partCode : '') . '),请刷新工艺后重试');
+            }
+
+            $processName = trim((string)$processInfo['process_name']);
+            $standardHour = floatval($processInfo['standard_hour']);
+            $standardScore = floatval($processInfo['standard_score']);
+            $standardMinutes = floatval($processInfo['standard_minutes']);
+            $money = floatval($processInfo['money']);
+            $coefficient = isset($processInfo['coefficient']) && $processInfo['coefficient'] !== ''
+                ? $processInfo['coefficient']
+                : (isset($item['coefficient']) && $item['coefficient'] !== '' ? $item['coefficient'] : 'C');
+            if ($partCode === null && isset($processInfo['part_code']) && $processInfo['part_code'] !== '') {
+                $partCode = intval($processInfo['part_code']);
+            }
+
+            $productionHour = round($standardHour * $number, 2);
+            $productionScore = round($standardScore * $number, 2);
+            $salary = $this->calcReportSalary($number, $standardScore);
+            $sysId = isset($item['sys_id']) ? trim((string)$item['sys_id']) : '';
 
             $insertData[] = [
                 'staff_no' => $item['staff_no'],
                 'staff_name' => $item['staff_name'],
-                'work_order' => !empty($item['work_order']) ? $item['work_order'] : $item['workorder'],
+                'work_order' => $workOrder,
                 'date' => !empty($item['date']) ? $item['date'] : date('Y-m-d'),
                 'majorprocess' => $item['majorprocess'],
                 'part_code' => $partCode,
-                'process_code' => $item['process_code'],
-                'process_name' => $item['process_name'],
+                'process_code' => $processCode,
+                'process_name' => $processName,
                 'standard_hour' => $standardHour,
                 'standard_score' => $standardScore,
                 'standard_minutes' => $standardMinutes,
                 'money' => $money,
                 'coefficient' => $coefficient,
                 'number' => $number,
-                'production_hour' => round($productionHour, 4),
-                'production_score' => round($productionScore, 4),
+                'production_hour' => $productionHour,
+                'production_score' => $productionScore,
                 'salary' => $salary,
-                'machine' => isset($item['machine']) ? $item['machine'] : '',
-                'sys_id' => isset($item['sys_id']) ? $item['sys_id'] : '',
+                'machine' => isset($item['machine']) ? trim((string)$item['machine']) : '',
+                'sys_id' => $sysId,
                 'sys_rq' => $now,
                 'mod_id' => isset($item['mod_id']) ? $item['mod_id'] : '',
                 'mod_rq' => null,
                 'del_rq' => null,
             ];
-            $workOrder = !empty($item['work_order']) ? $item['work_order'] : $item['workorder'];
             $logList[] = $this->buildReportingWorkLogRow([
                 'oper_type' => '新增报工',
-                'oper_user' => isset($item['sys_id']) ? $item['sys_id'] : '',
+                'oper_user' => $sysId !== '' ? $sysId : (isset($item['mod_id']) ? $item['mod_id'] : ''),
                 'oper_time' => $now,
                 'work_order' => $workOrder,
                 'majorprocess' => $item['majorprocess'],
                 'part_code' => $partCode,
-                'process_code' => $item['process_code'],
-                'process_name' => $item['process_name'],
+                'process_code' => $processCode,
+                'process_name' => $processName,
                 'staff_no' => $item['staff_no'],
                 'staff_name' => $item['staff_name'],
-                'machine' => isset($item['machine']) ? $item['machine'] : '',
+                'machine' => isset($item['machine']) ? trim((string)$item['machine']) : '',
                 'report_date' => !empty($item['date']) ? $item['date'] : date('Y-m-d'),
             ], '数量', '', (string)$number);
         }
@@ -478,11 +516,14 @@ class ReportingWork extends Api
         }
 
         $number = floatval($params['number']);
+        if ($number <= 0) {
+            $this->error('产量必须大于0');
+        }
         $standardHour = floatval($record['standard_hour']);
         $standardScore = floatval($record['standard_score']);
 
-        $productionHour = round($standardHour * $number, 4);
-        $productionScore = round($standardScore * $number, 4);
+        $productionHour = round($standardHour * $number, 2);
+        $productionScore = round($standardScore * $number, 2);
         $salary = $this->calcReportSalary($number, $standardScore);
         $operTime = date('Y-m-d H:i:s');
         $operUser = !empty($params['mod_id']) ? $params['mod_id'] : ($record['sys_id'] ?? '');
@@ -740,7 +781,7 @@ class ReportingWork extends Api
     }
 
     /**
-     * 按新公式批量重算历史报工工资(数量 × 工分 × 0.067)
+     * 按新公式批量重算历史报工工资(数量 × 工分 × 0.067,保留2位
      * @ApiMethod (POST)
      * @param string workorder 可选,限定工单编号
      * @param string date_start 可选,报工日期起(Y-m-d)
@@ -774,8 +815,8 @@ class ReportingWork extends Api
         Db::startTrans();
         try {
             $affected = $applyFilters(Db::table('设备_工分计酬')->whereNull('del_rq'))->update([
-                'production_score' => Db::raw('ROUND(`number` * `standard_score`, 4)'),
-                'salary' => Db::raw('ROUND(`number` * `standard_score` * 0.067, 4)'),
+                'production_score' => Db::raw('ROUND(`number` * `standard_score`, 2)'),
+                'salary' => Db::raw('ROUND(`number` * `standard_score` * 0.067, 2)'),
             ]);
             if ($affected === false) {
                 throw new \Exception('批量更新失败');
@@ -891,13 +932,13 @@ SQL;
     }
 
     /**
-     * 报工工资:数量 × 工分 × 0.067
+     * 报工工资:数量 × 工分 × 0.067(保留2位,与库字段 decimal(10,2) 一致)
      * @param mixed $number
      * @param mixed $standardScore
      * @return float
      */
     private function calcReportSalary($number, $standardScore)
     {
-        return round(floatval($number) * floatval($standardScore) * 0.067, 4);
+        return round(floatval($number) * floatval($standardScore) * 0.067, 2);
     }
 }

+ 53 - 152
application/api/controller/StaffSalary.php

@@ -177,6 +177,10 @@ class StaffSalary extends Api
     /**
      * 查询员工工资列表
      * @ApiMethod (GET)
+     * @param string $sys_rq 可选,左侧树日期:2026 / 2026-06 / 2026-06-08
+     * @param string $big_process 可选,大工序
+     * @param string $group 可选,小组
+     * @param string $search 可选,顶部搜索:订单编号/员工编号/员工姓名(也兼容 keyword、work_order、staff_no、staff_name)
      * @return array
      * @throws \think\db\exception\DataNotFoundException
      * @throws \think\db\exception\ModelNotFoundException
@@ -187,100 +191,72 @@ class StaffSalary extends Api
         if (!$this->request->isGet()) {
             $this->error('请求方法错误');
         }
-        $param = $this->request->param();
-
-        if (empty($param['sys_rq'])) {
-            $this->error('请选择日期(sys_rq)');
+        $rawParam = $this->request->param();
+        $param = [];
+        foreach ($rawParam as $key => $value) {
+            $normKey = strtolower(rtrim(str_replace([' ', ' '], '', (string)$key), '_'));
+            if ($normKey === '') {
+                continue;
+            }
+            if (!array_key_exists($normKey, $param) || $param[$normKey] === '' || $param[$normKey] === null) {
+                $param[$normKey] = $value;
+            }
         }
 
-        $sysRq = $param['sys_rq'];
-        $bigProcess = !empty($param['big_process']) ? $param['big_process'] : '';
-        $group = !empty($param['group']) ? $param['group'] : '';
-
-        // ========== 第一步:连表查询员工列表(无exp、无whereRaw) ==========
-        $staffWhere = [];
-        $staffWhere['a.status'] = 1;
-
-        // 日期条件:纯LIKE匹配,兼容带时分秒的sys_rq
-        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $sysRq)) {
-            $staffWhere['b.sys_rq'] = ['like', $sysRq . '%'];
-        } elseif (preg_match('/^\d{4}-\d{2}$/', $sysRq)) {
-            $staffWhere['b.sys_rq'] = ['like', $sysRq . '%'];
-        } elseif (preg_match('/^\d{4}$/', $sysRq)) {
-            $staffWhere['b.sys_rq'] = ['like', $sysRq . '%'];
-        } else {
-            $this->error('sys_rq 格式错误,请使用 2026 / 2026-06 / 2026-06-08 格式');
+        $sysRq = !empty($param['sys_rq']) ? trim((string)$param['sys_rq']) : '';
+        $bigProcess = !empty($param['big_process']) ? trim((string)$param['big_process']) : '';
+        $group = !empty($param['group']) ? trim((string)$param['group']) : '';
+        $search = '';
+        foreach (['search', 'keyword', 'work_order', 'staff_no', 'staff_name'] as $key) {
+            if (!empty($param[$key])) {
+                $search = trim((string)$param[$key]);
+                break;
+            }
         }
 
-        // 工序筛选(可选)
-        if (!empty($bigProcess)) {
-            $staffWhere['a.big_process'] = $bigProcess;
+        //日期 或 搜索
+        if ($sysRq === '' && $search === '') {
+            $this->error('请选择左侧日期,或输入订单编号/员工编号/姓名进行查询');
         }
 
-        // 小组筛选(可选)
-        if (!empty($group)) {
-            $staffWhere['b.sys_id'] = $group;
+        if ($sysRq !== '') {
+            if (!preg_match('/^\d{4}(-\d{2})?(-\d{2})?$/', $sysRq)) {
+                $this->error('sys_rq 格式错误,请使用 2026 / 2026-06 / 2026-06-08 格式');
+            }
         }
 
-        // 关键:用whereNull处理del_rq IS NULL,彻底解决EXP报错
-        $staff = db('人员_基本资料')
-            ->alias('a')
-            ->join('设备_工分计酬 b','a.staff_no = b.staff_no','left')
-            ->where($staffWhere)
-            ->whereNull('b.del_rq')
-            ->field('a.staff_no,a.staff_name,sum(b.salary) as salary')
-            ->group('a.staff_no,a.staff_name')
-            ->select();
-
-        if(empty($staff)){
-            $this->error('当前条件下暂无工资数据');
-        }
+        // 直接查报工计酬表,支持无日期仅按订单/员工搜索
+        $query = db('设备_工分计酬')->whereNull('del_rq');
 
-        $staffMap = [];
-        $staffNos = [];
-        foreach ($staff as $item) {
-            $staffMap[$item['staff_no']] = $item['staff_name'];
-            $staffNos[] = $item['staff_no'];
+        if ($sysRq !== '') {
+            $query->where('sys_rq', 'like', $sysRq . '%');
         }
-
-        // ========== 第二步:查询明细数据(无exp、无whereRaw) ==========
-        $detailWhere = [];
-        $detailWhere['staff_no'] = ['in', $staffNos];
-
-        // 日期条件:和上面保持一致
-        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $sysRq)) {
-            $detailWhere['sys_rq'] = ['like', $sysRq . '%'];
-        } elseif (preg_match('/^\d{4}-\d{2}$/', $sysRq)) {
-            $detailWhere['sys_rq'] = ['like', $sysRq . '%'];
-        } elseif (preg_match('/^\d{4}$/', $sysRq)) {
-            $detailWhere['sys_rq'] = ['like', $sysRq . '%'];
+        if ($bigProcess !== '') {
+            $query->where('majorprocess', $bigProcess);
         }
-
-        // 工序筛选(可选)
-        if (!empty($bigProcess)) {
-            $detailWhere['majorprocess'] = $bigProcess;
+        if ($group !== '') {
+            $query->where('sys_id', $group);
         }
-
-        // 小组筛选(可选)
-        if (!empty($group)) {
-            $detailWhere['sys_id'] = $group;
+        if ($search !== '') {
+            // 订单编号 / 员工编号 / 员工姓名 模糊匹配
+            $query->where('work_order|staff_no|staff_name', 'like', '%' . $search . '%');
         }
 
-        // 关键:用whereNull处理del_rq IS NULL
-        $salaryRows = db('设备_工分计酬')
-            ->where($detailWhere)
-            ->whereNull('del_rq')
-            // 新增sys_id字段,并在group里加上,确保不同小组的数据不被合并
-            ->field('staff_no,staff_name,DATE_FORMAT(sys_rq, "%Y-%m-%d") as date,sum(salary) as salary,sys_id')
-            ->group('staff_no,DATE_FORMAT(sys_rq, "%Y-%m-%d"),sys_id')
-            ->order('staff_no asc,sys_rq asc')
+        $salaryRows = $query
+            ->field('staff_no,staff_name,DATE_FORMAT(IFNULL(sys_rq, date), "%Y-%m-%d") as date,sum(salary) as salary,sys_id')
+            ->group('staff_no,DATE_FORMAT(IFNULL(sys_rq, date), "%Y-%m-%d"),sys_id')
+            ->order('staff_no asc,date asc')
             ->select();
 
+        if (empty($salaryRows)) {
+            // $this->error('当前条件下暂无工资数据');
+        }
+
         $grouped = [];
         foreach ($salaryRows as $row) {
             $staffNo = $row['staff_no'];
+            $name = !empty($row['staff_name']) ? $row['staff_name'] : '';
             if (!isset($grouped[$staffNo])) {
-                $name = !empty($row['staff_name']) ? $row['staff_name'] : (isset($staffMap[$staffNo]) ? $staffMap[$staffNo] : '');
                 $grouped[$staffNo] = [
                     'staff' => $staffNo . '-' . $name,
                     'total_salary' => 0,
@@ -289,10 +265,9 @@ class StaffSalary extends Api
             }
             $grouped[$staffNo]['children'][] = [
                 '员工编号' => $staffNo,
-                '员工姓名' => !empty($row['staff_name']) ? $row['staff_name'] : (isset($staffMap[$staffNo]) ? $staffMap[$staffNo] : ''),
+                '员工姓名' => $name,
                 '日期' => $row['date'],
                 '工资' => $row['salary'],
-                // 新增小组字段
                 '小组' => $row['sys_id'],
             ];
             $grouped[$staffNo]['total_salary'] += $row['salary'];
@@ -301,81 +276,6 @@ class StaffSalary extends Api
         $this->success('成功', array_values($grouped));
     }
 
-//    /**
-//     * 查询员工工资列表
-//     * @ApiMethod (GET)
-//     * @return array
-//     * @throws \think\db\exception\DataNotFoundException
-//     * @throws \think\db\exception\ModelNotFoundException
-//     * @throws \think\exception\DbException
-//     */
-//    public function GetStaffSalaryList()
-//    {
-//        if (!$this->request->isGet()) {
-//            $this->error('请求方法错误');
-//        }
-//        $param = $this->request->param();
-//        if (empty($param['month'])) {
-//            $this->error('请选择月份');
-//        }
-//        if (empty($param['big_process'])) {
-//            $this->error('请选择部门');
-//        }
-//        //获取大工序员工列表
-//        $staff = db('人员_基本资料')
-//            ->alias('a')
-//            ->join('设备_工分计酬 b','a.staff_no = b.staff_no','left')
-//            ->where('a.big_process',$param['big_process'])
-//            ->where('a.status',1)
-//            ->where('b.date', 'like', $param['month'] . '%')
-//            ->field('a.staff_no,a.staff_name,sum(b.salary) as salary')
-//            ->group('a.staff_no,a.staff_name')
-//            ->select();
-//        if(empty($staff)){
-//            $this->error('该工序没有报工数据');
-//        }
-//
-//        $staffMap = [];
-//        $staffNos = [];
-//        foreach ($staff as $item) {
-//            $staffMap[$item['staff_no']] = $item['staff_name'];
-//            $staffNos[] = $item['staff_no'];
-//        }
-//
-//        $salaryRows = db('设备_工分计酬')
-//            ->where('del_rq', null)
-//            ->where('date', 'like', $param['month'] . '%')
-//            ->where('staff_no', 'in', $staffNos)
-//            ->field('staff_no,staff_name,DATE_FORMAT(date, "%Y-%m-%d") as date,sum(salary) as salary,sys_id')
-//            ->group('staff_no,DATE_FORMAT(date, "%Y-%m-%d")')
-//            ->order('staff_no asc,date asc')
-//            ->select();
-//
-//        $grouped = [];
-//        foreach ($salaryRows as $row) {
-//            $staffNo = $row['staff_no'];
-//            if (!isset($grouped[$staffNo])) {
-//                $name = !empty($row['staff_name']) ? $row['staff_name'] : (isset($staffMap[$staffNo]) ? $staffMap[$staffNo] : '');
-//                $grouped[$staffNo] = [
-//                    'staff' => $staffNo . '-' . $name,
-//                    'total_salary' => 0,
-//                    'children' => [],
-//                ];
-//            }
-//            $grouped[$staffNo]['children'][] = [
-//                '员工编号' => $staffNo,
-//                '员工姓名' => !empty($row['staff_name']) ? $row['staff_name'] : (isset($staffMap[$staffNo]) ? $staffMap[$staffNo] : ''),
-//                '日期' => $row['date'],
-//                '工资' => $row['salary'],
-//            ];
-//            $grouped[$staffNo]['total_salary'] += $row['salary'];
-//        }
-//
-//        $this->success('成功', array_values($grouped));
-//
-//    }
-
-
     /**
      * 查询员工工资详情
      *
@@ -396,12 +296,13 @@ class StaffSalary extends Api
             'a.staff_no' => $param['staff_no'],
             'a.sys_rq' => ['like', $param['date'] . '%'],
             'a.del_rq' => null,
+            'c.mod_rq' => null,
         ];
         $list = db('设备_工分计酬')
             ->alias('a')
             ->join('工单_部件资料 b', 'a.work_order = b.work_order and a.part_code = b.part_code', 'left')
             ->join('工单_基本资料 c', 'a.work_order = c.订单编号', 'left')
-            ->field('a.work_order as 订单编号,DATE_FORMAT(a.date, "%Y-%m-%d") as 日期,b.part_name as 部件名称,
+            ->field('a.id,a.work_order as 订单编号,DATE_FORMAT(a.date, "%Y-%m-%d") as 日期,a.sys_rq,b.part_name as 部件名称,
             a.part_code as 部件编号,a.salary as 工资,a.number as 数量,a.production_hour as 生产工时,a.production_score as 生产分数,
             a.machine as 设备名称,a.process_code as 工序编号,a.process_name as 工序名称,a.standard_hour as 标准工时,a.standard_score as 标准分数,
             a.coefficient as 系数,a.sys_id as 设备编号,c.生产款号,c.款式')

+ 35 - 8
application/api/controller/WorkOrder.php

@@ -342,14 +342,24 @@ class WorkOrder extends Api
         if (empty($param['订单编号'])) {
             $this->error('订单编号不能为空');
         }
-        $param['订单编号'] = $this->resolveUniqueWorkOrderNo($param['订单编号']);
-
-        $data = $param;
-        unset($data['关联订单']);
-        unset($data['关联面料ID']);
-        //新增订单插入至工单基本资料
-        $sql = \db('工单_基本资料')->fetchSql(true)->insert($data);
-        \db()->query($sql);
+
+        // 命名锁串行化「取号+插入」,避免并发下先查后插导致重复订单编号
+        $lockName = 'work_order_no_alloc';
+        if (!$this->acquireWorkOrderNoLock($lockName)) {
+            $this->error('系统繁忙,请稍后重试');
+        }
+        try {
+            $param['订单编号'] = $this->resolveUniqueWorkOrderNo($param['订单编号']);
+
+            $data = $param;
+            unset($data['关联订单']);
+            unset($data['关联面料ID']);
+            //新增订单插入至工单基本资料
+            $sql = \db('工单_基本资料')->fetchSql(true)->insert($data);
+            \db()->query($sql);
+        } finally {
+            $this->releaseWorkOrderNoLock($lockName);
+        }
         if (!empty($param['关联订单']) && !empty($param['关联面料ID'])){
             //关联订单编号
             $OrderId = $param['关联订单'];
@@ -5889,6 +5899,23 @@ class WorkOrder extends Api
         $this->success('修改成功');
     }
 
+    /**
+     * 获取订单编号分配锁(跨请求互斥)
+     */
+    private function acquireWorkOrderNoLock($lockName, $timeout = 10)
+    {
+        $result = \db()->query("SELECT GET_LOCK(?, ?) AS locked", [$lockName, (int)$timeout]);
+        return !empty($result[0]['locked']);
+    }
+
+    /**
+     * 释放订单编号分配锁
+     */
+    private function releaseWorkOrderNoLock($lockName)
+    {
+        \db()->query("SELECT RELEASE_LOCK(?)", [$lockName]);
+    }
+
     /**
      * 订单编号是否已存在(未删除)
      */

+ 257 - 6
application/api/controller/WorkOrderProcess.php

@@ -578,7 +578,7 @@ class WorkOrderProcess extends Api
         } catch (\Exception $e) {
             Db::rollback();
             $msg = trim($e->getMessage());
-            $this->error('更新失败' . ($msg !== '' ? $msg : '未知错误'));
+            $this->error('更新失败' . ($msg !== '' ? $msg : '未知错误'));
         }
         $this->success('更新成功');
     }
@@ -603,6 +603,32 @@ class WorkOrderProcess extends Api
             $this->error('无效的工艺ID');
         }
 
+        $processes = db('工单_基础工艺资料')
+            ->whereIn('id', $ids)
+            ->whereNull('del_rq')
+            ->field('id,work_order,process_code,process_name,part_code')
+            ->select();
+        if (empty($processes)) {
+            $this->error('未找到可删除的工艺');
+        }
+
+        foreach ($processes as $process) {
+            $reportQuery = Db::table('设备_工分计酬')
+                ->where('work_order', $process['work_order'])
+                ->where('process_code', $process['process_code'])
+                ->whereNull('del_rq');
+            if ($process['part_code'] !== null && $process['part_code'] !== '') {
+                $reportQuery->where('part_code', $process['part_code']);
+            }
+            $hasReport = (int)$reportQuery->count();
+            if ($hasReport > 0) {
+                $this->error(
+                    '工序' . $process['process_code'] . '「' . $process['process_name']
+                    . '」已有报工记录,禁止删除。如需停用请联系管理员处理历史报工后再操作'
+                );
+            }
+        }
+
         $result = db('工单_基础工艺资料')
             ->whereIn('id', $ids)
             ->whereNull('del_rq')
@@ -783,10 +809,36 @@ class WorkOrderProcess extends Api
                     throw new \Exception('缺少工艺编号');
                 }
 
+                $process = db('工单_基础工艺资料')
+                    ->where('id', intval($value['id']))
+                    ->whereNull('del_rq')
+                    ->field('id,work_order,process_code,process_name')
+                    ->find();
+                if (empty($process)) {
+                    throw new \Exception('工艺不存在或已删除');
+                }
+
+                $newCode = intval($value['process_code']);
+                $oldCode = intval($process['process_code']);
+                // 有报工时禁止改工序号,避免历史报工挂空/错挂
+                if ($newCode !== $oldCode) {
+                    $hasReport = (int)Db::table('设备_工分计酬')
+                        ->where('work_order', $process['work_order'])
+                        ->whereNull('del_rq')
+                        ->count();
+                    if ($hasReport > 0) {
+                        throw new \Exception(
+                            '该工单已有工分报工记录,禁止调整工序号(原'
+                            . $process['process_code'] . '「' . $process['process_name']
+                            . '」不可改为' . $newCode . ')。如需新增工序请在最大序号后追加'
+                        );
+                    }
+                }
+
                 $result = db('工单_基础工艺资料')
                     ->where('id', intval($value['id']))
                     ->whereNull('del_rq')
-                    ->update(['process_code' => intval($value['process_code'])]);
+                    ->update(['process_code' => $newCode]);
                 if ($result === false) {
                     throw new \Exception('排序失败');
                 }
@@ -905,6 +957,208 @@ class WorkOrderProcess extends Api
             ];
         }
 
+        if (empty($workOrderProcesses)) {
+            $this->error('没有可导入的工序数据');
+        }
+
+        // 已有工分报工时:仅允许尾部追加(旧序号/名称/部件顺序不变),禁止中间插入或重排序号
+        $hasReport = (int)Db::table('设备_工分计酬')
+            ->where('work_order', $params['workorder'])
+            ->whereNull('del_rq')
+            ->count();
+        if ($hasReport > 0) {
+            $oldProcesses = Db::table('工单_基础工艺资料')
+                ->where('work_order', $params['workorder'])
+                ->whereNull('del_rq')
+                ->field('process_code,process_name,part_code,part_name')
+                ->select();
+            if (empty($oldProcesses)) {
+                // 工艺表为空时,用已报工快照做兼容校验
+                $oldProcesses = Db::table('设备_工分计酬')
+                    ->where('work_order', $params['workorder'])
+                    ->whereNull('del_rq')
+                    ->field('process_code,process_name,part_code')
+                    ->group('process_code,process_name,part_code')
+                    ->select();
+                foreach ($oldProcesses as $k => $row) {
+                    $oldProcesses[$k]['part_name'] = '';
+                }
+            }
+            if (empty($oldProcesses)) {
+                $this->error('禁止中间插入或重排序号');
+            }
+
+            $oldParts = Db::table('工单_部件资料')
+                ->where('work_order', $params['workorder'])
+                ->whereNull('del_rq')
+                ->field('part_code,part_name')
+                ->orderRaw('CAST(part_code AS UNSIGNED) ASC')
+                ->select();
+
+            $normCode = function ($code) {
+                $code = trim((string)$code);
+                if ($code !== '' && is_numeric($code)) {
+                    return (string)intval($code);
+                }
+                return $code;
+            };
+
+            $newProcessMap = [];
+            foreach ($workOrderProcesses as $p) {
+                $codeKey = $normCode($p['process_code']);
+                if ($codeKey === '') {
+                    continue;
+                }
+                $newProcessMap[$codeKey] = [
+                    'process_name' => trim((string)$p['process_name']),
+                    'part_name' => trim((string)$p['part_name']),
+                    'process_code' => $codeKey,
+                ];
+            }
+
+            $conflicts = [];
+            $oldCodeSet = [];
+            $oldMaxCode = 0;
+            foreach ($oldProcesses as $old) {
+                $oldCode = $normCode($old['process_code']);
+                if ($oldCode === '') {
+                    continue;
+                }
+                $oldCodeSet[$oldCode] = true;
+                $oldCodeInt = intval($oldCode);
+                if ($oldCodeInt > $oldMaxCode) {
+                    $oldMaxCode = $oldCodeInt;
+                }
+                $oldName = trim((string)$old['process_name']);
+                $oldPartName = isset($old['part_name']) ? trim((string)$old['part_name']) : '';
+                if (!isset($newProcessMap[$oldCode])) {
+                    $conflicts[] = [
+                        'process_code' => $oldCode,
+                        'message' => '原工序在新表中不存在',
+                        'old_value' => $oldName,
+                        'new_value' => '',
+                    ];
+                    continue;
+                }
+                $newName = $newProcessMap[$oldCode]['process_name'];
+                $newPartName = $newProcessMap[$oldCode]['part_name'];
+                if ($oldName !== '' && $newName !== $oldName) {
+                    $conflicts[] = [
+                        'process_code' => $oldCode,
+                        'message' => '与已有工序不一致',
+                        'old_value' => $oldName,
+                        'new_value' => $newName,
+                    ];
+                }
+                if ($oldPartName !== '' && $newPartName !== $oldPartName) {
+                    $conflicts[] = [
+                        'process_code' => $oldCode,
+                        'message' => '与已有部件不一致',
+                        'old_value' => $oldPartName,
+                        'new_value' => $newPartName,
+                    ];
+                }
+            }
+
+            foreach ($newProcessMap as $codeKey => $p) {
+                if (isset($oldCodeSet[$codeKey])) {
+                    continue;
+                }
+                if (intval($codeKey) <= $oldMaxCode) {
+                    $conflicts[] = [
+                        'process_code' => $codeKey,
+                        'message' => '中间插入,请将新增序号设为' . ($oldMaxCode + 1) . '及以后',
+                        'old_value' => '旧最大序号' . $oldMaxCode,
+                        'new_value' => $p['process_name'],
+                    ];
+                }
+            }
+
+            foreach ($oldParts as $oldPart) {
+                $oldPartName = trim((string)$oldPart['part_name']);
+                $oldPartCode = intval($oldPart['part_code']);
+                if ($oldPartName === '') {
+                    continue;
+                }
+                if (!isset($partMap[$oldPartName])) {
+                    $conflicts[] = [
+                        'process_code' => '',
+                        'message' => '原部件在新表中不存在',
+                        'old_value' => $oldPartName . '(编号' . $oldPartCode . ')',
+                        'new_value' => '',
+                    ];
+                    continue;
+                }
+                $newPartCode = intval($partMap[$oldPartName]['part_code']);
+                if ($newPartCode !== $oldPartCode) {
+                    $conflicts[] = [
+                        'process_code' => '',
+                        'message' => '部件编号将变更',
+                        'old_value' => $oldPartName . '→编号' . $oldPartCode,
+                        'new_value' => $oldPartName . '→编号' . $newPartCode,
+                    ];
+                }
+            }
+
+            if (!empty($conflicts)) {
+                // 冲突序号集合,供左右列表高亮
+                $diffCodeMap = [];
+                foreach ($conflicts as $c) {
+                    $pc = isset($c['process_code']) ? trim((string)$c['process_code']) : '';
+                    if ($pc !== '') {
+                        $diffCodeMap[$pc] = true;
+                    }
+                }
+
+                $oldList = [];
+                foreach ($oldProcesses as $old) {
+                    $code = $normCode($old['process_code']);
+                    if ($code === '') {
+                        continue;
+                    }
+                    $oldList[] = [
+                        'process_code' => $code,
+                        'process_name' => trim((string)$old['process_name']),
+                        'part_name' => isset($old['part_name']) ? trim((string)$old['part_name']) : '',
+                        'diff' => isset($diffCodeMap[$code]),
+                    ];
+                }
+                usort($oldList, function ($a, $b) {
+                    return intval($a['process_code']) - intval($b['process_code']);
+                });
+
+                $newList = [];
+                foreach ($workOrderProcesses as $p) {
+                    $code = $normCode($p['process_code']);
+                    if ($code === '') {
+                        continue;
+                    }
+                    $newList[] = [
+                        'process_code' => $code,
+                        'process_name' => trim((string)$p['process_name']),
+                        'part_name' => trim((string)$p['part_name']),
+                        'diff' => isset($diffCodeMap[$code]),
+                    ];
+                }
+                usort($newList, function ($a, $b) {
+                    return intval($a['process_code']) - intval($b['process_code']);
+                });
+
+                // 约定:msg 短提示;data 含左右完整列表 + conflicts,供前端左右对比弹窗
+                $this->error(
+                    '禁止中间插入或重排序号',
+                    [
+                        'error_type' => 'import_append_only',
+                        'conflict_count' => count($conflicts),
+                        'suggest_min_new_code' => $oldMaxCode + 1,
+                        'old_list' => $oldList,
+                        'new_list' => $newList,
+                        'conflicts' => $conflicts,
+                    ]
+                );
+            }
+        }
+
         Db::startTrans();
         try {
             Db::name('工单_部件资料')->where('work_order', $params['workorder'])->delete();
@@ -915,9 +1169,6 @@ class WorkOrderProcess extends Api
                 }
             }
             Db::name('工单_基础工艺资料')->where('work_order', $params['workorder'])->delete();
-            if (empty($workOrderProcesses)) {
-                throw new \Exception('没有可导入的工序数据');
-            }
             $processInsertCount = Db::name('工单_基础工艺资料')->insertAll($workOrderProcesses);
             if ($processInsertCount === false || $processInsertCount === 0) {
                 throw new \Exception('工单工艺导入失败');
@@ -1873,7 +2124,7 @@ class WorkOrderProcess extends Api
                 a.sys_id as 小组,
                 SUM(a.number) as 数量, MIN(a.date) as 开工日期, MAX(a.date) as 完工日期,
                 SUM(a.standard_score) as 工分, SUM(a.production_score) as 工时, SUM(a.salary) as 工资')
-            ->group('a.process_code, a.process_name, a.staff_no, a.staff_name')
+            ->group('a.process_code, a.process_name, a.staff_no, a.staff_name, a.sys_id')
             ->select();
 
         // 按工序号、员工号数字排序