Skip to content

Commit 8b622a1

Browse files
committed
wip
Signed-off-by: Sanjay Babu <sanjaytkbabu@gmail.com>
1 parent 4088eaf commit 8b622a1

18 files changed

Lines changed: 660 additions & 40 deletions

app/src/controllers/permit.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ import { generateCreateStamps, generateUpdateStamps } from '../db/utils/utils.ts
77
import { summarizePeachRecord } from '../parsers/peach.ts';
88
import { email } from '../services/email.ts';
99
import { getPeachRecord } from '../services/peach.ts';
10-
import { deletePermit, getPermit, getPermitTypes, listPermits, upsertPermit } from '../services/permit.ts';
10+
import {
11+
deletePermit,
12+
getPermit,
13+
getPermitTypes,
14+
listPermits,
15+
searchPermitsPCNS,
16+
upsertPermit
17+
} from '../services/permit.ts';
1118
import { createPermitNote } from '../services/permitNote.ts';
1219
import { deleteManyPermitTracking, upsertPermitTracking } from '../services/permitTracking.ts';
1320
import { getProjectByActivityId } from '../services/project.ts';
@@ -33,6 +40,7 @@ import type {
3340
PermitTracking,
3441
PermitType,
3542
PermitUpdateEmailParams,
43+
SearchPermitsOptions,
3644
SourceSystemKind
3745
} from '../types/index.ts';
3846

@@ -113,6 +121,25 @@ export const sendPermitUpdateEmail = async (params: PermitUpdateEmailParams) =>
113121
await email(emailData);
114122
};
115123

124+
export const searchPermitsController = async (
125+
req: Request<never, never, never, SearchPermitsOptions>,
126+
res: Response
127+
) => {
128+
console.log('\n\ninitiative\n\n' + req.currentContext.initiative);
129+
const response = await transactionWrapper<{ permits: Permit[]; totalRecords: number }>(
130+
async (tx: PrismaTransactionClient) => {
131+
const options: SearchPermitsOptions = {
132+
...req.query
133+
};
134+
135+
options.initiative = req.currentContext.initiative!;
136+
137+
return await searchPermitsPCNS(tx, options);
138+
}
139+
);
140+
res.status(200).json(response);
141+
};
142+
116143
/**
117144
* Creates update notes and sends out email notifications for updated permits
118145
* @param permit Permit to send notifications for

app/src/routes/v1/permit.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
getPermitController,
77
getPermitTypesController,
88
listPermitsController,
9+
searchPermitsController,
910
upsertPermitController
1011
} from '../../controllers/permit.ts';
1112
import { hasAccess, hasAuthorization } from '../../middleware/authorization.ts';
@@ -37,6 +38,15 @@ router.delete(
3738
/** Get a list of permit types */
3839
router.get('/types', hasAuthorization(Resource.PERMIT, Action.READ), getPermitTypesController);
3940

41+
router.get(
42+
'/search',
43+
hasAuthorization(Resource.PERMIT, Action.READ),
44+
// permitValidator.listPermits,
45+
//FIXME
46+
//@ts-expect-error - ignore this
47+
searchPermitsController
48+
);
49+
4050
/** Get a permit */
4151
router.get(
4252
'/:permitId',

app/src/services/permit.ts

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import { Initiative } from '../utils/enums/application.ts';
22

33
import type { PrismaTransactionClient } from '../db/dataConnection.ts';
4-
import type { ListPermitsOptions, Permit, PermitBase, PermitSearchParams, PermitType } from '../types/index.ts';
4+
import type {
5+
ListPermitsOptions,
6+
Permit,
7+
PermitBase,
8+
PermitSearchParams,
9+
PermitType,
10+
SearchPermitsOptions
11+
} from '../types/index.ts';
512

613
/**
714
* Delete a specific permit
@@ -166,6 +173,144 @@ export const searchPermits = async (tx: PrismaTransactionClient, params: PermitS
166173
return response;
167174
};
168175

176+
/**
177+
* Retrieve all permits if no activityId is provided, otherwise retrieve permits for a specific activity
178+
* @param tx Prisma transaction client
179+
* @param options Optional filtering parameters
180+
* @param options.activityId Optional PCNS Activity ID
181+
* @param options.includeNotes Optional flag to include permit notes
182+
* @returns A Promise that resolves to an object with permits array and total count
183+
*/
184+
export const searchPermitsPCNS = async (
185+
tx: PrismaTransactionClient,
186+
options: SearchPermitsOptions
187+
): Promise<{ permits: Permit[]; totalRecords: number }> => {
188+
console.log('\n\nsearchPermitsPCNS options:\n\n', options);
189+
190+
// Determine project table based on initiative
191+
const projectTableMap: Record<Initiative, string> = {
192+
[Initiative.HOUSING]: 'housingProject',
193+
[Initiative.ELECTRIFICATION]: 'electrificationProject',
194+
[Initiative.PCNS]: 'housingProject' // FIXME remove
195+
};
196+
197+
const projectTable = projectTableMap[options.initiative];
198+
199+
// Build dynamic orderBy clause
200+
const sortDirection = options?.sortOrder === '1' ? 'asc' : 'desc';
201+
const validSortFields = ['submittedDate', 'state', 'stage', 'statusLastChanged', 'decisionDate', 'projectName'];
202+
203+
//FIXME
204+
//eslint-disable-next-line
205+
let orderBy: any = { submittedDate: 'desc' }; // default sorting
206+
207+
if (options?.sortField && validSortFields.includes(options.sortField)) {
208+
orderBy = { [options.sortField]: sortDirection };
209+
}
210+
211+
console.log('\n\nsearchPermitsPCNS orderBy clause:\n\n', orderBy);
212+
213+
// Build where clause (reused for both count and findMany)
214+
const whereClause = {
215+
AND: [
216+
// Ensure permit has at least one project
217+
{
218+
activity: {
219+
[projectTable]: {
220+
some: {}
221+
}
222+
}
223+
},
224+
options.dateRange
225+
? {
226+
OR: [
227+
{ submittedDate: { gte: options.dateRange[0], lte: options.dateRange[1] } },
228+
{ decisionDate: { gte: options.dateRange[0], lte: options.dateRange[1] } },
229+
{ statusLastChanged: { gte: options.dateRange[0], lte: options.dateRange[1] } }
230+
]
231+
}
232+
: {},
233+
options?.permitTypeId ? { permitTypeId: parseInt(options.permitTypeId) } : {},
234+
options?.sourceSystemKindId
235+
? {
236+
permitTracking: {
237+
some: {
238+
sourceSystemKindId: parseInt(options.sourceSystemKindId)
239+
}
240+
}
241+
}
242+
: {},
243+
options?.searchTag
244+
? {
245+
OR: [
246+
{ activityId: { contains: options.searchTag, mode: 'insensitive' as const } },
247+
{ stage: { contains: options.searchTag, mode: 'insensitive' as const } },
248+
{ state: { contains: options.searchTag, mode: 'insensitive' as const } },
249+
{ permitType: { name: { contains: options.searchTag, mode: 'insensitive' as const } } },
250+
{ permitType: { businessDomain: { contains: options.searchTag, mode: 'insensitive' as const } } },
251+
{
252+
activity: {
253+
[projectTable]: {
254+
some: {
255+
OR: [
256+
{ projectName: { contains: options.searchTag, mode: 'insensitive' as const } },
257+
{ companyNameRegistered: { contains: options.searchTag, mode: 'insensitive' as const } },
258+
{ streetAddress: { contains: options.searchTag, mode: 'insensitive' as const } },
259+
{ locality: { contains: options.searchTag, mode: 'insensitive' as const } },
260+
{ province: { contains: options.searchTag, mode: 'insensitive' as const } }
261+
]
262+
}
263+
}
264+
}
265+
}
266+
]
267+
}
268+
: {}
269+
]
270+
};
271+
272+
// Get total count (without pagination)
273+
const totalRecords = await tx.permit.count({
274+
where: whereClause
275+
});
276+
277+
// Get paginated data
278+
const permits = await tx.permit.findMany({
279+
skip: options?.skip ? parseInt(options.skip) : 0,
280+
take: options?.take ? parseInt(options.take) : 10,
281+
where: whereClause,
282+
orderBy: orderBy,
283+
include: {
284+
permitType: true,
285+
permitTracking: {
286+
include: {
287+
sourceSystemKind: true
288+
}
289+
},
290+
activity: {
291+
include: {
292+
[projectTable]: true
293+
}
294+
}
295+
}
296+
});
297+
298+
// Map the results to alias the project table as 'project'
299+
const permitsWithProjectAlias = permits.map((permit) => {
300+
const { [projectTable]: projectData, ...activityData } = permit.activity;
301+
302+
return {
303+
...permit,
304+
activity: {
305+
...activityData,
306+
project: projectData
307+
}
308+
};
309+
});
310+
311+
return { permits: permitsWithProjectAlias, totalRecords };
312+
};
313+
169314
/**
170315
* Upsert a Permit
171316
* @param tx Prisma transaction client

app/src/types/stuff.d.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,18 @@ export type DateTimeStrings = SplitDatetimeBase<string>;
346346

347347
export type NullableDateTimeStrings = SplitDatetimeBase<string | null>;
348348

349+
export interface SearchPermitsOptions extends Partial<IStamps> {
350+
dateRange?: [Date, Date];
351+
initiative: Initiative;
352+
skip?: string;
353+
take?: string;
354+
searchTag?: string;
355+
sortField?: string;
356+
sortOrder?: string;
357+
sourceSystemKindId?: string;
358+
permitTypeId?: string;
359+
}
360+
349361
export interface StatisticsFilters extends ParsedQs {
350362
dateFrom: string;
351363
dateTo: string;

app/src/validators/housingProject.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const schema = {
6868
body: Joi.object({
6969
activityId: Joi.array().items(Joi.string()),
7070
createdBy: Joi.array().items(Joi.string()),
71+
includePermits: Joi.boolean(),
7172
includeUser: Joi.boolean(),
7273
housingProjectId: Joi.array().items(uuidv4),
7374
submissionType: Joi.array().items(...SUBMISSION_TYPE_LIST)

app/src/validators/permit.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ const schema = {
5050
listPermits: {
5151
query: Joi.object({
5252
activityId: Joi.string().min(8).max(8).allow(null),
53-
includeNotes: Joi.boolean().allow(null)
53+
includeNotes: Joi.boolean().allow(null),
54+
pageNo: Joi.number().integer().min(1).allow(null),
55+
permitTracking: permitTrackingSchema.allow(null),
56+
permitType: permitTypeSchema.allow(null)
5457
})
5558
},
5659
upsertPermit: {

frontend/src/components/authorization/AuthorizationCard.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ function toCopy(toCopy: string) {
124124
</span>
125125
</div>
126126
<div class="my-2">
127-
<span class="font-bold">{{ t('authorization.authorizationCard.statusChangeDate') }}:</span>
127+
<span class="font-bold">{{ t('authorization.common.statusChangeDate') }}:</span>
128128
<span
129129
v-if="permit.statusLastChanged"
130130
class="ml-1"
@@ -133,7 +133,7 @@ function toCopy(toCopy: string) {
133133
</span>
134134
</div>
135135
<div class="my-2">
136-
<span class="font-bold">{{ t('authorization.authorizationCard.submittedDate') }}:</span>
136+
<span class="font-bold">{{ t('authorization.common.submittedDate') }}:</span>
137137
<span
138138
v-if="permit.submittedDate"
139139
class="ml-1"
@@ -142,7 +142,7 @@ function toCopy(toCopy: string) {
142142
</span>
143143
</div>
144144
<div class="my-2">
145-
<span class="font-bold">{{ t('authorization.authorizationCard.decisionDate') }}:</span>
145+
<span class="font-bold">{{ t('authorization.common.decisionDate') }}:</span>
146146
<span
147147
v-if="permit.decisionDate"
148148
class="ml-1"

frontend/src/components/authorization/AuthorizationCardIntake.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ onBeforeMount(async () => {
4646
<div>
4747
<Select
4848
name="authorizationType"
49-
:label="t('authorization.authorizationCardIntake.authorization')"
49+
:label="t('authorization.common.authorization')"
5050
:placeholder="t('authorization.authorizationCardIntake.selectAuthorization')"
5151
:options="getPermitTypes"
5252
:option-label="(e) => `${e.businessDomain}: ${e.name}`"
@@ -72,7 +72,7 @@ onBeforeMount(async () => {
7272
<div class="grid grid-cols-3 gap-x-6 gap-y-6 mt-2">
7373
<Select
7474
:name="`permitTracking[${idx}].sourceSystemKindId`"
75-
:placeholder="t('authorization.authorizationCardIntake.selectId')"
75+
:placeholder="t('authorization.common.selectId')"
7676
:options="sourceSystemKinds"
7777
:option-label="(e) => `${e.description}, ${codeDisplay.SourceSystem[e.sourceSystem]}`"
7878
option-value="sourceSystemKindId"

frontend/src/components/authorization/AuthorizationForm.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ const formSchema = object({
6767
value !== PermitNeeded.UNDER_INVESTIGATION
6868
);
6969
}),
70-
stage: string().required().oneOf(PERMIT_STAGE_LIST).label(t('authorization.authorizationForm.applicationStage')),
70+
stage: string().required().oneOf(PERMIT_STAGE_LIST).label(t('authorization.common.applicationStage')),
7171
issuedPermitId: string().nullable(),
7272
permitId: string(),
7373
permitTracking: array().of(
@@ -100,8 +100,8 @@ const formSchema = object({
100100
if (value === PermitState.ACCEPTED) return stage === PermitStage.APPLICATION_SUBMISSION;
101101
return true;
102102
}),
103-
submittedDate: notInFutureValidator.nullable().label(t('authorization.authorizationForm.submittedDate')),
104-
decisionDate: notInFutureValidator.nullable().label(t('authorization.authorizationForm.decisionDate')),
103+
submittedDate: notInFutureValidator.nullable().label(t('authorization.common.submittedDate')),
104+
decisionDate: notInFutureValidator.nullable().label(t('authorization.common.decisionDate')),
105105
statusLastVerified: notInFutureValidator.nullable().label(t('authorization.authorizationForm.statusLastVerified')),
106106
statusLastChanged: notInFutureValidator.nullable().label(t('authorization.authorizationForm.statusLastChanged'))
107107
});

frontend/src/components/authorization/AuthorizationInfoProponent.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const { t } = useI18n();
3232
</div>
3333
<div class="border-color px-4 py-5 border border-[0.063rem]">
3434
<div>
35-
<b>{{ t('authorization.authorizationInfoProponent.submittedDate') }}:</b>
35+
<b>{{ t('authorization.common.submittedDate') }}:</b>
3636
<span class="ml-1">
3737
{{ formatDateOnly(permit?.submittedDate) }}
3838
</span>
@@ -48,7 +48,7 @@ const { t } = useI18n();
4848
</div>
4949
<div class="border-color px-4 py-5 border border-[0.063rem]">
5050
<div>
51-
<b>{{ t('authorization.authorizationInfoProponent.decisionDate') }}:</b>
51+
<b>{{ t('authorization.common.decisionDate') }}:</b>
5252
<span class="ml-1">
5353
<span v-if="permit.decisionDate">
5454
{{ formatDateOnly(permit.decisionDate) }}

0 commit comments

Comments
 (0)