Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GRANT-deploy: BCAT tickets #104

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/src/application/application.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Comment } from '../comments/comment.entity';
import { FormMetaData } from '../FormMetaData/formmetadata.entity';
import { RemovableBaseEntity } from '../common/removable-base.entity';
import { User } from '../user/user.entity';
import { WorkshopScore } from '@/score/workshop-score.entity';

@Entity({
name: 'BCAT_APPLICATION',
Expand Down Expand Up @@ -47,6 +48,10 @@ export class Application extends RemovableBaseEntity {
@JoinColumn({ name: 'FORM_METADATA_ID' })
form: FormMetaData;

@OneToMany(() => WorkshopScore, (workshopScore) => workshopScore.application)
@JoinColumn()
workshopScores?: WorkshopScore[];

// Might belong to multiple users in the future, so
// change to ManyToMany accordingly if needed.
@ManyToOne(() => User, (user) => user.applications)
Expand Down
8 changes: 7 additions & 1 deletion api/src/application/application.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { FormMetaData } from '../FormMetaData/formmetadata.entity';
import { GenericException } from '../common/generic-exception';
import { GetApplicationsDto } from '../common/dto/get-applications.dto';
import { PaginationRO } from '../common/ro/pagination.ro';
import { RawDataRo } from '@/score/ro/raw-data.ro';
import { RawDataRo } from '../score/ro/raw-data.ro';
import { SaveApplicationDto } from '../common/dto/save-application.dto';
import { ScoreDto } from '../score/dto/score.dto';
import { UpdateStatusDto } from './dto/update-status.dto';
Expand Down Expand Up @@ -258,13 +258,19 @@ export class ApplicationService {
'a.projectTitle',
'a.totalEstimatedCost',
'a.updatedAt',
'a.submission',
'applicationType.name',
'status.name',
'user.displayName',
'workshopScore.data',
])
.leftJoin('a.assignedTo', 'user')
.leftJoin('a.status', 'status')
.leftJoin('a.applicationType', 'applicationType')
.leftJoin('a.workshopScores', 'workshopScore')
.where('status.name NOT ILIKE :rejectedStatus', {
rejectedStatus: `%${ApplicationStatus.DENIED}%`,
})
.getMany();
}
}
5 changes: 4 additions & 1 deletion api/src/database/scripts/sync-chefs-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,22 +111,25 @@ export class SyncChefsDataService {
responseType,
};
const files = await this.attachmentService.getAllAttachments(false);
if (!token) Logger.error(`No TOKEN found`);

for (const file of files) {
try {
Logger.log(`Fetching attachment - ${file.id}`);
// Get file data form server
const url = FILE_URL + file.url;
const fileRes = await axios({ ...options, url });
Logger.log(`File fetched successfully - ${file.id}`);
const fileData = Buffer.from(fileRes.data);
Logger.log(`Buffer extracted successfully - ${file.id}`);
file.data = fileData;
await this.attachmentService.updateAttachment(file);
} catch (error) {
Logger.error(
`Error occurred fetching attachment - ${file.id} - `,
JSON.stringify(getGenericError(error))
);
throw new GenericException(SyncDataError.SYNC_ATTACHMENT_ERROR);
// throw new GenericException(SyncDataError.SYNC_ATTACHMENT_ERROR);
}
}
}
Expand Down
248 changes: 224 additions & 24 deletions api/src/score/ro/raw-data.ro.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,262 @@
import dayjs from 'dayjs';
import { Application } from '@/application/application.entity';
import { Application } from '../../application/application.entity';
import { ApplicationType } from '../../common/constants';

// { label: 'Withdrawn / Cancelled', value: 'emptyVal' },

const roHeaders = {
sheet: 'Raw Data',
columns: [
{ label: 'Funding Year', value: 'fundingYear' },
{ label: 'Electoral District', value: 'emptyVal' },
{ label: 'Regional District', value: 'emptyVal' },
{ label: 'Economic Development Region', value: 'emptyVal' },
{ label: 'Applicant Name', value: 'applicantName' },
{ label: 'Applicant Type', value: 'applicantType' },
{ label: 'Indigenous Government', value: 'indigenousGovernment' },
{ label: 'Population', value: 'population', format: '#,##0' },
{ label: 'CGA', value: 'emptyVal' },
{ label: 'Application Type', value: 'applicationType' },
{ label: 'Project Title', value: 'projectTitle' },
{ label: 'Estimated Project Cost', value: 'totalEstimatedCost' },
{ label: 'Raw Ask', value: 'asks' },
{ label: 'Project Description', value: 'projectDescription' },
{ label: 'Project Type', value: 'emptyVal' },
{ label: 'Eligibility', value: 'eligibility' },
{ label: 'Project Length', value: 'emptyVal' },
{ label: 'Latitude Start', value: 'latitudeStart' },
{ label: 'Latitude End', value: 'latitudeEnd' },
{ label: 'Longitude Start', value: 'longitudeStart' },
{ label: 'Longitude End', value: 'longitudeEnd' },
{ label: 'Program', value: 'program' },
{ label: 'Estimated Project Cost', value: 'totalEstimatedCost', format: '$#,##0.00' },
{
label: 'Total Eligible Project Cost',
value: 'totalEligibleProjectCost',
format: '$#,##0.00',
},
{ label: 'Raw Ask', value: 'asks', format: '$#,##0.00' },
{ label: 'Assigned To', value: 'assignedTo' },
{ label: 'Last Update', value: 'lastUpdated' },
{ label: 'Status', value: 'status' },
{ label: 'Confirmation ID', value: 'confirmationId' },
{ label: 'Workshop Score', value: 'workshopScore' },
{ label: 'Total Actual Payment', value: 'emptyVal', format: '$#,##0.00' },
{ label: 'Project Open', value: 'emptyVal' },
],
};

const usageCountHeaders = {
sheet: 'Usage Count Data',
columns: [
{ label: 'Municipality', value: 'municipality' },
{ label: 'Project Name', value: 'projectName' },
{ label: 'Funding Year', value: 'fundingYear' },
{ label: 'Usage Count - Date', value: 'usageCountDate' },
{ label: 'Usage Count - Time', value: 'usageCountTime' },
{ label: 'Usage Count - Location', value: 'usageCountLocation' },
{ label: 'Usage Count - Pedestrian Count', value: 'usageCountPedestrian' },
{ label: 'Usage Count - Bicycle Count', value: 'usageCountBicycle' },
{ label: 'Usage Count - Other', value: 'usageCountOther' },
],
};

interface RawContent {
applicantName: string;
applicantType: string;
applicationType: string;
asks: string;
assignedTo: string;
confirmationId: string;
eligibility: string;
emptyVal: string;
fundingYear: string;
indigenousGovernment: string;
lastUpdated: string;
latitudeEnd: string;
latitudeStart: string;
longitudeEnd: string;
longitudeStart: string;
population: string;
program: string;
projectDescription: string;
projectTitle: string;
status: string;
totalEstimatedCost: string;
workshopScore: string;
}

interface UsageCountContent {
fundingYear: string;
municipality: string;
projectName: string;
usageCountDate: string;
usageCountTime: string;
usageCountLocation: string;
usageCountPedestrian: string;
usageCountBicycle: string;
usageCountOther: string;
}

export interface RawData {
sheet: string;
columns: { label: string; value: string }[];

content: {
applicantName: string;
applicationType: string;
projectTitle: string;
totalEstimatedCost: string;
asks: string;
assignedTo: string;
lastUpdated: string;
status: string;
confirmationId: string;
}[];
columns: { label: string; value: string; format?: string }[];
content: any[];
}

export class RawDataRo {
result: RawData[];
constructor(data: Application[]) {
this.result = [{ ...roHeaders, ...this.convertApplicationToContent(data) }];
this.result = [
{ ...roHeaders, content: this.convertApplicationToContent(data) },
{ ...usageCountHeaders, content: this.convertUsageCountToContent(data) },
];
}
convertApplicationToContent(data: Application[]) {

convertApplicationToContent(data: Application[]): RawContent[] {
const NO_VALUE = '-';

const formatDate = (itemDate: any) => {
return dayjs(itemDate).isValid() ? dayjs(itemDate).format('YYYY-MM-DD') : NO_VALUE;
};

const checkIfItemHasWorkshopScore = (item: any) => {
return item.workshopScores && item.workshopScores.length > 0;
};

const getFundingYear = (signedDate: any) => {
if (!signedDate) return NO_VALUE;
return `${dayjs(signedDate).format('YY')}/${dayjs(signedDate).add(1, 'year').format('YY')}`;
};

const checkGovernmentApplicant = (primaryApplicant: string, secondaryApplicant: string) => {
const YES = 'Y';
const NO = 'N';
const NA = '-';

if (!primaryApplicant) return NA;

return primaryApplicant.includes('indigenous') || secondaryApplicant?.includes('indigenous')
? YES
: NO;
};

const addScores = (array: any) => {
let sum = 0;
array.forEach((item: any) => {
if (Number.isInteger(item)) {
sum += Number(item);
}
});
return sum.toString();
};

const content = data.map((item: Application) => {
const NO_VALUE = '-';
const formattedDate = dayjs(item.updatedAt).isValid()
? dayjs(item.updatedAt).format('YYYY-MM-DD')
: NO_VALUE;
const submission = item.submission || {};

return {
eligibility: checkIfItemHasWorkshopScore(item)
? item.workshopScores[0].data?.eligibilityScore
: NO_VALUE,
fundingYear: getFundingYear(submission?.s10Container?.s10ProjectMangerApproverDate),
applicantName: item.applicantName,
indigenousGovernment: checkGovernmentApplicant(
submission.s1Container?.s1GovernmentType,
submission.s1Container?.s1SecondaryGovernmentType
),
applicationType: item.applicationType?.name || NO_VALUE,
applicantType: submission.s1Container?.s1GovernmentType || NO_VALUE,
population:
submission.s1Container?.s1PrimaryCommunityPopulation ||
submission.s1Container?.s1CommunityPopulation,
projectDescription:
submission.s5Container?.s5ProjectInformation ||
submission.s3Container?.s3DescriptionHighLevelScopeOutline ||
NO_VALUE,
latitudeStart: submission.s5Container?.s6ProjectStartLatitude,
latitudeEnd: submission.s5Container?.s6ProjectEndLatitude,
longitudeStart: submission.s5Container?.s6ProjectStartLongitude,
longitudeEnd: submission.s5Container?.s6ProjectEndLongitude,
program: 'AT',
totalEligibleProjectCost: submission.s8Container?.s8TotalEligibleProjectCost,
workshopScore: checkIfItemHasWorkshopScore(item)
? addScores(Object.values(item.workshopScores[0].data))
: NO_VALUE,
asks: item.asks,
assignedTo: item?.assignedTo?.displayName || NO_VALUE,
confirmationId: item.confirmationId,
lastUpdated: formattedDate,
lastUpdated: formatDate(item.updatedAt),
projectTitle: item.projectTitle,
status: item.status?.name || NO_VALUE,
totalEstimatedCost: item.totalEstimatedCost,
emptyVal: '',
};
});
return { content };

return content;
}

convertUsageCountToContent(data: Application[]): UsageCountContent[] {
const NO_VALUE = '-';
const content = [];

const formatDate = (itemDate: any) => {
return dayjs(itemDate).isValid() ? dayjs(itemDate).format('YYYY-MM-DD') : NO_VALUE;
};

const getFundingYear = (signedDate: any) => {
if (!signedDate) return NO_VALUE;
return `${dayjs(signedDate).format('YY')}/${dayjs(signedDate).add(1, 'year').format('YY')}`;
};

for (const item of data) {
if (item.applicationType?.name === ApplicationType.NETWORK_FORM) continue;

const submission = item.submission || {};
const usageCountGrid = submission.s5Container?.s5UsageCountFormGrid;

if (!usageCountGrid) {
const newItem = {
municipality: submission.s1Container?.s1GovernmentType || NO_VALUE,
projectName:
submission.s4Container?.s4ProjectTitle ||
submission.s3Container?.s3ProjectTitle ||
NO_VALUE,
fundingYear: getFundingYear(
submission.s10Container?.s10ProjectMangerApproverDate ||
submission.s11Container?.s11ProjectMangerApproverDate
),
usageCountDate: NO_VALUE,
usageCountTime: NO_VALUE,
usageCountLocation: NO_VALUE,
usageCountPedestrian: NO_VALUE,
usageCountBicycle: NO_VALUE,
usageCountOther: NO_VALUE,
};

content.push(newItem);
continue;
}

usageCountGrid.forEach((row: any) => {
content.push({
municipality: item.applicantName || NO_VALUE,
projectName:
submission.s4Container?.s4ProjectTitle ||
submission.s3Container?.s3ProjectTitle ||
NO_VALUE,
fundingYear: getFundingYear(
submission.s10Container?.s10ProjectMangerApproverDate ||
submission.s11Container?.s11ProjectMangerApproverDate
),
usageCountDate: formatDate(row.dateCell),
usageCountTime: row.countPeriodCell || NO_VALUE,
usageCountLocation: row.stationLocationCell || NO_VALUE,
usageCountPedestrian: row.pedestrianCell,
usageCountBicycle: row.bicycleCell,
usageCountOther: row.otherCell,
});
});
}

return content;
}
}
4 changes: 3 additions & 1 deletion client/components/application/BroaderReview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ export const BroaderReview: React.FC<BroaderReviewProps> = ({
<div className='py-5'>
<Radio
horizontal
label='Does this project meet the eligibility requirements necessary to advance in the evaluation process? (manual)'
label={`Section ${
applicationType === ApplicationType.INFRASTRUCTURE_FORM ? '3' : '2'
}: Does this project meet the eligibility requirements necessary to advance in the evaluation process? (manual)`}
name='eligibilityScore'
options={[
{ label: 'Yes', value: 'yes' },
Expand Down
4 changes: 3 additions & 1 deletion client/components/application/WorkshopReview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ export const WorkshopReview: React.FC<WorkshopReviewProps> = ({
<div className='py-5'>
<Radio
horizontal
label='Does this project meet the eligibility requirements necessary to advance in the evaluation process? (manual)'
label={`Section ${
applicationType === ApplicationType.INFRASTRUCTURE_FORM ? '3' : '2'
}: Does this project meet the eligibility requirements necessary to advance in the evaluation process? (manual)`}
name='eligibilityScore'
options={[
{ label: 'Yes', value: 'yes' },
Expand Down
Loading
Loading