CaiWuXiTong/modules/EcommerceFinance/Repositories/AlibabaBillRepository.php
chenxi a9fd2cfb37 接入 1688 营销账户账单 (#12)
根据 Alibaba.xlsx 字段接入 JianSun.dbo.alibaba_bill,提供筛选、汇总、分页、快照刷新和错误状态。
2026-08-06 16:07:31 +08:00

139 lines
5.2 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\UpstreamReadOnlyQuery;
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQueryFactory;
/**
* Alibaba/1688 营销账户账单。
*
* 数据来源:JianSun.dbo.alibaba_bill。金额字段由上游按元存储。
*/
class AlibabaBillRepository
{
private const TABLE = '[dbo].[alibaba_bill]';
private const SOURCE = 'jiansun';
private UpstreamReadOnlyQuery $query;
public function __construct(UpstreamReadOnlyQueryFactory $factory)
{
$this->query = $factory->forSource(self::SOURCE);
}
/** @return array{shops: string[], product_types: string[]} */
public function filterOptions(): array
{
$shops = $this->query->select(
'SELECT DISTINCT TOP (500) shopName FROM ' . self::TABLE
. ' WHERE shopName IS NOT NULL AND LTRIM(RTRIM(shopName)) <> \'\' ORDER BY shopName'
);
$types = $this->query->select(
'SELECT DISTINCT TOP (500) tradeTypeName FROM ' . self::TABLE
. ' WHERE tradeTypeName IS NOT NULL AND LTRIM(RTRIM(tradeTypeName)) <> \'\' ORDER BY tradeTypeName'
);
return [
'shops' => array_values(array_map(static fn (array $row): string => (string) ($row['shopName'] ?? ''), $shops)),
'product_types' => array_values(array_map(static fn (array $row): string => (string) ($row['tradeTypeName'] ?? ''), $types)),
];
}
/** @return array{rows: array<int,array<string,mixed>>, total: int} */
public function paginate(
?string $shopName = null,
?string $productType = null,
?string $fromDate = null,
?string $toDate = null,
int $page = 1,
int $pageSize = 25,
): array {
$page = max(1, $page);
$pageSize = min(max(1, $pageSize), 100);
[$where, $bindings] = $this->where($shopName, $productType, $fromDate, $toDate);
$total = (int) $this->query->scalar('SELECT COUNT(*) FROM ' . self::TABLE . ' src' . $where, $bindings);
if ($total === 0) {
return ['rows' => [], 'total' => 0];
}
$start = (($page - 1) * $pageSize) + 1;
$end = $page * $pageSize;
$sql = 'SELECT * FROM ('
. 'SELECT src.id, src.shopName, src.tradeDay, src.tradeTypeName, '
. 'src.tradeAMTIn, src.tradeAMTOut, '
. 'ROW_NUMBER() OVER (ORDER BY src.tradeDay DESC, src.id DESC) AS rn '
. 'FROM ' . self::TABLE . ' src' . $where
. ') AS t WHERE t.rn BETWEEN ? AND ? ORDER BY t.rn';
$rows = $this->query->select($sql, array_merge($bindings, [$start, $end]));
return [
'rows' => array_map(fn (array $row): array => $this->normalize($row), $rows),
'total' => $total,
];
}
/** @return array{income_total: float, expense_total: float, net: float, count: int} */
public function summary(
?string $shopName = null,
?string $productType = null,
?string $fromDate = null,
?string $toDate = null,
): array {
[$where, $bindings] = $this->where($shopName, $productType, $fromDate, $toDate);
$row = $this->query->first(
'SELECT COALESCE(SUM(tradeAMTIn), 0) AS income_total, '
. 'COALESCE(SUM(tradeAMTOut), 0) AS expense_total, COUNT(*) AS cnt '
. 'FROM ' . self::TABLE . ' src' . $where,
$bindings,
) ?? [];
$income = round((float) ($row['income_total'] ?? 0), 2);
$expense = round((float) ($row['expense_total'] ?? 0), 2);
return [
'income_total' => $income,
'expense_total' => $expense,
'net' => round($income - $expense, 2),
'count' => (int) ($row['cnt'] ?? 0),
];
}
/** @return array{0: string, 1: array<int,mixed>} */
private function where(?string $shopName, ?string $productType, ?string $fromDate, ?string $toDate): array
{
$conditions = [];
$bindings = [];
if ($shopName !== null && $shopName !== '') {
$conditions[] = 'src.shopName = ?';
$bindings[] = $shopName;
}
if ($productType !== null && $productType !== '') {
$conditions[] = 'src.tradeTypeName = ?';
$bindings[] = $productType;
}
if ($fromDate !== null && preg_match('/^\d{4}-\d{2}-\d{2}$/', $fromDate) === 1) {
$conditions[] = 'CAST(src.tradeDay AS date) >= CAST(? AS date)';
$bindings[] = $fromDate;
}
if ($toDate !== null && preg_match('/^\d{4}-\d{2}-\d{2}$/', $toDate) === 1) {
$conditions[] = 'CAST(src.tradeDay AS date) <= CAST(? AS date)';
$bindings[] = $toDate;
}
return [$conditions === [] ? '' : ' WHERE ' . implode(' AND ', $conditions), $bindings];
}
private function normalize(array $row): array
{
return [
'shop_name' => (string) ($row['shopName'] ?? ''),
'trade_day' => (string) ($row['tradeDay'] ?? ''),
'product_type' => (string) ($row['tradeTypeName'] ?? ''),
'income_amount' => round((float) ($row['tradeAMTIn'] ?? 0), 2),
'expense_amount' => round((float) ($row['tradeAMTOut'] ?? 0), 2),
];
}
}