Compare commits

..

2 Commits

39 changed files with 9015 additions and 223 deletions

View File

@@ -0,0 +1,12 @@
CREATE TABLE "siloAdjustments" (
"lsiloAdjust_id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"level" integer,
"locationID" integer,
"currentStockLevel" numeric,
"newLevel" numeric,
"comments" text DEFAULT '',
"dateAdjusted" timestamp DEFAULT now(),
"lastDateAdjusted" timestamp DEFAULT now(),
"statusMessage" text DEFAULT '',
"add_user" text DEFAULT 'LST_Serivce'
);

View File

@@ -0,0 +1,5 @@
ALTER TABLE "siloAdjustments" ADD COLUMN "comment" text DEFAULT '';--> statement-breakpoint
ALTER TABLE "siloAdjustments" ADD COLUMN "commentAddedBy" text;--> statement-breakpoint
ALTER TABLE "siloAdjustments" ADD COLUMN "commentDate" text;--> statement-breakpoint
ALTER TABLE "siloAdjustments" DROP COLUMN "comments";--> statement-breakpoint
ALTER TABLE "siloAdjustments" DROP COLUMN "statusMessage";

View File

@@ -0,0 +1 @@
ALTER TABLE "siloAdjustments" ADD COLUMN "commentKey" text;

View File

@@ -0,0 +1 @@
CREATE UNIQUE INDEX "subModule_name" ON "subModules" USING btree ("name");

View File

@@ -0,0 +1 @@
ALTER TABLE "subModules" ADD COLUMN "icon" text;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -302,6 +302,41 @@
"when": 1743778477759,
"tag": "0042_big_power_pack",
"breakpoints": true
},
{
"idx": 43,
"version": "7",
"when": 1743809547351,
"tag": "0043_free_winter_soldier",
"breakpoints": true
},
{
"idx": 44,
"version": "7",
"when": 1743811709366,
"tag": "0044_hot_smasher",
"breakpoints": true
},
{
"idx": 45,
"version": "7",
"when": 1743819367359,
"tag": "0045_heavy_ravenous",
"breakpoints": true
},
{
"idx": 46,
"version": "7",
"when": 1743821039322,
"tag": "0046_keen_firebird",
"breakpoints": true
},
{
"idx": 47,
"version": "7",
"when": 1743822056329,
"tag": "0047_silky_starbolt",
"breakpoints": true
}
]
}

View File

@@ -0,0 +1,39 @@
import {
text,
pgTable,
numeric,
timestamp,
uuid,
integer,
} from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import { z } from "zod";
export const siloAdjustments = pgTable(
"siloAdjustments",
{
siloAdjust_id: uuid("lsiloAdjust_id").defaultRandom().primaryKey(),
warehouseID: integer("level"),
locationID: integer("locationID"),
currentStockLevel: numeric("currentStockLevel"),
newLevel: numeric("newLevel"),
comment: text("comment").default(""),
dateAdjusted: timestamp("dateAdjusted").defaultNow(),
lastDateAdjusted: timestamp("lastDateAdjusted").defaultNow(),
commentAddedBy: text("commentAddedBy"),
commentDate: text("commentDate"),
commentKey: text("commentKey"),
add_user: text("add_user").default("LST_Serivce"),
},
(table) => [
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
// uniqueIndex("role_name").on(table.name),
]
);
// Schema for inserting a user - can be used to validate API requests
// export const insertRolesSchema = createInsertSchema(roles, {
// name: z.string().min(3, {message: "Role name must be more than 3 letters"}),
// });
// Schema for selecting a Expenses - can be used to validate API responses
export const selectRolesSchema = createSelectSchema(siloAdjustments);

View File

@@ -23,16 +23,17 @@ export const subModules = pgTable(
link: text("link").notNull(),
active: boolean("active").default(false),
roles: jsonb("roles").notNull().default(["systemAdmin"]), // ["view", "technician", "supervisor","manager", "admin","systemAdmin"]
icon: text("icon"),
subSubModule: jsonb("subSubModule").default([]),
add_User: text("add_User").default("LST_System").notNull(),
add_Date: timestamp("add_Date").defaultNow(),
upd_user: text("upd_User").default("LST_System").notNull(),
upd_date: timestamp("upd_date").defaultNow(),
}
// (table) => [
// // uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
// uniqueIndex("subModule_name").on(table.name),
// ]
},
(table) => [
// uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
uniqueIndex("subModule_name").on(table.name),
]
);
// Schema for inserting a user - can be used to validate API requests

View File

@@ -3,7 +3,7 @@ import { LstCard } from "../extendedUI/LstCard";
import { CardHeader } from "../ui/card";
import { toast } from "sonner";
import { z } from "zod";
import { useRouter } from "@tanstack/react-router";
import { useRouter, useSearch } from "@tanstack/react-router";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Label } from "../ui/label";
@@ -12,153 +12,160 @@ import { Checkbox } from "../ui/checkbox";
import { Button } from "../ui/button";
const FormSchema = z.object({
username: z.string().min(1, "You must enter a valid username"),
password: z.string().min(4, "You must enter a valid password"),
rememberMe: z.boolean(),
username: z.string().min(1, "You must enter a valid username"),
password: z.string().min(4, "You must enter a valid password"),
rememberMe: z.boolean(),
});
const LoginForm = () => {
const { setSession } = useSessionStore();
const rememeberMe = localStorage.getItem("rememberMe") === "true";
const username = localStorage.getItem("username") || "";
const router = useRouter();
const {
register,
handleSubmit,
control,
formState: { errors },
} = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
username: username || "",
password: "",
rememberMe: rememeberMe,
},
});
const onSubmitLogin = async (value: z.infer<typeof FormSchema>) => {
// Do something with form data
// first update the rememberMe incase it was selected
if (value.rememberMe) {
localStorage.setItem("rememberMe", value.rememberMe.toString());
localStorage.setItem("username", value.username);
} else {
localStorage.removeItem("rememberMe");
localStorage.removeItem("username");
}
try {
const response = await fetch("/api/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
const { setSession } = useSessionStore();
const rememeberMe = localStorage.getItem("rememberMe") === "true";
const username = localStorage.getItem("username") || "";
const router = useRouter();
const search = useSearch({ from: "/login" });
const {
register,
handleSubmit,
control,
formState: { errors },
} = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
username: username || "",
password: "",
rememberMe: rememeberMe,
},
body: JSON.stringify({
username: value.username,
password: value.password,
}),
});
});
const data = await response.json();
const onSubmitLogin = async (value: z.infer<typeof FormSchema>) => {
// Do something with form data
// Store token in localStorage
// localStorage.setItem("auth_token", data.data.token);
if (data.success) {
const prod = btoa(`${value.username.toLowerCase()}:${value.password}`);
const prodUser = { ...data.user, prod: prod };
// first update the rememberMe incase it was selected
if (value.rememberMe) {
localStorage.setItem("rememberMe", value.rememberMe.toString());
localStorage.setItem("username", value.username);
} else {
localStorage.removeItem("rememberMe");
localStorage.removeItem("username");
}
setSession(prodUser, data.token);
toast.success(`You are logged in as ${data.user.username}`);
router.navigate({ to: "/" });
}
try {
const response = await fetch("/api/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: value.username,
password: value.password,
}),
});
if (!data.success) {
toast.error(`${data.message}`);
}
const data = await response.json();
//console.log(data);
} catch (err) {
toast.error("Invalid credentials");
}
};
// Store token in localStorage
// localStorage.setItem("auth_token", data.data.token);
if (data.success) {
const prod = btoa(
`${value.username.toLowerCase()}:${value.password}`
);
const prodUser = { ...data.user, prod: prod };
return (
<div className="ml-[25%]">
<LstCard className="p-3 w-96">
<CardHeader>
<div>
<p className="text-2xl">Login to LST</p>
</div>
</CardHeader>
<hr className="rounded"></hr>
<form onSubmit={handleSubmit(onSubmitLogin)}>
<div>
<Label htmlFor="username" className="m-1">
Username
</Label>
<Input
placeholder="smith001"
{...register("username")}
className={errors.username ? "border-red-500" : ""}
aria-invalid={!!errors.username}
/>
{errors.username && (
<p className="text-red-500 text-sm mt-1">
{errors.username.message}
</p>
)}
</div>
<div>
<>
<Label htmlFor={"password"} className="m-1">
Password
</Label>
<Input
type="password"
{...register("password")}
className={errors.password ? "border-red-500" : ""}
aria-invalid={!!errors.password}
/>
{errors.password && (
<p className="text-red-500 text-sm mt-1">
{errors.password.message}
</p>
)}
</>
</div>
<div className="flex justify-between pt-2">
<div className="flex">
<Controller
render={({ field }) => (
<>
<Checkbox
id="remember"
checked={field.value}
onCheckedChange={field.onChange}
/>
<label
htmlFor="remember"
className="pl-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
remember me
</label>
</>
)}
control={control}
name="rememberMe"
defaultValue={rememeberMe}
/>
</div>
setSession(prodUser, data.token);
toast.success(`You are logged in as ${data.user.username}`);
<div className="flex justify-end">
<Button type="submit">Submit</Button>
</div>
</div>
</form>
</LstCard>
</div>
);
console.log(search.redirect ? search.redirect : "oops");
router.history.push(search.redirect ? search.redirect : "/");
}
if (!data.success) {
toast.error(`${data.message}`);
}
//console.log(data);
} catch (err) {
toast.error("Invalid credentials");
}
};
return (
<div className="ml-[25%]">
<LstCard className="p-3 w-96">
<CardHeader>
<div>
<p className="text-2xl">Login to LST</p>
</div>
</CardHeader>
<hr className="rounded"></hr>
<form onSubmit={handleSubmit(onSubmitLogin)}>
<div>
<Label htmlFor="username" className="m-1">
Username
</Label>
<Input
placeholder="smith001"
{...register("username")}
className={errors.username ? "border-red-500" : ""}
aria-invalid={!!errors.username}
/>
{errors.username && (
<p className="text-red-500 text-sm mt-1">
{errors.username.message}
</p>
)}
</div>
<div>
<>
<Label htmlFor={"password"} className="m-1">
Password
</Label>
<Input
type="password"
{...register("password")}
className={
errors.password ? "border-red-500" : ""
}
aria-invalid={!!errors.password}
/>
{errors.password && (
<p className="text-red-500 text-sm mt-1">
{errors.password.message}
</p>
)}
</>
</div>
<div className="flex justify-between pt-2">
<div className="flex">
<Controller
render={({ field }) => (
<>
<Checkbox
id="remember"
checked={field.value}
onCheckedChange={field.onChange}
/>
<label
htmlFor="remember"
className="pl-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
remember me
</label>
</>
)}
control={control}
name="rememberMe"
defaultValue={rememeberMe}
/>
</div>
<div className="flex justify-end">
<Button type="submit">Submit</Button>
</div>
</div>
</form>
</LstCard>
</div>
);
};
export default LoginForm;

View File

@@ -1,4 +1,4 @@
import { Cylinder, Package, Truck } from "lucide-react";
//import { Cylinder, Package, Truck } from "lucide-react";
import {
SidebarGroup,
SidebarGroupContent,
@@ -9,67 +9,68 @@ import {
} from "../../ui/sidebar";
import { hasPageAccess } from "@/utils/userAccess";
import { User } from "@/types/users";
import { useSubModuleStore } from "@/lib/store/useSubModuleStore";
// this will need to be moved to a links section the db to make it more easy to remove and add
const items = [
{
title: "Silo Adjustments",
url: "#",
icon: Cylinder,
role: ["admin", "systemAdmin"],
module: "logistics",
active: true,
},
{
name: "Bulk orders",
moduleName: "logistics",
description: "",
link: "#",
icon: Truck,
role: ["systemAdmin"],
active: true,
subSubModule: [],
},
{
name: "Forecast",
moduleName: "logistics",
description: "",
link: "#",
icon: Truck,
role: ["systemAdmin"],
active: true,
subSubModule: [],
},
{
name: "Ocme cycle counts",
moduleName: "logistics",
description: "",
link: "#",
icon: Package,
role: ["technician", "supervisor", "manager", "admin", "systemAdmin"],
active: false,
subSubModule: [],
},
{
name: "Material Helper",
moduleName: "logistics",
description: "",
link: "/materialHelper/consumption",
icon: Package,
role: ["technician", "supervisor", "manager", "admin", "systemAdmin"],
active: true,
subSubModule: [],
},
{
name: "Ocme Cyclecount",
moduleName: "logistics",
description: "",
link: "/cyclecount",
icon: Package,
role: ["technician", "supervisor", "manager", "admin", "systemAdmin"],
active: true,
subSubModule: [],
},
];
// const items = [
// {
// title: "Silo Adjustments",
// url: "#",
// icon: Cylinder,
// role: ["admin", "systemAdmin"],
// module: "logistics",
// active: true,
// },
// {
// name: "Bulk orders",
// moduleName: "logistics",
// description: "",
// link: "#",
// icon: Truck,
// role: ["systemAdmin"],
// active: true,
// subSubModule: [],
// },
// {
// name: "Forecast",
// moduleName: "logistics",
// description: "",
// link: "#",
// icon: Truck,
// role: ["systemAdmin"],
// active: true,
// subSubModule: [],
// },
// {
// name: "Ocme cycle counts",
// moduleName: "logistics",
// description: "",
// link: "#",
// icon: Package,
// role: ["technician", "supervisor", "manager", "admin", "systemAdmin"],
// active: false,
// subSubModule: [],
// },
// {
// name: "Material Helper",
// moduleName: "logistics",
// description: "",
// link: "/materialHelper/consumption",
// icon: Package,
// role: ["technician", "supervisor", "manager", "admin", "systemAdmin"],
// active: true,
// subSubModule: [],
// },
// {
// name: "Ocme Cyclecount",
// moduleName: "logistics",
// description: "",
// link: "/cyclecount",
// icon: Package,
// role: ["technician", "supervisor", "manager", "admin", "systemAdmin"],
// active: true,
// subSubModule: [],
// },
// ];
export function LogisticsSideBar({
user,
@@ -78,26 +79,35 @@ export function LogisticsSideBar({
user: User | null;
moduleID: string;
}) {
const { subModules } = useSubModuleStore();
const items = subModules.filter((m) => m.moduleName === "logistics");
console.log(items);
return (
<SidebarGroup>
<SidebarGroupLabel>Logistics</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<>
{hasPageAccess(user, item.role, moduleID) &&
item.active && (
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
)}
</>
</SidebarMenuItem>
))}
{items.map((item) => {
return (
<SidebarMenuItem key={item.submodule_id}>
<>
{hasPageAccess(
user,
item.roles,
moduleID
) &&
item.active && (
<SidebarMenuButton asChild>
<a href={item.link}>
<span>{item.name}</span>
</a>
</SidebarMenuButton>
)}
</>
</SidebarMenuItem>
);
})}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>

View File

@@ -0,0 +1,120 @@
import { LstCard } from "@/components/extendedUI/LstCard";
import { Button } from "@/components/ui/button";
import { CardContent, CardFooter, CardHeader } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { useForm } from "@tanstack/react-form";
import { useRouter } from "@tanstack/react-router";
import axios from "axios";
import { useState } from "react";
import { toast } from "sonner";
export default function Comment(data: any) {
const token = localStorage.getItem("auth_token");
const [isSubmitting, setIsSubmitting] = useState(false);
const router = useRouter();
const form = useForm({
defaultValues: {
comment: "",
},
onSubmit: async ({ value }) => {
setIsSubmitting(true);
try {
const res = await axios.post(
`/api/logistics/postcomment/${data.id.split("&")[0]}`,
{
comment: value.comment,
key: data.id.split("&")[1],
},
{ headers: { Authorization: `Bearer ${token}` } }
);
if (res.data.success) {
toast.success(res.data.message);
form.reset();
router.navigate({ to: "/siloAdjustments" });
}
if (!res.data.success) {
toast.error(res.data.message);
form.reset();
}
} catch (error) {
console.log(error);
toast.error(`There was an error posting your comment.`);
}
setIsSubmitting(false);
},
});
return (
<div className="">
<LstCard>
<CardHeader>
Please enter your comment for the silo adjust
</CardHeader>
<CardContent>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<form.Field
name="comment"
validators={{
// We can choose between form-wide and field-specific validators
onChange: ({ value }) =>
value.length > 10
? undefined
: "Comment must be longer than 10 characters.",
}}
children={(field) => {
return (
<div className="m-2 min-w-48 max-w-96 p-2">
<Label
htmlFor="comment"
className="mb-2"
>
Comment
</Label>
<Textarea
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
//type="number"
onChange={(e) =>
field.handleChange(
e.target.value
)
}
/>
{field.state.meta.errors.length ? (
<em>
{field.state.meta.errors.join(
","
)}
</em>
) : null}
</div>
);
}}
/>
</form>
</CardContent>
<CardFooter>
<div className="flex justify-end">
<Button
onClick={form.handleSubmit}
disabled={isSubmitting}
>
Submit
</Button>
</div>
</CardFooter>
</LstCard>
</div>
);
}

View File

@@ -3,6 +3,7 @@ import { useModuleStore } from "../../lib/store/useModuleStore";
import { useEffect } from "react";
import { useSettingStore } from "@/lib/store/useSettings";
import { useGetUserRoles } from "@/lib/store/useGetRoles";
import { useSubModuleStore } from "@/lib/store/useSubModuleStore";
const queryClient = new QueryClient();
@@ -14,11 +15,13 @@ export const SessionProvider = ({
const { fetchModules } = useModuleStore();
const { fetchSettings } = useSettingStore();
const { fetchUserRoles } = useGetUserRoles();
const { fetchSubModules } = useSubModuleStore();
useEffect(() => {
fetchModules();
fetchSettings();
fetchUserRoles();
fetchSubModules();
}, []);
return (
<QueryClientProvider client={queryClient}>

View File

@@ -0,0 +1,30 @@
import { SubModules } from "@/types/modules";
import axios from "axios";
import { create } from "zustand";
interface SettingState {
subModules: SubModules[];
fetchSubModules: () => Promise<void>;
setSubModules: (modules: SubModules[]) => void;
}
interface FetchModulesResponse {
data: SubModules[];
}
export const useSubModuleStore = create<SettingState>()((set) => ({
subModules: [],
setSubModules: (subModules) => set({ subModules }),
fetchSubModules: async () => {
try {
//const response = await axios.get<{data: Setting[]}>(`${process.env.NEXT_PUBLIC_URL}/api/settings/client`);
const response = await axios.get(`/api/server/submodules`, {});
const data: FetchModulesResponse = response.data; //await response.json();
//console.log(data);
set({ subModules: data.data });
} catch (error) {
console.error("Failed to fetch settings:", error);
set({ subModules: [] });
}
},
}));

View File

@@ -25,10 +25,12 @@ import { Route as AdminSettingsImport } from './routes/_admin/settings'
import { Route as AdminServersImport } from './routes/_admin/servers'
import { Route as AdminModulesImport } from './routes/_admin/modules'
import { Route as ocmeCyclecountIndexImport } from './routes/(ocme)/cyclecount/index'
import { Route as logisticsSiloAdjustmentsIndexImport } from './routes/(logistics)/siloAdjustments/index'
import { Route as logisticsMaterialHelperIndexImport } from './routes/(logistics)/materialHelper/index'
import { Route as EomArticleAvImport } from './routes/_eom/article/$av'
import { Route as logisticsMaterialHelperSiloLinkIndexImport } from './routes/(logistics)/materialHelper/siloLink/index'
import { Route as logisticsMaterialHelperConsumptionIndexImport } from './routes/(logistics)/materialHelper/consumption/index'
import { Route as logisticsSiloAdjustmentsCommentCommentImport } from './routes/(logistics)/siloAdjustments/comment/$comment'
// Create/Update Routes
@@ -113,6 +115,13 @@ const ocmeCyclecountIndexRoute = ocmeCyclecountIndexImport.update({
getParentRoute: () => rootRoute,
} as any)
const logisticsSiloAdjustmentsIndexRoute =
logisticsSiloAdjustmentsIndexImport.update({
id: '/(logistics)/siloAdjustments/',
path: '/siloAdjustments/',
getParentRoute: () => rootRoute,
} as any)
const logisticsMaterialHelperIndexRoute =
logisticsMaterialHelperIndexImport.update({
id: '/(logistics)/materialHelper/',
@@ -140,6 +149,13 @@ const logisticsMaterialHelperConsumptionIndexRoute =
getParentRoute: () => rootRoute,
} as any)
const logisticsSiloAdjustmentsCommentCommentRoute =
logisticsSiloAdjustmentsCommentCommentImport.update({
id: '/(logistics)/siloAdjustments/comment/$comment',
path: '/siloAdjustments/comment/$comment',
getParentRoute: () => rootRoute,
} as any)
// Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' {
@@ -249,6 +265,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof logisticsMaterialHelperIndexImport
parentRoute: typeof rootRoute
}
'/(logistics)/siloAdjustments/': {
id: '/(logistics)/siloAdjustments/'
path: '/siloAdjustments'
fullPath: '/siloAdjustments'
preLoaderRoute: typeof logisticsSiloAdjustmentsIndexImport
parentRoute: typeof rootRoute
}
'/(ocme)/cyclecount/': {
id: '/(ocme)/cyclecount/'
path: '/cyclecount'
@@ -256,6 +279,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ocmeCyclecountIndexImport
parentRoute: typeof rootRoute
}
'/(logistics)/siloAdjustments/comment/$comment': {
id: '/(logistics)/siloAdjustments/comment/$comment'
path: '/siloAdjustments/comment/$comment'
fullPath: '/siloAdjustments/comment/$comment'
preLoaderRoute: typeof logisticsSiloAdjustmentsCommentCommentImport
parentRoute: typeof rootRoute
}
'/(logistics)/materialHelper/consumption/': {
id: '/(logistics)/materialHelper/consumption/'
path: '/materialHelper/consumption'
@@ -327,7 +357,9 @@ export interface FileRoutesByFullPath {
'/ocp': typeof OcpIndexRoute
'/article/$av': typeof EomArticleAvRoute
'/materialHelper': typeof logisticsMaterialHelperIndexRoute
'/siloAdjustments': typeof logisticsSiloAdjustmentsIndexRoute
'/cyclecount': typeof ocmeCyclecountIndexRoute
'/siloAdjustments/comment/$comment': typeof logisticsSiloAdjustmentsCommentCommentRoute
'/materialHelper/consumption': typeof logisticsMaterialHelperConsumptionIndexRoute
'/materialHelper/siloLink': typeof logisticsMaterialHelperSiloLinkIndexRoute
}
@@ -346,7 +378,9 @@ export interface FileRoutesByTo {
'/ocp': typeof OcpIndexRoute
'/article/$av': typeof EomArticleAvRoute
'/materialHelper': typeof logisticsMaterialHelperIndexRoute
'/siloAdjustments': typeof logisticsSiloAdjustmentsIndexRoute
'/cyclecount': typeof ocmeCyclecountIndexRoute
'/siloAdjustments/comment/$comment': typeof logisticsSiloAdjustmentsCommentCommentRoute
'/materialHelper/consumption': typeof logisticsMaterialHelperConsumptionIndexRoute
'/materialHelper/siloLink': typeof logisticsMaterialHelperSiloLinkIndexRoute
}
@@ -368,7 +402,9 @@ export interface FileRoutesById {
'/ocp/': typeof OcpIndexRoute
'/_eom/article/$av': typeof EomArticleAvRoute
'/(logistics)/materialHelper/': typeof logisticsMaterialHelperIndexRoute
'/(logistics)/siloAdjustments/': typeof logisticsSiloAdjustmentsIndexRoute
'/(ocme)/cyclecount/': typeof ocmeCyclecountIndexRoute
'/(logistics)/siloAdjustments/comment/$comment': typeof logisticsSiloAdjustmentsCommentCommentRoute
'/(logistics)/materialHelper/consumption/': typeof logisticsMaterialHelperConsumptionIndexRoute
'/(logistics)/materialHelper/siloLink/': typeof logisticsMaterialHelperSiloLinkIndexRoute
}
@@ -389,7 +425,9 @@ export interface FileRouteTypes {
| '/ocp'
| '/article/$av'
| '/materialHelper'
| '/siloAdjustments'
| '/cyclecount'
| '/siloAdjustments/comment/$comment'
| '/materialHelper/consumption'
| '/materialHelper/siloLink'
fileRoutesByTo: FileRoutesByTo
@@ -407,7 +445,9 @@ export interface FileRouteTypes {
| '/ocp'
| '/article/$av'
| '/materialHelper'
| '/siloAdjustments'
| '/cyclecount'
| '/siloAdjustments/comment/$comment'
| '/materialHelper/consumption'
| '/materialHelper/siloLink'
id:
@@ -427,7 +467,9 @@ export interface FileRouteTypes {
| '/ocp/'
| '/_eom/article/$av'
| '/(logistics)/materialHelper/'
| '/(logistics)/siloAdjustments/'
| '/(ocme)/cyclecount/'
| '/(logistics)/siloAdjustments/comment/$comment'
| '/(logistics)/materialHelper/consumption/'
| '/(logistics)/materialHelper/siloLink/'
fileRoutesById: FileRoutesById
@@ -442,7 +484,9 @@ export interface RootRouteChildren {
LoginRoute: typeof LoginRoute
OcpIndexRoute: typeof OcpIndexRoute
logisticsMaterialHelperIndexRoute: typeof logisticsMaterialHelperIndexRoute
logisticsSiloAdjustmentsIndexRoute: typeof logisticsSiloAdjustmentsIndexRoute
ocmeCyclecountIndexRoute: typeof ocmeCyclecountIndexRoute
logisticsSiloAdjustmentsCommentCommentRoute: typeof logisticsSiloAdjustmentsCommentCommentRoute
logisticsMaterialHelperConsumptionIndexRoute: typeof logisticsMaterialHelperConsumptionIndexRoute
logisticsMaterialHelperSiloLinkIndexRoute: typeof logisticsMaterialHelperSiloLinkIndexRoute
}
@@ -456,7 +500,10 @@ const rootRouteChildren: RootRouteChildren = {
LoginRoute: LoginRoute,
OcpIndexRoute: OcpIndexRoute,
logisticsMaterialHelperIndexRoute: logisticsMaterialHelperIndexRoute,
logisticsSiloAdjustmentsIndexRoute: logisticsSiloAdjustmentsIndexRoute,
ocmeCyclecountIndexRoute: ocmeCyclecountIndexRoute,
logisticsSiloAdjustmentsCommentCommentRoute:
logisticsSiloAdjustmentsCommentCommentRoute,
logisticsMaterialHelperConsumptionIndexRoute:
logisticsMaterialHelperConsumptionIndexRoute,
logisticsMaterialHelperSiloLinkIndexRoute:
@@ -481,7 +528,9 @@ export const routeTree = rootRoute
"/login",
"/ocp/",
"/(logistics)/materialHelper/",
"/(logistics)/siloAdjustments/",
"/(ocme)/cyclecount/",
"/(logistics)/siloAdjustments/comment/$comment",
"/(logistics)/materialHelper/consumption/",
"/(logistics)/materialHelper/siloLink/"
]
@@ -551,9 +600,15 @@ export const routeTree = rootRoute
"/(logistics)/materialHelper/": {
"filePath": "(logistics)/materialHelper/index.tsx"
},
"/(logistics)/siloAdjustments/": {
"filePath": "(logistics)/siloAdjustments/index.tsx"
},
"/(ocme)/cyclecount/": {
"filePath": "(ocme)/cyclecount/index.tsx"
},
"/(logistics)/siloAdjustments/comment/$comment": {
"filePath": "(logistics)/siloAdjustments/comment/$comment.tsx"
},
"/(logistics)/materialHelper/consumption/": {
"filePath": "(logistics)/materialHelper/consumption/index.tsx"
},

View File

@@ -0,0 +1,34 @@
import Comment from "@/components/logistics/siloAdjustments/Comment";
import { createFileRoute, redirect } from "@tanstack/react-router";
export const Route = createFileRoute(
"/(logistics)/siloAdjustments/comment/$comment"
)({
beforeLoad: async () => {
const auth = localStorage.getItem("auth_token");
if (!auth) {
throw redirect({
to: "/login",
search: {
// Use the current location to power a redirect after login
// (Do not use `router.state.resolvedLocation` as it can
// potentially lag behind the actual current location)
redirect: location.pathname + location.search,
},
});
}
},
// In a loader
loader: ({ params }) => params.comment,
// Or in a component
component: RouteComponent,
});
function RouteComponent() {
const { comment } = Route.useParams();
return (
<div className="ml-20 mt-20">
<Comment id={comment} />
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/(logistics)/siloAdjustments/')({
component: RouteComponent,
})
function RouteComponent() {
return <div>Hello "/(logistics)/siloAdjustments/"!</div>
}

View File

@@ -1,4 +1,4 @@
import {createFileRoute, redirect} from "@tanstack/react-router";
import { createFileRoute, redirect } from "@tanstack/react-router";
// src/routes/_authenticated.tsx
export const Route = createFileRoute("/_admin")({
@@ -7,6 +7,12 @@ export const Route = createFileRoute("/_admin")({
if (!auth) {
throw redirect({
to: "/login",
search: {
// Use the current location to power a redirect after login
// (Do not use `router.state.resolvedLocation` as it can
// potentially lag behind the actual current location)
redirect: location.pathname + location.search,
},
});
}
},

View File

@@ -1,4 +1,4 @@
import {createFileRoute, redirect} from "@tanstack/react-router";
import { createFileRoute, redirect } from "@tanstack/react-router";
// src/routes/_authenticated.tsx
export const Route = createFileRoute("/_auth")({
@@ -7,6 +7,12 @@ export const Route = createFileRoute("/_auth")({
if (!auth) {
throw redirect({
to: "/login",
search: {
// Use the current location to power a redirect after login
// (Do not use `router.state.resolvedLocation` as it can
// potentially lag behind the actual current location)
redirect: location.href,
},
});
}
},

View File

@@ -1,6 +1,7 @@
import {createFileRoute, redirect} from "@tanstack/react-router";
import { createFileRoute, redirect } from "@tanstack/react-router";
import LoginForm from "@/components/auth/LoginForm";
import { z } from "zod";
export const Route = createFileRoute("/login")({
component: RouteComponent,
@@ -12,6 +13,9 @@ export const Route = createFileRoute("/login")({
});
}
},
validateSearch: z.object({
redirect: z.string().optional(),
}),
});
function RouteComponent() {

View File

@@ -8,3 +8,13 @@ export interface Modules {
upd_user: string;
upd_date: Date;
}
export interface SubModules {
submodule_id: string;
name: string;
link: string;
icon: string;
moduleName: string;
active: boolean;
roles: string[];
}

View File

@@ -0,0 +1,32 @@
//import { Input } from "@/components/ui/input";
//import { Label } from "@radix-ui/react-dropdown-menu";
// export const FormInput = (form: any, label: string) => {
// // <form.Field
// // name="username"
// // validators={{
// // // We can choose between form-wide and field-specific validators
// // onChange: ({ value }) =>
// // value.length > 3
// // ? undefined
// // : "Username must be longer than 3 letters",
// // }}
// // children={(field) => {
// // return (
// // <div className="m-2 min-w-48 max-w-96 p-2">
// // <Label htmlFor="username">{label}</Label>
// // <Input
// // name={field.name}
// // value={field.state.value}
// // onBlur={field.handleBlur}
// // //type="number"
// // onChange={(e) => field.handleChange(e.target.value)}
// // />
// // {field.state.meta.errors.length ? (
// // <em>{field.state.meta.errors.join(",")}</em>
// // ) : null}
// // </div>
// // );
// // }}
// // />;
// };

View File

@@ -0,0 +1,12 @@
import { getHours } from "date-fns";
export const greetingStuff = async (date = new Date()) => {
const hour = getHours(date);
if (hour < 12) {
return "Good morning";
} else if (hour < 18) {
return "Good afternoon";
} else {
return "Good evening";
}
};

View File

@@ -0,0 +1,11 @@
import crypto from "crypto";
export const generateOneTimeKey = async (length = 32) => {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let key = "";
const bytes = crypto.randomBytes(length);
for (let i = 0; i < length; i++) {
key += chars[bytes[i] % chars.length];
}
return key.match(/.{1,4}/g)!.join("-"); // group by 4 chars
};

View File

@@ -0,0 +1,130 @@
import { db } from "../../../../../database/dbclient.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
import { query } from "../../../sqlServer/prodSqlServer.js";
import { siloQuery } from "../../../sqlServer/querys/silo/siloQuery.js";
import { postAdjustment } from "./postAdjustment.js";
import { siloAdjustments } from "../../../../../database/schema/siloAdjustments.js";
import { greetingStuff } from "../../../../globalUtils/greetingEmail.js";
import { sendEmail } from "../../../notifications/controller/sendMail.js";
import { settings } from "../../../../../database/schema/settings.js";
import { generateOneTimeKey } from "../../../../globalUtils/singleUseKey.js";
import { eq } from "drizzle-orm";
export const createSiloAdjustment = async (
data: any | null,
user: any | null
) => {
/**
* Creates a silo adjustment based off warehouse, location, and qty.
* qty will come from the hmi, prolink, or silo patrol
*/
const { data: set, error: setError } = await tryCatch(
db.select().from(settings)
);
if (setError) {
return {
success: false,
message: `There was an error getting setting data to post to the server.`,
data: setError,
};
}
// getting stock data first so we have it prior to the adjustment
const { data: stock, error: stockError } = await tryCatch(
query(siloQuery, "Silo data Query")
);
if (stockError) {
return {
success: false,
message: `There was an error getting stock data to post to the server.`,
data: stockError,
};
}
const { data: a, error: errorAdj } = await tryCatch(
postAdjustment(data, user.prod)
);
if (errorAdj) {
return {
success: false,
message: `There was an error doing the silo adjustment.`,
data: errorAdj,
};
}
/**
* Checking to see the difference, and send email if +/- 5% will change later if needed
*/
const stockNummy = stock.filter((s: any) => s.LocationID === data.laneId);
const theDiff =
((data.quantity - stockNummy[0].Stock_Total) /
((data.quantity + stockNummy[0].Stock_Total) / 2)) *
100;
/**
* Post the data to our db.
*/
//console.log(stockNummy);
const { data: postAdj, error: postAdjError } = await tryCatch(
db
.insert(siloAdjustments)
.values({
warehouseID: data.warehouseId,
locationID: data.laneId,
currentStockLevel: stockNummy[0].Stock_Total,
newLevel: data.quantity,
lastDateAdjusted: new Date(stockNummy[0].LastAdjustment),
add_user: user.username,
})
.returning({ id: siloAdjustments.siloAdjust_id })
);
if (postAdjError) {
//console.log(postAdjError);
return {
success: false,
message: `There was an error posting the new adjustment.`,
data: postAdjError,
};
}
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");
const port = set.filter((n: any) => n.name === "serverPort");
const key = await generateOneTimeKey();
const updateKey = await db
.update(siloAdjustments)
.set({ commentKey: key })
.where(eq(siloAdjustments.siloAdjust_id, postAdj[0].id));
const emailSetup = {
email: user.email,
subject: `Alert - Siloadjustment was done with a descrepancy of 5% or greater`,
template: "siloAdjustmentComment",
context: {
greeting: await greetingStuff(),
siloName: stockNummy[0].Description,
variance: `${theDiff.toFixed(2)}%`,
currentLevel: stockNummy[0].Stock_Total,
newLevel: data.quantity,
variancePer: 5,
adjustID: `${postAdj[0].id}&${key}`,
server: server[0].value,
port: port[0].value,
},
};
//console.log(emailSetup);
await sendEmail(emailSetup);
}
let adj: any = a;
return { success: adj.success, message: adj.message, data: adj.data };
};

View File

@@ -0,0 +1,81 @@
import axios from "axios";
import { prodEndpointCreation } from "../../../../globalUtils/createUrl.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
export const postAdjustment = async (data: any, prod: any) => {
if (data.warehouseId === undefined) {
return {
sucess: false,
message: `Missing mandatory field: warehouseID`,
data: { error: `Missing mandatory field: warehouseID` },
};
}
if (data.laneId === undefined) {
return {
sucess: false,
message: `Missing mandatory field: locationID`,
data: { error: `Missing mandatory field: locationID` },
};
}
if (data.quantity == "0") {
return {
sucess: false,
message: `You entered 0 for the quantity to post, quantity needs to be at leave 1`,
data: {
error: `You entered 0 for the quantity to post, quantity needs to be at leave 1`,
},
};
}
const siloAdjustment = {
warehouseId: data.warehouseId,
laneId: data.laneId,
quantity: data.quantity,
};
let url = await prodEndpointCreation(
"/public/v1.0/Warehousing/AdjustSiloStockLevel"
);
const { data: silo, error } = await tryCatch(
axios.post(url, siloAdjustment, {
headers: { Authorization: `Basic ${prod}` },
})
);
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 (silo.status !== 200) {
return {
success: false,
message: "Error in posting the silo adjustment",
data: {
status: silo.status,
statusText: silo.statusText,
data: silo.data,
},
};
}
return {
success: true,
message: "Adjustment was completed",
data: {
status: silo.status,
statusText: silo.statusText,
data: silo.data,
},
};
};

View File

@@ -0,0 +1,64 @@
import { eq, sql } from "drizzle-orm";
import { db } from "../../../../../database/dbclient.js";
import { siloAdjustments } from "../../../../../database/schema/siloAdjustments.js";
import { tryCatch } from "../../../../globalUtils/tryCatch.js";
export const postSiloComment = async (
id: string,
comment: string,
commentk: string,
user: any
) => {
/**
* We will add the comment to the silo adjustment so we know the why we had this.
*/
// make sure we havea valid key
const { data: key, error: keyErro } = await tryCatch(
db
.select()
.from(siloAdjustments)
.where(eq(siloAdjustments.siloAdjust_id, id))
);
if (keyErro) {
return {
success: false,
message: "There was an error getting the adjustment.",
data: keyErro,
};
}
if (key[0].commentKey != commentk) {
return {
success: false,
message: "The key you provided is invalid.",
data: keyErro,
};
}
const { data, error } = await tryCatch(
db
.update(siloAdjustments)
.set({
comment: comment,
commentAddedBy: user.username,
commentDate: sql`NOW()`,
commentKey: null,
})
.where(eq(siloAdjustments.siloAdjust_id, id))
);
if (error) {
return {
success: false,
message: "There was an error adding the comment.",
data: error,
};
}
return {
success: true,
message: "Comment was successfully added.",
};
};

View File

@@ -2,9 +2,18 @@ import { OpenAPIHono } from "@hono/zod-openapi";
import comsumeMaterial from "./route/consumeMaterial.js";
import returnMat from "./route/returnMaterial.js";
import createSiloAdjustment from "./route/siloAdjustments/createSiloAdjustment.js";
import postComment from "./route/siloAdjustments/postComment.js";
const app = new OpenAPIHono();
const routes = [comsumeMaterial, returnMat] as const;
const routes = [
comsumeMaterial,
returnMat,
// silo
createSiloAdjustment,
postComment,
] as const;
// app.route("/server", modules);
const appRoutes = routes.forEach((route) => {

View File

@@ -0,0 +1,64 @@
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import { verify } from "hono/jwt";
import { authMiddleware } from "../../../auth/middleware/authMiddleware.js";
import { responses } from "../../../../globalUtils/routeDefs/responses.js";
import { createSiloAdjustment } from "../../controller/siloAdjustments/createSiloAdjustment.js";
const app = new OpenAPIHono();
const responseSchema = z.object({
success: z.boolean().optional().openapi({ example: true }),
message: z.string().optional().openapi({ example: "user access" }),
});
app.openapi(
createRoute({
tags: ["logistics"],
summary: "Creates silo adjustmennt",
method: "post",
path: "/createsiloadjustment",
middleware: authMiddleware,
description:
"Creates a silo adjustment for the silo if and stores the stock numbers.",
responses: responses(),
}),
async (c) => {
//apiHit(c, { endpoint: "api/sqlProd/close" });
const authHeader = c.req.header("Authorization");
const token = authHeader?.split("Bearer ")[1] || "";
try {
const payload = await verify(token, process.env.JWT_SECRET!);
try {
//return apiReturn(c, true, access?.message, access?.data, 200);
const data = await c.req.json();
const createSiloAdj = await createSiloAdjustment(
data,
payload.user
);
return c.json(
{
success: createSiloAdj.success,
message: createSiloAdj.message,
data: createSiloAdj.data,
},
200
);
} catch (error) {
//console.log(error);
//return apiReturn(c, false, "Error in setting the user access", error, 400);
return c.json(
{
success: false,
message: "Missing data please try again",
error,
},
400
);
}
} catch (error) {
return c.json({ success: false, message: "Unauthorized" }, 401);
}
}
);
export default app;

View File

@@ -0,0 +1,85 @@
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
import { verify } from "hono/jwt";
import { authMiddleware } from "../../../auth/middleware/authMiddleware.js";
import { responses } from "../../../../globalUtils/routeDefs/responses.js";
import { createSiloAdjustment } from "../../controller/siloAdjustments/createSiloAdjustment.js";
import { postSiloComment } from "../../controller/siloAdjustments/postComment.js";
const app = new OpenAPIHono();
const ParamsSchema = z.object({
adjId: z
.string()
.min(3)
.openapi({
param: {
name: "adjId",
in: "path",
},
example: "3b555052-a960-4301-8d38-a6f1acb98dbe",
}),
});
const Body = z.object({
comment: z
.string()
.openapi({ example: "Reason to why i had a badd adjustment." }),
});
app.openapi(
createRoute({
tags: ["logistics"],
summary: "Post a comment to why you had a discrepancy",
method: "post",
path: "/postcomment/:adjId",
middleware: authMiddleware,
request: {
params: ParamsSchema,
body: { content: { "application/json": { schema: Body } } },
},
// description:
// "Creates a silo adjustment for the silo if and stores the stock numbers.",
responses: responses(),
}),
async (c: any) => {
//apiHit(c, { endpoint: "api/sqlProd/close" });
const authHeader = c.req.header("Authorization");
const token = authHeader?.split("Bearer ")[1] || "";
const { adjId } = c.req.valid("param");
try {
const payload = await verify(token, process.env.JWT_SECRET!);
try {
//return apiReturn(c, true, access?.message, access?.data, 200);
const data = await c.req.json();
const addComment = await postSiloComment(
adjId,
data.comment,
data.key,
payload.user
);
return c.json(
{
success: addComment.success,
message: addComment.message,
data: addComment.data,
},
200
);
} catch (error) {
return c.json(
{
success: false,
message: "Missing data please try again",
error,
},
400
);
}
} catch (error) {
return c.json({ success: false, message: "Unauthorized" }, 401);
}
}
);
export default app;

View File

@@ -0,0 +1,41 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{{!--<title>Order Summary</title> --}}
{{> styles}}
<style>
pre {
background-color: #f8f9fa;
color: #d63384;
padding: 10px;
border-radius: 5px;
white-space: pre-wrap;
font-family: monospace;
}
</style>
{{!-- <link rel="stylesheet" href="styles/styles.css" /> --}}
</head>
<body>
<p>
{{greeting}},<br/><br/>
A silo adjustment was just completed on {{siloName}}, with a variation of {{variance}}.<br/><br/>
The data that was passed over.<br/><br/>
Current stock at the time of the adjustment: {{currentLevel}}.<br/><br/>
What was entered as the new number: {{newLevel}}<br/><br/>
Please add your comment as to why the variance greater than {{variancePer}}<br/><br/>
<a href="http://{{server}}:5173/siloAdjustments/comment/{{adjustID}}"
style="display:inline-block; padding:10px 20px; text-decoration:none; border-radius:5px;">
Add a Comment
</a><br/><br/>
Best regards,<br/><br/>
LST team<br/>
</p>
</body>
</html>

View File

@@ -0,0 +1,73 @@
import { z, createRoute, OpenAPIHono } from "@hono/zod-openapi";
import { modules } from "../../../../../database/schema/modules.js";
import { db } from "../../../../../database/dbclient.js";
import { subModules } from "../../../../../database/schema/subModules.js";
// Define the request body schema
const requestSchema = z.object({
ip: z.string().optional(),
endpoint: z.string().optional(),
action: z.string().optional(),
stats: z.string().optional(),
});
// Define the response schema
const responseSchema = z.object({
message: z.string().optional(),
module_id: z
.string()
.openapi({ example: "6c922c6c-7de3-4ec4-acb0-f068abdc" })
.optional(),
name: z.string().openapi({ example: "Production" }).optional(),
active: z.boolean().openapi({ example: true }).optional(),
roles: z
.string()
.openapi({ example: `["viewer","technician"]` })
.optional(),
});
const app = new OpenAPIHono();
app.openapi(
createRoute({
tags: ["server"],
summary: "Returns all submodules in the server",
method: "get",
path: "/submodules",
responses: {
200: {
content: {
"application/json": { schema: responseSchema },
},
description: "Response message",
},
},
}),
async (c) => {
//console.log("system modules");
let module: any = [];
try {
module = await db.select().from(subModules); // .where(eq(modules.active, true));
} catch (error) {
console.log(error);
module = [];
}
// parse the roles
const updateModules = module.map((m: any) => {
if (m.roles) {
return { ...m, roles: m?.roles };
}
return m;
}); //JSON.parse(module[0]?.roles);
// Return response with the received data
return c.json({
message: `All active submodules`,
data: module,
});
}
);
export default app;

View File

@@ -15,6 +15,7 @@ import updateServer from "./route/updates/updateServer.js";
import { setPerms } from "./utils/testServerPerms.js";
import serviceControl from "./route/servers/serverContorl.js";
import { areSubModulesIn } from "./utils/subModuleCheck.js";
import getSubmodules from "./route/modules/getSubModules.js";
// making sure all modules are in properly
setTimeout(async () => {
@@ -31,6 +32,7 @@ const routes = [
getModules,
updateModule,
addModule,
getSubmodules,
// settings
addSetting,
getSettings,

View File

@@ -12,7 +12,7 @@ const newSubModules = [
name: "Silo Adjustmnet",
moduleName: "logistics",
description: "Do a silo adjustmnet",
link: "/sa",
link: "/siloAdjustments",
icon: "Cylinder",
active: false,
roles: ["tester", "systemAdmin"],
@@ -156,6 +156,7 @@ export const areSubModulesIn = async () => {
roles: newSubModules[i].roles,
link: newSubModules[i].link,
subSubModule: newSubModules[i].subSubModule,
icon: newSubModules[i].icon,
},
}) // this will only update the ones that are new :D
.returning({ name: subModules.name });
@@ -167,6 +168,7 @@ export const areSubModulesIn = async () => {
"SubModules were just added due to missing them on server startup"
);
} catch (error) {
console.log(error);
createLog(
"error",
"lst",

View File

@@ -0,0 +1,26 @@
export const siloQuery = `
SELECT
V_LagerAbteilungen.Bezeichnung AS Description,
V_LagerAbteilungen.IdWarenLager AS WarehouseID,
V_LagerAbteilungen.IdLagerAbteilung AS LocationID,
ROUND(SUM(einlagerungsmengesum), 2) AS Stock_Total,
COALESCE(LastAdjustment, '1900-01-01') AS LastAdjustment
FROM AlplaPROD_test1.dbo.V_LagerAbteilungen (NOLOCK)
JOIN
AlplaPROD_test1.dbo.V_LagerPositionenBarcodes ON
AlplaPROD_test1.dbo.V_LagerAbteilungen.IdLagerAbteilung =
AlplaPROD_test1.dbo.V_LagerPositionenBarcodes.IdLagerAbteilung
LEFT JOIN (
SELECT
IdLagerAbteilung,
MAX(CASE WHEN CONVERT(CHAR(10), Buchungsdatum, 120) IS NULL THEN '1900-01-01' ELSE CONVERT(CHAR(10), Buchungsdatum, 120) END) AS LastAdjustment
FROM AlplaPROD_test1.dbo.V_LagerBuchungen (NOLOCK)
WHERE urheber = 2900
GROUP BY IdLagerAbteilung
) AS LastAdj ON AlplaPROD_test1.dbo.V_LagerAbteilungen.IdLagerAbteilung = LastAdj.IdLagerAbteilung
WHERE materialsilo = 1
AND aktiv = 1
GROUP BY V_LagerAbteilungen.Bezeichnung, V_LagerAbteilungen.IdWarenLager, V_LagerAbteilungen.IdLagerAbteilung, LastAdjustment
ORDER BY V_LagerAbteilungen.Bezeichnung
`;