CaiWuXiTong/modules/EcommerceFinance/Repositories/TaobaoWechatOpeningBalanceRepository.php

116 lines
3.8 KiB
PHP

<?php
namespace Modules\EcommerceFinance\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\DatabaseManager;
use Illuminate\Support\Facades\Schema;
class TaobaoWechatOpeningBalanceRepository
{
public const TABLE = 'taobao_wechat_opening_balances';
public const BASELINE_DATE = '2026-06-30';
private const BILL_TABLE = 'taobao_wechat_bill_rows';
private readonly ConnectionInterface $connection;
public function __construct(DatabaseManager $database)
{
$this->connection = $database->connection(
(string) config('upstream_mirrors.connection', 'business_mirror'),
);
}
/**
* @return array{balance:?float,date:?string,source:string,store_count:int}
*/
public function openingBalance(?string $storeName, ?string $periodStart): array
{
if (! $this->hasTable(self::TABLE)) {
return $this->unavailable();
}
$query = $this->connection->table(self::TABLE)
->where('balance_date', self::BASELINE_DATE);
if ($storeName !== null && $storeName !== '') {
$query->where('store_name', $storeName);
}
$baseline = (clone $query)->selectRaw(
'COALESCE(SUM(balance), 0) AS balance, COUNT(*) AS store_count, '
. 'COUNT(DISTINCT source) AS source_count, MIN(source) AS source'
)->first();
$storeCount = (int) ($baseline->store_count ?? 0);
if ($storeCount === 0) {
return $this->unavailable();
}
$balance = round((float) $baseline->balance, 2);
$date = self::BASELINE_DATE;
$source = (int) ($baseline->source_count ?? 0) === 1
? (string) $baseline->source
: 'mixed_opening_sources';
$start = $this->date($periodStart);
if ($start !== null && $start->gt('2026-07-01')) {
$movement = $this->connection->table(self::BILL_TABLE)
->where('occurred_at', '>=', '2026-07-01 00:00:00')
->where('occurred_at', '<', $start->format('Y-m-d') . ' 00:00:00')
->when($storeName !== null && $storeName !== '', function ($query) use ($storeName): void {
$query->where('store_name', $storeName);
})
->selectRaw(
'COALESCE(SUM(income_amount), 0) AS income, '
. 'COALESCE(SUM(expense_amount), 0) AS expense'
)
->first();
$balance = round(
$balance + (float) ($movement->income ?? 0) - (float) ($movement->expense ?? 0),
2,
);
$date = $start->subDay()->format('Y-m-d');
if (! str_ends_with($source, '_carried_forward')) {
$source .= '_carried_forward';
}
}
return [
'balance' => $balance,
'date' => $date,
'source' => $source,
'store_count' => $storeCount,
];
}
/** @return array{balance:null,date:null,source:string,store_count:int} */
private function unavailable(): array
{
return [
'balance' => null,
'date' => null,
'source' => 'unavailable',
'store_count' => 0,
];
}
private function date(?string $value): ?CarbonImmutable
{
if ($value === null || preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) !== 1) {
return null;
}
$date = CarbonImmutable::createFromFormat('!Y-m-d', $value);
return $date !== false && $date->format('Y-m-d') === $value ? $date : null;
}
private function hasTable(string $table): bool
{
return Schema::connection($this->connection->getName())->hasTable($table);
}
}