From be2a43c39d654d7ac0bd3d1794ae3699ee11d054 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 31 Jul 2025 14:09:13 +0200 Subject: [PATCH 1/6] Add indexes to transaction table --- src/Entity/Accounting/Transaction.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Entity/Accounting/Transaction.php b/src/Entity/Accounting/Transaction.php index 15d70d9..2de26fc 100644 --- a/src/Entity/Accounting/Transaction.php +++ b/src/Entity/Accounting/Transaction.php @@ -21,6 +21,8 @@ use Symfony\Component\Validator\Constraints as Assert; #[MapProvider(EntityMapProvider::class)] #[ORM\Table(name: 'accounting_transaction')] #[ORM\Entity(repositoryClass: TransactionRepository::class)] +#[ORM\Index(fields: ['origin'])] +#[ORM\Index(fields: ['target'])] class Transaction { use TimestampedCreationEntity; From 857706e982c833b01d08cc481384bc26d4b0a00d Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Thu, 31 Jul 2025 14:14:04 +0200 Subject: [PATCH 2/6] Small optimizations on accounting trxs aggregated code --- .../Accounting/TransactionRepository.php | 14 +++++--------- src/Service/AccountingService.php | 10 ++++++---- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/Repository/Accounting/TransactionRepository.php b/src/Repository/Accounting/TransactionRepository.php index a160df0..b44aaaf 100644 --- a/src/Repository/Accounting/TransactionRepository.php +++ b/src/Repository/Accounting/TransactionRepository.php @@ -34,19 +34,15 @@ class TransactionRepository extends ServiceEntityRepository ?\DateTimeInterface $dateEnd = null, ): array { $queryBuilder = $this->createQueryBuilder('t'); - $accountingExpr = $queryBuilder->expr(); - - if ($dateEnd === null) { - $dateEnd = new \DateTime(); - } $query = $queryBuilder - ->andWhere($accountingExpr->orX( - $accountingExpr->eq('t.origin', $accounting->getId()), - $accountingExpr->eq('t.target', $accounting->getId()), + ->andWhere($queryBuilder->expr()->orX( + 't.origin = :accountingId', + 't.target = :accountingId' )) + ->setParameter('accountingId', $accounting->getId()) ->andWhere('t.dateCreated <= :dateEnd') - ->setParameter('dateEnd', $dateEnd); + ->setParameter('dateEnd', $dateEnd ?? new \DateTime()); if ($dateStart !== null) { $query->andWhere('t.dateCreated >= :dateStart') diff --git a/src/Service/AccountingService.php b/src/Service/AccountingService.php index 6be9760..9c00bcd 100644 --- a/src/Service/AccountingService.php +++ b/src/Service/AccountingService.php @@ -30,12 +30,13 @@ class AccountingService $balance = new Money(0, $accounting->getCurrency()); $trxs = $this->transactionRepository->findByAccounting($accounting); + $accountingId = $accounting->getId(); foreach ($trxs as $transaction) { - if ($transaction->getTarget() === $accounting) { + if ($transaction->getTarget()->getId() === $accountingId) { $balance = $this->money->add($transaction->getMoney(), $balance); } - if ($transaction->getOrigin() === $accounting) { + if ($transaction->getOrigin()->getId() === $accountingId) { $balance = $this->money->substract($transaction->getMoney(), $balance); } } @@ -95,6 +96,7 @@ class AccountingService $points = []; $totalBalance = new Money(0, $accounting->getCurrency()); + $accountingId = $accounting->getId(); foreach ($period as $start) { $end = \DateTime::createFromInterface($start)->add($period->getDateInterval()); $periodTrxs = $this->getTransactionsInPeriod($trxs, $start, $end); @@ -103,11 +105,11 @@ class AccountingService foreach ($periodTrxs as $trx) { $trxMoney = $trx->getMoney(); - if ($trx->getTarget() === $accounting) { + if ($trx->getTarget()->getId() === $accountingId) { $balance = $this->money->add($trxMoney, $balance); } - if ($trx->getOrigin() === $accounting) { + if ($trx->getOrigin()->getId() === $accountingId) { $balance = $this->money->substract($trxMoney, $balance); } } From a892c449d17a886d1d4d319a50c59921465f4666 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Mon, 4 Aug 2025 12:42:17 +0200 Subject: [PATCH 3/6] Split simple Accounting and BalancedAccounting api resources --- .../Accounting/AccountingApiResource.php | 12 +----------- .../BalancedAccountingApiResource.php | 19 +++++++++++++++++++ .../Accounting/AccountingStateProvider.php | 3 ++- 3 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 src/ApiResource/Accounting/BalancedAccountingApiResource.php diff --git a/src/ApiResource/Accounting/AccountingApiResource.php b/src/ApiResource/Accounting/AccountingApiResource.php index bdcb8a9..91bd86a 100644 --- a/src/ApiResource/Accounting/AccountingApiResource.php +++ b/src/ApiResource/Accounting/AccountingApiResource.php @@ -10,14 +10,11 @@ use App\ApiResource\TipjarApiResource; use App\ApiResource\User\UserApiResource; use App\Entity\Accounting\Accounting; use App\Entity\Matchfunding\MatchCall; -use App\Entity\Money; use App\Entity\Project\Project; use App\Entity\Tipjar; use App\Entity\User\User; -use App\Mapping\Transformer\AccountingBalanceMapTransformer; use App\State\Accounting\AccountingStateProcessor; use App\State\Accounting\AccountingStateProvider; -use AutoMapper\Attribute\MapFrom; /** * v4 features an advanced economy model under the hood. @@ -34,7 +31,7 @@ use AutoMapper\Attribute\MapFrom; processor: AccountingStateProcessor::class, )] #[API\GetCollection()] -#[API\Get()] +#[API\Get(output: BalancedAccountingApiResource::class)] #[API\Patch(security: 'is_granted("ACCOUNTING_EDIT", object)')] class AccountingApiResource { @@ -46,13 +43,6 @@ class AccountingApiResource */ public string $currency; - /** - * The money currently held by the Accounting. - */ - #[MapFrom(Accounting::class, transformer: AccountingBalanceMapTransformer::class)] - #[API\ApiProperty(writable: false, security: 'is_granted("ACCOUNTING_VIEW", object)')] - public Money $balance; - #[API\ApiProperty(readable: false, writable: false)] public string $ownerClass; diff --git a/src/ApiResource/Accounting/BalancedAccountingApiResource.php b/src/ApiResource/Accounting/BalancedAccountingApiResource.php new file mode 100644 index 0000000..8af3f15 --- /dev/null +++ b/src/ApiResource/Accounting/BalancedAccountingApiResource.php @@ -0,0 +1,19 @@ +autoMapper->map($accounting, AccountingApiResource::class); + return $this->autoMapper->map($accounting, BalancedAccountingApiResource::class); } } From e3dc2ffa7571a702d40ebab21bbec1426a591687 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Mon, 4 Aug 2025 15:55:21 +0200 Subject: [PATCH 4/6] Update accounting balance calc with simplified trx data --- .../Accounting/TransactionRepository.php | 39 +++++++++++++++++++ src/Service/AccountingService.php | 16 ++++---- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/Repository/Accounting/TransactionRepository.php b/src/Repository/Accounting/TransactionRepository.php index b44aaaf..d2c70f9 100644 --- a/src/Repository/Accounting/TransactionRepository.php +++ b/src/Repository/Accounting/TransactionRepository.php @@ -54,6 +54,45 @@ class TransactionRepository extends ServiceEntityRepository return $query->getQuery()->getResult(); } + /** + * @param \DateTimeInterface|null $dateStart a date to act as lower bound of the selection + * @param \DateTimeInterface|null $dateEnd A date to act as upper bound of the selection. Current date will be used if null. + * + * @return array Simplified array data of Transactions originated from or targeting the Accounting + */ + public function findForBalanceCalc( + Accounting $accounting, + ?\DateTimeInterface $dateStart = null, + ?\DateTimeInterface $dateEnd = null, + ): array { + $queryBuilder = $this->createQueryBuilder('t'); + + $query = $queryBuilder + ->select(' + t.id, + t.money.amount, + t.money.currency, + IDENTITY(t.origin) AS origin_id, + IDENTITY(t.target) AS target_id + ') + ->andWhere($queryBuilder->expr()->orX( + 't.origin = :accountingId', + 't.target = :accountingId' + )) + ->setParameter('accountingId', $accounting->getId()) + ->andWhere('t.dateCreated <= :dateEnd') + ->setParameter('dateEnd', $dateEnd ?? new \DateTime()); + + if ($dateStart !== null) { + $query->andWhere('t.dateCreated >= :dateStart') + ->setParameter('dateStart', $dateStart); + } + + $query->orderBy('t.id', 'ASC'); + + return $query->getQuery()->getArrayResult(); + } + /** * @return Transaction[] Transactions originated from the Accounting */ diff --git a/src/Service/AccountingService.php b/src/Service/AccountingService.php index 9c00bcd..66ab530 100644 --- a/src/Service/AccountingService.php +++ b/src/Service/AccountingService.php @@ -27,17 +27,19 @@ class AccountingService return $this->wallet->getBalance($accounting); } - $balance = new Money(0, $accounting->getCurrency()); - $trxs = $this->transactionRepository->findByAccounting($accounting); + $trxs = $this->transactionRepository->findForBalanceCalc($accounting); $accountingId = $accounting->getId(); - foreach ($trxs as $transaction) { - if ($transaction->getTarget()->getId() === $accountingId) { - $balance = $this->money->add($transaction->getMoney(), $balance); + $balance = new Money(0, $accounting->getCurrency()); + foreach ($trxs as $trx) { + $transaction = new Money($trx['money.amount'], $trx['money.currency']); + + if ($trx['target_id'] === $accountingId) { + $balance = $this->money->add($transaction, $balance); } - if ($transaction->getOrigin()->getId() === $accountingId) { - $balance = $this->money->substract($transaction->getMoney(), $balance); + if ($trx['origin_id'] === $accountingId) { + $balance = $this->money->substract($transaction, $balance); } } From 6bd87eae1c5fd05af3628be553ca1cde150a2a38 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Mon, 4 Aug 2025 15:56:21 +0200 Subject: [PATCH 5/6] Split balanced accountings for single account retrieval --- .../Accounting/AccountingApiResource.php | 24 ++++---- .../BalancedAccountingApiResource.php | 60 ++++++++++++++++++- 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/ApiResource/Accounting/AccountingApiResource.php b/src/ApiResource/Accounting/AccountingApiResource.php index 91bd86a..85c3f16 100644 --- a/src/ApiResource/Accounting/AccountingApiResource.php +++ b/src/ApiResource/Accounting/AccountingApiResource.php @@ -46,6 +46,18 @@ class AccountingApiResource #[API\ApiProperty(readable: false, writable: false)] public string $ownerClass; + #[API\ApiProperty(readable: false, writable: false)] + public ?UserApiResource $user = null; + + #[API\ApiProperty(readable: false, writable: false)] + public ?ProjectApiResource $project = null; + + #[API\ApiProperty(readable: false, writable: false)] + public ?TipjarApiResource $tipjar = null; + + #[API\ApiProperty(readable: false, writable: false)] + public ?MatchCallApiResource $matchCall; + /** * The resource owning this Accounting. * @@ -66,16 +78,4 @@ class AccountingApiResource return null; } - - #[API\ApiProperty(readable: false, writable: false)] - public ?UserApiResource $user = null; - - #[API\ApiProperty(readable: false, writable: false)] - public ?ProjectApiResource $project = null; - - #[API\ApiProperty(readable: false, writable: false)] - public ?TipjarApiResource $tipjar = null; - - #[API\ApiProperty(readable: false, writable: false)] - public ?MatchCallApiResource $matchCall; } diff --git a/src/ApiResource/Accounting/BalancedAccountingApiResource.php b/src/ApiResource/Accounting/BalancedAccountingApiResource.php index 8af3f15..6d0a975 100644 --- a/src/ApiResource/Accounting/BalancedAccountingApiResource.php +++ b/src/ApiResource/Accounting/BalancedAccountingApiResource.php @@ -3,17 +3,75 @@ namespace App\ApiResource\Accounting; use ApiPlatform\Metadata as API; +use App\ApiResource\Matchfunding\MatchCallApiResource; +use App\ApiResource\Project\ProjectApiResource; +use App\ApiResource\TipjarApiResource; +use App\ApiResource\User\UserApiResource; use App\Entity\Accounting\Accounting; +use App\Entity\Matchfunding\MatchCall; use App\Entity\Money; +use App\Entity\Project\Project; +use App\Entity\Tipjar; +use App\Entity\User\User; use App\Mapping\Transformer\AccountingBalanceMapTransformer; use AutoMapper\Attribute\MapFrom; +use Symfony\Component\Serializer\Attribute\Ignore; -class BalancedAccountingApiResource extends AccountingApiResource +class BalancedAccountingApiResource { + public int $id; + + /** + * The preferred currency for monetary operations.\ + * 3-letter ISO 4217 currency code. + */ + public string $currency; + /** * The money currently held by the Accounting. */ #[MapFrom(Accounting::class, transformer: AccountingBalanceMapTransformer::class)] #[API\ApiProperty(writable: false, security: 'is_granted("ACCOUNTING_VIEW", object)')] public Money $balance; + + #[Ignore] + #[API\ApiProperty(readable: false, writable: false)] + public string $ownerClass; + + #[Ignore] + #[API\ApiProperty(readable: false, writable: false)] + public ?UserApiResource $user = null; + + #[Ignore] + #[API\ApiProperty(readable: false, writable: false)] + public ?ProjectApiResource $project = null; + + #[Ignore] + #[API\ApiProperty(readable: false, writable: false)] + public ?TipjarApiResource $tipjar = null; + + #[Ignore] + #[API\ApiProperty(readable: false, writable: false)] + public ?MatchCallApiResource $matchCall; + + /** + * The resource owning this Accounting. + * + * @return UserApiResource|ProjectApiResource|TipjarApiResource + */ + public function getOwner(): ?object + { + switch ($this->ownerClass) { + case User::class: + return $this->user; + case Project::class: + return $this->project; + case Tipjar::class: + return $this->tipjar; + case MatchCall::class: + return $this->matchCall; + } + + return null; + } } From 9c425e38ef496c39d7f169ad149ca5df685f76f3 Mon Sep 17 00:00:00 2001 From: Daniel Subiabre Date: Mon, 4 Aug 2025 15:56:34 +0200 Subject: [PATCH 6/6] Enable CECA Gateway --- src/Gateway/Gateway/CecaGateway.php | 37 ++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Gateway/Gateway/CecaGateway.php b/src/Gateway/Gateway/CecaGateway.php index 6f0771e..63d7d00 100644 --- a/src/Gateway/Gateway/CecaGateway.php +++ b/src/Gateway/Gateway/CecaGateway.php @@ -2,12 +2,47 @@ namespace App\Gateway\Gateway; +use App\Entity\Gateway\Charge; +use App\Entity\Gateway\Checkout; +use App\Gateway\ChargeType; use App\Gateway\GatewayInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; -abstract class CecaGateway implements GatewayInterface +class CecaGateway implements GatewayInterface { + public const LEGACY_MESSAGE = 'This Gateway implementation is only meant to give support to legacy-records. DO NOT USE. DO NOT IMPLEMENT.'; + public static function getName(): string { return 'tpv'; } + + public static function getSupportedChargeTypes(): array + { + return [ + ChargeType::Single, + ]; + } + + public function process(Checkout $checkout): Checkout + { + throw new \Exception(self::LEGACY_MESSAGE); + } + + public function handleRedirect(Request $request): RedirectResponse + { + throw new \Exception(self::LEGACY_MESSAGE); + } + + public function handleWebhook(Request $request): Response + { + throw new \Exception(self::LEGACY_MESSAGE); + } + + public function processRefund(Charge $charge): void + { + throw new \Exception(self::LEGACY_MESSAGE); + } }