null, 'checked_at' => 0 ]; private static $healthCheckInterval = 60; private static function isMssqlAvailable() { $now = time(); if ($now - self::$healthCache['checked_at'] < self::$healthCheckInterval) { return self::$healthCache['available']; } try { Db::connect('mssql')->query('SELECT 1'); self::$healthCache['available'] = true; self::$healthCache['checked_at'] = $now; Log::info('SQL Server 连接恢复正常'); } catch (\Exception $e) { self::$healthCache['available'] = false; self::$healthCache['checked_at'] = $now; Log::warning("SQL Server 不可用(下次探测 " . self::$healthCheckInterval . "s 后): " . $e->getMessage()); } return self::$healthCache['available']; } public static function queryChuku($startDate, $endDate, $page, $limit, $isolationSql, $isolationParams) { if (!self::isMssqlAvailable()) { throw new \Exception('SQL Server 连接不可用'); } return self::queryChukuReal($startDate, $endDate, $page, $limit, $isolationSql, $isolationParams); } private static function queryChukuReal($startDate, $endDate, $page, $limit, $isolationSql, $isolationParams) { $dateCondition = ''; $dateParams = []; if ($startDate) { $dateCondition .= " AND x.[日期] >= :start_date"; $dateParams['start_date'] = $startDate; } if ($endDate) { $dateCondition .= " AND x.[日期] <= :end_date"; $dateParams['end_date'] = $endDate; } $startRow = ($page - 1) * $limit + 1; $endRow = $page * $limit; $countParams = array_merge($dateParams, $isolationParams); $dataParams = array_merge($countParams, [ 'start_row' => $startRow, 'end_row' => $endRow ]); $sqlResult = self::buildLogSql(self::getChukuBaseSql(), $dateCondition, $isolationSql, $dataParams); $dataSql = $sqlResult['dataSql']; $logSql = $sqlResult['logSql']; $countSql = self::getChukuCountSql(); $countSql = str_replace(['{date_condition}', '{isolation_condition}'], [$dateCondition, $isolationSql], $countSql); Log::debug("出库查询 - 统计SQL: " . $countSql); Log::debug("出库查询 - 统计参数: " . json_encode($countParams)); Log::debug("出库查询 - 统计参数数量: " . count($countParams)); try { $total = Db::connect('mssql')->query($countSql, $countParams); $total = isset($total[0]['total']) ? (int)$total[0]['total'] : 0; Log::debug("出库查询 - 统计成功,总数: " . $total); } catch (\Exception $e) { Log::error("出库查询 - 统计失败: " . $e->getMessage()); Log::error("出库查询 - 统计SQL(原始): " . $countSql); Log::error("出库查询 - 统计参数(原始): " . json_encode($countParams)); throw $e; } Log::debug("出库查询 - 数据SQL: " . $dataSql); Log::debug("出库查询 - 数据参数: " . json_encode($dataParams)); Log::debug("出库查询 - 数据参数数量: " . count($dataParams)); try { $rows = Db::connect('mssql')->query($dataSql, $dataParams); Log::debug("出库查询 - 数据查询成功,行数: " . count($rows)); } catch (\Exception $e) { Log::error("出库查询 - 数据查询失败: " . $e->getMessage()); Log::error("出库查询 - 数据SQL(原始): " . $dataSql); Log::error("出库查询 - 数据参数(原始): " . json_encode($dataParams)); throw $e; } $records = []; foreach ($rows as $row) { $records[] = self::convertChukuRow($row); } return [ 'records' => $records, 'total' => $total, 'sql' => $logSql ]; } public static function queryRuku($startDate, $endDate, $page, $limit, $isolationSql, $isolationParams) { if (!self::isMssqlAvailable()) { throw new \Exception('SQL Server 连接不可用'); } return self::queryRukuReal($startDate, $endDate, $page, $limit, $isolationSql, $isolationParams); } private static function queryRukuReal($startDate, $endDate, $page, $limit, $isolationSql, $isolationParams) { $dateCondition = ''; $dateParams = []; if ($startDate) { $dateCondition .= " AND x.[日期] >= :start_date"; $dateParams['start_date'] = $startDate; } if ($endDate) { $dateCondition .= " AND x.[日期] <= :end_date"; $dateParams['end_date'] = $endDate; } $startRow = ($page - 1) * $limit + 1; $endRow = $page * $limit; $countParams = array_merge($dateParams, $isolationParams); $dataParams = array_merge($countParams, [ 'start_row' => $startRow, 'end_row' => $endRow ]); $sqlResult = self::buildLogSql(self::getRukuBaseSql(), $dateCondition, $isolationSql, $dataParams); $dataSql = $sqlResult['dataSql']; $logSql = $sqlResult['logSql']; $countSql = self::getRukuCountSql(); $countSql = str_replace(['{date_condition}', '{isolation_condition}'], [$dateCondition, $isolationSql], $countSql); Log::debug("入库查询 - 统计SQL: " . $countSql); Log::debug("入库查询 - 统计参数: " . json_encode($countParams)); $total = Db::connect('mssql')->query($countSql, $countParams); $total = isset($total[0]['total']) ? (int)$total[0]['total'] : 0; Log::debug("入库查询 - 数据SQL: " . $dataSql); Log::debug("入库查询 - 数据参数: " . json_encode($dataParams)); $rows = Db::connect('mssql')->query($dataSql, $dataParams); $records = []; foreach ($rows as $row) { $records[] = self::convertRukuRow($row); } return [ 'records' => $records, 'total' => $total, 'sql' => $logSql ]; } private static function buildLogSql($sqlTemplate, $dateCondition, $isolationSql, $allParams) { $dataSql = str_replace(['{date_condition}', '{isolation_condition}'], [$dateCondition, $isolationSql], $sqlTemplate); $logSql = $dataSql; foreach ($allParams as $key => $value) { $placeholder = ':' . $key; if (is_string($value)) { $logSql = str_replace($placeholder, "'" . $value . "'", $logSql); } else { $logSql = str_replace($placeholder, (string)$value, $logSql); } } return ['dataSql' => $dataSql, 'logSql' => $logSql]; } private static function convertChukuRow($row) { return [ 'chuku_id' => $row['chuku_id'] ?? '', 'ruku_id' => $row['ruku_id'] ?? '', 'ticket_no' => $row['ticket_no'] ?? '', 'date' => self::convertDate($row['date'] ?? null), 'warehouse' => $row['warehouse'] ?? '', 'batch_no' => $row['batch_no'] ?? '', 'expire_date' => self::convertDate($row['expire_date'] ?? null), 'quantity' => self::convertFloat($row['quantity'] ?? null), 'unit_price' => self::convertFloat($row['unit_price'] ?? null), 'amount' => self::convertFloat($row['amount'] ?? null), 'purchase_price' => self::convertFloat($row['purchase_price'] ?? null), 'profit' => self::convertFloat($row['profit'] ?? null), 'operator' => $row['operator'] ?? '', 'salesman' => $row['salesman'] ?? '', 'receiver' => $row['receiver'] ?? '', 'summary' => $row['summary'] ?? '', 'supplier_id' => $row['supplier_id'] ?? '', 'supplier_name' => $row['supplier_name'] ?? '', 'supplier_short_name' => $row['supplier_short_name'] ?? '', 'customer_id' => $row['customer_id'] ?? '', 'customer_name' => $row['customer_name'] ?? '', 'customer_short_name' => $row['customer_short_name'] ?? '', 'customer_region' => $row['customer_region'] ?? '', 'product_id' => $row['product_id'] ?? '', 'product_name' => $row['product_name'] ?? '', 'product_spec' => $row['product_spec'] ?? '', 'product_origin' => $row['product_origin'] ?? '', 'product_unit' => $row['product_unit'] ?? '', 'manufacturer' => $row['manufacturer'] ?? '', ]; } private static function convertRukuRow($row) { return [ 'ruku_id' => $row['ruku_id'] ?? '', 'ticket_no' => $row['ticket_no'] ?? '', 'date' => self::convertDate($row['date'] ?? null), 'warehouse' => $row['warehouse'] ?? '', 'batch_no' => $row['batch_no'] ?? '', 'expire_date' => self::convertDate($row['expire_date'] ?? null), 'quantity' => self::convertFloat($row['quantity'] ?? null), 'unit_price' => self::convertFloat($row['unit_price'] ?? null), 'amount' => self::convertFloat($row['amount'] ?? null), 'purchase_price' => self::convertFloat($row['purchase_price'] ?? null), 'warehouse_qty' => self::convertFloat($row['warehouse_qty'] ?? null), 'operator' => $row['operator'] ?? '', 'purchaser' => $row['purchaser'] ?? '', 'responsible_person' => $row['responsible_person'] ?? '', 'arrival_no' => $row['arrival_no'] ?? '', 'invoice_no' => $row['invoice_no'] ?? '', 'summary' => $row['summary'] ?? '', 'supplier_id' => $row['supplier_id'] ?? '', 'supplier_name' => $row['supplier_name'] ?? '', 'supplier_short_name' => $row['supplier_short_name'] ?? '', 'product_id' => $row['product_id'] ?? '', 'product_name' => $row['product_name'] ?? '', 'product_spec' => $row['product_spec'] ?? '', 'product_origin' => $row['product_origin'] ?? '', 'product_unit' => $row['product_unit'] ?? '', 'manufacturer' => $row['manufacturer'] ?? '', ]; } private static function convertDate($value) { if ($value === null) { return null; } if (is_numeric($value)) { try { $base = new \DateTime('1899-12-30'); $base->add(new \DateInterval('P' . (int)$value . 'D')); return $base->format('Y-m-d'); } catch (\Exception $e) { return (string)$value; } } if ($value instanceof \DateTime) { return $value->format('Y-m-d'); } return (string)$value; } private static function convertFloat($value) { if ($value === null) { return null; } return is_numeric($value) ? (float)$value : null; } private static function getChukuBaseSql() { return " WITH PageData AS ( SELECT ROW_NUMBER() OVER (ORDER BY x.[日期] DESC) AS RowID, x.[出库序号] AS chuku_id, x.[入库序号] AS ruku_id, x.[票号] AS ticket_no, x.[日期] AS date, x.[库房] AS warehouse, x.[批号] AS batch_no, x.[效期] AS expire_date, x.[数量] AS quantity, x.[单价] AS unit_price, x.[金额] AS amount, x.[进价] AS purchase_price, x.[毛利] AS profit, x.[操作员] AS operator, x.[业务员] AS salesman, x.[提货人] AS receiver, x.[摘要] AS summary, x.[供方序号] AS supplier_id, k.[供方全称] AS supplier_name, k.[简称] AS supplier_short_name, x.[销方序号] AS customer_id, f.[销方全称] AS customer_name, f.[简称] AS customer_short_name, f.[地区] AS customer_region, x.[产品序号] AS product_id, p.[产品名称] AS product_name, p.[规格] AS product_spec, p.[产地] AS product_origin, p.[单位] AS product_unit, p.[生产厂家] AS manufacturer FROM [ywsj].[dbo].[vckmx] x JOIN [ywsj].[dbo].[bgfkh] k ON x.[供方序号] = k.[供方序号] JOIN [ywsj].[dbo].[bcp] p ON x.[产品序号] = p.[产品序号] JOIN [ywsj].[dbo].[bxfkh] f ON x.[销方序号] = f.[销方序号] WHERE 1=1 {date_condition} {isolation_condition} ) SELECT chuku_id, ruku_id, ticket_no, date, warehouse, batch_no, expire_date, quantity, unit_price, amount, purchase_price, profit, operator, salesman, receiver, summary, supplier_id, supplier_name, supplier_short_name, customer_id, customer_name, customer_short_name, customer_region, product_id, product_name, product_spec, product_origin, product_unit, manufacturer FROM PageData WHERE RowID BETWEEN :start_row AND :end_row ORDER BY RowID "; } private static function getChukuCountSql() { return " SELECT COUNT(*) AS total FROM [ywsj].[dbo].[vckmx] x JOIN [ywsj].[dbo].[bgfkh] k ON x.[供方序号] = k.[供方序号] JOIN [ywsj].[dbo].[bcp] p ON x.[产品序号] = p.[产品序号] JOIN [ywsj].[dbo].[bxfkh] f ON x.[销方序号] = f.[销方序号] WHERE 1=1 {date_condition} {isolation_condition} "; } private static function getRukuBaseSql() { return " WITH PageData AS ( SELECT ROW_NUMBER() OVER (ORDER BY x.[日期] DESC) AS RowID, x.[入库序号] AS ruku_id, x.[票号] AS ticket_no, x.[日期] AS date, x.[库房] AS warehouse, x.[批号] AS batch_no, x.[效期] AS expire_date, x.[数量] AS quantity, x.[单价] AS unit_price, x.[金额] AS amount, x.[进价] AS purchase_price, x.[库房数量] AS warehouse_qty, x.[操作员] AS operator, x.[采购员] AS purchaser, x.[负责人] AS responsible_person, x.[来货单号] AS arrival_no, x.[发票单号] AS invoice_no, x.[摘要] AS summary, x.[供方序号] AS supplier_id, k.[供方全称] AS supplier_name, k.[简称] AS supplier_short_name, x.[产品序号] AS product_id, p.[产品名称] AS product_name, p.[规格] AS product_spec, p.[产地] AS product_origin, p.[单位] AS product_unit, p.[生产厂家] AS manufacturer FROM [ywsj].[dbo].[vrkmx] x JOIN [ywsj].[dbo].[bgfkh] k ON x.[供方序号] = k.[供方序号] JOIN [ywsj].[dbo].[bcp] p ON x.[产品序号] = p.[产品序号] WHERE 1=1 {date_condition} {isolation_condition} ) SELECT ruku_id, ticket_no, date, warehouse, batch_no, expire_date, quantity, unit_price, amount, purchase_price, warehouse_qty, operator, purchaser, responsible_person, arrival_no, invoice_no, summary, supplier_id, supplier_name, supplier_short_name, product_id, product_name, product_spec, product_origin, product_unit, manufacturer FROM PageData WHERE RowID BETWEEN :start_row AND :end_row ORDER BY RowID "; } private static function getRukuCountSql() { return " SELECT COUNT(*) AS total FROM [ywsj].[dbo].[vrkmx] x JOIN [ywsj].[dbo].[bgfkh] k ON x.[供方序号] = k.[供方序号] JOIN [ywsj].[dbo].[bcp] p ON x.[产品序号] = p.[产品序号] WHERE 1=1 {date_condition} {isolation_condition} "; } }