Merge pull request #7 from goteo/develop

chore: sync main to open-core develop
This commit is contained in:
Daniel Subiabre García
2026-10-27 14:05:32 +01:00
committed by GitHub
18 changed files with 668 additions and 10 deletions
+42
View File
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20261015093422 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add User, Person and Organization profile fields';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE user_category (user_id INT NOT NULL, category_id INT NOT NULL, INDEX IDX_E6C1FDC1A76ED395 (user_id), INDEX IDX_E6C1FDC112469DE2 (category_id), PRIMARY KEY(user_id, category_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE user_category ADD CONSTRAINT FK_E6C1FDC1A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE user_category ADD CONSTRAINT FK_E6C1FDC112469DE2 FOREIGN KEY (category_id) REFERENCES category (id) ON DELETE CASCADE');
$this->addSql('CREATE TABLE user_theme (user_id INT NOT NULL, theme_id INT NOT NULL, INDEX IDX_75B71C50A76ED395 (user_id), INDEX IDX_75B71C5059027487 (theme_id), PRIMARY KEY(user_id, theme_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE user_theme ADD CONSTRAINT FK_75B71C50A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE user_theme ADD CONSTRAINT FK_75B71C5059027487 FOREIGN KEY (theme_id) REFERENCES theme (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE user ADD share_location TINYINT(1) DEFAULT 0 NOT NULL, ADD is_anonymous TINYINT(1) DEFAULT 0 NOT NULL, ADD referral_source VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE user_organization ADD tax_id_type VARCHAR(255) DEFAULT NULL, ADD legal_form VARCHAR(255) DEFAULT NULL, ADD public_entity TINYINT(1) DEFAULT 0 NOT NULL');
$this->addSql('ALTER TABLE user_person ADD tax_id_type VARCHAR(255) DEFAULT NULL, ADD birth_year INT DEFAULT NULL, ADD gender VARCHAR(255) DEFAULT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE user_category DROP FOREIGN KEY FK_E6C1FDC1A76ED395');
$this->addSql('ALTER TABLE user_category DROP FOREIGN KEY FK_E6C1FDC112469DE2');
$this->addSql('DROP TABLE user_category');
$this->addSql('ALTER TABLE user_theme DROP FOREIGN KEY FK_75B71C50A76ED395');
$this->addSql('ALTER TABLE user_theme DROP FOREIGN KEY FK_75B71C5059027487');
$this->addSql('DROP TABLE user_theme');
$this->addSql('ALTER TABLE user_person DROP tax_id_type, DROP birth_year, DROP gender');
$this->addSql('ALTER TABLE user DROP share_location, DROP is_anonymous, DROP referral_source');
$this->addSql('ALTER TABLE user_organization DROP tax_id_type, DROP legal_form, DROP public_entity');
}
}
@@ -5,6 +5,8 @@ namespace App\ApiResource\User;
use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata as API;
use App\Entity\User\Organization;
use App\Entity\User\OrganizationLegalForm;
use App\Entity\User\TaxIdType;
use App\State\ApiResourceStateProcessor;
use App\State\ApiResourceStateProvider;
use Symfony\Component\Validator\Constraints as Assert;
@@ -73,4 +75,28 @@ class OrganizationApiResource
securityPostDenormalize: 'is_granted("ORGANIZATION_EDIT", previous_object)',
)]
public string $businessName;
/**
* The kind of document the tax ID belongs to.
*/
#[API\ApiProperty(
securityPostDenormalize: 'is_granted("ORGANIZATION_EDIT", previous_object)',
)]
public ?TaxIdType $taxIdType = null;
/**
* Legal form under which the organization is constituted.
*/
#[API\ApiProperty(
securityPostDenormalize: 'is_granted("ORGANIZATION_EDIT", previous_object)',
)]
public ?OrganizationLegalForm $legalForm = null;
/**
* Is the organization a public entity? e.g: city councils, public universities, etc.
*/
#[API\ApiProperty(
securityPostDenormalize: 'is_granted("ORGANIZATION_EDIT", previous_object)',
)]
public bool $publicEntity = false;
}
@@ -5,9 +5,12 @@ namespace App\ApiResource\User;
use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata as API;
use App\Entity\User\Person;
use App\Entity\User\PersonGender;
use App\Entity\User\TaxIdType;
use App\State\ApiResourceStateProcessor;
use App\State\ApiResourceStateProvider;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* Most times a real person is behind a User account,
@@ -69,4 +72,40 @@ class PersonApiResource
securityPostDenormalize: 'is_granted("PERSON_EDIT", previous_object)',
)]
public string $lastName;
/**
* The kind of document the tax ID belongs to.
*/
#[API\ApiProperty(
securityPostDenormalize: 'is_granted("PERSON_EDIT", previous_object)',
)]
public ?TaxIdType $taxIdType = null;
/**
* Year of birth of the person. e.g: 1990.
*/
#[Assert\GreaterThanOrEqual(1900)]
#[Assert\Callback([self::class, 'validateBirthYear'])]
#[API\ApiProperty(
securityPostDenormalize: 'is_granted("PERSON_EDIT", previous_object)',
)]
public ?int $birthYear = null;
/**
* Gender of the person.
*/
#[API\ApiProperty(
securityPostDenormalize: 'is_granted("PERSON_EDIT", previous_object)',
)]
public ?PersonGender $gender = null;
/**
* `Range` only resolves relative time strings (e.g. 'now') against DateTime values, not int years.
*/
public static function validateBirthYear(?int $birthYear, ExecutionContextInterface $context): void
{
if ($birthYear !== null && $birthYear > (int) date('Y')) {
$context->buildViolation('The birth year cannot be in the future.')->addViolation();
}
}
}
+41
View File
@@ -8,12 +8,15 @@ use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata as API;
use App\ApiResource\Accounting\AccountingApiResource;
use App\ApiResource\CategoryApiResource;
use App\ApiResource\ThemeApiResource;
use App\ApiResource\TimestampedCreationApiResource;
use App\ApiResource\TimestampedUpdationApiResource;
use App\Dto\User\UserSignupDto;
use App\Dto\User\UserUpdationDto;
use App\Entity\Territory;
use App\Entity\User\User;
use App\Entity\User\UserReferralSource;
use App\Entity\User\UserType;
use App\Filter\EncryptedSearchFilter;
use App\Filter\InArrayFilter;
@@ -191,4 +194,42 @@ class UserApiResource
* Free-form rich text description for the User.
*/
public string $description;
/**
* Has the User agreed to share their location with the platform?
* Used to generate anonymous statistics and impact indicators, never disclosed to third parties.\
* Only available to themselves and platform administrators.
*/
#[API\ApiProperty(security: 'is_granted("USER_EDIT", object)')]
public bool $shareLocation = false;
/**
* Does this User wish to have their personal data to remain undisclosed to 3rd parties?\
* Only available to themselves and platform administrators.
*/
#[API\ApiProperty(security: 'is_granted("USER_EDIT", object)')]
public bool $isAnonymous = false;
/**
* How the User got to know about the platform.\
* Only available to themselves and platform administrators.
*/
#[API\ApiProperty(security: 'is_granted("USER_EDIT", object)')]
public ?UserReferralSource $referralSource = null;
/**
* The Categories the User is interested in.
*
* @var CategoryApiResource[]
*/
#[API\ApiProperty(writableLink: false)]
public array $preferredCategories = [];
/**
* The Themes the User is interested in.
*
* @var ThemeApiResource[]
*/
#[API\ApiProperty(writableLink: false)]
public array $preferredThemes = [];
}
@@ -48,6 +48,10 @@ final class OAuthClientsController extends AbstractController
private function getBaseUri(string|array $url): string
{
if (empty($url)) {
return '';
}
if (is_array($url)) {
$url = $url[0];
}
+38
View File
@@ -3,9 +3,13 @@
namespace App\Dto\User;
use ApiPlatform\Metadata as API;
use App\ApiResource\CategoryApiResource;
use App\ApiResource\ThemeApiResource;
use App\ApiResource\User\UserApiResource;
use App\Dto\CategorizedInputDtoTrait;
use App\Entity\Territory;
use App\Entity\User\User;
use App\Entity\User\UserReferralSource;
use App\Entity\User\UserType;
use App\Mapping\Transformer\RawLinksMapTransformer;
use App\Validator\NotExisting;
@@ -15,6 +19,8 @@ use Symfony\Component\Validator\Constraints as Assert;
final class UserUpdationDto
{
use CategorizedInputDtoTrait;
/**
* A unique, non white space, byte-safe string identifier for this User.
*/
@@ -75,4 +81,36 @@ final class UserUpdationDto
* Free-form rich text description for the User.
*/
public string $description;
/**
* Has the User agreed to share their location with the platform?
* Used to generate anonymous statistics and impact indicators, never disclosed to third parties.
*/
public bool $shareLocation;
/**
* Does this User wish to have their personal data to remain undisclosed to 3rd parties?
*/
public bool $isAnonymous;
/**
* How the User got to know about the platform.
*/
public ?UserReferralSource $referralSource;
/**
* The Categories the User is interested in.
*
* @var CategoryApiResource[]
*/
#[API\ApiProperty(writableLink: false, openapiContext: self::CATEGORIES_OPENAPI_CONTEXT)]
public array $preferredCategories;
/**
* The Themes the User is interested in.
*
* @var ThemeApiResource[]
*/
#[API\ApiProperty(writableLink: false, openapiContext: self::THEMES_OPENAPI_CONTEXT)]
public array $preferredThemes;
}
+3
View File
@@ -2,10 +2,13 @@
namespace App\Entity;
use App\Mapping\Provider\EntityMapProvider;
use App\Repository\ThemeRepository;
use AutoMapper\Attribute\MapProvider;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
#[MapProvider(EntityMapProvider::class)]
#[ORM\Entity(repositoryClass: ThemeRepository::class)]
class Theme
{
+54
View File
@@ -30,6 +30,24 @@ class Organization
#[BlindIndex(sourceField: 'taxId', normalizer: BlindIndex::NORMALIZE_UPPERCASE)]
private ?string $taxIdLookup = null;
/**
* The kind of document the tax ID belongs to.
*/
#[ORM\Column(nullable: true, enumType: TaxIdType::class)]
private ?TaxIdType $taxIdType = null;
/**
* Legal form under which the organization is constituted.
*/
#[ORM\Column(nullable: true, enumType: OrganizationLegalForm::class)]
private ?OrganizationLegalForm $legalForm = null;
/**
* Is the organization a public entity?
*/
#[ORM\Column(options: ['default' => false])]
private bool $publicEntity = false;
/**
* Organization legal name before government,
* as it appears on legal documents issued by or for this organization.
@@ -110,4 +128,40 @@ class Organization
return $this;
}
public function getTaxIdType(): ?TaxIdType
{
return $this->taxIdType;
}
public function setTaxIdType(?TaxIdType $taxIdType): static
{
$this->taxIdType = $taxIdType;
return $this;
}
public function getLegalForm(): ?OrganizationLegalForm
{
return $this->legalForm;
}
public function setLegalForm(?OrganizationLegalForm $legalForm): static
{
$this->legalForm = $legalForm;
return $this;
}
public function isPublicEntity(): bool
{
return $this->publicEntity;
}
public function setPublicEntity(bool $publicEntity): static
{
$this->publicEntity = $publicEntity;
return $this;
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Entity\User;
enum OrganizationLegalForm: string
{
case Association = 'association';
case Foundation = 'foundation';
case Cooperative = 'cooperative';
/**
* For-profit companies of any kind.
*/
case Company = 'company';
/**
* Self-employed professionals.
*/
case SelfEmployed = 'self_employed';
/**
* Government bodies and public administrations.
*/
case PublicAdministration = 'public_administration';
case Other = 'other';
}
+51
View File
@@ -37,6 +37,21 @@ class Person
#[BlindIndex(sourceField: 'taxId', normalizer: BlindIndex::NORMALIZE_UPPERCASE)]
private ?string $taxIdLookup = null;
/**
* The kind of document the tax ID belongs to.
*/
#[ORM\Column(nullable: true, enumType: TaxIdType::class)]
private ?TaxIdType $taxIdType = null;
/**
* Year of birth of the person.
*/
#[ORM\Column(nullable: true)]
private ?int $birthYear = null;
#[ORM\Column(nullable: true, enumType: PersonGender::class)]
private ?PersonGender $gender = null;
/**
* First-part of the name of the person, usually the given name(s). e.g: John, Juan.
*/
@@ -116,4 +131,40 @@ class Person
return $this;
}
public function getTaxIdType(): ?TaxIdType
{
return $this->taxIdType;
}
public function setTaxIdType(?TaxIdType $taxIdType): static
{
$this->taxIdType = $taxIdType;
return $this;
}
public function getBirthYear(): ?int
{
return $this->birthYear;
}
public function setBirthYear(?int $birthYear): static
{
$this->birthYear = $birthYear;
return $this;
}
public function getGender(): ?PersonGender
{
return $this->gender;
}
public function setGender(?PersonGender $gender): static
{
$this->gender = $gender;
return $this;
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Entity\User;
enum PersonGender: string
{
case Female = 'female';
case Male = 'male';
case NonBinary = 'non_binary';
/**
* Any other gender identity, or the person prefers not to say.
*/
case Other = 'other';
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Entity\User;
enum TaxIdType: string
{
/**
* National identity or tax identification document, for people and organizations.
*/
case NationalId = 'national_id';
case Passport = 'passport';
/**
* Any other national or foreign identification document.
*/
case Other = 'other';
}
+122
View File
@@ -5,12 +5,14 @@ namespace App\Entity\User;
use App\Entity\Accounting\Accounting;
use App\Entity\Accounting\AccountingOwnerInterface;
use App\Entity\Address;
use App\Entity\Category;
use App\Entity\DateCreatedTrait;
use App\Entity\DateUpdatedTrait;
use App\Entity\DedupedTrait;
use App\Entity\MigratedTrait;
use App\Entity\Project\Project;
use App\Entity\Territory;
use App\Entity\Theme;
use App\Library\Link;
use App\Mapping\Provider\EntityMapProvider;
use App\Repository\User\UserRepository;
@@ -131,6 +133,40 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Account
#[ORM\OneToMany(targetEntity: Address::class, mappedBy: 'user', cascade: ['persist'])]
private Collection $addresses;
/**
* Has the User agreed to share their location with the platform for anonymous statistics?
*/
#[ORM\Column(options: ['default' => false])]
private bool $shareLocation = false;
/**
* Does this User wish to have their personal data to remain undisclosed to 3rd parties?
*/
#[ORM\Column(options: ['default' => false])]
private bool $isAnonymous = false;
/**
* How the User got to know about the platform.
*/
#[ORM\Column(nullable: true, enumType: UserReferralSource::class)]
private ?UserReferralSource $referralSource = null;
/**
* The Categories the User is interested in.
*
* @var Collection<int, Category>
*/
#[ORM\ManyToMany(targetEntity: Category::class)]
private Collection $preferredCategories;
/**
* The Themes the User is interested in.
*
* @var Collection<int, Theme>
*/
#[ORM\ManyToMany(targetEntity: Theme::class)]
private Collection $preferredThemes;
public function __construct()
{
$this->accounting = Accounting::of($this);
@@ -141,6 +177,8 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Account
$this->emailConfirmed = false;
$this->active = false;
$this->addresses = new ArrayCollection();
$this->preferredCategories = new ArrayCollection();
$this->preferredThemes = new ArrayCollection();
}
public function getId(): ?int
@@ -431,4 +469,88 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Account
return $this;
}
public function isShareLocation(): bool
{
return $this->shareLocation;
}
public function setShareLocation(bool $shareLocation): static
{
$this->shareLocation = $shareLocation;
return $this;
}
public function isAnonymous(): bool
{
return $this->isAnonymous;
}
public function setIsAnonymous(bool $isAnonymous): static
{
$this->isAnonymous = $isAnonymous;
return $this;
}
public function getReferralSource(): ?UserReferralSource
{
return $this->referralSource;
}
public function setReferralSource(?UserReferralSource $referralSource): static
{
$this->referralSource = $referralSource;
return $this;
}
/**
* @return Collection<int, Category>
*/
public function getPreferredCategories(): Collection
{
return $this->preferredCategories;
}
public function addPreferredCategory(Category $category): static
{
if (!$this->preferredCategories->contains($category)) {
$this->preferredCategories->add($category);
}
return $this;
}
public function removePreferredCategory(Category $category): static
{
$this->preferredCategories->removeElement($category);
return $this;
}
/**
* @return Collection<int, Theme>
*/
public function getPreferredThemes(): Collection
{
return $this->preferredThemes;
}
public function addPreferredTheme(Theme $theme): static
{
if (!$this->preferredThemes->contains($theme)) {
$this->preferredThemes->add($theme);
}
return $this;
}
public function removePreferredTheme(Theme $theme): static
{
$this->preferredThemes->removeElement($theme);
return $this;
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
namespace App\Entity\User;
enum UserReferralSource: string
{
case SearchEngine = 'search_engine';
case SocialMedia = 'social_media';
/**
* Word of mouth: a friend, relative or acquaintance.
*/
case Friend = 'friend';
/**
* News, press or media coverage.
*/
case Press = 'press';
/**
* An event, talk or workshop.
*/
case Event = 'event';
case Other = 'other';
}
+22 -9
View File
@@ -6,20 +6,33 @@
{% block body %}
<div class="mx-auto max-w-360 px-10 mb-20">
<ul class="mx-auto max-w-122 flex flex-col gap-4">
<div class="mx-auto max-w-180 flex flex-col gap-4">
<h1 class="text-black text-2xl font-bold">{{ 'clients.title' | trans }}</h1>
<p class="text-content">{{ 'clients.description' | trans | raw }}</p>
<div class="flex gap-6">
<div class="grid grid-cols-3 gap-6">
{% for client in oauth_clients %}
<li>
<div class="bg-white border border-grey rounded-2xl p-4 shadow-sm">
<a href="{{ client.baseUri }}">
<img class="max-h-12 h-full" src="/oauth2/clients/{{ client.identifier }}.png" alt={{ client.name }}>
</a>
<a href="{{ client.baseUri }}" class="block h-full">
<div class="h-full bg-white border border-grey rounded-2xl p-4 shadow-sm flex items-center">
<div class="flex flex-col gap-4">
<img
class="max-h-16 h-auto grayscale"
src="/oauth2/clients/{{ client.identifier }}.png"
alt="{{ client.name }}"
>
<div>
<span class="text-content text-xs">Name</span>
<p>{{ client.name }}</p>
</div>
<div>
<span class="text-content text-xs">Site</span>
<p class="text-sm">{{ client.baseUri | replace({'https://' : '', 'https://www.' : '', 'http://' : '', '/' : ''}) }}</p>
</div>
</div>
</div>
</li>
</a>
{% endfor %}
</div>
</ul>
</div>
</div>
{% endblock %}
+4
View File
@@ -26,6 +26,10 @@
<input type="password" placeholder="{{ 'login.password.placeholder' | trans }}" name="_password" id="password" class="form-control peer border-secondary focus:ring-tertiary w-full rounded-md border bg-white p-4 text-base text-gray-700 placeholder-gray-400 focus:ring-1 focus:outline-none" autocomplete="current-password" required>
</div>
<a class="text-secondary" href="{{ path('app_forgot_password_request') }}">
{{ 'login.password.forgotten' | trans }}
</a>
<input type="hidden" name="_csrf_token" data-controller="csrf-protection" value="{{ csrf_token('authenticate') }}">
<div class="checkbox mb-3 flex flex-row gap-2">
+128
View File
@@ -0,0 +1,128 @@
<?php
namespace App\Tests\Entity\UserApi;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use App\Entity\Theme;
use App\Entity\User\UserType;
use App\Factory\CategoryFactory;
use App\Factory\User\UserFactory;
use App\Tests\Fixtures\TestUser;
use App\Tests\Traits\RequestingTestTrait;
use Symfony\Component\HttpFoundation\Response;
use Zenstruck\Foundry\Test\Factories;
use Zenstruck\Foundry\Test\ResetDatabase;
use function Zenstruck\Foundry\Persistence\persist;
class UpdateTest extends ApiTestCase
{
use ResetDatabase;
use Factories;
use RequestingTestTrait;
public function testUpdateProfileFields(): void
{
$user = TestUser::get();
$category = CategoryFactory::createOne(['name' => 'test']);
$theme = persist(Theme::class, ['name' => 'test']);
$uri = \sprintf('/v4/users/%d', $user->getId());
$this->request('PATCH', $uri, [
'headers' => $this->withAuthHeader($user),
'json' => [
'shareLocation' => true,
'isAnonymous' => true,
'referralSource' => 'social_media',
'preferredCategories' => [\sprintf('/v4/categories/%s', $category->getId())],
'preferredThemes' => [\sprintf('/v4/themes/%s', $theme->getId())],
],
], Response::HTTP_OK);
$this->assertJsonContains([
'shareLocation' => true,
'isAnonymous' => true,
'referralSource' => 'social_media',
]);
$data = $this->request('GET', $uri, ['headers' => $this->withAuthHeader($user)])->toArray();
$this->assertCount(1, $data['preferredCategories']);
$this->assertCount(1, $data['preferredThemes']);
}
public function testPrivateFieldsHiddenFromOthers(): void
{
$user = TestUser::get();
$other = UserFactory::createOne(['handle' => 'other_user', 'email' => 'other@example.com']);
$data = $this->request('GET', \sprintf('/v4/users/%d', $user->getId()), [
'headers' => $this->withAuthHeader($other),
], Response::HTTP_OK)->toArray();
$this->assertArrayNotHasKey('shareLocation', $data);
$this->assertArrayNotHasKey('isAnonymous', $data);
$this->assertArrayNotHasKey('referralSource', $data);
}
public function testUpdatePersonFields(): void
{
$user = TestUser::get();
$this->request('PATCH', \sprintf('/v4/users/%d/person', $user->getId()), [
'headers' => $this->withAuthHeader($user),
'json' => [
'taxIdType' => 'passport',
'birthYear' => 1990,
'gender' => 'non_binary',
],
], Response::HTTP_OK);
$this->assertJsonContains([
'taxIdType' => 'passport',
'birthYear' => 1990,
'gender' => 'non_binary',
]);
}
public function testUpdatePersonInvalidBirthYear(): void
{
$user = TestUser::get();
$this->request('PATCH', \sprintf('/v4/users/%d/person', $user->getId()), [
'headers' => $this->withAuthHeader($user),
'json' => ['birthYear' => 1800],
], Response::HTTP_UNPROCESSABLE_ENTITY);
$this->request('PATCH', \sprintf('/v4/users/%d/person', $user->getId()), [
'headers' => $this->withAuthHeader($user),
'json' => ['birthYear' => (int) date('Y') + 1],
], Response::HTTP_UNPROCESSABLE_ENTITY);
}
public function testUpdateOrganizationFields(): void
{
$user = TestUser::get();
$this->request('PATCH', \sprintf('/v4/users/%d', $user->getId()), [
'headers' => $this->withAuthHeader($user),
'json' => ['type' => UserType::Organization->value],
], Response::HTTP_OK);
$this->request('PATCH', \sprintf('/v4/users/%d/organization', $user->getId()), [
'headers' => $this->withAuthHeader($user),
'json' => [
'taxId' => 'G12345678',
'legalName' => 'Test Association',
'taxIdType' => 'national_id',
'legalForm' => 'association',
'publicEntity' => true,
],
], Response::HTTP_OK);
$this->assertJsonContains([
'taxIdType' => 'national_id',
'legalForm' => 'association',
'publicEntity' => true,
]);
}
}
+3 -1
View File
@@ -10,6 +10,7 @@ login:
password:
label: Password
placeholder: TrustNo1
forgotten: Did you forget your password?
rememberme: Remember me
submit: Sign in
oauth: 'You can also login via:'
@@ -49,7 +50,8 @@ password_reset:
clients:
title: Goteo sites
description: This site is an instance of <a class="text-primary" href="/v4">the Goteo v4 API</a>, these clients are connected to it.
description: This site is an instance of <a class="text-secondary" href="/v4">the Goteo v4 API</a>, these clients are connected to it.
noClients: No active clients registered
footer:
legal: