Skip to content

Commit c553c57

Browse files
Merge pull request #801 from City-of-Helsinki/release-2025-11-12
Release 2025 11 12
2 parents 68d03cc + eeac50e commit c553c57

File tree

11 files changed

+150
-26
lines changed

11 files changed

+150
-26
lines changed

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"minimum-stability": "dev",
88
"prefer-stable": true,
99
"require": {
10-
"asuntomyynti/react": "1.4.7b",
10+
"asuntomyynti/react": "1.4.9b",
1111
"composer/installers": "^2.0",
1212
"cweagans/composer-patches": "^1.7.3",
1313
"dinbror/blazy": "^1.8",
@@ -186,9 +186,9 @@
186186
"type": "package",
187187
"package": {
188188
"name": "asuntomyynti/react",
189-
"version": "1.4.7b",
189+
"version": "1.4.9b",
190190
"dist": {
191-
"url": "https://github.com/City-of-Helsinki/asuntomyynti-react/releases/download/v1.4.7b/asuntomyynti-react-1.4.7b.zip",
191+
"url": "https://github.com/City-of-Helsinki/asuntomyynti-react/releases/download/v1.4.9b/asuntomyynti-react-1.4.9b.zip",
192192
"type": "zip"
193193
}
194194
}

composer.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/modules/custom/asu_api/asu_api.module

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ function asu_api_get_apartment_status_request_cron() {
105105
return;
106106
}
107107

108-
$free_states = ['for_sale', 'free_for_reservations'];
108+
$free_states = ['for_sale', 'free_for_reservations', 'reserved', 'reserved_haso'];
109109

110110
foreach ($nids as $nid) {
111111
/** @var \Drupal\node\Entity\Node $node */
@@ -139,7 +139,7 @@ function asu_api_get_apartment_status_request_cron() {
139139
$dirty = TRUE;
140140
}
141141
}
142-
elseif ($current === 'sold' && in_array($new, $free_states, TRUE)) {
142+
elseif (in_array($new, $free_states, TRUE)) {
143143
if (!$node->isPublished()) {
144144
$node->setPublished(TRUE);
145145
$dirty = TRUE;

public/modules/custom/asu_application/src/Form/ApplicationForm.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,15 @@ public function buildForm(array $form, FormStateInterface $form_state) {
197197
array_push($limit, ['reserved', 'reserved_haso']);
198198
}
199199

200+
// Dont allow users who have a reservation with the state
201+
// 'offered', 'offer_accepted' or 'sold' on the project to apply.
202+
// This shouldn't normally be shown except if the user uses
203+
// the application/add/<type>/<project> link directly.
204+
if ($project->getUserHasReservedOrSoldApartments($this->currentUser->id())) {
205+
$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.'));
206+
return new RedirectResponse($form['#project_url']->toString());
207+
}
208+
200209
if (!$project_data = $this->getApartments($project, $limit)) {
201210
$this->logger('asu_application')->critical('User tried to access nonexistent project of id ' . $project_id);
202211
$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) {
318327
$form['actions']['draft'] = [
319328
'#type' => 'submit',
320329
'#value' => $this->t('Save as a draft'),
321-
'#attributes' => ['class' => ['hds-button--secondary']],
330+
'#attributes' => ['class' => ['hds-button--secondary application-as-draft-button']],
322331
'#limit_validation_errors' => [],
323332
'#name' => 'submit-draft',
324333
'#submit' => ['::submitDraft'],
@@ -525,6 +534,8 @@ public function save(array $form, FormStateInterface $form_state) {
525534
/**
526535
* Handle saving the form values.
527536
*
537+
* Deletes old application.
538+
*
528539
* @param array $form
529540
* Form array.
530541
* @param \Drupal\Core\Form\FormStateInterface $form_state
@@ -539,7 +550,12 @@ private function doSave(array $form, FormStateInterface $form_state, $errors = T
539550
$oldBackendId = $this->entity->get('field_backend_id')->value ?? NULL;
540551
$confirmDeletion = $form_state->getValue('confirm_application_deletion') ?? 'NOT SET';
541552

542-
if ($oldBackendId && $confirmDeletion == '1') {
553+
$project_id = $this->entity->get('project_id')->value;
554+
$project = $this->entityTypeManager->getStorage('node')->load($project_id);
555+
$canApplyAfterwards = $project->get('field_can_apply_afterwards')->value;
556+
557+
// Applications made after shouldn't be deleted.
558+
if ($oldBackendId && $confirmDeletion == '1' && !$canApplyAfterwards) {
543559
try {
544560
$user = \Drupal::entityTypeManager()->getStorage('user')->load(\Drupal::currentUser()->id());
545561
\Drupal::service('asu_api.backendapi')->deleteApplication($user, $oldBackendId);

public/modules/custom/asu_content/src/Entity/Project.php

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use Drupal\node\Entity\Node;
66
use Drupal\user\UserInterface;
7+
use Drupal\user\Entity\User;
8+
use Drupal\asu_api\Api\BackendApi\Request\ApplicationLotteryResult;
79

810
/**
911
* Class for node's project bundle.
@@ -70,7 +72,7 @@ public function getCanApplyAfterwards(): string {
7072
if ($field_can_apply_afterwards->isEmpty()) {
7173
return "";
7274
}
73-
\Drupal::logger('asu_application')->info("field_can_apply_afterwards: " . $field_can_apply_afterwards->value);
75+
7476
return $field_can_apply_afterwards->value;
7577
}
7678

@@ -163,6 +165,56 @@ public function isArchievable(): bool {
163165
return TRUE;
164166
}
165167

168+
/**
169+
* Get reservations the given user has for the project's apartments.
170+
*
171+
* @param int $userId
172+
* Id of the user to check.
173+
*
174+
* @return array
175+
* reservations
176+
*/
177+
public function getUserReservations($userId): array {
178+
$user = User::load(\Drupal::currentUser()->id());
179+
180+
// Fetch reservations for this project.
181+
$request = new ApplicationLotteryResult($this->uuid());
182+
$request->setSender($user);
183+
$backendApi = \Drupal::service('asu_api.backendapi');
184+
185+
$userReservations = $backendApi
186+
->send($request)
187+
->getContent();
188+
189+
return $userReservations;
190+
}
191+
192+
/**
193+
* Checks if the given user has a reservation with the state 'offered'.
194+
*
195+
* 'offer_accepted' or 'sold' on the project.
196+
*
197+
* @param int $userId
198+
* Id of the user to check.
199+
*
200+
* @return bool
201+
* If user has a reservation with those states.
202+
*/
203+
public function getUserHasReservedOrSoldApartments($userId): bool {
204+
$userHasReservedOrSoldApartment = FALSE;
205+
$userReservations = $this->getUserReservations($userId);
206+
$states = ['offered', 'offer_accepted', 'sold'];
207+
208+
// phpcs:ignore
209+
foreach ($userReservations as $key => $reservation) {
210+
211+
if (in_array($reservation['state'], $states)) {
212+
$userHasReservedOrSoldApartment = TRUE;
213+
}
214+
}
215+
return $userHasReservedOrSoldApartment;
216+
}
217+
166218
/**
167219
* Get project sales person information.
168220
*

public/modules/custom/asu_content/translations/fi.po

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43323,3 +43323,6 @@ msgid "Tunnistamo"
4332343323
msgstr ""
4332443324
msgid "Helsinki Logo"
4332543325
msgstr "Helsingin kaupungin logo"
43326+
43327+
msgid "You already have an offer or have been sold an apartment in this project and cannot submit a new application."
43328+
msgstr "Sinulla on jo myyty tai tarjottu asunto tässä kohteessa etkä siksi voi tehdä uutta hakemusta."

public/themes/custom/asuntotuotanto/asuntotuotanto.theme

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,12 @@ function asuntotuotanto_preprocess_views_view(&$variables) {
229229
}
230230

231231
/**
232+
* Passes variables to project-apartments-listing.html.twig.
233+
*
232234
* Implements hook_preprocess_HOOK().
233235
*/
234236
function asuntotuotanto_preprocess_views_view_table(&$variables) {
237+
235238
if ($variables['view']->id() === 'project_apartments_listing') {
236239

237240
$results = $variables['result'];
@@ -251,6 +254,14 @@ function asuntotuotanto_preprocess_views_view_table(&$variables) {
251254
$variables['header']['field_debt_free_sales_price']['content'] = $price_title;
252255
unset($variables['header']['field_release_payment']);
253256

257+
$userHasReservedOrSoldApartment = FALSE;
258+
if (\Drupal::currentUser()->isAuthenticated()) {
259+
// Check if user has offered, offer_accepted or sold reservations.
260+
$userHasReservedOrSoldApartment = $project->getUserHasReservedOrSoldApartments(
261+
\Drupal::currentUser()->id()
262+
);
263+
}
264+
254265
foreach ($results as $key => $row) {
255266
/** @var \Drupal\asu_content\Entity\Apartment $entity */
256267
$apartment = $row->_entity;
@@ -282,6 +293,7 @@ function asuntotuotanto_preprocess_views_view_table(&$variables) {
282293
$variables['rows'][$key]['application_url_title'] = $apartment->getApplicationUrlTitle();
283294
$variables['rows'][$key]['is_free'] = $apartment->isFree();
284295
$variables['rows'][$key]['can_apply_afterwards'] = $project->getCanApplyAfterwards();
296+
$variables['rows'][$key]['user_has_reserved_or_sold_apartment'] = $userHasReservedOrSoldApartment;
285297
}
286298

287299
usort($variables['rows'], function ($a, $b) {
@@ -589,6 +601,14 @@ function asuntotuotanto_preprocess_node(&$variables) {
589601
);
590602
}
591603

604+
$userHasReservedOrSoldApartment = FALSE;
605+
if (\Drupal::currentUser()->isAuthenticated()) {
606+
// Check if user has offered, offer_accepted or sold reservations.
607+
$userHasReservedOrSoldApartment = $project->getUserHasReservedOrSoldApartments(
608+
\Drupal::currentUser()->id()
609+
);
610+
}
611+
592612
if (!empty($node->field_street_address->value)) {
593613
$street_address_splitted = split_address_line($node->field_street_address->value);
594614

@@ -618,6 +638,9 @@ function asuntotuotanto_preprocess_node(&$variables) {
618638
$variables['estimated_completion_date'] = $estimated_completion_date;
619639
$variables['is_application_period_active'] = $is_application_period_active;
620640
$variables['project_type'] = $project_type;
641+
// No idea why, but shows up as null if it has.
642+
// The more sensible name of 'user_has_reserved_or_sold_apartment'.
643+
$variables['current_user_has_reserved_or_sold_apartment'] = $userHasReservedOrSoldApartment;
621644
$variables['apartment_prices'] = $prices_string;
622645
break;
623646

@@ -768,6 +791,14 @@ function asuntotuotanto_preprocess_node(&$variables) {
768791
}
769792
}
770793

794+
$userHasReservedOrSoldApartment = FALSE;
795+
if (\Drupal::currentUser()->isAuthenticated()) {
796+
// Check if user has offered, offer_accepted or sold reservations.
797+
$userHasReservedOrSoldApartment = $project->getUserHasReservedOrSoldApartments(
798+
\Drupal::currentUser()->id()
799+
);
800+
}
801+
771802
$street_address = $project->get('field_street_address')->value ?? NULL;
772803
$postal_code = $project->get('field_postal_code')->value ?? NULL;
773804
$city = $project->get('field_city')->value ?? NULL;
@@ -819,6 +850,7 @@ function asuntotuotanto_preprocess_node(&$variables) {
819850
$variables['field_parking_fee'] = $apartment->get('field_parking_fee')->getValue()[0]['value'] ?? NULL;
820851
$variables['field_parking_fee_explanation'] = $apartment->get('field_parking_fee_explanation')->getValue()[0]['value'] ?? NULL;
821852
$variables['field_other_fees'] = $apartment->get('field_other_fees')->getValue()[0]['value'] ?? NULL;
853+
$variables['current_user_has_reserved_or_sold_apartment'] = $userHasReservedOrSoldApartment;
822854
}
823855
break;
824856
}
@@ -1017,7 +1049,9 @@ function get_project_apartment_teaser_values($project_id) {
10171049
$values['project_ownership_type'] = $project->field_ownership_type->entity ? $project->field_ownership_type->entity->name->value : '';
10181050
$values['project_district'] = $project->field_district->entity ? $project->field_district->entity->name->value : '';
10191051
$values['project_main_image_url'] = isset($project->field_main_image[0]) ? \Drupal::service('file_url_generator')->generateAbsoluteString($project->field_main_image[0]->entity->getFileUri()) : '';
1020-
$values['can_apply_afterwards'] = $project->getCanApplyAfterwards();
1052+
if ($project) {
1053+
$values['can_apply_afterwards'] = $project->get('field_can_apply_afterwards')->value;
1054+
}
10211055

10221056
foreach ($fields as $key => $field) {
10231057
if ($project && $project->hasField($field)) {

public/themes/custom/asuntotuotanto/src/scss/06_components/content/apartment/_apartment-cta.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,8 @@ line-height: $lineheight-m;
111111
background-color: transparent;
112112
}
113113
}
114+
115+
.application-as-draft-button {
116+
opacity: 0.75;
117+
}
114118
}

public/themes/custom/asuntotuotanto/templates/content/node--apartment--full.html.twig

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -189,24 +189,27 @@
189189
</h2>
190190
{% endif %}
191191
</div>
192+
192193
<div class="apartment__header-section apartment__header-section--actions {{ is_application_period_active or can_apply_afterwards ? 'is-application-period-active' }}">
194+
{% if current_user_has_reserved_or_sold_apartment == FALSE %}
193195
{% if is_application_period_active %}
194196
{% include '@asuntotuotanto/button/button.html.twig' with {
195197
type: 'primary',
196198
disabled: false,
197199
label: 'Create an application'|t,
198200
href: application_url
199201
}
200-
%}
201-
{% elseif can_apply_afterwards == 1 %}
202-
{% include '@asuntotuotanto/button/button.html.twig' with {
203-
type: 'primary',
204-
disabled: false,
205-
label: 'Create an after-application'|t,
206-
href: application_url
207-
}
208-
%}
209-
{% endif %}
202+
%}
203+
{% elseif can_apply_afterwards == 1 %}
204+
{% include '@asuntotuotanto/button/button.html.twig' with {
205+
type: 'primary',
206+
disabled: false,
207+
label: 'Create an after-application'|t,
208+
href: application_url
209+
}
210+
%}
211+
{% endif %}
212+
{% endif %}
210213
<p class="apartment__application-information">
211214
{% if (is_application_period_in_the_past is same as(false)) and (application_start_time != null and application_end_time != null) %}
212215
{% trans %}

public/themes/custom/asuntotuotanto/templates/content/node--project--full.html.twig

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,19 @@
7272
#}
7373

7474
{{ attach_library('asuntotuotanto/sticky-navigation') }}
75+
{% if current_user_has_reserved_or_sold_apartment %}
76+
<section data-drupal-messages="" aria-label="Notification" type="button" class="hds-notification hds-notification--alert">
77+
<div class="hds-notification__content">
78+
<div class="hds-notification__label" role="heading" aria-level="2">
79+
<span class="hds-icon hds-icon--alert-circle-fill" aria-hidden="true"></span>
80+
<span>{% trans %}Varoitusviesti{% endtrans %}</span>
81+
</div>
82+
<div class="hds-notification__body">
83+
{% trans %}Sinulla on jo myyty tai tarjottu asunto tässä kohteessa etkä siksi voi tehdä uutta hakemusta.{% endtrans %}
84+
</div>
85+
</div>
86+
</section>
87+
{% endif %}
7588

7689
{%
7790
set classes = [
@@ -83,7 +96,6 @@
8396
'project'
8497
]
8598
%}
86-
8799
{% set images = content.field_images %}
88100
{% set district = content.field_district.0 %}
89101
{% set street_address = content.field_street_address.0['#context']['value'] %}
@@ -254,6 +266,7 @@
254266
Project description
255267
{% endtrans %}
256268
</h2>
269+
257270
<div class="project__project-description">{{ project_description|raw }}</div>
258271
{% if sales_email %}
259272
<div class="project-contact">

0 commit comments

Comments
 (0)