src/App/Entity/Profile/JobseekerProfile.php line 36

Open in your IDE?
  1. <?php
  2. namespace App\Entity\Profile;
  3. use App\Entity\ConversationMessage\ConversationMessage;
  4. use App\Entity\ExternalPartner\IntegratedExternalPartnerCustomer;
  5. use App\Entity\JobseekerOccupationalFieldCapabilityValue;
  6. use App\Entity\JobseekerProfileAdditionalFile;
  7. use App\Entity\OccupationalField;
  8. use App\Entity\Profile;
  9. use App\Entity\ProfileBlock;
  10. use App\Entity\ProfileFavorization;
  11. use App\Entity\ProfileReview;
  12. use App\Entity\User;
  13. use App\Entity\WantedJob;
  14. use App\Service\EntityCacheService;
  15. use App\Service\OccupationalFieldCapabilitiesService;
  16. use App\Utility\TextCleaner;
  17. use App\Validator\Constraint as AppAssert;
  18. use App\Value\MimeTypes;
  19. use App\Value\ZipcodeRadiusesValue;
  20. use DateTime;
  21. use Doctrine\Common\Collections\ArrayCollection;
  22. use Doctrine\Common\Collections\Collection;
  23. use Doctrine\ORM\Mapping as ORM;
  24. use Exception;
  25. use InvalidArgumentException;
  26. use Symfony\Component\Validator\Constraints as Assert;
  27. use Symfony\Component\Validator\Context\ExecutionContextInterface;
  28. /**
  29. * @ORM\Entity
  30. *
  31. * @ORM\Table(name="jobseeker_profiles")
  32. */
  33. class JobseekerProfile extends Profile
  34. {
  35. public const POPULAR_SEARCHTERMS = [
  36. 'Helfer',
  37. 'Fahrer PKW/LKW',
  38. 'Lagerhelfer',
  39. 'Pflegekraft',
  40. 'Kassenkraft',
  41. 'Reinigungskraft',
  42. 'Sicherheitsdienst',
  43. 'Verkäufer',
  44. 'Vertriebler',
  45. 'Hausmeister',
  46. 'Kaufmann/-frau',
  47. 'Sachbearbeiter',
  48. 'Service/Kellner',
  49. 'Call-Center',
  50. 'Empfang/Rezeption',
  51. 'Büroassistent',
  52. 'Küchenhilfe',
  53. 'Kundenberater',
  54. 'Handwerkshelfer',
  55. 'Monteur',
  56. 'Influencer',
  57. 'Garten-Landschaftsbau'
  58. ];
  59. public function __construct()
  60. {
  61. parent::__construct();
  62. $this->occupationalFieldCapabilityValues = new ArrayCollection();
  63. $this->wantedJobs = new ArrayCollection();
  64. $this->additionalFiles = new ArrayCollection();
  65. $this->availabilityRadius = ZipcodeRadiusesValue::JOBSEEKER_PROFILE_DEFAULT;
  66. $this->experience = self::EXPERIENCE_MORE_THAN_ONE_YEAR;
  67. }
  68. /**
  69. * @var User
  70. *
  71. * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="jobseekerProfiles", cascade={"persist"})
  72. *
  73. * @ORM\JoinColumn(name="users_id", referencedColumnName="id", nullable=false, onDelete="CASCADE")
  74. */
  75. protected $user;
  76. /**
  77. * @var string
  78. *
  79. * @ORM\Column(name="selfdescription", type="text", length=10000, nullable=true)
  80. */
  81. protected $selfdescription;
  82. public function setSelfdescription(?string $selfdescription = null): void
  83. {
  84. $this->selfdescription = $selfdescription;
  85. }
  86. public function getSelfdescription(): ?string
  87. {
  88. return TextCleaner::removeSpecialCharacters($this->selfdescription);
  89. }
  90. /**
  91. * @var string
  92. *
  93. * @ORM\Column(name="firstname", type="string", length=128, nullable=true)
  94. *
  95. * @Assert\NotBlank()
  96. *
  97. * @Assert\Length(
  98. * min = 2,
  99. * max = 50,
  100. * )
  101. */
  102. protected $firstname;
  103. public function setFirstname(?string $firstname = null): void
  104. {
  105. $this->firstname = $firstname;
  106. }
  107. public function getFirstname(): ?string
  108. {
  109. return $this->firstname;
  110. }
  111. /**
  112. * @var string
  113. *
  114. * @ORM\Column(name="lastname", type="string", length=128, nullable=true)
  115. *
  116. * @Assert\Length(
  117. * min = 0,
  118. * max = 50,
  119. * )
  120. */
  121. protected $lastname;
  122. public function setLastname(?string $lastname = null): void
  123. {
  124. $this->lastname = $lastname;
  125. }
  126. public function getLastname(): ?string
  127. {
  128. return $this->lastname;
  129. }
  130. /**
  131. * @var string
  132. *
  133. * @ORM\Column(name="address", type="string", length=128, nullable=true)
  134. *
  135. * @Assert\Length(
  136. * min = 0,
  137. * max = 128,
  138. * )
  139. */
  140. protected $address;
  141. public function setAddress(?string $address = null): void
  142. {
  143. $this->address = $address;
  144. }
  145. public function getAddress(): ?string
  146. {
  147. return $this->address;
  148. }
  149. /**
  150. * @var string
  151. *
  152. * @ORM\Column(name="zipcode", type="string", length=128, nullable=true)
  153. *
  154. * @AppAssert\KnownZipcode
  155. */
  156. protected $zipcode;
  157. public function setZipcode(?string $zipcode = null): void
  158. {
  159. if (!is_null($zipcode)) {
  160. $trimmedZipcode = trim($zipcode);
  161. if (mb_strlen($trimmedZipcode) !== 5 || !is_numeric($trimmedZipcode)) {
  162. throw new InvalidArgumentException("zipcode must either be null or a string with 5 digits, but got '$zipcode'");
  163. }
  164. $zipcode = $trimmedZipcode;
  165. }
  166. $this->zipcode = $zipcode;
  167. }
  168. public function getZipcode(): ?string
  169. {
  170. return $this->zipcode;
  171. }
  172. /**
  173. * @var string
  174. *
  175. * @ORM\Column(name="city", type="string", length=128, nullable=true)
  176. *
  177. * @Assert\Length(
  178. * min = 0,
  179. * max = 128,
  180. * )
  181. */
  182. protected $city;
  183. public function setCity(?string $city = null): void
  184. {
  185. $this->city = mb_substr($city, 0, 128);
  186. }
  187. public function getCity(): ?string
  188. {
  189. return $this->city;
  190. }
  191. /**
  192. * @var ConversationMessage[]|Collection|array
  193. *
  194. * @ORM\OneToMany(targetEntity="\App\Entity\ConversationMessage\ConversationMessage", mappedBy="jobseekerProfile", cascade={"persist", "remove"})
  195. */
  196. protected $conversationMessages;
  197. public function addConversationMessage(ConversationMessage $conversationMessage): void
  198. {
  199. $this->conversationMessages[] = $conversationMessage;
  200. }
  201. /** @return ConversationMessage[]|Collection|array */
  202. public function getConversationMessages(): Collection
  203. {
  204. return $this->conversationMessages;
  205. }
  206. /**
  207. * @var ProfileReview|Collection
  208. *
  209. * @ORM\OneToMany(targetEntity="\App\Entity\ProfileReview", mappedBy="jobseekerProfile", cascade={"persist", "remove"})
  210. */
  211. protected $profileReviews;
  212. public function addProfileReview(ProfileReview $profileReview): void
  213. {
  214. $this->profileReviews[] = $profileReview;
  215. }
  216. public function getProfileReviews()
  217. {
  218. return $this->profileReviews;
  219. }
  220. /**
  221. * @var bool
  222. *
  223. * @ORM\Column(name="mobilenumber_public", type="boolean", nullable=true)
  224. */
  225. protected $mobilenumberPublic;
  226. public function getMobilenumberPublic(): ?bool
  227. {
  228. return $this->mobilenumberPublic;
  229. }
  230. public function setMobilenumberPublic(?bool $mobilenumberPublic): void
  231. {
  232. $this->mobilenumberPublic = $mobilenumberPublic;
  233. }
  234. /**
  235. * @ORM\Column(name="whatsapp_allowed", type="boolean", nullable=true)
  236. */
  237. protected ?bool $whatsappAllowed = null;
  238. public function getWhatsappAllowed(): ?bool
  239. {
  240. return $this->whatsappAllowed;
  241. }
  242. public function setWhatsappAllowed(?bool $whatsappAllowed): void
  243. {
  244. $this->whatsappAllowed = $whatsappAllowed;
  245. }
  246. /**
  247. * @var ProfileBlock|Collection
  248. *
  249. * @ORM\OneToMany(targetEntity="\App\Entity\ProfileBlock", mappedBy="jobseekerProfile", cascade={"persist", "remove"})
  250. */
  251. protected $profileBlocks;
  252. public function addProfileBlock(ProfileBlock $profileBlock): void
  253. {
  254. $this->profileBlocks[] = $profileBlock;
  255. }
  256. public function getProfileBlocks()
  257. {
  258. return $this->profileBlocks;
  259. }
  260. /** @throws Exception */
  261. public function getProfileBlockForBlockOfJoboffererProfile(JoboffererProfile $joboffererProfile): ProfileBlock
  262. {
  263. /** @var ProfileBlock $profileBlock */
  264. foreach ($this->profileBlocks as $profileBlock) {
  265. if ($profileBlock->isBlocker($this) && !is_null($profileBlock->getJoboffererProfile()) && $profileBlock->getJoboffererProfile()->getId() === $joboffererProfile->getId()) {
  266. return $profileBlock;
  267. }
  268. }
  269. throw new Exception('Jobseeker profile ' . $this->getId() . ' has not blocked jobofferer profile ' . $joboffererProfile->getId());
  270. }
  271. /** @throws Exception */
  272. public function getProfileBlockForBlockOfCustomer(IntegratedExternalPartnerCustomer $customer): ProfileBlock
  273. {
  274. /** @var ProfileBlock $profileBlock */
  275. foreach ($this->profileBlocks as $profileBlock) {
  276. if ($profileBlock->isBlocker($this) && !is_null($profileBlock->getIntegratedExternalPartnerCustomer()) && $profileBlock->getIntegratedExternalPartnerCustomer()->getId() === $customer->getId()) {
  277. return $profileBlock;
  278. }
  279. }
  280. throw new Exception('Jobseeker profile ' . $this->getId() . ' has not blocked jobofferer profile ' . $customer->getId());
  281. }
  282. /**
  283. * @var ProfileFavorization|Collection
  284. *
  285. * @ORM\OneToMany(targetEntity="\App\Entity\ProfileFavorization", mappedBy="jobseekerProfile", cascade={"persist", "remove"})
  286. */
  287. protected $profileFavorizations;
  288. public function addProfileFavorization(ProfileFavorization $profileFavorization): void
  289. {
  290. $this->profileFavorizations[] = $profileFavorization;
  291. }
  292. public function getProfileFavorizations()
  293. {
  294. return $this->profileFavorizations;
  295. }
  296. /** @throws Exception */
  297. public function getProfileFavorizationForFavorizationOfJoboffererProfile(JoboffererProfile $joboffererProfile): ProfileFavorization
  298. {
  299. /** @var ProfileFavorization $profileFavorization */
  300. foreach ($this->profileFavorizations as $profileFavorization) {
  301. if ($profileFavorization->isFavorer($this) && $profileFavorization->getJoboffererProfile()->getId() === $joboffererProfile->getId()) {
  302. return $profileFavorization;
  303. }
  304. }
  305. throw new Exception('Jobseeker profile ' . $this->getId() . ' has not favored jobofferer profile ' . $joboffererProfile->getId());
  306. }
  307. /**
  308. * @var JobseekerOccupationalFieldCapabilityValue
  309. *
  310. * @ORM\OneToMany(targetEntity="\App\Entity\JobseekerOccupationalFieldCapabilityValue", mappedBy="jobseekerProfile", cascade={"persist", "remove"})
  311. */
  312. protected $occupationalFieldCapabilityValues;
  313. /** @return JobseekerOccupationalFieldCapabilityValue[]|Collection */
  314. public function getOccupationalFieldCapabilityValues(): Collection
  315. {
  316. return $this->occupationalFieldCapabilityValues;
  317. }
  318. public function hasAtLeastOneOccupationalFieldCapabilityAboveZero(?EntityCacheService $entityCacheService = null, string $entityCacheContext = ''): bool
  319. {
  320. $occupationalFieldCapabilityValues = null;
  321. if (!is_null($entityCacheService)) {
  322. $occupationalFieldCapabilityValues = $entityCacheService->getEntitiesFromCacheForEntityClassNameByField(
  323. JobseekerOccupationalFieldCapabilityValue::class,
  324. 'jobseekerProfile',
  325. $this->getId(),
  326. $entityCacheContext
  327. );
  328. }
  329. if (is_null($occupationalFieldCapabilityValues)) {
  330. $occupationalFieldCapabilityValues = $this->occupationalFieldCapabilityValues;
  331. }
  332. /** @var JobseekerOccupationalFieldCapabilityValue $occupationalFieldCapabilityValue */
  333. foreach ($occupationalFieldCapabilityValues as $occupationalFieldCapabilityValue) {
  334. if ($occupationalFieldCapabilityValue->getValue() > 0) {
  335. return true;
  336. }
  337. }
  338. return false;
  339. }
  340. public function getAllOccupationalFieldIdsToCapabilityIdsToValuesSlim(): array
  341. {
  342. $result = [];
  343. foreach (OccupationalFieldCapabilitiesService::CAPABILITY_IDS_BY_OCCUPATIONAL_FIELD_IDS as $occupationalFieldId => $capabilityIds) {
  344. foreach ($capabilityIds as $capabilityId) {
  345. $value = 0;
  346. /** @var JobseekerOccupationalFieldCapabilityValue $occupationalFieldCapabilityValue */
  347. foreach ($this->occupationalFieldCapabilityValues as $occupationalFieldCapabilityValue) {
  348. if ($occupationalFieldCapabilityValue->getCapabilityId() === $capabilityId) {
  349. $value = $occupationalFieldCapabilityValue->getValue();
  350. }
  351. }
  352. $result[$capabilityId] = $value;
  353. }
  354. }
  355. return $result;
  356. }
  357. public function getAllOccupationalFieldIdsToCapabilityIdsToValuesSlimIncludeLegacy(): array
  358. {
  359. $result = [];
  360. $capabilities = OccupationalFieldCapabilitiesService::CAPABILITY_IDS_BY_OCCUPATIONAL_FIELD_IDS;
  361. array_push($capabilities[0], OccupationalFieldCapabilitiesService::CAPABILITY_ID_GENERAL_LEGACY);
  362. foreach ($capabilities as $occupationalFieldId => $capabilityIds) {
  363. foreach ($capabilityIds as $capabilityId) {
  364. $value = 0;
  365. /** @var JobseekerOccupationalFieldCapabilityValue $occupationalFieldCapabilityValue */
  366. foreach ($this->occupationalFieldCapabilityValues as $occupationalFieldCapabilityValue) {
  367. if ($occupationalFieldCapabilityValue->getCapabilityId() === $capabilityId) {
  368. $value = $occupationalFieldCapabilityValue->getValue();
  369. }
  370. }
  371. $result[$capabilityId] = $value;
  372. }
  373. }
  374. return $result;
  375. }
  376. /**
  377. * @var WantedJob[]|Collection
  378. *
  379. * @ORM\OneToMany(targetEntity="\App\Entity\WantedJob", mappedBy="jobseekerProfile", cascade={"persist", "remove"})
  380. */
  381. protected $wantedJobs;
  382. public function addWantedJob(WantedJob $wantedJob): void
  383. {
  384. $this->wantedJobs[] = $wantedJob;
  385. }
  386. /**
  387. * @return WantedJob[]|Collection
  388. */
  389. public function getWantedJobs(): Collection
  390. {
  391. return $this->wantedJobs;
  392. }
  393. /**
  394. * @var OccupationalField|Collection
  395. *
  396. * @ORM\ManyToMany(targetEntity="App\Entity\OccupationalField", inversedBy="jobseekerProfiles", cascade={"persist"})
  397. *
  398. * @ORM\JoinTable(
  399. * name="jobseeker_profiles_occupational_fields",
  400. * joinColumns={
  401. *
  402. * @ORM\JoinColumn(name="jobseeker_profile_id", referencedColumnName="id", onDelete="CASCADE")
  403. * },
  404. * inverseJoinColumns={
  405. * @ORM\JoinColumn(name="occupational_field_id", referencedColumnName="id", onDelete="CASCADE")
  406. * }
  407. * )
  408. *
  409. * @Assert\Type("\Doctrine\Common\Collections\Collection")
  410. */
  411. protected $occupationalFields;
  412. public function setOccupationalFields(Collection $occupationalFields)
  413. {
  414. $this->occupationalFields = $occupationalFields;
  415. }
  416. /**
  417. * @return Collection|OccupationalField[]
  418. */
  419. public function getOccupationalFields(?EntityCacheService $entityCacheService = null, string $entityCacheContext = ''): Collection
  420. {
  421. $occupationalFields = null;
  422. if (!is_null($entityCacheService)) {
  423. $occupationalFields = $entityCacheService->getEntitiesFromCacheForEntityClassNameByField(
  424. OccupationalField::class,
  425. 'jobseekerProfile',
  426. $this->getId(),
  427. $entityCacheContext
  428. );
  429. }
  430. if (is_null($occupationalFields)) {
  431. return $this->occupationalFields;
  432. } else {
  433. return new ArrayCollection($occupationalFields);
  434. }
  435. }
  436. /**
  437. * @var JobseekerProfileAdditionalFile|Collection
  438. *
  439. * @ORM\OneToMany(targetEntity="App\Entity\JobseekerProfileAdditionalFile", mappedBy="jobseekerProfile", cascade={"persist", "remove"})
  440. *
  441. * @Assert\Count(min=0,max=10)
  442. *
  443. * @Assert\Type("\Doctrine\Common\Collections\Collection")
  444. */
  445. protected $additionalFiles;
  446. public function setAdditionalFiles(Collection $additionalFiles): void
  447. {
  448. $this->additionalFiles = $additionalFiles;
  449. }
  450. public function getAdditionalFiles(): Collection
  451. {
  452. if (is_null($this->additionalFiles)) {
  453. return new ArrayCollection();
  454. } else {
  455. return $this->additionalFiles;
  456. }
  457. }
  458. /**
  459. * @var string
  460. *
  461. * @ORM\Column(name="availability_zipcode", type="string", length=5, nullable=true)
  462. *
  463. * @Assert\Type("string")
  464. *
  465. * @Assert\NotNull()
  466. *
  467. * @Assert\NotBlank()
  468. *
  469. * @AppAssert\KnownZipcode
  470. */
  471. protected $availabilityZipcode;
  472. public function setAvailabilityZipcode(?string $availabilityZipcode = null): void
  473. {
  474. if (!is_null($availabilityZipcode)) {
  475. $trimmedZipcode = trim($availabilityZipcode);
  476. if (mb_strlen($trimmedZipcode) !== 5 || !is_numeric($trimmedZipcode)) {
  477. throw new InvalidArgumentException("zipcode must either be null or a string with 5 digits, but got '$availabilityZipcode'");
  478. }
  479. $availabilityZipcode = $trimmedZipcode;
  480. }
  481. $this->availabilityZipcode = $availabilityZipcode;
  482. }
  483. public function getAvailabilityZipcode(): ?string
  484. {
  485. return $this->availabilityZipcode;
  486. }
  487. /**
  488. * @var int
  489. *
  490. * @ORM\Column(name="availability_radius", type="integer", nullable=false)
  491. *
  492. * @Assert\Type("int")
  493. */
  494. protected $availabilityRadius;
  495. public function getLegacyAvailabilityRadius(): int
  496. {
  497. return $this->availabilityRadius;
  498. }
  499. /**
  500. * @var int
  501. *
  502. * @ORM\Column(name="experience", type="smallint", nullable=false)
  503. *
  504. * @Assert\Type("int")
  505. */
  506. protected $experience;
  507. public const EXPERIENCE_NONE = 0;
  508. public const EXPERIENCE_MORE_THAN_ONE_YEAR = 1;
  509. public const EXPERIENCE_MORE_THAN_THREE_YEARS = 3;
  510. public const EXPERIENCE_MORE_THAN_FIVE_YEARS = 5;
  511. // This may only grow, never remove existing entries because it makes data already in the db invalid!
  512. // Change POSSIBLE_EXPERIENCES_AVAILABLE_FOR_SELECTION instead.
  513. public const POSSIBLE_EXPERIENCES = [
  514. self::EXPERIENCE_NONE,
  515. self::EXPERIENCE_MORE_THAN_ONE_YEAR,
  516. self::EXPERIENCE_MORE_THAN_THREE_YEARS,
  517. self::EXPERIENCE_MORE_THAN_FIVE_YEARS,
  518. ];
  519. public const POSSIBLE_EXPERIENCES_AVAILABLE_FOR_SELECTION = [
  520. self::EXPERIENCE_NONE,
  521. self::EXPERIENCE_MORE_THAN_ONE_YEAR,
  522. self::EXPERIENCE_MORE_THAN_THREE_YEARS
  523. ];
  524. public const POSSIBLE_EXPERIENCES_AVAILABLE_FOR_SELECTION_WITH_TRANSLATION_MAPPING = [
  525. 'profiles.jobseeker.experience_none' => self::EXPERIENCE_NONE,
  526. 'profiles.jobseeker.experience_more_than_one_year' => self::EXPERIENCE_MORE_THAN_ONE_YEAR,
  527. 'profiles.jobseeker.experience_more_than_three_years' => self::EXPERIENCE_MORE_THAN_THREE_YEARS
  528. ];
  529. // This needs to contain ALL translations, even for entries that are not available in the frontend,
  530. // because the db can contain entries with an experience value that is no longer offered, but must
  531. // still be presented in the frontend
  532. public const POSSIBLE_EXPERIENCES_WITH_TRANSLATION_MAPPING_REVERSE = [
  533. self::EXPERIENCE_NONE => 'profiles.jobseeker.experience_none',
  534. self::EXPERIENCE_MORE_THAN_ONE_YEAR => 'profiles.jobseeker.experience_more_than_one_year',
  535. self::EXPERIENCE_MORE_THAN_THREE_YEARS => 'profiles.jobseeker.experience_more_than_three_years',
  536. self::EXPERIENCE_MORE_THAN_FIVE_YEARS => 'profiles.jobseeker.experience_more_than_five_years'
  537. ];
  538. public function getLegacyExperience(): int
  539. {
  540. return $this->experience;
  541. }
  542. /**
  543. * @Assert\Callback
  544. *
  545. * We have to handle two stages of validation here. If the user has not yet created a valid "base profile"
  546. * consisting of firstname, occupational fields, availabilities, availabilityZipcode, and zipcodeCircumcircle,
  547. * then we do not yet validate the fields for the "extended profile" like lastname, description, address etc.
  548. */
  549. public function validate(ExecutionContextInterface $context, $payload): void
  550. {
  551. parent::validate($context, $payload);
  552. /** @var JobseekerProfileAdditionalFile $additionalFile */
  553. foreach ($this->additionalFiles as $additionalFile) {
  554. if (!is_null($additionalFile->getFile()) && $additionalFile->getFile() != '') {
  555. if (!in_array($additionalFile->getFile()->getMimeType(), MimeTypes::ALLOWED_FOR_USER_UPLOAD_ALL)) {
  556. $context
  557. ->buildViolation('profiles.editor_page.additional_file_filetype_not_allowed')
  558. ->setTranslationDomain('messages')
  559. ->atPath('additionalFiles')
  560. ->addViolation();
  561. }
  562. if ($additionalFile->getFile()->getSize() > 11485760) { // 10 MiB plus a bit of buffer
  563. $context
  564. ->buildViolation('profiles.editor_page.additional_file_file_too_large')
  565. ->setTranslationDomain('messages')
  566. ->atPath('additionalFiles')
  567. ->addViolation();
  568. }
  569. }
  570. }
  571. }
  572. /**
  573. * @var DateTime
  574. *
  575. * @ORM\Column(name="paused_since", type="datetime", nullable=true)
  576. */
  577. private $pausedSince;
  578. public function getPausedSince(): ?DateTime
  579. {
  580. return $this->pausedSince;
  581. }
  582. public function setPausedSince(?DateTime $pausedSince = null): void
  583. {
  584. $this->pausedSince = $pausedSince;
  585. }
  586. public function isPaused(): bool
  587. {
  588. return $this->pausedSince !== null;
  589. }
  590. public function getStarsScoreRating(): float
  591. {
  592. $score = 2.5;
  593. $step = 0.5;
  594. $score += $this->getSelfdescription() ? $step : 0;
  595. $score += $this->getMobilenumber() ? $step : 0;
  596. $score += $this->getDocumentFileName() ? $step : 0;
  597. $score += $this->getPhotoFileName() ? $step : 0;
  598. $score += $this->hasAtLeastOneOccupationalFieldCapabilityAboveZero() ? $step : 0;
  599. return $score;
  600. }
  601. public function getZipcodeIfAvailable(): ?string
  602. {
  603. if (!is_null($this->availabilityZipcode)) {
  604. return $this->availabilityZipcode;
  605. }
  606. if (!is_null($this->zipcode)) {
  607. return $this->zipcode;
  608. }
  609. return null;
  610. }
  611. }