diff --git a/backend/src/lambda/README.md b/backend/src/lambda/README.md
new file mode 100644
index 00000000..39a20eb9
--- /dev/null
+++ b/backend/src/lambda/README.md
@@ -0,0 +1,33 @@
+# Lambda
+
+This is where our code for building our Lambda function lives.
+
+## Table of Contents
+
+1. [File Structure](#file-structure)
+2. [Tutorial](#tutorial)
+
+### File-Structure
+
+The template repository is laid out as follows below.
+
+```bash
+├── email-processor
+│ ├── emails
+│ └── email-template.tsx # the template for the disaster email. created using react-email
+│ └── lambda-src
+│ └── index.ts
+│ └── ses-client.ts
+│ └── build.sh # Run this to re-create lambda files
+├── README.md
+```
+
+### Tutorial
+
+The logic for the lambda function resides in `lambda-src/index.ts`. Once
+you edit this you can rebuild the `layer.zip` and `function.zip` using
+`build.sh`.
+
+Then you can either re-deploy this using out terraform functions or edit
+these straight from the aws console. If you would like to do this using
+terraform please refer to the README in `./terraform`
\ No newline at end of file
diff --git a/backend/src/modules/quickbooks/service.ts b/backend/src/modules/quickbooks/service.ts
index f13ae87f..ab56cc72 100644
--- a/backend/src/modules/quickbooks/service.ts
+++ b/backend/src/modules/quickbooks/service.ts
@@ -1,4 +1,7 @@
import dayjs from "dayjs";
+import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
+dayjs.extend(isSameOrAfter);
+
import { IQuickbooksClient } from "../../external/quickbooks/client";
import { withServiceErrorHandling } from "../../utilities/error";
import { IQuickbooksTransaction } from "./transaction";
@@ -176,11 +179,11 @@ export class QuickbooksService implements IQuickbooksService {
const now = dayjs();
- if (!session || !externalId || now.isSameOrAfter(session.refreshExpiryTimestamp)) {
+ if (!session || !externalId || now.isSameOrAfter(dayjs(session.refreshExpiryTimestamp))) {
throw Boom.unauthorized("Quickbooks session is expired");
}
- if (now.isSameOrAfter(session.accessExpiryTimestamp)) {
+ if (now.isSameOrAfter(dayjs(session.accessExpiryTimestamp))) {
session = await this.refreshQuickbooksSession({
refreshToken: session.refreshToken,
companyId: session.companyId,
@@ -198,9 +201,7 @@ export class QuickbooksService implements IQuickbooksService {
const lastImport = user.company.lastQuickBooksInvoiceImportTime;
const lastImportDate = lastImport ? dayjs(lastImport) : null;
- const {
- QueryResponse: { Invoice: invoices },
- } = await this.makeRequestToQB({
+ const response = await this.makeRequestToQB({
userId,
request: (session) =>
this.qbClient.query<{ Invoice: QBInvoice[] }>({
@@ -211,11 +212,13 @@ export class QuickbooksService implements IQuickbooksService {
: `SELECT * FROM Invoice`,
}),
});
- if (invoices === undefined) {
+ if (!response || !response.QueryResponse || !response.QueryResponse.Invoice) {
logMessageToFile("No new invoices to import");
return;
}
+ const invoices = response.QueryResponse.Invoice;
+
const createdInvoices = await this.invoiceTransaction.createOrUpdateInvoices(
invoices.map((i) => ({
companyId: user.companyId,
@@ -243,9 +246,7 @@ export class QuickbooksService implements IQuickbooksService {
const lastImport = user.company.lastQuickBooksPurchaseImportTime;
const lastImportDate = lastImport ? dayjs(lastImport) : null;
- const {
- QueryResponse: { Purchase: purchases },
- } = await this.makeRequestToQB({
+ const response = await this.makeRequestToQB({
userId,
request: (session) =>
this.qbClient.query<{ Purchase: QBPurchase[] }>({
@@ -256,20 +257,25 @@ export class QuickbooksService implements IQuickbooksService {
: `SELECT * FROM Purchase`,
}),
});
- if (purchases === undefined) {
+ if (!response || !response.QueryResponse || !response.QueryResponse.Purchase) {
+ console.log("no purchases to import");
logMessageToFile("No new purchases to import");
return;
}
+ const purchases = response.QueryResponse.Purchase;
+
const createdPurchases = await this.purchaseTransaction.createOrUpdatePurchase(
- purchases.map((p) => ({
- isRefund: p.Credit !== undefined ? p.Credit : false,
- companyId: user.companyId,
- totalAmountCents: Math.round(p.TotalAmt * 100),
- quickbooksDateCreated: p.MetaData.CreateTime,
- quickBooksId: parseInt(p.Id),
- vendor: p.EntityRef?.type === "Vendor" ? p.EntityRef.DisplayName || p.EntityRef.GivenName : undefined,
- }))
+ purchases.map((p) => {
+ return {
+ isRefund: p.Credit !== undefined ? p.Credit : false,
+ companyId: user.companyId,
+ totalAmountCents: Math.round(p.TotalAmt * 100),
+ quickbooksDateCreated: p.MetaData.CreateTime,
+ quickBooksId: parseInt(p.Id),
+ vendor: p.EntityRef?.DisplayName || p.EntityRef?.GivenName || p.EntityRef?.name || undefined,
+ };
+ })
);
const lineItemData = purchases.flatMap((i) => {
@@ -310,12 +316,12 @@ export class QuickbooksService implements IQuickbooksService {
if (!session || !externalId) {
throw Boom.unauthorized("Quickbooks session not found");
}
- if (now.isSameOrAfter(session.refreshExpiryTimestamp)) {
+ if (now.isSameOrAfter(dayjs(session.refreshExpiryTimestamp))) {
// Redirect to quickbooks auth?
throw Boom.unauthorized("Quickbooks session is expired");
}
- if (now.isSameOrAfter(session.accessExpiryTimestamp)) {
+ if (now.isSameOrAfter(dayjs(session.accessExpiryTimestamp))) {
session = await this.refreshQuickbooksSession({
refreshToken: session.refreshToken,
companyId: session.companyId,
@@ -398,10 +404,18 @@ function getPurchaseLineItems(purchase: QBPurchase) {
quickBooksId: parseInt(lineItem.Id),
type: PurchaseLineItemType.TYPICAL, // when importing, for now we mark everything as typical
description: lineItem.Description,
- category: lineItem.AccountBasedExpenseLineDetail.AccountRef.value,
+ category: getLastAccountName(lineItem.AccountBasedExpenseLineDetail.AccountRef.name),
});
}
}
return out;
}
+
+function getLastAccountName(accountPath: string | undefined): string | undefined {
+ if (!accountPath) {
+ return accountPath;
+ }
+ const parts = accountPath.split(":");
+ return parts[parts.length - 1];
+}
diff --git a/backend/src/tests/quickbooks/purchase/update.test.ts b/backend/src/tests/quickbooks/purchase/update.test.ts
index ae2dfaa4..a2949237 100644
--- a/backend/src/tests/quickbooks/purchase/update.test.ts
+++ b/backend/src/tests/quickbooks/purchase/update.test.ts
@@ -91,6 +91,7 @@ describe("inserting purcahse data", () => {
AccountBasedExpenseLineDetail: {
AccountRef: {
value: "acc-ref",
+ name: "category",
},
},
Amount: 5.5,
@@ -101,6 +102,7 @@ describe("inserting purcahse data", () => {
Credit: true,
EntityRef: {
type: "Vendor",
+ name: "name",
DisplayName: "Testing Display Name",
GivenName: "Testing given name",
},
@@ -143,7 +145,7 @@ describe("inserting purcahse data", () => {
quickBooksId: 2,
amountCents: 550,
purchaseId: purchases[0].id,
- category: "acc-ref",
+ category: "category",
description: "Testing description 2",
quickbooksDateCreated: new Date(now),
type: PurchaseLineItemType.TYPICAL,
@@ -226,7 +228,7 @@ describe("inserting purcahse data", () => {
quickBooksId: 2,
amountCents: 550,
purchaseId: purchases[0].id,
- category: "acc-ref",
+ category: null,
description: "Testing description 2",
quickbooksDateCreated: new Date(now),
type: PurchaseLineItemType.TYPICAL,
@@ -271,6 +273,7 @@ describe("inserting purcahse data", () => {
],
EntityRef: {
type: "Vendor",
+ name: "name",
GivenName: "Testing Given Name",
},
},
@@ -318,9 +321,10 @@ describe("inserting purcahse data", () => {
},
],
EntityRef: {
- type: "other",
- DisplayName: "SHOULD NOT EXIST",
- GivenName: "SHOULD NOT EXIST",
+ type: "vendor",
+ name: "name",
+ DisplayName: "name",
+ GivenName: "name",
},
},
],
@@ -339,7 +343,7 @@ describe("inserting purcahse data", () => {
id: expect.anything(),
dateCreated: expect.anything(),
lastUpdated: expect.anything(),
- vendor: null,
+ vendor: "name",
});
const lineItemsAfter = await db
@@ -363,7 +367,7 @@ describe("inserting purcahse data", () => {
quickBooksId: 2,
amountCents: 550,
purchaseId: purchasesAfter[0].id,
- category: "acc-ref",
+ category: null,
description: "Testing description 2",
quickbooksDateCreated: new Date(oneDayAgo),
type: PurchaseLineItemType.TYPICAL,
diff --git a/backend/src/types/quickbooks/purchase.ts b/backend/src/types/quickbooks/purchase.ts
index 060843e2..daebfc47 100644
--- a/backend/src/types/quickbooks/purchase.ts
+++ b/backend/src/types/quickbooks/purchase.ts
@@ -14,6 +14,7 @@ export type QBPurchase = {
type: string;
DisplayName?: string;
GivenName: string;
+ name: string;
};
};
diff --git a/backend/src/utilities/cron-jobs/FemaDisasterDataCron.ts b/backend/src/utilities/cron-jobs/FemaDisasterDataCron.ts
index 7a36c9e4..81d89a9b 100644
--- a/backend/src/utilities/cron-jobs/FemaDisasterDataCron.ts
+++ b/backend/src/utilities/cron-jobs/FemaDisasterDataCron.ts
@@ -54,7 +54,7 @@ export class FemaDisasterFetching implements CronJobHandler {
this.qbClient = new QuickbooksClient({
clientId: process.env.QUICKBOOKS_CLIENT_ID!,
clientSecret: process.env.QUICKBOOKS_CLIENT_SECRET!,
- environment: process.env.NODE_ENV === "dev" ? "sandbox" : "production",
+ environment: process.env.NODE_ENV === "production" ? "production" : "sandbox",
});
this.quickbooksService = new QuickbooksService(
diff --git a/backend/terraform/lambda.tf b/backend/terraform/lambda.tf
index f9be65bb..09ad4e26 100644
--- a/backend/terraform/lambda.tf
+++ b/backend/terraform/lambda.tf
@@ -1,3 +1,13 @@
+# Lambda Layer
+resource "aws_lambda_layer_version" "email_processor_layer" {
+ filename = "../src/lambda/email-processor/layer.zip"
+ layer_name = "${var.project_name}-email-processor-layer-${var.environment}"
+ compatible_runtimes = ["nodejs20.x"]
+ compatible_architectures = ["arm64"]
+ source_code_hash = filebase64sha256("../src/lambda/email-processor/layer.zip")
+}
+
+
# Lambda Function
resource "aws_lambda_function" "email_processor" {
filename = "../src/lambda/email-processor/function.zip"
@@ -10,6 +20,8 @@ resource "aws_lambda_function" "email_processor" {
memory_size = 128
architectures = ["arm64"]
+ layers = [aws_lambda_layer_version.email_processor_layer.arn]
+
environment {
variables = {
SES_FROM_EMAIL = var.ses_from_email
diff --git a/frontend/app/login/page.tsx b/frontend/app/login/page.tsx
index c18c0ead..685240ec 100644
--- a/frontend/app/login/page.tsx
+++ b/frontend/app/login/page.tsx
@@ -121,6 +121,7 @@ export default function LoginPage() {
disabled={status.pending}
className="max-h-[45px] w-fit bg-[var(--fuchsia)] hover:bg-pink hover:text-fuchsia text-white px-[20px] py-[12px] text-[16px]"
>
+ {status.pending ?
Error Setting Status
} diff --git a/frontend/app/signup/page.tsx b/frontend/app/signup/page.tsx index e6a9b082..10e22e4f 100644 --- a/frontend/app/signup/page.tsx +++ b/frontend/app/signup/page.tsx @@ -130,6 +130,7 @@ function SignUpContent() { disabled={status.pending} className="max-h-[45px] w-fit bg-[var(--fuchsia)] text-white hover:bg-pink hover:text-fuchsia px-[20px] py-[12px] text-[16px]" > + {status.pending ?