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
9 changes: 9 additions & 0 deletions backend/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"jsonwebtoken": "^9.0.2",
"node-cron": "^4.2.1",
"papaparse": "^5.5.3",
"pdf-lib": "^1.17.1",
"pdf-parse": "^2.4.5",
"pg": "^8.16.3",
"pg-mem": "^3.0.5",
Expand Down
12 changes: 12 additions & 0 deletions backend/src/entities/Claim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ClaimLocation } from "./ClaimLocation.js";
import { Company } from "./Company.js";
import { FemaDisaster } from "./FemaDisaster.js";
import { InsurancePolicy } from "./InsurancePolicy.js";
import { Document } from "./Document.js";
import { PurchaseLineItem } from "./PurchaseLineItem.js";
import { SelfDeclaredDisaster } from "./SelfDisaster.js";

Expand Down Expand Up @@ -83,4 +84,15 @@ export class Claim {
@ManyToOne(() => InsurancePolicy, (ip) => ip.id)
@JoinColumn({ name: "insurancePolicyId" })
insurancePolicy!: InsurancePolicy;

@ManyToMany(() => Document, (document) => document.claims)
@JoinTable({
name: "claim_uploaded_documents",
joinColumn: { name: "claimId" },
inverseJoinColumn: { name: "documentId" },
})
documents!: Document[];

@OneToMany(() => Document, (document) => document.exportedClaim)
exportedDocuments?: Relation<Document>[];
}
18 changes: 12 additions & 6 deletions backend/src/entities/Document.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryGeneratedColumn } from "typeorm";
import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import type { Relation } from "typeorm";
import { User } from "./User";
import { Company } from "./Company";
Expand Down Expand Up @@ -43,10 +43,16 @@ export class Document {
@JoinColumn({ name: "companyId" })
company!: Relation<Company>;

@Column({ name: "claimId", nullable: true })
claimId?: string;
@ManyToMany(() => Claim, (claim) => claim.documents)
// The claims this document is associated with as "additional info"
claims?: Relation<Claim>[];

@OneToOne(() => Claim)
@JoinColumn({ name: "claimId" })
claim?: Relation<Claim>;
@Column({ name: "exportedClaimID", nullable: true })
// The claim ID this document was exported from (if this is an export)
exportedClaimID?: string;

@ManyToOne(() => Claim, (claim) => claim.exportedDocuments)
@JoinColumn({ name: "exportedClaimID" })
// The claim this document is an export of
exportedClaim?: Relation<Claim>;
}
53 changes: 53 additions & 0 deletions backend/src/migrations/1764575649956-add-claim-doc-relation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class AddClaimDocRelation1764575649956 implements MigrationInterface {
name = "AddClaimDocRelation1764575649956";

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "document" DROP CONSTRAINT "FK_6d01193b2c2c5039535f28f2f9a"`);
await queryRunner.query(
`CREATE TABLE "claim_uploaded_documents" ("claimId" uuid NOT NULL, "documentId" uuid NOT NULL, CONSTRAINT "PK_cc722a588b00303d7a474d17b35" PRIMARY KEY ("claimId", "documentId"))`
);
await queryRunner.query(
`CREATE INDEX "IDX_5f4ce74d175befa86934bef8e7" ON "claim_uploaded_documents" ("claimId") `
);
await queryRunner.query(
`CREATE INDEX "IDX_ee133e8c24ee149c2fd05998e4" ON "claim_uploaded_documents" ("documentId") `
);
await queryRunner.query(`ALTER TABLE "document" DROP CONSTRAINT "REL_6d01193b2c2c5039535f28f2f9"`);
await queryRunner.query(`ALTER TABLE "document" DROP COLUMN "claimId"`);
await queryRunner.query(`ALTER TABLE "document" DROP COLUMN "createdAt"`);
await queryRunner.query(`ALTER TABLE "document" ADD "createdAt" character varying`);
await queryRunner.query(`ALTER TABLE "document" DROP COLUMN "lastModified"`);
await queryRunner.query(`ALTER TABLE "document" ADD "lastModified" character varying`);
await queryRunner.query(
`ALTER TABLE "claim_uploaded_documents" ADD CONSTRAINT "FK_5f4ce74d175befa86934bef8e74" FOREIGN KEY ("claimId") REFERENCES "claim"("id") ON DELETE CASCADE ON UPDATE CASCADE`
);
await queryRunner.query(
`ALTER TABLE "claim_uploaded_documents" ADD CONSTRAINT "FK_ee133e8c24ee149c2fd05998e41" FOREIGN KEY ("documentId") REFERENCES "document"("id") ON DELETE CASCADE ON UPDATE CASCADE`
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "claim_uploaded_documents" DROP CONSTRAINT "FK_ee133e8c24ee149c2fd05998e41"`
);
await queryRunner.query(
`ALTER TABLE "claim_uploaded_documents" DROP CONSTRAINT "FK_5f4ce74d175befa86934bef8e74"`
);
await queryRunner.query(`ALTER TABLE "document" DROP COLUMN "lastModified"`);
await queryRunner.query(`ALTER TABLE "document" ADD "lastModified" TIMESTAMP`);
await queryRunner.query(`ALTER TABLE "document" DROP COLUMN "createdAt"`);
await queryRunner.query(`ALTER TABLE "document" ADD "createdAt" TIMESTAMP`);
await queryRunner.query(`ALTER TABLE "document" ADD "claimId" uuid`);
await queryRunner.query(
`ALTER TABLE "document" ADD CONSTRAINT "REL_6d01193b2c2c5039535f28f2f9" UNIQUE ("claimId")`
);
await queryRunner.query(`DROP INDEX "public"."IDX_ee133e8c24ee149c2fd05998e4"`);
await queryRunner.query(`DROP INDEX "public"."IDX_5f4ce74d175befa86934bef8e7"`);
await queryRunner.query(`DROP TABLE "claim_uploaded_documents"`);
await queryRunner.query(
`ALTER TABLE "document" ADD CONSTRAINT "FK_6d01193b2c2c5039535f28f2f9a" FOREIGN KEY ("claimId") REFERENCES "claim"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class DocumentExportedFromColumn1764661281754 implements MigrationInterface {
name = 'DocumentExportedFromColumn1764661281754'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "document" ADD "exportedClaimID" uuid`);
await queryRunner.query(`ALTER TABLE "document" ADD CONSTRAINT "FK_74453c649175b5fdd4cd7cbe6fd" FOREIGN KEY ("exportedClaimID") REFERENCES "claim"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "document" DROP CONSTRAINT "FK_74453c649175b5fdd4cd7cbe6fd"`);
await queryRunner.query(`ALTER TABLE "document" DROP COLUMN "exportedClaimID"`);
}

}
22 changes: 20 additions & 2 deletions backend/src/modules/claim/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import {
import { withControllerErrorHandling } from "../../utilities/error";
import { ControllerResponse } from "../../utilities/response";
import { IClaimService } from "./service";
import { ClaimPDFGenerationResponse } from "./types";
import {
ClaimPDFGenerationResponse,
LinkBusinessDocumentToClaimRequest,
LinkBusinessDocumentToClaimRequestSchema,
} from "./types";

export interface IClaimController {
getClaimByCompanyId(_ctx: Context): ControllerResponse<TypedResponse<GetClaimsByCompanyIdResponse, 200>>;
Expand All @@ -33,15 +37,29 @@ export interface IClaimController {
createClaimPDF(_ctx: Context): ControllerResponse<TypedResponse<ClaimPDFGenerationResponse, 200>>;
getClaimById(_ctx: Context): ControllerResponse<TypedResponse<GetClaimByIdResponse, 200>>;
updateClaimStatus(_ctx: Context): ControllerResponse<TypedResponse<UpdateClaimStatusResponse, 200>>;
linkClaimToBusinessDocument(
_ctx: Context
): ControllerResponse<TypedResponse<LinkBusinessDocumentToClaimRequest, 200>>;
}

export class ClaimController {
export class ClaimController implements IClaimController {
private claimService: IClaimService;

constructor(service: IClaimService) {
this.claimService = service;
}

linkClaimToBusinessDocument = withControllerErrorHandling(
async (ctx: Context): ControllerResponse<TypedResponse<LinkBusinessDocumentToClaimRequest, 200>> => {
const json = await ctx.req.json();
const payload = LinkBusinessDocumentToClaimRequestSchema.parse(json);

await this.claimService.linkClaimToBusinessDocument(payload.claimId, payload.businessDocumentId);

return ctx.json(payload, 200);
}
);

getClaimByCompanyId = withControllerErrorHandling(
async (ctx: Context): ControllerResponse<TypedResponse<GetClaimsByCompanyIdResponse, 200>> => {
const id = ctx.get("companyId");
Expand Down
1 change: 1 addition & 0 deletions backend/src/modules/claim/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const claimRoutes = (db: DataSource): Hono => {
claim.get("/:id/line-item", (ctx) => claimController.getLinkedPurchaseLineItems(ctx));
claim.delete("/:claimId/line-item/:lineItemId", (ctx) => claimController.deletePurchaseLineItem(ctx));
claim.get("/:id/pdf", (ctx) => claimController.createClaimPDF(ctx));
claim.post("/document", (ctx) => claimController.linkClaimToBusinessDocument(ctx));

return claim;
};
20 changes: 17 additions & 3 deletions backend/src/modules/claim/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import { ClaimData, ClaimDataForPDF, ClaimPDFGenerationResponse } 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 { generatePdfToBuffer } from "./utilities/react-pdf-handler";
import { ICompanyTransaction } from "../company/transaction";

export interface IClaimService {
Expand All @@ -41,6 +42,7 @@ export interface IClaimService {
payload: UpdateClaimStatusDTO,
companyId: string
): Promise<UpdateClaimStatusResponse>;
linkClaimToBusinessDocument(claimId: string, documentId: string): Promise<void>;
}

export class ClaimService implements IClaimService {
Expand All @@ -61,6 +63,10 @@ export class ClaimService implements IClaimService {
this.db = db;
}

async linkClaimToBusinessDocument(claimId: string, documentId: string): Promise<void> {
await this.claimTransaction.linkClaimToDocument(claimId, documentId);
}

createClaim = withServiceErrorHandling(
async (payload: CreateClaimDTO, companyId: string): Promise<CreateClaimResponse> => {
if (!payload.selfDisasterId && !payload.femaDisasterId) {
Expand Down Expand Up @@ -160,7 +166,15 @@ export class ClaimService implements IClaimService {
// await generatePdfToFile(claimData);
// return { url: "test" };

const pdfBuffer = await generatePdfToBuffer(claimData);
const additionalDocuments = await this.claimTransaction.getAllDocumentsAssociatedWithClaim(claimId);

const s3Service = new S3Service(this.db, new DocumentTransaction(this.db));

const urls = await Promise.all(
additionalDocuments.map(async (doc) => s3Service.getPresignedDownloadUrl(doc.key))
);

const pdfBuffer = await generatePdfWithAttachments(claimData, urls);

const company = await this.companyTransaction.getCompanyById({ id: companyId });

Expand All @@ -174,7 +188,7 @@ export class ClaimService implements IClaimService {
key: key,
documentId: documentId,
documentType: DocumentTypes.CLAIM,
claimId: claimId,
exportedFromClaimId: claimId,
userId: userId,
companyId: companyId,
});
Expand Down
18 changes: 18 additions & 0 deletions backend/src/modules/claim/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { IPurchaseLineItemTransaction, PurchaseLineItemTransaction } from "../pu
import { UserTransaction } from "../user/transaction";
import { PurchaseTransaction } from "../purchase/transaction";
import { ClaimDataForPDF } from "./types";
import { Document } from "../../entities/Document";

export interface IClaimTransaction {
/**
Expand Down Expand Up @@ -120,6 +121,10 @@ export interface IClaimTransaction {
payload: UpdateClaimStatusDTO,
companyId: string
): Promise<UpdateClaimStatusResponse | null>;

linkClaimToDocument(claimId: string, documentId: string): Promise<void>;

getAllDocumentsAssociatedWithClaim(claimId: string): Promise<Document[]>;
}

export class ClaimTransaction implements IClaimTransaction {
Expand Down Expand Up @@ -624,4 +629,17 @@ export class ClaimTransaction implements IClaimTransaction {
return null;
}
}

async linkClaimToDocument(claimId: string, documentId: string): Promise<void> {
await this.db.manager.createQueryBuilder().relation(Claim, "documents").of(claimId).add(documentId);
}

async getAllDocumentsAssociatedWithClaim(claimId: string): Promise<Document[]> {
const claim = await this.db.getRepository(Claim).findOne({
where: { id: claimId },
relations: ["documents"],
});

return claim?.documents || [];
}
}
6 changes: 6 additions & 0 deletions backend/src/modules/claim/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ export type ClaimDataForPDF = {
insuranceInfo?: InsurancePolicy;
};

export const LinkBusinessDocumentToClaimRequestSchema = z.object({
businessDocumentId: z.string(),
claimId: z.string(),
});

export const UserInfoSchema = z.object({
firstName: z.string(),
lastName: z.string(),
Expand Down Expand Up @@ -97,3 +102,4 @@ export type SelfDisasterInfo = z.infer<typeof SelfDisasterInfoSchema>;
export type ImpactedLocation = z.infer<typeof ImpactedLocationSchema>;
export type RelevantExpense = z.infer<typeof RelevantExpenseSchema>;
export type ClaimData = z.infer<typeof ClaimDataSchema>;
export type LinkBusinessDocumentToClaimRequest = z.infer<typeof LinkBusinessDocumentToClaimRequestSchema>;
Loading
Loading