Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Matchfunding #41

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
6 changes: 6 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ services:
tags: ['app.lib.benzina.pump.pump']
App\Library\Economy\Currency\ExchangeInterface:
tags: ['app.lib.economy.currency.exchange']
App\Matchfunding\MatchStrategy\MatchStrategyInterface:
tags: ['app.matchfunding.strategy.strategy']
AutoMapper\Provider\ProviderInterface:
tags: ['app.mapping.map_provider']

Expand Down Expand Up @@ -55,6 +57,10 @@ services:
- '%env(STRIPE_API_KEY)%'
- '%env(STRIPE_WEBHOOK_SECRET)%'

App\Matchfunding\MatchStrategy\MatchStrategyLocator:
arguments:
- !tagged 'app.matchfunding.strategy.strategy'

App\Service\Auth\AuthService:
arguments:
- '%env(APP_SECRET)%'
Expand Down
52 changes: 52 additions & 0 deletions src/ApiResource/Matchfunding/MatchCallApiResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace App\ApiResource\Matchfunding;

use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata as API;
use App\ApiResource\Accounting\AccountingApiResource;
use App\ApiResource\User\UserApiResource;
use App\Entity\Matchfunding\MatchCall;
use App\State\ApiResourceStateProcessor;
use App\State\ApiResourceStateProvider;

/**
* A MatchCall is an owned and managed event which accepts MatchCallSubmissions from Projects to receive *matchfunding* financement.
* This means any money inside a Transaction going to a Project in a MatchCall will be matched with funds from the MatchCall accounting.
* \
* \
* MatchCallSubmissions from Projects can be accepted or rejected by the managers.
* They can also choose from predefined strategies and tune them to perform the matching.
*/
#[API\ApiResource(
shortName: 'MatchCall',
stateOptions: new Options(entityClass: MatchCall::class),
provider: ApiResourceStateProvider::class,
processor: ApiResourceStateProcessor::class
)]
class MatchCallApiResource
{
#[API\ApiProperty(identifier: true, writable: false)]
public int $id;

/**
* The Accounting which holds and spends the funds for this MatchCall.
*/
#[API\ApiProperty(writable: false)]
public AccountingApiResource $accounting;

/**
* A list of Users who can modify this MatchCall.
*
* @var UserApiResource[]
*/
#[API\ApiProperty(securityPostDenormalize: 'is_granted("MATCHCALL_EDIT", object)')]
public array $managers;

/**
* A list of the MatchCallSubmissions received by this MatchCall.
*
* @var MatchCallSubmissionApiResource[]
*/
public array $matchCallSubmissions;
}
43 changes: 43 additions & 0 deletions src/ApiResource/Matchfunding/MatchCallSubmissionApiResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace App\ApiResource\Matchfunding;

use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata as API;
use App\ApiResource\Project\ProjectApiResource;
use App\Entity\Matchfunding\MatchCallSubmission;
use App\Entity\Matchfunding\MatchCallSubmissionStatus;
use App\State\ApiResourceStateProcessor;
use App\State\ApiResourceStateProvider;

/**
* MatchCallSubmissions represent the will of a Project to be held under a MatchCall and receive matchfunding financement.
*/
#[API\ApiResource(
shortName: 'MatchCallSubmission',
stateOptions: new Options(entityClass: MatchCallSubmission::class),
provider: ApiResourceStateProvider::class,
processor: ApiResourceStateProcessor::class
)]
class MatchCallSubmissionApiResource
{
#[API\ApiProperty(identifier: true, writable: false)]
public int $id;

/**
* The MatchCall to which this MatchCallSubmission belongs to.
*/
public MatchCallApiResource $matchCall;

/**
* The Project that applied for the MatchCall.
*/
public ProjectApiResource $project;

/**
* The status of the Project's application for the MatchCall.\
* Only MatchCallSubmissions with an status `accepted` will receive matchfunding.
*/
#[API\ApiProperty(securityPostDenormalize: 'is_granted("MATCHCALLSUBMISSION_EDIT", object)')]
public MatchCallSubmissionStatus $status;
}
20 changes: 20 additions & 0 deletions src/ApiResource/Matchfunding/MatchStrategyApiResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\ApiResource\Matchfunding;

use ApiPlatform\Metadata as API;
use App\State\Matchfunding\MatchStrategyStateProvider;

/**
* A MatchStrategy is a predefined code implementation for matching funds in Transactions under a MatchCall.
* MatchStrategies can be chosen by the managers in a MatchCall and their behaviour fine-tuned.
*/
#[API\ApiResource(
shortName: 'MatchStrategy',
provider: MatchStrategyStateProvider::class
)]
class MatchStrategyApiResource
{
#[API\ApiProperty(identifier: true, writable: false)]
public string $name;
}
25 changes: 25 additions & 0 deletions src/Entity/Accounting/Accounting.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Entity\Accounting;

use App\Entity\Interface\AccountingOwnerInterface;
use App\Entity\Matchfunding\MatchCall;
use App\Entity\Project\Project;
use App\Entity\Tipjar;
use App\Entity\User\User;
Expand Down Expand Up @@ -43,6 +44,9 @@ class Accounting
#[ORM\OneToOne(mappedBy: 'accounting', cascade: ['persist'])]
private ?Tipjar $tipjar = null;

#[ORM\OneToOne(mappedBy: 'accounting', cascade: ['persist'])]
private ?MatchCall $matchCall = null;

/**
* Create a new Accounting entity instance for the given owner.
*/
Expand Down Expand Up @@ -101,6 +105,8 @@ public function getOwner(): ?AccountingOwnerInterface
return $this->getProject();
case Tipjar::class:
return $this->getTipjar();
case MatchCall::class:
return $this->getMatchCall();
}

return null;
Expand All @@ -121,6 +127,8 @@ public function setOwner(?AccountingOwnerInterface $owner): static
return $this->setProject($owner);
case Tipjar::class:
return $this->setTipjar($owner);
case MatchCall::class:
return $this->setMatchCall($owner);
}

return $this;
Expand Down Expand Up @@ -191,4 +199,21 @@ public function setTipjar(?Tipjar $tipjar): static

return $this;
}

public function getMatchCall(): ?MatchCall
{
return $this->matchCall;
}

public function setMatchCall(MatchCall $matchCall): static
{
// set the owning side of the relation if necessary
if ($matchCall->getAccounting() !== $this) {
$matchCall->setAccounting($this);
}

$this->matchCall = $matchCall;

return $this;
}
}
129 changes: 129 additions & 0 deletions src/Entity/Matchfunding/MatchCall.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

namespace App\Entity\Matchfunding;

use App\Entity\Accounting\Accounting;
use App\Entity\Interface\AccountingOwnerInterface;
use App\Entity\User\User;
use App\Repository\Matchfunding\MatchCallRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: MatchCallRepository::class)]
class MatchCall implements AccountingOwnerInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;

#[ORM\OneToOne(inversedBy: 'matchCall', cascade: ['persist'])]
#[ORM\JoinColumn(nullable: false)]
private ?Accounting $accounting = null;

/**
* @var Collection<int, User>
*/
#[ORM\ManyToMany(targetEntity: User::class)]
private Collection $managers;

/**
* @var Collection<int, MatchCallSubmission>
*/
#[ORM\OneToMany(mappedBy: 'matchCall', targetEntity: MatchCallSubmission::class)]
private Collection $matchCallSubmissions;

#[ORM\Column(length: 255)]
private ?string $strategyName = null;

public function __construct()
{
$this->accounting = Accounting::of($this);
$this->managers = new ArrayCollection();
$this->matchCallSubmissions = new ArrayCollection();
}

public function getId(): ?int
{
return $this->id;
}

public function getAccounting(): ?Accounting
{
return $this->accounting;
}

public function setAccounting(Accounting $accounting): static
{
$this->accounting = $accounting;

return $this;
}

/**
* @return Collection<int, User>
*/
public function getManagers(): Collection
{
return $this->managers;
}

public function addManager(User $manager): static
{
if (!$this->managers->contains($manager)) {
$this->managers->add($manager);
}

return $this;
}

public function removeManager(User $manager): static
{
$this->managers->removeElement($manager);

return $this;
}

/**
* @return Collection<int, MatchCallSubmission>
*/
public function getMatchCallSubmissions(): Collection
{
return $this->matchCallSubmissions;
}

public function addMatchCallSubmission(MatchCallSubmission $submission): static
{
if (!$this->matchCallSubmissions->contains($submission)) {
$this->matchCallSubmissions->add($submission);
$submission->setMatchCall($this);
}

return $this;
}

public function removeMatchCallSubmission(MatchCallSubmission $submission): static
{
if ($this->matchCallSubmissions->removeElement($submission)) {
// set the owning side to null (unless already changed)
if ($submission->getMatchCall() === $this) {
$submission->setMatchCall(null);
}
}

return $this;
}

public function getStrategyName(): ?string
{
return $this->strategyName;
}

public function setStrategyName(string $strategyName): static
{
$this->strategyName = $strategyName;

return $this;
}
}
68 changes: 68 additions & 0 deletions src/Entity/Matchfunding/MatchCallSubmission.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace App\Entity\Matchfunding;

use App\Entity\Project\Project;
use App\Repository\Matchfunding\MatchCallSubmissionRepository;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: MatchCallSubmissionRepository::class)]
class MatchCallSubmission
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;

#[ORM\ManyToOne(inversedBy: 'matchCallSubmissions')]
#[ORM\JoinColumn(nullable: false)]
private ?MatchCall $matchCall = null;

#[ORM\ManyToOne(inversedBy: 'matchCallSubmissions')]
#[ORM\JoinColumn(nullable: false)]
private ?Project $project = null;

#[ORM\Column(enumType: MatchCallSubmissionStatus::class)]
private ?MatchCallSubmissionStatus $status = null;

public function getId(): ?int
{
return $this->id;
}

public function getMatchCall(): ?MatchCall
{
return $this->matchCall;
}

public function setMatchCall(?MatchCall $matchCall): static
{
$this->matchCall = $matchCall;

return $this;
}

public function getProject(): ?Project
{
return $this->project;
}

public function setProject(?Project $project): static
{
$this->project = $project;

return $this;
}

public function getStatus(): ?MatchCallSubmissionStatus
{
return $this->status;
}

public function setStatus(MatchCallSubmissionStatus $status): static
{
$this->status = $status;

return $this;
}
}
Loading
Loading