fix: map Epicor SP column names and use portal_Orders view
Inventory service now maps PascalCase SP columns (VorteqPartNum, OnHandQty, etc.) to snake_case types. Fixed non-V6 SP parameter from Customer to CUSTID. Orders service now uses the portal_Orders view instead of raw table queries with nonexistent columns. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
90ad1925e7
commit
cf9af94f7d
4 changed files with 127 additions and 159 deletions
|
|
@ -40,10 +40,7 @@ export function InventorySummaryTable({
|
||||||
'Description',
|
'Description',
|
||||||
'Plant',
|
'Plant',
|
||||||
'Warehouse',
|
'Warehouse',
|
||||||
'On Hand',
|
'On Hand Qty',
|
||||||
'Allocated',
|
|
||||||
'Available',
|
|
||||||
'UM',
|
|
||||||
];
|
];
|
||||||
const rows = filteredData.map((row) => [
|
const rows = filteredData.map((row) => [
|
||||||
row.part_num,
|
row.part_num,
|
||||||
|
|
@ -51,9 +48,6 @@ export function InventorySummaryTable({
|
||||||
row.plant,
|
row.plant,
|
||||||
row.warehouse,
|
row.warehouse,
|
||||||
row.on_hand_qty,
|
row.on_hand_qty,
|
||||||
row.allocated_qty || 0,
|
|
||||||
row.available_qty || 0,
|
|
||||||
row.um,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const csvContent = [headers, ...rows]
|
const csvContent = [headers, ...rows]
|
||||||
|
|
@ -105,10 +99,7 @@ export function InventorySummaryTable({
|
||||||
<TableHead>Description</TableHead>
|
<TableHead>Description</TableHead>
|
||||||
<TableHead>Plant</TableHead>
|
<TableHead>Plant</TableHead>
|
||||||
<TableHead>Warehouse</TableHead>
|
<TableHead>Warehouse</TableHead>
|
||||||
<TableHead className="text-right">On Hand</TableHead>
|
<TableHead className="text-right">On Hand Qty</TableHead>
|
||||||
<TableHead className="text-right">Allocated</TableHead>
|
|
||||||
<TableHead className="text-right">Available</TableHead>
|
|
||||||
<TableHead>UM</TableHead>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
|
@ -116,7 +107,7 @@ export function InventorySummaryTable({
|
||||||
<TableRow key={idx}>
|
<TableRow key={idx}>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Link
|
<Link
|
||||||
href={`/inventory/${category}/detail?part=${row.part_num}&plant=${row.plant}&warehouse=${row.warehouse}`}
|
href={`/inventory/${category}/detail?part=${encodeURIComponent(row.part_num)}&plant=${encodeURIComponent(row.plant_key || row.plant)}&warehouse=${encodeURIComponent(row.warehouse)}`}
|
||||||
className="font-medium hover:underline"
|
className="font-medium hover:underline"
|
||||||
>
|
>
|
||||||
{row.part_num}
|
{row.part_num}
|
||||||
|
|
@ -128,15 +119,8 @@ export function InventorySummaryTable({
|
||||||
<TableCell>{row.plant}</TableCell>
|
<TableCell>{row.plant}</TableCell>
|
||||||
<TableCell>{row.warehouse}</TableCell>
|
<TableCell>{row.warehouse}</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
{row.on_hand_qty.toLocaleString()}
|
{Number(row.on_hand_qty).toLocaleString()}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
|
||||||
{(row.allocated_qty || 0).toLocaleString()}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-right">
|
|
||||||
{(row.available_qty || 0).toLocaleString()}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{row.um}</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import Link from 'next/link';
|
|
||||||
import type { OrderRow } from '@/services/orders';
|
import type { OrderRow } from '@/services/orders';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
|
|
@ -33,39 +32,37 @@ export function OrdersTable({ data }: Props) {
|
||||||
const searchLower = searchTerm.toLowerCase();
|
const searchLower = searchTerm.toLowerCase();
|
||||||
return (
|
return (
|
||||||
row.order_num?.toString().includes(searchLower) ||
|
row.order_num?.toString().includes(searchLower) ||
|
||||||
row.po_num?.toLowerCase().includes(searchLower) ||
|
row.customer_po?.toLowerCase().includes(searchLower) ||
|
||||||
row.customer_part?.toLowerCase().includes(searchLower) ||
|
row.vorteq_part?.toLowerCase().includes(searchLower) ||
|
||||||
row.vorteq_part?.toLowerCase().includes(searchLower)
|
row.job_num?.toLowerCase().includes(searchLower)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleExportCSV = () => {
|
const handleExportCSV = () => {
|
||||||
const headers = [
|
const headers = [
|
||||||
'Order #',
|
'Order #',
|
||||||
'PO #',
|
'Customer PO',
|
||||||
'Order Date',
|
|
||||||
'Need By',
|
|
||||||
'Customer Part',
|
|
||||||
'Vorteq Part',
|
'Vorteq Part',
|
||||||
'Order Qty',
|
'Description',
|
||||||
'Shipped',
|
'Plant',
|
||||||
'Remaining',
|
'Warehouse',
|
||||||
'UM',
|
'Qty Completed',
|
||||||
'Status',
|
'Completion Date',
|
||||||
|
'Job #',
|
||||||
];
|
];
|
||||||
|
|
||||||
const rows = filteredData.map((row) => [
|
const rows = filteredData.map((row) => [
|
||||||
row.order_num?.toString() || '',
|
row.order_num?.toString() || '',
|
||||||
row.po_num || '',
|
row.customer_po || '',
|
||||||
row.order_date ? new Date(row.order_date).toLocaleDateString() : '',
|
|
||||||
row.need_by_date ? new Date(row.need_by_date).toLocaleDateString() : '',
|
|
||||||
row.customer_part || '',
|
|
||||||
row.vorteq_part || '',
|
row.vorteq_part || '',
|
||||||
row.order_qty?.toString() || '0',
|
row.part_description || '',
|
||||||
row.shipped_qty?.toString() || '0',
|
row.plant || '',
|
||||||
row.remaining_qty?.toString() || '0',
|
row.warehouse || '',
|
||||||
row.um || '',
|
row.qty_completed?.toString() || '0',
|
||||||
row.status || '',
|
row.completion_date
|
||||||
|
? new Date(row.completion_date).toLocaleDateString()
|
||||||
|
: '',
|
||||||
|
row.job_num || '',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const csvContent = [headers, ...rows]
|
const csvContent = [headers, ...rows]
|
||||||
|
|
@ -92,7 +89,7 @@ export function OrdersTable({ data }: Props) {
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
<Search className="absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
<Search className="absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search order #, PO, customer part, or Vorteq part..."
|
placeholder="Search order #, PO, part, or job..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="pl-8"
|
className="pl-8"
|
||||||
|
|
@ -109,22 +106,21 @@ export function OrdersTable({ data }: Props) {
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Order #</TableHead>
|
<TableHead>Order #</TableHead>
|
||||||
<TableHead>PO #</TableHead>
|
<TableHead>Customer PO</TableHead>
|
||||||
<TableHead>Order Date</TableHead>
|
|
||||||
<TableHead>Need By</TableHead>
|
|
||||||
<TableHead>Customer Part</TableHead>
|
|
||||||
<TableHead>Vorteq Part</TableHead>
|
<TableHead>Vorteq Part</TableHead>
|
||||||
<TableHead className="text-right">Qty</TableHead>
|
<TableHead>Description</TableHead>
|
||||||
<TableHead className="text-right">Shipped</TableHead>
|
<TableHead>Plant</TableHead>
|
||||||
<TableHead className="text-right">Remaining</TableHead>
|
<TableHead>Warehouse</TableHead>
|
||||||
<TableHead>Status</TableHead>
|
<TableHead className="text-right">Qty Completed</TableHead>
|
||||||
|
<TableHead>Completion Date</TableHead>
|
||||||
|
<TableHead>Job #</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{filteredData.length === 0 ? (
|
{filteredData.length === 0 ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell
|
<TableCell
|
||||||
colSpan={10}
|
colSpan={9}
|
||||||
className="text-center text-muted-foreground"
|
className="text-center text-muted-foreground"
|
||||||
>
|
>
|
||||||
No orders found
|
No orders found
|
||||||
|
|
@ -133,49 +129,27 @@ export function OrdersTable({ data }: Props) {
|
||||||
) : (
|
) : (
|
||||||
filteredData.map((row, i) => (
|
filteredData.map((row, i) => (
|
||||||
<TableRow key={i}>
|
<TableRow key={i}>
|
||||||
<TableCell>
|
<TableCell className="font-medium">
|
||||||
<Link
|
{row.order_num}
|
||||||
href={`/orders/${row.order_num}`}
|
|
||||||
className="font-medium hover:underline"
|
|
||||||
>
|
|
||||||
{row.order_num}
|
|
||||||
</Link>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{row.po_num || '-'}</TableCell>
|
<TableCell>{row.customer_po || '-'}</TableCell>
|
||||||
<TableCell>
|
|
||||||
{row.order_date
|
|
||||||
? new Date(row.order_date).toLocaleDateString()
|
|
||||||
: '-'}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{row.need_by_date
|
|
||||||
? new Date(row.need_by_date).toLocaleDateString()
|
|
||||||
: '-'}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{row.customer_part || '-'}</TableCell>
|
|
||||||
<TableCell className="font-mono">
|
<TableCell className="font-mono">
|
||||||
{row.vorteq_part || '-'}
|
{row.vorteq_part || '-'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="max-w-xs truncate">
|
||||||
{row.order_qty?.toFixed(0) || '0'}
|
{row.part_description || '-'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>{row.plant || '-'}</TableCell>
|
||||||
|
<TableCell>{row.warehouse || '-'}</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
{row.shipped_qty?.toFixed(0) || '0'}
|
{Number(row.qty_completed || 0).toLocaleString()}
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-right">
|
|
||||||
{row.remaining_qty?.toFixed(0) || '0'}
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<span
|
{row.completion_date
|
||||||
className={
|
? new Date(row.completion_date).toLocaleDateString()
|
||||||
row.open_order
|
: '-'}
|
||||||
? 'rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-800'
|
|
||||||
: 'rounded-full bg-gray-100 px-2 py-1 text-xs font-medium text-gray-800'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{row.status}
|
|
||||||
</span>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>{row.job_num || '-'}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
/**
|
/**
|
||||||
* Inventory Service
|
* Inventory Service
|
||||||
*
|
*
|
||||||
* Handles inventory data retrieval from Epicor stored procedures
|
* Handles inventory data retrieval from Epicor stored procedures.
|
||||||
|
* SP columns are PascalCase; we map them to snake_case for the UI.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { execStoredProc } from '@/lib/epicor';
|
import { execStoredProc } from '@/lib/epicor';
|
||||||
|
|
@ -10,11 +11,11 @@ export type InventorySummaryRow = {
|
||||||
part_num: string;
|
part_num: string;
|
||||||
description: string;
|
description: string;
|
||||||
plant: string;
|
plant: string;
|
||||||
|
plant_key: string;
|
||||||
warehouse: string;
|
warehouse: string;
|
||||||
on_hand_qty: number;
|
on_hand_qty: number;
|
||||||
allocated_qty: number;
|
paint_code?: string;
|
||||||
available_qty: number;
|
cust_part_num?: string;
|
||||||
um: string; // Unit of measure
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InventoryDetailRow = {
|
export type InventoryDetailRow = {
|
||||||
|
|
@ -35,9 +36,25 @@ export type InventoryDetailRow = {
|
||||||
uom?: string;
|
uom?: string;
|
||||||
receipt_date?: Date;
|
receipt_date?: Date;
|
||||||
paint_code?: string;
|
paint_code?: string;
|
||||||
[key: string]: unknown; // Allow additional fields from stored procedures
|
[key: string]: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map raw Epicor summary SP row to our normalized type
|
||||||
|
*/
|
||||||
|
function mapSummaryRow(raw: Record<string, unknown>): InventorySummaryRow {
|
||||||
|
return {
|
||||||
|
part_num: String(raw.VorteqPartNum ?? ''),
|
||||||
|
description: String(raw.VorteqPartDesc ?? ''),
|
||||||
|
plant: String(raw.Plant ?? ''),
|
||||||
|
plant_key: String(raw.PlantKey ?? ''),
|
||||||
|
warehouse: String(raw.Warehouse ?? ''),
|
||||||
|
on_hand_qty: Number(raw.OnHandQty ?? 0),
|
||||||
|
paint_code: raw.PaintCode ? String(raw.PaintCode) : undefined,
|
||||||
|
cust_part_num: raw.CustPartNum ? String(raw.CustPartNum) : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type InventoryCategory =
|
export type InventoryCategory =
|
||||||
| 'wip'
|
| 'wip'
|
||||||
| 'finished-goods'
|
| 'finished-goods'
|
||||||
|
|
@ -54,7 +71,7 @@ export async function getWorkInProgressSummary(
|
||||||
dbName: string,
|
dbName: string,
|
||||||
sub: number
|
sub: number
|
||||||
): Promise<InventorySummaryRow[]> {
|
): Promise<InventorySummaryRow[]> {
|
||||||
const result = await execStoredProc<InventorySummaryRow[]>(
|
const result = await execStoredProc<Record<string, unknown>[]>(
|
||||||
'PortalWorkInProgressInventorySummaryV6',
|
'PortalWorkInProgressInventorySummaryV6',
|
||||||
{
|
{
|
||||||
Customer: custId,
|
Customer: custId,
|
||||||
|
|
@ -63,7 +80,7 @@ export async function getWorkInProgressSummary(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
return result.map(mapSummaryRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -74,7 +91,7 @@ export async function getFinishedGoodsSummary(
|
||||||
dbName: string,
|
dbName: string,
|
||||||
sub: number
|
sub: number
|
||||||
): Promise<InventorySummaryRow[]> {
|
): Promise<InventorySummaryRow[]> {
|
||||||
const result = await execStoredProc<InventorySummaryRow[]>(
|
const result = await execStoredProc<Record<string, unknown>[]>(
|
||||||
'PortalFinishedGoodsInventorySummaryV6',
|
'PortalFinishedGoodsInventorySummaryV6',
|
||||||
{
|
{
|
||||||
Customer: custId,
|
Customer: custId,
|
||||||
|
|
@ -83,7 +100,7 @@ export async function getFinishedGoodsSummary(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
return result.map(mapSummaryRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -94,7 +111,7 @@ export async function getProcessedOtherSummary(
|
||||||
dbName: string,
|
dbName: string,
|
||||||
sub: number
|
sub: number
|
||||||
): Promise<InventorySummaryRow[]> {
|
): Promise<InventorySummaryRow[]> {
|
||||||
const result = await execStoredProc<InventorySummaryRow[]>(
|
const result = await execStoredProc<Record<string, unknown>[]>(
|
||||||
'PortalProcessedOtherInventorySummaryV6',
|
'PortalProcessedOtherInventorySummaryV6',
|
||||||
{
|
{
|
||||||
Customer: custId,
|
Customer: custId,
|
||||||
|
|
@ -103,12 +120,13 @@ export async function getProcessedOtherSummary(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
return result.map(mapSummaryRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Unprocessed inventory summary
|
* Get Unprocessed inventory summary
|
||||||
* Note: Sub-users are blocked from accessing this category
|
* Note: Sub-users are blocked from accessing this category.
|
||||||
|
* Non-V6 SPs use @CUSTID parameter.
|
||||||
*/
|
*/
|
||||||
export async function getUnprocessedSummary(
|
export async function getUnprocessedSummary(
|
||||||
custId: string,
|
custId: string,
|
||||||
|
|
@ -116,18 +134,18 @@ export async function getUnprocessedSummary(
|
||||||
isSubUser: boolean
|
isSubUser: boolean
|
||||||
): Promise<InventorySummaryRow[]> {
|
): Promise<InventorySummaryRow[]> {
|
||||||
if (isSubUser) {
|
if (isSubUser) {
|
||||||
return []; // Sub-users cannot access unprocessed inventory
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await execStoredProc<InventorySummaryRow[]>(
|
const result = await execStoredProc<Record<string, unknown>[]>(
|
||||||
'PortalUnprocessedInventorySummary',
|
'PortalUnprocessedInventorySummary',
|
||||||
{
|
{
|
||||||
Customer: custId,
|
CUSTID: custId,
|
||||||
DBNAME: dbName,
|
DBNAME: dbName,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
return result.map(mapSummaryRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -139,18 +157,18 @@ export async function getUnprocessedRRSummary(
|
||||||
isSubUser: boolean
|
isSubUser: boolean
|
||||||
): Promise<InventorySummaryRow[]> {
|
): Promise<InventorySummaryRow[]> {
|
||||||
if (isSubUser) {
|
if (isSubUser) {
|
||||||
return []; // Sub-users cannot access R&R inventory
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await execStoredProc<InventorySummaryRow[]>(
|
const result = await execStoredProc<Record<string, unknown>[]>(
|
||||||
'PortalUnprocessedInventoryRejectsAndReturnsSummary',
|
'PortalUnprocessedInventoryRejectsAndReturnsSummary',
|
||||||
{
|
{
|
||||||
Customer: custId,
|
CUSTID: custId,
|
||||||
DBNAME: dbName,
|
DBNAME: dbName,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
return result.map(mapSummaryRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -162,18 +180,18 @@ export async function getProcessedRRSummary(
|
||||||
isSubUser: boolean
|
isSubUser: boolean
|
||||||
): Promise<InventorySummaryRow[]> {
|
): Promise<InventorySummaryRow[]> {
|
||||||
if (isSubUser) {
|
if (isSubUser) {
|
||||||
return []; // Sub-users cannot access R&R inventory
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await execStoredProc<InventorySummaryRow[]>(
|
const result = await execStoredProc<Record<string, unknown>[]>(
|
||||||
'PortalProcessedInventoryRejectsAndReturnsSummary',
|
'PortalProcessedInventoryRejectsAndReturnsSummary',
|
||||||
{
|
{
|
||||||
Customer: custId,
|
CUSTID: custId,
|
||||||
DBNAME: dbName,
|
DBNAME: dbName,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
return result.map(mapSummaryRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -236,10 +254,12 @@ export async function getInventoryDetails(
|
||||||
DBNAME: dbName,
|
DBNAME: dbName,
|
||||||
};
|
};
|
||||||
|
|
||||||
// V6 procedures use Customer, non-V6 also use Customer
|
// V6 procedures use @Customer, non-V6 use @CUSTID
|
||||||
params.Customer = custId;
|
|
||||||
if (procConfig.isV6) {
|
if (procConfig.isV6) {
|
||||||
|
params.Customer = custId;
|
||||||
params.SUBUSER = sub;
|
params.SUBUSER = sub;
|
||||||
|
} else {
|
||||||
|
params.CUSTID = custId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add filters if provided (only for filtered variant)
|
// Add filters if provided (only for filtered variant)
|
||||||
|
|
|
||||||
|
|
@ -1,70 +1,60 @@
|
||||||
/**
|
/**
|
||||||
* Orders Service
|
* Orders Service
|
||||||
* Handles order data retrieval from Epicor
|
* Handles order data retrieval from Epicor using the portal_Orders view
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { execQuery } from '@/lib/epicor';
|
import { execQuery } from '@/lib/epicor';
|
||||||
|
|
||||||
export type OrderRow = {
|
export type OrderRow = {
|
||||||
order_num: number;
|
order_num: number;
|
||||||
po_num: string;
|
customer_po: string;
|
||||||
order_date: Date;
|
|
||||||
need_by_date: Date;
|
|
||||||
customer_part: string;
|
|
||||||
vorteq_part: string;
|
vorteq_part: string;
|
||||||
order_qty: number;
|
part_description: string;
|
||||||
shipped_qty: number;
|
plant: string;
|
||||||
remaining_qty: number;
|
warehouse: string;
|
||||||
um: string;
|
qty_completed: number;
|
||||||
open_order: boolean;
|
completion_date: Date;
|
||||||
status: string;
|
job_num: string;
|
||||||
ship_to_name?: string;
|
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get top 100 orders for a customer
|
* Map raw portal_Orders view row to our normalized type
|
||||||
* Special handling for HDC customer
|
*/
|
||||||
|
function mapOrderRow(raw: Record<string, unknown>): OrderRow {
|
||||||
|
return {
|
||||||
|
order_num: Number(raw.VorteqSalesOrderNum ?? 0),
|
||||||
|
customer_po: String(raw.CustomerPO ?? ''),
|
||||||
|
vorteq_part: String(raw.VorteqPartNum ?? ''),
|
||||||
|
part_description: String(raw.PartDescription ?? ''),
|
||||||
|
plant: String(raw.Plant ?? ''),
|
||||||
|
warehouse: String(raw.Warehouse ?? ''),
|
||||||
|
qty_completed: Number(raw.QuantityCompleted ?? 0),
|
||||||
|
completion_date: raw.CompletionDate as Date,
|
||||||
|
job_num: String(raw.VorteqJobNum ?? ''),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get top 100 orders for a customer using the portal_Orders view.
|
||||||
|
* HDC exception: uses portal_OrdersHDC view and maps to CustID 'HDM'.
|
||||||
*/
|
*/
|
||||||
export async function getTop100Orders(custId: string): Promise<OrderRow[]> {
|
export async function getTop100Orders(custId: string): Promise<OrderRow[]> {
|
||||||
// HDC exception: use different customer ID for second parameter
|
const viewName = custId === 'HDC' ? 'portal_OrdersHDC' : 'portal_Orders';
|
||||||
const cust2 = custId === 'HDC' ? 'HDM' : custId;
|
const queryCustId = custId === 'HDC' ? 'HDM' : custId;
|
||||||
|
|
||||||
// Query Epicor OrderHed and OrderDtl tables
|
const query = `
|
||||||
// This is a simplified version - the actual portal_Orders.sql may have more complex logic
|
SELECT TOP 100 *
|
||||||
const sql = `
|
FROM dbo.${viewName}
|
||||||
SELECT TOP 100
|
WHERE CustomerID = @CustID
|
||||||
oh.OrderNum AS order_num,
|
ORDER BY CompletionDate DESC
|
||||||
oh.PONum AS po_num,
|
|
||||||
oh.OrderDate AS order_date,
|
|
||||||
oh.NeedByDate AS need_by_date,
|
|
||||||
od.XPartNum AS customer_part,
|
|
||||||
od.PartNum AS vorteq_part,
|
|
||||||
od.OrderQty AS order_qty,
|
|
||||||
od.ShippedQty AS shipped_qty,
|
|
||||||
(od.OrderQty - od.ShippedQty) AS remaining_qty,
|
|
||||||
od.IUM AS um,
|
|
||||||
oh.OpenOrder AS open_order,
|
|
||||||
CASE
|
|
||||||
WHEN oh.OpenOrder = 1 THEN 'Open'
|
|
||||||
ELSE 'Closed'
|
|
||||||
END AS status,
|
|
||||||
st.Name AS ship_to_name
|
|
||||||
FROM Erp.OrderHed oh
|
|
||||||
INNER JOIN Erp.OrderDtl od ON oh.Company = od.Company AND oh.OrderNum = od.OrderNum
|
|
||||||
INNER JOIN Erp.Customer c ON oh.Company = c.Company AND oh.CustNum = c.CustNum
|
|
||||||
LEFT JOIN Erp.ShipTo st ON oh.Company = st.Company AND oh.ShipToNum = st.ShipToNum
|
|
||||||
WHERE c.CustID = @Cust1
|
|
||||||
OR c.CustID = @Cust2
|
|
||||||
ORDER BY oh.OrderDate DESC, oh.OrderNum DESC
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const result = await execQuery<OrderRow[]>(sql, {
|
const result = await execQuery<Record<string, unknown>[]>(query, {
|
||||||
Cust1: custId,
|
CustID: queryCustId,
|
||||||
Cust2: cust2,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result.map(mapOrderRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue