CaiWuXiTong/modules/EcommerceFinance/Repositories/VipshopMpOpeningBalanceRepository.php
qiuxiang 378cf52fa7 feat: 增加唯品会MP账户月度期初余额
从唯品会后台账单余额表导入店铺月末余额,7月按期初加收入减支出滚动,后续月份自动承接上月期末;新增余额快照表、导入命令和回归测试。
2026-11-06 17:57:53 +08:00

87 lines
3.3 KiB
PHP

<?php
namespace Modules\EcommerceFinance\Repositories;
use Carbon\CarbonImmutable;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\DatabaseManager;
use Modules\EcommerceFinance\Services\VipshopMpOpeningBalanceImporter;
class VipshopMpOpeningBalanceRepository
{
private readonly ConnectionInterface $mirror;
private readonly ConnectionInterface $bills;
public function __construct(DatabaseManager $databases)
{
$this->mirror = $databases->connection(
(string) config('upstream_mirrors.connection', 'business_mirror'),
);
$this->bills = $databases->connection();
}
/** @return array{balance:float,date:?string,source:string} */
public function openingBalance(?string $storeName, ?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 ['balance' => 0.0, 'date' => null, 'source' => 'none'];
}
if (! $this->mirror->getSchemaBuilder()->hasTable(VipshopMpOpeningBalanceImporter::TABLE)) {
return ['balance' => 0.0, 'date' => null, 'source' => 'none'];
}
$store = trim((string) $storeName);
$snapshot = $this->mirror->table(VipshopMpOpeningBalanceImporter::TABLE)
->where('period_end', '2026-06-30')
->when($store !== '', static fn ($query) => $query->where('store_name', $store));
if ((clone $snapshot)->doesntExist()) {
return ['balance' => 0.0, 'date' => null, 'source' => 'none'];
}
$balance = round((float) (clone $snapshot)->sum('balance'), 2);
if ($start->gt($baselineMonth)) {
$movement = $this->bills->table('ec_account_bills')
->where('company_id', (int) (company_id() ?: 1))
->where('bill_type', 'vipshop')
->whereNull('deleted_at')
->where('payload->settle_period', '>=', '2026-07')
->where('payload->settle_period', '<', $start->format('Y-m'))
->when($store !== '', static fn ($query) => $query->where('store_name', $store))
->selectRaw('COALESCE(SUM(income), 0) AS income, COALESCE(SUM(expense), 0) AS expense')
->first();
$balance = round(
$balance
+ (float) ($movement->income ?? 0)
- (float) ($movement->expense ?? 0),
2,
);
}
return [
'balance' => $balance,
'date' => $start->subDay()->format('Y-m-d'),
'source' => $start->equalTo($baselineMonth) ? 'june_ending' : 'previous_period',
];
}
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;
}
}