Skip to content

Commit 27d8f00

Browse files
feat: non-regulated fields implementation (#5455)
* fix: fix schema formatting * chore: add new enum types * chore: add new Listings model fields * chore: add new fields to the UnitGroup model * chore: update DTO's to changes in schema * chore: generate new swagger types * chore: add migration for new schema updates * fix: update depositValue field decimal range * fix: add missing field names * chore: add custom deposit validation decorator * chore: add unit group rent type validation * fix: remove unnecessary import * chore: update sunit summary DTO to contain new fields * chore: update service layer * fix: remove unnecessary migration update * fix: update db fields * chore: update rent validation decorator * chore: add the cocInfo to the listing DTO * chore: regenerate backend swagger * chore: add listing type selector * chore: add unit form non regulated rent fields * fix: fix invalid logic * chore: update prop to and optional value * chore: add rent type fields for unit group form * chore: hide additional fields for non regulated listings * chore: add fields for the deposit types * chore: update labels * chore: update community types fields * chore: add listing ebll clearance field * chore: update built year filed rendering logic * chore: fix field decorator error * fix: fix decorators import paths * chore: update required documents form fields * chore: update the schema to include the new required documents list table * fix: remove unused import * chore: update listings factory to include the new required documents field * chore: hide non regulated switch field based on feature flag * chore: retain the original required documents field * Revert "chore: hide non regulated switch field based on feature flag" This reverts commit 11f0874. * fix: add missing fields validation * fix: update imports * fix: remove the package manager filed in package JSON * fix: update the hasHudEbllClearance field * fix: remove unused import * chore: update the label to match the current listing type * fix: fix the application fee full width field * fix: hide the new deposit and rent fields for regulated units * fix: boolean ebll formatting for form submission * chore: add non regulated listing fields to intro and fees sections * chore: add missing documents model column mapping * fix: udpdate form subssion for the new required documents field * chore: add required documents preview in the details * fix: add feature flag based regulated fields control * chore: update the validation decorator * chore: update listing details page to new jest standard * fix: fix failing backend API tests * fix: fix backend integration tests * fix: fix decorator logic * fix: add unit group rent range revalidation triggering * fix: fix unwanted initial ebll field reset * fix: required documents preview filtering * fix: update the developer field title switching on listing preview page * fix: hide new fields on listing preview based on the non-regulated feature flag * fix: fix label typo * fix: update new required documents field label formatting * fix: hide the new documents field group on regulated listings * fix: remove unused import * fix: add missing monthly rent field to database * fix: fix unit groups integration tests * fix: paper listing form failing tests * chore: rename migration files * fix: clean-up merge conflict resolve * fix: remove unnecessary listing service code * fix: hide the new required documents field from preview page based on feature flag * fix: remove unused prop * fix: update the listings factory to add new required documents only for non regulated mocks * chore: add logic to disconnect and delete documents table entry automatically * fix: fix Units component feature flag detection code * chore: add integration tests for non regulated listing preview * chore: add integration tests for the ListingIntro section * chore: add integration tests for AdditionalDetails form section component * chore: add deposit type default field for the listing form * chore: add integration tests for the AdditionalFees listing form component * chore: add integration tests for the unit group form for non-regulated listing * fix: update the listing service to remove the documents entry after completed transaction * fix: revert the CommunityType component logic * chore: partially revert additional fees section logic * fix: update the listingType field clearing to undefined * fix: remove unused imports * fix: revert changes for unit form * test: re-run testing suite * chore: remove the depositRangeMin and depositRangeMax fields * chore: update the data formatter to handle different deposit type values * fix: update api integration tests to new changes * fix: fix csv export integration tests * test: re-run testing suite * fix: update partners site integration tests * test: re-run testing suite * fix: remove unnecessary console log * fix: clear deposit on save and continue (#5598) --------- Co-authored-by: Morgan Ludtke <42942267+ludtkemorgan@users.noreply.github.com>
1 parent 263fff9 commit 27d8f00

48 files changed

Lines changed: 2594 additions & 906 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api/prisma/migrations/32_add_non_regulated_listings_fields/migration.sql

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ CREATE TYPE "rent_type_enum" AS ENUM ('fixedRent', 'rentRange');
1010
-- AlterTable
1111
ALTER TABLE "listings"
1212
ADD COLUMN "coc_info" TEXT,
13-
ADD COLUMN "deposit_range_max" INTEGER,
14-
ADD COLUMN "deposit_range_min" INTEGER,
1513
ADD COLUMN "deposit_type" "deposit_type_enum",
1614
ADD COLUMN "deposit_value" DECIMAL(65,30),
1715
ADD COLUMN "has_hud_ebll_clearance" BOOLEAN,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
-- AlterTable
2+
ALTER TABLE "listings" ADD COLUMN "documents_id" UUID;
3+
4+
-- CreateTable
5+
CREATE TABLE "listing_documents" (
6+
"id" UUID NOT NULL DEFAULT uuid_generate_v4(),
7+
"created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
8+
"updated_at" TIMESTAMP(6) NOT NULL,
9+
"birth_certificate" BOOLEAN,
10+
"current_landlord_reference" BOOLEAN,
11+
"government_issued_id" BOOLEAN,
12+
"previous_landlord_reference" BOOLEAN,
13+
"proof_of_assets" BOOLEAN,
14+
"proof_of_custody" BOOLEAN,
15+
"proof_of_income" BOOLEAN,
16+
"residency_documents" BOOLEAN,
17+
"social_security_card" BOOLEAN,
18+
19+
CONSTRAINT "listing_documents_pkey" PRIMARY KEY ("id")
20+
);
21+
22+
-- CreateIndex
23+
CREATE UNIQUE INDEX "listings_documents_id_key" ON "listings"("documents_id");
24+
25+
-- AddForeignKey
26+
ALTER TABLE "listings" ADD CONSTRAINT "listings_documents_id_fkey" FOREIGN KEY ("documents_id") REFERENCES "listing_documents"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ALTER TABLE "unit_group"
2+
ADD COLUMN "monthly_rent" DECIMAL,
3+
ALTER COLUMN "flat_rent_value_from" SET DATA TYPE DECIMAL,
4+
ALTER COLUMN "flat_rent_value_to" SET DATA TYPE DECIMAL;

api/prisma/schema.prisma

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,24 @@ model ListingUtilities {
527527
@@map("listing_utilities")
528528
}
529529

530+
model ListingDocuments {
531+
id String @id() @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
532+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(6)
533+
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamp(6)
534+
socialSecurityCard Boolean? @map("social_security_card")
535+
currentLandlordReference Boolean? @map("current_landlord_reference")
536+
birthCertificate Boolean? @map("birth_certificate")
537+
previousLandlordReference Boolean? @map("previous_landlord_reference")
538+
governmentIssuedId Boolean? @map("government_issued_id")
539+
proofOfAssets Boolean? @map("proof_of_assets")
540+
proofOfIncome Boolean? @map("proof_of_income")
541+
residencyDocuments Boolean? @map("residency_documents")
542+
proofOfCustody Boolean? @map("proof_of_custody")
543+
listings Listings?
544+
545+
@@map("listing_documents")
546+
}
547+
530548
enum ListingTypeEnum {
531549
regulated
532550
nonRegulated
@@ -594,8 +612,6 @@ model Listings {
594612
depositMax String? @map("deposit_max")
595613
depositType DepositTypeEnum? @map("deposit_type")
596614
depositValue Decimal? @map("deposit_value") @db.Decimal(8, 2)
597-
depositRangeMin Int? @map("deposit_range_min")
598-
depositRangeMax Int? @map("deposit_range_max")
599615
depositHelperText String? @map("deposit_helper_text")
600616
disableUnitsAccordion Boolean? @map("disable_units_accordion")
601617
hasHudEbllClearance Boolean? @map("has_hud_ebll_clearance")
@@ -611,6 +627,7 @@ model Listings {
611627
rentalAssistance String? @map("rental_assistance")
612628
rentalHistory String? @map("rental_history")
613629
requiredDocuments String? @map("required_documents")
630+
requiredDocumentsList ListingDocuments? @relation(fields: [documentsId], references: [id], onDelete: NoAction, onUpdate: NoAction)
614631
specialNotes String? @map("special_notes")
615632
waitlistCurrentSize Int? @map("waitlist_current_size")
616633
waitlistMaxSize Int? @map("waitlist_max_size")
@@ -644,6 +661,7 @@ model Listings {
644661
resultId String? @map("result_id") @db.Uuid
645662
featuresId String? @unique() @map("features_id") @db.Uuid
646663
utilitiesId String? @unique() @map("utilities_id") @db.Uuid
664+
documentsId String? @unique() @map("documents_id") @db.Uuid
647665
includeCommunityDisclaimer Boolean? @map("include_community_disclaimer")
648666
communityDisclaimerTitle String? @map("community_disclaimer_title")
649667
communityDisclaimerDescription String? @map("community_disclaimer_description")
@@ -1016,8 +1034,9 @@ model UnitGroup {
10161034
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamp(6)
10171035
maxOccupancy Int? @map("max_occupancy")
10181036
minOccupancy Int? @map("min_occupancy")
1019-
flatRentValueFrom Decimal? @map("flat_rent_value_from")
1020-
flatRentValueTo Decimal? @map("flat_rent_value_to")
1037+
flatRentValueFrom Decimal? @map("flat_rent_value_from") @db.Decimal
1038+
flatRentValueTo Decimal? @map("flat_rent_value_to") @db.Decimal
1039+
monthlyRent Decimal? @map("monthly_rent") @db.Decimal
10211040
floorMin Int? @map("floor_min")
10221041
floorMax Int? @map("floor_max")
10231042
totalCount Int? @map("total_count")

api/prisma/seed-helpers/listing-factory.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
PrismaClient,
99
ReservedCommunityTypes,
1010
ReviewOrderTypeEnum,
11+
ListingTypeEnum,
1112
} from '@prisma/client';
1213
import { randomInt } from 'crypto';
1314
import dayjs from 'dayjs';
@@ -51,6 +52,18 @@ type optionalFeatures = {
5152
loweredCabinets?: boolean;
5253
};
5354

55+
type requiredDocuments = {
56+
socialSecurityCard?: boolean;
57+
currentLandlordReference?: boolean;
58+
birthCertificate?: boolean;
59+
previousLandlordReference?: boolean;
60+
governmentIssuedId?: boolean;
61+
proofOfAssets?: boolean;
62+
proofOfIncome?: boolean;
63+
residencyDocuments?: boolean;
64+
proofOfCustody?: boolean;
65+
};
66+
5467
type optionalUtilities = {
5568
water?: boolean;
5669
gas?: boolean;
@@ -89,6 +102,7 @@ export const listingFactory = async (
89102
unitGroups?: Prisma.UnitGroupCreateWithoutListingsInput[];
90103
units?: Prisma.UnitsCreateWithoutListingsInput[];
91104
userAccounts?: Prisma.UserAccountsWhereUniqueInput[];
105+
requiredDocumentsList?: requiredDocuments;
92106
},
93107
): Promise<Prisma.ListingsCreateInput> => {
94108
const previousListing = optionalParams?.listing || {};
@@ -260,6 +274,9 @@ export const listingFactory = async (
260274
optionalParams?.optionalFeatures,
261275
optionalParams?.optionalUtilities,
262276
),
277+
...(optionalParams?.listing?.listingType === ListingTypeEnum.nonRegulated
278+
? listingsRequiredDocuments(optionalParams?.requiredDocumentsList)
279+
: {}),
263280
...previousListing,
264281
};
265282
};
@@ -289,6 +306,27 @@ const buildingFeatures = (includeBuildingFeatures: boolean) => {
289306
};
290307
};
291308

309+
export const listingsRequiredDocuments = (
310+
requiredDocumentsList?: requiredDocuments,
311+
): {
312+
requiredDocumentsList;
313+
} => ({
314+
requiredDocumentsList: {
315+
create: {
316+
socialSecurityCard: randomBoolean(),
317+
currentLandlordReference: randomBoolean(),
318+
birthCertificate: randomBoolean(),
319+
previousLandlordReference: randomBoolean(),
320+
governmentIssuedId: randomBoolean(),
321+
proofOfAssets: randomBoolean(),
322+
proofOfIncome: randomBoolean(),
323+
residencyDocuments: randomBoolean(),
324+
proofOfCustody: randomBoolean(),
325+
...requiredDocumentsList,
326+
},
327+
},
328+
});
329+
292330
export const featuresAndUtilites = (
293331
optionalFeatures?: optionalFeatures,
294332
optionalUtilities?: optionalUtilities,

api/src/decorators/validate-listing-deposit.decorator.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ValidatorOptions,
88
} from 'class-validator';
99
import Listing from '../dtos/listings/listing.dto';
10-
import { DepositTypeEnum } from '@prisma/client';
10+
import { DepositTypeEnum, ListingTypeEnum } from '@prisma/client';
1111

1212
export function ValidateListingDeposit(validationOptions?: ValidatorOptions) {
1313
return function (object: object, propertyName: string) {
@@ -28,28 +28,32 @@ export class DepositValueConstraint implements ValidatorConstraintInterface {
2828
}
2929

3030
validate(value: DepositTypeEnum, args: ValidationArguments) {
31-
const { depositValue, depositRangeMin, depositRangeMax } =
31+
const { depositValue, depositMin, depositMax, listingType } =
3232
args.object as Listing;
3333

34+
if (!listingType || listingType === ListingTypeEnum.regulated) {
35+
return true;
36+
}
37+
3438
if (value === DepositTypeEnum.fixedDeposit) {
35-
return (
36-
!this.isFieldEmpty(depositValue) &&
37-
this.isFieldEmpty(depositRangeMin) &&
38-
this.isFieldEmpty(depositRangeMax)
39-
);
39+
return this.isFieldEmpty(depositMin) && this.isFieldEmpty(depositMax);
40+
}
41+
if (value === DepositTypeEnum.depositRange) {
42+
return this.isFieldEmpty(depositValue);
4043
}
4144

45+
// If no Deposit type is selected then validate that it's either just depositValue or just the range values
4246
return (
43-
this.isFieldEmpty(depositValue) &&
44-
!this.isFieldEmpty(depositRangeMin) &&
45-
!this.isFieldEmpty(depositRangeMax)
47+
this.isFieldEmpty(depositValue) ||
48+
(this.isFieldEmpty(depositMin) && this.isFieldEmpty(depositMax))
4649
);
4750
}
4851
defaultMessage(args?: ValidationArguments): string {
4952
const value = args.value as DepositTypeEnum;
53+
5054
if (value === DepositTypeEnum.fixedDeposit) {
51-
return 'When deposit is of type "fixedDeposit" the "depositValue" must be filled and the "depositRangeMin"|"depositRangeMax" fields must be null';
55+
return 'When deposit is of type "fixedDeposit" the "depositValue" must be filled and the "depositMin"|"depositMax" fields must be null';
5256
}
53-
return 'When deposit is of type "depositRange" the "depositRangeMin" and "depositRangeMax" fields must be filled and "depositValue" must be null';
57+
return 'When deposit is of type "depositRange" the "depositMin" and "depositMax" fields must be filled and "depositValue" must be null';
5458
}
5559
}

api/src/decorators/validate-unit-groups-rent.decorator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ValidatorConstraintInterface,
88
ValidatorOptions,
99
} from 'class-validator';
10-
import UnitGroup from 'src/dtos/unit-groups/unit-group.dto';
10+
import UnitGroup from '../dtos/unit-groups/unit-group.dto';
1111

1212
export function ValidateUnitGroupRent(validationOptions?: ValidatorOptions) {
1313
return function (object: object, propertyName: string) {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { RentTypeEnum } from '@prisma/client';
2+
import {
3+
registerDecorator,
4+
ValidationArguments,
5+
ValidationTypes,
6+
ValidatorConstraint,
7+
ValidatorConstraintInterface,
8+
ValidatorOptions,
9+
} from 'class-validator';
10+
import UnitGroup from '../dtos/unit-groups/unit-group.dto';
11+
12+
export function ValidateUnitGroupRent(validationOptions?: ValidatorOptions) {
13+
return function (object: object, propertyName: string) {
14+
registerDecorator({
15+
name: ValidationTypes.CUSTOM_VALIDATION,
16+
target: object.constructor,
17+
propertyName: propertyName,
18+
options: validationOptions,
19+
validator: RentValueConstraint,
20+
});
21+
};
22+
}
23+
24+
@ValidatorConstraint()
25+
export class RentValueConstraint implements ValidatorConstraintInterface {
26+
isFieldEmpty(value): boolean {
27+
return value === null || value === undefined;
28+
}
29+
30+
validate(
31+
value: RentTypeEnum,
32+
validationArguments?: ValidationArguments,
33+
): Promise<boolean> | boolean {
34+
const { flatRentValueFrom, flatRentValueTo } =
35+
validationArguments.object as UnitGroup;
36+
37+
if (value === RentTypeEnum.rentRange) {
38+
return (
39+
!this.isFieldEmpty(flatRentValueFrom) &&
40+
!this.isFieldEmpty(flatRentValueTo)
41+
);
42+
} else {
43+
return (
44+
this.isFieldEmpty(flatRentValueFrom) &&
45+
this.isFieldEmpty(flatRentValueTo)
46+
);
47+
}
48+
49+
return true;
50+
}
51+
52+
defaultMessage(validationArguments?: ValidationArguments): string {
53+
const rentType = validationArguments.value as RentTypeEnum;
54+
if (rentType === RentTypeEnum.rentRange) {
55+
return 'When rent is of type "rentRange" the "flatRentValueFrom" and "flatRentValueTo" fields must be filled';
56+
}
57+
}
58+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { ApiPropertyOptional } from '@nestjs/swagger';
2+
import { Expose } from 'class-transformer';
3+
import { IsBoolean } from 'class-validator';
4+
import { ValidationsGroupsEnum } from '../../enums/shared/validation-groups-enum';
5+
6+
export class ListingDocuments {
7+
@Expose()
8+
@IsBoolean({ groups: [ValidationsGroupsEnum.default] })
9+
@ApiPropertyOptional()
10+
socialSecurityCard?: boolean;
11+
12+
@Expose()
13+
@IsBoolean({ groups: [ValidationsGroupsEnum.default] })
14+
@ApiPropertyOptional()
15+
currentLandlordReference?: boolean;
16+
17+
@Expose()
18+
@IsBoolean({ groups: [ValidationsGroupsEnum.default] })
19+
@ApiPropertyOptional()
20+
birthCertificate?: boolean;
21+
22+
@Expose()
23+
@IsBoolean({ groups: [ValidationsGroupsEnum.default] })
24+
@ApiPropertyOptional()
25+
previousLandlordReference?: boolean;
26+
27+
@Expose()
28+
@IsBoolean({ groups: [ValidationsGroupsEnum.default] })
29+
@ApiPropertyOptional()
30+
governmentIssuedId?: boolean;
31+
32+
@Expose()
33+
@IsBoolean({ groups: [ValidationsGroupsEnum.default] })
34+
@ApiPropertyOptional()
35+
proofOfAssets?: boolean;
36+
37+
@Expose()
38+
@IsBoolean({ groups: [ValidationsGroupsEnum.default] })
39+
@ApiPropertyOptional()
40+
proofOfIncome?: boolean;
41+
42+
@Expose()
43+
@IsBoolean({ groups: [ValidationsGroupsEnum.default] })
44+
@ApiPropertyOptional()
45+
residencyDocuments?: boolean;
46+
47+
@Expose()
48+
@IsBoolean({ groups: [ValidationsGroupsEnum.default] })
49+
@ApiPropertyOptional()
50+
proofOfCustody?: boolean;
51+
}

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

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
ValidateOnlyUnitsOrUnitGroups,
6363
} from '../../decorators/validate-units-required.decorator';
6464
import { ValidateListingDeposit } from '../../decorators/validate-listing-deposit.decorator';
65+
import { ListingDocuments } from './listing-documents.dto';
6566

6667
class Listing extends AbstractDTO {
6768
@Expose()
@@ -398,22 +399,6 @@ class Listing extends AbstractDTO {
398399
@ApiPropertyOptional()
399400
depositValue?: number;
400401

401-
@Expose()
402-
@ValidateListingPublish('depositRangeMin', {
403-
groups: [ValidationsGroupsEnum.default],
404-
})
405-
@IsNumber()
406-
@ApiPropertyOptional()
407-
depositRangeMin?: number;
408-
409-
@Expose()
410-
@ValidateListingPublish('depositRangeMax', {
411-
groups: [ValidationsGroupsEnum.default],
412-
})
413-
@IsNumber()
414-
@ApiPropertyOptional()
415-
depositRangeMax?: number;
416-
417402
@Expose()
418403
@ValidateListingPublish('depositHelperText', {
419404
groups: [ValidationsGroupsEnum.default],
@@ -554,6 +539,15 @@ class Listing extends AbstractDTO {
554539
@ApiPropertyOptional()
555540
requiredDocuments?: string;
556541

542+
@Expose()
543+
@ValidateListingPublish('requiredDocumentsList', {
544+
groups: [ValidationsGroupsEnum.default],
545+
})
546+
@Type(() => ListingDocuments)
547+
@ValidateNested({ groups: [ValidationsGroupsEnum.default], each: true })
548+
@ApiPropertyOptional({ type: ListingDocuments })
549+
requiredDocumentsList?: ListingDocuments;
550+
557551
@Expose()
558552
@ValidateListingPublish('specialNotes', {
559553
groups: [ValidationsGroupsEnum.default],

0 commit comments

Comments
 (0)