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; } }