diff --git a/src/ApiResource/Accounting/AccountingApiResource.php b/src/ApiResource/Accounting/AccountingApiResource.php index bdcb8a9..85c3f16 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,16 +43,21 @@ 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; + #[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. * @@ -76,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 new file mode 100644 index 0000000..6d0a975 --- /dev/null +++ b/src/ApiResource/Accounting/BalancedAccountingApiResource.php @@ -0,0 +1,77 @@ +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; + } +} 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; 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); + } } diff --git a/src/Repository/Accounting/TransactionRepository.php b/src/Repository/Accounting/TransactionRepository.php index a160df0..d2c70f9 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') @@ -58,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 6be9760..66ab530 100644 --- a/src/Service/AccountingService.php +++ b/src/Service/AccountingService.php @@ -27,16 +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); - foreach ($trxs as $transaction) { - if ($transaction->getTarget() === $accounting) { - $balance = $this->money->add($transaction->getMoney(), $balance); + $accountingId = $accounting->getId(); + $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() === $accounting) { - $balance = $this->money->substract($transaction->getMoney(), $balance); + if ($trx['origin_id'] === $accountingId) { + $balance = $this->money->substract($transaction, $balance); } } @@ -95,6 +98,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 +107,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); } } diff --git a/src/State/Accounting/AccountingStateProvider.php b/src/State/Accounting/AccountingStateProvider.php index 9543437..a993d48 100644 --- a/src/State/Accounting/AccountingStateProvider.php +++ b/src/State/Accounting/AccountingStateProvider.php @@ -9,6 +9,7 @@ use ApiPlatform\Metadata\Operation; use ApiPlatform\State\Pagination\TraversablePaginator; use ApiPlatform\State\ProviderInterface; use App\ApiResource\Accounting\AccountingApiResource; +use App\ApiResource\Accounting\BalancedAccountingApiResource; use App\Mapping\AutoMapper; use App\Service\AccountingService; use Symfony\Component\DependencyInjection\Attribute\Autowire; @@ -48,6 +49,6 @@ class AccountingStateProvider implements ProviderInterface return null; } - return $this->autoMapper->map($accounting, AccountingApiResource::class); + return $this->autoMapper->map($accounting, BalancedAccountingApiResource::class); } }