various improvements on 3party

This commit is contained in:
Julien Fastré 2021-10-07 17:28:33 +02:00
parent 7ea2c36c5e
commit ca3ced0308
20 changed files with 464 additions and 305 deletions

View File

@ -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);

View File

@ -20,16 +20,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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é <julien.fastre@champs-libres.coop>
*/
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;

View File

@ -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]);
}
}

View File

@ -168,6 +168,10 @@
{{ form_widget(entry) }}
</div>
</li>
{% else %}
<li data-collection-empty-explain="1">
<span class="chill-no-data-statement">{{ form.vars.empty_collection_explain|default('No item')|trans }}</span>
</li>
{% endfor %}
</ul>

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Create civility table
*/
final class Version20211007150019 extends AbstractMigration
{
public function getDescription(): string
{
return 'create civility table';
}
public function up(Schema $schema): void
{
$this->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');
}
}

View File

@ -1,18 +1,23 @@
<template>
<div class="container">
<span class="name">
{{ item.result.text }}
</span>
<span class="location">
{{ item.result.address.text }} -
<template v-if="item.result.address != null">
{{ item.result.address.text }} -
{{ item.result.address.postcode.name }}
</template>
</span>
</div>
<div class="right_actions">
<span class="badge rounded-pill bg-secondary" :title="item.key">
<span class="badge rounded-pill bg-info" v-if="item.result.isChild">
{{ $t('item.thirdparty_contact')}}
</span>
<span class="badge rounded-pill bg-secondary" :title="item.key">
{{ $t('item.type_thirdparty') }}
</span>
<on-the-fly
@ -20,7 +25,7 @@
v-bind:id="item.result.id"
action="show">
</on-the-fly>
</div>
</template>

View File

@ -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
]);
}
}

View File

@ -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;
}
}

View File

@ -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';
}
}

View File

@ -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) }}
<h2>{{ 'Contacts'|trans }}</h2>
{{ form_widget(form.children) }}
<div class="mb-3 row">
{{ form_label(form.address) }}
{{ form_widget(form.address) }}

View File

@ -0,0 +1,35 @@
{% block _third_party_children_entry_widget %}
<div class="container">
<div class="row">
<div class="form-group col-md-3 mb-3">
{{ form_widget(form.civility) }}
{{ form_errors(form.civility) }}
{{ form_label(form.civility) }}
</div>
<div class="form-group col-md-5 mb-3">
{{ form_widget(form.name) }}
{{ form_errors(form.name) }}
{{ form_label(form.name) }}
</div>
<div class="form-group col-md-4 mb-3">
{{ form_widget(form.profession) }}
{{ form_errors(form.profession) }}
{{ form_label(form.profession) }}
</div>
</div>
<div class="row">
<div class="form-group col-md-6 mb-3">
{{ form_widget(form.telephone) }}
{{ form_errors(form.telephone) }}
{{ form_label(form.telephone) }}
</div>
<div class="form-group col-md-6 mb-3">
{{ form_widget(form.email) }}
{{ form_errors(form.email) }}
{{ form_label(form.email) }}
</div>
</div>
</div>
{% endblock %}

View File

@ -57,7 +57,10 @@
{% for tp in third_parties %}
<tr>
<th>{{ (tp.active ? '<i class="fa fa-check chill-green">' : '<i class="fa fa-times chill-red">')|raw }}</th>
<td>{{ tp.name }}</td>
<td>
{{ tp.name }}
{% if tp.isChild %}<span class="badge bg-info">{{ 'Contact'|trans }}</span>{% endif %}
</td>
{% set types = [] %}
{% for t in tp.types %}
{% set types = types|merge( [ ('chill_3party.key_label.'~t)|trans ] ) %}
@ -77,12 +80,14 @@
<ul class="record_actions">
{% if is_granted('CHILL_3PARTY_3PARTY_UPDATE', tp) %}
<li>
<a href="{{ chill_path_add_return_path('chill_crud_3party_3party_edit', { 'id': tp.id }) }}" class="btn btn-sm btn-update"></a>
<a href="{{ chill_path_add_return_path('chill_crud_3party_3party_edit',
{ 'id': (tp.isParent ? tp.id : tp.parent.id) }) }}" class="btn btn-sm btn-update"></a>
</li>
{% endif %}
{% if is_granted('CHILL_3PARTY_3PARTY_SHOW', tp) %}
<li>
<a href="{{ chill_path_add_return_path('chill_crud_3party_3party_view', { 'id': tp.id }) }}" class="btn btn-sm btn-show"></a>
<a href="{{ chill_path_add_return_path('chill_crud_3party_3party_view',
{ 'id': (tp.isParent ? tp.id : tp.parent.id) }) }}" class="btn btn-sm btn-show"></a>
</li>
{% endif %}
</ul>

View File

@ -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 %}

View File

@ -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 }) %}

View File

@ -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;
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\ThirdParty;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add created_by to third party
*/
final class Version20211006200924 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add link to creator (created_by) to thirdParty';
}
public function up(Schema $schema): void
{
$this->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');
}
}

View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\ThirdParty;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* migrate data from 3party.civility to chill_main_civility table
*/
final class Version20211007150459 extends AbstractMigration
{
public function getDescription(): string
{
return 'migrate data from 3party.civility to chill_main_civility table';
}
public function up(Schema $schema): void
{
$this->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)');
}
}

View File

@ -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