新增功能页面: - 货款对账 (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 动态视图解析
541 lines
20 KiB
PHP
541 lines
20 KiB
PHP
<?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 !== ''));
|
||
}
|
||
}
|