mirror of
https://gitlab.com/izyim/prototypes/ddd-example.git
synced 2026-08-19 04:24:04 +00:00
company module
This commit is contained in:
@@ -186,6 +186,7 @@ return [
|
||||
* Domain Service Providers...
|
||||
*/
|
||||
\Src\Zone\User\Application\Providers\UserServiceProvider::class,
|
||||
\Src\Zone\Company\Application\Providers\CompanyServiceProvider::class,
|
||||
|
||||
/*
|
||||
* Package Service Providers...
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('companies', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('fiscal_name');
|
||||
$table->string('social_name');
|
||||
$table->string('vat');
|
||||
$table->boolean('is_active')->default(1);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('companies');
|
||||
}
|
||||
};
|
||||
@@ -39,6 +39,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||
->group(function() {
|
||||
require base_path('src/Auth/Presentation/HTTP/routes.php');
|
||||
require base_path('src/Zone/User/Presentation/HTTP/routes.php');
|
||||
require base_path('src/Zone/Company/Presentation/HTTP/routes.php');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Application\Exceptions;
|
||||
|
||||
final class VatAlreadyUsedException extends \DomainException
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('Vat is already used');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Application\Mappers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Src\Zone\Company\Domain\Model\Company;
|
||||
use Src\Zone\Company\Domain\Model\ValueObjects\FiscalName;
|
||||
use Src\Zone\Company\Domain\Model\ValueObjects\SocialName;
|
||||
use Src\Zone\Company\Domain\Model\ValueObjects\Vat;
|
||||
use Src\Zone\Company\Infrastructure\EloquentModels\CompanyEloquentModel;
|
||||
|
||||
class CompanyMapper
|
||||
{
|
||||
public static function fromRequest(Request $request, ?int $company_id = null): Company
|
||||
{
|
||||
return new Company(
|
||||
id: $company_id,
|
||||
fiscal_name: new FiscalName($request->input('fiscal_name')),
|
||||
social_name: new SocialName($request->input('social_name')),
|
||||
vat: new Vat($request->input('vat')),
|
||||
is_active: $request->input('is_active'),
|
||||
);
|
||||
}
|
||||
|
||||
public static function fromEloquent(CompanyEloquentModel $companyEloquent): Company
|
||||
{
|
||||
return new Company(
|
||||
id: $companyEloquent->id,
|
||||
fiscal_name: new FiscalName($companyEloquent->fiscal_name),
|
||||
social_name: new SocialName($companyEloquent->social_name),
|
||||
vat: new Vat($companyEloquent->vat),
|
||||
is_active: $companyEloquent->is_active,
|
||||
);
|
||||
}
|
||||
|
||||
public static function toEloquent(Company $company): CompanyEloquentModel
|
||||
{
|
||||
$companyEloquent = new CompanyEloquentModel();
|
||||
if ($company->id) {
|
||||
$companyEloquent = CompanyEloquentModel::query()->find($company->id);
|
||||
}
|
||||
$companyEloquent->fiscal_name = $company->fiscal_name;
|
||||
$companyEloquent->social_name = $company->social_name;
|
||||
$companyEloquent->vat = $company->vat;
|
||||
$companyEloquent->is_active = $company->is_active;
|
||||
return $companyEloquent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Application\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class CompanyServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
$this->app->bind(
|
||||
\Src\Zone\Company\Domain\Repositories\CompanyRepositoryInterface::class,
|
||||
\Src\Zone\Company\Application\Repositories\Eloquent\CompanyRepository::class
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Application\Repositories\Eloquent;
|
||||
|
||||
use Src\Zone\Company\Application\Mappers\CompanyMapper;
|
||||
use Src\Zone\Company\Domain\Model\Company;
|
||||
use Src\Zone\Company\Domain\Repositories\CompanyRepositoryInterface;
|
||||
use Src\Zone\Company\Infrastructure\EloquentModels\CompanyEloquentModel;
|
||||
|
||||
class CompanyRepository implements CompanyRepositoryInterface
|
||||
{
|
||||
public function findAll(): array
|
||||
{
|
||||
$companies = [];
|
||||
foreach (CompanyEloquentModel::all() as $companyEloquent) {
|
||||
$companies[] = CompanyMapper::fromEloquent($companyEloquent);
|
||||
}
|
||||
return $companies;
|
||||
}
|
||||
public function findById(string $id): Company
|
||||
{
|
||||
$companyEloquent = CompanyEloquentModel::query()->findOrFail($id);
|
||||
return CompanyMapper::fromEloquent($companyEloquent);
|
||||
}
|
||||
public function findByVat(string $vat): Company
|
||||
{
|
||||
$companyEloquent = CompanyEloquentModel::query()->where('vat', $vat)->firstOrFail();
|
||||
return CompanyMapper::fromEloquent($companyEloquent);
|
||||
}
|
||||
|
||||
public function store(Company $company): Company
|
||||
{
|
||||
$companyEloquent = CompanyMapper::toEloquent($company);
|
||||
$companyEloquent->save();
|
||||
|
||||
return CompanyMapper::fromEloquent($companyEloquent);
|
||||
}
|
||||
public function update(Company $company): void
|
||||
{
|
||||
$companyArray = $company->toArray();
|
||||
$companyEloquent = CompanyEloquentModel::query()->findOrFail($company->id);
|
||||
$companyEloquent->fill($companyArray);
|
||||
$companyEloquent->save();
|
||||
}
|
||||
public function delete(int $company_id): void
|
||||
{
|
||||
$companyEloquent = CompanyEloquentModel::query()->findOrFail($company_id);
|
||||
$companyEloquent->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Application\UseCases\Commands;
|
||||
|
||||
use Src\Zone\Company\Domain\Policies\CompanyPolicy;
|
||||
use Src\Zone\Company\Domain\Repositories\CompanyRepositoryInterface;
|
||||
use Src\Common\Domain\CommandInterface;
|
||||
|
||||
class DestroyCompanyCommand implements CommandInterface
|
||||
{
|
||||
private CompanyRepositoryInterface $repository;
|
||||
private CompanyPolicy $policy;
|
||||
|
||||
public function __construct(
|
||||
private readonly int $company_id
|
||||
)
|
||||
{
|
||||
$this->repository = app()->make(CompanyRepositoryInterface::class);
|
||||
$this->policy = new CompanyPolicy();
|
||||
}
|
||||
|
||||
public function execute(): void
|
||||
{
|
||||
authorize('delete', $this->policy);
|
||||
$this->repository->delete($this->company_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Application\UseCases\Commands;
|
||||
|
||||
use Src\Zone\Company\Application\Exceptions\VatAlreadyUsedException;
|
||||
use Src\Zone\Company\Domain\Model\Company;
|
||||
use Src\Zone\Company\Domain\Policies\CompanyPolicy;
|
||||
use Src\Zone\Company\Domain\Repositories\CompanyRepositoryInterface;
|
||||
use Src\Zone\Company\Infrastructure\EloquentModels\CompanyEloquentModel;
|
||||
use Src\Common\Domain\CommandInterface;
|
||||
|
||||
class StoreCompanyCommand implements CommandInterface
|
||||
{
|
||||
private CompanyRepositoryInterface $repository;
|
||||
private CompanyPolicy $policy;
|
||||
|
||||
public function __construct(
|
||||
private readonly Company $company
|
||||
)
|
||||
{
|
||||
$this->repository = app()->make(CompanyRepositoryInterface::class);
|
||||
$this->policy = new CompanyPolicy();
|
||||
}
|
||||
|
||||
public function execute(): Company
|
||||
{
|
||||
authorize('store', $this->policy);
|
||||
if (CompanyEloquentModel::query()->where('vat', $this->company->vat)->exists()) {
|
||||
throw new VatAlreadyUsedException();
|
||||
}
|
||||
|
||||
return $this->repository->store($this->company);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Application\UseCases\Commands;
|
||||
|
||||
use Src\Zone\Company\Application\Exceptions\VatAlreadyUsedException;
|
||||
use Src\Zone\Company\Domain\Model\Company;
|
||||
use Src\Zone\Company\Domain\Policies\CompanyPolicy;
|
||||
use Src\Zone\Company\Domain\Repositories\CompanyRepositoryInterface;
|
||||
use Src\Zone\Company\Infrastructure\EloquentModels\CompanyEloquentModel;
|
||||
use Src\Common\Domain\CommandInterface;
|
||||
|
||||
class UpdateCompanyCommand implements CommandInterface
|
||||
{
|
||||
private CompanyRepositoryInterface $repository;
|
||||
private CompanyPolicy $policy;
|
||||
|
||||
public function __construct(
|
||||
private readonly Company $company
|
||||
)
|
||||
{
|
||||
$this->repository = app()->make(CompanyRepositoryInterface::class);
|
||||
$this->policy = new CompanyPolicy();
|
||||
}
|
||||
|
||||
public function execute(): void
|
||||
{
|
||||
authorize('update', $this->policy);
|
||||
if (CompanyEloquentModel::query()->where('vat', $this->company->vat)->where('id', '!=', $this->company->id)->exists()) {
|
||||
throw new VatAlreadyUsedException();
|
||||
}
|
||||
|
||||
$this->repository->update($this->company);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Application\UseCases\Queries;
|
||||
|
||||
use Src\Zone\Company\Domain\Policies\CompanyPolicy;
|
||||
use Src\Zone\Company\Domain\Repositories\CompanyRepositoryInterface;
|
||||
use Src\Common\Domain\QueryInterface;
|
||||
|
||||
class FindAllCompaniesQuery implements QueryInterface
|
||||
{
|
||||
private CompanyRepositoryInterface $repository;
|
||||
private CompanyPolicy $policy;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->repository = app()->make(CompanyRepositoryInterface::class);
|
||||
$this->policy = new CompanyPolicy();
|
||||
}
|
||||
|
||||
public function handle(): array
|
||||
{
|
||||
authorize('findAll', $this->policy);
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Application\UseCases\Queries;
|
||||
|
||||
use Src\Zone\Company\Domain\Model\Company;
|
||||
use Src\Zone\Company\Domain\Policies\CompanyPolicy;
|
||||
use Src\Zone\Company\Domain\Repositories\CompanyRepositoryInterface;
|
||||
use Src\Common\Domain\QueryInterface;
|
||||
|
||||
class FindCompanyByIdQuery implements QueryInterface
|
||||
{
|
||||
private CompanyRepositoryInterface $repository;
|
||||
private CompanyPolicy $policy;
|
||||
|
||||
public function __construct(
|
||||
private readonly int $id
|
||||
)
|
||||
{
|
||||
$this->repository = app()->make(CompanyRepositoryInterface::class);
|
||||
$this->policy = new CompanyPolicy();
|
||||
}
|
||||
|
||||
public function handle(): Company
|
||||
{
|
||||
authorize('findById', $this->policy, ['company_id' => $this->id]);
|
||||
return $this->repository->findById($this->id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Application\UseCases\Queries;
|
||||
|
||||
use Src\Zone\Company\Domain\Policies\CompanyPolicy;
|
||||
use Src\Zone\Company\Domain\Repositories\CompanyRepositoryInterface;
|
||||
use Src\Common\Domain\QueryInterface;
|
||||
|
||||
class FindCompanyByVatQuery implements QueryInterface
|
||||
{
|
||||
private CompanyRepositoryInterface $repository;
|
||||
private CompanyPolicy $policy;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $vat
|
||||
)
|
||||
{
|
||||
$this->repository = app()->make(CompanyRepositoryInterface::class);
|
||||
$this->policy = new CompanyPolicy();
|
||||
}
|
||||
|
||||
public function handle(): array
|
||||
{
|
||||
authorize('findByVat', $this->policy);
|
||||
return $this->repository->findByVat($this->vat)->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Domain\Exceptions;
|
||||
|
||||
class IncorrectVatFormatException extends \DomainException
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('Vat must be valid');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Domain\Factories;
|
||||
|
||||
use Src\Zone\Company\Domain\Model\Company;
|
||||
use Src\Zone\Company\Domain\Model\ValueObjects\FiscalName;
|
||||
use Src\Zone\Company\Domain\Model\ValueObjects\SocialName;
|
||||
use Src\Zone\Company\Domain\Model\ValueObjects\Vat;
|
||||
|
||||
class CompanyFactory
|
||||
{
|
||||
public static function new(array $attributes = null): Company
|
||||
{
|
||||
$attributes = $attributes ?: [];
|
||||
|
||||
$defaults = [
|
||||
'id' => null,
|
||||
'fiscal_name' => fake()->name,
|
||||
'social_name' => fake()->company,
|
||||
'vat' => fake()->bothify('?#########'),
|
||||
'is_active' => true,
|
||||
];
|
||||
|
||||
$attributes = array_replace($defaults, $attributes);
|
||||
|
||||
return new Company(
|
||||
id: null,
|
||||
fiscal_name: new FiscalName($attributes['fiscal_name']),
|
||||
social_name: new SocialName($attributes['social_name']),
|
||||
vat: new Vat($attributes['vat']),
|
||||
is_active: $attributes['is_active'],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Src\Zone\Company\Domain\Model;
|
||||
|
||||
use Src\Zone\Company\Domain\Model\ValueObjects\FiscalName;
|
||||
use Src\Zone\Company\Domain\Model\ValueObjects\SocialName;
|
||||
use Src\Zone\Company\Domain\Model\ValueObjects\Vat;
|
||||
use Src\Common\Domain\AggregateRoot;
|
||||
use Src\Common\Domain\Exceptions\EntityNotFoundException;
|
||||
|
||||
class Company extends AggregateRoot implements \JsonSerializable
|
||||
{
|
||||
public function __construct(
|
||||
public readonly ?int $id,
|
||||
public readonly FiscalName $fiscal_name,
|
||||
public readonly SocialName $social_name,
|
||||
public readonly Vat $vat,
|
||||
public readonly bool $is_active = true
|
||||
) {}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'fiscal_name' => $this->fiscal_name,
|
||||
'social_name' => $this->social_name,
|
||||
'vat' => $this->vat,
|
||||
'is_active' => $this->is_active,
|
||||
];
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Src\Zone\Company\Domain\Model\ValueObjects;
|
||||
|
||||
use Src\Common\Domain\Exceptions\RequiredException;
|
||||
|
||||
final class FiscalName implements \JsonSerializable
|
||||
{
|
||||
private string $name;
|
||||
|
||||
public function __construct(?string $name)
|
||||
{
|
||||
if (!$name) {
|
||||
throw new RequiredException('razón social');
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Src\Zone\Company\Domain\Model\ValueObjects;
|
||||
|
||||
use Src\Common\Domain\Exceptions\RequiredException;
|
||||
|
||||
final class SocialName implements \JsonSerializable
|
||||
{
|
||||
private string $name;
|
||||
|
||||
public function __construct(?string $name)
|
||||
{
|
||||
if (!$name) {
|
||||
throw new RequiredException('nombre comercial');
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Domain\Model\ValueObjects;
|
||||
|
||||
use Src\Zone\Company\Domain\Exceptions\IncorrectVatFormatException;
|
||||
use Src\Common\Domain\Exceptions\RequiredException;
|
||||
|
||||
final class Vat implements \JsonSerializable
|
||||
{
|
||||
private string $vat;
|
||||
|
||||
public function __construct(?string $vat)
|
||||
{
|
||||
if (!$vat) {
|
||||
throw new RequiredException('vat');
|
||||
}
|
||||
|
||||
if (!preg_match('/([a-z]|[A-Z]|[0-9])[0-9]{7}([a-z]|[A-Z]|[0-9])/', $vat)) {
|
||||
throw new IncorrectVatFormatException();
|
||||
}
|
||||
|
||||
$this->vat = $vat;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->vat;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): string
|
||||
{
|
||||
return $this->vat;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Domain\Policies;
|
||||
|
||||
class CompanyPolicy
|
||||
{
|
||||
public function findAll(): bool
|
||||
{
|
||||
return auth()->user()->is_admin;
|
||||
}
|
||||
|
||||
public function findById(string $company_id): bool
|
||||
{
|
||||
return auth()->user()->is_admin || auth()->user()->company_id == $company_id;
|
||||
}
|
||||
|
||||
public function findByVat(): bool
|
||||
{
|
||||
return auth()->user()->is_admin;
|
||||
}
|
||||
|
||||
public function store(): bool
|
||||
{
|
||||
return auth()->user()->is_admin;
|
||||
}
|
||||
|
||||
public function update(): bool
|
||||
{
|
||||
return auth()->user()->is_admin;
|
||||
}
|
||||
|
||||
public function delete(): bool
|
||||
{
|
||||
return auth()->user()->is_admin;
|
||||
}
|
||||
|
||||
public function persistAddresses(): bool
|
||||
{
|
||||
return auth()->user()->is_admin;
|
||||
}
|
||||
|
||||
public function removeAddress(): bool
|
||||
{
|
||||
return auth()->user()->is_admin;
|
||||
}
|
||||
|
||||
public function persistDepartments(): bool
|
||||
{
|
||||
return auth()->user()->is_admin;
|
||||
}
|
||||
|
||||
public function removeDepartment(): bool
|
||||
{
|
||||
return auth()->user()->is_admin;
|
||||
}
|
||||
|
||||
public function persistContacts(): bool
|
||||
{
|
||||
return auth()->user()->is_admin;
|
||||
}
|
||||
|
||||
public function removeContact(): bool
|
||||
{
|
||||
return auth()->user()->is_admin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Domain\Repositories;
|
||||
|
||||
use Src\Zone\Company\Domain\Model\Company;
|
||||
|
||||
interface CompanyRepositoryInterface
|
||||
{
|
||||
public function findAll(): array;
|
||||
public function findById(string $id): Company;
|
||||
public function findByVat(string $vat): Company;
|
||||
|
||||
public function store(Company $company): Company;
|
||||
public function update(Company $company): void;
|
||||
public function delete(int $company_id): void;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Infrastructure\EloquentModels;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CompanyEloquentModel extends Model
|
||||
{
|
||||
protected $table = 'companies';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'fiscal_name',
|
||||
'social_name',
|
||||
'vat',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
public array $rules = [
|
||||
'fiscal_name' => 'required|string|max:255',
|
||||
'social_name' => 'required|string|max:255',
|
||||
'vat' => 'required|string|max:255',
|
||||
'is_active' => 'required|boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Src\Zone\Company\Presentation\HTTP;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Src\Zone\Company\Application\Mappers\CompanyMapper;
|
||||
use Src\Zone\Company\Application\UseCases\Commands\DestroyCompanyCommand;
|
||||
use Src\Zone\Company\Application\UseCases\Commands\StoreCompanyCommand;
|
||||
use Src\Zone\Company\Application\UseCases\Commands\UpdateCompanyCommand;
|
||||
use Src\Zone\Company\Application\UseCases\Queries\FindAllCompaniesQuery;
|
||||
use Src\Zone\Company\Application\UseCases\Queries\FindCompanyByIdQuery;
|
||||
use Src\Common\Domain\Exceptions\UnauthorizedUserException;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CompanyController
|
||||
{
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
return response()->json((new FindAllCompaniesQuery())->handle());
|
||||
} catch (UnauthorizedUserException $e) {
|
||||
return response()->json(['error' => $e->getMessage()], Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
return response()->json((new FindCompanyByIdQuery($id))->handle());
|
||||
} catch (UnauthorizedUserException $e) {
|
||||
return response()->json(['error' => $e->getMessage()], Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$newCompany = CompanyMapper::fromRequest($request);
|
||||
$company = (new StoreCompanyCommand($newCompany))->execute();
|
||||
return response()->json($company, Response::HTTP_CREATED);
|
||||
} catch (\DomainException $domainException) {
|
||||
return response()->json(['error' => $domainException->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
} catch (UnauthorizedUserException $e) {
|
||||
return response()->json(['error' => $e->getMessage()], Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(int $company_id, Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$company = CompanyMapper::fromRequest($request, $company_id);
|
||||
(new UpdateCompanyCommand($company))->execute();
|
||||
return response()->json($company->toArray(), Response::HTTP_OK);
|
||||
} catch (\DomainException $domainException) {
|
||||
return response()->json(['error' => $domainException->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
} catch (UnauthorizedUserException $e) {
|
||||
return response()->json(['error' => $e->getMessage()], Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(int $company_id): JsonResponse
|
||||
{
|
||||
try {
|
||||
(new DestroyCompanyCommand($company_id))->execute();
|
||||
return response()->json(null, Response::HTTP_NO_CONTENT);
|
||||
} catch (UnauthorizedUserException $e) {
|
||||
return response()->json(['error' => $e->getMessage()], Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Src\Zone\Company\Presentation\HTTP\CompanyController;
|
||||
|
||||
Route::group([
|
||||
'prefix' => 'company'
|
||||
], function () {
|
||||
Route::get('index', [CompanyController::class, 'index']);
|
||||
Route::get('{id}', [CompanyController::class, 'show']);
|
||||
Route::post('', [CompanyController::class, 'store']);
|
||||
Route::put('{id}', [CompanyController::class, 'update']);
|
||||
Route::delete('{id}', [CompanyController::class, 'destroy']);
|
||||
});
|
||||
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Tests\TestCase;
|
||||
use Tests\Support\WithLogin;
|
||||
use Tests\Support\WithCompanies;
|
||||
|
||||
class CompanyTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase, WithLogin, WithCompanies;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->company_uri = '/company';
|
||||
$this->index_uri = $this->company_uri . '/index';
|
||||
$this->adminToken = $this->newLoggedAdmin()['token'];
|
||||
$this->userData = $this->newLoggedUser();
|
||||
$this->userToken = $this->userData['token'];
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function admin_can_retrieve_all_companies()
|
||||
{
|
||||
$companiesCount = $this->faker->numberBetween(1, 10);
|
||||
$this->createRandomCompanies($companiesCount);
|
||||
|
||||
$companies = $this->withHeaders(['Authorization' => 'Bearer ' . $this->adminToken])
|
||||
->get($this->index_uri)
|
||||
->assertStatus(Response::HTTP_OK)
|
||||
->assertJsonCount($companiesCount);
|
||||
|
||||
$companyInfo = $companies->json()[0];
|
||||
$this->assertEquals(
|
||||
['id', 'fiscal_name', 'social_name', 'vat', 'is_active'],
|
||||
array_keys($companyInfo)
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function user_cannot_retrieve_all_companies()
|
||||
{
|
||||
$companiesCount = $this->faker->numberBetween(1, 10);
|
||||
$this->createRandomCompanies($companiesCount);
|
||||
|
||||
$this->withHeaders(['Authorization' => 'Bearer ' . $this->userToken])
|
||||
->get($this->index_uri)
|
||||
->assertStatus(Response::HTTP_UNAUTHORIZED)
|
||||
->assertSee(['error' => 'The user is not authorized to access this resource']);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function admin_can_get_specific_company_by_id()
|
||||
{
|
||||
$companiesCount = $this->faker->numberBetween(1, 10);
|
||||
$this->createRandomCompanies($companiesCount);
|
||||
$randomCompanyId = $this->faker->numberBetween(1, $companiesCount);
|
||||
|
||||
$this->withHeaders(['Authorization' => 'Bearer ' . $this->adminToken])
|
||||
->get($this->company_uri . '/' . $randomCompanyId)
|
||||
->assertStatus(Response::HTTP_OK)
|
||||
->assertJsonStructure(['id', 'fiscal_name', 'social_name', 'vat', 'is_active']);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function user_cannot_get_specific_company_by_id_except_if_belongs_to()
|
||||
{
|
||||
$companiesCount = $this->faker->numberBetween(1, 10);
|
||||
$this->createRandomCompanies($companiesCount);
|
||||
$randomCompanyId = $this->faker->numberBetween(2, $companiesCount + 1);
|
||||
|
||||
// User cannot retrieve company where it is not belonged to
|
||||
$this->withHeaders(['Authorization' => 'Bearer ' . $this->userToken])
|
||||
->get($this->company_uri . '/' . $randomCompanyId)
|
||||
->assertStatus(Response::HTTP_UNAUTHORIZED)
|
||||
->assertSee(['error' => 'The user is not authorized to access this resource']);
|
||||
|
||||
// User can retrieve company where it belongs
|
||||
//todo implement user relationship to company
|
||||
// $this->withHeaders(['Authorization' => 'Bearer ' . $this->userToken])
|
||||
// ->get($this->company_uri . '/' . $companyId)
|
||||
// ->assertStatus(Response::HTTP_OK)
|
||||
// ->assertJsonStructure(['id', 'fiscal_name', 'social_name', 'vat', 'is_active']);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function admin_can_create_a_company()
|
||||
{
|
||||
$requestBody = [
|
||||
'fiscal_name' => $this->faker->name,
|
||||
'social_name' => $this->faker->company,
|
||||
'vat' => $this->faker->bothify('?#########'),
|
||||
'is_active' => $this->faker->boolean,
|
||||
];
|
||||
|
||||
$expectedResponse = [
|
||||
'id' => 1,
|
||||
'fiscal_name' => $requestBody['fiscal_name'],
|
||||
'social_name' => $requestBody['social_name'],
|
||||
'vat' => $requestBody['vat'],
|
||||
'is_active' => $requestBody['is_active']
|
||||
];
|
||||
|
||||
|
||||
$this->withHeaders(['Authorization' => 'Bearer ' . $this->adminToken])
|
||||
->post($this->company_uri, $requestBody)
|
||||
->assertStatus(Response::HTTP_CREATED)
|
||||
->assertJson($expectedResponse);
|
||||
|
||||
// Assert cannot create company with same vat
|
||||
$this->withHeaders(['Authorization' => 'Bearer ' . $this->adminToken])
|
||||
->post($this->company_uri, $requestBody)
|
||||
->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)
|
||||
->assertJson(['error' => 'Vat is already used']);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function user_cannot_create_a_company()
|
||||
{
|
||||
$requestBody = [
|
||||
'fiscal_name' => $this->faker->name,
|
||||
'social_name' => $this->faker->company,
|
||||
'vat' => $this->faker->bothify('?#########'),
|
||||
'is_active' => $this->faker->boolean
|
||||
];
|
||||
|
||||
$this->withHeaders(['Authorization' => 'Bearer ' . $this->userToken])
|
||||
->post($this->company_uri, $requestBody)
|
||||
->assertStatus(Response::HTTP_UNAUTHORIZED)
|
||||
->assertSee(['error' => 'The user is not authorized to access this resource']);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function cannot_create_company_with_invalid_vat()
|
||||
{
|
||||
$requestBodyInvalidVat = [
|
||||
'fiscal_name' => $this->faker->name,
|
||||
'social_name' => $this->faker->company,
|
||||
'vat' => 'invalidvat',
|
||||
'is_active' => $this->faker->boolean,
|
||||
];
|
||||
|
||||
$this->withHeaders(['Authorization' => 'Bearer ' . $this->adminToken])
|
||||
->post($this->company_uri, $requestBodyInvalidVat)
|
||||
->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)
|
||||
->assertJson(['error' => 'Vat must be valid']);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function admin_can_update_a_company()
|
||||
{
|
||||
$numberCompanies = $this->faker->numberBetween(1, 10);
|
||||
$this->createRandomCompanies($numberCompanies);
|
||||
$randomCompanyId = $this->faker->numberBetween(1, $numberCompanies);
|
||||
|
||||
$this->withHeaders(['Authorization' => 'Bearer ' . $this->adminToken])
|
||||
->get($this->company_uri . '/' . $randomCompanyId)
|
||||
->assertStatus(Response::HTTP_OK)
|
||||
->assertJsonStructure(['id', 'fiscal_name', 'social_name', 'vat', 'is_active']);
|
||||
|
||||
$requestBody = [
|
||||
'fiscal_name' => $this->faker->name,
|
||||
'social_name' => $this->faker->company,
|
||||
'vat' => $this->faker->bothify('?#########'),
|
||||
'is_active' => $this->faker->boolean,
|
||||
];
|
||||
|
||||
$expectedResponse = [
|
||||
'id' => $randomCompanyId,
|
||||
'fiscal_name' => $requestBody['fiscal_name'],
|
||||
'social_name' => $requestBody['social_name'],
|
||||
'vat' => $requestBody['vat'],
|
||||
'is_active' => $requestBody['is_active']
|
||||
];
|
||||
|
||||
$this->withHeaders(['Authorization' => 'Bearer ' . $this->adminToken])
|
||||
->put($this->company_uri . '/' . $randomCompanyId, $requestBody)
|
||||
->assertStatus(Response::HTTP_OK)
|
||||
->assertJson($expectedResponse);
|
||||
|
||||
$requestBodyInvalidVat = [
|
||||
'fiscal_name' => $this->faker->name,
|
||||
'social_name' => $this->faker->company,
|
||||
'vat' => 'invalidvat',
|
||||
'is_active' => $this->faker->boolean,
|
||||
];
|
||||
|
||||
$this->withHeaders(['Authorization' => 'Bearer ' . $this->adminToken])
|
||||
->put($this->company_uri . '/' . $randomCompanyId, $requestBodyInvalidVat)
|
||||
->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)
|
||||
->assertJson(['error' => 'Vat must be valid']);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function user_cannot_update_a_company()
|
||||
{
|
||||
$numberCompanies = $this->faker->numberBetween(1, 10);
|
||||
$this->createRandomCompanies($numberCompanies);
|
||||
$randomCompanyId = $this->faker->numberBetween(1, $numberCompanies);
|
||||
|
||||
$company = $this->withHeaders(['Authorization' => 'Bearer ' . $this->adminToken])
|
||||
->get($this->company_uri . '/' . $randomCompanyId)
|
||||
->assertStatus(Response::HTTP_OK)
|
||||
->assertJsonStructure(['id', 'fiscal_name', 'social_name', 'vat', 'is_active']);
|
||||
|
||||
$requestBody = [
|
||||
'fiscal_name' => $this->faker->name,
|
||||
'social_name' => $this->faker->company,
|
||||
'vat' => $this->faker->bothify('?#########'),
|
||||
'is_active' => $this->faker->boolean
|
||||
];
|
||||
|
||||
$this->withHeaders(['Authorization' => 'Bearer ' . $this->userToken])
|
||||
->put($this->company_uri . '/' . $randomCompanyId, $requestBody)
|
||||
->assertStatus(Response::HTTP_UNAUTHORIZED)
|
||||
->assertSee(['error' => 'The user is not authorized to access this resource']);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function admin_can_delete_a_company()
|
||||
{
|
||||
$numberCompanies = $this->faker->numberBetween(1, 10);
|
||||
$this->createRandomCompanies($numberCompanies);
|
||||
$randomCompanyId = $this->faker->numberBetween(1, $numberCompanies);
|
||||
|
||||
$this->withHeaders(['Authorization' => 'Bearer ' . $this->adminToken])
|
||||
->delete($this->company_uri . '/' . $randomCompanyId)
|
||||
->assertStatus(Response::HTTP_NO_CONTENT);
|
||||
|
||||
$this->withHeaders(['Authorization' => 'Bearer ' . $this->adminToken])
|
||||
->get($this->company_uri . '/' . $randomCompanyId)
|
||||
->assertStatus(Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function user_cannot_delete_a_company()
|
||||
{
|
||||
$numberCompanies = $this->faker->numberBetween(1, 10);
|
||||
$this->createRandomCompanies($numberCompanies);
|
||||
$randomCompanyId = $this->faker->numberBetween(1, $numberCompanies);
|
||||
|
||||
$this->withHeaders(['Authorization' => 'Bearer ' . $this->userToken])
|
||||
->delete($this->company_uri . '/' . $randomCompanyId)
|
||||
->assertStatus(Response::HTTP_UNAUTHORIZED)
|
||||
->assertSee(['error' => 'The user is not authorized to access this resource']);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function cannot_delete_company_if_does_not_exists()
|
||||
{
|
||||
$this->withHeaders(['Authorization' => 'Bearer ' . $this->adminToken])
|
||||
->delete($this->company_uri . '/' . 3)
|
||||
->assertStatus(Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Support;
|
||||
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Src\Zone\Company\Application\Mappers\CompanyMapper;
|
||||
use Src\Zone\Company\Domain\Factories\CompanyFactory;
|
||||
use Src\Zone\Company\Domain\Model\Company;
|
||||
|
||||
trait WithCompanies
|
||||
{
|
||||
use WithFaker;
|
||||
|
||||
public function newCompany(): Company
|
||||
{
|
||||
$company = CompanyFactory::new();
|
||||
$companyEloquentModel = CompanyMapper::toEloquent($company);
|
||||
$companyEloquentModel->save();
|
||||
return CompanyMapper::fromEloquent($companyEloquentModel);
|
||||
}
|
||||
|
||||
public function createRandomCompanies(int $companiesCount)
|
||||
{
|
||||
foreach (range(1, $companiesCount) as $_) {
|
||||
$this->newCompany();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user