|
@@ -93,10 +93,10 @@ class UnifiedCostCalculationService
|
|
|
*/
|
|
*/
|
|
|
public function calculateAndSaveAll(array $param): array
|
|
public function calculateAndSaveAll(array $param): array
|
|
|
{
|
|
{
|
|
|
- Log::info("开始成本计算,参数: " . json_encode($param));
|
|
|
|
|
-
|
|
|
|
|
Db::startTrans();
|
|
Db::startTrans();
|
|
|
try {
|
|
try {
|
|
|
|
|
+ Log::info("开始成本计算", ['param' => $param, 'timestamp' => date('Y-m-d H:i:s')]);
|
|
|
|
|
+
|
|
|
if (!isset($param['month'])) {
|
|
if (!isset($param['month'])) {
|
|
|
throw new Exception("缺少必要参数: month");
|
|
throw new Exception("缺少必要参数: month");
|
|
|
}
|
|
}
|
|
@@ -104,26 +104,23 @@ class UnifiedCostCalculationService
|
|
|
$month = $param['month'];
|
|
$month = $param['month'];
|
|
|
$sysId = $param['sys_id'] ?? uniqid();
|
|
$sysId = $param['sys_id'] ?? uniqid();
|
|
|
|
|
|
|
|
- Log::info("开始清空旧数据");
|
|
|
|
|
|
|
+ // 重置数据数组,确保是空数组
|
|
|
|
|
+ $this->monthlyCostDetails = [];
|
|
|
|
|
+ $this->allocationFactors = [];
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 清空旧数据
|
|
|
$this->clearOldData($month);
|
|
$this->clearOldData($month);
|
|
|
|
|
|
|
|
- Log::info("开始计算直接人工");
|
|
|
|
|
|
|
+ // 2. 执行五项计算
|
|
|
$this->calculateDirectLabor($param);
|
|
$this->calculateDirectLabor($param);
|
|
|
- Log::info("直接人工计算完成,记录数: " . count($this->monthlyCostDetails));
|
|
|
|
|
|
|
+ Log::info("直接人工计算完成", ['记录数' => is_countable($this->monthlyCostDetails) ? count($this->monthlyCostDetails) : 0]);
|
|
|
|
|
|
|
|
- Log::info("开始计算直接水电");
|
|
|
|
|
$this->calculateDirectUtilities($param);
|
|
$this->calculateDirectUtilities($param);
|
|
|
-
|
|
|
|
|
- Log::info("开始计算间接材料");
|
|
|
|
|
$this->calculateIndirectMaterials($month);
|
|
$this->calculateIndirectMaterials($month);
|
|
|
-
|
|
|
|
|
- Log::info("开始计算间接人工");
|
|
|
|
|
$this->calculateIndirectLabor($month);
|
|
$this->calculateIndirectLabor($month);
|
|
|
-
|
|
|
|
|
- Log::info("开始计算分摊水电");
|
|
|
|
|
$this->calculateApportionedUtilities($param);
|
|
$this->calculateApportionedUtilities($param);
|
|
|
|
|
|
|
|
- Log::info("开始保存数据");
|
|
|
|
|
|
|
+ // 3. 统一插入数据
|
|
|
$this->saveAllData($month, $sysId);
|
|
$this->saveAllData($month, $sysId);
|
|
|
|
|
|
|
|
Db::commit();
|
|
Db::commit();
|
|
@@ -132,37 +129,48 @@ class UnifiedCostCalculationService
|
|
|
|
|
|
|
|
return $this->buildSuccessResponse($month);
|
|
return $this->buildSuccessResponse($month);
|
|
|
|
|
|
|
|
- } catch (\Throwable $t) { // 使用 Throwable 而不是 Exception
|
|
|
|
|
|
|
+ } catch (\Throwable $t) {
|
|
|
Db::rollback();
|
|
Db::rollback();
|
|
|
|
|
|
|
|
- // 记录详细的错误信息
|
|
|
|
|
- $errorInfo = [
|
|
|
|
|
- 'message' => $t->getMessage(),
|
|
|
|
|
- 'code' => $t->getCode(),
|
|
|
|
|
- 'file' => $t->getFile(),
|
|
|
|
|
- 'line' => $t->getLine(),
|
|
|
|
|
- 'trace' => $t->getTraceAsString(),
|
|
|
|
|
- 'param' => $param
|
|
|
|
|
- ];
|
|
|
|
|
-
|
|
|
|
|
- Log::error("统一成本核算失败: " . json_encode($errorInfo, JSON_UNESCAPED_UNICODE));
|
|
|
|
|
|
|
+ // 直接记录错误,不通过 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 [
|
|
return [
|
|
|
'success' => false,
|
|
'success' => false,
|
|
|
'message' => '成本核算失败: ' . $t->getMessage(),
|
|
'message' => '成本核算失败: ' . $t->getMessage(),
|
|
|
'error' => $t->getMessage(),
|
|
'error' => $t->getMessage(),
|
|
|
- 'error_details' => $errorInfo
|
|
|
|
|
|
|
+ 'file' => $t->getFile(),
|
|
|
|
|
+ 'line' => $t->getLine()
|
|
|
];
|
|
];
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 清空旧数据
|
|
* 清空旧数据
|
|
|
*/
|
|
*/
|
|
|
protected function clearOldData(string $month): void
|
|
protected function clearOldData(string $month): void
|
|
|
{
|
|
{
|
|
|
|
|
+ // 确保数据数组是有效的数组
|
|
|
$this->monthlyCostDetails = [];
|
|
$this->monthlyCostDetails = [];
|
|
|
$this->allocationFactors = [];
|
|
$this->allocationFactors = [];
|
|
|
|
|
+
|
|
|
|
|
+ // 记录清空操作
|
|
|
|
|
+ Log::info("清空旧数据", ['month' => $month]);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -1317,69 +1325,66 @@ class UnifiedCostCalculationService
|
|
|
*/
|
|
*/
|
|
|
protected function validateDataStructure(): void
|
|
protected function validateDataStructure(): void
|
|
|
{
|
|
{
|
|
|
- if (empty($this->monthlyCostDetails)) {
|
|
|
|
|
- Log::warning("月度成本明细数据为空");
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (!is_array($this->monthlyCostDetails)) {
|
|
|
|
|
+ Log::error("monthlyCostDetails 不是数组: " . gettype($this->monthlyCostDetails));
|
|
|
|
|
+ $this->monthlyCostDetails = [];
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- $tableName = '成本v23_月度成本明细';
|
|
|
|
|
|
|
+ if (empty($this->monthlyCostDetails)) {
|
|
|
|
|
+ Log::warning("月度成本明细数据为空");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $tableName = '成本v23_月度成本明细';
|
|
|
|
|
|
|
|
- try {
|
|
|
|
|
- // 获取数据库表结构
|
|
|
|
|
$columns = Db::query("DESCRIBE `{$tableName}`");
|
|
$columns = Db::query("DESCRIBE `{$tableName}`");
|
|
|
- $columnNames = array_column($columns, 'Field');
|
|
|
|
|
|
|
+ if (empty($columns)) {
|
|
|
|
|
+ Log::error("无法获取表结构: {$tableName}");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ $columnNames = array_column($columns, 'Field');
|
|
|
Log::info("表{$tableName}结构字段数: " . count($columnNames));
|
|
Log::info("表{$tableName}结构字段数: " . count($columnNames));
|
|
|
- Log::debug("表字段列表: " . implode(', ', $columnNames));
|
|
|
|
|
|
|
|
|
|
- // 检查并修正每行数据
|
|
|
|
|
|
|
+ // 验证并修复每条记录
|
|
|
foreach ($this->monthlyCostDetails as $index => &$row) {
|
|
foreach ($this->monthlyCostDetails as $index => &$row) {
|
|
|
// 确保行是数组
|
|
// 确保行是数组
|
|
|
- $rowArray = is_object($row) ? (array)$row : $row;
|
|
|
|
|
-
|
|
|
|
|
- // 1. 移除表中不存在的字段
|
|
|
|
|
- foreach ($rowArray as $key => $value) {
|
|
|
|
|
- if (!in_array($key, $columnNames)) {
|
|
|
|
|
- Log::warning("移除无效字段 (行 {$index}): {$key}");
|
|
|
|
|
- unset($rowArray[$key]);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!is_array($row)) {
|
|
|
|
|
+ Log::warning("第{$index}行不是数组: " . gettype($row));
|
|
|
|
|
+ $row = [];
|
|
|
|
|
+ continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 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}");
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 检查字段数是否匹配
|
|
|
|
|
+ $rowCount = count($row);
|
|
|
|
|
+ $columnCount = count($columnNames);
|
|
|
|
|
|
|
|
- // 3. 确保字段顺序与表一致
|
|
|
|
|
- $orderedRow = [];
|
|
|
|
|
- foreach ($columnNames as $column) {
|
|
|
|
|
- $orderedRow[$column] = $rowArray[$column] ?? null;
|
|
|
|
|
|
|
+ if ($rowCount !== $columnCount) {
|
|
|
|
|
+ Log::warning("第" . ($index + 1) . "行字段数不匹配: 数据{$rowCount}个,表{$columnCount}个");
|
|
|
|
|
+ $this->fixRowData($row, $columnNames, $index);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 4. 修复字段名中的特殊字符
|
|
|
|
|
- $this->sanitizeFieldNames($orderedRow);
|
|
|
|
|
-
|
|
|
|
|
- $row = $orderedRow;
|
|
|
|
|
-
|
|
|
|
|
- // 记录第一行的字段信息用于调试
|
|
|
|
|
- if ($index === 0) {
|
|
|
|
|
- Log::debug("第一行字段数: " . count($orderedRow) . ", 字段: " . implode(', ', array_keys($orderedRow)));
|
|
|
|
|
|
|
+ // 确保所有键名都是字符串
|
|
|
|
|
+ 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));
|
|
Log::info("数据结构验证完成,总记录数: " . count($this->monthlyCostDetails));
|
|
|
|
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
|
- Log::error("验证数据结构时出错: " . $e->getMessage());
|
|
|
|
|
|
|
+ } catch (\Throwable $t) {
|
|
|
|
|
+ Log::error("验证数据结构时出错: " . $t->getMessage());
|
|
|
|
|
+ // 不抛出异常,继续执行
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1390,14 +1395,32 @@ class UnifiedCostCalculationService
|
|
|
protected function fixRowData(array &$row, array $columnNames, int $index): void
|
|
protected function fixRowData(array &$row, array $columnNames, int $index): void
|
|
|
{
|
|
{
|
|
|
$fixedRow = [];
|
|
$fixedRow = [];
|
|
|
|
|
+
|
|
|
foreach ($columnNames as $column) {
|
|
foreach ($columnNames as $column) {
|
|
|
- $fixedRow[$column] = $row[$column] ?? 0;
|
|
|
|
|
|
|
+ // 确保列名是字符串
|
|
|
|
|
+ $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;
|
|
$this->monthlyCostDetails[$index] = $fixedRow;
|
|
|
Log::info("已修复第" . ($index + 1) . "行数据");
|
|
Log::info("已修复第" . ($index + 1) . "行数据");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 插入所有数据
|
|
* 插入所有数据
|
|
|
*/
|
|
*/
|
|
@@ -1505,11 +1528,31 @@ class UnifiedCostCalculationService
|
|
|
*/
|
|
*/
|
|
|
protected function logSuccess(string $month): void
|
|
protected function logSuccess(string $month): void
|
|
|
{
|
|
{
|
|
|
- Log::info("成本核算完成", [
|
|
|
|
|
- 'month' => $month,
|
|
|
|
|
- '月度成本明细记录数' => count($this->monthlyCostDetails),
|
|
|
|
|
- '分摊系数记录数' => count($this->allocationFactors)
|
|
|
|
|
- ]);
|
|
|
|
|
|
|
+ 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());
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -1517,15 +1560,26 @@ class UnifiedCostCalculationService
|
|
|
*/
|
|
*/
|
|
|
protected function buildSuccessResponse(string $month): array
|
|
protected function buildSuccessResponse(string $month): array
|
|
|
{
|
|
{
|
|
|
- return [
|
|
|
|
|
- 'success' => true,
|
|
|
|
|
- 'message' => '成本核算完成',
|
|
|
|
|
- 'month' => $month,
|
|
|
|
|
- 'stats' => [
|
|
|
|
|
- 'monthly_cost_details' => count($this->monthlyCostDetails),
|
|
|
|
|
- 'allocation_factors' => count($this->allocationFactors)
|
|
|
|
|
- ]
|
|
|
|
|
- ];
|
|
|
|
|
|
|
+ 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()
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -1533,22 +1587,27 @@ class UnifiedCostCalculationService
|
|
|
*/
|
|
*/
|
|
|
protected function logError(\Throwable $t): void
|
|
protected function logError(\Throwable $t): void
|
|
|
{
|
|
{
|
|
|
- $errorDetails = [
|
|
|
|
|
- '时间' => date('Y-m-d H:i:s'),
|
|
|
|
|
- '错误类型' => get_class($t),
|
|
|
|
|
- '错误信息' => $t->getMessage(),
|
|
|
|
|
- '错误代码' => $t->getCode(),
|
|
|
|
|
- '文件' => $t->getFile(),
|
|
|
|
|
- '行号' => $t->getLine(),
|
|
|
|
|
- '堆栈跟踪' => $t->getTraceAsString(),
|
|
|
|
|
- '月度成本明细数' => count($this->monthlyCostDetails),
|
|
|
|
|
- '分摊系数数' => count($this->allocationFactors),
|
|
|
|
|
- ];
|
|
|
|
|
|
|
+ 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("统一成本核算失败详情: " . json_encode($errorDetails, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
|
|
|
|
|
|
+ Log::error("统一成本核算失败详情", $errorDetails);
|
|
|
|
|
+ } catch (\Throwable $logError) {
|
|
|
|
|
+ // 如果日志记录也失败,至少输出到标准错误
|
|
|
|
|
+ error_log("无法记录错误日志: " . $logError->getMessage());
|
|
|
|
|
+ error_log("原始错误: " . $t->getMessage());
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
* 构建错误响应
|
|
* 构建错误响应
|
|
|
*/
|
|
*/
|