207 lines
8.3 KiB
PHP
207 lines
8.3 KiB
PHP
<?php
|
|
|
|
namespace Modules\EcommerceFinance\Services;
|
|
|
|
use Carbon\CarbonImmutable;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Modules\EcommerceFinance\Http\Controllers\AccountBills;
|
|
use Modules\EcommerceFinance\Http\Controllers\AlipayBills;
|
|
use Modules\EcommerceFinance\Http\Controllers\PinduoduoAccountBills;
|
|
use Modules\EcommerceFinance\Http\Controllers\TaobaoWechatAccountBills;
|
|
use Modules\EcommerceFinance\Repositories\PinduoduoSettlementBillRepository;
|
|
use Modules\EcommerceFinance\Repositories\StoreReceivingAlipayBillMirrorRepository;
|
|
use Modules\EcommerceFinance\Repositories\TaobaoWechatOpeningBalanceRepository;
|
|
use Modules\EcommerceFinance\Repositories\TaobaoWechatSettlementBillRepository;
|
|
use RuntimeException;
|
|
use Throwable;
|
|
|
|
class StoreReceivingBalanceOverviewService
|
|
{
|
|
/** @var array<int,array{slug:string,name:string}> */
|
|
public const PLATFORMS = [
|
|
['slug' => 'taobao-alipay', 'name' => '淘系支付宝账户'],
|
|
['slug' => 'taobao-wechat', 'name' => '淘系微信账户'],
|
|
['slug' => 'pinduoduo', 'name' => '拼多多账户'],
|
|
['slug' => 'jd-wallet', 'name' => '京东网银钱包账户'],
|
|
['slug' => 'douyin', 'name' => '抖音账户'],
|
|
['slug' => 'vipshop', 'name' => '唯品会MP账户'],
|
|
['slug' => 'vipshop-jx', 'name' => '唯品会JX账户'],
|
|
['slug' => 'xiaohongshu', 'name' => '小红书账户'],
|
|
];
|
|
|
|
public function __construct(
|
|
private readonly AlipayBills $alipayBills,
|
|
private readonly TaobaoWechatAccountBills $taobaoWechatBills,
|
|
private readonly PinduoduoAccountBills $pinduoduoBills,
|
|
private readonly AccountBills $accountBills,
|
|
) {}
|
|
|
|
/** @return array<string,mixed> */
|
|
public function overview(string $month): array
|
|
{
|
|
$start = CarbonImmutable::createFromFormat('!Y-m', $month, 'Asia/Shanghai');
|
|
if (! $start instanceof CarbonImmutable || $start->format('Y-m') !== $month) {
|
|
throw new RuntimeException('invalid_balance_overview_month');
|
|
}
|
|
|
|
$periodStart = $start->startOfMonth()->format('Y-m-d');
|
|
$periodEnd = $start->endOfMonth()->format('Y-m-d');
|
|
$platforms = [];
|
|
$availableTotals = [
|
|
'opening_balance' => 0.0,
|
|
'income' => 0.0,
|
|
'expense' => 0.0,
|
|
];
|
|
$availableCount = 0;
|
|
|
|
foreach (self::PLATFORMS as $definition) {
|
|
try {
|
|
$summary = $this->fetchPlatformSummary(
|
|
$definition['slug'],
|
|
$periodStart,
|
|
$periodEnd,
|
|
);
|
|
if ($summary['opening_balance'] === null) {
|
|
throw new RuntimeException('平台期初余额未就绪');
|
|
}
|
|
|
|
$availableCount++;
|
|
$availableTotals['opening_balance'] += (float) $summary['opening_balance'];
|
|
$availableTotals['income'] += (float) $summary['income'];
|
|
$availableTotals['expense'] += (float) $summary['expense'];
|
|
$platforms[] = [
|
|
'slug' => $definition['slug'],
|
|
'name' => $definition['name'],
|
|
'available' => true,
|
|
'opening_balance' => round((float) $summary['opening_balance'], 2),
|
|
'income' => round((float) $summary['income'], 2),
|
|
'expense' => round((float) $summary['expense'], 2),
|
|
'balance' => round(
|
|
(float) $summary['opening_balance']
|
|
+ (float) $summary['income']
|
|
- (float) $summary['expense'],
|
|
2,
|
|
),
|
|
'error' => null,
|
|
];
|
|
} catch (Throwable $exception) {
|
|
report($exception);
|
|
$platforms[] = [
|
|
'slug' => $definition['slug'],
|
|
'name' => $definition['name'],
|
|
'available' => false,
|
|
'opening_balance' => null,
|
|
'income' => null,
|
|
'expense' => null,
|
|
'balance' => null,
|
|
'error' => $this->safeErrorMessage($exception),
|
|
];
|
|
}
|
|
}
|
|
|
|
$complete = $availableCount === count(self::PLATFORMS);
|
|
$openingBalance = $complete ? round($availableTotals['opening_balance'], 2) : null;
|
|
$income = $complete ? round($availableTotals['income'], 2) : null;
|
|
$expense = $complete ? round($availableTotals['expense'], 2) : null;
|
|
|
|
return [
|
|
'month' => $month,
|
|
'period' => [
|
|
'start' => $periodStart,
|
|
'end' => $periodEnd,
|
|
],
|
|
'summary' => [
|
|
'opening_balance' => $openingBalance,
|
|
'income' => $income,
|
|
'expense' => $expense,
|
|
'balance' => $complete
|
|
? round($openingBalance + $income - $expense, 2)
|
|
: null,
|
|
'platform_count' => count(self::PLATFORMS),
|
|
'available_platform_count' => $availableCount,
|
|
'complete' => $complete,
|
|
],
|
|
'platforms' => $platforms,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array{opening_balance:float|null,income:float,expense:float}
|
|
*/
|
|
protected function fetchPlatformSummary(string $platform, string $start, string $end): array
|
|
{
|
|
$request = Request::create('/ecommerce-finance/account-bills/overview', 'GET', [
|
|
'start' => $start,
|
|
'end' => $end,
|
|
'page' => 1,
|
|
'limit' => 1,
|
|
'sort' => 'occurred_at',
|
|
'dir' => 'desc',
|
|
], [], [], ['HTTP_ACCEPT' => 'application/json']);
|
|
|
|
$response = match ($platform) {
|
|
'taobao-alipay' => $this->alipayBills->index(
|
|
$request,
|
|
app(StoreReceivingAlipayBillMirrorRepository::class),
|
|
),
|
|
'taobao-wechat' => $this->taobaoWechatBills->index(
|
|
$request,
|
|
app(TaobaoWechatSettlementBillRepository::class),
|
|
app(TaobaoWechatOpeningBalanceRepository::class),
|
|
),
|
|
'pinduoduo' => $this->pinduoduoBills->index(
|
|
$request,
|
|
app(PinduoduoSettlementBillRepository::class),
|
|
),
|
|
'jd-wallet', 'douyin', 'vipshop', 'vipshop-jx', 'xiaohongshu' => $this->accountBills->index(
|
|
$request,
|
|
$platform,
|
|
),
|
|
default => throw new RuntimeException('unsupported_balance_overview_platform'),
|
|
};
|
|
|
|
return $this->summaryFromResponse($response);
|
|
}
|
|
|
|
/** @return array{opening_balance:float|null,income:float,expense:float} */
|
|
private function summaryFromResponse(JsonResponse $response): array
|
|
{
|
|
$payload = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
|
if ($response->getStatusCode() !== 200 || ! is_array($payload)) {
|
|
throw new RuntimeException((string) ($payload['message'] ?? '平台账单暂时无法读取'));
|
|
}
|
|
|
|
$summary = $payload['summary'] ?? null;
|
|
if (! is_array($summary)
|
|
|| ! array_key_exists('opening_balance', $summary)
|
|
|| ! is_numeric($summary['income'] ?? null)
|
|
|| ! is_numeric($summary['expense'] ?? null)
|
|
) {
|
|
throw new RuntimeException('平台账单汇总数据不完整');
|
|
}
|
|
|
|
return [
|
|
'opening_balance' => is_numeric($summary['opening_balance'])
|
|
? round((float) $summary['opening_balance'], 2)
|
|
: null,
|
|
'income' => round((float) $summary['income'], 2),
|
|
'expense' => round((float) $summary['expense'], 2),
|
|
];
|
|
}
|
|
|
|
private function safeErrorMessage(Throwable $exception): string
|
|
{
|
|
$message = trim($exception->getMessage());
|
|
|
|
return match ($message) {
|
|
'平台期初余额未就绪' => $message,
|
|
'平台账单汇总数据不完整' => $message,
|
|
'invalid_balance_overview_month' => '月份格式无效',
|
|
'unsupported_balance_overview_platform' => '平台类型无效',
|
|
'' => '平台账单暂时无法读取',
|
|
default => mb_substr($message, 0, 160),
|
|
};
|
|
}
|
|
}
|