CaiWuXiTong/modules/EcommerceFinance/Repositories/RawBoardPayablesRepository.php

264 lines
12 KiB
PHP

<?php
namespace Modules\EcommerceFinance\Repositories;
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQuery;
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQueryFactory;
/**
* 原材料素板应付只读数据仓库。
*/
class RawBoardPayablesRepository
{
private const SOURCE = 'jiansun';
private const RECONCILIATION_SUMMARY_KEYS = [
'outbound_quantity',
'received_quantity',
'reconciliation_amount',
];
private ?UpstreamReadOnlyQuery $query = null;
public function __construct(private readonly UpstreamReadOnlyQueryFactory $queries)
{
}
/** @return array{data:array<int,array<string,mixed>>,total:int} */
public function prices(array $filters, int $page, int $limit): array
{
$where = ['ISNULL(p.price, 0) > 0'];
$bindings = [];
if (($filters['raw_name'] ?? '') !== '') {
$where[] = "ISNULL(plain_material.name, '') LIKE ? ESCAPE '\\'";
$bindings[] = $this->contains($filters['raw_name']);
}
if (($filters['raw_code'] ?? '') !== '') {
$where[] = "p.number LIKE ? ESCAPE '\\'";
$bindings[] = $this->contains($filters['raw_code']);
}
if (($filters['production_code'] ?? '') !== '') {
$where[] = "COALESCE(NULLIF(p.proNumber, ''), plain_material.proNumber, '') LIKE ? ESCAPE '\\'";
$bindings[] = $this->contains($filters['production_code']);
}
if (($filters['supplier'] ?? '') !== '') {
$where[] = "COALESCE(NULLIF(supplier_info.supplierText, ''), p.supplier, '') LIKE ? ESCAPE '\\'";
$bindings[] = $this->contains($filters['supplier']);
}
$page = max($page, 1);
$limit = min(max($limit, 1), 100);
$offset = ($page - 1) * $limit;
$whereSql = implode(' AND ', $where);
$sql = "WITH latest_plain_materials AS (
SELECT number, MAX(Id) AS id
FROM [dbo].[pm_plainPlateData]
WHERE ISNULL([delete], 0) = 0
AND NULLIF(number, '') IS NOT NULL
GROUP BY number
), deduplicated_prices AS (
SELECT
number,
supplier,
proNumber,
price,
MAX(COALESCE(updateTime, createTime)) AS sort_time,
MAX(id) AS sort_id
FROM [dbo].[pm_boardPurchasePrice]
WHERE ISNULL(price, 0) > 0
GROUP BY number, supplier, proNumber, price
), source_rows AS (
SELECT
ISNULL(plain_material.name, '') AS raw_name,
ISNULL(p.number, '') AS raw_code,
COALESCE(NULLIF(p.proNumber, ''), plain_material.proNumber, '') AS production_code,
CAST(p.price AS decimal(18,2)) AS latest_price,
COALESCE(NULLIF(supplier_info.supplierText, ''), p.supplier, '') AS target_supplier,
ROW_NUMBER() OVER (
ORDER BY p.sort_time DESC, p.sort_id DESC
) AS row_no,
COUNT(*) OVER () AS total_count
FROM deduplicated_prices p
INNER JOIN latest_plain_materials latest_plain
ON latest_plain.number = p.number
INNER JOIN [dbo].[pm_plainPlateData] plain_material
ON plain_material.Id = latest_plain.id
OUTER APPLY (
SELECT TOP 1 i.supplierText
FROM [dbo].[pm_boardPriceChangeImport] i
WHERE i.number = p.number
AND i.supplier = p.supplier
AND NULLIF(LTRIM(RTRIM(i.supplierText)), '') IS NOT NULL
ORDER BY i.id DESC
) supplier_info
WHERE {$whereSql}
)
SELECT raw_name, raw_code, production_code, latest_price,
target_supplier, total_count
FROM source_rows
WHERE row_no BETWEEN ? AND ?
ORDER BY row_no";
$rows = $this->query()->select($sql, array_merge($bindings, [$offset + 1, $offset + $limit]));
foreach ($rows as &$row) {
$row['price_type'] = '采购价';
$row['factory_type'] = '素板厂';
}
return $this->pageResult($rows);
}
/** @return array{data:array<int,array<string,mixed>>,total:int,totals:array<string,float>} */
public function reconciliations(array $filters, int $page, int $limit): array
{
$where = ['1 = 1'];
$bindings = [];
if (($filters['date_from'] ?? '') !== '') {
$where[] = 'CONVERT(date, d.inDate) >= ?';
$bindings[] = $filters['date_from'];
}
if (($filters['date_to'] ?? '') !== '') {
$where[] = 'CONVERT(date, d.inDate) <= ?';
$bindings[] = $filters['date_to'];
}
if (($filters['production_code'] ?? '') !== '') {
$where[] = "d.number LIKE ? ESCAPE '\\'";
$bindings[] = $this->contains($filters['production_code']);
}
if (($filters['raw_name'] ?? '') !== '') {
$where[] = "d.name LIKE ? ESCAPE '\\'";
$bindings[] = $this->contains($filters['raw_name']);
}
if (($filters['outbound_supplier'] ?? '') !== '') {
$where[] = "d.giveSupplier LIKE ? ESCAPE '\\'";
$bindings[] = $this->contains($filters['outbound_supplier']);
}
if (($filters['arrival_supplier'] ?? '') !== '') {
$where[] = "d.arrSupplier LIKE ? ESCAPE '\\'";
$bindings[] = $this->contains($filters['arrival_supplier']);
}
if (($filters['outbound_no'] ?? '') !== '') {
$where[] = "(ISNULL(d.deliveryNo, '') LIKE ? ESCAPE '\\'
OR ISNULL(d.sendNo, '') LIKE ? ESCAPE '\\')";
$needle = $this->contains($filters['outbound_no']);
$bindings[] = $needle;
$bindings[] = $needle;
}
if (($filters['return_flag'] ?? '') === 'return') {
$where[] = 'ISNULL(d.deliveryCount, 0) < 0';
} elseif (($filters['return_flag'] ?? '') === 'normal') {
$where[] = 'ISNULL(d.deliveryCount, 0) >= 0';
}
$page = max($page, 1);
$limit = min(max($limit, 1), 100);
$offset = ($page - 1) * $limit;
$whereSql = implode(' AND ', $where);
$sql = "WITH source_rows AS (
SELECT
CONVERT(varchar(19), d.inDate, 120) AS inbound_date,
CONVERT(varchar(19), d.deliveryDate, 120) AS outbound_date,
ISNULL(d.number, '') AS production_code,
ISNULL(d.name, '') AS raw_name,
CAST(ISNULL(d.deliveryCount, 0) AS decimal(18,2)) AS outbound_quantity,
ISNULL(d.giveSupplier, '') AS outbound_supplier,
ISNULL(d.arrSupplier, '') AS arrival_supplier,
CONVERT(varchar(10), d.date, 120) AS batch_date,
ISNULL(d.deliveryNo, '') AS outbound_no,
ISNULL(d.deliveryRemarks, '') AS outbound_remark,
ISNULL(d.applyNo, '') AS requisition_no,
ISNULL(d.numberType, '') AS number_type,
ISNULL(d.sendNo, '') AS delivery_no,
CAST(ISNULL(d.inCount, 0) AS decimal(18,2)) AS received_quantity,
CAST(ISNULL(d.priceA, 0) AS decimal(18,2)) AS unit_price,
CAST(
COALESCE(d.moneyA, ISNULL(d.inCount, 0) * ISNULL(d.priceA, 0), 0)
AS decimal(18,2)
) AS reconciliation_amount,
CAST(NULL AS decimal(18,2)) AS freight_with_allocation,
CAST(NULL AS decimal(18,2)) AS allocated_freight,
CAST(ISNULL(freight_info.transportCost, 0) AS decimal(18,2)) AS freight,
CAST(SUM(CAST(ISNULL(d.deliveryCount, 0) AS decimal(38,2))) OVER () AS decimal(18,2)) AS summary_outbound_quantity,
CAST(SUM(CAST(ISNULL(d.inCount, 0) AS decimal(38,2))) OVER () AS decimal(18,2)) AS summary_received_quantity,
CAST(SUM(CAST(
COALESCE(d.moneyA, ISNULL(d.inCount, 0) * ISNULL(d.priceA, 0), 0)
AS decimal(38,2)
)) OVER () AS decimal(18,2)) AS summary_reconciliation_amount,
ROW_NUMBER() OVER (ORDER BY d.inDate DESC, d.id DESC) AS row_no,
COUNT(*) OVER () AS total_count
FROM [dbo].[finance_boardDeduction] d
OUTER APPLY (
SELECT TOP 1 ad.transportCost
FROM [dbo].[pm_boardApplyDetail] ad
WHERE ad.applyNo = d.applyNo
AND ad.name = d.name
ORDER BY
CASE
WHEN ad.inCount = d.inCount AND ad.deliveryCount = d.deliveryCount THEN 0
ELSE 1
END,
ad.Id DESC
) freight_info
WHERE {$whereSql}
)
SELECT inbound_date, outbound_date, production_code, raw_name,
outbound_quantity, outbound_supplier, arrival_supplier,
batch_date, outbound_no, outbound_remark, requisition_no,
number_type, delivery_no, received_quantity, unit_price,
reconciliation_amount, freight_with_allocation,
allocated_freight, freight, summary_outbound_quantity,
summary_received_quantity, summary_reconciliation_amount,
total_count
FROM source_rows
WHERE row_no BETWEEN ? AND ?
ORDER BY row_no";
$rows = $this->query()->select($sql, array_merge($bindings, [$offset + 1, $offset + $limit]));
$totals = array_fill_keys(self::RECONCILIATION_SUMMARY_KEYS, 0.0);
if ($rows !== []) {
foreach (self::RECONCILIATION_SUMMARY_KEYS as $key) {
$totals[$key] = (float) ($rows[0]['summary_' . $key] ?? 0);
}
}
foreach ($rows as &$row) {
$row['unit'] = '张';
$row['return_flag'] = (float) ($row['outbound_quantity'] ?? 0) < 0 ? '退货' : '正常出库';
foreach (self::RECONCILIATION_SUMMARY_KEYS as $key) {
unset($row['summary_' . $key]);
}
}
return array_merge($this->pageResult($rows), ['totals' => $totals]);
}
private function query(): UpstreamReadOnlyQuery
{
return $this->query ??= $this->queries->forSource(self::SOURCE);
}
private function contains(string $value): string
{
return '%' . str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], trim($value)) . '%';
}
/** @param array<int,array<string,mixed>> $rows */
private function pageResult(array $rows): array
{
$total = (int) ($rows[0]['total_count'] ?? 0);
foreach ($rows as &$row) {
unset($row['total_count']);
}
return ['data' => $rows, 'total' => $total];
}
}