Skip to content

Commit

Permalink
KRP-1232 Compliance questions (#208)
Browse files Browse the repository at this point in the history
* extend request with business

* Add new middleware for business idents

* Add compliance questions types

* Make headers optional when creating business

* Add compliance questions logic

- list questions
- answer question
- mark ident as ready for review

* Fix lint

* Add business identification section

* Fix failed rebase

* Update src/app.ts

* Update src/routes/business/complianceQuestions.ts

* CR fixes

* CR fixes

---------

Co-authored-by: lera <[email protected]>
  • Loading branch information
p-janik and lera authored Nov 11, 2024
1 parent 95c9b46 commit 88b8f3b
Show file tree
Hide file tree
Showing 9 changed files with 486 additions and 7 deletions.
23 changes: 23 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,29 @@ router.get(
safeRequestHandler(businessesAPI.retrieveBusinessIdentification)
);

// COMPLIANCE QUESTIONS

router.get(
"/businesses/:business_id/identifications/:business_identification_id/legal_identification/questions",
middlewares.withBusiness,
middlewares.withBusinessIdentification,
safeRequestHandler(businessesAPI.listComplianceQuestions)
);

router.post(
"/businesses/:business_id/identifications/:business_identification_id/legal_identification/questions/:question_id/answers",
middlewares.withBusiness,
middlewares.withBusinessIdentification,
safeRequestHandler(businessesAPI.answerComplianceQuestion)
);

router.patch(
"/businesses/:business_id/identifications/:business_identification_id/legal_identification/mark_as_ready",
middlewares.withBusiness,
middlewares.withBusinessIdentification,
safeRequestHandler(businessesAPI.markLegalIdentificationAsReady)
);

// COMMERCIAL REGISTRATIONS

router.get(
Expand Down
41 changes: 39 additions & 2 deletions src/helpers/middlewares.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import * as express from "express";
import HttpStatusCodes from "http-status";
import { getPerson, getBusiness } from "../db";
import { MockPerson, MockBusiness } from "./types";
import { MockPerson, MockBusiness, BusinessIdentification } from "./types";
import generateID from "./id";

export type RequestWithPerson = express.Request & { person?: MockPerson };
export type RequestWithBusiness = express.Request & { business?: MockBusiness };
export type RequestWithBusiness = express.Request & { business: MockBusiness };
export type RequestWithBusinessIdentification = RequestWithBusiness & {
businessIdentification: BusinessIdentification;
};

export const withBusiness = async (
req: RequestWithBusiness,
Expand Down Expand Up @@ -102,3 +105,37 @@ export const withAccount = async (
],
});
};

export const withBusinessIdentification = async (
req: RequestWithBusiness,
res: express.Response,
next: express.NextFunction
) => {
const { business } = req;
const businessIdentificationId =
req.params.business_identification_id ||
req.params.businessIdentificationId;

const businessIdentification = (business?.identifications || []).find(
(identification) => identification.id === businessIdentificationId
);

if (!businessIdentification) {
res.status(HttpStatusCodes.NOT_FOUND).send({
errors: [
{
id: generateID(),
status: 404,
code: "model_not_found",
title: "Model Not Found",
detail: `Couldn't find 'Solaris::BusinessIdentification' for id '${businessIdentificationId}'.`,
},
],
});
return;
}

(req as RequestWithBusinessIdentification).businessIdentification =
businessIdentification;
next();
};
16 changes: 16 additions & 0 deletions src/helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ export enum LegalIdentificationStatus {
SUCCESSFUL = "successful",
FAILED = "failed",
EXPIRED = "expired",
PENDING = "pending",
}

export type LegalRepresentativeIdentification = {
Expand Down Expand Up @@ -346,8 +347,23 @@ export type BusinessIdentification = {
legal_identification_missing_information_details?: string;
legal_representatives: LegalRepresentativeIdentificationResponse[];
legal_identification_missing_information: string[];
meta?: {
complianceQuestions?: ComplianceQuestion[];
};
};

export interface ComplianceQuestion {
question_id: string;
question_text: string;
legal_identification_id: string;
business_identification_id: string;
business_id: string;
asked_at: string;
answer_id: string | null;
answer_text: string | null;
answered_at: string | null;
}

export enum LegalRepresentativeType {
PERSON = "Person",
BUSINESS = "Business",
Expand Down
2 changes: 1 addition & 1 deletion src/routes/business/businesses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export const createBusiness = async (req, res) => {

await storeBusinessInSortedSet(business);

if (req.headers.origin) {
if (req.headers?.origin) {
await setBusinessOrigin(businessId, req.headers.origin);
}
});
Expand Down
136 changes: 136 additions & 0 deletions src/routes/business/complianceQuestions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import type { Response } from "express";
import generateID from "../../helpers/id";
import { RequestWithBusinessIdentification } from "../../helpers/middlewares";
import { fetchRandomQuestion } from "../../helpers/questionsAndAnswers";
import {
BusinessIdentificationStatus,
ComplianceQuestion,
LegalIdentificationStatus,
} from "../../helpers/types";
import { saveBusiness } from "../../db";

const QUESTION_COUNT = 2;

export const listComplianceQuestions = async (
req: RequestWithBusinessIdentification,
res: Response
) => {
const { businessIdentification, business } = req;

if (businessIdentification.meta?.complianceQuestions) {
res.status(200).send(businessIdentification.meta.complianceQuestions);
return;
}

const legalIdentificationId =
businessIdentification.legal_representatives[0].identifications?.[0]?.id;

const questions: ComplianceQuestion[] = await Promise.all(
Array.from({ length: QUESTION_COUNT }).map(async () => {
const question = await fetchRandomQuestion();

return {
question_id: generateID(),
question_text: question,
legal_identification_id: legalIdentificationId,
business_identification_id: businessIdentification.id,
business_id: business.id,
asked_at: new Date().toISOString(),
answer_id: null,
answer_text: null,
answered_at: null,
};
})
);

businessIdentification.meta = businessIdentification.meta || {};
businessIdentification.meta.complianceQuestions = questions;
await saveBusiness(business);

res.status(200).send(questions);
};

export const answerComplianceQuestion = async (
req: RequestWithBusinessIdentification,
res: Response
) => {
const { question_id: questionId } = req.params;
const { business, businessIdentification } = req;
const { text: answerText } = req.body;

const question = businessIdentification.meta?.complianceQuestions?.find(
(q) => q.question_id === questionId
);

if (!question) {
res.status(404).send({
errors: [
{
id: generateID(),
status: 404,
code: "model_not_found",
title: "Model Not Found",
detail: `Couldn't find 'ComplianceQuestion' for id '${questionId}'.`,
},
],
});
return;
}

question.answer_id = generateID();
question.answer_text = answerText;
question.answered_at = new Date().toISOString();

await saveBusiness(business);

res.status(201).send(question);
};

export const markLegalIdentificationAsReady = async (
req: RequestWithBusinessIdentification,
res: Response
) => {
const { businessIdentification, business } = req;

const allQuestionsAnswered =
businessIdentification.meta?.complianceQuestions?.every(
(question) => question.answer_id
);

if (!allQuestionsAnswered) {
res.status(400).send({
errors: [
{
id: generateID(),
status: 400,
code: "missing_answers",
title: "Missing Answers",
detail: "Not all compliance questions are answered.",
},
],
});
return;
}

businessIdentification.legal_identification_missing_information_details =
null;
businessIdentification.legal_identification_missing_information = [];
businessIdentification.legal_identification_status =
LegalIdentificationStatus.PENDING;

await saveBusiness(business);

const response = {
id: generateID(),
status: businessIdentification.status,
identification_id: businessIdentification.id,
business_id: business.id,
reason: null,
missing_information:
businessIdentification.legal_identification_missing_information,
missing_information_details:
businessIdentification.legal_identification_missing_information_details,
};

res.status(200).send(response);
};
16 changes: 12 additions & 4 deletions src/routes/business/identification.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Response } from "express";
import _ from "lodash";

import { RequestWithBusiness } from "../../helpers/middlewares";
import { saveBusiness, getPerson, savePerson } from "../../db";
import { saveBusiness, getPerson } from "../../db";
import generateID from "../../helpers/id";
import {
BusinessIdentification,
Expand Down Expand Up @@ -61,7 +62,7 @@ export const createBusinessIdentification = async (

await saveBusiness(business);

return res.status(200).send(identification);
replyWithIdentification(res, identification, 201);
};

export const retrieveBusinessIdentification = async (
Expand All @@ -88,8 +89,15 @@ export const retrieveBusinessIdentification = async (
],
};

return res.status(404).send(resp);
res.status(404).send(resp);
return;
}

return res.status(200).send(identification);
replyWithIdentification(res, identification, 200);
};

const replyWithIdentification = (
res: Response,
identification: BusinessIdentification,
status: number
) => res.status(status).send(_.omit(identification, "meta"));
1 change: 1 addition & 0 deletions src/routes/business/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./businesses";
export * from "./documents";
export * from "./beneficialOwner";
export * from "./identification";
export * from "./complianceQuestions";
39 changes: 39 additions & 0 deletions src/templates/business.html
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,45 @@ <h4>Beneficial Owners</h4>
{% endfor %}
</div>
</div>

<!-- Business Identifications -->
<div class="panel panel-default">
<div class="panel-heading">
<h4>Business Identifications</h4>
</div>
<div class="panel-body">
{% for identification in business.identifications %}
<div class="form-group">
<p><strong>Identification ID:</strong> {{ identification.id }}</p>
<p><strong>Status:</strong> {{ identification.status }}</p>
<p><strong>Method:</strong> {{ identification.method }}</p>
<p><strong>Reference:</strong> {{ identification.reference }}</p>
<p><strong>Completed At:</strong> {{ identification.completed_at }}</p>
</div>
<hr>
{% if identification.meta && identification.meta.complianceQuestions %}
<div class="panel panel-default">
<div class="panel-heading">
<h4>Compliance Questions</h4>
</div>
<div class="panel-body">
{% for question in identification.meta.complianceQuestions %}
<div class="form-group">
<p><strong>Question ID:</strong> {{ question.question_id }}</p>
<p><strong>Question Text:</strong> {{ question.question_text }}</p>
<p><strong>Asked At:</strong> {{ question.asked_at }}</p>
<p><strong>Answer ID:</strong> {{ question.answer_id }}</p>
<p><strong>Answer Text:</strong> {{ question.answer_text }}</p>
<p><strong>Answered At:</strong> {{ question.answered_at }}</p>
</div>
<hr>
{% endfor %}
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>

Expand Down
Loading

0 comments on commit 88b8f3b

Please sign in to comment.