소스 검색

工资计算优化及成本核算

unknown 1 개월 전
부모
커밋
9fbbb37083

+ 164 - 59
application/api/controller/CostAccounting.php

@@ -412,6 +412,7 @@ class CostAccounting extends Api
                 'Sys_rq' => date('Y-m-d H:i:s', time())
             ];
         }
+        db('成本v23_月度成本明细')->where('sys_ny', $param['month'])->delete();
         $sql = db('成本v23_月度成本明细')->fetchSql(true)->insertAll($data);
         $res = db()->query($sql);
         if ($res !== false) {
@@ -1230,70 +1231,174 @@ class CostAccounting extends Api
         return $distribution;
     }
 
+    /**
+     * 计算工艺材料分摊
+     * @param $month
+     * @return bool
+     */
+    public function calculationChroma($month)
+    {
+        try {
+            // 1. 批量获取数据,减少数据库查询次数
+            $date = substr($month, 0, 4) . '-' . substr($month, 4, 2);
+
+            // 获取分摊材料总金额
+            $totalMoney = db('材料出库单列表')
+                ->where([
+                    '出库日期' => ['like', $date . '%'],
+                    '部门' => '印刷成本中心'
+                ])
+                ->whereNull('表体生产订单号')
+                ->field('SUM(金额) as money')
+                ->find();
+
+            if (!$totalMoney || $totalMoney['money'] <= 0) {
+                return false;
+            }
+
+            // 获取工单工艺数据及总色度数
+            $processData = db('成本v23_月度成本明细')
+                ->whereNotNull('车间名称')
+                ->where('sys_ny', $month)
+                ->field('Uniqid, 班组车头产量*sczl_ms as 色度数, SUM(班组车头产量*sczl_ms) OVER() as total_chroma')
+                ->order('Uniqid asc')
+                ->select();
 
-    //获取工单编号
-    public function getmonthWorkorderList($month)
+            if (empty($processData)) {
+                return false;
+            }
+
+            // 从第一条数据中获取总色度数
+            $totalChroma = $processData[0]['total_chroma'] ?? 0;
+
+            if ($totalChroma <= 0) {
+                return false; // 防止除以零
+            }
+
+            // 2. 批量更新数据,减少数据库操作次数
+            $updateData = [];
+            foreach ($processData as $process) {
+                $money = round($totalMoney['money'] * ($process['色度数'] / $totalChroma), 2);
+                $updateData[] = [
+                    'Uniqid' => $process['Uniqid'],
+                    '分摊材料' => $money
+                ];
+            }
+
+            // 批量更新(使用fetchSql构建安全的SQL)
+            if (!empty($updateData)) {
+                return $this->batchUpdateWithFetchSql('成本v23_月度成本明细', $updateData, 'Uniqid');
+            }
+
+            return false;
+
+        } catch (\Exception $e) {
+            // 记录错误日志
+            \think\Log::error('工艺材料分摊计算失败: ' . $e->getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * 使用fetchSql方法批量更新数据(安全处理中文字段名)
+     * @param string $table 表名
+     * @param array $data 更新数据数组
+     * @param string $pk 主键字段名
+     * @return bool
+     */
+    protected function batchUpdateWithFetchSql($table, $data, $pk = 'Uniqid')
     {
-        $worderList = db('成本v23_月度成本明细')
-            ->alias('a')
-            ->join('工单_印件资料 b','a.sczl_gdbh = b.Yj_Gdbh and a.sczl_yjno = b.yj_Yjno')
-            ->join('物料_收发记录 c', 'a.sczl_gdbh = c.st_gdbh and b.yj_Yjdh = c.cpdh')
-            ->field('a.sczl_gdbh,a.sczl_yjno,c.st_wlbh,rtrim(c.st_jylb) as st_jylb,c.仓库编号,c.st_jtbh,
-            rtrim(c.cpdh) as cpdh,c.领用单价,c.st_oldSl,c.Uniqid')
-            ->group('a.sczl_gdbh,cpdh,c.st_wlbh,c.Uniqid')
-            ->where('a.sys_ny', $month)
-            ->where('c.领用单价','<>',0)
-            ->select();
-        $data = [];
-        foreach ($worderList as $item) {
-            if (preg_match("/[A-Za-z]/", $item['仓库编号'])) {
-                $sist = $item['仓库编号'];
-            } else {
-                $sist = 'Y' . $item['仓库编号'];
+        if (empty($data)) {
+            return false;
+        }
+
+        try {
+            // 方法一:使用查询构建器配合fetchSql
+            $ids = array_column($data, $pk);
+
+            // 开始事务
+            db()->startTrans();
+
+            // 先查询现有数据
+            $query = db($table)
+                ->whereIn($pk, $ids)
+                ->field($pk)
+                ->fetchSql(true)
+                ->select();
+            $sql = $query;
+
+            // 构造批量更新SQL
+            $updateSql = "UPDATE `{$table}` SET ";
+
+            // 获取所有需要更新的字段(排除主键)
+            $fields = array_keys($data[0]);
+            $updateFields = array_diff($fields, [$pk]);
+
+            $cases = [];
+            foreach ($updateFields as $field) {
+                $caseSql = "`{$field}` = CASE ";
+                foreach ($data as $item) {
+                    $caseSql .= "WHEN `{$pk}` = '{$item[$pk]}' THEN '{$item[$field]}' ";
+                }
+                $caseSql .= "ELSE `{$field}` END";
+                $cases[] = $caseSql;
             }
-            if (!isset($data[$sist])) {
-                $data[$sist] = [];
+
+            $updateSql .= implode(', ', $cases);
+            $updateSql .= " WHERE `{$pk}` IN ('" . implode("','", $ids) . "')";
+
+            // 执行更新
+            $result = db()->execute($updateSql);
+
+            db()->commit();
+            return $result !== false;
+
+        } catch (\Exception $e) {
+            db()->rollback();
+            \think\Log::error('批量更新失败: ' . $e->getMessage());
+
+            // 如果批量更新失败,回退到单条更新(也使用fetchSql)
+            return $this->singleUpdateFallback($table, $data, $pk);
+        }
+    }
+
+    /**
+     * 单条更新回退方案(使用fetchSql)
+     */
+    protected function singleUpdateFallback($table, $data, $pk)
+    {
+        $success = true;
+
+        foreach ($data as $item) {
+            try {
+                // 使用fetchSql构建安全的更新语句
+                $updateData = [];
+                foreach ($item as $key => $value) {
+                    if ($key !== $pk) {
+                        $updateData[$key] = $value;
+                    }
+                }
+
+                $sql = db($table)
+                    ->where($pk, $item[$pk])
+                    ->update($updateData)
+                    ->fetchSql(true);
+
+                // 获取SQL并执行
+                $result = db()->execute($sql);
+
+                if ($result === false) {
+                    $success = false;
+                    \think\Log::error('单条更新失败 - SQL: ' . $sql);
+                }
+
+            } catch (\Exception $e) {
+                $success = false;
+                \think\Log::error('单条更新异常: ' . $e->getMessage());
             }
-            $data[$sist][] = [
-                'gdbh' => $item['sczl_gdbh'],
-                'yjno' => $item['sczl_yjno'],
-                'st_wlbh' => $item['st_wlbh'],
-                'jtbh' => $item['st_jtbh'],
-                'cpdh' => $item['cpdh'],
-                'price' => $item['领用单价'],
-                'st_sl' => $item['st_oldSl'],
-                'Uniqid' => $item['Uniqid']
-            ];
         }
-        halt($data);
+
+        return $success;
     }
 
-    //获取分摊材料数据
-//    public function MaterialsShareList($month)
-//    {
-//        $year = substr($month, 0, 4) . '-' . substr($month, 4, 2);
-//        $list = db('物料_收发记录')
-//            ->field(['rtrim(st_jylb) as st_jylb', 'st_oldSl', '领用单价', '仓库编号','rtrim(st_jtbh) as jtbh'])
-//            ->where([
-//                'st_rq' => ['like',$year . '%'],
-//            ])
-//            ->whereNotIn('st_jylb', ['生产领料','制版领用'])
-//            ->select();
-//        $data = [];
-//        foreach ($list as $item) {
-//            if ($item['st_jylb'] == '修理领用') {
-//
-//            }}
-//            if (!isset($data[$item['st_jylb']])) {
-//                $data[$item['st_jylb']] = [];
-//            }
-//            $data[$item['st_jylb']][] = [
-//                'sl' => $item['st_oldSl'],
-//                'price' => $item['领用单价'],
-//                'sist' => $item['仓库编号'],
-//                'jtbh' => $item['jtbh'],
-//            ];
-//        }
-//        halt($data);
-//    }
 }

+ 749 - 877
application/api/controller/CostCalculation.php

@@ -1,951 +1,823 @@
 <?php
 
-
 namespace app\api\controller;
 
 use app\common\controller\Api;
+use think\Db;
+use think\Queue;
+use app\service\UnifiedCostCalculationService;
+use think\Request;
 
 class CostCalculation extends Api
 {
-
     protected $noNeedLogin = ['*'];
     protected $noNeedRight = ['*'];
 
-    // 车间常量
-    const WORKSHOP_ROLLER_PRESS = '03、卷凹机组';
-    const WORKSHOP_LIST_ALL = ['凹丝印车间', '胶印车间', '印后车间', '检验车间'];
-
-    // 科目名称关键词
-    const SUBJECT_WASTE_GAS = '废气处理';
-    const SUBJECT_BOILER = '锅炉';
-    const SUBJECT_HOT_WATER_BOILER = '热水锅炉';
-    const SUBJECT_AIR_COMPRESSOR_A = '空压机A';
-    const SUBJECT_AIR_COMPRESSOR_B = '空压机B';
-    const SUBJECT_VACUUM_BLOWER_A = '真空鼓风机A';
-    const SUBJECT_VACUUM_BLOWER_B = '真空鼓风机B';
-    const SUBJECT_CENTRAL_AIR_CONDITIONER_A = '中央空调A';
-    const SUBJECT_CENTRAL_AIR_CONDITIONER_B = '中央空调B';
-    const SUBJECT_TOTAL_TO_APPORTION = '待分摊总额';
+//    // 楼层编组映射(优化版)
+//    const FLOOR_GROUP_MAP = [
+//        '1' => ['02、胶印机组', '03、卷凹机组', '06、单凹机组', '05、圆切机组', '04、圆烫机组', '10、模切机组', '09、烫金机组'],     // 1楼编组
+//        '2' => ['01、切纸机组', '11、检品机组', '07、丝印机组', '12、覆膜机组', '08、喷码机组'],                     // 2楼编组
+//    ];
+//
+//    // 科目名称常量(简化版)
+//    const SUBJECT_WASTE_GAS = '废气处理(RTO)';
+//    const SUBJECT_BOILER = '锅炉';
+//    const SUBJECT_AIR_COMPRESSOR = '空压机';
+//    const SUBJECT_HOT_WATER_BOILER = '热水锅炉';
+//    const SUBJECT_VACUUM_BLOWER = '真空鼓风机';
+//    const SUBJECT_CENTRAL_AIR_CONDITIONER = '中央空调';
+//    const SUBJECT_TOTAL_TO_APPORTION = '待分摊总额';
+//
+//    // 需要分配到卷凹机组的科目列表
+//    const ROLL_COATER_ONLY_SUBJECTS = [
+//        self::SUBJECT_BOILER,
+//        self::SUBJECT_HOT_WATER_BOILER,
+//    ];
+//
+//    // 简化科目列表
+//    const SIMPLIFIED_SUBJECTS = [
+//        self::SUBJECT_WASTE_GAS,
+//        self::SUBJECT_BOILER,
+//        self::SUBJECT_AIR_COMPRESSOR,
+//        self::SUBJECT_HOT_WATER_BOILER,
+//        self::SUBJECT_VACUUM_BLOWER,
+//        self::SUBJECT_CENTRAL_AIR_CONDITIONER,
+//        self::SUBJECT_TOTAL_TO_APPORTION,
+//    ];
+//
+//    /**
+//     * 根据设备编号获取楼层信息
+//     */
+//    protected function getFloorByMachineCode($machineCode)
+//    {
+//        static $machineFloorCache = [];
+//
+//        if (isset($machineFloorCache[$machineCode])) {
+//            return $machineFloorCache[$machineCode];
+//        }
+//
+//        // 查询设备编组
+//        $group = Db::name('设备_基本资料')
+//            ->where('sys_sbID', '<>','')
+//            ->where('设备编号', $machineCode)
+//            ->value('设备编组');
+//
+//        if (!$group) {
+//            $machineFloorCache[$machineCode] = null;
+//            return null;
+//        }
+//
+//        // 根据编组判断楼层
+//        foreach (self::FLOOR_GROUP_MAP as $floor => $groups) {
+//            foreach ($groups as $groupName) {
+//                if (strpos($group, $groupName) !== false) {
+//                    $machineFloorCache[$machineCode] = $floor;
+//                    return $floor;
+//                }
+//            }
+//        }
+//
+//        $machineFloorCache[$machineCode] = null;
+//        return null;
+//    }
+//
+//    /**
+//     * 根据楼层获取该楼层的所有机台
+//     */
+//    protected function getMachinesByFloor($floor, $month)
+//    {
+//        // 获取该楼层的所有编组
+//        $groups = self::FLOOR_GROUP_MAP[$floor] ?? [];
+//        if (empty($groups)) {
+//            return [];
+//        }
+//
+//        try {
+//            // 方法1:使用更简单的查询条件构建方式
+//            $query = Db::name('成本v23_月度成本明细')
+//                ->alias('c')
+//                ->join('设备_基本资料 e', 'c.sczl_jtbh = e.设备编号', 'LEFT')
+//                ->where('c.sys_ny', $month)
+//                ->where('e.设备编号', '<>', '')
+//                ->whereIn('e.设备编组', $groups);
+//
+//
+//            $query->field([
+//                'c.sczl_jtbh' => '机台编号',
+//                'c.占用机时',
+//                'e.设备编组',
+//                'c.sczl_gdbh' => '工单编号',
+//                'c.sczl_yjno' => '印件号',
+//                'c.sczl_gxh' => '工序号',
+//                'c.Uniqid',
+//                'c.车间名称'
+//            ]);
+//
+//            $machines = $query->select();
+//
+//            return $machines ?: [];
+//
+//        } catch (\Exception $e) {
+//            throw new \Exception('查询机台数据失败: ' . $e->getMessage());
+//
+//        }
+//    }
+//
+//    /**
+//     * 获取所有机台(不分楼层)
+//     */
+//    protected function getAllMachines($month)
+//    {
+//        return Db::name('成本v23_月度成本明细')
+//            ->alias('c')
+//            ->join('设备_基本资料 e', 'c.sczl_jtbh = e.设备编号')
+//            ->where('c.sys_ny', $month)
+//            ->where('e.设备编号', '<>', '')
+//            ->field([
+//                'c.sczl_jtbh' => '机台编号',
+//                'c.占用机时',
+//                'e.设备编组',
+//                'c.sczl_gdbh' => '工单编号',
+//                'c.sczl_yjno' => '印件号',
+//                'c.sczl_gxh' => '工序号',
+//                'c.Uniqid',
+//                'c.车间名称'
+//            ])
+//            ->select();
+//    }
+//
+//    /**
+//     * 获取卷凹机组的所有机台
+//     */
+//    protected function getRollCoaterMachines($month)
+//    {
+//        return Db::name('成本v23_月度成本明细')
+//            ->alias('c')
+//            ->join('设备_基本资料 e', 'c.sczl_jtbh = e.设备编号')
+//            ->where('c.sys_ny', $month)
+//            ->where('e.设备编号', '<>', '')
+//            ->where('e.设备编组', 'like', '%03、卷凹机组%')
+//            ->field([
+//                'c.sczl_jtbh' => '机台编号',
+//                'c.占用机时',
+//                'e.设备编组',
+//                'c.sczl_gdbh' => '工单编号',
+//                'c.sczl_yjno' => '印件号',
+//                'c.sczl_gxh' => '工序号',
+//                'c.Uniqid',
+//                'c.车间名称'
+//            ])
+//            ->select();
+//    }
+//
+//    /**
+//     * 主计算方法
+//     */
+//    public function costCalculation()
+//    {
+//        if (!$this->request->isGet()) {
+//            $this->error('请求错误');
+//        }
+//
+//        $param = $this->request->param();
+//        if (empty($param['month'])) {
+//            $this->error('请选择月份');
+//        }
+//
+//        $month = $param['month'];
+//        $sysId = $param['sys_id'] ?? '';
+//
+//
+//        // 计算分摊
+//        $apportionmentResults = $this->calculateApportionment($month);
+//
+//        // 格式化结果并保存
+//        $formattedResults = $this->formatApportionmentResults($apportionmentResults, $month, $sysId);
+//        $this->saveApportionmentCoefficients($formattedResults, $month);
+//
+//        // 进行二次分配(到工单)
+//        $this->allocateToWorkOrders($month);
+//
+//        $this->success('成本分摊计算成功');
+//    }
+//
+//    /**
+//     * 计算分摊(核心方法)
+//     */
+//    protected function calculateApportionment($month)
+//    {
+//        // 获取水电气分摊数据
+//        $utilityData = $this->getUtilityData($month);
+//
+//        $results = [];
+//
+//        foreach ($utilityData as $item) {
+//            $subject = $this->simplifySubjectName($item['科目名称']);
+//            $amount = $this->calculateAmount($item);
+//
+//            if ($subject === self::SUBJECT_TOTAL_TO_APPORTION) {
+//                // 待分摊总额按楼层分摊
+//                $floorResults = $this->allocateByFloor($amount, $month);
+//
+//                foreach ($floorResults as $floor => $machineAllocations) {
+//                    foreach ($machineAllocations as $machineCode => $machineAmount) {
+//                        if (!isset($results[$machineCode])) {
+//                            $results[$machineCode] = [];
+//                        }
+//                        $results[$machineCode][$subject] = $machineAmount;
+//                    }
+//                }
+//            } elseif (in_array($subject, self::ROLL_COATER_ONLY_SUBJECTS)) {
+//                // 锅炉和热水锅炉只分配到卷凹机组
+//                $rollCoaterResults = $this->allocateToRollCoaterOnly($amount, $month);
+//
+//                foreach ($rollCoaterResults as $machineCode => $machineAmount) {
+//                    if (!isset($results[$machineCode])) {
+//                        $results[$machineCode] = [];
+//                    }
+//                    if (!isset($results[$machineCode][$subject])) {
+//                        $results[$machineCode][$subject] = $machineAmount;
+//                    }
+//                }
+//            } else {
+//                // 其他科目按所有机台分摊
+//                $globalResults = $this->allocateGlobally($amount, $month);
+//                foreach ($globalResults as $machineCode => $machineAmount) {
+//                    if (!isset($results[$machineCode])) {
+//                        $results[$machineCode] = [];
+//                    }
+//                    $results[$machineCode][$subject] = $machineAmount;
+//                }
+//            }
+//        }
+//
+//        return $results;
+//    }
+//
+//    /**
+//     * 获取水电气数据
+//     */
+//    protected function getUtilityData($month)
+//    {
+//        return Db::name('成本_各月水电气')
+//            ->where('Sys_ny', $month)
+//            ->whereLike('费用类型', '%分摊%')
+//            ->select();
+//    }
+//
+//    /**
+//     * 简化科目名称
+//     */
+//    protected function simplifySubjectName($subjectName)
+//    {
+//        $simplifiedMap = [
+//            '废气处理(RTO)' => self::SUBJECT_WASTE_GAS,
+//            '锅炉' => self::SUBJECT_BOILER,
+//            '热水锅炉' => self::SUBJECT_HOT_WATER_BOILER,
+//            '空压机' => self::SUBJECT_AIR_COMPRESSOR,
+//            '真空鼓风机' => self::SUBJECT_VACUUM_BLOWER,
+//            '中央空调' => self::SUBJECT_CENTRAL_AIR_CONDITIONER,
+//            '待分摊总额' => self::SUBJECT_TOTAL_TO_APPORTION,
+//        ];
+//
+//        foreach ($simplifiedMap as $keyword => $simplified) {
+//            if (strpos($subjectName, $keyword) !== false) {
+//                return $simplified;
+//            }
+//        }
+//
+//        return $subjectName;
+//    }
+//
+//    /**
+//     * 计算金额
+//     */
+//    protected function calculateAmount($item)
+//    {
+//        $electricity = ($this->getSafeNumericValue($item['耗电量']) * $this->getSafeNumericValue($item['单位电价']));
+//        $gas = ($this->getSafeNumericValue($item['耗气量']) * $this->getSafeNumericValue($item['单位气价']));
+//
+//        return round($electricity + $gas, 2);
+//    }
+//
+//    /**
+//     * 按楼层分摊
+//     */
+//    protected function allocateByFloor($totalAmount, $month)
+//    {
+//        $results = [];
+//
+//        // 计算每个楼层的总机时
+//        $floorTotalHours = [];
+//        foreach ([1, 2] as $floor) {
+//            $machines = $this->getMachinesByFloor($floor, $month);
+//            $totalHours = 0;
+//            foreach ($machines as $machine) {
+//                $totalHours += $machine['占用机时'];
+//            }
+//            $floorTotalHours[$floor] = $totalHours;
+//        }
+//
+//        $allFloorsTotal = array_sum($floorTotalHours);
+//        if ($allFloorsTotal <= 0) {
+//            return $results;
+//        }
+//
+//        // 按楼层比例分摊
+//        foreach ($floorTotalHours as $floor => $hours) {
+//            $floorAmount = round($totalAmount * ($hours / $allFloorsTotal), 2);
+//
+//            // 在楼层内按机台分摊
+//            $machines = $this->getMachinesByFloor($floor, $month);
+//            $floorResults = $this->allocateWithinGroup($floorAmount, $machines);
+//
+//            $results[$floor] = $floorResults;
+//        }
+//
+//        return $results;
+//    }
+//
+//    /**
+//     * 全局分摊(不分楼层)
+//     */
+//    protected function allocateGlobally($totalAmount, $month)
+//    {
+//        $machines = $this->getAllMachines($month);
+//        return $this->allocateWithinGroup($totalAmount, $machines);
+//    }
+//
+//    /**
+//     * 只分摊到卷凹机组
+//     */
+//    protected function allocateToRollCoaterOnly($totalAmount, $month)
+//    {
+//        $machines = $this->getRollCoaterMachines($month);
+//        return $this->allocateWithinGroup($totalAmount, $machines);
+//    }
+//
+//    /**
+//     * 在组内按机台运行时间分摊
+//     */
+//    protected function allocateWithinGroup($totalAmount, $machines)
+//    {
+//        $results = [];
+//
+//        if (empty($machines)) {
+//            return $results;
+//        }
+//
+//        $totalHours = 0;
+//        $machineHours = [];
+//
+//        foreach ($machines as $machine) {
+//            $hours = floatval($machine['占用机时']);
+//            $machineCode = $machine['机台编号'];
+//
+//            if ($hours > 0) {
+//                $totalHours += $hours;
+//                if (!isset($machineHours[$machineCode])) {
+//                    $machineHours[$machineCode] = $hours;
+//                } else {
+//                    $machineHours[$machineCode] += $hours;
+//                }
+//            }
+//        }
+//
+//        if ($totalHours <= 0) {
+//            return $results;
+//        }
+//
+//        foreach ($machineHours as $machineCode => $hours) {
+//            $results[$machineCode] = round($totalAmount * ($hours / $totalHours), 2);
+//        }
+//
+//        return $results;
+//    }
+//
+//    /**
+//     * 格式化分摊结果
+//     */
+//    protected function formatApportionmentResults($results, $month, $sysId)
+//    {
+//        $formatted = [];
+//        $now = date('Y-m-d H:i:s');
+//
+//        foreach ($results as $machineCode => $subjects) {
+//            foreach ($subjects as $subject => $amount) {
+//                $formatted[] = [
+//                    'Sys_ny' => $month,
+//                    '科目名称' => $subject,
+//                    '设备编号' => $machineCode,
+//                    '分摊系数' => 1,
+//                    '分摊金额' => $amount,
+//                    'Sys_id' => $sysId,
+//                    'Sys_rq' => $now,
+//                ];
+//            }
+//        }
+//
+//        return $formatted;
+//    }
+//
+//    /**
+//     * 保存分摊系数
+//     */
+//    protected function saveApportionmentCoefficients($data, $month)
+//    {
+//        // 删除旧数据
+//        Db::name('成本_各月分摊系数')->where('Sys_ny', $month)->delete();
+//
+//        if (!empty($data)) {
+//            $sql = Db::name('成本_各月分摊系数')->fetchSql(true)->insertAll($data);
+//            \db()->query($sql);
+//        }
+//    }
+//
+//    /**
+//     * 分摊到工单(二次分配)
+//     */
+//    protected function allocateToWorkOrders($month)
+//    {
+//        // 获取所有机台的分摊系数(每机时费用)
+//        $coefficients = $this->getMachineCoefficients($month);
+//
+//        // 获取所有机台的工单
+//        $workOrders = $this->getWorkOrdersByMonth($month);
+//
+//        $updates = [];
+//        foreach ($workOrders as $order) {
+//            $machineCode = $order['机台编号'];
+//
+//            if (!isset($coefficients[$machineCode])) {
+//                continue;
+//            }
+//
+//            $machineCoeffs = $coefficients[$machineCode];
+//            $hours = floatval($order['占用机时']);
+//
+//            if ($hours <= 0) {
+//                continue;
+//            }
+//
+//            // 计算各科目分摊金额
+//            $updateData = [
+//                '直接水电' => round($hours * 0.69, 2), // 直接水电费
+//            ];
+//
+//            foreach ($machineCoeffs as $subject => $rate) {
+//                $subjectKey = ($subject === '待分摊总额') ? '分摊水电' : $subject;
+//                $updateData[$subjectKey] = round($hours * $rate, 2);
+//            }
+//
+//            // 构建更新条件
+//            $where = [
+//                'sys_ny' => $month,
+//                'sczl_gdbh' => $order['工单编号'],
+//                'sczl_yjno' => $order['印件号'],
+//                'sczl_gxh' => $order['工序号'],
+//                'sczl_jtbh' => $machineCode,
+//            ];
+//
+//            $updates[] = [
+//                'where' => $where,
+//                'data' => $updateData
+//            ];
+//        }
+//
+//        // 批量更新
+//        $this->batchUpdateWorkOrders($updates, $month);
+//    }
+//
+//    /**
+//     * 获取机台分摊系数(每机时费用)
+//     */
+//    protected function getMachineCoefficients($month)
+//    {
+//        // 查询分摊系数和机台运行时间
+//        $data = Db::name('成本_各月分摊系数')
+//            ->alias('c')
+//            ->join('成本v23_月度成本明细 d', 'd.sys_ny = c.Sys_ny AND d.sczl_jtbh = c.设备编号')
+//            ->where('c.Sys_ny', $month)
+//            ->field([
+//                'c.设备编号',
+//                'c.科目名称',
+//                'c.分摊金额',
+//                'SUM(d.占用机时)' => 'total_hours'
+//            ])
+//            ->group('c.设备编号, c.科目名称')
+//            ->select();
+//
+//        $coefficients = [];
+//
+//        foreach ($data as $item) {
+//            $machineCode = $item['设备编号'];
+//            $subject = $item['科目名称'];
+//            $amount = floatval($item['分摊金额']);
+//            $hours = floatval($item['total_hours']);
+//
+//            if ($hours > 0) {
+//                $rate = round($amount / $hours, 4);
+//
+//                if (!isset($coefficients[$machineCode])) {
+//                    $coefficients[$machineCode] = [];
+//                }
+//
+//                $coefficients[$machineCode][$subject] = $rate;
+//            }
+//        }
+//
+//        return $coefficients;
+//    }
+//
+//    /**
+//     * 获取所有工单
+//     */
+//    protected function getWorkOrdersByMonth($month)
+//    {
+//        return Db::name('成本v23_月度成本明细')
+//            ->where('sys_ny', $month)
+//            ->field([
+//                'sczl_gdbh' => '工单编号',
+//                'sczl_yjno' => '印件号',
+//                'sczl_gxh' => '工序号',
+//                'sczl_jtbh' => '机台编号',
+//                '占用机时'
+//            ])
+//            ->select();
+//    }
+//
+//    /**
+//     * 批量更新工单数据
+//     */
+//    protected function batchUpdateWorkOrders($updates, $month)
+//    {
+//        $db = Db::name('成本v23_月度成本明细');
+//
+//        foreach ($updates as $update) {
+//            $sql = $db->where($update['where'])->fetchSql(true)->update($update['data']);
+//            $db->query($sql);
+//        }
+//    }
+//
+//    /**
+//     * 安全数值获取
+//     */
+//    protected function getSafeNumericValue($value)
+//    {
+//        if ($value === null || $value === '' || $value === false) {
+//            return 0;
+//        }
+//        return floatval($value);
+//    }
+
+// app/controller/UnifiedCostController.php
 
     /**
-     * 统一的安全数值获取方法
-     * 处理空字符串和null值,转换为0
+     * 执行成本计算
+     * @ApiMethod POST
+     * @param string month 年月
+     * @param string sys_id 系统ID
      */
-    protected function getSafeNumericValue($value)
+    public function calculate()
     {
-        if ($value === null || $value === '' || $value === false) {
-            return 0;
+        if (Request::instance()->isPost() == false) {
+            $this->error('非法请求');
         }
-        return floatval($value);
-    }
 
-    /**
-     * 安全的金额计算
-     */
-    protected function calculateSafeAmount($quantity, $unitPrice)
-    {
-        $safeQuantity = $this->getSafeNumericValue($quantity);
-        $safeUnitPrice = $this->getSafeNumericValue($unitPrice);
-        return $safeQuantity * $safeUnitPrice;
-    }
+        $params = Request::instance()->param();
 
-    public function costCalculation()
-    {
-        if (!$this->request->isGet()) {
-            $this->error('请求错误');
+        if (!isset($params['month']) || empty($params['month'])) {
+            $this->error('月份参数错误');
         }
 
-        $param = $this->request->param();
-        if (empty($param['month'])) {
-            $this->error('请选择月份');
-        }
-
-        $month = $param['month'];
+        $month = trim($params['month']);
+        $sysId = $params['sys_id'] ?? '';
 
-        // 计算车间水电气分摊(返回统计结果)
-        $apportionmentResults = $this->apportionmentOne($month);
-
-        // 格式化最终结果
-        $formattedResults = $this->formatFinalResults($apportionmentResults,$month,$param['sys_id']);
-        $machine = [];
-        foreach ($formattedResults as $formattedResult) {
-            $machine[] = $formattedResult['设备编号'];
-        }
-        $machineList = array_unique($machine);
-        $machineWorkOrder = [];
-        foreach ($machineList as $machine) {
-            $machineWorkOrder[$machine] = $this->getMachineWorkOrder($machine, $month);
-        }
-
-        $apportionData = db('成本_各月分摊系数')->where(['Sys_ny' => $month])->select();
-        if (!empty($apportionData)) {
-            db('成本_各月分摊系数')->where(['Sys_ny' => $month])->delete();
-        }
-        $apportionSql = db('成本_各月分摊系数')->fetchSql(true)->insertAll($formattedResults);
-        $apportionResults = db()->query($apportionSql);
-
-        //查询车间机台色度数
-        $machineTotal = $this->getMachineTotal($month);
-        $data = [];
-        foreach ($machineWorkOrder as $machineCode => $orders) {
-            // 检查机台编号是否存在于第二个数组中
-            if (!isset($machineTotal[$machineCode])) {
-                continue; // 如果机台在分摊数组中不存在,跳过
-            }
+        // 检查是否有正在执行的任务
+        $runningTask = Db::name('queue_tasks')
+            ->where('task_type', 'cost_calculation')
+            ->where('task_data', 'like', '%"month":"' . $month . '"%')
+            ->where('status', 'in', ['pending', 'processing'])
+            ->find();
 
-            $machineRates = $machineTotal[$machineCode]; // 获取该机台的费用单价
-
-            // 遍历该机台下的所有工单
-            foreach ($orders as $order) {
-                $occupationHours = (float)$order['占用机时']; // 占用机时
-
-                // 创建新记录(基础信息)
-                $newRecord = [
-                    'sczl_gdbh' => $order['工单编号'],
-                    'sczl_yjno' => $order['印件号'],
-                    'sczl_gxh' => $order['工序号'],
-                    'sczl_jtbh' => $order['机台编号']
-                ];
-
-                // 动态添加所有科目分摊金额(基于第二个数组中的科目名称)
-                foreach ($machineRates as $subject => $rate) {
-                    if ($subject === '待分摊总额') {
-                        $subject = '分摊水电';
-                    }
-                    $newRecord[$subject] = round($occupationHours * $rate, 2);
-                    $newRecord['直接水电'] = round($occupationHours * 0.69, 2);
-                }
-
-                $data[] = $newRecord;
-            }
-        }
-        $i = 0;
-        foreach ($data as $item) {
-            $gdbh = $item['sczl_gdbh'];
-            $yjno = $item['sczl_yjno'];
-            $gxh = $item['sczl_gxh'];
-            $jtbh = $item['sczl_jtbh'];
-            unset($item['sczl_gdbh'], $item['sczl_yjno'], $item['sczl_gxh'], $item['sczl_jtbh']);
-            $sql = db('成本v23_月度成本明细')
-                ->where([
-                    'sys_ny' => $month,
-                    'sczl_gdbh' => $gdbh,
-                    'sczl_yjno' => $yjno,
-                    'sczl_gxh' => $gxh,
-                    'sczl_jtbh' => $jtbh,
-                ])
-                ->fetchSql(true)
-                ->update($item);
-            $res = db()->query($sql);
-            if ($res === false) {
-                $i++;
-            }
+        if ($runningTask) {
+            $this->success('该月份的成本计算任务已在执行中,请勿重复提交');
         }
-        if ($i > 0) {
-            $this->error('失败');
-        }else{
-            $this->success('成功');
-        }
-
-    }
 
+        // 检查工资计算是否在执行中(可选)
+        $salaryRunning = Db::name('queue_tasks')
+            ->where('task_type', 'salary_calculation')
+            ->where('task_data', 'like', '%"date":"' . $month . '"%')
+            ->where('status', 'in', ['pending', 'processing'])
+            ->find();
 
-    /**
-     * 计算水电气分摊费用(优化版)
-     */
-    protected function apportionmentOne($month)
-    {
-        $utilityList = db('成本_各月水电气')
-            ->where('Sys_ny', $month)
-            ->whereLike('费用类型', '%分摊%')
-            ->select();
-
-        if (empty($utilityList)) {
-            return [];
-        }
-
-        // 初始化统计数据结构
-        $machineSummary = [];        // 按机台统计
-        $subjectSummary = [];        // 按科目统计
-        $workshopSummary = [];       // 按车间统计
-        $apportionmentResults = [];  // 原始分摊结果
-        $allMachineData = [];        // 所有机台数据
-
-        // 先处理所有分摊项目
-        foreach ($utilityList as $item) {
-            // 确保数值安全
-            $item['耗电量'] = $this->getSafeNumericValue($item['耗电量']);
-            $item['单位电价'] = $this->getSafeNumericValue($item['单位电价']);
-            $item['耗气量'] = $this->getSafeNumericValue($item['耗气量']);
-            $item['单位气价'] = $this->getSafeNumericValue($item['单位气价']);
-
-            $result = $this->processUtilityItem($item, $month);
-            if ($result) {
-                $apportionmentResults[] = $result;
-
-                // 合并所有机台数据
-                $this->mergeMachineData($result['data'], $allMachineData, $item['科目名称']);
-            }
+        if ($salaryRunning) {
+            $this->error('该月份的工资计算正在执行中,建议等待工资计算完成后再进行成本计算');
         }
 
-        // 按机台+科目进行汇总统计
-        $this->summarizeByMachineAndSubject($allMachineData, $machineSummary, $subjectSummary, $workshopSummary);
-
-        // 对统计结果进行排序
-        ksort($machineSummary);
-        return $machineSummary;
-    }
-
-    /**
-     * 合并所有机台数据
-     */
-    protected function mergeMachineData($machineData, &$allMachineData, $subjectName)
-    {
-        if (empty($machineData)) {
-            return;
-        }
-
-        foreach ($machineData as $machine) {
-            $machineId = $machine['机台编号'] ?? '';
-            $workshop = $machine['车间名称'] ?? '未知车间';
-            $workOrder = $machine['工单编号'] ?? '';
-            $processNo = $machine['工序号'] ?? '';
-            $printNo = $machine['印件号'] ?? '';
-            $machineTime = floatval($machine['占用机时'] ?? 0);
-            $uniqid = $machine['Uniqid'] ?? '';
-
-            if (empty($machineId)) {
-                continue;
-            }
-
-            // 初始化机台数据
-            if (!isset($allMachineData[$machineId])) {
-                $allMachineData[$machineId] = [
-                    '机台编号' => $machineId,
-                    '车间名称' => $workshop,
-                    '占用机时' => $machineTime,
-                    '工单编号' => $workOrder,
-                    '工序号' => $processNo,
-                    '印件号' => $printNo,
-                    'Uniqid' => $uniqid,
-                    '科目明细' => [],
-                    '工单列表' => [],
-                    '科目总计' => 0
-                ];
-            }
-
-            // 更新机台基础信息(如果有更完整的信息)
-            if (empty($allMachineData[$machineId]['工单编号']) && !empty($workOrder)) {
-                $allMachineData[$machineId]['工单编号'] = $workOrder;
-            }
-            if (empty($allMachineData[$machineId]['工序号']) && !empty($processNo)) {
-                $allMachineData[$machineId]['工序号'] = $processNo;
-            }
-            if (empty($allMachineData[$machineId]['印件号']) && !empty($printNo)) {
-                $allMachineData[$machineId]['印件号'] = $printNo;
-            }
-
-            // 记录工单(去重)
-            if (!empty($workOrder) && !in_array($workOrder, $allMachineData[$machineId]['工单列表'])) {
-                $allMachineData[$machineId]['工单列表'][] = $workOrder;
-            }
-
-            // 合并科目金额
-            $apportionmentTypes = ['分摊水电', '废气处理', '锅炉', '热水锅炉',
-                '空压机A', '空压机B', '真空鼓风机A', '真空鼓风机B',
-                '中央空调A', '中央空调B'];
-
-            $machineTotal = 0;
-            foreach ($apportionmentTypes as $type) {
-                if (isset($machine[$type]) && floatval($machine[$type]) > 0) {
-                    $amount = floatval($machine[$type]);
-                    $machineTotal += $amount;
-
-                    // 初始化科目明细
-                    if (!isset($allMachineData[$machineId]['科目明细'][$type])) {
-                        $allMachineData[$machineId]['科目明细'][$type] = [
-                            'subject_name' => $type,
-                            'total_amount' => 0,
-                            'source_count' => 0,
-                            'source_subjects' => ''
-                        ];
-                    }
-
-                    // 累加金额
-                    $allMachineData[$machineId]['科目明细'][$type]['total_amount'] += $amount;
-                    $allMachineData[$machineId]['科目明细'][$type]['source_count']++;
-
-                    // 记录来源科目
-                    $allMachineData[$machineId]['科目明细'][$type]['source_subjects'] = $subjectName;
-
-                }
-            }
-
-            // 更新机台总计
-            $allMachineData[$machineId]['科目总计'] += $machineTotal;
-        }
-    }
-
-    /**
-     * 按机台+科目进行汇总统计
-     */
-    protected function summarizeByMachineAndSubject($allMachineData, &$machineSummary, &$subjectSummary, &$workshopSummary)
-    {
-        foreach ($allMachineData as $machineId => $machineInfo) {
-            $workshop = $machineInfo['车间名称'];
-
-            // 1. 按机台统计
-            $machineSummary[$machineId] = [
-                '机台编号' => $machineId,
-                '车间名称' => $workshop,
-                '工单数量' => count($machineInfo['工单列表']),
-                '占用机时' => $machineInfo['占用机时'],
-                '科目总计' => round($machineInfo['科目总计'], 2),
-                '科目明细' => [],
-                '工单列表' => $machineInfo['工单列表']
-            ];
-
-            // 处理科目明细
-            foreach ($machineInfo['科目明细'] as $subjectType => $subjectDetail) {
-                $amount = round($subjectDetail['total_amount'], 2);
-                $machineSummary[$machineId]['科目明细'][$subjectType] = [
-                    'amount' => $amount,
-                    'source_count' => $subjectDetail['source_count'],
-                    'source_subjects' => $subjectDetail['source_subjects'],
-                    'percentage' => $machineInfo['科目总计'] > 0
-                        ? round(($subjectDetail['total_amount'] / $machineInfo['科目总计']) * 100, 2)
-                        : 0
-                ];
-            }
-
-            // 2. 按科目统计
-            foreach ($machineInfo['科目明细'] as $subjectType => $subjectDetail) {
-                $amount = round($subjectDetail['total_amount'], 2);
-
-                // 初始化科目统计
-                if (!isset($subjectSummary[$subjectType])) {
-                    $subjectSummary[$subjectType] = [
-                        'subject_name' => $subjectType,
-                        'total_amount' => 0,
-                        'machine_count' => 0,
-                        'workshop_distribution' => [],
-                        'machine_list' => []
-                    ];
-                }
-
-                // 累加科目总计
-                $subjectSummary[$subjectType]['total_amount'] += $subjectDetail['total_amount'];
-                $subjectSummary[$subjectType]['machine_count']++;
-
-                // 按车间分布
-                if (!isset($subjectSummary[$subjectType]['workshop_distribution'][$workshop])) {
-                    $subjectSummary[$subjectType]['workshop_distribution'][$workshop] = 0;
-                }
-                $subjectSummary[$subjectType]['workshop_distribution'][$workshop] += $subjectDetail['total_amount'];
-
-                // 机台列表
-                $subjectSummary[$subjectType]['machine_list'][] = [
-                    'machine_id' => $machineId,
-                    'workshop' => $workshop,
-                    'amount' => $amount,
-                    'percentage' => $subjectSummary[$subjectType]['total_amount'] > 0
-                        ? round(($subjectDetail['total_amount'] / $subjectSummary[$subjectType]['total_amount']) * 100, 2)
-                        : 0
-                ];
-            }
-
-            // 3. 按车间统计
-            if (!isset($workshopSummary[$workshop])) {
-                $workshopSummary[$workshop] = [
-                    'workshop_name' => $workshop,
-                    'machine_count' => 0,
-                    'total_amount' => 0,
-                    'subject_distribution' => [],
-                    'machine_list' => []
-                ];
-            }
-
-            $workshopSummary[$workshop]['machine_count']++;
-            $workshopSummary[$workshop]['total_amount'] += $machineInfo['科目总计'];
-
-            // 车间内科目分布
-            foreach ($machineInfo['科目明细'] as $subjectType => $subjectDetail) {
-                if (!isset($workshopSummary[$workshop]['subject_distribution'][$subjectType])) {
-                    $workshopSummary[$workshop]['subject_distribution'][$subjectType] = 0;
-                }
-                $workshopSummary[$workshop]['subject_distribution'][$subjectType] += $subjectDetail['total_amount'];
-            }
-
-            // 车间内机台列表
-            $workshopSummary[$workshop]['machine_list'][] = [
-                'machine_id' => $machineId,
-                'total_amount' => round($machineInfo['科目总计'], 2),
-                'subject_count' => count($machineInfo['科目明细']),
-                'work_order_count' => count($machineInfo['工单列表'])
-            ];
-        }
-
-        // 对科目统计进行后处理
-        $this->processSubjectSummary($subjectSummary);
-
-        // 对车间统计进行后处理
-        $this->processWorkshopSummary($workshopSummary);
-    }
-
-
-    /**
-     * 处理科目统计
-     */
-    protected function processSubjectSummary(&$subjectSummary)
-    {
-        foreach ($subjectSummary as &$subject) {
-            // 金额四舍五入
-            $subject['total_amount'] = round($subject['total_amount'], 2);
-
-            // 车间分布百分比
-            foreach ($subject['workshop_distribution'] as $workshop => &$amount) {
-                $amount = round($amount, 2);
-            }
-
-            // 按金额排序机台列表
-            usort($subject['machine_list'], function($a, $b) {
-                return $b['amount'] <=> $a['amount'];
-            });
-
-            // 计算机台平均金额
-            $subject['avg_per_machine'] = $subject['machine_count'] > 0
-                ? round($subject['total_amount'] / $subject['machine_count'], 2)
-                : 0;
-        }
-    }
-
-    /**
-     * 处理车间统计
-     */
-    protected function processWorkshopSummary(&$workshopSummary)
-    {
-        foreach ($workshopSummary as &$workshop) {
-            // 金额四舍五入
-            $workshop['total_amount'] = round($workshop['total_amount'], 2);
-
-            // 科目分布百分比
-            $workshop['subject_percentage'] = [];
-            foreach ($workshop['subject_distribution'] as $subjectType => $amount) {
-                $roundedAmount = round($amount, 2);
-                $workshop['subject_distribution'][$subjectType] = $roundedAmount;
-
-                if ($workshop['total_amount'] > 0) {
-                    $workshop['subject_percentage'][$subjectType] =
-                        round(($roundedAmount / $workshop['total_amount']) * 100, 2);
-                }
-            }
-
-            // 按金额排序机台列表
-            usort($workshop['machine_list'], function($a, $b) {
-                return $b['total_amount'] <=> $a['total_amount'];
-            });
-
-            // 计算机台平均金额
-            $workshop['avg_per_machine'] = $workshop['machine_count'] > 0
-                ? round($workshop['total_amount'] / $workshop['machine_count'], 2)
-                : 0;
-
-            // 统计科目种类数
-            $workshop['subject_count'] = count($workshop['subject_distribution']);
-        }
-    }
-
-    /**
-     * 处理单个水电费用项
-     */
-    protected function processUtilityItem($item, $month)
-    {
-        $subjectName = $item['科目名称'];
-
-        // 使用switch-like结构提高可读性
-        $processMap = [
-            self::SUBJECT_TOTAL_TO_APPORTION => 'processTotalApportionment',
-            self::SUBJECT_WASTE_GAS => 'processWasteGas',
-            self::SUBJECT_BOILER => 'processBoiler',
-            self::SUBJECT_AIR_COMPRESSOR_A => 'processAirCompressorA',
-            self::SUBJECT_AIR_COMPRESSOR_B => 'processAirCompressorB',
-            self::SUBJECT_VACUUM_BLOWER_A => 'processVacuumBlowerA',
-            self::SUBJECT_VACUUM_BLOWER_B => 'processVacuumBlowerB',
-            self::SUBJECT_CENTRAL_AIR_CONDITIONER_A => 'processCentralAirConditionerA',
-            self::SUBJECT_CENTRAL_AIR_CONDITIONER_B => 'processCentralAirConditionerB',
-            self::SUBJECT_HOT_WATER_BOILER => 'processHotWaterBoiler',
+        // 准备任务数据
+        $taskData = [
+            'month' => $month,
+            'sys_id' => $sysId,
+            'user_id' => session('user_id') ?? 0,
+            'user_name' => session('user_name') ?? '系统',
+            'request_time' => date('Y-m-d H:i:s')
         ];
 
-        // 检查是否完全匹配
-        if (isset($processMap[$subjectName])) {
-            $method = $processMap[$subjectName];
-            return $this->$method($item, $month);
-        }
-
-        // 检查是否包含关键词
-        foreach ($processMap as $keyword => $method) {
-            if (strpos($subjectName, $keyword) !== false) {
-                // 特殊处理:如果包含"锅炉"但不包含"热水锅炉"
-                if ($keyword === self::SUBJECT_BOILER && strpos($subjectName, self::SUBJECT_HOT_WATER_BOILER) !== false) {
-                    continue;
-                }
-                return $this->$method($item, $month);
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * 处理待分摊总额
-     */
-    protected function processTotalApportionment($item, $month)
-    {
-        $money = $this->calculateSafeAmount($item['耗电量'], $item['单位电价']);
-        $data = $this->getMachineTime($item['部门名称'], $month);
+        // 先创建任务记录
+        $taskId = Db::name('queue_tasks')->insertGetId([
+            'task_type' => 'cost_calculation',
+            'task_data' => json_encode($taskData, JSON_UNESCAPED_UNICODE),
+            'status' => 'pending',
+            'queue_name' => 'cost_calculation',
+            'create_time' => date('Y-m-d H:i:s')
+        ]);
+
+        // 添加到任务数据中
+        $taskData['task_id'] = $taskId;
+
+        // 提交到成本计算队列
+        $jobHandlerClassName = 'app\job\CostCalculationJob';
+        $queueName = 'cost_calculation';
+
+        $jobId = Queue::push($jobHandlerClassName, $taskData, $queueName);
+
+        if ($jobId) {
+            // 更新任务记录
+            Db::name('queue_tasks')
+                ->where('id', $taskId)
+                ->update([
+                    'job_id' => $jobId,
+                    'update_time' => date('Y-m-d H:i:s')
+                ]);
+
+            $this->success('成本计算任务已提交到队列,请稍后查看结果', null, [
+                'task_id' => $taskId,
+                'job_id' => $jobId,
+                'month' => $month,
+                'queue_name' => $queueName
+            ]);
+        } else {
+            // 更新任务状态为失败
+            Db::name('queue_tasks')
+                ->where('id', $taskId)
+                ->update([
+                    'status' => 'failed',
+                    'error' => '任务提交到队列失败',
+                    'update_time' => date('Y-m-d H:i:s')
+                ]);
 
-        foreach ($data['machine'] as &$machine) {
-            $machine['分摊水电'] = $this->calculateApportionment($money, $machine['占用机时'], $data['sist']);
+            $this->error('成本计算任务提交失败');
         }
-
-        return [
-            'type' => 'total_apportionment',
-            'data' => $data['machine']
-        ];
     }
 
     /**
-     * 处理废气处理
+     * 查询成本计算状态
+     * @ApiMethod GET
+     * @param string month 年月
      */
-    protected function processWasteGas($item, $month)
+    public function status()
     {
-        $electricityMoney = $this->calculateSafeAmount($item['耗电量'], $item['单位电价']);
-        $gasMoney = $this->calculateSafeAmount($item['耗气量'], $item['单位气价']);
-        $data = $this->getMachineTime(self::WORKSHOP_ROLLER_PRESS, $month);
-
-        foreach ($data['machine'] as &$machine) {
-            $machine['分摊水电'] = $this->calculateApportionment($electricityMoney, $machine['占用机时'], $data['sist']);
-            $machine['废气处理'] = $this->calculateApportionment($gasMoney, $machine['占用机时'], $data['sist']);
-        }
-
-        return [
-            'type' => 'waste_gas',
-            'data' => $data['machine']
-        ];
-    }
-
-    /**
-     * 处理锅炉
-     */
-    protected function processBoiler($item, $month)
-    {
-        $electricityMoney = $this->calculateSafeAmount($item['耗电量'], $item['单位电价']);
-        $gasMoney = $this->calculateSafeAmount($item['耗气量'], $item['单位气价']);
-        $data = $this->getMachineTime(self::WORKSHOP_ROLLER_PRESS, $month);
-
-        foreach ($data['machine'] as &$machine) {
-            $machine['分摊水电'] = $this->calculateApportionment($electricityMoney, $machine['占用机时'], $data['sist']);
-            $machine['锅炉'] = $this->calculateApportionment($gasMoney, $machine['占用机时'], $data['sist']);
+        $month = Request::instance()->param('month');
+
+        if (empty($month)) {
+            $this->error('月份参数错误');
+        }
+
+        // 查询任务记录
+        $task = Db::name('queue_tasks')
+            ->where('task_type', 'cost_calculation')
+            ->where('task_data', 'like', '%"month":"' . $month . '"%')
+            ->order('id', 'desc')
+            ->find();
+
+        if ($task) {
+            $result = json_decode($task['result'] ?? '{}', true);
+            $taskData = json_decode($task['task_data'] ?? '{}', true);
+
+            $response = [
+                'exists' => true,
+                'task_id' => $task['id'],
+                'month' => $month,
+                'status' => $task['status'],
+                'queue_name' => $task['queue_name'],
+                'job_id' => $task['job_id'],
+                'start_time' => $task['start_time'],
+                'end_time' => $task['end_time'],
+                'retry_count' => $task['retry_count'],
+                'result' => $result,
+                'error' => $task['error'] ?? '',
+                'create_time' => $task['create_time'],
+                'user_info' => [
+                    'user_id' => $taskData['user_id'] ?? 0,
+                    'user_name' => $taskData['user_name'] ?? ''
+                ]
+            ];
+        } else {
+            $response = ['exists' => false, 'month' => $month];
         }
 
-        return [
-            'type' => 'boiler',
-            'data' => $data['machine']
-        ];
-    }
-
-    /**
-     * 处理空压机A
-     */
-    protected function processAirCompressorA($item, $month)
-    {
-        return $this->processGeneralElectricEquipment($item, $month, self::SUBJECT_AIR_COMPRESSOR_A);
-    }
-
-    /**
-     * 处理空压机B
-     */
-    protected function processAirCompressorB($item, $month)
-    {
-        return $this->processGeneralElectricEquipment($item, $month, self::SUBJECT_AIR_COMPRESSOR_B);
-    }
-
-    /**
-     * 处理真空鼓风机A
-     */
-    protected function processVacuumBlowerA($item, $month)
-    {
-        return $this->processGeneralElectricEquipment($item, $month, self::SUBJECT_VACUUM_BLOWER_A);
-    }
-
-    /**
-     * 处理真空鼓风机B
-     */
-    protected function processVacuumBlowerB($item, $month)
-    {
-        return $this->processGeneralElectricEquipment($item, $month, self::SUBJECT_VACUUM_BLOWER_B);
-    }
-
-    /**
-     * 处理中央空调A
-     */
-    protected function processCentralAirConditionerA($item, $month)
-    {
-        return $this->processGeneralElectricEquipment($item, $month, self::SUBJECT_CENTRAL_AIR_CONDITIONER_A);
-    }
-
-    /**
-     * 处理中央空调B
-     */
-    protected function processCentralAirConditionerB($item, $month)
-    {
-        return $this->processGeneralElectricEquipment($item, $month, self::SUBJECT_CENTRAL_AIR_CONDITIONER_B);
+        $this->success('查询成功', null, $response);
     }
 
     /**
-     * 处理热水锅炉
+     * 获取成本计算任务列表
+     * @ApiMethod GET
      */
-    protected function processHotWaterBoiler($item, $month)
+    public function list()
     {
-        return $this->processGeneralElectricEquipment($item, $month, '热水锅炉');
-    }
+        $page = Request::instance()->param('page', 1);
+        $limit = Request::instance()->param('limit', 20);
+        $month = Request::instance()->param('month');
+        $status = Request::instance()->param('status');
 
-    /**
-     * 通用电气设备处理(适用于所有车间)
-     */
-    protected function processGeneralElectricEquipment($item, $month, $equipmentType)
-    {
-        $money = $this->calculateSafeAmount($item['耗电量'], $item['单位电价']);
-        $data = $this->getMachineTime(self::WORKSHOP_LIST_ALL, $month);
+        $query = Db::name('queue_tasks')
+            ->where('task_type', 'cost_calculation');
 
-        foreach ($data['machine'] as &$machine) {
-            $machine[$equipmentType] = $this->calculateApportionment($money, $machine['占用机时'], $data['sist']);
+        if ($month) {
+            $query->where('task_data', 'like', '%"month":"' . $month . '"%');
         }
 
-        return [
-            'type' => $equipmentType,
-            'data' => $data['machine']
-        ];
-    }
-
-    /**
-     * 计算分摊金额
-     */
-    protected function calculateApportionment($totalAmount, $machineTime, $totalTime)
-    {
-        if ($totalTime <= 0) {
-            return 0;
-        }
-        return round($totalAmount * ($machineTime / $totalTime), 2);
-    }
-
-    /**
-     * 查询车间机台通电机时数据(优化版)
-     */
-    protected function getMachineTime($workshop, $month)
-    {
-        $where = [
-            'a.sys_ny' => $month,
-            'b.sys_sbID' => ['<>','']
-        ];
-
-        // 构建车间查询条件
-        if (is_array($workshop)) {
-            $where['a.车间名称'] = ['in', $workshop];
-        } else {
-            if (strpos($workshop, '机组') !== false) {
-                $where['b.设备编组'] = $workshop;
-            } elseif (strpos($workshop, '车间') !== false) {
-                $where['a.车间名称'] = $workshop;
-            }
+        if ($status) {
+            $query->where('status', $status);
         }
 
-        // 查询机台数据
-        $machine = db('成本v23_月度成本明细')
-            ->alias('a')
-            ->join('设备_基本资料 b', 'a.sczl_jtbh = b.设备编号', 'left')
-            ->field('rtrim(车间名称) as 车间名称,a.sczl_gdbh as 工单编号,a.sczl_yjno as 印件号,a.sczl_gxh as 工序号,a.sczl_jtbh as 机台编号,a.占用机时,a.Uniqid')
-            ->where($where)
+        $total = $query->count();
+        $list = $query->order('id', 'desc')
+            ->page($page, $limit)
             ->select();
 
-        // 查询总时长
-        $totalTime = db('成本v23_月度成本明细')
-            ->alias('a')
-            ->join('设备_基本资料 b', 'a.sczl_jtbh = b.设备编号', 'left')
-            ->where($where)
-            ->value('sum(a.占用机时) as 占用机时', 0);
-
-        return [
-            'machine' => $machine,
-            'sist' => $totalTime
-        ];
-    }
-
-
-    /**
-     * 统计分摊结果
-     */
-    protected function summarizeApportionment($result, $department, $subject,
-                                              &$summaryByDepartment, &$summaryBySubject,
-                                              &$summaryByDepartmentSubject)
-    {
-        $resultType = $result['type'];
-        $machineData = $result['data'];
-
-        if (empty($machineData)) {
-            return;
-        }
-
-        // 清洗部门名称
-        $cleanDepartment = trim($department);
-        $cleanSubject = trim($subject);
-
-        // 初始化统计键
-        if (!isset($summaryByDepartment[$cleanDepartment])) {
-            $summaryByDepartment[$cleanDepartment] = [];
-        }
-        if (!isset($summaryBySubject[$resultType])) {
-            $summaryBySubject[$resultType] = [];
-        }
-        if (!isset($summaryByDepartmentSubject[$cleanDepartment])) {
-            $summaryByDepartmentSubject[$cleanDepartment] = [];
-        }
-        if (!isset($summaryByDepartmentSubject[$cleanDepartment][$resultType])) {
-            $summaryByDepartmentSubject[$cleanDepartment][$resultType] = [
-                'department' => $cleanDepartment,
-                'subject' => $cleanSubject,
-                'result_type' => $resultType,
-                'total_amount' => 0,
-                'machine_count' => 0,
-                'work_order_count' => 0
-            ];
-        }
+        // 解析任务数据
+        foreach ($list as &$item) {
+            $taskData = json_decode($item['task_data'] ?? '{}', true);
+            $item['month'] = $taskData['month'] ?? '';
+            $item['user_name'] = $taskData['user_name'] ?? '';
+            $item['request_time'] = $taskData['request_time'] ?? '';
 
-        // 按车间和机台统计
-        foreach ($machineData as $machine) {
-            // 获取车间名称(已使用rtrim清洗过)
-            $workshop = isset($machine['车间名称']) ? $machine['车间名称'] : '未知车间';
-
-            // 统计按部门
-            if (!isset($summaryByDepartment[$cleanDepartment][$workshop])) {
-                $summaryByDepartment[$cleanDepartment][$workshop] = [
-                    'total_amount' => 0,
-                    'machine_count' => 0,
-                    'subjects' => []
-                ];
-            }
-
-            // 统计按科目
-            if (!isset($summaryBySubject[$resultType][$workshop])) {
-                $summaryBySubject[$resultType][$workshop] = [
-                    'total_amount' => 0,
-                    'machine_count' => 0,
-                    'departments' => []
-                ];
-            }
-
-            // 计算此机台的总分摊金额
-            $machineTotal = 0;
-            $apportionmentTypes = ['分摊水电', '废气处理', '锅炉', '热水锅炉',
-                '空压机A', '空压机B', '真空鼓风机A', '真空鼓风机B',
-                '中央空调A', '中央空调B'];
-
-            foreach ($apportionmentTypes as $type) {
-                if (isset($machine[$type])) {
-                    $amount = floatval($machine[$type]);
-                    $machineTotal += $amount;
-
-                    // 按部门+车间统计各科目金额
-                    if (!isset($summaryByDepartment[$cleanDepartment][$workshop]['subjects'][$type])) {
-                        $summaryByDepartment[$cleanDepartment][$workshop]['subjects'][$type] = 0;
-                    }
-                    $summaryByDepartment[$cleanDepartment][$workshop]['subjects'][$type] += $amount;
-
-                    // 按科目+车间统计各部门金额
-                    if (!in_array($cleanDepartment, $summaryBySubject[$resultType][$workshop]['departments'])) {
-                        $summaryBySubject[$resultType][$workshop]['departments'][] = $cleanDepartment;
-                    }
-                }
-            }
-
-            // 更新统计信息
-            if ($machineTotal > 0) {
-                $summaryByDepartment[$cleanDepartment][$workshop]['total_amount'] += $machineTotal;
-                $summaryByDepartment[$cleanDepartment][$workshop]['machine_count']++;
-
-                $summaryBySubject[$resultType][$workshop]['total_amount'] += $machineTotal;
-                $summaryBySubject[$resultType][$workshop]['machine_count']++;
-
-                $summaryByDepartmentSubject[$cleanDepartment][$resultType]['total_amount'] += $machineTotal;
-                $summaryByDepartmentSubject[$cleanDepartment][$resultType]['machine_count']++;
-
-                // 统计工单数(去重)
-                if (!isset($summaryByDepartmentSubject[$cleanDepartment][$resultType]['work_orders'])) {
-                    $summaryByDepartmentSubject[$cleanDepartment][$resultType]['work_orders'] = [];
-                }
-                if (isset($machine['工单编号'])) {
-                    $summaryByDepartmentSubject[$cleanDepartment][$resultType]['work_orders'][] = $machine['工单编号'];
-                }
+            if (!empty($item['result'])) {
+                $item['result_data'] = json_decode($item['result'], true);
             }
         }
 
-        // 计算工单数(去重)
-        foreach ($summaryByDepartmentSubject[$cleanDepartment] as $type => &$data) {
-            if (isset($data['work_orders'])) {
-                $data['work_order_count'] = count(array_unique($data['work_orders']));
-                unset($data['work_orders']);
-            }
-        }
-
-        // 为部门统计添加汇总信息
-        $this->addSummaryToDepartment($summaryByDepartment[$cleanDepartment]);
-
-        // 为科目统计添加汇总信息
-        $this->addSummaryToSubject($summaryBySubject[$resultType]);
+        $this->success('查询成功', null, [
+            'list' => $list,
+            'total' => $total,
+            'page' => $page,
+            'pages' => ceil($total / $limit)
+        ]);
     }
 
-
     /**
-     * 为部门统计添加汇总信息
+     * 手动重试失败的任务
+     * @ApiMethod POST
+     * @param int task_id 任务ID
      */
-    protected function addSummaryToDepartment(&$departmentData)
+    public function retry()
     {
-        $totalAmount = 0;
-        $totalMachines = 0;
+        $taskId = Request::instance()->param('task_id');
 
-        foreach ($departmentData as $workshop => $data) {
-            if ($workshop === 'summary') continue;
-
-            $totalAmount += $data['total_amount'];
-            $totalMachines += $data['machine_count'];
+        if (empty($taskId)) {
+            $this->error('任务ID不能为空');
         }
 
-        $departmentData['summary'] = [
-            'total_amount' => round($totalAmount, 2),
-            'total_machines' => $totalMachines,
-            'workshop_count' => count($departmentData) - 1
-        ];
-    }
-
-    /**
-     * 为科目统计添加汇总信息
-     */
-    protected function addSummaryToSubject(&$subjectData)
-    {
-        $totalAmount = 0;
-        $totalMachines = 0;
-        $allDepartments = [];
-
-        foreach ($subjectData as $workshop => $data) {
-            if ($workshop === 'summary') continue;
+        $task = Db::name('queue_tasks')
+            ->where('id', $taskId)
+            ->where('task_type', 'cost_calculation')
+            ->where('status', 'failed')
+            ->find();
 
-            $totalAmount += $data['total_amount'];
-            $totalMachines += $data['machine_count'];
-            $allDepartments = array_merge($allDepartments, $data['departments']);
+        if (!$task) {
+            $this->error('任务不存在或无法重试');
         }
 
-        $subjectData['summary'] = [
-            'total_amount' => round($totalAmount, 2),
-            'total_machines' => $totalMachines,
-            'workshop_count' => count($subjectData) - 1,
-            'department_count' => count(array_unique($allDepartments))
-        ];
-    }
+        // 解析原任务数据
+        $taskData = json_decode($task['task_data'], true);
+        $taskData['task_id'] = $taskId;
+        $taskData['retry_time'] = date('Y-m-d H:i:s');
 
+        // 更新原任务状态
+        Db::name('queue_tasks')
+            ->where('id', $taskId)
+            ->update([
+                'status' => 'retrying',
+                'update_time' => date('Y-m-d H:i:s')
+            ]);
 
-    /**
-     * 格式化最终统计结果
-     */
-    protected function formatFinalResults($apportionmentResults,$month,$sys)
-    {
-        $data = [];
-        foreach ($apportionmentResults as $key => $value) {
-            foreach ($value['科目明细'] as $item) {
-                $data[] = [
-                    'Sys_ny' => $month,
-                    '科目名称' => $item['source_subjects'],
-                    '设备编号' => $key,
-                    '分摊系数' => 1,
-                    '分摊金额' => $item['amount'],
-                    'Sys_id' => $sys,
-                    'Sys_rq' => date('Y-m-d H:i:s',time()),
-                ];
-            }
-        }
-        return $data;
-    }
-
-    /**
-     * 获取车间机台生产工单
-     * @param $sist
-     * @param $month
-     * @return bool|\PDOStatement|string|\think\Collection
-     * @throws \think\db\exception\DataNotFoundException
-     * @throws \think\db\exception\ModelNotFoundException
-     * @throws \think\exception\DbException
-     */
-    protected function getMachineWorkOrder($machine,$month)
-    {
-        $list = db('成本v23_月度成本明细')
-            ->where([
-                'sys_ny' => $month,
-                'sczl_jtbh' => $machine,
-                ])
-            ->field([
-                'sczl_gdbh' => '工单编号',
-                'sczl_yjno' => '印件号',
-                'sczl_gxh' => '工序号',
-                'sczl_jtbh' => '机台编号',
-                '占用机时',
-                ])
-            ->group('工单编号,印件号,工序号')
-            ->select();
-        return $list;
-    }
+        // 提交到队列
+        $jobHandlerClassName = 'app\job\CostCalculationJob';
+        $queueName = 'cost_calculation';
 
-    /**
-     * 查询车间机台色度数
-     * @param $month
-     * @return bool|\PDOStatement|string|\think\Collection
-     * @throws \think\db\exception\DataNotFoundException
-     * @throws \think\db\exception\ModelNotFoundException
-     * @throws \think\exception\DbException
-     */
-    protected function getMachineTotal($month)
-    {
-        $list = db('成本_各月分摊系数')
-            ->alias('a')
-            ->join('成本v23_月度成本明细 b', 'b.sys_ny = a.Sys_ny and b.sczl_jtbh = a.设备编号','LEFT')
-            ->where([
-                'a.Sys_ny' => $month,
-                'b.车间名称' => ['in', self::WORKSHOP_LIST_ALL],
-                ])
-            ->field([
-                'b.sczl_jtbh' => '机台编号',
-                'sum(b.占用机时)' => '占用机时',
-                'a.分摊金额',
-                'a.科目名称',
-            ])
-            ->group('机台编号,a.科目名称')
-            ->select();
-        foreach ($list as $key => $item) {
-            $list[$key]['金额'] = round($item['分摊金额']/$item['占用机时'], 2);
-        }
-        $data = [];
-        foreach ($list as $item) {
-            $machineCode = $item['机台编号'];
-            $subjectName = explode('(',$item['科目名称']);
-            $amount = $item['金额'];
-
-            // 如果这个机台编号还没有在结果数组中,初始化它
-            if (!isset($data[$machineCode])) {
-                $data[$machineCode] = [];
-            }
+        $jobId = Queue::push($jobHandlerClassName, $taskData, $queueName);
 
-            // 将科目名称和金额添加到对应的机台编号下
-            $data[$machineCode][$subjectName[0]] = $amount;
+        if ($jobId) {
+            $this->success('任务已重新提交到队列', null, [
+                'task_id' => $taskId,
+                'new_job_id' => $jobId,
+                'queue_name' => $queueName
+            ]);
+        } else {
+            $this->error('任务重试失败');
         }
-        return $data;
     }
 
 }

+ 111 - 11
application/api/controller/StaffSalary.php

@@ -1,6 +1,15 @@
 <?php
 namespace app\api\controller;
 
+//use app\common\controller\Api;
+//use \think\Request;
+//use think\Db;
+//use app\service\InsertDataJob;
+//use think\Queue;
+//use think\Cache;
+//use think\cache\driver\Redis;
+//use function fast\e;
+
 use app\common\controller\Api;
 use \think\Request;
 use think\Db;
@@ -8,8 +17,6 @@ use app\job\InsertDataJob;
 use think\Queue;
 use think\Cache;
 use think\cache\driver\Redis;
-use function fast\e;
-
 /**
  * 员工计件工资核算
  */
@@ -66,9 +73,14 @@ class StaffSalary extends Api
             'prefix'     => '',
         ];
         $redis = new Redis($options);
-        $taskIdentifier = md5(json_encode('date'));
-        $queueKey =  $redis->get($taskIdentifier);
-        if ($queueKey){
+//        $taskIdentifier = md5(json_encode('date'));
+//        $queueKey =  $redis->get($taskIdentifier);
+//        if ($queueKey){
+//            $this->success('数据正在处理中,请等待...');
+//        }
+        $taskIdentifier = md5('salary_calculation_' . json_encode($params));
+
+        if ($redis->has($taskIdentifier)) {
             $this->success('数据正在处理中,请等待...');
         }
         $vacationOneArr = [];
@@ -613,14 +625,102 @@ class StaffSalary extends Api
             $data[$key]['法定天数'] = $params['days'];
             $data[$key]['sczl_type'] = trim($item['sczl_type']);
         }
+
         // 检查任务是否已经存在于队列中,如果不存在则推送任务到队列
         if (!$redis->has($taskIdentifier)) {
-            $job = new InsertDataJob($data); // 创建任务实例
-            // 推送任务到队列
-            Queue::push($job,'','default'); // 推送任务到队列
-            // 设置任务的标识符到缓存中,并设置有效期,有效期为队列执行时间的两倍
-            $redis->set($taskIdentifier, true);
-            $this->success('数据正在处理中,请等待...');
+
+            // 在提交队列任务前,先记录到任务表
+            $taskData = [
+                'date' => $params['date'],
+                'start_date' => $startDate,
+                'end_date' => $endDate,
+                'sys_id' => $params['sys_id'] ?? '',
+                'user_id' => session('user_id') ?? 0,
+                'user_name' => session('user_name') ?? '系统',
+                'request_time' => date('Y-m-d H:i:s')
+            ];
+
+            $taskId = Db::name('queue_tasks')->insertGetId([
+                'task_type' => 'salary_calculation',
+                'task_data' => json_encode($taskData, JSON_UNESCAPED_UNICODE),
+                'status' => 'pending',
+                'queue_name' => 'salary_calculation',
+                'create_time' => date('Y-m-d H:i:s')
+            ]);
+
+            // 将任务ID添加到任务数据中
+            $taskData['task_id'] = $taskId;
+
+            // 提交任务到工资计算队列
+            $job = new InsertDataJob($data);
+
+            // 修改:使用工资计算队列名称
+            $queueResult = Queue::push($job, $taskData, 'salary_calculation');
+
+            if ($queueResult !== false) {
+                // 更新任务记录
+                Db::name('queue_tasks')
+                    ->where('id', $taskId)
+                    ->update([
+                        'job_id' => $queueResult,
+                        'update_time' => date('Y-m-d H:i:s')
+                    ]);
+
+                $redis->set($taskIdentifier, true);
+                $this->success('数据正在处理中,请等待...');
+            }
+//            $job = new InsertDataJob($data); // 创建任务实例
+//            // 推送任务到队列
+//            Queue::push($job,'','default'); // 推送任务到队列
+//            // 设置任务的标识符到缓存中,并设置有效期,有效期为队列执行时间的两倍
+//            $redis->set($taskIdentifier, true);
+//            $this->success('数据正在处理中,请等待...');
+        }
+    }
+
+
+    /**
+     * 新增:查询工资计算任务状态
+     * @ApiMethod GET
+     */
+    public function salaryStatus()
+    {
+        $date = Request::instance()->param('date');
+
+        if (empty($date)) {
+            $this->error('月份参数错误');
+        }
+
+        $task = Db::name('queue_tasks')
+            ->where('task_type', 'salary_calculation')
+            ->where('task_data', 'like', '%"date":"' . $date . '"%')
+            ->order('id', 'desc')
+            ->find();
+
+        if ($task) {
+            $taskData = json_decode($task['task_data'] ?? '{}', true);
+            $result = json_decode($task['result'] ?? '{}', true);
+
+            $this->success('查询成功', null, [
+                'exists' => true,
+                'task_id' => $task['id'],
+                'date' => $date,
+                'status' => $task['status'],
+                'queue_name' => $task['queue_name'],
+                'job_id' => $task['job_id'],
+                'start_time' => $task['start_time'],
+                'end_time' => $task['end_time'],
+                'retry_count' => $task['retry_count'],
+                'result' => $result,
+                'error' => $task['error'] ?? '',
+                'create_time' => $task['create_time'],
+                'user_info' => [
+                    'user_id' => $taskData['user_id'] ?? 0,
+                    'user_name' => $taskData['user_name'] ?? ''
+                ]
+            ]);
+        } else {
+            $this->success('查询成功', null, ['exists' => false, 'date' => $date]);
         }
     }
 }

+ 45 - 9
application/extra/queue.php

@@ -1,12 +1,48 @@
 <?php
+//return [
+//    'connector'  => 'Redis',          // Redis 驱动
+//    'expire'     => 60,             // 任务的过期时间,默认为60秒; 若要禁用,则设置为 null
+//    'default'    => 'default',    // 默认的队列名称
+//    'host'       => '127.0.0.1',       // redis 主机ip
+//    'port'       => 6379,        // redis 端口
+//    'password'   => '',             // redis 密码
+//    'select'     => 15,          // 使用哪一个 db,默认为 db0
+//    'timeout'    => 0,          // redis连接的超时时间
+//    'persistent' => false,
+//];
+
+
+// config/queue.php
 return [
-    'connector'  => 'Redis',          // Redis 驱动
-    'expire'     => 60,             // 任务的过期时间,默认为60秒; 若要禁用,则设置为 null
-    'default'    => 'default',    // 默认的队列名称
-    'host'       => '127.0.0.1',       // redis 主机ip
-    'port'       => 6379,        // redis 端口
-    'password'   => '',             // redis 密码
-    'select'     => 15,          // 使用哪一个 db,默认为 db0
-    'timeout'    => 0,          // redis连接的超时时间
+    'connector'  => 'Redis',           // Redis 驱动
+    'expire'     => 600,              // 任务的过期时间
+    'default'    => 'salary_calculation', // 默认队列名称改为工资计算
+    'host'       => '127.0.0.1',      // redis 主机ip
+    'port'       => 6379,            // redis 端口
+    'password'   => '123456',               // redis 密码
+    'select'     => 15,              // 使用哪一个 db
+    'timeout'    => 0,               // redis连接的超时时间
     'persistent' => false,
-];
+
+    // 自定义配置 - 多队列支持
+    'queues' => [
+        // 工资计算队列(默认队列,保持原有逻辑)
+        'salary_calculation' => [
+            'expire' => 600,         // 10分钟超时
+            'delay'  => 0,
+            'retry'  => 2,
+        ],
+        // 成本计算队列(新增队列)
+        'cost_calculation' => [
+            'expire' => 1800,        // 半小时超时
+            'delay'  => 0,           // 立即执行
+            'retry'  => 3,           // 重试次数
+        ],
+        // 低优先级队列
+        'low_priority' => [
+            'expire' => 3600,        // 1小时超时
+            'delay'  => 300,         // 延迟5分钟执行
+            'retry'  => 1,
+        ]
+    ],
+];

+ 128 - 45
application/job/InsertDataJob.php

@@ -2,25 +2,50 @@
 
 namespace app\job;
 
+//use think\Db;
+//use think\Cache;
+//use think\cache\driver\Redis;
+
 use think\Db;
 use think\Cache;
+use think\Log;
 use think\cache\driver\Redis;
 
 class InsertDataJob
 {
+//    protected $data;
+//
+//    public function __construct($data)
+//    {
+//        $this->data = $data;
+//    }
+
     protected $data;
+    protected $taskData;
 
     public function __construct($data)
     {
         $this->data = $data;
     }
-
     public function handle()
     {
+//        $options = [
+//            'host'       => '127.0.0.1',
+//            'port'       => 6379,
+//            'password'   => '',
+//            'select'     => 15,
+//            'timeout'    => 0,
+//            'expire'     => 0,
+//            'persistent' => false,
+//            'prefix'     => '',
+//        ];
+//        $redis = new Redis($options);
+//        $taskIdentifier = md5(json_encode('date'));
+//        $handData = [];
         $options = [
             'host'       => '127.0.0.1',
             'port'       => 6379,
-            'password'   => '',
+            'password'   => '123456',
             'select'     => 15,
             'timeout'    => 0,
             'expire'     => 0,
@@ -28,49 +53,107 @@ class InsertDataJob
             'prefix'     => '',
         ];
         $redis = new Redis($options);
-        $taskIdentifier = md5(json_encode('date'));
-        $handData = [];
-        foreach ($this->data as $key=>$value){
-            $handData[$key]['sczl_gdbh'] = $value['sczl_gdbh'];
-            $handData[$key]['sczl_yjno'] = $value['sczl_yjno'];
-            $handData[$key]['sczl_gxh'] = $value['sczl_gxh'];
-            $handData[$key]['sczl_type'] = $value['sczl_type'];
-            $handData[$key]['sczl_rq'] = $value['sczl_rq'];
-            $handData[$key]['sczl_jtbh'] = $value['sczl_jtbh'];
-            $handData[$key]['班组车头产量'] = $value['班组车头产量'];
-            $handData[$key]['工价系数'] = $value['工价系数'];
-            $handData[$key]['工序难度系数'] = $value['工序难度系数'];
-            $handData[$key]['装版工时'] = $value['装版工时'];
-            $handData[$key]['保养工时'] = $value['保养工时'];
-            $handData[$key]['打样工时'] = $value['打样工时'];
-            $handData[$key]['异常停机工时'] = $value['异常停机工时'];
-            $handData[$key]['车头产量占用机时'] = $value['车头产量占用机时'];
-            $handData[$key]['日定额'] = $value['日定额'];
-            $handData[$key]['千件工价'] = $value['千件工价'];
-            $handData[$key]['补产标准'] = $value['补产标准'];
-            $handData[$key]['班组换算产量'] = $value['班组换算产量'];
-            $handData[$key]['计时补差额工资'] = $value['计时补差额工资'];
-            $handData[$key]['bh'] = $value['bh'];
-            $handData[$key]['xm'] = $value['xm'];
-            $handData[$key]['Rate'] = $value['Rate'];
-            $handData[$key]['sczl_ms'] = $value['sczl_ms'];
-            $handData[$key]['工时占比'] = $value['工时占比'];
-            $handData[$key]['达标定额'] = $value['达标定额'];
-            $handData[$key]['个人计件工资'] = $value['个人计件工资'];
-            $handData[$key]['个人加班工资'] = $value['个人加班工资'];
-            $handData[$key]['UniqID'] = $value['UniqID'];
-            $handData[$key]['sys_ny'] = $value['sys_ny'];
-            $handData[$key]['sys_rq'] = $value['sys_rq'];
-            $handData[$key]['sys_id'] = $value['sys_id'];
-            $handData[$key]['法定天数'] = $value['法定天数'];
-        }
-        $sql =Db::name('绩效工资汇总')->fetchSql(true)->insertAll($handData);
-        $res = Db::query($sql);
-        if ($res !== false){
-            // 获取队列的键名
-            $queueKey = 'default';
-            // 删除队列
-            Cache::store('redis')->handler()->del($queueKey);
+
+        // 从任务数据中获取月份
+        $date = $this->taskData['date'] ?? '';
+        $taskId = $this->taskData['task_id'] ?? 0;
+        $taskIdentifier = md5('salary_calculation_' . $date);
+
+        try {
+            // 更新任务状态为执行中
+            if ($taskId) {
+                Db::name('queue_tasks')
+                    ->where('id', $taskId)
+                    ->update([
+                        'status' => 'processing',
+                        'start_time' => date('Y-m-d H:i:s'),
+                        'update_time' => date('Y-m-d H:i:s')
+                    ]);
+            }
+
+            // 原有的数据处理逻辑
+            $handData = [];
+            foreach ($this->data as $key=>$value){
+                $handData[$key]['sczl_gdbh'] = $value['sczl_gdbh'];
+                $handData[$key]['sczl_yjno'] = $value['sczl_yjno'];
+                $handData[$key]['sczl_gxh'] = $value['sczl_gxh'];
+                $handData[$key]['sczl_type'] = $value['sczl_type'];
+                $handData[$key]['sczl_rq'] = $value['sczl_rq'];
+                $handData[$key]['sczl_jtbh'] = $value['sczl_jtbh'];
+                $handData[$key]['班组车头产量'] = $value['班组车头产量'];
+                $handData[$key]['工价系数'] = $value['工价系数'];
+                $handData[$key]['工序难度系数'] = $value['工序难度系数'];
+                $handData[$key]['装版工时'] = $value['装版工时'];
+                $handData[$key]['保养工时'] = $value['保养工时'];
+                $handData[$key]['打样工时'] = $value['打样工时'];
+                $handData[$key]['异常停机工时'] = $value['异常停机工时'];
+                $handData[$key]['车头产量占用机时'] = $value['车头产量占用机时'];
+                $handData[$key]['日定额'] = $value['日定额'];
+                $handData[$key]['千件工价'] = $value['千件工价'];
+                $handData[$key]['补产标准'] = $value['补产标准'];
+                $handData[$key]['班组换算产量'] = $value['班组换算产量'];
+                $handData[$key]['计时补差额工资'] = $value['计时补差额工资'];
+                $handData[$key]['bh'] = $value['bh'];
+                $handData[$key]['xm'] = $value['xm'];
+                $handData[$key]['Rate'] = $value['Rate'];
+                $handData[$key]['sczl_ms'] = $value['sczl_ms'];
+                $handData[$key]['工时占比'] = $value['工时占比'];
+                $handData[$key]['达标定额'] = $value['达标定额'];
+                $handData[$key]['个人计件工资'] = $value['个人计件工资'];
+                $handData[$key]['个人加班工资'] = $value['个人加班工资'];
+                $handData[$key]['UniqID'] = $value['UniqID'];
+                $handData[$key]['sys_ny'] = $value['sys_ny'];
+                $handData[$key]['sys_rq'] = $value['sys_rq'];
+                $handData[$key]['sys_id'] = $value['sys_id'];
+                $handData[$key]['法定天数'] = $value['法定天数'];
+            }
+            $sql =Db::name('绩效工资汇总')->fetchSql(true)->insertAll($handData);
+            $res = Db::query($sql);
+//        if ($res !== false){
+//            // 获取队列的键名
+//            $queueKey = 'default';
+//            // 删除队列
+//            Cache::store('redis')->handler()->del($queueKey);
+//            $redis->rm($taskIdentifier);
+//        }
+            if ($res !== false){
+                // 更新任务状态为成功
+                if ($taskId) {
+                    Db::name('queue_tasks')
+                        ->where('id', $taskId)
+                        ->update([
+                            'status' => 'success',
+                            'end_time' => date('Y-m-d H:i:s'),
+                            'result' => json_encode(['success' => true, 'message' => '工资计算完成']),
+                            'update_time' => date('Y-m-d H:i:s')
+                        ]);
+                }
+
+                // 清理缓存
+                $queueKey = 'salary_calculation';
+                Cache::store('redis')->handler()->del($queueKey);
+                $redis->rm($taskIdentifier);
+
+                Log::info('工资计算任务执行成功', ['date' => $date]);
+            } else {
+                throw new \Exception('数据插入失败');
+            }
+
+        } catch (\Exception $e) {
+            Log::error('工资计算任务执行失败: ' . $e->getMessage());
+
+            // 更新任务状态为失败
+            if ($taskId) {
+                Db::name('queue_tasks')
+                    ->where('id', $taskId)
+                    ->update([
+                        'status' => 'failed',
+                        'end_time' => date('Y-m-d H:i:s'),
+                        'error' => $e->getMessage(),
+                        'update_time' => date('Y-m-d H:i:s')
+                    ]);
+            }
+
             $redis->rm($taskIdentifier);
         }
     }

+ 122 - 0
application/job/UnifiedCostCalculationJob.php

@@ -0,0 +1,122 @@
+<?php
+
+namespace app\job;
+
+use think\Db;
+use think\Log;
+
+/**
+ * 成本计算队列任务
+ */
+class UnifiedCostCalculationJob
+{
+    /**
+     * 任务处理方法
+     * @param \think\Job $job 任务对象
+     * @param array $data 任务数据
+     */
+    public function fire($job, $data)
+    {
+        try {
+            Log::info('开始执行成本计算队列任务', $data);
+
+            // 获取任务ID
+            $taskId = $data['task_id'] ?? 0;
+            $month = $data['month'];
+
+            // 更新任务状态为执行中
+            $this->updateTaskStatus($taskId, 'processing', [
+                'start_time' => date('Y-m-d H:i:s'),
+                'retry_count' => $job->attempts()
+            ]);
+
+            // 执行成本计算
+            $result = $this->executeCostCalculation($month, $data['sys_id'] ?? '');
+
+            if ($result['success']) {
+                // 任务成功
+                $job->delete();
+                $this->updateTaskStatus($taskId, 'success', [
+                    'end_time' => date('Y-m-d H:i:s'),
+                    'result' => json_encode($result, JSON_UNESCAPED_UNICODE)
+                ]);
+
+                Log::info('成本计算任务执行成功', ['month' => $month, 'result' => $result]);
+            } else {
+                // 任务失败
+                if ($job->attempts() >= 3) {
+                    $job->delete();
+                    $this->updateTaskStatus($taskId, 'failed', [
+                        'end_time' => date('Y-m-d H:i:s'),
+                        'error' => $result['message'],
+                        'retry_count' => $job->attempts()
+                    ]);
+                    Log::error('成本计算任务重试超过3次失败', ['month' => $month, 'error' => $result['message']]);
+                } else {
+                    $delay = $this->getRetryDelay($job->attempts());
+                    $job->release($delay);
+                    Log::warning('成本计算任务重试', ['month' => $month, 'attempts' => $job->attempts(), 'delay' => $delay]);
+                }
+            }
+
+        } catch (\Exception $e) {
+            Log::error('成本计算队列任务异常: ' . $e->getMessage());
+
+            if ($job->attempts() >= 3) {
+                $job->delete();
+                $this->updateTaskStatus($taskId ?? 0, 'error', [
+                    'end_time' => date('Y-m-d H:i:s'),
+                    'error' => $e->getMessage(),
+                    'retry_count' => $job->attempts()
+                ]);
+            } else {
+                $job->release(60); // 延迟60秒重试
+            }
+        }
+    }
+
+    /**
+     * 执行成本计算
+     */
+    protected function executeCostCalculation(string $month, string $sysId = ''): array
+    {
+        try {
+            // 这里调用您的成本计算服务
+            $service = new \app\service\UnifiedCostCalculationService();
+            return $service->calculateAndSaveAll([
+                'month' => $month,
+                'sys_id' => $sysId,
+            ]);
+
+        } catch (\Exception $e) {
+            return [
+                'success' => false,
+                'message' => '成本计算执行失败: ' . $e->getMessage()
+            ];
+        }
+    }
+
+
+    /**
+     * 更新任务状态
+     */
+    protected function updateTaskStatus(int $taskId, string $status, array $data = []): void
+    {
+        if ($taskId <= 0) return;
+
+        $updateData = array_merge(['status' => $status, 'update_time' => date('Y-m-d H:i:s')], $data);
+
+        Db::name('queue_tasks')
+            ->where('id', $taskId)
+            ->update($updateData);
+    }
+
+    /**
+     * 获取重试延迟时间
+     */
+    protected function getRetryDelay(int $attempts): int
+    {
+        $delays = [10, 30, 60]; // 10秒, 30秒, 1分钟
+        return $delays[min($attempts - 1, count($delays) - 1)] ?? 60;
+    }
+}

+ 760 - 0
application/service/UnifiedCostCalculationService.php

@@ -0,0 +1,760 @@
+<?php
+
+namespace app\service;
+
+use think\Db;
+use think\Exception;
+use think\Log;
+
+/**
+ * 统一成本核算服务类
+ * 五项计算完成后统一插入数据库
+ */
+class UnifiedCostCalculationService
+{
+    // 存储中间数据的数组
+    protected $monthlyCostDetails = [];  // 成本v23_月度成本明细
+    protected $monthlyUtilities = [];    // 成本_各月水电气
+    protected $allocationFactors = [];   // 成本_各月分摊系数
+
+    // 配置常量
+    const FLOOR_GROUP_MAP = [
+        '1' => ['02、胶印机组', '03、卷凹机组', '06、单凹机组', '05、圆切机组', '04、圆烫机组', '10、模切机组', '09、烫金机组'],
+        '2' => ['01、切纸机组', '11、检品机组', '07、丝印机组', '12、覆膜机组', '08、喷码机组'],
+    ];
+
+    /**
+     * 主入口:执行所有成本计算并统一入库
+     */
+    public function calculateAndSaveAll(array $param): array
+    {
+        Db::startTrans();
+        try {
+            $month = $param['month'];
+            $sysId = $param['sys_id'] ?? '';
+
+            // 1. 清空旧数据
+            $this->clearOldData($month);
+
+            // 2. 执行五项计算
+            $this->calculateDirectLabor($param);        // 直接人工
+            $this->calculateDirectUtilities($param);    // 直接水电
+            $this->calculateIndirectMaterials($month);  // 间接材料分摊
+            $this->calculateIndirectLabor($month);      // 间接人工分摊
+            $this->calculateApportionedUtilities($param); // 分摊水电
+
+            // 3. 统一插入数据
+            $this->saveAllData($month, $sysId);
+
+            Db::commit();
+
+            Log::info("成本核算完成", [
+                'month' => $month,
+                '月度成本明细记录数' => count($this->monthlyCostDetails),
+                '水电记录数' => count($this->monthlyUtilities),
+                '分摊系数记录数' => count($this->allocationFactors)
+            ]);
+
+            return [
+                'success' => true,
+                'message' => '成本核算完成',
+                'stats' => [
+                    'monthly_cost_details' => count($this->monthlyCostDetails),
+                    'utilities' => count($this->monthlyUtilities),
+                    'allocation_factors' => count($this->allocationFactors)
+                ]
+            ];
+
+        } catch (Exception $e) {
+            Db::rollback();
+            Log::error("统一成本核算失败: " . $e->getMessage());
+            return [
+                'success' => false,
+                'message' => '成本核算失败: ' . $e->getMessage()
+            ];
+        }
+    }
+
+    /**
+     * 清空旧数据
+     */
+    protected function clearOldData(string $month): void
+    {
+        // 这里只记录要删除,实际删除操作在最后统一执行
+        // 所有数据都缓存在内存中,最后统一插入
+        $this->monthlyCostDetails = [];
+        $this->monthlyUtilities = [];
+        $this->allocationFactors = [];
+    }
+
+    /**
+     * 1. 计算直接人工
+     */
+    protected function calculateDirectLabor(array $param): void
+    {
+        $month = $param['month'];
+        $sysId = $param['sys_id'] ?? '';
+
+        $list = Db::name('绩效工资汇总')
+            ->alias('a')
+            ->join('工单_工艺资料 b', 'a.sczl_gdbh = b.Gy0_gdbh and a.sczl_yjno = b.Gy0_yjno and a.sczl_gxh = b.Gy0_gxh')
+            ->join('设备_基本资料 c', 'a.sczl_jtbh = c.设备编号', 'LEFT')
+            ->join('工单_印件资料 d', 'a.sczl_gdbh = d.Yj_Gdbh and a.sczl_yjno = d.yj_Yjno')
+            ->field('a.sczl_gdbh as 工单编号,a.sczl_yjno as 印件号,a.sczl_gxh as 工序号,sum(a.班组车头产量) as 班组车头产量,b.Gy0_gxmc as 工序名称,
+                a.sczl_ms as 墨色数,c.使用部门,b.印刷方式,b.版距,b.工价系数,a.sczl_jtbh,d.yj_yjmc as 印件名称,sum(a.车头产量占用机时) as 占用机时,a.sys_rq as 年月,
+                a.工序难度系数,sum(a.班组换算产量) as 班组换算产量,a.千件工价')
+            ->where('a.sys_ny', $month)
+            ->group('a.sczl_gdbh,a.sczl_yjno,a.sczl_gxh,a.sczl_jtbh')
+            ->select();
+
+        $now = date('Y-m-d H:i:s');
+        foreach ($list as $k => $v) {
+            // 处理特殊值
+            $banju = $v['版距'] === '0.0' ? 1000 : $v['版距'];
+            $moshushu = $v['墨色数'] === '0.00' ? 1 : $v['墨色数'];
+
+            if (strpos($v['工序名称'], '切废')) {
+                $moshushu = 0.2;
+            }
+
+            $chanliang = $v['班组车头产量'] * $v['工序难度系数'] + $v['班组换算产量'];
+            $renGongFenTan = ($chanliang / 1000) * $v['千件工价'];
+
+            $this->monthlyCostDetails[] = [
+                '车间名称' => $v['使用部门'] ?? '',
+                'sys_ny' => $month,
+                'sczl_gdbh' => $v['工单编号'],
+                '印件名称' => $v['印件名称'] ?? '',
+                'sczl_yjno' => $v['印件号'],
+                'sczl_gxh' => $v['工序号'],
+                '工序名称' => $v['工序名称'] ?? '',
+                'sczl_jtbh' => $v['sczl_jtbh'] ?? '',
+                '卷张换算系数' => floatval($banju) / 1000,
+                '占用机时' => floatval($v['占用机时']) ?? 0,
+                '班组车头产量' => floatval($v['班组车头产量']) ?? 0,
+                'sczl_ms' => floatval($moshushu),
+                '工序难度系数' => floatval($v['工序难度系数']) ?? 1,
+                '班组换算产量' => floatval($v['班组换算产量']) ?? 0,
+                '千件工价' => floatval($v['千件工价']) ?? 0,
+                '计件产量' => floatval($chanliang),
+                '水电分摊因子' => floatval($v['占用机时']) ?? 0,
+                '材料分摊因子' => floatval($chanliang),
+                '人工分摊因子' => floatval($renGongFenTan),
+                '直接水电' => 0,  // 后续计算
+                '分摊材料' => 0,  // 后续计算
+                '车间人工' => 0,  // 后续计算
+                '部门人工附加' => 0, // 后续计算
+                '分摊水电' => 0, // 后续计算
+                '废气处理' => 0, // 后续计算
+                '锅炉' => 0,    // 后续计算
+                '空压机' => 0,   // 后续计算
+                '热水锅炉' => 0,  // 后续计算
+                '真空鼓风机' => 0, // 后续计算
+                '中央空调' => 0,  // 后续计算
+                'Sys_id' => $sysId,
+                'Sys_rq' => $now
+            ];
+        }
+    }
+
+    /**
+     * 2. 计算直接水电
+     */
+    protected function calculateDirectUtilities(array $param): void
+    {
+        $month = $param['month'];
+        $sysId = $param['sys_id'] ?? '';
+        $monthStr = substr($month, 0, 4) . '-' . substr($month, 4, 2);
+
+        $sist = ['胶印车间', '凹丝印车间', '印后车间'];
+        $list = Db::name('设备_产量计酬')
+            ->alias('a')
+            ->join('设备_基本资料 b', 'a.sczl_jtbh = b.设备编号')
+            ->where('a.sczl_rq', 'like', $monthStr . '%')
+            ->where('b.sys_sbID', '<>', '')
+            ->where('b.使用部门', 'in', $sist)
+            ->field('a.sczl_jtbh,sum(a.sczl_设备运行工时) as 通电工时,b.使用部门,rtrim(b.设备名称) as 设备名称')
+            ->order('b.使用部门,a.sczl_jtbh')
+            ->group('a.sczl_jtbh')
+            ->select();
+
+        $now = date('Y-m-d H:i:s');
+        foreach ($list as $v) {
+            $this->monthlyUtilities[] = [
+                'Sys_ny' => $month,
+                '部门名称' => $v['使用部门'] ?? '',
+                '费用类型' => '直接',
+                '设备编号' => $v['sczl_jtbh'] ?? '',
+                '科目名称' => $v['设备名称'] ?? '',
+                '耗电量' => floatval($v['通电工时']) ?? 0,
+                '单位电价' => 0.69,
+                '耗气量' => 0,
+                '单位气价' => 0,
+                'Sys_id' => $sysId,
+                'Sys_rq' => $now,
+            ];
+        }
+    }
+
+    /**
+     * 3. 计算间接材料分摊
+     */
+    protected function calculateIndirectMaterials(string $month): void
+    {
+        if (empty($this->monthlyCostDetails)) {
+            return;
+        }
+
+        $date = substr($month, 0, 4) . '-' . substr($month, 4, 2);
+
+        // 获取分摊材料总金额
+        $totalMoney = Db::name('材料出库单列表')
+            ->where([
+                '出库日期' => ['like', $date . '%'],
+                '部门' => '印刷成本中心'
+            ])
+            ->whereNull('表体生产订单号')
+            ->field('SUM(金额) as money')
+            ->find();
+
+        if (!$totalMoney || $totalMoney['money'] <= 0) {
+            return;
+        }
+
+        // 计算总色度数
+        $totalChroma = 0;
+        foreach ($this->monthlyCostDetails as $detail) {
+            $chroma = floatval($detail['班组车头产量']) * floatval($detail['sczl_ms']);
+            $totalChroma += $chroma;
+        }
+
+        if ($totalChroma <= 0) {
+            return;
+        }
+
+        // 分摊到每个记录
+        foreach ($this->monthlyCostDetails as &$detail) {
+            $chroma = floatval($detail['班组车头产量']) * floatval($detail['sczl_ms']);
+            $money = round($totalMoney['money'] * ($chroma / $totalChroma), 2);
+            $detail['分摊材料'] = $money;
+        }
+    }
+
+    /**
+     * 4. 计算间接人工分摊
+     */
+    protected function calculateIndirectLabor(string $month): void
+    {
+        // 获取工资比例
+        $wageRatio = $this->getWageRatio($month);
+        if (empty($wageRatio)) {
+            return;
+        }
+
+        // 获取月度工资数据
+        $monthWage = Db::name('成本_各月其他费用')
+            ->where('sys_ny', $month)
+            ->field('部门人员工资,管理人员工资')
+            ->find();
+
+        if (empty($monthWage)) {
+            return;
+        }
+
+        // 计算每个车间的分配数据
+        foreach ($wageRatio as $workshopName => $ratio) {
+            $chromaData = $this->getChromaDataForWorkshop($workshopName);
+
+            if (empty($chromaData['list']) || $chromaData['total'] == 0) {
+                continue;
+            }
+
+            // 计算两种工资类型的分配
+            $this->allocateWageToWorkshop($workshopName, $ratio, $monthWage, $chromaData);
+        }
+    }
+
+    /**
+     * 获取车间色度数数据
+     */
+    protected function getChromaDataForWorkshop(string $workshop): array
+    {
+        $data = [
+            'total' => 0,
+            'list' => []
+        ];
+
+        foreach ($this->monthlyCostDetails as $detail) {
+            if ($detail['车间名称'] === $workshop) {
+                $chroma = floatval($detail['班组车头产量']) * floatval($detail['sczl_ms']);
+                $data['total'] += $chroma;
+                $data['list'][] = [
+                    'sczl_gdbh' => $detail['sczl_gdbh'],
+                    'sczl_yjno' => $detail['sczl_yjno'],
+                    'sczl_gxh' => $detail['sczl_gxh'],
+                    'sczl_jtbh' => $detail['sczl_jtbh'],
+                    'chroma' => $chroma
+                ];
+            }
+        }
+
+        return $data;
+    }
+
+    /**
+     * 分配工资到车间
+     */
+    protected function allocateWageToWorkshop(string $workshop, float $ratio, array $monthWage, array $chromaData): void
+    {
+        $updateTypes = [
+            '部门人员工资' => '车间人工',
+            '管理人员工资' => '部门人工附加'
+        ];
+
+        foreach ($updateTypes as $wageType => $fieldName) {
+            if (!isset($monthWage[$wageType]) || $monthWage[$wageType] <= 0) {
+                continue;
+            }
+
+            $money = $ratio * $monthWage[$wageType];
+
+            if ($chromaData['total'] <= 0) {
+                continue;
+            }
+
+            // 找到对应记录并更新
+            foreach ($this->monthlyCostDetails as &$detail) {
+                if ($detail['车间名称'] === $workshop) {
+                    $chroma = floatval($detail['班组车头产量']) * floatval($detail['sczl_ms']);
+                    $perAmount = round($chroma / $chromaData['total'] * $money, 2);
+
+                    if ($fieldName === '车间人工') {
+                        $detail['车间人工'] += $perAmount;
+                    } else {
+                        $detail['部门人工附加'] += $perAmount;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 获取工资比例
+     */
+    protected function getWageRatio(string $month): array
+    {
+        // 从缓存数据中计算
+        $workshopTotals = [];
+
+        foreach ($this->monthlyCostDetails as $detail) {
+            $workshop = $detail['车间名称'];
+            $amount = floatval($detail['人工分摊因子']);
+
+            if (!isset($workshopTotals[$workshop])) {
+                $workshopTotals[$workshop] = 0;
+            }
+            $workshopTotals[$workshop] += $amount;
+        }
+
+        $total = array_sum($workshopTotals);
+        if ($total <= 0) {
+            return [];
+        }
+
+        $ratios = [];
+        foreach ($workshopTotals as $workshop => $workshopTotal) {
+            $ratios[$workshop] = round($workshopTotal / $total, 4);
+        }
+
+        return $ratios;
+    }
+
+    /**
+     * 5. 计算分摊水电
+     */
+    protected function calculateApportionedUtilities(array $param): void
+    {
+        $month = $param['month'];
+        $sysId = $param['sys_id'] ?? '';
+
+        // 获取分摊水电数据
+        $utilityData = Db::name('成本_各月水电气')
+            ->where('Sys_ny', $month)
+            ->whereLike('费用类型', '%分摊%')
+            ->select();
+
+        if (empty($utilityData)) {
+            return;
+        }
+
+        // 计算各机台的分摊金额
+        $machineAllocations = $this->calculateMachineAllocations($utilityData, $month);
+
+        // 生成分摊系数记录
+        $this->generateAllocationFactors($machineAllocations, $month, $sysId);
+
+        // 分配到工单
+        $this->allocateUtilitiesToWorkOrders($machineAllocations, $month);
+    }
+
+    /**
+     * 计算机台分摊金额
+     */
+    protected function calculateMachineAllocations(array $utilityData, string $month): array
+    {
+        $allocations = [];
+
+        // 先按设备编号分组计算总机时
+        $machineHours = $this->getMachineHours($month);
+
+        // 按科目处理分摊
+        foreach ($utilityData as $item) {
+            $subject = $this->simplifySubjectName($item['科目名称']);
+            $amount = $this->calculateUtilityAmount($item);
+
+            if ($amount <= 0) {
+                continue;
+            }
+
+            // 根据科目类型选择分摊方式
+            if ($subject === '待分摊总额') {
+                $this->allocateByFloor($allocations, $amount, $machineHours, $month);
+            } elseif (in_array($subject, ['锅炉', '热水锅炉'])) {
+                $this->allocateToRollCoater($allocations, $amount, $machineHours, $month);
+            } else {
+                $this->allocateGlobally($allocations, $amount, $machineHours);
+            }
+        }
+
+        return $allocations;
+    }
+
+    /**
+     * 获取机台运行时间
+     */
+    protected function getMachineHours(string $month): array
+    {
+        $hours = [];
+
+        foreach ($this->monthlyCostDetails as $detail) {
+            $machine = $detail['sczl_jtbh'];
+            $hour = floatval($detail['占用机时']);
+
+            if (!isset($hours[$machine])) {
+                $hours[$machine] = 0;
+            }
+            $hours[$machine] += $hour;
+        }
+
+        return $hours;
+    }
+
+    /**
+     * 按楼层分摊
+     */
+    protected function allocateByFloor(array &$allocations, float $amount, array $machineHours, string $month): void
+    {
+        // 按楼层分组
+        $floorHours = ['1' => 0, '2' => 0];
+        $floorMachines = ['1' => [], '2' => []];
+
+        // 先计算每个楼层总机时
+        foreach ($machineHours as $machine => $hours) {
+            $floor = $this->getFloorByMachine($machine);
+            if ($floor && isset($floorHours[$floor])) {
+                $floorHours[$floor] += $hours;
+                $floorMachines[$floor][] = $machine;
+            }
+        }
+
+        // 按楼层比例分摊
+        $totalFloorHours = array_sum($floorHours);
+        if ($totalFloorHours <= 0) {
+            return;
+        }
+
+        foreach ($floorHours as $floor => $hours) {
+            if ($hours <= 0) continue;
+
+            $floorAmount = $amount * ($hours / $totalFloorHours);
+
+            // 在楼层内按机台分摊
+            foreach ($floorMachines[$floor] as $machine) {
+                if (!isset($allocations[$machine])) {
+                    $allocations[$machine] = [];
+                }
+                $machineAmount = round($floorAmount * ($machineHours[$machine] / $hours), 2);
+                $allocations[$machine]['待分摊总额'] = ($allocations[$machine]['待分摊总额'] ?? 0) + $machineAmount;
+            }
+        }
+    }
+
+    /**
+     * 只分摊到卷凹机组
+     */
+    protected function allocateToRollCoater(array &$allocations, float $amount, array $machineHours, string $month): void
+    {
+        // 筛选卷凹机组的机台
+        $rollCoaterMachines = $this->filterRollCoaterMachines(array_keys($machineHours));
+        $totalHours = 0;
+
+        foreach ($rollCoaterMachines as $machine) {
+            $totalHours += $machineHours[$machine];
+        }
+
+        if ($totalHours <= 0) {
+            return;
+        }
+
+        foreach ($rollCoaterMachines as $machine) {
+            if (!isset($allocations[$machine])) {
+                $allocations[$machine] = [];
+            }
+            $machineAmount = round($amount * ($machineHours[$machine] / $totalHours), 2);
+            $allocations[$machine]['锅炉'] = ($allocations[$machine]['锅炉'] ?? 0) + $machineAmount;
+        }
+    }
+
+    /**
+     * 全局分摊
+     */
+    protected function allocateGlobally(array &$allocations, float $amount, array $machineHours): void
+    {
+        $totalHours = array_sum($machineHours);
+        if ($totalHours <= 0) {
+            return;
+        }
+
+        foreach ($machineHours as $machine => $hours) {
+            if ($hours <= 0) continue;
+
+            if (!isset($allocations[$machine])) {
+                $allocations[$machine] = [];
+            }
+            $machineAmount = round($amount * ($hours / $totalHours), 2);
+            $allocations[$machine][$this->getSubjectKey($amount)] = ($allocations[$machine][$this->getSubjectKey($amount)] ?? 0) + $machineAmount;
+        }
+    }
+
+    /**
+     * 根据机台获取楼层
+     */
+    protected function getFloorByMachine(string $machine): ?string
+    {
+        // 简化实现,实际应从数据库查询
+        $groups = Db::name('设备_基本资料')
+            ->where('设备编号', $machine)
+            ->value('设备编组');
+
+        if (!$groups) {
+            return null;
+        }
+
+        foreach (self::FLOOR_GROUP_MAP as $floor => $groupNames) {
+            foreach ($groupNames as $groupName) {
+                if (strpos($groups, $groupName) !== false) {
+                    return $floor;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * 筛选卷凹机组的机台
+     */
+    protected function filterRollCoaterMachines(array $machines): array
+    {
+        $rollCoater = [];
+
+        foreach ($machines as $machine) {
+            $group = Db::name('设备_基本资料')
+                ->where('设备编号', $machine)
+                ->value('设备编组');
+
+            if ($group && strpos($group, '03、卷凹机组') !== false) {
+                $rollCoater[] = $machine;
+            }
+        }
+
+        return $rollCoater;
+    }
+
+    /**
+     * 简化科目名称
+     */
+    protected function simplifySubjectName(string $subject): string
+    {
+        $map = [
+            '废气处理(RTO)' => '废气处理',
+            '锅炉' => '锅炉',
+            '空压机' => '空压机',
+            '热水锅炉' => '热水锅炉',
+            '真空鼓风机' => '真空鼓风机',
+            '中央空调' => '中央空调',
+            '待分摊总额' => '待分摊总额',
+        ];
+
+        foreach ($map as $keyword => $simple) {
+            if (strpos($subject, $keyword) !== false) {
+                return $simple;
+            }
+        }
+
+        return $subject;
+    }
+
+    /**
+     * 计算水电金额
+     */
+    protected function calculateUtilityAmount(array $item): float
+    {
+        $electricity = floatval($item['耗电量'] ?? 0) * floatval($item['单位电价'] ?? 0);
+        $gas = floatval($item['耗气量'] ?? 0) * floatval($item['单位气价'] ?? 0);
+        return round($electricity + $gas, 2);
+    }
+
+    /**
+     * 获取科目键名
+     */
+    protected function getSubjectKey(float $amount): string
+    {
+        // 简化的实现
+        return '其他分摊';
+    }
+
+    /**
+     * 生成分摊系数记录
+     */
+    protected function generateAllocationFactors(array $allocations, string $month, string $sysId): void
+    {
+        $now = date('Y-m-d H:i:s');
+
+        foreach ($allocations as $machine => $subjects) {
+            foreach ($subjects as $subject => $amount) {
+                $this->allocationFactors[] = [
+                    'Sys_ny' => $month,
+                    '科目名称' => $subject,
+                    '设备编号' => $machine,
+                    '分摊系数' => 1,
+                    '分摊金额' => $amount,
+                    'Sys_id' => $sysId,
+                    'Sys_rq' => $now,
+                ];
+            }
+        }
+    }
+
+    /**
+     * 分配水电到工单
+     */
+    protected function allocateUtilitiesToWorkOrders(array $machineAllocations, string $month): void
+    {
+        // 计算机台每机时费用
+        $machineRates = $this->calculateMachineRates($machineAllocations);
+
+        // 更新月度成本明细
+        foreach ($this->monthlyCostDetails as &$detail) {
+            $machine = $detail['sczl_jtbh'];
+            $hours = floatval($detail['占用机时']);
+
+            if ($hours <= 0 || !isset($machineRates[$machine])) {
+                continue;
+            }
+
+            // 直接水电费
+            $detail['直接水电'] = round($hours * 0.69, 2);
+
+            // 分摊水电费
+            foreach ($machineRates[$machine] as $subject => $rate) {
+                $field = $this->getUtilityFieldName($subject);
+                $detail[$field] = round($hours * $rate, 2);
+            }
+        }
+    }
+
+    /**
+     * 计算机台每机时费用
+     */
+    protected function calculateMachineRates(array $allocations): array
+    {
+        $rates = [];
+        $machineHours = [];
+
+        // 先计算机台总机时
+        foreach ($this->monthlyCostDetails as $detail) {
+            $machine = $detail['sczl_jtbh'];
+            $hours = floatval($detail['占用机时']);
+
+            if (!isset($machineHours[$machine])) {
+                $machineHours[$machine] = 0;
+            }
+            $machineHours[$machine] += $hours;
+        }
+
+        // 计算每机时费用
+        foreach ($allocations as $machine => $subjects) {
+            $totalHours = $machineHours[$machine] ?? 0;
+
+            if ($totalHours <= 0) {
+                continue;
+            }
+
+            $rates[$machine] = [];
+            foreach ($subjects as $subject => $amount) {
+                $rates[$machine][$subject] = round($amount / $totalHours, 4);
+            }
+        }
+
+        return $rates;
+    }
+
+    /**
+     * 获取水电字段名
+     */
+    protected function getUtilityFieldName(string $subject): string
+    {
+        $map = [
+            '待分摊总额' => '分摊水电',
+            '废气处理' => '废气处理',
+            '锅炉' => '锅炉',
+            '空压机' => '空压机',
+            '热水锅炉' => '热水锅炉',
+            '真空鼓风机' => '真空鼓风机',
+            '中央空调' => '中央空调',
+        ];
+
+        return $map[$subject] ?? $subject;
+    }
+
+    /**
+     * 统一保存所有数据
+     */
+    protected function saveAllData(string $month, string $sysId): void
+    {
+        $now = date('Y-m-d H:i:s');
+
+        // 1. 删除旧数据
+        Db::name('成本v23_月度成本明细')->where('sys_ny', $month)->delete();
+        Db::name('成本_各月水电气')->where('Sys_ny', $month)->delete();
+        Db::name('成本_各月分摊系数')->where('Sys_ny', $month)->delete();
+
+        // 2. 插入新数据
+        if (!empty($this->monthlyCostDetails)) {
+            $sql = Db::name('成本v23_月度成本明细')->fetchSql(true)->insertAll($this->monthlyCostDetails);
+            Db::query($sql);
+        }
+
+        if (!empty($this->monthlyUtilities)) {
+            $sql = Db::name('成本_各月水电气')->fetchSql(true)->insertAll($this->monthlyUtilities);
+            Db::query($sql);
+        }
+
+        if (!empty($this->allocationFactors)) {
+            $sql = Db::name('成本_各月分摊系数')->fetchSql(true)->insertAll($this->allocationFactors);
+            Db::query($sql);
+        }
+    }
+}
+