@rentcheck/biz 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ import { Feature, Inspection, InspectionTemplate, Property } from '@rentcheck/types';
2
+ export declare const parseTemplateRoomName: (name: string) => string;
3
+ export declare const sectionCanExistMultipleTimes: (sectionName: string) => boolean;
4
+ type BuildTemplateFeaturesParams = {
5
+ inspection: Inspection;
6
+ property: Property;
7
+ inspectionTemplate: InspectionTemplate;
8
+ };
9
+ export declare const buildTemplateFeatures: ({ inspection, property, inspectionTemplate, }: BuildTemplateFeaturesParams) => Promise<Feature[]>;
10
+ export {};
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.buildTemplateFeatures = exports.sectionCanExistMultipleTimes = exports.parseTemplateRoomName = void 0;
13
+ const utils_1 = require("../../utils");
14
+ const createResponsesForFeature = (feature) => feature.questions.map((q) => {
15
+ if (q.type === 'yes/no') {
16
+ return 'Yes';
17
+ }
18
+ return '';
19
+ });
20
+ const createEmptyFeature = (inspection, basicFeatureProps, section, sectionName, sectionNameWithOrdinal) => (Object.assign(Object.assign({}, basicFeatureProps), { id: '', inspection_id: inspection.id, property_id: inspection.propertyID, section: {
21
+ name: sectionName,
22
+ name_with_ordinal: sectionNameWithOrdinal !== null && sectionNameWithOrdinal !== void 0 ? sectionNameWithOrdinal : sectionName,
23
+ type: section.type,
24
+ }, images: [], videos: [], image_links: [], video_links: [], rating: undefined, incomplete: true, not_applicable: false, notes: '', responses: createResponsesForFeature(basicFeatureProps), uploaded: false, revision_requested: false, revision_requested_update: false, edited_by_reviewer: false, review_notes: '', reviewed_by: undefined, reviewed_at: undefined, maintenance_flags: [], created_date: new Date(), updated_date: new Date() }));
25
+ const createFeaturesFromSection = (inspection, section, sectionName, sectionNameWithOrdinal) => {
26
+ if (!section.features.length) {
27
+ return [];
28
+ }
29
+ /**
30
+ * If fast track is enabled, we only create one feature for the section
31
+ * with the name of the section as feature name and a predefined description.
32
+ */
33
+ if (inspection.fast_track) {
34
+ const basicFeatureProps = {
35
+ name: sectionName,
36
+ description: '',
37
+ is_rating_required: true,
38
+ reference_photos: [],
39
+ number_of_photos_required: 1,
40
+ questions: [],
41
+ };
42
+ if (section.type === 'room') {
43
+ basicFeatureProps.description =
44
+ `Stand back from each corner to ` +
45
+ `ensure you get photos of the ${sectionName} in its entirety.`;
46
+ }
47
+ else if (section.type === 'section') {
48
+ basicFeatureProps.description =
49
+ `Take any additional ${sectionName}` +
50
+ ` photos that you would like to document.`;
51
+ }
52
+ return [
53
+ createEmptyFeature(inspection, basicFeatureProps, section, sectionName, sectionNameWithOrdinal),
54
+ ];
55
+ }
56
+ /**
57
+ * If fast track is not enabled, we create features based on the template
58
+ * features for the section.
59
+ */
60
+ return section.features.map((tf) => createEmptyFeature(inspection, tf, section, sectionName, sectionNameWithOrdinal));
61
+ };
62
+ const parseTemplateRoomName = (name) => {
63
+ if (name === 'Floors') {
64
+ return 'Floor';
65
+ }
66
+ if (name === 'Full Bathrooms') {
67
+ return 'Full Bathroom';
68
+ }
69
+ if (name === 'Half Bathrooms') {
70
+ return 'Half Bathroom';
71
+ }
72
+ if (name === 'Bedrooms') {
73
+ return 'Bedroom';
74
+ }
75
+ return name;
76
+ };
77
+ exports.parseTemplateRoomName = parseTemplateRoomName;
78
+ const sectionCanExistMultipleTimes = (sectionName) => [
79
+ 'Bedroom',
80
+ 'Bedrooms',
81
+ 'Full Bathroom',
82
+ 'Full Bathrooms',
83
+ 'Half Bathroom',
84
+ 'Half Bathrooms',
85
+ 'Floor',
86
+ 'Floors',
87
+ ].includes(sectionName);
88
+ exports.sectionCanExistMultipleTimes = sectionCanExistMultipleTimes;
89
+ const addOrdinalToFloorSection = (number) => {
90
+ /**
91
+ * America has no concept of a ground floor, so we add 1 to the number
92
+ * to get the correct floor name. /shrug
93
+ */
94
+ const floor = number + 1;
95
+ if (floor % 10 === 1) {
96
+ return `${floor}st Floor`;
97
+ }
98
+ if (floor % 10 === 2) {
99
+ return `${floor}nd Floor`;
100
+ }
101
+ if (floor % 10 === 3) {
102
+ return `${floor}rd Floor`;
103
+ }
104
+ return `${floor}th Floor`;
105
+ };
106
+ /**
107
+ * Adds an ordinal to the section to account for repeatable sections.
108
+ *
109
+ * @param sectionName Floor, Bedroom, Full Bathroom, Half Bathroom
110
+ * @param number index of the section, 0 indexed
111
+ * @returns the updated section name, e.g. "First Floor", "Second Bedroom"
112
+ */
113
+ const addOrdinalToSectionName = (sectionName, number) => {
114
+ if (sectionName === 'Floor') {
115
+ return addOrdinalToFloorSection(number);
116
+ }
117
+ return `${utils_1.Constants.ordinalPrefixes[number]} ${sectionName}`;
118
+ };
119
+ const createFeaturesFromRepeatableSection = (sectionName, section, inspection, property) => {
120
+ const sectionCount = sectionName === 'Floor'
121
+ ? property.floors
122
+ : inspection.rooms.filter((r) => r === sectionName).length;
123
+ return new Array(sectionCount)
124
+ .fill(null)
125
+ .map((_, i) => createFeaturesFromSection(inspection, section, sectionName, addOrdinalToSectionName(sectionName, i)))
126
+ .flat();
127
+ };
128
+ const parseSelectedSectionName = (name) => {
129
+ if (/(\d)+(st|nd|rd|th) Floor/.test(name)) {
130
+ return 'Floors';
131
+ }
132
+ if (/\w+ Full Bathroom/.test(name)) {
133
+ return 'Full Bathrooms';
134
+ }
135
+ if (/\w+ Half Bathroom/.test(name)) {
136
+ return 'Half Bathrooms';
137
+ }
138
+ if (/\w+ Bedroom/.test(name)) {
139
+ return 'Bedrooms';
140
+ }
141
+ return name;
142
+ };
143
+ const createFeaturesForSpecificFeaturesTemplate = (inspection, inspectionTemplate) => {
144
+ var _a;
145
+ const features = [];
146
+ const selectedFeatures = Array.from((_a = inspection.selected_features) !== null && _a !== void 0 ? _a : []);
147
+ for (const selectedFeature of selectedFeatures) {
148
+ const sectionName = parseSelectedSectionName(selectedFeature.section);
149
+ const section = inspectionTemplate.sections.find((s) => s.name === sectionName);
150
+ if (!section) {
151
+ continue;
152
+ }
153
+ const selectedFeaturesInSection = section.features.filter((f) => selectedFeature.feature === f.name);
154
+ const limitedSection = Object.assign(Object.assign({}, section), { features: selectedFeaturesInSection });
155
+ /**
156
+ * For selected features we create sections in the template regardless of
157
+ * property rooms. We assume all that logic already happened in the backend
158
+ * at the time of inspection creation
159
+ */
160
+ features.push(...createFeaturesFromSection(inspection, limitedSection, section.name, selectedFeature.room_name));
161
+ }
162
+ return features;
163
+ };
164
+ const createFeaturesForAllRoomsTemplate = (inspection, inspectionTemplate, property) => {
165
+ const features = [];
166
+ for (const section of inspectionTemplate.sections) {
167
+ /**
168
+ * We create sections in the template regardless of
169
+ * property rooms.
170
+ */
171
+ if (section.type === 'section') {
172
+ features.push(...createFeaturesFromSection(inspection, section, section.name));
173
+ }
174
+ /**
175
+ * For room type sections we first check if the room exists on
176
+ * the property/inspection. If it does we create the features for that room.
177
+ */
178
+ if (section.type === 'room') {
179
+ /**
180
+ * Some section names use plural nomenclature, while the property rooms
181
+ * are all singular, that's why we first need to change
182
+ */
183
+ const singularName = (0, exports.parseTemplateRoomName)(section.name);
184
+ if (!inspection.rooms.includes(singularName)) {
185
+ continue;
186
+ }
187
+ /**
188
+ * If this is a section that can be repeated (Bedroom, Bathroom, etc.)
189
+ * we need to create features for each room of that type.
190
+ */
191
+ if ((0, exports.sectionCanExistMultipleTimes)(section.name)) {
192
+ features.push(...createFeaturesFromRepeatableSection(singularName, section, inspection, property));
193
+ }
194
+ else {
195
+ features.push(...createFeaturesFromSection(inspection, section, singularName));
196
+ }
197
+ }
198
+ }
199
+ return features;
200
+ };
201
+ const buildTemplateFeatures = (_a) => __awaiter(void 0, [_a], void 0, function* ({ inspection, property, inspectionTemplate, }) {
202
+ const features = [];
203
+ if (!property) {
204
+ return [];
205
+ }
206
+ if (inspectionTemplate.template_type === 'all-rooms') {
207
+ features.push(...createFeaturesForAllRoomsTemplate(inspection, inspectionTemplate, property));
208
+ }
209
+ if (inspectionTemplate.template_type === 'specific-features') {
210
+ features.push(...createFeaturesForSpecificFeaturesTemplate(inspection, inspectionTemplate));
211
+ }
212
+ return features;
213
+ });
214
+ exports.buildTemplateFeatures = buildTemplateFeatures;
215
+ //# sourceMappingURL=build-template-features.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-template-features.js","sourceRoot":"","sources":["../../../src/api/inspection-template/build-template-features.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare const InspectionTemplateBiz: {
2
+ buildTemplateFeatures: ({ inspection, property, inspectionTemplate, }: {
3
+ inspection: import("@rentcheck/types").Inspection;
4
+ property: import("@rentcheck/types").Property;
5
+ inspectionTemplate: import("@rentcheck/types").InspectionTemplate;
6
+ }) => Promise<import("@rentcheck/types").Feature[]>;
7
+ };
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InspectionTemplateBiz = void 0;
4
+ const build_template_features_1 = require("./build-template-features");
5
+ exports.InspectionTemplateBiz = { buildTemplateFeatures: build_template_features_1.buildTemplateFeatures };
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/api/inspection-template/index.ts"],"names":[],"mappings":";;;AAAA,uEAAkE;AAErD,QAAA,qBAAqB,GAAG,EAAE,qBAAqB,EAArB,+CAAqB,EAAE,CAAC"}
@@ -0,0 +1 @@
1
+ export declare const ordinalPrefixes: string[];
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ordinalPrefixes = void 0;
4
+ exports.ordinalPrefixes = [
5
+ 'First',
6
+ 'Second',
7
+ 'Third',
8
+ 'Fourth',
9
+ 'Fifth',
10
+ 'Sixth',
11
+ 'Seventh',
12
+ 'Eighth',
13
+ 'Ninth',
14
+ ];
15
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/utils/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,eAAe,GAAG;IAC9B,OAAO;IACP,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,OAAO;IACP,OAAO;IACP,SAAS;IACT,QAAQ;IACR,OAAO;CACP,CAAC"}
@@ -0,0 +1 @@
1
+ export * as Constants from './constants';
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.Constants = void 0;
27
+ exports.Constants = __importStar(require("./constants"));
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,yDAAyC"}
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@rentcheck/biz",
3
+ "version": "1.0.0",
4
+ "description": "RC biz",
5
+ "types": "lib/index.d.ts",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "build:watch": "tsc --watch",
10
+ "test": "cross-env NODE_ENV=test jest --verbose --forceExit"
11
+ },
12
+ "author": "engineering@getrentcheck.com",
13
+ "license": "MIT",
14
+ "devDependencies": {
15
+ "@rentcheck/types": "^1.0.460",
16
+ "@types/node": "^20.11.30",
17
+ "jest": "^29.7.0",
18
+ "typescript": "^5.4.3"
19
+ }
20
+ }
@@ -0,0 +1,387 @@
1
+ import {
2
+ Building,
3
+ Feature,
4
+ Inspection,
5
+ InspectionTemplate,
6
+ OfflineFeature,
7
+ Property,
8
+ TemplateFeature,
9
+ TemplateSection,
10
+ Timestamp,
11
+ } from '@rentcheck/types';
12
+
13
+ import { Constants } from '../../utils';
14
+
15
+ type BasicFeatureProps = Pick<
16
+ TemplateFeature,
17
+ | 'name'
18
+ | 'description'
19
+ | 'is_rating_required'
20
+ | 'reference_photos'
21
+ | 'number_of_photos_required'
22
+ | 'questions'
23
+ >;
24
+
25
+ const createResponsesForFeature = (feature: BasicFeatureProps): string[] =>
26
+ feature.questions.map((q) => {
27
+ if (q.type === 'yes/no') {
28
+ return 'Yes';
29
+ }
30
+
31
+ return '';
32
+ });
33
+
34
+ const createEmptyFeature = (
35
+ inspection: Inspection,
36
+ basicFeatureProps: BasicFeatureProps,
37
+ section: TemplateSection,
38
+ sectionName: string,
39
+ sectionNameWithOrdinal?: string
40
+ ): OfflineFeature => ({
41
+ ...basicFeatureProps,
42
+
43
+ id: '',
44
+ inspection_id: inspection.id,
45
+ property_id: inspection.propertyID,
46
+
47
+ section: {
48
+ name: sectionName,
49
+ name_with_ordinal: sectionNameWithOrdinal ?? sectionName,
50
+ type: section.type,
51
+ },
52
+
53
+ images: [],
54
+ videos: [],
55
+ image_links: [],
56
+ video_links: [],
57
+ rating: undefined,
58
+ incomplete: true,
59
+ not_applicable: false,
60
+ notes: '',
61
+ responses: createResponsesForFeature(basicFeatureProps),
62
+
63
+ uploaded: false,
64
+
65
+ revision_requested: false,
66
+ revision_requested_update: false,
67
+ edited_by_reviewer: false,
68
+ review_notes: '',
69
+ reviewed_by: undefined,
70
+ reviewed_at: undefined,
71
+
72
+ maintenance_flags: [],
73
+
74
+ created_date: new Date() as unknown as Timestamp,
75
+ updated_date: new Date() as unknown as Timestamp,
76
+ });
77
+
78
+ const createFeaturesFromSection = (
79
+ inspection: Inspection,
80
+ section: TemplateSection,
81
+ sectionName: string,
82
+ sectionNameWithOrdinal?: string
83
+ ): Feature[] => {
84
+ if (!section.features.length) {
85
+ return [];
86
+ }
87
+
88
+ /**
89
+ * If fast track is enabled, we only create one feature for the section
90
+ * with the name of the section as feature name and a predefined description.
91
+ */
92
+ if (inspection.fast_track) {
93
+ const basicFeatureProps: BasicFeatureProps = {
94
+ name: sectionName,
95
+ description: '',
96
+ is_rating_required: true,
97
+ reference_photos: [],
98
+ number_of_photos_required: 1,
99
+ questions: [],
100
+ };
101
+
102
+ if (section.type === 'room') {
103
+ basicFeatureProps.description =
104
+ `Stand back from each corner to ` +
105
+ `ensure you get photos of the ${sectionName} in its entirety.`;
106
+ } else if (section.type === 'section') {
107
+ basicFeatureProps.description =
108
+ `Take any additional ${sectionName}` +
109
+ ` photos that you would like to document.`;
110
+ }
111
+
112
+ return [
113
+ createEmptyFeature(
114
+ inspection,
115
+ basicFeatureProps,
116
+ section,
117
+ sectionName,
118
+ sectionNameWithOrdinal
119
+ ),
120
+ ];
121
+ }
122
+
123
+ /**
124
+ * If fast track is not enabled, we create features based on the template
125
+ * features for the section.
126
+ */
127
+ return section.features.map((tf) =>
128
+ createEmptyFeature(
129
+ inspection,
130
+ tf,
131
+ section,
132
+ sectionName,
133
+ sectionNameWithOrdinal
134
+ )
135
+ );
136
+ };
137
+
138
+ export const parseTemplateRoomName = (name: string) => {
139
+ if (name === 'Floors') {
140
+ return 'Floor';
141
+ }
142
+
143
+ if (name === 'Full Bathrooms') {
144
+ return 'Full Bathroom';
145
+ }
146
+
147
+ if (name === 'Half Bathrooms') {
148
+ return 'Half Bathroom';
149
+ }
150
+
151
+ if (name === 'Bedrooms') {
152
+ return 'Bedroom';
153
+ }
154
+
155
+ return name;
156
+ };
157
+
158
+ export const sectionCanExistMultipleTimes = (sectionName: string) =>
159
+ [
160
+ 'Bedroom',
161
+ 'Bedrooms',
162
+ 'Full Bathroom',
163
+ 'Full Bathrooms',
164
+ 'Half Bathroom',
165
+ 'Half Bathrooms',
166
+ 'Floor',
167
+ 'Floors',
168
+ ].includes(sectionName);
169
+
170
+ const addOrdinalToFloorSection = (number: number) => {
171
+ /**
172
+ * America has no concept of a ground floor, so we add 1 to the number
173
+ * to get the correct floor name. /shrug
174
+ */
175
+ const floor = number + 1;
176
+
177
+ if (floor % 10 === 1) {
178
+ return `${floor}st Floor`;
179
+ }
180
+
181
+ if (floor % 10 === 2) {
182
+ return `${floor}nd Floor`;
183
+ }
184
+
185
+ if (floor % 10 === 3) {
186
+ return `${floor}rd Floor`;
187
+ }
188
+
189
+ return `${floor}th Floor`;
190
+ };
191
+
192
+ /**
193
+ * Adds an ordinal to the section to account for repeatable sections.
194
+ *
195
+ * @param sectionName Floor, Bedroom, Full Bathroom, Half Bathroom
196
+ * @param number index of the section, 0 indexed
197
+ * @returns the updated section name, e.g. "First Floor", "Second Bedroom"
198
+ */
199
+ const addOrdinalToSectionName = (sectionName: string, number: number) => {
200
+ if (sectionName === 'Floor') {
201
+ return addOrdinalToFloorSection(number);
202
+ }
203
+
204
+ return `${Constants.ordinalPrefixes[number]} ${sectionName}`;
205
+ };
206
+
207
+ const createFeaturesFromRepeatableSection = (
208
+ sectionName: string,
209
+ section: TemplateSection,
210
+ inspection: Inspection,
211
+ property: Property
212
+ ): Feature[] => {
213
+ const sectionCount =
214
+ sectionName === 'Floor'
215
+ ? (property as Building).floors
216
+ : inspection.rooms.filter((r) => r === sectionName).length;
217
+
218
+ return new Array(sectionCount)
219
+ .fill(null)
220
+ .map((_, i) =>
221
+ createFeaturesFromSection(
222
+ inspection,
223
+ section,
224
+ sectionName,
225
+ addOrdinalToSectionName(sectionName, i)
226
+ )
227
+ )
228
+ .flat();
229
+ };
230
+
231
+ const parseSelectedSectionName = (name: string) => {
232
+ if (/(\d)+(st|nd|rd|th) Floor/.test(name)) {
233
+ return 'Floors';
234
+ }
235
+
236
+ if (/\w+ Full Bathroom/.test(name)) {
237
+ return 'Full Bathrooms';
238
+ }
239
+
240
+ if (/\w+ Half Bathroom/.test(name)) {
241
+ return 'Half Bathrooms';
242
+ }
243
+
244
+ if (/\w+ Bedroom/.test(name)) {
245
+ return 'Bedrooms';
246
+ }
247
+
248
+ return name;
249
+ };
250
+
251
+ const createFeaturesForSpecificFeaturesTemplate = (
252
+ inspection: Inspection,
253
+ inspectionTemplate: InspectionTemplate
254
+ ): Feature[] => {
255
+ const features: Feature[] = [];
256
+
257
+ const selectedFeatures = Array.from(inspection.selected_features ?? []);
258
+
259
+ for (const selectedFeature of selectedFeatures) {
260
+ const sectionName = parseSelectedSectionName(selectedFeature.section);
261
+ const section = inspectionTemplate.sections.find(
262
+ (s) => s.name === sectionName
263
+ );
264
+
265
+ if (!section) {
266
+ continue;
267
+ }
268
+
269
+ const selectedFeaturesInSection = section.features.filter(
270
+ (f) => selectedFeature.feature === f.name
271
+ );
272
+
273
+ const limitedSection = { ...section, features: selectedFeaturesInSection };
274
+
275
+ /**
276
+ * For selected features we create sections in the template regardless of
277
+ * property rooms. We assume all that logic already happened in the backend
278
+ * at the time of inspection creation
279
+ */
280
+ features.push(
281
+ ...createFeaturesFromSection(
282
+ inspection,
283
+ limitedSection,
284
+ section.name,
285
+ selectedFeature.room_name
286
+ )
287
+ );
288
+ }
289
+
290
+ return features;
291
+ };
292
+
293
+ const createFeaturesForAllRoomsTemplate = (
294
+ inspection: Inspection,
295
+ inspectionTemplate: InspectionTemplate,
296
+ property: Property
297
+ ): Feature[] => {
298
+ const features: Feature[] = [];
299
+
300
+ for (const section of inspectionTemplate.sections) {
301
+ /**
302
+ * We create sections in the template regardless of
303
+ * property rooms.
304
+ */
305
+ if (section.type === 'section') {
306
+ features.push(
307
+ ...createFeaturesFromSection(inspection, section, section.name)
308
+ );
309
+ }
310
+
311
+ /**
312
+ * For room type sections we first check if the room exists on
313
+ * the property/inspection. If it does we create the features for that room.
314
+ */
315
+ if (section.type === 'room') {
316
+ /**
317
+ * Some section names use plural nomenclature, while the property rooms
318
+ * are all singular, that's why we first need to change
319
+ */
320
+ const singularName = parseTemplateRoomName(section.name);
321
+
322
+ if (!inspection.rooms.includes(singularName)) {
323
+ continue;
324
+ }
325
+
326
+ /**
327
+ * If this is a section that can be repeated (Bedroom, Bathroom, etc.)
328
+ * we need to create features for each room of that type.
329
+ */
330
+ if (sectionCanExistMultipleTimes(section.name)) {
331
+ features.push(
332
+ ...createFeaturesFromRepeatableSection(
333
+ singularName,
334
+ section,
335
+ inspection,
336
+ property
337
+ )
338
+ );
339
+ } else {
340
+ features.push(
341
+ ...createFeaturesFromSection(inspection, section, singularName)
342
+ );
343
+ }
344
+ }
345
+ }
346
+
347
+ return features;
348
+ };
349
+
350
+ type BuildTemplateFeaturesParams = {
351
+ inspection: Inspection;
352
+ property: Property;
353
+ inspectionTemplate: InspectionTemplate;
354
+ };
355
+
356
+ export const buildTemplateFeatures = async ({
357
+ inspection,
358
+ property,
359
+ inspectionTemplate,
360
+ }: BuildTemplateFeaturesParams): Promise<Feature[]> => {
361
+ const features: Feature[] = [];
362
+
363
+ if (!property) {
364
+ return [];
365
+ }
366
+
367
+ if (inspectionTemplate.template_type === 'all-rooms') {
368
+ features.push(
369
+ ...createFeaturesForAllRoomsTemplate(
370
+ inspection,
371
+ inspectionTemplate,
372
+ property
373
+ )
374
+ );
375
+ }
376
+
377
+ if (inspectionTemplate.template_type === 'specific-features') {
378
+ features.push(
379
+ ...createFeaturesForSpecificFeaturesTemplate(
380
+ inspection,
381
+ inspectionTemplate
382
+ )
383
+ );
384
+ }
385
+
386
+ return features;
387
+ };
@@ -0,0 +1,3 @@
1
+ import { buildTemplateFeatures } from './build-template-features';
2
+
3
+ export const InspectionTemplateBiz = { buildTemplateFeatures };
@@ -0,0 +1,11 @@
1
+ export const ordinalPrefixes = [
2
+ 'First',
3
+ 'Second',
4
+ 'Third',
5
+ 'Fourth',
6
+ 'Fifth',
7
+ 'Sixth',
8
+ 'Seventh',
9
+ 'Eighth',
10
+ 'Ninth',
11
+ ];
@@ -0,0 +1 @@
1
+ export * as Constants from './constants';
@@ -0,0 +1,5 @@
1
+ {
2
+ "include": [
3
+ ".eslintrc.js", "decs.d.ts", "api.d.ts", "utils.d.ts", "rentManager.d.ts"
4
+ ]
5
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ // target options
4
+ "module": "commonjs",
5
+ "target": "es6",
6
+ "moduleResolution": "node",
7
+ "esModuleInterop": true,
8
+ "allowSyntheticDefaultImports": true,
9
+
10
+ // typeorm
11
+ "emitDecoratorMetadata": true,
12
+ "experimentalDecorators": true,
13
+ "sourceMap": true,
14
+
15
+ // directories
16
+ "rootDir": "src",
17
+ "outDir": "dist",
18
+
19
+ // workspace
20
+ "declaration": true,
21
+ "composite": true
22
+
23
+ // other options
24
+ },
25
+ "include": ["src/**/*"],
26
+ "exclude": ["node_modules"]
27
+ }