213 lines
7.0 KiB
PHP
213 lines
7.0 KiB
PHP
<?php
|
||
|
||
namespace Modules\EcommerceFinance\Repositories;
|
||
|
||
use Carbon\CarbonInterface;
|
||
use Illuminate\Database\ConnectionInterface;
|
||
use Illuminate\Database\DatabaseManager;
|
||
use Illuminate\Support\Facades\Schema;
|
||
|
||
class JushuitanShopBalanceRepository
|
||
{
|
||
public const TABLE = 'jushuitan_shop_balance_snapshots';
|
||
|
||
private readonly ConnectionInterface $connection;
|
||
|
||
public function __construct(DatabaseManager $database)
|
||
{
|
||
$this->connection = $database->connection(
|
||
(string) config('upstream_mirrors.connection', 'business_mirror'),
|
||
);
|
||
}
|
||
|
||
/**
|
||
* @param iterable<object> $stores
|
||
* @return array<int,array{status:string,balance:?float,source_store_name:?string,source_platform:?string}>
|
||
*/
|
||
public function matchesForStores(iterable $stores, string $balanceDate): array
|
||
{
|
||
$stores = is_array($stores) ? $stores : iterator_to_array($stores);
|
||
$result = [];
|
||
|
||
foreach ($stores as $store) {
|
||
$result[(int) $store->id] = $this->emptyMatch('not_ready');
|
||
}
|
||
|
||
if (! $this->hasTable()) {
|
||
return $result;
|
||
}
|
||
|
||
$sourceRows = $this->connection->table(self::TABLE)
|
||
->where('balance_date', $balanceDate)
|
||
->orderBy('id')
|
||
->get([
|
||
'id', 'source_platform', 'store_name', 'normalized_store_name',
|
||
'platform_account_id', 'balance',
|
||
]);
|
||
|
||
if ($sourceRows->isEmpty()) {
|
||
return $result;
|
||
}
|
||
|
||
foreach ($stores as $store) {
|
||
$names = array_values(array_unique(array_filter([
|
||
self::normalizeStoreName((string) ($store->shop_name ?? '')),
|
||
self::normalizeStoreName((string) ($store->erp_store_name ?? '')),
|
||
])));
|
||
|
||
$matches = $sourceRows->filter(
|
||
static fn (object $row): bool => in_array((string) $row->normalized_store_name, $names, true),
|
||
)->values();
|
||
|
||
if ($matches->isEmpty()) {
|
||
$result[(int) $store->id] = $this->emptyMatch('unmatched');
|
||
continue;
|
||
}
|
||
|
||
$targetPlatform = self::platformKey((string) ($store->store_platform ?? ''));
|
||
if ($targetPlatform !== null) {
|
||
$matches = $matches->filter(
|
||
static function (object $row) use ($targetPlatform): bool {
|
||
$sourcePlatform = self::platformKey((string) ($row->source_platform ?? ''))
|
||
?? self::platformKey((string) self::sourcePlatform((string) ($row->store_name ?? '')));
|
||
|
||
return $sourcePlatform !== null && $sourcePlatform === $targetPlatform;
|
||
},
|
||
)->values();
|
||
|
||
if ($matches->isEmpty()) {
|
||
$result[(int) $store->id] = $this->emptyMatch('unmatched');
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if ($matches->count() !== 1) {
|
||
$result[(int) $store->id] = $this->emptyMatch('ambiguous');
|
||
continue;
|
||
}
|
||
|
||
$match = $matches->first();
|
||
$result[(int) $store->id] = [
|
||
'status' => 'matched',
|
||
'balance' => (float) $match->balance,
|
||
'source_store_name' => (string) $match->store_name,
|
||
'source_platform' => $match->source_platform === null ? null : (string) $match->source_platform,
|
||
];
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
public function countForDate(string $balanceDate): int
|
||
{
|
||
if (! $this->hasTable()) {
|
||
return 0;
|
||
}
|
||
|
||
return $this->connection->table(self::TABLE)
|
||
->where('balance_date', $balanceDate)
|
||
->count();
|
||
}
|
||
|
||
/**
|
||
* 取不晚于今天的最新余额日期;没有任何快照时返回 null。
|
||
*/
|
||
public function latestBalanceDate(?CarbonInterface $today = null): ?string
|
||
{
|
||
if (! $this->hasTable()) {
|
||
return null;
|
||
}
|
||
|
||
$today = ($today ?? now())->copy()->startOfDay();
|
||
$latest = $this->connection->table(self::TABLE)
|
||
->where('balance_date', '<=', $today->toDateString())
|
||
->max('balance_date');
|
||
|
||
return $latest === null ? null : substr((string) $latest, 0, 10);
|
||
}
|
||
|
||
/**
|
||
* 取最早余额日期,供页面日期筛选下限使用;没有快照时返回 null。
|
||
*/
|
||
public function earliestBalanceDate(): ?string
|
||
{
|
||
if (! $this->hasTable()) {
|
||
return null;
|
||
}
|
||
|
||
$earliest = $this->connection->table(self::TABLE)->min('balance_date');
|
||
|
||
return $earliest === null ? null : substr((string) $earliest, 0, 10);
|
||
}
|
||
|
||
public static function normalizeStoreName(string $value): string
|
||
{
|
||
$value = trim(str_replace(['-', '—', '–'], '-', $value));
|
||
$value = preg_replace(
|
||
'/^(支付宝|天猫|淘宝|阿里巴巴|1688|京东商城|京东|京喜小店|京喜|拼多多|抖音|快手|小红书|唯品会|得物|淘工厂)[\s\-_::]+/u',
|
||
'',
|
||
$value,
|
||
) ?? $value;
|
||
$value = preg_replace('/\s+/u', '', $value) ?? $value;
|
||
|
||
return mb_substr(mb_strtolower($value), 0, 255);
|
||
}
|
||
|
||
public static function sourcePlatform(string $storeName): ?string
|
||
{
|
||
if (preg_match(
|
||
'/^(支付宝|天猫|淘宝|阿里巴巴|1688|京东商城|京东|京喜小店|京喜|拼多多|抖音|快手|小红书|唯品会|得物|淘工厂)[\s\-—–_::]+/u',
|
||
trim($storeName),
|
||
$matches,
|
||
) === 1) {
|
||
return $matches[1];
|
||
}
|
||
|
||
return str_ends_with(mb_strtolower(trim($storeName)), '1688') ? '1688' : null;
|
||
}
|
||
|
||
private static function platformKey(string $platform): ?string
|
||
{
|
||
$platform = mb_strtolower(trim($platform));
|
||
foreach ([
|
||
'tmall' => ['天猫'],
|
||
'taobao' => ['淘宝'],
|
||
'1688' => ['1688', '阿里巴巴'],
|
||
'jd' => ['京东'],
|
||
'jingxi' => ['京喜'],
|
||
'pinduoduo' => ['拼多多'],
|
||
'douyin' => ['抖音', '头条放心购'],
|
||
'kuaishou' => ['快手'],
|
||
'xiaohongshu' => ['小红书'],
|
||
'vipshop' => ['唯品会'],
|
||
'dewu' => ['得物'],
|
||
'taogongchang' => ['淘工厂'],
|
||
] as $key => $aliases) {
|
||
foreach ($aliases as $alias) {
|
||
if (str_contains($platform, mb_strtolower($alias))) {
|
||
return $key;
|
||
}
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/** @return array{status:string,balance:null,source_store_name:null,source_platform:null} */
|
||
private function emptyMatch(string $status): array
|
||
{
|
||
return [
|
||
'status' => $status,
|
||
'balance' => null,
|
||
'source_store_name' => null,
|
||
'source_platform' => null,
|
||
];
|
||
}
|
||
|
||
private function hasTable(): bool
|
||
{
|
||
return Schema::connection($this->connection->getName())->hasTable(self::TABLE);
|
||
}
|
||
|
||
}
|