m0_70156489 před 3 týdny
rodič
revize
b5bd037ce2

+ 844 - 844
application/api/controller/Product.php

@@ -1,844 +1,844 @@
-<?php
-
-namespace app\api\controller;
-
-use app\common\controller\Api;
-use \think\Request;
-use \think\Db;
-use think\Cache;
-use function fast\e;
-
-/**
- * 产品资料
- */
-class Product extends Api
-{
-    protected $noNeedLogin = ['*'];
-    protected $noNeedRight = ['*'];
-
-    /**
-     * 首页
-     */
-    public function index()
-    {
-        $this->success('产品_基本资料');
-    }
-
-    /**
-     * 获取左侧菜单产品分类列表
-     * 按 product_type 分组 → 分类下显示对应产品
-     */
-    public function ProductTypeMenu()
-    {
-        if (Request::instance()->isGet() == false) {
-            $this->error('非法请求');
-        }
-        $params = Request::instance()->param();
-
-        // 获取所有产品
-        $productList = \db('产品_基本资料')->whereNull('mod_rq')
-            ->column('id,product_code,product_name,product_type');
-
-        $data = [];
-        if ($productList) {
-            foreach ($productList as $item) {
-                $type = $item['product_type'] ?: '未分类';
-                // 分类
-                $data[$type]['name'] = $type;
-                // 分类下的产品
-                $data[$type]['list'][] = [
-                    'id' => $item['id'],
-                    'product_code' => $item['product_code'],
-                    'product_name' => $item['product_name']
-                ];
-            }
-        }
-
-        // 转成索引数组返回
-        $result = array_values($data);
-
-        $this->success('获取成功', $result);
-    }
-
-    /**
-     * 获取产品资料列表(产品→部件→工艺)
-     */
-    public function ProductList()
-    {
-        if (!Request::instance()->isGet()) {
-            $this->error('非法请求');
-        }
-
-        $params = Request::instance()->param();
-
-        $where = [];
-        if (!empty($params['search'])) {
-            $where['product_code|product_name|product_type'] = ['like', '%' . $params['search'] . '%'];
-        }
-
-        $limit = empty($params['limit']) ? 30 : $params['limit'];
-        $pages = empty($params['page']) ? 1 : $params['page'];
-
-        // 1. 查询产品主表
-        $productList = db('产品_基本资料')
-            ->whereNull('mod_rq')
-            ->where($where)
-            ->page($pages, $limit)
-            ->order('id desc')
-            ->select();
-
-        if (empty($productList)) {
-            $this->success('获取成功', ['list' => [], 'count' => 0]);
-        }
-
-        // 提取产品编号
-        $productCodes = array_column($productList, 'product_code');
-
-        // 2. 查询部件(按 product_code 关联,并按 part_sort 排序)
-        $parts = db('产品_部件资料')
-            ->whereNull('mod_rq')
-            ->whereIn('product_code', $productCodes)
-            ->order('part_sort asc') // 按部件排序号升序
-            ->select();
-
-        // 提取 (product_code + part_sort) 作为关联键
-        $partKeys = [];
-        foreach ($parts as $p) {
-            $key = $p['product_code'] . '_' . $p['part_sort'];
-            $partKeys[] = $key;
-        }
-
-        // 3. 查询工艺(按 product_code + part_sort 关联,并按 gy_sort 排序)
-        $process = [];
-        if (!empty($productCodes)) {
-            $process = db('产品_工艺资料')
-                ->whereNull('mod_rq')
-                ->whereIn('product_code', $productCodes)
-                ->order('part_sort asc, gy_sort asc') // 按部件排序+工序排序
-                ->select();
-        }
-
-        // 部件按 product_code 分组
-        $partGroup = [];
-        foreach ($parts as $val) {
-            $partGroup[$val['product_code']][] = $val;
-        }
-
-        // 工艺按 (product_code + part_sort) 分组
-        $processGroup = [];
-        foreach ($process as $val) {
-            $key = $val['product_code'] . '_' . $val['part_sort'];
-            $processGroup[$key][] = $val;
-        }
-
-        // 组装数据:产品 → 部件 → 工艺
-        foreach ($productList as &$product) {
-            $currentParts = $partGroup[$product['product_code']] ?? [];
-            foreach ($currentParts as &$item) {
-                // 用 product_code + part_sort 关联工艺
-                $key = $product['product_code'] . '_' . $item['part_sort'];
-                $item['processes'] = $processGroup[$key] ?? [];
-            }
-            $product['parts'] = $currentParts;
-        }
-
-        // 统计总数
-        $count = db('产品_基本资料')
-            ->whereNull('mod_rq')
-            ->where($where)
-            ->count();
-
-        $this->success('获取成功', [
-            'list' => $productList,
-            'count' => $count
-        ]);
-    }
-
-    /**
-     * 新增产品资料
-     */
-    public function ProductAdd()
-    {
-        if (!Request::instance()->isPost()) {
-            $this->error('非法请求');
-        }
-
-        $params = Request::instance()->param();
-
-        // 自动生成产品编号:CP000001
-        $lastCode = \db('产品_基本资料')->order('id desc')->value('product_code');
-        if ($lastCode) {
-            $num = intval(str_replace('CP', '', $lastCode)) + 1;
-        } else {
-            $num = 1;
-        }
-        $params['product_code'] = 'CP' . str_pad($num, 6, '0', STR_PAD_LEFT);
-
-        $data = [];
-        $data['product_code'] = $params['product_code'];
-        $data['product_name'] = $params['product_name'];
-        $data['product_type'] = $params['product_type'];
-        $data['unit'] = $params['unit'];
-        $data['sys_id'] = $params['sys_id'];
-        $data['status'] = 1;
-        $data['sys_rq'] = date('Y-m-d H:i:s');
-
-        $result = \db('产品_基本资料')->insert($data);
-
-        if ($result) {
-            $this->success('新增成功');
-        } else {
-            $this->error('新增失败');
-        }
-    }
-
-    /**
-     * 修改产品资料
-     */
-    public function ProductEdit()
-    {
-        if (!Request::instance()->isPost()) {
-            $this->error('非法请求');
-        }
-        $params = Request::instance()->param();
-
-        if (empty($params['id'])) {
-            $this->error('请选择数据');
-        }
-
-        // 禁止修改编号
-        unset($params['product_code']);
-
-        $data = [];
-        $data['product_name'] = $params['product_name'];
-        $data['product_type'] = $params['product_type'];
-        $data['unit'] = $params['unit'];
-        $data['sys_id'] = $params['sys_id'];
-        $data['updatetime'] = date('Y-m-d H:i:s');
-
-        $result = \db('产品_基本资料')
-            ->where('id', $params['id'])
-            ->update($data);
-
-        if ($result !== false) {
-            $this->success('修改成功');
-        } else {
-            $this->error('修改失败');
-        }
-    }
-
-    /**
-     * 删除产品资料
-     */
-    public function ProductDelete()
-    {
-        if (!Request::instance()->isPost()) {
-            $this->error('非法请求');
-        }
-
-        $id = input('id');
-        if (empty($id)) {
-            $this->error('请选择需要删除的数据');
-        }
-
-        $ids = explode(',', $id);
-
-        $result = \db('产品_基本资料')
-            ->where('id', 'in', $ids)
-            ->update(['mod_rq' => date('Y-m-d H:i:s')]);
-
-        if ($result !== false) {
-            $this->success('删除成功');
-        } else {
-            $this->error('删除失败');
-        }
-    }
-
-
-    /**
-     * 获取产品部件列表
-     */
-    public function ProductPartList()
-    {
-        if (Request::instance()->isGet() == false) {
-            $this->error('非法请求');
-        }
-        $params = Request::instance()->param();
-
-        $where = [];
-
-        if (!empty($params['search'])) {
-            $where['part_name'] = array('like', '%' . $params['search'] . '%');
-        }
-        if (!empty($params['product_code'])) {
-            $where['product_code'] = array('like', '%' . $params['product_code'] . '%');
-        }
-        $limit = $params['limit'];
-        if (empty($limit)) {
-            $limit = 30;
-        }
-        $pages = $params['page'];
-        if (empty($pages)) {
-            $pages = 1;
-        }
-        $list = \db('产品_部件资料')->whereNull('mod_rq')->where($where)->page($pages)->limit($limit)->order('id desc')->select();
-        $count = \db('产品_部件资料')->whereNull('mod_rq')->where($where)->page($pages)->limit($limit)->count();
-
-        $this->success('获取成功', ['list' => $list, 'count' => $count]);
-    }
-
-
-    /**
-     * 新增部件资料(支持批量)
-     */
-    public function PartAdd()
-    {
-        if (!Request::instance()->isPost()) {
-            $this->error('非法请求');
-        }
-        $params = Request::instance()->param();
-
-        // 必传校验
-        if (empty($params['product_code']) || empty($params['part_list'])) {
-            $this->error('产品编号/部件列表不能为空');
-        }
-
-        $productCode = $params['product_code'];
-        $sysId = $params['sys_id'];
-        $partList = $params['part_list'];
-
-        // 组装批量数据
-        $insertData = [];
-        foreach ($partList as $item) {
-            if (empty($item['part_name']) || empty($item['part_sort'])) {
-                continue; // 跳过无效数据
-            }
-            $insertData[] = [
-                'product_code' => $productCode,
-                'part_sort' => $item['part_sort'],
-                'part_name' => $item['part_name'],
-                'sys_id' => $sysId,
-                'sys_rq' => date('Y-m-d H:i:s'),
-                'mod_rq' => null
-            ];
-        }
-
-        if (empty($insertData)) {
-            $this->error('无有效部件数据');
-        }
-        $res = db('产品_部件资料')->insertAll($insertData);
-        return $res ? $this->success('新增成功') : $this->error('新增失败');
-    }
-
-    /**
-     * 修改部件资料
-     */
-    public function PartEdit()
-    {
-        if (!Request::instance()->isPost()) {
-            $this->error('非法请求');
-        }
-        $params = Request::instance()->param();
-
-        // 必传校验
-        if (empty($params['id']) || empty($params['part_name'])) {
-            $this->error('ID/部件名称不能为空');
-        }
-
-        $data = [
-            'part_name' => $params['part_name'],
-            'sys_id' => $params['sys_id'],
-            'updatetime' => date('Y-m-d H:i:s')
-        ];
-
-        $res = db('产品_部件资料')
-            ->where('id', $params['id'])
-            ->whereNull('mod_rq')
-            ->update($data);
-
-        return $res !== false ? $this->success('修改成功') : $this->error('修改失败');
-    }
-
-    /**
-     * 删除部件资料
-     */
-    public function PartDelete()
-    {
-        if (!Request::instance()->isPost()) {
-            $this->error('非法请求');
-        }
-
-        $id = input('id');
-        if (empty($id)) {
-            $this->error('请选择需要删除的数据');
-        }
-
-        $ids = explode(',', $id);
-
-        $result = \db('产品_部件资料')
-            ->where('id', 'in', $ids)
-            ->update(['mod_rq' => date('Y-m-d H:i:s')]);
-
-        if ($result !== false) {
-            $this->success('删除成功');
-        } else {
-            $this->error('删除失败');
-        }
-    }
-
-    /**
-     * 获取产品工艺列表
-     */
-    public function ProductGyList()
-    {
-        if (!Request::instance()->isGet()) {
-            $this->error('非法请求');
-        }
-        $params = Request::instance()->param();
-
-        $where = [];
-        if (!empty($params['search'])) {
-            $where['a.gy_name'] = ['like', '%' . $params['search'] . '%'];
-        }
-
-        if (!empty($params['product_code'])) {
-            $where['a.product_code'] = $params['product_code'];
-        }
-
-        $limit = empty($params['limit']) ? 30 : $params['limit'];
-        $pages = empty($params['page']) ? 1 : $params['page'];
-
-        //获取产品_工艺数据
-        $list = \db('产品_工艺资料')->alias('a')
-            ->join('产品_部件资料 b', 'a.product_code = b.product_code AND a.part_sort = b.part_sort', 'LEFT')
-            ->field('
-            a.id,
-            a.product_code,
-            a.part_sort,
-            a.gy_sort,
-            a.gy_name,
-            a.big_process,
-            a.standard_hour,
-            a.standard_score,
-            a.difficulty_coef,
-            a.sys_id,
-            a.sys_rq,
-            a.updatetime,
-            b.part_name
-        ')
-            ->whereNull('a.mod_rq')
-            ->whereNull('b.mod_rq')
-            ->where($where)
-            ->page($pages, $limit)
-            ->order('a.part_sort asc, a.id asc')
-            ->select();
-
-        //统计总数量
-        $count = \db('产品_工艺资料')->alias('a')
-            ->join('产品_部件资料 b', 'a.product_code = b.product_code AND a.part_sort = b.part_sort', 'LEFT')
-            ->whereNull('a.mod_rq')
-            ->whereNull('b.mod_rq')
-            ->where($where)
-            ->count();
-
-        $this->success('获取成功', [
-            'list' => $list,
-            'count' => $count
-        ]);
-    }
-
-    /**
-     * 新增工艺资料(批量)
-     */
-    public function ProcessAdd()
-    {
-        if (!Request::instance()->isPost()) {
-            $this->error('非法请求');
-        }
-
-        $params = Request::instance()->param();
-
-        $sys_id = $params['sys_id'];
-        $process_list = $params['process_list'] ?? [];
-
-        if (empty($process_list)) {
-            $this->error('工艺数据不能为空');
-        }
-
-        $insertData = [];
-        foreach ($process_list as $item) {
-            // 必传校验
-            if (empty($item['gy_name']) || empty($item['big_process'])) {
-                continue;
-            }
-
-            $insertData[] = [
-                'product_code' => $item['product_code'],
-                'part_sort' => $item['part_sort'],
-                'part_name' => $item['part_name'],
-                'gy_sort' => $item['gy_sort'],
-                'gy_name' => $item['gy_name'],
-                'big_process' => $item['big_process'],
-                'standard_hour' => $item['standard_hour'] ?? 0,
-                'standard_score' => $item['standard_score'] ?? 0,
-                'difficulty_coef' => $item['difficulty_coef'] ?? 1,
-                'sys_id' => $sys_id,
-                'sys_rq' => date('Y-m-d H:i:s')
-            ];
-        }
-
-        if (empty($insertData)) {
-            $this->error('无有效工艺数据');
-        }
-
-        $result = db('产品_工艺资料')->insertAll($insertData);
-        return $result ? $this->success('新增成功') : $this->error('新增失败');
-    }
-
-    /**
-     * 修改工艺资料
-     */
-    public function ProcessEdit()
-    {
-        if (!Request::instance()->isPost()) {
-            $this->error('非法请求');
-        }
-
-        $params = Request::instance()->param();
-//echo "<pre>";
-//print_r($params);
-//echo "<pre>";die;
-        if (empty($params['id']) || empty($params['gy_name'])) {
-            $this->error('ID/工序名称不能为空');
-        }
-
-        $data = [
-            'gy_name' => $params['gy_name'],
-            'big_process' => $params['big_process'],
-//            'part_sort' => $params['part_sort'],
-            'gy_sort' => $params['gy_sort'],
-            'standard_hour' => $params['standard_hour'] ?? 0,
-            'standard_score' => $params['standard_score'] ?? 0,
-            'difficulty_coef' => $params['difficulty_coef'] ?? 1,
-            'sys_id' => $params['sys_id'],
-            'updatetime' => date('Y-m-d H:i:s')
-        ];
-
-        $res = db('产品_工艺资料')
-            ->where('id', $params['id'])
-            ->whereNull('mod_rq')
-            ->update($data);
-
-        return $res !== false ? $this->success('修改成功') : $this->error('修改失败');
-    }
-
-    /**
-     * 删除工艺资料
-     */
-    public function ProcessDelete()
-    {
-        if (!Request::instance()->isPost()) {
-            $this->error('非法请求');
-        }
-
-        $id = input('id');
-        if (empty($id)) {
-            $this->error('请选择需要删除的数据');
-        }
-
-        $ids = explode(',', $id);
-
-        $result = \db('产品_工艺资料')
-            ->where('id', 'in', $ids)
-            ->update(['mod_rq' => date('Y-m-d H:i:s')]);
-
-        if ($result !== false) {
-            $this->success('删除成功');
-        } else {
-            $this->error('删除失败');
-        }
-    }
-
-    /**
-     * 调整部件与工艺序号牌组
-     */
-    public function UpdataSort(){
-        if (!Request::instance()->isPost()) {
-            $this->error('非法请求');
-        }
-
-        $params = Request::instance()->param();
-
-        $product_code = $params['product_code'];
-        $sys_id       = $params['sys_id'];
-
-        if (empty($product_code)) {
-            $this->error('产品编号不能为空');
-        }
-
-        // ======================
-        // 更新 产品_部件资料
-        // ======================
-        if (!empty($params['part_list']) && is_array($params['part_list'])) {
-            foreach ($params['part_list'] as $item) {
-                $id = $item['id'] ?? 0;
-                if ($id <= 0) continue;
-
-                // 🔥 把要更新的内容放变量里
-                $updateData = [
-                    'part_sort' => $item['part_sort'],
-                    'part_name' => $item['part_name'],
-                    'sys_id'  => $sys_id
-                ];
-
-                Db::name('产品_部件资料')
-                    ->where('id', $id)
-                    ->update($updateData);
-            }
-        }
-
-        // ======================
-        // 更新 产品_工艺资料
-        // ======================
-        if (!empty($params['process_list']) && is_array($params['process_list'])) {
-            foreach ($params['process_list'] as $item) {
-                $id = $item['id'] ?? 0;
-                if ($id <= 0) continue;
-
-                // 🔥 把要更新的内容放变量里
-                $updateData = [
-                    'gy_sort'        => $item['gy_sort'],
-                    'gy_name'        => $item['gy_name'] ,
-                    'big_process'    => $item['big_process'],
-                    'standard_hour'  => $item['standard_hour'],
-                    'standard_score' => $item['standard_score'],
-                    'part_sort'      => $item['part_sort'],
-                    'part_name'      => $item['part_name'],
-                    'sys_id'       => $sys_id
-                ];
-
-                Db::name('产品_工艺资料')
-                    ->where('id', $id)
-                    ->update($updateData);
-            }
-        }
-
-        $this->success('排序更新成功');
-    }
-
-    /**
-     * Excel 导入 部件 + 工艺
-     */
-    public function ProcessExcel(){
-        if (!Request::instance()->isPost()) {
-            $this->error('非法请求');
-        }
-
-        $params = Request::instance()->param();
-        $file = request()->file('file');
-
-        // 1. 先判断文件是否存在
-        if (empty($file)) {
-            $this->error('请上传Excel文件');
-        }
-
-        // 2. 必传参数校验
-        $product_code = $params['product_code'];
-        $sys_id = $params['sys_id'];
-
-        if (empty($product_code)) {
-            $this->error('请选择产品后,再进行提交');
-        }
-
-        // 3. 正确获取文件扩展名(解决 getExtension 为空的问题)
-        $fileInfo = $file->getInfo();
-        $fileName = $fileInfo['name'];
-        $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
-
-        if (!in_array($ext, ['xlsx', 'xls'])) {
-            $this->error('只支持 xls / xlsx 格式');
-        }
-
-        $file_path = $file->getRealPath();
-        $data = [];
-
-        if ($ext == 'xlsx') {
-            $data = $this->readXlsx($file_path);
-        } elseif ($ext == 'xls') {
-            $data = $this->readXls($file_path);
-        }
-
-        // 4. 解析数据
-        $rows = array_slice($data, 1);
-        $insert_data = [];
-        foreach ($rows as $row) {
-            $part_name = trim($row[0] ?? '');
-            $gy_name = trim($row[1] ?? '');
-            if (empty($part_name) || empty($gy_name)) continue;
-
-            $insert_data[] = [
-                'part_name'        => $part_name,
-                'gy_name'          => $gy_name,
-                'big_process'      => trim($row[2] ?? ''),
-                'standard_hour'    => floatval($row[3] ?? 0),
-                'standard_score'   => floatval($row[4] ?? 0),
-                'difficulty_coef'  => floatval($row[5] ?? 1),
-            ];
-        }
-
-        if (empty($insert_data)) {
-            $this->error('Excel 无有效数据');
-        }
-
-        // 5. 处理部件
-        $part_map = [];
-        $part_sort = 1;
-        foreach ($insert_data as $item) {
-            $name = $item['part_name'];
-            if (!isset($part_map[$name])) {
-                $part_map[$name] = $part_sort++;
-            }
-        }
-
-        $part_all = [];
-        foreach ($part_map as $name => $sort) {
-            $part_all[] = [
-                'product_code' => $product_code,
-                'part_sort'    => $sort,
-                'part_name'    => $name,
-                'sys_id'       => $sys_id,
-                'sys_rq'   => date('Y-m-d H:i:s'),
-                'mod_rq'       => null
-            ];
-        }
-
-        // 6. 处理工艺
-        $gy_sort_map = [];
-        $process_all = [];
-        foreach ($insert_data as $item) {
-            $part_sort = $part_map[$item['part_name']];
-            $gy_sort_map[$part_sort] = isset($gy_sort_map[$part_sort]) ? $gy_sort_map[$part_sort] + 1 : 1;
-
-            $process_all[] = [
-                'product_code'    => $product_code,
-                'part_sort'       => $part_sort,
-                'part_name'       => $item['part_name'],
-                'gy_sort'         => $gy_sort_map[$part_sort],
-                'gy_name'         => $item['gy_name'],
-                'big_process'     => $item['big_process'],
-                'standard_hour'   => $item['standard_hour'],
-                'standard_score'  => $item['standard_score'],
-                'difficulty_coef' => $item['difficulty_coef'],
-                'sys_id'          => $sys_id,
-                'sys_rq'      => date('Y-m-d H:i:s'),
-                'mod_rq'       => null
-            ];
-        }
-
-echo "<pre>";
-print_r($part_all);
-echo "<pre>";die;
-        // 7. 写入数据库(你可以按需注释)
-        db('产品_部件资料')->where('product_code', $product_code)->update(['mod_rq' => date('Y-m-d H:i:s')]);
-        db('产品_部件资料')->insertAll($part_all);
-
-        db('产品_工艺资料')->where('product_code', $product_code)->update(['mod_rq' => date('Y-m-d H:i:s')]);
-        db('产品_工艺资料')->insertAll($process_all);
-
-        $this->success('导入成功', [
-            '部件数量' => count($part_all),
-            '工艺数量' => count($process_all)
-        ]);
-    }
-
-    private function readXlsx($file)
-    {
-        if (!class_exists('ZipArchive')) {
-            return [];
-        }
-
-        $zip = new \ZipArchive();
-        if ($zip->open($file) !== true) {
-            return [];
-        }
-
-        // 读取工作表内容和共享字符串
-        $content = $zip->getFromName('xl/worksheets/sheet1.xml');
-        $strings = $zip->getFromName('xl/sharedStrings.xml');
-        $zip->close();
-
-        // 解析共享字符串
-        $str_map = [];
-        if ($strings) {
-            preg_match_all('/<si><t[^>]*>([^<]+)<\/t><\/si>/s', $strings, $matches);
-            $str_map = $matches[1] ?? [];
-        }
-
-        $rows = [];
-        // 解析每一行
-        preg_match_all('/<row[^>]*>(.*?)<\/row>/s', $content, $row_matches);
-        foreach ($row_matches[1] as $row_xml) {
-            $cols = array_fill(0, 6, ''); // 固定6列,防止错位
-            $cellIndex = 0;
-
-            // 解析单元格
-            preg_match_all('/<c r="[^"]+"(?: t="([^"]+)")?[^>]*>(.*?)<\/c>/s', $row_xml, $cell_matches);
-            foreach ($cell_matches[0] as $i => $cell_xml) {
-                if ($cellIndex >= 6) break; // 只取前6列
-
-                $cell_type = $cell_matches[1][$i] ?? '';
-                $cell_inner = $cell_matches[2][$i] ?? '';
-                $value = '';
-
-                // 读取单元格值
-                if (preg_match('/<v>([^<]+)<\/v>/', $cell_inner, $v_match)) {
-                    $value = $v_match[1];
-                } elseif (preg_match('/<t>([^<]+)<\/t>/', $cell_inner, $t_match)) {
-                    $value = $t_match[1];
-                }
-
-                // 处理共享字符串
-                if ($cell_type === 's' && isset($str_map[$value])) {
-                    $value = $str_map[$value];
-                }
-
-                $cols[$cellIndex] = $value;
-                $cellIndex++;
-            }
-
-            $rows[] = $cols;
-        }
-
-        return $rows;
-    }
-
-    private function readXls($file)
-    {
-        $data = file_get_contents($file);
-        $rows = [];
-        $len = strlen($data);
-        $p = 0;
-
-        while ($p < $len) {
-            $id = ord($data[$p]);
-            $size = unpack('v', substr($data, $p + 1, 2))[1];
-            if ($id == 0x20) {
-                $row = [];
-                $sp = $p + 4;
-                while ($sp < $p + $size) {
-                    $type = ord($data[$sp]);
-                    if ($type == 0x02) {
-                        $val = substr($data, $sp + 3, 1);
-                        $row[] = trim($val);
-                        $sp += 8;
-                    } else {
-                        $sp++;
-                    }
-                }
-                if (!empty($row)) $rows[] = $row;
-            }
-            $p += $size;
-        }
-        return $rows;
-    }
-}
+<?php
+
+namespace app\api\controller;
+
+use app\common\controller\Api;
+use \think\Request;
+use \think\Db;
+use think\Cache;
+use function fast\e;
+
+/**
+ * 产品资料
+ */
+class Product extends Api
+{
+    protected $noNeedLogin = ['*'];
+    protected $noNeedRight = ['*'];
+
+    /**
+     * 首页
+     */
+    public function index()
+    {
+        $this->success('产品_基本资料');
+    }
+
+    /**
+     * 获取左侧菜单产品分类列表
+     * 按 product_type 分组 → 分类下显示对应产品
+     */
+    public function ProductTypeMenu()
+    {
+        if (Request::instance()->isGet() == false) {
+            $this->error('非法请求');
+        }
+        $params = Request::instance()->param();
+
+        // 获取所有产品
+        $productList = \db('产品_基本资料')->whereNull('mod_rq')
+            ->column('id,product_code,product_name,product_type');
+
+        $data = [];
+        if ($productList) {
+            foreach ($productList as $item) {
+                $type = $item['product_type'] ?: '未分类';
+                // 分类
+                $data[$type]['name'] = $type;
+                // 分类下的产品
+                $data[$type]['list'][] = [
+                    'id' => $item['id'],
+                    'product_code' => $item['product_code'],
+                    'product_name' => $item['product_name']
+                ];
+            }
+        }
+
+        // 转成索引数组返回
+        $result = array_values($data);
+
+        $this->success('获取成功', $result);
+    }
+
+    /**
+     * 获取产品资料列表(产品→部件→工艺)
+     */
+    public function ProductList()
+    {
+        if (!Request::instance()->isGet()) {
+            $this->error('非法请求');
+        }
+
+        $params = Request::instance()->param();
+
+        $where = [];
+        if (!empty($params['search'])) {
+            $where['product_code|product_name|product_type'] = ['like', '%' . $params['search'] . '%'];
+        }
+
+        $limit = empty($params['limit']) ? 30 : $params['limit'];
+        $pages = empty($params['page']) ? 1 : $params['page'];
+
+        // 1. 查询产品主表
+        $productList = db('产品_基本资料')
+            ->whereNull('mod_rq')
+            ->where($where)
+            ->page($pages, $limit)
+            ->order('id desc')
+            ->select();
+
+        if (empty($productList)) {
+            $this->success('获取成功', ['list' => [], 'count' => 0]);
+        }
+
+        // 提取产品编号
+        $productCodes = array_column($productList, 'product_code');
+
+        // 2. 查询部件(按 product_code 关联,并按 part_sort 排序)
+        $parts = db('产品_部件资料')
+            ->whereNull('mod_rq')
+            ->whereIn('product_code', $productCodes)
+            ->order('part_sort asc') // 按部件排序号升序
+            ->select();
+
+        // 提取 (product_code + part_sort) 作为关联键
+        $partKeys = [];
+        foreach ($parts as $p) {
+            $key = $p['product_code'] . '_' . $p['part_sort'];
+            $partKeys[] = $key;
+        }
+
+        // 3. 查询工艺(按 product_code + part_sort 关联,并按 gy_sort 排序)
+        $process = [];
+        if (!empty($productCodes)) {
+            $process = db('产品_工艺资料')
+                ->whereNull('mod_rq')
+                ->whereIn('product_code', $productCodes)
+                ->order('part_sort asc, gy_sort asc') // 按部件排序+工序排序
+                ->select();
+        }
+
+        // 部件按 product_code 分组
+        $partGroup = [];
+        foreach ($parts as $val) {
+            $partGroup[$val['product_code']][] = $val;
+        }
+
+        // 工艺按 (product_code + part_sort) 分组
+        $processGroup = [];
+        foreach ($process as $val) {
+            $key = $val['product_code'] . '_' . $val['part_sort'];
+            $processGroup[$key][] = $val;
+        }
+
+        // 组装数据:产品 → 部件 → 工艺
+        foreach ($productList as &$product) {
+            $currentParts = $partGroup[$product['product_code']] ?? [];
+            foreach ($currentParts as &$item) {
+                // 用 product_code + part_sort 关联工艺
+                $key = $product['product_code'] . '_' . $item['part_sort'];
+                $item['processes'] = $processGroup[$key] ?? [];
+            }
+            $product['parts'] = $currentParts;
+        }
+
+        // 统计总数
+        $count = db('产品_基本资料')
+            ->whereNull('mod_rq')
+            ->where($where)
+            ->count();
+
+        $this->success('获取成功', [
+            'list' => $productList,
+            'count' => $count
+        ]);
+    }
+
+    /**
+     * 新增产品资料
+     */
+    public function ProductAdd()
+    {
+        if (!Request::instance()->isPost()) {
+            $this->error('非法请求');
+        }
+
+        $params = Request::instance()->param();
+
+        // 自动生成产品编号:CP000001
+        $lastCode = \db('产品_基本资料')->order('id desc')->value('product_code');
+        if ($lastCode) {
+            $num = intval(str_replace('CP', '', $lastCode)) + 1;
+        } else {
+            $num = 1;
+        }
+        $params['product_code'] = 'CP' . str_pad($num, 6, '0', STR_PAD_LEFT);
+
+        $data = [];
+        $data['product_code'] = $params['product_code'];
+        $data['product_name'] = $params['product_name'];
+        $data['product_type'] = $params['product_type'];
+        $data['unit'] = $params['unit'];
+        $data['sys_id'] = $params['sys_id'];
+        $data['status'] = 1;
+        $data['sys_rq'] = date('Y-m-d H:i:s');
+
+        $result = \db('产品_基本资料')->insert($data);
+
+        if ($result) {
+            $this->success('新增成功');
+        } else {
+            $this->error('新增失败');
+        }
+    }
+
+    /**
+     * 修改产品资料
+     */
+    public function ProductEdit()
+    {
+        if (!Request::instance()->isPost()) {
+            $this->error('非法请求');
+        }
+        $params = Request::instance()->param();
+
+        if (empty($params['id'])) {
+            $this->error('请选择数据');
+        }
+
+        // 禁止修改编号
+        unset($params['product_code']);
+
+        $data = [];
+        $data['product_name'] = $params['product_name'];
+        $data['product_type'] = $params['product_type'];
+        $data['unit'] = $params['unit'];
+        $data['sys_id'] = $params['sys_id'];
+        $data['updatetime'] = date('Y-m-d H:i:s');
+
+        $result = \db('产品_基本资料')
+            ->where('id', $params['id'])
+            ->update($data);
+
+        if ($result !== false) {
+            $this->success('修改成功');
+        } else {
+            $this->error('修改失败');
+        }
+    }
+
+    /**
+     * 删除产品资料
+     */
+    public function ProductDelete()
+    {
+        if (!Request::instance()->isPost()) {
+            $this->error('非法请求');
+        }
+
+        $id = input('id');
+        if (empty($id)) {
+            $this->error('请选择需要删除的数据');
+        }
+
+        $ids = explode(',', $id);
+
+        $result = \db('产品_基本资料')
+            ->where('id', 'in', $ids)
+            ->update(['mod_rq' => date('Y-m-d H:i:s')]);
+
+        if ($result !== false) {
+            $this->success('删除成功');
+        } else {
+            $this->error('删除失败');
+        }
+    }
+
+
+    /**
+     * 获取产品部件列表
+     */
+    public function ProductPartList()
+    {
+        if (Request::instance()->isGet() == false) {
+            $this->error('非法请求');
+        }
+        $params = Request::instance()->param();
+
+        $where = [];
+
+        if (!empty($params['search'])) {
+            $where['part_name'] = array('like', '%' . $params['search'] . '%');
+        }
+        if (!empty($params['product_code'])) {
+            $where['product_code'] = array('like', '%' . $params['product_code'] . '%');
+        }
+        $limit = $params['limit'];
+        if (empty($limit)) {
+            $limit = 30;
+        }
+        $pages = $params['page'];
+        if (empty($pages)) {
+            $pages = 1;
+        }
+        $list = \db('产品_部件资料')->whereNull('mod_rq')->where($where)->page($pages)->limit($limit)->order('id desc')->select();
+        $count = \db('产品_部件资料')->whereNull('mod_rq')->where($where)->page($pages)->limit($limit)->count();
+
+        $this->success('获取成功', ['list' => $list, 'count' => $count]);
+    }
+
+
+    /**
+     * 新增部件资料(支持批量)
+     */
+    public function PartAdd()
+    {
+        if (!Request::instance()->isPost()) {
+            $this->error('非法请求');
+        }
+        $params = Request::instance()->param();
+
+        // 必传校验
+        if (empty($params['product_code']) || empty($params['part_list'])) {
+            $this->error('产品编号/部件列表不能为空');
+        }
+
+        $productCode = $params['product_code'];
+        $sysId = $params['sys_id'];
+        $partList = $params['part_list'];
+
+        // 组装批量数据
+        $insertData = [];
+        foreach ($partList as $item) {
+            if (empty($item['part_name']) || empty($item['part_sort'])) {
+                continue; // 跳过无效数据
+            }
+            $insertData[] = [
+                'product_code' => $productCode,
+                'part_sort' => $item['part_sort'],
+                'part_name' => $item['part_name'],
+                'sys_id' => $sysId,
+                'sys_rq' => date('Y-m-d H:i:s'),
+                'mod_rq' => null
+            ];
+        }
+
+        if (empty($insertData)) {
+            $this->error('无有效部件数据');
+        }
+        $res = db('产品_部件资料')->insertAll($insertData);
+        return $res ? $this->success('新增成功') : $this->error('新增失败');
+    }
+
+    /**
+     * 修改部件资料
+     */
+    public function PartEdit()
+    {
+        if (!Request::instance()->isPost()) {
+            $this->error('非法请求');
+        }
+        $params = Request::instance()->param();
+
+        // 必传校验
+        if (empty($params['id']) || empty($params['part_name'])) {
+            $this->error('ID/部件名称不能为空');
+        }
+
+        $data = [
+            'part_name' => $params['part_name'],
+            'sys_id' => $params['sys_id'],
+            'updatetime' => date('Y-m-d H:i:s')
+        ];
+
+        $res = db('产品_部件资料')
+            ->where('id', $params['id'])
+            ->whereNull('mod_rq')
+            ->update($data);
+
+        return $res !== false ? $this->success('修改成功') : $this->error('修改失败');
+    }
+
+    /**
+     * 删除部件资料
+     */
+    public function PartDelete()
+    {
+        if (!Request::instance()->isPost()) {
+            $this->error('非法请求');
+        }
+
+        $id = input('id');
+        if (empty($id)) {
+            $this->error('请选择需要删除的数据');
+        }
+
+        $ids = explode(',', $id);
+
+        $result = \db('产品_部件资料')
+            ->where('id', 'in', $ids)
+            ->update(['mod_rq' => date('Y-m-d H:i:s')]);
+
+        if ($result !== false) {
+            $this->success('删除成功');
+        } else {
+            $this->error('删除失败');
+        }
+    }
+
+    /**
+     * 获取产品工艺列表
+     */
+    public function ProductGyList()
+    {
+        if (!Request::instance()->isGet()) {
+            $this->error('非法请求');
+        }
+        $params = Request::instance()->param();
+
+        $where = [];
+        if (!empty($params['search'])) {
+            $where['a.gy_name'] = ['like', '%' . $params['search'] . '%'];
+        }
+
+        if (!empty($params['product_code'])) {
+            $where['a.product_code'] = $params['product_code'];
+        }
+
+        $limit = empty($params['limit']) ? 30 : $params['limit'];
+        $pages = empty($params['page']) ? 1 : $params['page'];
+
+        //获取产品_工艺数据
+        $list = \db('产品_工艺资料')->alias('a')
+            ->join('产品_部件资料 b', 'a.product_code = b.product_code AND a.part_sort = b.part_sort', 'LEFT')
+            ->field('
+            a.id,
+            a.product_code,
+            a.part_sort,
+            a.gy_sort,
+            a.gy_name,
+            a.big_process,
+            a.standard_hour,
+            a.standard_score,
+            a.difficulty_coef,
+            a.sys_id,
+            a.sys_rq,
+            a.updatetime,
+            b.part_name
+        ')
+            ->whereNull('a.mod_rq')
+            ->whereNull('b.mod_rq')
+            ->where($where)
+            ->page($pages, $limit)
+            ->order('a.part_sort asc, a.id asc')
+            ->select();
+
+        //统计总数量
+        $count = \db('产品_工艺资料')->alias('a')
+            ->join('产品_部件资料 b', 'a.product_code = b.product_code AND a.part_sort = b.part_sort', 'LEFT')
+            ->whereNull('a.mod_rq')
+            ->whereNull('b.mod_rq')
+            ->where($where)
+            ->count();
+
+        $this->success('获取成功', [
+            'list' => $list,
+            'count' => $count
+        ]);
+    }
+
+    /**
+     * 新增工艺资料(批量)
+     */
+    public function ProcessAdd()
+    {
+        if (!Request::instance()->isPost()) {
+            $this->error('非法请求');
+        }
+
+        $params = Request::instance()->param();
+
+        $sys_id = $params['sys_id'];
+        $process_list = $params['process_list'] ?? [];
+
+        if (empty($process_list)) {
+            $this->error('工艺数据不能为空');
+        }
+
+        $insertData = [];
+        foreach ($process_list as $item) {
+            // 必传校验
+            if (empty($item['gy_name']) || empty($item['big_process'])) {
+                continue;
+            }
+
+            $insertData[] = [
+                'product_code' => $item['product_code'],
+                'part_sort' => $item['part_sort'],
+                'part_name' => $item['part_name'],
+                'gy_sort' => $item['gy_sort'],
+                'gy_name' => $item['gy_name'],
+                'big_process' => $item['big_process'],
+                'standard_hour' => $item['standard_hour'] ?? 0,
+                'standard_score' => $item['standard_score'] ?? 0,
+                'difficulty_coef' => $item['difficulty_coef'] ?? 1,
+                'sys_id' => $sys_id,
+                'sys_rq' => date('Y-m-d H:i:s')
+            ];
+        }
+
+        if (empty($insertData)) {
+            $this->error('无有效工艺数据');
+        }
+
+        $result = db('产品_工艺资料')->insertAll($insertData);
+        return $result ? $this->success('新增成功') : $this->error('新增失败');
+    }
+
+    /**
+     * 修改工艺资料
+     */
+    public function ProcessEdit()
+    {
+        if (!Request::instance()->isPost()) {
+            $this->error('非法请求');
+        }
+
+        $params = Request::instance()->param();
+//echo "<pre>";
+//print_r($params);
+//echo "<pre>";die;
+        if (empty($params['id']) || empty($params['gy_name'])) {
+            $this->error('ID/工序名称不能为空');
+        }
+
+        $data = [
+            'gy_name' => $params['gy_name'],
+            'big_process' => $params['big_process'],
+//            'part_sort' => $params['part_sort'],
+            'gy_sort' => $params['gy_sort'],
+            'standard_hour' => $params['standard_hour'] ?? 0,
+            'standard_score' => $params['standard_score'] ?? 0,
+            'difficulty_coef' => $params['difficulty_coef'] ?? 1,
+            'sys_id' => $params['sys_id'],
+            'updatetime' => date('Y-m-d H:i:s')
+        ];
+
+        $res = db('产品_工艺资料')
+            ->where('id', $params['id'])
+            ->whereNull('mod_rq')
+            ->update($data);
+
+        return $res !== false ? $this->success('修改成功') : $this->error('修改失败');
+    }
+
+    /**
+     * 删除工艺资料
+     */
+    public function ProcessDelete()
+    {
+        if (!Request::instance()->isPost()) {
+            $this->error('非法请求');
+        }
+
+        $id = input('id');
+        if (empty($id)) {
+            $this->error('请选择需要删除的数据');
+        }
+
+        $ids = explode(',', $id);
+
+        $result = \db('产品_工艺资料')
+            ->where('id', 'in', $ids)
+            ->update(['mod_rq' => date('Y-m-d H:i:s')]);
+
+        if ($result !== false) {
+            $this->success('删除成功');
+        } else {
+            $this->error('删除失败');
+        }
+    }
+
+    /**
+     * 调整部件与工艺序号牌组
+     */
+    public function UpdataSort(){
+        if (!Request::instance()->isPost()) {
+            $this->error('非法请求');
+        }
+
+        $params = Request::instance()->param();
+
+        $product_code = $params['product_code'];
+        $sys_id       = $params['sys_id'];
+
+        if (empty($product_code)) {
+            $this->error('产品编号不能为空');
+        }
+
+        // ======================
+        // 更新 产品_部件资料
+        // ======================
+        if (!empty($params['part_list']) && is_array($params['part_list'])) {
+            foreach ($params['part_list'] as $item) {
+                $id = $item['id'] ?? 0;
+                if ($id <= 0) continue;
+
+                // 🔥 把要更新的内容放变量里
+                $updateData = [
+                    'part_sort' => $item['part_sort'],
+                    'part_name' => $item['part_name'],
+                    'sys_id'  => $sys_id
+                ];
+
+                Db::name('产品_部件资料')
+                    ->where('id', $id)
+                    ->update($updateData);
+            }
+        }
+
+        // ======================
+        // 更新 产品_工艺资料
+        // ======================
+        if (!empty($params['process_list']) && is_array($params['process_list'])) {
+            foreach ($params['process_list'] as $item) {
+                $id = $item['id'] ?? 0;
+                if ($id <= 0) continue;
+
+                // 🔥 把要更新的内容放变量里
+                $updateData = [
+                    'gy_sort'        => $item['gy_sort'],
+                    'gy_name'        => $item['gy_name'] ,
+                    'big_process'    => $item['big_process'],
+                    'standard_hour'  => $item['standard_hour'],
+                    'standard_score' => $item['standard_score'],
+                    'part_sort'      => $item['part_sort'],
+                    'part_name'      => $item['part_name'],
+                    'sys_id'       => $sys_id
+                ];
+
+                Db::name('产品_工艺资料')
+                    ->where('id', $id)
+                    ->update($updateData);
+            }
+        }
+
+        $this->success('排序更新成功');
+    }
+
+    /**
+     * Excel 导入 部件 + 工艺
+     */
+    public function ProcessExcel(){
+        if (!Request::instance()->isPost()) {
+            $this->error('非法请求');
+        }
+
+        $params = Request::instance()->param();
+        $file = request()->file('file');
+
+        // 1. 先判断文件是否存在
+        if (empty($file)) {
+            $this->error('请上传Excel文件');
+        }
+
+        // 2. 必传参数校验
+        $product_code = $params['product_code'];
+        $sys_id = $params['sys_id'];
+
+        if (empty($product_code)) {
+            $this->error('请选择产品后,再进行提交');
+        }
+
+        // 3. 正确获取文件扩展名(解决 getExtension 为空的问题)
+        $fileInfo = $file->getInfo();
+        $fileName = $fileInfo['name'];
+        $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
+
+        if (!in_array($ext, ['xlsx', 'xls'])) {
+            $this->error('只支持 xls / xlsx 格式');
+        }
+
+        $file_path = $file->getRealPath();
+        $data = [];
+
+        if ($ext == 'xlsx') {
+            $data = $this->readXlsx($file_path);
+        } elseif ($ext == 'xls') {
+            $data = $this->readXls($file_path);
+        }
+
+        // 4. 解析数据
+        $rows = array_slice($data, 1);
+        $insert_data = [];
+        foreach ($rows as $row) {
+            $part_name = trim($row[0] ?? '');
+            $gy_name = trim($row[1] ?? '');
+            if (empty($part_name) || empty($gy_name)) continue;
+
+            $insert_data[] = [
+                'part_name'        => $part_name,
+                'gy_name'          => $gy_name,
+                'big_process'      => trim($row[2] ?? ''),
+                'standard_hour'    => floatval($row[3] ?? 0),
+                'standard_score'   => floatval($row[4] ?? 0),
+                'difficulty_coef'  => floatval($row[5] ?? 1),
+            ];
+        }
+
+        if (empty($insert_data)) {
+            $this->error('Excel 无有效数据');
+        }
+
+        // 5. 处理部件
+        $part_map = [];
+        $part_sort = 1;
+        foreach ($insert_data as $item) {
+            $name = $item['part_name'];
+            if (!isset($part_map[$name])) {
+                $part_map[$name] = $part_sort++;
+            }
+        }
+
+        $part_all = [];
+        foreach ($part_map as $name => $sort) {
+            $part_all[] = [
+                'product_code' => $product_code,
+                'part_sort'    => $sort,
+                'part_name'    => $name,
+                'sys_id'       => $sys_id,
+                'sys_rq'   => date('Y-m-d H:i:s'),
+                'mod_rq'       => null
+            ];
+        }
+
+        // 6. 处理工艺
+        $gy_sort_map = [];
+        $process_all = [];
+        foreach ($insert_data as $item) {
+            $part_sort = $part_map[$item['part_name']];
+            $gy_sort_map[$part_sort] = isset($gy_sort_map[$part_sort]) ? $gy_sort_map[$part_sort] + 1 : 1;
+
+            $process_all[] = [
+                'product_code'    => $product_code,
+                'part_sort'       => $part_sort,
+                'part_name'       => $item['part_name'],
+                'gy_sort'         => $gy_sort_map[$part_sort],
+                'gy_name'         => $item['gy_name'],
+                'big_process'     => $item['big_process'],
+                'standard_hour'   => $item['standard_hour'],
+                'standard_score'  => $item['standard_score'],
+                'difficulty_coef' => $item['difficulty_coef'],
+                'sys_id'          => $sys_id,
+                'sys_rq'      => date('Y-m-d H:i:s'),
+                'mod_rq'       => null
+            ];
+        }
+
+echo "<pre>";
+print_r($part_all);
+echo "<pre>";die;
+        // 7. 写入数据库(你可以按需注释)
+        db('产品_部件资料')->where('product_code', $product_code)->update(['mod_rq' => date('Y-m-d H:i:s')]);
+        db('产品_部件资料')->insertAll($part_all);
+
+        db('产品_工艺资料')->where('product_code', $product_code)->update(['mod_rq' => date('Y-m-d H:i:s')]);
+        db('产品_工艺资料')->insertAll($process_all);
+
+        $this->success('导入成功', [
+            '部件数量' => count($part_all),
+            '工艺数量' => count($process_all)
+        ]);
+    }
+
+    private function readXlsx($file)
+    {
+        if (!class_exists('ZipArchive')) {
+            return [];
+        }
+
+        $zip = new \ZipArchive();
+        if ($zip->open($file) !== true) {
+            return [];
+        }
+
+        // 读取工作表内容和共享字符串
+        $content = $zip->getFromName('xl/worksheets/sheet1.xml');
+        $strings = $zip->getFromName('xl/sharedStrings.xml');
+        $zip->close();
+
+        // 解析共享字符串
+        $str_map = [];
+        if ($strings) {
+            preg_match_all('/<si><t[^>]*>([^<]+)<\/t><\/si>/s', $strings, $matches);
+            $str_map = $matches[1] ?? [];
+        }
+
+        $rows = [];
+        // 解析每一行
+        preg_match_all('/<row[^>]*>(.*?)<\/row>/s', $content, $row_matches);
+        foreach ($row_matches[1] as $row_xml) {
+            $cols = array_fill(0, 6, ''); // 固定6列,防止错位
+            $cellIndex = 0;
+
+            // 解析单元格
+            preg_match_all('/<c r="[^"]+"(?: t="([^"]+)")?[^>]*>(.*?)<\/c>/s', $row_xml, $cell_matches);
+            foreach ($cell_matches[0] as $i => $cell_xml) {
+                if ($cellIndex >= 6) break; // 只取前6列
+
+                $cell_type = $cell_matches[1][$i] ?? '';
+                $cell_inner = $cell_matches[2][$i] ?? '';
+                $value = '';
+
+                // 读取单元格值
+                if (preg_match('/<v>([^<]+)<\/v>/', $cell_inner, $v_match)) {
+                    $value = $v_match[1];
+                } elseif (preg_match('/<t>([^<]+)<\/t>/', $cell_inner, $t_match)) {
+                    $value = $t_match[1];
+                }
+
+                // 处理共享字符串
+                if ($cell_type === 's' && isset($str_map[$value])) {
+                    $value = $str_map[$value];
+                }
+
+                $cols[$cellIndex] = $value;
+                $cellIndex++;
+            }
+
+            $rows[] = $cols;
+        }
+
+        return $rows;
+    }
+
+    private function readXls($file)
+    {
+        $data = file_get_contents($file);
+        $rows = [];
+        $len = strlen($data);
+        $p = 0;
+
+        while ($p < $len) {
+            $id = ord($data[$p]);
+            $size = unpack('v', substr($data, $p + 1, 2))[1];
+            if ($id == 0x20) {
+                $row = [];
+                $sp = $p + 4;
+                while ($sp < $p + $size) {
+                    $type = ord($data[$sp]);
+                    if ($type == 0x02) {
+                        $val = substr($data, $sp + 3, 1);
+                        $row[] = trim($val);
+                        $sp += 8;
+                    } else {
+                        $sp++;
+                    }
+                }
+                if (!empty($row)) $rows[] = $row;
+            }
+            $p += $size;
+        }
+        return $rows;
+    }
+}

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

@@ -1098,87 +1098,231 @@ SQL;
     /**
      * 工分产量报工列表
      * @ApiMethod (GET)
-     * @param string search 关键词搜索(订单编号、员工编号、员工姓名),与菜单模式二选一
-     * @param string majorprocess 大工序(菜单模式必填)
-     * @param string machine 机台编号(菜单模式必填)
-     * @param string sys_rq 上报日期(菜单模式必填,Y-m-d)
-     * @param int page 页码,默认1
-     * @param int limit 每页条数,默认50
      */
     public function WorkpointsOutputList()
     {
-        if (!$this->request->isGet()) {
-            $this->error('请求方法错误');
+        if ($this->request->isGet() === false) {
+            $this->error('请求错误');
         }
-        $params = $this->request->param();
-
-        $page = max(1, intval($params['page'] ?? 1));
-        $limit = max(1, min(200, intval($params['limit'] ?? 50)));
-        $offset = ($page - 1) * $limit;
 
-        // 关键词搜索模式
-        if (!empty($params['search'])) {
-            $search = trim($params['search']);
-            $staffNo = !empty($params['staff_no']) ? trim($params['staff_no']) : '';
-            $staffName = !empty($params['staff_name']) ? trim($params['staff_name']) : '';
-
-            // 先查出生产款号匹配的工单编号
-            $matchingWorkOrders = db('工单_基本资料')
-                ->where('Mod_rq', null)
-                ->where('生产款号', 'like', "%{$search}%")
-                ->column('订单编号');
+        $search = input('search');
+        $page = input('page');
+        $limit = input('limit');
+        $majorprocess = input('majorprocess');
+        $machine = input('machine');
+        $date = input('date');
 
+        $where = [];
+        if (!empty($search)) {
+            // 关键词搜索模式
             $searchLike = "%{$search}%";
-            $staffNoLike = $staffNo ? "%{$staffNo}%" : '';
-            $staffNameLike = $staffName ? "%{$staffName}%" : '';
+            $where[] = "(del_rq IS NULL AND (work_order LIKE '{$searchLike}' OR staff_no LIKE '{$searchLike}' OR staff_name LIKE '{$searchLike}'))";
+        } else {
+            // 菜单模式
+            if (!empty($majorprocess)) {
+                $where['majorprocess'] = $majorprocess;
+            }
+            if (!empty($machine)) {
+                $where['machine'] = $machine;
+            }
+            if (!empty($date)) {
+                $where['sys_rq'] = ['like', $date . '%'];
+            }
+            $where['del_rq'] = null;
+        }
 
-            // 拼接 OR 条件
-            $orderSql = !empty($matchingWorkOrders)
-                ? " OR `work_order` IN ('" . implode("','", array_map('addslashes', $matchingWorkOrders)) . "')"
-                : "";
+        $list = db('设备_工分计酬')
+            ->where($where)
+            ->field('id,work_order,majorprocess,process_code,process_name,part_code,
+                     staff_no,staff_name,standard_hour,standard_score,money,coefficient,
+                     number,production_hour,production_score,salary,machine,sys_id,sys_rq')
+            ->order('sys_rq desc,process_code asc,staff_no asc')
+            ->limit(($page - 1) * $limit, $limit)
+            ->select();
 
-            $whereSql = "(`del_rq` IS NULL AND (`work_order` LIKE '$searchLike' OR `staff_no` LIKE '$searchLike' OR `staff_name` LIKE '$searchLike'$orderSql))";
+        $count = db('设备_工分计酬')
+            ->where($where)
+            ->count();
 
-            if ($staffNo) {
-                $whereSql .= " AND `staff_no` LIKE '$staffNoLike'";
+        // 批量查询生产款号、款式
+        $workOrders = array_unique(array_filter(array_column($list, 'work_order')));
+        $orderMap = [];
+        if (!empty($workOrders)) {
+            $orderRows = db('工单_基本资料')
+                ->whereIn('订单编号', $workOrders)
+                ->whereNull('Mod_rq')
+                ->field('订单编号,生产款号,款式')
+                ->select();
+            foreach ($orderRows as $row) {
+                $orderMap[$row['订单编号']] = [
+                    '生产款号' => $row['生产款号'],
+                    '款式' => $row['款式'],
+                ];
             }
-            if ($staffName) {
-                $whereSql .= " AND `staff_name` LIKE '$staffNameLike'";
+        }
+
+        // 批量查询部件名称
+        $partMap = [];
+        $partKeys = [];
+        foreach ($list as $row) {
+            if (!empty($row['part_code']) && !empty($row['work_order'])) {
+                $key = $row['work_order'] . '|' . $row['part_code'];
+                if (!in_array($key, $partKeys)) {
+                    $partKeys[] = $key;
+                }
             }
+        }
+        if (!empty($partKeys)) {
+            $partConditions = [];
+            foreach ($partKeys as $key) {
+                list($wo, $pc) = explode('|', $key, 2);
+                $partConditions[] = "(work_order = '" . addslashes($wo) . "' AND part_code = '" . addslashes($pc) . "')";
+            }
+            $partRows = db('工单_部件资料')
+                ->whereRaw('(' . implode(' OR ', $partConditions) . ')')
+                ->whereNull('del_rq')
+                ->field('work_order,part_code,part_name')
+                ->select();
+            foreach ($partRows as $pr) {
+                $mapKey = $pr['work_order'] . '|' . $pr['part_code'];
+                $partMap[$mapKey] = $pr['part_name'];
+            }
+        }
 
-            $total = (int)db('设备_工分计酬')
-                ->where($whereSql)
-                ->count();
+        // 组装返回数据
+        $data = [];
+        foreach ($list as $value) {
+            $orderInfo = $orderMap[$value['work_order']] ?? [];
+            $partKey = $value['work_order'] . '|' . $value['part_code'];
+            // 工分和工资直接从表字段取值,不做计算
+            $data[] = [
+                'id' => $value['id'],
+                'work_order' => $value['work_order'],
+                '款号' => $orderInfo['生产款号'] ?? '',
+                '款式' => $orderInfo['款式'] ?? '',
+                '部件编号' => $value['part_code'],
+                '部件名称' => $partMap[$partKey] ?? '',
+                'sys_id' => $value['sys_id'],
+                '员工编号' => $value['staff_no'],
+                'staff_name' => $value['staff_name'],
+                'process_code' => $value['process_code'],
+                'process_name' => $value['process_name'],
+                '工分' => round(floatval($value['standard_score']), 2),
+                '工资' => round(floatval($value['salary']), 2),
+                'number' => $value['number'],
+                'sys_rq' => $value['sys_rq'],
+                '标准工时' => round(floatval($value['standard_hour']), 2),
+                '标准总工时' => round(floatval($value['production_hour']), 2),
+                '设备编号' => $value['machine'],
+            ];
+        }
 
-            $rows = db('设备_工分计酬')
-                ->where($whereSql)
-                ->order('sys_rq desc,process_code asc,staff_no asc')
-                ->limit($offset, $limit)
-                ->select();
-        } else {
-            // 菜单选择模式
-            $query = db('设备_工分计酬')
-                ->field('id,work_order,majorprocess,process_code,process_name,
-                         staff_no,staff_name,standard_hour,standard_score,money,coefficient,
-                         number,production_hour,production_score,salary,machine,sys_id,sys_rq')
-                ->where('del_rq', null);
-
-            if (!empty($params['majorprocess'])) {
-                $query->where('majorprocess', $params['majorprocess']);
+        $this->success('成功', ['list' => $data, 'total' => $count]);
+    }
+
+
+    // ==================== 手工新增工分报工 ====================
+
+    /**
+     * 手工新增工分报工 - 左侧菜单
+     * 一级:报工日期(年月日)
+     * 二级:机台编号 + 手工新增人员
+     * @ApiMethod (GET)
+     */
+    public function GetManualReportingLeft()
+    {
+        if (!$this->request->isGet()) {
+            $this->error('请求方法错误');
+        }
+        $startDate = date('Y-m-d', strtotime('-90 day'));
+        $endDate = date('Y-m-d');
+
+        // 查询手工报工数据(machine为空或手工标记)
+        $list = db('设备_工分计酬')
+            ->where('del_rq', null)
+            ->where('date', 'between time', [$startDate, $endDate])
+            ->where(function($query) {
+                $query->where('machine', '')->whereOr('machine', null);
+            })
+            ->field('machine, date, staff_no, staff_name')
+            ->order('date desc, machine asc')
+            ->select();
+
+        // 按日期分组
+        $dateMap = [];
+        foreach ($list as $row) {
+            $date = $row['date'];
+            if (!isset($dateMap[$date])) {
+                $dateMap[$date] = [];
             }
-            if (!empty($params['machine'])) {
-                $query->where('machine', $params['machine']);
+            // 同一日期下按机台+人员去重
+            $key = ($row['machine'] ?: '手工') . '|' . $row['staff_no'];
+            if (!isset($dateMap[$date][$key])) {
+                $dateMap[$date][$key] = [
+                    'machine' => $row['machine'] ?: '',
+                    'staff_no' => $row['staff_no'],
+                    'staff_name' => $row['staff_name'],
+                ];
             }
-            if (!empty($params['sys_rq'])) {
-                $query->where('sys_rq', 'like', $params['sys_rq'] . '%');
+        }
+
+        $data = [];
+        foreach ($dateMap as $date => $items) {
+            $children = [];
+            foreach ($items as $item) {
+                $machine = $item['machine'] ?: '手工-' . $item['staff_no'];
+                $children[] = [
+                    'label' => $machine . ' - ' . $item['staff_name'],
+                    'id' => 'machine-' . $item['staff_no'] . '-' . $date,
+                    'staff_no' => $item['staff_no'],
+                    'staff_name' => $item['staff_name'],
+                    'machine' => $item['machine'],
+                ];
             }
-            $query->order('sys_rq desc,process_code asc,staff_no asc');
+            $data[] = [
+                'label' => $date,
+                'id' => 'date-' . $date,
+                'sys_rq' => $date,
+                'children' => $children,
+            ];
+        }
+
+        $this->success('成功', $data);
+    }
 
-            $total = (int)$query->count();
-            $rows = $query->limit($offset, $limit)->select();
+    /**
+     * 手工新增工分报工 - 列表
+     * @ApiMethod (GET)
+     * @param string $date 报工日期
+     * @param string $staff_no 员工编号(可选)
+     */
+    public function GetManualReportingList()
+    {
+        if (!$this->request->isGet()) {
+            $this->error('请求方法错误');
         }
-        // 批量查询生产款号和款式
-        $workOrders = array_unique(array_filter(array_column($rows, 'work_order')));
+        $params = $this->request->param();
+        $date = isset($params['date']) ? trim($params['date']) : '';
+        $staffNo = isset($params['staff_no']) ? trim($params['staff_no']) : '';
+
+        $query = db('设备_工分计酬')
+            ->where('del_rq', null)
+            ->where(function($q) {
+                $q->where('machine', '')->whereOr('machine', null);
+            });
+
+        if (!empty($date)) {
+            $query->where('date', $date);
+        }
+        if (!empty($staffNo)) {
+            $query->where('staff_no', $staffNo);
+        }
+
+        $list = $query->order('sys_rq desc')->select();
+        $count = count($list);
+
+        // 查询订单款号款式信息
+        $workOrders = array_unique(array_filter(array_column($list, 'work_order')));
         $orderMap = [];
         if (!empty($workOrders)) {
             $orderRows = db('工单_基本资料')
@@ -1194,33 +1338,180 @@ SQL;
             }
         }
 
-        // 返回明细数据
-        $list = [];
-        foreach ($rows as $row) {
-            $orderInfo = $orderMap[$row['work_order']] ?? [];
-            $list[] = [
-                'id' => $row['id'],
-                '小组' => $row['majorprocess'],
-                '员工编号' => $row['staff_no'],
-                '员工姓名' => $row['staff_name'],
-                '工序名称' => $row['process_name'],
-                '设备编号' => $row['machine'],
-                '报工数量' => $row['number'],
-                '计件单价' => $row['money'],
-                '工序产值' => $row['production_score'],
-                '报工日期' => substr($row['sys_rq'], 0, 10),
-                '订单编号' => $row['work_order'],
-                '生产款号' => $orderInfo['生产款号'] ?? '',
+        $data = [];
+        foreach ($list as $value) {
+            $orderInfo = $orderMap[$value['work_order']] ?? [];
+            $data[] = [
+                'id' => $value['id'],
+                'work_order' => $value['work_order'],
+                '款号' => $orderInfo['生产款号'] ?? '',
                 '款式' => $orderInfo['款式'] ?? '',
-                'sys_id' => $row['sys_id'],
+                '部件编号' => $value['part_code'],
+                '部件名称' => '',
+                'sys_id' => $value['sys_id'],
+                '员工编号' => $value['staff_no'],
+                'staff_name' => $value['staff_name'],
+                'process_code' => $value['process_code'],
+                'process_name' => $value['process_name'],
+                '工分' => round(floatval($value['standard_score']), 2),
+                '工资' => round(floatval($value['salary']), 2),
+                'number' => $value['number'],
+                'date' => $value['date'],
+                'sys_rq' => $value['sys_rq'],
+                '标准工时' => round(floatval($value['standard_hour']), 2),
+                '标准总工时' => round(floatval($value['production_hour']), 2),
+                '设备编号' => $value['machine'],
             ];
         }
 
-        $this->success('成功', [
-            'total' => $total,
-            'page' => $page,
-            'limit' => $limit,
-            'list' => $list,
-        ]);
+        $this->success('成功', ['list' => $data, 'total' => $count]);
+    }
+
+    /**
+     * 手工新增工分报工 - 新增
+     * @ApiMethod (POST)
+     */
+    public function AddManualReporting()
+    {
+        if (!$this->request->isPost()) {
+            $this->error('请求方法错误');
+        }
+        $params = $this->request->post();
+        $reportList = [];
+
+        if (isset($params[0]) && is_array($params[0])) {
+            $reportList = $params;
+        } elseif (!empty($params['list']) && is_array($params['list'])) {
+            $reportList = $params['list'];
+        } elseif (!empty($params['data']) && is_array($params['data'])) {
+            $reportList = $params['data'];
+        } else {
+            $rawBody = file_get_contents('php://input');
+            $decoded = json_decode($rawBody, true);
+            if (is_array($decoded)) {
+                if (isset($decoded[0]) && is_array($decoded[0])) {
+                    $reportList = $decoded;
+                } elseif (!empty($decoded['list']) && is_array($decoded['list'])) {
+                    $reportList = $decoded['list'];
+                } elseif (!empty($decoded['data']) && is_array($decoded['data'])) {
+                    $reportList = $decoded['data'];
+                }
+            }
+        }
+
+        if (empty($reportList)) {
+            $this->error('请传入报工数据');
+        }
+
+        $insertData = [];
+        $logList = [];
+        $now = date('Y-m-d H:i:s');
+
+        foreach ($reportList as $idx => $item) {
+            $rowNo = $idx + 1;
+            if (empty($item['staff_no'])) {
+                $this->error('第' . $rowNo . '条员工编号不能为空');
+            }
+            if (empty($item['staff_name'])) {
+                $this->error('第' . $rowNo . '条员工姓名不能为空');
+            }
+            if (empty($item['work_order']) && empty($item['workorder'])) {
+                $this->error('第' . $rowNo . '条订单编号不能为空');
+            }
+            if (empty($item['majorprocess'])) {
+                $this->error('第' . $rowNo . '条大工序不能为空');
+            }
+            if (empty($item['process_code'])) {
+                $this->error('第' . $rowNo . '条工艺编号不能为空');
+            }
+            if (!isset($item['number']) || $item['number'] === '') {
+                $this->error('第' . $rowNo . '条产量不能为空');
+            }
+
+            $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 . '条工序不存在或已删除');
+            }
+
+            $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' => $workOrder,
+                'date' => !empty($item['date']) ? $item['date'] : date('Y-m-d'),
+                'majorprocess' => $item['majorprocess'],
+                'part_code' => $partCode,
+                'process_code' => $processCode,
+                'process_name' => $processName,
+                'standard_hour' => $standardHour,
+                'standard_score' => $standardScore,
+                'standard_minutes' => $standardMinutes,
+                'money' => $money,
+                'coefficient' => $coefficient,
+                'number' => $number,
+                'production_hour' => $productionHour,
+                'production_score' => $productionScore,
+                'salary' => $salary,
+                'machine' => '',  // 手工报工,机台为空
+                'sys_id' => $sysId,
+                'sys_rq' => $now,
+                'mod_id' => isset($item['mod_id']) ? $item['mod_id'] : '',
+                'mod_rq' => null,
+                'del_rq' => null,
+            ];
+        }
+
+        Db::startTrans();
+        try {
+            $result = db('设备_工分计酬')->insertAll($insertData);
+            if ($result === false) {
+                throw new \Exception('报工失败');
+            }
+            Db::commit();
+            $this->success('报工成功', ['count' => count($insertData)]);
+        } catch (\think\exception\HttpResponseException $e) {
+            throw $e;
+        } catch (\Exception $e) {
+            Db::rollback();
+            $this->error('报工失败:' . $e->getMessage());
+        }
     }
+
 }

+ 69 - 19
application/api/controller/WorkOrder.php

@@ -328,6 +328,8 @@ class WorkOrder extends Api
 
     public function WorkOrderAdd()
     {
+        \think\Log::record("【WorkOrderAdd】开始执行", 'info');
+
         if (Request::instance()->isPost() === false){
             $this->error('请求错误');
         }
@@ -342,6 +344,7 @@ class WorkOrder extends Api
         if (empty($param['订单编号'])) {
             $this->error('订单编号不能为空');
         }
+        \think\Log::record("【WorkOrderAdd】订单编号: " . $param['订单编号'] . ",面料: " . ($param['面料'] ?? '空'), 'info');
 
         // 命名锁串行化「取号+插入」,避免并发下先查后插导致重复订单编号
         $lockName = 'work_order_no_alloc';
@@ -350,6 +353,7 @@ class WorkOrder extends Api
         }
         try {
             $param['订单编号'] = $this->resolveUniqueWorkOrderNo($param['订单编号']);
+            \think\Log::record("【WorkOrderAdd】解析后订单编号: " . $param['订单编号'], 'info');
 
             $data = $param;
             unset($data['关联订单']);
@@ -449,8 +453,9 @@ class WorkOrder extends Api
         }else{
             //判断新增时面料是否存在,如果有调用gtp自动生成BOM资料
             if (!empty($param['面料'])) {
-                // 只有面料不为空时,才调用 GdGtpAiOrder 方法
-                $this->GdGtpAiOrder($param['订单编号']);
+                \think\Log::record("【WorkOrderAdd】开始调用 GdGtpAiOrder,订单号: " . $param['订单编号'], 'info');
+                $bomResult = $this->GdGtpAiOrder($param['订单编号']);
+                \think\Log::record("【WorkOrderAdd】GdGtpAiOrder 返回: " . json_encode($bomResult, JSON_UNESCAPED_UNICODE), 'info');
             }
         }
         $this->success('成功');
@@ -1604,6 +1609,16 @@ class WorkOrder extends Api
         if (empty($param) || !isset($param['cy']) || !isset($param['order'])) {
             $this->error('参数错误');
         }
+
+            //获取当前订单PO款号
+        $res = \db('工单_印件资料')
+            ->field('款号')
+            ->where('订单编号',$param['order'])
+            ->whereNull('Mod_rq')
+            ->order('子订单编号 desc')
+            ->select();
+        $po = $res ? array_column($res, '款号') : [];
+
         // 判断是否“船样”获取对应的子订单编号
         if ($param['cy'] == '否') {
 
@@ -1675,9 +1690,13 @@ class WorkOrder extends Api
             $colorlist = \db('工单_颜色编号')->select();
 
             //3.获取历史尺码数据
+            // 注意:必须过滤掉已软删除(Mod_rq 不为空)的数据,
+            // 否则删除颜色资料后再次新增时,会把已删除记录的旧尺码带出来
+            // 取该订单下最新一条未删除记录作为历史尺码来源
             $cm_list = \db('工单_印件资料')
                 ->where('订单编号', $param['order'])
-                ->group('订单编号')
+                ->whereNull('Mod_rq')
+                ->order('Sys_rq desc')
                 ->find();
 
             $cm_data = [];
@@ -1697,7 +1716,7 @@ class WorkOrder extends Api
 
 
 
-            $result = ['order' => $order,'colorlist' => $colorlist,'cm' => $cm_data];
+            $result = ['order' => $order,'colorlist' => $colorlist,'cm' => $cm_data,'po' => $po];
             $this->success('成功', $result);
 
         } else if ($param['cy'] == '是') {
@@ -1706,6 +1725,7 @@ class WorkOrder extends Api
             $data = \db('工单_印件资料')
                 ->where('订单编号', $param['order'])
                 ->where('船样', '=', 1)
+                ->whereNull('Mod_rq')
                 ->order('子订单编号 asc')
                 ->find();
 
@@ -1725,7 +1745,7 @@ class WorkOrder extends Api
                 }
             }
 
-            $this->success('成功', $order);
+            $this->success('成功', ['order' => $order, 'kuanhao' => $po]);
         }
     }
 
@@ -2053,12 +2073,33 @@ class WorkOrder extends Api
             }
             $this->success('成功', $list);
         } else {
+            // BOM 数据为空,触发 AI 生成(异步生成后返回空,前端需轮询)
+            \think\Log::record("【OrderBomList】BOM数据为空,触发 AI 生成,订单号: " . $param['order'], 'info');
             $this->GdGtpAiOrder($param['order']);
-            $this->success('没有找到相关数据',[]);
-//            return $this->success('没有找到相关数据', []);
+            $this->success('BOM生成中,请稍后刷新', []);
         }
     }
 
+    /**
+     * 触发 BOM 资料 AI 生成(同步,前端调用)
+     * @return void
+     */
+    public function TriggerBomGenerate()
+    {
+        $param = $this->request->param();
+        $orderNo = isset($param['order']) ? $param['order'] : '';
+
+        if (empty($orderNo)) {
+            $this->error('参数错误');
+        }
+
+        \think\Log::record("【TriggerBomGenerate】开始生成,订单号: " . $orderNo, 'info');
+        $result = $this->GdGtpAiOrder($orderNo);
+        \think\Log::record("【TriggerBomGenerate】生成完成,结果条数: " . count($result), 'info');
+
+        $this->success('BOM生成完成', $result);
+    }
+
     /**
      * Bom资料删除
      * */
@@ -2140,6 +2181,8 @@ class WorkOrder extends Api
      * 前端选择订单时如果BOM资料数据为空则单独调用,重新生成最新
      */
     public function GdGtpAiOrder($order){
+        \think\Log::record("【GdGtpAiOrder】开始执行,参数 order: " . $order, 'info');
+
         // 判断是否有指定的订单号
         if (!empty($order)) {
             // 查询单个订单的最大编号
@@ -2148,6 +2191,7 @@ class WorkOrder extends Api
                 ->order('订单编号', 'desc')
                 ->limit(1)
                 ->value('订单编号');
+            \think\Log::record("【GdGtpAiOrder】maxOrder: " . ($maxOrder ?? 'null'), 'info');
 
             // 查询该订单的基本资料
             $list = \db('工单_基本资料')
@@ -2155,19 +2199,17 @@ class WorkOrder extends Api
                 ->order('订单编号', 'desc')
                 ->limit(1)
                 ->find();
+            \think\Log::record("【GdGtpAiOrder】查到订单: " . ($list ? json_encode($list) : 'null') . ",面料: " . ($list['面料'] ?? 'null'), 'info');
 
-            // 如果面料数据为空,提示错误
+            // 如果面料数据为空,返回提示
             if (empty($list['面料'])) {
-                $this->error('面料数据为空无法定义BOM');
+                \think\Log::record("【GdGtpAiOrder】面料为空,退出", 'info');
+                return ['msg' => '面料数据为空,请先在工单基本资料中填写面料信息', 'data' => []];
             }
 
-            // 处理订单编号
-            $numericPart = substr($maxOrder, 2);
-            $newNumericPart = str_pad((int)$numericPart + 1, strlen($numericPart), '0', STR_PAD_LEFT);
-            $param['订单编号'] = $order . $newNumericPart;
-
             // 处理物料信息
             $massage = empty($list['粘衬']) || $list['粘衬'] == '无' ? $list['面料'] : $list['面料'] . ',粘衬:' . $list['粘衬'];
+            \think\Log::record("【GdGtpAiOrder】massage: " . $massage, 'info');
             $materialCategories = [];
             $pattern = '/(\S+?):([^,]+)/'; // 匹配 类别:物料 格式
 
@@ -2184,6 +2226,7 @@ class WorkOrder extends Api
             }
 
             $mianliao = $this->Gpt($massage);
+            \think\Log::record("【GdGtpAiOrder】Gpt 返回: " . json_encode($mianliao, JSON_UNESCAPED_UNICODE), 'info');
 
             $data = [];
 
@@ -2191,8 +2234,9 @@ class WorkOrder extends Api
 //                $this->success('成功');
 //            }
 
-            foreach ($mianliao as $value) {
-                if (!empty($value) && $value !== '粘衬') { // 排除空值和粘衬
+            if (is_array($mianliao)) {
+                foreach ($mianliao as $value) {
+                    if (!empty($value) && $value !== '粘衬') { // 排除空值和粘衬
                     $category = '';
 
                     // 查找物料对应的分类
@@ -2214,6 +2258,7 @@ class WorkOrder extends Api
                     ];
                 }
             }
+            }
             foreach ($data as &$item) {
                 if (empty($item['物料分类'])) {
                     $item['物料分类'] = '';
@@ -2224,9 +2269,14 @@ class WorkOrder extends Api
 
             // 批量插入BOM资料
             if (!empty($data)) {
+                \think\Log::record("【GdGtpAiOrder】准备插入 " . count($data) . " 条 BOM 数据", 'info');
                 \db('工单_bom资料')->insertAll($data);
+                \think\Log::record("【GdGtpAiOrder】BOM 数据插入完成", 'info');
+            } else {
+                \think\Log::record("【GdGtpAiOrder】data 为空,未插入", 'info');
             }
-            $this->success('成功',$order);
+            // 返回插入的数据
+            return $data;
         } else {
             // 如果没有指定订单号,批量查询订单号并处理
             $has_bom = \db('工单_bom资料')->alias('a')->field('a.BOM_工单编号')->group('a.BOM_工单编号')->select();
@@ -2257,7 +2307,7 @@ class WorkOrder extends Api
                     ->find();
 
                 if (empty($list['面料'])) {
-                    $this->error("订单 {$orderData['订单编号']} 面料数据为空,无法定义BOM");
+                    continue; // 面料为空,跳过该订单
                 }
 
                 // 处理订单编号
@@ -2339,7 +2389,7 @@ class WorkOrder extends Api
                     \db('工单_bom资料')->insertAll($data);
                 }
             }
-            $this->success('成功');
+            return $data;
         }
     }
 

+ 32 - 4
application/api/controller/WorkOrderProcess.php

@@ -2086,6 +2086,7 @@ class WorkOrderProcess extends Api
      * 工序产量核查:按工单查询报工汇总(工单信息 / 工序列表 / 人员报工明细)
      * @param workorder 工单编号
      * @param process 工序(可选,按big_process过滤)
+     * @param month 月份筛选(可选,格式:2024-01,不传则查全部)
      * @return array
      */
     public function checkProcessProduction()
@@ -2100,7 +2101,14 @@ class WorkOrderProcess extends Api
 
         $workorder = $params['workorder'];
         $processFilter = $params['process'] ?? '';
-
+        $monthFilter = $params['month'] ?? '';  // 格式:2024-01
+        
+        // 解析月份筛选(用于显示)
+        $monthWhere = '';
+        if (!empty($monthFilter) && preg_match('/^(\d{4})-(\d{2})$/', $monthFilter, $matches)) {
+            $monthWhere = "{$matches[1]}年{$matches[2]}月";
+        }
+        
         // 1. 查询工单基本信息
         $workOrderInfo = Db::name('工单_基本资料')
             ->where('订单编号', $workorder)
@@ -2135,10 +2143,17 @@ class WorkOrderProcess extends Api
             return intval($a['工序号']) - intval($b['工序号']);
         });
 
-        // 3. 查询设备_工分计酬中的报工数据,补齐staff_no字段
-        $reportList = Db::name('设备_工分计酬')->alias('a')
+        // 3. 查询设备_工分计酬中的报工数据,补齐staff_no字段,同时查出已报工月份列表
+        $reportQuery = Db::name('设备_工分计酬')->alias('a')
             ->where('a.work_order', $workorder)
-            ->whereNull('a.del_rq')
+            ->whereNull('a.del_rq');
+        
+        // 月份筛选
+        if (!empty($monthFilter) && preg_match('/^(\d{4})-(\d{2})$/', $monthFilter, $matches)) {
+            $reportQuery->whereTime('a.date', 'between', [$matches[1] . '-' . $matches[2] . '-01', $matches[1] . '-' . $matches[2] . '-31']);
+        }
+        
+        $reportList = $reportQuery
             ->field('a.process_code as 工序号, a.process_name as 工序名称, a.staff_no as 员工号, a.staff_name as 员工姓名,
                 a.sys_id as 小组,
                 SUM(a.number) as 数量, MIN(a.date) as 开工日期, MAX(a.date) as 完工日期,
@@ -2146,6 +2161,17 @@ class WorkOrderProcess extends Api
             ->group('a.process_code, a.process_name, a.staff_no, a.staff_name, a.sys_id')
             ->select();
 
+        // 查出该工单所有已报工月份(用于前端月份筛选器)
+        $monthRows = Db::name('设备_工分计酬')
+            ->where('work_order', $workorder)
+            ->whereNull('del_rq')
+            ->whereNotNull('date')
+            ->where('date', '<>', '')
+            ->field("DISTINCT DATE_FORMAT(date, '%Y-%m') as month")
+            ->order('month DESC')
+            ->select();
+        $reportedMonths = array_column($monthRows, 'month');
+
         // 按工序号、员工号数字排序
         if (!empty($reportList)) {
             usort($reportList, function ($a, $b) {
@@ -2162,6 +2188,8 @@ class WorkOrderProcess extends Api
             '生产款号' => $workOrderInfo['生产款号'],
             '款式' => $workOrderInfo['款式'],
             '订单数量' => $manufactureQty,
+            '筛选月份' => $monthWhere,  // 显示当前筛选的月份,不筛选则为空
+            '已报工月份列表' => $reportedMonths,  // 前端月份筛选器下拉数据
             '工序列表' => [],
         ];
 

+ 7 - 10
application/api/controller/WorkOrderSpotCheck.php

@@ -393,6 +393,7 @@ class WorkOrderSpotCheck extends Api{
                     }
                     $where['c.工序编号'] = '3';
                     $where['c.mod_rq'] = null;
+                    $where['j.Mod_rq'] = null;
                     $existingRecords = \db('设备_产量计酬')->alias('c')
                         ->field('c.订单编号, c.子订单编号, c.款号, c.工序编号, c.工序名称, c.尺码, 
                          CASE 
@@ -2330,13 +2331,13 @@ class WorkOrderSpotCheck extends Api{
         }
 
         // 查询工单基本资料表的数据
-        $orderlist = \db('工单_基本资料')->where('订单编号', $list_gd[0]['订单编号'])->find();
+        $orderlist = \db('工单_基本资料')->where('订单编号', $list_gd[0]['订单编号'])->whereNull('Mod_rq')->find();
 
         // 检查code是否为 "包装"
         if ($params['code'] == '包装') {
             // 如果是 "包装",则增加工单基本资料表中的入仓数量
             $addorder['工单入仓数量'] = $orderlist['工单入仓数量'] + $params['sl'];
-            $result = \db('工单_基本资料')->where('订单编号', $list_gd[0]['订单编号'])->fetchSql(true)->update($addorder);
+            $result = \db('工单_基本资料')->where('订单编号', $list_gd[0]['订单编号'])->whereNull('Mod_rq')->fetchSql(true)->update($addorder);
             $sql = \db()->query($result);
         }
 
@@ -2400,7 +2401,7 @@ class WorkOrderSpotCheck extends Api{
                 $order_id = explode('-', $params['order_id']);
                 $order = $order_id[0];
                 //先查询这条订单是否存在
-                $sql = \db('工单_基本资料')->where('订单编号', $order)->fetchSql(true)->find();
+                $sql = \db('工单_基本资料')->where('订单编号', $order)->whereNull('Mod_rq')->fetchSql(true)->find();
                 $order_list = \db()->query($sql);
                 if (empty($order_list)) {
                     $this->error('没有找到对应的订单信息');
@@ -2439,7 +2440,7 @@ class WorkOrderSpotCheck extends Api{
                 $list = [];
                 $order_id = explode('-', $params['order_id']);
                 $order = $order_id[0];
-                $sql = \db('工单_基本资料')->where('订单编号', $order)->fetchSql(true)->find();
+                $sql = \db('工单_基本资料')->where('订单编号', $order)->whereNull('Mod_rq')->fetchSql(true)->find();
                 $list_gd = \db()->query($sql); // 查询的结果可能是多条记录,确保是数组
                 if (empty($list_gd)) {
                     $this->error('没有找到对应的订单信息');
@@ -2721,9 +2722,7 @@ class WorkOrderSpotCheck extends Api{
 
                 // 根据 UniqId 查询未删除的产量报工记录
                 $result = \db('设备_产量计酬')->where('UniqId', $uniqId)
-                    ->where(function ($query) {
-                        $query->whereNull('mod_rq')->whereOr('mod_rq', '');
-                    })
+                    ->whereNull('mod_rq')
                     ->find();
 
                 // 如果查询不到数据,跳过当前处理
@@ -2748,9 +2747,7 @@ class WorkOrderSpotCheck extends Api{
                 // 软删除产量报工记录
                 $updateResult = \db('设备_产量计酬')
                     ->where('UniqId', $uniqId)
-                    ->where(function ($query) {
-                        $query->whereNull('mod_rq')->whereOr('mod_rq', '');
-                    })
+                    ->whereNull('mod_rq')
                     ->update([
                         'mod_rq' => $operTime,
                         'delsys_id' => $operUser,