You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
5.4 KiB
200 lines
5.4 KiB
<?php
|
|
|
|
/*
|
|
* Transaction.php
|
|
* Copyright (c) 2021 james@firefly-iii.org
|
|
*
|
|
* This file is part of the Firefly III Data Importer
|
|
* (https://github.com/firefly-iii/data-importer).
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\SimpleFIN\Model;
|
|
|
|
use InvalidArgumentException;
|
|
use Carbon\Carbon;
|
|
|
|
/**
|
|
* Class Transaction
|
|
*/
|
|
class Transaction
|
|
{
|
|
private readonly string $id;
|
|
private readonly int $posted;
|
|
private readonly string $amount;
|
|
private readonly string $description;
|
|
private readonly ?int $transactedAt;
|
|
private readonly bool $pending;
|
|
private array $extra;
|
|
|
|
public function __construct(array $data)
|
|
{
|
|
$this->validateRequiredFields($data);
|
|
|
|
$this->id = $data['id'];
|
|
$this->posted = $data['posted'];
|
|
$this->amount = $data['amount'];
|
|
$this->description = $data['description'];
|
|
$this->transactedAt = $data['transacted_at'] ?? null;
|
|
$this->pending = $data['pending'] ?? false;
|
|
$this->extra = $data['extra'] ?? [];
|
|
}
|
|
|
|
public static function fromArray(array $data): self
|
|
{
|
|
return new self($data);
|
|
}
|
|
|
|
public function getId(): string
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
public function getPosted(): int
|
|
{
|
|
return $this->posted;
|
|
}
|
|
|
|
public function getPostedAsCarbon(): ?Carbon
|
|
{
|
|
return 0 === $this->posted ? null : Carbon::createFromTimestamp($this->posted);
|
|
}
|
|
|
|
public function getAmount(): string
|
|
{
|
|
return $this->amount;
|
|
}
|
|
|
|
public function getAmountAsFloat(): float
|
|
{
|
|
return (float) $this->amount;
|
|
}
|
|
|
|
public function isDeposit(): bool
|
|
{
|
|
return $this->getAmountAsFloat() >= 0;
|
|
}
|
|
|
|
public function isWithdrawal(): bool
|
|
{
|
|
return $this->getAmountAsFloat() < 0;
|
|
}
|
|
|
|
public function getAbsoluteAmount(): float
|
|
{
|
|
return abs($this->getAmountAsFloat());
|
|
}
|
|
|
|
public function getDescription(): string
|
|
{
|
|
return $this->description;
|
|
}
|
|
|
|
public function getTransactedAt(): ?int
|
|
{
|
|
return $this->transactedAt;
|
|
}
|
|
|
|
public function getTransactedAtAsCarbon(): ?Carbon
|
|
{
|
|
return $this->transactedAt !== null && $this->transactedAt !== 0 ? Carbon::createFromTimestamp($this->transactedAt) : null;
|
|
}
|
|
|
|
public function isPending(): bool
|
|
{
|
|
return $this->pending;
|
|
}
|
|
|
|
public function isPosted(): bool
|
|
{
|
|
return !$this->pending && $this->posted > 0;
|
|
}
|
|
|
|
public function getExtra(): array
|
|
{
|
|
return $this->extra;
|
|
}
|
|
|
|
public function getExtraValue(string $key): mixed
|
|
{
|
|
return $this->extra[$key] ?? null;
|
|
}
|
|
|
|
public function hasExtra(string $key): bool
|
|
{
|
|
return array_key_exists($key, $this->extra);
|
|
}
|
|
|
|
public function getEffectiveDate(): Carbon
|
|
{
|
|
// Use transacted_at if available, otherwise fall back to posted date
|
|
if ($this->transactedAt && $this->transactedAt > 0) {
|
|
return Carbon::createFromTimestamp($this->transactedAt);
|
|
}
|
|
|
|
if ($this->posted > 0) {
|
|
return Carbon::createFromTimestamp($this->posted);
|
|
}
|
|
|
|
// If both are 0 or invalid, return current time
|
|
return Carbon::now();
|
|
}
|
|
|
|
public function toArray(): array
|
|
{
|
|
return [
|
|
'id' => $this->id,
|
|
'posted' => $this->posted,
|
|
'amount' => $this->amount,
|
|
'description' => $this->description,
|
|
'transacted_at' => $this->transactedAt,
|
|
'pending' => $this->pending,
|
|
'extra' => $this->extra,
|
|
];
|
|
}
|
|
|
|
private function validateRequiredFields(array $data): void
|
|
{
|
|
$requiredFields = ['id', 'posted', 'amount', 'description'];
|
|
|
|
foreach ($requiredFields as $field) {
|
|
if (!array_key_exists($field, $data)) {
|
|
throw new InvalidArgumentException(sprintf('Missing required field: %s', $field));
|
|
}
|
|
}
|
|
|
|
// Validate posted is numeric
|
|
if (!is_numeric($data['posted'])) {
|
|
throw new InvalidArgumentException('Posted date must be a numeric timestamp');
|
|
}
|
|
|
|
// Validate amount is numeric string
|
|
if (!is_numeric($data['amount'])) {
|
|
throw new InvalidArgumentException('Amount must be a numeric string');
|
|
}
|
|
|
|
// Validate transacted_at if present
|
|
if (isset($data['transacted_at']) && !is_numeric($data['transacted_at'])) {
|
|
throw new InvalidArgumentException('Transacted at must be a numeric timestamp');
|
|
}
|
|
|
|
// Validate pending if present
|
|
if (isset($data['pending']) && !is_bool($data['pending'])) {
|
|
throw new InvalidArgumentException('Pending must be a boolean');
|
|
}
|
|
}
|
|
}
|