diff --git a/api/prisma/seed-helpers/translation-factory.ts b/api/prisma/seed-helpers/translation-factory.ts index 4d330f896b..0ff65731f5 100644 --- a/api/prisma/seed-helpers/translation-factory.ts +++ b/api/prisma/seed-helpers/translation-factory.ts @@ -160,6 +160,7 @@ const translations = (jurisdictionName?: string, language?: LanguagesEnum) => { rentalOpportunity: { subject: 'New rental opportunity', intro: 'Rental opportunity at', + viewListingNotice: 'Please view listing for the most updated information', applicationsDue: 'Applications Due', community: 'Community', address: 'Address', diff --git a/api/src/controllers/script-runner.controller.ts b/api/src/controllers/script-runner.controller.ts index 6f5a6655c1..0d3b6d8a9f 100644 --- a/api/src/controllers/script-runner.controller.ts +++ b/api/src/controllers/script-runner.controller.ts @@ -249,6 +249,20 @@ export class ScirptRunnerController { return await this.scriptRunnerService.removeWorkAddresses(req); } + @Put('addNoticeToListingOpportunityEmail') + @ApiOperation({ + summary: + 'A script that adds notice translations for the listing opportunity email', + operationId: 'addNoticeToListingOpportunityEmail', + }) + @ApiOkResponse({ type: SuccessDTO }) + async addNoticeToListingOpportunityEmail( + @Request() req: ExpressRequest, + ): Promise { + return await this.scriptRunnerService.addNoticeToListingOpportunityEmail( + req, + ); + } @Put('addFeatureFlags') @ApiOperation({ diff --git a/api/src/services/email.service.ts b/api/src/services/email.service.ts index efddcbd0b3..3a11c66d66 100644 --- a/api/src/services/email.service.ts +++ b/api/src/services/email.service.ts @@ -528,7 +528,8 @@ export class EmailService { label: this.polyglot.t('rentalOpportunity.applicationsDue'), value: dayjs(listing.applicationDueDate) .tz(process.env.TIME_ZONE) - .format('MMMM D, YYYY'), + .format('MMMM D, YYYY [at] h:mma z'), + bolded: true, }); } tableRows.push({ @@ -577,7 +578,7 @@ export class EmailService { label: this.polyglot.t('rentalOpportunity.lottery'), value: dayjs(lotteryEvent.startDate) .tz(process.env.TIME_ZONE) - .format('MMMM D, YYYY'), + .format('MMMM D, YYYY [at] h:mma z'), }); } } diff --git a/api/src/services/script-runner.service.ts b/api/src/services/script-runner.service.ts index 69d5cc1a50..d47cdded34 100644 --- a/api/src/services/script-runner.service.ts +++ b/api/src/services/script-runner.service.ts @@ -1382,6 +1382,57 @@ export class ScriptRunnerService { return { success: true }; } + /** + * + * @param req incoming request object + * @returns successDTO + * @description updates single use code translations to show extended expiration time + */ + async addNoticeToListingOpportunityEmail( + req: ExpressRequest, + ): Promise { + const requestingUser = mapTo(User, req['user']); + await this.markScriptAsRunStart( + 'add notice translation for listing opportunity email', + requestingUser, + ); + + await this.updateTranslationsForLanguage(LanguagesEnum.en, { + rentalOpportunity: { + viewListingNotice: + 'Please view listing for the most updated information', + }, + }); + await this.updateTranslationsForLanguage(LanguagesEnum.es, { + rentalOpportunity: { + viewListingNotice: + 'Consulte el listado para obtener la información más actualizada', + }, + }); + await this.updateTranslationsForLanguage(LanguagesEnum.tl, { + rentalOpportunity: { + viewListingNotice: + 'Mangyaring tingnan ang listahan para sa pinakabagong impormasyon', + }, + }); + await this.updateTranslationsForLanguage(LanguagesEnum.vi, { + rentalOpportunity: { + viewListingNotice: 'Vui lòng xem danh sách để biết thông tin mới nhất', + }, + }); + await this.updateTranslationsForLanguage(LanguagesEnum.zh, { + rentalOpportunity: { + viewListingNotice: '请查看列表以获取最新信息', + }, + }); + + await this.markScriptAsComplete( + 'add notice translation for listing opportunity email', + requestingUser, + ); + return { success: true }; + } + /** Adds all existing feature flags across Bloom to the database */ diff --git a/api/src/views/listing-opportunity.hbs b/api/src/views/listing-opportunity.hbs index 923ead50cf..5f34b99b65 100644 --- a/api/src/views/listing-opportunity.hbs +++ b/api/src/views/listing-opportunity.hbs @@ -1,6 +1,7 @@ {{#> layout_default }} -

{{t "rentalOpportunity.intro"}}
{{{listingName}}}

+

{{t "rentalOpportunity.intro"}}
{{{listingName}}}

+

{{t "rentalOpportunity.viewListingNotice"}}

@@ -12,7 +13,7 @@ - diff --git a/api/test/unit/services/email.service.spec.ts b/api/test/unit/services/email.service.spec.ts index 07ea3cef24..c467137120 100644 --- a/api/test/unit/services/email.service.spec.ts +++ b/api/test/unit/services/email.service.spec.ts @@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ConfigModule } from '@nestjs/config'; import { LanguagesEnum, + ListingEventsTypeEnum, ListingsStatusEnum, ReviewOrderTypeEnum, } from '@prisma/client'; @@ -16,8 +17,10 @@ import { User } from '../../../src/dtos/users/user.dto'; import { ApplicationCreate } from '../../../src/dtos/applications/application-create.dto'; import { of } from 'rxjs'; import { HttpService } from '@nestjs/axios'; +import dayjs from 'dayjs'; let sendMock; +let govSendMock; const translationServiceMock = { getMergedTranslations: () => { return translationFactory().translations; @@ -64,8 +67,10 @@ describe('Testing email service', () => { beforeEach(async () => { jest.useFakeTimers(); sendMock = jest.fn(); + govSendMock = jest.fn(); service = await module.resolve(EmailService); service.sendSES = sendMock; + service.govSend = govSendMock; }); const user = { @@ -443,5 +448,60 @@ describe('Testing email service', () => { expect(emailMock.html).toMatch('Thank you,'); expect(emailMock.html).toMatch('Bloom Housing Portal'); }); + + it('should notify people of listing opportunity', async () => { + const listing = { + id: 'listingId', + name: 'test listing', + reviewOrderType: ReviewOrderTypeEnum.firstComeFirstServe, + applicationDueDate: new Date(), + status: ListingsStatusEnum.active, + jurisdictions: { name: 'test jurisdiction', id: 'jurisdictionId' }, + displayWaitlistSize: false, + showWaitlist: false, + applicationMethods: [], + assets: [], + listingEvents: [ + { + type: ListingEventsTypeEnum.publicLottery, + startDate: new Date(), + }, + ], + units: [], + referralApplication: undefined, + createdAt: new Date(), + updatedAt: new Date(), + listingsBuildingAddress: { + ...yellowstoneAddress, + id: 'addressId', + createdAt: new Date(), + updatedAt: new Date(), + }, + }; + const service = await module.resolve(EmailService); + await service.listingOpportunity(listing); + + const emailMock = govSendMock.mock.calls[0][0]; + expect(emailMock).toMatch( + 'Rental opportunity at
test listing', + ); + expect(emailMock).toMatch( + 'Please view listing for the most updated information', + ); + expect(emailMock).toMatch( + /`, + ), + ); + expect(emailMock).toMatch(/