diff --git a/composer.json b/composer.json index b0eddc727..c9b4604dd 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "minimum-stability": "dev", "prefer-stable": true, "require": { - "asuntomyynti/react": "1.4.7b", + "asuntomyynti/react": "1.4.9b", "composer/installers": "^2.0", "cweagans/composer-patches": "^1.7.3", "dinbror/blazy": "^1.8", @@ -186,9 +186,9 @@ "type": "package", "package": { "name": "asuntomyynti/react", - "version": "1.4.7b", + "version": "1.4.9b", "dist": { - "url": "https://github.com/City-of-Helsinki/asuntomyynti-react/releases/download/v1.4.7b/asuntomyynti-react-1.4.7b.zip", + "url": "https://github.com/City-of-Helsinki/asuntomyynti-react/releases/download/v1.4.9b/asuntomyynti-react-1.4.9b.zip", "type": "zip" } } diff --git a/composer.lock b/composer.lock index 21cd91b68..44fd4f66b 100644 --- a/composer.lock +++ b/composer.lock @@ -59,10 +59,10 @@ }, { "name": "asuntomyynti/react", - "version": "1.4.7b", + "version": "1.4.9b", "dist": { "type": "zip", - "url": "https://github.com/City-of-Helsinki/asuntomyynti-react/releases/download/v1.4.7b/asuntomyynti-react-1.4.7b.zip" + "url": "https://github.com/City-of-Helsinki/asuntomyynti-react/releases/download/v1.4.9b/asuntomyynti-react-1.4.9b.zip" }, "type": "library" }, diff --git a/public/modules/custom/asu_api/asu_api.module b/public/modules/custom/asu_api/asu_api.module index aee2e44c9..8cd44dfa5 100644 --- a/public/modules/custom/asu_api/asu_api.module +++ b/public/modules/custom/asu_api/asu_api.module @@ -105,7 +105,7 @@ function asu_api_get_apartment_status_request_cron() { return; } - $free_states = ['for_sale', 'free_for_reservations']; + $free_states = ['for_sale', 'free_for_reservations', 'reserved', 'reserved_haso']; foreach ($nids as $nid) { /** @var \Drupal\node\Entity\Node $node */ @@ -139,7 +139,7 @@ function asu_api_get_apartment_status_request_cron() { $dirty = TRUE; } } - elseif ($current === 'sold' && in_array($new, $free_states, TRUE)) { + elseif (in_array($new, $free_states, TRUE)) { if (!$node->isPublished()) { $node->setPublished(TRUE); $dirty = TRUE; diff --git a/public/modules/custom/asu_application/src/Form/ApplicationForm.php b/public/modules/custom/asu_application/src/Form/ApplicationForm.php index 521696334..a394ff6d0 100644 --- a/public/modules/custom/asu_application/src/Form/ApplicationForm.php +++ b/public/modules/custom/asu_application/src/Form/ApplicationForm.php @@ -197,6 +197,15 @@ public function buildForm(array $form, FormStateInterface $form_state) { array_push($limit, ['reserved', 'reserved_haso']); } + // Dont allow users who have a reservation with the state + // 'offered', 'offer_accepted' or 'sold' on the project to apply. + // This shouldn't normally be shown except if the user uses + // the application/add// link directly. + if ($project->getUserHasReservedOrSoldApartments($this->currentUser->id())) { + $this->messenger()->addError($this->t('You already have an offer or have been sold an apartment in this project and cannot submit a new application.')); + return new RedirectResponse($form['#project_url']->toString()); + } + if (!$project_data = $this->getApartments($project, $limit)) { $this->logger('asu_application')->critical('User tried to access nonexistent project of id ' . $project_id); $this->messenger()->addMessage($this->t('Unfortunately the project you are trying to apply for is unavailable.')); @@ -318,7 +327,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['actions']['draft'] = [ '#type' => 'submit', '#value' => $this->t('Save as a draft'), - '#attributes' => ['class' => ['hds-button--secondary']], + '#attributes' => ['class' => ['hds-button--secondary application-as-draft-button']], '#limit_validation_errors' => [], '#name' => 'submit-draft', '#submit' => ['::submitDraft'], @@ -525,6 +534,8 @@ public function save(array $form, FormStateInterface $form_state) { /** * Handle saving the form values. * + * Deletes old application. + * * @param array $form * Form array. * @param \Drupal\Core\Form\FormStateInterface $form_state @@ -539,7 +550,12 @@ private function doSave(array $form, FormStateInterface $form_state, $errors = T $oldBackendId = $this->entity->get('field_backend_id')->value ?? NULL; $confirmDeletion = $form_state->getValue('confirm_application_deletion') ?? 'NOT SET'; - if ($oldBackendId && $confirmDeletion == '1') { + $project_id = $this->entity->get('project_id')->value; + $project = $this->entityTypeManager->getStorage('node')->load($project_id); + $canApplyAfterwards = $project->get('field_can_apply_afterwards')->value; + + // Applications made after shouldn't be deleted. + if ($oldBackendId && $confirmDeletion == '1' && !$canApplyAfterwards) { try { $user = \Drupal::entityTypeManager()->getStorage('user')->load(\Drupal::currentUser()->id()); \Drupal::service('asu_api.backendapi')->deleteApplication($user, $oldBackendId); diff --git a/public/modules/custom/asu_content/src/Entity/Project.php b/public/modules/custom/asu_content/src/Entity/Project.php index 0018480db..0b3784356 100644 --- a/public/modules/custom/asu_content/src/Entity/Project.php +++ b/public/modules/custom/asu_content/src/Entity/Project.php @@ -4,6 +4,8 @@ use Drupal\node\Entity\Node; use Drupal\user\UserInterface; +use Drupal\user\Entity\User; +use Drupal\asu_api\Api\BackendApi\Request\ApplicationLotteryResult; /** * Class for node's project bundle. @@ -70,7 +72,7 @@ public function getCanApplyAfterwards(): string { if ($field_can_apply_afterwards->isEmpty()) { return ""; } - \Drupal::logger('asu_application')->info("field_can_apply_afterwards: " . $field_can_apply_afterwards->value); + return $field_can_apply_afterwards->value; } @@ -163,6 +165,56 @@ public function isArchievable(): bool { return TRUE; } + /** + * Get reservations the given user has for the project's apartments. + * + * @param int $userId + * Id of the user to check. + * + * @return array + * reservations + */ + public function getUserReservations($userId): array { + $user = User::load(\Drupal::currentUser()->id()); + + // Fetch reservations for this project. + $request = new ApplicationLotteryResult($this->uuid()); + $request->setSender($user); + $backendApi = \Drupal::service('asu_api.backendapi'); + + $userReservations = $backendApi + ->send($request) + ->getContent(); + + return $userReservations; + } + + /** + * Checks if the given user has a reservation with the state 'offered'. + * + * 'offer_accepted' or 'sold' on the project. + * + * @param int $userId + * Id of the user to check. + * + * @return bool + * If user has a reservation with those states. + */ + public function getUserHasReservedOrSoldApartments($userId): bool { + $userHasReservedOrSoldApartment = FALSE; + $userReservations = $this->getUserReservations($userId); + $states = ['offered', 'offer_accepted', 'sold']; + + // phpcs:ignore + foreach ($userReservations as $key => $reservation) { + + if (in_array($reservation['state'], $states)) { + $userHasReservedOrSoldApartment = TRUE; + } + } + return $userHasReservedOrSoldApartment; + } + /** * Get project sales person information. * diff --git a/public/modules/custom/asu_content/translations/fi.po b/public/modules/custom/asu_content/translations/fi.po index 43a957258..47dd9343b 100644 --- a/public/modules/custom/asu_content/translations/fi.po +++ b/public/modules/custom/asu_content/translations/fi.po @@ -43323,3 +43323,6 @@ msgid "Tunnistamo" msgstr "" msgid "Helsinki Logo" msgstr "Helsingin kaupungin logo" + +msgid "You already have an offer or have been sold an apartment in this project and cannot submit a new application." +msgstr "Sinulla on jo myyty tai tarjottu asunto tässä kohteessa etkä siksi voi tehdä uutta hakemusta." diff --git a/public/themes/custom/asuntotuotanto/asuntotuotanto.theme b/public/themes/custom/asuntotuotanto/asuntotuotanto.theme index ca12f8697..09e0582e1 100644 --- a/public/themes/custom/asuntotuotanto/asuntotuotanto.theme +++ b/public/themes/custom/asuntotuotanto/asuntotuotanto.theme @@ -229,9 +229,12 @@ function asuntotuotanto_preprocess_views_view(&$variables) { } /** + * Passes variables to project-apartments-listing.html.twig. + * * Implements hook_preprocess_HOOK(). */ function asuntotuotanto_preprocess_views_view_table(&$variables) { + if ($variables['view']->id() === 'project_apartments_listing') { $results = $variables['result']; @@ -251,6 +254,14 @@ function asuntotuotanto_preprocess_views_view_table(&$variables) { $variables['header']['field_debt_free_sales_price']['content'] = $price_title; unset($variables['header']['field_release_payment']); + $userHasReservedOrSoldApartment = FALSE; + if (\Drupal::currentUser()->isAuthenticated()) { + // Check if user has offered, offer_accepted or sold reservations. + $userHasReservedOrSoldApartment = $project->getUserHasReservedOrSoldApartments( + \Drupal::currentUser()->id() + ); + } + foreach ($results as $key => $row) { /** @var \Drupal\asu_content\Entity\Apartment $entity */ $apartment = $row->_entity; @@ -282,6 +293,7 @@ function asuntotuotanto_preprocess_views_view_table(&$variables) { $variables['rows'][$key]['application_url_title'] = $apartment->getApplicationUrlTitle(); $variables['rows'][$key]['is_free'] = $apartment->isFree(); $variables['rows'][$key]['can_apply_afterwards'] = $project->getCanApplyAfterwards(); + $variables['rows'][$key]['user_has_reserved_or_sold_apartment'] = $userHasReservedOrSoldApartment; } usort($variables['rows'], function ($a, $b) { @@ -589,6 +601,14 @@ function asuntotuotanto_preprocess_node(&$variables) { ); } + $userHasReservedOrSoldApartment = FALSE; + if (\Drupal::currentUser()->isAuthenticated()) { + // Check if user has offered, offer_accepted or sold reservations. + $userHasReservedOrSoldApartment = $project->getUserHasReservedOrSoldApartments( + \Drupal::currentUser()->id() + ); + } + if (!empty($node->field_street_address->value)) { $street_address_splitted = split_address_line($node->field_street_address->value); @@ -618,6 +638,9 @@ function asuntotuotanto_preprocess_node(&$variables) { $variables['estimated_completion_date'] = $estimated_completion_date; $variables['is_application_period_active'] = $is_application_period_active; $variables['project_type'] = $project_type; + // No idea why, but shows up as null if it has. + // The more sensible name of 'user_has_reserved_or_sold_apartment'. + $variables['current_user_has_reserved_or_sold_apartment'] = $userHasReservedOrSoldApartment; $variables['apartment_prices'] = $prices_string; break; @@ -768,6 +791,14 @@ function asuntotuotanto_preprocess_node(&$variables) { } } + $userHasReservedOrSoldApartment = FALSE; + if (\Drupal::currentUser()->isAuthenticated()) { + // Check if user has offered, offer_accepted or sold reservations. + $userHasReservedOrSoldApartment = $project->getUserHasReservedOrSoldApartments( + \Drupal::currentUser()->id() + ); + } + $street_address = $project->get('field_street_address')->value ?? NULL; $postal_code = $project->get('field_postal_code')->value ?? NULL; $city = $project->get('field_city')->value ?? NULL; @@ -819,6 +850,7 @@ function asuntotuotanto_preprocess_node(&$variables) { $variables['field_parking_fee'] = $apartment->get('field_parking_fee')->getValue()[0]['value'] ?? NULL; $variables['field_parking_fee_explanation'] = $apartment->get('field_parking_fee_explanation')->getValue()[0]['value'] ?? NULL; $variables['field_other_fees'] = $apartment->get('field_other_fees')->getValue()[0]['value'] ?? NULL; + $variables['current_user_has_reserved_or_sold_apartment'] = $userHasReservedOrSoldApartment; } break; } @@ -1017,7 +1049,9 @@ function get_project_apartment_teaser_values($project_id) { $values['project_ownership_type'] = $project->field_ownership_type->entity ? $project->field_ownership_type->entity->name->value : ''; $values['project_district'] = $project->field_district->entity ? $project->field_district->entity->name->value : ''; $values['project_main_image_url'] = isset($project->field_main_image[0]) ? \Drupal::service('file_url_generator')->generateAbsoluteString($project->field_main_image[0]->entity->getFileUri()) : ''; - $values['can_apply_afterwards'] = $project->getCanApplyAfterwards(); + if ($project) { + $values['can_apply_afterwards'] = $project->get('field_can_apply_afterwards')->value; + } foreach ($fields as $key => $field) { if ($project && $project->hasField($field)) { diff --git a/public/themes/custom/asuntotuotanto/src/scss/06_components/content/apartment/_apartment-cta.scss b/public/themes/custom/asuntotuotanto/src/scss/06_components/content/apartment/_apartment-cta.scss index ecd51abd4..ce9e1ab63 100644 --- a/public/themes/custom/asuntotuotanto/src/scss/06_components/content/apartment/_apartment-cta.scss +++ b/public/themes/custom/asuntotuotanto/src/scss/06_components/content/apartment/_apartment-cta.scss @@ -111,4 +111,8 @@ line-height: $lineheight-m; background-color: transparent; } } + + .application-as-draft-button { + opacity: 0.75; + } } diff --git a/public/themes/custom/asuntotuotanto/templates/content/node--apartment--full.html.twig b/public/themes/custom/asuntotuotanto/templates/content/node--apartment--full.html.twig index d40c68a1c..ac2ee538e 100644 --- a/public/themes/custom/asuntotuotanto/templates/content/node--apartment--full.html.twig +++ b/public/themes/custom/asuntotuotanto/templates/content/node--apartment--full.html.twig @@ -189,7 +189,9 @@ {% endif %} +
+ {% if current_user_has_reserved_or_sold_apartment == FALSE %} {% if is_application_period_active %} {% include '@asuntotuotanto/button/button.html.twig' with { type: 'primary', @@ -197,16 +199,17 @@ label: 'Create an application'|t, href: application_url } - %} - {% elseif can_apply_afterwards == 1 %} - {% include '@asuntotuotanto/button/button.html.twig' with { - type: 'primary', - disabled: false, - label: 'Create an after-application'|t, - href: application_url - } - %} - {% endif %} + %} + {% elseif can_apply_afterwards == 1 %} + {% include '@asuntotuotanto/button/button.html.twig' with { + type: 'primary', + disabled: false, + label: 'Create an after-application'|t, + href: application_url + } + %} + {% endif %} + {% endif %}

{% if (is_application_period_in_the_past is same as(false)) and (application_start_time != null and application_end_time != null) %} {% trans %} diff --git a/public/themes/custom/asuntotuotanto/templates/content/node--project--full.html.twig b/public/themes/custom/asuntotuotanto/templates/content/node--project--full.html.twig index f19bf8d44..91b51d3e7 100644 --- a/public/themes/custom/asuntotuotanto/templates/content/node--project--full.html.twig +++ b/public/themes/custom/asuntotuotanto/templates/content/node--project--full.html.twig @@ -72,6 +72,19 @@ #} {{ attach_library('asuntotuotanto/sticky-navigation') }} +{% if current_user_has_reserved_or_sold_apartment %} +

+
+
+ + {% trans %}Varoitusviesti{% endtrans %} +
+
+ {% trans %}Sinulla on jo myyty tai tarjottu asunto tässä kohteessa etkä siksi voi tehdä uutta hakemusta.{% endtrans %} +
+
+
+{% endif %} {% set classes = [ @@ -83,7 +96,6 @@ 'project' ] %} - {% set images = content.field_images %} {% set district = content.field_district.0 %} {% set street_address = content.field_street_address.0['#context']['value'] %} @@ -254,6 +266,7 @@ Project description {% endtrans %} +
{{ project_description|raw }}
{% if sales_email %}
diff --git a/public/themes/custom/asuntotuotanto/templates/views/project/views-view-table--project-apartments-listing.html.twig b/public/themes/custom/asuntotuotanto/templates/views/project/views-view-table--project-apartments-listing.html.twig index deb8d6f73..58418be32 100644 --- a/public/themes/custom/asuntotuotanto/templates/views/project/views-view-table--project-apartments-listing.html.twig +++ b/public/themes/custom/asuntotuotanto/templates/views/project/views-view-table--project-apartments-listing.html.twig @@ -41,7 +41,6 @@ sticky ? 'sticky-enabled', ] %} - {# === Pre-pass: single calculation for mobile + desktop to avoid duplicating code === #} {% set _computed = [] %} {% for row in rows %} @@ -279,6 +278,7 @@ {% set application_url_title = row.application_url_title %} {% set can_apply_afterwards = row.can_apply_afterwards %} {% set is_free = row.is_free %} + {% set user_has_reserved_or_sold_apartment = row.user_has_reserved_or_sold_apartment %} {# pre-calculated values #} {% set comp = _computed[loop.index0] %} @@ -322,17 +322,16 @@ } %} - {# {% if (is_application_period_active or (is_free or can_apply_afterwards)) %} + {% if (is_application_period_active or (is_free or can_apply_afterwards)) and not user_has_reserved_or_sold_apartment %} {% include '@asuntotuotanto/button/button.html.twig' with { type: 'primary', - disabled: false, size: 'small', label: can_apply_afterwards ? 'Create an after-application'|t : application_url_title|t, href: application_url, aria_label: 'Create an application'|t ~ ', ' ~ 'apartment'|t ~ ' ' ~ apartment_number } %} - {% endif %} #} + {% endif %} {% endfor %}