feat: relation revendeur sur Customer/Website + WebsiteConfiguration
Customer : - Ajout revendeurCode (VARCHAR 10, nullable) : stocke le code du revendeur apporteur d'affaire (pas de FK, suppression revendeur sans impact) - Select revendeur dans le formulaire de création client - Champ revendeur dans la fiche client (info + section système) Website : - Ajout revendeurCode (VARCHAR 10, nullable) : même logique que Customer WebsiteConfiguration (nouvelle entité) : - website (ManyToOne CASCADE) : site parent - type (VARCHAR 25) : clé de configuration - value (TEXT) : valeur - Contrainte unique (website_id, type) Formulaire création client : - Select "Revendeur (apporteur d'affaire)" avec liste des revendeurs actifs Fiche client : - Onglet Info : champ code revendeur éditable - Section système : affiche le code revendeur Migrations : ALTER TABLE customer/website ADD revendeur_code, CREATE TABLE website_configuration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
31
migrations/Version20260404193257.php
Normal file
31
migrations/Version20260404193257.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260404193257 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE customer ADD revendeur_code VARCHAR(10) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE customer DROP revendeur_code');
|
||||
}
|
||||
}
|
||||
37
migrations/Version20260404193605.php
Normal file
37
migrations/Version20260404193605.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260404193605 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE website_configuration (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, type VARCHAR(25) NOT NULL, value TEXT NOT NULL, website_id INT NOT NULL, PRIMARY KEY (id))');
|
||||
$this->addSql('CREATE INDEX IDX_8BC287E818F45C82 ON website_configuration (website_id)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_8BC287E818F45C828CDE5729 ON website_configuration (website_id, type)');
|
||||
$this->addSql('ALTER TABLE website_configuration ADD CONSTRAINT FK_8BC287E818F45C82 FOREIGN KEY (website_id) REFERENCES website (id) ON DELETE CASCADE NOT DEFERRABLE');
|
||||
$this->addSql('ALTER TABLE website ADD revendeur_code VARCHAR(10) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE website_configuration DROP CONSTRAINT FK_8BC287E818F45C82');
|
||||
$this->addSql('DROP TABLE website_configuration');
|
||||
$this->addSql('ALTER TABLE website DROP revendeur_code');
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use App\Service\MailerService;
|
||||
use App\Service\MeilisearchService;
|
||||
use App\Service\OvhService;
|
||||
use App\Service\UserManagementService;
|
||||
use App\Repository\RevendeurRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
@@ -47,6 +48,7 @@ class ClientsController extends AbstractController
|
||||
public function create(
|
||||
Request $request,
|
||||
CustomerRepository $customerRepository,
|
||||
RevendeurRepository $revendeurRepository,
|
||||
EntityManagerInterface $em,
|
||||
MeilisearchService $meilisearch,
|
||||
UserManagementService $userService,
|
||||
@@ -57,7 +59,9 @@ class ClientsController extends AbstractController
|
||||
#[Autowire(env: 'STRIPE_SK')] string $stripeSecretKey,
|
||||
): Response {
|
||||
if ('POST' !== $request->getMethod()) {
|
||||
return $this->render('admin/clients/create.html.twig');
|
||||
return $this->render('admin/clients/create.html.twig', [
|
||||
'revendeurs' => $revendeurRepository->findBy(['isActive' => true], ['codeRevendeur' => 'ASC']),
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -223,6 +227,7 @@ class ClientsController extends AbstractController
|
||||
$customer->setZipCode(trim($request->request->getString('zipCode')) ?: null);
|
||||
$customer->setCity(trim($request->request->getString('city')) ?: null);
|
||||
$customer->setTypeCompany(trim($request->request->getString('typeCompany')) ?: null);
|
||||
$customer->setRevendeurCode(trim($request->request->getString('revendeurCode')) ?: null);
|
||||
$customer->setGeoLat(trim($request->request->getString('geoLat')) ?: null);
|
||||
$customer->setGeoLong(trim($request->request->getString('geoLong')) ?: null);
|
||||
}
|
||||
|
||||
@@ -112,6 +112,9 @@ class Customer
|
||||
#[ORM\Column(length: 50, unique: true, nullable: true)]
|
||||
private ?string $codeComptable = null;
|
||||
|
||||
#[ORM\Column(length: 10, nullable: true)]
|
||||
private ?string $revendeurCode = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?\DateTimeImmutable $updatedAt = null;
|
||||
|
||||
@@ -133,6 +136,18 @@ class Customer
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRevendeurCode(): ?string
|
||||
{
|
||||
return $this->revendeurCode;
|
||||
}
|
||||
|
||||
public function setRevendeurCode(?string $revendeurCode): static
|
||||
{
|
||||
$this->revendeurCode = $revendeurCode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function generateCodeComptable(): string
|
||||
{
|
||||
$prefix = '411';
|
||||
|
||||
@@ -38,6 +38,9 @@ class Website
|
||||
#[ORM\Column(length: 20)]
|
||||
private string $state = self::STATE_CREATED;
|
||||
|
||||
#[ORM\Column(length: 10, nullable: true)]
|
||||
private ?string $revendeurCode = null;
|
||||
|
||||
#[ORM\Column]
|
||||
private \DateTimeImmutable $createdAt;
|
||||
|
||||
@@ -110,6 +113,18 @@ class Website
|
||||
return self::STATE_OPEN === $this->state;
|
||||
}
|
||||
|
||||
public function getRevendeurCode(): ?string
|
||||
{
|
||||
return $this->revendeurCode;
|
||||
}
|
||||
|
||||
public function setRevendeurCode(?string $revendeurCode): static
|
||||
{
|
||||
$this->revendeurCode = $revendeurCode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): \DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
|
||||
66
src/Entity/WebsiteConfiguration.php
Normal file
66
src/Entity/WebsiteConfiguration.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\UniqueConstraint(columns: ['website_id', 'type'])]
|
||||
class WebsiteConfiguration
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Website::class)]
|
||||
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
|
||||
private Website $website;
|
||||
|
||||
#[ORM\Column(length: 25)]
|
||||
private string $type;
|
||||
|
||||
#[ORM\Column(type: 'text')]
|
||||
private string $value;
|
||||
|
||||
public function __construct(Website $website, string $type, string $value)
|
||||
{
|
||||
$this->website = $website;
|
||||
$this->type = $type;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getWebsite(): Website
|
||||
{
|
||||
return $this->website;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function setType(string $type): static
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function setValue(string $value): static
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,15 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<label for="revendeurCode" class="block text-xs font-bold uppercase tracking-wider mb-2">Revendeur (apporteur d'affaire)</label>
|
||||
<select id="revendeurCode" name="revendeurCode" class="w-full px-4 py-3 glass text-sm font-bold">
|
||||
<option value="">— Aucun —</option>
|
||||
{% for rev in revendeurs %}
|
||||
<option value="{{ rev.codeRevendeur }}">{{ rev.codeRevendeur }} — {{ rev.raisonSociale ?? rev.user.fullName }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="glass p-6">
|
||||
|
||||
@@ -120,6 +120,10 @@
|
||||
<label for="rna" class="block text-xs font-bold uppercase tracking-wider mb-2">RNA</label>
|
||||
<input type="text" id="rna" name="rna" value="{{ customer.rna }}" maxlength="20" class="w-full px-4 py-3 input-glass text-sm font-medium">
|
||||
</div>
|
||||
<div>
|
||||
<label for="revendeurCode" class="block text-xs font-bold uppercase tracking-wider mb-2">Code revendeur</label>
|
||||
<input type="text" id="revendeurCode" name="revendeurCode" value="{{ customer.revendeurCode }}" maxlength="10" placeholder="EC-XXXX" class="w-full px-4 py-3 input-glass text-sm font-medium font-mono">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -168,6 +172,10 @@
|
||||
<span class="text-gray-400 font-bold uppercase text-[9px] block">Modifie le</span>
|
||||
<span class="font-bold">{{ customer.updatedAt ? customer.updatedAt|date('d/m/Y H:i') : '—' }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-400 font-bold uppercase text-[9px] block">Revendeur</span>
|
||||
<span class="font-mono font-bold">{{ customer.revendeurCode ?? '—' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if customer.user.hasTempPassword %}
|
||||
|
||||
@@ -78,7 +78,7 @@ class ClientsControllerTest extends TestCase
|
||||
$userService = $this->createStub(UserManagementService::class);
|
||||
$logger = $this->createStub(LoggerInterface::class);
|
||||
|
||||
$response = $controller->create($request, $repo, $em, $meilisearch, $userService, $logger, $this->createStub(HttpClientInterface::class), $this->createStub(MailerService::class), $this->createStub(Environment::class), 'sk_test_***');
|
||||
$response = $controller->create($request, $repo, $this->createStub(\App\Repository\RevendeurRepository::class), $em, $meilisearch, $userService, $logger, $this->createStub(HttpClientInterface::class), $this->createStub(MailerService::class), $this->createStub(Environment::class), 'sk_test_***');
|
||||
|
||||
$this->assertInstanceOf(Response::class, $response);
|
||||
}
|
||||
@@ -98,7 +98,7 @@ class ClientsControllerTest extends TestCase
|
||||
$userService->method('createBaseUser')->willThrowException(new \InvalidArgumentException('Champs requis'));
|
||||
$logger = $this->createStub(LoggerInterface::class);
|
||||
|
||||
$response = $controller->create($request, $repo, $em, $meilisearch, $userService, $logger, $this->createStub(HttpClientInterface::class), $this->createStub(MailerService::class), $this->createStub(Environment::class), 'sk_test_***');
|
||||
$response = $controller->create($request, $repo, $this->createStub(\App\Repository\RevendeurRepository::class), $em, $meilisearch, $userService, $logger, $this->createStub(HttpClientInterface::class), $this->createStub(MailerService::class), $this->createStub(Environment::class), 'sk_test_***');
|
||||
|
||||
$this->assertInstanceOf(Response::class, $response);
|
||||
}
|
||||
@@ -118,7 +118,7 @@ class ClientsControllerTest extends TestCase
|
||||
$userService->method('createBaseUser')->willThrowException(new \RuntimeException('DB error'));
|
||||
$logger = $this->createStub(LoggerInterface::class);
|
||||
|
||||
$response = $controller->create($request, $repo, $em, $meilisearch, $userService, $logger, $this->createStub(HttpClientInterface::class), $this->createStub(MailerService::class), $this->createStub(Environment::class), 'sk_test_***');
|
||||
$response = $controller->create($request, $repo, $this->createStub(\App\Repository\RevendeurRepository::class), $em, $meilisearch, $userService, $logger, $this->createStub(HttpClientInterface::class), $this->createStub(MailerService::class), $this->createStub(Environment::class), 'sk_test_***');
|
||||
|
||||
$this->assertInstanceOf(Response::class, $response);
|
||||
}
|
||||
@@ -286,6 +286,7 @@ class ClientsControllerTest extends TestCase
|
||||
$response = $controller->create(
|
||||
$request,
|
||||
$repo,
|
||||
$this->createStub(\App\Repository\RevendeurRepository::class),
|
||||
$this->createStub(EntityManagerInterface::class),
|
||||
$this->createStub(MeilisearchService::class),
|
||||
$userService,
|
||||
@@ -326,6 +327,7 @@ class ClientsControllerTest extends TestCase
|
||||
$response = $controller->create(
|
||||
$request,
|
||||
$repo,
|
||||
$this->createStub(\App\Repository\RevendeurRepository::class),
|
||||
$this->createStub(EntityManagerInterface::class),
|
||||
$this->createStub(MeilisearchService::class),
|
||||
$userService,
|
||||
@@ -369,6 +371,7 @@ class ClientsControllerTest extends TestCase
|
||||
$response = $controller->create(
|
||||
$request,
|
||||
$repo,
|
||||
$this->createStub(\App\Repository\RevendeurRepository::class),
|
||||
$this->createStub(EntityManagerInterface::class),
|
||||
$meilisearch,
|
||||
$userService,
|
||||
|
||||
Reference in New Issue
Block a user