TASK-40: Add ability to lock-up clients
This commit is contained in:
parent
85a6212ef5
commit
5cf26579fc
@ -3,8 +3,9 @@ Feature:
|
|||||||
|
|
||||||
Background:
|
Background:
|
||||||
Given there exist following clients:
|
Given there exist following clients:
|
||||||
| clientId | name | initialBalance | currentBalance |
|
| clientId | name | initialBalance | currentBalance | locked |
|
||||||
| 018edd2e-894a-78d7-b10c-16e05ca933a3 | Kacper | 10000 | 200000 |
|
| 018edd2e-894a-78d7-b10c-16e05ca933a3 | Kacper | 10000 | 200000 | no |
|
||||||
|
| 018ede1e-a587-76a8-88f3-23987a8dade9 | Jacek | 10000 | 60000000 | yes |
|
||||||
|
|
||||||
Scenario: Frontend is able to send new orders
|
Scenario: Frontend is able to send new orders
|
||||||
Given the request has the following body:
|
Given the request has the following body:
|
||||||
@ -52,3 +53,21 @@ Feature:
|
|||||||
"""
|
"""
|
||||||
When I send a POST request to "/orders"
|
When I send a POST request to "/orders"
|
||||||
Then the response status should be 422
|
Then the response status should be 422
|
||||||
|
|
||||||
|
Scenario: Locked clients should not be allowed
|
||||||
|
Given the request has the following body:
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"orderId": "018eddae-ff52-7813-9f88-ada9e61a76f3",
|
||||||
|
"clientId": "018ede1e-a587-76a8-88f3-23987a8dade9",
|
||||||
|
"products": [
|
||||||
|
{ "productId": "p1", "quantity": 2, "price": 20.0, "weight": 100 },
|
||||||
|
{ "productId": "p2", "quantity": 1, "price": 100.0, "weight": 200 },
|
||||||
|
{ "productId": "p3", "quantity": 5, "price": 200.0, "weight": 100 },
|
||||||
|
{ "productId": "p4", "quantity": 1, "price": 50.0, "weight": 400 },
|
||||||
|
{ "productId": "p5", "quantity": 10, "price": 10.0, "weight": 100 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
When I send a POST request to "/orders"
|
||||||
|
Then the response status should be 403
|
||||||
|
@ -3,8 +3,9 @@ Feature:
|
|||||||
|
|
||||||
Background:
|
Background:
|
||||||
Given there exist following clients:
|
Given there exist following clients:
|
||||||
| clientId | name | initialBalance | currentBalance |
|
| clientId | name | initialBalance | currentBalance | locked |
|
||||||
| 018edd2e-894a-78d7-b10c-16e05ca933a3 | Kacper | 10000 | 5000 |
|
| 018edd2e-894a-78d7-b10c-16e05ca933a3 | Kacper | 10000 | 5000 | no |
|
||||||
|
| 018ede1e-a587-76a8-88f3-23987a8dade9 | Jacek | 10000 | 60000000 | yes |
|
||||||
|
|
||||||
Scenario: CRM is able to create new clients
|
Scenario: CRM is able to create new clients
|
||||||
Given the request has the following body:
|
Given the request has the following body:
|
||||||
@ -43,3 +44,25 @@ Feature:
|
|||||||
When I send a POST request to "/clients"
|
When I send a POST request to "/clients"
|
||||||
Then the response status should be 409
|
Then the response status should be 409
|
||||||
And client with id "018edd2e-894a-78d7-b10c-16e05ca933a3" should exist
|
And client with id "018edd2e-894a-78d7-b10c-16e05ca933a3" should exist
|
||||||
|
|
||||||
|
Scenario: CRM is able to lock existing client
|
||||||
|
Given the request has the following body:
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"lock": true
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
When I send a POST request to "/clients/018edd2e-894a-78d7-b10c-16e05ca933a3/_lock"
|
||||||
|
Then the response status should be 200
|
||||||
|
And client with id "018edd2e-894a-78d7-b10c-16e05ca933a3" should be locked
|
||||||
|
|
||||||
|
Scenario: CRM is able to unlock existing client
|
||||||
|
Given the request has the following body:
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"lock": false
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
When I send a POST request to "/clients/018ede1e-a587-76a8-88f3-23987a8dade9/_lock"
|
||||||
|
Then the response status should be 200
|
||||||
|
And client with id "018ede1e-a587-76a8-88f3-23987a8dade9" should not be locked
|
||||||
|
8
src/Contract/LockClientRequestDto.php
Normal file
8
src/Contract/LockClientRequestDto.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Contract;
|
||||||
|
|
||||||
|
readonly class LockClientRequestDto
|
||||||
|
{
|
||||||
|
public function __construct(public bool $lock) {}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Contract\ClientDto;
|
use App\Contract\ClientDto;
|
||||||
|
use App\Contract\LockClientRequestDto;
|
||||||
use App\Contract\TopUpRequestDto;
|
use App\Contract\TopUpRequestDto;
|
||||||
use App\Entity\Client;
|
use App\Entity\Client;
|
||||||
use App\Exception\ClientAlreadyExistsException;
|
use App\Exception\ClientAlreadyExistsException;
|
||||||
@ -49,4 +50,18 @@ readonly class ClientController
|
|||||||
|
|
||||||
return new Response();
|
return new Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route(path: '/{client}/_lock', name: 'lock', methods: ['POST'])]
|
||||||
|
public function lock(
|
||||||
|
#[MapRequestPayload] LockClientRequestDto $lockClientRequestDto,
|
||||||
|
Client $client,
|
||||||
|
): Response {
|
||||||
|
if ($lockClientRequestDto->lock) {
|
||||||
|
$this->clientService->lock($client);
|
||||||
|
} else {
|
||||||
|
$this->clientService->unlock($client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ namespace App\Controller;
|
|||||||
|
|
||||||
use App\Aggregate\AcceptOrderCommand;
|
use App\Aggregate\AcceptOrderCommand;
|
||||||
use App\Contract\Order;
|
use App\Contract\Order;
|
||||||
|
use App\Exception\ClientLockedException;
|
||||||
use App\Exception\ClientNotExistsException;
|
use App\Exception\ClientNotExistsException;
|
||||||
use App\Service\ClientService;
|
use App\Service\ClientService;
|
||||||
use App\Service\OrderService;
|
use App\Service\OrderService;
|
||||||
@ -39,6 +40,8 @@ readonly class OrderController
|
|||||||
return new Response();
|
return new Response();
|
||||||
} catch (ClientNotExistsException) {
|
} catch (ClientNotExistsException) {
|
||||||
return new Response(status: Response::HTTP_UNPROCESSABLE_ENTITY);
|
return new Response(status: Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||||
|
} catch (ClientLockedException) {
|
||||||
|
return new Response(status: Response::HTTP_FORBIDDEN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,9 @@ class Client
|
|||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?int $currentBalance = null,
|
private ?int $currentBalance = null,
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private ?\DateTimeImmutable $lockedAt = null,
|
||||||
) {
|
) {
|
||||||
$this->currentBalance ??= $this->initialBalance;
|
$this->currentBalance ??= $this->initialBalance;
|
||||||
}
|
}
|
||||||
@ -66,4 +69,19 @@ class Client
|
|||||||
{
|
{
|
||||||
$this->currentBalance += $amount;
|
$this->currentBalance += $amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setLockedAt(?\DateTimeImmutable $dateTime)
|
||||||
|
{
|
||||||
|
$this->lockedAt = $dateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLockedAt(): ?\DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->lockedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isLocked(): bool
|
||||||
|
{
|
||||||
|
return $this->lockedAt !== null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
7
src/Exception/ClientLockedException.php
Normal file
7
src/Exception/ClientLockedException.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exception;
|
||||||
|
|
||||||
|
class ClientLockedException extends \LogicException
|
||||||
|
{
|
||||||
|
}
|
@ -6,12 +6,14 @@ use App\Entity\Client;
|
|||||||
use App\Exception\ClientAlreadyExistsException;
|
use App\Exception\ClientAlreadyExistsException;
|
||||||
use App\Exception\ClientNotExistsException;
|
use App\Exception\ClientNotExistsException;
|
||||||
use App\Repository\ClientRepository;
|
use App\Repository\ClientRepository;
|
||||||
|
use Psr\Clock\ClockInterface;
|
||||||
use Symfony\Component\Uid\Uuid;
|
use Symfony\Component\Uid\Uuid;
|
||||||
|
|
||||||
class ClientService
|
class ClientService
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public readonly ClientRepository $clientRepository
|
public readonly ClientRepository $clientRepository,
|
||||||
|
public readonly ClockInterface $clock,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,17 +46,31 @@ class ClientService
|
|||||||
return $this->clientRepository->save($client);
|
return $this->clientRepository->save($client);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deduceFromBalance(Client $client, int $amount)
|
public function deduceFromBalance(Client $client, int $amount): void
|
||||||
{
|
{
|
||||||
$client->deduceFromBalance($amount);
|
$client->deduceFromBalance($amount);
|
||||||
|
|
||||||
$this->clientRepository->save($client);
|
$this->clientRepository->save($client);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function topUpBalance(Client $client, int $amount)
|
public function topUpBalance(Client $client, int $amount): void
|
||||||
{
|
{
|
||||||
$client->topUpBalance($amount);
|
$client->topUpBalance($amount);
|
||||||
|
|
||||||
$this->clientRepository->save($client);
|
$this->clientRepository->save($client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function lock(Client $client): void
|
||||||
|
{
|
||||||
|
$client->setLockedAt($this->clock->now());
|
||||||
|
|
||||||
|
$this->clientRepository->save($client);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unlock(Client $client): void
|
||||||
|
{
|
||||||
|
$client->setLockedAt(null);
|
||||||
|
|
||||||
|
$this->clientRepository->save($client);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace App\Service;
|
namespace App\Service;
|
||||||
|
|
||||||
use App\Aggregate\AcceptOrderCommand;
|
use App\Aggregate\AcceptOrderCommand;
|
||||||
|
use App\Exception\ClientLockedException;
|
||||||
use Symfony\Component\Serializer\SerializerInterface;
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
|
||||||
@ -16,8 +17,15 @@ readonly class OrderService
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \App\Exception\ClientLockedException
|
||||||
|
*/
|
||||||
public function acceptOrder(AcceptOrderCommand $command)
|
public function acceptOrder(AcceptOrderCommand $command)
|
||||||
{
|
{
|
||||||
|
if ($command->client->isLocked()) {
|
||||||
|
throw new ClientLockedException(sprintf("Client '%s' is locked.", $command->client->getName()));
|
||||||
|
}
|
||||||
|
|
||||||
$this->crmClient->request('POST', '/order', [
|
$this->crmClient->request('POST', '/order', [
|
||||||
'body' => $this->serializer->serialize($command->order, format: 'json')
|
'body' => $this->serializer->serialize($command->order, format: 'json')
|
||||||
]);
|
]);
|
||||||
|
@ -47,6 +47,7 @@ class ClientsContext implements Context
|
|||||||
name: $row['name'],
|
name: $row['name'],
|
||||||
initialBalance: intval($row['initialBalance']),
|
initialBalance: intval($row['initialBalance']),
|
||||||
currentBalance: intval($row['currentBalance']),
|
currentBalance: intval($row['currentBalance']),
|
||||||
|
lockedAt: $row['locked'] === 'yes' ? new \DateTimeImmutable() : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->clientRepository->save($client);
|
$this->clientRepository->save($client);
|
||||||
@ -69,6 +70,22 @@ class ClientsContext implements Context
|
|||||||
Assert::assertNotNull($this->getClient($clientId));
|
Assert::assertNotNull($this->getClient($clientId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^client with id "([^"]+)" should be locked$/
|
||||||
|
*/
|
||||||
|
public function clientWithIdIsLocked(string $clientId)
|
||||||
|
{
|
||||||
|
Assert::assertTrue($this->getClient($clientId)->isLocked());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^client with id "([^"]+)" should not be locked$/
|
||||||
|
*/
|
||||||
|
public function clientWithIdIsNotLocked(string $clientId)
|
||||||
|
{
|
||||||
|
Assert::assertFalse($this->getClient($clientId)->isLocked());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Given /^client with id "([^"]+)" should have balance of (\d+)$/
|
* @Given /^client with id "([^"]+)" should have balance of (\d+)$/
|
||||||
*/
|
*/
|
||||||
@ -76,7 +93,7 @@ class ClientsContext implements Context
|
|||||||
{
|
{
|
||||||
$client = $this->getClient($clientId);
|
$client = $this->getClient($clientId);
|
||||||
|
|
||||||
Assert::assertEquals($client->getBalance(), $balance);
|
Assert::assertEquals($balance, $client->getBalance());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user