pdo ??= JianSunConnection::get(); } // =================================================================== // 汇总统计(轻量:只用 COUNT + SUM,不做复杂 JOIN) // =================================================================== public function summary(array $filters = []): array { $purchase = $this->purchaseSummary($filters); $sale = $this->saleSummary($filters); return [ 'totalAmount' => round($purchase['totalAmount'] + $sale['totalAmount'], 2), 'totalGuessAmount' => round($purchase['totalGuessAmount'] + $sale['totalGuessAmount'], 2), 'sentCount' => $purchase['sentCount'] + $sale['sentCount'], 'unsentCount' => $purchase['unsentCount'] + $sale['unsentCount'], 'totalCount' => $purchase['totalCount'] + $sale['totalCount'], ]; } private function purchaseSummary(array $filters): array { $where = $this->buildPurchaseWhere($filters); $params = $this->buildPurchaseParams($filters); // 按需 JOIN,减少不必要的全表扫描 $joinA = $joinU = ''; if (strpos($where, 'a.') !== false) { $joinA = ' LEFT JOIN pm_averageUnitPrice a ON s.procode = a.number AND s.scmname = a.supplier'; } if (strpos($where, 'u.') !== false) { $joinU = ' LEFT JOIN pm_unOrderDetails u ON s.spo = u.purchase AND s.skuoid = u.sku'; } $sql = "SELECT COUNT(*) as totalCount, ISNULL(SUM(s.settlementPrice), 0) as totalAmount, 0 as totalGuessAmount, COUNT(*) as sentCount, 0 as unsentCount FROM fys_supplierGoods_sttlement_purchase s {$joinA}{$joinU} WHERE 1=1 {$where}"; $stmt = $this->db()->prepare($sql); $stmt->execute($params); $row = $stmt->fetch(); return $row ?: ['totalCount' => 0, 'totalAmount' => 0, 'totalGuessAmount' => 0, 'sentCount' => 0, 'unsentCount' => 0]; } private function saleSummary(array $filters): array { $where = $this->buildSaleWhere($filters); $params = $this->buildSaleParams($filters); $joinA = ''; if (strpos($where, 'a.') !== false) { $joinA = ' LEFT JOIN pm_averageUnitPrice a ON s.procode = a.number AND s.supplier = a.supplier'; } $sql = "SELECT COUNT(*) as totalCount, ISNULL(SUM(s.settlementPrice), 0) as totalAmount, 0 as totalGuessAmount, ISNULL(SUM(CASE WHEN s.expressNo IS NOT NULL AND s.expressNo != '' THEN 1 ELSE 0 END), 0) as sentCount, ISNULL(SUM(CASE WHEN s.expressNo IS NULL OR s.expressNo = '' THEN 1 ELSE 0 END), 0) as unsentCount FROM fys_supplierGoods_sttlement_sale s {$joinA} WHERE 1=1 {$where}"; $stmt = $this->db()->prepare($sql); $stmt->execute($params); $row = $stmt->fetch(); return $row ?: ['totalCount' => 0, 'totalAmount' => 0, 'totalGuessAmount' => 0, 'sentCount' => 0, 'unsentCount' => 0]; } // =================================================================== // 列表查询(核心 — 分别查入库/代发再合并) // =================================================================== public function list(array $filters = [], int $page = 1, int $perPage = 25): array { $type = $filters['record_type'] ?? 'all'; // 分别查询(同一分页参数,各自内部分页) $purchaseResult = null; $saleResult = null; $totalPurchase = 0; $totalSale = 0; if ($type === 'all' || $type === 'purchase') { $where = $this->buildPurchaseWhere($filters); $params = $this->buildPurchaseParams($filters); $totalPurchase = $this->countPurchase($where, $params); $purchaseResult = $this->fetchPurchase($where, $params, $page, $perPage); } if ($type === 'all' || $type === 'sale') { $where = $this->buildSaleWhere($filters); $params = $this->buildSaleParams($filters); $totalSale = $this->countSale($where, $params); $saleResult = $this->fetchSale($where, $params, $page, $perPage); } // 合并并按 createTime 排序,取前 perPage 条 $merged = array_merge($purchaseResult ?? [], $saleResult ?? []); // 按 createTime DESC 排序 usort($merged, function ($a, $b) { return strcmp($b['createTime'] ?? '', $a['createTime'] ?? ''); }); // 截取当前页 $merged = array_slice($merged, 0, $perPage); // 格式化金额 foreach ($merged as &$row) { $row['amount'] = round((float) $row['amount'], 2); $row['guessAmount'] = round((float) $row['guessAmount'], 2); $row['unitPrice'] = round((float) $row['unitPrice'], 2); $row['guessUnitPrice'] = round((float) ($row['guessUnitPrice'] ?? 0), 2); } return [ 'data' => $merged, 'total' => $totalPurchase + $totalSale, ]; } // =================================================================== // 筛选器选项 // =================================================================== public function filterOptions(): array { $sql = "SELECT DISTINCT scmname FROM fys_supplierGoods_sttlement_purchase WHERE scmname IS NOT NULL AND scmname != '' UNION SELECT DISTINCT supplier FROM fys_supplierGoods_sttlement_sale WHERE supplier IS NOT NULL AND supplier != '' ORDER BY scmname"; $stmt = $this->db()->query($sql); $suppliers = $stmt->fetchAll(PDO::FETCH_COLUMN); $sql = "SELECT DISTINCT whname FROM fys_supplierGoods_sttlement_purchase WHERE whname IS NOT NULL AND whname != '' UNION SELECT DISTINCT whName FROM fys_supplierGoods_sttlement_sale WHERE whName IS NOT NULL AND whName != '' ORDER BY whname"; $stmt = $this->db()->query($sql); $warehouses = $stmt->fetchAll(PDO::FETCH_COLUMN); return [ 'suppliers' => $suppliers, 'warehouses' => $warehouses, ]; } // =================================================================== // 入库查询 // =================================================================== private function countPurchase(string $where, array $params): int { // 仅当筛选条件引用关联表字段时才 JOIN,否则纯主表 COUNT $joins = ''; if (strpos($where, 'p.') !== false) { $joins .= ' LEFT JOIN pm_purchaseIn p ON s.spo = p.sourceno'; } if (strpos($where, 'a.') !== false) { $joins .= ' LEFT JOIN pm_averageUnitPrice a ON s.procode = a.number AND s.scmname = a.supplier'; } if (strpos($where, 'u.') !== false) { $joins .= ' LEFT JOIN pm_unOrderDetails u ON s.spo = u.purchase AND s.skuoid = u.sku'; } $sql = "SELECT COUNT(*) as cnt FROM fys_supplierGoods_sttlement_purchase s{$joins} WHERE 1=1 {$where}"; $stmt = $this->db()->prepare($sql); $stmt->execute($params); return (int) $stmt->fetch()['cnt']; } private function fetchPurchase(string $where, array $params, int $page, int $perPage): array { $offset = ($page - 1) * $perPage; $sql = "SELECT 'purchase' AS recordType, ISNULL(p.hno, '') AS stockNo, ISNULL(s.spo, '') AS purchaseNo, FORMAT(ISNULL(s.created, s.createTime), 'yyyy-MM-dd HH:mm:ss') AS sendTime, ISNULL(s.scmname, '') AS supplier, ISNULL(s.skuoid, '') AS skuCode, ISNULL(s.procode, '') AS productCode, ISNULL(s.skuname, '') AS skuName, ISNULL(s.settlementStock, 0) AS qty, ISNULL(s.singlePrice, 0) AS unitPrice, ISNULL(s.settlementPrice, 0) AS amount, ISNULL(a.price, 0) AS guessUnitPrice, ISNULL(s.settlementStock * ISNULL(a.price, 0), 0) AS guessAmount, '' AS supplierGoodsCode, ISNULL(s.remark, '') AS remark, ISNULL(s.whname, '') AS warehouse, FORMAT(s.createTime, 'yyyy-MM-dd HH:mm:ss') AS createTime, '' AS expressName, '' AS expressNo, CASE WHEN u.Id IS NOT NULL THEN 1 ELSE 0 END AS isUnsent, s.id AS sourceId FROM fys_supplierGoods_sttlement_purchase s LEFT JOIN pm_purchaseIn p ON s.spo = p.sourceno LEFT JOIN pm_averageUnitPrice a ON s.procode = a.number AND s.scmname = a.supplier LEFT JOIN pm_unOrderDetails u ON s.spo = u.purchase AND s.skuoid = u.sku WHERE 1=1 {$where} ORDER BY s.createTime DESC OFFSET {$offset} ROWS FETCH NEXT {$perPage} ROWS ONLY"; $stmt = $this->db()->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(); } // =================================================================== // 代发查询 // =================================================================== private function countSale(string $where, array $params): int { $sql = "SELECT COUNT(*) as cnt FROM fys_supplierGoods_sttlement_sale s LEFT JOIN pm_averageUnitPrice a ON s.procode = a.number AND s.supplier = a.supplier WHERE 1=1 {$where}"; $stmt = $this->db()->prepare($sql); $stmt->execute($params); return (int) $stmt->fetch()['cnt']; } private function fetchSale(string $where, array $params, int $page, int $perPage): array { $offset = ($page - 1) * $perPage; $sql = "SELECT 'sale' AS recordType, '' AS stockNo, '' AS purchaseNo, FORMAT(s.sendTime, 'yyyy-MM-dd HH:mm:ss') AS sendTime, ISNULL(s.supplier, '') AS supplier, ISNULL(s.skucode, '') AS skuCode, ISNULL(s.procode, '') AS productCode, ISNULL(s.skuname, '') AS skuName, ISNULL(s.settlementStock, 0) AS qty, ISNULL(s.singlePrice, 0) AS unitPrice, ISNULL(s.settlementPrice, 0) AS amount, ISNULL(a.price, 0) AS guessUnitPrice, ISNULL(s.settlementStock * ISNULL(a.price, 0), 0) AS guessAmount, '' AS supplierGoodsCode, ISNULL(s.remark, '') AS remark, ISNULL(s.whName, '') AS warehouse, FORMAT(s.createTime, 'yyyy-MM-dd HH:mm:ss') AS createTime, ISNULL(s.expressName, '') AS expressName, ISNULL(s.expressNo, '') AS expressNo, CASE WHEN s.expressNo IS NULL OR s.expressNo = '' THEN 1 ELSE 0 END AS isUnsent, s.Id AS sourceId FROM fys_supplierGoods_sttlement_sale s LEFT JOIN pm_averageUnitPrice a ON s.procode = a.number AND s.supplier = a.supplier WHERE 1=1 {$where} ORDER BY s.createTime DESC OFFSET {$offset} ROWS FETCH NEXT {$perPage} ROWS ONLY"; $stmt = $this->db()->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(); } // =================================================================== // WHERE 与参数构建 // =================================================================== private function like(string $v): string { return '%' . $v . '%'; } // --- 入库 WHERE --- private function buildPurchaseWhere(array $filters): string { $c = []; if (!empty($filters['supplier'])) $c[] = 'AND s.scmname LIKE :supplier'; if (!empty($filters['stock_no'])) $c[] = 'AND p.hno LIKE :stock_no'; if (!empty($filters['purchase_no'])) $c[] = 'AND s.spo LIKE :purchase_no'; if (!empty($filters['product_code'])) $c[] = 'AND s.procode LIKE :product_code'; if (!empty($filters['warehouse'])) $c[] = 'AND s.whname LIKE :warehouse'; if (!empty($filters['send_time_from'])) $c[] = 'AND s.created >= :send_time_from'; if (!empty($filters['send_time_to'])) $c[] = 'AND s.created <= :send_time_to'; if (!empty($filters['create_time_from'])) $c[] = 'AND s.createTime >= :create_time_from'; if (!empty($filters['create_time_to'])) $c[] = 'AND s.createTime <= :create_time_to'; if (!empty($filters['sent_status'])) { if ($filters['sent_status'] === 'sent') $c[] = 'AND u.Id IS NULL'; if ($filters['sent_status'] === 'unsent') $c[] = 'AND u.Id IS NOT NULL'; } if (!empty($filters['has_diff'])) { $c[] = 'AND ABS(ISNULL(s.settlementPrice, 0) - ISNULL(s.settlementStock * ISNULL(a.price, 0), 0)) > 0.01'; } return $c ? ' ' . implode(' ', $c) : ''; } private function buildPurchaseParams(array $filters): array { $p = []; if (!empty($filters['supplier'])) $p[':supplier'] = $this->like($filters['supplier']); if (!empty($filters['stock_no'])) $p[':stock_no'] = $this->like($filters['stock_no']); if (!empty($filters['purchase_no'])) $p[':purchase_no'] = $this->like($filters['purchase_no']); if (!empty($filters['product_code'])) $p[':product_code'] = $this->like($filters['product_code']); if (!empty($filters['warehouse'])) $p[':warehouse'] = $this->like($filters['warehouse']); if (!empty($filters['send_time_from'])) $p[':send_time_from'] = $filters['send_time_from']; if (!empty($filters['send_time_to'])) $p[':send_time_to'] = $filters['send_time_to']; if (!empty($filters['create_time_from'])) $p[':create_time_from'] = $filters['create_time_from']; if (!empty($filters['create_time_to'])) $p[':create_time_to'] = $filters['create_time_to']; return $p; } // --- 代发 WHERE --- private function buildSaleWhere(array $filters): string { $c = []; if (!empty($filters['supplier'])) $c[] = 'AND s.supplier LIKE :supplier_s'; if (!empty($filters['product_code'])) $c[] = 'AND s.procode LIKE :product_code_s'; if (!empty($filters['express_no'])) $c[] = 'AND s.expressNo LIKE :express_no'; if (!empty($filters['warehouse'])) $c[] = 'AND s.whName LIKE :warehouse_s'; if (!empty($filters['send_time_from'])) $c[] = 'AND s.sendTime >= :send_time_from_s'; if (!empty($filters['send_time_to'])) $c[] = 'AND s.sendTime <= :send_time_to_s'; if (!empty($filters['create_time_from'])) $c[] = 'AND s.createTime >= :create_time_from_s'; if (!empty($filters['create_time_to'])) $c[] = 'AND s.createTime <= :create_time_to_s'; if (!empty($filters['sent_status'])) { if ($filters['sent_status'] === 'sent') $c[] = "AND s.expressNo IS NOT NULL AND s.expressNo != ''"; if ($filters['sent_status'] === 'unsent') $c[] = "AND (s.expressNo IS NULL OR s.expressNo = '')"; } if (!empty($filters['has_diff'])) { $c[] = 'AND ABS(ISNULL(s.settlementPrice, 0) - ISNULL(s.settlementStock * ISNULL(a.price, 0), 0)) > 0.01'; } return $c ? ' ' . implode(' ', $c) : ''; } private function buildSaleParams(array $filters): array { $p = []; if (!empty($filters['supplier'])) $p[':supplier_s'] = $this->like($filters['supplier']); if (!empty($filters['product_code'])) $p[':product_code_s'] = $this->like($filters['product_code']); if (!empty($filters['express_no'])) $p[':express_no'] = $this->like($filters['express_no']); if (!empty($filters['warehouse'])) $p[':warehouse_s'] = $this->like($filters['warehouse']); if (!empty($filters['send_time_from'])) $p[':send_time_from_s'] = $filters['send_time_from']; if (!empty($filters['send_time_to'])) $p[':send_time_to_s'] = $filters['send_time_to']; if (!empty($filters['create_time_from'])) $p[':create_time_from_s'] = $filters['create_time_from']; if (!empty($filters['create_time_to'])) $p[':create_time_to_s'] = $filters['create_time_to']; return $p; } }