116 lines
3.8 KiB
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);
|
|
}
|
|
}
|