From a1be396e77ce417cda322519fda5d02dacfd3f73 Mon Sep 17 00:00:00 2001 From: ZainabImadulla Date: Fri, 5 Dec 2025 14:19:28 -0500 Subject: [PATCH 1/7] fixed a bunch of quickbooks stuff --- backend/src/external/quickbooks/client.ts | 2 +- backend/src/modules/quickbooks/service.ts | 46 +++++++++++-------- backend/src/types/quickbooks/purchase.ts | 1 + .../cron-jobs/FemaDisasterDataCron.ts | 2 +- frontend/app/login/page.tsx | 2 +- frontend/app/notifications/notification.tsx | 7 ++- frontend/app/signup/page.tsx | 2 +- frontend/components/dashboard/NoDataPopup.tsx | 7 ++- 8 files changed, 39 insertions(+), 30 deletions(-) diff --git a/backend/src/external/quickbooks/client.ts b/backend/src/external/quickbooks/client.ts index ec380d1a..0f3f66eb 100644 --- a/backend/src/external/quickbooks/client.ts +++ b/backend/src/external/quickbooks/client.ts @@ -9,7 +9,7 @@ import { } from "./types"; const PROD_BASE_URL = "https://quickbooks.api.intuit.com"; -const SANDBOX_BASE_URL = "https://sandbox-quickbooks.api.intuit.com"; +const SANDBOX_BASE_URL = "https://sandbox-quickbooks.api.intuit.com"; const PROD_PRISERE_API_URL = process.env.PROD_PRISERE_API_URL; const DEV_PRISERE_API_URL = process.env.DEV_PRISERE_API_URL; diff --git a/backend/src/modules/quickbooks/service.ts b/backend/src/modules/quickbooks/service.ts index f13ae87f..a029c1ba 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, 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/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 ? : <>} Log In diff --git a/frontend/app/notifications/notification.tsx b/frontend/app/notifications/notification.tsx index f56dd2c5..5b747018 100644 --- a/frontend/app/notifications/notification.tsx +++ b/frontend/app/notifications/notification.tsx @@ -7,6 +7,7 @@ import { useMutation } from "@tanstack/react-query"; import { useState } from "react"; import { RiMore2Fill } from "react-icons/ri"; import formatDescription from "./utils"; +import { Spinner } from "@/components/ui/spinner"; interface NotificationProps { notification: NotificationType; @@ -14,7 +15,7 @@ interface NotificationProps { export default function Notification({ notification }: NotificationProps) { const [error, setError] = useState(false); const [title, setTitle] = useState(notification.notificationStatus); - const { mutate } = useMutation({ + const { mutate, isPending } = useMutation({ mutationFn: () => updateNotificationStatus(notification.id, title == "read" ? "unread" : "read"), onError: () => { setError(false); @@ -41,10 +42,12 @@ export default function Notification({ notification }: NotificationProps) {
{error &&

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 ? : <>} Sign Up
diff --git a/frontend/components/dashboard/NoDataPopup.tsx b/frontend/components/dashboard/NoDataPopup.tsx index be411e90..11a62c89 100644 --- a/frontend/components/dashboard/NoDataPopup.tsx +++ b/frontend/components/dashboard/NoDataPopup.tsx @@ -38,8 +38,7 @@ export default function NoDataPopup({ isOpen, onClose }: Props) {

- - - + + - + - {/* + {/*