Skip to content
Merged
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
6 changes: 3 additions & 3 deletions backend/src/database/seeds/user.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export default class UserSeeder implements Seeder {
{
id: "c34197fc-b944-4291-89ee-2e47ea77dc27",
firstName: "Zainab",
lastName: "Imadulla",
email: "zainab.imadulla@gmail.com",
lastName: "i",
email: "z@fakegmail.com",
phoneNumber: "1234567890",
},
{
Expand All @@ -23,7 +23,7 @@ export default class UserSeeder implements Seeder {
{
id: "0199e103-5452-76d7-8d4d-92e70c641bdb",
firstName: "zahra",
lastName: "wibisana",
lastName: "w",
email: "zahra.wib@example.com",
companyId: "ffc8243b-876e-4b6d-8b80-ffc73522a838",
phoneNumber: "1234567890",
Expand Down
6 changes: 5 additions & 1 deletion backend/src/modules/claim/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ControllerResponse } from "../../utilities/response";
import { IClaimService } from "./service";
import {
ClaimPDFGenerationResponse,
GetClaimsByCompanyInputSchema,
LinkBusinessDocumentToClaimRequest,
LinkBusinessDocumentToClaimRequestSchema,
} from "./types";
Expand Down Expand Up @@ -67,7 +68,10 @@ export class ClaimController implements IClaimController {
return ctx.json({ error: "Invalid company ID format" }, 400);
}

const claimResponse = await this.claimService.getClaimsByCompanyId(id);
const json = await ctx.req.json();
const input = GetClaimsByCompanyInputSchema.parse(json);

const claimResponse = await this.claimService.getClaimsByCompanyId(id, input);

return ctx.json(claimResponse, 200);
}
Expand Down
6 changes: 3 additions & 3 deletions backend/src/modules/claim/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Hono } from "hono";
import { DataSource } from "typeorm";
import { ClaimController, IClaimController } from "./controller";
import { CompanyTransaction, ICompanyTransaction } from "../company/transaction";
import { DocumentTransaction, IDocumentTransaction } from "../documents/transaction";
import { ClaimController, IClaimController } from "./controller";
import { ClaimService, IClaimService } from "./service";
import { ClaimTransaction, IClaimTransaction } from "./transaction";
import { CompanyTransaction, ICompanyTransaction } from "../company/transaction";

export const claimRoutes = (db: DataSource): Hono => {
const claim = new Hono();
Expand All @@ -15,7 +15,7 @@ export const claimRoutes = (db: DataSource): Hono => {
const claimService: IClaimService = new ClaimService(claimTransaction, documentTransaction, companyTransaction, db);
const claimController: IClaimController = new ClaimController(claimService);

claim.get("/company", (ctx) => claimController.getClaimByCompanyId(ctx));
claim.post("/company", (ctx) => claimController.getClaimByCompanyId(ctx));
claim.post("/", (ctx) => claimController.createClaim(ctx));
claim.get("/:id", (ctx) => claimController.getClaimById(ctx));
claim.patch("/:id/status", (ctx) => claimController.updateClaimStatus(ctx));
Expand Down
21 changes: 10 additions & 11 deletions backend/src/modules/claim/service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Boom from "@hapi/boom";
import { DataSource } from "typeorm";
import {
CreateClaimDTO,
CreateClaimResponse,
Expand All @@ -15,21 +16,19 @@ import {
UpdateClaimStatusDTO,
UpdateClaimStatusResponse,
} from "../../types/Claim";
import { DocumentTypes } from "../../types/S3Types";
import { withServiceErrorHandling } from "../../utilities/error";
import { ICompanyTransaction } from "../company/transaction";
import { DocumentTransaction, IDocumentTransaction } from "../documents/transaction";
import { S3Service } from "../s3/service";
import { IClaimTransaction } from "./transaction";
import { ClaimData, ClaimDataForPDF, ClaimPDFGenerationResponse } from "./types";
import { ClaimData, ClaimDataForPDF, ClaimPDFGenerationResponse, GetClaimsByCompanyInput } from "./types";
import { restructureClaimDataForPdf } from "./utilities/pdf-mapper";
import { DataSource } from "typeorm";
import { DocumentTypes } from "../../types/S3Types";
import { DocumentTransaction } from "../documents/transaction";
import { generatePdfWithAttachments } from "./utilities/react-pdf-handler";
import { IDocumentTransaction } from "../documents/transaction";
import { ICompanyTransaction } from "../company/transaction";

export interface IClaimService {
createClaim(payload: CreateClaimDTO, companyId: string): Promise<CreateClaimResponse>;
getClaimsByCompanyId(companyId: string): Promise<GetClaimsByCompanyIdResponse>;
getClaimsByCompanyId(companyId: string, input: GetClaimsByCompanyInput): Promise<GetClaimsByCompanyIdResponse>;
deleteClaim(payload: DeleteClaimDTO, companyId: string): Promise<DeleteClaimResponse>;
linkClaimToLineItem(payload: LinkClaimToLineItemDTO): Promise<LinkClaimToLineItemResponse>;
linkClaimToPurchaseItems(payload: LinkClaimToPurchaseDTO): Promise<LinkClaimToPurchaseResponse>;
Expand Down Expand Up @@ -87,12 +86,12 @@ export class ClaimService implements IClaimService {
);

getClaimsByCompanyId = withServiceErrorHandling(
async (companyId: string): Promise<GetClaimsByCompanyIdResponse> => {
const claim = await this.claimTransaction.getClaimsByCompanyId(companyId);
if (!claim) {
async (companyId: string, input: GetClaimsByCompanyInput): Promise<GetClaimsByCompanyIdResponse> => {
const claims = await this.claimTransaction.getClaimsByCompanyId(companyId, input);
if (!claims) {
throw new Error("Claim not found");
}
return claim;
return claims;
}
);

Expand Down
49 changes: 40 additions & 9 deletions backend/src/modules/claim/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Boom from "@hapi/boom";
import { plainToClass } from "class-transformer";
import { DataSource, In } from "typeorm";
import { Between, DataSource, FindOptionsWhere, In, LessThan, Like, MoreThan } from "typeorm";
import { Claim } from "../../entities/Claim";
import { Document } from "../../entities/Document";
import { PurchaseLineItem } from "../../entities/PurchaseLineItem";
import {
CreateClaimDTO,
Expand All @@ -24,10 +25,9 @@ import { ClaimStatusInProgressTypes, ClaimStatusType } from "../../types/ClaimSt
import { logMessageToFile } from "../../utilities/logger";
import { InvoiceTransaction } from "../invoice/transaction";
import { IPurchaseLineItemTransaction, PurchaseLineItemTransaction } from "../purchase-line-item/transaction";
import { UserTransaction } from "../user/transaction";
import { PurchaseTransaction } from "../purchase/transaction";
import { ClaimDataForPDF } from "./types";
import { Document } from "../../entities/Document";
import { UserTransaction } from "../user/transaction";
import { ClaimDataForPDF, GetClaimsByCompanyInput } from "./types";

export interface IClaimTransaction {
/**
Expand All @@ -42,7 +42,10 @@ export interface IClaimTransaction {
* @param payload ID of the claim to be fetched
* @returns Promise resolving to fetched claim or null if not found
*/
getClaimsByCompanyId(companyId: string): Promise<GetClaimsByCompanyIdResponse | null>;
getClaimsByCompanyId(
companyId: string,
input: GetClaimsByCompanyInput
): Promise<GetClaimsByCompanyIdResponse | null>;

/**
* Deletes a claim by its id
Expand Down Expand Up @@ -188,10 +191,31 @@ export class ClaimTransaction implements IClaimTransaction {
}
}

async getClaimsByCompanyId(companyId: string): Promise<GetClaimsByCompanyIdResponse | null> {
async getClaimsByCompanyId(
companyId: string,
{ filters, page, resultsPerPage }: GetClaimsByCompanyInput
): Promise<GetClaimsByCompanyIdResponse | null> {
try {
const result: Claim[] = await this.db.getRepository(Claim).find({
where: { companyId: companyId },
const options: FindOptionsWhere<Claim> = { companyId, status: ClaimStatusType.FILED };

if (filters.date) {
if (filters.date.from && filters.date.to) {
options.createdAt = Between(new Date(filters.date.from), new Date(filters.date.to));
} else if (filters.date.from) {
options.createdAt = MoreThan(new Date(filters.date.from));
} else if (filters.date.to) {
options.createdAt = LessThan(new Date(filters.date.to));
}
}

if (filters.search) {
options.name = Like(`%${filters.search}%`);
}

const [result, count] = await this.db.getRepository(Claim).findAndCount({
where: options,
skip: page * resultsPerPage,
take: resultsPerPage,
relations: {
femaDisaster: true,
selfDisaster: true,
Expand All @@ -203,7 +227,7 @@ export class ClaimTransaction implements IClaimTransaction {
},
});

return result.map((claim) => ({
const data = result.map((claim) => ({
id: claim.id,
name: claim.name,
status: claim.status,
Expand Down Expand Up @@ -239,6 +263,13 @@ export class ClaimTransaction implements IClaimTransaction {
.filter((loc) => loc !== null && loc !== undefined),
purchaseLineItemIds: claim.purchaseLineItems?.map((item) => item.id) ?? [],
}));

return {
data,
totalCount: count,
hasMore: (page + 1) * resultsPerPage < count,
hasPrevious: page > 0,
};
} catch (error) {
logMessageToFile(`Transaction error: ${error}`);
return null;
Expand Down
18 changes: 17 additions & 1 deletion backend/src/modules/claim/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { z } from "zod";
import { ClaimLocation } from "../../entities/ClaimLocation";
import { CompanyTypesEnum } from "../../entities/Company";
import { InsurancePolicy } from "../../entities/InsurancePolicy";
import { PurchaseLineItem } from "../../entities/PurchaseLineItem";
import { User } from "../../entities/User";
import { LINE_ITEM_CATEGORY_CHARS } from "../../utilities/constants";
import { SingleInsurancePolicyResponseSchema } from "../insurance-policy/types";
import { InsurancePolicy } from "../../entities/InsurancePolicy";

export const ClaimPDFGenerationResponseSchema = z.object({
url: z.url(),
Expand Down Expand Up @@ -94,6 +94,22 @@ export const ClaimDataSchema = z.object({
insuranceInfo: SingleInsurancePolicyResponseSchema.optional(),
});

export const GetClaimsByCompanyInputSchema = z.object({
filters: z.object({
date: z
.object({
from: z.iso.datetime().optional(),
to: z.iso.datetime().optional(),
})
.optional(),
search: z.string().optional(),
}),
page: z.number(),
resultsPerPage: z.number(),
});

export type GetClaimsByCompanyInput = z.infer<typeof GetClaimsByCompanyInputSchema>;

export type ClaimPDFGenerationResponse = z.infer<typeof ClaimPDFGenerationResponseSchema>;
export type UserInfo = z.infer<typeof UserInfoSchema>;
export type CompanyInfo = z.infer<typeof CompanyInfoSchema>;
Expand Down
2 changes: 1 addition & 1 deletion backend/src/modules/claim/utilities/react-pdf-handler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ function ClaimPDF({ data }: { data: ClaimData }) {

const disasterInfo = data.femaDisaster
? [
{ label: "Type of Claim", value: getIncidentTypeMeanings(data.femaDisaster.designatedIncidentTypes) },
{ label: "Incident Type", value: getIncidentTypeMeanings(data.femaDisaster.designatedIncidentTypes) },
{ label: "Incident\nDeclaration Date", value: data.femaDisaster.declarationDate.toLocaleDateString() },
{
label: "Incident\nBegin Date",
Expand Down
20 changes: 17 additions & 3 deletions backend/src/modules/openapi/claim-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ import { openApiErrorCodes } from "../../utilities/error";
import { ClaimController, IClaimController } from "../claim/controller";
import { ClaimService, IClaimService } from "../claim/service";
import { ClaimTransaction, IClaimTransaction } from "../claim/transaction";
import { ClaimPDFGenerationResponseSchema, LinkBusinessDocumentToClaimRequestSchema } from "../claim/types";
import { DocumentTransaction, IDocumentTransaction } from "../documents/transaction";
import {
ClaimPDFGenerationResponseSchema,
GetClaimsByCompanyInputSchema,
LinkBusinessDocumentToClaimRequestSchema,
} from "../claim/types";
import { CompanyTransaction, ICompanyTransaction } from "../company/transaction";
import { DocumentTransaction, IDocumentTransaction } from "../documents/transaction";

export const createOpenAPIClaimRoutes = (openApi: OpenAPIHono, db: DataSource): OpenAPIHono => {
const claimTransaction: IClaimTransaction = new ClaimTransaction(db);
Expand Down Expand Up @@ -75,10 +79,20 @@ const createClaimRoute = createRoute({
});

const getClaimsByCompanyIdRoute = createRoute({
method: "get",
method: "post",
path: "/claims/company",
summary: "Gets all the claims associated with a company",
description: "Gets all the claims for a company using a company ID",
request: {
body: {
required: true,
content: {
"application/json": {
schema: GetClaimsByCompanyInputSchema,
},
},
},
},
responses: {
200: {
content: {
Expand Down
29 changes: 19 additions & 10 deletions backend/src/tests/claim/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,20 +61,25 @@ describe("POST /claims", () => {
expect(body.updatedAt).toBeDefined();

const fetchResponse = await app.request(TESTING_PREFIX + `/claims/company`, {
method: "GET",
method: "POST",
headers: {
"Content-Type": "application/json",
companyId: companyId,
},
body: JSON.stringify({
filters: {},
page: 0,
resultsPerPage: 10,
}),
});
const fetchBody = await fetchResponse.json();

expect(fetchResponse.status).toBe(200);
expect(fetchBody.length).toBe(2);
expect(fetchBody[1].id).toBe(body.id);
expect(fetchBody[1].femaDisaster.id).toBe(requestBody.femaDisasterId);
expect(fetchBody[1].insurancePolicy.id).toBe(requestBody.insurancePolicyId);
expect(fetchBody[1].companyId).toBe(companyId);
// only includes previous filed claim b/c the one just created is not filed
expect(fetchBody.data.length).toBe(1);
expect(fetchBody.data[0].status).toBe(ClaimStatusType.FILED);
expect(fetchBody.data[0].id).not.toBe(body.id);
expect(fetchBody.data[0].companyId).toBe(companyId);
});

test("POST /claims - Success", async () => {
Expand Down Expand Up @@ -103,17 +108,21 @@ describe("POST /claims", () => {
expect(body.updatedAt).toBeDefined();

const fetchResponse = await app.request(TESTING_PREFIX + `/claims/company`, {
method: "POST",
headers: {
companyId: companyId,
},
body: JSON.stringify({
filters: {},
page: 0,
resultsPerPage: 10,
}),
});
const fetchBody = await fetchResponse.json();

expect(fetchResponse.status).toBe(200);
expect(fetchBody.length).toBe(1);
expect(fetchBody[0].id).toBe(body.id);
expect(fetchBody[0].selfDisaster.id).toBe(requestBody.selfDisasterId);
expect(fetchBody[0].companyId).toBe(companyId);
expect(fetchBody.data.length).toBe(0);
// should not be fetching the just created claim b/c it is not filed
});

test("POST /claims - Multiple in progress claims fail", async () => {
Expand Down
Loading
Loading