Skip to content

Commit cc55be8

Browse files
authored
feat: partners new marketing flyer section (#5621)
* feat: add marketing flyer section * fix: hide when no jurisdiction * fix: remove should dirty * test: fix listing service test * fix: update table for marketing flyer * test: update marketing flyer tests * test: use possible data for listing service test
1 parent 70f5d12 commit cc55be8

19 files changed

Lines changed: 1322 additions & 26 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- AlterTable
2+
ALTER TABLE "listings" ADD COLUMN "accessible_marketing_flyer" TEXT,
3+
ADD COLUMN "accessible_marketing_flyer_file_id" UUID,
4+
ADD COLUMN "marketing_flyer" TEXT,
5+
ADD COLUMN "marketing_flyer_file_id" UUID;
6+
7+
-- AddForeignKey
8+
ALTER TABLE "listings" ADD CONSTRAINT "listings_marketing_flyer_file_id_fkey" FOREIGN KEY ("marketing_flyer_file_id") REFERENCES "assets"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
9+
10+
-- AddForeignKey
11+
ALTER TABLE "listings" ADD CONSTRAINT "listings_accessible_marketing_flyer_file_id_fkey" FOREIGN KEY ("accessible_marketing_flyer_file_id") REFERENCES "assets"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;

api/prisma/schema.prisma

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,8 @@ model Assets {
278278
listingEvents ListingEvents[]
279279
listingImages ListingImages[]
280280
buildingSelectionCriteriaFile Listings[] @relation("building_selection_criteria_file")
281+
marketingFlyerFile Listings[] @relation("marketing_flyer_file")
282+
accessibleMarketingFlyerFile Listings[] @relation("accessible_marketing_flyer_file")
281283
listingsResult Listings[] @relation("listings_result")
282284
paperApplications PaperApplications[]
283285
@@ -604,6 +606,8 @@ model Listings {
604606
applicationDropOffAddressType ApplicationAddressTypeEnum? @map("application_drop_off_address_type")
605607
applicationMailingAddressType ApplicationAddressTypeEnum? @map("application_mailing_address_type")
606608
buildingSelectionCriteria String? @map("building_selection_criteria")
609+
marketingFlyer String? @map("marketing_flyer")
610+
accessibleMarketingFlyer String? @map("accessible_marketing_flyer")
607611
cocInfo String? @map("coc_info")
608612
costsNotIncluded String? @map("costs_not_included")
609613
creditHistory String? @map("credit_history")
@@ -654,6 +658,8 @@ model Listings {
654658
applicationDropOffAddressId String? @map("application_drop_off_address_id") @db.Uuid
655659
applicationMailingAddressId String? @map("application_mailing_address_id") @db.Uuid
656660
buildingSelectionCriteriaFileId String? @map("building_selection_criteria_file_id") @db.Uuid
661+
marketingFlyerFileId String? @map("marketing_flyer_file_id") @db.Uuid
662+
accessibleMarketingFlyerFileId String? @map("accessible_marketing_flyer_file_id") @db.Uuid
657663
copyOfId String? @map("copy_of_id") @db.Uuid
658664
jurisdictionId String? @map("jurisdiction_id") @db.Uuid
659665
leasingAgentAddressId String? @map("leasing_agent_address_id") @db.Uuid
@@ -699,6 +705,8 @@ model Listings {
699705
listingsApplicationDropOffAddress Address? @relation("application_drop_off_address", fields: [applicationDropOffAddressId], references: [id], onDelete: NoAction, onUpdate: NoAction)
700706
reservedCommunityTypes ReservedCommunityTypes? @relation(fields: [reservedCommunityTypeId], references: [id], onDelete: NoAction, onUpdate: NoAction)
701707
listingsBuildingSelectionCriteriaFile Assets? @relation("building_selection_criteria_file", fields: [buildingSelectionCriteriaFileId], references: [id], onDelete: NoAction, onUpdate: NoAction)
708+
listingsMarketingFlyerFile Assets? @relation("marketing_flyer_file", fields: [marketingFlyerFileId], references: [id], onDelete: NoAction, onUpdate: NoAction)
709+
listingsAccessibleMarketingFlyerFile Assets? @relation("accessible_marketing_flyer_file", fields: [accessibleMarketingFlyerFileId], references: [id], onDelete: NoAction, onUpdate: NoAction)
702710
listingsResult Assets? @relation("listings_result", fields: [resultId], references: [id], onDelete: NoAction, onUpdate: NoAction)
703711
listingUtilities ListingUtilities? @relation(fields: [utilitiesId], references: [id], onDelete: NoAction, onUpdate: NoAction)
704712
listingsApplicationMailingAddress Address? @relation("application_mailing_address", fields: [applicationMailingAddressId], references: [id], onDelete: NoAction, onUpdate: NoAction)

api/prisma/seed-staging.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ export const stagingSeed = async (
163163
FeatureFlagEnum.enableHousingDeveloperOwner,
164164
FeatureFlagEnum.enableListingFileNumber,
165165
FeatureFlagEnum.enableListingFiltering,
166+
FeatureFlagEnum.enableMarketingFlyer,
166167
FeatureFlagEnum.enableMarketingStatus,
167168
FeatureFlagEnum.enableMarketingStatusMonths,
168169
FeatureFlagEnum.enableNeighborhoodAmenities,

api/src/dtos/listings/listing-update.dto.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ export class ListingUpdate extends OmitType(Listing, [
4343
'listingsLeasingAgentAddress',
4444
'listingsBuildingAddress',
4545
'listingsBuildingSelectionCriteriaFile',
46+
'listingsMarketingFlyerFile',
47+
'listingsAccessibleMarketingFlyerFile',
4648
'listingEvents',
4749
'listingFeatures',
4850
'listingUtilities',
@@ -190,6 +192,24 @@ export class ListingUpdate extends OmitType(Listing, [
190192
@ApiPropertyOptional({ type: AssetCreate })
191193
listingsBuildingSelectionCriteriaFile?: AssetCreate;
192194

195+
@Expose()
196+
@ValidateListingPublish('listingsMarketingFlyerFile', {
197+
groups: [ValidationsGroupsEnum.default],
198+
})
199+
@ValidateNested({ groups: [ValidationsGroupsEnum.default] })
200+
@Type(() => AssetCreate)
201+
@ApiPropertyOptional({ type: AssetCreate })
202+
listingsMarketingFlyerFile?: AssetCreate;
203+
204+
@Expose()
205+
@ValidateListingPublish('listingsAccessibleMarketingFlyerFile', {
206+
groups: [ValidationsGroupsEnum.default],
207+
})
208+
@ValidateNested({ groups: [ValidationsGroupsEnum.default] })
209+
@Type(() => AssetCreate)
210+
@ApiPropertyOptional({ type: AssetCreate })
211+
listingsAccessibleMarketingFlyerFile?: AssetCreate;
212+
193213
@Expose()
194214
@ValidateNested({ groups: [ValidationsGroupsEnum.default] })
195215
@Type(() => AssetCreate)

api/src/dtos/listings/listing.dto.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,30 @@ class Listing extends AbstractDTO {
339339
)
340340
buildingSelectionCriteria?: string;
341341

342+
@Expose()
343+
@ValidateListingPublish('marketingFlyer', {
344+
groups: [ValidationsGroupsEnum.default],
345+
})
346+
@IsString({ groups: [ValidationsGroupsEnum.default] })
347+
@ApiPropertyOptional()
348+
@IsUrl(
349+
{ require_protocol: true },
350+
{ groups: [ValidationsGroupsEnum.default] },
351+
)
352+
marketingFlyer?: string;
353+
354+
@Expose()
355+
@ValidateListingPublish('accessibleMarketingFlyer', {
356+
groups: [ValidationsGroupsEnum.default],
357+
})
358+
@IsString({ groups: [ValidationsGroupsEnum.default] })
359+
@ApiPropertyOptional()
360+
@IsUrl(
361+
{ require_protocol: true },
362+
{ groups: [ValidationsGroupsEnum.default] },
363+
)
364+
accessibleMarketingFlyer?: string;
365+
342366
@Expose()
343367
@ValidateListingPublish('cocInfo7', {
344368
groups: [ValidationsGroupsEnum.default],
@@ -852,6 +876,24 @@ class Listing extends AbstractDTO {
852876
@ApiPropertyOptional({ type: Asset })
853877
listingsBuildingSelectionCriteriaFile?: Asset;
854878

879+
@Expose()
880+
@ValidateListingPublish('listingsMarketingFlyerFile', {
881+
groups: [ValidationsGroupsEnum.default],
882+
})
883+
@ValidateNested({ groups: [ValidationsGroupsEnum.default] })
884+
@Type(() => Asset)
885+
@ApiPropertyOptional({ type: Asset })
886+
listingsMarketingFlyerFile?: Asset;
887+
888+
@Expose()
889+
@ValidateListingPublish('listingsAccessibleMarketingFlyerFile', {
890+
groups: [ValidationsGroupsEnum.default],
891+
})
892+
@ValidateNested({ groups: [ValidationsGroupsEnum.default] })
893+
@Type(() => Asset)
894+
@ApiPropertyOptional({ type: Asset })
895+
listingsAccessibleMarketingFlyerFile?: Asset;
896+
855897
@Expose()
856898
@IsDefined({ groups: [ValidationsGroupsEnum.default] })
857899
@ValidateNested({ groups: [ValidationsGroupsEnum.default] })

api/src/enums/feature-flags/feature-flags-enum.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export enum FeatureFlagEnum {
2424
enableListingOpportunity = 'enableListingOpportunity',
2525
enableListingPagination = 'enableListingPagination',
2626
enableListingUpdatedAt = 'enableListingUpdatedAt',
27+
enableMarketingFlyer = 'enableMarketingFlyer',
2728
enableMarketingStatus = 'enableMarketingStatus',
2829
enableMarketingStatusMonths = 'enableMarketingStatusMonths',
2930
enableNeighborhoodAmenities = 'enableNeighborhoodAmenities',
@@ -160,6 +161,11 @@ export const featureFlagMap: {
160161
name: FeatureFlagEnum.enableListingUpdatedAt,
161162
description: 'When true, listings detail will display an updated at date',
162163
},
164+
{
165+
name: FeatureFlagEnum.enableMarketingFlyer,
166+
description:
167+
"When true, the 'marketing flyer' sub-section is displayed in listing creation/edit and the public listing view",
168+
},
163169
{
164170
name: FeatureFlagEnum.enableMarketingStatus,
165171
description:

api/src/services/listing-csv-export.service.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,22 @@ export class ListingCsvExporterService implements CsvExporterServiceInterface {
393393
return '';
394394
}
395395

396+
marketingFlyer(value: string, listing?: Listing): string {
397+
if (value) return listing.marketingFlyer;
398+
if (listing?.listingsMarketingFlyerFile?.fileId)
399+
return formatCloudinaryPdfUrl(listing.listingsMarketingFlyerFile?.fileId);
400+
return '';
401+
}
402+
403+
accessibleMarketingFlyer(value: string, listing?: Listing): string {
404+
if (value) return listing.accessibleMarketingFlyer;
405+
if (listing?.listingsAccessibleMarketingFlyerFile?.fileId)
406+
return formatCloudinaryPdfUrl(
407+
listing.listingsAccessibleMarketingFlyerFile?.fileId,
408+
);
409+
return '';
410+
}
411+
396412
cloudinaryPdfFromId(publicId: string): string {
397413
if (publicId) {
398414
return formatCloudinaryPdfUrl(publicId);
@@ -1244,6 +1260,23 @@ export class ListingCsvExporterService implements CsvExporterServiceInterface {
12441260
.join(', ');
12451261
},
12461262
},
1263+
...(doAnyJurisdictionHaveFeatureFlagSet(
1264+
user.jurisdictions,
1265+
FeatureFlagEnum.enableMarketingFlyer,
1266+
)
1267+
? [
1268+
{
1269+
path: 'marketingFlyer',
1270+
label: 'Marketing Flyer',
1271+
format: this.marketingFlyer,
1272+
},
1273+
{
1274+
path: 'accessibleMarketingFlyer',
1275+
label: 'Accessible Marketing Flyer',
1276+
format: this.accessibleMarketingFlyer,
1277+
},
1278+
]
1279+
: []),
12471280
{
12481281
path: 'userAccounts',
12491282
label: 'Partners Who Have Access',

api/src/services/listing.service.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ includeViews.full = {
160160
},
161161
},
162162
listingsBuildingSelectionCriteriaFile: true,
163+
listingsMarketingFlyerFile: true,
164+
listingsAccessibleMarketingFlyerFile: true,
163165
listingEvents: {
164166
include: {
165167
assets: true,
@@ -1409,6 +1411,21 @@ export class ListingService implements OnModuleInit {
14091411
},
14101412
}
14111413
: undefined,
1414+
listingsMarketingFlyerFile: dto.listingsMarketingFlyerFile
1415+
? {
1416+
create: {
1417+
...dto.listingsMarketingFlyerFile,
1418+
},
1419+
}
1420+
: undefined,
1421+
listingsAccessibleMarketingFlyerFile:
1422+
dto.listingsAccessibleMarketingFlyerFile
1423+
? {
1424+
create: {
1425+
...dto.listingsAccessibleMarketingFlyerFile,
1426+
},
1427+
}
1428+
: undefined,
14121429
listingUtilities: dto.listingUtilities
14131430
? {
14141431
create: {
@@ -2231,7 +2248,7 @@ export class ListingService implements OnModuleInit {
22312248
},
22322249
}
22332250
: undefined,
2234-
// Three options for the building selection criteria file
2251+
// Three options for the building selection criteria and marketing Flyers files
22352252
// create new one, connect existing one, or deleted (disconnect)
22362253
listingsBuildingSelectionCriteriaFile:
22372254
incomingDto.listingsBuildingSelectionCriteriaFile
@@ -2255,6 +2272,47 @@ export class ListingService implements OnModuleInit {
22552272
: {
22562273
disconnect: true,
22572274
},
2275+
listingsMarketingFlyerFile: incomingDto.listingsMarketingFlyerFile
2276+
? incomingDto.listingsMarketingFlyerFile.id
2277+
? {
2278+
connectOrCreate: {
2279+
where: {
2280+
id: incomingDto.listingsMarketingFlyerFile.id,
2281+
},
2282+
create: {
2283+
...incomingDto.listingsMarketingFlyerFile,
2284+
},
2285+
},
2286+
}
2287+
: {
2288+
create: {
2289+
...incomingDto.listingsMarketingFlyerFile,
2290+
},
2291+
}
2292+
: {
2293+
disconnect: true,
2294+
},
2295+
listingsAccessibleMarketingFlyerFile:
2296+
incomingDto.listingsAccessibleMarketingFlyerFile
2297+
? incomingDto.listingsAccessibleMarketingFlyerFile.id
2298+
? {
2299+
connectOrCreate: {
2300+
where: {
2301+
id: incomingDto.listingsAccessibleMarketingFlyerFile.id,
2302+
},
2303+
create: {
2304+
...incomingDto.listingsAccessibleMarketingFlyerFile,
2305+
},
2306+
},
2307+
}
2308+
: {
2309+
create: {
2310+
...incomingDto.listingsAccessibleMarketingFlyerFile,
2311+
},
2312+
}
2313+
: {
2314+
disconnect: true,
2315+
},
22582316
listingUtilities: incomingDto.listingUtilities
22592317
? {
22602318
upsert: {

0 commit comments

Comments
 (0)