Compare commits

..

4 Commits

19 changed files with 639 additions and 274 deletions

View File

@@ -1,7 +1,7 @@
vars { vars {
url: http://localhost:4200 url: http://localhost:4200
session_cookie: session_cookie:
urlv2: http://localhost:3000 urlv2: http://usmcd1vms036:3000
jwtV2: jwtV2:
userID: userID:
} }

View File

@@ -26,7 +26,8 @@ export type UserRoles = {
| "supervisor" | "supervisor"
| "manager" | "manager"
| "admin" | "admin"
| "systemAdmin"; | "systemAdmin"
| "user";
}; };
type UserRoleState = { type UserRoleState = {

View File

@@ -42,8 +42,8 @@ export default function TableNoExpand({
}); });
return ( return (
<div className="p-4"> <div className="p-4">
<ScrollArea className="w-11/12 rounded-md border whitespace-nowrap"> <Table className="table-fixed w-full">
<Table> <ScrollArea className="w-full rounded-md border whitespace-nowrap">
<TableHeader> <TableHeader>
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}> <TableRow key={headerGroup.id}>
@@ -89,27 +89,28 @@ export default function TableNoExpand({
</React.Fragment> </React.Fragment>
))} ))}
</TableBody> </TableBody>
</Table> <ScrollBar orientation="horizontal" />
<div className="flex items-center justify-end space-x-2 py-4"> </ScrollArea>
<Button </Table>
variant="outline"
size="sm" <div className="flex items-center justify-end space-x-2 py-4">
onClick={() => table.previousPage()} <Button
disabled={!table.getCanPreviousPage()} variant="outline"
> size="sm"
Previous onClick={() => table.previousPage()}
</Button> disabled={!table.getCanPreviousPage()}
<Button >
variant="outline" Previous
size="sm" </Button>
onClick={() => table.nextPage()} <Button
disabled={!table.getCanNextPage()} variant="outline"
> size="sm"
Next onClick={() => table.nextPage()}
</Button> disabled={!table.getCanNextPage()}
</div> >
<ScrollBar orientation="horizontal" /> Next
</ScrollArea> </Button>
</div>
</div> </div>
); );
} }

View File

@@ -7,7 +7,13 @@ export const Route = createFileRoute("/_old/old/(logistics)/siloAdjustments/")({
component: RouteComponent, component: RouteComponent,
beforeLoad: async () => { beforeLoad: async () => {
const auth = await checkUserAccess({ const auth = await checkUserAccess({
allowedRoles: ["systemAdmin", "technician", "admin", "manager"], allowedRoles: [
"systemAdmin",
"technician",
"admin",
"manager",
"supervisor",
],
moduleName: "siloAdjustments", // optional moduleName: "siloAdjustments", // optional
}); });

View File

@@ -1,9 +1,10 @@
//import { LstCard } from "@/components/extendedUI/LstCard"; //import { LstCard } from "@/components/extendedUI/LstCard";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { LstCard } from "@/components/ui/lstCard";
import TableNoExpand from "@/lib/tableStuff/TableNoExpand";
import { getPPOO } from "../../../-utils/querys/logistics/getPPOO"; import { getPPOO } from "../../../-utils/querys/logistics/getPPOO";
import { columns } from "../../../-utils/tableData/ppoo/ppooColumns"; import { columns } from "../../../-utils/tableData/ppoo/ppooColumns";
import { PPOOTable } from "../../../-utils/tableData/ppoo/ppooData";
//import { CircleX } from "lucide-react"; //import { CircleX } from "lucide-react";
//import { Suspense } from "react"; //import { Suspense } from "react";
//import { toast } from "sonner"; //import { toast } from "sonner";
@@ -12,7 +13,7 @@ export default function PPOO() {
//{ style = {} } //{ style = {} }
const { data, isError, isLoading } = useQuery(getPPOO()); const { data, isError, isLoading } = useQuery(getPPOO());
if (isLoading) return <div>Loading adjustmnet data...</div>; if (isLoading) return <div>Loading adjustments data...</div>;
if (isError) { if (isError) {
return ( return (
<div> <div>
@@ -28,11 +29,15 @@ export default function PPOO() {
// }; // };
return ( return (
<PPOOTable <>
columns={columns} <LstCard className="">
data={data} <TableNoExpand
//style={style} columns={columns}
/> data={data}
//style={style}
/>
</LstCard>
</>
); );
// return ( // return (
// <div style={style}> // <div style={style}>

View File

@@ -2,14 +2,16 @@ import { useQuery } from "@tanstack/react-query";
import { useNavigate, useRouterState } from "@tanstack/react-router"; import { useNavigate, useRouterState } from "@tanstack/react-router";
import { createColumnHelper } from "@tanstack/react-table"; import { createColumnHelper } from "@tanstack/react-table";
import axios from "axios"; import axios from "axios";
import { ArrowDown, ArrowUp } from "lucide-react"; import { ArrowDown, ArrowUp, Trash2 } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Select, Select,
SelectContent, SelectContent,
SelectGroup,
SelectItem, SelectItem,
SelectLabel,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
@@ -30,7 +32,6 @@ type Pallets = {
durationToMove: null; durationToMove: null;
qualityDurationToInspect: number; qualityDurationToInspect: number;
returnDurationToInspect: number; returnDurationToInspect: number;
locationMovesTo: string;
locationDropOff: string; locationDropOff: string;
palletStatus: number; palletStatus: number;
palletStatusText: string; palletStatusText: string;
@@ -40,6 +41,8 @@ type Pallets = {
add_user: string; add_user: string;
upd_date: Date; upd_date: Date;
upd_user: string; upd_user: string;
palletComplete: string;
canceled: string;
}; };
export default function QualityRequest() { export default function QualityRequest() {
@@ -50,35 +53,35 @@ export default function QualityRequest() {
const router = useRouterState(); const router = useRouterState();
const currentPath = router.location.href; const currentPath = router.location.href;
const palletCompleted = async (e: any) => { // const palletCompleted = async (e: any) => {
if (!session || !session.user) { // if (!session || !session.user) {
toast.error("You are allowed to do this unless you are logged in"); // toast.error("You are allowed to do this unless you are logged in");
navigate({ to: "/login", search: { redirect: currentPath } }); // navigate({ to: "/login", search: { redirect: currentPath } });
return; // return;
} // }
const data = { // const data = {
username: session?.user.username, // username: session?.user.username,
runningNr: Number(e.original.runningNr), // runningNr: Number(e.original.runningNr),
palletStatusText: "return", // palletStatusText: "return",
}; // };
try { // try {
const res = await axios.post("/lst/old/api/quality/newrequest", data); // const res = await axios.post("/lst/old/api/quality/newrequest", data);
//console.log(res.data); // //console.log(res.data);
if (res.data.success) { // if (res.data.success) {
toast.success(res.data.message); // toast.success(res.data.message);
refetch(); // refetch();
} // }
if (!res.data.success) { // if (!res.data.success) {
toast.error(res.data.message); // toast.error(res.data.message);
} // }
} catch (error) { // } catch (error) {
console.log(error); // console.log(error);
toast.error("Encountered and error please try again"); // toast.error("Encountered and error please try again");
} // }
}; // };
const columns = [ const columns = [
columnHelper.accessor("article", { columnHelper.accessor("article", {
@@ -166,14 +169,14 @@ export default function QualityRequest() {
); );
}, },
}), }),
columnHelper.accessor("locationMovesTo", { columnHelper.accessor("locationMovedTo", {
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
variant="ghost" variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
> >
<span className="flex flex-row gap-2">Location At Request</span> <span className="flex flex-row gap-2">Location Moved to</span>
{column.getIsSorted() === "asc" ? ( {column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" /> <ArrowUp className="ml-2 h-4 w-4" />
) : ( ) : (
@@ -227,7 +230,7 @@ export default function QualityRequest() {
); );
}, },
}), }),
columnHelper.accessor("palletRequest", { columnHelper.accessor("palletComplete", {
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
@@ -245,15 +248,78 @@ export default function QualityRequest() {
}, },
cell: ({ row }) => { cell: ({ row }) => {
// if pending // if pending
//const [p, setP] = useState(`${getValue()}`);
const okToCompleteStats = [2, 4, 5]; const okToCompleteStats = [2, 4, 5];
return ( return (
<> <>
{okToCompleteStats.includes(row.original.palletStatus) ? ( {okToCompleteStats.includes(row.original.palletStatus) ? (
<Button variant="outline" onClick={() => palletCompleted(row)}> <Select
Check Complete //value={"return to warehouse"}
</Button> onValueChange={async (value) => {
if (!session || !session.user) {
toast.error(
"You are allowed to do this unless you are logged in",
);
navigate({
to: "/login",
search: { redirect: currentPath },
});
return;
}
const data = {
username: session?.user.username,
runningNr: Number(row.original.runningNr),
palletStatusText: value,
};
try {
const res = await axios.post(
"/lst/old/api/quality/newrequest",
data,
);
//console.log(res.data);
if (res.data.success) {
toast.success(res.data.message);
refetch();
}
if (!res.data.success) {
toast.error(res.data.message);
}
} catch (error) {
console.log(error);
toast.error("Encountered and error please try again");
}
}}
>
<SelectTrigger
// className={cn(
// "w-[100px]",
// active
// ? "border-green-500 text-green-600"
// : "border-gray-400 text-gray-500",
// )}
>
<SelectValue placeholder="Complete Selection" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Location to drop off</SelectLabel>
<SelectItem value="return to warehouse">
Return to Warehouse
</SelectItem>
<SelectItem value="rework">Rework</SelectItem>
<SelectItem value="grind">Grind</SelectItem>
<SelectItem value="scrap">Scrap</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
) : ( ) : (
<span>Pending to be completed</span> <span>Pending move</span>
)} )}
</> </>
); );
@@ -262,7 +328,9 @@ export default function QualityRequest() {
]; ];
let adminColumns: any = []; let adminColumns: any = [];
if (userAccess("quality", ["systemAdmin", "admin", "supervisor"])) { if (
userAccess("quality", ["systemAdmin", "admin", "supervisor", "manager"])
) {
adminColumns = [ adminColumns = [
...columns, ...columns,
columnHelper.accessor("priority", { columnHelper.accessor("priority", {
@@ -287,7 +355,6 @@ export default function QualityRequest() {
// if pending // if pending
const [p, setP] = useState(`${getValue()}`); const [p, setP] = useState(`${getValue()}`);
console.log(getValue());
return ( return (
<> <>
<Select <Select
@@ -343,6 +410,78 @@ export default function QualityRequest() {
); );
}, },
}), }),
columnHelper.accessor("palletComplete", {
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() =>
column.toggleSorting(column.getIsSorted() === "asc")
}
>
<span className="flex flex-row gap-2">Cancel</span>
{column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" />
) : (
<ArrowDown className="ml-2 h-4 w-4" />
)}
</Button>
);
},
cell: ({ row }) => {
return (
<>
<Button
//value={"return to warehouse"}
variant="destructive"
onClick={async () => {
if (!session || !session.user) {
toast.error(
"You are allowed to do this unless you are logged in",
);
navigate({
to: "/login",
search: { redirect: currentPath },
});
return;
}
const data = {
username: session?.user.username,
runningNr: Number(row.original.runningNr),
palletStatusText: "canceled",
};
try {
const res = await axios.post(
"/lst/old/api/quality/newrequest",
data,
);
//console.log(res.data);
if (res.data.success) {
toast.success(
`${row.original.runningNr} was just canceled`,
);
refetch();
}
if (!res.data.success) {
toast.error(res.data.message);
}
} catch (error) {
console.log(error);
toast.error("Encountered and error please try again");
}
}}
>
<Trash2 className="w-[24px] h-[24px]" />
</Button>
</>
);
},
}),
]; ];
} }
if (isLoading) { if (isLoading) {

View File

@@ -46,6 +46,7 @@ export default function PPOO() {
<Button <Button
variant="ghost" variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
className="w-16 ml-1"
> >
<span className="flex flex-row gap-2">Article</span> <span className="flex flex-row gap-2">Article</span>
{column.getIsSorted() === "asc" ? ( {column.getIsSorted() === "asc" ? (
@@ -63,6 +64,7 @@ export default function PPOO() {
<Button <Button
variant="ghost" variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
className="w-16 ml-1"
> >
<span className="flex flex-row gap-2">Running Number</span> <span className="flex flex-row gap-2">Running Number</span>
{column.getIsSorted() === "asc" ? ( {column.getIsSorted() === "asc" ? (
@@ -80,6 +82,7 @@ export default function PPOO() {
<Button <Button
variant="ghost" variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
className="w-16 ml-1"
> >
<span className="flex flex-row gap-2">Location</span> <span className="flex flex-row gap-2">Location</span>
{column.getIsSorted() === "asc" ? ( {column.getIsSorted() === "asc" ? (
@@ -97,8 +100,9 @@ export default function PPOO() {
<Button <Button
variant="ghost" variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
className="w-16 ml-1"
> >
<span className="flex flex-row gap-2">Drop off at</span> <span className="flex flex-row gap-1">Drop off at</span>
{column.getIsSorted() === "asc" ? ( {column.getIsSorted() === "asc" ? (
<ArrowUp className="ml-2 h-4 w-4" /> <ArrowUp className="ml-2 h-4 w-4" />
) : ( ) : (
@@ -111,7 +115,7 @@ export default function PPOO() {
]; ];
return ( return (
<LstCard className="w-fit"> <LstCard className="w-5/6">
<CardHeader> <CardHeader>
Quality Request Card Total Pallets {filteredData.length} Quality Request Card Total Pallets {filteredData.length}
</CardHeader> </CardHeader>

View File

@@ -13,6 +13,7 @@ export const Route = createFileRoute("/_old/old/quality/")({
"admin", "admin",
"manager", "manager",
"supervisor", "supervisor",
"user",
], ],
moduleName: "quality", // optional moduleName: "quality", // optional
}); });

View File

@@ -9,11 +9,14 @@ import {
import { db } from "../../../../../database/dbclient.js"; import { db } from "../../../../../database/dbclient.js";
import { invHistoricalData } from "../../../../../database/schema/historicalINV.js"; import { invHistoricalData } from "../../../../../database/schema/historicalINV.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js"; import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js";
import { query } from "../../../sqlServer/prodSqlServer.js"; import { query } from "../../../sqlServer/prodSqlServer.js";
import { import {
materialPerDay, materialPerDay,
materialPurchasesPerDay, materialPurchasesPerDay,
} from "../../../sqlServer/querys/dataMart/materialPerDay.js"; } from "../../../sqlServer/querys/dataMart/materialPerDay.js";
import { singleArticle } from "../../../sqlServer/querys/misc/singleArticle.js";
import { sendEmail } from "../sendMail.js";
import { materialPurchases } from "./materialPurchases.js"; import { materialPurchases } from "./materialPurchases.js";
import { buildInventoryTimeline } from "./materialWithInv.js"; import { buildInventoryTimeline } from "./materialWithInv.js";
@@ -99,43 +102,77 @@ export default async function materialPerDayCheck() {
// purchases // purchases
const { data: p, error: pe } = (await tryCatch( const pOrders = (await materialPurchases({ startDate, endDate })) as any;
query(
materialPurchasesPerDay
.replace("[startDate]", startDate)
.replace("[endDate]", endDate),
"material check",
),
)) as any;
if (error) {
return {
success: false,
message: "Error getting the material data",
error,
};
}
if (!data.success) {
return {
success: false,
message: data.message,
data: [],
};
}
//console.log(pOrders);
const openingInventory: Record<string, number> = {}; const openingInventory: Record<string, number> = {};
const inventoryRows = await getInv(); const inventoryRows = await getInv();
for (const row of inventoryRows) { for (const row of inventoryRows) {
openingInventory[row.article] = Number(row.total_QTY) || 0; openingInventory[row.article] = Number(row.total_QTY) || 0;
} }
const materialsDemand = buildInventoryTimeline(
sumByMaterialAndWeek(data.data) as any,
openingInventory,
pOrders,
);
const { data: av, error: eav } = await tryCatch(
query(singleArticle.replace("[av]", "107"), "single article"),
);
const formattedMaterials = materialsDemand
.filter((n) => n.MaterialHumanReadableId === `${av?.data[0].article}`)
.map((i) => ({
...i,
OpeningInventory: i.OpeningInventory?.toLocaleString("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
Purchases: i.Purchases?.toLocaleString("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
Consumption: i.Consumption?.toLocaleString("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
ClosingInventory: i.ClosingInventory?.toLocaleString("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
}));
// send the email stuff
const emailSetup = {
email: "blake.matthes@alpla.com",
subject: `Material Week.`,
template: "materialPerDay",
context: {
items: formattedMaterials,
article: av?.data[0].combined,
},
};
const { data: sentEmail, error: sendEmailError } = await tryCatch(
sendEmail(emailSetup),
);
if (sendEmailError) {
createLog(
"error",
"blocking",
"notify",
"Failed to send email, will try again on next interval",
);
return {
success: false,
message: "Failed to send email, will try again on next interval",
};
}
return { return {
success: true, success: true,
message: "material data", message: "material data",
data: buildInventoryTimeline( data: formattedMaterials,
sumByMaterialAndWeek(data.data) as any,
openingInventory,
),
}; };
} }

View File

@@ -1,4 +1,7 @@
import { formatISO, parseISO, startOfWeek } from "date-fns"; import { formatISO, parseISO, startOfWeek } from "date-fns";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { query } from "../../../sqlServer/prodSqlServer.js";
import { materialPurchasesPerDay } from "../../../sqlServer/querys/dataMart/materialPerDay.js";
function toDate(val: any) { function toDate(val: any) {
if (val instanceof Date) return val; if (val instanceof Date) return val;
@@ -6,20 +9,52 @@ function toDate(val: any) {
return new Date(val); return new Date(val);
} }
export const materialPurchases = async (data: any) => { export const materialPurchases = async ({
startDate,
endDate,
}: {
startDate: string;
endDate: string;
}) => {
/** @type {Record<string, Record<string, number>>} */ /** @type {Record<string, Record<string, number>>} */
const grouped: any = {}; const grouped: any = {};
for (const r of data) { const { data: p, error } = (await tryCatch(
query(
materialPurchasesPerDay
.replace("[startDate]", startDate)
.replace("[endDate]", endDate),
"material check",
),
)) as any;
if (error) {
return {
success: false,
message: "Error getting the material data",
error,
};
}
if (!p.success) {
return {
success: false,
message: p.message,
data: [],
};
}
for (const r of p.data) {
const pOrder = String(r.purhcaseOrder);
const mat = String(r.MaterialHumanReadableId); const mat = String(r.MaterialHumanReadableId);
const d = toDate(r.CalDate); const d = toDate(r.deliveryDate);
const week = formatISO(startOfWeek(d, { weekStartsOn: 1 }), { const week = formatISO(startOfWeek(d, { weekStartsOn: 1 }), {
representation: "date", representation: "date",
}); });
grouped[mat] ??= {}; grouped[mat] ??= {};
grouped[mat][week] ??= 0; grouped[mat][week] ??= 0;
grouped[mat][week] += Number(r.DailyMaterialDemand) || 0; grouped[mat][week] += Number(r.qty) || 0;
} }
const result = []; const result = [];
@@ -29,7 +64,7 @@ export const materialPurchases = async (data: any) => {
result.push({ result.push({
MaterialHumanReadableId: mat, MaterialHumanReadableId: mat,
WeekStart: week, WeekStart: week,
WeeklyDemand: Number(total).toFixed(2), WeeklyPurchase: Number(total).toFixed(2),
}); });
} }
} }

View File

@@ -5,25 +5,51 @@ export const buildInventoryTimeline = (
WeeklyDemand: number; WeeklyDemand: number;
}>, }>,
opening: Record<string, number>, opening: Record<string, number>,
weeklyPurchases?: Array<{
MaterialHumanReadableId: string;
WeekStart: string;
WeeklyPurchase: number;
}>,
) => { ) => {
// group weekly demand by material // group weekly demand by material
const grouped: Record< const groupedDemand: Record<
string, string,
Array<{ WeekStart: string; Demand: number }> Array<{ WeekStart: string; Demand: number }>
> = {}; > = {};
for (const d of weeklyDemand) { for (const d of weeklyDemand) {
const mat = d.MaterialHumanReadableId; const mat = d.MaterialHumanReadableId;
grouped[mat] ??= []; groupedDemand[mat] ??= [];
grouped[mat].push({ groupedDemand[mat].push({
WeekStart: d.WeekStart, WeekStart: d.WeekStart,
Demand: Number(d.WeeklyDemand), Demand: Number(d.WeeklyDemand),
}); });
} }
// sort weeks chronologically per material // group weekly purchases by material
for (const mat of Object.keys(grouped)) { const groupedPurchases: Record<
grouped[mat].sort( string,
Array<{ WeekStart: string; Purchase: number }>
> = {};
if (weeklyPurchases) {
for (const p of weeklyPurchases) {
const mat = p.MaterialHumanReadableId;
groupedPurchases[mat] ??= [];
groupedPurchases[mat].push({
WeekStart: p.WeekStart,
Purchase: Number(p.WeeklyPurchase),
});
}
}
// sort both chronologically
for (const mat of Object.keys(groupedDemand)) {
groupedDemand[mat].sort(
(a, b) =>
new Date(a.WeekStart).getTime() - new Date(b.WeekStart).getTime(),
);
}
for (const mat of Object.keys(groupedPurchases)) {
groupedPurchases[mat].sort(
(a, b) => (a, b) =>
new Date(a.WeekStart).getTime() - new Date(b.WeekStart).getTime(), new Date(a.WeekStart).getTime() - new Date(b.WeekStart).getTime(),
); );
@@ -33,25 +59,30 @@ export const buildInventoryTimeline = (
MaterialHumanReadableId: string; MaterialHumanReadableId: string;
WeekStart: string; WeekStart: string;
OpeningInventory: number; OpeningInventory: number;
Purchases: number;
Consumption: number; Consumption: number;
ClosingInventory: number; ClosingInventory: number;
}> = []; }> = [];
for (const [mat, weeks] of Object.entries(grouped)) { for (const [mat, weeks] of Object.entries(groupedDemand)) {
// get starting inventory from the ERP result
let inv = opening[mat] ?? 0; let inv = opening[mat] ?? 0;
const purchasesForMaterial = groupedPurchases[mat] ?? [];
for (const w of weeks) { for (const week of weeks) {
const week = w.WeekStart; const demand = Number(week.Demand);
const demand = Number(w.Demand); const purchase =
purchasesForMaterial.find((p) => p.WeekStart === week.WeekStart)
?.Purchase ?? 0;
const openingInv = inv; const openingInv = inv;
const closingInv = openingInv - demand; const adjustedInv = openingInv + purchase;
const closingInv = adjustedInv - demand;
result.push({ result.push({
MaterialHumanReadableId: mat, MaterialHumanReadableId: mat,
WeekStart: week, WeekStart: week.WeekStart,
OpeningInventory: Number(openingInv.toFixed(2)), OpeningInventory: Number(openingInv.toFixed(2)),
Purchases: Number(purchase.toFixed(2)),
Consumption: Number(demand.toFixed(2)), Consumption: Number(demand.toFixed(2)),
ClosingInventory: Number(closingInv.toFixed(2)), ClosingInventory: Number(closingInv.toFixed(2)),
}); });

View File

@@ -1,149 +1,152 @@
import { tryCatch } from "../../../globalUtils/tryCatch.js"; import Handlebars from "handlebars";
import { db } from "../../../../database/dbclient.js";
import { settings } from "../../../../database/schema/settings.js";
import nodemailer from "nodemailer";
import type { Transporter } from "nodemailer"; import type { Transporter } from "nodemailer";
import type SMTPTransport from "nodemailer/lib/smtp-transport/index.js"; import nodemailer from "nodemailer";
import type Mail from "nodemailer/lib/mailer/index.js"; import type Mail from "nodemailer/lib/mailer/index.js";
import type { Address } from "nodemailer/lib/mailer/index.js"; import type { Address } from "nodemailer/lib/mailer/index.js";
import type SMTPTransport from "nodemailer/lib/smtp-transport/index.js";
import hbs from "nodemailer-express-handlebars";
import path from "path"; import path from "path";
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
import hbs from "nodemailer-express-handlebars";
import { promisify } from "util"; import { promisify } from "util";
import { createLog } from "../../logger/logger.js"; import { db } from "../../../../database/dbclient.js";
import { settings } from "../../../../database/schema/settings.js";
import { tryCatch } from "../../../globalUtils/tryCatch.js";
import { installed } from "../../../index.js"; import { installed } from "../../../index.js";
import { createLog } from "../../logger/logger.js";
interface HandlebarsMailOptions extends Mail.Options { interface HandlebarsMailOptions extends Mail.Options {
template: string; template: string;
context: Record<string, unknown>; // Use a generic object for context context: Record<string, unknown>; // Use a generic object for context
} }
interface EmailData { interface EmailData {
email: string; email: string;
subject: string; subject: string;
template: string; template: string;
context: []; context: [];
} }
export const sendEmail = async (data: any): Promise<any> => { export const sendEmail = async (data: any): Promise<any> => {
if (!installed) { if (!installed) {
createLog("error", "notify", "notify", "server not installed."); createLog("error", "notify", "notify", "server not installed.");
return; return;
} }
let transporter: Transporter; let transporter: Transporter;
let fromEmail: string | Address; let fromEmail: string | Address;
const { data: settingData, error: settingError } = await tryCatch( const { data: settingData, error: settingError } = await tryCatch(
db.select().from(settings) db.select().from(settings),
); );
if (settingError) { if (settingError) {
return { return {
success: false, success: false,
message: "There was an error getting the settings.", message: "There was an error getting the settings.",
settingError, settingError,
}; };
} }
// get the plantToken // get the plantToken
const server = settingData.filter((n) => n.name === "server"); const server = settingData.filter((n) => n.name === "server");
if ( if (
server[0].value === "localhost" && server[0].value === "localhostx" &&
process.env.EMAIL_USER && process.env.EMAIL_USER &&
process.env.EMAIL_PASSWORD process.env.EMAIL_PASSWORD
) { ) {
transporter = nodemailer.createTransport({ transporter = nodemailer.createTransport({
service: "gmail", service: "gmail",
auth: { host: "smtp.gmail.com",
user: process.env.EMAIL_USER, port: 465,
pass: process.env.EMAIL_PASSWORD, auth: {
}, user: process.env.EMAIL_USER,
//debug: true, pass: process.env.EMAIL_PASSWORD,
}); },
//debug: true,
});
// update the from email // update the from email
fromEmail = process.env.EMAIL_USER; fromEmail = process.env.EMAIL_USER;
} else { } else {
// convert to the correct plant token. // convert to the correct plant token.
const plantToken = settingData.filter((s) => s.name === "plantToken"); const plantToken = settingData.filter((s) => s.name === "plantToken");
let host = `${plantToken[0].value}-smtp.alpla.net`; let host = `${plantToken[0].value}-smtp.alpla.net`;
const testServers = ["test1", "test2", "test3"]; const testServers = ["test1", "test2", "test3"];
if (testServers.includes(plantToken[0].value)) { if (testServers.includes(plantToken[0].value)) {
host = "USMCD1-smtp.alpla.net"; host = "USMCD1-smtp.alpla.net";
} }
if (plantToken[0].value === "usiow2") { if (plantToken[0].value === "usiow2") {
host = "USIOW1-smtp.alpla.net"; host = "USIOW1-smtp.alpla.net";
} }
transporter = nodemailer.createTransport({ transporter = nodemailer.createTransport({
host: host, host: host,
port: 25, port: 25,
rejectUnauthorized: false, rejectUnauthorized: false,
//secure: false, //secure: false,
// auth: { // auth: {
// user: "alplaprod", // user: "alplaprod",
// pass: "obelix", // pass: "obelix",
// }, // },
debug: true, debug: true,
} as SMTPTransport.Options); } as SMTPTransport.Options);
// update the from email // update the from email
fromEmail = `noreply@alpla.com`; fromEmail = `noreply@alpla.com`;
} }
// creating the handlbar options // creating the handlbar options
const viewPath = path.resolve( const viewPath = path.resolve(
path.dirname(fileURLToPath(import.meta.url)), path.dirname(fileURLToPath(import.meta.url)),
"../utils/views/" "../utils/views/",
); );
const handlebarOptions = { const handlebarOptions = {
viewEngine: { viewEngine: {
extname: ".hbs", extname: ".hbs",
//layoutsDir: path.resolve(viewPath, "layouts"), // Path to layouts directory //layoutsDir: path.resolve(viewPath, "layouts"), // Path to layouts directory
defaultLayout: "", // Specify the default layout defaultLayout: "", // Specify the default layout
partialsDir: viewPath, partialsDir: viewPath,
}, },
viewPath: viewPath, viewPath: viewPath,
extName: ".hbs", // File extension for Handlebars templates extName: ".hbs", // File extension for Handlebars templates
}; };
transporter.use("compile", hbs(handlebarOptions)); transporter.use("compile", hbs(handlebarOptions));
const mailOptions: HandlebarsMailOptions = { const mailOptions: HandlebarsMailOptions = {
from: fromEmail, from: fromEmail,
to: data.email, to: data.email,
subject: data.subject, subject: data.subject,
//text: "You will have a reset token here and only have 30min to click the link before it expires.", //text: "You will have a reset token here and only have 30min to click the link before it expires.",
//html: emailTemplate("BlakesTest", "This is an example with css"), //html: emailTemplate("BlakesTest", "This is an example with css"),
template: data.template, // Name of the Handlebars template (e.g., 'welcome.hbs') template: data.template, // Name of the Handlebars template (e.g., 'welcome.hbs')
context: data.context, context: data.context,
}; };
// now verify and send the email // now verify and send the email
const sendMailPromise = promisify(transporter.sendMail).bind(transporter); const sendMailPromise = promisify(transporter.sendMail).bind(transporter);
try { try {
// Send email and await the result // Send email and await the result
const info = await sendMailPromise(mailOptions); const info = await sendMailPromise(mailOptions);
createLog( createLog(
"info", "info",
"notification", "notification",
"system", "system",
`Email was sent to: ${data.email}` `Email was sent to: ${data.email}`,
); );
return { success: true, message: "Email sent.", data: info }; return { success: true, message: "Email sent.", data: info };
} catch (err) { } catch (err) {
console.log(err); console.log(err);
createLog( createLog(
"error", "error",
"notification", "notification",
"system", "system",
`Error sending Email: ${JSON.stringify(err)}` `Error sending Email: ${JSON.stringify(err)}`,
); );
return { success: false, message: "Error sending email.", error: err }; return { success: false, message: "Error sending email.", error: err };
} }
}; };

View File

@@ -0,0 +1,28 @@
// an external way to creating logs
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import { apiHit } from "../../../globalUtils/apiHits.js";
import { responses } from "../../../globalUtils/routeDefs/responses.js";
import materialPerDayCheck from "../controller/materialsPerDay/materialPerDay.js";
import runTiImport from "../controller/notifications/tiIntergration.js";
const app = new OpenAPIHono({ strict: false });
app.openapi(
createRoute({
tags: ["notify"],
summary: "Manually trigger TI intergrations.",
method: "get",
path: "/materialPerDayCheck",
//middleware: authMiddleware,
responses: responses(),
}),
async (c) => {
apiHit(c, { endpoint: "/materialPerDayCheck" });
const tiImport = await materialPerDayCheck();
return c.json({
success: tiImport?.success,
message: tiImport?.message,
});
},
);
export default app;

View File

@@ -1,28 +1,27 @@
// an external way to creating logs // an external way to creating logs
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import { responses } from "../../../globalUtils/routeDefs/responses.js";
import runTiImport from "../controller/notifications/tiIntergration.js";
import { apiHit } from "../../../globalUtils/apiHits.js"; import { apiHit } from "../../../globalUtils/apiHits.js";
import { responses } from "../../../globalUtils/routeDefs/responses.js";
import runTiImport from "../controller/notifications/tiIntergration.js";
const app = new OpenAPIHono({ strict: false }); const app = new OpenAPIHono({ strict: false });
app.openapi( app.openapi(
createRoute({ createRoute({
tags: ["notify"], tags: ["notify"],
summary: "Manually trigger TI intergrations.", summary: "Manually trigger TI intergrations.",
method: "get", method: "get",
path: "/tiTrigger", path: "/tiTrigger",
//middleware: authMiddleware, //middleware: authMiddleware,
responses: responses(), responses: responses(),
}), }),
async (c) => { async (c) => {
apiHit(c, { endpoint: "/tiTrigger" }); apiHit(c, { endpoint: "/tiTrigger" });
const tiImport = await runTiImport(); const tiImport = await runTiImport();
return c.json({ return c.json({
success: tiImport?.success, success: tiImport?.success,
message: tiImport?.message, message: tiImport?.message,
}); });
} },
); );
export default app; export default app;

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
{{> styles}}
</head>
<body>
<p>All,</p>
<p>Below is the weekly material demand for Article: {{article}}.</p>
<table >
<thead>
<tr>
<th>AV</th>
<th>Week</th>
<th>Opening Inventory</th>
<th>Purchases</th>
<th>Consumption</th>
<th>Closing Inventory</th>
</tr>
</thead>
<tbody>
{{#each items}}
<tr>
<td>{{MaterialHumanReadableId}}</td>
<td>{{WeekStart}}</td>
<td>{{OpeningInventory}}</td>
<td>{{Purchases}}</td>
<td>{{Consumption}}</td>
<td>{{ClosingInventory}}</td>
</tr>
{{/each}}
</tbody>
</table>
<div>
<p>Thank you,</p>
<p>LST Team</p>
</div>
</body>
</html>

View File

@@ -13,6 +13,10 @@ export const addNewPallet = async (data: any) => {
* Post new pallets * Post new pallets
*/ */
const returnLabels = ["return to warehouse", "rework", "grind", "scrap"];
console.log(data, returnLabels.includes(data.palletStatusText));
if (parseInt(data.runningNr) <= 0) { if (parseInt(data.runningNr) <= 0) {
return { return {
success: false, success: false,
@@ -40,7 +44,7 @@ export const addNewPallet = async (data: any) => {
const palletData: any = c; const palletData: any = c;
// if the pallet exist then tell the user to check on it // if the pallet exist then tell the user to check on it
const pStatus = [1, 4, 6]; const pStatus = [1, 4, 6];
if (!data.priority) { if (!data.priority && data.palletStatusText !== "canceled") {
if (palletData && pStatus.includes(palletData[0]?.palletStatus)) { if (palletData && pStatus.includes(palletData[0]?.palletStatus)) {
return { return {
success: false, success: false,
@@ -73,22 +77,39 @@ export const addNewPallet = async (data: any) => {
upd_user: data.user, upd_user: data.user,
upd_date: sql`NOW()`, upd_date: sql`NOW()`,
}; };
} else { } else if (data.palletStatusText === "canceled") {
pData = { pData = {
warehouseAtRequest: p[0].warehouseAtRequest, warehouseAtRequest: null,
locationAtRequest: p[0].locationAtRequest, locationAtRequest: null,
warehouseMovedTo: null, warehouseMovedTo: null,
locationMovedTo: null, locationMovedTo: null,
palletStatus: data.palletStatusText === "return" ? 6 : 4, palletStatus: 5,
//durationToMove: 0, //durationToMove: 0,
palletStatusText: palletStatusText: "canceled",
data.palletStatusText === "return" ? "return" : "reactivated", qualityDurationToInspect: 0,
qualityDurationToInspect: locationDropOff: "canceled",
data.palletStatusText === "return" palletRequest: palletData[0].palletStatus + 1,
? differenceInMinutes(new Date(Date.now()), p[0].lastMove) upd_user: data.user,
: 0, upd_date: sql`NOW()`,
locationDropOff: };
data.palletStatusText === "return" ? "Return to warhouse" : "", } else {
pData = {
warehouseAtRequest: p[0].warehouseAtRequest || "no longer on stock",
locationAtRequest: p[0].locationAtRequest || "no longer on stock",
warehouseMovedTo: null,
locationMovedTo: null,
palletStatus: returnLabels.includes(data.palletStatusText) ? 6 : 4,
//durationToMove: 0,
palletStatusText: returnLabels.includes(data.palletStatusText)
? data.palletStatusText
: "reactivated",
qualityDurationToInspect: returnLabels.includes(data.palletStatusText)
? differenceInMinutes(new Date(Date.now()), p[0].lastMove)
: 0,
locationDropOff: returnLabels.includes(data.palletStatusText)
? data.palletStatusText
: "No location Checked",
palletRequest: palletData[0].palletStatus + 1, palletRequest: palletData[0].palletStatus + 1,
upd_user: data.user, upd_user: data.user,
upd_date: sql`NOW()`, upd_date: sql`NOW()`,

View File

@@ -71,21 +71,21 @@ export const qualityCycle = async () => {
const qDataPost = { const qDataPost = {
warehouseMovedTo: prodData[0]?.warehouseAtRequest, warehouseMovedTo: prodData[0]?.warehouseAtRequest,
locationMovedTo: prodData[0]?.locationAtRequest, locationMovedTo: prodData[0]?.locationAtRequest,
// how ling did it take the warhouse to originally move the pallet // how ling did it take the warehouse to originally move the pallet
durationToMove: warehouse.includes(lstQData[i].palletStatus) durationToMove: warehouse.includes(lstQData[i].palletStatus)
? differenceInMinutes( ? differenceInMinutes(
new Date(Date.now()), new Date(Date.now()),
lstQData[i].upd_date, lstQData[i].upd_date,
) )
: lstQData[i].durationToMove, : lstQData[i].durationToMove,
// how long did it take warehouse to move the pallet back agian // how long did it take warehouse to move the pallet back again
returnDurationToInspect: returnDurationToInspect:
lstQData[i].palletStatus === 7 lstQData[i].palletStatus === 7
? differenceInMinutes( ? differenceInMinutes(
new Date(Date.now()), new Date(Date.now()),
lstQData[i].upd_date, lstQData[i].upd_date,
) )
: lstQData[i].qualityDurationToInspect, : lstQData[i].returnDurationToInspect,
palletStatus: 2, palletStatus: 2,
palletStatusText: "moved", palletStatusText: "moved",
upd_date: sql`NOW()`, upd_date: sql`NOW()`,

View File

@@ -3,7 +3,7 @@
* startdate and end date should be passed over * startdate and end date should be passed over
*/ */
export const materialPerDay = ` export const materialPerDay = `
use [test3_AlplaPROD2.0_Read] use [test1_AlplaPROD2.0_Read]
DECLARE @ShiftStartHour INT = 6 DECLARE @ShiftStartHour INT = 6
declare @startDate nvarchar(max) = '[startDate]' declare @startDate nvarchar(max) = '[startDate]'
@@ -121,7 +121,7 @@ declare @endDate nvarchar(max) = '[endDate]'
SELECT SELECT
[IdBestellung] as purhcaseOrder [IdBestellung] as purhcaseOrder
,[IdArtikelVarianten] ,[IdArtikelVarianten] as MaterialHumanReadableId
,convert(date, [BestellDatum], 120) as orderDate ,convert(date, [BestellDatum], 120) as orderDate
,convert(date, [Lieferdatum], 120) as deliveryDate ,convert(date, [Lieferdatum], 120) as deliveryDate
,[BestellMenge] as qty ,[BestellMenge] as qty

View File

@@ -0,0 +1,10 @@
export const singleArticle = `
use AlplaPROD_test1
select
idartikelvarianten as article,
CONCAT(idartikelvarianten , ' - ' , Alias) as combined
from V_Artikel (nolock)
where idartikelvarianten = '[av]'
`;