Compare commits

..

7 Commits

Author SHA1 Message Date
Julien Rosset 8ccba31bf4 Material type: add unit (to pages) 10 months ago
Julien Rosset c2da5f7aaf Entity name : unique 10 months ago
Julien Rosset 21c7a0c317 Material type: add unit 10 months ago
Julien Rosset c5730cc3bd Add configuration pages for material 10 months ago
Julien Rosset d98e3f0dfc Add configuration pages for machines 10 months ago
Julien Rosset fae1d06e93 Code cleaning 10 months ago
Julien Rosset 037806df82 Material types edition : better form 10 months ago

@ -0,0 +1,35 @@
<?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 Version20250530165755 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(<<<'SQL'
ALTER TABLE material_type ADD unit VARCHAR(5) DEFAULT NULL
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE material_type DROP unit
SQL);
}
}

@ -0,0 +1,53 @@
<?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 Version20250530165852 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(<<<'SQL'
CREATE UNIQUE INDEX UNIQ_1505DF845E237E06 ON machine (name)
SQL);
$this->addSql(<<<'SQL'
CREATE UNIQUE INDEX UNIQ_7CBE75955E237E06 ON material (name)
SQL);
$this->addSql(<<<'SQL'
CREATE UNIQUE INDEX UNIQ_D8B63A1C5E237E06 ON material_type (name)
SQL);
$this->addSql(<<<'SQL'
CREATE UNIQUE INDEX UNIQ_DA88B1375E237E06 ON recipe (name)
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
DROP INDEX UNIQ_1505DF845E237E06 ON machine
SQL);
$this->addSql(<<<'SQL'
DROP INDEX UNIQ_7CBE75955E237E06 ON material
SQL);
$this->addSql(<<<'SQL'
DROP INDEX UNIQ_D8B63A1C5E237E06 ON material_type
SQL);
$this->addSql(<<<'SQL'
DROP INDEX UNIQ_DA88B1375E237E06 ON recipe
SQL);
}
}

@ -0,0 +1,96 @@
<?php
namespace App\Controller\Config;
use App\Entity\Machine;
use App\Form\Config\MachineEditForm;
use App\Misc\FlashType;
use App\Repository\MachineRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
/**
* Controller for the configuration pages of machines
*/
#[Route('/Config/Machine')]
class MachineController extends AbstractController {
private readonly EntityManagerInterface $entityManager;
/**
* @var MachineRepository The machine repository
*/
private readonly MachineRepository $machineRepository;
/**
* Initialization
*
* @param EntityManagerInterface $entityManager The entity manager
* @param MachineRepository $machineRepository The machine repository
*/
public function __construct (EntityManagerInterface $entityManager, MachineRepository $machineRepository) {
$this->entityManager = $entityManager;
$this->machineRepository = $machineRepository;
}
/**
* List of machines
*
* @return Response The response
*/
#[Route('/', name: 'config_machine_list', alias: 'config_machine')]
public function list (): Response {
return $this->render(
'Config/Machine/List.html.twig',
[
'machines' => $this->machineRepository->findAll(),
]
);
}
/**
* Edit/Create a machine
*
* @param Request $request The request
* @param Machine|null $machine The machine to edit
*
* @return Response The response
*/
#[Route('/Create', name: 'config_machine_create')]
#[Route('/Edit-{id}', name: 'config_machine_edit')]
public function edit (Request $request, ?Machine $machine = null): Response {
$machine ??= new Machine();
$form = $this->createForm(MachineEditForm::class, $machine);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->entityManager->persist($machine);
$this->entityManager->flush();
$this->addFlash(FlashType::SUCCESS, 'La machine a bien été enregistré.');
return $this->redirectToRoute('config_machine_list');
}
return $this->render('Config/Machine/Edit.html.twig', [
'machine' => $machine,
'form' => $form->createView(),
]);
}
/**
* Delete a machine
*
* @param Machine $machine The machine to delete
*
* @return Response The response
*/
#[Route('/Delete-{id}', name: 'config_machine_delete')]
public function delete (Machine $machine): Response {
$this->entityManager->remove($machine);
$this->entityManager->flush();
$this->addFlash(FlashType::SUCCESS, 'La machine a bien été supprimé.');
return $this->redirectToRoute('config_machine_list');
}
}

@ -0,0 +1,99 @@
<?php
namespace App\Controller\Config;
use App\Entity\Machine;
use App\Entity\Material;
use App\Form\Config\MachineEditForm;
use App\Form\Config\MaterialEditForm;
use App\Misc\FlashType;
use App\Repository\MachineRepository;
use App\Repository\MaterialRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
/**
* Controller for the configuration pages of material
*/
#[Route('/Config/Material')]
class MaterialController extends AbstractController {
private readonly EntityManagerInterface $entityManager;
/**
* @var MaterialRepository The material repository
*/
private readonly MaterialRepository $materialRepository;
/**
* Initialization
*
* @param EntityManagerInterface $entityManager The entity manager
* @param MaterialRepository $materialRepository The material repository
*/
public function __construct (EntityManagerInterface $entityManager, MaterialRepository $materialRepository) {
$this->entityManager = $entityManager;
$this->materialRepository = $materialRepository;
}
/**
* List of materials
*
* @return Response The response
*/
#[Route('/', name: 'config_material_list', alias: 'config_material')]
public function list (): Response {
return $this->render(
'Config/Material/List.html.twig',
[
'materials' => $this->materialRepository->findAll(),
]
);
}
/**
* Edit/Create a material
*
* @param Request $request The request
* @param Material|null $material The material to edit
*
* @return Response The response
*/
#[Route('/Create', name: 'config_material_create')]
#[Route('/Edit-{id}', name: 'config_material_edit')]
public function edit (Request $request, ?Material $material = null): Response {
$material ??= new Material();
$form = $this->createForm(MaterialEditForm::class, $material);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->entityManager->persist($material);
$this->entityManager->flush();
$this->addFlash(FlashType::SUCCESS, 'Le matériau a bien été enregistré.');
return $this->redirectToRoute('config_material_list');
}
return $this->render('Config/Material/Edit.html.twig', [
'material' => $material,
'form' => $form->createView(),
]);
}
/**
* Delete a material
*
* @param Material $material The material to delete
*
* @return Response The response
*/
#[Route('/Delete-{id}', name: 'config_material_delete')]
public function delete (Material $material): Response {
$this->entityManager->remove($material);
$this->entityManager->flush();
$this->addFlash(FlashType::SUCCESS, 'Le matériau a bien été supprimé.');
return $this->redirectToRoute('config_material_list');
}
}

@ -13,7 +13,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
/**
* Controllers for the configuration pages
* Controller for the configuration pages of material types
*/
#[Route('/Config/MaterialType')]
class MaterialTypeController extends AbstractController {
@ -24,8 +24,9 @@ class MaterialTypeController extends AbstractController {
private readonly MaterialTypeRepository $materialTypeRepository;
/**
* Initialisation
* Initialization
*
* @param EntityManagerInterface $entityManager The entity manager
* @param MaterialTypeRepository $materialTypeRepository The material type repository
*/
public function __construct (EntityManagerInterface $entityManager, MaterialTypeRepository $materialTypeRepository) {
@ -51,6 +52,9 @@ class MaterialTypeController extends AbstractController {
/**
* Edit/Create a material type
*
* @param Request $request The request
* @param MaterialType|null $materialType The material type to edit
*
* @return Response The response
*/
#[Route('/Create', name: 'config_materialType_create')]
@ -69,6 +73,7 @@ class MaterialTypeController extends AbstractController {
}
return $this->render('Config/MaterialType/Edit.html.twig', [
'materialType' => $materialType,
'form' => $form->createView(),
]);
}
@ -76,6 +81,8 @@ class MaterialTypeController extends AbstractController {
/**
* Delete a material type
*
* @param MaterialType $materialType The material type to delete
*
* @return Response The response
*/
#[Route('/Delete-{id}', name: 'config_materialType_delete')]

@ -6,6 +6,7 @@ use App\Repository\MachineRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Stringable;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
@ -14,7 +15,7 @@ use Symfony\Component\Validator\Constraints as Assert;
*/
#[ORM\Entity(repositoryClass: MachineRepository::class)]
#[UniqueEntity(fields: ['name'], message: 'Il existe déjà une machine avec ce nom')]
class Machine {
class Machine implements Stringable {
use TBaseEntity;
use TNamedEntity;

@ -6,6 +6,7 @@ use App\Repository\MaterialRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Stringable;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
@ -14,7 +15,7 @@ use Symfony\Component\Validator\Constraints as Assert;
*/
#[ORM\Entity(repositoryClass: MaterialRepository::class)]
#[UniqueEntity(fields: ['name'], message: 'Il existe déjà un matériau avec ce nom')]
class Material {
class Material implements Stringable {
use TBaseEntity;
use TNamedEntity;

@ -4,6 +4,7 @@ namespace App\Entity;
use App\Repository\MaterialTypeRepository;
use Doctrine\ORM\Mapping as ORM;
use Stringable;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
@ -12,10 +13,17 @@ use Symfony\Component\Validator\Constraints as Assert;
*/
#[ORM\Entity(repositoryClass: MaterialTypeRepository::class)]
#[UniqueEntity(fields: ['name'], message: 'Il existe déjà un type de matériau avec ce nom')]
class MaterialType {
class MaterialType implements Stringable {
use TBaseEntity;
use TNamedEntity;
/**
* @var string|null The unit
*/
#[ORM\Column(length: 5, nullable: true)]
#[Assert\NotBlank(message: 'Veuillez saisir une unité', allowNull: true)]
private ?string $unit = null;
/**
* @var string|null The “stack” name
*/
@ -31,6 +39,26 @@ class MaterialType {
#[Assert\Positive(message: 'La taille de pile doit être strictement positive')]
private ?int $stackSize = null;
/**
* The unit
*
* @return string|null The unit
*/
public function getUnit (): ?string {
return $this->unit;
}
/**
* Set the unit
*
* @param string|null $unit The unit
*
* @return $this
*/
public function setUnit (?string $unit): self {
$this->unit = $unit;
return $this;
}
/**
* The “stack” name
*

@ -6,13 +6,14 @@ use App\Repository\RecipeRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Stringable;
use Symfony\Component\Validator\Constraints as Assert;
/**
* A crafting recipe
*/
#[ORM\Entity(repositoryClass: RecipeRepository::class)]
class Recipe {
class Recipe implements Stringable {
use TBaseEntity;
use TNamedEntity;

@ -12,8 +12,8 @@ trait TNamedEntity {
/**
* @var string|null The name
*/
#[ORM\Column(length: 50)]
#[Assert\NotBlank(message: 'Veuillez saisir un email')]
#[ORM\Column(length: 50, unique: true)]
#[Assert\NotBlank(message: 'Veuillez saisir un nom')]
private ?string $name = null;
/**
@ -36,4 +36,11 @@ trait TNamedEntity {
return $this;
}
/**
* @inheritDoc
*/
public function __toString (): string {
return $this->getName();
}
}

@ -0,0 +1,46 @@
<?php
namespace App\Form\Config;
use App\Entity\Machine;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* The form for editing a material type
*/
class MachineEditForm extends AbstractType {
/**
* @inheritDoc
*/
public function configureOptions (OptionsResolver $resolver): void {
$resolver->setDefaults(
[
'data_class' => Machine::class,
]
);
}
/**
* @inheritDoc
*/
public function buildForm (FormBuilderInterface $builder, array $options): void {
$builder
->add('name', null, [
'label' => 'Nom',
])
->add('labelExtraInfo1', null, [
'label' => 'Nom d\'information complémentaire n° 1',
'required' => false,
])
->add('labelExtraInfo2', null, [
'label' => 'Nom d\'information complémentaire n° 2',
'required' => false,
])
->add('submit', SubmitType::class, [
'label' => 'Enregistrer',
]);
}
}

@ -0,0 +1,44 @@
<?php
namespace App\Form\Config;
use App\Entity\Material;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* The form for editing a material
*/
class MaterialEditForm extends AbstractType {
/**
* @inheritDoc
*/
public function configureOptions (OptionsResolver $resolver): void {
$resolver->setDefaults(
[
'data_class' => Material::class,
]
);
}
/**
* @inheritDoc
*/
public function buildForm (FormBuilderInterface $builder, array $options): void {
$builder
->add('name', null, [
'label' => 'Nom',
])
->add('type', null, [
'label' => 'Type',
])
->add('isCraftableByDefault', null, [
'label' => 'Est-ce que ce matériel est craftable par défaut ?',
])
->add('submit', SubmitType::class, [
'label' => 'Enregistrer',
]);
}
}

@ -12,7 +12,6 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
* The form for editing a material type
*/
class MaterialTypeEditForm extends AbstractType {
/**
* @inheritDoc
*/
@ -32,6 +31,10 @@ class MaterialTypeEditForm extends AbstractType {
->add('name', null, [
'label' => 'Nom',
])
->add('unit', null, [
'label' => 'Unité',
'required' => false,
])
->add('stackName', null, [
'label' => 'Nom de la pile',
])

@ -0,0 +1,15 @@
{% extends 'base.html.twig' %}
{% block title %}{% if machine.id is null %}Création {% else %}Modification{% endif %} machine - {{ parent() }}{% endblock %}
{% block mainContent %}
<h1>
{% if machine.id is null %}
Création nouvelle machine
{% else %}
Modification du la machine "{{ machine.name }}"
{% endif %}
</h1>
{{ form(form) }}
<a href="{{ path('config_machine_list') }}" class="btn btn-danger">Annuler</a>
{% endblock %}

@ -0,0 +1,62 @@
{% extends '/base.html.twig' %}
{% block title %}Liste des machines - {{ parent() }}{% endblock %}
{% block importmap %}{{ importmap(['app', 'datatables2']) }}{% endblock %}
{% block mainContent %}
<h1>Liste des machines</h1>
<div class="d-flex">
<div class="table-responsive mnw-25">
<table class="table table-sm table-striped table-hover table-bordered table-datatable2">
<thead>
<tr>
<th scope="col" data-sort-onLoad="1" class="align-middle">Nom</th>
<th scope="col" data-sort="false" class="fit-content align-middle">
<a href="{{ path('config_machine_create') }}" class="btn btn-primary" data-bs-toggle="tooltip" data-bs-title="Ajouter">
<i class="fa-solid fa-square-plus"></i>
</a>
</th>
</tr>
</thead>
<tbody>
{% for machine in machines %}
<tr>
<td>{{ machine.name }}</td>
<td class="fit-content">
<a href="{{ path('config_machine_edit', {id: machine.id}) }}"
class="text-primary me-2"
data-bs-toggle="tooltip"
data-bs-title="Éditer"
><i class="fa-solid fa-pen"></i></a>
<a href="#" class="text-danger" id="btDelete" data-bs-toggle="tooltip" data-bs-title="Supprimer">
<span data-bs-toggle="modal"
data-bs-target="#deleteConfirmation"
data-modal-dynamic-link-url="{{ path('config_machine_delete', {id: machine.id}) }}"
><i class="fa-solid fa-xmark"></i></span>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="modal modal-dynamic fade" id="deleteConfirmation" tabindex="-1" aria-label="btDelete" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5">Suppression</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Êtes-vous sûr de vouloir supprimer cette machine ?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Non</button>
<a href="#" class="modal-confirm-link btn btn-danger">Oui</a>
</div>
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,15 @@
{% extends 'base.html.twig' %}
{% block title %}{% if material.id is null %}Création {% else %}Modification{% endif %} matériau - {{ parent() }}{% endblock %}
{% block mainContent %}
<h1>
{% if material.id is null %}
Création nouveau matériau
{% else %}
Modification du matériau "{{ material.name }}"
{% endif %}
</h1>
{{ form(form) }}
<a href="{{ path('config_material_list') }}" class="btn btn-danger">Annuler</a>
{% endblock %}

@ -0,0 +1,64 @@
{% extends '/base.html.twig' %}
{% block title %}Liste des matériaux - {{ parent() }}{% endblock %}
{% block importmap %}{{ importmap(['app', 'datatables2']) }}{% endblock %}
{% block mainContent %}
<h1>Liste des matériaux</h1>
<div class="d-flex">
<div class="table-responsive mnw-25">
<table class="table table-sm table-striped table-hover table-bordered table-datatable2">
<thead>
<tr>
<th scope="col" data-sort-onLoad="1" class="align-middle">Nom</th>
<th scope="col" class="align-middle">Type</th>
<th scope="col" data-sort="false" class="fit-content align-middle">
<a href="{{ path('config_material_create') }}" class="btn btn-primary" data-bs-toggle="tooltip" data-bs-title="Ajouter">
<i class="fa-solid fa-square-plus"></i>
</a>
</th>
</tr>
</thead>
<tbody>
{% for material in materials %}
<tr>
<td>{{ material.name }}</td>
<td>{{ material.type.name }}</td>
<td class="fit-content">
<a href="{{ path('config_material_edit', {id: material.id}) }}"
class="text-primary me-2"
data-bs-toggle="tooltip"
data-bs-title="Éditer"
><i class="fa-solid fa-pen"></i></a>
<a href="#" class="text-danger" id="btDelete" data-bs-toggle="tooltip" data-bs-title="Supprimer">
<span data-bs-toggle="modal"
data-bs-target="#deleteConfirmation"
data-modal-dynamic-link-url="{{ path('config_material_delete', {id: material.id}) }}"
><i class="fa-solid fa-xmark"></i></span>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="modal modal-dynamic fade" id="deleteConfirmation" tabindex="-1" aria-label="btDelete" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5">Suppression</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Êtes-vous sûr de vouloir supprimer ce matériau ?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Non</button>
<a href="#" class="modal-confirm-link btn btn-danger">Oui</a>
</div>
</div>
</div>
</div>
{% endblock %}

@ -1,8 +1,15 @@
{% extends 'base.html.twig' %}
{% block title %}Modification type de matériau - {{ parent() }}{% endblock %}
{% block title %}{% if materialType.id is null %}Création {% else %}Modification{% endif %} type de matériau - {{ parent() }}{% endblock %}
{% block mainContent %}
<h1>Modification type de matériau</h1>
<h1>
{% if materialType.id is null %}
Création nouveau type de matériau
{% else %}
Modification du type de matériau "{{ materialType.name }}"
{% endif %}
</h1>
{{ form(form) }}
<a href="{{ path('config_materialType_list') }}" class="btn btn-danger">Annuler</a>
{% endblock %}

@ -11,6 +11,7 @@
<thead>
<tr>
<th scope="col" data-sort-onLoad="1" class="align-middle">Nom</th>
<th scope="col" data-sort-onLoad="1" class="align-middle">Unit</th>
<th scope="col" data-sort="false" class="fit-content align-middle">
<a href="{{ path('config_materialType_create') }}" class="btn btn-primary" data-bs-toggle="tooltip" data-bs-title="Ajouter">
<i class="fa-solid fa-square-plus"></i>
@ -22,6 +23,7 @@
{% for materialType in materialTypes %}
<tr>
<td>{{ materialType.name }}</td>
<td>{{ materialType.unit }}</td>
<td class="fit-content">
<a href="{{ path('config_materialType_edit', {id: materialType.id}) }}"
class="text-primary me-2"

@ -13,6 +13,8 @@
</a>
<ul class="dropdown-menu" aria-labelledby="dropdown-config">
<li><a href="{{ path('config_materialType_list') }}" class="dropdown-item">Types de matériaux</a></li>
<li><a href="{{ path('config_machine_list') }}" class="dropdown-item">Machines</a></li>
<li><a href="{{ path('config_material_list') }}" class="dropdown-item">Matériaux</a></li>
</ul>
</li>
</ul>

Loading…
Cancel
Save