Files
chill-bundles/src/Bundle/ChillPersonBundle/Entity/Person.php
2021-04-26 17:18:38 +02:00

1177 lines
26 KiB
PHP

<?php
/**
* Chill is a software for social workers.
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*
* @see https://www.champs-libres.coop/
*/
declare(strict_types=1);
namespace Chill\PersonBundle\Entity;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Country;
use Chill\MainBundle\Entity\HasCenterInterface;
use DateTime;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Exception;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function count;
use function in_array;
/**
* Person Class.
*
* @ORM\Entity(repositoryClass="Chill\PersonBundle\Repository\PersonRepository")
* @ORM\Table(name="chill_person_person",
* indexes={@ORM\Index(
* name="person_names",
* columns={"firstName", "lastName"}
* )})
* @ORM\HasLifecycleCallbacks
*/
class Person implements HasCenterInterface
{
public const BOTH_GENDER = 'both';
// have days in commun
public const ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD = 2; // where there exist
public const ERROR_PERIODS_ARE_COLLAPSING = 1; // when two different periods
public const FEMALE_GENDER = 'woman';
public const MALE_GENDER = 'man';
/**
* The person's accompanying periods (when the person was accompanied by the center).
*
* @var Collection
*
* @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class,
* mappedBy="person",
* cascade={"persist", "remove", "merge", "detach"})
*/
private $accompanyingPeriodParticipations;
/**
* Addresses.
*
* @var Collection
*
* @ORM\ManyToMany(
* targetEntity="Chill\MainBundle\Entity\Address",
* cascade={"persist", "remove", "merge", "detach"})
* @ORM\JoinTable(name="chill_person_persons_to_addresses")
* @ORM\OrderBy({"validFrom": "DESC"})
*/
private $addresses;
/**
* @var Collection
*
* @ORM\OneToMany(
* targetEntity="Chill\PersonBundle\Entity\PersonAltName",
* mappedBy="person",
* cascade={"persist", "remove", "merge", "detach"},
* orphanRemoval=true)
*/
private $altNames;
/**
* The person's birthdate.
*
* @var DateTime
*
* @ORM\Column(type="date", nullable=true)
*/
private $birthdate; //to change in birthdate
/**
* The person's center.
*
* @var Center
*
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Center")
* @ORM\JoinColumn(nullable=false)
*/
private $center;
/**
* Array where customfield's data are stored.
*
* @var array
*
* @ORM\Column(type="json_array")
*/
private $cFData;
/**
* Contact information for contacting the person.
*
* @var string
*
* @ORM\Column(type="text", nullable=true)
*/
private $contactInfo = '';
/**
* The person's country of birth.
*
* @var Country
*
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Country")
*
* sf4 check: option inversedBy="birthsIn" return error mapping !!
*
* @ORM\JoinColumn(nullable=true)
*/
private $countryOfBirth;
/**
* The person's email.
*
* @var string
*
* @ORM\Column(type="text", nullable=true)
*/
private $email = '';
/**
* The person's first name.
*
* @var string
*
* @ORM\Column(type="string", length=255)
*/
private $firstName;
/**
* @var string
*
* @ORM\Column(type="text", nullable=true)
*/
private $fullnameCanonical;
/**
* The person's gender.
*
* @var string
*
* @ORM\Column(type="string", length=9, nullable=true)
*/
private $gender;
/**
* The person's id.
*
* @var int
*
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* The person's last name.
*
* @var string
*
* @ORM\Column(type="string", length=255)
*/
private $lastName;
/**
* The marital status of the person.
*
* @var MaritalStatus
*
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\MaritalStatus")
* @ORM\JoinColumn(nullable=true)
*/
private $maritalStatus;
/**
* A remark over the person.
*
* @var string
*
* @ORM\Column(type="text")
*/
private $memo = ''; // TO-CHANGE in remark
/**
* The person's mobile phone number.
*
* @var string
*
* @ORM\Column(type="text", length=40, nullable=true)
*/
private $mobilenumber = '';
/**
* The person's nationality.
*
* @var Country
*
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Country")
*
* sf4 check: option inversedBy="nationals" return error mapping !!
*
* @ORM\JoinColumn(nullable=true)
*/
private $nationality;
/**
* @var Collection
*
* @ORM\OneToMany(
* targetEntity="Chill\PersonBundle\Entity\PersonPhone",
* mappedBy="person",
* cascade={"persist", "remove", "merge", "detach"},
* orphanRemoval=true
* )
*/
private $otherPhoneNumbers;
/**
* The person's phonenumber.
*
* @var string
*
* @ORM\Column(type="text", length=40, nullable=true)
*/
private $phonenumber = '';
/**
* The person's place of birth.
*
* @var string
*
* @ORM\Column(type="string", length=255, name="place_of_birth")
*/
private $placeOfBirth = '';
/**
* @var bool
*
* @deprecated
*
* @ORM\Column(type="boolean")
*/
private $proxyAccompanyingPeriodOpenState = false; //TO-DELETE ?
//TO-ADD caseOpeningDate
//TO-ADD nativeLanguag
/**
* The person's spoken languages.
*
* @var ArrayCollection
*
* @ORM\ManyToMany(targetEntity="Chill\MainBundle\Entity\Language")
* @ORM\JoinTable(
* name="persons_spoken_languages",
* joinColumns={@ORM\JoinColumn(name="person_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="language_id", referencedColumnName="id")}
* )
*/
private $spokenLanguages;
/**
* Person constructor.
*/
public function __construct(?DateTime $opening = null)
{
$this->accompanyingPeriodParticipations = new ArrayCollection();
$this->spokenLanguages = new ArrayCollection();
$this->addresses = new ArrayCollection();
$this->altNames = new ArrayCollection();
$this->otherPhoneNumbers = new ArrayCollection();
if (null === $opening) {
$opening = new DateTime();
}
$this->open(new AccompanyingPeriod($opening));
}
/**
* @return string
*/
public function __toString()
{
return $this->getLabel();
}
/**
* Add AccompanyingPeriodParticipation.
*
* @uses AccompanyingPeriod::addPerson
*/
public function addAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): self
{
$participation = new AccompanyingPeriodParticipation($accompanyingPeriod, $this);
$this->accompanyingPeriodParticipations->add($participation);
return $this;
}
/**
* @return $this
*/
public function addAddress(Address $address)
{
$this->addresses[] = $address;
return $this;
}
/**
* @return $this
*/
public function addAltName(PersonAltName $altName)
{
if (false === $this->altNames->contains($altName)) {
$this->altNames->add($altName);
$altName->setPerson($this);
}
return $this;
}
/**
* @return $this
*/
public function addOtherPhoneNumber(PersonPhone $otherPhoneNumber)
{
if (false === $this->otherPhoneNumbers->contains($otherPhoneNumber)) {
$otherPhoneNumber->setPerson($this);
$this->otherPhoneNumbers->add($otherPhoneNumber);
}
return $this;
}
// a period opened and another one after it
/**
* Function used for validation that check if the accompanying periods of
* the person are not collapsing (i.e. have not shared days) or having
* a period after an open period.
*
* @return true | array True if the accompanying periods are not collapsing,
* an array with data for displaying the error
*/
public function checkAccompanyingPeriodsAreNotCollapsing()
{
$periods = $this->getAccompanyingPeriodsOrdered();
$periodsNbr = count($periods);
$i = 0;
while ($periodsNbr - 1 > $i) {
$periodI = $periods[$i];
$periodAfterI = $periods[$i + 1];
if ($periodI->isOpen()) {
return [
'result' => self::ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD,
'dateOpening' => $periodAfterI->getOpeningDate(),
'dateClosing' => $periodAfterI->getClosingDate(),
'date' => $periodI->getOpeningDate(),
];
}
if ($periodI->getClosingDate() >= $periodAfterI->getOpeningDate()) {
return [
'result' => self::ERROR_PERIODS_ARE_COLLAPSING,
'dateOpening' => $periodI->getOpeningDate(),
'dateClosing' => $periodI->getClosingDate(),
'date' => $periodAfterI->getOpeningDate(),
];
}
++$i;
}
return true;
}
/**
* Set the Person file as closed at the given date.
*
* For update a closing date, you should update AccompanyingPeriod instance
* directly.
*
* To check if the Person and its accompanying period are consistent, use validation.
*
* @throws Exception if two lines of the accompanying period are open.
*/
public function close(?AccompanyingPeriod $accompanyingPeriod = null): void
{
$this->proxyAccompanyingPeriodOpenState = false;
}
/**
* This public function is the same but return only true or false.
*/
public function containsAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
{
return ($this->participationsContainAccompanyingPeriod($accompanyingPeriod)) ? false : true;
}
/**
* Get AccompanyingPeriodParticipations Collection.
*/
public function getAccompanyingPeriodParticipations(): Collection
{
return $this->accompanyingPeriodParticipations;
}
/**
* Get AccompanyingPeriods array.
*/
public function getAccompanyingPeriods(): array
{
$accompanyingPeriods = [];
foreach ($this->accompanyingPeriodParticipations as $participation) {
/** @var AccompanyingPeriodParticipation $participation */
$accompanyingPeriods[] = $participation->getAccompanyingPeriod();
}
return $accompanyingPeriods;
}
/**
* Get the accompanying periods of a give person with the chronological order.
*/
public function getAccompanyingPeriodsOrdered(): array
{
$periods = $this->getAccompanyingPeriods();
//order by date :
usort($periods, static function ($a, $b) {
$dateA = $a->getOpeningDate();
$dateB = $b->getOpeningDate();
if ($dateA === $dateB) {
$dateEA = $a->getClosingDate();
$dateEB = $b->getClosingDate();
if ($dateEA === $dateEB) {
return 0;
}
if ($dateEA < $dateEB) {
return -1;
}
return +1;
}
if ($dateA < $dateB) {
return -1;
}
return 1;
});
return $periods;
}
/**
* By default, the addresses are ordered by date, descending (the most
* recent first).
*
* @return \Chill\MainBundle\Entity\Address[]
*/
public function getAddresses()
{
return $this->addresses;
}
public function getAltNames(): Collection
{
return $this->altNames;
}
/**
* Get birthdate.
*
* @return DateTime
*/
public function getBirthdate()
{
return $this->birthdate;
}
/**
* Get center.
*
* @return Center
*/
public function getCenter()
{
return $this->center;
}
/**
* Get cFData.
*
* @return array
*/
public function getCFData()
{
if (null === $this->cFData) {
$this->cFData = [];
}
return $this->cFData;
}
/**
* Get contactInfo.
*
* @return string
*/
public function getcontactInfo()
{
return $this->contactInfo;
}
/**
* Get countryOfBirth.
*
* @return Chill\MainBundle\Entity\Country
*/
public function getCountryOfBirth()
{
return $this->countryOfBirth;
}
/**
* Returns the opened accompanying period.
*
* @deprecated since 1.1 use `getOpenedAccompanyingPeriod instead
*/
public function getCurrentAccompanyingPeriod(): AccompanyingPeriod
{
return $this->getOpenedAccompanyingPeriod();
}
/**
* Get email.
*
* @return string
*/
public function getEmail()
{
return $this->email;
}
/**
* Get firstName.
*
* @return string
*/
public function getFirstName()
{
return $this->firstName;
}
public function getFullnameCanonical(): string
{
return $this->fullnameCanonical;
}
/**
* Get gender.
*
* @return string
*/
public function getGender()
{
return $this->gender;
}
/**
* return gender as a Numeric form.
* This is used for translations.
*
* @return int
*/
public function getGenderNumeric()
{
if ($this->getGender() === self::FEMALE_GENDER) {
return 1;
}
return 0;
}
/**
* Get id.
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @return string
*/
public function getLabel()
{
return $this->getFirstName() . ' ' . $this->getLastName();
}
/**
* @return null
*/
public function getLastAddress(?DateTime $date = null)
{
if (null === $date) {
$date = new DateTime('now');
}
$addresses = $this->getAddresses();
if (null === $addresses) {
return null;
}
return $addresses->first();
}
/**
* Get lastName.
*
* @return string
*/
public function getLastName()
{
return $this->lastName;
}
/**
* Get maritalStatus.
*
* @return MaritalStatus
*/
public function getMaritalStatus()
{
return $this->maritalStatus;
}
/**
* Get memo.
*
* @return string
*/
public function getMemo()
{
return $this->memo;
}
/**
* Get mobilenumber.
*
* @return string
*/
public function getMobilenumber()
{
return $this->mobilenumber;
}
/**
* Get nationality.
*
* @return Chill\MainBundle\Entity\Country
*/
public function getNationality()
{
return $this->nationality;
}
/**
* Return the opened accompanying period.
*/
public function getOpenedAccompanyingPeriod(): ?AccompanyingPeriod
{
if ($this->isOpen() === false) {
return null;
}
foreach ($this->accompanyingPeriodParticipations as $participation) {
/** @var AccompanyingPeriodParticipation $participation */
if ($participation->getAccompanyingPeriod()->isOpen()) {
return $participation->getAccompanyingPeriod();
}
}
}
public function getOtherPhoneNumbers(): Collection
{
return $this->otherPhoneNumbers;
}
/**
* Get phonenumber.
*
* @return string
*/
public function getPhonenumber()
{
return $this->phonenumber;
}
/**
* Get placeOfBirth.
*
* @return string
*/
public function getPlaceOfBirth()
{
return $this->placeOfBirth;
}
/**
* Get spokenLanguages.
*
* @return ArrayCollection
*/
public function getSpokenLanguages()
{
return $this->spokenLanguages;
}
/**
* Return true if the person has two addresses with the
* same validFrom date (in format 'Y-m-d').
*/
public function hasTwoAdressWithSameValidFromDate()
{
$validYMDDates = [];
foreach ($this->addresses as $ad) {
$validDate = $ad->getValidFrom()->format('Y-m-d');
if (in_array($validDate, $validYMDDates, true)) {
return true;
}
$validYMDDates[] = $validDate;
}
return false;
}
/**
* Validation callback that checks if the accompanying periods are valid.
*
* This method add violation errors.
*/
public function isAccompanyingPeriodValid(ExecutionContextInterface $context)
{
$r = $this->checkAccompanyingPeriodsAreNotCollapsing();
if (true !== $r) {
if (self::ERROR_PERIODS_ARE_COLLAPSING === $r['result']) {
$context->buildViolation('Two accompanying periods have days in commun')
->atPath('accompanyingPeriods')
->addViolation();
}
if (self::ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD === $r['result']) {
$context->buildViolation('A period is opened and a period is added after it')
->atPath('accompanyingPeriods')
->addViolation();
}
}
}
/**
* Validation callback that checks if the addresses are valid (do not have
* two addresses with the same validFrom date).
*
* This method add violation errors.
*/
public function isAddressesValid(ExecutionContextInterface $context)
{
if ($this->hasTwoAdressWithSameValidFromDate()) {
$context
->buildViolation('Two addresses has the same validFrom date')
->atPath('addresses')
->addViolation();
}
}
/**
* Check if the person is opened.
*/
public function isOpen(): bool
{
foreach ($this->getAccompanyingPeriods() as $period) {
if ($period->isOpen()) {
return true;
}
}
return false;
}
/**
* set the Person file as open at the given date.
*
* For updating a opening's date, you should update AccompanyingPeriod instance
* directly.
*
* For closing a file, @see this::close
*
* To check if the Person and its accompanying period is consistent, use validation.
*/
public function open(AccompanyingPeriod $accompanyingPeriod): void
{
$this->proxyAccompanyingPeriodOpenState = true;
$this->addAccompanyingPeriod($accompanyingPeriod);
}
/**
* Remove AccompanyingPeriod.
*/
public function removeAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): void
{
$participation = $this->participationsContainAccompanyingPeriod($accompanyingPeriod);
if (!null === $participation) {
$participation->setEndDate(DateTimeImmutable::class);
$this->accompanyingPeriodParticipations->removeElement($participation);
}
}
public function removeAddress(Address $address)
{
$this->addresses->removeElement($address);
}
/**
* @return $this
*/
public function removeAltName(PersonAltName $altName)
{
if ($this->altNames->contains($altName)) {
$altName->setPerson(null);
$this->altNames->removeElement($altName);
}
return $this;
}
/**
* @return $this
*/
public function removeOtherPhoneNumber(PersonPhone $otherPhoneNumber)
{
if ($this->otherPhoneNumbers->contains($otherPhoneNumber)) {
$this->otherPhoneNumbers->removeElement($otherPhoneNumber);
}
return $this;
}
/**
* @return $this
*/
public function setAltNames(Collection $altNames)
{
$this->altNames = $altNames;
return $this;
}
/**
* Set birthdate.
*
* @param DateTime $birthdate
*
* @return Person
*/
public function setBirthdate($birthdate)
{
$this->birthdate = $birthdate;
return $this;
}
/**
* Set the center.
*
* @return \Chill\PersonBundle\Entity\Person
*/
public function setCenter(Center $center)
{
$this->center = $center;
return $this;
}
/**
* Set cFData.
*
* @param array $cFData
*
* @return Report
*/
public function setCFData($cFData)
{
$this->cFData = $cFData;
return $this;
}
/**
* Set contactInfo.
*
* @param string $contactInfo
*
* @return Person
*/
public function setcontactInfo($contactInfo)
{
if (null === $contactInfo) {
$contactInfo = '';
}
$this->contactInfo = $contactInfo;
return $this;
}
/**
* Set countryOfBirth.
*
* @param Chill\MainBundle\Entity\Country $countryOfBirth
*
* @return Person
*/
public function setCountryOfBirth(?Country $countryOfBirth = null)
{
$this->countryOfBirth = $countryOfBirth;
return $this;
}
/**
* Set email.
*
* @param string $email
*
* @return Person
*/
public function setEmail($email)
{
if (null === $email) {
$email = '';
}
$this->email = $email;
return $this;
}
/**
* Set firstName.
*
* @param string $firstName
*
* @return Person
*/
public function setFirstName($firstName)
{
$this->firstName = $firstName;
return $this;
}
public function setFullnameCanonical($fullnameCanonical): Person
{
$this->fullnameCanonical = $fullnameCanonical;
return $this;
}
/**
* Set gender.
*
* @param string $gender
*
* @return Person
*/
public function setGender($gender)
{
$this->gender = $gender;
return $this;
}
/**
* Set lastName.
*
* @param string $lastName
*
* @return Person
*/
public function setLastName($lastName)
{
$this->lastName = $lastName;
return $this;
}
/**
* Set maritalStatus.
*
* @param MaritalStatus $maritalStatus
*
* @return Person
*/
public function setMaritalStatus(?MaritalStatus $maritalStatus = null)
{
$this->maritalStatus = $maritalStatus;
return $this;
}
/**
* Set memo.
*
* @param string $memo
*
* @return Person
*/
public function setMemo($memo)
{
if (null === $memo) {
$memo = '';
}
if ($this->memo !== $memo) {
$this->memo = $memo;
}
return $this;
}
/**
* Set mobilenumber.
*
* @param string $mobilenumber
*
* @return Person
*/
public function setMobilenumber($mobilenumber = '')
{
$this->mobilenumber = $mobilenumber;
return $this;
}
/**
* Set nationality.
*
* @param Chill\MainBundle\Entity\Country $nationality
*
* @return Person
*/
public function setNationality(?Country $nationality = null)
{
$this->nationality = $nationality;
return $this;
}
/**
* @return $this
*/
public function setOtherPhoneNumbers(Collection $otherPhoneNumbers)
{
$this->otherPhoneNumbers = $otherPhoneNumbers;
return $this;
}
/**
* Set phonenumber.
*
* @param string $phonenumber
*
* @return Person
*/
public function setPhonenumber($phonenumber = '')
{
$this->phonenumber = $phonenumber;
return $this;
}
/**
* Set placeOfBirth.
*
* @param string $placeOfBirth
*
* @return Person
*/
public function setPlaceOfBirth($placeOfBirth)
{
if (null === $placeOfBirth) {
$placeOfBirth = '';
}
$this->placeOfBirth = $placeOfBirth;
return $this;
}
/**
* Set spokenLanguages.
*
* @param type $spokenLanguages
*
* @return Person
*/
public function setSpokenLanguages($spokenLanguages)
{
$this->spokenLanguages = $spokenLanguages;
return $this;
}
/**
* This private function scan accompanyingPeriodParticipations Collection,
* searching for a given AccompanyingPeriod.
*/
private function participationsContainAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): ?AccompanyingPeriodParticipation
{
foreach ($this->accompanyingPeriodParticipations as $participation) {
/** @var AccompanyingPeriodParticipation $participation */
if ($participation->getAccompanyingPeriod() === $accompanyingPeriod) {
return $participation;
}
}
return null;
}
}