['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); // 直接人工(第一步,生成monthlyCostDetails) $this->calculateDirectUtilities($param); // 直接水电(第二步,依赖calculateDirectLabor的结果) $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->allocationFactors) ]); return [ 'success' => true, 'message' => '成本核算完成', 'stats' => [ 'monthly_cost_details' => count($this->monthlyCostDetails), '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->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, // 后续计算 '分摊其他' => 0, 'Sys_id' => $sysId, 'Sys_rq' => $now ]; } } /** * 2. 计算直接水电 */ protected function calculateDirectUtilities(array $param): void { $month = $param['month']; $sysId = $param['sys_id'] ?? ''; // 检查是否已有直接人工数据 if (empty($this->monthlyCostDetails)) { Log::error("直接水电计算需要先计算直接人工数据"); throw new Exception("请先执行直接人工计算"); } try { // 1. 从成本_各月水电气表查询直接水电费用数据 $utilityData = Db::name('成本_各月水电气') ->where('Sys_ny', $month) ->where('费用类型', '直接') ->field('设备编号, 部门名称, 科目名称, 耗电量, 单位电价, 耗气量, 单位气价') ->select(); if (empty($utilityData)) { Log::info("{$month}月份未找到直接水电费用数据"); return; } Log::info("找到直接水电费用数据条数: " . count($utilityData)); // 2. 计算每个机台的总水电费金额 $machineUtilities = []; foreach ($utilityData as $item) { $machineCode = $item['设备编号'] ?? ''; if (empty($machineCode)) { continue; } // 计算电费 $electricityCost = floatval($item['耗电量'] ?? 0) * floatval($item['单位电价'] ?? 0); // 计算气费 $gasCost = floatval($item['耗气量'] ?? 0) * floatval($item['单位气价'] ?? 0); // 总费用 $totalCost = round($electricityCost + $gasCost, 2); if (!isset($machineUtilities[$machineCode])) { $machineUtilities[$machineCode] = [ '机器编号' => $machineCode, '部门名称' => $item['部门名称'] ?? '', '总费用' => 0, '电费' => 0, '气费' => 0, ]; } $machineUtilities[$machineCode]['总费用'] += $totalCost; $machineUtilities[$machineCode]['电费'] += $electricityCost; $machineUtilities[$machineCode]['气费'] += $gasCost; } Log::info("机台水电费统计: " . json_encode($machineUtilities, JSON_UNESCAPED_UNICODE)); // 3. 获取本月所有工单的机台运行时间 $workOrderHours = $this->getMachineWorkHours($month); if (empty($workOrderHours)) { Log::warning("{$month}月份未找到工单运行时间数据"); return; } // 4. 计算每个机台的总运行时间 $machineTotalHours = []; foreach ($workOrderHours as $workOrder) { $machineCode = $workOrder['sczl_jtbh'] ?? ''; $hours = floatval($workOrder['占用机时'] ?? 0); if (empty($machineCode) || $hours <= 0) { continue; } if (!isset($machineTotalHours[$machineCode])) { $machineTotalHours[$machineCode] = 0; } $machineTotalHours[$machineCode] += $hours; } // 5. 计算每个工单应分摊的水电费 $allocationResults = $this->allocateUtilitiesToWorkOrders($machineUtilities, $workOrderHours, $machineTotalHours); // 6. 将分配结果更新到monthlyCostDetails中 $this->updateDirectUtilitiesToCostDetails($allocationResults); Log::info("直接水电费分配完成,更新工单数: " . count($allocationResults)); } catch (\Exception $e) { Log::error("计算直接水电费失败: " . $e->getMessage()); throw new Exception("直接水电费计算失败: " . $e->getMessage()); } } /** * 获取机台生产工单的运行时间 * 从已计算的数据中获取,因为成本v23_月度成本明细表中尚未插入数据 */ protected function getMachineWorkHours(string $month): array { if (empty($this->monthlyCostDetails)) { Log::warning("月度成本明细数据为空,无法获取机台运行时间"); return []; } $workHours = []; foreach ($this->monthlyCostDetails as $detail) { $machineCode = $detail['sczl_jtbh'] ?? ''; $hours = floatval($detail['占用机时'] ?? 0); if (empty($machineCode) || $hours <= 0) { continue; } $workHours[] = [ 'sczl_gdbh' => $detail['sczl_gdbh'] ?? '', 'sczl_yjno' => $detail['sczl_yjno'] ?? '', 'sczl_gxh' => $detail['sczl_gxh'] ?? '', 'sczl_jtbh' => $machineCode, '占用机时' => $hours, '车间名称' => $detail['车间名称'] ?? '', ]; } Log::info("从月度成本明细数据中获取机台运行时间,记录数: " . count($workHours)); return $workHours; } /** * 将水电费按照工时比例分配到各个工单 */ protected function allocateUtilitiesToWorkOrders(array $machineUtilities, array $workOrderHours, array $machineTotalHours): array { $allocationResults = []; foreach ($workOrderHours as $workOrder) { $machineCode = $workOrder['sczl_jtbh'] ?? ''; $hours = floatval($workOrder['占用机时'] ?? 0); // 如果机台没有水电费数据,跳过 if (empty($machineCode) || !isset($machineUtilities[$machineCode]) || $machineUtilities[$machineCode]['总费用'] <= 0) { continue; } // 如果机台总运行时间为0,跳过 if (!isset($machineTotalHours[$machineCode]) || $machineTotalHours[$machineCode] <= 0) { Log::warning("机台 {$machineCode} 总运行时间为0,无法分摊"); continue; } // 计算该工单应分摊的金额 $allocationRatio = $hours / $machineTotalHours[$machineCode]; $allocatedAmount = round($machineUtilities[$machineCode]['总费用'] * $allocationRatio, 2); // 构建唯一标识(用于查找对应的monthlyCostDetails记录) $uniqueKey = $this->getWorkOrderUniqueKey($workOrder); $allocationResults[$uniqueKey] = [ 'unique_key' => $uniqueKey, 'sczl_gdbh' => $workOrder['sczl_gdbh'] ?? '', 'sczl_yjno' => $workOrder['sczl_yjno'] ?? '', 'sczl_gxh' => $workOrder['sczl_gxh'] ?? '', 'sczl_jtbh' => $machineCode, '占用机时' => $hours, '分摊比例' => $allocationRatio, '分摊金额' => $allocatedAmount, '机台总费用' => $machineUtilities[$machineCode]['总费用'], '机台总工时' => $machineTotalHours[$machineCode], ]; } return $allocationResults; } /** * 获取工单唯一标识 */ protected function getWorkOrderUniqueKey(array $workOrder): string { return sprintf( '%s-%s-%s-%s', $workOrder['sczl_gdbh'] ?? '', $workOrder['sczl_yjno'] ?? '', $workOrder['sczl_gxh'] ?? '', $workOrder['sczl_jtbh'] ?? '' ); } /** * 将分配的水电费更新到monthlyCostDetails中 */ protected function updateDirectUtilitiesToCostDetails(array $allocationResults): void { if (empty($allocationResults) || empty($this->monthlyCostDetails)) { Log::warning("水电费分配结果或成本明细数据为空,无法更新"); return; } $updatedCount = 0; $totalAllocatedAmount = 0; foreach ($this->monthlyCostDetails as &$costDetail) { $uniqueKey = $this->getWorkOrderUniqueKey($costDetail); if (isset($allocationResults[$uniqueKey])) { $allocatedAmount = $allocationResults[$uniqueKey]['分摊金额']; $costDetail['直接水电'] = $allocatedAmount; $totalAllocatedAmount += $allocatedAmount; $updatedCount++; } } Log::info("已更新直接水电费的工单数量: {$updatedCount}, 分配总金额: {$totalAllocatedAmount}"); } /** * 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 = [ '废气处理' => '废气处理', '锅炉' => '锅炉', '空压机' => '空压机', '热水锅炉' => '热水锅炉', '真空鼓风机' => '真空鼓风机', '中央空调' => '中央空调', '待分摊总额' => '待分摊总额', ]; 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(); // 2. 插入前检查数据结构 $this->validateAndFixData(); // 3. 插入新数据(使用分批插入避免单次数据量过大) $this->insertDataInBatches(); } /** * 验证并修复数据 */ protected function validateAndFixData(): void { if (empty($this->monthlyCostDetails)) { return; } // 获取表结构 $tableName = '成本v23_月度成本明细'; $columns = Db::query("DESCRIBE `{$tableName}`"); $columnNames = array_column($columns, 'Field'); Log::info("表{$tableName}结构: " . implode(', ', $columnNames)); Log::info("插入数据字段数: " . count(array_keys($this->monthlyCostDetails[0]))); // 检查每条数据的字段数 foreach ($this->monthlyCostDetails as $index => $row) { $rowCount = count($row); if ($rowCount !== count($columnNames)) { Log::error("第{$index}行字段数不匹配: 数据有{$rowCount}个字段,表有" . count($columnNames) . "个字段"); Log::error("数据字段: " . implode(', ', array_keys($row))); // 修正第58行数据(从0开始计数,第58行是索引57) if ($index === 57) { $this->fixRowData($row, $columnNames); } } } } /** * 修复行数据 */ protected function fixRowData(array &$row, array $columnNames): void { $fixedRow = []; foreach ($columnNames as $column) { $fixedRow[$column] = $row[$column] ?? null; } // 替换原数据 $this->monthlyCostDetails[57] = $fixedRow; Log::info("已修复第58行数据"); } /** * 分批插入数据 */ protected function insertDataInBatches(): void { // 分批插入月度成本明细 if (!empty($this->monthlyCostDetails)) { $batchSize = 100; $total = count($this->monthlyCostDetails); for ($i = 0; $i < $total; $i += $batchSize) { $batch = array_slice($this->monthlyCostDetails, $i, $batchSize); // 构建确保字段顺序正确的SQL $firstRow = reset($batch); $fields = array_keys($firstRow); $fieldStr = '`' . implode('`,`', $fields) . '`'; $values = []; foreach ($batch as $row) { $rowValues = []; foreach ($fields as $field) { $value = $row[$field] ?? null; $rowValues[] = is_numeric($value) ? $value : "'" . addslashes($value) . "'"; } $values[] = '(' . implode(',', $rowValues) . ')'; } $sql = "INSERT INTO `成本v23_月度成本明细` ({$fieldStr}) VALUES " . implode(',', $values); try { Db::execute($sql); Log::info("成功插入批次 " . ($i/$batchSize + 1)); } catch (\Exception $e) { Log::error("插入批次失败: " . $e->getMessage()); throw $e; } } } if (!empty($this->allocationFactors)) { $sql = Db::name('成本_各月分摊系数')->fetchSql(true)->insertAll($this->allocationFactors); Db::query($sql); } } }