diff --git a/src/app/(portal)/dashboard/loading.tsx b/src/app/(portal)/dashboard/loading.tsx
deleted file mode 100644
index 804a6f4..0000000
--- a/src/app/(portal)/dashboard/loading.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Card, CardContent, CardHeader } from '@/components/ui/card';
-
-export default function DashboardLoading() {
- return (
-
-
Dashboard
-
-
- {[...Array(4)].map((_, i) => (
-
-
-
-
-
-
-
-
- ))}
-
-
- {[...Array(2)].map((_, i) => (
-
-
-
-
-
-
- {[...Array(5)].map((_, j) => (
-
- ))}
-
-
-
- ))}
-
-
-
- );
-}
diff --git a/src/app/(portal)/inventory/[category]/detail/loading.tsx b/src/app/(portal)/inventory/[category]/detail/loading.tsx
deleted file mode 100644
index c3cb890..0000000
--- a/src/app/(portal)/inventory/[category]/detail/loading.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Card, CardContent } from '@/components/ui/card';
-
-export default function InventoryDetailLoading() {
- return (
-
-
-
-
-
- {[...Array(8)].map((_, i) => (
-
- ))}
-
-
-
-
- );
-}
diff --git a/src/app/(portal)/inventory/[category]/detail/page.tsx b/src/app/(portal)/inventory/[category]/detail/page.tsx
index 7c46536..cd03587 100644
--- a/src/app/(portal)/inventory/[category]/detail/page.tsx
+++ b/src/app/(portal)/inventory/[category]/detail/page.tsx
@@ -52,11 +52,9 @@ async function InventoryDetailData({
plant?: string;
warehouse?: string;
}) {
- const [session, activeCompany, userIsSubUser] = await Promise.all([
- getQuestSession(),
- getActiveCompany(),
- isSubUser(),
- ]);
+ const session = await getQuestSession();
+ const activeCompany = await getActiveCompany();
+ const userIsSubUser = await isSubUser();
if (!session || !activeCompany) {
redirect('/select-company');
diff --git a/src/app/(portal)/inventory/finished-goods/page.tsx b/src/app/(portal)/inventory/finished-goods/page.tsx
index 4548f51..8c8fc96 100644
--- a/src/app/(portal)/inventory/finished-goods/page.tsx
+++ b/src/app/(portal)/inventory/finished-goods/page.tsx
@@ -9,29 +9,26 @@ import {
import { redirect } from 'next/navigation';
import { InventorySummaryTable } from '@/components/inventory/inventory-summary-table';
import { Card, CardContent } from '@/components/ui/card';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
async function FinishedGoodsInventoryData() {
- const [session, activeCompany, userIsSubUser] = await Promise.all([
- getQuestSession(),
- getActiveCompany(),
- isSubUser(),
- ]);
+ const session = await getQuestSession();
+ const activeCompany = await getActiveCompany();
+ const userIsSubUser = await isSubUser();
if (!session || !activeCompany) {
redirect('/select-company');
}
- const custId = activeCompany.epicor_cust_id;
const dbName = `[${process.env.PORTAL_DB_NAME || 'VorteqPortal'}]`;
const sub = userIsSubUser ? 1 : 0;
const [inventory, canShip] = await Promise.all([
- cachedQuery(
- { key: `inv:${custId}:finished-goods:summary:${sub}`, ttlSeconds: 300 },
- () => getFinishedGoodsSummary(custId, dbName, sub)
+ getFinishedGoodsSummary(
+ activeCompany.epicor_cust_id,
+ dbName,
+ sub
).catch(() => []),
hasPermission('create_shipment_request'),
]);
diff --git a/src/app/(portal)/inventory/processed-other/page.tsx b/src/app/(portal)/inventory/processed-other/page.tsx
index 8c5af7b..6572b0a 100644
--- a/src/app/(portal)/inventory/processed-other/page.tsx
+++ b/src/app/(portal)/inventory/processed-other/page.tsx
@@ -8,28 +8,25 @@ import {
import { redirect } from 'next/navigation';
import { InventorySummaryTable } from '@/components/inventory/inventory-summary-table';
import { Card, CardContent } from '@/components/ui/card';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
async function ProcessedOtherInventoryData() {
- const [session, activeCompany, userIsSubUser] = await Promise.all([
- getQuestSession(),
- getActiveCompany(),
- isSubUser(),
- ]);
+ const session = await getQuestSession();
+ const activeCompany = await getActiveCompany();
+ const userIsSubUser = await isSubUser();
if (!session || !activeCompany) {
redirect('/select-company');
}
- const custId = activeCompany.epicor_cust_id;
const dbName = `[${process.env.PORTAL_DB_NAME || 'VorteqPortal'}]`;
const sub = userIsSubUser ? 1 : 0;
- const inventory = await cachedQuery(
- { key: `inv:${custId}:processed-other:summary:${sub}`, ttlSeconds: 300 },
- () => getProcessedOtherSummary(custId, dbName, sub)
+ const inventory = await getProcessedOtherSummary(
+ activeCompany.epicor_cust_id,
+ dbName,
+ sub
).catch(() => []);
return ;
diff --git a/src/app/(portal)/inventory/processed-rr/page.tsx b/src/app/(portal)/inventory/processed-rr/page.tsx
index 174866c..8bef9a7 100644
--- a/src/app/(portal)/inventory/processed-rr/page.tsx
+++ b/src/app/(portal)/inventory/processed-rr/page.tsx
@@ -8,16 +8,13 @@ import {
import { redirect } from 'next/navigation';
import { InventorySummaryTable } from '@/components/inventory/inventory-summary-table';
import { Card, CardContent } from '@/components/ui/card';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
async function ProcessedRRInventoryData() {
- const [session, activeCompany, userIsSubUser] = await Promise.all([
- getQuestSession(),
- getActiveCompany(),
- isSubUser(),
- ]);
+ const session = await getQuestSession();
+ const activeCompany = await getActiveCompany();
+ const userIsSubUser = await isSubUser();
if (!session || !activeCompany) {
redirect('/select-company');
@@ -28,12 +25,12 @@ async function ProcessedRRInventoryData() {
redirect('/inventory');
}
- const custId = activeCompany.epicor_cust_id;
const dbName = `[${process.env.PORTAL_DB_NAME || 'VorteqPortal'}]`;
- const inventory = await cachedQuery(
- { key: `inv:${custId}:processed-rr:summary`, ttlSeconds: 300 },
- () => getProcessedRRSummary(custId, dbName, userIsSubUser)
+ const inventory = await getProcessedRRSummary(
+ activeCompany.epicor_cust_id,
+ dbName,
+ userIsSubUser
).catch(() => []);
return ;
diff --git a/src/app/(portal)/inventory/unprocessed-rr/page.tsx b/src/app/(portal)/inventory/unprocessed-rr/page.tsx
index f6d1520..f732bff 100644
--- a/src/app/(portal)/inventory/unprocessed-rr/page.tsx
+++ b/src/app/(portal)/inventory/unprocessed-rr/page.tsx
@@ -8,16 +8,13 @@ import {
import { redirect } from 'next/navigation';
import { InventorySummaryTable } from '@/components/inventory/inventory-summary-table';
import { Card, CardContent } from '@/components/ui/card';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
async function UnprocessedRRInventoryData() {
- const [session, activeCompany, userIsSubUser] = await Promise.all([
- getQuestSession(),
- getActiveCompany(),
- isSubUser(),
- ]);
+ const session = await getQuestSession();
+ const activeCompany = await getActiveCompany();
+ const userIsSubUser = await isSubUser();
if (!session || !activeCompany) {
redirect('/select-company');
@@ -28,12 +25,12 @@ async function UnprocessedRRInventoryData() {
redirect('/inventory');
}
- const custId = activeCompany.epicor_cust_id;
const dbName = `[${process.env.PORTAL_DB_NAME || 'VorteqPortal'}]`;
- const inventory = await cachedQuery(
- { key: `inv:${custId}:unprocessed-rr:summary`, ttlSeconds: 300 },
- () => getUnprocessedRRSummary(custId, dbName, userIsSubUser)
+ const inventory = await getUnprocessedRRSummary(
+ activeCompany.epicor_cust_id,
+ dbName,
+ userIsSubUser
).catch(() => []);
return ;
diff --git a/src/app/(portal)/inventory/unprocessed/page.tsx b/src/app/(portal)/inventory/unprocessed/page.tsx
index 4cb1fe2..7f4523c 100644
--- a/src/app/(portal)/inventory/unprocessed/page.tsx
+++ b/src/app/(portal)/inventory/unprocessed/page.tsx
@@ -8,16 +8,13 @@ import {
import { redirect } from 'next/navigation';
import { InventorySummaryTable } from '@/components/inventory/inventory-summary-table';
import { Card, CardContent } from '@/components/ui/card';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
async function UnprocessedInventoryData() {
- const [session, activeCompany, userIsSubUser] = await Promise.all([
- getQuestSession(),
- getActiveCompany(),
- isSubUser(),
- ]);
+ const session = await getQuestSession();
+ const activeCompany = await getActiveCompany();
+ const userIsSubUser = await isSubUser();
if (!session || !activeCompany) {
redirect('/select-company');
@@ -28,12 +25,12 @@ async function UnprocessedInventoryData() {
redirect('/inventory');
}
- const custId = activeCompany.epicor_cust_id;
const dbName = `[${process.env.PORTAL_DB_NAME || 'VorteqPortal'}]`;
- const inventory = await cachedQuery(
- { key: `inv:${custId}:unprocessed:summary`, ttlSeconds: 300 },
- () => getUnprocessedSummary(custId, dbName, userIsSubUser)
+ const inventory = await getUnprocessedSummary(
+ activeCompany.epicor_cust_id,
+ dbName,
+ userIsSubUser
).catch(() => []);
return ;
diff --git a/src/app/(portal)/inventory/wip/page.tsx b/src/app/(portal)/inventory/wip/page.tsx
index 92ff228..c9755de 100644
--- a/src/app/(portal)/inventory/wip/page.tsx
+++ b/src/app/(portal)/inventory/wip/page.tsx
@@ -6,26 +6,23 @@ import { getQuestSession, getActiveCompany, isSubUser } from '@/lib/permissions'
import { redirect } from 'next/navigation';
import { InventorySummaryTable } from '@/components/inventory/inventory-summary-table';
import { Card, CardContent } from '@/components/ui/card';
-import { cachedQuery } from '@/lib/cache';
async function WIPInventoryData() {
- const [session, activeCompany, userIsSubUser] = await Promise.all([
- getQuestSession(),
- getActiveCompany(),
- isSubUser(),
- ]);
+ const session = await getQuestSession();
+ const activeCompany = await getActiveCompany();
+ const userIsSubUser = await isSubUser();
if (!session || !activeCompany) {
redirect('/select-company');
}
- const custId = activeCompany.epicor_cust_id;
const dbName = `[${process.env.PORTAL_DB_NAME || 'VorteqPortal'}]`;
const sub = userIsSubUser ? 1 : 0;
- const inventory = await cachedQuery(
- { key: `inv:${custId}:wip:summary:${sub}`, ttlSeconds: 300 },
- () => getWorkInProgressSummary(custId, dbName, sub)
+ const inventory = await getWorkInProgressSummary(
+ activeCompany.epicor_cust_id,
+ dbName,
+ sub
).catch(() => []);
return ;
diff --git a/src/app/(portal)/orders/loading.tsx b/src/app/(portal)/orders/loading.tsx
deleted file mode 100644
index d7d0501..0000000
--- a/src/app/(portal)/orders/loading.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Card, CardContent } from '@/components/ui/card';
-
-export default function OrdersLoading() {
- return (
-
-
Orders
-
- View your most recent orders and order acknowledgements
-
-
-
-
- {[...Array(10)].map((_, i) => (
-
- ))}
-
-
-
-
- );
-}
diff --git a/src/app/(portal)/orders/page.tsx b/src/app/(portal)/orders/page.tsx
index 9d33495..e191cc8 100644
--- a/src/app/(portal)/orders/page.tsx
+++ b/src/app/(portal)/orders/page.tsx
@@ -4,29 +4,23 @@ import { getTop100Orders } from '@/services/orders';
import { getQuestSession, getActiveCompany } from '@/lib/permissions';
import { OrdersTable } from '@/components/orders/orders-table';
import { Card, CardContent } from '@/components/ui/card';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
async function OrdersData() {
- const [session, activeCompany] = await Promise.all([
- getQuestSession(),
- getActiveCompany(),
- ]);
+ const session = await getQuestSession();
+ const activeCompany = await getActiveCompany();
if (!session || !activeCompany) {
redirect('/select-company');
}
- const custId = activeCompany.epicor_cust_id;
-
- const orders = await cachedQuery(
- { key: `orders:${custId}:top100`, ttlSeconds: 300 },
- () => getTop100Orders(custId)
- ).catch((err) => {
- console.error('Failed to fetch orders:', err);
- return [];
- });
+ const orders = await getTop100Orders(activeCompany.epicor_cust_id).catch(
+ (err) => {
+ console.error('Failed to fetch orders:', err);
+ return [];
+ }
+ );
return ;
}
diff --git a/src/app/(portal)/shipments/loading.tsx b/src/app/(portal)/shipments/loading.tsx
deleted file mode 100644
index be00c7a..0000000
--- a/src/app/(portal)/shipments/loading.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Card, CardContent } from '@/components/ui/card';
-
-export default function ShipmentsLoading() {
- return (
-
-
Shipments
-
- View your most recent shipments and BOL details
-
-
-
-
- {[...Array(10)].map((_, i) => (
-
- ))}
-
-
-
-
- );
-}
diff --git a/src/app/api/available-coils/route.ts b/src/app/api/available-coils/route.ts
index 6420c1f..451f24a 100644
--- a/src/app/api/available-coils/route.ts
+++ b/src/app/api/available-coils/route.ts
@@ -1,7 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { getQuestSession, getActiveCompany, requirePermission } from '@/lib/permissions';
import { getAvailableCoils } from '@/services/available-coils';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
@@ -29,12 +28,12 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'jobNumber is required' }, { status: 400 });
}
- const custId = activeCompany.epicor_cust_id;
const dbName = `[${process.env.PORTAL_DB_NAME || 'VorteqPortal'}]`;
- const coils = await cachedQuery(
- { key: `avail-coils:${custId}:${jobNumber}`, ttlSeconds: 120 },
- () => getAvailableCoils(custId, dbName, jobNumber)
+ const coils = await getAvailableCoils(
+ activeCompany.epicor_cust_id,
+ dbName,
+ jobNumber
);
return NextResponse.json(coils);
diff --git a/src/app/api/coil-activity/coil-by-coil/route.ts b/src/app/api/coil-activity/coil-by-coil/route.ts
index d671a16..bbcb385 100644
--- a/src/app/api/coil-activity/coil-by-coil/route.ts
+++ b/src/app/api/coil-activity/coil-by-coil/route.ts
@@ -1,7 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { getQuestSession, getActiveCompany } from '@/lib/permissions';
import { getCoilByCoil } from '@/services/coil-activity';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
@@ -20,13 +19,8 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'jobNum is required' }, { status: 400 });
}
- const custId = activeCompany.epicor_cust_id;
-
try {
- const data = await cachedQuery(
- { key: `coil:${custId}:cbc:${jobNum}`, ttlSeconds: 300 },
- () => getCoilByCoil(jobNum, custId)
- );
+ const data = await getCoilByCoil(jobNum, activeCompany.epicor_cust_id);
return NextResponse.json(data);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
diff --git a/src/app/api/coil-activity/receipts/route.ts b/src/app/api/coil-activity/receipts/route.ts
index 300be5b..4a3e49c 100644
--- a/src/app/api/coil-activity/receipts/route.ts
+++ b/src/app/api/coil-activity/receipts/route.ts
@@ -1,7 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { getCoilReceipts } from '@/services/coil-activity';
import { getQuestSession, getActiveCompany } from '@/lib/permissions';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
@@ -45,12 +44,11 @@ export async function GET(request: NextRequest) {
);
}
- const custId = activeCompany.epicor_cust_id;
-
try {
- const data = await cachedQuery(
- { key: `coil:${custId}:receipts:${startDate}:${endDate}`, ttlSeconds: 300 },
- () => getCoilReceipts(custId, startDate, endDate)
+ const data = await getCoilReceipts(
+ activeCompany.epicor_cust_id,
+ startDate,
+ endDate
);
return NextResponse.json(data);
} catch (err) {
diff --git a/src/app/api/coil-activity/usage/route.ts b/src/app/api/coil-activity/usage/route.ts
index 08838f8..d98e1e0 100644
--- a/src/app/api/coil-activity/usage/route.ts
+++ b/src/app/api/coil-activity/usage/route.ts
@@ -1,7 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { getCoilUsage } from '@/services/coil-activity';
import { getQuestSession, getActiveCompany } from '@/lib/permissions';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
@@ -47,12 +46,11 @@ export async function GET(request: NextRequest) {
);
}
- const custId = activeCompany.epicor_cust_id;
-
try {
- const data = await cachedQuery(
- { key: `coil:${custId}:usage:${startDate}:${endDate}`, ttlSeconds: 300 },
- () => getCoilUsage(custId, startDate, endDate)
+ const data = await getCoilUsage(
+ activeCompany.epicor_cust_id,
+ startDate,
+ endDate
);
return NextResponse.json(data);
} catch (err) {
diff --git a/src/app/api/dashboard/route.ts b/src/app/api/dashboard/route.ts
index a521c2a..876330c 100644
--- a/src/app/api/dashboard/route.ts
+++ b/src/app/api/dashboard/route.ts
@@ -6,7 +6,6 @@ import {
} from '@/services/dashboard';
import { getQuestSession, getActiveCompany } from '@/lib/permissions';
import { getUnreadAlertCount } from '@/services/notifications';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
@@ -18,23 +17,12 @@ export async function GET() {
return NextResponse.json({ error: 'No active company' }, { status: 401 });
}
- const custId = activeCompany.epicor_cust_id;
-
try {
const [orders, shipments, inventorySummary, unreadNotifications] =
await Promise.all([
- cachedQuery(
- { key: `dash:${custId}:orders`, ttlSeconds: 180 },
- () => getRecentOrders(custId)
- ).catch(() => []),
- cachedQuery(
- { key: `dash:${custId}:shipments`, ttlSeconds: 180 },
- () => getRecentShipments(custId)
- ).catch(() => []),
- cachedQuery(
- { key: `dash:${custId}:inv-summary`, ttlSeconds: 180 },
- () => getInventorySummary(custId)
- ).catch(() => ({
+ getRecentOrders(activeCompany.epicor_cust_id).catch(() => []),
+ getRecentShipments(activeCompany.epicor_cust_id).catch(() => []),
+ getInventorySummary(activeCompany.epicor_cust_id).catch(() => ({
wip_count: 0,
finished_goods_count: 0,
unprocessed_count: 0,
diff --git a/src/app/api/inventory/details/route.ts b/src/app/api/inventory/details/route.ts
index 8950846..fc25732 100644
--- a/src/app/api/inventory/details/route.ts
+++ b/src/app/api/inventory/details/route.ts
@@ -8,7 +8,6 @@ import {
getInventoryDetails,
type InventoryCategory,
} from '@/services/inventory';
-import { cachedQuery } from '@/lib/cache';
const VALID_CATEGORIES: InventoryCategory[] = [
'wip',
@@ -61,14 +60,15 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ data: [] });
}
- const custId = activeCompany.epicor_cust_id;
const dbName = `[${process.env.PORTAL_DB_NAME || 'VorteqPortal'}]`;
const sub = userIsSubUser ? 1 : 0;
- const filterKey = [part, plant, warehouse].filter(Boolean).join(':') || 'all';
- const details = await cachedQuery(
- { key: `inv:${custId}:${category}:detail:${sub}:${filterKey}`, ttlSeconds: 300 },
- () => getInventoryDetails(category, custId, dbName, sub, { partNum: part, plant, warehouse })
+ const details = await getInventoryDetails(
+ category,
+ activeCompany.epicor_cust_id,
+ dbName,
+ sub,
+ { partNum: part, plant, warehouse }
);
return NextResponse.json({ data: details });
diff --git a/src/app/api/jobs/[jobNum]/traveler/route.ts b/src/app/api/jobs/[jobNum]/traveler/route.ts
index 77f1634..810f8b2 100644
--- a/src/app/api/jobs/[jobNum]/traveler/route.ts
+++ b/src/app/api/jobs/[jobNum]/traveler/route.ts
@@ -1,7 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { getQuestSession, getActiveCompany } from '@/lib/permissions';
import { getJobTraveler } from '@/services/jobs';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
@@ -17,13 +16,9 @@ export async function GET(
}
const { jobNum } = await params;
- const custId = activeCompany.epicor_cust_id;
try {
- const data = await cachedQuery(
- { key: `jobs:${custId}:traveler:${jobNum}`, ttlSeconds: 600 },
- () => getJobTraveler(jobNum)
- );
+ const data = await getJobTraveler(jobNum);
if (data === null) {
return NextResponse.json({ error: 'Job not found' }, { status: 404 });
diff --git a/src/app/api/jobs/status/route.ts b/src/app/api/jobs/status/route.ts
index 59bd83c..d17562f 100644
--- a/src/app/api/jobs/status/route.ts
+++ b/src/app/api/jobs/status/route.ts
@@ -1,7 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { getQuestSession, getActiveCompany } from '@/lib/permissions';
import { getJobStatusByPlant } from '@/services/jobs';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
@@ -13,13 +12,8 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'No active company' }, { status: 401 });
}
- const custId = activeCompany.epicor_cust_id;
-
try {
- const data = await cachedQuery(
- { key: `jobs:${custId}:status`, ttlSeconds: 300 },
- () => getJobStatusByPlant(custId)
- );
+ const data = await getJobStatusByPlant(activeCompany.epicor_cust_id);
return NextResponse.json(data);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
diff --git a/src/app/api/orders/[orderNum]/route.ts b/src/app/api/orders/[orderNum]/route.ts
index 0114e43..6727a22 100644
--- a/src/app/api/orders/[orderNum]/route.ts
+++ b/src/app/api/orders/[orderNum]/route.ts
@@ -1,7 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { getOrderAcknowledgement } from '@/services/orders';
import { getQuestSession, getActiveCompany } from '@/lib/permissions';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
@@ -26,12 +25,10 @@ export async function GET(
);
}
- const custId = activeCompany.epicor_cust_id;
-
try {
- const data = await cachedQuery(
- { key: `order:${custId}:ack:${orderNumInt}`, ttlSeconds: 600 },
- () => getOrderAcknowledgement(orderNumInt, custId)
+ const data = await getOrderAcknowledgement(
+ orderNumInt,
+ activeCompany.epicor_cust_id
);
if (!data) {
diff --git a/src/app/api/ship-to-addresses/route.ts b/src/app/api/ship-to-addresses/route.ts
index d76ea2c..06d02b0 100644
--- a/src/app/api/ship-to-addresses/route.ts
+++ b/src/app/api/ship-to-addresses/route.ts
@@ -1,7 +1,6 @@
import { NextResponse } from 'next/server';
import { getQuestSession, getActiveCompany, isSubUser } from '@/lib/permissions';
import { getShipToAddresses } from '@/services/ship-to-addresses';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
@@ -17,14 +16,13 @@ export async function GET() {
return NextResponse.json({ error: 'No active company' }, { status: 401 });
}
- const custId = activeCompany.epicor_cust_id;
const dbName = `[${process.env.PORTAL_DB_NAME || 'VorteqPortal'}]`;
const sub = await isSubUser();
- const subKey = sub ? 1 : 0;
- const addresses = await cachedQuery(
- { key: `shipto:${custId}:${subKey}`, ttlSeconds: 900 },
- () => getShipToAddresses(custId, dbName, sub)
+ const addresses = await getShipToAddresses(
+ activeCompany.epicor_cust_id,
+ dbName,
+ sub
);
return NextResponse.json(addresses);
diff --git a/src/app/api/shipments/[bol]/route.ts b/src/app/api/shipments/[bol]/route.ts
index f5b01f5..03f436a 100644
--- a/src/app/api/shipments/[bol]/route.ts
+++ b/src/app/api/shipments/[bol]/route.ts
@@ -1,7 +1,6 @@
import { NextResponse } from 'next/server';
import { getBOLDetail } from '@/services/shipments';
import { getQuestSession, getActiveCompany } from '@/lib/permissions';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
@@ -22,13 +21,8 @@ export async function GET(
return NextResponse.json({ error: 'Invalid BOL number' }, { status: 400 });
}
- const custId = activeCompany.epicor_cust_id;
-
try {
- const data = await cachedQuery(
- { key: `bol:${custId}:${bolNum}`, ttlSeconds: 600 },
- () => getBOLDetail(bolNum, custId)
- );
+ const data = await getBOLDetail(bolNum, activeCompany.epicor_cust_id);
if (!data) {
return NextResponse.json({ error: 'BOL not found' }, { status: 404 });
diff --git a/src/app/api/shipments/route.ts b/src/app/api/shipments/route.ts
index c138637..435176b 100644
--- a/src/app/api/shipments/route.ts
+++ b/src/app/api/shipments/route.ts
@@ -1,7 +1,6 @@
import { NextResponse } from 'next/server';
import { getTop100Shipments } from '@/services/shipments';
import { getQuestSession, getActiveCompany } from '@/lib/permissions';
-import { cachedQuery } from '@/lib/cache';
export const dynamic = 'force-dynamic';
@@ -13,13 +12,8 @@ export async function GET() {
return NextResponse.json({ error: 'No active company' }, { status: 401 });
}
- const custId = activeCompany.epicor_cust_id;
-
try {
- const shipments = await cachedQuery(
- { key: `ship:${custId}:top100`, ttlSeconds: 300 },
- () => getTop100Shipments(custId)
- );
+ const shipments = await getTop100Shipments(activeCompany.epicor_cust_id);
return NextResponse.json(shipments);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
diff --git a/src/lib/cache.ts b/src/lib/cache.ts
deleted file mode 100644
index 9663edc..0000000
--- a/src/lib/cache.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { ensureRedisConnected } from '@/lib/redis';
-
-type CachedQueryOptions = {
- key: string;
- ttlSeconds: number;
-};
-
-/**
- * Wraps any async data fetch with Redis caching.
- *
- * - On cache hit: returns parsed JSON from Redis
- * - On cache miss: calls fetchFn, stores result in Redis with TTL, returns result
- * - On any Redis error: falls through to fetchFn (graceful degradation)
- */
-export async function cachedQuery(
- options: CachedQueryOptions,
- fetchFn: () => Promise
-): Promise {
- const { key, ttlSeconds } = options;
-
- try {
- const redis = await ensureRedisConnected();
- const cached = await redis.get(key);
-
- if (cached !== null) {
- return JSON.parse(cached) as T;
- }
- } catch {
- // Redis unavailable — fall through to fetchFn
- }
-
- const result = await fetchFn();
-
- try {
- const redis = await ensureRedisConnected();
- await redis.set(key, JSON.stringify(result), { EX: ttlSeconds });
- } catch {
- // Redis unavailable — result is still returned
- }
-
- return result;
-}
-
-/**
- * Delete all keys matching a pattern (e.g. `inv:ACM:*`).
- * Uses SCAN to avoid blocking Redis.
- */
-export async function invalidatePattern(pattern: string): Promise {
- try {
- const redis = await ensureRedisConnected();
- let deleted = 0;
-
- for await (const key of redis.scanIterator({ MATCH: pattern })) {
- await redis.del(key);
- deleted++;
- }
-
- return deleted;
- } catch {
- return 0;
- }
-}
-
-/**
- * Delete a single cache key.
- */
-export async function invalidateKey(key: string): Promise {
- try {
- const redis = await ensureRedisConnected();
- const result = await redis.del(key);
- return result > 0;
- } catch {
- return false;
- }
-}
diff --git a/src/lib/epicor.ts b/src/lib/epicor.ts
index 09c9cda..e3b53df 100644
--- a/src/lib/epicor.ts
+++ b/src/lib/epicor.ts
@@ -93,15 +93,7 @@ async function getPool(): Promise {
}
// Create new connection
- connectionPromise = new sql.ConnectionPool({
- ...config,
- pool: {
- max: 15,
- min: 2,
- idleTimeoutMillis: 60000,
- acquireTimeoutMillis: 30000,
- },
- })
+ connectionPromise = new sql.ConnectionPool(config)
.connect()
.then((connectedPool) => {
pool = connectedPool;
diff --git a/src/lib/redis.ts b/src/lib/redis.ts
deleted file mode 100644
index b70a195..0000000
--- a/src/lib/redis.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { createClient, type RedisClientType } from 'redis';
-
-const globalForRedis = globalThis as unknown as {
- redis: RedisClientType | undefined;
-};
-
-function createRedisClient(): RedisClientType {
- const client = createClient({
- url: process.env.REDIS_URL || 'redis://localhost:6379',
- });
-
- client.on('error', (err) => {
- console.error('Redis client error:', err);
- });
-
- return client as RedisClientType;
-}
-
-export const redis: RedisClientType =
- globalForRedis.redis ?? createRedisClient();
-
-if (process.env.NODE_ENV !== 'production') globalForRedis.redis = redis;
-
-export async function ensureRedisConnected(): Promise {
- if (!redis.isOpen) {
- await redis.connect();
- }
- return redis;
-}
diff --git a/src/services/dashboard.ts b/src/services/dashboard.ts
index 29b61a7..84be6be 100644
--- a/src/services/dashboard.ts
+++ b/src/services/dashboard.ts
@@ -9,7 +9,8 @@
* we need into plain objects.
*/
-import { execQuery, execStoredProc, getPortalDbName } from '@/lib/epicor';
+import sql from 'mssql';
+import { execQuery, getPortalDbName } from '@/lib/epicor';
import { getTop100Shipments } from '@/services/shipments';
export type DashboardOrder = {
@@ -106,7 +107,7 @@ export async function getRecentShipments(
/**
* Get inventory summary counts for dashboard.
*
- * Calls the real portal inventory stored procedures via the shared connection pool:
+ * Calls the real portal inventory stored procedures:
* - PortalWorkInProgressInventorySummaryV6 (@Customer, @DBNAME, @SUBUSER)
* - PortalFinishedGoodsInventorySummaryV6 (@Customer, @DBNAME, @SUBUSER)
* - PortalUnprocessedInventorySummary (@CUSTID, @DBNAME)
@@ -119,54 +120,89 @@ export async function getInventorySummary(
): Promise {
const dbName = getPortalDbName();
- const [wipRows, fgRows, unprocessedRows] = await Promise.all([
- execStoredProc[]>(
- 'PortalWorkInProgressInventorySummaryV6',
- { Customer: custId, DBNAME: dbName, SUBUSER: 0 }
- ).catch(() => []),
- execStoredProc[]>(
- 'PortalFinishedGoodsInventorySummaryV6',
- { Customer: custId, DBNAME: dbName, SUBUSER: 0 }
- ).catch(() => []),
- execStoredProc[]>(
- 'PortalUnprocessedInventorySummary',
- { CUSTID: custId, DBNAME: dbName }
- ).catch(() => []),
- ]);
-
- let wipCount = 0;
- let fgCount = 0;
- let unprocessedCount = 0;
- let totalWeight = 0;
-
- for (const r of wipRows) {
- wipCount += Number(r.Rows ?? 0);
- totalWeight += Number(r.OnHandQty ?? 0);
- }
-
- for (const r of fgRows) {
- fgCount += Number(r.Rows ?? 0);
- totalWeight += Number(r.OnHandQty ?? 0);
- }
-
- for (const r of unprocessedRows) {
- unprocessedCount += Number(r.Rows ?? 0);
- totalWeight += Number(r.OnHandQty ?? 0);
- }
-
- return {
- wip_count: wipCount,
- finished_goods_count: fgCount,
- unprocessed_count: unprocessedCount,
- total_weight: totalWeight,
+ const config = {
+ server: process.env.MSSQL_HOST || '',
+ database: process.env.MSSQL_DATABASE || '',
+ user: process.env.MSSQL_USER || '',
+ password: process.env.MSSQL_PASSWORD || '',
+ port: parseInt(process.env.MSSQL_PORT || '1433', 10),
+ options: {
+ encrypt: false,
+ trustServerCertificate: true,
+ connectTimeout: 30000,
+ requestTimeout: 120000,
+ },
};
+
+ const pool = await sql.connect(config);
+ try {
+ // Run all three inventory SPs in parallel
+ const [wipResult, fgResult, unprocessedResult] = await Promise.all([
+ pool
+ .request()
+ .input('Customer', custId)
+ .input('DBNAME', dbName)
+ .input('SUBUSER', 0)
+ .execute('PortalWorkInProgressInventorySummaryV6')
+ .catch(() => null),
+ pool
+ .request()
+ .input('Customer', custId)
+ .input('DBNAME', dbName)
+ .input('SUBUSER', 0)
+ .execute('PortalFinishedGoodsInventorySummaryV6')
+ .catch(() => null),
+ pool
+ .request()
+ .input('CUSTID', custId)
+ .input('DBNAME', dbName)
+ .execute('PortalUnprocessedInventorySummary')
+ .catch(() => null),
+ ]);
+
+ // Aggregate: sum Rows for counts, sum OnHandQty for total weight
+ let wipCount = 0;
+ let fgCount = 0;
+ let unprocessedCount = 0;
+ let totalWeight = 0;
+
+ if (wipResult) {
+ for (const r of wipResult.recordset) {
+ wipCount += Number(r.Rows ?? 0);
+ totalWeight += Number(r.OnHandQty ?? 0);
+ }
+ }
+
+ if (fgResult) {
+ for (const r of fgResult.recordset) {
+ fgCount += Number(r.Rows ?? 0);
+ totalWeight += Number(r.OnHandQty ?? 0);
+ }
+ }
+
+ if (unprocessedResult) {
+ for (const r of unprocessedResult.recordset) {
+ unprocessedCount += Number(r.Rows ?? 0);
+ totalWeight += Number(r.OnHandQty ?? 0);
+ }
+ }
+
+ return {
+ wip_count: wipCount,
+ finished_goods_count: fgCount,
+ unprocessed_count: unprocessedCount,
+ total_weight: totalWeight,
+ };
+ } finally {
+ // Don't close the pool — mssql reuses it globally
+ }
}
/**
* Get unread notification count
*/
export async function getUnreadNotificationCount(
- _userId: string
+ userId: string
): Promise {
// This would query the quest_user_notification_alert_read table
// For now, return 0 as placeholder
diff --git a/src/services/shipments.ts b/src/services/shipments.ts
index 1d41345..f65aec9 100644
--- a/src/services/shipments.ts
+++ b/src/services/shipments.ts
@@ -5,7 +5,8 @@
* SP columns are PascalCase; we map them to snake_case for the UI.
*/
-import { execQuery, execStoredProc, getPortalDbName } from '@/lib/epicor';
+import sql from 'mssql';
+import { execQuery, getPortalDbName } from '@/lib/epicor';
import type { BOLData } from '@/types/shipments';
export type ShipmentRow = {
@@ -19,32 +20,56 @@ export type ShipmentRow = {
/**
* Get top 100 shipments for a customer.
*
- * Uses the shared connection pool via execStoredProc, then immediately extracts
- * only the fields we need into plain objects (RSC-safe).
+ * Bypasses execStoredProc to avoid holding 7k+ mssql row objects in scope
+ * (which causes Next.js RSC serialization to blow the stack for large customers).
+ * Instead, we query directly and immediately extract only the fields we need.
*/
export async function getTop100Shipments(
custId: string
): Promise {
const dbName = getPortalDbName();
- const result = await execStoredProc[]>(
- 'portal_GetShipmentsV1',
- { CustID: custId, DBNAME: dbName }
- );
+ const config = {
+ server: process.env.MSSQL_HOST || '',
+ database: process.env.MSSQL_DATABASE || '',
+ user: process.env.MSSQL_USER || '',
+ password: process.env.MSSQL_PASSWORD || '',
+ port: parseInt(process.env.MSSQL_PORT || '1433', 10),
+ options: {
+ encrypt: false,
+ trustServerCertificate: true,
+ connectTimeout: 30000,
+ requestTimeout: 120000,
+ },
+ };
- // Immediately extract only the fields we need into plain objects.
- // This prevents mssql row metadata from leaking into RSC serialization.
- const rows: ShipmentRow[] = result.map((r) => ({
- bol_num: String(r.BOLNum ?? ''),
- ship_date: r.ShipDate ? new Date(r.ShipDate as string).toISOString() : '',
- ship_to: String(r.ShipToLoc ?? '').replace(/, /g, '\n'),
- plant: String(r.PlantName ?? ''),
- weight: Number(r.Pounds ?? 0),
- }));
+ const pool = await sql.connect(config);
+ try {
+ const result = await pool.request()
+ .input('CustID', custId)
+ .input('DBNAME', dbName)
+ .execute('portal_GetShipmentsV1');
- // Sort by ship date descending and take top 100
- rows.sort((a, b) => b.ship_date.localeCompare(a.ship_date));
- return rows.slice(0, 100);
+ // Immediately extract only the fields we need into plain objects.
+ // This prevents mssql row metadata from leaking into RSC serialization.
+ const rows: ShipmentRow[] = [];
+ for (let i = 0; i < result.recordset.length; i++) {
+ const r = result.recordset[i];
+ rows.push({
+ bol_num: String(r.BOLNum ?? ''),
+ ship_date: r.ShipDate ? new Date(r.ShipDate as string).toISOString() : '',
+ ship_to: String(r.ShipToLoc ?? '').replace(/, /g, '\n'),
+ plant: String(r.PlantName ?? ''),
+ weight: Number(r.Pounds ?? 0),
+ });
+ }
+
+ // Sort by ship date descending and take top 100
+ rows.sort((a, b) => b.ship_date.localeCompare(a.ship_date));
+ return rows.slice(0, 100);
+ } finally {
+ // Don't close the pool — mssql reuses it globally
+ }
}
/**