diff --git a/src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdPartyCivility.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php similarity index 79% rename from src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdPartyCivility.php rename to src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php index 36325deaa..f398e9e1d 100644 --- a/src/Bundle/ChillThirdPartyBundle/DataFixtures/ORM/LoadThirdPartyCivility.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadCivility.php @@ -2,21 +2,21 @@ namespace Chill\ThirdPartyBundle\DataFixtures\ORM; -use Chill\ThirdPartyBundle\Entity\ThirdPartyCivility; +use Chill\MainBundle\Entity\Civility; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface; use Doctrine\Persistence\ObjectManager; /** * Class LoadThirdPartyCivility - * @package Chill\ThirdPartyBundle\DataFixtures\ORM + * * @author Mathieu Jaumotte mathieu.jaumotte@champs-libres.coop */ -class LoadThirdPartyCivility extends Fixture implements FixtureGroupInterface +class LoadCivility extends Fixture implements FixtureGroupInterface { public static function getGroups(): array { - return ['thirdparty_civilities']; + return ['civilities']; } public function load(ObjectManager $manager): void @@ -34,7 +34,6 @@ class LoadThirdPartyCivility extends Fixture implements FixtureGroupInterface ]; foreach ( $civilities as $val) { - print "Creating thirdparty civility : " . $val['name']['fr'] . "\n"; $civility = (new ThirdPartyCivility()) ->setName($val['name']) ->setActive(true); diff --git a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdPartyCivility.php b/src/Bundle/ChillMainBundle/Entity/Civility.php similarity index 72% rename from src/Bundle/ChillThirdPartyBundle/Entity/ThirdPartyCivility.php rename to src/Bundle/ChillMainBundle/Entity/Civility.php index 6d09f05f1..7bed4f633 100644 --- a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdPartyCivility.php +++ b/src/Bundle/ChillMainBundle/Entity/Civility.php @@ -20,16 +20,15 @@ * along with this program. If not, see . */ -namespace Chill\ThirdPartyBundle\Entity; +namespace Chill\MainBundle\Entity; -use Chill\ThirdPartyBundle\Repository\ThirdPartyCivilityRepository; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Table(name="chill_3party.party_civility") - * @ORM\Entity(repositoryClass=ThirdPartyCivilityRepository::class) + * @ORM\Table(name="chill_main_civility") + * @ORM\Entity */ -class ThirdPartyCivility +class Civility { /** * @ORM\Id @@ -41,12 +40,17 @@ class ThirdPartyCivility /** * @ORM\Column(type="json") */ - private $name = []; + private array $name = []; + + /** + * @ORM\Column(type="json") + */ + private array $abbreviation = []; /** * @ORM\Column(type="boolean") */ - private $active = true; + private bool $active = true; public function getId(): ?int { @@ -76,4 +80,22 @@ class ThirdPartyCivility return $this; } + + /** + * @return array + */ + public function getAbbreviation(): array + { + return $this->abbreviation; + } + + /** + * @param array $abbreviation + * @return Civility + */ + public function setAbbreviation(array $abbreviation): self + { + $this->abbreviation = $abbreviation; + return $this; + } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php b/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php index 1068522c9..2b20dbd98 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ChillCollectionType.php @@ -24,14 +24,14 @@ use Symfony\Component\Form\FormInterface; /** * Available options : - * + * * - `button_add_label` * - `button_remove_label` * - `identifier`: an identifier to identify the kind of collecton. Useful if some * javascript should be launched associated to `add_entry`, `remove_entry` events. - * + * - `empty_collection_explain` + * * - * @author Julien Fastré */ class ChillCollectionType extends AbstractType { @@ -41,10 +41,11 @@ class ChillCollectionType extends AbstractType ->setDefaults([ 'button_add_label' => 'Add an entry', 'button_remove_label' => 'Remove entry', - 'identifier' => '' + 'identifier' => '', + 'empty_collection_explain' => '', ]); } - + public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['button_add_label'] = $options['button_add_label']; @@ -52,8 +53,9 @@ class ChillCollectionType extends AbstractType $view->vars['allow_delete'] = (int) $options['allow_delete']; $view->vars['allow_add'] = (int) $options['allow_add']; $view->vars['identifier'] = $options['identifier']; + $view->vars['empty_collection_explain'] = $options['empty_collection_explain']; } - + public function getParent() { return \Symfony\Component\Form\Extension\Core\Type\CollectionType::class; diff --git a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyCivilityRepository.php b/src/Bundle/ChillMainBundle/Repository/CivilityRepository.php similarity index 100% rename from src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyCivilityRepository.php rename to src/Bundle/ChillMainBundle/Repository/CivilityRepository.php diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/collection/index.js b/src/Bundle/ChillMainBundle/Resources/public/lib/collection/index.js index 0a4955207..b763c4ddb 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/collection/index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/collection/index.js @@ -1,27 +1,27 @@ /** * Javascript file which handle ChillCollectionType - * - * Two events are emitted by this module, both on window and on collection / ul. - * + * + * Two events are emitted by this module, both on window and on collection / ul. + * * Collection (an UL element) and entry (a li element) are associated with those * events. - * + * * ``` * window.addEventListener('collection-add-entry', function(e) { * console.log(e.detail.collection); * console.log(e.detail.entry); * }); - * + * * window.addEventListener('collection-remove-entry', function(e) { * console.log(e.detail.collection); * console.log(e.detail.entry); * }); - * + * * collection.addEventListener('collection-add-entry', function(e) { * console.log(e.detail.collection); * console.log(e.detail.entry); * }); - * + * * collection.addEventListener('collection-remove-entry', function(e) { * console.log(e.detail.collection); * console.log(e.detail.entry); @@ -38,7 +38,7 @@ class CollectionEvent { } /** - * + * * @param {type} button * @returns {handleAdd} */ @@ -47,6 +47,7 @@ var handleAdd = function(button) { form_name = button.dataset.collectionAddTarget, prototype = button.dataset.formPrototype, collection = document.querySelector('ul[data-collection-name="'+form_name+'"]'), + empty_explain = collection.querySelector('li[data-collection-empty-explain]'), entry = document.createElement('li'), event = new CustomEvent('collection-add-entry', { detail: { collection: collection, entry: entry } }), counter = collection.childNodes.length, @@ -56,8 +57,11 @@ var handleAdd = function(button) { entry.innerHTML = content; entry.classList.add('entry'); initializeRemove(collection, entry); + if (empty_explain !== null) { + empty_explain.remove(); + } collection.appendChild(entry); - + collection.dispatchEvent(event); window.dispatchEvent(event); }; @@ -70,30 +74,30 @@ var initializeRemove = function(collection, entry) { allowDelete = collection.dataset.collectionAllowDelete, event = new CustomEvent('collection-remove-entry', { detail: { collection: collection, entry: entry } }) ; - + if (allowDelete === '0' && isPersisted === '1') { return; } - + button.classList.add('btn', 'btn-delete', 'remove-entry'); button.textContent = content; - + button.addEventListener('click', function(e) { e.preventDefault(); entry.remove(); collection.dispatchEvent(event); window.dispatchEvent(event); }); - + entry.appendChild(button); }; window.addEventListener('load', function() { - var + var addButtons = document.querySelectorAll("button[data-collection-add-target]"), collections = document.querySelectorAll("ul[data-collection-name]") ; - + for (let i = 0; i < addButtons.length; i ++) { let addButton = addButtons[i]; addButton.addEventListener('click', function(e) { @@ -101,11 +105,15 @@ window.addEventListener('load', function() { handleAdd(e.target); }); } - + for (let i = 0; i < collections.length; i ++) { let entries = collections[i].querySelectorAll(':scope > li'); - + for (let j = 0; j < entries.length; j ++) { + console.log(entries[j].dataset); + if (entries[j].dataset.collectionEmptyExplain === "1") { + continue; + } initializeRemove(collections[i], entries[j]); } } diff --git a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig index 2179d1366..1799059e4 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig @@ -168,6 +168,10 @@ {{ form_widget(entry) }} + {% else %} +
  • + {{ form.vars.empty_collection_explain|default('No item')|trans }} +
  • {% endfor %} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20211007150019.php b/src/Bundle/ChillMainBundle/migrations/Version20211007150019.php new file mode 100644 index 000000000..b81c7f74e --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20211007150019.php @@ -0,0 +1,31 @@ +addSql('CREATE SEQUENCE chill_main_civility_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_main_civility (id INT NOT NULL, name JSON NOT NULL, abbreviation JSON NOT NULL, active BOOLEAN NOT NULL, PRIMARY KEY(id))'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP SEQUENCE chill_main_civility_id_seq'); + $this->addSql('DROP TABLE chill_main_civility'); + } +} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue index 0d7a3a88b..f91f3b16b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue @@ -1,18 +1,23 @@ diff --git a/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php b/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php index 224280d85..8078f720a 100644 --- a/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php +++ b/src/Bundle/ChillThirdPartyBundle/Controller/ThirdPartyController.php @@ -8,6 +8,7 @@ use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\ThirdPartyBundle\Repository\ThirdPartyACLAwareRepositoryInterface; use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Symfony\Component\HttpFoundation\Request; @@ -67,115 +68,14 @@ final class ThirdPartyController extends CRUDController $paginator->getCurrentPageFirstItemNumber()); } - - /** - * @Route("/new", name="chill_3party_3party_new") - */ - public function newActionkk(Request $request) + protected function onPostCheckACL($action, Request $request, $entity): ?Response { - $this->denyAccessUnlessGranted(ThirdPartyVoter::CREATE); - - $centers = []; - - $thirdParty = new ThirdParty(); - $thirdParty->setCenters(new ArrayCollection($centers)); - - $form = $this->createForm(ThirdPartyType::class, $thirdParty, [ - 'usage' => 'create' - ]); - $form->add('submit', SubmitType::class); - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $em = $this->getDoctrine()->getManager(); - $em->persist($thirdParty); - $em->flush(); - - $this->addFlash('success', - $this->translator->trans("Third party created") - ); - - return $this->redirectToRoute('chill_3party_3party_show', [ - 'thirdparty_id' => $thirdParty->getId() - ]); - - } elseif ($form->isSubmitted()) { - $msg = $this->translator->trans('This form contains errors'); - $this->addFlash('error', $msg); - } - - return $this->render('@ChillThirdParty/ThirdParty/new.html.twig', [ - 'form' => $form->createView(), - 'thirdParty' => $thirdParty - ]); - } - - /** - * @Route("/{thirdparty_id}/update", name="chill_3party_3party_update") - * @ParamConverter("thirdParty", options={"id": "thirdparty_id"}) - */ - public function updateActionkk(ThirdParty $thirdParty, Request $request) - { - $this->denyAccessUnlessGranted(ThirdPartyVoter::CREATE); - - $repository = $this->getDoctrine()->getManager() - ->getRepository(ThirdParty::class); - - $centers = $repository->findAll(); - - // we want to keep centers the users has no access to. So we will add them - // later if they are removed. (this is a ugly hack but it will works - $centersAssociatedNotForUsers = \array_diff( - $thirdParty->getCenters()->toArray(), - $centers); - - $form = $this->createForm(ThirdPartyType::class, $thirdParty, [ - 'usage' => 'create' - ]); - $form->add('submit', SubmitType::class); - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - // re-add centers the user has no accesses: - foreach ($centersAssociatedNotForUsers as $c) { - $thirdParty->addCenter($c); + if ('edit' === $action || 'view' === $action) { + if ($entity->isChild()) { + throw $this->createAccessDeniedException(); } - - $em = $this->getDoctrine()->getManager(); - $em->flush(); - - $this->addFlash('success', - $this->translator->trans("Third party updated") - ); - - return $this->redirectToRoute('chill_3party_3party_show', [ - 'thirdparty_id' => $thirdParty->getId() - ]); - - } elseif ($form->isSubmitted()) { - $msg = $this->translator->trans('This form contains errors'); - $this->addFlash('error', $msg); } - return $this->render('@ChillThirdParty/ThirdParty/update.html.twig', [ - 'form' => $form->createView(), - 'thirdParty' => $thirdParty - ]); + return null; } - - /** - * @Route("/{thirdparty_id}/show", name="chill_3party_3party_show") - * @ParamConverter("thirdParty", options={"id": "thirdparty_id"}) - */ - public function showAction(ThirdParty $thirdParty, Request $request) - { - $this->denyAccessUnlessGranted(ThirdPartyVoter::SHOW, $thirdParty); - - return $this->render('@ChillThirdParty/ThirdParty/show.html.twig', [ - 'thirdParty' => $thirdParty - ]); - } - } diff --git a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php index 38ee0e5be..288715a8b 100644 --- a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php +++ b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php @@ -22,6 +22,9 @@ namespace Chill\ThirdPartyBundle\Entity; +use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; +use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; +use Chill\MainBundle\Entity\Civility; use Chill\MainBundle\Entity\User; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\Collection; @@ -31,6 +34,7 @@ use Symfony\Component\Validator\Constraints as Assert; use Chill\MainBundle\Entity\Address; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Serializer\Annotation\Groups; +use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint; /** * ThirdParty is a party recorded in the database. @@ -39,14 +43,13 @@ use Symfony\Component\Serializer\Annotation\Groups; * all users with the right 'CHILL_3PARTY_3PARTY_SEE', 'CHILL_3PARTY_3 to see, select and edit parties for this * center. * - * @ORM\Table(name="chill_3party.third_party") * @ORM\Entity + * @ORM\Table(name="chill_3party.third_party") * @DiscriminatorMap(typeProperty="type", mapping={ * "thirdparty"=ThirdParty::class * }) - * @ORM\HasLifecycleCallbacks() */ -class ThirdParty +class ThirdParty implements TrackCreationInterface, TrackUpdateInterface { /** * @var int @@ -54,7 +57,15 @@ class ThirdParty * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ - private $id; + private ?int $id = null; + + const KIND_CONTACT = 'contact'; + const KIND_COMPANY = 'company'; + + /** + * @ORM\Column(name="kind", type="string", length="20", options={"default":""}) + */ + private ?string $kind = ""; /** * @var string @@ -62,7 +73,7 @@ class ThirdParty * @Assert\Length(min="2") * @Groups({"read", "write"}) */ - private $name; + private ?string $name = ""; /** * [fr] Raison sociale @@ -71,7 +82,12 @@ class ThirdParty * @Assert\Length(min="3") * @Groups({"read", "write"}) */ - private $nameCompany; + private ?string $nameCompany = ""; + + /** + * @ORM\Column(name="canonicalized", type="text", options={"default":""}) + */ + private ?string $canonicalized = ""; /** * [fr] Sigle @@ -80,7 +96,7 @@ class ThirdParty * @Assert\Length(min="2") * @Groups({"read", "write"}) */ - private $acronym; + private ?string $acronym = ""; /** * @var ThirdPartyCategory @@ -89,18 +105,20 @@ class ThirdParty * joinColumns={@ORM\JoinColumn(name="thirdparty_id", referencedColumnName="id")}, * inverseJoinColumns={@ORM\JoinColumn(name="category_id", referencedColumnName="id")}) */ - private $categories; + private Collection $categories; /** * @var array|null * @ORM\Column(name="types", type="json", nullable=true) - * @Assert\Count(min=1) */ private $types; /** * Contact Persons: One Institutional ThirdParty has Many Contact Persons - * @ORM\OneToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty", mappedBy="parent") + * @ORM\OneToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty", mappedBy="parent", + * cascade={"persist"}) + * @var ThirdParty[]|Collection + * @Assert\Valid(traverse=true) */ private Collection $children; @@ -108,23 +126,24 @@ class ThirdParty * Institutional ThirdParty: Many Contact Persons have One Institutional ThirdParty * @ORM\ManyToOne(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty", inversedBy="children") * @ORM\JoinColumn(name="parent_id", referencedColumnName="id") + * @Groups({"read"}) */ private ?ThirdParty $parent; /** - * @var ThirdPartyCivility - * @ORM\OneToOne(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdPartyCivility") - * @ORM\JoinColumn(name="civility", referencedColumnName="id", nullable=true) + * @var Civility + * @ORM\ManyToOne(targetEntity=Civility::class) + * ORM\JoinColumn(name="civility", referencedColumnName="id", nullable=true) */ - private $civility; + private ?Civility $civility; /** * [fr] Qualité * @var ThirdPartyProfession - * @ORM\OneToOne(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdPartyProfession") - * @ORM\JoinColumn(name="profession", referencedColumnName="id", nullable=true) + * @ORM\ManyToOne(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdPartyProfession") + * ORM\JoinColumn(name="profession", referencedColumnName="id", nullable=true) */ - private $profession; + private ?ThirdPartyProfession $profession; /** * @var string|null @@ -132,9 +151,10 @@ class ThirdParty * @Assert\Regex("/^([\+{1}])([0-9\s*]{4,20})$/", * message="Invalid phone number: it should begin with the international prefix starting with ""+"", hold only digits and be smaller than 20 characters. Ex: +33123456789" * ) + * @PhonenumberConstraint(type="any") * @Groups({"read", "write"}) */ - private $telephone; + private ?string $telephone = null; /** * @var string|null @@ -142,7 +162,7 @@ class ThirdParty * @Assert\Email(checkMX=false) * @Groups({"read", "write"}) */ - private $email; + private ?string $email = null; /** * @var Address|null @@ -151,27 +171,24 @@ class ThirdParty * @ORM\JoinColumn(nullable=true, onDelete="SET NULL") * @Groups({"read", "write"}) */ - private $address; + private ?Address $address = null; /** * Soft-delete flag - * @var boolean * @ORM\Column(name="active", type="boolean", options={"defaut": true}) */ - private $active = true; + private bool $active = true; /** - * @var string|null * @ORM\Column(name="comment", type="text", nullable=true) */ - private $comment; + private ?string $comment = null; /** - * @var Collection * @ORM\ManyToMany(targetEntity="\Chill\MainBundle\Entity\Center") * @ORM\JoinTable(name="chill_3party.party_center") */ - private $centers; + private Collection $centers; /** * @ORM\Column(name="created_at", type="datetime_immutable", nullable=false) @@ -179,33 +196,21 @@ class ThirdParty private \DateTimeImmutable $createdAt; /** - * @ORM\Column(name="updated_at", type="datetime", nullable=true) + * @ORM\Column(name="updated_at", type="datetime_immutable", nullable=true) */ - private ?\DateTime $updatedAt; + private ?\DateTimeImmutable $updatedAt; /** - * @var User * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User") * @ORM\JoinColumn(name="updated_by", referencedColumnName="id") */ - private $updatedBy; - + private ?User $updatedBy; /** - * @ORM\PrePersist() + * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User") + * @ORM\JoinColumn(name="created_by", referencedColumnName="id") */ - public function prePersist() - { - $this->createdAt = new \DateTimeImmutable(); - } - - /** - * @ORM\PreUpdate() - */ - public function preUpdate() - { - $this->updatedAt = new \DateTime(); - } + private ?User $createdBy; /** @@ -228,6 +233,17 @@ class ThirdParty return $this->id; } + public function getKind(): ?string + { + return $this->kind; + } + + public function setKind(?string $kind): ThirdParty + { + $this->kind = $kind; + return $this; + } + /** * Set name. * @@ -331,6 +347,10 @@ class ThirdParty // remove all keys from the input data $this->type = \array_values($type); + foreach ($this->children as $child) { + $child->setTypes($type); + } + return $this; } @@ -367,6 +387,9 @@ class ThirdParty public function setActive(bool $active) { $this->active = $active; + foreach ($this->children as $child) { + $child->setActive($active); + } return $this; } @@ -486,7 +509,14 @@ class ThirdParty */ public function addCategory(ThirdPartyCategory $category): self { - $this->categories[] = $category; + if (!$this->categories->contains($category)) { + $this->categories[] = $category; + } + + foreach ($this->children as $child) { + $child->addCategory($child); + } + return $this; } @@ -497,6 +527,11 @@ class ThirdParty public function removeCategory(ThirdPartyCategory $category): self { $this->categories->removeElement($category); + + foreach ($this->children as $child) { + $child->removeCategory($child); + } + return $this; } @@ -509,19 +544,18 @@ class ThirdParty } /** - * isLeaf aliases + * @Groups({"read"}) */ public function isChild():bool { - return $this->isLeaf(); + return $this->parent !== null; } public function isParent():bool { - return !$this->isLeaf(); + return !$this->isChild(); } - /** * @return Collection */ @@ -537,6 +571,8 @@ class ThirdParty public function addChild(ThirdParty $child): self { $this->children[] = $child; + $child->setParent($this); + return $this; } @@ -547,7 +583,8 @@ class ThirdParty public function removeChild(ThirdParty $child): self { $this->children->removeElement($child); - $this->active = false; + $child->setActive(false); + return $this; } @@ -617,7 +654,7 @@ class ThirdParty * @param \DateTimeImmutable $createdAt * @return $this */ - public function setCreatedAt(\DateTimeImmutable $createdAt): ThirdParty + public function setCreatedAt(\DateTimeInterface $createdAt): ThirdParty { $this->createdAt = $createdAt; return $this; @@ -626,16 +663,16 @@ class ThirdParty /** * @return \DateTime|null */ - public function getUpdatedAt(): ?\DateTime + public function getUpdatedAt(): ?\DateTimeImmutable { return $this->updatedAt; } /** - * @param \DateTime $updatedAt + * @param \DateTimeImmutable $updatedAt * @return $this */ - public function setUpdatedAt(\DateTime $updatedAt): ThirdParty + public function setUpdatedAt(\DateTimeInterface $updatedAt): ThirdParty { $this->updatedAt = $updatedAt; return $this; @@ -659,6 +696,12 @@ class ThirdParty return $this; } + public function setCreatedBy(User $user): TrackCreationInterface + { + $this->createdBy = $user; + + return $this; + } } diff --git a/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php b/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php index 20c843e71..5523dad6b 100644 --- a/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php +++ b/src/Bundle/ChillThirdPartyBundle/Form/ThirdPartyType.php @@ -3,6 +3,7 @@ namespace Chill\ThirdPartyBundle\Form; use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Form\Type\ChillCollectionType; use Chill\MainBundle\Form\Type\PickCenterType; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -16,6 +17,8 @@ use Doctrine\Persistence\ObjectManager; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolver; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; @@ -60,58 +63,11 @@ class ThirdPartyType extends AbstractType */ public function buildForm(FormBuilderInterface $builder, array $options) { - $types = []; - foreach ($this->typesManager->getProviders() as $key => $provider) { - $types['chill_3party.key_label.'.$key] = $key; - } - if (count($types) === 1) { - $builder - ->add('types', HiddenType::class, [ - 'data' => array_values($types) - ]) - ->get('types') - ->addModelTransformer(new CallbackTransformer( - function (?array $typeArray): ?string { - if (null === $typeArray) { - return null; - } - return implode(',', $typeArray); - }, - function (?string $typeStr): ?array { - if (null === $typeStr) { - return null; - } - return explode(',', $typeStr); - } - )) - ; - } else { - $builder->add('types', ChoiceType::class, [ - 'choices' => $types, - 'expanded' => true, - 'multiple' => true, - 'label' => 'thirdparty.Type' - ]); - } $builder ->add('name', TextType::class, [ 'required' => true ]) - ->add('categories', EntityType::class, [ - 'label' => 'thirdparty.Categories', - 'class' => ThirdPartyCategory::class, - 'choice_label' => function (ThirdPartyCategory $category): string { - return $this->translatableStringHelper->localize($category->getName()); - }, - 'query_builder' => function (EntityRepository $er): QueryBuilder { - return $er->createQueryBuilder('c') - ->where('c.active = true'); - }, - 'required' => true, - 'multiple' => true, - 'attr' => ['class' => 'select2'] - ]) ->add('telephone', TextType::class, [ 'label' => 'Phonenumber', 'required' => false @@ -119,34 +75,17 @@ class ThirdPartyType extends AbstractType ->add('email', EmailType::class, [ 'required' => false ]) - ->add('active', ChoiceType::class, [ - 'label' => 'thirdparty.Status', - 'choices' => [ - 'Active, shown to users' => true, - 'Inactive, not shown to users' => false - ], - 'expanded' => true, - 'multiple' => false - ]) ->add('comment', ChillTextareaType::class, [ 'required' => false ]) ->add('centers', PickCenterType::class, [ - 'role' => $this->om->contains($options['data']) ? + 'role' => (\array_key_exists('data', $options) && $this->om->contains($options['data'])) ? ThirdPartyVoter::UPDATE : ThirdPartyVoter::CREATE, 'choice_options' => [ 'multiple' => true, 'attr' => ['class' => 'select2'] ] ]) - /* - ->add('centers', EntityType::class, [ - 'choices' => $this->getReachableCenters($options), - 'class' => \Chill\MainBundle\Entity\Center::class, - 'multiple' => true, - 'attr' => ['class' => 'select2'] - ]) - */ ; $builder @@ -171,7 +110,7 @@ class ThirdPartyType extends AbstractType ; // Contact Person ThirdParty (child) - if ($options['data']->isChild()) { + if ($options['is_child']) { $builder ->add('civility', EntityType::class, [ 'label' => 'thirdparty.Civility', @@ -184,7 +123,7 @@ class ThirdPartyType extends AbstractType ->where('c.active = true'); }, 'placeholder' => 'thirdparty.choose civility', - 'required' => true + 'required' => false ]) ->add('profession', EntityType::class, [ 'label' => 'thirdparty.Profession', @@ -212,27 +151,82 @@ class ThirdPartyType extends AbstractType 'label' => 'thirdparty.Acronym', 'required' => false ]) + ->add('categories', EntityType::class, [ + 'label' => 'thirdparty.Categories', + 'class' => ThirdPartyCategory::class, + 'choice_label' => function (ThirdPartyCategory $category): string { + return $this->translatableStringHelper->localize($category->getName()); + }, + 'query_builder' => function (EntityRepository $er): QueryBuilder { + return $er->createQueryBuilder('c') + ->where('c.active = true'); + }, + 'required' => true, + 'multiple' => true, + 'attr' => ['class' => 'select2'] + ]) + ->add('children', ChillCollectionType::class, [ + 'entry_type' => ThirdPartyType::class, + 'entry_options' => [ + 'is_child' => true, + 'block_name' => 'children' + ], + 'allow_add' => true, + 'allow_delete' => true, + 'by_reference' => false, + 'button_add_label' => "Add a contact", + 'button_remove_label' => "Remove a contact", + 'empty_collection_explain' => "Any contact" + ]) + ->add('active', ChoiceType::class, [ + 'label' => 'thirdparty.Status', + 'choices' => [ + 'Active, shown to users' => true, + 'Inactive, not shown to users' => false + ], + 'expanded' => true, + 'multiple' => false + ]) ; + + // add the types + $types = []; + foreach ($this->typesManager->getProviders() as $key => $provider) { + $types['chill_3party.key_label.'.$key] = $key; + } + if (count($types) === 1) { + $builder + ->add('types', HiddenType::class, [ + 'data' => array_values($types) + ]) + ->get('types') + ->addModelTransformer(new CallbackTransformer( + function (?array $typeArray): ?string { + if (null === $typeArray) { + return null; + } + return implode(',', $typeArray); + }, + function (?string $typeStr): ?array { + if (null === $typeStr) { + return null; + } + return explode(',', $typeStr); + } + )) + ; + } else { + $builder + ->add('types', ChoiceType::class, [ + 'choices' => $types, + 'expanded' => true, + 'multiple' => true, + 'label' => 'thirdparty.Type' + ]); + } } } - /** - * - * @param array $options - * @return \Chill\MainBundle\Entity\Center[] - */ - protected function getReachableCenters(array $options) - { - switch($options['usage']) { - case 'create': $role = new Role(ThirdPartyVoter::CREATE); - break; - case 'update': $role = new Role(ThirdPartyVoter::UPDATE); - break; - } - - return $this->authorizationHelper->getReachableCenters( - $this->tokenStorage->getToken()->getUser(), $role); - } /** * {@inheritdoc} @@ -240,23 +234,8 @@ class ThirdPartyType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'data_class' => ThirdParty::class + 'data_class' => ThirdParty::class, + 'is_child' => false, )); - - /* - $resolver->setRequired('usage') - ->setAllowedValues('usage', ['create', 'update']) - ; - */ } - - /** - * {@inheritdoc} - */ - public function getBlockPrefix() - { - return 'chill_thirdpartybundle_thirdparty'; - } - - } diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig index 9fb8fd56e..b0968e7ad 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig @@ -1,3 +1,6 @@ + + + {% if form.civility is defined %} {{ form_row(form.civility) }} {% endif %} @@ -19,6 +22,9 @@ {{ form_row(form.telephone) }} {{ form_row(form.email) }} +

    {{ 'Contacts'|trans }}

    + {{ form_widget(form.children) }} +
    {{ form_label(form.address) }} {{ form_widget(form.address) }} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form_thirdparty_children.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form_thirdparty_children.html.twig new file mode 100644 index 000000000..44f4fc153 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form_thirdparty_children.html.twig @@ -0,0 +1,35 @@ + +{% block _third_party_children_entry_widget %} +
    +
    +
    + {{ form_widget(form.civility) }} + {{ form_errors(form.civility) }} + {{ form_label(form.civility) }} +
    +
    + {{ form_widget(form.name) }} + {{ form_errors(form.name) }} + {{ form_label(form.name) }} +
    +
    + {{ form_widget(form.profession) }} + {{ form_errors(form.profession) }} + {{ form_label(form.profession) }} +
    +
    +
    +
    + {{ form_widget(form.telephone) }} + {{ form_errors(form.telephone) }} + {{ form_label(form.telephone) }} +
    +
    + {{ form_widget(form.email) }} + {{ form_errors(form.email) }} + {{ form_label(form.email) }} +
    +
    +
    +{% endblock %} + diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/index.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/index.html.twig index e59cdb451..25c1779af 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/index.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/index.html.twig @@ -57,7 +57,10 @@ {% for tp in third_parties %} {{ (tp.active ? '' : '')|raw }} - {{ tp.name }} + + {{ tp.name }} + {% if tp.isChild %}{{ 'Contact'|trans }}{% endif %} + {% set types = [] %} {% for t in tp.types %} {% set types = types|merge( [ ('chill_3party.key_label.'~t)|trans ] ) %} @@ -77,12 +80,14 @@
      {% if is_granted('CHILL_3PARTY_3PARTY_UPDATE', tp) %}
    • - +
    • {% endif %} {% if is_granted('CHILL_3PARTY_3PARTY_SHOW', tp) %}
    • - +
    • {% endif %}
    diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/new.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/new.html.twig index fe326cc0d..a93d2d30b 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/new.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/new.html.twig @@ -1,6 +1,7 @@ {% extends "@ChillMain/layout.html.twig" %} {% set thirdParty = entity %} +{% form_theme form '@ChillThirdParty/ThirdParty/_form_thirdparty_children.html.twig' %} {% block title 'Create third party'|trans %} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/update.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/update.html.twig index 605d91c73..2cdc3d281 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/update.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/update.html.twig @@ -1,6 +1,7 @@ {% extends "@ChillMain/layout.html.twig" %} {% set thirdParty = entity %} +{% form_theme form '@ChillThirdParty/ThirdParty/_form_thirdparty_children.html.twig' %} {% block title 'Update third party %name%'|trans({ '%name%': thirdParty.name }) %} diff --git a/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php b/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php index b8c3e2827..cef2d562e 100644 --- a/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php +++ b/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php @@ -23,6 +23,8 @@ class ThirdPartyNormalizer implements NormalizerInterface, NormalizerAwareInterf [ 'address_rendering' => 'short' ]); $data['phonenumber'] = $thirdParty->getTelephone(); $data['email'] = $thirdParty->getEmail(); + $data['isChild'] = $thirdParty->isChild(); + $data['parent'] = $this->normalizer->normalize($thirdParty->getParent(), $format, $context); return $data; } diff --git a/src/Bundle/ChillThirdPartyBundle/migrations/Version20211006200924.php b/src/Bundle/ChillThirdPartyBundle/migrations/Version20211006200924.php new file mode 100644 index 000000000..d35552604 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/migrations/Version20211006200924.php @@ -0,0 +1,37 @@ +addSql('ALTER TABLE chill_3party.third_party ADD created_by INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_3party.third_party ALTER created_at TYPE TIMESTAMP(0) WITHOUT TIME ZONE'); + $this->addSql('ALTER TABLE chill_3party.third_party ALTER created_at DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_3party.third_party ALTER updated_at TYPE TIMESTAMP(0) WITHOUT TIME ZONE'); + $this->addSql('ALTER TABLE chill_3party.third_party ALTER updated_at DROP DEFAULT'); + $this->addSql('COMMENT ON COLUMN chill_3party.third_party.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_3party.third_party.updated_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE chill_3party.third_party ADD CONSTRAINT FK_D952467BDE12AB56 FOREIGN KEY (created_by) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_D952467BDE12AB56 ON chill_3party.third_party (created_by)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_3party.third_party DROP created_by'); + } +} diff --git a/src/Bundle/ChillThirdPartyBundle/migrations/Version20211007150459.php b/src/Bundle/ChillThirdPartyBundle/migrations/Version20211007150459.php new file mode 100644 index 000000000..fb4f9bed1 --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/migrations/Version20211007150459.php @@ -0,0 +1,69 @@ +addSql('DROP INDEX chill_3party.uniq_d952467b384d4799'); + $this->addSql('DROP INDEX chill_3party.uniq_d952467bba930d69'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD civility_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD profession_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD kind VARCHAR(20) NOT NULL DEFAULT \'\''); + $this->addSql('ALTER TABLE chill_3party.third_party ADD canonicalized TEXT NOT NULL DEFAULT \'\''); + $this->addSql('CREATE TEMPORARY TABLE civility_migration AS SELECT * FROM chill_3party.party_civility'); + $this->addSql('ALTER TABLE civility_migration ADD COLUMN new_id INT DEFAULT NULL'); + $this->addSql('UPDATE civility_migration SET new_id = nextval(\'chill_main_civility_id_seq\')'); + $this->addSql(' + INSERT INTO chill_main_civility (id, name, abbreviation, active) + SELECT new_id, name, \'{}\'::json, active from civility_migration + '); + $this->addSql('UPDATE chill_3party.third_party SET civility_id = new_id + FROM civility_migration WHERE civility_migration.id = third_party.civility'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP CONSTRAINT fk_d952467b384d4799'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP CONSTRAINT fk_d952467bba930d69'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP civility'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP profession'); + $this->addSql('DROP SEQUENCE chill_3party.party_civility_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_3party.party_civility'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD CONSTRAINT FK_D952467B23D6A298 FOREIGN KEY (civility_id) REFERENCES chill_main_civility (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD CONSTRAINT FK_D952467BFDEF8996 FOREIGN KEY (profession_id) REFERENCES chill_3party.party_profession (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_D952467B23D6A298 ON chill_3party.third_party (civility_id)'); + $this->addSql('CREATE INDEX IDX_D952467BFDEF8996 ON chill_3party.third_party (profession_id)'); + } + + public function down(Schema $schema): void + { + $this->throwIrreversibleMigrationException('Reversible migration not implemented'); + + // for reference: + $this->addSql('CREATE SEQUENCE chill_3party.party_civility_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_3party.party_civility (id INT NOT NULL, name JSON NOT NULL, active BOOLEAN NOT NULL, PRIMARY KEY(id))'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP CONSTRAINT FK_D952467B23D6A298'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP CONSTRAINT FK_D952467BFDEF8996'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD civility INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD profession INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP civility_id'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP profession_id'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP kind'); + $this->addSql('ALTER TABLE chill_3party.third_party DROP canonicalized'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD CONSTRAINT fk_d952467b384d4799 FOREIGN KEY (civility) REFERENCES chill_3party.party_civility (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_3party.third_party ADD CONSTRAINT fk_d952467bba930d69 FOREIGN KEY (profession) REFERENCES chill_3party.party_profession (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE UNIQUE INDEX uniq_d952467b384d4799 ON chill_3party.third_party (civility)'); + $this->addSql('CREATE UNIQUE INDEX uniq_d952467bba930d69 ON chill_3party.third_party (profession)'); + } +} diff --git a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml index 7878ec196..83131cc23 100644 --- a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml @@ -45,6 +45,10 @@ Inactive, not shown to users: Inactif, invisible pour les utilisateurs Inactive: Inactif not shown to users: invisible pour les utilisateurs Show thirdparty: Voir le tiers +Add a contact: Ajouter un contact +Remove a contact: Supprimer +Contacts: Contacts +Any contact: Aucun contact No nameCompany given: Aucune raison sociale renseignée No acronym given: Aucun sigle renseigné @@ -58,3 +62,9 @@ No third parties: Aucun tiers CHILL_3PARTY_3PARTY_CREATE: Ajouter un Tiers CHILL_3PARTY_3PARTY_SHOW: Voir un Tiers CHILL_3PARTY_3PARTY_UPDATE: Modifier un Tiers + +# crud: +crud: + 3party_3party: + index: + add_new: Créer