array, 'total' => int] */ class SupplierPayableRepository { private PDO $db; public function __construct() { $this->db = JianSunConnection::get(); } /** * 通用工具:带筛选 + 分页的查询 */ private function runQuery(string $sql, array $params, int $page, int $pageSize, int $countThreshold = 5000): array { $offset = ($page - 1) * $pageSize; // 1. 总数(先查总数,超阈值再用近似) $countSql = 'SELECT COUNT(*) AS cnt FROM (' . $sql . ') AS sub'; $total = 0; try { $stmt = $this->db->prepare($countSql); $stmt->execute($params); $total = (int) $stmt->fetchColumn(); } catch (\Throwable $e) { // 极端情况:表太大查不到总数时退回到 -1 $total = -1; } // 2. 分页数据(用 TOP + OFFSET/FETCH 不可行,统一使用 ROW_NUMBER() OVER 包装) $pagedSql = 'SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS rn FROM (' . $sql . ') AS src) AS t WHERE rn BETWEEN ? AND ?'; $paramsWithRange = array_merge($params, [$offset + 1, $offset + $pageSize]); $stmt = $this->db->prepare($pagedSql); $stmt->execute($paramsWithRange); return [ 'data' => $stmt->fetchAll(), 'total' => $total, ]; } /** * 计算分页的 SELECT TOP N(无 COUNT),加速大表查询 */ private function runQueryFast(string $sql, array $params, int $page, int $pageSize, int $total = -1): array { $offset = ($page - 1) * $pageSize; $pagedSql = 'SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS rn FROM (' . $sql . ') AS src) AS t WHERE rn BETWEEN ? AND ?'; $paramsWithRange = array_merge($params, [$offset + 1, $offset + $pageSize]); $stmt = $this->db->prepare($pagedSql); $stmt->execute($paramsWithRange); return [ 'data' => $stmt->fetchAll(), 'total' => $total, ]; } // ============================================================ // 1. 退货对账 — finance_purchaseReturn (36791行) // ============================================================ public function returnReconciliations(array $filters, int $page, int $pageSize): array { $where = []; $params = []; if (!empty($filters['supplier'])) { $where[] = 'supplierName LIKE ?'; $params[] = '%' . $filters['supplier'] . '%'; } if (!empty($filters['warehouse'])) { $where[] = 'warehouseName LIKE ?'; $params[] = '%' . $filters['warehouse'] . '%'; } if (!empty($filters['sku_id'])) { $where[] = 'sku_id LIKE ?'; $params[] = '%' . $filters['sku_id'] . '%'; } if (!empty($filters['express_no'])) { $where[] = 'expressNo LIKE ?'; $params[] = '%' . $filters['express_no'] . '%'; } if (!empty($filters['date_from'])) { $where[] = 'date >= ?'; $params[] = $filters['date_from']; } if (!empty($filters['date_to'])) { $where[] = 'date <= ?'; $params[] = $filters['date_to']; } $whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; $sql = "SELECT CONVERT(varchar(10), date, 120) AS send_time, supplierName AS supplier, sku_id AS barcode, name AS sku_name, CAST(qty AS decimal(18,2)) AS qty, CAST(price AS decimal(18,2)) AS unit_price, CAST(price * qty AS decimal(18,2)) AS amount, CAST(priceB AS decimal(18,2)) AS guess_unit_price, CAST(priceB * qty AS decimal(18,2)) AS guess_amount, remark, io_id AS wms_pick_no, warehouseName AS warehouse, expressNo AS express_no, expressName AS express_name, CONVERT(varchar(10), io_date, 120) AS io_date, labels, creator_name AS creator, '' AS title, '' AS supplier_barcode, '' AS original_barcode, '' AS reconciler FROM finance_purchaseReturn $whereSql"; $result = $this->runQuery($sql, $params, $page, $pageSize); return [ 'data' => $result['data'], 'total' => $result['total'], ]; } public function returnReconciliationSummary(array $filters): array { $where = []; $params = []; if (!empty($filters['supplier'])) { $where[] = 'supplierName LIKE ?'; $params[] = '%' . $filters['supplier'] . '%'; } if (!empty($filters['date_from'])) { $where[] = 'date >= ?'; $params[] = $filters['date_from']; } if (!empty($filters['date_to'])) { $where[] = 'date <= ?'; $params[] = $filters['date_to']; } $whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; $sql = "SELECT ISNULL(SUM(price * qty), 0) AS total_amount, ISNULL(SUM(priceB * qty), 0) AS total_guess_amount, COUNT(*) AS total_count FROM finance_purchaseReturn $whereSql"; $stmt = $this->db->prepare($sql); $stmt->execute($params); $row = $stmt->fetch(); return [ 'totalAmount' => (float) $row['total_amount'], 'totalGuessAmount' => (float) $row['total_guess_amount'], 'totalCount' => (int) $row['total_count'], ]; } // ============================================================ // 2. 发票扣款 — finance_invoiceDeduction (195行) // ============================================================ public function invoiceDeductions(array $filters, int $page, int $pageSize): array { $where = []; $params = []; if (!empty($filters['supplier'])) { $where[] = 'supplier LIKE ?'; $params[] = '%' . $filters['supplier'] . '%'; } if (!empty($filters['month_from'])) { $where[] = 'month >= ?'; $params[] = $filters['month_from']; } if (!empty($filters['month_to'])) { $where[] = 'month <= ?'; $params[] = $filters['month_to']; } $whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; $sql = "SELECT CONVERT(varchar(7), month, 120) AS month_str, '' AS category, supplier AS payee, supplier AS receiver, '' AS board_factory, CAST(0 AS decimal(18,2)) AS subsidy_fee, remark, CAST(price AS decimal(18,2)) AS price, remark AS detail, '' AS note, personCharge AS person_charge, CONVERT(varchar(19), createTime, 120) AS create_time, creator FROM finance_invoiceDeduction $whereSql"; return $this->runQuery($sql, $params, $page, $pageSize); } public function invoiceDeductionSummary(array $filters): array { $where = []; $params = []; if (!empty($filters['supplier'])) { $where[] = 'supplier LIKE ?'; $params[] = '%' . $filters['supplier'] . '%'; } $whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; $sql = "SELECT ISNULL(SUM(price), 0) AS total_price, COUNT(*) AS total_count FROM finance_invoiceDeduction $whereSql"; $stmt = $this->db->prepare($sql); $stmt->execute($params); $row = $stmt->fetch(); return [ 'totalPrice' => (float) $row['total_price'], 'totalCount' => (int) $row['total_count'], ]; } // ============================================================ // 3. 赔偿明细 — fys_paper_factory + fys_pressing_factory UNION // ============================================================ public function compensationDetails(array $filters, int $page, int $pageSize): array { $where = []; $params = []; if (!empty($filters['supplier'])) { $where[] = 'supplier LIKE ?'; $params[] = '%' . $filters['supplier'] . '%'; } if (!empty($filters['month_from'])) { $where[] = 'month >= ?'; $params[] = $filters['month_from']; } if (!empty($filters['month_to'])) { $where[] = 'month <= ?'; $params[] = $filters['month_to']; } $whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; $sql = "SELECT month, supplier, CAST(compensation_amount AS decimal(18,2)) AS amount, reason, '' AS remark, creator, CONVERT(varchar(19), create_time, 120) AS create_time, '纸板厂' AS factory_type FROM ( SELECT month, factory_subject_name AS supplier, compensation_amount, reason, creator, create_time FROM fys_paper_factory_compensation_detail UNION ALL SELECT month, factory_subject_name AS supplier, compensation_amount, reason, creator, create_time FROM fys_pressing_factory_compensation_detail ) AS t $whereSql ORDER BY month DESC, create_time DESC"; return $this->runQuery($sql, $params, $page, $pageSize); } public function compensationDetailSummary(array $filters): array { $where = []; $params = []; if (!empty($filters['supplier'])) { $where[] = 'supplier LIKE ?'; $params[] = '%' . $filters['supplier'] . '%'; } $whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; $sql = "SELECT ISNULL(SUM(compensation_amount), 0) AS total_amount, COUNT(*) AS total_count FROM ( SELECT compensation_amount, factory_subject_name AS supplier FROM fys_paper_factory_compensation_detail UNION ALL SELECT compensation_amount, factory_subject_name AS supplier FROM fys_pressing_factory_compensation_detail ) AS t $whereSql"; $stmt = $this->db->prepare($sql); $stmt->execute($params); $row = $stmt->fetch(); return [ 'totalAmount' => (float) $row['total_amount'], 'totalCount' => (int) $row['total_count'], ]; } // ============================================================ // 4. 借支明细 — finance_advanceLoans (4行, 字段不足,部分列显示-) // ============================================================ public function advanceDetails(array $filters, int $page, int $pageSize): array { $where = []; $params = []; if (!empty($filters['supplier'])) { $where[] = 'supplier LIKE ?'; $params[] = '%' . $filters['supplier'] . '%'; } if (!empty($filters['month_from'])) { $where[] = 'month >= ?'; $params[] = $filters['month_from']; } if (!empty($filters['month_to'])) { $where[] = 'month <= ?'; $params[] = $filters['month_to']; } $whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; $sql = "SELECT supplier, '' AS contract_name, CAST(price AS decimal(18,2)) AS advance_amount, CAST(0 AS decimal(18,2)) AS actual_pay, CAST(0 AS decimal(18,2)) AS month_deduct, CAST(0 AS decimal(18,2)) AS should_deduct, '' AS deduct_start_month, '' AS deduct_end_month, 0 AS remain_periods, CONVERT(varchar(7), month, 120) AS month_str, '' AS loan_term, remark, '' AS is_paid, CONVERT(varchar(7), month, 120) AS pay_month, '' AS serial_no, '' AS loan_agreement, personCharge AS person_charge, '' AS receipt_0, '' AS receipt_1, '' AS receipt_2, CONVERT(varchar(19), createTime, 120) AS create_time, creator FROM finance_advanceLoans $whereSql ORDER BY month DESC, createTime DESC"; return $this->runQuery($sql, $params, $page, $pageSize); } public function advanceDetailSummary(array $filters): array { $where = []; $params = []; if (!empty($filters['supplier'])) { $where[] = 'supplier LIKE ?'; $params[] = '%' . $filters['supplier'] . '%'; } $whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; $sql = "SELECT ISNULL(SUM(price), 0) AS total_amount, COUNT(*) AS total_count FROM finance_advanceLoans $whereSql"; $stmt = $this->db->prepare($sql); $stmt->execute($params); $row = $stmt->fetch(); return [ 'totalAmount' => (float) $row['total_amount'], 'totalCount' => (int) $row['total_count'], ]; } // ============================================================ // 5. 样品扣款 — finance_sampleDeduction (12行) // ============================================================ public function sampleDeductions(array $filters, int $page, int $pageSize): array { $where = []; $params = []; if (!empty($filters['supplier'])) { $where[] = 'supplier LIKE ?'; $params[] = '%' . $filters['supplier'] . '%'; } if (!empty($filters['department'])) { $where[] = 'department LIKE ?'; $params[] = '%' . $filters['department'] . '%'; } if (!empty($filters['creator'])) { $where[] = 'creator LIKE ?'; $params[] = '%' . $filters['creator'] . '%'; } if (!empty($filters['sku_id'])) { $where[] = 'sku_id LIKE ?'; $params[] = '%' . $filters['sku_id'] . '%'; } if (!empty($filters['date_from'])) { $where[] = 'finishTime >= ?'; $params[] = $filters['date_from']; } if (!empty($filters['date_to'])) { $where[] = 'finishTime <= ?'; $params[] = $filters['date_to']; } $whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; $sql = "SELECT CONVERT(varchar(10), finishTime, 120) AS finish_time, department, creator, sku_id, skuName, CAST(num AS decimal(18,2)) AS num, supplier, purchase, productCode, CAST(shippingFee AS decimal(18,2)) AS shipping_fee, CAST(costPrice AS decimal(18,2)) AS cost_price, CAST(totalAmount AS decimal(18,2)) AS total_amount, CAST(estimatedPrice AS decimal(18,2)) AS estimated_price, CAST(estimatedPriceAll AS decimal(18,2)) AS estimated_price_all, '' AS remark, businessId, CONVERT(varchar(19), updateAt, 120) AS update_time FROM finance_sampleDeduction $whereSql ORDER BY finishTime DESC"; return $this->runQuery($sql, $params, $page, $pageSize); } public function sampleDeductionSummary(array $filters): array { $where = []; $params = []; if (!empty($filters['supplier'])) { $where[] = 'supplier LIKE ?'; $params[] = '%' . $filters['supplier'] . '%'; } $whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; $sql = "SELECT ISNULL(SUM(totalAmount), 0) AS total_amount, ISNULL(SUM(estimatedPriceAll), 0) AS total_estimate, COUNT(*) AS total_count FROM finance_sampleDeduction $whereSql"; $stmt = $this->db->prepare($sql); $stmt->execute($params); $row = $stmt->fetch(); return [ 'totalAmount' => (float) $row['total_amount'], 'totalEstimate' => (float) $row['total_estimate'], 'totalCount' => (int) $row['total_count'], ]; } // ============================================================ // 筛选器选项 // ============================================================ public function filterOptionsForReturn(): array { $suppliers = $this->fetchDistinct('SELECT DISTINCT supplierName FROM finance_purchaseReturn WHERE supplierName IS NOT NULL ORDER BY supplierName'); $warehouses = $this->fetchDistinct('SELECT DISTINCT warehouseName FROM finance_purchaseReturn WHERE warehouseName IS NOT NULL ORDER BY warehouseName'); return compact('suppliers', 'warehouses'); } public function filterOptionsForInvoice(): array { $suppliers = $this->fetchDistinct('SELECT DISTINCT supplier FROM finance_invoiceDeduction WHERE supplier IS NOT NULL ORDER BY supplier'); return ['suppliers' => $suppliers]; } public function filterOptionsForCompensation(): array { $suppliers = $this->fetchDistinct("SELECT DISTINCT supplier FROM ( SELECT factory_subject_name AS supplier FROM fys_paper_factory_compensation_detail WHERE factory_subject_name IS NOT NULL UNION SELECT factory_subject_name AS supplier FROM fys_pressing_factory_compensation_detail WHERE factory_subject_name IS NOT NULL ) AS t ORDER BY supplier"); return ['suppliers' => $suppliers]; } public function filterOptionsForAdvance(): array { $suppliers = $this->fetchDistinct('SELECT DISTINCT supplier FROM finance_advanceLoans WHERE supplier IS NOT NULL ORDER BY supplier'); return ['suppliers' => $suppliers]; } public function filterOptionsForSample(): array { $suppliers = $this->fetchDistinct('SELECT DISTINCT supplier FROM finance_sampleDeduction WHERE supplier IS NOT NULL ORDER BY supplier'); $departments = $this->fetchDistinct('SELECT DISTINCT department FROM finance_sampleDeduction WHERE department IS NOT NULL ORDER BY department'); $creators = $this->fetchDistinct('SELECT DISTINCT creator FROM finance_sampleDeduction WHERE creator IS NOT NULL ORDER BY creator'); return compact('suppliers', 'departments', 'creators'); } private function fetchDistinct(string $sql): array { $rows = $this->db->query($sql)->fetchAll(PDO::FETCH_COLUMN); return array_values(array_filter($rows, fn ($v) => $v !== null && $v !== '')); } }