Compare commits

...

8 Commits

27 changed files with 2121 additions and 70 deletions

View File

@@ -0,0 +1,2 @@
ALTER TABLE "siloAdjustments" RENAME COLUMN "lsiloAdjust_id" TO "siloAdjust_id";--> statement-breakpoint
ALTER TABLE "printerData" ALTER COLUMN "printDelay" SET DEFAULT '90';

File diff suppressed because it is too large Load Diff

View File

@@ -337,6 +337,13 @@
"when": 1743822056329,
"tag": "0047_silky_starbolt",
"breakpoints": true
},
{
"idx": 48,
"version": "7",
"when": 1743946411873,
"tag": "0048_tearful_bushwacker",
"breakpoints": true
}
]
}

View File

@@ -12,7 +12,7 @@ import { z } from "zod";
export const siloAdjustments = pgTable(
"siloAdjustments",
{
siloAdjust_id: uuid("lsiloAdjust_id").defaultRandom().primaryKey(),
siloAdjust_id: uuid("siloAdjust_id").defaultRandom().primaryKey(),
warehouseID: integer("level"),
locationID: integer("locationID"),
currentStockLevel: numeric("currentStockLevel"),

View File

@@ -8,7 +8,6 @@ import {
jsonb,
} from "drizzle-orm/pg-core";
import { createSelectSchema } from "drizzle-zod";
import { modules } from "./modules.js";
//import {z} from "zod";
export const subModules = pgTable(

View File

@@ -1,14 +1,181 @@
import { LstCard } from "@/components/extendedUI/LstCard";
import { Button } from "@/components/ui/button";
import { CardHeader } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { getStockSilo } from "@/utils/querys/logistics/siloAdjustments/getStockSilo";
import { useForm } from "@tanstack/react-form";
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import { format } from "date-fns";
import { CircleAlert } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
export default function SiloCard(data: any) {
const token = localStorage.getItem("auth_token");
const [submitting, setSubmitting] = useState(false);
const { refetch } = useQuery(getStockSilo());
const silo = data.silo;
const form = useForm({
defaultValues: {
newLevel: "",
},
onSubmit: async ({ value }) => {
setSubmitting(true);
const dataToSubmit = {
quantity: parseFloat(value.newLevel),
warehouseId: silo.WarehouseID,
laneId: silo.LocationID,
};
try {
const res = await axios.post(
"/api/logistics/createsiloadjustment",
dataToSubmit,
{ headers: { Authorization: `Bearer ${token}` } }
);
console.log(res.data);
if (res.data.success) {
toast.success(res.data.message);
refetch();
form.reset();
}
if (!res.data.success && res.data.data?.status === 400) {
if (res.data.data.status === 400) {
toast.error(res.data.data.data.errors[0].message);
}
} else if (!res.data.success) {
toast.error(res.data.message);
}
setSubmitting(false);
} catch (error: any) {
//console.log(error);
if (error.status === 401) {
toast.error(error.response.statusText);
setSubmitting(false);
}
}
},
});
return (
<LstCard>
<CardHeader>{silo.Description}</CardHeader>
<div>
<hr />
<div className="flex flex-row">
<LstCard className="grow m-1 max-w-[400px]">
<CardHeader>{silo.Description}</CardHeader>
<div className="m-1">
<hr className="m-2" />
<span>Current Stock: </span>
{silo.Stock_Total}
<hr className="m-2" />
<span>Last date adjusted </span>
{format(silo.LastAdjustment, "M/dd/yyyy")}
<hr className="m-2" />
</div>
<div>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<form.Field
name="newLevel"
validators={{
// We can choose between form-wide and field-specific validators
onChange: ({ value }) =>
value.length > 1
? undefined
: "You must enter a value greate than 1",
}}
children={(field) => {
return (
<div className="m-2 min-w-48 max-w-96 p-2">
<div className="flex flex-row">
<Label htmlFor="newLevel">
New level
</Label>
<div>
<Disclaimer />
</div>
</div>
<div className="flex flex-row">
<Input
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
type="decimal"
onChange={(e) =>
field.handleChange(
e.target.value
)
}
/>
<Button
className="ml-1"
type="submit"
onClick={form.handleSubmit}
disabled={submitting}
>
{submitting ? (
<span className="w-24">
Submitting...
</span>
) : (
<span className="w-24">
Submit
</span>
)}
</Button>
</div>
{field.state.meta.errors.length ? (
<em>
{field.state.meta.errors.join(
","
)}
</em>
) : null}
</div>
);
}}
/>
</form>
</div>
</LstCard>
<div className="grow max-w-[400px]">
<LstCard className="m-1 ">charts go here</LstCard>
<LstCard className="m-1">extra options here</LstCard>
</div>
</div>
</LstCard>
);
}
const Disclaimer = () => {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<CircleAlert className="ml-1 w-[14px]" />
</TooltipTrigger>
<TooltipContent className="max-w-48">
<p className="text-pretty">
If you have had this page open for a period of time
before submitting your data, there is a chance that the
stock levels will be different from the ones you see
above
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
};

View File

@@ -19,8 +19,12 @@ export default function SiloPage() {
);
return (
<div className="flex flex-row gap-2">
{data?.map((s: any) => <SiloCard silo={s} />)}
<div className="flex flex-wrap">
{data?.map((s: any) => (
<div key={s.LocationID} className="grow m-2 max-w-[800px]">
<SiloCard silo={s} />
</div>
))}
</div>
);
}

View File

@@ -5,6 +5,7 @@ import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
@@ -137,6 +138,16 @@ export default function LabelLog() {
))}
</TableBody>
)}
<TableFooter>
{labelData.length === 0 && (
<div>
<h2 className="text-center text-2xl">
No labels have been printed in the last 2 hours
</h2>
</div>
)}
</TableFooter>
</Table>
</LstCard>
);

View File

@@ -71,6 +71,7 @@ export default function Lots() {
const server = settings.filter((n) => n.name === "server")[0]?.value || "";
const roles = ["admin", "manager", "operator"];
const lotdata = data ? data : [];
if (user && roles.includes(user.role)) {
//width = 1280;
@@ -144,7 +145,6 @@ export default function Lots() {
</div>
);
}
return (
<LstCard className="m-2 p-2 min-h-2/5">
<ScrollArea className="h-[400px]">
@@ -197,7 +197,7 @@ export default function Lots() {
</>
) : (
<TableBody>
{data?.map((lot: LotType) => (
{lotdata.map((lot: LotType) => (
<TableRow key={lot.LabelOnlineID}>
<TableCell className="font-medium">
{lot.MachineLocation}

View File

@@ -10,6 +10,7 @@ export default function OCPPage() {
const { settings } = useSettingStore();
const server = settings.filter((n) => n.plantToken === "usday1");
console.log(server);
return (
<div className="h-screen w-full ">
<div className="flex flex-wrap gap-2">
@@ -40,7 +41,7 @@ export default function OCPPage() {
</div>
</div>
<div className="w-1/6 flex flex-col">
{server && (
{server.length >= 1 && (
<div>
<WrapperManualTrigger />
</div>

View File

@@ -9,12 +9,10 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import { getPrinters } from "@/utils/querys/production/printers";
import { useQuery } from "@tanstack/react-query";
let printerCols = [
{
key: "status",
label: "Status",
},
{
key: "printer",
label: "Printer",
@@ -25,10 +23,12 @@ let printerCols = [
},
];
export default function PrinterStatus() {
return (
<LstCard className="m-2 p-2">
<ScrollArea className="max-h-[300px]">
<p className="text-center">Printer Status</p>
const { data, isError, isLoading } = useQuery(getPrinters());
if (isError) {
return (
<ScrollArea className="h-[400px]">
<p className="text-center">Printer Staus error</p>
<Table>
<TableHeader>
@@ -50,14 +50,65 @@ export default function PrinterStatus() {
<TableCell>
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</ScrollArea>
);
}
/**
* only show the assigned printers
*/
const assigned = data?.filter((a: any) => a.assigned) || [];
return (
<LstCard className="m-2 p-2">
<ScrollArea className="max-h-[800px]">
<p className="text-center">
{isLoading ? (
<span>Printers status loading...</span>
) : (
<span>Printer Status</span>
)}
</p>
<Table>
<TableHeader>
<TableRow>
{printerCols.map((l) => (
<TableHead key={l.key}>{l.label}</TableHead>
))}
</TableRow>
</TableHeader>{" "}
{isLoading ? (
<TableBody>
{Array(5)
.fill(0)
.map((_, i) => (
<TableRow key={i}>
<TableCell className="font-medium">
<Skeleton className="h-4" />
</TableCell>
<TableCell>
<Skeleton className="h-4" />
</TableCell>
</TableRow>
))}
</TableBody>
) : (
<TableBody>
{assigned?.map((p: any) => (
<TableRow key={p.printer_id}>
<TableCell>{p.name}</TableCell>
<TableCell>{p.statusText}</TableCell>
</TableRow>
))}
</TableBody>
)}
</Table>
</ScrollArea>
</LstCard>
);
}

View File

@@ -0,0 +1,20 @@
import { queryOptions } from "@tanstack/react-query";
import axios from "axios";
export function getPrinters() {
return queryOptions({
queryKey: ["printers"],
queryFn: () => fetchSettings(),
staleTime: 1000,
refetchInterval: 2 * 2000,
refetchOnWindowFocus: true,
});
}
const fetchSettings = async () => {
const { data } = await axios.get(`/api/ocp/getprinters`);
// if we are not localhost ignore the devDir setting.
//const url: string = window.location.host.split(":")[0];
return data.data ?? [];
};

View File

@@ -35,7 +35,7 @@
}
},
"admConfig": {
"build": 171,
"build": 174,
"oldBuild": "backend-0.1.3.zip"
},
"devDependencies": {
@@ -88,4 +88,4 @@
"ws": "^8.18.1",
"zod": "^3.24.2"
}
}
}

View File

@@ -24,6 +24,7 @@ const transport = pino.transport({
const log: Logger = pino(
{
level: process.env.LOG_LEVEL || logLevel,
//level: "debug",
// formatters: {
// level: (label) => {
// return {level: label.toUpperCase()};

View File

@@ -58,6 +58,17 @@ export const createSiloAdjustment = async (
* Checking to see the difference, and send email if +/- 5% will change later if needed
*/
const sa: any = a;
if (!sa.success) {
console.log(`insde error`);
return {
success: sa.success,
message: sa.message,
data: sa.data,
};
}
const stockNummy = stock.filter((s: any) => s.LocationID === data.laneId);
const theDiff =
((data.quantity - stockNummy[0].Stock_Total) /
@@ -91,7 +102,7 @@ export const createSiloAdjustment = async (
data: postAdjError,
};
}
let adj: any = a;
if (Math.abs(theDiff) > 5) {
// console.log(`Send for comment due to being: ${theDiff.toFixed(2)}%`);
const server = set.filter((n: any) => n.name === "server");
@@ -123,8 +134,14 @@ export const createSiloAdjustment = async (
//console.log(emailSetup);
await sendEmail(emailSetup);
return {
success: adj.success,
message: `Silo adjustmnet was completed you will also receive and email due to the adjustment having a variation of ${Math.abs(
theDiff
).toFixed(2)}%`,
data: adj.data,
};
} else {
return { success: adj.success, message: adj.message, data: adj.data };
}
let adj: any = a;
return { success: adj.success, message: adj.message, data: adj.data };
};

View File

@@ -45,22 +45,45 @@ export const postAdjustment = async (data: any, prod: any) => {
})
);
let e = error as any;
if (error) {
return {
success: false,
message: "Error in posting the silo adjustment.",
data: {
status: e.response?.status,
statusText: e.response?.statusText,
data: e.response?.data,
},
};
if (e) {
if (e.status === 401) {
const data = {
success: false,
message: "Incorrect alpla prod password.",
data: {
status: e.response?.status,
statusText: e.response?.statusText,
data: e.response?.data,
},
};
return data;
} else {
return {
success: false,
message: "Error in posting the silo adjustment.",
data: {
status: e.response?.status,
statusText: e.response?.statusText,
data: e.response?.data,
},
};
}
}
if (silo.status !== 200) {
if (silo?.status !== 200) {
return {
success: false,
message: "Error in posting the silo adjustment",
data: {
status: silo?.status,
statusText: silo?.statusText,
data: silo?.data,
},
};
} else {
return {
success: true,
message: "Adjustment was completed",
data: {
status: silo.status,
statusText: silo.statusText,
@@ -68,14 +91,4 @@ export const postAdjustment = async (data: any, prod: any) => {
},
};
}
return {
success: true,
message: "Adjustment was completed",
data: {
status: silo.status,
statusText: silo.statusText,
data: silo.data,
},
};
};

View File

@@ -36,6 +36,7 @@ app.openapi(
data,
payload.user
);
return c.json(
{
success: createSiloAdj.success,

View File

@@ -4,13 +4,23 @@ import { settings } from "../../../../../database/schema/settings.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { createLog } from "../../../logger/logger.js";
import { getPrinters } from "./getPrinters.js";
import { printerStatus } from "./printerStatus.js";
import { autoLabelingStats, printerStatus } from "./printerStatus.js";
let isPrinterCycling = false;
let actualPrinterCycle: any;
export const printerCycle = async () => {
/**
* We will cycle through the printers to check there states.
*/
if (isPrinterCycling)
return {
success: false,
message: "Printers are already being cycled.",
};
createLog("info", "ocp", "ocp", "Printer cycle has started.");
// get the printers
const { data: s, error: se } = await tryCatch(
db.select().from(settings).where(eq(settings.name, "ocpCycleDelay"))
@@ -29,9 +39,9 @@ export const printerCycle = async () => {
}
const ocpDelay: any = s;
isPrinterCycling = true;
// start the actual printer cycle
const actualPrinterCycle = setInterval(async () => {
actualPrinterCycle = setInterval(async () => {
const { data, error } = await tryCatch(getPrinters());
if (error) {
@@ -51,10 +61,8 @@ export const printerCycle = async () => {
// only keep the assigned ones
printers = printers.filter((p: any) => p.assigned === true);
// for autolabelers like dayton and MCD we want to ignore them from the loop as well.
printers = printers.filter(
(p: any) => p.name != "Autolabeler" && !p.remark.includes("ignore")
);
// for printers we want to ignore there must be a remark stateing to ignore.
printers = printers.filter((p: any) => !p.remark.includes("ignore"));
printers.forEach(async (p: any) => {
/**
@@ -75,7 +83,31 @@ export const printerCycle = async () => {
return;
}
if (p.name === "Autolabeler") {
await autoLabelingStats(p);
return;
}
// for all other printers
await printerStatus(p);
});
}, parseInt(ocpDelay[0]?.value) * 1000);
return { success: true, message: "Printer cycle has been started." };
};
export const stopPrinterCycle = async () => {
/**
* We will stop the print cylce this is more an emergancy thing.
*/
if (actualPrinterCycle && !actualPrinterCycle._destroyed) {
createLog("info", "ocp", "ocp", "Printer cycle is being stopped.");
clearInterval(actualPrinterCycle);
isPrinterCycling = false;
return { success: true, message: "Printer cycle has been stopped." };
} else {
createLog("info", "ocp", "ocp", "Printer cycle is already stopped.");
isPrinterCycling = false;
return { success: true, message: "Printer cycle is already Stopped." };
}
};

View File

@@ -8,6 +8,7 @@ export const printStatus = [
{ code: 2, text: "Pending labels" },
{ code: 3, text: "Printing to fast" },
{ code: 4, text: "Creating label" },
{ code: 5, text: "Waiting" },
{ code: 6, text: "Printer Paused" },
{ code: 7, text: "Printer error" },
{

View File

@@ -68,9 +68,9 @@ export const printerStatus = async (p: any) => {
"debug",
"ocp",
"ocp",
`${p.name}: timeBetween: ${timeBetween}, delay ${
p.printDelay || 90
}, ${currentTime}... ${lastTime}`
`${p.name}: timeBetween: ${timeBetween}, delay ${parseInt(
p.printDelay
)}, ${currentTime}... ${lastTime}`
);
if (tmp[2] === "0" && tmp[4] !== "000") {
@@ -80,7 +80,7 @@ export const printerStatus = async (p: any) => {
"ocp",
"ocp",
`Unpaused and printing labels, time remaing ${differenceInSeconds(
p.printDelay || 90,
parseInt(p.printDelay),
timeBetween
)}`
);
@@ -96,7 +96,7 @@ export const printerStatus = async (p: any) => {
`${
p.name
} paused to soon, unpausing, remaining time: ${differenceInSeconds(
p.printDelay || 90,
parseInt(p.printDelay),
timeBetween
)}`
);
@@ -105,20 +105,20 @@ export const printerStatus = async (p: any) => {
printerUpdate(p, 2);
unPausePrinter(p);
} else if ((tmp[2] === "0" && timeBetween < p.printDelay) || 90) {
} else if (tmp[2] === "0" && timeBetween < parseInt(p.printDelay)) {
// was unpaused to soon so repause it
createLog(
"debug",
"ocp",
"ocp",
`${p.name} Unpaused before the time allowed, time left ${
differenceInSeconds(p.printDelay || 90, timeBetween) //seconds
differenceInSeconds(parseInt(p.printDelay), timeBetween) //seconds
}`
);
printerUpdate(p, 3);
pausePrinter(p);
} else if ((tmp[2] === "0" && timeBetween > p.printDelay) || 90) {
} else if (tmp[2] === "0" && timeBetween > parseInt(p.printDelay)) {
// its been long enough we can print a label
createLog(
"debug",
@@ -188,3 +188,68 @@ export const printerStatus = async (p: any) => {
});
});
};
export const autoLabelingStats = async (p: any) => {
/**
* Checks autolabeling printers just to see what they are doing.
*/
createLog("debug", "ocp", "ocp", `Printer cycling`);
const printer = new net.Socket();
return new Promise((resolve) => {
// connect to the printer, and check its status
printer.connect(p.port, p.ipAddress, async () => {
// write the message to the printer below gives us a feedback of the printer
printer.write("~HS");
});
// read the data from the printer
printer.on("data", async (data) => {
const res = data.toString();
// turn the data into an array to make it more easy to deal with
const tmp = res.split(",");
if (tmp[4] !== "000") {
// unpaused and printing labels - reset timer
createLog("debug", "ocp", "ocp", `Printing Labels`);
// update last time printed in the array
printerUpdate(p, 1);
}
if (tmp[4] === "000") {
// unpaused and printing labels - reset timer
createLog("debug", "ocp", "ocp", `Printing Labels`);
// update last time printed in the array
printerUpdate(p, 5);
}
});
printer.on("error", async (error) => {
// just going to say theres an error with the printer
if (!errorCheck) {
createLog(
"error",
"ocp",
"ocp",
`${p.name} encountered an error: ${error}`
);
}
await printerUpdate(p, 7);
errorCheck = true;
// send log data
// fake line
printer.end();
resolve({
success: false,
message: "There was an error with the printer.",
});
});
});
};

View File

@@ -57,6 +57,7 @@ export const updatePrinters = async () => {
port: prodPrinterInfo[i].port,
remark: prodPrinterInfo[i].remark,
upd_date: sql`NOW()`,
printDelay: "90", // need to remove in a couple weeks
},
})
);

View File

@@ -15,7 +15,8 @@ import dycoClose from "./routes/specialProcesses/dyco/closeConnection.js";
import manualprint from "./routes/labeling/manualPrint.js";
import { assignedPrinters } from "./utils/checkAssignments.js";
import { printerCycle } from "./controller/printers/printerCycle.js";
import { tryCatch } from "../../globalUtils/tryCatch.js";
import stopPrinterCycle from "./routes/printers/stopCycle.js";
import startPrinterCycle from "./routes/printers/startCycle.js";
const app = new OpenAPIHono();
@@ -24,6 +25,8 @@ const routes = [
//printer
getPrinters,
updateprinters,
startPrinterCycle,
stopPrinterCycle,
// lots
getLots,
// labeling
@@ -55,21 +58,21 @@ const ocpActive = setting.filter((n) => n.name === "ocpActive");
// do the intnal connection to the dyco
setTimeout(() => {
if (dycoActive[0].value === "1") {
if (dycoActive[0]?.value === "1") {
dycoConnect();
}
}, 3 * 1000);
// check for printers being assigned
setInterval(() => {
if (ocpActive[0].value === "1") {
if (ocpActive[0]?.value === "1") {
assignedPrinters();
}
}, 60 * 1000);
// start the printer process after everything else is up ad running
setTimeout(async () => {
if (ocpActive[0].value === "1") {
if (ocpActive[0]?.value === "1") {
await updatePrinters();
await assignedPrinters();
printerCycle();

View File

@@ -0,0 +1,41 @@
// an external way to creating logs
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import { authMiddleware } from "../../../auth/middleware/authMiddleware.js";
import { responses } from "../../../../globalUtils/routeDefs/responses.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { printerCycle } from "../../controller/printers/printerCycle.js";
const app = new OpenAPIHono({ strict: false });
app.openapi(
createRoute({
tags: ["ocp:printers"],
summary: "starts the printers cycling.",
method: "get",
path: "/startsprintercycle",
middleware: authMiddleware,
//description: "This might be a temp soltuin during the transtion between versions",
// request: {
// body: {content: {"application/json": {schema: CreateLog}}},
// },
responses: responses(),
}),
async (c) => {
const { data, error } = await tryCatch(printerCycle());
const dataError: any = error;
if (error) {
return c.json({
success: false,
message: "Error in stopping the printer cycle",
data: dataError?.data,
});
}
const getData: any = data;
return c.json({
success: getData?.success,
message: getData?.message,
data: getData.data ?? [],
});
}
);
export default app;

View File

@@ -0,0 +1,41 @@
// an external way to creating logs
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import { authMiddleware } from "../../../auth/middleware/authMiddleware.js";
import { responses } from "../../../../globalUtils/routeDefs/responses.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { stopPrinterCycle } from "../../controller/printers/printerCycle.js";
const app = new OpenAPIHono({ strict: false });
app.openapi(
createRoute({
tags: ["ocp:printers"],
summary: "Stops the printers cycling.",
method: "get",
path: "/stopprintercycle",
middleware: authMiddleware,
//description: "This might be a temp soltuin during the transtion between versions",
// request: {
// body: {content: {"application/json": {schema: CreateLog}}},
// },
responses: responses(),
}),
async (c) => {
const { data, error } = await tryCatch(stopPrinterCycle());
const dataError: any = error;
if (error) {
return c.json({
success: false,
message: "Error in stopping the printer cycle",
data: dataError?.data,
});
}
const getData: any = data;
return c.json({
success: getData?.success,
message: getData?.message,
data: getData.data ?? [],
});
}
);
export default app;

View File

@@ -8,7 +8,7 @@ import { getPrinters } from "../controller/printers/getPrinters.js";
import { createLog } from "../../logger/logger.js";
export const assignedPrinters = async () => {
createLog("info", "ocp", "ocp", "Lot assignment check");
createLog("debug", "ocp", "ocp", "Lot assignment check");
const { data: l, error: lotError } = await tryCatch(getLots());
if (lotError) {

View File

@@ -194,7 +194,7 @@ const newSettings = [
// ocp
{
name: "acpActive",
name: "ocpActive",
value: `1`,
description: "Are we pritning on demand?",
serviceBelowsTo: "ocp",

View File

@@ -9,7 +9,7 @@ import { createLog } from "../../logger/logger.js";
// "view", "technician", "supervisor","manager", "admin", "systemAdmin"
const newSubModules = [
{
name: "Silo Adjustmnet",
name: "Silo Adjustments",
moduleName: "logistics",
description: "Do a silo adjustmnet",
link: "/siloAdjustments",