CaiWuXiTong/modules/EcommerceFinance/Repositories/SupplierPayableRepository.php
WuYongJie e5c068036b feat: 供应商应付 - 货款对账 + 5个子功能页面
新增功能页面:
- 货款对账 (payment-reconciliation): 入库+代发合并对账,228K+2.8M条记录
- 退货对账 (return-reconciliation): finance_purchaseReturn, 36791行
- 发票扣款 (invoice-deductions): finance_invoiceDeduction, 195行
- 赔偿明细 (compensation-details): 纸板厂+压板厂 UNION, 98行
- 借支明细 (advance-details): finance_advanceLoans, 4行
- 样品扣款 (sample-deductions): finance_sampleDeduction, 12行

后端:
- JianSunConnection: PDO_ODBC 直连 SQL Server
- SupplierPayableRepository: 5个子页面的数据查询+分页
- GoodsReconciliationRepository: 货款对账数据查询
- 15个新API路由 (supplier-payables/*)
- 12个JianSun Eloquent模型

前端:
- 6个Blade视图 + 6个Vue组件
- webpack.mix.js 新增5个编译入口
- show.blade.php 动态视图解析
2026-07-21 18:40:24 +08:00

541 lines
20 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Modules\EcommerceFinance\Repositories;
use Modules\EcommerceFinance\Services\JianSunConnection;
use PDO;
/**
* 供应商应付 - 子功能页面数据仓库
*
* 封装 5 个子页面的数据访问逻辑:
* - returnReconciliations() 退货对账
* - invoiceDeductions() 发票扣款
* - compensationDetails() 赔偿明细(UNION 纸板 + 压板)
* - advanceDetails() 借支明细
* - sampleDeductions() 样品扣款
*
* 所有方法返回统一结构: ['data' => array<int,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 !== ''));
}
}