CaiWuXiTong/modules/EcommerceFinance/Repositories/VipshopJxOpeningBalanceRepository.php

109 lines
3.9 KiB
PHP

<?php
namespace Modules\EcommerceFinance\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\DatabaseManager;
class VipshopJxOpeningBalanceRepository
{
public const TABLE = 'vipshop_jx_opening_balance_snapshots';
public const BASELINE_PERIOD_END = '2026-06-30';
private readonly ConnectionInterface $mirror;
private readonly ConnectionInterface $bills;
private readonly VipshopJxBankPaymentRepository $bankPayments;
public function __construct(
DatabaseManager $databases,
VipshopJxBankPaymentRepository $bankPayments,
)
{
$this->mirror = $databases->connection(
(string) config('upstream_mirrors.connection', 'business_mirror'),
);
$this->bills = $databases->connection();
$this->bankPayments = $bankPayments;
}
/** @return array{balance:float,date:?string,source:string,withdrawal_amount:float,withdrawal_count:int,withdrawal_latest_date:?string} */
public function openingBalance(?string $startDate, ?string $endDate): array
{
$start = $this->monthStart($startDate) ?? $this->monthStart($endDate);
$baselineMonth = CarbonImmutable::create(2026, 7, 1)->startOfDay();
if ($start === null || $start->lt($baselineMonth)) {
return $this->none();
}
if (! $this->mirror->getSchemaBuilder()->hasTable(self::TABLE)) {
return $this->none();
}
$baseline = $this->mirror->table(self::TABLE)
->where('period_end', self::BASELINE_PERIOD_END)
->value('balance');
if ($baseline === null) {
return $this->none();
}
$withdrawnBefore = $this->bankPayments->netBefore($start->format('Y-m'));
if ($withdrawnBefore === null) {
return $this->none();
}
$balance = (float) $baseline - $withdrawnBefore;
if ($start->gt($baselineMonth)) {
$movement = $this->bills->table('ec_account_bills')
->where('company_id', (int) (company_id() ?: 1))
->where('bill_type', 'vipshop-jx')
->whereNull('deleted_at')
->where('payload->settle_period', '>=', '2026-07')
->where('payload->settle_period', '<', $start->format('Y-m'))
->selectRaw('COALESCE(SUM(income), 0) AS income, COALESCE(SUM(expense), 0) AS expense')
->first();
$balance += (float) ($movement->income ?? 0) - (float) ($movement->expense ?? 0);
}
$withdrawal = $this->bankPayments->forPeriod($start->format('Y-m'));
return [
'balance' => round($balance, 2),
'date' => $start->subDay()->format('Y-m-d'),
'source' => $start->equalTo($baselineMonth) ? 'bank_settled' : 'previous_period',
'withdrawal_amount' => $withdrawal['amount'],
'withdrawal_count' => $withdrawal['count'],
'withdrawal_latest_date' => $withdrawal['latest_pay_date'],
];
}
/** @return array{balance:float,date:null,source:string,withdrawal_amount:float,withdrawal_count:int,withdrawal_latest_date:null} */
private function none(): array
{
return [
'balance' => 0.0,
'date' => null,
'source' => 'none',
'withdrawal_amount' => 0.0,
'withdrawal_count' => 0,
'withdrawal_latest_date' => null,
];
}
private function monthStart(?string $value): ?CarbonImmutable
{
$date = trim((string) $value);
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) !== 1) {
return null;
}
try {
$parsed = CarbonImmutable::createFromFormat('!Y-m-d', $date);
} catch (\Throwable) {
return null;
}
return $parsed instanceof CarbonImmutable && $parsed->format('Y-m-d') === $date
? $parsed->startOfMonth()
: null;
}
}