|
|
@@ -56,6 +56,35 @@ class UnifiedCostCalculationService
|
|
|
'成本合计' => 0,
|
|
|
];
|
|
|
|
|
|
+ // 添加科目名称到数据库字段的映射
|
|
|
+ const SUBJECT_TO_FIELD_MAP = [
|
|
|
+ // 真空鼓风机相关
|
|
|
+ '真空鼓风机' => '真空鼓风机',
|
|
|
+ '真空鼓风' => '真空鼓风机',
|
|
|
+ '6#楼真空鼓风' => '真空鼓风机',
|
|
|
+ '真空风机' => '真空鼓风机',
|
|
|
+
|
|
|
+ // 废气处理相关
|
|
|
+ '废气处理' => '废气处理',
|
|
|
+ '废气' => '废气处理',
|
|
|
+
|
|
|
+ // 锅炉相关
|
|
|
+ '锅炉' => '锅炉',
|
|
|
+ '热水锅炉' => '热水锅炉',
|
|
|
+
|
|
|
+ // 空压机相关
|
|
|
+ '空压机' => '空压机',
|
|
|
+ '空压' => '空压机',
|
|
|
+
|
|
|
+ // 中央空调相关
|
|
|
+ '中央空调' => '中央空调',
|
|
|
+ '空调' => '中央空调',
|
|
|
+
|
|
|
+ // 待分摊总额
|
|
|
+ '待分摊总额' => '分摊水电',
|
|
|
+ '分摊总额' => '分摊水电',
|
|
|
+ ];
|
|
|
+
|
|
|
// 批次大小
|
|
|
const BATCH_SIZE = 100;
|
|
|
|
|
|
@@ -825,6 +854,10 @@ class UnifiedCostCalculationService
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ // 记录原始科目名称用于调试
|
|
|
+ $originalSubjects = array_unique(array_column($utilityData, '科目名称'));
|
|
|
+ Log::info("原始科目名称: " . implode(', ', $originalSubjects));
|
|
|
+
|
|
|
$machineAllocations = $this->calculateMachineAllocations($utilityData);
|
|
|
|
|
|
if (!empty($machineAllocations)) {
|
|
|
@@ -1114,13 +1147,26 @@ class UnifiedCostCalculationService
|
|
|
*/
|
|
|
protected function simplifySubjectName(string $subject): string
|
|
|
{
|
|
|
- foreach (self::SUBJECT_MAPPING as $keyword => $simple) {
|
|
|
+ // 先尝试完全匹配
|
|
|
+ $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) {
|
|
|
- return $simple;
|
|
|
+ Log::debug("科目名称部分匹配: {$subject} => {$mappedField} (关键词: {$keyword})");
|
|
|
+ return $mappedField;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return $subject;
|
|
|
+ // 如果都不匹配,使用默认映射
|
|
|
+ Log::warning("未识别的科目名称: {$subject}, 将映射到 '分摊水电'");
|
|
|
+ return '分摊水电';
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -1152,9 +1198,13 @@ class UnifiedCostCalculationService
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
+ // 使用简化后的科目名称
|
|
|
+ $simplifiedSubject = $this->simplifySubjectName($subject);
|
|
|
+
|
|
|
$this->allocationFactors[] = [
|
|
|
'Sys_ny' => $month,
|
|
|
- '科目名称' => $subject,
|
|
|
+ '科目名称' => $subject, // 原始科目名称
|
|
|
+ '简化科目名称' => $simplifiedSubject, // 简化后的科目名称
|
|
|
'设备编号' => (string)$machine,
|
|
|
'分摊系数' => 1,
|
|
|
'分摊金额' => floatval($amount),
|
|
|
@@ -1164,7 +1214,6 @@ class UnifiedCostCalculationService
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
/**
|
|
|
* 分配分摊水电
|
|
|
*/
|
|
|
@@ -1180,15 +1229,20 @@ class UnifiedCostCalculationService
|
|
|
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);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
/**
|
|
|
* 计算机台每机时费用
|
|
|
*/
|
|
|
@@ -1218,17 +1272,21 @@ class UnifiedCostCalculationService
|
|
|
*/
|
|
|
protected function getUtilityFieldName(string $subject): string
|
|
|
{
|
|
|
- $map = [
|
|
|
- '待分摊总额' => '分摊水电',
|
|
|
- '废气处理' => '废气处理',
|
|
|
- '锅炉' => '锅炉',
|
|
|
- '空压机' => '空压机',
|
|
|
- '热水锅炉' => '热水锅炉',
|
|
|
- '真空鼓风机' => '真空鼓风机',
|
|
|
- '中央空调' => '中央空调',
|
|
|
- ];
|
|
|
+ // 使用映射表
|
|
|
+ if (isset(self::SUBJECT_TO_FIELD_MAP[$subject])) {
|
|
|
+ return self::SUBJECT_TO_FIELD_MAP[$subject];
|
|
|
+ }
|
|
|
|
|
|
- return $map[$subject] ?? $subject;
|
|
|
+ // 尝试部分匹配
|
|
|
+ foreach (self::SUBJECT_TO_FIELD_MAP as $keyword => $mappedField) {
|
|
|
+ if (strpos($subject, $keyword) !== false) {
|
|
|
+ return $mappedField;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 默认映射到 '分摊水电'
|
|
|
+ Log::warning("未知的科目字段: {$subject}, 映射到 '分摊水电'");
|
|
|
+ return '分摊水电';
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -1268,31 +1326,59 @@ class UnifiedCostCalculationService
|
|
|
$tableName = '成本v23_月度成本明细';
|
|
|
|
|
|
try {
|
|
|
+ // 获取数据库表结构
|
|
|
$columns = Db::query("DESCRIBE `{$tableName}`");
|
|
|
$columnNames = array_column($columns, 'Field');
|
|
|
|
|
|
Log::info("表{$tableName}结构字段数: " . count($columnNames));
|
|
|
+ Log::debug("表字段列表: " . implode(', ', $columnNames));
|
|
|
|
|
|
+ // 检查并修正每行数据
|
|
|
foreach ($this->monthlyCostDetails as $index => &$row) {
|
|
|
// 确保行是数组
|
|
|
$rowArray = is_object($row) ? (array)$row : $row;
|
|
|
|
|
|
- if (count($rowArray) !== count($columnNames)) {
|
|
|
- Log::warning("第" . ($index + 1) . "行字段数不匹配: 数据" . count($rowArray) . "个,表" . count($columnNames) . "个");
|
|
|
- $this->fixRowData($rowArray, $columnNames, $index);
|
|
|
- $row = $rowArray;
|
|
|
- }
|
|
|
-
|
|
|
- // 检查所有数组键名是否为字符串
|
|
|
+ // 1. 移除表中不存在的字段
|
|
|
foreach ($rowArray as $key => $value) {
|
|
|
- if (!is_string($key) && !is_int($key)) {
|
|
|
- Log::error("发现非法键名类型 (行 {$index}, 键类型: " . gettype($key) . ")");
|
|
|
- // 转换为字符串
|
|
|
+ if (!in_array($key, $columnNames)) {
|
|
|
+ Log::warning("移除无效字段 (行 {$index}): {$key}");
|
|
|
unset($rowArray[$key]);
|
|
|
- $rowArray[(string)$key] = $value;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // 2. 确保所有表字段都存在
|
|
|
+ foreach ($columnNames as $column) {
|
|
|
+ if (!array_key_exists($column, $rowArray)) {
|
|
|
+ // 设置默认值
|
|
|
+ if (in_array($column, ['直接水电', '分摊材料', '车间人工', '部门人工附加', '分摊水电',
|
|
|
+ '废气处理', '锅炉', '空压机', '热水锅炉', '真空鼓风机', '中央空调', '分摊其他'])) {
|
|
|
+ $rowArray[$column] = 0;
|
|
|
+ } else {
|
|
|
+ $rowArray[$column] = null;
|
|
|
+ }
|
|
|
+ Log::debug("添加缺失字段 (行 {$index}): {$column}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 确保字段顺序与表一致
|
|
|
+ $orderedRow = [];
|
|
|
+ foreach ($columnNames as $column) {
|
|
|
+ $orderedRow[$column] = $rowArray[$column] ?? null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 修复字段名中的特殊字符
|
|
|
+ $this->sanitizeFieldNames($orderedRow);
|
|
|
+
|
|
|
+ $row = $orderedRow;
|
|
|
+
|
|
|
+ // 记录第一行的字段信息用于调试
|
|
|
+ if ($index === 0) {
|
|
|
+ Log::debug("第一行字段数: " . count($orderedRow) . ", 字段: " . implode(', ', array_keys($orderedRow)));
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ Log::info("数据结构验证完成,总记录数: " . count($this->monthlyCostDetails));
|
|
|
+
|
|
|
} catch (\Exception $e) {
|
|
|
Log::error("验证数据结构时出错: " . $e->getMessage());
|
|
|
}
|
|
|
@@ -1344,31 +1430,62 @@ class UnifiedCostCalculationService
|
|
|
*/
|
|
|
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 $row) {
|
|
|
+ foreach ($batch as $rowIndex => $row) {
|
|
|
$rowValues = [];
|
|
|
foreach ($fields as $field) {
|
|
|
$value = $row[$field] ?? null;
|
|
|
- $rowValues[] = is_numeric($value) ? $value : "'" . addslashes($value) . "'";
|
|
|
+ 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 {
|
|
|
- Db::execute($sql);
|
|
|
- Log::info("成功插入批次 " . (($startIndex / self::BATCH_SIZE) + 1));
|
|
|
+ $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;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
/**
|
|
|
* 插入分摊系数
|
|
|
*/
|
|
|
@@ -1444,4 +1561,21 @@ class UnifiedCostCalculationService
|
|
|
'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;
|
|
|
+ }
|
|
|
}
|