| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639 |
- <?php
- namespace app\service;
- use think\Db;
- use think\Exception;
- use think\Log;
- /**
- * 统一成本核算服务类
- * 五项计算完成后统一插入数据库
- */
- class UnifiedCostCalculationService
- {
- // 存储中间数据的数组
- protected $monthlyCostDetails = [];
- protected $allocationFactors = [];
- // 配置常量
- const FLOOR_GROUP_MAP = [
- '1' => ['02、胶印机组', '03、卷凹机组', '06、单凹机组', '05、圆切机组', '04、圆烫机组', '10、模切机组', '09、烫金机组'],
- '2' => ['01、切纸机组', '11、检品机组', '07、丝印机组', '12、覆膜机组', '08、喷码机组'],
- ];
- // 科目名称映射
- const SUBJECT_MAPPING = [
- '废气处理' => '废气处理',
- '锅炉' => '锅炉',
- '空压机' => '空压机',
- '热水锅炉' => '热水锅炉',
- '真空鼓风机' => '真空鼓风机',
- '中央空调' => '中央空调',
- '待分摊总额' => '待分摊总额',
- ];
- // 字段默认值
- const DEFAULT_FIELD_VALUES = [
- '直接水电' => 0,
- '分摊材料' => 0,
- '车间人工' => 0,
- '部门人工附加' => 0,
- '分摊水电' => 0,
- '废气处理' => 0,
- '锅炉' => 0,
- '空压机' => 0,
- '热水锅炉' => 0,
- '真空鼓风机' => 0,
- '中央空调' => 0,
- '分摊其他' => 0,
- 'A_小时折旧额' => 0,
- '直接材料' => 0,
- '考核直接材料' => 0,
- '直接折旧' => 0,
- '分摊折旧' => 0,
- '场地租金' => 0,
- '成本合计' => 0,
- ];
- // 添加科目名称到数据库字段的映射
- const SUBJECT_TO_FIELD_MAP = [
- // 真空鼓风机相关
- '真空鼓风机' => '真空鼓风机',
- '真空鼓风' => '真空鼓风机',
- '6#楼真空鼓风' => '真空鼓风机',
- '真空风机' => '真空鼓风机',
- // 废气处理相关
- '废气处理' => '废气处理',
- '废气' => '废气处理',
- // 锅炉相关
- '锅炉' => '锅炉',
- '热水锅炉' => '热水锅炉',
- // 空压机相关
- '空压机' => '空压机',
- '空压' => '空压机',
- // 中央空调相关
- '中央空调' => '中央空调',
- '空调' => '中央空调',
- // 待分摊总额
- '待分摊总额' => '分摊水电',
- '分摊总额' => '分摊水电',
- ];
- // 批次大小
- const BATCH_SIZE = 100;
- /**
- * 主入口:执行所有成本计算并统一入库
- */
- public function calculateAndSaveAll(array $param): array
- {
- Db::startTrans();
- try {
- if (!isset($param['month'])) {
- throw new Exception("缺少必要参数: month");
- }
- $month = $param['month'];
- $sysId = $param['sys_id'] ?? uniqid();
- // 重置数据数组,确保是空数组
- $this->monthlyCostDetails = [];
- $this->allocationFactors = [];
- // 1. 清空旧数据
- $this->clearOldData($month);
- // 2. 执行五项计算
- $this->calculateDirectLabor($param);
- // Log::info("直接人工计算完成", ['记录数' => is_countable($this->monthlyCostDetails) ? count($this->monthlyCostDetails) : 0]);
- $this->calculateDirectUtilities($param);
- $this->calculateIndirectMaterials($month);
- $this->calculateIndirectLabor($month);
- $this->calculateApportionedUtilities($param);
- // 3. 统一插入数据
- $this->saveAllData($month, $sysId);
- Db::commit();
- $this->logSuccess($month);
- return $this->buildSuccessResponse($month);
- } catch (\Throwable $t) {
- Db::rollback();
- // 直接记录错误,不通过 logError 方法
- try {
- error_log("成本核算失败: " . $t->getMessage() . " at " . $t->getFile() . ":" . $t->getLine());
- error_log("堆栈跟踪: " . $t->getTraceAsString());
- // 尝试记录到系统日志
- // if (class_exists('think\Log')) {
- // Log::error("成本核算失败(直接记录)", [
- // 'message' => $t->getMessage(),
- // 'file' => $t->getFile(),
- // 'line' => $t->getLine()
- // ]);
- // }
- } catch (\Throwable $logError) {
- // 忽略日志记录错误
- }
- return [
- 'success' => false,
- 'message' => '成本核算失败: ' . $t->getMessage(),
- 'error' => $t->getMessage(),
- 'file' => $t->getFile(),
- 'line' => $t->getLine()
- ];
- }
- }
- /**
- * 清空旧数据
- */
- protected function clearOldData(string $month): void
- {
- // 确保数据数组是有效的数组
- $this->monthlyCostDetails = [];
- $this->allocationFactors = [];
- // 记录清空操作
- // Log::info("清空旧数据", ['month' => $month]);
- }
- /**
- * 1. 计算直接人工
- */
- protected function calculateDirectLabor(array $param): void
- {
- try {
- $month = $param['month'];
- $sysId = $param['sys_id'] ?? '';
- $now = date('Y-m-d H:i:s');
- $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($this->getDirectLaborFields())
- ->where('a.sys_ny', $month)
- ->group('a.sczl_gdbh,a.sczl_yjno,a.sczl_gxh,a.sczl_jtbh')
- ->select();
- // 转换为数组(如果返回的是对象)
- if (!is_array($list)) {
- $list = $list->toArray();
- }
- // Log::info("直接人工查询结果数: " . count($list));
- foreach ($list as $item) {
- // 确保 $item 是数组
- $itemArray = is_object($item) ? (array)$item : $item;
- if (!is_array($itemArray)) {
- // Log::warning("直接人工数据项格式不正确: " . gettype($item));
- continue;
- }
- $this->monthlyCostDetails[] = $this->buildMonthlyCostDetail($itemArray, $month, $sysId, $now);
- }
- } catch (\Exception $e) {
- // Log::error("计算直接人工失败: " . $e->getMessage());
- throw new Exception("直接人工计算失败: " . $e->getMessage());
- }
- }
- /**
- * 获取直接人工查询字段
- */
- protected function getDirectLaborFields(): string
- {
- return '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.千件工价';
- }
- /**
- * 构建月度成本明细记录
- */
- protected function buildMonthlyCostDetail(array $data, string $month, string $sysId, string $now): array
- {
- $banju = $data['版距'] === '0.0' ? 1000 : $data['版距'];
- $moshushu = $this->calculateMoshushu($data);
- $chanliang = $this->calculateChanliang($data);
- $renGongFenTan = $this->calculateRenGongFenTan($chanliang, $data);
- return array_merge([
- '车间名称' => $data['使用部门'] ?? '',
- 'sys_ny' => $month,
- 'sczl_gdbh' => $data['工单编号'],
- '印件名称' => $data['印件名称'] ?? '',
- 'sczl_yjno' => $data['印件号'],
- 'sczl_gxh' => $data['工序号'],
- '工序名称' => $data['工序名称'] ?? '',
- 'sczl_jtbh' => $data['sczl_jtbh'] ?? '',
- '卷张换算系数' => floatval($banju) / 1000,
- '占用机时' => floatval($data['占用机时']) ?? 0,
- '班组车头产量' => floatval($data['班组车头产量']) ?? 0,
- 'sczl_ms' => floatval($moshushu),
- '工序难度系数' => floatval($data['工序难度系数']) ?? 1,
- '班组换算产量' => floatval($data['班组换算产量']) ?? 0,
- '千件工价' => floatval($data['千件工价']) ?? 0,
- '计件产量' => floatval($chanliang),
- '水电分摊因子' => floatval($data['占用机时']) ?? 0,
- '材料分摊因子' => floatval($chanliang),
- '人工分摊因子' => floatval($renGongFenTan),
- 'Sys_id' => $sysId,
- 'Sys_rq' => $now
- ], self::DEFAULT_FIELD_VALUES);
- }
- /**
- * 计算墨色数
- */
- protected function calculateMoshushu(array $data): float
- {
- if (strpos($data['工序名称'] ?? '', '切废') !== false) {
- return 0.2;
- }
- return $data['墨色数'] === '0.00' ? 1.0 : floatval($data['墨色数']);
- }
- /**
- * 计算产量
- */
- protected function calculateChanliang(array $data): float
- {
- return ($data['班组车头产量'] * $data['工序难度系数']) + $data['班组换算产量'];
- }
- /**
- * 计算人工分摊
- */
- protected function calculateRenGongFenTan(float $chanliang, array $data): float
- {
- return ($chanliang / 1000) * $data['千件工价'];
- }
- /**
- * 2. 计算直接水电
- */
- protected function calculateDirectUtilities(array $param): void
- {
- $month = $param['month'];
- if (empty($this->monthlyCostDetails)) {
- throw new Exception("请先执行直接人工计算");
- }
- $utilityData = $this->fetchDirectUtilities($month);
- if (empty($utilityData)) {
- // Log::info("{$month}月份未找到直接水电费用数据");
- return;
- }
- $machineUtilities = $this->calculateMachineUtilities($utilityData);
- $workOrderHours = $this->getMachineWorkHours();
- $machineTotalHours = $this->calculateMachineTotalHours($workOrderHours);
- $allocationResults = $this->allocateUtilitiesToWorkOrders(
- $machineUtilities,
- $workOrderHours,
- $machineTotalHours
- );
- $this->updateDirectUtilitiesToCostDetails($allocationResults);
- }
- /**
- * 获取直接水电数据
- */
- protected function fetchDirectUtilities(string $month): array
- {
- return Db::name('成本_各月水电气')
- ->where('Sys_ny', $month)
- ->where('费用类型', '直接')
- ->field('设备编号, 部门名称, 科目名称, 耗电量, 单位电价, 耗气量, 单位气价')
- ->select();
- }
- /**
- * 计算机台水电费用
- */
- protected function calculateMachineUtilities(array $utilityData): array
- {
- $machineUtilities = [];
- foreach ($utilityData as $item) {
- $machineCode = $item['设备编号'] ?? '';
- if (empty($machineCode)) {
- continue;
- }
- $electricityCost = $this->calculateElectricityCost($item);
- $gasCost = $this->calculateGasCost($item);
- $totalCost = $electricityCost + $gasCost;
- if (!isset($machineUtilities[$machineCode])) {
- $machineUtilities[$machineCode] = [
- '机器编号' => $machineCode,
- '部门名称' => $item['部门名称'] ?? '',
- '总费用' => 0,
- '电费' => 0,
- '气费' => 0,
- ];
- }
- $machineUtilities[$machineCode]['总费用'] += $totalCost;
- $machineUtilities[$machineCode]['电费'] += $electricityCost;
- $machineUtilities[$machineCode]['气费'] += $gasCost;
- }
- return $machineUtilities;
- }
- /**
- * 计算电费
- */
- protected function calculateElectricityCost(array $item): float
- {
- return round(floatval($item['耗电量'] ?? 0) * floatval($item['单位电价'] ?? 0), 2);
- }
- /**
- * 计算气费
- */
- protected function calculateGasCost(array $item): float
- {
- return round(floatval($item['耗气量'] ?? 0) * floatval($item['单位气价'] ?? 0), 2);
- }
- /**
- * 获取机台工作小时数
- */
- protected function getMachineWorkHours(): array
- {
- $workHours = [];
- foreach ($this->monthlyCostDetails as $detail) {
- try {
- // 确保 $detail 是数组
- $detailArray = is_object($detail) ? (array)$detail : $detail;
- if (!is_array($detailArray)) {
- // Log::warning("成本明细数据格式不正确: " . gettype($detail));
- continue;
- }
- $machineCode = $detailArray['sczl_jtbh'] ?? '';
- $hours = floatval($detailArray['占用机时'] ?? 0);
- if (!empty($machineCode) && $hours > 0) {
- $workHours[] = [
- 'sczl_gdbh' => $detailArray['sczl_gdbh'] ?? '',
- 'sczl_yjno' => $detailArray['sczl_yjno'] ?? '',
- 'sczl_gxh' => $detailArray['sczl_gxh'] ?? '',
- 'sczl_jtbh' => $machineCode,
- '占用机时' => $hours,
- '车间名称' => $detailArray['车间名称'] ?? '',
- ];
- }
- } catch (\Exception $e) {
- // Log::error("处理机台工作小时数时出错: " . $e->getMessage());
- continue;
- }
- }
- // Log::info("从月度成本明细数据中获取机台运行时间,记录数: " . count($workHours));
- return $workHours;
- }
- /**
- * 计算机台总小时数
- */
- protected function calculateMachineTotalHours(array $workOrderHours): array
- {
- $machineTotalHours = [];
- foreach ($workOrderHours as $workOrder) {
- $machineCode = $workOrder['sczl_jtbh'] ?? '';
- $hours = floatval($workOrder['占用机时'] ?? 0);
- if (!empty($machineCode) && $hours > 0) {
- $machineTotalHours[$machineCode] = ($machineTotalHours[$machineCode] ?? 0) + $hours;
- }
- }
- return $machineTotalHours;
- }
- /**
- * 分摊水电到工单
- */
- protected function allocateUtilitiesToWorkOrders(
- array $machineUtilities,
- array $workOrderHours,
- array $machineTotalHours
- ): array {
- $allocationResults = [];
- $processedCount = 0;
- $skippedCount = 0;
- foreach ($workOrderHours as $workOrder) {
- try {
- // 确保 $workOrder 是数组
- $orderArray = is_object($workOrder) ? (array)$workOrder : $workOrder;
- if (!is_array($orderArray)) {
- // Log::warning("工单小时数据格式不正确: " . gettype($workOrder));
- $skippedCount++;
- continue;
- }
- $machineCode = $orderArray['sczl_jtbh'] ?? '';
- $hours = floatval($orderArray['占用机时'] ?? 0);
- if (empty($machineCode)) {
- // Log::debug("工单缺少机器编号: " . json_encode($orderArray));
- $skippedCount++;
- continue;
- }
- if (!isset($machineUtilities[$machineCode])) {
- // Log::debug("机器 {$machineCode} 没有水电费用数据");
- $skippedCount++;
- continue;
- }
- if ($machineUtilities[$machineCode]['总费用'] <= 0) {
- // Log::debug("机器 {$machineCode} 水电费用为0");
- $skippedCount++;
- continue;
- }
- if (!isset($machineTotalHours[$machineCode]) || $machineTotalHours[$machineCode] <= 0) {
- // Log::warning("机器 {$machineCode} 总运行时间为0,无法分摊");
- $skippedCount++;
- continue;
- }
- $allocationRatio = $hours / $machineTotalHours[$machineCode];
- $allocatedAmount = round($machineUtilities[$machineCode]['总费用'] * $allocationRatio, 2);
- $uniqueKey = $this->getWorkOrderUniqueKey($orderArray);
- $allocationResults[$uniqueKey] = [
- 'unique_key' => $uniqueKey,
- 'sczl_gdbh' => $orderArray['sczl_gdbh'] ?? '',
- 'sczl_yjno' => $orderArray['sczl_yjno'] ?? '',
- 'sczl_gxh' => $orderArray['sczl_gxh'] ?? '',
- 'sczl_jtbh' => $machineCode,
- '占用机时' => $hours,
- '分摊比例' => $allocationRatio,
- '分摊金额' => $allocatedAmount,
- '机台总费用' => $machineUtilities[$machineCode]['总费用'],
- '机台总工时' => $machineTotalHours[$machineCode],
- ];
- $processedCount++;
- } catch (\Exception $e) {
- // Log::error("分摊水电到工单时出错: " . $e->getMessage());
- $skippedCount++;
- continue;
- }
- }
- // Log::info("分摊水电处理完成,成功: {$processedCount}, 跳过: {$skippedCount}, 总计: " . count($workOrderHours));
- return $allocationResults;
- }
- /**
- * 获取工单唯一键
- */
- protected function getWorkOrderUniqueKey($workOrder): string
- {
- // 记录传入参数的类型和内容(用于调试)
- $type = gettype($workOrder);
- // Log::debug("getWorkOrderUniqueKey 接收参数类型: {$type}");
- // 如果是对象,转换为数组
- if (is_object($workOrder)) {
- $workOrder = (array)$workOrder;
- }
- // 确保是数组
- // if (!is_array($workOrder)) {
- // Log::error("getWorkOrderUniqueKey 参数不是数组: {$type}, 值: " . print_r($workOrder, true));
- // return 'invalid-' . uniqid();
- // }
- // 调试:记录数组内容
- // if (count($workOrder) < 4) {
- // Log::debug("getWorkOrderUniqueKey 数组内容: " . json_encode($workOrder));
- // }
- // 安全地获取所有可能键名
- $gdbh = '';
- $yjno = '';
- $gxh = '';
- $jtbh = '';
- // 尝试各种可能的键名
- if (isset($workOrder['sczl_gdbh'])) {
- $gdbh = $workOrder['sczl_gdbh'];
- } elseif (isset($workOrder['工单编号'])) {
- $gdbh = $workOrder['工单编号'];
- }
- if (isset($workOrder['sczl_yjno'])) {
- $yjno = $workOrder['sczl_yjno'];
- } elseif (isset($workOrder['印件号'])) {
- $yjno = $workOrder['印件号'];
- }
- if (isset($workOrder['sczl_gxh'])) {
- $gxh = $workOrder['sczl_gxh'];
- } elseif (isset($workOrder['工序号'])) {
- $gxh = $workOrder['工序号'];
- }
- if (isset($workOrder['sczl_jtbh'])) {
- $jtbh = $workOrder['sczl_jtbh'];
- } elseif (isset($workOrder['机器编号'])) {
- $jtbh = $workOrder['机器编号'];
- }
- // 验证所有字段都不为空
- // if (empty($gdbh) || empty($yjno) || empty($gxh) || empty($jtbh)) {
- // Log::warning("工单唯一键字段不完整: gdbh={$gdbh}, yjno={$yjno}, gxh={$gxh}, jtbh={$jtbh}");
- // }
- $key = sprintf('%s-%s-%s-%s', $gdbh, $yjno, $gxh, $jtbh);
- // Log::debug("生成的唯一键: {$key}");
- return $key;
- }
- /**
- * 更新直接水电到成本明细
- */
- protected function updateDirectUtilitiesToCostDetails(array $allocationResults): void
- {
- if (empty($allocationResults) || empty($this->monthlyCostDetails)) {
- // Log::warning("水电费分配结果或成本明细数据为空,无法更新");
- return;
- }
- $updatedCount = 0;
- $totalAllocatedAmount = 0;
- $notFoundCount = 0;
- foreach ($this->monthlyCostDetails as &$costDetail) {
- try {
- // 确保 $costDetail 是数组
- $detailArray = is_object($costDetail) ? (array)$costDetail : $costDetail;
- $uniqueKey = $this->getWorkOrderUniqueKey($detailArray);
- if (isset($allocationResults[$uniqueKey])) {
- $allocatedAmount = $allocationResults[$uniqueKey]['分摊金额'] ?? 0;
- $costDetail['直接水电'] = $allocatedAmount;
- $totalAllocatedAmount += $allocatedAmount;
- $updatedCount++;
- } else {
- $notFoundCount++;
- }
- } catch (\Exception $e) {
- // Log::error("更新直接水电费时出错: " . $e->getMessage());
- continue;
- }
- }
- // Log::info("已更新直接水电费的工单数量: {$updatedCount}, 未找到的工单数: {$notFoundCount}, 分配总金额: {$totalAllocatedAmount}");
- // 如果有大量未找到的工单,记录详细信息
- // if ($notFoundCount > 0 && $notFoundCount > $updatedCount) {
- // Log::warning("有大量工单未匹配到水电费分配结果,可能键名不匹配");
- // }
- }
- /**
- * 3. 计算间接材料分摊
- */
- protected function calculateIndirectMaterials(string $month): void
- {
- if (empty($this->monthlyCostDetails)) {
- return;
- }
- $date = substr($month, 0, 4) . '-' . substr($month, 4, 2);
- $totalMoney = $this->getIndirectMaterialTotal($date);
- if (!$totalMoney || $totalMoney <= 0) {
- return;
- }
- $totalChroma = $this->calculateTotalChroma();
- if ($totalChroma <= 0) {
- return;
- }
- $this->allocateIndirectMaterials($totalMoney, $totalChroma);
- }
- /**
- * 获取间接材料总额
- */
- protected function getIndirectMaterialTotal(string $date): float
- {
- $result = Db::name('材料出库单列表')
- ->where([
- '出库日期' => ['like', $date . '%'],
- '部门' => '印刷成本中心'
- ])
- ->whereNull('表体生产订单号')
- ->field('SUM(金额) as money')
- ->find();
- return $result ? floatval($result['money']) : 0;
- }
- /**
- * 计算总色度数
- */
- protected function calculateTotalChroma(): float
- {
- $totalChroma = 0;
- foreach ($this->monthlyCostDetails as $detail) {
- $totalChroma += floatval($detail['班组车头产量']) * floatval($detail['sczl_ms']);
- }
- return $totalChroma;
- }
- /**
- * 分摊间接材料
- */
- protected function allocateIndirectMaterials(float $totalMoney, float $totalChroma): void
- {
- foreach ($this->monthlyCostDetails as &$detail) {
- $chroma = floatval($detail['班组车头产量']) * floatval($detail['sczl_ms']);
- $money = round($totalMoney * ($chroma / $totalChroma), 2);
- $detail['分摊材料'] = $money;
- }
- }
- /**
- * 4. 计算间接人工分摊
- */
- protected function calculateIndirectLabor(string $month): void
- {
- $wageRatio = $this->getWageRatio();
- if (empty($wageRatio)) {
- return;
- }
- $monthWage = $this->getMonthlyWage($month);
- if (empty($monthWage)) {
- return;
- }
- $this->allocateIndirectLabor($wageRatio, $monthWage);
- }
- /**
- * 获取工资比例
- */
- protected function getWageRatio(): array
- {
- $workshopTotals = [];
- foreach ($this->monthlyCostDetails as $detail) {
- $workshop = $detail['车间名称'];
- $amount = floatval($detail['人工分摊因子']);
- $workshopTotals[$workshop] = ($workshopTotals[$workshop] ?? 0) + $amount;
- }
- $total = array_sum($workshopTotals);
- if ($total <= 0) {
- return [];
- }
- $ratios = [];
- foreach ($workshopTotals as $workshop => $workshopTotal) {
- $ratios[$workshop] = round($workshopTotal / $total, 4);
- }
- return $ratios;
- }
- /**
- * 获取月度工资数据
- */
- protected function getMonthlyWage(string $month): array
- {
- return Db::name('成本_各月其他费用')
- ->where('sys_ny', $month)
- ->field('部门人员工资,管理人员工资')
- ->find() ?: [];
- }
- /**
- * 分摊间接人工
- */
- protected function allocateIndirectLabor(array $wageRatio, array $monthWage): void
- {
- 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 {
- $wageTypes = [
- '部门人员工资' => '车间人工',
- '管理人员工资' => '部门人工附加'
- ];
- foreach ($wageTypes as $wageType => $fieldName) {
- if (empty($monthWage[$wageType]) || $monthWage[$wageType] <= 0) {
- continue;
- }
- $workshopAmount = $ratio * $monthWage[$wageType];
- if ($chromaData['total'] <= 0) {
- continue;
- }
- $this->allocateWageToDetails($workshop, $workshopAmount, $chromaData, $fieldName);
- }
- }
- /**
- * 分配工资到明细记录
- */
- protected function allocateWageToDetails(
- string $workshop,
- float $workshopAmount,
- array $chromaData,
- string $fieldName
- ): void {
- foreach ($this->monthlyCostDetails as &$detail) {
- if ($detail['车间名称'] === $workshop) {
- $chroma = floatval($detail['班组车头产量']) * floatval($detail['sczl_ms']);
- $amount = round($chroma / $chromaData['total'] * $workshopAmount, 2);
- $detail[$fieldName] += $amount;
- }
- }
- }
- /**
- * 5. 计算分摊水电
- */
- protected function calculateApportionedUtilities(array $param): void
- {
- try {
- $month = $param['month'];
- $sysId = $param['sys_id'] ?? '';
- $utilityData = $this->fetchApportionedUtilities($month);
- if (empty($utilityData)) {
- // Log::info("{$month}月份未找到分摊水电数据");
- return;
- }
- // 记录原始科目名称用于调试
- $originalSubjects = array_unique(array_column($utilityData, '科目名称'));
- // Log::info("原始科目名称: " . implode(', ', $originalSubjects));
- $machineAllocations = $this->calculateMachineAllocations($utilityData);
- if (!empty($machineAllocations)) {
- $this->generateAllocationFactors($machineAllocations, $month, $sysId);
- $this->allocateApportionedUtilities($machineAllocations);
- }
- } catch (\Exception $e) {
- // Log::error("计算分摊水电时出错: " . $e->getMessage());
- throw new Exception("分摊水电计算失败: " . $e->getMessage());
- }
- }
- /**
- * 获取分摊水电数据
- */
- protected function fetchApportionedUtilities(string $month): array
- {
- return Db::name('成本_各月水电气')
- ->where('Sys_ny', $month)
- ->whereLike('费用类型', '%分摊%')
- ->select();
- }
- /**
- * 计算机台分摊金额
- */
- protected function calculateMachineAllocations(array $utilityData): array
- {
- $allocations = [];
- $machineHours = $this->getMachineHours();
- foreach ($utilityData as $item) {
- try {
- $subject = $this->simplifySubjectName($item['科目名称']);
- $amount = $this->calculateUtilityAmount($item);
- if ($amount <= 0) {
- continue;
- }
- $this->allocateBySubject($allocations, $subject, $amount, $machineHours);
- } catch (\Exception $e) {
- // Log::error("计算机台分摊金额时出错: " . $e->getMessage());
- continue;
- }
- }
- return $allocations;
- }
- /**
- * 获取机台运行时间
- */
- protected function getMachineHours(): array
- {
- $hours = [];
- foreach ($this->monthlyCostDetails as $detail) {
- $machine = $detail['sczl_jtbh'];
- $hour = floatval($detail['占用机时']);
- if (!empty($machine)) {
- $hours[$machine] = ($hours[$machine] ?? 0) + $hour;
- }
- }
- return $hours;
- }
- /**
- * 按科目分摊
- */
- protected function allocateBySubject(
- array &$allocations,
- string $subject,
- float $amount,
- array $machineHours
- ): void {
- try {
- if ($subject === '待分摊总额') {
- $this->allocateByFloor($allocations, $amount, $machineHours);
- } elseif (in_array($subject, ['锅炉', '热水锅炉'])) {
- $this->allocateToRollCoater($allocations, $subject, $amount, $machineHours);
- } else {
- $this->allocateGlobally($allocations, $subject, $amount, $machineHours);
- }
- } catch (\Exception $e) {
- // Log::error("按科目分摊时出错 (科目: {$subject}): " . $e->getMessage());
- }
- }
- /**
- * 按楼层分摊
- */
- protected function allocateByFloor(array &$allocations, float $amount, array $machineHours): void
- {
- $floorData = $this->groupMachinesByFloor($machineHours);
- if ($floorData['totalHours'] <= 0) {
- return;
- }
- foreach ($floorData['floors'] as $floor => $data) {
- if ($data['hours'] <= 0) {
- continue;
- }
- $floorAmount = $amount * ($data['hours'] / $floorData['totalHours']);
- $this->allocateToFloorMachines($allocations, $floorAmount, $data['machines'], $machineHours);
- }
- }
- /**
- * 按楼层分组机台
- */
- protected function groupMachinesByFloor(array $machineHours): array
- {
- $floors = ['1' => ['hours' => 0, 'machines' => []], '2' => ['hours' => 0, 'machines' => []]];
- $totalHours = 0;
- foreach ($machineHours as $machine => $hours) {
- $floor = $this->getFloorByMachine($machine);
- if ($floor && isset($floors[$floor])) {
- $floors[$floor]['hours'] += $hours;
- $floors[$floor]['machines'][] = $machine;
- $totalHours += $hours;
- }
- }
- return ['floors' => $floors, 'totalHours' => $totalHours];
- }
- /**
- * 分摊到楼层机台
- */
- protected function allocateToFloorMachines(
- array &$allocations,
- float $floorAmount,
- array $machines,
- array $machineHours
- ): void {
- $floorHours = 0;
- foreach ($machines as $machine) {
- $floorHours += $machineHours[$machine];
- }
- if ($floorHours <= 0) {
- return;
- }
- foreach ($machines as $machine) {
- $machineHoursAmount = $machineHours[$machine];
- if ($machineHoursAmount <= 0) {
- continue;
- }
- $allocations[$machine]['待分摊总额'] =
- ($allocations[$machine]['待分摊总额'] ?? 0) +
- round($floorAmount * ($machineHoursAmount / $floorHours), 2);
- }
- }
- /**
- * 只分摊到卷凹机组
- */
- protected function allocateToRollCoater(
- array &$allocations,
- string $subject,
- float $amount,
- array $machineHours
- ): void {
- $rollCoaterMachines = $this->filterRollCoaterMachines(array_keys($machineHours));
- $totalHours = 0;
- foreach ($rollCoaterMachines as $machine) {
- $totalHours += $machineHours[$machine];
- }
- if ($totalHours <= 0) {
- return;
- }
- foreach ($rollCoaterMachines as $machine) {
- $machineHoursAmount = $machineHours[$machine];
- if ($machineHoursAmount <= 0) {
- continue;
- }
- $allocations[$machine][$subject] =
- ($allocations[$machine][$subject] ?? 0) +
- round($amount * ($machineHoursAmount / $totalHours), 2);
- }
- }
- /**
- * 全局分摊
- */
- protected function allocateGlobally(
- array &$allocations,
- string $subject,
- float $amount,
- array $machineHours
- ): void {
- $totalHours = array_sum($machineHours);
- if ($totalHours <= 0) {
- // Log::warning("全局分摊失败:总机时为0");
- return;
- }
- foreach ($machineHours as $machine => $hours) {
- try {
- // 确保 $machine 是有效的字符串键名
- if (!is_string($machine) && !is_numeric($machine)) {
- // Log::warning("无效的机器键名: " . print_r($machine, true));
- continue;
- }
- $machine = (string)$machine;
- if ($hours <= 0) {
- continue;
- }
- if (!isset($allocations[$machine])) {
- $allocations[$machine] = [];
- }
- $machineAmount = round($amount * ($hours / $totalHours), 2);
- $allocations[$machine][$subject] =
- ($allocations[$machine][$subject] ?? 0) + $machineAmount;
- } catch (\Exception $e) {
- // Log::error("全局分摊到机器 {$machine} 时出错: " . $e->getMessage());
- continue;
- }
- }
- }
- /**
- * 根据机台获取楼层
- */
- protected function getFloorByMachine(string $machine): ?string
- {
- $group = Db::name('设备_基本资料')
- ->where('设备编号', $machine)
- ->value('设备编组');
- if (!$group) {
- return null;
- }
- foreach (self::FLOOR_GROUP_MAP as $floor => $groupNames) {
- foreach ($groupNames as $groupName) {
- if (strpos($group, $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
- {
- // 先尝试完全匹配
- $subject = trim($subject);
- foreach (self::SUBJECT_TO_FIELD_MAP as $keyword => $mappedField) {
- if ($subject === $keyword) {
- return $mappedField;
- }
- }
- // 尝试部分匹配
- foreach (self::SUBJECT_TO_FIELD_MAP as $keyword => $mappedField) {
- if (strpos($subject, $keyword) !== false) {
- // Log::debug("科目名称部分匹配: {$subject} => {$mappedField} (关键词: {$keyword})");
- return $mappedField;
- }
- }
- // 如果都不匹配,使用默认映射
- // Log::warning("未识别的科目名称: {$subject}, 将映射到 '分摊水电'");
- return '分摊水电';
- }
- /**
- * 计算水电金额
- */
- protected function calculateUtilityAmount(array $item): float
- {
- $electricity = $this->calculateElectricityCost($item);
- $gas = $this->calculateGasCost($item);
- return $electricity + $gas;
- }
- /**
- * 生成分摊系数记录
- */
- protected function generateAllocationFactors(array $allocations, string $month, string $sysId): void
- {
- $now = date('Y-m-d H:i:s');
- foreach ($allocations as $machine => $subjects) {
- if (!is_string($machine) && !is_numeric($machine)) {
- // Log::warning("无效的机台标识: " . print_r($machine, true));
- continue;
- }
- foreach ($subjects as $subject => $amount) {
- if (!is_string($subject)) {
- // Log::warning("无效的科目名称: " . print_r($subject, true));
- continue;
- }
- // 使用简化后的科目名称
- $simplifiedSubject = $this->simplifySubjectName($subject);
- $this->allocationFactors[] = [
- 'Sys_ny' => $month,
- '科目名称' => $simplifiedSubject, // 原始科目名称
- '设备编号' => (string)$machine,
- '分摊系数' => 1,
- '分摊金额' => floatval($amount),
- 'Sys_id' => $sysId,
- 'Sys_rq' => $now,
- ];
- }
- }
- }
- /**
- * 分配分摊水电
- */
- protected function allocateApportionedUtilities(array $machineAllocations): 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);
- if (!isset($detail[$field])) {
- // Log::warning("数据库字段不存在: {$field},跳过该分摊");
- continue;
- }
- $detail[$field] = round($hours * $rate, 2);
- }
- }
- }
- /**
- * 计算机台每机时费用
- */
- protected function calculateMachineRates(array $allocations): array
- {
- $rates = [];
- $machineHours = $this->getMachineHours();
- 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
- {
- // 使用映射表
- if (isset(self::SUBJECT_TO_FIELD_MAP[$subject])) {
- return self::SUBJECT_TO_FIELD_MAP[$subject];
- }
- // 尝试部分匹配
- foreach (self::SUBJECT_TO_FIELD_MAP as $keyword => $mappedField) {
- if (strpos($subject, $keyword) !== false) {
- return $mappedField;
- }
- }
- // 默认映射到 '分摊水电'
- // Log::warning("未知的科目字段: {$subject}, 映射到 '分摊水电'");
- return '分摊水电';
- }
- /**
- * 统一保存所有数据
- */
- protected function saveAllData(string $month, string $sysId): void
- {
- // 1. 删除旧数据
- $this->deleteOldData($month);
- // 2. 插入前检查数据结构
- $this->validateDataStructure();
- // 3. 插入新数据
- $this->insertAllData();
- }
- /**
- * 删除旧数据
- */
- protected function deleteOldData(string $month): void
- {
- Db::name('成本v23_月度成本明细')->where('sys_ny', $month)->delete();
- Db::name('成本_各月分摊系数')->where('Sys_ny', $month)->delete();
- }
- /**
- * 验证数据结构
- */
- protected function validateDataStructure(): void
- {
- try {
- if (!is_array($this->monthlyCostDetails)) {
- // Log::error("monthlyCostDetails 不是数组: " . gettype($this->monthlyCostDetails));
- $this->monthlyCostDetails = [];
- return;
- }
- if (empty($this->monthlyCostDetails)) {
- // Log::warning("月度成本明细数据为空");
- return;
- }
- $tableName = '成本v23_月度成本明细';
- $columns = Db::query("DESCRIBE `{$tableName}`");
- if (empty($columns)) {
- // Log::error("无法获取表结构: {$tableName}");
- return;
- }
- $columnNames = array_column($columns, 'Field');
- // Log::info("表{$tableName}结构字段数: " . count($columnNames));
- // 验证并修复每条记录
- foreach ($this->monthlyCostDetails as $index => &$row) {
- // 确保行是数组
- if (!is_array($row)) {
- // Log::warning("第{$index}行不是数组: " . gettype($row));
- $row = [];
- continue;
- }
- // 检查字段数是否匹配
- $rowCount = count($row);
- $columnCount = count($columnNames);
- if ($rowCount !== $columnCount) {
- // Log::warning("第" . ($index + 1) . "行字段数不匹配: 数据{$rowCount}个,表{$columnCount}个");
- $this->fixRowData($row, $columnNames, $index);
- }
- // 确保所有键名都是字符串
- foreach ($row as $key => $value) {
- if (!is_string($key) && !is_int($key)) {
- // Log::warning("发现非法键名类型 (行 {$index}): " . gettype($key));
- // 移除非法键名
- unset($row[$key]);
- // 如果有值,尝试保存
- if ($value !== null) {
- $row['invalid_key_' . $index] = $value;
- }
- }
- }
- }
- // Log::info("数据结构验证完成,总记录数: " . count($this->monthlyCostDetails));
- } catch (\Throwable $t) {
- // Log::error("验证数据结构时出错: " . $t->getMessage());
- // 不抛出异常,继续执行
- }
- }
- /**
- * 修复行数据
- */
- protected function fixRowData(array &$row, array $columnNames, int $index): void
- {
- $fixedRow = [];
- foreach ($columnNames as $column) {
- // 确保列名是字符串
- $column = (string)$column;
- // 获取值,如果不存在则设置默认值
- if (array_key_exists($column, $row)) {
- $value = $row[$column];
- } else {
- // 根据列名设置默认值
- if (in_array($column, ['直接水电', '分摊材料', '车间人工', '部门人工附加', '分摊水电',
- '废气处理', '锅炉', '空压机', '热水锅炉', '真空鼓风机', '中央空调', '分摊其他'])) {
- $value = 0;
- } else {
- $value = null;
- }
- }
- $fixedRow[$column] = $value;
- }
- $this->monthlyCostDetails[$index] = $fixedRow;
- // Log::info("已修复第" . ($index + 1) . "行数据");
- }
- /**
- * 插入所有数据
- */
- protected function insertAllData(): void
- {
- $this->insertMonthlyCostDetails();
- $this->insertAllocationFactors();
- }
- /**
- * 插入月度成本明细
- */
- protected function insertMonthlyCostDetails(): void
- {
- if (empty($this->monthlyCostDetails)) {
- return;
- }
- $total = count($this->monthlyCostDetails);
- for ($i = 0; $i < $total; $i += self::BATCH_SIZE) {
- $batch = array_slice($this->monthlyCostDetails, $i, self::BATCH_SIZE);
- $this->insertBatch($batch, '成本v23_月度成本明细', $i);
- }
- }
- /**
- * 插入批次数据
- */
- protected function insertBatch(array $batch, string $tableName, int $startIndex): void
- {
- if (empty($batch)) {
- // Log::warning("批次数据为空,跳过插入");
- return;
- }
- $firstRow = reset($batch);
- $fields = array_keys($firstRow);
- // 验证字段名不包含特殊字符
- foreach ($fields as $field) {
- if (preg_match('/[#@$%^&*()+\-=\[\]{}|;:"<>,.?\/]/', $field)) {
- // Log::error("字段名包含特殊字符: {$field}");
- throw new Exception("字段名 '{$field}' 包含非法字符");
- }
- }
- $fieldStr = '`' . implode('`,`', $fields) . '`';
- $values = [];
- foreach ($batch as $rowIndex => $row) {
- $rowValues = [];
- foreach ($fields as $field) {
- $value = $row[$field] ?? null;
- if (is_numeric($value)) {
- $rowValues[] = $value;
- } elseif (is_null($value)) {
- $rowValues[] = 'NULL';
- } else {
- $rowValues[] = "'" . addslashes((string)$value) . "'";
- }
- }
- $values[] = '(' . implode(',', $rowValues) . ')';
- // 记录第一行数据用于调试
- // if ($rowIndex === 0 && $startIndex === 0) {
- // Log::debug("第一行数据字段: " . implode(', ', $fields));
- // Log::debug("第一行数据值: " . implode(', ', $rowValues));
- // }
- }
- $sql = "INSERT INTO `{$tableName}` ({$fieldStr}) VALUES " . implode(',', $values);
- // 记录SQL语句(前200个字符)
- // Log::debug("SQL语句: " . substr($sql, 0, 200) . "...");
- try {
- $result = Db::execute($sql);
- // Log::info("成功插入批次 " . (($startIndex / self::BATCH_SIZE) + 1) . ", 影响行数: " . $result);
- } catch (\Exception $e) {
- // Log::error("插入批次失败: " . $e->getMessage());
- // Log::error("失败SQL: " . $sql);
- throw $e;
- }
- }
- /**
- * 插入分摊系数
- */
- protected function insertAllocationFactors(): void
- {
- if (!empty($this->allocationFactors)) {
- $total = count($this->allocationFactors);
- for ($i = 0; $i < $total; $i += self::BATCH_SIZE) {
- $batch = array_slice($this->allocationFactors, $i, self::BATCH_SIZE);
- $this->insertBatch($batch, '成本_各月分摊系数', $i);
- }
- }
- }
- /**
- * 记录成功日志
- */
- protected function logSuccess(string $month): void
- {
- try {
- // 确保记录数为整数
- $monthlyCostDetailsCount = is_countable($this->monthlyCostDetails) ? count($this->monthlyCostDetails) : 0;
- $allocationFactorsCount = is_countable($this->allocationFactors) ? count($this->allocationFactors) : 0;
- $logData = [
- 'month' => $month,
- '月度成本明细记录数' => $monthlyCostDetailsCount,
- '分摊系数记录数' => $allocationFactorsCount,
- 'timestamp' => date('Y-m-d H:i:s')
- ];
- // 记录每个字段的数据类型,用于调试
- if (!empty($this->monthlyCostDetails)) {
- $firstRecord = reset($this->monthlyCostDetails);
- $logData['first_record_keys'] = is_array($firstRecord) ? array_keys($firstRecord) : 'invalid';
- $logData['first_record_type'] = gettype($firstRecord);
- }
- // Log::info("成本核算完成", $logData);
- } catch (\Throwable $t) {
- // 如果日志记录失败,至少输出到标准错误
- // error_log("成本核算完成(日志记录失败): " . $t->getMessage());
- }
- }
- /**
- * 构建成功响应
- */
- protected function buildSuccessResponse(string $month): array
- {
- try {
- return [
- 'success' => true,
- 'message' => '成本核算完成',
- 'month' => $month,
- 'stats' => [
- 'monthly_cost_details' => is_countable($this->monthlyCostDetails) ? count($this->monthlyCostDetails) : 0,
- 'allocation_factors' => is_countable($this->allocationFactors) ? count($this->allocationFactors) : 0
- ],
- 'timestamp' => date('Y-m-d H:i:s')
- ];
- } catch (\Throwable $t) {
- // 即使构建响应失败,也返回基本成功信息
- return [
- 'success' => true,
- 'message' => '成本核算完成(统计信息获取失败)',
- 'month' => $month,
- 'error' => $t->getMessage()
- ];
- }
- }
- /**
- * 记录错误日志
- */
- protected function logError(\Throwable $t): void
- {
- try {
- $errorDetails = [
- '时间' => date('Y-m-d H:i:s'),
- '错误类型' => get_class($t),
- '错误信息' => $t->getMessage(),
- '错误代码' => $t->getCode(),
- '文件' => $t->getFile(),
- '行号' => $t->getLine(),
- '堆栈跟踪' => $t->getTraceAsString(),
- '月度成本明细数' => is_countable($this->monthlyCostDetails) ? count($this->monthlyCostDetails) : 'N/A',
- '分摊系数数' => is_countable($this->allocationFactors) ? count($this->allocationFactors) : 'N/A',
- ];
- // Log::error("统一成本核算失败详情", $errorDetails);
- } catch (\Throwable $logError) {
- // 如果日志记录也失败,至少输出到标准错误
- // error_log("无法记录错误日志: " . $logError->getMessage());
- // error_log("原始错误: " . $t->getMessage());
- }
- }
- /**
- * 构建错误响应
- */
- protected function buildErrorResponse(Exception $e): array
- {
- return [
- 'success' => false,
- 'message' => '成本核算失败: ' . $e->getMessage(),
- 'error' => $e->getMessage()
- ];
- }
- /**
- * 清理字段名中的特殊字符
- */
- protected function sanitizeFieldNames(array &$row): void
- {
- $sanitized = [];
- foreach ($row as $key => $value) {
- // 移除字段名中的特殊字符,只保留字母、数字、下划线和中文字符
- $cleanKey = preg_replace('/[^\w\x{4e00}-\x{9fa5}]/u', '', $key);
- // if ($cleanKey !== $key) {
- // Log::debug("清理字段名: {$key} => {$cleanKey}");
- // }
- $sanitized[$cleanKey] = $value;
- }
- $row = $sanitized;
- }
- }
|