| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025 |
- <?php
- namespace app\service;
- use think\Db;
- use think\Exception;
- use think\Log;
- /**
- * 统一成本核算服务类
- * 五项计算完成后统一插入数据库
- */
- class UnifiedCostCalculationService
- {
- // 存储中间数据的数组
- protected $monthlyCostDetails = []; // 成本v23_月度成本明细
- 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); // 直接人工(第一步,生成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);
- }
- }
- }
|